# Writing content

> Add a guide, blog post, or changelog update without creating a new route.

Source: http://tinyro.bot/docs/writing-content

Content Collections turns local Markdown into typed data. Its Vite adapter handles generation during development and production builds.

## Add a documentation page

Create a Markdown file under `web/web-content/content/docs`:

```yaml
---
title: Your guide
description: A short explanation of what this guide covers.
section: Guides
order: 3
---
```

Write the body below the frontmatter. The file path becomes the URL, and the page appears automatically in the sidebar and docs overview. Use `Getting started` or `Guides` for the section. The `order` field controls ordering within that section.

## Add a blog post

Create a file under `web/web-content/content/blog`:

```yaml
---
title: A new note
description: A short summary for the blog index.
date: '2026-10-16'
author: tinyrobot
draft: false
category: Field notes
artwork: recognition
sample: false
---
```

Quote dates and use the YYYY-MM-DD format. Posts are listed newest first. Set `draft: true` to hide a post from both the index and its public route. Draft content is still generated locally; it is not a place to store secrets.

`artwork` selects a reviewed illustration from `@tinyrobot/artwork`, used on the
index and article page. It defaults to `small-steps`; examples include
`recognition`, `denoising`, and `robot-companion`. These are illustrations, not
experiment outputs. `category` defaults to `Notebook`. Set `sample: true` for
visible mock posts: cards and articles label them as samples. Also identify sample
content in the Markdown body so that Markdown and LLM readers see the disclosure.

## Add a changelog update

Create a file under `web/web-content/content/changelog`:

```yaml
---
title: A small improvement
description: A short summary of what changed.
date: '2026-10-16'
draft: false
---
```

Write the changes below the frontmatter using headings and lists. The file path
becomes the URL: `a-small-improvement.md` is served at
`/changelog/a-small-improvement`. Nested paths work too. Updates are listed newest
first, with file paths breaking ties on the same date. `draft` defaults to `false`;
set it to `true` to hide an update from both the index and its public route.
As with blog drafts, generated content is not a place to store secrets.

Changelog routes and screens live alongside home, docs, blog, and the Markdown
pipeline in `web/web-content`, included only in the cloud deployment. Adding an
entry does not require adding a route file.

## Write for the reader

The title is rendered by the page layout, so start body sections with second-level headings. Standard Markdown supports links, lists, quotes, images, and fenced code blocks.

Use site paths for internal links, such as `/docs/quickstart`. Raw HTML is disabled during Markdown compilation. Only repository-authored Markdown is supported; this is not an upload or user-content pipeline.

### Code highlighting

Add a language after a code fence, such as `sh`, `ts`, `python`, `json`, or
`yaml`. [Shiki](https://shiki.style/) highlights fenced blocks during content
generation for docs, blog posts, and changelog entries; no highlighter runs in
the reader's browser. Unlabeled blocks, `text`, and unknown languages render
as plain text. Inline code keeps its existing styling.

Syntax colors follow the site's light/dark mode through shared UI theme
tokens. Highlighting changes only the HTML; Markdown and LLM endpoints keep
the original code fences and source text.

## Markdown for agents

Every published doc, blog post, and changelog entry also has a Markdown URL:
append `.md` to its page URL, for example `/docs/quickstart.md`. Nested paths
work the same way. The response includes the title, description, source URL,
publication metadata when available, and the original Markdown body without
frontmatter or the site shell.

Use the **View as Markdown** link on each entry, or start at [/llms.txt](/llms.txt)
for a grouped index. [/llms-full.txt](/llms-full.txt) includes the full text of
every published entry. Both update from the same Content Collections data as
the website; there is no second copy to maintain. Blog and changelog drafts
are excluded from all three forms, and missing Markdown URLs return 404.

These endpoints belong to the cloud website, not the app-only self-hosted
deployment. Marketing, index, application, and status pages are not authored
content entries and do not have `.md` counterparts.

## Generated content

### Python API reference

Edit Python SDK signatures and Markdown docstrings in `sdk/py/src/tinyrobot`,
then run:

```sh
uv run --locked python scripts/generate_python_docs.py
pnpm --filter @tinyrobot/web-content generate
```

Griffe statically extracts the package's `__all__` exports and the explicit
additional public symbols in `scripts/generate_python_docs.py`. It does not
import the SDK or require optional robotics dependencies. Generated
`content/generated/python-api/*.json` files are committed and must never be edited by
hand. Removed exports are removed from this directory on regeneration.

Content Collections validates those JSON files, converts them to reference
Markdown in TypeScript, and compiles them with Shiki. All symbols are appended to
the single Python reference page, with in-page heading links. That complete page
joins navigation, search, Markdown responses, and LLM exports.
The cloud build needs only Node; Python validation needs no Node. SDK checks
include `uv run --locked python scripts/generate_python_docs.py --check` to reject
missing, changed, or obsolete metadata. Nx exposes the same tasks as
`tinyrobot-py:docs:generate` and `tinyrobot-py:docs:check`.

Use ordinary Markdown docstrings (including fenced Python examples). This small
adapter does not interpret Sphinx roles or generate versioned release docs.

### TypeScript API reference

The [TypeScript SDK reference](/docs/reference/ts) is generated from public exports
in `sdk/js/src/index.ts` with TypeDoc and its Markdown plugin. The
[Python SDK reference](/docs/reference/py) each occupy one page and one item under
the sidebar's Reference section. Symbols use in-page anchors, not separate pages.
Both complete pages appear in search, Markdown responses, and LLM exports.

After editing TypeScript exports or JSDoc, run:

```sh
pnpm --filter tinyrobot docs:generate
pnpm --filter @tinyrobot/web-content generate
```

TypeDoc's Markdown plugin handles overloads, generics, types, and JSDoc. The
exporter uses the module router to store a single normalized reference page in
`content/generated/typescript-api/*.json`. These are committed build inputs,
not rendered HTML. Generated metadata for both languages lives under
`content/generated/`; regenerate it rather than editing it manually.
The compiled `.content-collections` output remains ignored.

`pnpm --filter tinyrobot docs:check` rejects missing, changed, or obsolete
metadata and also runs during SDK tests and Nx `generate:check` validation.
The library currently exports no public symbols, so its landing page says so;
CLI internals are not treated as library APIs. Future exports automatically
appear on that same page. Web builds consume the committed metadata and do not
run Griffe or TypeDoc.

### Build artifacts

Do not edit `web/web-content/.content-collections` or deployment `src/routeTree.gen.ts` files and `web/web-router/src/*-routeTypes.gen.d.ts`. Content Collections and the TanStack Start plugin regenerate these files. Run `pnpm --filter @tinyrobot/web-content generate` before a standalone typecheck when generated data is absent.

For more options, see the [Content Collections documentation](https://www.content-collections.dev/docs).

