# Theme C — High-interaction workspaces (Components v2) Status: active (1.x) Last updated: 2026-08-27 **Roadmap home:** [docs/ROADMAP.md](../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 1. Shared `SpeakerIdActionService` owns mutations for **legacy and CCv2** (Phase −1 shipped). 2. Three state tiers: browser-local ephemeral · sparse Streamlit `setStateValue` · revisioned domain triggers. **Never** stream `current_time_ms` via `setStateValue`. 3. Every domain trigger is a revisioned command envelope; acks carry authoritative revisions. 4. Optimistic reconciliation: one mutating speaker action in flight; nav may be optimistic; ignore stale/out-of-order acks by `action_seq`; duplicate `action_id` never writes twice; protocol/build mismatch fails closed. 5. Python CCv2 `key=` is **transcript-scoped** (`speaker_id_ws:{transcript_id}`), not global. 6. ClipTransport **T0 = measured base64** in JSON `data`. Binary only via a separate tested conduit (T1); no undocumented Streamlit media URLs. 7. CCv2 bridge uses **only** non-blocking ClipService APIs (`cached_clip_status` / `get_cached_clip_bytes` / `enqueue_clip`). Never cold `get_clip_path` / `get_clip_bytes`. 8. Packaged CCv2 from Streamlit `component-template` v2 layout: component-level `[[tool.streamlit.component.components]]`, assets in wheel/sdist. 9. Dist policy: **commit built `frontend/build` assets** into the workspaces package (reproducible installs without Node at runtime). CI rebuilds and fails on drift. Lockfile + Node engines pinned. 10. Shadow DOM = style isolation only. Render text via `textContent`. `asset_dir` is public. 11. Feature flag default **on** (Phase 5). Rollback with `TX_SPEAKER_ID_WORKSPACE_COMPONENT=0`. Missing `transcriptx-workspaces` falls through to legacy. Legacy retired only in Phase 9. ## Protocol See `transcriptx.app.speaker_id.protocol` and `transcriptx_workspaces` protocol modules. | Field | Role | |-------|------| | `protocol_version` | Fail closed on mismatch (`1`) | | `frontend_build_id` | Fail closed → reload/fallback | | `action_id` | Idempotency | | `action_seq` | Monotonic; ignore out-of-order acks | | `transcript_id` / `transcript_revision` | Workspace identity | | `expected_speaker_id` / `expected_mapping_revision` | Stale reject | | `audio_fingerprint` | 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 `_MAX_INFLIGHT` (8) | Revoke Blob URLs on replacement, transcript switch, and unmount. ## Quantitative gates ### Phase 0 - Zero `