Skip to content

docs(web/guides): correct routing guides to verified router behavior (audit batch 2) - #3092

Merged
bpamiri merged 1 commit into
developfrom
peter/docs-audit2-routing-guides
Jun 12, 2026
Merged

docs(web/guides): correct routing guides to verified router behavior (audit batch 2)#3092
bpamiri merged 1 commit into
developfrom
peter/docs-audit2-routing-guides

Conversation

@bpamiri

@bpamiri bpamiri commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Guide-behavioral-audit batch 2, work item p1-10-routing. Fixes the four docs-affected findings (R4, R14b, H1, H13) plus the docs side of the two code-broken findings (R11/R12), all verified live on Lucee 7 (docker, H2/SQLite) and Adobe 2023 (SQLite).

Corrections

basics/routing.mdx

  • Namespaced sections (R11/R12, code-broken — docs side): the documented .namespace(name="admin", callback=function(map){...}) form never executes its callback — namespace() forwards to scope(), which has no callback handling — and the scope stays open, so the following outer .resources("posts") was silently absorbed into /admin (public /posts → 404 Wheels.RouteNotFound). Rewrote the example to the verified manual .namespace("admin") … .end() form (the only form framework specs use, mapperSpec.cfc:165), added a caution Aside citing #3072, and noted that controllers under app/controllers/admin/ need extends="app.controllers.Controller" (bare extends="Controller" fails to resolve from the subfolder on Lucee). Corrected {test:compile} block passes the harness.
  • Resource routes (R4, docs-wrong): $addMemberRoutes registers BOTH PATCH and PUT for update, and every route gets a .[format] twin — changed "PATCH /posts/:key" to "PATCH/PUT /posts/:key", softened "seven REST routes" to "the seven REST actions", and mentioned the .[format] twins.
  • Constrain pattern placeholders (R14b, docs-wrong): $patternToRegex compiles placeholders to ([^\./]+) — verified /posts/20.26/04 does NOT match an unconstrained placeholder. Replaced "any non-slash string matches" with "any string without a slash or dot ([^\./]+ — dots are reserved for the optional .[format] suffix)".

core-concepts/how-routing-works.mdx

  • Match algorithm (H13, docs-wrong; H1, both): routes are compiled at boot, and static (placeholder-free) routes resolve via an O(1) exact-path index BEFORE the ordered placeholder scan (Mapper.cfc:262-278, Dispatch.cfc:196-211, introduced by ee13e31e0) — verified: /posts/featured hits featured in BOTH declaration orders, and first-declared wins between identical static patterns. Rewrote "The match algorithm" as the two-step static-first/then-declaration-order description and cited #3073 for the docs-vs-behavior reconciliation.
  • Order rules (H1): bullet 1 now states the literal-beats-placeholder rule is enforced by the router itself; bullet 3 (featured-must-come-first) inverted to describe actual behavior — order only genuinely decides placeholder-vs-placeholder conflicts — while keeping declare-first as style advice.
  • Expansion table intro (R4): notes the .[format] twin of every row; existing PATCH/PUT row kept (already correct).

Evidence

Raw verifier output: guide-behavioral-audit task wp6mteswq, claims R4/R14b/H1/H13 (plus R11/R12) — manifest /tmp/p1b2-docs-manifest.md, item p1-10-routing.

Verification

pnpm verify:docs src/content/docs/v4-0-0/basics/routing.mdx src/content/docs/v4-0-0/core-concepts/how-routing-works.mdx → exit 0, 7 passed, 0 failed (all tagged blocks, including the rewritten namespace {test:compile} block).

Issue refs: #3072, #3073

🤖 Generated with Claude Code

Guide-behavioral-audit batch 2 (p1-10-routing) fixes for basics/routing.mdx and
core-concepts/how-routing-works.mdx, verified live on Lucee 7 and Adobe 2023:

- Namespaced sections: replace the .namespace(name="admin", callback=...) example
  with the working .namespace("admin")...end() form. scope()/namespace() silently
  ignore callback= and the unclosed scope swallows subsequent routes (#3072); add a
  caution Aside and the extends="app.controllers.Controller" note for subfolder
  controllers.
- Match algorithm + order rules: document the static-route O(1) index — literal
  paths resolve before the ordered placeholder scan regardless of declaration
  position; declaration order governs placeholder-vs-placeholder and ties between
  identical static patterns (#3073). Invert the /posts/featured order-rules bullet
  accordingly.
- Resource routes: update registers both PATCH and PUT; seven REST actions (not
  seven routes) and every route gets a .[format] twin.
- Placeholder default match is [^\./]+ (non-slash AND non-dot), not "any
  non-slash string".

verify:docs: 7 tagged blocks pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer

TL;DR: This PR corrects the two routing guides to match verified router behavior (namespace callback trap, PATCH+PUT for update, .[format] twins, [^\./]+ placeholder default, static-route-index precedence). I verified every behavioral claim against the framework source and all of them are accurate; the cited issues (#3072, #3073) exist and match. Verdict: comment — one minor arithmetic/phrasing nit in the new prose, nothing blocking.

Verification of the diff claims (all confirmed)

  • PATCH/PUT both map to update$addMemberRoutes registers both verbs (vendor/wheels/mapper/mapping.cfc:102-105).
  • .[format] twins(.[format]) is an optional segment that $match expands into a separate route row (vendor/wheels/mapper/matching.cfc:449-470), and mapFormat defaults to true (vendor/wheels/Mapper.cfc:19).
  • namespace() has no callback support — its signature is (name, package, path) and it forwards to scope(), which never invokes a callback (vendor/wheels/mapper/scoping.cfc:106-114); an unclosed scope stays on the stack, so the "subsequent routes get swallowed" warning is correct. The rewritten example uses the same manual .namespace("admin")…end() form the framework specs use (vendor/wheels/tests/specs/mapperSpec.cfc:165).
  • Placeholder default is [^\./]+$patternToRegex (vendor/wheels/Mapper.cfc:117).
  • Static-first matching$addRoute indexes placeholder-free patterns per-method with first-registration-wins (vendor/wheels/Mapper.cfc:238-278), and $findMatchingRoute consults that index before the ordered scan (vendor/wheels/Dispatch.cfc:197-212). So the inverted /posts/featured order-rules bullet and the two-step match-algorithm description are both accurate.
  • extends="app.controllers.Controller" for subfolder controllers — matches existing prior art: every examples/starter-app/app/controllers/admin/*.cfc, the CLI admin templates (cli/lucli/templates/admin/controller.txt), and the v3 nesting-controllers guide.
  • Issues #3072 and #3073 are open and their titles describe exactly the behaviors cited.

Docs

  • web/sites/guides/src/content/docs/v4-0-0/core-concepts/how-routing-works.mdx:32minor nit: "Each row is also registered with a .[format] twin … so the actual table holds twice as many rows." The table shows 7 rows but the PATCH/PUT row is two route registrations, so the actual count is 8 base × 2 = 16, not 7 × 2 = 14. Same family of imprecision at line 28 ("registers an entry per REST action" — update registers two). The sibling phrasing in basics/routing.mdx:54 ("more rows than seven") is safely vague; consider mirroring it here, e.g. "…so the actual table holds twice as many entries as the routes generated (and update itself is two routes, PATCH and PUT)." Non-blocking — the doubled-per-route claim is itself true.

Commits

Single commit docs(web/guides): correct routing guides to verified router behavior — valid type, header ≤ 100 chars, DCO sign-off present and matching the author. No changelog fragment needed for a docs guide correction.

No correctness, cross-engine, security, or test findings — docs-only change, and the {test:compile} blocks were re-verified by the PR's own verify:docs run (harness confirmed at web/sites/guides/scripts/verify-docs/).

@bpamiri
bpamiri merged commit f180fb5 into develop Jun 12, 2026
14 checks passed
@bpamiri
bpamiri deleted the peter/docs-audit2-routing-guides branch June 12, 2026 11:33
bpamiri pushed a commit that referenced this pull request Jun 12, 2026
…sides now that scope() honors it

#3092 and #3095 landed caution asides on develop (routing.mdx, cors.mdx,
rate-limiting.mdx) stating scope()/namespace() silently ignore callback=,
tracked in #3072. This branch fixes exactly that, so merge develop in and
rewrite the three statements: document the now-supported callback= form
(auto-closing the scope), keep the unclosed-scope warning for the explicit
.end() form, and note that releases <= 4.0.3 still ignore callback=. Adds a
compile-tested namespace(callback=) example to routing.mdx, matching the
already-correct callback examples in route-model-binding.mdx and
multi-tenancy.mdx.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <petera@pai.com>
bpamiri pushed a commit that referenced this pull request Jun 12, 2026
…cope (#3100)

* fix(router): scope()/namespace() honor callback= and auto-close the scope

scope() declared no callback argument, so the documented `.scope(path="/x", callback=function(map){...})` form (and namespace()/package()/controller(), which forward to scope()) silently dropped it: the callback never ran (its routes 404'd) and nothing closed the scope, so every route declared after inherited the scope's path prefix and middleware.

scope() now consumes callback the same way group() does — runs callback(this) then auto-end()s — using the cross-engine-proven IsCustomFunction guard. Adds MapperRobustnessSpec coverage for the scope/namespace/package callback forms plus the no-swallow and no-middleware-leak guarantees for routes declared after the block.

Refs #3072

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>

* docs: update scope()/namespace() callback= examples in CLAUDE.md (#3072)

Three focused updates:
- Anti-pattern #3: note that scope()/namespace()/package()/controller()
  also accept callback= and auto-close the scope (not just resources())
- Middleware Quick Reference: use callback form for route-scoped .scope()
- Route Model Binding: use callback form for the .scope(binding=true) example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>

* docs(web/guides): flip the three #3072 'callback= silently ignored' asides now that scope() honors it

#3092 and #3095 landed caution asides on develop (routing.mdx, cors.mdx,
rate-limiting.mdx) stating scope()/namespace() silently ignore callback=,
tracked in #3072. This branch fixes exactly that, so merge develop in and
rewrite the three statements: document the now-supported callback= form
(auto-closing the scope), keep the unclosed-scope warning for the explicit
.end() form, and note that releases <= 4.0.3 still ignore callback=. Adds a
compile-tested namespace(callback=) example to routing.mdx, matching the
already-correct callback examples in route-model-binding.mdx and
multi-tenancy.mdx.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <petera@pai.com>

---------

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Signed-off-by: Peter Amiri <petera@pai.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Amiri <petera@pai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant