Commit Graph

99 Commits

Author SHA1 Message Date
bvandeusen aba0ca6256 feat: gate News tab and briefing news cards on server rss_enabled setting
Fetch server-side settings on app launch via new serverSettingsProvider.
Hide News from bottom sheet and NavigationRail when RSS is disabled.
Skip news card rendering in briefing messages when disabled.
Refactor tab navigation to use dynamic tabs list and route-based refresh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 14:29:05 -04:00
bvandeusen 70a3279192 feat: background update download, snackbar prompt, chat image rendering
Update provider: auto-downloads APK in background after finding a newer
version, prompts via snackbar only when ready, cleans up old APKs on
startup. Replaces modal dialog with dismissible snackbar.

Chat bubble: resolve relative image URLs (/api/images/{id}) against
server base URL with auth cookies so search_images results render on
the phone app.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 12:38:48 -04:00
bvandeusen fc6c9648f9 fix(voice): eliminate dual-recorder conflict and defunct element crashes
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>
2026-04-17 08:04:03 -04:00
bvandeusen 5f11b344a3 feat(voice): replace amplitude silence detection with Silero VAD
Use VadHandler from the vad package (Silero VAD v5 ONNX) for speech
detection instead of amplitude thresholds. VAD runs its own PCM
stream while the file recorder captures AAC-LC for Whisper. Grace
period starts on speech-start, auto-stop on speech-end after grace.
No-speech guard shows error on manual stop without detected speech.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 20:57:52 -04:00
bvandeusen 58d4cfab4d feat: tablet landscape improvements
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>
2026-04-16 01:31:52 -04:00
bvandeusen bdaa5210f0 feat(voice): dynamic silence threshold
The previous -40 dBFS static threshold sat right on top of typical
phone mic ambient, so silence detection rarely fired and the user
always had to tap stop manually.

Silence threshold is now dynamic: track the session peak dBFS and
treat "silent" as 15 dB below peak. Auto-calibrates per mic and
environment rather than assuming a fixed ambient level.

- Grace period (1500 ms) at start so the user has time to begin
  speaking before checks arm.
- Static -35 dB fallback until the peak clears -20 dB so a dead-
  silent session doesn't spin forever.
- Silence duration bumped 1500 → 2000 ms for breathing room.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 00:28:04 -04:00
bvandeusen 48c134ce6a feat(voice): pulse mic button with live amplitude
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>
2026-04-14 22:46:48 -04:00
bvandeusen 00878a8a42 fix(news): use onSurface for article titles instead of primary
Purple-on-black was eye-catching but hard to read. Switch title text
to onSurface for contrast; keep the underline in primary at 70% alpha
as the "this is a link" signal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 18:13:31 -04:00
bvandeusen fa84e40efc feat(briefing): stall watchdog + pull-to-refresh; chat attaches to in-flight streams
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>
2026-04-14 07:55:47 -04:00
bvandeusen 75b7d6d0fe feat(chat): stall watchdog, pull-to-refresh, and unfreeze on refresh
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>
2026-04-13 18:13:46 -04:00
bvandeusen 8ea244ecaa feat(chat): tap tool-call chip to open the created note/task/event
Chips now show the entity title ("Created note: Grocery List") and
deep-link to the matching screen when tapped — notes to the detail
screen, tasks to the edit screen, projects to the project tasks view,
events to the calendar. Read-only tools and errors stay non-tappable.

Closes the main goal behind the mobile chip work: seeing that a note
was created and jumping straight to it without backing out to the
library.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 18:06:03 -04:00
bvandeusen a3fe0b4b61 feat(chat): render tool-call chips and live peek status in mobile bubbles
The mobile app was receiving SSE status events but showing the peek text
only while the assistant bubble was empty — as soon as the first token
arrived, the status line was replaced by streaming content and any tool
calls fired mid-turn left no trace. Tool call SSE events were also being
dropped by the parser, and Message.fromJson never read the persisted
tool_calls array, so chips never rendered after reload either.

Parse tool_call SSE frames into a new ChatToolCall event, carry tool
calls on Message, and update the chat and briefing streaming loops to
append chips to the in-flight assistant message as they arrive. Rework
ChatMessageBubble to show a chip row + rolling peek status line above
any streamed text, matching the web ToolCallCard/status indicator
behaviour across chat, briefing, and briefing history surfaces.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 17:48:16 -04:00
bvandeusen 4240c90d55 fix(voice,knowledge): mic recording + pagination + STT-only mode
Voice: recreate AudioRecorder each session to avoid stale native
state, improve permission handling (re-check after settings), show
feedback after 3 consecutive empty transcripts, allow STT-only mode
when TTS is unavailable.

Knowledge: hydrate IDs immediately after loading more pages so
scroll-based pagination doesn't stall at the bottom.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 11:34:00 -04:00
bvandeusen 47c190891e fix(knowledge): hold stale items during refresh to eliminate flicker 2026-04-08 14:45:57 -04:00
bvandeusen 3e888b6458 fix: eliminate pull-to-refresh flicker by holding stale data during re-fetch 2026-04-08 14:39:50 -04:00
bvandeusen 6c29b685e8 feat(theme): shift Android palette from indigo to deep violet to match web identity 2026-04-08 13:54:39 -04:00
bvandeusen d2582f9111 feat: reduce resume cooldown to 30s; add pull-to-refresh to News, Calendar; refresh chat on resume 2026-04-07 17:43:46 -04:00
bvandeusen 36350d35b1 feat(stt): pass last assistant response as Whisper context to reduce mishearings 2026-04-07 09:57:07 -04:00
bvandeusen d75d34ce8e fix(calendar): convert event times to local timezone on parse 2026-04-06 23:05:33 -04:00
bvandeusen c177bf0691 fix: restore offline queue for captures, drain via chat on retry
Offline queue (SharedPreferences) persists captures when the device
is offline and replays them as chat conversations on next launch,
preserving the same fire-and-forget guarantee as the online path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:06:13 -04:00
bvandeusen 4ebc57d2e5 feat: quick capture sends to chat instead of quick-capture endpoint
Replace POST /api/quick-capture with: create a conversation, send the
message, and fire-and-forget the SSE generation stream so the assistant
processes the request in the background without blocking the UI.
The new conversation appears immediately in the chat tab.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:00:33 -04:00
bvandeusen 946b70ecc4 fix: use dialog context in event delete confirmation
Same navigator mismatch as the chat delete bug — outer context inside
showDialog builder resolves to the wrong navigator ancestor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 18:35:41 -04:00
bvandeusen 6ea268bf58 fix: use dialog context in delete confirmation to prevent shell nav pop
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>
2026-04-06 18:33:04 -04:00
bvandeusen 79dce1a01c feat: wire discuss button in briefing RSS cards, cap cards at 3
Adds discussArticle() to BriefingApi and wires it through to the
RSS news cards in BriefingScreen so tapping Discuss opens a chat
conversation seeded with the article. Also caps RSS cards per
message at 3 to avoid overly long briefing threads.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 17:49:16 -04:00
bvandeusen cb3a09756f fix: create conversations with empty title so server auto-names them
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>
2026-04-06 17:46:23 -04:00
bvandeusen d441dcf954 fix(voice): fix silent STT failure — use AAC/M4A, store onError, guard NaN amplitude
Three bugs causing silent STT failure on Android:

1. AudioEncoder.opus produces an OGG container on Android but the file
   was named .webm — faster-whisper rejected it due to format mismatch.
   Changed to AudioEncoder.aacLc + .m4a (reliable on all Android versions).
   Updated voice_api.dart to send audio/mp4 MIME type accordingly.

2. onError callback was never stored, so errors in _startListening() and
   _handleSilence() were silently swallowed. Now stored as _onError and
   called before exitVoiceMode() on any failure.

3. Amplitude stream can emit NaN or ±Infinity on some Android devices
   during recorder initialisation. NaN < -40.0 is false, so silence was
   never detected. Now treated explicitly as silence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 17:39:21 -04:00
bvandeusen 5014eca9ac feat: auto-refresh data on app resume and tab switch
- App resume: invalidates all main providers (conversations, calendar,
  news, knowledge, briefing) when the app returns to foreground.
  Throttled to once per 5 minutes to avoid hammering the server.
- Tab switch: refreshes the incoming tab's provider when navigating
  between Briefing / Knowledge / Conversations shell tabs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 12:45:33 -04:00
bvandeusen d530920284 fix: load tasks via TasksApi in Knowledge view, remove tab counts
Tasks are not part of the knowledge API (backend rejects type=task).
When the Tasks tab is selected, fetch from /api/tasks and convert to
KnowledgeItem. Also removes per-type counts from tab labels to prevent
tabs from awkwardly resizing when counts load.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 12:36:00 -04:00
bvandeusen e2a358a158 feat: show tool status notifications in chat during generation
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>
2026-04-06 11:46:42 -04:00
bvandeusen 4919f7a185 feat: wire Calendar route to CalendarScreen 2026-04-06 10:32:53 -04:00
bvandeusen 5b639dbd4c feat: add CalendarScreen with month strip and agenda list 2026-04-06 10:32:13 -04:00
bvandeusen 334882520c feat: add EventFormSheet with CRUD, recurrence picker, and color chips
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 10:31:08 -04:00
bvandeusen c4dca6d4ed feat: add CalendarNotifier and calendarProvider 2026-04-06 10:28:53 -04:00
bvandeusen 776b394874 feat: add EventsApi and eventsApiProvider 2026-04-06 10:27:11 -04:00
bvandeusen b56c0fc02d feat: add table_calendar package and CalendarEvent model with tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 10:25:52 -04:00
bvandeusen 3a07221968 feat: wire News route to NewsScreen 2026-04-06 08:30:08 -04:00
bvandeusen e7d174cef7 feat: add NewsScreen with feed filter, reactions, and discuss 2026-04-06 08:29:30 -04:00
bvandeusen f4e39c00eb fix: use ref.watch in build() and avoid stale snapshot in loadMore catch 2026-04-06 08:26:59 -04:00
bvandeusen e08a8906e3 feat: add NewsNotifier and feedsProvider
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 08:22:24 -04:00
bvandeusen 01ea6b48db feat: add RssItemMeta.fromNewsItem adapter factory 2026-04-06 08:18:34 -04:00
bvandeusen b56f3d3a0f feat: add NewsApi, openArticleInChat, and newsApiProvider 2026-04-06 08:16:00 -04:00
bvandeusen 77fc82af45 feat: add NewsItem and BriefingFeed models with tests 2026-04-06 08:13:06 -04:00
bvandeusen 95d0f529ea fix: invalidate project tasks on return from task edit screen 2026-04-06 07:15:35 -04:00
bvandeusen 36bc36cd9d feat: replace Projects tab with More bottom sheet (news/calendar stubs) 2026-04-06 07:14:38 -04:00
bvandeusen 1b08b2fd9e feat(voice): integrate voice mode into Briefing screen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 14:47:38 -04:00
bvandeusen 211bf0d658 feat(voice): integrate one-shot voice capture into Quick Capture bar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 14:46:34 -04:00
bvandeusen 2231c60bfb feat(voice): integrate voice mode into Chat screen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 14:45:53 -04:00
bvandeusen 81077349a5 feat(voice): add VoiceMicButton animated widget
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 14:44:55 -04:00
bvandeusen 2cb566336b feat(voice): add VoiceNotifier with recording, silence detection, and streaming TTS
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 14:44:21 -04:00
bvandeusen 6e33d74178 feat(voice): add VoiceRepository and provider registration 2026-04-05 14:40:49 -04:00