From f6b496dad7c658f999e25fdb9a648b99b52dd608 Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 7 Apr 2026 14:01:04 -0400 Subject: [PATCH] fix: two-row header layout, flexible path input, button error handling - Split header into two rows: breadcrumb + count on top, path/buttons below - Path input is now flexible (fills available width) instead of fixed 320px - Fixes Row overflow on narrow windows - Load/Browse buttons now same height (36px) as path input - Added try/catch with snackbar error feedback for Load and Browse - Removed initialDirectory from file picker (could fail in non-sandbox) - Browse also updates the path input field when a folder is selected - Signed, notarized, stapled DMG --- lib/screens/home/home_screen.dart | 307 +++++++++++++++++------------- 1 file changed, 176 insertions(+), 131 deletions(-) diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart index 54dc10e..b6c75b5 100644 --- a/lib/screens/home/home_screen.dart +++ b/lib/screens/home/home_screen.dart @@ -332,11 +332,24 @@ class _HomeScreenState extends State { } Future _pickFolder() async { - final result = await FilePicker.platform.getDirectoryPath( - dialogTitle: 'Select a folder containing Claude session files', - ); - if (result != null) { - await _scanFolder(result); + try { + final result = await FilePicker.platform.getDirectoryPath( + dialogTitle: 'Select a folder containing Claude session files', + ); + if (result != null) { + _pathController.text = result; + await _scanFolder(result); + } + } catch (e) { + debugPrint('[Browse] Error: $e'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Could not open folder picker: $e'), + backgroundColor: AppColors.error, + ), + ); + } } } @@ -393,14 +406,24 @@ class _HomeScreenState extends State { } Future _loadSingleFile() async { - final home = _getRealHome() ?? ''; - final result = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['jsonl'], - initialDirectory: '$home/.claude/projects', - ); - if (result != null && result.files.single.path != null) { - await _loadFile(result.files.single.path!); + try { + final result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['jsonl'], + ); + if (result != null && result.files.single.path != null) { + await _loadFile(result.files.single.path!); + } + } catch (e) { + debugPrint('[Load] Error: $e'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Could not open file picker: $e'), + backgroundColor: AppColors.error, + ), + ); + } } } @@ -491,131 +514,153 @@ class _HomeScreenState extends State { // ─── Header with breadcrumb ────────────────────────────── Widget _buildHeader(SessionProvider provider) { - return Row( + return Column( children: [ - const Icon(Icons.terminal, size: 24, color: AppColors.assistant), - const SizedBox(width: 10), - // Breadcrumb - InkWell( - onTap: () => setState(() => _selectedProject = null), - borderRadius: BorderRadius.circular(4), - child: Text( - 'Projects', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: _selectedProject != null - ? AppColors.assistant - : AppColors.textPrimary, - decoration: _selectedProject != null - ? TextDecoration.underline - : TextDecoration.none, - decorationColor: AppColors.assistant, + // Row 1: breadcrumb + count + Row( + children: [ + const Icon(Icons.terminal, size: 24, color: AppColors.assistant), + const SizedBox(width: 10), + InkWell( + onTap: () => setState(() => _selectedProject = null), + borderRadius: BorderRadius.circular(4), + child: Text( + 'Projects', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: _selectedProject != null + ? AppColors.assistant + : AppColors.textPrimary, + decoration: _selectedProject != null + ? TextDecoration.underline + : TextDecoration.none, + decorationColor: AppColors.assistant, + ), + ), ), - ), + if (_selectedProject != null) ...[ + const Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Icon(Icons.chevron_right, size: 18, color: AppColors.textMuted), + ), + Flexible( + child: Text( + _selectedProject!.displayName, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.textPrimary, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + const Spacer(), + Text( + _selectedProject != null + ? '${_selectedProject!.sessionCount} session${_selectedProject!.sessionCount == 1 ? '' : 's'}' + : '${_projects.length} projects', + style: const TextStyle(fontSize: 12, color: AppColors.textMuted), + ), + ], ), - if (_selectedProject != null) ...[ - const Padding( - padding: EdgeInsets.symmetric(horizontal: 8), - child: Icon(Icons.chevron_right, size: 18, color: AppColors.textMuted), - ), - Flexible( - child: Text( - _selectedProject!.displayName, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: AppColors.textPrimary, - ), - overflow: TextOverflow.ellipsis, - ), - ), - ], - const Spacer(), - Text( - _selectedProject != null - ? '${_selectedProject!.sessionCount} session${_selectedProject!.sessionCount == 1 ? '' : 's'}' - : '${_projects.length} projects', - style: const TextStyle(fontSize: 12, color: AppColors.textMuted), - ), - // Path input field - SizedBox( - width: 320, - child: TextField( - controller: _pathController, - focusNode: _pathFocusNode, - style: const TextStyle( - fontSize: 12, - color: AppColors.textPrimary, - fontFamily: 'JetBrains Mono', - ), - decoration: InputDecoration( - hintText: '~/ .claude/projects', - hintStyle: const TextStyle( - fontSize: 12, - color: AppColors.textMuted, - fontFamily: 'JetBrains Mono', - ), - isDense: true, - contentPadding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 8, - ), - filled: true, - fillColor: AppColors.surface, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(6), - borderSide: const BorderSide(color: AppColors.surfaceBorder), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(6), - borderSide: const BorderSide(color: AppColors.surfaceBorder), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(6), - borderSide: const BorderSide(color: AppColors.assistant), - ), - suffixIcon: IconButton( - icon: const Icon(Icons.arrow_forward, size: 14, - color: AppColors.textMuted), - tooltip: 'Scan this folder', - onPressed: _scanFromPathInput, + const SizedBox(height: 12), + // Row 2: path input + Load + Browse + Row( + children: [ + // Path input field — flexible to fill available space + Expanded( + child: SizedBox( + height: 36, + child: TextField( + controller: _pathController, + focusNode: _pathFocusNode, + style: const TextStyle( + fontSize: 12, + color: AppColors.textPrimary, + fontFamily: 'JetBrains Mono', + ), + decoration: InputDecoration( + hintText: '~/.claude/projects', + hintStyle: const TextStyle( + fontSize: 12, + color: AppColors.textMuted, + fontFamily: 'JetBrains Mono', + ), + isDense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 8, + ), + filled: true, + fillColor: AppColors.surface, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(6), + borderSide: const BorderSide(color: AppColors.surfaceBorder), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(6), + borderSide: const BorderSide(color: AppColors.surfaceBorder), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(6), + borderSide: const BorderSide(color: AppColors.assistant), + ), + suffixIcon: IconButton( + icon: const Icon(Icons.arrow_forward, size: 14, + color: AppColors.textMuted), + tooltip: 'Scan this folder', + onPressed: _scanFromPathInput, + ), + ), + onSubmitted: (_) => _scanFromPathInput(), + ), ), ), - onSubmitted: (_) => _scanFromPathInput(), - ), - ), - const SizedBox(width: 8), - ElevatedButton.icon( - onPressed: _loadSingleFile, - icon: const Icon(Icons.insert_drive_file_outlined, size: 14), - label: const Text('Load'), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.surfaceLight, - foregroundColor: AppColors.textPrimary, - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - side: const BorderSide(color: AppColors.surfaceBorder), + const SizedBox(width: 8), + SizedBox( + height: 36, + child: ElevatedButton.icon( + onPressed: _loadSingleFile, + icon: const Icon(Icons.insert_drive_file_outlined, size: 14), + label: const Text('Load'), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.surfaceLight, + foregroundColor: AppColors.textPrimary, + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 0), + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + side: const BorderSide(color: AppColors.surfaceBorder), + ), + textStyle: const TextStyle(fontSize: 12), + ), + ), ), - textStyle: const TextStyle(fontSize: 12), - ), - ), - const SizedBox(width: 8), - ElevatedButton.icon( - onPressed: _pickFolder, - icon: const Icon(Icons.folder_open, size: 14), - label: const Text('Browse'), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.surfaceLight, - foregroundColor: AppColors.textPrimary, - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - side: const BorderSide(color: AppColors.surfaceBorder), + const SizedBox(width: 8), + SizedBox( + height: 36, + child: ElevatedButton.icon( + onPressed: _pickFolder, + icon: const Icon(Icons.folder_open, size: 14), + label: const Text('Browse'), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.surfaceLight, + foregroundColor: AppColors.textPrimary, + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 0), + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + side: const BorderSide(color: AppColors.surfaceBorder), + ), + textStyle: const TextStyle(fontSize: 12), + ), + ), ), - textStyle: const TextStyle(fontSize: 12), - ), + ], ), ], );