@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.
- package/CHANGELOG.md +12 -0
- package/README.md +7 -0
- package/dist/reports/src/layer-merge.d.ts +5 -1
- package/dist/reports/src/layer-merge.js +17 -2
- package/dist/server/src/settings-page.d.ts +4 -4
- package/dist/server/src/settings-page.js +17 -78
- package/docs/agents.md +25 -0
- package/docs/reference/reports.md +2 -0
- package/docs/reference/server.md +4 -4
- package/docs/report-contract.md +3 -0
- package/docs/ui.md +142 -0
- package/docs/workspaces.md +130 -0
- package/llms-full.txt +306 -0
- package/llms.txt +2 -0
- package/package.json +17 -10
- package/packages/visualizer/src/admin/SettingsPage.tsx +456 -0
- package/packages/visualizer/src/admin/main.tsx +11 -0
- package/packages/visualizer/src/app/AppShell.tsx +62 -38
- package/packages/visualizer/src/app/gazetteer.tsx +90 -83
- package/packages/visualizer/src/app/router.tsx +33 -15
- package/packages/visualizer/src/components/ActivityIndicator.tsx +4 -5
- package/packages/visualizer/src/components/DataIssues.tsx +36 -54
- package/packages/visualizer/src/components/Header.tsx +197 -125
- package/packages/visualizer/src/components/Legend.tsx +13 -41
- package/packages/visualizer/src/components/ProjectPicker.tsx +61 -26
- package/packages/visualizer/src/components/Screen.tsx +80 -71
- package/packages/visualizer/src/components/SpecLogo.tsx +15 -0
- package/packages/visualizer/src/components/Status.tsx +20 -34
- package/packages/visualizer/src/components/labels.tsx +8 -3
- package/packages/visualizer/src/components/spec-logos/APACHE-2.0.txt +206 -0
- package/packages/visualizer/src/components/spec-logos/CAMUNDA-MIT.txt +21 -0
- package/packages/visualizer/src/components/spec-logos/FILE-ICONS-ISC.txt +13 -0
- package/packages/visualizer/src/components/spec-logos/README.md +23 -0
- package/packages/visualizer/src/components/spec-logos/SIMPLE-ICONS-CC0.txt +30 -0
- package/packages/visualizer/src/components/spec-logos/alloy.svg +1 -0
- package/packages/visualizer/src/components/spec-logos/dmn.svg +3 -0
- package/packages/visualizer/src/components/spec-logos/gherkin.svg +1 -0
- package/packages/visualizer/src/components/spec-logos/openslo.svg +63 -0
- package/packages/visualizer/src/components/spec-logos/quint.svg +1 -0
- package/packages/visualizer/src/evidence/artifacts.tsx +263 -110
- package/packages/visualizer/src/evidence/chart-source.tsx +18 -0
- package/packages/visualizer/src/evidence/check-detail.tsx +283 -139
- package/packages/visualizer/src/evidence/decision-table.tsx +35 -26
- package/packages/visualizer/src/evidence/document-page.tsx +51 -29
- package/packages/visualizer/src/evidence/feature-page.tsx +121 -53
- package/packages/visualizer/src/evidence/marks.tsx +112 -79
- package/packages/visualizer/src/evidence/model-page.tsx +77 -46
- package/packages/visualizer/src/evidence/model-trace.tsx +55 -35
- package/packages/visualizer/src/evidence/overview.tsx +137 -69
- package/packages/visualizer/src/evidence/recording-page.tsx +70 -31
- package/packages/visualizer/src/evidence/recording.tsx +135 -68
- package/packages/visualizer/src/evidence/snapshot.tsx +99 -43
- package/packages/visualizer/src/evidence/source-text.tsx +33 -0
- package/packages/visualizer/src/evidence/sources-panel.tsx +138 -144
- package/packages/visualizer/src/evidence/spec-header.tsx +52 -36
- package/packages/visualizer/src/evidence/spec-index.tsx +61 -32
- package/packages/visualizer/src/evidence/spec-page.tsx +56 -25
- package/packages/visualizer/src/evidence/test-filter.tsx +38 -14
- package/packages/visualizer/src/explorer/Explorer.tsx +688 -261
- package/packages/visualizer/src/explorer/ExplorerIndex.tsx +136 -68
- package/packages/visualizer/src/explorer/RunsView.tsx +307 -94
- package/packages/visualizer/src/explorer/TestDetail.tsx +393 -118
- package/packages/visualizer/src/explorer/parts.tsx +35 -13
- package/packages/visualizer/src/find/Finder.tsx +106 -90
- package/packages/visualizer/src/find/open.ts +17 -20
- package/packages/visualizer/src/live/test-progress.tsx +60 -22
- package/packages/visualizer/src/looks/Stage.tsx +223 -110
- package/packages/visualizer/src/main.tsx +9 -11
- package/packages/visualizer/src/map/ChartMap.tsx +125 -69
- package/packages/visualizer/src/map/EventEdge.tsx +59 -37
- package/packages/visualizer/src/map/map.module.css +12 -546
- package/packages/visualizer/src/map/nodes.tsx +124 -70
- package/packages/visualizer/src/reachable.test.ts +3 -2
- package/packages/visualizer/src/spec-kind.ts +2 -2
- package/packages/visualizer/src/ui/grammars/ALLOY-MIT.txt +21 -0
- package/packages/visualizer/src/ui/grammars/QUINT-APACHE-2.0.txt +206 -0
- package/packages/visualizer/src/ui/grammars/README.md +14 -0
- package/packages/visualizer/src/ui/grammars/alloy.json +71 -0
- package/packages/visualizer/src/ui/grammars/quint.json +152 -0
- package/packages/visualizer/src/ui/highlight.ts +50 -0
- package/packages/visualizer/src/ui/theme.tsx +109 -0
- package/packages/visualizer/src/views/ChartPage.tsx +46 -26
- package/packages/visualizer/src/views/ChartTests.tsx +59 -22
- package/packages/visualizer/src/views/ProductPage.tsx +16 -15
- package/packages/visualizer/src/views/SidePanel.tsx +241 -131
- package/packages/visualizer/src/app/gazetteer.module.css +0 -107
- package/packages/visualizer/src/components/ActivityIndicator.module.css +0 -4
- package/packages/visualizer/src/components/DataIssues.module.css +0 -136
- package/packages/visualizer/src/components/Header.module.css +0 -114
- package/packages/visualizer/src/components/Legend.module.css +0 -84
- package/packages/visualizer/src/components/Screen.module.css +0 -312
- package/packages/visualizer/src/components/Status.module.css +0 -116
- package/packages/visualizer/src/components/icons.tsx +0 -156
- package/packages/visualizer/src/evidence/artifacts.module.css +0 -213
- package/packages/visualizer/src/evidence/evidence.module.css +0 -907
- package/packages/visualizer/src/evidence/recording.module.css +0 -460
- package/packages/visualizer/src/evidence/sources.module.css +0 -168
- package/packages/visualizer/src/evidence/spec-snapshot.tsx +0 -26
- package/packages/visualizer/src/explorer/explorer.module.css +0 -1238
- package/packages/visualizer/src/find/Finder.module.css +0 -168
- package/packages/visualizer/src/looks/stage.module.css +0 -300
- package/packages/visualizer/src/styles/global.css +0 -105
- package/packages/visualizer/src/styles/tokens.css +0 -80
- package/packages/visualizer/src/views/views.module.css +0 -546
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
|
3
|
+
"name": "quint",
|
|
4
|
+
"patterns": [
|
|
5
|
+
{
|
|
6
|
+
"include": "#hashbangLine"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"include": "#docComments"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"include": "#lineComments"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"include": "#blockComments"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"include": "#keywords"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"include": "#storage"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"include": "#constants"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"include": "#punctuation"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"include": "#operators"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"include": "#identifiers"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"include": "#strings"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"include": "#numbers"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"repository": {
|
|
43
|
+
"identifiers": {
|
|
44
|
+
"patterns": [
|
|
45
|
+
{
|
|
46
|
+
"name": "meta.quint",
|
|
47
|
+
"match": "[a-zA-Z_]([a-zA-Z0-9_])*"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"keywords": {
|
|
52
|
+
"patterns": [
|
|
53
|
+
{
|
|
54
|
+
"name": "keyword.control.quint",
|
|
55
|
+
"match": "\\b(module|import|from|export|as|if|else|match|not|or|and|implies|iff|all|any|leadsTo|strongFair|weakFair)\\b"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"storage": {
|
|
60
|
+
"patterns": [
|
|
61
|
+
{
|
|
62
|
+
"name": "storage.modifier.quint",
|
|
63
|
+
"match": "\\b(type|assume|const|var|val|nondet|def|pure|action|temporal|run)\\b"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "storage.type.quint",
|
|
67
|
+
"match": "\\b(Set|List|int|str|bool)\\b"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "support.type.quint",
|
|
71
|
+
"match": "\\b(Map|Tup|Rec)\\b"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"operators": {
|
|
76
|
+
"patterns": [
|
|
77
|
+
{
|
|
78
|
+
"name": "keyword.operator.quint",
|
|
79
|
+
"match": "(=>|->|<=|>|<|=|!=|'|\\.|\\*|\\+|-|/|%|\\^)"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
"strings": {
|
|
84
|
+
"name": "string.quoted.double.quint",
|
|
85
|
+
"begin": "\"",
|
|
86
|
+
"end": "\""
|
|
87
|
+
},
|
|
88
|
+
"numbers": {
|
|
89
|
+
"patterns": [
|
|
90
|
+
{
|
|
91
|
+
"name": "constant.numeric.quint",
|
|
92
|
+
"match": "-?(0x[0-9a-fA-F]([0-9a-fA-F]|_[0-9a-fA-F])*|0|[1-9]([0-9]|_[0-9])*)"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"constants": {
|
|
97
|
+
"patterns": [
|
|
98
|
+
{
|
|
99
|
+
"name": "constant.language.quint",
|
|
100
|
+
"match": "\\b(false|true|Bool|Int|Nat)\\b"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
"hashbangLine": {
|
|
105
|
+
"patterns": [
|
|
106
|
+
{
|
|
107
|
+
"name": "comment.line.hashbang.quint",
|
|
108
|
+
"match": "\\A#!.*$"
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"docComments": {
|
|
113
|
+
"patterns": [
|
|
114
|
+
{
|
|
115
|
+
"name": "comment.block.documentation.quint",
|
|
116
|
+
"match": "///.*$"
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
"punctuation": {
|
|
121
|
+
"patterns": [
|
|
122
|
+
{
|
|
123
|
+
"name": "keyword.operator.spread.quint",
|
|
124
|
+
"match": "\\.\\.\\."
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "punctuation.accessor.quint",
|
|
128
|
+
"match": "::"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "punctuation.separator.quint",
|
|
132
|
+
"match": "\\|"
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
"lineComments": {
|
|
137
|
+
"patterns": [
|
|
138
|
+
{
|
|
139
|
+
"name": "comment.line.double-slash.quint",
|
|
140
|
+
"match": "//.*$"
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
"blockComments": {
|
|
145
|
+
"name": "comment.block.quint",
|
|
146
|
+
"begin": "/\\*",
|
|
147
|
+
"end": "\\*/",
|
|
148
|
+
"patterns": []
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"scopeName": "source.quint"
|
|
152
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createShikiAdapter } from '@mantine/code-highlight';
|
|
2
|
+
import type { HighlighterCore, LanguageInput, LanguageRegistration } from 'shiki/core';
|
|
3
|
+
|
|
4
|
+
const grammars: Record<string, () => Promise<LanguageInput>> = {
|
|
5
|
+
gherkin: () => import('shiki/langs/gherkin.mjs').then((module) => module.default),
|
|
6
|
+
yaml: () => import('shiki/langs/yaml.mjs').then((module) => module.default),
|
|
7
|
+
xml: () => import('shiki/langs/xml.mjs').then((module) => module.default),
|
|
8
|
+
json: () => import('shiki/langs/json.mjs').then((module) => module.default),
|
|
9
|
+
quint: () => import('./grammars/quint.json').then((module) => module.default as unknown as LanguageRegistration),
|
|
10
|
+
alloy: () => import('./grammars/alloy.json').then((module) => module.default as unknown as LanguageRegistration)
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
async function createContext() {
|
|
14
|
+
const [{ createHighlighterCore }, { createJavaScriptRegexEngine }] = await Promise.all([
|
|
15
|
+
import('shiki/core'),
|
|
16
|
+
import('shiki/engine/javascript')
|
|
17
|
+
]);
|
|
18
|
+
const highlighter = await createHighlighterCore({ langs: [], themes: [], engine: createJavaScriptRegexEngine() });
|
|
19
|
+
return {
|
|
20
|
+
getLoadedLanguages: () => highlighter.getLoadedLanguages(),
|
|
21
|
+
loadLanguage: (...languages: Parameters<HighlighterCore['loadLanguage']>) => highlighter.loadLanguage(...languages),
|
|
22
|
+
codeToHtml: (code: string, options: Parameters<HighlighterCore['codeToHtml']>[1]) =>
|
|
23
|
+
highlighter.codeToHtml(code, {
|
|
24
|
+
...options,
|
|
25
|
+
transformers: [
|
|
26
|
+
{
|
|
27
|
+
line(node, line) {
|
|
28
|
+
node.properties.id = `line-${line}`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
})
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
let context: ReturnType<typeof createContext> | undefined;
|
|
36
|
+
/** One lazy, cached syntax engine; only a viewed file's grammar is downloaded. */
|
|
37
|
+
export const specHighlightAdapter = createShikiAdapter(() => (context ??= createContext()), {
|
|
38
|
+
resolveLanguage: (language) => grammars[language]
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/** Choose the actual notation, never a visually similar language. */
|
|
42
|
+
export function specLanguage(file: string): string {
|
|
43
|
+
if (/\.feature$/i.test(file)) return 'gherkin';
|
|
44
|
+
if (/\.qnt$/i.test(file)) return 'quint';
|
|
45
|
+
if (/\.als$/i.test(file)) return 'alloy';
|
|
46
|
+
if (/\.(dmn|scxml|xml)$/i.test(file)) return 'xml';
|
|
47
|
+
if (/\.ya?ml$/i.test(file)) return 'yaml';
|
|
48
|
+
if (/\.json$/i.test(file)) return 'json';
|
|
49
|
+
return 'text';
|
|
50
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** @module Viewer design system
|
|
2
|
+
* The sole Mantine theme and provider for the report viewer and server settings.
|
|
3
|
+
*/
|
|
4
|
+
import { CodeHighlight, CodeHighlightTabs } from '@mantine/code-highlight';
|
|
5
|
+
import {
|
|
6
|
+
ActionIcon,
|
|
7
|
+
DEFAULT_THEME,
|
|
8
|
+
darken,
|
|
9
|
+
type MantineColorsTuple,
|
|
10
|
+
type CSSVariablesResolver,
|
|
11
|
+
Anchor,
|
|
12
|
+
Badge,
|
|
13
|
+
Button,
|
|
14
|
+
Card,
|
|
15
|
+
createTheme,
|
|
16
|
+
Drawer,
|
|
17
|
+
InputWrapper,
|
|
18
|
+
List,
|
|
19
|
+
MantineProvider,
|
|
20
|
+
Modal,
|
|
21
|
+
NativeSelect,
|
|
22
|
+
Paper,
|
|
23
|
+
Table,
|
|
24
|
+
Text,
|
|
25
|
+
Title,
|
|
26
|
+
Tooltip
|
|
27
|
+
} from '@mantine/core';
|
|
28
|
+
import type { ReactNode } from 'react';
|
|
29
|
+
// Retain Mantine's scales while making small outcome labels readable on light surfaces.
|
|
30
|
+
const readableScale = (scale: MantineColorsTuple) =>
|
|
31
|
+
scale.map((color, index) => (index >= 6 ? darken(color, 0.25) : color)) as unknown as MantineColorsTuple;
|
|
32
|
+
const cssVariablesResolver: CSSVariablesResolver = () => ({
|
|
33
|
+
variables: {},
|
|
34
|
+
light: { '--mantine-color-dimmed': 'var(--mantine-color-gray-7)' },
|
|
35
|
+
dark: {}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/** Atlas uses Mantine's blue scale for navigation and standard outcome colors for evidence. */
|
|
39
|
+
export const theme = createTheme({
|
|
40
|
+
primaryColor: 'blue',
|
|
41
|
+
primaryShade: 8,
|
|
42
|
+
autoContrast: true,
|
|
43
|
+
colors: {
|
|
44
|
+
green: readableScale(DEFAULT_THEME.colors.green),
|
|
45
|
+
red: readableScale(DEFAULT_THEME.colors.red),
|
|
46
|
+
orange: readableScale(DEFAULT_THEME.colors.orange)
|
|
47
|
+
},
|
|
48
|
+
defaultRadius: 'md',
|
|
49
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
50
|
+
fontFamilyMonospace: 'ui-monospace, SFMono-Regular, Menlo, monospace',
|
|
51
|
+
headings: { fontWeight: '650', sizes: { h1: { fontSize: '1.75rem' }, h2: { fontSize: '1.25rem' }, h3: { fontSize: '1rem' } } },
|
|
52
|
+
respectReducedMotion: true,
|
|
53
|
+
focusRing: 'auto',
|
|
54
|
+
components: {
|
|
55
|
+
CodeHighlight: CodeHighlight.extend({
|
|
56
|
+
defaultProps: { withLineNumbers: true, withFirstLineIndentation: true, defaultExpanded: true, withBorder: true, radius: 'md' }
|
|
57
|
+
}),
|
|
58
|
+
CodeHighlightTabs: CodeHighlightTabs.extend({
|
|
59
|
+
defaultProps: {
|
|
60
|
+
withLineNumbers: true,
|
|
61
|
+
withFirstLineIndentation: true,
|
|
62
|
+
defaultExpanded: true,
|
|
63
|
+
withBorder: true,
|
|
64
|
+
radius: 'md',
|
|
65
|
+
withExpandButton: true,
|
|
66
|
+
maxCollapsedHeight: '70vh'
|
|
67
|
+
}
|
|
68
|
+
}),
|
|
69
|
+
// Mantine List wraps labels in inline-flex; report rows need the available width.
|
|
70
|
+
// Keep this single supported Styles API adjustment here, not in each feature.
|
|
71
|
+
List: List.extend({ styles: { itemWrapper: { display: 'flex' }, itemLabel: { flex: 1, minWidth: 0, width: '100%' } } }),
|
|
72
|
+
Button: Button.extend({ defaultProps: { size: 'sm', variant: 'light' } }),
|
|
73
|
+
ActionIcon: ActionIcon.extend({ defaultProps: { size: 'lg', variant: 'subtle' } }),
|
|
74
|
+
Anchor: Anchor.extend({ defaultProps: { size: 'sm', underline: 'hover' } }),
|
|
75
|
+
Badge: Badge.extend({ defaultProps: { variant: 'light', tt: 'none' } }),
|
|
76
|
+
Paper: Paper.extend({ defaultProps: { p: 'md' } }),
|
|
77
|
+
Card: Card.extend({ defaultProps: { withBorder: true, padding: 'md' } }),
|
|
78
|
+
Text: Text.extend({ defaultProps: { size: 'sm' } }),
|
|
79
|
+
Title: Title.extend({ defaultProps: { textWrap: 'pretty' } }),
|
|
80
|
+
InputWrapper: InputWrapper.extend({ defaultProps: { size: 'sm' } }),
|
|
81
|
+
NativeSelect: NativeSelect.extend({ defaultProps: { size: 'sm' } }),
|
|
82
|
+
Modal: Modal.extend({
|
|
83
|
+
defaultProps: { centered: true, size: 'lg', overlayProps: { backgroundOpacity: 0.35 }, transitionProps: { duration: 120 } }
|
|
84
|
+
}),
|
|
85
|
+
Drawer: Drawer.extend({ defaultProps: { position: 'right', size: 'lg', padding: 'lg', transitionProps: { duration: 120 } } }),
|
|
86
|
+
Table: Table.extend({ defaultProps: { verticalSpacing: 'sm', horizontalSpacing: 'sm', highlightOnHover: true, tabularNums: true } }),
|
|
87
|
+
Tooltip: Tooltip.extend({ defaultProps: { openDelay: 300, withArrow: true } })
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
/** Use this provider at every UI root; the viewer currently has a light visual identity. */
|
|
91
|
+
export function AtlasThemeProvider({ children, env }: { children: ReactNode; env?: 'test' }) {
|
|
92
|
+
return (
|
|
93
|
+
<MantineProvider env={env} theme={theme} cssVariablesResolver={cssVariablesResolver} forceColorScheme="light">
|
|
94
|
+
{children}
|
|
95
|
+
</MantineProvider>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
/** Outcome colors shared by report badges, progress bars, and the graph. Unknown outcomes stay gray. */
|
|
99
|
+
export function statusColor(status: string): string {
|
|
100
|
+
return status === 'passed'
|
|
101
|
+
? 'green'
|
|
102
|
+
: status === 'failed'
|
|
103
|
+
? 'red'
|
|
104
|
+
: status === 'flaky'
|
|
105
|
+
? 'orange'
|
|
106
|
+
: status === 'running'
|
|
107
|
+
? 'blue'
|
|
108
|
+
: 'gray';
|
|
109
|
+
}
|
|
@@ -1,26 +1,32 @@
|
|
|
1
|
+
import { Anchor, Container, Flex, Stack, Text, Title } from '@mantine/core';
|
|
1
2
|
import { Link, useParams, useSearch } from '@tanstack/react-router';
|
|
2
|
-
|
|
3
3
|
import { useAtlas } from '../app/atlas-context';
|
|
4
4
|
import { mapFrame } from '../layout/graph';
|
|
5
5
|
import { ChartMap } from '../map/ChartMap';
|
|
6
|
-
import { SidePanel } from './SidePanel';
|
|
7
|
-
import styles from './views.module.css';
|
|
8
6
|
import { ChartTests } from './ChartTests';
|
|
9
|
-
|
|
7
|
+
import { ChartSource } from '../evidence/chart-source';
|
|
8
|
+
import { SidePanel } from './SidePanel';
|
|
10
9
|
export function ChartPage() {
|
|
11
10
|
const { chartId } = useParams({ from: '/chart/$chartId' });
|
|
12
11
|
const search = useSearch({ from: '/chart/$chartId' });
|
|
13
12
|
const { view } = useAtlas();
|
|
14
|
-
if (!view)
|
|
13
|
+
if (!view)
|
|
14
|
+
return (
|
|
15
|
+
<Container role="status" component="article" size="xl" w="100%" py="xl">
|
|
16
|
+
Loading the spec…
|
|
17
|
+
</Container>
|
|
18
|
+
);
|
|
15
19
|
if (!view.charts.has(chartId)) {
|
|
16
20
|
return (
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
<Container component="article" size="xl" w="100%" py="xl">
|
|
22
|
+
<Title order={1} mb="sm">
|
|
23
|
+
This chart isn’t in the spec
|
|
24
|
+
</Title>
|
|
25
|
+
<Text component="div" c="dimmed">
|
|
26
|
+
It may have been renamed or removed since this link was shared.
|
|
27
|
+
</Text>
|
|
28
|
+
<Anchor renderRoot={(props) => <Link {...props} to="/" search={(prev) => ({ run: prev.run })} />}>Back to the product map</Anchor>
|
|
29
|
+
</Container>
|
|
24
30
|
);
|
|
25
31
|
}
|
|
26
32
|
const selection = { screen: search.screen, event: search.event, journey: search.journey };
|
|
@@ -30,25 +36,39 @@ export function ChartPage() {
|
|
|
30
36
|
const frame = mapFrame(view, chartId, search.group ?? null);
|
|
31
37
|
const homeFocus = mapFrame(view, chartId, null).focus;
|
|
32
38
|
return (
|
|
33
|
-
<
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
|
|
39
|
+
<Stack gap="sm" p="md">
|
|
40
|
+
<Flex gap="xs" align="center" wrap="wrap">
|
|
41
|
+
<Title order={1} mb="sm">
|
|
42
|
+
{chart.name}
|
|
43
|
+
</Title>
|
|
44
|
+
{chart.description && <Text component="div">{chart.description}</Text>}
|
|
37
45
|
{frame.focus && (
|
|
38
|
-
<
|
|
39
|
-
<span>
|
|
46
|
+
<Text component="div">
|
|
47
|
+
<Text span inherit>
|
|
48
|
+
{frame.focus}
|
|
49
|
+
</Text>
|
|
40
50
|
{frame.wider && (
|
|
41
|
-
<
|
|
51
|
+
<Anchor
|
|
52
|
+
renderRoot={(props) => (
|
|
53
|
+
<Link
|
|
54
|
+
{...props}
|
|
55
|
+
to="/chart/$chartId"
|
|
56
|
+
params={{ chartId }}
|
|
57
|
+
search={(prev) => ({ run: prev.run, group: frame.wider && frame.wider !== homeFocus ? frame.wider : undefined })}
|
|
58
|
+
/>
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
42
61
|
Wider map
|
|
43
|
-
</
|
|
62
|
+
</Anchor>
|
|
44
63
|
)}
|
|
45
|
-
</
|
|
64
|
+
</Text>
|
|
46
65
|
)}
|
|
47
|
-
</
|
|
48
|
-
<
|
|
66
|
+
</Flex>
|
|
67
|
+
<ChartSource file={chart.file} />
|
|
68
|
+
<Stack gap="sm">
|
|
49
69
|
<ChartMap key={`${chartId}:${search.group ?? ''}`} view={view} chartId={chartId} selection={selection} group={search.group} />
|
|
50
|
-
</
|
|
51
|
-
<ChartTests view={view} chartId={chartId}
|
|
52
|
-
</
|
|
70
|
+
</Stack>
|
|
71
|
+
<ChartTests view={view} chartId={chartId} />
|
|
72
|
+
</Stack>
|
|
53
73
|
);
|
|
54
74
|
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
+
import { Box, List, Title } from '@mantine/core';
|
|
1
2
|
import { lazy, Suspense, useState } from 'react';
|
|
2
3
|
import type { AtlasView } from '../data/model';
|
|
3
|
-
import { useEvidence } from '../evidence/evidence-context';
|
|
4
4
|
import { CheckDetail, ResultRow, type CheckSelection } from '../evidence/check-detail';
|
|
5
|
-
import {
|
|
5
|
+
import { useEvidence } from '../evidence/evidence-context';
|
|
6
6
|
import { Bounded } from '../evidence/marks';
|
|
7
|
+
import { TestFilter, useRunningFilter } from '../evidence/test-filter';
|
|
7
8
|
import { outlinePath } from '../spec-kind';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const RecordingPage = lazy(() => import('../evidence/recording-page').then(module => ({ default: module.RecordingPage })));
|
|
11
|
-
|
|
9
|
+
const RecordingPage = lazy(() => import('../evidence/recording-page').then((module) => ({ default: module.RecordingPage })));
|
|
12
10
|
/** Statechart test reports stay in the chart, including while their recordings are still arriving. */
|
|
13
11
|
export function ChartTests({ view, chartId, compact = false }: { view: AtlasView; chartId: string; compact?: boolean }) {
|
|
14
12
|
const evidence = useEvidence();
|
|
@@ -23,21 +21,60 @@ export function ChartTests({ view, chartId, compact = false }: { view: AtlasView
|
|
|
23
21
|
files.add(outlinePath(chart.file));
|
|
24
22
|
chart = chart.parent ? view.charts.get(chart.parent.chartId) : undefined;
|
|
25
23
|
}
|
|
26
|
-
const slots = [...files].flatMap(file => evidence.view.specs.get(file)?.slots ?? []);
|
|
27
|
-
const visible = slots.filter(slot => !runningOnly || slot.status === 'running');
|
|
24
|
+
const slots = [...files].flatMap((file) => evidence.view.specs.get(file)?.slots ?? []);
|
|
25
|
+
const visible = slots.filter((slot) => !runningOnly || slot.status === 'running');
|
|
28
26
|
if (!slots.length) return null;
|
|
29
|
-
return
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
return (
|
|
28
|
+
<Box aria-label="Chart tests" mah={compact ? '40vh' : undefined} p="md" component="section" mb="lg">
|
|
29
|
+
<Title order={3} mb="sm">
|
|
30
|
+
Tests
|
|
31
|
+
</Title>
|
|
32
|
+
<TestFilter slots={slots} />
|
|
33
|
+
<List spacing="xs" listStyleType="none">
|
|
34
|
+
<Bounded items={visible} size={40} more="More tests">
|
|
35
|
+
{(slot) => (
|
|
36
|
+
<ResultRow
|
|
37
|
+
key={slot.key}
|
|
38
|
+
label={slot.title}
|
|
39
|
+
slot={slot}
|
|
40
|
+
open={selectedKey === slot.key}
|
|
41
|
+
onToggle={() => {
|
|
42
|
+
setSelectedKey(selectedKey === slot.key ? null : slot.key);
|
|
43
|
+
setSelection({ check: slot.id });
|
|
44
|
+
setRecording(false);
|
|
45
|
+
setMoment(null);
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
{recording ? (
|
|
49
|
+
<Suspense fallback={<p role="status">Loading the recording…</p>}>
|
|
50
|
+
<RecordingPage
|
|
51
|
+
slot={slot}
|
|
52
|
+
layerId={selection.layer}
|
|
53
|
+
label={slot.title}
|
|
54
|
+
back="Test report"
|
|
55
|
+
moment={moment}
|
|
56
|
+
onMoment={setMoment}
|
|
57
|
+
onClose={() => setRecording(false)}
|
|
58
|
+
/>
|
|
59
|
+
</Suspense>
|
|
60
|
+
) : (
|
|
61
|
+
<CheckDetail
|
|
62
|
+
slot={slot}
|
|
63
|
+
layerId={selection.layer}
|
|
64
|
+
reads="steps"
|
|
65
|
+
select={(patch) => {
|
|
66
|
+
setSelection({ check: patch.check ?? slot.id, layer: patch.layer ?? undefined });
|
|
67
|
+
if (patch.view === 'recording') {
|
|
68
|
+
setRecording(true);
|
|
69
|
+
setMoment(null);
|
|
70
|
+
}
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
</ResultRow>
|
|
75
|
+
)}
|
|
76
|
+
</Bounded>
|
|
77
|
+
</List>
|
|
78
|
+
</Box>
|
|
79
|
+
);
|
|
43
80
|
}
|
|
@@ -1,35 +1,36 @@
|
|
|
1
|
+
import { Container, Text, Title } from '@mantine/core';
|
|
1
2
|
import { Navigate, useSearch } from '@tanstack/react-router';
|
|
2
|
-
|
|
3
3
|
import { useAtlas } from '../app/atlas-context';
|
|
4
4
|
import type { AtlasView } from '../data/model';
|
|
5
|
-
import styles from './views.module.css';
|
|
6
|
-
|
|
7
5
|
function openingPlace(view: AtlasView) {
|
|
8
6
|
const screens = [...view.states.values()].filter((state) => state.kind === 'screen' || state.kind === 'final');
|
|
9
7
|
const withFailedEvent = screens.find((state) => state.outgoing.some((id) => view.transitions.get(id)?.status === 'failed'));
|
|
10
8
|
return withFailedEvent ?? screens.find((state) => state.status === 'failed') ?? screens[0] ?? null;
|
|
11
9
|
}
|
|
12
|
-
|
|
13
10
|
export function ProductPage() {
|
|
14
11
|
const { view } = useAtlas();
|
|
15
12
|
const search = useSearch({ strict: false });
|
|
16
13
|
const run = typeof search.run === 'string' ? search.run : undefined;
|
|
17
|
-
if (!view)
|
|
14
|
+
if (!view)
|
|
15
|
+
return (
|
|
16
|
+
<Container role="status" component="article" size="xl" w="100%" py="xl">
|
|
17
|
+
Loading the spec…
|
|
18
|
+
</Container>
|
|
19
|
+
);
|
|
18
20
|
const place = openingPlace(view);
|
|
19
21
|
if (!place) {
|
|
20
22
|
return (
|
|
21
|
-
<article
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
<Container component="article" size="xl" w="100%" py="xl">
|
|
24
|
+
<Title order={1} mb="sm">
|
|
25
|
+
{view.title}
|
|
26
|
+
</Title>
|
|
27
|
+
<Text component="div" c="dimmed">
|
|
28
|
+
This spec has no screens yet.
|
|
29
|
+
</Text>
|
|
30
|
+
</Container>
|
|
25
31
|
);
|
|
26
32
|
}
|
|
27
33
|
return (
|
|
28
|
-
<Navigate
|
|
29
|
-
to="/chart/$chartId"
|
|
30
|
-
params={{ chartId: place.chartId }}
|
|
31
|
-
search={{ ...(run ? { run } : {}), screen: place.name }}
|
|
32
|
-
replace
|
|
33
|
-
/>
|
|
34
|
+
<Navigate to="/chart/$chartId" params={{ chartId: place.chartId }} search={{ ...(run ? { run } : {}), screen: place.name }} replace />
|
|
34
35
|
);
|
|
35
36
|
}
|