feat: Voice Workspace canvas enrichment (Mermaid, images, panel history) + orb polish (#979) - #981
Merged
Merged
Conversation
… polish (#979) Add two in-process voice panel tools and frontend canvas enrichments to the voice workspace (VOICE-009), endpoint contract unchanged. Backend (services/gemini_voice.py): - show_diagram(diagram, title?) → mermaid panel type - show_image(src, title?, caption?) → image panel type - _classify_image_src(): in-process path confinement for show_image — accepts http(s) URLs and workspace-confined paths, rejects '..' traversal, absolute escapes, the /home/developer-evil sibling, data:, and non-http schemes (stricter than the agent-server prefix check) - register both in _PANEL_TOOL_NAMES + WORKSPACE_PANEL_INSTRUCTIONS Frontend (views/AgentWorkspace.vue): - mermaid: strict opaque-origin sandboxed iframe via self-contained mermaid.min.js IIFE bundle (no chunk fetches); diagram text injected as a JS string (JSON.stringify + '<'→<, no </script> breakout); securityLevel:'strict'; invalid syntax → contained error + source - image: web URLs via Vue :src; workspace paths fetched as authenticated blob via /files/preview (getFilePreviewBlob); objectURLs cached + revoked on eviction/unmount; out-of-order load guard - client-side panel history: 40-snapshot ring buffer + prev/next + dropdown - orb polish: asymmetric attack/release energy smoothing, smoothed core size, idle breathe floor, larger core/glow - cross-fade canvas transition honoring prefers-reduced-motion - iframes refactored to reactive :srcdoc bindings (transition-safe) - mermaid added to package.json Tests (tests/unit/test_voice_tools.py): show_diagram/show_image state, path-confinement rejection (traversal/sibling/data:/scheme), classifier unit tests, tool registration + declaration. Full unit suite green (1811 passed). Panel tools are backend+frontend only (not on MCP — Invariant #13 N/A). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The design-token linter (check:tokens) rejected `text-status-error-400` — `error` is not a registered status-* token. The failure/error color in the design system is `status-danger` (aliased to red). Swap the class so the build's "Verify design tokens" step passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
show_diagram(Mermaid) andshow_image— to the Voice Workspace canvas, plus client-side panel history, orb polish, and a graceful canvas transition. Endpoint contract unchanged (still the 300ms poll ofGET /voice/{session_id}/panel).mermaid.min.jsIIFE bundle that exposeswindow.mermaidwith zero runtime chunk fetches — so the Chart.js?url-into-iframe pattern transfers cleanly.:src; agent-workspace file paths stream through the existing authenticated/files/previewendpoint as a blob (a bare<img src>would 401).Changes
src/backend/services/gemini_voice.py—show_diagram+show_imagetool declarations, registered in_PANEL_TOOL_NAMESandWORKSPACE_PANEL_INSTRUCTIONS;_classify_image_src()path-confinement helper (rejects.., absolute escapes, the/home/developer-evilsibling,data:, non-http schemes — stricter than the agent-server prefix check).src/frontend/src/views/AgentWorkspace.vue—mermaid/imagerender branches; reactive:srcdociframe bindings (transition-safe); 40-snapshot history ring buffer (prev/next + dropdown, live/pinned); orb asymmetric attack/release smoothing + idle breathe + larger core/glow;prefers-reduced-motion-aware cross-fade + header "updated" flash; image objectURL cache with eviction/unmount revocation + out-of-order load guard.src/frontend/package.json— addmermaid(v11).tests/unit/test_voice_tools.py— new panel-tool state tests, path-confinement rejection (traversal/sibling/data:/scheme),_classify_image_srcunit tests, tool registration/declaration.docs/memory/requirements.md(VOICE-009) +docs/memory/feature-flows/voice-chat.md.Security
sandbox="allow-scripts"iframe; diagram text injected as a JS string (JSON.stringify+<→<, no</script>breakout) with mermaidsecurityLevel:'strict'.:src(nov-html); url-kind restricted tohttp(s)://.Test Plan
pytest tests/unit/test_voice_tools.py(58 passed)npm run build); mermaid emitted as a hashed same-origin assetshow_diagramrenders a flowchart; invalid syntax shows a contained error;show_imagewith a web URL and a workspace path; history prev/next; orb smoothing; reduced-motionFixes #979
🤖 Generated with Claude Code