fix: deterministic attributes and thread-safe nested layouts

Sort attribute keys for deterministic HTML output (I-1).
Clone nested layouts before mutating path in Render (I-3),
preventing data races when the same tree is rendered concurrently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude 2026-02-16 23:53:31 +00:00
parent 7e484aba8b
commit 40da0d8b1d
No known key found for this signature in database
GPG key ID: AF404715446AEB41
2 changed files with 14 additions and 4 deletions

View file

@ -100,9 +100,12 @@ func (l *Layout) Render(ctx *Context) string {
b.WriteString(`">`)
for _, child := range children {
// Propagate path to nested layouts.
// Clone nested layouts before setting path (thread-safe).
if inner, ok := child.(*Layout); ok {
inner.path = bid + "-"
clone := *inner
clone.path = bid + "-"
b.WriteString(clone.Render(ctx))
continue
}
b.WriteString(child.Render(ctx))
}

11
node.go
View file

@ -1,6 +1,7 @@
package html
import (
"sort"
"strings"
i18n "forge.lthn.ai/core/go-i18n"
@ -76,11 +77,17 @@ func (n *elNode) Render(ctx *Context) string {
b.WriteByte('<')
b.WriteString(n.tag)
for key, val := range n.attrs {
// Sort attribute keys for deterministic output.
keys := make([]string, 0, len(n.attrs))
for k := range n.attrs {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
b.WriteByte(' ')
b.WriteString(key)
b.WriteString(`="`)
b.WriteString(escapeAttr(val))
b.WriteString(escapeAttr(n.attrs[key]))
b.WriteByte('"')
}