claude_session_viewer/lib/widgets/navigation/app_shell.dart
Mathias Beaulieu-Duncan 659dade82d perf: Phase 1 critical performance fixes + production macOS build
Performance:
- Move JSON parsing to background isolate via Isolate.run() (eliminates 2-4s UI freeze)
- Cache filteredEntries with key-based invalidation (eliminates O(n) recomputation)
- Debounce search queries at 300ms (prevents cascade rebuilds on keystroke)
- Flatten timeline from 2-level turn×response Column to single virtualized ListView.builder
- Add RepaintBoundary per timeline item (isolates repaints during scroll)
- Use context.select for granular rebuilds instead of top-level context.watch
- Lazy ExpandableCard: child not built until first expand (replaces AnimatedCrossFade)
- Use IndexedStack in AppShell (preserves screen state across tab switches)

Fixes:
- Collect parse errors instead of silently swallowing them
- Show parse error count in timeline filter bar
- Fix overflow in tokens screen pie chart legend

Build:
- Configure Developer ID signing with hardened runtime for production distribution
- Enable secure timestamps for notarization
- Update app name to Claude Session Viewer
- Signed, notarized, stapled DMG distribution
2026-04-07 13:32:13 -04:00

57 lines
1.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/session_provider.dart';
import '../../screens/agents/agents_screen.dart';
import '../../screens/home/home_screen.dart';
import '../../screens/timeline/timeline_screen.dart';
import '../../screens/tokens/tokens_screen.dart';
import '../../screens/toolbelt/toolbelt_screen.dart';
import 'sidebar.dart';
class AppShell extends StatefulWidget {
const AppShell({super.key});
@override
State<AppShell> createState() => _AppShellState();
}
class _AppShellState extends State<AppShell> {
SidebarScreen _screen = SidebarScreen.home;
void _onScreenSelected(SidebarScreen screen) {
setState(() => _screen = screen);
}
void _onSessionLoaded() {
setState(() => _screen = SidebarScreen.timeline);
}
@override
Widget build(BuildContext context) {
final provider = context.watch<SessionProvider>();
final hasSession = provider.session != null;
return Row(
children: [
Sidebar(
selected: _screen,
onSelect: _onScreenSelected,
hasSession: hasSession,
),
Expanded(
child: IndexedStack(
index: _screen.index,
children: [
HomeScreen(onSessionLoaded: _onSessionLoaded),
const TimelineScreen(),
const AgentsScreen(),
const ToolbeltScreen(),
const TokensScreen(),
],
),
),
],
);
}
}