gloomberb 0.9.7 → 0.9.9

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 (276) hide show
  1. package/package.json +1 -1
  2. package/src/api-client/auth.ts +20 -0
  3. package/src/api-client/index.test.ts +3 -3
  4. package/src/api-client/index.ts +48 -6
  5. package/src/api-client/request.test.ts +63 -0
  6. package/src/api-client/request.ts +53 -5
  7. package/src/api-client/types.ts +45 -2
  8. package/src/app/pane-runtime/layout-placement.ts +0 -1
  9. package/src/app/runtime/desktop-deeplink.test.ts +87 -0
  10. package/src/app/runtime/desktop-deeplink.ts +496 -0
  11. package/src/app.tsx +34 -0
  12. package/src/brokers/account-cache.ts +2 -10
  13. package/src/brokers/broker-portfolio-sync.ts +0 -1
  14. package/src/brokers/broker-ticker-sync.ts +20 -16
  15. package/src/brokers/remote-broker-adapter.ts +3 -0
  16. package/src/capabilities/registry.test.ts +25 -0
  17. package/src/capabilities/registry.ts +22 -5
  18. package/src/cli/commands/automation.ts +1 -3
  19. package/src/cli/commands/command-utils.ts +0 -7
  20. package/src/cli/commands/overview.ts +2 -2
  21. package/src/cli/commands/plugins.ts +34 -17
  22. package/src/cli/index.ts +2 -2
  23. package/src/cli.test.ts +0 -157
  24. package/src/components/chart/bar-chart-renderer.test.ts +87 -12
  25. package/src/components/chart/bar-chart-renderer.ts +111 -55
  26. package/src/components/chart/comparison/renderer.ts +0 -1
  27. package/src/components/chart/comparison-stock-chart/render-output.ts +3 -8
  28. package/src/components/chart/comparison-stock-chart/view.tsx +0 -2
  29. package/src/components/chart/core/index.test.ts +0 -24
  30. package/src/components/chart/core/renderer.ts +0 -4
  31. package/src/components/chart/core/resolution.test.ts +0 -17
  32. package/src/components/chart/core/resolution.ts +0 -26
  33. package/src/components/chart/core/scroll.test.ts +0 -6
  34. package/src/components/chart/core/scroll.ts +0 -22
  35. package/src/components/chart/core/viewport.test.ts +2 -23
  36. package/src/components/chart/core/viewport.ts +0 -27
  37. package/src/components/chart/market-session.ts +1 -1
  38. package/src/components/chart/native/surface/hooks.ts +10 -3
  39. package/src/components/chart/static/bar-chart-surface.tsx +104 -20
  40. package/src/components/chart/stock-chart/data-runtime.ts +0 -1
  41. package/src/components/chart/stock-chart/footer.test.ts +65 -0
  42. package/src/components/chart/stock-chart/footer.ts +68 -13
  43. package/src/components/chart/stock-chart/resolved.tsx +18 -4
  44. package/src/components/chart/stock-chart/viewport/effects.ts +0 -3
  45. package/src/components/command-bar/helpers.test.ts +1 -80
  46. package/src/components/command-bar/helpers.ts +1 -36
  47. package/src/components/command-bar/pane-settings.ts +1 -1
  48. package/src/components/command-bar/pane-templates/items.ts +11 -3
  49. package/src/components/command-bar/panel/runtime.ts +0 -2
  50. package/src/components/command-bar/routes/root/results.ts +1 -1
  51. package/src/components/command-bar/routes/ticker-search/results.ts +0 -11
  52. package/src/components/command-bar/surface/index.test.tsx +52 -16
  53. package/src/components/command-bar/surface/index.tsx +0 -1
  54. package/src/components/command-bar/workflow/ops.ts +81 -20
  55. package/src/components/feed-data-table/stack-view.test.tsx +36 -130
  56. package/src/components/feed-data-table/stack-view.tsx +0 -1
  57. package/src/components/index.ts +1 -0
  58. package/src/components/layout/shell/index.test.tsx +2 -113
  59. package/src/components/layout/shell/index.tsx +0 -1
  60. package/src/components/layout/shell/menu.ts +0 -1
  61. package/src/components/layout/shell/terminal-pointer-runtime.ts +0 -1
  62. package/src/components/onboarding/onboarding-wizard.tsx +3 -3
  63. package/src/components/onboarding/wizard-keyboard.ts +2 -23
  64. package/src/components/price-selector-dialog.tsx +3 -3
  65. package/src/components/ticker/badge/text.tsx +0 -1
  66. package/src/components/toggle-list.tsx +29 -73
  67. package/src/components/ui/button.tsx +4 -1
  68. package/src/components/ui/checkbox.tsx +76 -0
  69. package/src/components/ui/choice-dialog.tsx +15 -3
  70. package/src/components/ui/frame.tsx +1 -1
  71. package/src/components/ui/index.ts +2 -0
  72. package/src/components/ui/message-composer.tsx +4 -0
  73. package/src/components/ui/native-select.tsx +4 -2
  74. package/src/components/ui/page-stack-view.tsx +1 -1
  75. package/src/components/ui/ui.test.tsx +0 -78
  76. package/src/core/state/app/layout-reducer.ts +0 -1
  77. package/src/data/config/layout.ts +3 -3
  78. package/src/data/resource-store.ts +6 -0
  79. package/src/data/sqlite/cache.test.ts +5 -0
  80. package/src/data/ticker-store.ts +0 -7
  81. package/src/market-data/coordinator/auxiliary.ts +1 -44
  82. package/src/market-data/coordinator/entries.ts +0 -1
  83. package/src/market-data/coordinator/events.test.ts +49 -0
  84. package/src/market-data/coordinator/events.ts +25 -4
  85. package/src/market-data/coordinator/index.test.ts +223 -10
  86. package/src/market-data/coordinator/index.ts +41 -21
  87. package/src/market-data/coordinator/quotes.ts +13 -11
  88. package/src/market-data/coordinator/subscriptions.test.ts +1 -0
  89. package/src/market-data/hooks.tsx +17 -13
  90. package/src/market-data/market/freshness.ts +58 -1
  91. package/src/market-data/market/status.ts +19 -1
  92. package/src/market-data/query-store.ts +0 -4
  93. package/src/market-data/quotes/freshness.test.ts +49 -1
  94. package/src/market-data/quotes/freshness.ts +8 -0
  95. package/src/market-data/quotes/resolution.test.ts +185 -0
  96. package/src/market-data/quotes/resolution.ts +96 -10
  97. package/src/market-data/request-types.test.ts +61 -1
  98. package/src/market-data/request-types.ts +42 -8
  99. package/src/market-data/result-types.ts +0 -11
  100. package/src/market-data/selectors.ts +1 -5
  101. package/src/news/aggregator.ts +5 -1
  102. package/src/pane-settings.test.ts +0 -18
  103. package/src/plugins/builtin/account-management/footer.ts +2 -5
  104. package/src/plugins/builtin/account-management/form-components.tsx +175 -59
  105. package/src/plugins/builtin/account-management/keyboard.ts +32 -2
  106. package/src/plugins/builtin/account-management/model.test.ts +99 -4
  107. package/src/plugins/builtin/account-management/model.ts +259 -23
  108. package/src/plugins/builtin/account-management/pane.tsx +784 -155
  109. package/src/plugins/builtin/broker-manager/index.test.tsx +0 -32
  110. package/src/plugins/builtin/buildout/cells.tsx +1 -1
  111. package/src/plugins/builtin/buildout/detail/satellite-images.tsx +1 -1
  112. package/src/plugins/builtin/buildout/detail/ui.tsx +0 -1
  113. package/src/plugins/builtin/buildout/format.ts +2 -11
  114. package/src/plugins/builtin/chat/chat.test.tsx +127 -1
  115. package/src/plugins/builtin/chat/content/channel-navigation.ts +1 -21
  116. package/src/plugins/builtin/chat/content/composer-cursor.ts +29 -0
  117. package/src/plugins/builtin/chat/content/composer-runtime.ts +22 -24
  118. package/src/plugins/builtin/chat/content/composer.tsx +84 -1
  119. package/src/plugins/builtin/chat/content/index.tsx +153 -16
  120. package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -1
  121. package/src/plugins/builtin/chat/content/mentions.test.ts +74 -0
  122. package/src/plugins/builtin/chat/content/mentions.ts +88 -0
  123. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +264 -0
  124. package/src/plugins/builtin/chat/content/shortcuts.ts +47 -1
  125. package/src/plugins/builtin/chat/content/snapshot.ts +7 -0
  126. package/src/plugins/builtin/chat/content/transcript.tsx +0 -5
  127. package/src/plugins/builtin/chat/controller/index.ts +0 -10
  128. package/src/plugins/builtin/chat/controller.test.ts +5 -5
  129. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +36 -0
  130. package/src/plugins/builtin/chat/message/profile-popover.tsx +124 -20
  131. package/src/plugins/builtin/chat/sidebar.test.tsx +83 -6
  132. package/src/plugins/builtin/chat/sidebar.tsx +26 -2
  133. package/src/plugins/builtin/chat/test-harness.tsx +22 -12
  134. package/src/plugins/builtin/cloud/plugin.tsx +8 -1
  135. package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -1
  136. package/src/plugins/builtin/congress-trades/model.ts +2 -7
  137. package/src/plugins/builtin/congress-trades/pane.tsx +0 -1
  138. package/src/plugins/builtin/correlation/index.tsx +4 -1
  139. package/src/plugins/builtin/correlation/relationship/controls.tsx +8 -2
  140. package/src/plugins/builtin/correlation/relationship/model.ts +22 -2
  141. package/src/plugins/builtin/correlation/relationship/pane.tsx +9 -3
  142. package/src/plugins/builtin/earnings/index.tsx +0 -9
  143. package/src/plugins/builtin/econ/index.tsx +1 -5
  144. package/src/plugins/builtin/help/index.tsx +2 -2
  145. package/src/plugins/builtin/holders/pane.tsx +0 -5
  146. package/src/plugins/builtin/insider/index.tsx +1 -8
  147. package/src/plugins/builtin/kelly-sizer/index.test.tsx +0 -26
  148. package/src/plugins/builtin/kelly-sizer/index.tsx +0 -2
  149. package/src/plugins/builtin/market-movers/index.tsx +0 -2
  150. package/src/plugins/builtin/news/index.tsx +0 -1
  151. package/src/plugins/builtin/news/wire/news/table.tsx +0 -11
  152. package/src/plugins/builtin/options/view.test.tsx +1 -38
  153. package/src/plugins/builtin/plugin-groups.ts +17 -3
  154. package/src/plugins/builtin/portfolio-list/column-values.ts +15 -8
  155. package/src/plugins/builtin/portfolio-list/index.test.ts +52 -25
  156. package/src/plugins/builtin/portfolio-list/metrics.test.ts +44 -7
  157. package/src/plugins/builtin/portfolio-list/pane/data.test.ts +54 -0
  158. package/src/plugins/builtin/portfolio-list/pane/data.ts +50 -3
  159. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +314 -16
  160. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -4
  161. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +83 -24
  162. package/src/plugins/builtin/sec/filing-documents.ts +0 -11
  163. package/src/plugins/builtin/sec/index.tsx +0 -2
  164. package/src/plugins/builtin/substack/article-detail.tsx +1 -1
  165. package/src/plugins/builtin/thirteenf/api.ts +0 -15
  166. package/src/plugins/builtin/thirteenf/pane.tsx +1 -3
  167. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +42 -6
  168. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +415 -64
  169. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +23 -2
  170. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +8 -1
  171. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +3 -1
  172. package/src/plugins/builtin/ticker-detail/data-panes.test.ts +95 -1
  173. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +0 -7
  174. package/src/plugins/builtin/ticker-detail/index.tsx +2 -0
  175. package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +5 -34
  176. package/src/plugins/builtin/world-indices/index.tsx +0 -4
  177. package/src/plugins/builtin/world-indices/model.ts +0 -7
  178. package/src/plugins/ibkr/account-cache.ts +1 -1
  179. package/src/plugins/ibkr/gateway/market-data.ts +12 -5
  180. package/src/plugins/ibkr/gateway/service/index.test.ts +31 -0
  181. package/src/plugins/ibkr/portfolio-performance.ts +1 -1
  182. package/src/plugins/ibkr/trade/utils.ts +1 -7
  183. package/src/plugins/loader.ts +2 -1
  184. package/src/plugins/prediction-markets/detail.test.tsx +0 -34
  185. package/src/plugins/registry/context.ts +15 -0
  186. package/src/plugins/registry/contributions.ts +22 -11
  187. package/src/plugins/registry/index.test.ts +44 -0
  188. package/src/plugins/registry/index.ts +75 -26
  189. package/src/remote/controller.ts +1 -7
  190. package/src/remote/json-patch.ts +15 -1
  191. package/src/remote/server.ts +16 -6
  192. package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
  193. package/src/renderers/electrobun/bun/core-capabilities.ts +2 -0
  194. package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +14 -0
  195. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +8 -0
  196. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
  197. package/src/renderers/electrobun/bun/index.ts +42 -0
  198. package/src/renderers/electrobun/shared/protocol.ts +5 -0
  199. package/src/renderers/electrobun/view/backend-rpc.ts +29 -0
  200. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
  201. package/src/renderers/electrobun/view/data-table/dom.ts +0 -1
  202. package/src/renderers/electrobun/view/desktop/controls.tsx +135 -4
  203. package/src/renderers/electrobun/view/desktop/list-view.tsx +38 -25
  204. package/src/renderers/electrobun/view/desktop-deeplink-bridge.ts +10 -0
  205. package/src/renderers/electrobun/view/host/box.tsx +0 -2
  206. package/src/renderers/electrobun/view/host/mouse.ts +0 -11
  207. package/src/renderers/electrobun/view/host/scroll-box.tsx +1 -2
  208. package/src/renderers/electrobun/view/host/text.tsx +1 -2
  209. package/src/renderers/electrobun/view/http-fetch.ts +15 -2
  210. package/src/renderers/electrobun/view/main.tsx +3 -0
  211. package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -18
  212. package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +50 -0
  213. package/src/renderers/electrobun/view/remote/capability-invoker.ts +36 -0
  214. package/src/renderers/electrobun/view/ui-host.tsx +2 -0
  215. package/src/renderers/opentui/host.tsx +11 -3
  216. package/src/renderers/opentui/input-host.tsx +5 -1
  217. package/src/sources/gloomberb-cloud/index.test.ts +0 -72
  218. package/src/sources/gloomberb-cloud/index.ts +1 -30
  219. package/src/sources/gloomberb-cloud/news.ts +0 -12
  220. package/src/sources/provider-router/batches.ts +11 -3
  221. package/src/sources/provider-router/cached-financials.test.ts +123 -0
  222. package/src/sources/provider-router/financial-routes.ts +77 -5
  223. package/src/sources/provider-router/financials.test.ts +49 -0
  224. package/src/sources/provider-router/financials.ts +40 -3
  225. package/src/sources/provider-router/index.test.ts +314 -0
  226. package/src/sources/provider-router/primary.ts +17 -3
  227. package/src/sources/provider-router/search.ts +5 -0
  228. package/src/sources/provider-router/streaming.ts +6 -1
  229. package/src/sources/provider-router/supplemental.ts +4 -3
  230. package/src/sources/sec-edgar.test.ts +42 -0
  231. package/src/sources/sec-edgar.ts +1 -2
  232. package/src/sources/yahoo-finance/history.ts +5 -1
  233. package/src/sources/yahoo-finance/mappers.test.ts +29 -0
  234. package/src/sources/yahoo-finance/mappers.ts +2 -1
  235. package/src/sources/yahoo-finance/options.ts +59 -14
  236. package/src/sources/yahoo-finance/quote-summary.ts +4 -1
  237. package/src/sources/yahoo-finance/requests.ts +2 -1
  238. package/src/sources/yahoo-finance/snapshots.ts +13 -2
  239. package/src/sources/yahoo-finance.test.ts +40 -21
  240. package/src/sources/yahoo-finance.ts +19 -10
  241. package/src/state/app/context/index.test.tsx +0 -81
  242. package/src/state/app/context/selector.test.tsx +6 -2
  243. package/src/state/hooks/quote-streaming.test.ts +14 -1
  244. package/src/state/hooks/quote-streaming.ts +5 -4
  245. package/src/sync/controller.test.ts +31 -0
  246. package/src/sync/controller.ts +300 -0
  247. package/src/sync/core-contributors.test.ts +251 -0
  248. package/src/sync/core-contributors.ts +400 -0
  249. package/src/sync/profile-analytics.ts +39 -0
  250. package/src/sync/react.ts +53 -0
  251. package/src/sync/types.ts +77 -0
  252. package/src/test-support/data-provider.ts +0 -1
  253. package/src/types/desktop-deeplink.ts +7 -0
  254. package/src/types/plugin.ts +3 -0
  255. package/src/ui/host.tsx +15 -1
  256. package/src/ui/index.tsx +2 -0
  257. package/src/updater.test.ts +0 -21
  258. package/src/updater.ts +20 -16
  259. package/src/utils/async-deadline.ts +26 -0
  260. package/src/utils/exchanges.test.ts +11 -1
  261. package/src/utils/exchanges.ts +5 -1
  262. package/src/utils/selection-clipboard.ts +2 -2
  263. package/src/version.ts +1 -1
  264. package/src/components/layout/pane/header.test.tsx +0 -39
  265. package/src/plugins/builtin/chat/channels.test.ts +0 -28
  266. package/src/plugins/builtin/chat/index.tsx +0 -4
  267. package/src/plugins/builtin/cloud-tweets/index.ts +0 -1
  268. package/src/plugins/builtin/earnings/index.test.ts +0 -12
  269. package/src/plugins/builtin/help/index.test.tsx +0 -242
  270. package/src/plugins/builtin/substack/article-detail.test.tsx +0 -10
  271. package/src/renderers/electrobun/bun/application-menu/index.test.ts +0 -21
  272. package/src/renderers/electrobun/bun/window/focus.test.ts +0 -35
  273. package/src/renderers/electrobun/view/host/mouse.test.ts +0 -37
  274. package/src/renderers/electrobun/view/host/text.test.ts +0 -22
  275. package/src/renderers/opentui/dialog-host.test.tsx +0 -44
  276. /package/src/{plugins/ibkr → utils}/hash.ts +0 -0
@@ -14,7 +14,6 @@ import { cloneLayout, createDefaultConfig, TICKER_RESEARCH_PANE_ID, type LayoutC
14
14
  import type { PluginRegistry } from "../../../plugins/registry";
15
15
  import type { PaneProps } from "../../../types/plugin";
16
16
  import { Textarea } from "../../../ui";
17
- import { Header } from "../header";
18
17
  import {
19
18
  buildNativeWindowState,
20
19
  resolvePaneManagementShortcut,
@@ -70,7 +69,7 @@ function createShellPluginRegistry(options?: {
70
69
  } as unknown as PluginRegistry;
71
70
  }
72
71
 
73
- async function emitKeypress(event: { name?: string; sequence?: string; ctrl?: boolean; meta?: boolean; shift?: boolean; alt?: boolean }) {
72
+ async function emitKeypress(event: { name?: string; sequence?: string; ctrl?: boolean; meta?: boolean; super?: boolean; shift?: boolean; alt?: boolean }) {
74
73
  await act(async () => {
75
74
  const keyEvent = {
76
75
  ctrl: false,
@@ -184,112 +183,6 @@ function findUpdateLayout(actions: ShellTestAction[]) {
184
183
  return actions.find((action) => action.type === "UPDATE_LAYOUT");
185
184
  }
186
185
 
187
- function HeaderHarness({
188
- updateAvailable = null,
189
- updateProgress = null,
190
- updateCheckInProgress = false,
191
- updateNotice = null,
192
- }: {
193
- updateAvailable?: ReturnType<typeof createInitialState>["updateAvailable"];
194
- updateProgress?: ReturnType<typeof createInitialState>["updateProgress"];
195
- updateCheckInProgress?: boolean;
196
- updateNotice?: string | null;
197
- }) {
198
- const initialState = createInitialState(createDefaultConfig("/tmp/gloomberb-header-test"));
199
- initialState.updateAvailable = updateAvailable;
200
- initialState.updateProgress = updateProgress;
201
- initialState.updateCheckInProgress = updateCheckInProgress;
202
- initialState.updateNotice = updateNotice;
203
- const [state, dispatch] = useReducer(appReducer, initialState);
204
-
205
- return (
206
- <AppContext value={{ state, dispatch }}>
207
- <Header />
208
- </AppContext>
209
- );
210
- }
211
-
212
- describe("Header", () => {
213
- test("shows automatic self-update status for standalone binaries", async () => {
214
- testSetup = await testRender(
215
- <HeaderHarness updateAvailable={{
216
- version: "0.3.0",
217
- tagName: "v0.3.0",
218
- downloadUrl: "https://example.com/gloomberb",
219
- publishedAt: "2026-04-01T00:00:00.000Z",
220
- updateAction: { kind: "self" },
221
- }} />,
222
- { width: 120, height: 2 },
223
- );
224
-
225
- await testSetup.renderOnce();
226
- const frame = testSetup!.captureCharFrame();
227
-
228
- expect(frame).toContain("v0.3.0 available");
229
- expect(frame).toContain("starting download");
230
- expect(frame).not.toContain("press u to update");
231
- });
232
-
233
- test("shows the manual npm command when self-update is disabled", async () => {
234
- testSetup = await testRender(
235
- <HeaderHarness updateAvailable={{
236
- version: "0.3.0",
237
- tagName: "v0.3.0",
238
- downloadUrl: "https://example.com/gloomberb",
239
- publishedAt: "2026-04-01T00:00:00.000Z",
240
- updateAction: { kind: "manual", command: "npm install -g gloomberb@latest" },
241
- }} />,
242
- { width: 120, height: 2 },
243
- );
244
-
245
- await testSetup.renderOnce();
246
- const frame = testSetup!.captureCharFrame();
247
-
248
- expect(frame).toContain("v0.3.0 available");
249
- expect(frame).toContain("run npm install -g gloomberb@latest");
250
- expect(frame).not.toContain("press u to update");
251
- });
252
-
253
- test("shows update download progress and notices", async () => {
254
- testSetup = await testRender(
255
- <HeaderHarness
256
- updateAvailable={{
257
- version: "0.3.0",
258
- tagName: "v0.3.0",
259
- downloadUrl: "https://example.com/gloomberb",
260
- publishedAt: "2026-04-01T00:00:00.000Z",
261
- updateAction: { kind: "self" },
262
- }}
263
- updateProgress={{ phase: "downloading", percent: 42 }}
264
- />,
265
- { width: 120, height: 2 },
266
- );
267
-
268
- await testSetup.renderOnce();
269
- expect(testSetup.captureCharFrame()).toContain("Downloading v0.3.0: 42%");
270
-
271
- testSetup.renderer.destroy();
272
-
273
- testSetup = await testRender(
274
- <HeaderHarness updateCheckInProgress />,
275
- { width: 120, height: 2 },
276
- );
277
-
278
- await testSetup.renderOnce();
279
- expect(testSetup.captureCharFrame()).toContain("Checking for updates...");
280
-
281
- testSetup.renderer.destroy();
282
-
283
- testSetup = await testRender(
284
- <HeaderHarness updateNotice="Already on v0.3.1" />,
285
- { width: 120, height: 2 },
286
- );
287
-
288
- await testSetup.renderOnce();
289
- expect(testSetup.captureCharFrame()).toContain("Already on v0.3.1");
290
- });
291
- });
292
-
293
186
  describe("Shell", () => {
294
187
  test("uses the desktop titlebar overlay height for shell chrome math", () => {
295
188
  expect(resolveAppHeaderHeightCells({ titleBarOverlay: true, cellHeightPx: 18 })).toBe(28 / 18);
@@ -716,7 +609,6 @@ describe("Shell", () => {
716
609
  await emitKeypress({
717
610
  name: "f",
718
611
  key: "f",
719
- meta: true,
720
612
  super: true,
721
613
  shift: true,
722
614
  targetEditable: true,
@@ -1330,10 +1222,7 @@ describe("Shell", () => {
1330
1222
  );
1331
1223
 
1332
1224
  await testSetup.renderOnce();
1333
- await act(async () => {
1334
- testSetup!.mockInput.pressKey("w", { meta: true });
1335
- await testSetup!.renderOnce();
1336
- });
1225
+ await emitKeypress({ name: "w", super: true });
1337
1226
 
1338
1227
  const updateLayout = actions.find((action) => action.type === "UPDATE_LAYOUT");
1339
1228
  expect(updateLayout?.layout.instances.map((instance: { instanceId: string }) => instance.instanceId)).toEqual(["portfolio-list:main"]);
@@ -212,7 +212,6 @@ export function Shell({
212
212
 
213
213
  const {
214
214
  dockedPanes,
215
- floatingPanes,
216
215
  paneMap,
217
216
  visibleFloatingPanes,
218
217
  } = useShellResolvedPanes({
@@ -3,7 +3,6 @@ import {
3
3
  applyDrop,
4
4
  floatPane,
5
5
  removePane,
6
- type LayoutBounds,
7
6
  type ResolvedPane,
8
7
  } from "../../../plugins/pane-manager";
9
8
  import type { PluginRegistry } from "../../../plugins/registry";
@@ -6,7 +6,6 @@ import type {
6
6
  LayoutBounds,
7
7
  ResolvedPane,
8
8
  } from "../../../plugins/pane-manager";
9
- import type { LayoutConfig } from "../../../types/config";
10
9
  import {
11
10
  constrainFloatingRectToBounds,
12
11
  pointInRect,
@@ -81,9 +81,9 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
81
81
  }
82
82
 
83
83
  useEffect(() => {
84
- if (editingField) {
85
- setTimeout(() => inputRef.current?.focus(), 10);
86
- }
84
+ if (!editingField) return;
85
+ const focusTimer = setTimeout(() => inputRef.current?.focus?.(), 10);
86
+ return () => clearTimeout(focusTimer);
87
87
  }, [editingField, portfolioSub, brokerFieldIdx]);
88
88
 
89
89
  useEffect(() => {
@@ -72,7 +72,7 @@ export function useOnboardingKeyboard({
72
72
  }
73
73
 
74
74
  if (editingField) {
75
- if (event.name === "return") {
75
+ if (event.name === "enter" || event.name === "return") {
76
76
  setEditingField(false);
77
77
  if (portfolioSub === "broker-fields" && selectedBrokerId) {
78
78
  const currentField = activeBrokerFields[brokerFieldIdx];
@@ -188,28 +188,7 @@ export function useOnboardingKeyboard({
188
188
  }
189
189
  }
190
190
  nextStep();
191
- } else if (isBackNavigationKey(event)) {
192
- if (step === "portfolio" && portfolioSub === "broker-sync") {
193
- resetBrokerSync();
194
- setPortfolioSub("broker-fields");
195
- return;
196
- }
197
- if (step === "portfolio" && portfolioSub === "broker-fields") {
198
- setPortfolioSub("broker-setup");
199
- return;
200
- }
201
- if (step === "portfolio" && portfolioSub === "broker-setup") {
202
- setPortfolioSub("broker-fields");
203
- setBrokerFieldIdx(0);
204
- return;
205
- }
206
- if (step === "portfolio" && portfolioSub !== "choose") {
207
- setPortfolioSub("choose");
208
- setBrokerFieldIdx(0);
209
- return;
210
- }
211
- prevStep();
212
- } else if (event.name === "left") {
191
+ } else if (isBackNavigationKey(event) || event.name === "left") {
213
192
  if (step === "portfolio" && portfolioSub === "broker-sync") {
214
193
  resetBrokerSync();
215
194
  setPortfolioSub("broker-fields");
@@ -87,7 +87,7 @@ export function PriceSelectorDialog({
87
87
  const inputRef = useRef<InputRenderable>(null);
88
88
 
89
89
  useEffect(() => {
90
- if (mode === "input") inputRef.current?.focus();
90
+ if (mode === "input") inputRef.current?.focus?.();
91
91
  }, [mode]);
92
92
 
93
93
  useDialogKeyboard((event) => {
@@ -108,7 +108,7 @@ export function PriceSelectorDialog({
108
108
  setMode("input");
109
109
  setIndex(CUSTOM_INDEX);
110
110
  }
111
- } else if (event.name === "return") {
111
+ } else if (event.name === "enter" || event.name === "return") {
112
112
  if (presets[index]) {
113
113
  resolve(String(presets[index].value));
114
114
  }
@@ -124,7 +124,7 @@ export function PriceSelectorDialog({
124
124
  if (event.name === "up" || (event.name === "tab" && presets.length > 0)) {
125
125
  setMode("list");
126
126
  setIndex(presets.length - 1);
127
- } else if (event.name === "return") {
127
+ } else if (event.name === "enter" || event.name === "return") {
128
128
  resolve(customValue.trim());
129
129
  }
130
130
  }
@@ -4,7 +4,6 @@ import { TickerBadge } from "./index";
4
4
  import { ExternalLinkText, openUrl } from "../../ui";
5
5
  import { tokenizeInlineContent } from "../../../utils/inline-content-tokenizer";
6
6
  import type { InlineTickerCatalogEntry } from "../../../state/hooks/inline-tickers";
7
- import { displayWidth } from "../../../utils/format";
8
7
  import { splitLongTextSegmentByDisplayWidth } from "../../../utils/text-wrap";
9
8
  import { blendHex, colors } from "../../../theme/colors";
10
9
 
@@ -1,6 +1,6 @@
1
- import { Box, Text, useUiHost } from "../ui";
2
- import { TextAttributes } from "../ui";
3
- import { blendHex, colors } from "../theme/colors";
1
+ import { Box, useUiHost } from "../ui";
2
+ import { colors } from "../theme/colors";
3
+ import { Checkbox } from "./ui/checkbox";
4
4
  import { ListView, type ListRowState, type ListViewItem } from "./ui/list-view";
5
5
 
6
6
  interface ToggleListItem {
@@ -36,23 +36,14 @@ function DesktopToggleRow({
36
36
  state,
37
37
  rowIdPrefix,
38
38
  enabled,
39
+ onPress,
39
40
  }: {
40
41
  item: ListViewItem;
41
42
  state: ListRowState;
42
43
  rowIdPrefix?: string;
43
44
  enabled: boolean;
45
+ onPress: () => void;
44
46
  }) {
45
- const checkboxBorder = enabled
46
- ? colors.borderFocused
47
- : state.selected
48
- ? colors.textMuted
49
- : colors.border;
50
- const textColor = state.disabled
51
- ? colors.textMuted
52
- : state.selected
53
- ? colors.text
54
- : colors.textDim;
55
-
56
47
  return (
57
48
  <Box
58
49
  id={rowIdPrefix ? `${rowIdPrefix}:${item.id}` : undefined}
@@ -67,48 +58,15 @@ function DesktopToggleRow({
67
58
  opacity: state.disabled ? 0.55 : 1,
68
59
  }}
69
60
  >
70
- <Box
71
- alignItems="center"
72
- justifyContent="center"
73
- backgroundColor={enabled ? colors.borderFocused : "transparent"}
74
- style={{
75
- width: 16,
76
- height: 16,
77
- minWidth: 16,
78
- alignSelf: "center",
79
- border: `1px solid ${checkboxBorder}`,
80
- borderRadius: 4,
81
- boxShadow: enabled
82
- ? `inset 0 1px 0 ${blendHex(colors.borderFocused, colors.textBright, 0.28)}`
83
- : `inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.05)}`,
84
- }}
85
- >
86
- {enabled && (
87
- <Text
88
- fg={colors.bg}
89
- attributes={TextAttributes.BOLD}
90
- style={{ fontSize: 12, lineHeight: "14px", fontWeight: 800 }}
91
- >
92
- {"✓"}
93
- </Text>
94
- )}
95
- </Box>
96
- <Box minWidth={0} flexShrink={1}>
97
- <Text
98
- fg={textColor}
99
- attributes={state.selected ? TextAttributes.BOLD : 0}
100
- style={{
101
- display: "block",
102
- overflow: "hidden",
103
- textOverflow: "ellipsis",
104
- whiteSpace: "nowrap",
105
- fontWeight: state.selected ? 700 : 500,
106
- lineHeight: "16px",
107
- }}
108
- >
109
- {item.label}
110
- </Text>
111
- </Box>
61
+ <Checkbox
62
+ label={item.label}
63
+ checked={enabled}
64
+ disabled={state.disabled}
65
+ active={state.selected}
66
+ width="100%"
67
+ variant="desktop"
68
+ onChange={onPress}
69
+ />
112
70
  </Box>
113
71
  );
114
72
  }
@@ -161,6 +119,13 @@ export function ToggleList({
161
119
  }}
162
120
  renderRow={(item, state, index) => {
163
121
  const toggleItem = items.find((entry) => entry.id === item.id);
122
+ const activate = (event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
123
+ event?.stopPropagation?.();
124
+ event?.preventDefault?.();
125
+ if (state.disabled) return;
126
+ onSelect?.(index);
127
+ onToggle?.(item.id);
128
+ };
164
129
  if (isDesktopWeb) {
165
130
  return (
166
131
  <DesktopToggleRow
@@ -168,33 +133,24 @@ export function ToggleList({
168
133
  state={state}
169
134
  rowIdPrefix={rowIdPrefix}
170
135
  enabled={toggleItem?.enabled === true}
136
+ onPress={() => activate()}
171
137
  />
172
138
  );
173
139
  }
174
140
 
175
- const checked = toggleItem?.enabled ? "\u2713" : " ";
176
- const activate = (event: any) => {
177
- event.stopPropagation?.();
178
- if (state.disabled) return;
179
- onSelect?.(index);
180
- onToggle?.(item.id);
181
- };
182
141
  return (
183
142
  <Box
184
143
  id={rowIdPrefix ? `${rowIdPrefix}:${item.id}` : undefined}
185
144
  flexDirection="row"
186
145
  onMouseDown={activate}
187
146
  >
188
- <Text fg={state.selected ? colors.selectedText : colors.textDim}>
189
- {state.selected ? "\u25b8 " : " "}
190
- </Text>
191
- <Text
192
- fg={state.selected ? colors.text : colors.textDim}
193
- attributes={state.selected ? TextAttributes.BOLD : 0}
194
- onMouseDown={activate}
195
- >
196
- {`[${checked}] ${item.label}`}
197
- </Text>
147
+ <Checkbox
148
+ label={item.label}
149
+ checked={toggleItem?.enabled === true}
150
+ disabled={state.disabled}
151
+ active={state.selected}
152
+ onChange={() => activate()}
153
+ />
198
154
  </Box>
199
155
  );
200
156
  }}
@@ -14,6 +14,7 @@ export interface ButtonProps {
14
14
  active?: boolean;
15
15
  shortcut?: string;
16
16
  width?: number;
17
+ height?: number | string;
17
18
  }
18
19
 
19
20
  function resolveButtonColors(variant: ButtonVariant, active: boolean, disabled: boolean) {
@@ -45,6 +46,7 @@ export function Button({
45
46
  active = false,
46
47
  shortcut,
47
48
  width,
49
+ height,
48
50
  }: ButtonProps) {
49
51
  useRemoteUiNode({
50
52
  role: "button",
@@ -68,6 +70,7 @@ export function Button({
68
70
  active={active}
69
71
  shortcut={shortcut}
70
72
  width={width}
73
+ height={height}
71
74
  />
72
75
  );
73
76
  }
@@ -77,7 +80,7 @@ export function Button({
77
80
  return (
78
81
  <Box
79
82
  width={width}
80
- height={1}
83
+ height={height ?? 1}
81
84
  flexDirection="row"
82
85
  backgroundColor={palette.bg}
83
86
  onMouseDown={() => {
@@ -0,0 +1,76 @@
1
+ import { type ComponentType } from "react";
2
+ import { Box, Text, TextAttributes, useUiHost, type HostCheckboxProps } from "../../ui";
3
+ import { colors, hoverBg } from "../../theme/colors";
4
+ import { useRemoteUiNode } from "../../remote/semantic-tree";
5
+
6
+ export type CheckboxProps = HostCheckboxProps;
7
+
8
+ export function Checkbox({
9
+ label,
10
+ displayLabel,
11
+ checked,
12
+ onChange,
13
+ disabled = false,
14
+ active = false,
15
+ description,
16
+ width,
17
+ variant = "default",
18
+ }: CheckboxProps) {
19
+ useRemoteUiNode({
20
+ role: "checkbox",
21
+ label,
22
+ disabled,
23
+ actions: {
24
+ toggle: () => {
25
+ if (!disabled) onChange?.(!checked);
26
+ },
27
+ press: () => {
28
+ if (!disabled) onChange?.(!checked);
29
+ },
30
+ },
31
+ metadata: { checked, active, variant },
32
+ });
33
+
34
+ const HostCheckbox = useUiHost().Checkbox as ComponentType<CheckboxProps> | undefined;
35
+ if (HostCheckbox) {
36
+ return (
37
+ <HostCheckbox
38
+ label={label}
39
+ displayLabel={displayLabel}
40
+ checked={checked}
41
+ onChange={onChange}
42
+ disabled={disabled}
43
+ active={active}
44
+ description={description}
45
+ width={width}
46
+ variant={variant}
47
+ />
48
+ );
49
+ }
50
+
51
+ const marker = checked ? "\u2713" : " ";
52
+ const visibleLabel = displayLabel ?? label;
53
+ const fg = disabled ? colors.textMuted : active ? colors.textBright : colors.text;
54
+ const descriptionWidth = typeof width === "number" ? Math.max(20, width - 2) : 28;
55
+ return (
56
+ <Box
57
+ flexDirection="column"
58
+ width={width}
59
+ backgroundColor={active && !disabled ? hoverBg() : undefined}
60
+ onMouseDown={(event?: { preventDefault?: () => void; stopPropagation?: () => void }) => {
61
+ event?.preventDefault?.();
62
+ event?.stopPropagation?.();
63
+ if (!disabled) onChange?.(!checked);
64
+ }}
65
+ >
66
+ <Text fg={fg} attributes={active ? TextAttributes.BOLD : 0}>
67
+ {`${active ? "> " : " "}[${marker}] ${visibleLabel}`}
68
+ </Text>
69
+ {description ? (
70
+ <Text fg={colors.textMuted} wrapText width={descriptionWidth}>
71
+ {description}
72
+ </Text>
73
+ ) : null}
74
+ </Box>
75
+ );
76
+ }
@@ -37,6 +37,15 @@ function getInitialChoiceIndex(choices: ChoiceDialogChoice[], selectedChoiceId:
37
37
  return selectedIndex >= 0 ? selectedIndex : 0;
38
38
  }
39
39
 
40
+ function choiceDialogWidth(title: string, choices: ChoiceDialogChoice[]): number {
41
+ const contentWidth = Math.max(
42
+ title.length,
43
+ ...choices.map((choice) => choice.label.length + (choice.detail ? choice.detail.length + 4 : 0)),
44
+ ...choices.map((choice) => getChoiceDescription(choice).length),
45
+ );
46
+ return Math.max(34, Math.min(76, contentWidth + 4));
47
+ }
48
+
40
49
  export function ChoiceDialog({
41
50
  resolve,
42
51
  title,
@@ -57,6 +66,7 @@ export function ChoiceDialog({
57
66
  detail: choice.detail,
58
67
  disabled: choice.disabled,
59
68
  })), [choices]);
69
+ const width = useMemo(() => choiceDialogWidth(title, choices), [choices, title]);
60
70
 
61
71
  useEffect(() => {
62
72
  setIndex((current) => clampChoiceIndex(current, choices.length));
@@ -81,19 +91,21 @@ export function ChoiceDialog({
81
91
  });
82
92
 
83
93
  return (
84
- <DialogFrame title={title} footer={footer}>
85
- <Box flexDirection="column">
94
+ <DialogFrame title={title} footer={footer} showTitleDivider={false}>
95
+ <Box flexDirection="column" width={width}>
86
96
  <ListView
87
97
  items={items}
88
98
  selectedIndex={selectedIndex}
89
99
  bgColor={bgColor}
90
100
  emptyMessage="No choices."
101
+ rowGap={0}
102
+ surface="framed"
91
103
  selectOnHover
92
104
  onSelect={setIndex}
93
105
  onActivate={(_, nextIndex) => activateChoice(choices[nextIndex])}
94
106
  />
95
107
  <Box height={1} />
96
- <Text fg={colors.textDim}>{getChoiceDescription(selectedChoice)}</Text>
108
+ <Text fg={colors.textDim} wrapText width={width}>{getChoiceDescription(selectedChoice)}</Text>
97
109
  </Box>
98
110
  </DialogFrame>
99
111
  );
@@ -11,7 +11,7 @@ export interface DialogFrameProps {
11
11
  showTitleDivider?: boolean;
12
12
  }
13
13
 
14
- export function DialogFrame({ title, children, footer, showTitleDivider = true }: DialogFrameProps) {
14
+ export function DialogFrame({ title, children, footer, showTitleDivider = false }: DialogFrameProps) {
15
15
  useThemeColors();
16
16
  const HostDialogFrame = useUiHost().DialogFrame as ComponentType<DialogFrameProps> | undefined;
17
17
  if (HostDialogFrame) {
@@ -6,6 +6,8 @@ export { DataTable } from "./data-table";
6
6
  export type { DataTableCell, DataTableColumn, DataTableProps } from "./data-table";
7
7
 
8
8
  export { Button } from "./button";
9
+ export { Checkbox } from "./checkbox";
10
+ export type { CheckboxProps } from "./checkbox";
9
11
  export { ShortcutHint } from "./shortcut-hint";
10
12
 
11
13
  export { MultiSelectDialogButton, MultiSelectDialogContent } from "./multi-select/dialog";
@@ -17,6 +17,7 @@ export interface MessageComposerProps {
17
17
  terminalBottomInset?: number;
18
18
  onFocusRequest?: () => void;
19
19
  onInput?: (value: string) => void;
20
+ onCursorChange?: () => void;
20
21
  onSubmit?: () => void;
21
22
  keyBindings?: Array<Record<string, unknown>>;
22
23
  wrapText?: boolean;
@@ -52,6 +53,7 @@ export function MessageComposer({
52
53
  terminalBottomInset = 0,
53
54
  onFocusRequest,
54
55
  onInput,
56
+ onCursorChange,
55
57
  onSubmit,
56
58
  keyBindings,
57
59
  wrapText = false,
@@ -91,6 +93,7 @@ export function MessageComposer({
91
93
  terminalBottomInset={terminalBottomInset}
92
94
  onFocusRequest={onFocusRequest}
93
95
  onInput={onInput}
96
+ onCursorChange={onCursorChange}
94
97
  onSubmit={onSubmit}
95
98
  keyBindings={keyBindings}
96
99
  wrapText={wrapText}
@@ -149,6 +152,7 @@ export function MessageComposer({
149
152
  focusedBackgroundColor={colors.bg}
150
153
  cursorColor={colors.textBright}
151
154
  onInput={handleInput}
155
+ onCursorChange={onCursorChange}
152
156
  keyBindings={keyBindings}
153
157
  onSubmit={onSubmit}
154
158
  wrapText={wrapText}
@@ -17,6 +17,7 @@ export interface NativeSelectProps {
17
17
  value: string;
18
18
  options: NativeSelectOption[];
19
19
  width?: number | string;
20
+ height?: number;
20
21
  includeUnsetOption?: boolean;
21
22
  selectRef?: (element: NativeSelectElement | null) => void;
22
23
  onFocus?: () => void;
@@ -41,6 +42,7 @@ export function NativeSelect({
41
42
  value,
42
43
  options,
43
44
  width = 184,
45
+ height = 28,
44
46
  includeUnsetOption = false,
45
47
  selectRef,
46
48
  onFocus,
@@ -49,7 +51,7 @@ export function NativeSelect({
49
51
  const hasCurrentValue = options.some((option) => option.value === value);
50
52
  const style: CSSProperties = {
51
53
  width,
52
- height: 28,
54
+ height,
53
55
  color: colors.text,
54
56
  backgroundColor: blendHex(colors.panel, colors.textBright, 0.06),
55
57
  border: `1px solid ${colors.border}`,
@@ -66,7 +68,7 @@ export function NativeSelect({
66
68
 
67
69
  return (
68
70
  <Box
69
- height="28px"
71
+ height={`${height}px`}
70
72
  flexDirection="row"
71
73
  alignItems="center"
72
74
  onMouseDown={(event: any) => {
@@ -88,7 +88,7 @@ export function PageStackView({
88
88
  >
89
89
  <Box height={1} flexDirection="row">
90
90
  <Box
91
- onMouseDown={(event) => {
91
+ onMouseDown={(event: { preventDefault?: () => void; stopPropagation?: () => void }) => {
92
92
  goBack(event);
93
93
  }}
94
94
  backgroundColor={colors.selected}