devpage 0.2.0

Live documents from a repository: sites, docs and slides from Markdown, and UI designs drawn from code — with comments, link checking and PDF
# Configuration reference

The exact contract of a devpage document file and of the `devpage` command
line: every field of each kind with its type, default and validation, the
commands and flags, and the errors devpage stops with. It is the reference page
under the [viewer](../README.md).

## Command line

```sh
devpage [path] [--root <folder>]
        [--host <address>] [--port <port>] [--user-header <name>]
devpage check [path] [--root <folder>]
devpage pdf <path> [--root <folder>] [-o <file>]
devpage video <path> [--root <folder>] [-o <file>]
devpage --help
devpage --version
```

| Command | Does |
| --- | --- |
| `devpage [path]` | Serves the document at `path` and everything in the served folder, until stopped. |
| `devpage check [path]` | Checks the document at `path` and everything reachable from it by links; prints each problem and exits `1` if there was any, `0` if not ([Checking links]../checking-links/README.md). |
| `devpage pdf <path>` | Prints a doc or a deck to PDF ([Printing to PDF]../pdf/README.md). |
| `devpage video <path>` | Records a deck to mp4, each slide's notes read aloud by GPT-Live ([Making a video]../making-a-video/README.md). |

`path` is read the same way by every command:

| `path` | Means |
| --- | --- |
| a folder | the document file `devpage.yml` inside it |
| a `.md` file | that file, as a doc of one chapter with default settings |
| a `.design` file | that [design]../designs/README.md, titled by its file name without `.design`: `/` leads to its canvas, and `devpage check` reports its problems |
| any other file | a document file, whatever its name |
| none | `.` — `devpage.yml` in the current folder |

### Flags

| Flag | Command | Default | Meaning |
| --- | --- | --- | --- |
| `--root <folder>` | all | the current folder | The [served folder]../writing-a-document/README.md#the-served-folder. The document must be inside it. |
| `--host <address>` | `devpage` | `127.0.0.1` | The address to listen on. Any non-loopback address makes devpage [deployed]../deploying/README.md. |
| `--port <port>` | `devpage` | the first free port from 3030 up | Listen on exactly this port; fail if it is taken. |
| `--user-header <name>` | `devpage` | `X-Forwarded-Email` | The request header naming a comment's author when deployed. Ignored locally. |
| `-o, --output <file>` | `devpage pdf` | `<title>.pdf` in the current folder | Where to write the PDF. |
| `-o, --output <file>` | `devpage video` | `<title>.mp4` in the current folder | Where to write the video. |

### Environment variables

Only `devpage video` reads any, and only these — never `OPENAI_API_KEY`
([the narrator](../making-a-video/README.md#the-narrator)). A variable set to
an empty string counts as not set.

| Variable | Default | Meaning |
| --- | --- | --- |
| `DEVPAGE_OPENAI_API_KEY` | none | Narrate with GPT-Live on OpenAI, with this key. |
| `DEVPAGE_AZURE_OPENAI_ENDPOINT` | none | Narrate with GPT-Live on this Azure Foundry resource — `https://<resource>.openai.azure.com/` or its bare host. Set, it selects Azure over OpenAI. |
| `DEVPAGE_AZURE_OPENAI_API_KEY` | none | The Azure resource's key; required with the endpoint. |
| `DEVPAGE_VIDEO_MODEL` | `gpt-live-1` | The model; on Azure, the deployment's name. |

On start, `devpage` checks the document is inside the
[served folder](../writing-a-document/README.md#the-served-folder), watches it, binds its address and prints one line on standard output — the entry
document's title and its address:

```text
<title> → http://localhost:<port>
```

It then logs every request on standard error (see
[Serving pages](../serving-pages/README.md#request-logging)) and each link
problem once, as it is found. It runs until stopped. Changes to document files
and pages take effect on the next request; nothing needs a restart.

## Document files

A document file is a YAML mapping whose `kind` field says what it describes.
Paths in it are relative to the file's folder and must stay inside the
served folder. A field the kind doesn't have is an error, not ignored.

### `site`

| Field | Type | Required | Meaning |
| --- | --- | --- | --- |
| `kind` | `site` | yes | Makes this file a site. |
| `title` | string | yes | The site's name: the sidebar heading and the second half of each tab title (`<page> · <title>`). |
| `root` | path | yes | The page the site opens on and the top of its sidebar tree. |
| `watch` | list of glob patterns | no | Narrows which files in the site's folder refresh open pages to those matching a pattern, relative to that folder. It applies to this site's folder only (a nested site's own list applies inside it), whichever document devpage was started with; document files always refresh. Default: every file that no `.gitignore` excludes. |
| `widgets` | path | no | The folder of this document's [project widgets]../widgets/project-widgets/README.md, relative to this file: every `.tsx` in it whose name starts with a capital letter is a widget. Default: none — only the system widgets. |
| `navbar` | map | no | The bar across the top: label → target links, dropdowns and icons ([the navbar]../organizing-navigation/README.md#the-navbar). Default: no navbar. |
| `sidebar` | boolean | no | Whether the Topics sidebar shows. Default: `true`. |
| `theme-selector` | boolean | no | Whether the button that switches between light and dark shows ([choosing a theme]../themes/README.md#choosing-a-theme). Default: `true`. |
| `logo` | path | no | An image — usually an SVG — shown before the title in the navbar (or the sidebar heading when there is no navbar) and used as the browser tab's icon. Default: none. |

A site's pages are the Markdown files in its document file's folder and below
it; a page there is shown in this site's frame. When sites are nested, a page
belongs to the nearest one.

### `doc`

| Field | Type | Required | Default | Meaning |
| --- | --- | --- | --- | --- |
| `kind` | `doc` | yes | | Makes this file a doc. |
| `title` | string | yes | | The doc's name: above the first chapter, in the tab, and the PDF's title and default file name. |
| `chapters` | list of paths | yes | | The chapter files, in reading order; at least one. |
| `pdf.paper` | `A4` or `Letter` | no | `A4` | The paper size. |
| `pdf.header` | string | no | none | Printed at the top of every page. |
| `pdf.footer` | string | no | `"{page} / {pages}"` | Printed at the bottom of every page. |
| `widgets` | path | no | none | The folder of this doc's [project widgets]../widgets/project-widgets/README.md. |
| `theme-selector` | boolean | no | `true` | Whether the light/dark switch shows. |
| `logo` | path | no | none | The browser tab's icon, and an image before the title. |

In `header` and `footer`, `{page}` is the page's number and `{pages}` the count.

A `.md` file given instead of a document file is a doc whose one chapter is that
file, titled by its first `# ` heading (or its file name), with every `pdf`
default.

### `slides`

| Field | Type | Required | Meaning |
| --- | --- | --- | --- |
| `kind` | `slides` | yes | Makes this file a deck. |
| `title` | string | yes | The deck's name: in the tab, and the PDF's title and default file name. |
| `slides` | list of paths | yes | The slide files, in order; at least one. |
| `context` | string | no | What the talk is, who it is for and its tone — what the slides' [notes]../making-slides/README.md#speaker-notes are read against; default none. |
| `widgets` | path | no | The folder of this deck's [project widgets]../widgets/project-widgets/README.md; default none. |
| `theme-selector` | boolean | no | Whether the light/dark switch shows; default `true`. Hidden in fullscreen. |
| `logo` | path | no | The browser tab's icon; default none. |

## Errors

devpage stops before serving when the **entry** document can't be loaded. It
prints `Error:` and what it was doing — `reading <file>` or `in <file>` — then
the cause:

### Loading the document

| Problem | Message |
| --- | --- |
| The file, or a folder's `devpage.yml`, can't be read | `reading <file>` — then the system error, e.g. `No such file or directory (os error 2)` |
| `kind` missing | `in <file>` — `` missing field `kind` `` |
| `kind` not one of the three | `in <file>` — ``kind: unknown variant `deck`, expected one of `site`, `doc`, `slides` at line 1 column 7`` |
| A field the kind doesn't have | `in <file>` — ``unknown field `root`, expected one of `title`, `chapters`, `pdf` `` |
| A `watch` pattern isn't a valid glob | `in <file>` — `watch pattern <pattern>`, then the glob error, e.g. `error parsing glob 'a[b': unclosed character class; missing ']'` |
| A required field missing, or of the wrong type | `in <file>` — the YAML error, with its line and column |
| `root`, a chapter or a slide isn't an existing file in the served folder | `in <file>` — `<field> <path> does not exist` |
| `chapters` or `slides` is an empty list | `in <file>` — `chapters lists no files` or `slides lists no files` |
| `widgets` names no folder, or one outside the served folder | `in <file>` — `widgets <folder> is not a folder` |
| A `navbar` dropdown holds another dropdown | `in <file>` — `navbar: <entry> under <dropdown> must be a link — dropdowns are one level deep` |
| A `navbar` dropdown lists no links | `in <file>` — `navbar: <dropdown> lists no links` |
| A `navbar` entry is neither a link nor a map of links — a number, or a list | `in <file>` — `navbar: <entry> must be a link or a map of links`, or the YAML error for a list |
| A `navbar` key has neither a label nor an `(icon)` | `in <file>` — `navbar has an entry with neither a label nor an icon` |
| The document isn't inside the served folder | `<path> is outside the served folder <folder>` |

### Binding the address

| Problem | Message |
| --- | --- |
| `--port` is taken | `binding <address>:<port>` — the system error |
| Every port from 3030 up is taken | `no free port` |

### After it starts

Once devpage is serving, an edit that makes the entry's file invalid doesn't
stop it either: `/` and the site's pages show the problem until the file is
fixed, and the last valid version is kept meanwhile. The same problems in a
document file reached by a link don't stop devpage: they are [problems](../checking-links/README.md), shown on the
pages that link to it and reported by `devpage check`. `devpage pdf` also stops
with `pdf prints a doc or slides, not a site` when given a site, and with the
error `downloading chrome-headless-shell into <cache folder>` when the first
download of [its Chrome](../pdf/README.md#the-chrome-it-drives) fails,
and with `pdf prints a doc or slides, not a design` when given a design.

`devpage video` stops, before it starts Chrome, with
`video needs GPT-Live: set DEVPAGE_OPENAI_API_KEY, or DEVPAGE_AZURE_OPENAI_ENDPOINT and DEVPAGE_AZURE_OPENAI_API_KEY`
when no narrator is set, `DEVPAGE_AZURE_OPENAI_ENDPOINT is set but DEVPAGE_AZURE_OPENAI_API_KEY is not`,
`DEVPAGE_AZURE_OPENAI_ENDPOINT is not an address: <endpoint>`,
`video records slides, not a site` (or `a doc`, `a design`), and
`every slide needs <Notes> to be narrated; these have none: <n> (<file>), …`.
Once running it stops with `downloading chrome-for-testing into <cache folder>`
when the first download of [its Chrome](../making-a-video/README.md#the-chrome-it-drives)
fails, `connecting to GPT-Live at <address>` when the session can't open (a
wrong key is `HTTP error: 401 Unauthorized`), with the error GPT-Live sends as
`GPT-Live: <message>`, and with
`the narrator said nothing for slide <n> within 60 seconds`. It writes no
file unless the whole deck was recorded.

A design's problems never stop devpage, even when the design is the entry:
its canvas shows them over the last valid drawing, and `devpage check` prints
each as `<file>:<line>:<column>: <message>` and exits `1`.

## Examples

devpage's own documentation, `docs/devpage.yml`:

```yaml
# devpage's own documentation, served by devpage.
kind: site
title: devpage
root: README.md
widgets: widgets
watch:
  - "**/*.md"
  - "**/*.yml"
```

[The feature catalog](../../../features/devpage.yml) shows the doc kind and
[the devpage talk](../../../talks/devpage/devpage.yml) the slides kind; each
`devpage.yml` is beside its chapters or slides.
[The checkout prototype](../../../designs/checkout/checkout.design) shows
the design kind, which has no document file
(`devpage docs/designs/checkout/checkout.design`).