@principal-ai/subsystems-react 0.44.3 → 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 +307 -309
- 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 +30 -0
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +29 -0
- package/dist/subsystem/nodes.js.map +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 +400 -403
- 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.test.ts +42 -1
- package/src/subsystem/nodes.tsx +38 -0
- 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,11 +20,11 @@ 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';
|
|
27
|
-
import { SubsystemComponentNode, SubsystemGroupNode, SubsystemEdge, SUBSYSTEM_CALLBACKS, hexWithAlpha, EDGE_DIM_ALPHA, fileMatchForNode, flowElementVisibility } from './nodes';
|
|
27
|
+
import { SubsystemComponentNode, SubsystemGroupNode, SubsystemEdge, SUBSYSTEM_CALLBACKS, hexWithAlpha, EDGE_DIM_ALPHA, fileMatchForNode, flowElementVisibility, flowNodeVisibility } from './nodes';
|
|
28
28
|
import { SubsystemDiagnosticToggle } from './DiagnosticToggle';
|
|
29
29
|
import { SubsystemIssueList, issueCategory, issueKindOrder, issueRung, ISSUE_KIND_ICON, ISSUE_RUNG_ORDER, } from './IssueList';
|
|
30
30
|
import { SubsystemFileTree } from './SubsystemFileTree';
|
|
@@ -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)
|
|
@@ -935,36 +942,29 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
935
942
|
// `isSelected` rides in data because the node's stopPropagation() keeps
|
|
936
943
|
// React Flow's own selection state from updating.
|
|
937
944
|
const dispNodes = useMemo(() => {
|
|
938
|
-
// While a step/flow is hovered,
|
|
939
|
-
//
|
|
940
|
-
//
|
|
941
|
-
//
|
|
942
|
-
// nothing to hide toward — everything shows, and focus/hover only dims.
|
|
943
|
-
const noOpenedFlow = openedEdgeIds == null;
|
|
945
|
+
// While a step/flow is hovered, hovered participants stay bright; every
|
|
946
|
+
// other node is dimmed (and, when a flow is expanded, hidden). Exception:
|
|
947
|
+
// with no expanded flow at all (a sidebar-less embed) there is nothing to
|
|
948
|
+
// hide toward — everything shows, and focus/hover only dims.
|
|
944
949
|
return xyflowNodesBase.map((n) => {
|
|
945
950
|
// Boundary frames follow their members: hidden when no member is
|
|
946
951
|
// visible, dimmed when members are dimmed. Never selectable.
|
|
947
952
|
if (n.type === 'subsystem-group') {
|
|
948
953
|
const region = n.data?.region;
|
|
949
954
|
const memberAliases = region?.memberAliases ?? [];
|
|
950
|
-
//
|
|
951
|
-
|
|
955
|
+
// `brightNodeIds` is the focused step's endpoints. It has to be a
|
|
956
|
+
// spotlight member as well as a bright set: without an expanded flow
|
|
957
|
+
// there is no opened set, so dimming has to be driven from the
|
|
958
|
+
// participants or nothing outside the focused step would dim.
|
|
959
|
+
const dimSource = previewNodeIds ?? brightNodeIds ?? issueFocusNodeIds;
|
|
960
|
+
const { hidden, dimmed } = flowNodeVisibility({
|
|
952
961
|
inOpened: memberAliases.some((alias) => openedNodeIds?.has(alias) === true),
|
|
953
962
|
inSelected: memberAliases.some((alias) => brightNodeIds?.has(alias) === true),
|
|
963
|
+
inSpotlight: memberAliases.some((alias) => dimSource?.has(alias) === true),
|
|
954
964
|
anyOpened: openedNodeIds != null,
|
|
955
|
-
anySelected: brightNodeIds != null
|
|
965
|
+
anySelected: brightNodeIds != null,
|
|
966
|
+
anySpotlight: dimSource != null,
|
|
956
967
|
});
|
|
957
|
-
// `brightNodeIds` is the focused step's endpoints. It has to be a dim
|
|
958
|
-
// source, not just a bright set: without an expanded flow,
|
|
959
|
-
// `flowElementVisibility` reports non-participants as *hidden*, and the
|
|
960
|
-
// noOpenedFlow escape below discards that — leaving them neither hidden
|
|
961
|
-
// nor dimmed. Naming the participants as the dim source dims everything
|
|
962
|
-
// outside the focused step however the flow got focused.
|
|
963
|
-
const dimSource = previewNodeIds ?? brightNodeIds ?? issueFocusNodeIds;
|
|
964
|
-
const dimmed = dimSource
|
|
965
|
-
? vis.hidden || !memberAliases.some((alias) => dimSource.has(alias))
|
|
966
|
-
: vis.dimmed;
|
|
967
|
-
const hidden = noOpenedFlow ? false : vis.hidden;
|
|
968
968
|
// Host override wins over the library's derived frame color.
|
|
969
969
|
const color = region?.key != null ? boundaryColors?.[region.key] : undefined;
|
|
970
970
|
// Boundary findings badge the FRAME, not a member — a region's shape is
|
|
@@ -984,22 +984,20 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
984
984
|
}
|
|
985
985
|
const comp = n.data?.component;
|
|
986
986
|
const fileMatch = fileMatchForNode(comp?.file, openFile, focusNodeIds?.has(n.id) === true);
|
|
987
|
-
//
|
|
988
|
-
//
|
|
989
|
-
// embed), only dim them. Bases selection on the focused step, not the
|
|
990
|
-
// hover preview, so hovering doesn't strip the frame's `isSelected`.
|
|
987
|
+
// Bases selection on the focused step, not the hover preview, so
|
|
988
|
+
// hovering doesn't strip the frame's `isSelected`.
|
|
991
989
|
const isSelected = selected?.alias !== undefined && comp?.alias === selected.alias;
|
|
992
|
-
|
|
990
|
+
// `brightNodeIds` (the focused step's endpoints) is a spotlight member as
|
|
991
|
+
// well as a bright set — see the group branch above.
|
|
992
|
+
const dimSource = previewNodeIds ?? brightNodeIds ?? issueFocusNodeIds;
|
|
993
|
+
const { hidden, dimmed } = flowNodeVisibility({
|
|
993
994
|
inOpened: openedNodeIds?.has(n.id) === true,
|
|
994
995
|
inSelected: brightNodeIds?.has(n.id) === true,
|
|
996
|
+
inSpotlight: dimSource?.has(n.id) === true,
|
|
995
997
|
anyOpened: openedNodeIds != null,
|
|
996
|
-
anySelected: brightNodeIds != null
|
|
998
|
+
anySelected: brightNodeIds != null,
|
|
999
|
+
anySpotlight: dimSource != null,
|
|
997
1000
|
});
|
|
998
|
-
// `brightNodeIds` (the focused step's endpoints) is a dim source as well
|
|
999
|
-
// as a bright set — see the group branch above.
|
|
1000
|
-
const dimSource = previewNodeIds ?? brightNodeIds ?? issueFocusNodeIds;
|
|
1001
|
-
const dimmed = dimSource ? (vis.hidden || !dimSource.has(n.id)) : vis.dimmed;
|
|
1002
|
-
const hidden = noOpenedFlow ? false : vis.hidden;
|
|
1003
1001
|
// Component findings key by alias, boundary findings by region node id.
|
|
1004
1002
|
// The two id spaces are disjoint (`module:` / `process:` are prefixed), so
|
|
1005
1003
|
// one lookup covers both node kinds.
|
|
@@ -1037,14 +1035,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1037
1035
|
};
|
|
1038
1036
|
// Edges outside the selected view are hidden entirely (labels included).
|
|
1039
1037
|
// graphify-native edges belong to the graphify view (they are static
|
|
1040
|
-
// topology, not runtime
|
|
1038
|
+
// topology, not runtime steps) regardless of their verb.
|
|
1041
1039
|
const edgeInView = (e) => {
|
|
1042
1040
|
const d = e.data;
|
|
1043
1041
|
const mechanism = d?.mechanism ?? 'uses';
|
|
1044
1042
|
if (resolvedEdgeView === 'graphify') {
|
|
1045
1043
|
return d?.provenance === 'graphify';
|
|
1046
1044
|
}
|
|
1047
|
-
return
|
|
1045
|
+
return isTrailMechanism(mechanism);
|
|
1048
1046
|
};
|
|
1049
1047
|
if (openedEdgeIds || focusEdgeIds || previewEdgeIds || issueFocusEdgeIds) {
|
|
1050
1048
|
return baseEdges.map((e) => {
|
|
@@ -1099,7 +1097,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1099
1097
|
const onNodeClick = useCallback((_e, node) => {
|
|
1100
1098
|
const comp = node.data?.component;
|
|
1101
1099
|
setSelectedEdgeId(null);
|
|
1102
|
-
|
|
1100
|
+
setFocusedTrailId(null);
|
|
1103
1101
|
setFocusedStepIndex(null);
|
|
1104
1102
|
if (node.type === 'subsystem-component' && comp) {
|
|
1105
1103
|
// Clicking the already-selected node unselects it (toggle off).
|
|
@@ -1121,15 +1119,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1121
1119
|
const onPaneClick = useCallback(() => {
|
|
1122
1120
|
setSelected(null);
|
|
1123
1121
|
setSelectedEdgeId(null);
|
|
1124
|
-
|
|
1122
|
+
setFocusedTrailId(null);
|
|
1125
1123
|
setFocusedStepIndex(null);
|
|
1126
1124
|
issueModuleFocusRef.current = null;
|
|
1127
1125
|
}, []);
|
|
1128
1126
|
// Sidebar file trees — one per repo on multi-repo graphs, each under its
|
|
1129
1127
|
// own owner-avatar header. Clicking a header collapses that repo's tree.
|
|
1130
1128
|
const repoGroups = useMemo(() => buildRepoGroups(components), [components]);
|
|
1131
|
-
const
|
|
1132
|
-
// 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
|
|
1133
1131
|
// (and steps) that touch one.
|
|
1134
1132
|
const proposedAliases = useMemo(() => new Set(components.filter((c) => c.proposed).map((c) => c.alias)), [components]);
|
|
1135
1133
|
// Diagnostics view in the sidebar. Uncontrolled unless the host pins
|
|
@@ -1190,7 +1188,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1190
1188
|
const [focusedBoundaryKey, setFocusedBoundaryKey] = useState(null);
|
|
1191
1189
|
const onNodeDoubleClick = useCallback((_e, node) => {
|
|
1192
1190
|
// Boundary frame → frame its members on the canvas; double-click again to
|
|
1193
|
-
// zoom back out. User-initiated, so it ignores the
|
|
1191
|
+
// zoom back out. User-initiated, so it ignores the trail zoom gate.
|
|
1194
1192
|
if (node.type === 'subsystem-group') {
|
|
1195
1193
|
const key = node.data?.region?.key;
|
|
1196
1194
|
if (!key)
|
|
@@ -1225,7 +1223,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1225
1223
|
const startLine = comp.declarationRef?.startLine;
|
|
1226
1224
|
onOpenDeclarationFile(comp.file, startLine != null ? { startLine } : undefined);
|
|
1227
1225
|
}, [onOpenDeclarationFile, fitView, focusedBoundaryKey]);
|
|
1228
|
-
const
|
|
1226
|
+
const onOpenFileFromTrail = useCallback((file, opts) => {
|
|
1229
1227
|
setFileOverlay({ file, startLine: opts?.startLine });
|
|
1230
1228
|
onFileSelect?.(file);
|
|
1231
1229
|
}, [onFileSelect]);
|
|
@@ -1239,14 +1237,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1239
1237
|
const drawerFillHeight = drawerTarget?.kind === 'file' && drawerTarget.startLine == null;
|
|
1240
1238
|
const fileOverlayOpen = fileOverlay != null;
|
|
1241
1239
|
const collapseCanvas = drawerFillHeight || fileOverlayOpen;
|
|
1242
|
-
// Drop the full-file overlay when the underlying
|
|
1240
|
+
// Drop the full-file overlay when the underlying trail drawer changes
|
|
1243
1241
|
// or closes — open-file keeps the same drawerTarget so the snippets stay mounted.
|
|
1244
|
-
const
|
|
1245
|
-
? `${drawerTarget.
|
|
1242
|
+
const trailDrawerKey = drawerTarget?.kind === 'trail'
|
|
1243
|
+
? `${drawerTarget.trailId}:${drawerTarget.stepIndex}`
|
|
1246
1244
|
: null;
|
|
1247
1245
|
useEffect(() => {
|
|
1248
1246
|
setFileOverlay(null);
|
|
1249
|
-
}, [
|
|
1247
|
+
}, [trailDrawerKey]);
|
|
1250
1248
|
useEffect(() => {
|
|
1251
1249
|
if (!fileOverlayOpen)
|
|
1252
1250
|
return;
|
|
@@ -1257,15 +1255,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1257
1255
|
window.addEventListener('keydown', onKey);
|
|
1258
1256
|
return () => window.removeEventListener('keydown', onKey);
|
|
1259
1257
|
}, [fileOverlayOpen, closeFileOverlay]);
|
|
1260
|
-
// Camera helper shared by the
|
|
1258
|
+
// Camera helper shared by the trail interactions. Fits the union of
|
|
1261
1259
|
// the focused edges' endpoint rects AND their routed waypoints — the edge
|
|
1262
1260
|
// carries its full polyline (`elkPathPoints`, absolute flow coords), so the
|
|
1263
|
-
// frame is the line the
|
|
1261
|
+
// frame is the line the step actually traces. That keeps a step whose route
|
|
1264
1262
|
// bulges out around intervening nodes from being clipped at the viewport
|
|
1265
1263
|
// edge, and it is the edge's own geometry doing the guiding, not fudge
|
|
1266
1264
|
// padding.
|
|
1267
1265
|
const fitFocusBounds = useCallback((ids) => {
|
|
1268
|
-
if (!
|
|
1266
|
+
if (!zoomOnTrailFocus)
|
|
1269
1267
|
return;
|
|
1270
1268
|
const nodeIds = new Set();
|
|
1271
1269
|
const points = [];
|
|
@@ -1315,14 +1313,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1315
1313
|
};
|
|
1316
1314
|
fitBounds(bounds, {
|
|
1317
1315
|
padding: 0.15,
|
|
1318
|
-
duration:
|
|
1316
|
+
duration: trailFocusDurationMs,
|
|
1319
1317
|
});
|
|
1320
1318
|
}, [
|
|
1321
1319
|
baseEdges,
|
|
1322
1320
|
built.absoluteRects,
|
|
1323
1321
|
fitBounds,
|
|
1324
|
-
|
|
1325
|
-
|
|
1322
|
+
zoomOnTrailFocus,
|
|
1323
|
+
trailFocusDurationMs,
|
|
1326
1324
|
]);
|
|
1327
1325
|
// A node's laid-out rect in absolute flow coords. `absoluteRects` is the
|
|
1328
1326
|
// authority; the fallback covers an unparented group node, whose `position` is
|
|
@@ -1368,12 +1366,12 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1368
1366
|
y: minY,
|
|
1369
1367
|
width: Math.max(1, maxX - minX),
|
|
1370
1368
|
height: Math.max(1, maxY - minY),
|
|
1371
|
-
}, { padding, duration:
|
|
1372
|
-
}, [rectForNode, fitBounds,
|
|
1373
|
-
// 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
|
|
1374
1372
|
// closes (visibility restores non-flow nodes that were hidden).
|
|
1375
1373
|
const fitOverview = useCallback(() => {
|
|
1376
|
-
if (!
|
|
1374
|
+
if (!zoomOnTrailFocus)
|
|
1377
1375
|
return;
|
|
1378
1376
|
fitView({
|
|
1379
1377
|
padding: 0.1,
|
|
@@ -1382,109 +1380,109 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1382
1380
|
maxZoom: 2,
|
|
1383
1381
|
duration: 300,
|
|
1384
1382
|
});
|
|
1385
|
-
}, [fitView,
|
|
1383
|
+
}, [fitView, zoomOnTrailFocus]);
|
|
1386
1384
|
// Focus an entire flow: hide everything but the flow's nodes and edges, and
|
|
1387
1385
|
// frame the flow on the canvas. Selection state is cleared — the graph now
|
|
1388
1386
|
// reads as the narrative. No drawer: the code view only opens on a step
|
|
1389
|
-
// click. A stale
|
|
1387
|
+
// click. A stale trail drawer (from a previously focused flow's step)
|
|
1390
1388
|
// closes; an explicitly opened file drawer stays.
|
|
1391
1389
|
// In `dim` mode: clear step highlight and leave the full graph visible
|
|
1392
1390
|
// (no zoom / hide) — matching "step away from a hovered step".
|
|
1393
|
-
const
|
|
1391
|
+
const focusTrailEdges = useCallback((tl) => {
|
|
1394
1392
|
setSelected(null);
|
|
1395
1393
|
setSelectedEdgeId(null);
|
|
1396
|
-
|
|
1397
|
-
if (
|
|
1394
|
+
setHoveredTrailStep(null);
|
|
1395
|
+
if (trailStepMode === 'dim') {
|
|
1398
1396
|
setFocusedStepIndex(null);
|
|
1399
|
-
|
|
1400
|
-
setDrawerTarget((prev) => (prev?.kind === '
|
|
1397
|
+
setFocusedTrailId(null);
|
|
1398
|
+
setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
|
|
1401
1399
|
return;
|
|
1402
1400
|
}
|
|
1403
1401
|
setFocusedStepIndex(null);
|
|
1404
|
-
|
|
1405
|
-
fitFocusBounds(new Set(tl.steps.map((s) =>
|
|
1406
|
-
setDrawerTarget((prev) => (prev?.kind === '
|
|
1407
|
-
}, [fitFocusBounds,
|
|
1408
|
-
const
|
|
1409
|
-
|
|
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);
|
|
1410
1408
|
setFocusedStepIndex(null);
|
|
1411
|
-
|
|
1412
|
-
setDrawerTarget((prev) => (prev?.kind === '
|
|
1409
|
+
setHoveredTrailStep(null);
|
|
1410
|
+
setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
|
|
1413
1411
|
}, []);
|
|
1414
|
-
// Host deep-link: opening the graph from a
|
|
1412
|
+
// Host deep-link: opening the graph from a trail row selects that
|
|
1415
1413
|
// flow — expand its steps, focus its edges, and switch the sidebar to
|
|
1416
|
-
//
|
|
1414
|
+
// Trails. Guarded by a ref so a later in-tab selection isn't yanked
|
|
1417
1415
|
// back; a remount (or a new id) re-applies it.
|
|
1418
|
-
const
|
|
1416
|
+
const appliedInitialTrailRef = useRef(null);
|
|
1419
1417
|
useEffect(() => {
|
|
1420
|
-
if (!
|
|
1418
|
+
if (!initialTrailId)
|
|
1421
1419
|
return;
|
|
1422
|
-
if (
|
|
1420
|
+
if (appliedInitialTrailRef.current === initialTrailId)
|
|
1423
1421
|
return;
|
|
1424
|
-
if (!layoutReady || !
|
|
1422
|
+
if (!layoutReady || !trails?.length)
|
|
1425
1423
|
return;
|
|
1426
|
-
const tl =
|
|
1424
|
+
const tl = trails.find((t) => t.id === initialTrailId);
|
|
1427
1425
|
if (!tl)
|
|
1428
1426
|
return;
|
|
1429
|
-
|
|
1430
|
-
setSidebarView('
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
}, [
|
|
1427
|
+
appliedInitialTrailRef.current = initialTrailId;
|
|
1428
|
+
setSidebarView('trails');
|
|
1429
|
+
setExpandedTrails((prev) => new Set(prev).add(tl.id));
|
|
1430
|
+
focusTrailEdges(tl);
|
|
1431
|
+
}, [initialTrailId, layoutReady, trails, focusTrailEdges]);
|
|
1434
1432
|
// Switching sidebar panels also switches the edge vocabulary. Leaving the
|
|
1435
|
-
//
|
|
1433
|
+
// Trails panel drops its canvas state (focus, expanded flows, selected
|
|
1436
1434
|
// edge) so the Files view's topology edges aren't gated by a flow the user
|
|
1437
1435
|
// can no longer see or un-dim.
|
|
1438
1436
|
const changeSidebarView = useCallback((view) => {
|
|
1439
1437
|
setSidebarView(view);
|
|
1440
|
-
if (view === '
|
|
1438
|
+
if (view === 'trails')
|
|
1441
1439
|
return;
|
|
1442
|
-
|
|
1443
|
-
|
|
1440
|
+
clearTrailFocus();
|
|
1441
|
+
setExpandedTrails(new Set());
|
|
1444
1442
|
setSelectedEdgeId(null);
|
|
1445
|
-
}, [
|
|
1446
|
-
// 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
|
|
1447
1445
|
// drawer to that step's snippet. Clicking the already-focused step clears
|
|
1448
1446
|
// step focus and returns to whole-flow (high-level) framing.
|
|
1449
1447
|
// In `dim` mode: only dim non-participants (same as hovering a step) —
|
|
1450
1448
|
// no camera move, no drawer, no hide.
|
|
1451
|
-
const
|
|
1449
|
+
const focusTrailStep = useCallback((tl, stepIndex) => {
|
|
1452
1450
|
const step = tl.steps[stepIndex];
|
|
1453
1451
|
if (!step)
|
|
1454
1452
|
return;
|
|
1455
1453
|
setSelected(null);
|
|
1456
1454
|
setSelectedEdgeId(null);
|
|
1457
|
-
if (
|
|
1455
|
+
if (trailStepMode === 'dim') {
|
|
1458
1456
|
setFocusedStepIndex(null);
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
setDrawerTarget((prev) => (prev?.kind === '
|
|
1457
|
+
setFocusedTrailId(null);
|
|
1458
|
+
setHoveredTrailStep({ trailId: tl.id, stepIndex });
|
|
1459
|
+
setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
|
|
1462
1460
|
return;
|
|
1463
1461
|
}
|
|
1464
1462
|
// Toggle: clicking the selected step unselects it (back to whole flow).
|
|
1465
|
-
if (
|
|
1463
|
+
if (focusedTrailId === tl.id && focusedStepIndex === stepIndex) {
|
|
1466
1464
|
setFocusedStepIndex(null);
|
|
1467
1465
|
// Pointer is still over the row — keep hover preview so dimming doesn't
|
|
1468
1466
|
// flash off until mouseleave.
|
|
1469
|
-
|
|
1470
|
-
setDrawerTarget((prev) => (prev?.kind === '
|
|
1467
|
+
setHoveredTrailStep({ trailId: tl.id, stepIndex });
|
|
1468
|
+
setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
|
|
1471
1469
|
if (pendingFocusFitRef.current != null) {
|
|
1472
1470
|
window.clearTimeout(pendingFocusFitRef.current);
|
|
1473
1471
|
pendingFocusFitRef.current = null;
|
|
1474
1472
|
}
|
|
1475
|
-
fitFocusBounds(new Set(tl.steps.map((s) =>
|
|
1473
|
+
fitFocusBounds(new Set(tl.steps.map((s) => trailStepGraphEdgeId(s))));
|
|
1476
1474
|
return;
|
|
1477
1475
|
}
|
|
1478
1476
|
setFocusedStepIndex(stepIndex);
|
|
1479
|
-
|
|
1480
|
-
|
|
1477
|
+
setFocusedTrailId(tl.id);
|
|
1478
|
+
setHoveredTrailStep(null);
|
|
1481
1479
|
// Open the drawer before fitting. If it was closed, wait for its height
|
|
1482
1480
|
// transition so fitView uses the reduced canvas — not full height.
|
|
1483
1481
|
const drawerWasOpen = drawerOpenRef.current;
|
|
1484
|
-
if (
|
|
1482
|
+
if (renderTrailViewer) {
|
|
1485
1483
|
setDrawerTarget({
|
|
1486
|
-
kind: '
|
|
1487
|
-
|
|
1484
|
+
kind: 'trail',
|
|
1485
|
+
trailId: tl.id,
|
|
1488
1486
|
stepIndex,
|
|
1489
1487
|
});
|
|
1490
1488
|
}
|
|
@@ -1495,7 +1493,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1495
1493
|
startLine: step.line,
|
|
1496
1494
|
});
|
|
1497
1495
|
}
|
|
1498
|
-
const edgeIds = new Set([
|
|
1496
|
+
const edgeIds = new Set([trailStepGraphEdgeId(step)]);
|
|
1499
1497
|
if (pendingFocusFitRef.current != null) {
|
|
1500
1498
|
window.clearTimeout(pendingFocusFitRef.current);
|
|
1501
1499
|
pendingFocusFitRef.current = null;
|
|
@@ -1512,9 +1510,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1512
1510
|
}, [
|
|
1513
1511
|
fitFocusBounds,
|
|
1514
1512
|
focusedStepIndex,
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1513
|
+
focusedTrailId,
|
|
1514
|
+
renderTrailViewer,
|
|
1515
|
+
trailStepMode,
|
|
1518
1516
|
]);
|
|
1519
1517
|
useEffect(() => {
|
|
1520
1518
|
return () => {
|
|
@@ -1532,11 +1530,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1532
1530
|
useEffect(() => {
|
|
1533
1531
|
const prev = prevPreviewEdgeIdsRef.current;
|
|
1534
1532
|
prevPreviewEdgeIdsRef.current = previewEdgeIds;
|
|
1535
|
-
if (!
|
|
1533
|
+
if (!zoomOnTrailFocus || trailStepMode === 'dim')
|
|
1536
1534
|
return;
|
|
1537
1535
|
// Camera follows hover only when a specific step is already focused (or
|
|
1538
1536
|
// autoplay is driving an auto-focus).
|
|
1539
|
-
if (!autoPlayFocus && (
|
|
1537
|
+
if (!autoPlayFocus && (focusedTrailId == null || focusedStepIndex == null)) {
|
|
1540
1538
|
return;
|
|
1541
1539
|
}
|
|
1542
1540
|
if (previewEdgeIds) {
|
|
@@ -1549,27 +1547,27 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1549
1547
|
}, [
|
|
1550
1548
|
previewEdgeIds,
|
|
1551
1549
|
focusEdgeIds,
|
|
1552
|
-
|
|
1550
|
+
focusedTrailId,
|
|
1553
1551
|
focusedStepIndex,
|
|
1554
1552
|
autoPlayFocus,
|
|
1555
1553
|
fitFocusBounds,
|
|
1556
|
-
|
|
1557
|
-
|
|
1554
|
+
zoomOnTrailFocus,
|
|
1555
|
+
trailStepMode,
|
|
1558
1556
|
]);
|
|
1559
|
-
// Graph-only embeds: cycle
|
|
1560
|
-
// 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
|
|
1561
1559
|
// viewer is supplied, its snippet drawer opens; in `dim` mode it only
|
|
1562
1560
|
// dim-highlights the step (hover-style) with no camera move.
|
|
1563
1561
|
useEffect(() => {
|
|
1564
|
-
if (!
|
|
1562
|
+
if (!autoPlayTrails || trails == null || trails.length === 0)
|
|
1565
1563
|
return;
|
|
1566
1564
|
if (!layoutReady)
|
|
1567
1565
|
return;
|
|
1568
|
-
const playable =
|
|
1566
|
+
const playable = trails.filter((tl) => tl.steps.length > 0);
|
|
1569
1567
|
if (playable.length === 0)
|
|
1570
1568
|
return;
|
|
1571
1569
|
// Only run the cycles that can actually be shown in the current edge view.
|
|
1572
|
-
const inView = playable.filter((tl) => tl.steps.some((s) =>
|
|
1570
|
+
const inView = playable.filter((tl) => tl.steps.some((s) => isTrailMechanism(s.mechanism)));
|
|
1573
1571
|
if (inView.length === 0)
|
|
1574
1572
|
return;
|
|
1575
1573
|
const cyc = inView;
|
|
@@ -1577,8 +1575,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1577
1575
|
let tlIdx = 0;
|
|
1578
1576
|
let stepIdx = 0;
|
|
1579
1577
|
let timer = null;
|
|
1580
|
-
const interval = Math.max(400,
|
|
1581
|
-
const focusMode =
|
|
1578
|
+
const interval = Math.max(400, trailAutoPlayIntervalMs);
|
|
1579
|
+
const focusMode = trailStepMode !== 'dim';
|
|
1582
1580
|
let cleanup = () => { };
|
|
1583
1581
|
const tick = () => {
|
|
1584
1582
|
if (cancelled)
|
|
@@ -1587,15 +1585,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1587
1585
|
setSelected(null);
|
|
1588
1586
|
setSelectedEdgeId(null);
|
|
1589
1587
|
if (focusMode) {
|
|
1590
|
-
|
|
1591
|
-
|
|
1588
|
+
setHoveredTrailStep(null);
|
|
1589
|
+
setFocusedTrailId(null);
|
|
1592
1590
|
setFocusedStepIndex(null);
|
|
1593
|
-
setAutoPlayFocus({
|
|
1591
|
+
setAutoPlayFocus({ trailId: tl.id, stepIndex: stepIdx });
|
|
1594
1592
|
if (!hideDrawer) {
|
|
1595
1593
|
const step = tl.steps[stepIdx];
|
|
1596
1594
|
if (step) {
|
|
1597
|
-
if (
|
|
1598
|
-
setDrawerTarget({ kind: '
|
|
1595
|
+
if (renderTrailViewer) {
|
|
1596
|
+
setDrawerTarget({ kind: 'trail', trailId: tl.id, stepIndex: stepIdx });
|
|
1599
1597
|
}
|
|
1600
1598
|
else {
|
|
1601
1599
|
setDrawerTarget({ kind: 'file', file: step.file, startLine: step.line });
|
|
@@ -1605,9 +1603,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1605
1603
|
}
|
|
1606
1604
|
else {
|
|
1607
1605
|
setAutoPlayFocus(null);
|
|
1608
|
-
|
|
1606
|
+
setFocusedTrailId(null);
|
|
1609
1607
|
setFocusedStepIndex(null);
|
|
1610
|
-
|
|
1608
|
+
setHoveredTrailStep({ trailId: tl.id, stepIndex: stepIdx });
|
|
1611
1609
|
}
|
|
1612
1610
|
stepIdx += 1;
|
|
1613
1611
|
if (stepIdx >= tl.steps.length) {
|
|
@@ -1621,28 +1619,28 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1621
1619
|
cancelled = true;
|
|
1622
1620
|
if (timer != null)
|
|
1623
1621
|
window.clearTimeout(timer);
|
|
1624
|
-
|
|
1625
|
-
|
|
1622
|
+
setHoveredTrailStep(null);
|
|
1623
|
+
setFocusedTrailId(null);
|
|
1626
1624
|
setFocusedStepIndex(null);
|
|
1627
1625
|
setAutoPlayFocus(null);
|
|
1628
1626
|
};
|
|
1629
1627
|
return cleanup;
|
|
1630
1628
|
}, [
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1629
|
+
autoPlayTrails,
|
|
1630
|
+
trails,
|
|
1631
|
+
trailAutoPlayIntervalMs,
|
|
1634
1632
|
layoutReady,
|
|
1635
|
-
|
|
1636
|
-
|
|
1633
|
+
trailStepMode,
|
|
1634
|
+
renderTrailViewer,
|
|
1637
1635
|
hideDrawer,
|
|
1638
1636
|
]);
|
|
1639
|
-
// Arrow keys step through the focused
|
|
1637
|
+
// Arrow keys step through the focused trail once a step is active
|
|
1640
1638
|
// (sidebar click or drawer open). Ignores typing targets and chords.
|
|
1641
1639
|
useEffect(() => {
|
|
1642
|
-
if (
|
|
1640
|
+
if (focusedTrailId == null || focusedStepIndex == null || !trails) {
|
|
1643
1641
|
return;
|
|
1644
1642
|
}
|
|
1645
|
-
const tl =
|
|
1643
|
+
const tl = trails.find((t) => t.id === focusedTrailId);
|
|
1646
1644
|
if (!tl || tl.steps.length === 0)
|
|
1647
1645
|
return;
|
|
1648
1646
|
const onKeyDown = (e) => {
|
|
@@ -1669,19 +1667,19 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1669
1667
|
if (next === focusedStepIndex)
|
|
1670
1668
|
return;
|
|
1671
1669
|
e.preventDefault();
|
|
1672
|
-
|
|
1670
|
+
focusTrailStep(tl, next);
|
|
1673
1671
|
};
|
|
1674
1672
|
window.addEventListener('keydown', onKeyDown);
|
|
1675
1673
|
return () => window.removeEventListener('keydown', onKeyDown);
|
|
1676
1674
|
}, [
|
|
1677
|
-
|
|
1675
|
+
focusedTrailId,
|
|
1678
1676
|
focusedStepIndex,
|
|
1679
|
-
|
|
1680
|
-
|
|
1677
|
+
trails,
|
|
1678
|
+
focusTrailStep,
|
|
1681
1679
|
]);
|
|
1682
|
-
const
|
|
1683
|
-
const collapsingLast =
|
|
1684
|
-
|
|
1680
|
+
const toggleTrailCollapsed = useCallback((tlId) => {
|
|
1681
|
+
const collapsingLast = expandedTrails.has(tlId) && expandedTrails.size === 1;
|
|
1682
|
+
setExpandedTrails((prev) => {
|
|
1685
1683
|
const next = new Set(prev);
|
|
1686
1684
|
if (next.has(tlId))
|
|
1687
1685
|
next.delete(tlId);
|
|
@@ -1689,7 +1687,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1689
1687
|
next.add(tlId);
|
|
1690
1688
|
return next;
|
|
1691
1689
|
});
|
|
1692
|
-
if (!collapsingLast || !
|
|
1690
|
+
if (!collapsingLast || !zoomOnTrailFocus)
|
|
1693
1691
|
return;
|
|
1694
1692
|
// Cancel any pending focus fit so it doesn't fight the overview zoom.
|
|
1695
1693
|
if (pendingFocusFitRef.current != null) {
|
|
@@ -1704,7 +1702,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1704
1702
|
pendingFocusFitRef.current = null;
|
|
1705
1703
|
requestAnimationFrame(() => fitOverview());
|
|
1706
1704
|
}, drawerWasOpen ? FILE_DRAWER_HEIGHT_MS + 20 : 20);
|
|
1707
|
-
}, [
|
|
1705
|
+
}, [expandedTrails, fitOverview, zoomOnTrailFocus]);
|
|
1708
1706
|
// Detail-panel links: related-name clicks select the matching component —
|
|
1709
1707
|
// resolved by alias, name, or symbol (call labels may carry a trailing `()`).
|
|
1710
1708
|
const resolveRelatedComponent = useCallback((ref) => {
|
|
@@ -1717,7 +1715,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1717
1715
|
return;
|
|
1718
1716
|
setSelected(comp);
|
|
1719
1717
|
setSelectedEdgeId(null);
|
|
1720
|
-
|
|
1718
|
+
setFocusedTrailId(null);
|
|
1721
1719
|
setFocusedStepIndex(null);
|
|
1722
1720
|
onSelect?.(comp.alias);
|
|
1723
1721
|
}, [components, onSelect]);
|
|
@@ -1732,9 +1730,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1732
1730
|
if (comp) {
|
|
1733
1731
|
setSelected(comp);
|
|
1734
1732
|
setSelectedEdgeId(null);
|
|
1735
|
-
|
|
1733
|
+
setFocusedTrailId(null);
|
|
1736
1734
|
setFocusedStepIndex(null);
|
|
1737
|
-
|
|
1735
|
+
setHoveredTrailStep(null);
|
|
1738
1736
|
issueModuleFocusRef.current = null;
|
|
1739
1737
|
onSelect?.(comp.alias);
|
|
1740
1738
|
requestAnimationFrame(() => {
|
|
@@ -1749,11 +1747,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1749
1747
|
}
|
|
1750
1748
|
else if (step) {
|
|
1751
1749
|
// A step finding focuses the step itself, through the same entry point
|
|
1752
|
-
// the
|
|
1750
|
+
// the trail panel uses — so the drawer, the step numbering, the
|
|
1753
1751
|
// dim-mode hover preview, and the camera all behave identically whether
|
|
1754
1752
|
// the step was reached from the panel or from a diagnostics card.
|
|
1755
1753
|
issueModuleFocusRef.current = null;
|
|
1756
|
-
|
|
1754
|
+
focusTrailStep(step.trail, step.stepIndex);
|
|
1757
1755
|
}
|
|
1758
1756
|
else {
|
|
1759
1757
|
// Nothing selectable — but a module target names a boundary frame, so
|
|
@@ -1762,9 +1760,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1762
1760
|
if (moduleNodeId) {
|
|
1763
1761
|
setSelected(null);
|
|
1764
1762
|
setSelectedEdgeId(null);
|
|
1765
|
-
|
|
1763
|
+
setFocusedTrailId(null);
|
|
1766
1764
|
setFocusedStepIndex(null);
|
|
1767
|
-
|
|
1765
|
+
setHoveredTrailStep(null);
|
|
1768
1766
|
issueModuleFocusRef.current = moduleNodeId;
|
|
1769
1767
|
requestAnimationFrame(() => {
|
|
1770
1768
|
fitNodeRects(new Set([moduleNodeId]), 0.4);
|
|
@@ -1776,7 +1774,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1776
1774
|
issueComponent,
|
|
1777
1775
|
issueStep,
|
|
1778
1776
|
issueModuleNodeId,
|
|
1779
|
-
|
|
1777
|
+
focusTrailStep,
|
|
1780
1778
|
onSelect,
|
|
1781
1779
|
onSelectIssue,
|
|
1782
1780
|
fitView,
|
|
@@ -1804,13 +1802,13 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1804
1802
|
// keeps its step focus in hover state instead (owned by the pointer,
|
|
1805
1803
|
// not the card), and correctly leaves that alone here.
|
|
1806
1804
|
const owned = focusedStepRef.current;
|
|
1807
|
-
if (owned?.
|
|
1805
|
+
if (owned?.trailId !== step.trail.id ||
|
|
1808
1806
|
owned?.stepIndex !== step.stepIndex) {
|
|
1809
1807
|
return;
|
|
1810
1808
|
}
|
|
1811
|
-
|
|
1809
|
+
setFocusedTrailId(null);
|
|
1812
1810
|
setFocusedStepIndex(null);
|
|
1813
|
-
setDrawerTarget((prev) => (prev?.kind === '
|
|
1811
|
+
setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
|
|
1814
1812
|
}
|
|
1815
1813
|
else if (moduleNodeId) {
|
|
1816
1814
|
// Only unwind framing this card still owns.
|
|
@@ -1826,14 +1824,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1826
1824
|
fitView({ padding: 0.1, duration: 300, minZoom: 0.05, maxZoom: 2 });
|
|
1827
1825
|
});
|
|
1828
1826
|
}, [issueComponent, issueStep, issueModuleNodeId, fitView]);
|
|
1829
|
-
// Construct tokens inside a
|
|
1827
|
+
// Construct tokens inside a trail snippet. A step's line is an edge
|
|
1830
1828
|
// between its `from`/`to` components, so a token naming either of those
|
|
1831
1829
|
// constructs should navigate to that construct's declaration. Index the
|
|
1832
|
-
// matchable identifiers per step (`
|
|
1833
|
-
const
|
|
1830
|
+
// matchable identifiers per step (`trailId:index`).
|
|
1831
|
+
const trailSymbolIndex = useMemo(() => {
|
|
1834
1832
|
const byAlias = new Map(components.map((c) => [c.alias, c]));
|
|
1835
1833
|
const index = new Map();
|
|
1836
|
-
for (const wt of
|
|
1834
|
+
for (const wt of trails ?? []) {
|
|
1837
1835
|
wt.steps.forEach((step, i) => {
|
|
1838
1836
|
const tokens = new Map();
|
|
1839
1837
|
for (const alias of [step.from, step.to]) {
|
|
@@ -1847,17 +1845,17 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1847
1845
|
});
|
|
1848
1846
|
}
|
|
1849
1847
|
return index;
|
|
1850
|
-
}, [components,
|
|
1851
|
-
const
|
|
1852
|
-
|
|
1853
|
-
// Stable resolver: reads the live index + focused
|
|
1854
|
-
// the memoized
|
|
1855
|
-
const
|
|
1856
|
-
const
|
|
1857
|
-
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)
|
|
1858
1856
|
return null;
|
|
1859
|
-
return (
|
|
1860
|
-
.get(`${
|
|
1857
|
+
return (trailSymbolIndexRef.current
|
|
1858
|
+
.get(`${trailId}:${query.stepIndex}`)
|
|
1861
1859
|
?.get(query.tokenText) ?? null);
|
|
1862
1860
|
}, []);
|
|
1863
1861
|
// A construct token click navigates to that construct's declaration: open
|
|
@@ -1920,10 +1918,10 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1920
1918
|
const fileViewerRef = useRef(fileViewer);
|
|
1921
1919
|
fileViewerRef.current = fileViewer;
|
|
1922
1920
|
const renderDrawerContent = useCallback((file, opts) => fileViewerRef.current?.(file, opts) ?? null, []);
|
|
1923
|
-
const
|
|
1924
|
-
|
|
1925
|
-
const
|
|
1926
|
-
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: {
|
|
1927
1925
|
width: sidebarWidth,
|
|
1928
1926
|
minWidth: sidebarWidth,
|
|
1929
1927
|
borderRight: `1px solid ${theme.colors.border}`,
|
|
@@ -1969,7 +1967,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1969
1967
|
color: descriptionVisible || descToggleHover ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
|
|
1970
1968
|
cursor: 'pointer',
|
|
1971
1969
|
transition: 'background 120ms ease, color 120ms ease',
|
|
1972
|
-
}, 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: {
|
|
1973
1971
|
...(showDesc ? { height: '50%' } : { flex: 1, minHeight: 0 }),
|
|
1974
1972
|
minHeight: 160,
|
|
1975
1973
|
flexShrink: 0,
|
|
@@ -1995,13 +1993,13 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
1995
1993
|
fontFamily: theme.fonts.monospace,
|
|
1996
1994
|
textTransform: 'capitalize',
|
|
1997
1995
|
cursor: 'pointer',
|
|
1998
|
-
}, 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: {
|
|
1999
1997
|
display: 'flex',
|
|
2000
1998
|
width: '100%',
|
|
2001
1999
|
flexShrink: 0,
|
|
2002
2000
|
borderBottom: `1px solid ${theme.colors.border}`,
|
|
2003
2001
|
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
2004
|
-
}, children: ['
|
|
2002
|
+
}, children: ['trails', 'files'].map((view) => (_jsx("button", { type: "button", role: "tab", "aria-selected": sidebarView === view, onClick: () => changeSidebarView(view), style: {
|
|
2005
2003
|
flex: 1,
|
|
2006
2004
|
minWidth: 0,
|
|
2007
2005
|
padding: '8px 8px',
|
|
@@ -2015,7 +2013,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2015
2013
|
fontFamily: theme.fonts.monospace,
|
|
2016
2014
|
textTransform: 'capitalize',
|
|
2017
2015
|
cursor: 'pointer',
|
|
2018
|
-
}, 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) => {
|
|
2019
2017
|
const groupKey = group.repoKey ?? '__no-repo__';
|
|
2020
2018
|
const collapsed = collapsedRepos.has(groupKey);
|
|
2021
2019
|
return (_jsxs("div", { style: {
|
|
@@ -2025,7 +2023,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2025
2023
|
flexDirection: 'column',
|
|
2026
2024
|
borderTop: i > 0 ? `1px solid ${theme.colors.border}` : undefined,
|
|
2027
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));
|
|
2028
|
-
}) })) : 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: {
|
|
2029
2027
|
width: 3,
|
|
2030
2028
|
flexShrink: 0,
|
|
2031
2029
|
cursor: 'col-resize',
|
|
@@ -2118,7 +2116,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2118
2116
|
flex: 1,
|
|
2119
2117
|
minHeight: 0,
|
|
2120
2118
|
background: theme.colors.background,
|
|
2121
|
-
}, children: _jsx(GraphChrome, {}) }, `${baseNodesKey}-${baseEdgesKey}`), (graphTitle ||
|
|
2119
|
+
}, children: _jsx(GraphChrome, {}) }, `${baseNodesKey}-${baseEdgesKey}`), (graphTitle || overlayTrailTitle || overlayTrailStep) && (_jsxs("div", { style: {
|
|
2122
2120
|
position: 'absolute',
|
|
2123
2121
|
top: 10,
|
|
2124
2122
|
left: '50%',
|
|
@@ -2144,9 +2142,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2144
2142
|
border: `1px solid ${theme.colors.border}`,
|
|
2145
2143
|
borderRadius: 6,
|
|
2146
2144
|
boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
|
|
2147
|
-
}, children: graphTitle })),
|
|
2145
|
+
}, children: graphTitle })), overlayTrailTitle && (_jsxs("div", { style: {
|
|
2148
2146
|
maxWidth: '100%',
|
|
2149
|
-
minWidth:
|
|
2147
|
+
minWidth: overlayTrailStep ? 160 : undefined,
|
|
2150
2148
|
display: 'flex',
|
|
2151
2149
|
flexDirection: 'column',
|
|
2152
2150
|
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
@@ -2155,9 +2153,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2155
2153
|
boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
|
|
2156
2154
|
overflow: 'hidden',
|
|
2157
2155
|
opacity: 0.95,
|
|
2158
|
-
}, "aria-label":
|
|
2159
|
-
? `${
|
|
2160
|
-
:
|
|
2156
|
+
}, "aria-label": overlayTrailStep
|
|
2157
|
+
? `${overlayTrailTitle}, step ${overlayTrailStep.index} of ${overlayTrailStep.total}`
|
|
2158
|
+
: overlayTrailTitle, children: [_jsx("div", { style: {
|
|
2161
2159
|
padding: '5px 14px',
|
|
2162
2160
|
fontSize: theme.fontSizes[2] ?? theme.fontSizes[1],
|
|
2163
2161
|
fontWeight: 600,
|
|
@@ -2167,14 +2165,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2167
2165
|
textOverflow: 'ellipsis',
|
|
2168
2166
|
whiteSpace: 'nowrap',
|
|
2169
2167
|
textAlign: 'center',
|
|
2170
|
-
}, children:
|
|
2168
|
+
}, children: overlayTrailTitle }), overlayTrailStep && overlayTrailStep.total > 0 && (_jsx("div", { style: {
|
|
2171
2169
|
display: 'flex',
|
|
2172
2170
|
gap: 3,
|
|
2173
2171
|
padding: '0 6px 5px',
|
|
2174
|
-
}, "aria-hidden": "true", children: Array.from({ length:
|
|
2172
|
+
}, "aria-hidden": "true", children: Array.from({ length: overlayTrailStep.total }, (_, i) => {
|
|
2175
2173
|
const n = i + 1;
|
|
2176
|
-
const active = n ===
|
|
2177
|
-
const done = n <
|
|
2174
|
+
const active = n === overlayTrailStep.index;
|
|
2175
|
+
const done = n < overlayTrailStep.index;
|
|
2178
2176
|
return (_jsx("span", { style: {
|
|
2179
2177
|
flex: 1,
|
|
2180
2178
|
height: 2,
|
|
@@ -2187,7 +2185,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2187
2185
|
opacity: active ? 1 : done ? 0.75 : 0.4,
|
|
2188
2186
|
transition: 'opacity 120ms ease, background 120ms ease',
|
|
2189
2187
|
} }, n));
|
|
2190
|
-
}) }))] })),
|
|
2188
|
+
}) }))] })), overlayTrailStep?.annotation && (_jsx("div", { style: {
|
|
2191
2189
|
maxWidth: '100%',
|
|
2192
2190
|
padding: '6px 14px',
|
|
2193
2191
|
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
@@ -2199,7 +2197,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2199
2197
|
fontFamily: theme.fonts.body,
|
|
2200
2198
|
color: theme.colors.textMuted ?? theme.colors.textSecondary,
|
|
2201
2199
|
lineHeight: 1.35,
|
|
2202
|
-
}, children:
|
|
2200
|
+
}, children: overlayTrailStep.annotation }))] })), selected && (_jsx("div", { style: {
|
|
2203
2201
|
position: 'absolute',
|
|
2204
2202
|
top: 10,
|
|
2205
2203
|
right: 10,
|
|
@@ -2224,9 +2222,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
|
|
|
2224
2222
|
drawerTarget.startLine != null &&
|
|
2225
2223
|
!!selected.file &&
|
|
2226
2224
|
selected.file === drawerTarget.file &&
|
|
2227
|
-
selected.declarationRef?.startLine === drawerTarget.startLine }) }))] }), _jsx(FileDrawer, { title: drawerTitle, onClose: closeDrawer, fillHeight: drawerFillHeight, suppressEscape: fileOverlayOpen, hidden: hideDrawer, children: drawerTarget?.kind === '
|
|
2228
|
-
|
|
2229
|
-
|
|
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: {
|
|
2230
2228
|
position: 'absolute',
|
|
2231
2229
|
inset: 0,
|
|
2232
2230
|
zIndex: 8,
|
|
@@ -2313,7 +2311,7 @@ export function SubsystemComponentGraph(props) {
|
|
|
2313
2311
|
}, []);
|
|
2314
2312
|
const constructsOnly = isConstructsOnlyModel({
|
|
2315
2313
|
components: props.components,
|
|
2316
|
-
|
|
2314
|
+
trails: props.trails,
|
|
2317
2315
|
graphifyRelations: props.graphifyRelations,
|
|
2318
2316
|
});
|
|
2319
2317
|
return (_jsx("div", { ref: wrapRef, style: {
|