AI briefing - content authoring
Guide for AI assistants helping users write content on a lazysite site.
Who this is for
This document briefs an AI assistant helping a user author content on a lazysite site. It covers the page format, front matter, Markdown extensions, and URL conventions. For view/theme authoring, see AI briefing - layouts. For configuration, see AI briefing - configuration.
Page format
Every page is a plain text file ending in .md. Pages begin with a
YAML front matter block delimited by ---, followed by the page body
in Markdown:
---
title: Page Title
subtitle: Optional subtitle shown below title
register:
- llms.txt
- sitemap.xml
---
Page content in Markdown.
The content body is converted to HTML and inserted into the site's
view template at [% content %]. The view wraps it with navigation,
header, and footer.
Front matter keys
title-
Page title. Required for most pages. Used in the
<title>tag and the page<h1>. The browser<title>renders as "page title - site name", so on the home page make this differ fromsite_nameinlazysite.conf- an identical title and site name read as "My Site - My Site". subtitle- Short description shown below the title. Optional.
ttl-
Cache TTL in seconds. The page regenerates after this interval rather
than on
.mdfile edit. Example:ttl: 300. register-
List of registry files the page appears in. Values match template
filenames in
lazysite/templates/registries/without the.ttextension. Common values:llms.txt,sitemap.xml,feed.rss,feed.atom. date-
Publication date in
YYYY-MM-DDformat. Used in feed entries. Falls back to file mtime if not set. tt_page_var-
Page-scoped Template Toolkit variables. Supports
url:,scan:, and${ENV}prefixes. Page variables override site variables.
tt_page_var:
hero_image: /img/landing.jpg
latest_release: url:https://raw.githubusercontent.com/example/repo/main/VERSION
blog_posts: scan:/blog/*.md sort=date desc
raw-
raw: trueoutputs the converted body without the view wrapper. Useful for fragments, AJAX partials, or API-style endpoints. api-
api: trueserves the page as a JSON API endpoint. Default content type isapplication/json; charset=utf-8. Combine withtt_page_varandquery_paramsfor dynamic JSON. content_type-
Overrides the HTTP Content-type header. Example:
content_type: text/html; charset=utf-8. layout-
Per-page layout override - names a layout under
lazysite/layouts/. Useful for previewing a staged layout on one page before activation. auth-
Authentication requirement.
required,optional, ornone(default). auth_groups- List of group names. User must be in one of them.
payment- Payment requirement for the x402 payment flow. See Payment.
query_params-
List of accepted URL query parameter names. Matched parameters are
available as
[% query.NAME %]and bypass the cache. tags- Tags for page scan results. YAML list, comma-separated, or single value.
search-
trueorfalse. Controls whether the page appears in search. Defaults to the site'ssearch_defaultsetting. form-
Enables form processing for the page and names the form. Requires a
matching
lazysite/forms/NAME.conf.
Markdown elements
Headings
# H1 is reserved - the page title is rendered by the view template.
Start content headings at ##.
Text, links, lists
Standard Markdown. Internal links should be extensionless:
Docs not Docs.
Tables
Standard GFM pipe tables are supported.
Code blocks
Fenced code blocks with language identifiers produce highlighted output:
```bash
curl https://example.com/
```
Inline code and fenced code blocks are protected from Template Toolkit
processing - [% tags %] inside code appear literally.
Fenced divs
Wrap content in a named CSS class:
::: classname
Content here. Standard Markdown works inside.
:::
Produces <div class="classname">...</div>. Class names must contain
only word characters and hyphens.
Common classes in the default theme:
widebox- full-width coloured bandtextbox- 60% width highlighted boxmarginbox- pull quote in the marginexamplebox- evidence or example highlight
oEmbed
::: oembed
https://www.youtube.com/watch?v=abc123
:::
Works with YouTube, Vimeo, SoundCloud, and any oEmbed provider.
Content includes
Inline local or remote content at render time:
::: include
partials/note.md
:::
.md files have their front matter stripped. Code files are wrapped in
syntax-highlighted code blocks. .html files are inserted bare.
Includes are single-pass - includes inside included files are not processed.
Use the multi-line fenced form above. A one-line ::: include path is left
as literal text, not expanded. Keep consecutive includes contiguous.
Embedded HTML
You can drop raw HTML into a page, but Markdown's block rules bite - both of these silently mangle it:
- HTML indented 4 or more spaces is treated as a code block. Keep embedded HTML flush to the left margin.
- HTML blocks separated by blank lines get wrapped in
<p>. Keep a run of HTML contiguous - no blank lines inside it.
When a fragment is awkward to keep flush and contiguous, move it into a .md
include partial and pull it in with ::: include; the partial's HTML is
inserted as-is. (This is also why an activation cache-clear that removes
generated <page>.html never touches your author partials - keep reusable
chrome in .md/.html partials.)
Template Toolkit in page content
TT variables are expanded in the page content before Markdown
conversion. Site variables come from lazysite.conf, page variables
from tt_page_var. Automatic variables (page_title, page_subtitle,
content) are set by the processor.
Current version: [% latest_release %]
[% IF beta %]
<div class="textbox">
This feature is in beta.
</div>
[% END %]
Inline code and fenced code blocks are protected from TT. Put TT tags outside code blocks if you want them to render.
Markdown link URLs do not reliably resolve TT variables (the Markdown
parser processes links before TT runs). Use HTML <a> tags when the
href contains a TT variable:
<a href="[% download_base %]/release-[% version %].tar.gz">Download</a>
URL structure
Page URLs derive from file paths, always without extension:
DOCROOT/index.md -> /
DOCROOT/about.md -> /about
DOCROOT/docs/install.md -> /docs/install
DOCROOT/docs/index.md -> /docs/
Always use extensionless URLs for internal links.
Remote pages
A .url file contains a single URL. The processor fetches the Markdown
from that URL, processes it through the full pipeline, and caches the
result.
# File: docs/install.url
https://raw.githubusercontent.com/example/repo/main/docs/INSTALL.md
The remote file should include YAML front matter. Cache TTL defaults to one hour.
Page scan
scan:/path/*.md returns an array of page metadata. Use in
lazysite.conf or tt_page_var:
tt_page_var:
blog_posts: scan:/blog/*.md sort=date desc
[% FOREACH post IN blog_posts %]
## [% post.title %]
[% post.subtitle %] - [% post.date %]
[% post.url %]
[% END %]
Each item has url, title, subtitle, date, and path.
Search
Pages are searchable by default. Set search: false in front matter to
exclude a page from search. The site default is controlled by
search_default in lazysite.conf.
Registries
Pages declare which registry files they appear in via register:.
Common registries: sitemap.xml, llms.txt, feed.rss, feed.atom.
Each name maps to a Template Toolkit template in
lazysite/templates/registries/. Registries regenerate after their
TTL expires (default 4 hours).
Tasks
Creating a new page
- Ask the user for: title, URL path, brief description.
- Create a file at
DOCROOT/PATH.md(e.g./docs/installbecomesDOCROOT/docs/install.md). - Write front matter with
title:and optionallysubtitle:. - Register in relevant feeds:
sitemap.xml,llms.txt, andfeed.rssorfeed.atomif it is a dated article. - Write the body in Markdown, starting headings at
##. - Write a
<file>.briefsidecar capturing the page's intent - purpose, sections, tone, image/content sources, and a "To change this pageā¦" line. The brief is the source of intent, the.mdis the build: when the owner edits the brief, refactor the page to match it. See Document your intent:.briefsidecars in AI briefing - publishing for the full structure and workflow.
Creating a blog/news index with scan
- Confirm blog posts live in a single directory, e.g.
/blog/. - Create
DOCROOT/blog/index.mdwithtt_page_var:
---
title: Blog
tt_page_var:
posts: scan:/blog/*.md sort=date desc
---
[% FOREACH p IN posts %]
## [[% p.title %]]([% p.url %])
[% p.subtitle %] - [% p.date %]
[% END %]
Creating a members-only page
- Confirm the required group name in
lazysite/auth/groups. - Write the page normally, then set:
---
title: Members area
auth: required
auth_groups:
- members
---
Protected pages are never cached.