Theme C — High-interaction workspaces (Components v2)¶
Status: active (1.x)
Last updated: 2026-08-27
Roadmap home: docs/ROADMAP.md §C
Product constraint: Streamlit shell + Python domain; specialised CCv2 workspaces only.
Goal¶
Escape Streamlit’s rerun model for workstation pages (Speaker ID → Corrections → later rich edit) without abandoning Streamlit for the analysis workbench.
Locked decisions¶
Shared
SpeakerIdActionServiceowns mutations for legacy and CCv2 (Phase −1 shipped).Three state tiers: browser-local ephemeral · sparse Streamlit
setStateValue· revisioned domain triggers. Never streamcurrent_time_msviasetStateValue.Every domain trigger is a revisioned command envelope; acks carry authoritative revisions.
Optimistic reconciliation: one mutating speaker action in flight; nav may be optimistic; ignore stale/out-of-order acks by
action_seq; duplicateaction_idnever writes twice; protocol/build mismatch fails closed.Python CCv2
key=is transcript-scoped (speaker_id_ws:{transcript_id}), not global.ClipTransport T0 = measured base64 in JSON
data. Binary only via a separate tested conduit (T1); no undocumented Streamlit media URLs.CCv2 bridge uses only non-blocking ClipService APIs (
cached_clip_status/get_cached_clip_bytes/enqueue_clip). Never coldget_clip_path/get_clip_bytes.Packaged CCv2 from Streamlit
component-templatev2 layout: component-level[[tool.streamlit.component.components]], assets in wheel/sdist.Dist policy: commit built
frontend/buildassets into the workspaces package (reproducible installs without Node at runtime). CI rebuilds and fails on drift. Lockfile + Node engines pinned.Shadow DOM = style isolation only. Render text via
textContent.asset_diris public.Feature flag default on (Phase 5). Rollback with
TX_SPEAKER_ID_WORKSPACE_COMPONENT=0. Missingtranscriptx-workspacesfalls through to legacy. Legacy retired only in Phase 9.
Protocol¶
See transcriptx.app.speaker_id.protocol and transcriptx_workspaces protocol modules.
Field |
Role |
|---|---|
|
Fail closed on mismatch ( |
|
Fail closed → reload/fallback |
|
Idempotency |
|
Monotonic; ignore out-of-order acks |
|
Workspace identity |
|
Stale reject |
|
When clip-relevant |
Prefetch / memory budgets¶
Budget |
Default |
|---|---|
Max clips per warm request |
8 |
Max bytes per clip into browser |
1_500_000 |
Max total Blob memory / workspace |
8_000_000 |
Max concurrent miss retries |
2 |
Retry count |
4 |
Backoff |
200ms × 2^n (cap 3s) |
Global ClipService inflight |
existing |
Revoke Blob URLs on replacement, transcript switch, and unmount.
Quantitative gates¶
Phase 0¶
Zero
<audio>element replacement on metadata-onlydatarefresh (element identity)Cleanup revokes Blob URLs / clears timers
Dist policy locked (this doc § Locked #9)
Phase 2¶
Zero audio replacement on mapping/metadata refresh for same transcript key
Bridge handlers return pending without joining cold ffmpeg (no multi-second block)
Prefetch budgets held; multi-session backpressure respected
Record p50/p95 trigger→ack for nav/warm in CI artefacts when measured
Phase 5 (default-on)¶
Browser harness green (audio identity, transcript switch, keyboard suppression)
Docker/web images install
transcriptx-workspaceswheelFlag defaults on; env/session rollback retained for one release window
Missing package auto-falls through to legacy (does not brick Speaker ID)
Zero duplicate mutations under replayed
action_id
Feature flags¶
Flag |
Default |
Meaning |
|---|---|---|
|
|
CCv2 Speaker ID workspace |
|
|
CCv2 Corrections |
Env enable: TX_SPEAKER_ID_WORKSPACE_COMPONENT=1 (redundant once default-on). Env rollback: TX_SPEAKER_ID_WORKSPACE_COMPONENT=0.
Frontend toolchain¶
Node
>=20 <23(CI uses 22.x)npm lockfile committed
@streamlit/component-v2-libpinned in workspaces packageVite,
base: "./", hashedindex-*.js/index-*.css
Keyboard map (Phase 3)¶
Active only when workspace focused; suppressed in inputs/contenteditable.
Key |
Action |
|---|---|
|
Next speaker |
|
Prev speaker |
|
Play/pause |
|
Save name |
|
Ignore toggle |
|
Help |
Avoid browser/AT reserved chords.
ClipTransport¶
T0: base64 (or data-URL string) inside JSON metadata
dataT1: only if measured need — dedicated binary conduit component whose entire
data=is bytes, correlated byclip_id+ revision in the metadata component; tested on min + current StreamlitT2: browser
Map<clipId, BlobURL>under budgetsT3: documented local route — escalation only
Invest / narrow / defer (after Phase 3)¶
Written decision required before Corrections expansion or SPA rewrite. Escalate to a custom local frontend only with evidence that CCv2 remount/bytes/focus limits block product goals.
First escalation (if the gate fires): loopback application API over existing app.controllers / workflows, with Streamlit remaining the only client until that API is stable — then grow workspaces off Streamlit hosting. Do not jump to Gradio/NiceGUI or an OS-native workbench rewrite. Full Streamlit retirement is a late 1.x / 2.0 programme (theme I), not a Theme C deliverable. Roadmap: ROADMAP.md §C (shell review) and §I.
Phase 9 legacy retirement¶
Remove legacy fragment contracts only when all are true:
Shared
SpeakerIdActionServicemeans both paths have identical domain semantics (enforced by tests) — doneRollback / flag-off path has survived the stated release window after default-on
Browser acceptance suite remains green in CI (Streamlit min + current)
Explicit changelog + known-limitations update
Until then: keep “single @st.fragment on Speaker ID” and flag-off characterisation contracts. Do not remove legacy merely because CCv2 is default-on.