世代マトリックス 画面設計書
最終更新: 2026-09-21 関連 Issue: MorningStatusApp#1571(親Issue)/ #128(初版の設計書作成)/ #138(データ取得方式の判定追記)/ #141(原仕様に沿った表示への改訂)/ MorningStatusApp#1632(実装)/ MorningStatusApp#1650(機能落ちの是正)
1. 画面概要
| 項目 | 内容 |
|---|---|
| URL | /generational-matrix |
| レンダリング方式 | SSR(動的) |
| アクセス制御 | シークレットモードでのみ閲覧可能(環境変数一覧 §4「シークレットモードとは」参照)。ナビゲーションからは一切リンクしない隠しルート |
| データソース | Vercel Blob members.json(全メンバーの dob・joinDate・gradDate) + Neon member_children(世代マトリックス データ設計書 §2-1) |
Topページの一覧ページリンクや、他画面の関連導線にも一切表示しない。到達経路は「URLを直接入力する」か「Topページの隠しジェスチャー(§5-1a)を成立させる」のいずれかのみ。
2. 表示目的
OGメンバーの子供の誕生年と現役メンバーの誕生年が交差する「世代の重なり」を、年×メンバーのグリッドとして一望できるようにする。原仕様(MorningStatusApp#1571本文)にあるノード形状・イベントの記号・縦線・世代交差の強調を、§4の方針で表現する。
備考: 初版(v1.0〜v1.1)は、原仕様の表現を「関係性が見通せる程度のシンプルな表現」に簡略化し、本人の誕生年を *、子供の誕生年を + で並べるだけの表示としていた。この方式では、メンバーの活動期間(加入〜卒業)が見えず、世代の重なりも記号の位置から読み取るしかないため、「見渡せる」「重なりが見える」という目的を果たせなかった(MorningStatusApp#1650)。簡略化は原仕様からの機能落ちであったため、v1.2で§4の方針に改めた。
3. アクセス制御フロー
secret_mode_sessionCookie(環境変数一覧 §4)が無効・未設定の場合、グリッドの代わりに合言葉入力フォームを表示する- フォーム送信で
POST /api/secret-mode/verifyを呼ぶ。成功時はCookieが発行され、そのままグリッド表示に切り替わる。失敗時は「合言葉が正しくありません」を表示する - Cookieが有効な場合はグリッドをそのままレンダリングする
notFound() は使わない。未認証時も合言葉フォームをそのまま表示するため、URLを知っていればページの存在自体は誰でもわかる。この画面を見つかりにくくしているのは「ナビゲーションのどこにもリンクを置かない」という一点のみで、URLが推測されないことを前提にはしていない。
4. 画面構成
┌───────────────────────────────────────────────────┐
│ 見出し「世代マトリックス」+説明文 │
│ (凡例: 記号・縦線・世代交差の帯と枠) │
├───────────────────────────────────────────────────┤
│ 年 | 〇Aさん | ◎Bさん | 〇Cさん | ... │
│ 1978 | ● | | | │
│ 1979 | ┃ | | | │
│ 1981 | ┃ | ● | | │
│ ... │
│ 2012 | ☆ | | | ← 帯・枠を重ねる │
└───────────────────────────────────────────────────┘
4-1. 縦軸(行)と横軸(列)
- 縦軸(行): 西暦年。範囲は「全メンバーの
dobの年」と「member_children.birthYear」の最小値から、現在の年までを1年刻みで連続させる(現役の縦線を現在の年まで引くため。固定のグループ結成年基準ではない。世代マトリックス データ設計書 §5の参考データは1973年〜現在に及ぶため) - 横軸(列): メンバー。
dobの誕生年昇順で並べる。OG・現役は区別せず同じ軸に混在させる。列見出しは氏名にノード形状を添える: OG=〇、現役=◎(判定は次項) - OG・現役の判定:
gradDateがnull、またはgradDateが現在の日付(JST)より後の場合は現役、それ以外(gradDateが現在の日付以前)はOGとする。卒業が決まっていて卒業日がまだ先のメンバーは、卒業日を迎えるまで現役として扱う。判定にstatusの値は使わない - 現在の年: 現在日付(JST)の暦年とする。サーバーはUTCで動作するため、JSTの暦日から求める。平均年齢の変遷グラフ画面設計書のような、結成記念日の到来による除外は行わない。行の範囲(本節)・縦線の終端(§4-3)・学齢の算出(§4-5)で共通に使う
4-2. セル内の記号
| 記号 | 意味 | 該当する年 |
|---|---|---|
| ● | 誕生 | メンバー本人の dob の年 |
| ▲ | 加入 | joinDate の年 |
| ▼ | 卒業 | OGの gradDate の年(現役は表示しない。卒業が決まっていて gradDate がある現役も表示しない) |
| ☆ | 子供の誕生 | そのメンバーの member_children.birthYear(同じ年に複数の子供がいる場合は ☆ を人数分並べる) |
- 年は、日付(
YYYY-MM-DD形式)の先頭4桁とする - 同じ年・同じ列に複数のイベントが該当する場合は、出来事の順(●・▲・▼・☆)に記号を横に並べる(例: 卒業と子供の誕生が同じ年なら「▼☆」)
- いずれにも該当しない場合: 空欄
4-3. 縦線(活動期間)
メンバーごとに、その列で誕生の行から卒業の行まで(現役は現在の年の行まで)を縦線で結ぶ。
- 誕生から加入まで: 細く薄い線(年齢の流れ)
- 加入から卒業まで(現役は現在の年まで。卒業が決まっている現役も同じ): 太く濃い線(活動期間)
- 卒業の行より後には、線を引かない
4-4. 背景色
本人の誕生年のセル(●)と、子供の誕生年のセル(☆)は、背景色を変える。色は§4-7の定義に従う。
4-5. 世代交差の表現
世代の重なりを、次の2つの関係で表す。どちらもセルの塗りと枠線で描き、2つの関係は色で識別する。
(1) 異種の関係(帯)
同じ年の行に、OGの加入(▲)またはOGの子供の誕生(☆)と、現役の誕生(●)が両方ある場合、その行について、該当するセルのうち最も左のセルから最も右のセルまでを、帯として塗る。
- 帯は年の行ごとに1本とし、間にある他のメンバーの列のセルも塗る
- 年がずれる組み合わせは、帯の対象にしない
(2) 同種の関係(学齢の枠)
子供の誕生(☆)同士、現役の誕生(●)同士を、学齢ごとの枠で囲む。
- 学齢は「現在の年 − 生年」の年単位で数える。区切りは学校制度に沿った定時の区切りとする
| 区分 | 年齢 |
|---|---|
| 未就学 | 0〜5歳 |
| 小学生 | 6〜11歳 |
| 中学生 | 12〜14歳 |
| 高校生 | 15〜17歳 |
- 18歳以上には、枠を作らない
- 枠は、その区分に属する☆(または●)のセルを囲む最小の四角形とする(行方向は該当セルの最小の年から最大の年まで、列方向は最も左のセルから最も右のセルまで)。子供の☆と現役の●は、別々に枠を作る
- 該当するセルが1つだけの区分は、「同士」の関係が成立しないため、枠を作らない
- 現在の年が変わると年齢が変わるため、枠もずれる
重ね方
帯と枠は、半透明の塗りと同色の枠線で描く。重なる部分は、色が混ざる。前後関係は、背景(誕生年のセルの背景色)→ 帯・枠 → 縦線 → 記号の順に重ね、記号は常に最前面で読めるようにする。
4-6. 凡例
画面上部に、記号(●・▲・▼・☆)、ノード形状(〇・◎)、縦線(誕生〜加入/加入〜卒業)、帯、学齢ごとの枠の意味を示す凡例を表示する。
4-7. 色の定義
色は、本アプリのカテゴリ色分けの規約に従い、Tailwind CSSの -400 から選ぶ。色の値は、morning-status-blume#141 のコメントに貼られたイメージ図の配色に合わせている。
| 対象 | 区分 | 色 | 値 |
|---|---|---|---|
| 誕生年のセルの背景 | 本人(●) | sky-400 | #38bdf8 |
| 誕生年のセルの背景 | 子供(☆) | pink-400 | #f472b6 |
| 帯(異種の関係) | — | violet-400 | #a78bfa |
| 学齢の枠(同種の関係) | 未就学 | emerald-400 | #34d399 |
| 学齢の枠(同種の関係) | 小学生 | cyan-400 | #22d3ee |
| 学齢の枠(同種の関係) | 中学生 | yellow-400 | #facc15 |
| 学齢の枠(同種の関係) | 高校生 | orange-400 | #fb923c |
- 子供の枠は実線、現役の枠は破線で描き、同じ区分の枠でも種別を見分けられるようにする
- 実データでの見え方は、実際の画面で確認して調整する(色相が近い組み合わせを含む)
4-8. キャンバス
この画面だけ、共通レイアウトの最大幅(1200px)の制限を外し、画面幅いっぱいに使う。メンバー数分の列が画面幅に収まらない場合は、グリッドの内部で横スクロールさせ(ページ全体は横にはみ出さない)、年の列と列見出しは固定する。実装(.app-container と data-wide-canvas)の詳細は Panda CSS 設計書 §3-3を参照。
5. 画面遷移
5-1. 遷移元
リンク・ボタン等の可視要素は設けない(本書§1参照)。唯一の遷移経路は、Topページに仕込む隠しタップシーケンス(§5-1a)のみ。
5-1a. 隠し単語タップシーケンスによる遷移
Topページのタイトル「Hello! Project History」(画面設計書 §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()(世代マトリックス データ設計書 §3)による認証状態チェックを行う - 未認証時は合言葉入力フォーム(新規クライアントコンポーネント、
'use client')を描画する - 認証済時は
getMembersFromBlob()と Neonmember_childrenを取得し、lib/generational-matrix-utils.tsの関数でグリッド用データ構造に変換してから、グリッド描画用コンポーネントに渡す。グリッド用データ構造は、次の情報を持つ- 列: メンバーID・氏名・現役かどうか・誕生年・加入年・卒業年(現役は、卒業が決まっている場合も
null) - 行: 西暦年と、列ごとのセル
- セル: その年のイベント(誕生・加入・卒業・子供の誕生の人数)、縦線の区間(なし/誕生〜加入/加入〜卒業)と、その行が線の始点(誕生の行)・終点(卒業の行。現役は現在の年の行)かどうか、帯に含まれるか(含まれる場合は帯の左端・右端のセルかどうか)、枠に含まれるか(学齢の区分と種別。四角形の外周にあたる上下左右の辺のどれか)
- 学齢の区分の年齢の境界(§4-5)は、定数として定義する
- グリッドは表示のみでクライアント側の状態・操作を持たないため
'use client'は付けない(app/average-age/page.tsxと同じ「Server Componentで計算 → 描画」の分担パターン)
- 列: メンバーID・氏名・現役かどうか・誕生年・加入年・卒業年(現役は、卒業が決まっている場合も
- データ取得方式: メンバー・子供の生年ともServer Componentから直接取得し、Route Handler(BFF)+ TanStack Queryは使わない。判定の根拠は統合フィードアイテム データ設計書 §6-6を参照。認証済と確認できた後にのみ取得し、未認証のリクエストではメンバー・子供の生年のいずれも取得しない
7. ローディング・エラー状態
| 状態 | 表示内容 |
|---|---|
| 未認証(Cookie無効・未設定) | 合言葉入力フォーム |
| 合言葉不一致 | フォーム下に「合言葉が正しくありません」を表示 |
| データ取得エラー(Blob/Neon) | 「データが取得できませんでした」等のエラーメッセージ |
改訂履歴(バージョン降順に記載)
| 版 | 更新日 | 変更内容 |
|---|---|---|
| 1.4 | 2026-09-25 | §4-8に、ワイドキャンバス機構の実装詳細(.app-container)を解説するPanda CSS 設計書 §3-3への参照リンクを追加した(MorningStatusApp#1655) |
| 1.3 | 2026-09-22 | §6のデータ取得方式の参照先を、統合フィードアイテム データ設計書の章構成再編(適用判定ログ§3から「適用しない」理由(備考)を新設§6へ分離)に追随して§3から§6-6に更新した(MorningStatusApp#1643・#1644) |
| 1.2 | 2026-09-21 | 【v10.6.1(MorningStatusApp#1650。実装後の改修)】原仕様(MorningStatusApp#1571)からの機能落ちの是正(MorningStatusApp#1650)として、§2に旧方式の備考を追記し、§4を全面改訂した(ノード形状、記号(●・▲・▼・☆)、縦線、背景色、世代交差の表現(同じ年の帯・学齢の枠)、色の定義、凡例、キャンバス)。§1のデータソースに joinDate・gradDate を追加し、generation・status を除外して(OG・現役の判定は gradDate の有無に一本化)、§6のグリッド用データ構造を新しい表示に合わせて改めた(morning-status-blume#141)。実装時に、§4-1の現在の年をJSTの暦年と明記し、§6のグリッド用データ構造に縦線の始点・終点、帯・枠の端の別を追記した(MorningStatusApp#1650)。また、OG・現役の判定を「卒業日が現在の日付(JST)より後なら現役」に改め(§4-1)、§4-2・§4-3・§6を合わせた。卒業日だけで判定すると、卒業が決まっているメンバーをOGと誤判定するため(MorningStatusApp#1650) |
| 1.1 | 2026-09-20 | 【v10.6.0(MorningStatusApp#1632。実装中の追加のため1.0と同じバージョン)】§6にデータ取得方式(Server Componentでの直接取得。Route Handler(BFF)+ TanStack Queryは適用しない)を明記。グリッド用データ構造を実装に合わせて具体化し(列情報の追加、複数の子供を表す marker の表現)、グリッド描画コンポーネントはクライアントコンポーネントにしない旨に改めた(morning-status-blume#138、MorningStatusApp#1632) |
| 1.0 | 2026-09-20 | 【v10.6.0(MorningStatusApp#1632)】初版作成(morning-status-blume#128) |