Floating Windows 移行: 実装計画¶
1. 実装順序¶
dataモデルを先に確定する- Bevy Spike を別バイナリで作る(wgpu 共存性 PoC を含む)
- GUI 状態とメッセージを
uuid::Uuidベースへ移す - Bevy frontend を dashboard に接続する(Q1 解決まで着手不可)
- 既存 pane 内容を順に移植する
- 旧
pane_grid依存を削除する(popout サポートはこの Phase までスコープ外)
統一決定: - 旧
saved-state.jsonは破棄し、新形式にschema_version: u32を導入する。 - popout(ウィンドウ分離)は Phase 6 までスコープ外とする。 - focus 型はOption<PaneLocation>に抽象化する(旧Option<(window::Id, pane_grid::Pane)>は廃止)。 - 座標系は logical px、原点 top-left、Y 軸下向き。Cameraは world→screen の affine 変換とする。
2. 主要変更対象¶
Phase 1¶
data/src/layout/mod.rsdata/src/layout/pane.rsdata/src/layout/dashboard.rsdata/tests/fixtures/saved-state-legacy-pane-grid-single.json(新規追加: 旧pane_grid形式・単一 pane)data/tests/fixtures/saved-state-legacy-with-popout.json(新規追加: 旧pane_grid形式・popout 込み)- 両 fixture を読み込んで
Dashboard::default()にフォールバックする挙動を Phase 1 で確立する。
Phase 2¶
Cargo.tomlsrc/bin/または Bevy frontend 用モジュール(flowsurface-bevy-spikeバイナリ)- wgpu 共存性 PoC: iced(既存 wgpu バックエンド)と Bevy(wgpu)の同時稼働可能性を検証する。 この PoC 結果が Q1 への入力となり、結論が出るまで Phase 4 へは進まない。
- pane 種別レンダリング分類の確定: architecture §3.5 の 3 分類
(
Bevy native/host existing renderer/keep iced overlay)に Heatmap / Kline / Ladder / TAS / Starter / Comparison chart / 設定 modal / indicator picker / study configurator / 認証 / Tachibana ログイン UI を割り当てて確定する。 Phase 3 着手時点で分類が未確定の場合、Phase 3 を始めない(Q2 / Q3 はこのフレームで closing する)。 分類確定の成果物としてdata::PaneKind::renderer_class()を導入し、全バリアントの 返り値を unit test (pane_kind_renderer_class_covers_all_variants) で assert する。 - iced::canvas host 困難リスクの実機判定: iced::canvas は texture 単独書き出し標準 API
を持たないため、Kline
host existing renderer不能リスクを Phase 2 spike で実機判定する。 不能と判定された場合は Kline をBevy native再実装にスコープ変更する。 - 入力境界契約の文書化: architecture §4.1 の優先順位(iced overlay > Bevy pane chrome > Bevy chart surface > Bevy canvas)と INV-INPUT-1〜4 を Phase 2 終了時点で確定し、 Phase 4 着手時点で iced 残置 UI の一覧を appendix として追加する。
Phase 3¶
Phase 3 で置換対象となる現行フィールドは以下の 3 つ(シンボル名で明示): -
Dashboard::panes: pane_grid::State<pane::State>-Dashboard::focus: Option<(window::Id, pane_grid::Pane)>-Dashboard::popout: HashMap<window::Id, (pane_grid::State<pane::State>, WindowSpec)>これらを
uuid::Uuidベース ID およびOption<PaneLocation>型へ移行する。
src/screen/dashboard.rs(Dashboard::panes/Dashboard::focus/Dashboard::popoutを中心に改修)src/screen/dashboard/pane.rs(pane::State構造体 /pane::Messageenum)src/screen/dashboard/replay_pane_registry.rs(ReplayPaneRegistry::register/::resolve/::unregisterの 3 箇所でpane_grid::Paneを参照)src/layout.rs(SerializableLayout/Layout::from_dashboard/Layout::apply)src/main.rs(Flowsurface::updateの pane 関連分岐 /Messageenum)src/modal/pane/settings.rs(SettingsModalの親 pane 特定)src/modal/pane/indicators.rs(IndicatorsModalの親 pane 特定)src/widget.rs(pane_grid依存ヘルパ)
実コード上の
pane_grid::Pane参照は 約 39 箇所 / 6 ファイル にわたる。暫定型: Phase 3 で
enum PaneLocation { Main(Uuid), Popout(window::Id, Uuid) }を導入する。Q1(wgpu 共存性)解決後にBevyWindow(...)バリアント追加候補とする(Phase 4 以降)。Acceptance: Phase 3 末で
PaneLocationを pattern match する箇所(メッセージ payload / modal の親特定 /replay_pane_registryの key 等)をGrepで列挙し、Q1 解決後の再 touch 範囲を確定する。
Phase 4-5¶
src/bevy_dashboard/または同等の新規モジュール群- dashboard と Bevy 間の同期レイヤ
3. 削除対象¶
pane_grid前提の状態管理PaneGrid::split呼び出し(Grep "panes.split("の全ヒット 現状 8 箇所)src/main.rs:2538OpenOrderPanel handler の panes.split()src/screen/dashboard.rs:231update(Message::SplitPane) の panes.split()src/screen/dashboard.rs:519fn merge_pane の panes.split()src/screen/dashboard.rs:547fn split_pane の panes.split()src/screen/dashboard.rs:911fn replace_new_pane / 自動生成 split (1) の panes.split()src/screen/dashboard.rs:927自動生成 split (2) の panes.split()src/screen/dashboard.rs:948OrderList 自動生成 split の panes.split()src/screen/dashboard.rs:964BuyingPower 自動生成 split の panes.split()- シンボル名は推定込み。正確な fn 名は Phase 3 着手時に再確認する。
- dashboard 用
pane_gridスタイル pane_grid::Paneベースの識別子配線(実コード 約 39 箇所 / 6 ファイル)
4. 検証¶
Phase ごとに acceptance を 1:1 で対応させる。
| Phase | 実行コマンド | テスト/ファイル | assert 内容 |
|---|---|---|---|
| Phase 1 | cargo test -p data |
data/src/layout/... 配下に 4 件必須 |
floatrect_rejects_negative_size / floating_pane_data_serde_roundtrip / camera_zoom_clamped / dashboard_legacy_pane_grid_falls_back_to_default |
| Phase 2 | cargo run -p flowsurface-bevy-spike + cargo test -p data pane_kind_renderer_class_covers_all_variants |
Bevy spike バイナリ(手動 + headless)+ data::PaneKind::renderer_class() unit test |
min サイズ 120×80 / focus 取得 pane の PaneZ が他より +1 以上 / ズーム範囲 0.25〜4.0 / マウスホイール 1 ノッチで 1.1 倍 / wgpu 共存性 PoC 結果が判定可能(Q1 への入力) / pane 種別レンダリング分類が architecture §3.5 の表で確定済み / 入力境界契約 INV-INPUT-1〜4 が architecture §4.1 で確定済み / pane_kind_renderer_class_covers_all_variants で全バリアントの分類値を assert |
| Phase 3 | cargo test --workspace |
src/screen/dashboard.rs::tests |
6 メッセージ(WindowMoved / WindowResized / WindowFocused / WindowClosed / WindowAdded / CameraChanged)の単体テストが pass / WindowClosed 受信で focus が他 pane へ移譲される / WindowAdded 受信で新 pane の PaneZ が最大値となる / WindowFocused 受信で対象 pane の PaneZ が他 pane +1 以上になる / INV-REPLAY 観測点: replay_registry_starts_1to1 / replay_registry_unregister_register_atomic / replay_registry_built_after_windows の 3 件が pass(spec §2 Phase 3 INV-REPLAY-1/2/3 に対応) |
| Phase 4 | cargo run + cargo test --workspace + bash scripts/measure_window_moved_rate.sh |
placeholder pane(手動操作)/ src/screen/dashboard.rs::tests の closing SoT / 強制 close unit & integration test / scripts/measure_window_moved_rate.sh 5 分計測 |
移動・close・ズーム・パンが動作する / INV-CLOSE-1: WindowClosed 受信時に 購読 stream cancel → aggregator drop → replay_pane_registry 解除 が data::Dashboard.windows 削除より前に行われることを assert / inv_input_1_overlay_blocks_canvas_drag / inv_input_2_closing_pane_excluded_from_hit_test / inv_input_5_keyboard_focus_overlay_owns / inv_input_6_drag_dead_zone_5px / inv_input_6_wheel_modifier_zoom / inv_input_7_context_menu_overlay_first / inv_close_1_teardown_drop_5s_timeout / inv_close_1_sequential_not_parallel / inv_close_1_closing_rejects_pointer_input の 9 件が pass(INV-INPUT-8(touch / tablet pen)は MVP non-goal のため test 名なし) / teardown timeout 後の auto-fail(強制 close)と再試行 UI が動作する unit/integration test が pass / Q5 計測: scripts/measure_window_moved_rate.sh で 5 分計測し、> 120 events/s で (c) 16ms throttle 採用、< 60 events/s で (d) 即 commit 許容、許容 latency ≤ 16ms を判定する |
| Phase 5 | (手動確認 + 既存 unit/integration テスト + tests/manual/floating-windows-CHECKLIST.md) |
spec §6 機能保持マトリクス(C1〜C5 / K1〜K4 / H1〜H2 / L1〜L2 / T1〜T3 / S1〜S3 / CMP1〜CMP2 / P1〜P6)/ tests/manual/floating-windows-CHECKLIST.md |
「表示が崩れない」ではなく機能保持マトリクスの全項目(C1〜C5 / K1〜K4 / H1〜H2 / L1〜L2 / T1〜T3 / S1〜S3 / CMP1〜CMP2 / P1〜P6(popout 実装時のみ))が現状同等に動作すること。Kline overlay marker 配信(K2)/ indicator 追加・削除・並べ替え(K3)/ Kline Sync all(K4)/ Heatmap GPU 描画維持(H1)/ 設定 modal・indicator picker・study configurator が iced overlay として動作(C3 / C4)/ 入力境界契約 INV-INPUT-5/6/7(C5)が architecture §4.1 通りに動作(INV-INPUT-8 は MVP non-goal)。回帰した項目は Issue 起票し Phase 5 を完了させない / tests/manual/floating-windows-CHECKLIST.md を成果物として PR にチェック済み証跡を添付する(未添付の PR は Phase 5 完了とみなさない) |
| Phase 6 | cargo test --workspace + grep + e2e |
tests/fixtures/saved-state-legacy-*.json 2 種 |
2 fixture とも fallback assert が pass / e2e smoke ログは target = flowsurface::floating_windows, level = INFO で発行され、grep は target フィルタ後に行う。(1) target=flowsurface::floating_windows のログ中に dashboard_loaded uuids=N が出現する / (2) 観測ウィンドウ中に同 target のログで camera saved zoom= が 1 回以上観測される / (3) pane_grid 文字列が flowsurface-current.log に出現しない |
4.1 CI 組込¶
- 既存
.github/workflows/rust-tests.ymlにbevy-spike-buildjob を Phase 2 で追加する(新規 yml は作らない)。 - headless 実行は Linux で
bevy_ci_testingまたはbevy/x11を使用する。 - spike バイナリのビルド失敗 / headless 起動失敗を CI ゲートとする。
- Phase 6 完了後:
rg "pane_grid" src/ data/src/ -t rust -lが空であることを CI で fail させる job をrust-tests.ymlに追加する(残存検出ガード)。 - Phase 6 完了後: e2e ログ 3 項目(target=
flowsurface::floating_windows、§4 Phase 6 行の (1)〜(3))の観測を nightly job として CI に組み込む。
Bevy 自動テスト方針は Q7(
open-questions.md)を参照。同質問は別 implementer が更新する。