From 7edd169fcf5c236e84fbd944bd8d2ad74ad674ef Mon Sep 17 00:00:00 2001 From: Johny Ho Date: Fri, 2 Oct 2026 10:59:50 -0400 Subject: [PATCH 1/2] Render a dug node that is an inline options object Digging to a node written with an inline options object, e.g. `json.cart(with.partial("cart"))`, rendered nothing: the found node is only rendered when it has a block, and an inline options node has none. Its content comes from the options, so render it when those are a Props::Options too. Digging through such a node into its partial already worked, which is what the existing specs covered. Co-Authored-By: Claude Opus 5.5 --- lib/props_template.rb | 6 +++++- spec/extensions/prop_template_search_spec.rb | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/props_template.rb b/lib/props_template.rb index 2cb0920..4506411 100644 --- a/lib/props_template.rb +++ b/lib/props_template.rb @@ -59,7 +59,11 @@ def set!(key, options = {}, &block) @builder = prev_builder @fragment_path = fragment_path - if found_block + # An inline options object, e.g. `json.foo(with.partial("foo"))`, has + # no block; its content comes from the options instead. + found_inline_options = found_options.is_a?(Props::Options) + + if found_block || found_inline_options @builder.item_context = found_item set!(key, found_options, &found_block) end diff --git a/spec/extensions/prop_template_search_spec.rb b/spec/extensions/prop_template_search_spec.rb index b96f8b4..a4c68c0 100644 --- a/spec/extensions/prop_template_search_spec.rb +++ b/spec/extensions/prop_template_search_spec.rb @@ -239,6 +239,23 @@ }) end + it "finds a node that is an inline options object" do + json = render(<<~PROPS) + json.data(dig: ['data', 'comment']) do + json.comment(Props::Options.new.partial('comment')) + end + PROPS + + expect(json).to eql_json({ + data: { + title: "some title", + details: { + body: "hello world" + } + } + }) + end + it "finds a subtree" do json = render(<<-PROPS) json.outer(dig: ['outer','inner', 'deep']) do From eea23b8b47e964056bac0dcbb99fd34f8553529f Mon Sep 17 00:00:00 2001 From: Johny Ho Date: Fri, 2 Oct 2026 11:15:24 -0400 Subject: [PATCH 2/2] Return the full graft path when no fragment was crossed found_path! sliced the found path by the length of the fragment path, which is only set when the dig crosses a fragment. Any other dig, or calling it without digging, raised NoMethodError on nil. superglue's layout calls it on every props_at request, so digs into plain nodes returned a 500. Fall back to an empty fragment path, which returns the full path the client grafts onto the page, as found_path! did before 4183fd0. Co-Authored-By: Claude Opus 5.5 --- lib/props_template.rb | 5 +- spec/extensions/prop_template_search_spec.rb | 59 ++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/lib/props_template.rb b/lib/props_template.rb index 4506411..3ef6dd8 100644 --- a/lib/props_template.rb +++ b/lib/props_template.rb @@ -76,8 +76,11 @@ def partial!(**options) @context.render options end + # Relative to the fragment the dig ended in, so the client can graft into + # it; the full path when no fragment was crossed. def found_path! - @found_path[@fragment_path.size..].join(".") + fragment_path = @fragment_path || [] + @found_path[fragment_path.size..].join(".") end def fragment_context! diff --git a/spec/extensions/prop_template_search_spec.rb b/spec/extensions/prop_template_search_spec.rb index a4c68c0..9f3e536 100644 --- a/spec/extensions/prop_template_search_spec.rb +++ b/spec/extensions/prop_template_search_spec.rb @@ -444,6 +444,65 @@ }) end + context "grafting path without a fragment" do + it "is the full path when digging through plain nodes" do + json = render(<<~PROPS) + json.data(dig: ['data', 'comment', 'details']) do + json.comment do + json.details do + json.body 'hello world' + end + end + end + json.fragmentContext json.fragment_context! + json.path json.found_path! + PROPS + + expect(json).to eql_json({ + data: { + body: "hello world" + }, + fragmentContext: nil, + path: "data.comment.details" + }) + end + + it "is the full path when digging through a partial without a fragment" do + json = render(<<~PROPS) + json.data(dig: ['data', 'comment', 'details']) do + json.comment(partial: 'comment') do + end + end + json.fragmentContext json.fragment_context! + json.path json.found_path! + PROPS + + expect(json).to eql_json({ + data: { + body: "hello world" + }, + fragmentContext: nil, + path: "data.comment.details" + }) + end + + it "is empty without digging" do + json = render(<<~PROPS) + json.data do + json.body 'hello world' + end + json.path json.found_path! + PROPS + + expect(json).to eql_json({ + data: { + body: "hello world" + }, + path: "" + }) + end + end + context "when searching through a partial" do it "returns the correct node in an object" do json = render(<<~PROPS)