Uh oh!
There was an error while loading. Please reload this page.
fix(docs): migrate apps/docs to the Next 16 proxy file convention - #10867
Conversation
`middleware.ts` is deprecated in Next 16.3.1 and every `pnpm docs:dev` boot printed the migration warning. The runtime had already renamed the slot -- request timing lines read `proxy.ts: 6ms` while the file was still spelled `middleware.ts` -- so this only aligns the repo with the name Next already uses. Produced by the vendor codemod (`@next/codemod@canary middleware-to-proxy`), scoped to `apps/docs`: it renames the file and the default-exported function. The unrelated `packages/runtime/src/middleware.ts` is deliberately untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
✅ ACCEPT — reviewer of record: |
| request | result | what it proves |
|---|---|---|
/docs, no cookie | 500, stack at getPreferredLanguage (proxy.ts:35) / proxy (proxy.ts:87) | the runtime names the renamed file and function — the new slot is genuinely wired |
/docs, Cookie: FD_LOCALE=en | 200 + rewrite, never reaches the throw | positive proof the cookie is READ and short-circuits ahead of the negotiator |
/en/docs, /en | still 307 + Set-Cookie | the pathname-locale branch never calls getPreferredLanguage |
Proving a short-circuit by showing the poison is not swallowed is a much stronger instrument than asserting the happy path. And ablation A's timing-segment count (baseline 3 / after 3 / ablated 0) is the same discipline applied to presence.
Both restore legs verified on disk — marker counts and git diff --quiet HEAD, not an editor's exit code.
⭐ You caught the pipe trap inside your own instrument
my first probe harness did read curl's status through a pipe to
trand reported exit=0 for six failed connections; I rewrote it to redirect first, which is how the wrongnextbinary path got caught
Six failed connections reported as success. That is the standing warning firing on the measuring apparatus rather than the subject — and the rewrite is what surfaced a second, unrelated defect. Recorded because it is the cleanest instance of that class today, and there have been several.
You also verified the filter rather than trusting it (pnpm --filter @objectstack/docs exec pwd printing the real path), which is #10853's lesson applied one day after it was measured.
skip-changeset is right: apps/docs is private: true with privatePackages.tag=false, and the HTTP behaviour is byte-identical — nothing is released.
#10871 — filed, and it is a real one
types:check (fumadocs-mdx → next typegen → tsc) is invoked by nothing, so the docs app is only ever type-checked without typegen; and since tsconfig.json includes .next/types/**/*.ts, its real coverage depends on whether an earlier build happened to populate .next. Your measurement — with .next deleted, typecheck exits 0 — is the whole finding in one line. Triaging separately.
Flipping ready and arming.
Generated by Claude Code
Uh oh!
There was an error while loading. Please reload this page.
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32495027400 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes#10782
Migrates
apps/docsfrom the Next 16 deprecatedmiddlewarefile convention toproxy. Everypnpm docs:devboot on Next16.3.1printed the migrationwarning; the runtime had already renamed the slot — request timing lines read
proxy.ts: 6mswhile the file was still spelledmiddleware.ts— so this onlyaligns the repo with the name Next already uses.
The change
Produced by the vendor codemod, scoped to the docs app:
Its entire output is two paths, both under
apps/docs/:— the file rename plus
export default function middlewarebecomingexport default function proxy. I scoped it toapps/docsrather than running itat the repo root on purpose: the unrelated
packages/runtime/src/middleware.tsisnot a Next file-convention file, yet it matches the codemod's
/(^|[/\\])middleware\./path test. It is untouched here.On top of the codemod, one doc-comment fix in the same file so it no longer
describes itself as middleware. No other file in the repo changes.
⭐ Verification — the rename is the easy half
A file-convention rename that silently stops being invoked is the failure this
change's shape invites, and it would be invisible: the deprecation warning
disappearing is what success looks like either way — deleting the file achieves it
too. So the evidence below is behavioural, from real requests against a real
next devboot, with ablations.Four boots on port 43782, each with a cold
.next. Full probe matrix:/en/docs,/docs(with and withoutFD_LOCALE),/zz/docs,/en,/api/search.1. The warning is gone
Baseline boot (
middleware.ts) printed:After the rename, grepping the boot log for
deprecat/middleware-to-proxyreturned
GREP_EXIT=1(absent). Boot output is otherwise identical.2. The locale negotiation still fires — behaviour is byte-identical
Diffing the baseline and post-rename response matrices (status line,
location,set-cookie,x-middleware-rewrite, page title) gaveDIFF_EXIT=0— identical:GET /en/docs307,location: /docs,set-cookie: FD_LOCALE=en; Path=/; SameSite=laxGET /docs(Accept-Language: zh-CN, no cookie)200,x-middleware-rewrite: /en/docs, titleDocumentation | ObjectStackGET /docs(Cookie: FD_LOCALE=en)200,x-middleware-rewrite: /en/docsGET /zz/docs404,x-middleware-rewrite: /en/zz/docsGET /en307,location: /,set-cookie: FD_LOCALE=enGET /api/search200, no rewrite, noproxy.ts:timing segment (matcher-excluded)That covers the whole negotiator: the
FD_LOCALEcookie is written(
/en/docs,/en),i18n.languagesmembership is consulted (/en/docsstrips the prefix,
/zz/docsdoes not and is rewritten instead — the two pathsdiverge on exactly
i18n.languages.some(...)), andhideLocale: 'default-locale'drives the redirect-vs-rewrite split.
3. It is the new file doing the work — two ablations
The timing line alone proves nothing here:
proxy.ts: 6mswas already printedwhile the file was named
middleware.ts. So it needed ablating.Ablation A — park
proxy.tsoutside the app. Mutation confirmed on disk:grep -rl FD_LOCALE apps/docs --include=*.tsreturned 0 files. The entire matrixcollapses —
/en/docsgoes307to200with noSet-Cookie;/docslosesx-middleware-rewriteand renders the landing page (titleObjectStack — Apps small enough for AI to hold whole.) instead of the docs index,because
[lang]captures"docs";/zz/docsgoes404to500;/engoes307to200. And theproxy.ts:timing-segment count per boot log:Restored;
git diff --quiet HEADconfirmed byte-identical.Ablation B —
throw new Error('OS_ABLATION_NEGOTIATOR')injected immediatelyafter
const negotiator = new Negotiator({ headers: negotiatorHeaders });.Mutation confirmed on disk (injected marker count 1, anchor count 1,
git diffdirty). One boot, three-way discrimination:
GET /docswithout the cookie gave500, and the dev server names the fileand the renamed function directly:
Negotiatorconstruction is on the live request path inproxy.ts.GET /docswithCookie: FD_LOCALE=engave200+x-middleware-rewrite,never reaching the throw — positive proof the
FD_LOCALEcookie is read andshort-circuits ahead of the negotiator.
/en/docsand/enstill gave307+Set-Cookie— the pathname-locale branchnever calls
getPreferredLanguage, as written.Restored via
git checkout HEAD --; marker count0, anchor intact,git statusclean.
Gates
Union re-derived with
node scripts/pm/dispatch-gates.mjs(no paths — it takesits own change set from the merge base) and run on final HEAD
353cb948e0, treeclean. Each gate's own verdict line:
Dependency closure built first (
pnpm --filter '@objectstack/docs^...' build,exit 0), then
pnpm --filter @objectstack/docs typecheck(tsc --noEmit), exit 0.The filter was probed rather than trusted —
pnpm --filter @objectstack/docs exec pwdprinted/home/user/objectstack-issue-10782/apps/docs, so it matched a realproject rather than printing
No projects matched the filtersand exiting 0.Changeset
skip-changeset.apps/docsisprivate: trueand.changeset/config.jsonsetsprivatePackages: { version: true, tag: false }, so@objectstack/docsis neverpublished. The docs site's HTTP behaviour is byte-identical (matrix above), so
nothing a reader or a package consumer can observe changes — the only delta is
that a dev-boot warning stops printing. That is this repo's own stated case for
the label: "such a PR releases nothing, so by the workflow's own prescription it
takes the label" (
.github/workflows/lint.yml).Generated by Claude Code