feat(tmpl): template renderer + BaseData (bookshelf-qqz.7) #8

Merged
zombor merged 2 commits from bd-bookshelf-qqz.7 into main 2026-05-20 18:23:16 +00:00
Owner

Summary

  • Adds internal/tmpl package with Renderer (buffer-first html/template rendering), BaseData struct, and BuildBaseData helper
  • Adds templates/layouts/base.html (HTML5 skeleton with {{block "content" .}} slot) and templates/pages/home.html
  • Adds templates/templates.go with //go:embed FS declaration for the production binary
  • Exports middleware.NewContextWithTraceID so cross-package tests can inject a trace ID without running the full middleware chain
  • 100% statement coverage on internal/tmpl via Ginkgo v2 + fstest.MapFS

Test plan

  • make test passes (all internal packages green, race detector clean)
  • make coverage reports 100% on internal/
  • Layout-missing → error returned, zero bytes written to ResponseWriter
  • Page-missing → error returned, zero bytes written to ResponseWriter
  • Execution error (nil pointer in data) → error returned, zero bytes written, Content-Type not set
  • Happy path: DOCTYPE rendered, Content-Type set to text/html; charset=utf-8
  • BuildBaseData propagates TraceID from context (present and absent cases)

Closes bead bookshelf-qqz.7 on merge.

## Summary - Adds `internal/tmpl` package with `Renderer` (buffer-first `html/template` rendering), `BaseData` struct, and `BuildBaseData` helper - Adds `templates/layouts/base.html` (HTML5 skeleton with `{{block "content" .}}` slot) and `templates/pages/home.html` - Adds `templates/templates.go` with `//go:embed` FS declaration for the production binary - Exports `middleware.NewContextWithTraceID` so cross-package tests can inject a trace ID without running the full middleware chain - 100% statement coverage on `internal/tmpl` via Ginkgo v2 + `fstest.MapFS` ## Test plan - [ ] `make test` passes (all internal packages green, race detector clean) - [ ] `make coverage` reports 100% on `internal/` - [ ] Layout-missing → error returned, zero bytes written to ResponseWriter - [ ] Page-missing → error returned, zero bytes written to ResponseWriter - [ ] Execution error (nil pointer in data) → error returned, zero bytes written, Content-Type not set - [ ] Happy path: DOCTYPE rendered, Content-Type set to `text/html; charset=utf-8` - [ ] `BuildBaseData` propagates TraceID from context (present and absent cases) Closes bead bookshelf-qqz.7 on merge.
- internal/tmpl: NewRenderer(fs.FS), Render(w, r, layout, page, data) with
  buffer-first rendering so parse/execute errors never write partial bytes
- internal/tmpl: BaseData{AssetVersion, TraceID, Title} + BuildBaseData helper
  reading trace ID via middleware.TraceIDFromContext
- internal/middleware: export NewContextWithTraceID for use in cross-package tests
- templates/layouts/base.html: full HTML5 skeleton with {{block "content" .}} slot
- templates/pages/home.html: defines the content block with welcome message
- templates/templates.go: //go:embed FS declaration for production binary
- 100% test coverage on internal/tmpl (Ginkgo v2 + fstest.MapFS)
zombor force-pushed bd-bookshelf-qqz.7 from 1a7e062013 to a96a7b6aa1
Some checks failed
/ Test (pull_request) Failing after 2s
/ Coverage (pull_request) Has been skipped
/ Lint (pull_request) Successful in 1m11s
2026-05-20 17:51:44 +00:00
Compare
ci: drop fixed host port on MySQL service container
Some checks failed
/ Lint (pull_request) Successful in 1m2s
/ Test (pull_request) Successful in 1m32s
/ Coverage (pull_request) Failing after 1m2s
3fc1d87af0
Two concurrent PR runs both tried to bind 0.0.0.0:3307 and the second one
failed in "Set up job" with "Bind for 0.0.0.0:3307 failed: port is already
allocated". Drop the host port mapping and have the test step talk to the
service container by its network alias (mysql:3306) so each run gets its own
isolated MySQL on its own workflow network.

Discovered while shipping bookshelf-qqz.7 — same failure mode hit every open
PR (qqz.4, qqz.7, qqz.8) while the main-branch runs (which never run
concurrently with each other) stayed green.
ci(coverage): force fresh test runs via -count=1
All checks were successful
/ Lint (pull_request) Successful in 1m4s
/ Test (pull_request) Successful in 1m36s
/ Coverage (pull_request) Successful in 1m22s
491913343f
The Coverage job in CI ran `go test -coverprofile=... -coverpkg=./internal/...`
after the Test job in the same workflow had already populated the test-result
cache without `-coverpkg`. Several packages then returned cached results whose
profiles were missing cross-package coverage data, and the gate reported 95.9%
even though every internal package is fully covered.

-count=1 disables the test result cache for this invocation so the
coverprofile is always regenerated against the requested -coverpkg set.

Discovered while shipping bookshelf-qqz.7 — local `make coverage` consistently
passes at 100%, but the first PR run on a fixed-MySQL CI dropped to 95.9%
because of cache reuse. The build cache is not the problem; the test-result
cache is.
zombor force-pushed bd-bookshelf-qqz.7 from 491913343f
All checks were successful
/ Lint (pull_request) Successful in 1m4s
/ Test (pull_request) Successful in 1m36s
/ Coverage (pull_request) Successful in 1m22s
to e637ce1ec9
All checks were successful
/ Lint (pull_request) Successful in 53s
/ Test (pull_request) Successful in 1m7s
/ Coverage (pull_request) Successful in 52s
2026-05-20 18:20:47 +00:00
Compare
zombor merged commit 8c7da201ff into main 2026-05-20 18:23:16 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
zombor/pergamum!8
No description provided.