Notes
matchMediaがない実行環境を先に弾かないとテーマフックが落ちる
テーマを "system" で解決するフックを実装するとき、typeof window !== "undefined" だけのガードだと不十分だった。CI の jsdom 実行時に window はあるのに window.matchMedia が未実装で、TypeError でテストが落ちた。
// NG: window はあるので通るが、matchMedia がない環境で落ちる
if (mode === "system" && typeof window !== "undefined") {
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
// OK: API の存在まで確認してからシステムテーマ判定する
const canUseMatchMedia =
typeof window !== "undefined" && typeof window.matchMedia === "function";
if (mode === "system" && canUseMatchMedia) {
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
同じ条件は useEffect 側(matchMedia(...).addEventListener(...))にも必要だった。初期計算だけ守っても、購読処理で同じ例外が起きる。
ポイントは「SSRかどうか」ではなく「使うブラウザAPIがあるか」を直接判定すること。window の有無を条件にすると、テストランナーや擬似ブラウザ実行環境で落とし穴になる。
バックログの未割り当てアイテムを未来スプリントに自動アサインして表示する
バックログには「まだスプリントに割り当てていないアイテム」が常にある。これを「未割り当て」としてフラットに並べるより、velocity をもとに未来のスプリントへ自動アサインして表示した方が計画の見通しがよくなる。
設計:
- 現在のスプリントの残キャパシティと velocity から「Sprint N+1」「Sprint N+2」のような未来スプリントを生成する
- バックログアイテムを優先度順に並べ、velocity に基づいて各スプリントにアサインしていく
- UIには通常のスプリントと同じ形式でグループ表示する
velocity の計算にはイテレーションごとの平均ではなく 直近21日のローリング平均を使った。イテレーション長が変動しても安定した予測になる。
トレードオフ:
- velocity の精度に依存するため、初期フェーズや人員変動が多いチームでは予測が外れやすい
- 「予測」であることをUIで明示しないと、確定スケジュールと混同されやすい
PivotalTracker や Linear でも同様のアプローチが取られており、特に PivotalTracker はこの予測スケジューリングをコアUXとして採用している。バックログの見通しがひと目でわかるので、このスタイルを好んで採用している。velocity ベースの自動スケジューリングは、バックログが積み上がってきたときに特に効果を発揮する。
watchモードしかないスクリプトにワンショットモードを追加してAIエージェント対応する
コード生成系のスクリプトがwatchモードのみで提供されていると、AIエージェントが実行したときにプロセスが終了せず詰まる。
対応:
- 既存スクリプトに
--watchフラグを追加し、デフォルトをワンショット(1回実行して終了)に変更 package.jsonでdev用には:watchサフィックス付きスクリプトを用意
{
"scripts": {
"generate": "ts-node scripts/generate.ts",
"generate:watch": "ts-node scripts/generate.ts --watch",
"build-types": "ts-node scripts/buildTypes.ts",
"build-types:watch": "ts-node scripts/buildTypes.ts --watch",
"dev": "run-p generate:watch build-types:watch"
}
}
ポイント:
- デフォルトをワンショットにすることで、AIエージェントもCIも
--watch=falseを知らなくても使える - 人間の開発体験は
devコマンド経由でwatch版が動くので変わらない - graphql-codegenのように設定ファイル側で
watch: trueがデフォルトのツールもあるので、そちらも同じパターンでワンショット版を分離するとよい
AIエージェント向けドキュメントは「コードから推論できない情報」だけに絞る
モノレポのサブパッケージにCLAUDE.md / AGENTS.mdを追加したとき、最初はディレクトリ構成、開発フロー、実装パターン、コンテキスト型の説明まで盛り込んだ。レビューしたら大半が「コードを読めばわかる」内容だった。
削ぎ落として残したのは3カテゴリだけ:
- 開発コマンド一覧 — パッケージ固有のスクリプト名とその用途
- AIエージェント向けのコマンド使い分け — ワンショット版(CI/AI向け)とwatch版(人間の開発時向け)の違い
- 注意事項 — 生成物の依存関係や実行順序の制約など、コードからは読み取りにくいルール
削った項目:
- ディレクトリ構成 →
lsで見える - 開発フロー(スキーマ定義 → 実装の流れ) → コード読めばわかる
- 主要パターン(実装パターン、ヘルパー、データローダー) → コード読めばわかる
- 型定義の説明 → 型定義ファイルを読めばわかる
判断基準: 「AIがファイルを数個読めば自力で把握できるか?」。Yesなら書かない。ドキュメントに書くべきは、コードに現れない暗黙知(コマンドの使い分けルール、実行順序の制約、生成物の依存関係)だけ。
サブディレクトリのCLAUDE.mdはそのディレクトリ内のファイルに触れたときにオンデマンドで読み込まれるので、コンテキスト圧迫の心配は少ない。ただし冗長なドキュメントはノイズになるので、短い方がよい。
MCPサーバーにおけるOAuthトークンの共有永続化とワークツリー間の認証維持
MCP (Model Context Protocol) サーバーにおいて、作業用ワークツリーごとに認証が切れる問題を、共有ストレージの導入により解決した。
課題
アクセストークンを Git worktree 配下の一時的なディレクトリやローカルのキャッシュフォルダに保持していたため、新しい作業(Story)ごとにワークツリーを作成・移動すると、キャッシュが引き継がれず、毎回 OAuth の認可フローが発生していた。
解決策
- グローバルな共有ストレージへの配置:
- トークンの保存先をリポジトリ(ワークツリー)配下ではなく、ユーザーのホームディレクトリ直下(例: ユーザー設定ディレクトリ)などの、ワークツリーから独立した場所に変更。
- キャッシュの共有:
- 複数の作業ディレクトリ(worktrees)から、単一のグローバルキャッシュを参照するように設計を変更。
- これにより、一度認可を完了すれば、他のどのワークツリーで AI エージェント(Codex, Claude Code 等)を起動しても、同じ認証状態が維持される。
- トークンの自動更新:
- 共有されたリフレッシュトークンを用いて、期限切れの際もバックグラウンドで自動更新し、開発者の手を止めない。
結論
AI エージェントとワークツリーを組み合わせた開発環境において、認証(OAuthキャッシュ)は「リポジトリ(作業空間)」ではなく「ユーザー(環境)」に紐づくべきである。グローバルな共有ストレージを活用することで、頻繁な認証ストレスを解消し、作業継続性を劇的に向上させることができる。
フレンド限定コンテンツを「存在だけ見せる」マスク実装の設計
GraphQL APIにおいて、完全に非公開にするのではなく、メタデータ(ID、タイトル、公開ステータス等)のみを公開リストに流し、本文などの機密情報をマスクする設計パターン。
背景
ユーザーがアウトプットを精力的に行っていることを非ログインユーザーにも示しつつ、詳細を読むためには会員登録(およびフレンド申請)が必要であることを提示し、サービスへの動機付け(リード獲得)を強化したい。
実装のポイント
- 認可レベルの分離:
Query.notesのフィルター条件で、publishStatusがpublished(公開)およびfriendOnly(フレンド限定)の両方を取得対象に含める。
- フィールドレベルのマスク:
- 閲覧権限がない場合、
content(本文)フィールドなどをnullで返す。 - これにより、フロントエンド側は「記事が存在すること」を検知しつつ、内容の表示を制限できる。
- 閲覧権限がない場合、
- UIによるロック表示:
- フロントエンド側で
note.content == nullを判定し、ロックアイコンや "Friend only", "Sign in to read" といったメッセージを表示する。 - 完全に隠す場合に比べて、ユーザーの活動量を視覚的に示すことができる。
- フロントエンド側で
セキュリティ上の考慮事項
- 安全なデフォルト: 特定のステータス以外を「除外」するよりも、「公開(published)以外はすべて null に倒す」というホワイトリスト形式の実装が安全である。
- メタデータの露出制限: タイトルや抜粋も伏せる必要がある場合は、それらもマスク対象に含める。
結論
「全か無か(公開か非公開か)」ではなく、メタデータのみを露出させる「段階的公開」を GraphQL のフィールドレベル認可で実現することで、プライバシーの保護とユーザー獲得施策を両立できる。
