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,250 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * Everything a file-tree row does that is not rendering: the context menu's
8
+ * action dispatch, the create/rename/delete dialogs and their server calls.
9
+ *
10
+ * Extracted from the row component because it is a stateful cluster with one
11
+ * shared dependency (`/api/fs/action` and the fetcher that submits to it), and
12
+ * because the row is at the repository's line ceiling. The component keeps the
13
+ * markup and the handlers that need its own DOM node; this keeps the behavior.
14
+ */
15
+
16
+ import { useEffect, useRef, useState } from 'preact/hooks';
17
+ import type { FormEvent, RefObject } from 'preact/compat';
18
+ import { useFetcher } from '@/client/lib/router/fetcher';
19
+ import { toAbsolutePath } from '@/shared/lib/fs/paths';
20
+ import { copyToClipboard } from '@/client/hooks/ui/clipboard';
21
+
22
+ /** The shape `/api/fs/action` answers with, as far as a row reads it. */
23
+ interface FsActionResponse {
24
+ success?: boolean;
25
+ /** Present on the git-history payload, which the history modal reads. */
26
+ type?: string;
27
+ /** Relative path of a freshly created file. */
28
+ path?: string;
29
+ error?: string;
30
+ }
31
+
32
+ export interface FileActionTarget {
33
+ path: string;
34
+ name: string;
35
+ /** Absolute base dir of the listing, anchoring Copy Path. */
36
+ basePath?: string;
37
+ rootPath?: string;
38
+ repo?: string;
39
+ }
40
+
41
+ export interface FileActionDeps {
42
+ file: FileActionTarget;
43
+ /** Which diff to open, when the row is a changed file. */
44
+ diff?: { staged?: boolean; status?: string };
45
+ onOpenFile?: (file: { path: string; name: string; root?: string; repo?: string }) => void;
46
+ onActionComplete: () => void;
47
+ }
48
+
49
+ export interface FileActions {
50
+ contextMenu: { x: number; y: number } | null;
51
+ contextMenuRef: RefObject<HTMLDivElement>;
52
+ openContextMenu: (x: number, y: number) => void;
53
+ closeContextMenu: () => void;
54
+ showRenameModal: boolean;
55
+ showDeleteModal: boolean;
56
+ showHistoryModal: boolean;
57
+ showCreateModal: boolean;
58
+ /** The server's refusal for the create request, rendered inside its dialog. */
59
+ createError: string | null;
60
+ createName: string;
61
+ setCreateName: (value: string) => void;
62
+ renameValue: string;
63
+ setRenameValue: (value: string) => void;
64
+ /** True while a request is in flight; every dialog's buttons read it. */
65
+ busy: boolean;
66
+ /** The fetcher's own state/data, for the history modal. */
67
+ fetcherState: string;
68
+ fetcherData: FsActionResponse | undefined;
69
+ handleAction: (actionType: string) => void;
70
+ submitCreate: (e: FormEvent) => void;
71
+ submitRename: (e: FormEvent) => void;
72
+ submitDelete: () => void;
73
+ closeCreate: () => void;
74
+ closeDelete: () => void;
75
+ closeRename: () => void;
76
+ closeHistory: () => void;
77
+ }
78
+
79
+ export function useFileActions({ file, diff, onOpenFile, onActionComplete }: FileActionDeps): FileActions {
80
+ const actionFetcher = useFetcher<FsActionResponse>();
81
+ const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null);
82
+ const contextMenuRef = useRef<HTMLDivElement>(null);
83
+ const [showRenameModal, setShowRenameModal] = useState(false);
84
+ const [showDeleteModal, setShowDeleteModal] = useState(false);
85
+ const [showHistoryModal, setShowHistoryModal] = useState(false);
86
+ const [showCreateModal, setShowCreateModal] = useState(false);
87
+ const [createName, setCreateName] = useState('');
88
+ const [createError, setCreateError] = useState<string | null>(null);
89
+ const [renameValue, setRenameValue] = useState(file.path);
90
+ /** Which dialog the in-flight request belongs to, so its result is routed. */
91
+ const pendingActionRef = useRef<'create' | null>(null);
92
+
93
+ const repo = file.repo;
94
+ const rootPath = file.rootPath;
95
+
96
+ /**
97
+ * The payload already acted on.
98
+ *
99
+ * The fetcher keeps `data` until the next request, and the callbacks below
100
+ * (`onActionComplete` → the panel's refresh → a re-render with a fresh
101
+ * callback identity) would otherwise re-run this effect for the SAME
102
+ * response forever: an unguarded effect here is an infinite refresh loop that
103
+ * pins the tab the moment a file is created.
104
+ */
105
+ const handledRef = useRef<FsActionResponse | null>(null);
106
+
107
+ useEffect(() => {
108
+ if (actionFetcher.state !== 'idle' || !actionFetcher.data) return;
109
+ const data = actionFetcher.data;
110
+ if (handledRef.current === data) return;
111
+ handledRef.current = data;
112
+ // The fetcher parses the body whatever the status, so a refusal arrives as
113
+ // `data.error` rather than as a throw — which is what lets the create
114
+ // dialog stay open and put the reason beside the field.
115
+ if (pendingActionRef.current === 'create') {
116
+ pendingActionRef.current = null;
117
+ if (data.success && data.path) {
118
+ // The user asked for a file to work on, so it opens: closing the dialog
119
+ // and refreshing the tree alone would leave them hunting for the empty
120
+ // row they just created.
121
+ setShowCreateModal(false);
122
+ setCreateName('');
123
+ setCreateError(null);
124
+ onActionComplete();
125
+ onOpenFile?.({
126
+ path: data.path,
127
+ name: data.path.split('/').pop() || data.path,
128
+ root: rootPath,
129
+ repo: repo || '.',
130
+ });
131
+ } else {
132
+ setCreateError(data.error || 'Could not create the file');
133
+ }
134
+ return;
135
+ }
136
+ if (data.success && !data.type) {
137
+ setShowRenameModal(false);
138
+ setShowDeleteModal(false);
139
+ onActionComplete();
140
+ }
141
+ // eslint-disable-next-line react-hooks/exhaustive-deps
142
+ }, [actionFetcher.state, actionFetcher.data]);
143
+
144
+ const submitAction = (formData: FormData) => {
145
+ if (repo && repo !== '.') formData.append('repo', repo);
146
+ if (rootPath) formData.append('root', rootPath);
147
+ void actionFetcher.submit(formData, { method: 'post', action: '/api/fs/action' });
148
+ };
149
+
150
+ const openFile = () => {
151
+ onOpenFile?.({ path: file.path, name: file.name, root: rootPath, repo: repo || '.' });
152
+ };
153
+
154
+ const handleAction = (actionType: string) => {
155
+ setContextMenu(null);
156
+ if (actionType === 'view') {
157
+ openFile();
158
+ } else if (actionType === 'diff') {
159
+ window.dispatchEvent(new CustomEvent('omp:open-diff', {
160
+ detail: {
161
+ file: file.path,
162
+ staged: diff?.staged,
163
+ status: diff?.status || 'M',
164
+ repo: repo || '.',
165
+ root: rootPath,
166
+ },
167
+ }));
168
+ } else if (actionType === 'explorer') {
169
+ const fd = new FormData();
170
+ fd.append('actionType', 'open_explorer');
171
+ fd.append('path', file.path);
172
+ submitAction(fd);
173
+ } else if (actionType === 'copy_path') {
174
+ void copyToClipboard(toAbsolutePath(file.basePath, file.path));
175
+ } else if (actionType === 'copy_relative') {
176
+ void copyToClipboard(file.path);
177
+ } else if (actionType === 'history') {
178
+ setShowHistoryModal(true);
179
+ const fd = new FormData();
180
+ fd.append('actionType', 'git_history');
181
+ fd.append('path', file.path);
182
+ submitAction(fd);
183
+ } else if (actionType === 'rename') {
184
+ setRenameValue(file.path);
185
+ setShowRenameModal(true);
186
+ } else if (actionType === 'new_file') {
187
+ setCreateName('');
188
+ setCreateError(null);
189
+ setShowCreateModal(true);
190
+ } else if (actionType === 'delete') {
191
+ setShowDeleteModal(true);
192
+ }
193
+ };
194
+
195
+ const submitCreate = (e: FormEvent) => {
196
+ e.preventDefault();
197
+ const name = createName.trim();
198
+ if (!name) return;
199
+ setCreateError(null);
200
+ pendingActionRef.current = 'create';
201
+ const fd = new FormData();
202
+ fd.append('actionType', 'create');
203
+ fd.append('path', file.path);
204
+ fd.append('name', name);
205
+ submitAction(fd);
206
+ };
207
+
208
+ const submitRename = (e: FormEvent) => {
209
+ e.preventDefault();
210
+ const fd = new FormData();
211
+ fd.append('actionType', 'rename');
212
+ fd.append('path', file.path);
213
+ fd.append('newPath', renameValue);
214
+ submitAction(fd);
215
+ };
216
+
217
+ const submitDelete = () => {
218
+ const fd = new FormData();
219
+ fd.append('actionType', 'delete');
220
+ fd.append('path', file.path);
221
+ submitAction(fd);
222
+ };
223
+
224
+ return {
225
+ contextMenu,
226
+ contextMenuRef,
227
+ openContextMenu: (x, y) => setContextMenu({ x, y }),
228
+ closeContextMenu: () => setContextMenu(null),
229
+ showRenameModal,
230
+ showDeleteModal,
231
+ showHistoryModal,
232
+ showCreateModal,
233
+ createError,
234
+ createName,
235
+ setCreateName,
236
+ renameValue,
237
+ setRenameValue,
238
+ busy: actionFetcher.state !== 'idle',
239
+ fetcherState: actionFetcher.state,
240
+ fetcherData: actionFetcher.data,
241
+ handleAction,
242
+ submitCreate,
243
+ submitRename,
244
+ submitDelete,
245
+ closeCreate: () => setShowCreateModal(false),
246
+ closeDelete: () => setShowDeleteModal(false),
247
+ closeRename: () => setShowRenameModal(false),
248
+ closeHistory: () => setShowHistoryModal(false),
249
+ };
250
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * Reads a session's live todo list for the right-panel view.
8
+ *
9
+ * The list belongs to omp and its only durable home is the session transcript,
10
+ * so this hook is a reader: it fetches `GET /api/omp/session-todos` and re-reads
11
+ * on two triggers —
12
+ *
13
+ * - a poll (`usePanelRefresh`, paused while hidden), which covers a list the
14
+ * agent changed while this client was not the one driving it;
15
+ * - `omp:session-updated`, which the chat dispatches at every turn boundary
16
+ * and therefore right after each `todo` tool call, so the panel follows a
17
+ * running agent without waiting out the poll.
18
+ *
19
+ * The event path is throttled: one run emits the signal several times around a
20
+ * single tool call, and the read is a disk parse. A read already in flight is
21
+ * not cancelled by a newer trigger — the server answers an unchanged session
22
+ * from a stat, so the extra request is cheap, and dropping the newer one would
23
+ * leave the panel showing the list from before the call that just finished.
24
+ */
25
+
26
+ import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
27
+ import { useChamberEvent } from '@/client/hooks/ui/window-event';
28
+ import { usePanelRefresh } from '@/client/hooks/workspace/panel-refresh';
29
+ import { TODO_REFRESH_EVENT_THROTTLE_MS } from '@/shared/lib/workspace/refresh-cadence';
30
+ import type { SessionTodosPayload } from '@/shared/types/todo';
31
+
32
+ export interface SessionTodosState {
33
+ data: SessionTodosPayload | null;
34
+ isLoading: boolean;
35
+ /** Set when the last read failed; the previous payload is kept meanwhile. */
36
+ error: string | null;
37
+ reload: () => void;
38
+ }
39
+
40
+ export function useSessionTodos(sessionId: string | null, enabled: boolean): SessionTodosState {
41
+ const [data, setData] = useState<SessionTodosPayload | null>(null);
42
+ const [isLoading, setIsLoading] = useState(false);
43
+ const [error, setError] = useState<string | null>(null);
44
+
45
+ // Latest session id for the fetcher, so switching sessions does not have to
46
+ // rebuild the callback (and with it the poll interval) on every render.
47
+ const sessionIdRef = useRef(sessionId);
48
+ sessionIdRef.current = sessionId;
49
+ // Guards against a response for a session the user has already left being
50
+ // written into the panel that now shows a different one.
51
+ const requestRef = useRef(0);
52
+
53
+ const load = useCallback(() => {
54
+ const id = sessionIdRef.current;
55
+ if (!id) {
56
+ setData(null);
57
+ return;
58
+ }
59
+ const request = ++requestRef.current;
60
+ setIsLoading(true);
61
+ fetch(`/api/omp/session-todos?sessionId=${encodeURIComponent(id)}`)
62
+ .then((response) => {
63
+ if (!response.ok) throw new Error(`Todo request failed (${response.status})`);
64
+ return response.json() as Promise<SessionTodosPayload>;
65
+ })
66
+ .then((payload) => {
67
+ if (requestRef.current !== request) return;
68
+ setData(payload);
69
+ setError(null);
70
+ })
71
+ .catch((err: unknown) => {
72
+ if (requestRef.current !== request) return;
73
+ setError(err instanceof Error ? err.message : 'Failed to load todos');
74
+ })
75
+ .finally(() => {
76
+ if (requestRef.current === request) setIsLoading(false);
77
+ });
78
+ }, []);
79
+
80
+ // A session switch must not render the previous session's list under the new
81
+ // title: drop the payload and re-read immediately.
82
+ useEffect(() => {
83
+ setData(null);
84
+ setError(null);
85
+ if (sessionId) load();
86
+ }, [sessionId, load]);
87
+
88
+ usePanelRefresh(load, enabled);
89
+
90
+ const throttleRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
91
+ useChamberEvent('omp:session-updated', () => {
92
+ if (!enabled || throttleRef.current !== undefined) return;
93
+ throttleRef.current = setTimeout(() => {
94
+ throttleRef.current = undefined;
95
+ load();
96
+ }, TODO_REFRESH_EVENT_THROTTLE_MS);
97
+ });
98
+
99
+ useEffect(() => () => clearTimeout(throttleRef.current), []);
100
+
101
+ return { data, isLoading, error, reload: load };
102
+ }
@@ -4,7 +4,12 @@
4
4
  */
5
5
 
6
6
  /**
7
- * Browser replacement for `elkjs`, aliased in `rsbuild.config.ts`.
7
+ * Browser replacement for `elkjs`, aliased through `tsconfig.json` `paths`.
8
+ *
9
+ * The alias moved there when the client build moved to Bun: Bun's bundler does
10
+ * not read an `alias` option (it is absent from `Bun.build`'s config and from
11
+ * `bun-types`; passing one is silently ignored — verified), while `paths` it
12
+ * honours for both internal aliases and package specifiers.
8
13
  *
9
14
  * Mermaid 12 changed the global `layout` default from `dagre` to `elk`, so every
10
15
  * flowchart pulled in `elkjs/lib/elk.bundled.js` whether the diagram asked for
@@ -246,6 +246,36 @@
246
246
  * The selector keys off the variant attribute, not a list of theme ids. */
247
247
  .shiki span { color: var(--shiki-light); }
248
248
  html[data-theme-variant="dark"] .shiki span { color: var(--shiki-dark); }
249
+
250
+ /* Find matches, painted inside the token spans the highlighter emits. An
251
+ * inline <mark> colours a background without moving a glyph, which is what
252
+ * keeps the transparent <textarea> above aligned with this layer — a block or
253
+ * a padding would shift the text under the caret. `color: inherit` is required:
254
+ * a <mark> would otherwise take the UA's own black-on-yellow, ignoring the
255
+ * dual-theme token colour it sits inside.
256
+ *
257
+ * No `border-radius`, and the current match is marked with TOP/BOTTOM borders
258
+ * rather than an outline: a match spanning several tokens (`const alpha` is
259
+ * three) is emitted as one <mark> per token, so any per-fragment decoration
260
+ * that draws vertical edges — radius, outline, left/right border — paints a
261
+ * seam in the middle of the match. Horizontal edges join seamlessly. */
262
+ .shiki .find-match {
263
+ color: inherit;
264
+ background: color-mix(in srgb, var(--theme-warning) 32%, transparent);
265
+ }
266
+ /* An extra ⌘D selection: same geometry as a find match, different ink. The
267
+ * caret can only live in the primary range, so the two must not look alike —
268
+ * the user has to see where their keystrokes will land. */
269
+ .shiki .find-match[data-occurrence] {
270
+ background: color-mix(in srgb, var(--theme-info) 30%, transparent);
271
+ border-top: 1px solid color-mix(in srgb, var(--theme-info) 55%, transparent);
272
+ border-bottom: 1px solid color-mix(in srgb, var(--theme-info) 55%, transparent);
273
+ }
274
+ .shiki .find-match[data-find-current] {
275
+ background: color-mix(in srgb, var(--theme-warning) 55%, transparent);
276
+ border-top: 1px solid color-mix(in srgb, var(--theme-warning) 75%, transparent);
277
+ border-bottom: 1px solid color-mix(in srgb, var(--theme-warning) 75%, transparent);
278
+ }
249
279
  .prose-content .katex-display { overflow-x: auto; overflow-y: hidden; }
250
280
 
251
281
  /* Math placeholder: `marked.ts` emits a `.math-pending` span and KaTeX is
@@ -1,13 +1,16 @@
1
1
  import { Elysia } from 'elysia';
2
2
  import pkg from '@/../package.json';
3
+ import shell from '@/../index.html';
3
4
  import { apiRoutes } from '@/server/routes';
4
5
  import { ssrRoutes } from '@/server/plugins/ssr';
6
+ import { setShellSource } from '@/server/plugins/shell.server';
5
7
  import { getDatabasePath } from '@/server/db.server';
6
8
  import { fetchOmpRegistrySnapshot } from '@/server/lib/models/provider-registry.server';
7
9
  import { ompStartupError, ompStartupLogLines } from '@/server/lib/omp/core/startup';
8
10
  import { readInstanceRecord, removeInstanceRecord, writeInstanceRecord } from '@/server/lib/lifecycle/instance';
9
11
  import { resolveLaunchMode } from '@/server/lib/lifecycle/launch-mode';
10
12
  import { acquirePortLock, claimPort, PortInUseError } from '@/server/lib/lifecycle/port-guard';
13
+ import { SHELL_ROUTE } from '@/server/lib/lifecycle/shell-route';
11
14
  import { isMockMode } from '@/server/mock.server';
12
15
 
13
16
  // Refuse to run without a resolvable omp binary — before the listener opens and
@@ -39,7 +42,15 @@ if (!lock) {
39
42
  process.exit(1);
40
43
  }
41
44
 
42
- const app = new Elysia().use(apiRoutes).use(ssrRoutes);
45
+ // `serve.routes` is where the HTML shell lives. Elysia merges its own static
46
+ // routes into that same table (its Bun adapter passes `serve` straight to
47
+ // `Bun.serve`), and Bun's table wins for the paths it declares — so `/_shell`
48
+ // renders the bundle while every other path falls through to `fetch`. The
49
+ // WebSocket routes keep working: Elysia composes its `websocket` handler
50
+ // alongside this object.
51
+ const app = new Elysia({ serve: { routes: { [SHELL_ROUTE]: shell } } })
52
+ .use(apiRoutes)
53
+ .use(ssrRoutes);
43
54
 
44
55
  try {
45
56
  await claimPort({
@@ -50,7 +61,14 @@ try {
50
61
  // `reusePort: true`, which lets a second server bind a port already in
51
62
  // use and then sit invisible while the first-bound socket takes every
52
63
  // connection.
53
- await app.listen({ port, hostname: host, reusePort: false });
64
+ app.listen({ port, hostname: host, reusePort: false });
65
+ if (!app.server) throw new Error('Bun.serve returned no listener');
66
+ // The shell is rendered by asking this same listener for it: Bun renders
67
+ // an HTML route only while serving, and there is no in-process API for an
68
+ // `HTMLBundle` (`new Response(bundle)` is "[object HTMLBundle]",
69
+ // `app.handle` answers 404). Handing the listener over is what makes
70
+ // `renderShell()` possible.
71
+ setShellSource(app.server);
54
72
  },
55
73
  });
56
74
  } catch (error) {
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * The font stylesheet, assembled at request time.
8
+ *
9
+ * `@font-face` blocks cannot live in the bundle: Bun's CSS loader resolves every
10
+ * local `url()`, and a font is always a local file — so a bundled face is either
11
+ * a base64 blob or an absolute filesystem path. `lib/bundler/css.ts` lifts each
12
+ * block out of its stylesheet and rewrites the urls to `<FONT_ROUTE_PREFIX><basename>`;
13
+ * this module serves the result.
14
+ *
15
+ * Two jobs, one route each:
16
+ *
17
+ * - `FONT_STYLESHEET_ROUTE` (`/fonts.css`) concatenates the extracted faces.
18
+ * Read per request rather than cached: it is a handful of kilobytes, and a
19
+ * cache is what would keep a stale face alive across an edit to a stylesheet.
20
+ * A missing directory is an empty stylesheet, not a 404 — the shell links this
21
+ * unconditionally.
22
+ *
23
+ * - `FONT_ROUTE_PREFIX` (`/fonts/<basename>`) serves the file itself from the
24
+ * installed packages. The basename is the whole key, so the lookup is an index
25
+ * rather than a path built from the request; see `fonts.server.ts`.
26
+ */
27
+
28
+ import { join } from 'path';
29
+ import { Glob } from 'bun';
30
+
31
+ import { packageRoot, resolveFontFile } from '@/server/lib/assets/fonts.server';
32
+
33
+ /** Directory holding one extracted face file per source stylesheet. */
34
+ export const FONT_FACE_DIR = join('.ompchamber-build', 'font-faces');
35
+
36
+ /** The route the assembled stylesheet is served on. */
37
+ export const FONT_STYLESHEET_ROUTE = '/fonts.css';
38
+
39
+ const FONT_MIME: Record<string, string> = {
40
+ '.woff2': 'font/woff2',
41
+ '.woff': 'font/woff',
42
+ '.ttf': 'font/ttf',
43
+ '.otf': 'font/otf',
44
+ };
45
+
46
+ const STYLESHEET_HEADERS = {
47
+ 'content-type': 'text/css; charset=utf-8',
48
+ 'cache-control': 'no-store',
49
+ } as const;
50
+
51
+ /** Every extracted face file, concatenated in a stable order. */
52
+ export async function fontStylesheet(): Promise<string> {
53
+ const dir = join(packageRoot(), FONT_FACE_DIR);
54
+ const glob = new Glob('*.css');
55
+ const files: string[] = [];
56
+ try {
57
+ for await (const file of glob.scan({ cwd: dir, onlyFiles: true })) files.push(file);
58
+ } catch {
59
+ return '';
60
+ }
61
+ // Sorted so the stylesheet is byte-stable between builds: the browser caches
62
+ // on the ETag, and an unstable order would invalidate it for no change.
63
+ files.sort();
64
+ const parts = await Promise.all(files.map((file) => Bun.file(join(dir, file)).text()));
65
+ return parts.join('\n');
66
+ }
67
+
68
+ /** The `/fonts.css` response. */
69
+ export async function serveFontStylesheet(): Promise<Response> {
70
+ return new Response(await fontStylesheet(), { headers: STYLESHEET_HEADERS });
71
+ }
72
+
73
+ /** The `/fonts/<basename>` response, or null when no package ships that file. */
74
+ export async function serveFontFile(name: string): Promise<Response | null> {
75
+ const file = await resolveFontFile(name);
76
+ if (!file) return null;
77
+ const ext = name.slice(name.lastIndexOf('.')).toLowerCase();
78
+ return new Response(Bun.file(file), {
79
+ headers: {
80
+ 'content-type': FONT_MIME[ext] ?? 'application/octet-stream',
81
+ // A font's bytes never change under a fixed basename inside a pinned
82
+ // dependency, so this is the one asset that can be cached hard.
83
+ 'cache-control': 'public, max-age=31536000, immutable',
84
+ },
85
+ });
86
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * Font files served straight from the installed packages.
8
+ *
9
+ * The client stylesheet reaches its `@font-face` sources through the bundler's
10
+ * CSS loader (`lib/bundler/css.ts`), which rewrites every one of them to
11
+ * `<FONT_ROUTE_PREFIX><basename>`. Nothing copies them into the repository: a
12
+ * font is addressed by its own basename, and this module resolves that basename
13
+ * back to the package that ships it.
14
+ *
15
+ * A directory index rather than a path built from the request, because the
16
+ * request must never choose a directory. The index is built once, lazily, by
17
+ * walking the vendor font directories, and a basename that is not in it is a
18
+ * 404 rather than a lookup somewhere else.
19
+ *
20
+ * The search roots are not just `process.cwd()`. An installed package runs the
21
+ * AOT bundle from `dist/client`, and a `serve --prod` started from another
22
+ * directory has neither `node_modules` nor the source tree under it — so the
23
+ * roots are every directory from the running module up to the filesystem root,
24
+ * plus the cwd. Whichever one holds `node_modules` is the package root.
25
+ */
26
+
27
+ import { existsSync } from 'fs';
28
+ import { basename, dirname, join, parse, resolve } from 'path';
29
+ import { Glob } from 'bun';
30
+
31
+ /** Path prefix every rewritten font url carries. */
32
+ export const FONT_ROUTE_PREFIX = '/fonts/';
33
+
34
+ /** Where the fonts live, relative to a package root. */
35
+ const FONT_SOURCES = [
36
+ 'node_modules/katex/dist/fonts',
37
+ 'node_modules/@fontsource/fira-code/files',
38
+ ] as const;
39
+
40
+ /**
41
+ * Directories a package root may be found under, nearest first.
42
+ *
43
+ * `import.meta.dir` is the running file's directory — the repo root for the
44
+ * TypeScript entry, `dist/client` for the AOT bundle — and walking up from it
45
+ * finds `node_modules` in both. The cwd is included because a source checkout
46
+ * run from elsewhere still resolves its dependencies through it.
47
+ */
48
+ function searchRoots(): string[] {
49
+ const roots: string[] = [];
50
+ const seen = new Set<string>();
51
+ const add = (dir: string) => {
52
+ if (dir && !seen.has(dir)) {
53
+ seen.add(dir);
54
+ roots.push(dir);
55
+ }
56
+ };
57
+
58
+ let dir = import.meta.dir;
59
+ const { root } = parse(dir);
60
+ for (;;) {
61
+ add(dir);
62
+ if (dir === root) break;
63
+ dir = dirname(dir);
64
+ }
65
+ add(resolve(process.cwd()));
66
+ return roots;
67
+ }
68
+
69
+ /** Basename -> absolute path. Built once; the packages do not change at runtime. */
70
+ let index: Record<string, string> | null = null;
71
+
72
+ /**
73
+ * The package root — the first search root that holds `node_modules`.
74
+ *
75
+ * Both ends of the build need the same answer: the plugin writes the extracted
76
+ * face files here and the server reads them back, and an AOT bundle runs from
77
+ * `dist/client` while a source checkout runs from the repo. Falling back to the
78
+ * cwd keeps a checkout with no `node_modules` (a publish dry-run) working.
79
+ */
80
+ export function packageRoot(): string {
81
+ for (const root of searchRoots()) {
82
+ // `existsSync` rather than `Bun.file().exists()`: the latter is async and
83
+ // file-only, and `node_modules` is a directory.
84
+ if (existsSync(join(root, 'node_modules'))) return root;
85
+ }
86
+ return process.cwd();
87
+ }
88
+
89
+ async function buildIndex(): Promise<Record<string, string>> {
90
+ const found: Record<string, string> = {};
91
+ const glob = new Glob('*.{woff2,woff,ttf,otf}');
92
+ for (const root of searchRoots()) {
93
+ for (const dir of FONT_SOURCES) {
94
+ const from = join(root, dir);
95
+ try {
96
+ for await (const file of glob.scan({ cwd: from, onlyFiles: true })) {
97
+ const name = basename(file);
98
+ // Nearest root wins: a checkout's own node_modules is what its build
99
+ // was made against, so it must not lose to a parent's copy.
100
+ found[name] ??= join(from, file);
101
+ }
102
+ } catch {
103
+ // A root without these packages contributes nothing; the CSS that
104
+ // referenced a missing font will 404, which is the honest outcome.
105
+ }
106
+ }
107
+ }
108
+ return found;
109
+ }
110
+
111
+ /** The font file for a basename, or null when nothing ships it. */
112
+ export async function resolveFontFile(name: string): Promise<string | null> {
113
+ // A basename only: a request for `../` or a nested path is not a font.
114
+ if (!name || name !== basename(name)) return null;
115
+ index ??= await buildIndex();
116
+ return index[name] ?? null;
117
+ }