@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
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* PierreTrailCodeView — multi-file step snippets via `@pierre/diffs` CodeView.
|
|
3
3
|
*
|
|
4
4
|
* Renders one sliced window per trail step in a single virtualized
|
|
5
|
-
* scroll; when `stepIndex` changes, scrolls that step's
|
|
6
|
-
* and highlights
|
|
5
|
+
* scroll; when `stepIndex` changes, scrolls that step's call into view
|
|
6
|
+
* and highlights its full range via CodeView `selectedLines` (same mechanism as
|
|
7
7
|
* `PierreSnippetView`).
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -46,9 +46,9 @@ import { resolveVisibleStepIndex, type VisibleStepRect } from './visibleStep';
|
|
|
46
46
|
import { fileUnavailableNotice, isFileUnavailableError } from './fileAvailability';
|
|
47
47
|
import {
|
|
48
48
|
remapSnippetLineNumbers,
|
|
49
|
-
sliceSnippetWindow,
|
|
50
49
|
type SnippetSlice,
|
|
51
50
|
} from './sliceSnippet';
|
|
51
|
+
import { sliceTrailStep, trailCallSelection } from './trailSnippet';
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* A token the user clicked or hovered inside a step's snippet. `stepIndex` is
|
|
@@ -88,7 +88,7 @@ export interface PierreTrailCodeViewProps {
|
|
|
88
88
|
stepIndex: number | null;
|
|
89
89
|
/** Read a step site; `purl` names the seam site's repo for checkout resolution. */
|
|
90
90
|
readFile: (path: string, purl?: string) => Promise<string>;
|
|
91
|
-
/**
|
|
91
|
+
/** Fallback context when a step has no caller construct span; defaults to 8. */
|
|
92
92
|
contextLines?: number;
|
|
93
93
|
/** Override Pierre's container background. */
|
|
94
94
|
background?: string;
|
|
@@ -100,11 +100,6 @@ export interface PierreTrailCodeViewProps {
|
|
|
100
100
|
* rather than the selected step. Only fires on change, not per scroll event.
|
|
101
101
|
*/
|
|
102
102
|
onVisibleStepChange?: (index: number) => void;
|
|
103
|
-
/**
|
|
104
|
-
* Aliases of components marked `proposed`. A step whose file can't be read
|
|
105
|
-
* but whose endpoint is proposed reads as "planned" rather than "missing".
|
|
106
|
-
*/
|
|
107
|
-
proposedAliases?: ReadonlySet<string>;
|
|
108
103
|
/**
|
|
109
104
|
* Resolve a token to a model construct key (e.g. a component alias) for the
|
|
110
105
|
* step its snippet belongs to. Return `null` when the token doesn't name a
|
|
@@ -138,10 +133,9 @@ function siteKey(step: Pick<SubsystemTrailStep, 'purl' | 'file'>): string {
|
|
|
138
133
|
function unavailableNotice(
|
|
139
134
|
step: SubsystemTrailStep,
|
|
140
135
|
error: string,
|
|
141
|
-
proposed: boolean,
|
|
142
136
|
): string {
|
|
143
137
|
return isFileUnavailableError(error)
|
|
144
|
-
? fileUnavailableNotice(step.file
|
|
138
|
+
? fileUnavailableNotice(step.file)
|
|
145
139
|
: `Couldn't load ${step.file}: ${error}`;
|
|
146
140
|
}
|
|
147
141
|
|
|
@@ -206,7 +200,6 @@ export function PierreTrailCodeView({
|
|
|
206
200
|
background: backgroundProp,
|
|
207
201
|
onOpenFile,
|
|
208
202
|
onVisibleStepChange,
|
|
209
|
-
proposedAliases,
|
|
210
203
|
resolveSymbol,
|
|
211
204
|
onSymbolClick,
|
|
212
205
|
}: PierreTrailCodeViewProps) {
|
|
@@ -292,24 +285,15 @@ export function PierreTrailCodeView({
|
|
|
292
285
|
scheduleVisibleStepReport();
|
|
293
286
|
}, [scheduleVisibleStepReport]);
|
|
294
287
|
|
|
295
|
-
// A step onto a proposed component is planned work; label its missing file
|
|
296
|
-
// accordingly instead of showing a bare "not found".
|
|
297
|
-
const isProposedStep = useCallback(
|
|
298
|
-
(step: SubsystemTrailStep): boolean =>
|
|
299
|
-
proposedAliases != null &&
|
|
300
|
-
(proposedAliases.has(step.from) || proposedAliases.has(step.to)),
|
|
301
|
-
[proposedAliases],
|
|
302
|
-
);
|
|
303
|
-
|
|
304
288
|
const pathsKey = useMemo(() => {
|
|
305
289
|
const keys = [...new Set(trail.steps.map((s) => siteKey(s)))];
|
|
306
290
|
keys.sort();
|
|
307
291
|
return keys.join('\0');
|
|
308
292
|
}, [trail.steps]);
|
|
309
293
|
|
|
310
|
-
// Load each site independently: one unreadable step
|
|
311
|
-
//
|
|
312
|
-
//
|
|
294
|
+
// Load each site independently: one unreadable step must not blank the
|
|
295
|
+
// whole trail. Failures are kept per-path and rendered as inline
|
|
296
|
+
// placeholders below.
|
|
313
297
|
useEffect(() => {
|
|
314
298
|
let cancelled = false;
|
|
315
299
|
setLoad({ status: 'loading' });
|
|
@@ -350,15 +334,13 @@ export function PierreTrailCodeView({
|
|
|
350
334
|
const failure = load.unavailable.get(key);
|
|
351
335
|
if (failure != null) {
|
|
352
336
|
return unavailableSlice(
|
|
353
|
-
unavailableNotice(step, failure
|
|
337
|
+
unavailableNotice(step, failure),
|
|
354
338
|
);
|
|
355
339
|
}
|
|
356
340
|
const contents = load.byPath.get(key) ?? '';
|
|
357
|
-
|
|
358
|
-
const end = step.span?.endLine ?? start;
|
|
359
|
-
return sliceSnippetWindow(contents, start, end, contextLines, start);
|
|
341
|
+
return sliceTrailStep(contents, step, contextLines);
|
|
360
342
|
});
|
|
361
|
-
}, [load, trail.steps, contextLines
|
|
343
|
+
}, [load, trail.steps, contextLines]);
|
|
362
344
|
|
|
363
345
|
const items = useMemo((): CodeViewItem<TrailStepAnnotation>[] => {
|
|
364
346
|
if (load.status !== 'ready' || slices.length === 0) return [];
|
|
@@ -392,23 +374,27 @@ export function PierreTrailCodeView({
|
|
|
392
374
|
name: step.file,
|
|
393
375
|
contents: slice.contents,
|
|
394
376
|
lang: pierreLangForPath(step.file),
|
|
395
|
-
cacheKey: `${trail.id}:${index}:${step.file}:${step.span?.startLine ?? ''}:${slice.sliceStart}-${slice.sliceEnd}${failure != null ? ':unavailable' : ''}`,
|
|
377
|
+
cacheKey: `${trail.id}:${index}:${step.file}:${step.span?.startLine ?? ''}:${step.site?.span?.startLine ?? ''}-${step.site?.span?.endLine ?? ''}:${slice.sliceStart}-${slice.sliceEnd}${failure != null ? ':unavailable' : ''}`,
|
|
396
378
|
},
|
|
397
379
|
};
|
|
398
380
|
});
|
|
399
|
-
}, [load, slices, trail.id, trail.steps
|
|
381
|
+
}, [load, slices, trail.id, trail.steps]);
|
|
400
382
|
|
|
401
383
|
const selectedLines = useMemo((): CodeViewLineSelection | null => {
|
|
402
384
|
if (stepIndex == null || stepIndex < 0 || stepIndex >= slices.length) {
|
|
403
385
|
return null;
|
|
404
386
|
}
|
|
405
|
-
const
|
|
406
|
-
|
|
387
|
+
const step = trail.steps[stepIndex];
|
|
388
|
+
const slice = slices[stepIndex];
|
|
389
|
+
if (step == null || slice == null || load.status !== 'ready') return null;
|
|
390
|
+
if (load.unavailable.has(siteKey(step))) return null;
|
|
391
|
+
const range = trailCallSelection(step, slice);
|
|
392
|
+
if (range == null) return null;
|
|
407
393
|
return {
|
|
408
394
|
id: stepItemId(trail.id, stepIndex),
|
|
409
|
-
range
|
|
395
|
+
range,
|
|
410
396
|
};
|
|
411
|
-
}, [stepIndex, slices, trail.id]);
|
|
397
|
+
}, [stepIndex, slices, trail.id, trail.steps, load]);
|
|
412
398
|
|
|
413
399
|
const sliceStartByItemId = useMemo(() => {
|
|
414
400
|
const map = new Map<string, number>();
|
|
@@ -468,7 +454,9 @@ export function PierreTrailCodeView({
|
|
|
468
454
|
</span>
|
|
469
455
|
) : (
|
|
470
456
|
<>
|
|
471
|
-
|
|
457
|
+
{step.site?.span
|
|
458
|
+
? `${step.site.symbol} · L${step.site.span.startLine}-${step.site.span.endLine}`
|
|
459
|
+
: `L${step.span?.startLine ?? '?'}`}
|
|
472
460
|
{onOpenFile && (
|
|
473
461
|
<OpenFileHeaderButton
|
|
474
462
|
file={step.file}
|
|
@@ -23,12 +23,6 @@ describe('isFileUnavailableError', () => {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
describe('fileUnavailableNotice', () => {
|
|
26
|
-
test('names proposed work as planned, not missing', () => {
|
|
27
|
-
expect(
|
|
28
|
-
fileUnavailableNotice('src/a.ts', true),
|
|
29
|
-
).toBe("Proposed — src/a.ts isn't in the local checkout yet.");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
26
|
test('falls back to a plain not-in-checkout notice', () => {
|
|
33
27
|
expect(fileUnavailableNotice('src/a.ts')).toBe(
|
|
34
28
|
'Not in the local checkout: src/a.ts',
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host file reads fail with a small, stable set of messages when a path can't
|
|
3
|
-
* be served from any local checkout
|
|
4
|
-
*
|
|
5
|
-
* Pierre views can show a proposed / not-yet-available notice instead of a raw
|
|
3
|
+
* be served from any local checkout. These helpers detect that class of failure
|
|
4
|
+
* so the Pierre views can show a not-yet-available notice instead of a raw
|
|
6
5
|
* host error.
|
|
7
6
|
*/
|
|
8
7
|
|
|
@@ -23,15 +22,8 @@ export function isFileUnavailableError(
|
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
|
-
* One-line notice shown in place of a snippet whose file can't be read.
|
|
27
|
-
* the step touches a proposed component the wording names that, so a proposed
|
|
28
|
-
* seam reads as "planned" rather than "missing".
|
|
25
|
+
* One-line notice shown in place of a snippet whose file can't be read.
|
|
29
26
|
*/
|
|
30
|
-
export function fileUnavailableNotice(
|
|
31
|
-
|
|
32
|
-
proposed?: boolean,
|
|
33
|
-
): string {
|
|
34
|
-
return proposed
|
|
35
|
-
? `Proposed — ${path} isn't in the local checkout yet.`
|
|
36
|
-
: `Not in the local checkout: ${path}`;
|
|
27
|
+
export function fileUnavailableNotice(path: string): string {
|
|
28
|
+
return `Not in the local checkout: ${path}`;
|
|
37
29
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import type { SubsystemTrailStep } from '../subsystem/model';
|
|
3
|
+
import { sliceTrailStep, trailCallSelection } from './trailSnippet';
|
|
4
|
+
|
|
5
|
+
const contents = Array.from({ length: 100 }, (_, i) => `line ${i + 1}`).join('\n');
|
|
6
|
+
const step: SubsystemTrailStep = {
|
|
7
|
+
from: 'caller',
|
|
8
|
+
to: 'callee',
|
|
9
|
+
mechanism: 'calls',
|
|
10
|
+
file: 'src/caller.ts',
|
|
11
|
+
purl: 'pkg:github/example/repo#src/caller.ts',
|
|
12
|
+
symbol: 'loadModel',
|
|
13
|
+
site: {
|
|
14
|
+
symbol: 'loadModel',
|
|
15
|
+
span: { startLine: 10, endLine: 70, contentHash: 'caller' },
|
|
16
|
+
},
|
|
17
|
+
span: { startLine: 35, endLine: 38, contentHash: 'call' },
|
|
18
|
+
target: {
|
|
19
|
+
file: 'src/callee.ts',
|
|
20
|
+
purl: 'pkg:github/example/repo#src/callee.ts',
|
|
21
|
+
symbol: 'readFile',
|
|
22
|
+
span: { startLine: 2, endLine: 8, contentHash: 'callee' },
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('trail construct snippets', () => {
|
|
27
|
+
test('shows the full caller span without extra context, not the callee span', () => {
|
|
28
|
+
const slice = sliceTrailStep(contents, step, 8);
|
|
29
|
+
expect(slice.sliceStart).toBe(10);
|
|
30
|
+
expect(slice.sliceEnd).toBe(70);
|
|
31
|
+
expect(slice.contents).toBe(contents.split('\n').slice(9, 70).join('\n'));
|
|
32
|
+
expect(slice.focusOffset).toBe(26);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('highlights every line of a multiline call inside the construct', () => {
|
|
36
|
+
const slice = sliceTrailStep(contents, step, 8);
|
|
37
|
+
expect(trailCallSelection(step, slice)).toEqual({ start: 26, end: 29 });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('preserves single-line call highlighting', () => {
|
|
41
|
+
const single = { ...step, span: { ...step.span!, endLine: 35 } };
|
|
42
|
+
expect(trailCallSelection(single, sliceTrailStep(contents, single, 8)))
|
|
43
|
+
.toEqual({ start: 26, end: 26 });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('keeps a context window for older steps without a site construct', () => {
|
|
47
|
+
const legacy = { ...step, site: undefined };
|
|
48
|
+
const slice = sliceTrailStep(contents, legacy, 8);
|
|
49
|
+
expect(slice.sliceStart).toBe(27);
|
|
50
|
+
expect(slice.sliceEnd).toBe(46);
|
|
51
|
+
expect(slice.focusOffset).toBe(9);
|
|
52
|
+
expect(trailCallSelection(legacy, slice)).toEqual({ start: 9, end: 12 });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('does not highlight the declaration as a call when call evidence is missing', () => {
|
|
56
|
+
const unverified = { ...step, span: undefined };
|
|
57
|
+
const slice = sliceTrailStep(contents, unverified, 8);
|
|
58
|
+
expect(slice.sliceStart).toBe(10);
|
|
59
|
+
expect(slice.sliceEnd).toBe(70);
|
|
60
|
+
expect(slice.focusOffset).toBe(1);
|
|
61
|
+
expect(trailCallSelection(unverified, slice)).toBeNull();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('clips stale call ranges to the displayed construct', () => {
|
|
65
|
+
const stale = { ...step, span: { ...step.span!, startLine: 68, endLine: 75 } };
|
|
66
|
+
expect(trailCallSelection(stale, sliceTrailStep(contents, stale, 8)))
|
|
67
|
+
.toEqual({ start: 59, end: 61 });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('does not highlight unrelated lines when the call falls outside the construct', () => {
|
|
71
|
+
const stale = { ...step, span: { ...step.span!, startLine: 80, endLine: 82 } };
|
|
72
|
+
expect(trailCallSelection(stale, sliceTrailStep(contents, stale, 8))).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SubsystemTrailStep } from '../subsystem/model';
|
|
2
|
+
import { sliceSnippetWindow, type SnippetSlice } from './sliceSnippet';
|
|
3
|
+
|
|
4
|
+
/** Show the caller's whole declaration, keeping focus on its call site. */
|
|
5
|
+
export function sliceTrailStep(
|
|
6
|
+
contents: string,
|
|
7
|
+
step: SubsystemTrailStep,
|
|
8
|
+
contextLines: number,
|
|
9
|
+
): SnippetSlice {
|
|
10
|
+
const site = step.site?.span;
|
|
11
|
+
const start = site?.startLine ?? step.span?.startLine ?? 1;
|
|
12
|
+
const end = site?.endLine ?? step.span?.endLine ?? start;
|
|
13
|
+
return sliceSnippetWindow(
|
|
14
|
+
contents,
|
|
15
|
+
start,
|
|
16
|
+
end,
|
|
17
|
+
site ? 0 : contextLines,
|
|
18
|
+
step.span?.startLine ?? start,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Convert the complete call range to snippet-local lines, never inventing evidence. */
|
|
23
|
+
export function trailCallSelection(
|
|
24
|
+
step: SubsystemTrailStep,
|
|
25
|
+
slice: SnippetSlice,
|
|
26
|
+
): { start: number; end: number } | null {
|
|
27
|
+
if (step.span == null) return null;
|
|
28
|
+
const start = Math.max(step.span.startLine, slice.sliceStart);
|
|
29
|
+
const end = Math.min(step.span.endLine, slice.sliceEnd);
|
|
30
|
+
if (start > end) return null;
|
|
31
|
+
return {
|
|
32
|
+
start: start - slice.sliceStart + 1,
|
|
33
|
+
end: end - slice.sliceStart + 1,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -12,9 +12,8 @@ import type {
|
|
|
12
12
|
import { trailFromSteps } from './fixtures';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* `deprecated`
|
|
16
|
-
*
|
|
17
|
-
* as siblings — proposed = not built yet, deprecated = built once, removed since.
|
|
15
|
+
* `deprecated` marks a claim whose source is gone upstream. The node keeps a
|
|
16
|
+
* transitional marker until a cleanup pass removes it.
|
|
18
17
|
*/
|
|
19
18
|
const meta = {
|
|
20
19
|
title: 'Canvases/Subsystem/Deprecated',
|
|
@@ -154,22 +153,9 @@ const spotlightRows: Array<{ label: string; component: SubsystemComponent }> = [
|
|
|
154
153
|
role: 'entry',
|
|
155
154
|
},
|
|
156
155
|
},
|
|
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
156
|
];
|
|
171
157
|
|
|
172
|
-
/** Spotlights: slate deprecated badge + border vs live
|
|
158
|
+
/** Spotlights: slate deprecated badge + border vs live and entry. */
|
|
173
159
|
export const Spotlights: Story = {
|
|
174
160
|
render: () => (
|
|
175
161
|
<div
|
|
@@ -142,33 +142,6 @@ const CHANNELS: Channel[] = [
|
|
|
142
142
|
declaration: storeDetail,
|
|
143
143
|
},
|
|
144
144
|
},
|
|
145
|
-
{
|
|
146
|
-
label: 'proposed',
|
|
147
|
-
note: 'dashed goldenrod border + right badge; fileless.',
|
|
148
|
-
component: {
|
|
149
|
-
alias: 'prop',
|
|
150
|
-
name: 'OpenCodeV2Lifecycle',
|
|
151
|
-
construct: 'function',
|
|
152
|
-
file: '',
|
|
153
|
-
purl: PURL,
|
|
154
|
-
symbol: 'OpenCodeV2Lifecycle',
|
|
155
|
-
proposed: true,
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
label: 'proposed + role',
|
|
160
|
-
note: 'proposed wins the right badge; role stays on the model.',
|
|
161
|
-
component: {
|
|
162
|
-
alias: 'prop-role',
|
|
163
|
-
name: 'startMaintainFlow',
|
|
164
|
-
construct: 'function',
|
|
165
|
-
file: '',
|
|
166
|
-
purl: PURL,
|
|
167
|
-
symbol: 'startMaintainFlow',
|
|
168
|
-
proposed: true,
|
|
169
|
-
role: 'service',
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
145
|
{
|
|
173
146
|
label: 'framework stereotype',
|
|
174
147
|
note: 'left badge prefers "framework · stereotype"; border stays construct.',
|
|
@@ -230,11 +203,11 @@ function ChannelCell({ channel }: { channel: Channel }) {
|
|
|
230
203
|
|
|
231
204
|
const LEGEND: Array<[string, string]> = [
|
|
232
205
|
['border color', 'construct kind (Pierre palette) — or primary when a file is open (spotlight)'],
|
|
233
|
-
['border style', 'solid normally;
|
|
206
|
+
['border style', 'solid normally; dotted when the node has findings'],
|
|
234
207
|
['border width', '2px; 4px when selected or file-matched'],
|
|
235
208
|
['corners', '8px rounded; square for `external`'],
|
|
236
209
|
['left badge (top edge)', 'construct / framework stereotype / entityKind'],
|
|
237
|
-
['right badge (top edge)', 'role
|
|
210
|
+
['right badge (top edge)', 'role; storage sits beside it'],
|
|
238
211
|
['bottom strip (bottom edge)', 'filename — hover only, or while that file is open'],
|
|
239
212
|
['fill', 'backgroundSecondary; opacity 0.18 when dimmed'],
|
|
240
213
|
['OCCUPIED', 'top-left · top-right · bottom-center · border · fill'],
|
|
@@ -439,8 +412,7 @@ export const IssueOverlayProposal: Story = {
|
|
|
439
412
|
</div>
|
|
440
413
|
<ul style={{ margin: 0, paddingLeft: 18, fontSize: 12, lineHeight: 1.7, color: '#b0b0b0' }}>
|
|
441
414
|
<li>
|
|
442
|
-
<b>Dotted border</b> (keeps the construct color) = the node has findings
|
|
443
|
-
<b> proposed</b>'s dashed, kept distinct so the two don't read alike.
|
|
415
|
+
<b>Dotted border</b> (keeps the construct color) = the node has findings.
|
|
444
416
|
</li>
|
|
445
417
|
<li>
|
|
446
418
|
<b>Corner chip</b> carries severity (<b>red</b> = error / contradicts source,{' '}
|
|
@@ -170,7 +170,6 @@ function TrailsDemo() {
|
|
|
170
170
|
trail,
|
|
171
171
|
stepIndex,
|
|
172
172
|
onOpenFile,
|
|
173
|
-
proposedAliases,
|
|
174
173
|
resolveSymbol,
|
|
175
174
|
onSymbolClick,
|
|
176
175
|
onVisibleStepChange,
|
|
@@ -181,7 +180,6 @@ function TrailsDemo() {
|
|
|
181
180
|
readFile={readStoryFile}
|
|
182
181
|
contextLines={4}
|
|
183
182
|
onOpenFile={onOpenFile}
|
|
184
|
-
proposedAliases={proposedAliases}
|
|
185
183
|
resolveSymbol={resolveSymbol}
|
|
186
184
|
onSymbolClick={onSymbolClick}
|
|
187
185
|
onVisibleStepChange={onVisibleStepChange}
|
|
@@ -223,189 +221,6 @@ export const ThreeTrails: Story = {
|
|
|
223
221
|
render: () => <TrailsDemo />,
|
|
224
222
|
};
|
|
225
223
|
|
|
226
|
-
/**
|
|
227
|
-
* The proposed-seam failure mode: a trail step whose component is
|
|
228
|
-
* `proposed` points at a file that isn't in the checkout yet (no story
|
|
229
|
-
* fixture). The step must render an inline "Proposed — … isn't in the local
|
|
230
|
-
* checkout yet." placeholder with no Open-file affordance, while the live steps
|
|
231
|
-
* around it still render their snippets — one bad step must not blank the flow.
|
|
232
|
-
*/
|
|
233
|
-
const proposedComponents: SubsystemComponent[] = [
|
|
234
|
-
...drawingComponents,
|
|
235
|
-
{
|
|
236
|
-
alias: 'runs',
|
|
237
|
-
name: 'SubsystemModelRunStore',
|
|
238
|
-
construct: 'store',
|
|
239
|
-
file: 'src/runs/subsystemModelRuns.ts',
|
|
240
|
-
purl: 'pkg:github/principal-ai/desktop-app',
|
|
241
|
-
symbol: 'SubsystemModelRunStore',
|
|
242
|
-
purpose: 'proposed store associating maintain runs with a model',
|
|
243
|
-
proposed: true,
|
|
244
|
-
process: 'draw-host',
|
|
245
|
-
},
|
|
246
|
-
];
|
|
247
|
-
|
|
248
|
-
const proposedTrails: SubsystemTrail[] = [
|
|
249
|
-
{
|
|
250
|
-
id: 'tl-associate-run',
|
|
251
|
-
title: 'Associate a finished run with its model (proposed)',
|
|
252
|
-
steps: [
|
|
253
|
-
{
|
|
254
|
-
from: 'host',
|
|
255
|
-
to: 'storage',
|
|
256
|
-
mechanism: 'calls',
|
|
257
|
-
file: 'src/hooks/useDrawingsHost.ts',
|
|
258
|
-
span: { startLine: 45, endLine: 45, contentHash: 'fixture' },
|
|
259
|
-
purl: stepPurl('src/hooks/useDrawingsHost.ts'),
|
|
260
|
-
symbol: 'useDrawingsHost.openDrawing',
|
|
261
|
-
annotation: 'Live step — its snippet still renders.',
|
|
262
|
-
},
|
|
263
|
-
{
|
|
264
|
-
from: 'storage',
|
|
265
|
-
to: 'runs',
|
|
266
|
-
mechanism: 'writes',
|
|
267
|
-
file: 'src/runs/subsystemModelRuns.ts',
|
|
268
|
-
span: { startLine: 1, endLine: 1, contentHash: 'fixture' },
|
|
269
|
-
purl: stepPurl('src/runs/subsystemModelRuns.ts'),
|
|
270
|
-
symbol: 'SubsystemModelRunStore.write',
|
|
271
|
-
annotation: 'Proposed seam — the file is not in the checkout yet.',
|
|
272
|
-
},
|
|
273
|
-
],
|
|
274
|
-
},
|
|
275
|
-
// Neighbours for the wrapped title above: a couple of one-line titles and
|
|
276
|
-
// one that wraps to two, so the header's padding reads against the rows on
|
|
277
|
-
// either side of it rather than in isolation.
|
|
278
|
-
{
|
|
279
|
-
id: 'tl-open-drawing',
|
|
280
|
-
title: 'Open drawing',
|
|
281
|
-
steps: [
|
|
282
|
-
{
|
|
283
|
-
from: 'panel',
|
|
284
|
-
to: 'host',
|
|
285
|
-
mechanism: 'produces',
|
|
286
|
-
file: 'src/panels/DrawingsLeftPanel.tsx',
|
|
287
|
-
span: { startLine: 85, endLine: 85, contentHash: 'fixture' },
|
|
288
|
-
purl: stepPurl('src/panels/DrawingsLeftPanel.tsx'),
|
|
289
|
-
symbol: 'DrawingsLeftPanel.openDrawing',
|
|
290
|
-
annotation: 'Emits the open intent up to the host.',
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
from: 'host',
|
|
294
|
-
to: 'storage',
|
|
295
|
-
mechanism: 'calls',
|
|
296
|
-
file: 'src/hooks/useDrawingsHost.ts',
|
|
297
|
-
span: { startLine: 45, endLine: 45, contentHash: 'fixture' },
|
|
298
|
-
purl: stepPurl('src/hooks/useDrawingsHost.ts'),
|
|
299
|
-
symbol: 'useDrawingsHost.openDrawing',
|
|
300
|
-
},
|
|
301
|
-
],
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
id: 'tl-save-drawing',
|
|
305
|
-
title: 'Save the drawing and refresh the list',
|
|
306
|
-
steps: [
|
|
307
|
-
{
|
|
308
|
-
from: 'tab',
|
|
309
|
-
to: 'fs',
|
|
310
|
-
mechanism: 'calls',
|
|
311
|
-
file: 'src/components/DrawingTabContent.tsx',
|
|
312
|
-
span: { startLine: 112, endLine: 112, contentHash: 'fixture' },
|
|
313
|
-
purl: stepPurl('src/components/DrawingTabContent.tsx'),
|
|
314
|
-
symbol: 'DrawingTabContent.save',
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
from: 'host',
|
|
318
|
-
to: 'panel',
|
|
319
|
-
mechanism: 'produces',
|
|
320
|
-
file: 'src/hooks/useDrawingsHost.ts',
|
|
321
|
-
span: { startLine: 64, endLine: 64, contentHash: 'fixture' },
|
|
322
|
-
purl: stepPurl('src/hooks/useDrawingsHost.ts'),
|
|
323
|
-
symbol: 'useDrawingsHost.onSaved',
|
|
324
|
-
},
|
|
325
|
-
],
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
id: 'tl-delete-drawing',
|
|
329
|
-
title: 'Delete drawing',
|
|
330
|
-
steps: [
|
|
331
|
-
{
|
|
332
|
-
from: 'host',
|
|
333
|
-
to: 'fs',
|
|
334
|
-
mechanism: 'calls',
|
|
335
|
-
file: 'src/hooks/useDrawingsHost.ts',
|
|
336
|
-
span: { startLine: 66, endLine: 66, contentHash: 'fixture' },
|
|
337
|
-
purl: stepPurl('src/hooks/useDrawingsHost.ts'),
|
|
338
|
-
symbol: 'useDrawingsHost.deleteDrawing',
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
from: 'panel',
|
|
342
|
-
to: 'storage',
|
|
343
|
-
mechanism: 'calls',
|
|
344
|
-
file: 'src/panels/DrawingsLeftPanel.tsx',
|
|
345
|
-
span: { startLine: 56, endLine: 56, contentHash: 'fixture' },
|
|
346
|
-
purl: stepPurl('src/panels/DrawingsLeftPanel.tsx'),
|
|
347
|
-
symbol: 'DrawingsLeftPanel.scan',
|
|
348
|
-
},
|
|
349
|
-
],
|
|
350
|
-
},
|
|
351
|
-
];
|
|
352
|
-
|
|
353
|
-
function ProposedMissingStepDemo() {
|
|
354
|
-
const renderTrailViewer = useCallback(
|
|
355
|
-
({
|
|
356
|
-
trail,
|
|
357
|
-
stepIndex,
|
|
358
|
-
onOpenFile,
|
|
359
|
-
proposedAliases,
|
|
360
|
-
resolveSymbol,
|
|
361
|
-
onSymbolClick,
|
|
362
|
-
onVisibleStepChange,
|
|
363
|
-
}: TrailViewerContext) => (
|
|
364
|
-
<PierreTrailCodeView
|
|
365
|
-
trail={trail}
|
|
366
|
-
stepIndex={stepIndex}
|
|
367
|
-
readFile={readStoryFile}
|
|
368
|
-
contextLines={4}
|
|
369
|
-
onOpenFile={onOpenFile}
|
|
370
|
-
proposedAliases={proposedAliases}
|
|
371
|
-
resolveSymbol={resolveSymbol}
|
|
372
|
-
onSymbolClick={onSymbolClick}
|
|
373
|
-
onVisibleStepChange={onVisibleStepChange}
|
|
374
|
-
/>
|
|
375
|
-
),
|
|
376
|
-
[],
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
return (
|
|
380
|
-
<div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
381
|
-
<SubsystemComponentGraph
|
|
382
|
-
components={proposedComponents}
|
|
383
|
-
trails={proposedTrails}
|
|
384
|
-
title="proposed seam with a missing file"
|
|
385
|
-
description="The **Trails** panel lists four flows so the long, wrapped title (*Associate a finished run with its model (proposed)*) reads against its neighbours — two one-line titles and one (*Save the drawing and refresh the list*) that also wraps. Expand **Associate a finished run with its model** and click either step: the proposed step has no file in the checkout, so its snippet shows an inline *Proposed — … isn't in the local checkout yet.* placeholder instead of failing the whole flow."
|
|
386
|
-
renderTrailViewer={renderTrailViewer}
|
|
387
|
-
renderFileViewer={(file) => (
|
|
388
|
-
<div
|
|
389
|
-
style={{
|
|
390
|
-
padding: 12,
|
|
391
|
-
fontFamily: 'monospace',
|
|
392
|
-
fontSize: 12,
|
|
393
|
-
color: '#bbb',
|
|
394
|
-
whiteSpace: 'pre',
|
|
395
|
-
}}
|
|
396
|
-
>
|
|
397
|
-
{`// ${file}\n …`}
|
|
398
|
-
</div>
|
|
399
|
-
)}
|
|
400
|
-
/>
|
|
401
|
-
</div>
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
export const ProposedMissingStep: Story = {
|
|
406
|
-
render: () => <ProposedMissingStepDemo />,
|
|
407
|
-
};
|
|
408
|
-
|
|
409
224
|
// --- Clickable constructs -------------------------------------------------
|
|
410
225
|
//
|
|
411
226
|
// A trail step's line is an edge between two constructs. When the host
|
|
@@ -554,7 +369,6 @@ function ClickableConstructsDemo() {
|
|
|
554
369
|
trail,
|
|
555
370
|
stepIndex,
|
|
556
371
|
onOpenFile,
|
|
557
|
-
proposedAliases,
|
|
558
372
|
resolveSymbol,
|
|
559
373
|
onSymbolClick,
|
|
560
374
|
onVisibleStepChange,
|
|
@@ -565,7 +379,6 @@ function ClickableConstructsDemo() {
|
|
|
565
379
|
readFile={readClickableFile}
|
|
566
380
|
contextLines={4}
|
|
567
381
|
onOpenFile={onOpenFile}
|
|
568
|
-
proposedAliases={proposedAliases}
|
|
569
382
|
resolveSymbol={resolveSymbol}
|
|
570
383
|
onSymbolClick={onSymbolClick}
|
|
571
384
|
onVisibleStepChange={onVisibleStepChange}
|
|
@@ -2711,7 +2711,6 @@ function Inner({ model, onSelectNode, onSelectEdge, onTrailClick, onToggleContai
|
|
|
2711
2711
|
>
|
|
2712
2712
|
{selectedConstruct.component.alias}
|
|
2713
2713
|
{selectedConstruct.role ? ` · ${selectedConstruct.role}` : ''}
|
|
2714
|
-
{selectedConstruct.component.proposed ? ' · proposed' : ''}
|
|
2715
2714
|
</div>
|
|
2716
2715
|
</div>
|
|
2717
2716
|
)}
|
|
@@ -26,6 +26,7 @@ export function FileDrawer({
|
|
|
26
26
|
suppressEscape = false,
|
|
27
27
|
hidden = false,
|
|
28
28
|
progress = null,
|
|
29
|
+
subheader = null,
|
|
29
30
|
children,
|
|
30
31
|
}: {
|
|
31
32
|
/** Drawer chrome title; `null` closes the drawer. */
|
|
@@ -43,6 +44,8 @@ export function FileDrawer({
|
|
|
43
44
|
* content with no position (a single file).
|
|
44
45
|
*/
|
|
45
46
|
progress?: { index: number; total: number } | null;
|
|
47
|
+
/** Optional chrome row below the title (e.g. the trail call stack). */
|
|
48
|
+
subheader?: ReactNode;
|
|
46
49
|
children?: ReactNode;
|
|
47
50
|
}) {
|
|
48
51
|
const { theme } = useTheme();
|
|
@@ -223,6 +226,9 @@ export function FileDrawer({
|
|
|
223
226
|
</button>
|
|
224
227
|
</div>
|
|
225
228
|
</div>
|
|
229
|
+
{subheader != null && open && (
|
|
230
|
+
<div style={{ flexShrink: 0, minHeight: 0 }}>{subheader}</div>
|
|
231
|
+
)}
|
|
226
232
|
<div style={{ flex: 1, minHeight: 0, overflow: "auto" }}>
|
|
227
233
|
{children}
|
|
228
234
|
</div>
|