You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Website: full Astro → templ conversion. templcomponents.lars.software is
now generated by a Go static-site generator (website/ module) that renders
every page through templ-components itself — layout.Base (SEO head,
canonical, JSON-LD, skip link, ThemeScript), display.Button, layout.ThemeToggle, the icons package, and errorpage.NotFound404 for
the 404 page. Docs moved from Starlight MDX to plain markdown rendered by
goldmark + chroma with a sidebar/TOC/prev-next layout, git-based
last-updated dates, and edit links. Better-than-before wins: hero counts
are derived from the codebase at build time (the old page claimed stale
94/102/37/9), fonts are self-hosted with preloads, highlighting CSS is
regenerated per build (dual github/github-dark), sitemaps carry git
lastmod, the newsletter popup no longer uses an inline onsubmit
handler, and CI builds the site with Go + the Tailwind CLI instead of
pnpm + Astro. Behavior parity preserved: same slugs, same localStorage
theme keys, same Buttondown form, mobile nav, scroll-reveal, and
motion-reduce fallbacks.
Added
wire.DecodeForm[T] — server-side counterpart to form-encoded wire
submissions. Decodes POST bodies and GET query parameters into any struct
via form:"name" tags (string, int, int64, bool; HTML checkbox "on"
handled), leaving absent fields at zero values and failing loudly on
malformed values (ErrUnsupportedFormField for other kinds). First
consumer: display.ParseKanbanMove, now a thin domain-validation layer on
top — its documented "form body or query parameters" contract is finally
true for GET too. Consumers writing their own wired handlers no longer
hand-roll ParseForm + PostForm.Get chains.
layout.Minimal gains optional SEO head tags — robots noindex,
canonical, hreflang alternates, and JSON-LD, with identical semantics to PageProps.SEO. Both shells now emit the tags through one shared
sub-template, so static/print documents (the Minimal use case) can opt
into structured data without giving up the zero-dependency default (the
zero value emits nothing). Unblocks the #156 adoption reason.
axe-core accessibility sweep over the live demo routes (visualtest):
the vendored axe-core 4.11.1 runtime (MPL-2.0) audits all 7 demo pages
(light + dark for index/forms) in real Chromium, with a positive-control
test proving the harness detects violations and a baseline ledger
(visualtest/testdata/axe_baseline.json) that fails CI on any new
critical/serious finding. Accepted palette-convention contrast debt is
documented in the baseline.
Demo click-through e2e suite (visualtest/demo_flows_e2e_test.go):
browser-level proof of five headline demo flows against the real demo
server — LoadMore through EndOfList, ConfirmDelete (native confirm
auto-accepted), LoadingButton busy gate, multipart upload echo, and kanban
move buttons on both htmx and Datastar boards. Every flow test bounds its
tab context (2 min) so a wedged renderer fails the test instead of hanging
the binary.
Demo route smoke test (visualtest/demo_smoke_test.go): all 7 demo
routes must render their unique page title with zero 500s in the server
log, so CI's existing Visual Regression job now fails when the demo a
consumer copies from is broken — previously nothing in CI exercised the
demo server at all.
Compiled-CSS target list is single-sourced in scripts/compiled-css-targets.txt: scripts/release.sh (recompile), scripts/check-css-minified.sh (minification), and utils.TestCompiledCSSInventory (tracked-set + existence guards) all read
the same <input> <output> list — previously the list lived in bash AND Go
and could drift independently. The minification guard now covers ALL three
compiled targets (previously only static/app.css), and CONTRIBUTING.md
states the rule: a committed artifact needs a named consumer.
Hook-adoption guard (Guard 0): .githooks/pre-commit now warns loudly
when git config core.hooksPath is unset or points elsewhere (a clone
running a stale .git/hooks/ copy), with the one-line fix
(scripts/setup-hooks.sh). README's contributing section names the step.
Changed
Compiled-CSS resurrection loop closed. The BuildFlow daemon re-added six
deleted .out.css artifacts within hours of the cleanup (504856b8 after 43f1522f) — utils.TestCompiledCSSInventory caught it immediately. .gitignore now ignores *.out.css repo-wide except the one tracked
distribution artifact, and the guard now asserts the git-tracked set
(fail-loud on missing git; untracked daemon litter in the worktree is
reported informationally), so daemon rebuilds can no longer re-stage zombie
artifacts or false-fail the guard.
Pre-commit hooks are now tracked in .githooks/pre-commit (fast guards
BuildFlow), activated per clone via scripts/setup-hooks.sh
(git config core.hooksPath .githooks). Previously the hook lived only in .git/hooks/ — untracked, silently absent for fresh clones. scripts/pre-commit.sh remains the full pre-push verify and is
intentionally not wired into the hook.
utils.TestDocsCountDrift now guards every prose count — icons
(README heading said 106 while the same README said 102), HTML golden
baselines (ROADMAP/FEATURES said 175; actual 242), FEATURES visual goldens
(114 → 125), and AGENTS golden files (102 → 242) are all machine-checked
against the tree. The informational TestSkillComponentCount ghost guard
(computed counts, compared nothing, skipped on missing fixture) is deleted; TestDocsCountDrift covers SKILL.md totals, per-package headings, and
icons for real. A new TestVersionMatchesReadmeBadge closes the
hand-edited version-badge drift gap.
Lint baseline is now literally zero across all 8 linted modules. The
seven pre-existing findings (gocognit ×5 in oversized test functions,
one noctx on exec.Command, one stale nolint:goconst directive) are
fixed by refactoring — table-driven constructors test, extracted scan
helpers, exec.CommandContext — so CI's "0 findings" claim is true again.
The two same-named templ-components-theme.css files now cross-reference
each other in their headers. The repo-root file (palette-override @theme example) and the templates/ file (ADR-0008 semantic-token
layer, release.sh compile input) are easy to confuse; each header now
states what it is, what it is NOT, and where the sibling lives.
Removed
Ten dead compiled-CSS artifacts deleted (~208 KB of recurring
daemon-recompile churn). The repo tracked compiled .out.css output for
four theme presets, the tc CLI starter kit, a root theme file, a
byte-identical duplicate of the demo stylesheet (demo.out.css), and a
website stylesheet (global.out.css) — none had any in-repo consumer: the
preset scaffolder (tc new) they were committed for never shipped, the
CLI only reads app.css/custom.css from its embedded starter, the demo
serves static/app.css, and the website compiles its own CSS via @tailwindcss/vite. The three legitimate compiled distribution targets
remain (examples/demo/static/app.css, templates/styles.css, templates/templ-components-theme.out.css), scripts/release.sh now
recompiles exactly those, and a new utils.TestCompiledCSSInventory guard
fails the build if any stray .out.css re-enters the tree.
Fixed
display.KanbanBoard no longer widens the page on phone-width viewports.
The board's fixed-width columns (~1200px intrinsic) propagated their
min-content width through any grid/flex ancestor that lacked min-w-0,
so on a 375px phone the whole document scrolled sideways instead of the
board scrolling internally. The board root now carries min-w-0 — found
by the new mobile sweep, not by a human.
Labeled form controls are now always associated with their labels. forms.Input, Textarea, Select, DatePicker, FileInput, Checkbox, Radio, RadioGroup options, Rating stars, Slider, TagsInput, and Combobox derive the control's DOM id from the field Name when no
explicit ID is set, so the rendered <label> always points at its control.
Previously, a labeled field rendered without an ID produced an unassociated
label (or an invalid for="") — a WCAG 1.3.1/4.1.2 failure that axe-core
reports as a critical label violation. FilterDropdown additionally gains
an accessible name fallback ("Filter") and TagsInput ("Add tag").
Form controls with role="progressbar"/role="img" are never unnamed. feedback.ProgressBar now emits an accessible name (AriaLabel → Label →
"Progress"), and display.BarChart/Heatmap fall back to "Bar chart" /
"Heatmap" for their role="img" wrappers when no AriaLabel is set —
fixing axe aria-progressbar-name/role-img-alt violations for consumers
who skipped the label props.
display.Tabs emits aria-controls only for tabs that own a panel.
Content-less tab links previously referenced non-existent panel ids (axe aria-valid-attr-value critical).
forms.Calendar uses role="group" instead of an invalid bare role="grid". Grid semantics require row structure the day-picker does
not have (axe aria-required-children/aria-required-parent).
display.Carousel's scroll-snap track is keyboard-focusable (tabindex
"Slides" label), satisfying axe scrollable-region-focusable for the
horizontally scrolling element itself.
display.StatCard trend colors meet WCAG contrast in light mode
(text-green-700/text-amber-700 instead of -600 shades that measured
3.2:1 on white).
charts/echarts.EChart renders role="img" when an AriaLabel is set — aria-label on a bare <div> is a prohibited attribute (axe aria-prohibited-attr).
Demo pages pass the axe sweep. Raw demo inputs/selects gained labels,
scrollable code blocks are focusable, and in-paragraph links are underlined.
Tooltip dismiss/re-show scripts no longer throw on document-targeted
events. The shared tooltip singleton's keydown/mouseenter/focusin
listeners called e.target.closest(...) unguarded; a mouseenter fired on document itself (every time the pointer enters the window on a page with
a tooltip) threw a console TypeError. All three now route through a
target guard.