fortrangoingonforty/sniffly / 2931df0

Browse files

fix empty tab UI state (label, breadcrumb, path clearing)

Authored by espadonne
SHA
2931df0a19ab663ac318012fd499519e0ab815ea
Parents
f0f1463
Tree
95de62a

6 changed files

StatusFile+-
M SNIFFLY-TABS.md 2 0
A STATE-AUDIT.md 130 0
A TABS-IMPLEMENTATION-PLAN.md 459 0
M src/gui/gtk_app.f90 10 0
M src/gui/tab_widget.f90 6 2
M src/gui/treemap_widget.f90 8 8
SNIFFLY-TABS.mdmodified
@@ -20,3 +20,5 @@ We'll have to be careful about scans in quicl succession or concurrent scans sin
20
 
20
 
21
 what other pitfalls do you see? Let's discuss and plan this feature'
21
 what other pitfalls do you see? Let's discuss and plan this feature'
22
 
22
 
23
+The tab bar should fit cleanly into the existing interface. we shouldn't ahve to condense or stretch a whole lot, we should just slot it underneath te toolbar on the right side, and again have it grow leftwards, we should cap at 5 tabs or whatever your performance analysis comes back with. My arbitrary 5 came from just wondering who would need that realistically. anyway. let's plan this!''
24
+
STATE-AUDIT.mdadded
@@ -0,0 +1,130 @@
1
+# Global State Audit for Tabs Feature
2
+
3
+## Overview
4
+This document identifies all global state that needs to be encapsulated into per-tab state.
5
+
6
+---
7
+
8
+## gtk_app.f90
9
+
10
+### Per-Tab State (MUST MOVE)
11
+| Variable | Line | Purpose |
12
+|----------|------|---------|
13
+| `global_scan_path` | 58 | Current directory being viewed |
14
+| `nav_history(MAX_HISTORY)` | 65 | Navigation history array |
15
+| `nav_history_count` | 66 | Number of entries in history |
16
+| `nav_history_pos` | 67 | Current position in history |
17
+| `navigating_history` | 68 | Flag for back/forward navigation |
18
+| `pending_synthetic_nav` | 82 | Synthetic navigation pending flag |
19
+| `pending_synthetic_child_name` | 83 | Child name for synthetic nav |
20
+| `synthetic_nav_attempts` | 84 | Attempt counter |
21
+
22
+### Global State (STAYS)
23
+| Variable | Line | Purpose | Notes |
24
+|----------|------|---------|-------|
25
+| `app_ptr` | 48 | GTK application | App-level |
26
+| `main_window_ptr` | 49 | Main window | App-level |
27
+| `status_label_ptr` | 50 | Status bar label | Shared, updated per active tab |
28
+| `progress_bar_ptr` | 51 | Progress bar widget | Shared, updated per active tab |
29
+| `path_entry_ptr` | 52 | Path display entry | Shared, updated per active tab |
30
+| `app_is_shutting_down` | 55 | Shutdown flag | Global state |
31
+| `pending_scan_path` | 61 | Initial scan path | Only for startup |
32
+| `back_btn_ptr` | 71 | Back button widget | Shared UI |
33
+| `forward_btn_ptr` | 72 | Forward button widget | Shared UI |
34
+| `cancel_scan_btn_ptr` | 73 | Cancel button widget | Shared UI |
35
+| `info_btn_ptr` | 76 | Info button widget | Shared UI |
36
+| `copy_path_btn_ptr` | 77 | Copy path button widget | Shared UI |
37
+| `open_finder_btn_ptr` | 78 | Open finder button widget | Shared UI |
38
+| `delete_btn_ptr` | 79 | Delete button widget | Shared UI |
39
+
40
+---
41
+
42
+## treemap_renderer.f90
43
+
44
+### Per-Tab State (MUST MOVE)
45
+| Variable | Line | Purpose |
46
+|----------|------|---------|
47
+| `current_view_node` | 57 | Pointer to current view in tree |
48
+| `has_data` | 58 | Whether scan data exists |
49
+| `scanned_path` | 59 | Path that was scanned |
50
+| `layout_calculated` | 67 | Layout calculation flag |
51
+| `last_width` | 68 | Last rendered width |
52
+| `last_height` | 68 | Last rendered height |
53
+| `show_file_extensions` | 71 | Toggle for file extensions |
54
+| `use_age_based_coloring` | 72 | Toggle for age-based colors |
55
+| `show_allocated_size` | 73 | Toggle allocated vs actual size |
56
+| `show_hidden_files` | 74 | Toggle dotfiles visibility |
57
+| `use_cushion_shading` | 75 | Toggle 3D vs flat rendering |
58
+| `path_names(MAX_PATH_DEPTH)` | 80 | Path components for navigation |
59
+| `path_depth` | 81 | Depth of current path |
60
+
61
+### Global State (STAYS)
62
+| Variable | Line | Purpose | Notes |
63
+|----------|------|---------|-------|
64
+| `dir_cache(MAX_CACHE_SIZE)` | 63 | Directory scan cache | Shared cache across tabs |
65
+| `cache_count` | 64 | Cache entry count | Shared |
66
+| `show_progress_cb` | 84 | Progress callback | Shared callbacks |
67
+| `hide_progress_cb` | 85 | Hide progress callback | Shared callbacks |
68
+| `update_progress_cb` | 86 | Update progress callback | Shared callbacks |
69
+| `scan_completion_cb` | 87 | Completion callback | Shared callbacks |
70
+| `widget_for_redraw` | 90 | Drawing widget pointer | Shared widget |
71
+
72
+---
73
+
74
+## progressive_scanner.f90
75
+
76
+### Per-Tab State (MUST MOVE)
77
+| Variable | Line | Purpose |
78
+|----------|------|---------|
79
+| `show_hidden_files` | 15 | Toggle hidden files in scan |
80
+| `scan_state` | 43 | Complete scan state (queue, progress, root node) |
81
+| `scan_idle_id` | 46 | GTK idle callback ID for cancellation |
82
+
83
+### Global State (STAYS)
84
+| Variable | Line | Purpose | Notes |
85
+|----------|------|---------|-------|
86
+| `update_callback` | 66 | Scan update callback | Shared callback |
87
+| `complete_callback` | 67 | Scan completion callback | Shared callback |
88
+| `initial_level_cb` | 68 | Initial level callback | Shared callback |
89
+
90
+---
91
+
92
+## Summary
93
+
94
+### Total Variables to Move: 29
95
+
96
+**gtk_app.f90**: 8 variables
97
+**treemap_renderer.f90**: 13 variables
98
+**progressive_scanner.f90**: 3 variables (including entire scan_state struct)
99
+
100
+### Critical Observations
101
+
102
+1. **Scan State is Complex**: The `scan_state` in progressive_scanner.f90 is a large struct containing:
103
+   - Queue for breadth-first scanning
104
+   - Progress tracking
105
+   - Root node pointer
106
+   - All scan context
107
+
108
+   This entire structure needs to be per-tab.
109
+
110
+2. **View Settings Are Per-Tab**: Each tab should remember its own:
111
+   - Show/hide extensions
112
+   - Flat vs cushioned rendering
113
+   - Hidden files visibility
114
+   - Color scheme
115
+
116
+3. **Navigation History is Per-Tab**: Each tab maintains its own back/forward history.
117
+
118
+4. **Cache Can Stay Global**: The directory scan cache can be shared across tabs for efficiency.
119
+
120
+5. **Widget Pointers Stay Global**: All GTK widget pointers stay global, but their content is updated when switching tabs.
121
+
122
+---
123
+
124
+## Next Steps
125
+
126
+1. Create `tab_state` type in `tab_manager.f90` containing all 29 variables
127
+2. Create `tabs(MAX_TABS)` array to hold tab states
128
+3. Add `active_tab_index` to track current tab
129
+4. Refactor all functions to operate on tab state instead of globals
130
+5. Create tab switching logic to swap active state
TABS-IMPLEMENTATION-PLAN.mdadded
@@ -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
src/gui/gtk_app.f90modified
@@ -937,6 +937,10 @@ contains
937
 
937
 
938
     print *, "  Active tab index: ", active_tab_index
938
     print *, "  Active tab index: ", active_tab_index
939
     print *, "  Tab has_data: ", tab%has_data
939
     print *, "  Tab has_data: ", tab%has_data
940
+    print *, "  Tab scan_path: ", trim(tab%scan_path)
941
+
942
+    ! Sync treemap widget's scan path with active tab's scan path
943
+    call set_scan_path(trim(tab%scan_path))
940
 
944
 
941
     ! Sync renderer state with active tab (CRITICAL for correct rendering)
945
     ! Sync renderer state with active tab (CRITICAL for correct rendering)
942
     call set_renderer_state_from_tab(tab%root_node, tab%current_view_node, tab%has_data)
946
     call set_renderer_state_from_tab(tab%root_node, tab%current_view_node, tab%has_data)
@@ -950,6 +954,12 @@ contains
950
         call gtk_widget_add_css_class(open_dir_btn_ptr, "suggested-action"//c_null_char)
954
         call gtk_widget_add_css_class(open_dir_btn_ptr, "suggested-action"//c_null_char)
951
       end if
955
       end if
952
 
956
 
957
+      ! Clear breadcrumb display
958
+      call update_breadcrumb_cache("")
959
+
960
+      ! Clear path entry
961
+      call update_path_entry("")
962
+
953
       ! Update status bar to guide user
963
       ! Update status bar to guide user
954
       call sniffly_update_status("No directory selected - click the folder icon to choose a directory")
964
       call sniffly_update_status("No directory selected - click the folder icon to choose a directory")
955
 
965
 
src/gui/tab_widget.f90modified
@@ -118,8 +118,12 @@ contains
118
       ! Create horizontal container for this tab (label + close button)
118
       ! Create horizontal container for this tab (label + close button)
119
       tab_container = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2_c_int)
119
       tab_container = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2_c_int)
120
 
120
 
121
-      ! Format label as ".../basename"
121
+      ! Format label - show just "Empty" for empty tabs, ".../basename" for normal tabs
122
-      label_text = ".../" // trim(tab%label)
122
+      if (trim(tab%label) == "Empty") then
123
+        label_text = "Empty"
124
+      else
125
+        label_text = ".../" // trim(tab%label)
126
+      end if
123
 
127
 
124
       ! Create tab label button
128
       ! Create tab label button
125
       tab_btn = gtk_button_new_with_label(trim(label_text)//c_null_char)
129
       tab_btn = gtk_button_new_with_label(trim(label_text)//c_null_char)
src/gui/treemap_widget.f90modified
@@ -429,16 +429,16 @@ contains
429
       return
429
       return
430
     end if
430
     end if
431
 
431
 
432
+    ! Skip rendering if no scan path set (empty tab - show blank canvas)
433
+    if (len_trim(scan_path) == 0) then
434
+      print *, "Skipping render - no scan path set (empty tab)"
435
+      return
436
+    end if
437
+
432
     ! Render the actual treemap with hover and selection
438
     ! Render the actual treemap with hover and selection
433
     ! mouse_x and mouse_y are updated by both mouse motion and arrow keys
439
     ! mouse_x and mouse_y are updated by both mouse motion and arrow keys
434
-    if (len_trim(scan_path) > 0) then
440
+    call scan_and_render_with_interaction(cr, width, height, mouse_x, mouse_y, &
435
-      call scan_and_render_with_interaction(cr, width, height, mouse_x, mouse_y, &
441
+                                           selected_index, trim(scan_path))
436
-                                             selected_index, trim(scan_path))
437
-    else
438
-      ! Fallback to default if no path set
439
-      call scan_and_render_with_interaction(cr, width, height, mouse_x, mouse_y, &
440
-                                             selected_index)
441
-    end if
442
 
442
 
443
     ! Update breadcrumbs after first render (when scan is complete)
443
     ! Update breadcrumbs after first render (when scan is complete)
444
     if (first_render) then
444
     if (first_render) then