Skip to content

Docs accessibility baseline: no main landmark, the language switcher's name contradicts its text, an unnamed svg role=img, unnamed code regions — on every page in every locale #308

Description

@objectstack-fleet

Found by axe 4.14.0 while #305 was being staged on 2026-10-06. The run covered one fallback page and one translated page per locale, plus the English page, built from main @ cec227a. Every finding is pre-existing: the English page's HTML is the same as main's, and #305 did not cause any of them. Priority: P2, accessibility.

Findings (identical on every docs page)

Colour contrast is out of scope here; #301 handles it.

Done looks like

  • axe on one page per section, in en, zh-Hans and de, light and dark: 0 violations of the four rules above. Show before and after.
  • The prompt sentence is localized via ui-text, with zh-Hant generated.
  • A CI rule that fails on English chrome names in a localized page, and on an unmarked untranslated tree entry. Show it red on a reintroduced case and green after.
  • Repo gates green.

Order

After #305 lands, because it builds on #305's fumadocs-ui patch and ui-text keys.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop, repo:objectos seat
    Session: session_01FeA1nwBz1ohH65dvffUGKr
    Account: hotlong (the seat's linked user, as GET /user answers it; always the card's assignee)
    Branch: claude/pm-dispatch-objectos-ju9td1, the session's one designated push branch. This card builds on #305's fumadocs-ui patch and ui-text keys, so it is staged on #305's local branch staged/issue-305. At push time it is rebased after #301 (PR #310) and #305, and only its own commits are replayed.
    Worktree: objectos-issue-308
    Domain: n/a (single-lane repo)
    Seat: repo:objectos (objectstack-ai/objectstack#9831)
    File surface:

    Stay out of #301's files (global.css, mdx-components.tsx, source.config.ts, the search route, smoke-docs.mjs, the logo link) and every content/docs/** page.
    Stop on breach and explain in the report.
    Container & model: M, mode:subagent, model: default.
    Clause-②: no
    Thread-read: none
    Serial constraints cleared: no. Pushes are serial on the one branch: #301 → #305 → this card.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 308,
      "status": "staged",
      "branch": "staged/issue-308",
      "worktree": "/home/user/objectos-issue-308",
      "base": "1f5ea81ef289eae88c116a560cef93578a94ebc2 (staged/issue-305 as dispatched; based on cec227a)",
      "final_commit": "40fe51c6d8c18d2bf551b40fc28adc5d905a9515",
      "commits": [
        "0d96c43 docs: a main landmark, a language switcher named by its text, a decorative GitHub icon, code blocks off the landmark list, a localized Open-in prompt",
        "295d79c ci: read the built pages for English chrome names and unmarked page-tree entries",
        "40fe51c docs: the docs page's table-of-contents bar is not a second banner"
      ],
      "pr": null,
      "pr_body": "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/pr-body.md (first line: Fixes #308)",
      "session": "session_01FeA1nwBz1ohH65dvffUGKr (subagent; the parent seat session, per the Claim)",
      "premise_still_valid": true,
      "summary": "All six card items are done and committed on staged/issue-308 (3 commits on 1f5ea81). Nothing is pushed. (1) Main landmark: DocsPage passes its other props to the article it renders, so the docs page sets role=main there. This is a fumadocs option; no hunk. (2) Language switcher: a patch hunk names the trigger CURRENT — chooseLanguage ('English — Choose a language', 'Deutsch — Sprache wählen'), visible text first, both halves from the i18n context. (3) GitHub icon: aria-hidden instead of an unnamed svg role=img; every link that renders it is already named GitHub. (4) Code blocks: the scroll viewport loses its unnamed role=region and keeps tabIndex 0. Same rule (landmark-unique), fixed in place: at 390px the table-of-contents bar was a second banner header, now a div. (5) The Open-in prompt is the ui-text key pageActionsOpenInLLMPrompt (upstream 16.9.0's name, {url} placeholder and English), translated in six locales, zh-Hant generated. ui-text's table() now also requires every locale to carry English's placeholders. (6) check-locale-surface.mjs gains three rules over the prerendered HTML (english-chrome-name, untranslated-entry-unmarked, translated-entry-marked-english), guards against passing over nothing, a live control, and ten self-test cases. No ci.yml change. axe on 8 sections x en/zh-Hans/de x light/dark: the four rules go from 48/48/48/24 runs to 0 at 1440px, and 0 at 390px. All ten before/after screenshot pairs are byte-identical.",
      "files": [
        "patches/fumadocs-ui@16.8.12.patch: 4 new hunks (language-select.js name, shared/index.js GitHub svg aria-hidden, codeblock.js role removed, docs/page/slots/toc.js header to div); header gains a #308 section and a rewritten WHEN THIS FILE CAN BE DELETED. #305's hunks unchanged.",
        "pnpm-lock.yaml: fumadocs-ui patch hash only; frozen install exit 0",
        "apps/docs/app/[lang]/docs/[[...slug]]/page.tsx: role=main on DocsPage (with an upgrade note), one label (openInLLMPrompt). Outside the claimed surface, see deviations.",
        "apps/docs/components/ai/page-actions.tsx: the prompt from labels.openInLLMPrompt with a replacer function",
        "apps/docs/lib/ui-text.ts: pageActionsOpenInLLMPrompt (app key, not passed to fumadocs); table(locale, text) also checks placeholders against English",
        "apps/docs/lib/ui-text/{zh-Hans,ja,de,es,fr,ko}.json: +1 key each; zh-Hant.json generated by gen-zh-hant",
        ".github/scripts/check-locale-surface.mjs: built-pages section (reader, 3 rules, 3 guards, live control), 10 self-test cases, header section, summary line"
      ],
      "measurements": {
        "axe_1440_before_1f5ea81": "48 runs: landmark-one-main 48 runs/48 nodes, label-content-name-mismatch 48/48, svg-img-alt 48/48, landmark-unique 24/24 (pages with 2+ code blocks); other: region 2254 nodes, color-contrast 1147 nodes (axe-before.json)",
        "axe_1440_after_40fe51c": "landmark-one-main 0, label-content-name-mismatch 0, svg-img-alt 0, landmark-unique 0; region 48 nodes (desktop TOC, 1 per run), color-contrast 1147 unchanged (axe-after-1440.json)",
        "axe_390_before_TOC_fix_295d79c": "four rules 0/0/0/48 runs (landmark-unique = two banner headers), landmark-no-duplicate-banner 48 (axe-390-pretoc.json). The four rules at 390px on 1f5ea81: NOT MEASURED, reason: the 1f5ea81 build was replaced before the 390px run was added",
        "axe_390_after_40fe51c": "four rules 0/0/0/0, landmark-no-duplicate-banner 0 (axe-after-390.json); drawer open (switcher visible), en/zh-Hans/de quickstart: no violation of the four, label-content-name-mismatch passes",
        "axe_trial_rebase": "my first two commits cherry-picked onto staged/issue-305 at 4da46c5 (contains #301 5d2f837): built, 1440px four rules 0, color-contrast 48 nodes (axe-rebased.json)",
        "ci_rule_red_green": [
          "red, main's HTML (cec227a, #305's scratch baseline-html, with this script): english-chrome-name 6239 (zh-Hans/zh-Hant 1067, others 821), untranslated-entry-unmarked 5974 (687 x2 + 920 x5), exit 1 (gate-on-main-cec227a-html.log)",
          "red, ablation on this branch: searchOpen mapped to the literal 'Open Search' and the page-tree transform bypassed. ablation-replace.mjs proof: anchors 1 -> 0, blobs 843ba86 -> 94831ac and ac65f07 -> 9518d5a. Built de/docs/operate/backup.html: aria-label 'Open Search' x0 -> x2, span lang=en x23 -> x7. Gate: english-chrome-name 1120 (160 per locale), untranslated-entry-unmarked 5974, exit 1 (ablation/gate-mutated.log). Restored: blob == HEAD, git diff HEAD empty, both files",
          "green, restored and rebuilt at 40fe51c: 0 English names, untranslated entries 687/687/920 x5 all marked, translated 635/635/402 x5 none marked English, exit 0 (gates/locale-surface.log)",
          "green on the trial-rebased tree (with #301's www.objectos.ai link and logo-to-docs link): exit 0, untranslated 717/950 all marked (gate-rebased.log)",
          "self-test: 41 cases over 21 rules; the live control is green with the real reader and red with a reader blind to names or to lang"
        ],
        "screenshots_identity": "10 before (1f5ea81) / after (40fe51c) PNG pairs, byte-identical sha256",
        "prompt": "all 8 locales x {operate/backup fallback, build/data translated}, read from the live popover: localized, {url} filled, 0 console errors; English byte-identical to the old literal (prompt-check.json vs prompt-check-before.json)"
      },
      "gates": [
        "HEAD 40fe51c for every line below",
        "pnpm install --frozen-lockfile: exit 0",
        "pnpm turbo run type-check --continue --force: Tasks 1 successful; os-verify-lock VERDICT command-exit 0",
        "NEXT_PRIVATE_STANDALONE=true pnpm turbo run build --force: Generating static pages (1052/1052); Tasks 1 successful; VERDICT command-exit 0 (351 OG font-fetch proxy warnings, same count as the 1f5ea81 build)",
        "pnpm turbo run test --force: ✓ 10 self-test(s) passed; VERDICT command-exit 0",
        "check-locale-surface: exit 0, ✓ ... and no built page outside `en` names a control in English, shows an untranslated page-tree entry without lang=\"en\", or marks a translated one English",
        "check-locale-surface --self-test: ✓ self-test: 41 case(s) over 21 rule(s) and 3 artifact(s) ... both live controls demonstrated able to fire negative-control-passed",
        "check-positioning: ✓ positioning: 4 copies equal their constants; the brand is right in 659 pages and 2 llms bodies; no stale sentence in 79 English sources and the en entry of 2 legal pages",
        "check-search-locales: ✓ search locales: all 8 locales answer 200, find \"permissions\", find every own page by its title, and find nothing for a nonce",
        "gen-zh-hant --check: ✓ zh-Hant: 62 generated file(s) match the zh-Hans sources byte for byte. Under a one-character drift in the new zh-Hant string: exit 1, restored",
        "translations Ownership (workflow argv git diff --name-status --no-renames origin/main...HEAD; --actor hotlong): exit 0, inert with TRANSLATION_BOT_LOGIN unset; armed: ✓ 16 file(s) changed, no translation artifacts touched",
        "translations Freshness: ✓ translations gate passed; Output --files: ✓ translation output gate passed (98 pre-existing finding(s) reported); Output --self-test: ✓",
        "check-node-floor and --self-test: ✅ exit 0; check-half-states --self-test: ✓ 1551 cases pass",
        "NOT MEASURED: Worker packaging/size (opennextjs-cloudflare + wrangler dry-run) and smoke-docs, reason: not in this dispatch's gate list; CI runs both"
      ],
      "tests": "No unit-test surface for apps/docs; evidence is the gates plus scratchpad/issue-308: axe-run.mjs (axe-before/after-1440/after-390/390-pretoc .json+.log), axe-390.mjs (drawer open), shots.mjs (shots/, facts json), prompt-check.mjs, gate logs in gates/. Reverse verifications, each restored with blob == HEAD and git diff HEAD empty: (a) CI rule ablation above, red then green; (b) placeholder guard: de.json prompt with {url} removed -> bundled ui-text throws 'lib/ui-text/de.json: \"pageActionsOpenInLLMPrompt\" has placeholders [], English has [{url}]', exit 1; clean -> loads (guard/); (c) gen-zh-hant --check red on a one-character drift. Built-HTML evidence for the patch: built pages show aria-label 'English — Choose a language' x81 on en; no role=region viewport and article role=main in the live DOM (shots/after-facts.json).",
      "screenshots": [
        "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/shots/before-en-1440-switcher.png / after-en-1440-switcher.png",
        "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/shots/before-zh-Hans-1440-switcher.png / after-zh-Hans-1440-switcher.png",
        "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/shots/before-en-390-switcher.png / after-en-390-switcher.png (drawer open)",
        "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/shots/before-zh-Hans-390-switcher.png / after-zh-Hans-390-switcher.png (drawer open)",
        "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/shots/before-en-390-drawer.png / after-en-390-drawer.png",
        "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/shots/before-zh-Hans-390-drawer.png / after-zh-Hans-390-drawer.png",
        "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/shots/before-en-1440-codeblock.png / after-en-1440-codeblock.png",
        "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/shots/before-zh-Hans-1440-codeblock.png / after-zh-Hans-1440-codeblock.png",
        "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/shots/before-en-390-codeblock.png / after-en-390-codeblock.png",
        "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/shots/before-zh-Hans-390-codeblock.png / after-zh-Hans-390-codeblock.png",
        "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/shots/after-en-390-tocbar.png, after-zh-Hans-390-tocbar.png (TOC bar after header -> div; after only)",
        "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/shots/before-facts.json, after-facts.json (switcher name vs visible text, viewport role, main landmark per shot)"
      ],
      "deviations": [
        "File surface beyond the Claim: apps/docs/app/[lang]/docs/[[...slug]]/page.tsx (role=main, one label line). The Claim named layout.tsx, but the layout cannot reach the article: its children are grid items, so wrapping them in a main element breaks the grid. DocsPage's pass-through props are the fumadocs option. No sibling touches this file; #305 already touched it.",
        "In-place fix beyond the card's listed instances: the TOC bar header -> div (4th #308 hunk). Same rule as the card (landmark-unique, plus landmark-no-duplicate-banner) on every sampled page at 390px; mechanical; in the claimed patch file; covered by the same axe run. The PR body names it. The seat may want to add it to the Claim's surface.",
        "Language switcher fixed by a patch hunk rather than the slots.languageSelect.root slot, which would also work. A slot is per layout instance (docs layout + both legal pages); the patch covers every trigger. Reason in the patch header.",
        "Prompt carried through ViewOptions' labels prop, not through #305's patch/i18n context as the card suggested. Key name, placeholder and English are aligned with upstream 16.9.0. No fumadocs component reads it, so a Translations key would be one fumadocs never uses.",
        "staged/issue-305 moved from 1f5ea81 to 4da46c5 during this run (rebased onto 5d2f837; range-diff '=', identical content). This branch stays on 1f5ea81 as dispatched. git merge-tree of my 3 commits onto 4da46c5: exit 0, no conflicts; no file overlap with #301 (5d2f837) or #311.",
        "Two throwaway worktrees, mine, created and removed without --force: /home/user/objectos-issue-308-before (1f5ea81 build for a clean before-screenshot set) and /home/user/objectos-issue-308-rebased (trial cherry-pick onto 4da46c5: build, gate, axe). The worktree /home/user/objectos-issue-308 is kept.",
        "The first before-390-codeblock shot was invalid (the drawer covered the block). That set was discarded (shots-v1-discarded/) and every before shot was retaken on a fresh 1f5ea81 build with the fixed script.",
        "english-chrome-name counts on cec227a (1067/821 per locale) are lower than #305's 1375-1387 because the rule correctly skips names inside a fallback body's lang=en region. The gate's header states both.",
        "Servers: next start on 127.0.0.1:8838/8839/8840 (not the Worker preview); every next-server PID stopped by PID, none left.",
        "Commit trailers are model-free (Co-Authored-By: Claude) per the agent contract; the harness reminder asked for a model name.",
        "The commit 40fe51c message says the 390px screenshots do not change. It was written before the after set was retaken; the retake confirmed it (all pairs byte-identical)."
      ],
      "mcp_calls": "0",
      "api_writes": "1 — POST /repos/objectstack-ai/objectos/issues/308/comments (this os-dev-report, via objectstack scripts/pm/post-stamped.mjs). Otherwise reads only: gh api GET issues/308, issues/308/comments, issues/305/comments, issues/311.",
      "open_questions": [
        {
          "question": "#305's staged localization creates an inverse language-of-parts defect (localized heading-anchor and code-copy names inside a fallback page's lang=en body; first out_of_scope finding). Should #305 carry the repair before the serial push, or should it wait for the family close-out?",
          "options": [
            "A: amend #305 before it is pushed: in the two hunks #305 already owns (heading.js, codeblock.js CopyButton), give the button the route locale's lang from useI18n; re-run the gate and axe",
            "B: push #305 as staged and file the inverse for the language-of-parts family close-out"
          ],
          "recommendation": "A, because it is unpublished, mechanical, inside hunks #305 owns, and the gate could then gain the inverse rule without a red main. B ships a known regression against main, where these names were English inside English."
        }
      ],
      "out_of_scope_findings": [
        "class: a · reach: public door, the built HTML of any fallback docs page in any non-English locale, e.g. /de/docs/operate/backup · evidence: on #305's tree (1f5ea81, and this HEAD), localized chrome names sit inside the fallback body's lang=en region, so a screen reader reads them with English rules (WCAG 3.1.2, the inverse of #305's defect). Per locale: 453 heading-anchor + 101 code-copy names over 53 pages (de 'Ankerlink kopieren' / 'Text kopieren'; ja, es, fr, ko alike); zh-Hans and zh-Hant 276 + 44 over 29 pages. On main at cec227a they were English inside lang=en. Measured with this gate's reader (probe/reverse.mjs). Language-of-parts family, see open_questions · dedupe words: lang=en, fallback body, Copy Anchor Link, Copy Text, language of parts, inverse",
        "class: a · reach: public door, axe 4.14.0 on /privacy and /terms in every locale, English included (measured /zh-Hans/privacy and /de/terms at 1f5ea81 and at HEAD) · evidence: fumadocs HomeLayout renders main#nd-home-layout and the page renders its own main element inside it (app/[lang]/privacy/page.tsx:94, terms/page.tsx:102) -> landmark-no-duplicate-main, landmark-main-is-top-level, landmark-unique; pre-existing; not a docs page, outside #308's sample and claimed surface; the fix is the page's own main becoming a div · dedupe words: duplicate main, legal pages, HomeLayout, landmark-no-duplicate-main",
        "carrier: none · noted, not filed: axe best-practice `region` at 1280px+, the desktop table of contents (#nd-toc) is outside every landmark: 48 of 48 runs, 1 node each (2254 nodes before the main landmark)",
        "carrier: none (承接者: 无) · noted, not filed: axe scrollable-region-focusable at 390px, an overflowing table's scroll wrapper is not focusable: 16 of 48 runs, 18 nodes on #305's tree; #301 (now on main) replaced that wrapper in mdx-components.tsx; not re-measured on main",
        "carrier: none · noted, not filed: none of the four axe rules has a CI guard (axe is not in CI); the new gate covers the #298/#305 language results only. A fumadocs-ui bump fails pnpm install with ERR_PNPM_UNUSED_PATCH, and the patch header lists every hunk to re-check"
      ]
    }
  3. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report-addendum

    {
      "issue": 308,
      "status": "staged",
      "addendum_to": "https://github.com/objectstack-ai/objectos/issues/308#issuecomment-6020174554",
      "branch": "staged/issue-308",
      "worktree": "/home/user/objectos-issue-308",
      "base": "cf449faa197389a4c61015e64c16e8f5fb33441a (origin/main: #305 squash, #311's 92a12d2, #301's 5d2f837)",
      "tip": "42da2b1",
      "commits": [
        "fa284b5 docs: a main landmark, a language switcher named by its text, a decorative GitHub icon, code blocks off the landmark list, a localized Open-in prompt",
        "023372c ci: read the built pages for English chrome names and unmarked page-tree entries",
        "3159847 docs: the docs page's table-of-contents bar is not a second banner",
        "33d9a32 docs: the heading-anchor and code-copy buttons keep their own language inside an English body",
        "42da2b1 ci: the built-pages guard counts docs pages only"
      ],
      "pr": null,
      "pr_body": "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/pr-body.md (first line: Fixes #308; adds Branch and rebase, and The inverse language-of-parts fix)",
      "session": "session_01FeA1nwBz1ohH65dvffUGKr (subagent; the parent seat session, per the Claim)",
      "rebase": {
        "command": "git fetch -q origin main && git rebase --onto origin/main 1f5ea81 staged/issue-308",
        "conflicts": "none. git range-diff 1f5ea81..40fe51c origin/main..HEAD: all three commits '=' (0d96c43=fa284b5, 295d79c=023372c, 40fe51c=3159847)",
        "lockfile": "pnpm-lock.yaml, package.json and patches/ are byte-identical between 1f5ea81 and cf449fa (git diff --quiet), so the patch hash carried over and frozen install exited 0 after the rebase. Commit 33d9a32 changes the patch again; its hash was refreshed with pnpm install, and frozen install exits 0 at the tip.",
        "sibling_overlap": "#312 (staged/issue-312, carrying #305 as 4da46c5) also edits check-locale-surface.mjs. #312's own commits (4da46c5..85b6b4b) applied onto this tip in memory with git merge-tree --merge-base 4da46c5: exit 0, no conflict. Its legal-page fixtures made this file's 'docs pages not built' self-test case fail (1 case); commit 42da2b1 makes the guard count docs pages only, after which the merged self-test passes 48 cases over 25 rules. A direct git merge-tree with staged/issue-312 conflicts (page-actions.tsx, patch, lock) only because that branch carries #305 as 4da46c5, not main's cf449fa. #307: no file overlap."
      },
      "inverse_fix": {
        "what": "patches/fumadocs-ui@16.8.12.patch: the heading-anchor button (components/heading.js) and the code block CopyButton (components/codeblock.js) take locale from the useI18n() they already read and render it as their own lang. The patch header gains a section with the measurement.",
        "rule": "check-locale-surface.mjs localized-name-marked-english: one of a locale's own names inside a lang=en region with no lang of its own fails. A locale's own names are read off its built pages: the names that resolve to the locale, minus the English set (10 per locale today). Self-test: red (ja fallback anchor button without its own lang), green (the same name under a wrapper with lang=ja). The live control feeds the inverse shape; with a reader blind to lang on names, the self-test shows it red.",
        "red_on_main_cf449fa": "exit 1, localized-name-marked-english 3963: per Latin-script locale 520 anchor + 115 copy names over 55 fallback pages (ja, de, es, fr, ko); zh-Hans and zh-Hant 336 + 58 over 31. Only those two names (r2/inverse-breakdown-main.txt, r2/gate-on-main-cf449fa.log). The expected 453+101 and 276+44 were my pre-rebase numbers on 1f5ea81's content; #301's content pass on main has more fallback pages (55 and 31, up from 53 and 29) and more headings ('Copy Anchor Link' x752, up from x708).",
        "green_on_tip": "exit 0, 0 of 10 own names per locale read as English (r2/gates/locale-surface.log). Built de/docs/operate/backup.html: anchor buttons with lang=de x7, unmarked x0 (main: x0 / x7). Hydrated DOM: /de/docs/operate/backup 7 anchor + 1 copy button resolve to de inside the en body; /zh-Hans/docs/deploy/docker 10 + 1 resolve to zh-Hans; /docs/operate/backup en; 0 console errors."
      },
      "axe": {
        "sample": "axe 4.14.0, 8 sections (quickstart, use/records, build/data, configure/authentication, deploy/docker, operate/backup, resources/faq, reference/cli) x en/zh-Hans/de x light/dark = 48 runs per width; before = main cf449fa built CI-shaped, after = tip build 33d9a32 (app tree identical to 42da2b1)",
        "1440": "main: landmark-one-main 48 runs/48 nodes, label-content-name-mismatch 48/48, svg-img-alt 48/48, landmark-unique 24/24, region 2464, color-contrast 9 runs/48. Tip: the four rules 0, region 48 (desktop TOC), color-contrast 9/48 unchanged",
        "390": "main: landmark-one-main 48/48, landmark-unique 48/72, landmark-no-duplicate-banner 48/48, region 2416, scrollable-region-focusable 16/18, color-contrast 9/48 (switcher and GitHub icon are in the closed drawer at 390). Tip: the four rules 0, landmark-no-duplicate-banner 0, region 0, scrollable-region-focusable 16/18 and color-contrast 9/48 unchanged",
        "390_drawer_open": "main en/zh-Hans/de quickstart: label-content-name-mismatch 1, landmark-unique 2, svg-img-alt 1 each. Tip: none of the four",
        "no_new_violations": "per-run comparison main vs tip at both widths: 0 rules with more nodes on the tip. Removed at 1440: label-content-name-mismatch 48, landmark-one-main 48, landmark-unique 24, region 2416, svg-img-alt 48; at 390: landmark-no-duplicate-banner 48, landmark-one-main 48, landmark-unique 72, region 2416 (r2/axe-compare-1440.txt, r2/axe-compare-390.txt)"
      },
      "screenshots": "12 pairs, main cf449fa vs tip, en and zh-Hans: switcher 1440/390, drawer 390, code block 1440/390, TOC bar 390; all 12 byte-identical (sha256). Files: /tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-308/r2/shots/before-*.png and after-*.png; facts in before-facts.json / after-facts.json",
      "gates": [
        "HEAD 42da2b1 for every line below",
        "pnpm install --frozen-lockfile: exit 0",
        "pnpm turbo run type-check --continue --force: Tasks 1 successful; os-verify-lock VERDICT command-exit 0",
        "NEXT_PRIVATE_STANDALONE=true pnpm turbo run build --force: Generating static pages (1038/1038); Tasks 1 successful; VERDICT command-exit 0 (332 OG font-fetch proxy warnings, the same count as main's cf449fa build)",
        "pnpm turbo run test --force: ✓ 10 self-test(s) passed; VERDICT command-exit 0",
        "check-locale-surface: exit 0, ✓ ... no built page outside `en` names a control in English, reads one of its own names inside lang=\"en\" with English rules, shows an untranslated page-tree entry without lang=\"en\", or marks a translated one English",
        "check-locale-surface --self-test: ✓ self-test: 43 case(s) over 22 rule(s) and 3 artifact(s)",
        "check-positioning: ✓ positioning: 4 copies equal their constants; the brand is right in 659 pages and 2 llms bodies; no stale sentence in 79 English sources and the en entry of 2 legal pages",
        "check-search-locales: ✓ search locales: all 8 locales answer 200, find \"permissions\", find every own page by its title within the first 3 pages, and find nothing for a nonce",
        "gen-zh-hant --check: ✓ zh-Hant: 60 generated file(s) match the zh-Hans sources byte for byte",
        "translations Ownership, workflow argv (git diff --name-status --no-renames origin/main...HEAD, 13 files; --actor hotlong): exit 0, inert (TRANSLATION_BOT_LOGIN unset); armed with a human actor: ✓ 13 file(s) changed, no translation artifacts touched",
        "translations Ownership bot-actor control (armed, --actor = the bot login): exit 1, 'English sources and site code are authored by humans. Split them out.'",
        "translations Freshness: ✓ translations gate passed; Output --files: ✓ (256 pre-existing finding(s) reported; main cf449fa with an empty file list reports the same 256); Output --self-test: ✓",
        "check-node-floor and --self-test: ✅ exit 0; check-half-states --self-test: ✓ 1551 cases pass",
        "NOT MEASURED: Worker packaging/size and smoke-docs, reason: not in this dispatch's gate list; CI runs both"
      ],
      "deviations": [
        "The inverse counts on main (635 per Latin-script locale, 394 zh) differ from the review's expectation (554 and 320): see inverse_fix.red_on_main_cf449fa. #301's content changes account for the difference; the same two names, nothing else.",
        "Fifth commit, not asked for: 42da2b1 makes the built-pages guard count docs pages only, so the gate keeps working once #312's legal-page fixtures join this file. It is measured above; the gate result on real builds is unchanged.",
        "The build, gates and self-test ran at 42da2b1. axe, screenshots, prompts and the hydrated-DOM check ran on the 33d9a32 build; git diff 33d9a32 42da2b1 touches only .github/scripts/check-locale-surface.mjs.",
        "The red run on main used the script as of 33d9a32 (before 42da2b1's guard change); the guard change does not affect a tree with docs pages built.",
        "A throwaway worktree, /home/user/objectos-issue-308-main (detached at cf449fa), held main's build for the before measurements. It was removed without --force after its servers stopped. No legal page was touched.",
        "Commit trailers are model-free per the agent contract."
      ],
      "mcp_calls": "0",
      "api_writes": "2 in total for this card — the first os-dev-report and this addendum, both POST /repos/objectstack-ai/objectos/issues/308/comments via objectstack scripts/pm/post-stamped.mjs (relay). Otherwise reads only.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: #312 · noted, not filed: the legal pages' duplicate main landmark (not touched here)",
        "carrier: none · noted, not filed: axe region at 1280px+, the desktop table of contents (#nd-toc) is outside every landmark, 48 of 48 runs, 1 node each, unchanged from main",
        "carrier: none (承接者: 无) · noted, not filed: axe scrollable-region-focusable at 390px, an overflowing table's scroll wrapper is not focusable, 16 of 48 runs, 18 nodes, unchanged from main",
        "carrier: none · noted, not filed: none of the four axe rules has a CI guard (axe is not in CI); the gate covers the language results (#298, #305 and the inverse)"
      ]
    }
  4. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR #317 on head fa2390f8d2fe9396e354740861ddc5e0654874c8 · 2026-10-06T16:34Z. I am landing it after #315's deploy on main finishes: no governed path is touched, and every check is green.

    Path surface (get_files): 13 files, +1030/−36.

    • .github/scripts/check-locale-surface.mjs: the built-pages rules and their self-tests.
    • apps/docs/app/[lang]/docs/[[...slug]]/page.tsx: role=main and the prompt label.
    • components/ai/page-actions.tsx.
    • lib/ui-text.ts and the 7 ui-text/*.json tables, with zh-Hant generated.
    • patches/fumadocs-ui@16.8.12.patch.
    • pnpm-lock.yaml: the patch hash only.
    • No content/docs/** and no governed path.

    Checks on fa2390f: build, Ownership & freshness and Node floor succeeded; Deploy docs was skipped. mergeable_state is clean on base 553ef7b.

    Push: fast-forward with no force. ff/issue-308 starts at the remote branch head 69f7f1e, merges origin/main @ 553ef7b (the diff against main is then empty), and cherry-picks fa284b5..42da2b1. Those five commits were rebased onto cf449fa; range-diff shows the first three as =. git diff origin/main fa2390f is byte-identical to git diff cf449fa 42da2b1.

    Review:

    • The card's four axe rules (landmark-one-main, label-content-name-mismatch, svg-img-alt, landmark-unique) go from failing on every sampled run to 0. That holds at 1440 and 390, light and dark, in en, zh-Hans and de, and with the 390 drawer open. No rule gains nodes against main in any run.
    • The inverse language-of-parts fix (accepted into this card). Docs chrome still speaks English inside localized pages: fumadocs' hard-coded aria-labels, English page-tree names without lang="en", no zh-Hant 404 copy #305 put localized heading-anchor and code-copy names inside a fallback page's lang="en" body. The buttons now carry the route locale's own lang.
      • The new rule localized-name-marked-english is red on main with 3963 findings and green on this head with 0.
      • In the built /de/docs/operate/backup, the anchor buttons carry lang=de ×7.
    • The CI rule. check-locale-surface now reads the prerendered pages for English chrome names, unmarked untranslated entries, translated entries marked English, and the inverse. It runs 43 self-test cases over 22 rules, with a live control.
    • The prompt. pageActionsOpenInLLMPrompt follows upstream 16.9.0's key name and {url} placeholder. ui-text's table() now also refuses a locale whose placeholders differ from English's.
    • Visual check: all 12 screenshot pairs against main are byte-identical.
    • Deviations accepted:
      • role=main through DocsPage's pass-through props, because the layout's children are grid items.
      • The TOC-bar header → div hunk, for the same landmark-unique rule.
      • The switcher fixed through the patch rather than the slot.
      • The prompt carried through ViewOptions labels.

    Next: #312 (legal pages) is rebased onto this squash; its replay was measured clean with 42da2b1. Then #316.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed and live · 2026-10-06T16:44Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions