feat(steps): render fork decisions as points with per-arm edges and captions
Adds full support for decisions at forks in both the code graph and the UI. Key changes introduce a decision model for forks (innermost guard decisions), propagate decision data through the server and wire layer, and render decisions in the UI as distinct points with labeled arms. New components (ForkPoint and DecisionCaption) visualize the decision and its arms, while utilities (armWords, forkLabel) generate arm captions. The order reading (canvas) now shows decisions as points, and arms are drawn as separate edges (yes/no/case), with labels and captions displayed under the deciding box. Tests, typings, and docs updated to reflect the new decision visualization and behavior, including selection reach and resting-label semantics. This lays the groundwork for clearer visualization of conditional navigation and guarded branches on the order canvas.
This commit is contained in:
@@ -757,9 +757,18 @@ function layPill(
|
||||
* pill: the pill for a hovered edge that is not the selected screen's is
|
||||
* placed separately by {@link hoverPill}.
|
||||
*/
|
||||
export function placeLabels(model: Picture, selected: string | null, atRest = false): PillLayout {
|
||||
export function placeLabels(
|
||||
model: Picture,
|
||||
selected: string | null,
|
||||
atRest: boolean | ReadonlySet<string> = false
|
||||
): PillLayout {
|
||||
const pills = new Map<string, PillPlacement>();
|
||||
if (selected === null && !atRest) return { pills, hidden: 0 };
|
||||
// `true` labels every line (the code's order, where the conditions ARE the
|
||||
// picture); a SET labels only those lines (the tree, where the arms of a
|
||||
// decision are the one thing worth saying before anything is selected).
|
||||
const restLabels = (id: string): boolean => (atRest === true ? true : atRest !== false && atRest.has(id));
|
||||
const anyAtRest = atRest === true || (atRest !== false && atRest.size > 0);
|
||||
if (selected === null && !anyAtRest) return { pills, hidden: 0 };
|
||||
const nodes = new Map(model.layout.nodes.map((n) => [n.id, n]));
|
||||
const lanes = laneCount(model.layerGap);
|
||||
const bounds = { width: model.layout.width, height: model.layout.height };
|
||||
@@ -770,7 +779,7 @@ export function placeLabels(model: Picture, selected: string | null, atRest = fa
|
||||
// A picture whose labels ARE its content says so (`atRest`): the Steps view
|
||||
// in the code's order, where the conditions on the lines are the flow.
|
||||
const candidates = model.layout.edges
|
||||
.filter((e) => atRest || e.source === selected || e.target === selected)
|
||||
.filter((e) => restLabels(e.id) || e.source === selected || e.target === selected)
|
||||
.map((edge) => {
|
||||
const end: 'source' | 'target' = selected !== null && edge.target === selected ? 'source' : 'target';
|
||||
const far = nodes.get(end === 'source' ? edge.source : edge.target);
|
||||
|
||||
Reference in New Issue
Block a user