Skip to content

Add keyed reactive components - #5

Merged
cardmagic merged 1 commit into
mainfrom
agent/keyed-reactive-components
Aug 7, 2026
Merged

Add keyed reactive components#5
cardmagic merged 1 commit into
mainfrom
agent/keyed-reactive-components

Conversation

@cardmagic

Copy link
Copy Markdown
Owner

Summary

  • allow reactive component names to repeat behind stable signed string or integer keys
  • sign safe JSON-compatible locals into component registrations and pass keys and locals through initial and refresh authorization
  • add opt-in Turbo morph refreshes with same-origin fetching, superseded-request cancellation, and final actor revision fencing
  • preserve the existing unkeyed replace API and accept component tokens issued before these fields existed
  • document the API, security model, consistency behavior, cost model, and asset-pipeline requirement

Why

Large actor-backed views currently have to choose between one coarse component or many uniquely named partials. Keyed components let applications reuse one conventional partial for repeated entities while retaining independent observable dependencies and revision tracking.

Morph refreshes keep personalized HTML request-time and authorized. Cable still carries only invalidation metadata; a gem-owned browser element fetches the signed endpoint with normal cookies and applies newer responses through Turbo's scoped morph operation.

API

<%= solid_object @room, authorization_context: current_user do |room| %>
  <% @players.each do |player| %>
    <%= room.component :player,
      key: player.id,
      observes: %i[players life_totals],
      locals: { player_id: player.id },
      refresh: :morph %>
  <% end %>
<% end %>

Correctness and security

  • (component name, key) is the subscription and DOM identity
  • keys, locals, dependencies, refresh strategy, and initial revision are signed
  • partial resolution remains conventional and cannot be client-selected
  • keys and locals are visible integrity-protected inputs, never authorization or encrypted data
  • authorize_query receives signed keys and locals on every initial and refresh render
  • replace mode retains detached-frame stale response protection
  • morph mode aborts superseded requests and compares the response revision with the current target immediately before applying HTML
  • no rendered HTML is added to durable outbox rows or shared actor broadcasts
  • no database migration is required

Validation

  • bundle exec rake
    • 230 tests, 929 assertions, 0 failures
    • Standard Ruby, RuboCop, RBS validation, Steep, and Brakeman pass
  • PostgreSQL 18: 230 tests, 906 assertions, 0 failures, 2 SQLite-only skips
  • MySQL 8.4: 230 tests, 906 assertions, 0 failures, 2 SQLite-only skips
  • node --check app/assets/javascripts/solid_objects/component_refresh.js
  • built solid_objects-0.5.0.gem and verified the browser module is packaged

Allow repeated component names behind signed keys, pass safe JSON locals through initial and refresh authorization, and add revision-fenced Turbo morph refreshes without broadcasting personalized HTML.
@greptile-apps

greptile-apps Bot commented Aug 7, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds keyed reactive components and an opt-in morph refresh path while retaining the existing unkeyed replacement behavior and compatibility with older component tokens.

  • Signs component keys, JSON-compatible locals, refresh strategy, dependencies, and initial revision into component registrations.
  • Propagates keyed identity and authorization arguments through initial rendering, subscriptions, reconnects, and refresh requests.
  • Adds a same-origin browser refresh element with cancellation and final revision fencing before Turbo morph replacement.
  • Updates documentation, examples, generated RBS signatures, and integration coverage for the new API.

Confidence Score: 5/5

The PR appears safe to merge; no concrete blocking defect or independently actionable non-blocking issue remains.

Key identity, signed registration fields, authorization arguments, refresh routing, and actor revision ordering remain consistent across initial rendering, reconnect, invalidation, and HTTP refresh paths.

Important Files Changed

Filename Overview
app/assets/javascripts/solid_objects/component_refresh.js Adds same-origin morph fetching, per-target request cancellation, response validation, and final actor-revision fencing.
lib/solid_objects/component_token.rb Extends signed component registrations with validated keys, locals, refresh methods, and backward-compatible defaults.
lib/solid_objects/component_registration.rb Centralizes keyed DOM identity, authorization arguments, refresh URLs, and registration metadata.
lib/solid_objects/component_subscriptions.rb Tracks keyed components independently and consistently applies revision ordering to reconnect and invalidation refreshes.
lib/solid_objects/component_renderer.rb Renders conventional partials with signed locals and passes keyed arguments through query authorization.
app/controllers/solid_objects/components_controller.rb Refreshes signed registrations against committed snapshots and emits revision-tagged frames for replace or morph handling.
app/helpers/solid_objects/actor_helper.rb Includes the morph refresh module only when the rendered actor scope contains morph components.
lib/solid_objects/turbo_stream_renderer.rb Emits keyed replace refreshes or temporary morph-fetch elements according to the signed refresh strategy.

Sequence Diagram

sequenceDiagram
  participant View as Rails View
  participant Cable as ActorChannel
  participant Browser as Refresh Element
  participant Endpoint as ComponentsController
  participant Policy as authorize_query
  View->>View: Render keyed component and signed registration
  View-->>Browser: Initial turbo-frame and module
  Cable-->>Browser: Append keyed refresh invalidation
  Browser->>Browser: Abort superseded request
  Browser->>Endpoint: Same-origin fetch with signed token
  Endpoint->>Policy: Authorize component, key, locals, dependencies
  Policy-->>Endpoint: Allow
  Endpoint-->>Browser: Latest turbo-frame with actor revision
  Browser->>Browser: Compare response and current revisions
  Browser->>Browser: Apply scoped Turbo morph if newer
Loading

Reviews (1): Last reviewed commit: "feat: add keyed reactive components" | Re-trigger Greptile

@cardmagic
cardmagic merged commit 5dd77ef into main Aug 7, 2026
11 checks passed
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