htmlrender

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 6, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func ACAnchor

func ACAnchor(acID string) string

ACAnchor returns the in-page anchor for one AC inside the milestone page: e.g. ACAnchor("AC-3") == "ac-3". The CSS `:target` selector in step-5 templates uses this convention.

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

type BodySectionView struct {
	Slug    string
	Heading string
	Content string
}

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

type DependencyEdge struct {
	From string
	To   string
}

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

type FindingCounts struct {
	Errors   int
	Warnings int
}

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

type Result struct {
	FilesWritten int
	ElapsedMs    int64
}

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

func Render(opts Options) (Result, error)

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

type SidebarMilestone struct {
	ID        string
	Title     string
	FileName  string
	IsCurrent bool
}

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 struct {
	ID       string
	Title    string
	Status   string
	FileName string
}

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

type StatusFinding struct {
	Code     string
	EntityID string
	Path     string
	Message  string
}

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

type StatusHealth struct {
	Entities int
	Errors   int
	Warnings int
}

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

type TestMetricsView struct {
	Pass  int
	Fail  int
	Skip  int
	Total int
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL