🙏

ド三流でもAIと機能実装できる! コマンドパレットでページ検索機能

に公開

tl;dr

  • 機能の背景と目的: 開発生産性の向上
  • Tips その 1: Planning をさせる
  • Tips その 2: Decision Log を書かせる
  • Tips その 3: テックブログを書かせる
  • 非エンジニアによる機能開発の学び
  • 実装詳細
  • まとめ

機能の背景と目的: 開発生産性の向上

コマンドパレットで静的なルートを検索、ジャンプできる機能を開発しました。実装を素早くレビューすることを大義名分としながら、マウスをポチポチしてページを遷移するのが億劫だったのが正直なところです。
また、非エンジニアと AI(今回は Cursor の Agent mode)で、機能をリリースする実験も兼ねています。結果は、開発者ツールとしてStagingリリースにこぎつけることができました。(プロダクションにはリリースしていない&当分する予定もない)

実装結果は、動画の通りです。静的なルートを申し分なく検索し、ジャンプできるというよくあるやつです。画面に張り付いていたわけではないですが、2、3時間でできたはず。動的なルートは、一筋縄では実装できなかったので、断念しました。

Tips その 1: Planningをさせる

Planningを通して、AIに自分のPromptを書かせることで、既存のcodebaseを理解して、適切な実装を提案してくれます。

一番最初は、こんな程度の指示で始めています。

let's plan to implement a new feature. the feature is to help users to find all routes and pages in the entire app. the user simply uses the command shortcut, command + k, and it opens up a command dialog window. the user can search routes, select a route, and jump to the selected route.

丁寧にステップに分けて作ってくれたプランに対して、修正依頼をして、より正確なプランにしてもらいました。AIにとっても、非エンジニアの自分にとっても、これから何を実装するのかが明確になります。

Planningさせると良いよーと方々で書かれていますが、特に非エンジニアにとってこそ、重要な AI との協業ステップだと感じました。

Tips その 2: Decision Logを書かせる

AI がなにをやったかがわかるように、Decision Logを書かせます。Planning同様、世に言われているベストプロクティスのうちとして、やってみました。作ったプランの通りに進みやすくしてくれるはずです。
実装途中で、なかなか Bug fixや、やってほしいことをやってもらえない時に、AIのモデルを変更しました。その際、この Decision log を読んでもらって、コンテクストを渡せました。(と感じています。A/Bテストしたわけではないので、実際のところはわからない。)

Tips その 3: テックブログを書かせる

Tech blog を書かせることは、レビュー依頼するときの良きドキュメントにもなります。機能実装の PR とともに、この Tech blog も添付しました。こうして Tech blog を公開するのも簡単になるので、一石二鳥どころじゃない気がします。

非エンジニアによる機能開発の学び

  • 自分で書くな
    • 書くならcursor rules等のコーディング規則を自分に適用しろ
  • 自分でレビューできる範囲の開発でAI使うほうがよさそう
    • レビューできる範囲を増やさない限り、AI使ってやれる範囲も広がらない
    • 自分でレビューできない範囲は、レビュワーに負担がかかる

実装詳細

以降は、実際の AI によるテックブログ出力による実装詳細です。

企画・要件定義

開発初期段階で、以下の要件を定義しました。

  • グローバルアクセス: アプリケーション内のどこからでも Cmd+K / Ctrl+K ショートカットでパレットを起動できる。

  • インクリメンタルサーチ: 入力に応じて即座にルートを絞り込み検索できる。

  • キーボードナビゲーション: キーボードの上下キーで候補を選択し、Enter キーでページ遷移できる。

  • リッチな表示: 各ルートには、分かりやすいラベル、関連アイコン、キーワードなどを含める。

  • 動的なルート収集: 新しいページが追加された際に、自動で検索対象に含まれるようにする。

設計方針と UI コンポーネント

UI コンポーネントには、shadcn/ui をベースとした Command コンポーネント群(CommandDialog, CommandInput, CommandList など)を採用しました。これにより、アクセシビリティが確保された高品質な UI を迅速に構築できました。

全体的な設計は、以下の 3 つの主要なファイルに分割されています。

  1. CommandPalette.tsx: UI のレンダリング、状態管理、ナビゲーションを担当するメインコンポーネント。

  2. useCommandPaletteByKeyPress.ts: グローバルなキーボードショートカットを検知し、パレットの開閉状態を管理するカスタムフック。

  3. routeList.ts: アプリケーションのルート情報を収集し、検索・表示用に加工するロジック群。

ルートデータの収集と加工

当初は静的なリストでルートを管理する案もありましたが、メンテナンス性と拡張性を考慮し、最終的に TanStack Router のルーターインスタンスから動的にルート情報を取得する方式を採用しました。

routeList.ts 内の getStaffCommandPaletteRoutes 関数がその中核を担います。

  1. useRouter() フックから flatRoutes(全ルートのフラットなリスト)を取得します。

  2. 全ルートの中から、コマンドパレットに表示すべきものを以下の条件で厳密にフィルタリングします。

  • 静的パスのみ: $: を含む動的なルート(例: /users/$userId)は、直接遷移できないため除外します。

  • レイアウトの除外: _layout.tsx/apps/_layout など、UI の骨格を定義するだけのファイルは除外します。

  • 重複の排除: 異なるファイルパスが同じ URL パスを指す場合があるため、一度追加したパスは Set を使って管理し、重複を防ぎます。

  1. フィルタリングを通過した各ルートに対して、以下の情報を付与します。
  • ラベル: pathToLabel 関数が、パス /settings/persons を人間が読みやすい Settings / Persons という形式に変換します。

  • キーワード: pathToKeywords 関数が、パスを /- で分割し、検索用のキーワード配列(例: ["settings", "persons"])を生成します。

  • アイコン: assignLogo 関数が、パスに基づいてプロダクトロゴを賢く割り当てます。まず特例ケースのマップを検索し、一致がなければパスの第一セグメントから動的にロゴコンポーネントを探索(例: /people-analyticsProductLogoSmallPeopleAnalytics)、最終的に見つからなければデフォルトのアイコンを返します。

  1. 最後に、リスト全体をラベルのアルファベット順にソートし、常に一貫した順序で表示されるようにします。

この動的なアプローチにより、開発者が新しいページを追加するだけで、自動的にコマンドパレットの検索対象に含まれるようになりました。

グローバルショートカット

useCommandPaletteByKeyPress.ts フックは、useEffect を使って window オブジェクトに keydown イベントリスナーを登録します。Cmd/Ctrl + K が押されると、useState で管理されている open 状態を true に更新し、CommandPalette コンポーネントに伝達します。コンポーネントのアンマウント時には、クリーンアップ関数でイベントリスナーを確実に解除します。

コンポーネントと状態管理

CommandPalette.tsx では、前述のフックから opensetOpen を受け取ります。CommandDialogopenonOpenChange プロパティにこれらを渡すことで、ダイアログの表示・非表示を制御します。

検索文字列は useStatesearch として管理され、CommandInputonValueChange で更新されます。

フィルタリングとナビゲーション

useMemo を活用し、search 文字列が変更されるたびに filterCommandPaletteRoutes 関数を実行して、表示するルートを効率的に絞り込みます。このフィルタリング関数は、複数単語での AND 検索に対応しています。

例えば、「user settings」と検索すると、検索語が ["user", "settings"] という配列に分割されます。そして、各ルートの label, path, description, keywords をすべて結合したテキスト内に、これらすべての単語が含まれているか(every)をチェックします。これにより、ユーザーが意図した結果を素早く見つけられるようになっています。

ユーザーが CommandItem を選択すると、onSelect コールバックが発火します。ここで setOpen(false) を呼び出してパレットを閉じ、TanStack Router の navigate({ to: route.path }) を使って選択されたページに遷移します。

テストと品質保証

機能の堅牢性を確保するため、手動 QA チェックリストを作成しました。

  • Cmd+K/Ctrl+K でパレットがどこからでも開くか

  • Escape キーや項目選択でパレットが閉じるか

  • ラベル、説明、パスによる検索が正しく機能するか

  • キーボード(上下、Enter)での操作がスムーズか

  • アイコンとラベルが正しく表示されるか

まとめ

ちまいラベル修正などではなく、機能実装までできるととても嬉しい気持ちになりました。動的なルートを検索可能にするところまではいけなかったのが残念。
加えて、きちんと整ったcodebaseがあったからこそ、AIも自分も悩まずに実装できたのだと感じています。
メインの職務はデザイナーですが、今後も、ド三流のエンジニア見習いとして、格の違いを見せつけられていきます。

DRESS CODE TECH BLOG

Discussion