@principal-ai/subsystems-react 0.38.0 → 0.39.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/graphify/consolidated.d.ts +8 -0
- package/dist/graphify/consolidated.d.ts.map +1 -1
- package/dist/graphify/construct.d.ts +18 -0
- package/dist/graphify/construct.d.ts.map +1 -1
- package/dist/graphify/construct.js +23 -0
- package/dist/graphify/construct.js.map +1 -1
- package/dist/graphify/index.d.ts +2 -2
- package/dist/graphify/index.d.ts.map +1 -1
- package/dist/graphify/index.js +1 -1
- package/dist/graphify/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/subsystem/IssueList.d.ts +49 -3
- package/dist/subsystem/IssueList.d.ts.map +1 -1
- package/dist/subsystem/IssueList.js +180 -79
- package/dist/subsystem/IssueList.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +9 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +461 -12
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/formatDeclaration.js +19 -5
- package/dist/subsystem/formatDeclaration.js.map +1 -1
- package/dist/subsystem/model.d.ts +38 -0
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +124 -67
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/subsystem/symbolRefs.d.ts.map +1 -1
- package/dist/subsystem/symbolRefs.js +2 -0
- package/dist/subsystem/symbolRefs.js.map +1 -1
- package/package.json +3 -3
- package/src/graphify/consolidated.ts +8 -0
- package/src/graphify/construct.test.ts +61 -1
- package/src/graphify/construct.ts +33 -0
- package/src/graphify/index.ts +6 -1
- package/src/index.ts +1 -0
- package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +625 -0
- package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +7 -1
- package/src/stories/Subsystem/ComponentGraph/NodeAnatomy.stories.tsx +40 -73
- package/src/stories/Subsystem/ComponentGraph/StoreValueType.stories.tsx +261 -0
- package/src/stories/Subsystem/StoreValueTypeDeclaration.stories.tsx +182 -0
- package/src/subsystem/IssueList.focus.test.tsx +113 -0
- package/src/subsystem/IssueList.icon.test.tsx +95 -0
- package/src/subsystem/IssueList.tsx +267 -111
- package/src/subsystem/SubsystemComponentGraph.tsx +530 -12
- package/src/subsystem/formatDeclaration.test.ts +64 -0
- package/src/subsystem/formatDeclaration.ts +20 -5
- package/src/subsystem/model.ts +46 -0
- package/src/subsystem/nodes.group.test.tsx +52 -0
- package/src/subsystem/nodes.tsx +102 -2
- package/src/subsystem/symbolRefs.ts +2 -0
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import '@xyflow/react/dist/style.css';
|
|
3
3
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
-
import { ReactFlowProvider, type NodeProps } from '@xyflow/react';
|
|
4
|
+
import { ReactFlowProvider, type Node, type NodeProps } from '@xyflow/react';
|
|
5
5
|
import { ThemeProvider, defaultEditorTheme, useTheme } from '@principal-ade/industry-theme';
|
|
6
|
-
import { FileX, Search, MapPin, Shapes, Sigma } from 'lucide-react';
|
|
7
6
|
import { SubsystemComponentNode } from '../../../subsystem/nodes';
|
|
8
7
|
import type { GraphifyComponentDetail } from '../../../graphify';
|
|
9
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
SubsystemComponent,
|
|
10
|
+
SubsystemGraphNodeData,
|
|
11
|
+
SubsystemNodeIssue,
|
|
12
|
+
} from '../../../subsystem/model';
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* NodeAnatomy — a dissection of the component node's visual language.
|
|
13
16
|
*
|
|
14
17
|
* Section 1 lays out every channel the node already spends (border, corners,
|
|
15
18
|
* left badge, right badge cluster, fill) so an added channel can be chosen
|
|
16
|
-
* against it. Section 2 shows the
|
|
17
|
-
* earliest-rung corner chip) composed over real nodes
|
|
18
|
-
*
|
|
19
|
+
* against it. Section 2 shows the diagnostics layer (dotted border +
|
|
20
|
+
* earliest-rung corner chip) composed over real nodes — now wired into the
|
|
21
|
+
* production node as `data.issue` (see `NodeIssueOverlay` in `nodes.tsx` and
|
|
22
|
+
* `issueBadgeByAlias` in `SubsystemComponentGraph`); this story stays the
|
|
23
|
+
* visual reference. Sections 3–4 (verification ladder, diagnostic hover) are
|
|
24
|
+
* still proposals.
|
|
19
25
|
*/
|
|
20
26
|
const meta = {
|
|
21
27
|
title: 'Subsystem/ComponentGraph/NodeAnatomy',
|
|
@@ -42,14 +48,19 @@ const MONO = 'ui-monospace, SFMono-Regular, Menlo, monospace';
|
|
|
42
48
|
|
|
43
49
|
function nodeProps(
|
|
44
50
|
component: SubsystemComponent,
|
|
45
|
-
opts?: {
|
|
46
|
-
|
|
51
|
+
opts?: {
|
|
52
|
+
selected?: boolean;
|
|
53
|
+
width?: number;
|
|
54
|
+
height?: number;
|
|
55
|
+
issue?: SubsystemNodeIssue;
|
|
56
|
+
},
|
|
57
|
+
): NodeProps<Node<SubsystemGraphNodeData, 'subsystem-component'>> {
|
|
47
58
|
return {
|
|
48
|
-
data: { component },
|
|
59
|
+
data: { component, ...(opts?.issue ? { issue: opts.issue } : {}) },
|
|
49
60
|
selected: opts?.selected ?? false,
|
|
50
61
|
width: opts?.width ?? NODE_W,
|
|
51
62
|
height: opts?.height ?? NODE_H,
|
|
52
|
-
} as unknown as NodeProps<
|
|
63
|
+
} as unknown as NodeProps<Node<SubsystemGraphNodeData, 'subsystem-component'>>;
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
// ---------------------------------------------------------------------------
|
|
@@ -219,7 +230,7 @@ function ChannelCell({ channel }: { channel: Channel }) {
|
|
|
219
230
|
|
|
220
231
|
const LEGEND: Array<[string, string]> = [
|
|
221
232
|
['border color', 'construct kind (Pierre palette) — or primary when a file is open (spotlight)'],
|
|
222
|
-
['border style', 'solid normally; dashed goldenrod when `proposed
|
|
233
|
+
['border style', 'solid normally; dashed goldenrod when `proposed`; dotted when the node has findings'],
|
|
223
234
|
['border width', '2px; 4px when selected or file-matched'],
|
|
224
235
|
['corners', '8px rounded; square for `external`'],
|
|
225
236
|
['left badge (top edge)', 'construct / framework stereotype / entityKind'],
|
|
@@ -290,14 +301,6 @@ function Legend() {
|
|
|
290
301
|
type Severity = 'error' | 'info';
|
|
291
302
|
type Rung = 'file' | 'symbol' | 'declaration' | 'type' | 'signature';
|
|
292
303
|
|
|
293
|
-
const RUNG_ICON = {
|
|
294
|
-
file: FileX,
|
|
295
|
-
symbol: Search,
|
|
296
|
-
declaration: MapPin,
|
|
297
|
-
type: Shapes,
|
|
298
|
-
signature: Sigma,
|
|
299
|
-
} as const;
|
|
300
|
-
|
|
301
304
|
const RUNG_LABEL: Record<Rung, string> = {
|
|
302
305
|
file: 'file',
|
|
303
306
|
symbol: 'symbol',
|
|
@@ -317,51 +320,13 @@ function IssueOverlayNode({
|
|
|
317
320
|
rung: Rung;
|
|
318
321
|
count?: number;
|
|
319
322
|
}) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
severity === 'error'
|
|
323
|
-
? (theme.colors.error ?? '#e5534b')
|
|
324
|
-
: (theme.colors.info ?? '#0893d2');
|
|
325
|
-
// Gaps (info) read hollow/dashed — "couldn't confirm"; errors solid.
|
|
326
|
-
const dashed = severity === 'info';
|
|
327
|
-
const Icon = RUNG_ICON[rung];
|
|
328
|
-
const badgeBg = theme.colors.backgroundSecondary ?? theme.colors.background;
|
|
323
|
+
// The diagnostics layer is wired into the production node — the border turns
|
|
324
|
+
// dotted (construct color kept) and the chip carries severity + rung + count.
|
|
329
325
|
return (
|
|
330
326
|
<div style={{ position: 'relative', width: NODE_W, height: NODE_H }}>
|
|
331
|
-
<SubsystemComponentNode
|
|
332
|
-
|
|
333
|
-
<div
|
|
334
|
-
style={{
|
|
335
|
-
position: 'absolute',
|
|
336
|
-
inset: -5,
|
|
337
|
-
borderRadius: 11,
|
|
338
|
-
border: `2px ${dashed ? 'dashed' : 'solid'} ${color}`,
|
|
339
|
-
pointerEvents: 'none',
|
|
340
|
-
}}
|
|
327
|
+
<SubsystemComponentNode
|
|
328
|
+
{...nodeProps(component, { issue: { severity, rung, count } })}
|
|
341
329
|
/>
|
|
342
|
-
{/* earliest-rung corner chip + count */}
|
|
343
|
-
<div
|
|
344
|
-
style={{
|
|
345
|
-
position: 'absolute',
|
|
346
|
-
right: -8,
|
|
347
|
-
bottom: -7,
|
|
348
|
-
display: 'inline-flex',
|
|
349
|
-
alignItems: 'center',
|
|
350
|
-
gap: 3,
|
|
351
|
-
padding: '1px 6px',
|
|
352
|
-
borderRadius: 999,
|
|
353
|
-
border: `1.5px solid ${color}`,
|
|
354
|
-
background: badgeBg,
|
|
355
|
-
color,
|
|
356
|
-
fontFamily: MONO,
|
|
357
|
-
fontSize: 10,
|
|
358
|
-
fontWeight: 600,
|
|
359
|
-
lineHeight: 1.4,
|
|
360
|
-
}}
|
|
361
|
-
>
|
|
362
|
-
<Icon size={11} color={color} />
|
|
363
|
-
{count > 1 ? <span>{count}</span> : null}
|
|
364
|
-
</div>
|
|
365
330
|
</div>
|
|
366
331
|
);
|
|
367
332
|
}
|
|
@@ -454,7 +419,7 @@ const ISSUE_EXAMPLES: Array<{
|
|
|
454
419
|
},
|
|
455
420
|
];
|
|
456
421
|
|
|
457
|
-
/**
|
|
422
|
+
/** Diagnostics layer over real nodes — dotted border + earliest-rung chip. */
|
|
458
423
|
export const IssueOverlayProposal: Story = {
|
|
459
424
|
render: () => {
|
|
460
425
|
return (
|
|
@@ -470,20 +435,22 @@ export const IssueOverlayProposal: Story = {
|
|
|
470
435
|
marginBottom: 10,
|
|
471
436
|
}}
|
|
472
437
|
>
|
|
473
|
-
|
|
438
|
+
Wired · data.issue
|
|
474
439
|
</div>
|
|
475
440
|
<ul style={{ margin: 0, paddingLeft: 18, fontSize: 12, lineHeight: 1.7, color: '#b0b0b0' }}>
|
|
476
441
|
<li>
|
|
477
|
-
<b>
|
|
478
|
-
|
|
442
|
+
<b>Dotted border</b> (keeps the construct color) = the node has findings — a sibling of
|
|
443
|
+
<b> proposed</b>'s dashed, kept distinct so the two don't read alike.
|
|
479
444
|
</li>
|
|
480
445
|
<li>
|
|
481
|
-
<b>Corner chip</b>
|
|
482
|
-
|
|
446
|
+
<b>Corner chip</b> carries severity (<b>red</b> = error / contradicts source,{' '}
|
|
447
|
+
<b>amber</b> = info/gap / couldn't confirm) and the <i>earliest failing rung</i> by
|
|
448
|
+
verification order (file → symbol → declaration → type → signature), with a count when a
|
|
449
|
+
node has more than one.
|
|
483
450
|
</li>
|
|
484
451
|
<li>
|
|
485
|
-
First cell is the clean node for contrast —
|
|
486
|
-
|
|
452
|
+
First cell is the clean node for contrast — solid border, no chip. Severity rides on the
|
|
453
|
+
chip so it never competes with the construct palette.
|
|
487
454
|
</li>
|
|
488
455
|
</ul>
|
|
489
456
|
</div>
|
|
@@ -526,7 +493,7 @@ function VerificationSquares({ statuses }: { statuses: RungStatus[] }) {
|
|
|
526
493
|
const { theme } = useTheme();
|
|
527
494
|
const success = theme.colors.success ?? '#2da44e';
|
|
528
495
|
const error = theme.colors.error ?? '#e5534b';
|
|
529
|
-
const
|
|
496
|
+
const warning = theme.colors.warning ?? '#d4a017';
|
|
530
497
|
const dim = theme.colors.border ?? '#444';
|
|
531
498
|
const nodeBg = theme.colors.backgroundSecondary ?? theme.colors.background;
|
|
532
499
|
return (
|
|
@@ -549,7 +516,7 @@ function VerificationSquares({ statuses }: { statuses: RungStatus[] }) {
|
|
|
549
516
|
if (status === 'failed') return <span key={i} style={{ ...base, background: error }} />;
|
|
550
517
|
if (status === 'unconfirmed')
|
|
551
518
|
return (
|
|
552
|
-
<span key={i} style={{ ...base, boxShadow: `inset 0 0 0 1.5px ${
|
|
519
|
+
<span key={i} style={{ ...base, boxShadow: `inset 0 0 0 1.5px ${warning}` }} />
|
|
553
520
|
);
|
|
554
521
|
// not reached / skipped — a filled neutral so it stays legible on the node
|
|
555
522
|
return <span key={i} style={{ ...base, background: dim }} />;
|
|
@@ -653,7 +620,7 @@ export const VerificationSteps: Story = {
|
|
|
653
620
|
</div>
|
|
654
621
|
<ul style={{ margin: 0, paddingLeft: 18, fontSize: 12, lineHeight: 1.7, color: '#b0b0b0' }}>
|
|
655
622
|
<li>
|
|
656
|
-
<b>green</b> = verified, <b>red</b> = failed, <b>hollow
|
|
623
|
+
<b>green</b> = verified, <b>red</b> = failed, <b>hollow amber</b> = unconfirmed (info),
|
|
657
624
|
<b> grey</b> = skipped / not reached.
|
|
658
625
|
</li>
|
|
659
626
|
<li>
|
|
@@ -694,7 +661,7 @@ function IssueHoverBadge({ severity, label }: { severity: Severity; label: strin
|
|
|
694
661
|
const color =
|
|
695
662
|
severity === 'error'
|
|
696
663
|
? (theme.colors.error ?? '#e5534b')
|
|
697
|
-
: (theme.colors.
|
|
664
|
+
: (theme.colors.warning ?? '#d4a017');
|
|
698
665
|
return (
|
|
699
666
|
<div
|
|
700
667
|
style={{
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import '@xyflow/react/dist/style.css';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { ReactFlowProvider, type NodeProps } from '@xyflow/react';
|
|
4
|
+
import { ThemeProvider, defaultEditorTheme, useTheme } from '@principal-ade/industry-theme';
|
|
5
|
+
import { SubsystemComponentNode } from '../../../subsystem/nodes';
|
|
6
|
+
import type { GraphifyComponentDetail } from '../../../graphify';
|
|
7
|
+
import type { SubsystemComponent, SubsystemGraphNode } from '../../../subsystem/model';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* StoreValueType — proposal sketch (not wired yet).
|
|
11
|
+
*
|
|
12
|
+
* A store is a state declaration, so it should name and type itself like every
|
|
13
|
+
* other declaration. `SubsystemStoreDeclaration` has no field for a store's value
|
|
14
|
+
* type today; the value type lives in the **declaration panel** (see the
|
|
15
|
+
* `Subsystem/StoreValueTypeDeclaration` story), shown when the node is clicked —
|
|
16
|
+
* NOT as a node subtitle.
|
|
17
|
+
*
|
|
18
|
+
* title = the state declaration's name (the variable name) — already shipped
|
|
19
|
+
* badge = storage backing (memory / disk / db) — already shipped
|
|
20
|
+
* valueType = declaration panel only (no node channel)
|
|
21
|
+
*
|
|
22
|
+
* Cases also contrast what a wrong anchor renders: a store whose anchor is the
|
|
23
|
+
* accessor shows the accessor's name instead of the state's.
|
|
24
|
+
*/
|
|
25
|
+
const meta = {
|
|
26
|
+
title: 'Subsystem/ComponentGraph/StoreValueType',
|
|
27
|
+
parameters: { layout: 'fullscreen' },
|
|
28
|
+
tags: ['autodocs'],
|
|
29
|
+
decorators: [
|
|
30
|
+
(Story) => (
|
|
31
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
32
|
+
<ReactFlowProvider>
|
|
33
|
+
<Story />
|
|
34
|
+
</ReactFlowProvider>
|
|
35
|
+
</ThemeProvider>
|
|
36
|
+
),
|
|
37
|
+
],
|
|
38
|
+
} satisfies Meta;
|
|
39
|
+
|
|
40
|
+
export default meta;
|
|
41
|
+
type Story = StoryObj<typeof meta>;
|
|
42
|
+
|
|
43
|
+
const PURL = 'pkg:github/principal-ai/subsystem-modeling';
|
|
44
|
+
const NODE_W = 230;
|
|
45
|
+
const NODE_H = 92;
|
|
46
|
+
const MONO = 'ui-monospace, SFMono-Regular, Menlo, monospace';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A store component with the *proposed* `valueType` field. The field is not in
|
|
50
|
+
* the schema yet, so it is carried here as `declaration.valueType` (the shape we
|
|
51
|
+
* intend to land) and read directly by this story graph.
|
|
52
|
+
*/
|
|
53
|
+
type ProposedStoreComponent = SubsystemComponent & {
|
|
54
|
+
declaration: GraphifyComponentDetail & { valueType?: string };
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function nodeProps(
|
|
58
|
+
component: SubsystemComponent,
|
|
59
|
+
opts?: { selected?: boolean; width?: number; height?: number },
|
|
60
|
+
): NodeProps<SubsystemGraphNode> {
|
|
61
|
+
return {
|
|
62
|
+
data: { component },
|
|
63
|
+
selected: opts?.selected ?? false,
|
|
64
|
+
width: opts?.width ?? NODE_W,
|
|
65
|
+
height: opts?.height ?? NODE_H,
|
|
66
|
+
} as unknown as NodeProps<SubsystemGraphNode>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Fixtures — the 23-store audit shapes, rendered with the proposed channels.
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
const CASES: Array<{ label: string; note: string; component: ProposedStoreComponent }> = [
|
|
74
|
+
{
|
|
75
|
+
label: 'memory · declares its value type',
|
|
76
|
+
note: 'title = state name · subtitle = value type · badge = memory.',
|
|
77
|
+
component: {
|
|
78
|
+
alias: 'feeds',
|
|
79
|
+
name: 'feeds',
|
|
80
|
+
construct: 'store',
|
|
81
|
+
file: 'packages/subsystems-studio/src/bun/opencode-v2-live.ts',
|
|
82
|
+
purl: PURL,
|
|
83
|
+
symbol: 'feeds',
|
|
84
|
+
declaration: {
|
|
85
|
+
kind: 'store',
|
|
86
|
+
storage: 'memory',
|
|
87
|
+
properties: [],
|
|
88
|
+
valueType: 'Map<string, OpencodeLiveFeedState>',
|
|
89
|
+
} as GraphifyComponentDetail & { valueType: string },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
label: 'memory · closure-local (no addressable symbol)',
|
|
94
|
+
note: 'state is a factory-local Map; anchor is non-exact → agent-confirmed store.',
|
|
95
|
+
component: {
|
|
96
|
+
alias: 'landed',
|
|
97
|
+
name: 'landed',
|
|
98
|
+
construct: 'store',
|
|
99
|
+
file: 'packages/subsystems-studio/src/bun/maintainer-probe.ts',
|
|
100
|
+
purl: PURL,
|
|
101
|
+
symbol: 'landed',
|
|
102
|
+
declaration: {
|
|
103
|
+
kind: 'store',
|
|
104
|
+
storage: 'memory',
|
|
105
|
+
properties: [],
|
|
106
|
+
valueType: 'Map<string, number>',
|
|
107
|
+
} as GraphifyComponentDetail & { valueType: string },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
label: 'memory · no value type (the gap)',
|
|
112
|
+
note: 'subtitle shows a muted `type: ?` — the completeness finding made visible.',
|
|
113
|
+
component: {
|
|
114
|
+
alias: 'tabs',
|
|
115
|
+
name: 'tabs',
|
|
116
|
+
construct: 'store',
|
|
117
|
+
file: 'packages/subsystems-studio/src/bun/index.ts',
|
|
118
|
+
purl: PURL,
|
|
119
|
+
symbol: 'tabs',
|
|
120
|
+
declaration: {
|
|
121
|
+
kind: 'store',
|
|
122
|
+
storage: 'memory',
|
|
123
|
+
properties: [],
|
|
124
|
+
} as GraphifyComponentDetail,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
label: 'disk · value type',
|
|
129
|
+
note: 'run log on the filesystem — badge reads `disk`.',
|
|
130
|
+
component: {
|
|
131
|
+
alias: 'runs',
|
|
132
|
+
name: 'runs',
|
|
133
|
+
construct: 'store',
|
|
134
|
+
file: 'packages/subsystems-studio/src/bun/subsystem-model-runs.ts',
|
|
135
|
+
purl: PURL,
|
|
136
|
+
symbol: 'runs',
|
|
137
|
+
declaration: {
|
|
138
|
+
kind: 'store',
|
|
139
|
+
storage: 'disk',
|
|
140
|
+
properties: [],
|
|
141
|
+
valueType: 'SubsystemModelRun[]',
|
|
142
|
+
} as GraphifyComponentDetail & { valueType: string },
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
label: 'db · row / members shape',
|
|
147
|
+
note: 'table name as title; value type is the row shape (members[]).',
|
|
148
|
+
component: {
|
|
149
|
+
alias: 'session_v2',
|
|
150
|
+
name: 'session_v2',
|
|
151
|
+
construct: 'store',
|
|
152
|
+
file: 'packages/subsystems-studio/src/bun/maintain-sessions.ts',
|
|
153
|
+
purl: PURL,
|
|
154
|
+
symbol: 'session_v2',
|
|
155
|
+
declaration: {
|
|
156
|
+
kind: 'store',
|
|
157
|
+
storage: 'external',
|
|
158
|
+
properties: [
|
|
159
|
+
{ name: 'id', type: 'text' },
|
|
160
|
+
{ name: 'title', type: 'text' },
|
|
161
|
+
{ name: 'agent', type: 'text' },
|
|
162
|
+
{ name: 'time_created', type: 'integer' },
|
|
163
|
+
],
|
|
164
|
+
} as GraphifyComponentDetail,
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Renders the component node as it should appear: title = the state
|
|
171
|
+
* declaration's name, badge = storage backing. The value type is intentionally
|
|
172
|
+
* NOT shown here — it belongs to the declaration panel (see
|
|
173
|
+
* `Subsystem/StoreValueTypeDeclaration`), shown when the node is clicked.
|
|
174
|
+
*/
|
|
175
|
+
function StoreNode({ component }: { component: ProposedStoreComponent }) {
|
|
176
|
+
return (
|
|
177
|
+
<div style={{ position: 'relative', width: NODE_W, height: NODE_H }}>
|
|
178
|
+
<SubsystemComponentNode {...nodeProps(component)} />
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function Cell({
|
|
184
|
+
label,
|
|
185
|
+
note,
|
|
186
|
+
component,
|
|
187
|
+
}: {
|
|
188
|
+
label: string;
|
|
189
|
+
note: string;
|
|
190
|
+
component: ProposedStoreComponent;
|
|
191
|
+
}) {
|
|
192
|
+
const { theme } = useTheme();
|
|
193
|
+
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
194
|
+
const valueType = component.declaration?.valueType;
|
|
195
|
+
return (
|
|
196
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, width: 280 }}>
|
|
197
|
+
<div style={{ fontFamily: MONO, fontSize: 11, color: muted }}>{label}</div>
|
|
198
|
+
<div style={{ position: 'relative', width: NODE_W, height: NODE_H, margin: '14px 0 16px' }}>
|
|
199
|
+
<StoreNode component={component} />
|
|
200
|
+
</div>
|
|
201
|
+
<div style={{ fontSize: 10, fontFamily: MONO, color: muted, lineHeight: 1.5 }}>
|
|
202
|
+
{note}
|
|
203
|
+
<br />
|
|
204
|
+
panel: {valueType ?? '(no value type — gap)'}
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Store nodes: title = state name, badge = storage. Value type lives in the panel. */
|
|
211
|
+
export const StoreValueType: Story = {
|
|
212
|
+
render: () => (
|
|
213
|
+
<div style={{ padding: 32, minHeight: '100vh', boxSizing: 'border-box' }}>
|
|
214
|
+
<Header
|
|
215
|
+
title="Store nodes"
|
|
216
|
+
lines={[
|
|
217
|
+
'A store is a state declaration, so its node names it: title = the state declaration (the variable name). The storage badge (memory / disk / db) shows how it persists.',
|
|
218
|
+
'The value type is not a node channel — it belongs to the declaration panel, shown when the node is clicked (see Subsystem/StoreValueTypeDeclaration).',
|
|
219
|
+
'The caption under each node shows what the declaration panel renders for it.',
|
|
220
|
+
]}
|
|
221
|
+
/>
|
|
222
|
+
<div
|
|
223
|
+
style={{
|
|
224
|
+
display: 'flex',
|
|
225
|
+
flexWrap: 'wrap',
|
|
226
|
+
gap: '40px 28px',
|
|
227
|
+
alignItems: 'flex-start',
|
|
228
|
+
marginTop: 28,
|
|
229
|
+
}}
|
|
230
|
+
>
|
|
231
|
+
{CASES.map((c) => (
|
|
232
|
+
<Cell key={c.component.alias} label={c.label} note={c.note} component={c.component} />
|
|
233
|
+
))}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
),
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
function Header({ title, lines }: { title: string; lines: string[] }) {
|
|
240
|
+
return (
|
|
241
|
+
<div style={{ maxWidth: 760 }}>
|
|
242
|
+
<div
|
|
243
|
+
style={{
|
|
244
|
+
fontSize: 12,
|
|
245
|
+
fontFamily: MONO,
|
|
246
|
+
textTransform: 'uppercase',
|
|
247
|
+
letterSpacing: 0.4,
|
|
248
|
+
color: '#a0a0a0',
|
|
249
|
+
marginBottom: 10,
|
|
250
|
+
}}
|
|
251
|
+
>
|
|
252
|
+
{title}
|
|
253
|
+
</div>
|
|
254
|
+
<ul style={{ margin: 0, paddingLeft: 18, fontSize: 12, lineHeight: 1.7, color: '#b0b0b0' }}>
|
|
255
|
+
{lines.map((l) => (
|
|
256
|
+
<li key={l}>{l}</li>
|
|
257
|
+
))}
|
|
258
|
+
</ul>
|
|
259
|
+
</div>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
3
|
+
import { generateDeclarationString } from '../../subsystem/formatDeclaration';
|
|
4
|
+
import type { SubsystemComponent } from '../../subsystem/model';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* StoreValueType — how a store's declared value type renders in the
|
|
8
|
+
* **declaration panel** (the click panel), not on the node.
|
|
9
|
+
*
|
|
10
|
+
* A store is a state declaration, so it declares its type the way every other
|
|
11
|
+
* declaration does: `declaration.valueType` (`Map<string, FeedState>` for a state
|
|
12
|
+
* block, a row type for a table), orthogonal to `declaration.storage`. The
|
|
13
|
+
* rendered string comes from the real `generateStore` in
|
|
14
|
+
* `formatDeclaration.ts` — not a sketch.
|
|
15
|
+
*
|
|
16
|
+
* The node itself is unchanged: title = the state declaration's name, badge =
|
|
17
|
+
* storage backing. The value type is panel-only.
|
|
18
|
+
*/
|
|
19
|
+
const meta = {
|
|
20
|
+
title: 'Subsystem/StoreValueTypeDeclaration',
|
|
21
|
+
parameters: { layout: 'padded' },
|
|
22
|
+
tags: ['autodocs'],
|
|
23
|
+
decorators: [
|
|
24
|
+
(Story) => (
|
|
25
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
26
|
+
<div style={{ maxWidth: '80ch', margin: '0 auto' }}>
|
|
27
|
+
<Story />
|
|
28
|
+
</div>
|
|
29
|
+
</ThemeProvider>
|
|
30
|
+
),
|
|
31
|
+
],
|
|
32
|
+
} satisfies Meta;
|
|
33
|
+
export default meta;
|
|
34
|
+
|
|
35
|
+
type Case = {
|
|
36
|
+
label: string;
|
|
37
|
+
note: string;
|
|
38
|
+
component: SubsystemComponent;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const CASES: Case[] = [
|
|
42
|
+
{
|
|
43
|
+
label: 'memory · value type, no named members',
|
|
44
|
+
note: 'The store declares what it holds; nothing else to render.',
|
|
45
|
+
component: {
|
|
46
|
+
id: 'feeds',
|
|
47
|
+
name: 'feeds',
|
|
48
|
+
construct: 'store',
|
|
49
|
+
symbol: 'feeds',
|
|
50
|
+
file: 'packages/subsystems-studio/src/bun/opencode-v2-live.ts',
|
|
51
|
+
purl: 'pkg:github/principal-ai/subsystem-modeling',
|
|
52
|
+
declaration: {
|
|
53
|
+
kind: 'store',
|
|
54
|
+
storage: 'memory',
|
|
55
|
+
properties: [],
|
|
56
|
+
valueType: 'Map<string, OpencodeLiveFeedState>',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
label: 'memory · closure-local state',
|
|
62
|
+
note: 'State created inside a factory, reached through an accessor. Still a declaration.',
|
|
63
|
+
component: {
|
|
64
|
+
id: 'landed',
|
|
65
|
+
name: 'landed',
|
|
66
|
+
construct: 'store',
|
|
67
|
+
symbol: 'landed',
|
|
68
|
+
file: 'packages/subsystems-studio/src/bun/maintainer-probe.ts',
|
|
69
|
+
purl: 'pkg:github/principal-ai/subsystem-modeling',
|
|
70
|
+
declaration: {
|
|
71
|
+
kind: 'store',
|
|
72
|
+
storage: 'memory',
|
|
73
|
+
properties: [],
|
|
74
|
+
valueType: 'Map<string, number>',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: 'disk · value type + named members',
|
|
80
|
+
note: 'Members render as a typed body under the value type.',
|
|
81
|
+
component: {
|
|
82
|
+
id: 'runs',
|
|
83
|
+
name: 'runs',
|
|
84
|
+
construct: 'store',
|
|
85
|
+
symbol: 'runs',
|
|
86
|
+
file: 'packages/subsystems-studio/src/bun/subsystem-model-runs.ts',
|
|
87
|
+
purl: 'pkg:github/principal-ai/subsystem-modeling',
|
|
88
|
+
declaration: {
|
|
89
|
+
kind: 'store',
|
|
90
|
+
storage: 'disk',
|
|
91
|
+
valueType: 'SubsystemModelRun[]',
|
|
92
|
+
properties: [{ name: 'runs', type: 'SubsystemModelRun[]' }],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
label: 'memory · no value type → the gap',
|
|
98
|
+
note: 'Says so explicitly — the completeness finding reads this back.',
|
|
99
|
+
component: {
|
|
100
|
+
id: 'tabs',
|
|
101
|
+
name: 'tabs',
|
|
102
|
+
construct: 'store',
|
|
103
|
+
symbol: 'tabs',
|
|
104
|
+
file: 'packages/subsystems-studio/src/bun/index.ts',
|
|
105
|
+
purl: 'pkg:github/principal-ai/subsystem-modeling',
|
|
106
|
+
declaration: { kind: 'store', storage: 'memory', properties: [] },
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
label: 'db · row shape, no scalar value type',
|
|
111
|
+
note: 'A table names its row/record type; columns stay as members.',
|
|
112
|
+
component: {
|
|
113
|
+
id: 'session_v2',
|
|
114
|
+
name: 'session_v2',
|
|
115
|
+
construct: 'store',
|
|
116
|
+
symbol: 'session_v2',
|
|
117
|
+
file: 'packages/subsystems-studio/src/bun/maintain-sessions.ts',
|
|
118
|
+
purl: 'pkg:github/principal-ai/subsystem-modeling',
|
|
119
|
+
declaration: {
|
|
120
|
+
kind: 'store',
|
|
121
|
+
storage: 'external',
|
|
122
|
+
properties: [
|
|
123
|
+
{ name: 'id', type: 'text' },
|
|
124
|
+
{ name: 'title', type: 'text' },
|
|
125
|
+
{ name: 'agent', type: 'text' },
|
|
126
|
+
{ name: 'time_created', type: 'integer' },
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
function Panel({ component }: { component: SubsystemComponent }) {
|
|
134
|
+
return (
|
|
135
|
+
<pre
|
|
136
|
+
style={{
|
|
137
|
+
margin: 0,
|
|
138
|
+
padding: '10px 12px',
|
|
139
|
+
borderRadius: 6,
|
|
140
|
+
background: 'rgba(0,0,0,0.35)',
|
|
141
|
+
border: '1px solid rgba(255,255,255,0.08)',
|
|
142
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
|
|
143
|
+
fontSize: 12,
|
|
144
|
+
lineHeight: 1.5,
|
|
145
|
+
color: '#e5e7eb',
|
|
146
|
+
whiteSpace: 'pre-wrap',
|
|
147
|
+
overflowX: 'auto',
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
150
|
+
{generateDeclarationString(component)}
|
|
151
|
+
</pre>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const StoreDeclaration: StoryObj = {
|
|
156
|
+
render: () => (
|
|
157
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 26 }}>
|
|
158
|
+
<div style={{ fontSize: 12, color: '#b0b0b0', lineHeight: 1.7 }}>
|
|
159
|
+
<strong>Rendered by the real <code>generateStore</code>.</strong> A store declares
|
|
160
|
+
its type the way every other declaration does — <code>declaration.valueType</code> —
|
|
161
|
+
and the value type is panel-only, not a node channel. The node stays{' '}
|
|
162
|
+
<code>name → title</code>, <code>storage → badge</code>.
|
|
163
|
+
</div>
|
|
164
|
+
{CASES.map((c) => (
|
|
165
|
+
<div key={c.component.id}>
|
|
166
|
+
<div
|
|
167
|
+
style={{
|
|
168
|
+
fontFamily: 'ui-monospace, monospace',
|
|
169
|
+
fontSize: 12,
|
|
170
|
+
color: '#f3f4f6',
|
|
171
|
+
marginBottom: 6,
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
{c.label}
|
|
175
|
+
</div>
|
|
176
|
+
<div style={{ fontSize: 11, color: '#9ca3af', marginBottom: 8 }}>{c.note}</div>
|
|
177
|
+
<Panel component={c.component} />
|
|
178
|
+
</div>
|
|
179
|
+
))}
|
|
180
|
+
</div>
|
|
181
|
+
),
|
|
182
|
+
};
|