From 8386c7e57dfad692d6877c0ea108f5a44a625e91 Mon Sep 17 00:00:00 2001 From: Virgil Date: Tue, 31 Mar 2026 19:58:38 +0000 Subject: [PATCH] fix(html): preserve block paths through conditional wrappers Co-Authored-By: Virgil --- edge_test.go | 13 +++++++++++++ layout.go | 52 +++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/edge_test.go b/edge_test.go index 1409bfb..0611a23 100644 --- a/edge_test.go +++ b/edge_test.go @@ -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) { diff --git a/layout.go b/layout.go index 67faeec..06e3318 100644 --- a/layout.go +++ b/layout.go @@ -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("