@crvouga/atlas 0.10.2 → 0.12.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.
Files changed (114) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +7 -0
  3. package/dist/reports/src/catalog-health.d.ts +142 -0
  4. package/dist/reports/src/catalog-health.js +257 -0
  5. package/dist/reports/src/test-catalog.d.ts +8 -0
  6. package/dist/reports/src/test-catalog.js +5 -1
  7. package/dist/schema/src/api.d.ts +79 -0
  8. package/dist/schema/src/api.js +50 -0
  9. package/dist/server/src/app.js +9 -0
  10. package/dist/server/src/llms.js +1 -0
  11. package/dist/server/src/openapi.js +18 -2
  12. package/dist/server/src/settings-page.d.ts +4 -4
  13. package/dist/server/src/settings-page.js +17 -78
  14. package/docs/reference/http-api.md +34 -0
  15. package/docs/reference/reports.md +224 -0
  16. package/docs/reference/schema.md +10 -0
  17. package/docs/reference/server.md +4 -4
  18. package/docs/server.md +10 -0
  19. package/docs/ui.md +142 -0
  20. package/docs/workspaces.md +130 -0
  21. package/llms-full.txt +322 -0
  22. package/llms.txt +2 -0
  23. package/package.json +17 -10
  24. package/packages/schema/schemas/catalog-health.schema.json +400 -0
  25. package/packages/server/openapi.json +596 -0
  26. package/packages/visualizer/src/admin/SettingsPage.tsx +456 -0
  27. package/packages/visualizer/src/admin/main.tsx +11 -0
  28. package/packages/visualizer/src/app/AppShell.tsx +62 -38
  29. package/packages/visualizer/src/app/gazetteer.tsx +90 -83
  30. package/packages/visualizer/src/app/router.tsx +33 -15
  31. package/packages/visualizer/src/components/ActivityIndicator.tsx +4 -5
  32. package/packages/visualizer/src/components/DataIssues.tsx +36 -54
  33. package/packages/visualizer/src/components/Header.tsx +197 -125
  34. package/packages/visualizer/src/components/Legend.tsx +13 -41
  35. package/packages/visualizer/src/components/ProjectPicker.tsx +61 -26
  36. package/packages/visualizer/src/components/Screen.tsx +80 -71
  37. package/packages/visualizer/src/components/SpecLogo.tsx +15 -0
  38. package/packages/visualizer/src/components/Status.tsx +20 -34
  39. package/packages/visualizer/src/components/labels.tsx +8 -3
  40. package/packages/visualizer/src/components/spec-logos/APACHE-2.0.txt +206 -0
  41. package/packages/visualizer/src/components/spec-logos/CAMUNDA-MIT.txt +21 -0
  42. package/packages/visualizer/src/components/spec-logos/FILE-ICONS-ISC.txt +13 -0
  43. package/packages/visualizer/src/components/spec-logos/README.md +23 -0
  44. package/packages/visualizer/src/components/spec-logos/SIMPLE-ICONS-CC0.txt +30 -0
  45. package/packages/visualizer/src/components/spec-logos/alloy.svg +1 -0
  46. package/packages/visualizer/src/components/spec-logos/dmn.svg +3 -0
  47. package/packages/visualizer/src/components/spec-logos/gherkin.svg +1 -0
  48. package/packages/visualizer/src/components/spec-logos/openslo.svg +63 -0
  49. package/packages/visualizer/src/components/spec-logos/quint.svg +1 -0
  50. package/packages/visualizer/src/evidence/artifacts.tsx +263 -110
  51. package/packages/visualizer/src/evidence/chart-source.tsx +18 -0
  52. package/packages/visualizer/src/evidence/check-detail.tsx +283 -139
  53. package/packages/visualizer/src/evidence/decision-table.tsx +35 -26
  54. package/packages/visualizer/src/evidence/document-page.tsx +51 -29
  55. package/packages/visualizer/src/evidence/feature-page.tsx +121 -53
  56. package/packages/visualizer/src/evidence/marks.tsx +112 -79
  57. package/packages/visualizer/src/evidence/model-page.tsx +77 -46
  58. package/packages/visualizer/src/evidence/model-trace.tsx +55 -35
  59. package/packages/visualizer/src/evidence/overview.tsx +137 -69
  60. package/packages/visualizer/src/evidence/recording-page.tsx +70 -31
  61. package/packages/visualizer/src/evidence/recording.tsx +135 -68
  62. package/packages/visualizer/src/evidence/snapshot.tsx +99 -43
  63. package/packages/visualizer/src/evidence/source-text.tsx +33 -0
  64. package/packages/visualizer/src/evidence/sources-panel.tsx +138 -144
  65. package/packages/visualizer/src/evidence/spec-header.tsx +52 -36
  66. package/packages/visualizer/src/evidence/spec-index.tsx +61 -32
  67. package/packages/visualizer/src/evidence/spec-page.tsx +56 -25
  68. package/packages/visualizer/src/evidence/test-filter.tsx +38 -14
  69. package/packages/visualizer/src/explorer/Explorer.tsx +688 -261
  70. package/packages/visualizer/src/explorer/ExplorerIndex.tsx +136 -68
  71. package/packages/visualizer/src/explorer/RunsView.tsx +307 -94
  72. package/packages/visualizer/src/explorer/TestDetail.tsx +393 -118
  73. package/packages/visualizer/src/explorer/parts.tsx +35 -13
  74. package/packages/visualizer/src/find/Finder.tsx +106 -90
  75. package/packages/visualizer/src/find/open.ts +17 -20
  76. package/packages/visualizer/src/live/test-progress.tsx +60 -22
  77. package/packages/visualizer/src/looks/Stage.tsx +223 -110
  78. package/packages/visualizer/src/main.tsx +9 -11
  79. package/packages/visualizer/src/map/ChartMap.tsx +125 -69
  80. package/packages/visualizer/src/map/EventEdge.tsx +59 -37
  81. package/packages/visualizer/src/map/map.module.css +12 -546
  82. package/packages/visualizer/src/map/nodes.tsx +124 -70
  83. package/packages/visualizer/src/reachable.test.ts +3 -2
  84. package/packages/visualizer/src/spec-kind.ts +2 -2
  85. package/packages/visualizer/src/ui/grammars/ALLOY-MIT.txt +21 -0
  86. package/packages/visualizer/src/ui/grammars/QUINT-APACHE-2.0.txt +206 -0
  87. package/packages/visualizer/src/ui/grammars/README.md +14 -0
  88. package/packages/visualizer/src/ui/grammars/alloy.json +71 -0
  89. package/packages/visualizer/src/ui/grammars/quint.json +152 -0
  90. package/packages/visualizer/src/ui/highlight.ts +50 -0
  91. package/packages/visualizer/src/ui/theme.tsx +109 -0
  92. package/packages/visualizer/src/views/ChartPage.tsx +46 -26
  93. package/packages/visualizer/src/views/ChartTests.tsx +59 -22
  94. package/packages/visualizer/src/views/ProductPage.tsx +16 -15
  95. package/packages/visualizer/src/views/SidePanel.tsx +241 -131
  96. package/packages/visualizer/src/app/gazetteer.module.css +0 -107
  97. package/packages/visualizer/src/components/ActivityIndicator.module.css +0 -4
  98. package/packages/visualizer/src/components/DataIssues.module.css +0 -136
  99. package/packages/visualizer/src/components/Header.module.css +0 -114
  100. package/packages/visualizer/src/components/Legend.module.css +0 -84
  101. package/packages/visualizer/src/components/Screen.module.css +0 -312
  102. package/packages/visualizer/src/components/Status.module.css +0 -116
  103. package/packages/visualizer/src/components/icons.tsx +0 -156
  104. package/packages/visualizer/src/evidence/artifacts.module.css +0 -213
  105. package/packages/visualizer/src/evidence/evidence.module.css +0 -907
  106. package/packages/visualizer/src/evidence/recording.module.css +0 -460
  107. package/packages/visualizer/src/evidence/sources.module.css +0 -168
  108. package/packages/visualizer/src/evidence/spec-snapshot.tsx +0 -26
  109. package/packages/visualizer/src/explorer/explorer.module.css +0 -1238
  110. package/packages/visualizer/src/find/Finder.module.css +0 -168
  111. package/packages/visualizer/src/looks/stage.module.css +0 -300
  112. package/packages/visualizer/src/styles/global.css +0 -105
  113. package/packages/visualizer/src/styles/tokens.css +0 -80
  114. package/packages/visualizer/src/views/views.module.css +0 -546
@@ -1,40 +1,46 @@
1
- import { MiniMap, ReactFlow, ReactFlowProvider, useReactFlow, type Node, type ReactFlowInstance, type Viewport } from '@xyflow/react';
1
+ import { ActionIcon, Alert, Box, Button, Center, Group, Loader, Paper, Stack, Text, useMantineTheme } from '@mantine/core';
2
+ import {
3
+ Background,
4
+ MiniMap,
5
+ Panel,
6
+ ReactFlow,
7
+ ReactFlowProvider,
8
+ useReactFlow,
9
+ type Node,
10
+ type ReactFlowInstance,
11
+ type Viewport
12
+ } from '@xyflow/react';
2
13
  import { useCallback, useMemo, useRef } from 'react';
3
-
4
- import type { AtlasView, ItemStatus } from '../data/model';
5
- import { FitIcon, MinusIcon, PlusIcon } from '../components/icons';
14
+ import { IconArrowsMaximize as FitIcon, IconMinus as MinusIcon, IconPlus as PlusIcon } from '@tabler/icons-react';
6
15
  import { Legend } from '../components/Legend';
16
+ import type { AtlasView, ItemStatus } from '../data/model';
7
17
  import { chartGraph } from '../layout/graph';
8
18
  import type { ChartLayout } from '../layout/layout';
9
19
  import { useChartLayout } from '../layout/use-chart-layout';
10
20
  import { useUiStore } from '../state/ui-store';
11
21
  import { EdgeMarkers, edgeTypes, type EventEdgeType } from './EventEdge';
12
- import styles from './map.module.css';
13
- import { nodeTypes, type ChartLinkNodeType, type ContextNodeType, type GroupNodeType, type ScreenNodeType, type SummaryNodeType } from './nodes';
14
22
 
23
+ import {
24
+ nodeTypes,
25
+ type ChartLinkNodeType,
26
+ type ContextNodeType,
27
+ type GroupNodeType,
28
+ type ScreenNodeType,
29
+ type SummaryNodeType
30
+ } from './nodes';
15
31
  type MapNode = ScreenNodeType | GroupNodeType | ChartLinkNodeType | ContextNodeType | SummaryNodeType;
16
-
17
- export type MapSelection = { screen?: string; event?: string; journey?: string };
18
-
19
- const MINIMAP_COLOR: Record<ItemStatus, string> = {
20
- passed: '#86efac',
21
- flaky: '#fdba74',
22
- failed: '#fca5a5',
23
- 'not-reached': '#e2e8f0',
24
- 'not-yet-run': '#e2e8f0',
25
- 'spec-only': '#ddd6fe'
32
+ export type MapSelection = {
33
+ screen?: string;
34
+ event?: string;
35
+ journey?: string;
26
36
  };
27
-
28
37
  const START_ZOOM = 0.7;
29
-
30
38
  function fitPadding() {
31
- const narrow = typeof window !== 'undefined' && window.matchMedia('(max-width: 900px)').matches;
39
+ const narrow = typeof window !== 'undefined' && window.matchMedia('(max-width: 62em)').matches;
32
40
  if (narrow) return { top: '180px', right: '64px', bottom: '16px', left: '16px' } as const;
33
41
  return { top: '16px', right: '72px', bottom: '120px', left: '16px' } as const;
34
42
  }
35
-
36
43
  const reducedMotion = () => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
37
-
38
44
  function useFlowElements(view: AtlasView, chartId: string, layout: ChartLayout | undefined, selection: MapSelection) {
39
45
  return useMemo(() => {
40
46
  if (!layout) return { nodes: [] as MapNode[], edges: [] as EventEdgeType[] };
@@ -61,7 +67,6 @@ function useFlowElements(view: AtlasView, chartId: string, layout: ChartLayout |
61
67
  const state = view.states.get(id);
62
68
  return Boolean(state?.children.some((child) => coversJourney(child)));
63
69
  };
64
-
65
70
  const nodes: MapNode[] = layout.nodes.flatMap((n): MapNode[] => {
66
71
  const state = view.states.get(n.id);
67
72
  if (!state) return [];
@@ -81,9 +86,18 @@ function useFlowElements(view: AtlasView, chartId: string, layout: ChartLayout |
81
86
  case 'parallel':
82
87
  return [{ ...base, type: 'group', zIndex: 0, data: { state, variant: n.kind, dimmed: false } }];
83
88
  case 'chart-link':
84
- return [{ ...base, type: 'chartLink', zIndex: 2, data: { state, chart: state.childChartId ? view.charts.get(state.childChartId) ?? null : null, dimmed: dim(n.id) } }];
89
+ return [
90
+ {
91
+ ...base,
92
+ type: 'chartLink',
93
+ zIndex: 2,
94
+ data: { state, chart: state.childChartId ? (view.charts.get(state.childChartId) ?? null) : null, dimmed: dim(n.id) }
95
+ }
96
+ ];
85
97
  case 'context':
86
- return [{ ...base, type: 'context', zIndex: 2, data: { state, chart: view.charts.get(state.chartId) ?? null, dimmed: dim(n.id) } }];
98
+ return [
99
+ { ...base, type: 'context', zIndex: 2, data: { state, chart: view.charts.get(state.chartId) ?? null, dimmed: dim(n.id) } }
100
+ ];
87
101
  case 'summary':
88
102
  return [
89
103
  {
@@ -97,7 +111,6 @@ function useFlowElements(view: AtlasView, chartId: string, layout: ChartLayout |
97
111
  return [{ ...base, type: 'screen', zIndex: 2, data: { state, chartId, selected: selection.screen === n.id, dimmed: dim(n.id) } }];
98
112
  }
99
113
  });
100
-
101
114
  const edges: EventEdgeType[] = layout.edges.flatMap((e): EventEdgeType[] => {
102
115
  const transition = e.transitionId ? (view.transitions.get(e.transitionId) ?? null) : null;
103
116
  if (!transition && !e.bundle) return [];
@@ -111,33 +124,55 @@ function useFlowElements(view: AtlasView, chartId: string, layout: ChartLayout |
111
124
  type: 'event',
112
125
  zIndex: 1,
113
126
  selectable: false,
114
- data: { placed: e, transition, bundle: e.bundle, chartId, selected: Boolean(e.transitionId) && selection.event === e.transitionId, step, dimmed }
127
+ data: {
128
+ placed: e,
129
+ transition,
130
+ bundle: e.bundle,
131
+ chartId,
132
+ selected: Boolean(e.transitionId) && selection.event === e.transitionId,
133
+ step,
134
+ dimmed
135
+ }
115
136
  }
116
137
  ];
117
138
  });
118
139
  return { nodes, edges };
119
140
  }, [view, chartId, layout, selection.screen, selection.event, selection.journey]);
120
141
  }
121
-
122
142
  function ZoomControls() {
123
143
  const flow = useReactFlow();
124
144
  const duration = reducedMotion() ? 0 : 250;
125
145
  return (
126
- <div className={styles.zoom} role="group" aria-label="Zoom">
127
- <button type="button" className={styles.zoomButton} onClick={() => void flow.zoomIn({ duration })} aria-label="Zoom in">
128
- <PlusIcon size={16} />
129
- </button>
130
- <button type="button" className={styles.zoomButton} onClick={() => void flow.zoomOut({ duration })} aria-label="Zoom out">
131
- <MinusIcon size={16} />
132
- </button>
133
- <button type="button" className={styles.zoomButton} onClick={() => void flow.fitView({ duration, padding: fitPadding() })} aria-label="Fit the whole map">
134
- <FitIcon size={16} />
135
- </button>
136
- </div>
146
+ <Paper shadow="xs" p="xs">
147
+ <Group gap="xs" role="group" aria-label="Zoom">
148
+ <ActionIcon variant="default" onClick={() => void flow.zoomIn({ duration })} aria-label="Zoom in">
149
+ <PlusIcon aria-hidden="true" size={16} />
150
+ </ActionIcon>
151
+ <ActionIcon variant="default" onClick={() => void flow.zoomOut({ duration })} aria-label="Zoom out">
152
+ <MinusIcon aria-hidden="true" size={16} />
153
+ </ActionIcon>
154
+ <ActionIcon variant="default" onClick={() => void flow.fitView({ duration, padding: fitPadding() })} aria-label="Fit the whole map">
155
+ <FitIcon aria-hidden="true" size={16} />
156
+ </ActionIcon>
157
+ </Group>
158
+ </Paper>
137
159
  );
138
160
  }
139
161
 
140
- function Flow({ view, chartId, scopeKey, layout, selection }: { view: AtlasView; chartId: string; scopeKey: string; layout: ChartLayout; selection: MapSelection }) {
162
+ function Flow({
163
+ view,
164
+ chartId,
165
+ scopeKey,
166
+ layout,
167
+ selection
168
+ }: {
169
+ view: AtlasView;
170
+ chartId: string;
171
+ scopeKey: string;
172
+ layout: ChartLayout;
173
+ selection: MapSelection;
174
+ }) {
175
+ const theme = useMantineTheme();
141
176
  const { nodes, edges } = useFlowElements(view, chartId, layout, selection);
142
177
  const saved = useRef<Viewport | undefined>(useUiStore.getState().viewports[scopeKey]);
143
178
  const saveViewport = useUiStore((s) => s.saveViewport);
@@ -155,7 +190,11 @@ function Flow({ view, chartId, scopeKey, layout, selection }: { view: AtlasView;
155
190
  const focus = layout.nodes.find((n) => n.id === selection.screen);
156
191
  if (focus) {
157
192
  const zoom = START_ZOOM;
158
- flow.setViewport({ x: width / 2 - (focus.absX + focus.width / 2) * zoom, y: height / 2 - (focus.absY + focus.height / 2) * zoom, zoom });
193
+ flow.setViewport({
194
+ x: width / 2 - (focus.absX + focus.width / 2) * zoom,
195
+ y: height / 2 - (focus.absY + focus.height / 2) * zoom,
196
+ zoom
197
+ });
159
198
  return;
160
199
  }
161
200
  const start = layout.nodes.find((n) => n.id === view.charts.get(chartId)?.initial) ?? layout.nodes[0]!;
@@ -182,71 +221,88 @@ function Flow({ view, chartId, scopeKey, layout, selection }: { view: AtlasView;
182
221
  edgesFocusable={false}
183
222
  elementsSelectable={false}
184
223
  onlyRenderVisibleElements
185
- className={styles.flow}
186
224
  aria-label="Map of screens and events. Drag to move, scroll or pinch to zoom."
187
225
  >
188
226
  <EdgeMarkers />
189
- <ZoomControls />
190
- <div className={styles.legendSlot}>
191
- <Legend showTime={hasTime} />
192
- </div>
227
+ <Background color={theme.colors.gray[3]} />
228
+ <Panel position="top-right">
229
+ <ZoomControls />
230
+ </Panel>
231
+ <Panel position="bottom-left">
232
+ <Paper p="xs" shadow="xs" maw="70vw">
233
+ <Legend showTime={hasTime} />
234
+ </Paper>
235
+ </Panel>
193
236
  <MiniMap<MapNode>
194
237
  pannable
195
238
  zoomable
196
239
  ariaLabel="Overview of the whole map"
197
- className={styles.minimap}
198
240
  nodeColor={(n: Node) => {
199
241
  if (n.type === 'group') return 'transparent';
200
- const data = n.data as { status?: ItemStatus; state?: { status: ItemStatus } };
201
- return MINIMAP_COLOR[data.status ?? data.state?.status ?? 'spec-only'];
242
+ const data = n.data as {
243
+ status?: ItemStatus;
244
+ state?: {
245
+ status: ItemStatus;
246
+ };
247
+ };
248
+ const status = data.status ?? data.state?.status;
249
+ return theme.colors[status === 'passed' ? 'green' : status === 'failed' ? 'red' : status === 'flaky' ? 'orange' : 'gray'][3];
202
250
  }}
203
- nodeStrokeColor={(n: Node) => (n.type === 'group' ? '#ddd6fe' : 'transparent')}
251
+ nodeStrokeColor={(n: Node) => (n.type === 'group' ? theme.colors.gray[3] : 'transparent')}
204
252
  nodeBorderRadius={8}
205
- maskColor="rgba(248, 250, 252, 0.7)"
253
+ maskColor="var(--mantine-color-gray-0)"
206
254
  />
207
255
  </ReactFlow>
208
256
  );
209
257
  }
210
-
211
258
  function MapSkeleton({ label }: { label: string }) {
212
259
  return (
213
- <div className={styles.skeleton} role="status">
214
- <div className={styles.skeletonRow}>
215
- {Array.from({ length: 5 }, (_, i) => (
216
- <span key={i} className={styles.skeletonPhone} style={{ animationDelay: `${i * 120}ms` }} />
217
- ))}
218
- </div>
219
- <span className={styles.skeletonLabel}>{label}</span>
220
- </div>
260
+ <Center h="60vh">
261
+ <Stack align="center">
262
+ <Loader />
263
+ <Text role="status" c="dimmed">
264
+ {label}
265
+ </Text>
266
+ </Stack>
267
+ </Center>
221
268
  );
222
269
  }
223
-
224
270
  /** One chart's map. Positions come from the cached layout, statuses and media from the view. */
225
- export function ChartMap({ view, chartId, selection, group }: { view: AtlasView; chartId: string; selection: MapSelection; group?: string }) {
271
+ export function ChartMap({
272
+ view,
273
+ chartId,
274
+ selection,
275
+ group
276
+ }: {
277
+ view: AtlasView;
278
+ chartId: string;
279
+ selection: MapSelection;
280
+ group?: string;
281
+ }) {
226
282
  const input = useMemo(() => chartGraph(view, chartId, group ?? null), [view, chartId, group]);
227
283
  const chart = view.charts.get(chartId);
228
284
  const layout = useChartLayout(input, input?.scoped ? null : (chart?.layout ?? null));
229
285
  if (layout.error) {
230
286
  return (
231
- <div className={styles.mapMessage} role="alert">
287
+ <Alert color="red" title="Map unavailable">
232
288
  <p>The map for this chart couldn’t be laid out.</p>
233
- <button type="button" className={styles.retryButton} onClick={() => void layout.refetch()}>
234
- Try again
235
- </button>
236
- </div>
289
+ <Button onClick={() => void layout.refetch()}>Try again</Button>
290
+ </Alert>
237
291
  );
238
292
  }
239
293
  if (!layout.data) return <MapSkeleton label={`Laying out ${chart?.name ?? 'the map'}…`} />;
240
294
  if (layout.data.nodes.length === 0) {
241
295
  return (
242
- <div className={styles.mapMessage}>
296
+ <Alert>
243
297
  <p>This chart has no states yet.</p>
244
- </div>
298
+ </Alert>
245
299
  );
246
300
  }
247
301
  return (
248
302
  <ReactFlowProvider>
249
- <Flow view={view} chartId={chartId} scopeKey={`${chartId}:${input?.focus ?? ''}`} layout={layout.data} selection={selection} />
303
+ <Box h="70vh" mih="24rem">
304
+ <Flow view={view} chartId={chartId} scopeKey={`${chartId}:${input?.focus ?? ''}`} layout={layout.data} selection={selection} />
305
+ </Box>
250
306
  </ReactFlowProvider>
251
307
  );
252
308
  }
@@ -1,14 +1,13 @@
1
+ import { Badge, Text } from '@mantine/core';
1
2
  import { Link } from '@tanstack/react-router';
2
3
  import { EdgeLabelRenderer, type Edge, type EdgeProps } from '@xyflow/react';
3
4
  import { memo } from 'react';
4
-
5
- import { STATUS_LABEL, type ItemStatus, type TransitionView } from '../data/model';
6
5
  import { KIND_LABEL, KindIcon } from '../components/labels';
6
+ import { STATUS_LABEL, type ItemStatus, type TransitionView } from '../data/model';
7
7
  import type { LayoutBundle } from '../layout/graph';
8
8
  import type { PlacedEdge } from '../layout/layout';
9
9
  import styles from './map.module.css';
10
10
  import { roundedPath } from './path';
11
-
12
11
  export type EventEdgeData = {
13
12
  placed: PlacedEdge;
14
13
  transition: TransitionView | null;
@@ -18,17 +17,13 @@ export type EventEdgeData = {
18
17
  step: number | null;
19
18
  dimmed: boolean;
20
19
  };
21
-
22
20
  export type EventEdgeType = Edge<EventEdgeData, 'event'>;
23
-
24
21
  const UNCHECKED = new Set(['not-reached', 'not-yet-run', 'spec-only']);
25
-
26
22
  function markerFor(status: ItemStatus, selected: boolean, step: number | null, kind: string) {
27
23
  if (selected || step !== null) return 'active';
28
24
  if (UNCHECKED.has(status)) return 'muted';
29
25
  return kind;
30
26
  }
31
-
32
27
  export const EventEdge = memo(function EventEdge({ data, id }: EdgeProps<EventEdgeType>) {
33
28
  if (!data) return null;
34
29
  const { placed, transition: t, bundle, chartId, selected, step, dimmed } = data;
@@ -47,17 +42,23 @@ export const EventEdge = memo(function EventEdge({ data, id }: EdgeProps<EventEd
47
42
  markerEnd={`url(#atlas-arrow-${unchecked ? 'muted' : 'user'})`}
48
43
  />
49
44
  <EdgeLabelRenderer>
50
- <Link
51
- to="/chart/$chartId"
52
- params={{ chartId }}
53
- search={(prev) => ({
54
- run: prev.run,
55
- journey: prev.journey,
56
- event: undefined,
57
- screen: bundle.screen ?? undefined,
58
- group: bundle.group ?? undefined
59
- })}
45
+ <Badge
46
+ renderRoot={(props) => (
47
+ <Link
48
+ {...props}
49
+ to="/chart/$chartId"
50
+ params={{ chartId }}
51
+ search={(prev) => ({
52
+ run: prev.run,
53
+ journey: prev.journey,
54
+ event: undefined,
55
+ screen: bundle.screen ?? undefined,
56
+ group: bundle.group ?? undefined
57
+ })}
58
+ />
59
+ )}
60
60
  className={`${styles.chip} nodrag nopan`}
61
+ variant="default"
61
62
  data-status={bundle.status}
62
63
  data-unchecked={unchecked}
63
64
  data-dimmed={dimmed}
@@ -65,8 +66,10 @@ export const EventEdge = memo(function EventEdge({ data, id }: EdgeProps<EventEd
65
66
  aria-label={`${bundle.label}. ${STATUS_LABEL[bundle.status]}.`}
66
67
  title={bundle.label}
67
68
  >
68
- <span className={styles.chipText}>{bundle.label}</span>
69
- </Link>
69
+ <Text span inherit truncate>
70
+ {bundle.label}
71
+ </Text>
72
+ </Badge>
70
73
  </EdgeLabelRenderer>
71
74
  </>
72
75
  );
@@ -89,11 +92,17 @@ export const EventEdge = memo(function EventEdge({ data, id }: EdgeProps<EventEd
89
92
  markerEnd={`url(#atlas-arrow-${markerFor(t.status, selected, step, t.kind)})`}
90
93
  />
91
94
  <EdgeLabelRenderer>
92
- <Link
93
- to="/chart/$chartId"
94
- params={{ chartId }}
95
- search={(prev) => ({ run: prev.run, event: t.id, journey: prev.journey })}
95
+ <Badge
96
+ renderRoot={(props) => (
97
+ <Link
98
+ {...props}
99
+ to="/chart/$chartId"
100
+ params={{ chartId }}
101
+ search={(prev) => ({ run: prev.run, event: t.id, journey: prev.journey })}
102
+ />
103
+ )}
96
104
  className={`${styles.chip} nodrag nopan`}
105
+ variant="default"
97
106
  data-kind={t.kind}
98
107
  data-status={t.status}
99
108
  data-unchecked={unchecked}
@@ -104,34 +113,47 @@ export const EventEdge = memo(function EventEdge({ data, id }: EdgeProps<EventEd
104
113
  aria-label={`${KIND_LABEL[t.kind]}: ${t.event}. ${STATUS_LABEL[t.status]}.${step !== null ? ` Step ${step} of the journey.` : ''}`}
105
114
  title={t.event}
106
115
  >
107
- {step !== null && <span className={styles.stepNo}>{step}</span>}
108
- <span className={styles.chipIcon}>
116
+ {step !== null && (
117
+ <Text span inherit fw={700}>
118
+ {step}
119
+ </Text>
120
+ )}
121
+ <Text span inherit>
109
122
  <KindIcon kind={t.kind} size={11} />
110
- </span>
111
- <span className={styles.chipText}>{t.event}</span>
112
- </Link>
123
+ </Text>
124
+ <Text span inherit truncate>
125
+ {t.event}
126
+ </Text>
127
+ </Badge>
113
128
  </EdgeLabelRenderer>
114
129
  </>
115
130
  );
116
131
  });
117
-
118
132
  export const edgeTypes = { event: EventEdge };
119
-
120
133
  export function EdgeMarkers() {
121
134
  const marker = (id: string, color: string) => (
122
- <marker key={id} id={`atlas-arrow-${id}`} viewBox="0 0 10 10" refX="8.5" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
135
+ <marker
136
+ key={id}
137
+ id={`atlas-arrow-${id}`}
138
+ viewBox="0 0 10 10"
139
+ refX="8.5"
140
+ refY="5"
141
+ markerWidth="7"
142
+ markerHeight="7"
143
+ orient="auto-start-reverse"
144
+ >
123
145
  <path d="M0,0.5 L9.5,5 L0,9.5 z" fill={color} />
124
146
  </marker>
125
147
  );
126
148
  return (
127
149
  <svg className={styles.markers} aria-hidden="true">
128
150
  <defs>
129
- {marker('user', 'var(--user)')}
130
- {marker('system', 'var(--system)')}
131
- {marker('time', 'var(--time)')}
132
- {marker('hand-off', 'var(--handoff)')}
133
- {marker('muted', 'var(--line-2)')}
134
- {marker('active', 'var(--place)')}
151
+ {marker('user', 'var(--mantine-color-blue-7)')}
152
+ {marker('system', 'var(--mantine-color-gray-6)')}
153
+ {marker('time', 'var(--mantine-color-gray-6)')}
154
+ {marker('hand-off', 'var(--mantine-color-dark-5)')}
155
+ {marker('muted', 'var(--mantine-color-gray-4)')}
156
+ {marker('active', 'var(--mantine-color-blue-7)')}
135
157
  </defs>
136
158
  </svg>
137
159
  );