ompchamber 2.0.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -11
- package/bunfig.toml +11 -0
- package/dist/client/index.html +16 -5
- package/dist/client/index.js +1393 -0
- package/dist/client/static/js/index-8t70jhyj.js +5149 -0
- package/dist/client/static/js/index-n4wkqj7t.css +1 -0
- package/index.html +54 -0
- package/package.json +9 -10
- package/public/apple-touch-icon.png +0 -0
- package/public/icon-192x192.png +0 -0
- package/public/icon.svg +60 -0
- package/public/manifest.webmanifest +38 -0
- package/src/cli/lib/commands/serve.js +9 -5
- package/src/cli/lib/runtime.js +18 -7
- package/src/client/components/common/code-editor/handler.test.ts +274 -0
- package/src/client/components/common/code-editor/handler.ts +175 -0
- package/src/client/components/common/code-editor/index.tsx +185 -220
- package/src/client/components/common/code-surface/index.tsx +109 -12
- package/src/client/components/common/lazy-panels.tsx +5 -1
- package/src/client/components/common/provider-icon/glyphs.ts +118 -0
- package/src/client/components/common/provider-icon/index.tsx +85 -0
- package/src/client/components/common/viewport.ts +5 -3
- package/src/client/components/layout/RightActivityBar.tsx +2 -1
- package/src/client/components/layout/desktop-layout/TopNavbar.tsx +21 -4
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +4 -0
- package/src/client/components/layout/desktop-layout/index.tsx +26 -4
- package/src/client/components/mobile/LayoutWrapper.tsx +16 -0
- package/src/client/components/mobile/RightSidebar.tsx +11 -4
- package/src/client/components/mobile/SessionSidebar.tsx +10 -0
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +0 -1
- package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +16 -1
- package/src/client/components/mobile/mobile-session-sidebar/List.tsx +3 -0
- package/src/client/components/settings/categories/ProviderSettings.tsx +45 -4
- package/src/client/components/settings/categories/UsageSettings.tsx +22 -7
- package/src/client/components/settings/categories/provider-settings/AddModelModal.tsx +322 -0
- package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +152 -56
- package/src/client/components/settings/categories/provider-settings/AuthSection.tsx +52 -1
- package/src/client/components/settings/categories/provider-settings/DeleteProviderModal.tsx +133 -0
- package/src/client/components/settings/categories/provider-settings/DialectFields.tsx +136 -0
- package/src/client/components/settings/categories/provider-settings/EffortLadder.tsx +89 -0
- package/src/client/components/settings/categories/provider-settings/Header.tsx +2 -2
- package/src/client/components/settings/categories/provider-settings/LoginModal.tsx +2 -2
- package/src/client/components/settings/categories/provider-settings/ModelsList.tsx +22 -2
- package/src/client/components/settings/categories/provider-settings/PresetPicker.tsx +82 -0
- package/src/client/components/settings/categories/provider-settings/PriceFields.tsx +107 -0
- package/src/client/components/settings/categories/provider-settings/ReconnectModal.tsx +43 -22
- package/src/client/components/settings/categories/provider-settings/SidebarList.tsx +2 -2
- package/src/client/components/settings/categories/usage-settings/LimitsSection.tsx +115 -0
- package/src/client/components/settings/categories/usage-settings/ProviderCard.tsx +19 -26
- package/src/client/components/settings/categories/usage-settings/ProviderDetail.tsx +29 -22
- package/src/client/components/settings/categories/usage-settings/SidebarList.tsx +60 -48
- package/src/client/components/settings/categories/usage-settings/format.ts +36 -0
- package/src/client/components/settings/categories/usage-settings/providers.ts +52 -28
- package/src/client/components/workspace/browser-panel/AddressBar.tsx +1 -10
- package/src/client/components/workspace/browser-panel/Screencast.tsx +4 -5
- package/src/client/components/workspace/browser-panel/index.tsx +6 -6
- package/src/client/components/workspace/browser-toolbar/index.tsx +22 -12
- package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/ToolCallCard.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Todo.tsx +1 -1
- package/src/client/components/workspace/diff-panel/Toolbar.tsx +71 -38
- package/src/client/components/workspace/diff-panel/index.tsx +139 -53
- package/src/client/components/workspace/editor/CommandPalette.test.ts +242 -0
- package/src/client/components/workspace/editor/CommandPalette.tsx +138 -0
- package/src/client/components/workspace/editor/FindWidget.test.ts +261 -0
- package/src/client/components/workspace/editor/FindWidget.tsx +219 -0
- package/src/client/components/workspace/editor/Tabs.tsx +2 -2
- package/src/client/components/workspace/editor/Toolbar.tsx +129 -26
- package/src/client/components/workspace/editor/index.tsx +180 -62
- package/src/client/components/workspace/file-explorer/Modals.tsx +79 -2
- package/src/client/components/workspace/file-explorer/TreeItem.tsx +43 -113
- package/src/client/components/workspace/model-dropdown/Item.tsx +13 -23
- package/src/client/components/workspace/model-dropdown/Panel.tsx +1 -1
- package/src/client/components/workspace/model-dropdown/Section.tsx +13 -4
- package/src/client/components/workspace/model-dropdown/index.tsx +7 -3
- package/src/client/components/workspace/todo-panel/PhaseGroup.tsx +76 -0
- package/src/client/components/workspace/todo-panel/Row.tsx +69 -0
- package/src/client/components/workspace/todo-panel/index.tsx +161 -0
- package/src/client/components/workspace/usage-panel/ProviderSelect.tsx +10 -5
- package/src/client/components/workspace/usage-panel/index.tsx +36 -18
- package/src/client/data/samples/dialogue-session.ts +1 -1
- package/src/client/data/samples/sample-tools/other.ts +1 -1
- package/src/client/data/settings/provider.ts +141 -9
- package/src/client/hooks/editor/find-harness.test-util.ts +170 -0
- package/src/client/hooks/editor/scroll-container.test.ts +102 -0
- package/src/client/hooks/editor/scroll-container.ts +26 -0
- package/src/client/hooks/editor/use-code-window.ts +2 -20
- package/src/client/hooks/editor/use-editor-find-write.test.ts +170 -0
- package/src/client/hooks/editor/use-editor-find.test.ts +118 -0
- package/src/client/hooks/editor/use-editor-find.ts +321 -0
- package/src/client/hooks/editor/use-editor-history.test.ts +216 -0
- package/src/client/hooks/editor/use-editor-history.ts +134 -0
- package/src/client/hooks/editor/use-file-editor.ts +24 -27
- package/src/client/hooks/editor/use-occurrences.test.ts +236 -0
- package/src/client/hooks/editor/use-occurrences.ts +98 -0
- package/src/client/hooks/models/use-provider-names.ts +1 -1
- package/src/client/hooks/settings/providers/actions.ts +337 -0
- package/src/client/hooks/settings/providers/api.ts +69 -0
- package/src/client/hooks/settings/providers/index.ts +138 -0
- package/src/client/hooks/settings/providers/messages.ts +77 -0
- package/src/client/hooks/settings/useUsageReport.ts +8 -3
- package/src/client/hooks/ui/theme.ts +3 -3
- package/src/client/hooks/workspace/file-tabs.ts +62 -11
- package/src/client/hooks/workspace/file-tree-actions.test.ts +348 -0
- package/src/client/hooks/workspace/file-tree-actions.ts +250 -0
- package/src/client/hooks/workspace/session-todos.ts +102 -0
- package/src/client/stubs/elkjs.ts +6 -1
- package/src/client/tailwind.css +30 -0
- package/src/server/index.ts +20 -2
- package/src/server/lib/assets/font-css.server.ts +86 -0
- package/src/server/lib/assets/fonts.server.ts +117 -0
- package/src/server/lib/bundler/css.ts +116 -0
- package/src/server/lib/lifecycle/shell-route.ts +17 -0
- package/src/server/lib/models/provider-listing.server.ts +257 -0
- package/src/server/lib/models/provider-registry.server.test.ts +122 -0
- package/src/server/lib/models/provider-registry.server.ts +76 -25
- package/src/server/lib/omp/config/models-config.ts +11 -0
- package/src/server/lib/omp/config/models-validation.test.ts +137 -0
- package/src/server/lib/omp/config/models-validation.ts +111 -2
- package/src/server/lib/omp/config/provider-seeds.test.ts +167 -0
- package/src/server/lib/omp/config/provider-seeds.ts +176 -0
- package/src/server/lib/omp/config/providers.ts +94 -100
- package/src/server/lib/omp/core/auth-credentials.ts +45 -0
- package/src/server/lib/omp/session/todos.ts +100 -0
- package/src/server/lib/omp/session/{usage.ts → usage/aggregate.ts} +1 -118
- package/src/server/lib/omp/session/usage/shaping.ts +130 -0
- package/src/server/lib/usage/deepseek.ts +4 -5
- package/src/server/lib/usage/kenari.ts +7 -6
- package/src/server/lib/usage/omp-usage.server.test.ts +126 -0
- package/src/server/lib/usage/omp-usage.server.ts +194 -0
- package/src/server/lib/usage/provider-key.ts +81 -3
- package/src/server/lib/usage/providers.server.test.ts +47 -0
- package/src/server/lib/usage/providers.server.ts +208 -0
- package/src/server/lib/usage/vendor-balance.server.test.ts +104 -0
- package/src/server/lib/usage/vendor-balance.server.ts +284 -0
- package/src/server/plugins/shell.server.ts +56 -0
- package/src/server/plugins/ssr.ts +24 -36
- package/src/server/plugins/static.test.ts +26 -29
- package/src/server/plugins/static.ts +40 -19
- package/src/server/routes/fs/action-create.test.ts +90 -0
- package/src/server/routes/fs/action.ts +23 -0
- package/src/server/routes/omp/index.ts +2 -0
- package/src/server/routes/omp/session-todos.ts +80 -0
- package/src/server/routes/settings/index.ts +2 -0
- package/src/server/routes/settings/provider-model.ts +107 -0
- package/src/server/routes/settings/provider-models.ts +65 -202
- package/src/server/routes/settings/providers.ts +22 -7
- package/src/server/routes/settings/usage.ts +125 -45
- package/src/server/routes/telemetry/tokens.ts +2 -1
- package/src/shared/lib/chat/{todo-parser.ts → todo/parser.ts} +17 -0
- package/src/shared/lib/chat/todo/progress.ts +97 -0
- package/src/shared/lib/chat/todo/snapshot.test.ts +156 -0
- package/src/shared/lib/chat/todo/snapshot.ts +215 -0
- package/src/shared/lib/code/editor/commands.test.ts +155 -0
- package/src/shared/lib/code/editor/commands.ts +260 -0
- package/src/shared/lib/code/editor/edits.test.ts +100 -0
- package/src/shared/lib/code/editor/edits.ts +122 -0
- package/src/shared/lib/code/editor/find-status.test.ts +50 -0
- package/src/shared/lib/code/editor/find-status.ts +36 -0
- package/src/shared/lib/code/editor/find.test.ts +203 -0
- package/src/shared/lib/code/editor/find.ts +224 -0
- package/src/{client/components/common/code-editor → shared/lib/code/editor}/history.test.ts +1 -1
- package/src/shared/lib/code/editor/keymap.test.ts +109 -0
- package/src/shared/lib/code/editor/keymap.ts +151 -0
- package/src/shared/lib/code/editor/layers.test.ts +77 -0
- package/src/shared/lib/code/editor/layers.ts +67 -0
- package/src/shared/lib/code/editor/line-edits.test.ts +131 -0
- package/src/shared/lib/code/editor/line-edits.ts +159 -0
- package/src/shared/lib/code/editor/lines.test.ts +81 -0
- package/src/shared/lib/code/editor/lines.ts +83 -0
- package/src/shared/lib/code/editor/marks.test.ts +86 -0
- package/src/shared/lib/code/editor/marks.ts +106 -0
- package/src/shared/lib/code/editor/occurrences.test.ts +117 -0
- package/src/shared/lib/code/editor/occurrences.ts +111 -0
- package/src/shared/lib/code/editor/palette.test.ts +91 -0
- package/src/shared/lib/code/editor/palette.ts +92 -0
- package/src/shared/lib/code/editor/reset-css.test.ts +35 -0
- package/src/shared/lib/code/editor/reset-css.ts +32 -0
- package/src/shared/lib/code/editor/text-commands.test.ts +178 -0
- package/src/shared/lib/code/editor/text-commands.ts +154 -0
- package/src/shared/lib/code/syntax-highlight.test.ts +75 -0
- package/src/shared/lib/code/syntax-highlight.ts +29 -17
- package/src/shared/lib/code/windowed-highlight.ts +52 -1
- package/src/shared/lib/fs/diff-parser.test.ts +116 -0
- package/src/shared/lib/fs/diff-parser.ts +86 -33
- package/src/shared/lib/models/{provider-connection.ts → provider/connection.ts} +37 -0
- package/src/shared/lib/models/provider/dialect.ts +179 -0
- package/src/shared/lib/models/provider/glyph.test.ts +116 -0
- package/src/shared/lib/models/provider/glyph.ts +200 -0
- package/src/shared/lib/models/{provider-models.ts → provider/models.ts} +91 -6
- package/src/shared/lib/models/provider/presets.test.ts +172 -0
- package/src/shared/lib/models/provider/presets.ts +83 -0
- package/src/shared/lib/ui/key-binding.test.ts +115 -0
- package/src/shared/lib/ui/key-binding.ts +143 -0
- package/src/shared/lib/util/platform.test.ts +87 -0
- package/src/shared/lib/util/platform.ts +30 -0
- package/src/shared/lib/workspace/file-tab-id.ts +29 -0
- package/src/shared/lib/workspace/keymap.test.ts +82 -0
- package/src/shared/lib/workspace/keymap.ts +30 -0
- package/src/shared/lib/workspace/refresh-cadence.ts +9 -0
- package/src/shared/lib/workspace/right-panels.ts +8 -0
- package/src/shared/lib/workspace/session-state/store.ts +0 -1
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/settings/provider.ts +81 -0
- package/src/shared/types/settings/usage.ts +88 -14
- package/src/shared/types/todo.ts +108 -0
- package/tsconfig.json +3 -1
- package/dist/client/static/css/index.5f630421b7.css +0 -2
- package/dist/client/static/font/KaTeX_AMS-Regular.a79f1c3119.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Bold.ec17d13264.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Regular.55fac25845.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Bold.d42a5579b0.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Regular.d3c882a649.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-Bold.c3fb5ac22f.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-BoldItalic.6f2bb1dff2.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-Italic.8916142bec.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-Regular.0462f03bdf.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Math-BoldItalic.572d331f69.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Math-Italic.f28c23acad.woff2 +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Bold.8c5b5494b6.woff2 +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Italic.3b1e59b3ba.woff2 +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Regular.ba21ed5f84.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Script-Regular.03e9641d6f.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Size1-Regular.eae34984b3.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Size2-Regular.5916a24fa3.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Size4-Regular.10d95fd3a2.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Typewriter-Regular.a8709e3622.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-400-normal.2655b10c55.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-500-normal.92a991f204.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-600-normal.43788eb7f1.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-700-normal.2667215d33.woff2 +0 -0
- package/dist/client/static/js/77.0d81c74cf0.js +0 -139
- package/dist/client/static/js/77.0d81c74cf0.js.LICENSE.txt +0 -8
- package/dist/client/static/js/async/0.8e59f2eb5b.js +0 -1
- package/dist/client/static/js/async/0k.5852e6b4fa.js +0 -1
- package/dist/client/static/js/async/0w.9ed36d5860.js +0 -1
- package/dist/client/static/js/async/1.f387c12b63.js +0 -1
- package/dist/client/static/js/async/12.d091f91c99.js +0 -1
- package/dist/client/static/js/async/13.8f810a8a6c.js +0 -1
- package/dist/client/static/js/async/19.6fe909d55a.js +0 -160
- package/dist/client/static/js/async/1b.23f1141a38.js +0 -1
- package/dist/client/static/js/async/1k.973e8e21fc.js +0 -2
- package/dist/client/static/js/async/1k.973e8e21fc.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/1p.0d5cddda96.js +0 -1
- package/dist/client/static/js/async/2.b589f1733b.js +0 -1
- package/dist/client/static/js/async/2v.96165101de.js +0 -1
- package/dist/client/static/js/async/3.0cacc08423.js +0 -1
- package/dist/client/static/js/async/37.b28b2f61cd.js +0 -1
- package/dist/client/static/js/async/3d.5085fb913f.js +0 -1
- package/dist/client/static/js/async/3j.357c80ff38.js +0 -2
- package/dist/client/static/js/async/3j.357c80ff38.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/3p.bcf32a0983.js +0 -1
- package/dist/client/static/js/async/3r.3a69e98ee0.js +0 -64
- package/dist/client/static/js/async/3u.1228460e2a.js +0 -1
- package/dist/client/static/js/async/3y.3530b824ad.js +0 -198
- package/dist/client/static/js/async/3y.3530b824ad.js.LICENSE.txt +0 -5
- package/dist/client/static/js/async/4.9442eeb472.js +0 -1
- package/dist/client/static/js/async/4e.678285ca55.js +0 -1
- package/dist/client/static/js/async/4k.33a0c7eeb4.js +0 -1
- package/dist/client/static/js/async/4m.928a1dde85.js +0 -31
- package/dist/client/static/js/async/4m.928a1dde85.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/4q.aad20dab25.js +0 -1
- package/dist/client/static/js/async/5.ebaf8b89cc.js +0 -36
- package/dist/client/static/js/async/51.95bcae98af.js +0 -2
- package/dist/client/static/js/async/51.95bcae98af.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/5b.d88304bc13.js +0 -1
- package/dist/client/static/js/async/5j.d6407c4248.js +0 -287
- package/dist/client/static/js/async/5o.88316d4460.js +0 -62
- package/dist/client/static/js/async/6.f034c9b528.js +0 -1
- package/dist/client/static/js/async/66.5dfa29f957.js +0 -93
- package/dist/client/static/js/async/6o.5b1a06a382.js +0 -1
- package/dist/client/static/js/async/7.e4a9288dd0.js +0 -1
- package/dist/client/static/js/async/73.308dff4da8.js +0 -1
- package/dist/client/static/js/async/7e.a1cc9bfd88.js +0 -1
- package/dist/client/static/js/async/7g.a7814350c3.js +0 -1
- package/dist/client/static/js/async/7l.fadbbc0583.js +0 -1
- package/dist/client/static/js/async/8.5067439d8c.js +0 -1
- package/dist/client/static/js/async/8j.ea110663fb.js +0 -1
- package/dist/client/static/js/async/a.bce4542392.js +0 -1
- package/dist/client/static/js/async/aa.65d3852576.js +0 -1
- package/dist/client/static/js/async/ar.9303e5f2e8.js +0 -2
- package/dist/client/static/js/async/ar.9303e5f2e8.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/az.d656062b0f.js +0 -1
- package/dist/client/static/js/async/b.b4f6f06515.js +0 -1
- package/dist/client/static/js/async/bi.09688ba75c.js +0 -1
- package/dist/client/static/js/async/bq.586bc738d1.js +0 -1
- package/dist/client/static/js/async/c.797d933882.js +0 -1
- package/dist/client/static/js/async/c0.0af1e640d3.js +0 -24
- package/dist/client/static/js/async/ca.8a52cfd447.js +0 -1
- package/dist/client/static/js/async/cf.d34fff5d67.js +0 -1
- package/dist/client/static/js/async/ct.b74928f7a3.js +0 -1
- package/dist/client/static/js/async/cx.3cb0242a3a.js +0 -226
- package/dist/client/static/js/async/d.a5fc8fe1b3.js +0 -1
- package/dist/client/static/js/async/da.f62c70f6c8.js +0 -1
- package/dist/client/static/js/async/dp.1725d6432c.js +0 -1
- package/dist/client/static/js/async/e.93214d622b.js +0 -79
- package/dist/client/static/js/async/f.ab50467719.js +0 -19
- package/dist/client/static/js/async/f.ab50467719.js.LICENSE.txt +0 -12
- package/dist/client/static/js/async/f7.46971e9d6d.js +0 -2
- package/dist/client/static/js/async/f7.46971e9d6d.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/f8.bbf0dbae70.js +0 -1
- package/dist/client/static/js/async/g.cb81980498.js +0 -2
- package/dist/client/static/js/async/g.cb81980498.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/h.4e790471e4.js +0 -27
- package/dist/client/static/js/async/h1.393f066d09.js +0 -1
- package/dist/client/static/js/async/h3.216e9798ab.js +0 -148
- package/dist/client/static/js/async/hd.0da931b622.js +0 -1
- package/dist/client/static/js/async/hk.ba7fb04d41.js +0 -1
- package/dist/client/static/js/async/hn.b645041e00.js +0 -1
- package/dist/client/static/js/async/ho.475a5e1ae4.js +0 -1
- package/dist/client/static/js/async/hp.e9fe2e395a.js +0 -2
- package/dist/client/static/js/async/hp.e9fe2e395a.js.LICENSE.txt +0 -17
- package/dist/client/static/js/async/hs.e7c59d8090.js +0 -41
- package/dist/client/static/js/async/i.7c148e8440.js +0 -1
- package/dist/client/static/js/async/id.573df20f29.js +0 -1
- package/dist/client/static/js/async/j.ab925882d8.js +0 -1
- package/dist/client/static/js/async/jd.63886a1f09.js +0 -1
- package/dist/client/static/js/async/jg.d86ebd4013.js +0 -86
- package/dist/client/static/js/async/jl.874d352278.js +0 -1
- package/dist/client/static/js/async/jll.11313b455a.js +0 -1
- package/dist/client/static/js/async/js.67182759ef.js +0 -1
- package/dist/client/static/js/async/jsc.23b70d6f40.js +0 -1
- package/dist/client/static/js/async/jse.334588ff5d.js +0 -1
- package/dist/client/static/js/async/jz.72eb929106.js +0 -77
- package/dist/client/static/js/async/k.3210286401.js +0 -217
- package/dist/client/static/js/async/ka.c1990ecaa3.js +0 -115
- package/dist/client/static/js/async/katex-vendor.de6a87c308.js +0 -1
- package/dist/client/static/js/async/ku.29c8a59d0c.js +0 -1
- package/dist/client/static/js/async/ky.8dab1dade0.js +0 -1
- package/dist/client/static/js/async/l.12dd34e1be.js +0 -1
- package/dist/client/static/js/async/l9.949710adaf.js +0 -83
- package/dist/client/static/js/async/l9.949710adaf.js.LICENSE.txt +0 -13
- package/dist/client/static/js/async/ls.9d9a01026c.js +0 -104
- package/dist/client/static/js/async/m.eb2e6752e2.js +0 -28
- package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/m3.d2b05f9604.js +0 -2
- package/dist/client/static/js/async/m3.d2b05f9604.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/ma.2c261b04dd.js +0 -2
- package/dist/client/static/js/async/ma.2c261b04dd.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/me.8ace414f80.js +0 -23
- package/dist/client/static/js/async/ms.98960af4eb.js +0 -67
- package/dist/client/static/js/async/ms.98960af4eb.js.LICENSE.txt +0 -12
- package/dist/client/static/js/async/mt.0352e976d8.js +0 -1
- package/dist/client/static/js/async/n.a9f1c51701.js +0 -1
- package/dist/client/static/js/async/na.a36cd532d6.js +0 -1
- package/dist/client/static/js/async/nk.fc7e58325e.js +0 -1
- package/dist/client/static/js/async/o.36353dfb88.js +0 -1
- package/dist/client/static/js/async/oc.b73042e8bd.js +0 -8
- package/dist/client/static/js/async/og.8eb577893a.js +0 -193
- package/dist/client/static/js/async/p.48ecfcd14e.js +0 -1
- package/dist/client/static/js/async/px.fc6877924b.js +0 -1
- package/dist/client/static/js/async/q.22588845a9.js +0 -21
- package/dist/client/static/js/async/q.22588845a9.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/q5.97dc1481b6.js +0 -286
- package/dist/client/static/js/async/qs.2a9ddb3c72.js +0 -1
- package/dist/client/static/js/async/r.51010ad87c.js +0 -1
- package/dist/client/static/js/async/rd.cb6bf495bb.js +0 -2
- package/dist/client/static/js/async/rd.cb6bf495bb.js.LICENSE.txt +0 -4
- package/dist/client/static/js/async/s.0f395cdda5.js +0 -94
- package/dist/client/static/js/async/t.4dca0e2ba8.js +0 -1
- package/dist/client/static/js/async/t9.d44f57d518.js +0 -1
- package/dist/client/static/js/async/ta.973534bbec.js +0 -1
- package/dist/client/static/js/async/tr.e878658140.js +0 -309
- package/dist/client/static/js/async/u.0871ac5b22.js +0 -1
- package/dist/client/static/js/async/ud.3065bfee31.js +0 -2
- package/dist/client/static/js/async/ud.3065bfee31.js.LICENSE.txt +0 -4
- package/dist/client/static/js/async/up.ea990da0ff.js +0 -32
- package/dist/client/static/js/async/uw.8890abbabc.js +0 -1
- package/dist/client/static/js/async/v.7d28d3c241.js +0 -34
- package/dist/client/static/js/async/vp.9b13bf8610.js +0 -1
- package/dist/client/static/js/async/vt.42416b707d.js +0 -1
- package/dist/client/static/js/async/w.08ac210f60.js +0 -1
- package/dist/client/static/js/async/wt.ad92393a90.js +0 -1
- package/dist/client/static/js/async/x.563f72ca6b.js +0 -1
- package/dist/client/static/js/async/x0.475516418f.js +0 -1
- package/dist/client/static/js/async/x1.da8687159b.js +0 -90
- package/dist/client/static/js/async/xe.da8dcc2015.js +0 -2
- package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/xl.a3c562a58c.js +0 -1
- package/dist/client/static/js/async/xn.6510c0abe2.js +0 -2
- package/dist/client/static/js/async/xn.6510c0abe2.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/xn1.83a7784fe1.js +0 -10
- package/dist/client/static/js/async/xn1.83a7784fe1.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/xnl.feb0021e50.js +0 -2
- package/dist/client/static/js/async/xnl.feb0021e50.js.LICENSE.txt +0 -12
- package/dist/client/static/js/async/y.1a9ed7a5d8.js +0 -1
- package/dist/client/static/js/async/y6.43c57defe4.js +0 -1
- package/dist/client/static/js/async/yb.8c1342401d.js +0 -1
- package/dist/client/static/js/async/ybq.4491f2f2dc.js +0 -1
- package/dist/client/static/js/async/yg.35bebd6d0c.js +0 -149
- package/dist/client/static/js/async/yq.917ddd7ba2.js +0 -1
- package/dist/client/static/js/async/ys.00687cf82d.js +0 -38
- package/dist/client/static/js/async/yv.d99d829136.js +0 -1
- package/dist/client/static/js/async/z.56f61a4374.js +0 -1
- package/dist/client/static/js/async/zi.5fdb07b86a.js +0 -24
- package/dist/client/static/js/async/zj.654f37e8ff.js +0 -1
- package/dist/client/static/js/async/zn.966d6f1cc6.js +0 -1
- package/dist/client/static/js/async/zo.26213b40c0.js +0 -1
- package/dist/client/static/js/async/zx.e9f4105e00.js +0 -1
- package/dist/client/static/js/index.e6a257402a.js +0 -38
- package/dist/client/static/js/index.e6a257402a.js.LICENSE.txt +0 -4
- package/dist/client/static/js/markdown-vendor.71e43278b3.js +0 -144
- package/dist/client/static/js/shiki-vendor.cb8452ddcd.js +0 -1
- package/src/client/components/settings/categories/provider-settings/Icons.tsx +0 -35
- package/src/client/hooks/settings/providers.ts +0 -326
- package/src/server/plugins/dev-assets.ts +0 -49
- package/src/shared/lib/code/editor-utils.ts +0 -16
- package/src/shared/lib/models/provider-presets.ts +0 -29
- /package/dist/client/{apple-touch-icon.png → static/apple-touch-icon-dskx5sr3.png} +0 -0
- /package/dist/client/{icon-192x192.png → static/icon-192x192-6gn5cr2f.png} +0 -0
- /package/dist/client/{icon.svg → static/icon-d07z9dtz.svg} +0 -0
- /package/dist/client/{manifest.webmanifest → static/manifest-bnyy75kt.webmanifest} +0 -0
- /package/{dist/client → public}/icon-512x512.png +0 -0
- /package/{dist/client → public}/icon-maskable-512x512.png +0 -0
- /package/{dist/client → public}/sw.js +0 -0
- /package/src/{client/components/common/code-editor → shared/lib/code/editor}/history.ts +0 -0
- /package/src/shared/lib/models/{provider-cleanup.ts → provider/cleanup.ts} +0 -0
- /package/src/shared/lib/models/{provider-label.ts → provider/label.ts} +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The chord matcher every shortcut in the app shares.
|
|
8
|
+
*
|
|
9
|
+
* Two failures are silent and both are covered here: matching on `event.key`
|
|
10
|
+
* (which never fires on macOS, where Option produces a different character), and
|
|
11
|
+
* a label that disagrees with the stroke it describes. The matcher is also
|
|
12
|
+
* platform-guarded, so a chord from the other platform must not fire.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, expect, test } from 'bun:test';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
bindingFor,
|
|
19
|
+
describeStroke,
|
|
20
|
+
matchesStroke,
|
|
21
|
+
printableKey,
|
|
22
|
+
resolveBinding,
|
|
23
|
+
strokeOf,
|
|
24
|
+
type KeyBinding,
|
|
25
|
+
type KeyboardEventLike,
|
|
26
|
+
} from '@/shared/lib/ui/key-binding';
|
|
27
|
+
|
|
28
|
+
type Command = 'open' | 'save' | 'redo';
|
|
29
|
+
|
|
30
|
+
const BINDINGS: readonly KeyBinding<Command>[] = [
|
|
31
|
+
{ command: 'open', label: 'Open', mac: { code: 'KeyO', meta: true }, other: { code: 'KeyO', ctrl: true } },
|
|
32
|
+
{ command: 'save', label: 'Save', mac: { code: 'KeyS', meta: true, shift: true }, other: { code: 'KeyS', ctrl: true, shift: true } },
|
|
33
|
+
{
|
|
34
|
+
command: 'redo',
|
|
35
|
+
label: 'Redo',
|
|
36
|
+
mac: { code: 'KeyZ', meta: true, shift: true },
|
|
37
|
+
other: { code: 'KeyZ', ctrl: true, shift: true },
|
|
38
|
+
windows: { code: 'KeyY', ctrl: true },
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
function key(code: string, keyName: string, modifiers: Partial<KeyboardEventLike> = {}): KeyboardEventLike {
|
|
43
|
+
return { code, key: keyName, metaKey: false, ctrlKey: false, altKey: false, shiftKey: false, ...modifiers };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe('matchesStroke', () => {
|
|
47
|
+
test('requires the exact modifier set', () => {
|
|
48
|
+
const stroke = { code: 'KeyF', meta: true };
|
|
49
|
+
expect(matchesStroke(stroke, key('KeyF', 'f', { metaKey: true }))).toBe(true);
|
|
50
|
+
expect(matchesStroke(stroke, key('KeyF', 'f', { metaKey: true, shiftKey: true }))).toBe(false);
|
|
51
|
+
expect(matchesStroke(stroke, key('KeyF', 'f', { ctrlKey: true }))).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('reads the physical key, not the character it produces', () => {
|
|
55
|
+
// macOS reports ⌥⌘C as `Ç`; a `key`-based test would never fire there.
|
|
56
|
+
expect(matchesStroke({ code: 'KeyC', meta: true, alt: true }, key('KeyC', 'Ç', { metaKey: true, altKey: true }))).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('strokeOf', () => {
|
|
61
|
+
test('picks the platform’s own chord', () => {
|
|
62
|
+
expect(strokeOf(BINDINGS[0], true)).toEqual({ code: 'KeyO', meta: true });
|
|
63
|
+
expect(strokeOf(BINDINGS[0], false)).toEqual({ code: 'KeyO', ctrl: true });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('uses the Windows override only where it exists', () => {
|
|
67
|
+
// Windows redoes with Ctrl+Y while Linux keeps Ctrl+Shift+Z; one `other`
|
|
68
|
+
// stroke cannot express both.
|
|
69
|
+
expect(strokeOf(BINDINGS[2], false)).toEqual({ code: 'KeyZ', ctrl: true, shift: true });
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('resolveBinding', () => {
|
|
74
|
+
test('returns the command a chord names', () => {
|
|
75
|
+
expect(resolveBinding(BINDINGS, key('KeyO', 'o', { metaKey: true }), true)).toBe('open');
|
|
76
|
+
expect(resolveBinding(BINDINGS, key('KeyO', 'o', { ctrlKey: true }), false)).toBe('open');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('a chord from the other platform never fires', () => {
|
|
80
|
+
expect(resolveBinding(BINDINGS, key('KeyO', 'o', { ctrlKey: true }), true)).toBeNull();
|
|
81
|
+
expect(resolveBinding(BINDINGS, key('KeyO', 'o', { metaKey: true }), false)).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('an unbound chord resolves to nothing', () => {
|
|
85
|
+
expect(resolveBinding(BINDINGS, key('KeyQ', 'q', { metaKey: true }), true)).toBeNull();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('bindingFor', () => {
|
|
90
|
+
test('finds the binding a tooltip needs to label', () => {
|
|
91
|
+
expect(bindingFor(BINDINGS, 'save')?.label).toBe('Save');
|
|
92
|
+
expect(bindingFor(BINDINGS, 'open')?.mac).toEqual({ code: 'KeyO', meta: true });
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('printableKey and describeStroke', () => {
|
|
97
|
+
test('prints the physical key the way a reader expects', () => {
|
|
98
|
+
expect(printableKey('KeyC')).toBe('C');
|
|
99
|
+
expect(printableKey('Digit1')).toBe('1');
|
|
100
|
+
expect(printableKey('Slash')).toBe('/');
|
|
101
|
+
expect(printableKey('BracketLeft')).toBe('[');
|
|
102
|
+
expect(printableKey('ArrowUp')).toBe('ArrowUp');
|
|
103
|
+
expect(printableKey('F3')).toBe('F3');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('mac chords render without separators, others with plus signs', () => {
|
|
107
|
+
expect(describeStroke({ code: 'KeyC', meta: true, alt: true }, true)).toBe('⌥⌘C');
|
|
108
|
+
expect(describeStroke({ code: 'KeyC', alt: true }, false)).toBe('Alt+C');
|
|
109
|
+
expect(describeStroke({ code: 'Slash', ctrl: true }, false)).toBe('Ctrl+/');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('modifier order is stable, so two labels for one chord cannot differ', () => {
|
|
113
|
+
expect(describeStroke({ code: 'ArrowDown', meta: true, alt: true, shift: true }, true)).toBe('⌥⇧⌘ArrowDown');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* One chord matcher for every shortcut in the app.
|
|
8
|
+
*
|
|
9
|
+
* The editor's find bar, the editor's own commands and the panel shortcuts all
|
|
10
|
+
* have to answer the same question — "is this event this chord?" — and they
|
|
11
|
+
* must answer it identically or the same physical key means different things
|
|
12
|
+
* depending on which surface has focus. They also have to *describe* it
|
|
13
|
+
* identically: a tooltip that reads `⌥⌘C` while the binding listens for
|
|
14
|
+
* something else is worse than an unlabeled button, and the command palette
|
|
15
|
+
* lists the very same strings.
|
|
16
|
+
*
|
|
17
|
+
* Matching is on `event.code`, not `event.key`: with Option held, macOS reports
|
|
18
|
+
* the character the combination produces (`⌥⌘C` arrives as `Ç`), so a
|
|
19
|
+
* `key`-based test silently never fires on the platform most of these
|
|
20
|
+
* shortcuts are copied from.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { isMacPlatform, isWindowsPlatform } from '@/shared/lib/util/platform';
|
|
24
|
+
|
|
25
|
+
export interface KeyStroke {
|
|
26
|
+
/** `KeyboardEvent.code`, e.g. `KeyC`, `ArrowUp`, `Slash`, `Digit1`. */
|
|
27
|
+
code: string;
|
|
28
|
+
meta?: boolean;
|
|
29
|
+
ctrl?: boolean;
|
|
30
|
+
alt?: boolean;
|
|
31
|
+
shift?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The subset of a keyboard event the matcher reads, so a test can fake one. */
|
|
35
|
+
export interface KeyboardEventLike {
|
|
36
|
+
code: string;
|
|
37
|
+
key: string;
|
|
38
|
+
metaKey: boolean;
|
|
39
|
+
ctrlKey: boolean;
|
|
40
|
+
altKey: boolean;
|
|
41
|
+
shiftKey: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface KeyBinding<Command extends string> {
|
|
45
|
+
command: Command;
|
|
46
|
+
/** What the palette and the tooltips call this command. */
|
|
47
|
+
label: string;
|
|
48
|
+
/** Chord on macOS. */
|
|
49
|
+
mac: KeyStroke;
|
|
50
|
+
/** Chord on Windows and Linux. */
|
|
51
|
+
other: KeyStroke;
|
|
52
|
+
/**
|
|
53
|
+
* Windows-only override. VS Code redoes with `Ctrl+Y` on Windows while Linux
|
|
54
|
+
* keeps `Ctrl+Shift+Z`, which one `other` stroke cannot express.
|
|
55
|
+
*/
|
|
56
|
+
windows?: KeyStroke;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function matchesStroke(stroke: KeyStroke, event: KeyboardEventLike): boolean {
|
|
60
|
+
return (
|
|
61
|
+
stroke.code === event.code &&
|
|
62
|
+
Boolean(stroke.meta) === event.metaKey &&
|
|
63
|
+
Boolean(stroke.ctrl) === event.ctrlKey &&
|
|
64
|
+
Boolean(stroke.alt) === event.altKey &&
|
|
65
|
+
Boolean(stroke.shift) === event.shiftKey
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The chord a binding resolves to on the current platform. */
|
|
70
|
+
export function strokeOf<C extends string>(binding: KeyBinding<C>, isMac: boolean): KeyStroke {
|
|
71
|
+
if (isMac) return binding.mac;
|
|
72
|
+
if (binding.windows && isWindowsPlatform()) return binding.windows;
|
|
73
|
+
return binding.other;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Codes whose printed name is not their `KeyboardEvent.code` suffix. Kept as a
|
|
78
|
+
* table because the alternative — a chain of `if`s — is what makes a label
|
|
79
|
+
* drift from the key it names.
|
|
80
|
+
*/
|
|
81
|
+
const KEY_LABEL_BY_CODE: Record<string, string> = {
|
|
82
|
+
Slash: '/',
|
|
83
|
+
Backslash: '\\',
|
|
84
|
+
Comma: ',',
|
|
85
|
+
Period: '.',
|
|
86
|
+
Semicolon: ';',
|
|
87
|
+
Quote: "'",
|
|
88
|
+
Backquote: '`',
|
|
89
|
+
Minus: '-',
|
|
90
|
+
Equal: '=',
|
|
91
|
+
BracketLeft: '[',
|
|
92
|
+
BracketRight: ']',
|
|
93
|
+
Space: 'Space',
|
|
94
|
+
Enter: 'Enter',
|
|
95
|
+
Escape: 'Esc',
|
|
96
|
+
Backspace: 'Backspace',
|
|
97
|
+
Delete: 'Del',
|
|
98
|
+
Tab: 'Tab',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/** `⌥⌘C` on macOS, `Alt+C` elsewhere. */
|
|
102
|
+
export function describeStroke(stroke: KeyStroke, isMac: boolean): string {
|
|
103
|
+
const parts: string[] = [];
|
|
104
|
+
if (stroke.ctrl) parts.push(isMac ? '⌃' : 'Ctrl');
|
|
105
|
+
if (stroke.alt) parts.push(isMac ? '⌥' : 'Alt');
|
|
106
|
+
if (stroke.shift) parts.push(isMac ? '⇧' : 'Shift');
|
|
107
|
+
if (stroke.meta) parts.push(isMac ? '⌘' : 'Win');
|
|
108
|
+
parts.push(printableKey(stroke.code));
|
|
109
|
+
return isMac ? parts.join('') : parts.join('+');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The printed name of a physical key: `KeyC` → `C`, `Slash` → `/`, `ArrowUp` → `ArrowUp`. */
|
|
113
|
+
export function printableKey(code: string): string {
|
|
114
|
+
if (KEY_LABEL_BY_CODE[code]) return KEY_LABEL_BY_CODE[code];
|
|
115
|
+
if (code.startsWith('Key')) return code.slice(3);
|
|
116
|
+
if (code.startsWith('Digit')) return code.slice(5);
|
|
117
|
+
return code;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The label a binding's shortcut gets in a tooltip or the palette. */
|
|
121
|
+
export function describeBinding<C extends string>(binding: KeyBinding<C>, isMac: boolean = isMacPlatform()): string {
|
|
122
|
+
return describeStroke(strokeOf(binding, isMac), isMac);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** The command `event` invokes, or null when no binding claims it. */
|
|
126
|
+
export function resolveBinding<C extends string>(
|
|
127
|
+
bindings: readonly KeyBinding<C>[],
|
|
128
|
+
event: KeyboardEventLike,
|
|
129
|
+
isMac: boolean = isMacPlatform(),
|
|
130
|
+
): C | null {
|
|
131
|
+
for (const binding of bindings) {
|
|
132
|
+
if (matchesStroke(strokeOf(binding, isMac), event)) return binding.command;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The binding for a command, for a call site that knows what it wants to label. */
|
|
138
|
+
export function bindingFor<C extends string>(
|
|
139
|
+
bindings: readonly KeyBinding<C>[],
|
|
140
|
+
command: C,
|
|
141
|
+
): KeyBinding<C> | undefined {
|
|
142
|
+
return bindings.find((binding) => binding.command === command);
|
|
143
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The platform sniffing the shortcut labels depend on.
|
|
8
|
+
*
|
|
9
|
+
* A wrong answer here is not cosmetic: the label and the binding are derived
|
|
10
|
+
* from the same predicate, so a mis-detected platform names a chord that does
|
|
11
|
+
* not fire. The server case matters too — these modules are imported by both
|
|
12
|
+
* bundles, and touching `navigator` unguarded would throw during SSR.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
|
16
|
+
|
|
17
|
+
import { isMacPlatform, isWindowsPlatform } from '@/shared/lib/util/platform';
|
|
18
|
+
|
|
19
|
+
const displaced = (globalThis as Record<string, unknown>).navigator;
|
|
20
|
+
|
|
21
|
+
function withPlatform(platform: string) {
|
|
22
|
+
(globalThis as Record<string, unknown>).navigator = { platform };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
afterAll(() => {
|
|
26
|
+
if (displaced === undefined) delete (globalThis as Record<string, unknown>).navigator;
|
|
27
|
+
else (globalThis as Record<string, unknown>).navigator = displaced;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('isMacPlatform', () => {
|
|
31
|
+
test('recognises every Apple platform the app runs on', () => {
|
|
32
|
+
for (const platform of ['MacIntel', 'Macintosh', 'iPhone', 'iPad', 'iPod']) {
|
|
33
|
+
withPlatform(platform);
|
|
34
|
+
expect(isMacPlatform()).toBe(true);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('is false elsewhere', () => {
|
|
39
|
+
for (const platform of ['Win32', 'Win64', 'Linux x86_64', 'Linux armv8l']) {
|
|
40
|
+
withPlatform(platform);
|
|
41
|
+
expect(isMacPlatform()).toBe(false);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('reports false rather than throwing without a navigator', () => {
|
|
46
|
+
// Shared modules are imported by the server bundle too.
|
|
47
|
+
delete (globalThis as Record<string, unknown>).navigator;
|
|
48
|
+
expect(isMacPlatform()).toBe(false);
|
|
49
|
+
expect(isWindowsPlatform()).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('isWindowsPlatform', () => {
|
|
54
|
+
test('recognises the Windows spellings', () => {
|
|
55
|
+
for (const platform of ['Win32', 'Win64', 'Windows']) {
|
|
56
|
+
withPlatform(platform);
|
|
57
|
+
expect(isWindowsPlatform()).toBe(true);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('is false on mac and Linux', () => {
|
|
62
|
+
for (const platform of ['MacIntel', 'Linux x86_64']) {
|
|
63
|
+
withPlatform(platform);
|
|
64
|
+
expect(isWindowsPlatform()).toBe(false);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('does not match a platform that merely contains “win”', () => {
|
|
69
|
+
// `Darwin` contains `win`; a substring test without the case rule would
|
|
70
|
+
// report every macOS machine as Windows and pick the Ctrl+Y redo chord.
|
|
71
|
+
withPlatform('Darwin');
|
|
72
|
+
expect(isWindowsPlatform()).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('mac and Windows are mutually exclusive', () => {
|
|
77
|
+
beforeAll(() => {
|
|
78
|
+
withPlatform('MacIntel');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('no platform reports both', () => {
|
|
82
|
+
for (const platform of ['MacIntel', 'Win32', 'Linux x86_64', 'iPhone']) {
|
|
83
|
+
withPlatform(platform);
|
|
84
|
+
expect(isMacPlatform() && isWindowsPlatform()).toBe(false);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Platform sniffing for the surfaces that label a keyboard shortcut.
|
|
8
|
+
*
|
|
9
|
+
* The labels ("⌘F" vs "Ctrl+F") and the modifiers they match have to come from
|
|
10
|
+
* one place: a shortcut that is documented as ⌘F but only bound to Ctrl+F is
|
|
11
|
+
* worse than an unlabeled one. `navigator.platform` is deprecated in favour of
|
|
12
|
+
* `userAgentData.platform`, but the replacement is Chromium-only — and these
|
|
13
|
+
* checks only choose a modifier label, so the deprecated source is the one that
|
|
14
|
+
* answers on every engine. Guarded for the server, where `navigator` does not
|
|
15
|
+
* exist (shared modules are imported by both bundles).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export function isMacPlatform(): boolean {
|
|
19
|
+
return typeof navigator !== 'undefined' && /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Anchored, because the unanchored form is wrong: `/Win/i` matches `Darwin`,
|
|
24
|
+
* which would report every macOS machine as Windows. (Measured: the redo chord
|
|
25
|
+
* then resolves to Ctrl+Y on a Mac — latent only because every call site checks
|
|
26
|
+
* `isMacPlatform` first.)
|
|
27
|
+
*/
|
|
28
|
+
export function isWindowsPlatform(): boolean {
|
|
29
|
+
return typeof navigator !== 'undefined' && /^Win/i.test(navigator.platform);
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The tab id an `omp:open-file` payload resolves to when the caller supplies
|
|
8
|
+
* none. Extracted so the diff→editor conversion can produce the *same* id a
|
|
9
|
+
* later `omp:open-file` for that path would: the two paths must dedupe onto one
|
|
10
|
+
* tab, and a second scheme would leave two tabs for one file.
|
|
11
|
+
*/
|
|
12
|
+
export function fileTabId(path: string): number {
|
|
13
|
+
let hash = 0;
|
|
14
|
+
for (let i = 0; i < path.length; i++) {
|
|
15
|
+
hash = (hash << 5) - hash + path.charCodeAt(i);
|
|
16
|
+
hash |= 0;
|
|
17
|
+
}
|
|
18
|
+
return Math.abs(hash) || Date.now();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The diff tab id for a path: staged and unstaged diffs are separate tabs. */
|
|
22
|
+
export function diffTabId(path: string, staged: boolean): string {
|
|
23
|
+
return `diff-${staged ? 'staged' : 'working'}-${path}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Display name for a diff tab: `main.ts` → `main.ts (Diff)`. */
|
|
27
|
+
export function diffTabName(path: string): string {
|
|
28
|
+
return `${path.split('/').pop() || path} (Diff)`;
|
|
29
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The workspace shortcuts.
|
|
8
|
+
*
|
|
9
|
+
* The table is what the panel tooltips read, so what matters is that every
|
|
10
|
+
* binding is unique on a platform (a duplicate silently shadows one of them) and
|
|
11
|
+
* that the chords are ones a browser or a text field does not already own —
|
|
12
|
+
* binding ⌘W or ⌘T here would break the tab, and ⌘A would break every selection
|
|
13
|
+
* in the app.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, expect, test } from 'bun:test';
|
|
17
|
+
|
|
18
|
+
import { WORKSPACE_KEY_BINDINGS } from '@/shared/lib/workspace/keymap';
|
|
19
|
+
import { resolveBinding, strokeOf, type KeyboardEventLike } from '@/shared/lib/ui/key-binding';
|
|
20
|
+
|
|
21
|
+
function key(code: string, modifiers: Partial<KeyboardEventLike> = {}): KeyboardEventLike {
|
|
22
|
+
return { code, key: code, metaKey: false, ctrlKey: false, altKey: false, shiftKey: false, ...modifiers };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Chords the browser or the OS owns; binding one would break the app. */
|
|
26
|
+
const RESERVED: Record<string, true> = {
|
|
27
|
+
KeyW: true,
|
|
28
|
+
KeyT: true,
|
|
29
|
+
KeyN: true,
|
|
30
|
+
KeyQ: true,
|
|
31
|
+
KeyA: true,
|
|
32
|
+
KeyR: true,
|
|
33
|
+
KeyP: true,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe('WORKSPACE_KEY_BINDINGS', () => {
|
|
37
|
+
test('every binding carries a label and both platforms', () => {
|
|
38
|
+
for (const binding of WORKSPACE_KEY_BINDINGS) {
|
|
39
|
+
expect(binding.label.length).toBeGreaterThan(0);
|
|
40
|
+
expect(binding.mac.code.length).toBeGreaterThan(0);
|
|
41
|
+
expect(binding.other.code.length).toBeGreaterThan(0);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('no binding claims a chord the browser already owns', () => {
|
|
46
|
+
for (const binding of WORKSPACE_KEY_BINDINGS) {
|
|
47
|
+
expect(RESERVED[binding.mac.code]).toBeUndefined();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('no two commands claim the same chord on a platform', () => {
|
|
52
|
+
for (const isMac of [true, false]) {
|
|
53
|
+
const seen: Record<string, string> = {};
|
|
54
|
+
for (const binding of WORKSPACE_KEY_BINDINGS) {
|
|
55
|
+
const stroke = strokeOf(binding, isMac);
|
|
56
|
+
const id = `${stroke.code}|${stroke.meta ? 'm' : ''}${stroke.ctrl ? 'c' : ''}${stroke.alt ? 'a' : ''}${stroke.shift ? 's' : ''}`;
|
|
57
|
+
expect(`${binding.command} vs ${seen[id]}`).toBe(`${binding.command} vs undefined`);
|
|
58
|
+
seen[id] = binding.command;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('the panel toggles resolve from their own chords', () => {
|
|
64
|
+
expect(resolveBinding(WORKSPACE_KEY_BINDINGS, key('KeyB', { metaKey: true }), true)).toBe('toggleSidebar');
|
|
65
|
+
expect(resolveBinding(WORKSPACE_KEY_BINDINGS, key('KeyJ', { metaKey: true }), true)).toBe('toggleEditorPanel');
|
|
66
|
+
expect(resolveBinding(WORKSPACE_KEY_BINDINGS, key('KeyJ', { metaKey: true, altKey: true }), true)).toBe('toggleRightPanel');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('⌘J and ⌥⌘J are different commands, not the same one twice', () => {
|
|
70
|
+
// The alt modifier is the only thing separating the editor and right panels.
|
|
71
|
+
const plain = resolveBinding(WORKSPACE_KEY_BINDINGS, key('KeyJ', { metaKey: true }), true);
|
|
72
|
+
const withAlt = resolveBinding(WORKSPACE_KEY_BINDINGS, key('KeyJ', { metaKey: true, altKey: true }), true);
|
|
73
|
+
|
|
74
|
+
expect(plain).not.toBe(withAlt);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('save is bound here but deliberately not handled by the layout', () => {
|
|
78
|
+
// The editor panel owns ⌘S (it is the only surface with a buffer); this
|
|
79
|
+
// binding exists so a tooltip can name the chord.
|
|
80
|
+
expect(resolveBinding(WORKSPACE_KEY_BINDINGS, key('KeyS', { metaKey: true }), true)).toBe('saveFile');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The workspace's own shortcuts — the ones that belong to a PANEL rather than
|
|
8
|
+
* to the text inside it. Kept in a table beside the editor's keymap for the
|
|
9
|
+
* same reason: the panel toggles carry their chord in a tooltip, and a tooltip
|
|
10
|
+
* that names a key nobody listens for is a lie.
|
|
11
|
+
*
|
|
12
|
+
* These are deliberately few. A global table is where a shortcut goes to
|
|
13
|
+
* collide with the browser's own (⌘W closes a tab, ⌘T opens one, ⌘N opens a
|
|
14
|
+
* window) or with a text field the user is typing in — so only chords that are
|
|
15
|
+
* inert in a text field and unambiguous in this app are bound here.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { KeyBinding } from '@/shared/lib/ui/key-binding';
|
|
19
|
+
|
|
20
|
+
export type WorkspaceCommand = 'toggleEditorPanel' | 'toggleRightPanel' | 'toggleSidebar' | 'openSettings' | 'saveFile';
|
|
21
|
+
|
|
22
|
+
export const WORKSPACE_KEY_BINDINGS: readonly KeyBinding<WorkspaceCommand>[] = [
|
|
23
|
+
// ⌘B / ⌘J are VS Code's own panel toggles.
|
|
24
|
+
{ command: 'toggleSidebar', label: 'Toggle sidebar', mac: { code: 'KeyB', meta: true }, other: { code: 'KeyB', ctrl: true } },
|
|
25
|
+
{ command: 'toggleEditorPanel', label: 'Toggle editor panel', mac: { code: 'KeyJ', meta: true }, other: { code: 'KeyJ', ctrl: true } },
|
|
26
|
+
{ command: 'toggleRightPanel', label: 'Toggle right panel', mac: { code: 'KeyJ', meta: true, alt: true }, other: { code: 'KeyJ', ctrl: true, alt: true } },
|
|
27
|
+
// ⌘, is the platform's own settings chord (the app already binds it).
|
|
28
|
+
{ command: 'openSettings', label: 'Open settings', mac: { code: 'Comma', meta: true }, other: { code: 'Comma', ctrl: true } },
|
|
29
|
+
{ command: 'saveFile', label: 'Save file', mac: { code: 'KeyS', meta: true }, other: { code: 'KeyS', ctrl: true } },
|
|
30
|
+
];
|
|
@@ -93,6 +93,15 @@ export const GIT_STATUS_EVENT_THROTTLE_MS = 1_000;
|
|
|
93
93
|
*/
|
|
94
94
|
export const REPO_DISCOVERY_POLL_MS = 1_500;
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Coalescing window for the todo panel's event-driven re-read. A run emits
|
|
98
|
+
* `omp:session-updated` at every turn boundary (which is what follows each tool
|
|
99
|
+
* call), so this collapses the several signals around one `todo` call into one
|
|
100
|
+
* request. Tighter than the git window on purpose: the server answers an
|
|
101
|
+
* unchanged read from a stat, and the panel is watched while the agent works.
|
|
102
|
+
*/
|
|
103
|
+
export const TODO_REFRESH_EVENT_THROTTLE_MS = 400;
|
|
104
|
+
|
|
96
105
|
/**
|
|
97
106
|
* CDP state/screencast poll. Latency-sensitive — the browser panel streams a
|
|
98
107
|
* live page, so this one stays tight on purpose.
|
|
@@ -18,6 +18,7 @@ export const RIGHT_PANEL_TYPES = [
|
|
|
18
18
|
'user-browser',
|
|
19
19
|
'browser',
|
|
20
20
|
'usage',
|
|
21
|
+
'todo',
|
|
21
22
|
] as const;
|
|
22
23
|
|
|
23
24
|
export type RightPanelType = (typeof RIGHT_PANEL_TYPES)[number];
|
|
@@ -37,6 +38,7 @@ export type RightPanelType = (typeof RIGHT_PANEL_TYPES)[number];
|
|
|
37
38
|
* - `search` matches `files`: both are single narrow columns of rows.
|
|
38
39
|
* - `usage` has no OpenChamber counterpart; 0.48 keeps the pixel default it
|
|
39
40
|
* shipped with.
|
|
41
|
+
* - `todo` has no OpenChamber counterpart either; 0.3 keeps its pixel default.
|
|
40
42
|
* - `context`, `git`, `terminal` and both browsers take OpenChamber's values.
|
|
41
43
|
*/
|
|
42
44
|
export const DEFAULT_RIGHT_PANEL_FRACTIONS: Record<RightPanelType, number> = {
|
|
@@ -49,6 +51,9 @@ export const DEFAULT_RIGHT_PANEL_FRACTIONS: Record<RightPanelType, number> = {
|
|
|
49
51
|
'user-browser': 0.45,
|
|
50
52
|
browser: 0.45,
|
|
51
53
|
usage: 0.48,
|
|
54
|
+
// A todo list is one column of short rows — narrower than usage, wider than
|
|
55
|
+
// a file tree, because a task line is a sentence.
|
|
56
|
+
todo: 0.3,
|
|
52
57
|
};
|
|
53
58
|
|
|
54
59
|
/**
|
|
@@ -65,6 +70,7 @@ export const DEFAULT_RIGHT_PANEL_WIDTHS: Record<RightPanelType, number> = {
|
|
|
65
70
|
'user-browser': 804,
|
|
66
71
|
browser: 804,
|
|
67
72
|
usage: 536,
|
|
73
|
+
todo: 384,
|
|
68
74
|
};
|
|
69
75
|
|
|
70
76
|
/**
|
|
@@ -88,6 +94,8 @@ export const MIN_RIGHT_PANEL_WIDTHS: Record<RightPanelType, number> = {
|
|
|
88
94
|
'user-browser': 320,
|
|
89
95
|
browser: 320,
|
|
90
96
|
usage: 420,
|
|
97
|
+
// Below this a task line wraps to three words a row.
|
|
98
|
+
todo: 280,
|
|
91
99
|
};
|
|
92
100
|
|
|
93
101
|
/** Guards a persisted or URL-provided view id before it keys a width. */
|
|
@@ -22,4 +22,5 @@ export * from '@/shared/types/omp/instructions';
|
|
|
22
22
|
export * from '@/shared/types/omp/subagent';
|
|
23
23
|
export * from '@/shared/types/browser';
|
|
24
24
|
export * from '@/shared/types/updates';
|
|
25
|
+
export * from '@/shared/types/todo';
|
|
25
26
|
export type { RightPanelType } from '@/shared/lib/workspace/right-panels';
|