Commit Graph

300 Commits

Author SHA1 Message Date
53624d470b fix(docker): remove undefined-variable reconnect that broke all container status/start/stop/restart
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
_docker_get_json/_docker_post called sock.connect(sock_path) a second
time after _docker_connection() had already connected the socket --
sock_path was never defined in either function, so every call raised
NameError. Surfaced in the UI as every engine card reporting "Not
installed" and the Active OCR Engine model dropdown reporting "Not
Found" regardless of what was actually running, found while diagnosing
a user report of that dropdown. Docker socket API calls (list/status/
start/stop/restart) were affected for every engine card, not just OCR.

Added tests/test_docker_client.py, which did not exist before, to
catch this class of regression going forward.

Also records a request to make the magazine ingestion "watching it
work" panel draw detected region boxes live over the page image as
each is classified, instead of today's static page-image-plus-text-
list (TODO.md; no code change for that yet).

254 tests passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 18:14:22 +02:00
c81ae28da2 feat(magazine): add figure/chart/table description generation and spoken narration
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
Adds a "Describe" action per figure in article review: sends the cropped
image to the configured vision engine (with fallback) for a type-aware
one- or two-sentence description -- a chart gets a prompt about what it
measures and its trend, a table about headings and what stands out, etc.
Editable before approval. A new "Read figure descriptions" toggle in the
reading controls speaks these where they occur; purely additive, off by
default, and a figure with no description behaves exactly as before.

New core/magazine.py describe_visual_region()/_chain() reuse the same
reliability guards as page OCR (repetition-loop detection, sentence-
boundary truncation). New routes: POST .../images/{id}/describe
(background job), PATCH .../images/{id} (manual edit), descriptions=1 on
the article-text endpoint.

Live-verified end to end against the ingested c't KI Wissen 2026
magazine. Also found and documented a real, config-dependent limitation:
the currently-configured primary vision engine is transcription-only and
returns empty content for any descriptive prompt (confirmed with three
different prompts) -- not a code defect, but description generation
needs a genuinely caption-capable engine reachable as the fallback.

251 tests passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 17:39:57 +02:00
1a2ea421c4 fix(engines): resolve LiteLLM card's own key server-side instead of a stale global one
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
Connect and "Use as LLM" on the LiteLLM Gateway card now resolve that
card's own saved key (engine_api_keys[name]) server-side in
routes/docker.py, ahead of the old global llm_api_key fallback. The
browser can no longer reliably forward a saved key at all -- it's
masked to "__configured__" in every /api/settings response the moment
it's saved -- so relying on a frontend-forwarded header alone left
Connect silently using a stale global key after LiteLLM's own key was
rotated, surfacing a misleading /api/version 404 instead of the real
401.

"Use as LLM" now goes through a dedicated /use-as-llm endpoint that
saves the exact credential Connect just verified (llm_url normalized
to .../v1, llm_api_key copied from the card), rather than a
separately-typed value that could drift again.

Also documents per-user provider-key storage as an explicit item for
the deferred multi-user/identity planning pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 13:58:54 +02:00
4c6f41c831 docs: record work-order priority (Magazine, Character, Audiobook)
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
No code change. User set explicit priority for upcoming feature work:
Magazine reader roadmap first, then the character-identity system,
then audiobook speaker attribution. Recorded in TODO.md so it survives
a context reset. Deployment and the deferred multi-user initiative
stay explicitly parked, not part of this order.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 12:58:01 +02:00
4249382ac6 feat(deploy): stand up correspondence-nuc as a second instance
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
The NFS blocker from TODO.md's deployment entry is resolved (export
set up by the user on the DGX); picks up from there and completes the
deployment end to end.

- Mounted the DGX's TTS_Voices export read-write on the NUC at
  /mnt/dgx-tts-voices, persisted in /etc/fstab -- confirmed writable
  and confirmed the NUC instance sees the DGX's real voice library
  (344 voices), not a separate copy.
- Deployed by rsyncing the working tree from the DGX (excluding .git,
  config/, logs/ and other local-only paths) and building the image
  directly on the NUC, rather than pulling ghcr.io/martin-b78/... --
  that registry copy turned out to be both several versions stale and
  private with no credentials configured on the NUC, and the source
  was already right there to build from.
- Found and fixed a real gap while wiring it up: vision_ocr_url and
  vision_ocr_fallback_url default to host.docker.internal, which
  resolves to whichever host the container is running on -- correct
  on the DGX itself, silently wrong on the NUC (would have pointed at
  the NUC's own nonexistent OCR engine). Pinned both explicitly to the
  DGX's LAN IP via environment overrides.
- The container crash-looped on first start (sqlite3.OperationalError:
  unable to open database file) because docker compose had created
  config/ and logs/ root-owned under this session's root SSH access to
  the NUC, while the image's app user is uid 1000 -- chown -R 1000:1000
  fixed it.
- New docker-compose.nuc.yml, tracked here as the reviewable reference
  for that host (docker-compose.yml stays the DGX's own -- different
  build/network/socket-proxy shape). Deployed to the NUC as its own
  docker-compose.yml, since that host has no .git checkout to diff
  against, only the rsynced tree.

Verified: container stable (no restart loop), HTTP 200, settings
confirm all four engine URLs correctly resolve to the DGX, voice count
matches the DGX exactly, restart policy is unless-stopped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 10:06:55 +02:00
c91aaabe3a fix(mobile): retry failed script loads; fix 8 phone-width layout bugs
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
Two reports from the same mobile session tied together: a voice-library
crash ("fmtDbfs is not defined") and a save button reporting "No audio
ready" despite a file being correctly loaded and trimmed. Root cause
was the same in both -- the module loader fetched each JS file once
with no retry, so a single dropped request on a flaky connection left
that module's functions/variables undefined while every other module
kept executing anyway, producing confusing symptoms nowhere near the
real cause.

loader.js: _load() now retries up to 3 times with backoff before
rejecting. If a module still can't load after that, the app says so
plainly via a toast (or a plain DOM banner if even utils.js failed to
load) instead of silently revealing a partially-broken page. Wired into
both the core-module fallback path and ensureFeatureModules' lazy path.

Loading every section of the app at a 375px viewport and checking for
elements running past the viewport edge (all now 0) found eight real,
independent layout bugs, each with a distinct root cause:

- .section-head's mobile wrap rule targeted .section-header, a class
  nothing in the app uses -- dead since it was written. Every section's
  icon/title/action row (e.g. "+ New voice") ran off-screen unreachable.
- The synth-mode toggle and Clone-a-Voice source tabs' icon-only mobile
  rules matched a <span> wrapper the markup didn't have. Added it, plus
  aria-labels so hiding the text doesn't erase the accessible name.
- voice-design.css loads after style.css and re-declares
  .design-sample-grid unscoped -- style.css's mobile override always
  lost the cascade regardless of viewport. Moved the override to the
  file that actually wins.
- Three separate minmax(380-440px,1fr) engine-card grids couldn't fit
  one column under ~460px. Collapsed to 1fr with min-width:0 on the
  items -- the column fix alone wasn't enough, since a grid item's
  content-based min-width is still a floor even inside a 1fr track.
- The routing test row (450px+ minimum) and the conversation input bar
  (a single non-wrapping flex row for text field + mic + gate + three
  toggles) now wrap under 767px.
- Try It Out's EQ sliders shrank to fit beside their toggle instead of
  ever wrapping below it, overflowing their own squeezed container.
  Forced onto their own full-width line.

Verified with a Playwright sweep of all 16 sections (0 overflowing
elements, 0 console errors) plus the existing 225-test suite and the
frontend syntax gate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 17:09:24 +02:00
a78dac98fa fix(magazine): catch vision OCR decoding loops before they become page text
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
Confirmed live: re-ingesting c't KI Wissen 2026 with a local Qwen VL
engine, page 18's chart transcription degenerated into "das Produkt,
das Produkt, das Produkt, ..." repeated hundreds of times, filling the
whole response budget -- and was accepted as real region text, which
would have been narrated verbatim.

The codebase already had two narrow signatures for specific observed
failures (a formatting-instruction echo, a fabricated LaTeX loop), both
scoped to the plain-text fallback path only. Added a general detector
(_looks_like_repetition_loop): any short phrase containing a real word,
repeated 8+ times immediately back-to-back. Deliberately excludes
patterns with no letters -- a dot-leader contents line, a repeated
number sequence -- which are legitimate and must not be flagged.

Applied at both call sites: vision_ocr_page (the labelled-region path
most engines use) and _whole_page_region (the plain-text fallback).
Treated as a failed call in both, so vision_ocr_page_chain's existing
fallback (secondary engine, then tesseract) gets a real chance instead
of garbage being accepted as the page's transcription.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 10:09:32 +02:00
b458306ab9 fix(magazine): recover sparse charts, stabilize adjacent-figure snapping
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
Real failures found by re-inspecting a fresh re-ingest of c't KI Wissen
2026 after the previous figure-boundary-snapping release: a bar chart
was still truncated to roughly a third of its real height, and a
chart's top edge cut into an unrelated paragraph above it.

Root causes, each independently confirmed against the real page render
before fixing:

- A bar chart's bars/gridlines/axis labels rarely reach the "solid
  ink" density a photo does (measured 0.25-0.5 vs the 0.55 threshold),
  so the existing pass anchored only on the one fully-solid element
  (the zero-line) and never grew further. Added a second, looser pass
  that grows an anchor -- or the seed itself -- through any real mark
  at all, fenced by the same blocked cells.

- Anchor selection ("which solid component does the seed overlap
  most") was seed-size-dependent: a small seed touching only the
  zero-line anchored there, but re-running the SAME function on the
  box it had just produced -- now large enough to also touch a dense
  axis-label strip -- picked that instead and shrank an already-good
  box back down. Fixed by dilating the solid mask and labelling once,
  BEFORE any seed is considered, so which components count as one
  figure is a property of the page alone.

- A merged bounding box can enclose real text sitting between two
  components even though neither component's own cells touched it (a
  rectangle union, not the components' actual shape). Added
  _trim_blocked_edges() to pull each edge back to the nearest text
  wall found searching outward from the box's own centre.

- Two adjacent figures fencing each other's growth made each one's
  result depend on the other's CURRENT box -- for two dark photos
  sharing a caption strip, this never settled, swinging by over 100pt
  call after call. snap_visual_bboxes() now re-seeds every visual on a
  page from every other's last result for a bounded number of rounds,
  deciding same-graphic ambiguity once up front (fenced against the
  original seeds, before any iteration) so two disputed seeds can't
  mutually fence each other into a tidy-looking split instead of being
  recognised as the same disputed claim.

- A result is now only accepted once it reproduces itself when fed
  back through the same rules (IoU-checked), and the minimum
  seed/graphic overlap for repositioning a badly-offset box was
  lowered slightly -- it was narrowly rejecting some real cases.

Verified against the full 204-page/147-figure document: idempotency
holds except for one confirmed non-convergent pair, which now lands on
the same deterministic answer every run instead of continuing to
drift.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 21:21:04 +02:00
96b0b9dd71 fix(magazine): finish and harden the live ingest preview
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
Resumes an in-progress, uncommitted change from a previous session: the
live "page image + recognised text" preview shown during ingestion was
reworked to render formatted HTML (styled headings, a figure
placeholder) instead of a plain-text dump, but was left with a
duplication bug, dead code, inline styles, and no tests.

- Fixed duplication: a figure block's own transcribed text (usually its
  on-screen UI copy) was rendered as an ordinary paragraph in addition
  to the new figure placeholder box. Figure/chart/table/code blocks are
  now represented only by the placeholder.
- Furniture/sidebar/pullquote/ad text is shown muted instead of being
  dropped with no replacement, and is never promoted to a heading
  regardless of font size.
- Factored the formatting logic out of the progress closure into a
  standalone, unit-tested _render_ingest_preview_html().
- Replaced dead getattr(bbox, "y0", ...) calls (bbox is always a plain
  [x, y, w, h] list) with direct indexing, moved statistics/logging
  imports to module level, switched manual character replacement to
  html.escape(), and bounded the preview by an accumulated line budget
  instead of a blind slice that could truncate mid-tag.
- Replaced inline style="..." with CSS classes matching the panel's
  existing dark console treatment.
- The frontend now always uses innerHTML (every text node is escaped
  server-side), dropping the startsWith('<') content-type sniffing.
- Marked the preview panel aria-hidden -- decorative, and would
  otherwise churn noisily for screen reader users on top of the
  adjacent progress bar.

Verified against the real c't KI Wissen 2026 layout: a figure's raw
OCR text no longer appears twice, a real headline renders as <h3>, a
footer/pull-quote render muted, and all text is correctly escaped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 15:24:16 +02:00
8e74060394 docs: scope magazine contents-view redesign and legal source browsing
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
No code change. Added to TODO.md:

- Magazine contents view (#s-magazine): logged three candidate layouts
  as open options rather than picking one -- three-column page/text/
  figures, synthesis-state-and-inline-play (the recommended quick win
  under any layout), and a flipbook with content columns underneath.
  Needs a decision before implementation.

- Browsing free PDF magazine sources: the two sites given as examples
  (freemagazines.top, pdf-magazines.org) redistribute current commercial
  titles without licence, so no scraper was built against them. Scoped
  instead against OPDS catalogues, archive.org's public-domain
  periodicals, publisher-run free issues, and open-access journals.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 10:25:12 +02:00
55f147d7a7 fix(magazine): derive figure boundaries from the page render
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
A vision OCR engine grounds a figure far less precisely than it
transcribes one. Over all 138 figure regions of a 204-page issue the
transcribed contents were right while the boxes were routinely offset,
undersized, or sitting on the neighbouring text column -- page 23's card
grid was reported at x 0.24-0.54 where the graphic runs 0.41-0.95.

snap_visual_bboxes() reduces the page render to an ink-coverage grid,
seeds a connected-component search with the engine's box, and re-joins
tiles a gutter apart so a card grid comes back as one figure. Text
blocks are excluded from the mask, which is what stops a figure
swallowing the column beside it. A candidate must be solidly inked or
framed on nearly all of its rows and columns (admitting a white-interior
screenshot), may not shrink below 30% of the seed, and reverts when two
figures land on the same graphic; anything else keeps the box it had.

Runs before the figure crop in both OCR paths, so the images shown
during playback are framed correctly too. Replaces refine_picture_
bounds(), which only recognised coloured panels and had no text fence.

Also adds "Fix figure boundaries" in review and POST
/api/magazine/{id}/figures/refine behind it, so an issue ingested before
this pass can be corrected without re-running OCR. Verified live against
c't KI Wissen 2026: 78 of 138 boxes corrected, the rest declined.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 10:17:00 +02:00
c1a2d6dc74 Release v1.26.14: Studio-style side-by-side Magazine upload layout with Durchsuchen button
Some checks failed
Test and SonarQube / quality (push) Has been cancelled
2026-09-10 14:45:00 +02:00
30e01a5cec Release v1.26.13: Fix drag-and-drop file opening in tab & improve magazine dropzone reliability 2026-09-10 14:31:16 +02:00
11142842c2 Release v1.26.12: Fix page-wide drag-and-drop interception & magOnShow navigation hook
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-10 13:22:26 +02:00
f5f887bb1d Release v1.26.11: Enhanced layout-aware OCR element recognition, grounding parsing, and preview thumbnails
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-10 13:08:53 +02:00
29dc913ed1 Release v1.26.10
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-10 12:50:33 +02:00
e6211f1333 Release v1.26.9
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-10 12:45:42 +02:00
9b1e29bb79 feat: automate semantic magazine layout
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 17:48:56 +02:00
f8af18e8f7 fix: release current magazine UI assets
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 17:40:25 +02:00
31b13a4e16 feat: improve magazine review navigation
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 17:36:27 +02:00
c1d86ec13d fix: return All magazines to library
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 17:14:27 +02:00
272d4872bd fix: use native Unlimited-OCR layout regions
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 13:27:14 +02:00
037f3dd74d fix: stabilize OCR and character sheet casting
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 07:51:59 +02:00
ff827a74bd fix: recover character sheet requests from gateway 405
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 06:49:52 +02:00
b7aade39db Release v1.26.1
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-08 23:39:44 +02:00
1a06a8bf8c Release v1.26.0
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-07 19:05:10 +02:00
6553d5003d fix: preserve custom OCR engine cards 2026-09-05 08:11:57 +02:00
60ef436ce1 feat: add OCR engine profiles 2026-09-04 22:29:11 +02:00
2caa417401 fix: preserve audiobook dialogue boundaries 2026-09-04 15:36:26 +02:00
29f5243f6b test: preserve dialogue-only recast merges 2026-09-04 15:31:45 +02:00
b6f4a9518b test: lock visual identity prompts 2026-09-04 15:30:39 +02:00
8181c491c0 docs: complete frontend namespace migration 2026-09-04 13:43:36 +02:00
18e586d06f refactor: scope conversation state 2026-09-04 13:42:02 +02:00
417d7215d2 refactor: scope i18n state 2026-09-04 13:40:07 +02:00
ee4208ec51 refactor: scope cast compatibility modules 2026-09-04 13:38:40 +02:00
542f44fbb8 refactor: scope seed finder 2026-09-04 13:36:55 +02:00
10f618c179 refactor: scope character columns 2026-09-04 13:35:10 +02:00
d2a8dd43b4 perf: tighten character pagination 2026-09-04 13:31:18 +02:00
7938f9ca74 refactor: scope frontend utilities 2026-09-04 13:27:43 +02:00
d055596504 refactor: scope voice inspector 2026-09-04 13:25:13 +02:00
67ee55d3ac refactor: scope character library 2026-09-04 13:23:54 +02:00
73f8f92826 refactor: scope character sheets 2026-09-04 13:21:31 +02:00
e5bd2a149f refactor: scope voice library 2026-09-04 13:19:52 +02:00
1b7d6b2346 refactor: scope generation module 2026-09-04 13:15:30 +02:00
ababdf6741 refactor: scope stt module 2026-09-04 13:13:10 +02:00
c53c47ce2d refactor: scope ai backends 2026-09-04 13:10:51 +02:00
716e601fac refactor: scope integrations module 2026-09-04 13:06:59 +02:00
3fffae9015 refactor: scope combined library 2026-09-04 13:04:46 +02:00
260c5c22a7 refactor: scope engine support 2026-09-04 13:02:29 +02:00
b747553bf6 refactor: scope routing module 2026-09-04 13:00:10 +02:00
28c72c76dd refactor: scope voice sources 2026-09-04 12:57:53 +02:00
d219aec676 refactor: scope voice clone 2026-09-04 12:55:50 +02:00
998a65f2ba refactor: scope tts preview 2026-09-04 12:53:44 +02:00
656d0473d4 refactor: scope settings module 2026-09-04 12:51:29 +02:00
8002ca1d75 refactor: scope character library 2026-09-04 12:49:22 +02:00
0e615d52de refactor: scope studio module 2026-09-04 12:47:30 +02:00
ac55cd88a3 refactor: scope rehearser cast 2026-09-04 12:44:58 +02:00
df95ada578 refactor: scope rehearser voice design 2026-09-04 12:44:08 +02:00
0f3d410260 refactor: scope rehearser voice picker 2026-09-04 12:43:11 +02:00
dea1de5c57 refactor: scope rehearser stage 2026-09-04 12:42:22 +02:00
da3957347c refactor: scope audiobook network 2026-09-04 12:41:35 +02:00
7ee5f65e00 refactor: scope rehearser library 2026-09-04 12:40:56 +02:00
0ee43421b7 refactor: scope reader module 2026-09-04 12:40:09 +02:00
66c5dd6e0c refactor: scope audiobook core 2026-09-04 12:37:37 +02:00
c6db55e55b refactor: scope rehearser core 2026-09-04 12:33:55 +02:00
6c62a0f75e refactor: scope rehearser recording 2026-09-04 12:31:03 +02:00
3a7f0bf677 refactor: scope rehearser io 2026-09-04 12:30:13 +02:00
a202230654 refactor: scope rehearser playback 2026-09-04 12:29:19 +02:00
cc4fc4ae2e refactor: scope audiobook state modules 2026-09-04 12:26:21 +02:00
302dbe1279 refactor: scope pure frontend leaves 2026-09-04 12:20:33 +02:00
d32985256d docs: close incremental p1 audit 2026-09-04 12:14:47 +02:00
cefd4bd494 refactor: extract rehearser training mode 2026-09-04 12:12:11 +02:00
0c7cc04643 refactor: extract rehearser recording 2026-09-04 12:10:00 +02:00
ba7b3115b9 refactor: extract audiobook preview export 2026-09-04 12:05:50 +02:00
76421dfe88 refactor: extract audiobook casting pipeline 2026-09-04 12:04:05 +02:00
eaf2f29d62 refactor: extract rehearser io 2026-09-04 12:01:38 +02:00
f97fd60e43 refactor: extract rehearser playback 2026-09-04 11:59:09 +02:00
e096b3e529 refactor: wire audiobook namespace APIs 2026-09-04 11:54:56 +02:00
6f2c0b7b3d refactor: extract audiobook cast view 2026-09-04 11:53:04 +02:00
262e37fffd refactor: extract rehearser voice matching 2026-09-04 11:47:51 +02:00
4c8022e8aa refactor: extract audiobook stylesheet 2026-09-04 11:44:55 +02:00
065445a20a refactor: extract rehearser stylesheet 2026-09-04 11:42:43 +02:00
eb008475ed refactor: extract rehearser cast renderer 2026-09-04 11:39:45 +02:00
ae9df4f7f0 feat: distinguish provider configuration and health 2026-09-04 11:35:52 +02:00
7c14f41cb9 refactor: extract audiobook speaker normalization 2026-09-04 11:32:20 +02:00
0d1299b19c refactor: extract audiobook cast cleanup 2026-09-04 11:30:12 +02:00
1951ce0646 refactor: extract audiobook dialogue merging 2026-09-04 11:27:49 +02:00
41349867a1 refactor: extract audiobook cast normalization 2026-09-04 11:25:52 +02:00
0876527554 refactor: extract audiobook quote segmentation 2026-09-04 11:22:15 +02:00
2fde0a176d refactor: extract rehearser voice design 2026-09-04 11:19:01 +02:00
d43959728d refactor: split voice design styles 2026-09-04 11:16:05 +02:00
de099d30c5 refactor: split background job styles 2026-09-04 11:13:36 +02:00
8d9a172993 refactor: split resource diagnostics styles 2026-09-04 11:10:43 +02:00
c0ac5dd775 refactor: extract audiobook cast serialization 2026-09-04 11:08:49 +02:00
d3bc806f33 refactor: extract rehearser training comparison 2026-09-04 11:06:23 +02:00
d613591526 refactor: extract rehearser cast controls 2026-09-04 11:04:13 +02:00
ed3b2d42cd refactor: publish feature APIs through TTVC 2026-09-04 11:00:22 +02:00
e2e20fa89f feat: add request correlation diagnostics 2026-09-04 10:58:46 +02:00
66ebd1a8b8 refactor: extract audiobook parsing primitives 2026-09-04 10:56:31 +02:00
0bfb6d694b refactor: extract rehearser voice picker 2026-09-04 10:54:21 +02:00