Commit graph

77 commits

Author SHA1 Message Date
avi
6efec5702c generateBuildInfo: register commit as task input — a plain rebuild after a new commit kept the STALE baked sha (task UP-TO-DATE), which made the in-app update check forever report 'update available' 2026-09-23 14:59:51 -05:00
avi
fc5502a7cd Installable app + one-click update from forgejo
- scripts/update-app.sh: detached updater (setsid, survives app exit):
  git pull atitlan master -> gradle createDistributable -> only then
  kill app+engine and relaunch the fresh binary. Pull or build failure
  leaves the running app alone. Log: ~/.config/shonar-desktop/logs/update.log.
- Settings → About & updates: 'Install update' button appears when the
  check finds a newer remote commit; hands off to the script and tells
  the user the app will relaunch itself.
- Desktop entry (~/.local/share/applications/shonar-desktop.desktop,
  outside the repo): launches the packaged distributable via
  launch-desktop.sh; SHONAR_REPO fallback already resolves the tree,
  so the packaged app spawns the engine and sees the updater script.

Gate: :app:compileKotlin + :app:test green; desktop-file-validate clean;
launcher verified (app window from the menu path); updater fail-safe
verified (dirty-tree pull refused, app survived).
2026-09-22 12:18:24 -05:00
avi
7195aa1ae6 Settings: 'About & updates' — check-for-updates against the forgejo repo
Bakes the source commit + build timestamp into the jar
(generateBuildInfo task -> shonar-build.properties on the classpath).
The Settings screen gains a 'Check for updates' button that queries the
anonymous Forgejo commits API for the latest master commit and compares
it to this build: up to date / update available (with commit shas and an
'Open repo' link via xdg-open) / 'newer than forgejo — local work not
yet pushed' for dev builds ahead of the remote. Network failures render
as a message with a repo link, never a crash.

Gate: :app:compileKotlin + :app:test green (9 suites); jar property
verified by unzip.
2026-09-22 09:12:09 -05:00
avi
7ec6229d76 Theme: 'Meridian' renamed to 'Dark' (legacy 'meridian'/'dark' prefs load it); Light mode retoned to warm paper (page #EDEAE3, off-white cards, warm-charcoal ink) — full white was glaring per user 2026-09-21 20:23:25 -05:00
avi
a4cd446926 Theme rename: 'Sonar' is now 'Default' (user directive) — Settings pill label, enum, and pref key 'default'; legacy 'dark'/'sonar' prefs load as Default; helper text fixed to teal 2026-09-21 19:52:36 -05:00
avi
13aae7818d Library rows: circular teal play button at row left (Android-home affordance) — plays/stops without opening the detail page; DONE files only; Stop square while playing 2026-09-21 19:45:20 -05:00
avi
d362dc6f42 Sonar theme = Android's real default: teal 4FD1C5 accent (not coral), dark ink on teal, logo tile #0B1220 with teal hairline, single-accent-color ping logo; icon.png regenerated teal-on-#0B1220 to match Android launcher 2026-09-21 19:36:43 -05:00
avi
a459ed1e4c Library rows: metadata line no longer paints in the accent (coral) on DONE — Android HomeScreen uses onSurfaceVariant for all statuses, error only for FAILED; matches the companion app 2026-09-21 18:42:28 -05:00
avi
5526699103 Theme: default is now 'Sonar' — Android companion's palette (DeepNavy 070D18, NavySurface, coral E76F51 accent, flat bg) so desktop and phone read as one product; previous charcoal/blue look kept as optional 'Meridian'; Settings pills Sonar/Meridian/Light; legacy 'dark' pref loads as Sonar 2026-09-21 18:28:19 -05:00
avi
7cc3737f92 Detail page: Play and Delete match library Delete-1 style — red-outline ghost buttons with white ink (ShonarDangerButton gains ink param; solid-filled variant removed) 2026-09-21 18:18:30 -05:00
avi
d0de0cc405 Detail page: Play and Delete are one solid red (Shonar.Error fill, black ink) via new ShonarDangerFilledButton; verified pixel-identical fills on-device 2026-09-21 17:13:24 -05:00
avi
81f9855c1f Theme: every typography tier + LocalContentColor carry explicit palette ink; root Surface transparency made Compose's black default visible-on-dark text (fix unresolved foundation.LocalContentColor -> material3) 2026-09-21 16:25:21 -05:00
avi
7fc388be4b Background: darker meridian page gradient (#1B1D21 top -> #101113 bottom) behind transparent root surface, per reference ramp 2026-09-21 15:54:37 -05:00
avi
758842ae2a Icons: brand blue (#6190E5) for in-app sonar logo and window/taskbar icon; recolor icon.png, ship as window icon 2026-09-21 15:45:12 -05:00
avi
0fce24644b Palette: neutral #232323 cards + #323232 borders on blue-cast #15161A page, matching reference pixel samples 2026-09-21 15:18:01 -05:00
avi
33438f44a1 Fainter red per reference: mauve row wash #573440 for highlighted recordings, white titles, darker page #141619 2026-09-21 14:50:43 -05:00
avi
30a9facf89 Match reference palette: charcoal page, blue interactive accent, pink-red reserved for recording titles and selected rows 2026-09-21 13:59:02 -05:00
avi
93151bf85e Theme: charcoal canvas + pink accent per reference screenshot; teal reserved for the brand logo only 2026-09-21 13:17:43 -05:00
avi
5852101a37 Revert "Shell: persistent sidebar (views, engine status, theme) + library top bar with search/sort/batch; responsive rail and drawer modes; Ctrl+F search"
This reverts commit ceb3be3dda.
2026-09-21 12:57:12 -05:00
avi
ceb3be3dda Shell: persistent sidebar (views, engine status, theme) + library top bar with search/sort/batch; responsive rail and drawer modes; Ctrl+F search 2026-09-21 12:35:19 -05:00
avi
6e5a47ed5c Components: app-wide keyboard focus rings plus workspace primitives (icon button, nav row, tag chip, status dot, banner, empty state) 2026-09-21 12:20:27 -05:00
avi
f3a84a2333 Design tokens: theme-aware palette via CompositionLocal, light theme, shape/elevation/motion tokens, persisted ui.theme pref with Settings toggle 2026-09-21 12:04:29 -05:00
avi
90f6f1b786 Detail: summary paragraph, voice chips, and copy actions consolidated into one card 2026-09-21 11:29:48 -05:00
avi
a6e1d30f92 Typography: explicit line heights on every tier so body text stops reading as a dense wall 2026-09-21 11:29:48 -05:00
avi
c32fc2079c PipelinePanel: wrap card content in a Column so stages stop painting over the headline
SonarCard's Surface lays un-wrapped siblings in a Box, so the whole
processing panel (teal headline, gray elapsed/note text, stage rows,
progress bar) drew stacked on one line — the 'text overlapping while
summarizing' garble, visible only mid-job. Every other card call site
wraps its content; this was the one that didn't. Headline also gets
weight(1f, fill=false) + maxLines/Ellipsis so it can't collide with
the elapsed/ETA text at narrow widths.
2026-09-20 20:49:21 -05:00
avi
b541cc2314 Never swallow detail-screen updates: revision field on DetailUi
THE root cause behind "summary changed but the open screen kept the
old voice until I left to the dashboard and back": the app mutates ONE
DetailUi instance in place and republishes it with copy(). StateFlow
drops any assignment equal() to its current value — and a copy of the
very object it already holds is ALWAYS equal (same mutated fields).
So every publish after the first was silently dropped. The screen
only ever refreshed by accident, when some other collected flow
(models, playback, library rows) happened to trigger a recomposition
that re-read the mutated fields mid-run; the final summary publish,
with nothing else changing at that moment, just vanished. Engine
logs proved the app fetched the fresh sarcastic summary — the UI
received it and threw it away.

DetailUi gains a rev discriminator bumped on every publish (23 sites),
so no update can ever conflate away. Two regression tests pin the
conflation semantics.
2026-09-18 22:05:04 -05:00
avi
05b84a361a Show the busy label the instant a click lands, before the first job row exists
Between pressing Transcribe and the first /jobs poll there was a blind
window where real work (POST in flight, token refresh, initial upload)
had no on-screen trace at all — the screenshot complaint 'shows
nothing'. Any non-null busy label now renders as a pipeline-style card
in the same slot the panel occupies, so feedback starts at click time.
2026-09-18 21:18:30 -05:00
avi
b5836d0fb6 Keep watching the job through transient fetch failures; don't freeze the screen
reprocessInPlace's poll loop swallowed fetchJobs failures as an empty
list and broke on the resulting missing status, so ONE dropped poll
during a 10-minute summarize ended the watch early: chips stayed
disabled, the screen showed stale content, and only leaving to the
library and back (fresh openDetail fetch) displayed the finished
summary — the exact reported symptom.

Now only a successful fetch reporting a terminal state ends the loop;
transient failures keep polling for ~30s, and a genuinely-lost engine
says so in plain words instead of silently stranding the screen. A
missing job row on healthy fetches gets 3 grace cycles before giving
up (engine rewrites rows mid-requeue).
2026-09-18 21:11:58 -05:00
avi
70483b4cf8 Voice chips follow the saved report's voice in saved-report view
Opening a recording whose live summary row isn't loaded fell through
to sumTone (null = Neutral), so the pill showed Neutral while the
report text on screen read "*Voice: funny*". chipTone now reads the
report's own *Voice: X* metadata line as the fallback, so the chips
can never contradict the artifact being displayed. Running job and
live summary rows still take precedence. 3 new tests (reportVoice).
2026-09-18 19:58:42 -05:00
avi
1a6acd92d6 Honest processing pipeline panel: real stages, real percent, real elapsed
Replaces the animated bars that moved without meaning anything with a
stage panel wired to engine truth only:

- Upload: real bytes fraction (backend measures it).
- Transcription: real audio-time percent + ETA (15-95% window only;
  outside it the ETA is withheld as noise).
- Summary: stage markers only — the engine's summarize percent means
  "streaming" (thinking models hit 99 on the first token), so no pct
  and no ETA are shown for it, ever. "waiting for model" vs "writing
  in a dry, witty voice · streaming reply" vs "queued on the
  summarizer" come from the actual job row.
- Finalizing: engine done, app hasn't landed the fresh result yet.
- Header shows elapsed (and ETA where derivable) counting from the
  job's real started_at, parsed as server UTC.
- The line beside the voice chips mirrors the Summary stage so a
  clicked chip always gets visible feedback even if the panel is
  scrolled away.

Rows flip only when the engine actually moves a stage. No timers,
no decoration pretending to be progress. 6 new unit tests pin the
contract (65 frontend tests, 0 failures).
2026-09-18 19:46:17 -05:00
avi
8dc74a98a9 Export button at the top of the detail actions row too
The long saved report pushed the bottom Export out of sight. It now
sits beside Re-transcribe right under the voice chips, and stays at
the bottom for the end-of-document flow.
2026-09-18 18:45:50 -05:00
avi
b5113f4abc Detail, Settings, Engine, dialogs, and toasts on the design system (phase 4-5)
Every remaining screen routes through the shared primitives: Detail's
header nav (ghost links + quiet-red Delete), playback transport,
in-recording search field, voice PillChips, model picker and transcript
edit; Settings' folder row, summarizer PillChips, and model cards
(default model gets the teal ◉); Engine's start/retry; the folder
picker's breadcrumbs and rows; and both app-wide toasts are SonarCards.
Export becomes a real dialog: pick Markdown or ODT + any folder
(exportTo in DesktopState — the library's own .transcript.md is never
touched). Unused Material widget imports removed app-wide.

Verified live: library screenshot shows the reference language end to
end (glowing selected pills, hairline cards, teal status text);
59 frontend tests green.
2026-09-18 18:39:56 -05:00
avi
a3d16a6c18 Dashboard on the design system (redesign phase 3)
Library screen now uses the shared primitives: filled SonarField search
with leading magnifier, PillChip sort/show filters (selected pill gets
the teal glow), SonarCard rows with a "Your recordings — N of M"
section header per the reference, ghost icon row-actions, and the
quiet-red bulk Delete. Rename dialog rides the same field/buttons.
All handlers, selection, filtering, and batch logic untouched.
2026-09-18 18:17:03 -05:00
avi
5c0d1edbef Design system: Shonar tokens + shared UI primitives (redesign phase 2)
Tokens centralize the reference design language: near-black navy page,
one elevated surface, hairline borders, mint-teal accent reserved for
active/live/selected, soft glow only on the selected pill; radius,
4dp spacing scale, icon sizes, and the full type hierarchy (22/700
page title through 12/regular labels) all live in one place.

Components.kt adds the primitives every screen will use: SonarCard,
ShonarButton (filled mint primary), ShonarOutlinedButton,
ShonarGhostButton, ShonarDangerButton, PillChip (the reference's
selected-pill-with-glow), SonarField (filled dark input with teal
focus border), SectionHeader, HelperText. Material3 scheme derived
from the same tokens so leftover defaults inherit the language.
2026-09-18 18:13:09 -05:00
avi
4800364f09 Export report as .odt from the detail screen
renderOdt builds the minimal valid ODF package (STORED mimetype first,
content.xml, manifest) mirroring the markdown report's sections so the
two exports can't drift in content. The Export .odt button lives once
for both detail paths (live transcript or library-opened saved report)
and writes <name>.report.odt next to the audio.

Tests: 5 new (package shape, section parity, XML escaping +
well-formedness, neutral fallback, filename). Verified in LibreOffice:
headings, bullet lists, and escaped text render natively.
2026-09-18 17:54:34 -05:00
avi
d1a85bb6d0 Keep the clicked voice chip selected while its summarize job runs
Chips are disabled during a run, so selection followed the previous
summary's voice while the new job churned — Neutral grayed out next to
a highlighted sarcastic even though sarcastic was what was running.
Selection now prefers the running job's tone, then the live draft, then
the saved summary.
2026-09-18 17:46:25 -05:00
avi
f258470a6a Detail bar shows real percent + tone once progress > 0
With milestone-backed progress the number is truthful, so replace the
indeterminate 'working' bar with a determinate one labeled 'Summarizing
<tone>… N%' (indeterminate spinner only while waiting for the first token).
2026-09-18 15:00:45 -05:00
avi
54e1f90c22 Detail: upload+transcribe now polls the WHOLE pipeline (transcribe AND summarize) before leaving the live-update loop — breaking on transcribe alone left 'Summarizing… 0%' on screen after the summary actually finished (seen live on 'Ali Tall buildings': engine succeeded at 13:38:52, screen kept the stale job snapshot until reopen). Also clears the busy label when no job is active instead of leaving the last one. 2026-09-18 09:10:45 -05:00
avi
bcb32e4f01 Detail playback row: drop the redundant Pause/Resume pill and the duplicate Stop text button — one filled Stop button, always present while playing 2026-09-18 08:33:43 -05:00
avi
5d1f615d4a Snapshot: summary-notify compiles + SummaryNotifyTest (compile+tests green) 2026-09-17 22:23:35 -05:00
avi
add622d649 WIP snapshot: summary-ready toast + unseen badge (safety commit before power loss; sibling session actively editing — may not compile yet) 2026-09-17 22:17:59 -05:00
avi
3061629dc5 Summarize: LAN primary with local-Ollama rescue + in-app failure guidance
- Engine: when the primary summarizer is out of retries (or misconfigured),
  run_summarize now finishes the job on the configured rescue provider
  (SHONAR_LLM_FALLBACK_*), tags the summary with the provider that wrote it,
  and stores a human note in job.error; success clears stale notes.
- App (auto/lan): passes Ollama as the rescue provider when it is up.
- Detail screen: shows the rescue-swap note in plain words, a red
  'Summary failed' line with Settings -> Summarizer fix instructions and a
  Retry summary button on hard failure.
- Settings copy explains the fallback. 3 new pytest cases (14/14 pass);
  live E2E on 2026-09-18: sarcastic summary v6 via LAN on attempt 2.
2026-09-17 21:44:02 -05:00
avi
cb0492fb3e Fix stuck 'Summarizing… 0%': the 'already running — attach to it' branch in reprocessInPlace returned without polling, stranding the initial busy label on screen forever (comment promised live progress the code never did). Now it falls through to the same poll loop as a fresh job. Also makes every early-return clear the busy state. 2026-09-17 16:16:27 -05:00
avi
1eb6d55f4d Honest summarize progress: backend percent pins at 99 the moment tokens start (thinking models stream immediately) then holds there for minutes — so streaming now shows 'Summarizing… working · m:ss' with an indeterminate bar; '0% waiting for model' stays for the pre-token wait; 100 only on success. Transcribe keeps its real percentage. 2026-09-17 14:43:03 -05:00
avi
8586bc2476 Summarize always shows a percentage: the pre-token wait state read 'Summarizing… waiting for model' with no number; now 'Summarizing… 0% waiting for model' everywhere it appears (jobProgress, both detail labels, reprocess initial busy). Tests updated. 2026-09-17 14:26:16 -05:00
avi
50d5a0c982 Library filter chips: drop Done and Failed (user request — All and Untranscribed cover the actionable cases; filter logic untouched). 2026-09-17 14:12:36 -05:00
avi
26f7b273ce Never double-import a file into the engine: detail Transcribe bails when the pump already owns the file (in-flight check was absent there — mid-upload the mapping sidecar doesn't exist yet, so the upload path created a second server recording; happened live with Gambino Tahoe 11-50). Pump's transcribeFile reuses an existing mapping via /reprocess instead of uploading fresh (a pipeline killed mid-run no longer re-imports as a new recording on next pump). Purged the 11:08 orphan recording from the engine DB. 2026-09-17 13:38:01 -05:00
avi
1f688e79bc Voice chips directly under the summary (before the long sections): every position further down fell off-screen and read as 'missing'; saved-report path keeps them under the report; dedupe with the bottom block 2026-09-17 11:09:19 -05:00
avi
104aaa4d66 Detail/library UX pass from live-use feedback:
- Detail: pinned header (leave from anywhere), Prev/Next + Alt+arrows + Esc
- Summary above transcript with full sections (key points/decisions/action
  items/details) + Copy summary / Copy action items buttons
- Voice chips moved under the summary; one-page scroll below the header
- Stuck-at-99% fixed: detail re-fetches transcript+summary when a watched
  job finishes while the screen is open
- Playback: Pause/Resume + seek slider + position readout over mpv IPC
  (socket verified; controls hide if IPC unavailable)
- Library: sort Name/Newest/Oldest (persisted, date shown per row) and
  status filter All/Untranscribed/Done/Failed
- Delete is undoable: trash + 10s Undo toast replaces confirm dialogs
  (single + bulk); startup sweeps stale trash; engine purge on undo-window
  close
2026-09-17 10:00:03 -05:00
avi
8f7e1f9ea5 Detail: voice chip click re-summarizes immediately; separate Re-summarize button removed (redundant) 2026-09-16 17:28:42 -05:00