fix(html): preserve block paths through conditional wrappers
All checks were successful
Security Scan / security (push) Successful in 8s
Test / test (push) Successful in 56s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-31 19:58:38 +00:00
parent 5d13a4028b
commit 8386c7e57d
2 changed files with 56 additions and 9 deletions

View file

@ -386,6 +386,19 @@ func TestLayout_EmptySlots_Ugly(t *testing.T) {
}
}
func TestLayout_NestedThroughIf_Ugly(t *testing.T) {
ctx := NewContext()
inner := NewLayout("C").C(Raw("wrapped"))
outer := NewLayout("C").C(If(func(*Context) bool { return true }, inner))
got := outer.Render(ctx)
if !containsText(got, `data-block="C-0-C-0"`) {
t.Fatalf("nested layout inside If should inherit block path, got:\n%s", got)
}
}
// --- Render convenience function edge cases ---
func TestRender_NilContext_Ugly(t *testing.T) {

View file

@ -27,6 +27,48 @@ type Layout struct {
slots map[byte][]Node // H, L, C, R, F → children
}
func renderWithLayoutPath(node Node, ctx *Context, path string) string {
if node == nil {
return ""
}
switch t := node.(type) {
case *Layout:
if t == nil {
return ""
}
clone := *t
clone.path = path
return clone.Render(ctx)
case *ifNode:
if t == nil || t.cond == nil || t.node == nil {
return ""
}
if t.cond(ctx) {
return renderWithLayoutPath(t.node, ctx, path)
}
return ""
case *unlessNode:
if t == nil || t.cond == nil || t.node == nil {
return ""
}
if !t.cond(ctx) {
return renderWithLayoutPath(t.node, ctx, path)
}
return ""
case *entitledNode:
if t == nil || t.node == nil {
return ""
}
if ctx == nil || ctx.Entitlements == nil || !ctx.Entitlements(t.feature) {
return ""
}
return renderWithLayoutPath(t.node, ctx, path)
default:
return node.Render(ctx)
}
}
// NewLayout creates a new Layout with the given variant string.
// Usage example: page := NewLayout("HLCRF")
// The variant determines which slots are rendered (e.g., "HLCRF", "HCF", "C").
@ -141,15 +183,7 @@ func (l *Layout) Render(ctx *Context) string {
if child == nil {
continue
}
// Clone nested layouts before setting path (thread-safe).
if inner, ok := child.(*Layout); ok && inner != nil {
clone := *inner
clone.path = bid + "-"
b.WriteString(clone.Render(ctx))
continue
}
b.WriteString(child.Render(ctx))
b.WriteString(renderWithLayoutPath(child, ctx, bid+"-"))
}
b.WriteString("</")