feat(steps): rows read in the code's order; a hop written inside another call says so
- branch-guards callSiteInTree: a call's span and the call it is written inside the arguments of (`within`), stopping at a function or block boundary - steps.ts: each step records the hop that first reached it (position, span, enclosing call — the fold's first hop out of the root, inherited down the fold); a row is ordered by that position, a hop inside another site's arguments before that site, and `WireStep.order` carries it; links carry `within` - map-model: an `order` option — the row's initial order, sweeps over parents only, tie-broken by it; the Map and Screens tabs pass none and are unchanged - viewer: rows laid out by `order`; `inside res.json(…)` in the panel rows and the tooltip - tests: servers fixture (a token signed inside the reply's arguments: `within`, and the row `create · queue · mail · jwt.sign · 201`), model row order; spec §3.13, CHANGELOG Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC
This commit is contained in:
co-authored by
Claude Fable 5
parent
46e3e7aaa0
commit
783f3954ec
+13
-3
@@ -239,6 +239,13 @@ export interface MapLayoutOptions {
|
||||
* longest chain of screens above the login page.
|
||||
*/
|
||||
layering?: (ids: string[], links: ReadonlyArray<{ source: string; target: string }>) => Map<string, number>;
|
||||
/**
|
||||
* A row order the view already knows — the Steps view's rows read in the
|
||||
* code's order. It is the initial order, and the sweeps then move a box
|
||||
* only to sit under its parents (a barycenter over parents alone, not
|
||||
* children), tie-broken by this order rather than by id.
|
||||
*/
|
||||
order?: (id: string) => number;
|
||||
/**
|
||||
* Vertical room between two layers; {@link LAYER_GAP} unless a view says
|
||||
* otherwise. The Screens view widens it because its edges carry labels, and
|
||||
@@ -328,13 +335,16 @@ export function buildMapLayout(
|
||||
const layerCount = Math.max(1, ...[...layer.values()].map((v) => v + 1));
|
||||
const rows: string[][] = Array.from({ length: layerCount }, () => []);
|
||||
for (const module of modules) rows[layer.get(module.id) ?? 0]!.push(module.id);
|
||||
for (const row of rows) row.sort();
|
||||
const given = options.order;
|
||||
for (const row of rows) row.sort(given ? (a, b) => given(a) - given(b) || a.localeCompare(b) : undefined);
|
||||
|
||||
// --- barycenter ordering, three sweeps -----------------------------------
|
||||
// With an order given, a box's barycenter is over its parents alone, so
|
||||
// siblings under one parent keep the order they came in.
|
||||
const neighbours = new Map<string, string[]>(modules.map((m) => [m.id, []]));
|
||||
for (const link of acyclic) {
|
||||
neighbours.get(link.source)?.push(link.target);
|
||||
neighbours.get(link.target)?.push(link.source);
|
||||
if (!given) neighbours.get(link.source)?.push(link.target);
|
||||
}
|
||||
const position = new Map<string, number>();
|
||||
for (const row of rows) row.forEach((id, i) => position.set(id, i));
|
||||
@@ -350,7 +360,7 @@ export function buildMapLayout(
|
||||
const bb = bary.get(b) ?? 0;
|
||||
if (ba !== bb && Number.isFinite(ba - bb)) return ba - bb;
|
||||
if (ba !== bb) return ba < bb ? -1 : 1;
|
||||
return (position.get(a) ?? 0) - (position.get(b) ?? 0) || a.localeCompare(b);
|
||||
return (position.get(a) ?? 0) - (position.get(b) ?? 0) || (given ? given(a) - given(b) : 0) || a.localeCompare(b);
|
||||
});
|
||||
row.forEach((id, i) => position.set(id, i));
|
||||
}
|
||||
|
||||
@@ -260,6 +260,8 @@ export function buildStepsModel(payload: WireStepsPayload): StepsModel {
|
||||
return { label: info?.label ?? m.id, meta: info?.sub ?? '' };
|
||||
},
|
||||
layering,
|
||||
// The server ordered each row the way the code reads; keep it.
|
||||
order: (id) => nodes.get(id)?.step.order ?? Number.MAX_SAFE_INTEGER,
|
||||
layerGap: SCREEN_LAYER_GAP,
|
||||
portPitch: PORT_PITCH,
|
||||
ports: 'directional',
|
||||
|
||||
@@ -760,6 +760,8 @@ export interface WireStep {
|
||||
events?: string[];
|
||||
/** For a handler: what fires it. */
|
||||
trigger?: WireStepTrigger;
|
||||
/** The step's place in its row, in the code's order (a hop written inside another site's arguments before that site). */
|
||||
order?: number;
|
||||
/**
|
||||
* For a screen or an endpoint — also a `bridge` step that is an endpoint
|
||||
* reached across a tier: its path and the symbol that serves it.
|
||||
@@ -795,6 +797,8 @@ export interface WireStepLink {
|
||||
when: string;
|
||||
/** How the last hop was established when it was not a plain call. */
|
||||
label: string;
|
||||
/** The call the first hop is written inside the arguments of — `res.json` for a token signed while building the reply. */
|
||||
within?: string;
|
||||
synthesized: boolean;
|
||||
uncertain: boolean;
|
||||
sites: WireStepSite[];
|
||||
|
||||
@@ -568,6 +568,7 @@
|
||||
<div class="tiprow">
|
||||
{#if link.trigger}<span class="fires"><b class="kw">FIRES FROM</b> {triggerWords(link.trigger)} <span class="dim">in {link.trigger.in}</span></span>{/if}
|
||||
{#if link.via.length > 0}<span class="via">via {stepViaText(link)}</span>{/if}
|
||||
{#if link.within}<span class="dim">inside {link.within}(…)</span>{/if}
|
||||
{#if link.sites.length > 1}<span class="dim">{link.sites.length} ways</span>{/if}
|
||||
<span class="when">{@render words(conditionTokens(link.when))}</span>
|
||||
{#if link.label}<span class="dim">{link.label}</span>{/if}
|
||||
@@ -666,6 +667,7 @@
|
||||
<button class="peer mono" onclick={() => (selected = link.from)}>{nameOf(link.from)}</button>
|
||||
{#if link.trigger}<div class="fires"><b class="kw">FIRES FROM</b> {triggerWords(link.trigger)} <span class="dim">in {link.trigger.in}</span></div>{/if}
|
||||
{#if link.via.length > 0}<div class="via">via {stepViaText(link)}</div>{/if}
|
||||
{#if link.within}<div class="via dim">inside {link.within}(…)</div>{/if}
|
||||
{#if sc.common.length > 0}<div class="when">{@render words(commonTokens(sc.common))}</div>{/if}
|
||||
{#if link.label}<div class="via dim">{link.label}</div>{/if}
|
||||
{#if sc.rows.length > 1}<div class="ways dim">{sc.rows.length} ways</div>{/if}
|
||||
@@ -712,6 +714,7 @@
|
||||
<button class="peer mono" onclick={() => (selected = link.to)}>{nameOf(link.to)}</button>
|
||||
{#if link.trigger}<div class="fires"><b class="kw">FIRES FROM</b> {triggerWords(link.trigger)} <span class="dim">in {link.trigger.in}</span></div>{/if}
|
||||
{#if link.via.length > 0}<div class="via">via {stepViaText(link)}</div>{/if}
|
||||
{#if link.within}<div class="via dim">inside {link.within}(…)</div>{/if}
|
||||
{#if sc.common.length > 0}<div class="when">{@render words(commonTokens(sc.common))}</div>{/if}
|
||||
{#if link.label}<div class="via dim">{link.label}</div>{/if}
|
||||
{#if sc.rows.length > 1}<div class="ways dim">{sc.rows.length} ways</div>{/if}
|
||||
|
||||
Reference in New Issue
Block a user