You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Every page in all seven locales declares lang="en" on the root html element #168
(Rewritten: the first version of this card was written with angle-bracket markup and the issue sanitizer stripped it, emptying the code block and three sentences. Restated below without any.)
What is on main
apps/docs/app/layout.tsx:19 opens the root html element with a hardcoded language attribute — literally lang="en" — alongside suppressHydrationWarning. That is the only html element in the app.
apps/docs/app/[lang]/layout.tsx is the only place the locale route segment is read, and it renders DocsRootProviderinside that element. It never overrides the attribute, and nothing else does.
So docs.objectos.ai/zh-Hans/docs/build/interface/views serves Chinese body text while declaring itself English, and the same holds for ja, de, es, fr and ko. Six of the seven locales are mislabelled on every page.
Why it matters here
This site has invested in seven locales, a locale negotiator (middleware.ts), reciprocal hreflang and per-locale canonical URLs (lib/seo.ts:23-30). The language attribute on the root element is the signal that tells a crawler what language the document it just fetched is actually written in — and right now it contradicts every one of those other signals on six locales out of seven.
The same line is also an accessibility defect: it is what a screen reader uses to choose a speech synthesizer, so Chinese and Japanese pages are currently read aloud by an English voice.
What to change
Set the attribute from the route segment. The locale is already in hand in app/[lang]/layout.tsx. Moving the root html and body shell down into the [lang] layout, or otherwise threading the segment up to the root layout, are both acceptable — pick whichever keeps a single root element and does not break the hideLocale: 'default-locale' routing in middleware.ts:134-166.
zh-Hans is the BCP 47 tag already used throughout lib/i18n.ts:17 and is valid as a language attribute value as-is. Do not rewrite it to zh-CN on the way through.
Verification
Fetch one page per locale from a local build and confirm the declared language matches the locale segment in the URL.
Confirm the prefix-less English paths (/docs/..., served by the internal rewrite at middleware.ts:156-170) still declare en.
changed the title [-]Every page in all seven locales declares `<html lang="en">`[/-][+]Every page in all seven locales declares lang="en" on the root html element[/+]on Aug 25, 2026
Claim: PM loop round 1
Session: session_01G5zjYc2BoFV2NjKBBapC7C
Branch: claude/issue-168-html-lang-locale
Worktree: objectos-issue-168
Domain: n/a (objectos carries no domain:* lanes)
File surface: apps/docs/app/layout.tsx, apps/docs/app/[lang]/layout.tsx (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus — PM judgement; scripts/pm/dispatch-gates.mjs lives in the objectstack repo and has no counterpart here, so no --tier reading is quoted.
Clause-②: no
Serial constraints cleared: no in-flight claim touches either layout file. #169 and #170 are dispatched in the same batch and their file surfaces do not intersect this one. Read coupling checked: neither sibling asserts behaviour of the root layout shell.
Claim: dev agent (os-dev subagent dispatched by the PM claim above)
Session: session_01G5zjYc2BoFV2NjKBBapC7C — I run as a mode:subagent inside the PM's session and am not issued a distinct session id, so this is the same id the PM comment carries. Distinguishing marker for this claim is the branch + worktree below.
Branch: claude/issue-168-html-lang-locale (pushed empty at base ee743793bc61b1ce4c04a2e0afb46c105ede310b before the first edit)
Worktree: /home/user/objectos-issue-168
File surface: apps/docs/app/layout.tsx, apps/docs/app/[lang]/layout.tsx — stop on breach and report.
Prior comments read before starting: 1 (the PM claim). No earlier dev claim with a different marker.
{
"issue": 168,
"status": "rework",
"branch": "claude/issue-168-html-lang-locale",
"pr": "https://github.com/objectstack-ai/objectos/pull/181",
"head_sha": "116f691",
"base_at_branch": "ee74379",
"premise_still_valid": true,
"premise_evidence": "Reproduced on a clean origin/main worktree at ee74379 before editing: /zh-Hans/docs, /ja/docs, /de/docs, /es/docs, /fr/docs, /ko/docs all declared en. app/layout.tsx:19 was the only root html element in the app (grep over apps/docs for html open tags returned exactly 1 hit).",
"files_changed": [
"apps/docs/app/layout.tsx",
"apps/docs/app/[lang]/layout.tsx"
],
"file_surface_respected": true,
"gates": {
"type-check": "pnpm run type-check on 116f691 -> exit 0 (captured via redirect-then-read, not through a pipe). Script name echoed in output, so it really ran.",
"build": "pnpm run build on 116f691 -> exit 0. Printed verdicts: 'Compiled successfully in 43s', 'Finished TypeScript in 6.1s', 'Generating static pages using 3 workers (740/740)'. 740 prerendered pages, unchanged from main -> static generation not lost.",
"runtime-locale-probe": "Production build served locally at 116f691. /docs=en, /docs/architecture=en, /zh-Hans/docs=zh-Hans, /ja/docs=ja, /de/docs=de, /es/docs=es, /fr/docs=fr, /ko/docs=ko, /privacy=en, /zh-Hans/privacy=zh-Hans, /terms=en, /ja/terms=ja. /en/docs still 307s to /docs. All 200s.",
"ablation-language-guard": "Against the COMMITTED state. Mutation proven on disk before any reading: object hash 9380230->463d3ad, guarded-occurrence grep 1->0, raw-occurrence grep 0->1. control=en, ablated=foo.bar, restored=en with hash back to 9380230 and git diff HEAD empty for the path.",
"ci": "in_progress - not awaited, per dispatch contract (report at draft-PR time)."
},
"not_run": {
"lint": "apps/docs declares no lint-able gate beyond type-check and build in package.json; repo has no changeset flow and no packages/. No changeset added, as instructed.",
"skip-changeset-label": "not applied - that label mechanism is objectstack-specific; minting it here would fabricate a repo label."
},
"deviations": [
"PR body first line is 'Part of #168', NOT 'Fixes #168' as the dispatch specified. Reason: the branch knowingly leaves one regression (below), so merging must not auto-close the card. Flagging the conflict rather than silently choosing.",
"PM mechanism assumption 2 is FALSIFIED in a bounded way. Moving the shell into the [lang] layout IS viable under the App Router - app/page.tsx, robots.ts and sitemap.ts are unaffected, and the root redirect still answers 307 to /docs exactly as on main - but routes outside the locale segment lose their root element, and one of them renders HTML: the 404. main: /no-such-page 404 declares en. This branch: no root element at all. In next dev the same request returns Next's __next_error__ document ('Missing html and body tags in the root layout'); measured, because that validator is dev-server-only. Remedy is a new apps/docs/app/not-found.tsx (~20 lines) rendering its own root element and body. That file is outside the granted surface and the dispatch said to stop and report rather than widen, so it is deliberately absent. app/global-not-found.tsx is the tidier convention but sits behind experimental.globalNotFound (default false), which would mean editing next.config.mjs.",
"PM mechanism assumption 1 CONFIRMED: exactly one root html element existed (app/layout.tsx:19); nothing in [lang]/layout.tsx or any Fumadocs provider set one.",
"Added a 4-line documentLanguage() guard inside the granted file surface, not requested by the card. Justified by measurement, not caution: middleware.ts's matcher skips any path containing a dot, so /foo.bar/privacy reaches the layout with lang='foo.bar' and answers 200. Ablation above shows the raw segment otherwise becomes the declared language.",
"Process incident, self-inflicted, fully recovered: I ran the first ablation against UNCOMMITTED edits with a trap restoring to HEAD, which discarded my [lang]/layout.tsx work (exactly the failure my contract warns about). Rewrote the file, committed FIRST, then re-ran the ablation against the committed state. Final tree verified clean; the readings quoted above are all post-commit.",
"Process incident, contained: my kill calls killed the npx wrappers but not the next-server children, so one 'AFTER' probe was served by a stale build. Detected via EADDRINUSE, re-measured on a clean rebuild of 116f691. While cleaning up I found pid 29761 running in /home/user/objectos-issue-170 - a SIBLING agent's worktree - and left it untouched; killed only my own PIDs by number, never by process name.",
"Duplicate-search channel: REST returned 403 for this session ('GitHub access is not enabled for this session'), so the pre-filing search used the MCP list_issues channel instead of REST."
],
"adjudications_honored": {
"lang-from-route-segment": "yes",
"single-root-element": "yes on every locale route; NO on the 404 route - see deviations",
"zh-Hans-not-rewritten": "yes - passes through verbatim; cross-checked against www.objectos.ai src/lib/i18n.ts:37 where htmlLang is the identity function, so the two repos agree",
"hideLocale-routing-unbroken": "yes - /en/docs still 307s to /docs; prefix-less /docs and /docs/architecture still declare en"
},
"open_questions": [
{
"question": "The 404 route needs a root element restored. May the file surface be extended by one new file, apps/docs/app/not-found.tsx?",
"options": [
"A - extend this card's surface by that one file; I add it on this branch and the card closes complete",
"B - keep the surface as granted; land PR #181 as-is and track the 404 shell as its own card, accepting that between merge and that fix, 404s carry no root element and next dev shows the missing-root-layout error",
"C - abandon the move-down and keep the hardcoded attribute (rejected: no alternative preserves static generation, since next/root-params cannot see lang while app/layout.tsx exists, and headers() would make all 740 pages dynamic)"
],
"recommendation": "A. The card's own wording asks for whichever approach 'keeps a single root element', and after this change the 404 route has none - so restoring it is finishing the card's stated requirement, not widening it. The file is a leaf, collides with no in-flight claim (#169 is on sitemap.ts/seo.ts/page.tsx, #170 on robots.ts/llms.txt), adds no new gate surface, and its correct shape is pinned by what main already rendered: root element with lang en, body class 'flex flex-col min-h-screen'. B is acceptable but ships a known dev-facing break."
}
],
"out_of_scope_findings": [
"filed as #180: a URL whose first segment contains a dot bypasses the locale middleware and returns HTTP 500 instead of 404 - /foo.bar/docs throws 'Cannot use in operator to search for root in undefined' from the Fumadocs page-tree lookup. Confirmed identical on origin/main at ee74379, so pre-existing. Filed unassigned, no labels (concrete defect, left for PM triage)."
],
"cleanup": "Both worktrees removed after the PR was opened (no --force needed); all processes I started are stopped. Sibling agent's #170 server left running."
}
{
"issue": 168,
"status": "done",
"supersedes": "the earlier os-dev-report on this card (status rework), whose open question has since been answered",
"branch": "claude/issue-168-html-lang-locale",
"pr": "https://github.com/objectstack-ai/objectos/pull/181",
"head_sha": "e04254e",
"base_at_branch": "ee74379",
"premise_still_valid": true,
"files_changed": [
"apps/docs/app/layout.tsx",
"apps/docs/app/[lang]/layout.tsx",
"apps/docs/app/not-found.tsx"
],
"file_surface_respected": true,
"file_surface_note": "app/not-found.tsx added under the explicit one-file extension granted in PM review. No other file joined the surface. next.config.mjs untouched, as instructed.",
"gates": {
"type-check": "pnpm run type-check on e04254e -> exit 0 (redirect-then-read, not through a pipe). Script name echoed, so it really ran.",
"build": "pnpm run build on e04254e -> exit 0. Printed verdicts: 'Compiled successfully in 43s', 'Finished TypeScript in 6.0s', 'Generating static pages using 3 workers (740/740)'. 740 prerendered pages, unchanged from main.",
"runtime-locale-probe": "Production build served locally at e04254e. /docs=en, /docs/architecture=en, /zh-Hans/docs=zh-Hans, /ja/docs=ja, /de/docs=de, /es/docs=es, /fr/docs=fr, /ko/docs=ko, /privacy=en, /zh-Hans/privacy=zh-Hans, /terms=en, /ja/terms=ja. All 200. /en/docs and / both still 307 to /docs.",
"404-probe-prod": "/no-such-page -> http=404, declared en, 404 copy present, no error shell. main at ee74379 measured the same. Regression closed.",
"404-probe-dev": "next dev, /no-such-page -> http=404, error shell: no, missing-root-layout message: no, declared en, 404 copy present. This was PM's explicit check and it passes.",
"ablation-language-guard": "Unchanged from the previous report and still valid at e04254e: control=en, ablated=foo.bar, restored=en; mutation proven on disk by hash 9380230->463d3ad plus grep counts 1->0 and 0->1 before any reading; restore proven by hash back to 9380230 and empty git diff HEAD.",
"ci": "in_progress - not awaited, per dispatch contract. PR mergeable_state=unstable (CI running), not dirty, so no conflict with the #169 merge; no main merge performed."
},
"rework_item_from_pm_review": "DONE. apps/docs/app/not-found.tsx added, shape matched to what main rendered (root element declaring en, body class 'flex flex-col min-h-screen', wrapping the Next.js built-in 404). Leaf file only. next.config.mjs untouched. PR body first line changed from 'Part of #168' to 'Fixes #168' and now quotes PM's reasoning for the surface extension.",
"deviations": [
"One PM premise needed correcting by measurement, and it did NOT cost an extra file. PM's review and my own earlier report both spoke of 'the 404 route' as one route. There are two distinct 404 entry points: an unmatched URL (/no-such-page), served from the root _not-found entry, and notFound() raised by a page below the locale segment (/docs/no-such-page). app/not-found.tsx fixes the first. It cannot fix the second: that boundary sits above the locale layout, which has already emitted a shell, so a second shell there produces Next's __next_error__ document.",
"Before treating that second path as my regression I measured main. On a clean origin/main worktree at ee74379 it is ALREADY broken there, byte-for-byte identical to my branch: /docs/no-such-page -> errorshell=1, visible404text=0, no declared language. So it is pre-existing, my branch neither causes nor worsens it, and no second surface extension is needed. Filed as #182 instead.",
"Because of that measurement there is no known regression left on this branch, which is what PM's instruction to switch to 'Fixes #168' was conditioned on. Switched as instructed.",
"Carried over from the previous report and accepted in review, recorded here so this report stands alone: (a) the documentLanguage() guard stays, justified by ablation; (b) I lost work to a trap restoring to HEAD against uncommitted edits, then committed first and re-ran clean - all readings are post-commit; (c) an earlier probe was served by a stale build because my kill hit the npx wrapper and not the next-server child, detected via EADDRINUSE and re-measured on a clean rebuild.",
"Process note for this round: I killed servers by resolving /proc/PID/cwd and matching my own worktrees, never by process name. pid 29761 in /home/user/objectos-issue-170 (sibling agent) was identified and deliberately left running, as were the #166 and #177 worktrees."
],
"adjudications_honored": {
"lang-from-route-segment": "yes",
"single-root-element": "yes - every locale route and the unmatched-URL 404 now render exactly one root element; the notFound()-below-locale path renders none, but that is pre-existing on main and filed as #182",
"zh-Hans-not-rewritten": "yes - verbatim; matches www.objectos.ai src/lib/i18n.ts:37 where htmlLang is the identity function",
"hideLocale-routing-unbroken": "yes - /en/docs and / still 307 to /docs; prefix-less /docs and /docs/architecture still declare en"
},
"open_questions": [],
"out_of_scope_findings": [
"filed as #180: a URL whose first segment contains a dot bypasses the locale middleware and returns HTTP 500 instead of 404 - /foo.bar/docs throws 'Cannot use in operator to search for root in undefined' from the Fumadocs page-tree lookup. Confirmed identical on origin/main at ee74379.",
"filed as #182: a 404 raised by notFound() below the locale segment (/docs/no-such-page, /zh-Hans/docs/no-such-page) serves a blank error shell instead of the 404 page - the 404 markup reaches the RSC payload but never the server-rendered HTML, so no-JS clients and crawlers get an empty body. Confirmed byte-for-byte identical on origin/main at ee74379 and on this branch. Likely fix is a not-found boundary inside the locale segment; noted in the issue, not attempted."
],
"cleanup": "Both worktrees removed (no --force needed); every process I started is stopped. Sibling agents' worktrees (#166, #177) and the #170 server left untouched."
}
ACCEPT — PM review of record, rework round. PR #181 at e04254e.
Checklist
item
verdict
PR shape
first line Fixes #168 — switched back from Part of, correctly, once the regression was gone ✓
File surface
changed_files: 3 — the two layouts plus the one-file extension I granted. next.config.mjs untouched as instructed ✓
CI
build and Node floor both completed/success on e04254e ✓
Path-surface fork
all under apps/docs/app/**; no governed surface ✓
Mergeable
clean ✓
Static generation
740 prerendered pages, unchanged from main — the thing that would have been silently lost ✓
Rework item
done: not-found.tsx added, shape matched to main rather than improved ✓
My premise was wrong and it was corrected the right way
Both my rework instruction and the dev's own first report spoke of "the 404 route" as a single thing. There are two entry points:
an unmatched URL (/no-such-page), served from the root _not-found entry — fixed by not-found.tsx;
notFound() raised by a page below the locale segment (/docs/no-such-page) — that boundary sits above the locale layout, which has already emitted a shell, so a second shell there produces Next's error document. not-found.tsx structurally cannot cover it.
What matters is what happened next. Before treating path 2 as a regression it had caused, the dev measured origin/main at ee74379 and found it byte-for-byte identical — already broken there. So no second surface extension was requested, no scope was widened, and #182 was filed instead.
That is the correct order of operations: measure the baseline before you accept the blame, and before you ask for more surface. Had it skipped that step, this card would have grown a second file for a defect it did not cause.
Spot checks
The next dev probe passes — missing-root-layout message: no, 404 copy present, declared en. That was the check I asked for specifically, because that validator is dev-server-only and would otherwise have shipped as a developer-facing break.
en hardcoded in not-found.tsx is correct, not lazy. That boundary sits above the locale segment and cannot read it — exactly the position the old root layout was in. Matching what main rendered keeps the 404 unchanged by the locale work rather than quietly redesigned by it.
The documentLanguage() guard is load-bearing, established by ablation rather than asserted: without it, /foo.bar/privacy puts an arbitrary URL segment into the document's declared language on a live 200.
zh-Hans passes through verbatim, cross-checked against the marketing site where htmlLang is the identity function. The two repos agree.
Process incidents — recorded, not held against
Carried forward from the first round and repeated here so this record stands alone: work lost to a trap restoring to HEAD against uncommitted edits (then committed first and re-run clean, all readings post-commit), and one probe served by a stale build (detected via EADDRINUSE, re-measured). Both were reported unprompted, both were fully recovered, and the process note for this round — killing servers by resolving /proc/PID/cwd against its own worktrees rather than by process name, and deliberately leaving the sibling agents' worktrees alone — is exactly the discipline that keeps parallel dispatch safe.
Activity
[-]Every page in all seven locales declares `<html lang="en">`[/-][+]Every page in all seven locales declares lang="en" on the root html element[/+]hotlong commentedon Aug 25, 2026
Claim: PM loop round 1
Session:
session_01G5zjYc2BoFV2NjKBBapC7CBranch:
claude/issue-168-html-lang-localeWorktree:
objectos-issue-168Domain: n/a (objectos carries no
domain:*lanes)File surface:
apps/docs/app/layout.tsx,apps/docs/app/[lang]/layout.tsx(stop on breach; explain in the report)Container & model: M,
mode:subagent,model: opus— PM judgement;scripts/pm/dispatch-gates.mjslives in the objectstack repo and has no counterpart here, so no--tierreading is quoted.Clause-②: no
Serial constraints cleared: no in-flight claim touches either layout file. #169 and #170 are dispatched in the same batch and their file surfaces do not intersect this one. Read coupling checked: neither sibling asserts behaviour of the root layout shell.
Generated by Claude Code
hotlong commentedon Aug 25, 2026
Claim: dev agent (os-dev subagent dispatched by the PM claim above)
Session:
session_01G5zjYc2BoFV2NjKBBapC7C— I run as amode:subagentinside the PM's session and am not issued a distinct session id, so this is the same id the PM comment carries. Distinguishing marker for this claim is the branch + worktree below.Branch:
claude/issue-168-html-lang-locale(pushed empty at baseee743793bc61b1ce4c04a2e0afb46c105ede310bbefore the first edit)Worktree:
/home/user/objectos-issue-168File surface:
apps/docs/app/layout.tsx,apps/docs/app/[lang]/layout.tsx— stop on breach and report.Prior comments read before starting: 1 (the PM claim). No earlier dev claim with a different marker.
Generated by Claude Code
Generated by Claude Code
hotlong commentedon Aug 25, 2026
os-dev-report
{ "issue": 168, "status": "rework", "branch": "claude/issue-168-html-lang-locale", "pr": "https://github.com/objectstack-ai/objectos/pull/181", "head_sha": "116f691", "base_at_branch": "ee74379", "premise_still_valid": true, "premise_evidence": "Reproduced on a clean origin/main worktree at ee74379 before editing: /zh-Hans/docs, /ja/docs, /de/docs, /es/docs, /fr/docs, /ko/docs all declared en. app/layout.tsx:19 was the only root html element in the app (grep over apps/docs for html open tags returned exactly 1 hit).", "files_changed": [ "apps/docs/app/layout.tsx", "apps/docs/app/[lang]/layout.tsx" ], "file_surface_respected": true, "gates": { "type-check": "pnpm run type-check on 116f691 -> exit 0 (captured via redirect-then-read, not through a pipe). Script name echoed in output, so it really ran.", "build": "pnpm run build on 116f691 -> exit 0. Printed verdicts: 'Compiled successfully in 43s', 'Finished TypeScript in 6.1s', 'Generating static pages using 3 workers (740/740)'. 740 prerendered pages, unchanged from main -> static generation not lost.", "runtime-locale-probe": "Production build served locally at 116f691. /docs=en, /docs/architecture=en, /zh-Hans/docs=zh-Hans, /ja/docs=ja, /de/docs=de, /es/docs=es, /fr/docs=fr, /ko/docs=ko, /privacy=en, /zh-Hans/privacy=zh-Hans, /terms=en, /ja/terms=ja. /en/docs still 307s to /docs. All 200s.", "ablation-language-guard": "Against the COMMITTED state. Mutation proven on disk before any reading: object hash 9380230->463d3ad, guarded-occurrence grep 1->0, raw-occurrence grep 0->1. control=en, ablated=foo.bar, restored=en with hash back to 9380230 and git diff HEAD empty for the path.", "ci": "in_progress - not awaited, per dispatch contract (report at draft-PR time)." }, "not_run": { "lint": "apps/docs declares no lint-able gate beyond type-check and build in package.json; repo has no changeset flow and no packages/. No changeset added, as instructed.", "skip-changeset-label": "not applied - that label mechanism is objectstack-specific; minting it here would fabricate a repo label." }, "deviations": [ "PR body first line is 'Part of #168', NOT 'Fixes #168' as the dispatch specified. Reason: the branch knowingly leaves one regression (below), so merging must not auto-close the card. Flagging the conflict rather than silently choosing.", "PM mechanism assumption 2 is FALSIFIED in a bounded way. Moving the shell into the [lang] layout IS viable under the App Router - app/page.tsx, robots.ts and sitemap.ts are unaffected, and the root redirect still answers 307 to /docs exactly as on main - but routes outside the locale segment lose their root element, and one of them renders HTML: the 404. main: /no-such-page 404 declares en. This branch: no root element at all. In next dev the same request returns Next's __next_error__ document ('Missing html and body tags in the root layout'); measured, because that validator is dev-server-only. Remedy is a new apps/docs/app/not-found.tsx (~20 lines) rendering its own root element and body. That file is outside the granted surface and the dispatch said to stop and report rather than widen, so it is deliberately absent. app/global-not-found.tsx is the tidier convention but sits behind experimental.globalNotFound (default false), which would mean editing next.config.mjs.", "PM mechanism assumption 1 CONFIRMED: exactly one root html element existed (app/layout.tsx:19); nothing in [lang]/layout.tsx or any Fumadocs provider set one.", "Added a 4-line documentLanguage() guard inside the granted file surface, not requested by the card. Justified by measurement, not caution: middleware.ts's matcher skips any path containing a dot, so /foo.bar/privacy reaches the layout with lang='foo.bar' and answers 200. Ablation above shows the raw segment otherwise becomes the declared language.", "Process incident, self-inflicted, fully recovered: I ran the first ablation against UNCOMMITTED edits with a trap restoring to HEAD, which discarded my [lang]/layout.tsx work (exactly the failure my contract warns about). Rewrote the file, committed FIRST, then re-ran the ablation against the committed state. Final tree verified clean; the readings quoted above are all post-commit.", "Process incident, contained: my kill calls killed the npx wrappers but not the next-server children, so one 'AFTER' probe was served by a stale build. Detected via EADDRINUSE, re-measured on a clean rebuild of 116f691. While cleaning up I found pid 29761 running in /home/user/objectos-issue-170 - a SIBLING agent's worktree - and left it untouched; killed only my own PIDs by number, never by process name.", "Duplicate-search channel: REST returned 403 for this session ('GitHub access is not enabled for this session'), so the pre-filing search used the MCP list_issues channel instead of REST." ], "adjudications_honored": { "lang-from-route-segment": "yes", "single-root-element": "yes on every locale route; NO on the 404 route - see deviations", "zh-Hans-not-rewritten": "yes - passes through verbatim; cross-checked against www.objectos.ai src/lib/i18n.ts:37 where htmlLang is the identity function, so the two repos agree", "hideLocale-routing-unbroken": "yes - /en/docs still 307s to /docs; prefix-less /docs and /docs/architecture still declare en" }, "open_questions": [ { "question": "The 404 route needs a root element restored. May the file surface be extended by one new file, apps/docs/app/not-found.tsx?", "options": [ "A - extend this card's surface by that one file; I add it on this branch and the card closes complete", "B - keep the surface as granted; land PR #181 as-is and track the 404 shell as its own card, accepting that between merge and that fix, 404s carry no root element and next dev shows the missing-root-layout error", "C - abandon the move-down and keep the hardcoded attribute (rejected: no alternative preserves static generation, since next/root-params cannot see lang while app/layout.tsx exists, and headers() would make all 740 pages dynamic)" ], "recommendation": "A. The card's own wording asks for whichever approach 'keeps a single root element', and after this change the 404 route has none - so restoring it is finishing the card's stated requirement, not widening it. The file is a leaf, collides with no in-flight claim (#169 is on sitemap.ts/seo.ts/page.tsx, #170 on robots.ts/llms.txt), adds no new gate surface, and its correct shape is pinned by what main already rendered: root element with lang en, body class 'flex flex-col min-h-screen'. B is acceptable but ships a known dev-facing break." } ], "out_of_scope_findings": [ "filed as #180: a URL whose first segment contains a dot bypasses the locale middleware and returns HTTP 500 instead of 404 - /foo.bar/docs throws 'Cannot use in operator to search for root in undefined' from the Fumadocs page-tree lookup. Confirmed identical on origin/main at ee74379, so pre-existing. Filed unassigned, no labels (concrete defect, left for PM triage)." ], "cleanup": "Both worktrees removed after the PR was opened (no --force needed); all processes I started are stopped. Sibling agent's #170 server left running." }Generated by Claude Code
Generated by Claude Code
hotlong commentedon Aug 25, 2026
os-dev-report
{ "issue": 168, "status": "done", "supersedes": "the earlier os-dev-report on this card (status rework), whose open question has since been answered", "branch": "claude/issue-168-html-lang-locale", "pr": "https://github.com/objectstack-ai/objectos/pull/181", "head_sha": "e04254e", "base_at_branch": "ee74379", "premise_still_valid": true, "files_changed": [ "apps/docs/app/layout.tsx", "apps/docs/app/[lang]/layout.tsx", "apps/docs/app/not-found.tsx" ], "file_surface_respected": true, "file_surface_note": "app/not-found.tsx added under the explicit one-file extension granted in PM review. No other file joined the surface. next.config.mjs untouched, as instructed.", "gates": { "type-check": "pnpm run type-check on e04254e -> exit 0 (redirect-then-read, not through a pipe). Script name echoed, so it really ran.", "build": "pnpm run build on e04254e -> exit 0. Printed verdicts: 'Compiled successfully in 43s', 'Finished TypeScript in 6.0s', 'Generating static pages using 3 workers (740/740)'. 740 prerendered pages, unchanged from main.", "runtime-locale-probe": "Production build served locally at e04254e. /docs=en, /docs/architecture=en, /zh-Hans/docs=zh-Hans, /ja/docs=ja, /de/docs=de, /es/docs=es, /fr/docs=fr, /ko/docs=ko, /privacy=en, /zh-Hans/privacy=zh-Hans, /terms=en, /ja/terms=ja. All 200. /en/docs and / both still 307 to /docs.", "404-probe-prod": "/no-such-page -> http=404, declared en, 404 copy present, no error shell. main at ee74379 measured the same. Regression closed.", "404-probe-dev": "next dev, /no-such-page -> http=404, error shell: no, missing-root-layout message: no, declared en, 404 copy present. This was PM's explicit check and it passes.", "ablation-language-guard": "Unchanged from the previous report and still valid at e04254e: control=en, ablated=foo.bar, restored=en; mutation proven on disk by hash 9380230->463d3ad plus grep counts 1->0 and 0->1 before any reading; restore proven by hash back to 9380230 and empty git diff HEAD.", "ci": "in_progress - not awaited, per dispatch contract. PR mergeable_state=unstable (CI running), not dirty, so no conflict with the #169 merge; no main merge performed." }, "rework_item_from_pm_review": "DONE. apps/docs/app/not-found.tsx added, shape matched to what main rendered (root element declaring en, body class 'flex flex-col min-h-screen', wrapping the Next.js built-in 404). Leaf file only. next.config.mjs untouched. PR body first line changed from 'Part of #168' to 'Fixes #168' and now quotes PM's reasoning for the surface extension.", "deviations": [ "One PM premise needed correcting by measurement, and it did NOT cost an extra file. PM's review and my own earlier report both spoke of 'the 404 route' as one route. There are two distinct 404 entry points: an unmatched URL (/no-such-page), served from the root _not-found entry, and notFound() raised by a page below the locale segment (/docs/no-such-page). app/not-found.tsx fixes the first. It cannot fix the second: that boundary sits above the locale layout, which has already emitted a shell, so a second shell there produces Next's __next_error__ document.", "Before treating that second path as my regression I measured main. On a clean origin/main worktree at ee74379 it is ALREADY broken there, byte-for-byte identical to my branch: /docs/no-such-page -> errorshell=1, visible404text=0, no declared language. So it is pre-existing, my branch neither causes nor worsens it, and no second surface extension is needed. Filed as #182 instead.", "Because of that measurement there is no known regression left on this branch, which is what PM's instruction to switch to 'Fixes #168' was conditioned on. Switched as instructed.", "Carried over from the previous report and accepted in review, recorded here so this report stands alone: (a) the documentLanguage() guard stays, justified by ablation; (b) I lost work to a trap restoring to HEAD against uncommitted edits, then committed first and re-ran clean - all readings are post-commit; (c) an earlier probe was served by a stale build because my kill hit the npx wrapper and not the next-server child, detected via EADDRINUSE and re-measured on a clean rebuild.", "Process note for this round: I killed servers by resolving /proc/PID/cwd and matching my own worktrees, never by process name. pid 29761 in /home/user/objectos-issue-170 (sibling agent) was identified and deliberately left running, as were the #166 and #177 worktrees." ], "adjudications_honored": { "lang-from-route-segment": "yes", "single-root-element": "yes - every locale route and the unmatched-URL 404 now render exactly one root element; the notFound()-below-locale path renders none, but that is pre-existing on main and filed as #182", "zh-Hans-not-rewritten": "yes - verbatim; matches www.objectos.ai src/lib/i18n.ts:37 where htmlLang is the identity function", "hideLocale-routing-unbroken": "yes - /en/docs and / still 307 to /docs; prefix-less /docs and /docs/architecture still declare en" }, "open_questions": [], "out_of_scope_findings": [ "filed as #180: a URL whose first segment contains a dot bypasses the locale middleware and returns HTTP 500 instead of 404 - /foo.bar/docs throws 'Cannot use in operator to search for root in undefined' from the Fumadocs page-tree lookup. Confirmed identical on origin/main at ee74379.", "filed as #182: a 404 raised by notFound() below the locale segment (/docs/no-such-page, /zh-Hans/docs/no-such-page) serves a blank error shell instead of the 404 page - the 404 markup reaches the RSC payload but never the server-rendered HTML, so no-JS clients and crawlers get an empty body. Confirmed byte-for-byte identical on origin/main at ee74379 and on this branch. Likely fix is a not-found boundary inside the locale segment; noted in the issue, not attempted." ], "cleanup": "Both worktrees removed (no --force needed); every process I started is stopped. Sibling agents' worktrees (#166, #177) and the #170 server left untouched." }Generated by Claude Code
Generated by Claude Code
hotlong commentedon Aug 25, 2026
ACCEPT — PM review of record, rework round. PR #181 at
e04254e.Checklist
Fixes #168— switched back fromPart of, correctly, once the regression was gone ✓changed_files: 3— the two layouts plus the one-file extension I granted.next.config.mjsuntouched as instructed ✓buildandNode floorbothcompleted/successone04254e✓apps/docs/app/**; no governed surface ✓clean✓not-found.tsxadded, shape matched to main rather than improved ✓My premise was wrong and it was corrected the right way
Both my rework instruction and the dev's own first report spoke of "the 404 route" as a single thing. There are two entry points:
/no-such-page), served from the root_not-foundentry — fixed bynot-found.tsx;notFound()raised by a page below the locale segment (/docs/no-such-page) — that boundary sits above the locale layout, which has already emitted a shell, so a second shell there produces Next's error document.not-found.tsxstructurally cannot cover it.What matters is what happened next. Before treating path 2 as a regression it had caused, the dev measured
origin/mainatee74379and found it byte-for-byte identical — already broken there. So no second surface extension was requested, no scope was widened, and #182 was filed instead.That is the correct order of operations: measure the baseline before you accept the blame, and before you ask for more surface. Had it skipped that step, this card would have grown a second file for a defect it did not cause.
Spot checks
next devprobe passes —missing-root-layout message: no, 404 copy present, declareden. That was the check I asked for specifically, because that validator is dev-server-only and would otherwise have shipped as a developer-facing break.enhardcoded innot-found.tsxis correct, not lazy. That boundary sits above the locale segment and cannot read it — exactly the position the old root layout was in. Matching what main rendered keeps the 404 unchanged by the locale work rather than quietly redesigned by it.documentLanguage()guard is load-bearing, established by ablation rather than asserted: without it,/foo.bar/privacyputs an arbitrary URL segment into the document's declared language on a live 200.zh-Hanspasses through verbatim, cross-checked against the marketing site wherehtmlLangis the identity function. The two repos agree.Process incidents — recorded, not held against
Carried forward from the first round and repeated here so this record stands alone: work lost to a trap restoring to HEAD against uncommitted edits (then committed first and re-run clean, all readings post-commit), and one probe served by a stale build (detected via
EADDRINUSE, re-measured). Both were reported unprompted, both were fully recovered, and the process note for this round — killing servers by resolving/proc/PID/cwdagainst its own worktrees rather than by process name, and deliberately leaving the sibling agents' worktrees alone — is exactly the discipline that keeps parallel dispatch safe.Proceeding to ready and merge.
Generated by Claude Code