ReviewOS

also looking at this

stacks/stx

fix(serve): an earlier page root wins at a dynamic path too

#1970
Closed chris wants to merge fix/app-dynamic-routes-win into main
0 files +0 -0
9 days ago

Problem

In a Stacks app (hq.training), resources/views/password/reset/[token].stx did not override the framework's defaults/resources/views/password/reset/[token].stx. The dev server rendered the framework default, even though the generated routes.ts listed the app file for /password/reset/:token. Static overrides such as forgot-password.stx worked.

getRoute sorts dynamic candidates with routeSpecificity(filePath) on the whole path. Every directory of the page root counts as a static segment (+100), so a page under a deeper root outranks the same route under a shallower one. Stacks passes ['resources/views', '<framework>/defaults/resources/views'], and the default wins by being further down the filesystem.

Page middleware had the same precedence bug. detectPageMiddleware only indexed pages that declared middleware, and it ran under Promise.all, so the last read to finish won. A default page's definePageMeta({ middleware }) could land on the app page that replaced it, or clobber the app's own.

Fix

  • pageRootOf(file, patterns) / orderDynamicRouteFiles(files, patterns), both exported and pure: rank on the path relative to its root, and give ties to the earlier root, then to discovery order.
  • Strategy 6 of getRoute iterates in that order.
  • indexPageMiddleware records every page, including ones with no middleware. The first root owns each static URL, and dynamic patterns are indexed in the order getRoute tries them.

Test

test/serve-page-root-precedence.test.ts covers the pure ordering and a real serve() with an app root ahead of a deeper defaults root:

  • the app page renders at a dynamic path both roots define
  • a default nobody overrode is still served
  • the overriding page's middleware runs, not the default's (dynamic and static), and the app's own guard still holds

Against the previous serve.ts, the first case renders DEFAULT-RESET and the middleware case answers 403. The full packages/bun-plugin suite passes (256 tests).

Stacks needs a release with this and a bun-plugin-stx bump. The companion Stacks PR has the details.

🤖 Generated with Claude Code

0 changed files on the files tab, with 0 review threads.

Sign in to comment on this pull request.