Skip to content

feat(studio): align chat transcript and composer - #1886

Merged
samuv merged 42 commits into
plan/studio-chat-transcript-composerfrom
impl/studio-chat-transcript-composer
Sep 25, 2026
Merged

samuv merged 42 commits into
plan/studio-chat-transcript-composerfrom
impl/studio-chat-transcript-composer

Conversation

@samuv

@samuv samuv commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Tracking: #1847

Summary

Stage: Implementation. This PR is stacked on the independent Plan / Interface PR #1880, which targets main. It implements all seven scenarios and 17 criteria in docs/acceptance/studio-chat-transcript-composer.md: a flat streamed transcript with markdown, code, images and tool results; truthful message status; a responsive composer; image and voice input; model/mode selection; send, queue, steer, replay, cancel and retry; IME safety; seed prompts; title updates; same-tab return notices; and recorded scheduled-delivery notes with replies. Inline approvals (#1848), delegated activity (#1849), side panels (#1850), and MCP inventory (#1851) remain outside this PR.

The stacked plan commit is 34e414c9011e0061b60eca62e4caf7df85900fc9 after rebasing on origin/main c8d1c32d6e07574ab8980e7efbf007a6b4273195. #1880 remains unmerged, so there is no merged approved baseline; the directing human explicitly authorized this stacked implementation. The plan's landed status in this branch is a proposed transition that becomes authoritative only after the implementation PR merges. Humans alone merge either PR.

Interface conformance

Interfaces match the #1847 plan contract: Yes. ADR 0351 remains intact: browser requests use the same-origin BFF and generated Studio client; only the BFF calls the daemon through the published @stacklok-oss/mecatl-sdk. Opaque BFF session IDs stay in chat URLs, BFF run IDs target controls, and SDK objects and credentials stay server-side. Daemon gRPC/protobuf, exported Go APIs, tool schemas, CLI/config, and the run.started | run.event | run.truncated | run.error SSE union are unchanged.

Additive BFF fields are titleRevision/titleProvenance on inventory and rename responses plus optional inert delivery annotations on canonical fenced user prompts; OpenAPI and the browser client are generated from source schemas. Image sending keeps the existing limits (16 images, 10 MiB each, 20 MiB decoded total) and selected-model capability gate. The BFF rejects excess before SDK access; the served CSP, auth, origin and CSRF gates remain in force. The arrival send=1 link always requires an explicit Send click. A visible idle chat reconciles full saved turns, including another client's messages and a delivery's reply, without inventing durable message IDs.

The directing human authorized a narrow amendment to already-landed Studio bootstrap AC4.6: clear unmarked studio. account data on first sight after storage failure, while keeping marked same-account data and device theme. That amendment lives in Plan / Interface PR #1880 and is implemented here to satisfy #1847 AC4.3. There is no other #1847 interface deviation or ADR amendment. Account-scoped folders, per-session queues, failed prompts, profile/model/canvas/panel state and UI scale are quarantined on an account transition; a stale tab cannot clear another account's marked data while authentication is rechecked.

Verification

  • The acceptance-plan checker and fixtures passed on docs(plan): define Studio chat transcript and composer #1880. On this rebased implementation, task studio:check passed Biome, type checks, generated contract equality, 518 web tests and 169 server tests. task studio:build, CGO_ENABLED=0 task docs, task site:build, and task ac-trace-strict passed; strict trace resolves 17/17 chat ACs with zero failures. git diff --check passed.
  • Focused Playwright auth recovery passed 34/34 desktop/mobile cases. It uses the actual folder, per-session queue and failed-run keys, checks same-account reload, second-account cleanup, sign-out, and a held peer-tab authentication recheck. A mounted BFF/client proof and unit tests cover distinct same-format image turns recorded by another client in the correct order without losing the local preview. Planted cleanup, quarantine, and image reconciliation regressions made their tests fail before repair.
  • A built Studio served by its BFF passed browser checks under the actual CSP with zero page/CSP errors: denied microphone permission produced an accessible typed fallback without a simulated speech callback; granted permission started and stopped dictation with a fake input device while retaining the draft; inline PNG previews loaded; Send stayed reachable without page overflow at 320/500/1280 px; arrival created no run; a confirmed prompt created a BFF session. The 20-second return notice passed with a simulated visibility transition. Chromium headed and headless kept background tabs visible, so native tab hiding was not observed; fake-clock component and Playwright transitions cover the timing and refresh rules.
  • CI passed on the exact pushed heads: Plan / Interface PR CI at 34e414c9011e0061b60eca62e4caf7df85900fc9, Implementation PR CI and the performance gate at 3d244e98cd8c317cdc2575ebe61c680d5e652141. The PR path classifier skipped repository-wide Go jobs for this Studio-scoped diff; the implementation run passed its Studio, documentation, and site checks, and the performance run passed its allocation gate and scenario comparisons.
  • A full CI dispatch on the same implementation commit passed 30 jobs, including Go race shards, analysis/lint, Studio, SDK, documentation, site, build, and offline smoke checks; only draft-only coverage was skipped as designed. Local go run ./cmd/mecademo passed and showed a tool call, permission ask/approval, and final result. Repair panel: Spec, Standards, Security, Architecture and Library reuse found no issues; Test adequacy has one advisory for native tab visibility, and Duplication has one advisory for a custom React row comparator. PANEL: ship_blockers=0 important=0 advisory=2 reviewer_failures=0.

Known limits

The existing stream marks scheduled origin-chat deliveries with canonical fenced text rather than authenticated event provenance. Byte-identical user text can receive the same inert note presentation; the plan records this and no stronger claim is made. The local mock does not advertise image sending or scheduling, so the built-browser image capability was enabled only for preview inspection; focused BFF/component tests cover sending and recorded note behavior. PDF/text-file sending, remote image embeds, persisted away markers, and cross-reload return notices are outside this contract.

@peppescg

Copy link
Copy Markdown
Contributor

Implementation review: #1886

CI is green. All 17 ACs are implemented. I found no XSS and no auto-send path:

  • There is no raw HTML rendering.
  • urlTransform only lets data:image/ and blob: through for images.
  • ?prompt=&send=1 only opens a confirm modal, and submit refuses unless the seed has been confirmed.
  • Delivery-note parsing is anchored and inert.
  • Account storage handles storage failures correctly.

Needs changes

  1. Contract amendment in an implementation PR. This PR changes the text and verify: line of the already-landed studio-bootstrap.md AC4.6 (first sight now clears unmarked data). It also flips docs(plan): define Studio chat transcript and composer #1880's status to landed. The AC4.6 amendment should live in the Plan/Interface PR (docs(plan): define Studio chat transcript and composer #1880); see my comment there.
  2. AC7.2: the reply to a delivery never appears in an open chat. use-chat-messages.ts merges saved transcript rows into the open chat only through mergeRecordedDeliveryMessages, which takes only messages carrying a delivery key. A scheduled delivery starts a real run, so an idle open chat shows "Scheduled task X completed" with no assistant reply until the user switches chats. Turns written from another device are hidden the same way.
  3. Verify lines that don't prove their AC.
    • AC5.1: the named test drives applyRunDelivery (run-stream.test.ts:93). Only the side-thread panel uses that reducer; the main chat's consumeRun doesn't. The real proofs are chat-workspace.integration.test.tsx:620 and chat-workspace.transcript.integration.test.tsx:218.
    • AC4.1 / AC4.2: the named tests (chat-workspace.test.tsx:16,45) cover only pure helpers. The mounted coverage is at chat-workspace.integration.test.tsx:487/539/580.

Minor

  • jsdom still in use: it was removed from apps/web/package.json, but option-field.test.tsx, chat-folders.test.ts and use-chat-messages.test.tsx still declare @vitest-environment jsdom. They only pass because the lockfile still carries jsdom as an optional vitest peer.
  • Legacy titles freeze (session-title.ts:19): a cached revision "0" rejects every later "0" candidate, and titledSessionItems prefers the cache over fresh inventory. A rename on legacy metadata never shows. Suggest not caching revision 0.
  • Return-notice timing: resume() takes Date.now() after the refetches finish, so a 17 s hide plus a 3 s refresh already shows the notice. The "Mecatl is offline." notice also looks unreachable, because the notice needs the refetches to succeed.
  • Weak verify targets:
    • AC2.1's width loop only sets innerWidth; happy-dom runs no layout or media queries.
    • AC2.3's test covers only the URL parser; no mounted test shows zero POSTs on arrival.
    • AC3.2 simulates denial with a synchronous throw instead of onerror({error:"not-allowed"}).
  • Nits:
    • Delivery notes are labelled "You" (chat-transcript.tsx:84).
    • The lockfile has unrelated @types/node drift.

samuv and others added 17 commits September 25, 2026 14:24
Co-Authored-By: Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Add confirmed arrival seeds, IME-safe send handling, responsive controls, bounded image previews, and speech recognition fallbacks with focused component proof.

Co-Authored-By: Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Validate each image and the prompt aggregate from decoded base64 byte counts before the BFF invokes the SDK. Pin exact 10 MiB, 20 MiB, and 16-image route boundaries, including rejection without SDK access.

Signed-off-by: Samuele Verzi <samu@stacklok.com>

Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Hydrate cached transcripts after session reset, keep folder edits in memory when browser writes fail, and ignore stale return refreshes after visibility flips.

Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Drive the real generated BFF client through mounted workspace flows for send, queue, steer, retry, activity reattachment, bounded replay, gaps, and abrupt closes. Snapshot replayed prompt text before React applies batched row updates.

Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
samuv and others added 20 commits September 25, 2026 14:24
Use account-scoped reads and writes for profile, model, panel, canvas, and thread state. Enumerate thread keys through the quarantined view and ignore raw folder storage event payloads.

Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Adapt the account and mounted chat proofs after rebasing onto the merged auth recovery flow. The chat fixture now models a verified request state and provider context.

Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Keep profile scale behind the account reconciliation boundary and reset it before the next account workspace renders. The mounted account-switch proof plants Alice scale and verifies Bob sees the default after partial storage cleanup.

Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Assert that a model fork selects its returned BFF chat and that the remaining same-account queue hydrates after remount.

Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Describe the device theme as retained and account preferences as cleared on sign-out or account switch.

Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Use the declared happy-dom environment in affected component tests and remove unrelated Node type lockfile churn.

Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Refresh an idle visible transcript after inventory changes and on a bounded safety poll. Merge saved turns in order while retaining matching live detail and unique render anchors, with mounted and offline browser proof.

Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Recheck the shared account marker when reading, listing, or writing browser
account data, including session-backed failed-run drafts. Keep storage-failure
drafts in memory without touching another account's marked store.

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Keep legacy title revision zero out of the title cache, measure away time at visibility return, and allow a verified offline notice after failed chat refreshes. Show the target, model, mode, and draft tool access before sending a URL seed; keep large seeds in the original tab during interactive sign-in.

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Capture the hidden interval before asynchronous refetches so events received after return cannot be attributed to time away. Mount the event-to-notice seam and mutation-check result and approval timing.

Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Match saved image prompts by occurrence and MIME type so transcript-assigned names preserve the richer live row. Prove exactly-once prompts, images, and tools through mounted replay and refetch tests.

Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Exercise offline and busy seed confirmation through the workspace BFF and verify rename response, streamed title events, and stale inventory in the header and folder.

Co-Authored-By: OpenAI Codex <codex@openai.com>
Signed-off-by: Samuele Verzi <samu@stacklok.com>
Detach stale tabs before session recheck, preserve marked peer data, and reconcile local and session storage by their markers.

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Match saved image rows by shared bytes or URL before consuming a live occurrence. Keep a preceding image from another client in recorded order while retaining the local attachment and its render details.

Signed-off-by: Samuele Verzi <samu@stacklok.com>

Co-Authored-By: OpenAI Codex <codex@openai.com>
Start speech recognition in the click gesture, then inspect microphone permission so browsers that omit a recognition error still leave Listening. Ignore late callbacks after stop or unmount while preserving the typed draft.

Co-Authored-By: OpenAI Codex <codex@openai.com>
Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>

Signed-off-by: Samuele Verzi <samu@stacklok.com>
@samuv

samuv commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in this stacked implementation PR (head 3d244e98cd8c317cdc2575ebe61c680d5e652141):

  • The authorized Studio bootstrap AC4.6 text and verify: amendment moved to Plan / Interface PR docs(plan): define Studio chat transcript and composer #1880; it is no longer in feat(studio): align chat transcript and composer #1886's diff. docs(plan): define Studio chat transcript and composer #1880 remains proposed. The landed status here is explicitly a candidate transition that becomes authoritative only if this implementation PR merges.
  • An idle open chat now refetches and reconciles its full saved transcript, including a scheduled delivery's assistant reply and tool content and ordinary turns from another client. use-chat-messages.ts uses reconcileRecordedMessages; matching retains richer live rows without duplicating repeated saved turns. Focused use-delivery-follow.test.ts and mounted workspace tests cover the note/reply and other-client cases, including distinct image turns.
  • The AC4.1, AC4.2, and AC5.1 verify: lines now point to mounted chat-workspace.integration.test.tsx cases that exercise actual BFF requests, queue/steer/cancel targets, and bounded replay. The earlier pure-helper and side-panel reducer targets no longer stand alone as those proofs.
  • The three remaining jsdom test directives were changed to happy-dom; legacy revision "0" titles are read from fresh inventory instead of cached, with a mounted rename proof. Return timing is frozen at visibility return before refetch, and a verified offline runtime can produce the offline notice even if inventory/detail fail. Delivery notes have a scheduled-task label. The unrelated @types/node lockfile drift was removed.
  • Browser proof checked the served CSP, usable Send at 320/500/1280 px, zero run POSTs on seed arrival, explicit confirmation, and a real denied microphone permission state with typed fallback. Async recognition denial also has a component test. Chromium did not emit a native hidden-tab transition in this environment; the timed return transition was simulated and remains a review advisory.

Strict trace passes 17/17 ACs. Implementation PR CI, the performance gate, and a full CI dispatch on the same commit passed. The final panel reports ship_blockers=0 important=0 advisory=2 reviewer_failures=0. This PR remains open for review; nothing has been merged.

@samuv
samuv merged commit 7e2718f into main Sep 25, 2026
33 checks passed
@samuv
samuv deleted the impl/studio-chat-transcript-composer branch September 25, 2026 12:37
@toolhive-release-app toolhive-release-app Bot mentioned this pull request Sep 25, 2026
2 tasks
jbeda added a commit that referenced this pull request Sep 25, 2026
…mit-coauthor-guidance

* origin/main: (32 commits)
  docs: plan optional native Kubernetes execution provider (#1579)
  feat(mecatui): restore session status metrics (#1891)
  docs(plan): bound agent inventory viewport (#1946)
  docs: include user-docs in matlatl and use local documentation links (#1934)
  feat(mecatui): bound sessions inventory (#1944)
  feat(studio): show session delegation activity (#1907)
  docs(plan): define Studio session activity (#1885)
  Release TypeScript SDK v0.4.0 (#1940)
  Release v0.0.41 (#1941)
  docs(mecatui): plan bounded sessions inventory (#1906)
  feat(studio): add exact plan control and authorization review prerequisite (#1909)
  test(app): eliminate real ToolHive probe deadline wait (#1937)
  docs(plan): define Studio chat approvals and exact plan control (#1888)
  test(sdk): enforce SDK/TUI high-level parity (#1471) (#1917)
  docs(plan): propose SDK high-level parity coverage (#1471) (#1916)
  docs: improve gRPC schema table readability (#1842)
  test(app): eliminate real waits in durable learning tests (#1933)
  feat(studio): align chat transcript and composer (#1886)
  docs(plan): define Studio chat transcript and composer (#1880)
  feat: correlate provider requests with the root session (#1883)
  ...

This branch was successfully deployed

1 active deployment
Preview — 86c67976 Deployed Sep 25, 2026 by vercel[bot]
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.

2 participants