This briefs an AI assistant working on the visual layer of a
lazysite site - the layout template (layout.tt) and its
themes. For content, see
AI briefing - authoring. For
configuration, see
AI briefing - configuration. For the
content/layout/theme separation this all rests on, see
AI briefing - building sites.
Layout: the Template Toolkit file (layout.tt) that wraps
every page. Provides <head>, header, navigation, footer.
Installed at lazysite/layouts/NAME/layout.tt with optional
lazysite/layouts/NAME/layout.json metadata.
Theme: colours, fonts, spacing, and assets that sit on top
of one or more layouts. Installed nested at
lazysite/layouts/LAYOUT/themes/THEME/. Declares compatibility
in theme.json's layouts[] array.
Manager UI: has its own internal template at
lazysite/manager/layout.tt. Outside the layout+theme system.
Do not modify unless explicitly asked.
On-disk example:
lazysite/
layouts/
default/
layout.tt
layout.json
themes/
odcc/
theme.json
main.css
assets/
manager/
layout.tt
assets/manager.css
lazysite-assets/
default/
odcc/
main.css
assets/
Always available:
content - rendered HTML page bodypage_title, page_subtitle - front-matter valuessite_name - from lazysite.confnav - array parsed from nav.confrequest_uri - current URL pathD013 additions:
layout_name - resolved layout name (string)theme_name - resolved theme name when a compatible theme is
active (string; unset otherwise)theme - hash, the parsed theme.json. Access config values
as [% theme.config.colours.primary %] etc. Empty hash when
no theme is active.theme_assets - URL prefix /lazysite-assets/LAYOUT/THEME
(nested for local themes), or /lazysite-assets/CACHE_KEY for
remote layouts (flat), or unset when no themetheme_css - pre-rendered `Naming: --theme-GROUP-KEY.
Use in the theme's CSS:
body {
color: var(--theme-colours-text);
font-family: var(--theme-fonts-body);
}
This is the recommended pattern: layout.tt emits theme_css;
the theme's own main.css references the variables. A theme
fork that only tweaks colours edits theme.json and doesn't
need to duplicate CSS structure.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>[% page_title %][% IF site_name %] - [% site_name %][% END %]</title>
[% theme_css %]
[% IF theme_assets %]
<link rel="stylesheet" href="[% theme_assets %]/main.css">
[% END %]
</head>
<body>
[% IF nav.size %]
<nav>
[% FOREACH item IN nav %]
<a href="[% item.url %]">[% item.label %]</a>
[% END %]
</nav>
[% END %]
<main>
<h1>[% page_title %]</h1>
[% IF page_subtitle %]<p>[% page_subtitle %]</p>[% END %]
[% content %]
</main>
</body>
</html>
A site has ONE active layout + theme, set in lazysite.conf:
layout: default
theme: odcc
Both values are sanitised to [A-Za-z0-9_-] at resolve time. Activate the
theme globally and keep pages layout-agnostic - do not put layout: in page
front matter as the way to apply a design. Every page then inherits the active
layout, so the whole site re-themes in one step. A per-page layout: is only
for previewing a staged candidate (below) or a deliberate one-off page - and
you remove preview overrides once you activate.
Agents set these themselves through the control API (layout-activate /
theme-activate), which also clears the cache - it is a self-serve action with
manage_layouts / manage_themes, not an operator hand-off.
If you publish over WebDAV you do NOT edit the live look in place - you stage a new layout beside the active one, preview it, and activate it yourself over the control API.
layout.tt) needs manage_layouts; theme files
need manage_themes (separate capabilities). The token does not encode
capabilities - they are read from your account on every request - so an
operator's grant takes effect immediately and you do NOT need a new
token. If a layout write still 403s right after a grant, you are
almost certainly writing the active layout (next point), which is
denied regardless of capability. (Ruled that out and a fresh grant still
seems not to apply? Rotating your token is a reliable belt-and-braces.)PUT into the
active layout returns 403: the live layout is immutable in place, by
design (a deliberate guard, not a grant failure). A path under a new
layout returns 409 until you create its collections, then it is
writable - so MKCOL lazysite/layouts/<new> and
…/themes/<theme> first, then PUT the files.layout: <new> in a single page's
front matter to render that page through the staged layout before any
global switch - this is the preview mechanism. The theme's SOURCE css is
web-served at /lazysite/layouts/<new>/themes/<theme>/main.css, so
reference that for preview; the canonical mirror
/lazysite-assets/<new>/<theme>/main.css is 404 until activation.POST action=layout-activate&path=<new> then
action=theme-activate&path=<theme> to the control API (needs
manage_layouts / manage_themes); each sets the pointer in
lazysite.conf AND clears the cache atomically - no operator step. Then
remove the per-page layout: preview overrides: they are a preview
tool, not the deploy mechanism, and left in place they quietly defeat the
next site-wide theme switch. Once active, the canonical /lazysite-assets/
mirror serves the theme CSS.main.css and other theme assets must live under the theme's assets/
directory: lazysite/layouts/<layout>/themes/<theme>/assets/main.css. On
activation the server builds a flattened mirror served at
/lazysite-assets/<layout>/<theme>/main.css, and layout.tt links that mirror.
main.css at the theme ROOT (not under assets/) is not mirrored, so
the page links a 404. Put assets under assets/.theme-activate /
layout-activate), so after activating, GET
/lazysite-assets/<layout>/<theme>/main.css returns 200 - a
copied-then-activated layout is drop-in, with no CSS-path edits needed. If it
ever 404s, re-activate to rebuild it (or, as a fallback, write the mirror
files directly over WebDAV)./lazysite/layouts/<layout>/themes/<theme>/main.css - use that for preview;
switch links to the /lazysite-assets/ mirror once active.theme.json must be strict JSON, ASCII, and quote-free in values - a
non-ASCII character (e.g. an em-dash in description) or embedded quotes in a
config value fails validation. The check runs at activation (and is
cached), so after fixing theme.json you must re-activate, not just
re-PUT it; a rejection now names the failing reason.
Author .html files in the content tree (include partials with no matching
.md/.url source) are content, not cache - the activation cache-clear
leaves them alone. Generated cache (<page>.html beside <page>.md) is what
gets cleared.
If theme.json.layouts does NOT contain the active layout:
theme not declared for layouttheme_css is emptytheme_assets is unsettheme is an empty hashlazysite/manager/layout.tt - that's the
manager UI's internal chrome.lazysite/themes/NAME/ - the processor doesn't look there
any more.view.tt - that file name is gone; it's
layout.tt now.layouts[] from theme.json - the manager
rejects the upload and the processor can't activate it.theme.config values; ;{}<>
are stripped to prevent declaration escape.