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">
<p>This feature is in beta.</p>
</div>
[% END %]
Inline code and fenced code blocks are protected from TT. Put TT tags outside code blocks if you want them to render.
No randomness primitive. There is no random/shuffle helper in the template
context, so a "random quote" or "random image" cannot be computed per request.
Either pick one at author time, or render the set as a carousel/cycle in the
page or layout. For list-driven pages (reviews, a homepage highlight strip),
scan: + a FOREACH loop give you the data deterministically.
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.