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:
co-authored by
Claude Fable 5
parent
5e06204deb
commit
950686def4
+46
-15
@@ -63,36 +63,66 @@ const HIT_SAMPLES = 24;
|
||||
|
||||
/* ---------------------------------------------------------------- words -- */
|
||||
|
||||
/** A short word for a step's kind, as the panel and the legend say it. */
|
||||
export function kindWord(kind: WireStep['kind']): string {
|
||||
/** What the index is a picture of; the server decides it from the routes (`WireStepsPayload.project`). */
|
||||
export type ProjectKind = WireStepsPayload['project'];
|
||||
|
||||
/**
|
||||
* A short word for a step's kind, as the panel and the legend say it — in the
|
||||
* project's own vocabulary. The same box is a screen in an app, a page in a
|
||||
* web app and an endpoint in an API; a route that leads with an HTTP verb is
|
||||
* an endpoint wherever it is. One place decides, so the legend, the panel
|
||||
* and the tooltip never disagree.
|
||||
*/
|
||||
export function kindWord(kind: WireStep['kind'], project: ProjectKind = 'app', step?: WireStep): string {
|
||||
return kindWords(kind, project, step)[0];
|
||||
}
|
||||
|
||||
/** The singular and the plural, for counts: `1 endpoint`, `3 outside the index`. */
|
||||
export function kindWords(kind: WireStep['kind'], project: ProjectKind = 'app', step?: WireStep): [string, string] {
|
||||
switch (kind) {
|
||||
case 'screen':
|
||||
return 'screen';
|
||||
if (step?.screen?.endpoint) return ['endpoint', 'endpoints'];
|
||||
return project === 'api' ? ['endpoint', 'endpoints'] : project === 'web' ? ['page', 'pages'] : ['screen', 'screens'];
|
||||
case 'trigger':
|
||||
return 'handler';
|
||||
return ['handler', 'handlers'];
|
||||
case 'bridge':
|
||||
return 'native call';
|
||||
return project === 'app' ? ['native call', 'native calls'] : project === 'web' ? ['call to the server', 'calls to the server'] : ['call to another tier', 'calls to another tier'];
|
||||
case 'event':
|
||||
return 'native event';
|
||||
return project === 'app' ? ['native event', 'native events'] : project === 'web' ? ['arrives from the server', 'arrive from the server'] : ['arrives from a queue or bus', 'arrive from a queue or bus'];
|
||||
case 'store':
|
||||
return 'store action';
|
||||
return project === 'api' ? ['data call', 'data calls'] : ['store action', 'store actions'];
|
||||
case 'effect':
|
||||
return 'outside the index';
|
||||
return ['outside the index', 'outside the index'];
|
||||
default:
|
||||
return 'start';
|
||||
return ['start', 'start'];
|
||||
}
|
||||
}
|
||||
|
||||
/** `3 handlers`, `1 endpoint`, `11 outside the index`. */
|
||||
export function countWords(n: number, kind: WireStep['kind'], project: ProjectKind = 'app'): string {
|
||||
const [one, many] = kindWords(kind, project);
|
||||
return `${n} ${n === 1 ? one : many}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* What fires something, in a few characters: `onPress · <Button>`,
|
||||
* `onSubmit · useFormik(…)`, `addListener('onZipComplete')`, `useEffect`.
|
||||
* `onSubmit · useFormik(…)`, `addListener('onZipComplete')`, `useEffect`;
|
||||
* for a server, `POST /users · after authenticate, validate(…)`,
|
||||
* `@Process('email')`, `page load · /blog/[slug]`.
|
||||
*/
|
||||
export function triggerWords(t: WireStepTrigger): string {
|
||||
const after = t.after && t.after.length > 0 ? ` · after ${t.after.join(', ')}` : '';
|
||||
switch (t.kind) {
|
||||
case 'prop':
|
||||
return t.of ? `${t.name} · <${t.of}>` : t.name;
|
||||
case 'option':
|
||||
return t.of ? `${t.name} · ${t.of}(…)` : t.name;
|
||||
case 'request':
|
||||
return `${t.name} ${t.of ?? ''}`.trim() + after;
|
||||
case 'decorator':
|
||||
return `@${t.name}(${t.of ?? ''})` + after;
|
||||
case 'load':
|
||||
return `page load · ${t.of ?? t.name}` + after;
|
||||
default:
|
||||
return t.of ? `${t.name}(${t.of})` : t.name;
|
||||
}
|
||||
@@ -114,7 +144,7 @@ export function stepLabel(step: WireStep): string {
|
||||
}
|
||||
|
||||
/** The second line: what the step is, then where it is. */
|
||||
export function stepSub(step: WireStep): string {
|
||||
export function stepSub(step: WireStep, project: ProjectKind = 'app'): string {
|
||||
const file = step.node ? step.node.file.slice(step.node.file.lastIndexOf('/') + 1) : '';
|
||||
switch (step.kind) {
|
||||
case 'screen':
|
||||
@@ -123,15 +153,16 @@ export function stepSub(step: WireStep): string {
|
||||
// The event before the file: `onPress · <Button> · index.tsx`.
|
||||
return step.trigger ? `${triggerWords(step.trigger)} · ${file}` : `handler · ${file}`;
|
||||
case 'bridge':
|
||||
return `native · ${file}`;
|
||||
return `${project === 'app' ? 'native' : project === 'web' ? 'server' : 'another tier'} · ${file}`;
|
||||
case 'event':
|
||||
return `${step.label} · ${file}`;
|
||||
case 'store':
|
||||
return `store · ${file}`;
|
||||
return `${project === 'api' ? 'data' : 'store'} · ${file}`;
|
||||
case 'effect':
|
||||
return step.sub;
|
||||
default:
|
||||
return step.sub;
|
||||
// The anchor: its file, at the size of a box; the panel prints the whole path.
|
||||
return step.node && step.sub === step.node.file ? file : step.sub;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +187,7 @@ export function buildStepsModel(payload: WireStepsPayload): StepsModel {
|
||||
}
|
||||
for (const step of payload.steps) {
|
||||
counts[step.kind]++;
|
||||
const info: StepNodeInfo = { id: step.id, step, label: stepLabel(step), sub: stepSub(step) };
|
||||
const info: StepNodeInfo = { id: step.id, step, label: stepLabel(step), sub: stepSub(step, payload.project) };
|
||||
nodes.set(step.id, info);
|
||||
modules.push({
|
||||
id: step.id,
|
||||
|
||||
+36
-5
@@ -712,17 +712,28 @@ export interface WireStepSite {
|
||||
when: string;
|
||||
/** What fires THIS site, when it differs from the link's first. */
|
||||
trigger?: WireStepTrigger;
|
||||
/** For a response site: the status code it sends, when literal. */
|
||||
status?: number;
|
||||
}
|
||||
|
||||
/** What fires a step or a link: the event it is written under, and the function that writes it there. */
|
||||
export interface WireStepTrigger {
|
||||
kind: 'prop' | 'option' | 'callback';
|
||||
/** `onPress`, `onSubmit`, `useEffect`, `addListener`. */
|
||||
/**
|
||||
* `prop` / `option` / `callback`: a binding at the call site (JSX attribute,
|
||||
* `on*` key, runs-later argument). `request`: the route a handler serves —
|
||||
* `name` the verb, `of` the path. `decorator`: a decorator on the handler —
|
||||
* `name` its name, `of` its literal argument (`@Process('email')`). `load`:
|
||||
* a page's own load-time work — `of` the page path.
|
||||
*/
|
||||
kind: 'prop' | 'option' | 'callback' | 'request' | 'decorator' | 'load';
|
||||
/** `onPress`, `onSubmit`, `useEffect`, `addListener`, `POST`, `Process`. */
|
||||
name: string;
|
||||
/** `Button` for a prop, `useFormik` for an option, the first string argument for a callback; null when unknown. */
|
||||
of: string | null;
|
||||
/** The function the binding is written in. */
|
||||
in: string;
|
||||
/** What runs before it fires: the middleware / guard chain, in order (`authenticate`, `validate(…)`). */
|
||||
after?: string[];
|
||||
}
|
||||
|
||||
export interface WireStep {
|
||||
@@ -748,9 +759,27 @@ export interface WireStep {
|
||||
events?: string[];
|
||||
/** For a handler: what fires it. */
|
||||
trigger?: WireStepTrigger;
|
||||
screen?: { path: string; component: WireNodeRef | null };
|
||||
/** The calls one function makes into one category, and the function. */
|
||||
effect?: { api: string; apis: string[]; category: string; by: WireNodeRef; line: number };
|
||||
/**
|
||||
* For a screen or an endpoint: its path and the symbol that serves it.
|
||||
* `endpoint` when the route leads with an HTTP verb; `inline` when the
|
||||
* handler is anonymous at the registration site (component is null).
|
||||
*/
|
||||
screen?: { path: string; component: WireNodeRef | null; endpoint: boolean; inline: boolean };
|
||||
/**
|
||||
* The calls one function makes into one category, and the function. A
|
||||
* database call names its model / table and read vs write when the call
|
||||
* says; a response box lists the status codes its sites send.
|
||||
*/
|
||||
effect?: {
|
||||
api: string;
|
||||
apis: string[];
|
||||
category: string;
|
||||
by: WireNodeRef;
|
||||
line: number;
|
||||
model?: string;
|
||||
access?: 'read' | 'write';
|
||||
statuses?: number[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface WireStepLink {
|
||||
@@ -775,6 +804,8 @@ export interface WireStepsPayload {
|
||||
anchor: WireNodeRef;
|
||||
/** Other symbols that share the anchor's name, when it was given by name. */
|
||||
ambiguous: WireNodeRef[];
|
||||
/** An `app` of screens, an `api` of endpoints, or a `web` app with both — the viewer's words follow it. */
|
||||
project: 'app' | 'api' | 'web';
|
||||
steps: WireStep[];
|
||||
links: WireStepLink[];
|
||||
depth: number;
|
||||
|
||||
Reference in New Issue
Block a user