Commit graph

101 commits

Author SHA1 Message Date
avi
36292beb16 Checkpoint: Shonar Desktop state 2026-09-23 19:17:13 -05:00
avi
6e34161828 make-deb.py: rebuild the Debian package from the current compose distributable on any host (stdlib-only ar/tar writer, GNU tar format for dpkg)
The distributable's host-compiled pieces (launcher bin, libapplauncher.so, jlink runtime) are linked against Arch's glibc 2.38/libstdc++ — bookworm has 2.36/3.4.30, so a straight repackage silently exits 1 at launch. dist/bookworm-compat/ holds bookworm-built replacements (launcher-bin, libapplauncher.so, runtime/) extracted once from the known-good 0.1.0-1 deb; make-deb.py injects them into the tar stream without touching the build tree. Verified in a bookworm container: apt install -> ii, zero unresolved libs, app stays up 30s under Xvfb, build 6efec57.
2026-09-23 16:23:10 -05:00
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
205342bcbc update-app.sh: authenticated pull via ~/.config/shonar-desktop/forgejo.token (chmod 600, outside the repo) — one-click in-app update works without interactive credentials; anonymous pull kept as fallback when no token file 2026-09-23 14:56:04 -05:00
avi
028724ff4d update-app.sh: last-resort kill by exact process name (pgrep -x) — the loose -f 'shonar-desktop' pattern still caught invoker shells outside the script's own ppid chain (persistent command daemons whose cmdline quotes the script path) 2026-09-22 12:30:37 -05:00
avi
c948ca7367 update-app.sh: kill by pattern EXCLUDING own process lineage — pkill -f matched any ancestor shell mentioning the script path, so an update run could SIGTERM its own invoker; app-kill now walks the ppid chain and spares it 2026-09-22 12:24:26 -05:00
avi
668ecbefd0 update-app.sh: also kill the packaged binary (…/bin/shonar-desktop), not just java MainKt — without it a packaged run survived the update and the relaunch left a twin window 2026-09-22 12:20:55 -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
b4e335d720 Model registry: Large v3 speed label corrected to measured ~0.3-0.4x real-time (CPU int8 quiet-machine benchmark Sep 22: 0.27x on 300s, 0.43x on 1200s; prior '~1x' was optimistic) 2026-09-22 10:38:34 -05:00
avi
89a882cb55 STATUS: close both Sep-16 open items — pump-error.log already gone; Large v3 quiet-CPU benchmark measured (0.27x RTF on 300s, 0.43x on 1200s, CPU int8, beam_size=5, engine args) 2026-09-22 10:00:32 -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
514d75fe92 Raw provider crash fails the job instead of escaping into the sweep requeue loop
A non-AIError from transcribe/summarize (av.InvalidDataError on corrupt
audio, seen live with 'My recording 63') escaped the consumer, left the
row 'running', and got requeued at every engine restart forever. Both
runners now catch it, fail the row with the exception type in the
message, and keep a usable transcript (summarize completes with a
'Summary failed' note). Regression tests pin both paths (proven red
without the fix).
2026-09-19 15:24:50 -05:00
avi
15381b2c41 Commit upload session row before responding; client's follow-up GET raced the post-response commit into a 404 2026-09-18 23:17:04 -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
11bb13cb3d Fix re-summarize running one voice behind: commit the job row before poking the inline queue.
The reprocess endpoint poked transport_enqueue while its request session
was still uncommitted; the inline worker woke instantly, read the job row
with its own session, and summarized with the PREVIOUS click's tone
(TRACE: click Funny -> worker read 'dry, witty'; click Neutral -> ran
'funny'). The UI disabled the voice chips for the whole wrong-voice run,
which read as 'spinner does nothing until I leave and come back'.

Rows are the real queue: commit before the poke. Regression test pins
worker tone to the requested tone and Neutral clearing a prior voice;
verified red without the fix, green with it.
2026-09-18 17:03:37 -05:00
avi
39aaa04a00 Honest summarize progress from real completed work units
Backend: new ContractProgress parses the streamed summary JSON and
advances only when contract units finish — first token 5%, each closed
key 5→90 (arrays step per closed item), root close 95, stored=100.
Replaces the char-count ticker that counted reasoning chars against a
guessed 1200-char output and sat frozen at 99 for minutes. No timers,
no elapsed-time estimates; the unfinishable thinking span honestly
earns only the alive-tick. Both adapters (openai_compat, ollama) feed
the accumulated content prefix. /jobs now carries the job's tone.

App: summarize progress shows a real percentage + determinate bar with
the tone named ('Summarizing dry wit… 47% · 1:12') in both detail
widgets; indeterminate only while queued or pre-first-token.

Tests: milestone sequence verified identical for char-by-char and
chunked streaming; adapter thinking-phase test updated to the new
contract (83 passed).
2026-09-18 15:42:16 -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
61f2124b1c AI progress: honest milestone bar from closed JSON contract units
Replace the char-count estimate (EST_OUTPUT_CHARS) with ContractProgress:
points are earned only when real output units complete — first token (5),
each closed contract key / array item (5-90), root closed (95), stored (100).
Reasoning/thinking streams no longer fake progress: the first thinking
delta fires the first-token milestone ('the model is alive') and holds.
Surface the job's tone through ProcessingJobOut/JobInfo so the bar can
label itself 'Summarizing dry wit…'. Test rewritten to pin the new
honest-thinking semantics.
2026-09-18 15:00:04 -05:00