Live preview
lttr serve (D17) is the day-to-day way to see templates while editing them: it compiles from disk on every request, so a save shows up on the next load with no restart.
make serve
# or directly:
go run ./cmd/lttr serve -addr localhost:7070 -dir examples/quiet/templates
opens http://localhost:7070.
Watch the reload loop
Unlike lttr preview, which renders the nine embedded sources once into static files, serve reads each template's file from -dir (default examples/quiet/templates) fresh on every request — so editing examples/quiet/templates/01-order-shipped.md and reloading shows the change immediately, no rebuild.
The index page itself polls GET /_version once a second. /_version hashes the name, size and modification time of every file in -dir; when the hash changes, the page:
- Fetches
/again and swaps in the refreshed sidebar list and detail sections (innerHTML, not a full page reload). - Re-selects whatever template was open and reloads its preview frames from
/t/{base}.html?v={version}— the version query string busts the browser's cache for that frame.
The live badge in the header shows this is happening (green dot, "live"); if /_version fails to answer, it flips to "offline" (grey) until polling succeeds again. This loop is what makes lttr serve a live preview and lttr preview's output a one-shot snapshot.
Read errors in the preview
A compile or render error never breaks the page. Errors are formatted as file:line:col: message (or file: message for one with no position) and shown two places:
- The sidebar replaces that template's family/stream/subject line with a red
N error(s)badge. - Its preview frame (
/t/{base}.html) serves a small standalone error page — dark red on a light red background, monospace, one<li>per error line — in place of the rendered HTML, with HTTP status 200 (not 500), so it renders inside the iframe instead of showing the browser's own error page.
/t/{base}.txt and /t/{base}.eml return the same error lines as plain text when the template fails, rather than a broken text part or a .eml that can't parse.
Use the index controls
The same index.html (embedded via //go:embed index.tmpl, shared by preview and serve) has a header with three control groups plus a standalone page-theme button, and keyboard shortcuts:
| Control | Values | Keyboard | What it does |
|---|---|---|---|
| Email colour scheme | Light / Dark | d | Sets color-scheme on each preview <iframe> and, when the frame's document is readable, flips the email's own prefers-color-scheme media rules directly through the CSSOM — some browsers don't propagate the iframe's color-scheme into the frame, so this is the belt-and-braces fix (D18, amended by D32). |
| Preview width | Both / 600 / 375 | w | Shows one or both size frames (the templates are built for 600px desktop and 375px mobile). |
| Part | HTML / Plain text | t | Toggles between the rendered HTML frames and the plain-text body shown as preformatted text. |
| Page theme | (button, no group) | — | Switches the page's own chrome between light and dark — independent of the email colour scheme above, and unrelated to it. |
j/k (or the arrow keys) move to the next/previous template in the sidebar. Every choice is remembered in localStorage and restored on the next visit; the page theme choice is applied before first paint via an inline script, so the chrome never flashes light-then-dark.
The static lttr preview output embeds each rendered email into its frame with srcdoc (rather than an iframe src URL) precisely so the page still works when opened directly as a file:// document, with no server behind it (D32); lttr serve instead points frames at /t/{base}.html, since it has a server to load from and wants each reload to hit it.
Run make preview
make preview
runs go run ./cmd/lttr preview -out preview, the static, one-shot equivalent — see The lttr CLI for exactly what it writes (.html, .txt, .eml, .md.txt, assets/, manifest.json, index.html). Open preview/index.html directly in a browser; there's no server and no reload loop, so it's the form to hand someone for a one-off review, or to feed to make shoot for the screenshot pass.
Next steps
- The lttr CLI for every command's flags.
- Screenshots for the automated pass that runs on top of
preview's output.