feat(ui): Steps for servers — route roots, server effects, request/decorator triggers, guards for Python/Java/Kotlin/C#/Go/C

- api/route-roots.ts: the symbol a route runs (references-edge handler, exported page component, or the route itself for an inline handler), shared by steps and screens; the bare Steps tab lists an API's endpoints by router file
- api/effects.ts: database / response / queue / email / payments / cache / auth / process / network / storage / device / telemetry, matched on the call as written per language family, with model + read/write and the literal status on a response site
- graph/branch-guards.ts: callSitesForFile (the whole member chain), memberTypesInTree, decoratorsForFile, request/decorator triggers with the middleware/guard chain; guard + argument rules for Python, Java, Kotlin, C#, Go and C
- steps.ts: classify on the chain before trusting a name match, retarget this.x.y() by declared type, skip test doubles after the effect pre-check, project kind on the wire
- viewer: kindWord/kindWords per project kind, endpoint chooser, response boxes labelled by status codes
- python.ts: FastAPI detected from a monorepo sub-directory; is-test-file: samples/examples package paths are not tests
- tests: ui-steps-api-servers, ui-effects, branch-guards-languages; spec §3.13 Servers paragraph, CHANGELOG, plan doc

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-28 13:45:38 -05:00
co-authored by Claude Fable 5
parent 5e06204deb
commit 950686def4
22 changed files with 3869 additions and 190 deletions
+23 -1
View File
@@ -4,7 +4,7 @@
* rule, and the panel's two lists.
*/
import { describe, it, expect } from 'vitest';
import { buildStepsModel, kindWord, stepLabel, stepNeighbourhood, stepSub, stepViaText, triggerWords } from '../ui/src/lib/steps-model';
import { buildStepsModel, countWords, kindWord, kindWords, stepLabel, stepNeighbourhood, stepSub, stepViaText, triggerWords } from '../ui/src/lib/steps-model';
import { placeLabels } from '../ui/src/lib/screens-model';
import type { WireNodeRef, WireStep, WireStepLink, WireStepsPayload } from '../ui/src/lib/wire';
@@ -25,6 +25,7 @@ function payload(steps: WireStep[], links: WireStepLink[]): WireStepsPayload {
return {
anchor: steps[0]!.node!,
ambiguous: [],
project: 'app',
steps,
links,
depth: 8,
@@ -115,3 +116,24 @@ describe('steps model', () => {
expect(lists.leadsTo.map((l) => l.to)).toEqual([effect.id, store.id, home.id, store.id]);
});
});
describe('words per project', () => {
it('names the same box for an app, an API and a web app', () => {
expect(kindWord('screen', 'app')).toBe('screen');
expect(kindWord('screen', 'api')).toBe('endpoint');
expect(kindWord('screen', 'web')).toBe('page');
// A route that leads with a verb is an endpoint wherever it is.
const endpoint = { id: 'r', kind: 'screen', anchor: false, node: null, label: 'POST /users', sub: 'createUser', depth: 1, cut: null, screen: { path: 'POST /users', component: null, endpoint: true, inline: false } } as const;
expect(kindWord('screen', 'web', endpoint)).toBe('endpoint');
expect(kindWords('store', 'api')).toEqual(['data call', 'data calls']);
expect(kindWords('bridge', 'app')).toEqual(['native call', 'native calls']);
expect(countWords(11, 'effect', 'api')).toBe('11 outside the index');
expect(countWords(1, 'trigger')).toBe('1 handler');
expect(countWords(3, 'trigger')).toBe('3 handlers');
});
it('says what fires a server-side step', () => {
expect(triggerWords({ kind: 'request', name: 'POST', of: '/users', in: 'users.routes.ts', after: ['authenticate', 'validate(…)'] })).toBe('POST /users · after authenticate, validate(…)');
expect(triggerWords({ kind: 'decorator', name: 'Process', of: "'email'", in: 'x.ts' })).toBe("@Process('email')");
expect(triggerWords({ kind: 'load', name: 'GET', of: '/blog/[slug]', in: 'page.tsx' })).toBe('page load · /blog/[slug]');
});
});