@principal-ai/subsystems-react 0.49.0 → 0.51.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 +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/pierre/PierreFileView.d.ts +1 -1
- package/dist/pierre/PierreFileView.d.ts.map +1 -1
- package/dist/pierre/PierreFileView.js +2 -1
- package/dist/pierre/PierreFileView.js.map +1 -1
- package/dist/pierre/PierreSnippetView.d.ts +1 -1
- package/dist/pierre/PierreSnippetView.d.ts.map +1 -1
- package/dist/pierre/PierreSnippetView.js +2 -1
- package/dist/pierre/PierreSnippetView.js.map +1 -1
- package/dist/pierre/PierreTrailCodeView.d.ts +7 -1
- package/dist/pierre/PierreTrailCodeView.d.ts.map +1 -1
- package/dist/pierre/PierreTrailCodeView.js +71 -18
- package/dist/pierre/PierreTrailCodeView.js.map +1 -1
- package/dist/pierre/pierreBackground.d.ts +36 -2
- package/dist/pierre/pierreBackground.d.ts.map +1 -1
- package/dist/pierre/pierreBackground.js +40 -13
- package/dist/pierre/pierreBackground.js.map +1 -1
- package/dist/pierre/visibleStep.d.ts +48 -0
- package/dist/pierre/visibleStep.d.ts.map +1 -0
- package/dist/pierre/visibleStep.js +53 -0
- package/dist/pierre/visibleStep.js.map +1 -0
- package/dist/stories/Subsystem/C4Graph/c4Associations.fixture.d.ts +95 -0
- package/dist/stories/Subsystem/C4Graph/c4Associations.fixture.d.ts.map +1 -0
- package/dist/stories/Subsystem/C4Graph/c4Associations.fixture.js +340 -0
- package/dist/stories/Subsystem/C4Graph/c4Associations.fixture.js.map +1 -0
- package/dist/stories/Subsystem/C4Graph/c4Fixture.d.ts +4 -0
- package/dist/stories/Subsystem/C4Graph/c4Fixture.d.ts.map +1 -1
- package/dist/stories/Subsystem/C4Graph/c4Fixture.js +7334 -1
- package/dist/stories/Subsystem/C4Graph/c4Fixture.js.map +1 -1
- package/dist/stories/Subsystem/C4Graph/generateFixture.d.ts +2 -0
- package/dist/stories/Subsystem/C4Graph/generateFixture.d.ts.map +1 -0
- package/dist/stories/Subsystem/C4Graph/generateFixture.js +145 -0
- package/dist/stories/Subsystem/C4Graph/generateFixture.js.map +1 -0
- package/dist/subsystem/C4Graph.d.ts.map +1 -1
- package/dist/subsystem/C4Graph.js +16 -60
- package/dist/subsystem/C4Graph.js.map +1 -1
- package/dist/subsystem/C4NodeCard.d.ts +84 -0
- package/dist/subsystem/C4NodeCard.d.ts.map +1 -0
- package/dist/subsystem/C4NodeCard.js +229 -0
- package/dist/subsystem/C4NodeCard.js.map +1 -0
- package/dist/subsystem/DiagnosticToggle.d.ts +8 -1
- package/dist/subsystem/DiagnosticToggle.d.ts.map +1 -1
- package/dist/subsystem/DiagnosticToggle.js +40 -6
- package/dist/subsystem/DiagnosticToggle.js.map +1 -1
- package/dist/subsystem/FileDrawer.d.ts +10 -1
- package/dist/subsystem/FileDrawer.d.ts.map +1 -1
- package/dist/subsystem/FileDrawer.js +61 -40
- package/dist/subsystem/FileDrawer.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +6 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +21 -10
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/c4Associations.d.ts +186 -0
- package/dist/subsystem/c4Associations.d.ts.map +1 -0
- package/dist/subsystem/c4Associations.js +284 -0
- package/dist/subsystem/c4Associations.js.map +1 -0
- package/dist/subsystem/model.d.ts +25 -1
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +15 -0
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +78 -7
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/subsystem/toC4.d.ts +119 -0
- package/dist/subsystem/toC4.d.ts.map +1 -1
- package/dist/subsystem/toC4.js +95 -5
- package/dist/subsystem/toC4.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +39 -0
- package/src/pierre/PierreFileView.tsx +2 -1
- package/src/pierre/PierreSnippetView.tsx +2 -1
- package/src/pierre/PierreTrailCodeView.tsx +81 -22
- package/src/pierre/pierreBackground.ts +62 -12
- package/src/pierre/visibleStep.test.ts +108 -0
- package/src/pierre/visibleStep.ts +76 -0
- package/src/stories/Subsystem/C4Graph/C4Associations.stories.tsx +273 -0
- package/src/stories/Subsystem/C4Graph/C4NodeGallery.stories.tsx +325 -0
- package/src/stories/Subsystem/C4Graph/c4Associations.fixture.ts +344 -0
- package/src/stories/Subsystem/C4Graph/c4Fixture.ts +7337 -4
- package/src/stories/Subsystem/C4Graph/generateFixture.ts +161 -0
- package/src/stories/Subsystem/ComponentGraph/Deprecated.stories.tsx +358 -0
- package/src/stories/Subsystem/ComponentGraph/Diagnostics.stories.tsx +27 -2
- package/src/stories/Subsystem/ComponentGraph/{Flows.stories.tsx → Trails.stories.tsx} +10 -4
- package/src/subsystem/C4Graph.tsx +51 -92
- package/src/subsystem/C4NodeCard.test.ts +196 -0
- package/src/subsystem/C4NodeCard.tsx +307 -0
- package/src/subsystem/DiagnosticToggle.tsx +58 -4
- package/src/subsystem/FileDrawer.tsx +112 -63
- package/src/subsystem/SubsystemComponentGraph.tsx +29 -5
- package/src/subsystem/c4Associations.test.ts +484 -0
- package/src/subsystem/c4Associations.ts +398 -0
- package/src/subsystem/model.ts +34 -1
- package/src/subsystem/nodes.tsx +124 -5
- package/src/subsystem/toC4.ts +210 -5
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regenerate BOTH C4 fixtures from one composition of the live store, so the
|
|
3
|
+
* document and its association set can never drift apart.
|
|
4
|
+
*
|
|
5
|
+
* Run from packages/subsystems-react:
|
|
6
|
+
* bun run src/stories/Subsystem/C4Graph/generateFixture.ts
|
|
7
|
+
*
|
|
8
|
+
* The two files it writes are paired:
|
|
9
|
+
* c4Fixture.ts the composed document the graph renders
|
|
10
|
+
* c4Associations.fixture.ts proposed associations keyed off that document
|
|
11
|
+
*
|
|
12
|
+
* They must be generated together. A document from one snapshot and an
|
|
13
|
+
* association set from another produces merges that silently do nothing.
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import * as os from 'node:os';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import { toC4 } from '../../../subsystem/toC4';
|
|
19
|
+
import { buildAssociations, deriveConcerns, suggestMerges } from '../../../subsystem/c4Associations';
|
|
20
|
+
import type { SubsystemModelDocument, SubsystemComponent } from '../../../subsystem/model';
|
|
21
|
+
|
|
22
|
+
const REPO = 'pkg:github/principal-ai/subsystem-modeling';
|
|
23
|
+
const SYSTEM_LABEL = 'Subsystem Modeling';
|
|
24
|
+
const dir = path.join(os.homedir(), '.principal/subsystem-models');
|
|
25
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.json') && f !== '_index.json');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Compose every stored model for one repo key into a single document.
|
|
29
|
+
*
|
|
30
|
+
* One canonical component per (purl#symbol); aliases rebased to `c0..cn` so
|
|
31
|
+
* two models cannot collide in one alias space.
|
|
32
|
+
*/
|
|
33
|
+
const byId = new Map<string, SubsystemComponent>();
|
|
34
|
+
const steps: NonNullable<SubsystemModelDocument['trails']>[number]['steps'] = [];
|
|
35
|
+
let n = 0;
|
|
36
|
+
|
|
37
|
+
for (const f of files) {
|
|
38
|
+
const d = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')) as SubsystemModelDocument;
|
|
39
|
+
const aliasMap = new Map<string, string>();
|
|
40
|
+
for (const c of d.components ?? []) {
|
|
41
|
+
if ((c.purl ?? '').split('#')[0] !== REPO) continue;
|
|
42
|
+
const key = `${c.purl!.split('#')[1] ?? ''}::${c.symbol ?? c.alias}`;
|
|
43
|
+
if (!byId.has(key)) byId.set(key, { ...c, alias: `c${n++}` });
|
|
44
|
+
aliasMap.set(c.alias, key);
|
|
45
|
+
}
|
|
46
|
+
for (const t of d.trails ?? []) {
|
|
47
|
+
for (const s of t.steps ?? []) {
|
|
48
|
+
const a = aliasMap.get(s.from);
|
|
49
|
+
const b = aliasMap.get(s.to);
|
|
50
|
+
if (!a || !b || a === b) continue;
|
|
51
|
+
steps.push({
|
|
52
|
+
from: byId.get(a)!.alias,
|
|
53
|
+
to: byId.get(b)!.alias,
|
|
54
|
+
mechanism: s.mechanism,
|
|
55
|
+
// Keep the site so the fixture is a faithful document, not a shape.
|
|
56
|
+
file: s.file,
|
|
57
|
+
line: s.line,
|
|
58
|
+
purl: s.purl,
|
|
59
|
+
symbol: s.symbol,
|
|
60
|
+
...(s.annotation ? { annotation: s.annotation } : {}),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The React-local document type carries components + trails only; the store
|
|
67
|
+
// record wraps it with title/description.
|
|
68
|
+
const document: SubsystemModelDocument = {
|
|
69
|
+
components: [...byId.values()],
|
|
70
|
+
trails: [{ id: 'all', title: 'all trails', steps }],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// --- Roll up once, then derive concerns off the same rollup -------------
|
|
74
|
+
const raw = toC4(document, { view: 'container', systemLabel: SYSTEM_LABEL });
|
|
75
|
+
|
|
76
|
+
const memberOf = new Map<string, SubsystemComponent>();
|
|
77
|
+
for (const c of byId.values()) memberOf.set(c.alias, c);
|
|
78
|
+
|
|
79
|
+
const rollup = raw.nodes
|
|
80
|
+
.filter((x) => x.kind === 'container')
|
|
81
|
+
.map((x) => {
|
|
82
|
+
const members = x.members.map((a) => memberOf.get(a)!).filter(Boolean);
|
|
83
|
+
const frameworks = [...new Set(members.map((m) => m.framework).filter(Boolean) as string[])];
|
|
84
|
+
return { key: x.key ?? x.id, members, frameworks };
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const set = buildAssociations({ repoKey: REPO, rollup, author: 'c4-proposer' });
|
|
88
|
+
const merges = suggestMerges(rollup.map((r) => ({ key: r.key, memberCount: r.members.length })));
|
|
89
|
+
const concerns = rollup.flatMap((r) => deriveConcerns(r.key, r.members, { frameworks: r.frameworks }));
|
|
90
|
+
|
|
91
|
+
/** Every derived key, so a story can assert nothing was invented. */
|
|
92
|
+
const derivedKeys = rollup.map((r) => r.key).sort();
|
|
93
|
+
|
|
94
|
+
/** Components behind each derived key — the evidence that picks a merge survivor. */
|
|
95
|
+
const memberCounts = Object.fromEntries(
|
|
96
|
+
rollup.map((r) => [r.key, r.members.length]).sort(([a], [b]) => String(a).localeCompare(String(b))),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// --- Write the document ------------------------------------------------
|
|
100
|
+
const docSrc = `// GENERATED from live store data by generateFixture.ts — do not hand-edit.
|
|
101
|
+
//
|
|
102
|
+
// Composite of ${files.length} stored subsystem models for ${REPO},
|
|
103
|
+
// joined on purl#symbol (one canonical component per real declaration).
|
|
104
|
+
//
|
|
105
|
+
// Regenerate together with c4Associations.fixture.ts — the association set is
|
|
106
|
+
// keyed off the container keys this document derives. Generating them
|
|
107
|
+
// separately produces merges that silently do nothing.
|
|
108
|
+
|
|
109
|
+
import type { SubsystemModelDocument } from "../../../subsystem/model";
|
|
110
|
+
|
|
111
|
+
/** Container keys this document is expected to derive. */
|
|
112
|
+
export const c4DerivedKeys = ${JSON.stringify(derivedKeys, null, 2)} as const;
|
|
113
|
+
|
|
114
|
+
/** Components behind each derived key — decides which side of a merge survives. */
|
|
115
|
+
export const c4MemberCounts: Record<string, number> = ${JSON.stringify(memberCounts, null, 2)};
|
|
116
|
+
|
|
117
|
+
export const c4FixtureModel: SubsystemModelDocument = ${JSON.stringify(document, null, 1)};
|
|
118
|
+
`;
|
|
119
|
+
fs.writeFileSync(path.join(__dirname, 'c4Fixture.ts'), docSrc);
|
|
120
|
+
|
|
121
|
+
// --- Write the associations -------------------------------------------
|
|
122
|
+
const assocSrc = `// GENERATED from live store data by generateFixture.ts — do not hand-edit.
|
|
123
|
+
//
|
|
124
|
+
// Derived from the SAME composition as c4Fixture.ts: ${byId.size} components
|
|
125
|
+
// → ${rollup.length} container keys → ${set.associations.length} associations.
|
|
126
|
+
//
|
|
127
|
+
// Every association is state:'proposed'. None of it is accepted — accepting is
|
|
128
|
+
// a human's decision, and this story exists to make that decision visible.
|
|
129
|
+
|
|
130
|
+
import type { C4Association } from "../../../subsystem/toC4";
|
|
131
|
+
|
|
132
|
+
/** Container keys whose trailing role matches — candidates for a merge. */
|
|
133
|
+
export const C4_MERGE_CANDIDATES = ${JSON.stringify(merges, null, 2)} as const;
|
|
134
|
+
|
|
135
|
+
/** Every mechanical concern, for the review panel. */
|
|
136
|
+
export const C4_CONCERNS = ${JSON.stringify(concerns, null, 2)} as const;
|
|
137
|
+
|
|
138
|
+
export const c4Associations: C4Association[] = ${JSON.stringify(set.associations, null, 2)};
|
|
139
|
+
`;
|
|
140
|
+
fs.writeFileSync(path.join(__dirname, 'c4Associations.fixture.ts'), assocSrc);
|
|
141
|
+
|
|
142
|
+
// --- Verify the pair actually agrees ----------------------------------
|
|
143
|
+
const reDerived = toC4(
|
|
144
|
+
JSON.parse(JSON.stringify(document)),
|
|
145
|
+
{ view: 'container', systemLabel: SYSTEM_LABEL, associations: set.associations },
|
|
146
|
+
);
|
|
147
|
+
const claimed = new Set(set.associations.flatMap((a) => a.sourceKeys));
|
|
148
|
+
const orphans = derivedKeys.filter((k) => !claimed.has(k));
|
|
149
|
+
|
|
150
|
+
console.log('wrote c4Fixture.ts + c4Associations.fixture.ts');
|
|
151
|
+
console.log(' models composed :', files.length);
|
|
152
|
+
console.log(' components :', byId.size);
|
|
153
|
+
console.log(' container keys :', rollup.length);
|
|
154
|
+
console.log(' associations :', set.associations.length);
|
|
155
|
+
console.log(' merge candidates:', merges.length);
|
|
156
|
+
console.log(' concerns :', concerns.length);
|
|
157
|
+
console.log(' types :', JSON.stringify(
|
|
158
|
+
set.associations.reduce<Record<string, number>>((a, x) => ({ ...a, [x.type]: (a[x.type] ?? 0) + 1 }), {}),
|
|
159
|
+
));
|
|
160
|
+
console.log(' containers drawn:', reDerived.nodes.filter((x) => x.kind === 'container').length);
|
|
161
|
+
if (orphans.length > 0) console.warn(' !! derived keys with no association:', orphans);
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import '@xyflow/react/dist/style.css';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
import { ReactFlowProvider, type NodeProps } from '@xyflow/react';
|
|
5
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
6
|
+
import { SubsystemComponentGraph } from '../../../subsystem/SubsystemComponentGraph';
|
|
7
|
+
import { SubsystemComponentNode } from '../../../subsystem/nodes';
|
|
8
|
+
import type {
|
|
9
|
+
SubsystemComponent,
|
|
10
|
+
SubsystemGraphNode,
|
|
11
|
+
} from '../../../subsystem/model';
|
|
12
|
+
import { trailFromSteps } from './fixtures';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* `deprecated` is the inverse of `proposed`: the claim was real, its source is
|
|
16
|
+
* gone upstream. Mirrors Proposed.stories.tsx so the two temporary states read
|
|
17
|
+
* as siblings — proposed = not built yet, deprecated = built once, removed since.
|
|
18
|
+
*/
|
|
19
|
+
const meta = {
|
|
20
|
+
title: 'Subsystem/ComponentGraph/Deprecated',
|
|
21
|
+
parameters: { layout: 'fullscreen' },
|
|
22
|
+
tags: ['autodocs'],
|
|
23
|
+
decorators: [
|
|
24
|
+
(Story) => (
|
|
25
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
26
|
+
<ReactFlowProvider>
|
|
27
|
+
<Story />
|
|
28
|
+
</ReactFlowProvider>
|
|
29
|
+
</ThemeProvider>
|
|
30
|
+
),
|
|
31
|
+
],
|
|
32
|
+
} satisfies Meta;
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
type Story = StoryObj<typeof meta>;
|
|
36
|
+
|
|
37
|
+
const PURL = 'pkg:github/principal-ai/subsystem-modeling';
|
|
38
|
+
|
|
39
|
+
function nodeProps(c: SubsystemComponent): NodeProps<SubsystemGraphNode> {
|
|
40
|
+
return {
|
|
41
|
+
data: { component: c },
|
|
42
|
+
selected: false,
|
|
43
|
+
width: 230,
|
|
44
|
+
height: 84,
|
|
45
|
+
} as unknown as NodeProps<SubsystemGraphNode>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Side-by-side single-node spotlights — deprecated vs its live neighbours. */
|
|
49
|
+
function NodeSpotlight({
|
|
50
|
+
label,
|
|
51
|
+
component,
|
|
52
|
+
}: {
|
|
53
|
+
label: string;
|
|
54
|
+
component: SubsystemComponent;
|
|
55
|
+
}) {
|
|
56
|
+
return (
|
|
57
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, width: 260 }}>
|
|
58
|
+
<div
|
|
59
|
+
style={{
|
|
60
|
+
fontFamily: 'ui-monospace, monospace',
|
|
61
|
+
fontSize: 11,
|
|
62
|
+
color: '#888',
|
|
63
|
+
minHeight: 32,
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
{label}
|
|
67
|
+
</div>
|
|
68
|
+
<div style={{ position: 'relative', height: 110, width: 240 }}>
|
|
69
|
+
<SubsystemComponentNode {...nodeProps(component)} />
|
|
70
|
+
</div>
|
|
71
|
+
<pre
|
|
72
|
+
style={{
|
|
73
|
+
margin: 0,
|
|
74
|
+
fontSize: 10,
|
|
75
|
+
fontFamily: 'ui-monospace, monospace',
|
|
76
|
+
background: '#1a1a1a',
|
|
77
|
+
color: '#ccc',
|
|
78
|
+
padding: 8,
|
|
79
|
+
borderRadius: 4,
|
|
80
|
+
overflow: 'auto',
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
{JSON.stringify(
|
|
84
|
+
{
|
|
85
|
+
construct: component.construct,
|
|
86
|
+
deprecated: component.deprecated || undefined,
|
|
87
|
+
removedIn: component.removedIn,
|
|
88
|
+
role: component.role,
|
|
89
|
+
symbol: component.symbol || undefined,
|
|
90
|
+
file: component.file || undefined,
|
|
91
|
+
},
|
|
92
|
+
null,
|
|
93
|
+
2,
|
|
94
|
+
)}
|
|
95
|
+
</pre>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const spotlightRows: Array<{ label: string; component: SubsystemComponent }> = [
|
|
101
|
+
{
|
|
102
|
+
label: 'live function (baseline)',
|
|
103
|
+
component: {
|
|
104
|
+
alias: 'live-fn',
|
|
105
|
+
name: 'auditSubsystemModel',
|
|
106
|
+
construct: 'function',
|
|
107
|
+
file: 'src/bun/verify-subsystem-component.ts',
|
|
108
|
+
purl: PURL,
|
|
109
|
+
symbol: 'auditSubsystemModel',
|
|
110
|
+
purpose: 'Deterministic dry-run audit of a stored model',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
label: 'deprecated + removedIn — slate border, badge',
|
|
115
|
+
component: {
|
|
116
|
+
alias: 'dead-fn',
|
|
117
|
+
name: 'auditTopologyRelations',
|
|
118
|
+
construct: 'function',
|
|
119
|
+
file: 'src/bun/topology-audit.ts',
|
|
120
|
+
purl: PURL,
|
|
121
|
+
symbol: 'auditTopologyRelations',
|
|
122
|
+
purpose: 'Verifies authored relations[] endpoints',
|
|
123
|
+
deprecated: true,
|
|
124
|
+
removedIn: {
|
|
125
|
+
commit: 'e168afc',
|
|
126
|
+
reason: 'Remove topology relations; nest module boundaries by path',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
label: 'deprecated, no removedIn (bare marker)',
|
|
132
|
+
component: {
|
|
133
|
+
alias: 'dead-bare',
|
|
134
|
+
name: 'graphifyHasRelationBetween',
|
|
135
|
+
construct: 'function',
|
|
136
|
+
file: 'src/bun/topology-audit.ts',
|
|
137
|
+
purl: PURL,
|
|
138
|
+
symbol: 'graphifyHasRelationBetween',
|
|
139
|
+
purpose: 'Relation predicate over the graphify edge set',
|
|
140
|
+
deprecated: true,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
label: 'deprecated + role: entry (badge shows deprecated only)',
|
|
145
|
+
component: {
|
|
146
|
+
alias: 'dead-entry',
|
|
147
|
+
name: 'topologyRelationsAudit',
|
|
148
|
+
construct: 'function',
|
|
149
|
+
file: 'src/bun/topology-audit.ts',
|
|
150
|
+
purl: PURL,
|
|
151
|
+
symbol: 'topologyRelationsAudit',
|
|
152
|
+
purpose: 'Audit entry for relation endpoints',
|
|
153
|
+
deprecated: true,
|
|
154
|
+
role: 'entry',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
label: 'proposed (the sibling temporary state)',
|
|
159
|
+
component: {
|
|
160
|
+
alias: 'prop-fn',
|
|
161
|
+
name: 'OpenCodeV2Lifecycle',
|
|
162
|
+
construct: 'function',
|
|
163
|
+
file: '',
|
|
164
|
+
purl: PURL,
|
|
165
|
+
symbol: 'OpenCodeV2Lifecycle',
|
|
166
|
+
purpose: 'Proposed Studio host lifecycle',
|
|
167
|
+
proposed: true,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
/** Spotlights: slate deprecated badge + border vs live, proposed, and entry. */
|
|
173
|
+
export const Spotlights: Story = {
|
|
174
|
+
render: () => (
|
|
175
|
+
<div
|
|
176
|
+
style={{
|
|
177
|
+
padding: 24,
|
|
178
|
+
display: 'flex',
|
|
179
|
+
flexWrap: 'wrap',
|
|
180
|
+
gap: 28,
|
|
181
|
+
alignItems: 'flex-start',
|
|
182
|
+
minHeight: '100vh',
|
|
183
|
+
boxSizing: 'border-box',
|
|
184
|
+
}}
|
|
185
|
+
>
|
|
186
|
+
{spotlightRows.map((row) => (
|
|
187
|
+
<NodeSpotlight key={row.component.alias} {...row} />
|
|
188
|
+
))}
|
|
189
|
+
</div>
|
|
190
|
+
),
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Full graph: live audit calling a deleted topology audit, which called two
|
|
195
|
+
// more deleted helpers. Mirrors the real wedged model.
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
const deprecatedComponents: SubsystemComponent[] = [
|
|
198
|
+
{
|
|
199
|
+
alias: 'audit',
|
|
200
|
+
name: 'auditSubsystemModel',
|
|
201
|
+
construct: 'function',
|
|
202
|
+
file: 'src/bun/verify-subsystem-component.ts',
|
|
203
|
+
purl: PURL,
|
|
204
|
+
symbol: 'auditSubsystemModel',
|
|
205
|
+
purpose: 'Deterministic dry-run audit of a stored model',
|
|
206
|
+
role: 'entry',
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
alias: 'topology',
|
|
210
|
+
name: 'auditTopologyRelations',
|
|
211
|
+
construct: 'function',
|
|
212
|
+
file: 'src/bun/topology-audit.ts',
|
|
213
|
+
purl: PURL,
|
|
214
|
+
symbol: 'auditTopologyRelations',
|
|
215
|
+
purpose: 'Orchestrates relations[] endpoint checks',
|
|
216
|
+
deprecated: true,
|
|
217
|
+
removedIn: {
|
|
218
|
+
commit: 'e168afc',
|
|
219
|
+
reason: 'Remove topology relations; nest module boundaries by path',
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
alias: 'topo-relation',
|
|
224
|
+
name: 'graphifyHasRelationBetween',
|
|
225
|
+
construct: 'function',
|
|
226
|
+
file: 'src/bun/topology-audit.ts',
|
|
227
|
+
purl: PURL,
|
|
228
|
+
symbol: 'graphifyHasRelationBetween',
|
|
229
|
+
purpose: 'True when Graphify records a relation between two nodes',
|
|
230
|
+
deprecated: true,
|
|
231
|
+
removedIn: {
|
|
232
|
+
commit: 'e168afc',
|
|
233
|
+
reason: 'Remove topology relations; nest module boundaries by path',
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
alias: 'topo-hints',
|
|
238
|
+
name: 'graphifyHasRelationTowardHints',
|
|
239
|
+
construct: 'function',
|
|
240
|
+
file: 'src/bun/topology-audit.ts',
|
|
241
|
+
purl: PURL,
|
|
242
|
+
symbol: 'graphifyHasRelationTowardHints',
|
|
243
|
+
purpose: 'Relation hints directed at a component',
|
|
244
|
+
deprecated: true,
|
|
245
|
+
removedIn: { commit: 'e168afc' },
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
alias: 'boundary',
|
|
249
|
+
name: 'auditBoundaryFields',
|
|
250
|
+
construct: 'function',
|
|
251
|
+
file: 'src/bun/boundary-audit.ts',
|
|
252
|
+
purl: PURL,
|
|
253
|
+
symbol: 'auditBoundaryFields',
|
|
254
|
+
purpose: 'Package/module containment + process checks',
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
const deprecatedTrails = [
|
|
259
|
+
trailFromSteps('audit-steps', 'Audit (dry-run, graphify-backed)', [
|
|
260
|
+
['audit', 'topology', 'calls', 'src/bun/verify-subsystem-component.ts', 1],
|
|
261
|
+
['audit', 'boundary', 'calls', 'src/bun/verify-subsystem-component.ts', 1],
|
|
262
|
+
['topology', 'topo-relation', 'calls', 'src/bun/topology-audit.ts', 1],
|
|
263
|
+
['topology', 'topo-hints', 'calls', 'src/bun/topology-audit.ts', 1],
|
|
264
|
+
]),
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
function DeprecatedDemo() {
|
|
268
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
269
|
+
return (
|
|
270
|
+
<div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
271
|
+
<SubsystemComponentGraph
|
|
272
|
+
components={deprecatedComponents}
|
|
273
|
+
trails={deprecatedTrails}
|
|
274
|
+
onSelect={(id) => setSelected(id)}
|
|
275
|
+
/>
|
|
276
|
+
<div style={{ marginTop: 8, padding: '0 12px', fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
|
|
277
|
+
Click a slate `deprecated` badge to open the removal provenance
|
|
278
|
+
{selected ? ` · selected: ${selected}` : ''}
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Full graph: the deleted topology subtree still wired into a live audit. */
|
|
285
|
+
export const RemovedSubtree: Story = {
|
|
286
|
+
render: () => <DeprecatedDemo />,
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The popover states, isolated — every badge variant the popover can render.
|
|
291
|
+
* `Spotlights` shows the closed badge only; this is what the click opens.
|
|
292
|
+
*/
|
|
293
|
+
export function RemovalPopover() {
|
|
294
|
+
const deprecatedColor = '#6b7280';
|
|
295
|
+
const variants = [
|
|
296
|
+
{
|
|
297
|
+
label: 'with removedIn',
|
|
298
|
+
removal: {
|
|
299
|
+
commit: 'e168afc',
|
|
300
|
+
reason: 'Remove topology relations; nest module boundaries by path',
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
{ label: 'no removedIn (fallback copy)', removal: undefined },
|
|
304
|
+
] as const;
|
|
305
|
+
return (
|
|
306
|
+
<div style={{ display: 'flex', gap: 24, padding: 24, alignItems: 'flex-start' }}>
|
|
307
|
+
{variants.map((v) => (
|
|
308
|
+
<div key={v.label} style={{ width: 300 }}>
|
|
309
|
+
<div
|
|
310
|
+
style={{
|
|
311
|
+
fontFamily: 'ui-monospace, monospace',
|
|
312
|
+
fontSize: 11,
|
|
313
|
+
color: '#888',
|
|
314
|
+
marginBottom: 6,
|
|
315
|
+
}}
|
|
316
|
+
>
|
|
317
|
+
{v.label}
|
|
318
|
+
</div>
|
|
319
|
+
<div
|
|
320
|
+
role="dialog"
|
|
321
|
+
aria-label="Removal provenance"
|
|
322
|
+
style={{
|
|
323
|
+
padding: '8px 10px',
|
|
324
|
+
borderRadius: 6,
|
|
325
|
+
background: '#1f1f1f',
|
|
326
|
+
border: `1px solid ${deprecatedColor}`,
|
|
327
|
+
boxShadow: '0 6px 20px rgba(0,0,0,0.35)',
|
|
328
|
+
fontFamily: 'system-ui, sans-serif',
|
|
329
|
+
fontSize: 13,
|
|
330
|
+
color: '#eee',
|
|
331
|
+
}}
|
|
332
|
+
>
|
|
333
|
+
<div style={{ fontWeight: 600, marginBottom: 4, color: deprecatedColor }}>
|
|
334
|
+
Deprecated
|
|
335
|
+
</div>
|
|
336
|
+
{v.removal ? (
|
|
337
|
+
<>
|
|
338
|
+
<div style={{ fontFamily: 'ui-monospace, monospace', marginBottom: 4 }}>
|
|
339
|
+
removed in {v.removal.commit}
|
|
340
|
+
</div>
|
|
341
|
+
{v.removal.reason && <div style={{ color: '#aaa' }}>{v.removal.reason}</div>}
|
|
342
|
+
</>
|
|
343
|
+
) : (
|
|
344
|
+
<div style={{ color: '#aaa' }}>
|
|
345
|
+
This claim's source was removed upstream.
|
|
346
|
+
</div>
|
|
347
|
+
)}
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
))}
|
|
351
|
+
</div>
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** The removal-provenance popover, both copy variants. */
|
|
356
|
+
export const Popover: Story = {
|
|
357
|
+
render: () => <RemovalPopover />,
|
|
358
|
+
};
|
|
@@ -46,24 +46,30 @@ const GRAPH_DESCRIPTION =
|
|
|
46
46
|
/** Every chip state side by side, for quick visual review. */
|
|
47
47
|
export const States: Story = {
|
|
48
48
|
render: () => {
|
|
49
|
-
const rows: Array<{ label: string; status: SubsystemDiagnosticStatus; issueCount?: number; stale?: boolean; busy?: boolean }> = [
|
|
49
|
+
const rows: Array<{ label: string; status: SubsystemDiagnosticStatus; issueCount?: number; fixableCount?: number; stale?: boolean; busy?: boolean }> = [
|
|
50
50
|
{ label: 'ok', status: 'ok' },
|
|
51
51
|
{ label: 'ok (stale)', status: 'ok', stale: true },
|
|
52
52
|
{ label: 'issues · 3', status: 'issues', issueCount: 3 },
|
|
53
53
|
{ label: 'gaps · 2', status: 'gaps', issueCount: 2 },
|
|
54
54
|
{ label: 'unknown (never checked)', status: 'unknown' },
|
|
55
55
|
{ label: 'issues (busy)', status: 'issues', issueCount: 3, busy: true },
|
|
56
|
+
{ label: 'issues · 7, 7 fixable', status: 'issues', issueCount: 7, fixableCount: 7 },
|
|
57
|
+
{ label: 'issues · 4, 2 fixable', status: 'issues', issueCount: 4, fixableCount: 2 },
|
|
58
|
+
{ label: 'issues · 2, fixable + stale', status: 'issues', issueCount: 2, fixableCount: 2, stale: true },
|
|
59
|
+
{ label: 'issues · 120, fixable capped', status: 'issues', issueCount: 120, fixableCount: 120 },
|
|
60
|
+
{ label: 'issues (busy) hides badge', status: 'issues', issueCount: 3, fixableCount: 3, busy: true },
|
|
56
61
|
];
|
|
57
62
|
return (
|
|
58
63
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 18, padding: 32 }}>
|
|
59
64
|
{rows.map((row) => (
|
|
60
65
|
<div key={row.label} style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
|
61
|
-
<span style={{ width:
|
|
66
|
+
<span style={{ width: 220, fontFamily: 'monospace', fontSize: 12, opacity: 0.7 }}>
|
|
62
67
|
{row.label}
|
|
63
68
|
</span>
|
|
64
69
|
<SubsystemDiagnosticToggle
|
|
65
70
|
status={row.status}
|
|
66
71
|
issueCount={row.issueCount}
|
|
72
|
+
fixableCount={row.fixableCount}
|
|
67
73
|
stale={row.stale}
|
|
68
74
|
busy={row.busy}
|
|
69
75
|
onToggle={() => {}}
|
|
@@ -75,6 +81,25 @@ export const States: Story = {
|
|
|
75
81
|
},
|
|
76
82
|
};
|
|
77
83
|
|
|
84
|
+
/**
|
|
85
|
+
* The real case that motivated the badge: seven re-pin findings, every one a
|
|
86
|
+
* one-click fix the construct-fixer defers to the human. The chip announces
|
|
87
|
+
* them so the deferral isn't silent.
|
|
88
|
+
*/
|
|
89
|
+
export const GraphFixableBadge: Story = {
|
|
90
|
+
render: () => (
|
|
91
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
92
|
+
<SubsystemComponentGraph
|
|
93
|
+
components={graphComponents}
|
|
94
|
+
trails={graphEdges.trails}
|
|
95
|
+
title="Vestigial detail layer vs single-source declaration"
|
|
96
|
+
description={GRAPH_DESCRIPTION}
|
|
97
|
+
diagnostic={{ status: 'issues', issueCount: 7, fixableCount: 7, onToggle: () => {} }}
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
),
|
|
101
|
+
};
|
|
102
|
+
|
|
78
103
|
/** The chip in its real home: the sidebar title row, beside the description toggle. */
|
|
79
104
|
export const GraphClean: Story = {
|
|
80
105
|
render: () => (
|
|
@@ -8,7 +8,7 @@ import { PierreTrailCodeView } from '../../../pierre';
|
|
|
8
8
|
import type { TrailViewerContext } from '../../../subsystem/SubsystemComponentGraph';
|
|
9
9
|
|
|
10
10
|
const meta = {
|
|
11
|
-
title: 'Subsystem/ComponentGraph/
|
|
11
|
+
title: 'Subsystem/ComponentGraph/Trails',
|
|
12
12
|
component: SubsystemComponentGraph,
|
|
13
13
|
parameters: {
|
|
14
14
|
layout: 'fullscreen',
|
|
@@ -164,7 +164,7 @@ const drawingTrails: SubsystemTrail[] = [
|
|
|
164
164
|
},
|
|
165
165
|
];
|
|
166
166
|
|
|
167
|
-
function
|
|
167
|
+
function TrailsDemo() {
|
|
168
168
|
const renderTrailViewer = useCallback(
|
|
169
169
|
({
|
|
170
170
|
trail,
|
|
@@ -173,6 +173,7 @@ function FlowsDemo() {
|
|
|
173
173
|
proposedAliases,
|
|
174
174
|
resolveSymbol,
|
|
175
175
|
onSymbolClick,
|
|
176
|
+
onVisibleStepChange,
|
|
176
177
|
}: TrailViewerContext) => (
|
|
177
178
|
<PierreTrailCodeView
|
|
178
179
|
trail={trail}
|
|
@@ -183,6 +184,7 @@ function FlowsDemo() {
|
|
|
183
184
|
proposedAliases={proposedAliases}
|
|
184
185
|
resolveSymbol={resolveSymbol}
|
|
185
186
|
onSymbolClick={onSymbolClick}
|
|
187
|
+
onVisibleStepChange={onVisibleStepChange}
|
|
186
188
|
/>
|
|
187
189
|
),
|
|
188
190
|
[],
|
|
@@ -217,8 +219,8 @@ function FlowsDemo() {
|
|
|
217
219
|
);
|
|
218
220
|
}
|
|
219
221
|
|
|
220
|
-
export const
|
|
221
|
-
render: () => <
|
|
222
|
+
export const ThreeTrails: Story = {
|
|
223
|
+
render: () => <TrailsDemo />,
|
|
222
224
|
};
|
|
223
225
|
|
|
224
226
|
/**
|
|
@@ -281,6 +283,7 @@ function ProposedMissingStepDemo() {
|
|
|
281
283
|
proposedAliases,
|
|
282
284
|
resolveSymbol,
|
|
283
285
|
onSymbolClick,
|
|
286
|
+
onVisibleStepChange,
|
|
284
287
|
}: TrailViewerContext) => (
|
|
285
288
|
<PierreTrailCodeView
|
|
286
289
|
trail={trail}
|
|
@@ -291,6 +294,7 @@ function ProposedMissingStepDemo() {
|
|
|
291
294
|
proposedAliases={proposedAliases}
|
|
292
295
|
resolveSymbol={resolveSymbol}
|
|
293
296
|
onSymbolClick={onSymbolClick}
|
|
297
|
+
onVisibleStepChange={onVisibleStepChange}
|
|
294
298
|
/>
|
|
295
299
|
),
|
|
296
300
|
[],
|
|
@@ -480,6 +484,7 @@ function ClickableConstructsDemo() {
|
|
|
480
484
|
proposedAliases,
|
|
481
485
|
resolveSymbol,
|
|
482
486
|
onSymbolClick,
|
|
487
|
+
onVisibleStepChange,
|
|
483
488
|
}: TrailViewerContext) => (
|
|
484
489
|
<PierreTrailCodeView
|
|
485
490
|
trail={trail}
|
|
@@ -490,6 +495,7 @@ function ClickableConstructsDemo() {
|
|
|
490
495
|
proposedAliases={proposedAliases}
|
|
491
496
|
resolveSymbol={resolveSymbol}
|
|
492
497
|
onSymbolClick={onSymbolClick}
|
|
498
|
+
onVisibleStepChange={onVisibleStepChange}
|
|
493
499
|
/>
|
|
494
500
|
),
|
|
495
501
|
[],
|