Source Architecture
WingStats Manager のフロントエンド (src/) の設計ドキュメント。最近のリファクタ(store 4 層分割 / log analyzer 3 層分割 / Container/View/Hook 3 層分離 / screens 構造)を反映している。
全体像
エントリポイントと screens 構造
| ファイル | 役割 |
|---|---|
src/main.tsx | 管理画面 (ControlPanel) のエントリ。<App /> をマウント |
src/overlay-main.tsx | OBS 用オーバーレイのエントリ。<Overlay /> を直接マウント |
src/App.tsx | パス (window.location.pathname) で control / overlay / bp-opponent を分岐 |
src/screens/ 配下は 画面単位 のディレクトリ。
src/screens/
├── broadcast/ # OBS オーバーレイ画面
│ ├── Overlay.tsx # Container 層 (153 行)
│ ├── overlay/
│ │ ├── views/ # 純粋プレゼンテーション
│ │ ├── hooks/ # 副作用 hook (clickthrough / font / state log 等)
│ │ ├── AnnouncementArea.tsx
│ │ ├── CommentArea.tsx
│ │ ├── StatsArea.tsx
│ │ └── ClockArea.tsx
│ └── Overlay.css
└── bp-opponent/ # BP 対戦相手ウィンドウ
├── BPOpponentContainer.tsx
├── BPOpponentView.tsx
├── components/
└── hooks/Container / View / Hook 3 層分離
Overlay.tsx は 466 行の monolith から Container 層 (153 行) に圧縮されている。責務分離は以下:
| 層 | 責務 | 例 |
|---|---|---|
| Container | ストア読み出し / hook 合成 / View へ props を渡す | Overlay.tsx, BPOpponentContainer.tsx |
| Hook | 副作用 (clickthrough / font 適用 / state log 等) | useOverlayClickthrough.ts, useDebugOverlay.ts |
| View | 純粋プレゼンテーション。Store も IPC も参照しない | OverlayView.tsx, BPOpponentView.tsx |
同じパターンが PlayerDashboard 系 (src/components/tabs/player-dashboard/) にも適用済み。
状態管理: Zustand Store
src/store/ は 4 層に分割 されている。useOverlayStore.ts は 組み立てだけを行う。
src/store/
├── useOverlayStore.ts # create() で 4 層を合成
├── index.ts # 公開 API + sync ブリッジ初期化
├── overlay/
│ ├── state.ts # OverlayStore 型 + 初期 state + 派生関数
│ ├── actions.ts # createOverlayActions (set/get を受け取る)
│ ├── sync.ts # createSyncBridge (broadcastSet 経由で adapter に流す)
│ └── persist.ts # createOverlayPersistOptions (zustand persist 設定)
└── sync/
├── adapter.ts # SyncAdapter interface
├── electronAdapter.ts
├── broadcastAdapter.ts
├── wsAdapter.ts
├── localStorageAdapter.ts
└── compositeAdapter.ts # 複数 adapter を 1 つに合成4 層分割の責務
- state: 純粋な状態定義 +
calculateWinRate等の派生関数 - actions:
set / getを受け取って各 setter を生成 - sync:
broadcastSetで Adapter に変更を流す。isReceivingフラグでループを防ぐ - persist: zustand
persistmiddleware の設定。onRehydrateStorageでLAYOUT_DEFAULTSから欠落フィールドを補完
詳細は Sync and State を参照。
ログ解析エンジン
src/utils/logAnalyzer/ は 3 層 + finalize + statistics に分割されている。
src/utils/logAnalyzer/
├── core/
│ ├── line/LineParser.ts # 行単位の純粋パーサ
│ ├── fsm/StreamFsm.ts # 状態機械 (IDLE → BP → INGAME → RESULT)
│ ├── aggregator/Aggregator.ts # picks / bans / players の集約
│ ├── finalize/finalizeMatchData.ts # 試合データの最終整形
│ ├── LogParserCore.ts # 一括解析(テスト用)
│ └── StreamLogParserV2.ts # ストリーム解析(リアルタイム用)
├── statistics/aggregate.ts # 純粋関数の試合統計集計
├── StatisticsCalculator.ts # 集計の facade
├── CharacterService.ts # キャラ ID ↔ 名前変換
├── LogParser.ts # ファイル一括解析の facade
└── types.ts3 層 + finalize の責務分離
| 層 | 責務 | テスト |
|---|---|---|
| line | 1 行を ParsedLine に変換する純粋関数群 | LineParser.test.ts |
| fsm | 状態遷移管理(明示的遷移表) | StreamFsm.test.ts |
| aggregator | picks / bans / players の収集 | Aggregator.test.ts |
| finalize | 集計結果の最終整形 (rank 推定 / type 確定) | finalizeMatchData.test.ts |
| statistics | 試合配列から統計指標を出す純粋集計 | aggregate.test.ts |
詳細は Log Parser Architecture を参照。
UI コンポーネント階層
src/components/
├── ControlPanel.tsx # 162 行 (Container 層)
├── controlPanel/ # 7 hooks + dialogs view
├── tabs/ # 機能別タブ
│ ├── db-viewer/
│ ├── player-dashboard/
│ ├── log-analysis/
│ ├── realtime-analysis/
│ ├── settings/
│ └── common/
├── common/ # 共有コンポーネント
└── hooks/MUI v6 / Grid v2 の利用
レイアウトは
Box/Container/Grid v2を優先Grid は v2 構文を使う(旧
item/ 直接xsプロパティは禁止)。例:tsx<Grid size={{ xs: 12, md: 6 }}>...</Grid>表示面は
Card/Paperを活用ストーリー (
src/stories/) で各コンポーネントの仕様を Storybook 化(49 ストーリー)
Utils と支援モジュール
| ディレクトリ / ファイル | 役割 |
|---|---|
src/utils/Logger.ts | アプリ全体の構造化ログ |
src/utils/configSync.ts | 設定エクスポート / インポート |
src/utils/obsUtils.ts | OBS 検出 / 推奨設定生成 |
src/utils/imageLoader.ts + imageImports.ts | 画像 import (scripts で生成) |
src/hooks/useDraggable.ts | ドラッグ実装 |
src/theme/themes.ts | MUI テーマ定義 |
src/types/ | 共通型 + electron API 型 |
src/constants/layoutDefaults.ts | レイアウトデフォルト |
src/test-helpers/ | テスト用 mock / factory |
命名・ファイル規約
- TypeScript 厳格モード:
anyの追加は禁止(残存箇所はeslint-any-todo.mdを参照) - 新規ファイルは
.ts/.tsxのみ。.js/.cjsは作らない - テストは
__tests__/か*.test.ts(x)で同一ディレクトリに配置 - 1 ファイル原則 200 行以内(例外は
code-split-and-coveragespec で例外理由を doc に明記)
開発スクリプト
| コマンド | 用途 |
|---|---|
yarn dev | Vite dev server (control + overlay) |
yarn dev:electron | Electron 開発モード |
yarn build | control + overlay バンドル |
yarn build:electron | Electron アプリビルド |
yarn tsc --noEmit | 型チェック |
yarn lint | ESLint (max-warnings 0) |
yarn test:unit | vitest 全実行 |
yarn storybook | Storybook 起動 |
拡張時のチェックリスト
新しい機能を追加する際の典型フロー:
- Type を
src/types/に追加 - State / Actions を
src/store/overlay/state.ts+actions.tsに追加(必要ならpersist.tsで永続化対象に) - UI は Container/View/Hook の 3 層で組む(既存パターン踏襲)
- Storybook に story を追加
- Test を
__tests__/に追加(必要に応じてsrc/test-helpers/を活用) - 必要なら IPC を
electron/preload/api/に追加(Electron Main 参照)
Last verified: 2026-05-06 / against commit e351b23a