背景
Issue #642 で DB/API レベルのリポジトリ別名(display_name)機能を追加し、Sessions・Worktree 詳細などの表示側には反映済みです。しかし現状 Repositories 画面(/repositories)は「Add Repository」フォームと「Sync All」ボタンのみで、登録済みリポジトリの 一覧表示そのものが存在しない ため、別名を設定するには PUT /api/repositories/[id] を直接 curl で叩くしか手段がありません。
UI から登録済みリポジトリを確認・別名編集できるようにします。
現状の課題
/repositories 画面(src/app/repositories/page.tsx)は RepositoryManager のみを描画しているが、RepositoryManager は登録フォーム+Sync ボタンしか持たない
リポジトリ一覧を返す GET API が存在しない(src/app/api/repositories/route.ts は DELETE のみ)
repositoryApi(src/lib/api-client.ts)に list 取得メソッドが存在しない
Issue feat: リポジトリに別名(エイリアス)を設定して表示名を変更可能にする #642 で追加した display_name を UI から編集する導線がない
要件
機能要件
リポジトリ一覧表示
/repositories 画面に登録済みリポジトリの一覧を表示する
各行に以下を表示:
リポジトリ名(name)
別名(displayName、未設定時は空欄 or プレースホルダ)
絶対パス(path)
紐づく worktree 数(worktreeCount)
有効/無効状態(enabled)
RepositoryList は RepositoryManager の上部に配置する(一覧ファーストの UX、S3-009)
無効化リポジトリの扱い(方針明確化・S1-003) :
GET /api/repositories は enabled=0 を含めた全リポジトリを返す(WHERE 句なし)
フロント側で各行の enabled フィールドを参照し、無効なリポジトリにはバッジ(例: "Disabled")を表示する
既存の /api/repositories/excluded(Issue トップ画面にて登録済みのリポジトリを削除してもsyncallで復活する #190 )は別目的(除外対象の専用リスト)なので 本 Issue では使用しない
別名の編集
一覧の各行で別名をインライン編集できる(編集ボタン → インプット → 保存/キャンセル、もしくはクリックで直接編集)
入力中は楽観更新ではなく、保存成功まで既存値を維持する
保存は既存の PUT /api/repositories/[id] を呼ぶ({ displayName: string }、空文字で解除)
バリデーション:
最大 100 文字(共有定数 MAX_DISPLAY_NAME_LENGTH を src/config/repository-config.ts に定義し、API ルートとフロント両方から import)
trim 後の空文字は「別名クリア」として扱う
保存成功時はトースト or 成功メッセージを表示し、一覧を再取得 or 楽観更新する
失敗時はエラーメッセージを表示し、入力値を保持する
非機能要件
一覧取得は画面マウント時に 1 回、編集後に差分再取得 or 該当行のみ更新
ポーリング方針(S3-008) : 本 Issue では Option A(ポーリングなし、Sync/Add/Delete イベント時のみ refresh) を採用する
定期ポーリングは実装しない(負荷増を避けるため)
RepositoryList の再取得トリガーは「RepositoryManager による Add/Sync 完了」または「将来追加される Delete」のイベント時のみ
他タブ・他ユーザーによる変更の即時反映が必要になった場合は別 Issue で WebSocket 購読(repository_deleted 等)を追加する
想定件数: 20〜100 件程度。仮想スクロールは不要
リポジトリ数が多い場合でも再レンダリングが過剰にならないよう React.memo / useCallback を適切に使用
既存の RepositoryManager コンポーネントとは責務を分離し、新規コンポーネント(例: RepositoryList.tsx)として追加する
ダークモード対応
キーボード操作(Enter で保存、Esc でキャンセル)
認証・IP 制限(S3-007) : GET /api/repositories は既存の認証ミドルウェア(src/middleware.ts)を流用するため、本 Issue での追加変更は不要。既存の DELETE /api/repositories / PUT /api/repositories/[id] / GET /api/repositories/excluded と同等の挙動となる
実装方針(たたき台)
1. API 追加
新規 : GET /api/repositories (src/app/api/repositories/route.ts に追記)
getAllRepositories(db)(既存・src/lib/db/db-repository.ts:290)を呼び出し、worktreeCount を別途 worktrees テーブルへの集計クエリで付与して返す
既存 getRepositories()(worktree-db.ts)を流用しない理由(S1-002) :
src/lib/db/worktree-db.ts:171 の既存 getRepositories() は worktrees テーブル起点の LEFT JOIN + GROUP BY クエリで構成されており、
worktree 0 件のリポジトリを返さない
id / enabled フィールドを返さない
という制約があるため、本 Issue の要件(全リポジトリ表示・enabled バッジ)を満たせない
そのため getAllRepositories(db)(repositories テーブル直接クエリ、src/lib/db/db-repository.ts:290-300、WHERE 句なしなので enabled=0 も含む全件を返す)を使用し、
worktreeCount は別途 worktrees テーブルへの集計クエリで付与する
worktreeCount 集計クエリの正しい形(S3-001 / Must Fix) :
重要 : worktrees テーブルには repository_id カラムが 存在しない (src/lib/db/migrations/v11-v15-feature-additions.ts:195-230 参照)。リポジトリとの紐付けは repository_path 文字列カラムで行われている(src/lib/db/worktree-db.ts:91,99,179-186)。repository_id FK は clone_jobs テーブルにのみ存在する
したがって集計クエリは repository_path ベースとする:
SELECT repository_path, COUNT (* ) AS count
FROM worktrees
WHERE repository_path IS NOT NULL
GROUP BY repository_path
フロントマージ手順:
getAllRepositories(db) で Repository 全件を取得
上記クエリ結果を Map<string, number>(key = repository_path)に変換
各 Repository について map.get(repo.path) ?? 0 で worktreeCount を付与
推奨(N+1 安全な代替案) : DB 層に新規ヘルパー getAllRepositoriesWithWorktreeCount(db) を追加する
SELECT r.* , (SELECT COUNT (* ) FROM worktrees w WHERE w .repository_path = r .path ) AS worktree_count
FROM repositories r
ORDER BY r .name ASC
こちらを採用する場合は既存 getAllRepositories(db) は 変更せず温存 する(S3-005、後述)
既存 getAllRepositories(db) のシグネチャは変更しない(S3-005 / 後方互換) :
getAllRepositories(db) は既に src/app/api/repositories/sync/route.ts:26 と src/lib/daily-summary-generator.ts:179 の 2 箇所で呼ばれている
既存シグネチャを変えると上記 2 箇所への影響(型変更、不要な worktreeCount フィールド追加)が発生するため、既存関数は変更しない
本 Issue で追加するロジックは次のいずれかに限定する:
(a) API ルート内で getAllRepositories(db) を呼び出した後、worktree 集計クエリ結果の Map を別途取得してマージする
(b) DB 層に新規ヘルパー getAllRepositoriesWithWorktreeCount(db) を追加する(既存 getAllRepositories は温存)
src/app/api/repositories/sync/route.ts:26 と src/lib/daily-summary-generator.ts:179 の既存呼び出し箇所は 変更しない
無効化リポジトリの返却方針(S1-003) :
getAllRepositories(db) は WHERE 句なしで全件返すため、enabled=0 のリポジトリもそのまま含めて返す
フロント側で enabled フィールドを参照して無効バッジを描画する
/api/repositories/excluded は呼ばない(excluded API は別用途のため)
既存 GET /api/worktrees との棲み分け(S3-002 / Must Fix) :
既存 GET /api/worktrees(src/app/api/worktrees/route.ts:97)は getRepositories(db)(src/lib/db/worktree-db.ts:171)経由で { path, name, displayName, worktreeCount } 形式の worktree 起点のリポジトリサマリー を返す
新規 GET /api/repositories は getAllRepositories(db) ベースで { id, name, displayName, path, enabled, worktreeCount } 形式の repositories テーブル起点の全件 を返す
Single Source of Truth 方針 : Repositories 画面は repositories テーブルを真の値として扱う。既存の Sessions 画面・Worktree 詳細画面・サイドバーなどで使われる WorktreesResponse.repositories(worktree 起点)の表示は 本 Issue では据え置き (既存仕様のまま)
別名更新後の既存画面への反映経路(最小実装方針) :
本 Issue では リロード後反映を許容する (Option C)
即時反映は実装せず、ユーザーが Sessions 画面等を再訪問/リロードした時点で /api/worktrees が再フェッチされて最新の名前が反映される
受け入れ条件の「保存した別名が Sessions 画面・Worktree 詳細画面など既存の表示箇所に反映される」は リロード後反映 として判定する
将来の拡張案(本 Issue では実装しない):
Option A: 保存後にフロント側で worktreeApi.getAll()(/api/worktrees)のキャッシュも無効化・再取得する(WorktreesCacheProvider 経由)
Option B: WebSocket で repository_display_name_updated イベントをブロードキャストし、購読側で更新する
リポジトリディレクトリをリネーム後に worktrees が古い名前で残る場合の表示不整合(getRepositories() が返す name と getAllRepositories() が返す name の食い違い)は 本 Issue のスコープ外 とし、Sync All で worktrees テーブルを再同期することで解消される既存動作に委ねる
レスポンス例:
{
"success" : true ,
"repositories" : [
{ "id" : " ..." , "name" : " ..." , "displayName" : " My Project" , "path" : " /abs/path" , "enabled" : true , "worktreeCount" : 3 }
]
}
既存 : PUT /api/repositories/[id](Issue #642 で追加済み)をそのまま利用
2. api-client 追加
src/lib/api-client.ts の repositoryApi に下記を追加:
// 一覧取得用の型(GET /api/repositories のレスポンス行)
// フロント専用のシリアライズ型。src/types/models.ts の Repository 型はそのまま維持する
// createdAt / updatedAt / cloneSource / isEnvManaged は本 Issue の機能に不要なため含めない(将来拡張は別 Issue)
export type RepositoryListItem = {
id : string ;
name : string ;
displayName : string | null ;
path : string ;
enabled : boolean ;
worktreeCount : number ;
} ;
// PUT /api/repositories/[id] は worktreeCount を返さないため専用の戻り値型を使用(S1-001)
export type UpdateRepositoryDisplayNameResponse = {
success : boolean ;
repository : Omit < RepositoryListItem , 'worktreeCount' > ;
} ;
async list ( ) : Promise < { success : boolean ; repositories : RepositoryListItem [ ] } > {
return fetchApi ( '/api/repositories' ) ;
} ,
async updateDisplayName ( id : string , displayName : string | null ) : Promise < UpdateRepositoryDisplayNameResponse > {
return fetchApi ( `/api/repositories/${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( { displayName } ) ,
} ) ;
} ,
レスポンス型不整合への対応(S1-001) :
PUT /api/repositories/[id](src/app/api/repositories/[id]/route.ts:63-73)は { id, name, displayName, path, enabled } のみを返し、worktreeCount を含まない
そのため updateDisplayName の戻り値型は RepositoryListItem をそのまま使わず、Omit<RepositoryListItem, 'worktreeCount'> を型として分離する(上記 UpdateRepositoryDisplayNameResponse 参照)
フロント側は保存後に (a) 該当行のみ局所更新(worktreeCount は既存値をそのまま保持)または (b) 一覧全体を再フェッチ、のいずれかで整合性を取る
3. UI コンポーネント追加
新規 : src/components/repository/RepositoryList.tsx
画面マウント時に repositoryApi.list() を呼び出し、state に保持
各行で別名のインライン編集(編集モード切替 → 保存/キャンセル)
保存後は該当行のみ楽観更新 or 再フェッチ
各行で enabled が false のリポジトリには無効バッジを描画
refreshKey prop(number)と onChanged prop(() => void)を受け取り、親からの再取得トリガーに対応する
更新 : src/app/repositories/page.tsx
RepositoryList を RepositoryManager の 上部 に配置する(一覧ファースト、S3-009)
新規追加 or Sync 完了後に RepositoryList の再取得をトリガーする
refresh 連携パターン(S3-004 / 具体実装) : page.tsx で refreshKey state を持ち、RepositoryManager の onRepositoryAdded でカウントアップして RepositoryList に渡すバケツリレー方式を採用する:
// src/app/repositories/page.tsx
'use client' ;
import { useCallback , useState } from 'react' ;
export default function RepositoriesPage ( ) {
const [ refreshKey , setRefreshKey ] = useState ( 0 ) ;
const handleChanged = useCallback ( ( ) => setRefreshKey ( ( k ) => k + 1 ) , [ ] ) ;
return (
< >
< RepositoryList refreshKey = { refreshKey } onChanged = { handleChanged } />
< RepositoryManager onRepositoryAdded = { handleChanged } />
</ >
) ;
}
削除導線は 本 Issue では追加しない (既存 WorktreeList 側に集約済み)
Context の導入は不要(2 コンポーネント間のシンプルなバケツリレーで十分)
4. 共有定数(S1-004 / S3-003)
新規ファイル : src/config/repository-config.ts
// リポジトリ設定関連の共有定数
export const MAX_DISPLAY_NAME_LENGTH = 100 ;
既存 src/config/auth-config.ts / review-config.ts / timer-constants.ts の命名パターンを踏襲する
フロント(src/components/repository/RepositoryList.tsx)からも同ファイルから import する
API ルート・フロント両方で同じ定数を参照することで、バリデーション値の二重管理を防ぐ
既存 PUT ルートの定数置換手順(S3-003) :
src/app/api/repositories/[id]/route.ts:13 の const MAX_DISPLAY_NAME_LENGTH = 100; を削除し、src/config/repository-config.ts からの import に切り替える
既存の挙動・エラーメッセージ文言を変更しないこと : `displayName must be ${MAX_DISPLAY_NAME_LENGTH} characters or less` のテンプレート文字列はそのまま残し、定数参照だけを置き換える
Issue feat: リポジトリに別名(エイリアス)を設定して表示名を変更可能にする #642 時点で PUT /api/repositories/[id] の Integration テストは未カバー(unit の tests/unit/db-repository-display-name.test.ts のみ)のため、本 Issue で Integration テストを新規追加 する(バリデーション・400・404・成功パス。詳細は「テスト戦略」セクション参照)
テスト戦略(S3-006 / 新規追加)
本 Issue では以下の 3 ファイルを新規追加する:
tests/integration/api-repositories-list.test.ts (新規 / GET /api/repositories 用)
成功パス(200 + { success: true, repositories: [...] })
enabled=0 を含めた全件返却の確認
worktreeCount 集計の正しさ(worktree 0 件・複数件・リポジトリなしケース)
repository_path ベースの集計クエリが正しく動作することの検証(S3-001 の回帰防止)
Repository 型の返却フィールドが仕様通り(id, name, displayName, path, enabled, worktreeCount)
NextRequest 経由のテスト(既存 tests/integration/api-repository-delete.test.ts を雛形にする)
tests/unit/components/repository/RepositoryList.test.tsx (新規 / RepositoryList コンポーネント用)
一覧レンダリング(名前・別名・パス・worktree 数・enabled バッジ)
インライン編集モード切替(編集ボタン → インプット → 保存/キャンセル)
保存成功時の UI 更新(該当行の局所更新 or 再フェッチ)
バリデーション: 100 文字超入力時にクライアント側エラー表示
Enter で保存、Esc でキャンセルのキーボード操作
enabled=false のリポジトリで「Disabled」バッジが描画される
既存 tests/unit/components/repository/RepositoryManager.test.tsx(645 行)を参考にした粒度
tests/integration/api-repositories-put.test.ts (新規 / PUT /api/repositories/[id] の Integration 補完・S3-003)
バリデーション(空文字 → null 扱い、trim、100 文字超で 400)
404(存在しない id)
成功パス(200 + { success: true, repository: {...} })
エラーメッセージ文言が `displayName must be ${MAX_DISPLAY_NAME_LENGTH} characters or less` のままであることの回帰確認(S3-003)
Issue feat: リポジトリに別名(エイリアス)を設定して表示名を変更可能にする #642 時点で未カバー分を本 Issue で補完する
src/lib/api-client.ts の新メソッド(list, updateDisplayName)は RepositoryList.test.tsx 経由で間接的にカバーする(既存 api-client の慣習に合わせる)。
受け入れ条件
関連
Issue feat: リポジトリに別名(エイリアス)を設定して表示名を変更可能にする #642 : DB/API 側の display_name 実装(本 Issue の前提)
ブランチ: feature/642-repo-display-name
関連ファイル:
src/app/repositories/page.tsx
src/components/repository/RepositoryManager.tsx
src/components/repository/RepositoryList.tsx(新規)
src/app/api/repositories/route.ts
src/app/api/repositories/[id]/route.ts
src/app/api/repositories/sync/route.ts(変更しない / S3-005 参照)
src/app/api/worktrees/route.ts(既存 GET との棲み分け参照 / S3-002)
src/lib/api-client.ts
src/lib/db/db-repository.ts
src/lib/db/worktree-db.ts(既存 getRepositories() との棲み分け参照)
src/lib/db/migrations/v11-v15-feature-additions.ts(worktrees スキーマ確認 / S3-001)
src/lib/daily-summary-generator.ts(変更しない / S3-005 参照)
src/types/models.ts
src/middleware.ts(認証ミドルウェア流用 / S3-007)
src/config/repository-config.ts(新規、MAX_DISPLAY_NAME_LENGTH 定義)
tests/integration/api-repositories-list.test.ts(新規 / S3-006)
tests/integration/api-repositories-put.test.ts(新規 / S3-006 / S3-003)
tests/unit/components/repository/RepositoryList.test.tsx(新規 / S3-006)
背景
Issue #642 で DB/API レベルのリポジトリ別名(
display_name)機能を追加し、Sessions・Worktree 詳細などの表示側には反映済みです。しかし現状 Repositories 画面(/repositories)は「Add Repository」フォームと「Sync All」ボタンのみで、登録済みリポジトリの 一覧表示そのものが存在しない ため、別名を設定するにはPUT /api/repositories/[id]を直接 curl で叩くしか手段がありません。UI から登録済みリポジトリを確認・別名編集できるようにします。
現状の課題
/repositories画面(src/app/repositories/page.tsx)はRepositoryManagerのみを描画しているが、RepositoryManagerは登録フォーム+Sync ボタンしか持たないsrc/app/api/repositories/route.tsはDELETEのみ)repositoryApi(src/lib/api-client.ts)に list 取得メソッドが存在しないdisplay_nameを UI から編集する導線がない要件
機能要件
リポジトリ一覧表示
/repositories画面に登録済みリポジトリの一覧を表示するname)displayName、未設定時は空欄 or プレースホルダ)path)worktreeCount)enabled)RepositoryListはRepositoryManagerの上部に配置する(一覧ファーストの UX、S3-009)GET /api/repositoriesはenabled=0を含めた全リポジトリを返す(WHERE 句なし)enabledフィールドを参照し、無効なリポジトリにはバッジ(例: "Disabled")を表示する/api/repositories/excluded(Issue トップ画面にて登録済みのリポジトリを削除してもsyncallで復活する #190)は別目的(除外対象の専用リスト)なので 本 Issue では使用しない別名の編集
PUT /api/repositories/[id]を呼ぶ({ displayName: string }、空文字で解除)MAX_DISPLAY_NAME_LENGTHをsrc/config/repository-config.tsに定義し、API ルートとフロント両方から import)非機能要件
RepositoryListの再取得トリガーは「RepositoryManagerによる Add/Sync 完了」または「将来追加される Delete」のイベント時のみrepository_deleted等)を追加するReact.memo/useCallbackを適切に使用RepositoryManagerコンポーネントとは責務を分離し、新規コンポーネント(例:RepositoryList.tsx)として追加するGET /api/repositoriesは既存の認証ミドルウェア(src/middleware.ts)を流用するため、本 Issue での追加変更は不要。既存のDELETE /api/repositories/PUT /api/repositories/[id]/GET /api/repositories/excludedと同等の挙動となる実装方針(たたき台)
1. API 追加
新規:
GET /api/repositories(src/app/api/repositories/route.tsに追記)getAllRepositories(db)(既存・src/lib/db/db-repository.ts:290)を呼び出し、worktreeCountを別途 worktrees テーブルへの集計クエリで付与して返すgetRepositories()(worktree-db.ts)を流用しない理由(S1-002):src/lib/db/worktree-db.ts:171の既存getRepositories()は worktrees テーブル起点の LEFT JOIN + GROUP BY クエリで構成されており、id/enabledフィールドを返さないという制約があるため、本 Issue の要件(全リポジトリ表示・enabled バッジ)を満たせない
getAllRepositories(db)(repositories テーブル直接クエリ、src/lib/db/db-repository.ts:290-300、WHERE 句なしなのでenabled=0も含む全件を返す)を使用し、worktreeCountは別途 worktrees テーブルへの集計クエリで付与するworktreesテーブルにはrepository_idカラムが 存在しない(src/lib/db/migrations/v11-v15-feature-additions.ts:195-230参照)。リポジトリとの紐付けはrepository_path文字列カラムで行われている(src/lib/db/worktree-db.ts:91,99,179-186)。repository_idFK はclone_jobsテーブルにのみ存在するrepository_pathベースとする:getAllRepositories(db)で Repository 全件を取得Map<string, number>(key =repository_path)に変換map.get(repo.path) ?? 0でworktreeCountを付与getAllRepositoriesWithWorktreeCount(db)を追加するgetAllRepositories(db)は 変更せず温存 する(S3-005、後述)getAllRepositories(db)のシグネチャは変更しない(S3-005 / 後方互換):getAllRepositories(db)は既にsrc/app/api/repositories/sync/route.ts:26とsrc/lib/daily-summary-generator.ts:179の 2 箇所で呼ばれているgetAllRepositories(db)を呼び出した後、worktree 集計クエリ結果の Map を別途取得してマージするgetAllRepositoriesWithWorktreeCount(db)を追加する(既存getAllRepositoriesは温存)src/app/api/repositories/sync/route.ts:26とsrc/lib/daily-summary-generator.ts:179の既存呼び出し箇所は 変更しないgetAllRepositories(db)は WHERE 句なしで全件返すため、enabled=0のリポジトリもそのまま含めて返すenabledフィールドを参照して無効バッジを描画する/api/repositories/excludedは呼ばない(excluded API は別用途のため)GET /api/worktreesとの棲み分け(S3-002 / Must Fix):GET /api/worktrees(src/app/api/worktrees/route.ts:97)はgetRepositories(db)(src/lib/db/worktree-db.ts:171)経由で{ path, name, displayName, worktreeCount }形式の worktree 起点のリポジトリサマリー を返すGET /api/repositoriesはgetAllRepositories(db)ベースで{ id, name, displayName, path, enabled, worktreeCount }形式の repositories テーブル起点の全件 を返すrepositoriesテーブルを真の値として扱う。既存の Sessions 画面・Worktree 詳細画面・サイドバーなどで使われるWorktreesResponse.repositories(worktree 起点)の表示は 本 Issue では据え置き(既存仕様のまま)/api/worktreesが再フェッチされて最新の名前が反映されるworktreeApi.getAll()(/api/worktrees)のキャッシュも無効化・再取得する(WorktreesCacheProvider経由)repository_display_name_updatedイベントをブロードキャストし、購読側で更新するgetRepositories()が返す name とgetAllRepositories()が返す name の食い違い)は 本 Issue のスコープ外とし、Sync All で worktrees テーブルを再同期することで解消される既存動作に委ねる{ "success": true, "repositories": [ { "id": "...", "name": "...", "displayName": "My Project", "path": "/abs/path", "enabled": true, "worktreeCount": 3 } ] }既存:
PUT /api/repositories/[id](Issue #642 で追加済み)をそのまま利用2. api-client 追加
src/lib/api-client.tsのrepositoryApiに下記を追加:レスポンス型不整合への対応(S1-001):
PUT /api/repositories/[id](src/app/api/repositories/[id]/route.ts:63-73)は{ id, name, displayName, path, enabled }のみを返し、worktreeCountを含まないupdateDisplayNameの戻り値型はRepositoryListItemをそのまま使わず、Omit<RepositoryListItem, 'worktreeCount'>を型として分離する(上記UpdateRepositoryDisplayNameResponse参照)3. UI コンポーネント追加
新規:
src/components/repository/RepositoryList.tsxrepositoryApi.list()を呼び出し、state に保持enabledが false のリポジトリには無効バッジを描画refreshKeyprop(number)とonChangedprop(() => void)を受け取り、親からの再取得トリガーに対応する更新:
src/app/repositories/page.tsxRepositoryListをRepositoryManagerの 上部 に配置する(一覧ファースト、S3-009)RepositoryListの再取得をトリガーするpage.tsxでrefreshKeystate を持ち、RepositoryManagerのonRepositoryAddedでカウントアップしてRepositoryListに渡すバケツリレー方式を採用する:WorktreeList側に集約済み)4. 共有定数(S1-004 / S3-003)
新規ファイル:
src/config/repository-config.tssrc/config/auth-config.ts/review-config.ts/timer-constants.tsの命名パターンを踏襲するsrc/components/repository/RepositoryList.tsx)からも同ファイルから import する既存 PUT ルートの定数置換手順(S3-003):
src/app/api/repositories/[id]/route.ts:13のconst MAX_DISPLAY_NAME_LENGTH = 100;を削除し、src/config/repository-config.tsからの import に切り替える`displayName must be ${MAX_DISPLAY_NAME_LENGTH} characters or less`のテンプレート文字列はそのまま残し、定数参照だけを置き換える/api/repositories/[id]の Integration テストは未カバー(unit のtests/unit/db-repository-display-name.test.tsのみ)のため、本 Issue で Integration テストを新規追加 する(バリデーション・400・404・成功パス。詳細は「テスト戦略」セクション参照)テスト戦略(S3-006 / 新規追加)
本 Issue では以下の 3 ファイルを新規追加する:
tests/integration/api-repositories-list.test.ts(新規 / GET/api/repositories用){ success: true, repositories: [...] })enabled=0を含めた全件返却の確認worktreeCount集計の正しさ(worktree 0 件・複数件・リポジトリなしケース)repository_pathベースの集計クエリが正しく動作することの検証(S3-001 の回帰防止)id,name,displayName,path,enabled,worktreeCount)tests/integration/api-repository-delete.test.tsを雛形にする)tests/unit/components/repository/RepositoryList.test.tsx(新規 /RepositoryListコンポーネント用)tests/unit/components/repository/RepositoryManager.test.tsx(645 行)を参考にした粒度tests/integration/api-repositories-put.test.ts(新規 / PUT/api/repositories/[id]の Integration 補完・S3-003){ success: true, repository: {...} })`displayName must be ${MAX_DISPLAY_NAME_LENGTH} characters or less`のままであることの回帰確認(S3-003)src/lib/api-client.tsの新メソッド(list,updateDisplayName)はRepositoryList.test.tsx経由で間接的にカバーする(既存 api-client の慣習に合わせる)。受け入れ条件
/repositories画面で登録済みリポジトリの一覧が表示される(RepositoryListはRepositoryManagerの上部に配置)enabled=false)が無効バッジ付きで一覧に表示されるnameが表示に戻るMAX_DISPLAY_NAME_LENGTHをsrc/config/repository-config.tsから import して使用)/api/repositories/[id]ルートのローカル定数MAX_DISPLAY_NAME_LENGTHがsrc/config/repository-config.tsからの import に置換され、エラーメッセージ文言は従来通りであること(S3-003)worktreeCount集計クエリがrepository_pathベースで動作し、repository_idではないこと(S3-001)getAllRepositories(db)のシグネチャが変更されていないこと、およびsrc/app/api/repositories/sync/route.tsとsrc/lib/daily-summary-generator.tsの挙動が変わっていないこと(S3-005)RepositoryManagerの Add/Sync 完了後にRepositoryListが自動再取得される(page.tsxのrefreshKeystate 連携、S3-004)GET /api/repositoriesが既存の認証ミドルウェア(src/middleware.ts)を正常に通過し、未認証時に既存 API と同等のレスポンス(307 redirect / 401 相当)を返すこと(S3-007)npm run lint/npx tsc --noEmit/npm run test:unit/npm run test:integrationがパスするtests/integration/api-repositories-list.test.ts(GET/api/repositories用、repository_path集計検証を含む)tests/unit/components/repository/RepositoryList.test.tsx(RepositoryListコンポーネント用)tests/integration/api-repositories-put.test.ts(PUT/api/repositories/[id]の Integration 補完)関連
display_name実装(本 Issue の前提)feature/642-repo-display-namesrc/app/repositories/page.tsxsrc/components/repository/RepositoryManager.tsxsrc/components/repository/RepositoryList.tsx(新規)src/app/api/repositories/route.tssrc/app/api/repositories/[id]/route.tssrc/app/api/repositories/sync/route.ts(変更しない / S3-005 参照)src/app/api/worktrees/route.ts(既存 GET との棲み分け参照 / S3-002)src/lib/api-client.tssrc/lib/db/db-repository.tssrc/lib/db/worktree-db.ts(既存getRepositories()との棲み分け参照)src/lib/db/migrations/v11-v15-feature-additions.ts(worktrees スキーマ確認 / S3-001)src/lib/daily-summary-generator.ts(変更しない / S3-005 参照)src/types/models.tssrc/middleware.ts(認証ミドルウェア流用 / S3-007)src/config/repository-config.ts(新規、MAX_DISPLAY_NAME_LENGTH定義)tests/integration/api-repositories-list.test.ts(新規 / S3-006)tests/integration/api-repositories-put.test.ts(新規 / S3-006 / S3-003)tests/unit/components/repository/RepositoryList.test.tsx(新規 / S3-006)