One Go binary serves a pre-rendered site. No Node, no bundler, no node_modules:
htmx and the SSE extension are vendored files, Markdown and code highlighting
run at build time (goldmark + chroma), and the server only writes files.
Implements docs/phase-2-static-site.md:
2.1 service skeleton cmd/build (content -> dist), cmd/server (dist -> HTTP),
env-configured, 127.0.0.1:8096, /healthz with separate
static / ai / search readiness, --health-check probe,
deploy/compose.yml + deploy/Dockerfile at 120m
2.2 rendering templ components (Layout, Nav, PillarCard, Stage,
EvidenceTable, CodeBlock, Timeline, Footer);
`make generate` is a build step and is byte-stable
2.3 navigation six-area rail, <details> disclosure under 900px,
breadcrumbs, aria-current, L1/L2/L3 with artefact pages
2.4 stage panel internal/stage owns the contract; RouteDriver (fragment
swap) and SSEDriver (event stream) differ only in the
Binding, never in layout. fragment ids live in the
contract so route files, links and streamed frames agree
2.5 search SQLite FTS5 over titles and body, rebuilt by the build;
one handler returns a page without HTMX, a fragment with
it. Visitors' FTS operators are never honoured
2.6 quality floor print stylesheet, JS-off navigation, focus states,
semantic landmarks, dark default; security headers
Definition of Done evidence (see issue AUD-142):
A1 all pages 200 in a network namespace with no external route
one binary serves the site; make dist writes 45 pages, 86 fragments, 40 docs
templ generate is deterministic (md5 identical across runs)
6 L1 + 6 L2 pages, artefact pages reachable in <= 3 clicks
FTS5 returns sensible hits for zigbee / signage / routing (tested)
Lighthouse 100 performance and 100 accessibility, mobile and desktop
first byte well inside 100ms over loopback (test)
panel renders from a route driver and re-points at the stream untouched
print legible: /edge/ renders to a 3-page PDF with the print stylesheet
Also fixes found while finishing the build:
- reject "." / ".." path segments before ServeMux redirects them into a file
outside the asset directory (/static/../build.json now 404, was 307->200)
- make test is the real gate: go test ./... plus the validator self-test
230 lines
5.5 KiB
Go
230 lines
5.5 KiB
Go
// Package templates holds every templ component and the view models they read.
|
|
// Layout never branches on the stage driver: it renders the binding it is given.
|
|
//
|
|
// The generated *_templ.go files are not committed; `make generate` writes them
|
|
// from the repository root before every build. There is deliberately no
|
|
// //go:generate directive: templ records the .templ path in its error messages,
|
|
// so generating from a package directory and generating from the root produce
|
|
// different files. One entry point keeps the output byte-identical.
|
|
package templates
|
|
|
|
import (
|
|
stdhtml "html"
|
|
"strings"
|
|
|
|
"github.com/a-h/templ"
|
|
|
|
"gitea.lab.audasmedia.com.au/sam/ai-resume/internal/content"
|
|
"gitea.lab.audasmedia.com.au/sam/ai-resume/internal/search"
|
|
"gitea.lab.audasmedia.com.au/sam/ai-resume/internal/stage"
|
|
)
|
|
|
|
// NavItem is one left-nav entry.
|
|
type NavItem struct {
|
|
Title string
|
|
Href string
|
|
Gutter string
|
|
Current bool
|
|
}
|
|
|
|
// Crumb is one breadcrumb.
|
|
type Crumb struct {
|
|
Label string
|
|
Href string
|
|
}
|
|
|
|
// LayoutView is everything the chrome needs.
|
|
type LayoutView struct {
|
|
Title string
|
|
Description string
|
|
Area string
|
|
Nav []NavItem
|
|
Crumbs []Crumb
|
|
Stage stage.Binding
|
|
SearchQuery string
|
|
Year int
|
|
SkipAI bool // true on Mode A pages, which never need a model
|
|
}
|
|
|
|
// StageView wraps the binding so the Stage component has one argument.
|
|
type StageView struct {
|
|
Binding stage.Binding
|
|
// Fragment, when set, is rendered into the panel at build time.
|
|
Fragment *stage.Fragment
|
|
}
|
|
|
|
// MetricView is one approved chip.
|
|
type MetricView struct {
|
|
Label string
|
|
Value string
|
|
}
|
|
|
|
// LinkView is a drill-down link. HXGet makes it a stage trigger; Href keeps it
|
|
// a working link when JavaScript is off.
|
|
type LinkView struct {
|
|
Label string
|
|
Href string
|
|
HXGet string
|
|
Kind string
|
|
Note string
|
|
Confidence string
|
|
}
|
|
|
|
// EvidenceRow is one row of an EvidenceTable.
|
|
type EvidenceRow struct {
|
|
Label string
|
|
Href string
|
|
HXGet string
|
|
Kind string
|
|
Confidence string
|
|
}
|
|
|
|
// EvidenceGroup groups rows by area on the evidence index.
|
|
type EvidenceGroup struct {
|
|
AreaTitle string
|
|
Href string
|
|
Rows []EvidenceRow
|
|
}
|
|
|
|
// TimelineItem is one row of a timeline.
|
|
type TimelineItem struct {
|
|
Period string
|
|
Title string
|
|
Href string
|
|
Confidence string
|
|
Claim string
|
|
}
|
|
|
|
// PillarCardView is one of the six landing cards.
|
|
type PillarCardView struct {
|
|
Area content.Area
|
|
Title string
|
|
Claim string
|
|
Href string
|
|
Metrics []MetricView
|
|
Tech []string
|
|
Confidence string
|
|
Level int
|
|
Pages int
|
|
}
|
|
|
|
// Layout of each page kind.
|
|
type (
|
|
// HomeView is the landing page.
|
|
HomeView struct {
|
|
Layout LayoutView
|
|
Positioning string
|
|
Support string
|
|
Location string
|
|
Cards []PillarCardView
|
|
Artifacts int
|
|
Pages int
|
|
Areas int
|
|
}
|
|
|
|
// DocView is an L1, L2 or L3 content page.
|
|
DocView struct {
|
|
Layout LayoutView
|
|
Doc *content.Document
|
|
AreaTitle string
|
|
Role string
|
|
Body templ.Component
|
|
Metrics []MetricView
|
|
Tech []string
|
|
Evidence []LinkView
|
|
Backlinks []LinkView
|
|
SubPages []LinkView
|
|
Timeline []TimelineItem
|
|
LevelLabel string
|
|
}
|
|
|
|
// EvidenceView is the index of every artefact.
|
|
EvidenceView struct {
|
|
Layout LayoutView
|
|
Groups []EvidenceGroup
|
|
Total int
|
|
}
|
|
|
|
// SearchView is the keyword search page.
|
|
SearchView struct {
|
|
Layout LayoutView
|
|
Query string
|
|
Results []search.Result
|
|
Total int
|
|
Indexed int
|
|
Failed bool
|
|
}
|
|
|
|
// StreamView is the SSE-driven stage demonstration page.
|
|
StreamView struct {
|
|
Layout LayoutView
|
|
Frames int
|
|
Fragment []LinkView
|
|
}
|
|
|
|
// NotFoundView is the 404 page.
|
|
NotFoundView struct {
|
|
Layout LayoutView
|
|
}
|
|
)
|
|
|
|
// MarkdownBody wraps pre-rendered Markdown HTML.
|
|
func MarkdownBody(html string) templ.Component { return templ.Raw(html) }
|
|
|
|
// SafeSnippet escapes a search snippet but keeps the <mark> tags the index
|
|
// inserted. Content is in-repo and trusted; escaping is defence in depth.
|
|
func SafeSnippet(s string) templ.Component {
|
|
s = stdhtml.EscapeString(s)
|
|
s = strings.ReplaceAll(s, "<mark>", "<mark>")
|
|
s = strings.ReplaceAll(s, "</mark>", "</mark>")
|
|
return templ.Raw(s)
|
|
}
|
|
|
|
// ConfidenceClass maps a confidence value onto a CSS class. The value itself is
|
|
// always displayed — never softened.
|
|
func ConfidenceClass(c string) string {
|
|
switch c {
|
|
case content.ConfidenceDelivered:
|
|
return "conf conf-delivered"
|
|
case content.ConfidenceRunning:
|
|
return "conf conf-running"
|
|
case content.ConfidenceInProgress:
|
|
return "conf conf-in-progress"
|
|
case content.ConfidenceAbandoned:
|
|
return "conf conf-abandoned"
|
|
case content.ConfidenceHistoric:
|
|
return "conf conf-historic"
|
|
}
|
|
return "conf"
|
|
}
|
|
|
|
// ConfidenceNote is the sentence shown under a status badge.
|
|
func ConfidenceNote(c string) string {
|
|
switch c {
|
|
case content.ConfidenceDelivered:
|
|
return "Delivered."
|
|
case content.ConfidenceRunning:
|
|
return "Running today."
|
|
case content.ConfidenceInProgress:
|
|
return "In progress — not finished."
|
|
case content.ConfidenceAbandoned:
|
|
return "Abandoned. Kept in the record deliberately."
|
|
case content.ConfidenceHistoric:
|
|
return "Historic — worked on, not maintained today."
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// LevelLabel names a depth level for the page kicker.
|
|
func LevelLabel(level int) string {
|
|
switch level {
|
|
case 1:
|
|
return "Level 1 · capability in one screen"
|
|
case 2:
|
|
return "Level 2 · what is actually run"
|
|
case 3:
|
|
return "Level 3 · artefact"
|
|
}
|
|
return ""
|
|
}
|