Files
codegraph/__tests__/ui-steps-program.test.ts
T
Colby McHenryandClaude Opus 5 b02e192ffa feat(steps): the same walk, read in the code's order
The picture answers "what does this set in motion", a row per distance from
the anchor. On proshop's login that puts `User.findOne`, `jwt.sign`, `200` and
`401` side by side — all one step out — when the code says: look the user up,
then IF the password matches sign a token and answer 200, ELSE answer 401. The
signing is not beside the 200, it happens INSIDE the reply it is part of.

So the walk now records what happens in each function where the code writes it
— the step reached (or the helper folded into), the call's position and span,
and the branch guards, structured — and `api/program.ts` folds those records
into the anchor's body: items in source order, a fork wherever two sites are
arms of one decision, a helper drawn in place at its call, an arm that answers
the request or leaves ending there. A call written inside another call's
arguments comes first, so the token is signed before the reply that carries it.

It is a derivation, not a second walk: the records are made by the pass that
makes the links, so the two readings can never hold different steps. A fork
exists only where a guard was READ — a language without rules, or a file that
changed since the index, reads as a plain sequence rather than an invented
structure. The reading opens each function once, however many times it is
called (`again`), and is capped like everything else here.

The payload carries it as `program`, with `defaultView`: the code's order for a
handler, an endpoint or any function; the tree for a screen, where handlers
fire on events and have no order between them.

Measured on a 87-step / 160-link screen: +3% wall clock, +95 KB.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC
2026-08-29 13:20:01 -05:00

243 lines
9.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* The Steps view's second reading: the anchor's body in the code's order.
*
* `buildProgram` is pure over the records the walk makes — no graph, no
* source — so this suite hands it records by hand and reads the block tree
* back. The end-to-end reading over real fixtures is in
* `ui-steps-api-servers.test.ts`; what is pinned here is the FOLD: which sites
* become arms of one decision, what ends an arm, and where a helper is drawn.
*/
import { describe, it, expect } from 'vitest';
import type { BranchGuard } from '../src/graph/branch-guards';
import { buildProgram, type ProgramInput, type ProgramSite, type WireBlock, type WireItem } from '../src/ui-server/api/program';
/* ------------------------------------------------------------ material -- */
let nextLine = 1;
/** A guard, with the fields the fold reads: which decision, which arm, how the arm leaves. */
function g(text: string, opts: Partial<BranchGuard> = {}): BranchGuard {
return { text, negated: false, form: 'if', line: 1, branch: `b:${text}`, ...opts };
}
/** A site at the next line, reaching a step. */
function at(step: string, guards: BranchGuard[] = [], extra: Partial<ProgramSite> = {}): ProgramSite {
const line = nextLine++;
return { step, link: `l:${step}`, at: { line, column: 0, end: { line, column: 40 } }, guards, ...extra };
}
/** A site that folds into a helper. */
function into(fn: string, guards: BranchGuard[] = [], extra: Partial<ProgramSite> = {}): ProgramSite {
const line = nextLine++;
return { into: fn, at: { line, column: 0, end: { line, column: 40 } }, guards, ...extra };
}
function program(sites: Record<string, ProgramSite[]>, replies: string[] = [], into: Record<string, string> = {}) {
nextLine = 1;
const input: ProgramInput = {
sites: new Map(Object.entries(sites)),
root: 'root',
node: (id) => ({ id, kind: 'function', name: id, qualifiedName: id, file: 'a.ts', line: 1, endLine: 2, language: 'typescript', test: false }),
step: (id) => ({ reply: replies.includes(id), into: into[id] ?? null }),
};
return buildProgram(input);
}
/** The shape of a block, one line per item, indented — what a reader would see. */
function shape(block: WireBlock, indent = ''): string[] {
const out: string[] = [];
for (const item of block) {
if (item.kind === 'step') {
out.push(`${indent}${item.step}${item.again ? ' (again)' : ''}${item.within ? ` inside ${item.within}` : ''}`);
if (item.body) out.push(...shape(item.body, `${indent} `));
} else if (item.kind === 'fork') {
out.push(`${indent}${item.form} ${item.on}`);
for (const arm of item.arms) {
out.push(`${indent} arm ${arm.when}${arm.ends ? ` ends:${arm.ends}` : ''}`);
out.push(...shape(arm.body, `${indent} `));
}
} else if (item.kind === 'block') {
out.push(`${indent}${item.block} ${item.label}${item.again ? ' (again)' : ''}`);
out.push(...shape(item.body, `${indent} `));
} else out.push(`${indent}cut ${item.why}`);
}
return out;
}
/* --------------------------------------------------------------- tests -- */
describe('buildProgram', () => {
it('reads a straight line in the codes order', () => {
const p = program({ root: [at('a'), at('b'), at('c')] });
expect(shape(p!.root)).toEqual(['a', 'b', 'c']);
});
it('is nothing when the anchor has no body to read', () => {
expect(program({})).toBeNull();
expect(buildProgram({ sites: new Map(), root: null, node: () => null, step: () => null })).toBeNull();
});
it('makes an if and its else two arms of ONE fork', () => {
const cond = 'user && ok';
const p = program({
root: [at('lookup'), at('sign', [g(cond)]), at('200', [g(cond)]), at('401', [g(cond, { negated: true, form: 'else' })])],
});
expect(shape(p!.root)).toEqual([
'lookup',
'if user && ok',
' arm user && ok',
' sign',
' 200',
' arm !(user && ok)',
' 401',
]);
const fork = p!.root[1] as Extract<WireItem, { kind: 'fork' }>;
expect(fork.arms).toHaveLength(2);
});
it('ends an arm that answers the request', () => {
const cond = 'user';
const p = program(
{ root: [at('200', [g(cond)]), at('401', [g(cond, { negated: true, form: 'else' })])] },
['200', '401']
);
expect(shape(p!.root)).toEqual(['if user', ' arm user ends:reply', ' 200', ' arm !user ends:reply', ' 401']);
});
it('draws an early exit as the forks other arm, with how it leaves', () => {
// `if (!product) { res.status(404); throw }` then the rest — the guard on
// the code AFTER carries the same branch, negated, and how the exit left.
const p = program({
root: [
at('404', [g('!product', { branch: 'b:1' })]),
at('save', [g('!product', { negated: true, form: 'guard', branch: 'b:1', exit: 'throw' })]),
],
}, ['404']);
expect(shape(p!.root)).toEqual(['if !product', ' arm !product ends:reply', ' 404', ' arm product', ' save']);
});
it('draws an early exit whose arm holds nothing as a terminal', () => {
const p = program({ root: [at('go', [g('busy', { negated: true, form: 'guard', exit: 'return' })])] });
expect(shape(p!.root)).toEqual(['if busy', ' arm busy ends:return', ' arm !busy', ' go']);
});
it('nests forks the way the code nests them', () => {
const outer = g('product', { branch: 'b:outer' });
const inner = g('reviewed', { branch: 'b:inner' });
const p = program(
{
root: [
at('400', [outer, inner]),
at('201', [outer, { ...inner, negated: true, form: 'guard', exit: 'throw' }]),
at('404', [{ ...outer, negated: true, form: 'else' }]),
],
},
['400', '201', '404']
);
expect(shape(p!.root)).toEqual([
'if product',
' arm product',
' if reviewed',
' arm reviewed ends:reply',
' 400',
' arm !reviewed ends:reply',
' 201',
' arm !product ends:reply',
' 404',
]);
});
it('puts every case of one switch under one fork', () => {
const branch = 'b:switch';
const p = program({
root: [
at('a', [g("kind === 'a'", { form: 'case', branch })]),
at('b', [g("kind === 'b'", { form: 'case', branch })]),
at('d', [g('kind: default', { form: 'case', branch })]),
],
});
expect(shape(p!.root)).toEqual([
"switch kind === 'a'",
" arm kind === 'a'",
' a',
" arm kind === 'b'",
' b',
' arm kind: default',
' d',
]);
});
it('keeps two try/catch blocks apart', () => {
const p = program({
root: [
at('first', [g('on error', { form: 'catch', branch: 'b:try1' })]),
at('second', [g('on error', { form: 'catch', branch: 'b:try2' })]),
],
});
expect(shape(p!.root)).toEqual(['try on error', ' arm on error', ' first', 'try on error', ' arm on error', ' second']);
});
it('draws a folded helper where it is called, and says what it is inside', () => {
const p = program({
root: [into('helper', [], { within: 'res.json' }), at('200')],
helper: [at('sign')],
});
expect(shape(p!.root)).toEqual(['inline via helper', ' sign', '200']);
const block = p!.root[0] as Extract<WireItem, { kind: 'block' }>;
expect(block.within).toBe('res.json');
expect(block.via?.name).toBe('helper');
});
it('puts a call written inside another calls arguments first', () => {
// `res.json({ token: generateToken(…) })` spans lines 1421 and the token is
// signed on line 19: the signing happens BEFORE the reply it is part of.
const reply: ProgramSite = { step: '200', at: { line: 14, column: 4, end: { line: 21, column: 6 } }, guards: [] };
const signed: ProgramSite = { step: 'sign', at: { line: 19, column: 13, end: { line: 19, column: 34 } }, guards: [] };
const p = program({ root: [reply, signed] });
expect(shape(p!.root)).toEqual(['sign', '200']);
});
it('reads a function once, however many times it is called', () => {
const p = program({
root: [into('helper'), at('x'), into('helper')],
helper: [at('work')],
});
expect(shape(p!.root)).toEqual(['inline via helper', ' work', 'x', 'inline via helper (again)']);
});
it('reads on into a step the walk entered, and stops at one it did not', () => {
// A step explores from its own function: `store`'s is `storeFn`, whose
// sites are its body. A boundary — another screen, an effect — has none.
const entered = program({ root: [at('store')], storeFn: [at('write')] }, [], { store: 'storeFn' });
expect(shape(entered!.root)).toEqual(['store', ' write']);
const boundary = program({ root: [at('store')], storeFn: [at('write')] });
expect(shape(boundary!.root)).toEqual(['store']);
});
it('says a helper that calls itself was already read', () => {
const p = program({ root: [into('a')], a: [at('x'), into('a')] });
expect(shape(p!.root)).toEqual(['inline via a', ' x', ' inline via a (again)']);
});
it('puts work registered to run later in a block of its own', () => {
const p = program({
root: [at('now'), at('afterwards', [], { trigger: { kind: 'callback', name: 'then', of: null } })],
});
expect(shape(p!.root)).toEqual(['now', 'later later · then', ' afterwards']);
});
it('puts calls started together in one block', () => {
const p = program({
root: [at('a', [], { within: 'Promise.all' }), at('b', [], { within: 'Promise.all' }), at('c')],
});
expect(shape(p!.root)).toEqual(['together together', ' a inside Promise.all', ' b inside Promise.all', 'c']);
});
it('closes a fork when the code leaves it', () => {
const cond = g('ready');
const p = program({ root: [at('inside', [cond]), at('after')] });
expect(shape(p!.root)).toEqual(['if ready', ' arm ready', ' inside', 'after']);
});
});