docs/ is the public documentation, published with Mintlify. It is not a
supplement to the code — for an operator deciding whether to trust an installation with money, and
for a customer deciding whether to trust it with their traffic, it is the product’s only description
of itself. AGENTS.md therefore treats a behaviour change and its documentation as one change:
update the docs with the behaviour, and run the documentation checks before merging.
The tree has two complete language trees, English at the root and Russian under docs/ru/, and both
are navigable from the published site. Keeping them in step is the single most easily broken rule on
this page, so it has its own section below.
How the source is organised
docs.json follows the https://mintlify.com/docs.json schema. Its navigation.languages array
holds two entries: en, marked default: true, and ru. Each carries its own group names — the
Russian entry uses Russian group titles (Начало, Коммерция, Эксплуатация) — and each group
lists page paths without the .mdx extension. A page that exists as a file but appears in no group
is not reachable from the navigation, and a group entry with no file behind it has nothing to serve,
so files and navigation entries are added and removed together.
The API reference group is not a page. It points Mintlify at
https://raw.githubusercontent.com/kewldan/omniflow/main/api/openapi.yaml, so every endpoint,
parameter, and schema in the published reference comes from the committed contract on main. An
endpoint is documented by changing api/openapi.yaml, never by hand-writing a page that describes
it — which is the same rule that stops the Go server types and the TypeScript client from drifting.
See Development for the generators that read the same file.
Page anatomy
Frontmatter is exactly two keys, on every one of the existing pages:title supplies the page’s heading, so the body starts at ## and never contains a #. description
is one sentence saying what the reader will be able to do after reading; it is what appears in search
results and link previews.
Open with one or two paragraphs of orientation before the first ##. A reader who arrives from a
search result needs to know what this page covers and what it does not before they meet a procedure.
For each capability a page covers, answer three questions in order:
- What it is — one or two concrete sentences.
- Why it exists — the failure it prevents, the guard it enforces, or the alternative that was rejected. Ground it in something visible in the code: a refusal path, an invariant, a privacy boundary. Never invent a rationale.
- How to use it — the exact route, screen, or command; the fields; the button; and what the reader sees on success, while pending, and when it is refused.
Components in use
Mintlify offers a large component set; this documentation deliberately uses a small part of it, so a reader learns one visual vocabulary.icon takes a Font Awesome name — database, server, wallet, shield-halved, bullhorn and
similar are already in use; prefer an icon the tree already uses over a new one. Do not stack three
callouts in a row: a page where everything is highlighted highlights nothing.
Code blocks always carry a language (bash, env, json, yaml, sql, mdx). Mermaid diagrams
are allowed for state machines and flows and are used on three pages today; keep them small enough
to read without zooming.
Tables are the right shape for environment variables, roles, permissions, states, providers, and any
enumerated matrix. Keep the column set consistent with the pages that already document the same kind
of thing — the environment-variable tables all use Variable / Default / Purpose, and diverging from
that makes two references impossible to read side by side.
Links
Internal links are absolute paths with a leading slash and no extension:/operations/admin-panel,
/commerce/orders-payments. Anchors are the target heading, lowercased and hyphenated:
/commerce/orders-payments#refunds.
CI runs mint broken-links --check-anchors over the English tree, so an anchor that does not match a
real heading on the target page fails the build the same way a missing page does. When you rename a
heading, search the tree for links to its anchor.
A link from one Russian page to an anchor on another Russian page is the one case to avoid: the
target’s headings are translated, so the English anchor no longer exists there. Link to the page
instead. An anchor pointing at a heading on the same page is fine, because you control both ends.
Cross-link generously. Almost every capability has a customer page, an operator page, and a
configuration reference, and a reader who lands on one of the three needs the other two.
Voice
Match the pages that are already at the bar —docs/customer/web-panel.mdx,
docs/operations/admin-panel.mdx, docs/commerce/gifts-and-goods.mdx.
- Declarative, precise, unhurried. Full sentences. No marketing adjectives, no “simply”, no “just”, no exclamation marks, no emoji in body text.
- Explain causality inline: “…which is why…”, “…because…”, “…rather than…”. Name the alternative that was rejected when the code makes it obvious.
- Second person for instructions (“Open
/admin/catalog”, “Paste the token”); third person for behaviour. - Prefer “an operator” and “a customer” over “the user”.
- Exact identifiers in backticks:
APP_BACKUP_ENABLED,/v1/panel/orders,catalog.write. - UTC for times, minor units for money, ISO dates.
The Russian mirror
docs/ru/** mirrors the English tree file for file: docs/index.mdx has docs/ru/index.mdx,
docs/commerce/wallet-ledger.mdx has docs/ru/commerce/wallet-ledger.mdx, and so on for every
entry in docs.json.
Russian is not a courtesy translation here. A new customer record is created with locale defaulting
to 'ru', APP_DEFAULT_CURRENCY defaults to RUB, and the operator panel refuses to engage
maintenance unless the operator supplies both a Russian and an English customer notice. The product
assumes a Russian-speaking audience, so a Russian page that lags is a defect in the primary
documentation, not in a secondary one.
What to translate and what to leave alone
Translate the body, the frontmattertitle and description, table headers, callout text, and the
label text inside components.
Never translate:
- environment variable names (
APP_TELEMETRY_ENABLED), route and API paths (/v1/panel/orders), permission names (system.write), database identifiers, CLI commands, log lines, and JSON or YAML keys; - component names (
<Note>,<Card>) andiconvalues; - product names: Omniflow, Remnawave, Telegram, YooKassa, CryptoBot, Telegram Stars, Xray, PostgreSQL, Valkey, River, Next.js.
/ru/operations/admin-panel, not
/operations/admin-panel. A Russian page that links into the English tree drops the reader out of
their language for the rest of the session.
Use professional technical Russian, «вы» without capitalisation, and «ё» where the letter belongs
(кошелёк, счёт). Some settled terms:
Keep sentence length and paragraph breaks close to the English page. The two trees are reviewed as a
diff against each other, and a Russian page restructured for style makes the next update impossible
to verify.
Validating locally
docs directory:
validate checks docs.json against its schema and the pages against Mintlify’s MDX rules —
malformed frontmatter, an unknown component, an unclosed tag. It covers both language trees.
PUPPETEER_SKIP_DOWNLOAD=true stops the CLI from pulling a headless browser it does not need.
broken-links is given an explicit file list rather than the whole site because it resolves every
link against the default language: a /ru/… link is reported as broken even though the page is
served at exactly that URL, as mint dev will show you. Rather than lose the check for Russian,
scripts/check-russian-mirror.sh takes it over — it asserts that every English page has a Russian
counterpart and no orphan exists the other way, that no Russian page links out into the English
tree, and that every /ru/… link resolves to a real file.
The docs job in .github/workflows/ci.yml runs exactly these three commands, so a green local run
is a green CI run for this area.
Accuracy obligations
- Never document something that only exists in
ROADMAP.md. A planned capability is either omitted or marked plainly as not yet available, with the version it is targeted at. - Never invent an environment variable, a default, a route, a permission, or a screen. If you cannot open the file that defines it, leave it out.
- A telemetry change carries its documentation. Any new field or event requires an update to the public telemetry documentation and schema in the same change — see Anonymous telemetry.
- Never paste a real secret, credential, access link, customer identifier, or payment payload into a page, including inside an example response. Synthesise one.
- Architecture decisions with long-term consequences belong in a numbered
docs/architecture/decision-NNNN-<slug>.mdxlisted on Architecture decisions, rather than inline in a feature page. Keep them flat:mint broken-linksdoes not resolve a link to a page nested two directories deep, so adocs/architecture/decisions/folder fails the docs job even though the pages build.
Adding a page
1
Write the English page
Create
docs/<section>/<slug>.mdx with title and description frontmatter and a body that
starts at ##.2
Write the Russian page
Create
docs/ru/<section>/<slug>.mdx at the mirrored path, translated per the rules above, with
its internal links pointing into /ru/.3
Register both in the navigation
Add
<section>/<slug> to the appropriate group in the en language block of docs.json, and
ru/<section>/<slug> to the matching group in the ru block. A page in one language block and
not the other is the most common way this tree goes out of step.4
Link to it
Add a link from at least one page a reader would plausibly arrive on first. A page nothing links
to is found only by search.
5
Validate
Run
make docs-check and fix every reported link and anchor before opening the pull request.docs: Conventional Commit scoped to the area — docs(telemetry): document the heartbeat payload. The commit conventions and the rest of the contribution flow are on
Development.