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:
Colby McHenry
2026-08-29 12:25:27 -05:00
co-authored by Claude Fable 5
parent 46e3e7aaa0
commit 783f3954ec
10 changed files with 207 additions and 15 deletions
+25 -2
View File
@@ -49,7 +49,13 @@ beforeAll(async () => {
' if (!user.verified) {\n' +
' await sendVerification(user)\n' +
' }\n' +
' res.status(201).json(user)\n' +
' res.status(201).json({\n' +
' id: user.id,\n' +
' token: signToken(user.id),\n' +
' })\n' +
'}\n' +
'function signToken(id) {\n' +
" return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '1d' })\n" +
'}\n' +
'export async function getUser(id: string) {\n' +
' const user = await prisma.user.findUnique({ where: { id } })\n' +
@@ -312,7 +318,24 @@ describe('Express', () => {
expect(res.label).toBe('201');
expect(res.effect?.statuses).toEqual([201]);
const resLink = p.links.find((l) => l.to === res.id)!;
expect(resLink.sites[0]).toMatchObject({ text: 'res.status(201).json', args: 'user', status: 201 });
expect(resLink.sites[0]).toMatchObject({ text: 'res.status(201).json', args: '{ id, token }', status: 201 });
// The token is signed while the reply is built: the link says so, and
// the row reads in the code's order — the write, the job, the mail, the
// signing (inside the reply's arguments), then the reply.
const auth = effect(p, 'auth')!;
expect(auth.label).toBe('jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn })');
const authLink = p.links.find((l) => l.to === auth.id)!;
expect(authLink.via.map((v) => v.name)).toEqual(['signToken']);
expect(authLink.within).toBe('res.status(201).json');
expect(resLink.within).toBeUndefined();
const row = p.steps.filter((s) => s.depth === 1).sort((a, b) => a.order! - b.order!).map((s) => s.label);
expect(row).toEqual([
'prisma.user.create({ data })',
"emailQueue.add('welcome', { userId })",
'transporter.sendMail({ to })',
'jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn })',
'201',
]);
});
it('walks an inline handler as the route itself, into the services read and its 404', async () => {
+13
View File
@@ -137,3 +137,16 @@ describe('words per project', () => {
expect(triggerWords({ kind: 'load', name: 'GET', of: '/blog/[slug]', in: 'page.tsx' })).toBe('page load · /blog/[slug]');
});
});
describe('row order', () => {
it('lays a row out in the order the server gave, not by id', () => {
const anchor = step('/login', 'screen', 0, { anchor: true });
const a = step('User.findOne', 'effect', 1, { order: 0 });
const b = step('jwt.sign', 'effect', 1, { order: 1 });
const c = step('200', 'effect', 1, { order: 2 });
const d = step('401', 'effect', 1, { order: 3 });
const model = buildStepsModel(payload([anchor, d, c, b, a], [link(anchor, a), link(anchor, b), link(anchor, c), link(anchor, d)]));
const row = model.layout.nodes.filter((n) => n.id !== anchor.id).sort((x, y) => x.x - y.x).map((n) => n.id);
expect(row).toEqual([a.id, b.id, c.id, d.id]);
});
});