Skip to content

feat(rendering): add generate_heex clauses for 5 shell-primitive kinds#144

Draft
ty13r wants to merge 1 commit into
mainfrom
claude/generate-heex-shell-primitives
Draft

feat(rendering): add generate_heex clauses for 5 shell-primitive kinds#144
ty13r wants to merge 1 commit into
mainfrom
claude/generate-heex-shell-primitives

Conversation

@ty13r
Copy link
Copy Markdown
Member

@ty13r ty13r commented May 20, 2026

Summary

Adds IUR fallback generate_heex clauses for five canonical kinds that currently render as empty <div> shells in force_fallback: true mode (operator_v2 Phase 2 + Wave C). This is documented as Substrate Gap #1 in ariston-ui/docs/operator-v2-phase-2-deferred-findings.md.

Tier 2 substrate gap proposal - opens as DRAFT for Pascal review. These are not new canonical primitives (all five already have LiveUi.Renderer clauses on the force_fallback: false path), but adding the IUR fallback path affects every consumer of force_fallback: true mode. Markup, class, and aria decisions need Pascal sign-off before merge.

Context

LiveUIAdapter.render(force_fallback: true) routes through generate_heex/2 clauses. The five kinds below had LiveUi.Renderer clauses (the normal runtime path) but no generate_heex clauses, so they fell through to the default empty-shell catch-all. ariston-ui works around this today by substituting alternative kinds with coverage (e.g., sticky_frosted_header for top_strip). Once Pascal approves this PR, those substitutions can be removed.

Per-kind decision matrix

Kind Wrapper element Class prefix Key IUR props read Children
top_strip <header> ash-top-strip title (h2), brand (span), context (span) yes - via generate_children/2
sidebar_section <section> ash-sidebar-section label (h3), action_label/action_glyph (button text), action_intent (boolean to show action button) yes - via generate_children/2
sidebar_item <li> ash-sidebar-item label (button text), selected?/selected (aria-current="page" + --selected modifier class) yes - nested inside <button>
tabs <div> ash-tabs items (list of %{"id" => ..., "label" => ...}), active_item_id (string matched against item id for aria-selected) no - tab items are inline buttons in a role=tablist div
tree_view <section> ash-tree-view nodes (list), selection_mode (data attr) no - nodes rendered by existing render_tree_nodes/1 helper

Open questions for Pascal

  1. top_strip: Is data-live-ui-shell-position="top" the right data-attr to carry through the IUR fallback, or is it handled by the layout shell and unnecessary on the <header> directly?
  2. sidebar_item: LiveUi.Renderer wraps children inside the <button> element. Is that correct or should children be siblings of the button?
  3. tabs: The IUR fallback renders tab items as inline <button type="button" role="tab"> (no patch/navigate targets, since IUR props don't carry routing info). Acceptable for the fallback path?
  4. tree_view: The fallback delegates to the existing render_tree_nodes/1 helper (which reads label/title/name/value but not selected?/expanded?). Should I extend that helper to carry those props, or is the current behavior sufficient for fallback mode?
  5. CSS class convention: All clauses use ash-* prefix per existing convention (LiveUi.Renderer uses live-ui-*). Please confirm ash-* is correct for the IUR fallback path.

Test plan

Quality-gate commands run from /tmp/ash-ui-gap-1 (worktree of ash_ui at claude/generate-heex-shell-primitives):

  • mix format --check-formatted lib/... test/... - PASS
  • MIX_ENV=test mix compile --warnings-as-errors - PASS (Generated ash_ui app)
  • MIX_ENV=test mix test test/ash_ui/rendering/live_ui_adapter_test.exs - PASS (45 tests, 0 failures, 37 existing + 8 new)

New tests verify: wrapper element + class present, key text props rendered, children propagated, aria-current="page" on selected sidebar_item, --selected modifier class, action button conditional on action_intent, aria-selected="true" on active tab item.

🤖 Generated with Claude Code

Adds IUR fallback rendering (force_fallback: true path) for five canonical
kinds that previously rendered as empty <div> shells in operator_v2 Phase 2.

Kinds added:
- top_strip: <header class="ash-top-strip"> with brand, title, context spans,
  data-live-ui-shell-position="top", and children
- sidebar_section: <section class="ash-sidebar-section"> with h3 label,
  optional action button (when action_intent prop is truthy), and children
- sidebar_item: <li class="ash-sidebar-item"> with <button> containing label,
  aria-current="page" + ash-sidebar-item--selected class when selected? is true
- tabs: <div class="ash-tabs"> with role=tablist and tab buttons; reads
  items list (each with id/label) and active_item_id for aria-selected
- tree_view: <section class="ash-tree-view"> with recursive <ul>/<li>
  structure via existing render_tree_nodes/1 helper; reads nodes list and
  selection_mode prop

IUR prop names for each kind documented in PR description; markup/class/aria
decisions open for Pascal review as this is a Tier 2 substrate gap proposal.

ariston-ui bridges these gaps today by substituting kinds with existing
generate_heex coverage (e.g., sticky_frosted_header for top_strip); this PR
enables the substitutions to be removed once Pascal approves the markup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ty13r
Copy link
Copy Markdown
Member Author

ty13r commented May 20, 2026

Cross-linking the three operator_v2 substrate-gap items (per project-ariston Update 8):

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