Authoring Guide
This guide covers everything you need to write documentation for Kabori Docs — whether you are a human contributor or an AI assistant. Follow it to produce content that is consistent, reviewable, and passes automated quality gates.
See also:
CONTRIBUTING.mdat the repository root cross-references this page.
Frontmatter Conventions
Every document starts with a YAML frontmatter block. Use the annotated template below.
---
title: Exact Page Title # required; matches the H1; Title Case
sidebar_position: 2 # integer; controls sidebar order within the section
# sidebar_label: Short Label # optional; shorter label in sidebar if title is long
# tags: [developer, internal] # optional; audience or section classification
---
Rules
| Field | Rule |
|---|---|
title | Required. Title Case. Docusaurus renders this as the page heading — do not add a body # H1. |
sidebar_position | Required. Integer starting from 1. Lower numbers appear higher in the sidebar. Leave gaps (1, 3, 5) if you anticipate inserting pages between them later. |
sidebar_label | Optional. Use only when the full title is too long for the sidebar. |
tags | Optional. Use developer, user, or internal to reflect the primary audience. |
Do:
---
title: Debug Tools Reference
sidebar_position: 3
---
Don't:
---
Title: "debug tools reference"
position: last
---
Component Usage Reference
All MDX components listed below are globally registered — no per-file import is needed.
The full visual catalog is at Developer → Components.
Callout
Use for prominent notices that must not be missed.
<Callout variant="info" title="Prerequisites">
You need Node 20+ and access to the cluster.
</Callout>
variant | When to use |
|---|---|
info | Neutral context, background information |
warning | Potential data loss or irreversible action |
success | Confirmation that a step succeeded |
danger | Destructive or security-sensitive operation |
Do: use warning before a command that modifies production state.
Don't: use danger for every notice — reserve it for truly destructive actions.
Steps / Step
Use for ordered installation or configuration workflows.
<Steps>
<Step title="Install dependencies">
Run `npm install` in the `apps/docs` directory.
</Step>
<Step title="Start the dev server">
Run `npm run dev`. The site is live at `http://localhost:3000`.
</Step>
</Steps>
Do: keep each <Step> to one logical action.
Don't: put an entire tutorial inside a single step.
StatusBadge
Use inline to communicate task or feature lifecycle state.
This feature is <StatusBadge status="in-progress" />.
Valid statuses: refinement, backlog, in-progress, review, staging, done, closed, blocked.
ApiExample
Use for REST API reference documentation.
<ApiExample
method="POST"
endpoint="/api/v1/tasks"
description="Create a new task"
request={`{"title": "My task", "projectId": "proj_123"}`}
response={`{"id": "task_456", "status": "backlog"}`}
statusCode={201}
/>
Do: include both request and response when documenting a mutating endpoint.
Don't: omit the statusCode — reviewers use it to verify correctness.
FeatureGate
Use to conditionally render content based on system-level, environment-constant feature flags. The gate is evaluated at build time: content inside a <FeatureGate> whose flag is OFF is never emitted to the static HTML and never enters the search index.
<FeatureGate flag="billingEnabled">
This section only appears in billing-enabled builds (when `DOCS_FLAG_BILLING_ENABLED=1`).
</FeatureGate>
You can gate a single paragraph, a subsection, or the entire body of a page.
Do: wrap only system-level flags (e.g. billing_enabled, ai_loop_detection_enabled) that are the same for every user in a given deployment.
Don't: use <FeatureGate> for per-organisation or per-user flags — there is no single flag value at build time, and the docs site cannot make runtime API calls.
Don't: use CSS display:none as an alternative — hidden content would still be indexed and visible in page source.
Available flags
| Flag key | Env var | Default | Controls |
|---|---|---|---|
billingEnabled | DOCS_FLAG_BILLING_ENABLED | off | Billing wallet and credit documentation |
To add a new flag, see Feature flag conventions.
How DOCS_FLAG_* values are set
DOCS_FLAG_* env vars are no longer hand-maintained in GitLab CI variables. The CI pipeline fetches live system settings from the environment's API at build time and bakes the values into the image automatically. Each environment (MR preview, staging, production) reflects its own database state.
Toggling a product flag in Settings → Super-admin → System Settings does not instantly update the docs site. Docs are rebuilt on the next code deploy or via the manual refresh:docs CI job.
See the Internal — Docs Flag Runbook for the full reconcile procedure.
Gating a whole page
Wrap the entire body content of the MDX file in <FeatureGate>. The page file still exists, so onBrokenLinks: 'throw' does not trip and the sidebar link remains valid. The rendered page will show only the frontmatter title when the flag is off.
---
title: Billing
sidebar_position: 10
---
<FeatureGate flag="billingEnabled">
All billing content goes here.
</FeatureGate>
When you need the page to be absent from the sidebar entirely (not just empty), replace the autogenerated entry with an explicit item list and conditionally include the gated page. Import flags from the shared flags.ts module — it reads the same env vars as docusaurus.config.ts:
// sidebars-internal.ts
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
import {flags} from './flags';
const sidebars: SidebarsConfig = {
internalSidebar: [
{type: 'doc', id: 'intro'},
{type: 'doc', id: 'ai-loop-detection'},
{type: 'doc', id: 'debug-tools'},
// Billing page is only included in the sidebar when the flag is on.
// When off, the page is excluded entirely so no sidebar link can break.
...(flags.billingEnabled ? [{type: 'doc' as const, id: 'billing'}] : []),
],
};
export default sidebars;
If you exclude a page from the sidebar, ensure no other page links to it with a relative path. onBrokenLinks: 'throw' will fail the build if a link target does not exist in the same build.
Feature flag conventions
System-level feature flags follow a strict naming convention so that flag state is auditable and predictable across environments.
Naming
- Env var:
DOCS_FLAG_<FEATURE_SCREAMING_SNAKE>(e.g.DOCS_FLAG_BILLING_ENABLED) flags.tskey: camelCase equivalent (e.g.billingEnabled)- Component prop: the
flags.tskey string (e.g.flag="billingEnabled")
Default state
All flags default to off (false) unless the env var is explicitly set to "1" or "true". This means gated content is hidden in local development and in any environment where the var is unset.
Adding a new flag
-
Add the flag to
apps/docs/flags.ts:export const flags = {billingEnabled: readFlag('DOCS_FLAG_BILLING_ENABLED'),myNewFeature: readFlag('DOCS_FLAG_MY_NEW_FEATURE'), // ← add here} as const; -
Add a seed default to
apps/docs/flag-defaults.ts(must equal thesystem_settingscolumn default inapps/api/src/db/schema.ts):export const SEED_DEFAULTS: Record<string, boolean> = {DOCS_FLAG_BILLING_ENABLED: false,DOCS_FLAG_MY_NEW_FEATURE: false, // ← add here};The
docs:check-flag-defaultsCI job fails if this entry is missing. -
Add the mapping to
apps/docs/scripts/fetch-flags.mjs(FLAG_MAPobject):const FLAG_MAP = {billingEnabled: 'DOCS_FLAG_BILLING_ENABLED',myNewFeature: 'DOCS_FLAG_MY_NEW_FEATURE', // ← add here}; -
Add the corresponding
--build-argline in all four kaniko jobs:build:docs,refresh:build:docs,build:docs-internal, andrefresh:build:docs-internalinci/docs.gitlab-ci.yml:EXTRA_BUILD_ARGS="$EXTRA_BUILD_ARGS --build-arg DOCS_FLAG_MY_NEW_FEATURE=${DOCS_FLAG_MY_NEW_FEATURE:-0}" -
Add an
ARG/ENVpair for the new flag inapps/docs/Dockerfile(after the existing flag declarations), and the sameARG/ENVpair inapps/docs/Dockerfile.internal. -
Update the Available flags table in this guide.
-
Use
<FeatureGate flag="myNewFeature">in the relevant MDX pages.
In-scope vs out-of-scope flags
| In scope | Out of scope |
|---|---|
System-level, environment-constant flags (billing_enabled, ai_loop_detection) | Per-organisation flags (no single truth at build time) |
| Flags that are identical for every user visiting a given deployment | Per-user flags |
| Build-time configuration injected as env vars | Runtime feature flags requiring API calls |
Style Rules
Voice and tone
| Rule | Do | Don't |
|---|---|---|
| Active voice | "Run the command." | "The command should be run." |
| Second person | "You can configure…" | "The user can configure…" |
| Present tense | "The flag enables…" | "The flag will enable…" |
| Direct | "Set LOG_LEVEL=debug." | "It is possible to set LOG_LEVEL to debug." |
Heading structure
- Do not add a
# H1in the body whentitle:is set in frontmatter — Docusaurus renders the frontmatter title as the page heading, and a body H1 creates a duplicate (triggers MD025). - Use
##for major sections,###for subsections. Do not skip levels. - Do not end headings with punctuation.
- Do not use code backticks inside headings.
Do:
## Configuration Options
### Log Level
Don't:
## `configuration` Options:
#### Log Level ← skipped ###
Code fences
Always specify the language identifier on every fenced code block.
| Content | Language tag |
|---|---|
| Shell commands | bash |
| TypeScript | typescript |
| JavaScript | javascript |
| JSON | json |
| YAML | yaml |
| Kubernetes manifests | yaml |
| Markdown | md |
| MDX | mdx |
| Plain output / logs | text |
Do:
```bash
npm run build
```
Don't:
```
npm run build
```
Links
| Situation | Format |
|---|---|
| Cross-doc page | Relative path: [Task Statuses](../user/task-statuses) |
| Anchor within page | [section](#section-id) |
| External URL | Full URL in angle brackets or standard markdown: [Docusaurus](https://docusaurus.io) |
| Inline code reference | Wrap in backticks: [`StatusBadge`](/developer/components#statusbadge) |
Do not use bare URLs. Do not use "click here" as link text.
AI-Specific Authoring Guidance
AI assistants generating documentation for Kabori Docs must follow the same style rules as human authors. The points below highlight areas where AI output most commonly drifts from house style.
Checklist for AI-generated content
Before submitting AI-generated content for review, verify every item:
- Frontmatter is complete:
titleandsidebar_positionare set. - No
# H1appears in the body — the frontmattertitle:field is the page heading. - No bare code fences (every fence has a language identifier).
- Active voice throughout — no passive constructions.
- No "Note:", "Important:", "Warning:" prefixed paragraphs — use
<Callout>instead. - No nested
#heading inside a<Step>— use bold text for sub-structure inside steps. - No fabricated status codes or API responses — verify against the actual API.
- No em-dashes (
—) inside sentences used as parentheticals — use commas or restructure. - No trailing whitespace or blank lines at end of file.
Passing review gates (Docs T5)
Automated lint rules (markdownlint) enforce:
| Rule | What it catches |
|---|---|
| MD025 | Multiple # H1 headings in a file |
| MD031 | Fenced code block not surrounded by blank lines |
| MD032 | Lists not surrounded by blank lines |
| MD040 | Fenced code block missing language identifier |
| MD024 | Duplicate heading text at the same level |
Run npx markdownlint-cli2 "**/*.md" "**/*.mdx" from apps/docs before committing.
Prompting best practices
When prompting an AI to generate a doc page, provide:
- Target audience — one of: developer, end-user, internal (Megatherium employees).
- Component catalog URL — link to
/developer/componentsso the model selects the right component. - Frontmatter template — paste the template from Frontmatter Conventions directly into the prompt.
- Existing page for tone reference — cite a specific page (e.g.,
user/task-statuses.md) so the model matches voice. - Explicit instruction to avoid passive voice — AI models default to passive; override it explicitly.
Prompt template:
Write a Kabori Docs page for the {audience} section on the topic: {topic}.
Audience: {developer | end-user | internal}
Sidebar position: {N}
Requirements:
- Follow the frontmatter template exactly:
---
title: {Title}
sidebar_position: {N}
---
- Active voice, present tense, second person.
- Use <Callout> (not "Note:") for warnings and notices.
- Every code fence must have a language identifier.
- Match the tone of this reference page: {link-to-reference-page}
Search
Local full-text search is powered by @easyops-cn/docusaurus-search-local. The public build indexes developer and user sections only; the internal build also indexes the internal section. The search bar appears in the navbar.
No action is needed from authors — the plugin indexes content automatically at build time.
See Versioning Strategy → Search Compatibility for notes on how search interacts with future per-release snapshots.
Related Resources
- Component Catalog — Visual catalog of every MDX component with props reference.
- Versioning Strategy — Decision record: rolling current vs. per-release snapshots.
- CONTRIBUTING.md at repo root — Quick-start for new contributors.