Skip to content

fix(docs): delete the shadowed root route; proxy.ts owns / - #12351

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-12255-shadowed-root-route
Aug 25, 2026
Merged

fix(docs): delete the shadowed root route; proxy.ts owns /#12351
os-zhuang merged 1 commit into
mainfrom
claude/issue-12255-shadowed-root-route

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#12255

apps/docs/app/page.tsx contained redirect('/docs') and never ran. proxy.ts
rewrites / to /en before routing, so app/[lang]/page.tsx serves the homepage.
It was code that grep finds and the runtime never executes — AGENTS.md
"Route & surface ownership" rule 1 — and its content was also wrong for today's
site, which no longer forwards / to the docs.

The decision, and what decided it

The card left two honest end states open: delete the file, or keep it as a real
fallback. The deciding question was measurable — with app/page.tsx deleted,
what does / do under a production build, and is there a reachable configuration
where deletion costs something?
Four configurations were built and served.

What was measured

Local production build only. Production has not redeployed since ~16:55 on
2026-08-25 (#12333), so the live site is evidence about a two-hour-old build and
was not used. Each row is its own pnpm --filter @objectstack/docs build
(Next 16.3.1, exit 0) followed by next start and curl:

app/page.tsxproxy.tsGET /GET /enGET /docs
present (main today)present200 text/html, 72117 B, homepage, canonical https://objectstack.ai307 to /200, 211597 B
absent (this PR)present200 text/html, 72117 B, homepage, same canonical307 to /200, 211597 B
absentabsent404, 8686 B, Next default error page200 homepage404
presentabsent307 to /docs — and /docs is itself a 404200 homepage404

Route table from the same builds: with the file present the build emits a /
entry; without it, / is gone and /en is still prerendered under /[lang].
The proxy line Proxy (Middleware) is present in rows 1–2 and absent in rows 3–4
(counted from the build logs, not assumed).

Byte-level check on row 2 versus row 1: the two / responses are 72117 bytes
each and differ in exactly two spans, both of them the Next build id
(0YebRTq5hijtQuB6iqeqN versus 3CUsAVIGaUla2OR4S-KdV), which changes on every
build. /docs — untouched by this diff — differs in the same way and only that
way, which is the control that identifies the difference as build-id noise rather
than a content change. RSC and prefetch requests to / (RSC: 1,
Next-Router-Prefetch: 1), a query-string request, and HEAD / were probed in
both rows and match status-for-status. /sitemap.xml and /robots.txt are
byte-identical (md5 equal) across the two.

What would have changed the answer

A reachable configuration in which / degrades and the rest of the site does
not — that is what a fallback is for. Row 3 shows there is no such
configuration: when the proxy does not run, every unprefixed path 404s,
/docs included; only /en/** answers. So the file cannot be a partial-
degradation safety net.

Row 4 measures the fallback option on its merits and it loses on its own terms:
with the file present and the proxy not running, / answers 307 to /docs,
which is a 404 in that same configuration. The "fallback" forwards one URL out
of a 404 and into another — and it makes the failure quieter, because /
answers 307 instead of 404 while the site is comprehensively broken. That is
the wrong direction for AGENTS.md rule 3, "absence must be loud". Rewriting the
file to redirect to /en instead would have the same defect: it only matters in
a configuration where nothing else works, and it would still soften the signal.

Had row 3 shown / 404ing while /docs kept working, the fallback would have
won and this PR would have rewritten the file instead of removing it.

proxy.ts is untouched: the measurement says the fix does not belong there. The
proxy handles / correctly, and its matcher is #12233's surface.

Why it is worth doing at all

Epic #12243 has cards adding metadataBase, canonical links and JSON-LD to the
homepage (#12234, #12240). An agent that puts the homepage's canonical in
app/page.tsx ships a green PR that changes nothing served; that trap has had to
be written into three dispatch prompts as a warning. Deleting the file closes it
at the source — grep no longer finds a root page to reason from.

Verification

Gate union re-run after the final commit, at afbe56173:

checkverdict line
pnpm check:docs-locale-catch-allVERDICT command-exit 0 — "1 top-level dynamic segment(s), 1 guarded; dotted paths bypass proxy.ts: true"
pnpm check:page-declaration-shapeVERDICT command-exit 0 — "34 page entries across 2188 sources … all reach the kernel through a discoverable declaration"
pnpm check:published-filesVERDICT command-exit 0
pnpm check:test-source-aliasVERDICT command-exit 0
pnpm check:type-source-resolutionVERDICT command-exit 0
pnpm check:nul-bytesVERDICT command-exit 0 — "scanned 6833 text file(s) … no raw ASCII control bytes"
pnpm --filter @objectstack/docs typecheckVERDICT command-exit 0
pnpm --filter @objectstack/docs buildexit 0 (typescript.ignoreBuildErrors: false, so the build is a type gate too)

The five check:* families are the ones node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
derives for this diff; re-derived after the commit and unchanged.

Declared narrowing — lint.pnpm lint (eslint . --no-inline-config over
the whole repo) was not run; CI runs it. Narrowed to every hand-written source
file in the app this diff touches: eslint apps/docs/app apps/docs/lib apps/docs/components apps/docs/proxy.ts --no-inline-config --format json
24 files linted, 0 errors, 0 warnings (count read from the JSON output, not
from a summary line). The narrowing excludes nothing that this diff could move:
eslint.config.mjs is unchanged and, by its own docblock, "never enables
type-aware linting (no parserOptions.project, no typed
@typescript-eslint rules) for ANY file", so no untouched file's verdict can
depend on a file being deleted. The diff also adds and modifies zero files, so
the set eslint lints after this change is exactly the set before it, minus one.

Declared narrowing — verification ran UNLOCKED.scripts/pm/os-verify-lock.sh
could not take the shared verify lock on this host: no usable flock (the lock
is Linux-only; a stock macOS does not ship util-linux). Every command above ran
through the entry point, which reported UNLOCKED (declared) each time. No
serialization guarantee held for these runs.

Landing notes

Docs-site only, publishes nothing — no changeset; skip-changeset applied.
Nothing outside apps/docs/app/page.tsx is touched: app/[lang]/** is #12240's
surface right now and proxy.ts is #12233's, and neither is in this diff.

Generated by Claude Code

`apps/docs/app/page.tsx` called `redirect('/docs')` and never ran. `proxy.ts`
rewrites `/` to `/en` before routing, so `app/[lang]/page.tsx` serves the
homepage. The file was code `grep` finds and the runtime never executes --
AGENTS.md "Route & surface ownership" rule 1 -- and its content was also wrong
for today's site, which no longer forwards `/` to the docs.
Measured on a local production build (`next build && next start`, Next 16.3.1),
four configurations, `GET /`:
page.tsx proxy GET / GET /docs
present present 200 homepage, 72117 B 200
absent present 200 homepage, 72117 B 200
absent absent 404 (Next default, 8686 B) 404
present absent 307 -> /docs, which is itself 404 404
Deleting it changes nothing served: the `/` response is byte-identical apart
from the Next build id, which changes on every build (`/docs`, untouched by
this diff, differs in exactly the same way -- the control).
Keeping it as a "fallback" was measured too, and it is not one. In the only
configuration where the file would run -- the proxy not matching `/` -- every
unprefixed path already 404s, `/docs` included, so the file rescues one URL out
of a 404 and into another. It also makes that failure quieter: with the file
present `/` answers 307 instead of 404, masking a proxy that has stopped
running. Absence must be loud (rule 3), so the honest end state is deletion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@os-zhuangos-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 25, 2026
@github-actionsgithub-actionsBot added size/xs documentation Improvements or additions to documentation labels Aug 25, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 25, 2026 20:25
@os-zhuang
os-zhuang added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit 1f94049Aug 25, 2026
29 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-12255-shadowed-root-route branch August 25, 2026 20:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xsskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] docs site: apps/docs/app/page.tsx is a shadowed route — the proxy rewrites / to /en, so it never runs

1 participant

@os-zhuang