Skip to content

fix(ash_ui validations): trust custom:* opaque widget types in :list binding allow-list - #113

Merged
ty13r merged 1 commit into
mainfrom
claude/validator-allow-artifact-row-and-custom-widgets
May 18, 2026
Merged

ty13r merged 1 commit into
mainfrom
claude/validator-allow-artifact-row-and-custom-widgets

Conversation

@ty13r

@ty13r ty13r commented May 18, 2026 •

Copy link
Copy Markdown
Member

Summary

Allows :list binding_type on "custom:*" opaque widget types in validate_element_binding_locality! at lib/ash_ui/resources/validations/authoring.ex:373.

(Scope narrowed in r2 per Codex P2 #2 + #3 review.) Original draft also added "artifact_row" to @list_widgets; Codex correctly pointed out that :list_repeat is the canonical destination wrapper for collection hydration (per the Phase31RepeatListElement test fixture), NOT :artifact_row. Row-template primitives like :artifact_row continue to belong INSIDE a :list_repeat parent that owns the list binding.

Why

The validator currently raises ArgumentError "does not expose collection semantics" for any :list-typed binding on Elements whose canonical widget_type isn't in @list_widgets ("list", "table", "info_list", "select", "list_repeat").

"custom:*" opaque widget types are application-extension primitives outside the canonical catalog (per Storage.canonical_widget_type at lib/ash_ui/dsl/storage.ex:162). The validator can't know their collection semantics and shouldn't gate the :list binding allow-list on them; trust the authoring intent.

Per Codex P2 #3 the bypass is tightened to ONLY match "custom:*" binary strings; nil widget_type (ui_element block missing entirely) still raises through downstream missing-element error paths.

Triggering precedent

Wave AshUI-3.9 Item 2 dispatch (2026-05-17/18). Ariston-ui attempted to add ui_bindings do binding_type(:list) end blocks to 11 destination Element modules per docs/iur-missing-list-binding-sizing.md. The sizing investigation got the architectural premise wrong (proposed :artifact_row as destination); the correct pattern is :list_repeat wrapping the row template (per Phase31RepeatListElement). Ariston-side fix becomes a structural restructure rather than a flat ui_bindings add. This PR's custom:* bypass is the only ash_ui-side gap that's truly Pascal-roadmap material.

Per umbrella CLAUDE.md "substrate gaps become our roadmap, not asks-of-Pascal" — the custom:* bypass DOES belong here since the canonical catalog has no opinion on opaque application types.

Files

  • lib/ash_ui/resources/validations/authoring.ex — validate_element_binding_locality! gets a custom_widget? bypass for "custom:*" widget types (binary-prefix check only)
  • test/ash_ui/resource/locality_test.exs — 1 new positive test for "custom:thread_card"; helper assert_validator_accepts_list_binding/1 for the validator-passed-but-downstream-domain-error pattern

Test plan

  • mix test test/ash_ui/resource/locality_test.exs — 5/0 pass (4 original + 1 new)
  • Existing negative tests still pass (regression check that allow-list still gates unknown-bad types like :text with :list)
  • New positive test: "custom:thread_card" widget compiles with binding_type :list

Cross-references

Scope notes

  • Narrower than r1. artifact_row extension dropped; custom:* bypass tightened
  • Validator scope extension only. No change to runtime hydration; no change to widget-component catalog; no change to how Storage.canonical_widget_type resolves types

🤖 Generated with Claude Code

…t binding allow-list

Authoring validator (`validate_element_binding_locality!` at
lib/ash_ui/resources/validations/authoring.ex:373) raises ArgumentError
"does not expose collection semantics" for any :list-typed binding on
Elements whose canonical widget_type isn't in @list_widgets ("list",
"table", "info_list", "select", "list_repeat").

`custom:*` opaque widget types are application-extension primitives
outside the canonical catalog (per Storage.canonical_widget_type at
lib/ash_ui/dsl/storage.ex:162). The validator can't know their
collection semantics and shouldn't gate the :list binding allow-list
on them; trust the authoring intent.

Conservative scope:
  - Bypass applies ONLY to "custom:*" string widget_types
  - nil widget_type (`ui_element` block missing entirely) still
    raises through downstream missing-element error paths
  - Canonical list-supporting widgets (`list`, `table`, etc.) remain
    the exclusive allow-list for non-custom Elements; row-template
    primitives like `:artifact_row` continue to belong INSIDE a
    `:list_repeat` parent that owns the list binding (per the
    Phase31RepeatListElement canonical pattern + Codex P2 #2 review)

Triggered by Wave AshUI-3.9 Item 2 dispatch (2026-05-17/18). The
original sizing investigation proposed adding `artifact_row` to
@list_widgets so destination Elements could hold :list bindings
directly; Codex P2 #2 review correctly pointed out that
:list_repeat is the canonical destination wrapper (not :artifact_row)
per the Phase31RepeatListElement test fixture. Per umbrella CLAUDE.md
"substrate gaps become our roadmap, not asks-of-Pascal" — the
`custom:*` bypass DOES belong here since the canonical catalog has
no opinion on opaque application types.

Per Codex P2 #3 review: the bypass is tightened to ONLY match
`"custom:*"` binary strings. Earlier draft included `is_nil(widget_type)`
which would have silently accepted bindings on Elements missing their
`ui_element` declaration entirely; that case should still surface
as a downstream missing-element error.

Test coverage:
  - Existing negative test (`:text` widget rejecting :list) still passes
    (regression check that the allow-list still gates unknown-bad types)
  - New positive test: `"custom:thread_card"` widget compiles with
    `binding_type :list`

The positive test uses a helper `assert_validator_accepts_list_binding`
that checks the SPECIFIC validator error is not raised; downstream
Ash domain-registration exceptions (which fire only AFTER the validator
we're testing passes) are acknowledged as proof the validator accepted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ty13r
ty13r force-pushed the claude/validator-allow-artifact-row-and-custom-widgets branch from 35a38e3 to b206a66 Compare May 18, 2026 02:43
@ty13r ty13r changed the title fix(ash_ui validations): allow :list binding_type on :artifact_row + custom:* widget types fix(ash_ui validations): trust custom:* opaque widget types in :list binding allow-list May 18, 2026
@ty13r

ty13r commented May 18, 2026

Copy link
Copy Markdown
Member Author

Codex r2 disposition — false-positive on the _build/ finding

Codex r2 raised one P2: "Remove generated build artifacts from the patch — _build/dev/lib/ash/ebin/ash.app:1-1 and many BEAM/manifests are also modified or deleted."

Verified false-positive. The PR diff is exactly 2 files:

$ gh pr view 113 --repo The-Metagraph/ash_ui --json files --jq '.files[].path'
lib/ash_ui/resources/validations/authoring.ex
test/ash_ui/resource/locality_test.exs

$ git diff --stat origin/main..HEAD
 lib/ash_ui/resources/validations/authoring.ex | 13 +++++-
 test/ash_ui/resource/locality_test.exs        | 65 +++++++++++++++++++++++++++
 2 files changed, 77 insertions(+), 1 deletion(-)

Codex appears to be reading the local working tree's _build/ state (which contains compilation byproducts from running mix test) rather than the actual PR diff against main. Same false-positive surfaced in r1.

Substantive correctness concerns from r1 (P2 #2 + P2 #3) were addressed in r2:

The architectural pivot is documented in ariston-ui PR #387 (sizing-doc update) and Issue #114 comment.

Merging on the substantive Codex-clean verdict (the _build/ false-positive doesn't block).

@ty13r
ty13r merged commit 7259ba3 into main May 18, 2026
8 checks passed
@ty13r
ty13r deleted the claude/validator-allow-artifact-row-and-custom-widgets branch May 18, 2026 02:48
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