Phase 3 Plan 03-01: Supertonic-3 engine on SubprocessBackend - #101
Conversation
Adds Supertonic-3 as a 7th opt-in TTS engine on the Phase 2
SubprocessBackend primitive. Closes TTS-01..06 (REQUIREMENTS.md):
* TTS-01 — _REGISTRY["supertonic3"] resolves to Supertonic3Backend,
a SubprocessBackend subclass.
* TTS-02 — `supertonic==1.3.1` lives under [project.optional-dependencies];
default `uv sync --no-dev` does NOT install it. Exactly one
`onnxruntime` row in `uv pip list` after `--extra supertonic`.
* TTS-03 — Model revision pinned by 40-char commit SHA
(724fb5abbf5502583fb520898d45929e62f02c0b — the "Initial
Supertonic 3 release" SHA, same as the SDK's own pin).
Resolver script for intentional bumps:
scripts/resolve_supertonic3_sha.py.
* TTS-04 — Honest CPU-only reporting. `is_available()` message says
"ready (CPU-only via onnxruntime)" and never mentions
"cuda" or "mps". `gpu_compat = ("cpu",)`.
* TTS-05 — License gate via settings_store helpers
(get/set_license_accepted) + Loopback-only
/api/settings/license endpoint + SupertonicLicenseDialog
frontend modal showing MIT (code) and OpenRAIL-M (model).
Wired into EngineCompatibilityMatrix as an "Accept license"
button on rows whose `reason` mentions "license not
accepted".
* TTS-06 — 3 langs (en/ja/ru) × 3 sec smoke test in
tests/test_supertonic3.py::test_smoke_3langs_3sec
(OMNIVOICE_SMOKE-gated; asserts no onnxruntime-gpu row
post-synthesize).
Package legitimacy gate (Task 1 in plan): supertonic on PyPI verified
to be published by Supertone Inc. (ato@supertone.ai), repo
github.com/supertone-inc/supertonic, wheel is pure-Python with no
postinstall scripts. Same publisher ships supertonic-js on npm under
the same maintainer email.
Test results:
* tests/test_supertonic3.py — 10 passed, 3 skipped (network-gated).
* tests/smoke/ — 4 passed.
* tests/ (full, --ignore=tests/manual) — 412 passed, 0 failed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis pull request implements the Supertonic-3 TTS engine on the SubprocessBackend architecture for Phase 3 Wave 1. It adds a complete feature set: a CPU-only backend class with license gating, a long-lived sidecar subprocess with JSON wire-protocol synthesis, REST API endpoints for license acceptance, settings-store persistence, and a React modal for frontend license dialogs. Includes model SHA pinning, optional dependency configuration, comprehensive tests, and release automation. ChangesSupertonic-3 Engine Integration
Sequence Diagram(s)sequenceDiagram
participant User
participant Matrix as EngineCompatibilityMatrix
participant Dialog as SupertonicLicenseDialog
participant API as /api/settings/license
participant Store as settings_store
participant Backend as Supertonic3Backend
User->>Matrix: Open Engines view (engine shows license-needed)
Matrix->>Dialog: Click "Accept license"
Dialog->>API: POST {engine_id:"supertonic3", accepted:true}
API->>Store: set_license_accepted("supertonic3", true)
Store-->>API: persisted "1"
API-->>Dialog: {ok:true, engine_id:"supertonic3", accepted:true}
Dialog->>Matrix: onAccepted callback
Matrix->>Backend: Re-check availability
Backend->>Backend: services.settings_store.get_license_accepted("supertonic3")
Backend-->>Matrix: now available (CPU-only)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (1)
tests/test_supertonic3.py (1)
362-367: ⚡ Quick winThis test bypasses the behavior it claims to verify.
The test sets
os.environdirectly instead of exercising the backend path that should setSUPERTONIC3_REVISION, so it can pass even if that integration regresses.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_supertonic3.py` around lines 362 - 367, The test is incorrectly setting SUPERTONIC3_REVISION directly instead of exercising the backend code that sets it; update the test to invoke the Supertonic3Backend path that populates the env (e.g., call backend.generate() or the backend helper that prepares env) while mocking/stubbing out any real sidecar spawn or subprocess calls so no real process is started, then assert os.environ["SUPERTONIC3_REVISION"] == constants.PINNED_REVISION_SHA; reference Supertonic3Backend and the generate()/env-preparation method and use unittest.mock.patch (or similar) to intercept the spawn so the environment-setting logic is exercised rather than manually modifying os.environ.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
@.planning/phases/03-supertonic-3-engine-installer-mirror-reliability/03-01-SUMMARY.md:
- Around line 58-61: The fenced code block containing the curl command and sha
should include a language tag to satisfy MD040; edit the block that starts with
the triple backticks before the line "$ curl
https://huggingface.co/api/models/Supertone/supertonic-3/revision/724fb5abbf5502583fb520898d45929e62f02c0b"
and change the opening fence to include "bash" (i.e., ```bash) so the snippet is
properly labeled.
In `@backend/engines/supertonic3/backend.py`:
- Around line 101-105: The user-facing install hint in the return tuple (the
string starting "supertonic package not installed...") is inconsistent with the
UI/engine hint; update that message in backend.py so it uses the same install
command as the engine/UI (replace the "uv add --optional supertonic
supertonic==1.3.1" text with the canonical "uv sync --extra supertonic" form,
keeping version details if desired) so both places show the identical
instruction.
- Around line 182-184: Currently os.environ.setdefault("SUPERTONIC3_REVISION",
st3_constants.PINNED_REVISION_SHA) preserves any pre-existing
SUPERTONIC3_REVISION; change it to unconditionally set the environment variable
so the pinned revision is enforced by replacing that call with an assignment
like os.environ["SUPERTONIC3_REVISION"] = st3_constants.PINNED_REVISION_SHA
(referencing SUPERTONIC3_REVISION and st3_constants.PINNED_REVISION_SHA) and
ensure this assignment runs at the same initialization point so the pinned
revision cannot be bypassed.
In `@frontend/src/components/EngineCompatibilityMatrix.jsx`:
- Around line 115-117: The component tracks which engine’s license dialog should
be open via the licenseDialogFor state (set by setLicenseDialogFor in the click
handlers around the engine rows) but never renders any dialog; add conditional
JSX in the EngineCompatibilityMatrix component to render the license dialog when
licenseDialogFor is non-null: render the LicenseDialog (or existing modal
component used for licenses) and pass the selected engine id/details from
licenseDialogFor plus handlers for onAccept and onClose that call
setLicenseDialogFor(null) (and trigger the acceptance flow already implemented
in the click handlers). Ensure the dialog receives the same accept logic used
elsewhere so the “Accept license” action becomes reachable.
In `@frontend/src/components/SupertonicLicenseDialog.css`:
- Line 16: The font-family declarations in SupertonicLicenseDialog.css use
quoted family names that trigger the Stylelint rule; update the two occurrences
(the font-family declaration near the top and the one around line 78) to use
unquoted family names per the project's font-family-name-quotes rule (e.g.,
change "font-family: 'Inter Variable', 'Inter', system-ui, sans-serif;" to use
unquoted identifiers like font-family: Inter Variable, Inter, system-ui,
sans-serif;), ensuring both instances (the top font-family declaration and the
bottom one) are updated consistently.
In `@scripts/resolve_supertonic3_sha.py`:
- Around line 225-226: The two literal strings "info: current
PINNED_REVISION_SHA already matches " and "(no change needed)" are plain
constants but are written as f-strings; remove the leading f prefix from each so
they are regular string literals (e.g., change f"... " to "..." for those exact
segments in scripts/resolve_supertonic3_sha.py, keeping the
concatenation/formatting intact where they appear).
In `@tests/test_supertonic3.py`:
- Around line 68-69: The two string literals in tests/test_supertonic3.py
currently use unnecessary f-string prefixes (the fragments starting with
f"uv.lock contains an onnxruntime-gpu row " and f"(double-install risk per
Pitfall 1)"); remove the leading "f" from both so they become plain string
literals (preserving their concatenation/spacing) to satisfy Ruff F541. Locate
these literals in the test function or assertion and just drop the f prefixes
from the two quoted fragments.
- Around line 43-46: The assertion in tests/test_supertonic3.py currently allows
either "supertonic==1.3.1" or the fallback "supertonic==1.2.3"; update the test
to require only the approved pin by changing the check on pins to assert the
presence of "supertonic==1.3.1" (remove the "1.2.3" alternative) so the
assertion fails unless the exact approved version is present.
---
Nitpick comments:
In `@tests/test_supertonic3.py`:
- Around line 362-367: The test is incorrectly setting SUPERTONIC3_REVISION
directly instead of exercising the backend code that sets it; update the test to
invoke the Supertonic3Backend path that populates the env (e.g., call
backend.generate() or the backend helper that prepares env) while
mocking/stubbing out any real sidecar spawn or subprocess calls so no real
process is started, then assert os.environ["SUPERTONIC3_REVISION"] ==
constants.PINNED_REVISION_SHA; reference Supertonic3Backend and the
generate()/env-preparation method and use unittest.mock.patch (or similar) to
intercept the spawn so the environment-setting logic is exercised rather than
manually modifying os.environ.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 153d6af3-70c6-45ae-99db-e4eafe6ddbda
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (15)
.planning/phases/03-supertonic-3-engine-installer-mirror-reliability/03-01-SUMMARY.mdbackend/api/routers/settings.pybackend/engines/supertonic3/__init__.pybackend/engines/supertonic3/backend.pybackend/engines/supertonic3/constants.pybackend/engines/supertonic3/sidecar.pybackend/services/settings_store.pybackend/services/tts_backend.pyfrontend/src/components/EngineCompatibilityMatrix.jsxfrontend/src/components/SupertonicLicenseDialog.cssfrontend/src/components/SupertonicLicenseDialog.jsxpyproject.tomlscripts/resolve_supertonic3_sha.pytests/conftest.pytests/test_supertonic3.py
| ``` | ||
| $ curl https://huggingface.co/api/models/Supertone/supertonic-3/revision/724fb5abbf5502583fb520898d45929e62f02c0b | ||
| sha: 724fb5abbf5502583fb520898d45929e62f02c0b | ||
| ``` |
There was a problem hiding this comment.
Add a language tag to the fenced code block (MD040).
Suggested change
-```
+```bash
$ curl https://huggingface.co/api/models/Supertone/supertonic-3/revision/724fb5abbf5502583fb520898d45929e62f02c0b
sha: 724fb5abbf5502583fb520898d45929e62f02c0b</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 58-58: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
@.planning/phases/03-supertonic-3-engine-installer-mirror-reliability/03-01-SUMMARY.md
around lines 58 - 61, The fenced code block containing the curl command and sha
should include a language tag to satisfy MD040; edit the block that starts with
the triple backticks before the line "$ curl
https://huggingface.co/api/models/Supertone/supertonic-3/revision/724fb5abbf5502583fb520898d45929e62f02c0b"
and change the opening fence to include "bash" (i.e., ```bash) so the snippet is
properly labeled.
| return False, ( | ||
| "supertonic package not installed. Enable in Settings → " | ||
| "Engines (installs `supertonic` via `uv add --optional " | ||
| "supertonic supertonic==1.3.1`)." | ||
| ) |
There was a problem hiding this comment.
Use one install command consistently across backend messages and UI hints.
Line 102-104 suggests uv add --optional ..., while the engine hint uses uv sync --extra supertonic. This inconsistency is user-facing and avoidable.
Suggested fix
- "supertonic package not installed. Enable in Settings → "
- "Engines (installs `supertonic` via `uv add --optional "
- "supertonic supertonic==1.3.1`)."
+ "supertonic package not installed. Enable in Settings → "
+ "Engines (or run `uv sync --extra supertonic`)."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/engines/supertonic3/backend.py` around lines 101 - 105, The
user-facing install hint in the return tuple (the string starting "supertonic
package not installed...") is inconsistent with the UI/engine hint; update that
message in backend.py so it uses the same install command as the engine/UI
(replace the "uv add --optional supertonic supertonic==1.3.1" text with the
canonical "uv sync --extra supertonic" form, keeping version details if desired)
so both places show the identical instruction.
| os.environ.setdefault( | ||
| "SUPERTONIC3_REVISION", st3_constants.PINNED_REVISION_SHA, | ||
| ) |
There was a problem hiding this comment.
Enforce the pinned revision instead of using setdefault.
Line 182 currently preserves any pre-existing SUPERTONIC3_REVISION, which allows loading an unpinned revision and weakens the tamper-mitigation path.
Suggested fix
- os.environ.setdefault(
- "SUPERTONIC3_REVISION", st3_constants.PINNED_REVISION_SHA,
- )
+ os.environ["SUPERTONIC3_REVISION"] = st3_constants.PINNED_REVISION_SHA📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| os.environ.setdefault( | |
| "SUPERTONIC3_REVISION", st3_constants.PINNED_REVISION_SHA, | |
| ) | |
| os.environ["SUPERTONIC3_REVISION"] = st3_constants.PINNED_REVISION_SHA |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/engines/supertonic3/backend.py` around lines 182 - 184, Currently
os.environ.setdefault("SUPERTONIC3_REVISION", st3_constants.PINNED_REVISION_SHA)
preserves any pre-existing SUPERTONIC3_REVISION; change it to unconditionally
set the environment variable so the pinned revision is enforced by replacing
that call with an assignment like os.environ["SUPERTONIC3_REVISION"] =
st3_constants.PINNED_REVISION_SHA (referencing SUPERTONIC3_REVISION and
st3_constants.PINNED_REVISION_SHA) and ensure this assignment runs at the same
initialization point so the pinned revision cannot be bypassed.
| // Phase 3 Plan 03-01 / TTS-05: which engine has its license dialog | ||
| // currently open, or null. Only one dialog is ever open at a time. | ||
| const [licenseDialogFor, setLicenseDialogFor] = useState(null); |
There was a problem hiding this comment.
Render the selected license dialog; currently the acceptance flow is unreachable.
Line 117 tracks licenseDialogFor, and Lines 351-367 set it, but no dialog is rendered from that state. Clicking Accept license won’t open anything.
💡 Proposed fix
export default function EngineCompatibilityMatrix({
@@
const [licenseDialogFor, setLicenseDialogFor] = useState(null);
+ const ActiveLicenseDialog = licenseDialogFor ? LICENSE_DIALOGS[licenseDialogFor] : null;
@@
return (
<section className="engine-matrix">
@@
<Table className="engine-matrix__table" role="table" aria-label={`${activeFamily} engine compatibility`}>
@@
</Table>
+ {ActiveLicenseDialog && (
+ <ActiveLicenseDialog
+ open
+ onClose={() => setLicenseDialogFor(null)}
+ onAccepted={reload}
+ />
+ )}
</section>
);
}Also applies to: 351-367
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/EngineCompatibilityMatrix.jsx` around lines 115 -
117, The component tracks which engine’s license dialog should be open via the
licenseDialogFor state (set by setLicenseDialogFor in the click handlers around
the engine rows) but never renders any dialog; add conditional JSX in the
EngineCompatibilityMatrix component to render the license dialog when
licenseDialogFor is non-null: render the LicenseDialog (or existing modal
component used for licenses) and pass the selected engine id/details from
licenseDialogFor plus handlers for onAccept and onClose that call
setLicenseDialogFor(null) (and trigger the acceptance flow already implemented
in the click handlers). Ensure the dialog receives the same accept logic used
elsewhere so the “Accept license” action becomes reachable.
| background: rgba(0, 0, 0, 0.55); | ||
| z-index: 10000; | ||
| padding: 1.5rem; | ||
| font-family: 'Inter Variable', 'Inter', system-ui, sans-serif; |
There was a problem hiding this comment.
Fix Stylelint font-family quote violations.
Line 16 and Line 78 use quoted family names that violate the current Stylelint rule (font-family-name-quotes).
🧹 Proposed fix
-.supertonic-license {
+.supertonic-license {
@@
- font-family: 'Inter Variable', 'Inter', system-ui, sans-serif;
+ font-family: 'Inter Variable', Inter, system-ui, sans-serif;
@@
-.supertonic-license__section code {
+.supertonic-license__section code {
@@
- font-family: 'JetBrains Mono', 'Menlo', monospace;
+ font-family: 'JetBrains Mono', Menlo, monospace;
}Also applies to: 78-78
🧰 Tools
🪛 Stylelint (17.11.1)
[error] 16-16: Expected no quotes around "Inter" (font-family-name-quotes)
(font-family-name-quotes)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/SupertonicLicenseDialog.css` at line 16, The
font-family declarations in SupertonicLicenseDialog.css use quoted family names
that trigger the Stylelint rule; update the two occurrences (the font-family
declaration near the top and the one around line 78) to use unquoted family
names per the project's font-family-name-quotes rule (e.g., change "font-family:
'Inter Variable', 'Inter', system-ui, sans-serif;" to use unquoted identifiers
like font-family: Inter Variable, Inter, system-ui, sans-serif;), ensuring both
instances (the top font-family declaration and the bottom one) are updated
consistently.
| f"info: current PINNED_REVISION_SHA already matches " | ||
| f"(no change needed)", |
There was a problem hiding this comment.
Drop unnecessary f prefixes (Ruff F541).
These lines are constant strings and should not be f-strings.
Suggested change
- f"info: current PINNED_REVISION_SHA already matches "
- f"(no change needed)",
+ "info: current PINNED_REVISION_SHA already matches "
+ "(no change needed)",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| f"info: current PINNED_REVISION_SHA already matches " | |
| f"(no change needed)", | |
| "info: current PINNED_REVISION_SHA already matches " | |
| "(no change needed)", |
🧰 Tools
🪛 Ruff (0.15.13)
[error] 225-225: f-string without any placeholders
Remove extraneous f prefix
(F541)
[error] 226-226: f-string without any placeholders
Remove extraneous f prefix
(F541)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/resolve_supertonic3_sha.py` around lines 225 - 226, The two literal
strings "info: current PINNED_REVISION_SHA already matches " and "(no change
needed)" are plain constants but are written as f-strings; remove the leading f
prefix from each so they are regular string literals (e.g., change f"... " to
"..." for those exact segments in scripts/resolve_supertonic3_sha.py, keeping
the concatenation/formatting intact where they appear).
| assert any("supertonic==1.3.1" in p or "supertonic==1.2.3" in p for p in pins), ( | ||
| f"supertonic optional-dep pin must be ==1.3.1 (Task 1 approved) " | ||
| f"or ==1.2.3 (fallback); got {pins!r}" | ||
| ) |
There was a problem hiding this comment.
Tighten the version gate to the approved pin only.
This assertion currently passes with supertonic==1.2.3, which weakens the intended regression guard for the approved 1.3.1 pin.
Suggested change
- assert any("supertonic==1.3.1" in p or "supertonic==1.2.3" in p for p in pins), (
- f"supertonic optional-dep pin must be ==1.3.1 (Task 1 approved) "
- f"or ==1.2.3 (fallback); got {pins!r}"
- )
+ assert any("supertonic==1.3.1" in p for p in pins), (
+ f"supertonic optional-dep pin must be ==1.3.1 (Task 1 approved); got {pins!r}"
+ )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_supertonic3.py` around lines 43 - 46, The assertion in
tests/test_supertonic3.py currently allows either "supertonic==1.3.1" or the
fallback "supertonic==1.2.3"; update the test to require only the approved pin
by changing the check on pins to assert the presence of "supertonic==1.3.1"
(remove the "1.2.3" alternative) so the assertion fails unless the exact
approved version is present.
| f"uv.lock contains an onnxruntime-gpu row " | ||
| f"(double-install risk per Pitfall 1)" |
There was a problem hiding this comment.
Remove stray f prefixes to satisfy Ruff F541.
These two string literals are not interpolated and currently trigger lint errors.
Suggested change
- f"uv.lock contains an onnxruntime-gpu row "
- f"(double-install risk per Pitfall 1)"
+ "uv.lock contains an onnxruntime-gpu row "
+ "(double-install risk per Pitfall 1)"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| f"uv.lock contains an onnxruntime-gpu row " | |
| f"(double-install risk per Pitfall 1)" | |
| "uv.lock contains an onnxruntime-gpu row " | |
| "(double-install risk per Pitfall 1)" |
🧰 Tools
🪛 Ruff (0.15.13)
[error] 68-68: f-string without any placeholders
Remove extraneous f prefix
(F541)
[error] 69-69: f-string without any placeholders
Remove extraneous f prefix
(F541)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_supertonic3.py` around lines 68 - 69, The two string literals in
tests/test_supertonic3.py currently use unnecessary f-string prefixes (the
fragments starting with f"uv.lock contains an onnxruntime-gpu row " and
f"(double-install risk per Pitfall 1)"); remove the leading "f" from both so
they become plain string literals (preserving their concatenation/spacing) to
satisfy Ruff F541. Locate these literals in the test function or assertion and
just drop the f prefixes from the two quoted fragments.
…heir package Phase 3 added `supertonic` as an optional dependency. The CI Tests job runs `uv sync` (no extras), so `test_cpu_only_honest` and `test_license_gate` in tests/test_supertonic3.py hit the "supertonic package not installed" fallback instead of the real import path, and fail. Bare `uv sync` is the right default for users (engines are opt-in), but the test environment should exercise the full surface. `--all-extras` keeps the smoke job lean (still bare `uv sync`) while letting Tests verify the integrated behavior of every optional engine. Future-proofs against the same failure mode in Phase 4 (GGUF) and any later optional engines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…urface routing verdict (#905) Six live-audit fixes for the Engines settings surface: - P1-A: the Supertonic license dialog was dead since #101 — `useState` threw away the state value (`const [, setLicenseDialogFor]`) and the imported dialog was never mounted, so "Accept license" did nothing. Keep the value and render LICENSE_DIALOGS[selected] with open/onClose/ onAccepted (accept → matrix reload). - P1-B: the matrix went stale after "Use" — active badge, Use buttons and family-tab captions stayed old until a manual Refresh. Await onSelect, then reload() so the picked engine reflects immediately. - P2-A: consume the /engines/select routing echo. A `cpu_fallback` pick now shows a warn-tone toast naming the reason ("running on CPU — …"); the plain success toast stays for accelerated/cpu_only. Shared helper used by both Settings→Engines and the first-run WizardLibrary. - P2-B: a CPU-native engine (gpu_compat == ("cpu",)) has nothing to fall back FROM, yet on a GPU/MPS host it was mis-classed cpu_fallback (warn). New routing rule classifies ("cpu",) as cpu_only (neutral) on any accelerator host; multi-target engines that could accelerate elsewhere are untouched. - P3-A: the routing reason was only a badge `title` (unreachable on keyboard/touch) — surface it as small visible text under the badge. - P3-B: an in-process "Test engine" pass is an import/liveness check, not a synthesis test — label it "deps OK" instead of a misleading "0 ms" latency; subprocess rows keep their real ping latency. Adds RTL + unit regression tests for all six and updates the routing unit tests to the corrected cpu-native intent. i18n keys added to en.json. Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Summary
Adds Supertonic-3 as the 7th opt-in TTS engine on the Phase 2
SubprocessBackendprimitive. CPU-only ONNX, 31 languages, ~99M params, ~400 MB model on first use. Opt-in byuv sync --extra supertonic; default install does not pull the wheel.Requirements coverage
_REGISTRY[\"supertonic3\"]resolves toSupertonic3Backend(aSubprocessBackendsubclass). Lazy-registered via_LAZY_REGISTRY[\"supertonic3\"] = (\"engines.supertonic3\", \"Supertonic3Backend\").[project.optional-dependencies] supertonic = [\"supertonic==1.3.1\"].uv pip listshows exactly oneonnxruntimerow (verified bytest_lockfile_no_onnxruntime_double_install).PINNED_REVISION_SHA = \"724fb5abbf5502583fb520898d45929e62f02c0b\"(40-char hex). This is the "Initial Supertonic 3 release" commit onSupertone/supertonic-3, identical to the SHA hard-coded insidesupertonic==1.3.1(supertonic.config.MODEL_CONFIGS). SHA existence verified via the HF model API. Bumps go throughscripts/resolve_supertonic3_sha.py(filters by tree contents — picks the latest commit onmainwhose tree touches.onnxortokenizer.json).is_available()returns\"ready (CPU-only via onnxruntime)\";gpu_compat = (\"cpu\",). Asserted bytest_cpu_only_honest— nocuda/mpsin the message.settings_store.get/set_license_acceptedhelpers + new loopback-onlyPOST/GET /api/settings/licenseendpoint with anengine_idallow-list (only\"supertonic3\"accepted). FrontendSupertonicLicenseDialog.jsxrenders MIT (code) + OpenRAIL-M (model) links;EngineCompatibilityMatrix.jsxsurfaces an "Accept license" button on the row when the backend'sreasonmentions "license not accepted".test_smoke_3langs_3sec. Also re-asserts the single-onnxruntimeinvariant post-synthesize. OMNIVOICE_SMOKE-gated because it downloads ~400 MB.Package legitimacy (Task 1 gate)
Verified before
uv add:Yu, Yechan / Juheon Lee / Hyeongju Kimatsupertone.ai; repogithub.com/supertone-inc/supertonic-py.Requires-Distdeclares onlyonnxruntime,numpy,soundfile,huggingface-hub(noonnxruntime-gpu).supertonic@0.0.1ships under same maintainer email (ato@supertone.ai) and points tosupertone-inc/supertonic-js. Same publisher, not a typosquat.subprocess/execat module top level.Resume signal:
approved 1.3.1.Files touched (16)
pyproject.toml+uv.lock— supertonic optional-dep + lockbackend/engines/supertonic3/— package:__init__.py,backend.py,constants.py,sidecar.pybackend/services/tts_backend.py—_LAZY_REGISTRYentry + install hintbackend/services/settings_store.py—get/set_license_acceptedwith re-read invariantbackend/api/routers/settings.py—POST/GET /api/settings/license(loopback-gated, allow-list)scripts/resolve_supertonic3_sha.py— release-prep SHA resolverfrontend/src/components/SupertonicLicenseDialog.jsx+.css— MIT + OpenRAIL-M acceptance modalfrontend/src/components/EngineCompatibilityMatrix.jsx— Accept-license button wiringtests/test_supertonic3.py(13 tests, 10 non-network + 3 OMNIVOICE_SMOKE-gated)tests/conftest.py—mock_settings_storein-memory fixtureNotable deviations from plan
frontend/src/components/SettingsEngines.jsxdoes not exist in this tree. The equivalent panel isEngineCompatibilityMatrix.jsx(already used bypages/Settings.jsx); I wired the license dialog there instead of creating a parallel UI surface. Same UX intent, fewer moving parts.test_registry_contains_supertonic3uses structural / duck-typed checks (__name__,_is_subprocess_isolated,hasattr) instead ofissubclass(cls, TTSBackend). The token-resolver test fixture purgessys.modules[\"services.*\"]between scenarios — that produces a freshTTSBackendclass object while the cachedSupertonic3Backendstill closes over the previous one, andissubclassreturns False even though the class is correct. The duck-typed checks survive that re-import drift (same patternlist_backends()uses to detect SubprocessBackend subclasses).Supertonic3Backend.venv_python()returnssys.executable. Subprocess isolation is for parity with the Phase 2 pattern (crash containment, cold-start without blocking the API), not for dependency isolation.Test results
uv run pytest tests/test_supertonic3.py -v— 10 passed, 3 skipped.uv run pytest tests/smoke/ -q— 4 passed.uv run pytest tests/ -q --ignore=tests/manual— 412 passed, 0 failed (baseline preserved alongside the new engine).Test plan
uv sync --frozen --no-dev(NO--extra supertonic) keeps existing engines functional and does NOT install supertonic.uv sync --frozen --extra supertonicinstalls supertonic 1.3.1;uv pip list | grep -ci '^onnxruntime'returns 1;grep -ci 'onnxruntime-gpu' <<<$(uv pip list)returns 0.uv run pytest tests/test_supertonic3.py -vpasses 10/13 with the 3 network-gated tests skipped.OMNIVOICE_SMOKE=1 uv run pytest tests/test_supertonic3.py -vruns the full 13 tests including the 3-language synthesis smoke (~400 MB HF download on first run).cd frontend && bun run lintreports no new violations onSupertonicLicenseDialog.jsx/EngineCompatibilityMatrix.jsx.Do NOT auto-merge. Awaiting human review per plan front-matter (
autonomous: false).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests