Skip to content

Research & Design Decisions — home


Purpose: home spec の design 書き直し(data-model の 3 系統契約への適合)で行った調査・統合判断・トレードオフを記録する。

Summary

  • Feature: home
  • Discovery Scope: Extension(既存トップページの実装基盤を 3 系統データアーキテクチャに合わせて再設計)
  • Key Findings:
    • 旧 home design は Prisma 単独で candidateService.getAllWithRank() を持ち、/api/images/[id] 経由で画像配信していた。新 data-model では候補者・画像とも microCMS、画像は CDN URL を直接利用。
    • VotingPeriod は DB シングルトンから Vercel ENV へ移管済み。home/page.tsx は lib/voting-period.ts 経由で取得し、失敗は error.tsx で表現する。
    • 得票集計は voteAggregationService.getVoteCountsByCandidateIds 経由で Purchase.status='SUCCEEDED' を JOIN フィルタする方式。_count.votes(status='ACTIVE') は廃止。

Research Log

Topic 1: 旧 home design とのギャップ

  • Context: 旧 design は data-model がリセット前提だった頃に書かれ、Prisma の _count.votesVotingPeriod テーブル、/api/images/[id] を前提に組まれていた。
  • Sources Consulted: home/design.md(旧)、data-model/design.md(新)、steering/tech.mdsteering/structure.md
  • Findings:
    • candidateService.getAllWithRank() は Prisma 直叩きで rank 算出も内包していた → 新設計では Server Component (app/page.tsx) が 3 系統合成と rank 付与を担う。
    • votingPeriodService.get() は DB から取得し null を返していた → 新設計は getVotingPeriod() が throw する。null フォールバックではなく error.tsx の責務。
    • images[0].id + updatedAt/api/images/{id}?v=... の組み立て箇所が散在していた → microCMS の CDN URL を next/imagesrc に直渡しに置換。
  • Implications:
    • app/page.tsx を Server で 3 系統合成 + rank 算出する責務に書き直す必要がある。
    • 旧 service / API 経路は Migration Strategy で削除対象として明示。

Topic 2: rank 一元化と楽観更新の整合

  • Context: 要件 Feature 4 は「順位の最新化はページ再訪問時とする」と明確に規定しており、投票成功時に rank を変えない実装が必要。
  • Sources Consulted: home/requirements.md Feature 4、React 19 useOptimistic ドキュメント
  • Findings:
    • Server 側で voteCount 降順 + id 昇順タイブレーク の rank を確定し、Client に渡す。
    • useOptimistic の reducer は voteCount のみ書き換え、rank を据え置く。useCandidateListWithRank は再計算を行わない。
  • Implications:
    • フック契約に「rank 再計算禁止」を明文化(useCandidateListWithRank の Invariants)。
    • Testing Strategy で「楽観更新後も rank が不変」を Unit テストで担保。

Topic 3: <CandidateCard> の Props 純化

  • Context: 旧設計では <CandidateCard> が voteLabel / voteDisabled を内部で算出していた可能性があり、責務がぼやけていた。
  • Sources Consulted: 旧 home design、steering/structure.md(features/components 分離)
  • Findings:
    • 期間内/外判定は useVotingPeriodStatus が一元化し、ラベルと disable は <HomePageClient> から props として渡す。
    • カードは presentation のみで、ビジネスロジックを持たない。
  • Implications:
    • <CandidateCard> の Props を voteLabel / voteDisabled を明示的に受ける形に固定。
    • これにより candidate-detail / 他画面でカードを再利用する際の振る舞いを揃えやすい(将来拡張余地)。

Architecture Pattern Evaluation

OptionDescriptionStrengthsRisks / LimitationsNotes
旧設計: Server が全データを candidateService.getAllWithRank() で取得単一サービスで rank 算出まで完結API が単純data-model が microCMS / Neon に分かれた今は責務が逆転不採用
採用: Server Component が 3 系統合成 + rank 確定、サービス層は薄い責務分離が明確、data-model の契約に従う合成ロジックが Server Component に集まるServer Component の単体テスト粒度を統合テストで補完data-model の境界に沿う
Client 側で rank 再計算リアルタイム順位反映要件 4.2 に違反、UX 上も視点ブレ採用不可不採用

Design Decisions

Decision: rank 算出を Server Component (app/page.tsx) に置く

  • Context: data-model の voteAggregationService は集計値(Map)のみ返し、rank 付与は呼び出し側責務。home / ranking で rank の意味が異なる(home は id 昇順表示で rank フィールド保持、ranking は voteCount 降順で再ソート)。
  • Alternatives Considered:
    1. voteAggregationService 側に rank 付与関数を持たせる。
    2. ページごとに Server Component が rank 算出する。
  • Selected Approach: 2。home/ranking 双方でアルゴリズムは同じだが、表示順が異なるため呼び出し側で完結させる方がカップリングが低い。
  • Rationale: data-model は「集計値の真実」のみ提供。表示順序と rank フィールドの意味は画面 spec の責務。
  • Trade-offs: rank 算出ロジックが home/ranking で重複する可能性 → 純関数として共通 util に切り出す余地はあるが、現状は重複コストが低く許容。
  • Follow-up: ranking spec の design 書き直し時に「同じ rank 算出関数を共有するか」を検討。

Decision: 投票期間取得失敗は error.tsx のみで表現する

  • Context: 旧設計は votingPeriod === null を許容しページ内分岐していたが、新 lib/voting-period.ts は throw する。
  • Alternatives Considered:
    1. Server Component で catch して <ErrorPage> を return。
    2. throw のまま error.tsx に伝播。
  • Selected Approach: 2。Next.js の error boundary を使う方が pure で、HomePageClient を誤って呼ばないことを構造で保証できる。
  • Rationale: 要件 1.4「ヘッダー・候補者一覧の描画を行わない」を満たす最も確実な手段。
  • Trade-offs: error.tsx の文言が他のエラーと共通になるが、InvalidVotingPeriodErrorcause を見て分岐すれば差別化可能。
  • Follow-up: app/error.tsx の整備タスクを kiro-spec-tasks home で出す。

Decision: 画像は microCMS CDN URL を直接 next/image に渡す

  • Context: 旧設計は自前 /api/images/{id}?v=... を経由していた(Prisma で BLOB を持っていた時代の名残)。
  • Alternatives Considered:
    1. 自前 API を残し、microCMS から取得した URL をプロキシ。
    2. next.config.tsimages.remotePatterns で microCMS ドメインを許可して直渡し。
  • Selected Approach: 2。data-model の Out-of-scope に「自前画像配信」が含まれており、再導入は禁止。
  • Rationale: パフォーマンス・コスト・設計境界のすべてで優位。
  • Trade-offs: microCMS の URL 形式変更に追随する必要があるが、microCMS は安定。
  • Follow-up: next.config.ts 修正タスクは data-model 側で先行。home 側は consumer。

Risks & Mitigations

  • Server Component のテストカバレッジ: 合成ロジックを Server Component に集めたため、単体テストが書きにくい。→ services をモックして統合テストで担保。
  • rank 算出のロジック重複: home / ranking が同じ計算をする。→ 純関数化を ranking spec design で再評価。
  • クライアント時計依存の期間判定: useVotingPeriodStatus がクライアント時計に依存。→ サーバー側 Server Action(voting spec)で必ず再判定する規約を維持。
  • useOptimistic の API 変更: React 19 の experimental flag が外れている前提。→ 依存バージョン固定 + リリースノート監視。

References


Gap Analysis — home (2026-05-17 追記)

Purpose: home spec の requirements / design に対し、現行コードベースとの差分を棚卸しし、design phase に持ち込む実装戦略の候補と前提を確定する。

1. Current State Investigation

既存資産の棚卸し

カテゴリ期待される場所現状備考
Home Server Componentapp/page.tsxプレースホルダー(再構築中です 文字列のみ)spec で前提とする 3 系統合成は未着手
Home Client Componentapp/_components/HomePageClient.tsx未作成_components ディレクトリ自体が存在しない
Layout / Providersapp/layout.tsx / app/providers.tsx最小構成のみ(フォント変数 + 空 Provider)背景装飾・グローバル状態は未配置
デザイントークンdesign-tokens/tokens.cssgold / rose-gold / ivory / charcoal / shadow-card / shadow-modal が定義済みui-design.md のパレット要件を充足
グローバル CSSapp/globals.cssshadcn ベース + brand color エイリアス + fade-in-up / card-fade-in / confetti-burst / shimmer keyframes が既に存在アニメーション仕様(ui-design.md)とほぼ整合
UI プリミティブcomponents/ui/button / card / dialog / input / label / sonner / table / textarea のみshadcn 基本セットは揃うが home 固有 presentation は皆無
公開向け presentationcomponents/public/ディレクトリ未作成HomeHero / CandidateGrid / CandidateCard / Confetti 等すべてゼロから
features 層features/**ディレクトリ未作成voting / candidates / cms / payment / voters のすべてが未実装
lib(画面層が依存)lib/voting-period.ts / lib/microcms.ts / lib/prisma.ts未作成(lib/utils.ts のみ存在)これらは data-model spec の所有物
Prisma スキーマprisma/schema.prisma未作成data-model 所有。home からは不可視
next.config.tsルート設定なし({})。images.remotePatterns 未設定microCMS CDN URL を next/image に渡すための前提が満たされない
error boundaryapp/error.tsx未作成spec design Flow 3 で必要
Storybook / テスト基盤.storybook/ / vitest.config.* / Playwright未導入tasks.md の 4.14.4 検証タスクの前提が未整備

規約・依存方向の確認

  • steering/structure.md の 3 層モデル(app → features / app → components)はまだコード上に投影されていない。components/ には UI プリミティブのみ存在し、public/ 区分も未作成のため、新規ファイルはすべて新しいディレクトリの「初投入」となる。
  • app/_components のページローカル分割パターン(steering の「現行実装メモ」)は採用予定だが未投入。home tasks 3.1 で初出。
  • 依存方向の違反は 現時点では存在しない(まだほぼ何もないため)。design.md 通りに進めれば違反は発生しない構造になっている。

統合面・データ取得経路

  • microCMS / Neon / Vercel ENV のクライアントインスタンスは未作成 → home 単独では import 不可。data-model spec 完了が コード上のハードブロッカー
  • next/image を microCMS CDN ホストに向けるための next.config.ts > images.remotePatterns。home 実装に進む前に data-model 側で更新される必要がある。

依存パッケージの状況(package.json レベル)

必要ライブラリ状態備考
next 16 / react 19 / react-dom 19導入済みApp Router・useOptimistic 利用可能
motion 12導入済みConfetti / hover アニメで利用
lucide-react導入済みTrendingUp 等のアイコン
next/font/google(Cormorant Garamond / Jost)layout.tsx で読み込み済みui-design.md の display / body フォント要件を充足
microcms-js-sdk未導入data-model 側で導入予定。home からは間接利用
@prisma/client@^7 / @prisma/adapter-pg@^7導入済みdata-model 側でスキーマ・クライアント生成が必要
tailwindcss 4 / tw-animate-css導入済みスタイリング基盤あり
Storybook / Vitest / Playwright未導入tasks 4.x のテスト計画は将来導入分に依存

2. Requirements Feasibility Analysis

Requirement-to-Asset Map

Req要件サマリ必要なアセット現状ギャップ種別
1.1Home 表示時にヘッダー + 候補者一覧描画Server Component が 3 系統合成app/page.tsx プレースホルダMissing
1.2サイトタイトル「Miss World Japan 2026」/ サブコピー / 投票期間 / ランキング遷移<HomeHero> + Intl.DateTimeFormat 整形未作成Missing
1.2(投票期間表示)期間を data-model 由来値から取得lib/voting-period.tsgetVotingPeriod()未作成(data-model 所有)Constraint: data-model 完了待ち
1.3投票期間値は data-model から取得・固定値禁止同上 + テストでハードコード文字列を検知テスト基盤未整備Missing(検証)
1.4期間取得失敗時はエラー画面 / 一覧描画しないapp/error.tsx + InvalidVotingPeriodError throwerror.tsx 未作成、Error 型は data-model 所有Missing
2.1全候補者を一覧表示<CandidateGrid> + microCMS から候補者取得未作成Missing
2.2各候補者の画像 / 順位 / 得票数(3桁区切り) / 名前 / 詳細導線<CandidateCard>(next/image + microCMS CDN URL 直渡し)未作成。next.config.ts.images.remotePatterns 未設定Missing + Constraint
3.1候補者一覧を ID 昇順表示Server で sort((a,b) => a.id.localeCompare(b.id))未実装Missing
3.2投票数変動で並び順を変えないuseCandidateListWithRank(useOptimistic reducer は voteCount のみ)フック未作成Missing
4.1現在順位は voteCount 降順で算出rankCandidates 純関数(candidate-ranking 所有)未作成(別 spec 所有)Constraint: candidate-ranking 完了待ち
4.2順位最新化はページ再訪時のみuseCandidateListWithRankrank 不変保証未実装Missing
4.3同得票時は ID 昇順タイブレークrankCandidates 内ロジック別 spec 所有Constraint
5.1候補者詳細への単一操作遷移<Link href={/candidate/${id}}>遷移先 route 自体も未実装(candidate-detail 所有)Constraint(リンク先のみ)
共通: 投票導線(2.2 補助)購入モーダル + ニックネーム + ConfettiuseVoteFlow / useVotingPeriodStatus / <PurchaseFlowContainer>(voting 所有)未実装Constraint: voting 完了待ち
非機能: LCP / 画像最適化先頭 4 件 priority + sizes<CandidateCard> 内で実装未実装Missing
非機能: メタデータapp/page.tsxexport const metadataプレースホルダ実装には未配置Missing
非機能: テスト(tasks.md 4.x)Vitest / Testing Library / Playwright未導入Missing(将来)

複雑性シグナル

  • Algorithmic logic: rank 算出ロジック(降順 + ID タイブレーク) → ただし candidate-ranking 所有なので home 側は import するだけ
  • Workflow: 投票成功 → 楽観的 voteCount 更新 + Confetti(useVoteFlow 経由) → home 側は配線のみ
  • External integrations: microCMS / Neon / Stripe(間接) → すべて data-model / voting を経由するため home スコープには直接出てこない
  • UI complexity: 高め(背景装飾 + hover アニメ + Confetti + Hero + Card stagger) → ただし ui-design.md で確定済み

Research Needed(design phase で詰めるべき項目)

  • <HomeHero> の投票期間整形フォーマット(ja-JPdateStyle: 'long' か手書きか)。固定値ハードコード禁止の要件 1.3 に抵触しない実装パターン
  • 期間表示の ハイドレーション安全性: サーバー(JST)とクライアントの差を Server で完結させて props 化することは決まっているが、ロケール差で年表記が揺れないかを design phase で再確認
  • Confetti のレイヤリング(z-index)とスクロール位置の関係(<ScrollToTopButton> との衝突)
  • 既存 app/globals.css のアニメーション keyframe (card-fade-in)を Tailwind utility として呼ぶ流儀(animation-name を直接 style に書くか、@layer utilities でクラス化するか)
  • Server Component の単体テスト粒度(design.md Risks にも記載済み)

3. Implementation Approach Options

ベースライン: 現行 app/page.tsx はプレースホルダのみで、再利用すべき既存実装は 皆無。したがって「既存を拡張するか / 新規を作るか」の選択肢は実質的に縮退するが、フォーマルに 3 案を提示する。

Option A: Extend Existing(プレースホルダを段階的に育てる)

  • 対象ファイル: app/page.tsx を直接書き換え、app/layout.tsx / app/providers.tsx の機能拡張で全責務を引き取る
  • 進め方: 1 ファイルで Server / Client を擬似的に同居させ、後から分割
  • 互換性: 後方互換の対象が無いため評価不能(=制約なし)
  • トレードオフ:
    • ✅ 初動の認知負荷が低い
    • design.md が明示する Server / Client の責務分離(Boundary Map)に反する
    • steering/structure.md の 3 層モデルを早期に逸脱し、後の voting / candidate-detail で再分割コストが発生
    • app/page.tsx が肥大化し、<CandidateCard> の再利用が阻害される

評価: 採用非推奨。spec の Architecture Pattern を破壊する。

Option B: Create New Components(spec 通りにゼロから構築)

  • 対象作成ファイル(home 単独で新規になるもの):
    • app/page.tsx(書き換え, Server Component 化)
    • app/error.tsx(新規, Client Component)
    • app/_components/HomePageClient.tsx(新規)
    • features/candidates/types.ts(新規, RankedCandidate の re-export)
    • features/candidates/hooks/useCandidateListWithRank.ts(新規)
    • features/candidates/hooks/useScrollToTop.ts(新規)
    • components/public/HomeHero.tsx
    • components/public/CandidateGrid.tsx
    • components/public/CandidateCard.tsx
    • components/public/Confetti.tsx
  • import するが本 spec で作らないファイル(他 spec 所有):
    • features/cms/services/candidateService.ts(data-model)
    • features/cms/services/creditPackageService.ts(data-model)
    • features/candidates/services/voteAggregationService.ts(data-model)
    • features/candidates/utils/rankCandidates.ts(candidate-ranking)
    • features/voting/hooks/useVoteFlow.ts / useVotingPeriodStatus.ts(voting)
    • features/voting/components/PurchaseFlowContainer.tsx(voting)
    • lib/voting-period.ts / lib/microcms.ts / lib/prisma.ts(data-model)
  • 責務境界: design.md の Boundary Commitments と完全一致
  • トレードオフ:
    • ✅ Spec の Architecture / Layered Module Plan に厳密準拠
    • ✅ presentation と logic の独立テストが可能(<CandidateCard> を Storybook 単体で確認可能 = ui-design.md ハンドオフが成立)
    • ✅ 後続 spec の再利用(candidate-detail / candidate-ranking<CandidateCard> 派生やフックを共有)に最も近い
    • ❌ 一度に作成するファイル数が多い(約 10 ファイル + テスト)
    • ❌ 他 spec の依存(data-model / voting / candidate-ranking)が解決するまで一部の import が dangling になる

評価: 採用推奨。design.md / tasks.md がすでに本案で書かれており、整合性が最も高い。

Option C: Hybrid(段階導入)

  • 段階分け:
    1. Phase 1: data-model 完了直後に、<HomeHero> + <CandidateGrid>(投票導線抜き) + useCandidateListWithRank を導入。useVoteFlow 等の voting 依存箇所は disabled なボタン + プレースホルダ で表現
    2. Phase 2: candidate-ranking 完了で rankCandidates を import、rank 表示を有効化
    3. Phase 3: voting 完了で投票導線・Confetti・楽観更新を有効化
  • メリット: 上流 spec の進行と並行して home の UI を可視化できる(デザインレビューの早期実施が可能)
  • トレードオフ:
    • ✅ デザイナーへの early feedback が可能
    • ✅ 各段階で動くものが出る
    • ❌ Phase 1 用の暫定 UI を捨てるコストが発生(disabled ボタン → 実ボタンの差し替え)
    • tasks.md の現行構成(Foundation → Core → Integration → 検証)とフェーズ境界がズレるため、tasks 再生成が必要

評価: 採用検討可。ただし上流 spec(data-model / voting / candidate-ranking)の並行進行が 同時に走る ことが条件。本リポジトリの状況では上流 spec も同程度に未着手なので、Option B の一括着手とほぼ差がない。

4. Implementation Complexity & Risk

観点評価一行 justification
EffortL (1–2 週)新規 ファイル 約 10 + テスト + Storybook 立ち上げ。1 spec 単独としては大きいが、UI / Logic / Hooks の役割が明確で並列化可能
RiskMedium上流 spec(data-model / voting / candidate-ranking)依存があるが、契約は design.md / data-model/design.md で確定済み。未知の技術や複雑な統合は無いが、order-of-implementation を間違えると import が壊れる

高リスク要因(Mitigation 付き)

  • R1. 他 spec の契約変更: Candidate / RankedCandidate 型・voteAggregationService のシグネチャが変わると home の全層が再ビルド対象に
    • Mitigation: design.md の Revalidation Triggers に明文化済み。型 import を features/candidates/types.ts 経由に集約してチョークポイント化
  • R2. next/image + microCMS CDN 設定不在: next.config.tsimages.remotePatterns が未設定のまま実装すると <Image> が動かない
    • Mitigation: data-model 完了マイルストーンに remotePatterns 反映 を確認チェックとして含める(本 spec 単独では編集禁止)
  • R3. Hydration mismatch(useVotingPeriodStatus): クライアント時計依存で SSR と差が出る可能性
    • Mitigation: design.md で voting spec に課す制約として明文化済み(初期 isOpen=false / useEffect で再判定)
  • R4. テスト基盤の未整備: Vitest / Playwright が package.json に無く、tasks.md 4.x がそのままでは実行不能
    • Mitigation: design phase 中に「テスト基盤導入」が data-model または専用 spec の責務か、home/tasks.md 内サブタスクかを決める
  • R5. Storybook 未導入: steering/structure.md が必須としているがリポジトリに存在しない
    • Mitigation: 同上。導入は別 spec(または共通 chore)として切り出す候補あり

5. Recommendations for Design Phase

Preferred Approach

  • Option B(spec 通りにゼロから構築)を採用 することを推奨。
  • 理由: 現行コードは空に近く、再利用すべき既存実装が無い。design.mdtasks.md がすでに本案を前提に書かれており、改変コストが最小。

Order of Implementation(上流依存を踏まえた推奨順)

  1. data-model 完了(lib/voting-period.ts / lib/microcms.ts / lib/prisma.ts / features/cms/services/** / features/candidates/services/voteAggregationService.ts / next.config.tsremotePatterns 更新)
  2. candidate-rankingfeatures/candidates/utils/rankCandidates.ts 確定
  3. votinguseVoteFlow / useVotingPeriodStatus / <PurchaseFlowContainer> 確定
  4. 本 spec の tasks.md を Foundation → Core → Integration → 検証 の順で実行

Key Decisions to Confirm in Design Phase

  • 投票期間表示の整形パターン(Intl.DateTimeFormat 引数の確定文言)
  • app/error.tsxInvalidVotingPeriodErrorerror.cause から識別する方式(他のエラーと文言を出し分けるかどうか)
  • Confetti の DOM 配置(HomePageClient 直下に置き z-index で最前面化、ScrollToTopButton との競合解消)
  • テスト基盤(Vitest / Playwright / Storybook)の導入 spec 配属

Research Items to Carry Forward

  • Server Component の単体テスト戦略(Vitest だけでは Server Component を直接 render できないため、services をモックして関数を切り出す形が現実的)
  • useOptimistic の React 19 安定版 API シグネチャ(reducer の return 型 vs 直接更新)
  • microCMS の customRequestInitnext.revalidate と組み合わせる際の TTL 数値(data-model 委譲だが home 描画の鮮度感に影響)

Carryover for tasks.md(必要なら spec 再生成時に追記)

  • next.config.tsimages.remotePatterns 確認チェック(home 単独では編集禁止)
  • テスト基盤(Vitest / Playwright)導入の前提タスク(現行 tasks 4.x が前提を欠く)