テーマ
Research & Design Decisions — candidate-ranking
Purpose: candidate-ranking spec の design 書き直し(data-model の 3 系統契約への適合)で行った調査・統合判断を記録する。
Summary
- Feature:
candidate-ranking - Discovery Scope: Simple Addition(閲覧専用画面、ロジックが薄い)
- Key Findings:
- 旧設計は Prisma 直叩きの
candidateService.getAllRanked()を持っていたが、新 data-model 契約では候補者は microCMS、得票は Neon JOIN 集計から合成する必要がある。 - 投票導線を持たないため、
useVoteFlow/useReorderAnimation/ Purchase モーダルへの依存を完全に削除できる。 - rank 算出ロジックを
features/candidates/utils/rankCandidates.tsの純関数として切り出し、home 側でも将来再利用可能にする。
- 旧設計は Prisma 直叩きの
Research Log
Topic 1: 旧設計とのギャップ
- Context: 旧 candidate-ranking design は
candidateService.getAllRanked()単独で完結していたが、新 data-model では責務が分離されている。 - Sources Consulted:
candidate-ranking/design.md(旧)、data-model/design.md(新) - Findings:
getCandidates+getVoteCountsByCandidateIdsを Server Component で合成する必要あり。- share 計算は Server で実施し、Client は表示のみが steering の features/components 分離に最も整合。
- Implications:
- 本画面は最も薄い「Server 合成 + presentation」パターンの教科書例になる。
- rank 算出を純関数化し home/ranking 両方で参照可能にする余地を確保。
Topic 2: 投票導線排除の徹底
- Context: 要件冒頭に「閲覧専用」と明記されているにも関わらず、旧設計のコードベースに
useVoteFlowの参照が残る可能性がある(home 等から流入)。 - Sources Consulted: 要件 Feature 4、
candidate-ranking/design.md(旧)、steering/structure.md - Findings:
- 旧設計の「設計の前提」セクションが既に「投票導線を一切持たない」と宣言済み。これを Boundary Commitments に格上げし、Out-of-scope で
useVoteFlow等を再導入禁止と明示する。
- 旧設計の「設計の前提」セクションが既に「投票導線を一切持たない」と宣言済み。これを Boundary Commitments に格上げし、Out-of-scope で
- Implications:
<RankingItem>の Props からonPaidVote/voteStateを完全排除。- Migration Strategy に「投票導線参照の削除」を明記。
Topic 3: rank 算出ユーティリティの所在
- Context: home と ranking で似た rank 算出をする。home spec の design では Server Component 直接記述だったが、ranking では純関数化したい。
- Sources Consulted: home/design.md、本 spec の Architecture
- Findings:
- 関数自体は
(items: { voteCount }[]) => { rank }[]の単純な純関数で、共通化のコストは低い。 features/candidates/utils/rankCandidates.tsに置くことで、home spec の改訂時に同じ関数を import できる。
- 関数自体は
- Implications:
- 本 spec の File Structure Plan に
features/candidates/utils/rankCandidates.tsを新規追加。 - home spec の
Decision: rank 算出を Server Component に置くを「将来的に純関数化を検討」と整合させる。
- 本 spec の File Structure Plan に
Architecture Pattern Evaluation
| Option | Description | Strengths | Risks / Limitations |
|---|---|---|---|
| Server で全データ確定 + Client は presentation | 採用 | 責務分離が最も鮮明、テストしやすい | フックを使わないため、状態追加時に拡張が必要 |
| Client Component で rank と share を再計算 | クライアント計算 | サーバー負荷低 | data-model 集計値の二重表現、テスト面倒 |
Design Decisions
Decision: share 計算を Server に置く
- Context: share = voteCount / maxVotes はクライアントでも計算可能だが、責務分離を優先。
- Selected Approach: Server Component で算出し、Client には数値で渡す。
- Rationale:
components/層に計算ロジックを置かない steering 方針(structure.md)に整合。<VoteShareBar>は数値を受け取って描画するだけになり、テストが純粋に props ベースになる。 - Trade-offs: Server レスポンスサイズが微増(各候補者あたり 1 数値)するが、無視可能。
Decision: rankCandidates を純関数として utils/ に置く
- Context: home / ranking 双方で voteCount 降順 + ID 昇順タイブレーク + rank 付与を行うため。
- Selected Approach:
features/candidates/utils/rankCandidates.tsに純関数として配置。 - Rationale: home 側でも将来採用する余地を残す。テストが容易。
- Trade-offs: home/ranking で表示順が異なる(home は id 昇順表示、ranking は voteCount 降順表示)ため、関数自体は rank を付けるだけにとどめ、表示順は呼び出し側の責務とする。
Risks & Mitigations
- 空配列・全員 0 票:
maxVotes = 0の場合に share=0 を返す分岐をテストでカバー。 - 大量候補者: 候補者数は通常 20-30 名と想定。N=100 程度までは O(N log N) のソートで問題なし。
- 画像 LCP: 上位候補者の画像はメインカードで先に読み込まれているケースが多い。本画面では
priorityを上位 3 位のみに付ける選択肢をui-design.md側で検討。
References
.kiro/specs/candidate-ranking/requirements.md— 機能要件.kiro/specs/data-model/design.md—Candidate/voteAggregationService.kiro/specs/home/design.md— 同等の Server 合成パターン.kiro/steering/structure.md— features / components / utils 責務分離
Gap Analysis: candidate-ranking ↔ 既存コードベース (2026-05-17)
本セクションは kiro-validate-gap skill 実行による要件 ↔ 既存実装のギャップ調査結果。情報提供であり決定ではない。
1. Current State
1.1 既存コードベース実態
調査対象パス: app/, features/, components/public/, app/_components/, prisma/, lib/
| パス | 状態 | 内容 |
|---|---|---|
app/page.tsx | 存在 | 単純な「再構築中」プレースホルダのみ |
app/layout.tsx | 存在 | Cormorant Garamond / Jost フォント + Providers ラッパのみ |
app/providers.tsx | 存在 | "use client" で children を素通しするだけの空 Provider |
app/globals.css | 存在(7.4 KB) | 内容未確認だがデザイントークン定義想定 |
app/ranking/ | 存在しない | 本 spec の主担当ディレクトリが未作成 |
app/candidate/[id]/ | 存在しない | 詳細画面遷移先(要件 4.1)が未作成 |
app/_components/ | 存在しない | ページローカル client 配置先が未作成 |
app/api/ | 存在しない | (本 spec では未使用なので影響なし) |
app/actions/ | 存在しない | (本 spec では未使用なので影響なし) |
features/ | 存在しない | cms/services/candidateService, candidates/services/voteAggregationService, candidates/utils/rankCandidates のいずれも未実装 |
components/ui/ | 存在 | shadcn プリミティブ用ディレクトリのみ。中身は確認していないが Button/Card 等が想定される |
components/public/ | 存在しない | RankingHeader / RankingList / RankingItem / RankIcon / VoteShareBar / BackToHomeLink のいずれも未実装 |
prisma/ | 存在しない | schema.prisma、migrations、seed.ts いずれも未生成 |
lib/ | 存在 | utils.ts(cn() 関数のみ)。prisma.ts / microcms.ts / voting-period.ts は未実装 |
auth.ts, app/admin/, app/api/images/ | 存在しない | steering 方針通り「保有しない」状態が維持されている |
1.2 依存パッケージ状況(package.json)
| 必要ライブラリ | 状態 | 備考 |
|---|---|---|
next 16.2.4 | 導入済み | App Router 利用可 |
react 19.2.4 / react-dom | 導入済み | Server Components 利用可 |
@prisma/client 7.8 / prisma 7.8 | 導入済み | スキーマ未作成 |
@prisma/adapter-pg 7.8 | 導入済み | Neon 用アダプタ |
clsx, tailwind-merge, class-variance-authority | 導入済み | shadcn 標準セット |
tailwindcss v4 | 導入済み | デザイントークン展開可 |
lucide-react 1.12.0 | 導入済み | Trophy / Medal / Award / TrendingUp / ChevronLeft アイコン取得可(ui-design.md 要求) |
motion 12.x | 導入済み | (本 spec では使わないが steering で容認) |
microcms-js-sdk | 未導入 | data-model 完了で導入される想定 |
zod 4.3 | 導入済み | (本 spec では未使用) |
1.3 tsconfig.json の paths ギャップ
現状の paths 定義は以下のみ:
json
{
"@/*": ["./*"],
"@/components/*": ["./components/*"],
"@/design-tokens/*": ["./design-tokens/*"]
}steering/structure.md「import エイリアス(実態)」に列挙されている @/features/* が 未定義。design.md の以下 import を成立させるには追加が必要:
@/features/cms/services/candidateService@/features/candidates/services/voteAggregationService@/features/candidates/utils/rankCandidates
ただし @/* で ./* を解決可能なため、明示的に @/features/* を追加しなくても @/features/cms/services/candidateService は解決される。steering/structure.md の理想形に合わせて追加することは推奨だが、本 spec の実装ブロッカーではない。
2. Feasibility
2.1 要件への対応可否
| 要件 | 既存資産で実現可能か | 不足 |
|---|---|---|
| Req 1.1 降順ソート表示 | 不可 | getCandidates / voteAggregationService / rankCandidates の 3 つすべて未実装 |
| Req 1.2 ホーム戻り導線 | 不可 | <BackToHomeLink> 未実装、戻り先の /(home spec)もプレースホルダ状態 |
| Req 2.1 順位行の表示要素 | 不可 | <RankingItem> / <RankIcon> / <VoteShareBar> 未実装。next/image の remotePatterns に microCMS ホストも未設定 |
| Req 3.1 1-3 位の視覚区分 | 不可 | UI コンポーネント未実装 |
| Req 4.1 詳細遷移 | 部分的に不可 | リンク先 /candidate/[id] がまだ存在しないため、リンクは張れても遷移後 404 となる。candidate-detail spec 完了が前提 |
| Req 5.1 同点 ID 昇順タイブレーク | 不可 | rankCandidates 純関数未実装 |
| エッジケース: 0 票 / 候補者数 < 3 | 不可 | 描画コンポーネント未実装 |
2.2 上流依存
本 spec の実装には以下 spec が 先に完了している必要 がある:
data-model—Candidate型、getCandidates、voteAggregationService.getVoteCountsByCandidateIds、lib/microcms.ts、next.config.tsのimages.remotePatterns(候補者画像表示の前提)candidate-detail(部分依存) — Req 4.1 の遷移先。リンク自体は本 spec で張れるが、E2E テストで遷移検証する場合に必要
home spec とは独立(双方が rankCandidates を import する関係)。voting spec とは独立(本画面は閲覧専用で投票導線を持たないため)。
2.3 競合・衝突するコードは存在しない
旧設計が懸念した「useVoteFlow の残存」「candidateService.getAllRanked() の Prisma 直叩き」「<RankingItem> の Props に onPaidVote が残る」等の負債は、a443022 以前の大規模リセットで既に消滅している。現状はゼロからの新規実装。
3. Options
Option A: design.md 通り 1 PR でフル実装
/kiro-impl candidate-ranking を発火し、以下を一括で作る。
features/candidates/utils/rankCandidates.ts(純関数 + ユニットテスト)app/ranking/page.tsx(Server Component)app/ranking/_components/RankingPageClient.tsxcomponents/public/RankingHeader.tsx/RankingList.tsx/RankingItem.tsx/RankIcon.tsx/VoteShareBar.tsx/BackToHomeLink.tsx
Strengths
- design.md と 1:1 対応するため、tasks.md の各タスクが PR 内タスクと素直に整合
- 純関数のテストが本 spec 内に閉じる
- 完了後に
/rankingを開けば画面動作確認が可能
Risks / Limitations
data-modelが未完了だとgetCandidates/voteAggregationServiceが import できないため、data-model完了が物理的なブロッカーcandidate-detail未完了の場合、行クリックの遷移先が 404 になる(画面表示自体には影響しないが E2E テスト未通過)<RankingItem>のnext/image表示にはnext.config.tsのimages.remotePatterns更新が必要 — 本 spec の保護パス外(data-model担当)で更新される想定なので、ここを跨ぐ場合の責務境界を明確化すること
Option B: rankCandidates 純関数だけを先行マージ(2 PR 分割)
- PR-1:
features/candidates/utils/rankCandidates.tsのみ + Vitest テスト - PR-2:
app/ranking/**とcomponents/public/Ranking*をdata-model完了後に積み上げ
Strengths
data-model完了を待たずに着手できる(純関数はCandidate型のシェイプだけ参照)- home spec が将来
rankCandidatesを採用する際の準備として早期に共有可能 - 純関数の契約(要件 5.1 タイブレーク)を独立にレビューできる
Risks / Limitations
- 1 spec が 2 PR にまたがるため、
/kiro-implの autonomous モードを 2 回走らせるか、tasks.md を分割実行する必要あり - 純関数だけマージ → 利用箇所なしのデッドコード期間が短期間生まれる
- Vitest が
package.jsonにまだ無いため、テストランナー導入の判断が混じる(steering では Vitest 採用方針だが現状 devDependencies に未追加)
Option C: data-model + candidate-ranking を統合 PR でまとめて投入
data-model 完了済みであることが前提なので、現状はこの選択肢は事実上 Option A と同じになる。data-model がまだの場合は data-model を先に流す方が安全(本 spec の保護パスを超えて prisma/, lib/, features/cms/ に触る必要があるため)。
4. Effort & Risk
4.1 工数の目安(Option A の場合)
| 区分 | 内容 | 概算ファイル数 |
|---|---|---|
| 新規作成 | features/candidates/utils/rankCandidates.ts | 1 |
| 新規作成 | app/ranking/page.tsx + _components/RankingPageClient.tsx | 2 |
| 新規作成 | components/public/Ranking* 系 6 ファイル | 6 |
| 新規作成 | 各コンポーネントのテスト(任意、Vitest 未導入なら後追い) | 0〜6 |
| 既存変更 | なし(保護パスでの変更なし) | 0 |
| 合計 | 9〜15 |
4.2 主要リスク
| リスク | 重大度 | 緩和策 |
|---|---|---|
data-model 未完了で getCandidates / voteAggregationService import 不可 | 高 | 着手前に /kiro-spec-status data-model で完了を確認。未完なら data-model を先行 |
next.config.ts の images.remotePatterns 未設定で microCMS 画像が next/image に弾かれる | 中 | data-model 完了で解消。本 spec の保護パス外(next.config.ts は直接編集可)なので必要なら明示依頼 |
@/features/* エイリアス未定義 | 低 | @/* で代替可能、優先度低 |
| Vitest 未導入で純関数の単体テストが書けない | 中 | テスト導入は本 spec のスコープを超える。テストファイルだけ用意して後追いで CI 接続も可 |
行クリック先 /candidate/[id] が 404 になる | 低(機能要件は満たす) | candidate-detail 完了後に E2E で再検証 |
Candidate 型の displayName フィールド名 design.md 参照ズレ | 低 | design.md(本 spec)では candidate.displayName を使用。features/cms/types.ts のフィールド名と一致するかを実装時に確認 |
5. Output Checklist
- [x] Current State: 既存コードの実態を列挙(主要パスは全て 未実装か空のプレースホルダ)
- [x] Feasibility: 要件 1.1-5.1 と既存資産の対応表
- [x] 上流依存:
data-model完了が物理的ブロッカーであることを明示 - [x] Options A/B/C: それぞれ Strengths と Risks/Limitations を列挙
- [x] Effort & Risk: 概算ファイル数とリスクテーブル
- [x] 競合コードの不存在(リセット後の新規実装で済むこと)を確認
6. 推奨される次の一手(情報提供のみ)
/kiro-spec-status data-modelで前提 spec の状態を確認- data-model が
implemented相当であれば、本 spec は Option A(1 PR でフル実装) が最短経路 - data-model が未完であれば、先に
/kiro-impl data-modelで前提を整える tsconfig.jsonへの@/features/*追記、next.config.tsのimages.remotePatterns更新は data-model spec の責務に含まれているか、/kiro-spec-status data-modelで確認しておくと境界がクリーンになる- テスト(Vitest)の導入是非は本 spec のスコープ外。
rankCandidates純関数のテストは導入後に追記する形でも要件達成には支障なし