REVELUP
shwld
shwld
作ることに関わるひとを幸福にしたい スキ: 個人開発 設計 アジャイル 関数型 DDD

Notes

Friend only
This note is available to friends only.
13 days ago
Sign in to read

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 の有無を条件にすると、テストランナーや擬似ブラウザ実行環境で落とし穴になる。

13 days ago
Friend only
This note is available to friends only.
13 days ago
Sign in to read
Friend only
This note is available to friends only.
13 days ago
Sign in to read
Friend only
This note is available to friends only.
14 days ago
Sign in to read
Friend only
This note is available to friends only.
17 days ago
Sign in to read
Friend only
This note is available to friends only.
17 days ago
Sign in to read
Friend only
This note is available to friends only.
17 days ago
Sign in to read
Friend only
This note is available to friends only.
17 days ago
Sign in to read
Friend only
This note is available to friends only.
17 days ago
Sign in to read
Friend only
This note is available to friends only.
18 days ago
Sign in to read
Friend only
This note is available to friends only.
18 days ago
Sign in to read
Friend only
This note is available to friends only.
18 days ago
Sign in to read
Friend only
This note is available to friends only.
19 days ago
Sign in to read
Friend only
This note is available to friends only.
19 days ago
Sign in to read
Friend only
This note is available to friends only.
19 days ago
Sign in to read
Friend only
This note is available to friends only.
20 days ago
Sign in to read

バックログの未割り当てアイテムを未来スプリントに自動アサインして表示する

バックログには「まだスプリントに割り当てていないアイテム」が常にある。これを「未割り当て」としてフラットに並べるより、velocity をもとに未来のスプリントへ自動アサインして表示した方が計画の見通しがよくなる。

設計:

  • 現在のスプリントの残キャパシティと velocity から「Sprint N+1」「Sprint N+2」のような未来スプリントを生成する
  • バックログアイテムを優先度順に並べ、velocity に基づいて各スプリントにアサインしていく
  • UIには通常のスプリントと同じ形式でグループ表示する

velocity の計算にはイテレーションごとの平均ではなく 直近21日のローリング平均を使った。イテレーション長が変動しても安定した予測になる。

トレードオフ:

  • velocity の精度に依存するため、初期フェーズや人員変動が多いチームでは予測が外れやすい
  • 「予測」であることをUIで明示しないと、確定スケジュールと混同されやすい

PivotalTracker や Linear でも同様のアプローチが取られており、特に PivotalTracker はこの予測スケジューリングをコアUXとして採用している。バックログの見通しがひと目でわかるので、このスタイルを好んで採用している。velocity ベースの自動スケジューリングは、バックログが積み上がってきたときに特に効果を発揮する。

20 days ago
Friend only
This note is available to friends only.
20 days ago
Sign in to read

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 がデフォルトのツールもあるので、そちらも同じパターンでワンショット版を分離するとよい
20 days ago

AIエージェント向けドキュメントは「コードから推論できない情報」だけに絞る

モノレポのサブパッケージにCLAUDE.md / AGENTS.mdを追加したとき、最初はディレクトリ構成、開発フロー、実装パターン、コンテキスト型の説明まで盛り込んだ。レビューしたら大半が「コードを読めばわかる」内容だった。

削ぎ落として残したのは3カテゴリだけ:

  1. 開発コマンド一覧 — パッケージ固有のスクリプト名とその用途
  2. AIエージェント向けのコマンド使い分け — ワンショット版(CI/AI向け)とwatch版(人間の開発時向け)の違い
  3. 注意事項 — 生成物の依存関係や実行順序の制約など、コードからは読み取りにくいルール

削った項目:

  • ディレクトリ構成 → ls で見える
  • 開発フロー(スキーマ定義 → 実装の流れ) → コード読めばわかる
  • 主要パターン(実装パターン、ヘルパー、データローダー) → コード読めばわかる
  • 型定義の説明 → 型定義ファイルを読めばわかる

判断基準: 「AIがファイルを数個読めば自力で把握できるか?」。Yesなら書かない。ドキュメントに書くべきは、コードに現れない暗黙知(コマンドの使い分けルール、実行順序の制約、生成物の依存関係)だけ。

サブディレクトリのCLAUDE.mdはそのディレクトリ内のファイルに触れたときにオンデマンドで読み込まれるので、コンテキスト圧迫の心配は少ない。ただし冗長なドキュメントはノイズになるので、短い方がよい。

20 days ago
Friend only
This note is available to friends only.
21 days ago
Sign in to read
Friend only
This note is available to friends only.
21 days ago
Sign in to read
Friend only
This note is available to friends only.
22 days ago
Sign in to read
Friend only
This note is available to friends only.
22 days ago
Sign in to read

MCPサーバーにおけるOAuthトークンの共有永続化とワークツリー間の認証維持

MCP (Model Context Protocol) サーバーにおいて、作業用ワークツリーごとに認証が切れる問題を、共有ストレージの導入により解決した。

課題

アクセストークンを Git worktree 配下の一時的なディレクトリやローカルのキャッシュフォルダに保持していたため、新しい作業(Story)ごとにワークツリーを作成・移動すると、キャッシュが引き継がれず、毎回 OAuth の認可フローが発生していた。

解決策

  1. グローバルな共有ストレージへの配置:
    • トークンの保存先をリポジトリ(ワークツリー)配下ではなく、ユーザーのホームディレクトリ直下(例: ユーザー設定ディレクトリ)などの、ワークツリーから独立した場所に変更。
  2. キャッシュの共有:
    • 複数の作業ディレクトリ(worktrees)から、単一のグローバルキャッシュを参照するように設計を変更。
    • これにより、一度認可を完了すれば、他のどのワークツリーで AI エージェント(Codex, Claude Code 等)を起動しても、同じ認証状態が維持される。
  3. トークンの自動更新:
    • 共有されたリフレッシュトークンを用いて、期限切れの際もバックグラウンドで自動更新し、開発者の手を止めない。

結論

AI エージェントとワークツリーを組み合わせた開発環境において、認証(OAuthキャッシュ)は「リポジトリ(作業空間)」ではなく「ユーザー(環境)」に紐づくべきである。グローバルな共有ストレージを活用することで、頻繁な認証ストレスを解消し、作業継続性を劇的に向上させることができる。

22 days ago

フレンド限定コンテンツを「存在だけ見せる」マスク実装の設計

GraphQL APIにおいて、完全に非公開にするのではなく、メタデータ(ID、タイトル、公開ステータス等)のみを公開リストに流し、本文などの機密情報をマスクする設計パターン。

背景

ユーザーがアウトプットを精力的に行っていることを非ログインユーザーにも示しつつ、詳細を読むためには会員登録(およびフレンド申請)が必要であることを提示し、サービスへの動機付け(リード獲得)を強化したい。

実装のポイント

  1. 認可レベルの分離:
    • Query.notes のフィルター条件で、publishStatus が published(公開)および friendOnly(フレンド限定)の両方を取得対象に含める。
  2. フィールドレベルのマスク:
    • 閲覧権限がない場合、content(本文)フィールドなどを null で返す。
    • これにより、フロントエンド側は「記事が存在すること」を検知しつつ、内容の表示を制限できる。
  3. UIによるロック表示:
    • フロントエンド側で note.content == null を判定し、ロックアイコンや "Friend only", "Sign in to read" といったメッセージを表示する。
    • 完全に隠す場合に比べて、ユーザーの活動量を視覚的に示すことができる。

セキュリティ上の考慮事項

  • 安全なデフォルト: 特定のステータス以外を「除外」するよりも、「公開(published)以外はすべて null に倒す」というホワイトリスト形式の実装が安全である。
  • メタデータの露出制限: タイトルや抜粋も伏せる必要がある場合は、それらもマスク対象に含める。

結論

「全か無か(公開か非公開か)」ではなく、メタデータのみを露出させる「段階的公開」を GraphQL のフィールドレベル認可で実現することで、プライバシーの保護とユーザー獲得施策を両立できる。

22 days ago
Friend only
This note is available to friends only.
22 days ago
Sign in to read
Friend only
This note is available to friends only.
22 days ago
Sign in to read
Next Page