12 KiB
HCIE-egui-app Refactoring Plan
Goal: Reduce spaghetti score from 7.5/10 to ~3/10 by splitting god files, removing dead code, standardizing architecture.
Constraints:
- Each phase must compile (
cargo check) and pass existing tests (cargo test) - Do NOT change any business logic
- Do NOT touch locked engine crates (Layer 1–5)
- All new files go in
hcie-egui-app/crates/hcie-gui-egui/src/ - Panel crates go in
hcie-egui-app/crates/
Phase 0: Baseline & Preparation
Files: hcie-egui-app/Cargo.toml, test files.
- Run full test suite, record pass/fail:
cargo test -p hcie-gui-egui 2>&1 | tee /tmp/baseline_test.log cargo clippy -p hcie-gui-egui 2>&1 | tee /tmp/baseline_clippy.log - Remove direct engine crate deps from
hcie-egui-app/Cargo.toml:- Remove
hcie-protocol,hcie-io,hcie-color,hcie-blendentries - If any crate in workspace actually uses them, add the dep only there
- Remove
- Verify compile with
cargo check -p hcie-gui-egui
Phase 1: Remove Grimdock Backend
Files to delete:
hcie-egui-app/crates/hcie-gui-egui/src/app/grim_dock.rs
Files to modify:
hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs:- Remove
pub mod grim_dock; - Remove
use_grimdock: boolfield fromHcieApp - Remove
grim_dock_host: Option<GrimDockHost>field - Remove
grim_floating_panelsfield - Remove all
if self.use_grimdock { ... } else { ... }branches indrain_events,ui_main_assembly,HcieApp::new - Remove
default_dock_state()calls guarded by grimdock toggle - Simplify
drain_eventsDocClosed handler (remove grimdock branch)
- Remove
hcie-egui-app/crates/hcie-gui-egui/src/app/dock_controller.rs:- Remove any grimdock references
hcie-egui-app/Cargo.toml:- Remove
grimdockdependency if present
- Remove
- Any test files referencing grimdock
Verification: cargo check -p hcie-gui-egui && cargo test -p hcie-gui-egui
Phase 2: Extract State Structs from app/mod.rs
Create app/state/ module. Move struct definitions out of mod.rs.
2a — app/state/mod.rs
pub mod app_state;
pub mod document;
pub mod tool_state;
2b — app/state/document.rs
Move AppDocument struct + impl (lines ~431–541) exactly as-is. Re-export from state::document.
2c — app/state/tool_state.rs
Move ToolState struct + impl (lines ~546–950) exactly as-is. Includes TextEditState, AiChatMessage, AiChatConfig, ComfyStatus, ComfyConfig, ComfyMode, VisionTask, VisionBackend, SelectionTransform, TransformHandle, Settings.
2d — app/state/app_state.rs
Move HcieApp struct + fields (lines ~960–1095), SaveErrorState (lines ~1097–1108), AppMode, constants (TOOL_SLOTS, PANEL_SIDES).
Replace in app/mod.rs
- Remove moved struct definitions
- Add
mod state;andpub use state::*; - Update all references to use new paths
Verification: cargo check -p hcie-gui-egui && cargo test
Phase 3: Extract Event Handlers from drain_events
Rationale: The 1,475-line drain_events function handles 60+ event variants inline. Each domain gets its own handler file.
Create app/events/ module:
Files:
| File | Handles AppEvent variants |
|---|---|
app/events/mod.rs |
Dispatch function + EventError type |
app/events/layers.rs |
LayerAdd, LayerDelete, LayerSelect, LayerDuplicate, LayerMerge, LayerFlatten, LayerClear, LayerMoveUp/Down, SetLayerOpacity, SetLayerBlend, ToggleLayerVisibility |
app/events/file.rs |
DocCreated, DocClosed, FileOpen, FileSave, FileSaveAs, FileExport, FileReload |
app/events/selection.rs |
SelectionGrow, SelectionShrink, SelectionFeather, SelectionInvert, SelectionDeselect, SelectionFromLayer |
app/events/vision.rs |
VisionSmartSelect, VisionSegmentAnything, VisionInpaint, VisionSuperResolution, VisionBackgroundRemove |
app/events/ai.rs |
AiChatSend, AiChatStop, AiActionRequest, AiToolCall |
app/events/edit.rs |
ClipboardCopy, ClipboardCut, ClipboardPaste, DeleteLayerContent, ClearSelection, TransformStart/Apply/Cancel |
app/events/view.rs |
ZoomIn, ZoomOut, ZoomReset, PanReset, ToggleGrid, ToggleGuides, ThemeChanged |
app/events/mod.rs pattern:
pub fn dispatch_event(app: &mut HcieApp, ctx: &egui::Context, event: AppEvent) {
match event {
AppEvent::LayerAdd { .. } => layers::handle_layer_add(app, ctx, ...),
AppEvent::FileSave => file::handle_file_save(app, ctx),
// ... one match arm per variant, delegating to handler fn
}
}
Replace drain_events body:
Collapse the 1,475-line function to:
pub fn drain_events(&mut self, ctx: &egui::Context) {
let mut count = 0;
while let Some(event) = self.event_bus.pop() {
count += 1;
if count > 100 { break; }
let _plugin_actions = self.plugin_host.dispatch_app_event(&event, &mut self.state);
events::dispatch_event(self, ctx, event);
}
}
Verification: cargo check -p hcie-gui-egui && cargo test -p hcie-gui-egui
Phase 4: Extract Lifecycle & Shortcuts from app/mod.rs
app/lifecycle.rs
HcieApp::new(cc, tablet_state, panel_screenshot_request)(~315 lines)HcieApp::logic(ctx)(~165 lines)enforce_constraints(...)(~202 lines)
app/shortcuts.rs
HcieApp::handle_shortcuts(ctx)(~195 lines)
app/theme.rs
apply_theme(ctx, theme)functionsetup_custom_fonts(ctx)functionThemeColorsstruct andThemePresetenumdefault_font_bytes()functionPANEL_SIDESconstant
app/dock_layout.rs
default_dock_state()(~30 lines)pane_from_name()(~15 lines)
After extraction, app/mod.rs should be ~400-600 lines (public module declarations, re-exports, drain_events function, ui_main_assembly).
Verification: cargo check -p hcie-gui-egui && cargo test
Phase 5: Split panels.rs (1,658 lines → individual panel crates)
Decision: Each major panel becomes a separate crate in hcie-egui-app/crates/.
New crates to create:
| Crate | Source code from panels.rs |
Approx lines |
|---|---|---|
egui-panel-menubar |
show_menu_bar() + helpers |
~120 |
egui-panel-toolbar |
show_unified_toolbar() + helpers |
~250 |
egui-panel-statusbar |
show_status_bar() + helpers |
~80 |
egui-panel-layers |
show_layers_ui() + helpers |
~400 |
egui-panel-history |
History panel section | ~80 |
egui-panel-properties |
Properties panel section | ~350 |
egui-panel-plugins |
show_plugins_ui() |
~80 |
Each crate follows:
crates/egui-panel-<name>/
├── Cargo.toml (depends on hcie-engine-api, egui)
└── src/
└── lib.rs (exports pub fn show(…))
app/panels.rs after extraction:
Becomes a thin re-export module (~40 lines).
Update:
hcie-egui-app/Cargo.toml— add new crate deps- All call sites
panels::show_menu_bar(app, ctx)→egui_panel_menubar::show(app, ctx)
Verification: cargo check -p hcie-gui-egui && cargo test
Phase 6: Split canvas/mod.rs (1,800 lines)
Files to create in canvas/:
| File | Content | Est. lines |
|---|---|---|
canvas/navigation.rs |
Zoom computation, scroll-wheel zoom, middle-mouse pan | ~200 |
canvas/drawing.rs |
Stroke handling, brush/pen input, pressure mapping | ~400 |
canvas/selection.rs |
Lasso selection drawing, marching ants, selection rect | ~250 |
canvas/text.rs |
Text overlay editing canvas | ~150 |
canvas/interaction.rs |
Pointer event dispatch, tool routing, context menu | ~300 |
After extraction, canvas/mod.rs contains only CanvasWidget struct + ui() entry point that delegates to sub-modules.
Verification: cargo check -p hcie-gui-egui && cargo test && cargo test -p hcie-gui-egui --test canvas_engine
Phase 7: Remove Dead Code
-
Remove
#![allow(dead_code)]from these files one at a time and fix what the compiler flags:app/mod.rsapp/panels.rsapp/grim_dock.rs(already deleted in Phase 1)app/dock.rsapp/dialogs.rsapp/gui_layout.rsapp/viewer.rsevent_bus.rsplugin.rs
-
Remove unused modules:
app/custom_loaders.rs(if superseded bycustom_loaders_kra_v2.rs)app/panel_layout.rs→panel_layout.jsonif layout-driven is readyapp/scrollable_panel.rsif nothing imports it
-
Remove dead code inside live files (commented-out blocks, TODO stubs marked dead)
Verification: cargo check -p hcie-gui-egui && cargo clippy -p hcie-gui-egui
Phase 8: Fix Panic Points
Replace unwrap() / expect() calls with proper error handling:
-
30 unwrap() calls — audit each:
Mutex::lock().unwrap()→ if not poison-poison, uselet Ok(guard) = ...elselog::error! + continueHashMap::get().unwrap()→.unwrap_or_else(|| { log::warn!; default })Vec::last().unwrap()→ pattern match orif let Some
-
24 expect() calls — same treatment with informative error messages in
expect()or convert to Result -
Priority order: event handlers (user-triggered) > startup > tests
Verification: cargo check -p hcie-gui-egui && cargo test
Phase 9: Standardize Panel Architecture
After Phase 5, all panels are separate crates. Now:
-
Define a
Paneltrait inhcie-gui-egui:pub trait Panel { fn id(&self) -> &'static str; fn title(&self) -> &'static str; fn show(&mut self, app: &mut HcieApp, ui: &mut egui::Ui); } -
Wrap each panel crate's
showfunction in a struct implementingPanel. -
Register panels in a
PanelRegistryowned byHcieApp, replacing the manualmatchdispatch indock.rs/HcieTabViewer.
This unifies how panels are discovered, rendered, and toggled.
Verification: cargo check -p hcie-gui-egui && cargo test -p hcie-gui-egui --test dock_behavior --test invariant_guards
Dependency Graph (safe order)
Phase 0 (baseline)
│
▼
Phase 1 (grimdock remove) ──► Phase 7 (dead code, partial)
│
▼
Phase 2 (state extract)
│
▼
Phase 3 (event handlers)
│
▼
Phase 4 (lifecycle extract)
│
▼
Phase 5 (panel crates) ──► Phase 9 (panel trait)
│
▼
Phase 6 (canvas split)
│
▼
Phase 7 (dead code, full)
│
▼
Phase 8 (unwrap fix)
Phases 1–4 and 7 are sequential (mod.rs shrinks step by step). Phases 5, 6 can parallelize. Phase 8 is last because line numbers shift. Phase 9 depends on Phase 5.
Risk Mitigation
| Risk | Mitigation |
|---|---|
app/mod.rs changes break imports everywhere |
Run cargo check after every file move; use IDE rename refactoring |
drain_events extraction introduces subtle behavior changes |
Write characterization tests first: record (event → resulting state) pairs for each variant, then assert same behavior after extraction |
| Panel crate extraction breaks dock integration | Keep egui-panel-* crates thin; pass &mut HcieApp so they have full access |
unwrap() removal changes control flow |
Wrap each removal in debug_assert! + if let with log::error! — panic behavior becomes logged warning, but execution continues |
| Dead code removal removes something used only at runtime | Run the app with cargo run after each removal pass; use #[cfg(not(debug_assertions))] for truly required dead code |
Validation Gate (run after each phase)
cargo check -p hcie-gui-egui 2>&1
cargo clippy -p hcie-gui-egui 2>&1 # deny warnings
cargo test -p hcie-gui-egui 2>&1
# Dock-specific tests:
cargo test -p hcie-gui-egui --test dock_behavior --test invariant_guards
# Visual baseline (if CI has display):
cargo run -p hcie-gui-egui -- --screenshot-panel "Layers" /tmp/check_Layers.png