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
@@ -61,3 +61,39 @@ export function formatAmount(value: string): string {
61
61
  if (value.trim() === '' || !Number.isFinite(parsed)) return value;
62
62
  return formatNumber(parsed);
63
63
  }
64
+
65
+ const usdFormatter = new Intl.NumberFormat('id-ID', {
66
+ minimumFractionDigits: 2,
67
+ maximumFractionDigits: 2,
68
+ });
69
+
70
+ /** USD amounts from provider reports and local cost records. */
71
+ export function formatUsd(value: number): string {
72
+ if (!Number.isFinite(value)) return '$0,00';
73
+ return `$${usdFormatter.format(value)}`;
74
+ }
75
+
76
+ /**
77
+ * Render one quota amount in its own unit. `percent` amounts arrive as 0..100
78
+ * (omp's `used`/`limit` for percent units), everything else as a raw count.
79
+ */
80
+ export function formatUsageAmount(value: number, unit: string): string {
81
+ if (!Number.isFinite(value)) return '—';
82
+ switch (unit) {
83
+ case 'percent':
84
+ return `${new Intl.NumberFormat('id-ID', { maximumFractionDigits: 1 }).format(value)}%`;
85
+ case 'usd':
86
+ return formatUsd(value);
87
+ case 'tokens':
88
+ case 'bytes':
89
+ return formatCompactTokens(value);
90
+ default:
91
+ return formatNumber(value);
92
+ }
93
+ }
94
+
95
+ /** Fraction (0..1) rendered as a whole-percent string. */
96
+ export function formatFractionPercent(fraction: number): string {
97
+ if (!Number.isFinite(fraction)) return '0%';
98
+ return `${new Intl.NumberFormat('id-ID', { maximumFractionDigits: 1 }).format(fraction * 100)}%`;
99
+ }
@@ -1,45 +1,69 @@
1
- import type { UsageReport } from '@/shared/types';
2
- import { formatAmount } from '@/client/components/settings/categories/usage-settings/format';
1
+ import type { UsageLimitWindow, UsageProviderSummary, UsageReport } from '@/shared/types';
2
+ import { formatAmount, formatUsd } from '@/client/components/settings/categories/usage-settings/format';
3
3
 
4
- /** Providers surfaced in the Settings → Usage sidebar and the Usage right panel. */
5
- export type UsageProviderId = 'kenari' | 'deepseek';
4
+ /**
5
+ * Providers surfaced in the Settings → Usage sidebar and the Usage right panel.
6
+ * The id is the provider slug, so the set is open-ended: any provider omp can
7
+ * authenticate shows up here without a code change.
8
+ */
9
+ export type UsageProviderId = string;
6
10
 
7
11
  export interface ProviderEntry {
8
12
  id: UsageProviderId;
9
13
  name: string;
10
- configured: boolean;
14
+ /** Provider-side failure, already human-readable. */
11
15
  error?: string;
16
+ /** Short status line under the name. */
12
17
  hint: string;
18
+ /** Provider-reported quota windows; empty when it exposes no usage endpoint. */
19
+ limits: UsageLimitWindow[];
20
+ /** Whether omp ships a usage adapter for this provider. */
21
+ tracked: boolean;
22
+ /** omp tracks this provider but its endpoint produced nothing. */
23
+ limitsUnavailable?: boolean;
24
+ summary: UsageProviderSummary;
25
+ }
26
+
27
+ /** Compact one-line summary of the most-used quota window. */
28
+ function limitHint(limits: UsageLimitWindow[]): string | undefined {
29
+ const used = limits.filter((limit) => limit.usedFraction !== undefined);
30
+ if (used.length === 0) {
31
+ // A credit balance has no limit to take a fraction of; the remaining
32
+ // amount IS the whole story.
33
+ const credit = limits.find((limit) => limit.unit === 'usd' && limit.remaining !== undefined);
34
+ if (credit) return `${formatUsd(credit.remaining ?? 0)} left`;
35
+ return limits[0]?.label;
36
+ }
37
+ const binding = used.reduce((worst, limit) =>
38
+ (limit.usedFraction ?? 0) > (worst.usedFraction ?? 0) ? limit : worst,
39
+ );
40
+ const percent = Math.round((binding.usedFraction ?? 0) * 100);
41
+ return binding.windowLabel ? `${percent}% · ${binding.windowLabel}` : `${percent}% used`;
13
42
  }
14
43
 
15
44
  /** Derives the provider list (status + hint) from a usage report. */
16
45
  export function buildProviders(report: UsageReport): ProviderEntry[] {
17
- const kenariBalance = report.kenari.quota?.planName;
18
- const deepseekEntry = report.deepseek.balance?.entries[0];
46
+ return report.providers.map((summary) => ({
47
+ id: summary.id,
48
+ name: summary.name,
49
+ error: summary.error,
50
+ hint: providerHint(summary),
51
+ limits: summary.limits,
52
+ tracked: summary.tracked,
53
+ limitsUnavailable: summary.limitsUnavailable,
54
+ summary,
55
+ }));
56
+ }
19
57
 
20
- return [
21
- {
22
- id: 'kenari',
23
- name: 'Kenari.id',
24
- configured: report.kenari.configured,
25
- error: report.kenari.error,
26
- hint: kenariBalance ?? 'Wallet & quota',
27
- },
28
- {
29
- id: 'deepseek',
30
- name: 'DeepSeek',
31
- configured: report.deepseek.configured,
32
- error: report.deepseek.error,
33
- hint: deepseekEntry
34
- ? `${deepseekEntry.currency} ${formatAmount(deepseekEntry.totalBalance)}`
35
- : 'Prepaid balance',
36
- },
37
- ];
58
+ function providerHint(summary: UsageProviderSummary): string {
59
+ const plan = summary.kenari?.quota?.planName;
60
+ if (plan) return plan;
61
+ const balance = summary.deepseek?.balance?.entries[0];
62
+ if (balance) return `${balance.currency} ${formatAmount(balance.totalBalance)}`;
63
+ return limitHint(summary.limits) ?? 'No quota reported';
38
64
  }
39
65
 
40
66
  /** Human-readable connection state for a provider entry. */
41
67
  export function statusLabel(provider: ProviderEntry): string {
42
- if (!provider.configured) return 'Not configured';
43
- if (provider.error) return 'Error';
44
- return 'Connected';
68
+ return provider.error ? 'Error' : 'Connected';
45
69
  }
@@ -1,20 +1,17 @@
1
1
  import { Globe, RotateCw } from 'lucide-preact';
2
2
  import { BrowserToolbar, ICON_BUTTON } from '@/client/components/workspace/browser-toolbar/index';
3
- import type { BrowserViewStatus, ViewportMode } from '@/shared/types';
3
+ import type { BrowserViewStatus } from '@/shared/types';
4
4
 
5
5
  interface BrowserAddressBarProps {
6
6
  url?: string;
7
7
  status: BrowserViewStatus;
8
- viewportMode: ViewportMode;
9
8
  zoomLevel?: number;
10
9
  onIncludeInChat: () => void;
11
10
  onReconnect: () => void;
12
11
  onOpenExternal: () => void;
13
- onChangeViewport: (mode: ViewportMode) => void;
14
12
  onZoomIn: () => void;
15
13
  onZoomOut: () => void;
16
14
  onResetZoom: () => void;
17
- onSetZoom: (zoom: number) => void;
18
15
  }
19
16
 
20
17
  const STATUS_LABEL: Record<BrowserViewStatus, string> = {
@@ -32,16 +29,13 @@ const STATUS_LABEL: Record<BrowserViewStatus, string> = {
32
29
  export function BrowserAddressBar({
33
30
  url,
34
31
  status,
35
- viewportMode,
36
32
  zoomLevel = 100,
37
33
  onIncludeInChat,
38
34
  onReconnect,
39
35
  onOpenExternal,
40
- onChangeViewport,
41
36
  onZoomIn,
42
37
  onZoomOut,
43
38
  onResetZoom,
44
- onSetZoom,
45
39
  }: BrowserAddressBarProps) {
46
40
  const isLive = status === 'live';
47
41
 
@@ -50,13 +44,10 @@ export function BrowserAddressBar({
50
44
  url={url ?? ''}
51
45
  statusDotClass={isLive ? 'bg-success animate-pulse' : 'bg-ink/25'}
52
46
  statusLabel={STATUS_LABEL[status]}
53
- viewportMode={viewportMode}
54
47
  zoomLevel={zoomLevel}
55
- onChangeViewport={onChangeViewport}
56
48
  onZoomIn={onZoomIn}
57
49
  onZoomOut={onZoomOut}
58
50
  onResetZoom={onResetZoom}
59
- onSetZoom={onSetZoom}
60
51
  onIncludeInChat={onIncludeInChat}
61
52
  onOpenExternal={onOpenExternal}
62
53
  openExternalLabel="Buka di jendela baru"
@@ -1,7 +1,6 @@
1
1
  import { Globe, Loader2, Radio } from 'lucide-preact';
2
2
  import { ActivityToasts } from '@/client/components/workspace/browser-panel/ActivityToasts';
3
- import { VIEWPORT_CLASSES } from '@/client/components/common/viewport';
4
- import type { BrowserPanelAction, BrowserTabInfo, BrowserViewStatus, ViewportMode } from '@/shared/types';
3
+ import type { BrowserPanelAction, BrowserTabInfo, BrowserViewStatus } from '@/shared/types';
5
4
 
6
5
  interface BrowserScreencastProps {
7
6
  status: BrowserViewStatus;
@@ -11,7 +10,6 @@ interface BrowserScreencastProps {
11
10
  frameSrc?: string;
12
11
  actions: BrowserPanelAction[];
13
12
  onSelectTarget: (targetId: string) => void;
14
- viewportMode: ViewportMode;
15
13
  zoomLevel: number;
16
14
  }
17
15
 
@@ -38,7 +36,6 @@ export function BrowserScreencast({
38
36
  frameSrc,
39
37
  actions,
40
38
  onSelectTarget,
41
- viewportMode,
42
39
  zoomLevel,
43
40
  }: BrowserScreencastProps) {
44
41
  const isLive = status === 'live';
@@ -73,8 +70,10 @@ export function BrowserScreencast({
73
70
 
74
71
  <div className="flex-1 flex items-center justify-center p-2 min-h-0 overflow-auto">
75
72
  {isLive ? (
73
+ // Always fills the panel: the surface is a fixed-resolution JPEG, so
74
+ // there is no device box to frame it in — only the zoom scale.
76
75
  <div
77
- className={`relative flex flex-col bg-paper ${VIEWPORT_CLASSES[viewportMode]} transition-all duration-200`}
76
+ className="relative flex flex-col bg-paper w-full h-full transition-all duration-200"
78
77
  style={{
79
78
  zoom: zoomLevel !== 100 ? `${zoomLevel}%` : undefined,
80
79
  transformOrigin: 'top center',
@@ -4,7 +4,6 @@ import { BrowserScreencast } from '@/client/components/workspace/browser-panel/S
4
4
  import { useScreencast } from '@/client/hooks/browser/use-screencast';
5
5
  import { useSessionState } from '@/client/hooks/workspace/session-state';
6
6
  import { emitBrowserPageContext } from '@/shared/lib/browser/page-context';
7
- import type { ViewportMode } from '@/shared/types';
8
7
 
9
8
  interface BrowserPanelProps {
10
9
  className?: string;
@@ -18,9 +17,14 @@ interface BrowserPanelProps {
18
17
  *
19
18
  * This panel only mirrors what the agent does. Interactive browsing belongs to
20
19
  * the separate user-browser panel, which runs its own private Chromium.
20
+ *
21
+ * There is deliberately NO device/viewport selector here: the surface is a
22
+ * fixed-resolution screencast JPEG, so a preset could only letterbox the image
23
+ * while claiming to emulate a device. Emulation belongs to the agent itself
24
+ * (`tab.emulate()` / `browser.open({ viewport })`); the user-browser panel,
25
+ * whose iframe's layout viewport *is* its CSS box, keeps the selector.
21
26
  */
22
27
  export function BrowserPanel({ className = '', active = true }: BrowserPanelProps) {
23
- const [viewportMode, setViewportMode] = useSessionState<ViewportMode>('browser.viewportMode', 'responsive');
24
28
  const [zoomLevel, setZoomLevel] = useSessionState<number>('browser.zoomLevel', 100);
25
29
  const { status, url, title, tabs, targetId, frameSrc, actions, selectTarget, reconnect } = useScreencast(active);
26
30
 
@@ -41,16 +45,13 @@ export function BrowserPanel({ className = '', active = true }: BrowserPanelProp
41
45
  <BrowserAddressBar
42
46
  url={url}
43
47
  status={status}
44
- viewportMode={viewportMode}
45
48
  zoomLevel={zoomLevel}
46
49
  onIncludeInChat={handleIncludeInChat}
47
50
  onReconnect={reconnect}
48
51
  onOpenExternal={handleOpenExternal}
49
- onChangeViewport={setViewportMode}
50
52
  onZoomIn={() => setZoomLevel((prev) => Math.min(200, prev + 10))}
51
53
  onZoomOut={() => setZoomLevel((prev) => Math.max(50, prev - 10))}
52
54
  onResetZoom={() => setZoomLevel(100)}
53
- onSetZoom={setZoomLevel}
54
55
  />
55
56
 
56
57
  <BrowserScreencast
@@ -61,7 +62,6 @@ export function BrowserPanel({ className = '', active = true }: BrowserPanelProp
61
62
  frameSrc={frameSrc}
62
63
  actions={actions}
63
64
  onSelectTarget={selectTarget}
64
- viewportMode={viewportMode}
65
65
  zoomLevel={zoomLevel}
66
66
  />
67
67
  </div>
@@ -13,15 +13,22 @@ export interface BrowserToolbarProps {
13
13
  /** Status dot class plus its label — the only per-panel difference. */
14
14
  statusDotClass: string;
15
15
  statusLabel: string;
16
- viewportMode: ViewportMode;
17
16
  zoomLevel: number;
18
- onChangeViewport: (mode: ViewportMode) => void;
19
17
  onZoomIn: () => void;
20
18
  onZoomOut: () => void;
21
19
  onResetZoom: () => void;
22
- onSetZoom: (zoom: number) => void;
23
20
  onIncludeInChat: () => void;
24
21
  onOpenExternal: () => void;
22
+ /**
23
+ * Device-testing cluster, rendered only when both are supplied. The USER
24
+ * panel passes them: its iframe's layout viewport *is* its CSS box, so a
25
+ * preset genuinely re-runs the page's media queries. The AGENT panel omits
26
+ * them — its surface is a fixed-resolution screencast JPEG, so a preset
27
+ * could only letterbox the image while claiming to emulate a device.
28
+ */
29
+ viewportMode?: ViewportMode;
30
+ onChangeViewport?: (mode: ViewportMode) => void;
31
+ onSetZoom?: (zoom: number) => void;
25
32
  /** Shown after the include/copy buttons — the agent panel's reconnect button. */
26
33
  trailingControls?: ComponentChildren;
27
34
  /** Open-external button label/title, differing between the two panels. */
@@ -34,9 +41,10 @@ export interface BrowserToolbarProps {
34
41
  /**
35
42
  * Shared address bar for both browser panels. The two panels differ only in
36
43
  * their status semantics, the middle slot (read-only URL vs editable form),
37
- * and the agent panel's reconnect button; everything else — the include,
38
- * copy, open-external buttons and the zoom cluster — lives here so the two
39
- * toolbars cannot drift.
44
+ * the agent panel's reconnect button, and whether the device-testing cluster
45
+ * is offered at all (user panel yes, agent panel no — see `viewportMode`);
46
+ * everything else — the include, copy, open-external buttons and the zoom
47
+ * cluster — lives here so the two toolbars cannot drift.
40
48
  */
41
49
  export function BrowserToolbar({
42
50
  url,
@@ -86,12 +94,14 @@ export function BrowserToolbar({
86
94
  {children}
87
95
 
88
96
  <div className="flex items-center space-x-1.5 flex-shrink-0">
89
- <BrowserViewportSelector
90
- viewportMode={viewportMode}
91
- onChangeViewport={onChangeViewport}
92
- zoomLevel={zoomLevel}
93
- onSetZoom={onSetZoom}
94
- />
97
+ {viewportMode && onChangeViewport && (
98
+ <BrowserViewportSelector
99
+ viewportMode={viewportMode}
100
+ onChangeViewport={onChangeViewport}
101
+ zoomLevel={zoomLevel}
102
+ onSetZoom={onSetZoom}
103
+ />
104
+ )}
95
105
 
96
106
  <ZoomControls
97
107
  zoomLevel={zoomLevel}
@@ -3,7 +3,7 @@ import type { ReactNode } from 'preact/compat';
3
3
  import { Bot, Brain, Clock3, Coins, Hourglass, MessageSquarePlus, MoreHorizontal, RotateCcw, X } from 'lucide-preact';
4
4
  import { CopyButton } from '@/client/components/common/CopyButton';
5
5
  import { formatDuration } from '@/shared/lib/chat/duration';
6
- import { providerLabel } from '@/shared/lib/models/provider-label';
6
+ import { providerLabel } from '@/shared/lib/models/provider/label';
7
7
  import { formatCompactTokens } from '@/shared/lib/format/number';
8
8
 
9
9
  interface AiMessageFooterProps {
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useState } from 'preact/hooks';
2
2
  import { Bot } from 'lucide-preact';
3
- import { providerLabel } from '@/shared/lib/models/provider-label';
3
+ import { providerLabel } from '@/shared/lib/models/provider/label';
4
4
 
5
5
  interface GeneratingIndicatorProps {
6
6
  modelName?: string;
@@ -12,7 +12,7 @@ import { toTitleCase } from '@/shared/lib/chat/title-case';
12
12
  import { FallbackOutput } from '@/client/components/workspace/chat-timeline/tool-renderers/shared/FallbackOutput';
13
13
  import { tryParseJson } from '@/shared/lib/code/syntax-highlight';
14
14
  import { JsonCodeBlock } from '@/client/components/workspace/chat-timeline/tool-renderers/shared/JsonCodeBlock';
15
- import { getTodoSummary } from '@/shared/lib/chat/todo-parser';
15
+ import { getTodoSummary } from '@/shared/lib/chat/todo/parser';
16
16
  import { parseAskQuestions } from '@/shared/lib/chat/ask-questions';
17
17
 
18
18
  interface ToolCallCardProps {
@@ -1,7 +1,7 @@
1
1
  import { useMemo } from 'preact/hooks';
2
2
  import { CheckCircle2, ListTodo, Loader2, Square } from 'lucide-preact';
3
3
  import type { ToolCallData } from '@/shared/types';
4
- import { parseTodoData } from '@/shared/lib/chat/todo-parser';
4
+ import { parseTodoData } from '@/shared/lib/chat/todo/parser';
5
5
  import { FallbackOutput } from '@/client/components/workspace/chat-timeline/tool-renderers/shared/FallbackOutput';
6
6
 
7
7
  /** Panel khusus untuk tool `todo` — task list dengan progress bar, phase groups, dan status yang readable. */
@@ -2,17 +2,21 @@ import { AlignJustify, Check, Code2, Columns, Copy, Minus, Plus, RefreshCw, Spac
2
2
  import { FileIcon } from '@/client/components/common/file-icon';
3
3
  import { getGitStatusInfo } from '@/shared/lib/fs/git-status';
4
4
 
5
+ export type DiffViewMode = 'unified' | 'split';
6
+
5
7
  interface DiffToolbarProps {
6
8
  filePath: string;
7
9
  status: string;
8
10
  isStaged: boolean;
9
- viewMode: 'unified' | 'split';
11
+ viewMode: DiffViewMode;
10
12
  ignoreWhitespace: boolean;
11
13
  additions: number;
12
14
  deletions: number;
13
15
  isLoading: boolean;
14
16
  isCopied: boolean;
15
- onToggleViewMode: () => void;
17
+ /** A stage/unstage/discard write is in flight; the toolbar locks its actions. */
18
+ isBusy?: boolean;
19
+ onSetViewMode: (mode: DiffViewMode) => void;
16
20
  onToggleWhitespace: () => void;
17
21
  onStageUnstage: () => void;
18
22
  onDiscard: () => void;
@@ -21,6 +25,9 @@ interface DiffToolbarProps {
21
25
  onCopyDiff: () => void;
22
26
  }
23
27
 
28
+ const ACTION_CLASS =
29
+ 'flex items-center justify-center p-1.5 rounded transition-colors cursor-pointer disabled:cursor-default disabled:opacity-40 disabled:hover:bg-transparent disabled:hover:text-ink/50';
30
+
24
31
  export function DiffToolbar({
25
32
  filePath,
26
33
  status,
@@ -31,7 +38,8 @@ export function DiffToolbar({
31
38
  deletions,
32
39
  isLoading,
33
40
  isCopied,
34
- onToggleViewMode,
41
+ isBusy = false,
42
+ onSetViewMode,
35
43
  onToggleWhitespace,
36
44
  onStageUnstage,
37
45
  onDiscard,
@@ -41,56 +49,72 @@ export function DiffToolbar({
41
49
  }: DiffToolbarProps) {
42
50
  const fileName = filePath.split('/').pop() || filePath;
43
51
  const statusInfo = getGitStatusInfo(status, isStaged);
52
+ // Only the actions that write to git lock while one is in flight. Copying the
53
+ // diff is a local read of what is already on screen, so it stays available.
54
+ const locked = isLoading || isBusy;
44
55
 
45
56
  return (
46
- <div className="flex items-center justify-between px-3 py-1.5 bg-paper border-b border-ink/10 text-xs font-sans select-none flex-shrink-0 overflow-x-auto no-scrollbar">
47
- {/* Left: File name, status badge, additions/deletions. Hidden on phone
48
- widths: the mobile diff header already carries the file identity, and
49
- dropping it keeps the actions reachable without a sideways scroll. */}
50
- <div className="hidden md:flex items-center space-x-2 min-w-0 pr-2">
57
+ // `@container` so the labels below can hide on a NARROW PANEL rather than a
58
+ // narrow viewport: the editor panel is 320px wide on a 1440px screen, and
59
+ // `sm:` (viewport-keyed) kept every label on, overflowing the toolbar.
60
+ <div className="@container flex items-center justify-between px-3 py-1.5 bg-paper border-b border-ink/10 text-xs font-sans select-none flex-shrink-0 gap-2 min-w-0">
61
+ {/* Left: File name and status badge. Hidden when the panel is narrow —
62
+ the editor tab already carries the name and the status character, and
63
+ the mobile diff header carries both. */}
64
+ <div className="hidden @[560px]:flex items-center space-x-2 min-w-0 pr-2">
51
65
  <FileIcon name={fileName} size={14} className="flex-shrink-0" />
52
66
  <span className="font-mono text-ink font-medium truncate" title={filePath}>
53
67
  {fileName}
54
68
  </span>
55
-
69
+
56
70
  <span className={`px-1.5 py-0.5 text-[10px] font-mono font-semibold rounded border ${statusInfo.badgeBgClass} flex-shrink-0`}>
57
71
  {statusInfo.charStatus} {statusInfo.label}
58
72
  </span>
73
+ </div>
59
74
 
75
+ {/* Right: Controls & actions. `ml-auto` keeps them right-aligned where the
76
+ identity block is hidden. This strip scrolls on its own when the panel
77
+ is too narrow, so no action is ever clipped out of reach. Each segment
78
+ sets its OWN mode — toggling meant clicking the already-active segment
79
+ flipped the view to the other one. */}
80
+ <div className="flex items-center space-x-0.5 @[440px]:space-x-1 text-ink/70 ml-auto min-w-0 overflow-x-auto no-scrollbar">
81
+ {/* Change counts live here, not in the identity block: the editor tab
82
+ carries the file name but never the counts, so this is the one piece
83
+ of the header that must survive a narrow panel. */}
60
84
  {(additions > 0 || deletions > 0) && (
61
- <div className="flex items-center space-x-1.5 text-[11px] font-mono flex-shrink-0 ml-1">
85
+ <div className="flex items-center space-x-1.5 text-[11px] font-mono flex-shrink-0 pr-1.5 mr-0.5 @[440px]:mr-1 border-r border-ink/10">
62
86
  {additions > 0 && <span className="text-success font-semibold">+{additions}</span>}
63
87
  {deletions > 0 && <span className="text-error font-semibold">-{deletions}</span>}
64
88
  </div>
65
89
  )}
66
- </div>
67
90
 
68
- {/* Right: Controls & actions. `ml-auto` keeps them right-aligned on phone
69
- widths, where the identity block above is hidden. */}
70
- <div className="flex items-center space-x-1 text-ink/70 flex-shrink-0 ml-auto">
71
91
  {/* View mode toggle (Unified vs Split) */}
72
- <div className="flex items-center bg-canvas rounded border border-ink/15 p-0.5 mr-1">
92
+ <div className="flex items-center bg-canvas rounded border border-ink/15 p-0.5 mr-0.5 @[440px]:mr-1 flex-shrink-0">
73
93
  <button
74
94
  type="button"
75
- onClick={onToggleViewMode}
76
- className={`px-2 py-0.5 rounded text-[11px] font-medium flex items-center space-x-1 transition-colors ${
95
+ onClick={() => onSetViewMode('split')}
96
+ className={`px-2 py-0.5 rounded text-[11px] font-medium flex items-center space-x-1 transition-colors cursor-pointer flex-shrink-0 ${
77
97
  viewMode === 'split' ? 'bg-paper text-ink shadow-xs' : 'text-ink/60 hover:text-ink'
78
98
  }`}
79
- title="Split (side-by-side) Diff"
99
+ title="Split (side-by-side) diff"
100
+ aria-label="Split (side-by-side) diff"
101
+ aria-pressed={viewMode === 'split'}
80
102
  >
81
103
  <Columns size={12} />
82
- <span className="hidden sm:inline">Split</span>
104
+ <span className="hidden @[420px]:inline">Split</span>
83
105
  </button>
84
106
  <button
85
107
  type="button"
86
- onClick={onToggleViewMode}
87
- className={`px-2 py-0.5 rounded text-[11px] font-medium flex items-center space-x-1 transition-colors ${
108
+ onClick={() => onSetViewMode('unified')}
109
+ className={`px-2 py-0.5 rounded text-[11px] font-medium flex items-center space-x-1 transition-colors cursor-pointer flex-shrink-0 ${
88
110
  viewMode === 'unified' ? 'bg-paper text-ink shadow-xs' : 'text-ink/60 hover:text-ink'
89
111
  }`}
90
- title="Unified Diff"
112
+ title="Unified diff"
113
+ aria-label="Unified diff"
114
+ aria-pressed={viewMode === 'unified'}
91
115
  >
92
116
  <AlignJustify size={12} />
93
- <span className="hidden sm:inline">Unified</span>
117
+ <span className="hidden @[420px]:inline">Unified</span>
94
118
  </button>
95
119
  </div>
96
120
 
@@ -98,10 +122,12 @@ export function DiffToolbar({
98
122
  <button
99
123
  type="button"
100
124
  onClick={onToggleWhitespace}
101
- className={`p-1.5 rounded transition-colors ${
125
+ className={`p-1.5 rounded transition-colors cursor-pointer flex-shrink-0 ${
102
126
  ignoreWhitespace ? 'bg-ink/10 text-ink' : 'text-ink/50 hover:text-ink hover:bg-ink/5'
103
127
  }`}
104
- title={ignoreWhitespace ? 'Whitespace ignored' : 'Ignore whitespace'}
128
+ title={ignoreWhitespace ? 'Whitespace changes hidden' : 'Hide whitespace changes'}
129
+ aria-label={ignoreWhitespace ? 'Whitespace changes hidden' : 'Hide whitespace changes'}
130
+ aria-pressed={ignoreWhitespace}
105
131
  >
106
132
  <Space size={13} />
107
133
  </button>
@@ -110,19 +136,21 @@ export function DiffToolbar({
110
136
  <button
111
137
  type="button"
112
138
  onClick={onRefresh}
113
- className="p-1.5 text-ink/50 hover:text-ink hover:bg-ink/5 rounded transition-colors"
114
- title="Refresh Diff"
115
- disabled={isLoading}
139
+ className={`${ACTION_CLASS} text-ink/50 hover:text-ink hover:bg-ink/5 flex-shrink-0`}
140
+ title="Reload this diff"
141
+ aria-label="Reload this diff"
142
+ disabled={locked}
116
143
  >
117
144
  <RefreshCw size={13} className={isLoading ? 'animate-spin' : ''} />
118
145
  </button>
119
146
 
120
- {/* Copy diff */}
147
+ {/* Copy diff — never disabled: it reads the buffer already on screen. */}
121
148
  <button
122
149
  type="button"
123
150
  onClick={onCopyDiff}
124
- className="p-1.5 text-ink/50 hover:text-ink hover:bg-ink/5 rounded transition-colors"
125
- title="Copy Diff Text"
151
+ className={`${ACTION_CLASS} text-ink/50 hover:text-ink hover:bg-ink/5 flex-shrink-0`}
152
+ title={isCopied ? 'Copied' : 'Copy diff text'}
153
+ aria-label={isCopied ? 'Copied' : 'Copy diff text'}
126
154
  >
127
155
  {isCopied ? <Check size={13} className="text-success" /> : <Copy size={13} />}
128
156
  </button>
@@ -132,8 +160,10 @@ export function DiffToolbar({
132
160
  <button
133
161
  type="button"
134
162
  onClick={onDiscard}
135
- className="p-1.5 text-ink/50 hover:text-error hover:bg-error/10 rounded transition-colors"
163
+ className={`${ACTION_CLASS} text-ink/50 hover:text-error hover:bg-error/10 flex-shrink-0`}
136
164
  title="Discard changes in this file"
165
+ aria-label="Discard changes in this file"
166
+ disabled={locked}
137
167
  >
138
168
  <Undo2 size={13} />
139
169
  </button>
@@ -143,18 +173,20 @@ export function DiffToolbar({
143
173
  <button
144
174
  type="button"
145
175
  onClick={onStageUnstage}
146
- className="px-2 py-1 text-[11px] font-medium rounded border border-ink/15 hover:bg-ink/5 flex items-center space-x-1 transition-colors text-ink"
176
+ className="px-2 py-1 text-[11px] font-medium rounded border border-ink/15 hover:bg-ink/5 flex items-center space-x-1 transition-colors text-ink cursor-pointer disabled:cursor-default disabled:opacity-40 disabled:hover:bg-transparent flex-shrink-0"
147
177
  title={isStaged ? 'Unstage file' : 'Stage file'}
178
+ aria-label={isStaged ? 'Unstage file' : 'Stage file'}
179
+ disabled={locked}
148
180
  >
149
181
  {isStaged ? (
150
182
  <>
151
183
  <Minus size={12} />
152
- <span>Unstage</span>
184
+ <span className="hidden @[360px]:inline">Unstage</span>
153
185
  </>
154
186
  ) : (
155
187
  <>
156
188
  <Plus size={12} />
157
- <span>Stage</span>
189
+ <span className="hidden @[360px]:inline">Stage</span>
158
190
  </>
159
191
  )}
160
192
  </button>
@@ -164,11 +196,12 @@ export function DiffToolbar({
164
196
  <button
165
197
  type="button"
166
198
  onClick={onOpenInEditor}
167
- className="px-2 py-1 text-[11px] font-medium rounded bg-ink text-canvas hover:bg-ink/90 flex items-center space-x-1 transition-colors shadow-xs ml-1"
168
- title="Open in Code Editor"
199
+ className="px-2 py-1 text-[11px] font-medium rounded bg-ink text-canvas hover:bg-ink/90 flex items-center space-x-1 transition-colors shadow-xs ml-1 cursor-pointer flex-shrink-0"
200
+ title="Open in code editor"
201
+ aria-label="Open in code editor"
169
202
  >
170
203
  <Code2 size={12} />
171
- <span className="hidden sm:inline">Edit File</span>
204
+ <span className="hidden @[360px]:inline">Edit File</span>
172
205
  </button>
173
206
  )}
174
207
  </div>