Repository navigation
feat(live-voice): dock the call bar as a global desktop widget - #1271
Merged
Merged
Conversation
The compact call bar was AppShell chrome, so it disappeared whenever the app was not the foreground window. It is now its own frameless, transparent, always-on-top window that floats above other applications and can be dragged anywhere on the desktop. - main owns the widget window: placement is remembered and clamped to the work area, the window is sized to the box the bar reports, and the status row plus its controls stay on one row - the widget is a view, never an owner: every press is forwarded to the main window, which keeps the microphone, media and call-scoped work, and a failure only that frame can observe is reported back so the bar names it in place - the main window draws no call bar and keeps the details surface, opened by the widget's Details action - the preparation panel carries no explanatory prose; only a blocking cause is stated - ipc: widget/visibility, widget/action, widget/issue, event/widgetState, event/widgetAction; ADR 0314 plus EN/zh spec updates and e2e coverage
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Widget action forwarding loses playback user activation, and initial widget state delivery has a subscription race.
Review effort: Balanced
Findings: 2
Open (4)
What changed in this PR
Moves Live Voice call controls into a persistent, draggable desktop widget while retaining call ownership in the main renderer.
Changes:
- Adds the widget window, IPC bridge, geometry persistence, and renderer UI.
- Updates interaction/E2E coverage and localized preparation copy.
- Documents the architecture and user experience in specs and ADR 0314.
| File | Description |
|---|---|
scripts/e2e/live-voice-desktop.mjs |
Supports separate main/widget CDP targets. |
scripts/e2e-live-voice.mjs |
Drives call controls through the widget. |
packages/shared/src/types/live-voice.ts |
Defines widget actions. |
packages/shared/src/protocol.ts |
Adds widget IPC channels. |
packages/i18n/test/catalogs.test.mjs |
Updates catalog expectations. |
packages/i18n/src/locales/zh-TW/index.ts |
Removes obsolete preparation copy. |
packages/i18n/src/locales/zh-CN/index.ts |
Removes obsolete preparation copy. |
packages/i18n/src/locales/tr/index.ts |
Removes obsolete preparation copy. |
packages/i18n/src/locales/pt-BR/index.ts |
Removes obsolete preparation copy. |
packages/i18n/src/locales/ko/index.ts |
Removes obsolete preparation copy. |
packages/i18n/src/locales/fr/index.ts |
Removes obsolete preparation copy. |
packages/i18n/src/locales/es/index.ts |
Removes obsolete preparation copy. |
packages/i18n/src/locales/en/index.ts |
Removes obsolete preparation copy. |
packages/i18n/src/locales/de/index.ts |
Removes obsolete preparation copy. |
docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md |
Documents widget E2E coverage. |
docs/zh-CN/spec/04-ux/08-component-spec.md |
Specifies widget UX. |
docs/zh-CN/spec/03-runtime/live-voice.md |
Documents widget ownership. |
docs/zh-CN/spec/03-runtime/01-ipc-protocol.md |
Documents widget IPC. |
docs/spec/06-delivery/04-e2e-test-plan.md |
Documents widget E2E coverage. |
docs/spec/04-ux/08-component-spec.md |
Specifies widget UX. |
docs/spec/03-runtime/live-voice.md |
Documents widget ownership. |
docs/spec/03-runtime/01-ipc-protocol.md |
Documents widget IPC. |
docs/adr/README.md |
Registers ADR 0314. |
docs/adr/0314-live-voice-docked-widget.md |
Records the widget architecture. |
apps/desktop/test/live-voice-widget.test.mjs |
Tests geometry and payload parsing. |
apps/desktop/test/live-voice-controls.test.mjs |
Updates call-bar presentation tests. |
apps/desktop/test/fixtures/live-voice-interaction.jsx |
Simulates widget IPC interactions. |
apps/desktop/test/fixtures/live-voice-interaction-runner.cjs |
Exercises widget behavior. |
apps/desktop/src/styles/voice.css |
Repositions the details anchor. |
apps/desktop/src/styles/live-voice-widget.css |
Styles the desktop widget. |
apps/desktop/src/main.tsx |
Dispatches the widget renderer surface. |
apps/desktop/src/features/voice/live/LiveVoiceWidget.tsx |
Implements widget rendering and actions. |
apps/desktop/src/features/voice/live/LiveVoiceStatusHost.tsx |
Executes forwarded actions in the owner. |
apps/desktop/src/features/voice/live/LiveVoicePreparation.tsx |
Simplifies preparation copy. |
apps/desktop/src/features/voice/live/LiveVoiceDetails.tsx |
Generalizes the details anchor type. |
apps/desktop/src/features/voice/live/LiveVoiceCallBar.tsx |
Makes failures inline widget chrome. |
apps/desktop/src/features/voice/live/live-voice-presentation.ts |
Derives widget snapshots and visibility. |
apps/desktop/src/features/voice/live/live-voice-api.ts |
Exposes widget IPC helpers. |
apps/desktop/electron/main/live-voice/widget-window.ts |
Manages the native widget window. |
apps/desktop/electron/main/live-voice/widget-geometry.ts |
Clamps widget size and placement. |
apps/desktop/electron/main/live-voice/runtime.ts |
Publishes call views to the widget. |
apps/desktop/electron/main/ipc/register.ts |
Wires widget IPC dependencies. |
apps/desktop/electron/main/ipc/live-voice-ipc.ts |
Validates widget IPC requests. |
apps/desktop/electron/main/index.ts |
Creates and disposes the widget. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+186
to
+188
| created.webContents.on("did-finish-load", () => { | ||
| loaded = true; | ||
| sendView(); |
| if (!main.isVisible()) main.show(); | ||
| main.focus(); | ||
| } | ||
| main.webContents.send(IPC.event.liveVoiceWidgetAction, { action }); |
Comment on lines
+271
to
+272
| await check("failed playback retry is visible without opening details", | ||
| `s.snapshot.errorCode === 'LIVE_PLAYBACK_FAILED' && document.body.textContent.includes('LIVE_PLAYBACK_FAILED') && ui.all('[role=status]').length > 0 && ${button("Resume sound")} && ${noDialog} && s.counts.play >= 2`); |
| "an unmeasurable box falls back to the default"); | ||
| }); | ||
|
|
||
| test("only the widget's own actions and box are accepted on its channels", async (t) => { |
The compact call bar was AppShell chrome, so it disappeared whenever the app was not the foreground window. It is now its own frameless, transparent, always-on-top window that floats above other applications and can be dragged anywhere on the desktop. - main owns the widget window: placement is remembered and clamped to the work area, the window is sized to the box the bar reports, and the status row plus its controls stay on one row - the widget is a view, never an owner: every press is forwarded to the main window, which keeps the microphone, media and call-scoped work, and a failure only that frame can observe is reported back so the bar names it in place - the main window draws no call bar and keeps the details surface, opened by the widget's Details action - the preparation panel carries no explanatory prose; only a blocking cause is stated - ipc: widget/visibility, widget/action, widget/issue, event/widgetState, event/widgetAction; ADR 0314 plus EN/zh spec updates and e2e coverage
Replay the widget snapshot after renderer subscription and hand playback recovery to an owner-frame gesture so the widget cannot lose initial state or user activation. Add sender authorization coverage and exercise the owner-frame retry path without duplicating assertions.
…to fix/pr-1271-review
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.


What
The Live Voice compact call bar becomes a docked desktop widget: its own frameless, transparent, always-on-top window that floats above other applications and can be dragged anywhere on the desktop. The main window no longer draws the bar.
live-voice-widget.jsonand clamped to the work area; the window is sized to the box the bar itself reports; the status row and its controls stay on one row (a folded status line would read as two rows of chrome, and a wrapped line would shrink the window into wrapping again); it joins every Space and never transforms the process type (ADR 0086).LIVE_*code.settings.languageitself when it becomes visible.voice/live/widget/visibility,voice/live/widget/action,voice/live/widget/issue,voice/live/event/widgetState,voice/live/event/widgetAction. The widget's channels are validated as that window and never enter owner derivation; every owner-validated channel still refuses another window withPERMISSION_DENIED.Verification
apps/desktoptypecheck, biome, style-token guard,check-architecture,check-agent-policy-sync,docs:check(83 EN/zh pairs, 542 pages).apps/desktop3323/3323 ·packages/shared1156/1156 ·packages/i18n28/28.pnpm test:e2e:live-voice: 5/5 journeys, run repeatedly against the real app, driving both CDP targets (main window and widget window).origin/main(7e775e6c9).Notes for review