Repository navigation
docs(content): propose a search-intent title rule, land it on the four pages with no sidebar cost - #12312
Merged
Conversation
The frontmatter title is the SERP <title>, the on-page <h1>, the sidebar label and the llms.txt heading — one string, four consumers. Lengthening it for search shortens nothing else, so this lands the rule only where it costs the navigation nothing: - content/docs/index.mdx, protocol/objectql/index.mdx and protocol/objectui/index.mdx have no sidebar entry of their own — their folder's meta.json title is what the tree shows. - protocol/objectui/record-alert.mdx was 64 characters with the site suffix, already over the 60-character budget; the new title is 52 and its sidebar label gets shorter, not longer. The rule and the full 180-row before/after table are in the PR body for the maintainer to judge. Nothing else is rewritten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
os-zhuang
marked this pull request as ready for review
August 26, 2026 00:08
4 tasks
This was referenced Sep 27, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
…hored pages, short nav labels kept via navTitle (objectstack-ai#20170) Fixes objectstack-ai#12237 Clause-②: no ## Summary Applies the maintainer-approved page-title rule to the authored docs pages that still carried their pre-rule title, and keeps every navigation label exactly as it was through `navTitle`. - **175 approved rows applied verbatim** from the table in PR objectstack-ai#12312, each with a `navTitle:` line holding the page's previous title. - **1 approved row re-worded** because a required gate rejects the approved text (`check:role-word`), and **1 authored page the table never listed** authored per the rule. Both are listed under *Needs maintainer voice review* below. - **One gate premise moved, not loosened:** `check:runtime-services-index` used to hold each `kernel/runtime-services/NAME-service.mdx` to `title: services.NAME`. It now reads that accessor premise from the page's `navTitle`, so the chapter's 8 approved titles land verbatim with `navTitle: services.NAME`. See *The runtime-services gate* below. (PM answer to the round-1 open question, review comment `5852831074` on objectstack-ai#12237.) - 177 docs files, frontmatter only: every one is `+2/-1` (`title:` replaced, `navTitle:` added). No `description:`, no page body, nothing under `references/**` or `releases/**`, nothing in `apps/docs/**` or any `meta.json`. Plus one gate script, `scripts/check-runtime-services-index.mjs` (file surface amended by the seat for this round). ⛔ **Draft, and it stays open for maintainer review — do not self-merge** (card body). The landing path after review is the seat's call. ## The rule ``` PRIMARY-KEYWORD — QUALIFIER ``` - The frontmatter `title:` string is **36–46 characters**, so the rendered title with the 14-character ` | ObjectStack` suffix lands in **50–60**; no rendered title over 60. (PM ruling on the card, comment `5414218147`, Q2: the band is authoritative.) - Separator ` — ` throughout; `ObjectStack` never inside a title, because the suffix carries it; one declared exception, `getting-started/index.mdx` keeps `What is ObjectStack?`. (Maintainer ruling recorded in comment `5419555720`, verbatim: 「其他同意」 — the rule and the full before/after table in PR objectstack-ai#12312 approved as they stand.) - Scope: authored pages only. The generator-emitted pages are objectstack-ai#15403 (`domain:spec`), per the triage split in comment `5541938216`. - Short navigation labels: `navTitle` (landed with PR objectstack-ai#14055). The fallback to `title` lives only in `navLabel()` in `apps/docs/lib/nav-title.ts`. ## What changes on the site, and what does not Measured in `apps/docs` at this head. A page's `title` is read at **12 read points on 5 faces**, plus the site search index. All of these take the longer title: | face | read points | |---|---| | SERP / Open Graph / Twitter metadata | `app/[lang]/docs/[[...slug]]/page.tsx` `generateMetadata`: `title` (232), `openGraph.title` (237), OG image `alt` (248), `twitter.title` (254) | | on-page h1 | `page.tsx:171` `DocsTitle` | | JSON-LD | `TechArticle` `headline` and `name` (`page.tsx:144,145`); the `BreadcrumbList` leaf crumb (`page.tsx:116`) | | `llms.txt` / `llms-full.txt` / `/llms.mdx/docs/…` | `app/llms.txt/route.ts:10`; `lib/source.ts:66` `getLLMText` | | Open Graph card image | `app/og/docs/[...slug]/route.tsx:18` | | site search results | `app/api/search/route.ts`, `createFromSource(source)` (fumadocs indexes `page.data.title`) | **Unchanged, by construction:** the page tree, i.e. the sidebar entries and the footer previous/next links. Every page this PR lengthens is a page-tree node, and each one declares `navTitle:` with its previous title verbatim (same YAML quoting), so `navLabel()` returns the old string. How "page-tree node" was measured, not guessed: replaying fumadocs-core 16.14.4 `buildFolder()` over every `meta.json` (all 19 authored `meta.json` list `pages` explicitly, none uses a rest entry, none lists `index`) gives **180 of 181 authored pages in the tree**: 162 sidebar leaves and 18 folder index pages. A folder index page is not a sidebar row (its folder label comes from `meta.json` `title`), but `flattenTree()` puts `folder.index` into the footer previous/next list, so it needs `navTitle` too. The one authored page outside the tree is `content/docs/index.mdx`, the global root (landed by objectstack-ai#12312, not touched here). The JSON-LD breadcrumb does not pick up the short label: `docsTrail()` calls `getBreadcrumbItems(…, { includePage: false })` and pushes `page.data.title` itself, pinned by leg D of `scripts/check-docs-nav-label.mjs` (run green below). ## Statistics, re-derived Population: every `content/docs/**/*.mdx` outside `references/**` and `releases/**`, **181 pages**. Rendered length = `title` + 14. Base `84880f9`, head `c69d5c7`. | | pages | median | min | max | in 50–60 | over 60 | under 50 | duplicate titles | `navTitle` declared | |---|--:|--:|--:|--:|--:|--:|--:|--:|--:| | before | 181 | 32 | 19 | 56 | 6 | 0 | 175 | 0 | 0 | | after | 181 | 54 | 50 | 60 | 181 | 0 | 0 | 0 | 177 | Duplicates were also checked against the 225 generated `references/**` + `releases/**` titles: no new title collides with any page. **Premise check on the approved table** (PR objectstack-ai#12312 body, section 6): it parses to **180 rows** — 4 landed with objectstack-ai#12312 and **176 not landed**. On base `84880f9`, **176 of 176** pages still carry exactly the table's *before*, **0** carry its *after*, **0** paths are missing. The generated-page test reads the file head for a marker, never prose: no authored page carries one (the nine prose hits of the word "generated" in a page head, e.g. `api/index.mdx`, `ui/index.mdx`, are body text). One authored page is absent from the table: `permissions/tenant-audit-census.mdx` (added after objectstack-ai#12312; its frontmatter is authored, only a body region is generated by `scripts/tenant-audit-census.mjs`). ## Needs maintainer voice review — not covered by the approved table Every row here is **applied in this diff**. | page | before | approved *after* | applied | rendered | why | |---|---|---|---|--:|---| | `permissions/administrator-guide.mdx` | Administrator Guide | Administrator guide to permissions and roles | **Administrator guide to permissions and access** | 59 | the approved text fails the required `check:role-word` gate: "role" is a reserved word (ADR-0090 D3). Smallest change that clears it. | | `permissions/tenant-audit-census.mdx` | Tenant-Audit Census | — (page not in the table) | **Tenant-audit census — tenancy write call sites** | 60 | authored page the table never listed; written per the rule. `navTitle: Tenant-Audit Census`. | ## The runtime-services gate `scripts/check-runtime-services-index.mjs` checks first that each chapter page declares the accessor its filename claims; the other five enumerations rest on that premise. It used to read `title`. The title rule lengthens `title` for the search-facing surfaces, so the premise now reads `navTitle`, which is the page-tree label and, on these 8 pages, the bare accessor. - Read from the page's **leading frontmatter block only**; one layer of matching YAML quotes is syntax, not value. The `title` is not read at all. - Still red: `navTitle` absent, blank, present only in the body, or not `services.NAME`. A page that still says `title: services.NAME` with no `navTitle` is red too. Each finding names the fix (`add \`navTitle: services.sms\` to the page's leading frontmatter block`). - The header prose that stated the `title:` premise is rewritten to say what the gate now does and why. - `--self-test`: new floored battery *The accessor premise lives in navTitle*, 7 cases (absent, wrong, old title-only spelling, body-only line and blank are red; a long title and a quoted value are green). Roster floor 8 to 9. The fixture's default `title` is now a long search-intent string, so every existing green case is also a green with a long title. 59 assertions. - **Ablation, trapped:** `scripts/ablation-replace.mjs` set `navTitle: services.sms` to `navTitle: services.text` in `sms-service.mdx` (anchor 1 to 0, blob `2001694b2859` to `67f9af393d4d`). The gate exited **1**: `frontmatter navTitle is "services.text", expected "services.sms" to match the filename -- set \`navTitle: services.sms\`, or rename the page if the accessor really changed`. Restored under an EXIT/INT/TERM trap to blob `2001694b2859`, which equals HEAD; `git diff HEAD` empty. - The live tree before the 8 pages were edited: the new gate went red with exactly 8 `declares no navTitle` findings. **Other readers of these pages' `title`** (`git grep` over `scripts/`, `packages/`, `apps/docs`, `.github/`, `content/docs`): no other gate or script parses it. `apps/docs` reads it generically through `page.data.title`, which is the intended change. `content/docs/kernel/index.mdx` names the accessors in its services table and does not read the page titles. One piece of stale prose: the header of `scripts/check-docs-single-h1.mjs` (lines 72-73) uses `audit-service.mdx` `title: services.audit` as a worked example. Its logic is unaffected and it is green, but the example no longer matches the page. That file is outside this PR's surface, so it is noted here and not edited. ## The complete table All 177 authored pages this PR changes. Rendered = `title` + ` | ObjectStack`. `navTitle` = the page's previous title, declared verbatim. ✳️ = in the review section above. | page | before | after | rendered | `navTitle` | |---|---|---|--:|:-:| | `ai/actions-as-tools.mdx` | Actions as Tools | MCP tools — expose actions to AI agents | 53 | yes | | `ai/agents.mdx` | AI Agents | AI agents — declare tools, model and prompt | 57 | yes | | `ai/connect-mcp.mdx` | Connect an MCP Client | MCP client setup — Claude, Cursor and IDEs | 56 | yes | | `ai/index.mdx` | AI Overview | AI features — MCP tools, agents and RAG | 53 | yes | | `ai/knowledge-rag.mdx` | Knowledge & RAG | RAG — embeddings and knowledge retrieval | 54 | yes | | `ai/natural-language-queries.mdx` | Natural Language Queries | Natural language queries over your data | 53 | yes | | `ai/skills-reference.mdx` | AI Skills Reference | AI skill reference — every field explained | 56 | yes | | `ai/skills.mdx` | AI Skills System | AI skills — reusable instructions for agents | 58 | yes | | `ai/tools.mdx` | Tool Records | Tool records — govern what an agent may call | 58 | yes | | `api/client-sdk.mdx` | Client SDK | Client SDK — typed JavaScript data access | 55 | yes | | `api/data-api.mdx` | Data API | REST data API — CRUD over every object | 52 | yes | | `api/data-flow.mdx` | Data Flow Diagrams | Request data flow — from HTTP to driver | 53 | yes | | `api/declarative-endpoints.mdx` | Declarative Endpoints | Custom endpoints declared as metadata | 51 | yes | | `api/environment-routing.mdx` | Environment-Scoped Routing | Environment routing — dev, preview, prod | 54 | yes | | `api/error-catalog.mdx` | Error Code Catalog | API error codes — the complete catalog | 52 | yes | | `api/error-handling-client.mdx` | Client-Side Error Handling | Client error handling — retries and codes | 55 | yes | | `api/error-handling-server.mdx` | Server-Side Error Handling | Server error handling — throw the envelope | 56 | yes | | `api/index.mdx` | API Overview | REST and GraphQL APIs — generated per object | 58 | yes | | `api/metadata-api.mdx` | Metadata & Package API | Metadata API — read and publish packages | 54 | yes | | `api/plugin-endpoints.mdx` | Plugin Endpoints | Plugin endpoints — add routes from a plugin | 57 | yes | | `api/wire-format.mdx` | Wire Format & JSON Examples | API wire format — request and response JSON | 57 | yes | | `automation/approvals.mdx` | Approval workflow | Approval chains — multi-step sign-off rules | 57 | yes | | `automation/connectors.mdx` | Connectors | Connectors — call external systems safely | 55 | yes | | `automation/email-templates.mdx` | Email Templates | Email templates — merge fields and layouts | 56 | yes | | `automation/flows.mdx` | Flow Metadata | Flows — DAG automation as typed metadata | 54 | yes | | `automation/hook-bodies.mdx` | Hook & Action Bodies (L1 / L2) | Hook and action bodies — the L1/L2 rules | 54 | yes | | `automation/hooks.mdx` | Hooks | Record hooks — beforeInsert to afterDelete | 56 | yes | | `automation/index.mdx` | Automation | Automation — flows, triggers and schedules | 56 | yes | | `automation/jobs.mdx` | Scheduled Jobs | Scheduled jobs — cron automation metadata | 55 | yes | | `automation/webhooks.mdx` | Webhook Delivery | Webhooks — outbound delivery and retries | 54 | yes | | `automation/workflows.mdx` | Workflow Metadata | Workflow rules — declarative record logic | 55 | yes | | `build-without-code.mdx` | Build Without Code | Build business apps without writing code | 54 | yes | | `capabilities/ai.mdx` | AI Under Governance | AI under governance — permissioned agents | 55 | yes | | `capabilities/analytics.mdx` | Analytics & Dashboards | Analytics — dashboards, reports and charts | 56 | yes | | `capabilities/approvals.mdx` | Approvals | Approvals — routing, queues and audit trail | 57 | yes | | `capabilities/automation.mdx` | Automation — Processes That Run Themselves | Automation — processes that run themselves | 56 | yes | | `capabilities/data.mdx` | Manage Business Data | Business data — objects, fields and records | 57 | yes | | `capabilities/forms.mdx` | Forms & Data Quality | Forms and data quality — validation rules | 55 | yes | | `capabilities/index.mdx` | What Can It Do? | What can it do? — the capability overview | 55 | yes | | `capabilities/integrations.mdx` | Integrations & Everyday Work | Integrations — email, files and everyday work | 59 | yes | | `capabilities/permissions.mdx` | Permissions — Who Sees What | Permissions — who sees what, enforced | 51 | yes | | `capabilities/request-template.mdx` | How to Request Features | Request a feature — how to describe it | 52 | yes | | `capabilities/views.mdx` | Views — See Data Your Way | Views — lists, kanban, calendar and gantt | 55 | yes | | `concepts/architecture.mdx` | Protocol Architecture | Protocol architecture — the four layers | 53 | yes | | `concepts/design-principles.mdx` | Design Principles | Design principles behind the metadata spec | 56 | yes | | `concepts/index.mdx` | Core Concepts | Core concepts — metadata, runtime, protocol | 57 | yes | | `concepts/metadata-driven.mdx` | Metadata-Driven Development | Metadata-driven development explained | 51 | yes | | `concepts/metadata-lifecycle.mdx` | Metadata Lifecycle & HMR | Metadata lifecycle — load, publish and HMR | 56 | yes | | `concepts/north-star.mdx` | North Star | North star — why this framework exists | 52 | yes | | `data-modeling/analytics.mdx` | Analytics Datasets | Analytics datasets — modelling for reports | 56 | yes | | `data-modeling/drivers.mdx` | Database Drivers | Database drivers — Postgres, MySQL, Mongo | 55 | yes | | `data-modeling/external-datasources.mdx` | External Datasources (Federation) | External datasources — query without ETL | 54 | yes | | `data-modeling/field-type-decision-tree.mdx` | Field Type Decision Tree | Which field type should I use? A decision tree | 60 | yes | | `data-modeling/field-types.mdx` | Field Type Gallery | Field types — the complete visual gallery | 55 | yes | | `data-modeling/fields.mdx` | Field Metadata | Field metadata — every option explained | 53 | yes | | `data-modeling/formulas.mdx` | Expressions (CEL) | Formulas — CEL expressions on records | 51 | yes | | `data-modeling/import-mappings.mdx` | Import Mappings | Import mappings — load external data files | 56 | yes | | `data-modeling/index.mdx` | Data Modeling | Data modelling — objects, fields, relations | 57 | yes | | `data-modeling/indexing.mdx` | Database Indexing | Database indexes — declare and tune them | 54 | yes | | `data-modeling/object-extensions.mdx` | Object Extensions | Object extensions — extend without forking | 56 | yes | | `data-modeling/objects.mdx` | Object Metadata | Object metadata — define your data schema | 55 | yes | | `data-modeling/queries.mdx` | Query Syntax Cheat Sheet | ObjectQL query syntax — a cheat sheet | 51 | yes | | `data-modeling/relationships.mdx` | Relationships & Lookups | Relationships — lookups, master-detail | 52 | yes | | `data-modeling/schema-design.mdx` | Schema Design | Schema design — model a business domain | 53 | yes | | `data-modeling/seed-data.mdx` | Seed Data & Fixtures | Seed data — fixtures and demo datasets | 52 | yes | | `data-modeling/validation-rules.mdx` | Field Validation Rules | Validation rules — reject bad records | 51 | yes | | `data-modeling/validation.mdx` | Validation Metadata | Validation metadata — declare the checks | 54 | yes | | `deployment/backup-restore.mdx` | Backup & Restore | Backup and restore — protect tenant data | 54 | yes | | `deployment/cli.mdx` | Command Line Interface | Command line interface — the os CLI reference | 59 | yes | | `deployment/environment-variables.mdx` | Environment Variables | Environment variables — the full list | 51 | yes | | `deployment/index.mdx` | Deployment Overview | Deployment — ship a runtime to production | 55 | yes | | `deployment/production-readiness.mdx` | Production Readiness | Production readiness — the go-live list | 53 | yes | | `deployment/publish-and-preview.mdx` | Publish, Versioning & Preview | Publish, versioning and preview builds | 52 | yes | | `deployment/seed-tenancy-repair.mdx` | Seed Tenancy Repair | Repair seed tenancy after a bad import | 52 | yes | | `deployment/self-hosting.mdx` | Self-Hosted Deployment | Self-hosting — run your own deployment | 52 | yes | | `deployment/single-project-mode.mdx` | Single-Environment Mode | Single-environment mode — the simple setup | 56 | yes | | `deployment/tenancy-modes.mdx` | Tenancy Postures & Membership | Tenancy postures and membership models | 52 | yes | | `deployment/troubleshooting.mdx` | Troubleshooting & FAQ | Troubleshooting — common errors and fixes | 55 | yes | | `deployment/validating-metadata.mdx` | Validating Metadata | Validate metadata before you deploy it | 52 | yes | | `getting-started/build-with-claude-code.mdx` | Build with Claude Code | Build an app with Claude Code, step by step | 57 | yes | | `getting-started/common-patterns.mdx` | Common Patterns | Common patterns — proven metadata recipes | 55 | yes | | `getting-started/examples.mdx` | Example Apps | Example apps — CRM, showcase and more | 51 | yes | | `getting-started/glossary.mdx` | Glossary | Glossary — every metadata term defined | 52 | yes | | `getting-started/how-ai-development-works.mdx` | How AI Development Works | How AI-written app development works | 50 | yes | | `getting-started/index.mdx` | What is ObjectStack? | What is ObjectStack? — a 5-minute intro | 53 | yes | | `getting-started/quick-reference.mdx` | Quick Reference Guide | Quick reference — every metadata type | 51 | yes | | `getting-started/quick-start.mdx` | Anatomy of an ObjectStack App | Anatomy of an app — your first metadata | 53 | yes | | `getting-started/your-first-project.mdx` | Your First Project | Your first project — from zero to running | 55 | yes | | `kernel/architecture.mdx` | Architecture | Core architecture — kernel and services | 53 | yes | | `kernel/cluster.mdx` | Cluster Semantics | Cluster semantics — multi-node runtimes | 53 | yes | | `kernel/contracts/auth-service.mdx` | IAuthService Contract | IAuthService — the authentication contract | 56 | yes | | `kernel/contracts/cache-service.mdx` | ICacheService Contract | ICacheService — the cache service contract | 56 | yes | | `kernel/contracts/data-engine.mdx` | IDataEngine Contract | IDataEngine — the storage driver contract | 55 | yes | | `kernel/contracts/index.mdx` | Service Contracts Overview | Service contracts — the kernel interfaces | 55 | yes | | `kernel/contracts/metadata-service.mdx` | IMetadataService Contract | IMetadataService — the metadata contract | 54 | yes | | `kernel/contracts/storage-service.mdx` | IStorageService Contract | IStorageService — the file storage contract | 57 | yes | | `kernel/events.mdx` | Events & Hooks | Kernel events and hooks — the full list | 53 | yes | | `kernel/index.mdx` | Kernel & Services | Kernel and services — the runtime core | 52 | yes | | `kernel/runtime-services/audit-service.mdx` | services.audit | services.audit — the audit log service API | 56 | yes | | `kernel/runtime-services/data-service.mdx` | services.data | services.data — the record CRUD service API | 57 | yes | | `kernel/runtime-services/email-service.mdx` | services.email | services.email — the outbound mail API | 52 | yes | | `kernel/runtime-services/examples.mdx` | Runtime Service Examples | Runtime service examples — copyable code | 54 | yes | | `kernel/runtime-services/index.mdx` | Runtime Service APIs | Runtime service APIs — the services object | 56 | yes | | `kernel/runtime-services/queue-service.mdx` | services.queue | services.queue — the job queue service API | 56 | yes | | `kernel/runtime-services/settings-service.mdx` | services.settings | services.settings — the settings API | 50 | yes | | `kernel/runtime-services/sharing-service.mdx` | services.sharing | services.sharing — the record share API | 53 | yes | | `kernel/runtime-services/sms-service.mdx` | services.sms | services.sms — the text message service API | 57 | yes | | `kernel/runtime-services/storage-service.mdx` | services.storage | services.storage — the file store API | 51 | yes | | `kernel/runtime-services/versioning.mdx` | Runtime Service API Versioning | Runtime service API versioning policy | 51 | yes | | `kernel/services-checklist.mdx` | Kernel Services Checklist | Kernel services checklist for reviewers | 53 | yes | | `kernel/services.mdx` | Service Registry | Service registry — resolve and override | 53 | yes | | `permissions/access-matrix.mdx` | Access-Matrix Snapshot Gate | Access matrix snapshot gate explained | 51 | yes | | `permissions/access-recipes.mdx` | Who can see data / automation / interface | Access recipes — data, automation and UI | 54 | yes | | `permissions/administrator-guide.mdx` ✳️ | Administrator Guide | Administrator guide to permissions and access | 59 | yes | | `permissions/attachments-access.mdx` | Attachments Access | Attachment access — who can read a file | 53 | yes | | `permissions/authentication.mdx` | Authentication | Authentication — sessions, tokens, SSO | 52 | yes | | `permissions/authorization.mdx` | Authorization Architecture | Authorization architecture — how it decides | 57 | yes | | `permissions/capabilities.mdx` | Declaring Capabilities | Declaring capabilities in a permission set | 56 | yes | | `permissions/delegated-administration.mdx` | Delegated Administration | Delegated administration — scoped admin rights | 60 | yes | | `permissions/explain.mdx` | Explain Engine | Explain engine — why access was denied | 52 | yes | | `permissions/field-level-security.mdx` | Field-Level Security | Field-level security — hide and mask fields | 57 | yes | | `permissions/index.mdx` | Permissions & Identity | Permissions and identity — the overview | 53 | yes | | `permissions/permission-metadata.mdx` | Permission Metadata | Permission metadata — every option explained | 58 | yes | | `permissions/permission-sets.mdx` | Permission Sets | Permission sets — grant access in bundles | 55 | yes | | `permissions/permissions-matrix.mdx` | Security Permissions Matrix | Security permissions matrix reference | 51 | yes | | `permissions/positions.mdx` | Positions | Positions — org hierarchy for sharing | 51 | yes | | `permissions/profiles.mdx` | Profiles (removed) | Profiles (removed) — use permission sets | 54 | yes | | `permissions/record-view-auditing.mdx` | Record-View Auditing | Record view auditing — who read what | 50 | yes | | `permissions/rls.mdx` | Row-Level Security (RLS) | Row-level security (RLS) — filter by rule | 55 | yes | | `permissions/sharing-rules.mdx` | Sharing Rules | Sharing rules and organization-wide defaults | 58 | yes | | `permissions/sso.mdx` | Social & Enterprise SSO | SSO — social and enterprise sign-in setup | 55 | yes | | `permissions/system-context.mdx` | System Context (isSystem) | System context (isSystem) — bypass rules | 54 | yes | | `permissions/tenant-audit-census.mdx` ✳️ | Tenant-Audit Census | Tenant-audit census — tenancy write call sites | 60 | yes | | `plugins/adding-a-metadata-type.mdx` | Adding a Metadata Type | Add a custom metadata type from a plugin | 54 | yes | | `plugins/anatomy.mdx` | Plugin Anatomy | Plugin anatomy — files, hooks and exports | 55 | yes | | `plugins/development.mdx` | Plugin Development | Plugin development — build and test one | 53 | yes | | `plugins/index.mdx` | Plugin System | Plugin system — extend the runtime safely | 55 | yes | | `plugins/packages.mdx` | Package Overview | Package overview — what each one does | 51 | yes | | `protocol/backward-compatibility.mdx` | Backward Compatibility Policy | Backward compatibility policy for the spec | 56 | yes | | `protocol/diagram.mdx` | Protocol Relationship Diagram | Protocol relationship diagram explained | 53 | yes | | `protocol/index.mdx` | Protocol Specification | Protocol specification — the open format | 54 | yes | | `protocol/kernel/config-resolution.mdx` | Configuration Resolution | Configuration resolution order and layers | 55 | yes | | `protocol/kernel/error-handling.mdx` | Error Handling | Error handling — the response envelope | 52 | yes | | `protocol/kernel/http-protocol.mdx` | HTTP API | HTTP API protocol — routes and verbs | 50 | yes | | `protocol/kernel/i18n-standard.mdx` | Internationalization Standard | Internationalization standard for metadata | 56 | yes | | `protocol/kernel/index.mdx` | Kernel: The System Protocol | Kernel — the system protocol specification | 56 | yes | | `protocol/kernel/lifecycle.mdx` | System Lifecycle | System lifecycle — from boot to ready state | 57 | yes | | `protocol/kernel/metadata-service.mdx` | Metadata Service | Metadata service protocol specification | 53 | yes | | `protocol/kernel/plugin-spec.mdx` | Plugin Package Specification | Plugin package specification and manifest | 55 | yes | | `protocol/kernel/realtime-protocol.mdx` | Real-Time Protocols | Real-time protocols — websockets, SSE | 51 | yes | | `protocol/knowledge.mdx` | Knowledge Protocol | Knowledge protocol — RAG as metadata | 50 | yes | | `protocol/objectql/query-syntax.mdx` | Query Syntax | ObjectQL query syntax — the full specification | 60 | yes | | `protocol/objectql/schema.mdx` | Schema Definition | ObjectQL schema definition specification | 54 | yes | | `protocol/objectql/security.mdx` | Security & Access Control | ObjectQL security and access control | 50 | yes | | `protocol/objectql/state-machine.mdx` | State Machine (Lifecycle) | State machine — record lifecycle spec | 51 | yes | | `protocol/objectql/types.mdx` | Type System | ObjectQL type system — the specification | 54 | yes | | `protocol/objectui/actions.mdx` | Action Protocol | Action protocol — buttons as metadata | 51 | yes | | `protocol/objectui/concept.mdx` | UI as Data Concept | UI as data — the core ObjectUI concept | 52 | yes | | `protocol/objectui/layout-dsl.mdx` | Layout DSL | Layout DSL — arrange fields declaratively | 55 | yes | | `protocol/objectui/widget-contract.mdx` | Widget Contract | Widget contract — build a custom widget | 53 | yes | | `ui/actions.mdx` | Actions | Actions — permission-checked UI buttons | 53 | yes | | `ui/apps.mdx` | App Metadata | App metadata — navigation and branding | 52 | yes | | `ui/audience-based-interfaces.mdx` | Audience-based interfaces | Audience-based interfaces — one app, many | 55 | yes | | `ui/create-vs-edit-form.mdx` | Create form ≠ edit form | Create form vs edit form — the differences | 56 | yes | | `ui/dashboards.mdx` | Dashboard Metadata | Dashboard metadata — charts and KPIs | 50 | yes | | `ui/doc-pages.mdx` | Doc Metadata | Doc metadata — in-app documentation pages | 55 | yes | | `ui/field-grouping-and-order.mdx` | Field grouping & order | Field grouping and order in generated forms | 57 | yes | | `ui/forms.mdx` | Forms (Public + Internal) | Forms — public and internal data entry | 52 | yes | | `ui/index.mdx` | UI Engine | UI engine — render views from metadata | 52 | yes | | `ui/pages.mdx` | Page Metadata | Page metadata — build custom app screens | 54 | yes | | `ui/public-data-collection.mdx` | Collect data from the public | Collect data from the public with web forms | 57 | yes | | `ui/react-pages.mdx` | React Pages | React pages — escape hatch for custom UI | 54 | yes | | `ui/reports.mdx` | Report Metadata | Report metadata — grouped, filtered data | 54 | yes | | `ui/setup-app.mdx` | Setup App | Setup app — administer a running runtime | 54 | yes | | `ui/translations.mdx` | Translations | Translations — labels and UI text per locale | 58 | yes | | `ui/views.mdx` | View Metadata | View metadata — list, kanban, calendar | 52 | yes | | `upgrading.mdx` | Upgrading | Upgrade guide — move between major versions | 57 | yes | ## Verification All at head `c69d5c7`, `git status --porcelain` empty. `origin/main` was merged in twice during the rework round (now at merge base `e2c4e12`); neither merge touched this PR's files. Gate list derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (178 paths vs merge base `e2c4e12`, three-dot; 640 changed lines, under the 5,000 human-merge threshold), then reconciled: `--ran` answered `74 derived famil(ies) accounted for — 74 run, 0 NOT-MEASURED (a DERIVED zero — all 74 recorded an exit code and none of them is 3)`. Each command's exit code was captured before any pipe: ``` exit 0 node scripts/check-ci-filter-parity.mjs exit 0 node scripts/check-closing-keyword-parity.mjs exit 0 node scripts/check-closing-keyword-parity.mjs --self-test exit 0 node scripts/check-comment-mask-corpus.mjs exit 0 node scripts/check-declaration-mirrors.mjs exit 0 node scripts/check-declaration-mirrors.mjs --self-test exit 0 node scripts/check-doc-frontmatter.mjs exit 0 node scripts/check-doc-frontmatter.mjs --self-test exit 0 node scripts/check-doc-route-spelling.mjs --advisory exit 0 node scripts/check-doc-route-spelling.mjs --self-test exit 0 node scripts/check-docs-section-name.mjs exit 0 node scripts/check-docs-section-name.mjs --self-test exit 0 node scripts/check-scripts-symbol-anchors.mjs exit 0 node scripts/check-scripts-symbol-anchors.mjs --self-test exit 0 node scripts/check-section-landing-index.mjs exit 0 node scripts/check-section-landing-index.mjs --self-test exit 0 node scripts/check-self-test-wired.mjs exit 0 node scripts/check-self-test-wired.mjs --self-test exit 0 node scripts/check-self-test-workflow-commands.mjs exit 0 node scripts/check-self-test-workflow-commands.mjs --self-test exit 0 node scripts/check-system-context-census.mjs exit 0 node scripts/check-system-context-census.mjs --self-test exit 0 node scripts/check-tenant-audit-census.mjs exit 0 node scripts/check-tenant-audit-census.mjs --self-test exit 0 node scripts/check-whole-set-label-write.mjs exit 0 node scripts/check-whole-set-label-write.mjs --self-test exit 0 node scripts/docs-audit/check-drift-comment.mjs exit 0 node scripts/pm/bare-root-worklist.mjs --self-test exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions exit 0 pnpm --filter @objectstack/lint run check:doc-security-posture exit 0 pnpm --filter @objectstack/spec run check:docs exit 0 pnpm --filter @objectstack/spec run check:empty-state exit 0 pnpm --filter @objectstack/spec run check:liveness exit 0 pnpm --filter @objectstack/spec run check:skill-docs exit 0 pnpm --filter @objectstack/spec run check:skill-examples exit 0 pnpm --filter @objectstack/spec run check:strictness-ledger exit 0 pnpm --filter @objectstack/spec run check:variant-docs exit 0 pnpm --filter @objectstack/spec run check:yaml-examples exit 0 pnpm check:agent-test-spelling exit 0 pnpm check:bash32-floor exit 0 pnpm check:cli-command-ids exit 0 pnpm check:cli-examples-parity exit 0 pnpm check:corpus-claim-drift exit 0 pnpm check:cross-package-test-inputs exit 0 pnpm check:doc-anchors exit 0 pnpm check:doc-authoring exit 0 pnpm check:docs-audit-scope exit 0 pnpm check:docs-image-tag exit 0 pnpm check:docs-redirects exit 0 pnpm check:docs-single-h1 exit 0 pnpm check:docs-spec-enumerations exit 0 pnpm check:docs-transcript-drift exit 0 pnpm check:driver-memory-census exit 0 pnpm check:entry-guard exit 0 pnpm check:error-status-conformance exit 0 pnpm check:gitlink-declared exit 0 pnpm check:lockstep-package-count exit 0 pnpm check:merge-driver exit 0 pnpm check:nul-bytes exit 0 pnpm check:overlay-whitelist-table exit 0 pnpm check:parse-guard exit 0 pnpm check:pm-dispatch-gates exit 0 pnpm check:pm-widening-tells exit 0 pnpm check:pnpm-filter-targets exit 0 pnpm check:published-readme-links exit 0 pnpm check:quick-reference-counts exit 0 pnpm check:ratchet-remedy-authority exit 0 pnpm check:react-page-adapter-contract exit 0 pnpm check:refd-timer-probe exit 0 pnpm check:role-word exit 0 pnpm check:runtime-services-index exit 0 pnpm check:skill-identifier-liveness exit 0 pnpm check:vendor-version-stamps exit 0 pnpm check:watch-hint-literal exit 0 node scripts/check-docs-nav-label.mjs exit 0 node scripts/check-docs-nav-label.mjs --self-test ``` Verdict lines: - `check-doc-anchors: 376 internal #fragment link(s) across 411 source file(s) all resolve to a real heading` (the card's acceptance gate) - `check-doc-frontmatter: 2 content root(s) verified, each against its own floor — content/docs 406, content/blog 3.` - `check-docs-nav-label`: `navTitle` named in code by 2 file(s) only; resolver executed; plugin wired into the docs loader; JSON-LD breadcrumb leg green. It reads `apps/docs/**` only, so a `navTitle:` frontmatter line is not judged by it; run as a guard that the mechanism this PR relies on is intact. - `check-runtime-services-index --self-test: 59 assertions …` and `check-runtime-services-index: 8 chapter page(s) vs meta.json "pages" …` both green, with the 8 approved titles applied. For the record, round 1: on the verbatim table `check:role-word` and `check:runtime-services-index` exited **1**. Both were real findings: the first is handled by the re-worded row, the second by the gate change above. `check:skill-examples` exited **3** (`PREREQUISITE NOT MET`, stale `client-react` declarations, nothing measured) until `@objectstack/client-react` was built. All green at this head. **NOT MEASURED locally:** the card's "link checker" is `.github/workflows/check-links.yml` (lychee, advisory lane); lychee is not installed in this container. It runs on this PR in CI. This diff changes no link, link text or file path, only two frontmatter keys. A full `next build` of `apps/docs` was not run locally; `fumadocs-mdx` regenerated the source index at exit 0, and `check-doc-frontmatter` parses every page with the `yaml` version the build resolves. `pnpm lint` is not owed: `.mdx` is outside the ESLint population and no other file type is touched. No changeset: docs content only, publishes nothing. `skip-changeset` applied. ## Acceptance notes - **30 approved rows have no ` — ` separator** (for example `ai/natural-language-queries.mdx` → `Natural language queries over your data`), although the ruling says "separator ` — ` throughout". The ruling approves the table "as they stand", so they are applied verbatim (PM answer in review comment `5852831074`); re-wording them is the maintainer's call. - **The two folder-index pages objectstack-ai#12312 lengthened show the long title in the footer previous/next links**: `protocol/objectql/index.mdx` and `protocol/objectui/index.mdx` carry no `navTitle`, and `flattenTree()` puts a folder index into that list. objectstack-ai#12312 judged them "not in the page tree / `meta.json` title wins", which holds for the sidebar only. Left as they are, per the PM answer in review comment `5852831074`. - The objectstack-ai#12236 trap note (76 demoted body H1s as a mapping-table input) is moot for the applied rows: the table was approved as it stands. - Pre-existing duplicate titles exist among the generated `references/**` pages (e.g. `Mapping`, `Sharing`, `Plugin`); that is the generator half, objectstack-ai#15403. --- _Generated by [Claude Code](https://claude.ai/code/session_018bR89KaSkoZVqgBnUYXtyD)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
veigajoao
pushed a commit
to veigajoao/objectstack
that referenced
this pull request
Sep 29, 2026
…ebar labels kept via navTitle (objectstack-ai#20401) Fixes objectstack-ai#15403 Clause-②: no ## Summary The generated reference pages under `content/docs/references/**` now carry the docs page-title rule, emitted by the generator itself, and every one keeps its sidebar label byte-identical through `navTitle`. - **One pure function holds the rule:** `packages/spec/scripts/lib/page-title.ts`. It builds each title from data the generator already holds (the module display name and the category's declared title) plus fixed words, picks the longest candidate inside the band, and refuses (never truncates) when no candidate fits. - **Three emission sites use it:** module pages and category index pages in `packages/spec/scripts/build-docs.ts`, and the root index in `packages/spec/scripts/lib/root-index.ts`. - **`navTitle` = the page's previous `title`, verbatim**, so `navLabel()` in `apps/docs/lib/nav-title.ts` returns the same string as before. The page tree is unchanged (replayed below, with a control). - **211 generated pages regenerated**, frontmatter only: each is `+2/-1` (the `title:` line replaced, a `navTitle:` line added). No `description:`, no body, no `meta.json`. ## The rule, and how the emitter expresses it The rule is the maintainer's, recorded on objectstack-ai#12237 in comment `5419555720`, verbatim 「其他同意」. Its text as landed is the `## The rule` section of PR objectstack-ai#20170: - shape `PRIMARY-KEYWORD — QUALIFIER`, separator ` — `; - frontmatter `title` is 36–46 characters, so the rendered title with the 14-character ` | ObjectStack` suffix is 50–60, none over 60; - `ObjectStack` never inside a title; - short navigation labels go in `navTitle`, whose fallback to `title` lives only in `navLabel()`. The generator holds only the module display name (3–29 characters on this tree), the category's declared title (11–20 for categories with pages) and fixed words. No single template lands every page inside an 11-character band, so each page kind has a short ladder, longest first, and the title is the first candidate inside the band: | page kind | candidate (longest first) | length | |---|---|--:| | module page | `NAME schema — CATEGORY property reference`, offered only when the page renders a `### Properties` table | name + category + 29 | | | `NAME schema — CATEGORY reference` | name + category + 20 | | | `NAME — CATEGORY reference` | name + category + 13 | | | `NAME — CATEGORY` | name + category + 3 | | category index | `CATEGORY — complete schema reference` | category + 28 | | | `CATEGORY — schema reference` | category + 19 | | root index | `Protocol reference — every schema by module` | 43 | The module rungs overlap end to end and cover every name + category length from 7 to 43; the longest pair on the tree is 41 (`Schemaless Node Config` in `Automation Protocol`). The first module rung is conditional: `property reference` is offered only when at least one of the page's schemas renders a `### Properties` table (`rendersPropertiesTable` in `lib/schema-section.ts`, built on `declaresProperties`, the one expression the section renderer itself branches on). A page without one, such as the enum-only `data/feed`, starts at the second rung; without the first rung the ladder covers name + category lengths 16 to 43, and the shortest property-less pair on the tree is 17 (`Feed` in `Data Protocol`). The category rungs cover category titles of 8 to 27 characters; the longest declared title is 25. The category in every module title keeps same-named modules apart (`Plugin` is both a `kernel` and a `studio` page). A page no rung fits stops `gen:docs` with a message naming the page and every candidate with its length. Rung usage on this tree: module pages 18 / 103 / 60 / 15 (rungs 1–4), category index pages 11 / 3, root index 1. All 18 rung-1 pages render a `### Properties` table; the 14 module pages that render none are all on rungs 2–4. ## Measured, before and after Population re-derived at base `862b6ce8` and at head: `content/docs/**/*.mdx` = **406**; pages carrying the generator's `AUTO-GENERATED — DO NOT EDIT` banner = **211**, all under `content/docs/references/**` (196 module pages, 14 category index pages, 1 root index). Rendered = `title` + 14. | | pages | min | median | max | in 36–46 | outside | rendered over 60 | with ` — ` | with `ObjectStack` | duplicate titles | `navTitle` | |---|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:| | before (`origin/main`) | 211 | 3 | 11 | 29 | 0 | 211 | 0 | 0 | 0 | 6 | 0 | | after (this head) | 211 | 37 | 43 | 46 | 211 | 0 | 0 | 211 | 0 | 0 | 211 | After: rendered 51–60. Duplicate titles across the whole corpus (406 pages, authored and generated): **0**. **The card body's 225 of 405 does not reproduce, and the difference is not generator output.** The generated set is 211; the 14 others in that count are `content/docs/releases/**` pages, which carry no generator banner and are release-owned (compiled by hand at release time). No other emitter writes a `title:` into `content/docs/**`: `build-skill-docs.ts` rewrites only the region between its markers in `content/docs/ai/skills-reference.mdx`, whose authored title already follows the rule (PR objectstack-ai#20170). The only `title:` emissions in the repo's generators are the three this PR changes (`git grep` over `scripts/`, `packages/*/scripts/`, `apps/docs/scripts/`). ## The page tree is unchanged Replayed with the site's own machinery: `fumadocs-core` 16.14.4 `loader()` (the `apps/docs` dependency) with the site's `i18n` settings and the site's `navTitlePlugin()`, over every `.mdx` frontmatter and `meta.json` under `content/docs`, once from `origin/main` and once from this head. Serialized page tree (folders, separators, pages, folder index pages): **449 nodes before, 449 after, `diff` exit 0.** Control, so the replay can see a title change at all: the same replay of this head WITHOUT `navTitlePlugin()` differs from `origin/main` in **388** nodes, exactly the 211 generated pages plus the 177 authored pages PR objectstack-ai#20170 gave a `navTitle`. Folder labels come from each category's `meta.json` `title`, which this PR does not touch. The category and root `index.mdx` pages carry `navTitle` anyway because the footer previous/next list walks folder index pages. ## Examples | page | before | after | chars / rendered | |---|---|---|--:| | `references/ai/mcp.mdx` | Mcp | Mcp schema — AI Protocol property reference | 43 / 57 | | `references/ai/agent.mdx` | Agent | Agent schema — AI Protocol property reference | 45 / 59 | | `references/data/feed.mdx` | Feed | Feed schema — Data Protocol reference | 37 / 51 | | `references/data/object.mdx` | Object | Object schema — Data Protocol reference | 39 / 53 | | `references/automation/flow.mdx` | Flow | Flow schema — Automation Protocol reference | 43 / 57 | | `references/kernel/plugin.mdx` | Plugin | Plugin schema — Kernel Protocol reference | 41 / 55 | | `references/studio/plugin.mdx` | Plugin | Plugin schema — Studio Protocol reference | 41 / 55 | | `references/kernel/plugin-registry.mdx` | Plugin Registry | Plugin Registry — Kernel Protocol reference | 43 / 57 | | `references/kernel/metadata-protection.mdx` | Metadata Protection | Metadata Protection — Kernel Protocol | 37 / 51 | | `references/ui/expression-bindable-text-keys.mdx` | Expression Bindable Text Keys | Expression Bindable Text Keys — UI Protocol | 43 / 57 | | `references/automation/schemaless-node-config.mdx` | Schemaless Node Config | Schemaless Node Config — Automation Protocol | 44 / 58 | | `references/ai/index.mdx` | AI Protocol | AI Protocol — complete schema reference | 39 / 53 | | `references/automation/index.mdx` | Automation Protocol | Automation Protocol — schema reference | 38 / 52 | | `references/identity/index.mdx` | Identity Protocol | Identity Protocol — complete schema reference | 45 / 59 | | `references/index.mdx` | Protocol Reference | Protocol reference — every schema by module | 43 / 57 | The complete table is at the end. ## Wording for maintainer voice review The rule is approved; the fixed words the generator adds are new public text and are not in the approved table of PR objectstack-ai#12312: `schema`, `property reference`, `reference`, `complete schema reference`, `schema reference`, and the root title `Protocol reference — every schema by module`. Rewording any of them is a one-line change in `lib/page-title.ts` plus `gen:docs`; the band, the unit test and `check:docs` hold the result either way. Non-blocking: the seat answered land-as-shipped in comment 5865474678 on objectstack-ai#15403, and `property reference` is now claimed only by pages that render a property table. ## File surface - `packages/spec/scripts/lib/page-title.ts` (new): the rule, the ladders, the refusal, the frontmatter lines. - `packages/spec/scripts/page-title.test.ts` (new): the pin (`local` vitest project; reads nothing outside the package). - `packages/spec/scripts/build-docs.ts`: the two emission sites (module pages, category index pages), located by symbol. - `packages/spec/scripts/lib/schema-section.ts`: `declaresProperties()` (an object that declares properties, the one spelling of that condition, now also used by the renderer's root and union-arm branches) and `rendersPropertiesTable()` (whether a schema renders at least one `### Properties` table), read by the module-page title. Rendered output unchanged: regenerating moved exactly one page, `data/feed.mdx`, and only its `title:` line. - `packages/spec/scripts/lib/root-index.ts`: **one site beyond the two the dispatch named.** It is the generator's third `title:` emission (the root `references/index.mdx`, previously `title: Protocol Reference`, 18 characters). Leaving it would have left one generated page outside the rule. - `content/docs/references/**`: 211 pages, regenerated by `gen:docs`, never hand-edited. - Not touched: authored pages, the `description:` emission, `check-generated.ts` (it regenerates and compares; nothing in it reads the title line), `build-skill-docs.ts` (emits no title). ## Changeset: `skip-changeset` Rule applied: a changeset is owed when a package's published `files[]` content moves. `@objectstack/spec` publishes `dist`, `json-schema`, `liveness`, `prompts`, `llms.txt`, `README.md`, `src/**/*.zod.ts`, `CHANGELOG.md`, `api-surface`, `spec-changes.json`; `scripts/` is not among them, and `apps/docs` (which renders `content/docs`) is private. Measured after the build: the new symbols (`titleFrontmatter`, `navTitle`, `property reference`) have **0** hits across all ten of those paths, while the positive control `ObjectSchema` hits in 9 of the 10 (all but `json-schema`). ## Verification Head `1ba9d84c`, REWORK round 1: `41b0f683` (the conditional rung), `0ea42f45` (`data/feed.mdx` regenerated), then a merge of `origin/main` at `df3ba164` whose two deferred pages, `security/{permission,rls}.mdx`, were regenerated on the merged tree in `1ba9d84c`. Round 0 was head `a61335f9`. - `pnpm --filter @objectstack/spec test`: 561 files, 16546 passed, 1 todo. - `pnpm --filter @objectstack/spec test:repo`: 35 files, 634 passed. - Type check: `check:scripts-typecheck` (`tsc -p tsconfig.scripts.json`, the program holding every file this diff edits, `page-title.test.ts` included) exit 0 at `1ba9d84c`. The full spec `typecheck` was green locally at round 0's pre-merge head `0a241893`, and `Type Check · workspace` ran it green in CI on `a61335f9`. - The new files are in the scripts tsc program (`tsc -p tsconfig.scripts.json --listFiles` lists `lib/page-title.ts` and `page-title.test.ts`). - Gates derived by `node scripts/pm/dispatch-gates.mjs --commands` over this diff at `1ba9d84c` (216 paths, the same 89 families as round 0), each exit code recorded, then `--ran`: **89 derived, 85 run (all exit 0), 4 NOT MEASURED, 0 UNRUN.** Among the 85: `check:docs`, `check:generated` (all 15 generated artifacts up to date), `check:doc-frontmatter`, `check:docs-single-h1`, `check:doc-anchors`, `check:nul-bytes`, `check:quick-reference-counts`. - NOT MEASURED, prerequisite refused (exit 3): `check:skill-examples` (needs the 36-package `@objectstack/client-react` closure built), `check:dual-build-cjs-loads` (needs every package built), `check:type-check-debt` (needs a 30-package closure built). - NOT MEASURED, timed out: `check:pm-dispatch-gates`. Its self-test of `scripts/pm/dispatch-gates.mjs`, which this diff does not edit, did not finish in a 420 s and then a 560 s window on the shared box. Not re-run in round 1; `Lint & Repo Gates` ran it green in CI on `a61335f9`. - **Ablation 1, the unit test can fail:** `scripts/ablation-replace.mjs` changed `TITLE_MAX = 46` to `60` in `lib/page-title.ts` (anchor 1 to 0, blob `3fe85286f82d` to `ee55509927c8`). `page-title.test.ts` went **12 failed / 13 passed**. Restored under an EXIT/INT/TERM trap to `3fe85286f82d` = HEAD blob, `git diff HEAD` empty. - **Ablation 2, `check:docs` sees the emission:** the first module rung's `property reference` changed to `field reference` (blob `3fe85286f82d` to `b8f4c6d26269`). `check:docs` exited **1** with **49** pages out of date: the 19 rung-1 pages, plus the pages the shorter word lets onto rung 1. Restored the same way. - **Ablation 3, the rung condition is pinned:** `...(documentsProperties ? [` changed to `...(true ? [` in `lib/page-title.ts` (anchor 1 to 0, blob `38277b23f728` to `90e0cbe18d3b`). `page-title.test.ts` went **3 failed / 38 passed**: the `data/feed` row, the condition pin, and the short property-less refusal. Restored to `38277b23f728` = HEAD blob, `git diff HEAD` empty. - **The predicate agrees with the renderer on every published schema:** over all 1523 JSON Schemas under `packages/spec/json-schema/`, `rendersPropertiesTable(name, schema)` equals whether `renderSchemaSection(name, schema)` emits a `### Properties` heading: 1219 true, **0 disagreements**. The unit pin holds the same agreement shape by shape (11 JSON Schema shapes covering every renderer branch). ## Acceptance notes - **Module display names are title-cased from the file slug, so abbreviations come out wrong:** `Mcp`, `Rls`, `Scim`, `Odata`, `Http`, `I18n`, `Driver Sql`, `Cli Extension`, `Io Node Config`, `Bpmn Interop`, `Package Api`, `Rest Server`, `Events Dlq`, and others. This is the `Qa Protocol` defect class that `lib/category-title.ts` fixed for category titles, one level down, and it predates this PR (these strings were the whole title before; they are the `navTitle` now, and the primary keyword of the new title). Out of scope here: fixing it changes nav labels, which this card keeps byte-identical. Noted, not filed. - The card body's 225 / 405 reading included 14 `releases/**` pages (see *Measured*); nothing here depends on them. - `api/protocol.mdx` renders 6 `### Properties` tables with no rows (object schemas declaring an empty `properties`). Pre-existing renderer output; that page is on rung 2 and carries non-empty tables as well. Noted, not filed. - The optional routing of `rootIndexTitle()` through `pageTitleOrExit` was not taken: `pageTitleOrExit` lives in `build-docs.ts`, so routing it means passing the title into `renderRootIndex` (its input type and the `root-index.test.ts` fixtures), more than one line. The root title is a fixed 43-character string pinned by the unit test. ## The complete table All 211 generated pages. `navTitle` = before, verbatim, on every row. | page | before | after | chars / rendered | `navTitle` | |---|---|---|--:|---| | `references/index.mdx` | Protocol Reference | Protocol reference — every schema by module | 43 / 57 | = before | | `references/ai/index.mdx` | AI Protocol | AI Protocol — complete schema reference | 39 / 53 | = before | | `references/ai/agent.mdx` | Agent | Agent schema — AI Protocol property reference | 45 / 59 | = before | | `references/ai/build-progress.mdx` | Build Progress | Build Progress schema — AI Protocol reference | 45 / 59 | = before | | `references/ai/conversation.mdx` | Conversation | Conversation schema — AI Protocol reference | 43 / 57 | = before | | `references/ai/embedding.mdx` | Embedding | Embedding schema — AI Protocol reference | 40 / 54 | = before | | `references/ai/knowledge-document.mdx` | Knowledge Document | Knowledge Document — AI Protocol reference | 42 / 56 | = before | | `references/ai/knowledge-source.mdx` | Knowledge Source | Knowledge Source — AI Protocol reference | 40 / 54 | = before | | `references/ai/mcp.mdx` | Mcp | Mcp schema — AI Protocol property reference | 43 / 57 | = before | | `references/ai/model-registry.mdx` | Model Registry | Model Registry schema — AI Protocol reference | 45 / 59 | = before | | `references/ai/skill.mdx` | Skill | Skill schema — AI Protocol property reference | 45 / 59 | = before | | `references/ai/solution-blueprint.mdx` | Solution Blueprint | Solution Blueprint — AI Protocol reference | 42 / 56 | = before | | `references/ai/tool.mdx` | Tool | Tool schema — AI Protocol property reference | 44 / 58 | = before | | `references/ai/usage.mdx` | Usage | Usage schema — AI Protocol property reference | 45 / 59 | = before | | `references/api/index.mdx` | API Protocol | API Protocol — complete schema reference | 40 / 54 | = before | | `references/api/analytics.mdx` | Analytics | Analytics schema — API Protocol reference | 41 / 55 | = before | | `references/api/auth-endpoints.mdx` | Auth Endpoints | Auth Endpoints schema — API Protocol reference | 46 / 60 | = before | | `references/api/auth.mdx` | Auth | Auth schema — API Protocol property reference | 45 / 59 | = before | | `references/api/automation-api.mdx` | Automation Api | Automation Api schema — API Protocol reference | 46 / 60 | = before | | `references/api/batch.mdx` | Batch | Batch schema — API Protocol property reference | 46 / 60 | = before | | `references/api/contract.mdx` | Contract | Contract schema — API Protocol reference | 40 / 54 | = before | | `references/api/discovery.mdx` | Discovery | Discovery schema — API Protocol reference | 41 / 55 | = before | | `references/api/dispatcher.mdx` | Dispatcher | Dispatcher schema — API Protocol reference | 42 / 56 | = before | | `references/api/documentation.mdx` | Documentation | Documentation schema — API Protocol reference | 45 / 59 | = before | | `references/api/endpoint.mdx` | Endpoint | Endpoint schema — API Protocol reference | 40 / 54 | = before | | `references/api/error-code-ledger.mdx` | Error Code Ledger | Error Code Ledger — API Protocol reference | 42 / 56 | = before | | `references/api/errors.mdx` | Errors | Errors schema — API Protocol reference | 38 / 52 | = before | | `references/api/events.mdx` | Events | Events schema — API Protocol reference | 38 / 52 | = before | | `references/api/export.mdx` | Export | Export schema — API Protocol reference | 38 / 52 | = before | | `references/api/http-cache.mdx` | Http Cache | Http Cache schema — API Protocol reference | 42 / 56 | = before | | `references/api/metadata.mdx` | Metadata | Metadata schema — API Protocol reference | 40 / 54 | = before | | `references/api/misc.mdx` | Misc | Misc schema — API Protocol property reference | 45 / 59 | = before | | `references/api/odata.mdx` | Odata | Odata schema — API Protocol property reference | 46 / 60 | = before | | `references/api/package-api-assembled.mdx` | Package Api Assembled | Package Api Assembled — API Protocol reference | 46 / 60 | = before | | `references/api/package-api.mdx` | Package Api | Package Api schema — API Protocol reference | 43 / 57 | = before | | `references/api/package-lifecycle.mdx` | Package Lifecycle | Package Lifecycle — API Protocol reference | 42 / 56 | = before | | `references/api/plugin-rest-api.mdx` | Plugin Rest Api | Plugin Rest Api — API Protocol reference | 40 / 54 | = before | | `references/api/protocol.mdx` | Protocol | Protocol schema — API Protocol reference | 40 / 54 | = before | | `references/api/query-adapter.mdx` | Query Adapter | Query Adapter schema — API Protocol reference | 45 / 59 | = before | | `references/api/realtime-shared.mdx` | Realtime Shared | Realtime Shared — API Protocol reference | 40 / 54 | = before | | `references/api/realtime.mdx` | Realtime | Realtime schema — API Protocol reference | 40 / 54 | = before | | `references/api/rest-server.mdx` | Rest Server | Rest Server schema — API Protocol reference | 43 / 57 | = before | | `references/api/router.mdx` | Router | Router schema — API Protocol reference | 38 / 52 | = before | | `references/api/sortability.mdx` | Sortability | Sortability schema — API Protocol reference | 43 / 57 | = before | | `references/api/storage.mdx` | Storage | Storage schema — API Protocol reference | 39 / 53 | = before | | `references/api/versioning.mdx` | Versioning | Versioning schema — API Protocol reference | 42 / 56 | = before | | `references/api/websocket.mdx` | Websocket | Websocket schema — API Protocol reference | 41 / 55 | = before | | `references/automation/index.mdx` | Automation Protocol | Automation Protocol — schema reference | 38 / 52 | = before | | `references/automation/approval.mdx` | Approval | Approval — Automation Protocol reference | 40 / 54 | = before | | `references/automation/bpmn-interop.mdx` | Bpmn Interop | Bpmn Interop — Automation Protocol reference | 44 / 58 | = before | | `references/automation/builtin-node-config.mdx` | Builtin Node Config | Builtin Node Config — Automation Protocol | 41 / 55 | = before | | `references/automation/control-flow.mdx` | Control Flow | Control Flow — Automation Protocol reference | 44 / 58 | = before | | `references/automation/execution.mdx` | Execution | Execution — Automation Protocol reference | 41 / 55 | = before | | `references/automation/flow-function.mdx` | Flow Function | Flow Function — Automation Protocol reference | 45 / 59 | = before | | `references/automation/flow.mdx` | Flow | Flow schema — Automation Protocol reference | 43 / 57 | = before | | `references/automation/io-node-config.mdx` | Io Node Config | Io Node Config — Automation Protocol reference | 46 / 60 | = before | | `references/automation/node-executor.mdx` | Node Executor | Node Executor — Automation Protocol reference | 45 / 59 | = before | | `references/automation/schedule-organization.mdx` | Schedule Organization | Schedule Organization — Automation Protocol | 43 / 57 | = before | | `references/automation/schemaless-node-config.mdx` | Schemaless Node Config | Schemaless Node Config — Automation Protocol | 44 / 58 | = before | | `references/automation/state-machine.mdx` | State Machine | State Machine — Automation Protocol reference | 45 / 59 | = before | | `references/automation/time-relative-trigger.mdx` | Time Relative Trigger | Time Relative Trigger — Automation Protocol | 43 / 57 | = before | | `references/automation/webhook.mdx` | Webhook | Webhook schema — Automation Protocol reference | 46 / 60 | = before | | `references/data/index.mdx` | Data Protocol | Data Protocol — complete schema reference | 41 / 55 | = before | | `references/data/analytics.mdx` | Analytics | Analytics schema — Data Protocol reference | 42 / 56 | = before | | `references/data/context-tokens.mdx` | Context Tokens | Context Tokens — Data Protocol reference | 40 / 54 | = before | | `references/data/data-engine.mdx` | Data Engine | Data Engine schema — Data Protocol reference | 44 / 58 | = before | | `references/data/datasource.mdx` | Datasource | Datasource schema — Data Protocol reference | 43 / 57 | = before | | `references/data/date-macros.mdx` | Date Macros | Date Macros schema — Data Protocol reference | 44 / 58 | = before | | `references/data/document.mdx` | Document | Document schema — Data Protocol reference | 41 / 55 | = before | | `references/data/driver-common.mdx` | Driver Common | Driver Common schema — Data Protocol reference | 46 / 60 | = before | | `references/data/driver-memory.mdx` | Driver Memory | Driver Memory schema — Data Protocol reference | 46 / 60 | = before | | `references/data/driver-mongo.mdx` | Driver Mongo | Driver Mongo schema — Data Protocol reference | 45 / 59 | = before | | `references/data/driver-mysql.mdx` | Driver Mysql | Driver Mysql schema — Data Protocol reference | 45 / 59 | = before | | `references/data/driver-nosql.mdx` | Driver Nosql | Driver Nosql schema — Data Protocol reference | 45 / 59 | = before | | `references/data/driver-postgres.mdx` | Driver Postgres | Driver Postgres — Data Protocol reference | 41 / 55 | = before | | `references/data/driver-sql.mdx` | Driver Sql | Driver Sql schema — Data Protocol reference | 43 / 57 | = before | | `references/data/driver-sqlite.mdx` | Driver Sqlite | Driver Sqlite schema — Data Protocol reference | 46 / 60 | = before | | `references/data/driver-turso.mdx` | Driver Turso | Driver Turso schema — Data Protocol reference | 45 / 59 | = before | | `references/data/driver.mdx` | Driver | Driver schema — Data Protocol reference | 39 / 53 | = before | | `references/data/external-catalog.mdx` | External Catalog | External Catalog — Data Protocol reference | 42 / 56 | = before | | `references/data/feed.mdx` | Feed | Feed schema — Data Protocol reference | 37 / 51 | = before | | `references/data/field-value.mdx` | Field Value | Field Value schema — Data Protocol reference | 44 / 58 | = before | | `references/data/field.mdx` | Field | Field schema — Data Protocol reference | 38 / 52 | = before | | `references/data/filter.mdx` | Filter | Filter schema — Data Protocol reference | 39 / 53 | = before | | `references/data/hook-body.mdx` | Hook Body | Hook Body schema — Data Protocol reference | 42 / 56 | = before | | `references/data/hook.mdx` | Hook | Hook schema — Data Protocol property reference | 46 / 60 | = before | | `references/data/mapping.mdx` | Mapping | Mapping schema — Data Protocol reference | 40 / 54 | = before | | `references/data/object.mdx` | Object | Object schema — Data Protocol reference | 39 / 53 | = before | | `references/data/query.mdx` | Query | Query schema — Data Protocol reference | 38 / 52 | = before | | `references/data/seed-loader.mdx` | Seed Loader | Seed Loader schema — Data Protocol reference | 44 / 58 | = before | | `references/data/seed.mdx` | Seed | Seed schema — Data Protocol property reference | 46 / 60 | = before | | `references/data/validation.mdx` | Validation | Validation schema — Data Protocol reference | 43 / 57 | = before | | `references/identity/index.mdx` | Identity Protocol | Identity Protocol — complete schema reference | 45 / 59 | = before | | `references/identity/eval-user.mdx` | Eval User | Eval User schema — Identity Protocol reference | 46 / 60 | = before | | `references/identity/identity.mdx` | Identity | Identity schema — Identity Protocol reference | 45 / 59 | = before | | `references/identity/organization.mdx` | Organization | Organization — Identity Protocol reference | 42 / 56 | = before | | `references/identity/position.mdx` | Position | Position schema — Identity Protocol reference | 45 / 59 | = before | | `references/identity/scim.mdx` | Scim | Scim schema — Identity Protocol reference | 41 / 55 | = before | | `references/integration/index.mdx` | Integration Protocol | Integration Protocol — schema reference | 39 / 53 | = before | | `references/integration/connector.mdx` | Connector | Connector — Integration Protocol reference | 42 / 56 | = before | | `references/kernel/index.mdx` | Kernel Protocol | Kernel Protocol — complete schema reference | 43 / 57 | = before | | `references/kernel/cli-extension.mdx` | Cli Extension | Cli Extension — Kernel Protocol reference | 41 / 55 | = before | | `references/kernel/cluster.mdx` | Cluster | Cluster schema — Kernel Protocol reference | 42 / 56 | = before | | `references/kernel/context.mdx` | Context | Context schema — Kernel Protocol reference | 42 / 56 | = before | | `references/kernel/dependency-resolution.mdx` | Dependency Resolution | Dependency Resolution — Kernel Protocol | 39 / 53 | = before | | `references/kernel/events-bus.mdx` | Events Bus | Events Bus schema — Kernel Protocol reference | 45 / 59 | = before | | `references/kernel/events-core.mdx` | Events Core | Events Core schema — Kernel Protocol reference | 46 / 60 | = before | | `references/kernel/events-dlq.mdx` | Events Dlq | Events Dlq schema — Kernel Protocol reference | 45 / 59 | = before | | `references/kernel/events-handlers.mdx` | Events Handlers | Events Handlers — Kernel Protocol reference | 43 / 57 | = before | | `references/kernel/events-integrations.mdx` | Events Integrations | Events Integrations — Kernel Protocol | 37 / 51 | = before | | `references/kernel/events-queue.mdx` | Events Queue | Events Queue — Kernel Protocol reference | 40 / 54 | = before | | `references/kernel/execution-context.mdx` | Execution Context | Execution Context — Kernel Protocol reference | 45 / 59 | = before | | `references/kernel/manifest.mdx` | Manifest | Manifest schema — Kernel Protocol reference | 43 / 57 | = before | | `references/kernel/metadata-loader.mdx` | Metadata Loader | Metadata Loader — Kernel Protocol reference | 43 / 57 | = before | | `references/kernel/metadata-plugin.mdx` | Metadata Plugin | Metadata Plugin — Kernel Protocol reference | 43 / 57 | = before | | `references/kernel/metadata-protection.mdx` | Metadata Protection | Metadata Protection — Kernel Protocol | 37 / 51 | = before | | `references/kernel/package-artifact.mdx` | Package Artifact | Package Artifact — Kernel Protocol reference | 44 / 58 | = before | | `references/kernel/package-registry.mdx` | Package Registry | Package Registry — Kernel Protocol reference | 44 / 58 | = before | | `references/kernel/package-upgrade.mdx` | Package Upgrade | Package Upgrade — Kernel Protocol reference | 43 / 57 | = before | | `references/kernel/plugin-capability.mdx` | Plugin Capability | Plugin Capability — Kernel Protocol reference | 45 / 59 | = before | | `references/kernel/plugin-lifecycle-advanced.mdx` | Plugin Lifecycle Advanced | Plugin Lifecycle Advanced — Kernel Protocol | 43 / 57 | = before | | `references/kernel/plugin-loading.mdx` | Plugin Loading | Plugin Loading — Kernel Protocol reference | 42 / 56 | = before | | `references/kernel/plugin-registry.mdx` | Plugin Registry | Plugin Registry — Kernel Protocol reference | 43 / 57 | = before | | `references/kernel/plugin-security-advanced.mdx` | Plugin Security Advanced | Plugin Security Advanced — Kernel Protocol | 42 / 56 | = before | | `references/kernel/plugin-security.mdx` | Plugin Security | Plugin Security — Kernel Protocol reference | 43 / 57 | = before | | `references/kernel/plugin-structure.mdx` | Plugin Structure | Plugin Structure — Kernel Protocol reference | 44 / 58 | = before | | `references/kernel/plugin-validator.mdx` | Plugin Validator | Plugin Validator — Kernel Protocol reference | 44 / 58 | = before | | `references/kernel/plugin-versioning.mdx` | Plugin Versioning | Plugin Versioning — Kernel Protocol reference | 45 / 59 | = before | | `references/kernel/plugin.mdx` | Plugin | Plugin schema — Kernel Protocol reference | 41 / 55 | = before | | `references/kernel/service-registry.mdx` | Service Registry | Service Registry — Kernel Protocol reference | 44 / 58 | = before | | `references/kernel/startup-orchestrator.mdx` | Startup Orchestrator | Startup Orchestrator — Kernel Protocol | 38 / 52 | = before | | `references/marketplace/index.mdx` | Marketplace Protocol | Marketplace Protocol — schema reference | 39 / 53 | = before | | `references/marketplace/marketplace.mdx` | Marketplace | Marketplace — Marketplace Protocol reference | 44 / 58 | = before | | `references/marketplace/package-version.mdx` | Package Version | Package Version — Marketplace Protocol | 38 / 52 | = before | | `references/marketplace/package.mdx` | Package | Package — Marketplace Protocol reference | 40 / 54 | = before | | `references/marketplace/template-manifest.mdx` | Template Manifest | Template Manifest — Marketplace Protocol | 40 / 54 | = before | | `references/qa/index.mdx` | QA Protocol | QA Protocol — complete schema reference | 39 / 53 | = before | | `references/qa/testing.mdx` | Testing | Testing schema — QA Protocol reference | 38 / 52 | = before | | `references/security/index.mdx` | Security Protocol | Security Protocol — complete schema reference | 45 / 59 | = before | | `references/security/explain.mdx` | Explain | Explain schema — Security Protocol reference | 44 / 58 | = before | | `references/security/misc.mdx` | Misc | Misc schema — Security Protocol reference | 41 / 55 | = before | | `references/security/permission.mdx` | Permission | Permission — Security Protocol reference | 40 / 54 | = before | | `references/security/rls.mdx` | Rls | Rls schema — Security Protocol reference | 40 / 54 | = before | | `references/security/sharing.mdx` | Sharing | Sharing schema — Security Protocol reference | 44 / 58 | = before | | `references/shared/index.mdx` | Shared Protocol | Shared Protocol — complete schema reference | 43 / 57 | = before | | `references/shared/duration.mdx` | Duration | Duration schema — Shared Protocol reference | 43 / 57 | = before | | `references/shared/enums.mdx` | Enums | Enums schema — Shared Protocol reference | 40 / 54 | = before | | `references/shared/epoch.mdx` | Epoch | Epoch schema — Shared Protocol reference | 40 / 54 | = before | | `references/shared/expression.mdx` | Expression | Expression schema — Shared Protocol reference | 45 / 59 | = before | | `references/shared/http.mdx` | Http | Http schema — Shared Protocol reference | 39 / 53 | = before | | `references/shared/identifiers.mdx` | Identifiers | Identifiers schema — Shared Protocol reference | 46 / 60 | = before | | `references/shared/mapping.mdx` | Mapping | Mapping schema — Shared Protocol reference | 42 / 56 | = before | | `references/shared/metadata-types.mdx` | Metadata Types | Metadata Types — Shared Protocol reference | 42 / 56 | = before | | `references/shared/protection.mdx` | Protection | Protection schema — Shared Protocol reference | 45 / 59 | = before | | `references/shared/value-domain.mdx` | Value Domain | Value Domain — Shared Protocol reference | 40 / 54 | = before | | `references/studio/index.mdx` | Studio Protocol | Studio Protocol — complete schema reference | 43 / 57 | = before | | `references/studio/flow-builder.mdx` | Flow Builder | Flow Builder — Studio Protocol reference | 40 / 54 | = before | | `references/studio/object-designer.mdx` | Object Designer | Object Designer — Studio Protocol reference | 43 / 57 | = before | | `references/studio/plugin.mdx` | Plugin | Plugin schema — Studio Protocol reference | 41 / 55 | = before | | `references/system/index.mdx` | System Protocol | System Protocol — complete schema reference | 43 / 57 | = before | | `references/system/app-install.mdx` | App Install | App Install schema — System Protocol reference | 46 / 60 | = before | | `references/system/auth-config.mdx` | Auth Config | Auth Config schema — System Protocol reference | 46 / 60 | = before | | `references/system/book.mdx` | Book | Book schema — System Protocol reference | 39 / 53 | = before | | `references/system/cache.mdx` | Cache | Cache schema — System Protocol reference | 40 / 54 | = before | | `references/system/collaboration.mdx` | Collaboration | Collaboration — System Protocol reference | 41 / 55 | = before | | `references/system/core-services.mdx` | Core Services | Core Services — System Protocol reference | 41 / 55 | = before | | `references/system/deploy-bundle.mdx` | Deploy Bundle | Deploy Bundle — System Protocol reference | 41 / 55 | = before | | `references/system/dev-login.mdx` | Dev Login | Dev Login schema — System Protocol reference | 44 / 58 | = before | | `references/system/disaster-recovery.mdx` | Disaster Recovery | Disaster Recovery — System Protocol reference | 45 / 59 | = before | | `references/system/doc.mdx` | Doc | Doc schema — System Protocol reference | 38 / 52 | = before | | `references/system/email-config.mdx` | Email Config | Email Config — System Protocol reference | 40 / 54 | = before | | `references/system/email-template.mdx` | Email Template | Email Template — System Protocol reference | 42 / 56 | = before | | `references/system/encryption.mdx` | Encryption | Encryption schema — System Protocol reference | 45 / 59 | = before | | `references/system/environment-artifact.mdx` | Environment Artifact | Environment Artifact — System Protocol | 38 / 52 | = before | | `references/system/http-server.mdx` | Http Server | Http Server schema — System Protocol reference | 46 / 60 | = before | | `references/system/job.mdx` | Job | Job schema — System Protocol reference | 38 / 52 | = before | | `references/system/license.mdx` | License | License schema — System Protocol reference | 42 / 56 | = before | | `references/system/logging.mdx` | Logging | Logging schema — System Protocol reference | 42 / 56 | = before | | `references/system/metadata-persistence.mdx` | Metadata Persistence | Metadata Persistence — System Protocol | 38 / 52 | = before | | `references/system/metrics.mdx` | Metrics | Metrics schema — System Protocol reference | 42 / 56 | = before | | `references/system/migration.mdx` | Migration | Migration schema — System Protocol reference | 44 / 58 | = before | | `references/system/notification.mdx` | Notification | Notification — System Protocol reference | 40 / 54 | = before | | `references/system/object-storage.mdx` | Object Storage | Object Storage — System Protocol reference | 42 / 56 | = before | | `references/system/registry-config.mdx` | Registry Config | Registry Config — System Protocol reference | 43 / 57 | = before | | `references/system/search-engine.mdx` | Search Engine | Search Engine — System Protocol reference | 41 / 55 | = before | | `references/system/security-context.mdx` | Security Context | Security Context — System Protocol reference | 44 / 58 | = before | | `references/system/settings-client.mdx` | Settings Client | Settings Client — System Protocol reference | 43 / 57 | = before | | `references/system/settings-manifest.mdx` | Settings Manifest | Settings Manifest — System Protocol reference | 45 / 59 | = before | | `references/system/stack-server.mdx` | Stack Server | Stack Server — System Protocol reference | 40 / 54 | = before | | `references/system/supplier-security.mdx` | Supplier Security | Supplier Security — System Protocol reference | 45 / 59 | = before | | `references/system/tenant.mdx` | Tenant | Tenant schema — System Protocol reference | 41 / 55 | = before | | `references/system/tracing.mdx` | Tracing | Tracing schema — System Protocol reference | 42 / 56 | = before | | `references/system/translation.mdx` | Translation | Translation schema — System Protocol reference | 46 / 60 | = before | | `references/system/worker.mdx` | Worker | Worker schema — System Protocol reference | 41 / 55 | = before | | `references/ui/index.mdx` | UI Protocol | UI Protocol — complete schema reference | 39 / 53 | = before | | `references/ui/action-params.mdx` | Action Params | Action Params schema — UI Protocol reference | 44 / 58 | = before | | `references/ui/action.mdx` | Action | Action schema — UI Protocol property reference | 46 / 60 | = before | | `references/ui/app.mdx` | App | App schema — UI Protocol property reference | 43 / 57 | = before | | `references/ui/bulk-action.mdx` | Bulk Action | Bulk Action schema — UI Protocol reference | 42 / 56 | = before | | `references/ui/chart.mdx` | Chart | Chart schema — UI Protocol property reference | 45 / 59 | = before | | `references/ui/component.mdx` | Component | Component schema — UI Protocol reference | 40 / 54 | = before | | `references/ui/dashboard.mdx` | Dashboard | Dashboard schema — UI Protocol reference | 40 / 54 | = before | | `references/ui/dataset.mdx` | Dataset | Dataset schema — UI Protocol reference | 38 / 52 | = before | | `references/ui/expression-bindable-text-keys.mdx` | Expression Bindable Text Keys | Expression Bindable Text Keys — UI Protocol | 43 / 57 | = before | | `references/ui/i18n.mdx` | I18n | I18n schema — UI Protocol property reference | 44 / 58 | = before | | `references/ui/notification.mdx` | Notification | Notification schema — UI Protocol reference | 43 / 57 | = before | | `references/ui/page.mdx` | Page | Page schema — UI Protocol property reference | 44 / 58 | = before | | `references/ui/report.mdx` | Report | Report schema — UI Protocol property reference | 46 / 60 | = before | | `references/ui/responsive.mdx` | Responsive | Responsive schema — UI Protocol reference | 41 / 55 | = before | | `references/ui/sharing.mdx` | Sharing | Sharing schema — UI Protocol reference | 38 / 52 | = before | | `references/ui/view.mdx` | View | View schema — UI Protocol property reference | 44 / 58 | = before | --- _Generated by [Claude Code](https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #12237
⛔ Draft on purpose. This awaits the maintainer's judgement on the rule — please do not merge or queue it. Titles are maintainer-voice copy, so the rule and the complete before/after table are below and the diff is deliberately four files.
TL;DR
Three things the card did not know, all re-derived rather than inherited:
content/docs/references/**(generated, docs content: the 38 generated reference pages still render two<h1>— the heading comes from a spec JSDoc header via build-docs.ts #12249) and 9 undercontent/docs/releases/**(CLAUDE.md hard stop, docs content: 4 pages under content/docs/releases/ still render two or three<h1>— three of them need a cascading demotion, not a mechanical one #12250). Both are barred, and together they are 55% of the population. The card's headline median of 14 is dominated by the pages nobody may edit here.titleis simultaneously the SERP<title>, the on-page<h1>, the sidebar nav label and thellms.txtheading. The card made settling this a precondition for mass-editing. It settles negative.Because of (2), this PR lands the rule only on the four pages where it demonstrably costs the navigation nothing, and leaves the other 176 as a table to be judged.
1. Re-derived statistics
The card's numbers came from the same session and the same method that produced #12236's wrong
205/129/76, so everything here was measured again — frontmatter-aware, reading only the leading---block so atitle:line inside a body code fence can never be counted.The card's own figures hold. A naive
grep '^title:'over whole files returnsn=405 median=14 max=50 <=20: 327; the frontmatter-aware read returnsn=403 median=14 max=50 <=20: 325— exactly the card. The two extra hits aretitle:lines inside YAML fences. Unlike #12236, the method error did not move the headline. That is reported as a null result rather than dressed up as a catch.What the card's framing does hide is the split:
content/docs/**/*.mdx(the card)references/**— generated, #12249releases/**— hard stop, #12250Corroborated independently by two gates on this branch:
check-doc-frontmatterreportscontent/docs 403, andcheck-docs-single-h1reports180 page(s) under content/docs/ (2 subtree(s) excluded)— the same 180.Other measured facts:
protocol/objectui/record-alert.mdxat 64. Fixed in this PR.X — Yand 3 useX: Y.2. The sidebar-label finding (the card's stated precondition)
Checked against this repo's actual configuration (
fumadocs-core@16.14.4,apps/docs/source.config.ts,apps/docs/lib/source.ts). The answer is no.<title>page.data.title+%s | ObjectStackapp/[lang]/docs/[[...slug]]/page.tsxgenerateMetadata;app/layout.tsxmetadata.title.template<h1>page.data.title<DocsTitle>{page.data.title}</DocsTitle>page.data.titlefumadocs-corepage-tree builder,buildFile():name: title ?? pathToName(basename(path, extname(path)))llms.txtheadingpage.data.titleapps/docs/lib/source.tsgetLLMText():`# ${page.data.title}`One string, four consumers. And no second field exists to split them:
pageSchema(fumadocs-core/dist/source/schema.js) declares exactlytitle,description,icon,full,_openapi— and compiles toz.core.$strip, so an inventedsidebarTitle:in frontmatter is silently dropped, not rejected.source.config.tsusespageSchemaunextended (the blog collection does extend it, so the mechanism exists and is simply unused for docs).meta.json(metaSchema) has atitle, butbuildFolder()uses it for the folder's own label (node.name = metadata.title ?? node.index?.name). It cannot name a child page.meta.jsonpagesdoes accept a[Label](url)form, butresolveLink()emits a bare link node with no$ref— it dropsdescription,iconand the page↔tree binding, and the real page would need!-excluding to avoid appearing twice. That is a link mechanism, not a label mechanism.The supported fix is code, roughly ten lines:
pageSchema.extend({ sidebarTitle: z.string().optional() })inapps/docs/source.config.ts, plus apageTree.transformersentry inapps/docs/lib/source.tswhosefile(node)prefers it. That is inside epic #12243's territory but outside this card's declared file surface, andapps/docs/lib/source.tsis contended by cards in flight — so it is filed rather than taken here.The three-page exemption this PR uses instead
buildFolder()sets a folder's label tometadata.title ?? node.index?.name, and excludes the index page fromchildrenunlessmeta.jsonpagesnames"index"explicitly. So for a folder whosemeta.jsoncarries atitleand whosepagesomits"index", that index page's frontmatter title never appears in the sidebar.Enumerated across all 19 editable folders: exactly three pages qualify. 16 folders list
"index"inpages, which puts the page in the tree as an ordinary child.content/docs/index.mdxDocumentationisRoot, and rootmeta.jsonpagesomits it)content/docs/protocol/objectql/index.mdxObjectQL: The Data Protocol"Data Protocol"fromprotocol/objectql/meta.jsoncontent/docs/protocol/objectui/index.mdxObjectUI: The UI Protocol"UI Protocol"fromprotocol/objectui/meta.jsonThose last two are worth noticing on their own: the repo already demonstrates the split the card is asking for — a short nav label beside a longer page title — using the one mechanism that happens to work for folder index pages.
3. The proposed rule
| ObjectStacksuffix the rendered title lands in the 50–60 band. (The card said "50–60 including the suffix" but wrote its examples to 50–60 excluding it;ObjectStack documentation: metadata-driven app frameworkis 70 rendered andObject metadata: define objects, fields and relationshipsis 71. OnlyExpose actions as MCP tools for AI agents, at 55, obeys the box.)—, matching the rule as written and the 4 titles already using it. Where the qualifier is a genuine restatement,:also reads fine; the table uses—throughout for one pattern.getting-started/index.mdxkeepsWhat is ObjectStack?because the product name is the search query for that page.AI-written/AI-authored/AI-generatedonly, no fourth spelling.AI-writtenis the anchor for titles because the homepage title landed in docs(site): lead the homepage title with the category, cut the 614-char description to 152 #12284 asMetadata framework for AI-written apps.AI-builtis not zero. It appears 35 times across 18 files repo-wide (packages/objectql,packages/metadata-protocol,docs/adr, …). It is zero undercontent/docs/**andapps/docs/**, which is presumably the surface that was measured. The ruling still stands — no fourth spelling in titles — but it stands on taste, not on absence.Applied to all 180 rows the rule yields: median 54, min 50, max 60, 180/180 in the 50–60 band, zero over 60, zero duplicates.
4. What this PR actually changes: 4 files
Every file in the diff either has no sidebar exposure or shortens its sidebar label. Zero navigation regression, by construction rather than by judgement.
content/docs/index.mdxDocumentationDocumentation — build apps from metadatacontent/docs/protocol/objectql/index.mdxObjectQL: The Data ProtocolObjectQL — the data protocol specificationmeta.jsontitle winscontent/docs/protocol/objectui/index.mdxObjectUI: The UI ProtocolObjectUI — the UI protocol specificationmeta.jsontitle winscontent/docs/protocol/objectui/record-alert.mdxrecord:alert — Conditional Banners on Record Pagesrecord:alert — banners on record pagesEvery file is
1+/1-. Frontmatterdescriptionis untouched (#12238's card, same block, same files). Nothing underreferences/**orreleases/**.content/docs/index.mdxalso takes the free input from #12236: its demoted## ObjectStack Documentationconfirmed the page had a better wording available thanDocumentation.5. Acceptance boxes
pnpm check:doc-anchorsgreen —check-doc-anchors: 278 internal #fragment link(s) across 408 source file(s) all resolve to a real heading6. Rule applied to all 180 — the complete before/after table
Rows: 180. Lengths include the
| ObjectStacksuffix (14 chars) the root layout appends.✅marks the 4 rows this PR actually lands; the rest are proposals awaiting the ruling.content/docs/ai/actions-as-tools.mdxagents.mdxconnect-mcp.mdxindex.mdxknowledge-rag.mdxnatural-language-queries.mdxskills-reference.mdxskills.mdxtools.mdxcontent/docs/api/client-sdk.mdxdata-api.mdxdata-flow.mdxdeclarative-endpoints.mdxenvironment-routing.mdxerror-catalog.mdxerror-handling-client.mdxerror-handling-server.mdxindex.mdxmetadata-api.mdxplugin-endpoints.mdxwire-format.mdxcontent/docs/automation/approvals.mdxconnectors.mdxemail-templates.mdxflows.mdxhook-bodies.mdxhooks.mdxindex.mdxjobs.mdxwebhooks.mdxworkflows.mdxcontent/docs/build-without-code.mdxcontent/docs/capabilities/ai.mdxanalytics.mdxapprovals.mdxautomation.mdxdata.mdxforms.mdxindex.mdxintegrations.mdxpermissions.mdxrequest-template.mdxviews.mdxcontent/docs/concepts/architecture.mdxdesign-principles.mdxindex.mdxmetadata-driven.mdxmetadata-lifecycle.mdxnorth-star.mdxcontent/docs/data-modeling/analytics.mdxdrivers.mdxexternal-datasources.mdxfield-type-decision-tree.mdxfield-types.mdxfields.mdxformulas.mdximport-mappings.mdxindex.mdxindexing.mdxobject-extensions.mdxobjects.mdxqueries.mdxrelationships.mdxschema-design.mdxseed-data.mdxvalidation-rules.mdxvalidation.mdxcontent/docs/deployment/backup-restore.mdxcli.mdxenvironment-variables.mdxindex.mdxproduction-readiness.mdxpublish-and-preview.mdxseed-tenancy-repair.mdxself-hosting.mdxsingle-project-mode.mdxtenancy-modes.mdxtroubleshooting.mdxvalidating-metadata.mdxcontent/docs/getting-started/build-with-claude-code.mdxcommon-patterns.mdxexamples.mdxglossary.mdxhow-ai-development-works.mdxindex.mdxquick-reference.mdxquick-start.mdxyour-first-project.mdxcontent/docs/index.mdx✅content/docs/kernel/architecture.mdxcluster.mdxcontent/docs/kernel/contracts/auth-service.mdxcache-service.mdxdata-engine.mdxindex.mdxmetadata-service.mdxstorage-service.mdxcontent/docs/kernel/events.mdxindex.mdxcontent/docs/kernel/runtime-services/audit-service.mdxdata-service.mdxemail-service.mdxexamples.mdxindex.mdxqueue-service.mdxsettings-service.mdxsharing-service.mdxsms-service.mdxstorage-service.mdxversioning.mdxcontent/docs/kernel/services-checklist.mdxservices.mdxcontent/docs/permissions/access-matrix.mdxaccess-recipes.mdxadministrator-guide.mdxattachments-access.mdxauthentication.mdxauthorization.mdxcapabilities.mdxdelegated-administration.mdxexplain.mdxfield-level-security.mdxindex.mdxpermission-metadata.mdxpermission-sets.mdxpermissions-matrix.mdxpositions.mdxprofiles.mdxrecord-view-auditing.mdxrls.mdxsharing-rules.mdxsso.mdxsystem-context.mdxcontent/docs/plugins/adding-a-metadata-type.mdxanatomy.mdxdevelopment.mdxindex.mdxpackages.mdxcontent/docs/protocol/backward-compatibility.mdxdiagram.mdxindex.mdxcontent/docs/protocol/kernel/config-resolution.mdxerror-handling.mdxhttp-protocol.mdxi18n-standard.mdxindex.mdxlifecycle.mdxmetadata-service.mdxplugin-spec.mdxrealtime-protocol.mdxcontent/docs/protocol/knowledge.mdxcontent/docs/protocol/objectql/index.mdx✅query-syntax.mdxschema.mdxsecurity.mdxstate-machine.mdxtypes.mdxcontent/docs/protocol/objectui/actions.mdxconcept.mdxindex.mdx✅layout-dsl.mdxrecord-alert.mdx✅widget-contract.mdxcontent/docs/ui/actions.mdxapps.mdxaudience-based-interfaces.mdxcreate-vs-edit-form.mdxdashboards.mdxdoc-pages.mdxfield-grouping-and-order.mdxforms.mdxindex.mdxpages.mdxpublic-data-collection.mdxreact-pages.mdxreports.mdxsetup-app.mdxtranslations.mdxviews.mdxcontent/docs/upgrading.mdxVerification
All at pushed head
ed3afdfda,git status --porcelainempty.Gate family re-derived from the actual diff, not from the dispatch list:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(4 paths vs merge base, three-dot). Its first run warnedSTALE TREE — 3 commit(s) behind origin/mainand named.github/workflows/lint.ymlas changed in that range, soorigin/mainwas merged in and the derivation re-run before anything was trusted.25 gate families run, every exit code captured before any pipe (each redirected to its own file,
$?read immediately), allexit=0, re-run in full at the final commited3afdfda:check:doc-anchors·check:doc-authoring·check:docs-audit-scope·check:docs-redirects·check:docs-single-h1·check:published-readme-links·check:react-page-adapter-contract·check:release-notes·check:role-word·check:cross-package-test-inputs·check:nul-bytes·check-ci-filter-parity·check-cross-package-test-inputs·check-doc-frontmatter·check-doc-route-spelling·check-docs-section-name·check-section-landing-index·@objectstack/lint check:doc-formula-expressions·@objectstack/lint check:doc-security-posture·@objectstack/spec check:docs·@objectstack/spec check:skill-examples·@objectstack/spec check:empty-state·@objectstack/spec check:liveness·@objectstack/spec check:strictness-ledger·@objectstack/spec check:variant-docsVerdict lines quoted rather than exit codes:
✅ check-doc-anchors: 278 internal #fragment link(s) across 408 source file(s) all resolve to a real heading✓ check-docs-single-h1: 180 page(s) under content/docs/ carry no body-level#heading (2 subtree(s) excluded)✓ check-doc-frontmatter: 2 content root(s) verified, each against its own floor — content/docs 403, content/blog 3.exit=1and are NOT recorded as failures — each printedPREREQUISITE NOT METor a missing-build-artifact banner and, in the gates' own words, "Nothing was measured … It is NOT a finding." They were re-run green afterpnpm exec turbo run build --filter=@objectstack/{formula,lint,spec,client,client-react}andpnpm --filter @objectstack/spec gen:schema. Recorded here so the first reading is not mistaken for a red that got quietly dropped.Two declared narrowings:
os-verify-lock.shran UNLOCKED. Every heavy command went through the entry point, which reports on this host:VERDICT command-exit 0 · UNLOCKED (declared) · no usableflockon this host, so the shared verify lock was NEVER taken and NOTHING was serialized · declare it in the PR body. macOS ships noflock; the script's own disclosure is pasted rather than paraphrased.pnpm lintnarrowed to the diff, with all three required measurements:eslint --print-config content/docs/index.mdxprintsundefined—.mdxis outside the lint population, and that is ESLint's answer, not an assumption;--format json: 4 files, 0 errors, 4 warnings, and all four warnings areFile ignored because no matching configuration was supplied;eslint.config.mjsstates at line 328 that this repo "never enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for ANY file", so a frontmatter change cannot move any untouched file's verdict.No changeset — docs content only, publishing nothing.
skip-changesetapplied.Out of scope, filed
apps/docschange that would unblock the remaining 176 rows of the table above.Generated by Claude Code