Documentation
¶
Overview ¶
Package htmlrender produces a static-site governance render of an aiwf planning tree: one index page, one page per epic, one page per milestone. Output is a directory of self-contained HTML files plus a single embedded stylesheet — no JS, no runtime, no external assets.
This package is the read-side renderer for I3 step 5's templates; step 3 (this file) lays the seams. Templates and CSS live under embedded/ and are pulled in via go:embed in embed.go. A minimal placeholder template ships now so callers can verify the directory layout, link integrity, and determinism (render twice → byte-identical output) before the real templates land in step 5.
The package is deterministic by construction:
- sorted iteration over the tree's entities (no map range);
- no wall-clock timestamps in output (all dates derive from the entity's commit metadata, captured by the caller);
- sorted directory enumeration where applicable.
See docs/pocv3/plans/governance-html-plan.md §8 "Determinism" for the load-bearing rules.
Index ¶
- func ACAnchor(acID string) string
- type ACDetail
- type BodySectionView
- type DependencyEdge
- type EntityData
- type EntityRef
- type EpicData
- type EpicSummary
- type FindingCounts
- type HistoryRow
- type IndexData
- type LinkedEntity
- type MilestoneData
- type MilestoneSummary
- type Options
- type PageDataResolver
- type PhaseEvent
- type ProvenanceData
- type Result
- type ScopeRow
- type SidebarData
- type SidebarEpic
- type SidebarMilestone
- type StatusData
- type StatusEntityLink
- type StatusEpicView
- type StatusFinding
- type StatusGapView
- type StatusHealth
- type StatusMilestoneView
- type TestMetricsView
- type TestsPolicy
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type ACDetail ¶
type ACDetail struct {
ID string
Title string
Status string
TDDPhase string
Description string
Phases []PhaseEvent
Tests *TestMetricsView
Anchor string // ac-N — pre-derived so templates don't call ACAnchor() per row
}
ACDetail is one AC's view inside the milestone Manifest tab. Description is the body prose under the `### AC-N — <title>` heading; Phases is the per-AC TDD timeline assembled from the AC's history (one row per phase transition); Tests is the latest test metrics for the AC (per-AC iterator authority).
type BodySectionView ¶
BodySectionView is the renderer-facing view of one body section. Heading is the original `## ` text; Slug is the kebab-friendly key (used as the section's CSS class); Content is the section prose (still markdown — G36 swaps in HTML rendering).
type DependencyEdge ¶
DependencyEdge captures a single `depends_on` edge between two milestones inside an epic. From and To are short ids (M-NNN).
type EntityData ¶
type EntityData struct {
Entity *EntityRef
Sections []BodySectionView
LinkedEntities []LinkedEntity
History []HistoryRow
Sidebar SidebarData
}
EntityData is the input to the shared entity template used for gap, ADR, decision, and contract pages. These four kinds have less structured rendering than epic/milestone — no AC tables, no dependency DAG, no scope FSM — so a single template walking each body section in source order covers them.
Sections preserves the order of `## `-level headings in the body markdown (per ParseBodySectionsOrdered) so the page reads as a recognizable rendering of the source file. LinkedEntities is the union of forward+reverse references, deduplicated and sorted, the same shape epic / milestone pages use.
type EntityRef ¶
type EntityRef struct {
ID string
Title string
Status string
Path string
FileName string
Kind string
TDD string
}
EntityRef is the minimal data the templates need about the page's own entity: id, title, status, plus `Path` (repo-relative file path) and the rendered file's name. Decouples templates from the internal entity.Entity struct so future schema changes don't rewrite templates.
type EpicData ¶
type EpicData struct {
Epic *EntityRef
Body map[string]string // section slug → prose, per entity.ParseBodySections
Milestones []MilestoneSummary
DependencyDAG []DependencyEdge
LinkedEntities []LinkedEntity
History []HistoryRow
ACMet int
ACTotal int
Sidebar SidebarData
}
EpicData is the input to the epic template. Milestones is sorted by id; LinkedEntities is the union of forward and reverse references (deduplicated, sorted), with each entry naming kind/id/title and the file the link should point at.
type EpicSummary ¶
type EpicSummary struct {
ID string
Title string
Status string
FileName string
MilestoneCount int
ACMet int
ACTotal int // total - cancelled
LastActivity string
}
EpicSummary is one row on the index page. ACMet is rolled up across every milestone under the epic; ACTotal is the sum of non-cancelled AC counts.
type FindingCounts ¶
FindingCounts feeds the "findings rollup" line on the index page. Errors and Warnings are the counts emitted by `aiwf check`; the renderer does not run check itself, the caller passes them in.
type HistoryRow ¶
type HistoryRow struct {
Date string
Commit string
Actor string
Principal string
OnBehalfOf string
Verb string
Detail string
To string
Force bool
ForceReason string
AuditOnly bool
AuditReason string
AuthorizedBy string
Scope string
ScopeEnds []string
Reason string
Tests *TestMetricsView
}
HistoryRow is one event in the Commits tab and in epic-page "Recent activity" sections. Verb is the trailer value; Detail is the commit subject; Force, AuditOnly, OnBehalfOf, Scope, ScopeEnds surface the I2.5 provenance shape; Tests is the parsed metrics when present.
type IndexData ¶
type IndexData struct {
Title string
Epics []EpicSummary
FindingCounts FindingCounts
LastActivity string // ISO date of most recent commit on aiwf-* trailer set; empty in pre-aiwf repos
Sidebar SidebarData
}
IndexData is the input to the index template. Epics is sorted by id; each row carries the AC met-rollup so the index can show the `met / (total - cancelled)` column without reaching back into the tree.
type LinkedEntity ¶
type LinkedEntity struct {
ID string
Title string
Status string
Kind string
FileName string
Direction string
}
LinkedEntity is one row in an epic / milestone "Linked entities" block. Direction is "forward" (this entity references Target) or "reverse" (Target references this entity).
type MilestoneData ¶
type MilestoneData struct {
Milestone *EntityRef
ParentEpic *EntityRef
Body map[string]string
ACs []ACDetail
Commits []HistoryRow
Provenance ProvenanceData
LinkedEntities []LinkedEntity
LinkedDecisions []LinkedEntity
TestsPolicy TestsPolicy
ACMet int
ACTotal int
Sidebar SidebarData
}
MilestoneData is the input to the milestone template. The six tabs (overview, manifest, build, tests, commits, provenance) read from this single struct; the templates branch internally on what to show in each tab.
LinkedDecisions / LinkedEntities are split so the Overview tab can render the decisions block conditionally (an empty list suppresses the heading entirely). LinkedEntities is the union shown on a separate "Linked entities" block when populated; LinkedDecisions is the kind-filtered subset surfaced in the Overview tab per I3 plan §3.3.
type MilestoneSummary ¶
type MilestoneSummary struct {
ID, Title, Status, FileName string
ACMet, ACTotal int
TDD string
LastActivity string
}
MilestoneSummary is one milestone row on an epic page.
type Options ¶
type Options struct {
OutDir string
Tree *tree.Tree
Root string
Scope string
Data PageDataResolver
}
Options is the input to Render. OutDir is the absolute directory the renderer writes into (callers who pass a relative path should resolve it first). Tree is the loaded planning tree; Root is the repo root used for any per-entity body reads. Scope, when non-empty, limits the render to one entity id and its referenced children (step-4 plumbing — step 3 ignores it but the field is reserved so the seam is in place).
Data is a per-page resolver supplied by the caller. The renderer calls Data.IndexData() once, Data.EpicData(id) for each epic, and Data.MilestoneData(id) for each milestone. Returning nil for any of these triggers the empty-state path in the template — the page still renders, with a "no data" line.
Splitting the data resolution out keeps htmlrender free of git / history walking. The cmd/aiwf side (which already has those helpers wired for `aiwf show`) builds the page data once per id and hands it in.
type PageDataResolver ¶
type PageDataResolver interface {
IndexData() (*IndexData, error)
EpicData(id string) (*EpicData, error)
MilestoneData(id string) (*MilestoneData, error)
// EntityData returns the page payload for the four kinds with
// no specialized template (gap, ADR, decision, contract). The
// renderer routes those kinds through a shared entity template;
// epic and milestone pages keep their dedicated resolvers above.
// A nil return (with no error) skips the entity's page —
// resolvers should only do this on a kind mismatch, otherwise
// every linked entity is expected to have a page.
EntityData(id string) (*EntityData, error)
// StatusData returns the project-status page payload. A nil
// return (with no error) means "skip the status page" — the
// renderer will not emit status.html and the sidebar will
// suppress the link. The default resolver returns nil so the
// htmlrender package's own tests don't need git access.
StatusData() (*StatusData, error)
}
PageDataResolver is the per-page data provider Render consults. Implementations build the typed view models from whatever sources they need (frontmatter, git log, scope FSM); the renderer walks the tree, calls the resolver per entity, and applies templates.
A nil Resolver triggers a minimal default that returns just the frontmatter shape — useful for the htmlrender package's own tests and for any caller who only wants id/title/status without history.
type PhaseEvent ¶
type PhaseEvent struct {
Date string
Phase string
Forced bool
Reason string
Tests *TestMetricsView
}
PhaseEvent is one TDD-phase transition for an AC. Date is ISO (YYYY-MM-DD); Phase is the to-state ("red"/"green"/"refactor"/ "done"); Forced when this transition was --force; Tests when the commit carried an aiwf-tests trailer.
type ProvenanceData ¶
type ProvenanceData struct {
Scopes []ScopeRow
Timeline []HistoryRow
}
ProvenanceData is the milestone Provenance tab payload — scopes table on top, full event timeline below. The renderer groups the timeline by scope at template time.
type Result ¶
Result reports what the render produced. FilesWritten is the count of HTML files emitted (excluding the stylesheet, which always writes once); ElapsedMs is wall-clock time for the render. Both surface in the verb's JSON envelope per I3 step 4.
func Render ¶
Render produces the static-site output under opts.OutDir. The directory is created if it does not exist and existing files at the same paths are overwritten.
The renderer is a pure function of opts.Tree (and the per-entity body files it reads). Two calls with identical inputs produce byte-identical files — the determinism test in step 4 pins this.
type ScopeRow ¶
type ScopeRow struct {
AuthSHA string // 8-char short form for table display
FullSHA string // full SHA for the show_authorization-style toggle
Agent string
Principal string
Opened string // YYYY-MM-DD
EndedAt string // YYYY-MM-DD; empty when state != ended
State string // active|paused|ended
EventCount int
}
ScopeRow is one row in the Provenance tab's scopes table.
type SidebarData ¶
type SidebarData struct {
Epics []SidebarEpic
HasStatus bool
IsCurrentStatus bool
IsCurrentIndex bool
}
SidebarData is the left-nav payload every page receives. Epics is sorted by id; each carries its child milestones (also sorted) so the renderer can emit a <details> per epic with milestones nested inside. The current page's ancestors carry IsActive=true so the template emits <details open> on the right epic and aria- current="page" on the right link.
HasStatus controls whether the "Project status" link appears in the top section. IsCurrentStatus marks that link active when the status page is the one being rendered. IsCurrentIndex marks the "Overview" link active when the index page is the one being rendered (the sidebar template otherwise has no way to know which page hosts it, since the index has no entity id).
type SidebarEpic ¶
type SidebarEpic struct {
ID string
Title string
FileName string
IsActive bool
IsCurrent bool // true on the epic page itself (drives aria-current)
Milestones []SidebarMilestone
}
SidebarEpic is one epic row in the sidebar. IsActive is true when this epic is the page's own (epic page) or its parent (milestone page).
type SidebarMilestone ¶
SidebarMilestone is one milestone row inside a SidebarEpic. IsCurrent is true when this milestone is the page being rendered.
type StatusData ¶
type StatusData struct {
Sidebar SidebarData
GeneratedAt string
Health StatusHealth
InFlightEpics []StatusEpicView
OpenDecisions []StatusEntityLink
OpenGaps []StatusGapView
Warnings []StatusFinding
RecentActivity []HistoryRow
}
StatusData is the input to the status-page template. Mirrors the shape of `aiwf status` (the cmd-side statusReport struct) but projects to renderer-facing types so the template stays free of internal cmd packages. GeneratedAt is the UTC date the report was built; Health rolls up the project-wide entity / error / warning counts.
type StatusEntityLink ¶
StatusEntityLink is a minimal link to another entity (decision / gap / contract) listed on the status page. No more fields than the page actually displays.
type StatusEpicView ¶
type StatusEpicView struct {
ID string
Title string
Status string
FileName string
Milestones []StatusMilestoneView
}
StatusEpicView is one in-flight epic block on the status page, with its in-progress milestones nested. Same shape as the SidebarEpic but carries milestone status / TDD / AC counts so the page can render a richer block.
type StatusFinding ¶
StatusFinding is a warning surfaced inline on the status page. Mirrors check.Finding's load-bearing fields.
type StatusGapView ¶
type StatusGapView struct {
ID string
Title string
Status string
FileName string
DiscoveredIn string // optional milestone id
}
StatusGapView is one open gap in the status report. Severity is optional (gaps have no required severity field today; we surface the title alone if severity is empty).
type StatusHealth ¶
StatusHealth rolls up counts surfaced in the report header.
type StatusMilestoneView ¶
type StatusMilestoneView struct {
ID string
Title string
Status string
FileName string
TDD string
ACMet int
ACTotal int
OpenACs int
}
StatusMilestoneView is one milestone row on the status page. ACMet / ACTotal are the same met/(total - cancelled) shape used throughout; OpenACs is the count of ACs still in `open` status, surfaced so the page can show "M/T met (N open)".
type TestMetricsView ¶
TestMetricsView is the renderer-facing view of a single aiwf-tests trailer. Total is computed by the caller (caller knows whether the on-wire trailer recorded total= explicitly).
type TestsPolicy ¶
type TestsPolicy struct {
Strict bool
}
TestsPolicy controls the milestone Tests tab's `strict` / `advisory` badge. Strict is true when aiwf.yaml.tdd. require_test_metrics is true.