d91beef0fd314250d8d9b94de86dfea019a8bd96
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
12480b81b1 |
fix(persistence): move longform projects to IndexedDB (#1636) (#1650)
Move unbounded Stories and Audiobook project data to revisioned IndexedDB storage with bounded local fallback, durable clear tombstones, migration/recovery safeguards, and deadline-safe persistence before exits and relaunches. Closes #1636. |
||
|
|
2b0cd04599 | docs: align workspace design specifications | ||
|
|
5cab8e0149 |
feat: rename the product to VoiceStudio (previously OmniVoice-Studio)
Renames what users see. The app, the installers, the window title, the
docs and all 21 locales now say VoiceStudio, with "(previously
OmniVoice-Studio)" noted near the title of each doc surface so people
recognise it.
Deliberately NOT renamed, because renaming any of them silently breaks
an existing install — there is no legacy-path fallback anywhere in this
codebase:
- bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode,
macOS TCC grants, managed venv, WebView localStorage, the
single-instance lock)
- data directories OmniVoice / .omnivoice and omnivoice.db
- the ~150 OMNIVOICE_* environment variables
- the X-OmniVoice-* HTTP headers (a wire protocol)
- the published Docker image paths
- the OmniVoice ENGINE, which is a model name and not this product
tests/test_identity_paths_survive_the_rename.py pins every one of those
so a future well-meaning sweep cannot orphan a user's library.
Linux .deb users install a new package name and should apt remove
omnivoice-studio; that note is in the changelog.
|
||
|
|
8690833137 |
feat(update): move update pill into status bar + Updates panel (changelog/channel/history) (#240)
* docs(spec): updates-in-status-bar design (move pill to LogsFooter + Updates panel) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(plan): updates-in-status-bar implementation plan (11 tasks, TDD) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(plan): pin i18n task to scripts/translate_all.py backfill * feat(update): pure chip + release presentation helpers * feat(update): listReleases + fetchAppVersion wrappers * feat(update): transient releasesSlice composed into store * feat(update): app version + channel in updaterSlice * feat(update): list_releases Tauri command (GitHub releases) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(update): UpdateStatusChip bar indicator * feat(update): UpdatesPanel (live row + channel + releases list) * feat(update): mount chip+panel in LogsFooter, retire floating UpdateBadge Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(update): Settings channel switcher shares store value (auto-sync) * i18n(update): add updates.* keys across 21 locales Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(deps): lock reqwest for list_releases command * polish(update): a11y radiogroup on channel switch, safer release key, drop dead test seam Addresses final-review nits (non-blocking): role=radiogroup/radio + aria-checked on the channel Segmented; key={r.name||r.version} to avoid collisions; remove the unused vi import + __loader seam in releasesSlice.test.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(update): i18n the channel-set error + correct flagged updates.* translations (PR #240 review) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(update): add 10s timeout to list_releases HTTP client (PR #240 review) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(update): guard chip Restart against in-flight dub job (greptile P1, PR #240) The always-visible status chip's one-click Restart (ready state) called installUpdate→relaunch without the dub-busy guard the panel uses, so a user with a dub/transcription job running could lose in-flight work. Mirror the panel's gate: toast update.busy and bail when dubStep === 'generating'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(update): surface channel-switch failures in the Updates panel (greptile, PR #240) Mirror Settings' error handling: the panel's stable/preview switch now catches a failed set_update_channel and toasts settings.channel_set_failed instead of an unhandled rejection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
94f2363fa2 |
feat(release): version preview builds (0.3.0-preview.N) + rollback spec (#226)
Phase A: stamp each preview build with a unique monotonic semver prerelease (<base>-preview.<run_number>) via an ephemeral tauri.conf.json rewrite on the preview path. Today every preview reported the static 0.3.0, so the updater never saw a newer version and never delivered preview updates. The prerelease ordering makes each new preview offer-able and converges to stable when <base> ships. (Windows MSI ProductVersion strips the prerelease — caveat noted to verify; mac/linux unaffected.) Phase B (rollback) is captured as a design spec for review, not implemented: per-version preview releases + retention, an in-app Preview-builds picker, an allow_downgrades install path, and the alembic-head data-safety boundary. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e1850b4bd9 |
feat(gallery): designed-voice archetype gallery + neutral importer (#203)
* feat(gallery): designed-voice archetype gallery + neutral importer Adds a browsable library of ~1,100 designed voice archetypes (no real people), generated from OmniVoice's own voice-design taxonomy and organized ElevenLabs-style: 24 curated Featured voices plus a facet-filtered "Browse all" explorer (595 English + 504 Chinese-dialect). Every generated instruct is built from the validator's own vocabulary, so none can trigger the issue-#89 synthesis crash. Backend: - core/archetypes.py: catalog engine (featured + generated, implausible combos pruned, stable hashed ids); loads the taxonomy by file path to stay torch-free in tests. - api/routers/archetypes.py: categories / list+filter+paginate / get / preview (render-on-demand + disk cache) / use (materialize a voice profile). Preview/use reuse generation.py's proven inference path. - gallery.py: drop the celebrity/character catalog; the importer is now a neutral, user-driven "My Imports" (paste a URL you have the rights to). No project-shipped directory of named real people. Frontend: - Gallery UI rewrite: Archetypes zone (featured grid + facet filters + favorites) and My Imports zone; per-card Use voice / Open in Designer. - api/archetypes.ts, useArchetypes/useArchetypeCategories hooks (v5 placeholderData:keepPreviousData), gallerySlice, en.json keys. Tests: 27 new (engine contract + API), full backend suite green (72); CJK guard allowlists the one functional Chinese preview sample. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(gallery): clear new bandit alerts (sha1 + SQL false-positive) The PR's code-scanning "Bandit" check fires on NEW alerts vs main's baseline. The archetype work introduced three: - archetypes.py / core/archetypes.py: hashlib.sha1 used to derive a deterministic preview-cache key and archetype id (not a security digest) — flagged B324 (HIGH). Add usedforsecurity=False; the digest is unchanged. - gallery.py: the UPDATE query interpolates only static, code-controlled column fragments ("is_favorite = ?", "description = ?"); every user value is bound via a ? placeholder — flagged B608 (false positive). Annotate `# nosec B608` with the justification. Behavior-preserving. Net new bandit alerts after this: zero (verified with bandit -ll -ii; only main's pre-existing baseline remains). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(gallery): resolve PR #203 CI (SHA-256 ids, log sanitization, CJK allowlist) All failures stemmed from the initial commit: - Bandit + CodeQL (2 high): SHA-1 weak-hash on the archetype id and the preview cache key. These are deterministic identifiers, never security digests — switched to SHA-256, which the SAST scanners accept. - CodeQL (log injection): the render-failure logs echoed the raw user-supplied archetype_id; log the catalog's canonical a["id"] instead (untainted — it comes from the trusted in-memory catalog, not the request). - CodeQL (superfluous argument): declare createGallerySlice's StateCreator store param so its arity matches the 3-arg call site. - Tests (test_no_hardcoded_cjk): the committed design spec's Chinese-dialect reference table tripped the guard; allowlist it under documentation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(gallery): clear CodeQL clear-text-logging on archetype render errors CodeQL's sensitive-data heuristic flags any request-derived value interpolated into a log call (it persisted even after switching the raw id to the catalog's canonical a["id"]). Log a static message with exc_info=True instead: the full traceback still reaches the backend log for debugging, but no data expression remains for the query to flag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
06560e5555 |
feat(stories): Pro Studio Phase 1 — real audiobook output, cast, persistence, reorder, i18n (#177)
* docs(spec): Stories Editor pro-studio design (line cards, auto-cast, pro output, projects) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(stories): Phase 1 — real audiobook output, cast, persistence, reorder, i18n First phase of the pro-studio Stories Editor (spec: docs/superpowers/specs/2026-05-30-stories-editor-studio-design.md). Makes the editor actually produce audiobooks and remember your work: - Persistence: storiesSlice (tracks + cast) via zustand persist -> localStorage; transient fields (generating/audioUrl) stripped on persist; id counter reseeds from persisted tracks. Dropped the hardcoded sample seed -> clean empty state. - Cast: editable CastMember[] (name, color, voice) with a Cast panel; each line picks a character and inherits its voice (per-line override still available). - Real Generate: exportStoryAudio() stitches every line + [pause] gaps into one WAV via the Web Audio API (job-less /generate per chunk) with a % progress indicator and download. Per-line preview already shipped (#176). - Reorder: native HTML5 drag-and-drop (pure reorder() helper). - i18n: all Stories strings via t('stories.*') (en + zh-CN). - Tests: storiesSlice reducers, storyCast resolution, storyExport WAV/concat/ silence, storyReorder. 18 new unit tests. No DB/alembic; localStorage only. Same-origin + PIN-safe synth (apiFetch). No new deps. Cross-platform-identical default behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fa1503c4eb |
feat: network sharing (PIN-gated LAN + QR) & Tailscale remote access (#125) (#159)
* docs(spec): network sharing + Tailscale remote access design Same-state LAN sharing via a second in-process uvicorn listener on a dedicated share port (no restart, model/jobs preserved), PIN-gated for non-loopback clients, with QR + all-LAN-addresses panel. Tailscale serve for private remote access. Supersedes the raw 0.0.0.0 default-flip in #125. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(spec): control endpoints reuse existing require_loopback gate Security review of #157 confirmed the /system router is already loopback-gated via Depends(require_loopback) (non-spoofable request.client.host). The network control endpoints inherit it and /system/set-env is auto-protected from the LAN listener — no new guard needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(network): share-listener module — LAN enumeration + PIN + lifecycle Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(network): loopback-only control endpoints + /system/info sharing fields Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(cjk): scan git-tracked files only, not untracked vendored dirs The no-hardcoded-CJK guard walked the filesystem, so local untracked vendored experiments (research/voice-pro etc. with JP issue templates) caused false local failures while CI (committed files) passed. Scan via git ls-files so local-only and CI behavior match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(network): PIN middleware — gate non-loopback API access when sharing on Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(network): inject X-OmniVoice-Pin globally + capture ?pin= from QR URL Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(network): remote PIN gate on 401 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(network): add qrcode dep for share QR Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(network): footer Local/Network toggle with LAN addresses, QR, copy/open Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(tailscale): CLI status + serve enable/disable + endpoints Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(network): Settings → Sharing & Remote Access panel (LAN + Tailscale) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(network): sharing & remote access guide (LAN PIN/QR + Tailscale) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(network): enable() tears down and raises if the share listener never binds Defensive guard (spec §7): if the second uvicorn server doesn't reach 'started' (e.g. the share port was taken in the race after the free-port probe), cancel the task, reset state, and raise — so the API surfaces the failure and the UI stays Local rather than reporting a dead 'Network' state. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(network): use globalThis (not Node global) in client.test.ts for tsc CI runs 'tsc --noEmit --checkJs false', which type-checks .ts files; Node's 'global' isn't typed there (TS2304). vitest (esbuild) tolerated it locally. Use globalThis (standard, typed) + cast the mock. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(network): apiFetch leaves opts untouched when no PIN set The unconditional headers merge changed the request shape for callers with no headers (e.g. FormData posts), breaking the legacy 'apiPost passes FormData without Content-Type override' node test. Only spread opts + inject X-OmniVoice-Pin when a PIN is actually present; otherwise pass opts through unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |