Authoring

# Authoring pages

One command scaffolds a page — the class and its registry line. Then write content; the shell, masthead, and TOC come free.

## One command

rails g docs_kit:page writes the class AND registers it — both derived from the title.

```shell
rails g docs_kit:page "Getting Started" --group=Guide
```

That writes `app/views/docs/pages/getting_started.rb` (slug `getting-started`, class `GettingStarted`) and injects `page "Getting Started", group: "Guide"` into the `Doc` registry, so the page is routed and in the sidebar the moment you fill in `#content`. Every derivation is overridable:

- `--slug=auth` — the URL slug,
- `--view=OauthGuide` — the class basename,
- `--eyebrow="Advanced"` — the eyebrow (defaults to the group),
- `--registry=Guide` — a differently-named registry class.

Re-running is idempotent, and a legacy hash-`entries` registry is left untouched (the generator prints the entry to add by hand).

> **Tip:** The rest of this page is what the generator produces — the shape to reach for when you hand-write or edit a page.

## A page is a Phlex class

Subclass DocsUI::Page, declare its metadata, fill in #content.

```ruby
# frozen_string_literal: true

# Compact class reference — Zeitwerk resolves it through the
# directory-implied namespaces, so no nested-module ceremony.
class Views::Docs::Pages::Guide < DocsUI::Page
  title "Guide"
  eyebrow "Getting started"

  def lead = "One sentence that sits under the page title."

  def content
    DocsUI::Section("First steps", description: "What this section covers.") do
      md <<~'MD'
        Prose written as Markdown, styled with the reading rhythm.
      MD

      DocsUI::Code(<<~SOURCE, filename: "config/routes.rb")
        Rails.application.routes.draw do
          mount DocsKit::Engine, at: "/docs"
        end
      SOURCE
    end
  end
end
```

`title` names the page, `eyebrow` groups it above the title, and `lead` is the summary sentence under it. Everything you render lives in `content`.

The shell (topbar, sidebar, theme switcher), the page masthead, and the **On this page** TOC are added automatically — you only write the body.

## Register the page

One line in the Doc registry — slug and view derive from the title.

A page shows up once it has a `page` line in the `Doc` registry. `slug` and `view` derive from the title (both overridable per line), and `group:` sets its sidebar heading. The generator injects this line for you.

```ruby
class Doc
  extend DocsKit::Registry
  path_prefix    "/docs"
  view_namespace "Views::Docs::Pages"

  page "Overview", group: "Getting started"
  page "Guide",    group: "Getting started"
  # overrides win: page "OAuth", group: "Guide", slug: "auth", view: "OauthGuide"
end
```

The sidebar derives from the registry — set `c.nav_registries = { "Docs" => Doc }` in the initializer and never hand-write a nav lambda again.

> **Note:** The sidebar only links a page whose class exists, so a page line without its class yet is a no-op — no dead links.

## The building blocks

The DocsUI kit you compose inside #content — and where each one is documented in full.

Inside `#content` you reach for a small kit. The everyday four — a `DocsUI::Section` wrapper, `md` for Markdown prose, `DocsUI::Code` for a highlighted block, and `DocsUI::Callout` for an aside — carry most pages. The rest are specialised; each has its own reference page rather than being re-explained here.

| Block | Use for | Full reference |
| --- | --- | --- |
| `DocsUI::Section(title)` | an anchored subsection with a heading (+ optional description:) | [Components](https://docs-kit.zoolutions.llc/docs/components) |
| `md(source)` | a block of GFM Markdown — the everyday prose helper | [Markdown authoring](https://docs-kit.zoolutions.llc/docs/markdown) |
| `prose { … }` | hand-authored prose (p/ul/code) in a reading-rhythm wrapper | [Components](https://docs-kit.zoolutions.llc/docs/components) |
| `DocsUI::Code(source)` | a Rouge-highlighted code block | [Code languages](https://docs-kit.zoolutions.llc/docs/languages) |
| `example { \|ex\| … }` | multi-language tabbed code | [Code languages](https://docs-kit.zoolutions.llc/docs/languages) |
| `DocsUI::Callout(level)` | note / tip / warning boxes | [Components](https://docs-kit.zoolutions.llc/docs/components) |
| `DocsUI::Table / PropTable` | reference tables — headers + rows, or the args preset | [Components](https://docs-kit.zoolutions.llc/docs/components) |
| `DocsUI::Endpoint / RequestExample` | the API-reference kit — a method+path line, client tabs, a fields table | [API reference](https://docs-kit.zoolutions.llc/docs/api) |

The primary argument is always positional — `Section("Title")`, `Code(source, filename:)` (the filename picks the language), `Header("Title")` — with modifiers as keywords (`description:`, `eyebrow:`).

For the wrappers that take no argument, use the lowercase page helpers `prose` / `example` (and `md` for Markdown). A lowercase method takes a block without parens, so `prose do … end` just works. The kit forms `DocsUI::Prose()` / `DocsUI::Example()` stay valid — they only need the empty `()` because a bare `DocsUI::Prose do` parses as a constant reference (a SyntaxError).

> **Tip:** Prose is a **Markdown island**: `md <<~'MD'` parses GFM with commonmarker and emits native Phlex nodes — tables, fenced code (routed through `DocsUI::Code`), links, all Phlex-escaped. Use a single-quoted heredoc so `#{...}` stays literal author text. See [Markdown authoring](https://docs-kit.zoolutions.llc/docs/markdown) for the full vocabulary.

## The "On this page" TOC

Built for you from your section headings.

Every `DocsUI::Section` heading becomes an entry in the automatic **On this page** table of contents — you never list them by hand.

Override its placement per page with `on_page`.

```ruby
class Views::Docs::Pages::Guide < DocsUI::Page
  on_page :toggle   # :toggle | :panel | :sidebar | false
end
```

See the [On this page](https://docs-kit.zoolutions.llc/docs/on-this-page) reference for every mode and the site-wide default.