diff --git a/packages/app-bundle/manifest.json b/packages/app-bundle/manifest.json index d4262f43..0570b393 100644 --- a/packages/app-bundle/manifest.json +++ b/packages/app-bundle/manifest.json @@ -2,25 +2,25 @@ "schema": 4, "slices": "b (complete app-graph delta)", "scope": "complete fork-vs-base delta of packages/{app,ui,session-ui,schema,core,sdk}", - "fork_tag": "v1.18.10-amicode.15", - "fork_sha": "b460cf8be8dbdaf0f3b6531e50a6edb5f80e6831", + "fork_tag": "v1.18.10-amicode.16", + "fork_sha": "77bb1c8b76631f878fc5c963d4796219bc99ff4a", "upstream_base": "v1.18.12", "upstream_base_sha": "0dd6950d1b06958fbcdcadf0ad56258257ab7fdb", - "extracted_at": "2026-08-22T21:37:25.141Z", + "extracted_at": "2026-08-23T15:32:52.432Z", "per_package": { "packages/app": { - "A": 94, - "M": 105, + "A": 99, + "M": 134, "D": 1 }, "packages/core": { - "A": 3, - "M": 23, + "A": 4, + "M": 30, "D": 0 }, "packages/schema": { "A": 2, - "M": 5, + "M": 6, "D": 0 }, "packages/sdk": { @@ -30,19 +30,19 @@ }, "packages/session-ui": { "A": 8, - "M": 16, + "M": 26, "D": 0 }, "packages/ui": { - "A": 142, - "M": 22, - "D": 0 + "A": 146, + "M": 45, + "D": 6 } }, "counts": { - "overlay_total": 422, - "deletions": 1, - "server_coupled": 35 + "overlay_total": 502, + "deletions": 7, + "server_coupled": 44 }, "server_coupled_port_inventory": [ "packages/core/schema.json", @@ -50,8 +50,11 @@ "packages/core/src/database/database.ts", "packages/core/src/database/migration.gen.ts", "packages/core/src/database/migration/20260813162312_shocking_karnak.ts", + "packages/core/src/database/migration/20260820000001_add_session_directories.ts", "packages/core/src/database/schema.gen.ts", "packages/core/src/flag/flag.ts", + "packages/core/src/location-mutation.ts", + "packages/core/src/location.ts", "packages/core/src/models-dev.ts", "packages/core/src/observability/otlp.ts", "packages/core/src/permission.ts", @@ -60,6 +63,8 @@ "packages/core/src/provider-permission.ts", "packages/core/src/session.ts", "packages/core/src/session/runner/llm.ts", + "packages/core/src/session/sql.ts", + "packages/core/src/system-context/builtins.ts", "packages/core/src/tool/bash.ts", "packages/core/src/tool/edit.ts", "packages/core/src/tool/glob.ts", @@ -70,8 +75,12 @@ "packages/core/src/tool/websearch.ts", "packages/core/src/tool/write.ts", "packages/core/src/v1/session.ts", + "packages/core/test/fixture/location.ts", + "packages/core/test/location-mutation.test.ts", + "packages/core/test/system-context/builtins.test.ts", "packages/core/test/tool-question.test.ts", "packages/schema/src/index.ts", + "packages/schema/src/location.ts", "packages/schema/src/prompt.ts", "packages/schema/src/provider-permission.ts", "packages/schema/src/question.ts", @@ -84,6 +93,7 @@ "true_overlays": [ "packages/app/e2e/regression/project-picker-recent-search.spec.ts", "packages/app/e2e/regression/review-line-comment.spec.ts", + "packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts", "packages/app/e2e/regression/session-timeline-tool-projection.spec.ts", "packages/app/e2e/regression/tab-navigate-mousedown.spec.ts", "packages/app/e2e/smoke/session-timeline.spec.ts", @@ -92,26 +102,45 @@ "packages/app/playwright.config.ts", "packages/app/public/oc-theme-preload.js", "packages/app/src/app.tsx", + "packages/app/src/components/dialog-command-palette-v2.css", + "packages/app/src/components/dialog-command-palette-v2.tsx", + "packages/app/src/components/dialog-connect-provider.tsx", + "packages/app/src/components/dialog-edit-project-v2.tsx", + "packages/app/src/components/dialog-edit-project.tsx", + "packages/app/src/components/dialog-release-notes.tsx", + "packages/app/src/components/dialog-select-directory-v2.css", + "packages/app/src/components/dialog-select-file.tsx", + "packages/app/src/components/help-button.tsx", "packages/app/src/components/prompt-input-v2.tsx", "packages/app/src/components/prompt-input.tsx", "packages/app/src/components/prompt-input/attachments.ts", + "packages/app/src/components/prompt-input/context-items.tsx", + "packages/app/src/components/prompt-input/image-attachments.tsx", "packages/app/src/components/prompt-input/placeholder.ts", + "packages/app/src/components/prompt-input/slash-popover.tsx", "packages/app/src/components/prompt-project-selector.tsx", "packages/app/src/components/session-context-usage.tsx", "packages/app/src/components/session/index.ts", "packages/app/src/components/session/session-context-tab.tsx", "packages/app/src/components/session/session-header.tsx", "packages/app/src/components/session/session-new-view.tsx", + "packages/app/src/components/session/session-sortable-terminal-tab-v2.tsx", "packages/app/src/components/settings-dialog.tsx", "packages/app/src/components/settings-v2/dialog-settings-v2.tsx", "packages/app/src/components/settings-v2/general.tsx", "packages/app/src/components/settings-v2/settings-v2.css", "packages/app/src/components/status-popover-body.tsx", + "packages/app/src/components/status-popover-indicator.test.ts", + "packages/app/src/components/status-popover-indicator.ts", "packages/app/src/components/status-popover.tsx", "packages/app/src/components/terminal.tsx", "packages/app/src/components/titlebar-tab-nav.css", + "packages/app/src/components/titlebar-tab-nav.tsx", + "packages/app/src/components/titlebar-tab-popover.css", "packages/app/src/components/titlebar-tab-strip.tsx", "packages/app/src/components/titlebar.tsx", + "packages/app/src/components/ui/drawer.tsx", + "packages/app/src/components/windows-app-menu.tsx", "packages/app/src/context/command.tsx", "packages/app/src/context/directory-sync.ts", "packages/app/src/context/global-sync/bootstrap.test.ts", @@ -160,9 +189,11 @@ "packages/app/src/pages/home-session-archive.test.ts", "packages/app/src/pages/home-session-archive.ts", "packages/app/src/pages/home.tsx", + "packages/app/src/pages/home/home-projects-view.tsx", "packages/app/src/pages/home/home-sessions-view.tsx", "packages/app/src/pages/layout-new.tsx", "packages/app/src/pages/layout.tsx", + "packages/app/src/pages/layout/project-avatar-state.ts", "packages/app/src/pages/layout/session-tab-avatar.tsx", "packages/app/src/pages/layout/sidebar-items.tsx", "packages/app/src/pages/layout/sidebar-workspace.tsx", @@ -175,19 +206,30 @@ "packages/app/src/pages/session/composer/session-composer-region.tsx", "packages/app/src/pages/session/composer/session-composer-state.ts", "packages/app/src/pages/session/composer/session-question-dock.tsx", + "packages/app/src/pages/session/composer/session-revert-dock.stories.tsx", "packages/app/src/pages/session/composer/session-revert-dock.tsx", + "packages/app/src/pages/session/composer/todo-panel-motion.stories.tsx", "packages/app/src/pages/session/helpers.test.ts", "packages/app/src/pages/session/helpers.ts", "packages/app/src/pages/session/session-panel-width.test.ts", "packages/app/src/pages/session/session-panel-width.ts", "packages/app/src/pages/session/session-side-panel.tsx", + "packages/app/src/pages/session/terminal-panel-v2.tsx", "packages/app/src/pages/session/timeline/message-timeline.tsx", "packages/app/src/pages/session/use-session-commands.tsx", "packages/app/src/pages/session/v2/review-panel-v2-state.ts", + "packages/app/src/theme-preload.test.ts", "packages/app/src/utils/server-compat.ts", + "packages/app/src/wsl/dialog-add-server.tsx", + "packages/app/src/wsl/dialog-add-wsl-server.css", "packages/app/src/wsl/settings-model.test.ts", "packages/app/src/wsl/settings-model.ts", + "packages/app/src/wsl/settings.tsx", "packages/session-ui/package.json", + "packages/session-ui/src/components/basic-tool.css", + "packages/session-ui/src/components/basic-tool.stories.tsx", + "packages/session-ui/src/components/basic-tool.tsx", + "packages/session-ui/src/components/line-comment-styles.ts", "packages/session-ui/src/components/markdown-stream.test.ts", "packages/session-ui/src/components/markdown-stream.ts", "packages/session-ui/src/components/markdown.css", @@ -195,39 +237,74 @@ "packages/session-ui/src/components/message-part.css", "packages/session-ui/src/components/message-part.tsx", "packages/session-ui/src/components/session-retry.tsx", + "packages/session-ui/src/components/session-review.css", "packages/session-ui/src/components/session-turn.css", + "packages/session-ui/src/components/session-turn.tsx", "packages/session-ui/src/components/tool-error-card.tsx", "packages/session-ui/src/components/tool-status-title.tsx", + "packages/session-ui/src/v2/components/attachment-card-v2.css", + "packages/session-ui/src/v2/components/basic-tool-v2.css", + "packages/session-ui/src/v2/components/basic-tool-v2.tsx", "packages/session-ui/src/v2/components/prompt-input/attachments.ts", "packages/session-ui/src/v2/components/prompt-input/index.tsx", "packages/session-ui/src/v2/components/session-review-file-preview-v2.tsx", "packages/session-ui/src/v2/components/session-review-v2.css", "packages/session-ui/src/v2/components/session-review-v2.tsx", + "packages/session-ui/src/v2/components/tool-error-card-v2.css", "packages/ui/package.json", "packages/ui/src/assets/favicon/site.webmanifest", + "packages/ui/src/components/context-menu.css", + "packages/ui/src/components/context-menu.stories.tsx", "packages/ui/src/components/dialog.css", "packages/ui/src/components/dock-surface.css", + "packages/ui/src/components/dropdown-menu.css", "packages/ui/src/components/favicon.tsx", + "packages/ui/src/components/hover-card.css", "packages/ui/src/components/icon.tsx", + "packages/ui/src/components/image-preview.css", + "packages/ui/src/components/list.stories.tsx", "packages/ui/src/components/logo.css", "packages/ui/src/components/logo.tsx", + "packages/ui/src/components/popover.css", + "packages/ui/src/components/resize-handle.stories.tsx", + "packages/ui/src/components/select.css", + "packages/ui/src/components/tabs.css", "packages/ui/src/components/text-field.tsx", + "packages/ui/src/components/text-reveal.stories.tsx", + "packages/ui/src/components/text-strikethrough.stories.tsx", + "packages/ui/src/components/thinking-heading.stories.tsx", + "packages/ui/src/components/toast.css", + "packages/ui/src/components/tooltip.css", "packages/ui/src/context/dialog.tsx", "packages/ui/src/context/marked.tsx", "packages/ui/src/i18n/en.ts", "packages/ui/src/styles/index.css", "packages/ui/src/styles/theme.css", "packages/ui/src/theme/context.tsx", + "packages/ui/src/theme/default-themes.ts", "packages/ui/src/theme/themes/oc-2.json", + "packages/ui/src/v2/components/badge-v2.css", "packages/ui/src/v2/components/dialog-v2.css", "packages/ui/src/v2/components/icon-button-v2.tsx", "packages/ui/src/v2/components/icon.tsx", + "packages/ui/src/v2/components/menu-v2.css", + "packages/ui/src/v2/components/menu-v2.stories.tsx", + "packages/ui/src/v2/components/radio-v2.css", + "packages/ui/src/v2/components/select-v2.css", "packages/ui/src/v2/components/switch-v2.css", + "packages/ui/src/v2/components/toast-v2.css", "packages/ui/src/v2/components/tooltip-v2.css", - "packages/ui/src/v2/components/wordmark-v2.tsx" + "packages/ui/src/v2/components/wordmark-v2.tsx", + "packages/ui/src/v2/styles/theme.css" ], "deletions": [ - "packages/app/src/components/debug-bar.tsx" + "packages/app/src/components/debug-bar.tsx", + "packages/ui/src/components/text-shimmer.css", + "packages/ui/src/components/text-shimmer.stories.tsx", + "packages/ui/src/components/text-shimmer.tsx", + "packages/ui/src/v2/components/text-shimmer-v2.css", + "packages/ui/src/v2/components/text-shimmer-v2.stories.tsx", + "packages/ui/src/v2/components/text-shimmer-v2.tsx" ], "classification": { "packages/app/CONTEXT.md": "A", @@ -241,6 +318,7 @@ "packages/app/e2e/regression/pane-boot-watch.spec.ts": "A", "packages/app/e2e/regression/project-picker-recent-search.spec.ts": "M", "packages/app/e2e/regression/review-line-comment.spec.ts": "M", + "packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts": "M", "packages/app/e2e/regression/session-timeline-tool-projection.spec.ts": "M", "packages/app/e2e/regression/tab-navigate-mousedown.spec.ts": "M", "packages/app/e2e/regression/workbench-drops.spec.ts": "A", @@ -251,6 +329,7 @@ "packages/app/playwright.config.ts": "M", "packages/app/public/amico.svg": "A", "packages/app/public/assets/Aero-Regular.ttf": "A", + "packages/app/public/assets/DMSans-Variable.woff2": "A", "packages/app/public/assets/JuliaMono-Regular.woff2": "A", "packages/app/public/assets/RacingSansOne-Regular.woff2": "A", "packages/app/public/oc-theme-preload.js": "M", @@ -265,7 +344,16 @@ "packages/app/src/components/amicode-defaults-capsule.tsx": "A", "packages/app/src/components/amicode-file-ref-bridge.tsx": "A", "packages/app/src/components/connection-banner.tsx": "A", + "packages/app/src/components/dialog-command-palette-v2.css": "M", + "packages/app/src/components/dialog-command-palette-v2.tsx": "M", + "packages/app/src/components/dialog-connect-provider.tsx": "M", + "packages/app/src/components/dialog-edit-project-v2.tsx": "M", + "packages/app/src/components/dialog-edit-project.tsx": "M", "packages/app/src/components/dialog-new-project.tsx": "A", + "packages/app/src/components/dialog-release-notes.tsx": "M", + "packages/app/src/components/dialog-select-directory-v2.css": "M", + "packages/app/src/components/dialog-select-file.tsx": "M", + "packages/app/src/components/help-button.tsx": "M", "packages/app/src/components/pane-bridge.tsx": "A", "packages/app/src/components/prompt-input-clipboard-structure.test.ts": "A", "packages/app/src/components/prompt-input-v2.tsx": "M", @@ -273,7 +361,10 @@ "packages/app/src/components/prompt-input/attachments.ts": "M", "packages/app/src/components/prompt-input/clipboard-bridge.test.ts": "A", "packages/app/src/components/prompt-input/clipboard-bridge.ts": "A", + "packages/app/src/components/prompt-input/context-items.tsx": "M", + "packages/app/src/components/prompt-input/image-attachments.tsx": "M", "packages/app/src/components/prompt-input/placeholder.ts": "M", + "packages/app/src/components/prompt-input/slash-popover.tsx": "M", "packages/app/src/components/prompt-project-selector.tsx": "M", "packages/app/src/components/report-bug-button.css": "A", "packages/app/src/components/report-bug-button.stories.tsx": "A", @@ -287,6 +378,7 @@ "packages/app/src/components/session/session-header.tsx": "M", "packages/app/src/components/session/session-new-view.tsx": "M", "packages/app/src/components/session/session-preview-tab.tsx": "A", + "packages/app/src/components/session/session-sortable-terminal-tab-v2.tsx": "M", "packages/app/src/components/session/use-context-warning.ts": "A", "packages/app/src/components/settings-dialog.tsx": "M", "packages/app/src/components/settings-v2/data-storage-controller.ts": "A", @@ -297,8 +389,11 @@ "packages/app/src/components/settings-v2/general.tsx": "M", "packages/app/src/components/settings-v2/permissions.tsx": "A", "packages/app/src/components/settings-v2/settings-v2.css": "M", + "packages/app/src/components/solver-switch-banner.tsx": "A", "packages/app/src/components/split-frame.tsx": "A", "packages/app/src/components/status-popover-body.tsx": "M", + "packages/app/src/components/status-popover-indicator.test.ts": "M", + "packages/app/src/components/status-popover-indicator.ts": "M", "packages/app/src/components/status-popover-model.test.ts": "A", "packages/app/src/components/status-popover-model.ts": "A", "packages/app/src/components/status-popover.tsx": "M", @@ -306,12 +401,16 @@ "packages/app/src/components/titlebar-channel.test.ts": "A", "packages/app/src/components/titlebar-channel.ts": "A", "packages/app/src/components/titlebar-tab-nav.css": "M", + "packages/app/src/components/titlebar-tab-nav.tsx": "M", + "packages/app/src/components/titlebar-tab-popover.css": "M", "packages/app/src/components/titlebar-tab-strip.tsx": "M", "packages/app/src/components/titlebar.tsx": "M", + "packages/app/src/components/ui/drawer.tsx": "M", "packages/app/src/components/vault-browser-model.test.ts": "A", "packages/app/src/components/vault-browser-model.ts": "A", "packages/app/src/components/vault-browser.tsx": "A", "packages/app/src/components/vault-panel.tsx": "A", + "packages/app/src/components/windows-app-menu.tsx": "M", "packages/app/src/components/workbench-panel.tsx": "A", "packages/app/src/context/command.tsx": "M", "packages/app/src/context/directory-sync.ts": "M", @@ -368,14 +467,19 @@ "packages/app/src/pages/home-session-archive.test.ts": "M", "packages/app/src/pages/home-session-archive.ts": "M", "packages/app/src/pages/home.tsx": "M", + "packages/app/src/pages/home/home-projects-view.tsx": "M", "packages/app/src/pages/home/home-sessions-view.tsx": "M", "packages/app/src/pages/layout-new.tsx": "M", "packages/app/src/pages/layout.tsx": "M", + "packages/app/src/pages/layout/project-avatar-state.ts": "M", "packages/app/src/pages/layout/session-tab-avatar.tsx": "M", + "packages/app/src/pages/layout/session-tab-status.test.ts": "A", + "packages/app/src/pages/layout/session-tab-status.tsx": "A", "packages/app/src/pages/layout/sidebar-items.tsx": "M", "packages/app/src/pages/layout/sidebar-workspace.tsx": "M", "packages/app/src/pages/new-session.tsx": "M", "packages/app/src/pages/new-session/new-session-draft-controller.ts": "M", + "packages/app/src/pages/new-session/new-session-view.test.ts": "A", "packages/app/src/pages/new-session/new-session-view.tsx": "M", "packages/app/src/pages/session.tsx": "M", "packages/app/src/pages/session/composer/bug-dock-controller.test.ts": "A", @@ -392,13 +496,16 @@ "packages/app/src/pages/session/composer/session-question-dock.helpers.test.ts": "A", "packages/app/src/pages/session/composer/session-question-dock.helpers.ts": "A", "packages/app/src/pages/session/composer/session-question-dock.tsx": "M", + "packages/app/src/pages/session/composer/session-revert-dock.stories.tsx": "M", "packages/app/src/pages/session/composer/session-revert-dock.tsx": "M", + "packages/app/src/pages/session/composer/todo-panel-motion.stories.tsx": "M", "packages/app/src/pages/session/helpers.test.ts": "M", "packages/app/src/pages/session/helpers.ts": "M", "packages/app/src/pages/session/session-panel-width.test.ts": "M", "packages/app/src/pages/session/session-panel-width.ts": "M", "packages/app/src/pages/session/session-side-panel-structure.test.ts": "A", "packages/app/src/pages/session/session-side-panel.tsx": "M", + "packages/app/src/pages/session/terminal-panel-v2.tsx": "M", "packages/app/src/pages/session/timeline/last-prompt-bubble.test.ts": "A", "packages/app/src/pages/session/timeline/message-timeline.tsx": "M", "packages/app/src/pages/session/use-amicode-commands.test.ts": "A", @@ -408,6 +515,7 @@ "packages/app/src/pages/session/v2/accumulate-diffs.ts": "A", "packages/app/src/pages/session/v2/review-panel-v2-state.test.ts": "A", "packages/app/src/pages/session/v2/review-panel-v2-state.ts": "M", + "packages/app/src/theme-preload.test.ts": "M", "packages/app/src/utils/amicode-bridge.ts": "A", "packages/app/src/utils/amicode-bug-report.test.ts": "A", "packages/app/src/utils/amicode-bug-report.ts": "A", @@ -427,15 +535,21 @@ "packages/app/src/utils/web-zoom.test.ts": "A", "packages/app/src/utils/web-zoom.ts": "A", "packages/app/src/utils/webview-context-menu.ts": "A", + "packages/app/src/wsl/dialog-add-server.tsx": "M", + "packages/app/src/wsl/dialog-add-wsl-server.css": "M", "packages/app/src/wsl/settings-model.test.ts": "M", "packages/app/src/wsl/settings-model.ts": "M", + "packages/app/src/wsl/settings.tsx": "M", "packages/core/schema.json": "M", "packages/core/src/config.ts": "M", "packages/core/src/database/database.ts": "M", "packages/core/src/database/migration.gen.ts": "M", "packages/core/src/database/migration/20260813162312_shocking_karnak.ts": "A", + "packages/core/src/database/migration/20260820000001_add_session_directories.ts": "A", "packages/core/src/database/schema.gen.ts": "M", "packages/core/src/flag/flag.ts": "M", + "packages/core/src/location-mutation.ts": "M", + "packages/core/src/location.ts": "M", "packages/core/src/models-dev.ts": "M", "packages/core/src/observability/otlp.ts": "M", "packages/core/src/permission.ts": "M", @@ -444,6 +558,8 @@ "packages/core/src/provider-permission.ts": "A", "packages/core/src/session.ts": "M", "packages/core/src/session/runner/llm.ts": "M", + "packages/core/src/session/sql.ts": "M", + "packages/core/src/system-context/builtins.ts": "M", "packages/core/src/tool/bash.ts": "M", "packages/core/src/tool/edit.ts": "M", "packages/core/src/tool/glob.ts": "M", @@ -454,8 +570,12 @@ "packages/core/src/tool/websearch.ts": "M", "packages/core/src/tool/write.ts": "M", "packages/core/src/v1/session.ts": "M", + "packages/core/test/fixture/location.ts": "M", + "packages/core/test/location-mutation.test.ts": "M", + "packages/core/test/system-context/builtins.test.ts": "M", "packages/core/test/tool-question.test.ts": "M", "packages/schema/src/index.ts": "M", + "packages/schema/src/location.ts": "M", "packages/schema/src/prompt.ts": "M", "packages/schema/src/provider-permission.ts": "A", "packages/schema/src/question.ts": "M", @@ -465,7 +585,11 @@ "packages/sdk/js/src/v2/gen/sdk.gen.ts": "M", "packages/sdk/js/src/v2/gen/types.gen.ts": "M", "packages/session-ui/package.json": "M", + "packages/session-ui/src/components/basic-tool.css": "M", + "packages/session-ui/src/components/basic-tool.stories.tsx": "M", + "packages/session-ui/src/components/basic-tool.tsx": "M", "packages/session-ui/src/components/group-parts.test.ts": "A", + "packages/session-ui/src/components/line-comment-styles.ts": "M", "packages/session-ui/src/components/markdown-file-refs.test.ts": "A", "packages/session-ui/src/components/markdown-file-refs.ts": "A", "packages/session-ui/src/components/markdown-stream.test.ts": "M", @@ -479,15 +603,21 @@ "packages/session-ui/src/components/message-part.css": "M", "packages/session-ui/src/components/message-part.tsx": "M", "packages/session-ui/src/components/session-retry.tsx": "M", + "packages/session-ui/src/components/session-review.css": "M", "packages/session-ui/src/components/session-turn.css": "M", + "packages/session-ui/src/components/session-turn.tsx": "M", "packages/session-ui/src/components/tool-error-card.tsx": "M", "packages/session-ui/src/components/tool-status-title.tsx": "M", "packages/session-ui/src/util/clipboard.ts": "A", + "packages/session-ui/src/v2/components/attachment-card-v2.css": "M", + "packages/session-ui/src/v2/components/basic-tool-v2.css": "M", + "packages/session-ui/src/v2/components/basic-tool-v2.tsx": "M", "packages/session-ui/src/v2/components/prompt-input/attachments.ts": "M", "packages/session-ui/src/v2/components/prompt-input/index.tsx": "M", "packages/session-ui/src/v2/components/session-review-file-preview-v2.tsx": "M", "packages/session-ui/src/v2/components/session-review-v2.css": "M", "packages/session-ui/src/v2/components/session-review-v2.tsx": "M", + "packages/session-ui/src/v2/components/tool-error-card-v2.css": "M", "packages/ui/package.json": "M", "packages/ui/src/amicode/amico-presence.css": "A", "packages/ui/src/amicode/amico-presence.stories.tsx": "A", @@ -563,6 +693,8 @@ "packages/ui/src/amicode/run-window.tsx": "A", "packages/ui/src/amicode/shell-row.test.ts": "A", "packages/ui/src/amicode/shell-row.ts": "A", + "packages/ui/src/amicode/solver-switch.test.ts": "A", + "packages/ui/src/amicode/solver-switch.ts": "A", "packages/ui/src/amicode/solver-toggle.test.ts": "A", "packages/ui/src/amicode/solver-toggle.tsx": "A", "packages/ui/src/amicode/spinner.tsx": "A", @@ -619,6 +751,7 @@ "packages/ui/src/components/amicode-run-gallery.tsx": "A", "packages/ui/src/components/amicode-run-window.tsx": "A", "packages/ui/src/components/amicode-shell-row.tsx": "A", + "packages/ui/src/components/amicode-solver-switch.tsx": "A", "packages/ui/src/components/amicode-solver-toggle.tsx": "A", "packages/ui/src/components/amicode-splash.tsx": "A", "packages/ui/src/components/amicode-thinking.tsx": "A", @@ -626,16 +759,31 @@ "packages/ui/src/components/amicode-widget-grid.tsx": "A", "packages/ui/src/components/brain-engine.tsx": "A", "packages/ui/src/components/brain-ref.tsx": "A", + "packages/ui/src/components/context-menu.css": "M", + "packages/ui/src/components/context-menu.stories.tsx": "M", "packages/ui/src/components/context-tree-data.tsx": "A", "packages/ui/src/components/context-tree-engine.tsx": "A", "packages/ui/src/components/dialog.css": "M", "packages/ui/src/components/dock-surface.css": "M", + "packages/ui/src/components/dropdown-menu.css": "M", "packages/ui/src/components/favicon.tsx": "M", + "packages/ui/src/components/hover-card.css": "M", "packages/ui/src/components/icon.tsx": "M", + "packages/ui/src/components/image-preview.css": "M", + "packages/ui/src/components/list.stories.tsx": "M", "packages/ui/src/components/logo.css": "M", "packages/ui/src/components/logo.tsx": "M", + "packages/ui/src/components/popover.css": "M", + "packages/ui/src/components/resize-handle.stories.tsx": "M", + "packages/ui/src/components/select.css": "M", + "packages/ui/src/components/tabs.css": "M", "packages/ui/src/components/text-field.tsx": "M", + "packages/ui/src/components/text-reveal.stories.tsx": "M", + "packages/ui/src/components/text-strikethrough.stories.tsx": "M", + "packages/ui/src/components/thinking-heading.stories.tsx": "M", "packages/ui/src/components/thinking-line.tsx": "A", + "packages/ui/src/components/toast.css": "M", + "packages/ui/src/components/tooltip.css": "M", "packages/ui/src/context/dialog.tsx": "M", "packages/ui/src/context/marked-math.test.ts": "A", "packages/ui/src/context/marked.tsx": "M", @@ -644,14 +792,23 @@ "packages/ui/src/styles/index.css": "M", "packages/ui/src/styles/theme.css": "M", "packages/ui/src/theme/context.tsx": "M", + "packages/ui/src/theme/default-themes.ts": "M", + "packages/ui/src/theme/themes/harmoniqs.json": "A", "packages/ui/src/theme/themes/oc-2.json": "M", "packages/ui/src/util/clipboard.ts": "A", + "packages/ui/src/v2/components/badge-v2.css": "M", "packages/ui/src/v2/components/dialog-v2.css": "M", "packages/ui/src/v2/components/icon-button-v2.tsx": "M", "packages/ui/src/v2/components/icon.tsx": "M", + "packages/ui/src/v2/components/menu-v2.css": "M", + "packages/ui/src/v2/components/menu-v2.stories.tsx": "M", + "packages/ui/src/v2/components/radio-v2.css": "M", + "packages/ui/src/v2/components/select-v2.css": "M", "packages/ui/src/v2/components/switch-v2.css": "M", + "packages/ui/src/v2/components/toast-v2.css": "M", "packages/ui/src/v2/components/tooltip-v2.css": "M", - "packages/ui/src/v2/components/wordmark-v2.tsx": "M" + "packages/ui/src/v2/components/wordmark-v2.tsx": "M", + "packages/ui/src/v2/styles/theme.css": "M" }, "files": { "packages/app/CONTEXT.md": "87b15a82a89ea1c82e0d647c169a259a1ef5193893854f2cfed54526762898ca", @@ -665,31 +822,42 @@ "packages/app/e2e/regression/pane-boot-watch.spec.ts": "3aa869f8b1d9881797e14dceb9854c4408f768f490d4db8722e29e511bca1833", "packages/app/e2e/regression/project-picker-recent-search.spec.ts": "64f4249f29cfce29d5e755a544c5aa2f27599d9fd874d363628f696f5f0e02b9", "packages/app/e2e/regression/review-line-comment.spec.ts": "953e9c67ed4cee91968369088237cf2288558eaa55e55b61f385571bccad7003", + "packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts": "00bfd4f6199c1b350d14a9eda5e3a4c546b020df32e3604609326281869d6d4f", "packages/app/e2e/regression/session-timeline-tool-projection.spec.ts": "c0bb866b113d38ddec084e04027030cf5b7c81f1fe4e6ee88fcd7b1b161296c3", "packages/app/e2e/regression/tab-navigate-mousedown.spec.ts": "8521d43183f0df624449edbba8ee4dc646f0ecb84a9c5a04d275613f9da875ae", "packages/app/e2e/regression/workbench-drops.spec.ts": "0188f4f1ff07a40945a4900bfa85e21a4f578be9f18257a803200a18ce41ac05", "packages/app/e2e/regression/workbench-panes.spec.ts": "e0dfb84bd1b8be00a89cf48387aeaab5ec75845e8efbc8e4fa8252204fb27e8f", "packages/app/e2e/smoke/session-timeline.spec.ts": "7dc5b08126af46fa11727bd0ff2f3b25a04e430afbf84c8bafb4b73f4609dd2d", "packages/app/e2e/utils/waits.ts": "53959eeae1e6562a2e6c578b261140c74fe9fc30e1e4c46295d3252b6d274a74", - "packages/app/index.html": "9240501ab6c74b84599a5eb9d852c4e606dc60eb2e69a4ac84b182bf8b1b5b68", + "packages/app/index.html": "fa38026be3b7e7ec38958a558d7b4d059140b2aff998c3ac862601543702584d", "packages/app/playwright.config.ts": "b7dae0b39248b4ddf7cba829af96ac7fe3cc919add9711ecb5292d9bd52e1805", "packages/app/public/amico.svg": "a14b9d543d895bcdf0758f7b9ef5908ee0acaac794446494af059b159247db8f", "packages/app/public/assets/Aero-Regular.ttf": "0ebf14f80d05c3a82c766124ab31196c310014744333d397425ac82a1e3537c4", + "packages/app/public/assets/DMSans-Variable.woff2": "aa530716b0d351866af7dbfa3eee4120fb36f2d071baff8c234185141865c7ff", "packages/app/public/assets/JuliaMono-Regular.woff2": "43f5dfca02a03035f89799da5af150f28d5c4aff729753076094c687fc61b063", "packages/app/public/assets/RacingSansOne-Regular.woff2": "2ff92c8abe7a35962922c54da215d68690f8502463698b1b595bed597b63e3ff", - "packages/app/public/oc-theme-preload.js": "483f1c347e8f4c7deeac3c20d324dc1329631a682da9c44e56b7b56b8df9f69b", + "packages/app/public/oc-theme-preload.js": "aad72db4d57b84d3a3533ea97a80e4a127f824ec729d4bf0b657fb911d1d149a", "packages/app/src/amicode/inspector/device-inspector.tsx": "5ab63709e88176bb4dad1072ffe28fd59ad67d414c9d844ee6c55353df50df79", "packages/app/src/amicode/inspector/inspector-bridge.ts": "460dbf4509b4e56a8eacd6cac7adcb3a63c6cd3e417d851df5f3e3b259095d3e", "packages/app/src/amicode/inspector/inspector-context.tsx": "ac81b9e0e2a4035f85b6af68b7f0cefcb9f256a175b244ba13136f3a307c0c77", "packages/app/src/amicode/inspector/run-inspector.tsx": "63a6351fad12a8377550eb87f7602a712bae586fe482ff46cd847c8544fc6e8a", - "packages/app/src/app.tsx": "48912ae64588cd21c71a2e6bbd563f174c19861defb18aa0b323bfd22e975342", + "packages/app/src/app.tsx": "de9d50560c1405dad9db47331aca59bc42ef15a780eba962b5b78ba46b26fa4e", "packages/app/src/components/amicode-default-model-value.ts": "416bb4a7cf051c678bdfc5246ddc5796a9a6efe48d1e5c2e933756800cfdae8d", "packages/app/src/components/amicode-default-model.test.ts": "941750d074706bd5f4e433c9737b19ebc0ac0f9ba9cd1faaa599a24b22126e05", "packages/app/src/components/amicode-default-model.tsx": "16a2de1feb12e07416fe5fbb25879391f912ee0909b553c868c1b531f2ed1f2d", - "packages/app/src/components/amicode-defaults-capsule.tsx": "6c0b143695f63423e651e0123ece7d5dc15ef2cb3e6ab9aaac566287e3efe6da", + "packages/app/src/components/amicode-defaults-capsule.tsx": "94dc081bc47abb7e50566fcd8e9e53124f9d7632d61f93eff4f1627e0c2efb2d", "packages/app/src/components/amicode-file-ref-bridge.tsx": "11c17d7fdb29d16530e188b13ee8af728f37d9adfbb518084106ce50cc0c527a", - "packages/app/src/components/connection-banner.tsx": "69aae9294b797d06b21cc3958c7bfa7a6dad451f14399638037ee19e4c9a0203", + "packages/app/src/components/connection-banner.tsx": "c793966e1f9d6f307af697032b235c2ff65afaa04f6cc232e40ce45e5745ded4", + "packages/app/src/components/dialog-command-palette-v2.css": "85984957adaf15efcc0828310f3a0b438cedaaba776a9fec2eddb3ad83487fa2", + "packages/app/src/components/dialog-command-palette-v2.tsx": "e12da3e5e9e503089dd9139b20e04f6b566cf74fa445f23a2e193f5883f7c5f3", + "packages/app/src/components/dialog-connect-provider.tsx": "bdcb33a4aca41578ef52e566223016626ba62dfb6ed31f572958abf92a30ade3", + "packages/app/src/components/dialog-edit-project-v2.tsx": "f34aaed28604facd9d8a46a4934e2b331421d5f60902a2b160ddcec11af03bd9", + "packages/app/src/components/dialog-edit-project.tsx": "5371da3de3a643d4347629af55c0adc2476bffe7b2f061535a92fd67e756e562", "packages/app/src/components/dialog-new-project.tsx": "102fc33e8eeaf3c76ddd1b953b7b97995d30c02a3579f8b7036e47448a1582a8", + "packages/app/src/components/dialog-release-notes.tsx": "0ea5b049786787643a7f6b54d4ccf84c4f8ffa4e8272426c0d32f60d445b6890", + "packages/app/src/components/dialog-select-directory-v2.css": "2e2f18abdca77f9fe85da157c40f288f3b1641ca92845208749e16455fd58637", + "packages/app/src/components/dialog-select-file.tsx": "e6278ee35dd27d203e00ad73f70c709c44a6c06f81ffbd931edfaa0c986b9e03", + "packages/app/src/components/help-button.tsx": "780d533f776a784660cfed072a131198bc6fcc8e8fe332e8e1a220d516c9684e", "packages/app/src/components/pane-bridge.tsx": "756b3306b51c963fac6778716f6c6c4626f0f3bf75c93b7cb4cab58ea0bf3d4e", "packages/app/src/components/prompt-input-clipboard-structure.test.ts": "8c15844499378d6ae1d84d7c6f5b99bf37cd9baf5b35a45dc1535246f3d324c6", "packages/app/src/components/prompt-input-v2.tsx": "9aabf52c91628015d4eeff15292696540c97a97572b5dadd26ee328631c0647a", @@ -697,10 +865,13 @@ "packages/app/src/components/prompt-input/attachments.ts": "c3c28df0d2f18378d39e17147a4c64e8ea01d63b97c39e1874ed9445ca671bb1", "packages/app/src/components/prompt-input/clipboard-bridge.test.ts": "cbc50495466c169d4f558cdb0aea5fc9215baf5257ddbde0414491826e4087d9", "packages/app/src/components/prompt-input/clipboard-bridge.ts": "556f79e96388bee80fa1c38b5e916f7efd1d87227c1e9eded8ddc59de47b961c", + "packages/app/src/components/prompt-input/context-items.tsx": "027b02e394ccd14849052bf3fcacdae1e090c71d164fa8955addfe3f7abf1758", + "packages/app/src/components/prompt-input/image-attachments.tsx": "30cdea4285884b4c257670b57ade4266f5861e8a303d3355a591f302ed4bf965", "packages/app/src/components/prompt-input/placeholder.ts": "f085dc0cae6c2267f1a4a0e819dc9061d97ebdabb3baeae44f91376fbf1a4837", + "packages/app/src/components/prompt-input/slash-popover.tsx": "a1800173edebcd419802796470f7270acaf0ef689a72f1b81a1cedfd8968337d", "packages/app/src/components/prompt-project-selector.tsx": "1d0970b4d067261d3b88af089537be8200369e490d2e02553043a140d9a8e961", "packages/app/src/components/report-bug-button.css": "909809b6241d53c386aa4c8ea34539ba162a8998fff755834c783025bc53a5fd", - "packages/app/src/components/report-bug-button.stories.tsx": "e3d28ffec2f9682227a3f6ded24674afbbf0416d64369cac4711bb12d7709e1a", + "packages/app/src/components/report-bug-button.stories.tsx": "82aed22bd3fcca448841205dee759ca0f0de92562a99d07c696eee5bd29e807b", "packages/app/src/components/report-bug-button.tsx": "a1beb8c94b4faaa4c7db3ab68de349f8a8a03cb3a058ce447f552aed35848d43", "packages/app/src/components/session-context-usage.tsx": "f5b07800bbe571067a8625d6175a29ec1626c365fc336e96db92f6506a84aaa3", "packages/app/src/components/session/context-warning-banner.tsx": "f5fab4d7a9536238b70d64488fc30d44f6a4948fdcffd910a9666f4b6c296019", @@ -708,9 +879,10 @@ "packages/app/src/components/session/panel-menu.tsx": "d07e11d635615107c9acdb06460460b5015b4141fb3f9c4002837933f960e22f", "packages/app/src/components/session/session-chats-dropdown.test.ts": "2003d2a15781337ea6bff2c68e730cc5b6df38697f15d927ab26913936444db7", "packages/app/src/components/session/session-context-tab.tsx": "227243b178b517f067d9ae0ae0eec3c559beeb6681828158b0600a17e98e7f81", - "packages/app/src/components/session/session-header.tsx": "e40067c61f958395892dea880d9424ec5c17fa6bcc093e9b33baaed922e2738e", - "packages/app/src/components/session/session-new-view.tsx": "3043eab823572207da9de02028bf7698600e074bef43b47f5e29b325961048c6", + "packages/app/src/components/session/session-header.tsx": "7f5174ee8bf2e78125904a12d58599d0c413a8aa65db079ed212c27c9171d03e", + "packages/app/src/components/session/session-new-view.tsx": "9510a4f550a3f0d4791e98e8025666f09d70a60fb66f193e48ee61feddae5a57", "packages/app/src/components/session/session-preview-tab.tsx": "b6b4b8fe6f751499e0d3005e1f12224351df4f28f221bba10fc2f91c0f09e777", + "packages/app/src/components/session/session-sortable-terminal-tab-v2.tsx": "08db0e378c3e07d243121f40c77e153bafe897e5e2ececd48a3e00786793032b", "packages/app/src/components/session/use-context-warning.ts": "af7a6d0159a5541aa02ad4d08fd694af1cc4853fc1c0a70763bc264a634d1c53", "packages/app/src/components/settings-dialog.tsx": "da3bc01dad4ece4214c14083ae4e7dea87ef64ef9829b315b867fdafccdb6916", "packages/app/src/components/settings-v2/data-storage-controller.ts": "fa5d143cc101f3a3b9d5ad445edddc981e0d02783021d52dbfed6c8d8bf62498", @@ -718,24 +890,31 @@ "packages/app/src/components/settings-v2/developer-tools-controller.ts": "dca45b8ca7fa5f7790ebd67dd1502d729e15887deeda5a86fd9df0f959464f98", "packages/app/src/components/settings-v2/developer-tools.tsx": "96ccc0e2ba8ee40eeeaea4933ad176a4029a1b972364e070f0cb027d8fb0a1db", "packages/app/src/components/settings-v2/dialog-settings-v2.tsx": "253c32f1309aa5a474075f8fe5d3ad2f40946b772903e07d38a6560f9c4e634c", - "packages/app/src/components/settings-v2/general.tsx": "e20b03b6f6bb6fc6418bc60ce6dc5af2a02fedc1da76b4ab6a861a2267b909ef", - "packages/app/src/components/settings-v2/permissions.tsx": "89b491b4971f23c5308f34096c2645b01831ceb1ed9e604fd494af9317d48f0c", - "packages/app/src/components/settings-v2/settings-v2.css": "481af8fec5288cdee579a66c9718a99c514b1cf732ab5bbb2829766d55af29ef", + "packages/app/src/components/settings-v2/general.tsx": "7efddf68e7b7332f396934b76c35267763223a351e91121e6eeaa9f5390df76d", + "packages/app/src/components/settings-v2/permissions.tsx": "c91efb2cd0b6fbc01cc28d540475b9b4ff19370d47f8291a22f7a0a2545bc51a", + "packages/app/src/components/settings-v2/settings-v2.css": "3a7fe0e2d0b463ae9127b420910fab2f726b4ea6fcc110ed8aba63510f8e0bc1", + "packages/app/src/components/solver-switch-banner.tsx": "ec7edeec6dc05a4a06b39241b4f3a8e7534da97722dd8ec6f26ff782025802e9", "packages/app/src/components/split-frame.tsx": "44c53737f72027b461097c03df48a4f411d04a82d02b1fce4901813c73dfcc72", - "packages/app/src/components/status-popover-body.tsx": "5e2fc75b6974960dd86f885d4496d8e53d0ff2d2226221e4d48309956d612fd8", + "packages/app/src/components/status-popover-body.tsx": "a78d557256283fd05657d999b2a6babc0bcb5ae3dcfa2cb07f770ebb602a94a1", + "packages/app/src/components/status-popover-indicator.test.ts": "9afc7b49d9021a94d74f3f777178230ae13bd26e5749da0cee4d3378eacacacd", + "packages/app/src/components/status-popover-indicator.ts": "887b3e659ff49fd0d2a8715fad6562dc7e9402b80d06442058502306bdb108f5", "packages/app/src/components/status-popover-model.test.ts": "10634035bab0f31de24564e0d6c8413a0f7434fb2c08ac3ef722981a69dd08e7", "packages/app/src/components/status-popover-model.ts": "ec5a5a3caf9344fe617c7bc668af81d96ad4290378c34186cd4cd959821f1c85", - "packages/app/src/components/status-popover.tsx": "47d308ada46d3c0d4c43ead93e97016a04ba5b17caaf9e5c6d3a92d1b1b6d019", - "packages/app/src/components/terminal.tsx": "3d5468362c63bd01661c745612e3d64a6d9e8a999d66b38202c5bb78401dcea6", + "packages/app/src/components/status-popover.tsx": "663b3ab44218780b3415120c42d89a2a410ba0054231e8fdd0887a97a1b75b23", + "packages/app/src/components/terminal.tsx": "747938975e4bdddd00d9ad2485aae885ee9af9222011c1dfc286f1f757c9b605", "packages/app/src/components/titlebar-channel.test.ts": "fe4dbe37e8b599878e16f2e56a62814fa787a94f2e523b3ca3f2d5310cf940cb", "packages/app/src/components/titlebar-channel.ts": "2e01bb619f36799a684952538bc47844bac96a2c5eaaed240c6317d2b07b8efa", - "packages/app/src/components/titlebar-tab-nav.css": "ab5dd7bc4810dd5c4c5f6550b10e98985823ca8e0b5365b1c5ddde6ff0b057a5", + "packages/app/src/components/titlebar-tab-nav.css": "90930c2f9476fb0eace8692424a029b3510781c88d1ee61f7f2edba559526832", + "packages/app/src/components/titlebar-tab-nav.tsx": "2304e936af56c24f27f1549f4f8751a600ed274123d7a3973ecfa6a5bb28102c", + "packages/app/src/components/titlebar-tab-popover.css": "d731db2d7d49838004089a3d82ff68c22214b0ca636bf3b8e02498c14dadbf8d", "packages/app/src/components/titlebar-tab-strip.tsx": "182a21a367b36dca30a570ea98015f434cec49db671edca6e921778a3abeb6e8", - "packages/app/src/components/titlebar.tsx": "ca2e24f3d73f92b075ec120acc399c23e1b04feed6f05878817d78a692937a0f", + "packages/app/src/components/titlebar.tsx": "359d41238a3503900621877d8094e50517cb53870428e4c0fb316858c3381a40", + "packages/app/src/components/ui/drawer.tsx": "76574ca4c6997209f37a766a61000d707332a9a857f6ea8100a3cee1be58d65c", "packages/app/src/components/vault-browser-model.test.ts": "7ca11eae3574033d58081bc4126b1ec772a5b496573c70159f05497a1365f4cd", "packages/app/src/components/vault-browser-model.ts": "d9ef6ff588a96ae79b13c3ae0c2a442a42b59021dd501b3a17bd810dda6f7b20", "packages/app/src/components/vault-browser.tsx": "509efa874d69bcd07ef2495a025bc8de2b2fa582736003ff709b4ca76096274b", "packages/app/src/components/vault-panel.tsx": "1d9f06f42b94ffb94a28bfd6d2693a36c75ceb969bcd434081b4cd2fbf99665c", + "packages/app/src/components/windows-app-menu.tsx": "460669a129e9be61ab2d5f51fe6ecad73579a395295d5f724f235b988cdc667e", "packages/app/src/components/workbench-panel.tsx": "d16f477044098842a214a2e16f9be719e7688a04bcb575889ca544988f23c690", "packages/app/src/context/command.tsx": "06ffdc83f39186481bba8120e4bc1b14c2160d1dc789a29e0a07ffc7c5a2cd35", "packages/app/src/context/directory-sync.ts": "0d9e6aa3cff6c6654c98928f59c4e2fcced04f42b167184eea77be04c00371ee", @@ -754,8 +933,8 @@ "packages/app/src/context/local.tsx": "3ab8b9fc2db082df4ba485373679f00a95d0ffe3c691a0afee1dc53db7eabd9e", "packages/app/src/context/models.tsx": "8904525767b36ed5ba93e85a0fddb9bf737a8d066dfff084380ce1a67182f535", "packages/app/src/context/platform.tsx": "d4ee3f33e485f5fd6d6d449edcc7da0bbbed733e139f9f293fd011e78153c6c5", - "packages/app/src/context/server-sdk.test.ts": "e9f0390dec05e8c55f4fc3127e18c05c2674856d853bde30a67952a313098d52", - "packages/app/src/context/server-sdk.tsx": "c147f740ffaa2feb87364a70f535c5bd20893a7f54b82f66374a21407d066b91", + "packages/app/src/context/server-sdk.test.ts": "5cd22962c8ea1e4374915508a765281ceb9f808ad8bbf241c06b00b48621c816", + "packages/app/src/context/server-sdk.tsx": "b3450d9bdafde6ed1a95d7dc970fc9f33bef81531b05245c36b789878d09d186", "packages/app/src/context/server-session.ts": "009fcdb6cd374c0f2cac53d37952afaa611f65371b43ff84f3654cffa8c56b18", "packages/app/src/context/server.tsx": "08e978e4b4b3094221a2a0b92e069f39cbedb6d6cf95935881e21cde52265ee2", "packages/app/src/context/settings.tsx": "1187bcab92b8e6f622f7522aaed185e144d1b174b91487935ad542703cbed3e5", @@ -764,7 +943,7 @@ "packages/app/src/context/vault-panel.ts": "f91cb5fb49a9f0d46bd2a1b131f9a218b30147e2ad3617d2a2dc4f1e14599a53", "packages/app/src/context/workbench.tsx": "69a44da2b69989bd2c67ebffdb49941841f3f4718d6a70d659c828b57928d9eb", "packages/app/src/context/zoom-keybind.test.ts": "a1385c93639687a7dac7d21f8f02841d82a55e42465ce3836b97b16c82b52469", - "packages/app/src/design-polish.css": "26cf372ab527f44ac45f660f44dcae900f89c432fbd3db8684161058e13f364e", + "packages/app/src/design-polish.css": "ac3721d894b22876afe34984f6272c0add22fb3f2194f1ed3b0accb3cfe54bb0", "packages/app/src/desktop-menu.ts": "55481604f4fd5dcb74b0678347076c42fda7f928590d90bc2c3d1633bea8bc36", "packages/app/src/entry.tsx": "da720fb72421c8e247a925fc627b24096e9d228dcff078cec81cc68f9bd37f2f", "packages/app/src/i18n/ar.ts": "2b5e0c7f5a8c4ebc0305c956a8d9944c3cc86b1169bfe995d1b72a5bc9b5067d", @@ -785,46 +964,54 @@ "packages/app/src/i18n/uk.ts": "98c397aaaba1e66bd3e9fc6041b2a5ea0913077b203839dd16b8714db90d9a04", "packages/app/src/i18n/zh.ts": "20c0ce167ed34d6fecf26d7f2c79839d9d7cbbf23286679ff019afd98f7a8d46", "packages/app/src/i18n/zht.ts": "1a40c07bc44d2494a09990f2eb9cdb972ded8c74d053e4b9fd49fe4fb4c5df8a", - "packages/app/src/index.css": "c453a2430a68c1970a6b570be7840a4b07156d0238cfce8d8ffb8af3d6985b7d", + "packages/app/src/index.css": "a08619fc66175810dc12514cacee7afc3cf592df91b888e1e92ffb553a00840d", "packages/app/src/pages/error.tsx": "db9ba0847cfb205100ad6133cce30c6ea2ce983802af44947e6d2b746c2a6ec5", "packages/app/src/pages/home-projects.test.ts": "a90155c37210a0e5b7a095bbfc3ba2d40681f02927d9726148a706bed5ba7891", "packages/app/src/pages/home-projects.ts": "d55124578dc839b98e2084b488d818e30e4a1abf1ae264499b83e403c2aabca7", "packages/app/src/pages/home-session-archive.test.ts": "e8f1126287a3e9daa455efcc378e5d72aebdd092158bf8526f7779669b72e850", "packages/app/src/pages/home-session-archive.ts": "1b56c173f37f4a4dfd54088ef5fdb0dbcd3d9b62aeb08de4bfa0ed34f07d7ad5", - "packages/app/src/pages/home.tsx": "42c9b0c6fe79702fe5fe548fb1b25dc7bb57b1e6c2bf4225922781f8df86ff9d", - "packages/app/src/pages/home/home-sessions-view.tsx": "757e5db2ed0b62fae5a636cd2ab9404d37bf07640b518866d2da0e0b296903c1", - "packages/app/src/pages/layout-new.tsx": "32485d90c94b3137812ce7439da4dd1ba14689a3340bab6d02cf4877715ec9ed", + "packages/app/src/pages/home.tsx": "9b99c900a576f16d3cd63da0e210ae9a854898c2311dd4649e83d29f4cc5b385", + "packages/app/src/pages/home/home-projects-view.tsx": "074f1cdd7926db9574164824666cd63f1f5bd0f8e8dfb65453f6fe8f2dca0ec8", + "packages/app/src/pages/home/home-sessions-view.tsx": "5b73b18e4349a628a15e09a3810ecf2319bde222e18fe8de5d08373ac91fb37c", + "packages/app/src/pages/layout-new.tsx": "86e910dcf29536232562ea2652062edcc63be48e7507db85d50d1fd7b0b6ea1d", "packages/app/src/pages/layout.tsx": "a6e6574054f6d85c0d91c1236c9db99674b8d51876643896b0c34f77a28bc85a", - "packages/app/src/pages/layout/session-tab-avatar.tsx": "a69e8ba00edaffe0aee86e2c1f8bd82a2e6438e9752322a39a70ef9d57c76f00", + "packages/app/src/pages/layout/project-avatar-state.ts": "7aadb235d27dc45799411c724f0fd1282af82d091cbcefb0344deb7c5d0659d4", + "packages/app/src/pages/layout/session-tab-avatar.tsx": "89e83e239d513f91b31f16473be531eaf82768cefe637ea5f10d752cea4ef887", + "packages/app/src/pages/layout/session-tab-status.test.ts": "5d635ddc3b62ff5c1f73bb0b207ac7b3351f19d6ea695fecd03f78732be193f3", + "packages/app/src/pages/layout/session-tab-status.tsx": "2127478855fa5451cd822d1062114ef1798c1a956718a8768966a964a0ad929a", "packages/app/src/pages/layout/sidebar-items.tsx": "daf940fc66f34e5f83410a8580f7bb1790bedc599ab4d64fcda2301537b24865", "packages/app/src/pages/layout/sidebar-workspace.tsx": "a0f8e3fbd16d09e783db2a7a09c686d3aa993436d882425d3436a59ec4adf94b", "packages/app/src/pages/new-session.tsx": "8cfa0d719590e29fc185811c57306e80c54faf62a7d6049138d15c02e7c844a1", - "packages/app/src/pages/new-session/new-session-draft-controller.ts": "d48383233337a8095ef7a577dac021ae12c712c609fb546427ecda4b6e5299df", - "packages/app/src/pages/new-session/new-session-view.tsx": "230d8c19a76efab586e4c6f48cf271271268bbfeadf71b7fa54b08c4f7722970", - "packages/app/src/pages/session.tsx": "ca906c2d99a61cd4038c76a4ed943d6ed72f55c31fab3b37d1a6d274ae5ac8d9", + "packages/app/src/pages/new-session/new-session-draft-controller.ts": "3607771a22b6855afd0b0d666de662c9070a2dcd56ae96ba51d89c9e11018e81", + "packages/app/src/pages/new-session/new-session-view.test.ts": "b27a1cc858897b6d146eecf3c50db4a17b2efc15421685efdca837dce91b4db8", + "packages/app/src/pages/new-session/new-session-view.tsx": "50c53e16688441b5f22243e38d20686ed1179d1a6954d58c6216b5f9a0f221fb", + "packages/app/src/pages/session.tsx": "e85df7c6f3672de127421617c40e74be32b8bfd967291fcdd4583154e34d1c1c", "packages/app/src/pages/session/composer/bug-dock-controller.test.ts": "fe3d68f2f2bb41b04ee5567cd95a9c01671a98fd21585788be9d4cbb01a4bc22", "packages/app/src/pages/session/composer/bug-dock-controller.ts": "ab2c72a761e1992f300b6a0cecc9d0351b00a56e4be08567bcb22d3fcece78f7", "packages/app/src/pages/session/composer/bug-dock.ts": "11eb1a1c911df8764162d47f07e5f135d94fcb622f8a2ad9b41a23233131ba58", "packages/app/src/pages/session/composer/report-bug.test.ts": "d4dc012309c2171298852501a14a077c6fe8cdf5d25538dc84ddd1f8195d3a8c", "packages/app/src/pages/session/composer/report-bug.ts": "bdc3b3dec38b11c7bfa5b867a246db2717e60e1d50692d6bddb3983c9e95e02b", - "packages/app/src/pages/session/composer/session-bug-dock.stories.tsx": "8244e22ad849861f9da24e559bc3166537e60331f15090d8b0fecbe45fd34d2c", + "packages/app/src/pages/session/composer/session-bug-dock.stories.tsx": "a5730bb3699751d63629134924ce8dea021b264722c40f5f6bb98236dc15eda7", "packages/app/src/pages/session/composer/session-bug-dock.tsx": "e7fed3c5ab80527d69a106440833f795bbbafc5deeba3883902ede9fba612605", "packages/app/src/pages/session/composer/session-composer-controls.ts": "b6942cd192eef5fb67343d54be35c986b735ca1e2185a20b29e226dd67f70e05", "packages/app/src/pages/session/composer/session-composer-region-controller.ts": "3209b9a8981cbaa7f0cd7bd09412b681c1dde108ba917ab54555f9144b6a443f", - "packages/app/src/pages/session/composer/session-composer-region.tsx": "7f8670d8bec3964527a916923426baa38e7b49090cd634c4101e6eccc81981a1", + "packages/app/src/pages/session/composer/session-composer-region.tsx": "93adca76d576fd0af6281f872067339106f591aa0827252ac76b8c1baa7ed7e6", "packages/app/src/pages/session/composer/session-composer-state.ts": "fcffa11d801309cf58f42a174143bf6eb3ea2b14f860ad91a8ffa4e097df5c16", "packages/app/src/pages/session/composer/session-question-dock.helpers.test.ts": "a1fe31fe4dfdf2914bc184030a0427bcbdfae0f3e7b146e1d6ee29ce31d85b64", "packages/app/src/pages/session/composer/session-question-dock.helpers.ts": "b16282d0cf5d39152bd76f4ef8bac32f964f68143a3067765ae0827a5f90deed", - "packages/app/src/pages/session/composer/session-question-dock.tsx": "c6f865f2973fcfe56dc63ad60f3ead07c120595c2880eade70ada63d871238a1", + "packages/app/src/pages/session/composer/session-question-dock.tsx": "c030421f085482688f49466f2539eee493c9ad8a23d5bb76347ad36ee442ee00", + "packages/app/src/pages/session/composer/session-revert-dock.stories.tsx": "0310ce448c1f8feaf4732e66429654fe3602d470f3121835cf6435084c455c36", "packages/app/src/pages/session/composer/session-revert-dock.tsx": "3f2d030126a4c673731bac65406fd48797464ddc515444b891e39b0eb538e002", + "packages/app/src/pages/session/composer/todo-panel-motion.stories.tsx": "70a8267c725af320d78d266b85fd0ff005d067c4d09a21bef824126602e831e7", "packages/app/src/pages/session/helpers.test.ts": "d9e2ba5625a2cdee7610d2f70b31da4972fd77e989db824d3f76b474691d9beb", "packages/app/src/pages/session/helpers.ts": "ac1ee7592c8aa77b3b5026950638c07e1404531d2cb92c64e622d92dca6ee18b", "packages/app/src/pages/session/session-panel-width.test.ts": "2b9daf379be0b54142dd791bed6bae6d215b2574176b3b3e4b1273db3baf433c", "packages/app/src/pages/session/session-panel-width.ts": "8723cb2f980972ea9bf182240fdf154d131bf40e22fe563fd538a7005cff9192", "packages/app/src/pages/session/session-side-panel-structure.test.ts": "c138fe905498c8326f459dccba61b146af6dfb12b78480303723b5a85046931a", - "packages/app/src/pages/session/session-side-panel.tsx": "d38bdee95cb8efd00f3350b503c701551cc050e2b8a9ca1f7379a0275a87a8cf", + "packages/app/src/pages/session/session-side-panel.tsx": "c2b37ba22cadf4a2c120041727b9bd64507a51786560910bbea2345e6138d07d", + "packages/app/src/pages/session/terminal-panel-v2.tsx": "68dad9307f1d2abf3ff9248e451bd08005acf01ddc46b30a6d01b58f4a90dce0", "packages/app/src/pages/session/timeline/last-prompt-bubble.test.ts": "83753b3ec7227570523c2e0b9e82401fae7a20f3a23980bb40deaeb13304327b", - "packages/app/src/pages/session/timeline/message-timeline.tsx": "b4cbbaaa0b51358efa4924374a7c1bbf16c4e827641252eea5bb76d260b5a7a1", + "packages/app/src/pages/session/timeline/message-timeline.tsx": "89e3d2777d8290d2a32fb44fda68bd4af19517acd45291fc65c916a24e33029b", "packages/app/src/pages/session/use-amicode-commands.test.ts": "65671d054c404edd2b799b0f6591a9c055e6ecb454a674fa0bd9a7fa6f68e111", "packages/app/src/pages/session/use-amicode-commands.tsx": "88bfdad9ae6dfd920b8e6a14ffebd2cf9e52b8f67bb9284cb358ad940643ca67", "packages/app/src/pages/session/use-session-commands.tsx": "7af9a112ae60415572ab7c9b5d4e73b2f8ed9115858c695be675ffdf07c3df2b", @@ -832,6 +1019,7 @@ "packages/app/src/pages/session/v2/accumulate-diffs.ts": "f10a1e097a86f746f3fc2b861454f3b8f509e6895c54b7e3bd0317dbf3d14acf", "packages/app/src/pages/session/v2/review-panel-v2-state.test.ts": "ac0cf1939c164948d9a0c54cc15076b30413612d872237e0fbd0912804c1ced0", "packages/app/src/pages/session/v2/review-panel-v2-state.ts": "e27c3aa236d2862e3cbd87128c4e03e6d810974faaaadb12a7e04cdd3b531e1c", + "packages/app/src/theme-preload.test.ts": "d9e4e96dd39a3491493637682611bedf6ddf4b6e4dbdddcb60bf006211b137a1", "packages/app/src/utils/amicode-bridge.ts": "5104a04c7822f80dc0c564619e7dd2d850ba50035dbc86ea875604ecd97df05f", "packages/app/src/utils/amicode-bug-report.test.ts": "b1bbf49230ff3aa6d1a43ffa9165ba5a84ca4ee18a6d49f52ee5cd887c3e6444", "packages/app/src/utils/amicode-bug-report.ts": "d55d9cecfe17719c0d4a0ca498345c7eff6fca1de6b645e4bc3fecb40f2f4645", @@ -850,16 +1038,22 @@ "packages/app/src/utils/start-prompt.ts": "809d8ad0ab6de76a97503f8368ade25f771431be3f92eb8f039a2c65244ab20f", "packages/app/src/utils/web-zoom.test.ts": "f373618138add504faa44dd6d2c9e3489b6e8dd0093ac6bb4f00f55307864e3d", "packages/app/src/utils/web-zoom.ts": "014f99ce01219cac2bf22f30f20936d271aef17870e710250f6b1aa459317e44", - "packages/app/src/utils/webview-context-menu.ts": "6a8620ffa52ca745295cb9f9e23364505789c3424c2d91afdf3f5b9621ad812d", + "packages/app/src/utils/webview-context-menu.ts": "876b532fc9c00156f722d6424c28476f34b2db1bd0bc3cff3c9866152600e7c3", + "packages/app/src/wsl/dialog-add-server.tsx": "a80a19cfc3590539026a11846d56e0d3cf7eb4ed7360dedab71412da58827247", + "packages/app/src/wsl/dialog-add-wsl-server.css": "a9c2f13e8823fe51989bec945ed1e36274eb0ad363f55cd91714fc91ca4d91de", "packages/app/src/wsl/settings-model.test.ts": "3304797c2779ca573f44eb25b758b67d66b2d7897432252375606680021390c3", "packages/app/src/wsl/settings-model.ts": "35481fe3029714becf30b07024e0f849438c99c369a41b437b5208e48095cbf4", + "packages/app/src/wsl/settings.tsx": "1d6bb448f5f7e574ddd2efb2fdb0b6cdafb7a2ca4af9bdd210f2f58da9d9b984", "packages/core/schema.json": "cf1dc9d2ffd2383d473d96e7054258df0f719bb3a727f025cebf14b4d2c1e431", "packages/core/src/config.ts": "b71c5920982666df2668de8aa04df85b245ddf95532c0c3df8fb6fb985402704", "packages/core/src/database/database.ts": "9406173aa6cac6acd1c3ec306ae71942e12c8e2d68b7cd3d58a9a094539bc32f", - "packages/core/src/database/migration.gen.ts": "26804530f487a84d3d7abbb5381f4202ad726624415a4601925b302124e41743", + "packages/core/src/database/migration.gen.ts": "ad7544dcd87b40e9caef76095d2e8f67df86f4379d92c10fc90cfdf67244bcf4", "packages/core/src/database/migration/20260813162312_shocking_karnak.ts": "f4e901f79e3f660b3809e336599a5ba7da7794ff595e6accf6218739c046ab68", + "packages/core/src/database/migration/20260820000001_add_session_directories.ts": "cb4b6359b26774e741c30da9d40f403d57d2a59572e4989673c262c653d6a5c0", "packages/core/src/database/schema.gen.ts": "723e8c027dc573e86cf8a27795672de865befe0b00ee4165cb526e84b962873d", "packages/core/src/flag/flag.ts": "55ffe731e35f8e045934463529e63bcd1e5f81df9fd69ebe585af85635ca72b0", + "packages/core/src/location-mutation.ts": "5fa852c48e98cec513346f848b422c2d5d6d4b753b39da851c73be073a03511a", + "packages/core/src/location.ts": "16325cfecf6c83bd4e653a0f1313d78172a31fb93cd28672f180053ae4719aef", "packages/core/src/models-dev.ts": "dbbbe33f470e219d5fdaa6ec8e4998be95356e80756f29c18e3656562523923c", "packages/core/src/observability/otlp.ts": "be20b97f84b2a5b4a1663fa3bed0b31c6c387950f7c28b76fbaa8525cd24d6e8", "packages/core/src/permission.ts": "8c46b08adc9edd6bbfb90481b592ea6f00c40743d1ea0ee6a8b2e185dfd2de66", @@ -868,58 +1062,74 @@ "packages/core/src/provider-permission.ts": "a672c1f40fa2efb0cd8923b0b7827b6debe770308bcaef692ad8589ba9b40685", "packages/core/src/session.ts": "9b0942acb81373bd177ec544eaba74815369c0be55ad35c76a48b7676f2ce315", "packages/core/src/session/runner/llm.ts": "e1c9da5f568cab64ebd49a467bff8e11a685306240b3faf672127663f1b02444", + "packages/core/src/session/sql.ts": "30267b6bd6f291051942cc1cc870267c073d6866f5dfd984fd7e9777e4c38c0e", + "packages/core/src/system-context/builtins.ts": "c5cba75156723c9f8e670c9ab578e95d0be6f81808000d6f0b854eb883a7c1a3", "packages/core/src/tool/bash.ts": "4b56de5f3f7ad3ddbcf1ba756caff49f0a65f0fbc73811cbfc312ea716e0e447", "packages/core/src/tool/edit.ts": "b1bd6df76da2c04681aef1c0d6e90e16e929b2c1ed09f6367a2bb9c655dc1d68", "packages/core/src/tool/glob.ts": "3da75d862c37d35aafadf6d54b5d7ef3a61eefae049ac8c3ffe9cadfec818006", "packages/core/src/tool/grep.ts": "db323a91b27727abcee372bf5403f322458ce456c9b581d4f905ab241bea6dd6", - "packages/core/src/tool/question.ts": "b4e114de69285147257a4be20d2ba54b17a42a5cb2daa178fea9fa4b8989887d", + "packages/core/src/tool/question.ts": "9b32d02d3d8e18adaeb86a2b344fff0a21fff1955fcacf91b4c79f93c93fd28f", "packages/core/src/tool/read.ts": "ceb37f787cc78a9973dec7c0f8c5fd647baeac979619741d0ff15167e13c6f29", "packages/core/src/tool/webfetch.ts": "904846f51dbee2579a36c4856efbfe1442d4ce6a31d6a280cc387c390fd22bef", "packages/core/src/tool/websearch.ts": "bd0502eeb9d6deeea975a21a592040b5b48abd6f2613e39dd6cfbd1a959c950a", "packages/core/src/tool/write.ts": "4759d89e4b6cd5fa7d7de4ba091d2bf859d830cb15038828a15751c80fd9b0ec", "packages/core/src/v1/session.ts": "53714b7754a3adecbed4e28d511f5c1ef97ae2fa379e3a07cc68714e1de8469e", - "packages/core/test/tool-question.test.ts": "e73b6d0aecdba94580e7d63e2da65cf17b475c81a66998d5225980cbb849e895", + "packages/core/test/fixture/location.ts": "8e93d87c20305911dc4583ab1717766b1f70eed3e687dac040ad2c646c25ceea", + "packages/core/test/location-mutation.test.ts": "2c4c8faa220cc8fabfe12d57bd74ec8dd8aac8485d0ecafd2761cdee1806d6b0", + "packages/core/test/system-context/builtins.test.ts": "2331a35ab10b3e69f5f5d2dd462ec56000246a747bccec77db783144a91ebbea", + "packages/core/test/tool-question.test.ts": "4df41393f15104a1d3a9c3d06e30cef515108490b41fcf4d9a7d9399fd4ed945", "packages/schema/src/index.ts": "007e8bc04cc91c2c0aba3a78212c47c4fed6a8edd0d4be05286667d303448d11", + "packages/schema/src/location.ts": "078649bbc39b8e3413311b5af85392437873597d8954494016943cfb13564805", "packages/schema/src/prompt.ts": "eb3b42b98ef856f85f32154482b19de546272f1eabe552ee9207db030346291b", "packages/schema/src/provider-permission.ts": "382d3b8c8e7bac64816d6a8cb0a84dff6e9a4bd1a921a653ebb20b23d4580e14", - "packages/schema/src/question.ts": "b334a3e7cea88e73ea11fef5bcdcbe258f57b586ef96142b760294faa6e4f0e3", - "packages/schema/src/v1/question.ts": "bee4686812c9492c39e4df34cbada59e63dc48240823d55fec61d6998dade5c7", + "packages/schema/src/question.ts": "538b73b25f8993700d7836b915ff27a1aff63cb6a6ef42e77b90bfbef83a5558", + "packages/schema/src/v1/question.ts": "3ba37742dbd5ef322b7edb42fea0dacf43a1322747dfe997af909bf564bffd35", "packages/schema/src/v1/session.ts": "5804deda9cfcf2a015c759a6905b5bdfac74897239994c7facb246a60dbcf285", "packages/schema/test/question.test.ts": "511f4fabf2841ed89e1fed9594f2e3a1be8eea5bcb50b680d762c49d6ef572d9", "packages/sdk/js/src/v2/gen/sdk.gen.ts": "57597d710845cc0004ba778522c9bdf088fe9f7c23d7d9a3d947038a1fdc2674", - "packages/sdk/js/src/v2/gen/types.gen.ts": "394f4238a9284d9b250bd95104e5c2882a68b51b7c8838c164e5293d1bff52e1", + "packages/sdk/js/src/v2/gen/types.gen.ts": "40460004bb971a4f8dcc5dcd9f9643cc839f5d75b6820a103ecf7cc21235d9b6", "packages/session-ui/package.json": "d0cd55d4641253e9715551a6e6f493ce692f641c51f6adeb19c8c896c336f2b8", + "packages/session-ui/src/components/basic-tool.css": "0bcf0592ce8af0d902bd69da26ecad2f00f5a7890275392abbffe6f4d80c7853", + "packages/session-ui/src/components/basic-tool.stories.tsx": "059c4bdce6a9ddc93c69949f5baf7323631ef0d5e69f04030fde0f2771a9fc09", + "packages/session-ui/src/components/basic-tool.tsx": "ff2678cc17510e1b39657469f6dd1a0311843664cfd5161d385256e617b4e759", "packages/session-ui/src/components/group-parts.test.ts": "a31ce19009e1d62e75e83b682190a4613da0eed334420409ed73a9e21834ae84", + "packages/session-ui/src/components/line-comment-styles.ts": "2077836c13bd3c3f1c39c67722119e448a2dfa123377f94bfd3676634e955d00", "packages/session-ui/src/components/markdown-file-refs.test.ts": "e2b1bed33629d3ab53c521f33266814088ea4122b6e64d6ab294eacc4cf15070", "packages/session-ui/src/components/markdown-file-refs.ts": "b75bddeb565dc0163635fab8f84bdd26723742f9f82d06b6fd5991777b311032", "packages/session-ui/src/components/markdown-stream.test.ts": "b73b1a15ffa2cfea5358b2d6313ce17d26a5a11302b0e3b7d823cde0c98cbc83", "packages/session-ui/src/components/markdown-stream.ts": "50e38abd5d4ff919564655799678be10463dec58502fc5487c1c97a9a8db5399", - "packages/session-ui/src/components/markdown.css": "31f40458ea34a594112b2b148c8d49d0677e3f3dfe03b36ba1bcb6ab02c4151a", + "packages/session-ui/src/components/markdown.css": "24e7c19218620386647656a5d27e116cdeca911f1916c5d5b8609edca11ff165", "packages/session-ui/src/components/markdown.tsx": "89e132e62d20c873311c9781d0ca4d7cd1a7ff882d99f19c8696aafaf4a8f298", "packages/session-ui/src/components/message-part-css.test.ts": "ab31eb47306e0826955a4471f18b263a77cd6f77f0eace7a46085645b2600c86", "packages/session-ui/src/components/message-part-groups.ts": "37be069bf46e926fcde83d3744ee706911ff002e6b532e279290d841aeee96da", "packages/session-ui/src/components/message-part-skill.test.ts": "453bdcb25e4df91ad1004635b8766070a60421355adf18ee53f43cd2fa7d0486", "packages/session-ui/src/components/message-part-skill.ts": "d65a15dc39b8c0dece13aebe5e308426ae2dd758b07740251525ba9257654cf7", - "packages/session-ui/src/components/message-part.css": "74cbc0af6b451c846c95974f4cb23fe1d5d9c3deaff1f7ff886274c257a1b491", - "packages/session-ui/src/components/message-part.tsx": "62629470594ab6270267cb1d8ca48d43c1166d4f904f0cb8802cf48c27ae73ac", + "packages/session-ui/src/components/message-part.css": "a5b289e6fe3f08870095676489a84133c48c35a55955fb6c0dbd0806cc2d2be8", + "packages/session-ui/src/components/message-part.tsx": "bd8105bcca5a5c936ad5a178911b248724fd13fe0d9467295457506c355709b6", "packages/session-ui/src/components/session-retry.tsx": "2cb1513d73275a269894f601e8c1d29bc790b14927134fe36177f2b371888721", + "packages/session-ui/src/components/session-review.css": "ec8c5b359dd4b7ff80d7fad8906359678adade9ce62c9d74aedc000b646db341", "packages/session-ui/src/components/session-turn.css": "792915f3373134eadf3cd51e1d4bfa88ec347e6b037b6caf60b639ec5e74fbd8", + "packages/session-ui/src/components/session-turn.tsx": "6f7d833a709cfb6229166521b208912cfbdf399193b7652aa094196ed8664b8f", "packages/session-ui/src/components/tool-error-card.tsx": "e27b89836b7c709120ac15fbffd2200038944058ea3506e61e5897ffb3537503", "packages/session-ui/src/components/tool-status-title.tsx": "ae4561b58f55985d2294a352802e723b18943ac9a6551d97f1c4a6cc95368c8a", "packages/session-ui/src/util/clipboard.ts": "c37ba53914714ed04135e5a254e8ea90186f097d7470fa0873a83de82f3fef2d", + "packages/session-ui/src/v2/components/attachment-card-v2.css": "3b4b9c23d69e76b35488bc3a65fb51211038c3e8aac015df3f531b4e97e96359", + "packages/session-ui/src/v2/components/basic-tool-v2.css": "55d3aa179704deddda7149342a951229ff5561127f786dd551e5df9aea8a7835", + "packages/session-ui/src/v2/components/basic-tool-v2.tsx": "ece46d761f7d51a7a0925b3df00a5c289d891e9a514ee721016eb723d87fb406", "packages/session-ui/src/v2/components/prompt-input/attachments.ts": "ff66078f2edc9a67a4df0d7dfd76a49be5eab27ac332b303f3412721ef7268ea", - "packages/session-ui/src/v2/components/prompt-input/index.tsx": "3d1d7f1b435d63aa0bb4fcfe5ad3eb2e7d386d5c5c1974f3a15161b15ea9617e", + "packages/session-ui/src/v2/components/prompt-input/index.tsx": "1f2f52954c53bb764408ae477969328a7c826d0fedd4913ba05e040912296b77", "packages/session-ui/src/v2/components/session-review-file-preview-v2.tsx": "2aa16fef06bf6de8d5294a85ebd5fbbdfff3879f00f75e900d8049e33605593a", - "packages/session-ui/src/v2/components/session-review-v2.css": "166b71562d1bd7ae3c6a38dca854704fd903be29cc7088e2396e06c66070bbd5", + "packages/session-ui/src/v2/components/session-review-v2.css": "12a2465355aa0fb8096a625d2d566a201b4c22720f5f4f556fc6cbace58109d4", "packages/session-ui/src/v2/components/session-review-v2.tsx": "a683eb509c3ee927a7ed04ceffef3fd65129f812ac55f5b04c7e628bf0611831", + "packages/session-ui/src/v2/components/tool-error-card-v2.css": "9eb0bf8b583a18159f3855465118ff6a853d0193ea783037d5c90a868fdb3ff1", "packages/ui/package.json": "b1d168d0371e9094faae1107fc6c00be197f09bc69daa2247a3890d607f4b629", "packages/ui/src/amicode/amico-presence.css": "b3f345b607e318dcfbefcdc677fe21cc7d387b9946b0f9d625b0a842fe7e31b9", - "packages/ui/src/amicode/amico-presence.stories.tsx": "33c854edd9a061d79c53e160ce5685ddb8cd41f4e205c6eb1b0dc96fec5017bb", + "packages/ui/src/amicode/amico-presence.stories.tsx": "6012506c946a18460f8a6105c5860a050e47e7708818b0408b516370f50b7094", "packages/ui/src/amicode/amico-presence.test.ts": "c7ea8c840004d06e231a7368b3c10e11b7dd7efcbcaca4867b0f30167bd2e2bb", "packages/ui/src/amicode/amico-presence.ts": "b4708117445d3fa5cf9be8c9b2f51bfb1f6b01a76e2fac114cde93107a075e26", - "packages/ui/src/amicode/amico-wave.stories.tsx": "ae2885a0294c2ebeec5850386028b939ea4211289b3265bbf495b2a7d879e2a9", + "packages/ui/src/amicode/amico-wave.stories.tsx": "fed064c5202bd772308e2c6165c9d63182661fe6ce590c75cac1fd0e9deadf94", "packages/ui/src/amicode/amico-wave.tsx": "478cd8b19a87679016d9dbb0aa494ba7d1f1ef67b3da1d09fb35d28e33de9017", - "packages/ui/src/amicode/amicode.css": "6a4be28bc521ab82d1d3800f326f372b41a85bfdaf9d71402a0b02ef8f7ddd1f", + "packages/ui/src/amicode/amicode.css": "92e902d7221464fa178644ad0eff67beab6f1c7c6cccd5b802d9a0ffbf92b729", "packages/ui/src/amicode/approval-bridge.test.ts": "5b5e5a41c827b7a3cdaf455dbf488cfa7b8d339f46e64f5a35a0ba9226eb0ebf", "packages/ui/src/amicode/approval-bridge.ts": "ceaa6024916794ece1bc80a004b79c36406d2bc512f82e003c25d919c9050e48", "packages/ui/src/amicode/approval-card.tsx": "e537eb262843aaa80e7c53be63a3aece4f83e367907cd22a26f0ff6c7674dbec", @@ -931,14 +1141,14 @@ "packages/ui/src/amicode/ask.ts": "5081435a8b09b2eba12240a9914bbe222303460bd21044bbf8482bc190ff2efe", "packages/ui/src/amicode/brain-data.ts": "f23483cda811e148748d4f47435c6d01c03d52e9a65ad74cae2c0ea30ce0b8dc", "packages/ui/src/amicode/brain-engine.test.ts": "3b244fbe15ac87529655acd688da9181af53a1dfeb3f9f87e5f77000db3aada6", - "packages/ui/src/amicode/brain-engine.ts": "0ec23c753167bd702739f5ee18e9ea862f19256841625be54f8aa8008d7bffc2", + "packages/ui/src/amicode/brain-engine.ts": "500b03cd43d4cdbd76a575f4517a4aaac788167e1d41a396d6dc6bce3cdfb8dc", "packages/ui/src/amicode/brain-ref.ts": "9d8aaa8b24fb0b74acc0c4cdafc265c1808c28c95d4ac9b815fd2e54ea89f46d", "packages/ui/src/amicode/bridge.ts": "e7b93a15060a0cb1ab0be2b367f73e926d74b990a8af917a9966b451ee303b5b", "packages/ui/src/amicode/calibration-view.tsx": "bc4193104fea43ac3e5476df0f9a91a55465b48ffffc80e4170047872a141812", "packages/ui/src/amicode/card.stories.tsx": "b1591abb4af426b0eb08588ea1585336a392d755b5d308cb31f17cb65acf5a3e", "packages/ui/src/amicode/card.tsx": "4d236acc454a051f7fed1cd6cac0e98dfa8df9994376d381672b27e75c6feb6f", - "packages/ui/src/amicode/connection-icon.tsx": "59977d3db33a67c39dd82efcaf03548bb83c2639327bf36dc6f3fa43ce5dc986", - "packages/ui/src/amicode/connection-picker.tsx": "d272e89d3a1fc914d194d803f41e723456ce1ce7079cba199d3fd69566236a3c", + "packages/ui/src/amicode/connection-icon.tsx": "2b36c05209a3b5091c6407d59add58fb51ec34724ae6411af85c3dcc797a9987", + "packages/ui/src/amicode/connection-picker.tsx": "12cc6f142ee109580442a278286f06118a2a35239cabeaef6d60c0dac1a7ed3f", "packages/ui/src/amicode/connections-paths.test.ts": "07c86e8e04700cdbd55bff8db342a8400d85114d76bc73fee8205e9162953f2a", "packages/ui/src/amicode/connections-tab.tsx": "06489a47bbd6d482ba4d2dc2cf4e9e8e9c648be774f78cd76e2abc87451116e5", "packages/ui/src/amicode/connections.stories.tsx": "fda54a41c4b46f5cab5278278f92c01d1fea70610f7b8641fa5855be6bd9d486", @@ -947,7 +1157,7 @@ "packages/ui/src/amicode/context-tree-data.test.ts": "bd51dc84d6c14c64f38c8f467ef29d9d8f45dcb77cc83ec92bffc8aaa983b463", "packages/ui/src/amicode/context-tree-data.ts": "d623578b957b9a49d6f7b930a2264b7bf0ea71fabf1a485d9666706c7b77485d", "packages/ui/src/amicode/context-tree-engine.test.ts": "afa3199c18c7ca05a19fdf0663a5b4d6e1fd2303e038cf25641c29e254e610f4", - "packages/ui/src/amicode/context-tree-engine.ts": "41c7bb2b351dec597e1cf8c996e4bf90fb938de3ce4f637ebffd3fe67570985c", + "packages/ui/src/amicode/context-tree-engine.ts": "2177d01855d58c5ee7c164c2ed1dac6f4a8a5cc04daea67566f195b02ed1ca06", "packages/ui/src/amicode/device-view.tsx": "1842053e9e25cd5c8076598449e44abf06008940a8f3f39403cbf435b1b8aa8d", "packages/ui/src/amicode/edit-row.test.ts": "7c68fcf083d3854bfb70768b648110e001843a290dcfdc25869c1a4681445b94", "packages/ui/src/amicode/edit-row.ts": "28d5f39470376968e7eabc3f50807a779177e9c9f2081302e4b5eb15306aef1b", @@ -962,10 +1172,10 @@ "packages/ui/src/amicode/frontier.test.ts": "229eb8e3f187113fcfc73f0a30f2690bab4266cb12c1c6c3802522dd7eb4af9c", "packages/ui/src/amicode/frontier.ts": "78cd89deb26a2c6c4b8a18230012455b7aecd5f23eb007dbd0cd440b6b6ec36c", "packages/ui/src/amicode/getting-started.tsx": "9cdb8df438521d33cf7732fd0ba64e21b4ba9d63de0c823e59d0dcd21ea90a7f", - "packages/ui/src/amicode/home-cards.tsx": "93ce40f0e67bf0d4ef9b8f95abb91d7d566649a6e1b690aa3a431205139c73c8", + "packages/ui/src/amicode/home-cards.tsx": "019069f82e0a29baa392b7609ce0825d0c036ce31112bbd1efd375ce1b077a9c", "packages/ui/src/amicode/institution-lookup.ts": "3edf90b0778ea50b048975cc317a18a1aa82290cd4f5241cf9df74fd17866fe9", "packages/ui/src/amicode/onboarding-wizard.test.ts": "268df538ea99280c5297f9b1dc06a236da2fb82830c2d49a107feca3f610279f", - "packages/ui/src/amicode/onboarding-wizard.tsx": "c1ccf8d936560b2f1d75381e27911310704bd3401dc4e3382ee3d9fbe793f0e0", + "packages/ui/src/amicode/onboarding-wizard.tsx": "75db623f8f50fd875f1bde8942841fbb5167c77a34fe3c6dbfa80732841b53e8", "packages/ui/src/amicode/problem.test.ts": "e7a15928df883fbed424744953a87ad0b5ba8a0c1039dc190ed00bfefe85eeee", "packages/ui/src/amicode/problem.ts": "d40ca9f4f411b112d97c7d1067b5834ff22f763456df17df7fa0ad31768a3a51", "packages/ui/src/amicode/rail-gate.test.ts": "7cf0ff5add76f27eb4022224a0f18532a7bc419dd5368788e7a0667f9913dcae", @@ -978,7 +1188,7 @@ "packages/ui/src/amicode/receipt.ts": "cc5e452b229b29386f474772cd27caf6a83a13862540de9035cff610ef06e929", "packages/ui/src/amicode/run-card.test.ts": "e0b3a7c94bbdddfa1cdcc81898404bad17319862f3c3a9f47255d17db1fe75fa", "packages/ui/src/amicode/run-card.tsx": "b074e95fd8fe8b1907d1527fa88ff85b5dd6ec2ffaa30f749969f9130e83e592", - "packages/ui/src/amicode/run-gallery.tsx": "4a62e71e5ab69f28ac74eb9cbf38434ca9b3c922a8c5250c6c35ebdb15f45dde", + "packages/ui/src/amicode/run-gallery.tsx": "80100e808f9e8e46bfca8daf927b1e149fafcb757cc2ab2ee5fdec3339625b9b", "packages/ui/src/amicode/run-plot.test.ts": "64ac70e7ff5784ef6c1c224136df2a4e8a43b4187d3ac0fc4382cb0464eb2953", "packages/ui/src/amicode/run-plot.ts": "b4bc50f279cb864f7720e56ba69b993c7a4e599a71047162173b1b93a63598ac", "packages/ui/src/amicode/run-series.test.ts": "2e7aec7cdf7eb056efbe52d180ff316482e3010e2442611daaf4bda444c82705", @@ -987,8 +1197,10 @@ "packages/ui/src/amicode/run-window.tsx": "748fee9b14152f0f173238acacf9883aefab7f36038b897efe6b007307916e17", "packages/ui/src/amicode/shell-row.test.ts": "48ec6f0786edf81f163e350904bc2a93b1a55375b8115e688237ce8c2f1a7e64", "packages/ui/src/amicode/shell-row.ts": "b39dc05d2b77ba6d44456248782b31f6219c891d56700203186a65ccb48319ad", - "packages/ui/src/amicode/solver-toggle.test.ts": "9aa734ab57f4f6f5d31b6c18b8d41bf3677f4fe1d13828ca555c08cc15146b25", - "packages/ui/src/amicode/solver-toggle.tsx": "0250d887e448685307bdf3cc27a5a0f803797f045fb29f5d10d9ededd6b2fff1", + "packages/ui/src/amicode/solver-switch.test.ts": "c9cc10d657d7adf0c388e56cb6058affed5a255bdeb9b8f2fea5c02f4daff1fe", + "packages/ui/src/amicode/solver-switch.ts": "041c2ad3cfa63770cdc09ee5fb2cda0c7d09c55e7e53c83927822476716c6b92", + "packages/ui/src/amicode/solver-toggle.test.ts": "2c5ede8eff8a60f1b5e6bb4423545438ebed6d5d60306d5bdc6f557144ede5e6", + "packages/ui/src/amicode/solver-toggle.tsx": "2d3f05bed264526b439e53be9897862d1dc1098d37d530bd26606ae6da804ddc", "packages/ui/src/amicode/spinner.tsx": "f76ae9b6a3945017b854d35ea0e6b2f4a08ca7f01e0f77236dbe1ba33d244fd7", "packages/ui/src/amicode/splash.tsx": "4d5485f48065d2c88a3ce41cc1abd81d5b58338dfd4ae240b1c211444fc689aa", "packages/ui/src/amicode/stage.test.ts": "b1a82d5ddf216f6e6ce09b42d0850cea3622792ac33bac5b2eaf64d850372a37", @@ -1019,8 +1231,8 @@ "packages/ui/src/amicode/widget-preview.ts": "21fa605139b96f45675b11a73fa0e8e974be3444feed0f024b8003e96e44927b", "packages/ui/src/amicode/widget-schema.test.ts": "b24049aeeda3fd2a922b5ce6601d99df9c8d3aa44b3e69dc8510a13120c7353a", "packages/ui/src/amicode/widget-schema.ts": "e8a0efa3c86ccfae34d2a6feead87b556a2030c1a4e1bbd4b7c5839e116376a6", - "packages/ui/src/amicode/widget-tokens.test.ts": "acf02ce091e4f52597eb89eb3c28643c838632bbc56c73d8051d310568849b6d", - "packages/ui/src/amicode/widget-tokens.ts": "aecb4735d5c8442c95acf9549199649d02dc47eab67413f7037b3f441604df86", + "packages/ui/src/amicode/widget-tokens.test.ts": "426064638b322a421f794bedf5dbf41ad75fc30b3da6e2d02dcf6ed2f85d5f45", + "packages/ui/src/amicode/widget-tokens.ts": "397edcfa18882b2b6b6abef0a8783d72323f793e974d0bd440ca240a10b2f737", "packages/ui/src/assets/favicon/amico.svg": "7c64b145bdd063bacd53318336a423f0654ec702de2b4e0c4a4a384694052804", "packages/ui/src/assets/favicon/site.webmanifest": "beee758dd8811f9c76c27cbfa3801f92fc2eff537068b51c555aca4d90804550", "packages/ui/src/components/amico-spinner.tsx": "75114d38aac924a37fee563e35375ebfd5e96cf3771690d01ad484f91df1c563", @@ -1043,38 +1255,63 @@ "packages/ui/src/components/amicode-run-gallery.tsx": "366919dc8b1fc48d4aeb454bc8f92e5e23621a4570c9084a2889c88952d6f692", "packages/ui/src/components/amicode-run-window.tsx": "5e5c623adc5d4e3fd35740e9b9cb5bae77e6f3f694850c5828f06a2616b4181f", "packages/ui/src/components/amicode-shell-row.tsx": "0ce6525dd2cb8c1b04c7779ae477417a7776ec83768b8b028d52817e734587a9", - "packages/ui/src/components/amicode-solver-toggle.tsx": "5c5d2a8ba8201439f490ac27bf40a381ce1f0c82c924de253c3f042d05650a08", + "packages/ui/src/components/amicode-solver-switch.tsx": "ea1e5d187fcbd505046af8c2bdcddeb7b30dd98a7d2ee9a4775e25be2c581270", + "packages/ui/src/components/amicode-solver-toggle.tsx": "b09c870bab880bfdf2557fb870fb6af13a065f90353f59d609f37a6054db858d", "packages/ui/src/components/amicode-splash.tsx": "0f69ebdd0e4e6595f38b1492c31a28bd1320706c7f1e26411ec4d25d1ab2921f", "packages/ui/src/components/amicode-thinking.tsx": "85ef2a86f8ebbaa79596ee3783cf5796adcb8160d4680b557db99001428ebd48", "packages/ui/src/components/amicode-vaults-tab.tsx": "9ad6262db994e2ecc7d7068c95f4a31c6fbb178c531364b0dc3c8fec427a9a0c", "packages/ui/src/components/amicode-widget-grid.tsx": "df589de093bca703e9fdbbffc6a13020e96ee0ef43e545718f91277ec0b7cd24", "packages/ui/src/components/brain-engine.tsx": "955723ad42c6abfb785313f62277143142f1248987c45da30eadaf28d5137072", "packages/ui/src/components/brain-ref.tsx": "c97a0ac0dad470c9b9e194d6ba94d1f1cfbb774bdbea3f798bece4a2a9968a60", + "packages/ui/src/components/context-menu.css": "7c701902c852f205a178ded4fe8c6181ba43cb4d1c42c6ad9a39ae368738aa1a", + "packages/ui/src/components/context-menu.stories.tsx": "0542961bd9db8e191e122b901519452217a410ea5339e8d3ee09a73258ba59a9", "packages/ui/src/components/context-tree-data.tsx": "ca261e7a5eeed09130af14bb755fc2258833d2ac9efd995418cb32927de05d86", "packages/ui/src/components/context-tree-engine.tsx": "92ca28c968cb0c1c346c1693163abd4e604287f875b1d53a434966f1c48cef97", "packages/ui/src/components/dialog.css": "f66f34aa3488b53e2ce1ad19d697a31c3f7cead0307adabaede0d710b17c7401", "packages/ui/src/components/dock-surface.css": "bc8945d4e19eb122078f98e5700e0b542e9e80901782ec927340a048fa0d3be1", + "packages/ui/src/components/dropdown-menu.css": "35d1e136a03fb3b59fd5801645de2870c20ecae6810488f69f01595b6abf5a3c", "packages/ui/src/components/favicon.tsx": "809fd1d2621b0e28e69f8ac8092405b89a11fb7a6dbad2938121f7c0f2f5938d", + "packages/ui/src/components/hover-card.css": "b6cf61d45ba88cc4252923fb6fb4643659410596eaf3f7456ee5a856dd22dc8f", "packages/ui/src/components/icon.tsx": "0b78b15fcb531e989ac9d4e20bd36c0e14500bfb21358da85974343f26edd281", + "packages/ui/src/components/image-preview.css": "fdb49ad0314cc89d95be7d905d9d1d50628e25cd07d536104c8b3d0ef5132beb", + "packages/ui/src/components/list.stories.tsx": "ba1302be7924b850a1ba2e8e7f3247fb10fa9a5e5f433164e56cbd1a457ab938", "packages/ui/src/components/logo.css": "f788db461b78bbe33ca2df8d8ff92104fe5c569c7feae124b43c24a5e7e9ab86", "packages/ui/src/components/logo.tsx": "de12f1af573c8bdee867a4c4c2ac40ba0064b5ffb0f2a0c72f89aea6446ebc63", + "packages/ui/src/components/popover.css": "cf73a22ed5fb53e6ce235126e0e7e877e611a9da87a0c982df5a8959729b82bc", + "packages/ui/src/components/resize-handle.stories.tsx": "c5624be901cbeb1c4658e65d22dae5659b2b2d4f53c93e619a8ac4d2c6611c55", + "packages/ui/src/components/select.css": "487d276d8398b94650505954f99393f8d7d2630510931206d4aee8b35131e5cc", + "packages/ui/src/components/tabs.css": "57d12ea6b52fb499ba39e6e1ae5a325fbc20b9d218b2a88fdd907e5ecf3871a9", "packages/ui/src/components/text-field.tsx": "5ad1870d4858cc582a0cc7bc0fd1c5332b5e5c96901131639d7bc8f2a38350cf", + "packages/ui/src/components/text-reveal.stories.tsx": "393d45b149299dacac37e688dfd8f0bb2eef0294aacb700f50a9416ac6fcaaa9", + "packages/ui/src/components/text-strikethrough.stories.tsx": "5117a3b4ed59c18063720f47affcc52f210dcf769934b24ede0ef14a2cbc5c43", + "packages/ui/src/components/thinking-heading.stories.tsx": "53e63e5106f8f2dad93c5b44e0f22d2a27174a153a0666016f333fa97c2f8a92", "packages/ui/src/components/thinking-line.tsx": "956e483aa21029161c26830544cbc2df23fb20a3f342d1618675b6f238d87709", + "packages/ui/src/components/toast.css": "60c06eda9f0ccc5641c03c002a6080db99c00d797dbdf7511c789089efc1c546", + "packages/ui/src/components/tooltip.css": "a615df9b86e64c1565384baa8054c2f80ebad11856d33f5e6f53902541eacef2", "packages/ui/src/context/dialog.tsx": "876f8a5dc26ebb98f1f5e9e528e7d0bd7aa79c0a107b62027dd9fe78ed2cc620", "packages/ui/src/context/marked-math.test.ts": "0fe460c09956335a469b382ff631809381875465cbcaf7e7c599f86240b0227c", "packages/ui/src/context/marked.tsx": "b4f693cba3f8020596f860a00293e43855ec75d0bd214e379e36250d02e2be4c", "packages/ui/src/i18n/en.ts": "42bf7f6816f016d4c201475a41edf1e448bf500aa6f50cb50824ae70cdf8d0e8", "packages/ui/src/solid-dnd.d.ts": "f1faf22b0773252852ff0865f68a00fa479777f5d0b4602ea4d1660c366793ff", - "packages/ui/src/styles/index.css": "ddafc16fcf2facc68094050e3dba52507016ef148e10d6d7ff5a10a6e8e8b394", - "packages/ui/src/styles/theme.css": "6fca3fb6b91255410f91897a4dcba6a3a91d8387809f36991922896a02e4748e", - "packages/ui/src/theme/context.tsx": "f7488be52021215f12b0cf32d85b869285fb7a878e9d3985082cc8154806395e", + "packages/ui/src/styles/index.css": "2eae4e3d82e992c3d99f4a91ff25b3d6e9e514b38e9bebd232e6c41009cdc1fd", + "packages/ui/src/styles/theme.css": "0dc88bf675df5cdf08cfe0cc53c11eaf030a1bc0cf8b36cbeb3977367c2b362b", + "packages/ui/src/theme/context.tsx": "93b0b3f8a258bc8a33d4b6a4cda7016e91f6d999bd32c60a43aaaf45eadd1227", + "packages/ui/src/theme/default-themes.ts": "878f3e9cb45803dfa740890ea2240e246b05833f2d7f1345c67909c1000d8761", + "packages/ui/src/theme/themes/harmoniqs.json": "36c3570b07ea7f05fe9f6a550bac4779ae9f1d66cdbba7954fc1cf6dd81b9af1", "packages/ui/src/theme/themes/oc-2.json": "539a59886cbee9f2e54362075486d47ac69884ef8a9282f767f7cfd3f1f598a8", "packages/ui/src/util/clipboard.ts": "218d321e1b0567b76514057ef22cfd23cb843fefc36bed7aab91f072e3233ad0", + "packages/ui/src/v2/components/badge-v2.css": "32135a9df5bad856a10138e00a2cb3ede7a6fc0cc2398797ad6bed9335d1f845", "packages/ui/src/v2/components/dialog-v2.css": "ae6f7b1de755929ba529b9dbb0e837c80f620f00a77188b9d2c59f10b0f28bbd", "packages/ui/src/v2/components/icon-button-v2.tsx": "966e535e3efe503f054045be4476fc20d2fad36092410b52f75e4cf8de6fce6f", "packages/ui/src/v2/components/icon.tsx": "2a5105e6f5502f511658fa3abd83b1c7ef5047ac67d5520fbf4836ccc47bad19", + "packages/ui/src/v2/components/menu-v2.css": "2f262a6c456a8e3cb9027f0b8b3bb3c4327794bd80cadd3cf8c16d08e6125d4d", + "packages/ui/src/v2/components/menu-v2.stories.tsx": "4ed1498d7e4cd51ce9c7459061f3540efdfae71751ff618901c78ee413951559", + "packages/ui/src/v2/components/radio-v2.css": "bf6eb3457a7fc13e48356f5a7dcf3cbf462382580250a3144a745402ff88e2e6", + "packages/ui/src/v2/components/select-v2.css": "4dd4104bee8f661c30ad08ec806e5bddec21d75be6a5d09b693dd297ea10a24c", "packages/ui/src/v2/components/switch-v2.css": "96406f51a412f931c99c005ecd6a889898ad80160143cca0a81cacfa13714f8f", - "packages/ui/src/v2/components/tooltip-v2.css": "613cbe940ad99dfba4c8bf9312347f2da2625a7e0f9bfefdd0c9cc0a9b4713a8", - "packages/ui/src/v2/components/wordmark-v2.tsx": "6037a68db862ddd67235cbae8dae5a95c9f7403e7c5f268b78fdaaf013950166" + "packages/ui/src/v2/components/toast-v2.css": "9b652d908f39e35cdf1764f1675e4db3dea1d7abf43cb792cfa2d27b699f2723", + "packages/ui/src/v2/components/tooltip-v2.css": "7cbd2523d9f183b7e24bfbc1f523c370abf8e32836f8be90cfdf75e1c70f05a9", + "packages/ui/src/v2/components/wordmark-v2.tsx": "6037a68db862ddd67235cbae8dae5a95c9f7403e7c5f268b78fdaaf013950166", + "packages/ui/src/v2/styles/theme.css": "3b6fc14460c39ed8b48c71f7527ab510282c014fc385ed6a08cae3686fd145bc" } } diff --git a/packages/app-bundle/overlay/packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts b/packages/app-bundle/overlay/packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts new file mode 100644 index 00000000..ccbd7a2b --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts @@ -0,0 +1,111 @@ +import { expect, test } from "@playwright/test" +import { + assistantMessage, + completedAssistantInfo, + messageUpdated, + partUpdated, + reasoningPart, + setupTimeline, + shell, + status, + textPart, + userMessage, +} from "../performance/timeline-stability/fixture" + +for (const expanded of [false, true]) { + test(`preserves shell user intent from a ${expanded ? "expanded" : "collapsed"} default`, async ({ page }) => { + const id = `prt_shell_default_${expanded}` + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistantMessage([shell(id, "completed", lines(3))])], + settings: { shellToolPartsExpanded: expanded }, + }) + const trigger = page.locator(`[data-timeline-part-id="${id}"] [data-slot="collapsible-trigger"]`) + await expect(trigger).toHaveAttribute("aria-expanded", String(expanded)) + await trigger.click() + await expect(trigger).toHaveAttribute("aria-expanded", String(!expanded)) + + await timeline.send(partUpdated(shell(id, "completed", lines(6))), 180) + await timeline.send(partUpdated(textPart(`prt_sibling_${expanded}`, "Sibling content")), 180) + await timeline.send(status("busy"), 100) + await timeline.send(status("idle"), 250) + await expect(trigger).toHaveAttribute("aria-expanded", String(!expanded)) + }) +} + +test("shows and expands a running shell command without shimmering it", async ({ page }) => { + const id = "prt_shell_running_command" + const command = "sleep 10 && echo done" + await setupTimeline(page, { + messages: [userMessage(), assistantMessage([shell(id, "running", "still running", command)], { completed: false })], + settings: { shellToolPartsExpanded: false }, + }) + + const tool = page.locator(`[data-timeline-part-id="${id}"]`) + await expect(tool.locator('[data-slot="basic-tool-tool-title"] [data-pending]')).toHaveAttribute("data-pending", "true") + await expect(tool.locator('[data-component="shell-submessage"]')).toHaveText(command) + await expect(tool.locator('[data-component="shell-submessage"] [data-pending]')).toHaveCount(0) + await tool.locator('[data-slot="collapsible-trigger"]').click() + await expect(tool.locator('[data-slot="collapsible-trigger"]')).toHaveAttribute("aria-expanded", "true") + await expect(tool.locator('[data-slot="bash-pre"]')).toContainText("still running") +}) + +test("transitions thinking and hidden reasoning through busy to idle", async ({ page }) => { + const reasoningID = "prt_reasoning_hidden" + const assistant = assistantMessage([reasoningPart(reasoningID, "## Inspecting stability")], { completed: false }) + const timeline = await setupTimeline(page, { + messages: [userMessage(), assistant], + settings: { showReasoningSummaries: false }, + cpuRate: 4, + }) + await timeline.send(status("busy"), 150) + + await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible() + await expect(page.getByText("Inspecting stability", { exact: true })).toBeVisible() + await expect(page.locator(`[data-timeline-part-id="${reasoningID}"]`)).toHaveCount(0) + await timeline.send(partUpdated(shell("prt_reasoning_shell", "running")), 160) + await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible() + await timeline.send(partUpdated(shell("prt_reasoning_shell", "completed", "done")), 180) + await timeline.send(messageUpdated(completedAssistantInfo(assistant.info)), 100) + await timeline.send(status("idle"), 300) + await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0) + await expect(page.locator(`[data-timeline-part-id="${reasoningID}"]`)).toHaveCount(0) +}) + +test("moves busy through retry and recovery to final idle content", async ({ page }) => { + const assistant = assistantMessage([], { completed: false }) + const timeline = await setupTimeline(page, { + messages: [ + userMessage(undefined, { + summary: { + diffs: [ + { + file: "src/retry.ts", + additions: 1, + deletions: 1, + patch: "@@ -1 +1 @@\n-export const retry = false\n+export const retry = true", + }, + ], + }, + }), + assistant, + ], + }) + await timeline.send(status("busy"), 140) + await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible() + await expect(page.locator('[data-timeline-row="DiffSummary"]')).toHaveCount(0) + await timeline.send(status("retry"), 180) + await expect(page.locator('[data-timeline-row="Retry"]')).toBeVisible() + await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0) + await timeline.send(status("busy", 2), 180) + await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible() + await timeline.send(partUpdated(textPart("prt_recovered", "Recovered response")), 140) + await timeline.send(messageUpdated(completedAssistantInfo(assistant.info)), 100) + await timeline.send(status("idle"), 350) + await expect(page.locator('[data-timeline-row="Retry"]')).toHaveCount(0) + await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0) + await expect(page.locator('[data-timeline-row="DiffSummary"]')).toBeVisible() +}) + +function lines(count: number) { + return Array.from({ length: count }, (_, index) => `line ${index + 1}`).join("\n") +} diff --git a/packages/app-bundle/overlay/packages/app/index.html b/packages/app-bundle/overlay/packages/app/index.html index bb0d1e3b..9e1f3253 100644 --- a/packages/app-bundle/overlay/packages/app/index.html +++ b/packages/app-bundle/overlay/packages/app/index.html @@ -1,5 +1,5 @@ - + - + diff --git a/packages/app-bundle/overlay/packages/app/public/assets/DMSans-Variable.woff2 b/packages/app-bundle/overlay/packages/app/public/assets/DMSans-Variable.woff2 new file mode 100644 index 00000000..9313206b Binary files /dev/null and b/packages/app-bundle/overlay/packages/app/public/assets/DMSans-Variable.woff2 differ diff --git a/packages/app-bundle/overlay/packages/app/public/oc-theme-preload.js b/packages/app-bundle/overlay/packages/app/public/oc-theme-preload.js index 0ab97420..0fe9a1b6 100644 --- a/packages/app-bundle/overlay/packages/app/public/oc-theme-preload.js +++ b/packages/app-bundle/overlay/packages/app/public/oc-theme-preload.js @@ -1,6 +1,17 @@ ;(function () { var key = "opencode-theme-id" - var themeId = localStorage.getItem(key) || "oc-2" + // amicode: the app HARD-PINS the brand theme (lockThemeId in app.tsx), so the + // pin is authoritative here too. Reading localStorage instead would let a + // stale id from a previous build stamp the pre-paint frame with the old + // theme, which then persists visibly until hydration corrects it. + // MUST match lockThemeId in packages/app/src/app.tsx. + var PINNED_THEME_ID = "harmoniqs" + var themeId = PINNED_THEME_ID + if (localStorage.getItem(key) !== themeId) { + localStorage.setItem(key, themeId) + localStorage.removeItem("opencode-theme-css-light") + localStorage.removeItem("opencode-theme-css-dark") + } if (themeId === "oc-1") { themeId = "oc-2" @@ -26,13 +37,16 @@ document.documentElement.dataset.theme = themeId document.documentElement.dataset.colorScheme = mode - document.documentElement.style.backgroundColor = isDark ? "#080808" : "#fafafa" + // Brand ground, tracking harmoniqs.json palette.neutral (dark #000, light #fff). + // This paints before any stylesheet, so a stock literal here shows through as + // the old brand for the first frame. + document.documentElement.style.backgroundColor = isDark ? "#0F0F0D" : "#ffffff" // Update theme-color meta tag to match app color scheme var metas = document.querySelectorAll("meta[name='theme-color']") - if (metas.length > 0) metas[0].setAttribute("content", isDark ? "#080808" : "#fafafa") + if (metas.length > 0) metas[0].setAttribute("content", isDark ? "#0F0F0D" : "#ffffff") - if (themeId === "oc-2") return + if (themeId === "oc-2") return // stock theme needs no cached CSS var css = localStorage.getItem("opencode-theme-css-" + mode) if (css) { diff --git a/packages/app-bundle/overlay/packages/app/src/app.tsx b/packages/app-bundle/overlay/packages/app/src/app.tsx index fc84096a..72568c49 100644 --- a/packages/app-bundle/overlay/packages/app/src/app.tsx +++ b/packages/app-bundle/overlay/packages/app/src/app.tsx @@ -498,7 +498,7 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) { { void window.api?.setTitlebar?.({ mode, scheme }) }} diff --git a/packages/app-bundle/overlay/packages/app/src/components/amicode-defaults-capsule.tsx b/packages/app-bundle/overlay/packages/app/src/components/amicode-defaults-capsule.tsx index 89414530..d3749f85 100644 --- a/packages/app-bundle/overlay/packages/app/src/components/amicode-defaults-capsule.tsx +++ b/packages/app-bundle/overlay/packages/app/src/components/amicode-defaults-capsule.tsx @@ -6,11 +6,13 @@ import { hpAfterConnect, hpClickAction, loadSolverMode, + releaseRequestForPick, modeAfterDisconnect, saveSolverMode, solverConnectionDot, type SolverMode, } from "@opencode-ai/ui/amicode-solver-toggle" +import { beginSolverSwitch } from "@/components/solver-switch-banner" import { ConnectionCard, type ConnectionActionView, @@ -46,6 +48,9 @@ export type AmicodeComputeControl = { onSubmit: (payload: CredentialSubmitPayload) => Promise onDisconnect: (id: string) => void onRevalidate: (id: string) => void + /** opencode#78: request the durable release of the HP tier. Optional so bare + * mounts (storybook) keep the localStorage-only legacy behavior. */ + onSelectPiccolo?: () => void refetch: () => void } @@ -74,6 +79,11 @@ export function AmicodeDefaultsCapsule(props: { compute?: AmicodeComputeControl const pick = (m: SolverMode) => { setMode(m) saveSolverMode(m) + // opencode#78: localStorage is the DISPLAY state; the durable half lives in + // the ops dir and only the server may write it. Releasing the tier is the + // one direction the client requests — hp arrives with a validated + // credential (submitCredential), never from a button. + if (releaseRequestForPick(m)) props.compute?.onSelectPiccolo?.() } const dot = createMemo(() => solverConnectionDot(props.compute?.view())) @@ -103,7 +113,12 @@ export function AmicodeDefaultsCapsule(props: { compute?: AmicodeComputeControl } const submitCredential = async (payload: CredentialSubmitPayload) => { const result = await props.compute!.onSubmit(payload) - if (hpAfterConnect(result)) pick("hp") + if (hpAfterConnect(result)) { + // #167 writes {mode:"hp",status:"switching"} on this same valid outcome, + // so the restart starts here — narrate it (opencode#78 follow-up). + beginSolverSwitch("hp") + pick("hp") + } return result } const disconnectCompute = (id: string) => { diff --git a/packages/app-bundle/overlay/packages/app/src/components/connection-banner.tsx b/packages/app-bundle/overlay/packages/app/src/components/connection-banner.tsx index dcb7bba1..be54a988 100644 --- a/packages/app-bundle/overlay/packages/app/src/components/connection-banner.tsx +++ b/packages/app-bundle/overlay/packages/app/src/components/connection-banner.tsx @@ -61,7 +61,7 @@ export function ConnectionBanner() { transform: "translateX(-50%)", "z-index": 40, padding: "6px 14px", - "border-radius": "999px", + "border-radius": "var(--radius-full)", "font-size": "12px", "font-weight": 600, border: "1px solid var(--v2-border-border-base, #3c3c3c)", diff --git a/packages/app-bundle/overlay/packages/app/src/components/dialog-command-palette-v2.css b/packages/app-bundle/overlay/packages/app/src/components/dialog-command-palette-v2.css new file mode 100644 index 00000000..c0758c57 --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/src/components/dialog-command-palette-v2.css @@ -0,0 +1,220 @@ +.command-palette-v2 { + overflow: hidden; +} + +/* Anchor to the top edge of where a centered 480px-tall dialog would sit, so the + top stays put while the content-driven height grows and shrinks. */ +[data-component="dialog-v2"]:has(.command-palette-v2) { + align-items: flex-start; +} + +[data-component="dialog-v2"]:has(.command-palette-v2) [data-slot="dialog-container"] { + width: min(calc(100vw - 24px), 640px); + height: auto; + min-height: 280px; + max-height: min(calc(100vh - 96px), 480px); + margin-top: max(48px, calc((100vh - 480px) / 2)); + border-radius: var(--radius-lg); + background: var(--v2-background-bg-base); + box-shadow: var(--v2-elevation-floating); +} + +.command-palette-v2-body { + display: flex; + min-height: 0; + flex: 1; + flex-direction: column; + gap: 0; + padding: 0; +} + +.command-palette-v2-search { + flex-shrink: 0; + padding: 6px; +} + +.command-palette-v2-search [data-component="text-input-v2"] { + width: 100%; + height: 36px; + border-radius: var(--radius-sm); + outline: 0; + background: color-mix(in srgb, var(--v2-background-bg-layer-02) 60%, transparent); + box-shadow: none; + transition: + background-color 120ms ease-in-out, + box-shadow 120ms ease-in-out; +} + +.command-palette-v2-search [data-component="text-input-v2"]:where(:hover):not([data-disabled], [data-invalid]), +.command-palette-v2-search [data-component="text-input-v2"]:where(:focus-within):not([data-disabled], [data-invalid]) { + background: var(--v2-background-bg-layer-02); + box-shadow: none; +} + +.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-value"] { + border-radius: var(--radius-sm); + background: transparent; + gap: 8px; +} + +.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-leading-icon"] { + padding-left: 12px; +} + +.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-input"] { + border-radius: var(--radius-sm); + background: transparent; +} + +.command-palette-v2-scroll { + min-height: 0; + flex: 1; +} + +.command-palette-v2-results { + display: flex; + flex-direction: column; + gap: 16px; + padding: 6px 6px 8px; +} + +.command-palette-v2-group { + display: flex; + flex-direction: column; + gap: 1px; +} + +.command-palette-v2-group-title { + margin: 6px 0; + padding: 0 12px; + color: var(--v2-text-text-muted); + font-size: 13px; + font-weight: 440; + line-height: 16px; + letter-spacing: -0.04px; + user-select: none; +} + +.command-palette-v2-row { + display: flex; + width: 100%; + height: 36px; + flex-shrink: 0; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 0 12px; + border: 0; + border-radius: var(--radius-sm); + background: transparent; + color: var(--v2-text-text-base); + text-align: left; + cursor: default; + scroll-margin: 6px 0; +} + +.command-palette-v2-row[data-active] { + background: var(--v2-overlay-simple-overlay-hover); +} + +.command-palette-v2-row:focus-visible { + background: var(--v2-overlay-simple-overlay-hover); + outline: none; +} + +.command-palette-v2-row-main { + display: flex; + min-width: 0; + flex: 1; + align-items: center; + gap: 8px; +} + +.command-palette-v2-row-icon { + flex-shrink: 0; + color: var(--v2-icon-icon-muted); +} + +.command-palette-v2-row-text { + display: flex; + min-width: 0; + align-items: center; + gap: 6px; +} + +.command-palette-v2-title { + min-width: 0; + overflow: hidden; + color: var(--v2-text-text-base); + font-size: 13px; + font-weight: 530; + line-height: 16px; + letter-spacing: -0.04px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.command-palette-v2-description, +.command-palette-v2-meta { + min-width: 0; + overflow: hidden; + color: var(--v2-text-text-muted); + font-size: 13px; + font-weight: 440; + line-height: 16px; + letter-spacing: -0.04px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.command-palette-v2-meta { + flex-shrink: 0; +} + +.command-palette-v2-file-path { + display: flex; + min-width: 0; + align-items: baseline; + font-size: 13px; + line-height: 16px; + letter-spacing: -0.04px; +} + +.command-palette-v2-file-dir { + min-width: 0; + overflow: hidden; + color: var(--v2-text-text-muted); + font-weight: 440; + text-overflow: ellipsis; + white-space: nowrap; +} + +.command-palette-v2-file-name { + flex-shrink: 0; + color: var(--v2-text-text-base); + font-weight: 530; + white-space: nowrap; +} + +.command-palette-v2-state { + display: grid; + min-height: 120px; + place-items: center; + color: var(--v2-text-text-muted); + font-size: 13px; + font-weight: 440; + line-height: 16px; + letter-spacing: -0.04px; +} + +@media (max-width: 640px) { + .command-palette-v2-row-text { + flex-direction: column; + align-items: flex-start; + gap: 1px; + } + + .command-palette-v2-description { + max-width: 100%; + } +} diff --git a/packages/app-bundle/overlay/packages/app/src/components/dialog-command-palette-v2.tsx b/packages/app-bundle/overlay/packages/app/src/components/dialog-command-palette-v2.tsx new file mode 100644 index 00000000..f2736183 --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/src/components/dialog-command-palette-v2.tsx @@ -0,0 +1,342 @@ +import { getDirectory, getFilename } from "@opencode-ai/core/util/path" +import { FileIcon } from "@opencode-ai/ui/file-icon" +import { ScrollView } from "@opencode-ai/ui/scroll-view" +import { Dialog, DialogBody } from "@opencode-ai/ui/v2/dialog-v2" +import { Icon } from "@opencode-ai/ui/v2/icon" +import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2" +import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { createEffect, createMemo, createResource, createSignal, For, Match, onCleanup, Show, Switch } from "solid-js" +import { commandPaletteOptions, formatKeybindParts, useCommand } from "@/context/command" +import { useGlobal } from "@/context/global" +import { useLanguage } from "@/context/language" +import { ServerConnection } from "@/context/server" +import { useTabs } from "@/context/tabs" +import { SessionTabAvatar } from "@/pages/layout/session-tab-avatar" +import { getRelativeTime } from "@/utils/time" +import { + createCommandPaletteCommandEntry, + createCommandPaletteFileEntry, + createCommandPaletteModel, + createServerSessionEntries, + uniqueCommandPaletteEntries, + type CommandPaletteEntry, +} from "./command-palette" +import "./dialog-command-palette-v2.css" + +function groups(entries: CommandPaletteEntry[]) { + const map = new Map() + for (const entry of entries) map.set(entry.category, [...(map.get(entry.category) ?? []), entry]) + return Array.from(map.entries()).map(([category, entries]) => ({ category, entries })) +} + +function matchesEntry(entry: CommandPaletteEntry, query: string) { + const value = query.toLowerCase() + return [entry.title, entry.description, entry.category].some((text) => text?.toLowerCase().includes(value)) +} + +export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => void }) { + const palette = createCommandPaletteModel(props) + const loadItems = async (text: string) => { + const q = text.trim() + if (!q) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()] + + const [files, nextSessions] = await Promise.all([palette.file.searchFiles(q), Promise.resolve(palette.sessions(q))]) + const category = palette.language.t("palette.group.files") + return [ + ...palette.commandEntries().filter((entry) => matchesEntry(entry, q)), + ...nextSessions, + ...files.map((path) => createCommandPaletteFileEntry(path, category)), + ] + } + + return ( + + ) +} + +export function DialogHomeCommandPaletteV2(props: { + server: ServerConnection.Any + onSelectSession: (entry: CommandPaletteEntry) => void +}) { + const command = useCommand() + const dialog = useDialog() + const global = useGlobal() + const language = useLanguage() + const serverCtx = global.ensureServerCtx(props.server) + const state = { cleanup: undefined as (() => void) | void, committed: false } + const commandEntries = createMemo(() => { + const category = language.t("palette.group.commands") + return commandPaletteOptions(command.options).map((option) => createCommandPaletteCommandEntry(option, category)) + }) + const sessions = createServerSessionEntries({ + server: ServerConnection.key(props.server), + opened: serverCtx.projects.list, + stored: () => serverCtx.sync.data.project, + load: (search, signal) => serverCtx.sdk.api.session.list({ parentID: null, search, limit: 50 }, { signal }), + untitled: () => language.t("command.session.new"), + category: () => language.t("command.category.session"), + }) + + const highlight = (item: CommandPaletteEntry | undefined) => { + state.cleanup?.() + state.cleanup = undefined + if (item?.type !== "command") return + state.cleanup = item.option?.onHighlight?.() + } + const select = (item: CommandPaletteEntry | undefined) => { + if (!item) return + state.committed = true + state.cleanup = undefined + dialog.close() + if (item.type === "command") { + item.option?.onSelect?.("palette") + return + } + if (item.type === "session") props.onSelectSession(item) + } + const loadItems = async (text: string) => { + const query = text.trim() + if (!query) return commandEntries().slice(0, 5) + return [...commandEntries().filter((entry) => matchesEntry(entry, query)), ...(await sessions(query))] + } + + onCleanup(() => { + if (state.committed) return + state.cleanup?.() + }) + + return ( + dialog.close()} + /> + ) +} + +function CommandPaletteView(props: { + placeholder: string + loadItems: (text: string) => CommandPaletteEntry[] | Promise + highlight: (item: CommandPaletteEntry | undefined) => void + select: (item: CommandPaletteEntry | undefined) => void + close: () => void +}) { + const language = useLanguage() + const tabs = useTabs() + const [query, setQuery] = createSignal("") + const [active, setActive] = createSignal(0) + + const [entries] = createResource(query, props.loadItems, { initialValue: [] as CommandPaletteEntry[] }) + // Render stale results while a new query loads to avoid flashing "Loading" per keystroke. + const visibleEntries = createMemo(() => uniqueCommandPaletteEntries(entries.latest ?? [])) + const groupedEntries = createMemo(() => groups(visibleEntries())) + const activeEntry = createMemo(() => visibleEntries()[active()]) + const openSessions = createMemo( + () => new Set(tabs.store.flatMap((tab) => (tab.type === "session" ? [`${tab.server}\0${tab.sessionId}`] : []))), + ) + + createEffect(() => { + query() + visibleEntries() + setActive(0) + }) + + createEffect(() => { + props.highlight(activeEntry()) + }) + + let resultsRef: HTMLDivElement | undefined + + const move = (delta: -1 | 1) => { + const count = visibleEntries().length + if (count === 0) return + setActive((index) => (index + delta + count) % count) + requestAnimationFrame(() => { + resultsRef?.querySelector("[data-active]")?.scrollIntoView({ block: "nearest" }) + }) + } + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "ArrowDown") { + event.preventDefault() + move(1) + return + } + if (event.key === "ArrowUp") { + event.preventDefault() + move(-1) + return + } + if (event.key === "Enter") { + event.preventDefault() + props.select(activeEntry()) + return + } + if (event.key === "Escape") { + event.preventDefault() + props.close() + } + } + + return ( + + + + (resultsRef = el)}> +
+ 0} + fallback={ +
+ {entries.loading ? language.t("common.loading") : language.t("palette.empty")} +
+ } + > + + {(group) => ( +
+ +
{group.category}
+
+ + {(item) => ( + setActive(visibleEntries().findIndex((entry) => entry.id === item.id))} + onSelect={() => props.select(item)} + /> + )} + +
+ )} +
+
+
+
+
+
+ ) +} + +function PaletteRow(props: { + item: CommandPaletteEntry + active: boolean + language: ReturnType + sessionOpen: boolean + onActive: () => void + onSelect: () => void +}) { + const session = () => + props.item.server && props.item.directory && props.item.sessionID + ? { server: props.item.server, directory: props.item.directory, sessionID: props.item.sessionID } + : undefined + + return ( + + ) +} diff --git a/packages/app-bundle/overlay/packages/app/src/components/dialog-connect-provider.tsx b/packages/app-bundle/overlay/packages/app/src/components/dialog-connect-provider.tsx new file mode 100644 index 00000000..a039749e --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/src/components/dialog-connect-provider.tsx @@ -0,0 +1,1179 @@ +import type { IntegrationMethod, IntegrationOauthConnectOutput } from "@opencode-ai/client/promise" +import { Button } from "@opencode-ai/ui/button" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { Dialog } from "@opencode-ai/ui/dialog" +import { Icon } from "@opencode-ai/ui/icon" +import { IconButton } from "@opencode-ai/ui/icon-button" +import { List, type ListRef } from "@opencode-ai/ui/list" +import { ProviderIcon } from "@opencode-ai/ui/provider-icon" +import { Spinner } from "@opencode-ai/ui/spinner" +import { Tag } from "@opencode-ai/ui/tag" +import { TextField } from "@opencode-ai/ui/text-field" +import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" +import { DialogBody, DialogHeader, DialogTitle, DialogV2 } from "@opencode-ai/ui/v2/dialog-v2" +import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2" +import { showToast } from "@/utils/toast" +import { + type Accessor, + type Component, + createEffect, + createMemo, + createResource, + createUniqueId, + For, + Match, + onCleanup, + onMount, + Show, + Switch, +} from "solid-js" +import { createStore, produce } from "solid-js/store" +import { useParams } from "@solidjs/router" +import { ExternalLink } from "@/components/external-link" +import { useServerSDK } from "@/context/server-sdk" +import { useServerSync } from "@/context/server-sync" +import { useLanguage } from "@/context/language" +import { useSettings } from "@/context/settings" +import { popularProviders, useProviders } from "@/hooks/use-providers" +import { CustomProviderForm } from "./dialog-custom-provider" +import { decode64 } from "@/utils/base64" + +const CUSTOM_ID = "_custom" +type ConnectMethod = Extract + +export function useProviderConnectController(options: { onBack?: () => void } = {}) { + const [store, setStore] = createStore({ selected: undefined as string | undefined }) + const reset = () => setStore("selected", undefined) + + return { + selected: () => store.selected, + select: (provider?: string) => setStore("selected", provider), + back: options.onBack ?? reset, + } +} + +export const DialogConnectProvider: Component<{ + directory?: Accessor + controller?: ReturnType +}> = (props) => { + const fallback = useProviderConnectController() + const controller = props.controller ?? fallback + const language = useLanguage() + const settings = useSettings() + const newLayout = settings.general.newLayoutDesigns + const reset = controller.back + const back = { current: reset } + let focusHost: HTMLDivElement | undefined + const holdFocus = () => focusHost?.focus({ preventScroll: true }) + const select = (provider?: string) => { + back.current = reset + controller.select(provider) + } + + function Content() { + return ( + + + + + + {(provider) => ( + (back.current = handler)} + /> + )} + + + + + + ) + } + + return ( + + back.current()} + aria-label={language.t("common.goBack")} + /> + + } + > + + + } + > + + + {language.t("command.provider.connect")}} + > + + + + +
+ +
+
+
+ + ) +} + +function ProviderPicker(props: { + directory?: Accessor + onSelect: (provider: string) => void + onPrepare?: () => void +}) { + const settings = useSettings() + if (settings.general.newLayoutDesigns()) + return + const providers = useProviders(() => props.directory?.()) + const language = useLanguage() + const popularGroup = () => language.t("dialog.provider.group.popular") + const otherGroup = () => language.t("dialog.provider.group.other") + const customLabel = () => language.t("settings.providers.tag.custom") + const note = (id: string) => { + if (id === "anthropic") return language.t("dialog.provider.anthropic.note") + if (id === "openai") return language.t("dialog.provider.openai.note") + if (id.startsWith("github-copilot")) return language.t("dialog.provider.copilot.note") + if (id === "opencode-go") return language.t("dialog.provider.opencodeGo.tagline") + return undefined + } + + return ( + x?.id} + items={() => { + language.locale() + return [{ id: CUSTOM_ID, name: customLabel() }, ...providers.all().values()] + }} + filterKeys={["id", "name"]} + groupBy={(x) => (popularProviders.includes(x.id) ? popularGroup() : otherGroup())} + sortBy={(a, b) => { + if (a.id === CUSTOM_ID) return -1 + if (b.id === CUSTOM_ID) return 1 + if (popularProviders.includes(a.id) && popularProviders.includes(b.id)) + return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id) + return a.name.localeCompare(b.name) + }} + sortGroupsBy={(a, b) => { + const popular = popularGroup() + if (a.category === popular && b.category !== popular) return -1 + if (b.category === popular && a.category !== popular) return 1 + return 0 + }} + onSelect={(x) => { + if (!x) return + props.onSelect(x.id) + }} + > + {(i) => ( +
+ + {i.name} + +
{language.t("dialog.provider.opencode.tagline")}
+
+ + {language.t("settings.providers.tag.custom")} + + + {language.t("dialog.provider.tag.recommended")} + + {(value) =>
{value()}
}
+ + {language.t("dialog.provider.tag.recommended")} + +
+ )} +
+ ) +} + +function ProviderPickerV2(props: { + directory?: Accessor + onSelect: (provider: string) => void + onPrepare?: () => void +}) { + const providers = useProviders(() => props.directory?.()) + const language = useLanguage() + const [store, setStore] = createStore({ + filter: "", + active: undefined as string | undefined, + connecting: undefined as string | undefined, + }) + const featured = ["opencode", "opencode-go", "anthropic", "openai", "google", "openrouter", "vercel"] + const custom = () => ({ id: CUSTOM_ID, name: language.t("dialog.provider.custom.label") }) + const all = createMemo(() => { + language.locale() + const query = store.filter.trim().toLowerCase() + const values = [custom(), ...providers.all().values()] + if (!query) return values + return values.filter((provider) => `${provider.id} ${provider.name}`.toLowerCase().includes(query)) + }) + const popular = createMemo(() => + all() + .filter((provider) => featured.includes(provider.id)) + .sort((a, b) => featured.indexOf(a.id) - featured.indexOf(b.id)), + ) + const other = createMemo(() => + all() + .filter((provider) => !featured.includes(provider.id)) + .sort((a, b) => { + if (a.id === CUSTOM_ID) return -1 + if (b.id === CUSTOM_ID) return 1 + return a.name.localeCompare(b.name) + }), + ) + const rows = createMemo(() => [...popular(), ...other()]) + let picker: HTMLDivElement | undefined + let search: HTMLInputElement | undefined + + onMount(() => search?.focus({ preventScroll: true })) + + const connect = (provider: string) => { + props.onPrepare?.() + props.onSelect(provider) + } + + const move = (event: KeyboardEvent, direction: number) => { + const items = rows() + if (items.length === 0) return + const index = items.findIndex((provider) => provider.id === store.active) + const next = index < 0 ? (direction > 0 ? 0 : items.length - 1) : (index + direction + items.length) % items.length + setStore("active", items[next].id) + picker + ?.querySelector(`[data-provider-id="${CSS.escape(items[next].id)}"]`) + ?.focus({ preventScroll: true }) + event.preventDefault() + } + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "ArrowDown") return move(event, 1) + if (event.key === "ArrowUp") return move(event, -1) + if (event.key !== "Enter" || !store.active) return + connect(store.active) + event.preventDefault() + } + + return ( +
+
+ } + placeholder={language.t("dialog.provider.search.placeholder")} + value={store.filter} + onInput={(event) => { + setStore({ filter: event.currentTarget.value, active: undefined }) + }} + /> +
+
+
+ + {(group) => ( + 0}> +
+
+ {group.title} +
+ + {(provider) => ( + + )} + +
+
+ )} +
+ +
+ {language.t("dialog.provider.empty")} +
+
+
+
+
+
+ ) +} + +function ProviderConnection(props: { + provider: string + directory?: Accessor + onBack: () => void + setBack: (handler: () => void) => void +}) { + const dialog = useDialog() + const serverSync = useServerSync() + const serverSDK = useServerSDK() + const params = useParams() + const language = useLanguage() + const settings = useSettings() + const newLayout = settings.general.newLayoutDesigns + const providers = useProviders(() => props.directory?.()) + const directory = () => props.directory?.() ?? decode64(params.dir) + const location = () => { + const value = directory() + return value ? { directory: value } : undefined + } + + const alive = { value: true } + const timer = { current: undefined as ReturnType | undefined } + + onCleanup(() => { + alive.value = false + if (timer.current === undefined) return + clearTimeout(timer.current) + timer.current = undefined + }) + + const provider = createMemo( + () => providers.all().get(props.provider) ?? serverSync().data.provider.all.get(props.provider)!, + ) + const fallback = createMemo(() => [ + { + type: "key" as const, + label: language.t("provider.connect.method.apiKey"), + }, + ]) + const [integration] = createResource( + () => ({ provider: props.provider, directory: directory() }), + (input) => + serverSDK() + .api.integration.get({ + integrationID: input.provider, + location: input.directory ? { directory: input.directory } : undefined, + }) + .then((result) => result.data), + ) + const loading = createMemo(() => integration.loading) + const methods = createMemo(() => { + const values = integration.latest?.methods.filter( + (method): method is ConnectMethod => method.type === "key" || method.type === "oauth", + ) + return values?.length ? values : fallback() + }) + const [store, setStore] = createStore({ + methodIndex: undefined as undefined | number, + authorization: undefined as undefined | IntegrationOauthConnectOutput["data"], + promptInputs: undefined as undefined | Record, + state: "pending" as undefined | "pending" | "complete" | "error" | "prompt", + error: undefined as string | undefined, + }) + + type Action = + | { type: "method.select"; index: number } + | { type: "method.reset" } + | { type: "auth.prompt" } + | { type: "auth.inputs"; inputs: Record } + | { type: "auth.pending" } + | { type: "auth.complete"; authorization: IntegrationOauthConnectOutput["data"] } + | { type: "auth.error"; error: string } + + function dispatch(action: Action) { + setStore( + produce((draft) => { + if (action.type === "method.select") { + draft.methodIndex = action.index + draft.authorization = undefined + draft.promptInputs = undefined + draft.state = undefined + draft.error = undefined + return + } + if (action.type === "method.reset") { + draft.methodIndex = undefined + draft.authorization = undefined + draft.promptInputs = undefined + draft.state = undefined + draft.error = undefined + return + } + if (action.type === "auth.prompt") { + draft.state = "prompt" + draft.error = undefined + return + } + if (action.type === "auth.inputs") { + draft.promptInputs = action.inputs + draft.state = undefined + draft.error = undefined + return + } + if (action.type === "auth.pending") { + draft.state = "pending" + draft.error = undefined + return + } + if (action.type === "auth.complete") { + draft.state = "complete" + draft.authorization = action.authorization + draft.error = undefined + return + } + draft.state = "error" + draft.error = action.error + }), + ) + } + + const method = createMemo(() => (store.methodIndex !== undefined ? methods().at(store.methodIndex!) : undefined)) + + const methodLabel = (value?: { type?: string; label?: string }) => { + if (!value) return "" + if (value.type === "key") return language.t("provider.connect.method.apiKey") + return value.label ?? "" + } + + const methodDetails = (value?: { type?: string; label?: string }) => { + const label = methodLabel(value) + const suffix = value?.label?.match(/\s+\((browser|headless)\)$/i) + const hint = suffix?.[1] + return { + label: suffix ? label.slice(0, -suffix[0].length) : label, + hint: hint ? hint[0].toUpperCase() + hint.slice(1) : value?.type === "key" ? "Browser" : undefined, + } + } + + function formatError(value: unknown, fallback: string): string { + if (value && typeof value === "object" && "data" in value) { + const data = (value as { data?: { message?: unknown } }).data + if (typeof data?.message === "string" && data.message) return data.message + } + if (value && typeof value === "object" && "error" in value) { + const nested = formatError((value as { error?: unknown }).error, "") + if (nested) return nested + } + if (value && typeof value === "object" && "message" in value) { + const message = (value as { message?: unknown }).message + if (typeof message === "string" && message) return message + } + if (value instanceof Error && value.message) return value.message + if (typeof value === "string" && value) return value + return fallback + } + + async function selectMethod(index: number, inputs?: Record) { + if (timer.current !== undefined) { + clearTimeout(timer.current) + timer.current = undefined + } + + const method = methods()[index] + dispatch({ type: "method.select", index }) + + if (method.type === "oauth") { + if (method.prompts?.length && !inputs) { + dispatch({ type: "auth.prompt" }) + return + } + dispatch({ type: "auth.pending" }) + await serverSDK() + .api.integration.oauth.connect({ + integrationID: props.provider, + methodID: method.id, + inputs: inputs ?? {}, + location: location(), + }) + .then((x) => { + if (!alive.value) return + dispatch({ type: "auth.complete", authorization: x.data }) + }) + .catch((e) => { + if (!alive.value) return + dispatch({ type: "auth.error", error: formatError(e, language.t("common.requestFailed")) }) + }) + } + } + + function AuthPromptsView() { + const [formStore, setFormStore] = createStore({ + value: {} as Record, + index: 0, + }) + + const prompts = createMemo(() => { + const value = method() + return value?.type === "oauth" ? (value.prompts ?? []) : [] + }) + const matches = (prompt: NonNullable[number]>, value: Record) => { + if (!prompt.when) return true + const actual = value[prompt.when.key] + if (actual === undefined) return false + return prompt.when.op === "eq" ? actual === prompt.when.value : actual !== prompt.when.value + } + const current = createMemo(() => { + const all = prompts() + const index = all.findIndex((prompt, index) => index >= formStore.index && matches(prompt, formStore.value)) + if (index === -1) return + return { + index, + prompt: all[index], + } + }) + const valid = createMemo(() => { + const item = current() + if (!item || item.prompt.type !== "text") return false + const value = formStore.value[item.prompt.key] ?? "" + return value.trim().length > 0 + }) + + async function next(index: number, value: Record) { + if (store.methodIndex === undefined) return + const next = prompts().findIndex((prompt, i) => i > index && matches(prompt, value)) + if (next !== -1) { + setFormStore("index", next) + return + } + await selectMethod(store.methodIndex, value) + } + + async function handleSubmit(e: SubmitEvent) { + e.preventDefault() + const item = current() + if (!item || item.prompt.type !== "text") return + if (!valid()) return + await next(item.index, formStore.value) + } + + const item = () => current() + const text = createMemo(() => { + const prompt = item()?.prompt + if (!prompt || prompt.type !== "text") return + return prompt + }) + const select = createMemo(() => { + const prompt = item()?.prompt + if (!prompt || prompt.type !== "select") return + return prompt + }) + + return ( +
+ + + { + const prompt = text() + if (!prompt) return + setFormStore("value", prompt.key, value) + }} + /> + + + +
+
{select()?.message}
+
+ x.value} + current={select()?.options.find((x) => x.value === formStore.value[select()!.key])} + onSelect={(value) => { + if (!value) return + const prompt = select() + if (!prompt) return + const nextValue = { + ...formStore.value, + [prompt.key]: value.value, + } + setFormStore("value", prompt.key, value.value) + void next(item()!.index, nextValue) + }} + > + {(option) => ( +
+
+ + {option.label} + {option.hint} +
+ )} + +
+
+ + + + ) + } + + let listRef: ListRef | undefined + function handleKey(e: KeyboardEvent) { + if (e.key === "Enter" && e.target instanceof HTMLInputElement) { + return + } + if (e.key === "Escape") return + listRef?.onKeyDown(e) + } + + let auto = false + createEffect(() => { + if (auto) return + if (loading()) return + if (methods().length === 1) { + auto = true + void selectMethod(0) + } + }) + + async function complete() { + await serverSync() + .refreshProviders() + .catch(() => undefined) + dialog.close() + showToast({ + variant: "success", + icon: "circle-check", + title: language.t("provider.connect.toast.connected.title", { provider: provider().name }), + description: language.t("provider.connect.toast.connected.description", { provider: provider().name }), + }) + } + + function goBack() { + if (methods().length > 1 && store.methodIndex !== undefined) { + dispatch({ type: "method.reset" }) + return + } + props.onBack() + } + + props.setBack(goBack) + + function MethodSelection() { + if (newLayout()) + return ( +
+
+ {language.t("provider.connect.selectMethod", { provider: provider().name })} +
+
+ + {(item, index) => { + const details = () => methodDetails(item) + return ( + + ) + }} + +
+
+ ) + + return ( + <> +
+ {language.t("provider.connect.selectMethod", { provider: provider().name })} +
+
+ { + listRef = ref + }} + items={methods} + key={(m) => m?.label ?? m?.type} + onSelect={async (selected, index) => { + if (!selected) return + void selectMethod(index) + }} + > + {(i) => ( +
+
+ + {methodLabel(i)} +
+ )} + +
+ + ) + } + + function ApiAuthView() { + let apiKey: HTMLInputElement | undefined + const errorID = createUniqueId() + const [formStore, setFormStore] = createStore({ + value: "", + error: undefined as string | undefined, + }) + + onMount(() => { + if (!newLayout()) return + apiKey?.focus({ preventScroll: true }) + }) + + async function handleSubmit(e: SubmitEvent) { + e.preventDefault() + + const form = e.currentTarget as HTMLFormElement + const formData = new FormData(form) + const apiKey = formData.get("apiKey") as string + + if (!apiKey?.trim()) { + setFormStore("error", language.t("provider.connect.apiKey.required")) + return + } + + setFormStore("error", undefined) + await serverSDK().api.integration.connect.key({ + integrationID: props.provider, + location: location(), + key: apiKey, + }) + await complete() + } + + if (newLayout()) + return ( +
+ +
+
{language.t("provider.connect.opencodeZen.line1")}
+
{language.t("provider.connect.opencodeZen.line2")}
+
+ {language.t("provider.connect.opencodeZen.visit.prefix")} + + {language.t("provider.connect.opencodeZen.visit.link")} + + {language.t("provider.connect.opencodeZen.visit.suffix")} +
+
+
+
+ + + {(error) => ( + + )} + + + {language.t("common.continue")} + +
+
+ ) + + return ( +
+ + +
+
{language.t("provider.connect.opencodeZen.line1")}
+
{language.t("provider.connect.opencodeZen.line2")}
+
+ {language.t("provider.connect.opencodeZen.visit.prefix")} + + {language.t("provider.connect.opencodeZen.visit.link")} + + {language.t("provider.connect.opencodeZen.visit.suffix")} +
+
+
+ +
+ {language.t("provider.connect.apiKey.description", { provider: provider().name })} +
+
+
+
+ setFormStore("value", v)} + validationState={formStore.error ? "invalid" : undefined} + error={formStore.error} + /> + + +
+ ) + } + + function OAuthCodeView() { + let codeInput: HTMLInputElement | undefined + const errorID = createUniqueId() + const [formStore, setFormStore] = createStore({ + value: "", + error: undefined as string | undefined, + }) + + onMount(() => { + if (!newLayout()) return + codeInput?.focus({ preventScroll: true }) + }) + + async function handleSubmit(e: SubmitEvent) { + e.preventDefault() + + const form = e.currentTarget as HTMLFormElement + const formData = new FormData(form) + const code = formData.get("code") as string + + if (!code?.trim()) { + setFormStore("error", language.t("provider.connect.oauth.code.required")) + return + } + + setFormStore("error", undefined) + const result = await serverSDK() + .api.integration.oauth.complete({ + integrationID: props.provider, + attemptID: store.authorization!.attemptID, + location: location(), + code, + }) + .then(() => ({ ok: true as const })) + .catch((error) => ({ ok: false as const, error })) + if (result.ok) { + await complete() + return + } + setFormStore("error", formatError(result.error, language.t("provider.connect.oauth.code.invalid"))) + } + + if (newLayout()) + return ( +
+
+ {language.t("provider.connect.oauth.code.visit.prefix")} + + {language.t("provider.connect.oauth.code.visit.link")} + + {language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })} +
+
+ + + {(error) => ( + + )} + + + {language.t("common.continue")} + +
+
+ ) + + return ( +
+
+ {language.t("provider.connect.oauth.code.visit.prefix")} + + {language.t("provider.connect.oauth.code.visit.link")} + + {language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })} +
+
+ setFormStore("value", v)} + validationState={formStore.error ? "invalid" : undefined} + error={formStore.error} + /> + + +
+ ) + } + + function OAuthAutoView() { + const code = createMemo(() => { + const instructions = store.authorization?.instructions + if (instructions?.includes(":")) { + return instructions.split(":").pop()?.trim() + } + return instructions + }) + + onMount(() => { + const poll = async () => { + const authorization = store.authorization + if (!authorization || !alive.value) return + const result = await serverSDK() + .api.integration.oauth.status({ + integrationID: props.provider, + attemptID: authorization.attemptID, + location: location(), + }) + .then((value) => ({ ok: true as const, status: value.data })) + .catch((error) => ({ ok: false as const, error })) + if (!alive.value) return + if (!result.ok) { + dispatch({ type: "auth.error", error: formatError(result.error, language.t("common.requestFailed")) }) + return + } + if (result.status.status === "complete") { + await complete() + return + } + if (result.status.status === "failed") { + dispatch({ type: "auth.error", error: result.status.message }) + return + } + if (result.status.status === "expired") { + dispatch({ type: "auth.error", error: language.t("common.requestFailed") }) + return + } + timer.current = setTimeout(poll, 1_000) + } + void poll() + }) + + return ( +
+
+ {language.t("provider.connect.oauth.auto.visit.prefix")} + + {language.t("provider.connect.oauth.auto.visit.link")} + + {language.t("provider.connect.oauth.auto.visit.suffix", { provider: provider().name })} +
+ +
+ + {language.t("provider.connect.status.waiting")} +
+
+ ) + } + + return ( +
+
+ +
+ + + {language.t("provider.connect.title.anthropicProMax")} + + {language.t("provider.connect.title", { provider: provider().name })} + +
+
+
+
+ + +
+
+ + {language.t("provider.connect.status.inProgress")} +
+
+
+ + + + +
+
+ + {language.t("provider.connect.status.inProgress")} +
+
+
+ + + + +
+
+ + {language.t("provider.connect.status.failed", { error: store.error ?? "" })} +
+
+
+ + + + + + + + + + + + + +
+
+
+
+ ) +} diff --git a/packages/app-bundle/overlay/packages/app/src/components/dialog-edit-project-v2.tsx b/packages/app-bundle/overlay/packages/app/src/components/dialog-edit-project-v2.tsx new file mode 100644 index 00000000..d0eab418 --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/src/components/dialog-edit-project-v2.tsx @@ -0,0 +1,156 @@ +import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" +import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle } from "@opencode-ai/ui/v2/dialog-v2" +import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2" +import { Field } from "@opencode-ai/ui/v2/field-v2" +import { Icon } from "@opencode-ai/ui/v2/icon" +import { ProjectAvatar, PROJECT_AVATAR_VARIANTS } from "@opencode-ai/ui/v2/project-avatar-v2" +import { TextareaV2 } from "@opencode-ai/ui/v2/textarea-v2" +import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2" +import { For, Show } from "solid-js" +import { useLanguage } from "@/context/language" +import { getProjectAvatarVariant, type LocalProject } from "@/context/layout" +import { ServerConnection } from "@/context/server" +import { getProjectAvatarSource } from "@/pages/layout/helpers" +import { createEditProjectModel } from "./edit-project" + +export function DialogEditProjectV2(props: { project: LocalProject; server: ServerConnection.Any }) { + const language = useLanguage() + const model = createEditProjectModel(props) + + return ( + +
+ + {language.t("dialog.project.edit.title")} + + + + + {language.t("dialog.project.edit.name")} + model.setStore("name", event.currentTarget.value)} + /> + + +
+
+ {language.t("dialog.project.edit.icon")} +
+
+ + { + model.setIconInput(element) + }} + type="file" + accept="image/*" + class="hidden" + onChange={model.inputChange} + /> +
+ {language.t("dialog.project.edit.icon.hint")} + {language.t("dialog.project.edit.icon.recommended")} +
+
+
+ + +
+
+ {language.t("dialog.project.edit.color")} +
+
+ + {(color) => ( + + )} + +
+
+
+ + + {language.t("dialog.project.edit.worktree.startup")} + {language.t("dialog.project.edit.worktree.startup.description")} + model.setStore("startup", event.currentTarget.value)} + /> + +
+ + + {language.t("common.cancel")} + + + {model.save.isPending ? language.t("common.saving") : language.t("common.save")} + + + +
+ ) +} diff --git a/packages/app-bundle/overlay/packages/app/src/components/dialog-edit-project.tsx b/packages/app-bundle/overlay/packages/app/src/components/dialog-edit-project.tsx new file mode 100644 index 00000000..e2287821 --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/src/components/dialog-edit-project.tsx @@ -0,0 +1,170 @@ +import { Button } from "@opencode-ai/ui/button" +import { Dialog } from "@opencode-ai/ui/dialog" +import { TextField } from "@opencode-ai/ui/text-field" +import { Icon } from "@opencode-ai/ui/icon" +import { For, Show } from "solid-js" +import { type LocalProject, getAvatarColors } from "@/context/layout" +import { Avatar } from "@opencode-ai/ui/avatar" +import { useLanguage } from "@/context/language" +import { getProjectAvatarSource } from "@/pages/layout/helpers" +import { ServerConnection } from "@/context/server" +import { createEditProjectModel } from "./edit-project" + +const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const + +export function DialogEditProject(props: { project: LocalProject; server: ServerConnection.Any }) { + const language = useLanguage() + const model = createEditProjectModel(props) + + return ( + +
+
+ model.setStore("name", v)} + /> + +
+ +
+
model.setStore("iconHover", true)} + onMouseLeave={() => model.setStore("iconHover", false)} + > +
+ + +
+ } + > + {(src) => ( + {language.t("dialog.project.edit.icon.alt")} + )} + +
+
+ +
+
+ +
+
+ { + model.setIconInput(el) + }} + type="file" + accept="image/*" + class="hidden" + onChange={model.inputChange} + /> +
+ {language.t("dialog.project.edit.icon.hint")} + {language.t("dialog.project.edit.icon.recommended")} +
+
+
+ + +
+ +
+ + {(color) => ( + + )} + +
+
+
+ + model.setStore("startup", v)} + spellcheck={false} + class="max-h-14 w-full overflow-y-auto font-mono text-xs" + /> +
+ +
+ + +
+ + + ) +} diff --git a/packages/app-bundle/overlay/packages/app/src/components/dialog-release-notes.tsx b/packages/app-bundle/overlay/packages/app/src/components/dialog-release-notes.tsx new file mode 100644 index 00000000..b03660fe --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/src/components/dialog-release-notes.tsx @@ -0,0 +1,144 @@ +import { createSignal } from "solid-js" +import { Dialog } from "@opencode-ai/ui/dialog" +import { Button } from "@opencode-ai/ui/button" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { useLanguage } from "@/context/language" +import { useSettings } from "@/context/settings" + +export type Highlight = { + title: string + description: string + media?: { + type: "image" | "video" + src: string + alt?: string + } +} + +export function DialogReleaseNotes(props: { highlights: Highlight[] }) { + const dialog = useDialog() + const language = useLanguage() + const settings = useSettings() + const [index, setIndex] = createSignal(0) + + const total = () => props.highlights.length + const last = () => Math.max(0, total() - 1) + const feature = () => props.highlights[index()] ?? props.highlights[last()] + const isFirst = () => index() === 0 + const isLast = () => index() >= last() + const paged = () => total() > 1 + + function handleNext() { + if (isLast()) return + setIndex(index() + 1) + } + + function handleClose() { + dialog.close() + } + + function handleDisable() { + settings.general.setReleaseNotes(false) + handleClose() + } + + function handleKeyDown(e: KeyboardEvent) { + if (e.key === "Escape") { + e.preventDefault() + handleClose() + return + } + + if (!paged()) return + if (e.key === "ArrowLeft" && !isFirst()) { + e.preventDefault() + setIndex(index() - 1) + } + if (e.key === "ArrowRight" && !isLast()) { + e.preventDefault() + setIndex(index() + 1) + } + } + + return ( + +
+ {/* Left side - Text content */} +
+ {/* Top section - feature content (fixed position from top) */} +
+
+

{feature()?.title ?? ""}

+
+

{feature()?.description ?? ""}

+
+ + {/* Spacer to push buttons to bottom */} +
+ + {/* Bottom section - buttons and indicators (fixed position) */} +
+
+ {isLast() ? ( + + ) : ( + + )} + + +
+ + {paged() && ( +
+ {props.highlights.map((_, i) => ( + + ))} +
+ )} +
+
+ + {/* Right side - Media content (edge to edge) */} + {feature()?.media && ( +
+ {feature()!.media!.type === "image" ? ( + {feature()!.media!.alt + ) : ( +
+ )} +
+
+ ) +} diff --git a/packages/app-bundle/overlay/packages/app/src/components/dialog-select-directory-v2.css b/packages/app-bundle/overlay/packages/app/src/components/dialog-select-directory-v2.css new file mode 100644 index 00000000..7382b4f1 --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/src/components/dialog-select-directory-v2.css @@ -0,0 +1,107 @@ +.directory-picker-v2-body { + display: flex; + min-height: 0; + flex: 1; + flex-direction: column; + gap: 12px; + padding: 2px 16px 0; +} + +.directory-picker-v2-path { + position: relative; + z-index: 10; + display: flex; + gap: 8px; +} + +.directory-picker-v2-actions { + display: flex; + flex-shrink: 0; + gap: 2px; +} + +.directory-picker-v2-suggestions { + position: absolute; + z-index: 20; + top: 36px; + right: 0; + left: 0; + display: flex; + flex-direction: column; + padding: 4px; + border: 1px solid var(--v2-border-border-base); + border-radius: var(--radius-sm); + background: var(--v2-background-bg-layer-02); + box-shadow: var(--v2-elevation-overlay); +} + +.directory-picker-v2-suggestions button { + overflow: hidden; + padding: 6px 8px; + border-radius: var(--radius-sm); + color: var(--v2-text-text-muted); + font-size: 12px; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; +} + +.directory-picker-v2-suggestions button:hover, +.directory-picker-v2-suggestions button[data-active] { + color: var(--v2-text-text-base); + background: var(--v2-overlay-simple-overlay-hover); +} + +.directory-picker-v2-browser { + position: relative; + z-index: 0; + isolation: isolate; + min-height: 0; + flex: 1; + overflow: auto; + border: 1px solid var(--v2-border-border-base); + border-radius: var(--radius-sm); + background: transparent; +} + +.directory-picker-v2-tree { + display: block; + width: 100%; + height: 100%; + --trees-bg-override: transparent; + --trees-fg-override: var(--v2-text-text-base); + --trees-fg-muted-override: var(--v2-text-text-muted); + --trees-bg-muted-override: transparent; + --trees-selected-bg-override: transparent; + --trees-selected-fg-override: var(--v2-text-text-base); + --trees-selected-focused-border-color-override: transparent; + --trees-focus-ring-color-override: transparent; + --trees-focus-ring-width-override: 0px; + --trees-focus-ring-offset-override: 0px; + --trees-border-color-override: var(--v2-border-border-base); + --trees-font-family-override: var(--font-family-sans); + --trees-font-size-override: 12px; + --trees-item-height: 24px; + --trees-border-radius-override: 4px; +} + +.directory-picker-v2-state { + position: absolute; + z-index: 1; + inset: 0; + display: grid; + place-items: center; + color: var(--v2-text-text-muted); + font-size: 12px; + pointer-events: none; +} + +.directory-picker-v2-selection { + overflow: hidden; + flex-shrink: 0; + color: var(--v2-text-text-muted); + font-size: 12px; + line-height: 16px; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/packages/app-bundle/overlay/packages/app/src/components/dialog-select-file.tsx b/packages/app-bundle/overlay/packages/app/src/components/dialog-select-file.tsx new file mode 100644 index 00000000..d9ccaea5 --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/src/components/dialog-select-file.tsx @@ -0,0 +1,189 @@ +import { Dialog } from "@opencode-ai/ui/dialog" +import { FileIcon } from "@opencode-ai/ui/file-icon" +import { Icon } from "@opencode-ai/ui/icon" +import { Keybind } from "@opencode-ai/ui/keybind" +import { List } from "@opencode-ai/ui/list" +import { getDirectory, getFilename } from "@opencode-ai/core/util/path" +import { createMemo, createSignal, lazy, Match, Show, Switch } from "solid-js" +import { formatKeybind } from "@/context/command" +import { useServerSDK } from "@/context/server-sdk" +import { useLanguage } from "@/context/language" +import { usePlatform } from "@/context/platform" +import { useSettings } from "@/context/settings" +import { useSessionLayout } from "@/pages/session/session-layout" +import { decode64 } from "@/utils/base64" +import { getRelativeTime } from "@/utils/time" +import { + createCommandPaletteFileEntry, + createCommandPaletteFileOpener, + createCommandPaletteModel, + uniqueCommandPaletteEntries, + type CommandPaletteEntry, +} from "./command-palette" +import { DialogCommandPaletteV2 } from "./dialog-command-palette-v2" + +const DialogSelectFileV2 = lazy(() => + import("./dialog-select-directory-v2").then((module) => ({ default: module.DialogSelectDirectoryV2 })), +) +type DialogSelectFileMode = "all" | "files" + +export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFile?: (path: string) => void }) { + const platform = usePlatform() + const settings = useSettings() + const filesOnly = () => props.mode === "files" + + if (!filesOnly() && settings.general.newLayoutDesigns()) { + return + } + + if (filesOnly() && platform.platform === "desktop" && settings.general.newLayoutDesigns()) { + return + } + + return +} + +function DialogSelectFileDesktopV2(props: { onOpenFile?: (path: string) => void }) { + const language = useLanguage() + const serverSDK = useServerSDK() + const { params } = useSessionLayout() + const projectDirectory = createMemo(() => decode64(params.dir) ?? "") + const openFile = createCommandPaletteFileOpener(props.onOpenFile) + + return ( + { + if (typeof result !== "string") return + openFile(result) + }} + /> + ) +} + +function DialogSelectFileLegacy(props: { filesOnly: () => boolean; onOpenFile?: (path: string) => void }) { + const palette = createCommandPaletteModel(props) + const [grouped, setGrouped] = createSignal(false) + + const items = async (text: string) => { + const query = text.trim() + setGrouped(query.length > 0) + + if (!query && props.filesOnly()) { + const loaded = palette.file.tree.state("")?.loaded + const pending = loaded ? Promise.resolve() : palette.file.tree.list("") + const next = uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()]) + + if (loaded || next.length > 0) { + void pending + return next + } + + await pending + return uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()]) + } + + if (!query) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()] + + if (props.filesOnly()) { + const files = await palette.file.searchFiles(query) + const category = palette.language.t("palette.group.files") + return files.map((path) => createCommandPaletteFileEntry(path, category)) + } + + const [files, nextSessions] = await Promise.all([ + palette.file.searchFiles(query), + Promise.resolve(palette.sessions(query)), + ]) + const category = palette.language.t("palette.group.files") + const entries = files.map((path) => createCommandPaletteFileEntry(path, category)) + return [...palette.commandEntries(), ...nextSessions, ...entries] + } + + return ( + + item.id} + filterKeys={["title", "description", "category"]} + skipFilter={(item) => item.type === "file"} + groupBy={grouped() ? (item) => item.category : () => ""} + onMove={(item: CommandPaletteEntry | undefined) => palette.highlight(item)} + onSelect={(item: CommandPaletteEntry | undefined) => palette.select(item)} + > + {(item) => ( + +
+ +
+ + {getDirectory(item.path ?? "")} + + {getFilename(item.path ?? "")} +
+
+
+ } + > + +
+
+ {item.title} + + {item.description} + +
+ + {formatKeybind(item.keybind ?? "", palette.language.t)} + +
+
+ +
+
+ +
+ + {item.title} + + + + {item.description} + + +
+
+ + + {getRelativeTime(new Date(item.updated!).toISOString(), palette.language.t)} + + +
+
+
+ )} + + + ) +} diff --git a/packages/app-bundle/overlay/packages/app/src/components/help-button.tsx b/packages/app-bundle/overlay/packages/app/src/components/help-button.tsx new file mode 100644 index 00000000..06084057 --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/src/components/help-button.tsx @@ -0,0 +1,148 @@ +import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" +import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" +import { createSignal, Show } from "solid-js" +import { Drawer, DrawerClose, DrawerContent } from "@/components/ui/drawer" +import { usePlatform } from "@/context/platform" +import { useSettings } from "@/context/settings" +import introducingTabsVideo from "@/assets/help/introducing-tabs.mp4" +import homeImage from "@/assets/help/home.png" +import tabsImage from "@/assets/help/tabs.png" + +// TODO: wire to changelog / seen-state when available +const showPopover = () => true + +// can remove this after the tabs rollout has been out for a while +export function TabsInfoPopup() { + const settings = useSettings() + const platform = usePlatform() + const [drawerOpen, setDrawerOpen] = createSignal(false) + const windows = () => platform.platform === "desktop" && platform.os === "windows" + + return ( + + +
+ + +
+
+ + + } + class="absolute top-[10px] left-[-36px]" + /> + +
+

+ July 14 +

+ + } + /> + +
+
+

+ Introducing Tabs +

+
+

OpenCode Desktop is now built around tabs.

+ +

+ Start a new session in a tab, or open an existing session from any of your projects. Open a new tab when + you're starting something new, and close it when you're done. +

+

+ Keeping a few tabs open makes it easier to organize your active sessions. Rename tabs to something + memorable if you plan to keep them around. +

+

+ You'll find all your sessions and projects on the new Home screen. Selecting a session opens it in a tab. +

+ +

When you reopen the app, your tabs are still open.

+

+ The new design does not support Git Worktrees yet, it's coming soon. So if you'd prefer to continue using + the previous layout, you can switch between layouts in Settings. Just keep in mind that the new layout + will become permanent in a few weeks. +

+
+
+
+
+ ) +} diff --git a/packages/app-bundle/overlay/packages/app/src/components/prompt-input/context-items.tsx b/packages/app-bundle/overlay/packages/app/src/components/prompt-input/context-items.tsx new file mode 100644 index 00000000..9ff5701d --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/src/components/prompt-input/context-items.tsx @@ -0,0 +1,98 @@ +import { Component, For, Show } from "solid-js" +import { Dynamic } from "solid-js/web" +import { FileIcon } from "@opencode-ai/ui/file-icon" +import { IconButton } from "@opencode-ai/ui/icon-button" +import { Tooltip } from "@opencode-ai/ui/tooltip" +import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" +import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/core/util/path" +import type { ContextItem } from "@/context/prompt" + +type PromptContextItem = ContextItem & { key: string } + +type ContextItemsProps = { + items: PromptContextItem[] + active: (item: PromptContextItem) => boolean + openComment: (item: PromptContextItem) => void + remove: (item: PromptContextItem) => void + newLayoutDesigns: boolean + t: (key: string) => string +} + +export const PromptContextItems: Component = (props) => { + return ( + 0}> +
+ + {(item) => { + const directory = getDirectory(item.path) + const filename = getFilename(item.path) + const label = getFilenameTruncated(item.path, 14) + const selected = props.active(item) + + return ( + + + {directory} + + {filename} + + } + placement="top" + openDelay={800} + > +
props.openComment(item)} + > +
+ +
+ {label} + + {(sel) => ( + + {sel().startLine === sel().endLine + ? `:${sel().startLine}` + : `:${sel().startLine}-${sel().endLine}`} + + )} + +
+ { + e.stopPropagation() + props.remove(item) + }} + aria-label={props.t("prompt.context.removeFile")} + /> +
+ + {(comment) =>
{comment()}
} +
+
+
+ ) + }} +
+
+
+ ) +} diff --git a/packages/app-bundle/overlay/packages/app/src/components/prompt-input/image-attachments.tsx b/packages/app-bundle/overlay/packages/app/src/components/prompt-input/image-attachments.tsx new file mode 100644 index 00000000..3478337c --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/src/components/prompt-input/image-attachments.tsx @@ -0,0 +1,168 @@ +import { Component, For, Show } from "solid-js" +import { Icon } from "@opencode-ai/ui/icon" +import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" +import { Tooltip } from "@opencode-ai/ui/tooltip" +import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" +import { AttachmentCardV2 } from "@opencode-ai/session-ui/v2/attachment-card-v2" +import { CommentCardV2 } from "@opencode-ai/session-ui/v2/comment-card-v2" +import { typeLabel } from "@opencode-ai/session-ui/message-file" +import type { ContextItem, ImageAttachmentPart } from "@/context/prompt" +import "./image-attachments.css" + +type PromptCommentItem = ContextItem & { key: string } + +type PromptImageAttachmentsProps = { + attachments: ImageAttachmentPart[] + onOpen: (attachment: ImageAttachmentPart) => void + onRemove: (id: string) => void + removeLabel: string + newLayoutDesigns: boolean + comments?: PromptCommentItem[] + commentActive?: (item: PromptCommentItem) => boolean + onOpenComment?: (item: PromptCommentItem) => void + onRemoveComment?: (item: PromptCommentItem) => void +} + +const fallbackClass = "size-16 rounded-md bg-surface-base flex items-center justify-center border border-border-base" +const imageClass = + "size-16 rounded-md object-cover border border-border-base hover:border-border-strong-base transition-colors" +const imageClassV2 = "w-[58px] h-[46px] rounded-sm object-cover" +// inset box-shadows do not paint over content, so the hairline is a separate overlay +const imageHairlineClassV2 = + "absolute inset-0 rounded-sm shadow-[inset_0_0_0_0.5px_var(--v2-border-border-base)] pointer-events-none" +const removeClass = + "absolute -top-1.5 -right-1.5 size-5 rounded-full bg-surface-raised-stronger-non-alpha border border-border-base flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity hover:bg-surface-raised-base-hover" +const removeClassV2 = + "absolute -top-1 -right-1 size-4 rounded-full bg-v2-icon-icon-muted outline-solid outline-1 outline-v2-icon-icon-contrast flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity" +const nameClass = "absolute bottom-0 left-0 right-0 px-1 py-0.5 bg-black/50 rounded-b-md" + +export const PromptImageAttachments: Component = (props) => { + return ( + 0 || (props.newLayoutDesigns && (props.comments?.length ?? 0) > 0)}> +
+
+ + + {(item) => ( +
+ + props.onOpenComment?.(item)} + /> + + +
+ )} +
+
+ + {(attachment) => { + const image = attachment.mime.startsWith("image/") + const media = () => ( + + +
+ } + > + + {typeLabel(attachment.filename, attachment.mime)} + + + } + > + {attachment.filename} props.onOpen(attachment)} + /> + + ) + const name = () => ( +
+ {attachment.filename} +
+ ) + const remove = () => ( + + ) + // v2 keeps the remove button outside the tooltip trigger so hovering it dismisses the tooltip + return ( + +
+ {media()} + {name()} + {remove()} +
+ + } + > +
+ + {media()} + +
+ + + {remove()} +
+
+ ) + }} + +
+ +
+
+ +
+ + ) +} diff --git a/packages/app-bundle/overlay/packages/app/src/components/prompt-input/slash-popover.tsx b/packages/app-bundle/overlay/packages/app/src/components/prompt-input/slash-popover.tsx new file mode 100644 index 00000000..e2579296 --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/src/components/prompt-input/slash-popover.tsx @@ -0,0 +1,376 @@ +import { Component, For, Match, Show, Switch } from "solid-js" +import { FileIcon } from "@opencode-ai/ui/file-icon" +import { Icon } from "@opencode-ai/ui/icon" +import { Tag } from "@opencode-ai/ui/v2/badge-v2" +import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2" +import { getDirectory, getFilename } from "@opencode-ai/core/util/path" + +export type AtOption = + | { type: "agent"; name: string; display: string } + | { + type: "resource" + name: string + uri: string + client: string + display: string + description?: string + mime?: string + } + | { type: "reference"; name: string; path: string; display: string; description: string } + | { type: "file"; path: string; display: string; recent?: boolean } + +export interface SlashCommand { + id: string + trigger: string + title: string + description?: string + keybind?: string + type: "builtin" | "custom" + source?: "command" | "mcp" | "skill" +} + +type PromptPopoverProps = { + popover: "at" | "slash" | null + setSlashPopoverRef: (el: HTMLDivElement) => void + atFlat: AtOption[] + atActive?: string + atKey: (item: AtOption) => string + setAtActive: (id: string) => void + onAtSelect: (item: AtOption) => void + slashFlat: SlashCommand[] + slashActive?: string + setSlashActive: (id: string) => void + onSlashSelect: (item: SlashCommand) => void + slashMenu: boolean + slashMenuQuery: string + onSlashMenuInput: (value: string) => void + onSlashMenuKeyDown: (event: KeyboardEvent) => void + commandKeybind: (id: string) => string | undefined + commandKeybindParts: (id: string) => string[] + newLayoutDesigns: boolean + t: (key: string) => string +} + +export const PromptPopover: Component = (props) => { + return ( + +
{ + if (props.popover === "slash") props.setSlashPopoverRef(el) + }} + class="absolute inset-x-0 -top-2 -translate-y-full origin-bottom-left max-h-80 min-h-10 + overflow-auto no-scrollbar flex flex-col p-2" + classList={{ + "z-[70] rounded-md bg-v2-background-bg-base shadow-[var(--v2-elevation-raised)]": props.newLayoutDesigns, + "rounded-lg bg-surface-raised-stronger-non-alpha shadow-[var(--shadow-lg-border-base)]": + !props.newLayoutDesigns, + }} + onMouseDown={(e) => e.preventDefault()} + > + + + 0} + fallback={ +
+ {props.t("prompt.popover.emptyResults")} +
+ } + > + + {(item) => { + const key = props.atKey(item) + + if (item.type === "agent") { + return ( + + ) + } + + if (item.type === "resource") { + return ( + + ) + } + + if (item.type === "reference") { + return ( + + ) + } + + const isDirectory = item.path.endsWith("/") + const directory = isDirectory ? item.path : getDirectory(item.path) + const filename = isDirectory ? "" : getFilename(item.path) + + return ( + + ) + }} + +
+
+ + +
+ requestAnimationFrame(() => el.focus())} + value={props.slashMenuQuery} + onInput={(event) => props.onSlashMenuInput(event.currentTarget.value)} + onKeyDown={props.onSlashMenuKeyDown} + onMouseDown={(event) => event.stopPropagation()} + aria-label={props.t("prompt.menu.commands")} + placeholder="/" + class="w-full bg-transparent outline-none text-[13px] leading-5 text-v2-text-text-base placeholder:text-v2-text-text-faint" + /> +
+
+ 0} + fallback={ +
+ {props.t("prompt.popover.emptyCommands")} +
+ } + > + + {(cmd) => { + const keybind = () => props.commandKeybind(cmd.id) + const keybindParts = () => props.commandKeybindParts(cmd.id) + return ( + + ) + }} + +
+
+
+
+
+ ) +} diff --git a/packages/app-bundle/overlay/packages/app/src/components/report-bug-button.stories.tsx b/packages/app-bundle/overlay/packages/app/src/components/report-bug-button.stories.tsx index b838e92e..0f1f38e4 100644 --- a/packages/app-bundle/overlay/packages/app/src/components/report-bug-button.stories.tsx +++ b/packages/app-bundle/overlay/packages/app/src/components/report-bug-button.stories.tsx @@ -134,7 +134,7 @@ export const Schemes = { display: "grid", gap: "12px", padding: "16px", - "border-radius": "12px", + "border-radius": "var(--radius-lg)", background: "var(--v2-background-bg-base)", border: "1px solid var(--v2-border-border-base)", }} diff --git a/packages/app-bundle/overlay/packages/app/src/components/session/session-header.tsx b/packages/app-bundle/overlay/packages/app/src/components/session/session-header.tsx index ddb0aa97..99cd93dd 100644 --- a/packages/app-bundle/overlay/packages/app/src/components/session/session-header.tsx +++ b/packages/app-bundle/overlay/packages/app/src/components/session/session-header.tsx @@ -22,6 +22,8 @@ import { useLayout } from "@/context/layout" import { usePlatform } from "@/context/platform" import { useServer, ServerConnection } from "@/context/server" import { useSettings } from "@/context/settings" +import { SessionTabStatusDot, sessionTabStatus } from "@/pages/layout/session-tab-status" +import { useSessionTabAvatarState } from "@/pages/layout/project-avatar-state" import { useSync } from "@/context/sync" import { sessionHasOpenTab, useTabs } from "@/context/tabs" import { useTerminal } from "@/context/terminal" @@ -622,7 +624,7 @@ function SessionHeaderV2Actions(props: { state: SessionHeaderV2ActionsState }) { // (amicode#273) inside the session header: tabbed Active/Archived, search, // open-tab indicators, archive/unarchive actions, and cursor-based pagination. const SESSION_DROPDOWN_ROW = - "flex min-w-0 w-full shrink-0 cursor-default items-center rounded-[6px] bg-transparent text-left transition-[background-color,color,box-shadow] duration-[120ms] ease-in-out focus-visible:outline-none h-7 gap-2 px-1.5 [font-weight:440] text-v2-text-text-muted hover:bg-v2-overlay-simple-overlay-hover hover:text-v2-text-text-base focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:text-v2-text-text-base" + "flex min-w-0 w-full shrink-0 cursor-default items-center rounded-sm bg-transparent text-left transition-[background-color,color,box-shadow] duration-[120ms] ease-in-out focus-visible:outline-none h-7 gap-2 px-1.5 [font-weight:440] text-v2-text-text-muted hover:bg-v2-overlay-simple-overlay-hover hover:text-v2-text-text-base focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:text-v2-text-text-base" function SessionChatsDropdown() { const tabs = useTabs() @@ -894,7 +896,7 @@ function SessionChatsDropdown() { ref={triggerRef} type="button" data-action="session-chats-toggle-flyout" - class="flex shrink-0 items-center justify-center rounded-[6px] border-none bg-transparent p-1.5 cursor-pointer text-v2-icon-icon-muted hover:text-v2-icon-icon-base hover:bg-v2-overlay-simple-overlay-hover transition-colors" + class="flex shrink-0 items-center justify-center rounded-sm border-none bg-transparent p-1.5 cursor-pointer text-v2-icon-icon-muted hover:text-v2-icon-icon-base hover:bg-v2-overlay-simple-overlay-hover transition-colors" aria-label="Sessions" onClick={() => setOpen(!open())} > @@ -919,7 +921,7 @@ function SessionChatsDropdown() { gap: "12px", overflow: "hidden auto", border: "1px solid var(--v2-border-border-base)", - "border-radius": "10px", + "border-radius": "var(--radius-md)", background: "var(--v2-background-bg-base)", "box-shadow": "0 14px 40px -18px rgba(0, 0, 0, 0.55)", padding: "14px", @@ -928,7 +930,7 @@ function SessionChatsDropdown() {
{/* Search */}
+ ) } diff --git a/packages/app-bundle/overlay/packages/app/src/components/settings-v2/settings-v2.css b/packages/app-bundle/overlay/packages/app/src/components/settings-v2/settings-v2.css index f6f1528e..0d9eec2e 100644 --- a/packages/app-bundle/overlay/packages/app/src/components/settings-v2/settings-v2.css +++ b/packages/app-bundle/overlay/packages/app/src/components/settings-v2/settings-v2.css @@ -84,7 +84,7 @@ } [data-component="settings-v2-list"] { - border-radius: 8px; + border-radius: var(--radius-md); background-color: var(--v2-background-bg-layer-01); padding-inline: 20px; box-shadow: inset 0 0 0 0.5px var(--v2-border-border-muted); @@ -397,7 +397,7 @@ height: 28px; padding: 4px 8px 4px 4px; border: 0; - border-radius: 4px; + border-radius: var(--radius-sm); background: transparent; color: var(--v2-text-text-base); } @@ -504,7 +504,7 @@ flex-direction: column; gap: 0; padding: 20px; - border-radius: 6px; + border-radius: var(--radius-sm); } .settings-v2-shortcuts [data-component="settings-v2-list"] > div { @@ -538,7 +538,7 @@ padding: 6px 8px; margin: -6px -8px; border: 0; - border-radius: 2px; + border-radius: var(--radius-xs); background: transparent; cursor: pointer; font-style: normal; @@ -565,7 +565,7 @@ .settings-v2-keybind-button--active { color: var(--v2-text-text-faint); - border-radius: 2px; + border-radius: var(--radius-xs); background-color: var(--v2-background-bg-layer-02); } @@ -612,7 +612,7 @@ flex-direction: column; gap: 0; padding: 20px; - border-radius: 6px; + border-radius: var(--radius-sm); } .settings-v2-servers-row { @@ -688,7 +688,7 @@ width: 480px; max-width: calc(100vw - 32px); height: auto; - border-radius: 8px; + border-radius: var(--radius-md); align-items: stretch; } @@ -728,194 +728,377 @@ color: var(--v2-state-fg-danger); } -/* Permissions tab */ +/* Permissions tab + Geometry/colour via the design-system tokens (design-polish.css). The + directory × action matrix is responsive: the tier card is an inline-size + container; the DEFAULT layout is stacked (pattern line, then a 2×2 of + labelled selects) because the settings dialog gives a tier card ~390px at + a normal window, and the table layout only switches on once the card is + wide enough to hold five columns. */ +.settings-v2-tab-header.settings-v2-permissions-header { + gap: var(--space-4); + padding-bottom: var(--space-6); +} .settings-v2-permissions-intro { - font-size: 13px; - font-weight: 440; + font-size: var(--font-size-md); + font-weight: 400; line-height: 18px; color: var(--v2-text-text-muted); margin: 0; } .settings-v2-permissions { - gap: 20px; + gap: var(--space-5); + min-width: 0; } .settings-v2-permissions-card { - border-radius: 8px; + container-type: inline-size; + min-width: 0; + border-radius: var(--radius-lg); background-color: var(--v2-background-bg-layer-01); - padding: 16px 20px; - box-shadow: inset 0 0 0 0.5px var(--v2-border-border-muted); + padding: var(--space-4); + box-shadow: inset 0 0 0 var(--border-width) var(--v2-border-border-muted); display: flex; flex-direction: column; - gap: 16px; + gap: var(--space-4); } .settings-v2-permissions-card-header { display: flex; align-items: center; justify-content: space-between; - gap: 12px; + gap: var(--space-3); } .settings-v2-permissions-card-title-row { display: flex; align-items: center; - gap: 8px; - flex: 1; + gap: var(--space-2); + flex: 1 1 auto; min-width: 0; } .settings-v2-permissions-card-title { - font-size: 14px; - font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.settings-v2-permissions-card-title { + font-size: var(--font-size-base, 14px); + font-weight: var(--font-weight-emphasis); color: var(--v2-text-text-base); margin: 0; } .settings-v2-permissions-badge { - font-size: 11px; + font-size: var(--font-size-xs); + flex-shrink: 0; + white-space: nowrap; } .settings-v2-permissions-card-actions { display: flex; - gap: 4px; + gap: var(--space-1); flex-shrink: 0; + margin-left: auto; } + +/* ── directory × action matrix ── + Stacked (default): each directory rule is a small settings list — the + pattern as its heading with the remove control, then one row per action + in the General-tab idiom (label left, inline select right). Table mode + (container ≥ 540px): header row + one grid row per pattern. */ .settings-v2-permissions-matrix { display: flex; flex-direction: column; - gap: 8px; -} -.settings-v2-permissions-matrix-header, -.settings-v2-permissions-matrix-row { - display: grid; - grid-template-columns: 1fr 90px 90px 90px 90px 32px; - gap: 8px; - align-items: center; + gap: var(--space-3); + min-width: 0; } .settings-v2-permissions-matrix-header { - font-size: 11px; - font-weight: 600; + display: none; /* table mode only — see the container query below */ + font-size: var(--font-size-xs); + font-weight: var(--font-weight-emphasis); color: var(--v2-text-text-faint); text-transform: uppercase; letter-spacing: 0.05em; } -.settings-v2-permissions-matrix-corner { - font-size: 11px; +.settings-v2-permissions-matrix-head { + text-align: right; +} +.settings-v2-permissions-rule { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + grid-template-areas: + "pattern remove" + "read read" + "write write" + "execute execute" + "network network"; + column-gap: var(--space-2); + row-gap: 0; + align-items: center; + min-width: 0; + border-radius: var(--radius-md); + border: var(--border-width) solid var(--v2-border-border-muted); + padding: var(--space-1) var(--space-3); } .settings-v2-permissions-matrix-pattern { - font-size: 13px; + grid-area: pattern; + font-size: var(--font-size-md); font-family: var(--font-mono, monospace); color: var(--v2-text-text-base); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + min-width: 0; + padding-block: var(--space-2); + justify-self: start; + max-width: 100%; } -.settings-v2-permissions-matrix-head { - text-align: center; +/* exception patterns are click-to-edit */ +button.settings-v2-permissions-matrix-pattern { + border: 0; + background: transparent; + padding-inline: 0; + cursor: text; + text-align: left; + border-radius: var(--radius-sm); + text-decoration: underline dotted transparent; + text-underline-offset: 3px; + transition: text-decoration-color 0.16s ease; } -.settings-v2-permissions-select { - width: 100%; - padding: 4px 6px; - border-radius: 6px; - border: 0.5px solid var(--v2-border-border-base); - background: var(--v2-background-bg-base); - font-size: 12px; +button.settings-v2-permissions-matrix-pattern:hover { + text-decoration-color: var(--v2-text-text-muted); +} +button.settings-v2-permissions-matrix-pattern:focus-visible { + outline: 2px solid var(--v2-border-border-focus); + outline-offset: 2px; +} +.settings-v2-permissions-pattern-input { + grid-area: pattern; + font-family: var(--font-mono, monospace); + min-width: 0; +} +/* the ** rule reads as a plain label, not a glob */ +.settings-v2-permissions-matrix-pattern--default { + font-family: inherit; + font-weight: var(--font-weight-emphasis); +} +.settings-v2-permissions-group-label { + margin: var(--space-2) 0 0; + font-size: var(--font-size-xs); + font-weight: var(--font-weight-emphasis); + color: var(--v2-text-text-faint); + text-transform: uppercase; + letter-spacing: 0.05em; +} +/* the default rule sits directly in the card — no box, its rows are the + card's own rows; exceptions are boxed so they read as overrides */ +.settings-v2-permissions-rule[data-default] { + border: 0; + padding-inline: 0; +} +.settings-v2-permissions-rule[data-default] .settings-v2-permissions-matrix-remove { + display: none; +} +.settings-v2-permissions-matrix-remove { + grid-area: remove; +} +.settings-v2-permissions-matrix-cell { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); + min-width: 0; + padding-block: var(--space-1); + border-top: var(--border-width) solid var(--v2-border-border-muted); +} +.settings-v2-permissions-matrix-cell[data-group="read"] { + grid-area: read; +} +.settings-v2-permissions-matrix-cell[data-group="write"] { + grid-area: write; +} +.settings-v2-permissions-matrix-cell[data-group="execute"] { + grid-area: execute; +} +.settings-v2-permissions-matrix-cell[data-group="network"] { + grid-area: network; +} +.settings-v2-permissions-matrix-cell-label { + font-size: var(--font-size-md); + font-weight: 500; color: var(--v2-text-text-base); + cursor: default; } -.settings-v2-permissions-select--danger { - border-color: var(--v2-state-fg-danger); - background: color-mix(in srgb, var(--v2-state-fg-danger) 8%, var(--v2-background-bg-base)); +/* execute / network = allow is the risky cell: value reads in the danger + colour, and a small marker so colour isn't the only signal */ +/* outranks select-v2's own value-text rule (lazy-loaded after this file) */ +[data-component="select-v2"][data-appearance] + [data-slot="select-v2-value-text"].settings-v2-permissions-effect--danger { + color: var(--v2-state-fg-danger); + text-decoration: underline dotted; + text-underline-offset: 3px; +} +.settings-v2-permissions-matrix-cell[data-danger] .settings-v2-permissions-matrix-cell-label::after { + content: " · risky"; + font-size: var(--font-size-xs); + font-weight: 400; color: var(--v2-state-fg-danger); } .settings-v2-permissions-matrix-help { - font-size: 11px; + font-size: var(--font-size-xs); color: var(--v2-text-text-faint); margin: 0; } + +/* table mode: five columns once the card can actually hold them */ +@container (min-width: 540px) { + .settings-v2-permissions-matrix { + gap: var(--space-2); + } + .settings-v2-permissions-matrix-header, + .settings-v2-permissions-rule { + display: grid; + grid-template-columns: minmax(0, 1fr) repeat(4, minmax(72px, 96px)) 28px; + grid-template-areas: none; + gap: var(--space-2); + align-items: center; + } + .settings-v2-permissions-rule { + border: 0; + border-radius: 0; + padding: 0; + } + .settings-v2-permissions-rule + .settings-v2-permissions-rule { + border-top: var(--border-width) solid var(--v2-border-border-muted); + padding-top: var(--space-2); + } + .settings-v2-permissions-rule[data-default] .settings-v2-permissions-matrix-remove { + display: block; + visibility: hidden; + } + .settings-v2-permissions-group-label { + margin-top: var(--space-1); + } + .settings-v2-permissions-pattern-input { + grid-area: auto; + } + .settings-v2-permissions-matrix-pattern, + .settings-v2-permissions-matrix-remove, + .settings-v2-permissions-matrix-cell[data-group] { + grid-area: auto; + } + .settings-v2-permissions-matrix-pattern { + padding-block: 0; + } + .settings-v2-permissions-matrix-cell { + justify-content: flex-end; + border-top: 0; + padding-block: 0; + } + /* the header row carries the column names; keep the per-cell label for + assistive tech only */ + .settings-v2-permissions-matrix-cell-label { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + } +} + +/* ── model assignment ── */ .settings-v2-permissions-models { display: flex; flex-direction: column; - gap: 8px; - padding-top: 8px; - border-top: 0.5px solid var(--v2-border-border-base); + gap: var(--space-2); + padding-top: var(--space-2); + border-top: var(--border-width) solid var(--v2-border-border-base); + min-width: 0; } .settings-v2-permissions-models-title { - font-size: 12px; - font-weight: 600; + font-size: var(--font-size-sm); + font-weight: var(--font-weight-emphasis); color: var(--v2-text-text-base); margin: 0; } .settings-v2-permissions-models-empty { - font-size: 12px; + font-size: var(--font-size-sm); color: var(--v2-text-text-muted); margin: 0; } .settings-v2-permissions-models-list { display: flex; flex-wrap: wrap; - gap: 6px; + gap: var(--space-2); } .settings-v2-permissions-model-chip { display: inline-flex; align-items: center; - gap: 6px; - padding: 4px 8px; - border-radius: 999px; + gap: var(--space-2); + max-width: 100%; + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-full); background: var(--v2-background-bg-layer-02); - font-size: 12px; + font-size: var(--font-size-sm); color: var(--v2-text-text-base); - border: 0.5px solid var(--v2-border-border-muted); + border: var(--border-width) solid var(--v2-border-border-muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .settings-v2-permissions-model-remove { border: 0; background: transparent; color: var(--v2-text-text-muted); cursor: pointer; - padding: 0 2px; + padding: 0 var(--space-1); + border-radius: var(--radius-sm); } .settings-v2-permissions-model-remove:hover { color: var(--v2-state-fg-danger); } -.settings-v2-permissions-model-select { - max-width: 320px; - width: 100%; - padding: 6px 8px; - border-radius: 6px; - border: 0.5px solid var(--v2-border-border-base); - background: var(--v2-background-bg-base); - font-size: 12px; +.settings-v2-permissions-model-remove:focus-visible { + outline: 2px solid var(--v2-border-border-focus); } .settings-v2-permissions-models-hint { - font-size: 11px; + font-size: var(--font-size-xs); color: var(--v2-text-text-faint); } -[data-component="permissions-tab"] .settings-v2-permissions-select--danger { - box-shadow: 0 0 0 1px var(--v2-state-fg-danger); -} .settings-v2-permissions-model-picker-help { - font-size: 11px; + font-size: var(--font-size-xs); color: var(--v2-text-text-faint); - margin: 0 0 6px; + margin: 0 0 var(--space-2); } .settings-v2-permissions-model-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); - gap: 6px; + gap: var(--space-1); max-height: 180px; overflow-y: auto; - padding: 8px; - border: 0.5px solid var(--v2-border-border-base); - border-radius: 6px; + padding: var(--space-2); + border: var(--border-width) solid var(--v2-border-border-base); + border-radius: var(--radius-md); background: var(--v2-background-bg-base); + min-width: 0; } .settings-v2-permissions-model-option { display: flex; align-items: center; - gap: 8px; - font-size: 12px; + gap: var(--space-2); + font-size: var(--font-size-sm); color: var(--v2-text-text-base); cursor: pointer; - padding: 4px 6px; - border-radius: 4px; + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-sm); + min-width: 0; } .settings-v2-permissions-model-option:hover { background: var(--v2-background-bg-layer-02); } +.settings-v2-permissions-model-option:focus-within { + outline: 2px solid var(--v2-border-border-focus); + outline-offset: -2px; +} .settings-v2-permissions-model-option input[type="checkbox"] { flex-shrink: 0; } diff --git a/packages/app-bundle/overlay/packages/app/src/components/solver-switch-banner.tsx b/packages/app-bundle/overlay/packages/app/src/components/solver-switch-banner.tsx new file mode 100644 index 00000000..70c15c5d --- /dev/null +++ b/packages/app-bundle/overlay/packages/app/src/components/solver-switch-banner.tsx @@ -0,0 +1,99 @@ +import { Show, createEffect, createSignal, onCleanup } from "solid-js" +import { + solverSwitchExpired, + solverSwitchLabel, + solverSwitchPhase, + type SolverSwitchPhase, +} from "@opencode-ai/ui/amicode-solver-switch" +import type { SolverMode } from "@opencode-ai/ui/amicode-solver-toggle" +import { useServerSDK } from "@/context/server-sdk" + +// Amicode: the visible half of a solver switch (opencode#78 follow-up). +// +// #221 made the switch REAL — the extension watcher re-preps the session config +// and restarts the opencode server. The webview survives that; its SSE stream +// does not. Nothing narrated the gap, so a deliberate tier change looked like a +// hang (the reconnect loop retries silently, which reads as an endless +// "thinking" wave). +// +// Scope is deliberately narrow. The old ConnectionBanner spoke for EVERY drop +// and was unmounted on 2026-08-07 (f696388/a03aa04) after opencode#132's stuck +// pill; this one only ever speaks for a switch the app itself requested, so it +// cannot get stuck on a transient blip and does not reinstate that decision. + +// Module-level: the two call sites that can start a switch live in different +// component trees (the popover's connections state and the home chrome's), and +// both must reach the one banner in the layout. +const [target, setTarget] = createSignal() +const [startedAt, setStartedAt] = createSignal(0) + +/** Announce a switch the app just requested. `hp` rides a validated credential, + * `piccolo` rides POST /amicode/solver-mode — this only mirrors that request, + * it never causes one. */ +export function beginSolverSwitch(mode: SolverMode) { + setTarget(mode) + setStartedAt(Date.now()) +} + +function endSolverSwitch() { + setTarget(undefined) + setStartedAt(0) +} + +export function SolverSwitchBanner() { + const sdk = useServerSDK() + const [sawDrop, setSawDrop] = createSignal(false) + const [elapsed, setElapsed] = createSignal(0) + + // One clock, alive only while a switch is outstanding: it drives the expiry + // check, which has nothing else to react to (the stall case is defined by the + // absence of any status change). + createEffect(() => { + if (!target()) { + setSawDrop(false) + setElapsed(0) + return + } + const timer = setInterval(() => setElapsed(Date.now() - startedAt()), 500) + onCleanup(() => clearInterval(timer)) + }) + + // Latch the drop: once the server has gone down, coming back up is the + // switch completing rather than the request still waiting to be picked up. + createEffect(() => { + if (target() && sdk().event.status() === "disconnected") setSawDrop(true) + }) + + const phase = (): SolverSwitchPhase => + solverSwitchPhase({ + target: target(), + connected: sdk().event.status() === "connected", + sawDrop: sawDrop(), + }) + + createEffect(() => { + const current = phase() + if (current === "idle") return + // Hold the completed chip briefly — it is the only confirmation the user + // gets inside the app, and the extension's toast lands outside the webview. + if (current === "ready") { + const done = setTimeout(endSolverSwitch, 3000) + onCleanup(() => clearTimeout(done)) + return + } + if (solverSwitchExpired(current, elapsed())) endSolverSwitch() + }) + + const label = () => solverSwitchLabel(phase(), target()) + + return ( + + {(text) => ( +
+