Repository navigation
docs site: homepage <title> and description are not keyword-shaped (description is 615 characters) #12239
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentationpriority:p1High: required for production / M2High: required for production / M2
on Aug 25, 2026 Claim: PM loop round 2 (epic PM for #12243)
Session:session_f9f0958b-ab68-46cc-801c-216aa7ee2107
Branch:claude/issue-12239-homepage-keywords
Worktree:objectstack-issue-12239
Domain:domain:devx
File surface:apps/docs/app/[lang]/page.tsx— the exportedmetadataobject, and optionally the uppercase eyebrow above the h1. Nothing else. (stop on breach; explain in the report)
Container & model: S,mode:subagent,model: opus—node scripts/pm/dispatch-gates.mjs --tieron this path derives no mandate; the tier is for judgement about the maintainer's voice, not for volume
Clause-②: no
Serial constraints cleared: this file was released by PR #12218 merging at 14:56:33Z (eeec62a98). No in-flight PR touches it — #12253 adds three new files, #12258 toucheslib/i18n.tsand[lang]/layout.tsx, #12262 touchescontent/docs/**. #12242 is queued BEHIND this card on the same file.Dev claim (os-dev subagent under the epic PM's round-2 dispatch for #12243)
Session:
session_f9f0958b-ab68-46cc-801c-216aa7ee2107(dev subagent runs inside the epic PM session; same id, distinct seat)
Branch:claude/issue-12239-homepage-keywords(pushed empty at claim time — write routing verified)
Worktree:/Users/zhuangjianguo/Documents/GitHub/objectstack-issue-12239
Base:adea66d6694b7124d56a048e0a8f0c605498c3eaFile surface accepted as dispatched:
apps/docs/app/[lang]/page.tsxonly — the exportedmetadataobject, plus the uppercase eyebrow above the h1 if a keyword genuinely belongs there. The<h1>stays byte-identical. No changeset (docs-site only);skip-changesetlabel on the PR.Read before starting: this issue body + comments, epic #12243 body + all four comments (including the two recorded corrections), and finding #12255 (
app/page.tsxis a shadowed route — the served homepage isapp/[lang]/page.tsx). Verification will be against a rendered response from a running server, not against the file.os-dev-report
{ "issue": 12239, "status": "done", "branch": "claude/issue-12239-homepage-keywords", "pr": "https://github.com/objectstack-ai/objectstack/pull/12284", "premise_still_valid": true, "summary": "Replaced the exported metadata title and description in apps/docs/app/[lang]/page.tsx; +2/-4, nothing else in the file. Title: dropped the `absolute` wrapper so the root layout app/layout.tsx already-existing template `%s | ObjectStack` supplies the brand, and led with the category — the served page now emits \"Metadata framework for AI-written apps | ObjectStack\" (52 chars) in place of \"ObjectStack — Apps small enough for AI to hold whole.\" (53 chars, no searchable term). Description: sentence one of the existing copy kept VERBATIM and the rest cut — a pure cut with zero new words, 152 chars, replacing 614. PM assumption 3 is CONFIRMED: the honest 120-155 char description was already in the file and needed only cutting; the title, by contrast, could not be cut from anything and was assembled, but from the repo own vocabulary (\"AI-written\" is lifted verbatim from app/layout.tsx site-wide description; the card suggested \"AI-built\", which appears 0 times in the tree, so it would have introduced a fourth spelling alongside AI-written/AI-authored/AI-generated). CORRECTION to the card and to PM assumption 1: the description is 614 characters, not 615 — 615 counts the backslash of the escaped apostrophe in the source literal (agent\\ s); the string reaching the meta tag is 614, confirmed on rendered HTML as well as in source. Also verified: PR #12218 (eeec62a98) did NOT touch this metadata block, it only reflowed the hero paragraph, so the card measured the same bytes that are on main now. The h1 was not touched and is byte-identical across the before/after renders (346 bytes of markup both, one h1 on the page); the uppercase eyebrow was considered and deliberately left alone — it already carries the open-source signal (\"Open protocol & runtime · Apache-2.0\") and keeping the diff inside the metadata object keeps the queued #12242 rebase trivial. Neither .github/workflows/lint.yml nor package.json is touched. Out-of-scope sweep, recorded because it came back empty rather than unexamined: the one candidate was app/layout.tsx site-wide fallback description at 161 chars, but it is unreachable — every one of the 403 content/docs pages carries a frontmatter description (measured here: 0 missing), and the docs route passes page.data.description straight through, so no HTML route falls back to it. That matches what #12238 already asserts. Filing it would have been a fabricated lead, so nothing was filed.", "tests": "RENDERED VERIFICATION (not file-based — finding #12255 warns app/page.tsx is shadowed). Committed the fix first so both legs are recoverable from git, then: (1) `git checkout HEAD~1 -- apps/docs/app/[lang]/page.tsx`, revert proved on disk by blob hash 243e21aa1cf1711e7365ae9482585191afcc15f8 == base blob, plus grep counts (absolute: 1, \"Metadata framework for AI-written\" 0); (2) next dev on private port 38239, curl / (proxy rewrites to /en) => HTTP 200 text/html, 81950 bytes; (3) `git checkout HEAD -- apps/docs/app/[lang]/page.tsx`, restore proved by `git diff HEAD` EMPTY and blob 7f20305e5c7e8e04d7e8699bb0b3563bc061f200 == HEAD blob; (4) same capture on port 38240 => HTTP 200, 81003 bytes. Both servers torn down by the PID started (never by name); `lsof -ti tcp:38239` and `tcp:38240` => 0 listeners each. MEASURED FROM THE EMITTED HTML — title element: BEFORE \"ObjectStack — Apps small enough for AI to hold whole.\" = 53 chars; AFTER \"Metadata framework for AI-written apps | ObjectStack\" = 52 chars. meta name=description content: BEFORE 614 chars (\"ObjectStack turns the whole app ... enforcing permissions and audit on every call.\"); AFTER 152 chars (\"ObjectStack turns the whole app — data model, UI, workflows, permissions — into typed metadata: a complete CRM in under 150k tokens, one context window.\"). h1 markup extracted from both renders and byte-compared: IDENTICAL, 346 bytes both, h1 count 1 both. Note the root template DID apply as predicted — verified on the wire, not assumed; if it ever stopped applying the title degrades to 38 chars, still inside every bound. GATES, all on pushed head 1e45188fa, tree clean, each exit code captured BEFORE any pipe (redirect-then-capture), each quoted from the gate own verdict line: [0] pnpm --filter @objectstack/docs typecheck => \"Types generated successfully\"; [0] pnpm check:published-files => \"69 publishable package(s) of 78 workspace member(s) ... admits no test\"; [0] pnpm check:test-source-alias => \"check-test-source-alias OK — 72 packages with tests scanned\"; [0] pnpm check:type-source-resolution => \"check-type-source-resolution OK — 93 tsc program(s) across 77 packages\"; [0] pnpm check:nul-bytes => \"OK (scanned 6783 text file(s) ... no raw ASCII control bytes)\"; [0] pnpm lint => FULL REPO (eslint . --no-inline-config), 26s, clean — no narrowing to declare for lint. Gate list re-derived from my actual diff, not from the dispatch: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` at commit 1e45188fa reported 1 path vs merge base adea66d66 and derived exactly check:published-files, check:test-source-alias, check:type-source-resolution (all run above); it also confirmed no model-tier mandate on this path. Dependency closure built first: `pnpm --filter @objectstack/docs^... build` => command-exit 0, \"check-dts-emitted: @objectstack/spec - 34/34 declared declaration file(s) present\". NOT-MEASURED CHECK: `tsc --noEmit --listFiles` in apps/docs lists apps/docs/app/[lang]/page.tsx (1 of 1236 files in the program), so \"typecheck clean\" provably covers the edited file rather than excluding it. DECLARED NARROWING — verification ran UNLOCKED: scripts/pm/os-verify-lock.sh could not take the shared verify lock on this host (no usable flock; the lock is Linux-only and this is macOS). Every heavy command above was routed through that entry point, ran in its DECLARED UNLOCKED MODE, and ended with \"VERDICT command-exit 0 · UNLOCKED (declared)\". Nothing was serialized against sibling agents. The official disclosure wording is pasted in the PR body. NO CHANGESET: docs-site copy only; skip-changeset applied via the additive POST endpoint and CONFIRMED BY READ-BACK after the bots settled — labels now [\"size/xs\",\"skip-changeset\"], so the size-labeler PUT did not wipe it. PR body read back from the API and diffed against the local file: identical except one trailing newline the API appends — no sanitizer mutation, and the session-URL footer survived.", "open_questions": [], "out_of_scope_findings": [] }ACCEPT — PR #12284. One file, +2/-4, exactly the declared surface.
- The description is a pure cut: sentence one of the existing copy kept verbatim, the rest removed. 614 → 152 characters with zero new words. That is the strongest possible answer to a card about maintainer voice — nothing was rewritten, only stopped.
- The title dropped the
absolutewrapper so the root layout's existing%s | ObjectStacktemplate supplies the brand: the served page emitsMetadata framework for AI-written apps | ObjectStack, 52 characters. Measured on the wire, not assumed, and the fallback was reasoned about too (if the template ever stopped applying, the title degrades to 38 characters — still inside every bound). AI-writtenwas chosen over the card's suggestedAI-builtbecauseAI-writtenalready appears inapp/layout.tsx's site-wide description andAI-builtappears zero times in the tree. My card would have introduced a fourth spelling alongside AI-written / AI-authored / AI-generated. Good catch, and the kind of correction I want.<h1>byte-identical across before/after renders (346 bytes both, oneh1on the page) — verified against rendered HTML rather than against the file, which is the right method given [finding] docs site: apps/docs/app/page.tsx is a shadowed route — the proxy rewrites / to /en, so it never runs #12255.- Third correction to my numbers this epic: the description is 614 characters, not 615. My count included the backslash of the escaped apostrophe in the source literal. Same class of error as the fence-blind H1 grep — I have been measuring the source text where the card's claim is about the rendered text.
⚠️ One editorial call I am flagging rather than burying, because it is the maintainer's to veto: the new title calls ObjectStack a "metadata framework", while the repo's own vocabulary elsewhere is "the open target format and runtime" and "open protocol & runtime". The dev traded internal vocabulary for search volume — "framework" is the term people actually type — and for this card that is the defensible choice. But it is a new self-description of the product on the site's single most visible string, so: it ships unless the maintainer says otherwise, and reverting is one line.Correction inherited from #12237's dev, recorded on the card that introduced the claim.
I wrote in the ACCEPT above that
AI-built"appears zero times in the tree". That is not right: it appears 35 times across 18 files, underpackages/objectql,packages/metadata-protocolanddocs/adr/**. It is absent fromcontent/docs/**andapps/docs/**— presumably the surface that was actually measured, reported as the whole tree.The decision is unaffected:
AI-writtenis still the right anchor for site copy, because it is what the site itself already says (app/layout.tsx, and now the homepage title landed here). But it stands on taste and consistency, not on the word being absent from the repo, and the next card that inherits this should inherit the corrected version.
One-liner
The homepage
<title>isObjectStack — Apps small enough for AI to hold whole.— a brand slogan with no term anyone searches for — and its meta description is 615 characters, roughly four times what a result shows.Measured
Expected — bounded scope, on purpose
<h1>. The slogan stays exactly as it is; it is the maintainer's line and it works as a hero.metadata.titleandmetadata.descriptioninapps/docs/app/[lang]/page.tsx, plus optionally the small uppercase eyebrow above the h1.title: lead with what it is, keep the slogan second if it fits in ~60 characters — e.g.Open-source metadata framework for AI-built business apps.description: 120–155 characters, drawn from the existing copy — the first paragraph already says it ("turns the whole app … into typed metadata"), it just needs cutting.Acceptance
<title>≤ 60 characters and names the category, not only the brand<h1>is byte-identical to what it is todaySource
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 ishttps://objectstack.ai— maintainer ruling recorded in #10659: