WIP Phase 2: content loader, stage contract, search index, templ components

This commit is contained in:
2026-10-26 20:01:38 +11:00
parent b267e55f8c
commit ea2c722816
13 changed files with 2368 additions and 0 deletions

View File

@@ -0,0 +1,258 @@
package templates
import (
"strconv"
"github.com/a-h/templ"
)
// HomePage is the landing page: the 90-second read.
templ HomePage(v HomeView) {
@Layout(v.Layout) {
<section class="hero">
<p class="hero-kicker">Melbourne · twenty-five years · AI · platform · edge</p>
<h1 class="hero-title">{ v.Positioning }</h1>
<p class="hero-support">{ v.Support }</p>
<p class="hero-facts">
<span>{ strconv.Itoa(v.Areas) } areas</span>
<span>{ strconv.Itoa(v.Pages) } pages</span>
<span>{ strconv.Itoa(v.Artifacts) } linked artefacts</span>
<span>no page needs a model to render</span>
</p>
@SearchForm("", "hero-search")
</section>
<section class="pillars" aria-label="Six areas">
for _, c := range v.Cards {
@PillarCard(c)
}
</section>
<section class="honesty" aria-labelledby="how-to-read">
<h2 id="how-to-read">How to read this site</h2>
<ul>
<li><strong>Level 1</strong> is one screen per area: the claim, the numbers, the way in.</li>
<li><strong>Level 2</strong> is what is actually run, with an evidence table and the trade-offs.</li>
<li><strong>Level 3</strong> is the artefact: code, diagram, screenshot, log.</li>
<li>Every claim carries a <strong>status</strong>, and finished, running, in-progress and abandoned are all shown as they are.</li>
<li>Every artefact is at most three clicks from here, and the <a href="/evidence/">evidence index</a> lists all of them on one page.</li>
</ul>
</section>
}
}
// DocPage renders an L1, L2 or L3 document.
templ DocPage(v DocView) {
@Layout(v.Layout) {
<article class="doc">
<header class="doc-head">
<p class="doc-kicker">{ v.AreaTitle } · { v.LevelLabel }</p>
<h1 class="doc-title">{ v.Doc.Title }</h1>
<p class="doc-claim">{ v.Doc.Claim }</p>
<div class="doc-chips">
<span class={ ConfidenceClass(v.Doc.Confidence) }>{ v.Doc.Confidence }</span>
if v.Doc.Period != "" {
<span class="chip chip-plain">{ v.Doc.Period }</span>
}
for _, m := range v.Metrics {
<li class="chip">
<strong>{ m.Value }</strong>
<span>{ m.Label }</span>
</li>
}
</div>
if len(v.Tech) > 0 {
<ul class="tech" aria-label="Technology">
for _, t := range v.Tech {
<li>{ t }</li>
}
</ul>
}
</header>
<div class="prose">
@v.Body
</div>
if len(v.Evidence) > 0 {
<section class="doc-evidence" aria-labelledby="evidence-heading">
<h2 id="evidence-heading">Evidence for this page</h2>
<p class="section-note">Select a row to load the artefact into the presentation panel. Each row is also a link, so this works without JavaScript.</p>
@EvidenceTable(evidenceRows(v.Evidence))
</section>
}
if len(v.Timeline) > 0 {
<section class="doc-timeline" aria-labelledby="timeline-heading">
<h2 id="timeline-heading">In time order</h2>
@Timeline(v.Timeline)
</section>
}
if len(v.SubPages) > 0 {
<section class="doc-sub" aria-labelledby="sub-heading">
<h2 id="sub-heading">{ subHeading(v.Doc.Level) }</h2>
<ul class="drill-list">
for _, s := range v.SubPages {
<li>
<a href={ templ.SafeURL(s.Href) }>{ s.Label }</a>
<span class="drill-kind">{ s.Kind }</span>
</li>
}
</ul>
</section>
}
if len(v.Backlinks) > 0 {
<section class="doc-back" aria-labelledby="back-heading">
<h2 id="back-heading">Referenced from</h2>
<ul class="drill-list">
for _, b := range v.Backlinks {
<li><a href={ templ.SafeURL(b.Href) }>{ b.Label }</a></li>
}
</ul>
</section>
}
<footer class="doc-foot">
<p>Status <span class={ ConfidenceClass(v.Doc.Confidence) }>{ v.Doc.Confidence }</span> — { ConfidenceNote(v.Doc.Confidence) }</p>
<p>Last verified { v.Doc.LastVerified } · id <code>{ v.Doc.ID }</code> · { v.Doc.Path }</p>
</footer>
</article>
}
}
// EvidencePage is one page listing every artefact, grouped by area. It is the
// shortest path from the landing page to any proof.
templ EvidencePage(v EvidenceView) {
@Layout(v.Layout) {
<article class="doc">
<header class="doc-head">
<p class="doc-kicker">Index</p>
<h1>Evidence index</h1>
<p class="doc-claim">Every artefact this site claims, on one page. { strconv.Itoa(v.Total) } links, each one a file in the repository.</p>
</header>
for _, g := range v.Groups {
<section class="evidence-group">
<h2><a href={ templ.SafeURL(g.Href) }>{ g.AreaTitle }</a></h2>
@EvidenceTable(g.Rows)
</section>
}
</article>
}
}
// SearchPage is the keyword search. The results block is swapped by HTMX on the
// same page; with JavaScript off the form simply reloads the page.
templ SearchPage(v SearchView) {
@Layout(v.Layout) {
<article class="doc">
<header class="doc-head">
<p class="doc-kicker">Keyword search</p>
<h1>Search { strconv.Itoa(v.Indexed) } pages</h1>
<p class="doc-claim">Full-text search over page titles and body text. Served from a local index — no model, no network call.</p>
</header>
@SearchForm(v.Query, "page-search")
@SearchResults(v)
</article>
}
}
// SearchResults is swapped into #search-results by HTMX.
templ SearchResults(v SearchView) {
<div id="search-results" aria-live="polite">
if v.Query == "" {
<p class="section-note">Try “zigbee”, “signage”, “routing”, “backups” or “guardrails”.</p>
} else if v.Failed {
<p class="search-empty">That query could not be run. Try fewer or simpler words.</p>
} else if len(v.Results) == 0 {
<p class="search-empty">No page matches <q>{ v.Query }</q>. The <a href="/evidence/">evidence index</a> lists everything by area.</p>
} else {
<p class="search-count">{ strconv.Itoa(len(v.Results)) } results for <q>{ v.Query }</q></p>
<ol class="results">
for _, r := range v.Results {
<li class="result">
<h2><a href={ templ.SafeURL(r.URL) }>{ r.Title }</a></h2>
<p class="result-meta">
<span class="kind">{ r.Area }</span>
<span class="kind">L{ strconv.Itoa(r.Level) }</span>
<span class={ ConfidenceClass(r.Confidence) }>{ r.Confidence }</span>
</p>
<p class="result-snippet">{ SafeSnippet(r.Snippet) }</p>
</li>
}
</ol>
}
</div>
}
// StreamPage proves the panel can be re-pointed at a stream. The layout and the
// panel markup are the ones every other page uses; only the binding differs.
templ StreamPage(v StreamView) {
@Layout(v.Layout) {
<article class="doc">
<header class="doc-head">
<p class="doc-kicker">Design note · not a product feature</p>
<h1>The panel takes content from a driver</h1>
<p class="doc-claim">This page renders the same layout and the same panel as every other page. Its panel is bound to an event stream instead of pre-rendered files, and no layout code changed.</p>
</header>
<div class="prose">
<p>The presentation panel is a content sink. In Mode A it takes pre-rendered fragments over
HTMX: you click a row in an evidence table and one file is swapped in. On this page the same
panel is bound to <code>/api/stage/stream</code> and receives { strconv.Itoa(v.Frames) } fragments
over server-sent events, one after another.</p>
<p>Fragments are pushed, not requested. Nothing about the panel — its markup, its fragment
view, its empty state — knows the difference. That is the point: the Phase 5 console
re-points this panel at retrieval output and the layout does not change.</p>
<p class="section-note">This demonstration streams the artefacts already built for the site, with
no model in the loop. It exists to prove the transport, not to pretend an AI is answering.</p>
</div>
if len(v.Fragment) > 0 {
<section class="doc-sub">
<h2>Fragments in this stream</h2>
<ul class="drill-list">
for _, f := range v.Fragment {
<li>
<a href={ templ.SafeURL(f.Href) } hx-get={ f.HXGet } hx-target="#stage-content" hx-swap="innerHTML">{ f.Label }</a>
<span class="drill-kind">{ f.Kind }</span>
</li>
}
</ul>
</section>
}
</article>
}
}
// NotFoundPage is the 404.
templ NotFoundPage(v NotFoundView) {
@Layout(v.Layout) {
<article class="doc">
<header class="doc-head">
<p class="doc-kicker">404</p>
<h1>That page is not here</h1>
<p class="doc-claim">The link may be from an older build. The six areas and the evidence index are the reliable ways in.</p>
</header>
<ul class="drill-list">
<li><a href="/">Landing page</a></li>
<li><a href="/evidence/">Evidence index</a></li>
<li><a href="/search">Search</a></li>
</ul>
</article>
}
}
func evidenceRows(links []LinkView) []EvidenceRow {
rows := make([]EvidenceRow, 0, len(links))
for _, l := range links {
rows = append(rows, EvidenceRow{Label: l.Label, Href: l.Href, HXGet: l.HXGet, Kind: l.Kind, Confidence: l.Confidence})
}
return rows
}
func subHeading(level int) string {
if level == 1 {
return "Drill-downs in this area"
}
return "Artefacts behind this page"
}