@crvouga/atlas 0.10.1 → 0.11.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 (104) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +7 -0
  3. package/dist/reports/src/layer-merge.d.ts +5 -1
  4. package/dist/reports/src/layer-merge.js +17 -2
  5. package/dist/server/src/settings-page.d.ts +4 -4
  6. package/dist/server/src/settings-page.js +17 -78
  7. package/docs/agents.md +25 -0
  8. package/docs/reference/reports.md +2 -0
  9. package/docs/reference/server.md +4 -4
  10. package/docs/report-contract.md +3 -0
  11. package/docs/ui.md +142 -0
  12. package/docs/workspaces.md +130 -0
  13. package/llms-full.txt +306 -0
  14. package/llms.txt +2 -0
  15. package/package.json +17 -10
  16. package/packages/visualizer/src/admin/SettingsPage.tsx +456 -0
  17. package/packages/visualizer/src/admin/main.tsx +11 -0
  18. package/packages/visualizer/src/app/AppShell.tsx +62 -38
  19. package/packages/visualizer/src/app/gazetteer.tsx +90 -83
  20. package/packages/visualizer/src/app/router.tsx +33 -15
  21. package/packages/visualizer/src/components/ActivityIndicator.tsx +4 -5
  22. package/packages/visualizer/src/components/DataIssues.tsx +36 -54
  23. package/packages/visualizer/src/components/Header.tsx +197 -125
  24. package/packages/visualizer/src/components/Legend.tsx +13 -41
  25. package/packages/visualizer/src/components/ProjectPicker.tsx +61 -26
  26. package/packages/visualizer/src/components/Screen.tsx +80 -71
  27. package/packages/visualizer/src/components/SpecLogo.tsx +15 -0
  28. package/packages/visualizer/src/components/Status.tsx +20 -34
  29. package/packages/visualizer/src/components/labels.tsx +8 -3
  30. package/packages/visualizer/src/components/spec-logos/APACHE-2.0.txt +206 -0
  31. package/packages/visualizer/src/components/spec-logos/CAMUNDA-MIT.txt +21 -0
  32. package/packages/visualizer/src/components/spec-logos/FILE-ICONS-ISC.txt +13 -0
  33. package/packages/visualizer/src/components/spec-logos/README.md +23 -0
  34. package/packages/visualizer/src/components/spec-logos/SIMPLE-ICONS-CC0.txt +30 -0
  35. package/packages/visualizer/src/components/spec-logos/alloy.svg +1 -0
  36. package/packages/visualizer/src/components/spec-logos/dmn.svg +3 -0
  37. package/packages/visualizer/src/components/spec-logos/gherkin.svg +1 -0
  38. package/packages/visualizer/src/components/spec-logos/openslo.svg +63 -0
  39. package/packages/visualizer/src/components/spec-logos/quint.svg +1 -0
  40. package/packages/visualizer/src/evidence/artifacts.tsx +263 -110
  41. package/packages/visualizer/src/evidence/chart-source.tsx +18 -0
  42. package/packages/visualizer/src/evidence/check-detail.tsx +283 -139
  43. package/packages/visualizer/src/evidence/decision-table.tsx +35 -26
  44. package/packages/visualizer/src/evidence/document-page.tsx +51 -29
  45. package/packages/visualizer/src/evidence/feature-page.tsx +121 -53
  46. package/packages/visualizer/src/evidence/marks.tsx +112 -79
  47. package/packages/visualizer/src/evidence/model-page.tsx +77 -46
  48. package/packages/visualizer/src/evidence/model-trace.tsx +55 -35
  49. package/packages/visualizer/src/evidence/overview.tsx +137 -69
  50. package/packages/visualizer/src/evidence/recording-page.tsx +70 -31
  51. package/packages/visualizer/src/evidence/recording.tsx +135 -68
  52. package/packages/visualizer/src/evidence/snapshot.tsx +99 -43
  53. package/packages/visualizer/src/evidence/source-text.tsx +33 -0
  54. package/packages/visualizer/src/evidence/sources-panel.tsx +138 -144
  55. package/packages/visualizer/src/evidence/spec-header.tsx +52 -36
  56. package/packages/visualizer/src/evidence/spec-index.tsx +61 -32
  57. package/packages/visualizer/src/evidence/spec-page.tsx +56 -25
  58. package/packages/visualizer/src/evidence/test-filter.tsx +38 -14
  59. package/packages/visualizer/src/explorer/Explorer.tsx +688 -261
  60. package/packages/visualizer/src/explorer/ExplorerIndex.tsx +136 -68
  61. package/packages/visualizer/src/explorer/RunsView.tsx +307 -94
  62. package/packages/visualizer/src/explorer/TestDetail.tsx +393 -118
  63. package/packages/visualizer/src/explorer/parts.tsx +35 -13
  64. package/packages/visualizer/src/find/Finder.tsx +106 -90
  65. package/packages/visualizer/src/find/open.ts +17 -20
  66. package/packages/visualizer/src/live/test-progress.tsx +60 -22
  67. package/packages/visualizer/src/looks/Stage.tsx +223 -110
  68. package/packages/visualizer/src/main.tsx +9 -11
  69. package/packages/visualizer/src/map/ChartMap.tsx +125 -69
  70. package/packages/visualizer/src/map/EventEdge.tsx +59 -37
  71. package/packages/visualizer/src/map/map.module.css +12 -546
  72. package/packages/visualizer/src/map/nodes.tsx +124 -70
  73. package/packages/visualizer/src/reachable.test.ts +3 -2
  74. package/packages/visualizer/src/spec-kind.ts +2 -2
  75. package/packages/visualizer/src/ui/grammars/ALLOY-MIT.txt +21 -0
  76. package/packages/visualizer/src/ui/grammars/QUINT-APACHE-2.0.txt +206 -0
  77. package/packages/visualizer/src/ui/grammars/README.md +14 -0
  78. package/packages/visualizer/src/ui/grammars/alloy.json +71 -0
  79. package/packages/visualizer/src/ui/grammars/quint.json +152 -0
  80. package/packages/visualizer/src/ui/highlight.ts +50 -0
  81. package/packages/visualizer/src/ui/theme.tsx +109 -0
  82. package/packages/visualizer/src/views/ChartPage.tsx +46 -26
  83. package/packages/visualizer/src/views/ChartTests.tsx +59 -22
  84. package/packages/visualizer/src/views/ProductPage.tsx +16 -15
  85. package/packages/visualizer/src/views/SidePanel.tsx +241 -131
  86. package/packages/visualizer/src/app/gazetteer.module.css +0 -107
  87. package/packages/visualizer/src/components/ActivityIndicator.module.css +0 -4
  88. package/packages/visualizer/src/components/DataIssues.module.css +0 -136
  89. package/packages/visualizer/src/components/Header.module.css +0 -114
  90. package/packages/visualizer/src/components/Legend.module.css +0 -84
  91. package/packages/visualizer/src/components/Screen.module.css +0 -312
  92. package/packages/visualizer/src/components/Status.module.css +0 -116
  93. package/packages/visualizer/src/components/icons.tsx +0 -156
  94. package/packages/visualizer/src/evidence/artifacts.module.css +0 -213
  95. package/packages/visualizer/src/evidence/evidence.module.css +0 -907
  96. package/packages/visualizer/src/evidence/recording.module.css +0 -460
  97. package/packages/visualizer/src/evidence/sources.module.css +0 -168
  98. package/packages/visualizer/src/evidence/spec-snapshot.tsx +0 -26
  99. package/packages/visualizer/src/explorer/explorer.module.css +0 -1238
  100. package/packages/visualizer/src/find/Finder.module.css +0 -168
  101. package/packages/visualizer/src/looks/stage.module.css +0 -300
  102. package/packages/visualizer/src/styles/global.css +0 -105
  103. package/packages/visualizer/src/styles/tokens.css +0 -80
  104. package/packages/visualizer/src/views/views.module.css +0 -546
@@ -1,10 +1,8 @@
1
- import { Link, useLocation, useParams, useSearch } from '@tanstack/react-router';
2
-
3
- import { ScreenImage } from '../components/Screen';
1
+ import { Badge, Box, Divider, NavLink, Stack, Text, Title } from '@mantine/core';
2
+ import { Link, useParams, useSearch } from '@tanstack/react-router';
4
3
  import { STATUS_LABEL, type AtlasView, type StateView } from '../data/model';
4
+ import { statusColor } from '../ui/theme';
5
5
  import { useAtlas } from './atlas-context';
6
- import styles from './gazetteer.module.css';
7
-
8
6
  function openingScreen(view: AtlasView, steps: readonly { transitionIds: readonly string[] }[]) {
9
7
  for (const step of steps) {
10
8
  const transition = view.transitions.get(step.transitionIds[0] ?? '');
@@ -13,105 +11,114 @@ function openingScreen(view: AtlasView, steps: readonly { transitionIds: readonl
13
11
  }
14
12
  return undefined;
15
13
  }
16
-
17
14
  function screensOf(view: AtlasView, chartId: string) {
18
- const chart = view.charts.get(chartId);
19
- if (!chart) return [];
20
- return chart.states
15
+ return (view.charts.get(chartId)?.states ?? [])
21
16
  .map((key) => view.states.get(key))
22
- .filter((state): state is StateView => Boolean(state && (state.kind === 'screen' || state.kind === 'final')));
17
+ .filter((state): state is StateView => !!state && (state.kind === 'screen' || state.kind === 'final'));
23
18
  }
24
-
25
19
  export function Gazetteer() {
26
20
  const { view } = useAtlas();
27
21
  const params = useParams({ strict: false });
28
22
  const search = useSearch({ strict: false });
29
- const { pathname } = useLocation();
30
23
  const screen = typeof search.screen === 'string' ? search.screen : undefined;
31
24
  const journeyId = typeof search.journey === 'string' ? search.journey : undefined;
32
25
  const group = typeof search.group === 'string' ? search.group : undefined;
33
26
  const journeys = (view?.journeys ?? []).filter((journey) => journey.chartIds.length > 0);
34
27
  return (
35
- <nav className={styles.index} aria-label="Places">
36
- <h2>Places</h2>
37
- {!view && <p className={styles.empty}>Loading the spec…</p>}
38
- {view?.contexts.map((context) => (
39
- <section key={context.id}>
40
- <h3>{context.name}</h3>
41
- {context.chartIds.map((chartId) => {
42
- const chart = view.charts.get(chartId);
43
- if (!chart) return null;
44
- const screens = screensOf(view, chartId);
45
- return (
46
- <div key={chartId}>
47
- <Link className={styles.chart} to="/chart/$chartId" params={{ chartId }} search={(prev) => ({ run: prev.run, screen: undefined, journey: undefined, group: undefined })} activeOptions={{ includeSearch: true, explicitUndefined: true }} aria-current={params.chartId === chartId && !screen && !journeyId && !group ? 'page' : undefined}>
48
- {chart.name}
49
- </Link>
50
- <ul>
51
- {screens.map((state) => (
52
- <li key={state.key}>
28
+ <Box component="nav" aria-label="Places" p="md">
29
+ <Stack gap="lg">
30
+ <Title order={2}>Places</Title>
31
+ {!view && <Text c="dimmed">Loading the spec…</Text>}
32
+ {view?.contexts.map((context) => (
33
+ <Stack key={context.id} gap="xs">
34
+ <Text size="xs" fw={600} c="dimmed">
35
+ {context.name}
36
+ </Text>
37
+ {context.chartIds.map((chartId) => {
38
+ const chart = view.charts.get(chartId);
39
+ if (!chart) return null;
40
+ return (
41
+ <Box key={chartId}>
42
+ <NavLink
43
+ label={chart.name}
44
+ fw={600}
45
+ active={params.chartId === chartId && !screen && !journeyId && !group}
46
+ renderRoot={(props) => (
53
47
  <Link
54
- className={styles.row}
55
- data-status={state.status}
48
+ {...props}
56
49
  to="/chart/$chartId"
57
50
  params={{ chartId }}
58
- search={(prev) => ({ run: prev.run, screen: state.name, journey: prev.journey })}
59
- aria-current={params.chartId === chartId && screen === state.name ? 'page' : undefined}
60
- >
61
- <span className={styles.thumb} aria-hidden="true">
62
- <ScreenImage state={state} size="thumb" description={false} lines={false} caption={false} />
63
- </span>
64
- <span className={styles.name}>{state.name}</span>
65
- <span className={styles.status}>{STATUS_LABEL[state.status]}</span>
66
- </Link>
67
- </li>
51
+ search={(prev) => ({ run: prev.run, screen: undefined, journey: undefined, group: undefined })}
52
+ />
53
+ )}
54
+ />
55
+ {screensOf(view, chartId).map((state) => (
56
+ <NavLink
57
+ key={state.key}
58
+ label={state.name}
59
+ active={params.chartId === chartId && screen === state.name}
60
+ aria-current={params.chartId === chartId && screen === state.name ? 'page' : undefined}
61
+ rightSection={
62
+ <Badge color={statusColor(state.status)} size="xs">
63
+ {STATUS_LABEL[state.status]}
64
+ </Badge>
65
+ }
66
+ renderRoot={(props) => (
67
+ <Link
68
+ {...props}
69
+ to="/chart/$chartId"
70
+ params={{ chartId }}
71
+ search={(prev) => ({ run: prev.run, screen: state.name, journey: prev.journey })}
72
+ />
73
+ )}
74
+ />
68
75
  ))}
69
- </ul>
70
- </div>
71
- );
72
- })}
73
- </section>
74
- ))}
75
- {journeys.length > 0 && (
76
- <section>
77
- <h3>Journeys</h3>
78
- <ul>
76
+ </Box>
77
+ );
78
+ })}
79
+ </Stack>
80
+ ))}
81
+ {journeys.length > 0 && (
82
+ <Stack gap="xs">
83
+ <Divider />
84
+ <Title order={3}>Journeys</Title>
79
85
  {journeys.map((journey) => {
80
- const opening = view ? openingScreen(view, journey.steps) : undefined;
86
+ const chartId = journey.chartIds[0]!;
87
+ const start = view && openingScreen(view, journey.steps);
81
88
  return (
82
- <li key={journey.id}>
83
- <Link
84
- className={styles.row}
85
- data-status={journey.status}
86
- to="/chart/$chartId"
87
- params={{ chartId: journey.chartIds[0] ?? '' }}
88
- search={(prev) => ({ run: prev.run, journey: journey.id })}
89
- aria-current={journeyId === journey.id && pathname.startsWith('/chart') ? 'page' : undefined}
90
- >
91
- {opening && (
92
- <span className={styles.thumb} aria-hidden="true">
93
- <ScreenImage state={opening} size="thumb" description={false} lines={false} caption={false} />
94
- </span>
95
- )}
96
- <span className={styles.name}>{journey.name}</span>
97
- <span className={styles.status}>{STATUS_LABEL[journey.status]}</span>
98
- </Link>
99
- </li>
89
+ <NavLink
90
+ key={journey.id}
91
+ label={journey.name}
92
+ active={journeyId === journey.id}
93
+ rightSection={
94
+ <Badge size="xs" color={statusColor(journey.status)}>
95
+ {STATUS_LABEL[journey.status]}
96
+ </Badge>
97
+ }
98
+ renderRoot={(props) => (
99
+ <Link
100
+ {...props}
101
+ to="/chart/$chartId"
102
+ params={{ chartId }}
103
+ search={(prev) => ({ run: prev.run, journey: journey.id, screen: start?.name })}
104
+ />
105
+ )}
106
+ />
100
107
  );
101
108
  })}
102
- </ul>
103
- </section>
104
- )}
105
- {view && view.excluded.length > 0 && (
106
- <section>
107
- <h2>Not in the spec yet</h2>
108
- <ul>
109
+ </Stack>
110
+ )}
111
+ {!!view?.excluded.length && (
112
+ <Stack gap="xs">
113
+ <Title order={3}>Not in the spec yet</Title>
109
114
  {view.excluded.map((item) => (
110
- <li key={item} className={styles.excluded}>{item}</li>
115
+ <Text key={item} c="dimmed">
116
+ {item}
117
+ </Text>
111
118
  ))}
112
- </ul>
113
- </section>
114
- )}
115
- </nav>
119
+ </Stack>
120
+ )}
121
+ </Stack>
122
+ </Box>
116
123
  );
117
124
  }
@@ -1,13 +1,20 @@
1
- import { createBrowserHistory, createMemoryHistory, createRootRoute, createRoute, createRouter, lazyRouteComponent, type RouterHistory } from '@tanstack/react-router';
1
+ import { Alert, Anchor, Button, Container, Stack, Text, Title } from '@mantine/core';
2
+ import {
3
+ Link,
4
+ createBrowserHistory,
5
+ createMemoryHistory,
6
+ createRootRoute,
7
+ createRoute,
8
+ createRouter,
9
+ lazyRouteComponent,
10
+ type RouterHistory
11
+ } from '@tanstack/react-router';
2
12
  import { z } from 'zod/v4';
3
-
4
13
  import { SpecPage } from '../evidence/spec-page';
14
+ import { ExplorerSearchFields } from '../explorer/search';
5
15
  import { ProductPage } from '../views/ProductPage';
6
16
  import { AppShell } from './AppShell';
7
- import { ExplorerSearchFields } from '../explorer/search';
8
-
9
17
  const RootSearch = z.object({ run: z.string().min(1).optional().catch(undefined) });
10
-
11
18
  const ChartSearch = RootSearch.extend({
12
19
  screen: z.string().min(1).optional().catch(undefined),
13
20
  event: z.string().min(1).optional().catch(undefined),
@@ -15,12 +22,9 @@ const ChartSearch = RootSearch.extend({
15
22
  group: z.string().min(1).optional().catch(undefined),
16
23
  t: z.coerce.number().nonnegative().optional().catch(undefined)
17
24
  });
18
-
19
25
  export type ChartSearch = z.infer<typeof ChartSearch>;
20
-
21
26
  /** The project home: the test explorer's filters, grouping, order, open test and open run (see `explorer/search.ts`). */
22
27
  const HomeSearch = RootSearch.extend(ExplorerSearchFields);
23
-
24
28
  /** A spec document, the check opened in it, one layer's record of that check, and a recorded moment. */
25
29
  const SpecSearch = RootSearch.extend({
26
30
  spec: z.string().min(1).optional().catch(undefined),
@@ -29,14 +33,32 @@ const SpecSearch = RootSearch.extend({
29
33
  view: z.enum(['recording']).optional().catch(undefined),
30
34
  moment: z.coerce.number().int().positive().optional().catch(undefined)
31
35
  });
32
-
33
36
  export type SpecSearch = z.infer<typeof SpecSearch>;
34
-
35
37
  /** A fresh route tree per router, so a test can run its own viewer beside the page's. */
36
38
  function routeTree() {
37
39
  const rootRoute = createRootRoute({
38
40
  validateSearch: (search) => RootSearch.parse(search),
39
- component: AppShell
41
+ component: AppShell,
42
+ errorComponent: ({ error, reset }) => (
43
+ <Container py="xl">
44
+ <Alert color="red" title="The page couldn’t be loaded">
45
+ <Stack>
46
+ <Text>{error instanceof Error ? error.message : String(error)}</Text>
47
+ <Button onClick={reset}>Try again</Button>
48
+ <Anchor renderRoot={(props) => <Link {...props} to="/" search={{}} />}>Back to reports</Anchor>
49
+ </Stack>
50
+ </Alert>
51
+ </Container>
52
+ ),
53
+ notFoundComponent: () => (
54
+ <Container py="xl">
55
+ <Stack>
56
+ <Title order={1}>Page not found</Title>
57
+ <Text c="dimmed">This address is not part of the report viewer.</Text>
58
+ <Anchor renderRoot={(props) => <Link {...props} to="/" search={{}} />}>Back to reports</Anchor>
59
+ </Stack>
60
+ </Container>
61
+ )
40
62
  });
41
63
  const productRoute = createRoute({
42
64
  getParentRoute: () => rootRoute,
@@ -59,11 +81,9 @@ function routeTree() {
59
81
  });
60
82
  return rootRoute.addChildren([productRoute, chartRoute, specRoute]);
61
83
  }
62
-
63
84
  export function createAtlasRouter(history: RouterHistory) {
64
85
  return createRouter({ routeTree: routeTree(), history, defaultPreload: false, scrollRestoration: true });
65
86
  }
66
-
67
87
  /**
68
88
  * Addresses in the hash, so a built site deep-links without server rewrites. The page's own query (`?project=geviti`)
69
89
  * selects the project and is not part of the address: TanStack's hash history appends it to the hash, which turned
@@ -82,9 +102,7 @@ export function createProjectHashHistory(win: Window = window): RouterHistory {
82
102
  createHref: (href) => `${win.location.pathname}${win.location.search}#${href}`
83
103
  });
84
104
  }
85
-
86
105
  export const router = createAtlasRouter(createProjectHashHistory());
87
-
88
106
  declare module '@tanstack/react-router' {
89
107
  interface Register {
90
108
  router: typeof router;
@@ -1,6 +1,5 @@
1
- import styles from './ActivityIndicator.module.css';
2
-
3
- /** A quiet indication of active work; reduced motion keeps a static ring. */
4
- export function ActivityIndicator({ label = 'Working' }: { label?: string }) {
5
- return <span className={styles.activity} role="status" aria-label={label} title={label}><span className={styles.ring} aria-hidden="true" /></span>;
1
+ import { Loader } from '@mantine/core';
2
+ /** A named, reduced-motion-aware indicator for work in progress. */
3
+ export function ActivityIndicator({ label }: { label: string }) {
4
+ return <Loader size="xs" aria-label={label} role="status" />;
6
5
  }
@@ -1,70 +1,52 @@
1
1
  import type { DataIssue } from '@crvouga/atlas/schema';
2
- import * as Dialog from '@radix-ui/react-dialog';
2
+ import { Alert, Button, Code, Drawer, Stack, Text, Title } from '@mantine/core';
3
3
  import { useMemo } from 'react';
4
-
5
4
  import { useUiStore } from '../state/ui-store';
6
- import styles from './DataIssues.module.css';
7
- import { CloseIcon, NoteIcon, WarnIcon } from './icons';
8
-
9
5
  const SEVERITY_LABEL = { error: 'Problem', warning: 'Warning', info: 'Note' } as const;
10
-
11
- /**
12
- * A quiet count of what went wrong while reading the data. It never interrupts; the list opens
13
- * only when asked for.
14
- */
6
+ /** File-by-file diagnostics; unreadable data never appears as a passing result. */
15
7
  export function DataIssues({ issues }: { issues: DataIssue[] }) {
16
8
  const open = useUiStore((s) => s.issuesOpen);
17
9
  const setOpen = useUiStore((s) => s.setIssuesOpen);
18
10
  const problems = issues.filter((i) => i.severity !== 'info').length;
19
- const byFile = useMemo(() => {
20
- const groups = new Map<string, DataIssue[]>();
21
- for (const issue of issues) groups.set(issue.file, [...(groups.get(issue.file) ?? []), issue]);
22
- return [...groups.entries()].sort(([, a], [, b]) => severityRank(b) - severityRank(a));
11
+ const groups = useMemo(() => {
12
+ const byFile = new Map<string, DataIssue[]>();
13
+ for (const issue of issues) byFile.set(issue.file, [...(byFile.get(issue.file) ?? []), issue]);
14
+ return [...byFile].sort(([, a], [, b]) => severityRank(b) - severityRank(a));
23
15
  }, [issues]);
24
- if (issues.length === 0) return null;
25
- const label = problems ? `${problems} data ${problems === 1 ? 'problem' : 'problems'}` : `${issues.length} data ${issues.length === 1 ? 'note' : 'notes'}`;
16
+ if (!issues.length) return null;
17
+ const label = problems
18
+ ? `${problems} data ${problems === 1 ? 'problem' : 'problems'}`
19
+ : `${issues.length} data ${issues.length === 1 ? 'note' : 'notes'}`;
26
20
  return (
27
- <Dialog.Root open={open} onOpenChange={setOpen}>
28
- <Dialog.Trigger className={styles.trigger} data-has-problems={problems > 0}>
29
- {problems ? <WarnIcon size={13} /> : <NoteIcon size={13} />}
21
+ <>
22
+ <Button color={problems ? 'orange' : 'gray'} onClick={() => setOpen(true)}>
30
23
  {label}
31
- </Dialog.Trigger>
32
- <Dialog.Portal>
33
- <Dialog.Overlay className={styles.overlay} />
34
- <Dialog.Content className={styles.sheet}>
35
- <div className={styles.head}>
36
- <Dialog.Title className={styles.title}>What we couldn’t read</Dialog.Title>
37
- <Dialog.Close className={styles.close} aria-label="Close">
38
- <CloseIcon size={16} />
39
- </Dialog.Close>
40
- </div>
41
- <Dialog.Description className={styles.lead}>
42
- Everything else on the map is shown as usual. These are the parts of the spec and the runs that were skipped or guessed, file by file.
43
- </Dialog.Description>
44
- <div className={styles.list}>
45
- {byFile.map(([file, list]) => (
46
- <section key={file} className={styles.file}>
47
- <h3 className={styles.fileName}>{file || 'General'}</h3>
48
- <ul className={styles.items}>
49
- {list.map((issue, i) => (
50
- <li key={i} className={styles.item} data-severity={issue.severity}>
51
- <span className={styles.severity}>{SEVERITY_LABEL[issue.severity]}</span>
52
- <span>
53
- {issue.path && <span className={styles.path}>{issue.path}: </span>}
54
- {issue.message}
55
- </span>
56
- </li>
57
- ))}
58
- </ul>
59
- </section>
60
- ))}
61
- </div>
62
- </Dialog.Content>
63
- </Dialog.Portal>
64
- </Dialog.Root>
24
+ </Button>
25
+ <Drawer opened={open} onClose={() => setOpen(false)} title="What we couldn’t read">
26
+ <Stack>
27
+ <Text c="dimmed">
28
+ Everything else on the map is shown as usual. These are the parts of the spec and the runs that were skipped or guessed, file by
29
+ file.
30
+ </Text>
31
+ {groups.map(([file, list]) => (
32
+ <Stack key={file} gap="xs">
33
+ <Title order={3}>{file || 'General'}</Title>
34
+ {list.map((issue, i) => (
35
+ <Alert
36
+ key={i}
37
+ color={issue.severity === 'error' ? 'red' : issue.severity === 'warning' ? 'orange' : 'blue'}
38
+ title={SEVERITY_LABEL[issue.severity]}
39
+ >
40
+ {issue.path && <Code>{issue.path}</Code>} {issue.message}
41
+ </Alert>
42
+ ))}
43
+ </Stack>
44
+ ))}
45
+ </Stack>
46
+ </Drawer>
47
+ </>
65
48
  );
66
49
  }
67
-
68
50
  function severityRank(list: DataIssue[]) {
69
51
  return Math.max(...list.map((i) => (i.severity === 'error' ? 2 : i.severity === 'warning' ? 1 : 0)));
70
52
  }