diff --git a/.papercuts/troubleshooting.md b/.papercuts/troubleshooting.md index a037eb5d..10b7db28 100644 --- a/.papercuts/troubleshooting.md +++ b/.papercuts/troubleshooting.md @@ -311,3 +311,12 @@ owns; reopen the terminal before judging the final live state. platform-independent test that exercises both certificate and keychain paths. - A changelog search conflated the stable and prerelease lines. Verify published package code before assuming a release contains the upstream patch. + +## 2026-09-04: queued composer controls +- This detached worktree had no .memory directory. Read the canonical checkout's PROJECT-CONTEXT.md and PLANNED.md before implementation; wrote scoped memory here. +- Pi harness queueSteer/queueFollowUp are not exposed through the foreground durable transcript path. Use Stop → persistence barrier → normal append for the desktop Steer action and explain its behavior in the control tooltip. +- E2E TypeScript uses an older lib target; use reverse/find rather than Array.at in new test helpers. +- The selected local Xcode began rejecting compiler/git calls for unaccepted license terms during this task. Used the installed Command Line Tools for git and rebuilt the renderer/Electron bundles with existing native helpers; native sources are unchanged and CI checks the full build. +- Appearance persistence uses the `settings` envelope in settings.json; E2E disk assertions must read `settings.appearance`, then verify the preference after an app relaunch. +- A CI navigation test raced the provider transport: queue removal confirms durable append, but does not mean the mock HTTP server has received the next generation request. Wait for that specific request before asserting its conversation history. +- The shared button radius overrides legacy per-corner utility classes. Joined actions need an explicit shared group rule: square inner seams, the common outer squircle radius, and visible overflow for focus outlines. Check hover fills and each seam's corner radius, not only the uniform outer radius. diff --git a/AGENTS.md b/AGENTS.md index 4c7e5d05..0421a538 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,8 @@ The current plan inventory and status live in [`docs/plans/README.md`](docs/plan ## UI design references +Follow `docs/design-guide.md` for reusable action shapes, semantic tokens, and accessibility. Reuse the shared squircle button treatment for new and existing actions rather than introducing per-screen button geometry. + Before adding or materially restyling any UI element or component, always review both `docs/chatgpt-desktop-ui-inspiration.md` and `docs/chatgpt-ui-element-specimen.html` for interaction, styling, state, motion, and accessibility inspiration. Adapt the references to Aiden's existing visual language rather than copying them blindly, and use the semantic design tokens in `renderer/styles.css` and `renderer/shared/appearance.ts` instead of introducing one-off colors. Do not put decorative borders or outlines around radio-button choice cards. Communicate selection with the radio control and existing background-state tokens instead. Always preserve visible keyboard `focus-visible` rings or outlines for accessibility. diff --git a/docs/chatgpt-desktop-ui-inspiration.md b/docs/chatgpt-desktop-ui-inspiration.md index 7e278e48..421a3c4e 100644 --- a/docs/chatgpt-desktop-ui-inspiration.md +++ b/docs/chatgpt-desktop-ui-inspiration.md @@ -4,6 +4,8 @@ Research date: 2026-07-18 ## Scope and confidence +For current reusable Aiden components and shape rules, follow the [design guide](design-guide.md). The observations below provide historical interaction inspiration. + This audit covers the installed `/Applications/ChatGPT.app`, build `26.715.31925` (`com.openai.codex`). Although the app is displayed as ChatGPT, its package and renderer identify it as the current ChatGPT-branded Codex desktop product. The findings therefore mix classic Chat, Codex agent, project, review, browser, and terminal flows. Evidence used: diff --git a/docs/chatgpt-ui-element-specimen.html b/docs/chatgpt-ui-element-specimen.html index 4892f7db..0e5f08bc 100644 --- a/docs/chatgpt-ui-element-specimen.html +++ b/docs/chatgpt-ui-element-specimen.html @@ -30,8 +30,9 @@ --success: #087c58; --warning: #9a6700; --radius-control: 10px; + --radius-button: 16px; --radius-popover: 12px; - --radius-composer: 20px; + --radius-composer: 40px; --radius-dialog: 16px; --shadow-hairline: 0 0 0 0.5px rgb(0 0 0 / 0.1); --shadow-control: 0 1px 2px -1px rgb(0 0 0 / 0.08); @@ -45,7 +46,7 @@ --duration-relaxed: 300ms; --ease-enter: cubic-bezier(0.19, 1, 0.22, 1); --ease-snappy: cubic-bezier(0.23, 1, 0.32, 1); - --focus: 0 0 0 3px rgb(11 125 229 / 0.24); + --focus: 0 0 0 2px rgb(24 24 23 / 0.55); } html[data-theme="dark"] { @@ -78,7 +79,7 @@ --shadow-composer: var(--shadow-hairline), 0 3px 12px rgb(0 0 0 / 0.46); --shadow-toast: var(--shadow-hairline), 0 5px 16px rgb(0 0 0 / 0.5); --shadow-dialog: var(--shadow-hairline), 0 24px 48px -12px rgb(0 0 0 / 0.72); - --focus: 0 0 0 3px rgb(75 156 242 / 0.3); + --focus: 0 0 0 2px rgb(245 245 243 / 0.55); } * { box-sizing: border-box; } @@ -86,6 +87,18 @@ body { margin: 0; color: var(--text); background: var(--bg); font-size: 14px; line-height: 1.45; } button, input, textarea { font: inherit; } button { color: inherit; } + :root :is(button, [role="button"], .button):where(:not([role="switch"]):not([role="radio"]):not([role="checkbox"])) { + border-radius: var(--radius-button); + corner-shape: squircle; + } + :root :is(button, [role="button"], .button):focus-visible { + outline: 2px solid var(--text-secondary); + outline-offset: 2px; + } + .squircle-action-group { display: inline-flex; border-radius: var(--radius-button); corner-shape: squircle; background: var(--control); } + :root .squircle-action-group > .button { border-radius: 0; } + :root .squircle-action-group > .button:first-child { border-start-start-radius: var(--radius-button); border-end-start-radius: var(--radius-button); } + :root .squircle-action-group > .button:last-child { border-start-end-radius: var(--radius-button); border-end-end-radius: var(--radius-button); } code { font-family: "SFMono-Regular", "SF Mono", Menlo, monospace; font-size: 12px; } .topbar { @@ -232,7 +245,7 @@ .approval-actions { display: flex; justify-content: flex-end; gap: 7px; margin-top: 12px; } .composer-wrap { position: absolute; right: 18px; bottom: 16px; left: 18px; } .context-strip { display: flex; width: fit-content; max-width: calc(100% - 20px); min-height: 28px; align-items: center; gap: 6px; margin: 0 10px -3px; padding: 4px 9px 7px; border-radius: 11px 11px 0 0; background: var(--control); color: var(--text-secondary); font-size: 11px; } - .composer { padding: 10px; border-radius: var(--radius-composer); background: var(--surface-raised); box-shadow: var(--shadow-composer); } + .composer { padding: 10px; border-radius: var(--radius-composer); corner-shape: squircle; background: var(--surface-raised); box-shadow: var(--shadow-composer); } .composer textarea { width: 100%; min-height: 58px; padding: 4px 5px; border: 0; outline: 0; resize: none; background: transparent; color: var(--text); } .composer textarea::placeholder { color: var(--text-tertiary); opacity: 1; } .composer-footer { display: flex; align-items: center; justify-content: space-between; gap: 8px; } @@ -439,7 +452,7 @@

Controls that feel quiet until you touch them.

Button vocabulary

-

Neutral buttons gain only a small surface and shadow change. Primary actions carry the accent. Ghost buttons stay flat. Pressed states move inward through an inset shadow rather than bouncing or shifting layout.

+

All actions reuse the same soft squircle shape, including icon buttons. Use the shared Button and 16px button radius; the composer uses a broader 40px radius. Neutral buttons gain a small surface and shadow change. Primary actions carry the accent, ghost buttons stay flat, and keyboard focus remains visible. See the design guide for reuse rules.

@@ -448,6 +461,10 @@

Button vocabulary

+
+ + +
diff --git a/docs/design-guide.md b/docs/design-guide.md new file mode 100644 index 00000000..feefcced --- /dev/null +++ b/docs/design-guide.md @@ -0,0 +1,54 @@ +# Aiden design guide + +## Reusable action shapes + +Use the shared `Button` from `renderer/components/ui.tsx` for actions, including +icon-only controls and links rendered with `asChild`. Labeled and icon-only +buttons use the same soft squircle shape. Choose the existing size and variant +for emphasis; do not add a separate pill or circular button variant. + +| Surface | Reuse | Geometry | +| --- | --- | --- | +| Buttons, toolbar actions, navigation actions, and button links | `Button`, or the shared action rule in `renderer/styles.css` for existing native buttons | `--radius-button: 16px` and `corner-shape: squircle` | +| A custom visual that must follow a button's silhouette, such as its artwork mask | `.squircle-control` | The same button radius and corner shape | +| Joined actions, such as an action with a separate menu trigger | `.squircle-control.squircle-action-group` around direct `Button` children | 16px outer corners, square internal seams | +| Chat composer | `.composer-shell` | `--radius-composer: 40px` and `corner-shape: squircle` | + +The renderer's action rule also covers existing `button`, `[role="button"]`, and +`[data-slot="button"]` elements, including the compact dictation window. Reuse +that rule instead of adding per-screen geometry. Radio indicators, checkboxes, +and switch tracks retain their recognizable selection shapes. Text fields keep +their existing field geometry and focus treatment. + +Use native CSS geometry in the Electron renderer. Keep shadows on the shaped +element and preserve visible overflow for menus, badges, and focus rings. Apply +clipping only to an inner artwork mask that needs it, never to the whole action +or composer. No JavaScript path generation or new dependency is needed. + +Joined actions reuse the group rule instead of per-corner utility overrides. +Their individual hover and focus fills meet at straight seams, while the group +and its end buttons share the outer squircle. Leave the group overflow visible +so each button's keyboard outline and portaled menus remain unobstructed. + +## Color, state, and accessibility + +Reuse semantic surface, text, accent, status, and elevation tokens from +`renderer/styles.css` and `renderer/shared/appearance.ts`. Shape does not change +an action's meaning: primary actions retain their accent, destructive actions +retain their semantic treatment, and transparent actions gain a soft fill on +hover. Avoid decorative borders and outlines. + +Preserve neutral `focus-visible` outlines on non-text controls, accessible labels +for icon actions, disabled semantics, and existing hit-target sizes. Text-entry +focus uses fill and caret changes without a new border or ring. Reuse existing +hover and press feedback and respect Reduce Motion; do not animate the corner +shape or add layout shifts. + +## Verification + +Check shared and custom actions in onboarding, the chat toolbar and composer, +queued-message dialogs, Settings, and compact windows. Exercise hover, keyboard +focus, disabled states, light and dark themes, narrow layouts, and overflow +menus. The interactive [element specimen](chatgpt-ui-element-specimen.html) +demonstrates the same geometry. Extend the button appearance contract and +Electron interaction tests when these rules change. diff --git a/package.json b/package.json index 91758a29..bc6eb99a 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "test:aiden-remote-speech": "tsx --test main/services/aiden-remote-speech.test.ts", "pretest": "npm run build:worktree-remover && npm run test:aiden-remote-speech && npm run test:aiden-remote && npm run test:aiden-service-boundary && npm run test:memory-policy && npm run test:ios-release && npm run test:terminal:coverage && npm run test:onboarding && npm run test:assistant-automations && npm run test:slash-commands && npm run test:display-image && npm run test:ask-user-question && npm run test:todo && npm run test:btw && npm run test:advisor && npm run test:generative-ui && npm run test:provider-failure && npm run test:web-search && npm run test:compaction && npm run test:subagents && tsx --test main/services/pi-remote-catalog.test.ts main/services/provider-model-info-core.test.ts main/services/aiden-remote-models.test.ts renderer/shared/provider-thinking.test.ts && npm run test:bots && npm run test:voice && npm run test:sidebar", "pretest:coverage": "npm run build:worktree-remover && npm run build:subagent-run-store && npm run test:preflight && npm run test:scheduled && npm run test:memory-policy && npm run test:google-provider && npm run test:config-recovery && npm run test:command-system && npm run test:slash-commands && npm run test:display-image && npm run test:generative-ui && npm run test:compaction && npm run test:subagents && npm run test:bots:coverage", - "test:preflight": "npm run test:artificial-analysis && npm run test:model-pad && tsx --test main/services/appearance-preview-core.test.ts main/services/generation-timeline.test.ts main/services/local-runtime-status.test.ts main/services/mcp-tool-result.test.ts main/services/pi-thinking-disclosure.integration.test.ts renderer/components/activity-feed.test.tsx renderer/components/chat-sidebar.test.tsx renderer/components/composer.test.tsx renderer/components/settings/providers-settings.test.tsx renderer/main/chat-transition.test.tsx renderer/components/reasoning-block.test.tsx renderer/components/reasoning-visibility-control.test.tsx renderer/components/thinking-control.test.tsx renderer/lib/agent-steps.test.ts renderer/lib/button-appearance-contract.test.ts renderer/lib/dialog-motion-contract.test.ts renderer/lib/inline-metadata-hierarchy.test.ts renderer/lib/scrollbar-gutter-contract.test.ts renderer/lib/text-entry-focus-contract.test.ts renderer/lib/pill-appearance.test.ts renderer/lib/reasoning-disclosure.test.ts renderer/lib/streaming-motion-contract.test.ts renderer/lib/streaming-reveal.test.ts renderer/lib/voice-recorder-core.test.ts renderer/lib/media-recorder-stop.test.ts renderer/lib/dictation-vad.test.ts renderer/lib/dictation-sounds.test.ts renderer/pill-preload-channels.test.ts renderer/shared/anthropic-thinking.test.ts renderer/shared/app-update.test.ts renderer/shared/claim-check.test.ts renderer/shared/codex-thinking.test.ts renderer/shared/google-thinking.test.ts renderer/shared/provider-deployment.test.ts", + "test:preflight": "npm run test:artificial-analysis && npm run test:model-pad && tsx --test main/services/appearance-preview-core.test.ts main/services/generation-timeline.test.ts main/services/local-runtime-status.test.ts main/services/mcp-tool-result.test.ts main/services/pi-thinking-disclosure.integration.test.ts renderer/components/activity-feed.test.tsx renderer/components/chat-sidebar.test.tsx renderer/components/composer.test.tsx renderer/lib/chat-message-queue.test.ts renderer/components/settings/providers-settings.test.tsx renderer/main/chat-transition.test.tsx renderer/components/reasoning-block.test.tsx renderer/components/reasoning-visibility-control.test.tsx renderer/components/thinking-control.test.tsx renderer/lib/agent-steps.test.ts renderer/lib/button-appearance-contract.test.ts renderer/lib/dialog-motion-contract.test.ts renderer/lib/inline-metadata-hierarchy.test.ts renderer/lib/scrollbar-gutter-contract.test.ts renderer/lib/text-entry-focus-contract.test.ts renderer/lib/pill-appearance.test.ts renderer/lib/reasoning-disclosure.test.ts renderer/lib/streaming-motion-contract.test.ts renderer/lib/streaming-reveal.test.ts renderer/lib/voice-recorder-core.test.ts renderer/lib/media-recorder-stop.test.ts renderer/lib/dictation-vad.test.ts renderer/lib/dictation-sounds.test.ts renderer/pill-preload-channels.test.ts renderer/shared/anthropic-thinking.test.ts renderer/shared/app-update.test.ts renderer/shared/claim-check.test.ts renderer/shared/codex-thinking.test.ts renderer/shared/google-thinking.test.ts renderer/shared/provider-deployment.test.ts", "test:sidebar": "tsx --test renderer/components/chat-sidebar.test.tsx renderer/lib/sidebar-workspace-groups.test.ts renderer/lib/sidebar-chat-shortcuts.test.ts", "test:aiden-remote": "tsx --test main/handlers/aiden-remote.test.ts main/services/aiden-remote-chat-summaries.test.ts main/services/aiden-remote-approved-roots.test.ts main/services/aiden-remote-revocation.test.ts main/services/aiden-remote-bot-files.test.ts main/services/aiden-remote-bots.test.ts main/services/aiden-remote-chat-http.test.ts main/services/aiden-remote-chats.test.ts main/services/aiden-remote-files.test.ts main/services/aiden-remote-git.test.ts main/services/aiden-remote-models.test.ts main/services/aiden-remote-protocol.test.ts main/services/aiden-remote-opaque-handles.test.ts main/services/aiden-remote-operation-contract.test.ts main/services/aiden-remote-pairing.test.ts main/services/aiden-remote-ports.test.ts main/services/aiden-remote-router.test.ts main/services/aiden-remote-schedules.test.ts main/services/aiden-remote-service.test.ts main/services/aiden-remote-state.test.ts main/services/aiden-remote-streams.test.ts main/services/aiden-remote-tailscale-route.test.ts main/services/aiden-remote-tailscale.test.ts main/services/aiden-remote-tls-identity.test.ts main/services/aiden-remote-workspace-browser.test.ts main/services/aiden-remote-workspace-http.test.ts main/services/aiden-remote-workspaces.test.ts renderer/components/remote-connection-popover.test.tsx renderer/components/settings/remote-access-settings.test.tsx renderer/lib/remote-approval.test.ts renderer/lib/remote-connection-status.test.ts renderer/lib/remote-pairing-lifecycle.test.ts renderer/lib/settings-section.test.ts && node --test scripts/aiden-remote-lan-transport-spike.test.mjs", "test:aiden-remote-chat-summaries": "tsx --test main/services/aiden-remote-chat-summaries.test.ts", @@ -63,7 +63,7 @@ "test:model-insights": "tsx --test main/services/openrouter-benchmark.test.ts main/services/models.test.ts main/services/provider-model-info-core.test.ts main/handlers/ipc-contract.test.ts", "test:model-pad": "tsx --test renderer/components/settings/model-pad-settings.test.tsx renderer/lib/google-provider-migration.test.ts renderer/lib/model-pad-layout.test.ts renderer/lib/model-picker-data.test.ts renderer/lib/pi-provider-display.test.ts", "test:command-system": "tsx --test main/services/native-menu-command-contract.test.ts main/services/renderer-readiness-core.test.ts main/services/shortcut-registration-core.test.ts main/services/shortcut-transaction-core.test.ts main/services/superseding-task-core.test.ts renderer/lib/appearance-intent.test.ts renderer/lib/command-palette-contract.test.ts renderer/lib/command-palette-recent.test.ts renderer/lib/command-system-core.test.ts renderer/lib/shortcut-settings-contract.test.ts renderer/lib/use-model-selection.test.ts renderer/shared/keybindings.test.ts", - "test:slash-commands": "tsx --test main/services/generation-initialization-terminal.test.ts main/handlers/attachments.contract.test.ts main/handlers/chat.parse.test.ts main/handlers/chat-create-params.test.ts main/handlers/chat-session-params.test.ts main/handlers/worktree-create-params.test.ts main/services/chat-workspace-authority.test.ts main/handlers/chats.append.contract.test.ts main/services/attachment-contract.test.ts main/services/attachments.test.ts main/services/chat-append-commit.test.ts main/services/chat-export.test.ts main/services/chat-message-contract.test.ts main/services/chat-session-copy.test.ts main/services/chat-store-core.test.ts main/services/chat-turn-admission.test.ts main/services/generation-messages.test.ts main/services/provider-auth-flow-core.test.ts main/services/provider-list-core.test.ts main/services/provider-artwork-core.test.ts main/services/scheduled-chat-creation.test.ts main/services/skill-invocation-flow.integration.test.ts main/services/skill-invocation-turn.test.ts main/services/skill-registry-core.test.ts main/services/skill-registry.test.ts main/services/skill-tools.test.ts main/services/skills-discovery.test.ts renderer/components/assistant/assistant-ui.test.tsx renderer/components/assistant/use-assistant-chat.test.ts renderer/components/composer.test.tsx renderer/components/message-bubble.test.tsx renderer/lib/chat-copy-view.test.ts renderer/lib/codex-provider-cache.test.ts renderer/lib/computer-use-control.test.ts renderer/lib/ipc-stream.test.ts renderer/lib/skill-catalog-workspace.test.ts renderer/lib/slash-command-actions.test.ts renderer/lib/slash-command-core.test.ts renderer/lib/slash-command-performance.test.ts renderer/main/chat-transition.test.tsx renderer/shared/attachment-contract.test.ts renderer/shared/chat-message-contract.test.ts renderer/shared/slash-commands.test.ts", + "test:slash-commands": "tsx --test main/services/generation-initialization-terminal.test.ts main/handlers/attachments.contract.test.ts main/handlers/chat.parse.test.ts main/handlers/chat-create-params.test.ts main/handlers/chat-session-params.test.ts main/handlers/worktree-create-params.test.ts main/services/chat-workspace-authority.test.ts main/handlers/chats.append.contract.test.ts main/services/attachment-contract.test.ts main/services/attachments.test.ts main/services/chat-append-commit.test.ts main/services/chat-export.test.ts main/services/chat-message-contract.test.ts main/services/chat-session-copy.test.ts main/services/chat-store-core.test.ts main/services/chat-turn-admission.test.ts main/services/generation-messages.test.ts main/services/provider-auth-flow-core.test.ts main/services/provider-list-core.test.ts main/services/provider-artwork-core.test.ts main/services/scheduled-chat-creation.test.ts main/services/skill-invocation-flow.integration.test.ts main/services/skill-invocation-turn.test.ts main/services/skill-registry-core.test.ts main/services/skill-registry.test.ts main/services/skill-tools.test.ts main/services/skills-discovery.test.ts renderer/components/assistant/assistant-ui.test.tsx renderer/components/assistant/use-assistant-chat.test.ts renderer/components/composer.test.tsx renderer/lib/chat-message-queue.test.ts renderer/components/message-bubble.test.tsx renderer/lib/chat-copy-view.test.ts renderer/lib/codex-provider-cache.test.ts renderer/lib/computer-use-control.test.ts renderer/lib/ipc-stream.test.ts renderer/lib/skill-catalog-workspace.test.ts renderer/lib/slash-command-actions.test.ts renderer/lib/slash-command-core.test.ts renderer/lib/slash-command-performance.test.ts renderer/main/chat-transition.test.tsx renderer/shared/attachment-contract.test.ts renderer/shared/chat-message-contract.test.ts renderer/shared/slash-commands.test.ts", "test:display-image": "tsx --test main/services/display-image-artifact-store.test.ts main/services/display-image-extension.test.ts main/services/generation-timeline.test.ts renderer/components/message-bubble.test.tsx renderer/lib/ipc-stream.test.ts", "test:ask-user-question": "tsx --test renderer/shared/ask-user-question.test.ts main/services/ask-user-question-coordinator.test.ts main/services/ask-user-question-extension.test.ts renderer/components/ask-user-question-composer.test.ts", "test:todo": "tsx --test main/services/rpiv-todo/*.test.ts renderer/shared/todo.test.ts renderer/components/todo-panel.test.tsx main/services/generation-timeline.test.ts main/handlers/ipc-contract.test.ts renderer/lib/ipc-stream.test.ts", @@ -109,8 +109,8 @@ "test:voice": "tsx --test main/services/transcription-core.test.ts main/services/gemini-live-transcription-core.test.ts main/services/dictation-coordinator.test.ts main/services/dictation-paste.test.ts main/services/parakeet-protocol.test.ts main/services/parakeet-process-core.test.ts main/services/parakeet-transcription-lane.test.ts renderer/shared/voice-models.test.ts renderer/shared/gemini-usage-scope.test.ts renderer/components/settings/gemini-voice-setup.test.tsx renderer/lib/accessibility-permission-core.test.ts renderer/lib/accessibility-refresh.test.ts renderer/lib/dictation-operation-gate.test.ts renderer/lib/gemini-recorded-retry.test.ts renderer/lib/live-pcm-capture.test.ts renderer/lib/voice-recorder-core.test.ts renderer/lib/wav-audio.test.ts", "test:diagnostics": "tsx --test main/services/diagnostics-contract.test.ts main/services/diagnostic-health.test.ts main/services/diagnostic-journal.test.ts main/services/diagnostic-support.test.ts main/services/dev-log.test.ts main/services/process-diagnostics.test.ts main/services/renderer-crash-recovery.test.ts main/services/renderer-diagnostic-rate.test.ts main/services/subagents/subagent-runtime-diagnostics.test.ts renderer/components/settings/diagnostics-settings.test.tsx && node --test scripts/diagnostic-policy.test.mjs", "diagnostics:failure-receipt": "node scripts/write-diagnostic-failure-receipt.mjs", - "test": "tsx --test main/services/mcp-oauth-client-metadata.test.ts main/handlers/assistant-parse.test.ts main/services/assistant/system-prompt.test.ts main/services/chat-activity-core.test.ts main/services/chat-generation-start.test.ts main/services/chat-title-policy.test.ts main/services/chat-title-routing.test.ts main/services/chat-store-core.test.ts main/services/codex-provider.test.ts main/services/coding-tools.test.ts main/services/config-store-core.test.ts main/services/computer-use/computer-use-foundation.test.ts main/services/computer-use/computer-use-tool.test.ts main/services/computer-use/generation-gate.test.ts main/services/computer-use/safety.test.ts main/services/computer-use/settings-core.test.ts main/services/computer-use/status-core.test.ts main/services/data-store.test.ts main/services/data-store.resilience.test.ts main/services/terminal.test.ts main/services/terminal-history.test.ts main/services/aiden-config-dir.test.ts main/services/portable-config-core.test.ts main/services/portable-config-core.roundtrip.test.ts main/services/portable-config-watch-core.test.ts main/services/secret-map-core.test.ts main/services/dev-log.test.ts main/services/process-diagnostics.test.ts main/services/dictation-coordinator.test.ts main/services/dictation-paste.test.ts main/services/dictation-cleanup-core.test.ts main/services/dictation-hotkey.test.ts main/services/dictation-key-state.test.ts main/services/dictation-keycode.test.ts main/services/parakeet-protocol.test.ts main/services/parakeet-process-core.test.ts main/services/foundation-models-connection.test.ts main/services/foundation-models-connection-core.test.ts main/services/gemini-context-cache.test.ts main/services/generation-bound-connection-cache.test.ts main/services/generation-context.test.ts main/services/generation-messages.test.ts main/services/generation-runtime.test.ts main/services/external-editors.test.ts main/services/git.test.ts main/services/model-runtime-core.test.ts main/services/models.test.ts main/services/mcp-oauth-operation.test.ts main/services/mcp-oauth-session.test.ts main/services/mcp-presets.test.ts renderer/shared/plugin-catalog.test.ts main/services/pi-credential-store-core.test.ts main/services/pi-provider-contract.test.ts main/services/profile-share-core.test.ts main/services/profile-share-files.test.ts main/services/profile.test.ts main/services/provider-auth-flow-core.test.ts main/services/provider-auth-owner.test.ts main/services/provider-key-policy.test.ts main/services/provider-list-core.test.ts main/services/provider-artwork-core.test.ts main/services/quit-barrier.test.ts main/services/scratch-workspace.test.ts main/services/skills-discovery.test.ts main/services/tool-approval.test.ts main/services/local-runtime-status.test.ts main/services/usage-store-core.test.ts main/services/workspace-files.test.ts main/windows/pill-window-security.test.ts renderer/components/assistant/use-assistant-chat.test.ts renderer/components/assistant/assistant-ui.test.tsx renderer/components/activity-feed.test.tsx renderer/components/environment-subagents-contract.test.ts renderer/components/subagents-panel.test.tsx renderer/components/chat-sidebar.test.tsx renderer/components/composer.test.tsx renderer/main/chat-transition.test.tsx renderer/components/usage/profile-share-card.test.tsx renderer/lib/accessibility-refresh.test.ts renderer/lib/agent-activity.test.ts renderer/lib/chat-activity.test.ts renderer/lib/assistant-dock.test.ts renderer/lib/assistant-motion-contract.test.ts renderer/lib/dialog-motion-contract.test.ts renderer/lib/scrollbar-gutter-contract.test.ts renderer/lib/text-entry-focus-contract.test.ts renderer/lib/chat-deletion-cache.test.ts renderer/lib/chat-terminal-sync.test.ts renderer/lib/ipc-stream.test.ts renderer/lib/chat-title-reveal.test.ts renderer/lib/codex-auth-session.test.ts renderer/lib/codex-provider-cache.test.ts renderer/lib/composer-placeholder.test.ts renderer/lib/computer-use-notice.test.ts renderer/lib/dictation-operation-gate.test.ts renderer/lib/media-recorder-stop.test.ts renderer/lib/dictation-vad.test.ts renderer/lib/dictation-sounds.test.ts renderer/lib/editor-preference.test.ts renderer/lib/environment-panel-layout.test.ts renderer/lib/subagent-view-state.test.ts renderer/lib/truncate-path.test.ts renderer/lib/mcp-preset-state.test.ts renderer/components/settings/mcp-settings.test.tsx renderer/components/settings/mcp-preset-icons.test.ts renderer/lib/model-display.test.ts renderer/lib/model-picker-data.test.ts renderer/lib/profile-share-data.test.ts renderer/lib/sidebar-chat-shortcuts.test.ts renderer/lib/usage-profile-data.test.ts renderer/shared/appearance.test.ts renderer/components/interface-polish.test.tsx renderer/shared/provider-deployment.test.ts main/handlers/ipc-contract.test.ts main/handlers/chat.parse.test.ts main/handlers/voice-codec.test.ts main/handlers/phase2-parse.test.ts scripts/apple-developer-tools.test.mjs scripts/check-macos-release.test.mjs scripts/computer-use-packaged-acceptance.test.mjs scripts/configure-electron-fuses.test.mjs scripts/model-snapshot-core.test.mjs scripts/prepare-macos-dev-runtime.test.mjs scripts/prepare-macos-package-output.test.mjs scripts/run-macos-distribution.test.mjs scripts/sign-macos.test.mjs scripts/hide-dmg-support-files.test.mjs scripts/update-model-capabilities.test.mjs scripts/vendor-cua-driver.test.mjs scripts/verify-macos-package.test.mjs && npm run test:telegram && npm run test:worktree-remover:native && npm run test:computer-use:native", - "test:coverage": "tsx --test --experimental-test-coverage main/services/mcp-oauth-client-metadata.test.ts main/handlers/assistant-parse.test.ts main/services/assistant/system-prompt.test.ts main/services/chat-activity-core.test.ts main/services/chat-generation-start.test.ts main/services/chat-title-policy.test.ts main/services/chat-title-routing.test.ts main/services/chat-store-core.test.ts main/services/codex-provider.test.ts main/services/coding-tools.test.ts main/services/config-store-core.test.ts main/services/computer-use/computer-use-foundation.test.ts main/services/computer-use/computer-use-tool.test.ts main/services/computer-use/generation-gate.test.ts main/services/computer-use/safety.test.ts main/services/computer-use/settings-core.test.ts main/services/computer-use/status-core.test.ts main/services/data-store.test.ts main/services/data-store.resilience.test.ts main/services/terminal.test.ts main/services/terminal-history.test.ts main/services/aiden-config-dir.test.ts main/services/portable-config-core.test.ts main/services/portable-config-core.roundtrip.test.ts main/services/portable-config-watch-core.test.ts main/services/dev-log.test.ts main/services/process-diagnostics.test.ts main/services/dictation-coordinator.test.ts main/services/dictation-paste.test.ts main/services/dictation-cleanup-core.test.ts main/services/dictation-hotkey.test.ts main/services/dictation-key-state.test.ts main/services/dictation-keycode.test.ts main/services/parakeet-protocol.test.ts main/services/parakeet-process-core.test.ts main/services/foundation-models-connection.test.ts main/services/foundation-models-connection-core.test.ts main/services/generation-bound-connection-cache.test.ts main/services/generation-context.test.ts main/services/generation-messages.test.ts main/services/generation-runtime.test.ts main/services/external-editors.test.ts main/services/git.test.ts main/services/model-runtime-core.test.ts main/services/pi-compaction-core.test.ts main/services/models.test.ts main/services/mcp-oauth-operation.test.ts main/services/mcp-oauth-session.test.ts main/services/mcp-presets.test.ts renderer/shared/plugin-catalog.test.ts main/services/pi-credential-store-core.test.ts main/services/pi-provider-contract.test.ts main/services/profile-share-core.test.ts main/services/profile-share-files.test.ts main/services/profile.test.ts main/services/provider-auth-flow-core.test.ts main/services/provider-auth-owner.test.ts main/services/provider-key-policy.test.ts main/services/provider-list-core.test.ts main/services/provider-artwork-core.test.ts main/services/quit-barrier.test.ts main/services/scratch-workspace.test.ts main/services/skills-discovery.test.ts main/services/tool-approval.test.ts main/services/local-runtime-status.test.ts main/services/usage-store-core.test.ts main/services/workspace-files.test.ts main/windows/pill-window-security.test.ts renderer/components/assistant/use-assistant-chat.test.ts renderer/components/assistant/assistant-ui.test.tsx renderer/components/environment-subagents-contract.test.ts renderer/components/subagents-panel.test.tsx renderer/components/chat-sidebar.test.tsx renderer/components/composer.test.tsx renderer/main/chat-transition.test.tsx renderer/components/usage/profile-share-card.test.tsx renderer/lib/accessibility-refresh.test.ts renderer/lib/agent-activity.test.ts renderer/lib/chat-activity.test.ts renderer/lib/assistant-dock.test.ts renderer/lib/assistant-motion-contract.test.ts renderer/lib/dialog-motion-contract.test.ts renderer/lib/chat-deletion-cache.test.ts renderer/lib/chat-terminal-sync.test.ts renderer/lib/ipc-stream.test.ts renderer/lib/chat-title-reveal.test.ts renderer/lib/codex-auth-session.test.ts renderer/lib/codex-auth-view-state.test.ts renderer/lib/codex-provider-cache.test.ts renderer/lib/composer-placeholder.test.ts renderer/lib/computer-use-notice.test.ts renderer/lib/dictation-operation-gate.test.ts renderer/lib/media-recorder-stop.test.ts renderer/lib/dictation-vad.test.ts renderer/lib/dictation-sounds.test.ts renderer/lib/editor-preference.test.ts renderer/lib/environment-panel-layout.test.ts renderer/lib/subagent-view-state.test.ts renderer/lib/truncate-path.test.ts renderer/lib/mcp-preset-state.test.ts renderer/components/settings/mcp-settings.test.tsx renderer/components/settings/mcp-preset-icons.test.ts renderer/lib/model-display.test.ts renderer/lib/profile-share-data.test.ts renderer/lib/sidebar-chat-shortcuts.test.ts renderer/lib/usage-profile-data.test.ts renderer/shared/appearance.test.ts renderer/shared/provider-deployment.test.ts main/handlers/ipc-contract.test.ts main/handlers/chat.parse.test.ts main/handlers/voice-codec.test.ts main/handlers/phase2-parse.test.ts scripts/apple-developer-tools.test.mjs scripts/check-macos-release.test.mjs scripts/computer-use-packaged-acceptance.test.mjs scripts/configure-electron-fuses.test.mjs scripts/model-snapshot-core.test.mjs scripts/prepare-macos-dev-runtime.test.mjs scripts/prepare-macos-package-output.test.mjs scripts/run-macos-distribution.test.mjs scripts/sign-macos.test.mjs scripts/update-model-capabilities.test.mjs scripts/vendor-cua-driver.test.mjs scripts/verify-macos-package.test.mjs", + "test": "tsx --test main/services/mcp-oauth-client-metadata.test.ts main/handlers/assistant-parse.test.ts main/services/assistant/system-prompt.test.ts main/services/chat-activity-core.test.ts main/services/chat-generation-start.test.ts main/services/chat-title-policy.test.ts main/services/chat-title-routing.test.ts main/services/chat-store-core.test.ts main/services/codex-provider.test.ts main/services/coding-tools.test.ts main/services/config-store-core.test.ts main/services/computer-use/computer-use-foundation.test.ts main/services/computer-use/computer-use-tool.test.ts main/services/computer-use/generation-gate.test.ts main/services/computer-use/safety.test.ts main/services/computer-use/settings-core.test.ts main/services/computer-use/status-core.test.ts main/services/data-store.test.ts main/services/data-store.resilience.test.ts main/services/terminal.test.ts main/services/terminal-history.test.ts main/services/aiden-config-dir.test.ts main/services/portable-config-core.test.ts main/services/portable-config-core.roundtrip.test.ts main/services/portable-config-watch-core.test.ts main/services/secret-map-core.test.ts main/services/dev-log.test.ts main/services/process-diagnostics.test.ts main/services/dictation-coordinator.test.ts main/services/dictation-paste.test.ts main/services/dictation-cleanup-core.test.ts main/services/dictation-hotkey.test.ts main/services/dictation-key-state.test.ts main/services/dictation-keycode.test.ts main/services/parakeet-protocol.test.ts main/services/parakeet-process-core.test.ts main/services/foundation-models-connection.test.ts main/services/foundation-models-connection-core.test.ts main/services/gemini-context-cache.test.ts main/services/generation-bound-connection-cache.test.ts main/services/generation-context.test.ts main/services/generation-messages.test.ts main/services/generation-runtime.test.ts main/services/external-editors.test.ts main/services/git.test.ts main/services/model-runtime-core.test.ts main/services/models.test.ts main/services/mcp-oauth-operation.test.ts main/services/mcp-oauth-session.test.ts main/services/mcp-presets.test.ts renderer/shared/plugin-catalog.test.ts main/services/pi-credential-store-core.test.ts main/services/pi-provider-contract.test.ts main/services/profile-share-core.test.ts main/services/profile-share-files.test.ts main/services/profile.test.ts main/services/provider-auth-flow-core.test.ts main/services/provider-auth-owner.test.ts main/services/provider-key-policy.test.ts main/services/provider-list-core.test.ts main/services/provider-artwork-core.test.ts main/services/quit-barrier.test.ts main/services/scratch-workspace.test.ts main/services/skills-discovery.test.ts main/services/tool-approval.test.ts main/services/local-runtime-status.test.ts main/services/usage-store-core.test.ts main/services/workspace-files.test.ts main/windows/pill-window-security.test.ts renderer/components/assistant/use-assistant-chat.test.ts renderer/components/assistant/assistant-ui.test.tsx renderer/components/activity-feed.test.tsx renderer/components/environment-subagents-contract.test.ts renderer/components/subagents-panel.test.tsx renderer/components/chat-sidebar.test.tsx renderer/components/composer.test.tsx renderer/lib/chat-message-queue.test.ts renderer/main/chat-transition.test.tsx renderer/components/usage/profile-share-card.test.tsx renderer/lib/accessibility-refresh.test.ts renderer/lib/agent-activity.test.ts renderer/lib/chat-activity.test.ts renderer/lib/assistant-dock.test.ts renderer/lib/assistant-motion-contract.test.ts renderer/lib/dialog-motion-contract.test.ts renderer/lib/scrollbar-gutter-contract.test.ts renderer/lib/text-entry-focus-contract.test.ts renderer/lib/chat-deletion-cache.test.ts renderer/lib/chat-terminal-sync.test.ts renderer/lib/ipc-stream.test.ts renderer/lib/chat-title-reveal.test.ts renderer/lib/codex-auth-session.test.ts renderer/lib/codex-provider-cache.test.ts renderer/lib/composer-placeholder.test.ts renderer/lib/computer-use-notice.test.ts renderer/lib/dictation-operation-gate.test.ts renderer/lib/media-recorder-stop.test.ts renderer/lib/dictation-vad.test.ts renderer/lib/dictation-sounds.test.ts renderer/lib/editor-preference.test.ts renderer/lib/environment-panel-layout.test.ts renderer/lib/subagent-view-state.test.ts renderer/lib/truncate-path.test.ts renderer/lib/mcp-preset-state.test.ts renderer/components/settings/mcp-settings.test.tsx renderer/components/settings/mcp-preset-icons.test.ts renderer/lib/model-display.test.ts renderer/lib/model-picker-data.test.ts renderer/lib/profile-share-data.test.ts renderer/lib/sidebar-chat-shortcuts.test.ts renderer/lib/usage-profile-data.test.ts renderer/shared/appearance.test.ts renderer/components/interface-polish.test.tsx renderer/shared/provider-deployment.test.ts main/handlers/ipc-contract.test.ts main/handlers/chat.parse.test.ts main/handlers/voice-codec.test.ts main/handlers/phase2-parse.test.ts scripts/apple-developer-tools.test.mjs scripts/check-macos-release.test.mjs scripts/computer-use-packaged-acceptance.test.mjs scripts/configure-electron-fuses.test.mjs scripts/model-snapshot-core.test.mjs scripts/prepare-macos-dev-runtime.test.mjs scripts/prepare-macos-package-output.test.mjs scripts/run-macos-distribution.test.mjs scripts/sign-macos.test.mjs scripts/hide-dmg-support-files.test.mjs scripts/update-model-capabilities.test.mjs scripts/vendor-cua-driver.test.mjs scripts/verify-macos-package.test.mjs && npm run test:telegram && npm run test:worktree-remover:native && npm run test:computer-use:native", + "test:coverage": "tsx --test --experimental-test-coverage main/services/mcp-oauth-client-metadata.test.ts main/handlers/assistant-parse.test.ts main/services/assistant/system-prompt.test.ts main/services/chat-activity-core.test.ts main/services/chat-generation-start.test.ts main/services/chat-title-policy.test.ts main/services/chat-title-routing.test.ts main/services/chat-store-core.test.ts main/services/codex-provider.test.ts main/services/coding-tools.test.ts main/services/config-store-core.test.ts main/services/computer-use/computer-use-foundation.test.ts main/services/computer-use/computer-use-tool.test.ts main/services/computer-use/generation-gate.test.ts main/services/computer-use/safety.test.ts main/services/computer-use/settings-core.test.ts main/services/computer-use/status-core.test.ts main/services/data-store.test.ts main/services/data-store.resilience.test.ts main/services/terminal.test.ts main/services/terminal-history.test.ts main/services/aiden-config-dir.test.ts main/services/portable-config-core.test.ts main/services/portable-config-core.roundtrip.test.ts main/services/portable-config-watch-core.test.ts main/services/dev-log.test.ts main/services/process-diagnostics.test.ts main/services/dictation-coordinator.test.ts main/services/dictation-paste.test.ts main/services/dictation-cleanup-core.test.ts main/services/dictation-hotkey.test.ts main/services/dictation-key-state.test.ts main/services/dictation-keycode.test.ts main/services/parakeet-protocol.test.ts main/services/parakeet-process-core.test.ts main/services/foundation-models-connection.test.ts main/services/foundation-models-connection-core.test.ts main/services/generation-bound-connection-cache.test.ts main/services/generation-context.test.ts main/services/generation-messages.test.ts main/services/generation-runtime.test.ts main/services/external-editors.test.ts main/services/git.test.ts main/services/model-runtime-core.test.ts main/services/pi-compaction-core.test.ts main/services/models.test.ts main/services/mcp-oauth-operation.test.ts main/services/mcp-oauth-session.test.ts main/services/mcp-presets.test.ts renderer/shared/plugin-catalog.test.ts main/services/pi-credential-store-core.test.ts main/services/pi-provider-contract.test.ts main/services/profile-share-core.test.ts main/services/profile-share-files.test.ts main/services/profile.test.ts main/services/provider-auth-flow-core.test.ts main/services/provider-auth-owner.test.ts main/services/provider-key-policy.test.ts main/services/provider-list-core.test.ts main/services/provider-artwork-core.test.ts main/services/quit-barrier.test.ts main/services/scratch-workspace.test.ts main/services/skills-discovery.test.ts main/services/tool-approval.test.ts main/services/local-runtime-status.test.ts main/services/usage-store-core.test.ts main/services/workspace-files.test.ts main/windows/pill-window-security.test.ts renderer/components/assistant/use-assistant-chat.test.ts renderer/components/assistant/assistant-ui.test.tsx renderer/components/environment-subagents-contract.test.ts renderer/components/subagents-panel.test.tsx renderer/components/chat-sidebar.test.tsx renderer/components/composer.test.tsx renderer/lib/chat-message-queue.test.ts renderer/main/chat-transition.test.tsx renderer/components/usage/profile-share-card.test.tsx renderer/lib/accessibility-refresh.test.ts renderer/lib/agent-activity.test.ts renderer/lib/chat-activity.test.ts renderer/lib/assistant-dock.test.ts renderer/lib/assistant-motion-contract.test.ts renderer/lib/dialog-motion-contract.test.ts renderer/lib/chat-deletion-cache.test.ts renderer/lib/chat-terminal-sync.test.ts renderer/lib/ipc-stream.test.ts renderer/lib/chat-title-reveal.test.ts renderer/lib/codex-auth-session.test.ts renderer/lib/codex-auth-view-state.test.ts renderer/lib/codex-provider-cache.test.ts renderer/lib/composer-placeholder.test.ts renderer/lib/computer-use-notice.test.ts renderer/lib/dictation-operation-gate.test.ts renderer/lib/media-recorder-stop.test.ts renderer/lib/dictation-vad.test.ts renderer/lib/dictation-sounds.test.ts renderer/lib/editor-preference.test.ts renderer/lib/environment-panel-layout.test.ts renderer/lib/subagent-view-state.test.ts renderer/lib/truncate-path.test.ts renderer/lib/mcp-preset-state.test.ts renderer/components/settings/mcp-settings.test.tsx renderer/components/settings/mcp-preset-icons.test.ts renderer/lib/model-display.test.ts renderer/lib/profile-share-data.test.ts renderer/lib/sidebar-chat-shortcuts.test.ts renderer/lib/usage-profile-data.test.ts renderer/shared/appearance.test.ts renderer/shared/provider-deployment.test.ts main/handlers/ipc-contract.test.ts main/handlers/chat.parse.test.ts main/handlers/voice-codec.test.ts main/handlers/phase2-parse.test.ts scripts/apple-developer-tools.test.mjs scripts/check-macos-release.test.mjs scripts/computer-use-packaged-acceptance.test.mjs scripts/configure-electron-fuses.test.mjs scripts/model-snapshot-core.test.mjs scripts/prepare-macos-dev-runtime.test.mjs scripts/prepare-macos-package-output.test.mjs scripts/run-macos-distribution.test.mjs scripts/sign-macos.test.mjs scripts/update-model-capabilities.test.mjs scripts/vendor-cua-driver.test.mjs scripts/verify-macos-package.test.mjs", "test:computer-use": "tsx --test main/services/computer-use/computer-use-foundation.test.ts main/services/computer-use/computer-use-tool.test.ts main/services/computer-use/generation-gate.test.ts main/services/computer-use/safety.test.ts main/services/computer-use/settings-core.test.ts main/services/computer-use/status-core.test.ts main/services/data-store.test.ts main/services/generation-messages.test.ts main/services/generation-runtime.test.ts main/services/quit-barrier.test.ts main/services/tool-approval.test.ts scripts/check-macos-release.test.mjs scripts/computer-use-packaged-acceptance.test.mjs scripts/configure-electron-fuses.test.mjs scripts/prepare-macos-package-output.test.mjs scripts/run-macos-distribution.test.mjs scripts/sign-macos.test.mjs scripts/vendor-cua-driver.test.mjs scripts/verify-macos-package.test.mjs && npm run test:computer-use:native", "test:computer-use:packaged": "node scripts/computer-use-packaged-acceptance.mjs", "test:computer-use:native": "cd native/computer-use-broker && CARGO_TARGET_DIR=../../build/computer-use-broker-test cargo fmt -- --check && CARGO_TARGET_DIR=../../build/computer-use-broker-test cargo test --locked && CARGO_TARGET_DIR=../../build/computer-use-broker-test cargo clippy --locked --all-targets -- -D warnings", diff --git a/renderer/components/assistant/assistant-bubble.tsx b/renderer/components/assistant/assistant-bubble.tsx index 9bfe8e1b..49d2f4fa 100644 --- a/renderer/components/assistant/assistant-bubble.tsx +++ b/renderer/components/assistant/assistant-bubble.tsx @@ -4,11 +4,11 @@ import { unreadBadgeLabel } from "../../lib/assistant-dock"; const AIDEN_MARK_URL = new URL("../../../resources/app-icon.png", import.meta.url).href; /** - * The minimized Aiden affordance: the app mark cropped to a circle, with an + * The minimized Aiden affordance: the app mark cropped to the shared button shape, with an * unread badge and an optional one-line preview of the latest reply. * * The mark is the macOS icon, which carries transparent padding around its - * squircle. Scaling it past the circular mask lets the artwork bleed to the + * squircle. Scaling it past the mask lets the artwork bleed to the * edge instead of leaving a ring of empty pixels. */ interface AssistantBubbleProps { @@ -36,9 +36,9 @@ export const AssistantBubble = React.forwardRef - + {badge ? ( diff --git a/renderer/components/composer-context-bar.tsx b/renderer/components/composer-context-bar.tsx new file mode 100644 index 00000000..0ecd9dc5 --- /dev/null +++ b/renderer/components/composer-context-bar.tsx @@ -0,0 +1,43 @@ +import * as React from "react"; +import { APPEARANCE_CHANGE_EVENT, readCachedAppearance } from "../lib/appearance-runtime"; + +function subscribe(listener: () => void) { + window.addEventListener(APPEARANCE_CHANGE_EVENT, listener); + return () => window.removeEventListener(APPEARANCE_CHANGE_EVENT, listener); +} + +function autoHideEnabled() { + return readCachedAppearance()?.autoHideComposerContext ?? true; +} + +/** Keep portal-based branch/worktree dialogs mounted while their context strip collapses. */ +export function ComposerContextBar({ + hasUserMessages, + inputRef, + children, +}: React.PropsWithChildren<{ + hasUserMessages: boolean; + inputRef?: React.RefObject; +}>) { + const autoHide = React.useSyncExternalStore(subscribe, autoHideEnabled, () => true); + const hidden = autoHide && hasUserMessages; + const stripRef = React.useRef(null); + React.useLayoutEffect(() => { + if (hidden && stripRef.current?.contains(document.activeElement)) { + inputRef?.current?.focus({ preventScroll: true }); + } + }, [hidden, inputRef]); + return ( +
+
{children}
+
+ ); +} diff --git a/renderer/components/composer.test.tsx b/renderer/components/composer.test.tsx index 70f6439c..2e86d1a4 100644 --- a/renderer/components/composer.test.tsx +++ b/renderer/components/composer.test.tsx @@ -6,6 +6,22 @@ function source(relativePath: string): string { return readFileSync(new URL(relativePath, import.meta.url), "utf8"); } +test("workspace context collapses only after a persisted user message and retains portal controls", () => { + const composer = source("./composer.tsx"); + const bar = source("./composer-context-bar.tsx"); + const styles = source("../styles.css"); + assert.match(composer, /sessionChat\?\.messages\.some\(\(message\) => message\.role === "user"\)/u); + assert.match(bar, /autoHide && hasUserMessages/u); + assert.match(bar, /inert=\{hidden \|\| undefined\}/u); + assert.match(bar, /aria-hidden=\{hidden \|\| undefined\}/u); + assert.match(bar, /APPEARANCE_CHANGE_EVENT/u); + assert.match(styles, /\.composer-context-collapse\[data-collapsed="true"\][\s\S]*?grid-template-rows: 0fr/u); + assert.match(styles, /transform: translateY\(8px\)/u); + assert.match(styles, /:root\[data-reduce-motion="true"\] \.composer-context-content \{\s*transition: none;/u); + // A hidden strip must not unmount slash-opened worktree dialogs. + assert.match(bar, /className="composer-context-content">\{children\}/u); +}); + test("composer focus tints the whole shell, not only the textarea", () => { const composer = source("./composer.tsx"); const styles = source("../styles.css"); @@ -167,7 +183,7 @@ test("composer slash palette is an overlaid textarea-owned accessible listbox", assert.match(composer, /skillSelectionEnabled/u); assert.match(composer, /Remove \$\{selectedSkill\.invocation\.displayName\} skill from message/u); const optimisticClear = composer.indexOf('setText("");'); - const sendAwait = composer.indexOf("await onSend("); + const sendAwait = composer.indexOf("await submit("); assert.ok(optimisticClear >= 0 && optimisticClear < sendAwait); assert.match(composer, /if \(sendPendingRef\.current\) return false;/u); assert.match(composer, /type: "send-started"/u); diff --git a/renderer/components/composer.tsx b/renderer/components/composer.tsx index d55f1229..dee359e3 100644 --- a/renderer/components/composer.tsx +++ b/renderer/components/composer.tsx @@ -25,6 +25,7 @@ import { ChevronDown, FileText, Folder, + ListPlus, Loader2, Lock, Mic, @@ -37,6 +38,7 @@ import { X, } from "lucide-react"; import { AidenIcon } from "./aiden-icon"; +import { ComposerContextBar } from "./composer-context-bar"; import { GitBranchPicker } from "./git-branch-picker"; import { WorkspacePicker } from "./workspace-picker"; import { useVoiceRecorder } from "../lib/use-voice-recorder"; @@ -122,6 +124,9 @@ interface ComposerProps { skillInvocation?: SkillInvocationV1, options?: { visualize?: boolean; btw?: boolean }, ) => Promise; + onQueue?: ComposerProps["onSend"]; + queuedMessages?: React.ReactNode; + hasQueuedMessages?: boolean; onStop: () => void; isGenerating: boolean; canStopGeneration?: boolean; @@ -265,6 +270,9 @@ export function Composer({ chatId, onSend, onStop, + onQueue, + queuedMessages, + hasQueuedMessages = false, isGenerating, canStopGeneration = isGenerating, configurationBusy = false, @@ -408,7 +416,7 @@ export function Composer({ const submissionAllowed = composerSubmissionAllowed({ ready, - isGenerating, + isGenerating: isGenerating && !onQueue, sending, permissionSaving, computerUseSaving: computerUse?.saving === true, @@ -792,7 +800,8 @@ export function Composer({ }); try { - await onSend( + const submit = onQueue && (isGenerating || hasQueuedMessages) && !payload.btw ? onQueue : onSend; + await submit( payload.sendText, payload.attachments, payload.selectedSkill?.invocation, @@ -839,7 +848,7 @@ export function Composer({ setSending(false); } }, - [onSend, setText, updateAttachments], + [onSend, onQueue, isGenerating, hasQueuedMessages, setText, updateAttachments], ); const selectSlashResult = React.useCallback( @@ -1485,7 +1494,9 @@ export function Composer({ ) : null} + {queuedMessages} {/* Workspace context: folder (opens in Finder) · local execution · git branch. */} + message.role === "user") ?? hasMessages} inputRef={inputRef}>
{workspacePickerEnabled && onSelectWorkspace && onCreateScratchWorkspace ? ( ) : null}
- +
@@ -1735,7 +1746,7 @@ export function Composer({ className="rounded-full" onClick={handleAttach} disabled={ - attaching || isGenerating || sending || gitOperationBusy || sessionCommandBusy + attaching || (isGenerating && !onQueue) || sending || gitOperationBusy || sessionCommandBusy } aria-label={ attaching ? "Choosing or loading attachments" : "Attach files or images" @@ -1943,7 +1954,7 @@ export function Composer({ variant={voice.recording ? "destructive" : "transparent"} size="small" iconOnly - disabled={voice.transcribing || isGenerating || sending || sessionCommandBusy} + disabled={voice.transcribing || (isGenerating && !onQueue) || sending || sessionCommandBusy} onClick={() => (voice.recording ? voice.stop() : voice.start())} aria-label={voice.recording ? "Stop recording" : "Start voice input"} > @@ -1953,12 +1964,19 @@ export function Composer({ )} - {isGenerating && canStopGeneration ? ( + {onQueue && isGenerating ? ( + + ) : null} + {isGenerating ? ( )}
diff --git a/renderer/components/model-picker.tsx b/renderer/components/model-picker.tsx index d30a8f32..72f591ef 100644 --- a/renderer/components/model-picker.tsx +++ b/renderer/components/model-picker.tsx @@ -565,7 +565,6 @@ export function ModelPicker({ tabIndex={view === "list" ? 0 : -1} variant="transparent" size="small" - radius="rounded" className={cn( "h-6 justify-center px-2", view === "list" && "bg-popover shadow-control", @@ -583,7 +582,6 @@ export function ModelPicker({ tabIndex={view === "pad" ? 0 : -1} variant="transparent" size="small" - radius="rounded" className={cn( "h-6 justify-center px-2", view === "pad" && "bg-popover shadow-control", diff --git a/renderer/components/onboarding-flow.test.tsx b/renderer/components/onboarding-flow.test.tsx index 624db126..913e06ba 100644 --- a/renderer/components/onboarding-flow.test.tsx +++ b/renderer/components/onboarding-flow.test.tsx @@ -305,6 +305,7 @@ test("onboarding presentation stays compact and free of decorative gradients", ( }); test("the final step is a complete grouped bento gallery with hover descriptions", () => { + assert.match(source, /Queue follow-ups, edit them, or steer the next response/u); assert.match(source, /data-onboarding-bento/u); assert.match(source, /data-onboarding-feature-count=\{featureBentos\.length\}/u); assert.match(source, /auto-rows-\[118px\][\s\S]*?grid-cols-6/u); diff --git a/renderer/components/onboarding-flow.tsx b/renderer/components/onboarding-flow.tsx index 58c6a736..65fab512 100644 --- a/renderer/components/onboarding-flow.tsx +++ b/renderer/components/onboarding-flow.tsx @@ -179,7 +179,7 @@ const featureBentos: FeatureBento[] = [ id: "workspace", group: "create", title: "Workspace Agent", - description: "Read, search, edit, and run commands inside the workspace you choose.", + description: "Read, search, edit, and run commands in your workspace. Queue follow-ups, edit them, or steer the next response.", icon: MessageSquare, imageUrl: FEATURE_ILLUSTRATIONS.workspace, size: "hero", diff --git a/renderer/components/open-in-editor-picker.tsx b/renderer/components/open-in-editor-picker.tsx index 7123861b..94e0c5f1 100644 --- a/renderer/components/open-in-editor-picker.tsx +++ b/renderer/components/open-in-editor-picker.tsx @@ -136,12 +136,11 @@ export function OpenInEditorPicker({ workspaceId, folderPath }: OpenInEditorPick
diff --git a/renderer/components/queued-messages.tsx b/renderer/components/queued-messages.tsx new file mode 100644 index 00000000..a64c475e --- /dev/null +++ b/renderer/components/queued-messages.tsx @@ -0,0 +1,231 @@ +import * as React from "react"; +import { + ArrowDown, + ArrowUp, + CornerDownRight, + FileText, + GripVertical, + MoreHorizontal, + Trash2, +} from "lucide-react"; +import { Button, Dialog, Textarea, toast } from "./ui"; +import { type ChatMessageQueue, type QueuedChatMessage } from "../lib/chat-message-queue"; + +export function QueuedMessages({ + queue, + canSteer, + onSteer, + returnFocus, +}: { + queue: ChatMessageQueue; + canSteer: boolean; + onSteer: (id: string) => void; + returnFocus: () => HTMLElement | null; +}) { + const state = React.useSyncExternalStore(queue.subscribe, queue.getSnapshot); + const [draft, setDraft] = React.useState(null); + const editorTrigger = React.useRef(null); + const draggedId = React.useRef(null); + React.useEffect(() => () => queue.closeEditor(), [queue]); + if (state.messages.length === 0) return null; + const editing = state.messages.find((message) => message.id === state.editingId); + const draftIndex = state.messages.findIndex((message) => message.id === draft?.id); + return ( +
+
+ + {state.messages.length} queued{state.paused ? " · Paused" : ""} + + +
+
    + {state.messages.map((message, index) => ( +
  1. { + if (draggedId.current) event.preventDefault(); + }} + onDrop={(event) => { + if (!draggedId.current) return; + event.preventDefault(); + event.stopPropagation(); + queue.move(draggedId.current, index); + draggedId.current = null; + }} + > + + {message.attachments[0] ? ( + message.attachments[0].kind === "image" ? ( + {message.attachments[0].name} + ) : ( +
  2. + ))} +
+

+ Unsent messages stay in this window until it closes. Open this chat to continue its queue. +

+ + editorTrigger.current?.isConnected ? editorTrigger.current : returnFocus() + } + onOpenChange={(open) => { + if (!open) { + queue.closeEditor(); + setDraft(null); + } + }} + onConfirm={() => { + if (!draft) return; + try { + queue.update(draft); + setDraft(null); + } catch (error) { + toast.error(error instanceof Error ? error.message : "Couldn't save the message."); + } + }} + > + {draft ? ( +
+