Commit Graph
735 Commits
Author SHA1 Message Date
jamiepineandcapy-ai-staging[bot] fc0bec8faf fix: force seroval 1.5.3 via bun overrides so @tanstack/router-core picks it up
Adding seroval as a direct root dependency left the lockfile's
@tanstack/router-core/seroval entry pinned at the vulnerable 1.5.0, so the
only real consumer was still bundling the CVE-2026-59940 version. A bun
'overrides' entry forces every resolution to 1.5.3 and drops the unused
direct dependency.
2026-10-04 00:00:47 +00:00
anupammeandcapy-ai-staging[bot] 84900d4e86 fix: CVE-2026-59940 security vulnerability
Automated dependency upgrade by OrbisAI Security
2026-10-04 00:00:47 +00:00
jamiepineandcapy-ai-staging[bot] dd1ada8239 fix(db): log which indexes the migration actually created 2026-10-04 00:00:41 +00:00
jamiepineandcapy-ai-staging[bot] 22a3f6b3dd fix(db): drop unused Index import 2026-10-04 00:00:41 +00:00
Will Andersonandcapy-ai-staging[bot] b97ffbfba9 Add indexes on high-traffic foreign keys and sort columns
Queries throughout the codebase filter on generations.profile_id,
generations.status, generations.created_at, story_items.story_id,
story_items.generation_id, generation_versions.generation_id, and
profile_samples.profile_id with every request. Without indexes SQLite
falls back to a full table scan; as history grows (hundreds or thousands
of generations) these scans become the dominant latency.

Changes:
- Add index=True on the most-queried FK and sort columns in models.py so
  new installs get them from Base.metadata.create_all
- Add _migrate_add_indexes() called from run_migrations() so existing
  installs get the same indexes on next startup (uses CREATE INDEX IF
  NOT EXISTS — idempotent, <10 ms on any realistic dataset)
2026-10-04 00:00:41 +00:00
jamiepineandcapy-ai-staging[bot] ccc092bad0 perf(db): set the SQLite pragmas from a connect hook and clear WAL sidecars on db reset 2026-10-04 00:00:35 +00:00
Will Andersonandcapy-ai-staging[bot] 36bb6c1ad7 Enable SQLite WAL journal mode and 5 s busy timeout
Switch from the default DELETE/ROLLBACK journal to WAL so concurrent
readers (SSE status polls, history queries) are not blocked while the
generation worker holds a write transaction.  Set a 5-second busy
timeout to eliminate "database is locked" errors under brief write
contention.

Both PRAGMAs are applied via a custom creator function so every
connection in the pool gets the settings at open time, not just the
first one.
2026-10-04 00:00:35 +00:00
jamiepineandcapy-ai-staging[bot] e86a2dcaa5 fix(cache): share the orphaned-.incomplete check with /models/status 2026-10-04 00:00:28 +00:00
Alejandro Gaston Alvarezandcapy-ai-staging[bot] f62aff0809 fix(cache): don't treat orphaned .incomplete blobs as an in-progress download
is_model_cached() marked a model as not-cached whenever any .incomplete
blob existed in its cache dir, even when a completed blob with the same
hash already sat next to it. A retried/concurrent download can leave
this orphan behind after the real transfer already finished, which made
the model appear perpetually "downloading" and re-trigger a full
re-download on every load.

Only .incomplete files with no matching completed blob now count as a
genuinely in-progress download.
2026-10-04 00:00:28 +00:00
jamiepineandcapy-ai-staging[bot] 6ad47dda89 test(speak): build the MCP test server from fastmcp, the package production imports 2026-10-04 00:00:22 +00:00
jamiepineandcapy-ai-staging[bot] 1a3942f3b3 style: ruff-format the new speak language tests 2026-10-04 00:00:22 +00:00
c339d2c324 fix(speak): honour the voice profile's language instead of forcing English
Both speak surfaces built their GenerationRequest with a hardcoded "en"
fallback and never consulted the resolved profile, so a profile created
with language="fr" was still synthesised as English unless the caller
passed language= explicitly.

This hurts the MCP path most: an agent calling voicebox.speak has no way
to know the bound profile's language, so it cannot pass the argument
either. Every agent-triggered generation on a non-English profile came
out with an English accent.

The fallback chain is now explicit argument -> resolved profile's
language -> "en", which matches how engine and personality already
consult the resolved binding. The "en" backstop is kept so profiles with
no language set behave exactly as before.

Adds backend/tests/test_speak_language.py covering both surfaces: the
fallback, explicit-argument precedence, and the unchanged "en" default.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-10-04 00:00:22 +00:00
jamiepineandcapy-ai-staging[bot] e440b44ae7 fix(ui): keep a first line that fits whole instead of re-cutting it at a sentence end 2026-10-04 00:00:15 +00:00
jamiepineandcapy-ai-staging[bot] 872121c0f7 style: apply biome import order and formatting to useGenerationProgress 2026-10-04 00:00:15 +00:00
8086c818b6 refactor(ui): name the head budget and the truncation suffix
Addresses the budget comment on #1058 by the second route the review
offered -- defining the constant as the head budget rather than reserving
the suffix inside it.

TOAST_ERROR_BUDGET read as though it bounded `display`, but `display` is
the head plus " …", so it could be 402. Renamed to HEAD_BUDGET and
documented as bounding the message rather than the rendered string, with
the suffix now a named constant instead of a literal in the template.

Reserving the two characters was the alternative, but nothing downstream
has a hard limit -- the description box scrolls -- so it would have
shortened the message to satisfy a round number.

Verified: head <= 400 and display <= 402 on every truncating input,
including no-space text, a short first line, sentence-boundary backoff
and the real 4795-char error, with the untouched-when-not-truncated and
exact-`full` invariants still holding.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-10-04 00:00:15 +00:00
e3b9c98977 fix(ui): return untruncated errors exactly as received
Follow-up to discussion_r3831361300 on #1058.

Both non-truncated return paths handed back the trimmed working copy, so
an error like "  request timed out\n" came back altered even though
nothing had been omitted. That contradicted the stated intent that short
errors pass through untouched, and left `display` differing from `full`
for no reason.

`display` is now byte-identical to `full` whenever `truncated` is false —
the trimmed copy is only used for measuring against the budget and for
building the shortened head. Documented on the field.

Verified across padded short errors, clean short errors, empty and
whitespace-only input, and either side of the threshold: display === full
on every untruncated case, and `full` matches the input exactly in all of
them.

One visible consequence: with whitespace-pre-wrap on the description, an
error carrying leading or trailing newlines now renders with that blank
space. Trivial for the messages this sees in practice, and the
alternative was silently editing the text.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-10-04 00:00:15 +00:00
96c6f9cad9 fix(ui): preserve the original error text and handle clipboard failures
Two CodeRabbit findings on #1058.

condenseError trimmed the input before storing it in `full`, which is
documented as the untouched original and is what the Copy action hands
over. The trim now applies only to the working copy used for measuring
and cutting, so `full` is byte-for-byte what the server sent while
`display` and `omitted` still ignore surrounding blank space.

The Copy handler called navigator.clipboard.writeText with no guard.
Outside a secure context the property access itself throws, and
writeText rejects when permission is denied; neither was handled, so a
click could become an unhandled rejection with no sign that nothing was
copied. Both paths are now caught and reported, pointing at Settings ->
Logs as the fallback.

Not taken: aligning MIN_TO_CONDENSE with the 400-char budget. The gap is
deliberate -- cutting a 450-char error to 400 saves 50 characters in a
description that already scrolls, and no Copy action is needed there
because `display` holds the whole message. Documented in place.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-10-04 00:00:15 +00:00
9ba2b33069 fix(ui): make long error toasts readable and copyable
A failed generation put the server's error straight into a toast. The
transformers "Unrecognized model" error is ~4.8KB, of which the first
sentence carries the meaning and the remaining 4.7KB is an alphabetical
list of every architecture it knows. In a 420px toast with
overflow-hidden and no scroll, that clipped the text at both ends and
pushed the close button off-screen: unreadable and undismissable.

- ToastDescription is capped at 40vh and scrolls, wraps on whitespace
  and breaks long unspaced tokens so a path cannot widen the toast.
- The toast aligns to the start rather than centring, so the title
  stays visible next to a tall description.
- condenseError() keeps the head of an oversized error, cutting at the
  first newline or the last sentence end inside a 400-char budget, and
  reports how many characters it dropped. Short errors pass through
  untouched.
- When it does truncate, the toast offers a Copy action for the full
  text and points at Settings -> Logs.

Verified against the real 4795-char error: 4795 -> 400 chars keeping
both meaningful sentences.

The hook moves to .tsx to render ToastAction, matching useAutoUpdater.tsx
which is a .tsx hook for the same reason. createElement was tried first
but this repo's ToastActionElement type is the older shadcn definition
(ReactElement<typeof ToastAction>) which only accepts JSX-constructed
elements.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-10-04 00:00:15 +00:00
devangkanthariaandcapy-ai-staging[bot] ca56137ca0 fix(settings): invalidate capture-readiness on auto_refine toggle (#753) 2026-10-04 00:00:08 +00:00
devangkanthariaandcapy-ai-staging[bot] 0e1e9922a8 fix(dictation): skip LLM readiness gate when auto_refine is off (#753)
When the user disables auto_refine (LLM polish) in Settings, the Qwen
refinement model is no longer required for dictation to arm. Previously,
canRecord checked llmReady unconditionally, so useChordSync called
disable_hotkey whenever Qwen was not downloaded -- even if the user
never intended to use refinement.

Changes:
- useDictationReadiness: gate llmReady behind autoRefine in canRecord,
  missing, and the polling predicates so hotkeys arm with just Whisper
  STT when refinement is off.
- DictationReadinessChecklist: hide the LLM row when autoRefine is
  false -- the checklist now shows only Whisper STT.

Closes #753
2026-10-04 00:00:08 +00:00
jamiepineandcapy-ai-staging[bot] 4a03cd7e77 fix(backend): log when VOICEBOX_FORCE_CPU forces the CPU device
Without a trace the Settings GPU label and /health keep reporting CUDA
while generation runs on CPU, so the user has no way to confirm the
override engaged.
2026-10-04 00:00:03 +00:00
Ousama Ben Younesandcapy-ai-staging[bot] 8b8c1429db fix(backend): honour VOICEBOX_FORCE_CPU in device selection
The override is documented in gpu-acceleration.mdx and listed as step 1 of
the get_torch_device() precedence in tts-generation.mdx, but grepping the
tree for VOICEBOX_FORCE_CPU matched only those two doc files - nothing read
it. Users whose GPU has no compiled kernels in the bundled PyTorch had no
way to fall back to CPU short of renaming the installed CUDA backend
directory.

Resolve it before torch is imported, so it still works when the installed
build is itself the reason CPU is wanted.
2026-10-04 00:00:03 +00:00
youtsuhoandcapy-ai-staging[bot] 029d4d3378 fix(backend): batch generation-version queries to eliminate N+1 in history listing
list_generations() fetched versions with one SELECT per generation on the
page (50 rows -> 51 queries). Add _get_versions_for_generations() which
loads all versions for the page in a single WHERE generation_id IN (...)
query and groups them in memory; the single-generation helper now
delegates to it so story item details behave identically.

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <[email protected]>
2026-10-03 23:59:56 +00:00
jamiepineandcapy-ai-staging[bot] 214adc5ff2 fix: keep the active tag row in view and document the full tag set
The 19-row menu is taller than its 280px max-height, so arrow-key
navigation past the fold lost its highlight. Scroll the active row into
view on index change, list the delivery tags in the README, and give
[sarcastic] and [whispering] emoji that are not already used by
[chuckle] and [shush].
2026-10-03 23:59:50 +00:00
jamiepineandcapy-ai-staging[bot] 50d8d6a34f style: wrap TAG_REGEX to satisfy biome formatter 2026-10-03 23:59:50 +00:00
Margalitandcapy-ai-staging[bot] 4cb8ad3b1f Add 10 missing paralinguistic delivery tags to ParalinguisticInput 2026-10-03 23:59:50 +00:00
jamiepineandcapy-ai-staging[bot] c9f5f2c3e7 fix(server): route writelines through the pipe-safe write
writelines() was forwarded straight to the wrapped stream by __getattr__,
so it could still raise BrokenPipeError after the app's pipe closed.
2026-10-03 23:59:43 +00:00
848bb2e4c6 fix(server): stop failing requests after the app's stdout pipe closes
When the server outlives the Tauri app that spawned it (keep-running
mode, or a sidecar the next launch reuses), its stdout/stderr pipe has
no reader. Every later print()/tqdm write raises BrokenPipeError, so
POST /captures and /transcribe return "[Errno 32] Broken pipe".

Wrap stdout/stderr so they fall back to devnull on the first failed
write instead of raising.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-03 23:59:43 +00:00
Nikhil Jangidandcapy-ai-staging[bot] f5d63540a6 docs: tidy changelog spacing
Keep the unreleased changelog formatting consistent.
2026-10-03 23:59:33 +00:00
Nikhil Jangidandcapy-ai-staging[bot] f2271ae845 docs: note profile engine default fix
Document the generation engine precedence correction.
2026-10-03 23:59:33 +00:00
Nikhil Jangidandcapy-ai-staging[bot] cdeeb38be4 test: cover generation engine defaults
Verify omitted engines remain unset while explicit values are validated.
2026-10-03 23:59:33 +00:00
Nikhil Jangidandcapy-ai-staging[bot] d4face494f fix: honor profile engine for generation requests
Allow omitted engines to fall through to the profile default.
2026-10-03 23:59:33 +00:00
jamiepine 0526e5ba36 docs(offline-guard): state exactly which cache checks were extended 2026-10-03 09:41:49 +00:00
jamiepine 865c324ff6 fix(offline-guard): make every cache check cover the files the forced-offline load reads
The load body now runs with HF_HUB_OFFLINE forced when _is_model_cached()
reports True, so a snapshot that holds the weights but not the small files
the loader also opens would fail hard instead of fetching them. Verified
against chatterbox-tts 0.1.7 (mtl_tts.py allow_patterns, tts_turbo.py
from_local) and the TADA loader's unsloth/Llama-3.2-1B tokenizer download;
list those files in the required_files checks. Also note in the changelog
why the 0.4.5 removal of this guard no longer applies.
2026-10-03 09:35:49 +00:00
jamiepine d7f5ba5843 fix(setup): on Windows, only look for a system Python when the venv must be built; add uv fallback 2026-10-03 09:34:44 +00:00
jamiepine e85ef65ff1 docs: update developer snippets to datetime.now(UTC) 2026-10-03 09:32:18 +00:00
jamiepine 3741a69cd2 fix: convert the utcnow() calls added to CloudSettings since the PR was opened 2026-10-03 09:26:19 +00:00
Will Andersonandjamiepine 5b79892a17 fix: import UTC in import_generation_from_zip function scope
The function-local import was missing UTC, causing NameError at runtime
when datetime.now(UTC) was called.
2026-10-03 09:26:05 +00:00
Will Andersonandjamiepine 851defa859 Replace deprecated datetime.utcnow() with datetime.now(UTC) throughout
Python 3.12 deprecates datetime.utcnow() with a DeprecationWarning and
it will be removed in a future release. Replace all call-site usages in
services, routes, and utils with datetime.now(UTC), and replace the
SQLAlchemy ORM column defaults (which used the bare function reference
datetime.utcnow) with lambda: datetime.now(UTC) so that the returned
objects are timezone-aware and consistent with Python best practice.

Affected: database/models.py, services/{stories,history,profiles,
channels,export_import}.py, routes/{profiles,tasks}.py, utils/tasks.py
2026-10-03 09:26:05 +00:00
jamiepine 1b8960ff77 docs(changelog): separate the new Developer Experience section from Linux 2026-10-03 09:26:04 +00:00
dhananjaypai08andjamiepine a4a47dd97a fix: setup-python now uses invoke-pip 2026-10-03 09:24:23 +00:00
dhananjaypai08andjamiepine 41db2ab446 validation and install uses python commands 2026-10-03 09:24:23 +00:00
dhananjaypai08andjamiepine a14f0ce3d9 fix(setup): pin dev venv to Python 3.12 2026-10-03 09:24:23 +00:00
Roman Dolgovandjamiepine 32ba50cdd2 test(offline-guard): run the opposite-mode nesting check in a subprocess, not a thread 2026-10-03 09:18:41 +00:00
Roman Dolgovandjamiepine 291a4d8b97 fix(offline-guard): raise instead of deadlocking when a thread nests opposite modes 2026-10-03 09:18:41 +00:00
Roman Dolgovandjamiepine e3b5e9b258 fix(offline-guard): keep uncached loads from inheriting a concurrent cached load's offline window 2026-10-03 09:18:41 +00:00
Roman Dolgovandjamiepine 0517c0687a Fix infinite HF retry storm when loading a cached model offline
model_load_progress() already received is_cached but never forwarded it
to force_offline_if_cached(), which is fully unit-tested but had zero
callers. A fully-cached model still resolved every config file against
huggingface.co, eating the default 5-retry backoff per file offline.
2026-10-03 09:18:41 +00:00
Alex SummerandGitHub 51f49dea19 fix(docs): update quick start guide to reflect correct terminology for voice profiles (#963) 2026-07-26 23:32:02 -07:00
80610d880e fix(ui): open FloatingGenerateBox selects upward to prevent clipping (fixes #928) (#936)
The floating generate box is fixed at the bottom of the viewport, so
all of its Select dropdowns (voice profile, language, engine, effects)
opened downward into — or beyond — the window edge. Add side="top" to
each SelectContent so the menus appear above their trigger instead.

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
2026-07-26 23:31:59 -07:00
Sai Sridhar TarraandGitHub 397051ba44 fix(key_codes): add Function key arm so macOS fn can be bound to a chord (#950)
key_from_str() had no arm for "Function", so it fell through to
None. Since build_chord propagates that as a hard Err via ?, binding
any chord containing fn made build_chord_bindings fail entirely —
HotkeyMonitor was never spawned, silently killing both push-to-talk
and toggle-to-talk until the chord was reverted.

Every other layer (keytap's macOS key tap, Key::Function itself, the
frontend's canonicalKeyFromEvent/displayLabelForKey) already handles
fn — only this string-to-Key bridge was missing the arm.

Fixes #941
2026-07-26 23:31:54 -07:00