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>
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>
Mirrors the same fix applied to the fabledassistant repo: merges to
main only happen after dev already passed CI and tagged releases are
the sole trigger for a signed APK build, so re-running analyze+test on
the merge commit just burns runner time.
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>
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>
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>
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>
- trigger: add branches: [dev, main] so analyze+test run on every
push, not just release tags. Build job stays tag-gated via
if: startsWith(github.ref, 'refs/tags/')
- pin Flutter to 3.41.6 instead of floating :stable for reproducible
release builds
- concurrency: cancel in-progress non-tag runs
- permissions: contents: read default, contents: write scoped to build
- extract release publish logic to scripts/publish_apk_release.sh so
it's testable locally (bash -x with env vars) and the YAML stays
readable. Adds set -euo pipefail + curl -f so failures surface
instead of getting swallowed by || echo "skipped"
- drop the broken artifact upload step (silently swallowed errors)
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>
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>
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>
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>
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>
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>
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>
Replaces outdated Library/stub descriptions with accurate coverage of
Knowledge, Calendar, News, Voice I/O, chat tool-status notifications,
auto-refresh, and the current file tree.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
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>
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>