Skip to content

docs content: meta descriptions too thin to serve as snippets — median 46 chars, 228 of 403 under 70 #12238

Description

@os-zhuang

One-liner

Every page has a description (good), but the median is 46 characters and 228 of 403 are under 70 — far below the ~155 characters a result gets. Google discards a description that thin and writes its own snippet from body text, so the one line we control is wasted. At the other end, 42 pages run past 160 and get cut mid-sentence.

Measured

description length: n=403  min=20  median=46  max=381   <70 chars: 228   >160 chars: 42

Expected

  • Rewrite to 120–155 characters: what the page lets you do, in the words a reader would search, ending on a reason to click.
  • Trim the 42 over-long ones to fit rather than letting the SERP truncate them.
  • Add a repo gate that fails a doc page whose description is outside 70–160 characters, so the fix does not decay.

⚠️ Same maintainer-voice caveat as the page-title card: ship the rule and the table in the PR body, leave it for maintainer review, do not self-merge.

Acceptance

  • every content/docs/** description is 70–160 characters
  • a check:* gate enforces the range and runs in Lint & Repo Gates
  • descriptions read as sentences, not as truncated titles

Source

Found in an SEO review of the docs site (apps/docs) run on 2026-08-25, measured against the local dev server and against production. The canonical origin is https://objectstack.ai — maintainer ruling recorded in #10659:

这个仓的文档站规范 URL 是 https://objectstack.ai

Activity

  1. os-zhuang commented on Aug 25, 2026

    @os-zhuang
    ContributorAuthor

    Held — hard serial on content/docs/**, behind #12236 and then #12237. Known trap: the description rewrite and the title rewrite touch the same frontmatter block of the same files, so they cannot run in parallel with each other either; and the length gate this card adds and the one #12237 may add should be one script, not two.

  2. claude commented on Aug 31, 2026

    @claude
    Contributor

    pm:queue → pm:blocked — the hold was real, documented, and invisible to every query

    domain:devx PM seat (#6023), R33. This card came up as the only remaining dispatch candidate in the lane, so this seat measured its fence before taking it rather than either obeying or ignoring it. The measurement said do not take it, and then said something more useful: the label was lying.

    What the hold actually is

    os-zhuang recorded it on 2026-08-25, and it is not a vague fence:

    Held — hard serial on content/docs/**, behind #12236 and then #12237. Known trap: the description rewrite and the title rewrite touch the same frontmatter block of the same files, so they cannot run in parallel with each other either.

    ⇒ a named predecessor chain and a named technical reason. Measured today:

    card state
    #12236 (double <h1>) closed / completed ✅
    #12237 (page titles) open, pm:blocked ⇐ this card's immediate predecessor, has not run
    #12238 (this card) open, pm:queue — i.e. advertising itself as dispatchable

    ⇒ dispatching this now would run the description rewrite before the title rewrite, into the same frontmatter block of the same files — the exact collision the hold names.

    ⛔ So the label is corrected, not the plan

    Blocked-by: #12237
    Unlock-action: re-check #12237
    

    ⚠️ A hold recorded only in prose is invisible to every mechanism that matters. Candidate selection, the unlock sweep and the ageing alarms all read labels and Blocked-by: lines — none of them reads a sentence in a comment. For six days this card has read "free to dispatch" to every query while carrying an explicit serial hold, and it took being the last candidate in the lane for anyone to open it.

    ⇒ ⭐ This is the same residue class this lane keeps filing (#13526, #13561) in its third shape: not a stale pm:dispatched, not an assignee with no claim, but a real constraint that was written down in the one place the state machine cannot see. Putting it in the vocabulary means it now inherits the existing unlock scan and ageing machinery for free — ⛔ no new label, no new sweep.

    ⛔ Nothing about the card's substance, grade, priority, size or ownership is touched. domain:devx, priority:p1, size/l, repo:objectstack all stand. The hold is os-zhuang's and is not overridden — it is being made machine-readable.

    ⚠️ Consequence for this lane, stated plainly: with this card correctly out of the queue, domain:devx has zero dispatchable cards and runs below its standing concurrency of 4. That is a measured fact with its exclusions shown, ⛔ not an assertion — and a fence honoured is a legitimate reason to run short, where forcing a fourth by crossing one is not.


    Generated by Claude Code

  3. 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

  4. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Maintainer rulings recorded: scope, rewrite volume, no new gate · 2026-09-27T14:48Z

    domain:devx seat 2 (#20163), session_018mA64scZ8fmpiPkrHVAwXj. Three questions were put to the maintainer in this session's chat, with the seat's recommendation marked on each. The answers are verbatim option picks. ⛔ Do not re-open them at dispatch.

    Readings they rest on (origin/main 0d3ec471, measured by the seat; the dev re-derives them):

    group pages median description length under 70 70–160 over 160
    authored (content/docs/** minus references/**, releases/**) 181 117 15 129 37
    generated references/** 211 28 210 1 0
    releases/** (release-owned, ⛔ out) 14 162.5 4 3 7

    The card body's "median 46, 228 of 403 under 70" was the mix of the two populations. Authored pages have since improved; the generated half is still the bulk.

    1. Scope: one card does both halves. The maintainer picked 「一张卡全做」 over the seat's recommended split (the docs content: page titles carry no search intent — median 14 characters, 325 of 403 under 20 #12237 / docs(spec): carry the page-title rule into the docs GENERATOR — 225 of 405 pages are emitted, so a hand edit is reverted (split (b) of #12237) #15403 shape). So this card's PR carries the authored pages AND the docs generator's description output for references/**. The generator lives under packages/spec/scripts/ (domain:spec), which makes this a cross-lane card by the maintainer's order. The claim will declare both halves' file surface, and the spec seats get a notice.
    2. Authored rewrite volume: only the out-of-range pages. The maintainer picked 「只改超范围的 52 页」 (recommended). Only the ~52 authored pages outside 70–160 characters are rewritten (15 too short, 37 too long). The 129 in-range pages are ⛔ not touched.
    3. No new gate. The maintainer picked 「不加门禁」 (recommended). The card's acceptance box "a check:* gate enforces the range" is dropped by this ruling. The fix is content only.

    The card body's maintainer-voice caveat still stands: the PR carries the rule and the full before/after table, and it stays draft for the maintainer's review.

    Dispatch waits only on this card's Blocked-by: #12237. PR #20170 is in the merge queue now. When it lands, this card is released and dispatched in the same round.


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Release: the pm:blocked hold (Blocked-by: #12237, recorded in 5473104258) is discharged. #12237 closed completed at 15:13Z when PR #20170 squashed as 5983d112. The unlock was re-derived before release: no other blocker is named on this card. The maintainer's rulings 5856895911 settle scope, rewrite volume and the gate. Destination: the Claim: below.

    Claim: PM loop round 2 (seat domain:devx#2)
    Session: session_018mA64scZ8fmpiPkrHVAwXj
    Account: os-litant
    Branch: claude/issue-12238-description-rule
    Worktree: objectstack-issue-12238 (in the dev's cloud container)
    Domain: domain:devx (cross-lane by the maintainer's ruling 「一张卡全做」: the generator half lives in domain:spec's packages/spec/scripts/)
    Seat: domain:devx#2
    File surface:


    Generated by Claude Code

  6. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 12238,
      "status": "done",
      "branch": "claude/issue-12238-description-rule",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20258",
      "session": "session_01RCEEP3Z95jrpXCeF3it3Y2",
      "premise_still_valid": true,
      "summary": "Both halves in one draft PR, per ruling 「一张卡全做」. (1) The authored half rewrites the 52 descriptions outside 70-160 (15 under, 37 over; counts re-derived on base e0f17a3 and matching the seat's reading) to 120-155 characters. Only the description line changes; title and navTitle lines are untouched. (2) The generator half: build-docs.ts now takes each module page's description from the module doc block that findModuleDocBlock already selects, via the new scripts/lib/page-description.ts (18-case test). A thin lead is completed with the page's schema names, a module with no doc block falls back to a schema-name sentence, category overviews get an in-range phrase, and the value is emitted JSON-double-quoted. There is no src edit and no new gate. Result: all 392 non-release pages are in 70-160 with 0 exceptions (authored median 117 → 129; references median 28 → 138, 210 under 70 → 0). Module-page tally: 115 from the doc block, 19 doc block + schema names, 62 on the schema-name fallback (no module doc block), plus 14 category indexes. main moved during the work and was merged via os-regen-merge.sh. builtin-node-config.mdx changed on both sides and was regenerated in its own commit (4b7d63cc). PR assignee os-litant; card assignee not written.",
      "tests": "On HEAD 4b7d63cc, after a real `pnpm --filter @objectstack/spec build` (os-verify-lock VERDICT command-exit 0): `check:generated` exit 0 with every gate ✓, `check:docs` reporting '226 generated files in sync'. `dispatch-gates --commands` derived 94 families; `--ran` with recorded exit codes gave '94 derived, 92 run, 2 NOT-MEASURED, 0 UNRUN' (exit 0), and all 92 exit 0. `check:pm-dispatch-gates` (a 711s self-test, 1925 cases) was run once on pre-merge head 652b36d; the other 93 families were re-run on 4b7d63cc. NOT MEASURED: check:dual-build-cjs-loads and check:type-check-debt --re-measure, both exit 3 PREREQUISITE NOT MET because they need the full packages/* build closure; CI builds that. `pnpm --filter @objectstack/spec typecheck` exit 0. vitest: page-description.test.ts 18/18; local project 3 files / 68 tests; repo project (file-description, references-banner, category-title, root-index) 4 files / 146 tests, all pass. Ad hoc: js-yaml parse of all 392 frontmatters gave 0 errors and 0 descriptions outside 70-160. No ablation: no gate was added.",
      "gates": "94 derived / 92 run exit 0 / 2 NOT-MEASURED (exit 3: check:dual-build-cjs-loads, check:type-check-debt) / 0 unrun; check:generated green on 4b7d63cc after a real spec build",
      "line_budget": "265 files, +801 / -266 = 1,067 changed lines vs origin/main, generated files included — under 5,000",
      "files_changed": "52 authored content/docs/**/*.mdx (description line only); 210 content/docs/references/**/*.mdx (regenerated, description line only); packages/spec/scripts/build-docs.ts; packages/spec/scripts/lib/page-description.ts (new); packages/spec/scripts/page-description.test.ts (new)",
      "deviations": [
        "The file surface named build-docs.ts 'description emission and its tests'. The emission rule lives in a NEW module, packages/spec/scripts/lib/page-description.ts, beside build-docs.ts. Reason: build-docs.ts is a side-effecting top-level script and cannot be unit-tested; file-description/format-type/schema-section were extracted the same way. The build-docs.ts hunks are listed in the PR body for #15403.",
        "check:pm-dispatch-gates: its 711s self-test verdict was taken on pre-merge head 652b36d and not re-run on 4b7d63cc. It is checker-health only and the merge touched none of its inputs."
      ],
      "changeset": "skip-changeset (measured): packages/spec files[] excludes scripts/; modulePageDescription/categoryIndexDescription have 0 hits in every shipped path after a build, while the positive control ObjectSchema has 56 hits in dist; content/docs belongs to no package",
      "mcp_calls": "3 — mcp__github__issue_read (get), mcp__github__issue_read (get_comments), mcp__github__list_pull_requests; all reads, no write tool",
      "api_writes": "3 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches) carrying 4 ops: pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft forced) · labels_add skip-changeset (POST /issues/20258/labels) + assign os-litant (POST /issues/20258/assignees) · comment (POST /issues/12238/comments, this report). Plus git push (not REST)",
      "open_questions": [
        { "question": "Two generated pages (references/data/validation, references/marketplace/package) end on a word-boundary ellipsis. Their first doc-block sentence is over 160 characters and has no clause boundary that keeps 70 or more. Acceptable?", "options": ["A keep the ellipsis (honest cut, in range)", "B fall back to the schema-name sentence for any over-long lead with no clause boundary"], "recommendation": "A: the page's own words beat a template, and the cut is visible rather than silent. B is a one-line change in shorten() if the maintainer's voice review prefers it." },
        { "question": "Generated descriptions are written double-quoted (JSON.stringify), while authored pages mostly use plain scalars. Should the generator quote only when needed?", "options": ["A always quote (deterministic, safe for ': ' and quotes in doc-block prose)", "B quote only when the YAML needs it"], "recommendation": "A: every value comes from free prose, and one rule is easier to keep correct than a YAML-special-character predicate." }
      ],
      "out_of_scope_findings": [
        "carrier: none (承接者:无) · 62 packages/spec/src modules have no module-level doc block, so their reference pages use the schema-name fallback (e.g. data/object, api/contract, kernel/plugin). Writing those docblocks is a src edit on the clause-② path limb. Noted in the PR's Acceptance notes and not filed: it is a polish item, not a defect, contract violation or authoring trap.",
        "carrier: none (承接者:无) · some module doc-block leads read as internal notes rather than reader copy (e.g. kernel/metadata-protection 'Phase 1 introduces the item-level lock …'). The generator reproduces the source faithfully. Noted in the Acceptance notes and not filed."
      ]
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    PM review — PR #20258: ACCEPT (head 4b7d63cc) · 2026-09-27T16:51Z

    domain:devx seat 2, session_018mA64scZ8fmpiPkrHVAwXj, reviewer of record. Report 5857657129. Every reading below was taken on GitHub or the tree, not from the report.

    check reading
    Shape Draft, base main, internal head. The first body line is Fixes #12238, and it is the only closing keyword. Assignee os-litant.
    Scope 265 files vs merge base e4621867: 52 authored content/docs/**/*.mdx, 210 content/docs/references/** (regenerated), and 3 under packages/spec/scripts/. 0 under packages/spec/src/**, so the clause-② path limb is not hit. 0 under releases/**.
    Authored diff, per file Only the description: line changed; 0 body and 0 other-frontmatter changes, so title: / navTitle: are untouched. Every old value was outside 70–160. Every new value is 138–155 (all 52 inside the card's 120–155).
    Generated diff, per file 210 pages, description: line only, 0 body changes. New values are 70–160, median 139.
    Generator build-docs.ts swaps its two template emissions (:479 module pages, :912 category indexes) for lib/page-description.ts, which reads the doc block findModuleDocBlock already selects. The output is deterministic: no clock, randomness or environment input. It adds a run-summary tally and has an 18-case test.
    Deviation The rule lives in a new module beside build-docs.ts (scripts/lib/page-description.ts + test) because the top-level script cannot be unit-tested. It follows the existing lib/ extraction pattern and stays in the same directory and concern, so it is accepted.
    Changeset skip-changeset is correct: packages/spec files[] does not ship scripts/, and the docs are not published.
    CI on 4b7d63cc 39 runs: 33 success, 6 skipped, 0 other.
    Governed / size 0 of 265 paths on the register. 1,067 changed lines, generated files included, under 5,000.
    Report mcp_calls 3, all reads, 0 write tools. Writes went only through the relay.
    • open_questions[1] (always quote generated values) → A. PM answer: one deterministic rule beats a YAML-special-character predicate over free prose. No change.
    • open_questions[0] (2 generated pages end in an ellipsis: references/data/validation, references/marketplace/package) is copy, and the card says descriptions "read as sentences". It goes to the maintainer with the voice review, on PR docs(content,spec): search-ready page descriptions — 52 authored rewrites and derived reference descriptions #20258.
    • Out-of-scope findings: (a) 62 modules have no module doc block and use the schema-name fallback; (b) some doc-block leads read as internal notes. Both go to Acceptance notes, already in the PR body. Not filed: polish, not a defect class. Writing those doc blocks would be a src/** edit on the clause-② path.
    • Landing: the card body keeps the PR for the maintainer's voice review. The PR carries needs-user-decision and a quick-read; this card stays pm:dispatched.

    Generated by Claude Code

  8. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    Maintainer approval recorded — PR #20258 · patch round ordered · 2026-09-28T02:13Z

    domain:devx seat 2, session_018mA64scZ8fmpiPkrHVAwXj. Every reading below was taken on GitHub at this act.

    Approval. Review 5333290305 by hotlong, APPROVED, submitted 2026-09-28T02:11:00Z on head 4b7d63cc, body empty, 0 inline comments. hotlong is one of the two authorized approval accounts (os-zhuang, hotlong). No new comment on this card or on the PR since the quick-read 5857844797. This discharges the card body's maintainer voice review: the rule and the before/after tables stand as approved. Under the protocol, a later push does not expire the approval. needs-user-decision leaves the PR in the same act.

    The A/B row. The approval carries no text, so it does not pick A or B. Seat reading:

    • The quick-read said: 「席位建议 B … dev 改完我复核后直接落地」. The approval came after it and raised no objection.
    • The card body's binding acceptance says descriptions "read as sentences, not truncated titles". The two … rows (references/data/validation, references/marketplace/package) do not meet it.
    • The choice changes doc wording only, with no contract change, so it sits in the PM's discretion class. The maintainer holds a veto window, not a permission gate.
    • ⇒ The seat applies B. Veto window: a 「选 A」 on PR docs(content,spec): search-ready page descriptions — 52 authored rewrites and derived reference descriptions #20258 before it merges reverts this row to A, and nothing else changes.

    Why a push is owed anyway (landing section A).

    • main moved 45 commits past the PR's merge base e4621867 (now d498113b).
    • 10 generated content/docs/references/** pages changed on both sides: api/{dispatcher,error-code-ledger,plugin-rest-api,rest-server}, automation/schemaless-node-config, data/{mapping,validation}, security/{permission,rls}, ui/report.
    • Non-test packages/spec/src/** doc sources also changed on main.
    • GitHub reads the merge as clean, but a textual merge can keep stale derived descriptions. So os-regen-merge.sh plus a full regenerate is required before the PR queues.

    Patch round (sent to the dev session session_01RCEEP3Z95jrpXCeF3it3Y2):

    1. Merge origin/main via bash scripts/pm/os-regen-merge.sh, then regenerate the docs. ⛔ No rebase and no force-push.
    2. B: in packages/spec/scripts/lib/page-description.ts, a doc-block lead that cannot fit 70–160 without an ellipsis cut falls back to the schema-name sentence. There is no … output path left. Update the test battery to pin this.
    3. Re-derive the numbers on the merged tree, re-run the gates, and push. Then post one os-dev-report delta on this card and update the PR body's generator section.

    After the delta, the seat re-reviews and then lands. The card stays pm:dispatched.


    Generated by Claude Code

  9. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    Correction to 5862075970 — B and the patch round RETRACTED · the maintainer landed the PR as it stands · 2026-09-28T02:15Z

    domain:devx seat 2, session_018mA64scZ8fmpiPkrHVAwXj.

    What I missed. The PR timeline shows that hotlong did more than approve. hotlong then landed the PR personally, and all of it came BEFORE my record 5862075970:

    • review APPROVED at 2026-09-28T02:11:00Z;
    • ready_for_review at 2026-09-28T02:11:04Z;
    • auto_merge_enabled at 2026-09-28T02:11:18Z;
    • added_to_merge_queue at 2026-09-28T02:12:19Z.

    The events reached this seat late. I wrote the record after reading the reviews and comments, but without re-reading the PR object or its timeline. That was the lapse.

    Reading, corrected. The maintainer's own landing act on head 4b7d63cc settles the A/B row as A: the two … rows stay as they are. It also answers the regen question for this landing. The merge queue's merge_group run is the drift gate on the merged tree. So:

    • B is retracted, together with the whole patch round in 5862075970.
    • The dev session session_01RCEEP3Z95jrpXCeF3it3Y2 was interrupted at 2026-09-28T02:14:54Z. At that moment the branch head was still 4b7d63cc (git ls-remote), so nothing was pushed and the queue entry is intact.
    • My needs-user-decision removal from the PR stands, because the approval discharged it.

    Now. The PR is in the merge queue (gh-readonly-queue/main/pr-20258-8af914a3…), with auto-merge by hotlong. The seat follows it to MERGED and then closes out this card.

    • If merge_group is red (for example generated drift from the 45 main commits), the seat handles the kick-out under landing section A: os-regen-merge.sh + regenerate, with the approved content unchanged, and A kept.
    • If the maintainer wants B after all, it becomes a follow-up change and does not touch this landing.

    Generated by Claude Code

  10. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    Landed — PR #20258 merged as 9daced0b · 2026-09-28T02:43Z

    domain:devx seat 2, session_018mA64scZ8fmpiPkrHVAwXj. Landing record for claim 5857109111.

    • Merge verified on main: 9daced0b is origin/main's HEAD at this read. It touches 265 files, +801 / −266. hotlong merged it through the merge queue at 2026-09-28T02:41:39Z. Spot reads on origin/main:
      • kernel/architecture.mdx and permissions/authorization.mdx carry the approved rewritten description: rows (143 and 156 characters).
      • references/data/mapping.mdx carries a derived sentence in place of the old template.
      • build-docs.ts emits modulePageDescription(…) (:489) and categoryIndexDescription(…) (:931).
      • 0 … protocol schemas / Complete reference for all … template descriptions remain under content/docs/references/**.
    • Landing criterion re-checked after the merge (landing section B), because the queue merge also goes through os-regen:
      • The 10 generated pages that main had also changed carry, on main, exactly the PR head's description: lines.
      • The merge_group run on 9daced0b was green: CI, Build Docs, Lint & Repo Gates, Lint & Type Check, Governed Surface Guard, Spec Liveness Check, Closing-Target Claim Guard.
    • Path:
      • ACCEPT 5857839052.
      • Maintainer voice review: hotlong APPROVED (review 5333290305), then marked the PR ready, enabled auto-merge and enqueued it at 02:11–02:12Z, keeping the 2 ellipsis rows as they are (A).
      • The seat's B reading 5862075970 was retracted in 5862098826 before any push.
      • Maintainer rulings 5856895911: 「一张卡全做」 · 「只改超范围的 52 页」 · 「不加门禁」.
    • State: closed completed by Fixes #12238. pm:dispatched is removed in this act; domain:devx / priority:p1 stay.
    • Downstream:
    • The dev session session_01RCEEP3Z95jrpXCeF3it3Y2 is archived after this record.

    Generated by Claude Code

  11. added a commit that references this issue on Sep 28, 2026
    9daced0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions