Project Pages (publishing conventions)
This document summarizes the project-pages publishing conventions that a project started from this skeleton adopts. The canonical, authoritative description lives in the homelab project: documents/09-project-pages-conventions.md. This document is the skeleton’s summary of it and points back to the canonical source.
Why pages exist
A project’s published pages are its read interface: status, live state, work plan (TODO), rules (prompts), and knowledge (documents). A human or LLM can understand the project — and decide how to work with it — without escaping the project’s sandbox.
The standard page set
A project publishes a directory tree served at /projects/<id>/, with a consistent relative nav. Every page is required:
| Page | Content |
|---|---|
index.html |
Status: what the project is, its current status, how it works. |
dashboard.html |
Dashboard: live state (point-in-time values from the owning host). |
todo.html |
Condensed TODO: open work items. |
prompts.html |
Prompts index: the entire prompts/ tree, grouped by directory, linking to full-text pages. |
documents.html |
Documents index: every file in documents/, linking to full-text pages. |
records.html |
Records index: every file in records/, linking to full-text pages. |
Rules:
- The generated pages (
todo.html,prompts.html,documents.html,records.html) are generated at build time from the project’s own files — never hand-maintained. The generator isscripts/site-condense.sh, wired intomake sitealongsidescripts/site-build.sh. - Every
.mdfile underprompts/(the entire tree),documents/, andrecords/is published as its own full-text page (e.g.prompts-common-02-universal-rules.html), linked from the index pages. Markdown is rendered with pandoc when available (minimal fallback otherwise). todo.htmlstays condensed: open items with their continuation text, completed items summarized as a count.- Relative links only, so the pages work at any depth under
/projects/<id>/. - The homelab owns the one navigation bar. The template marks where it belongs with
<!-- HOMELAB-NAV -->and must not contain a<nav>of its own; the homelab injects the bar (graphic, breadcrumb, and the project’s page links) at publish time, and removes any project-authored<nav>. Projects never hardcode the labs site URL. - The page set is declared in
site.in/pages.nav(optional), an orderedhref|Labellist that must contain the standard set;make sitewritessite.out/pages.txtfor the homelab (the standard set when there is no file). Extra pages are listed there and appear in the one nav. - Pages are self-contained (their own
<style>); the reference template issite.in/template.html. - The
<title>of every page must be a real title, never the bare file name. - Recommended: a footer line linking back to
../and naming the project. - Generated output (
site.out/) is gitignored; onlysite.in/is authored.
Registration and publishing (homelab model)
- The project registers once in the homelab project’s
sources/projects.yaml(fields:id,title,summary,status,visibility,self_published: true,pages_source). - Activity vs phase (owner 2026-08-26): the registry
status(active/planned/deferred/complete) is the human-declared activity, in the single common homelab. The project’s phase belongs to this project: declare it inPHASES.md(Current: phase N — state), change it only when committing the project, and letscripts/site-condense.shemitsite.out/phase.txtso the homelab can show “active · phase N started” on the projects page. pages_sourcetells the homelab how to read the project’s generated tree: a path (site.out/, local or SSHFS) or an ssh command that streams the tree.- The homelab project is the only publisher. It fetches every registered
pages_source, runs the leak gate, and publishes with its own deploy. Projects do not push to the web server themselves — a project’smake deployis retired and only reminds you of this. - Live-state freshness: the project refreshes its own state (
make state, run on the owning host for hardware/network values) before the homelab publishes.
Sanitization (non-negotiable)
Published pages must contain no MAC addresses, private LAN IPs, usernames, home paths, or private-key material. The exact gate patterns live in the homelab (scripts/labs-deploy.sh, tests/03-labs-site.sh, tests/06-project-pages.sh); the homelab re-runs the gate at publish time.
Conformance checklist
- Read the canonical conventions doc in the homelab.
- Generate the standard page set per the rules above (the skeleton’s
site.in/+scripts/site-build.sh+scripts/site-condense.sh+make siteproduce it). - Register once in the homelab
sources/projects.yamlwith a readablepages_source. - Confirm the leak gate passes over the generated output.
- After a homelab deploy, verify
curl https://labs.bannister.us/projects/<id>/returns 200 and the content is sanitized.
How this project does it
site.in/index.txt— the status page, written for this project rather than left as the skeleton placeholder.site.in/dashboard.txt— the live state, as__KEY__placeholders.site.in/pages.nav— the page set, with AppImages as an extra page.scripts/site-state-fetch.sh(make state) — writesdataflow.out/site-state.txt:FETCHED,HOST,VERSION,RELEASE,INTEGRATED_COUNT,MANAGED_DIR,HANDLER,AUDIT_ERRORS,AUDIT_WARNINGS. Values are sanitized of home paths (the publishing gate refuses them) and HTML-escaped, because they land in HTML.scripts/site-build.sh— substitutes__KEY__from that file; a key with no value becomesunavailable, so the pages build on a host where the tool is not installed.scripts/site-condense.sh— additionally generatesappimages.html, the live inventory of what this tool has integrated on the owning host, read from the tool at build time so it cannot drift from the desktop. It also reads the phase fromPHASES.md(the state is the last field of theCurrent:line, so the line can carry a description as well).
Live example
amd-mi25-fan-service(SSHFS at~/remote/beast.lan/work/...) andmodel-elevation-earth(~/work/01-model-elevation-earth/) are live atlabs.bannister.us/projects/<id>/using exactly this pattern.