@principal-ai/subsystems-react 0.51.2 → 0.51.3
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/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/subsystem/C4Graph.d.ts +12 -1
- package/dist/subsystem/C4Graph.d.ts.map +1 -1
- package/dist/subsystem/C4Graph.js +82 -14
- package/dist/subsystem/C4Graph.js.map +1 -1
- package/dist/subsystem/C4NodeCard.d.ts +22 -22
- package/dist/subsystem/C4NodeCard.d.ts.map +1 -1
- package/dist/subsystem/C4NodeCard.js +56 -77
- package/dist/subsystem/C4NodeCard.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +0 -6
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/TrailsPanel.d.ts.map +1 -1
- package/dist/subsystem/TrailsPanel.js +8 -4
- package/dist/subsystem/TrailsPanel.js.map +1 -1
- package/dist/subsystem/c4.d.ts +24 -86
- package/dist/subsystem/c4.d.ts.map +1 -1
- package/dist/subsystem/c4.js +6 -285
- package/dist/subsystem/c4.js.map +1 -1
- package/dist/subsystem/c4Evidence.js +1 -1
- package/dist/subsystem/c4Evidence.js.map +1 -1
- package/dist/subsystem/model.d.ts +8 -0
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/techIcons/bun.d.ts +11 -0
- package/dist/subsystem/techIcons/bun.d.ts.map +1 -0
- package/dist/subsystem/techIcons/bun.js +11 -0
- package/dist/subsystem/techIcons/bun.js.map +1 -0
- package/dist/subsystem/techIcons/index.d.ts +36 -0
- package/dist/subsystem/techIcons/index.d.ts.map +1 -0
- package/dist/subsystem/techIcons/index.js +55 -0
- package/dist/subsystem/techIcons/index.js.map +1 -0
- package/dist/subsystem/techIcons/node.d.ts +12 -0
- package/dist/subsystem/techIcons/node.d.ts.map +1 -0
- package/dist/subsystem/techIcons/node.js +39 -0
- package/dist/subsystem/techIcons/node.js.map +1 -0
- package/dist/subsystem/techIcons/react.d.ts +12 -0
- package/dist/subsystem/techIcons/react.d.ts.map +1 -0
- package/dist/subsystem/techIcons/react.js +19 -0
- package/dist/subsystem/techIcons/react.js.map +1 -0
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +16 -5
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +4 -6
- package/src/stories/Subsystem/C4Graph/C4Container.stories.tsx +212 -0
- package/src/stories/Subsystem/C4Graph/C4NodeGallery.stories.tsx +20 -10
- package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +4 -2
- package/src/subsystem/C4Graph.tsx +127 -12
- package/src/subsystem/C4NodeCard.test.ts +71 -77
- package/src/subsystem/C4NodeCard.tsx +65 -95
- package/src/subsystem/SubsystemComponentGraph.tsx +0 -6
- package/src/subsystem/TrailsPanel.tsx +10 -5
- package/src/subsystem/c4.ts +23 -357
- package/src/subsystem/c4Evidence.test.ts +1 -1
- package/src/subsystem/c4Evidence.ts +1 -1
- package/src/subsystem/model.ts +8 -0
- package/src/subsystem/techIcons/bun.ts +10 -0
- package/src/subsystem/techIcons/index.tsx +73 -0
- package/src/subsystem/techIcons/node.ts +38 -0
- package/src/subsystem/techIcons/react.ts +18 -0
- package/src/subsystem/techIcons/techIcons.test.ts +39 -0
- package/src/utils/elkLayout.ts +16 -5
- package/dist/stories/Subsystem/C4Graph/c4ProposedElements.fixture.d.ts +0 -493
- package/dist/stories/Subsystem/C4Graph/c4ProposedElements.fixture.d.ts.map +0 -1
- package/dist/stories/Subsystem/C4Graph/c4ProposedElements.fixture.js +0 -329
- package/dist/stories/Subsystem/C4Graph/c4ProposedElements.fixture.js.map +0 -1
- package/src/stories/Subsystem/C4Graph/ProposedElements.stories.tsx +0 -293
- package/src/stories/Subsystem/C4Graph/c4ProposedElements.fixture.ts +0 -366
- package/src/subsystem/c4.test.ts +0 -573
|
@@ -7,24 +7,24 @@
|
|
|
7
7
|
*
|
|
8
8
|
* The encoding has one job: make confirmation state legible at a glance. A box
|
|
9
9
|
* nobody has looked at must not read the same as a box a person signed off.
|
|
10
|
-
* Kind
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Kind is carried by shape and size — plus an icon for a container's sort,
|
|
11
|
+
* application vs data-store — and C4 notation is surfaced in the order a reader
|
|
12
|
+
* wants it: technology on the top row, description under the label.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
16
|
-
import {
|
|
16
|
+
import { AppWindow, Database } from 'lucide-react';
|
|
17
17
|
import type { LucideIcon } from 'lucide-react';
|
|
18
|
-
import {
|
|
18
|
+
import { TechMark, technologyBrand } from './techIcons';
|
|
19
19
|
import type { C4Node } from './c4';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Card box for a container: a wide rectangle. A container carries four things
|
|
23
|
-
* at once (
|
|
24
|
-
* and a strip would force the last two to
|
|
23
|
+
* at once (technology row, label, description, and the notation gap it still
|
|
24
|
+
* has), and a strip would force the last two to wrap hard.
|
|
25
25
|
*/
|
|
26
|
-
export const NODE_W =
|
|
27
|
-
export const NODE_H =
|
|
26
|
+
export const NODE_W = 250;
|
|
27
|
+
export const NODE_H = 150;
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* A component is a proper square — equal sides, and visibly smaller than the
|
|
@@ -32,7 +32,7 @@ export const NODE_H = 138;
|
|
|
32
32
|
* these are different C4 levels, and it survives grayscale and colour-blind
|
|
33
33
|
* reading where a border-weight difference alone does not.
|
|
34
34
|
*/
|
|
35
|
-
export const COMPONENT_SIZE =
|
|
35
|
+
export const COMPONENT_SIZE = 140;
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Per-level box size. The graph lays out against these, so a square component
|
|
@@ -45,12 +45,10 @@ export function nodeSize(node: C4Node): { width: number; height: number } {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* Fixed slot
|
|
49
|
-
*
|
|
50
|
-
* marker is two lines shorter and a row of cards reads as ragged.
|
|
48
|
+
* Fixed label slot, so the description below sits at the same y on every card
|
|
49
|
+
* even when a label is one line rather than two.
|
|
51
50
|
*/
|
|
52
51
|
const LABEL_SLOT_H = 40;
|
|
53
|
-
const GAP_SLOT_H = 18;
|
|
54
52
|
|
|
55
53
|
type Theme = ReturnType<typeof useTheme>['theme'];
|
|
56
54
|
|
|
@@ -91,32 +89,18 @@ export function nodeStyle(node: C4Node, theme: Theme, selected = false): NodeSty
|
|
|
91
89
|
// Dashed — an agent asked; a human has not answered.
|
|
92
90
|
return { color: theme.colors.warning ?? '#e8a33a', dash: 'dashed', width: weight };
|
|
93
91
|
case 'rejected':
|
|
94
|
-
// Should never be drawn (
|
|
92
|
+
// Should never be drawn (a rejected element is not projected); belt-and-braces.
|
|
95
93
|
return { color: muted, dash: 'dotted', width: 1 };
|
|
96
94
|
}
|
|
97
95
|
}
|
|
98
96
|
|
|
99
|
-
function baseName(path: string): string {
|
|
100
|
-
const parts = path.split('/').filter(Boolean);
|
|
101
|
-
return parts[parts.length - 1] ?? path;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
97
|
/**
|
|
105
|
-
* The
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* not repeated here. Technology is the remaining confirmed attribute; without
|
|
109
|
-
* one we fall back to what the document told us.
|
|
98
|
+
* The line under the label: the element's one-line responsibility, which C4
|
|
99
|
+
* asks every element to carry. No fallback — when nothing is stated, the
|
|
100
|
+
* notation-gap row says so rather than the card inventing a summary.
|
|
110
101
|
*/
|
|
111
102
|
export function nodeSubtitle(node: C4Node): string {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// No stated technology: fall back to what the document told us.
|
|
115
|
-
if (node.kind === 'component') {
|
|
116
|
-
return `${node.component?.construct ?? 'code'}${node.component?.file ? ` · ${baseName(node.component.file)}` : ''}`;
|
|
117
|
-
}
|
|
118
|
-
const count = node.members.length;
|
|
119
|
-
return `${describeConstructBreakdown(node.constructs)}${count > 0 ? ` · ${count} component${count === 1 ? '' : 's'}` : ''}`;
|
|
103
|
+
return node.description ?? '';
|
|
120
104
|
}
|
|
121
105
|
|
|
122
106
|
/**
|
|
@@ -130,31 +114,26 @@ export function nodeTag(node: C4Node): string {
|
|
|
130
114
|
}
|
|
131
115
|
|
|
132
116
|
/**
|
|
133
|
-
* The
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
117
|
+
* The icon on the top row: a container's sort, **application vs data-store**.
|
|
118
|
+
*
|
|
119
|
+
* The C4 kind itself does not need a glyph — the box shape already carries it
|
|
120
|
+
* (square vs rectangle vs pill), and the row's one text slot is worth more
|
|
121
|
+
* spent on the technology. Only containers have a C4-recognised split, so the
|
|
122
|
+
* other three kinds return nothing.
|
|
137
123
|
*/
|
|
138
|
-
export function nodeKindIcon(node: C4Node): LucideIcon {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return Container;
|
|
142
|
-
case 'component':
|
|
143
|
-
return Component;
|
|
144
|
-
case 'external-system':
|
|
145
|
-
return Cloud;
|
|
146
|
-
case 'person':
|
|
147
|
-
return UserRound;
|
|
148
|
-
}
|
|
124
|
+
export function nodeKindIcon(node: C4Node): LucideIcon | undefined {
|
|
125
|
+
if (node.kind !== 'container') return undefined;
|
|
126
|
+
return node.containerKind === 'data-store' ? Database : AppWindow;
|
|
149
127
|
}
|
|
150
128
|
|
|
151
129
|
/**
|
|
152
|
-
* The
|
|
153
|
-
*
|
|
154
|
-
* so
|
|
130
|
+
* The text on the top row: the technology the box is built with (Bun, React,
|
|
131
|
+
* Postgres…). Falls back to the container kind / C4 kind when no technology is
|
|
132
|
+
* stated, so the row is never blank.
|
|
155
133
|
*/
|
|
156
|
-
export function
|
|
157
|
-
|
|
134
|
+
export function nodeTopLabel(node: C4Node): string {
|
|
135
|
+
if (node.technology) return node.technology;
|
|
136
|
+
return node.kind === 'container' ? (node.containerKind ?? 'container') : node.kind;
|
|
158
137
|
}
|
|
159
138
|
|
|
160
139
|
/** How a box is drawn. Shape carries the C4 kind, independent of colour. */
|
|
@@ -226,11 +205,15 @@ export function C4NodeCard({ node, selected = false, handles, onClick }: C4NodeC
|
|
|
226
205
|
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
227
206
|
const style = nodeStyle(node, theme, selected);
|
|
228
207
|
const shape = nodeShape(node);
|
|
229
|
-
const missing = nodeMissing(node);
|
|
230
208
|
const Icon = nodeKindIcon(node);
|
|
231
|
-
const
|
|
209
|
+
const brand = technologyBrand(node.technology);
|
|
210
|
+
const topLabel = nodeTopLabel(node);
|
|
211
|
+
const subtitle = nodeSubtitle(node);
|
|
232
212
|
|
|
233
213
|
const size = nodeSize(node);
|
|
214
|
+
// A pill (a person) has no straight left edge, so left-aligned text reads as
|
|
215
|
+
// spilling out of the oval. Centre the text and inset it from the curve.
|
|
216
|
+
const centered = shape.kind === 'pill';
|
|
234
217
|
|
|
235
218
|
return (
|
|
236
219
|
<div
|
|
@@ -246,7 +229,7 @@ export function C4NodeCard({ node, selected = false, handles, onClick }: C4NodeC
|
|
|
246
229
|
// height on every card. In a graph grid that makes the row unscannable.
|
|
247
230
|
justifyContent: 'flex-start',
|
|
248
231
|
gap: 3,
|
|
249
|
-
padding: '10px 12px',
|
|
232
|
+
padding: centered ? '10px 30px' : '10px 12px',
|
|
250
233
|
borderRadius: shape.radius,
|
|
251
234
|
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
252
235
|
border: `${style.width} ${style.dash} ${style.color}`,
|
|
@@ -255,30 +238,26 @@ export function C4NodeCard({ node, selected = false, handles, onClick }: C4NodeC
|
|
|
255
238
|
fontFamily: theme.fonts.body,
|
|
256
239
|
}}
|
|
257
240
|
>
|
|
258
|
-
{/*
|
|
259
|
-
|
|
260
|
-
|
|
241
|
+
{/* Left: the brand mark when we hold the official one, then the
|
|
242
|
+
technology. Right: a container's C4 sort, application vs data-store.
|
|
243
|
+
The kind itself stays on the shape, confirmation on the border (see
|
|
244
|
+
`nodeShape`, `nodeStyle`). */}
|
|
261
245
|
<span
|
|
262
246
|
style={{
|
|
263
247
|
display: 'flex',
|
|
264
248
|
alignItems: 'center',
|
|
249
|
+
justifyContent: centered ? 'center' : 'space-between',
|
|
265
250
|
gap: 6,
|
|
266
251
|
fontFamily: theme.fonts.monospace,
|
|
267
252
|
fontSize: theme.fontSizes[0],
|
|
268
|
-
color:
|
|
253
|
+
color: muted,
|
|
269
254
|
letterSpacing: 0.5,
|
|
270
255
|
textTransform: 'uppercase',
|
|
271
256
|
minWidth: 0,
|
|
272
257
|
}}
|
|
273
258
|
>
|
|
274
|
-
<span
|
|
275
|
-
|
|
276
|
-
aria-label={nodeTag(node)}
|
|
277
|
-
style={{ display: 'inline-flex', flexShrink: 0 }}
|
|
278
|
-
>
|
|
279
|
-
<Icon size={13} strokeWidth={2.25} />
|
|
280
|
-
</span>
|
|
281
|
-
{typeLabel && (
|
|
259
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }}>
|
|
260
|
+
{brand && <TechMark brand={brand} />}
|
|
282
261
|
<span
|
|
283
262
|
style={{
|
|
284
263
|
fontWeight: 600,
|
|
@@ -288,7 +267,16 @@ export function C4NodeCard({ node, selected = false, handles, onClick }: C4NodeC
|
|
|
288
267
|
minWidth: 0,
|
|
289
268
|
}}
|
|
290
269
|
>
|
|
291
|
-
{
|
|
270
|
+
{topLabel}
|
|
271
|
+
</span>
|
|
272
|
+
</span>
|
|
273
|
+
{Icon && (
|
|
274
|
+
<span
|
|
275
|
+
title={node.containerKind ?? node.kind}
|
|
276
|
+
aria-label={node.containerKind ?? node.kind}
|
|
277
|
+
style={{ display: 'inline-flex', flexShrink: 0, color: style.color }}
|
|
278
|
+
>
|
|
279
|
+
<Icon size={13} strokeWidth={2.25} />
|
|
292
280
|
</span>
|
|
293
281
|
)}
|
|
294
282
|
</span>
|
|
@@ -307,43 +295,25 @@ export function C4NodeCard({ node, selected = false, handles, onClick }: C4NodeC
|
|
|
307
295
|
WebkitLineClamp: 2,
|
|
308
296
|
WebkitBoxOrient: 'vertical',
|
|
309
297
|
overflow: 'hidden',
|
|
298
|
+
textAlign: centered ? 'center' : 'left',
|
|
310
299
|
}}
|
|
311
300
|
title={node.label}
|
|
312
301
|
>
|
|
313
302
|
{node.label}
|
|
314
303
|
</span>
|
|
315
|
-
|
|
316
|
-
style={{
|
|
317
|
-
fontFamily: theme.fonts.monospace,
|
|
318
|
-
fontSize: theme.fontSizes[0],
|
|
319
|
-
color: muted,
|
|
320
|
-
whiteSpace: 'nowrap',
|
|
321
|
-
overflow: 'hidden',
|
|
322
|
-
textOverflow: 'ellipsis',
|
|
323
|
-
}}
|
|
324
|
-
title={node.description ?? node.members.join(', ')}
|
|
325
|
-
>
|
|
326
|
-
{nodeSubtitle(node)}
|
|
327
|
-
</span>
|
|
328
|
-
{/* Reserved row: without it, a card with no notation gap is two lines
|
|
329
|
-
shorter and the row of cards reads as ragged. */}
|
|
330
|
-
<span style={{ height: GAP_SLOT_H }} aria-hidden="true" />
|
|
331
|
-
{missing.length > 0 && (
|
|
304
|
+
{subtitle && (
|
|
332
305
|
<span
|
|
333
|
-
title={`C4 requires a ${missing.join(' and a ')} on every container`}
|
|
334
306
|
style={{
|
|
335
307
|
fontFamily: theme.fonts.monospace,
|
|
336
308
|
fontSize: theme.fontSizes[0],
|
|
337
|
-
color:
|
|
338
|
-
lineHeight: 1.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
WebkitBoxOrient: 'vertical',
|
|
343
|
-
overflow: 'hidden',
|
|
309
|
+
color: muted,
|
|
310
|
+
lineHeight: 1.35,
|
|
311
|
+
whiteSpace: 'normal',
|
|
312
|
+
overflowWrap: 'anywhere',
|
|
313
|
+
textAlign: centered ? 'center' : 'left',
|
|
344
314
|
}}
|
|
345
315
|
>
|
|
346
|
-
|
|
316
|
+
{subtitle}
|
|
347
317
|
</span>
|
|
348
318
|
)}
|
|
349
319
|
{handles}
|
|
@@ -1190,18 +1190,15 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1190
1190
|
const stepIssueBadgeByEdgeId = useMemo(() => {
|
|
1191
1191
|
const map = new Map<string, { kind: 'step_unconfirmed' | 'step_stale'; severity: 'info' | 'error'; count: number }>();
|
|
1192
1192
|
if (!issues?.length || !trails?.length) {
|
|
1193
|
-
console.log('[stepIssueBadge] early return: issues=', issues?.length, 'trails=', trails?.length);
|
|
1194
1193
|
return map;
|
|
1195
1194
|
}
|
|
1196
1195
|
for (const issue of issues) {
|
|
1197
1196
|
if (issue.kind !== 'step_unconfirmed' && issue.kind !== 'step_stale') continue;
|
|
1198
1197
|
const stepInfo = issueStep(issue);
|
|
1199
|
-
console.log('[stepIssueBadge] issue', issue.id, 'kind=', issue.kind, 'stepInfo=', stepInfo);
|
|
1200
1198
|
if (!stepInfo) continue;
|
|
1201
1199
|
const step = stepInfo.trail.steps[stepInfo.stepIndex];
|
|
1202
1200
|
if (!step) continue;
|
|
1203
1201
|
const edgeId = trailStepGraphEdgeId(step);
|
|
1204
|
-
console.log('[stepIssueBadge] -> edgeId=', edgeId);
|
|
1205
1202
|
const prev = map.get(edgeId);
|
|
1206
1203
|
// step_stale is worse than step_unconfirmed
|
|
1207
1204
|
const isWorse = issue.kind === 'step_stale' && prev?.kind !== 'step_stale';
|
|
@@ -1211,7 +1208,6 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1211
1208
|
count: (prev?.count ?? 0) + 1,
|
|
1212
1209
|
});
|
|
1213
1210
|
}
|
|
1214
|
-
console.log('[stepIssueBadge] final map size=', map.size);
|
|
1215
1211
|
return map;
|
|
1216
1212
|
}, [issues, trails, issueStep]);
|
|
1217
1213
|
|
|
@@ -2499,7 +2495,6 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
2499
2495
|
pathLength?: number;
|
|
2500
2496
|
} | undefined;
|
|
2501
2497
|
const stepIssue = stepIssueBadgeByEdgeId.get(e.id);
|
|
2502
|
-
console.log('[edgeLabels] edge', e.id, 'stepIssue=', stepIssue);
|
|
2503
2498
|
return {
|
|
2504
2499
|
id: e.id,
|
|
2505
2500
|
mechanism: d?.mechanism ?? 'uses',
|
|
@@ -2512,7 +2507,6 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
2512
2507
|
stepIssue,
|
|
2513
2508
|
};
|
|
2514
2509
|
});
|
|
2515
|
-
console.log('[edgeLabels] total labels=', labels.length, 'with stepIssue=', labels.filter(l => l.stepIssue).length);
|
|
2516
2510
|
return labels;
|
|
2517
2511
|
}, [dispEdges, selectedFlowStepNos, stepIssueBadgeByEdgeId]);
|
|
2518
2512
|
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
reorderTrails,
|
|
21
21
|
trailStepGraphEdgeId,
|
|
22
22
|
type SubsystemTrail,
|
|
23
|
+
type SubsystemTrailStep,
|
|
23
24
|
} from './model';
|
|
24
25
|
import { buildStepBrief } from './trailBrief';
|
|
25
26
|
|
|
@@ -90,10 +91,14 @@ function TrailFlow({
|
|
|
90
91
|
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
91
92
|
const hoverBg = theme.colors.background;
|
|
92
93
|
const wholeFlowActive = active !== null && active.stepIndex === null;
|
|
93
|
-
// A step is "proposed" when
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
94
|
+
// A step is "proposed" when it says so itself OR either endpoint component
|
|
95
|
+
// is proposed — the step flag covers planned seams whose endpoints already
|
|
96
|
+
// exist as real components.
|
|
97
|
+
const stepProposed = (step: SubsystemTrailStep) =>
|
|
98
|
+
step.proposed === true ||
|
|
99
|
+
(proposedAliases != null &&
|
|
100
|
+
(proposedAliases.has(step.from) || proposedAliases.has(step.to)));
|
|
101
|
+
const touchesProposed = trail.steps.some((s) => stepProposed(s));
|
|
97
102
|
const [headerHover, setHeaderHover] = useState(false);
|
|
98
103
|
const [collapseHover, setCollapseHover] = useState(false);
|
|
99
104
|
const [playHover, setPlayHover] = useState(false);
|
|
@@ -366,7 +371,7 @@ function TrailFlow({
|
|
|
366
371
|
>
|
|
367
372
|
{trail.steps.map((step, i) => {
|
|
368
373
|
const stepActive = active !== null && active.stepIndex === i;
|
|
369
|
-
const proposed = stepProposed(step
|
|
374
|
+
const proposed = stepProposed(step);
|
|
370
375
|
const revealed =
|
|
371
376
|
stepActive || hoveredStep === i || focusedStep === i || copiedStep === i;
|
|
372
377
|
const copied = copiedStep === i;
|