Per-screen application of the design system to the Flutter app.
Mirrors the web's surface phase landed in FabledScribe v26.04.28.1.
Foundation port shipped in 0f05f47; this is the surface work.
Lucide icon migration
- Added lucide_icons ^0.257.0 dependency
- 107 Material Icons references → LucideIcons across 21 files. Drop-in
IconData swap (Icon(LucideIcons.X) instead of Icon(Icons.x)).
- Lucide import added to each touched file.
Input border radius
- theme.dart inputDecorationTheme borderRadius 24 → 8 in both light
and dark themes. Doc says radius-md (8px) for inputs; previous pill
shape was Material default that the doc deviates from.
Illuminated Transcript pattern (ChatMessageBubble)
- User bubble: accent-tinted border → neutral Pewter (scheme.outline).
Asymmetric corner already correct (bottomRight 4px).
- Assistant bubble: topLeft corner 4 → 16; only bottomLeft stays 4
(the "tail" effect, mirroring web's `border-bottom-left-radius: 4px`).
Background switched from surfaceContainerHighest (Slate) to surface
(Iron) per the doc spec "card surface".
- Assistant bubble glow shadow added — accent-tinted blur (28px alpha
0.14) + depth shadow (8px alpha 0.4 black). Mirrors web's
--color-bubble-asst-shadow.
ActionColors wiring (Hybrid rule)
- 5 'Delete' confirm buttons across notes / tasks / chat conversations
/ calendar event sheet → Oxblood action-destructive via the
ActionColors ThemeExtension defined in the foundation port. Foreground
for ghost/text variants, backgroundColor for filled.
- Calendar event Save button → Moss action-primary. The first call
site to wire ActionColors.primary; serves as the pattern for future
Save reclassifications.
- Other Save buttons (note edit, task edit, project edit, etc.) still
flow through colorScheme.primary (dusty violet) and read as
brand-moment. Reclassifying those is deferred — the wiring pattern
is established and can be applied incrementally as files are touched.
Indigo cleanup
- 4 hardcoded #7C3AED / #5B21B6 literals → dusty-violet equivalents
(#5B4A8A / #3F3560). Spots: project_tasks_screen color fallback
(×2), journal_screen gradient.
Verification
- flutter analyze: No issues found
What's deferred
- Per-screen Save / Cancel reclassification beyond the calendar event
Save button. Wiring pattern established; rollout opportunistic.
- Long-form 1.7 line-height on assistant Markdown content (would
require MarkdownStyleSheet work; minor).
- Surface walk on Knowledge / Projects / Settings screens for any
hardcoded styling that needs touch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
VadHandler is now the sole mic owner — removed separate AudioRecorder that
caused audio focus contention on Android. Audio from onSpeechEnd is encoded
as WAV for Whisper. Provider switched to autoDispose to match widget lifecycle,
preventing defunct element assertions. Recording UI deferred until mic is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shell: move Projects/News/Calendar into ShellRoute so the NavigationRail
persists across all screens. Show all 6 nav destinations on tablet
instead of 3+More overflow. Phone bottom nav unchanged.
Chat: master-detail layout on tablet — conversations list (320px) with
inline chat panel. Tapping a conversation updates state instead of
pushing a new route.
Knowledge: responsive grid (2 cols portrait, 3 cols landscape) with
card layout showing icon, title, body preview, and tags. Fix snippet
data — read json['snippet'] which the API actually sends. Bump snippet
length from 120 to 200 chars. Tasks now show description as snippet.
News: responsive grid with the same breakpoints. Larger snippet
(5 lines) in grid mode via snippetMaxLines parameter.
Briefing: centered reading column (maxWidth 700) on wide screens,
matching the web UI layout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
VoiceState now carries a normalized mic amplitude (0..1) updated
from the existing onAmplitudeChanged subscription, with a 0.02
change threshold so we don't spam rebuilds.
VoiceMicButton swaps the constant-rate AnimationController for an
AnimatedScale + animated glow driven by the live amplitude. 0.1
floor keeps the button breathing on silence; 120ms ease-out smooths
between 200ms samples.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Briefing chat now uses the same StreamIterator + 45s per-event timeout
as the main chat provider, so a dropped SSE socket no longer leaves
isBriefingStreaming stuck true. Adds refreshMessages() that unfreezes
state when the server-side message is complete, wired to a new
pull-to-refresh gesture and the app lifecycle-resume hook.
Chat provider gains attachToGeneration() — safe to call unconditionally
on screen init, so landing on a conversation that's already mid-stream
(e.g. the /news discuss button, which now creates a conv and auto-kicks
generation server-side) picks up live tokens instead of freezing on the
placeholder.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mobile chat occasionally froze with the input disabled and nothing
progressing — the underlying cause is SSE sockets dropping silently on
mobile network handoffs / proxy idle timeouts. Dio never observes the
close, so the send loop's `await for` hangs indefinitely with
isStreaming=true.
Three changes:
- Wrap the SSE stream in a StreamIterator with a 45s per-event timeout
as a stall watchdog. On timeout, break out and let the polling pass
reconcile state from the server.
- Add pull-to-refresh on the chat message list and an AppBar refresh
button with inline spinner feedback. Works on both empty and
populated states.
- MessagesNotifier.refresh() now also clears isStreaming /
streamingStatus when the server reports the latest assistant message
as complete — so a stuck UI unfreezes the moment the user pulls down
or taps refresh, even if the original SSE loop is still hanging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Navigator.pop(outerContext) inside a showDialog builder resolves to the
ShellRoute's nested navigator instead of the root navigator where the
dialog lives, popping the conversations route and showing a black screen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The app was creating conversations with title 'New conversation'.
The server only generates a title when conv_title is falsy (empty).
With a non-empty title, should_gen_title is False for the first
message (msg_count % 10 != 0), so auto-naming never fired.
Now creates with empty title (matching web app behaviour). The list
still displays 'New conversation' as a UI placeholder until the
server-generated title arrives.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Parse SSE `status` events alongside `chunk` events and display the
status text next to the spinner while the assistant is generating.
Previously the Android app discarded all non-chunk SSE events.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migrate StateNotifierProvider/StateNotifier → NotifierProvider/Notifier,
StateProvider → NotifierProvider with simple notifier, FamilyAsyncNotifier
removed in Riverpod 3 (replaced with constructor-arg pattern), and rename
.valueOrNull → .value throughout all providers and screens.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
flutter_markdown was discontinued; flutter_markdown_plus is the
maintained drop-in replacement (same API, updated imports only).
Also set android:label to "Fabled" so the app appears correctly
on the home screen and in the app drawer instead of "fabled_app".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In wide mode, the floating action button defaulted to bottom-right of
the screen, sitting on top of the chat send button and note detail pane.
Replace the FAB with a pinned ListTile at the bottom of the list pane
in wide mode (New conversation / New note). The FAB is still used on
narrow/phone screens where there is no overlap.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On screens ≥600dp, Notes and Chat show a two-pane layout:
- Left pane (300dp): scrollable list with selection highlight
- Right pane: inline detail (NoteDetailScreen / ChatScreen)
Tapping a list item selects it in wide mode instead of pushing a route.
Creating a new conversation on wide screen auto-selects it in the pane.
Deleting a selected note/conversation clears the right pane.
NoteDetailScreen gains an optional onDeleted callback used by the
embedded pane to clear selection rather than call context.pop().
Tasks keep push-based navigation (edit form has save/pop semantics
that require a full-screen context).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Switch navigation breakpoint from orientation to width (≥600dp):
NavigationRail shown on tablets in portrait and phones in landscape
- Chat bubble max-width capped at 480dp (prevents 640dp+ bubbles on tablets)
- Chat input maxLines reduced to 2 when width ≥600dp
- Task edit form centered and constrained to 600dp max-width on tablets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Collapsible search in notes and tasks: magnifying glass in AppBar
expands to a text field inline; close button resets the filter
- Offline capture queue: failed quick-captures (NetworkException) are
persisted to SharedPreferences and retried automatically on next
successful submit or app start; badge shows pending count
- App icon: book-with-sparkle logo from FabledAssistant SVG rendered
at all Android densities with adaptive icon (indigo #6366f1 bg)
- Dark mode subtitle fix: use colorScheme.onSurfaceVariant for note
preview and conversation timestamp text
- Remove swipe-to-delete (accidental deletions); long-press remains
- Chat: SSE streaming reliability, polling fallback, title patching
- Settings: theme toggle (system/light/dark) persisted to prefs
- Notes: delete button in edit screen; body preview in list
- Tasks: fix delete dialog context; description search support
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>