Skip to content

fix: render batched components as HTML - #12

Merged
cardmagic merged 3 commits into
mainfrom
agent/fix-batch-html-rendering
Aug 9, 2026
Merged

fix: render batched components as HTML#12
cardmagic merged 3 commits into
mainfrom
agent/fix-batch-html-rendering

Conversation

@cardmagic

Copy link
Copy Markdown
Owner

Fixes the batch rendering bug you reported, and improves the authorization API for batching.

The bug

Confirmed exactly as reported. The browser module requests the batch with Accept: application/json,
so Rails resolved component partials against the JSON format, raised ActionView::MissingTemplate,
and the controller converted that into UnknownComponent and a 404. Any application whose
components are ordinary .html.erb partials, which is all of them, got a 404 for every batch.

Worth noting how it hid: my first version of the regression test passed. ActionController::TestCase
defaults the request format to HTML, so the test did not reproduce what the browser actually sends.
Adding the Accept: application/json header reproduced the 404 with an empty body immediately. The
tests now set that header, which is what makes them regression tests rather than decoration.

The fix

ComponentRenderer renders partials with an explicit formats: [ :html ]. The outer response is
still JSON with the same envelope. Applications do not need .json.erb copies of anything.

Fixing it in the renderer rather than the batch action means both endpoints resolve templates the
same way, so a future caller cannot reintroduce the bug by rendering components under a different
format.

Single-component refresh sends an HTML Accept header and was never affected; a test asserts it
still returns text/html with rendered markup.

Authorization API

component_authorization_context now receives registrations: alongside controller: — one
registration for a single refresh, all of them for a batch — so applications no longer inspect
params[:tokens] by hand:

configuration.component_authorization_context = lambda do |controller:, registrations:|
  Current.user if registrations.all? { |r| r.component_key == controller.session[:seat] }
end

Backward compatible. The controller inspects the callable's parameters and passes the extra
keyword only to callables that declare it, either as registrations: or a keyword splat. A
callback accepting only controller: is called exactly as before. Tests cover all three shapes.

Unchanged behaviour, all asserted

refresh_method, target IDs, per-frame revisions, authorization, and stale-revision handling are
untouched. Missing component still 404, invalid token still 400, unauthorized still 403, newer
requested revision still 409.

Tests

14 controller tests: an HTML ERB component through the JSON endpoint, several components in one
batch, a component with locals and dependencies, HTML rendering while the request format is JSON,
frame metadata, the four error cases, the three authorization callback shapes, and single-component
refresh still rendering HTML.

Upgrade notes

No database change, no migration, no initializer regeneration, no token format change. Applications
on 0.7.0 or 0.7.1 using batch: should upgrade, since batching returns 404 for HTML partials on
those versions. No application code change is required; adopting registrations: is optional.

Validation

bundle exec rake   # 285 runs, 1098 assertions, 0 failures
npm test           # 23 pass, 0 fail

Standard Ruby, RuboCop, RBS, Steep, and Brakeman clean. Version bumped to 0.7.2.

The batch endpoint is requested with a JSON Accept header, so Rails resolved partials against the JSON format, raised ActionView::MissingTemplate, and the controller converted that into a 404 for any application whose components are ordinary .html.erb partials. Render component partials with an explicit HTML format while the outer response stays JSON. Single-component refresh sends an HTML Accept header and was never affected. Also pass registrations to component_authorization_context, one for a single refresh and all of them for a batch, so applications no longer decode params[:tokens] by hand; callbacks accepting only controller: are detected and called unchanged.
@greptile-apps

greptile-apps Bot commented Aug 9, 2026

Copy link
Copy Markdown

Greptile Summary

The PR fixes batched rendering of ordinary HTML partials under JSON requests and extends authorization callbacks with registration context while preserving existing callback shapes.

  • Forces component partial resolution to use HTML while retaining the JSON batch envelope.
  • Passes one or multiple registrations to authorization callbacks that support the keyword.
  • Adds regression coverage for batch rendering, authorization callback shapes, metadata, and error responses.
  • Updates documentation, changelog, generated signatures, and the package version to 0.7.2.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
app/controllers/solid_objects/components_controller.rb Adds backward-compatible authorization callback capability detection; the previously reported callable-object failure is fixed.
lib/solid_objects/component_renderer.rb Explicitly resolves component partials as HTML regardless of the outer request format.
test/integration/component_batch_controller_test.rb Adds focused regression coverage for JSON batch requests, HTML rendering, callback forms, metadata, and error cases.
docs/realtime.md Documents the optional registrations-aware authorization callback API.

Reviews (2): Last reviewed commit: "fix: detect registrations on callable ob..." | Re-trigger Greptile

Comment on lines +147 to +153
def accepts_registrations?(callable)
return false unless callable.respond_to?(:parameters)

callable.parameters.any? do |type, name|
type == :keyrest || (%i[key keyreq].include?(type) && name == :registrations)
end
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Callable objects lose registrations

When configuration uses a callable object whose call method requires registrations:, capability detection inspects the object rather than its method and invokes it with only controller:, causing an ArgumentError and a server error for component refreshes.

Suggested change
def accepts_registrations?(callable)
return false unless callable.respond_to?(:parameters)
callable.parameters.any? do |type, name|
type == :keyrest || (%i[key keyreq].include?(type) && name == :registrations)
end
end
def accepts_registrations?(callable)
parameters = callable.respond_to?(:parameters) ? callable.parameters : callable.method(:call).parameters
parameters.any? do |type, name|
type == :keyrest || (%i[key keyreq].include?(type) && name == :registrations)
end
end
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/controllers/solid_objects/components_controller.rb
Line: 147-153

Comment:
**Callable objects lose registrations**

When configuration uses a callable object whose `call` method requires `registrations:`, capability detection inspects the object rather than its method and invokes it with only `controller:`, causing an `ArgumentError` and a server error for component refreshes.

```suggestion
    def accepts_registrations?(callable)
      parameters = callable.respond_to?(:parameters) ? callable.parameters : callable.method(:call).parameters

      parameters.any? do |type, name|
        type == :keyrest || (%i[key keyreq].include?(type) && name == :registrations)
      end
    end
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 0d04645. A Proc answers parameters; a callable object does not, so my detection fell through to the controller:-only path and an object whose call requires registrations: raised ArgumentError. This is not hypothetical for this repository, which already accepts callable objects for configuration, as component_path_resolver does.

Parameters are now read from the call method when the object does not expose them directly, with a fallback to an empty list so an object exposing neither cannot raise here.

Two tests, one per shape: a callable object requiring registrations: receives them, and one accepting only controller: keeps working. I verified the first genuinely catches the defect by stashing the fix, where it fails, and restoring it, where it passes.

Capability detection asked the configured object for its parameters, which a lambda answers but a callable object does not, so an object whose call method requires registrations: was invoked with only controller: and raised ArgumentError. Read parameters from the call method when the object does not expose them directly. This repository already accepts callable objects for configuration, as component_path_resolver does.
@cardmagic

Copy link
Copy Markdown
Owner Author

@greptileai review

@cardmagic
cardmagic merged commit c1f12ed into main Aug 9, 2026
13 checks passed
@cardmagic
cardmagic deleted the agent/fix-batch-html-rendering branch August 9, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant