Skip to content

Source Architecture

WingStats Manager のフロントエンド (src/) の設計ドキュメント。最近のリファクタ(store 4 層分割 / log analyzer 3 層分割 / Container/View/Hook 3 層分離 / screens 構造)を反映している。

全体像

エントリポイントと screens 構造

ファイル役割
src/main.tsx管理画面 (ControlPanel) のエントリ。<App /> をマウント
src/overlay-main.tsxOBS 用オーバーレイのエントリ。<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 persist middleware の設定。onRehydrateStorageLAYOUT_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.ts

3 層 + finalize の責務分離

責務テスト
line1 行を ParsedLine に変換する純粋関数群LineParser.test.ts
fsm状態遷移管理(明示的遷移表)StreamFsm.test.ts
aggregatorpicks / 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.tsOBS 検出 / 推奨設定生成
src/utils/imageLoader.ts + imageImports.ts画像 import (scripts で生成)
src/hooks/useDraggable.tsドラッグ実装
src/theme/themes.tsMUI テーマ定義
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-coverage spec で例外理由を doc に明記)

開発スクリプト

コマンド用途
yarn devVite dev server (control + overlay)
yarn dev:electronElectron 開発モード
yarn buildcontrol + overlay バンドル
yarn build:electronElectron アプリビルド
yarn tsc --noEmit型チェック
yarn lintESLint (max-warnings 0)
yarn test:unitvitest 全実行
yarn storybookStorybook 起動

拡張時のチェックリスト

新しい機能を追加する際の典型フロー:

  1. Type を src/types/ に追加
  2. State / Actionssrc/store/overlay/state.ts + actions.ts に追加(必要なら persist.ts で永続化対象に)
  3. UI は Container/View/Hook の 3 層で組む(既存パターン踏襲)
  4. Storybook に story を追加
  5. Test__tests__/ に追加(必要に応じて src/test-helpers/ を活用)
  6. 必要なら IPCelectron/preload/api/ に追加(Electron Main 参照)

Last verified: 2026-05-06 / against commit e351b23a