@tradejs/app 3.1.8-beta.206 → 3.1.8-beta.210

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/app",
3
- "version": "3.1.8-beta.206",
3
+ "version": "3.1.8-beta.210",
4
4
  "description": "Installable Next.js UI for the TradeJS TypeScript framework: dashboards, backtests, charts, and runtime data.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -52,11 +52,11 @@
52
52
  "@chakra-ui/charts": "3.36.1",
53
53
  "@chakra-ui/react": "3.36.1",
54
54
  "@emotion/react": "^11.14.0",
55
- "@tradejs/core": "^3.1.8-beta.206",
56
- "@tradejs/indicators": "^3.1.8-beta.206",
57
- "@tradejs/infra": "^3.1.8-beta.206",
58
- "@tradejs/node": "^3.1.8-beta.206",
59
- "@tradejs/types": "^3.1.8-beta.206",
55
+ "@tradejs/core": "^3.1.8-beta.210",
56
+ "@tradejs/indicators": "^3.1.8-beta.210",
57
+ "@tradejs/infra": "^3.1.8-beta.210",
58
+ "@tradejs/node": "^3.1.8-beta.210",
59
+ "@tradejs/types": "^3.1.8-beta.210",
60
60
  "@types/bcryptjs": "2.4.6",
61
61
  "@types/lodash": "4.17.24",
62
62
  "@types/node": "24.13.3",
@@ -1,9 +1,10 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
  import {
3
- getRuntimeDeployment,
4
- saveRuntimeDeployment,
5
- } from '@tradejs/infra/runtimeDeployments';
6
- import { recordRuntimeStrategyControlEvent } from '@tradejs/infra/runtimeStrategyReleases';
3
+ pauseRuntimeStrategy,
4
+ recordRuntimeStrategyControlEvent,
5
+ resumeRuntimeStrategy,
6
+ } from '@tradejs/infra/runtimeControls';
7
+ import { getRuntimeDeployment } from '@tradejs/node/runtimeStrategies';
7
8
  import type { RuntimeStrategyControlState } from '@tradejs/types';
8
9
  import { getCurrentUserName } from '#app/lib/currentUser';
9
10
 
@@ -21,7 +22,13 @@ export const PATCH = async (
21
22
  }
22
23
  const { deploymentId, strategyName: encodedStrategyName } = await params;
23
24
  const strategyName = decodeURIComponent(encodedStrategyName);
24
- const deployment = await getRuntimeDeployment(userName, deploymentId);
25
+ const projectRoot =
26
+ String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
27
+ const deployment = await getRuntimeDeployment({
28
+ userName,
29
+ projectRoot,
30
+ deploymentId,
31
+ });
25
32
  if (!deployment) {
26
33
  return NextResponse.json(
27
34
  { error: 'Deployment not found' },
@@ -44,7 +51,7 @@ export const PATCH = async (
44
51
  );
45
52
  if (!reference) {
46
53
  return NextResponse.json(
47
- { error: 'Pause/resume requires a versioned strategy release' },
54
+ { error: 'Strategy is not declared in tradejs.config.ts' },
48
55
  { status: 400 },
49
56
  );
50
57
  }
@@ -52,19 +59,35 @@ export const PATCH = async (
52
59
  if (previousState === controlState) {
53
60
  return NextResponse.json({ deployment, controlState });
54
61
  }
55
- const updated = await saveRuntimeDeployment(userName, {
56
- ...deployment,
57
- strategies: deployment.strategies.map((strategy) =>
58
- strategy.strategyName === strategyName
59
- ? { ...strategy, controlState }
60
- : strategy,
61
- ),
62
+ if (
63
+ controlState === 'active' &&
64
+ (!deployment.enabled || !reference.enabled)
65
+ ) {
66
+ return NextResponse.json(
67
+ { error: 'Strategy is disabled in tradejs.config.ts' },
68
+ { status: 409 },
69
+ );
70
+ }
71
+ if (controlState === 'entries_paused') {
72
+ await pauseRuntimeStrategy({
73
+ userName,
74
+ deploymentId,
75
+ strategyName,
76
+ updatedBy: userName,
77
+ });
78
+ } else {
79
+ await resumeRuntimeStrategy({ userName, deploymentId, strategyName });
80
+ }
81
+ const updated = await getRuntimeDeployment({
82
+ userName,
83
+ projectRoot,
84
+ deploymentId,
62
85
  });
63
86
  const event = await recordRuntimeStrategyControlEvent({
64
87
  userName,
65
88
  deploymentId: deployment.id,
66
89
  strategyName,
67
- releaseVersion: reference.releaseVersion,
90
+ version: reference.version,
68
91
  previousState,
69
92
  nextState: controlState,
70
93
  createdBy: userName,
@@ -1,44 +1,18 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import {
3
- getRuntimeDeploymentHeartbeat,
4
- listRuntimeDeployments,
5
- saveRuntimeDeployment,
6
- } from '@tradejs/infra/runtimeDeployments';
7
- import { getTradingAccount } from '@tradejs/infra/tradingAccounts';
8
- import { getRuntimeStrategyRelease } from '@tradejs/infra/runtimeStrategyReleases';
9
- import type {
10
- MarketUniverse,
11
- RuntimeDeployment,
12
- RuntimeDeploymentStrategy,
13
- } from '@tradejs/types';
1
+ import { NextResponse } from 'next/server';
2
+ import { getRuntimeDeploymentHeartbeat } from '@tradejs/infra/runtimeHeartbeats';
3
+ import { listRuntimeDeployments } from '@tradejs/node/runtimeStrategies';
14
4
  import { getCurrentUserName } from '#app/lib/currentUser';
15
5
 
16
6
  export const dynamic = 'force-dynamic';
17
7
 
18
- const isRuntimeStrategyReference = (
19
- value: unknown,
20
- ): value is RuntimeDeploymentStrategy => {
21
- if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
22
- const record = value as Record<string, unknown>;
23
- return (
24
- typeof record.strategyName === 'string' &&
25
- record.strategyName.trim().length > 0 &&
26
- Number.isSafeInteger(record.releaseVersion) &&
27
- Number(record.releaseVersion) > 0 &&
28
- (record.controlState === 'active' ||
29
- record.controlState === 'entries_paused') &&
30
- Object.keys(record).every((key) =>
31
- ['strategyName', 'releaseVersion', 'controlState'].includes(key),
32
- )
33
- );
34
- };
35
-
36
8
  export const GET = async () => {
37
9
  const userName = await getCurrentUserName();
38
10
  if (!userName) {
39
11
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
40
12
  }
41
- const deployments = await listRuntimeDeployments(userName);
13
+ const projectRoot =
14
+ String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
15
+ const deployments = await listRuntimeDeployments({ userName, projectRoot });
42
16
  const withHeartbeats = await Promise.all(
43
17
  deployments.map(async (deployment) => ({
44
18
  ...deployment,
@@ -47,90 +21,3 @@ export const GET = async () => {
47
21
  );
48
22
  return NextResponse.json({ deployments: withHeartbeats });
49
23
  };
50
-
51
- export const POST = async (request: NextRequest) => {
52
- const userName = await getCurrentUserName();
53
- if (!userName) {
54
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
55
- }
56
- try {
57
- const body = (await request.json()) as Partial<RuntimeDeployment>;
58
- if (
59
- !Array.isArray(body.strategies) ||
60
- !body.strategies.length ||
61
- !body.strategies.every(isRuntimeStrategyReference)
62
- ) {
63
- return NextResponse.json(
64
- { error: 'Invalid runtime strategy reference' },
65
- { status: 400 },
66
- );
67
- }
68
- const releases = await Promise.all(
69
- body.strategies.map(async (strategy) => {
70
- const release = await getRuntimeStrategyRelease(
71
- userName,
72
- strategy.strategyName,
73
- strategy.releaseVersion,
74
- );
75
- if (!release) {
76
- throw new Error(
77
- `Release not found: ${strategy.strategyName} v${strategy.releaseVersion}`,
78
- );
79
- }
80
- return release;
81
- }),
82
- );
83
- if (
84
- !body.id ||
85
- !body.label ||
86
- !body.connectorName ||
87
- !body.provider ||
88
- !body.accountId ||
89
- typeof body.enabled !== 'boolean'
90
- ) {
91
- return NextResponse.json(
92
- { error: 'Invalid runtime deployment' },
93
- { status: 400 },
94
- );
95
- }
96
- const account = await getTradingAccount(userName, body.accountId);
97
- if (
98
- !account ||
99
- !account.enabled ||
100
- account.provider !== body.provider.toLowerCase()
101
- ) {
102
- return NextResponse.json(
103
- { error: 'Deployment trading account is unavailable' },
104
- { status: 400 },
105
- );
106
- }
107
- const unsupportedUniverse = releases
108
- .map((release) => release.config.UNIVERSE as MarketUniverse)
109
- .find((universe) => !account.universes.includes(universe));
110
- if (unsupportedUniverse) {
111
- return NextResponse.json(
112
- {
113
- error: `Account ${account.id} does not support ${unsupportedUniverse}`,
114
- },
115
- { status: 400 },
116
- );
117
- }
118
- const deployment = await saveRuntimeDeployment(userName, {
119
- id: body.id,
120
- label: body.label,
121
- connectorName: body.connectorName,
122
- provider: body.provider,
123
- accountId: body.accountId,
124
- enabled: body.enabled,
125
- strategies: body.strategies,
126
- assetClasses: body.assetClasses,
127
- tickers: body.tickers,
128
- });
129
- return NextResponse.json({ deployment });
130
- } catch (error) {
131
- return NextResponse.json(
132
- { error: error instanceof Error ? error.message : String(error) },
133
- { status: 400 },
134
- );
135
- }
136
- };
@@ -3,7 +3,7 @@ import {
3
3
  deleteTradingAccount,
4
4
  getTradingAccount,
5
5
  } from '@tradejs/infra/tradingAccounts';
6
- import { listRuntimeDeployments } from '@tradejs/infra/runtimeDeployments';
6
+ import { listRuntimeDeployments } from '@tradejs/node/runtimeStrategies';
7
7
  import { getCurrentUserName } from '#app/lib/currentUser';
8
8
 
9
9
  export const DELETE = async (
@@ -19,9 +19,11 @@ export const DELETE = async (
19
19
  if (!account) {
20
20
  return NextResponse.json({ error: 'Account not found' }, { status: 404 });
21
21
  }
22
- const referencedBy = (await listRuntimeDeployments(userName)).filter(
23
- (deployment) => deployment.accountId === account.id,
24
- );
22
+ const projectRoot =
23
+ String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
24
+ const referencedBy = (
25
+ await listRuntimeDeployments({ userName, projectRoot })
26
+ ).filter((deployment) => deployment.accountId === account.id);
25
27
  if (referencedBy.length) {
26
28
  return NextResponse.json(
27
29
  {
@@ -135,7 +135,7 @@ export const RuntimeStrategyCard = ({
135
135
  TF: {strategy.interval}m
136
136
  </Badge>
137
137
  <Badge colorPalette="gray" variant="outline">
138
- release: v{strategy.releaseVersion}
138
+ version: v{strategy.version}
139
139
  </Badge>
140
140
  {strategy.accountId ? (
141
141
  <Badge colorPalette="purple" variant="outline">
@@ -258,7 +258,6 @@ export const RuntimeStrategyCard = ({
258
258
  <RuntimeStrategyChart
259
259
  orderLog={strategy.orderLog}
260
260
  stat={strategy.stat}
261
- evidenceTimeline={strategy.evidenceTimeline}
262
261
  startTimestamp={startTimestamp}
263
262
  endTimestamp={endTimestamp}
264
263
  />
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useMemo, useState } from 'react';
3
+ import { useMemo } from 'react';
4
4
  import { Box, Flex, Text } from '@chakra-ui/react';
5
5
  import { Chart, useChart } from '@chakra-ui/charts';
6
6
  import {
@@ -13,24 +13,13 @@ import {
13
13
  YAxis,
14
14
  } from 'recharts';
15
15
  import { getFormatted } from '@tradejs/core/backtest';
16
- import type {
17
- SimpleOrderLogData,
18
- StrategyEvidenceMarkerType,
19
- StrategyEvidenceTimeline,
20
- TestStat,
21
- } from '@tradejs/types';
16
+ import type { SimpleOrderLogData, TestStat } from '@tradejs/types';
22
17
  import { formatTimeSeriesTooltipTimestamp } from '#app/lib/timeSeriesChart';
23
18
  import { TimeSeriesXAxis } from '#shared/Charts/TimeSeriesXAxis';
24
- import {
25
- filterStrategyEvidenceMarkers,
26
- STRATEGY_EVIDENCE_MARKER_PRESENTATION,
27
- StrategyEvidencePopover,
28
- } from './StrategyEvidencePopover';
29
19
 
30
20
  interface RuntimeStrategyChartProps {
31
21
  orderLog: SimpleOrderLogData;
32
22
  stat: TestStat;
33
- evidenceTimeline: StrategyEvidenceTimeline;
34
23
  startTimestamp: number;
35
24
  endTimestamp: number;
36
25
  height?: string | number;
@@ -39,13 +28,10 @@ interface RuntimeStrategyChartProps {
39
28
  export const RuntimeStrategyChart = ({
40
29
  orderLog,
41
30
  stat,
42
- evidenceTimeline,
43
31
  startTimestamp,
44
32
  endTimestamp,
45
33
  height = '350px',
46
34
  }: RuntimeStrategyChartProps) => {
47
- const [showParity, setShowParity] = useState(true);
48
- const [showRecommendations, setShowRecommendations] = useState(true);
49
35
  const chartData = useMemo(
50
36
  () => ({
51
37
  data: orderLog.map(([timestamp, amount]) => ({
@@ -65,31 +51,8 @@ export const RuntimeStrategyChart = ({
65
51
  const chart = useChart(chartData as any);
66
52
  const { formatted: maxAmount } = getFormatted(stat, 'maxAmount');
67
53
  const { formatted: minAmount } = getFormatted(stat, 'minAmount');
68
- const visibleEvidenceMarkers = filterStrategyEvidenceMarkers({
69
- markers:
70
- evidenceTimeline.status === 'verified' ? evidenceTimeline.markers : [],
71
- showParity,
72
- showRecommendations,
73
- });
74
- const markerColor = (type: StrategyEvidenceMarkerType) =>
75
- chart.color(STRATEGY_EVIDENCE_MARKER_PRESENTATION[type].color);
76
- const markerDash = (type: StrategyEvidenceMarkerType) => {
77
- if (type === 'L') return '7 4';
78
- if (type === 'P' || type === 'R') return '2 5';
79
- return '3 5';
80
- };
81
-
82
54
  return (
83
55
  <Box w="100%" minW="600px" h={height} display="flex" flexDirection="column">
84
- <Flex minH="32px" px={2} justify="flex-end" align="center" flexShrink={0}>
85
- <StrategyEvidencePopover
86
- timeline={evidenceTimeline}
87
- showParity={showParity}
88
- showRecommendations={showRecommendations}
89
- onShowParityChange={setShowParity}
90
- onShowRecommendationsChange={setShowRecommendations}
91
- />
92
- </Flex>
93
56
  <Box flex="1" minH={0} pr={2}>
94
57
  {orderLog.length ? (
95
58
  <ResponsiveContainer width="100%" height="100%">
@@ -126,24 +89,6 @@ export const RuntimeStrategyChart = ({
126
89
  position: 'bottom',
127
90
  }}
128
91
  />
129
- {visibleEvidenceMarkers.map((marker, index) => (
130
- <ReferenceLine
131
- key={marker.id}
132
- x={marker.timestamp}
133
- stroke={markerColor(marker.type)}
134
- strokeDasharray={markerDash(marker.type)}
135
- strokeWidth={1.5}
136
- label={{
137
- value: marker.type,
138
- fill: markerColor(marker.type),
139
- fontSize: 10,
140
- fontWeight: 600,
141
- offset: 6,
142
- position:
143
- index % 2 === 0 ? 'insideTopRight' : 'insideTopLeft',
144
- }}
145
- />
146
- ))}
147
92
  <TimeSeriesXAxis
148
93
  startTimestamp={startTimestamp}
149
94
  endTimestamp={endTimestamp}
@@ -30,7 +30,7 @@ export const RuntimeStrategyConfigDrawer = ({
30
30
  <Drawer.Content bg="gray.950">
31
31
  <Drawer.Header>
32
32
  <Drawer.Title>
33
- {strategy.strategyName} release v{strategy.releaseVersion}
33
+ {strategy.strategyName} version v{strategy.version}
34
34
  </Drawer.Title>
35
35
  <Drawer.CloseTrigger asChild>
36
36
  <CloseButton size="sm" />
@@ -38,8 +38,8 @@ export const RuntimeStrategyConfigDrawer = ({
38
38
  </Drawer.Header>
39
39
  <Drawer.Body display="flex" flexDirection="column" gap={4}>
40
40
  <Text color="gray.400">
41
- Published releases are immutable. Roll out a new release to change
42
- this configuration.
41
+ This configuration is read-only here. Change it in
42
+ tradejs.config.ts and deploy a new Project image.
43
43
  </Text>
44
44
  <Textarea
45
45
  value={JSON.stringify(strategy.config, null, 2)}
@@ -1,25 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import {
3
- deleteRuntimeDeployment,
4
- getRuntimeDeployment,
5
- } from '@tradejs/infra/runtimeDeployments';
6
- import { getCurrentUserName } from '#app/lib/currentUser';
7
-
8
- export const DELETE = async (
9
- _request: Request,
10
- { params }: { params: Promise<{ deploymentId: string }> },
11
- ) => {
12
- const userName = await getCurrentUserName();
13
- if (!userName) {
14
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
15
- }
16
- const { deploymentId } = await params;
17
- if (!(await getRuntimeDeployment(userName, deploymentId))) {
18
- return NextResponse.json(
19
- { error: 'Deployment not found' },
20
- { status: 404 },
21
- );
22
- }
23
- await deleteRuntimeDeployment(userName, deploymentId);
24
- return NextResponse.json({ deleted: true });
25
- };
@@ -1,261 +0,0 @@
1
- 'use client';
2
-
3
- import {
4
- Badge,
5
- Box,
6
- Button,
7
- Checkbox,
8
- Flex,
9
- Popover,
10
- Portal,
11
- Text,
12
- } from '@chakra-ui/react';
13
- import type {
14
- StrategyEvidenceMarker,
15
- StrategyEvidenceMarkerType,
16
- StrategyEvidenceTimeline,
17
- } from '@tradejs/types';
18
- import { formatTimeSeriesTooltipTimestamp } from '#app/lib/timeSeriesChart';
19
-
20
- export const STRATEGY_EVIDENCE_MARKER_PRESENTATION: Record<
21
- StrategyEvidenceMarkerType,
22
- { name: string; color: string; optional: boolean }
23
- > = {
24
- G: { name: 'Composition / gate', color: 'purple.400', optional: false },
25
- L: { name: 'MAX_LOSS_VALUE', color: 'orange.400', optional: false },
26
- E: { name: 'Evidence boundary', color: 'teal.400', optional: false },
27
- D: { name: 'Deployment', color: 'blue.400', optional: false },
28
- P: { name: 'Runtime parity', color: 'cyan.400', optional: true },
29
- R: { name: 'Recommendation', color: 'pink.400', optional: true },
30
- };
31
-
32
- const MARKER_TYPES = Object.keys(
33
- STRATEGY_EVIDENCE_MARKER_PRESENTATION,
34
- ) as StrategyEvidenceMarkerType[];
35
-
36
- export const filterStrategyEvidenceMarkers = ({
37
- markers,
38
- showParity,
39
- showRecommendations,
40
- }: {
41
- markers: StrategyEvidenceMarker[];
42
- showParity: boolean;
43
- showRecommendations: boolean;
44
- }) =>
45
- markers.filter(
46
- (marker) =>
47
- (marker.type !== 'P' || showParity) &&
48
- (marker.type !== 'R' || showRecommendations),
49
- );
50
-
51
- const statusColor = (status: StrategyEvidenceTimeline['status']) => {
52
- if (status === 'verified') return 'teal';
53
- if (status === 'invalid') return 'red';
54
- return 'orange';
55
- };
56
-
57
- const uniqueProvenance = (markers: StrategyEvidenceMarker[]) => {
58
- const seen = new Set<string>();
59
- return markers.filter((marker) => {
60
- const key = `${marker.artifactId}:${marker.artifactSha256}`;
61
- if (seen.has(key)) return false;
62
- seen.add(key);
63
- return true;
64
- });
65
- };
66
-
67
- const FilterCheckbox = ({
68
- checked,
69
- label,
70
- onChange,
71
- }: {
72
- checked: boolean;
73
- label: string;
74
- onChange: (checked: boolean) => void;
75
- }) => (
76
- <Checkbox.Root
77
- size="sm"
78
- checked={checked}
79
- onCheckedChange={({ checked: nextChecked }) =>
80
- onChange(nextChecked === true)
81
- }
82
- >
83
- <Checkbox.HiddenInput />
84
- <Checkbox.Control>
85
- <Checkbox.Indicator />
86
- </Checkbox.Control>
87
- <Checkbox.Label>{label}</Checkbox.Label>
88
- </Checkbox.Root>
89
- );
90
-
91
- export const StrategyEvidencePopover = ({
92
- timeline,
93
- showParity,
94
- showRecommendations,
95
- onShowParityChange,
96
- onShowRecommendationsChange,
97
- }: {
98
- timeline: StrategyEvidenceTimeline;
99
- showParity: boolean;
100
- showRecommendations: boolean;
101
- onShowParityChange: (checked: boolean) => void;
102
- onShowRecommendationsChange: (checked: boolean) => void;
103
- }) => {
104
- const provenance =
105
- timeline.status === 'verified' ? uniqueProvenance(timeline.markers) : [];
106
-
107
- return (
108
- <Popover.Root positioning={{ placement: 'bottom-end' }} lazyMount>
109
- <Popover.Trigger asChild>
110
- <Button
111
- size="xs"
112
- variant="outline"
113
- aria-label={`Evidence: ${timeline.status}`}
114
- >
115
- Evidence
116
- <Badge size="xs" colorPalette={statusColor(timeline.status)}>
117
- {timeline.status === 'verified'
118
- ? `${timeline.markers.length}`
119
- : timeline.status}
120
- </Badge>
121
- </Button>
122
- </Popover.Trigger>
123
- <Portal>
124
- <Popover.Positioner>
125
- <Popover.Content width="min(430px, calc(100vw - 24px))">
126
- <Popover.Arrow />
127
- <Popover.Body>
128
- <Flex direction="column" gap={3}>
129
- <Box>
130
- <Text fontWeight="semibold">Immutable evidence</Text>
131
- <Text fontSize="xs" color="gray.500">
132
- {timeline.status === 'verified'
133
- ? timeline.observedFrom == null
134
- ? 'Verified artifact; no observation boundary supplied.'
135
- : `Verified from ${formatTimeSeriesTooltipTimestamp(
136
- timeline.observedFrom,
137
- )}.`
138
- : timeline.status === 'invalid'
139
- ? 'Evidence failed checksum, identity, or structure verification. No markers are rendered.'
140
- : timeline.status === 'not_attached'
141
- ? 'The runtime release is published, but no checksum-verified research artifact is attached yet.'
142
- : 'No checksum-verified evidence artifact is available. No mutable fallback is used.'}
143
- </Text>
144
- </Box>
145
-
146
- <Box>
147
- <Text fontSize="xs" fontWeight="semibold" mb={1}>
148
- Legend
149
- </Text>
150
- <Flex wrap="wrap" gapX={3} gapY={1}>
151
- {MARKER_TYPES.map((type) => (
152
- <Flex key={type} align="center" gap={1}>
153
- <Text
154
- color={
155
- STRATEGY_EVIDENCE_MARKER_PRESENTATION[type].color
156
- }
157
- fontWeight="bold"
158
- fontSize="xs"
159
- >
160
- {type}
161
- </Text>
162
- <Text fontSize="xs" color="gray.500">
163
- {STRATEGY_EVIDENCE_MARKER_PRESENTATION[type].name}
164
- </Text>
165
- </Flex>
166
- ))}
167
- </Flex>
168
- </Box>
169
-
170
- <Box>
171
- <Text fontSize="xs" fontWeight="semibold" mb={1}>
172
- Optional markers
173
- </Text>
174
- <Flex gap={4}>
175
- <FilterCheckbox
176
- checked={showParity}
177
- label="Parity (P)"
178
- onChange={onShowParityChange}
179
- />
180
- <FilterCheckbox
181
- checked={showRecommendations}
182
- label="Recommendations (R)"
183
- onChange={onShowRecommendationsChange}
184
- />
185
- </Flex>
186
- </Box>
187
-
188
- <Box>
189
- <Text fontSize="xs" fontWeight="semibold" mb={1}>
190
- Events and provenance
191
- </Text>
192
- {timeline.status === 'verified' && timeline.markers.length ? (
193
- <Flex
194
- direction="column"
195
- gap={2}
196
- maxH="220px"
197
- overflowY="auto"
198
- >
199
- {timeline.markers.map((marker) => (
200
- <Box key={marker.id}>
201
- <Flex align="baseline" gap={2}>
202
- <Text
203
- color={
204
- STRATEGY_EVIDENCE_MARKER_PRESENTATION[
205
- marker.type
206
- ].color
207
- }
208
- fontWeight="bold"
209
- fontSize="xs"
210
- >
211
- {marker.type}
212
- </Text>
213
- <Text fontSize="xs" fontWeight="semibold">
214
- {marker.label}
215
- </Text>
216
- <Text fontSize="xs" color="gray.500">
217
- {formatTimeSeriesTooltipTimestamp(
218
- marker.timestamp,
219
- )}
220
- </Text>
221
- </Flex>
222
- <Text fontSize="xs" color="gray.500">
223
- {marker.summary}
224
- </Text>
225
- </Box>
226
- ))}
227
- </Flex>
228
- ) : (
229
- <Text fontSize="xs" color="gray.500">
230
- No verified events in the selected window.
231
- </Text>
232
- )}
233
- </Box>
234
-
235
- {provenance.length ? (
236
- <Box>
237
- <Text fontSize="xs" fontWeight="semibold" mb={1}>
238
- Artifact provenance
239
- </Text>
240
- {provenance.map((marker) => (
241
- <Text
242
- key={`${marker.artifactId}:${marker.artifactSha256}`}
243
- fontSize="xs"
244
- color="gray.500"
245
- fontFamily="mono"
246
- title={marker.artifactSha256}
247
- >
248
- {marker.artifactId} ·{' '}
249
- {marker.artifactSha256.slice(0, 12)}
250
- </Text>
251
- ))}
252
- </Box>
253
- ) : null}
254
- </Flex>
255
- </Popover.Body>
256
- </Popover.Content>
257
- </Popover.Positioner>
258
- </Portal>
259
- </Popover.Root>
260
- );
261
- };