refactor: move shared UI under Electron

This commit is contained in:
Palash Debnath
2026-09-26 11:46:37 -07:00
parent b74af7f83f
commit 29c8a13260
1137 changed files with 529 additions and 445 deletions
+3 -3
View File
@@ -211,11 +211,11 @@ one. The rules:
Anything over 500 lines must be split. (The cap does **not** apply to Anything over 500 lines must be split. (The cap does **not** apply to
`src/index.css` — it is the single, intentional styling foundation and the `src/index.css` — it is the single, intentional styling foundation and the
only app stylesheet; see the CSS rule above.) only app stylesheet; see the CSS rule above.)
- **Pages are thin orchestrators.** A file in `frontend/src/pages/` is just - **Pages are thin orchestrators.** A file in `electron/src/shared/pages/` is just
layout + routing + state wiring that composes feature components — no inline layout + routing + state wiring that composes feature components — no inline
sub-component over ~50 lines. sub-component over ~50 lines.
- **One component per file.** Co-locate `Foo.jsx` + `Foo.test.jsx` together in a - **One component per file.** Co-locate `Foo.jsx` + `Foo.test.jsx` together in a
per-page feature folder under `frontend/src/components/` (e.g. per-page feature folder under `electron/src/shared/components/` (e.g.
`components/settings/`, `components/dub/`). Styling is **not** co-located — `components/settings/`, `components/dub/`). Styling is **not** co-located —
it's utilities + shadcn, with any irreducible rules in `src/index.css`. it's utilities + shadcn, with any irreducible rules in `src/index.css`.
- **Shared bits go in a `primitives/` folder** inside the feature folder - **Shared bits go in a `primitives/` folder** inside the feature folder
@@ -309,7 +309,7 @@ hard rules from the first prompt.
var, or CLI flag). var, or CLI flag).
- **i18n — all 21 locales (hard rule):** every user-facing string goes through - **i18n — all 21 locales (hard rule):** every user-facing string goes through
`t('...')` and the key must exist in **all 21** files under `t('...')` and the key must exist in **all 21** files under
`frontend/src/i18n/locales/`. Translate; don't copy English into non-English `electron/src/shared/i18n/locales/`. Translate; don't copy English into non-English
locales. CI fails on hardcoded CJK outside the allowlist in locales. CI fails on hardcoded CJK outside the allowlist in
`tests/test_no_hardcoded_cjk.py` (extend `_ALLOWED_FILES` with a `tests/test_no_hardcoded_cjk.py` (extend `_ALLOWED_FILES` with a
justification for legitimate functional CJK). justification for legitimate functional CJK).
+1 -1
View File
@@ -61,7 +61,7 @@ jobs:
cache-dependency-glob: "uv.lock" cache-dependency-glob: "uv.lock"
# Node 22 is needed for --experimental-strip-types so node:test can # Node 22 is needed for --experimental-strip-types so node:test can
# import .ts files directly from frontend/src/api/*. # import .ts files directly from electron/src/shared/api/*.
- name: Setup Node 22 - name: Setup Node 22
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
+3 -3
View File
@@ -3,7 +3,7 @@
Electron (`electron/`) is the only desktop and web UI. The Tauri shell and legacy Electron (`electron/`) is the only desktop and web UI. The Tauri shell and legacy
UI entrypoints are removed; do not restore build, runtime, release, or CI paths for UI entrypoints are removed; do not restore build, runtime, release, or CI paths for
them. Triage reports from final Tauri installations toward the migration guide and them. Triage reports from final Tauri installations toward the migration guide and
preserve their immutable updater feeds. Some modules under `frontend/src/` remain preserve their immutable updater feeds. Some modules under `electron/src/shared/` remain
temporarily shared by Electron; they are not a runnable app. New UI, IPC, setup temporarily shared by Electron; they are not a runnable app. New UI, IPC, setup
instructions, tests, and browser assets belong in Electron; validate Electron on instructions, tests, and browser assets belong in Electron; validate Electron on
macOS, Windows and Linux. macOS, Windows and Linux.
@@ -37,7 +37,7 @@ Binding for every AI agent (Claude, Codex, Cursor, review bots, …). CLAUDE.md
- Root-cause the class, not the instance; fail-before/pass-after regression test; smallest correct change. - Root-cause the class, not the instance; fail-before/pass-after regression test; smallest correct change.
- Default behavior identical on macOS/Windows/Linux; platform-only features go behind explicit opt-in. Divergent default = P0. - Default behavior identical on macOS/Windows/Linux; platform-only features go behind explicit opt-in. Divergent default = P0.
- Local-first: no new required network calls; any HF download gated on installed-ness or explicit user action; all synthetic audio through the `mark_synthetic` chokepoint. - Local-first: no new required network calls; any HF download gated on installed-ness or explicit user action; all synthetic audio through the `mark_synthetic` chokepoint.
- Every user-facing string via i18n, present in ALL 21 `frontend/src/i18n/locales/*.json` with real translations. - Every user-facing string via i18n, present in ALL 21 `electron/src/shared/i18n/locales/*.json` with real translations.
- Docs-sync in the same PR. CHANGELOG Unreleased: quiet one-liners ending `(#N)` + `— thanks @user!` for community work, under a short `**Highlights**` list. - Docs-sync in the same PR. CHANGELOG Unreleased: quiet one-liners ending `(#N)` + `— thanks @user!` for community work, under a short `**Highlights**` list.
- Tagged release announcements lead with the biggest user-visible change; redesigns need real UI screenshots and migrations need installer links and steps. Verify all contributor credits from the tag comparison and included PRs; list authors and bug reporters separately (see `docs/RELEASING.md`). - Tagged release announcements lead with the biggest user-visible change; redesigns need real UI screenshots and migrations need installer links and steps. Verify all contributor credits from the tag comparison and included PRs; list authors and bug reporters separately (see `docs/RELEASING.md`).
- Versioning: root `package.json` is the single source of truth; never bump without the owner asking. - Versioning: root `package.json` is the single source of truth; never bump without the owner asking.
@@ -46,7 +46,7 @@ Binding for every AI agent (Claude, Codex, Cursor, review bots, …). CLAUDE.md
## Shared select controls ## Shared select controls
- Use `frontend/src/components/SearchableSelect.jsx` for all new or redesigned select boxes. Reuse `VoiceSelector` for voice choices. Do not introduce native `<select>` controls. - Use `electron/src/shared/components/SearchableSelect.jsx` for all new or redesigned select boxes. Reuse `VoiceSelector` for voice choices. Do not introduce native `<select>` controls.
- Provide a localized `ariaLabel`; use `menuPortal` inside scrolling or clipping containers. Preserve keyboard selection and disabled states. - Provide a localized `ariaLabel`; use `menuPortal` inside scrolling or clipping containers. Preserve keyboard selection and disabled states.
## Agent skills ## Agent skills
+3 -3
View File
@@ -3,7 +3,7 @@
Electron (`electron/`) is the only desktop and web UI. The Tauri shell and legacy Electron (`electron/`) is the only desktop and web UI. The Tauri shell and legacy
UI entrypoints are removed; do not restore build, runtime, release, or CI paths for UI entrypoints are removed; do not restore build, runtime, release, or CI paths for
them. Triage reports from final Tauri installations toward the migration guide and them. Triage reports from final Tauri installations toward the migration guide and
preserve their immutable updater feeds. Some modules under `frontend/src/` remain preserve their immutable updater feeds. Some modules under `electron/src/shared/` remain
temporarily shared by Electron; they are not a runnable app. New UI, IPC, setup temporarily shared by Electron; they are not a runnable app. New UI, IPC, setup
instructions, tests, and browser assets belong in Electron; validate Electron on instructions, tests, and browser assets belong in Electron; validate Electron on
macOS, Windows and Linux. macOS, Windows and Linux.
@@ -34,7 +34,7 @@ Everything else (new engines, fancy features) is downstream of "the thing instal
The May-2026 stack research that used to live here served five capabilities that have all since shipped (HF-token Settings panel, prefilled-URL bug reporting, uv mirror fallback for restricted networks, the Supertonic-3 engine, in-repo Markdown docs). Follow the patterns in the code itself; the durable *don'ts* that research established: The May-2026 stack research that used to live here served five capabilities that have all since shipped (HF-token Settings panel, prefilled-URL bug reporting, uv mirror fallback for restricted networks, the Supertonic-3 engine, in-repo Markdown docs). Follow the patterns in the code itself; the durable *don'ts* that research established:
- **No third-party endpoints for bug reporting or crash dumps** (`sentry-tauri` was evaluated and rejected) — bug reporting stays opt-in via prefilled GitHub-issue URLs, submitted from the user's own browser. The one sanctioned third-party endpoint is the opt-in PostHog US product analytics and error tracking (owner-set 2026-07-16; migrated 2026-09-26; sanitized renderer errors approved 2026-09-26), which is consent-gated behind the first-run prompt and ships allowlisted content-free metadata only. Raw exception, DOM, and session autocapture stay disabled; manually captured renderer exceptions must replace messages and scrub stack paths before transmission. Its publishable write-only project token is committed in-repo (owner reversal 2026-07-20, #1193 — source builds get the same consent-gated analytics as installers; env/baked token overrides), allowed by `tests/test_no_committed_analytics_token.py` in exactly `backend/core/analytics.py` + `frontend/src/utils/analytics.ts`. - **No third-party endpoints for bug reporting or crash dumps** (`sentry-tauri` was evaluated and rejected) — bug reporting stays opt-in via prefilled GitHub-issue URLs, submitted from the user's own browser. The one sanctioned third-party endpoint is the opt-in PostHog US product analytics and error tracking (owner-set 2026-07-16; migrated 2026-09-26; sanitized renderer errors approved 2026-09-26), which is consent-gated behind the first-run prompt and ships allowlisted content-free metadata only. Raw exception, DOM, and session autocapture stay disabled; manually captured renderer exceptions must replace messages and scrub stack paths before transmission. Its publishable write-only project token is committed in-repo (owner reversal 2026-07-20, #1193 — source builds get the same consent-gated analytics as installers; env/baked token overrides), allowed by `tests/test_no_committed_analytics_token.py` in exactly `backend/core/analytics.py` + `electron/src/shared/utils/analytics.ts`.
- **No PAT/token-based GitHub posting from the app** — the user submits from their own browser. - **No PAT/token-based GitHub posting from the app** — the user submits from their own browser.
- **Don't recommend `setx` for env vars on Windows** (silent truncation, no current-shell propagation) — use the in-app Settings panel or PowerShell `[Environment]::SetEnvironmentVariable`. - **Don't recommend `setx` for env vars on Windows** (silent truncation, no current-shell propagation) — use the in-app Settings panel or PowerShell `[Environment]::SetEnvironmentVariable`.
- **Don't adopt Material for MkDocs** for any future docs site (maintenance mode since Nov 2025) — Astro Starlight is the precedent if docs ever outgrow the repo. - **Don't adopt Material for MkDocs** for any future docs site (maintenance mode since Nov 2025) — Astro Starlight is the precedent if docs ever outgrow the repo.
@@ -58,7 +58,7 @@ For anything new: prefer what's already pinned in `pyproject.toml` / `electron/p
**Release presentation and credits (owner-set 2026-09-17):** Tagged release announcements lead with the biggest user-visible change; redesigns need real UI screenshots and migrations need installer links and steps. Verify all contributor credits from the tag comparison and included PRs; list authors and bug reporters separately (see `docs/RELEASING.md`). Keep Highlights to 3–5 bullets; the release introduction can include prose, images, and a download table before the concise change entries. **Release presentation and credits (owner-set 2026-09-17):** Tagged release announcements lead with the biggest user-visible change; redesigns need real UI screenshots and migrations need installer links and steps. Verify all contributor credits from the tag comparison and included PRs; list authors and bug reporters separately (see `docs/RELEASING.md`). Keep Highlights to 3–5 bullets; the release introduction can include prose, images, and a download table before the concise change entries.
**Localization (hard rule):** No hardcoded non-English (CJK) **user-facing text** anywhere in the codebase except the translation layer (`frontend/src/i18n/`). All UI strings go through i18n (`t('...')` keys in `locales/*.json`); native language names live in `i18n/index.ts` (`LANGUAGES`). Functional CJK is allowed and tracked via the allowlist in `tests/test_no_hardcoded_cjk.py` — text-processing regexes, model/engine vocabulary & identifiers (e.g. CosyVoice speaker IDs), localized error matching, demo/eval data, and test fixtures. CI fails on any hardcoded CJK outside the allowlist; to add legitimate functional CJK, extend `_ALLOWED_FILES` there with a justification. **Localization (hard rule):** No hardcoded non-English (CJK) **user-facing text** anywhere in the codebase except the translation layer (`electron/src/shared/i18n/`). All UI strings go through i18n (`t('...')` keys in `locales/*.json`); native language names live in `i18n/index.ts` (`LANGUAGES`). Functional CJK is allowed and tracked via the allowlist in `tests/test_no_hardcoded_cjk.py` — text-processing regexes, model/engine vocabulary & identifiers (e.g. CosyVoice speaker IDs), localized error matching, demo/eval data, and test fixtures. CI fails on any hardcoded CJK outside the allowlist; to add legitimate functional CJK, extend `_ALLOWED_FILES` there with a justification.
**Release deployment channels (hard rule, owner-set 2026-07-16):** a version bump is not "released" until **every** maintained channel ships it — the full checklist lives in `docs/RELEASING.md`. The channels are the four-platform Electron GitHub Release, GHCR and Docker Hub in CUDA and ROCm flavors, and the Docker Hub overview page synced from `deploy/dockerhub-overview.md`. The final Tauri feeds are immutable compatibility assets copied into releases for old clients; they are never rebuilt. Verify all channels after tagging. Preview/RC builds always source from `main`; never cut a side-branch build. **Release deployment channels (hard rule, owner-set 2026-07-16):** a version bump is not "released" until **every** maintained channel ships it — the full checklist lives in `docs/RELEASING.md`. The channels are the four-platform Electron GitHub Release, GHCR and Docker Hub in CUDA and ROCm flavors, and the Docker Hub overview page synced from `deploy/dockerhub-overview.md`. The final Tauri feeds are immutable compatibility assets copied into releases for old clients; they are never rebuilt. Verify all channels after tagging. Preview/RC builds always source from `main`; never cut a side-branch build.
+1 -1
View File
@@ -53,7 +53,7 @@ _OFF_VALUES = {"1", "true", "yes", "on"}
#: A `POSTHOG_PROJECT_TOKEN` env var (release builds bake one in via the #: A `POSTHOG_PROJECT_TOKEN` env var (release builds bake one in via the
#: desktop shell; developers can point at their own project) always wins. #: desktop shell; developers can point at their own project) always wins.
#: Committed-token guard: tests/test_no_committed_analytics_token.py allows a #: Committed-token guard: tests/test_no_committed_analytics_token.py allows a
#: `phc_` literal in exactly this file and frontend/src/utils/analytics.ts. #: `phc_` literal in exactly this file and electron/src/shared/utils/analytics.ts.
_PUBLIC_PROJECT_TOKEN = "phc_kJK9nFqtwFmB4CYWS4bMDTY7z9P4KCRyiRT7CzJTXm9f" # gitleaks:allow — publishable write-only key (#1193) _PUBLIC_PROJECT_TOKEN = "phc_kJK9nFqtwFmB4CYWS4bMDTY7z9P4KCRyiRT7CzJTXm9f" # gitleaks:allow — publishable write-only key (#1193)
_DEFAULT_HOST = "https://us.i.posthog.com" _DEFAULT_HOST = "https://us.i.posthog.com"
+2 -2
View File
@@ -83,7 +83,7 @@ _DIALECTS_SORTED = sorted(_CAT[5]) # 12 Chinese dialects
# ── Use-case categories (replaces the named-real-person buckets) ────────────── # ── Use-case categories (replaces the named-real-person buckets) ──────────────
# `icon` values are lucide-react component names; the frontend maps them to SVG # `icon` values are lucide-react component names; the frontend maps them to SVG
# components (see frontend/src/utils/archetypeIcons.jsx). No emoji — they render # components (see electron/src/shared/utils/archetypeIcons.jsx). No emoji — they render
# inconsistently across OSes. # inconsistently across OSes.
USE_CASES = [ USE_CASES = [
{"id": "narration", "name": "Narration & Story", "icon": "BookOpen"}, {"id": "narration", "name": "Narration & Story", "icon": "BookOpen"},
@@ -323,7 +323,7 @@ def _make_featured():
# gender/age/pitch axes keeps every instruct inside the validator's vocabulary — # gender/age/pitch axes keeps every instruct inside the validator's vocabulary —
# ``test_archetypes.py`` enforces this independently. # ``test_archetypes.py`` enforces this independently.
# #
# Each ``language`` label must match an entry in ``frontend/src/languages.json`` # Each ``language`` label must match an entry in ``electron/src/shared/languages.json``
# verbatim, because the string flows straight into ``model.generate(language=…)`` # verbatim, because the string flows straight into ``model.generate(language=…)``
# with no normalization. ("Arabic" is intentionally omitted — it is not in that # with no normalization. ("Arabic" is intentionally omitted — it is not in that
# list.) ``_ML_SAMPLES`` is functional demo/eval text (like ``_ZH_SAMPLE``); the # list.) ``_ML_SAMPLES`` is functional demo/eval text (like ``_ZH_SAMPLE``); the
+1 -1
View File
@@ -31,7 +31,7 @@ Design notes
Localization note (CLAUDE.md): the only hardcoded CJK here is Localization note (CLAUDE.md): the only hardcoded CJK here is
``DIALECT_PINYIN`` — a functional pinyin → Chinese-dialect-token mapping ``DIALECT_PINYIN`` — a functional pinyin → Chinese-dialect-token mapping
(model vocabulary, like ``frontend/src/utils/constants.js``). Registered in (model vocabulary, like ``electron/src/shared/utils/constants.js``). Registered in
``tests/test_no_hardcoded_cjk.py``'s allowlist with this justification. ``tests/test_no_hardcoded_cjk.py``'s allowlist with this justification.
""" """
from __future__ import annotations from __future__ import annotations
+2 -2
View File
@@ -1,7 +1,7 @@
"""Error class → docs URL mapping for the in-app deeplink button. """Error class → docs URL mapping for the in-app deeplink button.
Used by the React ErrorBoundary's "Open docs for this error" button (via the Used by the React ErrorBoundary's "Open docs for this error" button (via the
TypeScript mirror at `frontend/src/utils/errorDocsMap.ts`) and by the Phase 5 TypeScript mirror at `electron/src/shared/utils/errorDocsMap.ts`) and by the Phase 5
bug-reporter for "this error has a docs page" links. bug-reporter for "this error has a docs page" links.
The error taxonomy below is the contract — Phase 5 reporter consumes it, The error taxonomy below is the contract — Phase 5 reporter consumes it,
@@ -10,7 +10,7 @@ locks the key set. To add a new class:
1. Pick a stable ALL_CAPS key (the API will live forever). 1. Pick a stable ALL_CAPS key (the API will live forever).
2. Add the entry here. 2. Add the entry here.
3. Mirror it in `frontend/src/utils/errorDocsMap.ts`. 3. Mirror it in `electron/src/shared/utils/errorDocsMap.ts`.
4. Update the `test_keys_match_taxonomy` set and the TS-side keys-sync test. 4. Update the `test_keys_match_taxonomy` set and the TS-side keys-sync test.
""" """
from __future__ import annotations from __future__ import annotations
+1 -1
View File
@@ -3,7 +3,7 @@
Everything VoiceStudio renders into a bug report or diagnostic dump goes Everything VoiceStudio renders into a bug report or diagnostic dump goes
through ``scrub_text()`` before it can reach a prefilled GitHub Issues URL through ``scrub_text()`` before it can reach a prefilled GitHub Issues URL
(the only outbound path — see CLAUDE.md Capability 2). The scrubber is the (the only outbound path — see CLAUDE.md Capability 2). The scrubber is the
backend twin of ``frontend/src/utils/bugReport.js``'s ``scrubText`` and backend twin of ``electron/src/shared/utils/bugReport.js``'s ``scrubText`` and
must stay at least as strict: must stay at least as strict:
- home directories → ``~`` (macOS ``/Users/<name>``, Linux ``/home/<name>``, - home directories → ``~`` (macOS ``/Users/<name>``, Linux ``/home/<name>``,
+1 -1
View File
@@ -34,7 +34,7 @@ _TOKEN_BYTES = 32
_ENCODED_TOKEN_LENGTH = 43 _ENCODED_TOKEN_LENGTH = 43
_TOKEN_BODY_RE = re.compile(rf"^[A-Za-z0-9_-]{{{_ENCODED_TOKEN_LENGTH}}}$") _TOKEN_BODY_RE = re.compile(rf"^[A-Za-z0-9_-]{{{_ENCODED_TOKEN_LENGTH}}}$")
# Every ticketed WebSocket route. The first-party mirror is ``ALLOWED_WS_PATHS`` # Every ticketed WebSocket route. The first-party mirror is ``ALLOWED_WS_PATHS``
# in frontend/src/api/authSession.ts — a route missing here mints a 422 and the # in electron/src/shared/api/authSession.ts — a route missing here mints a 422 and the
# UI consumer fails silently (#1769 added /ws/tts for the live dub preview). # UI consumer fails silently (#1769 added /ws/tts for the live dub preview).
_ALLOWED_WS_PATHS = frozenset( _ALLOWED_WS_PATHS = frozenset(
{"/ws/events", "/ws/transcribe", "/ws/tts", "/v1/audio/transcriptions/stream"} {"/ws/events", "/ws/transcribe", "/ws/tts", "/v1/audio/transcriptions/stream"}
+1 -1
View File
@@ -3,7 +3,7 @@
The longform marker dialect (``# heading``, ``[voice:NAME]``, ``[pause …]``, The longform marker dialect (``# heading``, ``[voice:NAME]``, ``[pause …]``,
``[slow]/[fast]/[emphasis]/[spell]``) was parsed by three independent code ``[slow]/[fast]/[emphasis]/[spell]``) was parsed by three independent code
paths that disagreed (client/server/regex-level). This module is the one paths that disagreed (client/server/regex-level). This module is the one
canonical Python parser; ``frontend/src/utils/longformParser.js`` is its canonical Python parser; ``electron/src/shared/utils/longformParser.js`` is its
mechanically-mirrored JS twin, and ``tests/fixtures/longform_parser_cases.json`` mechanically-mirrored JS twin, and ``tests/fixtures/longform_parser_cases.json``
is the shared golden corpus asserted byte-for-byte against both. is the shared golden corpus asserted byte-for-byte against both.
+1 -1
View File
@@ -226,7 +226,7 @@ _WEBVTT_TAG_RE = re.compile(r"<[^<>\n]*>")
# SubRip has no escaping, so `a<b and c>d` is dialogue. Only exact tag # SubRip has no escaping, so `a<b and c>d` is dialogue. Only exact tag
# shapes are markup: `<i>`/`<b.x>`/`</u>`, `<c.colorE5E5E5>`, `<v Roger>`, # shapes are markup: `<i>`/`<b.x>`/`</u>`, `<c.colorE5E5E5>`, `<v Roger>`,
# `<font color=...>` and karaoke timestamps. Mirrored in # `<font color=...>` and karaoke timestamps. Mirrored in
# frontend/src/utils/importStory.js (CUE_MARKUP); keep the two in step. # electron/src/shared/utils/importStory.js (CUE_MARKUP); keep the two in step.
_SRT_MARKUP_RE = re.compile( _SRT_MARKUP_RE = re.compile(
r"</?(?:[biu]|c|ruby|rt)(?:\.[^\s.<>]+)*>" r"</?(?:[biu]|c|ruby|rt)(?:\.[^\s.<>]+)*>"
r"|<(?:v|lang)(?:\.[^\s.<>]+)*[ \t][^<>\n]*>|</(?:v|lang)>" r"|<(?:v|lang)(?:\.[^\s.<>]+)*[ \t][^<>\n]*>|</(?:v|lang)>"
+2 -2
View File
@@ -19,7 +19,7 @@ Design rules (load-bearing):
pass through a pipeline is harmless. pass through a pipeline is harmless.
* **Per-language.** Numbers go through ``num2words`` only for languages it * **Per-language.** Numbers go through ``num2words`` only for languages it
supports (``_NUM2WORDS_LANGS``; the request's ``language`` is a full supports (``_NUM2WORDS_LANGS``; the request's ``language`` is a full
display name from frontend/src/languages.json or an ISO-ish code — both display name from electron/src/shared/languages.json or an ISO-ish code — both
resolve via :func:`_num2words_lang`), or through a hand-written native resolve via :func:`_num2words_lang`), or through a hand-written native
verbalizer for languages num2words lacks (``_NATIVE_VERBALIZERS``, e.g. verbalizer for languages num2words lacks (``_NATIVE_VERBALIZERS``, e.g.
Malayalam). Everything else keeps its digits. Malayalam). Everything else keeps its digits.
@@ -75,7 +75,7 @@ PREF_KEY = "text_normalization_enabled"
# ── Language resolution ─────────────────────────────────────────────────────── # ── Language resolution ───────────────────────────────────────────────────────
# #
# The `language` kwarg across the app is normally a full display name from # The `language` kwarg across the app is normally a full display name from
# frontend/src/languages.json ("English", "German", …) — see # electron/src/shared/languages.json ("English", "German", …) — see
# resolve_kokoro_lang_code in services/tts_backend.py — but ISO-ish codes # resolve_kokoro_lang_code in services/tts_backend.py — but ISO-ish codes
# ("en", "pt-BR") also flow through dub/API callers. Map both to a num2words # ("en", "pt-BR") also flow through dub/API callers. Map both to a num2words
# locale; anything unmapped keeps its digits (false negatives are fine). # locale; anything unmapped keeps its digits (false negatives are fine).
+1 -1
View File
@@ -2230,7 +2230,7 @@ class KittenTTSBackend(TTSBackend):
# #977: Kokoro's own ALIASES table (mlx_audio.tts.models.kokoro.pipeline) only # #977: Kokoro's own ALIASES table (mlx_audio.tts.models.kokoro.pipeline) only
# recognizes ISO-ish tokens ("en", "es", "fr-fr", "pt-br", …) — it has no idea # recognizes ISO-ish tokens ("en", "es", "fr-fr", "pt-br", …) — it has no idea
# what a full language name is. OmniVoice's `language` kwarg is normally a # what a full language name is. OmniVoice's `language` kwarg is normally a
# full display name from frontend/src/languages.json (e.g. "Dutch", # full display name from electron/src/shared/languages.json (e.g. "Dutch",
# "Spanish"), forwarded verbatim by the frontend and by # "Spanish"), forwarded verbatim by the frontend and by
# `OmniVoiceBackend.generate()`. Translate the subset Kokoro actually # `OmniVoiceBackend.generate()`. Translate the subset Kokoro actually
# supports to the ISO token its own ALIASES expects; a caller that already # supports to the ISO token its own ALIASES expects; a caller that already
+128 -5
View File
@@ -4,9 +4,6 @@
"workspaces": { "workspaces": {
"": { "": {
"name": "omnivoice-studio-monorepo", "name": "omnivoice-studio-monorepo",
"dependencies": {
"posthog-js": "^1.434.14",
},
"devDependencies": { "devDependencies": {
"concurrently": "^10", "concurrently": "^10",
"playwright": "^1.63.0", "playwright": "^1.63.0",
@@ -24,37 +21,57 @@
"@electron-toolkit/preload": "^3.0.2", "@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0", "@electron-toolkit/utils": "^4.0.0",
"@fontsource-variable/inter": "^5.3.0", "@fontsource-variable/inter": "^5.3.0",
"@fontsource-variable/source-serif-4": "^5.3.0",
"@fontsource/ibm-plex-mono": "^5.3.0", "@fontsource/ibm-plex-mono": "^5.3.0",
"@lobehub/icons-static-svg": "^1.95.0", "@lobehub/icons-static-svg": "^1.95.0",
"@radix-ui/react-dialog": "^1.1.23",
"@radix-ui/react-dropdown-menu": "^2.1.24",
"@radix-ui/react-progress": "^1.1.16",
"@radix-ui/react-select": "^2.3.7",
"@radix-ui/react-slider": "^1.4.7",
"@radix-ui/react-slot": "^1.3.3",
"@radix-ui/react-tabs": "^1.1.21",
"@radix-ui/react-toggle": "^1.1.18",
"@radix-ui/react-toggle-group": "^1.1.19",
"@radix-ui/react-tooltip": "^1.2.16",
"@scalar/api-reference-react": "^0.9.67", "@scalar/api-reference-react": "^0.9.67",
"@tanstack/react-form": "^1.33.5", "@tanstack/react-form": "^1.33.5",
"@tanstack/react-pacer": "^0.23.0", "@tanstack/react-pacer": "^0.23.0",
"@tanstack/react-query": "^5.102.8", "@tanstack/react-query": "^5.102.8",
"@tanstack/react-router": "^1.170.36", "@tanstack/react-router": "^1.170.36",
"@tanstack/react-store": "^0.11.1", "@tanstack/react-store": "^0.11.1",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.14.13", "@tanstack/react-virtual": "^3.14.13",
"@tanstack/store": "^0.11.1", "@tanstack/store": "^0.11.1",
"@vidstack/react": "1.15.6", "@vidstack/react": "1.15.6",
"@xyflow/react": "^12.12.0", "@xyflow/react": "^12.12.0",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cn": "^0.3.0", "cn": "^0.3.0",
"country-flag-icons": "^1.6.20",
"dashjs": "^5.2.1", "dashjs": "^5.2.1",
"electron-updater": "^6.8.9", "electron-updater": "^6.8.9",
"hls.js": "^1.7.3", "hls.js": "^1.7.3",
"html-entities": "^2.6.0",
"i18next": "^26.4.2", "i18next": "^26.4.2",
"i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^1.46.0", "lucide-react": "^1.46.0",
"posthog-js": "^1.434.14", "posthog-js": "^1.434.14",
"proxy-agent": "6.5.0", "proxy-agent": "6.5.0",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"react": "^19.3.0", "react": "^19.3.0",
"react-dom": "^19.3.0", "react-dom": "^19.3.0",
"react-hot-toast": "^2.6.0",
"react-i18next": "^17.0.14", "react-i18next": "^17.0.14",
"react-window": "^2.3.1",
"remotion": "4.0.524", "remotion": "4.0.524",
"shadcn": "^4.21.0", "shadcn": "^4.21.0",
"sonner": "^2.0.8", "sonner": "^2.0.8",
"tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"wavesurfer.js": "^7.12.12", "wavesurfer.js": "^7.12.12",
"word-extractor": "1.0.4", "word-extractor": "1.0.4",
"zustand": "^5.0.15",
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/vite": "^4.3.3", "@tailwindcss/vite": "^4.3.3",
@@ -297,6 +314,8 @@
"@fontsource-variable/inter": ["@fontsource-variable/inter@5.3.0", "", {}, "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA=="], "@fontsource-variable/inter": ["@fontsource-variable/inter@5.3.0", "", {}, "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA=="],
"@fontsource-variable/source-serif-4": ["@fontsource-variable/source-serif-4@5.3.0", "", {}, "sha512-9vch9WqxjaaA+1o9Ur8pOgIGbCYLjRReOUel23A6lOpD1syptgjtkORevvNmldJ5kGXQL29onQUqI5Ltz0s3bQ=="],
"@fontsource/ibm-plex-mono": ["@fontsource/ibm-plex-mono@5.3.0", "", {}, "sha512-eTgnZjZEGk1QtD3ZstF+Vclo2HLAni8YMy34/DxllwZvyz1lR/1RF/xTiAquOBO7MvqBx8D2Ig2WCPMVfdZu7Q=="], "@fontsource/ibm-plex-mono": ["@fontsource/ibm-plex-mono@5.3.0", "", {}, "sha512-eTgnZjZEGk1QtD3ZstF+Vclo2HLAni8YMy34/DxllwZvyz1lR/1RF/xTiAquOBO7MvqBx8D2Ig2WCPMVfdZu7Q=="],
"@hapi/address": ["@hapi/address@5.1.1", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA=="], "@hapi/address": ["@hapi/address@5.1.1", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA=="],
@@ -487,6 +506,80 @@
"@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="], "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="],
"@radix-ui/number": ["@radix-ui/number@1.1.3", "", {}, "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA=="],
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.7", "", {}, "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q=="],
"@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.15", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA=="],
"@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.15", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA=="],
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.5", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA=="],
"@radix-ui/react-context": ["@radix-ui/react-context@1.2.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA=="],
"@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.23", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA=="],
"@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg=="],
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.19", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-effect-event": "0.0.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w=="],
"@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.24", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-menu": "2.1.24", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g=="],
"@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.6", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ=="],
"@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.16", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ=="],
"@radix-ui/react-id": ["@radix-ui/react-id@1.1.4", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA=="],
"@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.24", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-callback-ref": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA=="],
"@radix-ui/react-popper": ["@radix-ui/react-popper@1.3.7", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-use-rect": "1.1.4", "@radix-ui/react-use-size": "1.1.4", "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg=="],
"@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.17", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ=="],
"@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.10", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw=="],
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.10", "", { "dependencies": { "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg=="],
"@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.16", "", { "dependencies": { "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg=="],
"@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.19", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-is-hydrated": "0.1.3", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ=="],
"@radix-ui/react-select": ["@radix-ui/react-select@2.3.7", "", { "dependencies": { "@radix-ui/number": "1.1.3", "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-use-previous": "1.1.4", "@radix-ui/react-visually-hidden": "1.2.11", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg=="],
"@radix-ui/react-slider": ["@radix-ui/react-slider@1.4.7", "", { "dependencies": { "@radix-ui/number": "1.1.3", "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-use-previous": "1.1.4", "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg=="],
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.5" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q=="],
"@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.21", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog=="],
"@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.18", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug=="],
"@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.19", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-toggle": "1.1.18", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA=="],
"@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg=="],
"@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ=="],
"@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-use-effect-event": "0.0.5", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ=="],
"@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.5", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg=="],
"@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw=="],
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw=="],
"@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg=="],
"@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.4", "", { "dependencies": { "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ=="],
"@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.4", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw=="],
"@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.11", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ=="],
"@radix-ui/rect": ["@radix-ui/rect@1.1.3", "", {}, "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw=="],
"@replit/codemirror-css-color-picker": ["@replit/codemirror-css-color-picker@6.3.0", "", { "peerDependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-19biDANghUm7Fz7L1SNMIhK48tagaWuCOHj4oPPxc7hxPGkTVY2lU/jVZ8tsbTKQPVG7BO2CBDzs7CBwb20t4A=="], "@replit/codemirror-css-color-picker": ["@replit/codemirror-css-color-picker@6.3.0", "", { "peerDependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-19biDANghUm7Fz7L1SNMIhK48tagaWuCOHj4oPPxc7hxPGkTVY2lU/jVZ8tsbTKQPVG7BO2CBDzs7CBwb20t4A=="],
"@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.7", "", { "os": "android", "cpu": "arm" }, "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ=="], "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.7", "", { "os": "android", "cpu": "arm" }, "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ=="],
@@ -661,6 +754,8 @@
"@tanstack/react-store": ["@tanstack/react-store@0.11.1", "", { "dependencies": { "@tanstack/store": "0.11.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ=="], "@tanstack/react-store": ["@tanstack/react-store@0.11.1", "", { "dependencies": { "@tanstack/store": "0.11.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ=="],
"@tanstack/react-table": ["@tanstack/react-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww=="],
"@tanstack/react-virtual": ["@tanstack/react-virtual@3.14.13", "", { "dependencies": { "@tanstack/virtual-core": "3.17.11" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-JbDTAwtzZ99aOeCrAfW5EsE5KSq5RWh6Af2dtFwyLIIk48Ja7vm6n4axu/43T3vfjFnEGJalxJ1wyYjSQD6bSg=="], "@tanstack/react-virtual": ["@tanstack/react-virtual@3.14.13", "", { "dependencies": { "@tanstack/virtual-core": "3.17.11" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-JbDTAwtzZ99aOeCrAfW5EsE5KSq5RWh6Af2dtFwyLIIk48Ja7vm6n4axu/43T3vfjFnEGJalxJ1wyYjSQD6bSg=="],
"@tanstack/router-core": ["@tanstack/router-core@1.171.30", "", { "dependencies": { "@tanstack/history": "1.162.4", "cookie-es": "^3.0.0", "seroval": "^1.6.2", "seroval-plugins": "^1.6.2" } }, "sha512-wtS1yCAb/k5CJBauvrCyqf2n4/kRc3aiz81tFBJ1QXH4Utsu81xFTMq15AdmNWgWmt2UstUSMfWi4Ua7ph2F+w=="], "@tanstack/router-core": ["@tanstack/router-core@1.171.30", "", { "dependencies": { "@tanstack/history": "1.162.4", "cookie-es": "^3.0.0", "seroval": "^1.6.2", "seroval-plugins": "^1.6.2" } }, "sha512-wtS1yCAb/k5CJBauvrCyqf2n4/kRc3aiz81tFBJ1QXH4Utsu81xFTMq15AdmNWgWmt2UstUSMfWi4Ua7ph2F+w=="],
@@ -669,6 +764,8 @@
"@tanstack/store": ["@tanstack/store@0.11.1", "", {}, "sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA=="], "@tanstack/store": ["@tanstack/store@0.11.1", "", {}, "sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA=="],
"@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="],
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.17.11", "", {}, "sha512-+ILjvtHup6Y2hzQ6YzwMgX1Q+oQpxEGOXCEsCNaPoIP0VxMbizIBTmYTDtkerkIQS8/CbP1BRuyt8V/8BCsy1g=="], "@tanstack/virtual-core": ["@tanstack/virtual-core@3.17.11", "", {}, "sha512-+ILjvtHup6Y2hzQ6YzwMgX1Q+oQpxEGOXCEsCNaPoIP0VxMbizIBTmYTDtkerkIQS8/CbP1BRuyt8V/8BCsy1g=="],
"@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.36", "", { "dependencies": { "@tanstack/virtual-core": "3.17.8" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-gKpExv4RbB9luVG+SucTXoqPZv/gzu/Yvz6BNO+8kpNxJ2x+I/ulryzl5W9BRciahZGp5Tls3Dp5XP1ztVGbMw=="], "@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.36", "", { "dependencies": { "@tanstack/virtual-core": "3.17.8" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-gKpExv4RbB9luVG+SucTXoqPZv/gzu/Yvz6BNO+8kpNxJ2x+I/ulryzl5W9BRciahZGp5Tls3Dp5XP1ztVGbMw=="],
@@ -1097,6 +1194,8 @@
"cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="], "cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="],
"country-flag-icons": ["country-flag-icons@1.6.20", "", {}, "sha512-py8JiEKzjhYw6HPJ0L7SxLgCYim36UPRTZX43/kqGueUCZLSvnrqAiwW8HtQibur7mdkFQUkjOgdK+o/9FBtaw=="],
"crelt": ["crelt@1.0.7", "", {}, "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA=="], "crelt": ["crelt@1.0.7", "", {}, "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA=="],
"cross-dirname": ["cross-dirname@0.1.0", "", {}, "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q=="], "cross-dirname": ["cross-dirname@0.1.0", "", {}, "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q=="],
@@ -1181,6 +1280,8 @@
"detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="],
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
"diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
@@ -1351,6 +1452,8 @@
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
"get-own-enumerable-keys": ["get-own-enumerable-keys@1.0.0", "", {}, "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA=="], "get-own-enumerable-keys": ["get-own-enumerable-keys@1.0.0", "", {}, "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
@@ -1455,6 +1558,8 @@
"i18next": ["i18next@26.4.2", "", { "peerDependencies": { "typescript": "^5 || ^6 || ^7" }, "optionalPeers": ["typescript"] }, "sha512-RX+R0VLg13IbvRuJSxnqykUFS9vQZTl8wYpWPCIUDWVrSGjsQywB5Y+pjzrkboxGAuYfJZVH1InFTdgBdxq6ug=="], "i18next": ["i18next@26.4.2", "", { "peerDependencies": { "typescript": "^5 || ^6 || ^7" }, "optionalPeers": ["typescript"] }, "sha512-RX+R0VLg13IbvRuJSxnqykUFS9vQZTl8wYpWPCIUDWVrSGjsQywB5Y+pjzrkboxGAuYfJZVH1InFTdgBdxq6ug=="],
"i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.1", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw=="],
"iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="],
"identifier-regex": ["identifier-regex@1.1.0", "", { "dependencies": { "reserved-identifiers": "^1.0.0" } }, "sha512-SLX4H/vtcYlYnL7XqnuJKHU7Z8517TgsW9nmQiGOgMCjQ8V/deLYu6bEmbGoXe7WMMhc9+EUGyFFneHja8KabA=="], "identifier-regex": ["identifier-regex@1.1.0", "", { "dependencies": { "reserved-identifiers": "^1.0.0" } }, "sha512-SLX4H/vtcYlYnL7XqnuJKHU7Z8517TgsW9nmQiGOgMCjQ8V/deLYu6bEmbGoXe7WMMhc9+EUGyFFneHja8KabA=="],
@@ -1935,10 +2040,20 @@
"react-dom": ["react-dom@19.3.0", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.0" } }, "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q=="], "react-dom": ["react-dom@19.3.0", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.0" } }, "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q=="],
"react-hot-toast": ["react-hot-toast@2.6.1", "", { "dependencies": { "csstype": "^3.1.3", "goober": "^2.1.16" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": ">=16", "react-dom": ">=16" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-gjFsf05dxcku0FkyMtFH+XmYFdA2odXfBVknEr/IZ+XpUjwsgvtY2616sOpUynK+l9ykQDRNpaJ1hYfuximUiA=="],
"react-i18next": ["react-i18next@17.0.14", "", { "dependencies": { "@babel/runtime": "^7.29.7", "html-parse-stringify": "^4.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 26.2.0", "react": ">= 16.8.0", "react-dom": "*", "react-native": "*", "typescript": "^5 || ^6 || ^7" }, "optionalPeers": ["react-dom", "react-native", "typescript"] }, "sha512-ZpMBfJL3BiXPuYHj5QMY1GwvKJNhE1vCxOJQ8BoMBdHP5XONTfi/Qss1nuRlnFdIl72PKo2jPmw+fxuLjRFgaQ=="], "react-i18next": ["react-i18next@17.0.14", "", { "dependencies": { "@babel/runtime": "^7.29.7", "html-parse-stringify": "^4.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 26.2.0", "react": ">= 16.8.0", "react-dom": "*", "react-native": "*", "typescript": "^5 || ^6 || ^7" }, "optionalPeers": ["react-dom", "react-native", "typescript"] }, "sha512-ZpMBfJL3BiXPuYHj5QMY1GwvKJNhE1vCxOJQ8BoMBdHP5XONTfi/Qss1nuRlnFdIl72PKo2jPmw+fxuLjRFgaQ=="],
"react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
"react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
"react-window": ["react-window@2.3.3", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-uu/dTwysXp+YL+i5GW/cC/3JrqyE7oE6DYXYVlTeMaDSDgCI+UGo+XCKwXfS8uoFPih8TJYTnikqjAnbDvn75Q=="],
"read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="], "read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="],
"readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
@@ -2131,7 +2246,7 @@
"tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
"tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], "tailwind-merge": ["tailwind-merge@3.7.0", "", {}, "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA=="],
"tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="], "tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="],
@@ -2243,6 +2358,10 @@
"update-browserslist-db": ["update-browserslist-db@1.3.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw=="], "update-browserslist-db": ["update-browserslist-db@1.3.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw=="],
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
"utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="], "utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="],
@@ -2351,7 +2470,7 @@
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
"zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], "zustand": ["zustand@5.0.15", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A=="],
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
@@ -2419,6 +2538,8 @@
"@scalar/types/zod": ["zod@4.5.4", "", {}, "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA=="], "@scalar/types/zod": ["zod@4.5.4", "", {}, "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA=="],
"@scalar/use-hooks/tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="],
"@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], "@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="],
@@ -2457,6 +2578,8 @@
"@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@xyflow/react/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="],
"accepts/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "accepts/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
"app-builder-lib/@electron/get": ["@electron/get@3.1.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ=="], "app-builder-lib/@electron/get": ["@electron/get@3.1.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ=="],
+1 -1
View File
@@ -19,7 +19,7 @@ COPY electron/package.json ./electron/
RUN bun install --frozen-lockfile RUN bun install --frozen-lockfile
# Build the maintained Electron renderer as the browser UI. `frontend/src` # Build the maintained Electron renderer as the browser UI. `electron/src/shared`
# contains transitional shared modules only; it has no app entry point. # contains transitional shared modules only; it has no app entry point.
COPY frontend/ ./frontend/ COPY frontend/ ./frontend/
COPY electron/ ./electron/ COPY electron/ ./electron/
+1 -1
View File
@@ -53,7 +53,7 @@ Live dictation uses a WebSocket, which cannot pass through the app protocol HTTP
`apiJson<T>(path, init?)`; `audioUrl(filename)` → `/api/audio/<filename>`; `apiJson<T>(path, init?)`; `audioUrl(filename)` → `/api/audio/<filename>`;
`profileAudioUrl(id)` → `/api/profiles/<id>/audio`. `profileAudioUrl(id)` → `/api/profiles/<id>/audio`.
- `@/lib/api/generate.ts`: `generateClone(input: CloneGenerateInput, opts?: { signal?: AbortSignal; onProgress?: (pct: number|null) => void }): Promise<GenerateResult>`; - `@/lib/api/generate.ts`: `generateClone(input: CloneGenerateInput, opts?: { signal?: AbortSignal; onProgress?: (pct: number|null) => void }): Promise<GenerateResult>`;
`sanitizeInstruct(free: string): { instruct: string; unsupported: string[]; duplicates: string[]; conflicts: string[] }` (port of frontend/src/utils/voiceInstruct.js buildDesignInstruct with empty vdStates); `sanitizeInstruct(free: string): { instruct: string; unsupported: string[]; duplicates: string[]; conflicts: string[] }` (port of electron/src/shared/utils/voiceInstruct.js buildDesignInstruct with empty vdStates);
`CLONE_MAX_SECONDS = 15`, `REF_HARD_MAX_SECONDS = 75`. `CLONE_MAX_SECONDS = 15`, `REF_HARD_MAX_SECONDS = 75`.
- `@/lib/api/profiles.ts`: `listProfiles()`, `createCloneProfile({ name, refAudio, refAudioName, refText, instruct, language })`, `deleteProfile(id)`. - `@/lib/api/profiles.ts`: `listProfiles()`, `createCloneProfile({ name, refAudio, refAudioName, refText, instruct, language })`, `deleteProfile(id)`.
- `@/lib/api/history.ts`: `listHistory()`, `clearHistory()`, `deleteHistoryItem(id)`, `setHistoryStarred(id, starred)`. - `@/lib/api/history.ts`: `listHistory()`, `clearHistory()`, `deleteHistoryItem(id)`, `setHistoryStarred(id, starred)`.
+1
View File
@@ -75,6 +75,7 @@ export default defineConfig({
resolve: { resolve: {
alias: { alias: {
'@': resolve(__dirname, 'src/renderer/src'), '@': resolve(__dirname, 'src/renderer/src'),
'@shared': resolve(__dirname, 'src/shared'),
// Scalar's optional AI client reaches a CommonJS browser entry that // Scalar's optional AI client reaches a CommonJS browser entry that
// native ESM cannot import by name. Mirror its browser API as real ESM. // native ESM cannot import by name. Mirror its browser API as real ESM.
'@vercel/oidc': resolve(__dirname, 'src/renderer/src/lib/vercel-oidc-browser.ts'), '@vercel/oidc': resolve(__dirname, 'src/renderer/src/lib/vercel-oidc-browser.ts'),
+21 -1
View File
@@ -39,12 +39,24 @@
"@electron-toolkit/preload": "^3.0.2", "@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0", "@electron-toolkit/utils": "^4.0.0",
"@fontsource-variable/inter": "^5.3.0", "@fontsource-variable/inter": "^5.3.0",
"@fontsource-variable/source-serif-4": "^5.3.0",
"@fontsource/ibm-plex-mono": "^5.3.0", "@fontsource/ibm-plex-mono": "^5.3.0",
"@lobehub/icons-static-svg": "^1.95.0", "@lobehub/icons-static-svg": "^1.95.0",
"@radix-ui/react-dialog": "^1.1.23",
"@radix-ui/react-dropdown-menu": "^2.1.24",
"@radix-ui/react-progress": "^1.1.16",
"@radix-ui/react-select": "^2.3.7",
"@radix-ui/react-slider": "^1.4.7",
"@radix-ui/react-slot": "^1.3.3",
"@radix-ui/react-tabs": "^1.1.21",
"@radix-ui/react-toggle": "^1.1.18",
"@radix-ui/react-toggle-group": "^1.1.19",
"@radix-ui/react-tooltip": "^1.2.16",
"@scalar/api-reference-react": "^0.9.67", "@scalar/api-reference-react": "^0.9.67",
"@tanstack/react-form": "^1.33.5", "@tanstack/react-form": "^1.33.5",
"@tanstack/react-pacer": "^0.23.0", "@tanstack/react-pacer": "^0.23.0",
"@tanstack/react-query": "^5.102.8", "@tanstack/react-query": "^5.102.8",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-router": "^1.170.36", "@tanstack/react-router": "^1.170.36",
"@tanstack/react-store": "^0.11.1", "@tanstack/react-store": "^0.11.1",
"@tanstack/react-virtual": "^3.14.13", "@tanstack/react-virtual": "^3.14.13",
@@ -52,24 +64,32 @@
"@vidstack/react": "1.15.6", "@vidstack/react": "1.15.6",
"@xyflow/react": "^12.12.0", "@xyflow/react": "^12.12.0",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cn": "^0.3.0", "cn": "^0.3.0",
"country-flag-icons": "^1.6.20",
"dashjs": "^5.2.1", "dashjs": "^5.2.1",
"electron-updater": "^6.8.9", "electron-updater": "^6.8.9",
"hls.js": "^1.7.3", "hls.js": "^1.7.3",
"html-entities": "^2.6.0",
"i18next": "^26.4.2", "i18next": "^26.4.2",
"i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^1.46.0", "lucide-react": "^1.46.0",
"posthog-js": "^1.434.14", "posthog-js": "^1.434.14",
"proxy-agent": "6.5.0", "proxy-agent": "6.5.0",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"react": "^19.3.0", "react": "^19.3.0",
"react-dom": "^19.3.0", "react-dom": "^19.3.0",
"react-hot-toast": "^2.6.0",
"react-i18next": "^17.0.14", "react-i18next": "^17.0.14",
"react-window": "^2.3.1",
"remotion": "4.0.524", "remotion": "4.0.524",
"shadcn": "^4.21.0", "shadcn": "^4.21.0",
"sonner": "^2.0.8", "sonner": "^2.0.8",
"tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"wavesurfer.js": "^7.12.12", "wavesurfer.js": "^7.12.12",
"word-extractor": "1.0.4" "word-extractor": "1.0.4",
"zustand": "^5.0.15"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/vite": "^4.3.3", "@tailwindcss/vite": "^4.3.3",
+2 -2
View File
@@ -1,7 +1,7 @@
import { readFileSync, writeFileSync, renameSync } from 'node:fs'; import { readFileSync, writeFileSync, renameSync } from 'node:fs';
import type { NativeCrashRecord } from '../preload/index.d'; import type { NativeCrashRecord } from '../preload/index.d';
import { nativeCrashExcerpt } from '../../../frontend/src/utils/crashReport'; import { nativeCrashExcerpt } from '../shared/utils/crashReport';
import { scrubText } from '../../../frontend/src/utils/scrub'; import { scrubText } from '../shared/utils/scrub';
/** Small version-scoped local journal. Read failures must never block startup. */ /** Small version-scoped local journal. Read failures must never block startup. */
export class CrashJournal { export class CrashJournal {
+1 -1
View File
@@ -1,6 +1,6 @@
// @vitest-environment node // @vitest-environment node
import { expect, it } from 'vitest'; import { expect, it } from 'vitest';
import { clampCrashTail } from '../../../frontend/src/utils/crashReport'; import { clampCrashTail } from '../shared/utils/crashReport';
it.each(['Fatal Python error: Segmentation fault', 'Windows fatal exception: access violation'])( it.each(['Fatal Python error: Segmentation fault', 'Windows fatal exception: access violation'])(
'preserves native fault frames under the report character limit: %s', 'preserves native fault frames under the report character limit: %s',
+1 -1
View File
@@ -1,6 +1,6 @@
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'; import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
import { dirname } from 'node:path'; import { dirname } from 'node:path';
import { scrubText } from '../../../frontend/src/utils/scrub'; import { scrubText } from '../shared/utils/scrub';
const MAX_RECORDS = 20; const MAX_RECORDS = 20;
const MAX_DETAIL = 20_000; const MAX_DETAIL = 20_000;
@@ -12,7 +12,7 @@ import {
capturePageview, capturePageview,
disableAnalytics, disableAnalytics,
enableAnalytics, enableAnalytics,
} from '../../../../../frontend/src/utils/analytics'; } from '@shared/utils/analytics';
import { routeBreadcrumb } from '@/lib/report-breadcrumb'; import { routeBreadcrumb } from '@/lib/report-breadcrumb';
interface AnalyticsState { interface AnalyticsState {
@@ -27,7 +27,7 @@ import { getBridge } from '@/components/bridge';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { getFrontendLogs } from '../../../../../../frontend/src/utils/consoleBuffer'; import { getFrontendLogs } from '@shared/utils/consoleBuffer';
import { useBackendStatus } from '@/hooks/use-backend-status'; import { useBackendStatus } from '@/hooks/use-backend-status';
import type { import type {
RepairAgentId, RepairAgentId,
@@ -34,7 +34,7 @@ import { ComputeVendorIcon, formatComputeRuntime } from '@/components/compute-ve
import { Popover, PopoverContent, PopoverTrigger } from '@/components/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/popover';
import { ComputeTargetChoices } from '@/components/compute-target-choices'; import { ComputeTargetChoices } from '@/components/compute-target-choices';
import { useComputeRuntime, useComputeTarget } from '@/hooks/use-compute-target'; import { useComputeRuntime, useComputeTarget } from '@/hooks/use-compute-target';
import type { BatchJob } from '../../../../../../frontend/src/api/batch-types'; import type { BatchJob } from '@shared/api/batch-types';
import { import {
IDLE_STATUS_POLL_MS, IDLE_STATUS_POLL_MS,
batchStatusPollMs, batchStatusPollMs,
@@ -1,7 +1,7 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useBackendStatus } from '@/hooks/use-backend-status'; import { useBackendStatus } from '@/hooks/use-backend-status';
import { scrubText } from '../../../../../frontend/src/utils/scrub'; import { scrubText } from '@shared/utils/scrub';
import { describeExitCode } from '../../../../../frontend/src/utils/nativeExit'; import { describeExitCode } from '@shared/utils/nativeExit';
import { ReportBug } from './report-bug'; import { ReportBug } from './report-bug';
import { getBridge } from './bridge'; import { getBridge } from './bridge';
@@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { DICTATION_SCRIPTS } from '../../../../../frontend/src/utils/dictationScripts'; import { DICTATION_SCRIPTS } from '@shared/utils/dictationScripts';
import { apiFetch, apiJson, apiPath, ApiError } from '@/lib/api/client'; import { apiFetch, apiJson, apiPath, ApiError } from '@/lib/api/client';
import { Button } from './ui/button'; import { Button } from './ui/button';
import { AudioPreviewButton } from './audio-preview-button'; import { AudioPreviewButton } from './audio-preview-button';
@@ -2,17 +2,17 @@ import {
classifyError, classifyError,
urlFor, urlFor,
type ErrorClass, type ErrorClass,
} from '../../../../../frontend/src/utils/errorDocsMap'; } from '@shared/utils/errorDocsMap';
import { ExternalLink } from './external-link'; import { ExternalLink } from './external-link';
import { buildIssueSearchUrl } from '../../../../../frontend/src/utils/bugReportDocument'; import { buildIssueSearchUrl } from '@shared/utils/bugReportDocument';
import { Component, useEffect, type ReactNode, type ErrorInfo } from 'react'; import { Component, useEffect, type ReactNode, type ErrorInfo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { BotIcon, CircleAlertIcon } from 'lucide-react'; import { BotIcon, CircleAlertIcon } from 'lucide-react';
import { scrubText } from '../../../../../frontend/src/utils/scrub'; import { scrubText } from '@shared/utils/scrub';
import { ReportBug } from './report-bug'; import { ReportBug } from './report-bug';
import { Button } from './ui/button'; import { Button } from './ui/button';
import { openRepairAgent } from '@/lib/repair-agent-events'; import { openRepairAgent } from '@/lib/repair-agent-events';
import { captureException } from '../../../../../frontend/src/utils/analytics'; import { captureException } from '@shared/utils/analytics';
const MODULE_RETRY_PREFIX = 'voicestudio.moduleRetry:'; const MODULE_RETRY_PREFIX = 'voicestudio.moduleRetry:';
const reloadRenderer = () => window.location.reload(); const reloadRenderer = () => window.location.reload();
@@ -3,12 +3,12 @@ import { useTranslation } from 'react-i18next';
import { useGenerateClone } from '@/hooks/use-generate'; import { useGenerateClone } from '@/hooks/use-generate';
import { router } from '@/router'; import { router } from '@/router';
import { writeDraft } from '@/features/design/design-draft'; import { writeDraft } from '@/features/design/design-draft';
import defaults from '../../../../../frontend/src/utils/firstSound.json'; import defaults from '@shared/utils/firstSound.json';
import { import {
instructToVdStates, instructToVdStates,
mergeDescribedAttrs, mergeDescribedAttrs,
} from '../../../../../frontend/src/utils/voiceInstruct'; } from '@shared/utils/voiceInstruct';
import { pickDesignSeed } from '../../../../../frontend/src/utils/seed'; import { pickDesignSeed } from '@shared/utils/seed';
import { FIRST_SOUND_EVENT } from '@/lib/first-sound'; import { FIRST_SOUND_EVENT } from '@/lib/first-sound';
/** /**
@@ -3,7 +3,7 @@ import {
inlineSegments, inlineSegments,
parseBlocks, parseBlocks,
type MarkdownBlock, type MarkdownBlock,
} from '../../../../../frontend/src/utils/markdownLite'; } from '@shared/utils/markdownLite';
function InlineMarkdown({ text }: { text: string }) { function InlineMarkdown({ text }: { text: string }) {
return inlineSegments(text).map((segment, index) => { return inlineSegments(text).map((segment, index) => {
@@ -17,7 +17,7 @@ import { RemotionProviderLoader } from '@vidstack/react/player/remotion';
import { useEffect, useMemo, useRef, type RefObject } from 'react'; import { useEffect, useMemo, useRef, type RefObject } from 'react';
import { claimPlayback } from '@/lib/audio/playback'; import { claimPlayback } from '@/lib/audio/playback';
import { useAecEnabled } from '@/lib/store/dictation-settings'; import { useAecEnabled } from '@/lib/store/dictation-settings';
import { attachPlaybackTap } from '../../../../../frontend/src/utils/aec/playbackTap'; import { attachPlaybackTap } from '@shared/utils/aec/playbackTap';
export type { MediaPlayerInstance }; export type { MediaPlayerInstance };
export { useMediaState } from '@vidstack/react'; export { useMediaState } from '@vidstack/react';
@@ -2,7 +2,7 @@ import { AlertCircleIcon, CopyIcon, XIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { urlFor } from '../../../../../frontend/src/utils/errorDocsMap'; import { urlFor } from '@shared/utils/errorDocsMap';
import type { PublicFailure } from '@/lib/api/failure'; import type { PublicFailure } from '@/lib/api/failure';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { ExternalLink } from './external-link'; import { ExternalLink } from './external-link';
@@ -14,11 +14,11 @@ vi.mock('@/hooks/use-backend-status', () => ({
})); }));
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'; import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { afterEach, beforeEach, expect, it, vi } from 'vitest'; import { afterEach, beforeEach, expect, it, vi } from 'vitest';
import { addBreadcrumb, clearBreadcrumbs } from '../../../../../frontend/src/utils/breadcrumbs'; import { addBreadcrumb, clearBreadcrumbs } from '@shared/utils/breadcrumbs';
import { import {
_resetBackendContactForTests, _resetBackendContactForTests,
recordBackendContact, recordBackendContact,
} from '../../../../../frontend/src/utils/backendContact'; } from '@shared/utils/backendContact';
const mock = vi.hoisted(() => ({ const mock = vi.hoisted(() => ({
crashed: false, crashed: false,
native: true, native: true,
@@ -1,15 +1,15 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { composeBugReportUrl } from '../../../../../frontend/src/utils/bugReportDocument'; import { composeBugReportUrl } from '@shared/utils/bugReportDocument';
import { formatBreadcrumbs } from '../../../../../frontend/src/utils/breadcrumbs'; import { formatBreadcrumbs } from '@shared/utils/breadcrumbs';
import { contactAge, lastBackendContact } from '../../../../../frontend/src/utils/backendContact'; import { contactAge, lastBackendContact } from '@shared/utils/backendContact';
import { clampCrashTail } from '../../../../../frontend/src/utils/crashReport'; import { clampCrashTail } from '@shared/utils/crashReport';
import { scrubText } from '../../../../../frontend/src/utils/scrub'; import { scrubText } from '@shared/utils/scrub';
import { describeExitCode, isNativeFaultExit } from '../../../../../frontend/src/utils/nativeExit'; import { describeExitCode, isNativeFaultExit } from '@shared/utils/nativeExit';
import { import {
_adaptLastRunCrash, _adaptLastRunCrash,
type LastRunCrashRecord, type LastRunCrashRecord,
} from '../../../../../frontend/src/utils/runCrashRecord'; } from '@shared/utils/runCrashRecord';
import { getBackendStatusSnapshot } from '@/hooks/use-backend-status'; import { getBackendStatusSnapshot } from '@/hooks/use-backend-status';
import { apiJson } from '@/lib/api/client'; import { apiJson } from '@/lib/api/client';
import { router } from '@/router'; import { router } from '@/router';
@@ -6,7 +6,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { apiJson } from '@/lib/api/client'; import { apiJson } from '@/lib/api/client';
import type { PreflightReport } from '../../../../../frontend/src/api/setup-types'; import type { PreflightReport } from '@shared/api/setup-types';
import { SystemPreflight } from '@/features/settings/system-preflight'; import { SystemPreflight } from '@/features/settings/system-preflight';
import { PermissionsSettings } from '@/features/settings/permissions-settings'; import { PermissionsSettings } from '@/features/settings/permissions-settings';
import { SetupMediaEngine } from '@/features/settings/media-tools'; import { SetupMediaEngine } from '@/features/settings/media-tools';
@@ -1,7 +1,7 @@
import { useEffect, useRef, useState, type FormEvent, type ReactNode } from 'react'; import { useEffect, useRef, useState, type FormEvent, type ReactNode } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { KeyRoundIcon } from 'lucide-react'; import { KeyRoundIcon } from 'lucide-react';
import { exchangeApiKey } from '../../../../../frontend/src/api/authSession'; import { exchangeApiKey } from '@shared/api/authSession';
import { absoluteApiBase, ApiError, apiFetch } from '@/lib/api/client'; import { absoluteApiBase, ApiError, apiFetch } from '@/lib/api/client';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
@@ -32,10 +32,10 @@ import { useTtsReadiness } from '@/hooks/use-tts-readiness';
import { getBridge } from '@/components/bridge'; import { getBridge } from '@/components/bridge';
import { apiJson, apiPath, describeError } from '@/lib/api/client'; import { apiJson, apiPath, describeError } from '@/lib/api/client';
import { saveExport } from '@/lib/export-history'; import { saveExport } from '@/lib/export-history';
import { LANG_CODES } from '../../../../../../frontend/src/utils/languages'; import { LANG_CODES } from '@shared/utils/languages';
import { PRESETS } from '../../../../../../frontend/src/utils/constants'; import { PRESETS } from '@shared/utils/constants';
import type { BatchJob } from '../../../../../../frontend/src/api/batch-types'; import type { BatchJob } from '@shared/api/batch-types';
import { generationFailureMessage } from '../../../../../../frontend/src/utils/generationFailureMessage'; import { generationFailureMessage } from '@shared/utils/generationFailureMessage';
import { enqueueVideos } from './enqueue'; import { enqueueVideos } from './enqueue';
import { useTranslationEngines } from '@/features/settings/translation-settings'; import { useTranslationEngines } from '@/features/settings/translation-settings';
const languageOptions = LANG_CODES.map((item) => item.label); const languageOptions = LANG_CODES.map((item) => item.label);
@@ -8,7 +8,7 @@ import { describeError } from '@/lib/api/client';
import { import {
createIngestTracker, createIngestTracker,
WATCH_POLL_MS, WATCH_POLL_MS,
} from '../../../../../../frontend/src/utils/watchFolderTracker'; } from '@shared/utils/watchFolderTracker';
import type { WatchSelection } from '../../../../preload/index.d'; import type { WatchSelection } from '../../../../preload/index.d';
export function WatchFolder({ export function WatchFolder({
@@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query';
import { useNavigate } from '@tanstack/react-router'; import { useNavigate } from '@tanstack/react-router';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FolderOpenIcon } from 'lucide-react'; import { FolderOpenIcon } from 'lucide-react';
import type { ProfileUsage } from '../../../../../../frontend/src/api/types'; import type { ProfileUsage } from '@shared/api/types';
import { apiJson, describeError } from '@/lib/api/client'; import { apiJson, describeError } from '@/lib/api/client';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { PipelineFailure } from '@/components/pipeline-failure'; import { PipelineFailure } from '@/components/pipeline-failure';
@@ -2,8 +2,8 @@ import type { HistoryItem, Profile } from '@/lib/api/types';
import { import {
instructToVdStates, instructToVdStates,
mergeDescribedAttrs, mergeDescribedAttrs,
} from '../../../../../../frontend/src/utils/voiceInstruct'; } from '@shared/utils/voiceInstruct';
import { pickDesignSeed } from '../../../../../../frontend/src/utils/seed'; import { pickDesignSeed } from '@shared/utils/seed';
export const STORAGE = 'voicestudio.design.v1'; export const STORAGE = 'voicestudio.design.v1';
export const DESIGN_DRAFT_EVENT = 'voicestudio:design-draft'; export const DESIGN_DRAFT_EVENT = 'voicestudio:design-draft';
@@ -50,13 +50,13 @@ import { queryKeys } from '@/lib/query';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { cloneSettingsStore } from '@/lib/store/clone-settings'; import { cloneSettingsStore } from '@/lib/store/clone-settings';
import type { Profile } from '@/lib/api/types'; import type { Profile } from '@/lib/api/types';
import { CATEGORIES, PRESETS } from '../../../../../../frontend/src/utils/constants'; import { CATEGORIES, PRESETS } from '@shared/utils/constants';
import { import {
applyVdState, applyVdState,
buildDesignInstruct, buildDesignInstruct,
mergeDescribedAttrs, mergeDescribedAttrs,
} from '../../../../../../frontend/src/utils/voiceInstruct'; } from '@shared/utils/voiceInstruct';
import { pickDesignSeed } from '../../../../../../frontend/src/utils/seed'; import { pickDesignSeed } from '@shared/utils/seed';
export function DesignPage() { export function DesignPage() {
const { t } = useTranslation(); const { t } = useTranslation();
const selectedTake = useSelectedTake(); const selectedTake = useSelectedTake();
@@ -12,7 +12,7 @@ import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { apiJson } from '@/lib/api/client'; import { apiJson } from '@/lib/api/client';
import { instructToVdStates } from '../../../../../../frontend/src/utils/voiceInstruct'; import { instructToVdStates } from '@shared/utils/voiceInstruct';
interface Personality { interface Personality {
id: string; id: string;
@@ -15,12 +15,12 @@ import {
import { ProfileAvatar } from '@/components/profile-avatar'; import { ProfileAvatar } from '@/components/profile-avatar';
import type { Profile } from '@/lib/api/types'; import type { Profile } from '@/lib/api/types';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { PRESETS } from '../../../../../../frontend/src/utils/constants'; import { PRESETS } from '@shared/utils/constants';
import { import {
autoProfileId, autoProfileId,
castParts, castParts,
castSpeakers, castSpeakers,
} from '../../../../../../frontend/src/utils/segments'; } from '@shared/utils/segments';
import type { DubSegment } from './dub-session'; import type { DubSegment } from './dub-session';
const DEFAULT_VOICE = '__default__'; const DEFAULT_VOICE = '__default__';
@@ -7,18 +7,18 @@ import { AgentFixButton } from '@/components/agent-fix-button';
import { getBridge } from '@/components/bridge'; import { getBridge } from '@/components/bridge';
import { WorkspaceHeader } from '@/components/app-shell/workspace-header'; import { WorkspaceHeader } from '@/components/app-shell/workspace-header';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { MAX_COOKIE_EXPORT_BYTES } from '../../../../../../frontend/src/utils/cookieExport'; import { MAX_COOKIE_EXPORT_BYTES } from '@shared/utils/cookieExport';
import { import {
hasCompleteTranslation, hasCompleteTranslation,
multiLangTargets, multiLangTargets,
} from '../../../../../../frontend/src/utils/multiLang'; } from '@shared/utils/multiLang';
import { segmentGenInputs } from '../../../../../../frontend/src/utils/segments'; import { segmentGenInputs } from '@shared/utils/segments';
import { clampSegmentEdit } from '../../../../../../frontend/src/utils/timeline'; import { clampSegmentEdit } from '@shared/utils/timeline';
import { import {
dialectLabel, dialectLabel,
dialectMatchesLang, dialectMatchesLang,
dialectOptionsFor, dialectOptionsFor,
} from '../../../../../../frontend/src/api/dialects'; } from '@shared/api/dialects';
import { DubExportPanel } from './dub-export-panel'; import { DubExportPanel } from './dub-export-panel';
import { DubTimeline } from './dub-timeline'; import { DubTimeline } from './dub-timeline';
import { PasteTranslation } from './paste-translation'; import { PasteTranslation } from './paste-translation';
@@ -97,7 +97,7 @@ import { apiFetch, apiJson, apiPath, describeError, isAbortError } from '@/lib/a
import { createObjectUrl, revokeObjectUrl } from '@/lib/audio/object-url'; import { createObjectUrl, revokeObjectUrl } from '@/lib/audio/object-url';
import { requestPlaybackSeek } from '@/lib/audio/playback-clock'; import { requestPlaybackSeek } from '@/lib/audio/playback-clock';
import { beginAppActivity } from '@/lib/app-activity'; import { beginAppActivity } from '@/lib/app-activity';
import { LANG_CODES } from '../../../../../../frontend/src/utils/languages'; import { LANG_CODES } from '@shared/utils/languages';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { import {
useDubSession, useDubSession,
@@ -3,7 +3,7 @@ import type { DubExportPreferences } from './dub-export';
import { import {
MAX_COOKIE_EXPORT_BYTES, MAX_COOKIE_EXPORT_BYTES,
_cookieTransportAllowed, _cookieTransportAllowed,
} from '../../../../../../frontend/src/utils/cookieExport'; } from '@shared/utils/cookieExport';
import { projectSession, type DubProject } from '../projects/project-format'; import { projectSession, type DubProject } from '../projects/project-format';
import { DUB_DRAFT_KEY, restoreDubDraft } from './dub-draft'; import { DUB_DRAFT_KEY, restoreDubDraft } from './dub-draft';
import { Store } from '@tanstack/store'; import { Store } from '@tanstack/store';
@@ -22,8 +22,8 @@ import {
settleCueSources, settleCueSources,
withOriginalCueSource, withOriginalCueSource,
withoutCueSource, withoutCueSource,
} from '../../../../../../frontend/src/utils/segments'; } from '@shared/utils/segments';
import { hasCompleteTranslation } from '../../../../../../frontend/src/utils/multiLang'; import { hasCompleteTranslation } from '@shared/utils/multiLang';
import { import {
ATTRIBUTION_FIELDS, ATTRIBUTION_FIELDS,
applyAttribution, applyAttribution,
@@ -36,9 +36,9 @@ import {
nextSegmentId, nextSegmentId,
partsFor, partsFor,
type SegmentPart, type SegmentPart,
} from '../../../../../../frontend/src/utils/segmentParts'; } from '@shared/utils/segmentParts';
import { commitMoveResize } from '../../../../../../frontend/src/utils/timeline'; import { commitMoveResize } from '@shared/utils/timeline';
import type { PasteTranslationRow } from '../../../../../../frontend/src/utils/pasteTranslations'; import type { PasteTranslationRow } from '@shared/utils/pasteTranslations';
import type { import type {
DubAgentTranslationRequest, DubAgentTranslationRequest,
DubAgentTranslationResult, DubAgentTranslationResult,
@@ -7,7 +7,7 @@ import {
nearestOnset, nearestOnset,
snapCandidates, snapCandidates,
snapTime, snapTime,
} from '../../../../../../frontend/src/utils/timeline'; } from '@shared/utils/timeline';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { import {
@@ -2,11 +2,11 @@
* Carrying a finished dub into the Stories editor. * Carrying a finished dub into the Stories editor.
* *
* The transform itself is shared with the browser app * The transform itself is shared with the browser app
* (`frontend/src/utils/dubToStory`); this is the Electron wiring around it, kept * (`electron/src/shared/utils/dubToStory`); this is the Electron wiring around it, kept
* out of `dub-page.tsx` so the rules that decide whether the action is offered, * out of `dub-page.tsx` so the rules that decide whether the action is offered,
* and what it overwrites, can be tested without mounting the page. * and what it overwrites, can be tested without mounting the page.
*/ */
import { dubToStory } from '../../../../../../frontend/src/utils/dubToStory'; import { dubToStory } from '@shared/utils/dubToStory';
import { import {
editLongform, editLongform,
longformSession, longformSession,
@@ -1,4 +1,4 @@
import { readTextFile } from '../../../../../../frontend/src/utils/readTextFile'; import { readTextFile } from '@shared/utils/readTextFile';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { AlertTriangleIcon, ClipboardPasteIcon, FileTextIcon, XIcon } from 'lucide-react'; import { AlertTriangleIcon, ClipboardPasteIcon, FileTextIcon, XIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -10,7 +10,7 @@ import {
detectPasteMode, detectPasteMode,
type PasteTranslationCue, type PasteTranslationCue,
type PasteTranslationRow, type PasteTranslationRow,
} from '../../../../../../frontend/src/utils/pasteTranslations'; } from '@shared/utils/pasteTranslations';
import type { DubSegment } from './dub-session'; import type { DubSegment } from './dub-session';
const PREVIEW_LIMIT = 120; const PREVIEW_LIMIT = 120;
@@ -2,7 +2,7 @@ import { createStreamingPreview, supportsStreamingPreview } from '@/lib/audio/st
import { backendWebSocketUrl } from '@/lib/api/websocket'; import { backendWebSocketUrl } from '@/lib/api/websocket';
import { beginAppActivity } from '@/lib/app-activity'; import { beginAppActivity } from '@/lib/app-activity';
import { acquireSynthesis } from '@/lib/synthesis-lock'; import { acquireSynthesis } from '@/lib/synthesis-lock';
import { segmentGenInputs } from '../../../../../../frontend/src/utils/segments'; import { segmentGenInputs } from '@shared/utils/segments';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -15,11 +15,11 @@ import { STORAGE, readDraft } from '@/features/design/design-draft';
import { import {
instructToVdStates, instructToVdStates,
mergeDescribedAttrs, mergeDescribedAttrs,
} from '../../../../../../frontend/src/utils/voiceInstruct'; } from '@shared/utils/voiceInstruct';
import type { import type {
CommunityItem, CommunityItem,
CommunityPage, CommunityPage,
} from '../../../../../../frontend/src/api/community-types'; } from '@shared/api/community-types';
import type { Profile } from '@/lib/api/types'; import type { Profile } from '@/lib/api/types';
import type { Mode } from '@/features/longform/longform-session'; import type { Mode } from '@/features/longform/longform-session';
import { placeLongformVoice } from './place-voice'; import { placeLongformVoice } from './place-voice';
@@ -1,5 +1,5 @@
import { apiJson } from '@/lib/api/client'; import { apiJson } from '@/lib/api/client';
import type { ArchetypePage } from '../../../../../../frontend/src/api/archetypes-types'; import type { ArchetypePage } from '@shared/api/archetypes-types';
export const FAVORITES_KEY = 'voicestudio.gallery.favorites.v1'; export const FAVORITES_KEY = 'voicestudio.gallery.favorites.v1';
export function readFavorites(): string[] { export function readFavorites(): string[] {
try { try {
@@ -16,7 +16,7 @@ import { VoiceDestinations } from './voice-destinations';
import { placeLongformVoice } from './place-voice'; import { placeLongformVoice } from './place-voice';
import type { Mode } from '@/features/longform/longform-session'; import type { Mode } from '@/features/longform/longform-session';
import { ImportsView } from './imports-view'; import { ImportsView } from './imports-view';
import { FACETS } from '../../../../../../frontend/src/components/gallery/facets'; import { FACETS } from '@shared/components/gallery/facets';
import { FAVORITES_KEY, readFavorites, favoriteCatalogue } from './favorites'; import { FAVORITES_KEY, readFavorites, favoriteCatalogue } from './favorites';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { useNavigate } from '@tanstack/react-router'; import { useNavigate } from '@tanstack/react-router';
@@ -33,8 +33,8 @@ import type { Profile } from '@/lib/api/types';
import type { import type {
ArchetypePage, ArchetypePage,
ArchetypeCategory, ArchetypeCategory,
} from '../../../../../../frontend/src/api/archetypes-types'; } from '@shared/api/archetypes-types';
import { mergeDescribedAttrs } from '../../../../../../frontend/src/utils/voiceInstruct'; import { mergeDescribedAttrs } from '@shared/utils/voiceInstruct';
import { STORAGE, readDraft } from '@/features/design/design-draft'; import { STORAGE, readDraft } from '@/features/design/design-draft';
import { setCloneSetting } from '@/lib/store/clone-settings'; import { setCloneSetting } from '@/lib/store/clone-settings';
@@ -3,8 +3,8 @@ import type {
DownloadParams, DownloadParams,
GalleryVoice, GalleryVoice,
YoutubeSearchResult, YoutubeSearchResult,
} from '../../../../../../frontend/src/api/gallery-types'; } from '@shared/api/gallery-types';
import type { PersonaImportResult } from '../../../../../../frontend/src/api/types'; import type { PersonaImportResult } from '@shared/api/types';
export type { GalleryVoice, YoutubeSearchResult }; export type { GalleryVoice, YoutubeSearchResult };
export const importsApi = { export const importsApi = {
list: (signal?: AbortSignal) => apiJson<GalleryVoice[]>('/gallery/voices', { signal }), list: (signal?: AbortSignal) => apiJson<GalleryVoice[]>('/gallery/voices', { signal }),
@@ -12,7 +12,7 @@ import {
decodeToMonoLowRate, decodeToMonoLowRate,
sliceToMono, sliceToMono,
encodeWav, encodeWav,
} from '../../../../../../frontend/src/utils/audioTrim'; } from '@shared/utils/audioTrim';
const MAX_TRIM_SECONDS = 60; const MAX_TRIM_SECONDS = 60;
@@ -5,7 +5,7 @@ import '@/i18n';
import { IntegrationDetailPage } from './integration-detail-page'; import { IntegrationDetailPage } from './integration-detail-page';
import { INTEGRATION_SETUPS } from './setup-registry'; import { INTEGRATION_SETUPS } from './setup-registry';
import en from '@/i18n/locales/en.json'; import en from '@/i18n/locales/en.json';
vi.mock('../../../../../../frontend/src/config/sponsors', () => ({ vi.mock('@shared/config/sponsors', () => ({
SPONSORS: [ SPONSORS: [
{ name: 'Example sponsor', url: 'https://example.org', logoUrl: '/example.svg', tier: 'gold' }, { name: 'Example sponsor', url: 'https://example.org', logoUrl: '/example.svg', tier: 'gold' },
], ],
@@ -22,8 +22,8 @@ import {
getIntegrationBySlug, getIntegrationBySlug,
integrationSlug, integrationSlug,
type IntegrationCatalogEntry, type IntegrationCatalogEntry,
} from '../../../../../../frontend/src/config/integration-catalog'; } from '@shared/config/integration-catalog';
import { SPONSORS } from '../../../../../../frontend/src/config/sponsors'; import { SPONSORS } from '@shared/config/sponsors';
import './integrations-page.css'; import './integrations-page.css';
/** Catalog entries and featured sponsors both have an in-app page. */ /** Catalog entries and featured sponsors both have an in-app page. */
@@ -18,7 +18,7 @@ vi.mock('@/components/app-shell/workspace-header', () => ({
vi.mock('@/hooks/use-backend-status', () => ({ vi.mock('@/hooks/use-backend-status', () => ({
useBackendStatus: () => ({ baseUrl: 'http://127.0.0.1:3912' }), useBackendStatus: () => ({ baseUrl: 'http://127.0.0.1:3912' }),
})); }));
vi.mock('../../../../../../frontend/src/config/sponsors', () => ({ vi.mock('@shared/config/sponsors', () => ({
SPONSORS: [ SPONSORS: [
{ name: 'Acme Voice', logoUrl: 'acme.svg', url: 'https://acme.example', tier: 'gold' }, { name: 'Acme Voice', logoUrl: 'acme.svg', url: 'https://acme.example', tier: 'gold' },
], ],
@@ -3,7 +3,7 @@ import {
INTEGRATION_CATALOG, INTEGRATION_CATALOG,
integrationSlug, integrationSlug,
getIntegrationBySlug, getIntegrationBySlug,
} from '../../../../../../frontend/src/config/integration-catalog'; } from '@shared/config/integration-catalog';
import { import {
INTEGRATION_CATEGORIES, INTEGRATION_CATEGORIES,
integrationCategoryKey, integrationCategoryKey,
@@ -8,8 +8,8 @@ import { Input } from '@/components/ui/input';
import { import {
INTEGRATION_CATALOG, INTEGRATION_CATALOG,
integrationSlug, integrationSlug,
} from '../../../../../../frontend/src/config/integration-catalog'; } from '@shared/config/integration-catalog';
import { SPONSORS } from '../../../../../../frontend/src/config/sponsors'; import { SPONSORS } from '@shared/config/sponsors';
import { integrationSetup } from './setup-registry'; import { integrationSetup } from './setup-registry';
import { INTEGRATION_CATEGORIES, integrationCategoryKey } from './integration-categories'; import { INTEGRATION_CATEGORIES, integrationCategoryKey } from './integration-categories';
import './integrations-page.css'; import './integrations-page.css';
@@ -12,7 +12,7 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, buttonVariants } from '@/components/ui/button'; import { Button, buttonVariants } from '@/components/ui/button';
import { TAGS } from '../../../../../../frontend/src/utils/constants'; import { TAGS } from '@shared/utils/constants';
export function AudiobookMarkupToolbar({ export function AudiobookMarkupToolbar({
textareaRef, textareaRef,
@@ -1,5 +1,5 @@
import { expect, it } from 'vitest'; import { expect, it } from 'vitest';
import { buildAutoCast } from '../../../../../../frontend/src/utils/autoCast'; import { buildAutoCast } from '@shared/utils/autoCast';
it('preserves assigned character voices and appends only new speakers', () => { it('preserves assigned character voices and appends only new speakers', () => {
const cast = [{ id: 'existing', name: 'Mara', profileId: 'actor' }]; const cast = [{ id: 'existing', name: 'Mara', profileId: 'actor' }];
const result = buildAutoCast('[Mara] Hello.\n[Guest] Welcome.', cast, [{ id: 'narrator' }]); const result = buildAutoCast('[Mara] Hello.\n[Guest] Welcome.', cast, [{ id: 'narrator' }]);
@@ -7,7 +7,7 @@
* occupies no time in the finished file. Give it a cue and every later * occupies no time in the finished file. Give it a cue and every later
* timestamp is wrong by that chapter's would-be duration. * timestamp is wrong by that chapter's would-be duration.
* *
* The timecode maths lives in `frontend/src/utils/storyExport` and is tested * The timecode maths lives in `electron/src/shared/utils/storyExport` and is tested
* there; this pins the part that is specific to a render — which chapters * there; this pins the part that is specific to a render — which chapters
* count, and when there is nothing worth downloading. * count, and when there is nothing worth downloading.
*/ */
@@ -2,7 +2,7 @@ import {
buildCueSheet, buildCueSheet,
cueSheetFilename, cueSheetFilename,
cuesFromChapters, cuesFromChapters,
} from '../../../../../../frontend/src/utils/storyExport'; } from '@shared/utils/storyExport';
import type { AudiobookRenderChapter } from './longform-session'; import type { AudiobookRenderChapter } from './longform-session';
const RENDERED: ReadonlySet<string> = new Set(['done', 'cached']); const RENDERED: ReadonlySet<string> = new Set(['done', 'cached']);
@@ -1,4 +1,4 @@
import { readTextFile } from '../../../../../../frontend/src/utils/readTextFile'; import { readTextFile } from '@shared/utils/readTextFile';
import { ProfileAvatar } from '@/components/profile-avatar'; import { ProfileAvatar } from '@/components/profile-avatar';
import { import {
BookOpenTextIcon, BookOpenTextIcon,
@@ -12,7 +12,7 @@ import { AudioLinesIcon } from 'lucide-react';
import { SecondarySidebar } from '@/components/workspace-sidebar'; import { SecondarySidebar } from '@/components/workspace-sidebar';
import { WorkspaceHeader } from '@/components/app-shell/workspace-header'; import { WorkspaceHeader } from '@/components/app-shell/workspace-header';
import { PipelineFailure } from '@/components/pipeline-failure'; import { PipelineFailure } from '@/components/pipeline-failure';
import { importToText } from '../../../../../../frontend/src/utils/importStory'; import { importToText } from '@shared/utils/importStory';
import { cueSheetFor } from './cue-sheet'; import { cueSheetFor } from './cue-sheet';
import { saveLocalFile } from '@/lib/local-export'; import { saveLocalFile } from '@/lib/local-export';
import { StoryCast, StoryEditor } from './story-editor'; import { StoryCast, StoryEditor } from './story-editor';
@@ -31,7 +31,7 @@ import {
scriptStats, scriptStats,
formatRuntimeClock, formatRuntimeClock,
validateScript, validateScript,
} from '../../../../../../frontend/src/utils/audiobookScript'; } from '@shared/utils/audiobookScript';
import { BookSettings } from './book-settings'; import { BookSettings } from './book-settings';
import { duplicateWords } from './book-options'; import { duplicateWords } from './book-options';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
@@ -52,7 +52,7 @@ import { useProfiles } from '@/hooks/use-profiles';
import { apiJson, apiPath, describeError } from '@/lib/api/client'; import { apiJson, apiPath, describeError } from '@/lib/api/client';
import { saveExport } from '@/lib/export-history'; import { saveExport } from '@/lib/export-history';
import { EngineLanguagePicker } from '@/features/clone/engine-language-picker'; import { EngineLanguagePicker } from '@/features/clone/engine-language-picker';
import { LANG_CODES } from '../../../../../../frontend/src/utils/languages'; import { LANG_CODES } from '@shared/utils/languages';
import { import {
editLongform, editLongform,
dismissLongformError, dismissLongformError,
@@ -62,7 +62,7 @@ import {
storiesImportEpoch, storiesImportEpoch,
type Mode, type Mode,
} from './longform-session'; } from './longform-session';
import { SAMPLE_AUDIOBOOK_SCRIPT } from '../../../../../../frontend/src/data/sampleAudiobook'; import { SAMPLE_AUDIOBOOK_SCRIPT } from '@shared/data/sampleAudiobook';
import { useTtsReadiness } from '@/hooks/use-tts-readiness'; import { useTtsReadiness } from '@/hooks/use-tts-readiness';
interface Recovery { interface Recovery {
job_id: string; job_id: string;
@@ -2,16 +2,16 @@ import {
DEFAULT_OVERRIDES, DEFAULT_OVERRIDES,
overridesToRequest, overridesToRequest,
type Overrides, type Overrides,
} from '../../../../../../frontend/src/utils/longformOverrides'; } from '@shared/utils/longformOverrides';
import { castVoice } from './cast-map'; import { castVoice } from './cast-map';
import { parseCastNames } from '../../../../../../frontend/src/utils/audiobookScript'; import { parseCastNames } from '@shared/utils/audiobookScript';
import { restoreBookOptions, lexiconMap, type BookOptions } from './book-options'; import { restoreBookOptions, lexiconMap, type BookOptions } from './book-options';
import { createCoalescedJsonStorage } from '../../../../../../frontend/src/utils/coalescedJsonStorage'; import { createCoalescedJsonStorage } from '@shared/utils/coalescedJsonStorage';
import { Store } from '@tanstack/store'; import { Store } from '@tanstack/store';
import { useStore } from '@tanstack/react-store'; import { useStore } from '@tanstack/react-store';
import { apiFetch } from '@/lib/api/client'; import { apiFetch } from '@/lib/api/client';
import { consumeLongformStream } from '../../../../../../frontend/src/utils/longformStream'; import { consumeLongformStream } from '@shared/utils/longformStream';
import { storyToSpans } from '../../../../../../frontend/src/utils/storyToSpans'; import { storyToSpans } from '@shared/utils/storyToSpans';
import { beginAppActivity } from '@/lib/app-activity'; import { beginAppActivity } from '@/lib/app-activity';
import { publicFailureFromEvent, type PublicFailure } from '@/lib/api/failure'; import { publicFailureFromEvent, type PublicFailure } from '@/lib/api/failure';
export type Mode = 'stories' | 'audiobook'; export type Mode = 'stories' | 'audiobook';
@@ -5,7 +5,7 @@ import i18n from '@/i18n';
import { import {
DEFAULT_OVERRIDES, DEFAULT_OVERRIDES,
overridesToRequest, overridesToRequest,
} from '../../../../../../frontend/src/utils/longformOverrides'; } from '@shared/utils/longformOverrides';
import { ProductionSettings } from './production-settings'; import { ProductionSettings } from './production-settings';
it('shows effective legacy joins for untouched and reset drafts', () => { it('shows effective legacy joins for untouched and reset drafts', () => {
render( render(
@@ -8,7 +8,7 @@ import { apiJson } from '@/lib/api/client';
import { import {
DEFAULT_OVERRIDES, DEFAULT_OVERRIDES,
type Overrides, type Overrides,
} from '../../../../../../frontend/src/utils/longformOverrides'; } from '@shared/utils/longformOverrides';
const knobs = [ const knobs = [
{ key: 'numStep', label: 'steps', min: 8, max: 64, step: 1, value: 32 }, { key: 'numStep', label: 'steps', min: 8, max: 64, step: 1, value: 32 },
{ key: 'guidanceScale', label: 'cfg', min: 0, max: 4, step: 0.1, value: 2 }, { key: 'guidanceScale', label: 'cfg', min: 0, max: 4, step: 0.1, value: 2 },
@@ -1,7 +1,7 @@
import { expect, it } from 'vitest'; import { expect, it } from 'vitest';
import { createProjectLibrary } from './project-library'; import { createProjectLibrary } from './project-library';
import { blankLongformDraft } from './longform-session'; import { blankLongformDraft } from './longform-session';
import type { DurableLongformRecord } from '../../../../../../frontend/src/utils/indexedDbLongformStore'; import type { DurableLongformRecord } from '@shared/utils/indexedDbLongformStore';
it('serializes simultaneous saves without losing a project and snapshots inputs', async () => { it('serializes simultaneous saves without losing a project and snapshots inputs', async () => {
let record: DurableLongformRecord | null = null; let record: DurableLongformRecord | null = null;
const library = createProjectLibrary({ const library = createProjectLibrary({
@@ -1,7 +1,7 @@
import { import {
createIndexedDbLongformStore, createIndexedDbLongformStore,
type LongformDurableStore, type LongformDurableStore,
} from '../../../../../../frontend/src/utils/indexedDbLongformStore'; } from '@shared/utils/indexedDbLongformStore';
import type { Draft, Mode } from './longform-session'; import type { Draft, Mode } from './longform-session';
export interface LongformProject { export interface LongformProject {
id: string; id: string;
@@ -7,14 +7,14 @@ import {
SPLIT_MODES, SPLIT_MODES,
splitStoryText, splitStoryText,
type SplitMode, type SplitMode,
} from '../../../../../../frontend/src/utils/splitStoryText'; } from '@shared/utils/splitStoryText';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { buildAutoCast } from '../../../../../../frontend/src/utils/autoCast'; import { buildAutoCast } from '@shared/utils/autoCast';
import { import {
SAMPLE_STORY_CAST, SAMPLE_STORY_CAST,
SAMPLE_STORY_LINES, SAMPLE_STORY_LINES,
SAMPLE_STORY_NAME, SAMPLE_STORY_NAME,
} from '../../../../../../frontend/src/data/sampleStory'; } from '@shared/data/sampleStory';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
ArrowDownIcon, ArrowDownIcon,
@@ -46,7 +46,7 @@ import {
} from '@/components/ui/select'; } from '@/components/ui/select';
import { PipelineFailure } from '@/components/pipeline-failure'; import { PipelineFailure } from '@/components/pipeline-failure';
import { describeError } from '@/lib/api/client'; import { describeError } from '@/lib/api/client';
import { reorder } from '../../../../../../frontend/src/utils/storyReorder'; import { reorder } from '@shared/utils/storyReorder';
import type { Draft } from './longform-session'; import type { Draft } from './longform-session';
interface Props { interface Props {
draft: Draft; draft: Draft;
@@ -1,9 +1,9 @@
import { expect, it } from 'vitest'; import { expect, it } from 'vitest';
import { importToText } from '../../../../../../frontend/src/utils/importStory'; import { importToText } from '@shared/utils/importStory';
import { import {
splitIntoChunks, splitIntoChunks,
splitStoryText, splitStoryText,
} from '../../../../../../frontend/src/utils/splitStoryText'; } from '@shared/utils/splitStoryText';
it('removes SRT metadata and retains spoken lines', () => { it('removes SRT metadata and retains spoken lines', () => {
expect( expect(
importToText( importToText(
@@ -43,7 +43,7 @@ it('split presets give one line per paragraph or per chapter, headings kept sepa
}); });
it('decodes a UTF-16 manuscript before extracting subtitle speech', async () => { it('decodes a UTF-16 manuscript before extracting subtitle speech', async () => {
const { readTextFile } = await import('../../../../../../frontend/src/utils/readTextFile'); const { readTextFile } = await import('@shared/utils/readTextFile');
const text = '1\n00:00:01,000 --> 00:00:02,000\nCafé — hello'; const text = '1\n00:00:01,000 --> 00:00:02,000\nCafé — hello';
const bytes = new Uint8Array(2 + text.length * 2); const bytes = new Uint8Array(2 + text.length * 2);
bytes.set([0xff, 0xfe]); bytes.set([0xff, 0xfe]);
@@ -1,4 +1,4 @@
import { storyToSpans } from '../../../../../../frontend/src/utils/storyToSpans'; import { storyToSpans } from '@shared/utils/storyToSpans';
import { castVoice } from './cast-map'; import { castVoice } from './cast-map';
import type { Draft } from './longform-session'; import type { Draft } from './longform-session';
export function storyVoicesReady(draft: Draft, profiles: { id: string }[]) { export function storyVoicesReady(draft: Draft, profiles: { id: string }[]) {
@@ -1,5 +1,5 @@
import { afterEach, expect, it, vi } from 'vitest'; import { afterEach, expect, it, vi } from 'vitest';
import { exportStoryAudio } from '../../../../../../frontend/src/utils/storyExport'; import { exportStoryAudio } from '@shared/utils/storyExport';
import { storyChunkBody } from './story-preview'; import { storyChunkBody } from './story-preview';
import { blankLongformDraft } from './longform-session'; import { blankLongformDraft } from './longform-session';
afterEach(() => vi.unstubAllGlobals()); afterEach(() => vi.unstubAllGlobals());
@@ -1,6 +1,6 @@
import { apiFetch } from '@/lib/api/client'; import { apiFetch } from '@/lib/api/client';
import { exportStoryAudio, exportStems } from '../../../../../../frontend/src/utils/storyExport'; import { exportStoryAudio, exportStems } from '@shared/utils/storyExport';
import { overridesToRequest } from '../../../../../../frontend/src/utils/longformOverrides'; import { overridesToRequest } from '@shared/utils/longformOverrides';
import { resolveStoryVoice } from './story-inputs'; import { resolveStoryVoice } from './story-inputs';
import type { Draft, Line } from './longform-session'; import type { Draft, Line } from './longform-session';
import { beginAppActivity } from '@/lib/app-activity'; import { beginAppActivity } from '@/lib/app-activity';
@@ -15,7 +15,7 @@ import { cn } from '@/lib/utils';
import { import {
activeWordIndex, activeWordIndex,
buildLyricsTimeline, buildLyricsTimeline,
} from '../../../../../../frontend/src/utils/audiobookLyrics'; } from '@shared/utils/audiobookLyrics';
import type { AudiobookRenderChapter } from './longform-session'; import type { AudiobookRenderChapter } from './longform-session';
interface TimelineWord { interface TimelineWord {
@@ -1,4 +1,4 @@
import { LANG_CODES } from '../../../../../../frontend/src/utils/languages'; import { LANG_CODES } from '@shared/utils/languages';
import { restoreDubDraft } from '../dub/dub-draft'; import { restoreDubDraft } from '../dub/dub-draft';
import type { DubSession } from '../dub/dub-session'; import type { DubSession } from '../dub/dub-session';
export interface DubProject { export interface DubProject {
@@ -57,8 +57,8 @@ import {
removeTranscription, removeTranscription,
subscribeTranscriptions, subscribeTranscriptions,
type TranscriptEntry, type TranscriptEntry,
} from '../../../../../../frontend/src/utils/transcriptionsStore'; } from '@shared/utils/transcriptionsStore';
import { preferredTranscript } from '../../../../../../frontend/src/utils/transcriptionFormat'; import { preferredTranscript } from '@shared/utils/transcriptionFormat';
import { import {
useDubSession, useDubSession,
openDubProject, openDubProject,
@@ -4,7 +4,7 @@ import {
removeTranscription, removeTranscription,
subscribeTranscriptions, subscribeTranscriptions,
TRANSCRIPTIONS_KEY, TRANSCRIPTIONS_KEY,
} from '../../../../../../frontend/src/utils/transcriptionsStore'; } from '@shared/utils/transcriptionsStore';
beforeEach(() => localStorage.clear()); beforeEach(() => localStorage.clear());
afterEach(() => vi.restoreAllMocks()); afterEach(() => vi.restoreAllMocks());
it('deletes from fresh storage and notifies mounted library subscribers', () => { it('deletes from fresh storage and notifies mounted library subscribers', () => {
@@ -8,7 +8,7 @@ import { Switch } from '@/components/ui/switch';
import { apiJson } from '@/lib/api/client'; import { apiJson } from '@/lib/api/client';
import { SettingsSection, SettingsRow } from './settings-layout'; import { SettingsSection, SettingsRow } from './settings-layout';
import { useSettingsAction } from './use-settings-action'; import { useSettingsAction } from './use-settings-action';
import { PROVIDER_FIELDS } from '../../../../../../frontend/src/utils/translationProviderFields'; import { PROVIDER_FIELDS } from '@shared/utils/translationProviderFields';
interface TokenState { interface TokenState {
active: string | null; active: string | null;
sources: { sources: {
@@ -15,7 +15,7 @@ import {
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { apiJson, describeError } from '@/lib/api/client'; import { apiJson, describeError } from '@/lib/api/client';
import type { DataDirectorySelection, DataRelocationStage } from '../../../../preload/index.d'; import type { DataDirectorySelection, DataRelocationStage } from '../../../../preload/index.d';
import { fmtBytes } from '../../../../../../frontend/src/components/settings/models/format'; import { fmtBytes } from '@shared/components/settings/models/format';
import { SettingsRow, SettingsSection } from './settings-layout'; import { SettingsRow, SettingsSection } from './settings-layout';
function relocationError(t: (key: string) => string, error: unknown): string { function relocationError(t: (key: string) => string, error: unknown): string {
@@ -6,7 +6,7 @@ import {
progressPct, progressPct,
isGoalMet, isGoalMet,
formatMoney, formatMoney,
} from '../../../../../../frontend/src/api/donation'; } from '@shared/api/donation';
import snapshotUrl from '../../../../../public/donation_progress.json?url'; import snapshotUrl from '../../../../../public/donation_progress.json?url';
export function DonationGoal() { export function DonationGoal() {
@@ -14,7 +14,7 @@ import { cn } from '@/lib/utils';
import { import {
getFrontendLogs, getFrontendLogs,
clearFrontendLogs, clearFrontendLogs,
} from '../../../../../../frontend/src/utils/consoleBuffer'; } from '@shared/utils/consoleBuffer';
interface LogResponse { interface LogResponse {
lines: string[]; lines: string[];
path?: string; path?: string;
@@ -37,7 +37,7 @@ import { SettingsSection, SettingsRow } from './settings-layout';
import { familyIcons, type ModelFamily } from './model-family'; import { familyIcons, type ModelFamily } from './model-family';
import { useModelCatalogue, type CatalogueModel } from './model-catalogue-query'; import { useModelCatalogue, type CatalogueModel } from './model-catalogue-query';
import { resolvePerformanceModelPack } from './performance-model-packs'; import { resolvePerformanceModelPack } from './performance-model-packs';
import { fmtBytes } from '../../../../../../frontend/src/components/settings/models/format'; import { fmtBytes } from '@shared/components/settings/models/format';
import { import {
engineSelectionFeedback, engineSelectionFeedback,
type EngineSelectionResult, type EngineSelectionResult,
@@ -8,7 +8,7 @@ import { Input } from '@/components/ui/input';
import { apiJson } from '@/lib/api/client'; import { apiJson } from '@/lib/api/client';
import { SettingsSection, SettingsRow } from './settings-layout'; import { SettingsSection, SettingsRow } from './settings-layout';
import { useSettingsAction } from './use-settings-action'; import { useSettingsAction } from './use-settings-action';
import { saveProxyPreference } from '../../../../../../frontend/src/utils/networkPreferences'; import { saveProxyPreference } from '@shared/utils/networkPreferences';
export function NetworkSettings() { export function NetworkSettings() {
const { t } = useTranslation(); const { t } = useTranslation();
const client = useQueryClient(); const client = useQueryClient();
@@ -9,7 +9,7 @@ import { Switch } from '@/components/ui/switch';
import { apiJson } from '@/lib/api/client'; import { apiJson } from '@/lib/api/client';
import { SettingsSection, SettingsRow } from './settings-layout'; import { SettingsSection, SettingsRow } from './settings-layout';
import { useSettingsAction } from './use-settings-action'; import { useSettingsAction } from './use-settings-action';
import { translatorPrivacy } from '../../../../../../frontend/src/utils/translatorPrivacy'; import { translatorPrivacy } from '@shared/utils/translatorPrivacy';
export function PrivacySettings({ showAnalytics = true }: { showAnalytics?: boolean } = {}) { export function PrivacySettings({ showAnalytics = true }: { showAnalytics?: boolean } = {}) {
const { t } = useTranslation(); const { t } = useTranslation();
const info = useQuery({ const info = useQuery({
@@ -6,7 +6,7 @@ import { apiJson } from '@/lib/api/client';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { SettingsSection, SettingsRow } from './settings-layout'; import { SettingsSection, SettingsRow } from './settings-layout';
import { refineFailureNoteKey } from '../../../../../../frontend/src/components/settings/refineStatus'; import { refineFailureNoteKey } from '@shared/components/settings/refineStatus';
const flags = ['auto', 'smart_cleanup', 'self_correction', 'preserve_technical'] as const; const flags = ['auto', 'smart_cleanup', 'self_correction', 'preserve_technical'] as const;
type Config = Record<(typeof flags)[number], boolean> & { type Config = Record<(typeof flags)[number], boolean> & {
llm_ready: boolean; llm_ready: boolean;
@@ -32,7 +32,7 @@ import { apiFetch, describeError } from '@/lib/api/client';
import { projectLibrary } from '@/features/longform/project-library'; import { projectLibrary } from '@/features/longform/project-library';
import { clearLongformDraftForReset } from '@/features/longform/longform-session'; import { clearLongformDraftForReset } from '@/features/longform/longform-session';
import { clearDubDraftForReset } from '@/features/dub/dub-session'; import { clearDubDraftForReset } from '@/features/dub/dub-session';
import { fmtBytes } from '../../../../../../frontend/src/components/settings/models/format'; import { fmtBytes } from '@shared/components/settings/models/format';
import { SettingsSection } from './settings-layout'; import { SettingsSection } from './settings-layout';
import { forgetSetupProgress } from '@/lib/setup-progress'; import { forgetSetupProgress } from '@/lib/setup-progress';
@@ -13,7 +13,7 @@ import {
import { import {
keyEventToAccelerator, keyEventToAccelerator,
isPureModifierEvent, isPureModifierEvent,
} from '../../../../../../frontend/src/utils/shortcutAccelerator'; } from '@shared/utils/shortcutAccelerator';
import { SettingsSection, SettingsRow } from './settings-layout'; import { SettingsSection, SettingsRow } from './settings-layout';
export function ShortcutSettings() { export function ShortcutSettings() {
@@ -10,7 +10,7 @@ import { getBridge } from '@/components/bridge';
import { SettingsSection } from './settings-layout'; import { SettingsSection } from './settings-layout';
import { HistoryRetention } from './privacy-settings'; import { HistoryRetention } from './privacy-settings';
import { useSettingsAction } from './use-settings-action'; import { useSettingsAction } from './use-settings-action';
import { fmtBytes } from '../../../../../../frontend/src/components/settings/models/format'; import { fmtBytes } from '@shared/components/settings/models/format';
import { ModelsDirectorySettings } from './models-directory-settings'; import { ModelsDirectorySettings } from './models-directory-settings';
import { ResetSettings } from './reset-settings'; import { ResetSettings } from './reset-settings';
import { DataDirectorySettings } from './data-directory-settings'; import { DataDirectorySettings } from './data-directory-settings';
@@ -18,7 +18,7 @@ import { UninstallSettings } from './uninstall-settings';
import { import {
warningText, warningText,
type StorageWarning, type StorageWarning,
} from '../../../../../../frontend/src/utils/storageWarnings'; } from '@shared/utils/storageWarnings';
interface Item { interface Item {
id: string; id: string;
path?: string; path?: string;
@@ -20,12 +20,12 @@ import { brandIcon } from '@/lib/brand';
import { DonationGoal } from './donation-goal'; import { DonationGoal } from './donation-goal';
import { ReportBug } from '@/components/report-bug'; import { ReportBug } from '@/components/report-bug';
import { ExternalLink } from '@/components/external-link'; import { ExternalLink } from '@/components/external-link';
import { KOFI_URL, PAYPAL_URL } from '../../../../../../frontend/src/utils/donateLinks'; import { KOFI_URL, PAYPAL_URL } from '@shared/utils/donateLinks';
import { import {
SPONSORS, SPONSORS,
SPONSOR_TIERS, SPONSOR_TIERS,
SPONSOR_CONTACT, SPONSOR_CONTACT,
} from '../../../../../../frontend/src/config/sponsors'; } from '@shared/config/sponsors';
import { import {
ISSUES_URL, ISSUES_URL,
DISCORD_URL, DISCORD_URL,
@@ -33,7 +33,7 @@ import {
EMAIL, EMAIL,
WEBSITE_URL, WEBSITE_URL,
X_URL, X_URL,
} from '../../../../../../frontend/src/utils/contactLinks'; } from '@shared/utils/contactLinks';
import './support-settings.css'; import './support-settings.css';
export function SupportSettings() { export function SupportSettings() {
@@ -1,7 +1,7 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { CheckCircleIcon, TriangleAlertIcon, CircleXIcon, ScanSearchIcon } from 'lucide-react'; import { CheckCircleIcon, TriangleAlertIcon, CircleXIcon, ScanSearchIcon } from 'lucide-react';
import type { PreflightReport } from '../../../../../../frontend/src/api/setup-types'; import type { PreflightReport } from '@shared/api/setup-types';
import { apiJson } from '@/lib/api/client'; import { apiJson } from '@/lib/api/client';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { SettingsRow, SettingsSection } from './settings-layout'; import { SettingsRow, SettingsSection } from './settings-layout';
@@ -25,7 +25,7 @@ import {
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { describeError } from '@/lib/api/client'; import { describeError } from '@/lib/api/client';
import type { UninstallTarget } from '../../../../preload/index.d'; import type { UninstallTarget } from '../../../../preload/index.d';
import { fmtBytes } from '../../../../../../frontend/src/components/settings/models/format'; import { fmtBytes } from '@shared/components/settings/models/format';
import { SettingsSection } from './settings-layout'; import { SettingsSection } from './settings-layout';
const ICONS: Record<UninstallTarget['key'], LucideIcon> = { const ICONS: Record<UninstallTarget['key'], LucideIcon> = {
@@ -13,7 +13,7 @@ import { cloneSettingsStore } from '@/lib/store/clone-settings';
import { acquireSynthesis } from '@/lib/synthesis-lock'; import { acquireSynthesis } from '@/lib/synthesis-lock';
import { queryKeys } from '@/lib/query'; import { queryKeys } from '@/lib/query';
import { describeError } from '@/lib/api/client'; import { describeError } from '@/lib/api/client';
import { PRESETS } from '../../../../../../frontend/src/utils/constants'; import { PRESETS } from '@shared/utils/constants';
import { beginAppActivity } from '@/lib/app-activity'; import { beginAppActivity } from '@/lib/app-activity';
import { useTtsReadiness } from '@/hooks/use-tts-readiness'; import { useTtsReadiness } from '@/hooks/use-tts-readiness';
@@ -12,7 +12,7 @@ import { Input } from '@/components/ui/input';
import { PipelineFailure } from '@/components/pipeline-failure'; import { PipelineFailure } from '@/components/pipeline-failure';
import { apiJson, describeError } from '@/lib/api/client'; import { apiJson, describeError } from '@/lib/api/client';
import { LanguagePicker } from '@/features/clone/language-picker'; import { LanguagePicker } from '@/features/clone/language-picker';
import { LANG_CODES } from '../../../../../../frontend/src/utils/languages'; import { LANG_CODES } from '@shared/utils/languages';
const options = LANG_CODES.map((item) => item.label); const options = LANG_CODES.map((item) => item.label);
const tools = [ const tools = [
{ {
@@ -4,7 +4,7 @@ import { CopyIcon, MicIcon, PauseIcon, PlayIcon, SquareIcon, XIcon } from 'lucid
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useBackendStatus } from '@/hooks/use-backend-status'; import { useBackendStatus } from '@/hooks/use-backend-status';
import { LiveDictation } from './live-dictation'; import { LiveDictation } from './live-dictation';
import { addTranscription } from '../../../../../../frontend/src/utils/transcriptionsStore'; import { addTranscription } from '@shared/utils/transcriptionsStore';
/** Native recorder stays separate from page navigation and the script editor. */ /** Native recorder stays separate from page navigation and the script editor. */
export function CaptureWidget() { export function CaptureWidget() {
@@ -7,7 +7,7 @@ import { Button, buttonVariants } from '@/components/ui/button';
import { AgentFixButton } from '@/components/agent-fix-button'; import { AgentFixButton } from '@/components/agent-fix-button';
import { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
import { apiJson, describeError } from '@/lib/api/client'; import { apiJson, describeError } from '@/lib/api/client';
import { fmtBytes } from '../../../../../../frontend/src/components/settings/models/format'; import { fmtBytes } from '@shared/components/settings/models/format';
interface DictationModel { interface DictationModel {
id: string; id: string;
@@ -4,7 +4,7 @@ import {
loadTranscriptions, loadTranscriptions,
TRANSCRIPTIONS_KEY, TRANSCRIPTIONS_KEY,
TRANSCRIPTION_EVENT, TRANSCRIPTION_EVENT,
} from '../../../../../../frontend/src/utils/transcriptionsStore'; } from '@shared/utils/transcriptionsStore';
beforeEach(() => localStorage.clear()); beforeEach(() => localStorage.clear());
it('preserves existing history and publishes a complete entry with nullable timings', () => { it('preserves existing history and publishes a complete entry with nullable timings', () => {
localStorage.setItem(TRANSCRIPTIONS_KEY, JSON.stringify([{ id: 1, text: 'Existing' }])); localStorage.setItem(TRANSCRIPTIONS_KEY, JSON.stringify([{ id: 1, text: 'Existing' }]));
@@ -5,7 +5,7 @@ vi.mock('@/hooks/use-backend-status', () => ({
getBackendStatusSnapshot: () => ({ baseUrl: 'http://127.0.0.1:3900' }), getBackendStatusSnapshot: () => ({ baseUrl: 'http://127.0.0.1:3900' }),
})); }));
vi.mock('@/components/bridge', () => ({ getBridge: () => mocks.bridge })); vi.mock('@/components/bridge', () => ({ getBridge: () => mocks.bridge }));
vi.mock('../../../../../../frontend/src/utils/aec/micCapture', () => ({ vi.mock('@shared/utils/aec/micCapture', () => ({
startMicCapture: mocks.capture, startMicCapture: mocks.capture,
})); }));
import { LiveDictation } from './live-dictation'; import { LiveDictation } from './live-dictation';
@@ -1,16 +1,16 @@
import { startMicCapture } from '../../../../../../frontend/src/utils/aec/micCapture'; import { startMicCapture } from '@shared/utils/aec/micCapture';
import { subscribeFarEnd } from '../../../../../../frontend/src/utils/aec/farEndBus'; import { subscribeFarEnd } from '@shared/utils/aec/farEndBus';
import { import {
AEC_FAR, AEC_FAR,
AEC_NEAR, AEC_NEAR,
floatToInt16, floatToInt16,
tagFrame, tagFrame,
} from '../../../../../../frontend/src/utils/aec/pcm'; } from '@shared/utils/aec/pcm';
import { import {
classifySherpaFinal, classifySherpaFinal,
sherpaSummaryTail, sherpaSummaryTail,
} from '../../../../../../frontend/src/utils/captureProtocol'; } from '@shared/utils/captureProtocol';
import type { TranscriptEntry } from '../../../../../../frontend/src/utils/transcriptionsStore'; import type { TranscriptEntry } from '@shared/utils/transcriptionsStore';
import { apiJson } from '@/lib/api/client'; import { apiJson } from '@/lib/api/client';
import { backendWebSocketUrl } from '@/lib/api/websocket'; import { backendWebSocketUrl } from '@/lib/api/websocket';
import { beginAppActivity } from '@/lib/app-activity'; import { beginAppActivity } from '@/lib/app-activity';
@@ -6,7 +6,7 @@ import {
segTimeRange, segTimeRange,
formatTranscriptExport, formatTranscriptExport,
preferredTranscript, preferredTranscript,
} from '../../../../../../frontend/src/utils/transcriptionFormat'; } from '@shared/utils/transcriptionFormat';
import { RecordingInputs } from '@/components/recording-inputs'; import { RecordingInputs } from '@/components/recording-inputs';
import { AgentFixButton } from '@/components/agent-fix-button'; import { AgentFixButton } from '@/components/agent-fix-button';
import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'; import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
@@ -52,14 +52,14 @@ import {
TRANSCRIPTIONS_KEY, TRANSCRIPTIONS_KEY,
TRANSCRIPTION_EVENT, TRANSCRIPTION_EVENT,
type TranscriptEntry, type TranscriptEntry,
} from '../../../../../../frontend/src/utils/transcriptionsStore'; } from '@shared/utils/transcriptionsStore';
import { beginAppActivity } from '@/lib/app-activity'; import { beginAppActivity } from '@/lib/app-activity';
import { formatRelative } from '@/features/clone/format'; import { formatRelative } from '@/features/clone/format';
import { useModelCatalogue } from '@/features/settings/model-catalogue-query'; import { useModelCatalogue } from '@/features/settings/model-catalogue-query';
import { useNativeShortcut } from '@/hooks/use-native-dictation'; import { useNativeShortcut } from '@/hooks/use-native-dictation';
import { recordActionBreadcrumb } from '@/lib/report-breadcrumb'; import { recordActionBreadcrumb } from '@/lib/report-breadcrumb';
import { saveLocalFile } from '@/lib/local-export'; import { saveLocalFile } from '@/lib/local-export';
import { formatShortcut } from '../../../../../../frontend/src/utils/dictationShortcut'; import { formatShortcut } from '@shared/utils/dictationShortcut';
type TranscriptionMode = 'fast' | 'accurate'; type TranscriptionMode = 'fast' | 'accurate';
const TRANSCRIPTION_MODE_KEY = 'voicestudio.transcription.mode'; const TRANSCRIPTION_MODE_KEY = 'voicestudio.transcription.mode';

Some files were not shown because too many files have changed in this diff Show More