markdown · 14656 bytes Raw Blame History

Sniffly Tabs Implementation Plan

Overview

Add browser-style tabs to Sniffly for multi-directory viewing without window clutter.

Design Decisions

Architecture

  • Tab limit: 5 tabs maximum (configurable constant)
  • Scan strategy: Serialize scans (one active scan at a time, queue others)
  • Widget strategy: Single drawing area, swap data on tab switch
  • Memory strategy: Keep all tab data in memory (lazy approach for MVP)
  • Layout: Tab bar on right side of toolbar, grows leftward

State Management

  • Encapsulate ALL per-tab state in tab_state type
  • No global state except active_tab_index and tabs array
  • Each tab fully independent with own history, scan state, view

Phase 1: Core Infrastructure (Must Have)

1.1 State Encapsulation

Goal: Move all global state to per-tab structures

Tasks:

  • Create tab_state type in new src/gui/tab_manager.f90

    • Path (current scan directory)
    • Navigation history array + position + count
    • Root node pointer (scan tree)
    • Current view node pointer
    • Selected index
    • Show hidden files flag
    • Show extensions flag
    • Render mode (flat/cushioned)
    • Scan state (embedded scan_state_type)
    • Tab label/title (last path segment)
  • Create tab_manager module with:

    • tabs(MAX_TABS) array
    • active_tab_index integer
    • num_tabs counter
    • Functions:
      • create_tab(path) - Initialize new tab
      • close_tab(index) - Cleanup and remove tab
      • switch_to_tab(index) - Make tab active
      • get_active_tab() - Return pointer to current tab
      • get_tab(index) - Return pointer to specific tab
  • Audit gtk_app.f90 for global state:

    • global_scan_path → per-tab
    • nav_history, nav_history_pos, nav_history_count → per-tab
    • navigating_history flag → per-tab
  • Audit treemap_renderer.f90 for global state:

    • current_view → per-tab
    • root_node → per-tab
    • selected_index → per-tab
    • show_hidden_files → per-tab
    • show_extensions → per-tab
    • render_mode → per-tab
  • Audit progressive_scanner.f90:

    • scan_state → per-tab (one scan context per tab)
    • Modify scan functions to accept tab_index parameter
    • Track which tab owns active scan

1.2 Scan Serialization

Goal: Prevent concurrent scans and I/O contention

Tasks:

  • Add scan queue to tab_manager.f90:

    • scan_queue(MAX_TABS) - tab indices waiting to scan
    • scan_queue_head, scan_queue_tail, scan_queue_size
    • active_scan_tab_index - which tab is scanning (0 = none)
  • Create scan queueing functions:

    • request_scan(tab_index, path) - Add to queue or start immediately
    • process_scan_queue() - Start next scan when current finishes
    • cancel_scan_for_tab(tab_index) - Remove from queue or stop if active
  • Modify progressive_scanner.f90:

    • Add active_tab_for_scan parameter
    • Scan completion callback triggers process_scan_queue()
    • Only update UI if scanning tab is active
  • Update scan buttons:

    • Show "Waiting..." status if tab queued but not scanning
    • Cancel button removes from queue or stops active scan

1.3 Tab UI Components

Goal: Create visual tab bar and interaction

Tasks:

  • Create src/gui/tab_widget.f90:

    • create_tab_bar() - Returns GTK box containing tabs
    • create_tab_button(tab_index, label) - Individual tab with close X
    • create_add_tab_button() - Plus button for new tabs
    • update_tab_highlights() - Yellow border on active tab
    • refresh_tab_bar() - Rebuild entire tab bar (on add/close)
  • Tab button design:

    • Width: 120px (enough for label + close button)
    • Height: 28px (compact)
    • Active: 2px yellow border (#FFD700)
    • Label: Last segment of path (truncate if needed)
    • Close button: Small X on right (8x8px)
  • Layout integration in gtk_app.f90:

    • Add horizontal box below toolbar
    • Tab bar aligned to right side
    • Grows leftward as tabs added
    • Plus button always on left edge of tab bar
  • Tab interactions:

    • Click tab → switch_to_tab(index)
    • Click close X → close_tab(index) (with confirmation if scanning)
    • Click plus → create_tab(current_path_parent) or show directory picker

1.4 Tab Switching Logic

Goal: Seamlessly switch between tab contexts

Tasks:

  • Implement switch_to_tab(index) in tab_manager.f90:

    • Save current tab state (selected index, scroll position if any)
    • Set active_tab_index = index
    • Load new tab state into rendering context
    • Update breadcrumb to new tab's path
    • Update status bar with new tab's stats
    • Update treemap widget data pointers
    • Trigger full redraw
  • Update all navigation functions:

    • Get active tab first: tab = get_active_tab()
    • Operate on tab's state, not globals
    • Update tab's history, not global history
  • Update all button handlers:

    • Back/Forward use active tab's history
    • Up navigates active tab's tree
    • Rescan targets active tab

1.5 Resource Cleanup

Goal: Prevent memory leaks on tab close

Tasks:

  • Implement close_tab(index) in tab_manager.f90:

    • Check if last tab → prevent close (must have at least 1)
    • Check if scan active → confirm with user dialog
    • Cancel scan if active: cancel_scan_for_tab(index)
    • Deallocate file tree: recursive_deallocate_node(tab.root_node)
    • Clear history array
    • Shift remaining tabs down (compact array)
    • Decrement num_tabs
    • If closed active tab → activate tab to the right (or left if last)
    • Refresh tab bar UI
  • Create recursive deallocation:

    • recursive_deallocate_node(node) in types or tab_manager
    • Free children array recursively
    • Deallocate path strings
    • Nullify pointers
  • Add confirmation dialog:

    • show_close_tab_confirmation(tab_index) if scan active
    • "Tab is scanning. Close anyway?" with Cancel/Close buttons

1.6 Keyboard Shortcuts

Goal: Cmd-1 through Cmd-5 switch tabs, Cmd-T new tab

Tasks:

  • Add to key handler in treemap_widget.f90 or gtk_app.f90:

    • Cmd-1 → switch_to_tab(1)
    • Cmd-2 → switch_to_tab(2)
    • ...
    • Cmd-5 → switch_to_tab(5)
    • Cmd-T → create_tab() with directory picker
    • Cmd-W → close_tab(active_tab_index) with confirmation
  • Handle out of bounds:

    • If num_tabs < requested_index → ignore or beep
    • Show feedback if tab doesn't exist

Phase 2: Should Have Features

2.1 Tab Close Safety

Goal: Prevent accidental data loss

Tasks:

  • Prevent closing last tab:

    • Disable close button if num_tabs == 1
    • Grey out close X
    • Cmd-W does nothing if last tab
  • Confirm close if scan active:

    • Native dialog: "Scanning in progress. Close tab anyway?"
    • Only show if scan > 10% complete (skip if just started)
  • Confirm close if large tree:

    • If tree has > 10,000 nodes → confirm
    • "This tab contains a large scan. Close anyway?"

2.2 Tab Tooltips

Goal: Show full path on hover

Tasks:

  • Add tooltip to each tab button:

    • Full path of tab's current directory
    • Scan status: "Scanning (45%)", "Ready", "Queued"
    • Item count: "1,234 items"
  • Use GTK tooltip API:

    • gtk_widget_set_tooltip_text(tab_button, full_info)
    • Update tooltip on scan progress, navigation

2.3 Graceful Memory Degradation

Goal: Handle low memory conditions

Tasks:

  • Warn on tab creation if memory high:

    • Check system memory (platform-specific)
    • If > 80% used → warn "System memory low. New tab may be slow."
  • Limit tab count dynamically:

    • If memory pressure detected → disable plus button
    • Show message: "Maximum tabs reached (memory limit)"

2.4 Tab Reordering

Goal: Let users organize tabs

Tasks:

  • Add drag-and-drop to tab buttons:

    • GTK drag source on each tab
    • GTK drop target on tab bar
    • On drop: reorder tabs array
    • Update active_tab_index if it changed
    • Refresh tab bar
  • Alternative: Shift-Left/Right arrow to move active tab:

    • Cmd-Shift-[ → move tab left
    • Cmd-Shift-] → move tab right

Phase 3: Nice to Have Features

3.1 Tab Color Coding

Goal: Visual distinction between tabs

Tasks:

  • Assign color to each tab:

    • Predefined palette: Blue, Green, Orange, Purple, Teal
    • Cycle through colors as tabs created
    • 3px colored bar on top of tab button
  • Color persistence:

    • Store color in tab_state
    • Let user click to cycle colors (shift-click tab?)

3.2 Recently Closed Tabs

Goal: Undo accidental tab close

Tasks:

  • Track last 5 closed tabs:

    • recently_closed_tabs array with path + timestamp
    • Don't store full tree (too much memory)
  • Restore closed tab:

    • Cmd-Shift-T → reopen last closed tab
    • Rescan directory (don't restore old tree)
    • Right-click on plus button → "Recently Closed" menu

3.3 Tab Persistence Across Sessions

Goal: Remember open tabs on quit/relaunch

Tasks:

  • Save tab state on quit:

    • Write ~/.config/sniffly/tabs.conf
    • Format: one path per line
    • Include active tab marker
  • Restore tabs on launch:

    • Read config file
    • Create tab for each path
    • Don't scan immediately (lazy)
    • Scan active tab only, others on-demand

3.4 Duplicate Tab

Goal: Explore same directory in parallel

Tasks:

  • Add "Duplicate Tab" action:
    • Right-click tab → "Duplicate"
    • Or Cmd-D keyboard shortcut
    • Create new tab with same path
    • Copy history if desired

3.5 Tab Renaming

Goal: Custom labels for tabs

Tasks:

  • Double-click tab label to edit:

    • Show inline text entry
    • Save custom name in tab_state.custom_label
    • Use custom label instead of path segment
  • Clear custom name:

    • Right-click → "Reset Label"
    • Reverts to automatic path-based label

3.6 Split View Within Tab

Goal: Advanced layout (future consideration)

Tasks:

  • Allow horizontal split within tab:
    • Two treemaps side-by-side
    • Shared tab context, independent views
    • This is VERY advanced - only if highly requested

Implementation Order

Sprint 1: Foundation (1-2 weeks)

  1. State encapsulation (1.1) - CRITICAL
  2. Scan serialization (1.2) - CRITICAL
  3. Basic tab manager module

Sprint 2: UI & Switching (1 week)

  1. Tab UI components (1.3)
  2. Tab switching logic (1.4)
  3. Keyboard shortcuts (1.6)

Sprint 3: Safety & Polish (1 week)

  1. Resource cleanup (1.5)
  2. Tab close safety (2.1)
  3. Tab tooltips (2.2)

Sprint 4: Enhancements (1 week)

  1. Tab reordering (2.4)
  2. Memory degradation handling (2.3)
  3. Testing and bug fixes

Sprint 5: Nice-to-Haves (Optional)

  1. Color coding (3.1)
  2. Recently closed (3.2)
  3. Tab persistence (3.3)
  4. Other nice-to-haves as desired

Testing Strategy

Unit Tests

  • Tab creation and destruction
  • Scan queue operations
  • History isolation between tabs
  • Memory deallocation (valgrind)

Integration Tests

  • Switch tabs during scan
  • Close tab during scan
  • Multiple tabs with different settings
  • Keyboard shortcuts work correctly

Stress Tests

  • 5 tabs with large directories (1M+ files each)
  • Rapid tab switching
  • Create/close tabs rapidly
  • Memory leak detection over time

Edge Cases

  • Close last tab (should prevent)
  • Switch to nonexistent tab index
  • Scan queue with 5 queued scans
  • Tab close during scan completion callback

Risks & Mitigations

Risk Severity Mitigation
Missed global state → cross-tab bugs HIGH Systematic audit, grep for save ::
Memory leaks on tab close HIGH Valgrind testing, careful deallocation
UI sluggishness with 5 active scans MEDIUM Serialize scans, only 1 active
Complex state management bugs MEDIUM Defensive programming, assertions
Keyboard shortcuts conflict LOW Document shortcuts, test thoroughly

Success Criteria

MVP (Phase 1):

  • Can create up to 5 tabs
  • Each tab has independent scan tree
  • Switching tabs updates UI correctly
  • Closing tabs doesn't leak memory
  • Scans are serialized (no concurrent scans)
  • Keyboard shortcuts work (Cmd-1 through Cmd-5)

Full Feature (Phase 2):

  • Tab tooltips show full path and status
  • Can't close last tab
  • Confirm dialog on close if scanning
  • Tab reordering works

Polished (Phase 3):

  • Color coded tabs
  • Recently closed tabs
  • Tab state persists across sessions

Open Questions

  1. Tab limit: Stick with 5 or make configurable?

    • Recommendation: Hardcode 5 for MVP, make constant easy to change
  2. Scan queue size: What if user queues 5 scans rapidly?

    • Recommendation: Queue size = MAX_TABS, show queue position in tooltip
  3. Tab close during other tab's scan: Allow or prevent?

    • Recommendation: Allow (only affects closed tab, not active scan)
  4. Initial tab count: Start with 1 tab or allow opening with multiple paths?

    • Recommendation: Always start with 1 tab, user creates more
  5. Tab bar overflow: What if tabs don't fit horizontally?

    • Recommendation: With 5 max tabs @ 120px = 600px, should fit on most screens
    • If needed: Scrollable tab bar or shrink tab width

File Structure

New files to create:

  • src/gui/tab_manager.f90 - Core tab state and management
  • src/gui/tab_widget.f90 - GTK tab bar UI components

Modified files:

  • src/gui/gtk_app.f90 - Remove globals, use active tab
  • src/gui/treemap_renderer.f90 - Accept tab context parameter
  • src/gui/treemap_widget.f90 - Key handler for tab shortcuts
  • src/core/progressive_scanner.f90 - Per-tab scan state
  • meson.build - Add new source files

Next Steps

  1. Review this plan with user
  2. Create feature branch: git checkout -b tabs-feature
  3. Start with Phase 1.1 (State Encapsulation)
  4. Commit frequently with descriptive messages
  5. Test thoroughly after each sprint
View source
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