gloomberb 0.15.6 → 0.15.7

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 (259) hide show
  1. package/README.md +2 -2
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/src/api-client/data.ts +37 -10
  5. package/src/api-client/estimate-revisions.ts +1 -1
  6. package/src/api-client/futures-curve.ts +1 -1
  7. package/src/api-client/index.ts +6 -0
  8. package/src/api-client/market-discovery.ts +84 -0
  9. package/src/api-client/paths.ts +14 -11
  10. package/src/api-client/request.ts +12 -2
  11. package/src/api-client/tape.ts +2 -2
  12. package/src/api-client/types.ts +2 -0
  13. package/src/app/dialog-bridge.tsx +33 -0
  14. package/src/app/pane-runtime/index.tsx +7 -2
  15. package/src/app/pane-runtime/pane-settings-runtime.tsx +2 -1
  16. package/src/app/pane-runtime/pane-template-runtime.ts +8 -28
  17. package/src/app/pane-runtime/plugin-bindings.ts +34 -12
  18. package/src/app/pane-runtime/ticker-open-runtime.ts +5 -3
  19. package/src/app/runtime/broker-import.ts +1 -1
  20. package/src/app/runtime/desktop-menu.ts +9 -6
  21. package/src/app/runtime/plugin-bindings.ts +7 -1
  22. package/src/app/runtime/ticker-refresh.ts +1 -1
  23. package/src/app.tsx +12 -0
  24. package/src/brokers/directory.ts +11 -2
  25. package/src/brokers/profile-form.ts +2 -1
  26. package/src/brokers/remove-profile.ts +61 -0
  27. package/src/brokers/signed-in/connect.ts +20 -7
  28. package/src/brokers/signed-in/sign-in-dialog.tsx +68 -30
  29. package/src/cli/desktop-pane-shot.ts +2 -2
  30. package/src/cli/history-rows.ts +1 -1
  31. package/src/cli/pane-functions/function-help.ts +3 -3
  32. package/src/cli/ticker-resolution.ts +2 -2
  33. package/src/components/command-bar/action-runtime.ts +7 -67
  34. package/src/components/command-bar/commands/direct/index.ts +9 -5
  35. package/src/components/command-bar/commands/direct/runtime.ts +12 -11
  36. package/src/components/command-bar/commands/plugin/actions.ts +6 -35
  37. package/src/components/command-bar/helpers.ts +3 -31
  38. package/src/components/command-bar/keyboard-handlers.ts +7 -219
  39. package/src/components/command-bar/keyboard-shortcuts.ts +25 -99
  40. package/src/components/command-bar/layout-items/current-layout.ts +2 -1
  41. package/src/components/command-bar/layout-items/focused-pane.ts +2 -1
  42. package/src/components/command-bar/layout-items/types.ts +2 -0
  43. package/src/components/command-bar/layout-items.ts +3 -0
  44. package/src/components/command-bar/list/model.ts +1 -1
  45. package/src/components/command-bar/list/navigation.ts +5 -12
  46. package/src/components/command-bar/pane-actions.ts +17 -14
  47. package/src/components/command-bar/pane-settings.ts +7 -250
  48. package/src/components/command-bar/pane-templates/workflow.ts +6 -13
  49. package/src/components/command-bar/panel/index.tsx +4 -110
  50. package/src/components/command-bar/panel/layout.ts +4 -16
  51. package/src/components/command-bar/panel/runtime.ts +1 -87
  52. package/src/components/command-bar/panel/state.ts +12 -29
  53. package/src/components/command-bar/panel/types.ts +2 -26
  54. package/src/components/command-bar/picker-activation.ts +7 -56
  55. package/src/components/command-bar/routes/root/plugin-install.ts +3 -3
  56. package/src/components/command-bar/routes/root/search-report.ts +2 -1
  57. package/src/components/command-bar/routing/actions.ts +21 -59
  58. package/src/components/command-bar/routing/confirm.ts +15 -88
  59. package/src/components/command-bar/routing/effects.ts +1 -20
  60. package/src/components/command-bar/routing/launch-request.ts +1 -36
  61. package/src/components/command-bar/routing/list-state.ts +5 -36
  62. package/src/components/command-bar/selection-runtime.ts +6 -35
  63. package/src/components/command-bar/surface/environment.ts +10 -6
  64. package/src/components/command-bar/surface/index.tsx +4 -37
  65. package/src/components/command-bar/workflow/alert.ts +23 -17
  66. package/src/components/command-bar/workflow/broker.ts +30 -40
  67. package/src/components/command-bar/workflow/builtin.ts +33 -38
  68. package/src/components/command-bar/workflow/collection-actions.ts +18 -21
  69. package/src/components/command-bar/workflow/collection-ticker.ts +4 -4
  70. package/src/components/command-bar/workflow/coordinator.ts +27 -156
  71. package/src/components/command-bar/workflow/fields.ts +6 -22
  72. package/src/components/command-bar/workflow/submit.ts +28 -43
  73. package/src/components/command-bar/workflow/ticker-listing-picker.ts +12 -9
  74. package/src/components/command-bar/workflow/tickers.ts +2 -2
  75. package/src/components/command-bar/workflow/types.ts +10 -35
  76. package/src/components/feedback-dialog.tsx +1 -13
  77. package/src/components/form-modal/broker-step.tsx +119 -0
  78. package/src/components/form-modal/confirm.tsx +145 -0
  79. package/src/components/form-modal/content.tsx +698 -0
  80. package/src/components/form-modal/deps.ts +77 -0
  81. package/src/components/form-modal/field-row.tsx +318 -0
  82. package/src/components/form-modal/host.tsx +169 -0
  83. package/src/components/form-modal/index.ts +2 -0
  84. package/src/components/form-modal/model.ts +131 -0
  85. package/src/components/form-modal/request.ts +67 -0
  86. package/src/components/form-modal/routes.ts +139 -0
  87. package/src/components/layout/shell/index.tsx +11 -0
  88. package/src/components/layout/status-bar.tsx +2 -1
  89. package/src/components/onboarding/first-run-workspace.ts +1 -1
  90. package/src/components/onboarding/onboarding-frame.tsx +4 -1
  91. package/src/components/onboarding/onboarding-wizard.tsx +1 -1
  92. package/src/components/pane-settings-dialog.tsx +7 -1
  93. package/src/components/pane-template-wizard.tsx +4 -149
  94. package/src/components/sign-in-code-panel.tsx +22 -9
  95. package/src/components/textarea-keys.ts +13 -0
  96. package/src/components/ui/button.tsx +3 -2
  97. package/src/components/ui/confirm-dialog.tsx +62 -10
  98. package/src/components/ui/frame.tsx +7 -2
  99. package/src/components/ui/list-view.tsx +16 -9
  100. package/src/core/app-runtime.ts +1 -1
  101. package/src/core/state/app/types.ts +1 -15
  102. package/src/data/config/home.ts +82 -11
  103. package/src/data/config/store/node.ts +29 -16
  104. package/src/i18n/es.ts +53 -7
  105. package/src/i18n/ja.ts +53 -7
  106. package/src/i18n/ko.ts +53 -7
  107. package/src/i18n/zh-cn.ts +53 -7
  108. package/src/i18n/zh-tw.ts +53 -7
  109. package/src/layout-marketplace/gallery.tsx +11 -3
  110. package/src/market-data/coordinator/fx-legs.ts +1 -1
  111. package/src/market-data/history-session.ts +3 -5
  112. package/src/market-data/market/freshness.ts +1 -1
  113. package/src/market-data/market/status.ts +1 -1
  114. package/src/market-data/quotes/metadata.ts +3 -3
  115. package/src/market-data/quotes/resolution.ts +3 -3
  116. package/src/news/types.ts +1 -1
  117. package/src/plugins/builtin/alerts/alert-engine.ts +2 -0
  118. package/src/plugins/builtin/alerts/cli.ts +2 -2
  119. package/src/plugins/builtin/alerts/types.ts +2 -0
  120. package/src/plugins/builtin/broker-manager/add-flow.tsx +625 -0
  121. package/src/plugins/builtin/broker-manager/add-request.ts +34 -0
  122. package/src/plugins/builtin/broker-manager/detail.tsx +128 -50
  123. package/src/plugins/builtin/broker-manager/footer.ts +6 -5
  124. package/src/plugins/builtin/broker-manager/index.tsx +62 -21
  125. package/src/plugins/builtin/broker-manager/model.ts +7 -0
  126. package/src/plugins/builtin/broker-manager/pane-actions.tsx +47 -44
  127. package/src/plugins/builtin/cloud/company-picker.tsx +658 -0
  128. package/src/plugins/builtin/credit-boards/model.ts +1 -1
  129. package/src/plugins/builtin/dividend-yield/browser.tsx +2 -25
  130. package/src/plugins/builtin/dividend-yield/client.ts +34 -122
  131. package/src/plugins/builtin/dividend-yield/index.tsx +9 -9
  132. package/src/plugins/builtin/dividend-yield/provider-client.ts +1 -1
  133. package/src/plugins/builtin/estimate-revisions/client.ts +3 -3
  134. package/src/plugins/builtin/estimate-revisions/pane.tsx +2 -2
  135. package/src/plugins/builtin/executives/pane.tsx +13 -4
  136. package/src/plugins/builtin/filing-events/pane.tsx +23 -6
  137. package/src/plugins/builtin/futures/contracts.ts +3 -3
  138. package/src/plugins/builtin/futures/returns.ts +1 -1
  139. package/src/plugins/builtin/futures-curve/client.ts +1 -1
  140. package/src/plugins/builtin/futures-curve/model.ts +1 -1
  141. package/src/plugins/builtin/help/index.tsx +8 -3
  142. package/src/plugins/builtin/market-heatmap/data.ts +13 -256
  143. package/src/plugins/builtin/market-heatmap/index.tsx +5 -15
  144. package/src/plugins/builtin/market-movers/client.ts +1 -1
  145. package/src/plugins/builtin/market-movers/screener.ts +60 -142
  146. package/src/plugins/builtin/market-valuation/defs.ts +1 -2
  147. package/src/plugins/builtin/market-valuation/sources.ts +1 -3
  148. package/src/plugins/builtin/news/wire/default-feeds.ts +2 -8
  149. package/src/plugins/builtin/news/wire/feed-config.ts +5 -1
  150. package/src/plugins/builtin/news/wire/persisted-articles.ts +9 -5
  151. package/src/plugins/builtin/news/wire/rss/cli.ts +1 -1
  152. package/src/plugins/builtin/news/wire/rss/managed-feed.ts +19 -0
  153. package/src/plugins/builtin/news/wire/rss/parser.ts +18 -9
  154. package/src/plugins/builtin/news/wire/rss/source.ts +1 -1
  155. package/src/plugins/builtin/plugin-marketplace/model.ts +5 -0
  156. package/src/plugins/builtin/plugin-marketplace/pane.tsx +4 -2
  157. package/src/plugins/builtin/relative-rotation/client.ts +1 -1
  158. package/src/plugins/builtin/research/analyst-model.ts +1 -1
  159. package/src/plugins/builtin/research/event-model.ts +1 -1
  160. package/src/plugins/builtin/risk-factors/pane.tsx +25 -10
  161. package/src/plugins/builtin/shared/pro-feature-wall.tsx +47 -0
  162. package/src/plugins/builtin/short-interest/client.ts +14 -106
  163. package/src/plugins/builtin/short-interest/headless.ts +2 -2
  164. package/src/plugins/builtin/short-interest/index.tsx +5 -5
  165. package/src/plugins/builtin/short-interest/pane.tsx +4 -4
  166. package/src/plugins/builtin/ticker-detail/pane.tsx +2 -2
  167. package/src/plugins/builtin/time-sales/client.ts +3 -3
  168. package/src/plugins/builtin/vol-surface/model.ts +1 -1
  169. package/src/plugins/builtin/volatility/model.ts +1 -1
  170. package/src/plugins/bundle.ts +2 -2
  171. package/src/plugins/catalog-manifest.ts +0 -1
  172. package/src/plugins/catalog-ui.ts +1 -1
  173. package/src/plugins/catalog.ts +0 -2
  174. package/src/plugins/host-link.ts +1 -1
  175. package/src/plugins/host-resolver.ts +1 -1
  176. package/src/plugins/loader.ts +6 -5
  177. package/src/plugins/registry/host-actions.ts +6 -1
  178. package/src/plugins/registry/index.ts +1 -0
  179. package/src/plugins/runtime/context.tsx +2 -0
  180. package/src/plugins/runtime/index.tsx +2 -0
  181. package/src/public/__snapshots__/public-api.test.ts.snap +0 -1
  182. package/src/public/market-data.ts +0 -7
  183. package/src/remote/controller-utils.ts +1 -1
  184. package/src/remote/controller.ts +27 -3
  185. package/src/remote/form.ts +20 -0
  186. package/src/remote/resources.ts +7 -0
  187. package/src/remote/schema.ts +25 -3
  188. package/src/remote/semantic-tree.tsx +15 -0
  189. package/src/remote/types.ts +2 -0
  190. package/src/renderers/browser/research-entry.ts +2 -2
  191. package/src/renderers/cloudflare/http-proxy.ts +157 -33
  192. package/src/renderers/cloudflare/proxy-session.ts +270 -0
  193. package/src/renderers/cloudflare/worker.ts +4 -1
  194. package/src/renderers/dom/desktop/controls.tsx +2 -1
  195. package/src/renderers/dom/dialog-host.tsx +17 -8
  196. package/src/renderers/dom/styles.css +4 -0
  197. package/src/renderers/electrobun/bun/application-menu/index.ts +4 -4
  198. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  199. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -2
  200. package/src/renderers/electrobun/bun/external-plugins.ts +1 -1
  201. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +3 -6
  202. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +3 -3
  203. package/src/renderers/opentui/dialog-host.tsx +14 -2
  204. package/src/renderers/opentui/start.tsx +2 -2
  205. package/src/renderers/opentui/terminal-media.ts +2 -2
  206. package/src/renderers/opentui/test-utils.tsx +9 -2
  207. package/src/renderers/opentui/toast-host.tsx +1 -1
  208. package/src/sources/gloomberb-cloud/index.ts +23 -24
  209. package/src/sources/gloomberb-cloud/news.ts +3 -3
  210. package/src/sources/history-retention.ts +6 -6
  211. package/src/sources/{yahoo-finance/symbols.ts → listing-symbols.ts} +16 -41
  212. package/src/sources/listing-target.ts +4 -4
  213. package/src/sources/provider-router/cache.ts +16 -6
  214. package/src/sources/provider-router/financials.ts +1 -1
  215. package/src/sources/provider-router/history.ts +3 -2
  216. package/src/state/app/bootstrap.ts +1 -1
  217. package/src/state/app/context/index.tsx +13 -0
  218. package/src/sync/core-contributors.ts +163 -16
  219. package/src/test-support/plugin-runtime.ts +1 -0
  220. package/src/tickers/instrument-kind.ts +1 -1
  221. package/src/tickers/open-target.ts +4 -4
  222. package/src/tickers/search/index.ts +3 -3
  223. package/src/tickers/search/ranking.ts +4 -4
  224. package/src/time-series/resolution.ts +1 -1
  225. package/src/time-series/resolve.ts +1 -1
  226. package/src/types/financials.ts +5 -5
  227. package/src/types/instrument.ts +1 -1
  228. package/src/types/plugin.ts +3 -3
  229. package/src/types/price-history.ts +1 -1
  230. package/src/ui/dialog-bridge.tsx +50 -0
  231. package/src/ui/dialog-context.ts +24 -0
  232. package/src/ui/dialog-stack.ts +47 -1
  233. package/src/ui/dialog.tsx +2 -10
  234. package/src/ui/host.tsx +6 -1
  235. package/src/ui/index.tsx +7 -2
  236. package/src/utils/latest-financial-period.ts +1 -1
  237. package/src/utils/plugin-proxy-hosts.json +1 -5
  238. package/src/utils/sec.ts +1 -1
  239. package/src/app/pane-template-dialog-wizard.tsx +0 -67
  240. package/src/components/command-bar/multi-select-picker.tsx +0 -225
  241. package/src/components/command-bar/multi-select-runtime.ts +0 -91
  242. package/src/components/command-bar/workflow/body.tsx +0 -164
  243. package/src/components/command-bar/workflow/field-row.tsx +0 -231
  244. package/src/components/command-bar/workflow/route-actions.ts +0 -235
  245. package/src/components/command-bar/workflow/runtime.ts +0 -247
  246. package/src/plugins/builtin/yahoo/index.ts +0 -38
  247. package/src/sources/yahoo-finance/chart-period.ts +0 -138
  248. package/src/sources/yahoo-finance/financials.ts +0 -370
  249. package/src/sources/yahoo-finance/history.ts +0 -150
  250. package/src/sources/yahoo-finance/http.ts +0 -119
  251. package/src/sources/yahoo-finance/mappers.ts +0 -549
  252. package/src/sources/yahoo-finance/names.ts +0 -28
  253. package/src/sources/yahoo-finance/options.ts +0 -190
  254. package/src/sources/yahoo-finance/quote-summary.ts +0 -176
  255. package/src/sources/yahoo-finance/requests.ts +0 -245
  256. package/src/sources/yahoo-finance/snapshots.ts +0 -336
  257. package/src/sources/yahoo-finance/types.ts +0 -198
  258. package/src/sources/yahoo-finance/yahoo-chart-interval.ts +0 -18
  259. package/src/sources/yahoo-finance.ts +0 -339
@@ -23,8 +23,9 @@ import {
23
23
  type TickerOpenTarget,
24
24
  } from "../../tickers/open-target";
25
25
  import { AmbiguousTickerError, findExactTickerSearchMatch } from "../../tickers/search";
26
+ import { isDialogOpen } from "../../ui/dialog-stack";
26
27
  import { parsePublicTickerKey } from "../../utils/exchanges";
27
- import { tickerHasYahooSuffix } from "../../sources/yahoo-finance/symbols";
28
+ import { tickerHasListingSuffix } from "../../sources/listing-symbols";
28
29
  import { instrumentFromTicker } from "../../market-data/request-types";
29
30
  import { tickerInstrumentLabel } from "../../tickers/instrument-label";
30
31
 
@@ -75,7 +76,8 @@ export function useAppTickerOpenRuntime({
75
76
  return target;
76
77
  } catch (err) {
77
78
  if (!canPresentFeedback()) return null;
78
- if (err instanceof AmbiguousTickerError) {
79
+ // Ticker search offers the listings, unless a dialog would sit over the bar.
80
+ if (err instanceof AmbiguousTickerError && !isDialogOpen()) {
79
81
  dispatch({ type: "SET_COMMAND_BAR", open: true, query: rawSymbol,
80
82
  launch: { kind: "ticker-search", query: rawSymbol } });
81
83
  }
@@ -114,7 +116,7 @@ export function useAppTickerOpenRuntime({
114
116
  if (!options?.forceNewPane && !existing && instrument === null && !target.ticker.metadata.broker_contracts?.length) {
115
117
  existing = findFixedTickerPaneForSymbol(currentLayout, paneType, symbol);
116
118
  }
117
- if (!options?.forceNewPane && !existing && !instrument && (parsePublicTickerKey(symbol).exchange || tickerHasYahooSuffix(symbol))) {
119
+ if (!options?.forceNewPane && !existing && !instrument && (parsePublicTickerKey(symbol).exchange || tickerHasListingSuffix(symbol))) {
118
120
  existing = currentLayout.instances.find((instance) => {
119
121
  if (instance.paneId !== paneType || instance.binding?.kind !== "fixed" || instance.binding.instrument || !isPaneInLayout(currentLayout, instance.instanceId)) return false;
120
122
  const ticker = currentState.tickers.get(instance.binding.symbol);
@@ -87,7 +87,7 @@ export function useBrokerImportRuntime({
87
87
 
88
88
  for (const position of result.positions) {
89
89
  throwIfBrokerImportCancelled(options?.signal);
90
- // Skip Yahoo Finance for broker option symbols; position marks are already available.
90
+ // Skip market data for broker option symbols; position marks are already available.
91
91
  // Position data (markPrice, marketValue, unrealizedPnl) is used directly.
92
92
  if (options?.refreshImportedTickers !== false && position.assetCategory !== "OPT") {
93
93
  refreshQuote(position.ticker, position.exchange, undefined, 1);
@@ -4,9 +4,12 @@ import type { PluginRegistry } from "../../plugins/registry";
4
4
  import type { AppAction, AppState } from "../../state/app/context";
5
5
  import type { DesktopApplicationMenuBridge } from "../../types/desktop-menu";
6
6
  import type { DesktopWindowBridge } from "../../types/desktop-window";
7
+ import { isDialogOpen } from "../../ui/dialog-stack";
7
8
  import type { RendererHost } from "../../ui/host";
8
9
  import { recordFunctionOpen } from "../../telemetry/usage-counts";
9
10
 
11
+ const OPENS_OVER_DIALOGS = new Set<string>(["open-command-bar", "open-plugin-workflow", "open-builtin-workflow"]);
12
+
10
13
  export function useDesktopApplicationMenuRuntime({
11
14
  desktopApplicationMenuBridge,
12
15
  desktopWindowKind,
@@ -27,6 +30,11 @@ export function useDesktopApplicationMenuRuntime({
27
30
  useEffect(() => {
28
31
  if (desktopWindowKind !== "main" || !desktopApplicationMenuBridge) return;
29
32
  return desktopApplicationMenuBridge.subscribe((command) => {
33
+ // The native menu stays reachable over a dialog, accelerators included.
34
+ // A bar opened there would sit over the dialog without its keys, and Esc
35
+ // would close the dialog under it; a form would stack over one the user
36
+ // has not finished. Both wait for the dialog to close.
37
+ if (OPENS_OVER_DIALOGS.has(command.type) && isDialogOpen()) return;
30
38
  switch (command.type) {
31
39
  case "open-command-bar":
32
40
  dispatch({ type: "SET_COMMAND_BAR", open: true, query: command.query });
@@ -35,12 +43,7 @@ export function useDesktopApplicationMenuRuntime({
35
43
  pluginRegistry.openPluginCommandWorkflow(command.commandId);
36
44
  break;
37
45
  case "open-builtin-workflow":
38
- dispatch({
39
- type: "SET_COMMAND_BAR",
40
- open: true,
41
- query: "",
42
- launch: { kind: "builtin-workflow", actionId: command.actionId },
43
- });
46
+ pluginRegistry.openBuiltInWorkflow(command.actionId);
44
47
  break;
45
48
  case "open-url":
46
49
  void rendererHost.openExternal(command.url).catch((error) => {
@@ -9,6 +9,7 @@ import type { MarketDataCoordinator } from "../../market-data/coordinator";
9
9
  import { instrumentFromTicker } from "../../market-data/request-types";
10
10
  import type { PluginRegistry } from "../../plugins/registry";
11
11
  import { saveConfigImmediately } from "../../state/config-save-scheduler";
12
+ import { settleWithin } from "../../utils/async-deadline";
12
13
  import type { AppAction, AppState } from "../../state/app/context";
13
14
  import type { AppConfig, BrokerInstanceConfig } from "../../types/config";
14
15
  import type { DataProvider } from "../../types/data-provider";
@@ -18,6 +19,9 @@ import {
18
19
  getBrokerInstance,
19
20
  } from "../../utils/broker-instances";
20
21
 
22
+ /** How long removing a profile waits for its adapter to disconnect before going on without it. */
23
+ const ADAPTER_DISCONNECT_WAIT_MS = 5_000;
24
+
21
25
  export function bindPluginRegistryRuntimeAccess({
22
26
  dataProvider,
23
27
  dispatch,
@@ -186,8 +190,10 @@ export function bindPluginRegistryRuntimeAccess({
186
190
 
187
191
  clearPersistedBrokerAccounts(pluginRegistry.persistence.resources, instance);
188
192
 
193
+ // Best effort: an adapter that throws, rejects or never answers (a desktop
194
+ // call to a busy Bun process) must not keep the profile.
189
195
  const broker = pluginRegistry.brokers.get(instance.brokerType);
190
- await broker?.disconnect?.(instance).catch(() => {});
196
+ await settleWithin(Promise.resolve().then(() => broker?.disconnect?.(instance)), ADAPTER_DISCONNECT_WAIT_MS);
191
197
 
192
198
  // A portfolio another profile of the account can keep (the account switched
193
199
  // to or from sign-in) goes to that profile; only this profile's positions go.
@@ -96,7 +96,7 @@ export function useTickerRefreshRuntime({
96
96
  if (quote.currency) void marketData.loadFxRate(quote.currency).catch(() => {});
97
97
  void marketData.loadFxRate(baseCurrency).catch(() => {});
98
98
  } catch {
99
- // Silently fail - the list can fall back to stale cache or Yahoo.
99
+ // Silently fail - the list can fall back to stale cache or the backend.
100
100
  } finally {
101
101
  quoteRefreshInFlight.delete(key);
102
102
  }
package/src/app.tsx CHANGED
@@ -19,6 +19,7 @@ import { DetachedPaneShell } from "./components/layout/detached-pane-shell";
19
19
  import { TransientLayoutProvider } from "./components/layout/transient-layout";
20
20
  import { CommandBar } from "./components/command-bar/surface";
21
21
  import { OnboardingWizard } from "./components/onboarding/onboarding-wizard";
22
+ import { CompanyPickerHost } from "./plugins/builtin/cloud/company-picker";
22
23
  import { SignInGate } from "./components/sign-in-gate";
23
24
  import { useDialog } from "./ui/dialog";
24
25
  import { PluginRegistry } from "./plugins/registry";
@@ -39,6 +40,7 @@ import { useBrokerImportRuntime } from "./app/runtime/broker-import";
39
40
  import { useDesktopDeepLinkRuntime } from "./app/runtime/desktop-deeplink";
40
41
  import { useDesktopApplicationMenuRuntime } from "./app/runtime/desktop-menu";
41
42
  import { useAppGlobalShortcuts } from "./app/global-shortcuts";
43
+ import { AppDialogBridge } from "./app/dialog-bridge";
42
44
  import { KeybindingsProvider, useResolvedKeybindings } from "./app/keybindings";
43
45
  import { useAppPaneRuntime } from "./app/pane-runtime";
44
46
  import { bindPluginRegistryRuntimeAccess } from "./app/runtime/plugin-bindings";
@@ -181,6 +183,10 @@ function AppInner({
181
183
  const toast = useToastHost();
182
184
  const isDetachedWindow = desktopWindowBridge?.kind === "detached";
183
185
  const detachedPaneId = isDetachedWindow ? desktopWindowBridge.paneId ?? null : null;
186
+ const focusDetachedPane = useMemo(() => {
187
+ const focus = desktopWindowBridge?.kind === "main" ? desktopWindowBridge.focusDetachedPane : undefined;
188
+ return focus ? (paneId: string) => { void focus(paneId).catch(() => {}); } : undefined;
189
+ }, [desktopWindowBridge]);
184
190
  const [desktopDockPreview, setDesktopDockPreview] = useState<DesktopDockPreviewState | null>(null);
185
191
  const [commandBarNativeOccluder, setCommandBarNativeOccluder] = useState<LayoutBounds | null>(null);
186
192
  appActiveRef.current = appActive;
@@ -354,6 +360,7 @@ function AppInner({
354
360
  dialog,
355
361
  dispatch,
356
362
  externalPlugins,
363
+ focusDetachedPane,
357
364
  isDetachedWindow,
358
365
  notify,
359
366
  persistConfig,
@@ -409,6 +416,7 @@ function AppInner({
409
416
  if (desktopWindowBridge?.kind === "detached" && desktopWindowBridge.paneId) {
410
417
  return (
411
418
  <KeybindingsProvider value={keybindings}>
419
+ <AppDialogBridge />
412
420
  <ContextMenuProvider pluginRegistry={pluginRegistry}>
413
421
  <RemoteControlHost
414
422
  adapter={remoteControlAdapter}
@@ -432,6 +440,7 @@ function AppInner({
432
440
 
433
441
  return (
434
442
  <KeybindingsProvider value={keybindings}>
443
+ <AppDialogBridge />
435
444
  <ContextMenuProvider pluginRegistry={pluginRegistry}>
436
445
  <RemoteControlHost
437
446
  adapter={remoteControlAdapter}
@@ -448,6 +457,8 @@ function AppInner({
448
457
  <TransientLayoutProvider>
449
458
  <Shell
450
459
  pluginRegistry={pluginRegistry}
460
+ dataProvider={dataProvider}
461
+ tickerRepository={tickerRepository}
451
462
  desktopWindowBridge={desktopWindowBridge}
452
463
  desktopDockPreview={desktopDockPreview}
453
464
  commandBarNativeOccluder={commandBarNativeOccluder}
@@ -460,6 +471,7 @@ function AppInner({
460
471
  }}
461
472
  />
462
473
  </TransientLayoutProvider>
474
+ {!onboardingActive ? <CompanyPickerHost pluginRegistry={pluginRegistry} /> : null}
463
475
  {onboardingActive && onOnboardingComplete ? (
464
476
  <OnboardingWizard
465
477
  pluginRegistry={pluginRegistry}
@@ -4,6 +4,7 @@
4
4
  * adapter. One broker offered both ways is one entry with two methods, so Add
5
5
  * Broker and onboarding list "Interactive Brokers" once.
6
6
  */
7
+ import { t, tf } from "../i18n";
7
8
  import type { BrokerAdapter } from "../types/broker";
8
9
  import type { SignedInBroker } from "./signed-in/client";
9
10
  import { SIGNED_IN_BROKER_TYPE } from "./signed-in/profile";
@@ -48,9 +49,17 @@ export function buildBrokerDirectory({
48
49
  return [...entries.values()].sort((left, right) => left.name.localeCompare(right.name));
49
50
  }
50
51
 
52
+ /** How a broker connects, beside its name in a list of brokers. */
53
+ export function brokerMethodSummary(entry: BrokerDirectoryEntry): string {
54
+ const signsIn = entry.methods.some((method) => method.kind === "signed-in");
55
+ const onDevice = entry.methods.some((method) => method.kind === "device");
56
+ if (signsIn && onDevice) return t("Sign in, or on this device");
57
+ return signsIn ? t("Sign in") : t("On this device");
58
+ }
59
+
51
60
  export function brokerMethodLabel(entry: BrokerDirectoryEntry, method: BrokerMethod): string {
52
61
  if (method.kind === "signed-in") {
53
- return entry.methods.some((candidate) => candidate.kind === "device") ? "Sign in (recommended)" : "Sign in";
62
+ return entry.methods.some((candidate) => candidate.kind === "device") ? t("Sign in (recommended)") : t("Sign in");
54
63
  }
55
- return entry.methods.length > 1 ? `On this device (${method.adapter.name})` : method.adapter.name;
64
+ return entry.methods.length > 1 ? tf("On this device ({broker})", { broker: method.adapter.name }) : method.adapter.name;
56
65
  }
@@ -1,6 +1,7 @@
1
1
  import type { BrokerAdapter, BrokerConfigField } from "../types/broker";
2
2
  import type { BrokerInstanceConfig } from "../types/config";
3
3
  import { resolveBrokerConfigFields } from "../types/broker";
4
+ import { t, tf } from "../i18n";
4
5
 
5
6
  export interface BrokerProfileDraft {
6
7
  label: string;
@@ -93,7 +94,7 @@ export function validateBrokerProfileValues(
93
94
  for (const field of getVisibleBrokerConfigFields(adapter, resolvedValues)) {
94
95
  if (!field.required) continue;
95
96
  if (!normalizeFormValue(resolvedValues[field.key]).trim()) {
96
- return `${field.label} is required.`;
97
+ return tf("{label} is required.", { label: t(field.label) });
97
98
  }
98
99
  }
99
100
  return null;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Removing a broker profile, from the Brokers pane or the command bar. A
3
+ * signed-in profile's connection belongs to the Gloom account, so it is
4
+ * disconnected there too, as far as Gloom answers: the profile leaves this
5
+ * device either way, and the message says what the account kept.
6
+ */
7
+ import type { ConfirmModalOptions } from "../components/form-modal";
8
+ import { t, tf } from "../i18n";
9
+ import type { BrokerInstanceConfig } from "../types/config";
10
+ import { disconnectSignedInProfile, signedInBrokerForProfile } from "./signed-in/connect";
11
+ import { isSignedInBrokerProfile } from "./signed-in/profile";
12
+
13
+ /**
14
+ * The confirm before `onConfirm` removes `instance`. `brokerName` names the
15
+ * broker when the connector list does not.
16
+ */
17
+ export function brokerProfileRemovalConfirm(
18
+ instance: BrokerInstanceConfig,
19
+ brokerName: string,
20
+ onConfirm: () => Promise<void>,
21
+ ): ConfirmModalOptions {
22
+ const signedIn = isSignedInBrokerProfile(instance) ? signedInBrokerForProfile(instance, brokerName) : null;
23
+ return {
24
+ confirmId: "remove-broker-profile",
25
+ title: "Disconnect broker?",
26
+ body: [
27
+ tf("Remove \"{label}\" and imported broker data?", { label: instance.label }),
28
+ t("Broker-managed portfolios, positions, and contracts will be removed."),
29
+ ...(signedIn
30
+ ? [tf("This also disconnects {broker} from your other devices and agents.", { broker: signedIn.name })]
31
+ : []),
32
+ ],
33
+ confirmLabel: "Disconnect",
34
+ tone: "danger",
35
+ onConfirm,
36
+ };
37
+ }
38
+
39
+ export interface BrokerProfileRemoval {
40
+ message: string;
41
+ /** The Gloom account still holds the broker, so its other devices and agents keep it. */
42
+ accountKept: boolean;
43
+ }
44
+
45
+ export async function removeBrokerProfile(
46
+ instance: BrokerInstanceConfig,
47
+ brokerName: string,
48
+ removeBrokerInstance: (instanceId: string) => Promise<void>,
49
+ ): Promise<BrokerProfileRemoval> {
50
+ const account = await disconnectSignedInProfile(instance);
51
+ await removeBrokerInstance(instance.id);
52
+ const label = instance.label;
53
+ if (!account.stillConnected) return { message: tf("Removed {label}.", { label }), accountKept: false };
54
+ const broker = signedInBrokerForProfile(instance, brokerName).name;
55
+ return {
56
+ accountKept: true,
57
+ message: account.signedOut
58
+ ? tf("Removed {label}. {broker} is still connected to your Gloom account. Sign in to Gloom to disconnect {broker} from your account.", { label, broker })
59
+ : tf("Removed {label}. {broker} is still connected to your Gloom account.", { label, broker }),
60
+ };
61
+ }
@@ -4,6 +4,7 @@
4
4
  * connecting again syncs the profile it already has.
5
5
  */
6
6
  import { ApiRequestError } from "../../api-client/errors";
7
+ import { withDeadline } from "../../utils/async-deadline";
7
8
  import type { AppConfig, BrokerInstanceConfig } from "../../types/config";
8
9
  import { findSignedInBroker } from "./catalog";
9
10
  import { disconnectSignedInBroker, type SignedInBroker } from "./client";
@@ -31,7 +32,7 @@ export interface ConnectSignedInBrokerDeps<T> {
31
32
  getConfig(): AppConfig;
32
33
  createBrokerInstance(brokerType: string, label: string, values: Record<string, unknown>): Promise<BrokerInstanceConfig>;
33
34
  syncBrokerInstance(instanceId: string): Promise<T>;
34
- /** Opens the connect dialog; replaced in tests. */
35
+ /** Asks the user to connect: a form's own connect step, or the connect dialog when missing. */
35
36
  requestSignIn?: (broker: SignedInBroker) => Promise<boolean>;
36
37
  }
37
38
 
@@ -50,20 +51,32 @@ export async function connectSignedInBrokerProfile<T>(
50
51
  return { instance, synced: await deps.syncBrokerInstance(instance.id) };
51
52
  }
52
53
 
54
+ /** Long enough for a slow answer, short enough that removing a profile never hangs on one. */
55
+ const DISCONNECT_TIMEOUT_MS = 10_000;
56
+
57
+ /** What became of the Gloom account's connection behind a profile being removed. */
58
+ export type SignedInProfileDisconnect =
59
+ | { stillConnected: false }
60
+ /** The account keeps the broker: signed out of Gloom, or Gloom did not do it. */
61
+ | { stillConnected: true; signedOut: boolean };
62
+
53
63
  /**
54
64
  * Disconnects the Gloom account's connection behind a signed-in profile before
55
- * the profile goes. Already gone is fine; signed out of Gloom, the account
56
- * keeps the broker, which the caller reports as `stillConnected`.
65
+ * the profile goes. Already gone is fine. It never throws: the profile leaves
66
+ * this device whatever Gloom answers, and the caller says what the account kept.
57
67
  */
58
- export async function disconnectSignedInProfile(instance: BrokerInstanceConfig): Promise<{ stillConnected: boolean }> {
68
+ export async function disconnectSignedInProfile(instance: BrokerInstanceConfig): Promise<SignedInProfileDisconnect> {
59
69
  if (!isSignedInBrokerProfile(instance)) return { stillConnected: false };
60
70
  try {
61
- await disconnectSignedInBroker(signedInBrokerId(instance));
71
+ await withDeadline(
72
+ disconnectSignedInBroker(signedInBrokerId(instance)),
73
+ DISCONNECT_TIMEOUT_MS,
74
+ "Gloom did not answer the disconnect.",
75
+ );
62
76
  return { stillConnected: false };
63
77
  } catch (error) {
64
78
  if (error instanceof ApiRequestError && error.status === 404) return { stillConnected: false };
65
- if (error instanceof ApiRequestError && error.status === 401) return { stillConnected: true };
66
- throw error;
79
+ return { stillConnected: true, signedOut: error instanceof ApiRequestError && error.status === 401 };
67
80
  }
68
81
  }
69
82
 
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * The dialog that connects a signed-in broker, plus the request bridge that
3
- * lets the command bar, the Brokers pane and onboarding open it. Those run
4
- * outside the React tree or in the desktop view, which is also where the
5
- * browser hand-off has to happen, so the broker adapter never opens it.
3
+ * lets the Brokers pane and onboarding open it. Those run outside the React
4
+ * tree or in the desktop view, which is also where the browser hand-off has to
5
+ * happen, so the broker adapter never opens it. The Brokers pane's add flow and
6
+ * New Portfolio's form show the same step in place, built from the pieces
7
+ * exported here.
6
8
  */
7
9
  import { useEffect, useRef, useState } from "react";
8
10
  import { apiClient, type AuthUser } from "../../api-client";
@@ -12,7 +14,7 @@ import { useAppLanguage } from "../../i18n/react";
12
14
  import { useViewport } from "../../react/input";
13
15
  import { colors } from "../../theme/colors";
14
16
  import { Box, Text, TextAttributes } from "../../ui";
15
- import { useDialog, useDialogKeyboard, type PromptContext } from "../../ui/dialog";
17
+ import { useDialog, useDialogKeyboard, type DialogApi, type PromptContext } from "../../ui/dialog";
16
18
  import { isPlainKey } from "../../utils/keyboard";
17
19
  import { DeviceSignInDialog } from "../../plugins/builtin/cloud/device-signin-dialog";
18
20
  import type { SignedInBroker } from "./client";
@@ -23,7 +25,7 @@ import {
23
25
  type BrokerSignInSnapshot,
24
26
  } from "./sign-in";
25
27
 
26
- function signInStatus(snapshot: BrokerSignInSnapshot, broker: SignedInBroker): { text: string; color: string } {
28
+ export function brokerSignInStatus(snapshot: BrokerSignInSnapshot, broker: SignedInBroker): { text: string; color: string } {
27
29
  switch (snapshot.phase) {
28
30
  case "connected":
29
31
  return { text: t("Connected"), color: colors.positive };
@@ -39,18 +41,31 @@ function signInStatus(snapshot: BrokerSignInSnapshot, broker: SignedInBroker): {
39
41
  }
40
42
  }
41
43
 
42
- function BrokerSignInDialog({
43
- resolve,
44
- dismiss,
45
- broker,
46
- write,
47
- }: PromptContext<BrokerSignInOutcome> & { broker: SignedInBroker; write: boolean }) {
48
- useAppLanguage();
49
- const { height: termHeight } = useViewport();
44
+ /** Under the code: the broker's own note, or what connecting a single-connection broker does to other apps. */
45
+ export function brokerSignInNote(broker: SignedInBroker): string | null {
46
+ return broker.capabilities.signupNote
47
+ ?? (broker.capabilities.singleConnection
48
+ ? tf("Other AI apps linked to {broker} get disconnected. Connect them to Gloom instead.", { broker: broker.name })
49
+ : null);
50
+ }
51
+
52
+ /**
53
+ * One attempt at connecting `broker`, with a controller of its own: started on
54
+ * mount, cancelled on unmount. A session Gloom refused ends it at once as
55
+ * "signed-out", for a new Gloom sign-in and a fresh attempt; "connected" waits
56
+ * a beat so it is seen, and `finish` skips the wait.
57
+ */
58
+ export function useBrokerSignInAttempt(
59
+ broker: SignedInBroker,
60
+ write: boolean,
61
+ onOutcome: (outcome: "connected" | "signed-out") => void,
62
+ ): { snapshot: BrokerSignInSnapshot; restart(): void; finish(): void } {
50
63
  const controllerRef = useRef<BrokerSignInController | null>(null);
51
64
  if (!controllerRef.current) controllerRef.current = new BrokerSignInController(broker, write);
52
65
  const controller = controllerRef.current;
53
66
  const [snapshot, setSnapshot] = useState(controller.getSnapshot());
67
+ const onOutcomeRef = useRef(onOutcome);
68
+ onOutcomeRef.current = onOutcome;
54
69
 
55
70
  useEffect(() => {
56
71
  const unsubscribe = controller.subscribe(setSnapshot);
@@ -61,33 +76,59 @@ function BrokerSignInDialog({
61
76
  };
62
77
  }, [controller]);
63
78
 
64
- // Close after a beat so "Connected" is visible; enter skips the wait. Signed
65
- // out closes at once, for the host to sign in to Gloom.
66
79
  useEffect(() => {
67
80
  if (snapshot.phase === "signed-out") {
68
- resolve("signed-out");
81
+ onOutcomeRef.current("signed-out");
69
82
  return;
70
83
  }
71
84
  if (snapshot.phase !== "connected") return;
72
- const closeTimer = setTimeout(() => resolve("connected"), 900);
85
+ const closeTimer = setTimeout(() => onOutcomeRef.current("connected"), 900);
73
86
  return () => clearTimeout(closeTimer);
74
- }, [resolve, snapshot.phase]);
87
+ }, [snapshot.phase]);
88
+
89
+ return {
90
+ snapshot,
91
+ restart: () => {
92
+ if (snapshot.phase !== "connected") controller.start();
93
+ },
94
+ finish: () => {
95
+ if (snapshot.phase === "connected") onOutcomeRef.current("connected");
96
+ },
97
+ };
98
+ }
99
+
100
+ /** Gloom's device sign-in, stacked over whatever asked for it. True once signed in. */
101
+ export async function promptGloomSignIn(dialog: DialogApi): Promise<boolean> {
102
+ return !!await dialog.prompt<AuthUser | undefined>({
103
+ size: "full",
104
+ content: (context: unknown) => <DeviceSignInDialog {...(context as PromptContext<AuthUser | undefined>)} />,
105
+ });
106
+ }
107
+
108
+ function BrokerSignInDialog({
109
+ resolve,
110
+ dismiss,
111
+ broker,
112
+ write,
113
+ }: PromptContext<BrokerSignInOutcome> & { broker: SignedInBroker; write: boolean }) {
114
+ useAppLanguage();
115
+ const { height: termHeight } = useViewport();
116
+ // Close after a beat so "Connected" is visible; enter skips the wait. Signed
117
+ // out closes at once, for the host to sign in to Gloom.
118
+ const { snapshot, restart, finish } = useBrokerSignInAttempt(broker, write, resolve);
75
119
 
76
120
  useDialogKeyboard((event) => {
77
121
  event.stopPropagation();
78
122
  if (event.name === "enter" || event.name === "return") {
79
- if (snapshot.phase === "connected") resolve("connected");
80
- } else if (isPlainKey(event, "r") && snapshot.phase !== "connected") {
81
- controller.start();
123
+ finish();
124
+ } else if (isPlainKey(event, "r")) {
125
+ restart();
82
126
  } else if (event.name === "escape") {
83
127
  dismiss();
84
128
  }
85
129
  });
86
130
 
87
- const note = broker.capabilities.signupNote
88
- ?? (broker.capabilities.singleConnection
89
- ? tf("Other AI apps linked to {broker} get disconnected. Connect them to Gloom instead.", { broker: broker.name })
90
- : null);
131
+ const note = brokerSignInNote(broker);
91
132
  // Title, spacing, note, and footer take about eleven rows around the panel.
92
133
  const panelHeight = Math.max(4, termHeight - (note ? 12 : 10));
93
134
 
@@ -105,7 +146,7 @@ function BrokerSignInDialog({
105
146
  <SignInCodePanel
106
147
  url={snapshot.connectUrl}
107
148
  code={snapshot.code}
108
- status={signInStatus(snapshot, broker)}
149
+ status={brokerSignInStatus(snapshot, broker)}
109
150
  height={panelHeight}
110
151
  shortcutScope="broker-signin:browser"
111
152
  />
@@ -161,10 +202,7 @@ export function BrokerSignInDialogHost() {
161
202
  // The broker connection belongs to the Gloom account, so there must be one.
162
203
  void runBrokerSignIn(request.broker, request.write, {
163
204
  isSignedIn: () => apiClient.isSignedIn(),
164
- signInToGloom: async () => !!await dialog.prompt<AuthUser | undefined>({
165
- size: "full",
166
- content: (context: unknown) => <DeviceSignInDialog {...(context as PromptContext<AuthUser | undefined>)} />,
167
- }),
205
+ signInToGloom: () => promptGloomSignIn(dialog),
168
206
  connectBroker: async (broker, write) => await dialog.prompt<BrokerSignInOutcome>({
169
207
  size: "full",
170
208
  content: (context: unknown) => (
@@ -65,7 +65,7 @@ export interface DesktopPaneShotPayload {
65
65
  paneState: Record<string, PaneRuntimeState>;
66
66
  /**
67
67
  * Installed plugins compiled for the browser, the same way the desktop view
68
- * receives them. Without these a pane from `~/.gloomberb/plugins` resolves in
68
+ * receives them. Without these a pane from the plugins folder resolves in
69
69
  * the CLI but is unknown to the page that renders it.
70
70
  */
71
71
  externalPlugins?: DesktopExternalPluginBundle[];
@@ -80,7 +80,7 @@ export interface DesktopPaneShotApiProxy {
80
80
  /**
81
81
  * The page renders panes that fetch their own data. Left alone it can only
82
82
  * reach the cloud API, so a pane whose values come from a provider the router
83
- * merges in (analyst price targets, Yahoo dividends) rendered thinner than the
83
+ * merges in (analyst price targets, cash dividends) rendered thinner than the
84
84
  * same pane in the terminal. The bridge lets the page ask the Bun process to
85
85
  * run those requests through the real provider router instead.
86
86
  */
@@ -22,7 +22,7 @@ function decimalPlaces(value: number): number {
22
22
  }
23
23
 
24
24
  /**
25
- * Providers serve prices as float32: Yahoo sends the exact float
25
+ * Providers serve prices as float32: The feed sends the exact float
26
26
  * (340.33 arrives as 340.3299865722656) and the cloud a float already rounded
27
27
  * to a few decimals (254.42999). Return the shortest decimal that is the same
28
28
  * float32. Values with fewer than five decimals are left alone, and a precise
@@ -372,21 +372,21 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
372
372
  summary: "The 10-K risk factor section grouped by theme, with what was added, dropped or rewritten since the prior year called out.",
373
373
  usage: ["RISK TSLA"],
374
374
  keys: [key("y", "ear"), key("o", "pen filing")],
375
- data: AS_FILED,
375
+ data: pro(AS_FILED.pro),
376
376
  bloomberg: [],
377
377
  },
378
378
  EXEC: {
379
379
  summary: "Named executive officers and what they were paid, read from the proxy statement and checked against the filing.",
380
380
  usage: ["EXEC NVDA"],
381
381
  keys: [key("y", "ear"), key("o", "pen filing")],
382
- data: AS_FILED,
382
+ data: pro(AS_FILED.pro),
383
383
  bloomberg: ["MGMT"],
384
384
  },
385
385
  EK: {
386
386
  summary: "The company's 8-Ks classified by item (agreements, executive changes, auditor changes, restructurings) with a summary of what each one said.",
387
387
  usage: ["EK NVDA"],
388
388
  keys: [key("o", "pen filing")],
389
- data: AS_FILED,
389
+ data: pro(AS_FILED.pro),
390
390
  bloomberg: ["CF"],
391
391
  },
392
392
  CALLS: {
@@ -40,8 +40,8 @@ export async function resolveTickerForCli(
40
40
  }
41
41
 
42
42
  /**
43
- * Search catalogues can file a listing under another company's name: Twelve
44
- * Data names BAE Systems' London line (BA:LSE) after Boeing. The listing's own
43
+ * Search catalogues can file a listing under another company's name, such as
44
+ * BAE Systems' London line (BA:LSE) under Boeing. The listing's own
45
45
  * quote names the security it prices, which is the name the ticker command
46
46
  * shows, so a record created here takes it.
47
47
  */