Skip to content

Source Architecture ​

WingStats Manager のフロントエンド (src/) の設計ドキュメント。最近のリファクタ(store の分割と正本の一本化 / 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 層
│   ├── 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 は Container 層で、副作用は hook、描画は View に分けている。責務分離は以下:

層責務例
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/ は state / actions / sync に分割されている。useOverlayStore.ts は 組み立てだけを行う。

オーバーレイ設定の正本は配信サーバの所有者の <userData>/overlay-config.json ただ 1 つ(main 側 electron/services/obsBrowserSource/OverlayConfigOwner.ts)。 renderer は保存しない(zustand persist は撤去済み)。起動時に正本から全量を受け取り、変更したキーだけ(パッチ)を送る。

src/store/
├── useOverlayStore.ts          # create() で state / actions / sync を合成
├── index.ts                    # 公開 API + sync ブリッジ初期化
├── overlay/
│   ├── state.ts                # OverlayStore 型 + 初期 state + 派生関数
│   ├── actions.ts              # createOverlayActions (set/get を受け取る)
│   ├── sync.ts                 # createSyncBridge (broadcastSet 経由で adapter に流す・正本の revision / bootId で古い全量を捨てる)
│   └── legacyOverlayStorage.ts # 旧 localStorage `overlay-storage` を移行元として読むだけ
└── sync/
    ├── adapter.ts              # SyncAdapter interface
    ├── electronAdapter.ts
    ├── broadcastAdapter.ts
    ├── wsAdapter.ts
    ├── localStorageAdapter.ts
    └── compositeAdapter.ts     # 複数 adapter を 1 つに合成

分割の責務 ​

  • state: 純粋な状態定義 + calculateWinRate 等の派生関数
  • actions: set / get を受け取って各 setter を生成
  • sync: broadcastSet で Adapter に変更を流す。isReceiving フラグでループを防ぐ
  • legacyOverlayStorage: 旧版が zustand persist で保存していた overlay-storage を読み、LAYOUT_DEFAULTS のキーに絞って整えて seed として送る(src/main.tsx の起動時に 1 回。正本が未作成のときだけ使われる)。旧キーの書き換え・削除はしない

詳細は 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              # 261 行 (Container 層)
├── controlPanel/                 # hooks/ + views/ (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 化(54 ストーリー)

Utils と支援モジュール ​

ディレクトリ / ファイル役割
src/utils/Logger.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 / Actions を src/store/overlay/state.ts + actions.ts に追加(既定値は src/constants/layoutDefaults.ts。保存は正本 overlay-config.json 側で行われる)
  3. UI は Container/View/Hook の 3 層で組む(既存パターン踏襲)
  4. Storybook に story を追加
  5. Test を __tests__/ に追加(必要に応じて src/test-helpers/ を活用)
  6. 必要なら IPC を electron/preload/api/ に追加(Electron Main 参照)

Last verified: 2026-06-01 / commit 12bb761f