From 8b54abc3042b2da9111c5f2fb02179dcee7469df Mon Sep 17 00:00:00 2001 From: aaron Date: Sun, 23 Aug 2026 11:34:14 -0400 Subject: [PATCH 1/4] fix(app-bundle): re-extract overlay at fork v1.18.10-amicode.16 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repairs the #513 drift-gate break on main: the overlay hand-edit is superseded by the fork's canonical question-default implementation (opencode#232, included in the .16 tag). 80 files added, 63 changed — the full UI wave (workspace engine, retheme, status-dot semantics, theme fixes). Round-trip verified: all 502 files byte-identical to the tag; drift gate PASS. Closes #530. Fork tag tracked by harmoniqs/opencode#238. --- packages/app-bundle/manifest.json | 401 ++++-- .../session-timeline-lifecycle-state.spec.ts | 111 ++ .../overlay/packages/app/index.html | 4 +- .../app/public/assets/DMSans-Variable.woff2 | Bin 0 -> 62556 bytes .../packages/app/public/oc-theme-preload.js | 22 +- .../overlay/packages/app/src/app.tsx | 2 +- .../components/amicode-defaults-capsule.tsx | 17 +- .../app/src/components/connection-banner.tsx | 2 +- .../components/dialog-command-palette-v2.css | 220 +++ .../components/dialog-command-palette-v2.tsx | 342 +++++ .../components/dialog-connect-provider.tsx | 1179 +++++++++++++++++ .../src/components/dialog-edit-project-v2.tsx | 156 +++ .../src/components/dialog-edit-project.tsx | 170 +++ .../src/components/dialog-release-notes.tsx | 144 ++ .../components/dialog-select-directory-v2.css | 107 ++ .../app/src/components/dialog-select-file.tsx | 189 +++ .../app/src/components/help-button.tsx | 148 +++ .../components/prompt-input/context-items.tsx | 98 ++ .../prompt-input/image-attachments.tsx | 168 +++ .../components/prompt-input/slash-popover.tsx | 376 ++++++ .../components/report-bug-button.stories.tsx | 2 +- .../src/components/session/session-header.tsx | 43 +- .../components/session/session-new-view.tsx | 8 +- .../session-sortable-terminal-tab-v2.tsx | 284 ++++ .../src/components/settings-v2/general.tsx | 25 - .../components/settings-v2/permissions.tsx | 211 ++- .../components/settings-v2/settings-v2.css | 349 +++-- .../src/components/solver-switch-banner.tsx | 99 ++ .../src/components/status-popover-body.tsx | 27 + .../status-popover-indicator.test.ts | 60 + .../components/status-popover-indicator.ts | 32 + .../app/src/components/status-popover.tsx | 2 +- .../packages/app/src/components/terminal.tsx | 16 +- .../app/src/components/titlebar-tab-nav.css | 2 +- .../app/src/components/titlebar-tab-nav.tsx | 405 ++++++ .../src/components/titlebar-tab-popover.css | 127 ++ .../packages/app/src/components/titlebar.tsx | 9 +- .../packages/app/src/components/ui/drawer.tsx | 119 ++ .../app/src/components/windows-app-menu.tsx | 131 ++ .../app/src/context/server-sdk.test.ts | 42 +- .../packages/app/src/context/server-sdk.tsx | 26 +- .../packages/app/src/design-polish.css | 222 +++- .../overlay/packages/app/src/index.css | 34 +- .../overlay/packages/app/src/pages/home.tsx | 71 +- .../app/src/pages/home/home-projects-view.tsx | 615 +++++++++ .../app/src/pages/home/home-sessions-view.tsx | 12 +- .../packages/app/src/pages/layout-new.tsx | 5 + .../src/pages/layout/project-avatar-state.ts | 56 + .../src/pages/layout/session-tab-avatar.tsx | 40 +- .../pages/layout/session-tab-status.test.ts | 20 + .../src/pages/layout/session-tab-status.tsx | 79 ++ .../new-session-draft-controller.ts | 17 +- .../new-session/new-session-view.test.ts | 26 + .../pages/new-session/new-session-view.tsx | 23 +- .../packages/app/src/pages/session.tsx | 4 +- .../composer/session-bug-dock.stories.tsx | 2 +- .../composer/session-composer-region.tsx | 4 +- .../composer/session-question-dock.tsx | 11 +- .../composer/session-revert-dock.stories.tsx | 124 ++ .../composer/todo-panel-motion.stories.tsx | 622 +++++++++ .../src/pages/session/session-side-panel.tsx | 26 +- .../src/pages/session/terminal-panel-v2.tsx | 352 +++++ .../session/timeline/message-timeline.tsx | 14 +- .../packages/app/src/theme-preload.test.ts | 69 + .../app/src/utils/webview-context-menu.ts | 4 +- .../app/src/wsl/dialog-add-server.tsx | 463 +++++++ .../app/src/wsl/dialog-add-wsl-server.css | 392 ++++++ .../overlay/packages/app/src/wsl/settings.tsx | 173 +++ .../core/src/database/migration.gen.ts | 1 + .../20260820000001_add_session_directories.ts | 11 + .../packages/core/src/location-mutation.ts | 170 +++ .../overlay/packages/core/src/location.ts | 41 + .../overlay/packages/core/src/session/sql.ts | 177 +++ .../core/src/system-context/builtins.ts | 58 + .../packages/core/src/tool/question.ts | 3 +- .../packages/core/test/fixture/location.ts | 27 + .../core/test/location-mutation.test.ts | 226 ++++ .../core/test/system-context/builtins.test.ts | 211 +++ .../packages/core/test/tool-question.test.ts | 26 + .../overlay/packages/schema/src/location.ts | 26 + .../packages/sdk/js/src/v2/gen/types.gen.ts | 7 +- .../session-ui/src/components/basic-tool.css | 358 +++++ .../src/components/basic-tool.stories.tsx | 133 ++ .../session-ui/src/components/basic-tool.tsx | 342 +++++ .../src/components/line-comment-styles.ts | 292 ++++ .../session-ui/src/components/markdown.css | 8 +- .../src/components/message-part.css | 18 +- .../src/components/message-part.tsx | 17 +- .../src/components/session-review.css | 247 ++++ .../src/components/session-turn.tsx | 542 ++++++++ .../src/v2/components/attachment-card-v2.css | 70 + .../src/v2/components/basic-tool-v2.css | 163 +++ .../src/v2/components/basic-tool-v2.tsx | 138 ++ .../src/v2/components/prompt-input/index.tsx | 4 +- .../src/v2/components/session-review-v2.css | 2 +- .../src/v2/components/tool-error-card-v2.css | 200 +++ .../ui/src/amicode/amico-presence.stories.tsx | 4 +- .../ui/src/amicode/amico-wave.stories.tsx | 4 +- .../packages/ui/src/amicode/amicode.css | 10 +- .../packages/ui/src/amicode/brain-engine.ts | 8 +- .../ui/src/amicode/connection-icon.tsx | 4 +- .../ui/src/amicode/connection-picker.tsx | 2 +- .../ui/src/amicode/context-tree-engine.ts | 6 +- .../packages/ui/src/amicode/home-cards.tsx | 16 +- .../ui/src/amicode/onboarding-wizard.tsx | 6 +- .../packages/ui/src/amicode/run-gallery.tsx | 2 +- .../ui/src/amicode/solver-switch.test.ts | 63 + .../packages/ui/src/amicode/solver-switch.ts | 60 + .../ui/src/amicode/solver-toggle.test.ts | 17 + .../packages/ui/src/amicode/solver-toggle.tsx | 12 +- .../ui/src/amicode/widget-tokens.test.ts | 26 +- .../packages/ui/src/amicode/widget-tokens.ts | 29 +- .../src/components/amicode-solver-switch.tsx | 10 + .../src/components/amicode-solver-toggle.tsx | 1 + .../ui/src/components/context-menu.css | 134 ++ .../src/components/context-menu.stories.tsx | 113 ++ .../ui/src/components/dropdown-menu.css | 135 ++ .../packages/ui/src/components/hover-card.css | 61 + .../ui/src/components/image-preview.css | 63 + .../ui/src/components/list.stories.tsx | 170 +++ .../packages/ui/src/components/popover.css | 98 ++ .../src/components/resize-handle.stories.tsx | 161 +++ .../packages/ui/src/components/select.css | 199 +++ .../packages/ui/src/components/tabs.css | 861 ++++++++++++ .../ui/src/components/text-reveal.stories.tsx | 320 +++++ .../components/text-strikethrough.stories.tsx | 279 ++++ .../components/thinking-heading.stories.tsx | 853 ++++++++++++ .../packages/ui/src/components/toast.css | 236 ++++ .../packages/ui/src/components/tooltip.css | 74 ++ .../overlay/packages/ui/src/styles/index.css | 1 - .../overlay/packages/ui/src/styles/theme.css | 16 +- .../overlay/packages/ui/src/theme/context.tsx | 11 +- .../packages/ui/src/theme/default-themes.ts | 119 ++ .../ui/src/theme/themes/harmoniqs.json | 482 +++++++ .../ui/src/v2/components/badge-v2.css | 37 + .../packages/ui/src/v2/components/menu-v2.css | 190 +++ .../ui/src/v2/components/menu-v2.stories.tsx | 216 +++ .../ui/src/v2/components/radio-v2.css | 206 +++ .../ui/src/v2/components/select-v2.css | 286 ++++ .../ui/src/v2/components/toast-v2.css | 225 ++++ .../ui/src/v2/components/tooltip-v2.css | 4 +- .../packages/ui/src/v2/styles/theme.css | 510 +++++++ 142 files changed, 18708 insertions(+), 505 deletions(-) create mode 100644 packages/app-bundle/overlay/packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts create mode 100644 packages/app-bundle/overlay/packages/app/public/assets/DMSans-Variable.woff2 create mode 100644 packages/app-bundle/overlay/packages/app/src/components/dialog-command-palette-v2.css create mode 100644 packages/app-bundle/overlay/packages/app/src/components/dialog-command-palette-v2.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/dialog-connect-provider.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/dialog-edit-project-v2.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/dialog-edit-project.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/dialog-release-notes.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/dialog-select-directory-v2.css create mode 100644 packages/app-bundle/overlay/packages/app/src/components/dialog-select-file.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/help-button.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/prompt-input/context-items.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/prompt-input/image-attachments.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/prompt-input/slash-popover.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/session/session-sortable-terminal-tab-v2.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/solver-switch-banner.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/status-popover-indicator.test.ts create mode 100644 packages/app-bundle/overlay/packages/app/src/components/status-popover-indicator.ts create mode 100644 packages/app-bundle/overlay/packages/app/src/components/titlebar-tab-nav.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/titlebar-tab-popover.css create mode 100644 packages/app-bundle/overlay/packages/app/src/components/ui/drawer.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/components/windows-app-menu.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/pages/home/home-projects-view.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/pages/layout/project-avatar-state.ts create mode 100644 packages/app-bundle/overlay/packages/app/src/pages/layout/session-tab-status.test.ts create mode 100644 packages/app-bundle/overlay/packages/app/src/pages/layout/session-tab-status.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/pages/new-session/new-session-view.test.ts create mode 100644 packages/app-bundle/overlay/packages/app/src/pages/session/composer/session-revert-dock.stories.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/pages/session/composer/todo-panel-motion.stories.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/pages/session/terminal-panel-v2.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/theme-preload.test.ts create mode 100644 packages/app-bundle/overlay/packages/app/src/wsl/dialog-add-server.tsx create mode 100644 packages/app-bundle/overlay/packages/app/src/wsl/dialog-add-wsl-server.css create mode 100644 packages/app-bundle/overlay/packages/app/src/wsl/settings.tsx create mode 100644 packages/app-bundle/overlay/packages/core/src/database/migration/20260820000001_add_session_directories.ts create mode 100644 packages/app-bundle/overlay/packages/core/src/location-mutation.ts create mode 100644 packages/app-bundle/overlay/packages/core/src/location.ts create mode 100644 packages/app-bundle/overlay/packages/core/src/session/sql.ts create mode 100644 packages/app-bundle/overlay/packages/core/src/system-context/builtins.ts create mode 100644 packages/app-bundle/overlay/packages/core/test/fixture/location.ts create mode 100644 packages/app-bundle/overlay/packages/core/test/location-mutation.test.ts create mode 100644 packages/app-bundle/overlay/packages/core/test/system-context/builtins.test.ts create mode 100644 packages/app-bundle/overlay/packages/schema/src/location.ts create mode 100644 packages/app-bundle/overlay/packages/session-ui/src/components/basic-tool.css create mode 100644 packages/app-bundle/overlay/packages/session-ui/src/components/basic-tool.stories.tsx create mode 100644 packages/app-bundle/overlay/packages/session-ui/src/components/basic-tool.tsx create mode 100644 packages/app-bundle/overlay/packages/session-ui/src/components/line-comment-styles.ts create mode 100644 packages/app-bundle/overlay/packages/session-ui/src/components/session-review.css create mode 100644 packages/app-bundle/overlay/packages/session-ui/src/components/session-turn.tsx create mode 100644 packages/app-bundle/overlay/packages/session-ui/src/v2/components/attachment-card-v2.css create mode 100644 packages/app-bundle/overlay/packages/session-ui/src/v2/components/basic-tool-v2.css create mode 100644 packages/app-bundle/overlay/packages/session-ui/src/v2/components/basic-tool-v2.tsx create mode 100644 packages/app-bundle/overlay/packages/session-ui/src/v2/components/tool-error-card-v2.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/amicode/solver-switch.test.ts create mode 100644 packages/app-bundle/overlay/packages/ui/src/amicode/solver-switch.ts create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/amicode-solver-switch.tsx create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/context-menu.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/context-menu.stories.tsx create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/dropdown-menu.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/hover-card.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/image-preview.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/list.stories.tsx create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/popover.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/resize-handle.stories.tsx create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/select.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/tabs.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/text-reveal.stories.tsx create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/text-strikethrough.stories.tsx create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/thinking-heading.stories.tsx create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/toast.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/components/tooltip.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/theme/default-themes.ts create mode 100644 packages/app-bundle/overlay/packages/ui/src/theme/themes/harmoniqs.json create mode 100644 packages/app-bundle/overlay/packages/ui/src/v2/components/badge-v2.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/v2/components/menu-v2.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/v2/components/menu-v2.stories.tsx create mode 100644 packages/app-bundle/overlay/packages/ui/src/v2/components/radio-v2.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/v2/components/select-v2.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/v2/components/toast-v2.css create mode 100644 packages/app-bundle/overlay/packages/ui/src/v2/styles/theme.css 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 0000000000000000000000000000000000000000..9313206b1ff91d44a7f48bd0e631aacb563a84ca GIT binary patch literal 62556 zcmZ5`Q;;Z3lkC{GZQJ%4+qP}nwr$&IY}>YN?zcZ;Kr1Aj(9*_RL_P^`@@4!lC!wv`p!ikpTmrxZ{Ou^@u`DY5?1JmbMQWjJW zBqEmp2mzrFAVGwH3J9P$5rz)N17HZPmjGk~A^{HP03iSka|Vy#(CX)M1S=!TOg%z2 z@6tJZH{C(4C+Xbn#lnom4Kt8;3O~OuBv4)CEC|H)LW~1Oc%Cr({r&Z`T_6+&{s3sM z**Gul+Jv`cNs@&wMqSCI7+bL-LSvsxuxcSqyrFsWKfdUR5vT8PEb~Pe2WRX)?28;i z&RYI(+($_<^fw<`k&CmnuI6iJ8oPl<7i?fnDQK*av0biD*D24H;eiZ18e%8ERUS7| z4_q(_s=6I+o}_C=fD=z*iA27RMj-5d?%T-_)w!d#ErA@N;rr!PuF5B%)IJo+^Sxx> zs4{_XD0!1#v7;rEjLdI0&VD$&^VoTvr0IwYpw)k~8la>B>t(q4*p1|$U?|SASC|>T zch}at9CCp8#OMz9rxmZPGYh0P@DXQb)F!1sOM1PU#jn>_;8DqyrFBxA@|EEp;+iLy zUz2`y?|P$$X3g^cjxVk5%~veP938s#zVw&2rJo5eU#wE)@hkfMWu?9KxfKqOBuyca zYR%fZayW4x13rQSxMJF*QCdhE{n>3HSEi=!kWAfl>`8Tn&RBM-v5 zu406O5RJtO#+rEy)j(~u;oZW?m9-2HT>2)*2vkn_-YiFyNyluheCN| zRBSk9e5L4t#tKGK|LZnv>+4o7kQ5<2oQO~$FnPt!YuHqGcZ{784_EKIH*@;pcGy*J zib~B>W^({R<}{#Auv>=rkI@6;_YrF$zD1%XRRy7{!&an+!n&A44xsP|i0M@Hx*X?Y zFF)zJt@OB-)oi=mTz7)6oRp)7RR*5`|l4;9*Vyt=Sh2t8j5AwdLO@NpuTwZzU# zKK9Rbop8jU@|^S~THzf#DnwrNwl$mrngUmN>8Z+=pUI2$@3EWkfjm)KUDw$wUqhGJ zXnnsI5&+s6XSHK_+Z7b1apN(OXate!fmE*R0ZA7Xf0vfei&M8cFYi~MgV(&tkKb@y zdkMur9lDE?F#K>aKQNJ?2GWwM3&h&@uzlhDo~7+oP**Ny(j5NN11}5Ur6W?A2-mi} z3g%PU45f=Ah8uany*Ms@6Yjg;ZB&TUhm{!ni)N+lX7*Up?0CL>;Z~D~yrIF?7@y}a zntCEgyx4cUrX>ZGIcMe0fVihrj-xSr#I@flDJc%u%b~*^1M6Kl5hZ|iKu6R@7so^O z%3J4@?8kuKq7lGKY|pb%^V=zhsEy~tMA1dq#Xp6#dyAAAD=W8)8O&V^{od!b6vE0S zy@zc3pzD|FWDrT3pU+|YiS<%xRlwn8UrGh@*`P?OCv^;9@>wBPad{y)@)^-pnpzA* zlCFtfcZkKt(9+@^m6JC^%#6)l70}P#xZwh!x4*AmyzBz*f#v=qq!y@`HCNSoCY9Q> z=gpC8h&@0McfF}IJ9+fdd^R>TPpEVd5l(GsogT^^G@>oU&6QnA5E-h*W9}p3q8uoi zMZ7rxKnNoCKT^vVAJF+%flJI(3%RSJ9jCNs=Q*^M$nEzZCQ26@$&TN@RpFH%{=t<% zq$rVv{((S%5{`(;O9T{M{Uk{&B?(9MjhZ+5uOzP^Z9L-!AE*Kyu!$EI@`-;P&oDWq zAvEB!4BKs9tnae4cG-qDd;VTK#Bt)td2!y{XVPSrDp$?>xFj93q{mX}mkdZ*@PNGC z7V}G;M)CQ+_^#uYI4?chCM;@D1CaW&s&c{X0djFZ~`fX~VtB>yhGr zLjOsD3BWbL5K~cMQhiNS8HCU_u?a~MMAg<2=K1!mV`J|~@}SEhsep{oHX|LIJGF8KqdMsF7*{YD{y-0pbSxQFa^p?RQJ!m$A+r)f6Jtfs&voA`*y9 z5}y1};3th<@@k?1C!=)j4=B#rch1DzHEF*Axx4&HsR}u4{PdadoeD4bxX`s~;+doH zPAv`L_r*RE+yHxYBmT14YbI%|lUFq)XA!!g7NbW25e|_Q!+5gO)Pn34XP8J8MCcEZ z0f5>U1&~){00jimx&;LhAt6ZN*xpA&)PRh4tcVso2+MRyL43nQsOwG8GeD4VKp25U zINnIW*&NRUCQ;xKZ&(t~q$&}kDUnkWPbL$OW+Tr5_X)%G5CSrq%$tHv>K zLhFrrku!s(HW|7q)EDF+^ic-@B}Tx=&`@tgG6K)-%2z4p3^0kT&}+N-GXU`Br|!O% z|0zyJb<6>LK9@7QV-fi46xy0Arz=5*LImXh^Vj8Od8F`rH{h5-t~U-a6>#iB_ZxSB zIIhnzn-WmwWlp%>dr1-go4dLnRjuzqwhD$9&dPqrqsPj(3(R!`Nd+Ud$*6VJP z{)7zB5ujC9iFo@`;VZ8_>-+8Nz5!EY-PTR;9==^sDC}=j<3qOnc=36imX}@o2x~e? z=XM`TdjTC<3^(Q!(m=?V8~H~cbmLU_cb1wl?R-`-?vaQ#%V21_2}^O_!Sd z1f~@wuH3Y^GhS~fnwyA{K}F4I`K*@vwf;4&{ps@?2@IN97^~8nrm%OdSGsb$94BH_ z*MprAWF^u8c)f5@aZ?%k7Gcm=EF!cGL)$I=?Lj76o>iLN+O)Pfs$6-~g`>%-4OS7B;(#ZnWeuo4T2F z1oQXQCKHh8K2U>OtsU1qfI<45QoXCMzaN0LIHqtQ;1_9iLI=)n6F#p(ns>m|Cc#vQEa$;U`5Z~W z7RSr_s|NXtgRF8tK3F0%F5(~)+gYzlhpJALa(;l3_^VJCcC5Kw&T+qfpV6b(W1mfL zb*n2sI{tQ^nfj^e{h3bJh2@mS*yfn+aJkm)#9xEx+kO+QXLdS)pFMuvTOTEYA?M28 zU1S|8YGoE|ixU!dLNbk~c}6_jYo_H{#}ZQO%t;t%`1;prkXD6Q}1A8`M z7yu4INS!aSX|(j$cED)gO>WE3^y?1*-iubvW1BI>$2+vXxx=H|ztNV%r>!^6wyc@y_D zUf-^@DaC-f4`&Ck9&l&Y zl%Tg`vs?9r*TG93ys;`p^P0u%%Y}ClE~#|A63|sT&9l(Peb0!zJCwnzJSr9HxYy2J z4DSIqU5vkWO`F4l(rXJjXnhVLB-nQ(^&RW<*L6~I4I+>MQ@LF?Tv{I7Ms1ljjJr*8 zo%PL`$CQ+Bz^hRhf18l-#c1l;xH`C=N7v&-UepAl{a6`XviZRMDN%ZCncdf;xT1>|N5XUI)~6chpFt%K>AN*kS-NADU&&OfLJeJy9@&BlGzTb6^}F2PV?m z;XoV+{@{Sh@_+!80>E_uu-QP$;Xvx;0JI1K&n;vDS|-ONKFXBtB! zfM`<`{dTNdshU$HY3-=u*rLXqM>b}VQrT5a2yUkYAx>_9 z@oae}fJ^}BGs`HFcnl`nedVb0x$tV^9(RdZx!v#jgneLrDSuKL;>VABT)CXtEi))I z1f)f(`^$zqCl8TLcwBMEmT_Y|p3!GZ%2s@W5$q7bH#(E4%|bqH&aCNyWOYD_*@M^W%$Vh6Wgr)8**mfp&}fz)iqvbe=53JJsP@ER&3EN;j*e#oAt3U z!!UI^=pAi zUh5X*O4H_U7vxH=WwP=fI(YvC*H`Toq^~6kF0ORnball_wWmdZ07V20SUc;Y zd>sFPS&%L`HttWdE9bRp1uldpXga{Y^cG!3#AOR3ig)+)ksAI0fj}YAD5sCfH{#3c zYh#fjC0K`A+9Io6vWTic(=2*+PxcB<%=A_-Fne36X`Og|hr&B$bLBQxxscK58N&({ zZBo8he{R+f?#1g>L6LzV0f$7;sXm`dp79U}-XxB3a+3(!w;9KrIw`ZTDO_8QSEnYd zG;9~RTK+P=O&f%otN<+OBi(-aD%NjmeQJf)FUXV=q9!D4%|&k4bGZ zlM*gjD_Al2(P&C2Yd~2ye)zWSikcSj4vjz|Q7>;mM17jP;ICTC!}b0IGoix3#Mo3{ z>FekXv0KdWztD@&J4M?FY5yOvCah+Uxw!gEidj@gz84OGp9Ymy&PBXKy-+Y=mhfd`UL|bx!88Y*(wdj zneAwKtvJV%gG(Ny$jg=;rJvo)@!n#_V3zjbd)%noqVJHrKX`gIDK$lY_gp znm6|CsPs^n!EQHst&8X#2I)9*vf;whZub8dn_p40ILk_l;BX!)<1C_z4~sOl8eWdK zC)hC+Mn<}Zx(dH7t6hNsEF(YDJvMY`OZr#w=*tehhns=BQ&`ucUX$L1Pc1NpI8;mQ zXcXIW1Vipp6qkxX4w~5s`todCwBxn}wIdNH%LtvvY9Ytxf#ba|6*_`F>5-W7fjZJn z@hGpNoaPu*9?j`HY7CnHh&JMb0$a;>M9pe;5;V zRrY`KR+|}INn59yHAv3gaGp)4$%Ai*yPE3?!3TTXzji7-I(NIO7z>|~f%CE<$2G{V z_4rNJ1au1yK7#1>F>;8tKjbaF>^#u^XO(=pM$W<)Hxq12g2Isq)|JAZBe)?D-30?x zk-vE0-xcg;@V=oTs4 zN3p=Vp?8^T`to2a(b9?-cDoM0i@C6efv1Av%T{PPhv}r_g>yPYZTH)8sgfwjdhD+N z{oj`m|Dm0cxC&&ax(wP&6|g67{4kgnt5*x~`7jP$hHQ@P#LJKNFq#qu1PK@vvJZpQ zhuSP0UlAm8?B|MG?PvU@{GoV!M(cJlDmH#&z>-*O@f?WBQy}*$=rALBvwmovZGI)9 zYZpin?&C;B$ze5#>)SWSV3VQi(dY20siU@VBtzJm_!%Mq0>LRge*wY35GZ6$N|%@k z3|sbUSm$uSy!iBsF)(eT#fm?b$L*;ND{|a4s!U3$HQ24!nT1o98*P(D)!1t#+*cge z3G@$)z58m`wQ`d%c-t};>9ad1a{np{Om|i^*!#k7 z(iHl_xV5Z1ZWhf@L>>)%!gJZbEBaQW`@^+1%9BO$cq<=3a62yGax=mOnXinm2v<4H z#k`f!`*j)`Z_P@=pU22qe?>gHev&ARqoqpeqnz1*Z={YqLM;#;EsSOL3J-z=3<~-0 zzO$@vI{_bl%P~Ew01zl-ZrGTyWkESZxHm81d^=BL4g8WjZ9LF_D8d;QEBJpzMqtdV zoO4XoKY4Ben4qbCD6a(gU(#H5L(L{vDzIAo)~+af8n;kDFXCxTalg?Co%VM|#f<|w zKc0bEQKYnv|G4FhcAU9m8$C}Vrd{aNDqwHL2#?6HYJYC%x&Fn+!4S{a#J%n05}Y*K z=NfwNJd+5Wk9ah7@HED0h|M#I>Gbv4<|wkA7_Y%@7*pGNA(76%Kz@x~i(#)9w|V4x zf-*R+`%gpVg{G{l{I34uafp!UiMvS8kS(m1vF9Rk5bMimx|O#!4@}@f+yt>@ZnVGTNb>q~*&Q^62a4sgG3v*~!d` z?F#6E*-|fHd0lXr&Ay)Wn=+X6>8naVI!?|6~=7 zO8Q8*LSMDT67eGTy+DTv+f2!TGYm@QzsYoOBQUfCld6C+G&VFfP|;}(wiK(Gsp>3W z@7=Mw?ipoEDA#Xmu;*Ru9KY1!A_f}Zsaiavwpg42`0)RiDWDDxIk8vxMH{^6ffnBg z0wVb?9Aryv5FP!Lw^)=zTL&u%jhjsHZa}!~2xgb>ovlm+{^LU1CFXd&I8!$JrXcLh zuVK`UkFKTTF#Bm_9Vr~QWQajQDv?5_6DqAS$q95OBmDmo zS7GJpO@{R@i#oT#xqYs73OE#OL;t9xf2}5W!4f`nGN$7&(M{bVh^ZD)*ghaB-3cXW zxv!os{IzZAu;JJ;%^zc!6@|wR7;_Pf!T2NNj?K=m@wMZbcSG_tVxW{B{0Hup83$hu)EpnZbc8nvHX4tbml~;>m`l`+J zk^yJmwv92|S0HDZ-}VPmvE)x0q0-8qnO6(!{{px}D`K!A`(Kj$J@`i8o1%6?<4DTk z#gBE^*Bh0X0&o;(tE&1edW?z_e(-dD~i!gdx6VT=cFIlDW^I*_5@TcC8YRTdgK~Fu(*K`r~Z&_Paj~@siBA3%hfLHqy`BMuvX1)TNXa({k(wWLS!wO z1hB5uTCp-N(>1UO$Fsg-U+3TxDDT=y32xgMKa3ZP9yGmM2~YUbICAocwHvbv%YI>6 z7HSn85v}$4084u}Uda(2JxF>&FYu4>3xz~zt3r>VQT_)XM>xTi`MHS{aq_%e16_=K zy9o_YVx^4!fyxheT2Zd$$s|-PKAL&+^TwDXhs;XSE={nS&t;qiZ|K(e4zDDZvz=qE z6HrB;-6tGI;z20PEI23ei7g(AeRdhxZp?gbkk_G>q!T<_K|pvz7RPw$@LxR$e;E~~ zy&CJ}^rcy$>L6G*@;hy3t3&6WjaV7MqOSfZQM#IzYtS+hP_|Eizp&7k(Z`=(} z%{%``Y7fJcd1XB_qT=@uq$`;y)mPhLPIRCbk#!NOUF0h7L0@$Vn0#Rf!guL5i2d-) ztf`gb4aSR*qsgY2zw?%}`gyzPuf54+tGUoBmj1#jjQt7IG%(B2eF9Y--XU;=Pb(ER zmFv=hk2CV#qAts1Fgs0z!_CwQ%o=KzFKTLCt|BwG_tt646G2kJGj1ecH%0|w!3@2B z5E9W?_VBjc?$>4)HS$JV4AXSrL=_2G&C>4DNuHYI@O^nwd1bYpdlKsE&dwC7=; z1m#u?xTi3cNaSN>A;BEy!Nesz?Gd>+=nF%M`v0>3JB>VVE1TSBCQyHj$x^J7ZVH5C z=$&g*$;pcaDp}5%j3+c__8Y9RG&>&7jux4xpgTlLv@3!BRlb$@H-YIw>?&4|H9bEV zpDl-$@`@$5jmpHwIy2)|L^&Wv6&Y-e!0Yjoxx(^61(%TED7}RQ(EOOxgseX{wroc7 z8&Yv&Y+ST&coWK1(H!!R*$b!#1& zFp!;X5Ai-#eh5bg~fx5x9|bIRA1?ogcMTf;^l| z#vEDx|MnRUG`Hc|I1?jkx@IB^!vBepFwmMv@Za4t6o<`5SW*Sy-|h~T{^PyG`U`XB zcv{x3&F@3KY^3PIltNy(T?~ww#V94Xuke|-G3k5c{=i}IEFRI?$I(=y!`~&V;l(|; z9F_lgPTdEfgcU@JMKhE9=ge}NYg*-RVt{w^+E^Xr<1BX|dO+Q;_vV>56=IhD_gMk63iuYLP`0u+C@^(66 zR^+@Sj2&g}6(rQlS>IhX-iDL(X$ohv3kHyuk#h2-wwu!2i?MMRfCxx?mjlyOItGy} z(ZJj4;i1J#5U39_Q0qZV$^2>Ch$G=^A*%%Y&A~{^Ru(_?S?9G2t%diV3p`Kb#g;f= zSvDmvfH75ynj+amrZHYm@GkGCSzfUkYq)Ky^G_oe#&Yw+8*2-^3Adfq74t zMwGLa(X{1&tD~(Y_OO^7ki;!kWCB5dShV@TE_-#8=LCGm?-aIg9liwa2b=dR9y;_C z-Lvn7gazy}#YRpn%j5|IleIe@{9|CD=ZWQU=EQWWnyF3J39A~DX8hKnuq~P^98#=m zT;ksPLfI#?nwm$Tl_t57zQP z=7^L5ZIW(()*;5pdEFQ<%hCe*b}09?AnHk_^cIcs9K0Hf!!8jJ8rW;*`=#eIOp^am zEQgOnygIU_^?XrqI=&rbY3D0PnS*TyMc)q@n``SY7mk-AF%GcPdkbg(HN)z5{S5D@ zD9??2B3IRnklEqK4qn~7y&IPeop%ondz>~C=zgxY8&QkEq2bZ{og3V1LNuP6otGD+ z&Gi@8W@-$CJAP!q-%>MMQ(65N6E?8L;4Ov|of8pLS_YZ1A81*)dj;t!s;MV{s4U-t zdv<9I=z!oo&L6zlSD+qj0D#U*bn71@^kNzhuh_PUhiwyTofV<_%}T7HPl{| zTL({*d7jXIJRlRb20%jS?9~oHo3#&|TZFtYurG66DXQ_NF>N9}lN^ z-OMmT0f6z1WCY!nvdw!f{0MtRxWIZ?E~mxbHyrhwHvaDspN~2R2#By0fXtO5H+K(< zhrj&rCc2f`_&VR!uiJjIXOcU0F88inW>-F*j;Y2klX_OQGhOU(c?Tpo9A5MH{5`Rx z^V6j>tLlEke_znezr-RxeBsf5Nk6qv$7!fD+ji8Zgl{nA$gO?}r05}22;M&MPkVDW zMwBE1W1>2L)S;8;~NdLKVN=zPrxACFyn^wky} zcTYCoVe4Z1lhx^{&f~Zl#A(#n!Db&7?pmGb6AA-sXwE`}V(gIZbzUnDZVR6McnK)7_g$rzDBk_Bi6yl+OS-Qg&Q!4n-r zr6PadQijnPtqZSrSJR_e&8nCC3_lO-p{4Df;#OiBJ@`dIzrSUkt!eT9whGqEI}YWx z95_vXn0U-!>4?^U;dsqm1*L_)AT&-AYZ?SI}aSh(c#$8i&P`O z+VnJ=QJT#oY)u|58a3s0L|p2++OeiGxFGNTz1p^(Q8<9*eXXqILr_st7y-4y{(TuXH%E_cuSzd$M@oa^{KTRCo2C=HcKf4DgGWVK1 zJ+dm~N{YVa8P%Z%!iDVNVJz@3;tqXoDKC0-!>n8WLB%&`Mz4C-h^z02is>-zS{dt$3=D-kszW3?&s#c0p+AO~p!-D^jFs;||2JmE9 zN`nH}$o!L<)w_Ztsp|=zG#?jJzbvzbTau=(Va&6Ml4mMY&hSZgHQ$XRukSEo^P62u z>yL_?E{X7P&x@WZKQ^^EzC>5*!pM$o^HT%J54H(*-LJz>R)r0X43J9z-4^rW< z#e*K52-=Mae)zQ7?Q9g$uZeWVsrR=?A+{W!H}~(*CyXG5SS{XOW@G z&)YQboBa}X4z7sft~k};ziV|$=SkZEA9L3<`KIeuD`GC;Y_s!+Ct?&Nxia(_kPXf3rWUHxhMA1Z=-`DE!Q2*k16HL$6y{&sT%|` z991fp%T>y9Ws*#3H*j7ehz~tPhNxyp1J`x*p)A)i2*_NZ0TInsQa1q2pPw>b zg;_{?SxyJZaU}!cdh`asITWr%z4^Zw_5qKLf}Q@$=EX{B#Hkkkxq^$JIrA1Oa)V+Z zIK^?138gKmRLyn;f`a^(V+}_IKw;T|hRPsMiU^9LSUeQ3fE44Ph`|fRIF0Qcyp-0) zl2e6!c<}u(LaZas50w&Hpm@|EJ*uh&)!va6laXn}#;2v46j@eLWI`a-eBa%rM3QH) z>&FJc6#jU~1}qC)N}*`gL%b; zT|_0u=fYfeQ6&{-OANtx8M%|LA_f_$wMC>p!1689j3e-6rKna8L-D&d6ZL_<^3lsx zafBa=$C0lU%Qlpx_GZlOYPq3(eQ)0(cVA1SoqFWqpBt~E2gCz=mqoX;Z@)H>VyPgQ ztf^i=LZLTEElZPM3T)AHZ>_*(OI)59hKPmN&Rf4&b(5U?$`OZdqI^tZEKOP_p*CWv zgh6E|R`q7&?*;s*#fmQTmQw5Gi^1jHLx2fG*`z`^=ig; zBL*UK``gv}FmBQlmNLmc*-l+7M`2XRv+Op-AT;;*G8^m4arG+?kh&#Y+6n|i|R zWs_Xh><>om{s8!Xm-y?i#SQk7g~@!d!xF!KB|%*Kz}kB_yvn^}S-K9i;04us#MD04 zGx=>h6+rOFvK6Dm+yUSM_kR=S6UBA}`b?j-zH<(b)HMHKL5+Iyc$Y4e3ndE2Oh4J%TPTt3m3>>6c&g*dm!YGdX~H!4ODSMURAhOABVH0_f5=wZFJrw(mbJaloHv z>l?ZClo50PW$jYEzuEi|G&|{#JsCNexaXSWMEFGYYZg{S4@zpkR~E-4lI`e#n>so{&YXAu!5;q{?#wM7m%jo@zW1G(*Si_tjy_Q zZi*>8l{M3Uw!PL4esD+iHF)kfBrCXk$^3b;LKz#+?L0$FDd{I>#8=ULW+nG4<&PI; z-!pQrUyGc}#p2mr=NDhj8wHEq^Zvf}%3vk13LUOuroU1*CMM4KU_`$00f*pTQ3QMQ z=DJLRzuaHA^Y?QbxFhi2E2Ffbgw7|dU!;K7YR1~VpFp?xv-i)b>T0nMFE$1?a48o^ zXKrEs6XoSpE}mLqzR*!GvoRl!m2-5R@bC9iu^W)yMu;9!GcR+j9ny+|1W2p>oJ5 zci`GW530h%C#o3HNsYsG=cuUF0=_q~ph;&X|ExnBh&!Fnvw=g|aac&Ga)(POy=f&9 zNKCXon6Vrs@hp`Mu7IMsHX_;Mm|A(o1E&K>U(m(S6ElAfgKGYHz?Z~xuFajxqYd20 z^#aVOh$9G{T!Y*`V8NueDF@zUR@wPWeHjP(yP0HIjpN6 z`r@QfH_?B5b85wkE({A6=L4L3K)>SaG<2fC94fX&#h$fbgMWadpGJ{QCBd~0>gjLZ z2+9C~94?WWQ6GGZ0Yt_qco$RTX*OL%oHKro)a?#xQTonJ;AY;qPgBDGT_oZoE=t@5 z&4lk8Spg`TV*Ih?$X^$DznFPc7e^Xl#+n$%sLJh&C9?iQ&fdgP^MWI7e5z7mz3FW; zmRH#Hr;K&;bi=!LiHkW6x|y``aq`u|lM8gTwb7@FZ7z0Rc;$PF@2}nOgr?;Vujr(V z)ka*aq0m8R#EEk8iQP)-jNTyf4Mz!(;f~9YteZ|(c%1P5yM{V3;xaa@oK`uDjGwb5-v$T@~_aV@!11`-@g`h})wigg0gy{^ae)?IW808*Mlk@~t zbSpK@g&UVHJs}D`FP;Q5dQAcoFasisa;w`CrZR7Ob!DanaJriG^ zXWz%v(@poYSq$%?0v{Ew%yCP)Y~B0;hh&3=DHk}&jn+BkvNv&IG!I;2Bl`0rxFtnJ z)soWV^?^Z9l88}C{)OyDy9oGCeqxV=8>l?Kj!VuGqI#2Yt~bMG`=h2rp-(CDpMSoT zc0$hLDa;3$ai4A~_uNfxV$JWzF3Q@QFc#jdr*2s=Q%(m@Te}#Y@WxDXn$q$KxYmw--r1aRX4zf5Qg2v3BF?~!S~GKgWKCX zgoin0c%O_ev_5>FqF=gMoRsBm{>J$hkgdz5;K26c2e1Wcj4dH5y@rOn)7WYPH~30u zdVXJxlePg!_1q^boel&TJl}O8MXHdiO+|ovi5xx6F-*`ZRScdyj^$lb9WY|X^5$s3 zT=zdfi?MtnT?rg-ZY*J(ZSCYDFEO3KQkBL@iG~l7;z7{}y5KTml%OE4Kw6!9D}Tt^ zH3JM?9L-_})xjQF?pU!a%#3O%j0DRnWX-NKw-TU=1W>xpypUAWT4KrO3q<{~6r{ED zMfCa@Xh4Vloe|O?o0E{L4u5Z3Sq5vZi%?s(%Vy~8e)3q5?I15GjWu6d;)Ek4m(FI? zACS?q{oD0RG6!r$oS{E=zIUGB6>S4THdbvhYNdo63WQ67nhPlI&#n={;`ND`-ernN&y8EHu3;V#(X|~AmqJ$ z(r^&>9h%$aXEF`EGUcIN5PY~wW$6JmE=>`{AP_QI6KY6wH=2Ii_Kzol@z&li{b`ec zBJA+9riVdGXy)vgZWZlD@wN2SGv^i=^B+Yg|L~LCa>kVPhj;7bql%JNYqq3CuZkpJ zvc!EzA}$jOLg56EV#v#fCOqBLx0ja*qn+}mvYqMYF%j7y?Tox7e|&5_{azz@ECINf z?83ndXgN?Xd4;h_mC7w`1Bh$LT&kgqvI_y%-`uxFkT*KS>s{d7+{*|TQGa5^C0x+I z^J^u!iMH7GYXWy+r1&F|uQ7m|qbNS=ZwdzYzFcc!;C?0EzQu3)lo*t>A6F7k@f(#U zs4rHnSnKZG?ZV8SqS|5d6(mvXcQB$<2k241@1G1MgO86#BHDD(8N4$+3iZcP&pQbm zMK+#{)FmpNRyty;k7==a=;q3b-=z}OZYl(k5#|%+{mjEc{3C}JyEyW^o-}z?;)y3} zW8gDWINHHu?S!y*ADB#Ubwc!Nb7uQQG=yMJESQ<^*Xa4ZyDt13oqy#k$*mZLn`W)d zs|de}lanD0O&{q>x|MCy98?NL^!myY4i%)N{2N)i_|L}GjqVxr+^fWE65|bhQ4-2J zTdxg{uXPoX@GdOqK>1j@QyH2)eL_z(vl&i>vBkh|yFQfm+^{ldNZ2#ur7G@I_Vp0@ ziq=PoOVK8ba=H3VXFvD%Mkk7*!vwn+*JiRLsZHchQdON?g$&Ul4I!qWk-RgaQ67R! zN$dhqj(v+J=@tXDv2>#_v?wH7mk33wvzJwQfZ*x+JcQ~P7s;kZLjuQq1c6lAukwT= zvYPEf?c;OQAUOd=^pIGkc?$}g__u!+u{^pl)_PygHxXCLftmOkSe~B8( zp2YV9VmqEB@DU)1EWm>zmQJU#BVVjV0%J zXu`#PMb@tPJy^x!ZEPUs@XIBdHqZ4wytoCI+Z8%DEDU96e(U~xEJh=jb|_cb$N_tr z!FJ(irF!hvD1zD>)bJV38jrNBg0f{F%6k&JSa^73oR?lhGG;4a3M2jgMB< z(Lw+33HjGX(jLt*NfvK<(F`V1x9jDGozz0;{Q0`Y(_zWF8DoYL4Mh18P@XV}3PB+L zN6`q;;XHse!IuKeqZ&`rG}s^T<@8L!J~ze|ttseoR-Rt1;`w$Fx*~-~;1WBI*DhA? zpUhh)r`|m1-Hu(rL^O*S!p(~d5g|S#i->jQ!l7S4N!*s&%9ZHUIkrj7+Wo(x6O0ut|f-H+SM5P>-aIqW}U*6s<)7a5avz9^fh?~HI9&jQA z^XO;NzNvUKaO1}v?_wkafE3dj6^v*mEb67{@N6VPtwa*Y5bmm- z&R{BiR5|Nijc$5h-^zBiQ}VTpDMes5Hb@Seq7-RiQpG$l$KgXDadAWySb0AHJ33=nHDimAz1e zt;LRw@A5tahsa4Cy~%M2I1LDg4-6H~zY{SI0-9=a<}UCQR5Pux=pyfhNPB8D*!aDw z6H^~*(tfrAL#k03N^bO7{AbL0`@vY&5gGEVfk!3V7`e2CW=kf2-VSKYz<8Odod`@3 z;WQ`JHeCvFZU%Q&t1x(kpl8rGUnbl9&fkOzQ`cNsmuvwD*4bf;VI!W<^I~-UlTEi5 zCEh40-p^|24>$s!jU#jniKtS~x`h^7V)_!qebA2}v?Lbo@8x8Qb6%n^vn83_-Q7$VWk>a-kbhE=wPPJmsc`M@z5mFlxa)S@?s<|gB*Eg< zsZaLGyaDUawS`Z7nH$=Tb@bW#qW@49#io1&S5 z17}x_pYH^_1X3EcuZv=@FA~+7$v65Myn8fza~Rdy5ZD+s7^ z5(albtTdpO@vhWqIFwU0g`NF0%RV&3kGk6CBC+^lI5A1l6;9x@XJT<7H4OghV(IV< zz_W=(eoSroEnqK;jl?+`nH6MLHhjW!OC=(ENMJGqcp@?O~vZ)6jCI zy2L8y1YG9jYr4OI$CzAd0n=(_i?o!i%OSw$e;Jj87R%h5m9_>8$S7No;;&Ldah+Zs zt5W%X%ym2uJO_DS+R7kxWWc9Ed?#s8F}0y#-e*bA&OmxDKF!prK3$61U=T#0H{ZLR zIY1S@5U}_AS7Z3Y&$`kMCVhW?|1vc<-X8uXqtXxF>XTd_3vW(V?LOh3dWOG&0 z`K^mpHX9OZebR1sQo3*k$9T^OyZb375p#bJoH{MX^&3}elYHwQfsOasgT-^b_Qi$8 z_C<~_E}G{}6c^?angB24hL}Pg*Bj)qn9`t+DxEY*Y$~c4UTjJzY>8q6gbTP18N{`d zhaggrfMk&H1>@U?#mq!bc7iDjHj4(+_23GN>Fh-5qpOuZAvwPA>H? z^|@~`=}#Z?c-V~!w?K5~Rf|0yfgIX3u`-fB5q>{sT_Etzw?U70zSnzGm3s0#mv{bT zYTC&yJnoj0#h3lQwSNC4QQQ|;3wnjeVdLnIkB5K0H|D3+!GE4Btn|&3{WAcO8H0ByocP`VquJ0=!v0eAevt4#p-uJi}(q>1b!*69oDH z5sQhdJ13UI>5R9|2RJIpCFJ6J#1aA+;on~V_G{fQufQ)a!A1B4FQolZUq4^`3ei6ReCgK<+}|t+ z^(er0A@C=+gLF6Swg_!}j#t+!_@k?D1gwmybUEXTe*{BaJwfL;g(fC=0;fVLaHJYy z3C`m?lnTC6e=gv(`bA7uTYXyU>_sf*qWU>jD?lR1kf4bG+B&t@r>1r-_Ra=eYHU)x zUHDKQ(;7h7EKo@{Y}(lE?Drqz+yb~_FTxs$C8MHbEX)`NDiiD0ZE+6x4?4SF$N+9K z_;LyfSl_$r(AcCH3{G66J{QAQHQXV6-eVMFjYI&?$Yrp|7o9dL?ZBo^J0%^6O&gKA zhY`~N_s0X~`PaL@Ec;Ph4p2Uk@f{?VxqQZ+Bzm_tKRa;@qWbKd*vji?@Xwn^K05Wz z2`lge@bR8^vqp{$CyvWc9DCN$HV_%u(Msr=I^(5rri+Wt_~>=dyT@VR2dpP7Ro6Z) zj^LgQoc4Zo;BUqc1Viw=qjexMF!SuhG5N5rXp`JAo{fjN@dFVItGEorct_tJJ?aj3JsUOp=|X@z+1mi}l|3{MIHf%`QHp?( z4&bjvWM7A*VX z(tmv3{O#V?$h^C;=YHy*XFp{tY5}_@RsGefZu~lda1Q_fwKh!shqhHk0t)Wl)oxtq zX3@pr*&zU*0A8k~E|1|u4#A~^z4BSs>B1B6i$}r-nuDbbq*w@-)c{um4nhCHUfFG& zR^aRac{8PRBe+JECLd)^p~E7+6l7^VL(Q0k|km&T1#Z9`JzyZ9ifNw zVjv4rO~khR)ELVeGhD*^%>Ct8#cnj=V;LlDD+@osvM)9q!vF$!6`}Qq`tMe!H5A~B zS5E^tqsLj=9M|duoXI`u%J|7x_QedxbjSj-**sII`&dw_MBr@>;)}YWf1mQofIkEjWlT{Bty5`~P{5eId`)%1VwB0LBl2Y+DISvTAI z*s75wXb=NAh1g}{oV}O5%JHAG!g(OWT)rcss6i@;g^`zUg=}{o>5o zvpuultnHe+b>6Ibzr9g5|H=aOg1rkf77bkV`})t8^(@ycKej@#;?c^)%2%s=tKMGq zx{J{@tLy94E7pu&J8Ru<10`EWY~8v2+|E%u?{(X{-`}O!wPVkWeb|BWgEtNfjzo?e zJ}Nmn@0j;^&GFYKT27{%bicj!?R)RkzZ*GquP42ypr@zj-*d*(9jBk4VVs$Bw(nW^ zY~$GzXTLkgKUaEg^SOuTndirz|MG(F!qtn^#rYS19xJ-E>e3(Mq066L8GmKpm7gvX zSG%tbymsXJ;OjfD|MSs|n}VBDZe|)XIDG-YuL0mE_Qzf9kN=Mk0{~wD=&P8YbZv2p zL%DMb+$eAy5Je0H#Xc}D=K=yhDbxpumJeej^q)Ertq?I4F_!;8UyzO1z{M?egQh4(t!F9wcNBlWIk2tZVB(rS_nx*-UTT#W|0$v0D!Jih1V zvNYCu4~-7wjoeqxg6U(*#Th95J$-*1qD$X=4WGIV;~u%cxDX2k?GqBE4RolP&+JH` zIC6?1-IhFBMvP%Pt13lUqO^c$ETWy%C9joxJjn9aLCm?3i+^b+u~u&O9t zh8WLHdGSG&oXfo(d9Xkc7cnMcOvD&vSOKYefYAetae)y<88{>^Ar~HPyHIQ6B-#>q zr>#U^-d2EWOF*_|khcvXYx797jsX8xct_>|Vhw-{0W1tOG|cvi6Cm2#yp@0giY(XM zDU+EOI}QIqw#`Qpa74x4wZd@7J0)sql?MPJAG>y2I4Gy%6i7$Fk`+dA5m2=eRX(J? z2gV~ne9tkQ?dfpf+<8wxj~y>B0ioq?9+wPhnvh}^9alFHIBz|n*qiz8s~6ppv$r1J z-Ki3~qq}EchbO=|ydQ}4=Z`AfXVj4GUctihME6GQ+NZ(QVDs-29~OkFbs~$IMk4xx zpmZ>NcGnN}XM@@=fskgBR|szzPZcWTUbf z^2N%nx~Al9(ba++FF^RdP$=jrZ~g#mj-WC%gmKcsMbYMNT2S z_s?239YU9raCqGgA%UU+DSJZ8fr}3A`?CTbh!bpV{Dg zBThjPPOEBw3OX2*BlXN>bCPD%P$8dZ^$z^lMMkvj8aQ)E$whz`EL-kSi9pZ z&kThOgXSIBX;lKXz(*$TPkXiaimyyJQA?i@;96Ewm4G6EZ4LloV3?(Cc^5)XEkU6RMQu z29Bx=V?65JCh>ym!*>3;cGZX_@vNcK{*^net;2-V49&tw24rF z5sY92A>2NlrZo_ubeVu5$H_Ls;Ma46;Fl+m86bOLa==k?DW3HR9gh%G<8-1EMJBSD zD1|5on@d<1*$mc2HiLCYFJUWY$K7fY=MQwO)vX^U%kx|+_s?LoIaEjrp%Hk~`DY^m zf+1j+r9sdHXjLLdj28Y?S&<{`z$h5;;k}9K%gY}`LqoBgc%NNFk&AZkeA-wEM$;eL ztV7srZL_f7YY#FH9@+*mYuIQXO#KoFrpvic6fgM?9jAx_i=9};G;YmFy_I|tY|>{- zQwJohFGyy?-ux`}8Mdv-L&Nl zXm+oWkmuL^0509X?>yLZbteDHdHw{tSCvVqY8xXTzjp0;(;2^V0~pqoN8B$5@anWb zT&$0He0JhdUcn8uw(H(JWFs9{`J{cyQh!dMl-;H zH`d?!G^uDwf+j^xq9sL%I#2zcGKPSR6gOa<5djI!vw-PA3eqMlNPsy2DP^fD;B)th zqU!>#J!~;8%Pl;hV)dX!wLn0|>OqTm?YJ*irq>dH3Z5OZHRA;^c&(YN#SmsNCDIUS z97Ri}b1DT>1qjlUN*G$AmsJVJHOoGM?$BRKblSXwep9F*>AN_7^&Y|L7S96E>w36mfawDGTpf^%b3epBl~^#v?dV?hyo;D9}9Q zh^k_+_+fry0YNO}lfCkJ}!Y{p);OUW~F6bR*AIX8m(YgE>7HX@uqlE>fsqkEwA zne`9aZPv(~+YveAosYF8r}VGCBWTel)xpKnkN!wFCrsEU9b-o!Ppq zD1L?VM?^xPWGJ)V2$5%s`ACP3gQ8EeK!XrGjOd`93%xZ-@(j9>;>rRK2|g7hq9+Z_ z+y-R<^D|0CZnkh$Zio_s9d2@J`m({OUuWK}*%i7dm$4hN5Jj8f5M)gHgRN4j&$$N@ zkR8~%Q}}(*OUBsG*ww1J@anjJ1G{G!Lhvmtv_oCA22Wj$s1c}O! zcLybUeT+;fhcHNIfW9qNp)lnBfWfj*bkKqVK-f5lJ@AbWhk(~Y*0Fp+3tnp`-5X3a zs=pL~@Jj(mw!%q%do}Q~0}6bzoZ2xeuo<{1gTO!>Yw5C775<^o$ui5L_t-JH>) zaq)3o(}0AxAyF_2%~+0t>@-bomq~R@UrlT!XMoEH*`oU|;vuU@%o%~VY$JT*oY#BU zXe4VMr7TTH^$(K#PqcF!ChTG-cs5E@1RPYkfA^9Ui?Pj(NvB*yPR-URFMuVHQ&SBP zIRzuwDd*~PEf&j|`Q@wPxB7*kqJ{^&kIS#@6i3k0zntfO8VpFc2hyHlDDa{4q`!Xp zX`eQ{0Y5hN@_As{`z)NudIW#nvQoJ04LE2EKW9+z9eUX^WfoN)@L-HU7Qp;u-WU06 z1PV}NkMYK}?87^SkcYAmmBD~~dqDK$vCxnj!4F~QKNmWU95D)VIbQY)dq|WU!#<;s z9~F$KfB<_>{G`1TU||6kW-U<42NqOMl-TIyhF(X)!TbQ}OP;@nt6g%1KYg=&g?Snf zp^E`B5GXB#w%5J+SROil2v)EQ(=ZLw3=Pwhr$Vki5T2ucI+=MyY_I1g0ikLWfc`{o z7jo0*BaZ=&KpgPfN8nHJau?KH&-vamCfnM068^vkqLxGfM1Dkm^kDJ=No-OLum*b| ztud~NFbkL=Y1Z?HEVWYRdlPUNh*8Oro@>*aH?R39B^#^nJxDDgxB&_t|uYYm>zx}(M{L>8weh>V>Rs6u$#M4iR?|e_+8Pv?V z9rz}1Q4_bQ^&nLw!i>r#!Ur@dz%QA>0$5;C0)q4)05TO?kBHa1I7d{C5wi(+{sk5* z{dmt@#8G1zSKv@Z1p6c&N@4K!O&BUC>H>eWJ}=mZ=L$IkEg)mr`GPC332P$3SSesZ zBp58ff=Dn(V3`8rW#aRE6agUOf_$beENJMrP7#xTfKR(5?{4ilP7Ld#JDwaihlouW zYvLcuy}R>I1p>bi`2#@D-~3Q?cWlA&!wsC2>tYV2paqwZ$`=)Y-2dfJ(RmFZ!u-V^ zJ~J0M%3Q{X0RTXuuX8JwLG^F-J~$LYi-DJ0;zPYd?c9V30s>_3b zj3I(rZyY)AYSK#Gc)D^4#NCv@@qa61W>j)Igb3n(4v*uUirhRdd28SUa!7f;!bEUp zZ7BaH3pej7Bo4GksR+7QnvIaBiE{IrtM222i?`VDCNmYlI}-R=1x#OlElq{}#RVI2 zn*;ZpJ~N|as13)Bu)(fe0k^Ev3kth#4(wI{JWGqLyq_0^U_fD{KLz$gY?)}oydnAB zgCQqKtOx|ZM$fqRoptAHbv}dzmu8j+x%}*C_%my4WilR?5F_$HpDW_V2?$uGGD9K^ zXeT{@mS`$)nS-PR1~>WvL9f&BiSxWfycX6%T4alDVJ)VGw&-tsu-D?ZjI86O7q~#S zX1*4gXbO=FQ3g>LY$ZhnV23CV?3jfwfxSJc2MS5Un>B@k$zkQpI@(A4Wcd`IcrQPv zT?K?}R9ySv)@Yb(e7n*WgIm|*)1-J@V;40iDD&xrxu!JmJMBkioYTW72^vK)xWI=S=5;&s3 zFvY#)0X{QdTo~6wJ2rI+4rQ_U%)g!}Ve1xePuBhk85(DS&df&I)*sP7h{qw6wag3)A!bfTJ zrcwDOJZdsUA!*L?0G9ZLFGqho{n|Xsy;>tW|2rUq$R;|s(O@=8##J&={RH*Z){nwF z_(#c(e=pYycfSQ(6$J?P98V_xjT_e=-nBmcR!8S!DG7&ce}t-U1#ZmBN(9=Yhh+#rZe#F3ZP^a znLZ<;SfV}q95J1GqGPrl_enq{r3DXDee6P%)I2P%NZe8Z_BoORf3cJbg(M^3ZQ!J$ z*2N=sBgo#Ia~jn_M;ypgM&}dhVfQp6f(2`$fv`_luwmWHH0$=e94aEuuVkO11zb2 zTcs*Q8Ki!Ru9wBvyKI3I(c#2LMS+S}bW3YmGR4Ameq8la0-47~>x6jJkZp6kVmGlr z5ow)HX|pHhq82F zj3VP+JuqF^|3_M%LhK(YuLuX{ys^rdAfv^s+Sq$)RTy&A)lx?`L=r@ylYQa;pR3Q)dD#LYNR=UG2o#oTxI6l_+V-rDlkySMY@?`m+sGuHgGx4kbu{T=rF zgSv6Qaga;DvSloFe}mdj{Mnmm#t1t{&}U{x}-}- zm6G{<49A!WEYeoe9OTNILrgY@NhmI3Ra58TZcJ&2jNkfFv({vThSO zf=b{g>*WpqN~>KcoXWq^QJ|gu(2=gdGY3E2l2x#UN_VBao&UFHWB+oJ-bu4j63}qFNb1UFz zk9Y;yMHWzrFl;P|(*Rq}XJJxURO;HX)rK+>(`u-j2Fb^Zs1lQQJFe+Pny%klWi;V& z(L&&r@ET^#Z0Zao+z>4UjzaS+0bZHNAjpJP?WWbN7Se~Dsd-i+vh@nGT@bil;Q8T? z5&5{+^;fx$?Fda(vn+W-EgK+(Ogh;F)BZ4_e)WUIp!RNBokcW*Xo#%8y_af zb?FFFk<-FN2$Y>?2T!d^R|gW+RmW9o0BvvLsbPgw<_0+iminSlOt5R1ZmNx6K;~5FBK>z|A*Fb-Pa$Bv9iQDa zDg&!T>EPY2JZ;stD1=U(Mb@oW17C#>qD{Io+ooq}Qmxj+N>c>RN>%aN4wBM!?K+G1 z7#Z>RHOIDfp5R2;;bp2fEn-JRH5(EjRAXU+o(i#QW~Xs?UJ44u=`5ByA*O2Zf27Ld znJzJ@5}1(P1_$OE0lQ|;YPnte7z(SwHaV7?uh8wc5NDccn0^bX2MJCmB#@sAZ%p+f zYpHY3oyd!N%S*GKT|G*l`D4zhG!4MQUbFaupddw$L4J%DbO~Wq$|Vci4caEhC5Avd zJl@Cw%miLq6JNCZjA$#h6w(UtUEfu6ec{Jp8tsWIsTJhGWiS-*Px7@D1Ri3FKgODF z*HTaAz}T?h0QoOg_WctqL=(d#M0eo$%CNky<&`*~aTM?YEi!nWA}nL@AS$@ctt%A0 z`8Ij}x5C=oaY##V?Mq#m_*{wcP>e{H`yDtm_8I0*%*Mm>?L%xRZ+{?)9FeEVr%Iv# z6G0%&IA{9JNeBw2(TrWQZ1jq7ng${%G&aK0%?{72#6I6!eZ<(XreLilWkP}3q6ZwLO0t|nt`XC@D0Bi@ z7{XaK?<+r-dV1qJdMscU$-%s$p8Kv3@!cO3L3GKtZ&|T9{3fPz_v;H_(0%VKXW1TQ<^Riv3Q2dZ04JHkonhAW=EO$@R)`_jO+dP1>^$*zJ_egT473w^;?LEl_ z$36W?=W}P=4d9J6@SzT#+G!s$EEzX|XH$S1KoSvX0zqHm!6$Kt+Ed?-dcM0{3yg7W z)RAG$8v?-viR7BSj9R_CVt&kYuTmfF1o33E<>h0eE3&egyG5gI6LO-@$uc%TQ?4~AzEucLM z%u-1k-6``blqvwtzkPfp?%=BNh@6!JO$4LTJLl6eYPNV zc`5>DHqs$G9k7SJ;gjkPXF`njl>fmVyR-XyJ6@aV?P4d+Ct@$v*z=X!@IRYoioU@H zY%|_$6u}LpyH09Bmssgv-kOFsfeOFS}SFPEhx8Efaw$(zB zVv-I*aWmiT`r99E;@@0;#)mwA1Apj%PJd7&HdyM3{!Sv-H{WMo5&mvsIvlYRqtP#d zof)9)bJ@VUZ{9R2FBx)zF!jnB=!>6JRNLSXB1I+;)brQ}nK^yxdDJC6$YRF*}*am~|aGbZj{6H-U-{9m+WN z0?6zBjA=b{YtL@Eq!M`#`HZx5j|7$l3`l_ygFdHXHzMJA&X8?_kTnXznvz!E_Po&l z-RrN%-#BnDJD9<}HC?~^9z|OIp9>a1!+}Nii50M@8B2J@cYV zmN_hwQ^pQlcsvHbp{+zL5)MGpr6SIE*MZWAu{{7-L7@oBO3F$i&=6B_;i~~tyub5! z>G{|FsQBC3LB!pJw7R{d@UQ5K_dNPPe|w_3@X_m#JS1H3>kCi(p8izd^z(S9OUpzU z&?vgUtH5fAMdnL!kFI*)>V=w10bPu{3`)^)=VTr(y^xvtUOy7=bhvM$uNHZOms&YG@U0k&$q>HSRQ z=Fczk|JM$`fFcH0W;5LGDQ(`u{A2^ox`42@cwHi#OQ&5?VxHyvjZ#L$8kt221Gv-0 zkF02&?l-Ca1zEitjLy}!6Rn1<*gqmDJ(-h5M|0{18#akJnB@;hp zrj(qs4;N{n%LVuaLwcPS_`@bNZG#3hK#J)5Bw0mT$b)U4q`emPvs$mbZ_jeSoq~#8 z#P;e|)pcv4`RG0U_&8H396~PQ$6%oyytE2mOEbarZ>Ff6;HQVAL3dDn8Mf$ z*}#(&X5cFpSH?`SKbAm5`t@!wQ!o|-UQC4SnusV{M8YE0i7;u#b%c|8U>YLMY+cj) z0XrU+WJ_o|n9@C-^c^XNj(Q@V9iEm-2FdzycDJ2~oL@4jHax zIesnqQ= zLQ@}XJx?xNEk=ixg^Peoah5l1YHAWVbU#QPO;7Rec|@FbaPRPX<~&?z-1&zBO&8tY zshm3~nJq8*VuFxM2wpG6;1-g5PDK-XgnSC2UCl!&)=AB_QbE~E54($uiWY!`YbIpq=Gx!? zUP|pWLc*EJX}S_|eIkVX?uS zo^*@`b}=Gu^!u|NdObx5=%XV;-9lqoBr}#VnNb zgtl!O+5;&7c+Oe*3MxXAA;DZLB9aYCW*#-iphGil7gI`7Gte@6X7IRCB6PXtCkjF` zL16{Bw{P2(tufX3ks=f$R|SS~kzfHz+>w4H#vj4mLK(0 zt+&8B>*WF{&C?<-jkdRS&4ILzvuq?Kb>FJo1d{cEYcJ*oz(S_d6-6)XVuya1rWX%P zueG66JgNrpGnhs7QnE&AUHuA3#ZsmeYwl;1cNY_~H9W0O0P&!ikh`T-R@&A!2;V#T z)k);4A_C3rV@(y%bGd94&6Zt}qiNPy_?i3}uC)Drr7eUESkdsWgo z>Iw`6VVKMoVBB9=Jx_(mf8PqDs{e@msiHnO=`No$x!HtfYn@2v*dm76Ldc zjxU19PZU!<&N-n*^HnS=KpD%Lg00S2mqSdlfqfp)2EcKhvED@(JJs$8Ou~FJAOf-# zAZM*d@Y$1w`^P?^MSEFgRABI6tOGn40;0Uzm>(Bgsm>6#vNRP?HD$#@F}j`WR&Zt* zt^dy>YowB6d#yk>uFu(+P2JGC50(VWd9MY7M&;8M589SEy zxG4uyy=5ihA3i*1m?nZa^q7%ukZ#aA%PshvY`nA_8RH3}pBRvprV@79W2gP*Ela|I zUOw7MaDW3+inBQaCTaPUcUXc*ZQ^6hcMT^99K&!@wn*7xuL(L$m2^IVgST0F^fgQ ztP8i2v6zh4c2jG9kK~W9>TR8ApQesiYUKv?I%(61=V|6G+U6|!b7*`?2Wv1UxtWAb z_NW(IwI(~N=MB$J< zP0iFxbcE)yG|^m-6nQ>pMpjZ_PiukG;^fSSznUV+_Zq-=#~@as)5nqyJQBv=o=xq$ z7g7P0UPTV`vPOk;jQYYFiyT;?N6)oK3fY{7frKMB`xB&_CAX(+&l4t1M8@z`3LIc? zs=5*6#+KC;8=YtY5B?b!YJ6Qovq8`u*ge>$LA0$wl_ONhS5b36o+I?^fA~CPhSNX> zKr+sy2*2F0u-IVppcEGL6^EmW)0;|KvS!7aHKz-6B2AD6+19xg8gft!t{UEbL3M?m zV%5@=Y!hXVi3<#_%I7){I*nnNz$=EJ%RuGJ)+YjA) z!NizgQ6V4-)5usN$g&>oFLHWu9E$(-h~JHQu&Po)`A%{kY6g^V3Bw3gbP@?Dh?7ld z`Y;+u=QKe(b)J8hKE?BLtvE}W>pHzYWjEspoz0Ma2x0`}G@1K#X#b`Hv(O>*ocM4! zqT%-coUMtHJHB5Xow>v<{#)UPjX{R>|iKFepE0ic3Jj)e6gHIuQn!2Yj&Dv*1-r=833R$8ZZkF z+-%;+!&$pg!6Jhin6EZI8MV8owo6@%F`~-KxeY-jmsVXC(eGNAdo)k4v8p1VzP zjA~_jLoz|oG^3&2>*_!r$L^)lMH1P~1WheFlF)qrlL@aZgwa?9z!FPJsf89;V1b1e z%61>h>kK_2jRY?rzBpbyzOikFg_V5kF?2(|DV*nrc6OW9fGpn!(Lhdg2x*#M>E`_L zjH8|`gt)JS0>`=_qCO5&iEx>|5d^L~^+)|&@xgbTZk@N~nx<=o+oGwvAzG_BRtKuk zpcUh+x`%3BZzST(QMo;l9+`-Qg_I$b*Wj=S_HrU-2aAyn<5J)`y3CJDJtFsH&WgvE z5RKsVbeJ(jh~X{IryGy78SD1F2t8=3?PPtW!@1pKseIaAOWXO+B%GMI=4BRkJF z$)aACK5NW3+JlfT%~)O{)x=KiPtSGAAH(SA;OrMi(O)HV#o6Gz7$7s|QX2`sT&x@$ z8iv3V&uXn6LhheZz>om5=NuKi?X-%*)M5(-5Ci}d5TO>cnUb50c*zg>E+ZPI-skBB z%lm0MctbwOO7%k6?SMwm2`E0MIPJbg%@ZJv;@nOIW;H ztTh*MT~tYC?t8Qij;=i>1DRM}*zzXDpuO$3OVZS0={|?BQXfNs75;SA<@;#f`4*E< znqcQDgM${fdC-;7AAhP*&^tu~DWLK4MbH{iPmmhB~a5?*r! zjMbEgSZGzQ(v&?#SCWTVKssaPu9|g69fvFR{Z6ywoT>;>LEMPq4q*4AG04+dZ6wP( zH|rY|CYe-UsaEq=h@A?0Bz6MH0_AY#o+{GvNTyheqVny^hIu41iqx^;1&&dt=0bPa#6Jmi~7h9_eY7Br$oi9=TAY_OWZV1h=&BsOP z>lZ4{vpUw?m7$7V{4VnO$u9O$jMA8j!f=D}1g;-qFG7nu2L^_nk_&z%QB^=J-xEH0 zDOc^h1xzb7W*Hcj5HjqK7{3D-ki1m35*S^DxiYtQpDYv7rdYRdCjO?KFb;*SuFD*D z-P$@Q!Pl?Vm510Pn4?Pd0jt>1f*ci2KDY~DWe)2;&02TvmS~TPVm&jtS)<q=j8xHvm&auLgP5kR4x?V=YHO8uXvn$+P6!< zPShnou>_M_U-s|w6ll)s;0x~3A#l3}Tu>0|+|35J@Wtjbd{=c2(Gm+3&rPEe zs25U?XdH4@PK((Pzf{hH6|$$uCWypbay7iHug@WUkRMhdXfmTq<bV?7l@38aLBaX(*+XP`Jt-9yUar@Q81g__2sqY*TAUe z&yLriI6#Wi_F;`Fh~KuR(2H?#OU8%rA3G{^_>+mgPtV}Xy^RGr_7u_YsR+DtAmc5> zNC{5?WOQzpDu&?yNpm|5*dK*5G=KTZi8DHi4*9ijHZl2t=-S%}M4mSl}x3F_pPMGb3Ra zVbF;Txz<($By1p)725JPsVR#1#oEy6a2M>AXA`M zaN^f}=3O6`N8b0U4}IV|FP*%~y-H|<*5*=WTrJW8OO))5-gDH@n8Dm^_eK0r>`T)j zN*A?CuMXyFfu64|b#|R`BjMHc4;lz%($!jK)H~9gmQir-TGnrG-72BTwEM$Cv4nha z+R3=6WGr|6Q28(w$xm_%W|U?k@nmtHpu^`-r|=4?TkifIP{Wqt>6y$XsU%)w)R- z_So9GcY9hk;|Hjs=qvFm_)~Th2=QI6jbEcb@KUHGp-IbY%P!MSf!43_ZAQ*FN`khNdNt`W5TxUQSj zWry8wY8~2|xw<;UD9@ua|3H1(Z=3X8+gy71Z&R3@?q=Jwj5cI}dAe!sSyEXC^}KH{ zAFCf~v0m|$%*m>0lK`zY?JXUIgJSvgTP-C+>!N;AwG@srEF5y9k|56wxp*FND6=Cb zqB{xs>4un1vq;_w&pfYreCh~fg5=ejISRu#39*zoyS8>&t;&@-Qww%rm!%X8PIxkg zw8M<)n1MnD`#P#I6SPz=Atom>Qh(3WK}Xz6y(<)ywrg_YLWtO|m7)-xr_PG!E$~@E zb38TUYHp^oDaKNI8#^E!oe@Qx^3NsUL%iLL_N(*N5OhZ8EpvzjxQ?Emg79hCOIuvF zpwjZVd9EA)%#p93fg*)Fy$XLFm%jfD#lR-t#%($fYG8Iui&T?X_hS-BOXb04!;0U2^BHaegJ$08WtpTi*(l4vgfi% z-M-B|M;~NdJ5#L)@=_ZL;P!&71}r*G6LomsQL}WXhG~30=!kUXsH@~;y16EC?_OX; z+xkOz%DO*}9CINiC=FBzo)M|i1XC?Yts+yoP_UM}1K0zGP-KxoR7tjOHajXBM$&+T zOaOMxMrn_>J1)q@14>A5U0jT8l6y{(?qhopzJoF`C{6&;0IDlSh{Xf%ni=^J8 zHL!J)ljbI@J>*ohs~xY1eoz3>p4vxV22%Xz61n}tEwrG66@LKH#waX6OlzTl78?39 z0*hr(z)AFd1q7fAW19tgK@Aw3Lv6hNBZ<|*P2!CWutCXmoFuB(Q74w$Vc7%~lRp=r zJp^p6EjTjjD&RQQk)T=3+RLz-4-AUs3-^ZXyp^Sf4qWjmYIm;|$6!HIOr@-A({T=nO`R;gGfn3=n3_ zD>fAy>O3A~y=e^FSkt01=z+`$(U~a^shK{pF3z}~8eqn|cS$hhPW&SVK+N-6N=@L| z&@m4g0Uq5flG|TKX5w?-;tE#Pp&&p4;fH8>@Q)>V_V9!(!FdmTt>=N05 zT_Qe+R47!alynV5JF&{U1IPUXOv?L%%@)9`0JA;@mR{Wilq1=^NREV$E{r3unwP?n z^vLOtT{VITjGXJC909ROPpC57N`_$whQ7Ub?~?Fy87pUTT7LSb_oqzTAF&9!o|zo( z95;Coz@+$OJbe9t2pvv^D%Ii$QK2eCDGi948#zV+5gA;nT^d`={6NwNgV@+~6vcHs zhXD`;vmpg3RB#tBH*D1sLqf1D)-|Mee8#m)@$Zj6?ljs(>Q6u9)ZQ0#>OB4LY;Xww zKaP?z+QH4&{p67!HjH_>P));wRdy$eP6P~+$E@je9h4R38K)u94+b-8JzbzNh)S)m*HR5)DWpA*YTdu6KWdVUxs zys}g@SQp<^DX>puW$5CY&zAG=p6_nl@111)++78sHDnPZFq#Y+wbNRs{ z8|Tbk!|ZPf_i7dks`(Y*gOl+N6W*79g=^HC^?KoYf3dIvc+3*EwQ1^;T(RZ0_yEk+ zCa$tE$p#0KwM0oxYt`ONgFB%(;4C$$w{0*{Oa@79DkVdpn}2s)*L0}dR&GqT#({gO%($;&&jMn#CP(@E zTU+HulPYb2v}$_D6q(gpTmEz))xonjeOpM*SboU#f-NGN!X>t zdZ8$|pt`XpLESEH)hICK9R`V>p8O#HdPTCzD7p3M5Oh`d9L+1X65CkFIz&gi5ebj_ z4J;gi+Ua#-n=B@v_IaCk>G^84fU4S4R4jGnnN^+id2}E{h~){dMb)sCG)3egZStb}@AMXPJ}daO?B_5vKX z4l7gDGtr9)>un$fj7che5@4%AxmPqMRei{w{?e8n5e{8x#sl}&!5$?c(3NtR8(S4- zSv4RZzm2N}h^oAi?pr_J z%%#qTzer^`YqLB_3M7`Ay=7e>?A(3xRNN=q7Yr{Ls$}Iu z&j>J~*UXS1Vk-f6#r%v7jCr4sN@!@2phQQQ`j}YSGkgPOmNSlH!BfipUC+#=fYAW) z>+#bc>h3T(_wx!6jDW`g$A$F9*bp(?>CH;Vop!Zq1C^i0vF=-N0f zefm<8Ac=0(m^KlPPo}ss6X@L|DPiKEv5SoFdI;$y?MhSpQ?6AZCG=cPiuJ3${ta7> zscGl-C#_iQQ#I*Bp;=iGkAk>`T(U)8-{BeVUP7gwC?DcP7`+{rk@G}$x7))v0- zTfcgnZ7nZu4B|GaVI8rKd3$32b8`#@tY62z?~`u%fMzBoiM*rUqj!($$hrN=jSn|s z&glKdRsbpN`_0pVqTq2ya0P{lM94A~P$R6v5h&7@3Ma6KNRG0zQW;4eHR0^AwMkNI zsw77MH;wa2-v~)<+pX40AZ$p^F*T}{(KlT^ilrhHvhm4gUvkw5NFOHENlIlie60)^W25>nH^r`C31oQt zlg<{`%k@)=`EnK`;VkoNC>b-N~K0?|MfCOuB0B zly0$~JwzuP_YJhX)quqxstwHMhzJyjH)>AY41_5y(xm|j37ja;wT z)^+qOVxn((qUr({sK_Y_x0`8kmvSL*3r2(u*CW>@tsWaV0c9_@Px+{BrlJF7>PtJi z7#VYVhYvoY*Efx!=IE&Q+P*p#w4XL0L}rrT&Jl~y-nijK+~Pv=a+*!*>ATD4-{XA$*^ zR8zk;C$;8HO5+p9B&3yh|B#&sN|jV8P6ky1rpx^2McEGO=?@6vAO)jvUnZDpV@9&` z4dfhS-d|h0-K+YpChG2~ZXgu$dG#}MDC|$ac7;oT%s6A*`HS@`E{2ogpynh8{&6Ng z7YYTPrfk^nl4++)8?%AnFgSRS>4L@?%^p<EKzRlA!Q?xyloIq0DA z)H*dy%QaBV8hHGA^t6Q1w!YvhdK-EUbw4~M{F0ANii(x z-_m0F-K!I+#OAMJt*@U8{_CCcj`ipXQjrk<*1{_>X}x(BIBbdWnjQT;H{)l z?-}6%AhczFz|u| zrL+}`w=>-$RJDV?Cl1YT&)68m9sqOJ)C92yz`S8T%}kWvbU z5Zyfn+QC2Z%?&@6{+-A+p1-o_hBjy&Zk3hE8rSwTSUPM0L6#Qq5-8Sy)G8mt9k{M( zVM^3T@o~Q<;?QCn>Jfn}d$8t$(-y%zk$!%{1`}>71N}^cc_1QRH-=2=g;*}iS$K#N zS!u2p4Vu_;k-D<7T7X>@1p3wELFLm%@@NMW_Y!=0Hmf?=m@Rk#J_E0H=iC~XI_tu+ zGSt${B6G+%p4JyXnP5B^tdYdElQ%q1*9~JhWWa^NPLaB_6v$fGFU)v~Uq;gA{L)}V zAsC*FW6*Rzb`(VaKPhaIyN$q6W%ayd$}okb5{kyEMyI>u;e8OF=@oCG4^aMEE@J<8;Y{NG8A;&h_y(7fDYSkQ$?6S@mCKm(m*WFG^^ej)Uc9%Nqu^jN!rEj8{ zSF;pXdCp!9qmbmN>Av-QnG~mPA4Z#gyF>Q%6TrM@L_b5%?Plz2nS-k=EeX z$VQaMG;t7*D)P(H%A=lcW20U!yD}@44%17UT){9N;$>dY2q~Ecqvct8ohcD6V~Bx! zRfVS3wJ7Y+{QlZnvAk6*`dF*yF`#tJ4@D}~C1}(f0SkpwS}!L(7YKy&{SF7GwanU^ zVu0KeJWVr@x=WenKC~i2+?fSna+?f)*lA7?yNWd_h@2V+2LqNv z-c_AfDC;JsGS5_2BHcD95uq$KVmZ1v-@BypTIhG!ux`E7UEpFDyUe*RGkMPZ|MNsP zfXM=RB@go1swgfN&J{0hVwic7pAPY4ELF#u(fV{TH5<&s#wZe{eG-@&jYN$Z(dO)z z^@k)k8FraB_r11h+O}nys#BsVTGa2gnd&t)K@vqN)9t~4qjkEZ}>CP}kX zg6Lh8CIY*TIU(EnI&Lsf5(in8I6$>q5a3lp74IB@5LmQ6YGA8tmxW3RlsIt4ntBd> zjO8?@Oy{N+EJUHJ@KAxZl5>FSnl_<pzjfONAt0ZD$^5&>o#-mC>8~OHbtj}XYv!RfER&`NbsmI6cz0} z53P-X%-Xgd!!GrfD`B%ryD-(%%moldm~%`G%RE2`XI11pInilJ5}Lc6Hp3}xWHC@1 zYa`VAWo=XnQ>)qs{l0^Du1j&_oYsQYA;6Xsan9*nhG19k3Y9%~*`VkZ3|`jT0qrcf zc7nO4)I{9IHqjvLqKwn6WN(~t*8;Ok@$n^pR^*jQhh6Um+g7F3C9%3|2RK}3!Jd#i zL1t!8+NIrKMQYr6fH6TX(N(Bo81fsIh|t?MS)B}rNx@K`&F=pK(*6oRLzu`L)) zr5kO$Knyt%s#BVmn7bK!n^&N{wfiN@!RqSrx4C zZ7iR-0lnkKS+1KE305qf5t?7>l$x6xbc{uBkCD!tzL2O|RA9oZ>kk0}7ErQAhD!VX zNyrEwgQADec#PxrF}i7OR94D&B`?#oq%`M-LuHLiiH%Lx#Hmt=B4H}bmGrm4$*h;_ zn)%z06~k&DekB`zWZ2hrsI4%YU_o$LOPykYzg79`Yxw)$is}mg#$LF_i`f7+Z5nz* zr(vaWH#Kc{u6QYafjFV9Djg&mCC^9TsiM{d`F@;aG0zshplybKIw2pI3F7@YvUr)s zfWKE?c?SF~xOnM|FGTpl$_e%13CTo7rEj4J6hSwOo43X^bSbz^dN~M2uq9I0c@Kw8 zf^MX$7L{ZQz#_l=K+_~eq+5~Lt$A4mXv~gWTEJ6*f)$K{_2xgq6{L|hj5wJX)s?%t zxHI6Yh!7nB^q@V%GZ^;@4_pOwV&eT>O;>s1g>e@UTAZ^&71!!M`NTb}p!os|t09#SUW3DU z?WIs5FtY6uv#QHi|8RE7^0#P86T5}Mh@%M}HU8te$VqkLhScUK&Z*elh**O%vDTLk z29z4;aSPPL06^-d>Owx8I*6#4_}O!Sf~1e-*!s_eXO*SiYXHM&_!rv4Sp-y!*vA$cdiO;jd$ zN2KAw>vvN$;M0DTQL(fe_yPm`i*Qz)UtQ+-wh)&PZRjf;z6N?I;D`Eo4D-_)3&=3iEa27clcf$e!^4Jdy6GObs z4YtdKTGr%@J7L&d^ZciN7$t64EVoU8YTJNf(5;fm417f_h3^uP+cdDgkkZ+U%u32m zUKjS7uq^?VUWOt}m(t6D?KTZ^i%-1C@-!#5(9`LpeDGpqdRNN?mZDnOVle5RzSPAV z0{ab(fCaY>iPjEzHNetr1n3T)i~S35ogVvr*b=N-Pd0UejPP+ip69)1(cz5h)GP<8B=7LuETh-)%947CVWYY27^ zB*68o2{*imhc>&r|InLtFHDFR4()_%vHauSk(>3SshYx4>V6b7? zGGr8n3>h+H1Ot{~@*}VveVNg(VqM6=U6Ib(*Ir zxhnHMT-$(kUCA)8?Ql`f?^3FM0myxtyax#hyBia2h9sqScL~6MSl4&{skiJcwYk}+ zbo$7MV-G*b9(sUPFS~@tqhH{L71K)AoIg8gbhhXwsvMiO3Y)nyd*mUawM8;E88j5l zRpopnDxGJ`E4FALo%?o9alJEXKzLqy?6l8LhwKeOcXj#LkhLO_PkXw!kUbKzM?$t6 z`&;H^v1CTvE=GF0k{y@X3589ws_DGG7CC(-yAFpm6z!I?m+OgRJ)_XT649YF_&!r9 zPEBJ!?Xq)B@{T$k3mu$%d-P0PqzVe4)VP2bZK{6G$IG^5Pa{1 zo^=wL*BvT9r?)t(rl?kzCxF&s#1PVw5|+$c={gFgH71~Ar2w#Kac1~Ye*fK!kvq~h zO+FQWBC@-WbPK#=48k3pgSFN$sbz8P_ z#yt8mO~UeWhcAO9Dz{Q(6H6-v1JXl)tWkg=0$j-k52#O)sQne(k^;{&ax_eZqENJ< z*zcmfbQKj972Rp3AmiUWfFDdf9i6he^MJ^Q^Kl=gd07&yK+|s3*p>&z*=epl;UzN+ z{(?S7EZY72X0WIJ;wgIkSEu72;)quopUdN5{BNf(-v725nC%h2e~~?>T*Fj57DA4g z*ZX;s7YlxmCz>;$J)$3<&%dZ4vX+-@o-kLaBo`glN|nFA-=_ zz((Pv0o7t5wAdhw8Q6ouB-?a{wV*hO?mc1{9~2m%XtQI(T?gY5a7D989Gvys`<@taoterEpX`ftvRM6w!py!5r9lc~3rK7)6Ze zCZ^C#NkfLCl}ICgB-APMLPps^Ku4PoJ|^yVk+xC@6=oHTS2)_P51JI*@?n3E37(II z_4C=Lq32e*(%~K8Odt7`RB+6IKAqcwm|+pFOW0e7Wtz%XV+yEe9D5(PiRX2q(IF_B z!dA2k^=QmolquwQ-T7J<*vhqUE3CI`kG3vcdQ~tdpgvsa<>Vlkq$frr7Y=_vL#5mMgnF=ucpo;jmspf%9dgqKC&FqW{M&W`+r&3!|BH%R@hY2jak40KO_XW06SKDAG+kj} zsWD=QY4O0PSc-vhnEZR8!=Z47w?O0j@C_nRS^5mtW3==oY*d`4!3WVim=_6X~`frvT@*;@Rsj}y4zl1(hr23C3#i@ zP%4Zsd4o?hD(2y9f*IgrNrGhC%B6~x-s5Ve7B7fr%7q*07J%TdPP8!-`}{Tj%zvVz zz-MpXq-#D^85(){4mHh90jTM`6#TUQlTsFaKN=p2zj(d9VMoDO9ae*C<1;<4RX8aM(@<-e@6Fb_(l+8|J4x3*4( zgVlJ|p6RL{1R}?41}o~O=VeYI=eh^3qgc_HSNg=)6S}S~4k=b_Mq%l2>gVa3lXV#85@6xG`blYOk6S4#-zN*PRs7Io2!*rwrh z!mEHJi$WNO`JVau%$c#6W$a|z(g|#_voyabm|x!M;6(v;cf|MjYK-ejQ66pnAxl@` z9KPCpj328P_9HM^%4BF4tQK?Uwo7mhd2%MfPLAayj>f?((o;l<$I zO{Ye~uUni#$)J3@JzYsi=qc9KvB)aaOrQcLSbTts8gK+80BBWmU@%}@#R)7|V_l-z z;A}Yn81%Y~RDw_gtQ z7W5j<%pv7{98v;t`l&(h8K-PRGeIl1!judW&2x^Z|D;7&K+(BoJj)AaG?)pGXmy}n z#xPBd3yLLc&(J%&ig6Meb|8b0rJ+XVaE8I$)n^Ds+^jEsbLUhfWsS7 zfEQ69vYS-zHVgPc$5n@-u&CUfi9ovsOZPBj+%R;RnSEG5#iKE|dAFBEHkUh(i}zJk zEWViU^j;alDvUa89Sct^);{grOU`!Xk_xnL#JSxJHpr1)*zN_I;X4!~JY%poR$e@x zql_TyGxk(tIKhEFLaNj?TiFQ_I5LC&L9=Mj)xA?U34c@wYTT1f&jt5_wj@edge|18 zCT7)m0ItEqg)fpl;-P`ZPXJNmM5$iQ)IG32X9kEkOR+%UoVDl!5X~P)cs1-gB28R6 z%XpFm3}S|b0~+jP3xVgP>Myn-BYJ=6k@ZFe2OOwlLlQ3+Ygc zd{bD2rGphUt9j6e;*{PBj>&x3sy<0BQV3D%g}>Ob)RQ0IH9!Wb6sf!RC&ag3xDkRt{+U_~^b|D~}uWIq0&298D&LLa@}?!Az2i1TErWd=>zx`!BO1?%ou#EK!H|l_flG7o%H>#mi~yAx+5DU_V&{5Q zy~f$~Xb4ToYweJxDh0z+7)H8o9EfQ(k^H0x#G{&2OCX|^>5$p=lv3fsGmjOCn4B+g zK+J327AD5&`J@1+$eQK zk*x)fr4_4YLAD2cGI2a?sSO-&4;}84dInFL(lfxa+H=P99LI}cP0j?FvJyRnsDx+) z(Mo>ERSA$1^#i9w{fGuwS)|__&9w!tS&$jgv@`!1dd}v8sK$jB^sPjzogJ08lo+TS z@<8*=#654+b>$~Y%&q^7cF2Hl+c>?hUpL&0rqij-W>+q5mvYf9i%^GVZ~^cGYF=s}3|I38;ES$!)T=S17KsV4;E_%P4& zg|1fI+FzIi@nwc#B%#q3>;IS_QNtc5M9WnX@I9xrHvsqeX|noHf-eXa7z9?<98(En zFoGWhv_^V;+L!yRZ2IX@I%X`;Vs9V%r&V`l?I-4TPzn+RLD26-maB5$uuUF+L+kx5 z2^3l1P=3%?>)YlXSU4~S{ji&EdC=Ykkv!-pD z5akeenzHi|Bx5<Dv#1^8_Bg3x{5B1j8D5+fa%b(h+;mNpLEBIop^@osWt}H`<9(Os%Q7 z*{Mx=6iI*A{ewMdsrU9x13b*TMmD#eD3heWTM|ZolBrAH8 z6w-9=4$Y|yqY8p3N=ny4KDfFH`EIZ(L#BeXiUpPg_cdto%v5fxoi`KKbsf=4CNGr1 zE}N2T%gv8RqiL2wK#@1y&nnGGpSO$gc1@!h>S@cZ-@4}`rMkY+)kR*?9cE>HmY}4K z5J!{*V$X>WrRVL5;)Fdoyq=s^QP;{$cJX!G!S0{>B=pfyoo1v-`P%s)`2WRd@v>bL zCY`!N1C=G6J%|#G)>M2Bfm+B9|6F-ULz+ggzyI$j<)Fc7()CQT+oYOLexh25k|5QQ zlEo9XdKEw#sM2uJ>jVrA2nfu(O)fEKy0xQ&NCYqOxs(@Nh#AdsP9H>~^MRTwj@E!K zQ&<+NxgV0j8?W_jZ6bUfkhBQnV51S`&UNAuu^B|cAsBkY0mOU?s!Digh{yo@0{fs< znc`ZTk@9bBBR*k#m;iiwF>3k^&X1;CTh>{3u65@+;e^kUa}>e@Usc6cepM|N-M2}we|dFS62)d zLEIaRr=TFt`Lfnt><|?!3Ky4@4R)`*TD>)}NaI8_rBAxO?FI<sC9S*Akc^r(3mzp!^z#{U*V`{>X zkYkM5RU#+%cRNWE2^5djPeCV!7ti8_-1Tdf*zY|)c`!ILqpjdNZXpnu6{o0{^(v?> z|3D~q(=1t_CTJ|*6?G)z$>%x9dyavdtBTw1EcDG$QKx~&)X)$-i70WK#MtlROHqzO zlEQ*F+y*`>lMeIXLw}m6!4L^TI#+w13@xI0Ud$L_I8IYJU#ZydKm<~Ee}~NwgE~t% zY+4ZHm?*C*-0Ciym33*FMa`p;>+bK4AWCgcMO~@Uihv_3?#2HtThE-b=aZ0hhkA~u zExHBZb3q)HDh9xA?$Gk8>f`{U5xAycsS_%m>NmGJW82Nh-`V#I2v|;rL~)qLQCu)E zAYpMT1&3DrUnv}4h5g<&>!aDYko*^emm(5=yAolZfFm$&dW0kV8_5$q#1ixvcycho zJ$K;0bHjV*Tnj1){HLoU&W+)(cu@w5BVbR@`?t%){@PR3NYRzs!jHn8qO}rj)J&U_ z{S+saf-Uv#nIOw$2cE+}zYcXqgLa-zNgFJN<6@e=>8 zbjG7^)smX-UdJknkQ+pSi-&nk%|elG)_+um4iG)9sRV%#n7o}UX&8(=^UhP_HCX12 z7p$c~)QQ54x@>xqFdBeN_Ui&-=SE=0#H_vCtt!fosqMDyB>EQaA$~e(xDK#R6RzIu z?y_Mk4t87SNgJ1Crh;ix{L@y*Unl{(P(Tf-0&U!dyq=E1Dj!Wi)I!M44@Y{>Yw#%z zPAUlXD_h2bHkowMBKH_)i?eEaNlp3#rw{8{U)i-le(GbOWyvQG#4n7uFY}3V{`c+q z@nn30qM+xPgO{xAX)%&vy(R;{AlejXV(poZn16iMXWWW6#t8GSowvQ(!S2Wuggx4a zkBZ@w*eNCig5+CL_0Sq1h7y<|;T%yqqLp2QF$jskZqYt z#fmT>8li%&umFtdi0Tr`*|YMhI{dD+hs5MW4M57za)OMEXuAn=5Q*T&1|**;-wnh8 zFd}Ql2oH4Dd*BNM%tLAq z7UC5#7ze`QhIouRcnD48+5GxJXs01U_BeXT+_xu%VVK+nGPdj32u)qrRFO?_lD4f` ztCVGEhiCw%C2?;a2HUs(@GH&OCg`bjU-^k4Rjak{kV?125Tbldg6yichLUufsMU*x zN}wKbtn9v;h~#xh-PSpeATU3zz;&ddfuaf*g(WpT_efSIj{jqIqsN!Xb8XYns}3bI zLPeD%$QAfuyxZ~wO0FvxSNsP}Fri&=#!f0k2B)-e>9StRH$}wPoei^#a=$_5Muw5}8H-KmgZ2C>!q$?U_91qvq&H`hK%gqcf13hs6!gP${#q3{L`Trz zxcWMgqS?Ppr^N;1a5EG1^ zO8h`M9wKoZ^7bH@8B4$tMl9M<-J&upQuaT}a!@6o*rm=m*Kym&i2-WyvDIQq*+9gI zlE-EYH(bn)q57Dq#A{5=W8O-9T%bT2xb^8=;r%g(tzcM&@03`Ve8I;07Hq7?c+TN) z*mQQ5wJCDgR)nZYoDAZL?k?dQj1VnpcbJPEn2j=sI_BcQ&U9FE-(ik#;<_rK_4{Oz84v61+MES+5y;L z`!5KN7!ZNwmb>Bfg`gcM0t+oiuP**;MSSCjwa!CnBidf+Cth1yg~DK17{d4b6>+#l{J>* zVSyK63;v4PAprZl_-KfF@b$CJFDgsxQhr%A^9lSN+jPvY*Id)%%s^6*eG^0zmHZ&UAIQeMJr$J>1w_B|)@9%>q8FQ7gH8l8Oie+%($L?GgFtu`# z87In{Qfm|y$J8k#QBU@pI$sz{I7UsYz_o9QM$&tvVA@7K+-{N#&UyPn3zGrbHKq7< z@fwR8aEYg^lLc-Vrg}sX<8+}`b6pu;gqKMEt_%F^EXOTbB%UGQD|D z*0a0Mh=!}kpTzd}fcXLSdjLgCcc88vo(D>3_^XY8%^wAM>hQkg#st~oK+&ZqAalm# zldac3c)%fO4!gj=2g1Oiz~ul2ej|_tJ`Wgh?1g)q;Tmj;bYK_b5-GqEY{Dii!6qfg zaLOs8QW)}(VwfeQKpClsI zA}1+RylhvTFfs9O_Rp5uxv)BIwp>XHoM|p!A81AvIB~1|PZ?#9V4hx}k zZ*JlLz`tiOAglA*FEIAgt{L+7VuA5?SfFE)*A`Da^qMWQ5)5ww3h*6?Li#9%F=ntP zV7@2N8O*uA#4R#D*@yp3PELaX$vN4FA5DhHrkHckB_EePM>Gbkiu{No%oI_MKz9t- zFEWU$5G~=SkVE&UNY#mBQd{y+ehX-p%`q%T1>2>&f5pxyUbfee1q5`#8Y?@=`0hJd7ywKu`C`M$bP>tS>%l5ht6iI&A)`i(}_a=2*2G{~=Ygo8D4++G&1No7r z$W$o0re`oKA>FjW37iwtLn=d4>7rHklY0=-0nWZp_B>Hj-vLqJPgOL@yd-7LY4eut1z&b}pHSh+TtddCXC$ z@8U(DBH2ZeVfqLIN%?`>A?->vm+brba@q}L_e56CMTJau)JC-= zpnF!di)D|59}^7;u8HP~of~i$JUE*^(aGR$c#tNgP{D38$iq176Y4UUVPJxF-NMv9 z<5C+8*cp-V7ZLcKozJW&#>kp>Y~Y@>CzmWkfi_lAIYpF?BZBz3szgh;KO#P15JYm$ zldOBVd10stJ{?(RItgH2^fXcgbj@8u4Y4E$_nx%u`(-mjzQMJ9QKAu1v~qc+>6V;m zU0p`Vw;K&SV*I|>xmH}o->m>jZj15DMc)d}BT+d+#z2A5<)JYkW zFxHcnZuC)5)HeXRLc9EACi_by*RcIhusv*Q^o+V`l46gBejAizOXsH<*xZGm^ z4g#9RTu--d$n^B%t5b+zf%T`wOQ^ly#A8g)o+&C!TP+t2b0x3kiM(N;A=QBaM{0v$ zOf{i4dw*I8jIKu9Ii~kjiTBB8Ja-fwKQ_~l&=frjJb$)h`ykxWolba~r~q6cRODHP z93+xaE(DyDJI#Tm>^(0Yr4Aph_Dueqi)qAj$jCP_eVq0UDVswy@S{Jm|P`#UqVSW7j|KE%IJXp6OGR_>7XU zA@rkZxW#F#21QjWLS0%o@Gn#yKn(S9^;o^2VE8}ntib)4((~4Z8f#=3U`H6#sQ5(L zF?$tGHOvFfsG79O4bXEEZS=kDW&Bj z16hgFATUk$3L`%b!4|Sr+q?T>}vr5zjr#cUo5eP?ZyI?=c|Fky1g6-WT(8WQ!z0QAv zqXQgWYwiu2bJ#zLCL!(rY@ANYgr_n9b0WrI4(1`A_;>7+_MuvtS{&JlM{81XC_elD zjEuCIDPiN-z|)8K-dNj6li{pDSldRw%X$2sQkM|Fgm+1 z415o4R?)UIl+}WmhGsNUSjy{470;t(tQhCAZ(MdWCoK@zjxh=x7R4osYo<8Y1vmjG z-~?=k6LIALhYk6;R6y{kJWMK~fG8qApC?@kTb@6>gFdt2d}YZK>42gtR`S|FGL%eb z?@sKh+>@Ew zg|?KewE6ZBmR;sLMZVG~R3UexXsza<5Xb+rTm8U>TQ?9G@Mq7(|&U z?4N3&+4uogA(i~`@jb@AR2y&{F2A93RnMk!@@WACJ_5DY`yzhW_+3h_O?0xiL@%h}dx}4)G6p}{K3x8(#;*Nt9cpLT4OeQ1 z!$UjXL$mKUcC`eX`Ue%i*ynAp@n>BB`@TXDa_m7zQYE}oci)+Rmgk(Y+M~Xnvu^Zu?Q}F-mS2>~Ix_r0t1qq(R3^W&s9&rYOi#P-1A`Q)6KQbi; zagP;5Buvt37>xxP67KWM|6K_RNLReJKzGFGz|3X3vuEaWwVFgB^pH$^YUf1@Q zfkf*RT&SNPiA!|8nRQ4c zb%6bnYFYqG&|3I^Fx`BPC0JKIs-btC=i77Y(VLs3d z&Z>jLeFD8Np_}(;wPaf?Vx8lURVy2X#AfAgK=YzdCIr9pu5M`q$yK4u=flV?Vt43$ z8KyPNOc*p~K$i*qH9_gWO)d6^3?V#4##4_%76^+g3_GZ>DEtI^Qt6ABYNxW5t(h^E zW64i?o6}%N1hc1gBuA?Qo!^V$X#vPEI2w)QUSnbf30SGvd6!w(#l0$pKD>`&s(4`U z5Pb1&i7Tne24@-puOx?Kd{3R41~|>6r@CPcSPipD3%dj-Pk#-T$0Q!WFoA~O!gIC` zG%GgU%+}QUQBnv=HO2NMu%!hwV}f2f2mpl7{#1E zMp7FG;ulh`6piT@Kyge@UosjOz~#|;yvZJ%%?9yO#-4%^he$}MvGz%UVN8eyB{_;k z_o*B$Q*jgZ9lLQ?#4OB=hGt`vgc8KVQ(xx39UIt*SB^av-UqLp?JUU&;SB7Bm%yuF zAG9#6^g)RfO-DBaY)=r{Dra5PY&sCMD4KO&>iP`F@5Ad#nRHLoAh~f(VCwKNW&?<%X%2inpSxvAVTn7t_6W_7uixp zE#H{3R+=W-e5w?xZ2L?mJ>SG?ExFM{KShcc1)+A@c?_eWzZG3!pnroV{^i$^t=h@~MZ;)uw2WqQ)thnz2 zLpP0Z$@vBgcXO()JGPHdnD(TEeBMKUk4+NaV|!waY1-HkU$G@u2B_G^5#XYq+Es=S zQz_Dk8Ko_Vy(E)sImt8ld~*oK7&EPApqOpI>za&?6thSeeT|)Ye;-T)nDl%D=QF)6 zzyrWC6g2w4Jn9`*HtNigQbGJcjNv<^!0&Mae^95;w8ZRgLQZu;9eh%qkkpBcyaBo{ zCY@Eoh+hFO01-$zaNQBu9R$6<`Xz$derMDS9UDnuyaRaS-^}VB**0}WF`N6X`s4Sl zW9-`A2&!ewP69HTPQt66a1CAqn`_RIcjPI&Yv<$kZ>Zpbhw^?Rtv7hHkfa0L75=E7^o_My5gR1d;<_$9kq2#l_otk#F+OVu3huxZ5{Di!v>o=bB^g zHk~fE=Te4|sFWUESGnRXdx(m)ZJ4?dppjerNJ|ZHmPxhQo%u#^W2PSX8F5uHCL^HC zm0X}PQ)SAKf$L=(gStX|CXIsYV@&%1Uf(@MXPD1=pwEUlGNhP?!1ol!47^-lTAO?a_h#Q@l#CYnU35;pK#~ zQXSNhG&UWKs(Ye!7ZP|xnZR%=2?jI*?wiqj`wZaS6_dLsMK=kdAw z{WE0h>MIkYKkE3WrsA(2_^)`&%;Xb&xGzI&Qi=hi95csorTEFp%M?zeBrjk1VC`v0 zYH*UI1xMrO?CWvM!PIcouU~e`z{?6b)s&*6z2*JoM*M292|s*II};o-{x}Bv_}a<} zodFv&VzDxrRr$U>oVcG0aV~6Hvl{g!$R% zxi~?6;;H^tsB|u(`#Me6=XC%Bu&fauybL&p5_p*|ZBJr|d0QYhL819wc*z#oYz>~O zDm=CmVlNb$UyU^be!=gCE%?)88zQ!3BGAZrqC&zY1a>vVGz=9IhoODS%boE+A*%^; zF}At8a2>~lKFHU_Y3s4pWPDvc4zh;+@QlcI`ncz;3u8O=8*#qiVq0Dk|P2p!q@-6Cdwwgm^cTFVFlJ;jWJ;iYcO6z(?X7W z(|2x-t$GlhE5LVnkexse)!3!ycvyJxtcRMWg&g<1JNjw@9KDw1^#5uykrr~?D_tG} zAn>niPz1Oa;shd^)FR>pFmtH65C-O97Ur#!@Dp+vVxPE7r3(9mz_0)>1>*ylA}Y}g zd%81hcsCP3zXOYbu6WacZ(-{M=}APAFqw%!cgPw<4_+F*L$f>}f7U^IaK*g+fP?u8 zyPz}?yq*ZhcpZCJPv`JOc*R6a-uhsiyIHAD!{OVUGx8yf=Rx+l7LuN4OZET%yvqvO z{z8q0i;D((o5t_fFM1${x6kO|ciqnim5^KF;#;dvuVv1}A|A!`z#^~#n7Q;H|{7O?|Oi2VNb!@NW2-biD`N~u_~ zFM6<3&m(EMtA)0oqatx@BDm8n&IsGtnA3=QmzRV}2Lx1s*etC|{RrKVwqg&XgnO?V zBeoe-)CRDB9BbtjLBUpbqcFqJ$9@Swo2XLICO;4r^8`|*t=M=qVHu@BHm2d{S5`qU z5#C34Y9=QZe-Ko{w{Lnoh*D0M-2KJ1&`&KK@B=M{u;qy~ogh&=s3Vx$=SeW*d&#yF zwr#3bQq6YiBgjpzZR7hpD{Y~!z3+%023{W!0>os{Lp@Bx(H@YFRv;;nHtZ(ORkzVp z7^0bi|1gd?qbZiHZK<6!(RKM)TRls&pdG{*V=C*6tz^~jxW{6kF{kf^i9yMK%387C zB|YT@-Ck1FfW&pwkT*Oh2#m^cyeLV$VNw;N*V@y;on!|Z74*n*1lM98AR#q5z7cxA zzt-?cqRVl~4M{`Lh%gy)my5rb);D;<8cX`%(r$9Qv_qr|}=W4}Pqg+oCjfV$ZMu zvlMHQwEyg{Fk%5*4$Bts^Lx78UPyS*>$Lm%UN`9vheVbYdVc& zZv7r2X(=N^nG6%RiN7O1nInpzlX2eIG52WLlEy+Q8|Fkmpq43x^`&3P1nX@Byzm*~YdlDFm0J6|v6+ zEUkK%EYPPpIS*%-ZhJhaX+jWpUleF6yUcQJ)qalAxIxYyUqPNi+;7*z znE~Vp30q_=FP9Gz#^szJLB6+OqD#*~#Ki4H{V`r2ElW*rNV_PT-}L=LPD`s`F3TicDX4S(Y6S{NgUBc zh@-e~T$%q|gX*xdnnY^G-hcnKi4r9jXk0@Z4@B!{xPR)xK&9 zQIvK3o<~T<)P*VD3dU)G1Gn;Z2an7oN*Y3u42%f`<)LtEfIYOSN~bM1OZPQs{(1D8 zKlPRg{nS*&1EVfyS%<#!_PEW#Z90AzTeEk=X-w!h?&n2WrD6>~Z%{0onci-JwHPa^ z1gG@UY)BY+hlSy3hlL}N>(k%Y4mLXT76Sq=(=Ol{G#kW1`F1wcjeqoO2w0F@jv`kAL7o$> zWUXRCk>pw{>DFA^J8DHzCd7jUt%)&1K~^Ogl}DRei-c{NMmJ?yjJiEIEqc#PUZBxQ zh5+NT+SDW+%u$E5q$%P2U7GfeM4&xssxeN)ZfZl}fjM zb57=_=cM`SW{b*9f!+*S)plkUH7}7W(X_PQnu6#>1}ANPbu@^2 zuaWV~y_?@=dx3hu!kcSL_+xX++TV^osY)BZx5qyvUfb zwU?PWzSW!5`$($c!(u)`Bn48~DPefg{knT(%KWSs^%}f>LCH4zh(%zw^&8 zXkpc*w&-YJrf2J@1dKpT+Eh#km8t;vz7S-VeRIcckfU0RvfJZ28|5-@woN}Qb7Mvo z5r*xfXx5g(gtE4EO~B%F+|Bcm&*@uMszj_1?EN{{CZmR`4dz_)d>nfK*T#*l1p%13 zRxyiamcY83AmaGLQ&RC<3Nz2a%)n7dxipov)uk7)m|6;2wM|OaOF)NTM=IudLWd>= z;?5@2OcaBOQi-C$oR_wnm>-d-x>;lD>ZML|#0>Zl$IXd`71LQTxkD z3xoII-}UPpuul6*Xhpvfls>C?EInksJ(W4Z2JBDwnQQ-G11NqQtqcFXPp3|3eF(yl ziRMP`c?83xcVrLc8DOrVurt*8{=W}%LZJY^A**Y!)u5K`$bHEUFhaEaQ#;%lp5AHG zk1u;b;|X<>fh3dMs`Q=wa=;x&SOYaKpRZT1@UtK|(u7bswbE6t%NCVl{w^HCcs6g+ zR?H{(k_ZR*(lpEo4A!M0Wi#azQ!$9MPZI16CwSn}6Cgy!oH_a=AGRMge;bB zQD3x%;(uvU$1%7@?G!`EtqMZCZ8a5nDrY>;FswlJ_R)eGSv5K?JB|G1C#RiHfQiwE z&;v&6rcF^bXV>-jRLfFGKb_NLnNBy8{TZIOLBF5ssN%h)$gTC@wt4`7Y8g!3UFI(OCRKtg?#b2g#1QV%7 zYs8F&3$b}sJNH3ku-|PD&j;dVPfi38as+nZT|^I#*OZ)|B?wLxJEll8f@Zo1;aKGn zmu5%KWhAO@J@T0FrY_sEa7;mNE}_=tf|4C-S&g1IXF@vnHM+?9523LMn^y6 z0cCVmk~X`CC`;8s-h*SKRD{1B;>lHDzx?!>fOL=Kost({ju8~bBt)1e6+lDIM7+Uk;iJqE zxBz=+!{;f${4|H;@!>yCgjJ4p#BqH-#+WhJy|z^2h81ZAGY|%(qibNyytA3$I6j@@ z)PTy4Ah!*HMU^5v)f~?1M9};$(!P*VoUoLpYGUdsN;thn7>1i}KY~9-BjW(ysJw(f z+u=*6=V=C6vy+QXf0ePgI+uWqhN%vbjw0+w50L7yPEpMb3PZu`$6-$5^(F$lx*Wt- zK~0Veik51ba|pO@4Jp&O!yRZ@bJ#Y)hCH5*}Xy1efm(RmjCR0TXM20P$ceFz7oEkh%xvyYy`2Fh@2 zwjX89h8=BN_P#xwrh-_~G9NSg67v#?e5m?VUAB(724&_ON5>-1is80CeX%S+;KFgL zHmobz?%?eouW-scU#1?Z*{0-;fZ6FR8^gm{z%8`uM0Ambe&rB0H+q+iI2-ulZZo_M zhh?hs44q;<%}1mKA>fZ;X&0uxKKaw_=uc}xvnM*H{(f=Ak;il1bm->%!xtVmXX^&S zzgUShWz&Clyn8Gk=RVFkFGb6nIrwpa)4*GYz%KENupH~Z0+_sGG$Soh0#OXMJ7HUQ zuozl(I=%cJkf#(eD|r7VTA^7$Pri^`fdspE+{mRRS@ezQ5I{L zlb&W2nEA_rJVgmrJ5kk!jZ_8zz5$@LGMK|~wxZHLu79?6V=I_5dQG#Wc+FO9N7wD+ z+xVW`yM^=2&WSRmcDA$hGG2ww9Iy;(<|K!)F%`}!nl-EhCPfr13c_pTgO>c8n}vU2i% zVt8AtU8t4@^A@JH{KGA1vJL0`#PRCQ{On`P`Hx7@TbLfqpR=%rqexd-Z8KA@S2>W$iUQhq;|#qkHAJ+|31+y*Ij96KI65K26sL zKTWnVzR``ExKzF?r|smV^IcSTaA zb}4;872Goxf@_ObyR$k=Z=!mbZQFFQUOEm4r!l@-lt=82Ekh3exhy^OOHlOk2y9_Lgl%L6x8SB~?xYIV` z=!dwsAee4FOH~?ZwzW6--+1=cwm-bMkDzkzV?p zdj9lwuUYtT@0{gXur~~;Lg}xD9+x+O z=%R5reJ|wq=f&)f=B?|bufZQH(a(&Y#4PM*}ykxa4h9~%o+Sa6~R4)!=Xiu5oBCcfYg(Va+9H0 zqfURMz-%TztJ`GU_Lk8h@OitH#V~cPiz8Acvr3}TXb{K#y7gi(n0;bklqTn5Ci3Rm ziMZDB#uq#os$kLsaL7l{O98nAo9J#tOC(w&(PpumL$pGoJff8n)dj~_F4Ua>v73Cw z-Y~1t+9f016ApZTZ4aiqD^c@6_t)vxkNQ2S{A{~Rh?x&V0?l zUBt3>RN}~X5*nTxW*A>2w@wLnAtV}w{y?UG#b9v3& z^u-VRz^uNAXdoiD0BJP1A{=L*Na#v*%85Y22MR1L7H>~qO~;b;A4l;9*toq(NQBpC z8caJiY4A0^;F=IpvaPWzb;pnun9_AlprdGtdPoThUC1|;vVijs#q6pg>rdq#O|UhXMD z>Zda@Es54BLYJi@3X}ARBl-jMu#AxpVLQ}LuIIG#h3xcpMaS*h!ruCq6;I|h4&4Dp zz{!!Gj{INw+Z#XJmg^h04Q2n`-h9W4((w0VUHnrlrtVDdo0DJRAFCAo;@1cMr}HHU zzx9ihKSP4J`A%7<^xuk$VV;Ynv)t!X`lK$3DgcH^;#gIf7)dT%)l)aP*tXa}4cWN{ zWrBvMiLTZ7WEyztySc($_m_=B59DgWRDWeFn4|n@F(;vTLOB^J{YGt~Ya>dv5Y@7r zXRH+-*(GV!b%og@TTx>12%3D>Fyw-D>l_E>y;p2v$*!`SZuN(Aqt$qGUVmAxGIZk_ zxglTmVkZZg?pSa&Zorgk@&eTKS>VwC>d7FWK^7aCjHBSd15wH!=3xT!FoBt?W^`h` z5L+@Dn~s6a`hw|?8)EvCv)7M&a_7B!D*pSN*{AmM>zho^Kno#4BJ>+%iZ~zBH9S@5 zZBmXTzBDA|l*h96XYAbAd=;jv>fV`P-pf=CL{7olG$$)hSeJ?d^;uwNC3PYpWZgHooA(yC za2yzJ^Vn50aG);dGq9y`N2-fsaeepV_y%|Qnzi<5iSO9Gu)Z#NN8yuaefP!X?x&Bk z50y)Q@q(rH2zt(!PR-^I?daEb&why_Yf>4-x{WohMFpa=3kDVH6inFV2pI@pAL#c| z!g}54>ws59hvUwVqa;??1?S;zrWf{!T)>I%fHW&z8X|Tpvpg!JAv6mZT||uPpmBDm zln06a#iN}X#Gvm6dW=A1P6o13g3g@x4UFY-kRe?^ess#T>DaCx3MPz4PQj+%nqGEV zGu%i`rb^EZpw2UO;NE-AE=eQArE3HiGTOiW_WApj4EVqQ{M#8I>&;WGHofn6;`^qH z@oD0h20%V7;|Y`bWaGx8=kLFi&;9d9H)dtYVP6G%`e1ohIx)?H4b?(knkvXOo;nQ< zs!^^fYK}mRW6I(W{Woxi>&cA@@IP6<8*jV9tj~Ph9)<*Z%8Fr#2$VL4ULgHoWI@x{ zpHhl?zmYN0hgJ36K#>X$4hK5C?T)yPMEc{QqcTjH9rLfOzEG@|Gh!a=KJ#bpeKh=w z9*5Mm|LNVkUSx?wznHuRD!A9FLLcEuf%o)3wFjk;OtVp@FIU zy9Vs9ENsw=`EqotEI$2jgM|Nc?^e`x5$YH`81jwP=>hV!57jSC!*}?s@Ea`6dY778y=Z>8fkg}*s`di$; z+c^wFK_D9{br>Zo(}*Mx+-~J6y z;2Uf?gHXPd*|5bJT&^m_80j7E6L&D$JRQ=yP5{yh^i*c80qOX~=@XuyusB_ejz>ko z)ruL-eB26+wn2S=2FdQo;QsyAg-cGF3qFSO?dA6~)gONRIa;Jw{M)ebYKszbOiXiA zqZ0*!M<<~sVNn^joKw9ZiUND5MgeYz+WUop34j1J#iaCTOl;n6?G9yP_ zHiJ0r3x@Aeh@$rmSrk=GzgW>qL!E0sR-4f-54Q?B% z15pb)4*36ojl)&x-H~7kme!EsbA7@SkSpSJG^gv&(jn1JcsqyQK7nNuFSpy=&1yiY zRADNsmH5c~r`wm#;K)2A1Ke_x9>I=GH%HVqF zV*c`Vsp=7H^iC>%iUH|?$K^IyXbdWgtx|+55blCL7MYb)W~CCzA|%AhQ_6&PC=f*ctKZ6 zQ1DhG-LeQD0ud~V6&a;J;j>x5UUTqtNHYQ?m3$=85t$Qblz5yleKZrjP5E4I!yP|s zJM7dVs0c9?*Rt~y=Z`5`jp)V!?Bg}F>0=?*ovx_}Amq}X7$b!16y9sKX8gZDJ34mV zL6CXR6m)eMrs|Z*(4MMy9`io%eB}F%ZF)Gyj;R@tlZCx=;h7sfT3(4w{r+TFsh$LL z-57{1x(+D?nc=S0hL$lCmV=(nw@KSu-g{we?WT-sF) z{-ELB>20s=%k3*&YaiXo&F=jShW~tvE;`?=-SRUlo_Wy~7j;+k`KOyCH>!F{rXJwM z(Ldh6r&{Ix8p+41I|y@rNfi(C-)CAaX1vZcI<#>>^ALl*z;-u_=tV973Y7oan!Ft! zpKVS%Pe(|p>=6OhooQ^1g;laPKWygVINzt87vpH%d^$g+g)^%jf5EDa`D#R>nVX+C z)uoTE?A|}M-`)^7l@0xWJ{|+_VL*5gZ>>)_MUFU?>4)mU51S_{zXU2+3cmPj)A;Q{ zQ$+uyPi#N=%+a;r8Ssn{68^$BBY)~ilHvITr;KwqzaWvR8BF$S(arecCOtasU1wN2 zP)7Bpi_&X$o*zzS<;W`?PF)9&xNuE6a`%kK9dG_!ubs12wk)@YO)?w7<|j>k^-<^3Kll|M?D% z>A9Hvqrtq#x8gTPUyyi&vo0!QV&~$*NaJ zkH_G?a#o@K8M|@~T#|=ttbOZ??1fYG3#I#a-Wun*h;R%BsR@!9j}EIE)nZZx^9$1l zjH)um&R@kDEh*LJRUQQ1*>HpVweJ2X%JIWr{r%#V9DMYSU)lQMU4WpBZ_<11T6gVw z?#J#ouD;{bSpNB)|9s6^JpX9&hnId={!pfw;kUoQ-^6nL(fj}Rh2-`Ahwd*f?b+{& zQ)V~B6V9xjys~iN=xoCz*3(%yoCQ{OiiY*o#Hm)2mISx{TQW;*MHaMJe)FyeE2n7H z#ao{b)>kJyi-&^WguziJ`>%3#Ngts3tGj>v!{zIQ-&x$=^XADT9;15u-jnepyqfy# zuSMxAo;q|1XvN%tS5E);xt#pbLG}zbf zmN&T`9S9=l_qRhsq}i$tR=e!b=P(7n&+80 zTzct(5MAincrpyVZg=0yb#~(AD zN_ZE~!|A|Hhq=5gqoQ8#_d{UFh_+_eyd$TM?+alD$fnIj&#OV#_Pg0o)M_g$by~^( z{k>ig$IrC7`qNRQ^1NKmq@{j80xv!fZ~(^xk@8B2{jzf-THbOWL&j#838w0rin&tr zqTpJ(T6Ax5_Q8H7AFX%~o-OVV0-riRN!Q1pU8nVqxqD+{!aMa-^R}6fbmG{taf&|c z%*~m;M$7+Mx9!42+-SrT%+7AnRmbM#(zr$88p)~jeM#LD~zj)ANk;}m62@9w_RH{LW-nATrTcCI@2>VEzQ1}QU(1(bf_AH( zB~O37;Z*w7!Sv-6v91TLS~V4m=|H2CFVW~3gMA3AO-bYy9bDlXiETW_WMYcuL1<#9 zy@k!!`Qlx41i1^u;NlHMmz0ULk=+IO%`9Ab*U7Tc><&K~Cd-<10qO$0agmK?)eZtg z&IE6`bZWH<$ae=as6NCS`2j4wbK;BtuJ9fEKp5)t^)xj*C^EqDl6iU%D|@s>XY2Y3nhdcgFkhZ@>&JO9XxfunKI2t#BC5FjQiuZ(l{b6%kPIYy#^^w#F}=P0qocj4MPQ0BH{Jm*e}b8N5MGQ8j74W4gR z^O04uA7(x*FTF*EA-~LSjNrWe;R4}#$KFCH(-2KBQQY79r*bFCc%`5;#zbcR4sf-) zfL90_#3%$>N66J*(J*mI52mr{^lD2ghYHk!F9qBPS|>C;86%>ty}V`=dW8pqef%K6_T&8vf8;PWAC=j7Q%Oua z?7a!HIPmDb@lGPI$|F*tYtE)USJC8X6-S9O0Pr={>&R4wZq#ZqWJhjNvT& ztZBAao3A6EuVvX?HSXmmRXeR)18cVeAW=+p`soDWXMDghvmn1t*G>vapsFRqWetxv zX8$S~-pA7+^(80yq=a)|8$G;t)1Mc5Y1d&4IU1zL?%LPv6LvgLZDO`z+Tx&3pD^fV zMA#Z}V_{=K(-BGnL#9RGGql?xAGd4;Z0!iIF(F)%uU|p+?9`n93HV}WuO1Ow3PU&yw=rIk35;L_!Hcpo#XZis=(#*RlpH2mJB-e?qv1kdmiU09 zf5=sM3d6hR6_;pMy=qBX?IfEq^$#dmTjn>qm_~w@6(UN#=ZEPfAQ33;R=kg4CoRsU z`SFrNzywahMWzV5U<0NhLTz#gq+u}Je8Ka#D0_?MkBj)=bp#sih~uSp0_52B0k{@> zlLR%kbXiiVEs{M^o&-6#6!>-&}0TRkYsn?$M7 z2>wdT0viewrJzk*u`x^*mLM99QFPzH6#|dP#%=_DLXc6vy6y=rNxio|gu=Cas`8;L~hcomYK3D8)GFoPFE{TEGt0#W|oh37X@zkD)dVrSu1el@q zpFSFc(sPOyNX(7&v5%j`cl(Z*^q)k9v87P8T)~WW+FU-{I?BfJ={i}o*Z}wWn7pNe zn?s!Zgo+Z~V2SK({T5nB+YGVHVrI_OBs9t*lBNl%hSpy$`$t*;6k30Pp``9b{*84% z!_34afsK`&`uAxj_B|%jb190%Tn7#X1Ba1_J%#g9MDKlWwh3&8Y${ifj)+J8#P&vV zhmmis1ED0%#}k@ABxY1>H;Bnx0X4|zT#2;^ZJC@lP*ujOrLuH*rccSE0PRJ0FqOs4Q%-c5HyLq6K;CsreEhp zg34&SgB=Uomi9D@=?KbUhK{%x7&@o8ij!*F>UqD!WP8R%bi_s`T9}w*A$G0m3!=tz zOJRQ$+d@Gtq8!$rmM7wd1kcP9d?;fghlL}Nap*<B#E5YgyhYT;TEkC{w78f;!JU;$u5?l{}mM2D&X-V=eM>g;!TUc z^Al@e##D|mrYu#@ip(u{&hqWtJc39v1>>hlgrw|13W^PbDk>DeNIPGG9|uH()W=xB zbpab_CvZSyCoZt{UcBZv;`ik(E+DXi0L{jgkdYAEd~PT+iO_T9jKY^}mo(_WJdX|ytt zX6!8WNo#HOM9x--6+C=Y5!vHcQn+bFR=ZwxotknxLsOzvQNyO0L*ndMEz0dC#+jN9 zGpxZ;(X4bTSt5rA!H#BbOpUDNLSgcbm#+~!o-nn~-t|7p99&^^fXRpr0tGP(_JCQqgwhBcaa1M_t}^9Pr5kt2F~^;7 z(lCb$E@EIOaU}e)^ssx=8{X1oq2F~okD1)9u`N=34?J{=f-fJNRT%QOZnw0qTB|pj zt#+r|>ko#b^^MJ~@%ChAcW*k|KR7(PaD4I7Ht>yfQxW2P|y5`~ReY$OULrwA~z1(%u6 zWhA-u<+h1zLzgnSt(YjthFSfbby;s@riA&NMq~Lpv?HFJJUORjtfWAq&`}=eUvgoO zyxZ^FNNc5y9fLc!Z}P^Wg<-ia7X(RpgbNhq1j<-uYFzm*R%oFcoexz`EwoNv{yG 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) => ( +
+
{ + tabRoot = el + forwardTabRef(props.ref, el) + }} + data-titlebar-tab + data-slot="titlebar-tab-item" + data-title-overflow={titleOverflowing()} + data-editing={editing()} + class="group relative flex h-7 w-full min-w-0 select-none flex-row items-center gap-1.5 overflow-hidden whitespace-nowrap rounded-sm px-1.5 [container-type:inline-size]" + classList={{ invisible: props.hidden }} + data-active={props.active} + data-dragging={props.dragging} + data-state={props.active || props.pressed ? "pressed" : undefined} + onMouseDown={(event) => { + if (event.button !== MIDDLE_MOUSE_BUTTON) return + event.preventDefault() + event.stopPropagation() + }} + onAuxClick={(event) => { + if (event.button !== MIDDLE_MOUSE_BUTTON) return + closeTab(event) + }} + > + { + event.preventDefault() + event.stopPropagation() + }} + onMouseDown={(event) => { + // Navigate on mousedown to shave the press-release delay off tab switches. + if (event.button !== 0) return + if (editing()) return + if (props.suppressNavigation?.()) return + props.onNavigate() + }} + onClick={(event) => { + event.preventDefault() + // Mouse navigation already happened on mousedown; detail 0 means keyboard activation. + if (event.detail > 0) return + if (editing()) return + if (props.suppressNavigation?.()) return + props.onNavigate() + }} + class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]" + > + + + + { + titleEl = el + titleEl.textContent = title() ?? "" + }} + data-slot="tab-title" + data-titlebar-tab-title + class="min-w-0 flex-1 outline-none leading-4" + classList={{ + "overflow-hidden text-clip whitespace-nowrap": !editing(), + "select-text": editing(), + }} + contenteditable={editing() ? true : undefined} + onDblClick={openRename} + onKeyDown={(event) => { + event.stopPropagation() + if (event.key === "Enter") { + event.preventDefault() + void closeRename(true) + return + } + if (event.key !== "Escape") return + event.preventDefault() + titleEl.textContent = props.session()?.title ?? "" + void closeRename(false) + }} + onBlur={() => void closeRename(true)} + onPointerDown={(event) => { + if (!editing()) return + event.stopPropagation() + }} + onClick={(event) => { + if (!editing()) return + event.preventDefault() + }} + /> + + +
+ { + event.preventDefault() + event.stopPropagation() + }} + onClick={closeTab} + icon={} + /> +
+