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
# Rendering a page

How a Markdown file becomes the HTML page a reader sees: comrak turns it into
HTML with highlighted code, the title and sidebar are worked out, and the
pieces go into the page frame. It is a mechanism page under the
[viewer](../README.md), beside [Serving pages](../serving-pages/README.md),
for someone about to change `src/render.rs` or `src/highlight.rs`.

```mermaid
flowchart LR
  md["page.md"] --> comrak["Markdown to HTML<br/>comrak"]
  comrak --> code["Code highlighted<br/>syntect"]
  code --> frame["The page frame"]
  title["Title"] --> frame
  side["Sidebar"] --> frame
  click comrak href "#markdown-to-html" "Markdown to HTML"
  click code href "#highlighted-code" "Highlighted code"
  click title href "#the-title-and-the-sidebar" "The title and the sidebar"
  click side href "#the-title-and-the-sidebar" "The title and the sidebar"
  click frame href "#the-page-frame" "The page frame"
```

A file ending in `.md` is read as text and passed to `render::page`, which
does the four steps below.

## Markdown to HTML

`render::markdown` runs comrak with these options
(`options()` in `src/render.rs`):

| Option | Effect |
| --- | --- |
| `extension.table` | GFM tables |
| `extension.strikethrough` | `~~text~~` |
| `extension.autolink` | bare URLs become links |
| `extension.tasklist` | `- [ ]` / `- [x]` items |
| `extension.footnotes` | `[^1]` footnotes |
| `extension.header_id_prefix = ""` | every heading gets an id from its text, with no prefix |
| `render.unsafe` | raw HTML passes through |
| `render.github_pre_lang` | a fence's language is passed as a `lang` attribute — which the highlighter below then drops for every block except `mermaid` |

Last, every Markdown table is wrapped in `<div class="table-scroll">`, so a
table wider than the page scrolls inside its own box instead of widening
the page.

An image of a `.design` file — `![Checkout](checkout.design)`, or a raw
`<img src="checkout.design">` however its attributes are quoted, spaced or
capitalised — becomes the design's embed (`embed_designs`): a
`<canvas class="design">` keeping the link in `src` and the image's text as
its label, which the page script has the
[design engine](../design-engine/README.md#in-the-browser) draw. Any other
image is left as it is.

## Highlighted code

`render::markdown` also plugs in a code-fence highlighter,
`highlight::Highlighter` in `src/highlight.rs`, through comrak's `Plugins`
(`SyntaxHighlighterAdapter`). For each fenced block it:

- takes the first word of the info string as the language and looks it up
  with syntect's `find_syntax_by_token` (name or file extension) in
  `two_face::syntax::extra_newlines()` — the syntax set `bat` ships, loaded
  once on first use through a `LazyLock`;
- if found, and the language isn't `mermaid`, emits syntect's class-based
  HTML (`ClassStyle::SpacedPrefixed { prefix: "hl-" }`): nested spans such
  as `<span class="hl-keyword hl-operator hl-arithmetic hl-rust">`, colored by the
  `--syntax-*` tokens in `assets/style.css` (see
  [Themes]../themes/README.md#the-tokens);
- otherwise — no language, an unknown one, `mermaid`, or a highlighting
  error partway through — writes the code HTML-escaped and unhighlighted;
- writes a bare `<pre>` and `<code>` with no language attributes, except
  `<pre lang="mermaid">` for Mermaid blocks, which is how the page script
  finds the diagrams to draw.

## The title and the sidebar

**Title.** `render::title` takes the first line starting with `# `, or the
file name without its extension.

**Sidebar.** The [navigation tree](../navigation-tree/README.md) is built
from the root page and rendered to nested lists, with the current page
marked.

## The page frame

The pieces go into one HTML page. A site's page, with a navbar and a sidebar,
has this shape:

```html
<html data-theme="light">
  <head>  <!-- icon, theme script, import map, stylesheet, pele, devpage.js -->
  <body class="site with-navbar">
    <header id="devpage-navbar">  <!-- logo, title, links, switches -->
    <nav id="devpage-nav">        <!-- the sidebar tree -->
    <main id="devpage-page">
      <article>                   <!-- the rendered Markdown -->
```

In detail:

- `<title>` is `<page title> · <document title>`;
- first in `<head>`, the document's logo as `<link rel="icon">` when it has
  one, then a small script that sets `data-theme` on `<html>` — the
  reader's stored choice, or the system's — before anything paints
  ([choosing a theme]../themes/README.md#choosing-a-theme);
- `<body>`'s class names the frame: `site`, plus `with-navbar` and
  `no-sidebar` as the document file says;
- `<header id="devpage-navbar">`, when the site has a navbar: its logo and
  title, its links and dropdowns with their icons drawn as inline SVG, then
  the comment-mode switch and the theme selector ([the navbar]../organizing-navigation/README.md#the-navbar);
- `<nav id="devpage-nav">`, unless `sidebar: false`, holds the tree — after a link
  to the site's root page with its logo and title when there is no navbar
  to hold it;
- with no navbar, the comment-mode switch and the theme selector (unless
  `theme-selector: false`) in `<div id="devpage-corner">`, top right;
- `<main id="devpage-page"><article>` holds the rendered Markdown;
- in `<head>`, an import map naming React (`react`, `react/jsx-runtime`,
  `react-dom/client` → `/__devpage/react.js`) and `window.devpageWidgets`,
  the document's widgets with the address of each
  ([Widgets]../widgets/how-widgets-run/README.md);
- the stylesheet and two scripts — pele, then `devpage.js` — are loaded from
  `/__devpage/`.

Every id the frame uses starts with `devpage-` — `devpage-page`,
`devpage-nav`, `devpage-navbar`, `devpage-corner`, `devpage-comments` and
`devpage-comments-toggle` — and a heading whose text would give it one of
them gets `-heading` added to its id. So a page can have a heading called
*Comments*, *Page* or *Navigation* and it stays an ordinary heading, never
styled or replaced as part of the frame.

`devpage-navbar`, `devpage-nav` and `devpage-page` are what
[live updates](../live-updates/README.md) replace in place. A doc's and a
deck's frames carry the same head — the logo's icon link and the theme script —
and the theme corner. Titles and sidebar labels are HTML-escaped; the page body
is not, since raw HTML is allowed.

Beyond the index, nothing is cached between requests: each page request
re-reads the page, its site's document file and, to build the sidebar, every
page the tree reaches.