diff --git a/lib/ash_ui/rendering/live_ui_adapter.ex b/lib/ash_ui/rendering/live_ui_adapter.ex
index 6b9fb9e5..6efe31b6 100644
--- a/lib/ash_ui/rendering/live_ui_adapter.ex
+++ b/lib/ash_ui/rendering/live_ui_adapter.ex
@@ -874,6 +874,104 @@ defmodule AshUI.Rendering.LiveUIAdapter do
"""
end
+ defp generate_heex(%{"type" => "top_strip"} = iur, opts) do
+ props = iur["props"] || %{}
+ title = escaped_text_prop(props, ["title", "label"], "")
+ brand = escaped_text_prop(props, "brand", "")
+ context = escaped_text_prop(props, "context", "")
+
+ """
+
+ #{if brand != "", do: "#{brand}", else: ""}
+ #{if title != "", do: "#{title}
", else: ""}
+ #{if context != "", do: "#{context}", else: ""}
+ #{generate_children(iur["children"], opts)}
+
+ """
+ end
+
+ defp generate_heex(%{"type" => "sidebar_section"} = iur, opts) do
+ props = iur["props"] || %{}
+ label = escaped_text_prop(props, "label", "")
+ action_label = escaped_text_prop(props, ["action_label", "action_glyph"], "+")
+ action_intent = truthy_prop(props, "action_intent", false)
+
+ action_html =
+ if action_intent do
+ ~s()
+ else
+ ""
+ end
+
+ """
+
+
+ #{generate_children(iur["children"], opts)}
+
+ """
+ end
+
+ defp generate_heex(%{"type" => "sidebar_item"} = iur, opts) do
+ props = iur["props"] || %{}
+ label = escaped_text_prop(props, "label", "")
+ selected? = truthy_prop(props, "selected?", truthy_prop(props, "selected", false))
+ aria_current = if selected?, do: ~s( aria-current="page"), else: ""
+
+ """
+
+
+
+ """
+ end
+
+ defp generate_heex(%{"type" => "tabs"} = iur, _opts) do
+ props = iur["props"] || %{}
+ items = prop(props, "items", []) |> List.wrap()
+ active_item_id = text_prop(props, "active_item_id")
+
+ tabs_html =
+ Enum.map_join(items, fn item ->
+ item = normalize_item(item)
+ item_id = text_prop(item, ["id", "item_id"], "")
+ item_label = escaped_text_prop(item, "label", "")
+ selected? = active_item_id && to_string(item_id) == to_string(active_item_id)
+ aria_selected = if selected?, do: "true", else: "false"
+ tab_index = if selected?, do: "0", else: "-1"
+
+ ~s()
+ end)
+
+ """
+
+ """
+ end
+
+ defp generate_heex(%{"type" => "tree_view"} = iur, _opts) do
+ props = iur["props"] || %{}
+ nodes = prop(props, "nodes", []) |> List.wrap()
+ selection_mode = escaped_text_prop(props, "selection_mode", "single")
+
+ nodes_html = render_tree_nodes(nodes)
+
+ """
+
+ """
+ end
+
defp generate_heex(%{"type" => "slide_over_panel"} = iur, opts) do
props = iur["props"] || %{}
label = escaped_text_prop(props, ["label", "title"], "Panel")
diff --git a/test/ash_ui/rendering/live_ui_adapter_test.exs b/test/ash_ui/rendering/live_ui_adapter_test.exs
index 95d7adc8..ea3c1679 100644
--- a/test/ash_ui/rendering/live_ui_adapter_test.exs
+++ b/test/ash_ui/rendering/live_ui_adapter_test.exs
@@ -1257,4 +1257,171 @@ defmodule AshUI.Rendering.LiveUIAdapterTest do
assert heex =~ ~s(data-selected="true")
end
end
+
+ describe "shell-primitive generate_heex clauses (gap-1)" do
+ test "top_strip renders header with ash-top-strip class and brand/context spans" do
+ iur = %{
+ "type" => "top_strip",
+ "id" => "top-strip-1",
+ "props" => %{"title" => "Dashboard", "brand" => "Ariston", "context" => "dev"},
+ "children" => [],
+ "metadata" => %{}
+ }
+
+ {:ok, heex} = LiveUIAdapter.render(iur, force_fallback: true)
+
+ assert heex =~ " "top_strip",
+ "id" => "top-strip-2",
+ "props" => %{"brand" => "Ariston"},
+ "children" => [
+ %{
+ "type" => "text",
+ "id" => "action-1",
+ "props" => %{"content" => "Actions"},
+ "children" => [],
+ "metadata" => %{}
+ }
+ ],
+ "metadata" => %{}
+ }
+
+ {:ok, heex} = LiveUIAdapter.render(iur, force_fallback: true)
+
+ assert heex =~ "ash-top-strip"
+ assert heex =~ "Actions"
+ end
+
+ test "sidebar_section renders section with heading label and children" do
+ iur = %{
+ "type" => "sidebar_section",
+ "id" => "sidebar-section-1",
+ "props" => %{"label" => "Navigation"},
+ "children" => [
+ %{
+ "type" => "text",
+ "id" => "nav-item-1",
+ "props" => %{"content" => "Home"},
+ "children" => [],
+ "metadata" => %{}
+ }
+ ],
+ "metadata" => %{}
+ }
+
+ {:ok, heex} = LiveUIAdapter.render(iur, force_fallback: true)
+
+ assert heex =~ " "sidebar_section",
+ "id" => "sidebar-section-2",
+ "props" => %{"label" => "Workspaces", "action_intent" => true, "action_label" => "Add"},
+ "children" => [],
+ "metadata" => %{}
+ }
+
+ {:ok, heex} = LiveUIAdapter.render(iur, force_fallback: true)
+
+ assert heex =~ "ash-sidebar-section-action"
+ assert heex =~ "Add"
+ end
+
+ test "sidebar_item renders li with button and label" do
+ iur = %{
+ "type" => "sidebar_item",
+ "id" => "sidebar-item-1",
+ "props" => %{"label" => "Proposals"},
+ "children" => [],
+ "metadata" => %{}
+ }
+
+ {:ok, heex} = LiveUIAdapter.render(iur, force_fallback: true)
+
+ assert heex =~ " "sidebar_item",
+ "id" => "sidebar-item-2",
+ "props" => %{"label" => "Specs", "selected?" => true},
+ "children" => [],
+ "metadata" => %{}
+ }
+
+ {:ok, heex} = LiveUIAdapter.render(iur, force_fallback: true)
+
+ assert heex =~ "ash-sidebar-item--selected"
+ assert heex =~ ~s(aria-current="page")
+ end
+
+ test "tabs renders tablist with tab buttons for each item" do
+ iur = %{
+ "type" => "tabs",
+ "id" => "tabs-1",
+ "props" => %{
+ "items" => [
+ %{"id" => "tab-a", "label" => "Overview"},
+ %{"id" => "tab-b", "label" => "Details"}
+ ],
+ "active_item_id" => "tab-a"
+ },
+ "children" => [],
+ "metadata" => %{}
+ }
+
+ {:ok, heex} = LiveUIAdapter.render(iur, force_fallback: true)
+
+ assert heex =~ "ash-tabs"
+ assert heex =~ ~s(role="tablist")
+ assert heex =~ ~s(role="tab")
+ assert heex =~ "Overview"
+ assert heex =~ "Details"
+ assert heex =~ ~s(aria-selected="true")
+ end
+
+ test "tree_view renders section with ul and li nodes" do
+ iur = %{
+ "type" => "tree_view",
+ "id" => "tree-1",
+ "props" => %{
+ "nodes" => [
+ %{"id" => "node-1", "label" => "Root Node"},
+ %{"id" => "node-2", "label" => "Other Node"}
+ ],
+ "selection_mode" => "single"
+ },
+ "children" => [],
+ "metadata" => %{}
+ }
+
+ {:ok, heex} = LiveUIAdapter.render(iur, force_fallback: true)
+
+ assert heex =~ "