|
| 1 | +# Filepicker Module Analysis and Improvement Recommendations |
| 2 | + |
| 3 | +## Executive Summary |
| 4 | + |
| 5 | +This document analyzes the filepicker implementation in tldw_chatbook and provides actionable recommendations for improvements. The analysis covers the current dual-implementation architecture (third-party `textual_fspicker` and custom `file_picker_dialog.py` wrapper), identifies pain points, and suggests both easy wins and more comprehensive improvements. |
| 6 | + |
| 7 | +## Current Architecture Overview |
| 8 | + |
| 9 | +### 1. Third-Party Component (`textual_fspicker`) |
| 10 | +- **Location**: `tldw_chatbook/Third_Party/textual_fspicker/` |
| 11 | +- **Components**: |
| 12 | + - `FileOpen`: File opening dialog |
| 13 | + - `FileSave`: File saving dialog |
| 14 | + - `DirectoryNavigation`: Core navigation widget |
| 15 | + - `DriveNavigation`: Windows drive selection |
| 16 | + - `Filters`: File type filtering system |
| 17 | + |
| 18 | +### 2. Custom Wrapper (`file_picker_dialog.py`) |
| 19 | +- **Location**: `tldw_chatbook/Widgets/file_picker_dialog.py` |
| 20 | +- **Purpose**: Evaluation-specific file picker dialogs |
| 21 | +- **Classes**: |
| 22 | + - `EvalFilePickerDialog`: Base modal dialog |
| 23 | + - `TaskFilePickerDialog`: Task file selection |
| 24 | + - `DatasetFilePickerDialog`: Dataset file selection |
| 25 | + - `ExportFilePickerDialog`: Result export |
| 26 | + - `QuickPickerWidget`: Inline file selection |
| 27 | + |
| 28 | +## Identified Issues and Improvements |
| 29 | + |
| 30 | +### 🏆 Easy Wins (Low effort, high impact) |
| 31 | + |
| 32 | +#### 1. **Add Keyboard Shortcuts** |
| 33 | +**Issue**: No keyboard shortcuts for common actions |
| 34 | +**Solution**: Add bindings for: |
| 35 | +```python |
| 36 | +BINDINGS = [ |
| 37 | + Binding("ctrl+h", "toggle_hidden", "Toggle hidden files"), |
| 38 | + Binding("ctrl+l", "focus_path_input", "Edit path directly"), |
| 39 | + Binding("ctrl+d", "bookmark_current", "Bookmark directory"), |
| 40 | + Binding("f5", "refresh", "Refresh directory"), |
| 41 | + Binding("tab", "toggle_focus", "Switch focus"), |
| 42 | +] |
| 43 | +``` |
| 44 | +**Effort**: 2-3 hours |
| 45 | +**Impact**: Significant UX improvement |
| 46 | + |
| 47 | +#### 2. **Recent Files/Directories List** |
| 48 | +**Issue**: Users must navigate from scratch each time |
| 49 | +**Solution**: Add a recent locations dropdown |
| 50 | +```python |
| 51 | +class RecentLocations: |
| 52 | + def __init__(self, max_items: int = 10): |
| 53 | + self.recent: List[Path] = [] |
| 54 | + self.load_from_config() |
| 55 | + |
| 56 | + def add(self, path: Path): |
| 57 | + if path in self.recent: |
| 58 | + self.recent.remove(path) |
| 59 | + self.recent.insert(0, path) |
| 60 | + self.recent = self.recent[:self.max_items] |
| 61 | + self.save_to_config() |
| 62 | +``` |
| 63 | +**Effort**: 3-4 hours |
| 64 | +**Impact**: Major time-saver for users |
| 65 | + |
| 66 | +#### 3. **Improve Path Display** |
| 67 | +**Issue**: Current path display can be unclear/truncated |
| 68 | +**Solution**: Add breadcrumb navigation |
| 69 | +```python |
| 70 | +class PathBreadcrumbs(Horizontal): |
| 71 | + """Clickable breadcrumb path navigation""" |
| 72 | + def render_path(self, path: Path): |
| 73 | + parts = path.parts |
| 74 | + for i, part in enumerate(parts): |
| 75 | + partial_path = Path(*parts[:i+1]) |
| 76 | + yield Button(part, classes="breadcrumb-button") |
| 77 | + if i < len(parts) - 1: |
| 78 | + yield Label("/", classes="breadcrumb-separator") |
| 79 | +``` |
| 80 | +**Effort**: 4-5 hours |
| 81 | +**Impact**: Better navigation clarity |
| 82 | + |
| 83 | +#### 4. **File Preview Panel** |
| 84 | +**Issue**: No way to preview file contents |
| 85 | +**Solution**: Add optional preview pane for text/image files |
| 86 | +```python |
| 87 | +class FilePreview(Container): |
| 88 | + def update_preview(self, file_path: Path): |
| 89 | + if file_path.suffix in ['.txt', '.json', '.yaml', '.md']: |
| 90 | + self.show_text_preview(file_path) |
| 91 | + elif file_path.suffix in ['.png', '.jpg', '.webp']: |
| 92 | + self.show_image_preview(file_path) |
| 93 | +``` |
| 94 | +**Effort**: 4-5 hours |
| 95 | +**Impact**: Reduces wrong file selections |
| 96 | + |
| 97 | +#### 5. **Search Within Directory** |
| 98 | +**Issue**: No search functionality for large directories |
| 99 | +**Solution**: Add search input with real-time filtering |
| 100 | +```python |
| 101 | +@on(Input.Changed, "#search-input") |
| 102 | +def filter_entries(self, event: Input.Changed): |
| 103 | + search_term = event.value.lower() |
| 104 | + for entry in self.directory_entries: |
| 105 | + entry.visible = search_term in entry.name.lower() |
| 106 | +``` |
| 107 | +**Effort**: 2-3 hours |
| 108 | +**Impact**: Much faster file finding |
| 109 | + |
| 110 | +### 🔨 Medium Improvements (Moderate effort, good impact) |
| 111 | + |
| 112 | +#### 6. **Multi-file Selection** |
| 113 | +**Issue**: Can only select one file at a time |
| 114 | +**Solution**: Add checkbox mode for batch operations |
| 115 | +```python |
| 116 | +class MultiSelectFileOpen(FileOpen): |
| 117 | + selected_files: reactive[Set[Path]] = reactive(set()) |
| 118 | + |
| 119 | + def toggle_selection(self, path: Path): |
| 120 | + if path in self.selected_files: |
| 121 | + self.selected_files.discard(path) |
| 122 | + else: |
| 123 | + self.selected_files.add(path) |
| 124 | +``` |
| 125 | +**Effort**: 8-10 hours |
| 126 | +**Impact**: Enables batch processing workflows |
| 127 | + |
| 128 | +#### 7. **Bookmarks/Favorites System** |
| 129 | +**Issue**: No way to save frequently used locations |
| 130 | +**Solution**: Add persistent bookmarks sidebar |
| 131 | +```python |
| 132 | +class BookmarksPanel(Container): |
| 133 | + def compose(self) -> ComposeResult: |
| 134 | + yield ListView(id="bookmarks-list") |
| 135 | + yield Button("+ Add Current", id="add-bookmark") |
| 136 | + |
| 137 | + def load_bookmarks(self) -> List[Bookmark]: |
| 138 | + return self.app.config.get("filepicker.bookmarks", []) |
| 139 | +``` |
| 140 | +**Effort**: 6-8 hours |
| 141 | +**Impact**: Significant workflow improvement |
| 142 | + |
| 143 | +#### 8. **Performance: Lazy Loading** |
| 144 | +**Issue**: Large directories load slowly |
| 145 | +**Solution**: Implement virtual scrolling with batch loading |
| 146 | +```python |
| 147 | +class LazyDirectoryNavigation(DirectoryNavigation): |
| 148 | + BATCH_SIZE = 100 |
| 149 | + |
| 150 | + async def load_entries_batch(self, start: int, end: int): |
| 151 | + # Load only visible entries + buffer |
| 152 | + entries = self.all_entries[start:end] |
| 153 | + await self.update_display(entries) |
| 154 | +``` |
| 155 | +**Effort**: 10-12 hours |
| 156 | +**Impact**: Much better performance for large dirs |
| 157 | + |
| 158 | +### 🚀 Major Improvements (Higher effort, transformative impact) |
| 159 | + |
| 160 | +#### 9. **Unified Filepicker Architecture** |
| 161 | +**Issue**: Dual implementation creates maintenance burden |
| 162 | +**Solution**: Create single, extensible filepicker system |
| 163 | +```python |
| 164 | +class UnifiedFilePicker: |
| 165 | + """Single filepicker with plugin system for different contexts""" |
| 166 | + def register_filter_preset(self, name: str, filters: Filters): |
| 167 | + self.filter_presets[name] = filters |
| 168 | + |
| 169 | + def register_validator(self, name: str, validator: Callable): |
| 170 | + self.validators[name] = validator |
| 171 | +``` |
| 172 | +**Effort**: 20-30 hours |
| 173 | +**Impact**: Cleaner codebase, easier to extend |
| 174 | + |
| 175 | +#### 10. **Drag and Drop Support** |
| 176 | +**Issue**: No modern drag/drop functionality |
| 177 | +**Solution**: Implement drop zones for file selection |
| 178 | +```python |
| 179 | +class DropZoneFilePicker(FilePicker): |
| 180 | + def on_drop(self, event: DropEvent): |
| 181 | + for file_path in event.files: |
| 182 | + self.add_selected_file(file_path) |
| 183 | +``` |
| 184 | +**Effort**: 15-20 hours |
| 185 | +**Impact**: Modern UX expected by users |
| 186 | + |
| 187 | +## Implementation Priority Matrix |
| 188 | + |
| 189 | +### Phase 1: Quick Wins (1-2 weeks) |
| 190 | +1. ✅ Keyboard shortcuts |
| 191 | +2. ✅ Recent files list |
| 192 | +3. ✅ Search within directory |
| 193 | +4. ✅ Improve path display |
| 194 | + |
| 195 | +### Phase 2: Enhanced UX (2-3 weeks) |
| 196 | +5. ✅ File preview panel |
| 197 | +6. ✅ Bookmarks system |
| 198 | +7. ✅ Basic multi-selection |
| 199 | + |
| 200 | +### Phase 3: Performance & Architecture (4-6 weeks) |
| 201 | +8. ✅ Lazy loading implementation |
| 202 | +9. ✅ Unified architecture refactor |
| 203 | +10. ✅ Drag and drop support |
| 204 | + |
| 205 | +## Code Quality Improvements |
| 206 | + |
| 207 | +### Type Hints Enhancement |
| 208 | +```python |
| 209 | +# Before |
| 210 | +def handle_file_selected(self, event): |
| 211 | + self.selected_file = str(event.path) |
| 212 | + |
| 213 | +# After |
| 214 | +def handle_file_selected(self, event: FileOpen.FileSelected) -> None: |
| 215 | + self.selected_file: str = str(event.path) |
| 216 | +``` |
| 217 | + |
| 218 | +### Error Handling Improvements |
| 219 | +```python |
| 220 | +# Add specific exception types |
| 221 | +class FilePickerError(Exception): |
| 222 | + """Base exception for filepicker errors""" |
| 223 | + |
| 224 | +class InvalidPathError(FilePickerError): |
| 225 | + """Raised when path is invalid or inaccessible""" |
| 226 | + |
| 227 | +class FilterError(FilePickerError): |
| 228 | + """Raised when file filter pattern is invalid""" |
| 229 | +``` |
| 230 | + |
| 231 | +### Consistent Event System |
| 232 | +```python |
| 233 | +# Define standard events |
| 234 | +class FilePickerEvents: |
| 235 | + class FileSelected(Message): |
| 236 | + path: Path |
| 237 | + |
| 238 | + class DirectoryChanged(Message): |
| 239 | + path: Path |
| 240 | + |
| 241 | + class FilterChanged(Message): |
| 242 | + filter: Filter |
| 243 | +``` |
| 244 | + |
| 245 | +## Testing Recommendations |
| 246 | + |
| 247 | +1. **Unit Tests**: Test each component in isolation |
| 248 | +2. **Integration Tests**: Test dialog workflows |
| 249 | +3. **Performance Tests**: Benchmark large directory handling |
| 250 | +4. **Accessibility Tests**: Ensure keyboard navigation works |
| 251 | + |
| 252 | +## Migration Strategy |
| 253 | + |
| 254 | +1. **Backward Compatibility**: Keep existing APIs working |
| 255 | +2. **Feature Flags**: Roll out new features gradually |
| 256 | +3. **Documentation**: Update examples and guides |
| 257 | +4. **Deprecation Path**: Clear timeline for old API removal |
| 258 | + |
| 259 | +## Conclusion |
| 260 | + |
| 261 | +The filepicker module has significant room for improvement. Starting with the easy wins will provide immediate user benefits while building toward a more comprehensive overhaul. The recommended phased approach balances quick improvements with long-term architectural benefits. |
| 262 | + |
| 263 | +### Next Steps |
| 264 | +1. Review and prioritize recommendations with team |
| 265 | +2. Create detailed implementation tickets |
| 266 | +3. Begin with Phase 1 easy wins |
| 267 | +4. Gather user feedback after each phase |
| 268 | +5. Iterate based on real-world usage patterns |
0 commit comments