• #Chrome拡張
  • #Twitter
  • #Node.js
  • #Volta
  • #日記自動生成
開発daily-logメモ

2026年2月8日のサイドプロジェクト

この日は3つのプロジェクトをまたいで作業した。Chrome拡張機能への新機能追加、古いNuxt 2プロジェクトの環境問題の解決、そしてmdx-playgroundの日記自動生成コマンドの実行。それぞれ別のコンテキストだが、どれも日常的に使っているツールのメンテナンスと改善という点で共通している。


1. Chrome拡張機能(chrome-extension-x): フォロー中リツイートタブの追加

やりたかったこと

X(旧Twitter)のタイムラインに、「フォロー中のユーザーのリツイートだけ」を表示するカスタムタブを追加したかった。X の検索機能では filter:follows include:nativeretweets というクエリを使えばフォロー中のネイティブリツイートを抽出できるが、毎回手動で検索するのは面倒なので、ホームや検索ページにタブとして常設したかった。

実装内容

既存のChrome拡張機能(X/Twitter用の動画ダウンロード機能を持つもの)に、カスタムタブバーを追加する機能を実装した。

タブをクリックすると、内部的に filter:follows include:nativeretweets の検索クエリを最新順(Latest)で実行し、その結果を表示する仕組み。X のUIに溶け込む形で、既存のタブバーの近くにカスタムタブを挿入する。

バグ1: タブバーの挿入位置が検索タブと横並びになる

最初の実装では、カスタムタブバーが検索ページの既存タブ(「おすすめ」「最新」など)と同じ行に横並びで表示されてしまった。

原因は、DOM上の挿入位置が ScrollSnap-SwipeableList(検索タブを含む横スクロールコンテナ)の内部になっていたこと。ScrollSnap-SwipeableList の親要素に insertBefore していたが、その親がまだ横スクロールコンテナの中だった。

// NG: 検索タブのコンテナ内に入ってしまう
const tabList = document.querySelector('[data-testid="ScrollSnap-SwipeableList"]');
tabList.parentElement.insertBefore(customTabBar, tabList.nextSibling);

修正では、DOMツリーをもっと上に遡り、primaryColumn の直接の子要素(ヘッダーセクション)を特定して、その後に挿入するようにした。検索結果エリアとヘッダーの間にカスタムタブバーが入る形になる。

// OK: primaryColumnの直下に挿入
const primaryColumn = document.querySelector('[data-testid="primaryColumn"]');
const headerSection = primaryColumn.querySelector(':scope > div > div');
// ヘッダーセクションの後に挿入
headerSection.parentElement.insertBefore(customTabBar, headerSection.nextSibling);

X のDOMは data-testid が使えるので要素の特定はやりやすいが、ネスト構造が深いのでどの階層に挿入するかの見極めが必要になる。

バグ2: ホームページでのラベルとアクティブ状態の問題

検索ページでは正常に動作したが、ホームページに移動すると2つの問題が発生した。

1つ目は、カスタムタブのラベルが「フォロー中」のまま変わらないこと。ホームページには既存の「おすすめ」「フォロー中」タブがあり、カスタムタブにも「フォロー中」と表示されてしまうと区別がつかない。span.textContent でラベルを書き換えているはずだったが、X のReact再レンダリングによって上書きされていた。

2つ目は、タブのアクティブ状態がコピーされてしまうこと。既存タブをクローンしてカスタムタブを作っていたが、クローン元が aria-selected="true" のアクティブタブだったため、カスタムタブも最初からアクティブに見えてしまっていた。

修正のポイントは以下の2つ。

  • クローン元として aria-selected="false"(非アクティブ)のタブを選択するようにした
  • MutationObserver でラベルの変更を監視し、Reactが再レンダリングしてもすぐにラベルを再設定するようにした

X のようなSPAの中に要素を注入するChrome拡張は、Reactの仮想DOM管理と戦うことになる。MutationObserver で変更を監視して再適用するパターンは定番だが、パフォーマンスに注意が必要になる。


2. schliemann: Nuxt 2プロジェクトのNode.js環境修正

背景

schliemannはNuxt 2で構築された既存のプロジェクト。このプロジェクトの中にお問い合わせページ(/tax/contact/?id=4)があり、その内容を確認する必要があった。

問題: Node 22でOpenSSL 3.0エラー

プロジェクトを起動しようとしたところ、npm run dev:eurekapu がエラーで失敗した。

このプロジェクトは package.jsonscripts 内で NODE_OPTIONS=--openssl-legacy-provider を指定している。これはNode 17以降でOpenSSL 3.0がデフォルトになったことへの対処で、古いwebpackが使うハッシュアルゴリズム(MD4など)を有効にするためのオプション。

ところが、現在のメイン環境はNode 22(v22.17.0)で、Node 22ではこのオプション自体がさらに制限されており、NODE_OPTIONS 経由での --openssl-legacy-provider がブロックされる。結果としてwebpackのビルドがOpenSSL関連のエラーで停止した。

解決策: Volta経由でNode 14を使う

schliemannプロジェクトの package.json にはVoltaの設定が含まれており、Node 14.21.2が指定されている。

{
  "volta": {
    "node": "14.21.2"
  }
}

通常であればプロジェクトディレクトリに入るだけでVoltaが自動的にNode 14に切り替えてくれるが、Git Bash環境ではVoltaのシム(shim)が正しく動作しないケースがあった。node --version を実行してもv22.17.0が返ってくる状態だった。

対処として、volta run コマンドを使って明示的にNode 14を指定して起動した。

volta run --node 14 npm run dev:eurekapu

これでNode 14.21.2が使われ、--openssl-legacy-provider も問題なく動作し、開発サーバーが http://localhost:3000/tax/ で正常に起動した。

お問い合わせページの確認

開発サーバーが起動した後、目的だったお問い合わせページの場所を確認した。

  • URL: http://localhost:3000/tax/contact/?id=4
  • ファイルパス: pages/contact.vue
  • /tax/ 部分は nuxt.config.jsrouter.base で設定されたルートベースであり、Vueファイル自体は pages/contact.vue
  • ?id=4 はクエリパラメータとして $route.query.id で受け取る形

Nuxt 2のルーティングはファイルベースなので、ページファイルの場所さえわかれば修正は簡単。ただし、このプロジェクトをメンテナンスするたびにNode環境の問題に遭遇するのは厄介で、将来的にはNuxt 3への移行を検討する必要がある。


3. mdx-playground: 日記自動生成コマンドの実行

/make-diary コマンドで2026-02-07の日記を自動生成

mdx-playgroundには /make-diary というカスタムコマンドがあり、claude-code-sync/ ディレクトリに蓄積されたClaude Codeの作業ログから、自動的に日記記事を生成する。

この日は、2026-02-07分の日記生成を実行した。

処理の流れ

  1. claude-code-sync/2026-02-07/ 配下のログファイルを全件取得
  2. 各ログを読み込み、プロジェクトごとにカテゴリ分け
  3. カテゴリごとにサブエージェントを並列起動し、詳細記事を生成
  4. 全記事の完成後、統合日記(その日の作業概要をまとめた記事)を作成
  5. frontmatterの整合性チェックとpathリンターによる検証

生成された記事

最初のスキャンでは4件のログが見つかったが、再スキャンすると合計16件のログが存在していた。sync-once.sh(ログ収集スクリプト)の実行タイミングによっては一部が取得されないことがある。

最終的に5本の詳細記事が生成された。

記事内容
book-knowledge-base関連PDF OCRからフロントエンド構築まで
書籍ビューアのスクロール修正ページめくり時のスクロール位置リセット
YomiToku OCR変換PDFからMarkdownへの変換処理
消費税仕訳パターンビューア伝票テンプレートの表示機能
LP業種別ランディングページ業種別のランディングページ

これらは全て apps/web/content/2026-02-07/ に保存され、frontmatterの project_namecategorytodo フィールドも自動で付与された。content.config.ts のenum値との整合性も確認済み。

日記自動生成の仕組みの利点

手動で日記を書く場合、「何をやったか」を思い出す作業がボトルネックになる。Claude Codeの作業ログがそのまま素材になるので、この思い出す工程がなくなる。ログには実際のコマンド実行結果やエラーメッセージも含まれているため、技術的な詳細も正確に記録できる。

一方で、ログが多い日は生成される記事も多くなり、サブエージェントの並列起動数が増える。この日は16件のログから5本の記事を生成しており、カテゴリの統合判断(どのログをまとめて1記事にするか)がうまく機能している。


この日の作業を振り返って

3つのプロジェクトに共通しているのは、「既存のツールを使おうとしたら環境やUIの問題にぶつかり、それを直した」というパターン。Chrome拡張はX のDOM構造との格闘、schliemannはNode.jsバージョンの互換性問題、日記自動生成はログ収集のタイミング問題。どれも機能自体は動いているが、周辺環境の変化に追従する作業が必要だった。

特にschliemannのNode.js問題は、古いプロジェクトを維持するコストの典型例。Node 14はLTSサポートが終了しており、Voltaで固定しているとはいえ、OS側のOpenSSLやnpmのバージョンとの組み合わせで新しい問題が出てくる。使用頻度が低いプロジェクトほど、起動するたびに環境修正が必要になるのは避けられない。