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
|
@@ -3,9 +3,12 @@ import {
|
|
|
3
3
|
addPaneFloating,
|
|
4
4
|
addPaneToLayout,
|
|
5
5
|
findDockLeaf,
|
|
6
|
-
isPaneInLayout,
|
|
7
6
|
} from "../../plugins/pane-manager";
|
|
8
7
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
8
|
+
import {
|
|
9
|
+
findTickerResearchFollower,
|
|
10
|
+
listVisibleTickerSourcePanes,
|
|
11
|
+
} from "../../plugins/ticker-navigation";
|
|
9
12
|
import type {
|
|
10
13
|
AppAction,
|
|
11
14
|
AppState,
|
|
@@ -45,6 +48,7 @@ export function useAppTickerInspectorRuntime({
|
|
|
45
48
|
pluginRegistry,
|
|
46
49
|
state,
|
|
47
50
|
}: UseAppTickerInspectorRuntimeOptions) {
|
|
51
|
+
// Only for APIs that must land in a collection (selecting a symbol inside a list).
|
|
48
52
|
const resolveCollectionSourcePaneId = useCallback((preferredPaneId?: string | null) => {
|
|
49
53
|
return resolveFollowBindingInstance(state.config.layout, preferredPaneId, isCollectionPaneInstance)?.instanceId
|
|
50
54
|
?? resolveFollowBindingInstance(state.config.layout, state.focusedPaneId, isCollectionPaneInstance)?.instanceId
|
|
@@ -52,6 +56,18 @@ export function useAppTickerInspectorRuntime({
|
|
|
52
56
|
?? null;
|
|
53
57
|
}, [state.config.layout, state.focusedPaneId]);
|
|
54
58
|
|
|
59
|
+
/** Any visible pane that publishes a cursor symbol can drive a follow binding. */
|
|
60
|
+
const resolveTickerSourcePaneId = useCallback((preferredPaneId?: string | null) => {
|
|
61
|
+
const isTickerSource = (instance: PaneInstanceConfig) => (
|
|
62
|
+
pluginRegistry.panes.get(instance.paneId)?.tickerSource === true
|
|
63
|
+
);
|
|
64
|
+
return resolveFollowBindingInstance(state.config.layout, preferredPaneId, isTickerSource)?.instanceId
|
|
65
|
+
?? resolveFollowBindingInstance(state.config.layout, state.focusedPaneId, isTickerSource)?.instanceId
|
|
66
|
+
?? findPrimaryPaneInstance(state.config.layout, "portfolio-list")?.instanceId
|
|
67
|
+
?? listVisibleTickerSourcePanes(state.config.layout, pluginRegistry.panes)[0]?.instanceId
|
|
68
|
+
?? null;
|
|
69
|
+
}, [pluginRegistry, state.config.layout, state.focusedPaneId]);
|
|
70
|
+
|
|
55
71
|
const resolveTickerContextSourcePaneId = useCallback((preferredPaneId?: string | null) => {
|
|
56
72
|
return resolveFollowBindingInstance(state.config.layout, preferredPaneId, isTickerContextPaneInstance)?.instanceId
|
|
57
73
|
?? resolveFollowBindingInstance(state.config.layout, state.focusedPaneId, isTickerContextPaneInstance)?.instanceId
|
|
@@ -67,12 +83,7 @@ export function useAppTickerInspectorRuntime({
|
|
|
67
83
|
}, [dispatch, resolveCollectionSourcePaneId]);
|
|
68
84
|
|
|
69
85
|
const resolveInspectorPane = useCallback((sourcePaneId: string): PaneInstanceConfig | null => {
|
|
70
|
-
return state.config.layout
|
|
71
|
-
instance.paneId === TICKER_RESEARCH_PANE_ID
|
|
72
|
-
&& instance.binding?.kind === "follow"
|
|
73
|
-
&& instance.binding.sourceInstanceId === sourcePaneId
|
|
74
|
-
&& isPaneInLayout(state.config.layout, instance.instanceId),
|
|
75
|
-
) ?? null;
|
|
86
|
+
return findTickerResearchFollower(state.config.layout, sourcePaneId);
|
|
76
87
|
}, [state.config.layout]);
|
|
77
88
|
|
|
78
89
|
const ensureInspectorPane = useCallback((sourcePaneId: string) => {
|
|
@@ -104,7 +115,7 @@ export function useAppTickerInspectorRuntime({
|
|
|
104
115
|
if (target?.paneId === TICKER_RESEARCH_PANE_ID) return target.instanceId;
|
|
105
116
|
const focused = state.focusedPaneId ? resolvePaneInstance(state.config.layout, state.focusedPaneId) : null;
|
|
106
117
|
if (focused?.paneId === TICKER_RESEARCH_PANE_ID) return focused.instanceId;
|
|
107
|
-
const sourcePaneId =
|
|
118
|
+
const sourcePaneId = resolveTickerSourcePaneId(preferredPaneId);
|
|
108
119
|
if (!sourcePaneId) return null;
|
|
109
120
|
const ensured = ensureInspectorPane(sourcePaneId);
|
|
110
121
|
if (!ensured) return null;
|
|
@@ -120,14 +131,14 @@ export function useAppTickerInspectorRuntime({
|
|
|
120
131
|
dispatch,
|
|
121
132
|
ensureInspectorPane,
|
|
122
133
|
persistLayout,
|
|
123
|
-
|
|
134
|
+
resolveTickerSourcePaneId,
|
|
124
135
|
state.config.layout,
|
|
125
136
|
state.focusedPaneId,
|
|
126
137
|
]);
|
|
127
138
|
|
|
128
139
|
const buildPaneBinding = useCallback((paneType: string, preferredPaneId?: string | null): PaneBinding | null => {
|
|
129
140
|
if (normalizePaneId(paneType) === TICKER_RESEARCH_PANE_ID) {
|
|
130
|
-
const sourceInstanceId =
|
|
141
|
+
const sourceInstanceId = resolveTickerSourcePaneId(preferredPaneId);
|
|
131
142
|
return sourceInstanceId ? { kind: "follow", sourceInstanceId } : null;
|
|
132
143
|
}
|
|
133
144
|
if (isTickerPaneId(paneType)) {
|
|
@@ -135,12 +146,12 @@ export function useAppTickerInspectorRuntime({
|
|
|
135
146
|
return sourceInstanceId ? { kind: "follow", sourceInstanceId } : null;
|
|
136
147
|
}
|
|
137
148
|
return { kind: "none" };
|
|
138
|
-
}, [
|
|
149
|
+
}, [resolveTickerContextSourcePaneId, resolveTickerSourcePaneId]);
|
|
139
150
|
|
|
140
151
|
const showTickerResearchPane = useCallback(() => {
|
|
141
|
-
const sourcePaneId =
|
|
152
|
+
const sourcePaneId = resolveTickerSourcePaneId();
|
|
142
153
|
if (!sourcePaneId) {
|
|
143
|
-
notify("Open a
|
|
154
|
+
notify("Open a ticker source pane first to inspect a ticker.");
|
|
144
155
|
return;
|
|
145
156
|
}
|
|
146
157
|
const ensured = ensureInspectorPane(sourcePaneId);
|
|
@@ -154,7 +165,7 @@ export function useAppTickerInspectorRuntime({
|
|
|
154
165
|
ensureInspectorPane,
|
|
155
166
|
notify,
|
|
156
167
|
persistLayout,
|
|
157
|
-
|
|
168
|
+
resolveTickerSourcePaneId,
|
|
158
169
|
state.config.layout,
|
|
159
170
|
]);
|
|
160
171
|
|
|
@@ -101,6 +101,22 @@ describe("desktop deeplinks", () => {
|
|
|
101
101
|
});
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
test("routes only opaque 32-character social share ids", () => {
|
|
105
|
+
const id = "0123456789abcdef0123456789abcdef";
|
|
106
|
+
expect(resolveDesktopDeepLinkAction(`gloomberb://share/${id}`)).toEqual({
|
|
107
|
+
type: "open-share",
|
|
108
|
+
id,
|
|
109
|
+
message: "Opened shared pane.",
|
|
110
|
+
});
|
|
111
|
+
expect(resolveDesktopDeepLinkAction(`gloomberb://layout/${id}`)).toEqual({
|
|
112
|
+
type: "open-layout",
|
|
113
|
+
id,
|
|
114
|
+
message: "Added shared layout.",
|
|
115
|
+
});
|
|
116
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://share/not-valid").type).toBe("unsupported");
|
|
117
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://layout/not-valid").type).toBe("unsupported");
|
|
118
|
+
});
|
|
119
|
+
|
|
104
120
|
test("rejects command-bar, non-gloomberb, and malformed links", () => {
|
|
105
121
|
expect(resolveDesktopDeepLinkAction("gloomberb://command?query=profile").type).toBe("unsupported");
|
|
106
122
|
expect(resolveDesktopDeepLinkAction("gloomberb://search/NVDA").type).toBe("unsupported");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, type Dispatch } from "react";
|
|
2
|
+
import { apiClient } from "../../api-client";
|
|
2
3
|
import { getDockedPaneIds } from "../../plugins/pane-manager";
|
|
3
4
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
4
5
|
import type { AppAction, AppState } from "../../state/app/context";
|
|
@@ -11,6 +12,9 @@ import type { DesktopDeepLinkBridge } from "../../types/desktop-deeplink";
|
|
|
11
12
|
import type { DesktopWindowBridge } from "../../types/desktop-window";
|
|
12
13
|
import { requestAccountManagementTab } from "../../plugins/builtin/account-management/navigation";
|
|
13
14
|
import { chatController } from "../../plugins/builtin/chat/controller";
|
|
15
|
+
import { getShare } from "../../shares/api";
|
|
16
|
+
import { openPaneShare } from "../../shares/pane";
|
|
17
|
+
import { materializeMarketplaceLayout } from "../../layout-marketplace/payload";
|
|
14
18
|
|
|
15
19
|
type CloudDeepLinkRoute = {
|
|
16
20
|
kind: "cloud-alerts" | "cloud-emails" | "cloud-roundup" | "cloud-success";
|
|
@@ -20,6 +24,7 @@ type CloudDeepLinkRoute = {
|
|
|
20
24
|
type CollectionDeepLinkKind = "collection" | "portfolio" | "watchlist";
|
|
21
25
|
type AlertDeepLinkCondition = "above" | "below" | "crosses";
|
|
22
26
|
type NewsDeepLinkKind = "breaking" | "feed" | "ticker" | "top";
|
|
27
|
+
const SHARE_ID = /^[a-f0-9]{32}$/;
|
|
23
28
|
|
|
24
29
|
export type DesktopDeepLinkAction =
|
|
25
30
|
| { type: "open-account-management"; route: CloudDeepLinkRoute; message: string }
|
|
@@ -33,6 +38,8 @@ export type DesktopDeepLinkAction =
|
|
|
33
38
|
| { type: "open-chat-channel"; channelId: string; messageId: string | null; message: string }
|
|
34
39
|
| { type: "open-chat-dm"; participants: string; message: string }
|
|
35
40
|
| { type: "open-news"; kind: NewsDeepLinkKind; symbol: string | null; message: string }
|
|
41
|
+
| { type: "open-share"; id: string; message: string }
|
|
42
|
+
| { type: "open-layout"; id: string; message: string }
|
|
36
43
|
| { type: "unsupported"; message: string };
|
|
37
44
|
|
|
38
45
|
interface ParsedGloomUrl {
|
|
@@ -241,6 +248,20 @@ function parseNewsDeepLink(parsed: ParsedGloomUrl): DesktopDeepLinkAction {
|
|
|
241
248
|
return { type: "unsupported", message: "Unsupported Gloomberb news link." };
|
|
242
249
|
}
|
|
243
250
|
|
|
251
|
+
function parseLayoutDeepLink(parsed: ParsedGloomUrl): DesktopDeepLinkAction {
|
|
252
|
+
const id = parsed.segments[0] ?? param(parsed.url, "id", "layout");
|
|
253
|
+
return id && SHARE_ID.test(id)
|
|
254
|
+
? { type: "open-layout", id, message: "Added shared layout." }
|
|
255
|
+
: { type: "unsupported", message: "Layout links need a valid id." };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function parseShareDeepLink(parsed: ParsedGloomUrl): DesktopDeepLinkAction {
|
|
259
|
+
const id = parsed.segments[0] ?? param(parsed.url, "id", "share");
|
|
260
|
+
return id && SHARE_ID.test(id)
|
|
261
|
+
? { type: "open-share", id, message: "Opened shared pane." }
|
|
262
|
+
: { type: "unsupported", message: "Share links need a valid id." };
|
|
263
|
+
}
|
|
264
|
+
|
|
244
265
|
export function resolveDesktopDeepLinkAction(rawUrl: string): DesktopDeepLinkAction {
|
|
245
266
|
const parsed = parseGloomUrl(rawUrl);
|
|
246
267
|
if (!parsed) return { type: "unsupported", message: "Unsupported Gloomberb link." };
|
|
@@ -262,6 +283,10 @@ export function resolveDesktopDeepLinkAction(rawUrl: string): DesktopDeepLinkAct
|
|
|
262
283
|
return parseChatDeepLink(parsed);
|
|
263
284
|
case "news":
|
|
264
285
|
return parseNewsDeepLink(parsed);
|
|
286
|
+
case "share":
|
|
287
|
+
return parseShareDeepLink(parsed);
|
|
288
|
+
case "layout":
|
|
289
|
+
return parseLayoutDeepLink(parsed);
|
|
265
290
|
default:
|
|
266
291
|
return { type: "unsupported", message: "Unsupported Gloomberb link." };
|
|
267
292
|
}
|
|
@@ -462,6 +487,33 @@ function handleOpenNews(
|
|
|
462
487
|
notifySuccess(pluginRegistry, action.message);
|
|
463
488
|
}
|
|
464
489
|
|
|
490
|
+
async function handleOpenLayout(
|
|
491
|
+
action: Extract<DesktopDeepLinkAction, { type: "open-layout" }>,
|
|
492
|
+
options: DesktopDeepLinkHandlerOptions,
|
|
493
|
+
): Promise<void> {
|
|
494
|
+
const entry = await apiClient.getMarketplaceLayout(action.id);
|
|
495
|
+
if (!entry) throw new Error("This shared layout is unavailable.");
|
|
496
|
+
const installed = materializeMarketplaceLayout(entry);
|
|
497
|
+
options.dispatch({
|
|
498
|
+
type: "INSTALL_LAYOUT_COPY",
|
|
499
|
+
name: entry.name,
|
|
500
|
+
layout: installed.layout,
|
|
501
|
+
paneState: installed.paneState,
|
|
502
|
+
});
|
|
503
|
+
notifySuccess(options.pluginRegistry, `Layout "${entry.name}" added.`);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
async function handleOpenShare(
|
|
507
|
+
action: Extract<DesktopDeepLinkAction, { type: "open-share" }>,
|
|
508
|
+
pluginRegistry: PluginRegistry,
|
|
509
|
+
): Promise<void> {
|
|
510
|
+
const share = await getShare(action.id, fetch, { trackView: false });
|
|
511
|
+
if (!share) throw new Error("This share is unavailable or has expired.");
|
|
512
|
+
if (share.kind !== "pane") throw new Error("This link contains a snapshot, not a live pane.");
|
|
513
|
+
await openPaneShare(pluginRegistry, share.data);
|
|
514
|
+
notifySuccess(pluginRegistry, action.message);
|
|
515
|
+
}
|
|
516
|
+
|
|
465
517
|
export function handleDesktopDeepLink(rawUrl: string, options: DesktopDeepLinkHandlerOptions): void {
|
|
466
518
|
const action = resolveDesktopDeepLinkAction(rawUrl);
|
|
467
519
|
if (action.type === "unsupported") {
|
|
@@ -499,6 +551,16 @@ export function handleDesktopDeepLink(rawUrl: string, options: DesktopDeepLinkHa
|
|
|
499
551
|
case "open-news":
|
|
500
552
|
handleOpenNews(action, options.pluginRegistry);
|
|
501
553
|
return;
|
|
554
|
+
case "open-share":
|
|
555
|
+
void handleOpenShare(action, options.pluginRegistry).catch((error) => {
|
|
556
|
+
notifyError(options.pluginRegistry, error instanceof Error ? error.message : "Could not open shared pane.");
|
|
557
|
+
});
|
|
558
|
+
return;
|
|
559
|
+
case "open-layout":
|
|
560
|
+
void handleOpenLayout(action, options).catch((error) => {
|
|
561
|
+
notifyError(options.pluginRegistry, error instanceof Error ? error.message : "Could not open shared layout.");
|
|
562
|
+
});
|
|
563
|
+
return;
|
|
502
564
|
}
|
|
503
565
|
}
|
|
504
566
|
|
|
@@ -506,19 +568,21 @@ export function useDesktopDeepLinkRuntime({
|
|
|
506
568
|
desktopDeepLinkBridge,
|
|
507
569
|
desktopWindowKind,
|
|
508
570
|
dispatch,
|
|
571
|
+
initialized,
|
|
509
572
|
pluginRegistry,
|
|
510
573
|
stateRef,
|
|
511
574
|
}: {
|
|
512
575
|
desktopDeepLinkBridge?: DesktopDeepLinkBridge;
|
|
513
576
|
desktopWindowKind?: DesktopWindowBridge["kind"];
|
|
514
577
|
dispatch: Dispatch<AppAction>;
|
|
578
|
+
initialized: boolean;
|
|
515
579
|
pluginRegistry: PluginRegistry;
|
|
516
580
|
stateRef: { current: AppState };
|
|
517
581
|
}) {
|
|
518
582
|
useEffect(() => {
|
|
519
|
-
if (desktopWindowKind
|
|
583
|
+
if (!initialized || desktopWindowKind === "detached" || !desktopDeepLinkBridge) return;
|
|
520
584
|
return desktopDeepLinkBridge.subscribe((deeplink) => {
|
|
521
585
|
handleDesktopDeepLink(deeplink.url, { dispatch, pluginRegistry, stateRef });
|
|
522
586
|
});
|
|
523
|
-
}, [desktopDeepLinkBridge, desktopWindowKind, dispatch, pluginRegistry, stateRef]);
|
|
587
|
+
}, [desktopDeepLinkBridge, desktopWindowKind, dispatch, initialized, pluginRegistry, stateRef]);
|
|
524
588
|
}
|
|
@@ -46,6 +46,9 @@ export function useDesktopApplicationMenuRuntime({
|
|
|
46
46
|
case "toggle-status-bar":
|
|
47
47
|
dispatch({ type: "TOGGLE_STATUS_BAR" });
|
|
48
48
|
break;
|
|
49
|
+
case "open-layout-gallery":
|
|
50
|
+
pluginRegistry.showPane("layout-marketplace");
|
|
51
|
+
break;
|
|
49
52
|
case "layout-undo":
|
|
50
53
|
dispatch({ type: "UNDO_LAYOUT" });
|
|
51
54
|
break;
|
|
@@ -23,7 +23,7 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
23
23
|
importBrokerPositions,
|
|
24
24
|
marketData,
|
|
25
25
|
pluginRegistry,
|
|
26
|
-
|
|
26
|
+
stateRef,
|
|
27
27
|
tickerRepository,
|
|
28
28
|
}: {
|
|
29
29
|
dataProvider: DataProvider;
|
|
@@ -31,31 +31,32 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
31
31
|
importBrokerPositions: (instanceId: string) => Promise<unknown>;
|
|
32
32
|
marketData: MarketDataCoordinator;
|
|
33
33
|
pluginRegistry: PluginRegistry;
|
|
34
|
-
|
|
34
|
+
stateRef: { current: AppState };
|
|
35
35
|
tickerRepository: AppTickerRepositoryPort;
|
|
36
36
|
}) {
|
|
37
|
-
pluginRegistry.getTickerFn = (symbol) =>
|
|
37
|
+
pluginRegistry.getTickerFn = (symbol) => stateRef.current.tickers.get(symbol) ?? null;
|
|
38
38
|
pluginRegistry.getDataFn = (symbol) => {
|
|
39
|
-
const ticker =
|
|
39
|
+
const ticker = stateRef.current.tickers.get(symbol) ?? null;
|
|
40
40
|
const instrument = instrumentFromTicker(ticker, symbol);
|
|
41
41
|
return instrument ? marketData.getTickerFinancialsSync(instrument) : null;
|
|
42
42
|
};
|
|
43
|
-
pluginRegistry.getConfigFn = () =>
|
|
44
|
-
pluginRegistry.getPaneRuntimeStateFn = (paneId) =>
|
|
43
|
+
pluginRegistry.getConfigFn = () => stateRef.current.config;
|
|
44
|
+
pluginRegistry.getPaneRuntimeStateFn = (paneId) => stateRef.current.paneState[paneId] ?? null;
|
|
45
45
|
pluginRegistry.updatePaneRuntimeStateFn = (paneId, patch) => {
|
|
46
46
|
dispatch({ type: "UPDATE_PANE_STATE", paneId, patch });
|
|
47
47
|
};
|
|
48
48
|
pluginRegistry.getPluginConfigValueFn = (pluginId, key) => (
|
|
49
|
-
(
|
|
49
|
+
(stateRef.current.config.pluginConfig[pluginId]?.[key] as any) ?? null
|
|
50
50
|
);
|
|
51
51
|
|
|
52
52
|
const setPluginConfigValues = async (pluginId: string, values: Record<string, unknown>) => {
|
|
53
|
+
const currentConfig = stateRef.current.config;
|
|
53
54
|
const nextConfig = {
|
|
54
|
-
...
|
|
55
|
+
...currentConfig,
|
|
55
56
|
pluginConfig: {
|
|
56
|
-
...
|
|
57
|
+
...currentConfig.pluginConfig,
|
|
57
58
|
[pluginId]: {
|
|
58
|
-
...(
|
|
59
|
+
...(currentConfig.pluginConfig[pluginId] ?? {}),
|
|
59
60
|
...values,
|
|
60
61
|
},
|
|
61
62
|
},
|
|
@@ -70,13 +71,14 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
70
71
|
};
|
|
71
72
|
pluginRegistry.setPluginConfigValuesFn = setPluginConfigValues;
|
|
72
73
|
pluginRegistry.deletePluginConfigValueFn = async (pluginId, key) => {
|
|
73
|
-
const
|
|
74
|
+
const currentConfig = stateRef.current.config;
|
|
75
|
+
const currentPluginConfig = currentConfig.pluginConfig[pluginId];
|
|
74
76
|
if (!currentPluginConfig || !(key in currentPluginConfig)) return;
|
|
75
77
|
|
|
76
78
|
const nextPluginConfig = { ...currentPluginConfig };
|
|
77
79
|
delete nextPluginConfig[key];
|
|
78
80
|
|
|
79
|
-
const nextAllPluginConfig = { ...
|
|
81
|
+
const nextAllPluginConfig = { ...currentConfig.pluginConfig };
|
|
80
82
|
if (Object.keys(nextPluginConfig).length === 0) {
|
|
81
83
|
delete nextAllPluginConfig[pluginId];
|
|
82
84
|
} else {
|
|
@@ -84,7 +86,7 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
const nextConfig = {
|
|
87
|
-
...
|
|
89
|
+
...currentConfig,
|
|
88
90
|
pluginConfig: nextAllPluginConfig,
|
|
89
91
|
};
|
|
90
92
|
dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
@@ -96,14 +98,14 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
96
98
|
setConfigAccessor?: (accessor: () => AppConfig) => void;
|
|
97
99
|
};
|
|
98
100
|
if (typeof configurableProvider.setConfigAccessor === "function") {
|
|
99
|
-
configurableProvider.setConfigAccessor(() =>
|
|
101
|
+
configurableProvider.setConfigAccessor(() => stateRef.current.config);
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
pluginRegistry.createBrokerInstanceFn = async (brokerType, label, values) => {
|
|
103
105
|
const instanceId = createBrokerInstanceId(
|
|
104
106
|
brokerType,
|
|
105
107
|
label,
|
|
106
|
-
|
|
108
|
+
stateRef.current.config.brokerInstances.map((instance) => instance.id),
|
|
107
109
|
);
|
|
108
110
|
const instance: BrokerInstanceConfig = {
|
|
109
111
|
id: instanceId,
|
|
@@ -114,8 +116,8 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
114
116
|
enabled: true,
|
|
115
117
|
};
|
|
116
118
|
const nextConfig = {
|
|
117
|
-
...
|
|
118
|
-
brokerInstances: [...
|
|
119
|
+
...stateRef.current.config,
|
|
120
|
+
brokerInstances: [...stateRef.current.config.brokerInstances, instance],
|
|
119
121
|
};
|
|
120
122
|
dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
121
123
|
await saveConfigImmediately(nextConfig);
|
|
@@ -124,7 +126,7 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
124
126
|
};
|
|
125
127
|
|
|
126
128
|
pluginRegistry.connectBrokerInstanceFn = async (instanceId) => {
|
|
127
|
-
const instance = getBrokerInstance(
|
|
129
|
+
const instance = getBrokerInstance(stateRef.current.config.brokerInstances, instanceId);
|
|
128
130
|
if (!instance) throw new Error("Broker profile not found.");
|
|
129
131
|
if (instance.enabled === false) throw new Error(`Broker profile "${instance.label}" is disabled.`);
|
|
130
132
|
|
|
@@ -142,8 +144,8 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
142
144
|
};
|
|
143
145
|
|
|
144
146
|
pluginRegistry.updateBrokerInstanceFn = async (instanceId, values, options = {}) => {
|
|
145
|
-
const currentInstance =
|
|
146
|
-
const nextInstances =
|
|
147
|
+
const currentInstance = stateRef.current.config.brokerInstances.find((instance) => instance.id === instanceId);
|
|
148
|
+
const nextInstances = stateRef.current.config.brokerInstances.map((instance) =>
|
|
147
149
|
instance.id === instanceId
|
|
148
150
|
? (() => {
|
|
149
151
|
const nextValues = options.replaceConfig ? values : { ...instance.config, ...values };
|
|
@@ -167,7 +169,7 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
167
169
|
clearPersistedBrokerAccounts(pluginRegistry.persistence.resources, currentInstance);
|
|
168
170
|
}
|
|
169
171
|
const nextConfig = {
|
|
170
|
-
...
|
|
172
|
+
...stateRef.current.config,
|
|
171
173
|
brokerInstances: nextInstances,
|
|
172
174
|
};
|
|
173
175
|
dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
@@ -180,7 +182,7 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
180
182
|
};
|
|
181
183
|
|
|
182
184
|
pluginRegistry.removeBrokerInstanceFn = async (instanceId) => {
|
|
183
|
-
const instance = getBrokerInstance(
|
|
185
|
+
const instance = getBrokerInstance(stateRef.current.config.brokerInstances, instanceId);
|
|
184
186
|
if (!instance) return;
|
|
185
187
|
|
|
186
188
|
clearPersistedBrokerAccounts(pluginRegistry.persistence.resources, instance);
|
|
@@ -189,15 +191,15 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
189
191
|
await broker?.disconnect?.(instance).catch(() => {});
|
|
190
192
|
|
|
191
193
|
const removedPortfolioIds = new Set(
|
|
192
|
-
|
|
194
|
+
stateRef.current.config.portfolios
|
|
193
195
|
.filter((portfolio) => portfolio.brokerInstanceId === instanceId)
|
|
194
196
|
.map((portfolio) => portfolio.id),
|
|
195
197
|
);
|
|
196
198
|
|
|
197
|
-
const nextPortfolios =
|
|
198
|
-
const nextTickers = new Map(
|
|
199
|
+
const nextPortfolios = stateRef.current.config.portfolios.filter((portfolio) => !removedPortfolioIds.has(portfolio.id));
|
|
200
|
+
const nextTickers = new Map(stateRef.current.tickers);
|
|
199
201
|
|
|
200
|
-
for (const ticker of
|
|
202
|
+
for (const ticker of stateRef.current.tickers.values()) {
|
|
201
203
|
const nextPositions = ticker.metadata.positions.filter((position) => position.brokerInstanceId !== instanceId);
|
|
202
204
|
const nextPortfolioRefs = ticker.metadata.portfolios.filter((portfolioId) => !removedPortfolioIds.has(portfolioId));
|
|
203
205
|
const nextBrokerContracts = (ticker.metadata.broker_contracts ?? []).filter((contract) => contract.brokerInstanceId !== instanceId);
|
|
@@ -233,8 +235,8 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
233
235
|
}
|
|
234
236
|
|
|
235
237
|
const nextConfig = {
|
|
236
|
-
...
|
|
237
|
-
brokerInstances:
|
|
238
|
+
...stateRef.current.config,
|
|
239
|
+
brokerInstances: stateRef.current.config.brokerInstances.filter((entry) => entry.id !== instanceId),
|
|
238
240
|
portfolios: nextPortfolios,
|
|
239
241
|
};
|
|
240
242
|
|
package/src/app.tsx
CHANGED
|
@@ -54,6 +54,7 @@ import { scheduleConfigSave } from "./state/config-save-scheduler";
|
|
|
54
54
|
import { measurePerf } from "./utils/perf-marks";
|
|
55
55
|
import { useAppLanguage } from "./i18n/react";
|
|
56
56
|
import { AppLanguageConfigObserver } from "./app/language-observer";
|
|
57
|
+
import { isPaneShareHandoff } from "./shares/location";
|
|
57
58
|
|
|
58
59
|
const EMPTY_EXTERNAL_PLUGINS: LoadedExternalPlugin[] = [];
|
|
59
60
|
|
|
@@ -158,7 +159,9 @@ function AppInner({
|
|
|
158
159
|
const type = notification.type ?? "info";
|
|
159
160
|
let toastId: string | number | undefined;
|
|
160
161
|
const options = {
|
|
161
|
-
|
|
162
|
+
title: notification.title,
|
|
163
|
+
subtitle: notification.subtitle,
|
|
164
|
+
duration: notification.persistent ? 0 : notification.duration,
|
|
162
165
|
action: notification.action
|
|
163
166
|
? {
|
|
164
167
|
label: notification.action.label,
|
|
@@ -246,6 +249,7 @@ function AppInner({
|
|
|
246
249
|
desktopDeepLinkBridge,
|
|
247
250
|
desktopWindowKind: desktopWindowBridge?.kind,
|
|
248
251
|
dispatch,
|
|
252
|
+
initialized: state.initialized,
|
|
249
253
|
pluginRegistry,
|
|
250
254
|
stateRef,
|
|
251
255
|
});
|
|
@@ -275,7 +279,7 @@ function AppInner({
|
|
|
275
279
|
importBrokerPositions,
|
|
276
280
|
marketData,
|
|
277
281
|
pluginRegistry,
|
|
278
|
-
|
|
282
|
+
stateRef,
|
|
279
283
|
tickerRepository,
|
|
280
284
|
});
|
|
281
285
|
|
|
@@ -285,6 +289,7 @@ function AppInner({
|
|
|
285
289
|
dispatch,
|
|
286
290
|
tickerRepository,
|
|
287
291
|
pluginRegistry,
|
|
292
|
+
appActive,
|
|
288
293
|
// Keep a first-run workspace stable while the local guide is active. Once
|
|
289
294
|
// onboarding finishes, the normal pull-before-push sync starts immediately.
|
|
290
295
|
initialized: state.initialized
|
|
@@ -464,8 +469,10 @@ export function App({
|
|
|
464
469
|
const [config, setConfig] = useState(() => {
|
|
465
470
|
return initialCliLaunch.config;
|
|
466
471
|
});
|
|
472
|
+
const shareHandoff = isPaneShareHandoff();
|
|
467
473
|
const [showOnboarding, setShowOnboarding] = useState(() => (
|
|
468
474
|
desktopWindowBridge?.kind !== "detached"
|
|
475
|
+
&& !shareHandoff
|
|
469
476
|
&& (!effectiveInitialConfig.onboardingComplete || !!effectiveInitialConfig.onboardingProgress)
|
|
470
477
|
));
|
|
471
478
|
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
SeriesTransform,
|
|
10
10
|
TimeSeriesPoint,
|
|
11
11
|
} from "../time-series/types";
|
|
12
|
+
import { CHART_RESOLUTIONS, TIME_RANGES } from "../time-series/range";
|
|
12
13
|
import type {
|
|
13
14
|
CapabilityInvoker,
|
|
14
15
|
CapabilitySchema,
|
|
@@ -24,8 +25,8 @@ export const MAX_CHART_SERIES_POINTS = 20_000;
|
|
|
24
25
|
const CAPABILITY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
|
|
25
26
|
const SERIES_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._~%:/-]{0,239}$/;
|
|
26
27
|
const chartSeriesLog = debugLog.createLogger("chart-series");
|
|
27
|
-
const ranges = new Set(
|
|
28
|
-
const resolutions = new Set(
|
|
28
|
+
const ranges = new Set<string>(TIME_RANGES);
|
|
29
|
+
const resolutions = new Set<string>(CHART_RESOLUTIONS);
|
|
29
30
|
const periods = new Set(["auto", "daily", "weekly", "monthly", "quarterly", "annual", "ttm"]);
|
|
30
31
|
const styles = new Set(["line", "area", "step", "columns", "points", "candles", "ohlc", "hlc"]);
|
|
31
32
|
const transforms = new Set(["raw", "percent", "index100", "yoy", "qoq", "log"]);
|
|
@@ -199,4 +199,13 @@ describe("headless Pi AI command", () => {
|
|
|
199
199
|
expect(disconnected.runs).toEqual([]);
|
|
200
200
|
expect(disconnected.closed()).toBe(1);
|
|
201
201
|
});
|
|
202
|
+
|
|
203
|
+
test("rejects unsupported verbs instead of returning a stub payload", async () => {
|
|
204
|
+
const harness = createHarness({
|
|
205
|
+
catalog: { providers: [], refreshErrors: {} },
|
|
206
|
+
});
|
|
207
|
+
await expect(harness.command.execute(["screen"], harness.context))
|
|
208
|
+
.rejects.toThrow("Usage: gloomberb ai providers|ask");
|
|
209
|
+
expect(harness.output).toEqual([]);
|
|
210
|
+
});
|
|
202
211
|
});
|