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
@@ -1,14 +1,11 @@
1
- import { useEffect, useRef, useState } from 'preact/hooks';
2
- import type { FormEvent } from 'preact/compat';
1
+ import { useEffect, useState } from 'preact/hooks';
3
2
  import { ChevronDown, ChevronRight } from 'lucide-preact';
4
- import { useFetcher } from '@/client/lib/router/fetcher';
5
3
  import { FileIcon } from '@/client/components/common/file-icon';
6
- import { FileContextMenu, FileDeleteModal, FileHistoryModal, FileRenameModal } from '@/client/components/workspace/file-explorer/Modals';
4
+ import { FileContextMenu, FileCreateModal, FileDeleteModal, FileHistoryModal, FileRenameModal } from '@/client/components/workspace/file-explorer/Modals';
7
5
  import { getGitStatusInfo, type FolderGitStatusInfo } from '@/shared/lib/fs/git-status';
8
- import { toAbsolutePath } from '@/shared/lib/fs/paths';
9
- import { copyToClipboard } from '@/client/hooks/ui/clipboard';
10
- import type { GitChange } from '@/shared/types/git';
6
+ import { useFileActions } from '@/client/hooks/workspace/file-tree-actions';
11
7
  import { useOnClickOutside } from '@/client/hooks/ui/on-click-outside';
8
+ import type { GitChange } from '@/shared/types/git';
12
9
 
13
10
  interface FileTreeItemProps {
14
11
  file: any;
@@ -41,16 +38,8 @@ export function FileTreeItem({
41
38
  const isFolder = file.type === 'folder';
42
39
  const [isOpen, setIsOpen] = useState(false);
43
40
  const [isLoadingChildren, setIsLoadingChildren] = useState(false);
44
- const actionFetcher = useFetcher<any>();
41
+ /** Portals need a real document; the first client render is the gate. */
45
42
  const [mounted, setMounted] = useState(false);
46
-
47
- const [contextMenu, setContextMenu] = useState<{ x: number, y: number } | null>(null);
48
- const contextMenuRef = useRef<HTMLDivElement>(null);
49
- const [showRenameModal, setShowRenameModal] = useState(false);
50
- const [showDeleteModal, setShowDeleteModal] = useState(false);
51
- const [showHistoryModal, setShowHistoryModal] = useState(false);
52
- const [renameValue, setRenameValue] = useState(file.path);
53
-
54
43
  const children = Array.isArray(file.children) ? file.children : [];
55
44
 
56
45
  const normalizedPath = (file.path || '').replace(/^\/+/, '');
@@ -80,19 +69,13 @@ export function FileTreeItem({
80
69
  }
81
70
  }, [isFolder, actualIsOpen, file.children, file.path, onLoadChildren]);
82
71
 
83
- useOnClickOutside(contextMenuRef, () => setContextMenu(null));
84
-
85
- useEffect(() => {
86
- if (actionFetcher.state === 'idle' && actionFetcher.data) {
87
- if (actionFetcher.data.success) {
88
- if (!actionFetcher.data.type) {
89
- setShowRenameModal(false);
90
- setShowDeleteModal(false);
91
- onActionComplete();
92
- }
93
- }
94
- }
95
- }, [actionFetcher.state, actionFetcher.data]);
72
+ const actions = useFileActions({
73
+ file: { path: file.path, name: file.name, basePath, rootPath, repo },
74
+ diff: { staged: gitChange?.staged || (gitChange?.status ? gitChange.status[0] !== ' ' && gitChange.status[0] !== '?' : false), status: gitStatusInfo?.charStatus },
75
+ onOpenFile,
76
+ onActionComplete,
77
+ });
78
+ useOnClickOutside(actions.contextMenuRef, actions.closeContextMenu);
96
79
 
97
80
  const handleToggle = (e: globalThis.MouseEvent) => {
98
81
  e.stopPropagation();
@@ -121,72 +104,7 @@ export function FileTreeItem({
121
104
  const handleContextMenu = (e: globalThis.MouseEvent) => {
122
105
  e.preventDefault();
123
106
  e.stopPropagation();
124
- setContextMenu({ x: e.clientX, y: e.clientY });
125
- };
126
-
127
- const submitAction = (formData: FormData) => {
128
- if (repo && repo !== '.') formData.append('repo', repo);
129
- if (rootPath) formData.append('root', rootPath);
130
- actionFetcher.submit(formData, { method: 'post', action: '/api/fs/action' });
131
- };
132
-
133
- const handleAction = (actionType: string) => {
134
- setContextMenu(null);
135
- if (actionType === 'view') {
136
- if (onOpenFile) {
137
- onOpenFile({
138
- ...file,
139
- root: rootPath,
140
- repo: repo || '.',
141
- });
142
- }
143
- } else if (actionType === 'diff') {
144
- window.dispatchEvent(new CustomEvent('omp:open-diff', {
145
- detail: {
146
- file: file.path,
147
- staged: gitChange?.staged || (gitChange?.status && gitChange.status[0] !== ' ' && gitChange.status[0] !== '?'),
148
- status: gitStatusInfo?.charStatus || 'M',
149
- repo: repo || '.',
150
- root: rootPath,
151
- }
152
- }));
153
- } else if (actionType === 'explorer') {
154
- const fd = new FormData();
155
- fd.append('actionType', 'open_explorer');
156
- fd.append('path', file.path);
157
- submitAction(fd);
158
- } else if (actionType === 'copy_path') {
159
- void copyToClipboard(toAbsolutePath(basePath, file.path));
160
- } else if (actionType === 'copy_relative') {
161
- void copyToClipboard(file.path);
162
- } else if (actionType === 'history') {
163
- setShowHistoryModal(true);
164
- const fd = new FormData();
165
- fd.append('actionType', 'git_history');
166
- fd.append('path', file.path);
167
- submitAction(fd);
168
- } else if (actionType === 'rename') {
169
- setRenameValue(file.path);
170
- setShowRenameModal(true);
171
- } else if (actionType === 'delete') {
172
- setShowDeleteModal(true);
173
- }
174
- };
175
-
176
- const submitRename = (e: FormEvent) => {
177
- e.preventDefault();
178
- const fd = new FormData();
179
- fd.append('actionType', 'rename');
180
- fd.append('path', file.path);
181
- fd.append('newPath', renameValue);
182
- submitAction(fd);
183
- };
184
-
185
- const submitDelete = () => {
186
- const fd = new FormData();
187
- fd.append('actionType', 'delete');
188
- fd.append('path', file.path);
189
- submitAction(fd);
107
+ actions.openContextMenu(e.clientX, e.clientY);
190
108
  };
191
109
 
192
110
  return (
@@ -255,44 +173,56 @@ export function FileTreeItem({
255
173
  </div>
256
174
  )}
257
175
 
258
- {mounted && contextMenu && (
176
+ {mounted && actions.contextMenu && (
259
177
  <FileContextMenu
260
- x={contextMenu.x}
261
- y={contextMenu.y}
178
+ x={actions.contextMenu.x}
179
+ y={actions.contextMenu.y}
262
180
  isFolder={isFolder}
263
181
  hasGitStatus={hasGitStatus}
264
- onAction={handleAction}
182
+ onAction={actions.handleAction}
183
+ />
184
+ )}
185
+
186
+ {mounted && actions.showCreateModal && (
187
+ <FileCreateModal
188
+ folderPath={file.path}
189
+ value={actions.createName}
190
+ error={actions.createError}
191
+ isLoading={actions.busy}
192
+ onChange={actions.setCreateName}
193
+ onCancel={actions.closeCreate}
194
+ onSubmit={actions.submitCreate}
265
195
  />
266
196
  )}
267
197
 
268
- {mounted && showDeleteModal && (
198
+ {mounted && actions.showDeleteModal && (
269
199
  <FileDeleteModal
270
200
  fileName={file.name}
271
201
  isFolder={isFolder}
272
- isLoading={actionFetcher.state !== 'idle'}
273
- onCancel={() => setShowDeleteModal(false)}
274
- onConfirm={submitDelete}
202
+ isLoading={actions.busy}
203
+ onCancel={actions.closeDelete}
204
+ onConfirm={actions.submitDelete}
275
205
  />
276
206
  )}
277
207
 
278
- {mounted && showRenameModal && (
208
+ {mounted && actions.showRenameModal && (
279
209
  <FileRenameModal
280
210
  isFolder={isFolder}
281
- renameValue={renameValue}
211
+ renameValue={actions.renameValue}
282
212
  originalPath={file.path}
283
- isLoading={actionFetcher.state !== 'idle'}
284
- onChange={setRenameValue}
285
- onCancel={() => setShowRenameModal(false)}
286
- onSubmit={submitRename}
213
+ isLoading={actions.busy}
214
+ onChange={actions.setRenameValue}
215
+ onCancel={actions.closeRename}
216
+ onSubmit={actions.submitRename}
287
217
  />
288
218
  )}
289
219
 
290
- {mounted && showHistoryModal && (
220
+ {mounted && actions.showHistoryModal && (
291
221
  <FileHistoryModal
292
222
  filePath={file.path}
293
- fetcherState={actionFetcher.state}
294
- fetcherData={actionFetcher.data}
295
- onClose={() => setShowHistoryModal(false)}
223
+ fetcherState={actions.fetcherState}
224
+ fetcherData={actions.fetcherData}
225
+ onClose={actions.closeHistory}
296
226
  />
297
227
  )}
298
228
  </div>
@@ -1,7 +1,8 @@
1
1
  import type { TargetedMouseEvent } from 'preact';
2
- import { Bot, Boxes, Brain, Check, Command, Globe, GripVertical, Sparkles, Star, Zap } from 'lucide-preact';
2
+ import { Brain, Check, Command, GripVertical, Star } from 'lucide-preact';
3
3
  import type { AIModelOption } from '@/shared/types';
4
4
  import { formatContextWindow } from '@/shared/lib/code/format';
5
+ import { ProviderIcon } from '@/client/components/common/provider-icon';
5
6
 
6
7
  interface ModelDropdownItemProps {
7
8
  model: AIModelOption;
@@ -35,28 +36,17 @@ export function ModelDropdownItem({
35
36
  );
36
37
  }
37
38
 
38
- switch (model.providerIcon || model.provider.toLowerCase()) {
39
- case 'deepseek':
40
- return (
41
- <span className="text-[12px] leading-none flex-shrink-0" title="DeepSeek">
42
- 🐋
43
- </span>
44
- );
45
- case 'anthropic':
46
- case 'claude':
47
- return <Bot size={13} className="text-orange-400 flex-shrink-0" />;
48
- case 'openai':
49
- return <Boxes size={13} className="text-emerald-500 flex-shrink-0" />;
50
- case 'google':
51
- case 'gemini':
52
- return <Sparkles size={13} className="text-blue-400 flex-shrink-0" />;
53
- case 'opencode':
54
- return <Zap size={13} className="text-violet-400 flex-shrink-0" />;
55
- case 'mistral':
56
- return <Globe size={13} className="text-rose-400 flex-shrink-0" />;
57
- default:
58
- return <Sparkles size={13} className="text-ink/60 flex-shrink-0" />;
59
- }
39
+ // The provider's own mark, keyed on the catalog's `providerIcon` first so a
40
+ // legacy key still resolves; unknown providers fall back to their initials.
41
+ return (
42
+ <ProviderIcon
43
+ icon={model.providerIcon}
44
+ slug={model.provider}
45
+ name={model.provider}
46
+ size={13}
47
+ className="text-ink/70"
48
+ />
49
+ );
60
50
  };
61
51
 
62
52
  return (
@@ -115,7 +115,7 @@ export function ModelDropdownPanel({
115
115
  key={provider}
116
116
  id={provider}
117
117
  title={provider}
118
- iconType={provider === 'DEEPSEEK' ? 'whale' : 'provider'}
118
+ iconType="provider"
119
119
  models={pModels}
120
120
  isCollapsed={!!collapsedSections[provider]}
121
121
  onToggleCollapse={onToggleCollapse}
@@ -1,13 +1,14 @@
1
1
  import type { TargetedMouseEvent } from 'preact';
2
- import { ChevronDown, ChevronUp, GripVertical, History, Star } from 'lucide-preact';
2
+ import { ChevronDown, ChevronUp, History, Star } from 'lucide-preact';
3
3
  import type { AIModelOption } from '@/shared/types';
4
4
  import { ModelDropdownItem } from '@/client/components/workspace/model-dropdown/Item';
5
+ import { ProviderIcon } from '@/client/components/common/provider-icon';
5
6
  import { modelKey } from '@/shared/lib/models/identity';
6
7
 
7
8
  interface ModelDropdownSectionProps {
8
9
  id: string;
9
10
  title: string;
10
- iconType?: 'star' | 'recent' | 'provider' | 'whale';
11
+ iconType?: 'star' | 'recent' | 'provider';
11
12
  models: AIModelOption[];
12
13
  isCollapsed: boolean;
13
14
  onToggleCollapse: (id: string) => void;
@@ -49,8 +50,16 @@ export function ModelDropdownSection({
49
50
  <div className="flex items-center space-x-1.5">
50
51
  {iconType === 'star' && <Star size={12} className="fill-sky-400 text-sky-400" />}
51
52
  {iconType === 'recent' && <History size={12} className="text-ink/60" />}
52
- {iconType === 'whale' && <span>🐋</span>}
53
- {iconType === 'provider' && <GripVertical size={11} className="text-ink/25" />}
53
+ {iconType === 'provider' && (
54
+ // The section's own provider, taken from a row rather than the
55
+ // uppercased group key, so the mark resolves off the real slug.
56
+ <ProviderIcon
57
+ icon={models[0]?.providerIcon}
58
+ slug={models[0]?.provider ?? id}
59
+ name={models[0]?.provider ?? id}
60
+ size={12}
61
+ />
62
+ )}
54
63
  <span>{title}</span>
55
64
  </div>
56
65
  {isCollapsed ? (
@@ -5,6 +5,7 @@ import type { AIModelOption, ModelPreferences } from '@/shared/types';
5
5
  import { useOnClickOutside } from '@/client/hooks/ui/on-click-outside';
6
6
  import { invalidateModelsCache } from '@/shared/lib/models/client';
7
7
  import { ModelDropdownPanel } from '@/client/components/workspace/model-dropdown/Panel';
8
+ import { ProviderIcon } from '@/client/components/common/provider-icon';
8
9
  import { useModelCatalog } from '@/client/components/workspace/model-dropdown/use-catalog';
9
10
  import { buildPickerGroups } from '@/client/components/workspace/model-dropdown/groups';
10
11
  import { modelKey } from '@/shared/lib/models/identity';
@@ -229,10 +230,13 @@ export function ModelDropdown({
229
230
  <span className="text-ink/60 flex-shrink-0">
230
231
  {selectedModel.isCmdAgent ? (
231
232
  <span className="font-mono text-[10px] font-bold text-ink/70">⌘</span>
232
- ) : selectedModel.provider.toLowerCase() === 'deepseek' ? (
233
- <span>🐋</span>
234
233
  ) : (
235
- <Sparkles size={12} className="text-ink/60" />
234
+ <ProviderIcon
235
+ icon={selectedModel.providerIcon}
236
+ slug={selectedModel.provider}
237
+ name={selectedModel.provider}
238
+ size={12}
239
+ />
236
240
  )}
237
241
  </span>
238
242
  <span className="flex items-center space-x-1 min-w-0 max-w-[280px]">
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * One phase group of the todo panel: a header with the phase's own `closed/total`
8
+ * badge, then its task rows.
9
+ *
10
+ * The collapse state is the panel's (persisted per session), not local: a list
11
+ * the operator collapsed should stay collapsed across a reload, and the panel
12
+ * owns every phase's flag in one map so "collapse all" is one write.
13
+ */
14
+
15
+ import { ChevronDown, ChevronRight } from 'lucide-preact';
16
+ import type { TodoPhase } from '@/shared/types/todo';
17
+ import { closedTaskCount } from '@/shared/lib/chat/todo/progress';
18
+ import { TodoRow } from '@/client/components/workspace/todo-panel/Row';
19
+
20
+ interface TodoPhaseGroupProps {
21
+ phase: TodoPhase;
22
+ index: number;
23
+ collapsed: boolean;
24
+ /** `phase:task` of the row to highlight as current, or null. */
25
+ currentKey: string | null;
26
+ onToggle: () => void;
27
+ }
28
+
29
+ export function TodoPhaseGroup({ phase, index, collapsed, currentKey, onToggle }: TodoPhaseGroupProps) {
30
+ const closed = closedTaskCount(phase);
31
+ const isComplete = phase.tasks.length > 0 && closed === phase.tasks.length;
32
+
33
+ return (
34
+ <div className="overflow-hidden rounded-lg border border-ink/10 bg-paper">
35
+ <button
36
+ type="button"
37
+ onClick={onToggle}
38
+ aria-expanded={!collapsed}
39
+ className="flex w-full items-center gap-1.5 border-b border-ink/8 bg-canvas/40 px-2.5 py-1.5 text-left hover:bg-ink/5"
40
+ >
41
+ {collapsed ? (
42
+ <ChevronRight size={12} className="shrink-0 text-ink/40" />
43
+ ) : (
44
+ <ChevronDown size={12} className="shrink-0 text-ink/40" />
45
+ )}
46
+ <span
47
+ className={`min-w-0 flex-1 truncate text-[10px] font-semibold uppercase tracking-wider ${
48
+ isComplete ? 'text-ink/35' : 'text-ink/60'
49
+ }`}
50
+ title={phase.name}
51
+ >
52
+ {index + 1}. {phase.name}
53
+ </span>
54
+ <span className="shrink-0 font-mono text-[9.5px] text-ink/40">
55
+ {closed}/{phase.tasks.length}
56
+ </span>
57
+ </button>
58
+
59
+ {!collapsed && (
60
+ <div className="divide-y divide-ink/[0.05] p-0.5">
61
+ {phase.tasks.length === 0 ? (
62
+ <div className="px-2.5 py-1.5 text-[11px] text-ink/40">No tasks in this phase</div>
63
+ ) : (
64
+ phase.tasks.map((task, taskIndex) => (
65
+ <TodoRow
66
+ key={`${taskIndex}-${task.content}`}
67
+ task={task}
68
+ current={currentKey === `${index}:${taskIndex}`}
69
+ />
70
+ ))
71
+ )}
72
+ </div>
73
+ )}
74
+ </div>
75
+ );
76
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * One task row of the todo panel.
8
+ *
9
+ * The five statuses render through one component because they are one axis of
10
+ * the same row — icon, tone, and whether the text is struck through — and
11
+ * splitting them across branches is how a `blocked` task ends up drawn as an
12
+ * ordinary pending one. A blocked task also carries its own note (`blocker`),
13
+ * which is the only place omp records WHY it is waiting.
14
+ */
15
+
16
+ import { Ban, CheckCircle2, Circle, Loader2, OctagonAlert } from 'lucide-preact';
17
+ import type { TodoItem } from '@/shared/types/todo';
18
+ import { taskNote, todoStatusVisual } from '@/shared/lib/chat/todo/progress';
19
+
20
+ function StatusIcon({ status, className }: { status: TodoItem['status']; className: string }) {
21
+ switch (status) {
22
+ case 'completed':
23
+ return <CheckCircle2 size={13} className={className} />;
24
+ case 'in_progress':
25
+ return <Loader2 size={13} className={`${className} animate-spin`} />;
26
+ case 'abandoned':
27
+ return <Ban size={13} className={className} />;
28
+ case 'blocked':
29
+ return <OctagonAlert size={13} className={className} />;
30
+ default:
31
+ return <Circle size={13} className={className} />;
32
+ }
33
+ }
34
+
35
+ export function TodoRow({ task, current }: { task: TodoItem; current: boolean }) {
36
+ const visual = todoStatusVisual(task.status);
37
+ const note = taskNote(task);
38
+
39
+ return (
40
+ <div
41
+ className={`flex items-start gap-2.5 px-2.5 py-1.5 transition-colors ${
42
+ current ? 'bg-ink/[0.04]' : ''
43
+ }`}
44
+ >
45
+ <StatusIcon status={task.status} className={`mt-[2px] shrink-0 ${visual.tone}`} />
46
+ <div className="min-w-0 flex-1">
47
+ <div
48
+ className={`text-[11.5px] leading-snug ${
49
+ visual.closed
50
+ ? 'text-ink/40 line-through'
51
+ : visual.active
52
+ ? 'font-medium text-ink'
53
+ : 'text-ink/75'
54
+ }`}
55
+ >
56
+ {task.content}
57
+ </div>
58
+ {note && (
59
+ <div className="mt-0.5 text-[10.5px] leading-snug text-warning/90 break-words">{note}</div>
60
+ )}
61
+ </div>
62
+ {visual.active && (
63
+ <span className="shrink-0 rounded bg-ink/8 px-1.5 py-0.5 font-mono text-[9px] uppercase tracking-wider text-ink/70">
64
+ Now
65
+ </span>
66
+ )}
67
+ </div>
68
+ );
69
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * Right-panel Todo view: the session's live todo list, as oh-my-pi itself
8
+ * would resume with it.
9
+ *
10
+ * The list is omp's, read from the session transcript (see
11
+ * `@/server/lib/omp/session/todos`), so this panel adds presentation only —
12
+ * phase groups, a progress bar, and the current task called out. It deliberately
13
+ * does NOT recompute or cache a list of its own: a chamber-side copy would drift
14
+ * from the one the agent rehydrates, and a plan panel that disagrees with the
15
+ * agent is worse than no panel.
16
+ *
17
+ * Empty states are distinguished, because they mean different things: no
18
+ * session selected, a session whose transcript has no todo snapshot at all
19
+ * (the agent never used the tool here), and a snapshot whose every task is
20
+ * closed.
21
+ */
22
+
23
+ import { useMemo } from 'preact/hooks';
24
+ import { AlertCircle, ListTodo, RefreshCw } from 'lucide-preact';
25
+ import { useSearchParams } from '@/client/lib/router/search-params';
26
+ import { useSessionState } from '@/client/hooks/workspace/session-state';
27
+ import { useSessionTodos } from '@/client/hooks/workspace/session-todos';
28
+ import { TodoPhaseGroup } from '@/client/components/workspace/todo-panel/PhaseGroup';
29
+ import { currentTaskLocation, todoProgressLabel, todoProgressPercent } from '@/shared/lib/chat/todo/progress';
30
+
31
+ interface TodoPanelProps {
32
+ className?: string;
33
+ /**
34
+ * False while the view is not the one on screen: pauses the poll and the
35
+ * event-driven re-read.
36
+ *
37
+ * Deliberately NOT the `enabled` gate the workspace-scoped panels take. That
38
+ * flag means "the active session's cwd resolves to a registered workspace
39
+ * folder", which files/search/git/terminal genuinely need. A todo list does
40
+ * not: it lives in the session's own transcript, so a session running outside
41
+ * every registered workspace still has one — gating on the workspace showed
42
+ * "No session selected" for a session that was open and had a full list.
43
+ */
44
+ active?: boolean;
45
+ }
46
+
47
+ export function TodoPanel({ className = '', active = true }: TodoPanelProps) {
48
+ const [searchParams] = useSearchParams();
49
+ const sessionId = searchParams.get('sessionId');
50
+ const [collapsedPhases, setCollapsedPhases] = useSessionState<Record<string, boolean>>(
51
+ 'todo.collapsedPhases',
52
+ {},
53
+ );
54
+ const { data, isLoading, error, reload } = useSessionTodos(sessionId, active);
55
+
56
+ const snapshot = data?.snapshot ?? null;
57
+ const progress = data?.progress ?? null;
58
+
59
+ // `phase:task` of the row to highlight. Derived from the snapshot rather than
60
+ // stored, so it follows the list as it moves.
61
+ const currentKey = useMemo(() => {
62
+ const location = currentTaskLocation(snapshot?.phases ?? []);
63
+ return location ? `${location.phase}:${location.task}` : null;
64
+ }, [snapshot]);
65
+
66
+ if (!sessionId) {
67
+ return (
68
+ <div className={`flex h-full flex-col items-center justify-center bg-paper text-ink/40 ${className}`}>
69
+ <span className="text-xs font-mono">No session selected</span>
70
+ </div>
71
+ );
72
+ }
73
+
74
+ return (
75
+ <div className={`flex h-full w-full min-w-0 flex-col overflow-hidden bg-paper text-ink ${className}`}>
76
+ <div className="flex flex-shrink-0 items-center justify-between gap-3 border-b border-ink/10 px-3.5 py-2.5">
77
+ <div className="flex min-w-0 items-center gap-2">
78
+ <h2 className="text-xs font-semibold uppercase tracking-wider text-ink">Todos</h2>
79
+ {snapshot && (
80
+ <span className="truncate font-mono text-[10.5px] text-ink/50" title={todoProgressLabel(progress!)}>
81
+ {todoProgressLabel(progress!)}
82
+ </span>
83
+ )}
84
+ {data?.isMock && (
85
+ <span className="flex-shrink-0 rounded border border-ink/15 bg-ink/5 px-1.5 py-0.5 font-mono text-[10px] uppercase tracking-wider text-ink/60">
86
+ Mock
87
+ </span>
88
+ )}
89
+ </div>
90
+ <button
91
+ type="button"
92
+ onClick={reload}
93
+ disabled={isLoading}
94
+ title="Refresh todos"
95
+ aria-label="Refresh todos"
96
+ className="flex-shrink-0 rounded-md border border-ink/15 bg-paper p-1.5 text-ink/80 transition-colors hover:bg-ink/5 disabled:opacity-50"
97
+ >
98
+ <RefreshCw size={12} className={isLoading ? 'animate-spin text-ink' : ''} />
99
+ </button>
100
+ </div>
101
+
102
+ {progress && progress.total > 0 && (
103
+ <div className="flex-shrink-0 border-b border-ink/10 px-3.5 py-2.5">
104
+ <div className="h-1.5 overflow-hidden rounded-full bg-ink/8">
105
+ <div
106
+ className="h-full rounded-full bg-success transition-all duration-300"
107
+ style={{ width: `${todoProgressPercent(progress)}%` }}
108
+ />
109
+ </div>
110
+ </div>
111
+ )}
112
+
113
+ <div className="min-h-0 flex-1 space-y-2 overflow-y-auto scrollbar-overlay-container scrollbar-overlay-static p-3.5">
114
+ {error && (
115
+ <div className="flex items-start gap-2 rounded-lg border border-error/30 bg-error/5 px-3 py-2 text-[11px] text-error">
116
+ <AlertCircle size={13} className="mt-[1px] shrink-0" />
117
+ <span className="min-w-0 break-words">{error}</span>
118
+ </div>
119
+ )}
120
+
121
+ {!snapshot ? (
122
+ <div className="flex flex-col items-center justify-center gap-2 py-12 text-center text-ink/40">
123
+ <ListTodo size={20} />
124
+ <p className="max-w-[240px] text-[11.5px] leading-relaxed">
125
+ No todo list in this session. The agent writes one with the <span className="font-mono">todo</span> tool
126
+ when a task is worth planning.
127
+ </p>
128
+ </div>
129
+ ) : (
130
+ <>
131
+ {snapshot.phases.map((phase, index) => (
132
+ <TodoPhaseGroup
133
+ key={`${index}-${phase.name}`}
134
+ phase={phase}
135
+ index={index}
136
+ collapsed={Boolean(collapsedPhases[String(index)])}
137
+ currentKey={currentKey}
138
+ onToggle={() =>
139
+ setCollapsedPhases((prev) => ({ ...prev, [String(index)]: !prev[String(index)] }))
140
+ }
141
+ />
142
+ ))}
143
+
144
+ <p className="pt-1 text-[10.5px] leading-relaxed text-ink/35">
145
+ Read from this session&apos;s transcript
146
+ {snapshot.updatedAt ? ` · updated ${formatStamp(snapshot.updatedAt)}` : ''}
147
+ {snapshot.source === 'custom' ? ' · edited by hand' : ''}.
148
+ </p>
149
+ </>
150
+ )}
151
+ </div>
152
+ </div>
153
+ );
154
+ }
155
+
156
+ /** Local time of a snapshot stamp; the raw ISO string when it cannot be read. */
157
+ function formatStamp(value: string): string {
158
+ const date = new Date(value);
159
+ if (Number.isNaN(date.getTime())) return value;
160
+ return date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
161
+ }