gloomberb 0.6.0 → 0.7.0
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/package.json +2 -1
- package/patches/@opentui%2Fcore@0.1.90.patch +48 -24
- package/src/app.tsx +9 -6
- package/src/cli/registry.test.ts +0 -1
- package/src/components/chart/chart-controller.ts +0 -1
- package/src/components/chart/chart-data.ts +1 -31
- package/src/components/chart/chart-renderer.ts +70 -0
- package/src/components/chart/chart-resolution.test.ts +0 -36
- package/src/components/chart/chart-resolution.ts +0 -27
- package/src/components/chart/chart.test.ts +17 -45
- package/src/components/chart/comparison-chart-data.test.ts +1 -5
- package/src/components/chart/comparison-chart-data.ts +1 -4
- package/src/components/chart/comparison-stock-chart.tsx +62 -77
- package/src/components/chart/native/kitty.test.ts +23 -0
- package/src/components/chart/native/surface-visibility.ts +48 -0
- package/src/components/chart/static-chart-surface.tsx +28 -4
- package/src/components/chart/stock-chart.test.ts +0 -16
- package/src/components/chart/stock-chart.tsx +52 -71
- package/src/components/chart/time-axis-label.tsx +43 -0
- package/src/components/command-bar/command-bar.test.tsx +71 -8
- package/src/components/command-bar/command-bar.tsx +388 -713
- package/src/components/command-bar/confirm-body.tsx +51 -0
- package/src/components/command-bar/list-model.ts +77 -0
- package/src/components/command-bar/view-model.ts +0 -19
- package/src/components/command-bar/workflow-ops.ts +4 -0
- package/src/components/data-table-stack-view.test.tsx +39 -4
- package/src/components/data-table-stack-view.tsx +11 -104
- package/src/components/data-table-view.tsx +43 -79
- package/src/components/feed-data-table-stack-view.tsx +10 -17
- package/src/components/feed-stack-controller.ts +39 -0
- package/src/components/index.ts +6 -2
- package/src/components/layout/floating-pane.tsx +30 -2
- package/src/components/layout/header.tsx +10 -7
- package/src/components/layout/pane-content.tsx +2 -0
- package/src/components/layout/pane-cycle-order.test.ts +30 -0
- package/src/components/layout/pane-cycle-order.ts +32 -0
- package/src/components/layout/pane-footer.tsx +26 -4
- package/src/components/layout/pane-header.tsx +5 -5
- package/src/components/layout/pane-keyboard-scroll.test.tsx +226 -0
- package/src/components/layout/shell.test.tsx +66 -22
- package/src/components/layout/shell.tsx +13 -6
- package/src/components/onboarding/onboarding-wizard.tsx +1 -2
- package/src/components/pane-settings-dialog.tsx +12 -4
- package/src/components/pane-template-wizard.test.tsx +3 -4
- package/src/components/speedometer-gauge.test.tsx +45 -0
- package/src/components/speedometer-gauge.tsx +558 -0
- package/src/components/table-view-shared.tsx +158 -0
- package/src/components/ticker-list-table-view.tsx +42 -72
- package/src/components/ui/confirm-dialog.tsx +66 -0
- package/src/components/ui/data-table.tsx +18 -10
- package/src/components/ui/index.ts +0 -1
- package/src/components/ui/page-stack-view.tsx +39 -10
- package/src/components/ui/remote-image.tsx +15 -40
- package/src/components/ui/table-layout.test.ts +16 -0
- package/src/components/ui/table-layout.ts +10 -3
- package/src/components/ui/ui.test.tsx +100 -20
- package/src/core/state/app-state.ts +6 -3
- package/src/market-data/coordinator.test.ts +1 -1
- package/src/market-data/coordinator.ts +0 -1
- package/src/news/aggregator.ts +0 -3
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +46 -16
- package/src/plugins/builtin/ai/providers.ts +0 -17
- package/src/plugins/builtin/ai/screener-pane.test.tsx +3 -4
- package/src/plugins/builtin/ai/screener-pane.tsx +0 -3
- package/src/plugins/builtin/ask-ai.test.tsx +89 -2
- package/src/plugins/builtin/broker-manager/index.test.tsx +2 -2
- package/src/plugins/builtin/broker-manager/index.tsx +19 -36
- package/src/plugins/builtin/chat-controller.test.ts +105 -0
- package/src/plugins/builtin/chat-controller.ts +75 -6
- package/src/plugins/builtin/chat.test.tsx +6 -6
- package/src/plugins/builtin/chat.tsx +29 -20
- package/src/plugins/builtin/cloud-tweets.tsx +0 -1
- package/src/plugins/builtin/comparison-chart.test.tsx +53 -2
- package/src/plugins/builtin/comparison-chart.tsx +11 -5
- package/src/plugins/builtin/correlation/index.tsx +1 -6
- package/src/plugins/builtin/debug.tsx +1 -3
- package/src/plugins/builtin/econ/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/fear-greed-data.test.ts +72 -0
- package/src/plugins/builtin/fear-greed/fear-greed-data.ts +366 -0
- package/src/plugins/builtin/fear-greed/index.tsx +478 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/help.tsx +2 -2
- package/src/plugins/builtin/holders/index.tsx +10 -15
- package/src/plugins/builtin/insider/index.tsx +22 -89
- package/src/plugins/builtin/market-movers/index.tsx +1 -1
- package/src/plugins/builtin/news-wire/index.tsx +41 -2
- package/src/plugins/builtin/news-wire/news-table.tsx +11 -11
- package/src/plugins/builtin/news.tsx +4 -16
- package/src/plugins/builtin/notes.tsx +18 -55
- package/src/plugins/builtin/options.test.tsx +58 -2
- package/src/plugins/builtin/options.tsx +108 -49
- package/src/plugins/builtin/plugin-groups.ts +2 -0
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +1 -1
- package/src/plugins/builtin/research/index.tsx +5 -21
- package/src/plugins/builtin/sec-filing-display.tsx +84 -0
- package/src/plugins/builtin/sec.tsx +13 -66
- package/src/plugins/builtin/sectors/index.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +36 -28
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +69 -4
- package/src/plugins/builtin/ticker-detail.test.tsx +102 -0
- package/src/plugins/builtin/world-indices/index.tsx +1 -1
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/ibkr/broker-adapter.ts +1 -3
- package/src/plugins/ibkr/gateway-service-native.ts +1 -5
- package/src/plugins/ibkr/gateway-service.ts +1 -1
- package/src/plugins/ibkr/index.test.tsx +0 -2
- package/src/plugins/ibkr/trade-tab.test.tsx +56 -5
- package/src/plugins/ibkr/trade-tab.tsx +13 -11
- package/src/plugins/pane-manager.ts +0 -13
- package/src/plugins/plugin-runtime.test.tsx +6 -6
- package/src/plugins/plugin-runtime.tsx +13 -8
- package/src/plugins/prediction-markets/cli.ts +0 -1
- package/src/plugins/prediction-markets/controller-data.ts +0 -2
- package/src/plugins/prediction-markets/pane.test.tsx +3 -11
- package/src/plugins/prediction-markets/test-helpers.tsx +1 -1
- package/src/plugins/registry.ts +10 -39
- package/src/react/input.ts +11 -2
- package/src/renderers/electrobun/view/data-table.tsx +28 -6
- package/src/renderers/electrobun/view/desktop-controls.tsx +34 -20
- package/src/renderers/electrobun/view/dialog-host.tsx +11 -1
- package/src/renderers/electrobun/view/input-host.tsx +123 -12
- package/src/renderers/electrobun/view/key-event.ts +1 -1
- package/src/renderers/electrobun/view/styles.css +331 -0
- package/src/renderers/electrobun/view/ui-host.tsx +22 -3
- package/src/renderers/opentui/chart-surface.tsx +179 -0
- package/src/renderers/opentui/host.tsx +6 -0
- package/src/renderers/opentui/image-loader.test.ts +25 -0
- package/src/renderers/opentui/image-loader.ts +105 -0
- package/src/renderers/opentui/image-surface.tsx +266 -0
- package/src/renderers/opentui/input-host.tsx +45 -6
- package/src/renderers/opentui/start.tsx +48 -30
- package/src/renderers/opentui/test-utils.tsx +17 -0
- package/src/renderers/opentui/ui-host.tsx +7 -3
- package/src/sources/provider-router.ts +0 -4
- package/src/state/app-context.test.ts +45 -0
- package/src/state/app-context.tsx +4 -0
- package/src/state/pane-scroll-registry.tsx +254 -0
- package/src/state/use-inline-tickers.test.tsx +73 -0
- package/src/state/use-inline-tickers.ts +7 -1
- package/src/test-support/plugin-runtime.ts +3 -2
- package/src/theme/colors.ts +12 -3
- package/src/theme/themes.test.ts +23 -1
- package/src/theme/themes.ts +115 -0
- package/src/types/broker.ts +1 -1
- package/src/types/data-provider.ts +0 -1
- package/src/types/plugin.ts +0 -2
- package/src/ui/host.tsx +4 -0
- package/src/ui/index.tsx +9 -1
- package/src/utils/back-navigation.ts +24 -1
- package/src/version.ts +1 -1
- package/src/plugins/builtin/news-wire/feed-pane.tsx +0 -21
- package/src/plugins/builtin/news-wire/top-pane.tsx +0 -21
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
getKeyboardPanCellCount,
|
|
28
28
|
resolveDragPanOffset,
|
|
29
29
|
} from "./chart-scroll";
|
|
30
|
-
import {
|
|
30
|
+
import { projectChartData, resolveBarSize, resolveStableOhlcProjectionOptions, type ProjectedChartPoint } from "./chart-data";
|
|
31
31
|
import {
|
|
32
32
|
buildPresetDateWindow,
|
|
33
33
|
buildVisibleDateWindow,
|
|
@@ -110,7 +110,6 @@ import {
|
|
|
110
110
|
} from "./chart-types";
|
|
111
111
|
import {
|
|
112
112
|
computeBitmapSize,
|
|
113
|
-
intersectCellRects,
|
|
114
113
|
renderNativeChartBase,
|
|
115
114
|
renderNativeCrosshairOverlay,
|
|
116
115
|
type CellRect,
|
|
@@ -121,8 +120,14 @@ import { ensureKittySupport, getCachedKittySupport } from "./native/kitty-suppor
|
|
|
121
120
|
import { resolveChartRendererState } from "./native/renderer-selection";
|
|
122
121
|
import { getNativeSurfaceManager } from "./native/surface-manager";
|
|
123
122
|
import { syncCachedNativeSurface } from "./native/surface-sync";
|
|
123
|
+
import {
|
|
124
|
+
getRenderableCellRect,
|
|
125
|
+
resolveNativeSurfaceVisibleRect,
|
|
126
|
+
type NativeSurfaceRenderableNode,
|
|
127
|
+
} from "./native/surface-visibility";
|
|
124
128
|
import type { PricePoint, TickerFinancials } from "../../types/financials";
|
|
125
129
|
import type { TickerRecord } from "../../types/ticker";
|
|
130
|
+
import { TimeAxisLabel } from "./time-axis-label";
|
|
126
131
|
|
|
127
132
|
const MODE_CHIPS: Record<ChartRenderMode, string> = {
|
|
128
133
|
area: "A",
|
|
@@ -169,7 +174,6 @@ interface StockChartProps {
|
|
|
169
174
|
}
|
|
170
175
|
|
|
171
176
|
interface ResolvedStockChartProps extends StockChartProps {
|
|
172
|
-
symbol: string | null;
|
|
173
177
|
ticker: TickerRecord | null;
|
|
174
178
|
financials: TickerFinancials | null;
|
|
175
179
|
}
|
|
@@ -275,14 +279,6 @@ interface DisplayCursorState {
|
|
|
275
279
|
pixelY: number | null;
|
|
276
280
|
}
|
|
277
281
|
|
|
278
|
-
interface RenderableNode {
|
|
279
|
-
x: number;
|
|
280
|
-
y: number;
|
|
281
|
-
width: number;
|
|
282
|
-
height: number;
|
|
283
|
-
parent: RenderableNode | null;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
282
|
const EMPTY_DISPLAY_CURSOR: DisplayCursorState = {
|
|
287
283
|
cellX: null,
|
|
288
284
|
cellY: null,
|
|
@@ -522,41 +518,6 @@ function resolveCursorMotionKind(
|
|
|
522
518
|
: "cell";
|
|
523
519
|
}
|
|
524
520
|
|
|
525
|
-
function extractCellRect(renderable: RenderableNode): CellRect {
|
|
526
|
-
return {
|
|
527
|
-
x: renderable.x,
|
|
528
|
-
y: renderable.y,
|
|
529
|
-
width: renderable.width,
|
|
530
|
-
height: renderable.height,
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function resolveVisibleRect(
|
|
535
|
-
renderable: RenderableNode | null,
|
|
536
|
-
terminalWidth: number,
|
|
537
|
-
terminalHeight: number,
|
|
538
|
-
): CellRect | null {
|
|
539
|
-
if (!renderable) return null;
|
|
540
|
-
|
|
541
|
-
let visible: CellRect = {
|
|
542
|
-
x: 0,
|
|
543
|
-
y: 0,
|
|
544
|
-
width: terminalWidth,
|
|
545
|
-
height: terminalHeight,
|
|
546
|
-
};
|
|
547
|
-
let current: RenderableNode | null = renderable;
|
|
548
|
-
|
|
549
|
-
while (current) {
|
|
550
|
-
const currentRect = extractCellRect(current);
|
|
551
|
-
const nextVisible = intersectCellRects(visible, currentRect);
|
|
552
|
-
if (!nextVisible) return null;
|
|
553
|
-
visible = nextVisible;
|
|
554
|
-
current = current.parent;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
return visible;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
521
|
function buildBlankPlotLines(width: number, height: number): string[] {
|
|
561
522
|
return Array.from({ length: height }, () => " ".repeat(width));
|
|
562
523
|
}
|
|
@@ -765,14 +726,6 @@ function isSeriesAcceptedForRequest(
|
|
|
765
726
|
return windowPoints.length >= minimumPointCount;
|
|
766
727
|
}
|
|
767
728
|
|
|
768
|
-
function canRenderAutoViewForWindow(
|
|
769
|
-
render: AutoRenderedView | null,
|
|
770
|
-
requestedWindow: DateWindowRange | null | undefined,
|
|
771
|
-
): render is AutoRenderedView {
|
|
772
|
-
if (!render || !requestedWindow?.start || !requestedWindow.end) return false;
|
|
773
|
-
return getWindowPoints(render.data, requestedWindow).length > 0;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
729
|
export function resolveAutoDisplayState(options: {
|
|
777
730
|
shouldUseRenderedAutoView: boolean;
|
|
778
731
|
renderedAutoView: AutoRenderedView | null;
|
|
@@ -811,13 +764,6 @@ export function resolveAutoDisplayState(options: {
|
|
|
811
764
|
};
|
|
812
765
|
}
|
|
813
766
|
|
|
814
|
-
export function resolveVisibleChartDateWindow(
|
|
815
|
-
points: readonly Pick<PricePoint, "date">[],
|
|
816
|
-
preferredWindow: DateWindowRange | null | undefined,
|
|
817
|
-
): DateWindowRange | null {
|
|
818
|
-
return getDateWindowBounds(getPointDates(points)) ?? preferredWindow ?? null;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
767
|
function buildDateWindowFromIndices(dates: readonly Date[], startIdx: number, endIdx: number): DateWindowRange | null {
|
|
822
768
|
if (dates.length === 0 || endIdx <= startIdx) return null;
|
|
823
769
|
return {
|
|
@@ -1469,8 +1415,8 @@ function buildIndicatorProjectionKey(options: {
|
|
|
1469
1415
|
}
|
|
1470
1416
|
|
|
1471
1417
|
export function StockChart(props: StockChartProps) {
|
|
1472
|
-
const {
|
|
1473
|
-
return <ResolvedStockChart {...props}
|
|
1418
|
+
const { ticker, financials } = usePaneTicker();
|
|
1419
|
+
return <ResolvedStockChart {...props} ticker={ticker} financials={financials} />;
|
|
1474
1420
|
}
|
|
1475
1421
|
|
|
1476
1422
|
export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
@@ -1487,7 +1433,6 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
1487
1433
|
showVolume: showVolumeOverride,
|
|
1488
1434
|
footerControls,
|
|
1489
1435
|
footerHints,
|
|
1490
|
-
symbol,
|
|
1491
1436
|
ticker,
|
|
1492
1437
|
financials,
|
|
1493
1438
|
}: ResolvedStockChartProps) {
|
|
@@ -1553,6 +1498,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
1553
1498
|
const lastCanvasBaseBitmapRef = useRef<{ key: string; bitmap: NativeChartBitmap } | null>(null);
|
|
1554
1499
|
const displayCursorRef = useRef<DisplayCursorState>(EMPTY_DISPLAY_CURSOR);
|
|
1555
1500
|
const targetCursorRef = useRef<DisplayCursorState>(EMPTY_DISPLAY_CURSOR);
|
|
1501
|
+
const mouseCrosshairDisabledRef = useRef(false);
|
|
1556
1502
|
const cursorMotionKindRef = useRef<ChartCursorMotionKind>("discrete");
|
|
1557
1503
|
const animationFrameRef = useRef<number | null>(null);
|
|
1558
1504
|
const pendingCanonicalResetRef = useRef(1);
|
|
@@ -2945,6 +2891,11 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
2945
2891
|
if (!interactive) return;
|
|
2946
2892
|
|
|
2947
2893
|
switch (key) {
|
|
2894
|
+
case "escape":
|
|
2895
|
+
mouseCrosshairDisabledRef.current = true;
|
|
2896
|
+
updateDisplayCursorTarget(EMPTY_DISPLAY_CURSOR, "discrete");
|
|
2897
|
+
commitSelectionCursor({ cursorX: null, cursorY: null });
|
|
2898
|
+
return;
|
|
2948
2899
|
case "left":
|
|
2949
2900
|
if (event.shift) {
|
|
2950
2901
|
if (effectiveResolution === "auto") {
|
|
@@ -2962,6 +2913,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
2962
2913
|
return { ...cleared, panOffset: current.panOffset + panStep };
|
|
2963
2914
|
});
|
|
2964
2915
|
} else {
|
|
2916
|
+
mouseCrosshairDisabledRef.current = false;
|
|
2965
2917
|
cursorMotionKindRef.current = "discrete";
|
|
2966
2918
|
const pointCount = projection.points.length;
|
|
2967
2919
|
const currentIndex = pointCount <= 0
|
|
@@ -3013,6 +2965,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3013
2965
|
return cleared.panOffset === nextPanOffset ? cleared : { ...cleared, panOffset: nextPanOffset };
|
|
3014
2966
|
});
|
|
3015
2967
|
} else {
|
|
2968
|
+
mouseCrosshairDisabledRef.current = false;
|
|
3016
2969
|
cursorMotionKindRef.current = "discrete";
|
|
3017
2970
|
const pointCount = projection.points.length;
|
|
3018
2971
|
const currentIndex = pointCount <= 0
|
|
@@ -3097,6 +3050,19 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3097
3050
|
colors: chartColors,
|
|
3098
3051
|
timeAxisDates,
|
|
3099
3052
|
}), [axisMode, chartColors, chartHeight, chartWidth, compact, projection.effectiveMode, projection.points, selectionCursorX, selectionCursorY, showVolume, timeAxisDates, volumeHeight]);
|
|
3053
|
+
const displayScene = useMemo(() => buildChartScene(projection.points, {
|
|
3054
|
+
width: chartWidth,
|
|
3055
|
+
height: chartHeight,
|
|
3056
|
+
showVolume: showVolume && !compact,
|
|
3057
|
+
volumeHeight,
|
|
3058
|
+
cursorX: displayCursorX,
|
|
3059
|
+
cursorY: displayCursorY,
|
|
3060
|
+
mode: projection.effectiveMode,
|
|
3061
|
+
axisMode,
|
|
3062
|
+
colors: chartColors,
|
|
3063
|
+
timeAxisDates,
|
|
3064
|
+
indicators,
|
|
3065
|
+
}), [axisMode, chartColors, chartHeight, chartWidth, compact, displayCursorX, displayCursorY, indicators, projection.effectiveMode, projection.points, showVolume, timeAxisDates, volumeHeight]);
|
|
3100
3066
|
const snappedSelectionCursorX = selectionScene
|
|
3101
3067
|
? getPointTerminalColumn(selectionScene.activeIdx, projection.points.length, chartWidth, projection.effectiveMode)
|
|
3102
3068
|
: null;
|
|
@@ -3236,8 +3202,9 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3236
3202
|
|
|
3237
3203
|
const syncPlacement = () => {
|
|
3238
3204
|
if (effectiveRenderer !== "kitty" || !rendererState.nativeReady || !plotRef.current) return;
|
|
3239
|
-
const
|
|
3240
|
-
const
|
|
3205
|
+
const plot = plotRef.current as NativeSurfaceRenderableNode;
|
|
3206
|
+
const rect = getRenderableCellRect(plot);
|
|
3207
|
+
const visibleRect = resolveNativeSurfaceVisibleRect(plot, renderer.terminalWidth, renderer.terminalHeight);
|
|
3241
3208
|
const previous = lastNativeGeometryRef.current;
|
|
3242
3209
|
if (previous
|
|
3243
3210
|
&& previous.rect.x === rect.x
|
|
@@ -3292,8 +3259,9 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3292
3259
|
return;
|
|
3293
3260
|
}
|
|
3294
3261
|
|
|
3295
|
-
const
|
|
3296
|
-
const
|
|
3262
|
+
const plot = plotRef.current as NativeSurfaceRenderableNode;
|
|
3263
|
+
const plotRect = getRenderableCellRect(plot);
|
|
3264
|
+
const visibleRect = resolveNativeSurfaceVisibleRect(plot, renderer.terminalWidth, renderer.terminalHeight);
|
|
3297
3265
|
const bitmapSize = computeBitmapSize(plotRect, renderer.resolution, renderer.terminalWidth, renderer.terminalHeight);
|
|
3298
3266
|
const bitmapKey = buildNativeBitmapKey(
|
|
3299
3267
|
projection.points.length,
|
|
@@ -3364,8 +3332,9 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3364
3332
|
return;
|
|
3365
3333
|
}
|
|
3366
3334
|
|
|
3367
|
-
const
|
|
3368
|
-
const
|
|
3335
|
+
const plot = plotRef.current as NativeSurfaceRenderableNode;
|
|
3336
|
+
const plotRect = getRenderableCellRect(plot);
|
|
3337
|
+
const visibleRect = resolveNativeSurfaceVisibleRect(plot, renderer.terminalWidth, renderer.terminalHeight);
|
|
3369
3338
|
const bitmapSize = computeBitmapSize(plotRect, renderer.resolution, renderer.terminalWidth, renderer.terminalHeight);
|
|
3370
3339
|
const renderablePixelSize = getRenderablePixelSize(plotRef.current, renderer);
|
|
3371
3340
|
const overlayPixelX = scaleLocalPixelCoordinate(
|
|
@@ -3455,6 +3424,8 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3455
3424
|
: null;
|
|
3456
3425
|
const isUpdating = !compact && (renderBodyState.updating || boundsBodyState.updating);
|
|
3457
3426
|
const timeAxisLabel = selectionScene?.timeLabels ?? staticResult.timeLabels;
|
|
3427
|
+
const cursorTimeAxisColumn = hasDisplayCursor ? displayScene?.cursorColumn ?? null : null;
|
|
3428
|
+
const cursorTimeAxisDate = hasDisplayCursor ? displayScene?.dateAtCursor ?? null : null;
|
|
3458
3429
|
const timeAxisBox = (
|
|
3459
3430
|
<Box
|
|
3460
3431
|
height={1}
|
|
@@ -3462,7 +3433,14 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3462
3433
|
overflow="hidden"
|
|
3463
3434
|
data-gloom-role="chart-time-axis"
|
|
3464
3435
|
>
|
|
3465
|
-
<
|
|
3436
|
+
<TimeAxisLabel
|
|
3437
|
+
timeLabels={timeAxisLabel}
|
|
3438
|
+
width={chartWidth}
|
|
3439
|
+
cursorColumn={cursorTimeAxisColumn}
|
|
3440
|
+
cursorDate={cursorTimeAxisDate}
|
|
3441
|
+
dates={timeAxisDates}
|
|
3442
|
+
cursorColor={chartColors.crosshairColor}
|
|
3443
|
+
/>
|
|
3466
3444
|
</Box>
|
|
3467
3445
|
);
|
|
3468
3446
|
const chartFooterHints = useMemo<PaneHint[]>(() => {
|
|
@@ -3609,6 +3587,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3609
3587
|
|
|
3610
3588
|
const handlePlotMove = (event: ChartMouseEvent) => {
|
|
3611
3589
|
if (!interactive || compact) return;
|
|
3590
|
+
if (mouseCrosshairDisabledRef.current) return;
|
|
3612
3591
|
const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
3613
3592
|
if (!localPointer) return;
|
|
3614
3593
|
const selectionCursor = resolveSelectionCursor(localPointer, projection.points.length, chartWidth, projection.effectiveMode);
|
|
@@ -3630,6 +3609,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3630
3609
|
if (compact) return;
|
|
3631
3610
|
if (!interactive) onActivate?.();
|
|
3632
3611
|
focusPaneForMouseInteraction(event);
|
|
3612
|
+
mouseCrosshairDisabledRef.current = false;
|
|
3633
3613
|
const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
3634
3614
|
if (!localPointer) return;
|
|
3635
3615
|
const selectionCursor = resolveSelectionCursor(localPointer, projection.points.length, chartWidth, projection.effectiveMode);
|
|
@@ -3654,6 +3634,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3654
3634
|
|
|
3655
3635
|
const handlePlotDrag = (event: ChartMouseEvent) => {
|
|
3656
3636
|
if (compact || (!interactive && !dragRef.current)) return;
|
|
3637
|
+
if (mouseCrosshairDisabledRef.current && !dragRef.current) return;
|
|
3657
3638
|
const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
3658
3639
|
if (localPointer) {
|
|
3659
3640
|
const selectionCursor = resolveSelectionCursor(localPointer, projection.points.length, chartWidth, projection.effectiveMode);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { Span, Text } from "../../ui";
|
|
3
|
+
import { colors } from "../../theme/colors";
|
|
4
|
+
import { buildCursorTimeAxisSegments } from "./chart-renderer";
|
|
5
|
+
|
|
6
|
+
interface TimeAxisLabelProps {
|
|
7
|
+
timeLabels: string;
|
|
8
|
+
width: number;
|
|
9
|
+
cursorColumn: number | null;
|
|
10
|
+
cursorDate: Date | string | number | null;
|
|
11
|
+
dates: Array<Date | string | number>;
|
|
12
|
+
cursorColor: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function TimeAxisLabel({
|
|
16
|
+
timeLabels,
|
|
17
|
+
width,
|
|
18
|
+
cursorColumn,
|
|
19
|
+
cursorDate,
|
|
20
|
+
dates,
|
|
21
|
+
cursorColor,
|
|
22
|
+
}: TimeAxisLabelProps) {
|
|
23
|
+
const segments = useMemo(() => buildCursorTimeAxisSegments({
|
|
24
|
+
timeLabels,
|
|
25
|
+
width,
|
|
26
|
+
cursorColumn,
|
|
27
|
+
cursorDate,
|
|
28
|
+
dates,
|
|
29
|
+
}), [cursorColumn, cursorDate, dates, timeLabels, width]);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Text style={{ whiteSpace: "pre" }}>
|
|
33
|
+
{segments.map((segment, index) => (
|
|
34
|
+
<Span
|
|
35
|
+
key={index}
|
|
36
|
+
fg={segment.highlighted ? cursorColor : colors.textDim}
|
|
37
|
+
>
|
|
38
|
+
{segment.text}
|
|
39
|
+
</Span>
|
|
40
|
+
))}
|
|
41
|
+
</Text>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
2
|
import { act, useReducer } from "react";
|
|
3
|
-
import { testRender } from "../../renderers/opentui/test-utils";
|
|
4
|
-
import { DialogProvider } from "@opentui-ui/dialog/react";
|
|
3
|
+
import { TestDialogProvider, testRender } from "../../renderers/opentui/test-utils";
|
|
5
4
|
import { CommandBar } from "./command-bar";
|
|
6
5
|
import { AppContext, type AppState, appReducer, createInitialState, getEffectiveThemeId } from "../../state/app-context";
|
|
7
6
|
import { createTestDataProvider } from "../../test-support/data-provider";
|
|
@@ -213,9 +212,9 @@ function makePluginRegistry(hasPaneSettings: (paneId: string) => boolean = () =>
|
|
|
213
212
|
getPluginPaneTemplateIds: () => [],
|
|
214
213
|
hasPaneSettings,
|
|
215
214
|
events: { emit: () => {} },
|
|
216
|
-
|
|
215
|
+
hidePane: () => {},
|
|
217
216
|
pinTicker: () => {},
|
|
218
|
-
|
|
217
|
+
showPane: () => {},
|
|
219
218
|
updateLayoutFn: () => {},
|
|
220
219
|
getTermSizeFn: () => ({ width: 80, height: 24 }),
|
|
221
220
|
notify: () => {},
|
|
@@ -304,7 +303,7 @@ function CommandBarHarness({
|
|
|
304
303
|
|
|
305
304
|
return (
|
|
306
305
|
<AppContext value={{ state: currentState, dispatch: currentDispatch }}>
|
|
307
|
-
<
|
|
306
|
+
<TestDialogProvider>
|
|
308
307
|
{live && <text>{`theme:${getEffectiveThemeId(currentState)}`}</text>}
|
|
309
308
|
{showQueryState && <text>{`query:${currentState.commandBarQuery}`}</text>}
|
|
310
309
|
{currentState.commandBarOpen && (
|
|
@@ -316,7 +315,7 @@ function CommandBarHarness({
|
|
|
316
315
|
onCheckForUpdates={onCheckForUpdates}
|
|
317
316
|
/>
|
|
318
317
|
)}
|
|
319
|
-
</
|
|
318
|
+
</TestDialogProvider>
|
|
320
319
|
</AppContext>
|
|
321
320
|
);
|
|
322
321
|
}
|
|
@@ -740,7 +739,12 @@ describe("CommandBar", () => {
|
|
|
740
739
|
expect(frame).toContain("Commands");
|
|
741
740
|
expect(frame).toContain("TH");
|
|
742
741
|
expect(frame).toContain("Themes");
|
|
743
|
-
expect(frame).
|
|
742
|
+
expect(frame).toContain("Amber");
|
|
743
|
+
expect(frame).toContain("Paper");
|
|
744
|
+
expect(frame).toContain("GitHub Light");
|
|
745
|
+
expect(frame).toContain("Solarized Light");
|
|
746
|
+
expect(frame).toContain("Gruvbox Light");
|
|
747
|
+
expect(frame).toContain("Nord Light");
|
|
744
748
|
});
|
|
745
749
|
|
|
746
750
|
test("renders plugin toggle state", async () => {
|
|
@@ -2197,6 +2201,50 @@ describe("CommandBar", () => {
|
|
|
2197
2201
|
expect(frame).toContain("AAPL,");
|
|
2198
2202
|
});
|
|
2199
2203
|
|
|
2204
|
+
test("CMP with one resolved ticker opens inline completion instead of creating a one-symbol chart", async () => {
|
|
2205
|
+
const created: Array<{ templateId: string; options?: Record<string, unknown> }> = [];
|
|
2206
|
+
|
|
2207
|
+
testSetup = await testRender(<CommandBarHarness
|
|
2208
|
+
query="CMP AMD"
|
|
2209
|
+
extraTickers={[makeTicker("AMD", "Advanced Micro Devices")]}
|
|
2210
|
+
configurePluginRegistry={(pluginRegistry) => {
|
|
2211
|
+
(pluginRegistry.panes as Map<string, any>).set("comparison-chart", {
|
|
2212
|
+
id: "comparison-chart",
|
|
2213
|
+
name: "Comparison Chart",
|
|
2214
|
+
component: () => null,
|
|
2215
|
+
defaultPosition: "right",
|
|
2216
|
+
});
|
|
2217
|
+
(pluginRegistry.paneTemplates as Map<string, any>).set("comparison-chart-pane", {
|
|
2218
|
+
id: "comparison-chart-pane",
|
|
2219
|
+
paneId: "comparison-chart",
|
|
2220
|
+
label: "Comparison Chart",
|
|
2221
|
+
description: "Compare multiple symbols in one pane",
|
|
2222
|
+
shortcut: { prefix: "CMP", argPlaceholder: "tickers", argKind: "ticker-list" },
|
|
2223
|
+
wizard: [{ key: "tickers", label: "Tickers", type: "text" }],
|
|
2224
|
+
canCreate: (_context: unknown, options?: { symbols?: string[] | null }) => !options?.symbols || options.symbols.length >= 2,
|
|
2225
|
+
});
|
|
2226
|
+
pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
|
|
2227
|
+
created.push({ templateId, options });
|
|
2228
|
+
};
|
|
2229
|
+
}}
|
|
2230
|
+
/>, {
|
|
2231
|
+
width: 100,
|
|
2232
|
+
height: 20,
|
|
2233
|
+
});
|
|
2234
|
+
|
|
2235
|
+
await testSetup.renderOnce();
|
|
2236
|
+
await act(async () => {
|
|
2237
|
+
testSetup!.mockInput.pressEnter();
|
|
2238
|
+
await Bun.sleep(0);
|
|
2239
|
+
await testSetup!.renderOnce();
|
|
2240
|
+
});
|
|
2241
|
+
|
|
2242
|
+
const frame = await waitForFrameToContain("Tickers");
|
|
2243
|
+
expect(created).toEqual([]);
|
|
2244
|
+
expect(frame).toContain("Comparison Chart");
|
|
2245
|
+
expect(frame).toContain("AMD");
|
|
2246
|
+
});
|
|
2247
|
+
|
|
2200
2248
|
test("AI <prompt> opens the inline workflow and prefills the textarea prompt", async () => {
|
|
2201
2249
|
const created: Array<{ templateId: string; options?: Record<string, unknown> }> = [];
|
|
2202
2250
|
|
|
@@ -2608,7 +2656,15 @@ describe("CommandBar", () => {
|
|
|
2608
2656
|
});
|
|
2609
2657
|
|
|
2610
2658
|
test("matches the direct help command", async () => {
|
|
2611
|
-
|
|
2659
|
+
const openedPanes: string[] = [];
|
|
2660
|
+
testSetup = await testRender(<CommandBarHarness
|
|
2661
|
+
query="help"
|
|
2662
|
+
configurePluginRegistry={(pluginRegistry) => {
|
|
2663
|
+
pluginRegistry.showPane = (paneId: string) => {
|
|
2664
|
+
openedPanes.push(paneId);
|
|
2665
|
+
};
|
|
2666
|
+
}}
|
|
2667
|
+
/>, {
|
|
2612
2668
|
width: 80,
|
|
2613
2669
|
height: 18,
|
|
2614
2670
|
});
|
|
@@ -2618,6 +2674,13 @@ describe("CommandBar", () => {
|
|
|
2618
2674
|
const frame = testSetup.captureCharFrame();
|
|
2619
2675
|
expect(frame).toContain("Help");
|
|
2620
2676
|
expect(frame).toContain("Navigation");
|
|
2677
|
+
|
|
2678
|
+
await act(async () => {
|
|
2679
|
+
testSetup!.mockInput.pressEnter();
|
|
2680
|
+
await testSetup!.renderOnce();
|
|
2681
|
+
});
|
|
2682
|
+
|
|
2683
|
+
expect(openedPanes).toEqual(["help"]);
|
|
2621
2684
|
});
|
|
2622
2685
|
|
|
2623
2686
|
test("skips pane templates whose canCreate throws", async () => {
|