feat(screens): Next.js as a Screens app — pages, route handlers, links and redirects
- frameworks/nextjs.ts (split out of react.ts): App Router app/**/page.tsx and Pages Router pages → routes named by path ((group) stripped, [slug] → :slug, [...all] → :all*), bound to the default export; app/**/route.ts exports → METHOD /api/… endpoints referencing their functions; pages/api → ANY; resolve() claims router.push/replace/prefetch, redirect/permanentRedirect and NextResponse.redirect(new URL(…)) into navigates edges via the Expo href readers, against a Next-only route table gated on the app's root
- next-router-synthesizer.ts: <Link href> and internal <a href> → dashed navigates edges from the component (next-link, registeredAt)
- expo-router.ts: href readers exported; matcher accepts :param / :all* segments
- steps.ts: a Next page's own work fires from page load; a Next page makes the project a web app; {status: 201} read off the call site (branch-guards CallSiteText.status) for response rows
- frameworks/package-deps.ts: nested package.json files probed on disk (getAllFiles lists only sources); Express/React/Expo/Nest detectors use it; routing manifest names constant handlers
- tests: nextjs.test.ts (file→route rules, extract, verbs, end to end with Screens and Steps); frameworks.test.ts Next cases moved to the Next resolver
- docs: CHANGELOG, spec §3.12 frameworks paragraph, CLAUDE.md, synthesis doc, plan P4 built, playbook rows for Next / MERN / Nest channels
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
b1f40c57dd
commit
9c6bc23b21
@@ -307,7 +307,7 @@ export function toHref(literal: string | null): HrefLiteral | null {
|
||||
* with a literal `pathname`, or a conditional whose two arms are each one of
|
||||
* those (`cond ? \`/x?id=${id}\` : '/x'`). Anything else is not static.
|
||||
*/
|
||||
function parseHrefExpression(expr: string): HrefLiteral | null {
|
||||
export function parseHrefExpression(expr: string): HrefLiteral | null {
|
||||
// `expr as any` / `expr satisfies Href` — a cast says nothing about the value.
|
||||
let args = expr.trim().replace(/\s+(?:as|satisfies)\s+[\w$.<>[\]|&\s]+$/, '');
|
||||
// `(a ? b : c)` — unwrap one layer of grouping parens.
|
||||
@@ -351,7 +351,7 @@ export function readHrefArgument(
|
||||
}
|
||||
|
||||
/** The source text of the navigation call's first argument, or null when there is no call there. */
|
||||
function firstArgumentText(
|
||||
export function firstArgumentText(
|
||||
lines: readonly string[],
|
||||
line: number,
|
||||
column: number,
|
||||
@@ -542,12 +542,22 @@ export function matchRoute(segs: string[], table: RouteTable): Node | null {
|
||||
return best && !tied ? best.node : null;
|
||||
}
|
||||
|
||||
/** A route segment that takes a value: Expo's `[id]`, or the `:id` every other framework's routes use. */
|
||||
function isParamSegment(seg: string): boolean {
|
||||
return (seg.startsWith('[') && seg.endsWith(']')) || (seg.startsWith(':') && !seg.endsWith('*'));
|
||||
}
|
||||
|
||||
/** A route segment that takes the rest of the path: `[...slug]`, or `:slug*`. */
|
||||
function isCatchAllSegment(seg: string): boolean {
|
||||
return (seg.startsWith('[...') && seg.endsWith(']')) || (seg.startsWith(':') && seg.endsWith('*'));
|
||||
}
|
||||
|
||||
function scoreMatch(href: string[], route: string[]): number | null {
|
||||
let score = 0;
|
||||
let i = 0;
|
||||
for (let r = 0; r < route.length; r++) {
|
||||
const seg = route[r]!;
|
||||
if (seg.startsWith('[...') && seg.endsWith(']')) {
|
||||
if (isCatchAllSegment(seg)) {
|
||||
// Catch-all: needs at least one segment and takes the rest.
|
||||
if (i >= href.length) return null;
|
||||
score += href.length - i;
|
||||
@@ -556,7 +566,7 @@ function scoreMatch(href: string[], route: string[]): number | null {
|
||||
}
|
||||
if (i >= href.length) return null;
|
||||
const h = href[i]!;
|
||||
if (seg.startsWith('[') && seg.endsWith(']')) score += 2;
|
||||
if (isParamSegment(seg)) score += 2;
|
||||
else if (h === seg) score += 3;
|
||||
else if (h === '*') score += 1;
|
||||
else return null;
|
||||
|
||||
Reference in New Issue
Block a user