@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.
Files changed (98) hide show
  1. package/dist/index.d.ts +6 -2
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +3 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/pierre/PierreFileView.js +3 -3
  6. package/dist/pierre/PierreFileView.js.map +1 -1
  7. package/dist/pierre/PierreTrailCodeView.d.ts +4 -9
  8. package/dist/pierre/PierreTrailCodeView.d.ts.map +1 -1
  9. package/dist/pierre/PierreTrailCodeView.js +28 -25
  10. package/dist/pierre/PierreTrailCodeView.js.map +1 -1
  11. package/dist/pierre/fileAvailability.d.ts +4 -7
  12. package/dist/pierre/fileAvailability.d.ts.map +1 -1
  13. package/dist/pierre/fileAvailability.js +5 -10
  14. package/dist/pierre/fileAvailability.js.map +1 -1
  15. package/dist/pierre/trailSnippet.d.ts +10 -0
  16. package/dist/pierre/trailSnippet.d.ts.map +1 -0
  17. package/dist/pierre/trailSnippet.js +22 -0
  18. package/dist/pierre/trailSnippet.js.map +1 -0
  19. package/dist/subsystem/C4Graph.d.ts.map +1 -1
  20. package/dist/subsystem/C4Graph.js +1 -1
  21. package/dist/subsystem/C4Graph.js.map +1 -1
  22. package/dist/subsystem/FileDrawer.d.ts +3 -1
  23. package/dist/subsystem/FileDrawer.d.ts.map +1 -1
  24. package/dist/subsystem/FileDrawer.js +2 -2
  25. package/dist/subsystem/FileDrawer.js.map +1 -1
  26. package/dist/subsystem/IssueList.d.ts.map +1 -1
  27. package/dist/subsystem/IssueList.js +26 -0
  28. package/dist/subsystem/IssueList.js.map +1 -1
  29. package/dist/subsystem/SubsystemComponentGraph.d.ts +9 -5
  30. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  31. package/dist/subsystem/SubsystemComponentGraph.js +37 -47
  32. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  33. package/dist/subsystem/TrailStackBreadcrumb.d.ts +21 -0
  34. package/dist/subsystem/TrailStackBreadcrumb.d.ts.map +1 -0
  35. package/dist/subsystem/TrailStackBreadcrumb.js +77 -0
  36. package/dist/subsystem/TrailStackBreadcrumb.js.map +1 -0
  37. package/dist/subsystem/TrailStepsNav.d.ts +4 -3
  38. package/dist/subsystem/TrailStepsNav.d.ts.map +1 -1
  39. package/dist/subsystem/TrailStepsNav.js +13 -16
  40. package/dist/subsystem/TrailStepsNav.js.map +1 -1
  41. package/dist/subsystem/TrailsPanel.d.ts +9 -7
  42. package/dist/subsystem/TrailsPanel.d.ts.map +1 -1
  43. package/dist/subsystem/TrailsPanel.js +126 -76
  44. package/dist/subsystem/TrailsPanel.js.map +1 -1
  45. package/dist/subsystem/model.d.ts +5 -18
  46. package/dist/subsystem/model.d.ts.map +1 -1
  47. package/dist/subsystem/model.js +3 -11
  48. package/dist/subsystem/model.js.map +1 -1
  49. package/dist/subsystem/nodes.d.ts.map +1 -1
  50. package/dist/subsystem/nodes.js +17 -24
  51. package/dist/subsystem/nodes.js.map +1 -1
  52. package/dist/subsystem/stepEmphasis.d.ts +78 -0
  53. package/dist/subsystem/stepEmphasis.d.ts.map +1 -0
  54. package/dist/subsystem/stepEmphasis.js +89 -0
  55. package/dist/subsystem/stepEmphasis.js.map +1 -0
  56. package/dist/subsystem/trailBrief.d.ts.map +1 -1
  57. package/dist/subsystem/trailBrief.js +20 -0
  58. package/dist/subsystem/trailBrief.js.map +1 -1
  59. package/dist/subsystem/trailStack.d.ts +53 -0
  60. package/dist/subsystem/trailStack.d.ts.map +1 -0
  61. package/dist/subsystem/trailStack.js +154 -0
  62. package/dist/subsystem/trailStack.js.map +1 -0
  63. package/dist/subsystem/trailStepDisplay.d.ts +38 -0
  64. package/dist/subsystem/trailStepDisplay.d.ts.map +1 -0
  65. package/dist/subsystem/trailStepDisplay.js +41 -0
  66. package/dist/subsystem/trailStepDisplay.js.map +1 -0
  67. package/package.json +3 -3
  68. package/src/index.ts +10 -1
  69. package/src/pierre/PierreFileView.tsx +3 -3
  70. package/src/pierre/PierreTrailCodeView.tsx +24 -36
  71. package/src/pierre/fileAvailability.test.ts +0 -6
  72. package/src/pierre/fileAvailability.ts +5 -13
  73. package/src/pierre/trailSnippet.test.ts +74 -0
  74. package/src/pierre/trailSnippet.ts +35 -0
  75. package/src/stories/Canvases/Subsystem/Deprecated.stories.tsx +3 -17
  76. package/src/stories/Canvases/Subsystem/NodeAnatomy.stories.tsx +3 -31
  77. package/src/stories/Canvases/Subsystem/Trails.stories.tsx +0 -187
  78. package/src/subsystem/C4Graph.tsx +0 -1
  79. package/src/subsystem/FileDrawer.tsx +6 -0
  80. package/src/subsystem/IssueList.tsx +26 -0
  81. package/src/subsystem/SubsystemComponentGraph.tsx +68 -65
  82. package/src/subsystem/TrailStackBreadcrumb.test.tsx +109 -0
  83. package/src/subsystem/TrailStackBreadcrumb.tsx +172 -0
  84. package/src/subsystem/TrailStepsNav.test.tsx +35 -0
  85. package/src/subsystem/TrailStepsNav.tsx +18 -19
  86. package/src/subsystem/TrailsPanel.test.tsx +149 -0
  87. package/src/subsystem/TrailsPanel.tsx +165 -39
  88. package/src/subsystem/model.test.ts +7 -22
  89. package/src/subsystem/model.ts +6 -20
  90. package/src/subsystem/nodes.tsx +19 -28
  91. package/src/subsystem/stepEmphasis.ts +151 -0
  92. package/src/subsystem/trailBrief.test.ts +29 -0
  93. package/src/subsystem/trailBrief.ts +20 -0
  94. package/src/subsystem/trailStack.test.ts +133 -0
  95. package/src/subsystem/trailStack.ts +229 -0
  96. package/src/subsystem/trailStepDisplay.ts +68 -0
  97. package/src/stories/Canvases/Subsystem/Proposed.stories.tsx +0 -256
  98. 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
- };