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.
Files changed (419) hide show
  1. package/README.md +31 -11
  2. package/bunfig.toml +11 -0
  3. package/dist/client/index.html +16 -5
  4. package/dist/client/index.js +1393 -0
  5. package/dist/client/static/js/index-8t70jhyj.js +5149 -0
  6. package/dist/client/static/js/index-n4wkqj7t.css +1 -0
  7. package/index.html +54 -0
  8. package/package.json +9 -10
  9. package/public/apple-touch-icon.png +0 -0
  10. package/public/icon-192x192.png +0 -0
  11. package/public/icon.svg +60 -0
  12. package/public/manifest.webmanifest +38 -0
  13. package/src/cli/lib/commands/serve.js +9 -5
  14. package/src/cli/lib/runtime.js +18 -7
  15. package/src/client/components/common/code-editor/handler.test.ts +274 -0
  16. package/src/client/components/common/code-editor/handler.ts +175 -0
  17. package/src/client/components/common/code-editor/index.tsx +185 -220
  18. package/src/client/components/common/code-surface/index.tsx +109 -12
  19. package/src/client/components/common/lazy-panels.tsx +5 -1
  20. package/src/client/components/common/provider-icon/glyphs.ts +118 -0
  21. package/src/client/components/common/provider-icon/index.tsx +85 -0
  22. package/src/client/components/common/viewport.ts +5 -3
  23. package/src/client/components/layout/RightActivityBar.tsx +2 -1
  24. package/src/client/components/layout/desktop-layout/TopNavbar.tsx +21 -4
  25. package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +4 -0
  26. package/src/client/components/layout/desktop-layout/index.tsx +26 -4
  27. package/src/client/components/mobile/LayoutWrapper.tsx +16 -0
  28. package/src/client/components/mobile/RightSidebar.tsx +11 -4
  29. package/src/client/components/mobile/SessionSidebar.tsx +10 -0
  30. package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +0 -1
  31. package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +16 -1
  32. package/src/client/components/mobile/mobile-session-sidebar/List.tsx +3 -0
  33. package/src/client/components/settings/categories/ProviderSettings.tsx +45 -4
  34. package/src/client/components/settings/categories/UsageSettings.tsx +22 -7
  35. package/src/client/components/settings/categories/provider-settings/AddModelModal.tsx +322 -0
  36. package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +152 -56
  37. package/src/client/components/settings/categories/provider-settings/AuthSection.tsx +52 -1
  38. package/src/client/components/settings/categories/provider-settings/DeleteProviderModal.tsx +133 -0
  39. package/src/client/components/settings/categories/provider-settings/DialectFields.tsx +136 -0
  40. package/src/client/components/settings/categories/provider-settings/EffortLadder.tsx +89 -0
  41. package/src/client/components/settings/categories/provider-settings/Header.tsx +2 -2
  42. package/src/client/components/settings/categories/provider-settings/LoginModal.tsx +2 -2
  43. package/src/client/components/settings/categories/provider-settings/ModelsList.tsx +22 -2
  44. package/src/client/components/settings/categories/provider-settings/PresetPicker.tsx +82 -0
  45. package/src/client/components/settings/categories/provider-settings/PriceFields.tsx +107 -0
  46. package/src/client/components/settings/categories/provider-settings/ReconnectModal.tsx +43 -22
  47. package/src/client/components/settings/categories/provider-settings/SidebarList.tsx +2 -2
  48. package/src/client/components/settings/categories/usage-settings/LimitsSection.tsx +115 -0
  49. package/src/client/components/settings/categories/usage-settings/ProviderCard.tsx +19 -26
  50. package/src/client/components/settings/categories/usage-settings/ProviderDetail.tsx +29 -22
  51. package/src/client/components/settings/categories/usage-settings/SidebarList.tsx +60 -48
  52. package/src/client/components/settings/categories/usage-settings/format.ts +36 -0
  53. package/src/client/components/settings/categories/usage-settings/providers.ts +52 -28
  54. package/src/client/components/workspace/browser-panel/AddressBar.tsx +1 -10
  55. package/src/client/components/workspace/browser-panel/Screencast.tsx +4 -5
  56. package/src/client/components/workspace/browser-panel/index.tsx +6 -6
  57. package/src/client/components/workspace/browser-toolbar/index.tsx +22 -12
  58. package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +1 -1
  59. package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +1 -1
  60. package/src/client/components/workspace/chat-timeline/ToolCallCard.tsx +1 -1
  61. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Todo.tsx +1 -1
  62. package/src/client/components/workspace/diff-panel/Toolbar.tsx +71 -38
  63. package/src/client/components/workspace/diff-panel/index.tsx +139 -53
  64. package/src/client/components/workspace/editor/CommandPalette.test.ts +242 -0
  65. package/src/client/components/workspace/editor/CommandPalette.tsx +138 -0
  66. package/src/client/components/workspace/editor/FindWidget.test.ts +261 -0
  67. package/src/client/components/workspace/editor/FindWidget.tsx +219 -0
  68. package/src/client/components/workspace/editor/Tabs.tsx +2 -2
  69. package/src/client/components/workspace/editor/Toolbar.tsx +129 -26
  70. package/src/client/components/workspace/editor/index.tsx +180 -62
  71. package/src/client/components/workspace/file-explorer/Modals.tsx +79 -2
  72. package/src/client/components/workspace/file-explorer/TreeItem.tsx +43 -113
  73. package/src/client/components/workspace/model-dropdown/Item.tsx +13 -23
  74. package/src/client/components/workspace/model-dropdown/Panel.tsx +1 -1
  75. package/src/client/components/workspace/model-dropdown/Section.tsx +13 -4
  76. package/src/client/components/workspace/model-dropdown/index.tsx +7 -3
  77. package/src/client/components/workspace/todo-panel/PhaseGroup.tsx +76 -0
  78. package/src/client/components/workspace/todo-panel/Row.tsx +69 -0
  79. package/src/client/components/workspace/todo-panel/index.tsx +161 -0
  80. package/src/client/components/workspace/usage-panel/ProviderSelect.tsx +10 -5
  81. package/src/client/components/workspace/usage-panel/index.tsx +36 -18
  82. package/src/client/data/samples/dialogue-session.ts +1 -1
  83. package/src/client/data/samples/sample-tools/other.ts +1 -1
  84. package/src/client/data/settings/provider.ts +141 -9
  85. package/src/client/hooks/editor/find-harness.test-util.ts +170 -0
  86. package/src/client/hooks/editor/scroll-container.test.ts +102 -0
  87. package/src/client/hooks/editor/scroll-container.ts +26 -0
  88. package/src/client/hooks/editor/use-code-window.ts +2 -20
  89. package/src/client/hooks/editor/use-editor-find-write.test.ts +170 -0
  90. package/src/client/hooks/editor/use-editor-find.test.ts +118 -0
  91. package/src/client/hooks/editor/use-editor-find.ts +321 -0
  92. package/src/client/hooks/editor/use-editor-history.test.ts +216 -0
  93. package/src/client/hooks/editor/use-editor-history.ts +134 -0
  94. package/src/client/hooks/editor/use-file-editor.ts +24 -27
  95. package/src/client/hooks/editor/use-occurrences.test.ts +236 -0
  96. package/src/client/hooks/editor/use-occurrences.ts +98 -0
  97. package/src/client/hooks/models/use-provider-names.ts +1 -1
  98. package/src/client/hooks/settings/providers/actions.ts +337 -0
  99. package/src/client/hooks/settings/providers/api.ts +69 -0
  100. package/src/client/hooks/settings/providers/index.ts +138 -0
  101. package/src/client/hooks/settings/providers/messages.ts +77 -0
  102. package/src/client/hooks/settings/useUsageReport.ts +8 -3
  103. package/src/client/hooks/ui/theme.ts +3 -3
  104. package/src/client/hooks/workspace/file-tabs.ts +62 -11
  105. package/src/client/hooks/workspace/file-tree-actions.test.ts +348 -0
  106. package/src/client/hooks/workspace/file-tree-actions.ts +250 -0
  107. package/src/client/hooks/workspace/session-todos.ts +102 -0
  108. package/src/client/stubs/elkjs.ts +6 -1
  109. package/src/client/tailwind.css +30 -0
  110. package/src/server/index.ts +20 -2
  111. package/src/server/lib/assets/font-css.server.ts +86 -0
  112. package/src/server/lib/assets/fonts.server.ts +117 -0
  113. package/src/server/lib/bundler/css.ts +116 -0
  114. package/src/server/lib/lifecycle/shell-route.ts +17 -0
  115. package/src/server/lib/models/provider-listing.server.ts +257 -0
  116. package/src/server/lib/models/provider-registry.server.test.ts +122 -0
  117. package/src/server/lib/models/provider-registry.server.ts +76 -25
  118. package/src/server/lib/omp/config/models-config.ts +11 -0
  119. package/src/server/lib/omp/config/models-validation.test.ts +137 -0
  120. package/src/server/lib/omp/config/models-validation.ts +111 -2
  121. package/src/server/lib/omp/config/provider-seeds.test.ts +167 -0
  122. package/src/server/lib/omp/config/provider-seeds.ts +176 -0
  123. package/src/server/lib/omp/config/providers.ts +94 -100
  124. package/src/server/lib/omp/core/auth-credentials.ts +45 -0
  125. package/src/server/lib/omp/session/todos.ts +100 -0
  126. package/src/server/lib/omp/session/{usage.ts → usage/aggregate.ts} +1 -118
  127. package/src/server/lib/omp/session/usage/shaping.ts +130 -0
  128. package/src/server/lib/usage/deepseek.ts +4 -5
  129. package/src/server/lib/usage/kenari.ts +7 -6
  130. package/src/server/lib/usage/omp-usage.server.test.ts +126 -0
  131. package/src/server/lib/usage/omp-usage.server.ts +194 -0
  132. package/src/server/lib/usage/provider-key.ts +81 -3
  133. package/src/server/lib/usage/providers.server.test.ts +47 -0
  134. package/src/server/lib/usage/providers.server.ts +208 -0
  135. package/src/server/lib/usage/vendor-balance.server.test.ts +104 -0
  136. package/src/server/lib/usage/vendor-balance.server.ts +284 -0
  137. package/src/server/plugins/shell.server.ts +56 -0
  138. package/src/server/plugins/ssr.ts +24 -36
  139. package/src/server/plugins/static.test.ts +26 -29
  140. package/src/server/plugins/static.ts +40 -19
  141. package/src/server/routes/fs/action-create.test.ts +90 -0
  142. package/src/server/routes/fs/action.ts +23 -0
  143. package/src/server/routes/omp/index.ts +2 -0
  144. package/src/server/routes/omp/session-todos.ts +80 -0
  145. package/src/server/routes/settings/index.ts +2 -0
  146. package/src/server/routes/settings/provider-model.ts +107 -0
  147. package/src/server/routes/settings/provider-models.ts +65 -202
  148. package/src/server/routes/settings/providers.ts +22 -7
  149. package/src/server/routes/settings/usage.ts +125 -45
  150. package/src/server/routes/telemetry/tokens.ts +2 -1
  151. package/src/shared/lib/chat/{todo-parser.ts → todo/parser.ts} +17 -0
  152. package/src/shared/lib/chat/todo/progress.ts +97 -0
  153. package/src/shared/lib/chat/todo/snapshot.test.ts +156 -0
  154. package/src/shared/lib/chat/todo/snapshot.ts +215 -0
  155. package/src/shared/lib/code/editor/commands.test.ts +155 -0
  156. package/src/shared/lib/code/editor/commands.ts +260 -0
  157. package/src/shared/lib/code/editor/edits.test.ts +100 -0
  158. package/src/shared/lib/code/editor/edits.ts +122 -0
  159. package/src/shared/lib/code/editor/find-status.test.ts +50 -0
  160. package/src/shared/lib/code/editor/find-status.ts +36 -0
  161. package/src/shared/lib/code/editor/find.test.ts +203 -0
  162. package/src/shared/lib/code/editor/find.ts +224 -0
  163. package/src/{client/components/common/code-editor → shared/lib/code/editor}/history.test.ts +1 -1
  164. package/src/shared/lib/code/editor/keymap.test.ts +109 -0
  165. package/src/shared/lib/code/editor/keymap.ts +151 -0
  166. package/src/shared/lib/code/editor/layers.test.ts +77 -0
  167. package/src/shared/lib/code/editor/layers.ts +67 -0
  168. package/src/shared/lib/code/editor/line-edits.test.ts +131 -0
  169. package/src/shared/lib/code/editor/line-edits.ts +159 -0
  170. package/src/shared/lib/code/editor/lines.test.ts +81 -0
  171. package/src/shared/lib/code/editor/lines.ts +83 -0
  172. package/src/shared/lib/code/editor/marks.test.ts +86 -0
  173. package/src/shared/lib/code/editor/marks.ts +106 -0
  174. package/src/shared/lib/code/editor/occurrences.test.ts +117 -0
  175. package/src/shared/lib/code/editor/occurrences.ts +111 -0
  176. package/src/shared/lib/code/editor/palette.test.ts +91 -0
  177. package/src/shared/lib/code/editor/palette.ts +92 -0
  178. package/src/shared/lib/code/editor/reset-css.test.ts +35 -0
  179. package/src/shared/lib/code/editor/reset-css.ts +32 -0
  180. package/src/shared/lib/code/editor/text-commands.test.ts +178 -0
  181. package/src/shared/lib/code/editor/text-commands.ts +154 -0
  182. package/src/shared/lib/code/syntax-highlight.test.ts +75 -0
  183. package/src/shared/lib/code/syntax-highlight.ts +29 -17
  184. package/src/shared/lib/code/windowed-highlight.ts +52 -1
  185. package/src/shared/lib/fs/diff-parser.test.ts +116 -0
  186. package/src/shared/lib/fs/diff-parser.ts +86 -33
  187. package/src/shared/lib/models/{provider-connection.ts → provider/connection.ts} +37 -0
  188. package/src/shared/lib/models/provider/dialect.ts +179 -0
  189. package/src/shared/lib/models/provider/glyph.test.ts +116 -0
  190. package/src/shared/lib/models/provider/glyph.ts +200 -0
  191. package/src/shared/lib/models/{provider-models.ts → provider/models.ts} +91 -6
  192. package/src/shared/lib/models/provider/presets.test.ts +172 -0
  193. package/src/shared/lib/models/provider/presets.ts +83 -0
  194. package/src/shared/lib/ui/key-binding.test.ts +115 -0
  195. package/src/shared/lib/ui/key-binding.ts +143 -0
  196. package/src/shared/lib/util/platform.test.ts +87 -0
  197. package/src/shared/lib/util/platform.ts +30 -0
  198. package/src/shared/lib/workspace/file-tab-id.ts +29 -0
  199. package/src/shared/lib/workspace/keymap.test.ts +82 -0
  200. package/src/shared/lib/workspace/keymap.ts +30 -0
  201. package/src/shared/lib/workspace/refresh-cadence.ts +9 -0
  202. package/src/shared/lib/workspace/right-panels.ts +8 -0
  203. package/src/shared/lib/workspace/session-state/store.ts +0 -1
  204. package/src/shared/types/index.ts +1 -0
  205. package/src/shared/types/settings/provider.ts +81 -0
  206. package/src/shared/types/settings/usage.ts +88 -14
  207. package/src/shared/types/todo.ts +108 -0
  208. package/tsconfig.json +3 -1
  209. package/dist/client/static/css/index.5f630421b7.css +0 -2
  210. package/dist/client/static/font/KaTeX_AMS-Regular.a79f1c3119.woff2 +0 -0
  211. package/dist/client/static/font/KaTeX_Caligraphic-Bold.ec17d13264.woff2 +0 -0
  212. package/dist/client/static/font/KaTeX_Caligraphic-Regular.55fac25845.woff2 +0 -0
  213. package/dist/client/static/font/KaTeX_Fraktur-Bold.d42a5579b0.woff2 +0 -0
  214. package/dist/client/static/font/KaTeX_Fraktur-Regular.d3c882a649.woff2 +0 -0
  215. package/dist/client/static/font/KaTeX_Main-Bold.c3fb5ac22f.woff2 +0 -0
  216. package/dist/client/static/font/KaTeX_Main-BoldItalic.6f2bb1dff2.woff2 +0 -0
  217. package/dist/client/static/font/KaTeX_Main-Italic.8916142bec.woff2 +0 -0
  218. package/dist/client/static/font/KaTeX_Main-Regular.0462f03bdf.woff2 +0 -0
  219. package/dist/client/static/font/KaTeX_Math-BoldItalic.572d331f69.woff2 +0 -0
  220. package/dist/client/static/font/KaTeX_Math-Italic.f28c23acad.woff2 +0 -0
  221. package/dist/client/static/font/KaTeX_SansSerif-Bold.8c5b5494b6.woff2 +0 -0
  222. package/dist/client/static/font/KaTeX_SansSerif-Italic.3b1e59b3ba.woff2 +0 -0
  223. package/dist/client/static/font/KaTeX_SansSerif-Regular.ba21ed5f84.woff2 +0 -0
  224. package/dist/client/static/font/KaTeX_Script-Regular.03e9641d6f.woff2 +0 -0
  225. package/dist/client/static/font/KaTeX_Size1-Regular.eae34984b3.woff2 +0 -0
  226. package/dist/client/static/font/KaTeX_Size2-Regular.5916a24fa3.woff2 +0 -0
  227. package/dist/client/static/font/KaTeX_Size4-Regular.10d95fd3a2.woff2 +0 -0
  228. package/dist/client/static/font/KaTeX_Typewriter-Regular.a8709e3622.woff2 +0 -0
  229. package/dist/client/static/font/fira-code-latin-400-normal.2655b10c55.woff2 +0 -0
  230. package/dist/client/static/font/fira-code-latin-500-normal.92a991f204.woff2 +0 -0
  231. package/dist/client/static/font/fira-code-latin-600-normal.43788eb7f1.woff2 +0 -0
  232. package/dist/client/static/font/fira-code-latin-700-normal.2667215d33.woff2 +0 -0
  233. package/dist/client/static/js/77.0d81c74cf0.js +0 -139
  234. package/dist/client/static/js/77.0d81c74cf0.js.LICENSE.txt +0 -8
  235. package/dist/client/static/js/async/0.8e59f2eb5b.js +0 -1
  236. package/dist/client/static/js/async/0k.5852e6b4fa.js +0 -1
  237. package/dist/client/static/js/async/0w.9ed36d5860.js +0 -1
  238. package/dist/client/static/js/async/1.f387c12b63.js +0 -1
  239. package/dist/client/static/js/async/12.d091f91c99.js +0 -1
  240. package/dist/client/static/js/async/13.8f810a8a6c.js +0 -1
  241. package/dist/client/static/js/async/19.6fe909d55a.js +0 -160
  242. package/dist/client/static/js/async/1b.23f1141a38.js +0 -1
  243. package/dist/client/static/js/async/1k.973e8e21fc.js +0 -2
  244. package/dist/client/static/js/async/1k.973e8e21fc.js.LICENSE.txt +0 -11
  245. package/dist/client/static/js/async/1p.0d5cddda96.js +0 -1
  246. package/dist/client/static/js/async/2.b589f1733b.js +0 -1
  247. package/dist/client/static/js/async/2v.96165101de.js +0 -1
  248. package/dist/client/static/js/async/3.0cacc08423.js +0 -1
  249. package/dist/client/static/js/async/37.b28b2f61cd.js +0 -1
  250. package/dist/client/static/js/async/3d.5085fb913f.js +0 -1
  251. package/dist/client/static/js/async/3j.357c80ff38.js +0 -2
  252. package/dist/client/static/js/async/3j.357c80ff38.js.LICENSE.txt +0 -11
  253. package/dist/client/static/js/async/3p.bcf32a0983.js +0 -1
  254. package/dist/client/static/js/async/3r.3a69e98ee0.js +0 -64
  255. package/dist/client/static/js/async/3u.1228460e2a.js +0 -1
  256. package/dist/client/static/js/async/3y.3530b824ad.js +0 -198
  257. package/dist/client/static/js/async/3y.3530b824ad.js.LICENSE.txt +0 -5
  258. package/dist/client/static/js/async/4.9442eeb472.js +0 -1
  259. package/dist/client/static/js/async/4e.678285ca55.js +0 -1
  260. package/dist/client/static/js/async/4k.33a0c7eeb4.js +0 -1
  261. package/dist/client/static/js/async/4m.928a1dde85.js +0 -31
  262. package/dist/client/static/js/async/4m.928a1dde85.js.LICENSE.txt +0 -11
  263. package/dist/client/static/js/async/4q.aad20dab25.js +0 -1
  264. package/dist/client/static/js/async/5.ebaf8b89cc.js +0 -36
  265. package/dist/client/static/js/async/51.95bcae98af.js +0 -2
  266. package/dist/client/static/js/async/51.95bcae98af.js.LICENSE.txt +0 -6
  267. package/dist/client/static/js/async/5b.d88304bc13.js +0 -1
  268. package/dist/client/static/js/async/5j.d6407c4248.js +0 -287
  269. package/dist/client/static/js/async/5o.88316d4460.js +0 -62
  270. package/dist/client/static/js/async/6.f034c9b528.js +0 -1
  271. package/dist/client/static/js/async/66.5dfa29f957.js +0 -93
  272. package/dist/client/static/js/async/6o.5b1a06a382.js +0 -1
  273. package/dist/client/static/js/async/7.e4a9288dd0.js +0 -1
  274. package/dist/client/static/js/async/73.308dff4da8.js +0 -1
  275. package/dist/client/static/js/async/7e.a1cc9bfd88.js +0 -1
  276. package/dist/client/static/js/async/7g.a7814350c3.js +0 -1
  277. package/dist/client/static/js/async/7l.fadbbc0583.js +0 -1
  278. package/dist/client/static/js/async/8.5067439d8c.js +0 -1
  279. package/dist/client/static/js/async/8j.ea110663fb.js +0 -1
  280. package/dist/client/static/js/async/a.bce4542392.js +0 -1
  281. package/dist/client/static/js/async/aa.65d3852576.js +0 -1
  282. package/dist/client/static/js/async/ar.9303e5f2e8.js +0 -2
  283. package/dist/client/static/js/async/ar.9303e5f2e8.js.LICENSE.txt +0 -11
  284. package/dist/client/static/js/async/az.d656062b0f.js +0 -1
  285. package/dist/client/static/js/async/b.b4f6f06515.js +0 -1
  286. package/dist/client/static/js/async/bi.09688ba75c.js +0 -1
  287. package/dist/client/static/js/async/bq.586bc738d1.js +0 -1
  288. package/dist/client/static/js/async/c.797d933882.js +0 -1
  289. package/dist/client/static/js/async/c0.0af1e640d3.js +0 -24
  290. package/dist/client/static/js/async/ca.8a52cfd447.js +0 -1
  291. package/dist/client/static/js/async/cf.d34fff5d67.js +0 -1
  292. package/dist/client/static/js/async/ct.b74928f7a3.js +0 -1
  293. package/dist/client/static/js/async/cx.3cb0242a3a.js +0 -226
  294. package/dist/client/static/js/async/d.a5fc8fe1b3.js +0 -1
  295. package/dist/client/static/js/async/da.f62c70f6c8.js +0 -1
  296. package/dist/client/static/js/async/dp.1725d6432c.js +0 -1
  297. package/dist/client/static/js/async/e.93214d622b.js +0 -79
  298. package/dist/client/static/js/async/f.ab50467719.js +0 -19
  299. package/dist/client/static/js/async/f.ab50467719.js.LICENSE.txt +0 -12
  300. package/dist/client/static/js/async/f7.46971e9d6d.js +0 -2
  301. package/dist/client/static/js/async/f7.46971e9d6d.js.LICENSE.txt +0 -6
  302. package/dist/client/static/js/async/f8.bbf0dbae70.js +0 -1
  303. package/dist/client/static/js/async/g.cb81980498.js +0 -2
  304. package/dist/client/static/js/async/g.cb81980498.js.LICENSE.txt +0 -11
  305. package/dist/client/static/js/async/h.4e790471e4.js +0 -27
  306. package/dist/client/static/js/async/h1.393f066d09.js +0 -1
  307. package/dist/client/static/js/async/h3.216e9798ab.js +0 -148
  308. package/dist/client/static/js/async/hd.0da931b622.js +0 -1
  309. package/dist/client/static/js/async/hk.ba7fb04d41.js +0 -1
  310. package/dist/client/static/js/async/hn.b645041e00.js +0 -1
  311. package/dist/client/static/js/async/ho.475a5e1ae4.js +0 -1
  312. package/dist/client/static/js/async/hp.e9fe2e395a.js +0 -2
  313. package/dist/client/static/js/async/hp.e9fe2e395a.js.LICENSE.txt +0 -17
  314. package/dist/client/static/js/async/hs.e7c59d8090.js +0 -41
  315. package/dist/client/static/js/async/i.7c148e8440.js +0 -1
  316. package/dist/client/static/js/async/id.573df20f29.js +0 -1
  317. package/dist/client/static/js/async/j.ab925882d8.js +0 -1
  318. package/dist/client/static/js/async/jd.63886a1f09.js +0 -1
  319. package/dist/client/static/js/async/jg.d86ebd4013.js +0 -86
  320. package/dist/client/static/js/async/jl.874d352278.js +0 -1
  321. package/dist/client/static/js/async/jll.11313b455a.js +0 -1
  322. package/dist/client/static/js/async/js.67182759ef.js +0 -1
  323. package/dist/client/static/js/async/jsc.23b70d6f40.js +0 -1
  324. package/dist/client/static/js/async/jse.334588ff5d.js +0 -1
  325. package/dist/client/static/js/async/jz.72eb929106.js +0 -77
  326. package/dist/client/static/js/async/k.3210286401.js +0 -217
  327. package/dist/client/static/js/async/ka.c1990ecaa3.js +0 -115
  328. package/dist/client/static/js/async/katex-vendor.de6a87c308.js +0 -1
  329. package/dist/client/static/js/async/ku.29c8a59d0c.js +0 -1
  330. package/dist/client/static/js/async/ky.8dab1dade0.js +0 -1
  331. package/dist/client/static/js/async/l.12dd34e1be.js +0 -1
  332. package/dist/client/static/js/async/l9.949710adaf.js +0 -83
  333. package/dist/client/static/js/async/l9.949710adaf.js.LICENSE.txt +0 -13
  334. package/dist/client/static/js/async/ls.9d9a01026c.js +0 -104
  335. package/dist/client/static/js/async/m.eb2e6752e2.js +0 -28
  336. package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +0 -11
  337. package/dist/client/static/js/async/m3.d2b05f9604.js +0 -2
  338. package/dist/client/static/js/async/m3.d2b05f9604.js.LICENSE.txt +0 -6
  339. package/dist/client/static/js/async/ma.2c261b04dd.js +0 -2
  340. package/dist/client/static/js/async/ma.2c261b04dd.js.LICENSE.txt +0 -6
  341. package/dist/client/static/js/async/me.8ace414f80.js +0 -23
  342. package/dist/client/static/js/async/ms.98960af4eb.js +0 -67
  343. package/dist/client/static/js/async/ms.98960af4eb.js.LICENSE.txt +0 -12
  344. package/dist/client/static/js/async/mt.0352e976d8.js +0 -1
  345. package/dist/client/static/js/async/n.a9f1c51701.js +0 -1
  346. package/dist/client/static/js/async/na.a36cd532d6.js +0 -1
  347. package/dist/client/static/js/async/nk.fc7e58325e.js +0 -1
  348. package/dist/client/static/js/async/o.36353dfb88.js +0 -1
  349. package/dist/client/static/js/async/oc.b73042e8bd.js +0 -8
  350. package/dist/client/static/js/async/og.8eb577893a.js +0 -193
  351. package/dist/client/static/js/async/p.48ecfcd14e.js +0 -1
  352. package/dist/client/static/js/async/px.fc6877924b.js +0 -1
  353. package/dist/client/static/js/async/q.22588845a9.js +0 -21
  354. package/dist/client/static/js/async/q.22588845a9.js.LICENSE.txt +0 -6
  355. package/dist/client/static/js/async/q5.97dc1481b6.js +0 -286
  356. package/dist/client/static/js/async/qs.2a9ddb3c72.js +0 -1
  357. package/dist/client/static/js/async/r.51010ad87c.js +0 -1
  358. package/dist/client/static/js/async/rd.cb6bf495bb.js +0 -2
  359. package/dist/client/static/js/async/rd.cb6bf495bb.js.LICENSE.txt +0 -4
  360. package/dist/client/static/js/async/s.0f395cdda5.js +0 -94
  361. package/dist/client/static/js/async/t.4dca0e2ba8.js +0 -1
  362. package/dist/client/static/js/async/t9.d44f57d518.js +0 -1
  363. package/dist/client/static/js/async/ta.973534bbec.js +0 -1
  364. package/dist/client/static/js/async/tr.e878658140.js +0 -309
  365. package/dist/client/static/js/async/u.0871ac5b22.js +0 -1
  366. package/dist/client/static/js/async/ud.3065bfee31.js +0 -2
  367. package/dist/client/static/js/async/ud.3065bfee31.js.LICENSE.txt +0 -4
  368. package/dist/client/static/js/async/up.ea990da0ff.js +0 -32
  369. package/dist/client/static/js/async/uw.8890abbabc.js +0 -1
  370. package/dist/client/static/js/async/v.7d28d3c241.js +0 -34
  371. package/dist/client/static/js/async/vp.9b13bf8610.js +0 -1
  372. package/dist/client/static/js/async/vt.42416b707d.js +0 -1
  373. package/dist/client/static/js/async/w.08ac210f60.js +0 -1
  374. package/dist/client/static/js/async/wt.ad92393a90.js +0 -1
  375. package/dist/client/static/js/async/x.563f72ca6b.js +0 -1
  376. package/dist/client/static/js/async/x0.475516418f.js +0 -1
  377. package/dist/client/static/js/async/x1.da8687159b.js +0 -90
  378. package/dist/client/static/js/async/xe.da8dcc2015.js +0 -2
  379. package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +0 -11
  380. package/dist/client/static/js/async/xl.a3c562a58c.js +0 -1
  381. package/dist/client/static/js/async/xn.6510c0abe2.js +0 -2
  382. package/dist/client/static/js/async/xn.6510c0abe2.js.LICENSE.txt +0 -6
  383. package/dist/client/static/js/async/xn1.83a7784fe1.js +0 -10
  384. package/dist/client/static/js/async/xn1.83a7784fe1.js.LICENSE.txt +0 -11
  385. package/dist/client/static/js/async/xnl.feb0021e50.js +0 -2
  386. package/dist/client/static/js/async/xnl.feb0021e50.js.LICENSE.txt +0 -12
  387. package/dist/client/static/js/async/y.1a9ed7a5d8.js +0 -1
  388. package/dist/client/static/js/async/y6.43c57defe4.js +0 -1
  389. package/dist/client/static/js/async/yb.8c1342401d.js +0 -1
  390. package/dist/client/static/js/async/ybq.4491f2f2dc.js +0 -1
  391. package/dist/client/static/js/async/yg.35bebd6d0c.js +0 -149
  392. package/dist/client/static/js/async/yq.917ddd7ba2.js +0 -1
  393. package/dist/client/static/js/async/ys.00687cf82d.js +0 -38
  394. package/dist/client/static/js/async/yv.d99d829136.js +0 -1
  395. package/dist/client/static/js/async/z.56f61a4374.js +0 -1
  396. package/dist/client/static/js/async/zi.5fdb07b86a.js +0 -24
  397. package/dist/client/static/js/async/zj.654f37e8ff.js +0 -1
  398. package/dist/client/static/js/async/zn.966d6f1cc6.js +0 -1
  399. package/dist/client/static/js/async/zo.26213b40c0.js +0 -1
  400. package/dist/client/static/js/async/zx.e9f4105e00.js +0 -1
  401. package/dist/client/static/js/index.e6a257402a.js +0 -38
  402. package/dist/client/static/js/index.e6a257402a.js.LICENSE.txt +0 -4
  403. package/dist/client/static/js/markdown-vendor.71e43278b3.js +0 -144
  404. package/dist/client/static/js/shiki-vendor.cb8452ddcd.js +0 -1
  405. package/src/client/components/settings/categories/provider-settings/Icons.tsx +0 -35
  406. package/src/client/hooks/settings/providers.ts +0 -326
  407. package/src/server/plugins/dev-assets.ts +0 -49
  408. package/src/shared/lib/code/editor-utils.ts +0 -16
  409. package/src/shared/lib/models/provider-presets.ts +0 -29
  410. /package/dist/client/{apple-touch-icon.png → static/apple-touch-icon-dskx5sr3.png} +0 -0
  411. /package/dist/client/{icon-192x192.png → static/icon-192x192-6gn5cr2f.png} +0 -0
  412. /package/dist/client/{icon.svg → static/icon-d07z9dtz.svg} +0 -0
  413. /package/dist/client/{manifest.webmanifest → static/manifest-bnyy75kt.webmanifest} +0 -0
  414. /package/{dist/client → public}/icon-512x512.png +0 -0
  415. /package/{dist/client → public}/icon-maskable-512x512.png +0 -0
  416. /package/{dist/client → public}/sw.js +0 -0
  417. /package/src/{client/components/common/code-editor → shared/lib/code/editor}/history.ts +0 -0
  418. /package/src/shared/lib/models/{provider-cleanup.ts → provider/cleanup.ts} +0 -0
  419. /package/src/shared/lib/models/{provider-label.ts → provider/label.ts} +0 -0
@@ -0,0 +1,261 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * The find bar's rendering.
8
+ *
9
+ * The arithmetic lives in `shared/lib/code/editor/find-status` and the state in
10
+ * `use-editor-find`; what this covers is the bar itself — that every toggle
11
+ * carries its own `aria-pressed`, that the chord in a tooltip comes from the
12
+ * keymap rather than a hand-written string, and that Enter and the buttons reach
13
+ * the hook rather than doing nothing.
14
+ *
15
+ * Rendered directly with `h()` (no JSX) against happy-dom; the component modules
16
+ * are imported dynamically, after those globals exist.
17
+ */
18
+
19
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from 'bun:test';
20
+ import { Window } from 'happy-dom';
21
+ import type { ComponentChildren, h as PreactH, render as PreactRender } from 'preact';
22
+ import type { act as PreactAct } from 'preact/test-utils';
23
+ import type { EditorFindState } from '@/client/hooks/editor/use-editor-find';
24
+
25
+ let FindWidget: (props: { find: EditorFindState }) => ComponentChildren;
26
+ let h: typeof PreactH;
27
+ let render: typeof PreactRender;
28
+ let act: typeof PreactAct;
29
+
30
+ let container: HTMLElement;
31
+ /** Calls the widget made into the hook, so the wiring is observable. */
32
+ const calls: string[] = [];
33
+
34
+ /** A find state whose every action records itself, so a click is provable. */
35
+ function findState(overrides: Partial<EditorFindState> = {}): EditorFindState {
36
+ return {
37
+ open: true,
38
+ replaceOpen: false,
39
+ query: '',
40
+ replacement: '',
41
+ options: { matchCase: false, wholeWord: false, isRegex: false },
42
+ matches: [],
43
+ truncated: false,
44
+ invalid: false,
45
+ currentIndex: -1,
46
+ setQuery: (value) => calls.push(`setQuery:${value}`),
47
+ setReplacement: (value) => calls.push(`setReplacement:${value}`),
48
+ openFind: () => calls.push('openFind'),
49
+ openReplace: () => calls.push('openReplace'),
50
+ toggleReplace: () => calls.push('toggleReplace'),
51
+ focusRequest: 0,
52
+ close: () => calls.push('close'),
53
+ runCommand: (command) => calls.push(`runCommand:${command}`),
54
+ toggleOption: (key) => calls.push(`toggleOption:${key}`),
55
+ step: (direction) => calls.push(`step:${direction}`),
56
+ replaceCurrent: () => calls.push('replaceCurrent'),
57
+ replaceAll: () => calls.push('replaceAll'),
58
+ ...overrides,
59
+ };
60
+ }
61
+
62
+ async function mount(find: EditorFindState) {
63
+ await act(async () => {
64
+ render(h(FindWidget, { find }), container);
65
+ });
66
+ }
67
+
68
+ function byLabel(label: string): HTMLElement | null {
69
+ return container.querySelector(`[aria-label="${label}"]`);
70
+ }
71
+
72
+ /** The BUTTON with this label — `Replace` also names the text field. */
73
+ function buttonByLabel(label: string): HTMLButtonElement | null {
74
+ return container.querySelector(`button[aria-label="${label}"]`);
75
+ }
76
+
77
+ /** The element whose label starts with `prefix` — the toggles carry a chord suffix. */
78
+ function byLabelPrefix(prefix: string): HTMLElement | null {
79
+ return container.querySelector(`[aria-label^="${prefix}"]`);
80
+ }
81
+
82
+ async function click(element: HTMLElement | null) {
83
+ await act(async () => {
84
+ element?.dispatchEvent(new globalThis.MouseEvent('click', { bubbles: true }));
85
+ });
86
+ }
87
+
88
+ /** Type into a field the way the browser reports it. */
89
+ async function type(input: HTMLElement | null, value: string) {
90
+ if (!input) return;
91
+ (input as HTMLInputElement).value = value;
92
+ await act(async () => {
93
+ input.dispatchEvent(new globalThis.Event('input', { bubbles: true }));
94
+ });
95
+ }
96
+
97
+ async function press(input: HTMLElement | null, key: string, shiftKey = false) {
98
+ await act(async () => {
99
+ input?.dispatchEvent(new globalThis.KeyboardEvent('keydown', { key, shiftKey, bubbles: true }));
100
+ });
101
+ }
102
+
103
+ function domGlobals(win: Window): Record<string, unknown> {
104
+ return {
105
+ window: win,
106
+ document: win.document,
107
+ navigator: win.navigator,
108
+ Node: win.Node,
109
+ Element: win.Element,
110
+ HTMLElement: win.HTMLElement,
111
+ HTMLInputElement: win.HTMLInputElement,
112
+ Event: win.Event,
113
+ MouseEvent: win.MouseEvent,
114
+ KeyboardEvent: win.KeyboardEvent,
115
+ };
116
+ }
117
+
118
+ const installed: Record<string, unknown> = {};
119
+ const displaced: Record<string, unknown> = {};
120
+
121
+ beforeAll(async () => {
122
+ const win = new Window({ url: 'http://localhost' });
123
+ for (const [key, value] of Object.entries(domGlobals(win))) {
124
+ displaced[key] = (globalThis as Record<string, unknown>)[key];
125
+ installed[key] = value;
126
+ (globalThis as Record<string, unknown>)[key] = value;
127
+ }
128
+ ({ FindWidget } = await import('@/client/components/workspace/editor/FindWidget'));
129
+ ({ h, render } = await import('preact'));
130
+ ({ act } = await import('preact/test-utils'));
131
+ });
132
+
133
+ afterAll(() => {
134
+ for (const key of Object.keys(installed)) {
135
+ if (displaced[key] === undefined) delete (globalThis as Record<string, unknown>)[key];
136
+ else (globalThis as Record<string, unknown>)[key] = displaced[key];
137
+ }
138
+ });
139
+
140
+ beforeEach(() => {
141
+ calls.length = 0;
142
+ container = document.createElement('div');
143
+ document.body.appendChild(container);
144
+ });
145
+
146
+ afterEach(() => {
147
+ render(null, container);
148
+ container.remove();
149
+ });
150
+
151
+ describe('FindWidget', () => {
152
+ test('focuses the query field on mount, so ⌘F needs no second click', async () => {
153
+ await mount(findState());
154
+
155
+ expect(document.activeElement).toBe(byLabel('Find'));
156
+ });
157
+
158
+ test('typing in the query reaches the hook', async () => {
159
+ await mount(findState());
160
+ await type(byLabel('Find'), 'alpha');
161
+
162
+ expect(calls).toEqual(['setQuery:alpha']);
163
+ });
164
+
165
+ test('the replace row is hidden until it is asked for', async () => {
166
+ await mount(findState());
167
+
168
+ expect(byLabel('Replace')).toBeNull();
169
+ expect(byLabelPrefix('Show replace')).not.toBeNull();
170
+ });
171
+
172
+ test('with the replace row open, typing the replacement reaches the hook', async () => {
173
+ await mount(findState({ replaceOpen: true }));
174
+ await type(byLabel('Replace'), 'OMEGA');
175
+
176
+ expect(calls).toEqual(['setReplacement:OMEGA']);
177
+ });
178
+
179
+ test('each toggle reports its own state and its own shortcut', async () => {
180
+ await mount(findState({ options: { matchCase: true, wholeWord: false, isRegex: true } }));
181
+
182
+ const matchCase = byLabelPrefix('Match case');
183
+ const wholeWord = byLabelPrefix('Match whole word');
184
+ const regex = byLabelPrefix('Use regular expression');
185
+
186
+ expect(matchCase?.getAttribute('aria-pressed')).toBe('true');
187
+ expect(wholeWord?.getAttribute('aria-pressed')).toBe('false');
188
+ expect(regex?.getAttribute('aria-pressed')).toBe('true');
189
+ // The chord is derived from the keymap, so a tooltip cannot name a key that
190
+ // is not bound.
191
+ expect(matchCase?.getAttribute('aria-label')).toMatch(/[⌘⌥⇧]|Alt\+/);
192
+ });
193
+
194
+ test('clicking a toggle names the option it flips', async () => {
195
+ await mount(findState());
196
+ await click(byLabelPrefix('Match whole word'));
197
+
198
+ expect(calls).toEqual(['toggleOption:wholeWord']);
199
+ });
200
+
201
+ test('Enter steps forward and ⇧Enter steps back, keeping focus in the field', async () => {
202
+ await mount(findState({ matches: [{ start: 0, end: 5 }], currentIndex: 0 }));
203
+
204
+ await press(byLabel('Find'), 'Enter');
205
+ await press(byLabel('Find'), 'Enter', true);
206
+
207
+ expect(calls).toEqual(['step:1', 'step:-1']);
208
+ });
209
+
210
+ test('a chorded Enter is left to the panel, which owns ⌘⏎', async () => {
211
+ await mount(findState({ matches: [{ start: 0, end: 5 }], currentIndex: 0 }));
212
+ const input = byLabel('Find');
213
+ await act(async () => {
214
+ input?.dispatchEvent(new globalThis.KeyboardEvent('keydown', { key: 'Enter', metaKey: true, bubbles: true }));
215
+ });
216
+
217
+ expect(calls).toEqual([]);
218
+ });
219
+
220
+ test('the stepper buttons are disabled with nothing to step to', async () => {
221
+ await mount(findState());
222
+
223
+ expect(buttonByLabel('Next match')?.disabled).toBe(true);
224
+ expect(buttonByLabel('Previous match')?.disabled).toBe(true);
225
+ });
226
+
227
+ test('the replace buttons are disabled with nothing to replace', async () => {
228
+ await mount(findState({ replaceOpen: true }));
229
+
230
+ expect(buttonByLabel('Replace')?.disabled).toBe(true);
231
+ expect(buttonByLabel('Replace all')?.disabled).toBe(true);
232
+ });
233
+
234
+ test('the count is announced and reads as the widget’s own status', async () => {
235
+ await mount(findState({ query: 'alpha', matches: [{ start: 0, end: 5 }, { start: 9, end: 14 }], currentIndex: 1 }));
236
+
237
+ const status = container.querySelector('[aria-live="polite"]');
238
+ expect(status?.textContent).toBe('2 of 2');
239
+ });
240
+
241
+ test('an invalid pattern is marked on the field, not only in the status', async () => {
242
+ // The user has to see WHICH field is wrong, not just that something is.
243
+ await mount(findState({ query: 'a(', invalid: true }));
244
+
245
+ expect(byLabel('Find')?.className).toContain('border-error');
246
+ });
247
+
248
+ test('the close button asks the hook to close', async () => {
249
+ await mount(findState());
250
+ await click(buttonByLabel('Close find'));
251
+
252
+ expect(calls).toEqual(['close']);
253
+ });
254
+
255
+ test('the expander toggles the replace row', async () => {
256
+ await mount(findState());
257
+ await click(byLabelPrefix('Show replace'));
258
+
259
+ expect(calls).toEqual(['toggleReplace']);
260
+ });
261
+ });
@@ -0,0 +1,219 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { useEffect, useRef } from 'preact/hooks';
7
+ import type { ComponentChildren, TargetedKeyboardEvent } from 'preact';
8
+ import {
9
+ ArrowDown,
10
+ ArrowUp,
11
+ CaseSensitive,
12
+ ChevronDown,
13
+ ChevronUp,
14
+ Regex,
15
+ Replace,
16
+ ReplaceAll,
17
+ WholeWord,
18
+ X,
19
+ } from 'lucide-preact';
20
+
21
+ import { EDITOR_KEY_BINDINGS, type EditorCommand } from '@/shared/lib/code/editor/keymap';
22
+ import { describeBinding } from '@/shared/lib/ui/key-binding';
23
+ import { isMacPlatform } from '@/shared/lib/util/platform';
24
+ import type { EditorFindState } from '@/client/hooks/editor/use-editor-find';
25
+ import type { FindOptions } from '@/shared/lib/code/editor/find';
26
+
27
+ const BUTTON_CLASS =
28
+ 'flex items-center justify-center p-1 rounded transition-colors cursor-pointer disabled:cursor-default disabled:opacity-40';
29
+
30
+ interface FindWidgetProps {
31
+ find: EditorFindState;
32
+ }
33
+
34
+ /** One of the three toggle buttons: `aria-pressed` is the state, the title the shortcut. */
35
+ function ToggleButton({
36
+ active,
37
+ title,
38
+ onToggle,
39
+ children,
40
+ }: {
41
+ active: boolean;
42
+ title: string;
43
+ onToggle: () => void;
44
+ children: ComponentChildren;
45
+ }) {
46
+ return (
47
+ <button
48
+ type="button"
49
+ onClick={onToggle}
50
+ className={`${BUTTON_CLASS} ${active ? 'bg-ink/10 text-ink' : 'text-ink/50 hover:text-ink hover:bg-ink/5'}`}
51
+ title={title}
52
+ aria-label={title}
53
+ aria-pressed={active}
54
+ >
55
+ {children}
56
+ </button>
57
+ );
58
+ }
59
+
60
+ /**
61
+ * The find/replace bar, pinned to the editor's top-right corner the way VS
62
+ * Code pins it.
63
+ *
64
+ * Two rows, not one: the query and its toggles, then the replacement and the
65
+ * two write actions. A single row would have to hide either the toggles or the
66
+ * replace buttons, and both are things this bar exists to expose.
67
+ *
68
+ * The counts are the honest ones — `N of M`, `M+` past the match cap, and a
69
+ * distinct message for a query that does not compile, because "no results" and
70
+ * "your regex is broken" are different answers and the user has to be able to
71
+ * tell them apart.
72
+ */
73
+ export function FindWidget({ find }: FindWidgetProps) {
74
+ const queryRef = useRef<HTMLInputElement | null>(null);
75
+ const isMac = isMacPlatform();
76
+
77
+ useEffect(() => {
78
+ queryRef.current?.focus();
79
+ queryRef.current?.select();
80
+ }, [find.focusRequest]);
81
+
82
+ const total = find.matches.length;
83
+ const status = find.invalid
84
+ ? 'Invalid pattern'
85
+ : total === 0
86
+ ? find.query
87
+ ? 'No results'
88
+ : ''
89
+ : `${find.currentIndex + 1} of ${total}${find.truncated ? '+' : ''}`;
90
+
91
+ const handleKeyDown = (e: TargetedKeyboardEvent<HTMLInputElement>) => {
92
+ // Escape is NOT handled here: the editor panel owns it on the capture
93
+ // phase (it has to work while focus is in the document too), and a second
94
+ // handler here would be unreachable behind that one.
95
+ // Enter / ⇧Enter step the matches while the focus stays in the field.
96
+ if (e.key === 'Enter' && !e.metaKey && !e.ctrlKey && !e.altKey) {
97
+ e.preventDefault();
98
+ find.step(e.shiftKey ? -1 : 1);
99
+ }
100
+ };
101
+
102
+ /** Tooltip text for a command: its palette label plus the chord it actually listens for. */
103
+ const withChord = (command: EditorCommand, label: string) => {
104
+ const binding = EDITOR_KEY_BINDINGS.find((entry) => entry.command === command);
105
+ return binding ? `${label} (${describeBinding(binding, isMac)})` : label;
106
+ };
107
+
108
+ const optionButton = (key: keyof FindOptions, label: string, command: EditorCommand, icon: ComponentChildren) => (
109
+ <ToggleButton active={find.options[key]} title={withChord(command, label)} onToggle={() => find.toggleOption(key)}>
110
+ {icon}
111
+ </ToggleButton>
112
+ );
113
+
114
+ return (
115
+ <div className="absolute top-2 right-4 z-30 flex flex-col gap-1 bg-paper border border-ink/15 rounded-md shadow-lg p-1.5 text-xs text-ink font-sans w-[330px]">
116
+ <div className="flex items-center gap-1">
117
+ <button
118
+ type="button"
119
+ onClick={find.toggleReplace}
120
+ className={`${BUTTON_CLASS} ${find.replaceOpen ? 'text-ink' : 'text-ink/50 hover:text-ink hover:bg-ink/5'}`}
121
+ title={find.replaceOpen ? 'Hide replace' : 'Show replace'}
122
+ aria-label={find.replaceOpen ? 'Hide replace' : 'Show replace'}
123
+ aria-pressed={find.replaceOpen}
124
+ >
125
+ {find.replaceOpen ? <ChevronUp size={13} /> : <ChevronDown size={13} />}
126
+ </button>
127
+
128
+ <input
129
+ ref={queryRef}
130
+ type="text"
131
+ value={find.query}
132
+ onInput={(e) => find.setQuery(e.currentTarget.value)}
133
+ onKeyDown={handleKeyDown}
134
+ placeholder="Find"
135
+ aria-label="Find"
136
+ className={`flex-1 min-w-0 bg-canvas border rounded px-2 py-1 font-mono text-xs focus:outline-none ${
137
+ find.invalid ? 'border-error text-error' : 'border-ink/20 focus:border-ink text-ink'
138
+ }`}
139
+ />
140
+
141
+ {optionButton('matchCase', 'Match case', 'toggleCaseSensitive', <CaseSensitive size={13} />)}
142
+ {optionButton('wholeWord', 'Match whole word', 'toggleWholeWord', <WholeWord size={13} />)}
143
+ {optionButton('isRegex', 'Use regular expression', 'toggleRegex', <Regex size={13} />)}
144
+
145
+ <span
146
+ className={`font-mono text-[10px] whitespace-nowrap min-w-[52px] text-right ${find.invalid ? 'text-error' : 'text-ink/50'}`}
147
+ aria-live="polite"
148
+ >
149
+ {status}
150
+ </span>
151
+
152
+ <button
153
+ type="button"
154
+ onClick={() => find.step(-1)}
155
+ disabled={total === 0}
156
+ className={`${BUTTON_CLASS} text-ink/50 hover:text-ink hover:bg-ink/5`}
157
+ title={withChord('previousMatch', 'Previous match')}
158
+ aria-label="Previous match"
159
+ >
160
+ <ArrowUp size={13} />
161
+ </button>
162
+ <button
163
+ type="button"
164
+ onClick={() => find.step(1)}
165
+ disabled={total === 0}
166
+ className={`${BUTTON_CLASS} text-ink/50 hover:text-ink hover:bg-ink/5`}
167
+ title={withChord('nextMatch', 'Next match')}
168
+ aria-label="Next match"
169
+ >
170
+ <ArrowDown size={13} />
171
+ </button>
172
+ <button
173
+ type="button"
174
+ onClick={find.close}
175
+ className={`${BUTTON_CLASS} text-ink/50 hover:text-ink hover:bg-ink/5`}
176
+ title="Close find (Escape)"
177
+ aria-label="Close find"
178
+ >
179
+ <X size={13} />
180
+ </button>
181
+ </div>
182
+
183
+ {find.replaceOpen && (
184
+ <div className="flex items-center gap-1">
185
+ <span className="w-[22px]" aria-hidden="true" />
186
+ <input
187
+ type="text"
188
+ value={find.replacement}
189
+ onInput={(e) => find.setReplacement(e.currentTarget.value)}
190
+ onKeyDown={handleKeyDown}
191
+ placeholder="Replace"
192
+ aria-label="Replace"
193
+ className="flex-1 min-w-0 bg-canvas border border-ink/20 rounded px-2 py-1 font-mono text-xs text-ink focus:outline-none focus:border-ink"
194
+ />
195
+ <button
196
+ type="button"
197
+ onClick={find.replaceCurrent}
198
+ disabled={total === 0}
199
+ className={`${BUTTON_CLASS} text-ink/50 hover:text-ink hover:bg-ink/5`}
200
+ title={withChord('replaceOne', 'Replace')}
201
+ aria-label="Replace"
202
+ >
203
+ <Replace size={13} />
204
+ </button>
205
+ <button
206
+ type="button"
207
+ onClick={find.replaceAll}
208
+ disabled={total === 0}
209
+ className={`${BUTTON_CLASS} text-ink/50 hover:text-ink hover:bg-ink/5`}
210
+ title={withChord('replaceAll', 'Replace all')}
211
+ aria-label="Replace all"
212
+ >
213
+ <ReplaceAll size={13} />
214
+ </button>
215
+ </div>
216
+ )}
217
+ </div>
218
+ );
219
+ }
@@ -74,7 +74,7 @@ export function EditorTabs({ openedFiles, activeFileId, onSelectFile, onCloseFil
74
74
  title={file.path}
75
75
  >
76
76
  {isDiff ? (
77
- <GitCompare size={14} className={isActive ? 'text-blue-600 dark:text-blue-400 flex-shrink-0' : 'opacity-60 flex-shrink-0'} />
77
+ <GitCompare size={14} className={isActive ? 'text-info flex-shrink-0' : 'text-info/70 flex-shrink-0'} />
78
78
  ) : (
79
79
  <FileIcon name={file.name} size={14} className={isActive ? 'flex-shrink-0' : 'opacity-60 flex-shrink-0'} />
80
80
  )}
@@ -123,7 +123,7 @@ export function EditorTabs({ openedFiles, activeFileId, onSelectFile, onCloseFil
123
123
  className="flex items-center space-x-2 px-3 py-1.5 hover:bg-ink/5 cursor-pointer group"
124
124
  >
125
125
  {file.isDiff ? (
126
- <GitCompare size={14} className="text-blue-600 opacity-70" />
126
+ <GitCompare size={14} className="text-info/80" />
127
127
  ) : (
128
128
  <FileIcon name={file.name} size={14} className="opacity-60" />
129
129
  )}