gloomberb 0.11.1 → 0.11.3
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.
- package/README.md +11 -9
- package/README.zh-CN.md +6 -6
- package/package.json +1 -1
- package/src/api-client/data.ts +12 -0
- package/src/api-client/index.test.ts +47 -0
- package/src/api-client/index.ts +66 -0
- package/src/api-client/paths.ts +16 -0
- package/src/api-client/types.ts +59 -0
- package/src/app/pane-runtime/index.tsx +4 -1
- package/src/app/pane-runtime/plugin-bindings.test.ts +117 -0
- package/src/app/pane-runtime/plugin-bindings.ts +25 -4
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +25 -14
- package/src/app/runtime/desktop-deeplink.test.ts +16 -0
- package/src/app/runtime/desktop-deeplink.ts +66 -2
- package/src/app/runtime/desktop-menu.ts +3 -0
- package/src/app/runtime/plugin-bindings.ts +30 -28
- package/src/app.tsx +9 -2
- package/src/capabilities/chart-series.ts +3 -2
- package/src/cli/commands/automation.test.ts +9 -0
- package/src/cli/commands/automation.ts +22 -73
- package/src/cli/commands/market.ts +2 -2
- package/src/cli/index.ts +0 -2
- package/src/cli/pane-functions/capabilities.ts +3 -3
- package/src/cli/registry.ts +0 -22
- package/src/cli/result.ts +7 -10
- package/src/components/chart/composite/composite-chart.test.tsx +45 -2
- package/src/components/chart/composite/composite-chart.tsx +12 -9
- package/src/components/chart/composite/scene.ts +0 -21
- package/src/components/chart/composite/tools.ts +49 -0
- package/src/components/command-bar/commands/direct/index.ts +4 -0
- package/src/components/command-bar/commands/registry.ts +8 -1
- package/src/components/command-bar/layout-items/current-layout.ts +1 -1
- package/src/components/command-bar/pane-templates/workflow.ts +4 -0
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -0
- package/src/components/command-bar/routes/root/shortcuts.test.ts +19 -0
- package/src/components/command-bar/routes/root/shortcuts.ts +3 -1
- package/src/components/command-bar/surface/index.test.tsx +6 -6
- package/src/components/command-bar/surface/pane.test.tsx +66 -2
- package/src/components/command-bar/view-model.test.ts +2 -1
- package/src/components/command-bar/workflow/ops.test.ts +54 -0
- package/src/components/command-bar/workflow/ops.ts +1 -1
- package/src/components/data-table/export.test.ts +21 -0
- package/src/components/data-table/export.ts +21 -0
- package/src/components/layout/detached-pane-shell.tsx +74 -10
- package/src/components/layout/pane/title.ts +31 -3
- package/src/components/layout/shell/active-drag.ts +4 -11
- package/src/components/layout/shell/drag/index.ts +4 -20
- package/src/components/layout/shell/drag/runtime.ts +3 -4
- package/src/components/layout/shell/index.test.tsx +17 -3
- package/src/components/layout/shell/index.tsx +86 -30
- package/src/components/layout/shell/menu.test.ts +37 -1
- package/src/components/layout/shell/menu.ts +14 -0
- package/src/components/layout/shell/native/pointer-runtime.ts +22 -8
- package/src/components/layout/shell/pane/actions.ts +0 -5
- package/src/components/layout/shell/pane/management-shortcuts.ts +9 -4
- package/src/components/layout/shell/shortcuts.ts +6 -3
- package/src/components/layout/shell/ticker-link-menu.test.ts +82 -0
- package/src/components/layout/shell/ticker-link-menu.ts +78 -0
- package/src/components/layout/status-bar.test.tsx +62 -61
- package/src/components/layout/status-bar.tsx +53 -75
- package/src/components/ui/data-table/index.tsx +19 -2
- package/src/core/state/app/layout-reducer.ts +36 -0
- package/src/core/state/app/layout.ts +10 -5
- package/src/core/state/app/state.ts +0 -13
- package/src/core/state/app/types.ts +6 -4
- package/src/data/config/chart-settings.ts +5 -5
- package/src/data/config/layout.ts +2 -0
- package/src/data/config/store/index.test.ts +27 -6
- package/src/data/config/store/normalize.ts +3 -5
- package/src/data/plugin-state-store.ts +11 -0
- package/src/data/sqlite/cache.test.ts +10 -0
- package/src/i18n/es.ts +3 -4
- package/src/i18n/ja.ts +3 -4
- package/src/i18n/ko.ts +3 -4
- package/src/i18n/zh-cn.ts +3 -4
- package/src/i18n/zh-tw.ts +3 -4
- package/src/layout-marketplace/api.test.ts +54 -0
- package/src/layout-marketplace/api.ts +65 -0
- package/src/layout-marketplace/gallery-desktop.test.tsx +262 -0
- package/src/layout-marketplace/gallery-desktop.tsx +375 -0
- package/src/layout-marketplace/gallery-terminal.tsx +406 -0
- package/src/layout-marketplace/gallery.test.tsx +148 -0
- package/src/layout-marketplace/gallery.tsx +323 -0
- package/src/layout-marketplace/geometry.test.ts +47 -0
- package/src/layout-marketplace/geometry.ts +80 -0
- package/src/layout-marketplace/mini-workspace.test.tsx +122 -0
- package/src/layout-marketplace/mini-workspace.tsx +428 -0
- package/src/layout-marketplace/model.test.ts +118 -0
- package/src/layout-marketplace/model.ts +171 -0
- package/src/layout-marketplace/name-dialog.tsx +60 -0
- package/src/layout-marketplace/pane-imagery.ts +85 -0
- package/src/layout-marketplace/pane.tsx +20 -0
- package/src/layout-marketplace/payload.test.ts +246 -0
- package/src/layout-marketplace/payload.ts +624 -0
- package/src/layout-marketplace/use-marketplace.ts +63 -0
- package/src/market-data/hooks.tsx +0 -41
- package/src/plugins/broker-sync/broker-sync.test.ts +29 -0
- package/src/plugins/broker-sync/normalize.ts +9 -2
- package/src/plugins/builtin/ai/index.tsx +18 -2
- package/src/plugins/builtin/ai/screener/pane.test.tsx +47 -4
- package/src/plugins/builtin/ai/screener/pane.tsx +35 -12
- package/src/plugins/builtin/alerts/alert-engine.test.ts +16 -1
- package/src/plugins/builtin/alerts/alert-engine.ts +4 -0
- package/src/plugins/builtin/alerts/index.tsx +4 -0
- package/src/plugins/builtin/alerts/pane.tsx +2 -8
- package/src/plugins/builtin/analytics/index.tsx +3 -0
- package/src/plugins/builtin/broker-manager/index.tsx +3 -0
- package/src/plugins/builtin/cds/client.test.ts +83 -0
- package/src/plugins/builtin/cds/client.ts +67 -0
- package/src/plugins/builtin/cds/index.tsx +41 -0
- package/src/plugins/builtin/cds/model.test.ts +232 -0
- package/src/plugins/builtin/cds/model.ts +401 -0
- package/src/plugins/builtin/cds/pane.test.tsx +132 -0
- package/src/plugins/builtin/cds/pane.tsx +358 -0
- package/src/plugins/builtin/chart-composer/chart-spec.ts +31 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
- package/src/plugins/builtin/chart-composer/index.tsx +88 -2
- package/src/plugins/builtin/chart-composer/pane-share.test.ts +71 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +81 -14
- package/src/plugins/builtin/chart-composer/settings.ts +2 -13
- package/src/plugins/builtin/chat/content/index.tsx +4 -1
- package/src/plugins/builtin/chat/controller/index.ts +8 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +13 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +14 -2
- package/src/plugins/builtin/chat/controller/utils.ts +4 -6
- package/src/plugins/builtin/chat/controller.test.ts +64 -14
- package/src/plugins/builtin/chat/message/desktop.test.tsx +88 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +3 -1
- package/src/plugins/builtin/chat/message/profile-popover.test.tsx +1 -29
- package/src/plugins/builtin/chat/profile-popover.ts +53 -22
- package/src/plugins/builtin/chat/sidebar.test.tsx +81 -0
- package/src/plugins/builtin/chat/sidebar.tsx +63 -0
- package/src/plugins/builtin/chat/test-harness.tsx +29 -1
- package/src/plugins/builtin/cloud/auth-commands.ts +0 -4
- package/src/plugins/builtin/cloud/auth-model.ts +4 -1
- package/src/plugins/builtin/cloud/plugin.tsx +11 -1
- package/src/plugins/builtin/cloud-tweets/pane-share.test.ts +56 -0
- package/src/plugins/builtin/cloud-tweets/pane.tsx +20 -5
- package/src/plugins/builtin/cloud-tweets/registration.ts +44 -5
- package/src/plugins/builtin/composite-plugins.ts +5 -1
- package/src/plugins/builtin/connections/index.ts +3 -0
- package/src/plugins/builtin/earnings/index.tsx +1 -0
- package/src/plugins/builtin/econ/calendar-model.ts +0 -5
- package/src/plugins/builtin/econ/index.tsx +1 -0
- package/src/plugins/builtin/fear-greed/charts.tsx +12 -8
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
- package/src/plugins/builtin/help/index.tsx +2 -2
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +4 -1
- package/src/plugins/builtin/layout-manager/index.tsx +17 -4
- package/src/plugins/builtin/market-halts/index.tsx +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +1 -0
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/feed-config.test.ts +15 -1
- package/src/plugins/builtin/news/wire/feed-config.ts +25 -5
- package/src/plugins/builtin/news/wire/index.tsx +2 -2
- package/src/plugins/builtin/options/analytics.test.ts +83 -0
- package/src/plugins/builtin/options/analytics.ts +110 -0
- package/src/plugins/builtin/options/index.tsx +18 -2
- package/src/plugins/builtin/options/table.ts +121 -51
- package/src/plugins/builtin/options/types.ts +22 -15
- package/src/plugins/builtin/options/view.test.tsx +49 -3
- package/src/plugins/builtin/options/view.tsx +92 -19
- package/src/plugins/builtin/plugin-module.test.ts +2 -4
- package/src/plugins/builtin/plugin-module.ts +0 -7
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +2 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
- package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
- package/src/plugins/builtin/portfolio-list/column-values.ts +15 -4
- package/src/plugins/builtin/portfolio-list/header.tsx +13 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +9 -0
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +31 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +10 -7
- package/src/plugins/builtin/portfolio-list/position-metrics.ts +25 -6
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +21 -16
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +20 -6
- package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +33 -7
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +109 -12
- package/src/plugins/builtin/research/index.tsx +4 -0
- package/src/plugins/builtin/scanner/index.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +1 -0
- package/src/plugins/builtin/shared/public-pane.test.ts +12 -0
- package/src/plugins/builtin/shared/public-pane.ts +9 -0
- package/src/plugins/builtin/shared/ticker-source.ts +32 -0
- package/src/plugins/builtin/shared/ticker-surface.test.ts +27 -0
- package/src/plugins/builtin/shared/ticker-surface.ts +17 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
- package/src/plugins/builtin/ticker-detail/index.tsx +12 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +3 -0
- package/src/plugins/builtin/world-indices/index.tsx +3 -0
- package/src/plugins/builtin/world-venue-map/index.ts +27 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +337 -0
- package/src/plugins/builtin/world-venue-map/model.test.ts +53 -0
- package/src/plugins/builtin/world-venue-map/model.ts +147 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
- package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
- package/src/plugins/catalog-backend.ts +6 -44
- package/src/plugins/catalog-browser.test.ts +10 -0
- package/src/plugins/catalog-browser.ts +5 -1
- package/src/plugins/gridlock-notification.ts +2 -1
- package/src/plugins/pane-manager/gridlock.ts +87 -3
- package/src/plugins/pane-manager.test.ts +30 -0
- package/src/plugins/pane-manager.ts +6 -1
- package/src/plugins/prediction-markets/chart.tsx +2 -2
- package/src/plugins/prediction-markets/controller/index.ts +5 -1
- package/src/plugins/prediction-markets/controller/keyboard.ts +10 -14
- package/src/plugins/prediction-markets/detail/overview.tsx +21 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +43 -34
- package/src/plugins/prediction-markets/detail/trades.tsx +34 -9
- package/src/plugins/prediction-markets/detail.test.tsx +29 -2
- package/src/plugins/prediction-markets/index.tsx +8 -4
- package/src/plugins/prediction-markets/navigation.ts +0 -1
- package/src/plugins/prediction-markets/pane.test.tsx +77 -0
- package/src/plugins/prediction-markets/pane.tsx +4 -0
- package/src/plugins/prediction-markets/plugin.test.tsx +36 -0
- package/src/plugins/prediction-markets/test-helpers.tsx +9 -0
- package/src/plugins/registry/index.ts +2 -0
- package/src/plugins/registry/pane-settings.test.ts +53 -0
- package/src/plugins/registry/pane-settings.ts +36 -3
- package/src/plugins/ticker-navigation.test.ts +24 -0
- package/src/plugins/ticker-navigation.ts +40 -0
- package/src/remote/schema.ts +1 -1
- package/src/renderers/browser/cloud-transport.test.ts +36 -0
- package/src/renderers/browser/cloud-transport.ts +19 -0
- package/src/renderers/browser/config-host.ts +0 -4
- package/src/renderers/browser/deeplink-bridge.test.ts +41 -0
- package/src/renderers/browser/deeplink-bridge.ts +22 -0
- package/src/renderers/browser/main.tsx +3 -0
- package/src/renderers/browser/ui-host.tsx +11 -0
- package/src/renderers/cloudflare/worker.test.ts +8 -5
- package/src/renderers/cloudflare/worker.ts +6 -5
- package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
- package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/plugin-state.ts +7 -1
- package/src/renderers/electrobun/bun/index.ts +1 -0
- package/src/renderers/electrobun/shared/protocol.ts +4 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/host/native.ts +0 -5
- package/src/renderers/electrobun/view/remote/persistence.ts +5 -0
- package/src/renderers/electrobun/view/styles.css +107 -7
- package/src/renderers/electrobun/view/toast-host.test.tsx +78 -0
- package/src/renderers/electrobun/view/toast-host.tsx +69 -43
- package/src/renderers/electrobun/view/ui-host.tsx +7 -1
- package/src/renderers/opentui/host.tsx +4 -0
- package/src/renderers/share/layout-view.test.tsx +58 -0
- package/src/renderers/share/layout-view.tsx +133 -0
- package/src/renderers/share/main.tsx +54 -6
- package/src/renderers/share/styles.css +35 -0
- package/src/renderers/share/view.test.tsx +78 -0
- package/src/renderers/share/view.tsx +71 -0
- package/src/shares/api.test.ts +52 -3
- package/src/shares/api.ts +39 -15
- package/src/shares/location.test.ts +19 -0
- package/src/shares/location.ts +11 -0
- package/src/shares/pane.test.ts +110 -0
- package/src/shares/pane.ts +54 -0
- package/src/shares/payload.ts +74 -1
- package/src/state/app/bootstrap.ts +6 -7
- package/src/state/app/context/index.test.ts +41 -1
- package/src/state/app/context/index.test.tsx +34 -16
- package/src/state/pane-table-export-registry.ts +31 -0
- package/src/state/selectors-ui.ts +0 -8
- package/src/sync/controller.test.ts +57 -1
- package/src/sync/controller.ts +22 -11
- package/src/sync/react.ts +13 -0
- package/src/theme/font-scale.ts +0 -11
- package/src/time-series/range.ts +12 -0
- package/src/time-series/resolution.ts +3 -15
- package/src/time-series/spec.ts +5 -5
- package/src/types/config.ts +35 -5
- package/src/types/desktop-menu.ts +1 -0
- package/src/types/plugin.ts +38 -20
- package/src/ui/context-menu.tsx +1 -1
- package/src/ui/host.tsx +7 -0
- package/src/ui/toast.tsx +2 -0
- package/src/utils/back-navigation.ts +0 -4
- package/src/utils/csv.test.ts +13 -0
- package/src/utils/csv.ts +37 -0
- package/src/utils/save-text-file.test.ts +24 -0
- package/src/utils/save-text-file.ts +36 -0
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ bun run web:build
|
|
|
46
46
|
bunx wrangler dev
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Validate the public artifacts with `bun run web:audit` and `bun run cloudflare:dry-run`. `wrangler.jsonc`
|
|
49
|
+
Validate the public artifacts with `bun run web:audit` and `bun run cloudflare:dry-run`. `wrangler.jsonc` remains generic for local checks. After verification passes on `main`, GitHub Actions deploys `term.gloom.sh` with `wrangler.production.jsonc`. The private Gloom Cloud API is deployed separately.
|
|
50
50
|
|
|
51
51
|
## Install
|
|
52
52
|
|
|
@@ -166,10 +166,9 @@ Human-readable output is the default. Automation can opt into structured output
|
|
|
166
166
|
| `gloomberb portfolio [action]` | Manage manual portfolios |
|
|
167
167
|
| `gloomberb watchlist [action]` | Manage watchlists |
|
|
168
168
|
| `gloomberb notes|alerts [action]` | Manage local notes and alerts |
|
|
169
|
-
| `gloomberb broker|ibkr [action]` | Inspect broker
|
|
170
|
-
| `gloomberb ai providers|ask
|
|
169
|
+
| `gloomberb broker|ibkr [action]` | Inspect broker profiles |
|
|
170
|
+
| `gloomberb ai providers|ask` | Use configured AI providers |
|
|
171
171
|
| `gloomberb rss fetch <url>` | Fetch an RSS feed |
|
|
172
|
-
| `gloomberb buildout|congress|substack|x-feed|tweets` | Access cloud and social data sources when an existing session is available |
|
|
173
172
|
| `gloomberb provider status` | Inspect enabled data providers |
|
|
174
173
|
| `gloomberb config|cache|plugin|layout|pane|debug|doctor|version|changelog` | Inspect and manage local app state |
|
|
175
174
|
| `gloomberb fn [...]` | Run a pane-backed report command |
|
|
@@ -180,8 +179,6 @@ Human-readable output is the default. Automation can opt into structured output
|
|
|
180
179
|
| `gloomberb remove <name>` | Remove an installed plugin |
|
|
181
180
|
| `gloomberb update [name]` | Update plugins |
|
|
182
181
|
|
|
183
|
-
Commands that need a signed-in cloud session may return `auth_required`; sign-in, account management, and chat workflows are still handled in the app UI for now.
|
|
184
|
-
|
|
185
182
|
## Plugins
|
|
186
183
|
|
|
187
184
|
Everything from the portfolio list to broker integrations is a plugin. Plugins can add panes, tabs, columns, command bar commands, CLI commands, status bar widgets, and data providers.
|
|
@@ -206,7 +203,7 @@ See [PLUGINS.md](PLUGINS.md) for the plugin API and the shared UI surface availa
|
|
|
206
203
|
| `Ctrl+Shift+M` | Move focused window (`WIN resize` starts resize mode) |
|
|
207
204
|
| `Ctrl+Shift+D` | Dock or float focused pane |
|
|
208
205
|
| `Ctrl+Shift+L` | Layout actions |
|
|
209
|
-
| `Ctrl+Shift+G` |
|
|
206
|
+
| `Ctrl+Shift+G` | Tidy windows |
|
|
210
207
|
| `Tab` | Switch panes |
|
|
211
208
|
| `j` / `k` | Navigate lists |
|
|
212
209
|
| `h` / `l` | Switch tabs |
|
|
@@ -283,12 +280,14 @@ The toolbar controls preset or exact date ranges, intervals from one minute thro
|
|
|
283
280
|
| `TBO` | TheBuildout infrastructure intelligence |
|
|
284
281
|
| `CG` | Congress trading disclosures |
|
|
285
282
|
| `WEI` | Global equity indices |
|
|
283
|
+
| `MAP` | Live world venue map with local market status and clocks |
|
|
286
284
|
| `FUT` | Front-month futures across index, rates, energy, metals, grains, and FX |
|
|
287
285
|
| `ECON` | Economic events and releases |
|
|
288
286
|
| `GC` | Yield curve |
|
|
289
287
|
| `AUCT` | Treasury auction results: high rate, bid-to-cover, indirect share, and size |
|
|
290
288
|
| `VIX` | VIX 30-day/3-month implied-volatility curve |
|
|
291
289
|
| `CRD` | Credit spreads |
|
|
290
|
+
| `CDS [ticker]` | Single-name corporate CDS activity: most-active issuers, or one issuer's trades |
|
|
292
291
|
| `ERN` | Earnings calendar |
|
|
293
292
|
| `IPO` | Upcoming and recent IPOs |
|
|
294
293
|
| `HALT` | US trading halts with reason and resumption times |
|
|
@@ -318,9 +317,10 @@ The toolbar controls preset or exact date ranges, intervals from one minute thro
|
|
|
318
317
|
| `AW` / `AP <ticker>` | Add a ticker to the active watchlist or portfolio |
|
|
319
318
|
| `RW` / `RP <ticker>` | Remove a ticker from the active watchlist or portfolio |
|
|
320
319
|
| `PS` | Open focused pane settings |
|
|
321
|
-
| `LAY
|
|
320
|
+
| `LAY` | Open the layout browser to switch, publish, or add layouts |
|
|
321
|
+
| `LMA <query>` | Layout and pane arrangement actions |
|
|
322
322
|
| `WIN move\|resize` | Move or resize the focused window |
|
|
323
|
-
| `GL` |
|
|
323
|
+
| `GL` | Tidy all windows |
|
|
324
324
|
| `SB` | Toggle the status bar |
|
|
325
325
|
| `VF` | Toggle quote value flashing |
|
|
326
326
|
| `TH <theme>` | Change color theme |
|
|
@@ -333,6 +333,8 @@ The toolbar controls preset or exact date ranges, intervals from one minute thro
|
|
|
333
333
|
| `LANG <locale>` | Change interface language (`auto`, `en`, `es`, `zh-CN`, `zh-TW`, `ja`, or `ko`) |
|
|
334
334
|
| `PL <plugin>` | Manage plugins |
|
|
335
335
|
|
|
336
|
+
Published layouts preserve portable pane setup and state, including searches, chart viewport, and drawings. Credentials, accounts, portfolios, and pane fields marked private stay local. Publishing copies a durable `term.gloom.sh/l/...` link for social sharing.
|
|
337
|
+
|
|
336
338
|
## Gloom Cloud sign-in
|
|
337
339
|
|
|
338
340
|
Sign in with email and password, or pick `Log In with QR Code` from the command bar and scan the code with the Gloomberb mobile companion app to sign the terminal in without typing. The onboarding wizard offers the same QR option as the recommended path, with email and password as the alternative.
|
package/README.zh-CN.md
CHANGED
|
@@ -136,10 +136,9 @@ gloomberb
|
|
|
136
136
|
| `gloomberb portfolio [action]` | 管理手动投资组合 |
|
|
137
137
|
| `gloomberb watchlist [action]` | 管理自选列表 |
|
|
138
138
|
| `gloomberb notes\|alerts [action]` | 管理本地笔记与提醒 |
|
|
139
|
-
| `gloomberb broker\|ibkr [action]` |
|
|
140
|
-
| `gloomberb ai providers\|ask
|
|
139
|
+
| `gloomberb broker\|ibkr [action]` | 查看券商配置 |
|
|
140
|
+
| `gloomberb ai providers\|ask` | 使用已配置的 AI 服务商 |
|
|
141
141
|
| `gloomberb rss fetch <url>` | 抓取 RSS 订阅源 |
|
|
142
|
-
| `gloomberb buildout\|congress\|substack\|x-feed\|tweets` | 在已有会话可用时访问云端与社交数据源 |
|
|
143
142
|
| `gloomberb provider status` | 查看已启用的数据源 |
|
|
144
143
|
| `gloomberb config\|cache\|plugin\|layout\|pane\|debug\|doctor\|version\|changelog` | 查看和管理本地应用状态 |
|
|
145
144
|
| `gloomberb fn [...]` | 运行基于面板的报告命令 |
|
|
@@ -150,8 +149,6 @@ gloomberb
|
|
|
150
149
|
| `gloomberb remove <name>` | 移除已安装插件 |
|
|
151
150
|
| `gloomberb update [name]` | 更新插件 |
|
|
152
151
|
|
|
153
|
-
需要已登录云端会话的命令可能返回 `auth_required`;登录、账户管理与聊天相关操作目前仍需在应用界面中完成。
|
|
154
|
-
|
|
155
152
|
## 插件
|
|
156
153
|
|
|
157
154
|
从投资组合列表到券商集成,一切皆为插件。插件可以添加面板、页签、列、命令栏命令、CLI 命令、状态栏组件与数据源。
|
|
@@ -260,7 +257,8 @@ gloomberb
|
|
|
260
257
|
| `AW` / `AP <ticker>` | 将股票添加到活动自选列表或投资组合 |
|
|
261
258
|
| `RW` / `RP <ticker>` | 从活动自选列表或投资组合中移除股票 |
|
|
262
259
|
| `PS` | 打开聚焦面板的设置 |
|
|
263
|
-
| `LAY
|
|
260
|
+
| `LAY` | 打开布局浏览器以切换、发布或添加布局 |
|
|
261
|
+
| `LMA <query>` | 布局与面板排列操作 |
|
|
264
262
|
| `WIN move\|resize` | 移动或缩放聚焦窗口 |
|
|
265
263
|
| `GL` | 所有可见面板网格对齐 |
|
|
266
264
|
| `SB` | 切换状态栏 |
|
|
@@ -270,6 +268,8 @@ gloomberb
|
|
|
270
268
|
| `LANG <locale>` | 切换界面语言(`auto`、`en`、`es`、`zh-CN`、`zh-TW`、`ja` 或 `ko`) |
|
|
271
269
|
| `PL <plugin>` | 管理插件 |
|
|
272
270
|
|
|
271
|
+
发布的布局会保留可移植的面板设置与状态,包括搜索条件、图表视窗和绘图;凭据、账户、投资组合以及标记为私密的字段始终保留在本地。发布后会自动复制一个长期有效的 `term.gloom.sh/l/...` 社交分享链接。
|
|
272
|
+
|
|
273
273
|
## 本地化界面
|
|
274
274
|
|
|
275
275
|
Gloomberb 内置 English、简体中文、繁體中文、日本語和한국어界面支持;英文仍是默认回退语言。
|
package/package.json
CHANGED
package/src/api-client/data.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { TickerFinancials } from "../types/financials";
|
|
|
2
2
|
import type { InstrumentSearchResult } from "../types/instrument";
|
|
3
3
|
import { normalizeTweetSearchResponse } from "./normalizers";
|
|
4
4
|
import {
|
|
5
|
+
cloudCdsPath,
|
|
5
6
|
cloudCongressHousePath,
|
|
6
7
|
cloudExchangeRatePath,
|
|
7
8
|
cloudSec13FPath,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
cloudStatementsPath,
|
|
18
19
|
cloudTickerTweetsPath,
|
|
19
20
|
cloudTweetSearchPath,
|
|
21
|
+
type CloudCdsParams,
|
|
20
22
|
type CloudCongressHouseParams,
|
|
21
23
|
type CloudFredSeriesParams,
|
|
22
24
|
type CloudHistoryParams,
|
|
@@ -29,6 +31,7 @@ import {
|
|
|
29
31
|
import type {
|
|
30
32
|
CloudAnalystResearchPayload,
|
|
31
33
|
CloudShortInterestPayload,
|
|
34
|
+
CloudCdsResponse,
|
|
32
35
|
CloudCompanyProfile,
|
|
33
36
|
CloudCongressHousePayload,
|
|
34
37
|
CloudCorporateActionsPayload,
|
|
@@ -52,6 +55,7 @@ import type {
|
|
|
52
55
|
CloudPricePointPayload,
|
|
53
56
|
CloudQuotePayload,
|
|
54
57
|
CloudTweetSearchResponse,
|
|
58
|
+
CloudWorldVenueMapPayload,
|
|
55
59
|
CloudYieldPointPayload,
|
|
56
60
|
} from "./types";
|
|
57
61
|
|
|
@@ -91,6 +95,10 @@ export class CloudDataApi {
|
|
|
91
95
|
return this.postMarketBatch("/market/quotes/batch", targets, mode);
|
|
92
96
|
}
|
|
93
97
|
|
|
98
|
+
async getCloudWorldVenues(): Promise<CloudMarketResponse<CloudWorldVenueMapPayload>> {
|
|
99
|
+
return this.request<CloudMarketResponse<CloudWorldVenueMapPayload>>("/market/venues");
|
|
100
|
+
}
|
|
101
|
+
|
|
94
102
|
async getCloudMarketScreener(
|
|
95
103
|
category: CloudMarketScreenerCategory,
|
|
96
104
|
count = 25,
|
|
@@ -209,6 +217,10 @@ export class CloudDataApi {
|
|
|
209
217
|
return this.request<CloudYieldPointPayload[]>("/cloud/econ/yield-curve");
|
|
210
218
|
}
|
|
211
219
|
|
|
220
|
+
async getCloudCds(params: CloudCdsParams = {}): Promise<CloudCdsResponse> {
|
|
221
|
+
return this.request<CloudCdsResponse>(cloudCdsPath(params));
|
|
222
|
+
}
|
|
223
|
+
|
|
212
224
|
async getCloudCongressHouse(params: CloudCongressHouseParams = {}): Promise<CloudCongressHousePayload> {
|
|
213
225
|
return this.request<CloudCongressHousePayload>(cloudCongressHousePath(params));
|
|
214
226
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { afterEach, describe, expect, jest, test } from "bun:test";
|
|
2
2
|
import type { AuthUser } from "./index";
|
|
3
3
|
import { apiClient, setCloudApiFetchTransport } from "./index";
|
|
4
|
+
import { publishableMarketplaceLayout } from "../layout-marketplace/payload";
|
|
5
|
+
import { createDefaultConfig } from "../types/config";
|
|
6
|
+
import type { PaneDef } from "../types/plugin";
|
|
4
7
|
|
|
5
8
|
const originalFetch = globalThis.fetch;
|
|
6
9
|
const originalWebSocket = globalThis.WebSocket;
|
|
@@ -108,6 +111,50 @@ afterEach(() => {
|
|
|
108
111
|
jest.useRealTimers();
|
|
109
112
|
});
|
|
110
113
|
|
|
114
|
+
describe("apiClient layout marketplace", () => {
|
|
115
|
+
test("lists and publishes validated layouts through authenticated transport", async () => {
|
|
116
|
+
const config = createDefaultConfig("/tmp/api-layout-marketplace-test");
|
|
117
|
+
const panes = new Map(config.layout.instances.map((instance) => [instance.paneId, {
|
|
118
|
+
id: instance.paneId,
|
|
119
|
+
name: instance.paneId,
|
|
120
|
+
component: () => null,
|
|
121
|
+
defaultPosition: "right" as const,
|
|
122
|
+
} satisfies PaneDef]));
|
|
123
|
+
const payload = publishableMarketplaceLayout(config.layout, {}, panes);
|
|
124
|
+
const entry = {
|
|
125
|
+
id: "0123456789abcdef0123456789abcdef",
|
|
126
|
+
name: "Research Desk",
|
|
127
|
+
...payload,
|
|
128
|
+
author: { username: "analyst", displayName: "Analyst" },
|
|
129
|
+
publishedAt: "2026-08-26T00:00:00.000Z",
|
|
130
|
+
};
|
|
131
|
+
const calls: Array<{ url: string; method: string; body?: unknown }> = [];
|
|
132
|
+
apiClient.setSessionToken("marketplace-session");
|
|
133
|
+
setCloudApiFetchTransport(async (url, init) => {
|
|
134
|
+
calls.push({
|
|
135
|
+
url,
|
|
136
|
+
method: init?.method ?? "GET",
|
|
137
|
+
...(typeof init?.body === "string" ? { body: JSON.parse(init.body) } : {}),
|
|
138
|
+
});
|
|
139
|
+
const path = new URL(url).pathname;
|
|
140
|
+
return createResponse(path === `/layouts/${entry.id}` || init?.method === "POST"
|
|
141
|
+
? entry
|
|
142
|
+
: { items: [entry] });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await expect(apiClient.listMarketplaceLayouts()).resolves.toEqual([entry]);
|
|
146
|
+
await expect(apiClient.getMarketplaceLayout(entry.id)).resolves.toEqual(entry);
|
|
147
|
+
await expect(apiClient.publishMarketplaceLayout(entry.name, payload)).resolves.toEqual(entry);
|
|
148
|
+
|
|
149
|
+
expect(calls.map((call) => [new URL(call.url).pathname, call.method])).toEqual([
|
|
150
|
+
["/layouts", "GET"],
|
|
151
|
+
[`/layouts/${entry.id}`, "GET"],
|
|
152
|
+
["/layouts", "POST"],
|
|
153
|
+
]);
|
|
154
|
+
expect(calls[2]?.body).toMatchObject({ name: "Research Desk", schemaVersion: 2, paneState: {} });
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
111
158
|
describe("apiClient auth cookies", () => {
|
|
112
159
|
test("accepts a browser-managed api.gloom.sh cookie without exposing its value", async () => {
|
|
113
160
|
apiClient.setCookieSessionMode(true);
|
package/src/api-client/index.ts
CHANGED
|
@@ -3,9 +3,11 @@ import type { InstrumentSearchResult } from "../types/instrument";
|
|
|
3
3
|
import { CloudAuthApi } from "./auth";
|
|
4
4
|
import { CloudChatApi } from "./chat";
|
|
5
5
|
import { CloudDataApi } from "./data";
|
|
6
|
+
import { ApiRequestError } from "./errors";
|
|
6
7
|
import { CloudApiRequestTransport } from "./request";
|
|
7
8
|
import { CloudApiSocket } from "./socket";
|
|
8
9
|
import type {
|
|
10
|
+
CloudCdsParams,
|
|
9
11
|
CloudCongressHouseParams,
|
|
10
12
|
CloudFredSeriesParams,
|
|
11
13
|
CloudSecFilingParams,
|
|
@@ -44,6 +46,7 @@ import type {
|
|
|
44
46
|
CloudEconEventPayload,
|
|
45
47
|
CloudEquityDiagnosticMode,
|
|
46
48
|
CloudEquityDiagnosticResult,
|
|
49
|
+
CloudCdsResponse,
|
|
47
50
|
CloudFredSeriesPayload,
|
|
48
51
|
CloudYieldPointPayload,
|
|
49
52
|
CloudCongressHousePayload,
|
|
@@ -58,6 +61,7 @@ import type {
|
|
|
58
61
|
CloudMarketBatchPayload,
|
|
59
62
|
CloudMarketScreenerCategory,
|
|
60
63
|
CloudMarketScreenerPayload,
|
|
64
|
+
CloudWorldVenueMapPayload,
|
|
61
65
|
CloudVerificationResponse,
|
|
62
66
|
DeviceAuthStartResponse,
|
|
63
67
|
DeviceAuthTokenResponse,
|
|
@@ -69,6 +73,13 @@ import type {
|
|
|
69
73
|
ScannerKind,
|
|
70
74
|
} from "./types";
|
|
71
75
|
import type { SyncSettings, SyncSnapshot } from "../sync/types";
|
|
76
|
+
import {
|
|
77
|
+
isMarketplaceLayoutId,
|
|
78
|
+
parseMarketplaceLayoutEntry,
|
|
79
|
+
parseMarketplaceLayoutList,
|
|
80
|
+
type LayoutMarketplaceEntry,
|
|
81
|
+
type LayoutMarketplacePayload,
|
|
82
|
+
} from "../layout-marketplace/payload";
|
|
72
83
|
|
|
73
84
|
export type * from "./types";
|
|
74
85
|
export { setCloudApiFetchTransport } from "./request";
|
|
@@ -282,6 +293,14 @@ class GloomApiClient {
|
|
|
282
293
|
return this.request<{ url: string }>("/stripe/checkout", { method: "POST", body: JSON.stringify({}) });
|
|
283
294
|
}
|
|
284
295
|
|
|
296
|
+
/** Stores a verified user's public terminal snapshot or pane handoff. */
|
|
297
|
+
async createTerminalShare(payload: unknown): Promise<{ id: string; expiresAt: string }> {
|
|
298
|
+
return this.request<{ id: string; expiresAt: string }>("/shares", {
|
|
299
|
+
method: "POST",
|
|
300
|
+
body: JSON.stringify(payload),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
285
304
|
/** Stripe billing portal for an account that already has a subscription. */
|
|
286
305
|
async createBillingPortal(): Promise<{ url: string }> {
|
|
287
306
|
return this.request<{ url: string }>("/stripe/portal", { method: "POST", body: JSON.stringify({}) });
|
|
@@ -321,6 +340,45 @@ class GloomApiClient {
|
|
|
321
340
|
});
|
|
322
341
|
}
|
|
323
342
|
|
|
343
|
+
async getMarketplaceLayout(
|
|
344
|
+
id: string,
|
|
345
|
+
options?: { signal?: AbortSignal },
|
|
346
|
+
): Promise<LayoutMarketplaceEntry | null> {
|
|
347
|
+
if (!isMarketplaceLayoutId(id)) return null;
|
|
348
|
+
try {
|
|
349
|
+
return parseMarketplaceLayoutEntry(await this.request<unknown>(`/layouts/${encodeURIComponent(id)}`, {
|
|
350
|
+
method: "GET",
|
|
351
|
+
signal: options?.signal,
|
|
352
|
+
}));
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (error instanceof ApiRequestError && error.status === 404) return null;
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async listMarketplaceLayouts(options?: { signal?: AbortSignal }): Promise<LayoutMarketplaceEntry[]> {
|
|
360
|
+
const items = parseMarketplaceLayoutList(await this.request<unknown>("/layouts", {
|
|
361
|
+
method: "GET",
|
|
362
|
+
signal: options?.signal,
|
|
363
|
+
}));
|
|
364
|
+
if (!items) throw new Error("The layout marketplace returned invalid data.");
|
|
365
|
+
return items;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async publishMarketplaceLayout(
|
|
369
|
+
name: string,
|
|
370
|
+
payload: LayoutMarketplacePayload,
|
|
371
|
+
options?: { signal?: AbortSignal },
|
|
372
|
+
): Promise<LayoutMarketplaceEntry> {
|
|
373
|
+
const item = parseMarketplaceLayoutEntry(await this.request<unknown>("/layouts", {
|
|
374
|
+
method: "POST",
|
|
375
|
+
body: JSON.stringify({ name: name.trim(), ...payload }),
|
|
376
|
+
signal: options?.signal,
|
|
377
|
+
}));
|
|
378
|
+
if (!item) throw new Error("The layout marketplace returned invalid data.");
|
|
379
|
+
return item;
|
|
380
|
+
}
|
|
381
|
+
|
|
324
382
|
async updateSyncSettings(update: Partial<SyncSettings>): Promise<SyncSettings> {
|
|
325
383
|
const result = await this.request<{ settings: SyncSettings }>("/sync/settings", {
|
|
326
384
|
method: "PATCH",
|
|
@@ -488,6 +546,10 @@ class GloomApiClient {
|
|
|
488
546
|
return this.data.getCloudQuotesBatch(targets, mode);
|
|
489
547
|
}
|
|
490
548
|
|
|
549
|
+
async getCloudWorldVenues(): Promise<CloudMarketResponse<CloudWorldVenueMapPayload>> {
|
|
550
|
+
return this.data.getCloudWorldVenues();
|
|
551
|
+
}
|
|
552
|
+
|
|
491
553
|
async getCloudMarketScreener(
|
|
492
554
|
category: CloudMarketScreenerCategory,
|
|
493
555
|
count = 25,
|
|
@@ -582,6 +644,10 @@ class GloomApiClient {
|
|
|
582
644
|
return this.data.getCloudYieldCurve();
|
|
583
645
|
}
|
|
584
646
|
|
|
647
|
+
async getCloudCds(params: CloudCdsParams = {}): Promise<CloudCdsResponse> {
|
|
648
|
+
return this.data.getCloudCds(params);
|
|
649
|
+
}
|
|
650
|
+
|
|
585
651
|
async getCloudCongressHouse(params: CloudCongressHouseParams = {}): Promise<CloudCongressHousePayload> {
|
|
586
652
|
return this.data.getCloudCongressHouse(params);
|
|
587
653
|
}
|
package/src/api-client/paths.ts
CHANGED
|
@@ -16,6 +16,13 @@ export type CloudFredSeriesParams = {
|
|
|
16
16
|
sortOrder?: "asc" | "desc";
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
export type CloudCdsParams = {
|
|
20
|
+
/** Reference entity name, matched by the backend against DTCC issuer names. */
|
|
21
|
+
issuer?: string;
|
|
22
|
+
days?: number;
|
|
23
|
+
limit?: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
19
26
|
export type CloudCongressHouseParams = {
|
|
20
27
|
year?: number;
|
|
21
28
|
limit?: number;
|
|
@@ -137,6 +144,15 @@ export function cloudFredSeriesPath(seriesId: string, params: CloudFredSeriesPar
|
|
|
137
144
|
return appendQuery(`/cloud/econ/series/${encodeURIComponent(seriesId)}`, search);
|
|
138
145
|
}
|
|
139
146
|
|
|
147
|
+
export function cloudCdsPath(params: CloudCdsParams = {}): string {
|
|
148
|
+
const search = new URLSearchParams();
|
|
149
|
+
const issuer = params.issuer?.trim();
|
|
150
|
+
if (issuer) search.set("issuer", issuer);
|
|
151
|
+
if (params.days != null) search.set("days", String(params.days));
|
|
152
|
+
if (params.limit != null) search.set("limit", String(params.limit));
|
|
153
|
+
return appendQuery("/cloud/credit/cds", search);
|
|
154
|
+
}
|
|
155
|
+
|
|
140
156
|
export type CloudSecFilingsParams = {
|
|
141
157
|
ticker: string;
|
|
142
158
|
limit?: number;
|
package/src/api-client/types.ts
CHANGED
|
@@ -253,6 +253,30 @@ export interface CloudQuotePayload extends Quote {
|
|
|
253
253
|
dataSource: "live" | "delayed";
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
+
export interface CloudWorldVenuePayload {
|
|
257
|
+
mic: string;
|
|
258
|
+
name: string;
|
|
259
|
+
title: string;
|
|
260
|
+
country: string;
|
|
261
|
+
countryCode: string;
|
|
262
|
+
city: string;
|
|
263
|
+
timezone: string;
|
|
264
|
+
latitude: number;
|
|
265
|
+
longitude: number;
|
|
266
|
+
isOpen: boolean;
|
|
267
|
+
timeAfterOpenSeconds?: number;
|
|
268
|
+
timeToOpenSeconds?: number;
|
|
269
|
+
timeToCloseSeconds?: number;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface CloudWorldVenueMapPayload {
|
|
273
|
+
providerId: "gloomberb-cloud";
|
|
274
|
+
checkedAt: number;
|
|
275
|
+
refreshAt: number;
|
|
276
|
+
stale?: boolean;
|
|
277
|
+
venues: CloudWorldVenuePayload[];
|
|
278
|
+
}
|
|
279
|
+
|
|
256
280
|
export interface CloudOptionsChainPayload extends OptionsChain {
|
|
257
281
|
providerId: "gloomberb-cloud";
|
|
258
282
|
}
|
|
@@ -322,6 +346,40 @@ export interface CloudFredSeriesPayload {
|
|
|
322
346
|
info: CloudFredSeriesInfoPayload | null;
|
|
323
347
|
}
|
|
324
348
|
|
|
349
|
+
/** One publicly disseminated single-name CDS transaction report. */
|
|
350
|
+
export interface CloudCdsTradePayload {
|
|
351
|
+
disseminationId: string;
|
|
352
|
+
originalDisseminationId: string | null;
|
|
353
|
+
actionType: string;
|
|
354
|
+
eventTimestamp: string;
|
|
355
|
+
executionTimestamp: string | null;
|
|
356
|
+
effectiveDate: string | null;
|
|
357
|
+
expirationDate: string | null;
|
|
358
|
+
maturityDate: string | null;
|
|
359
|
+
issuerName: string | null;
|
|
360
|
+
underlierId: string | null;
|
|
361
|
+
underlierIdSource: string | null;
|
|
362
|
+
upi: string | null;
|
|
363
|
+
upiFisn: string | null;
|
|
364
|
+
upiUnderlierName: string | null;
|
|
365
|
+
notionalAmount: number | null;
|
|
366
|
+
/** Reported notional is a regulatory cap, so the real trade was at least this size. */
|
|
367
|
+
notionalCapped: boolean;
|
|
368
|
+
notionalCurrency: string | null;
|
|
369
|
+
fixedRate: number | null;
|
|
370
|
+
reportedSpread: number | null;
|
|
371
|
+
/** Unit of `reportedSpread`, e.g. basis points or percent. */
|
|
372
|
+
spreadNotation: string | null;
|
|
373
|
+
upfrontAmount: number | null;
|
|
374
|
+
upfrontCurrency: string | null;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export interface CloudCdsResponse {
|
|
378
|
+
source: string;
|
|
379
|
+
asOf: string | null;
|
|
380
|
+
trades: CloudCdsTradePayload[];
|
|
381
|
+
}
|
|
382
|
+
|
|
325
383
|
export interface CloudShortInterestPointPayload {
|
|
326
384
|
settlementDate: string;
|
|
327
385
|
sharesShort: number;
|
|
@@ -720,6 +778,7 @@ export interface CloudEquityDiagnosticPending {
|
|
|
720
778
|
|
|
721
779
|
export interface CloudEquityDiagnosticResponse {
|
|
722
780
|
schemaVersion: 1;
|
|
781
|
+
access: "preview" | "full";
|
|
723
782
|
symbol: string;
|
|
724
783
|
exchange: string;
|
|
725
784
|
companyName?: string;
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
12
12
|
import {
|
|
13
13
|
getFocusedCollectionId,
|
|
14
|
+
resolveTickerForPane,
|
|
14
15
|
syncConfigActiveLayoutState,
|
|
15
16
|
type AppAction,
|
|
16
17
|
type AppState,
|
|
@@ -75,7 +76,9 @@ export function useAppPaneRuntime({
|
|
|
75
76
|
|
|
76
77
|
const persistLayout = useCallback((layout: LayoutConfig, options?: { pushHistory?: boolean; focusedPaneId?: string | null }) => {
|
|
77
78
|
const currentState = stateRef.current;
|
|
78
|
-
const normalizedLayout = normalizePaneLayout(layout
|
|
79
|
+
const normalizedLayout = normalizePaneLayout(layout, {
|
|
80
|
+
resolveOrphanSymbol: (instanceId) => resolveTickerForPane(currentState, instanceId),
|
|
81
|
+
});
|
|
79
82
|
if (options?.pushHistory !== false) {
|
|
80
83
|
dispatch({ type: "PUSH_LAYOUT_HISTORY" });
|
|
81
84
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { createInitialState } from "../../state/app/context";
|
|
3
|
+
import { createTestDataProvider } from "../../test-support/data-provider";
|
|
4
|
+
import { createDefaultConfig, type PaneInstanceConfig } from "../../types/config";
|
|
5
|
+
import type { PaneDef } from "../../types/plugin";
|
|
6
|
+
import { bindAppPanePluginRegistry } from "./plugin-bindings";
|
|
7
|
+
|
|
8
|
+
const paneDef: PaneDef = {
|
|
9
|
+
id: "prediction-markets",
|
|
10
|
+
name: "Prediction Markets",
|
|
11
|
+
component: () => null,
|
|
12
|
+
defaultPosition: "left",
|
|
13
|
+
defaultMode: "floating",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function bindPortablePaneRuntime(disabledPlugins: string[] = []) {
|
|
17
|
+
const config = createDefaultConfig("/tmp/gloomberb-portable-pane-test");
|
|
18
|
+
config.disabledPlugins = disabledPlugins;
|
|
19
|
+
const state = createInitialState(config);
|
|
20
|
+
const actions: any[] = [];
|
|
21
|
+
const built: PaneInstanceConfig[] = [];
|
|
22
|
+
const placed: Array<{ instance: PaneInstanceConfig; options: unknown }> = [];
|
|
23
|
+
const pluginRegistry = {
|
|
24
|
+
panes: new Map([[paneDef.id, paneDef]]),
|
|
25
|
+
getPanePluginId: () => "prediction-markets",
|
|
26
|
+
getTermSizeFn: () => ({ width: 120, height: 40 }),
|
|
27
|
+
} as any;
|
|
28
|
+
|
|
29
|
+
bindAppPanePluginRegistry({
|
|
30
|
+
activatePane() {},
|
|
31
|
+
buildPaneInstance: (paneId, options) => {
|
|
32
|
+
const instance = { paneId, ...options } as PaneInstanceConfig;
|
|
33
|
+
built.push(instance);
|
|
34
|
+
return instance;
|
|
35
|
+
},
|
|
36
|
+
createPaneFromTemplate: async () => {},
|
|
37
|
+
dataProvider: createTestDataProvider(),
|
|
38
|
+
detachedPaneId: null,
|
|
39
|
+
dispatch: (action) => actions.push(action),
|
|
40
|
+
focusVisiblePane() {},
|
|
41
|
+
isDetachedWindow: false,
|
|
42
|
+
openPaneSettings: async () => {},
|
|
43
|
+
openPinnedTicker: async () => {},
|
|
44
|
+
persistLayout() {},
|
|
45
|
+
placePaneInstance: (instance, _def, options) => placed.push({ instance, options }),
|
|
46
|
+
placePinnedTickerTarget() {},
|
|
47
|
+
pluginRegistry,
|
|
48
|
+
publishTickerOpenTarget() {},
|
|
49
|
+
resolveOpenTickerTarget: async () => null,
|
|
50
|
+
resolvePaneTarget: () => null,
|
|
51
|
+
selectTickerInPane() {},
|
|
52
|
+
showPane() {},
|
|
53
|
+
state,
|
|
54
|
+
stateRef: { current: state },
|
|
55
|
+
switchTickerResearchTab() {},
|
|
56
|
+
tickerRepository: {} as any,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return { actions, built, placed, pluginRegistry };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const portablePane = {
|
|
63
|
+
schemaVersion: 2 as const,
|
|
64
|
+
sourceConfigVersion: 13,
|
|
65
|
+
layout: {
|
|
66
|
+
dockRoot: null,
|
|
67
|
+
instances: [{
|
|
68
|
+
instanceId: "p1",
|
|
69
|
+
paneId: "prediction-markets",
|
|
70
|
+
title: "Fed Markets",
|
|
71
|
+
binding: { kind: "none" as const },
|
|
72
|
+
params: { query: "fed" },
|
|
73
|
+
settings: { dense: true },
|
|
74
|
+
}],
|
|
75
|
+
floating: [{ instanceId: "p1", x: 0, y: 0, width: 100, height: 30 }],
|
|
76
|
+
detached: [],
|
|
77
|
+
},
|
|
78
|
+
paneState: {
|
|
79
|
+
p1: { pluginState: { "prediction-markets": { searchQuery: "fed" } } },
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
describe("portable pane runtime", () => {
|
|
84
|
+
test("creates a fresh floating pane and restores its complete state", async () => {
|
|
85
|
+
const runtime = bindPortablePaneRuntime();
|
|
86
|
+
|
|
87
|
+
await runtime.pluginRegistry.openPortablePaneShareAsyncFn(portablePane);
|
|
88
|
+
|
|
89
|
+
expect(runtime.built).toHaveLength(1);
|
|
90
|
+
expect(runtime.built[0]).toMatchObject({
|
|
91
|
+
paneId: "prediction-markets",
|
|
92
|
+
title: "Fed Markets",
|
|
93
|
+
binding: { kind: "none" },
|
|
94
|
+
params: { query: "fed" },
|
|
95
|
+
settings: { dense: true },
|
|
96
|
+
});
|
|
97
|
+
expect(runtime.built[0]!.instanceId).toMatch(/^prediction-markets:shared-/);
|
|
98
|
+
expect(runtime.placed).toEqual([{
|
|
99
|
+
instance: runtime.built[0],
|
|
100
|
+
options: { placement: "floating" },
|
|
101
|
+
}]);
|
|
102
|
+
expect(runtime.actions).toContainEqual({
|
|
103
|
+
type: "REPLACE_PANE_STATE",
|
|
104
|
+
paneId: runtime.built[0]!.instanceId,
|
|
105
|
+
state: portablePane.paneState.p1,
|
|
106
|
+
});
|
|
107
|
+
expect(runtime.actions.some((action) => action.type === "INSTALL_LAYOUT_COPY")).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("rejects panes owned by a disabled plugin", async () => {
|
|
111
|
+
const runtime = bindPortablePaneRuntime(["prediction-markets"]);
|
|
112
|
+
await expect(runtime.pluginRegistry.openPortablePaneShareAsyncFn(portablePane)).rejects.toThrow(
|
|
113
|
+
"unavailable",
|
|
114
|
+
);
|
|
115
|
+
expect(runtime.built).toHaveLength(0);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "../../components/command-bar/workflow/ops";
|
|
6
6
|
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
7
7
|
import { setLayoutManagerDispatch } from "../../plugins/builtin/layout-manager";
|
|
8
|
+
import { materializeMarketplaceLayout } from "../../layout-marketplace/payload";
|
|
8
9
|
import {
|
|
9
10
|
isPaneInLayout,
|
|
10
11
|
removePane,
|
|
@@ -112,7 +113,7 @@ export function bindAppPanePluginRegistry({
|
|
|
112
113
|
launch: { kind: "plugin-command", commandId },
|
|
113
114
|
});
|
|
114
115
|
};
|
|
115
|
-
pluginRegistry.getLayoutFn = () =>
|
|
116
|
+
pluginRegistry.getLayoutFn = () => stateRef.current.config.layout;
|
|
116
117
|
pluginRegistry.updateLayoutFn = (layout) => {
|
|
117
118
|
if (isDetachedWindow) return;
|
|
118
119
|
persistLayout(layout);
|
|
@@ -134,6 +135,25 @@ export function bindAppPanePluginRegistry({
|
|
|
134
135
|
placePaneInstance,
|
|
135
136
|
});
|
|
136
137
|
};
|
|
138
|
+
pluginRegistry.openPortablePaneShareAsyncFn = async (payload) => {
|
|
139
|
+
if (isDetachedWindow) throw new Error("Open shared panes in the main window.");
|
|
140
|
+
const materialized = materializeMarketplaceLayout(payload);
|
|
141
|
+
const sharedPane = materialized.layout.instances[0];
|
|
142
|
+
if (!sharedPane) throw new Error("This shared pane is invalid.");
|
|
143
|
+
const paneDef = pluginRegistry.panes.get(sharedPane.paneId);
|
|
144
|
+
const ownerId = pluginRegistry.getPanePluginId(sharedPane.paneId);
|
|
145
|
+
if (!paneDef || (ownerId && stateRef.current.config.disabledPlugins.includes(ownerId))) {
|
|
146
|
+
throw new Error("This shared pane is unavailable in this version of Gloomberb.");
|
|
147
|
+
}
|
|
148
|
+
const instance = buildPaneInstance(sharedPane.paneId, sharedPane);
|
|
149
|
+
if (!instance) throw new Error("This shared pane could not be created.");
|
|
150
|
+
dispatch({
|
|
151
|
+
type: "REPLACE_PANE_STATE",
|
|
152
|
+
paneId: instance.instanceId,
|
|
153
|
+
state: materialized.paneState[instance.instanceId] ?? {},
|
|
154
|
+
});
|
|
155
|
+
placePaneInstance(instance, paneDef, { placement: "floating" });
|
|
156
|
+
};
|
|
137
157
|
pluginRegistry.createPaneFromTemplateFn = (templateId, options) => {
|
|
138
158
|
if (isDetachedWindow) return;
|
|
139
159
|
void createPaneFromTemplate(templateId, options);
|
|
@@ -151,8 +171,9 @@ export function bindAppPanePluginRegistry({
|
|
|
151
171
|
pluginRegistry.hidePaneFn = (paneId) => {
|
|
152
172
|
if (isDetachedWindow) return;
|
|
153
173
|
const instanceId = resolvePaneTarget(paneId);
|
|
154
|
-
|
|
155
|
-
|
|
174
|
+
const layout = stateRef.current.config.layout;
|
|
175
|
+
if (!instanceId || !isPaneInLayout(layout, instanceId)) return;
|
|
176
|
+
persistLayout(removePane(layout, instanceId));
|
|
156
177
|
};
|
|
157
178
|
pluginRegistry.focusPaneFn = (paneId) => {
|
|
158
179
|
if (isDetachedWindow) {
|
|
@@ -162,7 +183,7 @@ export function bindAppPanePluginRegistry({
|
|
|
162
183
|
return;
|
|
163
184
|
}
|
|
164
185
|
const instanceId = resolvePaneTarget(paneId);
|
|
165
|
-
if (!instanceId || !isPaneInLayout(
|
|
186
|
+
if (!instanceId || !isPaneInLayout(stateRef.current.config.layout, instanceId)) {
|
|
166
187
|
showPane(paneId);
|
|
167
188
|
return;
|
|
168
189
|
}
|