Skip to content

Writing Kiodoc

Kiodoc is Markdown with Kio-aware checks. Use it for package guides, API notes, and /// comments that should stay in sync with the code.

The command-line entry point is kio doc:

kio doc check
kio doc build

kio doc check validates snippets and links. kio doc build validates first, then writes the rendered site declared by the package's build { docs { ... } } block.

Doc comments

Write /// immediately above a module item:

/// Adds one to `x`.
///
/// See [`@signature inc`].
pub fn inc(x: Int) -> Int { add_i32(x, 1(Int)) }

The body is Markdown. Blank /// lines become blank Markdown lines. The rendered docs include the item's signature, and editor hover shows the same Markdown content for documented declarations. This works for functions, aliases, newtypes, labels, operators, and user-defined elaborators.

Editor highlighting distinguishes headings, emphasis, list and quote markers, links, Kiodoc reference operands, directive words, and fenced examples from surrounding comment prose. Compiler-derived colors show ordinary Kio tokens inside kio examples, including incomplete module and expression fragments. A highlighted reference is a written Kiodoc form, not a promise that the name resolves; kio doc check performs that validation. Escaped delimiters and inline code stay literal. TextMate and standalone Tree-sitter use conservative coloring after an inline-code opener without a same-line closer: the rest of that comment run stays neutral, even after a closer on a later line. Compiler-derived semantic colors and Kiodoc completion use the whole comment. TextMate styles emphasis only when its closing marker is on the same line; references inside multiline emphasis still receive their Kiodoc colors. TextMate colors each physical Kio example line independently, so multiline Kio context does not carry across those lines or escape the example's fence. The VS Code Tree-sitter consumer instead classifies each complete fence body; standalone Tree-sitter queries retain a readable code-body style.

Inside a Kiodoc reference, editor completion offers names from the surrounding module and its explicit imports. After @, it offers the directive words; @type narrows the name list to declarations with a bound value type. Choosing a name replaces the whole reference operand, including operator spellings. Ordinary prose, links and literal code do not offer these name suggestions.

Module docs use the same syntax before the module line:

/// Utilities for the package's public API.
module demo/util;

References

Use backtick links for names that should be checked:

Call [`render`] after [`parse`].

kio doc check verifies that each referenced name is in scope. In a /// comment, the scope is the surrounding module. In a Markdown guide, the scope is the package's bridge surface. When a name resolves, the rendered site links to its documentation page; when a name is outside the package, rendered output falls back to code style.

Normal Markdown links still work:

[Kio]: https://github.com/

Item directives

Three inline directives embed information about a named item:

[`@signature render`]
[`@type render`]
[`@source render`]

@signature inserts the declaration header. @type inserts the bound value type. @source inserts the item's source as a Kio code block. The REPL exposes the same views as :signature, :type, and :source, so prose and interactive inspection use the same names.

For a member of a recursive type group, the signature and source include the whole rec { ... } group: those peer declarations are needed to understand and reuse the selected type. The member keeps its own documentation and link anchor. A comment before the group's rec keyword appears once under “Recursive group”, separately from member comments. Kiodoc checks both the group comment and every member comment.

You can also refer to the uppercase type generated by ordinary or recursive labels. Its signature and source show the original labels declaration, including its recursive context when present. Like other type names, it has no bound value for @type.

A forwarded label uses a braced declaration name, such as {field} or pkg/record.{field}. Its @signature and @source views show the forwarding declaration, and its page shows its own comment. This keeps it distinct from a function named field. It introduces no uppercase type and has no bound value for @type.

The Package Boundary page lists the bridged modules' host and unscoped pub declarations. Imports and private declarations do not become package exports. An item's own comment keeps its original module context on both pages. Links identify the declaring module as well as the declaration: Box in pkg/left uses #item-pkg_2fleft-Box, distinct from Box in pkg/right. Module and boundary sections share that fragment, and links point to the declaring module page. The anchor scheme defines the encoding, including the separate identities of prefix and binary operators with the same displayed token.

Kio snippets

A Markdown guide can contain checked Kio fences. Use a harness when the snippet needs surrounding declarations: declare it once with harness=NAME and a placeholder=... marker, then reference it from snippets with {@NAME}. Here the marker is inside a function, because the snippet contains statements and a result expression:

module demo/main;

fn id(x: .) { x }

fn example() {
  __SNIPPET__
}
let x = id(());
x

Substitution is literal: a marker at module scope accepts declarations, not expression statements. A harness may instead live in an HTML comment such as <!--kio {harness=demo placeholder="__SNIPPET__"} ... -->. Hide only repetitive wrapping; show or explain any declarations and imports the reader needs to understand the example.

A {file} fence is different: it declares a document-scoped support file — not a snippet and not a harness — that snippets and file-backed harnesses can import from. The support files and the substituted harness form one scratch package. Their module paths can have different prefixes; for example, app/main can import utils/list_ops. You can supply a package file with {file}; otherwise, Kiodoc creates one.

Inside a /// comment, use {@} for snippets that should be checked inside the surrounding module:

/// ```kio {@}
/// let value = id(());
/// value
/// ```
fn id(x: .) { x }

Editor completion inside this fence uses the surrounding module, just like the check. It does not borrow the documented function's local variables. Accepted plain-text edits preserve the physical /// prefixes and line endings; snippet candidates insert a plain name rather than expanding placeholder scaffolds across comment lines.

Use {ignore} only for true fragments or examples that are deliberately not Kio input.

Root module support files

Markdown snippets can import module helpers with import; Kiodoc finds *.kio modules under the package root automatically. Submodules keep their full relative paths: helpers/map.kio is imported as helpers/map, not map. When documentation snippets share checked helpers from another directory, list that directory in the package build block:

build {
  docs {
    md "docs";
    support "../test-data/poc/elab/workdir";
  }
}

Support files are used for snippet validation only. They are not rendered as documentation pages.

Only modules named by the snippet's imports, and their transitive imports, are included. The package root takes precedence over configured support directories, which are considered in their listed order. A chosen top-level module namespace comes entirely from that location; its descendants are not filled in from a later directory. Document-scoped files and file-backed harnesses take precedence over support files in their module namespace.

Discovery stays within each directory's package: a nested package is a separate source boundary. Hidden directories, out, and target are skipped, and directory symlinks are not followed.

Documentation for host authors

Package builds carry docs on exported functions, host requirements, and public newtypes into the generated host interface. Host tools display them in their usual documentation form. References show the Kio names you wrote; item directives show their operand without embedding another declaration's source. Code examples remain documentation, not host code to execute.

Private implementation and module docs stay out of this interface. The boundary documentation contract gives the exact selection rules and links to each target's presentation.

Editor support

The language server renders Kiodoc as Markdown in hover. It also shows builtin docs for __intrinsics__ and __comptime__, so compile-time helpers and user-defined elaborators are presented through the same documentation surface.