feat(live_ui): declared phx-click + arbitrary data-* attribute passthrough for IUR nodes#159
Merged
Merged
Conversation
… LiveUIAdapter
Add an additive, opt-in passthrough so an IUR node can declare interaction
and identity attributes via its props and have LiveUIAdapter emit them:
* "on_click" => %{"event" => "select_doc", "values" => %{"doc_id" => "d1"}}
(or a bare "select_doc" string) -> phx-click + phx-value-<key> pairs
* "data" => %{"block_id" => "b1"} -> data-<key> attributes
WHY: ariston-ui operator Screens need swimlane artifact rows to carry a
custom phx-click event + phx-value-* (e.g. select_doc / doc_id) and doc-block
widgets to carry data-block-id. Today those are regex-injected into rendered
HTML as a post-processing hack (the form-widget clauses only emit a fixed
event_name/phx-value-binding_id shape, and the generic fallback emits only
class/style/data-widget-id). This gives the renderer a first-class capability
and lets ariston-ui drop the regex workaround for #829.
A new shared helper passthrough_attrs/1 turns those props into attribute
strings via the existing attr/2 helper, wired into the artifact_row clause and
the generic/custom widget fallback. Values are HTML-escaped via html_attr/1 and
attribute-name keys are restricted to [A-Za-z0-9_-] so a hostile key cannot
inject extra attributes. Pairs emit in sorted key order for deterministic output.
Backward-compatible + additive only: a node WITHOUT the new props renders
byte-identically (passthrough_attrs/1 returns ""). The full existing ash_ui
suite stays green (1085 tests, 0 failures). New tests cover on_click ->
phx-click+phx-value, data -> data-*, the unchanged-node baseline, value
escaping, and hostile-key rejection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…onest backward-compat comment (closes Codex P3) WHY: Codex P3 — on_click.event NAME had no explicit escaping test (existing test covered only phx-value-* + data-* VALUES). lib/ untouched; test-only. * Add test: renders artifact_row with event = x" onmouseover="evil(), asserts phx-click value is escaped (x" onmouseover="evil()) and refutes raw injection string appearing as a live attribute. * Replace tautological assert without == also_without in the byte-identically test with a comment noting drift-from-main is caught by the full suite (1085/0); concrete refutes on phx-click/phx-value-/data-* remain. * Suite: 55 tests, 0 failures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Additive attribute passthrough for IUR nodes
AshUI.Rendering.LiveUIAdaptercouldn't emit a customphx-clickevent name (only fixed/derived names) or arbitrarydata-*attributes from an IUR node, so host apps resorted to regex-injecting them into rendered HTML. This adds a clean, additive passthrough.API (opt-in node props, read via the existing
prop/2):"on_click" => %{"event" => "select_doc", "values" => %{"doc_id" => "d1"}}→ emitsphx-click="select_doc"+ onephx-value-<k>="<v>"per value. Bare string"on_click" => "select_doc"also accepted."data" => %{"block_id" => "b1"}→ emits onedata-<k>="<v>"per pair.Implemented via a shared
passthrough_attrs/1(in theattr/2helper family), wired append-only into theartifact_rowclause + the generic/custom fallback. Mirrors the form widgets' existingphx-click/phx-value-*pairing, so the emitted HTML is idiomatic.Security: every emitted value goes through
html_attr/1(escapes& < > " '); attribute-name keys are restricted to~r/\A[A-Za-z0-9_-]+\z/so a hostile key can't inject an extra attribute/handler. Cross-model review (Codex) ran a live injection probe —event = x" onmouseover="evil()renders asphx-click="x" onmouseover="evil()"(escaped, no injected attribute); hostile keys dropped. A regression test codifies the event-name escaping.Backward-compat:
passthrough_attrs/1returns""when neither prop is present → nodes without these props render byte-identically. Both call sites are append-only; no existing clause's output changed. Full suite green: 1085 tests, 0 failures. 55 tests in the adapter test file (8 new for this feature, incl. value + event escaping + hostile-key rejection + unchanged-node).Consumed by ariston-ui (operator Screens) to replace an HTML-regex-injection workaround (#829). Co-maintained framework change; reviewed Opus-impl → Codex-review (writer≠reviewer).