Skip to content

feat: Voice Lab as a Codescribe website module - #78

Open
div0-space wants to merge 22 commits into
feat/overlay-assist-stackfrom
feat/site-voice-lab
Open

feat: Voice Lab as a Codescribe website module#78
div0-space wants to merge 22 commits into
feat/overlay-assist-stackfrom
feat/site-voice-lab

Conversation

@div0-space

@div0-space div0-space commented Aug 16, 2026

Copy link
Copy Markdown
Member

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-lab after #77. Handshake-strict quality HTML and Voice Lab :8444:8446 remaps continue on #79.

Voice Lab (the named slice)

Voice Lab is a Codescribe module, not a sidecar PWA.

  • /voice/lab in the existing site chrome (Space Grotesk, terracotta, nav/footer).
  • Teacher on demand. Same teach() triangle as codescribe-teacher (live × Whisper × human → Needs attention → lexicon). Nothing runs until Run teacher. Proof take 01 is a button, not a boot path.
  • Seal Atlas on demand. The 120 KB gold take is not fetched on page load. Open take 01 mounts it.
  • Corpus surface. codescribe-corpus writes Seal Atlas HTML (quality/seal-atlas.{profile}.html); handshake in docs/quality-reports/CONTRACT.md.

Mobile is readable. It is not the product.

Also on this branch (review this risk)

  • Workspace bump 0.14.0 → 0.14.1 and fat-SKU Whisper embed fail-closed.
  • Public HTTPS /v1/audio/transcriptions stays file (OpenAI and Libraxis). Explicit ws/wss stays a live socket. Settings → Test / Cloud Retranscribe invert a stored socket to multipart; loopback :8446 → file :8444.
  • Overlay copy-as-evidence, finalize-teaches, developer make install-app Lab gate, Dictionary _raw archive pairing.
  • macOS overlay / Settings / Voice Lab panel and Rust engine changes that are not the Astro page.

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 later POST /voice/lab/teach can wrap the binary without changing the contract.

Verify

cd site && npm run test:teacher
# site: make site-dev   (not make site:dev — Make treats : as a rule separator)
# then: open /voice/lab → Load proof take 01 → Run teacher → Open take 01

Stack

develop
  ← #73 fix/the-tail-patches
    ← #74 fix/engine-routing
      ← #76 fix/delivery-route-throne
        ← #77 feat/overlay-assist-stack
          ← #78 feat/site-voice-lab   ← this PR
            ← #79 fix/p0-p2-before-release

Copilot AI lite review requested due to automatic review settings August 16, 2026 12:11

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Code review skipped — your organization has reached its monthly code review spending cap.

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/lab Astro 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.

Comment thread site/src/pages/voice/lab.astro
Comment thread core/quality/seal_atlas_html.rs
Comment thread core/quality/engine_contract.rs
Copilot AI review requested due to automatic review settings August 16, 2026 12:34

Copy link
Copy Markdown
Member Author

Overlay canvas laws are now on this PR (port onto the #77 OverlayState, not a cherry-pick of the parked branch).

  • Assistive stays on the live overlay. Tag language: RECORDING / AGENT / PROCESSING / READY. Copy from the first letter. Timer survives stop.
  • Action row sits at 0.22 until hover.
  • Retranscribe menu: Full HQ + Cloud. Stop retains last_session.wav. Rust hq: / cloud: prefixes are on CodescribeHotkeys.transcribe_file — Swift bindings land with make app-bindings; OverlayState + tests already speak the engine protocol.
  • Forest glass (sitsInForest), grab on chrome only, panel non-key until you click FINAL. Screenshot chords → statusBar; SecurityAgent yields.

Still stacked on #77.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_escape does 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 into innerHTML (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 to CodescribeHotkeys.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(

Copilot AI review requested due to automatic review settings August 16, 2026 13:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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());
    }

Comment thread macos/Codescribe/Screens/Overlay/OverlayState.swift Outdated
Copilot AI review requested due to automatic review settings August 16, 2026 16:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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());
    }

Copilot AI review requested due to automatic review settings August 17, 2026 04:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_html currently treats the surface meta as present if the HTML contains name="quality-report-surface" and the string seal-atlas anywhere. That can pass even when the meta exists but has the wrong content, or when seal-atlas appears only in body copy. It should explicitly verify the meta tag’s content is seal-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

Copilot AI review requested due to automatic review settings August 17, 2026 12:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/helperPending are stored once for the whole VoiceLabPanel, 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:876
  • retranscribe(pass:) trims the returned transcript into next and validates it, but then assigns formattedText from result.text (untrimmed). This can reintroduce leading/trailing whitespace even though you already normalized and validated next.
    app/controller/mod.rs:299
  • retain_last_session_audio writes last_session.wav directly via std::fs::copy. If the app is interrupted mid-copy (crash / power loss), you can leave a truncated/corrupted last_session.wav that 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:#}"),

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_html does not currently verify that the quality-report-surface meta tag's content is actually seal-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_html can pass even when the engine-contract meta tag has the wrong content value, as long as the HTML mentions the-engine/v1 somewhere else. The handshake should validate the meta tag's content attribute 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 loopback http(s)://…/v1/audio/transcriptions to a ws(s) URL, but it does not account for the Voice Lab split ports (:8444 multipart worker vs :8446 live socket). If STT_ENDPOINT is 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())

grok and others added 5 commits August 19, 2026 12:01
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>
@div0-space
div0-space force-pushed the feat/site-voice-lab branch from 9af80e1 to 26ba1c3 Compare August 19, 2026 10:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants