@@ -0,0 +1,459 @@ |
| | 1 | +# Sniffly Tabs Implementation Plan |
| | 2 | + |
| | 3 | +## Overview |
| | 4 | +Add browser-style tabs to Sniffly for multi-directory viewing without window clutter. |
| | 5 | + |
| | 6 | +## Design Decisions |
| | 7 | + |
| | 8 | +### Architecture |
| | 9 | +- **Tab limit**: 5 tabs maximum (configurable constant) |
| | 10 | +- **Scan strategy**: Serialize scans (one active scan at a time, queue others) |
| | 11 | +- **Widget strategy**: Single drawing area, swap data on tab switch |
| | 12 | +- **Memory strategy**: Keep all tab data in memory (lazy approach for MVP) |
| | 13 | +- **Layout**: Tab bar on right side of toolbar, grows leftward |
| | 14 | + |
| | 15 | +### State Management |
| | 16 | +- Encapsulate ALL per-tab state in `tab_state` type |
| | 17 | +- No global state except `active_tab_index` and `tabs` array |
| | 18 | +- Each tab fully independent with own history, scan state, view |
| | 19 | + |
| | 20 | +--- |
| | 21 | + |
| | 22 | +## Phase 1: Core Infrastructure (Must Have) |
| | 23 | + |
| | 24 | +### 1.1 State Encapsulation |
| | 25 | +**Goal**: Move all global state to per-tab structures |
| | 26 | + |
| | 27 | +**Tasks**: |
| | 28 | +- [ ] Create `tab_state` type in new `src/gui/tab_manager.f90` |
| | 29 | + - [ ] Path (current scan directory) |
| | 30 | + - [ ] Navigation history array + position + count |
| | 31 | + - [ ] Root node pointer (scan tree) |
| | 32 | + - [ ] Current view node pointer |
| | 33 | + - [ ] Selected index |
| | 34 | + - [ ] Show hidden files flag |
| | 35 | + - [ ] Show extensions flag |
| | 36 | + - [ ] Render mode (flat/cushioned) |
| | 37 | + - [ ] Scan state (embedded `scan_state_type`) |
| | 38 | + - [ ] Tab label/title (last path segment) |
| | 39 | + |
| | 40 | +- [ ] Create `tab_manager` module with: |
| | 41 | + - [ ] `tabs(MAX_TABS)` array |
| | 42 | + - [ ] `active_tab_index` integer |
| | 43 | + - [ ] `num_tabs` counter |
| | 44 | + - [ ] Functions: |
| | 45 | + - [ ] `create_tab(path)` - Initialize new tab |
| | 46 | + - [ ] `close_tab(index)` - Cleanup and remove tab |
| | 47 | + - [ ] `switch_to_tab(index)` - Make tab active |
| | 48 | + - [ ] `get_active_tab()` - Return pointer to current tab |
| | 49 | + - [ ] `get_tab(index)` - Return pointer to specific tab |
| | 50 | + |
| | 51 | +- [ ] Audit `gtk_app.f90` for global state: |
| | 52 | + - [ ] `global_scan_path` → per-tab |
| | 53 | + - [ ] `nav_history`, `nav_history_pos`, `nav_history_count` → per-tab |
| | 54 | + - [ ] `navigating_history` flag → per-tab |
| | 55 | + |
| | 56 | +- [ ] Audit `treemap_renderer.f90` for global state: |
| | 57 | + - [ ] `current_view` → per-tab |
| | 58 | + - [ ] `root_node` → per-tab |
| | 59 | + - [ ] `selected_index` → per-tab |
| | 60 | + - [ ] `show_hidden_files` → per-tab |
| | 61 | + - [ ] `show_extensions` → per-tab |
| | 62 | + - [ ] `render_mode` → per-tab |
| | 63 | + |
| | 64 | +- [ ] Audit `progressive_scanner.f90`: |
| | 65 | + - [ ] `scan_state` → per-tab (one scan context per tab) |
| | 66 | + - [ ] Modify scan functions to accept `tab_index` parameter |
| | 67 | + - [ ] Track which tab owns active scan |
| | 68 | + |
| | 69 | +### 1.2 Scan Serialization |
| | 70 | +**Goal**: Prevent concurrent scans and I/O contention |
| | 71 | + |
| | 72 | +**Tasks**: |
| | 73 | +- [ ] Add scan queue to `tab_manager.f90`: |
| | 74 | + - [ ] `scan_queue(MAX_TABS)` - tab indices waiting to scan |
| | 75 | + - [ ] `scan_queue_head`, `scan_queue_tail`, `scan_queue_size` |
| | 76 | + - [ ] `active_scan_tab_index` - which tab is scanning (0 = none) |
| | 77 | + |
| | 78 | +- [ ] Create scan queueing functions: |
| | 79 | + - [ ] `request_scan(tab_index, path)` - Add to queue or start immediately |
| | 80 | + - [ ] `process_scan_queue()` - Start next scan when current finishes |
| | 81 | + - [ ] `cancel_scan_for_tab(tab_index)` - Remove from queue or stop if active |
| | 82 | + |
| | 83 | +- [ ] Modify `progressive_scanner.f90`: |
| | 84 | + - [ ] Add `active_tab_for_scan` parameter |
| | 85 | + - [ ] Scan completion callback triggers `process_scan_queue()` |
| | 86 | + - [ ] Only update UI if scanning tab is active |
| | 87 | + |
| | 88 | +- [ ] Update scan buttons: |
| | 89 | + - [ ] Show "Waiting..." status if tab queued but not scanning |
| | 90 | + - [ ] Cancel button removes from queue or stops active scan |
| | 91 | + |
| | 92 | +### 1.3 Tab UI Components |
| | 93 | +**Goal**: Create visual tab bar and interaction |
| | 94 | + |
| | 95 | +**Tasks**: |
| | 96 | +- [ ] Create `src/gui/tab_widget.f90`: |
| | 97 | + - [ ] `create_tab_bar()` - Returns GTK box containing tabs |
| | 98 | + - [ ] `create_tab_button(tab_index, label)` - Individual tab with close X |
| | 99 | + - [ ] `create_add_tab_button()` - Plus button for new tabs |
| | 100 | + - [ ] `update_tab_highlights()` - Yellow border on active tab |
| | 101 | + - [ ] `refresh_tab_bar()` - Rebuild entire tab bar (on add/close) |
| | 102 | + |
| | 103 | +- [ ] Tab button design: |
| | 104 | + - [ ] Width: 120px (enough for label + close button) |
| | 105 | + - [ ] Height: 28px (compact) |
| | 106 | + - [ ] Active: 2px yellow border (#FFD700) |
| | 107 | + - [ ] Label: Last segment of path (truncate if needed) |
| | 108 | + - [ ] Close button: Small X on right (8x8px) |
| | 109 | + |
| | 110 | +- [ ] Layout integration in `gtk_app.f90`: |
| | 111 | + - [ ] Add horizontal box below toolbar |
| | 112 | + - [ ] Tab bar aligned to right side |
| | 113 | + - [ ] Grows leftward as tabs added |
| | 114 | + - [ ] Plus button always on left edge of tab bar |
| | 115 | + |
| | 116 | +- [ ] Tab interactions: |
| | 117 | + - [ ] Click tab → `switch_to_tab(index)` |
| | 118 | + - [ ] Click close X → `close_tab(index)` (with confirmation if scanning) |
| | 119 | + - [ ] Click plus → `create_tab(current_path_parent)` or show directory picker |
| | 120 | + |
| | 121 | +### 1.4 Tab Switching Logic |
| | 122 | +**Goal**: Seamlessly switch between tab contexts |
| | 123 | + |
| | 124 | +**Tasks**: |
| | 125 | +- [ ] Implement `switch_to_tab(index)` in `tab_manager.f90`: |
| | 126 | + - [ ] Save current tab state (selected index, scroll position if any) |
| | 127 | + - [ ] Set `active_tab_index = index` |
| | 128 | + - [ ] Load new tab state into rendering context |
| | 129 | + - [ ] Update breadcrumb to new tab's path |
| | 130 | + - [ ] Update status bar with new tab's stats |
| | 131 | + - [ ] Update treemap widget data pointers |
| | 132 | + - [ ] Trigger full redraw |
| | 133 | + |
| | 134 | +- [ ] Update all navigation functions: |
| | 135 | + - [ ] Get active tab first: `tab = get_active_tab()` |
| | 136 | + - [ ] Operate on tab's state, not globals |
| | 137 | + - [ ] Update tab's history, not global history |
| | 138 | + |
| | 139 | +- [ ] Update all button handlers: |
| | 140 | + - [ ] Back/Forward use active tab's history |
| | 141 | + - [ ] Up navigates active tab's tree |
| | 142 | + - [ ] Rescan targets active tab |
| | 143 | + |
| | 144 | +### 1.5 Resource Cleanup |
| | 145 | +**Goal**: Prevent memory leaks on tab close |
| | 146 | + |
| | 147 | +**Tasks**: |
| | 148 | +- [ ] Implement `close_tab(index)` in `tab_manager.f90`: |
| | 149 | + - [ ] Check if last tab → prevent close (must have at least 1) |
| | 150 | + - [ ] Check if scan active → confirm with user dialog |
| | 151 | + - [ ] Cancel scan if active: `cancel_scan_for_tab(index)` |
| | 152 | + - [ ] Deallocate file tree: `recursive_deallocate_node(tab.root_node)` |
| | 153 | + - [ ] Clear history array |
| | 154 | + - [ ] Shift remaining tabs down (compact array) |
| | 155 | + - [ ] Decrement `num_tabs` |
| | 156 | + - [ ] If closed active tab → activate tab to the right (or left if last) |
| | 157 | + - [ ] Refresh tab bar UI |
| | 158 | + |
| | 159 | +- [ ] Create recursive deallocation: |
| | 160 | + - [ ] `recursive_deallocate_node(node)` in types or tab_manager |
| | 161 | + - [ ] Free children array recursively |
| | 162 | + - [ ] Deallocate path strings |
| | 163 | + - [ ] Nullify pointers |
| | 164 | + |
| | 165 | +- [ ] Add confirmation dialog: |
| | 166 | + - [ ] `show_close_tab_confirmation(tab_index)` if scan active |
| | 167 | + - [ ] "Tab is scanning. Close anyway?" with Cancel/Close buttons |
| | 168 | + |
| | 169 | +### 1.6 Keyboard Shortcuts |
| | 170 | +**Goal**: Cmd-1 through Cmd-5 switch tabs, Cmd-T new tab |
| | 171 | + |
| | 172 | +**Tasks**: |
| | 173 | +- [ ] Add to key handler in `treemap_widget.f90` or `gtk_app.f90`: |
| | 174 | + - [ ] Cmd-1 → `switch_to_tab(1)` |
| | 175 | + - [ ] Cmd-2 → `switch_to_tab(2)` |
| | 176 | + - [ ] ... |
| | 177 | + - [ ] Cmd-5 → `switch_to_tab(5)` |
| | 178 | + - [ ] Cmd-T → `create_tab()` with directory picker |
| | 179 | + - [ ] Cmd-W → `close_tab(active_tab_index)` with confirmation |
| | 180 | + |
| | 181 | +- [ ] Handle out of bounds: |
| | 182 | + - [ ] If `num_tabs < requested_index` → ignore or beep |
| | 183 | + - [ ] Show feedback if tab doesn't exist |
| | 184 | + |
| | 185 | +--- |
| | 186 | + |
| | 187 | +## Phase 2: Should Have Features |
| | 188 | + |
| | 189 | +### 2.1 Tab Close Safety |
| | 190 | +**Goal**: Prevent accidental data loss |
| | 191 | + |
| | 192 | +**Tasks**: |
| | 193 | +- [ ] Prevent closing last tab: |
| | 194 | + - [ ] Disable close button if `num_tabs == 1` |
| | 195 | + - [ ] Grey out close X |
| | 196 | + - [ ] Cmd-W does nothing if last tab |
| | 197 | + |
| | 198 | +- [ ] Confirm close if scan active: |
| | 199 | + - [ ] Native dialog: "Scanning in progress. Close tab anyway?" |
| | 200 | + - [ ] Only show if scan > 10% complete (skip if just started) |
| | 201 | + |
| | 202 | +- [ ] Confirm close if large tree: |
| | 203 | + - [ ] If tree has > 10,000 nodes → confirm |
| | 204 | + - [ ] "This tab contains a large scan. Close anyway?" |
| | 205 | + |
| | 206 | +### 2.2 Tab Tooltips |
| | 207 | +**Goal**: Show full path on hover |
| | 208 | + |
| | 209 | +**Tasks**: |
| | 210 | +- [ ] Add tooltip to each tab button: |
| | 211 | + - [ ] Full path of tab's current directory |
| | 212 | + - [ ] Scan status: "Scanning (45%)", "Ready", "Queued" |
| | 213 | + - [ ] Item count: "1,234 items" |
| | 214 | + |
| | 215 | +- [ ] Use GTK tooltip API: |
| | 216 | + - [ ] `gtk_widget_set_tooltip_text(tab_button, full_info)` |
| | 217 | + - [ ] Update tooltip on scan progress, navigation |
| | 218 | + |
| | 219 | +### 2.3 Graceful Memory Degradation |
| | 220 | +**Goal**: Handle low memory conditions |
| | 221 | + |
| | 222 | +**Tasks**: |
| | 223 | +- [ ] Warn on tab creation if memory high: |
| | 224 | + - [ ] Check system memory (platform-specific) |
| | 225 | + - [ ] If > 80% used → warn "System memory low. New tab may be slow." |
| | 226 | + |
| | 227 | +- [ ] Limit tab count dynamically: |
| | 228 | + - [ ] If memory pressure detected → disable plus button |
| | 229 | + - [ ] Show message: "Maximum tabs reached (memory limit)" |
| | 230 | + |
| | 231 | +### 2.4 Tab Reordering |
| | 232 | +**Goal**: Let users organize tabs |
| | 233 | + |
| | 234 | +**Tasks**: |
| | 235 | +- [ ] Add drag-and-drop to tab buttons: |
| | 236 | + - [ ] GTK drag source on each tab |
| | 237 | + - [ ] GTK drop target on tab bar |
| | 238 | + - [ ] On drop: reorder `tabs` array |
| | 239 | + - [ ] Update `active_tab_index` if it changed |
| | 240 | + - [ ] Refresh tab bar |
| | 241 | + |
| | 242 | +- [ ] Alternative: Shift-Left/Right arrow to move active tab: |
| | 243 | + - [ ] Cmd-Shift-[ → move tab left |
| | 244 | + - [ ] Cmd-Shift-] → move tab right |
| | 245 | + |
| | 246 | +--- |
| | 247 | + |
| | 248 | +## Phase 3: Nice to Have Features |
| | 249 | + |
| | 250 | +### 3.1 Tab Color Coding |
| | 251 | +**Goal**: Visual distinction between tabs |
| | 252 | + |
| | 253 | +**Tasks**: |
| | 254 | +- [ ] Assign color to each tab: |
| | 255 | + - [ ] Predefined palette: Blue, Green, Orange, Purple, Teal |
| | 256 | + - [ ] Cycle through colors as tabs created |
| | 257 | + - [ ] 3px colored bar on top of tab button |
| | 258 | + |
| | 259 | +- [ ] Color persistence: |
| | 260 | + - [ ] Store color in `tab_state` |
| | 261 | + - [ ] Let user click to cycle colors (shift-click tab?) |
| | 262 | + |
| | 263 | +### 3.2 Recently Closed Tabs |
| | 264 | +**Goal**: Undo accidental tab close |
| | 265 | + |
| | 266 | +**Tasks**: |
| | 267 | +- [ ] Track last 5 closed tabs: |
| | 268 | + - [ ] `recently_closed_tabs` array with path + timestamp |
| | 269 | + - [ ] Don't store full tree (too much memory) |
| | 270 | + |
| | 271 | +- [ ] Restore closed tab: |
| | 272 | + - [ ] Cmd-Shift-T → reopen last closed tab |
| | 273 | + - [ ] Rescan directory (don't restore old tree) |
| | 274 | + - [ ] Right-click on plus button → "Recently Closed" menu |
| | 275 | + |
| | 276 | +### 3.3 Tab Persistence Across Sessions |
| | 277 | +**Goal**: Remember open tabs on quit/relaunch |
| | 278 | + |
| | 279 | +**Tasks**: |
| | 280 | +- [ ] Save tab state on quit: |
| | 281 | + - [ ] Write `~/.config/sniffly/tabs.conf` |
| | 282 | + - [ ] Format: one path per line |
| | 283 | + - [ ] Include active tab marker |
| | 284 | + |
| | 285 | +- [ ] Restore tabs on launch: |
| | 286 | + - [ ] Read config file |
| | 287 | + - [ ] Create tab for each path |
| | 288 | + - [ ] Don't scan immediately (lazy) |
| | 289 | + - [ ] Scan active tab only, others on-demand |
| | 290 | + |
| | 291 | +### 3.4 Duplicate Tab |
| | 292 | +**Goal**: Explore same directory in parallel |
| | 293 | + |
| | 294 | +**Tasks**: |
| | 295 | +- [ ] Add "Duplicate Tab" action: |
| | 296 | + - [ ] Right-click tab → "Duplicate" |
| | 297 | + - [ ] Or Cmd-D keyboard shortcut |
| | 298 | + - [ ] Create new tab with same path |
| | 299 | + - [ ] Copy history if desired |
| | 300 | + |
| | 301 | +### 3.5 Tab Renaming |
| | 302 | +**Goal**: Custom labels for tabs |
| | 303 | + |
| | 304 | +**Tasks**: |
| | 305 | +- [ ] Double-click tab label to edit: |
| | 306 | + - [ ] Show inline text entry |
| | 307 | + - [ ] Save custom name in `tab_state.custom_label` |
| | 308 | + - [ ] Use custom label instead of path segment |
| | 309 | + |
| | 310 | +- [ ] Clear custom name: |
| | 311 | + - [ ] Right-click → "Reset Label" |
| | 312 | + - [ ] Reverts to automatic path-based label |
| | 313 | + |
| | 314 | +### 3.6 Split View Within Tab |
| | 315 | +**Goal**: Advanced layout (future consideration) |
| | 316 | + |
| | 317 | +**Tasks**: |
| | 318 | +- [ ] Allow horizontal split within tab: |
| | 319 | + - [ ] Two treemaps side-by-side |
| | 320 | + - [ ] Shared tab context, independent views |
| | 321 | + - [ ] This is VERY advanced - only if highly requested |
| | 322 | + |
| | 323 | +--- |
| | 324 | + |
| | 325 | +## Implementation Order |
| | 326 | + |
| | 327 | +### Sprint 1: Foundation (1-2 weeks) |
| | 328 | +1. State encapsulation (1.1) - CRITICAL |
| | 329 | +2. Scan serialization (1.2) - CRITICAL |
| | 330 | +3. Basic tab manager module |
| | 331 | + |
| | 332 | +### Sprint 2: UI & Switching (1 week) |
| | 333 | +4. Tab UI components (1.3) |
| | 334 | +5. Tab switching logic (1.4) |
| | 335 | +6. Keyboard shortcuts (1.6) |
| | 336 | + |
| | 337 | +### Sprint 3: Safety & Polish (1 week) |
| | 338 | +7. Resource cleanup (1.5) |
| | 339 | +8. Tab close safety (2.1) |
| | 340 | +9. Tab tooltips (2.2) |
| | 341 | + |
| | 342 | +### Sprint 4: Enhancements (1 week) |
| | 343 | +10. Tab reordering (2.4) |
| | 344 | +11. Memory degradation handling (2.3) |
| | 345 | +12. Testing and bug fixes |
| | 346 | + |
| | 347 | +### Sprint 5: Nice-to-Haves (Optional) |
| | 348 | +13. Color coding (3.1) |
| | 349 | +14. Recently closed (3.2) |
| | 350 | +15. Tab persistence (3.3) |
| | 351 | +16. Other nice-to-haves as desired |
| | 352 | + |
| | 353 | +--- |
| | 354 | + |
| | 355 | +## Testing Strategy |
| | 356 | + |
| | 357 | +### Unit Tests |
| | 358 | +- [ ] Tab creation and destruction |
| | 359 | +- [ ] Scan queue operations |
| | 360 | +- [ ] History isolation between tabs |
| | 361 | +- [ ] Memory deallocation (valgrind) |
| | 362 | + |
| | 363 | +### Integration Tests |
| | 364 | +- [ ] Switch tabs during scan |
| | 365 | +- [ ] Close tab during scan |
| | 366 | +- [ ] Multiple tabs with different settings |
| | 367 | +- [ ] Keyboard shortcuts work correctly |
| | 368 | + |
| | 369 | +### Stress Tests |
| | 370 | +- [ ] 5 tabs with large directories (1M+ files each) |
| | 371 | +- [ ] Rapid tab switching |
| | 372 | +- [ ] Create/close tabs rapidly |
| | 373 | +- [ ] Memory leak detection over time |
| | 374 | + |
| | 375 | +### Edge Cases |
| | 376 | +- [ ] Close last tab (should prevent) |
| | 377 | +- [ ] Switch to nonexistent tab index |
| | 378 | +- [ ] Scan queue with 5 queued scans |
| | 379 | +- [ ] Tab close during scan completion callback |
| | 380 | + |
| | 381 | +--- |
| | 382 | + |
| | 383 | +## Risks & Mitigations |
| | 384 | + |
| | 385 | +| Risk | Severity | Mitigation | |
| | 386 | +|------|----------|------------| |
| | 387 | +| Missed global state → cross-tab bugs | HIGH | Systematic audit, grep for `save ::` | |
| | 388 | +| Memory leaks on tab close | HIGH | Valgrind testing, careful deallocation | |
| | 389 | +| UI sluggishness with 5 active scans | MEDIUM | Serialize scans, only 1 active | |
| | 390 | +| Complex state management bugs | MEDIUM | Defensive programming, assertions | |
| | 391 | +| Keyboard shortcuts conflict | LOW | Document shortcuts, test thoroughly | |
| | 392 | + |
| | 393 | +--- |
| | 394 | + |
| | 395 | +## Success Criteria |
| | 396 | + |
| | 397 | +**MVP (Phase 1)**: |
| | 398 | +- [ ] Can create up to 5 tabs |
| | 399 | +- [ ] Each tab has independent scan tree |
| | 400 | +- [ ] Switching tabs updates UI correctly |
| | 401 | +- [ ] Closing tabs doesn't leak memory |
| | 402 | +- [ ] Scans are serialized (no concurrent scans) |
| | 403 | +- [ ] Keyboard shortcuts work (Cmd-1 through Cmd-5) |
| | 404 | + |
| | 405 | +**Full Feature (Phase 2)**: |
| | 406 | +- [ ] Tab tooltips show full path and status |
| | 407 | +- [ ] Can't close last tab |
| | 408 | +- [ ] Confirm dialog on close if scanning |
| | 409 | +- [ ] Tab reordering works |
| | 410 | + |
| | 411 | +**Polished (Phase 3)**: |
| | 412 | +- [ ] Color coded tabs |
| | 413 | +- [ ] Recently closed tabs |
| | 414 | +- [ ] Tab state persists across sessions |
| | 415 | + |
| | 416 | +--- |
| | 417 | + |
| | 418 | +## Open Questions |
| | 419 | + |
| | 420 | +1. **Tab limit**: Stick with 5 or make configurable? |
| | 421 | + - Recommendation: Hardcode 5 for MVP, make constant easy to change |
| | 422 | + |
| | 423 | +2. **Scan queue size**: What if user queues 5 scans rapidly? |
| | 424 | + - Recommendation: Queue size = MAX_TABS, show queue position in tooltip |
| | 425 | + |
| | 426 | +3. **Tab close during other tab's scan**: Allow or prevent? |
| | 427 | + - Recommendation: Allow (only affects closed tab, not active scan) |
| | 428 | + |
| | 429 | +4. **Initial tab count**: Start with 1 tab or allow opening with multiple paths? |
| | 430 | + - Recommendation: Always start with 1 tab, user creates more |
| | 431 | + |
| | 432 | +5. **Tab bar overflow**: What if tabs don't fit horizontally? |
| | 433 | + - Recommendation: With 5 max tabs @ 120px = 600px, should fit on most screens |
| | 434 | + - If needed: Scrollable tab bar or shrink tab width |
| | 435 | + |
| | 436 | +--- |
| | 437 | + |
| | 438 | +## File Structure |
| | 439 | + |
| | 440 | +New files to create: |
| | 441 | +- `src/gui/tab_manager.f90` - Core tab state and management |
| | 442 | +- `src/gui/tab_widget.f90` - GTK tab bar UI components |
| | 443 | + |
| | 444 | +Modified files: |
| | 445 | +- `src/gui/gtk_app.f90` - Remove globals, use active tab |
| | 446 | +- `src/gui/treemap_renderer.f90` - Accept tab context parameter |
| | 447 | +- `src/gui/treemap_widget.f90` - Key handler for tab shortcuts |
| | 448 | +- `src/core/progressive_scanner.f90` - Per-tab scan state |
| | 449 | +- `meson.build` - Add new source files |
| | 450 | + |
| | 451 | +--- |
| | 452 | + |
| | 453 | +## Next Steps |
| | 454 | + |
| | 455 | +1. Review this plan with user |
| | 456 | +2. Create feature branch: `git checkout -b tabs-feature` |
| | 457 | +3. Start with Phase 1.1 (State Encapsulation) |
| | 458 | +4. Commit frequently with descriptive messages |
| | 459 | +5. Test thoroughly after each sprint |