fix(dx): update CLAUDE.md and improve test coverage
All checks were successful
Security Scan / security (pull_request) Successful in 8s
Test / test (pull_request) Successful in 48s

- Fix WASM size gate: 3 MB → 3.5 MB to match size_test.go
- Fix dependencies: remove stale replace directive, add go-io and go-log
- Add error handling and I/O conventions to coding standards
- Add tests: reader error (cmd/codegen), duplicate tag dedup, bundle
  error propagation (codegen) — coverage: codegen 89→96%, cmd/codegen
  71→78%

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-17 08:27:58 +00:00
parent 44b3f77806
commit 023c8db79d
3 changed files with 41 additions and 3 deletions

View file

@ -27,7 +27,7 @@ See `docs/architecture.md` for full detail. Summary:
- **Responsive**: Multi-variant breakpoint wrapper (`data-variant` attributes), renders all variants in insertion order
- **Pipeline**: Render → StripTags → go-i18n/reversal Tokenise → GrammarImprint (server-side only)
- **Codegen**: Web Component classes with closed Shadow DOM, generated at build time by `cmd/codegen/`
- **WASM**: `cmd/wasm/` exports `renderToString()` only — size gate: < 3 MB raw, < 1 MB gzip
- **WASM**: `cmd/wasm/` exports `renderToString()` only — size gate: < 3.5 MB raw, < 1 MB gzip
## Server/Client Split
@ -40,9 +40,10 @@ Files guarded with `//go:build !js` are excluded from WASM:
## Dependencies
- `forge.lthn.ai/core/go-i18n` (replace directive → `../go-i18n`)
- `forge.lthn.ai/core/go-i18n` — internationalisation, text reversal pipeline
- `forge.lthn.ai/core/go-io` — file I/O abstraction (`coreio.Local`)
- `forge.lthn.ai/core/go-log` — structured logging and error wrapping (`log.E()`)
- `forge.lthn.ai/core/go-inference` (indirect, via go-i18n)
- Both `go-i18n` and `go-inference` must be cloned alongside this repo for builds
- Go 1.26+ required (uses `range` over integers, `iter.Seq`, `maps.Keys`, `slices.Collect`)
## Coding Standards
@ -53,6 +54,8 @@ Files guarded with `//go:build !js` are excluded from WASM:
- Licence: EUPL-1.2 — add `// SPDX-Licence-Identifier: EUPL-1.2` to new files
- Safe-by-default: HTML escaping via `html.EscapeString()` on Text nodes and attribute values, void element handling, entitlement deny-by-default
- Deterministic output: sorted attributes on El nodes, reproducible block ID paths
- Error handling: use `log.E(scope, message, err)` from `go-log`, never `fmt.Errorf`
- File I/O: use `coreio.Local` from `go-io`, never `os.ReadFile`/`os.WriteFile`
- Commits: conventional commits + `Co-Authored-By: Virgil <virgil@lethean.io>`
## Test Conventions

View file

@ -2,6 +2,7 @@ package main
import (
"bytes"
"errors"
"strings"
"testing"
@ -9,6 +10,12 @@ import (
"github.com/stretchr/testify/require"
)
type errReader struct{}
func (errReader) Read([]byte) (int, error) {
return 0, errors.New("read failed")
}
func TestRun_Good(t *testing.T) {
input := strings.NewReader(`{"H":"nav-bar","C":"main-content"}`)
var output bytes.Buffer
@ -49,3 +56,11 @@ func TestRun_Good_Empty(t *testing.T) {
require.NoError(t, err)
assert.Empty(t, output.String())
}
func TestRun_Bad_ReadError(t *testing.T) {
var output bytes.Buffer
err := run(errReader{}, &output)
assert.Error(t, err)
assert.Contains(t, err.Error(), "reading stdin")
}

View file

@ -52,3 +52,23 @@ func TestGenerateBundle_Good(t *testing.T) {
assert.Contains(t, js, "MainContent")
assert.Equal(t, 2, strings.Count(js, "extends HTMLElement"))
}
func TestGenerateBundle_Good_DeduplicatesTags(t *testing.T) {
slots := map[string]string{
"H": "nav-bar",
"F": "nav-bar",
}
js, err := GenerateBundle(slots)
require.NoError(t, err)
assert.Equal(t, 1, strings.Count(js, "extends HTMLElement"),
"duplicate tag should produce only one class definition")
}
func TestGenerateBundle_Bad_InvalidTag(t *testing.T) {
slots := map[string]string{
"H": "notag",
}
_, err := GenerateBundle(slots)
assert.Error(t, err)
assert.Contains(t, err.Error(), "hyphen")
}