From 23174b206aef1cd1f86070178c37d79360740c46 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Sat, 29 Aug 2026 04:11:58 +0200 Subject: [PATCH] FE-1509: Surface cycles and initial places across the notebook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cell rows gain a tinted ↻ badge for cycle members and a blue initial badge for places the net cannot seed itself; hovering a badge lights the whole cycle up in the list and the graph. Topological ordering hoists the seed places to the front of the flow. In the graph explorer, cycle members carry dashed rings (solid while their cycle is hovered, with intra-cycle edges recoloured to match), initial places a hollow token marker inside the node, and the header counts both. --- .../src/ui/views/Notebook/graph-explorer.tsx | 38 +++++ .../src/ui/views/Notebook/net-graph.tsx | 144 +++++++++++++++++- .../src/ui/views/Notebook/notebook-cell.tsx | 99 ++++++++++++ .../src/ui/views/Notebook/notebook-view.tsx | 45 +++++- 4 files changed, 318 insertions(+), 8 deletions(-) diff --git a/libs/@hashintel/petrinaut/src/ui/views/Notebook/graph-explorer.tsx b/libs/@hashintel/petrinaut/src/ui/views/Notebook/graph-explorer.tsx index e2420532b27..6de4fa9830b 100644 --- a/libs/@hashintel/petrinaut/src/ui/views/Notebook/graph-explorer.tsx +++ b/libs/@hashintel/petrinaut/src/ui/views/Notebook/graph-explorer.tsx @@ -9,6 +9,8 @@ import { CELL_KIND_ICONS, CELL_KIND_LABELS } from "./cell-kinds"; import { NetGraphView } from "./net-graph"; import type { FocusGrid } from "../../worksheet/use-focus-grid"; +import type { CycleGroup } from "./net-cycles"; +import type { InitialPlaceGroup } from "./net-siphons"; import type { CellConnections, NetGraph, NodeRef } from "./notebook-model"; const containerStyle = css({ @@ -34,6 +36,12 @@ const headerActionsStyle = css({ gap: "1", }); +const cycleSummaryStyle = css({ + fontSize: "xs", + color: "neutral.fg.subtle", + whiteSpace: "nowrap", +}); + /** The graph takes every pixel the lists below don't claim. */ const graphPaneStyle = css({ flex: "[1]", @@ -248,6 +256,9 @@ export type ExplorerGraph = { dependencyIds: ReadonlySet; dependentIds: ReadonlySet; placeColors: ReadonlyMap; + cycleByNode: ReadonlyMap; + initialByPlace: ReadonlyMap; + hoveredCycleKey: string | null; /** Set to re-layer the diagram around this node. */ focusId: string | null; }; @@ -260,6 +271,9 @@ export interface GraphExplorerProps { /** Id of the selected cell — keys the connection lists' focus memory. */ selectedCellId: string | null; selectedName: string | null; + /** Every cycle in the net, for the summary in the header. */ + cycleGroups: CycleGroup[]; + onHoverCycle: (cycleKey: string | null) => void; isFocusMode: boolean; /** Focus mode needs a place or transition selected to have something to centre. */ canFocus: boolean; @@ -280,6 +294,8 @@ export const GraphExplorer: React.FC = ({ connections, selectedCellId, selectedName, + cycleGroups, + onHoverCycle, isFocusMode, canFocus, onToggleFocus, @@ -287,12 +303,30 @@ export const GraphExplorer: React.FC = ({ }) => { // How much of the pane the lists claim; the graph fills whatever is left. const [listsHeight, setListsHeight] = useState(DEFAULT_LISTS_HEIGHT); + const initialPlaceCount = graph.initialByPlace.size; return (
Graph explorer
+ {cycleGroups.length > 0 && ( + + ↻ {cycleGroups.length}{" "} + {cycleGroups.length === 1 ? "cycle" : "cycles"} + + )} + {initialPlaceCount > 0 && ( + + ○ {initialPlaceCount} initial + + )}