gloomberb 0.3.7 → 0.4.1
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 +19 -8
- package/package.json +1 -1
- package/src/app.tsx +45 -69
- package/src/cli/base-converter.ts +31 -0
- package/src/cli/commands/search.ts +11 -3
- package/src/cli/commands/ticker.ts +22 -11
- package/src/cli/context.ts +27 -32
- package/src/cli/helpers.ts +1 -4
- package/src/cli/index.ts +140 -123
- package/src/cli/registry.test.ts +226 -0
- package/src/cli/registry.ts +201 -0
- package/src/cli.test.ts +219 -0
- package/src/components/chart/chart-controller.ts +309 -0
- package/src/components/chart/chart-data.ts +5 -6
- package/src/components/chart/chart-pane-settings.ts +27 -0
- package/src/components/chart/chart-resolution.test.ts +56 -0
- package/src/components/chart/chart-resolution.ts +362 -0
- package/src/components/chart/chart-types.ts +12 -4
- package/src/components/chart/chart-viewport.test.ts +25 -0
- package/src/components/chart/chart-viewport.ts +28 -0
- package/src/components/chart/comparison-chart-data.ts +20 -27
- package/src/components/chart/comparison-stock-chart.tsx +374 -99
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +124 -0
- package/src/components/chart/stock-chart.tsx +455 -106
- package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
- package/src/components/command-bar/command-bar.test.tsx +650 -5
- package/src/components/command-bar/command-bar.tsx +363 -63
- package/src/components/command-bar/command-registry.ts +8 -0
- package/src/components/command-bar/helpers.test.ts +23 -1
- package/src/components/command-bar/helpers.ts +14 -5
- package/src/components/command-bar/pane-template-display.ts +12 -0
- package/src/components/command-bar/root-shortcuts.ts +2 -1
- package/src/components/command-bar/workflow-ops.ts +4 -2
- package/src/components/detail-feed-view.test.tsx +124 -49
- package/src/components/detail-feed-view.tsx +396 -160
- package/src/components/layout/floating-pane.tsx +4 -2
- package/src/components/layout/header.tsx +3 -3
- package/src/components/layout/pane-header.tsx +26 -2
- package/src/components/layout/pane-sizing.ts +6 -0
- package/src/components/layout/pane.tsx +5 -1
- package/src/components/layout/shell.test.tsx +77 -5
- package/src/components/layout/shell.tsx +67 -52
- package/src/components/layout/status-bar.test.tsx +66 -0
- package/src/components/layout/status-bar.tsx +26 -5
- package/src/components/onboarding/onboarding-wizard.test.tsx +65 -0
- package/src/components/onboarding/onboarding-wizard.tsx +5 -6
- package/src/components/ticker-list-table.test.tsx +83 -0
- package/src/components/ticker-list-table.tsx +181 -97
- package/src/components/ui/data-table.tsx +50 -5
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/page-stack-view.tsx +60 -0
- package/src/components/use-double-click-activation.ts +31 -0
- package/src/index.tsx +10 -10
- package/src/market-data/coordinator.test.ts +92 -4
- package/src/market-data/coordinator.ts +32 -5
- package/src/market-data/hooks.test.tsx +120 -0
- package/src/market-data/hooks.tsx +53 -16
- package/src/market-data/request-types.ts +4 -2
- package/src/market-data/selectors.ts +24 -58
- package/src/plugins/builtin/chat-controller.test.ts +400 -3
- package/src/plugins/builtin/chat-controller.ts +328 -35
- package/src/plugins/builtin/chat.test.tsx +118 -6
- package/src/plugins/builtin/chat.tsx +180 -116
- package/src/plugins/builtin/comparison-chart.test.tsx +66 -5
- package/src/plugins/builtin/comparison-chart.tsx +21 -1
- package/src/plugins/builtin/layout-manager.tsx +9 -0
- package/src/plugins/builtin/news.tsx +1 -11
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +656 -0
- package/src/plugins/builtin/portfolio-list/command-bar.ts +122 -0
- package/src/plugins/builtin/portfolio-list/header.tsx +16 -34
- package/src/plugins/builtin/portfolio-list/index.tsx +5 -0
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +61 -0
- package/src/plugins/builtin/portfolio-list/metrics.ts +2 -2
- package/src/plugins/builtin/portfolio-list/mutations.test.ts +187 -0
- package/src/plugins/builtin/portfolio-list/mutations.ts +226 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +39 -17
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -2
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +163 -0
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +81 -0
- package/src/plugins/builtin/sec.tsx +1 -11
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +13 -2
- package/src/plugins/builtin/ticker-detail/financials-tab.tsx +21 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +45 -10
- package/src/plugins/builtin/ticker-detail/pane.tsx +35 -17
- package/src/plugins/builtin/ticker-detail/settings.ts +19 -1
- package/src/plugins/builtin/ticker-detail.test.tsx +47 -0
- package/src/plugins/catalog.ts +65 -0
- package/src/plugins/ibkr/broker-adapter.ts +28 -0
- package/src/plugins/ibkr/gateway-service.test.ts +67 -1
- package/src/plugins/ibkr/gateway-service.ts +147 -7
- package/src/plugins/ibkr/index.tsx +7 -1
- package/src/plugins/pane-manager.test.ts +1 -1
- package/src/plugins/plugin-runtime.tsx +27 -7
- package/src/plugins/prediction-markets/cli.ts +28 -0
- package/src/plugins/prediction-markets/controller-data.ts +132 -17
- package/src/plugins/prediction-markets/controller.ts +142 -122
- package/src/plugins/prediction-markets/index.tsx +7 -1
- package/src/plugins/prediction-markets/launch.ts +51 -16
- package/src/plugins/prediction-markets/metrics.ts +9 -5
- package/src/plugins/prediction-markets/pane.test.tsx +197 -57
- package/src/plugins/prediction-markets/pane.tsx +80 -155
- package/src/plugins/prediction-markets/services/fetch.ts +4 -0
- package/src/plugins/prediction-markets/table.tsx +6 -3
- package/src/plugins/prediction-markets/test-helpers.tsx +20 -5
- package/src/plugins/prediction-markets/types.ts +0 -1
- package/src/plugins/registry.ts +23 -0
- package/src/sources/gloomberb-cloud.test.ts +29 -0
- package/src/sources/gloomberb-cloud.ts +89 -1
- package/src/sources/provider-router.test.ts +265 -3
- package/src/sources/provider-router.ts +239 -11
- package/src/sources/yahoo-finance.test.ts +26 -0
- package/src/sources/yahoo-finance.ts +54 -3
- package/src/state/app-context-selector.test.tsx +99 -0
- package/src/state/app-context.test.ts +18 -0
- package/src/state/app-context.tsx +170 -65
- package/src/state/selectors.ts +25 -0
- package/src/theme/colors.ts +8 -8
- package/src/theme/themes.test.ts +2 -0
- package/src/theme/themes.ts +22 -10
- package/src/types/broker.ts +21 -0
- package/src/types/config.ts +17 -1
- package/src/types/data-provider.ts +18 -0
- package/src/types/financials.ts +31 -1
- package/src/types/plugin.ts +70 -0
- package/src/updater.test.ts +2 -0
- package/src/utils/api-client.test.ts +57 -0
- package/src/utils/api-client.ts +47 -12
- package/src/utils/back-navigation.ts +27 -0
- package/src/utils/format.test.ts +14 -0
- package/src/utils/format.ts +3 -3
- package/src/utils/market-status.ts +46 -1
- package/src/utils/quote-freshness.ts +127 -0
- package/src/utils/quote-resolution.test.ts +277 -0
- package/src/utils/quote-resolution.ts +641 -0
- package/src/utils/slugify.ts +3 -0
- package/src/utils/timestamp.ts +25 -0
- package/src/version.ts +1 -1
- package/src/cli/commands/portfolio.ts +0 -210
- package/src/cli/commands/watchlist.ts +0 -210
- package/src/plugins/builtin/data-providers.ts +0 -16
- package/src/sources/exchange-rates.ts +0 -18
- package/src/utils/ascii-chart.ts +0 -172
- package/src/utils/ticker-tokenizer.test.ts +0 -48
|
@@ -18,11 +18,12 @@ import {
|
|
|
18
18
|
import { getFocusedCollectionId, useAppState, useFocusedTicker } from "../../state/app-context";
|
|
19
19
|
import { fuzzyFilter } from "../../utils/fuzzy-search";
|
|
20
20
|
import { commands, getThemeOptions, matchPrefix, type Command } from "./command-registry";
|
|
21
|
-
import { applyTheme
|
|
21
|
+
import { applyTheme } from "../../theme/colors";
|
|
22
22
|
import { exportConfig, importConfig, resetAllData, saveConfig } from "../../data/config-store";
|
|
23
23
|
import type { DataProvider } from "../../types/data-provider";
|
|
24
24
|
import type { TickerRepository } from "../../data/ticker-repository";
|
|
25
25
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
26
|
+
import type { TickerRecord } from "../../types/ticker";
|
|
26
27
|
import type {
|
|
27
28
|
CommandDef,
|
|
28
29
|
PaneSettingField,
|
|
@@ -68,6 +69,7 @@ import {
|
|
|
68
69
|
type TickerSearchCandidate,
|
|
69
70
|
} from "../../utils/ticker-search";
|
|
70
71
|
import { debugLog } from "../../utils/debug-log";
|
|
72
|
+
import { isPlainBackspace } from "../../utils/back-navigation";
|
|
71
73
|
import type {
|
|
72
74
|
CommandBarFieldOption,
|
|
73
75
|
CommandBarFieldValue,
|
|
@@ -78,11 +80,13 @@ import type {
|
|
|
78
80
|
CommandBarWorkflowRoute,
|
|
79
81
|
} from "./workflow-types";
|
|
80
82
|
import { parseRootShortcutIntent } from "./root-shortcuts";
|
|
83
|
+
import { getPaneTemplateDisplayLabel } from "./pane-template-display";
|
|
81
84
|
import {
|
|
82
85
|
applyCollectionMembershipChange,
|
|
83
86
|
getCollectionTargetOptions,
|
|
84
87
|
resolvePreferredCollectionTarget,
|
|
85
88
|
resolveTickerInput,
|
|
89
|
+
resolveTickerInputOrThrow,
|
|
86
90
|
resolveTickerListInput,
|
|
87
91
|
} from "./workflow-ops";
|
|
88
92
|
import {
|
|
@@ -112,6 +116,19 @@ import {
|
|
|
112
116
|
summarizeWorkflowFieldValue,
|
|
113
117
|
toggleSelectedValue,
|
|
114
118
|
} from "./helpers";
|
|
119
|
+
import {
|
|
120
|
+
buildAddToPortfolioWorkflow,
|
|
121
|
+
buildSetPortfolioPositionWorkflow,
|
|
122
|
+
} from "../../plugins/builtin/portfolio-list/command-bar";
|
|
123
|
+
import {
|
|
124
|
+
addTickerToPortfolio,
|
|
125
|
+
createManualPortfolio as createManualPortfolioConfig,
|
|
126
|
+
deleteManualPortfolio,
|
|
127
|
+
isManualPortfolio,
|
|
128
|
+
removeTickerFromPortfolio,
|
|
129
|
+
resolveManualPositionCurrency,
|
|
130
|
+
setManualPortfolioPosition,
|
|
131
|
+
} from "../../plugins/builtin/portfolio-list/mutations";
|
|
115
132
|
|
|
116
133
|
interface CommandBarProps {
|
|
117
134
|
dataProvider: DataProvider;
|
|
@@ -433,7 +450,7 @@ export function CommandBar({
|
|
|
433
450
|
query: initialQuery,
|
|
434
451
|
selectedIdx: 0,
|
|
435
452
|
hoveredIdx: null,
|
|
436
|
-
themeBaseId: screen === "themes" ?
|
|
453
|
+
themeBaseId: screen === "themes" ? stateRef.current.config.theme : undefined,
|
|
437
454
|
payload,
|
|
438
455
|
});
|
|
439
456
|
}, [pushRoute]);
|
|
@@ -590,6 +607,39 @@ export function CommandBar({
|
|
|
590
607
|
return next;
|
|
591
608
|
}, []);
|
|
592
609
|
|
|
610
|
+
const openAddToPortfolioWorkflow = useCallback((
|
|
611
|
+
ticker: TickerRecord,
|
|
612
|
+
preferredPortfolioId?: string | null,
|
|
613
|
+
) => {
|
|
614
|
+
const defaultAvgCost = stateRef.current.financials.get(ticker.metadata.ticker)?.quote?.price ?? null;
|
|
615
|
+
const workflow = buildAddToPortfolioWorkflow(stateRef.current.config, {
|
|
616
|
+
preferredPortfolioId,
|
|
617
|
+
ticker,
|
|
618
|
+
defaultAvgCost,
|
|
619
|
+
});
|
|
620
|
+
if (!workflow) {
|
|
621
|
+
pluginRegistry.showToastFn("Create a manual portfolio first.", { type: "info" });
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
openWorkflowRoute({
|
|
626
|
+
kind: "workflow",
|
|
627
|
+
workflowId: "builtin:add-portfolio",
|
|
628
|
+
title: `Add ${ticker.metadata.ticker} to Portfolio`,
|
|
629
|
+
subtitle: "Choose a portfolio and optionally record the manual position now.",
|
|
630
|
+
fields: workflow.fields,
|
|
631
|
+
values: workflow.values,
|
|
632
|
+
activeFieldId: getFirstVisibleFieldId(workflow.fields, workflow.values),
|
|
633
|
+
submitLabel: "Add to Portfolio",
|
|
634
|
+
cancelLabel: "Back",
|
|
635
|
+
pendingLabel: workflow.pendingLabel,
|
|
636
|
+
pending: false,
|
|
637
|
+
error: null,
|
|
638
|
+
successBehavior: "close",
|
|
639
|
+
payload: { kind: "builtin", actionId: "add-portfolio" },
|
|
640
|
+
});
|
|
641
|
+
}, [openWorkflowRoute, pluginRegistry]);
|
|
642
|
+
|
|
593
643
|
const connectBrokerProfile = useCallback(async (
|
|
594
644
|
brokerId: string,
|
|
595
645
|
values: WorkflowStringValues,
|
|
@@ -625,25 +675,15 @@ export function CommandBar({
|
|
|
625
675
|
|
|
626
676
|
const createManualPortfolio = useCallback(async (name: string) => {
|
|
627
677
|
const currentState = stateRef.current;
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const id = slugifyName(trimmedName, "portfolio");
|
|
634
|
-
const newPortfolio = {
|
|
635
|
-
id,
|
|
636
|
-
name: trimmedName,
|
|
637
|
-
currency: currentState.config.baseCurrency || "USD",
|
|
638
|
-
};
|
|
639
|
-
const nextConfig = {
|
|
640
|
-
...currentState.config,
|
|
641
|
-
portfolios: [...currentState.config.portfolios, newPortfolio],
|
|
642
|
-
};
|
|
678
|
+
const { config: nextConfig, portfolio } = createManualPortfolioConfig(
|
|
679
|
+
currentState.config,
|
|
680
|
+
name,
|
|
681
|
+
currentState.config.baseCurrency,
|
|
682
|
+
);
|
|
643
683
|
dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
644
|
-
setActiveCollection(id);
|
|
684
|
+
setActiveCollection(portfolio.id);
|
|
645
685
|
await saveConfig(nextConfig);
|
|
646
|
-
pluginRegistry.showToastFn(`Created portfolio "${
|
|
686
|
+
pluginRegistry.showToastFn(`Created portfolio "${portfolio.name}".`, { type: "success" });
|
|
647
687
|
}, [dispatch, pluginRegistry, setActiveCollection]);
|
|
648
688
|
|
|
649
689
|
const createWatchlist = useCallback(async (name: string) => {
|
|
@@ -691,11 +731,21 @@ export function CommandBar({
|
|
|
691
731
|
if (!portfolio) {
|
|
692
732
|
throw new Error("Portfolio not found.");
|
|
693
733
|
}
|
|
734
|
+
if (!isManualPortfolio(portfolio)) {
|
|
735
|
+
throw new Error("Broker-managed portfolios cannot be deleted here.");
|
|
736
|
+
}
|
|
694
737
|
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
738
|
+
const result = deleteManualPortfolio(
|
|
739
|
+
currentState.config,
|
|
740
|
+
[...currentState.tickers.values()],
|
|
741
|
+
portfolioId,
|
|
742
|
+
);
|
|
743
|
+
for (const ticker of result.tickers) {
|
|
744
|
+
await tickerRepository.saveTicker(ticker);
|
|
745
|
+
dispatch({ type: "UPDATE_TICKER", ticker });
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const nextConfig = result.config;
|
|
699
749
|
dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
700
750
|
if (activeCollectionId === portfolioId) {
|
|
701
751
|
const fallback = nextConfig.portfolios[0]?.id || nextConfig.watchlists[0]?.id || "";
|
|
@@ -703,7 +753,87 @@ export function CommandBar({
|
|
|
703
753
|
}
|
|
704
754
|
await saveConfig(nextConfig);
|
|
705
755
|
pluginRegistry.showToastFn(`Deleted "${portfolio.name}".`, { type: "success" });
|
|
706
|
-
}, [activeCollectionId, dispatch, pluginRegistry, setActiveCollection]);
|
|
756
|
+
}, [activeCollectionId, dispatch, pluginRegistry, setActiveCollection, tickerRepository]);
|
|
757
|
+
|
|
758
|
+
const setPortfolioPositionFromWorkflow = useCallback(async (values: Record<string, CommandBarFieldValue>) => {
|
|
759
|
+
const currentState = stateRef.current;
|
|
760
|
+
const portfolioId = coerceFieldString(values.portfolioId).trim();
|
|
761
|
+
const portfolio = currentState.config.portfolios.find((entry) => entry.id === portfolioId);
|
|
762
|
+
if (!portfolio || !isManualPortfolio(portfolio)) {
|
|
763
|
+
throw new Error("Choose a manual portfolio.");
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const shares = Number(coerceFieldString(values.shares));
|
|
767
|
+
if (!Number.isFinite(shares) || shares <= 0) {
|
|
768
|
+
throw new Error("Shares must be greater than 0.");
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const avgCost = Number(coerceFieldString(values.avgCost));
|
|
772
|
+
if (!Number.isFinite(avgCost)) {
|
|
773
|
+
throw new Error("Avg Cost must be a valid number.");
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const resolvedTicker = await resolveTickerInputOrThrow(
|
|
777
|
+
coerceFieldString(values.ticker),
|
|
778
|
+
activeTickerSymbol,
|
|
779
|
+
activeCollectionId,
|
|
780
|
+
{
|
|
781
|
+
dataProvider,
|
|
782
|
+
tickerRepository,
|
|
783
|
+
pluginRegistry,
|
|
784
|
+
dispatch,
|
|
785
|
+
getState: () => stateRef.current,
|
|
786
|
+
},
|
|
787
|
+
);
|
|
788
|
+
|
|
789
|
+
const currency = resolveManualPositionCurrency(
|
|
790
|
+
coerceFieldString(values.currency),
|
|
791
|
+
resolvedTicker.ticker,
|
|
792
|
+
portfolio,
|
|
793
|
+
currentState.config.baseCurrency,
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
const result = setManualPortfolioPosition(resolvedTicker.ticker, portfolio.id, {
|
|
797
|
+
shares,
|
|
798
|
+
avgCost,
|
|
799
|
+
currency,
|
|
800
|
+
});
|
|
801
|
+
await tickerRepository.saveTicker(result.ticker);
|
|
802
|
+
dispatch({ type: "UPDATE_TICKER", ticker: result.ticker });
|
|
803
|
+
pluginRegistry.showToastFn(`Set position for ${result.ticker.metadata.ticker} in "${portfolio.name}".`, { type: "success" });
|
|
804
|
+
}, [activeCollectionId, activeTickerSymbol, dataProvider, dispatch, pluginRegistry, tickerRepository]);
|
|
805
|
+
|
|
806
|
+
const addTickerMembershipFromWorkflow = useCallback(async (values: Record<string, CommandBarFieldValue>) => {
|
|
807
|
+
const currentState = stateRef.current;
|
|
808
|
+
const portfolioId = coerceFieldString(values.portfolioId).trim();
|
|
809
|
+
const portfolio = currentState.config.portfolios.find((entry) => entry.id === portfolioId);
|
|
810
|
+
if (!portfolio || !isManualPortfolio(portfolio)) {
|
|
811
|
+
throw new Error("Choose a manual portfolio.");
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const resolvedTicker = await resolveTickerInputOrThrow(
|
|
815
|
+
coerceFieldString(values.ticker),
|
|
816
|
+
activeTickerSymbol,
|
|
817
|
+
activeCollectionId,
|
|
818
|
+
{
|
|
819
|
+
dataProvider,
|
|
820
|
+
tickerRepository,
|
|
821
|
+
pluginRegistry,
|
|
822
|
+
dispatch,
|
|
823
|
+
getState: () => stateRef.current,
|
|
824
|
+
},
|
|
825
|
+
);
|
|
826
|
+
|
|
827
|
+
const result = addTickerToPortfolio(resolvedTicker.ticker, portfolio.id);
|
|
828
|
+
if (result.changed) {
|
|
829
|
+
await tickerRepository.saveTicker(result.ticker);
|
|
830
|
+
dispatch({ type: "UPDATE_TICKER", ticker: result.ticker });
|
|
831
|
+
pluginRegistry.showToastFn(`Added ${result.ticker.metadata.ticker} to "${portfolio.name}".`, { type: "success" });
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
pluginRegistry.showToastFn(`${result.ticker.metadata.ticker} is already in "${portfolio.name}".`, { type: "info" });
|
|
836
|
+
}, [activeCollectionId, activeTickerSymbol, dataProvider, dispatch, pluginRegistry, tickerRepository]);
|
|
707
837
|
|
|
708
838
|
const disconnectBrokerInstance = useCallback(async (instanceId: string) => {
|
|
709
839
|
const instance = stateRef.current.config.brokerInstances.find((entry) => entry.id === instanceId);
|
|
@@ -783,6 +913,33 @@ export function CommandBar({
|
|
|
783
913
|
openWorkflowRoute(workflow);
|
|
784
914
|
return;
|
|
785
915
|
}
|
|
916
|
+
case "set-portfolio-position": {
|
|
917
|
+
const workflow = buildSetPortfolioPositionWorkflow(state.config, {
|
|
918
|
+
activeCollectionId,
|
|
919
|
+
activeTicker: activeTickerData,
|
|
920
|
+
});
|
|
921
|
+
if (!workflow) {
|
|
922
|
+
pluginRegistry.showToastFn("Create a manual portfolio first.", { type: "info" });
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
openWorkflowRoute({
|
|
926
|
+
kind: "workflow",
|
|
927
|
+
workflowId: "builtin:set-portfolio-position",
|
|
928
|
+
title: "Set Portfolio Position",
|
|
929
|
+
subtitle: "Create or update a manual position without leaving the command bar.",
|
|
930
|
+
fields: workflow.fields,
|
|
931
|
+
values: workflow.values,
|
|
932
|
+
activeFieldId: getFirstVisibleFieldId(workflow.fields, workflow.values),
|
|
933
|
+
submitLabel: "Save Position",
|
|
934
|
+
cancelLabel: "Back",
|
|
935
|
+
pendingLabel: workflow.pendingLabel,
|
|
936
|
+
pending: false,
|
|
937
|
+
error: null,
|
|
938
|
+
successBehavior: "close",
|
|
939
|
+
payload: { kind: "builtin", actionId },
|
|
940
|
+
});
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
786
943
|
case "add-broker-account": {
|
|
787
944
|
const workflow = buildBrokerWorkflow(
|
|
788
945
|
"brokerType",
|
|
@@ -801,7 +958,7 @@ export function CommandBar({
|
|
|
801
958
|
default:
|
|
802
959
|
return;
|
|
803
960
|
}
|
|
804
|
-
}, [buildBrokerWorkflow, openWorkflowRoute, pluginRegistry, state.config
|
|
961
|
+
}, [activeCollectionId, activeTickerData, buildBrokerWorkflow, openWorkflowRoute, pluginRegistry, state.config]);
|
|
805
962
|
|
|
806
963
|
const openPickerRoute = useCallback((
|
|
807
964
|
route: CommandBarRoute,
|
|
@@ -912,6 +1069,55 @@ export function CommandBar({
|
|
|
912
1069
|
return;
|
|
913
1070
|
}
|
|
914
1071
|
|
|
1072
|
+
if (kind === "portfolio" && action === "add") {
|
|
1073
|
+
const manualPortfolios = stateForCommand.config.portfolios.filter(isManualPortfolio);
|
|
1074
|
+
if (manualPortfolios.length === 0) {
|
|
1075
|
+
pluginRegistry.showToastFn("Create a manual portfolio first.", { type: "info" });
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
const preferredTargetId = explicitTargetId
|
|
1080
|
+
?? (activeCollectionId && manualPortfolios.some((portfolio) => portfolio.id === activeCollectionId)
|
|
1081
|
+
? activeCollectionId
|
|
1082
|
+
: manualPortfolios.length === 1
|
|
1083
|
+
? manualPortfolios[0]!.id
|
|
1084
|
+
: null);
|
|
1085
|
+
|
|
1086
|
+
if (!preferredTargetId) {
|
|
1087
|
+
const options = manualPortfolios.map((portfolio) => {
|
|
1088
|
+
const isMember = resolvedTicker.ticker.metadata.portfolios.includes(portfolio.id);
|
|
1089
|
+
const description = isMember
|
|
1090
|
+
? `Update position in "${portfolio.name}"`
|
|
1091
|
+
: `Add to "${portfolio.name}"`;
|
|
1092
|
+
return {
|
|
1093
|
+
id: portfolio.id,
|
|
1094
|
+
label: portfolio.name,
|
|
1095
|
+
detail: description,
|
|
1096
|
+
description,
|
|
1097
|
+
};
|
|
1098
|
+
});
|
|
1099
|
+
openPickerRoute({
|
|
1100
|
+
kind: "picker",
|
|
1101
|
+
pickerId: "collection-target",
|
|
1102
|
+
title: `Add ${resolvedTicker.symbol} to Portfolio`,
|
|
1103
|
+
query: "",
|
|
1104
|
+
selectedIdx: 0,
|
|
1105
|
+
hoveredIdx: null,
|
|
1106
|
+
options,
|
|
1107
|
+
payload: {
|
|
1108
|
+
commandId,
|
|
1109
|
+
kind,
|
|
1110
|
+
action,
|
|
1111
|
+
symbol: resolvedTicker.symbol,
|
|
1112
|
+
},
|
|
1113
|
+
});
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
openAddToPortfolioWorkflow(resolvedTicker.ticker, preferredTargetId);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
915
1121
|
const targetId = explicitTargetId
|
|
916
1122
|
?? resolvePreferredCollectionTarget(
|
|
917
1123
|
stateForCommand,
|
|
@@ -956,13 +1162,32 @@ export function CommandBar({
|
|
|
956
1162
|
return;
|
|
957
1163
|
}
|
|
958
1164
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1165
|
+
let changed = false;
|
|
1166
|
+
if (kind === "watchlist") {
|
|
1167
|
+
({ changed } = await applyCollectionMembershipChange(
|
|
1168
|
+
resolvedTicker.ticker,
|
|
1169
|
+
kind,
|
|
1170
|
+
action,
|
|
1171
|
+
targetId,
|
|
1172
|
+
deps,
|
|
1173
|
+
));
|
|
1174
|
+
} else {
|
|
1175
|
+
const portfolio = stateForCommand.config.portfolios.find((entry) => entry.id === targetId);
|
|
1176
|
+
if (!portfolio || !isManualPortfolio(portfolio)) {
|
|
1177
|
+
pluginRegistry.showToastFn("Choose a manual portfolio.", { type: "error" });
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const result = action === "add"
|
|
1182
|
+
? addTickerToPortfolio(resolvedTicker.ticker, targetId)
|
|
1183
|
+
: removeTickerFromPortfolio(resolvedTicker.ticker, targetId);
|
|
1184
|
+
|
|
1185
|
+
changed = result.changed;
|
|
1186
|
+
if (result.changed) {
|
|
1187
|
+
await deps.tickerRepository.saveTicker(result.ticker);
|
|
1188
|
+
deps.dispatch({ type: "UPDATE_TICKER", ticker: result.ticker });
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
966
1191
|
const targetName = (kind === "watchlist"
|
|
967
1192
|
? stateForCommand.config.watchlists.find((entry) => entry.id === targetId)?.name
|
|
968
1193
|
: stateForCommand.config.portfolios.find((entry) => entry.id === targetId)?.name) || targetId;
|
|
@@ -985,6 +1210,7 @@ export function CommandBar({
|
|
|
985
1210
|
activeTickerSymbol,
|
|
986
1211
|
buildSharedWorkflowDeps,
|
|
987
1212
|
closeAll,
|
|
1213
|
+
openAddToPortfolioWorkflow,
|
|
988
1214
|
openModeRoute,
|
|
989
1215
|
openPickerRoute,
|
|
990
1216
|
pluginRegistry,
|
|
@@ -1234,6 +1460,18 @@ export function CommandBar({
|
|
|
1234
1460
|
await connectBrokerProfile(brokerId, values);
|
|
1235
1461
|
break;
|
|
1236
1462
|
}
|
|
1463
|
+
case "add-portfolio": {
|
|
1464
|
+
const shares = coerceFieldString(route.values.shares).trim();
|
|
1465
|
+
if (!shares) {
|
|
1466
|
+
await addTickerMembershipFromWorkflow(route.values);
|
|
1467
|
+
} else {
|
|
1468
|
+
await setPortfolioPositionFromWorkflow(route.values);
|
|
1469
|
+
}
|
|
1470
|
+
break;
|
|
1471
|
+
}
|
|
1472
|
+
case "set-portfolio-position":
|
|
1473
|
+
await setPortfolioPositionFromWorkflow(route.values);
|
|
1474
|
+
break;
|
|
1237
1475
|
default:
|
|
1238
1476
|
break;
|
|
1239
1477
|
}
|
|
@@ -1329,6 +1567,8 @@ export function CommandBar({
|
|
|
1329
1567
|
extractBrokerWorkflowValues,
|
|
1330
1568
|
getWorkflowFieldStringValue,
|
|
1331
1569
|
pluginRegistry,
|
|
1570
|
+
addTickerMembershipFromWorkflow,
|
|
1571
|
+
setPortfolioPositionFromWorkflow,
|
|
1332
1572
|
state.config.activeLayoutIndex,
|
|
1333
1573
|
syncActiveWorkflowTextarea,
|
|
1334
1574
|
updateTopRoute,
|
|
@@ -1375,6 +1615,7 @@ export function CommandBar({
|
|
|
1375
1615
|
}, []);
|
|
1376
1616
|
|
|
1377
1617
|
const openPaneTemplateWorkflow = useCallback((template: PaneTemplateDef, options?: { arg?: string }) => {
|
|
1618
|
+
const displayLabel = getPaneTemplateDisplayLabel(template);
|
|
1378
1619
|
const normalized = template.wizard && template.wizard.length > 0
|
|
1379
1620
|
? normalizeWizardFields(template.wizard)
|
|
1380
1621
|
: { fields: [] as CommandBarWorkflowField[], description: [] as string[], initialValues: {} as Record<string, CommandBarFieldValue> };
|
|
@@ -1395,7 +1636,7 @@ export function CommandBar({
|
|
|
1395
1636
|
openWorkflowRoute({
|
|
1396
1637
|
kind: "workflow",
|
|
1397
1638
|
workflowId: `pane-template:${template.id}`,
|
|
1398
|
-
title:
|
|
1639
|
+
title: displayLabel,
|
|
1399
1640
|
subtitle: template.description,
|
|
1400
1641
|
description: normalized.description,
|
|
1401
1642
|
fields,
|
|
@@ -1403,7 +1644,7 @@ export function CommandBar({
|
|
|
1403
1644
|
activeFieldId: getFirstVisibleFieldId(fields, values),
|
|
1404
1645
|
submitLabel: "Create Pane",
|
|
1405
1646
|
cancelLabel: "Back",
|
|
1406
|
-
pendingLabel: normalized.pendingLabel ?? `Creating ${
|
|
1647
|
+
pendingLabel: normalized.pendingLabel ?? `Creating ${displayLabel.toLowerCase()}…`,
|
|
1407
1648
|
successLabel: normalized.successLabel,
|
|
1408
1649
|
pending: false,
|
|
1409
1650
|
error: null,
|
|
@@ -1426,8 +1667,9 @@ export function CommandBar({
|
|
|
1426
1667
|
await pluginRegistry.createPaneFromTemplateAsyncFn(template.id, createOptions);
|
|
1427
1668
|
closeAll({ revertThemePreview: false });
|
|
1428
1669
|
} catch (error) {
|
|
1670
|
+
const displayLabel = getPaneTemplateDisplayLabel(template);
|
|
1429
1671
|
pluginRegistry.showToastFn(
|
|
1430
|
-
error instanceof Error ? error.message : `Could not create ${
|
|
1672
|
+
error instanceof Error ? error.message : `Could not create ${displayLabel.toLowerCase()}.`,
|
|
1431
1673
|
{ type: "error" },
|
|
1432
1674
|
);
|
|
1433
1675
|
}
|
|
@@ -1701,13 +1943,12 @@ export function CommandBar({
|
|
|
1701
1943
|
): ResultItem => {
|
|
1702
1944
|
const pluginId = pluginRegistry.getPaneTemplatePluginId(template.id);
|
|
1703
1945
|
const pluginName = pluginId ? pluginRegistry.allPlugins.get(pluginId)?.name : null;
|
|
1704
|
-
const
|
|
1946
|
+
const displayLabel = getPaneTemplateDisplayLabel(template);
|
|
1705
1947
|
const shortcutLabel = template.shortcut
|
|
1706
1948
|
? [template.shortcut.prefix, template.shortcut.argPlaceholder && `<${template.shortcut.argPlaceholder}>`]
|
|
1707
1949
|
.filter(Boolean)
|
|
1708
1950
|
.join(" ")
|
|
1709
1951
|
: null;
|
|
1710
|
-
const placementLabel = paneDef?.defaultMode === "floating" ? "float" : "dock";
|
|
1711
1952
|
const arg = options?.createOptions?.arg;
|
|
1712
1953
|
|
|
1713
1954
|
const action = () => {
|
|
@@ -1724,12 +1965,12 @@ export function CommandBar({
|
|
|
1724
1965
|
|
|
1725
1966
|
return {
|
|
1726
1967
|
id: `pane-template:${template.id}:${arg || ""}`,
|
|
1727
|
-
label:
|
|
1968
|
+
label: displayLabel,
|
|
1728
1969
|
detail: shortcutLabel ? `${template.description} · ${shortcutLabel}` : template.description,
|
|
1729
1970
|
category: options?.category ?? (pluginName ? `${pluginName} Panes` : "New Panes"),
|
|
1730
1971
|
kind: "action",
|
|
1731
|
-
right: options?.showShortcut ?
|
|
1732
|
-
searchText: `${template.paneId} ${template.keywords?.join(" ") || ""} ${shortcutLabel || ""} ${pluginName || ""}`,
|
|
1972
|
+
right: options?.showShortcut ? template.shortcut?.prefix : undefined,
|
|
1973
|
+
searchText: `${displayLabel} ${template.label} ${template.paneId} ${template.keywords?.join(" ") || ""} ${shortcutLabel || ""} ${pluginName || ""}`,
|
|
1733
1974
|
action,
|
|
1734
1975
|
};
|
|
1735
1976
|
}, [openPaneTemplateDirect, openPaneTemplateWorkflow, pluginRegistry, runPaneTemplateShortcut, shouldOpenTemplateConfig]);
|
|
@@ -1863,11 +2104,17 @@ export function CommandBar({
|
|
|
1863
2104
|
case "add-broker-account":
|
|
1864
2105
|
case "new-portfolio":
|
|
1865
2106
|
case "new-watchlist":
|
|
2107
|
+
case "set-portfolio-position":
|
|
1866
2108
|
case "disconnect-broker-account":
|
|
1867
2109
|
case "delete-watchlist":
|
|
1868
2110
|
case "delete-portfolio":
|
|
1869
2111
|
case "reset-all-data":
|
|
1870
|
-
if (
|
|
2112
|
+
if (
|
|
2113
|
+
command.id === "add-broker-account"
|
|
2114
|
+
|| command.id === "new-portfolio"
|
|
2115
|
+
|| command.id === "new-watchlist"
|
|
2116
|
+
|| command.id === "set-portfolio-position"
|
|
2117
|
+
) {
|
|
1871
2118
|
openBuiltInWorkflow(command.id);
|
|
1872
2119
|
return;
|
|
1873
2120
|
}
|
|
@@ -1914,7 +2161,7 @@ export function CommandBar({
|
|
|
1914
2161
|
return;
|
|
1915
2162
|
}
|
|
1916
2163
|
if (command.id === "delete-portfolio") {
|
|
1917
|
-
const deletable = state.config.portfolios.filter(
|
|
2164
|
+
const deletable = state.config.portfolios.filter(isManualPortfolio);
|
|
1918
2165
|
const options = deletable.map((portfolio) => ({
|
|
1919
2166
|
id: portfolio.id,
|
|
1920
2167
|
label: portfolio.name,
|
|
@@ -2160,6 +2407,7 @@ export function CommandBar({
|
|
|
2160
2407
|
|
|
2161
2408
|
const isWatchlistTab = state.config.watchlists.some((entry) => entry.id === activeCollectionId);
|
|
2162
2409
|
const isPortfolioTab = state.config.portfolios.some((entry) => entry.id === activeCollectionId);
|
|
2410
|
+
const manualPortfolios = state.config.portfolios.filter(isManualPortfolio);
|
|
2163
2411
|
const tickerData = activeTickerData;
|
|
2164
2412
|
const focusedPaneHasSettings = !!state.focusedPaneId && pluginRegistry.hasPaneSettings(state.focusedPaneId);
|
|
2165
2413
|
|
|
@@ -2167,8 +2415,8 @@ export function CommandBar({
|
|
|
2167
2415
|
? activeCollectionId
|
|
2168
2416
|
: state.config.watchlists[0]?.id ?? null;
|
|
2169
2417
|
const targetPortfolioId = isPortfolioTab
|
|
2170
|
-
? activeCollectionId
|
|
2171
|
-
:
|
|
2418
|
+
? manualPortfolios.find((entry) => entry.id === activeCollectionId)?.id ?? null
|
|
2419
|
+
: manualPortfolios[0]?.id ?? null;
|
|
2172
2420
|
|
|
2173
2421
|
function shouldShow(command: Command): boolean {
|
|
2174
2422
|
switch (command.id) {
|
|
@@ -2177,16 +2425,18 @@ export function CommandBar({
|
|
|
2177
2425
|
case "remove-watchlist":
|
|
2178
2426
|
return !!tickerData && tickerData.metadata.watchlists.length > 0;
|
|
2179
2427
|
case "add-portfolio":
|
|
2180
|
-
return !!tickerData &&
|
|
2428
|
+
return !!tickerData && manualPortfolios.length > 0;
|
|
2181
2429
|
case "remove-portfolio":
|
|
2182
2430
|
return !!tickerData && tickerData.metadata.portfolios.some((id) =>
|
|
2183
|
-
state.config.portfolios.some((entry) => entry.id === id &&
|
|
2431
|
+
state.config.portfolios.some((entry) => entry.id === id && isManualPortfolio(entry)));
|
|
2432
|
+
case "set-portfolio-position":
|
|
2433
|
+
return manualPortfolios.length > 0;
|
|
2184
2434
|
case "disconnect-broker-account":
|
|
2185
2435
|
return state.config.brokerInstances.length > 0;
|
|
2186
2436
|
case "delete-watchlist":
|
|
2187
2437
|
return state.config.watchlists.length > 0;
|
|
2188
2438
|
case "delete-portfolio":
|
|
2189
|
-
return
|
|
2439
|
+
return manualPortfolios.length > 0;
|
|
2190
2440
|
case "pane-settings":
|
|
2191
2441
|
return focusedPaneHasSettings;
|
|
2192
2442
|
default:
|
|
@@ -2204,6 +2454,8 @@ export function CommandBar({
|
|
|
2204
2454
|
return activeTickerSymbol ? `Add ${activeTickerSymbol} to Portfolio` : command.label;
|
|
2205
2455
|
case "remove-portfolio":
|
|
2206
2456
|
return activeTickerSymbol ? `Remove ${activeTickerSymbol} from Portfolio` : command.label;
|
|
2457
|
+
case "set-portfolio-position":
|
|
2458
|
+
return activeTickerSymbol ? `Set Position for ${activeTickerSymbol}` : command.label;
|
|
2207
2459
|
default:
|
|
2208
2460
|
return command.label;
|
|
2209
2461
|
}
|
|
@@ -2227,11 +2479,19 @@ export function CommandBar({
|
|
|
2227
2479
|
}
|
|
2228
2480
|
case "remove-portfolio": {
|
|
2229
2481
|
const names = tickerData?.metadata.portfolios
|
|
2230
|
-
.map((id) => state.config.portfolios.find((entry) => entry.id === id &&
|
|
2482
|
+
.map((id) => state.config.portfolios.find((entry) => entry.id === id && isManualPortfolio(entry))?.name)
|
|
2231
2483
|
.filter(Boolean);
|
|
2232
2484
|
return names?.length ? `from "${names.join(", ")}"` : command.description;
|
|
2233
2485
|
}
|
|
2486
|
+
case "set-portfolio-position": {
|
|
2487
|
+
const name = state.config.portfolios.find((entry) => entry.id === targetPortfolioId)?.name;
|
|
2488
|
+
return name ? `in "${name}"` : command.description;
|
|
2489
|
+
}
|
|
2234
2490
|
case "check-for-updates":
|
|
2491
|
+
if (state.updateProgress?.phase === "downloading") return `Downloading v${state.updateAvailable?.version}: ${state.updateProgress.percent ?? 0}%`;
|
|
2492
|
+
if (state.updateProgress?.phase === "replacing") return "Installing update";
|
|
2493
|
+
if (state.updateProgress?.phase === "done") return "Update installed - restart to apply";
|
|
2494
|
+
if (state.updateProgress?.phase === "error") return `Update failed: ${state.updateProgress.error}`;
|
|
2235
2495
|
if (state.updateCheckInProgress) return "Checking GitHub releases now";
|
|
2236
2496
|
if (state.updateAvailable) return `Latest available: v${state.updateAvailable.version}`;
|
|
2237
2497
|
if (state.updateNotice) return state.updateNotice;
|
|
@@ -2241,6 +2501,15 @@ export function CommandBar({
|
|
|
2241
2501
|
}
|
|
2242
2502
|
}
|
|
2243
2503
|
|
|
2504
|
+
function smartSearchText(command: Command): string {
|
|
2505
|
+
switch (command.id) {
|
|
2506
|
+
case "set-portfolio-position":
|
|
2507
|
+
return "edit position update position modify position manual position portfolio position";
|
|
2508
|
+
default:
|
|
2509
|
+
return "";
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2244
2513
|
function commandToItem(command: Command): ResultItem | null {
|
|
2245
2514
|
if (!shouldShow(command)) return null;
|
|
2246
2515
|
return {
|
|
@@ -2250,6 +2519,7 @@ export function CommandBar({
|
|
|
2250
2519
|
category: command.category,
|
|
2251
2520
|
kind: "command",
|
|
2252
2521
|
right: command.prefix || undefined,
|
|
2522
|
+
searchText: smartSearchText(command),
|
|
2253
2523
|
disabled: command.id === "check-for-updates" && (state.updateCheckInProgress || !!state.updateProgress),
|
|
2254
2524
|
action: () => runDirectCommand(command, ""),
|
|
2255
2525
|
};
|
|
@@ -2291,13 +2561,19 @@ export function CommandBar({
|
|
|
2291
2561
|
const displayTicker = normalizeTickerInput(activeTickerSymbol, rootShortcutIntent.argText);
|
|
2292
2562
|
const displayName = kind === "watchlist" ? "Watchlist" : "Portfolio";
|
|
2293
2563
|
const localTicker = displayTicker ? state.tickers.get(displayTicker) ?? null : null;
|
|
2294
|
-
const preferredTargetId =
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2564
|
+
const preferredTargetId = commandId === "add-portfolio"
|
|
2565
|
+
? (activeCollectionId && manualPortfolios.some((portfolio) => portfolio.id === activeCollectionId)
|
|
2566
|
+
? activeCollectionId
|
|
2567
|
+
: manualPortfolios.length === 1
|
|
2568
|
+
? manualPortfolios[0]!.id
|
|
2569
|
+
: null)
|
|
2570
|
+
: resolvePreferredCollectionTarget(
|
|
2571
|
+
state,
|
|
2572
|
+
kind,
|
|
2573
|
+
activeCollectionId,
|
|
2574
|
+
action,
|
|
2575
|
+
localTicker,
|
|
2576
|
+
);
|
|
2301
2577
|
const preferredTargetName = preferredTargetId
|
|
2302
2578
|
? (kind === "watchlist"
|
|
2303
2579
|
? state.config.watchlists.find((entry) => entry.id === preferredTargetId)?.name
|
|
@@ -2872,22 +3148,22 @@ export function CommandBar({
|
|
|
2872
3148
|
const symbol = normalizeTickerInput(activeTickerSymbol, rootShortcutIntent.argText);
|
|
2873
3149
|
if (symbol) {
|
|
2874
3150
|
return rootShortcutIntent.kind === "inferred-complete"
|
|
2875
|
-
? `Shortcut: ${rootShortcutIntent.
|
|
2876
|
-
: `Shortcut: ${rootShortcutIntent.
|
|
3151
|
+
? `Shortcut: ${rootShortcutIntent.label} for ${symbol} · Tab to accept`
|
|
3152
|
+
: `Shortcut: ${rootShortcutIntent.label} for ${symbol}`;
|
|
2877
3153
|
}
|
|
2878
|
-
return `Shortcut: ${rootShortcutIntent.
|
|
3154
|
+
return `Shortcut: ${rootShortcutIntent.label} · Enter to choose ticker`;
|
|
2879
3155
|
}
|
|
2880
3156
|
if (argKind === "ticker-list") {
|
|
2881
3157
|
if (rootShortcutIntent.argText) {
|
|
2882
|
-
return `Shortcut: ${rootShortcutIntent.
|
|
3158
|
+
return `Shortcut: ${rootShortcutIntent.label} · ${rootShortcutIntent.argText}`;
|
|
2883
3159
|
}
|
|
2884
3160
|
const inferred = normalizeTickerInput(activeTickerSymbol, undefined);
|
|
2885
3161
|
if (inferred) {
|
|
2886
|
-
return `Shortcut: ${rootShortcutIntent.
|
|
3162
|
+
return `Shortcut: ${rootShortcutIntent.label} · inferred ${inferred} · Tab to accept`;
|
|
2887
3163
|
}
|
|
2888
|
-
return `Shortcut: ${rootShortcutIntent.
|
|
3164
|
+
return `Shortcut: ${rootShortcutIntent.label} · Enter tickers to compare`;
|
|
2889
3165
|
}
|
|
2890
|
-
return `Shortcut: ${rootShortcutIntent.
|
|
3166
|
+
return `Shortcut: ${rootShortcutIntent.label}`;
|
|
2891
3167
|
}
|
|
2892
3168
|
|
|
2893
3169
|
if (rootShortcutIntent.command.id === "search-ticker") {
|
|
@@ -3617,6 +3893,7 @@ export function CommandBar({
|
|
|
3617
3893
|
visibleListStateRef.current = routeListState;
|
|
3618
3894
|
|
|
3619
3895
|
useEffect(() => {
|
|
3896
|
+
if (!state.commandBarOpen) return;
|
|
3620
3897
|
if (!routeListState) return;
|
|
3621
3898
|
const selected = currentRoute?.kind === "mode" && currentRoute.screen === "themes"
|
|
3622
3899
|
? routeListState.results[currentRoute.selectedIdx]
|
|
@@ -3628,7 +3905,7 @@ export function CommandBar({
|
|
|
3628
3905
|
applyTheme(selected.themeId);
|
|
3629
3906
|
dispatch({ type: "SET_THEME", theme: selected.themeId });
|
|
3630
3907
|
}
|
|
3631
|
-
}, [currentRoute, dispatch, rootModeInfo.kind, rootSelectedIdx, routeListState, state.config.theme]);
|
|
3908
|
+
}, [currentRoute, dispatch, rootModeInfo.kind, rootSelectedIdx, routeListState, state.commandBarOpen, state.config.theme]);
|
|
3632
3909
|
|
|
3633
3910
|
useEffect(() => {
|
|
3634
3911
|
if (currentRoute?.kind !== "workflow") return;
|
|
@@ -4080,6 +4357,7 @@ export function CommandBar({
|
|
|
4080
4357
|
name: string;
|
|
4081
4358
|
sequence?: string;
|
|
4082
4359
|
ctrl?: boolean;
|
|
4360
|
+
option?: boolean;
|
|
4083
4361
|
meta?: boolean;
|
|
4084
4362
|
shift?: boolean;
|
|
4085
4363
|
stopPropagation: () => void;
|
|
@@ -4089,6 +4367,7 @@ export function CommandBar({
|
|
|
4089
4367
|
name: string;
|
|
4090
4368
|
sequence?: string;
|
|
4091
4369
|
ctrl?: boolean;
|
|
4370
|
+
option?: boolean;
|
|
4092
4371
|
meta?: boolean;
|
|
4093
4372
|
shift?: boolean;
|
|
4094
4373
|
stopPropagation: () => void;
|
|
@@ -4099,6 +4378,7 @@ export function CommandBar({
|
|
|
4099
4378
|
name: string;
|
|
4100
4379
|
sequence?: string;
|
|
4101
4380
|
ctrl?: boolean;
|
|
4381
|
+
option?: boolean;
|
|
4102
4382
|
meta?: boolean;
|
|
4103
4383
|
shift?: boolean;
|
|
4104
4384
|
stopPropagation: () => void;
|
|
@@ -4112,6 +4392,12 @@ export function CommandBar({
|
|
|
4112
4392
|
}
|
|
4113
4393
|
|
|
4114
4394
|
if (currentRoute?.kind === "confirm") {
|
|
4395
|
+
if (isPlainBackspace(event)) {
|
|
4396
|
+
event.stopPropagation();
|
|
4397
|
+
event.preventDefault();
|
|
4398
|
+
popRoute();
|
|
4399
|
+
return;
|
|
4400
|
+
}
|
|
4115
4401
|
if (event.name === "return" || event.name === "enter" || event.name === "y") {
|
|
4116
4402
|
event.stopPropagation();
|
|
4117
4403
|
event.preventDefault();
|
|
@@ -4126,6 +4412,20 @@ export function CommandBar({
|
|
|
4126
4412
|
return;
|
|
4127
4413
|
}
|
|
4128
4414
|
|
|
4415
|
+
if (
|
|
4416
|
+
currentRoute
|
|
4417
|
+
&& (currentRoute.kind === "mode"
|
|
4418
|
+
|| currentRoute.kind === "picker"
|
|
4419
|
+
|| currentRoute.kind === "pane-settings")
|
|
4420
|
+
&& isPlainBackspace(event)
|
|
4421
|
+
&& currentRoute.query.length === 0
|
|
4422
|
+
) {
|
|
4423
|
+
event.stopPropagation();
|
|
4424
|
+
event.preventDefault();
|
|
4425
|
+
popRoute();
|
|
4426
|
+
return;
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4129
4429
|
if (currentRoute?.kind === "workflow") {
|
|
4130
4430
|
const visibleFields = getVisibleWorkflowFields(currentRoute.fields, currentRoute.values);
|
|
4131
4431
|
const activeField = visibleFields.find((field) => field.id === currentRoute.activeFieldId) ?? visibleFields[0];
|