テーマ
Research & Design Decisions — candidate-detail
Purpose: candidate-detail spec の design 書き直し(data-model の 3 系統契約 + voting の購入導線への適合)で行った調査・統合判断・トレードオフを記録する。
Summary
- Feature:
candidate-detail - Discovery Scope: Extension(既存の候補者詳細画面を、3 系統データアーキテクチャと最新の
voting/candidate-ranking契約に合わせて再構築) - Key Findings:
- 旧 design の
candidateService.getAllWithRank()/voterService.getTopByCandidateId()/ DB 由来votingPeriodService//api/images/[id]がすべて廃止対象であり、4 つの参照経路を新契約に置き換える必要がある。 - 順位算出は
candidate-rankingspec が所有するfeatures/candidates/utils/rankCandidatesを再利用し、本 spec ではそこから target ID を抽出する一手間のみを担う。 - 投票導線は
votingspec 所有の<PurchaseFlowContainer>/useVoteFlow/useVotingPeriodStatusをそのまま借用し、本 spec で再実装しない。 - 動的メタデータ(OGP / title)は要件には存在しないが、SEO 強化の選択肢として
generateMetadataで扱う(non-functional 要件側に追記候補)。
- 旧 design の
Research Log
Topic 1: 旧設計のデータアクセス経路の総入れ替え
- Context: 旧 design は Prisma 単独前提で
candidateService.getAllWithRank()を呼び、画像は/api/images/[id]経由で配信、投票期間は DB 由来。新 data-model 契約ではすべて経路が変わる。 - Sources Consulted: 旧
candidate-detail/design.md、data-model/design.md、voting/design.md、candidate-ranking/design.md - Findings:
- 候補者単体取得は
getCandidate(id)で 404 判定可能、ただし順位算出には全候補者の voteCount が必要なのでgetCandidates() + getVoteCountsByCandidateIds()も並走する。 - 応援者ランキングは
getTopVotersByCandidateId(candidateId, limit=5)を新設(voteAggregationService側でPurchase.status='SUCCEEDED'JOIN + nickname GROUP BY)。 - 画像 URL は microCMS の CDN URL を
next/imageに直渡し可能。/api/images/[id]プロキシは不要。 - 投票期間は
getVotingPeriod()(Vercel ENV 由来)を Server で呼び、Client へは period オブジェクトを渡す。
- 候補者単体取得は
- Implications:
- File Structure Plan から
/api/images/[id]ルートを完全削除。 - data-model spec 側に
getTopVotersByCandidateIdの追加が必要(対応する Boundary Commitments を data-model に伝播)。 - 404 判定は単体
getCandidateの null 戻り値 + ランク済み配列の find 失敗の二段で行う(microCMS の draft 状態などの取りこぼし対策)。
- File Structure Plan から
Topic 2: 順位算出ユーティリティの再利用
- Context:
candidate-rankingspec でfeatures/candidates/utils/rankCandidatesを純関数化することにした。本 spec でも同じロジックが必要(順位は全候補者中の順位)。 - Sources Consulted:
candidate-ranking/design.md、candidate-ranking/research.md - Findings:
- 関数シグネチャ
rankCandidates(items: { id, voteCount, ... }[]) => Ranked[]は本 spec で必要な「voteCount 降順 + ID 昇順タイブレーク」と完全に一致。 - 表示順は呼び出し側の責務という方針(ranking の design 決定事項)に従い、本 spec では rank 付き配列から target ID を
findするだけ。
- 関数シグネチャ
- Implications:
- 本 spec の Allowed Dependencies に
features/candidates/utils/rankCandidatesを明示し、Out of Boundary に「rank 算出ルールの定義」を入れる。 candidate-ranking側の関数変更があれば本 spec の Revalidation Trigger になる(両 spec で同じ関数を共有する宣言が必要)。
- 本 spec の Allowed Dependencies に
Topic 3: 順位ハイライト(useOptimistic 後の rank 不変)
- Context: 要件 Feature 3 で「投票後も rank は不変」と明記。
useOptimisticで voteCount を加算しても rank を再計算してはいけない。 - Sources Consulted: 要件 Feature 3、
home/design.mdの楽観更新フック設計 - Findings:
- reducer 内で
state.voteCount += deltaのみ更新し、rankには触れない実装で要件を満たす。 - home / candidate-detail で似た振る舞いをするため、共通化の余地はあるが、画面ごとに状態形が微妙に違う(home は配列、detail は単体)ため一旦別フックでよい。
- reducer 内で
- Implications:
useOptimisticreducer のロジックを設計書に明示し、テストで rank 不変を検証する旨を Testing Strategy に記載。
Topic 4: 動的メタデータ(OGP / title)
- Context: 旧 design に
generateMetadataの記述があったが、要件には明示されていない。SEO 強化として残すか削除するかの判断が必要。 - Sources Consulted: 旧 design、Next.js 15
generateMetadataドキュメント - Findings:
generateMetadataは Server Component と同じデータ取得経路(getCandidate(id))を再利用でき、追加コストは microCMS 1 リクエストのみ。- 候補者名入り title + 画像 OGP は SNS シェアでの誘導力に直結する。
- Implications:
- 動的メタデータを non-functional 要件として design に明記。要件側に「SEO/OGP」項目を追加する余地は今後の要件改訂時に検討。
- メタデータ用の取得は Next.js が page 描画前に自動で呼ぶため、page.tsx と二重取得になるが、unstable_cache or Request Memoization(Next.js 15)で抑える方針。
Architecture Pattern Evaluation
| Option | Description | Strengths | Risks / Limitations |
|---|---|---|---|
| Server で 3 系統合成 + Client は presentation + 投票借用 | 採用 | 責務分離が鮮明、voting/candidate-ranking を再利用 | rank 付与のために全候補者の voteCount を取得する必要あり |
| Server で対象 1 件 + voteCount 1 件のみ取得 | 軽量取得 | Neon クエリが軽い | 順位算出ができない(要件 Feature 3 違反) |
| Client で順位算出 | 全候補者を Client へ送付 | Server レスポンスを 1 件に絞れる | ペイロード増大、初期描画に不利 |
Design Decisions
Decision: 404 判定を二段(単体 getCandidate + ランク済み配列 find)で行う
- Context: microCMS の draft 状態や ID 不正で
getCandidateが null を返すことがある。一方でgetCandidates()のキャッシュタイミングずれで「全候補者には含まれていないが単体取得は成功する」逆ケースも理論上ありうる。 - Alternatives Considered:
getCandidate(id)単体で 404 判定。getCandidates() + findで 404 判定。- 両方で判定し、どちらかが失敗したら
notFound()。
- Selected Approach: 3。安全側に倒す。
- Rationale: microCMS のキャッシュ整合性は完全保証されないため、二段判定でユーザーに「壊れた詳細画面」を見せないことを優先。
- Trade-offs: 微小な実装コスト増、判定の冗長性。ただし可読性は高い。
- Follow-up:
unstable_cacheの TTL を candidate / ranking で揃え、二段判定の発火頻度を抑える。
Decision: 応援者ランキングを voteAggregationService.getTopVotersByCandidateId に集約
- Context: 旧設計の
voterService.getTopByCandidateId()は data-model 契約に存在せず、新規定義が必要。 - Alternatives Considered:
- Page.tsx 内で Prisma 直接叩き。
features/candidates/services/voteAggregationServiceに追加。
- Selected Approach: 2。data-model の契約として明記し、他 spec からも再利用可能にする。
- Rationale: Page.tsx に Prisma を持ち込むと steering の services 層責務に反する。集約クエリを 1 箇所に集めることで、
Purchase.status='SUCCEEDED'JOIN ルールを単一の場所で守れる。 - Trade-offs: data-model spec への変更が伴うため、本 spec の確定前に data-model 側で当該関数の Boundary Commitments を追記する必要がある。
- Follow-up: data-model design に
getTopVotersByCandidateId(candidateId: string, limit?: number)を追記(本 spec design の Out of Boundary で参照済み)。
Decision: 動的メタデータを採用し、Server Component と取得経路を共有
- Context:
generateMetadataを別経路で実装すると保守性が落ちる。 - Alternatives Considered:
- 静的メタデータのみ(全候補者で同じ title)。
generateMetadataでgetCandidate(id)を再利用。
- Selected Approach: 2。
- Rationale: SEO/シェア導線の改善効果が大きい。Next.js 15 の Request Memoization で実質 1 回の取得に最適化される。
- Trade-offs: メタデータ取得失敗時のフォールバック(候補者未存在で
Not Foundタイトル)を明記する必要あり。 - Follow-up:
generateMetadata失敗時の挙動を Testing Strategy に追加。
Risks & Mitigations
- microCMS の整合性ずれ: 単体取得と一覧取得の結果が一時的に食い違うと、両方で 404 判定する設計で最終的に
notFound()に倒す。 - 応援者ランキング集計の重さ: ニックネーム単位 GROUP BY は Purchase JOIN を伴うため、
(candidateId, status)複合 INDEX を data-model 側で確保。 - 画像 LCP: 最初の画像のみ
priorityを付与し、それ以外は遅延読み込み(ui-design.md 委譲)。 - メタデータ取得失敗: try/catch で fallback
Not Foundを返す。Stripe / DB 障害には依存しないので影響は限定的。 - 楽観更新の rank 巻き込み: reducer テストで rank 不変を強制(Testing Strategy 必須項目)。
References
.kiro/specs/candidate-detail/requirements.md— 機能要件.kiro/specs/data-model/design.md—Candidate/voteAggregationService/nicknameService.kiro/specs/voting/design.md—<PurchaseFlowContainer>/useVoteFlow.kiro/specs/candidate-ranking/design.md—rankCandidates純関数.kiro/steering/structure.md— features / components / services 責務分離- Next.js generateMetadata
Gap Analysis — candidate-detail (2026-05-17)
Purpose: 既に承認済みの requirements / design / tasks と、現行コードベース(2026-05 リセット後の最小スケルトン)との間の実装ギャップを洗い出し、/kiro-impl candidate-detail 起動時の戦略判断を補助する情報を提供する。判断はこの分析の利用者(人間 + 後続 skill)に委ねる。
Summary
- 対象 spec:
candidate-detail(phase:tasks-generated、requirements / design / tasks すべて承認済み) - 既存実装の状態: ほぼ greenfield。
app/page.tsx/app/layout.tsx/app/providers.tsx/components/ui/*(shadcn 9 種) /design-tokens/*/lib/utils.tsのみが存在し、app/candidate/[id]/**,features/**,components/public/**,prisma/**は すべて未作成。 - 依存 spec の前提:
data-model,candidate-ranking,votingの 3 つの spec が本 spec の Allowed Dependencies(getCandidate/getCandidates/getVoteCountsByCandidateIds/getTopVotersByCandidateId/rankCandidates/RankedCandidate/useVoteFlow/useVotingPeriodStatus/<PurchaseFlowContainer>/lib/voting-period.getVotingPeriod)を所有する。すべて未実装。 - 推奨アプローチ: Option B(新規ファイルのみ)が design.md の File Structure Plan と完全一致する。前提 spec の完了を待ってから本 spec の Foundation → Core → Integration → 検証の順で
/kiro-implする正攻法が最も低リスク。 - 重大リスク: 依存パッケージ(
microcms-js-sdk/@stripe/react-stripe-js/@upstash/ratelimit/@upstash/redis)未導入、lucide-reactのバージョンが steering 想定(0.487.0+)と乖離(^1.12.0)、shadcn ^4.6.0の正体不明エントリ等、package.json側の不整合が複数。前提 spec の impl 段階で発覚するためここで指摘するに留める。
1. Current State Investigation
1.1 リポジトリ構造の実測
/home/mikamix/works/miss-world-japan/
├── app/
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx # Cormorant Garamond + Jost + Providers
│ ├── next-env.d.ts
│ ├── page.tsx # 「実装は .kiro/specs に基づいて再構築中」プレースホルダ
│ └── providers.tsx # 中身空(children pass-through)
├── components/
│ └── ui/ # shadcn: button/card/dialog/input/label/sonner/table/textarea
├── design-tokens/ # colors.ts / tokens.css / typography.ts
├── lib/
│ └── utils.ts # cn() のみ想定
├── docker-compose.yml # postgres:17 用(未確認だが steering 記述と一致)
├── prisma.config.ts # 設定ファイル(seed 用)
├── package.json # 後述
└── public/ # 静的アセット- 欠如:
app/candidate/[id]/,app/api/,app/actions/,features/,prisma/schema.prisma,prisma/migrations/,components/public/,types/,auth.ts,proxy.ts,lib/prisma.ts,lib/microcms.ts,lib/voting-period.ts,lib/date.tsのすべてが未作成。 - Boundary Map との関係: design.md の File Structure Plan で新規追加すべきファイル(
app/candidate/[id]/page.tsx,app/candidate/[id]/_components/CandidateDetailClient.tsx,features/candidates/hooks/useImageSlider.ts,components/public/{DetailHeader,CandidateImageSlider,CandidateInfo,VoterRanking,PaidVoteButton}.tsx)はすべて未存在。Modified Files は本 spec の design でも「なし」と明示されている。
1.2 既存 candidate / vote 関連の痕跡
Grepで大文字小文字無視でcandidate|Candidate|microcms|prisma|voting|voteを.kiro/node_modules/.git/docs-site/ lock files 除外で検索した結果、ヒットはpackage.json/prisma.config.ts/app/globals.css(CSS 変数名のみ) /CLAUDE.md/.claude/skills/**の 設定・指針系のみ。実装コードには痕跡ゼロ。- これは前回(2026-05-09 頃の大規模リセット)の意図と整合する: 過去の
features/voting/services/voterService.ts,features/voting/services/votingPeriodService.ts,candidateService.getAllWithRank(),app/api/images/[id]/**といった「再導入禁止」対象が物理的に存在しない状態を維持できている。
1.3 アーキテクチャ・規約の現状
app/layout.tsx:next/font/googleで Cormorant Garamond / Jost を--font-display/--font-bodyに bind 済み。steering tech.md の指針と一致。app/providers.tsx:"use client"だが Provider 集約は未着手で、<>{children}</>のみ。本 spec は新規 Provider を導入しないので影響なし。app/globals.css: 中身は未確認だがdesign-tokens/tokens.cssを読み込んでいる想定(structure.md の単一ソース原則と一致)。tsconfig.json: import エイリアス(@/*,@/features/*,@/components/*,@/design-tokens/*)は structure.md の記述通り設定済みと想定(未確認だが lint / build を通すには必須)。
1.4 ルートレベル設定ファイル
next.config.ts: 中身未確認。microCMS CDN をimages.remotePatternsに追加する必要(design.md の Performance 節 + tech.md の方針)。本 spec の Modified Files には含まれないが、candidate-detail の画像描画時に必要な前提として data-model または別タスクで処理されているか要確認。eslint.config.mjs/postcss.config.mjs: 直接編集可リスト(CLAUDE.md)の範囲。docker-compose.yml: Neon 互換のローカル DB(postgres:17)用。本 spec 単体では DB を起動しないが、E2E や統合テスト時に必要。
1.5 package.json の実装ベース整合性チェック
| 想定(steering) | 実態 | コメント |
|---|---|---|
next 15+ | next 16.2.4 | steering より新しい。App Router の API 変化(params: Promise<...>)はすでに design.md に反映済み(await params)。 |
react 19 | 19.2.4 | 一致。useOptimistic を本 spec が利用。 |
prisma 7.8.0 + @prisma/client 7.8.0 | 一致 | data-model spec 側で利用。 |
stripe 22.1.0 + @stripe/stripe-js 9.3.1 | 導入済み | voting spec 側で利用。 |
@stripe/react-stripe-js | 未導入 | voting spec の Payment Element 表示に必要。本 spec は import しないが、<PurchaseFlowContainer> を経由する以上、voting spec 実装時に追加必要。 |
microcms-js-sdk | 未導入 | data-model spec の candidateService / creditPackageService が利用。本 spec の getCandidate / getCandidates 呼び出しはすべて data-model 側経由なので、本 spec の impl 開始前に data-model 完了が必須。 |
@upstash/ratelimit + @upstash/redis | 未導入 | voting spec のレート制限で利用。本 spec には直接関係しないが、voting spec の前提。 |
react-slick + slick-carousel | 導入済み | tech.md は候補者詳細のメインスライダーとして react-slick を挙げているが、本 spec の design.md は useImageSlider 純フック + サムネイル + next/image で組み立てる方針。react-slick 採用かどうかは design.md では暗黙。tasks 2.2 にも react-slick の指定なし → 自前実装で良い余地あり。要 design/レビュー時の確認。 |
motion 12.x | 一致 | Confetti を motion で実装可。 |
lucide-react 0.487.0+ (steering) | ^1.12.0 | 異常。lucide-react は本来 0.4xx 系。^1.12.0 は別パッケージかフォーク。本 spec のアイコン使用は ui-design.md 委譲だが、依存先パッケージ確認が必要。 |
shadcn 4.6.0 | 導入済み | CLI 名と同名のランタイム依存は通常存在しない。要確認だが本 spec の影響は最小。 |
zod 4.3.6 | 導入済み | 本 spec は zod を直接使わない(voting/data-model 側)。 |
react-hook-form + @hookform/resolvers | 導入済み | 本 spec は使用しない。 |
@dnd-kit/* / csv-parse / @base-ui/react | 導入済み | 本 spec とは無関係。past artifacts と思われる。 |
本 spec はパッケージ追加を伴わない。アイコンを使う場合に
lucide-reactの不一致がブロッカーになり得るので、ui-design.md と合わせて確認すること。
2. Requirements Feasibility Analysis (Requirement-to-Asset Map)
凡例: ✅ 既設計で完全カバー / 🟡 既設計あり・依存 spec 未実装 / 🔴 Missing / ❓ Unknown(要研究) / 🧱 Constraint
| Req | Asset(設計上) | 既存実装 | Status | Notes |
|---|---|---|---|---|
| 1.1 ID で候補者取得 | data-model: getCandidate(id) 経由で page.tsx | なし | 🟡 | data-model spec 完了待ち。 |
| 1.2 404 フォールバック | page.tsx notFound() + 二段判定 | なし | 🟡 | 実装は import のみで OK。 |
| 1.3 候補者名入りメタデータ | generateMetadata | なし | 🟡 | Request Memoization に依存。data-model の契約に従う。 |
| 2.1 戻り導線 | <DetailHeader> 内 <Link> | なし | 🔴 | 単純実装。 |
| 3.1 順位・名前・得票数 | <DetailHeader> props | なし | 🔴 | toLocaleString('ja-JP') で 3 桁区切り。 |
| 3.2 ページ表示時点の順位 | rankCandidates + getVoteCountsByCandidateIds | なし | 🟡 | candidate-ranking + data-model 完了待ち。 |
| 3.3 投票後 voteCount のみ更新 | useOptimistic reducer | なし | 🔴 | reducer 内で rank に触れない明示テスト必要(tasks 4.3 で検証)。 |
| 3.4 順位は再訪問時に最新化 | Server 算出 | なし | 🔴 | Client 側で rank を再計算しない。 |
| 4.1 投票アクション UI | <PaidVoteButton> + useVotingPeriodStatus | なし | 🟡 | voting spec の useVotingPeriodStatus 完了待ち。 |
| 5.1-5.5 画像ギャラリー | <CandidateImageSlider> + useImageSlider | なし | 🔴 | 純フックは自前実装。hasMultiple 判定で 1 枚時に切替 UI 抑止。 |
| 6.1-6.2 プロフィール表示 | <CandidateInfo> | なし | 🔴 | Candidate 型の region / occupation / height / hobbies[] / certifications[] 必須。data-model 完了待ち。 |
| 7.1-7.2 PR メッセージ | <CandidateInfo> | なし | 🔴 | white-space: pre-line で改行保持。 |
| 8.1-8.4 応援者ランキング | <VoterRanking> + getTopVotersByCandidateId(id, 5) | なし | 🟡 | data-model spec で getTopVotersByCandidateId 新規定義必要(research.md Topic 1 で既出)。 |
| 9.1 投票成功フィードバック | <Confetti> + useVoteFlow | なし | 🟡 | voting spec の useVoteFlow.handlePaidVoteConfirmed 完了待ち。<Confetti> は home spec 所有でも可(tasks 3.1)。 |
| 非機能-メタデータ | generateMetadata OGP | なし | 🟡 | 1.3 と同じ。 |
| 全画面共通: 戻るボタン / フッター / Scroll Top | layout 側 or 共通 components | 未確認 | ❓ | product.md 全画面共通要件。本 spec の責務外だが、layout 完成度を要確認。 |
Constraints / Unknowns
- 🧱 保護パス制約:
features/**,app/candidate/**,app/_components/**,components/public/**,auth.ts,prisma/schema.prisma,lib/prisma.ts,types/**は/kiro-impl経由のみ。本 spec の impl がこれらの新規作成権限を持つ。 - 🧱 Request Memoization 契約: data-model 側で
getCandidateをReact cache()かfetchnext.revalidate を直接使う実装でなければ、generateMetadataとpage.tsxで二重取得が発生。本 spec の Performance 想定が崩れるので data-model design に明示が必要(既に design.md Allowed Dependencies / Revalidation Triggers に記載済み)。 - 🧱
Vote.nickname正規化済み保存: 応援者ランキング集計の正しさは voting spec の Webhook ハンドラがnormalizeNickname適用済み値を保存していることに依存(design.md Req 8.3 で明示済み)。 - ❓
react-slick採用可否: 本 spec の design.md は自前useImageSliderを採用するが、tech.md はreact-slickを挙げている。react-slickを採用しない場合、tech.md との乖離をどうするか要判断(steering 更新 or design 維持)。 - ❓ microCMS
images.remotePatterns設定:next.config.tsへの microCMS CDN ホスト追加は誰が行うか(data-model / 全画面共通)。本 spec impl 時に未設定だとnext/imageが拒否する。前提タスクとして要確認。 - ❓
<Confetti>の所有 spec: design.md はvotingかhomespec 所有を示唆。tasks 3.1 に「<Confetti>(home spec の Confetti を共有してよい)」と明記。homespec の Confetti 実装状況を要確認。 - ❓ 共通 layout の Footer / ScrollToTopButton: 詳細画面でも product.md の全画面共通 UI を提供する必要。本 spec の Modified Files は「なし」なので、layout 側の整備状況に依存。
複雑度シグナル
- Simple CRUD: なし(本 spec は描画専用)。
- Algorithmic: 順位算出は別 spec 所有の純関数を再利用するため、本 spec 内に複雑なアルゴリズムは持たない。
- Workflow: 投票成功時の楽観更新フローを 1 つ持つが、状態機械は
useVoteFlow(voting 所有)に集約。 - External integration: microCMS / Neon / Vercel ENV は すべて data-model 経由で間接アクセス。本 spec から直接外部呼び出しはなし。
3. Implementation Approach Options
Option A: 既存ファイルを拡張する
該当ファイル: なし。本 spec が触る範囲はすべて新規作成のため、Option A の前提が成立しない。
Trade-offs: N/A(候補からは除外)。
Option B: 新規ファイルのみで構築する(design.md の File Structure Plan に従う) — 第一候補
新規作成ファイル:
app/candidate/[id]/page.tsx(Server Component)app/candidate/[id]/_components/CandidateDetailClient.tsx(Client)features/candidates/hooks/useImageSlider.ts(純フック)components/public/DetailHeader.tsxcomponents/public/CandidateImageSlider.tsxcomponents/public/CandidateInfo.tsxcomponents/public/VoterRanking.tsxcomponents/public/PaidVoteButton.tsx
統合ポイント:
- 上記すべての import 先(
@/features/cms/services/candidateService等)は data-model / candidate-ranking / voting spec が所有・実装する。 - 本 spec の impl 時点で前提 spec が
/kiro-impl済みであることを必須条件とする(tasks.md 冒頭の前提宣言と一致)。
Trade-offs:
- ✅ design / tasks と完全一致。レビューが最も簡素。
- ✅ 保護パス境界が明確で、
/kiro-impl candidate-detailの自律実行に乗りやすい。 - ✅ 既存実装ゼロのため、後方互換性の懸念がない。
- ❌ 前提 spec(data-model, candidate-ranking, voting)未完了のままでは本 spec を起動できない(直列依存)。
推奨: 第一候補。tasks.md の Foundation 1.1 → Core 2.x → Integration 3.x → 検証 4.x の順で進める。
Option C: ハイブリッド(本 spec のスタブを先行作成し、前提 spec を平行で進める)
戦略:
- 本 spec の
components/public/*.tsxを 依存 import なしの presentation-only stub として先に作成し、props 契約のみ確定。 - 並行で data-model / voting / candidate-ranking spec を
/kiro-implで進める。 - 前提が揃った段階で
app/candidate/[id]/page.tsxと<CandidateDetailClient>を Integration する。
Trade-offs:
- ✅ UI レビュー(Storybook 想定)を先行できる。
- ✅ ui-design.md の検証を並走できる。
- ❌ 保護パス制約により
features/candidates/hooks/useImageSlider.ts等は/kiro-impl candidate-detail経由でしか作れないため、stub と本実装で/kiro-implを 2 回回す必要がある(workflow 上のオーバーヘッド)。 - ❌ tasks.md の
_Depends順序を分断するため、autonomous mode との相性が悪い。 - ❌ 前提 spec が完成すれば Option B との差が消えるため、得るものが少ない。
推奨度: 低。並走の必要が明示された場合のみ採用。
4. Effort & Risk
| 観点 | 評価 | 根拠(1 行) |
|---|---|---|
| Effort | M(3–7 日) | 新規 5 コンポーネント + 1 純フック + 1 Server Component + generateMetadata + テスト群。前提 spec が揃えば 1 ファイル 1〜4 時間。 |
| Risk | Medium | 単体パターンは確立済みだが、(a) 前提 spec の契約変更追従、(b) useOptimistic での rank 不変保証、(c) Request Memoization に頼った Performance 想定、の 3 点が外部要因で揺れる可能性。 |
リスクと緩和策
- R1 前提 spec 未完了: data-model / candidate-ranking / voting のいずれかが impl 未完了なら本 spec を起動しない。
/kiro-spec-statusで確認してから/kiro-impl candidate-detailする。 - R2 Request Memoization 失効: data-model 側の実装が
cache()orfetch revalidateでないと、generateMetadataとpage.tsxで microCMS を二重に叩く。Performance 想定が崩れたら本 spec の Revalidation Trigger 発火 → data-model 修正 → 本 spec 再検証。 - R3
useOptimisticreducer の rank 上書き混入: tasks 4.3 で「rank 不変」を必ずアサート。レビュー観点でも明示。 - R4
react-slickvs 自前 slider の方針乖離: tech.md と design.md の整合性レビューを ui-design.md 確認時に同時実施。 - R5 microCMS
images.remotePatterns未設定: 本 spec の impl 時にnext/imageが microCMS CDN を拒否してビルドが落ちる。data-model spec か別の全画面共通タスクでnext.config.tsに host を追加しておくこと。 - R6
<Confetti>の所有 spec 不明: home spec 側の実装が未着手なら、本 spec で<Confetti>をどう調達するか追加判断が必要。tasks 3.1 は home 側を再利用する書き方になっているので、home spec の status を要確認。 - R7 共通 layout 整備: フッター・ScrollToTopButton・候補者一覧/ランキング戻り導線(Header)などの全画面共通 UI が
app/layout.tsxに未組み込み。本 spec の Header は<DetailHeader>内で戻り導線を持つ設計だが、全画面共通フッターは別管轄。
5. Recommendations for Design Phase / Impl Phase
design / tasks はすでに承認済みのため、design phase の再起動は不要。以下は
/kiro-impl candidate-detailを回す前のチェックリストとして提供する。
起動前チェックリスト
- ✅
/kiro-spec-status data-modelでtasksがcompleted(かつ実装ファイルが存在)であることを確認。 - ✅
/kiro-spec-status candidate-rankingでtasksがcompleted(rankCandidates/RankedCandidateが export 済み)。 - ✅
/kiro-spec-status votingでtasksがcompleted(useVoteFlow/useVotingPeriodStatus/<PurchaseFlowContainer>が import 可能)。 - ✅
next.config.tsのimages.remotePatternsに microCMS CDN ホストが含まれていること(なければ data-model または別 spec で追加)。 - ✅
package.jsonにmicrocms-js-sdk/@stripe/react-stripe-js/@upstash/ratelimit/@upstash/redisが追加されていること(なければ前提 spec の impl で追加されるはず)。 - ✅
lucide-react ^1.12.0の正体を確認(本物 lucide-react か別パッケージか)。アイコン採用箇所が ui-design.md にあれば必須。 - ✅
<Confetti>の所有 spec が決定済み(home / voting / candidate-detail のいずれか)。
推奨アプローチ
- Option B(新規ファイル)を採用。tasks.md の
_Dependsを尊重し、autonomous mode(/kiro-impl candidate-detail)で subagent per task に任せる。 - 並走の必要があれば Option C を取るが、
/kiro-implの境界を 2 回横断するため工程管理コストが上がる点を許容できる場合に限る。
Research items to carry forward(impl 中に再評価)
- 画像スライダーの実装ライブラリ選定:
react-slickを採用するか自前useImageSliderで完結させるか。ui-design.md と design.md tasks 2.2 を突き合わせて確定する。 <Confetti>の依存解決: home spec 側で実装されている場合は import、未実装なら本 spec で同時実装するか home 側の/kiro-implを先行する。- layout 側の全画面共通要素:
Footer/ScrollToTopButtonの所有 spec を確認し、本 spec impl 中に layout が空のままなら一時的に candidate-detail 範囲外として記録する。 next.config.tsのimages.remotePatterns: data-model spec の impl 段階で確実に設定されたか、本 spec impl 開始時に再確認。- Request Memoization の実証: data-model 側の
getCandidate実装がcache()ベースかfetchベースかを確認し、本 spec の Performance 想定が成り立つことを実機で確認(tasks 3.2 の Network タブ確認手順)。
Output Checklist 達成状況
- [x] Requirement-to-Asset Map(Section 2)— Missing / Unknown / Constraint タグ付き
- [x] Options A/B/C(Section 3)— rationale + trade-offs
- [x] Effort(M) / Risk(Medium)— 1 行根拠
- [x] Recommendations(Section 5)— 推奨アプローチ + research items