|
| 1 | +# TUI Resize Fix + Session Restore Design |
| 2 | + |
| 3 | +## Part 1: Window Resize Mouse Coordinate Fix |
| 4 | + |
| 5 | +### Problem |
| 6 | + |
| 7 | +After terminal window resize, mouse click coordinates drift — all interactive areas (tab bar, miller columns, status bar) respond to wrong positions. The terminal reports mouse positions based on stale coordinate mapping. |
| 8 | + |
| 9 | +### Root Cause |
| 10 | + |
| 11 | +bubbletea's mouse tracking mode is not automatically reset when the terminal window size changes. Some terminals (tmux, screen) need the mouse tracking ANSI sequences re-sent to recalibrate. |
| 12 | + |
| 13 | +Additionally, the recently added LLM anchor line may have changed the Y-offset calculation without updating mouse coordinate translation. |
| 14 | + |
| 15 | +### Fix |
| 16 | + |
| 17 | +1. In `WindowSizeMsg` handler, reset mouse tracking: |
| 18 | +```go |
| 19 | +case tea.WindowSizeMsg: |
| 20 | + a.width = msg.Width |
| 21 | + a.height = msg.Height |
| 22 | + // Reset mouse tracking to recalibrate coordinates |
| 23 | + return a, tea.Batch( |
| 24 | + tea.DisableMouse, |
| 25 | + tea.EnableMouseCellMotion, |
| 26 | + ) |
| 27 | +``` |
| 28 | + |
| 29 | +2. Audit all Y-coordinate offsets in mouse handling: |
| 30 | + - `msg.Y - 1` offset for tab bar — verify against actual chrome height |
| 31 | + - If LLM anchor line is rendered, add +1 to Y offset |
| 32 | + - Status bar hit test Y coordinate check |
| 33 | + |
| 34 | +### Files |
| 35 | + |
| 36 | +- `cmd/mxcli/tui/app.go` — WindowSizeMsg handler, mouse Y-offset audit |
| 37 | + |
| 38 | +## Part 2: Session Restore (-c flag) |
| 39 | + |
| 40 | +### Overview |
| 41 | + |
| 42 | +Save TUI state on exit, restore on startup with `-c` flag. Enables quick restart-verify cycles during development. |
| 43 | + |
| 44 | +### Storage |
| 45 | + |
| 46 | +File: `~/.mxcli/tui-session.json` (overwritten on each exit) |
| 47 | + |
| 48 | +### Session State Schema |
| 49 | + |
| 50 | +```json |
| 51 | +{ |
| 52 | + "version": 1, |
| 53 | + "timestamp": "2026-03-26T01:30:00Z", |
| 54 | + "tabs": [ |
| 55 | + { |
| 56 | + "projectPath": "/path/to/App.mpr", |
| 57 | + "millerPath": ["Project", "MyFirstModule", "Pages"], |
| 58 | + "selectedNode": "P_ComboBox_Enum", |
| 59 | + "previewMode": "MDL" |
| 60 | + } |
| 61 | + ], |
| 62 | + "activeTab": 0, |
| 63 | + "viewStack": [ |
| 64 | + {"type": "browser"}, |
| 65 | + {"type": "overlay", "title": "mx check", "filter": "all"} |
| 66 | + ], |
| 67 | + "checkNavActive": true, |
| 68 | + "checkNavIndex": 1 |
| 69 | +} |
| 70 | +``` |
| 71 | + |
| 72 | +### Fields |
| 73 | + |
| 74 | +| Field | Type | Description | |
| 75 | +|-------|------|-------------| |
| 76 | +| `version` | int | Schema version for forward compat (currently 1) | |
| 77 | +| `timestamp` | string | ISO 8601 save time | |
| 78 | +| `tabs` | []TabState | All open tabs | |
| 79 | +| `tabs[].projectPath` | string | Absolute path to .mpr file | |
| 80 | +| `tabs[].millerPath` | []string | Navigation breadcrumb path | |
| 81 | +| `tabs[].selectedNode` | string | Currently selected node name | |
| 82 | +| `tabs[].previewMode` | string | "MDL" or "NDSL" | |
| 83 | +| `activeTab` | int | Index of active tab | |
| 84 | +| `viewStack` | []ViewState | Stack of open views (browser at bottom) | |
| 85 | +| `viewStack[].type` | string | "browser", "overlay", "compare", etc. | |
| 86 | +| `viewStack[].title` | string | Overlay title (for overlay type) | |
| 87 | +| `viewStack[].filter` | string | Check filter (for check overlay) | |
| 88 | +| `checkNavActive` | bool | Whether check nav mode is active | |
| 89 | +| `checkNavIndex` | int | Current nav position | |
| 90 | + |
| 91 | +### Edge Cases |
| 92 | + |
| 93 | +| Scenario | Handling | |
| 94 | +|----------|----------| |
| 95 | +| Project file deleted | Skip tab, log warning, open remaining tabs | |
| 96 | +| Selected node deleted | Fall back to nearest existing parent in miller path | |
| 97 | +| Miller path partially invalid | Expand to last valid level, select first child | |
| 98 | +| Overlay data unavailable | Skip overlay restore, show browser only | |
| 99 | +| Check results stale | Re-run check on restore, don't restore old results | |
| 100 | +| All tabs invalid | Normal startup (no restore) | |
| 101 | +| Session file corrupt/missing | Ignore, normal startup | |
| 102 | +| Schema version mismatch | Ignore if version > current, attempt if <= current | |
| 103 | + |
| 104 | +### Implementation |
| 105 | + |
| 106 | +**New file:** `cmd/mxcli/tui/session.go` |
| 107 | +- `TUISession` struct matching JSON schema |
| 108 | +- `TabState`, `ViewState` sub-structs |
| 109 | +- `SaveSession(app *App) error` — serialize current state to file |
| 110 | +- `LoadSession() (*TUISession, error)` — read and parse session file |
| 111 | +- `sessionFilePath() string` — returns `~/.mxcli/tui-session.json` |
| 112 | + |
| 113 | +**Modified files:** |
| 114 | + |
| 115 | +`cmd/mxcli/tui/app.go`: |
| 116 | +- On quit (case "q"): call `SaveSession(a)` before returning `tea.Quit` |
| 117 | +- New `RestoreSession(session *TUISession)` method on App |
| 118 | + - Opens each tab's project, navigates miller path, selects node |
| 119 | + - Restores view stack (overlay, etc.) with fallbacks |
| 120 | +- Accept `*TUISession` in App constructor or Init |
| 121 | + |
| 122 | +`cmd/mxcli/cmd_tui.go`: |
| 123 | +- Add `-c` / `--continue` flag |
| 124 | +- When set, call `LoadSession()` and pass to App |
| 125 | + |
| 126 | +`cmd/mxcli/tui/browserview.go` / `cmd/mxcli/tui/miller.go`: |
| 127 | +- May need `NavigateToPath(path []string) bool` method |
| 128 | +- Reuse existing `navigateToNode` with path-walking |
| 129 | + |
| 130 | +### Task Order |
| 131 | + |
| 132 | +1. Part 1: Mouse fix (small, isolated) |
| 133 | +2. Part 2a: Session save/load infrastructure (session.go) |
| 134 | +3. Part 2b: App integration (save on quit, restore on start) |
| 135 | +4. Part 2c: CLI flag + edge case testing |
0 commit comments