Skip to content

docs: a short /docs description, legal-page metadata, share cards that fit, no MDX comments in llms bodies - #304

Merged
hotlong merged 3 commits into
mainfrom
claude/pm-dispatch-objectos-ju9td1
Oct 6, 2026
Merged

hotlong merged 3 commits into
mainfrom
claude/pm-dispatch-objectos-ju9td1

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #299

Page titles belong to #167, which this PR does not address. Head claude/pm-dispatch-objectos-ju9td1 @ d6431d6, three commits on main @ 53ba2f3 (rebased from 6b0e9a5 through 3b17984). 19 files, +284 / -57.

What this does

1. Share cards: the description is clamped to the card's room

app/og/docs/[...slug]/route.tsx passed the raw description to fumadocs-ui/og. That component puts the title, the description and the brand line in a fixed 1200x630 flex column and clips none of them, so text that does not fit is drawn over the next element. clampDescription now cuts the description at the last word boundary inside a budget and adds an ellipsis.

  • Budget: 120 width units under a one-line title, which is three description lines, and one line (40 units) fewer for each extra title line. Titles over 26 units wrap at 82px.
    • The title-aware part is needed because the card's own example, build/ai-skills, has a two-line title ("IDE Skills (Claude Code / Cursor / Copilot)"). Three description lines under it still overlap the divider.
  • CJK: a Han, kana or Hangul character, or a full-width form, counts as two units, so a CJK card is cut at the same width as an English one. Scripts without spaces are cut between characters, which is where they wrap anyway.
  • Parentheses: the cut never ends inside a parenthesis it left open.
  • Reach: 58 of the 309 prerendered cards are clamped, counted by running the route's own clamp over every (title, description) pair.

Before and after, 11 cards were looked at; the PNGs are attached:

Card Before After
/og/docs/en/image.png (index) the 511-character description runs over the title, the divider and the brand line 3 lines: "ObjectOS is the commercial runtime environment built on ObjectStack, hosted (ObjectOS Cloud) or self-managed…"
en/build/ai-skills (2-line title) description drawn over the title's second line and the brand line 2 lines, clear of both
en/extend-existing-systems, en/reference/environment-variables, en/deploy/kubernetes 4-5 lines, over the divider 3 lines, ending "…and…", "…fail at…", "…probes…"
zh-Hans, ja, ko, es build/ai-skills over the divider and the brand line (ko) 2 lines each, clear
en/operate/backup, en/build/interface/dashboards fit unchanged (not clamped)

2. /docs meta description: a short form, with the full paragraph in the page body

  • lib/positioning.ts gains POSITIONING_SHORT (153 characters). It leads with "ObjectOS", because a search snippet of the 511-character paragraph never got that far:

    ObjectOS is the commercial runtime environment built on ObjectStack, hosted (ObjectOS Cloud) or self-managed (ObjectOS Enterprise). You own the ontology.

  • content/docs/index.mdx: the frontmatter description is that string. The full POSITIONING paragraph is now the page's opening paragraph, byte for byte, wrapped at the file's usual width.
  • The choice for rule (a): both halves. check-positioning.mjs rule (a) compares the frontmatter description to POSITIONING_SHORT, and the opening paragraph (wrapped lines joined) to POSITIONING.
    • The card offered either a short constant or a body comparison. Doing only the first would leave the moved 511-character copy unguarded, and that copy is the one rule (a) exists for. Doing only the second would leave a positioning statement in frontmatter with nothing tying it to positioning.ts.
    • The other two copies (the site-wide meta on _not-found.html and the /llms.txt summary line) still compare to POSITIONING.
    • The gate goes from 189 to 195 lines. composed() became constants(), which returns every literal plus the composed POSITIONING; a 2-line lede() was added; and the copies table carries the constant each copy is checked against.
    • Self-test: the good fixture wraps its paragraph across two lines (0 findings). The bad fixture paraphrases both index copies (4 findings, up from 3).

3. English descriptions: 160 characters or fewer

On main @ 53ba2f3 there are 6 English descriptions over 160 characters besides the index, not the nine the card counted, plus the three generic ones the card names. All nine were rewritten; no locale sibling was touched.

Page Before After
build/ai-skills 186: Install the ObjectStack skills into your coding agent so Claude Code, Cursor, Copilot, Codex and friends know how to author ObjectStack metadata — the ontology ObjectOS runs — correctly. 159: Install the ObjectStack skills so Claude Code, Cursor, Copilot, Codex and other coding agents author correct ObjectStack metadata — the ontology ObjectOS runs.
extend-existing-systems 180: Federate a database you already run into ObjectOS as an external datasource — read-only by default, and early — and model the tables you care about as objects, without a migration. 158: Federate a database you already run into ObjectOS as an external datasource — read-only by default, and early — and model its tables as objects, no migration.
reference/environment-variables 176: The environment contract of a self-hosted ObjectOS deployment — what each variable decides, which combinations are refused at startup, and the names that no longer do anything. 156: The environment contract of a self-hosted ObjectOS deployment — what each variable decides, which combinations fail at startup, and which names are retired.
deploy/kubernetes 172: The properties any orchestrator must preserve when running the licensed ObjectOS image — digest pinning, migration ordering, probes, and what multi-replica makes mandatory. 156: What any orchestrator must preserve to run the licensed ObjectOS image — digest pinning, migration ordering, probes, and what multi-replica makes mandatory.
quickstart 168: From zero to a running app on the open-source ObjectStack runtime — install one CLI, run one command. ObjectOS Cloud needs none of it, sign in and build in the browser. 156 (now quoted, it holds a colon): From zero to a running app on the open-source ObjectStack runtime: one CLI, one command. ObjectOS Cloud needs none of it — sign in and build in the browser.
deploy/air-gapped 161: Air-gap is a licence mode, not a firewall setting — the two settings a disconnected deployment must declare, and the combinations the runtime refuses at startup. 149: Air-gap is a licence mode, not a firewall setting — the two settings a disconnected deployment must declare, and the combinations refused at startup.
resources/faq 43, generic: Answers to questions we get asked the most. 153: Common questions about ObjectOS — getting started, databases and multi-tenancy, migrations, permissions, integrations, operations, pricing and licensing.
configure/webhooks 48, generic: Outbound webhook delivery, signing, and retries. 145: Outbound webhooks from ObjectOS via a persistent outbox — at-least-once delivery, HMAC signing, bounded retries, and what a receiver must handle.
configure/permissions/record-access 47, generic: Control which records a user can see or modify. 153: Control which records a user can see or modify once object permissions allow it — sharing-model defaults, tenant isolation, sharing rules, record shares.
index 511: the positioning paragraph 153: POSITIONING_SHORT (section 2)

Result: 0 of 79 English descriptions are over 160 characters (was 7). The FAQ, webhooks and record-access summaries are taken from those pages' own section headings and opening paragraphs.

4. /privacy and /terms: head metadata, and a link that reaches them

  • Metadata: each page gets a generateMetadata through one helper, staticPageMetadata in lib/seo.ts, placed beside localeUrl and languageAlternates. It gives the title, the description, a canonical URL, hreflang over the locales the content record is written in (en, zh-Hans, x-default), and an explicit og:title, plus og:url, og:site_name and og:type. A locale with no entry renders English and names the English URL as canonical, the same rule docs pages follow.
  • Descriptions: each content record gains a description per written locale. The page body text is unchanged. The zh-Hans descriptions sit beside the zh-Hans body that docs(legal): correct the terms and privacy pages to ObjectOS's editions and licence facts (PR 2 of #171) #294 hand-wrote in the same record (see open question 2).

Built heads, before and after:

Page Before After
/privacy title "ObjectOS"; description the 511-character site default; no canonical; no og:title "Privacy Policy | ObjectOS"; 150 chars "What ObjectStack AI LLC collects from its public websites and hosted accounts, and the data inside a self-managed deployment that it does not collect."; canonical /privacy; hreflang en, zh-Hans, x-default; og:title "Privacy Policy"
/terms same as /privacy "Terms of Service | ObjectOS"; 155 chars "How ObjectOS editions are licensed, the Apache-2.0 license of this site's content, the ObjectOS trademark, and responsibility for self-managed deployments."; canonical /terms; og:title "Terms of Service"
/zh-Hans/privacy, /zh-Hans/terms same as /privacy "隐私政策 | ObjectOS" and "服务条款 | ObjectOS"; zh-Hans descriptions; canonical is their own zh-Hans URL
/ja/privacy, /ja/terms (and the other four unwritten locales) English text, no canonical English head; canonical /privacy and /terms; the same hreflang cluster
  • Footer link: apps/docs has no footer component. The docs layout's sidebar footer slot (app/[lang]/docs/layout.tsx) now carries "Privacy" and "Terms" links. That slot sits at the foot of the sidebar on desktop and of the drawer on mobile, so both pages are reachable from every docs page in every locale, and each link stays in the reader's locale.

5. Export: MDX comments no longer ship

  • getLLMText (lib/source.ts) strips {/* … */} from the processed Markdown and keeps any that sit inside a code fence. A whole-line comment goes with its trailing blank lines, so no gap is left.
  • The pin: a new rule, mdx-comment, in check-locale-surface.mjs. It runs over both llms consumers, the llms-full.txt body and all 79 llms.mdx bodies, on the same footing as Nothing asserts /llms-full.txt is free of HTML numeric character references, so #197's fix regresses silently on the next fumadocs bump #282's encoding rules: it checks the outcome on the built bytes.
    • Two red fixtures were added, one per consumer, and the per-consumer coverage check now includes the rule. The green fixture gained a /api/v1/data/* path, which contains /* and is not a comment.
    • The tally table has a new "MDX comments" column, and the verdict line names the rule.
    • The gate goes from 1859 to 1896 lines.
  • llms-full.txt before to after differs in exactly two places: the index's new opening paragraph is added, and the 11-line {/* Naming, decided under #79… */} block is gone.

Verification on d6431d6 (final HEAD, after the rebase onto 53ba2f3)

Build-shaped commands ran through the shared verify lock. The local build adds --env-mode=loose with the proxy CA so that next/og can fetch CJK fonts through this container's TLS proxy. Without it the CJK cards render tofu; otherwise it is CI's NEXT_PRIVATE_STANDALONE=true pnpm turbo run build. 0 font-fetch failures.

  • pnpm turbo run build --force: VERDICT command-exit 0.
  • pnpm turbo run test --force: ✓ 10 self-test(s) passed.
  • pnpm turbo run type-check --continue --force: fumadocs-mdx && next typegen && tsc --noEmit, Tasks: 1 successful; the lock wrapper reported VERDICT command-exit 0.
  • node apps/docs/scripts/gen-zh-hant.mjs --check: ✓ zh-Hant: 61 generated file(s) match the zh-Hans sources byte for byte.
  • node .github/scripts/check-locale-surface.mjs: ✓ … and neither llms consumer carries a numeric character reference, a malformed link target or an MDX comment. Tally: llms-full.txt 1/1 bodies, 691 targets, 0 MDX comments; llms.mdx 79/79 bodies, 691 targets, 0 MDX comments.
  • --self-test: ✓ self-test: 31 case(s) over 15 rule(s) ….
  • node .github/scripts/check-positioning.mjs: ✓ 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.
  • --self-test: ✓ self-test: every rule fails its bad fixture and passes its good one.
  • node .github/scripts/check-search-locales.mjs: ✓ search locales: all 8 locales answer 200 …; self-test green.
  • node .github/scripts/check-translations.mjs: ✓ translations gate passed. The siblings of ai-skills, webhooks and record-access are now stale; that is reported, not blocking, by design.
  • check-translation-output.mjs --self-test: green.
  • Ownership, run with the workflow's argv: ✓ 19 file(s) changed, no translation artifacts touched.; its self-test is green.
  • check-node-floor (gate and self-test) and check-half-states --self-test (1551 cases): green.
  • Worker bundle: opennextjs-cloudflare build --skipNextBuild then wrangler deploy --dry-run. main @ 53ba2f3 is 49098.99 KiB, measured in a throwaway worktree; this branch is 49074.15 KiB (-24.84 KiB). The legal pages now import lib/seo.ts, which imports source, and that added nothing measurable.
  • Control bytes: the C0/DEL scan over the 19 files is clean; the positive control fires.
  • Browser: next start from the final build, Playwright Chromium, 1280x900 and 390x844, 0 console or page errors.
    • On /docs, the description under the H1 is the short form and the first body paragraph is the full positioning.
    • The sidebar's Privacy and Terms links navigate to /privacy ("Privacy Policy | ObjectOS") and /terms ("Terms of Service | ObjectOS").
    • From /zh-Hans/docs/quickstart, Privacy goes to /zh-Hans/privacy (canonical is itself). /ja/terms has the English head with canonical /terms and hreflang en, zh-Hans, x-default.
    • On mobile, the links appear once the drawer opens, and Terms navigates.
    • The server was stopped by its own PID; the port is free afterwards.

Ablation (one-time, nothing left in the tree), run on the pre-rebase commit whose files are identical to this one:

  • Removed only the strip call in getLLMText with ablation-replace.mjs in wrap mode. Anchor count went 1 to 0 and the blob changed.
  • Rebuilt: the built license.body carries the note again (count 1).
  • The gate exits 1 with exactly 2 findings, both mdx-comment: llms-full.txt line 11071 and llms.mdx resources/license line 12. Nothing else fired.
  • The tool's restore leg showed the blob equal to HEAD and git diff HEAD empty. The final build above is from the restored, committed tree.

Open questions for the maintainer

  1. POSITIONING_SHORT is new copy. It is built from the constant's own words (the README's definition, the edition names, and "you own it" from the promise), but it is not a README quote. Edit it in positioning.ts and in the index.mdx frontmatter together; the gate holds the two equal.
  2. The zh-Hans description entries on the legal pages are hand-written. They sit in the same TSX record whose zh-Hans body docs(legal): correct the terms and privacy pages to ObjectOS's editions and licence facts (PR 2 of #171) #294 hand-wrote. The translation pass covers MDX files only, so nothing else would produce them.
  3. The footer links read "Privacy" and "Terms" in every locale. The site configures no UI-string translations; the search box and "On this page" are English in every locale too.

Acceptance notes

  • Premise drift: the card counted "nine more" descriptions over 160 characters. On 53ba2f3 there are six; with the three generic ones, nine were rewritten.
  • Surface beyond the card's list:
  • Not changed: the root layout's site-wide description is still the 511-character POSITIONING, which rule (a) pins. After this PR, only pages with no metadata of their own inherit it: _not-found and the locale-root redirects.
  • Copy Markdown and /docs/*.mdx share getLLMText with llms-full.txt, so the one strip covers all three.

Generated by Claude Code

claude added 3 commits October 6, 2026 09:36
…ds, no MDX comments in llms bodies

- lib/positioning.ts: POSITIONING_SHORT (153 chars) for the /docs meta
  description; the full paragraph moves to index.mdx's opening paragraph.
  check-positioning rule (a) now compares the frontmatter to
  POSITIONING_SHORT and the opening paragraph to POSITIONING.
- og/docs route: the description is clamped to the card's room at a word
  boundary with an ellipsis, 120 width units under a one-line title and one
  line fewer per extra title line; CJK characters count two.
- Nine English descriptions rewritten to 160 characters or fewer (six were
  over, three were generic). No locale sibling touched.
- privacy/terms: generateMetadata via lib/seo.ts staticPageMetadata (title,
  description, canonical, hreflang over the authored locales, og:title);
  the docs sidebar footer links to both.
- getLLMText strips MDX comments outside code fences; check-locale-surface
  gains an mdx-comment rule over both llms consumers, with red fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 6, 2026 09:57
@hotlong
hotlong merged commit 601bb37 into main Oct 6, 2026
4 checks passed
hotlong pushed a commit that referenced this pull request Oct 6, 2026
The legal links #304 added to the docs sidebar footer rendered "Legal",
"Privacy" and "Terms" in English under every locale. They are interface copy,
so they now come from lib/ui-text.ts like the rest of the chrome; the zh-Hant
strings are regenerated from zh-Hans by gen-zh-hant.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants