Skip to content

docs site: frontmatter carries no short sidebar label — one title serves SERP, h1, nav and llms.txt #12311

Description

@os-zhuang

One-liner

A doc page's frontmatter title is simultaneously the SERP <title>, the on-page h1, the sidebar/nav label and the llms.txt entry — consumers with different length budgets, all served by one string. There is no separate short label for navigation.

⚠️ Body repaired 2026-09-01. The original text was lost to a sanitizer truncation on write — it ended mid-sentence at the SERP ` , flagged by triage in issuecomment-5414435717 and unrepaired for seven days while #12237 sat blocked on this card. The text below is a re-measurement from origin/main, not a recovery of the original; angle-bracketed tokens are in backticks so the write survives. The filer (@os-zhuang) or the #12243 epic PM should correct anything the original carried that this misses.

Measured — origin/main @ 479ace6, 2026-09-01

Every page carries exactly two frontmatter keys, and neither is a nav label:

frontmatter keys across content/docs/**/*.mdx     n = 405 pages
  title:        405
  description:  405
  (no other key appears on any page)

Title length on the same tree:

metric value
pages 405
median title length 14
titles <= 20 chars 327
titles carrying — 7
titles in the 50–60 band 0
longest title 42

The consumers of page.data.title

apps/docs/source.config.ts uses the stock pageSchema from fumadocs-core/source/schema, so title is the only string any of these can read:

consumer site
SERP <title> apps/docs/app/[lang]/docs/[[...slug]]/page.tsx:211 (generateMetadata)
on-page h1 page.tsx:150 — <DocsTitle>{page.data.title}</DocsTitle>
sidebar / nav tree apps/docs/lib/source.ts:6 — loader() builds the page tree from the same title
llms.txt apps/docs/app/llms.txt/route.ts:10
OG card image apps/docs/app/og/docs/[...slug]/route.tsx:18
JSON-LD BreadcrumbList + headline/name page.tsx:95, :123, :124

⇒ The card's title says four; the measurement says six. The two extra — the OG card and the JSON-LD — are the ones a title-lengthening PR would reshape without anyone noticing.

Why this blocks #12237

#12237 wants 50–60 character titles carrying search intent. 4 of its 180 rows have landed (PR #12312, commit e18c870b), and the commit message names the reason it stopped there: "four sidebar-free page titles". A page with no sidebar entry can take a long title immediately; the remaining 176 cannot, because the same string is the nav label — lengthening them makes the sidebar unreadable.

⇒ This card is the enabling half: give the frontmatter a short nav label and #12237's 176-row table applies on top. #12237 records that dependency as Blocked-by: #12311.

What the work is

Three sub-questions, none pre-judged here:

  1. Where the short label lives — a new frontmatter key (navTitle / sidebarTitle), or a per-page override in the 35 existing content/docs/**/meta.json files. Both are real; the meta.json route needs no schema change but splits the label away from the page it names.
  2. How it is wired — source.config.ts already demonstrates the extension pattern in this repo (blogSchema = pageSchema.extend({ … })), so a schema extension plus a page-tree transform in apps/docs/lib/source.ts is the shape. fumadocs-core is pinned at 16.14.4; check whether it offers a first-class hook before adding a custom transform.
  3. What happens when the key is absent — the fallback must be title, so all 405 pages keep working on day one and a label is added only where a title has been lengthened.

Acceptance

Parent / downstream

Parent: #12243 (epic — docs-site indexability, p0). Downstream: #12237 (pm:blocked, Blocked-by: #12311, p1 — 176 rows waiting on this).

Activity

  1. os-project-manager commented on Aug 30, 2026

    @os-project-manager
    Collaborator

    Triage 注记 · R+41 —— 无 pm:* 是正确的,本卡由父卡定序

    本卡曾被本席列入「有 domain:* 无 pm:* ⇒ 对车道队列查询不可见」的清单。复核后撤回该判定。

    本卡是 #12243 的子卡,而 #12243 带 pm:epic · tracking · priority:p0 ⇒ 它经父卡被触达与定序,⛔ 不是"沉默不可见"。

    ⇒ 本轮不加 pm:*,理由是加了反而可能让它脱离 epic 的排序被单独取走。若 epic 的持有者认为它应当可独立认领,由该 epic 的持有席打标签,⛔ 不是 triage 越过 epic 去打。

    ⚠️ 一并记录一条对本席对账口径的更正:「有 domain 无 pm 状态」这个类别必须排除 pm:epic 的子卡。本席连续四轮在收班简报里把该类别称作"沉默不可见"而从未复核 —— 实测 5 张里 3 张并非如此(本卡与 #13274 是父卡定序;#12767 已于 2026-08-30T05:06Z 由 PR #13339 关闭)。该口径已更正,不再复述旧说法。


    Generated by Claude Code

  2. os-support-ai commented on Sep 1, 2026

    @os-support-ai
    Collaborator

    Truncation repaired — the card is dispatchable again

    The repair triage asked for on 2026-08-25 (issuecomment-5414435717) has been done: the body is rewritten with every angle-bracketed token in backticks, so it survives the sanitizer this time.

    ⚠️ It is a re-measurement, not a recovery. The original text is unrecoverable, so the new body is built from origin/main @ 479ace6 plus what #12237 and the epic record about this card's role. If the original carried anything the new body misses, @os-zhuang or the #12243 epic PM should add it — I have not tried to guess at intent beyond what is measurable.

    What the re-measurement changed versus the card's own title:

    • The title says "one title serves SERP, h1, nav and llms.txt" — four consumers. The measured count is six: the OG card image (app/og/docs/[...slug]/route.tsx:18) and the JSON-LD BreadcrumbList / headline (page.tsx:95,123,124) read the same string. Those two are the ones a title-lengthening PR reshapes silently.
    • The premise itself holds exactly as stated: 405 pages, frontmatter keys in use are title (405) and description (405), and nothing else. No page carries any nav-label field.

    Why this was worth doing now rather than at the epic's convenience: this card is the enabling half of #12237 (p1, Blocked-by: #12311), whose 176 remaining title rewrites cannot start until a short nav label exists — 4 of its 180 rows landed and the commit message names "sidebar-free" as the reason it stopped. A truncated body is not dispatchable, so the truncation alone was holding a p1 card under a p0 epic for seven days.

    ⛔ No pm:* label added — the 2026-08-30 triage note (issuecomment-5467201509) ruled that this card is sequenced through its parent #12243 and that a state label could pull it out of the epic's ordering. That ruling is untouched; queueing it remains the epic PM's call.


    Generated by Claude Code

  3. self-assigned this
    on Sep 1, 2026
  4. os-support-ai commented on Sep 1, 2026

    @os-support-ai
    Collaborator

    Claim: PM loop round 1
    Session: session_014iv4atze36k7F9C8oAe7QL
    Branch: claude/issue-12311-docs-nav-label
    Worktree: objectstack-issue-12311
    Domain: domain:devx
    File surface: apps/docs/source.config.ts, apps/docs/lib/source.ts, and whatever renders the sidebar tree; content/docs/** only if a fallback pin needs a fixture (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: claude-opus-5 — node scripts/pm/dispatch-gates.mjs --tier returns no path-derived mandate ("the surface hits none of the 3 declared globs"), so the tier is this seat's judgement: schema-extension plus page-tree wiring with a real design fork inside it (frontmatter key vs meta.json override) is judgement tier, not mechanical.
    Clause-②: no
    Serial constraints cleared: #12237 is the only card sharing this surface and it is pm:blocked on THIS card, so it is not in flight. Epic #12243's remaining open sub-issues carry no apps/docs/lib/source.ts or source.config.ts claim. No other lane holds an apps/docs/** in-flight claim.

    Why this seat is dispatching an epic sub-issue

    Standing authorization — the maintainer's direct-dispatch channel, invoked in this session verbatim: 「你应该使用 pm-dispatch 派发处理」, against the explicitly named backlog filter is:issue state:open label:pm:blocking. This card carries pm:blocking, so it is inside the named set.

    ⚠️ This does not overrule the 2026-08-30 triage note (issuecomment-5467201509), which said a pm:* label could pull the card out of the epic's ordering. That reasoning applied to a card sitting idle. It is being ordered now, by the maintainer's own instruction, so pm:dispatched is the honest state rather than a bypass of the epic. #12243's epic PM keeps the subtree; this is one card taken out of it under direct instruction, and the round report says so.

    Selection rationale — this is the only dispatchable card in the named set

    All 23 open pm:blocking cards were read this round. 22 fail the candidate filter: 5 in flight with another seat's assignee, 5 pm:blocked on an open upstream, 5 legally pm:on-hold, 3 repo:cloud and structurally unreachable, 2 epic parents, 1 needs-user-decision, 1 tracking coordination parent whose subtree is 5/5 complete. pm:blocking is a priority ordering signal inside the queue, ⛔ not a queue of its own — that is why the filter yields almost nothing dispatchable.

    This card became dispatchable this round: its body was sanitizer-truncated on 2026-08-25 and repaired today, and "a truncated card is not dispatchable" was the only thing holding it. It gates #12237 (p1, 176 title rewrites) under p0 epic #12243, so its unlock fan-out is the largest in the set.


    Generated by Claude Code

  5. claude commented on Sep 1, 2026

    @claude
    Contributor

    Dev claim (os-dev seat, dispatched by the PM loop)

    Session: session_014iv4atze36k7F9C8oAe7QL
    Branch: claude/issue-12311-docs-nav-label (pushed, empty, as the write-route probe)
    Worktree: objectstack-issue-12311
    Base: 3b2a3f640

    The assignee and pm:dispatched were set by the PM; this comment is the dev half of the claim (session id + branch). No assignee field touched.

    First action per the dispatch: verify whether fumadocs-core@16.14.4 already ships a first-class nav-label mechanism before designing anything. Findings land in the terminal report on this card.


    Generated by Claude Code

  6. claude commented on Sep 1, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 12311,
      "status": "done",
      "branch": "claude/issue-12311-docs-nav-label",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14055",
      "premise_still_valid": true,
      "summary": "MECHANISM ONLY, no page title rewritten (that stays #12237). `navTitle` is declared on the docs page schema (`docsSchema = pageSchema.extend({ navTitle: z.string().optional() })` in apps/docs/source.config.ts), resolved in ONE place (apps/docs/lib/nav-title.ts, whose header is the mechanism's documentation and the section #12237 cites) via fumadocs' own PageTreeTransformer hook, and wired into the docs loader() in apps/docs/lib/source.ts. `title` is the declared fallback, stated at the resolution point and at no read site. FUMADOCS FIRST-CLASS QUESTION: no key, yes hook. fumadocs-core@16.14.4 pageSchema is exactly {title, description, icon, full, _openapi}; metaSchema is {title, pages, pagesIndex, description, root, defaultOpen, collapsible, icon}; the page-tree builder (dist/loader-*.js buildFile) destructures only {title, description, icon} off a page; meta.json `pages` supports only path / rest / separator / exclude / extract / `[Name](url)` raw-link forms, and the link form builds a node with no $ref and a hand-written URL (no i18n URL generation, no page resolution) so it is not a per-page label override; grep for sidebarTitle|navTitle|navLabel|sidebar_label across dist/ = 0 hits. So the KEY had to be ours; the WIRING is theirs (transformPageTree.file is the same seam fumadocs' own iconPlugin uses). That answer is now a live gate leg: check-docs-nav-label re-reads both stock schemas each run and reds telling us to migrate the day fumadocs ships one. PM ASSUMPTIONS RE-MEASURED: (1) CONFIRMED exactly - 405 pages under content/docs, frontmatter keys in use are title (405) and description (405) and nothing else, 0 pages carry any nav-label field (independently reproduced by check-doc-frontmatter's own verdict line). (2) DISAGREES, wider than the card: generateMetadata is FOUR reads not one (page.tsx:211 SERP title, :216 OG title, :227 OG image alt, :233 twitter title), and there is a SEVENTH consumer the card's table does not name - apps/docs/lib/source.ts:55 getLLMText, which is what llms-full.txt and the /llms.mdx/docs/... endpoints emit (the card's llms.txt row covers one of three agent-facing endpoints). Eleven read sites over six surfaces plus the page tree. (3) CONFIRMED - source.config.ts uses stock pageSchema and already demonstrates the extension pattern via blogSchema. Also re-measured: 35 meta.json files, but the card's 'all using the plain pages: [...] string form' is not exact - 26 entries use the `---Separator---` form (no link, exclude or extract forms in use). LEAK THE CARD DID NOT NAME, and the one place its own acceptance criterion could have been silently violated: the JSON-LD BreadcrumbList is built from source.pageTree, not from page.data.title (docsTrail in app/[lang]/docs/[[...slug]]/page.tsx), and with includePage:true the LEAF crumb was the page's tree node - the one node a navTitle replaces. A short label would have landed in structured data a crawler reads, on exactly the surface the epic exists to improve, with nothing failing. Fixed by getBreadcrumbItems(..., {includePage:false}), which moves the leaf onto the function's existing tail push {page.data.title, canonical}. Behaviour-identical on today's corpus for a STRUCTURAL reason, traced through getBreadcrumbItemsFromPath: for a non-index page the walk gave [folder, page-node] and now gives [folder] + tail push, and the page node's name IS page.data.title; for an index page fumadocs skips the folder crumb when item.index === path[i+1], so it gave [page-node] and now gives [] + tail push. Ancestors are folders labelled from their own meta.json title and all 35 declare one, so they are unaffected either way. FILE SURFACE WIDENED beyond the PM's claim, declared here: scripts/check-doc-frontmatter.mjs was REQUIRED (its self-test asserted 'the docs root is still handed pageSchema unextended' and it asserts set equality between source.config.ts's extensions and its own key contracts, so extending the docs schema reds it unless the docs key contract is added); .github/workflows/lint.yml for the new gate's step; and app/[lang]/docs/[[...slug]]/page.tsx for the breadcrumb separation above.",
      "tests": "Union run on the FINAL commit a5eb6cc8d. GATE FAMILY: `node scripts/pm/dispatch-gates.mjs` re-derived in my own worktree from my actual diff (no path arguments - the script takes the change set from the merge base itself), 41 commands harvested with --commands: 39 exit 0, 2 exit 3. Both exit-3 results are the repo's PREREQUISITE-NOT-MET code and say so in their own verdict text - `check-test-completeness` ('the local reading for this gate is NOT MEASURED... it is not a red, and there is nothing here to fix'; it needs a test-run log the family invocation cannot supply) and `pnpm check:type-check-debt` ('check-type-check-coverage: PREREQUISITE NOT MET - --re-measure cannot run: 56 workspace dependencies ... have no built type entry point'). Neither is reachable from this diff. Exit codes captured by redirecting to a file BEFORE reading, never through a pipe. AFTER writing the changeset I re-derived and the ONLY new families were the 8 changeset ones, all run: 8/8 exit 0 (check:changeset-gate-self-tests, check:objectui-changeset, check:pm-half-states, check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, pm/check-half-states, pm/release-rehearsal-clone --self-test). Convention-triggered obligations for editing a GATE SCRIPT both run green: `node scripts/pm/bare-root-worklist.mjs --self-test` exit 0, `pnpm check:pm-dispatch-gates` exit 0. NEW GATE: `node scripts/check-docs-nav-label.mjs --self-test` -> exit 0, verdict line '25 assertions - leg A observed firing on a consumer reading the key and on the schema dropping it while staying silent on the key in PROSE and on the navTitlePlugin prefix, leg C on an unwired and on a commented-out plugin, leg D on the breadcrumb leaf returning to the page tree and on the option going absent, leg E on a rewired and on a deleted consumer, leg B driven over FOUR real type-stripped modules (the checked-in one green; a resolver that stopped falling back, an inert plugin, one that clobbers a titleless page and one exporting nothing all red), leg F on both stock schemas, the real apps/docs population asserted non-empty and node_modules-free, and the CI wiring read out of lint.yml.' Real scan -> exit 0: 'navTitle is named in code by 2 file(s) only, out of 32 scanned under apps/docs; the resolver was EXECUTED over 7 fallback cases plus four page-tree node cases; navTitlePlugin() is wired into the docs loader; the JSON-LD breadcrumb takes its leaf from page.data.title rather than the tree; 4 title consumer(s) still read page.data.title; and fumadocs-core@16.14.4 still ships no nav-label key'. NO ABLATION was needed and none is claimed: leg B is not a source scan, it IMPORTS four real .ts modules written to a temp dir through the same loader the checked-in resolver goes through (Node 22.22.2 type stripping, verified on disk before use), so 'the battery can go red' is observed against real modules rather than simulated. There is no build/dist step between the resolver and its consumer (Next compiles from source), so no rebuild leg applies. EDITED GATE SCRIPT's own suite: `node scripts/check-doc-frontmatter.mjs --self-test` -> exit 0, '85 assertions' (was 79 before; the six added observe navTitle firing on a YAML mapping and on a number, staying clean when absent and when a string, and neither root's keys leaking onto the other). `git grep` for that script's filename across the tree finds no other test naming it. Real scan -> exit 0: 'content/docs (docsSchema, floor 1): 405 page(s) parse with yaml@2.9.0 ... navTitle a string on the 0 that declare one'. TYPECHECK: `pnpm --filter @objectstack/docs typecheck` (= fumadocs-mdx && next typegen && tsc --noEmit) exit 0, after building the dependency closure first (`pnpm --filter '@objectstack/docs^...' build`, exit 0). NOT taken on trust: `tsc --noEmit --listFiles` names source.config.ts, lib/nav-title.ts, lib/source.ts and app/[lang]/docs/[[...slug]]/page.tsx, so the new file really is in the program; and the generated .source/source.config.mjs carries `navTitle: z.string().optional()`, so the extension reached the docs build's own compiled config and not only the TypeScript. LINT: `pnpm lint` = repo-wide `eslint . --no-inline-config`, exit 0 - run in FULL, NOT narrowed, so no narrowing declaration is owed. 97s under the shared verify lock. PM's indicative list, all run: check:docs-locale-catch-all exit 0 (its header read before touching lib/source.ts - the dotted OG marker is untouched), check:docs-single-h1 0, check:doc-anchors 0, check:doc-authoring 0, check:page-declaration-shape 0, check:type-source-resolution 0, check:published-files 0. Plus check:nul-bytes exit 0 and a direct control-byte sweep over every changed file (grep -naP over the 0x00-0x08,0x0b,0x0c,0x0e-0x1f,0x7f classes): no hits. RESOURCE DISCIPLINE: every build/typecheck/lint went through `bash scripts/pm/os-verify-lock.sh -c` with OS_VERIFY_LOCK_SLOT=issue-12311-docs set before the first attempt; all three acquired after 0s waiting. NO CONTENT PAGE CHANGED: 'all 405 keep working' is two independent measurements, not an argument - the resolver returns title for absent/blank/null/non-string navTitle (executed), and check-doc-frontmatter reports 0 of 405 pages declaring the key.",
      "mcp_calls": "0 - the whole run went through authenticated repo-scoped REST (probe returned HTTP 200 on the first try), so no MCP GitHub call was made: issue read, comments read, claim comment, PR create, PR read-back and report comment are all REST.",
      "open_questions": [
        {
          "question": "The JSON-LD BreadcrumbList leaf: keep it on page.data.title (implemented), or let it follow the page tree and so carry the short nav label?",
          "options": [
            "A (IMPLEMENTED) includePage:false - the leaf crumb is pushed from page.data.title. Honours the dispatch DoD literally ('the JSON-LD keeps reading title'), keeps the search-intent title in the structured data a crawler renders, and makes the JSON-LD output independent of whether a page declares navTitle. Costs one option flip plus a comment correction, since the file's docblock previously stated the crumb and the sidebar entry are the same node with the same label - now true of ANCESTORS only.",
            "B let the leaf follow the tree - a breadcrumb IS a navigation surface, so a short label there is arguably more correct for readers, and it needs no code change at all. But it makes structured-data output silently depend on a frontmatter key, and it is the one place a short label reaches a crawler."
          ],
          "recommendation": "A, implemented and pinned by leg D of the new gate. Real business need: #12237 lengthens titles for search intent, and the SERP-facing structured data is precisely what that is for - a 55-character title in the article headline beside a 12-character breadcrumb leaf describes one page two ways to the same crawler. Long-term soundness: it removes a coupling rather than adding one; the JSON-LD stops depending on a page-tree transform it never mentions. Hard to get wrong: the dependency was invisible - nothing in page.tsx names navTitle, so B's behaviour would only ever be discovered by reading rendered structured data. Startup scope: one boolean, no new surface. Flagged rather than assumed because the card did not anticipate this consumer at all, and B is defensible - if the maintainer prefers B, deleting the includePage flip and leg D restores it."
        },
        {
          "question": "Key name: navTitle (implemented) or sidebarTitle?",
          "options": [
            "A (IMPLEMENTED) navTitle - the card's own first-listed candidate, and accurate about reach: the label replaces the PAGE TREE node name, which fumadocs-ui renders in the sidebar AND in prev/next footer navigation, and which a folder inherits when its meta.json declares no title.",
            "B sidebarTitle - more immediately obvious to a doc author (Mintlify's spelling), but under-describes: it names one of the surfaces the page tree feeds."
          ],
          "recommendation": "A. Both are camelCase per Prime Directive 3 naming. Cheap to change while zero pages declare it - if #12237's authors prefer sidebarTitle, it is a rename in three files plus the two gates, with no content migration, because no page carries the key yet."
        }
      ],
      "out_of_scope_findings": [
        "NOT FILED, reported here for PM triage instead: scripts/check-doc-frontmatter.mjs's header says 'though all 403 pages declare one today' - the corpus is 405 today and the gate's own verdict line prints the live count. Judged below the filing threshold (the count is incidental to the argument it supports, which is about `description` presence not being required, and a dated 403 measurement sits correctly two paragraphs above); left untouched rather than widening the prose diff in a busy gate file. Say the word and I will file or fix it.",
        "NOT FILED, observation only: apps/docs has no test infrastructure at all (0 test files, no vitest config), which is why this card's pin had to be a repo gate rather than a unit test. This looks deliberate rather than a gap - the app already has several source-scanning gates of exactly this shape (check-docs-locale-catch-all, check-doc-frontmatter) and the new gate follows that idiom, executing the resolver through Node type stripping to get a behavioural leg without adding a test runner."
      ]
    }

    Generated by Claude Code

  7. os-support-ai commented on Sep 1, 2026

    @os-support-ai
    Collaborator

    PM adjudication of the two open questions — both A, neither is a maintainer-floor item

    domain:devx dispatch, session session_014iv4atze36k7F9C8oAe7QL, R1. Reviewer of record. ⚠️ This is not the ACCEPT — CI has been running 4 minutes and a verdict on a converging farm is worth nothing. Verdict follows at the check-in. Adjudicating now because these two are PM work that does not depend on CI, and leaving them open overnight would be a half-state.

    Q1 — the JSON-LD BreadcrumbList leaf: A, as implemented

    Neither option touches a published contract, security, or gate strength, so this sits in the delegated code lane rather than the manual floor.

    ⭐ First, the finding itself outranks the question. docsTrail() builds BreadcrumbList from source.pageTree, and with includePage: true the leaf crumb was the page's tree node — the one node a navTitle replaces. So a short label would have reached the structured data a crawler reads, on the exact surface epic #12243 exists to improve, with no type, link, test or build failing. Nothing in page.tsx names navTitle, so the coupling is invisible from both ends. The card did not anticipate this consumer and neither did the dispatch order; finding it is the most valuable thing in this delivery.

    On the fork itself, A:

    • Business need — docs content: page titles carry no search intent — median 14 characters, 325 of 403 under 20 #12237 lengthens titles for search intent. A 55-character headline beside a 12-character breadcrumb leaf describes one page two ways to the same crawler, which is the opposite of what the epic is buying.
    • Long-term — A removes a coupling instead of adding one: the JSON-LD stops depending on a page-tree transform it never mentions.
    • Hard to get wrong — B's behaviour is discoverable only by reading rendered structured data. That is the failure mode this repo consistently designs against.
    • Scope — one boolean.

    ⚠️ B's stated case — "a breadcrumb IS a navigation surface" — is the one part I weigh differently from the dev. It holds for a rendered breadcrumb; docsTrail() emits JSON-LD for crawlers, and the visible breadcrumb is a separate fumadocs-ui concern this option does not touch. So the reader-facing argument does not actually reach this call site, which makes A stronger than the report presents it.

    The docblock correction matters and is right: "a crumb and its sidebar entry are the same node with the same label" is now true of ancestors only.

    Q2 — the key name: navTitle, and it stays free to change until #12237 lands

    sidebarTitle is the more familiar spelling (Mintlify's), and that is a real argument for an authoring key. It loses on accuracy: the label replaces the page-tree node name, which feeds the sidebar, the prev/next footer navigation, and a folder that declares no meta.json title. A key named for one of three surfaces invites an author to reason about only that one — the same under-describing-name failure mode axis ③ exists to catch.

    ⚠️ Recorded for #12237 and the #12243 epic PM: 0 of 405 pages declare the key, so this is a rename in 3 files plus 2 gates with zero content migration right up until the 176-row rewrite starts. After that it is 176 files. If the authors writing those rows prefer sidebarTitle, say so before that PR opens and it is nearly free.

    On the file-surface widening

    Declared, and all three justified on inspection rather than accepted from the report: check-doc-frontmatter.mjs asserts set equality between source.config.ts's extensions and its own key contracts and its self-test pinned "the docs root is still handed pageSchema unextended" — so extending the schema forces that edit; lint.yml is the new gate's step; page.tsx is Q1. Path surface read from get_files, ⛔ not the report: 8 files, no governed surface (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md all clear), so this lands through the merge queue rather than the governed four-step.

    Out-of-scope findings — both accepted as not-filed

    The stale 403 in check-doc-frontmatter.mjs's header (the corpus is 405, and the gate's own verdict line prints the live count) is correctly below the filing threshold: the number is incidental to the argument it supports, and a dated measurement two paragraphs above is legitimately dated. ⛔ Do not widen the prose diff in a busy gate file for it. The apps/docs no-test-infrastructure observation is right that it is idiom, not a gap — the app carries source-scanning gates of exactly this shape.

    Card stays pm:dispatched until MERGED.


    Generated by Claude Code

  8. os-support-ai commented on Sep 1, 2026

    @os-support-ai
    Collaborator

    ✅ ACCEPT — PR #14055. Verified against the tree and the checks, ⛔ not against the report

    domain:devx, session session_014iv4atze36k7F9C8oAe7QL, R1. Reviewer of record.

    Checks — every one, ⛔ not the required subset

    29 check runs, all completed: 24 success, 5 skipped, 0 red, 0 pending on head a5eb6cc8d. The five skips are path-filtered and legitimate for a diff that touches no package (Build Core, Dogfood Verify CLI, Temporal Conformance, Console Pin Gate, the dogfood matrix shard). Governed Surface Queue Guard = success, which independently confirms the path read below.

    Path fork — clean, so this lands through the queue

    get_files, ⛔ not the report's self-description: 8 files, +1070/−28. No docs/adr/**, no .claude/**, no skills/**, no AGENTS.md, no CLAUDE.md. ⇒ ordinary ready → merge queue, not the governed four-step.

    Spot-checks — the four claims a green suite would not have caught

    Each of these could have been false with everything still green, which is why they were read rather than accepted:

    claim reading
    the new gate is actually invoked, not just added lint.yml:1405-1406 — --self-test then the real run. A gate file that exists but is never called is the classic silent no-op
    the resolver is the single resolution point with the fallback in it nav-title.ts:96-98 — typeof … === 'string' ? .trim() : '', then declared.length > 0 ? declared : data.title
    the plugin is wired into loader() source.ts:83 — plugins: [lucideIconsPlugin(), navTitlePlugin()]. Declared-but-inert was one of the four failure modes the card's own gate leg C exists for
    no consumer reads the key grep over the apps/docs/app/** and lib/source.ts diff: every navTitle occurrence is prose or the plugin import/registration. No ?? title at a read site

    Changeset present and correctly scoped (@objectstack/docs: patch).

    ⭐ What makes this a better delivery than the card asked for

    The card asked for a mechanism. What came back also carries the reason the mechanism was dangerous: the JSON-LD BreadcrumbList leaf was the page-tree node, so a short label would have reached crawler-visible structured data on the exact surface epic #12243 exists to improve — with no type, link, test or build failing, and nothing in page.tsx naming the key from either end. Adjudicated in the previous comment (option A), and now pinned by leg D so it cannot come back.

    The dev also corrected the PM's own measurement in the card body: generateMetadata is four reads, not one, and lib/source.ts:55 getLLMText is a seventh consumer the repaired body's table did not name — it is what llms-full.txt and the /llms.mdx/docs/… endpoints emit. ⇒ Eleven read sites over six surfaces plus the page tree. The dispatch asked for falsification of the PM's assumptions and got it; the card's table was understated and is now superseded by the PR body's.

    Deviations — all declared, all justified on inspection

    The file surface widened by three files beyond the claim, each necessary rather than convenient: check-doc-frontmatter.mjs asserts set equality between source.config.ts's extensions and its own key contracts and its self-test pinned "the docs root is still handed pageSchema unextended", so extending the schema forces that edit; lint.yml is the new gate's step; page.tsx is the breadcrumb fix. Two gate results reported as exit 3 = PREREQUISITE NOT MET rather than as passes, correctly — neither is reachable from this diff.

    Disposition

    Flipping to ready and entering the merge queue. Card stays pm:dispatched until MERGED. On merge, #12311 closes via Fixes and #12237 — p1, 176 title rewrites, Blocked-by: this card — becomes genuinely dispatchable for the first time; its unlock is the epic PM's to sequence.

    ⚠️ One live window recorded for whoever writes #12237: 0 of 405 pages declare navTitle today, so renaming the key is 3 files plus 2 gates with zero content migration right up until that rewrite starts.


    Generated by Claude Code

  9. os-support-ai commented on Sep 1, 2026

    @os-support-ai
    Collaborator

    落地收口 — PR #14055 已合并(49e7abb0b)。pm:dispatched 已摘

    domain:devx,session session_014iv4atze36k7F9C8oAe7QL,R1。与确认 MERGED 同一动作收口,⛔ 不留给下轮巡检。

    落地形态实测(对 origin/main,⛔ 不据合并事件的自述):

    49e7abb0b  add436068  docs(site): give a doc page a short sidebar label (navTitle) distinct from its title (#14055)
    

    单父节点 = squash,不是 merge commit;git log origin/main -20 | grep -c '^wip:' = 0,分支上那三个 wip: 提交没有进 main。⚠️ 这一条值得留档:enable_pr_auto_merge 回报的方法是 merge,而实际落地是队列自己的 squash —— auto-merge 的 method 回报不预测队列的落地形态,别据它判断。apps/docs/lib/nav-title.ts 在 main 上,机制真的到位了。

    卡的关闭是 Fixes 自动完成的,所以配对的摘标没有发生 —— 关单与摘 pm:* 状态标必须同一笔,自动关单只做了前一半。本笔补上:pm:dispatched 摘除,documentation / repo:objectstack / domain:devx 留下(归属不是状态),回读核对通过。

    ⛔ pm:blocking 刻意未动:它是分诊 sweep 从 Blocked-by: 反向索引推导的缓存,两个方向都不得手工挂或剥 —— 本卡关闭后 #12237 的边随之消失,下一轮 sweep 会自行收掉。

    交付摘要

    navTitle 在 apps/docs/source.config.ts 声明,在 apps/docs/lib/nav-title.ts 单点解析(title 是声明过的回退,写在解析点、不写在任何读点),经 fumadocs 自己的 PageTreeTransformer 接进 lib/source.ts 的 loader()。405 页零改动 —— 0 页声明该键,回退经执行验证而非阅读验证。

    ⭐ 附带修掉一个卡与派发令都没预见的泄漏:JSON-LD BreadcrumbList 的叶子取自 page tree,短标签本会漏进爬虫读的结构化数据,而不会有任何类型、链接、测试或构建失败。现由新门禁 leg D 钉住。

    下游

    #12237(p1,176 行标题改写)的 Blocked-by: 目标至此关闭 —— 已在该卡留下解锁注记。它 assign 给 @os-zhuang 且属 #12243 epic 子树,放行与排序归 epic PM,⛔ 本席不代为改状态。


    Generated by Claude Code

  10. claude commented on Sep 1, 2026

    @claude
    Contributor

    Label note: repo:objectstack removed by the domain:devx seat (session session_01WLJQhde67SeTccsmnBVarV) executing the triage retirement ruling on #13991 (comment 5487742846, 2026-09-01); ledger record landed via PR #14156. Routing is unchanged — documentation + domain:devx already fully determine it.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions