@principal-ai/subsystems-react 0.55.3 → 0.56.1
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 +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -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 +4 -9
- package/dist/pierre/PierreTrailCodeView.d.ts.map +1 -1
- package/dist/pierre/PierreTrailCodeView.js +28 -25
- 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/pierre/trailSnippet.d.ts +10 -0
- package/dist/pierre/trailSnippet.d.ts.map +1 -0
- package/dist/pierre/trailSnippet.js +22 -0
- package/dist/pierre/trailSnippet.js.map +1 -0
- 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/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/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 +10 -1
- package/src/pierre/PierreFileView.tsx +3 -3
- package/src/pierre/PierreTrailCodeView.tsx +24 -36
- package/src/pierre/fileAvailability.test.ts +0 -6
- package/src/pierre/fileAvailability.ts +5 -13
- package/src/pierre/trailSnippet.test.ts +74 -0
- package/src/pierre/trailSnippet.ts +35 -0
- 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/Trails.stories.tsx +0 -187
- package/src/subsystem/C4Graph.tsx +0 -1
- 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/model.test.ts +7 -22
- package/src/subsystem/model.ts +6 -20
- package/src/subsystem/nodes.tsx +19 -28
- 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
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive a call stack for one trail step.
|
|
3
|
+
*
|
|
4
|
+
* A trail is a timeline, not a stack frame dump: steps can return to an
|
|
5
|
+
* ancestor, take sibling calls, or describe a non-call seam (`uses`, `feeds`,
|
|
6
|
+
* …). This module links each `calls` step to the prior call that entered its
|
|
7
|
+
* caller construct, then renders that ancestry as a stack. Non-call steps are
|
|
8
|
+
* annotations on the nearest frame — they are not frames.
|
|
9
|
+
*
|
|
10
|
+
* The links use the resolved `site`/`target` when present, falling back to the
|
|
11
|
+
* component aliases. That keeps the view useful before every step has its
|
|
12
|
+
* construct refs captured.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
SubsystemTrail,
|
|
17
|
+
SubsystemTrailMechanism,
|
|
18
|
+
SubsystemTrailStep,
|
|
19
|
+
} from './model';
|
|
20
|
+
import {
|
|
21
|
+
trailComponentLabel,
|
|
22
|
+
type TrailComponentLabels,
|
|
23
|
+
} from './trailStepDisplay';
|
|
24
|
+
|
|
25
|
+
export interface TrailStackFrame {
|
|
26
|
+
/** The step whose call creates this frame. */
|
|
27
|
+
stepIndex: number;
|
|
28
|
+
/** Frame label — the caller construct, or the component alias. */
|
|
29
|
+
label: string;
|
|
30
|
+
/** Component alias of the caller (for focus/selection wiring). */
|
|
31
|
+
componentAlias: string;
|
|
32
|
+
/** The selected step's own frame (innermost). */
|
|
33
|
+
current: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface TrailStackAnnotation {
|
|
37
|
+
/** Non-call step attached to the current frame. */
|
|
38
|
+
stepIndex: number;
|
|
39
|
+
mechanism: SubsystemTrailMechanism;
|
|
40
|
+
label: string;
|
|
41
|
+
/** The selected step itself (vs context around it). */
|
|
42
|
+
selected: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface TrailStackView {
|
|
46
|
+
/** Outermost → innermost. Empty only for an out-of-range index. */
|
|
47
|
+
frames: TrailStackFrame[];
|
|
48
|
+
/** Non-call seams attached to the current frame. */
|
|
49
|
+
annotations: TrailStackAnnotation[];
|
|
50
|
+
/** True when the selected step is a call frame. */
|
|
51
|
+
isCall: boolean;
|
|
52
|
+
/** The selected call's target, shown as the next frame-to-be. */
|
|
53
|
+
callee?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function callerSymbol(step: SubsystemTrailStep): string {
|
|
57
|
+
return step.site?.symbol?.trim() || step.symbol?.trim() || step.from;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function calleeSymbol(step: SubsystemTrailStep): string {
|
|
61
|
+
return step.target?.symbol?.trim() || step.to;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function callerLabel(
|
|
65
|
+
step: SubsystemTrailStep,
|
|
66
|
+
byAlias?: TrailComponentLabels,
|
|
67
|
+
): string {
|
|
68
|
+
return callerSymbol(step) || trailComponentLabel(step.from, byAlias);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function calleeLabel(
|
|
72
|
+
step: SubsystemTrailStep,
|
|
73
|
+
byAlias?: TrailComponentLabels,
|
|
74
|
+
): string {
|
|
75
|
+
return step.target?.symbol?.trim() || trailComponentLabel(step.to, byAlias);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isCallStep(step: SubsystemTrailStep | undefined): boolean {
|
|
79
|
+
return step?.mechanism === 'calls';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Does call `a` lead into the caller of call `b`? */
|
|
83
|
+
function leadsInto(a: SubsystemTrailStep, b: SubsystemTrailStep): boolean {
|
|
84
|
+
return a.to === b.from || calleeSymbol(a) === callerSymbol(b);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The nearest earlier call that entered this step's caller frame. */
|
|
88
|
+
function parentCallIndex(
|
|
89
|
+
trail: SubsystemTrail,
|
|
90
|
+
stepIndex: number,
|
|
91
|
+
): number | null {
|
|
92
|
+
const child = trail.steps[stepIndex];
|
|
93
|
+
if (!child) return null;
|
|
94
|
+
for (let i = stepIndex - 1; i >= 0; i--) {
|
|
95
|
+
const candidate = trail.steps[i];
|
|
96
|
+
if (!isCallStep(candidate)) continue;
|
|
97
|
+
if (leadsInto(candidate!, child)) return i;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The call chain ending at `stepIndex`, outermost first. */
|
|
103
|
+
function callChain(trail: SubsystemTrail, stepIndex: number): number[] {
|
|
104
|
+
const chain: number[] = [];
|
|
105
|
+
const seen = new Set<number>();
|
|
106
|
+
let cursor: number | null = stepIndex;
|
|
107
|
+
while (cursor != null && !seen.has(cursor)) {
|
|
108
|
+
seen.add(cursor);
|
|
109
|
+
chain.unshift(cursor);
|
|
110
|
+
cursor = parentCallIndex(trail, cursor);
|
|
111
|
+
}
|
|
112
|
+
return chain;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function framesForChain(
|
|
116
|
+
trail: SubsystemTrail,
|
|
117
|
+
chain: number[],
|
|
118
|
+
selectedStepIndex: number | null,
|
|
119
|
+
byAlias?: TrailComponentLabels,
|
|
120
|
+
): TrailStackFrame[] {
|
|
121
|
+
return chain.map((idx) => {
|
|
122
|
+
const step = trail.steps[idx]!;
|
|
123
|
+
return {
|
|
124
|
+
stepIndex: idx,
|
|
125
|
+
label: callerLabel(step, byAlias),
|
|
126
|
+
componentAlias: step.from,
|
|
127
|
+
current: idx === selectedStepIndex,
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The nearest earlier call that shares this non-call step's source component.
|
|
134
|
+
* `uses`/`feeds`/… are dependencies of that frame, not new frames.
|
|
135
|
+
*/
|
|
136
|
+
function anchorCallIndex(
|
|
137
|
+
trail: SubsystemTrail,
|
|
138
|
+
stepIndex: number,
|
|
139
|
+
): number | null {
|
|
140
|
+
const step = trail.steps[stepIndex];
|
|
141
|
+
if (!step) return null;
|
|
142
|
+
const source = callerSymbol(step);
|
|
143
|
+
for (let i = stepIndex - 1; i >= 0; i--) {
|
|
144
|
+
const candidate = trail.steps[i];
|
|
145
|
+
if (!isCallStep(candidate)) continue;
|
|
146
|
+
if (
|
|
147
|
+
candidate!.from === step.from ||
|
|
148
|
+
candidate!.to === step.from ||
|
|
149
|
+
callerSymbol(candidate!) === source ||
|
|
150
|
+
calleeSymbol(candidate!) === source
|
|
151
|
+
) {
|
|
152
|
+
return i;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function annotationLabel(
|
|
159
|
+
step: SubsystemTrailStep,
|
|
160
|
+
byAlias?: TrailComponentLabels,
|
|
161
|
+
): string {
|
|
162
|
+
return `${step.mechanism} ${calleeLabel(step, byAlias)}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Build the stack at one trail step.
|
|
167
|
+
*
|
|
168
|
+
* - `calls` → the chain of calls into the caller frame, plus the callee as the
|
|
169
|
+
* next frame (not yet on the stack).
|
|
170
|
+
* - `uses` / data + event seams → the nearest call frame they attach to, with
|
|
171
|
+
* the seam as an annotation.
|
|
172
|
+
*/
|
|
173
|
+
export function buildTrailStack(
|
|
174
|
+
trail: SubsystemTrail,
|
|
175
|
+
stepIndex: number,
|
|
176
|
+
byAlias?: TrailComponentLabels,
|
|
177
|
+
): TrailStackView {
|
|
178
|
+
const step = trail.steps[stepIndex];
|
|
179
|
+
if (!step) {
|
|
180
|
+
return { frames: [], annotations: [], isCall: false };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (isCallStep(step)) {
|
|
184
|
+
const chain = callChain(trail, stepIndex);
|
|
185
|
+
return {
|
|
186
|
+
frames: framesForChain(trail, chain, stepIndex, byAlias),
|
|
187
|
+
annotations: [],
|
|
188
|
+
isCall: true,
|
|
189
|
+
callee: calleeLabel(step, byAlias),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const anchor = anchorCallIndex(trail, stepIndex);
|
|
194
|
+
if (anchor != null) {
|
|
195
|
+
const chain = callChain(trail, anchor);
|
|
196
|
+
return {
|
|
197
|
+
frames: framesForChain(trail, chain, anchor, byAlias),
|
|
198
|
+
annotations: [
|
|
199
|
+
{
|
|
200
|
+
stepIndex,
|
|
201
|
+
mechanism: step.mechanism,
|
|
202
|
+
label: annotationLabel(step, byAlias),
|
|
203
|
+
selected: true,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
isCall: false,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
frames: [
|
|
212
|
+
{
|
|
213
|
+
stepIndex,
|
|
214
|
+
label: callerLabel(step, byAlias),
|
|
215
|
+
componentAlias: step.from,
|
|
216
|
+
current: true,
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
annotations: [
|
|
220
|
+
{
|
|
221
|
+
stepIndex,
|
|
222
|
+
mechanism: step.mechanism,
|
|
223
|
+
label: annotationLabel(step, byAlias),
|
|
224
|
+
selected: true,
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
isCall: false,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a trail step reads in the Trails panel.
|
|
3
|
+
*
|
|
4
|
+
* A step is one seam, but the panel shows the logical hop and the site in a
|
|
5
|
+
* single wrapping line:
|
|
6
|
+
*
|
|
7
|
+
* - `calls` — the caller frame (the site construct).
|
|
8
|
+
* - `uses` — `from uses to`, where `to` is the precise target when resolved.
|
|
9
|
+
* - Everything else — `from mechanism to`.
|
|
10
|
+
*
|
|
11
|
+
* The evidence site lives in the code view and the stack breadcrumb; the
|
|
12
|
+
* sidebar row stays one line so it can wrap instead of truncating.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { SubsystemTrailStep, SubsystemTrailMechanism } from './model';
|
|
16
|
+
|
|
17
|
+
/** The slice of a component the labels need — keeps fixtures light. */
|
|
18
|
+
export interface TrailComponentLabelSource {
|
|
19
|
+
alias: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
symbol?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Alias → component, for turning model-local aliases into display names. */
|
|
25
|
+
export type TrailComponentLabels = ReadonlyMap<string, TrailComponentLabelSource>;
|
|
26
|
+
|
|
27
|
+
/** One row's primary text. */
|
|
28
|
+
export interface TrailStepDisplay {
|
|
29
|
+
mechanism: SubsystemTrailMechanism;
|
|
30
|
+
/** The line that names the step: caller frame for `calls`, hop otherwise. */
|
|
31
|
+
primary: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Display label for a component alias; falls back to the alias. */
|
|
35
|
+
export function trailComponentLabel(
|
|
36
|
+
alias: string,
|
|
37
|
+
byAlias?: TrailComponentLabels,
|
|
38
|
+
): string {
|
|
39
|
+
const component = byAlias?.get(alias);
|
|
40
|
+
const label = component?.name?.trim() || component?.symbol?.trim();
|
|
41
|
+
return label || alias;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build the single display line for one step.
|
|
46
|
+
*
|
|
47
|
+
* The resolved target (`step.target.symbol`) is the precise callee, while `to`
|
|
48
|
+
* is only a component alias and can be a whole class/store. Prefer the target.
|
|
49
|
+
*/
|
|
50
|
+
export function trailStepDisplay(
|
|
51
|
+
step: SubsystemTrailStep,
|
|
52
|
+
byAlias?: TrailComponentLabels,
|
|
53
|
+
): TrailStepDisplay {
|
|
54
|
+
const from = trailComponentLabel(step.from, byAlias);
|
|
55
|
+
const to = step.target?.symbol?.trim() || trailComponentLabel(step.to, byAlias);
|
|
56
|
+
const siteSymbol = step.site?.symbol?.trim() || step.symbol?.trim() || from;
|
|
57
|
+
|
|
58
|
+
if (step.mechanism === 'calls') {
|
|
59
|
+
return { mechanism: step.mechanism, primary: siteSymbol };
|
|
60
|
+
}
|
|
61
|
+
if (step.mechanism === 'uses') {
|
|
62
|
+
return { mechanism: step.mechanism, primary: `${from} uses ${to}` };
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
mechanism: step.mechanism,
|
|
66
|
+
primary: `${from} ${step.mechanism} ${to}`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -1,256 +0,0 @@
|
|
|
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
|
-
const meta = {
|
|
15
|
-
title: 'Canvases/Subsystem/Proposed',
|
|
16
|
-
parameters: { layout: 'fullscreen' },
|
|
17
|
-
tags: ['autodocs'],
|
|
18
|
-
decorators: [
|
|
19
|
-
(Story) => (
|
|
20
|
-
<ThemeProvider theme={defaultEditorTheme}>
|
|
21
|
-
<ReactFlowProvider>
|
|
22
|
-
<Story />
|
|
23
|
-
</ReactFlowProvider>
|
|
24
|
-
</ThemeProvider>
|
|
25
|
-
),
|
|
26
|
-
],
|
|
27
|
-
} satisfies Meta;
|
|
28
|
-
|
|
29
|
-
export default meta;
|
|
30
|
-
type Story = StoryObj<typeof meta>;
|
|
31
|
-
|
|
32
|
-
const PURL = 'pkg:github/principal-ai/subsystem-modeling';
|
|
33
|
-
|
|
34
|
-
function nodeProps(c: SubsystemComponent): NodeProps<SubsystemGraphNode> {
|
|
35
|
-
return {
|
|
36
|
-
data: { component: c },
|
|
37
|
-
selected: false,
|
|
38
|
-
width: 230,
|
|
39
|
-
height: 84,
|
|
40
|
-
} as unknown as NodeProps<SubsystemGraphNode>;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Side-by-side single-node spotlights — proposed vs normal vs role combos. */
|
|
44
|
-
function NodeSpotlight({
|
|
45
|
-
label,
|
|
46
|
-
component,
|
|
47
|
-
}: {
|
|
48
|
-
label: string;
|
|
49
|
-
component: SubsystemComponent;
|
|
50
|
-
}) {
|
|
51
|
-
return (
|
|
52
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, width: 260 }}>
|
|
53
|
-
<div
|
|
54
|
-
style={{
|
|
55
|
-
fontFamily: 'ui-monospace, monospace',
|
|
56
|
-
fontSize: 11,
|
|
57
|
-
color: '#888',
|
|
58
|
-
minHeight: 32,
|
|
59
|
-
}}
|
|
60
|
-
>
|
|
61
|
-
{label}
|
|
62
|
-
</div>
|
|
63
|
-
<div style={{ position: 'relative', height: 110, width: 240 }}>
|
|
64
|
-
<SubsystemComponentNode {...nodeProps(component)} />
|
|
65
|
-
</div>
|
|
66
|
-
<pre
|
|
67
|
-
style={{
|
|
68
|
-
margin: 0,
|
|
69
|
-
fontSize: 10,
|
|
70
|
-
fontFamily: 'ui-monospace, monospace',
|
|
71
|
-
background: '#1a1a1a',
|
|
72
|
-
color: '#ccc',
|
|
73
|
-
padding: 8,
|
|
74
|
-
borderRadius: 4,
|
|
75
|
-
overflow: 'auto',
|
|
76
|
-
}}
|
|
77
|
-
>
|
|
78
|
-
{JSON.stringify(
|
|
79
|
-
{
|
|
80
|
-
construct: component.construct,
|
|
81
|
-
proposed: component.proposed || undefined,
|
|
82
|
-
role: component.role,
|
|
83
|
-
symbol: component.symbol || undefined,
|
|
84
|
-
file: component.file || undefined,
|
|
85
|
-
},
|
|
86
|
-
null,
|
|
87
|
-
2,
|
|
88
|
-
)}
|
|
89
|
-
</pre>
|
|
90
|
-
</div>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const spotlightRows: Array<{ label: string; component: SubsystemComponent }> = [
|
|
95
|
-
{
|
|
96
|
-
label: 'normal function (baseline)',
|
|
97
|
-
component: {
|
|
98
|
-
alias: 'live-fn',
|
|
99
|
-
name: 'ensureOpencode',
|
|
100
|
-
construct: 'function',
|
|
101
|
-
file: 'src/bun/opencode-v2.ts',
|
|
102
|
-
purl: PURL,
|
|
103
|
-
symbol: 'ensureOpencode',
|
|
104
|
-
purpose: 'Detect / install / update the OpenCode CLI',
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
label: 'proposed function — dashed border + right badge',
|
|
109
|
-
component: {
|
|
110
|
-
alias: 'prop-fn',
|
|
111
|
-
name: 'OpenCodeV2Lifecycle',
|
|
112
|
-
construct: 'function',
|
|
113
|
-
file: '',
|
|
114
|
-
purl: PURL,
|
|
115
|
-
symbol: 'OpenCodeV2Lifecycle',
|
|
116
|
-
purpose: 'Studio-managed OpenCode V2 detect / install / update',
|
|
117
|
-
proposed: true,
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
label: 'proposed class',
|
|
122
|
-
component: {
|
|
123
|
-
alias: 'prop-class',
|
|
124
|
-
name: 'MaintainModelAgent',
|
|
125
|
-
construct: 'class',
|
|
126
|
-
file: 'src/bun/maintain-model-agent.ts',
|
|
127
|
-
purl: PURL,
|
|
128
|
-
symbol: 'MaintainModelAgent',
|
|
129
|
-
purpose: 'Planned host agent that drives model maintenance',
|
|
130
|
-
proposed: true,
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
label: 'proposed + role: entry (badge shows proposed only)',
|
|
135
|
-
component: {
|
|
136
|
-
alias: 'prop-entry',
|
|
137
|
-
name: 'startMaintainFlow',
|
|
138
|
-
construct: 'function',
|
|
139
|
-
file: '',
|
|
140
|
-
purl: PURL,
|
|
141
|
-
symbol: 'startMaintainFlow',
|
|
142
|
-
purpose: 'Planned RPC entry for maintain',
|
|
143
|
-
proposed: true,
|
|
144
|
-
role: 'entry',
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
label: 'external (contrast — real outside system)',
|
|
149
|
-
component: {
|
|
150
|
-
alias: 'ext',
|
|
151
|
-
name: 'OpenCode CLI',
|
|
152
|
-
construct: 'external',
|
|
153
|
-
file: '',
|
|
154
|
-
purl: 'pkg:npm/@opencode-ai/cli',
|
|
155
|
-
symbol: '',
|
|
156
|
-
purpose: 'External OpenCode product',
|
|
157
|
-
role: 'service',
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
];
|
|
161
|
-
|
|
162
|
-
/** Spotlights: proposed badge + dashed border vs normal and external. */
|
|
163
|
-
export const Spotlights: Story = {
|
|
164
|
-
render: () => (
|
|
165
|
-
<div
|
|
166
|
-
style={{
|
|
167
|
-
padding: 24,
|
|
168
|
-
display: 'flex',
|
|
169
|
-
flexWrap: 'wrap',
|
|
170
|
-
gap: 28,
|
|
171
|
-
alignItems: 'flex-start',
|
|
172
|
-
minHeight: '100vh',
|
|
173
|
-
boxSizing: 'border-box',
|
|
174
|
-
}}
|
|
175
|
-
>
|
|
176
|
-
{spotlightRows.map((row) => (
|
|
177
|
-
<NodeSpotlight key={row.component.alias} {...row} />
|
|
178
|
-
))}
|
|
179
|
-
</div>
|
|
180
|
-
),
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
// ---------------------------------------------------------------------------
|
|
184
|
-
// Full graph: live seam calling a proposed host lifecycle + real external
|
|
185
|
-
// ---------------------------------------------------------------------------
|
|
186
|
-
const migrationComponents: SubsystemComponent[] = [
|
|
187
|
-
{
|
|
188
|
-
alias: 'audit',
|
|
189
|
-
name: 'auditSubsystemModel',
|
|
190
|
-
construct: 'function',
|
|
191
|
-
file: 'src/bun/verify-subsystem-component.ts',
|
|
192
|
-
purl: PURL,
|
|
193
|
-
symbol: 'auditSubsystemModel',
|
|
194
|
-
purpose: 'Deterministic dry-run audit of a stored model',
|
|
195
|
-
role: 'entry',
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
alias: 'lifecycle',
|
|
199
|
-
name: 'OpenCodeV2Lifecycle',
|
|
200
|
-
construct: 'function',
|
|
201
|
-
file: '',
|
|
202
|
-
purl: PURL,
|
|
203
|
-
symbol: 'OpenCodeV2Lifecycle',
|
|
204
|
-
purpose: 'Proposed Studio host: detect / install / update / Service.ensure',
|
|
205
|
-
proposed: true,
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
alias: 'maintain',
|
|
209
|
-
name: 'runMaintainModel',
|
|
210
|
-
construct: 'function',
|
|
211
|
-
file: 'src/bun/maintain-model.ts',
|
|
212
|
-
purl: PURL,
|
|
213
|
-
symbol: 'runMaintainModel',
|
|
214
|
-
purpose: 'Existing maintain agent runner',
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
alias: 'cli',
|
|
218
|
-
name: '@opencode-ai/cli',
|
|
219
|
-
construct: 'external',
|
|
220
|
-
file: '',
|
|
221
|
-
purl: 'pkg:npm/@opencode-ai/cli@beta',
|
|
222
|
-
symbol: '',
|
|
223
|
-
purpose: 'Real external OpenCode CLI',
|
|
224
|
-
role: 'service',
|
|
225
|
-
},
|
|
226
|
-
];
|
|
227
|
-
|
|
228
|
-
const migrationTrails = [
|
|
229
|
-
trailFromSteps('migration-steps', 'Migration steps', [
|
|
230
|
-
['audit', 'maintain', 'calls', 'src/bun/verify-subsystem-component.ts', 1],
|
|
231
|
-
['maintain', 'lifecycle', 'uses', 'src/bun/maintain-model.ts', 1],
|
|
232
|
-
['lifecycle', 'cli', 'calls', 'src/bun/opencode-v2.ts', 1],
|
|
233
|
-
]),
|
|
234
|
-
];
|
|
235
|
-
|
|
236
|
-
function MigrationDemo() {
|
|
237
|
-
const [selected, setSelected] = useState<string | null>(null);
|
|
238
|
-
return (
|
|
239
|
-
<div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
240
|
-
<SubsystemComponentGraph
|
|
241
|
-
components={migrationComponents}
|
|
242
|
-
trails={migrationTrails}
|
|
243
|
-
onSelect={(id) => setSelected(id)}
|
|
244
|
-
/>
|
|
245
|
-
<div style={{ marginTop: 8, padding: '0 12px', fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
|
|
246
|
-
proposed OpenCodeV2Lifecycle between live maintain code and real external CLI
|
|
247
|
-
{selected ? ` · selected: ${selected}` : ''}
|
|
248
|
-
</div>
|
|
249
|
-
</div>
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/** Full graph: proposed node between live code and a true external. */
|
|
254
|
-
export const MigrationPlaceholder: Story = {
|
|
255
|
-
render: () => <MigrationDemo />,
|
|
256
|
-
};
|