Skip to main content

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.md at 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​

FieldRule
titleRequired. Title Case. Docusaurus renders this as the page heading — do not add a body # H1.
sidebar_positionRequired. 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_labelOptional. Use only when the full title is too long for the sidebar.
tagsOptional. 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>
variantWhen to use
infoNeutral context, background information
warningPotential data loss or irreversible action
successConfirmation that a step succeeded
dangerDestructive 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 keyEnv varDefaultControls
billingEnabledDOCS_FLAG_BILLING_ENABLEDoffBilling 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;
Sidebar exclusion requires onBrokenLinks care

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.ts key: camelCase equivalent (e.g. billingEnabled)
  • Component prop: the flags.ts key 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​

  1. 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;
  2. Add a seed default to apps/docs/flag-defaults.ts (must equal the system_settings column default in apps/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-defaults CI job fails if this entry is missing.

  3. Add the mapping to apps/docs/scripts/fetch-flags.mjs (FLAG_MAP object):

    const FLAG_MAP = {
    billingEnabled: 'DOCS_FLAG_BILLING_ENABLED',
    myNewFeature: 'DOCS_FLAG_MY_NEW_FEATURE', // ← add here
    };
  4. Add the corresponding --build-arg line in all four kaniko jobs: build:docs, refresh:build:docs, build:docs-internal, and refresh:build:docs-internal in ci/docs.gitlab-ci.yml:

    EXTRA_BUILD_ARGS="$EXTRA_BUILD_ARGS --build-arg DOCS_FLAG_MY_NEW_FEATURE=${DOCS_FLAG_MY_NEW_FEATURE:-0}"
  5. Add an ARG / ENV pair for the new flag in apps/docs/Dockerfile (after the existing flag declarations), and the same ARG / ENV pair in apps/docs/Dockerfile.internal.

  6. Update the Available flags table in this guide.

  7. Use <FeatureGate flag="myNewFeature"> in the relevant MDX pages.

In-scope vs out-of-scope flags​

In scopeOut 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 deploymentPer-user flags
Build-time configuration injected as env varsRuntime feature flags requiring API calls

Style Rules​

Voice and tone​

RuleDoDon'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 # H1 in the body when title: 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.

ContentLanguage tag
Shell commandsbash
TypeScripttypescript
JavaScriptjavascript
JSONjson
YAMLyaml
Kubernetes manifestsyaml
Markdownmd
MDXmdx
Plain output / logstext

Do:

```bash
npm run build
```

Don't:

```
npm run build
```
SituationFormat
Cross-doc pageRelative path: [Task Statuses](../user/task-statuses)
Anchor within page[section](#section-id)
External URLFull URL in angle brackets or standard markdown: [Docusaurus](https://docusaurus.io)
Inline code referenceWrap 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: title and sidebar_position are set.
  • No # H1 appears in the body — the frontmatter title: 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:

RuleWhat it catches
MD025Multiple # H1 headings in a file
MD031Fenced code block not surrounded by blank lines
MD032Lists not surrounded by blank lines
MD040Fenced code block missing language identifier
MD024Duplicate 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:

  1. Target audience — one of: developer, end-user, internal (Megatherium employees).
  2. Component catalog URL — link to /developer/components so the model selects the right component.
  3. Frontmatter template — paste the template from Frontmatter Conventions directly into the prompt.
  4. Existing page for tone reference — cite a specific page (e.g., user/task-statuses.md) so the model matches voice.
  5. 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}

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.