---
title: "世代マトリックス 画面設計書"
---

最終更新: 2026-09-20
関連 Issue: MorningStatusApp#1571（親Issue）/ #128（本Issue）/ #138（データ取得方式の判定追記）/ MorningStatusApp#1632（実装）

---

## 1. 画面概要

| 項目 | 内容 |
|---|---|
| URL | `/generational-matrix` |
| レンダリング方式 | SSR（動的） |
| アクセス制御 | シークレットモードでのみ閲覧可能（[環境変数一覧](/design/common/environment-variables) §4「シークレットモードとは」参照）。**ナビゲーションからは一切リンクしない隠しルート** |
| データソース | Vercel Blob `members.json`（全メンバーの `dob`・`status`・`generation`） + Neon `member_children`（[世代マトリックス データ設計書](/design/member/generational-matrix-data-design) §2-1） |

Topページの一覧ページリンクや、他画面の関連導線にも一切表示しない。到達経路は「URLを直接入力する」か「Topページの隠しジェスチャー（§5-1a）を成立させる」のいずれかのみ。

---

## 2. 表示目的

OGメンバーの子供の誕生年と現役メンバーの誕生年が交差する「世代の重なり」を、年×メンバーのグリッドとして一望できるようにする。原仕様（MorningStatusApp#1571本文）にあったノード形状の作り分け・複数種の線・ハイライト表示は、関係性（本人／子供の対応、世代の重なり）が見通せる程度のシンプルな表現に簡略化する（think-issueでの検討結果、同Issueコメント参照）。

---

## 3. アクセス制御フロー

1. `secret_mode_session` Cookie（[環境変数一覧](/design/common/environment-variables) §4）が無効・未設定の場合、グリッドの代わりに合言葉入力フォームを表示する
2. フォーム送信で `POST /api/secret-mode/verify` を呼ぶ。成功時はCookieが発行され、そのままグリッド表示に切り替わる。失敗時は「合言葉が正しくありません」を表示する
3. Cookieが有効な場合はグリッドをそのままレンダリングする

`notFound()` は使わない。未認証時も合言葉フォームをそのまま表示するため、URLを知っていればページの存在自体は誰でもわかる。この画面を見つかりにくくしているのは「ナビゲーションのどこにもリンクを置かない」という一点のみで、URLが推測されないことを前提にはしていない。

---

## 4. 画面構成

```
┌───────────────────────────────────────────┐
│ 見出し「世代マトリックス」＋説明文           │
│ （凡例: * 本人の誕生年 / + 子供の誕生年）     │
├───────────────────────────────────────────┤
│      年 × メンバー グリッド                  │
│                                           │
│  年   | Aさん | Bさん | Cさん | ...          │
│  1978 |  *    |       |       |             │
│  1981 |       |  *    |       |             │
│  2012 |  +    |       |       |             │
│  ...                                       │
└───────────────────────────────────────────┘
```

- **縦軸（行）**: 西暦年。範囲は「全メンバーの `dob` の年」と「`member_children.birthYear`」の最小値〜最大値を実データから動的に算出する（固定のグループ結成年基準ではない。[世代マトリックス データ設計書](/design/member/generational-matrix-data-design) §5の参考データは1973年〜現在に及ぶため）
- **横軸（列）**: メンバー。`dob` の誕生年昇順で並べる。OG・現役は区別せず同じ軸に混在させる（列ヘッダーの文字色・バッジ等でOG/現役の区別を補助してもよいが、必須要件ではない）
- **セル内容**:
  - その年がメンバー本人の誕生年に一致する場合: `*`
  - その年がそのメンバーの子供の誕生年に一致する場合: `+`（同じ年に複数の子供がいる場合は `++` のように誕生日数分繰り返す）
  - 上記いずれにも該当しない場合: 空欄
- 縦線・横線・血縁を示す斜め線・世代交差のハイライト等、原仕様にあった装飾表現は設けない（本節冒頭参照）。関係性は「同じ列に `*` と `+` が並ぶ」ことで表現する

---

## 5. 画面遷移

### 5-1. 遷移元

リンク・ボタン等の可視要素は設けない（本書§1参照）。唯一の遷移経路は、Topページに仕込む隠しタップシーケンス（§5-1a）のみ。

### 5-1a. 隠し単語タップシーケンスによる遷移

Topページのタイトル「Hello! Project History」（[画面設計書](/design/common/screen-design) §1参照）を構成する3単語（`Hello!`／`Project`／`History`）を、決められた順序でタップ（クリック）した場合のみ `/generational-matrix` へ遷移する。「合言葉をタイトルへのタップ操作で入力する」という遊び心のあるUIで、URLを直接知らなくても本画面に到達できる第二の経路を提供する。

- **タップ順序**: `Project → History → Hello! → Project`（4タップ）
- **入力手段（デスクトップ・スマホ共通）**: 3単語をそれぞれ個別の要素（`<span>`）でラップし、`onClick`ハンドラを仕込む。マウスクリックとタッチタップは同一の`onClick`イベントとして扱えるため、入力手段（デスクトップ/スマホ）ごとに別処理を持つ必要がない
- **ドラッグ（スワイプ）を採用しない理由**: 当初は方向入力シーケンス（`↑↑↓↓←→←→`、コナミコマンド風）をスワイプ・矢印キーで実装する設計を検討したが、スマホでは上下スワイプがページのスクロール操作と区別できず、スワイプ中にページ自体がスクロールしてタイトル要素が画面外へ流れてしまい、複数回の方向入力を同じ位置で続けられない問題が設計段階で判明した（開発ノート「UI/UX設計」#19参照）。単語単位のクリック/タップは要素の当たり判定が十分な大きさを持ち、ドラッグを伴わないためスクロール・ネイティブジェスチャーとは原理的に衝突しない
- **状態管理**: 新規クライアントコンポーネント `components/HiddenMatrixTrigger.tsx`（`'use client'`）がTopページのタイトルをラップし、タップシーケンスの進捗をコンポーネント内state（`useState`）で保持する。4タップのうち何番目まで一致しているかを追跡し、次にタップされた単語が期待値と一致すれば進捗を1つ進め、一致しなければ進捗を0にリセットする。4タップすべて一致した時点で `router.push('/generational-matrix')` を呼ぶ
- **視覚的フィードバックは一切表示しない**（進捗が分かる表示を出すと隠し機能の存在に気付かれやすくなるため）
- 各単語の要素には`aria-label`等でこの隠し機能を説明する属性は追加しない（スクリーンリーダー利用者への機能案内は行わない。既存のタイトルテキストの読み上げ自体は変更しない）
- **注意**: この仕組みはタイトルの実際の文言（`Hello!`／`Project`／`History`という3単語）に依存する。将来Topページのタイトル文言を変更する場合は、タップ対象の単語構成・タップ順序も合わせて見直す必要がある（本書§1参照）

### 5-2. 復帰導線

画面上部に「Topに戻る」リンクを設置する（他の主要一覧画面と同様のパターン）。ただしTopページ側からこの画面へのリンクは設けない（一方向）。

---

## 6. コンポーネント方針

- Server Component（`app/generational-matrix/page.tsx`）で `requireSecretMode()`（[世代マトリックス データ設計書](/design/member/generational-matrix-data-design) §3）による認証状態チェックを行う
- 未認証時は合言葉入力フォーム（新規クライアントコンポーネント、`'use client'`）を描画する
- 認証済み時は `getMembersFromBlob()` と Neon `member_children` を取得し、`lib/generational-matrix-utils.ts`（新規）の関数でグリッド用データ構造（`{ columns: { memberId: string; name: string; status: string }[]; rows: { year: number; cells: { memberId: string; marker: string | null }[] }[] }`。`marker` は本人の誕生年が `*`、子供の誕生年が人数分の `+`（§4）、該当なしが `null`）に変換してから、グリッド描画用コンポーネントに渡す。グリッドは表示のみでクライアント側の状態・操作を持たないため `'use client'` は付けない（`app/average-age/page.tsx` と同じ「Server Componentで計算 → 描画」の分担パターン）
- **データ取得方式**: メンバー・子供の生年ともServer Componentから直接取得し、Route Handler（BFF）+ TanStack Queryは使わない。判定の根拠は[統合フィードアイテム データ設計書](/design/common/feed-data-design) §3を参照。認証済みと確認できた後にのみ取得し、未認証のリクエストではメンバー・子供の生年のいずれも取得しない

---

## 7. ローディング・エラー状態

| 状態 | 表示内容 |
|---|---|
| 未認証（Cookie無効・未設定） | 合言葉入力フォーム |
| 合言葉不一致 | フォーム下に「合言葉が正しくありません」を表示 |
| データ取得エラー（Blob/Neon） | 「データが取得できませんでした」等のエラーメッセージ |

---

## 改訂履歴（バージョン降順に記載）

| 版 | 更新日 | 変更内容 |
|---|---|---|
| 1.1 | 2026-09-20 | §6にデータ取得方式（Server Componentでの直接取得。Route Handler（BFF）+ TanStack Queryは適用しない）を明記。グリッド用データ構造を実装に合わせて具体化し（列情報の追加、複数の子供を表す `marker` の表現）、グリッド描画コンポーネントはクライアントコンポーネントにしない旨に改めた（morning-status-blume#138、MorningStatusApp#1632） |
| 1.0 | 2026-09-20 | 初版作成（morning-status-blume#128） |
