Repository navigation
Description
Summary
Pre-1.0 public-API decisions surfaced by the multi-agent review. Each is a product/owner call (signature, naming, or surface change), so they were deferred from the stabilization PR. Userland before/after below per the PR-description guide. Best resolved before first stable external adoption (CLAUDE.md API Hygiene).
1. router.use() — Middleware alias + pattern-param overload
Rango.use() is typed with MiddlewareFn (the curated re-export surfaces the friendlier Middleware alias by name), and .use('/users/:id', mw) has no pattern-generic overload, so ctx.params is untyped.
// today: ctx.params is Record<string,string> (untyped keys)
router.use("/users/:id", (ctx) => { ctx.params.id /* string, but not known-present */ })
// proposed: pattern-generic overload -> ctx.params.id knownDecision: add the typed pattern overload + rename to Middleware in the public signature, or document the gap as intentional.
2. useAction() return type is @internal
useAction()'s return type (TrackedActionState/ActionLifecycleState) carries @internal — may change without notice, yet it is the public hook's return type — consumers can't name it.
const action = useAction(addToCart);
// ^? type is reachable only as an @internal alias — can't be written in a consumer signatureDecision: promote/alias a stable public return type from ./client.
3. useRouter().prefetch() can't opt into source-scoped prefetch
<Link prefetchKey=":source"> exists, but the imperative router.prefetch(url) has no options arg.
router.prefetch("/product/1"); // today
router.prefetch("/product/1", { key: ":source" }); // proposed parity with <Link>4. buildEnv: "auto" permitted on the Node preset at the type level
BuildEnvOption allows "auto" on RangoNodeOptions, but "auto" (wrangler getPlatformProxy()) is Cloudflare-only; misuse is caught only at build time. Decision: move buildEnv onto per-preset interfaces (CF with "auto", Node without).
5. Plugin name vite-plugin-poke breaks the @rangojs/router:* convention
The public poke() plugin's name field is vite-plugin-poke; consumers keying on plugin name for ordering/filtering won't match the @rangojs/router:* family. Decision: rename before 1.0.
6. ./build entrypoint re-exports pipeline internals
./build exposes realm-crossing internals (TrieNode/TrieLeaf, etc.) indistinguishable from intended public API. Decision: move internals to an internal subpath or @internal-tag them. (generateManifestCode is documented public API — keep it.)
7. createScanFilter/ScanFilter is half-wired
DiscoveryState.scanFilter is permanently undefined and createScanFilter is never invoked by the Vite plugin, while the public createScanFilter/ScanFilter/findTsFiles API IS used by the CLI/build layer. Decision: complete the feature (wire it into discovery) or trim the internal seam.
8. mergeHandlerUse skips mount-site allow-list validation
The explicit-only / merged-explicit branches return explicitUse() output unvalidated, so a structurally-valid-but-prohibited item (e.g. middleware() inside a parallel slot) can slip through.
parallel({ "@modal": () => [middleware(auth)] }) // prohibited, but not currently rejected on the explicit pathDecision: validate there too (newly throws; breaks a referential-identity test that must be updated) or document the asymmetry.
9. profile-registry is mostly dead after removing cache("profileName")
With the build-breaking cache("profileName") overload removed (shipped), the global _profiles registry / getCacheProfile() reader is largely orphaned (still used by "use cache: <profile>" resolution + setCacheProfiles() from router.ts). Decision: audit whether the global registry is still needed or can be trimmed.
10. return redirect() from a server action forces as any
The package's own example actions cast return redirect() to any. Ergonomics gap in the action return type.
"use server";
export async function checkout() { return redirect("/done") as any; } // the `as any` shouldn't be needed
Activity
fix(router): keep build internals on ./build (revert #569.6) + throw …
fix(router): review follow-ups — #567 dual-mode e2e, #569 pre-1.0 API…
fix(router): matrix-critical review follow-ups — #568 trie-suffix, #569…
ivogt commentedon Jun 14, 2026
All 10 decisions resolved (shipped in #573 + #575). Resolution map:
router.use(pattern, mw)typingExtractParams(bare form unchanged)useAction()return typeActionState/ActionLifecycleState;TrackedActionStatestays internalprefetch(url, { key: ":source" })(parity with<Link>)buildEnv: "auto""auto", Node rejects at type levelvite-plugin-poke→@rangojs/router:poke./buildinternalsbuildRouteTrie/buildPerRouterTrie/collectFallbackClientRefs/TrieNode/TrieLeaffrom the barrel; discovery imports them via direct source path (realm-safe). Pinned bypublic-surface.test.tsscanFilterdiscovery: { include, exclude }compiled intostate.scanFiltermergeHandlerUsevalidation gap_profiles/setCacheProfiles/getCacheProfile; kept request-scopedresolveCacheProfilesreturn redirect()in actionsthrow redirect(); return-form test actions throw; skill guidance strengthenedClosing as completed.
ivogt commentedon Jun 14, 2026
Re-closing: all 10 decisions verified complete on main — see the resolution map above (#573 + #575). The earlier close was deliberate, not accidental.