@principal-ai/subsystems-react 0.44.4 → 0.45.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/README.md +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/pierre/{PierreWalkthroughCodeView.d.ts → PierreTrailCodeView.d.ts} +11 -11
- package/dist/pierre/PierreTrailCodeView.d.ts.map +1 -0
- package/dist/pierre/{PierreWalkthroughCodeView.js → PierreTrailCodeView.js} +36 -36
- package/dist/pierre/PierreTrailCodeView.js.map +1 -0
- package/dist/pierre/index.d.ts +2 -2
- package/dist/pierre/index.d.ts.map +1 -1
- package/dist/pierre/index.js +1 -1
- package/dist/pierre/index.js.map +1 -1
- package/dist/stories/Subsystem/AggregateGraph/aggregateViewFixture.d.ts.map +1 -1
- package/dist/stories/Subsystem/AggregateGraph/aggregateViewFixture.js +1 -1
- package/dist/stories/Subsystem/AggregateGraph/aggregateViewFixture.js.map +1 -1
- package/dist/stories/Subsystem/C4Graph/c4Fixture.js +1 -1
- package/dist/stories/Subsystem/C4Graph/c4Fixture.js.map +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts +9 -9
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js +12 -12
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
- package/dist/subsystem/C4Graph.d.ts +2 -2
- package/dist/subsystem/C4Graph.js +2 -2
- package/dist/subsystem/ConstructsCatalog.d.ts +1 -1
- package/dist/subsystem/ConstructsCatalog.js +1 -1
- package/dist/subsystem/DiagnosticToggle.d.ts +1 -1
- package/dist/subsystem/DiagnosticToggle.js +1 -1
- package/dist/subsystem/FileDrawer.d.ts +2 -2
- package/dist/subsystem/FileDrawer.js +2 -2
- package/dist/subsystem/IssueList.d.ts +6 -6
- package/dist/subsystem/IssueList.d.ts.map +1 -1
- package/dist/subsystem/IssueList.js +9 -9
- package/dist/subsystem/IssueList.js.map +1 -1
- package/dist/subsystem/SubsystemAggregateGraph.d.ts +3 -3
- package/dist/subsystem/SubsystemAggregateGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemAggregateGraph.js +2 -2
- package/dist/subsystem/SubsystemComponentGraph.d.ts +45 -45
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +284 -277
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/SubsystemModelTransition.d.ts +10 -10
- package/dist/subsystem/SubsystemModelTransition.d.ts.map +1 -1
- package/dist/subsystem/SubsystemModelTransition.js +42 -42
- package/dist/subsystem/SubsystemModelTransition.js.map +1 -1
- package/dist/subsystem/TrailsPanel.d.ts +55 -0
- package/dist/subsystem/TrailsPanel.d.ts.map +1 -0
- package/dist/subsystem/{WalkthroughsPanel.js → TrailsPanel.js} +51 -51
- package/dist/subsystem/TrailsPanel.js.map +1 -0
- package/dist/subsystem/declarationRef.d.ts +1 -1
- package/dist/subsystem/declarationRef.d.ts.map +1 -1
- package/dist/subsystem/model.d.ts +40 -40
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +17 -17
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts +1 -1
- package/dist/subsystem/nodes.js +1 -1
- package/dist/subsystem/toC4.d.ts +2 -2
- package/dist/subsystem/toC4.d.ts.map +1 -1
- package/dist/subsystem/toC4.js +2 -2
- package/dist/subsystem/toC4.js.map +1 -1
- package/dist/subsystem/trailBrief.d.ts +18 -0
- package/dist/subsystem/trailBrief.d.ts.map +1 -0
- package/dist/subsystem/{walkthroughBrief.js → trailBrief.js} +8 -8
- package/dist/subsystem/trailBrief.js.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +15 -15
- package/src/pierre/{PierreWalkthroughCodeView.tsx → PierreTrailCodeView.tsx} +56 -56
- package/src/pierre/index.ts +2 -2
- package/src/stories/Pierre/CodeView.stories.tsx +3 -3
- package/src/stories/Subsystem/AggregateGraph/AggregateFrameGraph.stories.tsx +1 -1
- package/src/stories/Subsystem/AggregateGraph/aggregateViewFixture.ts +1 -1
- package/src/stories/Subsystem/C4Graph/c4Fixture.ts +1 -1
- package/src/stories/Subsystem/ComponentDeclarationAudit.stories.tsx +8 -8
- package/src/stories/Subsystem/ComponentGraph/AnimatedProgression.stories.tsx +13 -13
- package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +2 -2
- package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +5 -5
- package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +8 -8
- package/src/stories/Subsystem/ComponentGraph/CustomEntities.stories.tsx +3 -3
- package/src/stories/Subsystem/ComponentGraph/Diagnostics.stories.tsx +6 -6
- package/src/stories/Subsystem/ComponentGraph/EdgeViews.stories.tsx +12 -12
- package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +36 -36
- package/src/stories/Subsystem/ComponentGraph/FrameworkStereotype.stories.tsx +7 -7
- package/src/stories/Subsystem/ComponentGraph/GraphTitle.stories.tsx +4 -4
- package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +8 -8
- package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +4 -4
- package/src/stories/Subsystem/ComponentGraph/ModuleBadges.stories.tsx +3 -3
- package/src/stories/Subsystem/ComponentGraph/Modules.stories.tsx +5 -5
- package/src/stories/Subsystem/ComponentGraph/Packages.stories.tsx +6 -6
- package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +3 -3
- package/src/stories/Subsystem/ComponentGraph/Proposed.stories.tsx +4 -4
- package/src/stories/Subsystem/ComponentGraph/{ProposedWalkthroughs.stories.tsx → ProposedTrails.stories.tsx} +9 -9
- package/src/stories/Subsystem/ComponentGraph/Reorder.stories.tsx +19 -19
- package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +21 -21
- package/src/stories/Subsystem/ComponentGraph/{WalkthroughAutoplay.stories.tsx → TrailAutoplay.stories.tsx} +22 -22
- package/src/stories/Subsystem/ComponentGraph/WorkspacePackageFrames.stories.tsx +3 -3
- package/src/stories/Subsystem/ComponentGraph/fixtures.ts +19 -19
- package/src/subsystem/C4Graph.tsx +2 -2
- package/src/subsystem/ConstructsCatalog.tsx +1 -1
- package/src/subsystem/DiagnosticToggle.tsx +1 -1
- package/src/subsystem/FileDrawer.tsx +2 -2
- package/src/subsystem/IssueList.focus.test.tsx +4 -4
- package/src/subsystem/IssueList.icon.test.tsx +3 -3
- package/src/subsystem/IssueList.tsx +14 -14
- package/src/subsystem/SubsystemAggregateGraph.tsx +3 -3
- package/src/subsystem/SubsystemComponentGraph.tsx +377 -371
- package/src/subsystem/SubsystemModelTransition.tsx +61 -61
- package/src/subsystem/{WalkthroughsPanel.test.tsx → TrailsPanel.test.tsx} +16 -16
- package/src/subsystem/{WalkthroughsPanel.tsx → TrailsPanel.tsx} +81 -81
- package/src/subsystem/declarationRef.ts +1 -1
- package/src/subsystem/formatDeclaration.test.ts +6 -6
- package/src/subsystem/model.test.ts +25 -25
- package/src/subsystem/model.ts +51 -51
- package/src/subsystem/nodes.tsx +1 -1
- package/src/subsystem/toC4.test.ts +1 -1
- package/src/subsystem/toC4.ts +3 -3
- package/src/subsystem/{walkthroughBrief.test.ts → trailBrief.test.ts} +10 -10
- package/src/subsystem/{walkthroughBrief.ts → trailBrief.ts} +8 -8
- package/dist/pierre/PierreWalkthroughCodeView.d.ts.map +0 -1
- package/dist/pierre/PierreWalkthroughCodeView.js.map +0 -1
- package/dist/subsystem/WalkthroughsPanel.d.ts +0 -55
- package/dist/subsystem/WalkthroughsPanel.d.ts.map +0 -1
- package/dist/subsystem/WalkthroughsPanel.js.map +0 -1
- package/dist/subsystem/walkthroughBrief.d.ts +0 -18
- package/dist/subsystem/walkthroughBrief.d.ts.map +0 -1
- package/dist/subsystem/walkthroughBrief.js.map +0 -1
|
@@ -8,7 +8,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
8
8
|
* inside one labeled boundary frame; nodes without one sit outside every
|
|
9
9
|
* boundary. Clicking a component invokes `onSelect`.
|
|
10
10
|
*
|
|
11
|
-
* When the model has components but no topology or
|
|
11
|
+
* When the model has components but no topology or trail edges, the
|
|
12
12
|
* canvas is a constructs catalog (list + signature) instead of a graph.
|
|
13
13
|
*
|
|
14
14
|
* This is a focused fork of the package's `GraphRenderer` pipeline (same ELK
|
|
@@ -20,7 +20,7 @@ import { ReactFlow, ReactFlowProvider, useReactFlow, useViewport, applyNodeChang
|
|
|
20
20
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
21
21
|
import { FileText, X } from 'lucide-react';
|
|
22
22
|
import { IndustryMarkdownSlide } from 'themed-markdown';
|
|
23
|
-
import { buildSubsystemGraph, deriveGraphEdges, isConstructsOnlyModel, moduleGroupNodeId,
|
|
23
|
+
import { buildSubsystemGraph, deriveGraphEdges, isConstructsOnlyModel, moduleGroupNodeId, isTrailMechanism, edgeColor, MECHANISM_DESCRIPTIONS, subsystemGraphLayoutKey, trailStepGraphEdgeId, } from './model';
|
|
24
24
|
import { ConstructsCatalog } from './ConstructsCatalog';
|
|
25
25
|
import { SubsystemAgentsPanel, } from './AgentsPanel';
|
|
26
26
|
import { MaintainLivePanel, } from '../components/maintain-events/MaintainLivePanel';
|
|
@@ -33,7 +33,7 @@ import { GRAPH_CANVAS_CLASS, GRAPH_NAV_PROPS, GraphChrome, GraphLayerStyle } fro
|
|
|
33
33
|
import { ComponentDeclaration } from './ComponentDeclaration';
|
|
34
34
|
import { FileDrawer, FILE_DRAWER_HEIGHT_MS } from './FileDrawer';
|
|
35
35
|
import { buildRepoGroups, repoAvatarUrl } from './paths';
|
|
36
|
-
import {
|
|
36
|
+
import { TrailsPanel, TRAIL_PLAY_PAUSE_MS } from './TrailsPanel';
|
|
37
37
|
import { EDGE_LABEL_WIDTH, EDGE_LABEL_HEIGHT, EDGE_LABEL_FONT_SIZE, EDGE_LABEL_CLOUD_PATH, EDGE_LABEL_CLOUD_EXTRA_TOP, } from '../utils/edgeLabel';
|
|
38
38
|
/** Identifiers a token could match to name this component as a construct. */
|
|
39
39
|
function constructIdentifiers(comp) {
|
|
@@ -112,9 +112,9 @@ function FileOverlayCloseButton({ onClose }) {
|
|
|
112
112
|
transition: 'background 120ms ease, color 120ms ease',
|
|
113
113
|
}, children: _jsx(X, { size: 14 }) }));
|
|
114
114
|
}
|
|
115
|
-
const
|
|
115
|
+
const TrailDrawerContent = memo(function TrailDrawerContent({ render, trail, stepIndex, onOpenFile, proposedAliases, resolveSymbol, onSymbolClick, }) {
|
|
116
116
|
return (_jsx(_Fragment, { children: render({
|
|
117
|
-
|
|
117
|
+
trail,
|
|
118
118
|
stepIndex,
|
|
119
119
|
onOpenFile,
|
|
120
120
|
proposedAliases,
|
|
@@ -122,14 +122,14 @@ const WalkthroughDrawerContent = memo(function WalkthroughDrawerContent({ render
|
|
|
122
122
|
onSymbolClick,
|
|
123
123
|
}) }));
|
|
124
124
|
});
|
|
125
|
-
function Inner({ components,
|
|
125
|
+
function Inner({ components, trails, graphifyRelations, orderByLine, initialTrailId, onReorderTrails, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, showSingletonFrames = true, moduleNesting, edgeView, title, hideSidebar, trailStepMode = 'focus', autoPlayTrails = false, trailAutoPlayIntervalMs = TRAIL_PLAY_PAUSE_MS, zoomOnTrailFocus = true, trailFocusDurationMs = 300, graphTitle, showTrailTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, focusIssueCategory, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileView, renderFileViewer, renderTrailViewer, onFileSelect, componentVerification, onInspectSymbol, boundaryColors, hideDrawer = false, persistKey, liveEvents, agentsPanel }) {
|
|
126
126
|
const { theme } = useTheme();
|
|
127
127
|
const { fitView, fitBounds, screenToFlowPosition } = useReactFlow();
|
|
128
128
|
const viewport = useViewport();
|
|
129
129
|
// Restored once per mount from `localStorage` (see `readViewState`). Each
|
|
130
130
|
// graph is its own tab/mount, so `persistKey` is stable for a mount.
|
|
131
131
|
const persisted = useMemo(() => readViewState(persistKey), [persistKey]);
|
|
132
|
-
const graphEdges = useMemo(() => deriveGraphEdges({
|
|
132
|
+
const graphEdges = useMemo(() => deriveGraphEdges({ trails }), [trails]);
|
|
133
133
|
const [built, setBuilt] = useState({
|
|
134
134
|
nodes: [],
|
|
135
135
|
edges: [],
|
|
@@ -137,9 +137,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
137
137
|
});
|
|
138
138
|
const [layoutReady, setLayoutReady] = useState(false);
|
|
139
139
|
const [selected, setSelected] = useState(null);
|
|
140
|
-
/** Bottom drawer: single file or
|
|
140
|
+
/** Bottom drawer: single file or trail multi-snippet mode. */
|
|
141
141
|
const [drawerTarget, setDrawerTarget] = useState(null);
|
|
142
|
-
/** Full-file layer over an open
|
|
142
|
+
/** Full-file layer over an open trail drawer — trail stays mounted. */
|
|
143
143
|
const [fileOverlay, setFileOverlay] = useState(null);
|
|
144
144
|
// Mirror so step-focus can decide whether to wait for the drawer open
|
|
145
145
|
// transition before fitView (avoids framing against full-height canvas).
|
|
@@ -214,39 +214,46 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
214
214
|
cancelAnimationFrame(raf);
|
|
215
215
|
};
|
|
216
216
|
}, [pathFrameRects, screenToFlowPosition]);
|
|
217
|
-
//
|
|
217
|
+
// Trail focus — selected flow (or step) is full strength; other
|
|
218
218
|
// opened-flow members stay visible but dimmed; everything else is hidden.
|
|
219
219
|
// Restored from the persisted view state when a `persistKey` is set.
|
|
220
|
-
const [
|
|
220
|
+
const [focusedTrailId, setFocusedTrailId] = useState(persisted.focusedTrailId ?? null);
|
|
221
221
|
// `null` = whole flow focused; a number = that single step's edge focused.
|
|
222
222
|
const [focusedStepIndex, setFocusedStepIndex] = useState(persisted.focusedStepIndex ?? null);
|
|
223
|
-
// Hovered
|
|
223
|
+
// Hovered trail in the flows panel: dims every canvas node/edge not
|
|
224
224
|
// involved in the hover preview (or selected ∪ hovered when a step is
|
|
225
225
|
// focused). `stepIndex: null` = whole flow (collapsed title hover);
|
|
226
226
|
// a number = that step. Transient — no drawer. Camera only reframes when
|
|
227
227
|
// a step is already selected.
|
|
228
|
-
const [
|
|
229
|
-
// Sidebar bottom half: which panel is shown when
|
|
230
|
-
|
|
228
|
+
const [hoveredTrailStep, setHoveredTrailStep] = useState(null);
|
|
229
|
+
// Sidebar bottom half: which panel is shown when trails exist.
|
|
230
|
+
// The persisted value is membership-checked rather than trusted: a stale
|
|
231
|
+
// localStorage entry from before the walkthrough->trail rename would match
|
|
232
|
+
// neither panel and render a blank sidebar with no selected tab.
|
|
233
|
+
const [sidebarView, setSidebarView] = useState(() => {
|
|
234
|
+
const stored = persisted.sidebarView;
|
|
235
|
+
const restored = stored === 'files' || stored === 'trails' ? stored : 'trails';
|
|
236
|
+
return trails?.length ? restored : 'files';
|
|
237
|
+
});
|
|
231
238
|
// Diagnostics area tab: issues list vs the Maintain agent pipeline.
|
|
232
239
|
const [diagnosticsTab, setDiagnosticsTab] = useState(() => persisted.diagnosticsTab ?? 'issues');
|
|
233
240
|
// One edge source at a time. When the caller doesn't pick, the sidebar's
|
|
234
|
-
// Files /
|
|
235
|
-
//
|
|
236
|
-
// or a sidebar-less embed) fall back to
|
|
237
|
-
const sidebarTabsVisible = !hideSidebar && (
|
|
241
|
+
// Files / Trails tab picks: Files draws graphify static edges,
|
|
242
|
+
// Trails draws runtime step edges. Without visible tabs (no trails,
|
|
243
|
+
// or a sidebar-less embed) fall back to trail edges.
|
|
244
|
+
const sidebarTabsVisible = !hideSidebar && (trails?.length ?? 0) > 0;
|
|
238
245
|
const resolvedEdgeView = edgeView ??
|
|
239
246
|
(sidebarTabsVisible
|
|
240
|
-
? (sidebarView === '
|
|
241
|
-
: (
|
|
242
|
-
? '
|
|
247
|
+
? (sidebarView === 'trails' ? 'trails' : 'graphify')
|
|
248
|
+
: (trails?.length ?? 0) > 0
|
|
249
|
+
? 'trails'
|
|
243
250
|
: (graphifyRelations?.length ?? 0) > 0
|
|
244
251
|
? 'graphify'
|
|
245
|
-
: '
|
|
246
|
-
//
|
|
252
|
+
: 'trails');
|
|
253
|
+
// Trail flows the user has expanded (via the title row). Closed by
|
|
247
254
|
// default so a graph with several flows doesn't dump every step list at once.
|
|
248
255
|
// Restored from the persisted view state when a `persistKey` is set.
|
|
249
|
-
const [
|
|
256
|
+
const [expandedTrails, setExpandedTrails] = useState(() => new Set(persisted.expandedTrails ?? []));
|
|
250
257
|
// Sidebar description visibility. Hidden by default so the files/flows
|
|
251
258
|
// panel gets the vertical room; the title-row toggle reveals it.
|
|
252
259
|
const [descriptionVisible, setDescriptionVisible] = useState(false);
|
|
@@ -296,20 +303,20 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
296
303
|
document.removeEventListener('mouseup', onUp);
|
|
297
304
|
};
|
|
298
305
|
}, [sidebarDrag, sidebarMaxWidth, fitView, persistKey]);
|
|
299
|
-
// Persist the
|
|
306
|
+
// Persist the trail working set per model. Each write merges into the
|
|
300
307
|
// stored blob, so the three fields never clobber one another.
|
|
301
308
|
useEffect(() => {
|
|
302
309
|
if (!persistKey)
|
|
303
310
|
return;
|
|
304
311
|
writeViewState(persistKey, {
|
|
305
|
-
|
|
312
|
+
expandedTrails: Array.from(expandedTrails),
|
|
306
313
|
});
|
|
307
|
-
}, [persistKey,
|
|
314
|
+
}, [persistKey, expandedTrails]);
|
|
308
315
|
useEffect(() => {
|
|
309
316
|
if (!persistKey)
|
|
310
317
|
return;
|
|
311
|
-
writeViewState(persistKey, {
|
|
312
|
-
}, [persistKey,
|
|
318
|
+
writeViewState(persistKey, { focusedTrailId });
|
|
319
|
+
}, [persistKey, focusedTrailId]);
|
|
313
320
|
useEffect(() => {
|
|
314
321
|
if (!persistKey)
|
|
315
322
|
return;
|
|
@@ -343,60 +350,60 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
343
350
|
: drawerTarget?.kind === 'file'
|
|
344
351
|
? { file: drawerTarget.file, startLine: drawerTarget.startLine }
|
|
345
352
|
: null;
|
|
346
|
-
const
|
|
347
|
-
if (drawerTarget?.kind !== '
|
|
353
|
+
const focusedTrail = useMemo(() => {
|
|
354
|
+
if (drawerTarget?.kind !== 'trail' || !trails)
|
|
348
355
|
return null;
|
|
349
|
-
return
|
|
350
|
-
}, [drawerTarget,
|
|
356
|
+
return trails.find((t) => t.id === drawerTarget.trailId) ?? null;
|
|
357
|
+
}, [drawerTarget, trails]);
|
|
351
358
|
// Autoplay focus: a sidebar-less embed has no expanded flow to "open", so
|
|
352
359
|
// branding every step as a selection would hide all other nodes. Instead the
|
|
353
|
-
// autoplay sets this (flow id + step) to frame the
|
|
360
|
+
// autoplay sets this (flow id + step) to frame the step and dim the rest —
|
|
354
361
|
// same treatment as hover — without hiding anything. Declared before the
|
|
355
362
|
// overlay memos, which read it to title the canvas chip + step bar.
|
|
356
363
|
const [autoPlayFocus, setAutoPlayFocus] = useState(null);
|
|
357
|
-
// Ref mirror of the drawer's
|
|
364
|
+
// Ref mirror of the drawer's trail id so the symbol resolver stays
|
|
358
365
|
// stable across graph re-renders (the drawer is memoized on callback identity).
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
//
|
|
366
|
+
const drawerTrailIdRef = useRef(null);
|
|
367
|
+
drawerTrailIdRef.current = focusedTrail?.id ?? null;
|
|
368
|
+
// Trail shown on the canvas title chip (focus, hover highlight, or
|
|
362
369
|
// autoplay focus).
|
|
363
|
-
const
|
|
364
|
-
if (!
|
|
370
|
+
const overlayTrailTitle = useMemo(() => {
|
|
371
|
+
if (!showTrailTitle || !trails?.length)
|
|
365
372
|
return null;
|
|
366
|
-
const id =
|
|
367
|
-
autoPlayFocus?.
|
|
368
|
-
|
|
373
|
+
const id = focusedTrailId ??
|
|
374
|
+
autoPlayFocus?.trailId ??
|
|
375
|
+
hoveredTrailStep?.trailId;
|
|
369
376
|
if (!id)
|
|
370
377
|
return null;
|
|
371
|
-
return
|
|
378
|
+
return trails.find((t) => t.id === id)?.title ?? null;
|
|
372
379
|
}, [
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
380
|
+
showTrailTitle,
|
|
381
|
+
trails,
|
|
382
|
+
focusedTrailId,
|
|
376
383
|
autoPlayFocus,
|
|
377
|
-
|
|
384
|
+
hoveredTrailStep,
|
|
378
385
|
]);
|
|
379
386
|
// Active step for the bottom-of-title progress + annotation chip.
|
|
380
|
-
const
|
|
381
|
-
if (!
|
|
387
|
+
const overlayTrailStep = useMemo(() => {
|
|
388
|
+
if (!showTrailTitle || !trails?.length)
|
|
382
389
|
return null;
|
|
383
|
-
const tlId =
|
|
384
|
-
autoPlayFocus?.
|
|
385
|
-
|
|
390
|
+
const tlId = focusedTrailId ??
|
|
391
|
+
autoPlayFocus?.trailId ??
|
|
392
|
+
hoveredTrailStep?.trailId ??
|
|
386
393
|
null;
|
|
387
394
|
let stepIndex = null;
|
|
388
|
-
if (
|
|
395
|
+
if (focusedTrailId != null) {
|
|
389
396
|
stepIndex = focusedStepIndex;
|
|
390
397
|
}
|
|
391
398
|
else if (autoPlayFocus != null) {
|
|
392
399
|
stepIndex = autoPlayFocus.stepIndex;
|
|
393
400
|
}
|
|
394
|
-
else if (
|
|
395
|
-
stepIndex =
|
|
401
|
+
else if (hoveredTrailStep != null) {
|
|
402
|
+
stepIndex = hoveredTrailStep.stepIndex;
|
|
396
403
|
}
|
|
397
404
|
if (tlId == null || stepIndex == null)
|
|
398
405
|
return null;
|
|
399
|
-
const tl =
|
|
406
|
+
const tl = trails.find((t) => t.id === tlId);
|
|
400
407
|
const step = tl?.steps[stepIndex];
|
|
401
408
|
if (!step || !tl)
|
|
402
409
|
return null;
|
|
@@ -406,19 +413,19 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
406
413
|
annotation: step.annotation,
|
|
407
414
|
};
|
|
408
415
|
}, [
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
416
|
+
showTrailTitle,
|
|
417
|
+
trails,
|
|
418
|
+
focusedTrailId,
|
|
412
419
|
focusedStepIndex,
|
|
413
420
|
autoPlayFocus,
|
|
414
|
-
|
|
421
|
+
hoveredTrailStep,
|
|
415
422
|
]);
|
|
416
423
|
const drawerTitle = useMemo(() => {
|
|
417
424
|
if (!drawerTarget)
|
|
418
425
|
return null;
|
|
419
426
|
if (drawerTarget.kind === 'file')
|
|
420
427
|
return drawerTarget.file;
|
|
421
|
-
const tl =
|
|
428
|
+
const tl = focusedTrail;
|
|
422
429
|
if (!tl)
|
|
423
430
|
return null;
|
|
424
431
|
if (drawerTarget.stepIndex == null)
|
|
@@ -428,7 +435,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
428
435
|
return tl.title;
|
|
429
436
|
const site = `${step.file.split('/').pop() ?? step.file}:${step.line}`;
|
|
430
437
|
return `${tl.title} · ${site}`;
|
|
431
|
-
}, [drawerTarget,
|
|
438
|
+
}, [drawerTarget, focusedTrail]);
|
|
432
439
|
// Refresh selected component when the components list updates (e.g. verify
|
|
433
440
|
// writes back declarationRef).
|
|
434
441
|
useEffect(() => {
|
|
@@ -441,12 +448,12 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
441
448
|
// Pass 1: build with estimated widths so React Flow can measure the DOM.
|
|
442
449
|
// The pane stays hidden until Pass 2 completes. Key off layout-affecting
|
|
443
450
|
// fields only — declarationRef updates after verify must not re-run ELK.
|
|
444
|
-
const layoutKey = useMemo(() => subsystemGraphLayoutKey({ components,
|
|
451
|
+
const layoutKey = useMemo(() => subsystemGraphLayoutKey({ components, trails, graphifyRelations }), [components, trails, graphifyRelations]);
|
|
445
452
|
const componentsRef = useRef(components);
|
|
446
|
-
const
|
|
453
|
+
const trailsRef = useRef(trails);
|
|
447
454
|
const graphifyRelationsRef = useRef(graphifyRelations);
|
|
448
455
|
componentsRef.current = components;
|
|
449
|
-
|
|
456
|
+
trailsRef.current = trails;
|
|
450
457
|
graphifyRelationsRef.current = graphifyRelations;
|
|
451
458
|
// Track measured dimensions from React Flow's dimension changes.
|
|
452
459
|
// These arrive as { type: 'dimensions', id, dimensions } in onNodesChange.
|
|
@@ -464,7 +471,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
464
471
|
pass2GenRef.current += 1;
|
|
465
472
|
const doc = {
|
|
466
473
|
components: componentsRef.current,
|
|
467
|
-
|
|
474
|
+
trails: trailsRef.current,
|
|
468
475
|
};
|
|
469
476
|
void buildSubsystemGraph(doc, {
|
|
470
477
|
maxNodeWidth,
|
|
@@ -523,7 +530,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
523
530
|
const measuredWidths = new Map(leafNodes.map((n) => [n.id, dims.get(n.id).width]));
|
|
524
531
|
const measuredHeights = new Map(leafNodes.map((n) => [n.id, dims.get(n.id).height]));
|
|
525
532
|
const gen = ++pass2GenRef.current;
|
|
526
|
-
void buildSubsystemGraph({ components,
|
|
533
|
+
void buildSubsystemGraph({ components, trails }, { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights, showSingletonFrames, moduleNesting, graphifyRelations, orderByLine })
|
|
527
534
|
.then(({ nodes, edges: e, absoluteRects }) => {
|
|
528
535
|
if (gen !== pass2GenRef.current)
|
|
529
536
|
return;
|
|
@@ -537,7 +544,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
537
544
|
// Reveal Pass 1 layout rather than leaving the cover up forever.
|
|
538
545
|
setLayoutReady(true);
|
|
539
546
|
});
|
|
540
|
-
}, [built.nodes, components,
|
|
547
|
+
}, [built.nodes, components, trails, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels, moduleNesting]);
|
|
541
548
|
// After Pass 1 commits, try Pass 2 immediately with retained measurements.
|
|
542
549
|
// Same-id live updates often get no new React Flow `dimensions` events, so
|
|
543
550
|
// waiting only on onNodesChange leaves the cover stuck.
|
|
@@ -578,8 +585,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
578
585
|
}
|
|
579
586
|
const src = edgeById.get(edgeId);
|
|
580
587
|
setSelectedEdgeId(edgeId);
|
|
581
|
-
// A direct edge selection on the canvas supersedes any
|
|
582
|
-
|
|
588
|
+
// A direct edge selection on the canvas supersedes any trail focus.
|
|
589
|
+
setFocusedTrailId(null);
|
|
583
590
|
setFocusedStepIndex(null);
|
|
584
591
|
if (src)
|
|
585
592
|
onEdgeSelect?.(src);
|
|
@@ -597,7 +604,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
597
604
|
}
|
|
598
605
|
setSelected(comp);
|
|
599
606
|
setSelectedEdgeId(null);
|
|
600
|
-
|
|
607
|
+
setFocusedTrailId(null);
|
|
601
608
|
setFocusedStepIndex(null);
|
|
602
609
|
onSelect?.(alias);
|
|
603
610
|
}
|
|
@@ -639,9 +646,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
639
646
|
return null;
|
|
640
647
|
return moduleGroupNodeId(key);
|
|
641
648
|
}, [components]);
|
|
642
|
-
// A `step` issue target names ONE step of a
|
|
649
|
+
// A `step` issue target names ONE step of a trail — the trail id in
|
|
643
650
|
// `id`, the 0-based step position in `stepIndex`. Validated against the loaded
|
|
644
|
-
//
|
|
651
|
+
// trails so a finding left over from an edited flow resolves to null
|
|
645
652
|
// rather than framing whatever now happens to sit at that index.
|
|
646
653
|
const issueStep = useCallback((issue) => {
|
|
647
654
|
const target = issue.target;
|
|
@@ -649,11 +656,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
649
656
|
return null;
|
|
650
657
|
if (target.id == null || target.stepIndex == null)
|
|
651
658
|
return null;
|
|
652
|
-
const
|
|
653
|
-
if (!
|
|
659
|
+
const trail = trails?.find((t) => t.id === target.id);
|
|
660
|
+
if (!trail?.steps[target.stepIndex])
|
|
654
661
|
return null;
|
|
655
|
-
return {
|
|
656
|
-
}, [
|
|
662
|
+
return { trail, stepIndex: target.stepIndex };
|
|
663
|
+
}, [trails]);
|
|
657
664
|
// Per-node diagnostics badge: fold each component-targeted finding that maps
|
|
658
665
|
// to a verification rung into one badge per node — worst severity wins, the
|
|
659
666
|
// chip shows the earliest failing rung, and the count tallies the findings.
|
|
@@ -731,7 +738,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
731
738
|
// target:
|
|
732
739
|
// component → that node
|
|
733
740
|
// module → every component in that module
|
|
734
|
-
//
|
|
741
|
+
// trail → the flow's nodes + its step edges
|
|
735
742
|
// repo → every component of that repo
|
|
736
743
|
// graph → whole-graph finding; implicates nothing specific
|
|
737
744
|
// Returns null when nothing is expanded, no expanded layer has findings, or
|
|
@@ -766,13 +773,13 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
766
773
|
}
|
|
767
774
|
}
|
|
768
775
|
}
|
|
769
|
-
else if (t.kind === '
|
|
770
|
-
const wt = (
|
|
776
|
+
else if (t.kind === 'trail') {
|
|
777
|
+
const wt = (trails ?? []).find((w) => w.id === t.id || w.title === t.label);
|
|
771
778
|
if (wt) {
|
|
772
779
|
for (const s of wt.steps) {
|
|
773
780
|
nodeIds.add(s.from);
|
|
774
781
|
nodeIds.add(s.to);
|
|
775
|
-
edgeIds.add(
|
|
782
|
+
edgeIds.add(trailStepGraphEdgeId(s));
|
|
776
783
|
}
|
|
777
784
|
}
|
|
778
785
|
}
|
|
@@ -793,7 +800,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
793
800
|
edgeIds.add(e.id);
|
|
794
801
|
}
|
|
795
802
|
return { nodeIds, edgeIds };
|
|
796
|
-
}, [issues, expandedIssueCategories, components,
|
|
803
|
+
}, [issues, expandedIssueCategories, components, trails, resolveAlias]);
|
|
797
804
|
const issueFocusNodeIds = issueFocus?.nodeIds ?? null;
|
|
798
805
|
const issueFocusEdgeIds = issueFocus?.edgeIds ?? null;
|
|
799
806
|
// When an edge is selected, dim every other edge + its label to focus it.
|
|
@@ -804,61 +811,61 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
804
811
|
selectedEdgeIdRef.current = selectedEdgeId;
|
|
805
812
|
// Ref mirrors of the focused step, so `unfocusIssueTarget` (a useCallback
|
|
806
813
|
// over early deps) can tell a step focus it still owns from one the user has
|
|
807
|
-
// since moved elsewhere in the
|
|
814
|
+
// since moved elsewhere in the trail panel.
|
|
808
815
|
const focusedStepRef = useRef(null);
|
|
809
816
|
focusedStepRef.current =
|
|
810
|
-
|
|
811
|
-
? {
|
|
817
|
+
focusedTrailId != null && focusedStepIndex != null
|
|
818
|
+
? { trailId: focusedTrailId, stepIndex: focusedStepIndex }
|
|
812
819
|
: null;
|
|
813
|
-
// Edge ids in
|
|
814
|
-
// step's edge). Used to frame the camera. `null` = no
|
|
820
|
+
// Edge ids in trail focus (an active flow's edge set, or a single
|
|
821
|
+
// step's edge). Used to frame the camera. `null` = no trail focus.
|
|
815
822
|
const focusEdgeIds = useMemo(() => {
|
|
816
823
|
if (autoPlayFocus) {
|
|
817
|
-
const tl =
|
|
824
|
+
const tl = trails?.find((t) => t.id === autoPlayFocus.trailId);
|
|
818
825
|
const step = tl?.steps[autoPlayFocus.stepIndex];
|
|
819
|
-
return step ? new Set([
|
|
826
|
+
return step ? new Set([trailStepGraphEdgeId(step)]) : null;
|
|
820
827
|
}
|
|
821
|
-
if (
|
|
828
|
+
if (focusedTrailId == null || !trails)
|
|
822
829
|
return null;
|
|
823
|
-
const tl =
|
|
830
|
+
const tl = trails.find((t) => t.id === focusedTrailId);
|
|
824
831
|
if (!tl)
|
|
825
832
|
return null;
|
|
826
833
|
if (focusedStepIndex != null) {
|
|
827
834
|
const step = tl.steps[focusedStepIndex];
|
|
828
|
-
return step ? new Set([
|
|
835
|
+
return step ? new Set([trailStepGraphEdgeId(step)]) : null;
|
|
829
836
|
}
|
|
830
|
-
return new Set(tl.steps.map((s) =>
|
|
831
|
-
}, [
|
|
837
|
+
return new Set(tl.steps.map((s) => trailStepGraphEdgeId(s)));
|
|
838
|
+
}, [trails, focusedTrailId, focusedStepIndex, autoPlayFocus]);
|
|
832
839
|
// 1-based step numbers per edge of the active flow (focused or
|
|
833
840
|
// hover/autoplay-highlighted). An edge can appear in more than one step.
|
|
834
841
|
const selectedFlowStepNos = useMemo(() => {
|
|
835
|
-
const activeId =
|
|
836
|
-
if (activeId == null || !
|
|
842
|
+
const activeId = focusedTrailId ?? hoveredTrailStep?.trailId;
|
|
843
|
+
if (activeId == null || !trails)
|
|
837
844
|
return null;
|
|
838
|
-
const tl =
|
|
845
|
+
const tl = trails.find((t) => t.id === activeId);
|
|
839
846
|
if (!tl)
|
|
840
847
|
return null;
|
|
841
848
|
const map = new Map();
|
|
842
849
|
tl.steps.forEach((s, i) => {
|
|
843
|
-
const list = map.get(
|
|
850
|
+
const list = map.get(trailStepGraphEdgeId(s)) ?? [];
|
|
844
851
|
list.push(i + 1);
|
|
845
|
-
map.set(
|
|
852
|
+
map.set(trailStepGraphEdgeId(s), list);
|
|
846
853
|
});
|
|
847
854
|
return map;
|
|
848
|
-
}, [
|
|
849
|
-
// Union of every expanded (opened)
|
|
855
|
+
}, [trails, focusedTrailId, hoveredTrailStep]);
|
|
856
|
+
// Union of every expanded (opened) trail's edges — the visible set.
|
|
850
857
|
const openedEdgeIds = useMemo(() => {
|
|
851
|
-
if (!
|
|
858
|
+
if (!trails || expandedTrails.size === 0)
|
|
852
859
|
return null;
|
|
853
860
|
const ids = new Set();
|
|
854
|
-
for (const tl of
|
|
855
|
-
if (!
|
|
861
|
+
for (const tl of trails) {
|
|
862
|
+
if (!expandedTrails.has(tl.id))
|
|
856
863
|
continue;
|
|
857
864
|
for (const s of tl.steps)
|
|
858
|
-
ids.add(
|
|
865
|
+
ids.add(trailStepGraphEdgeId(s));
|
|
859
866
|
}
|
|
860
867
|
return ids.size > 0 ? ids : null;
|
|
861
|
-
}, [
|
|
868
|
+
}, [trails, expandedTrails]);
|
|
862
869
|
const endpointsOf = (edgeIds) => {
|
|
863
870
|
if (!edgeIds)
|
|
864
871
|
return null;
|
|
@@ -873,22 +880,22 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
873
880
|
};
|
|
874
881
|
// Edge/nodes involved in the hovered step or whole flow.
|
|
875
882
|
const hoverEdgeIds = useMemo(() => {
|
|
876
|
-
if (!
|
|
883
|
+
if (!hoveredTrailStep || !trails)
|
|
877
884
|
return null;
|
|
878
|
-
const tl =
|
|
885
|
+
const tl = trails.find((t) => t.id === hoveredTrailStep.trailId);
|
|
879
886
|
if (!tl)
|
|
880
887
|
return null;
|
|
881
|
-
if (
|
|
882
|
-
return new Set(tl.steps.map((s) =>
|
|
888
|
+
if (hoveredTrailStep.stepIndex == null) {
|
|
889
|
+
return new Set(tl.steps.map((s) => trailStepGraphEdgeId(s)));
|
|
883
890
|
}
|
|
884
|
-
const step = tl.steps[
|
|
885
|
-
return step ? new Set([
|
|
886
|
-
}, [
|
|
891
|
+
const step = tl.steps[hoveredTrailStep.stepIndex];
|
|
892
|
+
return step ? new Set([trailStepGraphEdgeId(step)]) : null;
|
|
893
|
+
}, [trails, hoveredTrailStep]);
|
|
887
894
|
// While hovering with a *step* already selected, brighten the union of
|
|
888
895
|
// selected + hovered participants. Whole-flow focus (or no focus) keeps
|
|
889
896
|
// the old hover-replace preview so a step hover still dims the rest.
|
|
890
897
|
const previewEdgeIds = useMemo(() => {
|
|
891
|
-
// Autoplay drives focus without a hover, so its
|
|
898
|
+
// Autoplay drives focus without a hover, so its step is the preview set.
|
|
892
899
|
if (autoPlayFocus)
|
|
893
900
|
return focusEdgeIds;
|
|
894
901
|
if (!hoverEdgeIds)
|
|
@@ -1028,14 +1035,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1028
1035
|
};
|
|
1029
1036
|
// Edges outside the selected view are hidden entirely (labels included).
|
|
1030
1037
|
// graphify-native edges belong to the graphify view (they are static
|
|
1031
|
-
// topology, not runtime
|
|
1038
|
+
// topology, not runtime steps) regardless of their verb.
|
|
1032
1039
|
const edgeInView = (e) => {
|
|
1033
1040
|
const d = e.data;
|
|
1034
1041
|
const mechanism = d?.mechanism ?? 'uses';
|
|
1035
1042
|
if (resolvedEdgeView === 'graphify') {
|
|
1036
1043
|
return d?.provenance === 'graphify';
|
|
1037
1044
|
}
|
|
1038
|
-
return
|
|
1045
|
+
return isTrailMechanism(mechanism);
|
|
1039
1046
|
};
|
|
1040
1047
|
if (openedEdgeIds || focusEdgeIds || previewEdgeIds || issueFocusEdgeIds) {
|
|
1041
1048
|
return baseEdges.map((e) => {
|
|
@@ -1090,7 +1097,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1090
1097
|
const onNodeClick = useCallback((_e, node) => {
|
|
1091
1098
|
const comp = node.data?.component;
|
|
1092
1099
|
setSelectedEdgeId(null);
|
|
1093
|
-
|
|
1100
|
+
setFocusedTrailId(null);
|
|
1094
1101
|
setFocusedStepIndex(null);
|
|
1095
1102
|
if (node.type === 'subsystem-component' && comp) {
|
|
1096
1103
|
// Clicking the already-selected node unselects it (toggle off).
|
|
@@ -1112,15 +1119,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1112
1119
|
const onPaneClick = useCallback(() => {
|
|
1113
1120
|
setSelected(null);
|
|
1114
1121
|
setSelectedEdgeId(null);
|
|
1115
|
-
|
|
1122
|
+
setFocusedTrailId(null);
|
|
1116
1123
|
setFocusedStepIndex(null);
|
|
1117
1124
|
issueModuleFocusRef.current = null;
|
|
1118
1125
|
}, []);
|
|
1119
1126
|
// Sidebar file trees — one per repo on multi-repo graphs, each under its
|
|
1120
1127
|
// own owner-avatar header. Clicking a header collapses that repo's tree.
|
|
1121
1128
|
const repoGroups = useMemo(() => buildRepoGroups(components), [components]);
|
|
1122
|
-
const
|
|
1123
|
-
// Aliases of proposed components — the flows panel tints
|
|
1129
|
+
const hasTrails = useMemo(() => (trails?.length ?? 0) > 0, [trails]);
|
|
1130
|
+
// Aliases of proposed components — the flows panel tints trail titles
|
|
1124
1131
|
// (and steps) that touch one.
|
|
1125
1132
|
const proposedAliases = useMemo(() => new Set(components.filter((c) => c.proposed).map((c) => c.alias)), [components]);
|
|
1126
1133
|
// Diagnostics view in the sidebar. Uncontrolled unless the host pins
|
|
@@ -1181,7 +1188,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1181
1188
|
const [focusedBoundaryKey, setFocusedBoundaryKey] = useState(null);
|
|
1182
1189
|
const onNodeDoubleClick = useCallback((_e, node) => {
|
|
1183
1190
|
// Boundary frame → frame its members on the canvas; double-click again to
|
|
1184
|
-
// zoom back out. User-initiated, so it ignores the
|
|
1191
|
+
// zoom back out. User-initiated, so it ignores the trail zoom gate.
|
|
1185
1192
|
if (node.type === 'subsystem-group') {
|
|
1186
1193
|
const key = node.data?.region?.key;
|
|
1187
1194
|
if (!key)
|
|
@@ -1216,7 +1223,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1216
1223
|
const startLine = comp.declarationRef?.startLine;
|
|
1217
1224
|
onOpenDeclarationFile(comp.file, startLine != null ? { startLine } : undefined);
|
|
1218
1225
|
}, [onOpenDeclarationFile, fitView, focusedBoundaryKey]);
|
|
1219
|
-
const
|
|
1226
|
+
const onOpenFileFromTrail = useCallback((file, opts) => {
|
|
1220
1227
|
setFileOverlay({ file, startLine: opts?.startLine });
|
|
1221
1228
|
onFileSelect?.(file);
|
|
1222
1229
|
}, [onFileSelect]);
|
|
@@ -1230,14 +1237,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1230
1237
|
const drawerFillHeight = drawerTarget?.kind === 'file' && drawerTarget.startLine == null;
|
|
1231
1238
|
const fileOverlayOpen = fileOverlay != null;
|
|
1232
1239
|
const collapseCanvas = drawerFillHeight || fileOverlayOpen;
|
|
1233
|
-
// Drop the full-file overlay when the underlying
|
|
1240
|
+
// Drop the full-file overlay when the underlying trail drawer changes
|
|
1234
1241
|
// or closes — open-file keeps the same drawerTarget so the snippets stay mounted.
|
|
1235
|
-
const
|
|
1236
|
-
? `${drawerTarget.
|
|
1242
|
+
const trailDrawerKey = drawerTarget?.kind === 'trail'
|
|
1243
|
+
? `${drawerTarget.trailId}:${drawerTarget.stepIndex}`
|
|
1237
1244
|
: null;
|
|
1238
1245
|
useEffect(() => {
|
|
1239
1246
|
setFileOverlay(null);
|
|
1240
|
-
}, [
|
|
1247
|
+
}, [trailDrawerKey]);
|
|
1241
1248
|
useEffect(() => {
|
|
1242
1249
|
if (!fileOverlayOpen)
|
|
1243
1250
|
return;
|
|
@@ -1248,15 +1255,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1248
1255
|
window.addEventListener('keydown', onKey);
|
|
1249
1256
|
return () => window.removeEventListener('keydown', onKey);
|
|
1250
1257
|
}, [fileOverlayOpen, closeFileOverlay]);
|
|
1251
|
-
// Camera helper shared by the
|
|
1258
|
+
// Camera helper shared by the trail interactions. Fits the union of
|
|
1252
1259
|
// the focused edges' endpoint rects AND their routed waypoints — the edge
|
|
1253
1260
|
// carries its full polyline (`elkPathPoints`, absolute flow coords), so the
|
|
1254
|
-
// frame is the line the
|
|
1261
|
+
// frame is the line the step actually traces. That keeps a step whose route
|
|
1255
1262
|
// bulges out around intervening nodes from being clipped at the viewport
|
|
1256
1263
|
// edge, and it is the edge's own geometry doing the guiding, not fudge
|
|
1257
1264
|
// padding.
|
|
1258
1265
|
const fitFocusBounds = useCallback((ids) => {
|
|
1259
|
-
if (!
|
|
1266
|
+
if (!zoomOnTrailFocus)
|
|
1260
1267
|
return;
|
|
1261
1268
|
const nodeIds = new Set();
|
|
1262
1269
|
const points = [];
|
|
@@ -1306,14 +1313,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1306
1313
|
};
|
|
1307
1314
|
fitBounds(bounds, {
|
|
1308
1315
|
padding: 0.15,
|
|
1309
|
-
duration:
|
|
1316
|
+
duration: trailFocusDurationMs,
|
|
1310
1317
|
});
|
|
1311
1318
|
}, [
|
|
1312
1319
|
baseEdges,
|
|
1313
1320
|
built.absoluteRects,
|
|
1314
1321
|
fitBounds,
|
|
1315
|
-
|
|
1316
|
-
|
|
1322
|
+
zoomOnTrailFocus,
|
|
1323
|
+
trailFocusDurationMs,
|
|
1317
1324
|
]);
|
|
1318
1325
|
// A node's laid-out rect in absolute flow coords. `absoluteRects` is the
|
|
1319
1326
|
// authority; the fallback covers an unparented group node, whose `position` is
|
|
@@ -1359,12 +1366,12 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1359
1366
|
y: minY,
|
|
1360
1367
|
width: Math.max(1, maxX - minX),
|
|
1361
1368
|
height: Math.max(1, maxY - minY),
|
|
1362
|
-
}, { padding, duration:
|
|
1363
|
-
}, [rectForNode, fitBounds,
|
|
1364
|
-
// Zoom back out to the full diagram after the last expanded
|
|
1369
|
+
}, { padding, duration: trailFocusDurationMs });
|
|
1370
|
+
}, [rectForNode, fitBounds, trailFocusDurationMs]);
|
|
1371
|
+
// Zoom back out to the full diagram after the last expanded trail
|
|
1365
1372
|
// closes (visibility restores non-flow nodes that were hidden).
|
|
1366
1373
|
const fitOverview = useCallback(() => {
|
|
1367
|
-
if (!
|
|
1374
|
+
if (!zoomOnTrailFocus)
|
|
1368
1375
|
return;
|
|
1369
1376
|
fitView({
|
|
1370
1377
|
padding: 0.1,
|
|
@@ -1373,109 +1380,109 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1373
1380
|
maxZoom: 2,
|
|
1374
1381
|
duration: 300,
|
|
1375
1382
|
});
|
|
1376
|
-
}, [fitView,
|
|
1383
|
+
}, [fitView, zoomOnTrailFocus]);
|
|
1377
1384
|
// Focus an entire flow: hide everything but the flow's nodes and edges, and
|
|
1378
1385
|
// frame the flow on the canvas. Selection state is cleared — the graph now
|
|
1379
1386
|
// reads as the narrative. No drawer: the code view only opens on a step
|
|
1380
|
-
// click. A stale
|
|
1387
|
+
// click. A stale trail drawer (from a previously focused flow's step)
|
|
1381
1388
|
// closes; an explicitly opened file drawer stays.
|
|
1382
1389
|
// In `dim` mode: clear step highlight and leave the full graph visible
|
|
1383
1390
|
// (no zoom / hide) — matching "step away from a hovered step".
|
|
1384
|
-
const
|
|
1391
|
+
const focusTrailEdges = useCallback((tl) => {
|
|
1385
1392
|
setSelected(null);
|
|
1386
1393
|
setSelectedEdgeId(null);
|
|
1387
|
-
|
|
1388
|
-
if (
|
|
1394
|
+
setHoveredTrailStep(null);
|
|
1395
|
+
if (trailStepMode === 'dim') {
|
|
1389
1396
|
setFocusedStepIndex(null);
|
|
1390
|
-
|
|
1391
|
-
setDrawerTarget((prev) => (prev?.kind === '
|
|
1397
|
+
setFocusedTrailId(null);
|
|
1398
|
+
setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
|
|
1392
1399
|
return;
|
|
1393
1400
|
}
|
|
1394
1401
|
setFocusedStepIndex(null);
|
|
1395
|
-
|
|
1396
|
-
fitFocusBounds(new Set(tl.steps.map((s) =>
|
|
1397
|
-
setDrawerTarget((prev) => (prev?.kind === '
|
|
1398
|
-
}, [fitFocusBounds,
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1402
|
+
setFocusedTrailId(tl.id);
|
|
1403
|
+
fitFocusBounds(new Set(tl.steps.map((s) => trailStepGraphEdgeId(s))));
|
|
1404
|
+
setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
|
|
1405
|
+
}, [fitFocusBounds, trailStepMode]);
|
|
1406
|
+
const clearTrailFocus = useCallback(() => {
|
|
1407
|
+
setFocusedTrailId(null);
|
|
1401
1408
|
setFocusedStepIndex(null);
|
|
1402
|
-
|
|
1403
|
-
setDrawerTarget((prev) => (prev?.kind === '
|
|
1409
|
+
setHoveredTrailStep(null);
|
|
1410
|
+
setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
|
|
1404
1411
|
}, []);
|
|
1405
|
-
// Host deep-link: opening the graph from a
|
|
1412
|
+
// Host deep-link: opening the graph from a trail row selects that
|
|
1406
1413
|
// flow — expand its steps, focus its edges, and switch the sidebar to
|
|
1407
|
-
//
|
|
1414
|
+
// Trails. Guarded by a ref so a later in-tab selection isn't yanked
|
|
1408
1415
|
// back; a remount (or a new id) re-applies it.
|
|
1409
|
-
const
|
|
1416
|
+
const appliedInitialTrailRef = useRef(null);
|
|
1410
1417
|
useEffect(() => {
|
|
1411
|
-
if (!
|
|
1418
|
+
if (!initialTrailId)
|
|
1412
1419
|
return;
|
|
1413
|
-
if (
|
|
1420
|
+
if (appliedInitialTrailRef.current === initialTrailId)
|
|
1414
1421
|
return;
|
|
1415
|
-
if (!layoutReady || !
|
|
1422
|
+
if (!layoutReady || !trails?.length)
|
|
1416
1423
|
return;
|
|
1417
|
-
const tl =
|
|
1424
|
+
const tl = trails.find((t) => t.id === initialTrailId);
|
|
1418
1425
|
if (!tl)
|
|
1419
1426
|
return;
|
|
1420
|
-
|
|
1421
|
-
setSidebarView('
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
}, [
|
|
1427
|
+
appliedInitialTrailRef.current = initialTrailId;
|
|
1428
|
+
setSidebarView('trails');
|
|
1429
|
+
setExpandedTrails((prev) => new Set(prev).add(tl.id));
|
|
1430
|
+
focusTrailEdges(tl);
|
|
1431
|
+
}, [initialTrailId, layoutReady, trails, focusTrailEdges]);
|
|
1425
1432
|
// Switching sidebar panels also switches the edge vocabulary. Leaving the
|
|
1426
|
-
//
|
|
1433
|
+
// Trails panel drops its canvas state (focus, expanded flows, selected
|
|
1427
1434
|
// edge) so the Files view's topology edges aren't gated by a flow the user
|
|
1428
1435
|
// can no longer see or un-dim.
|
|
1429
1436
|
const changeSidebarView = useCallback((view) => {
|
|
1430
1437
|
setSidebarView(view);
|
|
1431
|
-
if (view === '
|
|
1438
|
+
if (view === 'trails')
|
|
1432
1439
|
return;
|
|
1433
|
-
|
|
1434
|
-
|
|
1440
|
+
clearTrailFocus();
|
|
1441
|
+
setExpandedTrails(new Set());
|
|
1435
1442
|
setSelectedEdgeId(null);
|
|
1436
|
-
}, [
|
|
1437
|
-
// Focus a single step's edge on the canvas and open/scroll the
|
|
1443
|
+
}, [clearTrailFocus]);
|
|
1444
|
+
// Focus a single step's edge on the canvas and open/scroll the trail
|
|
1438
1445
|
// drawer to that step's snippet. Clicking the already-focused step clears
|
|
1439
1446
|
// step focus and returns to whole-flow (high-level) framing.
|
|
1440
1447
|
// In `dim` mode: only dim non-participants (same as hovering a step) —
|
|
1441
1448
|
// no camera move, no drawer, no hide.
|
|
1442
|
-
const
|
|
1449
|
+
const focusTrailStep = useCallback((tl, stepIndex) => {
|
|
1443
1450
|
const step = tl.steps[stepIndex];
|
|
1444
1451
|
if (!step)
|
|
1445
1452
|
return;
|
|
1446
1453
|
setSelected(null);
|
|
1447
1454
|
setSelectedEdgeId(null);
|
|
1448
|
-
if (
|
|
1455
|
+
if (trailStepMode === 'dim') {
|
|
1449
1456
|
setFocusedStepIndex(null);
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
setDrawerTarget((prev) => (prev?.kind === '
|
|
1457
|
+
setFocusedTrailId(null);
|
|
1458
|
+
setHoveredTrailStep({ trailId: tl.id, stepIndex });
|
|
1459
|
+
setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
|
|
1453
1460
|
return;
|
|
1454
1461
|
}
|
|
1455
1462
|
// Toggle: clicking the selected step unselects it (back to whole flow).
|
|
1456
|
-
if (
|
|
1463
|
+
if (focusedTrailId === tl.id && focusedStepIndex === stepIndex) {
|
|
1457
1464
|
setFocusedStepIndex(null);
|
|
1458
1465
|
// Pointer is still over the row — keep hover preview so dimming doesn't
|
|
1459
1466
|
// flash off until mouseleave.
|
|
1460
|
-
|
|
1461
|
-
setDrawerTarget((prev) => (prev?.kind === '
|
|
1467
|
+
setHoveredTrailStep({ trailId: tl.id, stepIndex });
|
|
1468
|
+
setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
|
|
1462
1469
|
if (pendingFocusFitRef.current != null) {
|
|
1463
1470
|
window.clearTimeout(pendingFocusFitRef.current);
|
|
1464
1471
|
pendingFocusFitRef.current = null;
|
|
1465
1472
|
}
|
|
1466
|
-
fitFocusBounds(new Set(tl.steps.map((s) =>
|
|
1473
|
+
fitFocusBounds(new Set(tl.steps.map((s) => trailStepGraphEdgeId(s))));
|
|
1467
1474
|
return;
|
|
1468
1475
|
}
|
|
1469
1476
|
setFocusedStepIndex(stepIndex);
|
|
1470
|
-
|
|
1471
|
-
|
|
1477
|
+
setFocusedTrailId(tl.id);
|
|
1478
|
+
setHoveredTrailStep(null);
|
|
1472
1479
|
// Open the drawer before fitting. If it was closed, wait for its height
|
|
1473
1480
|
// transition so fitView uses the reduced canvas — not full height.
|
|
1474
1481
|
const drawerWasOpen = drawerOpenRef.current;
|
|
1475
|
-
if (
|
|
1482
|
+
if (renderTrailViewer) {
|
|
1476
1483
|
setDrawerTarget({
|
|
1477
|
-
kind: '
|
|
1478
|
-
|
|
1484
|
+
kind: 'trail',
|
|
1485
|
+
trailId: tl.id,
|
|
1479
1486
|
stepIndex,
|
|
1480
1487
|
});
|
|
1481
1488
|
}
|
|
@@ -1486,7 +1493,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1486
1493
|
startLine: step.line,
|
|
1487
1494
|
});
|
|
1488
1495
|
}
|
|
1489
|
-
const edgeIds = new Set([
|
|
1496
|
+
const edgeIds = new Set([trailStepGraphEdgeId(step)]);
|
|
1490
1497
|
if (pendingFocusFitRef.current != null) {
|
|
1491
1498
|
window.clearTimeout(pendingFocusFitRef.current);
|
|
1492
1499
|
pendingFocusFitRef.current = null;
|
|
@@ -1503,9 +1510,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1503
1510
|
}, [
|
|
1504
1511
|
fitFocusBounds,
|
|
1505
1512
|
focusedStepIndex,
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1513
|
+
focusedTrailId,
|
|
1514
|
+
renderTrailViewer,
|
|
1515
|
+
trailStepMode,
|
|
1509
1516
|
]);
|
|
1510
1517
|
useEffect(() => {
|
|
1511
1518
|
return () => {
|
|
@@ -1523,11 +1530,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1523
1530
|
useEffect(() => {
|
|
1524
1531
|
const prev = prevPreviewEdgeIdsRef.current;
|
|
1525
1532
|
prevPreviewEdgeIdsRef.current = previewEdgeIds;
|
|
1526
|
-
if (!
|
|
1533
|
+
if (!zoomOnTrailFocus || trailStepMode === 'dim')
|
|
1527
1534
|
return;
|
|
1528
1535
|
// Camera follows hover only when a specific step is already focused (or
|
|
1529
1536
|
// autoplay is driving an auto-focus).
|
|
1530
|
-
if (!autoPlayFocus && (
|
|
1537
|
+
if (!autoPlayFocus && (focusedTrailId == null || focusedStepIndex == null)) {
|
|
1531
1538
|
return;
|
|
1532
1539
|
}
|
|
1533
1540
|
if (previewEdgeIds) {
|
|
@@ -1540,27 +1547,27 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1540
1547
|
}, [
|
|
1541
1548
|
previewEdgeIds,
|
|
1542
1549
|
focusEdgeIds,
|
|
1543
|
-
|
|
1550
|
+
focusedTrailId,
|
|
1544
1551
|
focusedStepIndex,
|
|
1545
1552
|
autoPlayFocus,
|
|
1546
1553
|
fitFocusBounds,
|
|
1547
|
-
|
|
1548
|
-
|
|
1554
|
+
zoomOnTrailFocus,
|
|
1555
|
+
trailStepMode,
|
|
1549
1556
|
]);
|
|
1550
|
-
// Graph-only embeds: cycle
|
|
1551
|
-
// selected (camera frames it via fitFocusBounds) and, when a
|
|
1557
|
+
// Graph-only embeds: cycle trail steps. In `focus` mode each step is
|
|
1558
|
+
// selected (camera frames it via fitFocusBounds) and, when a trail
|
|
1552
1559
|
// viewer is supplied, its snippet drawer opens; in `dim` mode it only
|
|
1553
1560
|
// dim-highlights the step (hover-style) with no camera move.
|
|
1554
1561
|
useEffect(() => {
|
|
1555
|
-
if (!
|
|
1562
|
+
if (!autoPlayTrails || trails == null || trails.length === 0)
|
|
1556
1563
|
return;
|
|
1557
1564
|
if (!layoutReady)
|
|
1558
1565
|
return;
|
|
1559
|
-
const playable =
|
|
1566
|
+
const playable = trails.filter((tl) => tl.steps.length > 0);
|
|
1560
1567
|
if (playable.length === 0)
|
|
1561
1568
|
return;
|
|
1562
1569
|
// Only run the cycles that can actually be shown in the current edge view.
|
|
1563
|
-
const inView = playable.filter((tl) => tl.steps.some((s) =>
|
|
1570
|
+
const inView = playable.filter((tl) => tl.steps.some((s) => isTrailMechanism(s.mechanism)));
|
|
1564
1571
|
if (inView.length === 0)
|
|
1565
1572
|
return;
|
|
1566
1573
|
const cyc = inView;
|
|
@@ -1568,8 +1575,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1568
1575
|
let tlIdx = 0;
|
|
1569
1576
|
let stepIdx = 0;
|
|
1570
1577
|
let timer = null;
|
|
1571
|
-
const interval = Math.max(400,
|
|
1572
|
-
const focusMode =
|
|
1578
|
+
const interval = Math.max(400, trailAutoPlayIntervalMs);
|
|
1579
|
+
const focusMode = trailStepMode !== 'dim';
|
|
1573
1580
|
let cleanup = () => { };
|
|
1574
1581
|
const tick = () => {
|
|
1575
1582
|
if (cancelled)
|
|
@@ -1578,15 +1585,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1578
1585
|
setSelected(null);
|
|
1579
1586
|
setSelectedEdgeId(null);
|
|
1580
1587
|
if (focusMode) {
|
|
1581
|
-
|
|
1582
|
-
|
|
1588
|
+
setHoveredTrailStep(null);
|
|
1589
|
+
setFocusedTrailId(null);
|
|
1583
1590
|
setFocusedStepIndex(null);
|
|
1584
|
-
setAutoPlayFocus({
|
|
1591
|
+
setAutoPlayFocus({ trailId: tl.id, stepIndex: stepIdx });
|
|
1585
1592
|
if (!hideDrawer) {
|
|
1586
1593
|
const step = tl.steps[stepIdx];
|
|
1587
1594
|
if (step) {
|
|
1588
|
-
if (
|
|
1589
|
-
setDrawerTarget({ kind: '
|
|
1595
|
+
if (renderTrailViewer) {
|
|
1596
|
+
setDrawerTarget({ kind: 'trail', trailId: tl.id, stepIndex: stepIdx });
|
|
1590
1597
|
}
|
|
1591
1598
|
else {
|
|
1592
1599
|
setDrawerTarget({ kind: 'file', file: step.file, startLine: step.line });
|
|
@@ -1596,9 +1603,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1596
1603
|
}
|
|
1597
1604
|
else {
|
|
1598
1605
|
setAutoPlayFocus(null);
|
|
1599
|
-
|
|
1606
|
+
setFocusedTrailId(null);
|
|
1600
1607
|
setFocusedStepIndex(null);
|
|
1601
|
-
|
|
1608
|
+
setHoveredTrailStep({ trailId: tl.id, stepIndex: stepIdx });
|
|
1602
1609
|
}
|
|
1603
1610
|
stepIdx += 1;
|
|
1604
1611
|
if (stepIdx >= tl.steps.length) {
|
|
@@ -1612,28 +1619,28 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1612
1619
|
cancelled = true;
|
|
1613
1620
|
if (timer != null)
|
|
1614
1621
|
window.clearTimeout(timer);
|
|
1615
|
-
|
|
1616
|
-
|
|
1622
|
+
setHoveredTrailStep(null);
|
|
1623
|
+
setFocusedTrailId(null);
|
|
1617
1624
|
setFocusedStepIndex(null);
|
|
1618
1625
|
setAutoPlayFocus(null);
|
|
1619
1626
|
};
|
|
1620
1627
|
return cleanup;
|
|
1621
1628
|
}, [
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1629
|
+
autoPlayTrails,
|
|
1630
|
+
trails,
|
|
1631
|
+
trailAutoPlayIntervalMs,
|
|
1625
1632
|
layoutReady,
|
|
1626
|
-
|
|
1627
|
-
|
|
1633
|
+
trailStepMode,
|
|
1634
|
+
renderTrailViewer,
|
|
1628
1635
|
hideDrawer,
|
|
1629
1636
|
]);
|
|
1630
|
-
// Arrow keys step through the focused
|
|
1637
|
+
// Arrow keys step through the focused trail once a step is active
|
|
1631
1638
|
// (sidebar click or drawer open). Ignores typing targets and chords.
|
|
1632
1639
|
useEffect(() => {
|
|
1633
|
-
if (
|
|
1640
|
+
if (focusedTrailId == null || focusedStepIndex == null || !trails) {
|
|
1634
1641
|
return;
|
|
1635
1642
|
}
|
|
1636
|
-
const tl =
|
|
1643
|
+
const tl = trails.find((t) => t.id === focusedTrailId);
|
|
1637
1644
|
if (!tl || tl.steps.length === 0)
|
|
1638
1645
|
return;
|
|
1639
1646
|
const onKeyDown = (e) => {
|
|
@@ -1660,19 +1667,19 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1660
1667
|
if (next === focusedStepIndex)
|
|
1661
1668
|
return;
|
|
1662
1669
|
e.preventDefault();
|
|
1663
|
-
|
|
1670
|
+
focusTrailStep(tl, next);
|
|
1664
1671
|
};
|
|
1665
1672
|
window.addEventListener('keydown', onKeyDown);
|
|
1666
1673
|
return () => window.removeEventListener('keydown', onKeyDown);
|
|
1667
1674
|
}, [
|
|
1668
|
-
|
|
1675
|
+
focusedTrailId,
|
|
1669
1676
|
focusedStepIndex,
|
|
1670
|
-
|
|
1671
|
-
|
|
1677
|
+
trails,
|
|
1678
|
+
focusTrailStep,
|
|
1672
1679
|
]);
|
|
1673
|
-
const
|
|
1674
|
-
const collapsingLast =
|
|
1675
|
-
|
|
1680
|
+
const toggleTrailCollapsed = useCallback((tlId) => {
|
|
1681
|
+
const collapsingLast = expandedTrails.has(tlId) && expandedTrails.size === 1;
|
|
1682
|
+
setExpandedTrails((prev) => {
|
|
1676
1683
|
const next = new Set(prev);
|
|
1677
1684
|
if (next.has(tlId))
|
|
1678
1685
|
next.delete(tlId);
|
|
@@ -1680,7 +1687,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1680
1687
|
next.add(tlId);
|
|
1681
1688
|
return next;
|
|
1682
1689
|
});
|
|
1683
|
-
if (!collapsingLast || !
|
|
1690
|
+
if (!collapsingLast || !zoomOnTrailFocus)
|
|
1684
1691
|
return;
|
|
1685
1692
|
// Cancel any pending focus fit so it doesn't fight the overview zoom.
|
|
1686
1693
|
if (pendingFocusFitRef.current != null) {
|
|
@@ -1695,7 +1702,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1695
1702
|
pendingFocusFitRef.current = null;
|
|
1696
1703
|
requestAnimationFrame(() => fitOverview());
|
|
1697
1704
|
}, drawerWasOpen ? FILE_DRAWER_HEIGHT_MS + 20 : 20);
|
|
1698
|
-
}, [
|
|
1705
|
+
}, [expandedTrails, fitOverview, zoomOnTrailFocus]);
|
|
1699
1706
|
// Detail-panel links: related-name clicks select the matching component —
|
|
1700
1707
|
// resolved by alias, name, or symbol (call labels may carry a trailing `()`).
|
|
1701
1708
|
const resolveRelatedComponent = useCallback((ref) => {
|
|
@@ -1708,7 +1715,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1708
1715
|
return;
|
|
1709
1716
|
setSelected(comp);
|
|
1710
1717
|
setSelectedEdgeId(null);
|
|
1711
|
-
|
|
1718
|
+
setFocusedTrailId(null);
|
|
1712
1719
|
setFocusedStepIndex(null);
|
|
1713
1720
|
onSelect?.(comp.alias);
|
|
1714
1721
|
}, [components, onSelect]);
|
|
@@ -1723,9 +1730,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1723
1730
|
if (comp) {
|
|
1724
1731
|
setSelected(comp);
|
|
1725
1732
|
setSelectedEdgeId(null);
|
|
1726
|
-
|
|
1733
|
+
setFocusedTrailId(null);
|
|
1727
1734
|
setFocusedStepIndex(null);
|
|
1728
|
-
|
|
1735
|
+
setHoveredTrailStep(null);
|
|
1729
1736
|
issueModuleFocusRef.current = null;
|
|
1730
1737
|
onSelect?.(comp.alias);
|
|
1731
1738
|
requestAnimationFrame(() => {
|
|
@@ -1740,11 +1747,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1740
1747
|
}
|
|
1741
1748
|
else if (step) {
|
|
1742
1749
|
// A step finding focuses the step itself, through the same entry point
|
|
1743
|
-
// the
|
|
1750
|
+
// the trail panel uses — so the drawer, the step numbering, the
|
|
1744
1751
|
// dim-mode hover preview, and the camera all behave identically whether
|
|
1745
1752
|
// the step was reached from the panel or from a diagnostics card.
|
|
1746
1753
|
issueModuleFocusRef.current = null;
|
|
1747
|
-
|
|
1754
|
+
focusTrailStep(step.trail, step.stepIndex);
|
|
1748
1755
|
}
|
|
1749
1756
|
else {
|
|
1750
1757
|
// Nothing selectable — but a module target names a boundary frame, so
|
|
@@ -1753,9 +1760,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1753
1760
|
if (moduleNodeId) {
|
|
1754
1761
|
setSelected(null);
|
|
1755
1762
|
setSelectedEdgeId(null);
|
|
1756
|
-
|
|
1763
|
+
setFocusedTrailId(null);
|
|
1757
1764
|
setFocusedStepIndex(null);
|
|
1758
|
-
|
|
1765
|
+
setHoveredTrailStep(null);
|
|
1759
1766
|
issueModuleFocusRef.current = moduleNodeId;
|
|
1760
1767
|
requestAnimationFrame(() => {
|
|
1761
1768
|
fitNodeRects(new Set([moduleNodeId]), 0.4);
|
|
@@ -1767,7 +1774,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1767
1774
|
issueComponent,
|
|
1768
1775
|
issueStep,
|
|
1769
1776
|
issueModuleNodeId,
|
|
1770
|
-
|
|
1777
|
+
focusTrailStep,
|
|
1771
1778
|
onSelect,
|
|
1772
1779
|
onSelectIssue,
|
|
1773
1780
|
fitView,
|
|
@@ -1795,13 +1802,13 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1795
1802
|
// keeps its step focus in hover state instead (owned by the pointer,
|
|
1796
1803
|
// not the card), and correctly leaves that alone here.
|
|
1797
1804
|
const owned = focusedStepRef.current;
|
|
1798
|
-
if (owned?.
|
|
1805
|
+
if (owned?.trailId !== step.trail.id ||
|
|
1799
1806
|
owned?.stepIndex !== step.stepIndex) {
|
|
1800
1807
|
return;
|
|
1801
1808
|
}
|
|
1802
|
-
|
|
1809
|
+
setFocusedTrailId(null);
|
|
1803
1810
|
setFocusedStepIndex(null);
|
|
1804
|
-
setDrawerTarget((prev) => (prev?.kind === '
|
|
1811
|
+
setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
|
|
1805
1812
|
}
|
|
1806
1813
|
else if (moduleNodeId) {
|
|
1807
1814
|
// Only unwind framing this card still owns.
|
|
@@ -1817,14 +1824,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1817
1824
|
fitView({ padding: 0.1, duration: 300, minZoom: 0.05, maxZoom: 2 });
|
|
1818
1825
|
});
|
|
1819
1826
|
}, [issueComponent, issueStep, issueModuleNodeId, fitView]);
|
|
1820
|
-
// Construct tokens inside a
|
|
1827
|
+
// Construct tokens inside a trail snippet. A step's line is an edge
|
|
1821
1828
|
// between its `from`/`to` components, so a token naming either of those
|
|
1822
1829
|
// constructs should navigate to that construct's declaration. Index the
|
|
1823
|
-
// matchable identifiers per step (`
|
|
1824
|
-
const
|
|
1830
|
+
// matchable identifiers per step (`trailId:index`).
|
|
1831
|
+
const trailSymbolIndex = useMemo(() => {
|
|
1825
1832
|
const byAlias = new Map(components.map((c) => [c.alias, c]));
|
|
1826
1833
|
const index = new Map();
|
|
1827
|
-
for (const wt of
|
|
1834
|
+
for (const wt of trails ?? []) {
|
|
1828
1835
|
wt.steps.forEach((step, i) => {
|
|
1829
1836
|
const tokens = new Map();
|
|
1830
1837
|
for (const alias of [step.from, step.to]) {
|
|
@@ -1838,17 +1845,17 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1838
1845
|
});
|
|
1839
1846
|
}
|
|
1840
1847
|
return index;
|
|
1841
|
-
}, [components,
|
|
1842
|
-
const
|
|
1843
|
-
|
|
1844
|
-
// Stable resolver: reads the live index + focused
|
|
1845
|
-
// the memoized
|
|
1846
|
-
const
|
|
1847
|
-
const
|
|
1848
|
-
if (
|
|
1848
|
+
}, [components, trails]);
|
|
1849
|
+
const trailSymbolIndexRef = useRef(trailSymbolIndex);
|
|
1850
|
+
trailSymbolIndexRef.current = trailSymbolIndex;
|
|
1851
|
+
// Stable resolver: reads the live index + focused trail from refs so
|
|
1852
|
+
// the memoized trail drawer isn't rebuilt on every graph render.
|
|
1853
|
+
const resolveTrailSymbol = useCallback((query) => {
|
|
1854
|
+
const trailId = drawerTrailIdRef.current;
|
|
1855
|
+
if (trailId == null)
|
|
1849
1856
|
return null;
|
|
1850
|
-
return (
|
|
1851
|
-
.get(`${
|
|
1857
|
+
return (trailSymbolIndexRef.current
|
|
1858
|
+
.get(`${trailId}:${query.stepIndex}`)
|
|
1852
1859
|
?.get(query.tokenText) ?? null);
|
|
1853
1860
|
}, []);
|
|
1854
1861
|
// A construct token click navigates to that construct's declaration: open
|
|
@@ -1911,10 +1918,10 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1911
1918
|
const fileViewerRef = useRef(fileViewer);
|
|
1912
1919
|
fileViewerRef.current = fileViewer;
|
|
1913
1920
|
const renderDrawerContent = useCallback((file, opts) => fileViewerRef.current?.(file, opts) ?? null, []);
|
|
1914
|
-
const
|
|
1915
|
-
|
|
1916
|
-
const
|
|
1917
|
-
return (_jsxs("div", { style: { width: '100%', height: '100%', display: 'flex', flexDirection: 'row', userSelect: sidebarDrag ? 'none' : undefined }, children: [!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 ||
|
|
1921
|
+
const trailViewerRef = useRef(renderTrailViewer);
|
|
1922
|
+
trailViewerRef.current = renderTrailViewer;
|
|
1923
|
+
const renderTrailDrawerContent = useCallback((ctx) => trailViewerRef.current?.(ctx) ?? null, []);
|
|
1924
|
+
return (_jsxs("div", { style: { width: '100%', height: '100%', display: 'flex', flexDirection: 'row', userSelect: sidebarDrag ? 'none' : undefined }, children: [!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasTrails) && (_jsxs("div", { style: {
|
|
1918
1925
|
width: sidebarWidth,
|
|
1919
1926
|
minWidth: sidebarWidth,
|
|
1920
1927
|
borderRight: `1px solid ${theme.colors.border}`,
|
|
@@ -1960,7 +1967,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1960
1967
|
color: descriptionVisible || descToggleHover ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
|
|
1961
1968
|
cursor: 'pointer',
|
|
1962
1969
|
transition: 'background 120ms ease, color 120ms ease',
|
|
1963
|
-
}, children: _jsx(FileText, { size: 14 }) }))] })] })), description && descriptionVisible && (_jsx("div", { style: { fontSize: theme.fontSizes[0], lineHeight: 1.5 }, children: _jsx(IndustryMarkdownSlide, { content: description, slideIdPrefix: "subsystem-desc", slideIndex: 0, isVisible: true, theme: theme, disableScroll: true, disableBasePadding: true, enableKeyboardScrolling: false, autoFocusOnVisible: false }) })), sidebarAfterDescription] }), (treeFilePaths.length > 0 ||
|
|
1970
|
+
}, children: _jsx(FileText, { size: 14 }) }))] })] })), description && descriptionVisible && (_jsx("div", { style: { fontSize: theme.fontSizes[0], lineHeight: 1.5 }, children: _jsx(IndustryMarkdownSlide, { content: description, slideIdPrefix: "subsystem-desc", slideIndex: 0, isVisible: true, theme: theme, disableScroll: true, disableBasePadding: true, enableKeyboardScrolling: false, autoFocusOnVisible: false }) })), sidebarAfterDescription] }), (treeFilePaths.length > 0 || hasTrails || issuesActive) && (_jsx("div", { style: {
|
|
1964
1971
|
...(showDesc ? { height: '50%' } : { flex: 1, minHeight: 0 }),
|
|
1965
1972
|
minHeight: 160,
|
|
1966
1973
|
flexShrink: 0,
|
|
@@ -1986,13 +1993,13 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1986
1993
|
fontFamily: theme.fonts.monospace,
|
|
1987
1994
|
textTransform: 'capitalize',
|
|
1988
1995
|
cursor: 'pointer',
|
|
1989
|
-
}, children: tab === 'issues' ? 'Issues' : 'Agents' }, tab))) }), diagnosticsTab === 'agents' ? (_jsx(SubsystemAgentsPanel, { ...agentsPanel })) : (_jsx(SubsystemIssueList, { issues: issues ?? [], focusCategory: focusIssueCategory, onSelectIssue: focusIssueTarget, onDeselectIssue: unfocusIssueTarget, onApplyFix: onApplyIssueFix, onHoverIssue: onHoverIssue, onExpandedCategoriesChange: handleExpandedCategoriesChange }))] })) : (_jsx(SubsystemIssueList, { issues: issues ?? [], focusCategory: focusIssueCategory, onSelectIssue: focusIssueTarget, onDeselectIssue: unfocusIssueTarget, onApplyFix: onApplyIssueFix, onHoverIssue: onHoverIssue, onExpandedCategoriesChange: handleExpandedCategoriesChange }))) : (_jsxs(_Fragment, { children: [
|
|
1996
|
+
}, children: tab === 'issues' ? 'Issues' : 'Agents' }, tab))) }), diagnosticsTab === 'agents' ? (_jsx(SubsystemAgentsPanel, { ...agentsPanel })) : (_jsx(SubsystemIssueList, { issues: issues ?? [], focusCategory: focusIssueCategory, onSelectIssue: focusIssueTarget, onDeselectIssue: unfocusIssueTarget, onApplyFix: onApplyIssueFix, onHoverIssue: onHoverIssue, onExpandedCategoriesChange: handleExpandedCategoriesChange }))] })) : (_jsx(SubsystemIssueList, { issues: issues ?? [], focusCategory: focusIssueCategory, onSelectIssue: focusIssueTarget, onDeselectIssue: unfocusIssueTarget, onApplyFix: onApplyIssueFix, onHoverIssue: onHoverIssue, onExpandedCategoriesChange: handleExpandedCategoriesChange }))) : (_jsxs(_Fragment, { children: [hasTrails && (_jsx("div", { role: "tablist", "aria-label": "Sidebar view", style: {
|
|
1990
1997
|
display: 'flex',
|
|
1991
1998
|
width: '100%',
|
|
1992
1999
|
flexShrink: 0,
|
|
1993
2000
|
borderBottom: `1px solid ${theme.colors.border}`,
|
|
1994
2001
|
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
1995
|
-
}, children: ['
|
|
2002
|
+
}, children: ['trails', 'files'].map((view) => (_jsx("button", { type: "button", role: "tab", "aria-selected": sidebarView === view, onClick: () => changeSidebarView(view), style: {
|
|
1996
2003
|
flex: 1,
|
|
1997
2004
|
minWidth: 0,
|
|
1998
2005
|
padding: '8px 8px',
|
|
@@ -2006,7 +2013,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2006
2013
|
fontFamily: theme.fonts.monospace,
|
|
2007
2014
|
textTransform: 'capitalize',
|
|
2008
2015
|
cursor: 'pointer',
|
|
2009
|
-
}, children: view === '
|
|
2016
|
+
}, children: view === 'trails' ? 'Trails' : 'Files' }, view))) })), sidebarView === 'trails' && trails && trails.length > 0 ? (_jsx(TrailsPanel, { trails: trails, expandedTrails: expandedTrails, focusedTrailId: focusedTrailId, focusedStepIndex: focusedStepIndex, hoveredTrailStep: hoveredTrailStep, onToggleCollapsed: toggleTrailCollapsed, onFocusFlow: focusTrailEdges, onClearFocus: clearTrailFocus, onFocusStep: focusTrailStep, onHoverStep: (tl, i) => setHoveredTrailStep({ trailId: tl.id, stepIndex: i }), onHoverFlow: (tl) => setHoveredTrailStep({ trailId: tl.id, stepIndex: null }), onLeaveStep: () => setHoveredTrailStep(null), onReorder: onReorderTrails, proposedAliases: proposedAliases })) : treeFilePaths.length > 0 ? (_jsx(_Fragment, { children: repoGroups.groups.map((group, i) => {
|
|
2010
2017
|
const groupKey = group.repoKey ?? '__no-repo__';
|
|
2011
2018
|
const collapsed = collapsedRepos.has(groupKey);
|
|
2012
2019
|
return (_jsxs("div", { style: {
|
|
@@ -2016,7 +2023,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2016
2023
|
flexDirection: 'column',
|
|
2017
2024
|
borderTop: i > 0 ? `1px solid ${theme.colors.border}` : undefined,
|
|
2018
2025
|
}, children: [repoGroups.multiRepo && (_jsx(RepoGroupHeader, { group: group, collapsed: collapsed, onToggle: () => toggleRepoCollapsed(groupKey) })), !collapsed && (_jsx(SubsystemFileTree, { files: group.entries.map((e) => e.displayPath), selectedFile: selected?.file ?? openFile, hoveredFile: hoveredFile, onSelectFile: onTreeSelectFile, hoveredFolder: hoveredBoundaryPath, headerless: repoGroups.multiRepo }))] }, groupKey));
|
|
2019
|
-
}) })) : null] })) }))] })), !hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 ||
|
|
2026
|
+
}) })) : null] })) }))] })), !hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasTrails) && (_jsx("div", { onMouseDown: onSidebarResizeStart, "aria-label": "Resize sidebar", title: "Drag to resize", style: {
|
|
2020
2027
|
width: 3,
|
|
2021
2028
|
flexShrink: 0,
|
|
2022
2029
|
cursor: 'col-resize',
|
|
@@ -2109,7 +2116,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2109
2116
|
flex: 1,
|
|
2110
2117
|
minHeight: 0,
|
|
2111
2118
|
background: theme.colors.background,
|
|
2112
|
-
}, children: _jsx(GraphChrome, {}) }, `${baseNodesKey}-${baseEdgesKey}`), (graphTitle ||
|
|
2119
|
+
}, children: _jsx(GraphChrome, {}) }, `${baseNodesKey}-${baseEdgesKey}`), (graphTitle || overlayTrailTitle || overlayTrailStep) && (_jsxs("div", { style: {
|
|
2113
2120
|
position: 'absolute',
|
|
2114
2121
|
top: 10,
|
|
2115
2122
|
left: '50%',
|
|
@@ -2135,9 +2142,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2135
2142
|
border: `1px solid ${theme.colors.border}`,
|
|
2136
2143
|
borderRadius: 6,
|
|
2137
2144
|
boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
|
|
2138
|
-
}, children: graphTitle })),
|
|
2145
|
+
}, children: graphTitle })), overlayTrailTitle && (_jsxs("div", { style: {
|
|
2139
2146
|
maxWidth: '100%',
|
|
2140
|
-
minWidth:
|
|
2147
|
+
minWidth: overlayTrailStep ? 160 : undefined,
|
|
2141
2148
|
display: 'flex',
|
|
2142
2149
|
flexDirection: 'column',
|
|
2143
2150
|
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
@@ -2146,9 +2153,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2146
2153
|
boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
|
|
2147
2154
|
overflow: 'hidden',
|
|
2148
2155
|
opacity: 0.95,
|
|
2149
|
-
}, "aria-label":
|
|
2150
|
-
? `${
|
|
2151
|
-
:
|
|
2156
|
+
}, "aria-label": overlayTrailStep
|
|
2157
|
+
? `${overlayTrailTitle}, step ${overlayTrailStep.index} of ${overlayTrailStep.total}`
|
|
2158
|
+
: overlayTrailTitle, children: [_jsx("div", { style: {
|
|
2152
2159
|
padding: '5px 14px',
|
|
2153
2160
|
fontSize: theme.fontSizes[2] ?? theme.fontSizes[1],
|
|
2154
2161
|
fontWeight: 600,
|
|
@@ -2158,14 +2165,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2158
2165
|
textOverflow: 'ellipsis',
|
|
2159
2166
|
whiteSpace: 'nowrap',
|
|
2160
2167
|
textAlign: 'center',
|
|
2161
|
-
}, children:
|
|
2168
|
+
}, children: overlayTrailTitle }), overlayTrailStep && overlayTrailStep.total > 0 && (_jsx("div", { style: {
|
|
2162
2169
|
display: 'flex',
|
|
2163
2170
|
gap: 3,
|
|
2164
2171
|
padding: '0 6px 5px',
|
|
2165
|
-
}, "aria-hidden": "true", children: Array.from({ length:
|
|
2172
|
+
}, "aria-hidden": "true", children: Array.from({ length: overlayTrailStep.total }, (_, i) => {
|
|
2166
2173
|
const n = i + 1;
|
|
2167
|
-
const active = n ===
|
|
2168
|
-
const done = n <
|
|
2174
|
+
const active = n === overlayTrailStep.index;
|
|
2175
|
+
const done = n < overlayTrailStep.index;
|
|
2169
2176
|
return (_jsx("span", { style: {
|
|
2170
2177
|
flex: 1,
|
|
2171
2178
|
height: 2,
|
|
@@ -2178,7 +2185,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2178
2185
|
opacity: active ? 1 : done ? 0.75 : 0.4,
|
|
2179
2186
|
transition: 'opacity 120ms ease, background 120ms ease',
|
|
2180
2187
|
} }, n));
|
|
2181
|
-
}) }))] })),
|
|
2188
|
+
}) }))] })), overlayTrailStep?.annotation && (_jsx("div", { style: {
|
|
2182
2189
|
maxWidth: '100%',
|
|
2183
2190
|
padding: '6px 14px',
|
|
2184
2191
|
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
@@ -2190,7 +2197,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2190
2197
|
fontFamily: theme.fonts.body,
|
|
2191
2198
|
color: theme.colors.textMuted ?? theme.colors.textSecondary,
|
|
2192
2199
|
lineHeight: 1.35,
|
|
2193
|
-
}, children:
|
|
2200
|
+
}, children: overlayTrailStep.annotation }))] })), selected && (_jsx("div", { style: {
|
|
2194
2201
|
position: 'absolute',
|
|
2195
2202
|
top: 10,
|
|
2196
2203
|
right: 10,
|
|
@@ -2215,9 +2222,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2215
2222
|
drawerTarget.startLine != null &&
|
|
2216
2223
|
!!selected.file &&
|
|
2217
2224
|
selected.file === drawerTarget.file &&
|
|
2218
|
-
selected.declarationRef?.startLine === drawerTarget.startLine }) }))] }), _jsx(FileDrawer, { title: drawerTitle, onClose: closeDrawer, fillHeight: drawerFillHeight, suppressEscape: fileOverlayOpen, hidden: hideDrawer, children: drawerTarget?.kind === '
|
|
2219
|
-
|
|
2220
|
-
|
|
2225
|
+
selected.declarationRef?.startLine === drawerTarget.startLine }) }))] }), _jsx(FileDrawer, { title: drawerTitle, onClose: closeDrawer, fillHeight: drawerFillHeight, suppressEscape: fileOverlayOpen, hidden: hideDrawer, children: drawerTarget?.kind === 'trail' &&
|
|
2226
|
+
focusedTrail &&
|
|
2227
|
+
renderTrailViewer ? (_jsx(TrailDrawerContent, { render: renderTrailDrawerContent, trail: focusedTrail, stepIndex: drawerTarget.stepIndex, onOpenFile: onOpenFileFromTrail, proposedAliases: proposedAliases, resolveSymbol: resolveTrailSymbol, onSymbolClick: openConstructDeclaration })) : drawerTarget?.kind === 'file' ? (_jsx(FileDrawerContent, { render: renderDrawerContent, file: drawerTarget.file, startLine: drawerTarget.startLine })) : null }), fileOverlay && (_jsxs("div", { style: {
|
|
2221
2228
|
position: 'absolute',
|
|
2222
2229
|
inset: 0,
|
|
2223
2230
|
zIndex: 8,
|
|
@@ -2304,7 +2311,7 @@ export function SubsystemComponentGraph(props) {
|
|
|
2304
2311
|
}, []);
|
|
2305
2312
|
const constructsOnly = isConstructsOnlyModel({
|
|
2306
2313
|
components: props.components,
|
|
2307
|
-
|
|
2314
|
+
trails: props.trails,
|
|
2308
2315
|
graphifyRelations: props.graphifyRelations,
|
|
2309
2316
|
});
|
|
2310
2317
|
return (_jsx("div", { ref: wrapRef, style: {
|