Allow agent to control light/dark color theme, and display screenshots in the chat - #3982
Conversation
There was a problem hiding this comment.
Pull request overview
Adds first-class screenshot artifacts to Studio’s agent conversations and expands screenshot/inspection tools to support explicit light/dark capture, improving theme-aware visual verification in the Agentic UI and CLI runtime.
Changes:
- Render
studio.chat_artifactmedia widgets (local screenshots) inline in the classic conversation transcript UI, with a loading/unavailable state. - Extend
take_screenshot,share_screenshot, andinspect_designtools withcolorSchemesupport (light/dark, plusallfortake_screenshot). - Suppress raw
mediaWidgetPayload*=marker lines from the conversation’s expanded tool-details UI while still emitting artifacts via chat events.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| apps/ui/src/ui-classic/components/session-view/conversation/style.module.css | Adds layout + skeleton/unavailable styles for inline media artifacts. |
| apps/ui/src/ui-classic/components/session-view/conversation/index.tsx | Renders chat artifact images inline; hides screenshot payload markers in tool output. |
| apps/ui/src/ui-classic/components/session-view/conversation/index.test.ts | Adds coverage for payload-line hiding and inline local screenshot rendering. |
| apps/cli/ai/tools/take-screenshot.ts | Adds colorScheme + multi-capture labeling and returns studioArtifacts for artifact emission. |
| apps/cli/ai/tools/share-screenshot.ts | Adds colorScheme option for prefers-color-scheme sharing captures. |
| apps/cli/ai/tools/screenshot-helpers.ts | Threads colorScheme into Playwright emulateMedia and temp filename suffixing. |
| apps/cli/ai/tools/inspect-design.ts | Adds colorScheme emulation for rendered DOM inspection. |
| apps/cli/ai/tools/index.ts | Exports withChatArtifactEmission for reuse and ensures tool execution can emit artifacts. |
| apps/cli/ai/tests/tools.test.ts | Adds/extends tests for colorScheme, multi-scheme capture, and artifact emission behavior. |
| apps/cli/ai/runtimes/pi/index.ts | Wraps remote-site screenshot tool to emit chat artifacts when enabled. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
📊 Performance Test ResultsComparing c812fcb vs trunk app-size
site-editor
site-startup
Results are median values from multiple test runs. Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff) |
| } ); | ||
| } | ||
|
|
||
| function stripScreenshotMediaPayloadLines( text: string ): string { |
There was a problem hiding this comment.
stripScreenshotMediaPayloadLines is duplicated in apps/studio/src/components/studio-code-session/conversation/index.tsx:68, and the mediaWidgetPayload=/mediaWidgetPayloads= prefixes are hardcoded here, there, and in the producer (apps/cli/ai/tools/take-screenshot.ts:146). If the CLI renames the prefix, both strippers silently break. Lift the prefixes + helper into tools/common? Non-blocking.
| return optionLabels.has( answer ) ? answer : undefined; | ||
| } | ||
|
|
||
| function isRecord( value: unknown ): value is Record< string, unknown > { |
There was a problem hiding this comment.
Dupes the unexported isRecord in tools/common/ai/chat-artifacts.ts:38 — export that one and import it instead?
Its taking the screenshot in light and dark mode, but since your site doesn't change anything, it looks weird. |
Hmmmm, maybe not. Looks like it might be showing them twice for some reason. Looking at a fix now. |
9757d73 to
f54ed57
Compare
…io tools Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
f54ed57 to
df716ff
Compare
…o/common Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…urally Removes the mediaWidgetPayload= lines from take_screenshot output and replaces the screenshot-local-media presentation rule, which told the model to re-present the auto-emitted screenshot via studio_present and produced duplicate images in the conversation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Screenshot artifacts persisted into session transcripts pointed at mkdtemp directories that macOS purges within days, leaving permanent 'Image unavailable' boxes. Captures now live in <session>.screenshots/ next to the session JSONL (with unique filenames) and are deleted with the session; standalone CLI/MCP runs still fall back to a temp directory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Validates persisted chat-artifact entries before rendering, caches local screenshot reads through react-query and object URLs instead of base64 data URLs, hides local-only artifacts on connectors that cannot read local files, and moves legacy payload-line stripping into the memoized render-item pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tion The legacy view hid the payload line but had no way to see the capture at all. Chat artifact entries now render as inline images (read over IPC with a per-path cache), using frame color tokens for light/dark support. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
blob: URLs are not allowed by the desktop renderer CSP (img-src permits data:), and revoke-on-cleanup broke the image when the artifact row remounted after a run finished. Data URLs need no lifecycle; conversion happens once inside the cache layer so files are still read over IPC only once per path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
👋 Update on the review feedback — a batch of fixes just landed on this branch (rebased onto trunk first). Here's the tour: The double screenshot @bcotrim spotted turned out not to be two captures at all — it was one capture presented twice. The tool now auto-emits its artifact, but the old Which also resolves the two duplication comments: with the producer no longer writing marker lines, the strippers only exist for old transcripts — there's now a single On Copilot's data-URL performance note: the conversion now happens once inside a cache layer — react-query in the classic UI, a per-path map in the legacy view — so each file crosses IPC and gets encoded a single time per app lifetime. (Fun detour: we tried A few more things surfaced while testing that seemed worth fixing in the same pass:
Typecheck is clean and the affected suites pass (~235 tests, including new coverage for the storage, delete sweep, and malformed-entry cases). Still worth a human eyeball on the inline images in light + dark mode. Written by Claude (working with @shaunandrews), who also did most of the implementation in this batch. |
…nts, leaner tests
…res (#4097) ## Related issues - Follow-up to #3982 ## How AI was used in this PR Implemented and tested with Claude Code, based on a confirmed finding from a multi-agent review of #3982; human-reviewed before opening. ## Proposed Changes Since #3982, every `take_screenshot` capture is automatically shown to the user as an inline chat artifact. That is right for deliberate captures, but the agent also screenshots constantly for its own eyes — the visual-polish loop alone takes up to 5 verification passes per page, and a light/dark × desktop/mobile check produces 4 images per call. Iterative design sessions flood the conversation with near-identical screenshots the user never asked for, and each one is persisted in the session transcript. (The previous presentation rule's "Do not present every internal verification screenshot" guidance was lost in the #3982 rewrite, and the model had no mechanism to comply with it anyway.) This PR gives the agent that mechanism: an optional `display` input on `take_screenshot` (default `true`, so current behavior is unchanged). Passing `display: false` keeps the capture out of the chat while the model still receives the image for its own visual verification, and the presentation rule now tells the agent to use it while iterating so users only see deliberate milestone captures. ## Testing Instructions - Run `npm test -- apps/cli/ai/tests/tools.test.ts apps/cli/ai/tests/system-prompt.test.ts packages/common` - Run `npm run typecheck` - Reproduce the flood on trunk: in Studio Code, prompt *"Check the homepage for spacing and contrast issues in light and dark mode, on desktop and mobile. After every change you make, take screenshots again to verify. Keep iterating until it looks right."* — the conversation fills with near-identical screenshots. - On this branch, repeat the same prompt: intermediate verification captures should stay out of the chat, with only deliberate/milestone captures displayed. - Ask directly for a screenshot ("take a screenshot of my site") and confirm it still appears inline as before. ## Pre-merge Checklist - [x] Have you checked for TypeScript, React or other console errors? 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reconciles the July 6–10 trunk sync: adopts trunk's productionized screenshot/theme tooling (#3982/#4097), grafts the copy-button (#4113), edit-message (#4094), and inline chat artifacts into the redesigned conversation views, keeps the merged Connector surface over trunk's menu-action wiring (#4085), and keeps the blueprint-selector deletion.


Proposed Changes
mediaWidgetPayload=text lines are gone from tool output (a shared stripper keeps old transcripts clean), and thestudio_presentscreenshot rule was replaced so the agent no longer re-presents the same capture, which caused the duplicate image reported in review.<session>.screenshots/directory next to the session file instead of the OS temp dir, so inline images keep working in older transcripts; deleting a session removes its screenshots.take_screenshotwas wrapped, so e.g.site_createnever emitted its site-preview artifact).studio ui) can't read local files, so local-only screenshot artifacts are hidden there via a newreadLocalMediaconnector capability instead of rendering guaranteed "Image unavailable" boxes.Screenshots
Testing Instructions
npm test -- apps/cli/ai/tests packages/common/ai apps/ui/src/ui-classic/components/session-view/conversation apps/studio/src/components/studio-code-session/conversationnpm run typecheck~/Library/Application Support/Studio/sessions/<date>/<session>.screenshots/and still renders after restarting the app and reopening the session.colorScheme: "all"and confirm both artifacts are produced, labeled(desktop light)/(desktop dark)..screenshots/directory is removed.mediaWidgetPayload=lines and confirm they stay hidden in the expanded tool details.