feat: Voice Lab as a Codescribe website module - #78
Conversation
There was a problem hiding this comment.
An organization admin can view or raise the cap at claude.ai/admin-settings/claude-code. The cap resets at the start of the next billing period.
Once the cap resets or is raised, reopen this pull request to trigger a review.
There was a problem hiding this comment.
Pull request overview
Introduces “Voice Lab” as a first-class module inside the existing Codescribe website, surfacing the on-demand teach() workflow and Seal Atlas quality-report viewing while formalizing the Seal Atlas HTML handshake contract across docs and core.
Changes:
- Adds
/voice/labAstro page with “Run teacher” UI and on-demand mounting of Seal Atlas take 01. - Implements a pure, local TypeScript
teach()(plus tests) for interactive teacher reports on the site. - Shifts corpus “quality HTML” output toward Seal Atlas (
quality/seal-atlas.{profile}.html) and documents/validates the HTML handshake contract.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| site/src/pages/voice/lab.astro | New Voice Lab page UI + client-side teacher runner + on-demand atlas iframe. |
| site/src/lib/teacher/teach.ts | Pure TS implementation of teach() contract + bundled proof take. |
| site/src/lib/teacher/teach.test.ts | Node test coverage for token normalization + proof take expectations. |
| site/src/components/Nav.astro | Adds “lab” nav entry + current-page handling + brand link to home. |
| site/src/components/Footer.astro | Adds “voice lab” footer link. |
| site/public/voice/atlas/the-engine.contract.html | Adds a public, static HTML contract plate for THE ENGINE. |
| site/package.json | Adds test:teacher script to run the new teacher unit tests. |
| docs/THE_ENGINE_CONTRACT.md | Links and clarifies the Seal Atlas HTML handshake and output locations. |
| docs/quality-reports/CONTRACT.md | New documented handshake contract for Seal Atlas HTML outputs. |
| core/quality/seal_atlas_html.rs | Adds Seal Atlas HTML renderer and handshake-valid HTML tests. |
| core/quality/mod.rs | Exposes Seal Atlas renderer types and validate_quality_html. |
| core/quality/engine_contract.rs | Adds Seal Atlas HTML handshake validator + tests ensuring contract files exist. |
| CHANGELOG.md | Documents Voice Lab module and Seal Atlas-as-report shift. |
| bin/codescribe-corpus.rs | Writes quality/seal-atlas.{profile}.html and moves Qube to quality/qube.{profile}.html. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Overlay canvas laws are now on this PR (port onto the #77
Still stacked on #77. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 25 changed files in this pull request and generated no new comments.
Suppressed comments (4)
core/quality/seal_atlas_html.rs:159
html_escapedoes not escape HTML special characters and currently contains an invalid Rust string literal (.replace('"', """)), which will fail to compile and also risks HTML injection in rendered reports.
fn html_escape(value: &str) -> String {
value
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
core/quality/engine_contract.rs:31
validate_quality_html's quality-report-surface check can pass even if the meta has the wrong content (it only requires the tag to exist and for the document to mention “seal atlas” somewhere). This weakens the handshake the Voice Lab is supposed to enforce.
if !html.contains(r#"name="quality-report-surface""#)
|| !lowered.contains("seal-atlas") && !lowered.contains("seal atlas")
{
failures.push("missing meta quality-report-surface=seal-atlas".into());
}
site/src/pages/voice/lab.astro:172
- escapeHtml currently performs no escaping (e.g.
.replaceAll('&', '&')) while the page interpolates user-controlled text intoinnerHTML(attention table + lexicon list). This enables XSS via crafted textarea content.
function escapeHtml(value: string): string {
return value
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
macos/Codescribe/Screens/Overlay/OverlayState.swift:2237
- ControllerDictationEngine.transcribeFile always throws, but the UI enables Retranscribe based on
lastSessionAudioPath(). As a result, Retranscribe will be offered in production yet can never succeed until the UniFFI Swift bindings are regenerated and wired through toCodescribeHotkeys.transcribe_file.
func transcribeFile(path: String) async throws -> CsTranscription {
// Rust already owns hq:/cloud: on CodescribeHotkeys.transcribe_file.
// Swift bindings appear after `make app-bindings`; until then this
// seam stays compile-safe so OverlayState + tests still ship.
throw NSError(
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 31 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
site/src/pages/voice/lab.astro:173
- escapeHtml() currently does not escape anything (it replaces characters with themselves). Because the page uses innerHTML to render teacher output, this makes it possible for transcript content (or lexicon hints) containing HTML to execute as markup/script.
core/quality/engine_contract.rs:34 - validate_quality_html() does not actually validate that the quality-report-surface meta has content="seal-atlas"; it only checks that the meta name exists OR that the document contains “seal atlas” somewhere. That can let non-Seal-Atlas pages with the wrong meta value pass the handshake.
if !html.contains(r#"name="quality-report-surface""#)
|| !lowered.contains("seal-atlas") && !lowered.contains("seal atlas")
{
failures.push("missing meta quality-report-surface=seal-atlas".into());
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 46 out of 48 changed files in this pull request and generated no new comments.
Suppressed comments (2)
site/src/pages/voice/lab.astro:174
- escapeHtml() is currently a no-op (it replaces characters with themselves) but the output is inserted via innerHTML. This leaves the Voice Lab page vulnerable to script injection from textarea inputs (e.g., tokens rendered into the Needs attention table).
core/quality/engine_contract.rs:34 - validate_quality_html() does not actually verify that the quality-report-surface meta tag's content is "seal-atlas"; it only checks that the tag exists and that the HTML mentions "seal atlas" somewhere. This can incorrectly treat pages with the wrong meta content as handshake-valid and can also emit a misleading 'missing meta' failure for pages that simply omit the phrase in body/title.
if !html.contains(r#"name="quality-report-surface""#)
|| !lowered.contains("seal-atlas") && !lowered.contains("seal atlas")
{
failures.push("missing meta quality-report-surface=seal-atlas".into());
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 76 out of 78 changed files in this pull request and generated no new comments.
Suppressed comments (2)
core/quality/engine_contract.rs:34
validate_quality_htmlcurrently treats the surface meta as present if the HTML containsname="quality-report-surface"and the stringseal-atlasanywhere. That can pass even when the meta exists but has the wrong content, or whenseal-atlasappears only in body copy. It should explicitly verify the meta tag’s content isseal-atlas(accepting common attribute order/quoting).
if !html.contains(r#"name="quality-report-surface""#)
|| !lowered.contains("seal-atlas") && !lowered.contains("seal atlas")
{
failures.push("missing meta quality-report-surface=seal-atlas".into());
}
CHANGELOG.md:79
- The changelog says the default Whisper idle-unload TTL is now 60 seconds, but this PR updates the default to 1800 seconds (30 minutes) in code/docs (
CODESCRIBE_WHISPER_IDLE_UNLOAD_SECS). This entry should be updated so release notes match behavior.
- **Whisper residency is bounded and observable** — the normal idle-weight TTL
is now 60 seconds (one minute), while `CODESCRIBE_WHISPER_IDLE_UNLOAD_SECS=0`
remains the explicit power-user keep-warm override. INFO lifecycle events now
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 78 out of 80 changed files in this pull request and generated no new comments.
Suppressed comments (3)
macos/Codescribe/Screens/Settings/VoiceLabPanel.swift:180
helperCompare/helperText/helperPendingare stored once for the wholeVoiceLabPanel, but they’re rendered inside the currently selected correction row. This makes helper results effectively “global” state: after retranscribing one correction, the compare text and “Use helper as correction” button can appear while browsing a different correction, and repeated runs overwrite each other without being tied to a specific row.
macos/Codescribe/Screens/Overlay/OverlayState.swift:876retranscribe(pass:)trims the returned transcript intonextand validates it, but then assignsformattedTextfromresult.text(untrimmed). This can reintroduce leading/trailing whitespace even though you already normalized and validatednext.
app/controller/mod.rs:299retain_last_session_audiowriteslast_session.wavdirectly viastd::fs::copy. If the app is interrupted mid-copy (crash / power loss), you can leave a truncated/corruptedlast_session.wavthat Retranscribe will later consume. Writing to a temp file and renaming into place would make this update atomic on Unix filesystems.
fn retain_last_session_audio(path: &std::path::Path) {
let dest = crate::config::Config::config_dir().join("last_session.wav");
match std::fs::copy(path, &dest) {
Ok(_) => info!("last_session.wav retained at {}", dest.display()),
Err(err) => warn!("last_session.wav retain failed: {err:#}"),
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 86 out of 89 changed files in this pull request and generated no new comments.
Suppressed comments (4)
core/quality/engine_contract.rs:34
validate_quality_htmldoes not currently verify that thequality-report-surfacemeta tag'scontentis actuallyseal-atlas; it only checks that the name exists and that the document mentions "seal-atlas"/"seal atlas" somewhere. This can accept an HTML page with the wrong surface meta while still mentioning Seal Atlas in prose.
if !html.contains(r#"name="quality-report-surface""#)
|| !lowered.contains("seal-atlas") && !lowered.contains("seal atlas")
{
failures.push("missing meta quality-report-surface=seal-atlas".into());
}
Makefile:186
- The PR description states this change is "only the Voice Lab module that belonged on the site", but this diff also changes macOS app behavior/UI, Rust engine behavior, scripts/release tooling, and bumps the workspace version to 0.14.1. Please update the PR description to reflect the actual scope, or split the unrelated changes into separate PRs so reviewers can evaluate risk and intent accurately.
install-app:
@echo "Building $(CODESCRIBE_APP_NAME).app (SwiftUI, optimized local profile) via scripts/build-app.sh ..."
@echo "Local install uses the development license verifier; CODESCRIBE_LICENSE_PUBLIC_KEY_HEX is reserved for distribution builds."
@BIT=$$(./scripts/developer-surface-gate.sh); \
echo "Developer surface: $$BIT (1 needs legit Sparkle + license public keys on this machine)."; \
core/quality/engine_contract.rs:29
validate_quality_htmlcan pass even when theengine-contractmeta tag has the wrongcontentvalue, as long as the HTML mentionsthe-engine/v1somewhere else. The handshake should validate the meta tag'scontentattribute explicitly (per docs/quality-reports/CONTRACT.md).
if !html.contains(r#"name="engine-contract""#) || !html.contains(ENGINE_CONTRACT_ID) {
failures.push("missing meta engine-contract=the-engine/v1".into());
}
core/asr_session/bootstrap.rs:79
live_websocket_endpoint()rewrites loopbackhttp(s)://…/v1/audio/transcriptionsto aws(s)URL, but it does not account for the Voice Lab split ports (:8444multipart worker vs:8446live socket). IfSTT_ENDPOINTis set to the loopback file worker URL, this currently produces a websocket URL on the wrong port and makes Cloud mode look unavailable.
if url.path().ends_with("/transcriptions") {
let path = url.path().trim_end_matches("transcriptions").to_string() + "transcribe";
url.set_path(&path);
}
Some(url.to_string())
Stacked on #77. Teacher + Seal Atlas live at /voice/lab — same teach() triangle as codescribe-teacher, idle until Run; Atlas HTML loads only on demand. Corpus writes Seal Atlas as the quality-report surface. Authored-By: grok <agents@vetcoders.io>
Four UX laws from the parked overlay branch, rewritten on OverlayState from feat/overlay-assist-stack — not a cherry-pick: - Assistive stay on the live overlay; chrome language is RECORDING / AGENT / PROCESSING / READY. Copy is live from the first letter. - Action row whispers at 0.22 until hover. - Retranscribe: Full HQ + Cloud on last_session.wav (retained at stop). - Forest glass, chrome grab, panel non-key until FINAL is clicked. Screenshot chords rise to statusBar; SecurityAgent yields. Authored-By: grok <agents@vetcoders.io>
- repair Seal Atlas HTML escaping and restore the canonical engine contract path - wire retranscription through generated UniFFI bindings - apply repository format contracts across the stacked Voice Lab surface Authored-By: codex <agents@vetcoders.io>
- Kill Final pass as a Settings control; leftover writes persist off - Add apple_only/local_power/cloud picker that writes CODESCRIBE_ASR_MODE - Selecting Cloud grants CODESCRIBE_CLOUD_CONSENT; display stays Apple without it - Move STT_ENDPOINT onto Dictation as the live WSS socket - Active STT reads last serving (local_apple → Apple) and refreshes on appear/stop - Retranscribe toasts missing last_session.wav and the real engine error - Expose asr_mode, cloud_consent, asr_gateway_url on CsSettings Authored-By: grok <agents@vetcoders.io>
- Inherit draft sample range, segments, and words on transcript_sealed - Publish word/utterance grain spans with optional capture-hop dBFS - Prefer SealedSpan.words when emitting Apple UtteranceFinal segments - Start a session energy ladder at live capture, queryable by sample range Authored-By: grok <agents@vetcoders.io>
…words - Allow high-coverage tail fill and digit-run unfuse; keep the 0.50 rewrite cap - Refuse 0-match short collapses (dwa→w, 3,4) and leading "po" on a healthy canvas - Overlay copy/close saves evidence only; lexicon grows on explicit teach Authored-By: grok <agents@vetcoders.io>
… decode - Default CODESCRIBE_WHISPER_IDLE_UNLOAD_SECS is 1800, not 60 - Stamp last_used when a decode finishes so a long HQ pass is not treated as idle - Overlay Retranscribe was dying because the reaper saw acquire-time idle and unloaded mid/after file HQ Authored-By: grok <agents@vetcoders.io>
… install-app - gate prints 1 only when Sparkle + license public keys both resolve - public clone install-app stays Lab-off - release/DMG refuse CSDeveloperSurface - cargo still uses the development license verifier Authored-By: grok <agents@vetcoders.io>
…e builds - hide Settings → Lab unless CSDeveloperSurface is baked - tray Voice Lab opens the loopback PWA - Agent rail stays visible on production and developer builds Authored-By: grok <agents@vetcoders.io>
…history rows - local_power → HQ file pass; cloud → cloud file pass; apple_only disabled - no last_session.wav fallback - live Whisper compare waits for the keyed install-app (60s reaper on the running app) Authored-By: grok <agents@vetcoders.io>
- Lab mode now vetoes OverlayController without flipping the tray toggle - Production bundles ignore leftover codescribe.lab_mode UserDefaults - Dictionary Retranscribe runs hq:/cloud: on the row archive only - Missing archive refuses last_session.wav; Accept is still Save correction - Document the Dictionary helper as an explicit file surface in STT_CONTRACT Authored-By: grok <agents@vetcoders.io>
- minor: Lab surface, Dictionary helper, bus pins, 30-minute Whisper idle - production DMG still refuses CSDeveloperSurface - SITE_VERSION stays 0.13.3 until a published GitHub release Authored-By: grok <agents@vetcoders.io>
- Settings Test posted the key probe at config.stt_endpoint even when that URL was the Voice Lab WebSocket, so validate_remote_endpoint failed closed - Map known live sockets to /v1/audio/transcriptions; loopback :8446 → :8444 - Leave unknown wss URLs unmapped so the insecure-endpoint check still fires Authored-By: grok <agents@vetcoders.io>
- Sign-in stored the ChatGPT row only after a probe to api.openai.com/v1/responses - Codex public tokens never carry api.responses.write, so login died while Libraxis keys still worked - Persist identity after exchange, same as main/develop; Responses write stays a row/lane Test Authored-By: grok <agents@vetcoders.io>
- Patch version for the STT Test file probe and ChatGPT OAuth sign-in - Add make release-stable / install-app-release: notarized slim DMG plus the same stapled .app, no re-sign - Keep SITE_VERSION at 0.13.3 until a published GitHub release Authored-By: grok <agents@vetcoders.io>
- Unblock make check prettier on two markdown tables/lines - No wording change Authored-By: grok <agents@vetcoders.io>
- Semgrep auto rule treated a templated live-socket URL as an open WebSocket - Keep the remap contract; write 127.0.0.1 explicitly Authored-By: grok <agents@vetcoders.io>
- Isolated make verify failed because copy no longer writes lexicon.custom.jsonl - Align the quality-chain test with overlay_commit_teaches_lexicon Authored-By: grok <agents@vetcoders.io>
…t SKU - Incomplete HF snapshots no longer win over ~/.codescribe/models - CODESCRIBE_EMBED_WHISPER=1 without tokenizer+mel+weights aborts the build Authored-By: grok <agents@vetcoders.io>
- HTTPS /v1/audio/transcriptions no longer becomes WSS for api.libraxis.cloud - Settings Test inverts any ws(s) …/transcribe onto …/transcriptions; loopback :8446 → :8444 - Contract matches: OpenAI and Libraxis file URLs take the same client path Authored-By: grok <agents@vetcoders.io>
…udio - Overlay Retranscribe click is the HQ pass; hold the menu for Cloud - Dictionary pairs `_raw_1.txt` with `_raw.m4a` so the button is not grey - Cloud pass uses the same file invert as Test (Voice Lab :8446 → :8444) Authored-By: grok <agents@vetcoders.io>
9af80e1 to
26ba1c3
Compare
Stacked on #77 (
feat/overlay-assist-stack). Continues from that tip — no rebase of the overlay throne commits.This branch is not only the Voice Lab site module. It also carries the 0.14.1 everyday-stable cut and STT/app wiring that landed on
feat/site-voice-labafter #77. Handshake-strict quality HTML and Voice Lab:8444↔:8446remaps continue on #79.Voice Lab (the named slice)
Voice Lab is a Codescribe module, not a sidecar PWA.
/voice/labin the existing site chrome (Space Grotesk, terracotta, nav/footer).teach()triangle ascodescribe-teacher(live × Whisper × human → Needs attention → lexicon). Nothing runs until Run teacher. Proof take 01 is a button, not a boot path.codescribe-corpuswrites Seal Atlas HTML (quality/seal-atlas.{profile}.html); handshake indocs/quality-reports/CONTRACT.md.Mobile is readable. It is not the product.
Also on this branch (review this risk)
/v1/audio/transcriptionsstays file (OpenAI and Libraxis). Explicitws/wssstays a live socket. Settings → Test / Cloud Retranscribe invert a stored socket to multipart; loopback:8446→ file:8444.make install-appLab gate, Dictionary_rawarchive pairing.Why not a daemon
The website is static on Caddy.
teach()is the same pure function as the Rust CLI, invoked when the operator asks. A laterPOST /voice/lab/teachcan wrap the binary without changing the contract.Verify
Stack