feat(steps): cross-tier channels — a client's fetch onto its own route, queue jobs onto consumers, bus and socket events onto handlers

- resolution/tier-synthesizer.ts: http-client (literal fetch/axios/ky/got/$fetch paths, axios.create baseURL instances, template holes as :params, base-URL holes by a two-segment tail; unique match only), queue-job (BullMQ/Bull add ↔ @Process/@Processor, WorkerHost process, new Worker, queue.process), event-bus (EventEmitter2 emit ↔ @OnEvent with globs; socket emit ↔ @SubscribeMessage / socket.on both ways with tier); channel, tier, callee, registeredAt on every edge; generic transport events never pair; test and generated files never sources; registered before the emitter pass
- steps.ts: crossing() reads tier/channel before languages; an endpoint reached across a tier is a bridge box and a boundary like a screen (through=1 enters it); a channel's call is not also an effect; sites read as written; a Next 'use server' action is a crossing by its directive (when.ts directive); a function-valued constant handler (asyncHandler(...)) is a route root and borrows the file-scope calls and refs within its lines
- express.ts: app.use('/prefix', router) mounts composed onto route names in postExtract (nested, by import or require); chained router.route('/x').get(h).put(h2) extracted, across lines
- frameworks/package-deps.ts: dependencies read from workspace package.json files too (Express, React, Expo Router, NestJS detect)
- routing manifest names constant handlers; e2e/ is a test directory; explore's Flow section labels the new channels
- tests: ui-steps-cross-tier (monorepo fixture: Next client + Express/Nest API), servers test updated for the queue landing
- docs: CHANGELOG, spec §3.13 cross-tier paragraph, CLAUDE.md, callback-edge-synthesis.md, plan P3 built

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 14:31:01 -05:00
co-authored by Claude Fable 5
parent 950686def4
commit b1f40c57dd
25 changed files with 2051 additions and 130 deletions
+4 -2
View File
@@ -3,7 +3,9 @@
* One step on the Steps view. The box is the Screens view's screen box with
* a kind: a screen is drawn exactly as there; a handler is a plain box; a
* native call or a native event carries an accent rule on its left, where
* the language changes under the code; a store action sits on `--paper-2`;
* the language changes under the code — and so does an endpoint the code
* crosses to (`⇢ POST /api/users`) or a job, an event, a message arriving;
* a store action sits on `--paper-2`;
* a call that leaves the index is dashed, like a trigger no screen reaches
* on the Screens view — a place the graph cannot follow into. The anchor
* carries the entry mark. A step the walk was cut at ends its name with an
@@ -43,7 +45,7 @@
case 'steps':
return ' The picture reached its size limit here.';
case 'screen':
return ' Another screen — a chapter of its own. Start here to see what happens on it.';
return ` Another ${kindWord('screen', node.project, step)} — a chapter of its own. Start here to see what happens on it.`;
case 'component':
return ' The event lands in a component of another screen — a picture of its own. Start here to see it.';
default:
+4
View File
@@ -86,6 +86,8 @@ export function kindWords(kind: WireStep['kind'], project: ProjectKind = 'app',
case 'trigger':
return ['handler', 'handlers'];
case 'bridge':
// An endpoint reached across a tier is a call to the server wherever it is.
if (step?.screen?.endpoint) return ['call to the server', 'calls to the server'];
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 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'];
@@ -153,6 +155,8 @@ export function stepSub(step: WireStep, project: ProjectKind = 'app'): string {
// The event before the file: `onPress · <Button> · index.tsx`.
return step.trigger ? `${triggerWords(step.trigger)} · ${file}` : `handler · ${file}`;
case 'bridge':
// An endpoint the code crosses to says its handler, as an endpoint box does.
if (step.screen) return step.sub;
return `${project === 'app' ? 'native' : project === 'web' ? 'server' : 'another tier'} · ${file}`;
case 'event':
return `${step.label} · ${file}`;
+4 -2
View File
@@ -750,7 +750,8 @@ export interface WireStep {
depth: number;
/**
* Why the walk did not go on from this step: a cap (`depth`, `fan-out`,
* `folded`, `steps`), or `screen` — another screen, drawn as a boundary.
* `folded`, `steps`), or `screen` — another screen, or an endpoint reached
* across a tier, drawn as a boundary.
*/
cut: 'depth' | 'fan-out' | 'folded' | 'steps' | 'screen' | 'component' | null;
/** The event name a native event step arrived on — the first, when several land here. */
@@ -760,7 +761,8 @@ export interface WireStep {
/** For a handler: what fires it. */
trigger?: WireStepTrigger;
/**
* For a screen or an endpoint: its path and the symbol that serves it.
* 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.
* `endpoint` when the route leads with an HTTP verb; `inline` when the
* handler is anonymous at the registration site (component is null).
*/