Writing
These rules cover prose everywhere in the repository. The sentence-level rules
apply to all of it. The page structure rules apply to everything under
docs/src/content/docs/, and specifications add two rules on top.
Readers
The site serves two audiences.
Integrators embed a C API from the language they already work in. Most know their own platform well and know MapLibre barely. Explain MapLibre concepts, and assume platform knowledge.
Contributors work on this repository, and read the pages under
docs/src/content/docs/development/. Assume they know the languages and tools
in the project map, and explain decisions this project made.
Many readers in both groups read English as a second or third language, so plainness matters more than rhythm.
Sentences
Use positive wording for guidance
Reserve negative wording for real prohibitions, safety rules, and hard boundaries.
- Avoid: "Examples should not grow into full applications."
- Prefer: "Examples stay small and focused."
- Avoid: "This layer should not try to manage execution models for every possible host."
- Prefer: "Higher-level adapters may add execution models above this layer."
State what is true
Describe an absence only when the reader arrives with a specific expectation, and name that expectation in the same sentence. Everything a library does not do is otherwise an infinite set.
- Avoid: "MapLibre gives you no flush and no final event, so read the state you mirror from events while the map is still live."
- Prefer: "Read the state you mirror from events while the map is live. Destroying a map discards that map's queued events immediately."
- Prefer: "Unlike the MapLibre Android and iOS SDKs, this API has no map view that drives a frame loop."
Put the payload in the main clause
A trailing which or so clause carries subordinate detail only.
- Avoid: "A render session takes the thread that attached it, which need not be the map's."
- Prefer: "A render session takes the thread that attached it. That thread can differ from the map's owner thread."
Use plain verbs
Replace phrasal verbs and metaphors with the literal verb. A term of art defined once, such as pump, is fine; decoration is not.
- Avoid: "Queries hang off the render session." "State rides along with the renderer." "Light steers fill-extrusion shading." "Reach for the setter."
- Prefer: "Queries belong to the render session." "State belongs to the renderer." "Light controls fill-extrusion shading." "Use the setter."
Cut the contrast when the positive statement stands alone
- Avoid: "Budget for it as work, not as a fixed per-frame slice."
- Prefer: "One call can span an entire style parse. Budget for it as variable work."
End a paragraph on the sentence that matters
Lead with the fact rather than saving a short sentence for emphasis. A closing fragment reads as significance, so it draws attention by position instead of by importance.
- Avoid: "Payload and message pointers stay valid only until the next poll. Copy what you keep."
- Prefer: "Copy any value you keep, because payload and message pointers stay valid only until the next poll."
Describe an API as a thing rather than as a person
- Avoid: "A parent refuses to close while a child is live."
- Prefer: "A parent returns
MLN_STATUS_INVALID_STATEwhile a child is live."
Keep the syntax explicit
Keep that after a verb, keep relative pronouns, and keep articles.
- Avoid: "The runtime drains the work the owner thread queued."
- Prefer: "The runtime drains the work that the owner thread queued."
Give each step one instruction
Keep procedural sentences under about twenty words.
Say it once, and say it plainly
Link to another page instead of copying from it. A copy drifts from its source and doubles the edit.
Each statement stands on its own, without pointing at an example or at the current state of the tree.
Cut hedges. "Or equivalent" and vague outcomes leave the reader to guess what the rule is.
Scope by constraint: general sections state general behavior, and platform- or API-specific rules belong in clearly labeled subsections.
One mode per page
Each page commits to one of four modes, after Diátaxis. Serving two modes on one page is the most common structural failure. This holds for contributor pages as much as for integrator pages.
| Mode | Serves | Contains |
|---|---|---|
| Onboarding | A reader with nothing working yet | The operations every integration needs |
| Guide | A reader who knows what they want | One task, start to finish |
| Concept | A reader building a mental model | The model and its consequences, no steps |
| Reference | A reader looking something up | Tables, values, complete coverage |
Explanation inside an onboarding page slows the reader who wants a working result. Steps inside a concept page make it useless for lookup. Move the material rather than blending it.
Onboarding shows the operations that every integration needs, so that a reader reaches the reference with the shape of the API already in mind. It is not a tutorial that builds one particular application.
Reader-facing navigation labels stay natural: "Get started", "Guides", "Concepts", "Reference". The mode names above are for authors.
A specification is reference, with the two extra rules below.
Headings mark sections, not paragraphs
A heading earns its place when a reader can jump to that section or skip it: a distinct sub-task, a second mechanism that serves the same need, or a rule that applies to everything above it. Two or three headings usually cover a page.
A page that walks one task from start to finish stays flat. Headings over each paragraph of a linear narrative offer navigation for a path that has no forks.
Paragraphs before the first heading are the lead, and they carry what holds for every section below.
Write a heading in sentence case, and name the task rather than the API.
Lead a guide with the decision
A reader arrives with a choice to make, not with a function to call. Name the choice, give the two or three shapes it takes, and say what each shape costs. The implementation follows the choice, one section per shape.
Only when there is a fork. A task with one main path leads with that path, and options that serve special cases follow under their own heading. Promoting an option to a decision the reader never arrived with delays the thing they came for.
Values and flags belong to the decision they serve. A section that lists what a parameter accepts, before the reader knows why the parameter exists, is reference material in the wrong place.
Keep the implementation light. The API reference states every behavior exactly, so a guide draws a rough map: enough of the route to walk it, and the traps that a reader cannot see from the code.
A guide covers a task, not an API surface
Finish the task and stop. Leave the rest of the domain alone.
A guide that names every function in an area has become reference. Parameter semantics, presence and absence of optional fields, and edge-case values belong in the API reference.
The test is whether a reader can finish the task, not whether the page mentions everything.
Prose is neutral, and snippets are concrete
One set of prose serves every language binding, every platform, and every render backend. A snippet has to pick one of each to compile, and that is where the specifics belong.
Keep prose as general as the API is. Say "attach a render session to your surface", not "attach an EGL surface", when the sentence holds for Metal and Vulkan too. A page whose prose names one backend throughout has narrowed itself for no reason.
State the snippet's choices once, near the snippet, so a reader knows what they are looking at and what to substitute.
- Prefer conceptual phrasing where it reads as clearly: "set the style URL", "pump the runtime".
- Name a C function when the name earns its place, and write it in full:
mln_map_set_style_url. A name earns its place when the reader is choosing between calls, or when a call is an exception to the pattern around it. - The same test applies to status codes, event types, constants, fields, and arguments, which are C spellings as much as function names are. Prefer "a style-loaded event", "an invalid-state status", "the repaint flag", and "the duration" over the C identifiers. A reader on any binding then reads prose that matches their own API, and the reference carries the exact spelling.
- Describe options as fields that a caller sets, rather than as a bit mask. The
C API pairs a mask with values, and other bindings use optional fields, so
"set the fields that you want to change" holds everywhere and "set its bit in
fields" holds in one place. - Divergence between bindings lives on that binding's own page. Mention it in shared prose only when a reader following the prose would otherwise write broken code, and then as a one-line pointer rather than an explanation.
- Behavior that genuinely differs per platform or backend belongs in a clearly labeled subsection or its own page, rather than spread through prose that otherwise holds everywhere.
- Installation and packaging differ per binding by nature. Keep them in pages of their own, and keep deep packaging detail in reference where churn stays quarantined.
The C API reference is canonical while binding docstrings are written by hand. Updating a binding reference is welcome; covering all of them is not mandatory.
Snippets
Snippet files under docs/snippets/ compile in CI, so keep each file complete
and runnable. A page shows only the part it discusses, extracted by name.
Mark a region in the snippet, and show it with region():
// #region create
mln_runtime_create(&options, &runtime);
// #endregion create
import { region } from "../../../snippets";
<Code code={region(snippet, "create")} lang="c" title="first-map.c" />
Region names beat line numbers, because reformatting a snippet cannot silently point a page at the wrong code. A name that no longer exists fails the build.
Show four to eight meaningful lines per block. Several small blocks with prose between them read better than one block that covers a whole file.
Comments inside a region carry the guidance that belongs next to the code. Keep them for what a reader cannot see: a non-obvious return value, a thread rule, a trap. Skip comments that narrate the next line, and skip anything the prose already says.
Line markers stay out of it. Expressive Code labels, mark={"A":15-19}, hold
one to three characters and draw on top of longer text, and every line-numbered
range breaks the next time a formatter moves a line.
Specifications
Specifications follow everything above, and add two rules.
Write each requirement so that a reader can check it on its own.
Use MUST, SHOULD, and MAY with their RFC 2119 meanings, and name the party that each one binds.
Terminology
One term per concept, across every page.
| Use | For | Avoid |
|---|---|---|
| runtime | The scheduler and event store for one owner thread | context, engine |
| map | Map state, independent of any render target | map view, map object |
| render session | The object that renders one map to one target | renderer |
| render target | The surface or texture that a session draws into | render session as a synonym |
| pump | Calling the runtime's pump function | drive, tick, service |
| drain | Reading queued events until none remain | pump, poll as a synonym |
| owner thread | The thread that a handle is affine to | owning thread, calling thread |
| handle | An opaque object that the API returns | pointer, object |
| host | The application embedding the library | client, user, consumer |
| binding | A language wrapper over the C API | SDK, wrapper |
Render targets
A render session has one kind. Render targets have three, and the kind belongs to the target:
| Target | Owned by | Attach with |
|---|---|---|
| native surface | caller | mln_<backend>_surface_attach |
| owned texture target | session | mln_<backend>_owned_texture_attach |
| borrowed texture target | caller | mln_<backend>_borrowed_texture_attach |
"Surface session" and "texture session" are not terms. The core triple is runtime, map, render session, and the render target is what a session draws into.
Before you finish
- Sentence-level rules hold throughout.
- Nothing is restated that a link would cover.
- The page serves one mode.
- A guide names the reader's choice before it shows an implementation.
- A guide finishes its task and skips the rest of the domain.
- Prose reads correctly for a reader on any binding.
- Terminology matches the tables above.