@principal-ai/subsystems-react 0.55.2 → 0.56.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/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/pierre/PierreFileView.js +3 -3
- package/dist/pierre/PierreFileView.js.map +1 -1
- package/dist/pierre/PierreTrailCodeView.d.ts +1 -6
- package/dist/pierre/PierreTrailCodeView.d.ts.map +1 -1
- package/dist/pierre/PierreTrailCodeView.js +21 -18
- package/dist/pierre/PierreTrailCodeView.js.map +1 -1
- package/dist/pierre/fileAvailability.d.ts +4 -7
- package/dist/pierre/fileAvailability.d.ts.map +1 -1
- package/dist/pierre/fileAvailability.js +5 -10
- package/dist/pierre/fileAvailability.js.map +1 -1
- package/dist/subsystem/C4Graph.d.ts.map +1 -1
- package/dist/subsystem/C4Graph.js +1 -1
- package/dist/subsystem/C4Graph.js.map +1 -1
- package/dist/subsystem/DecorationPanel.d.ts +37 -0
- package/dist/subsystem/DecorationPanel.d.ts.map +1 -0
- package/dist/subsystem/DecorationPanel.js +147 -0
- package/dist/subsystem/DecorationPanel.js.map +1 -0
- package/dist/subsystem/FileDrawer.d.ts +3 -1
- package/dist/subsystem/FileDrawer.d.ts.map +1 -1
- package/dist/subsystem/FileDrawer.js +2 -2
- package/dist/subsystem/FileDrawer.js.map +1 -1
- package/dist/subsystem/IssueList.d.ts.map +1 -1
- package/dist/subsystem/IssueList.js +26 -0
- package/dist/subsystem/IssueList.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +9 -5
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +37 -47
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/TrailStackBreadcrumb.d.ts +21 -0
- package/dist/subsystem/TrailStackBreadcrumb.d.ts.map +1 -0
- package/dist/subsystem/TrailStackBreadcrumb.js +77 -0
- package/dist/subsystem/TrailStackBreadcrumb.js.map +1 -0
- package/dist/subsystem/TrailStepsNav.d.ts +4 -3
- package/dist/subsystem/TrailStepsNav.d.ts.map +1 -1
- package/dist/subsystem/TrailStepsNav.js +13 -16
- package/dist/subsystem/TrailStepsNav.js.map +1 -1
- package/dist/subsystem/TrailsPanel.d.ts +9 -7
- package/dist/subsystem/TrailsPanel.d.ts.map +1 -1
- package/dist/subsystem/TrailsPanel.js +126 -76
- package/dist/subsystem/TrailsPanel.js.map +1 -1
- package/dist/subsystem/decoration.d.ts +112 -0
- package/dist/subsystem/decoration.d.ts.map +1 -0
- package/dist/subsystem/decoration.js +54 -0
- package/dist/subsystem/decoration.js.map +1 -0
- package/dist/subsystem/model.d.ts +5 -18
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +3 -11
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +17 -24
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/subsystem/stepEmphasis.d.ts +78 -0
- package/dist/subsystem/stepEmphasis.d.ts.map +1 -0
- package/dist/subsystem/stepEmphasis.js +89 -0
- package/dist/subsystem/stepEmphasis.js.map +1 -0
- package/dist/subsystem/trailBrief.d.ts.map +1 -1
- package/dist/subsystem/trailBrief.js +20 -0
- package/dist/subsystem/trailBrief.js.map +1 -1
- package/dist/subsystem/trailStack.d.ts +53 -0
- package/dist/subsystem/trailStack.d.ts.map +1 -0
- package/dist/subsystem/trailStack.js +154 -0
- package/dist/subsystem/trailStack.js.map +1 -0
- package/dist/subsystem/trailStepDisplay.d.ts +38 -0
- package/dist/subsystem/trailStepDisplay.d.ts.map +1 -0
- package/dist/subsystem/trailStepDisplay.js +41 -0
- package/dist/subsystem/trailStepDisplay.js.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +47 -1
- package/src/pierre/PierreFileView.tsx +3 -3
- package/src/pierre/PierreTrailCodeView.tsx +25 -28
- package/src/pierre/fileAvailability.test.ts +0 -6
- package/src/pierre/fileAvailability.ts +5 -13
- package/src/stories/Canvases/Subsystem/Deprecated.stories.tsx +3 -17
- package/src/stories/Canvases/Subsystem/NodeAnatomy.stories.tsx +3 -31
- package/src/stories/Canvases/Subsystem/StepEmphasis.stories.tsx +528 -0
- package/src/stories/Canvases/Subsystem/Trails.stories.tsx +0 -187
- package/src/stories/Components/DecorationPanel.stories.tsx +160 -0
- package/src/subsystem/C4Graph.tsx +0 -1
- package/src/subsystem/DecorationPanel.test.tsx +128 -0
- package/src/subsystem/DecorationPanel.tsx +334 -0
- package/src/subsystem/FileDrawer.tsx +6 -0
- package/src/subsystem/IssueList.tsx +26 -0
- package/src/subsystem/SubsystemComponentGraph.tsx +68 -65
- package/src/subsystem/TrailStackBreadcrumb.test.tsx +109 -0
- package/src/subsystem/TrailStackBreadcrumb.tsx +172 -0
- package/src/subsystem/TrailStepsNav.test.tsx +35 -0
- package/src/subsystem/TrailStepsNav.tsx +18 -19
- package/src/subsystem/TrailsPanel.test.tsx +149 -0
- package/src/subsystem/TrailsPanel.tsx +165 -39
- package/src/subsystem/decoration.test.ts +72 -0
- package/src/subsystem/decoration.ts +151 -0
- package/src/subsystem/model.test.ts +7 -22
- package/src/subsystem/model.ts +6 -20
- package/src/subsystem/nodes.tsx +19 -28
- package/src/subsystem/stepEmphasis.test.ts +110 -0
- package/src/subsystem/stepEmphasis.ts +151 -0
- package/src/subsystem/trailBrief.test.ts +29 -0
- package/src/subsystem/trailBrief.ts +20 -0
- package/src/subsystem/trailStack.test.ts +133 -0
- package/src/subsystem/trailStack.ts +229 -0
- package/src/subsystem/trailStepDisplay.ts +68 -0
- package/src/stories/Canvases/Subsystem/Proposed.stories.tsx +0 -256
- package/src/stories/Canvases/Subsystem/ProposedTrails.stories.tsx +0 -184
|
@@ -170,7 +170,6 @@ function TrailsDemo() {
|
|
|
170
170
|
trail,
|
|
171
171
|
stepIndex,
|
|
172
172
|
onOpenFile,
|
|
173
|
-
proposedAliases,
|
|
174
173
|
resolveSymbol,
|
|
175
174
|
onSymbolClick,
|
|
176
175
|
onVisibleStepChange,
|
|
@@ -181,7 +180,6 @@ function TrailsDemo() {
|
|
|
181
180
|
readFile={readStoryFile}
|
|
182
181
|
contextLines={4}
|
|
183
182
|
onOpenFile={onOpenFile}
|
|
184
|
-
proposedAliases={proposedAliases}
|
|
185
183
|
resolveSymbol={resolveSymbol}
|
|
186
184
|
onSymbolClick={onSymbolClick}
|
|
187
185
|
onVisibleStepChange={onVisibleStepChange}
|
|
@@ -223,189 +221,6 @@ export const ThreeTrails: Story = {
|
|
|
223
221
|
render: () => <TrailsDemo />,
|
|
224
222
|
};
|
|
225
223
|
|
|
226
|
-
/**
|
|
227
|
-
* The proposed-seam failure mode: a trail step whose component is
|
|
228
|
-
* `proposed` points at a file that isn't in the checkout yet (no story
|
|
229
|
-
* fixture). The step must render an inline "Proposed — … isn't in the local
|
|
230
|
-
* checkout yet." placeholder with no Open-file affordance, while the live steps
|
|
231
|
-
* around it still render their snippets — one bad step must not blank the flow.
|
|
232
|
-
*/
|
|
233
|
-
const proposedComponents: SubsystemComponent[] = [
|
|
234
|
-
...drawingComponents,
|
|
235
|
-
{
|
|
236
|
-
alias: 'runs',
|
|
237
|
-
name: 'SubsystemModelRunStore',
|
|
238
|
-
construct: 'store',
|
|
239
|
-
file: 'src/runs/subsystemModelRuns.ts',
|
|
240
|
-
purl: 'pkg:github/principal-ai/desktop-app',
|
|
241
|
-
symbol: 'SubsystemModelRunStore',
|
|
242
|
-
purpose: 'proposed store associating maintain runs with a model',
|
|
243
|
-
proposed: true,
|
|
244
|
-
process: 'draw-host',
|
|
245
|
-
},
|
|
246
|
-
];
|
|
247
|
-
|
|
248
|
-
const proposedTrails: SubsystemTrail[] = [
|
|
249
|
-
{
|
|
250
|
-
id: 'tl-associate-run',
|
|
251
|
-
title: 'Associate a finished run with its model (proposed)',
|
|
252
|
-
steps: [
|
|
253
|
-
{
|
|
254
|
-
from: 'host',
|
|
255
|
-
to: 'storage',
|
|
256
|
-
mechanism: 'calls',
|
|
257
|
-
file: 'src/hooks/useDrawingsHost.ts',
|
|
258
|
-
span: { startLine: 45, endLine: 45, contentHash: 'fixture' },
|
|
259
|
-
purl: stepPurl('src/hooks/useDrawingsHost.ts'),
|
|
260
|
-
symbol: 'useDrawingsHost.openDrawing',
|
|
261
|
-
annotation: 'Live step — its snippet still renders.',
|
|
262
|
-
},
|
|
263
|
-
{
|
|
264
|
-
from: 'storage',
|
|
265
|
-
to: 'runs',
|
|
266
|
-
mechanism: 'writes',
|
|
267
|
-
file: 'src/runs/subsystemModelRuns.ts',
|
|
268
|
-
span: { startLine: 1, endLine: 1, contentHash: 'fixture' },
|
|
269
|
-
purl: stepPurl('src/runs/subsystemModelRuns.ts'),
|
|
270
|
-
symbol: 'SubsystemModelRunStore.write',
|
|
271
|
-
annotation: 'Proposed seam — the file is not in the checkout yet.',
|
|
272
|
-
},
|
|
273
|
-
],
|
|
274
|
-
},
|
|
275
|
-
// Neighbours for the wrapped title above: a couple of one-line titles and
|
|
276
|
-
// one that wraps to two, so the header's padding reads against the rows on
|
|
277
|
-
// either side of it rather than in isolation.
|
|
278
|
-
{
|
|
279
|
-
id: 'tl-open-drawing',
|
|
280
|
-
title: 'Open drawing',
|
|
281
|
-
steps: [
|
|
282
|
-
{
|
|
283
|
-
from: 'panel',
|
|
284
|
-
to: 'host',
|
|
285
|
-
mechanism: 'produces',
|
|
286
|
-
file: 'src/panels/DrawingsLeftPanel.tsx',
|
|
287
|
-
span: { startLine: 85, endLine: 85, contentHash: 'fixture' },
|
|
288
|
-
purl: stepPurl('src/panels/DrawingsLeftPanel.tsx'),
|
|
289
|
-
symbol: 'DrawingsLeftPanel.openDrawing',
|
|
290
|
-
annotation: 'Emits the open intent up to the host.',
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
from: 'host',
|
|
294
|
-
to: 'storage',
|
|
295
|
-
mechanism: 'calls',
|
|
296
|
-
file: 'src/hooks/useDrawingsHost.ts',
|
|
297
|
-
span: { startLine: 45, endLine: 45, contentHash: 'fixture' },
|
|
298
|
-
purl: stepPurl('src/hooks/useDrawingsHost.ts'),
|
|
299
|
-
symbol: 'useDrawingsHost.openDrawing',
|
|
300
|
-
},
|
|
301
|
-
],
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
id: 'tl-save-drawing',
|
|
305
|
-
title: 'Save the drawing and refresh the list',
|
|
306
|
-
steps: [
|
|
307
|
-
{
|
|
308
|
-
from: 'tab',
|
|
309
|
-
to: 'fs',
|
|
310
|
-
mechanism: 'calls',
|
|
311
|
-
file: 'src/components/DrawingTabContent.tsx',
|
|
312
|
-
span: { startLine: 112, endLine: 112, contentHash: 'fixture' },
|
|
313
|
-
purl: stepPurl('src/components/DrawingTabContent.tsx'),
|
|
314
|
-
symbol: 'DrawingTabContent.save',
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
from: 'host',
|
|
318
|
-
to: 'panel',
|
|
319
|
-
mechanism: 'produces',
|
|
320
|
-
file: 'src/hooks/useDrawingsHost.ts',
|
|
321
|
-
span: { startLine: 64, endLine: 64, contentHash: 'fixture' },
|
|
322
|
-
purl: stepPurl('src/hooks/useDrawingsHost.ts'),
|
|
323
|
-
symbol: 'useDrawingsHost.onSaved',
|
|
324
|
-
},
|
|
325
|
-
],
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
id: 'tl-delete-drawing',
|
|
329
|
-
title: 'Delete drawing',
|
|
330
|
-
steps: [
|
|
331
|
-
{
|
|
332
|
-
from: 'host',
|
|
333
|
-
to: 'fs',
|
|
334
|
-
mechanism: 'calls',
|
|
335
|
-
file: 'src/hooks/useDrawingsHost.ts',
|
|
336
|
-
span: { startLine: 66, endLine: 66, contentHash: 'fixture' },
|
|
337
|
-
purl: stepPurl('src/hooks/useDrawingsHost.ts'),
|
|
338
|
-
symbol: 'useDrawingsHost.deleteDrawing',
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
from: 'panel',
|
|
342
|
-
to: 'storage',
|
|
343
|
-
mechanism: 'calls',
|
|
344
|
-
file: 'src/panels/DrawingsLeftPanel.tsx',
|
|
345
|
-
span: { startLine: 56, endLine: 56, contentHash: 'fixture' },
|
|
346
|
-
purl: stepPurl('src/panels/DrawingsLeftPanel.tsx'),
|
|
347
|
-
symbol: 'DrawingsLeftPanel.scan',
|
|
348
|
-
},
|
|
349
|
-
],
|
|
350
|
-
},
|
|
351
|
-
];
|
|
352
|
-
|
|
353
|
-
function ProposedMissingStepDemo() {
|
|
354
|
-
const renderTrailViewer = useCallback(
|
|
355
|
-
({
|
|
356
|
-
trail,
|
|
357
|
-
stepIndex,
|
|
358
|
-
onOpenFile,
|
|
359
|
-
proposedAliases,
|
|
360
|
-
resolveSymbol,
|
|
361
|
-
onSymbolClick,
|
|
362
|
-
onVisibleStepChange,
|
|
363
|
-
}: TrailViewerContext) => (
|
|
364
|
-
<PierreTrailCodeView
|
|
365
|
-
trail={trail}
|
|
366
|
-
stepIndex={stepIndex}
|
|
367
|
-
readFile={readStoryFile}
|
|
368
|
-
contextLines={4}
|
|
369
|
-
onOpenFile={onOpenFile}
|
|
370
|
-
proposedAliases={proposedAliases}
|
|
371
|
-
resolveSymbol={resolveSymbol}
|
|
372
|
-
onSymbolClick={onSymbolClick}
|
|
373
|
-
onVisibleStepChange={onVisibleStepChange}
|
|
374
|
-
/>
|
|
375
|
-
),
|
|
376
|
-
[],
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
return (
|
|
380
|
-
<div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
381
|
-
<SubsystemComponentGraph
|
|
382
|
-
components={proposedComponents}
|
|
383
|
-
trails={proposedTrails}
|
|
384
|
-
title="proposed seam with a missing file"
|
|
385
|
-
description="The **Trails** panel lists four flows so the long, wrapped title (*Associate a finished run with its model (proposed)*) reads against its neighbours — two one-line titles and one (*Save the drawing and refresh the list*) that also wraps. Expand **Associate a finished run with its model** and click either step: the proposed step has no file in the checkout, so its snippet shows an inline *Proposed — … isn't in the local checkout yet.* placeholder instead of failing the whole flow."
|
|
386
|
-
renderTrailViewer={renderTrailViewer}
|
|
387
|
-
renderFileViewer={(file) => (
|
|
388
|
-
<div
|
|
389
|
-
style={{
|
|
390
|
-
padding: 12,
|
|
391
|
-
fontFamily: 'monospace',
|
|
392
|
-
fontSize: 12,
|
|
393
|
-
color: '#bbb',
|
|
394
|
-
whiteSpace: 'pre',
|
|
395
|
-
}}
|
|
396
|
-
>
|
|
397
|
-
{`// ${file}\n …`}
|
|
398
|
-
</div>
|
|
399
|
-
)}
|
|
400
|
-
/>
|
|
401
|
-
</div>
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
export const ProposedMissingStep: Story = {
|
|
406
|
-
render: () => <ProposedMissingStepDemo />,
|
|
407
|
-
};
|
|
408
|
-
|
|
409
224
|
// --- Clickable constructs -------------------------------------------------
|
|
410
225
|
//
|
|
411
226
|
// A trail step's line is an edge between two constructs. When the host
|
|
@@ -554,7 +369,6 @@ function ClickableConstructsDemo() {
|
|
|
554
369
|
trail,
|
|
555
370
|
stepIndex,
|
|
556
371
|
onOpenFile,
|
|
557
|
-
proposedAliases,
|
|
558
372
|
resolveSymbol,
|
|
559
373
|
onSymbolClick,
|
|
560
374
|
onVisibleStepChange,
|
|
@@ -565,7 +379,6 @@ function ClickableConstructsDemo() {
|
|
|
565
379
|
readFile={readClickableFile}
|
|
566
380
|
contextLines={4}
|
|
567
381
|
onOpenFile={onOpenFile}
|
|
568
|
-
proposedAliases={proposedAliases}
|
|
569
382
|
resolveSymbol={resolveSymbol}
|
|
570
383
|
onSymbolClick={onSymbolClick}
|
|
571
384
|
onVisibleStepChange={onVisibleStepChange}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
4
|
+
import {
|
|
5
|
+
DecorationPanel,
|
|
6
|
+
type DecorationPanelProps,
|
|
7
|
+
} from '../../subsystem/DecorationPanel';
|
|
8
|
+
import {
|
|
9
|
+
decorationTrailKey,
|
|
10
|
+
type SubsystemDecoration,
|
|
11
|
+
} from '../../subsystem/decoration';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* PROTOTYPE: the decoration container as a reading surface.
|
|
15
|
+
*
|
|
16
|
+
* The fixture deliberately spans two models (desktop + sync service) and
|
|
17
|
+
* includes one draft trail + draft structure, to show that a decoration is
|
|
18
|
+
* additive and unverified — the base models stay untouched.
|
|
19
|
+
*/
|
|
20
|
+
const meta = {
|
|
21
|
+
title: 'Components/DecorationPanel',
|
|
22
|
+
component: DecorationPanel,
|
|
23
|
+
parameters: { layout: 'fullscreen' },
|
|
24
|
+
decorators: [
|
|
25
|
+
(Story) => (
|
|
26
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
27
|
+
<Story />
|
|
28
|
+
</ThemeProvider>
|
|
29
|
+
),
|
|
30
|
+
],
|
|
31
|
+
} satisfies Meta<typeof DecorationPanel>;
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
type Story = StoryObj<typeof meta>;
|
|
35
|
+
|
|
36
|
+
const decoration: SubsystemDecoration = {
|
|
37
|
+
version: 1,
|
|
38
|
+
id: 'dec-local-first-sync',
|
|
39
|
+
title: 'Local-first sync',
|
|
40
|
+
description: [
|
|
41
|
+
'How a drawing reaches disk **and** the sync service, in the order a reader should follow it.',
|
|
42
|
+
'',
|
|
43
|
+
'- The desktop stays the source of truth for the open tab.',
|
|
44
|
+
'- The service is a projection; it never blocks the save.',
|
|
45
|
+
'',
|
|
46
|
+
'> This is a reading path, not a verified model claim.',
|
|
47
|
+
].join('\n'),
|
|
48
|
+
trails: [
|
|
49
|
+
{
|
|
50
|
+
kind: 'base',
|
|
51
|
+
modelId: 'sg-desktop',
|
|
52
|
+
trailId: 'tl-save',
|
|
53
|
+
stepAnchors: [{ stepKey: 'fixture', note: 'Write first; the sync hop is optional.' }],
|
|
54
|
+
},
|
|
55
|
+
{ kind: 'base', modelId: 'sg-server', trailId: 'tl-persist' },
|
|
56
|
+
{
|
|
57
|
+
kind: 'draft',
|
|
58
|
+
modelId: 'sg-desktop',
|
|
59
|
+
trail: {
|
|
60
|
+
id: 'tl-planned-sync',
|
|
61
|
+
title: 'Planned sync (draft)',
|
|
62
|
+
steps: [
|
|
63
|
+
{
|
|
64
|
+
from: 'tab',
|
|
65
|
+
to: 'sync-agent',
|
|
66
|
+
mechanism: 'produces',
|
|
67
|
+
file: 'src/components/DrawingTabContent.tsx',
|
|
68
|
+
purl: 'pkg:github/principal-ai/desktop-app#src/components/DrawingTabContent.tsx',
|
|
69
|
+
symbol: 'DrawingTabContent.emitSaved',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
from: 'sync-agent',
|
|
73
|
+
to: 'sync-service',
|
|
74
|
+
mechanism: 'calls',
|
|
75
|
+
file: 'src/sync/syncAgent.ts',
|
|
76
|
+
purl: 'pkg:github/principal-ai/desktop-app#src/sync/syncAgent.ts',
|
|
77
|
+
symbol: 'SyncAgent.push',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
draft: {
|
|
84
|
+
components: [
|
|
85
|
+
{
|
|
86
|
+
alias: 'sync-agent',
|
|
87
|
+
name: 'SyncAgent',
|
|
88
|
+
construct: 'class',
|
|
89
|
+
purpose: 'queues a saved drawing for the sync service',
|
|
90
|
+
process: 'draw-host',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
alias: 'sync-service',
|
|
94
|
+
name: 'SyncService',
|
|
95
|
+
construct: 'external',
|
|
96
|
+
purpose: 'remote projection of saved drawings',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
edges: [
|
|
100
|
+
{ from: 'tab', to: 'sync-agent', mechanism: 'produces' },
|
|
101
|
+
{ from: 'sync-agent', to: 'sync-service', mechanism: 'calls' },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
provenance: {
|
|
105
|
+
source: 'author',
|
|
106
|
+
author: 'you',
|
|
107
|
+
at: '2026-10-20T12:00:00.000Z',
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const resolveTrail: NonNullable<DecorationPanelProps['resolveTrail']> = (ref) => {
|
|
112
|
+
if (ref.kind === 'draft') return undefined;
|
|
113
|
+
const key = decorationTrailKey(ref);
|
|
114
|
+
if (key === 'sg-desktop:tl-save') return { title: 'Save drawing', stepCount: 4 };
|
|
115
|
+
if (key === 'sg-server:tl-persist') return { title: 'Persist file', stepCount: 5 };
|
|
116
|
+
return undefined;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const modelLabels = new Map([
|
|
120
|
+
['sg-desktop', 'desktop-app'],
|
|
121
|
+
['sg-server', 'sync-service'],
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
function Demo() {
|
|
125
|
+
const [active, setActive] = useState(true);
|
|
126
|
+
const [selected, setSelected] = useState<string>('none');
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div style={{ display: 'flex', height: '100vh', background: '#0f1115' }}>
|
|
130
|
+
<div style={{ width: 420, height: '100%', borderRight: '1px solid #2a2f3a' }}>
|
|
131
|
+
<DecorationPanel
|
|
132
|
+
decoration={decoration}
|
|
133
|
+
active={active}
|
|
134
|
+
onToggleActive={setActive}
|
|
135
|
+
resolveTrail={resolveTrail}
|
|
136
|
+
modelLabels={modelLabels}
|
|
137
|
+
onSelectTrail={(ref) => setSelected(decorationTrailKey(ref))}
|
|
138
|
+
onSelectDraftComponent={(component) => setSelected(component.alias)}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
<div
|
|
142
|
+
style={{
|
|
143
|
+
flex: 1,
|
|
144
|
+
padding: 24,
|
|
145
|
+
color: '#9aa4b2',
|
|
146
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
|
|
147
|
+
fontSize: 12,
|
|
148
|
+
lineHeight: 1.6,
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<div>active: {String(active)}</div>
|
|
152
|
+
<div>selected: {selected}</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const TwoModels: Story = {
|
|
159
|
+
render: () => <Demo />,
|
|
160
|
+
};
|
|
@@ -2711,7 +2711,6 @@ function Inner({ model, onSelectNode, onSelectEdge, onTrailClick, onToggleContai
|
|
|
2711
2711
|
>
|
|
2712
2712
|
{selectedConstruct.component.alias}
|
|
2713
2713
|
{selectedConstruct.role ? ` · ${selectedConstruct.role}` : ''}
|
|
2714
|
-
{selectedConstruct.component.proposed ? ' · proposed' : ''}
|
|
2715
2714
|
</div>
|
|
2716
2715
|
</div>
|
|
2717
2716
|
)}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { afterEach, describe, expect, mock, test } from 'bun:test';
|
|
2
|
+
import { Window } from 'happy-dom';
|
|
3
|
+
import { cleanup, fireEvent, render } from '@testing-library/react/pure';
|
|
4
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
5
|
+
import { DecorationPanel, type DecorationPanelProps } from './DecorationPanel';
|
|
6
|
+
import { decorationTrailKey, type SubsystemDecoration } from './decoration';
|
|
7
|
+
|
|
8
|
+
// Same DOM shim as TrailsPanel.test: happy-dom lacks ResizeObserver, which
|
|
9
|
+
// themed-markdown uses to measure its slide container.
|
|
10
|
+
const dom = new Window();
|
|
11
|
+
const globals = globalThis as unknown as Record<string, unknown>;
|
|
12
|
+
globals.window = dom;
|
|
13
|
+
globals.document = dom.document;
|
|
14
|
+
globals.navigator = dom.navigator;
|
|
15
|
+
globals.HTMLElement = dom.HTMLElement;
|
|
16
|
+
globals.Node = dom.Node;
|
|
17
|
+
globals.Element = dom.Element;
|
|
18
|
+
globals.Event = dom.Event;
|
|
19
|
+
globals.MouseEvent = dom.MouseEvent;
|
|
20
|
+
globals.FocusEvent = dom.FocusEvent;
|
|
21
|
+
globals.KeyboardEvent = dom.KeyboardEvent;
|
|
22
|
+
globals.getComputedStyle = dom.getComputedStyle.bind(dom);
|
|
23
|
+
globals.requestAnimationFrame = dom.requestAnimationFrame.bind(dom);
|
|
24
|
+
globals.cancelAnimationFrame = dom.cancelAnimationFrame.bind(dom);
|
|
25
|
+
globals.localStorage = dom.localStorage;
|
|
26
|
+
globals.sessionStorage = dom.sessionStorage;
|
|
27
|
+
globals.matchMedia = dom.matchMedia.bind(dom);
|
|
28
|
+
globals.ResizeObserver = class {
|
|
29
|
+
observe() {}
|
|
30
|
+
unobserve() {}
|
|
31
|
+
disconnect() {}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
afterEach(cleanup);
|
|
35
|
+
|
|
36
|
+
const decoration: SubsystemDecoration = {
|
|
37
|
+
version: 1,
|
|
38
|
+
id: 'dec-sync',
|
|
39
|
+
title: 'Local-first sync',
|
|
40
|
+
description: 'A **reading path** for the save flow.',
|
|
41
|
+
trails: [
|
|
42
|
+
{ kind: 'base', modelId: 'sg-desktop', trailId: 'tl-save' },
|
|
43
|
+
{ kind: 'base', modelId: 'sg-server', trailId: 'tl-persist' },
|
|
44
|
+
{
|
|
45
|
+
kind: 'draft',
|
|
46
|
+
modelId: 'sg-desktop',
|
|
47
|
+
trail: { id: 'tl-planned', title: 'Planned sync', steps: [] },
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
draft: {
|
|
51
|
+
components: [
|
|
52
|
+
{ alias: 'sync-agent', name: 'SyncAgent', construct: 'class' },
|
|
53
|
+
],
|
|
54
|
+
edges: [{ from: 'tab', to: 'sync-agent', mechanism: 'produces' }],
|
|
55
|
+
},
|
|
56
|
+
provenance: { source: 'author', at: '2026-10-20T00:00:00.000Z' },
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const resolveTrail: NonNullable<DecorationPanelProps['resolveTrail']> = (ref) => {
|
|
60
|
+
if (ref.kind === 'draft') return undefined;
|
|
61
|
+
const key = decorationTrailKey(ref);
|
|
62
|
+
if (key === 'sg-desktop:tl-save') return { title: 'Save drawing', stepCount: 4 };
|
|
63
|
+
if (key === 'sg-server:tl-persist') return { title: 'Persist file', stepCount: 5 };
|
|
64
|
+
return undefined;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function renderPanel(overrides: Partial<DecorationPanelProps> = {}) {
|
|
68
|
+
const props: DecorationPanelProps = {
|
|
69
|
+
decoration,
|
|
70
|
+
active: true,
|
|
71
|
+
onToggleActive: () => {},
|
|
72
|
+
resolveTrail,
|
|
73
|
+
modelLabels: new Map([
|
|
74
|
+
['sg-desktop', 'desktop-app'],
|
|
75
|
+
['sg-server', 'sync-service'],
|
|
76
|
+
]),
|
|
77
|
+
...overrides,
|
|
78
|
+
};
|
|
79
|
+
return render(
|
|
80
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
81
|
+
<DecorationPanel {...props} />
|
|
82
|
+
</ThemeProvider>,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
describe('DecorationPanel', () => {
|
|
87
|
+
test('renders the description, grouped trails, and draft structure', () => {
|
|
88
|
+
const { getByText, container } = renderPanel();
|
|
89
|
+
expect(container.textContent).toContain('reading path');
|
|
90
|
+
expect(getByText('desktop-app')).not.toBeNull();
|
|
91
|
+
expect(getByText('sync-service')).not.toBeNull();
|
|
92
|
+
expect(getByText('Save drawing')).not.toBeNull();
|
|
93
|
+
expect(getByText('Persist file')).not.toBeNull();
|
|
94
|
+
expect(getByText('Planned sync')).not.toBeNull();
|
|
95
|
+
expect(getByText('draft')).not.toBeNull();
|
|
96
|
+
expect(getByText('SyncAgent')).not.toBeNull();
|
|
97
|
+
expect(getByText('+ 1 draft edge')).not.toBeNull();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('the toggle reports the next active state', () => {
|
|
101
|
+
const onToggleActive = mock(() => {});
|
|
102
|
+
const { getByLabelText } = renderPanel({ onToggleActive });
|
|
103
|
+
fireEvent.click(getByLabelText('Turn decoration off'));
|
|
104
|
+
expect(onToggleActive).toHaveBeenCalledWith(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('selecting a trail hands the ref back to the host', () => {
|
|
108
|
+
const onSelectTrail = mock(() => {});
|
|
109
|
+
const { getByText } = renderPanel({ onSelectTrail });
|
|
110
|
+
fireEvent.click(getByText('Save drawing'));
|
|
111
|
+
expect(onSelectTrail).toHaveBeenCalledWith({
|
|
112
|
+
kind: 'base',
|
|
113
|
+
modelId: 'sg-desktop',
|
|
114
|
+
trailId: 'tl-save',
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('selecting a draft component hands the component back', () => {
|
|
119
|
+
const onSelectDraftComponent = mock(() => {});
|
|
120
|
+
const { getByText } = renderPanel({ onSelectDraftComponent });
|
|
121
|
+
fireEvent.click(getByText('SyncAgent'));
|
|
122
|
+
expect(onSelectDraftComponent).toHaveBeenCalledWith({
|
|
123
|
+
alias: 'sync-agent',
|
|
124
|
+
name: 'SyncAgent',
|
|
125
|
+
construct: 'class',
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|