フィットネス アプリ: クライアント アーキテクチャ仕様
アーティファクトこれは、エピソード 3「アプリに頭脳を与える」とエピソード 4「ノーコード アプリの実装計画」で使用されるアーティファクトです。この仕様は、フロントエンド ビュー、再利用可能な UI コンポーネント、クライアント状態管理、平易な英語のテストケースのリファレンスとして使用できます。
このアーティファクトにアクセスするにはログインしてください
Google デベロッパー プロフィールでログインすると、Build It! シリーズで使用されているアーティファクト全体を表示してコピーできます。
デベロッパー プロフィールが必要です
デベロッパー プロフィールは無効です。この機能を有効にするよう、Workspace 管理者に連絡してください。
デベロッパー プロフィールを作成する
無料の Google デベロッパー プロフィールを作成して、Build It! のすべてのプロンプトとアーティファクトを利用できるようにしましょう。
Builder Central に参加してロックを解除
ワンクリックで無料の Builder メンバーシップ バッジを取得して、Build It! のすべてのプロンプトとアーティファクトにアクセスしましょう。
ステータス: 計画セッションの準備完了(クライアント プランニング コンパニオンを使用してドラフト作成中)
コンパニオン ドキュメント: データ アーキテクチャ仕様
アーキテクチャの目標: AI Studio スマート フィットネス プロトタイプのクリーンで、保守可能、モジュール式、テスト可能なフロントエンドの再構築。
0: コア技術スタックとエンジニアリング標準
- ランタイム フレームワーク: Next.js(App Router、React 19) + プログレッシブ ウェブアプリ(PWA)のオフライン アセット キャッシュ。
- スタイリング アーキテクチャ: Tailwind CSS v4 + Tailwind Variants(決定論的なコンポーネント スタイリング用)+ Lucide React(アイコン)。
- クライアントの状態の区分:
- Zustand(ローカル セッション): 揮発性のジムフロアの状態(
useActiveSessionStore)、ストップウォッチ タイマー、アクティブなセットバッファ、事前に計算されたステップ シーケンスを管理します。スマートフォンのロック解除用にlocalStorageと同期しました。 - TanStack Query(サーバー状態):
weekly_plans、user_profiles、prs、workout_logsの Firestore ドキュメント キャッシュ、バックグラウンドでの再検証、楽観的更新を管理します。
- Zustand(ローカル セッション): 揮発性のジムフロアの状態(
- グラフの可視化: Recharts(カスタムのダークジムモード トークンでスタイル設定)。
- 認証: Firebase Authentication。すべてのサーバー エンドポイントに渡される署名なしトークン認証ヘッダーを使用します。
1. フロントエンドの原則と設計原則
- ジムでの使用を最優先した人間工学: ワークアウト中に汗をかいた手でも、片手で操作できるように設計されています。タップ ターゲットを大きくする(48x48 dp 以上)、コントラストを高くして読みやすくする、必要な入力操作を最小限に抑える。
- 楽観的 UI とオフライン復元力: セットの記録、休憩タイマーの開始、エクササイズの入れ替えは、UI で即座に(16 ミリ秒未満で)応答する必要があります。バックグラウンド同期は、ユーザーの操作を妨げることなく、
POST /api/workouts/logへのサーバーの永続性を処理します。 - 関心の厳格な分離: ダム / プレゼンテーション コンポーネントは、プロパティを使用してデータを受信し、イベントを生成します。スマート コンテナ ビューは、ビジネス ロジック、状態ストア、API の調整を管理します。
- 段階的なミューテーション UX: AI コーチ(
POST /api/coach/chat)によって生成された提案は、アプリケーションの状態に変更を commit する前に、明示的な [プランを承認] と [変更] のコントロールを備えたインタラクティブな段階的プレビュー カードとしてレンダリングされます。 - 行動駆動型の品質: すべてのページ、コンポーネント、インタラクションは、期待される動作を明確に定義する平易な英語のテストケースとペアになっています。
2. アプリケーション ナビゲーションとビュー階層
| ルートパス | ビュー名 | 主な目的 | 含まれる主要コンポーネント |
|---|---|---|---|
/ |
ルート リダイレクト | /today に即座にリダイレクトします。 |
(ルーターのリダイレクト) |
/today |
今日のワークアウトのプレビュー | デフォルト ビュー。現在の曜日に自動的に解決され、今日のワークアウトのプレビューが表示されます。 | <BackButton />、<WorkoutHeader />、<CoachNoteCard />、<WorkoutExerciseSummary />、<WorkoutBlock />、<ExerciseCard />、<EmptyDayPlaceholder />、<ModifyWithCoachCard /> |
/workouts/:day |
1 日のワークアウトのプレビュー | 特定の曜日(/workouts/monday、/workouts/thursday など)のワークアウトのプレビューを表示します。 |
<BackButton />、<WorkoutHeader />、<CoachNoteCard />、<WorkoutExerciseSummary />、<WorkoutBlock />、<ExerciseCard />、<EmptyDayPlaceholder />、<ModifyWithCoachCard /> |
/workout/active |
アクティブ セッション トラッカー | 重要なワークアウトの実行画面(セット、回数、タイマー)。下部ナビゲーションは表示されたままになります。 | <ActiveSessionStickyHeader />, <ActiveExerciseCardHeader />, <CoachTipCard />, <CurrentSetTargetCard />, <SetNavigationRow />, <ExerciseSetsTable />, <ActiveRestTimerDock />, <SessionExerciseOverview />, <ActiveSessionControlsDock />, <SwapExerciseModal />, <FinishConfirmationModal /> |
/week |
トレーニング スケジュール / 週間ビュー | 7 日間のスケジュール全体の概要。週単位のボリューム / 時間の集計。 | <WeekHeader />、<WeeklyStatsSummary />、<DayScheduleCardList />、<DayScheduleCard />、<ModifyWeekWithCoachCard /> |
/coach |
AI コーチチャット | コンテキスト チップと段階的なワークアウトの提案による会話型コーチング。 | <CoachHeader />、<ChatMessageList />、<ChatBubble />、<StagedProposalCard />、<ContextualPromptsBar />、<ChatInputDock />、<ContextBanner />、<ChatInputBar />、<SpeechToTextButton /> |
/progress |
進捗状況と分析 | 過去のトレーニング ログ、自己ベストの記録、ボリューム チャート。 | <ProgressHeader />、<PRShowcaseBanner />、<VolumeProgressChartCard />、<AllTimeStatsGrid />、<WorkoutHistorySection />、<HistorySessionCard />、<EmptyHistoryPlaceholder /> |
/settings |
ユーザー プロフィールと設定 | 機器プロファイル、通知、3 層メモリ管理。 | <SettingsHeader />、<UserProfileCard />、<EquipmentSelectorCard />、<EquipmentToggleChip />、<PreferencesGroup />、<MemoryTierViewerCard />、<MemoryItemCard />、<AccountActionsGroup /> |
/exercises/:exerciseId |
エクササイズ百科事典の詳細 | 動画とフォームのヒントを含む、ディープリンク可能なエクササイズ ガイド(例: /exercises/incline-db-bench-press)。 |
<BackButton />、<ExerciseVideoPlayer />、<TargetMusclesCard />、<HowToPerformCard />、<FormTipsCard /> |
3. ページとビューの仕様
表示: DailyWorkoutView(/today、/workouts/:day)
- 目的: 特定の日に予定されているトレーニング メニュー(ワークアウトの重点、所要時間、目標筋肉負荷配分、コーチのメモ、ストレート セット ブロック、スーパーセット ブロックなど)を表示します。ワークアウトが予定されていない場合は、クリーンな空の状態にフォールバックします。
- 視覚的な構造:
- 上:
<BackButton label="Back to Week" targetRoute="/week" />。 - ヘッダー: 1 行のタイトル(
"Today"または曜日)、条件付きの"✓ Completed"バッジ、時間ピル("⏱ 50 min")、ターゲット筋肉の強度パーセンテージ バー。 - コーチメモ: アイコンと緑色の枠線が付いた、コンテキストに応じたアドバイス カード。
- 本文:
<WorkoutExerciseSummary />を使用したエクササイズ ブロックのスクロール可能なリスト(border-left: 3px solid #a3e635バッジと🔁 SUPERSET BLOCKバッジを使用したストレート セットまたはスーパーセット)。 - 空の状態:
<EmptyDayPlaceholder message="Nothing scheduled for this day" />。 - 下部フッターのアクション:
<ModifyWithCoachCard />(/coach?day=:dayに移動)。
- 上:
表示: WeekScheduleView(/week)
- 目的: 7 日間のトレーニング スケジュールの概要をすべて表示します。これには、1 週間の合計量 / 時間の指標、アクティブなワークアウト日の厳選されたリスト(スケジュールされていない日や休息日は厳密に除外)、AI コーチで週間プランをオーバーホールするための直接ショートカットが含まれます。
- 視覚的な構造:
<WeekHeader />、3 列の<WeeklyStatsSummary />(合計時間、合計量、頻度)、<DayScheduleCard variant="normal" />を使用したスケジュール設定された日のリスト(当日はネオングリーンのTODAYバッジ付き)、<ModifyWeekWithCoachCard />(/coach?context=weekにルーティング)。
表示: CoachChatView(/coach)
- 目的: Gemini トレーナー エージェントによる会話型コーチング ハブ。モチベーションを高める戦略的なトレーニング アドバイスを提供し、音声入力によるディクテーションをサポートします。また、明示的な承認コントロールにより、人間参加型の段階的なワークアウト調整を行います。
- 視覚的な構造:
<CoachHeader />、スクロール可能な<ChatMessageList />(<ChatBubble />と<StagedProposalCard />)、条件付き<ContextualPromptsBar />、ドッキングされた<ChatInputDock />([✕]閉じる +<ChatInputBar />+<SpeechToTextButton />を含む<ContextBanner />)。
表示: ExerciseDetailView(/exercises/:exerciseId)
- 目的: ディープリンク可能な百科事典のエントリ。自動ループのミュートされた動画デモ(
<ExerciseVideoPlayer />)、主要なターゲット筋肉と二次的なターゲット筋肉のバッジ(<TargetMusclesCard />)、順序付けられたステップバイステップのパフォーマンス手順(<HowToPerformCard />)、フォームのヒント(<FormTipsCard />)を提供します。
表示: ActiveWorkoutView(/workout/active)
- 目的: 重要なワークアウトの実行とセットの記録画面。ライブ セッションのストップウォッチの状態、現在のセットの目標、回数 / 重量入力、瞬時のチェックマーク完了(16 ミリ秒未満)、自動スーパーセット切り替え、触覚 / 音声による休憩タイマーのカウントダウン、エクササイズの入れ替え、
POST /api/workouts/logへの最終的な永続化を管理します。 - 視覚的な構造:
<ActiveSessionStickyHeader />、<ActiveExerciseCardHeader />、<CoachTipCard />、<CurrentSetTargetCard />、フローティング<ActiveRestTimerDock />(+30s、Skip)、<SetNavigationRow />、<ExerciseSetsTable />(+ Add Set)、<SessionExerciseOverview />、ドッキングされた<ActiveSessionControlsDock />([⇄ SWAP EXERCISE]+[▶ START WORKOUT]/[⏸ PAUSE]/[▶ RESUME])、<SwapExerciseModal />、<FinishConfirmationModal />。
表示: ProgressView(/progress)
- 目的: 運動の進捗状況を長期的に追跡し、最近の自己ベスト(
<PRShowcaseBanner />)を記録します。インタラクティブな Recharts(4W | 3M | 1Yの切り替えが可能な<VolumeProgressChartCard />)で週単位 / 月単位の運動量の傾向を可視化し、<AllTimeStatsGrid />を表示して、過去に完了したワークアウト セッション(<WorkoutHistorySection />/<HistorySessionCard />)をアーカイブします。
表示: SettingsView(/settings)
- 目的: アスリートのプロフィール情報(
<UserProfileCard />)、機器の在庫(<EquipmentSelectorCard />/<EquipmentToggleChip />)、ワークアウトの設定(LBS / KGの<PreferencesGroup />、ハプティクス、サウンド)を管理し、AI トレーナーの 3 階層のライブメモリ(<MemoryTierViewerCard />/<MemoryItemCard />)の透過的なユーザー管理と削除を提供します。
4. 再利用可能なコンポーネントのカタログとインターフェース
アプリケーション全体の主要な TypeScript コンポーネントのプロパティ契約:
// src/components/workout/WorkoutExerciseSummary.tsx
interface WorkoutExerciseSummaryProps {
blocks: Array<{
label?: string;
category?: string;
exercises: Array<{
exercise_id: string; // Canonical slug (e.g. 'incline-db-bench-press')
name: string; // Denormalized display name (e.g. 'Incline DB Bench Press')
target_sets: number;
target_reps: string | number;
rest_seconds?: number;
}>;
}>;
activeExerciseId?: string;
onSelectExercise?: (exerciseId: string) => void;
}
// src/components/week/DayScheduleCard.tsx
interface DayScheduleCardProps {
dayName: string;
dayShort: string;
subtitleLabel?: string;
isToday?: boolean;
title: string;
focusBadge: string;
durationMin: number;
volumeLbs?: number;
targetMuscles: string[];
variant?: 'normal' | 'condensed';
onPress: (dayName: string) => void;
}
// src/components/coach/StagedProposalCard.tsx
interface StagedProposalCardProps {
proposalId: string;
type: 'UPDATE_DAY' | 'UPDATE_WEEK';
targetDay?: string;
summaryDiff: string;
proposalData: any;
isApplied?: boolean;
onAccept: (proposalId: string, data: any) => void;
onModify: (summaryDiff: string) => void;
}
// src/components/workout/CurrentSetTargetCard.tsx
interface CurrentSetTargetCardProps {
setNumber: number;
targetWeightLbs: number;
targetReps: number;
isCompleted: boolean;
onWeightChange: (weight: number) => void;
onRepsChange: (reps: number) => void;
onToggleComplete: () => void;
}5. クライアントの状態管理とデータフロー マトリックス
5.1 レイテンシと読み込みポリシー: 2 段階のエンジン
- ジムフロアの楽観的 UI(16 ミリ秒未満): セットのチェック、重量や回数の増加、タブの切り替え、器具の切り替えなど、ジムフロアでの触覚的な操作は、16 ミリ秒以内にローカル状態を更新して再レンダリングします。ユーザーがウェイトを保持している間、読み込みインジケーターによってブロックされることはありません。
- 非同期オペレーション用の明示的な読み込みスピナーとインジケーター:
- AI コーチとの会話(
POST /api/coach/chat): チャット メッセージを送信すると、Gemini が回答またはワークアウトの提案を返すまで、チャット ストリームにコーチの思考スピナー バブルがアニメーションで表示されます。 - 初期コールド スタートとネットワーク同期: ビューコンテナは、
weekly_plansまたはhistorical_workoutsの取得中にスケルトン ローダーと高コントラストの読み込みスピナーを表示します。 - ワークアウトの送信(
[Finish]): セッションのペイロードが Cloud Firestore のPOST /api/workouts/logに commit されている間、終了確認モーダルにアクティブな読み込みスピナーが表示されます。
- AI コーチとの会話(
5.2 クライアント ストアのアーキテクチャとドメイン境界
| 店舗レイヤ | 状態ドメインと主要プロパティ | 永続性とキャッシュ ポリシー | 調整されたバックエンド契約 |
|---|---|---|---|
useActiveSessionStore(Zustand) |
sessionState(unstarted、running、paused)、elapsedSeconds、currentStepIndex、steps: WorkoutStep[]、loggedExercises |
localStorage に 1 秒ごとに同期されるため、スマートフォンのロックや更新による復元が確実に行われます。 |
完了したペイロードを POST /api/workouts/log に送信します。 |
useWeeklyPlanQuery(TanStack Query) |
現在の 7 日間のスケジュール、1 日のワークアウト オブジェクト、スーパーセット ブロック、完了ステータス(scheduled、completed)。 |
TanStack Query(staleTime: 5 min)にキャッシュされ、起動時にプリフェッチされます。 |
ユーザーが POST /api/coach/chat からの提案に対して [Accept Plan] をタップしたときに commit されます。 |
useCoachChatStore(Zustand + TanStack) |
時系列のメッセージ吹き出し、activeContext、プロンプト チップ、isCoachTyping: boolean、段階的な提案。 |
リアルタイム サブスクリプション(onSnapshot)を使用して、Firestore の週次チャット バケットと同期。 |
クエリを POST /api/coach/chat にディスパッチします。 |
useProgressQuery(TanStack Query) |
過去のワークアウトのアーカイブ、PR レコードボード(/users/{userId}/prs)、全期間のボリューム集計、期間フィルタ(4W、3M、1Y)。 |
TanStack Query(staleTime: 10 min)にキャッシュに保存されます。 |
ワークアウト セッションの完了直後に更新されます。 |
useUserProfileStore(TanStack Query) |
装備インベントリ、ユニット(LBS/KG)、サウンド / 触覚の切り替え、3 段階の生活記憶(必需品、生理的、状況的)。 |
TanStack Query にキャッシュされ、Firestore user_profiles と双方向で同期されます。 |
POST /api/coach/chat の memory_mutations を使用して自律的に更新されます。 |
useExerciseLibraryQuery(TanStack Query) |
静的な百科事典のエントリ、動画の URL、主動筋 / 補助筋、手順、フォームのヒント。 | 無限の TTL(staleTime: Infinity)を使用した積極的なキャッシュ ファースト。 |
不変の Firestore exercises コレクションからの読み取り専用。 |
5.3 事前計算されたアクティブ セッションのステップグラフ
export interface WorkoutStep {
stepIndex: number; // 1-based index (e.g. 1 to 13)
totalSteps: number; // Total sets in the session
blockIndex: number; // Index of block in day plan
blockType: 'straight' | 'superset';
exerciseId: string; // Canonical slug
exerciseName: string; // Display name
setNumber: number; // Set index for this exercise (1, 2, 3...)
targetWeightLbs: number; // Target weight in lbs
targetReps: string | number; // Target reps (e.g. "8-10")
restSecondsAfter: number; // Prescribed rest duration
isCompleted: boolean; // Completion checkmark status
actualWeightLbs?: number; // User entered weight
actualRepsCompleted?: number; // User entered reps
}5.4 エンドツーエンドのデータフローとミューテーション マトリックス
| ユーザー トリガー | 元のビュー | クライアント ストアが変更されました | UI のレイテンシ / 読み込み状態 | バックエンドの調整 | ロールバック / 失敗の処理 |
|---|---|---|---|---|---|
[[✓] セットを完了] をタップします。 |
/workout/active |
useActiveSessionStore |
<16ms(楽観的) |
ローカル セッション バッファが更新され、ネットワーク待機がなくなりました。 | 信頼性の高いローカル永続性を備えたローカルメモリ書き込み。 |
[Finish Workout] をタップします。 |
/workout/active |
useActiveSessionStore、useWeeklyPlanStore、useProgressStore |
同期中にボタンに読み込み中のスピナーが表示される | POST /api/workouts/log に送信します。Firestore の日を completed としてマークします。 |
オフラインの場合は、ペイロードをローカルの同期バッファにキューに入れ、再接続時に再試行します。 |
[Accept Plan] をタップします。 |
/coach |
useWeeklyPlanStore、useCoachChatStore |
インスタント UI スタンプ("✓ Plan Applied") |
proposal.data を Firestore weekly_plans に commit します。 |
ネットワークに障害が発生すると、トースト通知でユーザーに通知アラートが表示され、ボタンはアクティブなままになり、再試行できます。 |
| Chat でメッセージを送信する | /coach |
useCoachChatStore、useUserProfileStore |
楽観的なバブルの追加 + コーチの思考スピナー | POST /api/coach/chat を呼び出します。返された commit memory_mutations。 |
スピナーを「送信できませんでした。[タップして再試行]」というバブルが表示されます。 |
| 装備チップを切り替える | /settings |
useUserProfileStore |
インスタント切り替え(<16ms) |
Firestore user_profiles へのバックグラウンド書き込みをディスパッチします。 |
書き込みに失敗した場合は、チップの選択を元に戻し、エラーのトーストを表示します。 |
AI メモリーのアイテムを削除する [🗑] |
/settings |
useUserProfileStore |
即時削除(<16ms) |
Firestore user_profiles に削除ミューテーションをディスパッチします。 |
メモリ項目を復元し、削除に失敗した場合はユーザーに通知します。 |
6. エンドツーエンドのユーザー ジャーニーとわかりやすいテスト マトリックス
- スイート 1: アプリシェル、ナビゲーション、ルート処理(テスト 1.1 ~ 1.6: ルート起動リダイレクト、永続ドック、アクティブ セッションのクイックリターン、無効な曜日のフォールバック、不明なルート 404 のフォールバック、無効なエクササイズ ディープリンクのフォールバック)。
- スイート 2: 1 日のワークアウト ビューとブロック レンダリング(テスト 2.1 ~ 2.7: 動的な日付タイトルの解決、特定の日付のルートと戻るボタン、単一ブロックとスーパーセット ブロックの処理、空の日付のプレースホルダ、完了したワークアウトのスタンプ、中央の再生ボタンでの純粋なナビゲーション、コーチ コンテキストの添付による変更)。
- スイート 3: 週間スケジュール ビューと 1 日のスケジュール カード(テスト 3.1 ~ 3.6: 週間統計のレンダリング、スケジュール設定された日のみのフィルタ、今日の視覚的なコールアウト、1 日カードのナビゲーション、週ナビゲーションの変更、縮小バリアントのレンダリング)。
- スイート 4: コーチチャットと段階的なミューテーション(テスト 4.1 ~ 4.7: チャット メッセージ ストリームのレンダリング、コンテキスト バナーの添付と非表示、プロンプト チップの事前入力、音声入力の切り替え、段階的なミューテーションの分離、提案の承認と commit、週次オーバーホール提案のプレビューと commit)。
- スイート 5: エクササイズの詳細と百科事典(テスト 5.1 ~ 5.5: エクササイズの詳細のレンダリング、自動ループのミュート再生、動画の一時停止切り替え、主筋肉タグと副筋肉タグ、履歴の「戻る」ナビゲーションとフォールバック)。
- スイート 6: アクティブなワークアウト トラッカーとロギング(テスト 6.1 ~ 6.7: 未開始の初期状態と手動開始、ストップウォッチの一時停止と再開、触覚セットのロギングとチェックマークの切り替え、スーパーセット ステップの交互、エクササイズ モーダルの入れ替えと置換、終了確認とサーバー同期、リロード時のセッション状態の復元)。
- スイート 7: 進捗状況と履歴の分析(テスト 7.1 ~ 7.5: 進捗状況ダッシュボードの概要のレンダリング、PR ショーケース バナーの表示、グラフの期間の切り替え、履歴が空の状態、過去のセッション履歴のタップ)。
- スイート 8: ユーザー設定、装備、AI メモリ(テスト 8.1 ~ 8.4: 設定ビューのレンダリング、装備の切り替えの更新、ユニットの切り替え、AI メモリアイテムの削除)。