From 0a83c42e561c386cac64fa841632e17b2c2d0ca2 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Tue, 25 Aug 2026 23:01:53 +0800 Subject: [PATCH 1/2] docs(content): drop the duplicate body H1 from 153 hand-written pages DocsTitle renders the frontmatter title as the page h1, so a body-level '# ' heading makes the page ship a second one. 137 pages repeated the title verbatim (heading deleted); 16 said something different (demoted to '## ', which keeps the wording and keeps the slug). Co-Authored-By: Claude Opus 5 --- content/docs/ai/actions-as-tools.mdx | 2 -- content/docs/ai/agents.mdx | 2 -- content/docs/ai/connect-mcp.mdx | 2 -- content/docs/ai/index.mdx | 2 -- content/docs/ai/knowledge-rag.mdx | 2 -- content/docs/ai/natural-language-queries.mdx | 2 -- content/docs/ai/skills-reference.mdx | 2 -- content/docs/ai/skills.mdx | 2 -- content/docs/ai/tools.mdx | 2 -- content/docs/api/client-sdk.mdx | 2 -- content/docs/api/data-api.mdx | 2 -- content/docs/api/data-flow.mdx | 2 -- content/docs/api/declarative-endpoints.mdx | 2 -- content/docs/api/environment-routing.mdx | 2 -- content/docs/api/error-catalog.mdx | 2 -- content/docs/api/error-handling-client.mdx | 2 -- content/docs/api/error-handling-server.mdx | 2 -- content/docs/api/index.mdx | 2 -- content/docs/api/metadata-api.mdx | 2 -- content/docs/api/plugin-endpoints.mdx | 2 -- content/docs/api/wire-format.mdx | 2 -- content/docs/automation/approvals.mdx | 2 -- content/docs/automation/connectors.mdx | 2 -- content/docs/automation/email-templates.mdx | 2 -- content/docs/automation/flows.mdx | 2 -- content/docs/automation/hook-bodies.mdx | 2 +- content/docs/automation/hooks.mdx | 2 -- content/docs/automation/index.mdx | 2 -- content/docs/automation/jobs.mdx | 2 -- content/docs/automation/webhooks.mdx | 2 -- content/docs/automation/workflows.mdx | 2 -- content/docs/concepts/architecture.mdx | 2 +- content/docs/concepts/design-principles.mdx | 2 -- content/docs/concepts/index.mdx | 2 -- content/docs/concepts/metadata-driven.mdx | 2 +- content/docs/concepts/metadata-lifecycle.mdx | 2 -- content/docs/concepts/north-star.mdx | 2 -- content/docs/data-modeling/analytics.mdx | 2 -- content/docs/data-modeling/drivers.mdx | 2 -- content/docs/data-modeling/external-datasources.mdx | 2 -- content/docs/data-modeling/field-type-decision-tree.mdx | 2 -- content/docs/data-modeling/field-types.mdx | 2 -- content/docs/data-modeling/fields.mdx | 2 -- content/docs/data-modeling/formulas.mdx | 2 +- content/docs/data-modeling/import-mappings.mdx | 2 -- content/docs/data-modeling/index.mdx | 2 -- content/docs/data-modeling/indexing.mdx | 2 -- content/docs/data-modeling/object-extensions.mdx | 2 -- content/docs/data-modeling/objects.mdx | 2 -- content/docs/data-modeling/queries.mdx | 2 -- content/docs/data-modeling/relationships.mdx | 2 -- content/docs/data-modeling/schema-design.mdx | 2 -- content/docs/data-modeling/seed-data.mdx | 2 -- content/docs/data-modeling/validation-rules.mdx | 2 -- content/docs/data-modeling/validation.mdx | 2 -- content/docs/deployment/backup-restore.mdx | 2 -- content/docs/deployment/cli.mdx | 2 +- content/docs/deployment/environment-variables.mdx | 2 -- content/docs/deployment/index.mdx | 2 -- content/docs/deployment/production-readiness.mdx | 2 -- content/docs/deployment/publish-and-preview.mdx | 2 -- content/docs/deployment/seed-tenancy-repair.mdx | 2 -- content/docs/deployment/self-hosting.mdx | 2 -- content/docs/deployment/single-project-mode.mdx | 2 -- content/docs/deployment/tenancy-modes.mdx | 2 -- content/docs/deployment/troubleshooting.mdx | 2 -- content/docs/deployment/validating-metadata.mdx | 2 -- content/docs/getting-started/build-with-claude-code.mdx | 2 -- content/docs/getting-started/common-patterns.mdx | 2 +- content/docs/getting-started/examples.mdx | 2 -- content/docs/getting-started/how-ai-development-works.mdx | 2 -- content/docs/getting-started/quick-reference.mdx | 2 -- content/docs/getting-started/quick-start.mdx | 2 -- content/docs/getting-started/your-first-project.mdx | 2 -- content/docs/index.mdx | 2 +- content/docs/kernel/architecture.mdx | 2 +- content/docs/kernel/cluster.mdx | 2 -- content/docs/kernel/contracts/auth-service.mdx | 2 -- content/docs/kernel/contracts/cache-service.mdx | 2 -- content/docs/kernel/contracts/data-engine.mdx | 2 -- content/docs/kernel/contracts/index.mdx | 2 -- content/docs/kernel/contracts/metadata-service.mdx | 2 -- content/docs/kernel/contracts/storage-service.mdx | 2 -- content/docs/kernel/events.mdx | 2 -- content/docs/kernel/index.mdx | 2 -- content/docs/kernel/runtime-services/audit-service.mdx | 2 -- content/docs/kernel/runtime-services/data-service.mdx | 2 -- content/docs/kernel/runtime-services/email-service.mdx | 2 -- content/docs/kernel/runtime-services/examples.mdx | 2 -- content/docs/kernel/runtime-services/index.mdx | 2 -- content/docs/kernel/runtime-services/queue-service.mdx | 2 -- content/docs/kernel/runtime-services/settings-service.mdx | 2 -- content/docs/kernel/runtime-services/sharing-service.mdx | 2 -- content/docs/kernel/runtime-services/sms-service.mdx | 2 -- content/docs/kernel/runtime-services/storage-service.mdx | 2 -- content/docs/kernel/runtime-services/versioning.mdx | 2 -- content/docs/kernel/services-checklist.mdx | 2 -- content/docs/kernel/services.mdx | 2 -- content/docs/permissions/access-matrix.mdx | 2 -- content/docs/permissions/access-recipes.mdx | 2 -- content/docs/permissions/administrator-guide.mdx | 2 -- content/docs/permissions/attachments-access.mdx | 2 -- content/docs/permissions/authentication.mdx | 2 +- content/docs/permissions/authorization.mdx | 2 -- content/docs/permissions/capabilities.mdx | 2 -- content/docs/permissions/delegated-administration.mdx | 2 -- content/docs/permissions/explain.mdx | 2 -- content/docs/permissions/field-level-security.mdx | 2 -- content/docs/permissions/index.mdx | 2 -- content/docs/permissions/permission-metadata.mdx | 2 -- content/docs/permissions/permission-sets.mdx | 2 -- content/docs/permissions/permissions-matrix.mdx | 2 -- content/docs/permissions/positions.mdx | 2 -- content/docs/permissions/profiles.mdx | 2 +- content/docs/permissions/record-view-auditing.mdx | 2 -- content/docs/permissions/rls.mdx | 2 -- content/docs/permissions/sharing-rules.mdx | 2 +- content/docs/permissions/sso.mdx | 2 -- content/docs/permissions/system-context.mdx | 2 -- content/docs/plugins/adding-a-metadata-type.mdx | 2 -- content/docs/plugins/anatomy.mdx | 2 -- content/docs/plugins/development.mdx | 2 +- content/docs/plugins/index.mdx | 2 -- content/docs/plugins/packages.mdx | 2 -- content/docs/protocol/backward-compatibility.mdx | 2 -- content/docs/protocol/diagram.mdx | 2 -- content/docs/protocol/index.mdx | 2 -- content/docs/protocol/kernel/config-resolution.mdx | 2 -- content/docs/protocol/kernel/error-handling.mdx | 2 -- content/docs/protocol/kernel/http-protocol.mdx | 2 -- content/docs/protocol/kernel/i18n-standard.mdx | 2 +- content/docs/protocol/kernel/lifecycle.mdx | 2 -- content/docs/protocol/kernel/plugin-spec.mdx | 2 -- content/docs/protocol/kernel/realtime-protocol.mdx | 2 -- content/docs/protocol/knowledge.mdx | 2 -- content/docs/protocol/objectql/security.mdx | 2 +- content/docs/protocol/objectql/state-machine.mdx | 2 +- content/docs/ui/actions.mdx | 2 -- content/docs/ui/apps.mdx | 2 -- content/docs/ui/audience-based-interfaces.mdx | 2 -- content/docs/ui/create-vs-edit-form.mdx | 2 -- content/docs/ui/dashboards.mdx | 2 -- content/docs/ui/doc-pages.mdx | 2 -- content/docs/ui/field-grouping-and-order.mdx | 2 -- content/docs/ui/forms.mdx | 2 +- content/docs/ui/index.mdx | 2 -- content/docs/ui/pages.mdx | 2 -- content/docs/ui/public-data-collection.mdx | 2 -- content/docs/ui/react-pages.mdx | 2 -- content/docs/ui/reports.mdx | 2 -- content/docs/ui/setup-app.mdx | 2 -- content/docs/ui/translations.mdx | 2 -- content/docs/ui/views.mdx | 2 -- 153 files changed, 16 insertions(+), 290 deletions(-) diff --git a/content/docs/ai/actions-as-tools.mdx b/content/docs/ai/actions-as-tools.mdx index 1396e2ae9e..8b937dbc97 100644 --- a/content/docs/ai/actions-as-tools.mdx +++ b/content/docs/ai/actions-as-tools.mdx @@ -3,8 +3,6 @@ title: Actions as Tools description: Expose declarative Action metadata as AI-callable tools with explicit opt-in, HITL approval, and permission-aware execution --- -# Actions as Tools - Part of the [AI module](/docs/ai) — how existing Action metadata becomes LLM-callable, and the guardrails around it. This is what makes the app you built **AI-operable**: connect an MCP client (Claude Code, Cursor, …) and your business Actions become callable tools — so an agent can "resolve this ticket" or "convert this lead" through the same logic and permissions as the Console button. diff --git a/content/docs/ai/agents.mdx b/content/docs/ai/agents.mdx index fbd1c5fe7e..d2b0540c43 100644 --- a/content/docs/ai/agents.mdx +++ b/content/docs/ai/agents.mdx @@ -3,8 +3,6 @@ title: AI Agents description: The two platform agents (ask and build), how skills extend them, and the shape of an AgentSchema record --- -# AI Agents - Part of the [AI module](/docs/ai). In the **open edition**, agents, tools, and skills are **typed metadata**. You author **skills** (`defineSkill`) — agents are platform-owned and tool records are optional (see below) — from the open diff --git a/content/docs/ai/connect-mcp.mdx b/content/docs/ai/connect-mcp.mdx index a79af08779..3a970420d3 100644 --- a/content/docs/ai/connect-mcp.mdx +++ b/content/docs/ai/connect-mcp.mdx @@ -3,8 +3,6 @@ title: Connect an MCP Client description: Point Claude Code, Claude Desktop, or any MCP client at your running app — OAuth or API key — and verify the agent can see and operate it. --- -# Connect an MCP Client - Every ObjectStack deployment is already an MCP server. The runtime serves the [Model Context Protocol](https://modelcontextprotocol.io) at **`/api/v1/mcp`** — on by default, no plugin to install, no configuration step. Your objects and diff --git a/content/docs/ai/index.mdx b/content/docs/ai/index.mdx index 033f42ed4d..cf508fea1e 100644 --- a/content/docs/ai/index.mdx +++ b/content/docs/ai/index.mdx @@ -3,8 +3,6 @@ title: AI Overview description: Complete guide to leveraging AI agents, RAG pipelines, and intelligent automation in ObjectStack --- -# AI Overview - AI in ObjectStack is a **cross-protocol capability layer**: agents, tools, and knowledge retrieval sit on top of the same objects, actions, permissions, and automation that power the rest of the platform. This module covers the architecture and each of its moving parts. diff --git a/content/docs/ai/knowledge-rag.mdx b/content/docs/ai/knowledge-rag.mdx index d8946214ae..1a35b357f8 100644 --- a/content/docs/ai/knowledge-rag.mdx +++ b/content/docs/ai/knowledge-rag.mdx @@ -3,8 +3,6 @@ title: Knowledge & RAG description: The Knowledge Protocol — permission-aware RAG for agents via pluggable adapter plugins (memory, RAGFlow, custom) --- -# Knowledge & RAG - Part of the [AI module](/docs/ai) — how agents retrieve knowledge through the Knowledge Protocol and its adapter plugins. ObjectStack ships a Knowledge Protocol that lets you retrieve from pluggable backends (RAGFlow, LlamaIndex, Dify, custom pgvector, …) with one call: `KnowledgeService.search(query, { sourceIds?, topK? })`. The framework defines the contract and runs permission-aware filtering; the adapter plugin does the actual retrieval. See [the protocol design](/docs/protocol/knowledge) for the rationale. diff --git a/content/docs/ai/natural-language-queries.mdx b/content/docs/ai/natural-language-queries.mdx index 7d9f3aea4a..4a55e1f2a1 100644 --- a/content/docs/ai/natural-language-queries.mdx +++ b/content/docs/ai/natural-language-queries.mdx @@ -3,8 +3,6 @@ title: Natural Language Queries description: How agents query live data through the open MCP data tools (query_records, get_record, aggregate_records) under RLS --- -# Natural Language Queries - Part of the [AI module](/docs/ai) — how natural-language questions become ObjectQL queries at runtime. Natural-language querying is **open**. Point your own AI — Claude, Cursor, any diff --git a/content/docs/ai/skills-reference.mdx b/content/docs/ai/skills-reference.mdx index 39f6307afc..657b24c9bb 100644 --- a/content/docs/ai/skills-reference.mdx +++ b/content/docs/ai/skills-reference.mdx @@ -3,8 +3,6 @@ title: AI Skills Reference description: Reference for every ObjectStack AI skill — domain-scoped knowledge modules that teach AI assistants the protocol's schemas and constraints --- -# AI Skills Reference - ObjectStack ships a set of **domain-specific skills** that teach AI assistants (Claude Code, GitHub Copilot, Cursor, …) the protocol's schemas, patterns, and constraints — plus **process** skills that teach how work moves through a project rather than what to author. This guide is the complete catalog. diff --git a/content/docs/ai/skills.mdx b/content/docs/ai/skills.mdx index 5a32d81dfd..d3ad01a399 100644 --- a/content/docs/ai/skills.mdx +++ b/content/docs/ai/skills.mdx @@ -5,8 +5,6 @@ description: How ObjectStack uses structured AI skills to enable intelligent cod import { Bot, Brain, Cpu, Database, Globe, Layout, Shield, Workflow, Wrench, Languages, Zap, Puzzle, BookOpen, Target, ArrowRight } from 'lucide-react'; -# AI Skills System - ObjectStack introduces a **Skills System** — structured, domain-specific knowledge modules that enable AI assistants (GitHub Copilot, Claude Code, Cursor, etc.) to understand and generate protocol-compliant code. Skills are what make [building with Claude Code](/docs/getting-started/build-with-claude-code) reliable: they teach the agent the protocol's rules up front, so it authors correct metadata instead of guessing. diff --git a/content/docs/ai/tools.mdx b/content/docs/ai/tools.mdx index eeb58b35f1..8936bb74d9 100644 --- a/content/docs/ai/tools.mdx +++ b/content/docs/ai/tools.mdx @@ -3,8 +3,6 @@ title: Tool Records description: The three ways a capability reaches an agent, and the narrow case where authoring a tool record is the right answer --- -# Tool Records - Part of the [AI module](/docs/ai). `tool` is an authorable metadata kind — `ToolSchema`, declared as `defineStack({ tools })` — and it is the **least likely** answer to "how do I give my agent a new capability". diff --git a/content/docs/api/client-sdk.mdx b/content/docs/api/client-sdk.mdx index dd20c154a9..e5309f72f3 100644 --- a/content/docs/api/client-sdk.mdx +++ b/content/docs/api/client-sdk.mdx @@ -3,8 +3,6 @@ title: Client SDK description: The official TypeScript client for ObjectStack — auto-discovery, typed metadata, CRUD, batch operations, and service-aware feature detection. --- -# Client SDK - The `@objectstack/client` is the official TypeScript client for ObjectStack. It provides a typed, protocol-aware interface that automatically adapts to your server's available services. ## Features diff --git a/content/docs/api/data-api.mdx b/content/docs/api/data-api.mdx index cc6698d0ed..4e76c19bf8 100644 --- a/content/docs/api/data-api.mdx +++ b/content/docs/api/data-api.mdx @@ -3,8 +3,6 @@ title: Data API description: REST endpoints for CRUD, batch operations, record cloning, and analytics queries. --- -# Data API - Record CRUD, batch operations, and analytics queries over REST. All paths are relative to the base URL (defaults to `/api/v1`) — see the [API Overview](/docs/api) for discovery and service availability. ## Data Operations diff --git a/content/docs/api/data-flow.mdx b/content/docs/api/data-flow.mdx index f18e8ef9f7..99b9b3f93b 100644 --- a/content/docs/api/data-flow.mdx +++ b/content/docs/api/data-flow.mdx @@ -3,8 +3,6 @@ title: Data Flow Diagrams description: How data moves through the ObjectStack system — from defineStack() configuration to database queries, realtime events, and metadata resolution --- -# Data Flow Diagrams - This guide provides detailed diagrams showing how data moves through every layer of the ObjectStack system — from developer configuration to database operations and back to the client. diff --git a/content/docs/api/declarative-endpoints.mdx b/content/docs/api/declarative-endpoints.mdx index 9522f836a1..f843e479dc 100644 --- a/content/docs/api/declarative-endpoints.mdx +++ b/content/docs/api/declarative-endpoints.mdx @@ -3,8 +3,6 @@ title: Declarative Endpoints description: "Expose your app to systems outside the platform by declaring an apis: endpoint as metadata — which channel to pick, the four publish gates, and the obligation that comes with an anonymous endpoint." --- -# Declarative Endpoints - A stack can publish an HTTP endpoint as **metadata** instead of writing a handler: a URL, a method, a policy block, and the pipeline it delegates to. That is the `apis:` block of `defineStack`, and its entries are **live from protocol 17** — an endpoint that passes its diff --git a/content/docs/api/environment-routing.mdx b/content/docs/api/environment-routing.mdx index 09c3ecdfee..1c26bca997 100644 --- a/content/docs/api/environment-routing.mdx +++ b/content/docs/api/environment-routing.mdx @@ -3,8 +3,6 @@ title: Environment-Scoped Routing description: Route REST, metadata, automation, AI, and package calls through /api/v1/environments/:environmentId/.... --- -# Environment-Scoped Routing - Environment-scoped routing makes the target runtime explicit in the URL: ```text diff --git a/content/docs/api/error-catalog.mdx b/content/docs/api/error-catalog.mdx index de607632f4..bf5911fc17 100644 --- a/content/docs/api/error-catalog.mdx +++ b/content/docs/api/error-catalog.mdx @@ -3,8 +3,6 @@ title: Error Code Catalog description: Complete reference for all ObjectStack error codes with causes, fixes, and retry strategies --- -# Error Code Catalog - ObjectStack uses a structured error system with **9 error categories** and **50 standardized error codes**. Every error includes a machine-readable code, HTTP status mapping, and retry guidance. diff --git a/content/docs/api/error-handling-client.mdx b/content/docs/api/error-handling-client.mdx index 0f02b2c2eb..bf57473e94 100644 --- a/content/docs/api/error-handling-client.mdx +++ b/content/docs/api/error-handling-client.mdx @@ -3,8 +3,6 @@ title: Client-Side Error Handling description: Best practices for handling ObjectStack errors in client applications — parsing, display, retry strategies, and React patterns --- -# Client-Side Error Handling - This guide covers best practices for handling ObjectStack API errors in client applications — from parsing error responses to displaying validation errors in forms and implementing retry strategies. diff --git a/content/docs/api/error-handling-server.mdx b/content/docs/api/error-handling-server.mdx index 43dc0d6461..c17356122b 100644 --- a/content/docs/api/error-handling-server.mdx +++ b/content/docs/api/error-handling-server.mdx @@ -3,8 +3,6 @@ title: Server-Side Error Handling description: Best practices for handling and throwing errors in ObjectStack plugins — custom errors, hook propagation, validation, transactions, and safeParsePretty --- -# Server-Side Error Handling - This guide covers best practices for handling and throwing errors within ObjectStack plugins and server-side code — from creating custom errors with proper codes to transaction rollback and integration with `safeParsePretty()`. diff --git a/content/docs/api/index.mdx b/content/docs/api/index.mdx index d509e666d7..6e2b236923 100644 --- a/content/docs/api/index.mdx +++ b/content/docs/api/index.mdx @@ -3,8 +3,6 @@ title: API Overview description: How ObjectStack generates its API surface from metadata — discovery, error handling wire formats, protocol types, and a map of the API & SDK docs. --- -# API Overview - ObjectStack generates its entire API surface — REST endpoints, realtime protocols, and the client SDK — from your metadata: define an object once and its endpoints exist. This module documents those surfaces and how to consume them. ObjectStack exposes a fully typed REST API. All endpoints use JSON request/response bodies. The API is **service-driven** — routes are only available when the corresponding plugin is installed. Use the [Discovery endpoint](#discovery) to determine what services are available at runtime. diff --git a/content/docs/api/metadata-api.mdx b/content/docs/api/metadata-api.mdx index f4b8c6816b..605319e398 100644 --- a/content/docs/api/metadata-api.mdx +++ b/content/docs/api/metadata-api.mdx @@ -3,8 +3,6 @@ title: Metadata & Package API description: REST endpoints for object schemas, metadata types, UI views, and package management. --- -# Metadata & Package API - Manage object schemas, metadata types, UI views, and installed packages over REST. All paths are relative to the base URL (defaults to `/api/v1`) — see the [API Overview](/docs/api) for discovery and service availability. ## Metadata diff --git a/content/docs/api/plugin-endpoints.mdx b/content/docs/api/plugin-endpoints.mdx index f8ba211b2a..f28d949c20 100644 --- a/content/docs/api/plugin-endpoints.mdx +++ b/content/docs/api/plugin-endpoints.mdx @@ -3,8 +3,6 @@ title: Plugin Endpoints description: REST endpoints that become available when the corresponding plugin is installed — auth, workflow, automation, views, realtime, notifications, AI, i18n, and file storage. --- -# Plugin Endpoints - These REST endpoints are only available when the corresponding plugin is installed. Check the [discovery manifest](/docs/api#discovery) `services` map before calling them; all paths are relative to the base URL (defaults to `/api/v1`). This page is a **catalog of routes the platform serves for you** — it is not about authoring an diff --git a/content/docs/api/wire-format.mdx b/content/docs/api/wire-format.mdx index 9045b6e39c..a031992f51 100644 --- a/content/docs/api/wire-format.mdx +++ b/content/docs/api/wire-format.mdx @@ -3,8 +3,6 @@ title: Wire Format & JSON Examples description: Complete HTTP request/response examples for ObjectStack API operations — CRUD, queries, metadata, errors, and batch operations --- -# Wire Format & JSON Examples - This page provides complete HTTP request and response examples for every common ObjectStack API operation. All examples use a `task` object with realistic data. diff --git a/content/docs/automation/approvals.mdx b/content/docs/automation/approvals.mdx index ffe7a2763f..af603b221c 100644 --- a/content/docs/automation/approvals.mdx +++ b/content/docs/automation/approvals.mdx @@ -3,8 +3,6 @@ title: Approval workflow description: Route a record for sign-off — who can configure the automation, and the run-identity decision that keeps an approval flow from quietly bypassing row-level security. --- -# Approval workflow - ## Scenario > A record (an invoice, a discount, a leave request) must be **routed for approval** before it proceeds. Who is allowed to build that automation, and how do I make sure it runs safely? diff --git a/content/docs/automation/connectors.mdx b/content/docs/automation/connectors.mdx index c9148dc81a..e22720d1cb 100644 --- a/content/docs/automation/connectors.mdx +++ b/content/docs/automation/connectors.mdx @@ -3,8 +3,6 @@ title: Connectors description: Call external systems from flows — plugin-registered connectors, and declarative provider-bound instances (rest / openapi / mcp) authored as pure metadata with reference-based credentials. --- -# Connectors - > **Status:** Shipped · **Audience:** App authors (human and AI), integration > engineers > diff --git a/content/docs/automation/email-templates.mdx b/content/docs/automation/email-templates.mdx index 633da0fa13..b127636ca3 100644 --- a/content/docs/automation/email-templates.mdx +++ b/content/docs/automation/email-templates.mdx @@ -3,8 +3,6 @@ title: Email Templates description: Author a localizable outbound mail template as metadata, and reach it from a flow's notify node or from services.email. --- -# Email Templates - An **email template** is a named, localizable subject + body that lives as metadata. Your app declares it; the email service resolves it by `(name, locale)` at send time and renders its `{{placeholders}}` against a diff --git a/content/docs/automation/flows.mdx b/content/docs/automation/flows.mdx index 9880dba07f..6d7e581fdb 100644 --- a/content/docs/automation/flows.mdx +++ b/content/docs/automation/flows.mdx @@ -3,8 +3,6 @@ title: Flow Metadata description: Build visual automation with decision trees, data operations, HTTP requests, and screen interactions --- -# Flow Metadata - A **Flow** is a visual automation that orchestrates business logic through connected nodes. Flows support decision branching, data operations (CRUD), HTTP integrations, script execution, user screens, and subflow composition. ## Basic Structure diff --git a/content/docs/automation/hook-bodies.mdx b/content/docs/automation/hook-bodies.mdx index 625260a6a6..01a7af2b6b 100644 --- a/content/docs/automation/hook-bodies.mdx +++ b/content/docs/automation/hook-bodies.mdx @@ -3,7 +3,7 @@ title: Hook & Action Bodies (L1 / L2) description: How hook handlers and script-action bodies travel through ObjectStack as pure metadata, and the spec they must conform to. --- -# Hook & Action Bodies +## Hook & Action Bodies ObjectStack treats every hook handler and every `type: 'script'` action as **pure metadata**. In the self-contained (body-only) form there is no separate `.mjs` file shipped alongside the project artifact, no dynamic `import()` at runtime, and no filesystem dependency on the cloud — though today a legacy `objectstack-runtime.{hash}.mjs` back-compat bundle can still ship, and does get dynamically imported at boot, for any handler that hasn't been lowered to a metadata `body` yet (see [Migration](#migration) below). A body is either: diff --git a/content/docs/automation/hooks.mdx b/content/docs/automation/hooks.mdx index 0f7dd1a07a..2bffbdba39 100644 --- a/content/docs/automation/hooks.mdx +++ b/content/docs/automation/hooks.mdx @@ -3,8 +3,6 @@ title: Hooks description: Run custom code at interception points in the ObjectQL execution pipeline — before/after insert, update, delete, and find --- -# Hooks - Hooks are the data-layer "logic layer": they run custom code at interception points in the ObjectQL execution pipeline (before/after insert, update, delete, find, etc.). The construct is `Hook`, imported from `@objectstack/spec/data`. diff --git a/content/docs/automation/index.mdx b/content/docs/automation/index.mdx index 39aa834a87..e58eb42655 100644 --- a/content/docs/automation/index.mdx +++ b/content/docs/automation/index.mdx @@ -3,8 +3,6 @@ title: Automation description: Hooks, flows, workflows, approvals, scheduled jobs, durable webhooks, and connectors — the process engine that reacts to your data. --- -# Automation - Automation is ObjectStack's process engine: you attach business logic to the data model **declaratively** — as metadata — instead of scattering it through application code. It is a cross-protocol capability: automations are declared in the Automation Protocol namespace and executed by the ObjectStack runtime against ObjectQL data. The smallest useful automation is a hook (from the CRM example app): diff --git a/content/docs/automation/jobs.mdx b/content/docs/automation/jobs.mdx index 59bde45ac4..a3597f1b69 100644 --- a/content/docs/automation/jobs.mdx +++ b/content/docs/automation/jobs.mdx @@ -3,8 +3,6 @@ title: Scheduled Jobs description: Run a TypeScript function on a cron, interval, or one-off schedule — and decide when a job is the right tool instead of a schedule-triggered flow. --- -# Scheduled Jobs - A **job** runs one named function in your bundle on a schedule. You declare the schedule as metadata; the platform's job service owns the timing, the retries, the per-attempt time limit, and the run history. diff --git a/content/docs/automation/webhooks.mdx b/content/docs/automation/webhooks.mdx index 8a59da4ca6..10d0784933 100644 --- a/content/docs/automation/webhooks.mdx +++ b/content/docs/automation/webhooks.mdx @@ -3,8 +3,6 @@ title: Webhook Delivery description: How ObjectStack reliably ships outbound HTTP notifications — from event to receiver — with at-least-once guarantees, signed payloads, and observable retries. --- -# Webhook Delivery - > **Status:** Shipped · **Audience:** Plugin authors, runtime engineers, > integration partners > diff --git a/content/docs/automation/workflows.mdx b/content/docs/automation/workflows.mdx index c8fc92f37b..0750ac05cb 100644 --- a/content/docs/automation/workflows.mdx +++ b/content/docs/automation/workflows.mdx @@ -3,8 +3,6 @@ title: Workflow Metadata description: Use Flow for automation and state machines for strict lifecycle transitions. --- -# Workflow Metadata - ObjectStack no longer has a standalone Salesforce-style Workflow Rule authoring type. Use: diff --git a/content/docs/concepts/architecture.mdx b/content/docs/concepts/architecture.mdx index f3b6564f7f..80329621d6 100644 --- a/content/docs/concepts/architecture.mdx +++ b/content/docs/concepts/architecture.mdx @@ -5,7 +5,7 @@ description: How the Data, System, and UI protocols work together as one cohesiv import { Database, Layout, Cpu, ArrowRight, CheckCircle, Workflow, Bot, Cloud } from 'lucide-react'; -# The Protocol Stack +## The Protocol Stack The architecture is built on foundational protocols that work together as a unified system: diff --git a/content/docs/concepts/design-principles.mdx b/content/docs/concepts/design-principles.mdx index 4b3176b584..f8493b0e24 100644 --- a/content/docs/concepts/design-principles.mdx +++ b/content/docs/concepts/design-principles.mdx @@ -5,8 +5,6 @@ description: The unshakable core principles that govern ObjectStack as an AI-nat import { Scale, Code2, Database, ScrollText } from 'lucide-react'; -# Design Principles - ObjectStack exists to make enterprise application development explicit enough for both humans and AI agents to operate safely: **structured business metadata**. To maintain a healthy, decoupled, and future-proof ecosystem, we uphold the following core principles. These are not suggestions; they are the constraints that enable our freedom. diff --git a/content/docs/concepts/index.mdx b/content/docs/concepts/index.mdx index d72da067d2..0b9dfc3729 100644 --- a/content/docs/concepts/index.mdx +++ b/content/docs/concepts/index.mdx @@ -5,8 +5,6 @@ description: The foundational ideas behind ObjectStack — metadata-driven devel import { Scale, Code2, Database, ScrollText, Laptop } from 'lucide-react'; -# Core Concepts - Before diving into code, understanding these foundational ideas will make everything else click. ## Find Your Path diff --git a/content/docs/concepts/metadata-driven.mdx b/content/docs/concepts/metadata-driven.mdx index 3e313bd46c..ddfe69a2f5 100644 --- a/content/docs/concepts/metadata-driven.mdx +++ b/content/docs/concepts/metadata-driven.mdx @@ -5,7 +5,7 @@ description: Understanding how structured metadata becomes APIs, UI, workflows, import { Database, Code, Link, Laptop } from 'lucide-react'; -# What is Metadata-Driven Development? +## What is Metadata-Driven Development? Metadata-driven development is a paradigm shift where **business intent is defined by declarative, analyzable metadata instead of scattered imperative code.** diff --git a/content/docs/concepts/metadata-lifecycle.mdx b/content/docs/concepts/metadata-lifecycle.mdx index 4cf009faa4..1725a24f91 100644 --- a/content/docs/concepts/metadata-lifecycle.mdx +++ b/content/docs/concepts/metadata-lifecycle.mdx @@ -3,8 +3,6 @@ title: Metadata Lifecycle & HMR description: How metadata flows through Repository → Change Log → Cache → Registry — the canonical event stream that powers Studio HMR, REST writes, and future cloud editing. --- -# Metadata Lifecycle & HMR - This page documents the metadata data path introduced by [ADR-0008](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0008-metadata-repository-and-change-log.md) and refined by [ADR-0005](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0005-metadata-customization-overlay.md). It is the canonical event stream that powers Studio Hot Module Replacement (HMR), REST writes, and future cloud editing. diff --git a/content/docs/concepts/north-star.mdx b/content/docs/concepts/north-star.mdx index 17c76b83cd..ec5e3eced4 100644 --- a/content/docs/concepts/north-star.mdx +++ b/content/docs/concepts/north-star.mdx @@ -3,8 +3,6 @@ title: North Star description: ObjectStack's current product and architecture direction. --- -# North Star - ObjectStack is the metadata-native backend for business software that humans and AI agents can both operate safely. The platform makes the business system explicit: data models, views, flows, permissions, actions, AI tools, and runtime diff --git a/content/docs/data-modeling/analytics.mdx b/content/docs/data-modeling/analytics.mdx index 94099a556c..256bc32296 100644 --- a/content/docs/data-modeling/analytics.mdx +++ b/content/docs/data-modeling/analytics.mdx @@ -3,8 +3,6 @@ title: Analytics Datasets description: The dataset semantic layer (ADR-0021) — define a metric once, bind reports and dashboards to it by name. --- -# Analytics Datasets - > **Related ADR:** [ADR-0021 — Analytics: one semantic `dataset` layer](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0021-analytics-dataset-semantic-layer.md) A **dataset** is a named, reusable analytical definition — a base object, the diff --git a/content/docs/data-modeling/drivers.mdx b/content/docs/data-modeling/drivers.mdx index 1d9153446f..28d80c5e42 100644 --- a/content/docs/data-modeling/drivers.mdx +++ b/content/docs/data-modeling/drivers.mdx @@ -3,8 +3,6 @@ title: Database Drivers description: Configuration reference for supported database drivers --- -# Database Drivers - ObjectStack supports multiple database backends through a unified driver interface. Drivers can be selected in two ways: diff --git a/content/docs/data-modeling/external-datasources.mdx b/content/docs/data-modeling/external-datasources.mdx index bb879d9ffd..6837030e8f 100644 --- a/content/docs/data-modeling/external-datasources.mdx +++ b/content/docs/data-modeling/external-datasources.mdx @@ -3,8 +3,6 @@ title: External Datasources (Federation) description: Declare an external database as a datasource and query its tables as ObjectStack objects — visible, auto-connected, and queryable with zero app code. --- -# External Datasources (Federation) - ObjectStack can treat a **mature external database** — one it does not own — as a read-only (or, with explicit opt-in, writable) datasource, and expose its tables as normal objects. This is *federation*: the data stays in the remote database; diff --git a/content/docs/data-modeling/field-type-decision-tree.mdx b/content/docs/data-modeling/field-type-decision-tree.mdx index 29095e2417..8f4b9241db 100644 --- a/content/docs/data-modeling/field-type-decision-tree.mdx +++ b/content/docs/data-modeling/field-type-decision-tree.mdx @@ -3,8 +3,6 @@ title: Field Type Decision Tree description: Interactive guide for choosing the right ObjectStack field type — decision flowchart, quick-reference table, and use case mapping --- -# Field Type Decision Tree - Not sure which field type to use? Follow this decision tree to find the right type for your data, then check the quick-reference table for details. diff --git a/content/docs/data-modeling/field-types.mdx b/content/docs/data-modeling/field-types.mdx index 91699ba91c..714c553980 100644 --- a/content/docs/data-modeling/field-types.mdx +++ b/content/docs/data-modeling/field-types.mdx @@ -3,8 +3,6 @@ title: Field Type Gallery description: Complete reference for every ObjectStack field type with per-type configuration properties --- -# Field Type Gallery - ObjectStack provides a **comprehensive set of field types** covering every data modeling need — from basic text and numbers to AI vectors and rich media. This guide organizes them by category with per-type configuration details. diff --git a/content/docs/data-modeling/fields.mdx b/content/docs/data-modeling/fields.mdx index 3fcc2c68ac..8238716e55 100644 --- a/content/docs/data-modeling/fields.mdx +++ b/content/docs/data-modeling/fields.mdx @@ -3,8 +3,6 @@ title: Field Metadata description: Configure field types and properties — text, numbers, dates, relationships, files, and more --- -# Field Metadata - A **Field** defines an individual property within an Object. ObjectStack provides a comprehensive set of field types covering text, numbers, dates, selections, relationships, files, calculations, and specialized types like vectors and QR codes. ## Basic Usage diff --git a/content/docs/data-modeling/formulas.mdx b/content/docs/data-modeling/formulas.mdx index 7811580455..f80ffaf782 100644 --- a/content/docs/data-modeling/formulas.mdx +++ b/content/docs/data-modeling/formulas.mdx @@ -3,7 +3,7 @@ title: Expressions (CEL) description: Canonical CEL-based expression language for formulas, predicates, conditions, and dynamic seed values --- -# Expressions +## Expressions ObjectStack uses a **single canonical expression language** for every place a piece of metadata needs to compute a value or evaluate a condition: diff --git a/content/docs/data-modeling/import-mappings.mdx b/content/docs/data-modeling/import-mappings.mdx index 84047ecb57..587284ae31 100644 --- a/content/docs/data-modeling/import-mappings.mdx +++ b/content/docs/data-modeling/import-mappings.mdx @@ -3,8 +3,6 @@ title: Import Mappings description: Named, reusable source-column to field projections for CSV/JSON/xlsx import, and the mappingName request that applies one. --- -# Import Mappings - An **import mapping** is a named, reusable projection from the columns of a source file onto the fields of one object. `mapping` is a first-class metadata kind, so a mapping either ships inside a package or is saved at runtime — and either way diff --git a/content/docs/data-modeling/index.mdx b/content/docs/data-modeling/index.mdx index fc69a519cb..a0a490c96f 100644 --- a/content/docs/data-modeling/index.mdx +++ b/content/docs/data-modeling/index.mdx @@ -3,8 +3,6 @@ title: Data Modeling description: Objects, fields, relationships, validation, CEL formulas, and a compiled query AST — the ObjectQL layer every other module builds on. --- -# Data Modeling - Every ObjectStack application starts here: you declare **objects** (business entities), their **fields**, **relationships**, and **validation rules** as typed metadata, and the platform derives the database schema, REST API, permission surface, and UI from that single definition. This module is the practical documentation for the **ObjectQL layer** — the Data Protocol specified normatively in the [ObjectQL spec](/docs/protocol/objectql). A real object definition looks like this (from the CRM example app): diff --git a/content/docs/data-modeling/indexing.mdx b/content/docs/data-modeling/indexing.mdx index 24b2ded18d..410160a0e1 100644 --- a/content/docs/data-modeling/indexing.mdx +++ b/content/docs/data-modeling/indexing.mdx @@ -3,8 +3,6 @@ title: Database Indexing description: Optimize query performance with single-field, unique, compound, and lookup indexes --- -# Database Indexing - Optimize query performance with indexes: {/* os:check */} diff --git a/content/docs/data-modeling/object-extensions.mdx b/content/docs/data-modeling/object-extensions.mdx index 46bad08c7f..302a1783e8 100644 --- a/content/docs/data-modeling/object-extensions.mdx +++ b/content/docs/data-modeling/object-extensions.mdx @@ -3,8 +3,6 @@ title: Object Extensions description: Add fields, validations and indexes to an object another package owns, without forking it — declaration, merge order, and what an extension may not contribute. --- -# Object Extensions - Every object has exactly **one owning package**. That package defines the table, the primary key and the core fields, and no second package may claim the same name — the registry refuses it outright. diff --git a/content/docs/data-modeling/objects.mdx b/content/docs/data-modeling/objects.mdx index 90161bd922..3d74990d40 100644 --- a/content/docs/data-modeling/objects.mdx +++ b/content/docs/data-modeling/objects.mdx @@ -3,8 +3,6 @@ title: Object Metadata description: Define business entities with ObjectSchema — the core building block of every ObjectStack application --- -# Object Metadata - An **Object** is the foundational metadata type in ObjectStack. It defines a business entity — its fields, capabilities, indexes, and behaviors. Each Object maps to a database table/collection and automatically gets CRUD APIs, UI forms, and query support. ## Basic Structure diff --git a/content/docs/data-modeling/queries.mdx b/content/docs/data-modeling/queries.mdx index d8c92eea56..4cd657c5f6 100644 --- a/content/docs/data-modeling/queries.mdx +++ b/content/docs/data-modeling/queries.mdx @@ -3,8 +3,6 @@ title: Query Syntax Cheat Sheet description: One-page reference for ObjectStack QuerySchema — filters, sorts, pagination, aggregations, and expand --- -# Query Syntax Cheat Sheet - Quick reference for building queries with the ObjectStack QuerySchema. diff --git a/content/docs/data-modeling/relationships.mdx b/content/docs/data-modeling/relationships.mdx index 1c05618e56..7fea4f7ce2 100644 --- a/content/docs/data-modeling/relationships.mdx +++ b/content/docs/data-modeling/relationships.mdx @@ -3,8 +3,6 @@ title: "Relationships & Lookups" description: Model relationships between objects — lookups, filtered lookups, self-referencing hierarchies, and related lists --- -# Relationships & Lookups - ### Lookup (Many-to-One) Creates a reference to another object: diff --git a/content/docs/data-modeling/schema-design.mdx b/content/docs/data-modeling/schema-design.mdx index c2bed2ca48..444a5c4187 100644 --- a/content/docs/data-modeling/schema-design.mdx +++ b/content/docs/data-modeling/schema-design.mdx @@ -3,8 +3,6 @@ title: Schema Design description: Design robust object schemas in ObjectStack — object metadata, enable flags, field groups, and enterprise best practices --- -# Schema Design - Complete guide to designing robust data models in ObjectStack following enterprise best practices. --- diff --git a/content/docs/data-modeling/seed-data.mdx b/content/docs/data-modeling/seed-data.mdx index 6a08f1b002..df9654b89a 100644 --- a/content/docs/data-modeling/seed-data.mdx +++ b/content/docs/data-modeling/seed-data.mdx @@ -3,8 +3,6 @@ title: Seed Data & Fixtures description: Populate ObjectStack objects with bootstrap data, reference records, and demo fixtures using defineSeed() --- -# Seed Data & Fixtures - `defineSeed()` is the canonical way to define seed data in ObjectStack. It provides compile-time type safety by inferring valid field keys directly from your object definition, so typos in record field names are caught before the code runs. diff --git a/content/docs/data-modeling/validation-rules.mdx b/content/docs/data-modeling/validation-rules.mdx index ef13e81639..28af424f20 100644 --- a/content/docs/data-modeling/validation-rules.mdx +++ b/content/docs/data-modeling/validation-rules.mdx @@ -3,8 +3,6 @@ title: Field Validation Rules description: Default validation behavior, required properties, and constraints for each ObjectStack field type --- -# Field Validation Rules - Every ObjectStack field type has built-in validation behavior that runs automatically at the schema level. This reference documents the **default constraints**, **required properties**, and **validation semantics** for each field type. diff --git a/content/docs/data-modeling/validation.mdx b/content/docs/data-modeling/validation.mdx index 5b81813359..0291807e9c 100644 --- a/content/docs/data-modeling/validation.mdx +++ b/content/docs/data-modeling/validation.mdx @@ -3,8 +3,6 @@ title: Validation Metadata description: Define data integrity rules — formula conditions, format, cross-field checks, state machine transitions, and more --- -# Validation Metadata - **Validation rules** enforce data integrity at the platform level. They run automatically on the write path (insert/update), preventing invalid data from being saved. A validation rule is a deterministic, synchronous, side-effect-free predicate over a single record — it must be decidable from the incoming write (and, on update, the prior record) with no I/O. ObjectStack supports 6 validation types: `script`, `state_machine`, `format`, `cross_field`, `json_schema`, and `conditional`. diff --git a/content/docs/deployment/backup-restore.mdx b/content/docs/deployment/backup-restore.mdx index 3b1c0e356e..2165c017df 100644 --- a/content/docs/deployment/backup-restore.mdx +++ b/content/docs/deployment/backup-restore.mdx @@ -3,8 +3,6 @@ title: Backup & Restore description: What state an ObjectStack deployment actually holds, how to back up each piece per database driver, and the restore drill to rehearse before you need it. --- -# Backup & Restore - The [go-live checklist](/docs/deployment/production-readiness#go-live-checklist) requires a documented, tested backup/restore drill. This page is that drill: what to back up, how, and how to prove the restore works. diff --git a/content/docs/deployment/cli.mdx b/content/docs/deployment/cli.mdx index e101d9c9e1..b2ccecb7ed 100644 --- a/content/docs/deployment/cli.mdx +++ b/content/docs/deployment/cli.mdx @@ -3,7 +3,7 @@ title: Command Line Interface description: Complete guide for using the ObjectStack CLI to build metadata-driven applications --- -# @objectstack/cli +## @objectstack/cli Command Line Interface for building metadata-driven applications with the ObjectStack Protocol. diff --git a/content/docs/deployment/environment-variables.mdx b/content/docs/deployment/environment-variables.mdx index 18468c1270..08d814e15b 100644 --- a/content/docs/deployment/environment-variables.mdx +++ b/content/docs/deployment/environment-variables.mdx @@ -3,8 +3,6 @@ title: Environment Variables description: Canonical list of OS_* environment variables read by the ObjectStack runtime, CLI, and built-in plugins. --- -# Environment Variables - All ObjectStack-owned environment variables use the `OS_` prefix. Variables are read at startup unless noted otherwise. Boolean variables accept `true` / `false` (case-insensitive); flag-style variables also accept `1` / `0` where noted. diff --git a/content/docs/deployment/index.mdx b/content/docs/deployment/index.mdx index de72249fa6..7726c47fac 100644 --- a/content/docs/deployment/index.mdx +++ b/content/docs/deployment/index.mdx @@ -3,8 +3,6 @@ title: Deployment Overview description: Two things deploy on this platform and they move on separate clocks — the platform runtime you operate, and the metadata app you build. Which one you are doing decides which pages in this section are yours. --- -# Deployment Overview - Two different things get deployed here, and they run on **separate clocks**: | | **The platform runtime** | **Your metadata app** | diff --git a/content/docs/deployment/production-readiness.mdx b/content/docs/deployment/production-readiness.mdx index c6248b9673..0384394453 100644 --- a/content/docs/deployment/production-readiness.mdx +++ b/content/docs/deployment/production-readiness.mdx @@ -3,8 +3,6 @@ title: Production Readiness description: Security headers, rate limiting, observability, and the go-live checklist for ObjectStack runtimes. --- -# Production Readiness - `@objectstack/runtime` ships first-class primitives for the cross-cutting concerns every production deployment needs: HTTP hardening, rate limiting, metrics, error reporting, and request-id correlation. Everything is diff --git a/content/docs/deployment/publish-and-preview.mdx b/content/docs/deployment/publish-and-preview.mdx index a056cec01d..6a1f3d7919 100644 --- a/content/docs/deployment/publish-and-preview.mdx +++ b/content/docs/deployment/publish-and-preview.mdx @@ -3,8 +3,6 @@ title: Publish, Versioning & Preview description: A metadata app is versioned in your catalog while the platform moves on its own release train. Compile the app into an artifact, then pick how it reaches a running platform — installed from the catalog, or pinned as the runtime's boot artifact. --- -# Publish, Versioning & Preview - ## Your app and the platform move on separate clocks A metadata app is **not part of the platform**. It compiles to its own immutable diff --git a/content/docs/deployment/seed-tenancy-repair.mdx b/content/docs/deployment/seed-tenancy-repair.mdx index 95e8c1009d..e07f529ea8 100644 --- a/content/docs/deployment/seed-tenancy-repair.mdx +++ b/content/docs/deployment/seed-tenancy-repair.mdx @@ -3,8 +3,6 @@ title: Seed Tenancy Repair description: The automatic repair that stamps organization_id on untenanted seed rows and merges the __global__ autonumber counter — when it runs, what it changes, what it deliberately leaves alone, and the manual remedy for a multi-organization install. --- -# Seed Tenancy Repair - ObjectStack ships one repair that **rewrites stored rows without an operator asking it to**. Every other row-rewriting migration on this platform is an explicit `os migrate … --apply` with a preview mode; this one is not. It has no diff --git a/content/docs/deployment/self-hosting.mdx b/content/docs/deployment/self-hosting.mdx index 36d3121cf5..1ed1ce73e1 100644 --- a/content/docs/deployment/self-hosting.mdx +++ b/content/docs/deployment/self-hosting.mdx @@ -3,8 +3,6 @@ title: Self-Hosted Deployment description: Run a compiled ObjectStack app on your own infrastructure with the official Docker image — plus Compose with Postgres, Kubernetes, and the bare Node.js fallback, including health checks, reverse-proxy wiring, and the secrets you must pin. --- -# Self-Hosted Deployment - This guide takes the artifact produced by `os build` / `os compile` and runs it on infrastructure **you** operate. **Docker is the standard path** — the platform publishes an official runtime image on every release, and Compose and diff --git a/content/docs/deployment/single-project-mode.mdx b/content/docs/deployment/single-project-mode.mdx index 1420b2919b..9dc324a5b9 100644 --- a/content/docs/deployment/single-project-mode.mdx +++ b/content/docs/deployment/single-project-mode.mdx @@ -3,8 +3,6 @@ title: Single-Environment Mode description: Run ObjectStack as a one-environment local stack without a Cloud control plane. --- -# Single-Environment Mode - Single-environment mode is the default boot shape for this repository's framework runtime and examples. One process owns one active environment, loads the configured metadata, and serves REST plus the published console bundle. diff --git a/content/docs/deployment/tenancy-modes.mdx b/content/docs/deployment/tenancy-modes.mdx index d0b9997e38..03296762ab 100644 --- a/content/docs/deployment/tenancy-modes.mdx +++ b/content/docs/deployment/tenancy-modes.mdx @@ -3,8 +3,6 @@ title: Tenancy Postures & Membership description: The three tenancy postures (single / group / isolated), how OS_TENANCY_POSTURE resolves, the membership policy for new users, and the degraded-tenancy boot guard. --- -# Tenancy Postures & Membership - An ObjectStack deployment runs in one of **three tenancy postures**. The posture is the single fact that decides what the authorization kernel's Layer 0 organization wall enforces, how new users are placed into an organization, and diff --git a/content/docs/deployment/troubleshooting.mdx b/content/docs/deployment/troubleshooting.mdx index 7bf626f44e..db62e4fe3b 100644 --- a/content/docs/deployment/troubleshooting.mdx +++ b/content/docs/deployment/troubleshooting.mdx @@ -3,8 +3,6 @@ title: Troubleshooting & FAQ description: Solutions to common ObjectStack issues — validation failures, query problems, configuration mistakes, and more --- -# Troubleshooting & FAQ - Solutions to the most common issues encountered when working with ObjectStack. --- diff --git a/content/docs/deployment/validating-metadata.mdx b/content/docs/deployment/validating-metadata.mdx index 04ef2d6a65..a67b2ff312 100644 --- a/content/docs/deployment/validating-metadata.mdx +++ b/content/docs/deployment/validating-metadata.mdx @@ -3,8 +3,6 @@ title: Validating Metadata description: Why ObjectStack metadata mistakes fail silently at runtime, and the one command that catches them at author time — run it after every metadata edit. --- -# Validating Metadata - ObjectStack metadata is data, not code paths — so most mistakes are **not** caught by the TypeScript compiler. They pass `tsc`, load fine, and then fail **silently at runtime**. The fix is one command you run after every metadata edit: diff --git a/content/docs/getting-started/build-with-claude-code.mdx b/content/docs/getting-started/build-with-claude-code.mdx index 98dc5cb1ef..cd473f894b 100644 --- a/content/docs/getting-started/build-with-claude-code.mdx +++ b/content/docs/getting-started/build-with-claude-code.mdx @@ -3,8 +3,6 @@ title: Build with Claude Code description: The core ObjectStack workflow — Claude Code authors the metadata, you verify in the visual Console, guardrails catch mistakes, and the app you build is itself AI-operable over MCP. --- -# Build with Claude Code - This is the **main way you build on ObjectStack**: you describe what you want in plain language, **Claude Code (or Cursor, Copilot, …) writes the typed metadata**, a validation gate catches the mistakes that fail silently at runtime, and **you diff --git a/content/docs/getting-started/common-patterns.mdx b/content/docs/getting-started/common-patterns.mdx index 9851d24186..97364d9c2e 100644 --- a/content/docs/getting-started/common-patterns.mdx +++ b/content/docs/getting-started/common-patterns.mdx @@ -3,7 +3,7 @@ title: Common Patterns description: Top 10 patterns for building applications with ObjectStack — CRUD, views, flows, agents, security, and more --- -# Common Patterns Guide +## Common Patterns Guide This guide covers the most common patterns you will use when building applications with ObjectStack. Each pattern includes a complete, copy-pasteable example. diff --git a/content/docs/getting-started/examples.mdx b/content/docs/getting-started/examples.mdx index 464dd9f732..39f9b6f476 100644 --- a/content/docs/getting-started/examples.mdx +++ b/content/docs/getting-started/examples.mdx @@ -5,8 +5,6 @@ description: Run and explore the built-in example applications to learn ObjectSt import { CheckSquare, Building2, BarChart3, Server } from 'lucide-react'; -# Example Apps - The monorepo ships three ready-to-run examples in `examples/` that progressively demonstrate ObjectStack features — from a simple Todo app to a full CRM and a kitchen-sink reference. For a larger external enterprise reference, see the [HotCRM repository](https://github.com/objectstack-ai/hotcrm). diff --git a/content/docs/getting-started/how-ai-development-works.mdx b/content/docs/getting-started/how-ai-development-works.mdx index 6be7726fa6..52d0cf8bd1 100644 --- a/content/docs/getting-started/how-ai-development-works.mdx +++ b/content/docs/getting-started/how-ai-development-works.mdx @@ -3,8 +3,6 @@ title: How AI Development Works description: The division of labor behind ObjectStack — AI authors the typed metadata, you verify in the visual UI, and layered guardrails keep the AI from shipping mistakes. --- -# How AI Development Works - ObjectStack is built for a specific way of working: **an AI agent writes the application; a human verifies it in a visual UI; and a stack of guardrails keeps the AI from shipping mistakes.** This page explains why that division of labor is diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx index 44153c3c61..0f171c89a3 100644 --- a/content/docs/getting-started/quick-reference.mdx +++ b/content/docs/getting-started/quick-reference.mdx @@ -4,8 +4,6 @@ description: Fast lookup table for all ObjectStack protocols --- -# Quick Reference Guide - Fast lookup for the ObjectStack protocols organized by category. diff --git a/content/docs/getting-started/quick-start.mdx b/content/docs/getting-started/quick-start.mdx index 612137850b..a102110a45 100644 --- a/content/docs/getting-started/quick-start.mdx +++ b/content/docs/getting-started/quick-start.mdx @@ -3,8 +3,6 @@ title: Anatomy of an ObjectStack App description: A tour of the metadata an AI agent writes for you — objects, actions, views, apps — and how to read it so you can verify the result in the Console. --- -# Anatomy of an ObjectStack App - An ObjectStack app *is* its metadata: a few hundred lines of typed definitions for objects, actions, views, apps, automation, and permissions. In the [AI-first workflow](/docs/getting-started/build-with-claude-code), **an agent diff --git a/content/docs/getting-started/your-first-project.mdx b/content/docs/getting-started/your-first-project.mdx index f0c1db5fe3..f2b8ceae94 100644 --- a/content/docs/getting-started/your-first-project.mdx +++ b/content/docs/getting-started/your-first-project.mdx @@ -3,8 +3,6 @@ title: Your First Project description: Scaffold a standalone ObjectStack project with npm, understand what was generated, extend the data model, call the REST API, and build a deployable artifact — no monorepo checkout, no AI agent required. --- -# Your First Project - This is the hands-on path for developers building **on** ObjectStack from the published npm packages — you never clone the framework repository. In about ten minutes you will scaffold a project, run it, add a field by hand, call the diff --git a/content/docs/index.mdx b/content/docs/index.mdx index ab66088b98..93e81c18ed 100644 --- a/content/docs/index.mdx +++ b/content/docs/index.mdx @@ -3,7 +3,7 @@ title: Documentation description: Technical documentation for ObjectStack. --- -# ObjectStack Documentation +## ObjectStack Documentation ObjectStack is an **AI-native business backend protocol** for structured, auditable business applications. The way you build it: **an AI agent (Claude Code) authors your diff --git a/content/docs/kernel/architecture.mdx b/content/docs/kernel/architecture.mdx index eed58bf653..3ecf5cfcb8 100644 --- a/content/docs/kernel/architecture.mdx +++ b/content/docs/kernel/architecture.mdx @@ -3,7 +3,7 @@ title: Architecture description: Deep dive into the ObjectKernel architecture --- -# Core Architecture +## Core Architecture The ObjectKernel architecture follows a strict **Life-Cycle** process to ensure system stability. diff --git a/content/docs/kernel/cluster.mdx b/content/docs/kernel/cluster.mdx index fbf4ee79e1..3961b44d68 100644 --- a/content/docs/kernel/cluster.mdx +++ b/content/docs/kernel/cluster.mdx @@ -3,8 +3,6 @@ title: Cluster Semantics description: How ObjectStack stays correct from a single laptop to a multi-node cluster — event scope, service scope, leader election, and metadata versioning. --- -# Cluster Semantics - > **Status:** Accepted · **Audience:** Plugin authors, runtime engineers > > **TL;DR** — ObjectStack runs identically on a laptop and on a 10-node cluster. To diff --git a/content/docs/kernel/contracts/auth-service.mdx b/content/docs/kernel/contracts/auth-service.mdx index a08d7993b1..18f7096645 100644 --- a/content/docs/kernel/contracts/auth-service.mdx +++ b/content/docs/kernel/contracts/auth-service.mdx @@ -3,8 +3,6 @@ title: IAuthService Contract description: Reference for the Auth Service contract — authentication, session verification, user resolution, and logout --- -# IAuthService Contract - The Auth Service handles **authentication** — verifying user identity through credentials, tokens, or external providers. It does not handle authorization (permissions); that is governed separately by the security layer (PermissionSets, sharing rules) in `plugin-security`. diff --git a/content/docs/kernel/contracts/cache-service.mdx b/content/docs/kernel/contracts/cache-service.mdx index 0dace2dd34..cf39548cb3 100644 --- a/content/docs/kernel/contracts/cache-service.mdx +++ b/content/docs/kernel/contracts/cache-service.mdx @@ -3,8 +3,6 @@ title: ICacheService Contract description: Reference for the Cache Service contract — key-value caching with TTL support --- -# ICacheService Contract - The Cache Service provides a **key-value caching** layer used throughout ObjectStack for metadata caching, query result caching, and rate limiting. Implementations can range from in-memory Maps to Redis clusters. It does **not** store sessions: the session of record is always the `sys_session` table, because ADR-0069 D4's session controls (idle timeout, absolute lifetime, concurrent-session cap) revoke a session by writing that row. A host may opt in explicitly with `cacheSecondaryStorage()` from `@objectstack/plugin-auth`, but should know what it buys: better-auth then answers session lookups from the cached snapshot without reading the database, so **opting in disables all three D4 session controls** — a revoked session stays usable until its cached copy expires. diff --git a/content/docs/kernel/contracts/data-engine.mdx b/content/docs/kernel/contracts/data-engine.mdx index 1728dae619..967eb870f6 100644 --- a/content/docs/kernel/contracts/data-engine.mdx +++ b/content/docs/kernel/contracts/data-engine.mdx @@ -3,8 +3,6 @@ title: IDataEngine Contract description: Reference for the Data Engine contract — the core data persistence layer for CRUD operations, queries, aggregations, and transactions --- -# IDataEngine Contract - The Data Engine is the **core persistence layer** of ObjectStack. Every data operation — inserts, finds, updates, deletes, counts, and aggregations — flows through this contract. The Kernel delegates to the Data Engine after applying security, validation, and hooks. diff --git a/content/docs/kernel/contracts/index.mdx b/content/docs/kernel/contracts/index.mdx index 65dac74a19..d39db3841b 100644 --- a/content/docs/kernel/contracts/index.mdx +++ b/content/docs/kernel/contracts/index.mdx @@ -3,8 +3,6 @@ title: Service Contracts Overview description: Reference for all ObjectStack service contracts — TypeScript interfaces that define the API between the Kernel and plugins --- -# Service Contracts Overview - Service contracts are **TypeScript interfaces** that define the boundaries between the ObjectStack Kernel and its plugins. Every plugin implements one or more contracts, and the Kernel consumes them through dependency injection. diff --git a/content/docs/kernel/contracts/metadata-service.mdx b/content/docs/kernel/contracts/metadata-service.mdx index 617e1b29fe..4a730bc1b8 100644 --- a/content/docs/kernel/contracts/metadata-service.mdx +++ b/content/docs/kernel/contracts/metadata-service.mdx @@ -3,8 +3,6 @@ title: IMetadataService Contract description: Reference for the Metadata Service contract — CRUD operations for object and field definitions, schema registry, overlay management, and import/export --- -# IMetadataService Contract - The Metadata Service manages all object and field definitions at runtime. It serves as the **schema registry** — plugins, the Kernel, and the API layer all query this service to discover what objects exist and what fields they contain. diff --git a/content/docs/kernel/contracts/storage-service.mdx b/content/docs/kernel/contracts/storage-service.mdx index f689456cce..a4ed486941 100644 --- a/content/docs/kernel/contracts/storage-service.mdx +++ b/content/docs/kernel/contracts/storage-service.mdx @@ -3,8 +3,6 @@ title: IStorageService Contract description: Reference for the Storage Service contract — file upload, download, deletion, metadata, and signed URL generation --- -# IStorageService Contract - The Storage Service provides a unified interface for **file management** — uploading, downloading, and organizing files across different storage backends (local filesystem and S3-compatible object storage). diff --git a/content/docs/kernel/events.mdx b/content/docs/kernel/events.mdx index f119cd3f43..58260ec497 100644 --- a/content/docs/kernel/events.mdx +++ b/content/docs/kernel/events.mdx @@ -3,8 +3,6 @@ title: Events & Hooks description: System-wide event bus for loose coupling between plugins --- -# Events & Hooks - ObjectStack has **two distinct hook systems**. They look similar but use different APIs, payloads, ordering, and error semantics — pick the right one for the job: - **Kernel lifecycle hooks** — `ctx.hook(name, handler)` / `ctx.trigger(name, ...args)` on the `PluginContext`. Used for system bootstrap events and custom plugin-to-plugin events, matched by **exact name**. diff --git a/content/docs/kernel/index.mdx b/content/docs/kernel/index.mdx index f227739c77..7e362d6567 100644 --- a/content/docs/kernel/index.mdx +++ b/content/docs/kernel/index.mdx @@ -3,8 +3,6 @@ title: Kernel & Services description: The ObjectKernel runtime — plugin host, event bus, service registry, and the versioned services.* APIs your hooks and plugins call. --- -# Kernel & Services - The kernel is ObjectStack's runtime: it loads your metadata artifact, hosts plugins, wires up services, and enforces the lifecycle that keeps the system stable. This module documents the **ObjectOS layer** in practice — the [System Protocol](/docs/protocol/kernel) is its normative spec. If you write hooks or plugins, this is where the capability surface you call — `ctx.api`, `ctx.getService(...)`, and the `services.*` contract they implement — is documented. ## How it fits together diff --git a/content/docs/kernel/runtime-services/audit-service.mdx b/content/docs/kernel/runtime-services/audit-service.mdx index 94cf5fd581..a5d01e85a5 100644 --- a/content/docs/kernel/runtime-services/audit-service.mdx +++ b/content/docs/kernel/runtime-services/audit-service.mdx @@ -3,8 +3,6 @@ title: services.audit description: Write ingress for audit events that are not CRUD — today, auth session events. --- -# `services.audit` - - **Stability:** `experimental` - **Canonical source:** `packages/plugins/plugin-audit/src/auth-event-audit.ts` - **Registry slot:** `audit` — resolve with `ctx.getService('audit')`. diff --git a/content/docs/kernel/runtime-services/data-service.mdx b/content/docs/kernel/runtime-services/data-service.mdx index 97d7bf031a..86c7904a07 100644 --- a/content/docs/kernel/runtime-services/data-service.mdx +++ b/content/docs/kernel/runtime-services/data-service.mdx @@ -3,8 +3,6 @@ title: services.data description: CRUD runtime helper API for records (`query`, `get`, `find`, `create`, `update`, `delete`). --- -# `services.data` - - **Stability:** `stable` - **Canonical source:** `packages/client/src/index.ts` — the `ObjectStackClient.data` surface (see [Canonical source](#canonical-source) for why this page names the SDK diff --git a/content/docs/kernel/runtime-services/email-service.mdx b/content/docs/kernel/runtime-services/email-service.mdx index e1200cb603..0d51c57428 100644 --- a/content/docs/kernel/runtime-services/email-service.mdx +++ b/content/docs/kernel/runtime-services/email-service.mdx @@ -3,8 +3,6 @@ title: services.email description: Outbound email delivery and template rendering APIs. --- -# `services.email` - - **Stability:** `stable` - **Canonical source:** `packages/spec/src/contracts/email-service.ts` - **Registry slot:** `email` — resolve with `ctx.getService('email')`. diff --git a/content/docs/kernel/runtime-services/examples.mdx b/content/docs/kernel/runtime-services/examples.mdx index 7bd93f7bf5..5042f95060 100644 --- a/content/docs/kernel/runtime-services/examples.mdx +++ b/content/docs/kernel/runtime-services/examples.mdx @@ -3,8 +3,6 @@ title: Runtime Service Examples description: Practical examples for flow nodes, hooks, and plugin event subscriptions. --- -# Runtime Service Examples - These pages document the `services.*` **contract surface** — the signatures, not a diff --git a/content/docs/kernel/runtime-services/index.mdx b/content/docs/kernel/runtime-services/index.mdx index 1136b50c2b..e22e54eb8f 100644 --- a/content/docs/kernel/runtime-services/index.mdx +++ b/content/docs/kernel/runtime-services/index.mdx @@ -3,8 +3,6 @@ title: Runtime Service APIs description: Reference entry for runtime `services.*` APIs used by flow nodes, hooks, and plugins. --- -# Runtime Service APIs - **Binding note.** These pages document the stable `services.*` contract surface (signatures match the client SDK and `packages/spec/src/contracts/`). In this diff --git a/content/docs/kernel/runtime-services/queue-service.mdx b/content/docs/kernel/runtime-services/queue-service.mdx index 82d3213c68..05173d6a2a 100644 --- a/content/docs/kernel/runtime-services/queue-service.mdx +++ b/content/docs/kernel/runtime-services/queue-service.mdx @@ -3,8 +3,6 @@ title: services.queue description: Async queue publish/subscribe and DLQ operations. --- -# `services.queue` - - **Stability:** `stable` - **Canonical source:** `packages/spec/src/contracts/queue-service.ts` - **Registry slot:** `queue` — resolve with `ctx.getService('queue')`. diff --git a/content/docs/kernel/runtime-services/settings-service.mdx b/content/docs/kernel/runtime-services/settings-service.mdx index e75d0c23a6..dc3df073c5 100644 --- a/content/docs/kernel/runtime-services/settings-service.mdx +++ b/content/docs/kernel/runtime-services/settings-service.mdx @@ -3,8 +3,6 @@ title: services.settings description: Namespace-based configuration service with OS env/global/tenant/user/default cascade. --- -# `services.settings` - - **Stability:** `stable` - **Canonical source:** `packages/services/service-settings/src/settings-service.ts` - **Registry slot:** `settings` — resolve with `ctx.getService('settings')`. diff --git a/content/docs/kernel/runtime-services/sharing-service.mdx b/content/docs/kernel/runtime-services/sharing-service.mdx index 50cc041e6b..41b34c06cb 100644 --- a/content/docs/kernel/runtime-services/sharing-service.mdx +++ b/content/docs/kernel/runtime-services/sharing-service.mdx @@ -3,8 +3,6 @@ title: services.sharing description: Record-level sharing and editability checks. --- -# `services.sharing` - - **Stability:** `stable` - **Canonical source:** `packages/spec/src/contracts/sharing-service.ts` - **Registry slot:** `sharing` — resolve with `ctx.getService('sharing')`. diff --git a/content/docs/kernel/runtime-services/sms-service.mdx b/content/docs/kernel/runtime-services/sms-service.mdx index 66d78a5796..c6db503dca 100644 --- a/content/docs/kernel/runtime-services/sms-service.mdx +++ b/content/docs/kernel/runtime-services/sms-service.mdx @@ -3,8 +3,6 @@ title: services.sms description: Outbound SMS delivery through pluggable providers (Aliyun SMS, Twilio). --- -# `services.sms` - - **Stability:** `stable` - **Canonical source:** `packages/spec/src/contracts/sms-service.ts` - **Registry slot:** `sms` — resolve with `ctx.getService('sms')`. diff --git a/content/docs/kernel/runtime-services/storage-service.mdx b/content/docs/kernel/runtime-services/storage-service.mdx index 0f9be1fad7..8b996b2ec9 100644 --- a/content/docs/kernel/runtime-services/storage-service.mdx +++ b/content/docs/kernel/runtime-services/storage-service.mdx @@ -3,8 +3,6 @@ title: services.storage description: File/object storage contract for upload/download and presigned URL workflows. --- -# `services.storage` - - **Stability:** `stable` - **Canonical source:** `packages/spec/src/contracts/storage-service.ts` - **Registry slot:** `storage` — resolve with `ctx.getService('storage')`. diff --git a/content/docs/kernel/runtime-services/versioning.mdx b/content/docs/kernel/runtime-services/versioning.mdx index f140aa1fa8..fce76d5c60 100644 --- a/content/docs/kernel/runtime-services/versioning.mdx +++ b/content/docs/kernel/runtime-services/versioning.mdx @@ -3,8 +3,6 @@ title: Runtime Service API Versioning description: Stability labels and how to track runtime service API breaking changes. --- -# Runtime Service API Versioning - ## Stability Labels - `stable`: backward-compatible within the current major version. diff --git a/content/docs/kernel/services-checklist.mdx b/content/docs/kernel/services-checklist.mdx index 0148603842..9d42d30132 100644 --- a/content/docs/kernel/services-checklist.mdx +++ b/content/docs/kernel/services-checklist.mdx @@ -3,8 +3,6 @@ title: Kernel Services Checklist description: Complete inventory of ObjectStack kernel services with protocol methods, implementation status, and development requirements. --- -# Kernel Services Checklist - **This page is hand-maintained.** The per-slot Provider column mirrors `CORE_SERVICE_PROVIDER` (`packages/spec/src/system/core-services.zod.ts`), which diff --git a/content/docs/kernel/services.mdx b/content/docs/kernel/services.mdx index 2e566d2daf..337b52caa1 100644 --- a/content/docs/kernel/services.mdx +++ b/content/docs/kernel/services.mdx @@ -3,8 +3,6 @@ title: Service Registry description: Dependency Injection mechanism for loose coupling between plugins --- -# Service Registry - ObjectStack uses a lightweight **Service Locator pattern** for Dependency Injection. Services are the primary way plugins expose and consume functionality. ## Concepts diff --git a/content/docs/permissions/access-matrix.mdx b/content/docs/permissions/access-matrix.mdx index c57482c8ae..2037f301a8 100644 --- a/content/docs/permissions/access-matrix.mdx +++ b/content/docs/permissions/access-matrix.mdx @@ -3,8 +3,6 @@ title: Access-Matrix Snapshot Gate description: A committed (permission set × object) capability matrix that fails the build on any grant drift, with the semantic diff as the review artifact (ADR-0090 D6). --- -# Access-Matrix Snapshot Gate - AI drafts permission metadata on this platform; the one thing it must never do is *silently* change who can do what. The access-matrix gate makes every capability change a **visible, reviewable build event**: the diff --git a/content/docs/permissions/access-recipes.mdx b/content/docs/permissions/access-recipes.mdx index bbae0b22c6..62f7b046d9 100644 --- a/content/docs/permissions/access-recipes.mdx +++ b/content/docs/permissions/access-recipes.mdx @@ -3,8 +3,6 @@ title: Who can see data / automation / interface description: Map a concrete access requirement onto the platform's layers — object CRUD, field-level security, row-level security, capabilities, app/nav gating, and the run-identity of automations. --- -# Who can see data / automation / interface - ## Scenario > I have a real access requirement — "sales reps see only their own deals; managers see the team's; finance can export; nobody but admins touches the automations." How do these map onto the platform's authorization layers? diff --git a/content/docs/permissions/administrator-guide.mdx b/content/docs/permissions/administrator-guide.mdx index 7d38d5536f..8a8107fea4 100644 --- a/content/docs/permissions/administrator-guide.mdx +++ b/content/docs/permissions/administrator-guide.mdx @@ -3,8 +3,6 @@ title: Administrator Guide description: The task-first operations manual for customer system administrators — onboard a tenant in four steps (build the org tree, add people, assign positions, verify), with the 90% rule — daily administration is assigning positions; the capability plumbing ships built-in. --- -# Administrator Guide - You are the **system administrator** of an ObjectStack tenant. You opened Setup for the first time and found dozens of positions and permission sets you never created. This page is the *"I'm the admin — what do I actually do?"* diff --git a/content/docs/permissions/attachments-access.mdx b/content/docs/permissions/attachments-access.mdx index c7faba4edf..d4bc791ca4 100644 --- a/content/docs/permissions/attachments-access.mdx +++ b/content/docs/permissions/attachments-access.mdx @@ -3,8 +3,6 @@ title: Attachments Access description: How access to record attachments is decided — the parent-derived read/create/delete model, authenticated downloads, the enable.files opt-in gate, and the storage-byte lifecycle. Covers sys_attachment and sys_file. --- -# Attachments Access - The generic **Attachments** surface (Salesforce "Notes & Attachments" parity) separates *file storage* from *where a file is attached*: diff --git a/content/docs/permissions/authentication.mdx b/content/docs/permissions/authentication.mdx index a36cf7a943..e5e88119b2 100644 --- a/content/docs/permissions/authentication.mdx +++ b/content/docs/permissions/authentication.mdx @@ -3,7 +3,7 @@ title: Authentication description: "Complete guide to implementing authentication in ObjectStack using plugin-auth with Better-Auth" --- -# Authentication Guide +## Authentication Guide Complete guide to implementing authentication in ObjectStack applications using the `@objectstack/plugin-auth` package powered by Better-Auth. diff --git a/content/docs/permissions/authorization.mdx b/content/docs/permissions/authorization.mdx index 129d6958fe..80a8e78711 100644 --- a/content/docs/permissions/authorization.mdx +++ b/content/docs/permissions/authorization.mdx @@ -3,8 +3,6 @@ title: Authorization Architecture description: The one-page map of ObjectStack authorization — the enforcement chain, combination semantics, package provenance, lifecycle coverage, and the CI governance — with its limits — behind "declared" equals "enforced". Stitches ADR-0049/0054/0056/0057/0066/0068/0069/0078/0086 into a single narrative. --- -# Authorization Architecture - This page is the consolidated overview of how authorization works across the platform. Every decision summarized here is owned by an ADR (index at the bottom) — this page adds no new decisions; it exists so you don't have to read diff --git a/content/docs/permissions/capabilities.mdx b/content/docs/permissions/capabilities.mdx index 9c6ce5c6fc..bb2c9b9b62 100644 --- a/content/docs/permissions/capabilities.mdx +++ b/content/docs/permissions/capabilities.mdx @@ -3,8 +3,6 @@ title: "Declaring Capabilities" description: "How a package DEFINES an authorization capability with defineCapability — the declaration half of ADR-0066 D1 — and how that name travels from source to the sys_capability catalogue to a permission-set grant to a requiredPermissions check." --- -# Declaring Capabilities - Every other page in this module is about **consuming** a capability: granting one through a permission set, requiring one on an object or an action, reading one back in an access matrix. This page is about the other end — how a package diff --git a/content/docs/permissions/delegated-administration.mdx b/content/docs/permissions/delegated-administration.mdx index 1d61949068..79403d0c19 100644 --- a/content/docs/permissions/delegated-administration.mdx +++ b/content/docs/permissions/delegated-administration.mdx @@ -3,8 +3,6 @@ title: Delegated Administration description: Administration itself as a scoped grant — a business-unit subtree, an action set, and an assignable-set allowlist, with self-escalation structurally impossible (ADR-0090 D12). --- -# Delegated Administration - A group with fifty subsidiaries cannot manage every grant from headquarters — but handing each subsidiary a full admin is worse. ADR-0090 D12's answer: **administration itself becomes a scoped capability**. A delegate can onboard diff --git a/content/docs/permissions/explain.mdx b/content/docs/permissions/explain.mdx index 22ce906a27..8c1aa172c3 100644 --- a/content/docs/permissions/explain.mdx +++ b/content/docs/permissions/explain.mdx @@ -3,8 +3,6 @@ title: Explain Engine description: Ask the runtime why a decision came out the way it did — every pipeline layer, with contributor attribution, over the kernel service or REST (ADR-0090 D6). --- -# Explain Engine - Authorization here is a nine-layer pipeline, and a nine-layer pipeline nobody can interrogate is a support ticket generator. The explain engine (ADR-0090 D6) is the first-class answer to *"why can 张三 PATCH 李四's diff --git a/content/docs/permissions/field-level-security.mdx b/content/docs/permissions/field-level-security.mdx index bcc741d1d3..c29e4a8dfd 100644 --- a/content/docs/permissions/field-level-security.mdx +++ b/content/docs/permissions/field-level-security.mdx @@ -3,8 +3,6 @@ title: "Field-Level Security" description: "Control visibility and editability of specific fields — readable/editable rules, hidden vs. read-only semantics, and server-side enforcement of declared rules." --- -# Field-Level Security - Control visibility and editability of specific fields. ## Field Permissions in Permission Sets diff --git a/content/docs/permissions/index.mdx b/content/docs/permissions/index.mdx index f0d51548e8..49ed30ad3d 100644 --- a/content/docs/permissions/index.mdx +++ b/content/docs/permissions/index.mdx @@ -3,8 +3,6 @@ title: "Permissions & Identity" description: "Authentication, authorization, and record- and field-level access control in ObjectStack — a cross-protocol capability enforced by the ObjectStack runtime and declared as ObjectQL security metadata." --- -# Permissions & Identity - This module covers authentication, authorization, and record- and field-level access control. It is a cross-protocol capability: enforced by the ObjectStack runtime, declared as ObjectQL security metadata. diff --git a/content/docs/permissions/permission-metadata.mdx b/content/docs/permissions/permission-metadata.mdx index de1232a132..33d49dc59d 100644 --- a/content/docs/permissions/permission-metadata.mdx +++ b/content/docs/permissions/permission-metadata.mdx @@ -3,8 +3,6 @@ title: Permission Metadata description: Define access control with permission sets — object CRUD, field security, tab visibility, and row-level security --- -# Permission Metadata - A **Permission Set** defines what a user can do within the application. It controls object-level CRUD operations, field-level visibility, tab/app access, and row-level security policies. ## Basic Structure diff --git a/content/docs/permissions/permission-sets.mdx b/content/docs/permissions/permission-sets.mdx index 2666720366..05081b0250 100644 --- a/content/docs/permissions/permission-sets.mdx +++ b/content/docs/permissions/permission-sets.mdx @@ -3,8 +3,6 @@ title: "Permission Sets" description: "The only capability container — object CRUD + FLS + scope depth + capabilities, union-merged across everything a user holds. Covers the built-in sets, assignment tables, access depth, the isDefault suggestion, and delegated-admin scopes." --- -# Permission Sets - A **permission set** is the *only* capability container in the platform (ADR-0090): a named bundle of object CRUD grants, field-level security, scope depth, tab visibility, system capabilities, and (optionally) RLS policies. diff --git a/content/docs/permissions/permissions-matrix.mdx b/content/docs/permissions/permissions-matrix.mdx index 80d42a0d1d..4b77ab6ba8 100644 --- a/content/docs/permissions/permissions-matrix.mdx +++ b/content/docs/permissions/permissions-matrix.mdx @@ -3,8 +3,6 @@ title: Security Permissions Matrix description: Visual reference for ObjectStack's security model — permission types, the object × permission-set matrix, field-level security, sharing rules, business-unit depth, and the access-matrix snapshot gate --- -# Security Permissions Matrix - This page provides a comprehensive visual reference for ObjectStack's security model — from object-level permissions to field-level security, sharing rules, and business-unit depth. diff --git a/content/docs/permissions/positions.mdx b/content/docs/permissions/positions.mdx index 4781387077..83a159e74b 100644 --- a/content/docs/permissions/positions.mdx +++ b/content/docs/permissions/positions.mdx @@ -3,8 +3,6 @@ title: "Positions" description: "Positions (岗位) are flat capability-distribution groups — users hold positions, positions bind permission sets. The visibility hierarchy lives on business units, never here. Includes the built-in identity positions and the everyone/guest audience anchors." --- -# Positions - A **position** (岗位) is a named, assignable distribution group: users hold positions (`sys_user_position` rows), positions bind permission sets (`sys_position_permission_set` rows), and a user's capability is the union of diff --git a/content/docs/permissions/profiles.mdx b/content/docs/permissions/profiles.mdx index 12f75b347b..7ba43de04a 100644 --- a/content/docs/permissions/profiles.mdx +++ b/content/docs/permissions/profiles.mdx @@ -3,7 +3,7 @@ title: "Profiles (removed)" description: "The Profile concept was removed by ADR-0090 D2. Baseline access is now authored with the everyone audience anchor, package isDefault suggestions, and ordinary permission sets distributed via positions." --- -# Profiles — removed (ADR-0090 D2) +## Profiles — removed (ADR-0090 D2) ObjectStack **no longer has a Profile concept**. It was removed pre-launch (ADR-0090 D2) because it duplicated permission sets while adding a second diff --git a/content/docs/permissions/record-view-auditing.mdx b/content/docs/permissions/record-view-auditing.mdx index 3fd36bb1f1..875bf00f93 100644 --- a/content/docs/permissions/record-view-auditing.mdx +++ b/content/docs/permissions/record-view-auditing.mdx @@ -3,8 +3,6 @@ title: Record-View Auditing description: "Who viewed this record, and when — the `read` action in sys_audit_log: its per-object opt-in, the four edges of its scope, and what a view row deliberately does not carry." --- -# Record-View Auditing - Every other layer in this module answers *who is allowed to see this record*. This page answers the question that comes after it, and that every regulated industry review opens with: **who actually opened it, and when?** diff --git a/content/docs/permissions/rls.mdx b/content/docs/permissions/rls.mdx index 37cf4dc1d0..0bad5c5573 100644 --- a/content/docs/permissions/rls.mdx +++ b/content/docs/permissions/rls.mdx @@ -3,8 +3,6 @@ title: Row-Level Security (RLS) description: Declarative per-row policies compiled into query filters — the grammar, the context variables, how policies compose, and the fail-closed contract. --- -# Row-Level Security (RLS) - RLS answers a question the other layers can't: *which rows* of an object may this user touch? Object permissions say "can read tasks"; the OWD baseline says "tasks are private"; RLS says **"only the tasks assigned to you"** — as a diff --git a/content/docs/permissions/sharing-rules.mdx b/content/docs/permissions/sharing-rules.mdx index d587d676b5..0fd68bd8c8 100644 --- a/content/docs/permissions/sharing-rules.mdx +++ b/content/docs/permissions/sharing-rules.mdx @@ -3,7 +3,7 @@ title: "Sharing Rules" description: "Record-level access: the organization-wide default (OWD) baseline per object, the external sharing dial, criteria sharing rules and recipient types, and the RLS-safe analytics read scope." --- -# Sharing & Organization-Wide Defaults +## Sharing & Organization-Wide Defaults Record-level access starts from each object's **organization-wide default (OWD)** and can only be **widened** from there — by scope-depth grants, diff --git a/content/docs/permissions/sso.mdx b/content/docs/permissions/sso.mdx index c2769438ee..15f26da01d 100644 --- a/content/docs/permissions/sso.mdx +++ b/content/docs/permissions/sso.mdx @@ -3,8 +3,6 @@ title: Social & Enterprise SSO description: Enable Google sign-in in open source, and extend auth providers through packages. --- -# Social & Enterprise SSO - ObjectStack open source ships two built-in social sign-in implementations, both wired by `os serve` from deployment env vars: - **Google OAuth**: configure in Setup → Authentication, or from diff --git a/content/docs/permissions/system-context.mdx b/content/docs/permissions/system-context.mdx index 2c322aed06..8ca05799f6 100644 --- a/content/docs/permissions/system-context.mdx +++ b/content/docs/permissions/system-context.mdx @@ -3,8 +3,6 @@ title: System Context (isSystem) description: The authoritative table of every platform behaviour keyed off `ExecutionContext.isSystem` — what an elevated write gets, what it loses, and what the flag deliberately does NOT do. Built by census over the whole repo, not by recall. --- -# System Context (`isSystem`) - `ExecutionContext.isSystem` is the platform's one elevation flag. Setting it on a write or read means "this operation is the engine acting on its own behalf" — the seed loader replaying package fixtures, a plugin's boot reconciler, a diff --git a/content/docs/plugins/adding-a-metadata-type.mdx b/content/docs/plugins/adding-a-metadata-type.mdx index 6248bf5ad2..0d8aac4c17 100644 --- a/content/docs/plugins/adding-a-metadata-type.mdx +++ b/content/docs/plugins/adding-a-metadata-type.mdx @@ -3,8 +3,6 @@ title: Adding a Metadata Type description: How to register a new metadata type so it shows up in the Studio app's Metadata Admin with full CRUD, overlay diffing, and (optionally) a custom editor. --- -# Adding a Metadata Type - The **Metadata Admin** engine (Studio app → *All Metadata Types*) automatically renders a directory tile, list page, schema-driven form, layered diff view, quick-find palette, and version history for **every** type registered in the diff --git a/content/docs/plugins/anatomy.mdx b/content/docs/plugins/anatomy.mdx index a7525c3c03..af4f0da0ee 100644 --- a/content/docs/plugins/anatomy.mdx +++ b/content/docs/plugins/anatomy.mdx @@ -3,8 +3,6 @@ title: Plugin Anatomy description: The Plugin contract — interface, context, and lifecycle phases every ObjectStack plugin implements --- -# Plugin Anatomy - Plugins are the building blocks of ObjectStack. A plugin is a plain JavaScript/TypeScript object (or class) that conforms to the `Plugin` interface. ## Anatomy of a Plugin diff --git a/content/docs/plugins/development.mdx b/content/docs/plugins/development.mdx index 5fe711e711..0324f7bb42 100644 --- a/content/docs/plugins/development.mdx +++ b/content/docs/plugins/development.mdx @@ -3,7 +3,7 @@ title: Plugin Development description: Step-by-step guide to creating, testing, and publishing ObjectStack plugins --- -# Plugin Development Tutorial +## Plugin Development Tutorial This guide walks you through creating an ObjectStack plugin from scratch — from project setup to testing and registration. diff --git a/content/docs/plugins/index.mdx b/content/docs/plugins/index.mdx index a694603244..ee038ae916 100644 --- a/content/docs/plugins/index.mdx +++ b/content/docs/plugins/index.mdx @@ -3,8 +3,6 @@ title: Plugin System description: Overview of the ObjectStack plugin architecture — plugin types, configuration, management, and built-in plugins. --- -# Plugin System - ObjectStack is built on a **microkernel architecture** where nearly everything beyond the core data engine is delivered as a **plugin**. This guide covers how to create, configure, and manage plugins using the CLI and configuration. diff --git a/content/docs/plugins/packages.mdx b/content/docs/plugins/packages.mdx index aecb72437a..ee7ab1d360 100644 --- a/content/docs/plugins/packages.mdx +++ b/content/docs/plugins/packages.mdx @@ -3,8 +3,6 @@ title: Package Overview description: Complete guide to all ObjectStack packages, services, drivers, plugins, and adapters --- -# Package Overview - ObjectStack is organized into **72 package manifests** across multiple categories. This guide provides an overview of the framework packages, services, drivers, plugins, and adapters in the [framework repository](https://github.com/objectstack-ai/objectstack/tree/main/packages). ### Package categories at a glance diff --git a/content/docs/protocol/backward-compatibility.mdx b/content/docs/protocol/backward-compatibility.mdx index 0452067156..9f5372f1c3 100644 --- a/content/docs/protocol/backward-compatibility.mdx +++ b/content/docs/protocol/backward-compatibility.mdx @@ -3,8 +3,6 @@ title: Backward Compatibility Policy description: Versioning strategy, deprecation timelines, breaking change policy, and migration support for ObjectStack --- -# Backward Compatibility Policy - ObjectStack follows strict backward compatibility guarantees to ensure predictable upgrades and long-term stability for production deployments. diff --git a/content/docs/protocol/diagram.mdx b/content/docs/protocol/diagram.mdx index 391db33be4..ec1b9f1c42 100644 --- a/content/docs/protocol/diagram.mdx +++ b/content/docs/protocol/diagram.mdx @@ -3,8 +3,6 @@ title: Protocol Relationship Diagram description: Visual architecture diagram showing how ObjectStack's protocol layers connect — Data, UI, System, Automation, AI, API, and Security --- -# Protocol Relationship Diagram - ObjectStack is composed of seven protocol layers that work together to form a complete application platform. This page provides visual diagrams showing how these layers connect and how data flows through the system. diff --git a/content/docs/protocol/index.mdx b/content/docs/protocol/index.mdx index 2e35c6c842..c87754654f 100644 --- a/content/docs/protocol/index.mdx +++ b/content/docs/protocol/index.mdx @@ -5,8 +5,6 @@ description: The formal ObjectStack protocol for Data, UI, and System layers import { Database, Layout, Cpu } from 'lucide-react'; -# Protocol Specification - The ObjectStack Protocol defines three interconnected layers that together describe a complete enterprise application: diff --git a/content/docs/protocol/kernel/config-resolution.mdx b/content/docs/protocol/kernel/config-resolution.mdx index 4d2e23b2b8..cc73f628f5 100644 --- a/content/docs/protocol/kernel/config-resolution.mdx +++ b/content/docs/protocol/kernel/config-resolution.mdx @@ -3,8 +3,6 @@ title: Configuration Resolution description: How a setting resolves — the env → global → tenant → user → default cascade, its storage, and the SettingsService API --- -# Configuration Resolution - **This page describes the shipped contract.** Every symbol below exists in the repository today: the resolver is `SettingsService` diff --git a/content/docs/protocol/kernel/error-handling.mdx b/content/docs/protocol/kernel/error-handling.mdx index 287e058530..74ca26bbf2 100644 --- a/content/docs/protocol/kernel/error-handling.mdx +++ b/content/docs/protocol/kernel/error-handling.mdx @@ -5,8 +5,6 @@ description: Global error codes, response formats, and debugging strategies for import { AlertCircle, Bug, Shield, Info, AlertTriangle, XCircle, Radio, Zap } from 'lucide-react'; -# Error Handling - The **Error Handling Protocol** defines standardized error codes, response formats, and debugging strategies across all ObjectStack APIs (HTTP, WebSocket). ## Why Standardized Errors Matter diff --git a/content/docs/protocol/kernel/http-protocol.mdx b/content/docs/protocol/kernel/http-protocol.mdx index 438c90e3ce..b4880e72c2 100644 --- a/content/docs/protocol/kernel/http-protocol.mdx +++ b/content/docs/protocol/kernel/http-protocol.mdx @@ -5,8 +5,6 @@ description: Standard REST mapping rules, CRUD operations, and request/response import { Radio, Code, Database, Lock, Zap, CheckCircle, AlertCircle } from 'lucide-react'; -# HTTP API - The **HTTP API** defines how ObjectStack maps data operations to RESTful HTTP endpoints. Every object you define automatically gets a complete set of CRUD (Create, Read, Update, Delete) operations with consistent request/response formats. ## Core Principles diff --git a/content/docs/protocol/kernel/i18n-standard.mdx b/content/docs/protocol/kernel/i18n-standard.mdx index 713930d8db..9f41798a29 100644 --- a/content/docs/protocol/kernel/i18n-standard.mdx +++ b/content/docs/protocol/kernel/i18n-standard.mdx @@ -5,7 +5,7 @@ description: Translation bundles, locale resolution, pluralization, date/number import { Globe, Languages, Calendar, Hash, FileText, MapPin } from 'lucide-react'; -# Internationalization (i18n) Standard +## Internationalization (i18n) Standard **Protocol spec, partial implementation.** The runtime i18n service diff --git a/content/docs/protocol/kernel/lifecycle.mdx b/content/docs/protocol/kernel/lifecycle.mdx index 237a27f9e0..4c2840bd40 100644 --- a/content/docs/protocol/kernel/lifecycle.mdx +++ b/content/docs/protocol/kernel/lifecycle.mdx @@ -5,8 +5,6 @@ description: Boot sequence, plugin installation, zero-downtime upgrades, and rol import { Zap, Power, Package, RefreshCw, ArrowLeft, CheckCircle, XCircle, AlertTriangle } from 'lucide-react'; -# System Lifecycle - **Protocol spec.** The boot sequence, schema-evolution model, and graceful shutdown described below reflect the current `@objectstack/core` and CLI diff --git a/content/docs/protocol/kernel/plugin-spec.mdx b/content/docs/protocol/kernel/plugin-spec.mdx index 58fef36f04..04367a2185 100644 --- a/content/docs/protocol/kernel/plugin-spec.mdx +++ b/content/docs/protocol/kernel/plugin-spec.mdx @@ -5,8 +5,6 @@ description: Manifest structure, directory layout, dependency management, and di import { Package, FileCode, Folder, Box, Link2, Shield, Settings } from 'lucide-react'; -# Plugin Package Specification - **Protocol spec.** This page describes the **target** plugin packaging specification — an ergonomic manifest wrapper, semantic-version dependency diff --git a/content/docs/protocol/kernel/realtime-protocol.mdx b/content/docs/protocol/kernel/realtime-protocol.mdx index 6da9dc7ff8..b7ef312e41 100644 --- a/content/docs/protocol/kernel/realtime-protocol.mdx +++ b/content/docs/protocol/kernel/realtime-protocol.mdx @@ -5,8 +5,6 @@ description: WebSocket subscriptions, Server-Sent Events, and event-driven commu import { Zap, Radio, Activity, Bell, Users, Gauge, AlertCircle } from 'lucide-react'; -# Real-Time Protocols - The **Real-Time Protocol** describes how live data synchronization is intended to work between clients and servers. Get instant updates when data changes without polling. diff --git a/content/docs/protocol/knowledge.mdx b/content/docs/protocol/knowledge.mdx index 6c32b95c28..7bead96e55 100644 --- a/content/docs/protocol/knowledge.mdx +++ b/content/docs/protocol/knowledge.mdx @@ -3,8 +3,6 @@ title: Knowledge Protocol description: Pluggable RAG / knowledge retrieval for ObjectStack agents — protocol + adapter model. --- -# Knowledge Protocol - ObjectStack does not ship its own RAG engine. The market has many mature options (RAGFlow, LlamaIndex, Dify, Vectara, pgvector + custom pipelines) and competing with them on chunking, embeddings, hybrid retrieval, and diff --git a/content/docs/protocol/objectql/security.mdx b/content/docs/protocol/objectql/security.mdx index 9a10b8bc91..db0f8d3393 100644 --- a/content/docs/protocol/objectql/security.mdx +++ b/content/docs/protocol/objectql/security.mdx @@ -5,7 +5,7 @@ description: Comprehensive security model - ACL, field-level security, row-level import { Shield, Lock, Users, Eye } from 'lucide-react'; -# Security Protocol +## Security Protocol ObjectStack implements a **multi-layered security model** that enforces access control at the data layer, before queries execute. Security is declarative—defined as metadata (permission sets, row-level policies, sharing rules), not scattered in application code. diff --git a/content/docs/protocol/objectql/state-machine.mdx b/content/docs/protocol/objectql/state-machine.mdx index 4b69171dd2..0b0617b2cd 100644 --- a/content/docs/protocol/objectql/state-machine.mdx +++ b/content/docs/protocol/objectql/state-machine.mdx @@ -3,7 +3,7 @@ title: State Machine (Lifecycle) description: Define strict business logic constraints prevents AI hallucinations by enforcing valid transitions. --- -# State Machine Protocol +## State Machine Protocol The **State Machine** (`state_machine` validation rule) lets you define the "Constitution" of a record's lifecycle: the legal `status` transitions a record may take. It is a **flat, textbook finite-state-machine** transition table that the write path enforces. diff --git a/content/docs/ui/actions.mdx b/content/docs/ui/actions.mdx index d3246d49f8..0ad92acebf 100644 --- a/content/docs/ui/actions.mdx +++ b/content/docs/ui/actions.mdx @@ -3,8 +3,6 @@ title: Actions description: Declarative buttons with server-side behavior — define once, bind to lists, records, and navigation, permission-check on both surfaces, and optionally expose to AI. --- -# Actions - An action is a **button declared as metadata**: where it appears (`locations`), when it's visible (`visible`), who may run it (`requiredPermissions`), and what it executes — an inline sandboxed script, a diff --git a/content/docs/ui/apps.mdx b/content/docs/ui/apps.mdx index becc2169bf..6dbcb4124c 100644 --- a/content/docs/ui/apps.mdx +++ b/content/docs/ui/apps.mdx @@ -3,8 +3,6 @@ title: App Metadata description: Define application containers with navigation, branding, and access control --- -# App Metadata - An **App** is a logical container that bundles objects, views, pages, and dashboards into a cohesive application experience. It defines the navigation structure, branding, and access permissions. ## Basic Structure diff --git a/content/docs/ui/audience-based-interfaces.mdx b/content/docs/ui/audience-based-interfaces.mdx index faa76ea42c..a8bdee9672 100644 --- a/content/docs/ui/audience-based-interfaces.mdx +++ b/content/docs/ui/audience-based-interfaces.mdx @@ -3,8 +3,6 @@ title: Audience-based interfaces description: The same data serves different audiences. Give end users a curated app/page and keep builder surfaces — Studio, raw object tables, automation config — out of their view. Separate consumer and builder paths by default. --- -# Audience-based interfaces - ## Scenario > The same object is touched by two very different audiences: **builders** who design the schema and **end users** who just enter and read data. How do I give each the right surface — without end users seeing (and being confused by) the builder chrome? diff --git a/content/docs/ui/create-vs-edit-form.mdx b/content/docs/ui/create-vs-edit-form.mdx index 3e6314731a..bc300adc36 100644 --- a/content/docs/ui/create-vs-edit-form.mdx +++ b/content/docs/ui/create-vs-edit-form.mdx @@ -3,8 +3,6 @@ title: Create form ≠ edit form description: The new-record form asks 5 fields; the full edit form shows 40 grouped into sections. Derive both from one flat field set; only hand-shape the create form when layout or flow genuinely diverges. --- -# Create form ≠ edit form - ## Scenario > The form for **creating** a record should ask only a handful of fields (the essentials). The form for **editing** an existing record shows the full record, grouped into sections. How do I model this without maintaining two field lists that drift apart? diff --git a/content/docs/ui/dashboards.mdx b/content/docs/ui/dashboards.mdx index 08164f6dfd..e8942c5745 100644 --- a/content/docs/ui/dashboards.mdx +++ b/content/docs/ui/dashboards.mdx @@ -3,8 +3,6 @@ title: Dashboard Metadata description: Build analytics dashboards with chart widgets, global filters, and auto-refresh --- -# Dashboard Metadata - A **Dashboard** defines an analytics page with chart widgets, key metrics, and data visualizations. Dashboards support configurable layouts, global date filters, and auto-refresh. ## Basic Structure diff --git a/content/docs/ui/doc-pages.mdx b/content/docs/ui/doc-pages.mdx index bc40c78265..8e9d478cfe 100644 --- a/content/docs/ui/doc-pages.mdx +++ b/content/docs/ui/doc-pages.mdx @@ -3,8 +3,6 @@ title: Doc Metadata description: Ship package documentation as metadata — flat src/docs/*.md files compiled into the manifest and rendered in the console, ordered by a book navigation spine --- -# Doc Metadata - A **Doc** is a single page of package documentation. You write plain Markdown files in a flat `src/docs/` directory; `os build` compiles each one into a `doc` metadata item that ships inside the package artifact and diff --git a/content/docs/ui/field-grouping-and-order.mdx b/content/docs/ui/field-grouping-and-order.mdx index abe7db36cc..c4abacabde 100644 --- a/content/docs/ui/field-grouping-and-order.mdx +++ b/content/docs/ui/field-grouping-and-order.mdx @@ -3,8 +3,6 @@ title: Field grouping & order description: The data model is a flat field set, but forms need sections. Where grouping actually lives — semantic field.group vs form sections vs a table's row grouping — and why those three "groups" are different things. --- -# Field grouping & order - ## Scenario > From the object-definition angle, fields are **flat**. In a table they're flat columns too. But a form needs them **grouped** into sections. Where does the grouping live — on the field, or on the form? And do I have to re-group on every form? diff --git a/content/docs/ui/forms.mdx b/content/docs/ui/forms.mdx index 8db251f4a4..a227915a07 100644 --- a/content/docs/ui/forms.mdx +++ b/content/docs/ui/forms.mdx @@ -3,7 +3,7 @@ title: Forms (Public + Internal) description: Render any FormView either publicly (anonymous, /f/:slug) or internally (authed operators, /forms/:name). The same metadata drives both, with URL prefill, configurable post-submit behavior, and declarative open-form actions. --- -# Forms +## Forms ObjectStack Forms are Airtable-style **metadata-driven** forms with two render modes that share **one** spec and **one** renderer: diff --git a/content/docs/ui/index.mdx b/content/docs/ui/index.mdx index d9e11ec51b..3a8454334d 100644 --- a/content/docs/ui/index.mdx +++ b/content/docs/ui/index.mdx @@ -3,8 +3,6 @@ title: UI Engine description: Apps, views, dashboards, branding, and public forms — server-driven UI declared as metadata and rendered by the ObjectUI runtime. --- -# UI Engine - The UI engine turns metadata into user interfaces: you declare **apps**, **views**, **pages**, and **dashboards** as data, and any compliant renderer draws them. This module is the practical documentation for the **ObjectUI layer** — the UI Protocol specified normatively in the [ObjectUI spec](/docs/protocol/objectui). Because the UI is data, it ships inside the same artifact as your schema, respects the same permissions, and can be generated or modified by AI agents through the same typed surface. An app is a navigation shell over your objects (from the CRM example app): diff --git a/content/docs/ui/pages.mdx b/content/docs/ui/pages.mdx index 47cadf0fc4..38192b10eb 100644 --- a/content/docs/ui/pages.mdx +++ b/content/docs/ui/pages.mdx @@ -3,8 +3,6 @@ title: Page Metadata description: Build custom pages with component-based layouts, variables, and event handling --- -# Page Metadata - A **Page** defines a custom UI layout using components, regions, and variables. Unlike Views which are bound to a single Object, Pages are flexible containers that can combine multiple components, embed views, and manage local state. ## Basic Structure diff --git a/content/docs/ui/public-data-collection.mdx b/content/docs/ui/public-data-collection.mdx index 0d9e91a411..e003460986 100644 --- a/content/docs/ui/public-data-collection.mdx +++ b/content/docs/ui/public-data-collection.mdx @@ -3,8 +3,6 @@ title: Collect data from the public description: Expose one form to anonymous visitors (web-to-lead, contact-us, intake) without opening the underlying base. Authorization is derived from the form's own declaration; only whitelisted fields are accepted. --- -# Collect data from the public - ## Scenario > I want anonymous visitors to submit a form — a "Contact us" / "Request a demo" / intake form — that creates a record. But I must **not** give them access to the base, and they must only be able to set the fields on the form. diff --git a/content/docs/ui/react-pages.mdx b/content/docs/ui/react-pages.mdx index b4ec8fb31a..192c750dc5 100644 --- a/content/docs/ui/react-pages.mdx +++ b/content/docs/ui/react-pages.mdx @@ -3,8 +3,6 @@ title: React Pages description: Author a page body as real React (kind:'react') or as constrained JSX that is parsed and never executed (kind:'html') — the two source-authoring tiers, and how to choose --- -# React Pages - Most pages are a **schema tree**: `regions[].components[]` of JSON nodes, described in [Page Metadata](/docs/ui/pages). Two page kinds let you write the body as a **source string** instead, for layouts and interactions the fixed schema cannot express. diff --git a/content/docs/ui/reports.mdx b/content/docs/ui/reports.mdx index 24b1c919bb..54ce5bdaca 100644 --- a/content/docs/ui/reports.mdx +++ b/content/docs/ui/reports.mdx @@ -3,8 +3,6 @@ title: Report Metadata description: Analytics reports as metadata — the four report shapes, dataset binding, drill-through, and how a report differs from a list view and a dashboard widget --- -# Report Metadata - A **Report** is an analytics artifact. It groups and aggregates the rows of a [dataset](/docs/data-modeling/analytics) into a pivot — grouped down-axis rows, optionally pivoted across a second axis, with measures in the cells — and gives that diff --git a/content/docs/ui/setup-app.mdx b/content/docs/ui/setup-app.mdx index 54ecd774a9..ca6baf5dbd 100644 --- a/content/docs/ui/setup-app.mdx +++ b/content/docs/ui/setup-app.mdx @@ -3,8 +3,6 @@ title: Setup App description: The platform's built-in administration UI — what it is, what it shows, and how it is wired --- -# Setup App - The **Setup App** (`/apps/setup`) is the built-in administration console for every ObjectStack project. It surfaces the platform `sys_*` objects, the System Overview dashboard, and a set of settings pages behind a diff --git a/content/docs/ui/translations.mdx b/content/docs/ui/translations.mdx index 0c48219455..94176a9c6e 100644 --- a/content/docs/ui/translations.mdx +++ b/content/docs/ui/translations.mdx @@ -3,8 +3,6 @@ title: Translations description: Labels and UI text as metadata — one bundle per locale, resolved per request, with CLI tooling to draft and to gate coverage in CI. --- -# Translations - Every label in an ObjectStack app — object and field names, picklist options, view titles, action buttons, app navigation — is metadata, not a string baked into a component. Adding a language means adding a **bundle**, not touching the diff --git a/content/docs/ui/views.mdx b/content/docs/ui/views.mdx index 2acebdacce..ff0068ccae 100644 --- a/content/docs/ui/views.mdx +++ b/content/docs/ui/views.mdx @@ -3,8 +3,6 @@ title: View Metadata description: Configure list views and form views — grid, kanban, calendar, gantt, and more --- -# View Metadata - A **View** defines how records of an Object are displayed to users. ObjectStack supports two main view categories: **List Views** for browsing records and **Form Views** for editing individual records. ## The `defineView` Container From 274e157a3972c1ff7037b1820a96a37c1c8246b1 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Tue, 25 Aug 2026 23:08:23 +0800 Subject: [PATCH 2/2] chore(scripts): add check:docs-single-h1 so a body h1 cannot come back Fences are stripped before anything looks for a heading: a naive scan reports 205 files, ten of which are shell comments inside working examples. The frontmatter and heading-text models are imported from check-doc-anchors rather than retyped, so the gate cannot disagree with the renderer about what a heading says. Two subtrees are excluded with named owners, and an exclusion whose tree is clean is a FAILURE, so the carve-outs retire themselves. Co-Authored-By: Claude Opus 5 --- .github/workflows/lint.yml | 26 ++ package.json | 1 + scripts/check-docs-single-h1.mjs | 437 +++++++++++++++++++++++++++++++ 3 files changed, 464 insertions(+) create mode 100644 scripts/check-docs-single-h1.mjs diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 14b9d38fe4..18d3761815 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1071,6 +1071,32 @@ jobs: - name: Docs anchors resolve to real headings run: pnpm check:doc-anchors + # #12236 one

per doc page. `DocsTitle` renders the frontmatter `title` + # as the page's

unconditionally (apps/docs/app/[lang]/docs/[[...slug]]/ + # page.tsx), so a body-level `# ` heading compiles to a SECOND one inside + # `DocsBody`. Measured on the dev server at /docs/data-modeling/objects + # before the sweep: two

, both reading "Object Metadata". 195 of the 403 + # pages were in that state, 138 of them with the two

identical. + # + # It lives next to check:doc-anchors because the two share the corpus and + # the heading model — this gate imports `stripFrontmatter` and + # `flattenHeadingText` from that one rather than re-modelling either, which + # is what makes the delete-vs-demote remedy right on the nine pages whose + # two headings differ only by inline code markup. + # + # The gate strips FENCED CODE before looking for a heading, and that is the + # load-bearing half: a naive `^# ` scan reports 205 files, ten of which are + # shell/YAML comments inside working examples. A fence-blind rule does not + # over-report by ten, it instructs the fixer to corrupt ten snippets. + # + # Two subtrees are excluded, each with a named owner (#12249 generated + # reference pages, #12250 release notes) and each self-retiring: the gate + # FAILS with DEAD-EXCLUSION when an excluded tree becomes clean, so the + # carve-outs cannot outlive their reasons. A run that judges zero pages + # fails too (#4690). + - name: One `

` per docs page + run: pnpm check:docs-single-h1 + # #9014 docs redirects: `apps/docs/redirects.mjs` is a 92-entry table of # PERMANENT (308) redirects, and before this step nothing in the repo read # it — `dispatch-gates.mjs apps/docs/redirects.mjs` named no family over 99 diff --git a/package.json b/package.json index fa949f9228..f80db02cce 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "check:stack-collection-maps": "node scripts/check-stack-collection-maps.mjs --self-test && node scripts/check-stack-collection-maps.mjs", "check:doc-authoring": "node scripts/check-doc-authoring.mjs --self-test && node scripts/check-doc-authoring.mjs", "check:doc-anchors": "node scripts/check-doc-anchors.mjs --self-test && node scripts/check-doc-anchors.mjs", + "check:docs-single-h1": "node scripts/check-docs-single-h1.mjs --self-test && node scripts/check-docs-single-h1.mjs", "check:docs-audit-scope": "node scripts/docs-audit/affected-docs.mjs --self-test && node scripts/docs-audit/check-audit-scope.mjs --self-test && node scripts/docs-audit/check-audit-scope.mjs", "check:docs-redirects": "node scripts/check-docs-redirects.mjs --self-test && node scripts/check-docs-redirects.mjs", "check:docs-image-tag": "node scripts/check-docs-image-tag.mjs --self-test && node scripts/check-docs-image-tag.mjs", diff --git a/scripts/check-docs-single-h1.mjs b/scripts/check-docs-single-h1.mjs new file mode 100644 index 0000000000..4a3b92bfaf --- /dev/null +++ b/scripts/check-docs-single-h1.mjs @@ -0,0 +1,437 @@ +#!/usr/bin/env node +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// check-docs-single-h1 (#12236) — no page under `content/docs/**` may contain a +// body-level `# ` heading, because the site already renders one `

` per page +// from the frontmatter `title` and a second one is not authorable, it is a bug. +// +// node scripts/check-docs-single-h1.mjs +// node scripts/check-docs-single-h1.mjs --list # every page and its verdict +// node scripts/check-docs-single-h1.mjs --self-test # prove the battery can go red +// +// ## The invariant, and where the other `

` comes from +// +// `apps/docs/app/[lang]/docs/[[...slug]]/page.tsx` renders every doc page as +// +// {page.data.title} +// ... +// +// +// `DocsTitle` is an `

`. So the frontmatter `title` IS the page's heading-one, +// unconditionally, on all 403 pages — and any `# ` the author writes in the body +// compiles to a SECOND `

` inside `DocsBody`. Measured on the dev server at +// `/docs/data-modeling/objects` before this gate landed: two `

` elements, +// both reading `Object Metadata`, because the page's frontmatter said +// `title: Object Metadata` and its body opened with `# Object Metadata`. +// +// That was true of 195 of the 403 pages, and on 138 of those the two `

` were +// the same rendered string. Two `

` split the strongest on-page signal a +// document has and a screen reader announces the title twice. +// +// The remedy has two shapes and this gate names which one applies (see +// `remedyFor`): when the body heading renders the same text as the title, DELETE +// it — the page loses nothing. When it says something different, DEMOTE it to +// `## `, which keeps the wording and keeps the slug, so no inbound `#anchor` +// breaks. Levels below one are never a finding: this gate has exactly one +// opinion and `check:doc-anchors` owns the rest of the heading surface. +// +// ## Why fenced code is stripped FIRST, and why that is the whole gate +// +// This is the measurement that decides whether this file is a gate or a hazard. +// A naive `^# ` scan over `content/docs/**` reports 205 files. The real number is +// 195. The ten-file gap is entirely shell and YAML comments inside fenced code +// blocks — they open a line with `# ` and render as CODE, not as a heading: +// +// content/docs/getting-started/index.mdx # Install pnpm globally +// content/docs/protocol/objectql/index.mdx # customer.object.yml +// content/docs/upgrading.mdx # docker-compose.yml, or your ... +// ...and seven more +// +// A fence-blind gate does not merely over-report by ten. It instructs an author +// — or a script-driven sweep, which is how a 195-file change gets made — to +// delete or demote a line inside a working example, silently corrupting ten +// copy-pasteable snippets to satisfy a check about HTML that those lines never +// produced. The false positive here is more expensive than the defect. +// +// So `stripFencedBlocks` runs before anything looks for a heading, and the +// self-test carries a control for both fence spellings (``` and ~~~). +// +// ## Why nothing here re-models Markdown +// +// Three things had to be decided — where the frontmatter ends, where a fenced +// block ends, and what text a heading renders as — and this repo already owns an +// answer to each. `stripFrontmatter` and `flattenHeadingText` are imported from +// `check-doc-anchors.mjs` and `stripFencedBlocks` from `check-adr-links.mjs`; +// none of the three is retyped here. +// +// That is not tidiness, it is the correctness argument. `flattenHeadingText` was +// diffed against the real fumadocs pipeline over the whole corpus (397 files, +// 6955 headings, zero disagreements — see that file's header) and it is what +// makes the delete-vs-demote call right on nine pages where the two headings +// differ ONLY by inline markup: +// +// content/docs/kernel/runtime-services/audit-service.mdx +// title: services.audit h1: `services.audit` +// +// Byte-compared, those two strings differ and the remedy reads "demote" — which +// would leave a `## services.audit` sitting directly under an `

services.audit`, +// preserving the duplicate this gate exists to remove, one level down. Compared +// as RENDERED TEXT they are equal and the remedy is "delete", which is correct. +// A second, private heading-text model in this file would have gotten those nine +// wrong, and a gate disagreeing with the renderer about what a page says is the +// same class of defect as no gate at all. +// +// ## Scope: two trees are excluded, and the exclusions are self-retiring +// +// `EXCLUSIONS` carves out two SUBTREES — never individual files. A file +// allowlist is where new failures go to be forgotten; a subtree with a named +// owner is a statement about who fixes that tree. +// +// - `content/docs/references/**` — 100% generated by +// `packages/spec/scripts/build-docs.ts` from JSDoc file headers in +// `packages/spec/src/**`. Its own required gate (`check:docs`) regenerates +// the tree and fails on any difference, so a hand-edit here does not merely +// get reverted at the next generator run — it ships CI-red immediately. The +// 38 pages that still violate are owned by #12249, in the generator. +// - `content/docs/releases/**` — `CLAUDE.md` puts a hard stop on this +// directory, and three of its four violations are not mechanical: the `# ` +// headings in `v15`/`v16`/`v17.mdx` are real top-level dividers with `##` +// children, so demoting one makes it a sibling of its own subsections. Owned +// by #12250. +// +// **An exclusion whose tree is CLEAN is a failure, not a pass.** When #12249 or +// #12250 lands, this gate goes red with `DEAD-EXCLUSION` and asks for its own +// carve-out to be deleted. Without that limb the two exclusions would outlive +// their reasons and quietly shrink the gate's scope by 42 pages forever — the +// vacuous-green shape (#4690) that this repo treats as worse than no check. +// +// The same reasoning gives the corpus-level limb: a run that reads ZERO pages +// exits 1. "Nothing differs" must never be reachable by checking nothing. +// +// ## Why this cannot be a `check:doc-authoring` rule +// +// `check-doc-authoring.mjs` judges CODE BLOCKS in docs; this judges the prose +// around them, and its central operation — strip the code blocks first — is that +// gate's population inverted. Sharing a file would mean one scanner holding both +// a "look only inside fences" and a "look only outside fences" mode, which is +// two gates in a trench coat. +// +// ## Cost +// +// One filesystem read of 403 files and a line scan. Sub-second, no build, no +// network. It reads `yaml` only to name the remedy — see `remedyFor` — and the +// finding itself does not depend on the frontmatter parsing at all. + +import { readFileSync, readdirSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join, relative, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { requireDependency } from './import-prerequisite.mjs'; +const { parse } = await requireDependency('yaml', () => import('yaml'), import.meta.url, { + measures: 'any page under content/docs/** contains a body-level `# ` heading', +}); + +import { stripFencedBlocks } from './check-adr-links.mjs'; +import { flattenHeadingText, stripFrontmatter } from './check-doc-anchors.mjs'; +import { isEntrypoint } from './invoked-as.mjs'; + +const HERE = resolve(fileURLToPath(import.meta.url), '..'); +const REPO_ROOT = resolve(HERE, '..'); + +/** + * The population this gate sweeps, as the repo-relative subtree it really reads. + * + * Spelled as a subtree WITH a separator on purpose: `scripts/pm/dispatch-gates.mjs` + * derives a dispatch's gate list by scanning each gate for the path literals it + * operates on, and refuses a literal with no separator as a WORD rather than a + * path. Spelled `'content'` this gate would contribute no hint at all and would + * score `silent` for every card editing a doc page — the same gap #9626 closed + * for `check:doc-anchors`. + */ +const PAGE_GLOB = 'content/docs/**'; + +/** The tree `PAGE_GLOB` names, derived from it so the two cannot drift apart. */ +const PAGE_ROOT = PAGE_GLOB.slice(0, PAGE_GLOB.lastIndexOf('/')); + +/** `.mdx` only: `defineDocs` compiles these, and `content/docs` holds nothing else. */ +const PAGE_EXTENSION = '.mdx'; + +/** + * Subtrees this gate does not judge, each with the issue that will make it + * empty. Read the header's "Scope" section before adding a third: an entry + * whose tree is clean FAILS, so every line here has a finite life. + */ +const EXCLUSIONS = [ + { + prefix: 'content/docs/references/', + owner: '#12249', + why: 'generated by packages/spec/scripts/build-docs.ts from JSDoc file headers; `check:docs` fails on any hand-edit', + }, + { + prefix: 'content/docs/releases/', + owner: '#12250', + why: 'CLAUDE.md stops code PRs at this directory, and three of the four violations need a cascading demotion', + }, +]; + +/** ATX heading: CommonMark allows up to three leading spaces. */ +const ATX_HEADING = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?[ \t]*$/; + +/** Fumadocs' custom-id suffix — `## Heading [#custom-id]`, remark-heading.ts. */ +const CUSTOM_ID = /\s*\[#([^\]]+?)]\s*$/; + +/* ------------------------------------------------------------------ scanning */ + +/** Every `.mdx` under `dir`, recursively, in a stable order. */ +export function listPages(dir, out = []) { + for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : 1))) { + const full = join(dir, entry.name); + if (entry.isDirectory()) listPages(full, out); + else if (entry.name.endsWith(PAGE_EXTENSION)) out.push(full); + } + return out; +} + +/** + * Every body-level `# ` heading one page contains, as `{ line, raw, text }`. + * + * Frontmatter and fenced code are blanked first, both line-count preserving, so + * a reported line number indexes the real file. See the header for why the fence + * pass is the load-bearing half. + */ +export function bodyH1s(markdown) { + const lines = stripFencedBlocks(stripFrontmatter(markdown)).split('\n'); + const found = []; + lines.forEach((line, i) => { + const m = ATX_HEADING.exec(line); + if (!m || m[1].length !== 1) return; + const raw = (m[2] ?? '').replace(/\s+#+\s*$/, ''); + found.push({ line: i + 1, raw: line, text: flattenHeadingText(raw.replace(CUSTOM_ID, '')) }); + }); + return found; +} + +/** The frontmatter `title`, or `null` when there is none this gate can read. */ +export function frontmatterTitle(markdown) { + const m = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*(\r?\n|$)/.exec(markdown); + if (!m) return null; + try { + const doc = parse(m[1]); + return doc && typeof doc.title === 'string' ? doc.title : null; + } catch { + // A page whose frontmatter does not parse is `check:doc-frontmatter`'s + // finding, not this gate's. The heading is still reported; only the remedy + // hint degrades. + return null; + } +} + +/** + * Which of the two fixes this heading wants. + * + * A HINT attached to a finding, never part of the verdict: the finding is "this + * page contains a body `# ` heading", which is true whatever the frontmatter + * says. Comparison is on RENDERED text, trimmed and case-folded — see the + * header for the nine pages that turn on exactly this. + */ +export function remedyFor(headingText, title) { + const norm = (s) => (s ?? '').trim().toLowerCase(); + if (title !== null && norm(headingText) === norm(title)) { + return { kind: 'delete', detail: 'renders the same text as the frontmatter title — delete the line' }; + } + return { kind: 'demote', detail: 'says something the title does not — demote it to `## ` (keeps the wording and the slug)' }; +} + +/** The exclusion covering `relPath`, or `null`. */ +function exclusionFor(relPath) { + return EXCLUSIONS.find((e) => relPath.startsWith(e.prefix)) ?? null; +} + +/** + * Judge one tree. Returns `{ findings, excluded, scanned }` — `excluded` is + * keyed by exclusion prefix so the caller can enforce the dead-exclusion rule. + */ +export function scanTree(root, repoRoot = REPO_ROOT) { + const findings = []; + const excluded = new Map(EXCLUSIONS.map((e) => [e.prefix, []])); + let scanned = 0; + + for (const abs of listPages(root)) { + const rel = relative(repoRoot, abs).split('\\').join('/'); + const src = readFileSync(abs, 'utf8'); + const headings = bodyH1s(src); + const exclusion = exclusionFor(rel); + if (exclusion) { + if (headings.length) excluded.get(exclusion.prefix).push({ rel, headings }); + continue; + } + scanned += 1; + if (!headings.length) continue; + const title = frontmatterTitle(src); + for (const h of headings) { + findings.push({ rel, line: h.line, raw: h.raw.trim(), ...remedyFor(h.text, title) }); + } + } + return { findings, excluded, scanned }; +} + +/* ---------------------------------------------------------------------- main */ + +function main(argv) { + const root = join(REPO_ROOT, PAGE_ROOT); + const { findings, excluded, scanned } = scanTree(root); + + if (argv.includes('--list')) { + for (const abs of listPages(root)) { + const rel = relative(REPO_ROOT, abs).split('\\').join('/'); + const ex = exclusionFor(rel); + const n = bodyH1s(readFileSync(abs, 'utf8')).length; + console.log(`${ex ? 'EXCLUDED' : n ? 'FINDING ' : 'ok '} ${rel}${n ? ` (${n} body h1)` : ''}`); + } + return 0; + } + + // Anti-vacuity: reading nothing must never read as success. + if (scanned === 0) { + console.error( + `✗ check-docs-single-h1 read ZERO judgeable pages under ${PAGE_ROOT}/.\n` + + ` Nothing was measured, so this exit code says nothing about the corpus.\n` + + ` Either the tree moved, or every page fell into an EXCLUSIONS subtree.`, + ); + return 1; + } + + let failed = false; + + if (findings.length) { + failed = true; + console.error(`✗ ${findings.length} body-level \`# \` heading(s) in ${PAGE_ROOT}/ — each renders a SECOND

:\n`); + for (const f of findings) { + console.error(` ${f.rel}:${f.line}`); + console.error(` ${f.raw}`); + console.error(` → ${f.kind.toUpperCase()}: ${f.detail}\n`); + } + console.error( + ` \`DocsTitle\` already renders the frontmatter \`title\` as this page's

\n` + + ` (apps/docs/app/[lang]/docs/[[...slug]]/page.tsx). A body \`# \` is a second one.\n`, + ); + } + + // A carve-out that no longer carves anything out must be deleted, in the same + // change that made it empty. See the header's "Scope" section. + for (const e of EXCLUSIONS) { + const hits = excluded.get(e.prefix); + if (hits.length) { + console.error(`ℹ EXCLUDED ${e.prefix}** — ${hits.length} page(s) still carry a body \`# \` heading, owned by ${e.owner}`); + continue; + } + failed = true; + console.error( + `✗ DEAD-EXCLUSION: ${e.prefix}** is clean, so its EXCLUSIONS entry checks nothing.\n` + + ` ${e.owner} appears to have landed. Delete that entry from scripts/check-docs-single-h1.mjs\n` + + ` in the same change, so the gate's scope grows back with the fix.\n`, + ); + } + + if (failed) return 1; + console.log( + `✓ check-docs-single-h1: ${scanned} page(s) under ${PAGE_ROOT}/ carry no body-level \`# \` heading ` + + `(${EXCLUSIONS.length} subtree(s) excluded, see --list).`, + ); + return 0; +} + +/* ----------------------------------------------------------------- self-test */ + +/** + * Positive AND negative controls over a real temp tree, running the real + * `scanTree`. A live corpus that is green cannot tell a working gate from a + * blind one, and this gate lands with its corpus green by construction. + */ +export function selfTest() { + const cases = []; + const t = (name, ok, detail) => cases.push({ name, ok: Boolean(ok), detail }); + + const dir = mkdtempSync(join(tmpdir(), 'docs-single-h1-')); + try { + const write = (rel, body) => { + const abs = join(dir, rel); + mkdirSync(dirname(abs), { recursive: true }); + writeFileSync(abs, body); + }; + const fm = (title) => `---\ntitle: ${title}\n---\n\n`; + + write('content/docs/clean.mdx', `${fm('Clean Page')}Prose with no heading-one.\n\n## A section\n`); + write('content/docs/dup.mdx', `${fm('Object Metadata')}# Object Metadata\n\nProse.\n`); + write('content/docs/differs.mdx', `${fm('Command Line Interface')}# @objectstack/cli\n\nProse.\n`); + // THE control: the ten real pages whose only `# ` line is a shell comment. + write('content/docs/fenced.mdx', `${fm('Fenced')}Prose.\n\n\`\`\`bash\n# Install pnpm globally\nnpm i -g pnpm\n\`\`\`\n`); + write('content/docs/fenced-tilde.mdx', `${fm('Tilde')}Prose.\n\n~~~yaml\n# customer.object.yml\nname: customer\n~~~\n`); + // Inline-markup-only difference: renders identically, so DELETE, not demote. + write('content/docs/inline-code.mdx', `${fm('services.audit')}# \`services.audit\`\n\nProse.\n`); + // Not headings: no space after the hash, and a level-two heading. + write('content/docs/not-a-heading.mdx', `${fm('Nope')}#NotAHeading\n\n## A real section\n`); + // CommonMark allows up to three leading spaces. + write('content/docs/indented.mdx', `${fm('Indented')} # Indented Heading\n\nProse.\n`); + // A `# ` in the frontmatter block itself is not body content. + write('content/docs/fm-hash.mdx', `---\ntitle: Hashy\ndescription: "# not a heading"\n---\n\nProse.\n`); + // Both excluded subtrees, each dirty — the live shape. + write('content/docs/references/gen.mdx', `${fm('Gen')}# Why this lives in \`spec\`\n\nProse.\n`); + write('content/docs/releases/v9.mdx', `${fm('v9.0.0')}# 9.0.0 in detail\n\nProse.\n`); + + const root = join(dir, 'content/docs'); + const { findings, excluded, scanned } = scanTree(root, dir); + const at = (rel) => findings.filter((f) => f.rel === rel); + + t('a page with no body h1 is not a finding', at('content/docs/clean.mdx').length === 0); + t('a body h1 repeating the title is a DELETE finding', at('content/docs/dup.mdx')[0]?.kind === 'delete', JSON.stringify(at('content/docs/dup.mdx'))); + t('a body h1 differing from the title is a DEMOTE finding', at('content/docs/differs.mdx')[0]?.kind === 'demote', JSON.stringify(at('content/docs/differs.mdx'))); + t('a `# ` inside a ``` fence is NOT a finding', at('content/docs/fenced.mdx').length === 0, JSON.stringify(at('content/docs/fenced.mdx'))); + t('a `# ` inside a ~~~ fence is NOT a finding', at('content/docs/fenced-tilde.mdx').length === 0, JSON.stringify(at('content/docs/fenced-tilde.mdx'))); + t( + 'a heading differing from the title ONLY by inline code is a DELETE finding', + at('content/docs/inline-code.mdx')[0]?.kind === 'delete', + JSON.stringify(at('content/docs/inline-code.mdx')), + ); + t('`#NoSpace` is not a heading', at('content/docs/not-a-heading.mdx').length === 0); + t('a `## ` heading is never a finding', findings.every((f) => f.raw.startsWith('#') && !f.raw.startsWith('##'))); + t('an h1 indented up to three spaces IS a finding', at('content/docs/indented.mdx').length === 1, JSON.stringify(at('content/docs/indented.mdx'))); + t('a `# ` inside the frontmatter block is not body content', at('content/docs/fm-hash.mdx').length === 0); + t('the reported line number indexes the real file', at('content/docs/dup.mdx')[0]?.line === 5, JSON.stringify(at('content/docs/dup.mdx'))); + t('an excluded subtree yields no findings', findings.every((f) => !exclusionFor(f.rel))); + t('...but its violations are still counted', excluded.get('content/docs/references/').length === 1 && excluded.get('content/docs/releases/').length === 1); + t('only judgeable pages are counted as scanned', scanned === 9, `scanned=${scanned}`); + + // The dead-exclusion limb: a clean excluded tree must be reported empty so + // `main` can fail on it. + rmSync(join(dir, 'content/docs/references/gen.mdx')); + const after = scanTree(root, dir); + t('a CLEAN excluded subtree reports zero — the dead-exclusion trigger', after.excluded.get('content/docs/references/').length === 0); + + // Anti-vacuity: a tree of nothing but excluded pages scans zero pages. + const empty = mkdtempSync(join(tmpdir(), 'docs-single-h1-empty-')); + try { + mkdirSync(join(empty, 'content/docs/releases'), { recursive: true }); + writeFileSync(join(empty, 'content/docs/releases/v1.mdx'), `${fm('v1')}# 1.0.0 in detail\n`); + t('a corpus of only excluded pages scans ZERO', scanTree(join(empty, 'content/docs'), empty).scanned === 0); + } finally { + rmSync(empty, { recursive: true, force: true }); + } + } finally { + rmSync(dir, { recursive: true, force: true }); + } + + const failed = cases.filter((c) => !c.ok); + for (const c of failed) console.error(` ✗ ${c.name}${c.detail ? ` — ${c.detail}` : ''}`); + if (failed.length) { + console.error(`✗ check-docs-single-h1 self-test: ${failed.length} of ${cases.length} case(s) failed.`); + return 1; + } + console.log(`✓ check-docs-single-h1 self-test: ${cases.length} cases pass (both fence spellings, inline-code equality, indentation, and both anti-vacuity limbs).`); + return 0; +} + +if (isEntrypoint(import.meta.url)) { + process.exit(process.argv.includes('--self-test') ? selfTest() : main(process.argv.slice(2))); +}