gloomberb 0.3.3 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,27 +1,6 @@
1
- import { useEffect, useMemo, useRef, useState } from "react";
2
- import { TextAttributes, type BoxRenderable } from "@opentui/core";
3
- import { useRenderer } from "@opentui/react";
4
- import {
5
- buildChartScene,
6
- renderChart,
7
- resolveChartPalette,
8
- type StyledContent,
9
- } from "../../components/chart/chart-renderer";
10
- import {
11
- computeBitmapSize,
12
- renderNativeChartBase,
13
- type CellRect,
14
- type NativeChartBitmap,
15
- } from "../../components/chart/native/chart-rasterizer";
16
- import {
17
- ensureKittySupport,
18
- getCachedKittySupport,
19
- } from "../../components/chart/native/kitty-support";
20
- import { resolveChartRendererState } from "../../components/chart/native/renderer-selection";
21
- import { getNativeSurfaceManager } from "../../components/chart/native/surface-manager";
22
- import { syncCachedNativeSurface } from "../../components/chart/native/surface-sync";
23
- import { projectChartData } from "../../components/chart/chart-data";
24
- import { useAppState, usePaneInstanceId } from "../../state/app-context";
1
+ import { useMemo } from "react";
2
+ import { TextAttributes } from "@opentui/core";
3
+ import { StockChart } from "../../components/chart/stock-chart";
25
4
  import { EmptyState } from "../../components/ui/status";
26
5
  import { colors } from "../../theme/colors";
27
6
  import { formatNumber, formatPercentRaw } from "../../utils/format";
@@ -29,15 +8,6 @@ import type { PricePoint } from "../../types/financials";
29
8
  import type { PredictionHistoryPoint, PredictionHistoryRange } from "./types";
30
9
 
31
10
  const RANGES: PredictionHistoryRange[] = ["1D", "1W", "1M", "ALL"];
32
- const AXIS_WIDTH = 8;
33
-
34
- interface RenderableNode {
35
- x: number;
36
- y: number;
37
- width: number;
38
- height: number;
39
- parent: RenderableNode | null;
40
- }
41
11
 
42
12
  function coercePredictionPointDate(value: unknown): Date | null {
43
13
  if (value instanceof Date) {
@@ -54,79 +24,47 @@ function toPricePoints(points: PredictionHistoryPoint[]): PricePoint[] {
54
24
  return points.flatMap((point) => {
55
25
  const date = coercePredictionPointDate(point.date);
56
26
  if (!date) return [];
57
- return [
58
- {
59
- date,
60
- close: point.close,
61
- open: point.open,
62
- high: point.high,
63
- low: point.low,
64
- volume: point.volume,
65
- },
66
- ];
27
+ return [{
28
+ date,
29
+ close: point.close,
30
+ open: point.open,
31
+ high: point.high,
32
+ low: point.low,
33
+ volume: point.volume,
34
+ }];
67
35
  });
68
36
  }
69
37
 
70
- function extractCellRect(renderable: RenderableNode): CellRect {
71
- return {
72
- x: renderable.x,
73
- y: renderable.y,
74
- width: renderable.width,
75
- height: renderable.height,
76
- };
77
- }
78
-
79
- function intersectCellRects(left: CellRect, right: CellRect): CellRect | null {
80
- const x = Math.max(left.x, right.x);
81
- const y = Math.max(left.y, right.y);
82
- const maxX = Math.min(left.x + left.width, right.x + right.width);
83
- const maxY = Math.min(left.y + left.height, right.y + right.height);
84
- if (maxX <= x || maxY <= y) return null;
85
- return { x, y, width: maxX - x, height: maxY - y };
86
- }
87
-
88
- function resolveVisibleRect(
89
- renderable: RenderableNode | null,
90
- terminalWidth: number,
91
- terminalHeight: number,
92
- ): CellRect | null {
93
- if (!renderable) return null;
94
-
95
- let visible: CellRect = {
96
- x: 0,
97
- y: 0,
98
- width: terminalWidth,
99
- height: terminalHeight,
100
- };
101
- let current: RenderableNode | null = renderable;
102
-
103
- while (current) {
104
- const nextVisible = intersectCellRects(visible, extractCellRect(current));
105
- if (!nextVisible) return null;
106
- visible = nextVisible;
107
- current = current.parent;
108
- }
109
-
110
- return visible;
111
- }
112
-
113
- function buildBlankPlotLines(width: number, height: number): string[] {
114
- return Array.from({ length: height }, () => " ".repeat(width));
115
- }
116
-
117
- function buildNativeBitmapKey(
118
- points: PricePoint[],
119
- pixelWidth: number,
120
- pixelHeight: number,
121
- paletteId: string,
122
- ): string {
123
- const fingerprint = points
124
- .map(
125
- (point) =>
126
- `${point.date.getTime()}:${point.open}:${point.high}:${point.low}:${point.close}:${point.volume ?? 0}`,
127
- )
128
- .join("|");
129
- return [pixelWidth, pixelHeight, paletteId, fingerprint].join("::");
38
+ function PredictionRangeTabs({
39
+ activeRange,
40
+ onRangeSelect,
41
+ }: {
42
+ activeRange: PredictionHistoryRange;
43
+ onRangeSelect: (range: PredictionHistoryRange) => void;
44
+ }) {
45
+ return (
46
+ <box flexDirection="row" gap={1}>
47
+ {RANGES.map((entry) => {
48
+ const active = entry === activeRange;
49
+ return (
50
+ <box
51
+ key={entry}
52
+ onMouseDown={(event) => {
53
+ event.preventDefault();
54
+ onRangeSelect(entry);
55
+ }}
56
+ >
57
+ <text
58
+ fg={active ? colors.textBright : colors.textDim}
59
+ attributes={active ? TextAttributes.BOLD : 0}
60
+ >
61
+ {entry}
62
+ </text>
63
+ </box>
64
+ );
65
+ })}
66
+ </box>
67
+ );
130
68
  }
131
69
 
132
70
  export function PredictionMarketChart({
@@ -142,299 +80,16 @@ export function PredictionMarketChart({
142
80
  range: PredictionHistoryRange;
143
81
  onRangeSelect: (range: PredictionHistoryRange) => void;
144
82
  }) {
145
- const { state } = useAppState();
146
- const renderer = useRenderer();
147
- const paneId = usePaneInstanceId();
148
- const nativeSurfaceManager = useMemo(
149
- () => getNativeSurfaceManager(renderer),
150
- [renderer],
151
- );
152
- const [kittySupport, setKittySupport] = useState<boolean | null>(() =>
153
- getCachedKittySupport(renderer),
154
- );
155
- const plotRef = useRef<BoxRenderable>(null);
156
- const nativeSurfaceIdRef = useRef(`prediction-chart-surface:${paneId}`);
157
- const lastNativeBitmapRef = useRef<{
158
- key: string;
159
- bitmap: NativeChartBitmap;
160
- } | null>(null);
161
- const lastNativeGeometryRef = useRef<{
162
- rect: CellRect;
163
- visibleRect: CellRect | null;
164
- } | null>(null);
165
-
166
- const chartWidth = Math.max(width - AXIS_WIDTH - 2, 18);
167
- const chartHeight = Math.max(height - 2, 6);
168
- const hasHistory = history.length > 0;
169
-
170
- useEffect(() => {
171
- if (kittySupport !== null) return;
172
- let disposed = false;
173
- ensureKittySupport(renderer)
174
- .then((supported) => {
175
- if (!disposed) setKittySupport(supported);
176
- })
177
- .catch(() => {
178
- if (!disposed) setKittySupport(false);
179
- });
180
- return () => {
181
- disposed = true;
182
- };
183
- }, [kittySupport, renderer]);
184
-
185
83
  const pricePoints = useMemo(() => toPricePoints(history), [history]);
186
- const projection = useMemo(
187
- () => projectChartData(pricePoints, chartWidth, "area"),
188
- [chartWidth, pricePoints],
189
- );
190
- const first = projection.points[0] ?? null;
191
- const last = projection.points[projection.points.length - 1] ?? null;
192
- const delta = first && last ? last.close - first.close : 0;
193
- const deltaPct = first?.close ? (delta / first.close) * 100 : 0;
194
- const palette = useMemo(
195
- () =>
196
- resolveChartPalette(
197
- colors,
198
- delta > 0 ? "positive" : delta < 0 ? "negative" : "neutral",
199
- ),
200
- [delta],
201
- );
202
- const rendererState = resolveChartRendererState(
203
- state.config.chartPreferences.renderer,
204
- kittySupport,
205
- renderer.resolution,
206
- );
207
- const effectiveRenderer = rendererState.renderer;
208
84
 
209
- const rendered = useMemo(
210
- () =>
211
- renderChart(projection.points, {
212
- width: chartWidth,
213
- height: chartHeight,
214
- mode: "area",
215
- showVolume: false,
216
- volumeHeight: 0,
217
- cursorX: null,
218
- cursorY: null,
219
- colors: palette,
220
- axisMode: "price",
221
- currency: "USD",
222
- }),
223
- [chartHeight, chartWidth, palette, projection.points],
224
- );
225
-
226
- const nativeScene = useMemo(
227
- () =>
228
- buildChartScene(projection.points, {
229
- width: chartWidth,
230
- height: chartHeight,
231
- showVolume: false,
232
- volumeHeight: 0,
233
- cursorX: null,
234
- cursorY: null,
235
- mode: "area",
236
- axisMode: "price",
237
- colors: palette,
238
- }),
239
- [chartHeight, chartWidth, palette, projection.points],
240
- );
241
-
242
- const axisByRow = useMemo(
243
- () => new Map(rendered.axisLabels.map((entry) => [entry.row, entry.label])),
244
- [rendered.axisLabels],
245
- );
246
- const blankPlotLines = useMemo(
247
- () => buildBlankPlotLines(chartWidth, chartHeight),
248
- [chartHeight, chartWidth],
249
- );
250
-
251
- useEffect(
252
- () => () => {
253
- lastNativeBitmapRef.current = null;
254
- lastNativeGeometryRef.current = null;
255
- nativeSurfaceManager.removeSurface(nativeSurfaceIdRef.current);
256
- renderer.requestRender();
257
- },
258
- [nativeSurfaceManager, renderer],
259
- );
260
-
261
- useEffect(() => {
262
- if (effectiveRenderer === "kitty" && rendererState.nativeReady) return;
263
- lastNativeBitmapRef.current = null;
264
- lastNativeGeometryRef.current = null;
265
- nativeSurfaceManager.removeSurface(nativeSurfaceIdRef.current);
266
- }, [effectiveRenderer, nativeSurfaceManager, rendererState.nativeReady]);
267
-
268
- useEffect(() => {
269
- if (
270
- effectiveRenderer !== "kitty" ||
271
- !rendererState.nativeReady ||
272
- !plotRef.current
273
- )
274
- return;
275
- const plot = plotRef.current;
276
- let mountTimer: ReturnType<typeof setTimeout> | null = null;
277
-
278
- const syncPlacement = () => {
279
- if (
280
- effectiveRenderer !== "kitty" ||
281
- !rendererState.nativeReady ||
282
- !plotRef.current
283
- )
284
- return;
285
- const rect = extractCellRect(plotRef.current);
286
- const visibleRect = resolveVisibleRect(
287
- plotRef.current,
288
- renderer.terminalWidth,
289
- renderer.terminalHeight,
290
- );
291
- const previous = lastNativeGeometryRef.current;
292
- if (
293
- previous &&
294
- previous.rect.x === rect.x &&
295
- previous.rect.y === rect.y &&
296
- previous.rect.width === rect.width &&
297
- previous.rect.height === rect.height &&
298
- previous.visibleRect?.x === visibleRect?.x &&
299
- previous.visibleRect?.y === visibleRect?.y &&
300
- previous.visibleRect?.width === visibleRect?.width &&
301
- previous.visibleRect?.height === visibleRect?.height
302
- ) {
303
- return;
304
- }
305
- lastNativeGeometryRef.current = { rect, visibleRect };
306
- syncCachedNativeSurface(
307
- nativeSurfaceManager,
308
- nativeSurfaceIdRef.current,
309
- { paneId, rect, visibleRect },
310
- lastNativeBitmapRef.current,
311
- );
312
- };
313
-
314
- plot.onLifecyclePass = syncPlacement;
315
- renderer.registerLifecyclePass(plot);
316
- syncPlacement();
317
- mountTimer = setTimeout(() => {
318
- syncPlacement();
319
- renderer.requestRender();
320
- }, 0);
321
-
322
- return () => {
323
- if (mountTimer) clearTimeout(mountTimer);
324
- plot.onLifecyclePass = null;
325
- renderer.unregisterLifecyclePass(plot);
326
- lastNativeGeometryRef.current = null;
327
- };
328
- }, [
329
- effectiveRenderer,
330
- nativeSurfaceManager,
331
- paneId,
332
- renderer,
333
- rendererState.nativeReady,
334
- ]);
335
-
336
- useEffect(() => {
337
- if (
338
- effectiveRenderer !== "kitty" ||
339
- !rendererState.nativeReady ||
340
- !renderer.resolution ||
341
- !plotRef.current ||
342
- !nativeScene
343
- ) {
344
- lastNativeBitmapRef.current = null;
345
- nativeSurfaceManager.removeSurface(nativeSurfaceIdRef.current);
346
- return;
347
- }
348
-
349
- const plotRect = extractCellRect(plotRef.current);
350
- const visibleRect = resolveVisibleRect(
351
- plotRef.current,
352
- renderer.terminalWidth,
353
- renderer.terminalHeight,
354
- );
355
- if (!visibleRect) {
356
- nativeSurfaceManager.removeSurface(nativeSurfaceIdRef.current);
357
- return;
358
- }
359
-
360
- const bitmapSize = computeBitmapSize(
361
- plotRect,
362
- renderer.resolution,
363
- renderer.terminalWidth,
364
- renderer.terminalHeight,
365
- );
366
- const bitmapKey = buildNativeBitmapKey(
367
- pricePoints,
368
- bitmapSize.pixelWidth,
369
- bitmapSize.pixelHeight,
370
- [palette.lineColor, palette.fillColor, palette.gridColor].join(","),
371
- );
372
- const cachedBitmap =
373
- lastNativeBitmapRef.current?.key === bitmapKey
374
- ? lastNativeBitmapRef.current.bitmap
375
- : null;
376
- const bitmap =
377
- cachedBitmap ??
378
- renderNativeChartBase(
379
- nativeScene,
380
- bitmapSize.pixelWidth,
381
- bitmapSize.pixelHeight,
382
- );
383
- if (!cachedBitmap) {
384
- lastNativeBitmapRef.current = { key: bitmapKey, bitmap };
385
- }
386
-
387
- nativeSurfaceManager.upsertSurface({
388
- id: nativeSurfaceIdRef.current,
389
- paneId,
390
- rect: plotRect,
391
- visibleRect,
392
- bitmap,
393
- bitmapKey,
394
- });
395
- renderer.requestRender();
396
- }, [
397
- effectiveRenderer,
398
- nativeScene,
399
- nativeSurfaceManager,
400
- paneId,
401
- palette.fillColor,
402
- palette.gridColor,
403
- palette.lineColor,
404
- pricePoints,
405
- renderer,
406
- rendererState.nativeReady,
407
- ]);
408
-
409
- const plotLines: Array<string | StyledContent> =
410
- effectiveRenderer === "kitty" ? blankPlotLines : rendered.lines;
411
- const plotContent = plotLines.map((line, index) => (
412
- <text key={index} content={line as any} />
413
- ));
414
-
415
- if (!hasHistory) {
85
+ if (pricePoints.length === 0) {
416
86
  return (
417
87
  <box flexDirection="column" height={height}>
418
- <box flexDirection="row" gap={1} height={1}>
419
- {RANGES.map((entry) => {
420
- const active = entry === range;
421
- return (
422
- <box
423
- key={entry}
424
- onMouseDown={(event) => {
425
- event.preventDefault();
426
- onRangeSelect(entry);
427
- }}
428
- >
429
- <text
430
- fg={active ? colors.textBright : colors.textDim}
431
- attributes={active ? TextAttributes.BOLD : 0}
432
- >
433
- {entry}
434
- </text>
435
- </box>
436
- );
437
- })}
88
+ <box flexDirection="row" height={1}>
89
+ <PredictionRangeTabs
90
+ activeRange={range}
91
+ onRangeSelect={onRangeSelect}
92
+ />
438
93
  </box>
439
94
  <box flexGrow={1} justifyContent="center">
440
95
  <EmptyState
@@ -446,30 +101,19 @@ export function PredictionMarketChart({
446
101
  );
447
102
  }
448
103
 
104
+ const first = pricePoints[0] ?? null;
105
+ const last = pricePoints[pricePoints.length - 1] ?? null;
106
+ const delta = first && last ? last.close - first.close : 0;
107
+ const deltaPct = first?.close ? (delta / first.close) * 100 : 0;
108
+ const chartHeight = Math.max(height - 1, 2);
109
+
449
110
  return (
450
111
  <box flexDirection="column" height={height}>
451
112
  <box flexDirection="row" height={1}>
452
- <box flexDirection="row" gap={1}>
453
- {RANGES.map((entry) => {
454
- const active = entry === range;
455
- return (
456
- <box
457
- key={entry}
458
- onMouseDown={(event) => {
459
- event.preventDefault();
460
- onRangeSelect(entry);
461
- }}
462
- >
463
- <text
464
- fg={active ? colors.textBright : colors.textDim}
465
- attributes={active ? TextAttributes.BOLD : 0}
466
- >
467
- {entry}
468
- </text>
469
- </box>
470
- );
471
- })}
472
- </box>
113
+ <PredictionRangeTabs
114
+ activeRange={range}
115
+ onRangeSelect={onRangeSelect}
116
+ />
473
117
  <box flexGrow={1} />
474
118
  <text
475
119
  fg={
@@ -484,29 +128,14 @@ export function PredictionMarketChart({
484
128
  </text>
485
129
  </box>
486
130
 
487
- <box flexDirection="row" height={chartHeight}>
488
- <box
489
- ref={plotRef}
490
- width={chartWidth}
491
- height={chartHeight}
492
- flexDirection="column"
493
- backgroundColor={palette.bgColor}
494
- >
495
- {plotContent}
496
- </box>
497
- <box width={1} />
498
- <box width={AXIS_WIDTH} flexDirection="column" height={chartHeight}>
499
- {Array.from({ length: chartHeight }, (_, index) => (
500
- <text key={index} fg={colors.textDim}>
501
- {axisByRow.get(index) ?? ""}
502
- </text>
503
- ))}
504
- </box>
505
- </box>
506
-
507
- <box height={1}>
508
- <text fg={colors.textDim}>{rendered.timeLabels}</text>
509
- </box>
131
+ <StockChart
132
+ width={width}
133
+ height={chartHeight}
134
+ focused={false}
135
+ compact
136
+ historyOverride={pricePoints}
137
+ currencyOverride="USD"
138
+ />
510
139
  </box>
511
140
  );
512
141
  }
@@ -431,14 +431,18 @@ export function usePredictionMarketsDataState({
431
431
  setTransportState("idle");
432
432
  return;
433
433
  }
434
- const detailSummary = selectedSummaryRef.current ?? selectedSummary;
434
+ const detailSummary = selectedSummaryRef.current;
435
+ if (!detailSummary) {
436
+ setTransportState("idle");
437
+ return;
438
+ }
435
439
  const delayMs = detailLoadDelayRef.current;
436
440
  detailLoadDelayRef.current = 0;
437
441
  const timeoutId = setTimeout(() => {
438
442
  void loadSelectedDetail(detailSummary);
439
443
  }, delayMs);
440
444
  return () => clearTimeout(timeoutId);
441
- }, [loadSelectedDetail, selectedSummaryKey, selectedSummary]);
445
+ }, [loadSelectedDetail, selectedSummaryKey]);
442
446
 
443
447
  useEffect(() => {
444
448
  if (!focused || selectedSummaryVenue !== "kalshi" || !selectedSummaryKey) {
@@ -27,11 +27,10 @@ export function PredictionMarketOutcomesView({
27
27
 
28
28
  return (
29
29
  <box flexDirection="column">
30
- <box flexDirection="column">
30
+ <box height={1}>
31
31
  <text fg={colors.textBright} attributes={TextAttributes.BOLD}>
32
32
  Outcomes
33
33
  </text>
34
- <text fg={colors.textDim}>Ranked by implied YES probability.</text>
35
34
  </box>
36
35
 
37
36
  <box flexDirection="row" height={1}>
@@ -64,10 +63,7 @@ export function PredictionMarketOutcomesView({
64
63
  fg={selected ? colors.selectedText : colors.text}
65
64
  attributes={selected ? TextAttributes.BOLD : 0}
66
65
  >
67
- {padTo(
68
- `${index === 0 ? "TOP " : ""}${market.marketLabel}`,
69
- labelWidth,
70
- )}
66
+ {padTo(market.marketLabel, labelWidth)}
71
67
  </text>
72
68
  </box>
73
69
  <box width={8}>
@@ -8,7 +8,6 @@ import { DETAIL_TABS } from "../navigation";
8
8
  import {
9
9
  formatPredictionEndsAt,
10
10
  formatPredictionMetric,
11
- formatPredictionPercent,
12
11
  formatPredictionProbability,
13
12
  formatPredictionSpread,
14
13
  getPredictionProbabilityColor,
@@ -34,10 +33,6 @@ interface MetricCell {
34
33
  color?: string;
35
34
  }
36
35
 
37
- function formatVenueLabel(venue: PredictionMarketSummary["venue"]): string {
38
- return venue === "polymarket" ? "Polymarket" : "Kalshi";
39
- }
40
-
41
36
  function MetricLabelRow({ metrics }: { metrics: MetricCell[] }) {
42
37
  return (
43
38
  <box flexDirection="row" height={1}>
@@ -114,14 +109,9 @@ export function PredictionMarketDetailPane({
114
109
  const summaryMetrics = detail?.summary ?? selectedSummary;
115
110
  const detailTitle =
116
111
  selectedRow?.kind === "group" ? selectedRow.title : summaryMetrics.title;
117
- const selectedLabel = truncatePredictionText(
118
- selectedSummary.marketLabel,
119
- Math.max(detailWidth - 18, 12),
120
- );
121
- const detailSubtitleParts = [formatVenueLabel(summaryMetrics.venue)];
112
+ const detailSubtitleParts: string[] = [];
122
113
  if (selectedRow?.kind === "group") {
123
114
  if (summaryMetrics.category) detailSubtitleParts.push(summaryMetrics.category);
124
- detailSubtitleParts.push(`${selectedRow.marketCount} targets`);
125
115
  } else if (
126
116
  summaryMetrics.eventLabel &&
127
117
  summaryMetrics.eventLabel !== summaryMetrics.title
@@ -195,7 +185,7 @@ export function PredictionMarketDetailPane({
195
185
  0,
196
186
  Math.max(Math.min(Math.floor((detailWidth - 10) / 18), 3), 0),
197
187
  ) ?? []);
198
- const headerHeight = selectedRow?.kind === "group" ? 4 : 3;
188
+ const headerHeight = detailSubtitle.length > 0 ? 3 : 2;
199
189
  const detailLoading = detailLoadCount > 0 && !detail;
200
190
  const titleColor = focused ? colors.textBright : colors.text;
201
191
 
@@ -207,20 +197,9 @@ export function PredictionMarketDetailPane({
207
197
  {detailTitle}
208
198
  </text>
209
199
  </box>
210
- <box flexDirection="row" height={1}>
211
- <text fg={colors.textDim}>{detailSubtitle}</text>
212
- </box>
213
- {selectedRow?.kind === "group" && (
200
+ {detailSubtitle.length > 0 && (
214
201
  <box flexDirection="row" height={1}>
215
- <text fg={colors.textDim}>Top target:</text>
216
- <box width={1} />
217
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>
218
- {selectedLabel}
219
- </text>
220
- <box width={1} />
221
- <text fg={getPredictionProbabilityColor(selectedSummary.yesPrice)}>
222
- {formatPredictionPercent(selectedSummary.yesPrice)}
223
- </text>
202
+ <text fg={colors.textDim}>{detailSubtitle}</text>
224
203
  </box>
225
204
  )}
226
205
  </box>
@@ -20,7 +20,6 @@ export function SummaryLink({
20
20
  }) {
21
21
  return (
22
22
  <box height={1} onMouseDown={() => openUrl(url)}>
23
- <text fg={colors.textDim}>{"Venue: "}</text>
24
23
  <text fg={colors.textBright} attributes={TextAttributes.UNDERLINE}>
25
24
  {truncatePredictionText(url, maxLength)}
26
25
  </text>
@@ -43,5 +43,9 @@ describe("prediction markets detail views", () => {
43
43
  expect(frame).toContain("Outcomes");
44
44
  expect(frame).toContain("Above 4.25%");
45
45
  expect(frame).toContain("Above 4.50%");
46
+ expect(frame).not.toContain("Ranked by implied YES probability.");
47
+ expect(frame).not.toContain("TOP Above 4.25%");
48
+ expect(frame).not.toContain("Kalshi");
49
+ expect(frame).not.toContain("targets");
46
50
  });
47
51
  });