# poetry-ui API

The component suite's Ruby surface beyond the gallery: the poetry_* view helpers, the model-bound FormBuilder, the Testing helpers, and the chat replay DSL.

## Poetry::Ui::ComponentsHelper

The `poetry_*` view helpers - the agent-facing surface ("use
poetry_button, never a raw <button> with hand-written Tailwind").
Every registered component ships one (drift-gated by
ComponentsHelperTest); slot blocks receive the component:

  <%= poetry_dialog do |dialog| %>
    <% dialog.with_trigger { "Open" } %>
    <% dialog.with_title { "Settings" } %>
  <% end %>

### #poetry_accordion(**, &)

A vertically stacked set of interactive headings that each reveal a
section of content.

### #poetry_alert(**, &)

A callout that highlights an important inline message.

### #poetry_alert_dialog(**, &)

A modal dialog that interrupts the user and expects a response.

### #poetry_aspect_ratio(**, &)

ratio: is a string fraction ("16/9") - Ruby's 16/9 would truncate.

### #poetry_attachment(**, &)

A file or image chip showing its name, type, and size.

### #poetry_attachment_group(**attrs, &)

The horizontally-scrolling attachment rail (scroll-fade + snap).

### #poetry_autocomplete(**, &)

An input that suggests options as you type - the text itself is the
value.

### #poetry_avatar(**, &)

The content block is the initials fallback.

### #poetry_badge(**, &)

A small count or status descriptor.

### #poetry_breadcrumb(**, &)

Shows the path to the current page as a trail of links.

### #poetry_bubble(**, &)

A chat message bubble aligned to its sender.

### #poetry_bubble_group(**attrs, &)

A styled wrapper stacking one sender's consecutive bubbles - a
dictionary element, not a component (a Bubble concern).

### #poetry_button(**, &)

Triggers an action or event, such as submitting a form or opening a
dialog.

### #poetry_button_group(**, &)

Visually joins adjacent buttons and controls into one group.

### #poetry_button_group_separator(**attrs)

The vertical divider between grouped controls.

### #poetry_button_group_text(**attrs, &block)

Non-interactive text inside a ButtonGroup run.

### #poetry_calendar(**, &)

The Calendar: a server-rendered month grid; name: makes it
a form control. poetry--core--calendar adds nav + selection.

### #poetry_card(**, &)

A container that groups related content and actions.

### #poetry_carousel(**, &)

The Carousel: native scroll-snap slides - with_item per
slide; label: names the region.

### #poetry_checkbox(**)

Void control (no content block) - the label is EXTERNAL (Label/Field
for= the button id) or label: (the aria-label fallback).

### #poetry_checkbox_group(**attrs, &block)

The select-all family (the APG mixed-state
parent): the wrapper carries the poetry--core--checkbox-group
controller; the parent checkbox fans out to every enabled item and
item toggles re-derive it (all -> checked, none -> unchecked,
some -> indeterminate).

### #poetry_checkbox_group_all(**attrs)

The group's parent checkbox (target: all) - checking it fans out.

### #poetry_checkbox_group_item(**attrs)

One member checkbox (target: item) - toggles re-derive the parent.

### #poetry_clipboard_text(**)

Read-only value + one copy affordance (API keys, install commands,
IDs); text_to_copy: overrides the clipboard when the display
truncates. Editable text is poetry_input; masked secrets are
poetry_sensitive_input.

### #poetry_code_block(**)

Server-rendered highlighted code panel (rouge, soft dependency):
language:, CSS-counter line_numbers:, highlight_lines:, and a
copy affordance reading the rendered code. Inline code stays
plain <code> typography.

### #poetry_collapsible(**, &)

An interactive element that expands and collapses a section of
content.

### #poetry_color_scheme_script

The color-scheme bootstrap (the pothole other ports patch
into every host by hand): tokens ship `.dark` + `color-scheme`, but
WHEN `.dark` applies is the host's job - and it must happen before
first paint or every visit flashes light. Render inside <head>,
before the stylesheets. Also wires window.Poetry.colorScheme
(current/set/toggle/clear) for toggle controls; an unset preference
follows the OS and tracks its changes live. Full recipe:
the Theming guide, "Color scheme (dark mode)".

### #poetry_combobox(**, &)

The type-to-filter value picker (Select's shell x Command's
engine): options ARE values, committed to a hidden native select.
Must be named (Field label via id: or aria-label). In forms,
prefer f.poetry_combobox.

### #poetry_command(**, &)

The filterable command-palette listbox: items DO
things - picking a VALUE for a form is Combobox territory.

### #poetry_command_dialog(**, &)

The ⌘K variant: a Command inside the Dialog chrome (sr-only
title/description) with the OPT-IN hotkey: global shortcut.

### #poetry_context_menu(**, &)

A menu of actions revealed by right-clicking an element.

### #poetry_data_table(**, &)

The server-driven DataTable: rows/state/path come from the
controller (State.from_params with a sortable: whitelist); columns
are declared in the block. Sorting/filter/page are URL state.

### #poetry_date_field(**)

The segmented date editor:
a native input type=date (ISO on the wire, native pickers with no
JS) enhanced into per-segment spinbutton editing.

### #poetry_date_picker(**, &)

The DatePicker: a field-shaped trigger opening a Calendar in
a Popover; name: is the form field.

### #poetry_date_time_field(**)

A segmented date-and-time editor over a native datetime-local
input: one control, one local wall-time value (no zone on the wire).

### #poetry_deferred(**, &)

A deferred region - Turbo loading physics (lazy/eager) plus
poetry-owned placeholder and retryable-error states. The block is the
placeholder.

### #poetry_dialog(**, &)

A window overlaid on the page for content that requires attention.

### #poetry_drawer(**, &)

The Drawer: the swipeable edge dialog - trigger/title/
description/footer slots, direction: down/up/left/right.

### #poetry_dropdown_menu(**, &)

A menu of actions or options triggered by a button.

### #poetry_empty(**, &)

An empty-state placeholder with an icon, message, and actions.

### #poetry_field(**, &)

Wraps a form control with its label, hint, and validation message.

### #poetry_field_group(**, &)

Stacks fields/fieldsets with the theme's rhythm; also the CSS
`@container` scope Field's orientation: :responsive measures against.

### #poetry_field_separator(**, &)

Divider between stacked fields; pass a block for the inline
caption form ("Or continue with").

### #poetry_fieldset(**, &)

The Field family's group layer: a run of related fields inside a
real <fieldset>, named by legend: (a real <legend>).

### #poetry_file_input(**, &)

A control for selecting, previewing, and removing files to upload.

### #poetry_hover_card(**, &)

A card that reveals preview content when its trigger is hovered.

### #poetry_icon(**)

Renders an inline SVG icon from the icon set.

### #poetry_id_integrity_script(force: false)

The composed-DOM duplicate-id tripwire: a DEVELOPMENT
guard that scans the live page for duplicate [id] values after
every composition event (load, Turbo loads, frame loads, morphs,
stream insertions) and console-warns - the runtime complement to
poetry check's static stable-identity heuristics, and the only
check that sees real composition. Render in the development
layout's <head>; it emits nothing outside development unless
force: true (the dummy's test pages dogfood it that way).

### #poetry_input(**)

A form control for entering a single line of text.

### #poetry_input_group(**, &)

One bordered surface combining an input with buttons, icons, or
add-ons.

### #poetry_input_group_addon(align: :"inline-start", **attrs, &block)

An addon region inside an InputGroup - icons, text, or small
buttons aligned to one of the four edges of the group.

### #poetry_input_group_button(size: :xs, **attrs, &)

The tiny in-group action: a ghost Button re-sized by the group's
dictionary (tailwind_merge lets h-6 beat the Button's own h-9).

### #poetry_input_group_input(**attrs)

The borderless in-group control: the group wears the chrome; the
data-slot=input-group-control is what its focus/invalid selectors
key on.

### #poetry_input_group_text(**attrs, &block)

Inline text inside an InputGroup - prefixes, suffixes, hints.

### #poetry_input_group_textarea(**attrs)

The borderless multiline in-group control - the group wears the
chrome, like poetry_input_group_input.

### #poetry_input_otp(**)

Fixed-length code entry: ONE native input over aria-hidden cells
(paste/SMS-autofill/IME all native) - never per-cell inputs.

### #poetry_item(**, &)

A generic list row with media, content, and actions.

### #poetry_item_separator(**attrs)

The row divider inside an item group: the Separator with the
item-separator slot + spacing.

### #poetry_kbd(**, &)

The key text is the content block: poetry_kbd { "⌘" }.

### #poetry_kbd_group(**attrs, &)

A run of key chords (upstream KbdGroup): a wrapping <kbd> carrying
the themed gap - poetry_kbd children render inside.

### #poetry_label(**, &)

An accessible caption bound to a form control.

### #poetry_link(**, &)

A styled navigational hyperlink.

### #poetry_marker(**, &)

A transcript divider or inline status marker for chat UIs.

### #poetry_menubar(**, &)

A horizontal bar of menus, like a desktop application menu.

### #poetry_message(**, &)

A chat row pairing an author and avatar with message content.

### #poetry_message_group(**attrs, &)

A styled wrapper stacking one sender's consecutive messages - a
dictionary element, not a component (a Message concern).

### #poetry_message_scroller(**, &)

A streaming-aware transcript that keeps the latest message in view.

### #poetry_message_scroller_item(id:, anchor: false, **attrs, &)

One transcript row - the id is how anchoring and Turbo Streams
find it (data-message-id; anchor: pins the reading position).

### #poetry_metadata_list(**, &)

A key-value list for labeled attributes on detail pages.

### #poetry_meter(**)

A quantity within a known range (disk, seats, strength) - role
"meter progressbar" (the two-token fallback); never indeterminate.

### #poetry_native_select(**, &)

A styled wrapper around the real native select control.

### #poetry_native_select_optgroup(**attrs, &block)

A styled <optgroup> inside poetry_native_select.

### #poetry_native_select_option(**attrs, &block)

One styled <option> inside poetry_native_select.

### #poetry_navigation_menu(**, &)

The NavigationMenu: a disclosure bar - with_item for
trigger+panel, with_link for destinations; label: names the nav.

### #poetry_navigation_menu_link(href:, active: false, **attrs, &block)

A panel entry: a REAL link (active: marks the current page).

### #poetry_number_field(**)

The formatted number field: a formatted text input
over a hidden type=number that submits the raw value; steppers
with press-and-hold; ArrowUp/Down (Shift/Alt sizes) on the input.

### #poetry_optimistic_form(attribute_name: nil, value: OptimisticFormBuilder::UNSET, **options, &)

Optimistic UI for a Turbo form: the block's
form.optimistic_template authors the PREDICTED state as a
turbo-stream in a <template>; the controller paints it on submit
and reconciles (morph refresh) only when the server rejects. The
server contract - success answers 204 or a targeted stream, NEVER
a redirect; failure answers 4xx - and the morph-refresh meta
prerequisite live in the Optimistic Forms guide on the docs site.
attribute_name:/value: auto-inject the submitted-value hidden field
(false survives; form.optimistic_hidden_field places it explicitly).

### #poetry_pagination(**)

Data-driven pagination: poetry_pagination(current:, total:,
path:) - path: is a callable ->(page) { url }; the component owns the
truncation math and the accessible nav.

### #poetry_popover(**, &)

Rich floating content anchored to a trigger.

### #poetry_progress(**)

A determinate progress bar toward task completion.

### #poetry_questionnaire(**, &)

One-question-at-a-time survey: a REAL form of fieldset items -
native radio/checkbox/text answers, validate-gated navigation.

### #poetry_radio_group(**, &)

The exclusive-choice control: group.with_item(value:, label:) -
one hidden native radio per item (collection_radio_buttons-exact
serialization). The group MUST be labelled (label: or
aria-labelledby).

### #poetry_resizable(**, &)

The Resizable panel group: with_panel x N; handles are
interleaved automatically (APG window splitters).

### #poetry_scroll_area(**, &)

The native scroll region: size it with classes; label: names it.

### #poetry_search_field(**)

type=search on InputGroup chrome: Escape clears-then-dismisses,
focus-holding clear button, native WebKit affordances suppressed.

### #poetry_select(**, &)

The value-choosing listbox (options ARE values; actions belong to
poetry_dropdown_menu). Must be named: a Field label (id: +
label[for]) or aria-label. In forms, prefer f.poetry_select.

### #poetry_sensitive_input(**)

Secret shown-on-demand (API keys, tokens): masked container is the
reveal button, blur/Escape/eye re-mask, copy: copies without
revealing. Plain passwords being SET (not shown) can stay a
poetry_input type: :password.

### #poetry_separator(**)

A thin divider between content, decorative or semantic.

### #poetry_sheet(**, &)

A dialog that slides in from a screen edge.

### #poetry_sidebar(**, &)

The Sidebar: the app-shell frame - with_nav is the column,
with_inset the page area; poetry--core--sidebar owns the collapse.

### #poetry_sidebar_group_action(label: nil, **attrs, &block)

The group-corner action (upstream SidebarGroupAction): pinned to the
group's top-right by the theme.

### #poetry_sidebar_group_label(**attrs, &block)

The group heading: a muted label above a sidebar menu.

### #poetry_sidebar_input(**attrs)

The sidebar search input (upstream SidebarInput): the themed Input
sized for the header well.

### #poetry_sidebar_menu_action(show_on_hover: false, label: nil, **attrs, &block)

An item-corner action (menu-action): absolutely positioned inside the
menu item (the menu button reserves pr-8 room via the group marker).
show_on_hover: keeps it invisible until the item is hovered/focused
on desktop.

### #poetry_sidebar_menu_badge(**attrs, &block)

A trailing badge (menu-badge): count/status chrome in the item corner,
pointer-transparent.

### #poetry_sidebar_menu_button(href: nil, active: false, size: :default, variant: :default, **attrs, &block)

A menu button: an anchor (href:) or a button, with the active state
+ size and variant axes. active: is the current route (data-active
styles it); variant: :outline draws the bordered treatment.

### #poetry_sidebar_menu_skeleton(icon: false, text_width: "70%", **attrs)

A loading placeholder row (upstream SidebarMenuSkeleton): optional
leading icon block + a text bar with a random-ish width the caller
can pin via text_width:.

### #poetry_sidebar_menu_sub_button(href: nil, active: false, **attrs, &block)

A nested menu entry: an anchor (href:) or a button; active: marks
the current route (aria-current + data-active).

### #poetry_sidebar_rail(**attrs)

The rail: an edge strip that toggles the sidebar (tabindex -1 - the
trigger is the keyboard affordance).

### #poetry_sidebar_separator(**attrs)

The themed divider between sidebar groups.

### #poetry_sidebar_trigger(**attrs)

The sidebar collapse toggle (lives in the inset): a ghost icon
Button wired to the sidebar controller on the wrapper.

### #poetry_skeleton(**, &)

A pulsing placeholder shown while content loads.

### #poetry_slider(**)

Void control - numeric value (value:) or [low, high] range
(values:) on a continuous track; every thumb needs a distinct
accessible name (label:).

### #poetry_spinner(**)

An indeterminate loading indicator that announces itself.

### #poetry_stat(**, &)

A single KPI: a muted label over a large metric value.

### #poetry_switch(**)

Void control - instant-effect on/off (role=switch announces on/off);
values staged for submit belong to poetry_checkbox.

### #poetry_table(**, &)

The Table: the component renders the overflow container + real
`<table>`; the part helpers stamp the data-slot + source-exact classes
onto the semantic table elements the consumer composes.

### #poetry_tabs(**, &)

Tabs: declare with with_tab(title, value:) + panel blocks;
the component owns the ARIA wiring and the two-controller split.

### #poetry_tag_group(**, &)

Removable-chip collection (grid semantics, roving arrows, Delete
removal w/ focus recovery); name: serializes name[] per tag.

### #poetry_textarea(**)

Input's multiline sibling - the value is the CONTENT (value:), not
a block; auto-grow is CSS (field-sizing-content), never a JS
autosizer.

### #poetry_time_field(**)

DateField at hour granularity: HH:MM[:SS] on the wire; the locale
decides 12- vs 24-hour editing (hour_cycle: pins it).

### #poetry_timeline(**, &)

A sequence of dated events as an ordered list.

### #poetry_toast(**, &)

A brief, auto-dismissing notification message.

### #poetry_toast_trigger(**, &)

Client-side toast delivery: pressing the trigger clones the
addressed <template>'s toast into the toaster region - no server
round-trip.

### #poetry_toaster(**, &)

The toast viewport - render ONCE in the application layout (it is
data-turbo-permanent; turbo_stream.poetry_toast appends into it).

### #poetry_toggle(**, &)

Pressed-state button (aria-pressed) - UI state, NOT form data; the
content block is the icon/text (icon-only requires label:).

### #poetry_toggle_group(**, &)

A set of Toggle-styled items under one value machine + one roving
tab stop: group.with_item(value:, label:) { icon/text }.

### #poetry_toolbar(**, &)

A horizontal group of controls that acts as one keyboard tab stop -
Tab passes over the group, Arrow keys move between its controls.

### #poetry_tooltip(**, &)

A floating label describing an element on hover or focus.

### #poetry_tooltip_provider(delay_duration: 0, skip_delay_duration: 300, disable_hoverable_content: false, **attrs, &)

The tooltip delay/warm SCOPE - a config-carrying div, NOT a
controller (the DOM ancestor IS the shared delay scope; the tooltip
controller reads closest('[data-slot=tooltip-provider]') and keys
the module-level warm registry by it). Wrap control rows in ONE
provider so the warm grace makes the row feel continuous.

### #poetry_tree(**, &)

Hierarchical expandable list (flat treegrid): items via the
nested with_item builder, expansion persisted by the host through
poetry:tree:toggle.

### #poetry_typeset(**, &)

Prose styling for long-form and rendered-markdown content.

### #poetry_webmcp_form(tool:, **options, &)

Declares a form as a WebMCP tool - the declarative registration
path: the <form> carries toolname/tooldescription, so a WebMCP
browser registers it as an agent-callable tool with NO JavaScript
(the parameter schema is synthesized from the controls; each
parameter's description comes from its <label>, so the poetry
FormBuilder's model-derived labels describe the tool for free;
tool_description: on a field overrides one). Defaults the builder
to Poetry::Ui::FormBuilder. autosubmit: true is GET-only by
construction - a mutating form always keeps the user's Submit.

## Poetry::Ui::FormBuilder

The poetry FormBuilder - the model-truth end of the error
quartet: `form.field(:email)` renders a Field wrapping an Input with
everything derived from the object, never hand-wired:

  label    from human_attribute_name (Rails i18n)
  value    from the object
  error    from object.errors (auto-flow)
  required from the model's presence validators (-> aria-required
           only - the label-station rule; never the native attribute)
  ids/aria field_id + aria-describedby via Field#control_attributes

Usage: form_with(model:, builder: Poetry::Ui::FormBuilder).

### .registry_section

The registry's form_builder section (consumed by LlmsText's Forms
section, the MCP server, and skills - boot-free from the committed
registry).

### #association(method, as: nil, collection: nil, hint: nil, **)

form.association(:company) - reflection-derived: belongs_to ->
company_id + Combobox, has_many/HABTM -> singular_ids + the
checkbox group; collection from the association klass, label
method auto-detected (to_label/name/title/to_s, the conventional
chain). as: overrides (:select, :combobox, :checkbox_group).

### #autocomplete(method, suggestions = nil, hint: nil, **options, &block)

form.autocomplete(:city, %w[...]) - the input IS the value
(suggestions are conveniences, not constraints); items from the
suggestions array ([label, value] pairs or bare strings) or a
block of auto.with_item calls.

### #button(value = nil, **options, &block)

form.button - #submit with block/content support: the block (or
value) is the Button's content, type stays submit.

### #calendar(method, hint: nil, **options)

form.calendar(:starts_on) - the always-visible month grid as a
form participant (mode: :range posts name[]).

### #check_box(method, options = {}, checked_value = "1", unchecked_value = "0")

The f.check_box-equivalent (the toggle family's form story): name/id
derived, checked: from the object's attribute truthiness, "1"/"0"
plus the unchecked-hidden pair (ActionView::Helpers::Tags::CheckBox
parity incl. hidden-input-first ordering - the Checkbox component
renders the pair). A BARE control mapping: compose with a Field
(control_attributes) for the label/hint/error quartet.

### #checkbox(method, options = {}, checked_value = "1", unchecked_value = "0")

The Rails 8 spelling of #check_box.

### #checkbox_group(method, collection, hint: nil, select_all: false, **options)

The collection_check_boxes-equivalent: a Field(group) wrapping the
APG select-all group (DD sweep) - items post name[] and the
leading empty hidden clears when none are checked. select_all:
true (or a label string) adds the mixed-state parent checkbox.

### #collection_check_boxes(method, collection, value_method, text_method, **)

ActionView's collection_check_boxes arity adapted onto
checkbox_group (name[] items plus the leading clearing hidden).

### #collection_checkboxes(method, collection, value_method, text_method, **)

The Rails 8 spelling of #collection_check_boxes.

### #collection_radio_buttons(method, collection, value_method, text_method, **)

ActionView's collection_radio_buttons arity adapted onto
radio_group (one hidden native radio per item, same serialization).

### #collection_select(method, collection, value_method, text_method, # rubocop:disable Metrics/ParameterLists options = {}, html_options = {})

ActionView's collection_select arity, verbatim, adapted onto
poetry_select (value_method/text_method read off each item).

### #date_field(method, hint: nil, **options)

form.date_field(:due_on) - a Field wrapping a DateField;
params arrive as ISO yyyy-mm-dd with or without JS. OVERRIDES
ActionView's date_field (the number_field precedent).

### #date_picker(method, hint: nil, **options)

form.date_picker(:due_on) - the calendar-popup pick; value posts
ISO like date_field. (Quartet ids land on the composed control's
root for now - the input-level aria refinement comes later.)

### #datetime_field(method, hint: nil, **options)

A Field-wrapped DateTimeField (one control, one datetime-local value; seconds:/hour_cycle: pass through).

### #email_field(method, hint: nil, **options)

form.email_field(:email) - a Field-wrapped Input (type: :email).

### #field(method, as: :input, hint: nil, **input_options)

One field entrypoint for field-shaped controls: as: :input (the
default, with type:) or as: :textarea (rows: passes through) -
Own-line controls slot in as as: values; group-shaped
controls get dedicated methods (radio_group, slider, otp_field).

### #fieldset(legend:, hint: nil, **options, &)

form.fieldset(legend: "Shipping") { |f| ... } - the grouped-fields
frame; yields the builder for nesting.

### #file_input(method, hint: nil, **options)

form.file_input(:document) / form.file_input(:photos, variant: :dropzone,
multiple: true) - a Field wrapping a FileInput; the native
input is the form value, so ActiveStorage attaches as usual.

### #group(**options, &)

form.group { |f| ... } - the FieldGroup stack (the @container the
responsive Field orientation measures against).

### #input(method, as: nil, collection: nil, hint: nil, required: nil, **options)

The inferred entrypoint: one call, everything derived from the
model. The control type comes from as: when given, otherwise from
attachment duck-typing, AR enums, the attribute/column type, and
name heuristics; hint/placeholder resolve from the poetry_form
(or simple_form) i18n chain when not passed; length and numeric
validations become maxlength/min/max/step attributes.

### #native_select(method, choices = nil, include_blank: nil, hint: nil, **options)

form.native_select(:country, [["USA", "us"], ...]) - the styled
NATIVE <select> (zero JS); poetry_select/poetry_combobox stay the
rich paths. include_blank: posts "" (Rails semantics).

### #number_field(method, hint: nil, **options)

form.number_field(:quantity, min: 0, max: 100) - a Field wrapping
a NumberField, label/error/required from the object. The
hidden <input type=number> submits the raw value; format: only
shapes the display.

### #otp_field(method, length: 6, hint: nil, **options)

The verification-code story: form.otp_field(:code, length: 6) - a
Field wrapping an InputOTP, label/error/required from the object.
The value is deliberately NEVER round-tripped (a rejected code is
dead; re-rendering it invites resubmit-the-same-wrong-code loops)
- pass value: explicitly to override.

### #password_field(method, hint: nil, **)

form.password_field(:password) - a Field-wrapped Input
(type: :password); the value NEVER round-trips (Rails' own
behavior). Revealable secrets are #sensitive_input.

### #telephone_field(method, hint: nil, **options)

form.telephone_field(:phone) - a Field-wrapped Input (type: :tel).
The Rails alias for telephone_field.

### #poetry_combobox(method, choices = nil, include_blank: nil, hint: nil, **options, &block)

The poetry_select twin for the type-to-filter picker (the
combobox capstone): a Field wrapping a Combobox, everything
derived from the object. The hidden native <select> is the
serialization truth, the Field's control_attributes land on the
TRIGGER (id + aria-describedby error-before-hint + aria-invalid),
and label[for: id] click-focuses the combobox.

choices accept the same Rails shapes as poetry_select:
[["Label", value], ...] pairs, flat %w[a b], grouped
{"Group" => [["Label", value], ...]} (group parts wear Command's
heading:), or nil plus a block of combobox.with_item calls.
include_blank: "Choose..." doubles as the placeholder text (the
blank option posts "" and fails native required validation -
deselection is a form affordance, never a re-click toggle).

multiple: true is the chips mode: the value is the model's ARRAY
(params post name[] - Rails' own convention, the [] derived for
you) and the control_attributes land on the INLINE INPUT (the
chips field has no trigger); include_blank has no meaning there.

### #poetry_select(method, choices = nil, include_blank: nil, hint: nil, **options, &block)

The FormBuilder#select-equivalent (the listbox capstone): a
Field wrapping a Select, everything derived from the object. The
hidden native <select> is the serialization truth (name/value/
required ride it), the Field's control_attributes land on the
TRIGGER (id + aria-describedby error-before-hint + aria-invalid),
and label[for: id] click-focuses the combobox.

choices accept the Rails shapes: [["Label", value], ...] pairs,
flat %w[a b], grouped {"Group" => [["Label", value], ...]}, or nil
plus a block of select.with_item calls. include_blank: "Choose..."
doubles as the placeholder text (Rails include_blank semantics -
the blank option posts "" and fails native required validation,
which is the correct behavior).

### #radio_group(method, collection, hint: nil, **options)

The collection_radio_buttons-equivalent (the exclusive-choice
story): a Field wrapping a RadioGroup, items from the collection
([[value, label], ...] pairs or bare values), value/label/error/
required derived from the object. Serialization is byte-identical
to collection_radio_buttons (one hidden native radio per item,
shared name; nothing submits when none is checked).

### #range_field(method, hint: nil, **)

Rails' range_field IS poetry's Slider (a naked native range would
silently bypass the Field quartet).

### #search_field(method, hint: nil, **options)

form.search_field(:query) - a Field wrapping the SearchField
(native type=search + the clear affordance).

### #select(method, choices = nil, options = {}, html_options = {}, &)

ActionView's select arity adapted onto poetry_select.

### #sensitive_input(method, hint: nil, **options)

form.sensitive_input(:api_key) - the revealable-secret story
: masked at rest, reveal + optional copy:.

### #slider(method, range: false, hint: nil, **options)

The bounded-numeric story: form.slider(:volume) single (label from
human_attribute_name), form.slider(:price_range, range: true,
label: [...]) reads an Array[2] and submits name[] (params:
["200", "800"] - Rails' own array convention). Field wraps for
hint/error; the describedby lands on each THUMB.

### #submit(value = nil, **options)

form.submit -> a poetry Button (type submit); label from Rails'
own i18n default ("Create Model" / "Update Model"). loading: true
opts into the Button loading treatment.

### #switch(method, options = {}, checked_value = "1", unchecked_value = "0")

The same mapping wearing switch semantics (Rails has NO native
switch builder): role=switch announces on/off; use it for
instant-effect settings, check_box for values staged for submit.

### #tag_group(method, hint: nil, **options)

form.tag_group(:topics) - the removable-chips story for an ARRAY
attribute: one hidden name[] per tag, plus the leading empty
hidden (Rails array convention: removing every tag still clears).

### #telephone_field(method, hint: nil, **options)

form.telephone_field(:phone) - a Field-wrapped Input (type: :tel).

### #text_area(method, hint: nil, **)

form.text_area(:bio) - a Field-wrapped Textarea (rows: passes
through; auto-grow is CSS, never a JS autosizer).

### #text_field(method, hint: nil, **options)

form.text_field(:name) - a Field-wrapped Input (type: :text).

### #textarea(method, hint: nil, **)

The Rails 8 spelling of #text_area.

### #time_field(method, hint: nil, **options)

form.time_field(:starts_at) - a Field wrapping a TimeField;
params arrive as HH:MM (HH:MM:SS with seconds: true).

### #url_field(method, hint: nil, **options)

form.url_field(:website) - a Field-wrapped Input (type: :url).

## Poetry::Ui::Testing

Consumer-facing interaction testers: drive poetry components
through their REAL keyboard/pointer
sequences in a Capybara system test and assert against the public
attribute contract (data-open, aria-expanded, data-highlighted) -
never against markup internals. Each tester is an executable spec of
its component's interaction contract; agents are taught to test
through them (the usage skill), so generated tests are correct by
construction.

  require "poetry/ui/testing"

  class PlanTest < ApplicationSystemTestCase
    include Poetry::Ui::Testing

    test "picking a plan" do
      visit settings_path
      select = poetry_select("#plan")
      select.select_option("Pro", via: :keyboard)
      assert_equal "pro", select.value
    end
  end

Testers locate parts by data-slot from the root you hand them, and
wait with Capybara's own retry discipline - no sleeps. `via:` swaps
the ENTIRE event sequence (:mouse clicks, :keyboard focuses the
trigger and drives keys), because the two paths exercise different
controller seams.

### #assert_poetry_controllers_registered(session: nil, application: "window.Stimulus")

Asserts that every poetry controller on the page is registered.
Flunks under Minitest, raises {RegistrationError} elsewhere, with
the identifiers and the two causes to check first.

### #poetry_combobox(root, session: nil)

A Combobox tester rooted at the component (single or multiple).

### #poetry_dialog(root, session: nil)

A Dialog tester rooted at the component.

### #poetry_dropdown_menu(root, session: nil)

A DropdownMenu tester rooted at the component.

### #poetry_select(root, session: nil)

A Select tester rooted at the component.

### #poetry_unregistered_controllers(session: nil, application: "window.Stimulus")

Every poetry controller identifier on the current page that the
host's Stimulus application has NOT registered - empty when the
wiring is healthy. Stimulus never errors on an unknown identifier
(the element just stays inert), and one failed import in the
controllers graph silently takes every poetry controller with it,
so nothing else surfaces this.

## Poetry::Ui::Chat

The chat replay DSL: script a user/assistant conversation in
Ruby, get a DETERMINISTIC timeline of
streaming frames to replay through the real Turbo Stream pipeline -
no model, no network, fixed ids, fixed pacing. The DSL is pure data
(transcript + timing); rendering frames into Message rows is the
consumer's job (the docs replay demo is the reference consumer).

A tool with `approval: true` PAUSES its segment after the input frame;
the frames that follow (its output - or denial - and later parts)
belong to the continuation and are produced by
`continuation_frames(approved:)` - the human-in-the-loop
model, server-shaped: the pause is a rendered form, the continuation
is the stream after the decision.

### .script(&)

Builds a Script from the block - instance_eval'd, so bare `user`
and `assistant` calls declare the segments in transcript order.

## Poetry

The poetry namespace: poetry-ui shares it with poetry-core (the DSL)
and the optional poetry-charts.

## Poetry::AddGenerator

`rails g poetry:add Button [@acme/fancy-chart ...]` - the copy-in tier
plus the ecosystem address scheme.

Bare names and @poetry/* are the installed gems' own items: component
source copies into app/components where Rails autoload precedence
shadows the gem ("own the code"; dependencies resolve recursively,
existing files are SKIPPED - local edits win, always), and block names
hand off to poetry:block. Everything else is a REMOTE registry address -
a URL, a local item file, or @namespace/item against the registries:
section of config/poetry_components.yml - resolved through
RegistryClient + RegistryInstaller: the dependency DAG fetches
recursively and writes in topo order, poetry components satisfy at
RUNTIME (the gem provides them - no copy), targets are
traversal-checked, and gem dependencies are REPORTED, never installed.
Every install records provenance in the manifest.

## Poetry::AgentRulesGenerator

`rails g poetry:agent_rules` - the two-file, opposite-lifecycle agent
ruleset:

  .poetry/agent-rules.md  gem-owned, FORCE-overwritten every run
                          (generated live from the component registry)
  .poetry/house-rules.md  seeded once, then user-owned - never touched again

plus an idempotent marker-import into CLAUDE.md and AGENTS.md:
insert-between-markers, replace-on-rerun, and detect-but-never-rewrite
when a file carries a broken half-marker.

## Poetry::AgentsGenerator

`rails g poetry:agents` - writes or refreshes the poetry section of the
host's AGENTS.md (the agent-facing pointer to llms.txt / poetry check)
without running the full install. poetry:install performs the same step.

## Poetry::BlockGenerator

`rails g poetry:block <name>` - copies a vetted composed
screen into the app as source the app OWNS. Blocks are starting points
an agent (or a person) edits, not components it configures: the file
lands under app/views/blocks/ as a partial, the sample content is meant
to be replaced in place, and `poetry check` keeps validating the result
like any other template. `--list` (or no argument) prints the catalog
from the registry's blocks section.

## Poetry::DiffGenerator

`rails g poetry:diff` - the copy-in upgrade report. poetry's
three ownership tiers upgrade differently: gem-owned code rides
`bundle update`, the vendored css set rides a `poetry:install` re-run,
and copy-ins are app-OWNED - nothing may rewrite them. This generator
closes the loop on that third tier: it reads the provenance
manifest (config/poetry_components.yml) and reports, file by file, where
the app's copies stand against what the installed gems ship NOW.
Read-only by contract - it prints, it never writes; `poetry:add`
re-run is the (skip-if-exists) way to pick up newly-shipped files.

## Poetry::EditorGenerator

`rails g poetry:editor` - wire the poetry agent surface into the editors a
Rails team actually uses. poetry has no bespoke extension (a possible
future direction); what it DOES have is a standard MCP stdio server and a
source-generated component registry, so this generator emits:

  .mcp.json            Claude Code   (mcpServers)
  .cursor/mcp.json     Cursor        (mcpServers)
  .vscode/mcp.json     VS Code       (servers + type: stdio)
  .vscode/poetry.code-snippets   one snippet per poetry_* helper, with the
                                  real variant/size enums as tab-stop choices
  .herb.yml            Herb (linter / formatter / language server), unless
                       the app already has one
  .stimulus-lsp/config.json   poetry's controller identifiers for Stimulus LSP
                              (upsert: an existing ignore list keeps its entries)

The MCP writes are upserts: an existing config keeps its other servers and
gains a `poetry` entry; a config that already has one is left untouched; a
JSONC file poetry can't parse is reported, never clobbered. Editors whose
MCP config is global / IDE-managed (Zed, Windsurf, RubyMine) get a
copy-paste block printed instead.

## Poetry::Generators

Namespace for poetry's Rails generators and their shared step
modules.

## Poetry::Generators::PaginationGenerator

`rails g poetry:pagination [kaminari|pagy|will_paginate]` - copies a
poetry adapter for the host's paginator(s) as OWNED host-app code
(the copied-source doctrine; poetry:diff tracks drift). No argument:
every paginator loaded in this app gets its adapter.

The doctrine baked into every adapter: poetry owns the window
(siblings/edges compute the visible pages) - the paginator's own
window options deliberately do not apply, so pagination looks the
same whichever gem drives it.

## Poetry::InstallGenerator

`rails g poetry:install` - wires poetry into a host app:

  app/assets/tailwind/poetry/tokens.css   the design tokens (:root + .dark)
  app/assets/tailwind/poetry/theme.css    the Tailwind v4 @theme mapping
  app/assets/tailwind/poetry/animate.css  the vendored animation
                                          utility layer (Rails hosts
                                          have no npm)
  app/assets/tailwind/poetry/safelist.txt every dictionary + template class
                                          (so the host build never purges
                                          classes resolved in Ruby)
  config/initializers/poetry.rb           commented configuration
  config/poetry_components.yml            the copy-in manifest (empty)

plus idempotent @import/@source injection into the host's Tailwind entry
(append-unless-present - re-running install is always safe).

`--charts` additionally wires poetry-charts (the gem must already be in
the bundle - its engine merges the @poetry/charts importmap pins and the
safelist pass picks the chart dictionaries up on its own; what a host
still needs by hand is the motion stylesheet in the Tailwind entry and
the Stimulus registration, and that is exactly what the flag does).

## Poetry::ScaffoldTemplatesGenerator

`rails g poetry:scaffold_templates`:
Rails has always let an app override its generator templates
from lib/templates/; what was missing was a set that renders with
poetry. This copies scaffold view templates - and a matching scaffold
controller template - so the STANDARD `rails g scaffold` produces
poetry-composed output: a DataTable index with sanitized URL state
(sortable whitelist, filter, pagination), Field-composed forms with
attribute-type -> component mapping (plus column-name -> input-type
heuristics and `null: false` -> required), a MetadataList show, and a
destructive-variant delete. The copies are the app's to edit; re-runs
never overwrite (skip-if-exists, the poetry:add contract).

## Poetry::SkillGenerator

`rails g poetry:skill` - installs or refreshes poetry's Claude Code
skills in the app's .claude/skills/:

  poetry/           the component-usage skill, GENERATED from the live
                    registry (SKILL.md menu + per-family references) -
                    re-run this generator after updating poetry gems
  poetry-design/    the taste layer (theme / compose / audit / study),
                    curated prose riding the DESIGN.md + design-lint rails
  poetry-component/ the authoring layer (anatomy / documentation /
                    audit) for components the app builds itself

poetry:install runs the same step; this standalone exists as the
refresh path (and for hosts that installed before the skills shipped).

## Poetry::Ui

The component library: ViewComponents tracking the full ported
component vocabulary, built entirely on
poetry-core's PUBLIC DSL - if a component here needs private core API,
that is a core API gap, not a license to reach in.

### .agent_skills

The MCP server's runtime skill map (get_skill): the SAME
files `rails g poetry:skill` writes, for hosts that cannot write
files. Boot-free by construction - the usage skill regenerates from
the COMMITTED registries (never the booted builders above), the
design skill reads its static templates - so the exe can serve both
without Rails. Lazy: nothing generates until an agent asks.

### .block_components(source, titles:)

The distinct components a block composes, folded from its poetry_*
helper calls: each call maps to the LONGEST component title that
prefixes it (poetry_sidebar_menu_badge -> sidebar; poetry_table_head
-> table); calls matching no component (pure wrapper helpers like
poetry_input_group_addon fold through input_group) are dropped
rather than guessed.

### .charts_registry

Tolerant on charts, like the AGENTS.md census: a host without the
gem (or with a stubbed/partial one) just drops the charts reference.

### .committed_charts_registry

The charts reference rides along whenever the charts gem is present
(its committed registry, not its booted builder); absent, the skill
simply drops it - the charts_registry tolerance, boot-free.

### .helper_names

The public poetry_* helper names, so poetry check / poetry-agent know
the full set (group / provider helpers AND the define_method'd part
helpers like poetry_table_cell) WITHOUT booting Rails. The module's
method bodies reference component constants only at call time, so
requiring the file standalone is safe.

### .recipe_items

The recipes projection: skill bundles, scaffold template
sets, and screen slices as registry items -
served at /r/*.json beside components and blocks, installed by
poetry:add or any client speaking the same item schema.

### .registry

The registry builder this gem commits from (booted contexts only:
rake registry:generate/verify and the sync test share it, so the
drift gate always compares against the exact construction that
generated the file - helpers AND blocks sections included).

### .registry_blocks(component_paths:)

The registry "blocks" section: every block template's metadata,
all source-derived - title/description/keywords from the
mandatory poetry:block header (keywords power the MCP compose
tool's brief routing), the composed component list from the
template's own poetry_* calls (longest-prefix fold:
sidebar_menu_button counts as sidebar), the gem-relative template
path for boot-free source reads. No hand-authored catalog to drift.

### .registry_descriptions

The editorial per-component descriptions merged into the registry
(component_path => one-liner, from config/component_descriptions.yml).
Absent file -> nil, so a registry without it stays lint-identical, like
every other optional section.

### .registry_helpers(component_paths:)

The registry "helpers" section: every poetry_* helper that
maps to no component - group/provider/item wrappers - each carrying
its declared value contract (ComponentsHelper::HELPER_CONTRACTS) or
{} for a plain wrapper.

### .registry_items

The interop item projection - registry items in the ecosystem's
shared JSON item schema, boot-free
from the COMMITTED registry - the docs site serves /r/*.json from
this, and the add generator matches gem-satisfied dependencies
against its names.

### .root

Gem root (the directory containing lib/, app/, config/).

### .skill_files

The installable component-usage skill: a lean SKILL.md menu +
per-family references, generated from the live
registry - the seam the poetry:skill generator and the eval
harness share (the agents_section_text pattern).

### .template_classes

## Poetry::Ui::Accordion

Vertically stacked expand/collapse sections.

## Poetry::Ui::Alert

Inline callouts for statuses and errors.

## Poetry::Ui::AlertDialog

Modal confirmations that must be answered.

## Poetry::Ui::AspectRatio

Ratio-locked media containers.

## Poetry::Ui::Attachment

File/image upload chips.

## Poetry::Ui::Autocomplete

Free-text inputs with filtering suggestion popups.

## Poetry::Ui::Avatar

Person images with initials fallbacks.

## Poetry::Ui::Badge

Non-interactive status pills.

## Poetry::Ui::Breadcrumb

Ancestor-trail navigation.

## Poetry::Ui::Bubble

Chat message bubbles.

## Poetry::Ui::Button

Action buttons and button-styled links.

## Poetry::Ui::ButtonGroup

Segmented groups of adjacent controls.

## Poetry::Ui::Calendar

Server-rendered month-grid date pickers.

## Poetry::Ui::Card

Content surface cards.

## Poetry::Ui::Carousel

Carousel family: the scroll-snap slide strip and its paging controls.

## Poetry::Ui::Checkbox

Checkbox family: the form-participating tri-state toggle.

## Poetry::Ui::ClipboardText

ClipboardText family: a read-only value with one copy affordance.

## Poetry::Ui::CodeBlock

CodeBlock family: the server-highlighted code panel.

## Poetry::Ui::CodeBlockHighlighter

The CodeBlock's rouge seam: highlighting is a SOFT capability -
`gem "rouge"` in the host Gemfile turns it on; without it the component
renders the plain escaped code unchanged (the fallback is simply
"no rouge", and nothing shifts because the markup shape is identical).

### .available?

Whether rouge is loadable in this host (memoized).

### .formatter

Rouge 5 asserts a PLAIN HTML delegate inside its line-wise wrappers
(HTMLLinewise cannot wrap HTMLLineHighlighter), so poetry carries its
own line formatter over the stable token_lines API.

### .highlight(code, language:, highlight_lines: [])

-> html_safe highlighted markup - every line wrapped in .line (the
counter hook), the requested ones also .hll (the theme's tint hook) -
or nil when rouge is absent. Unknown languages lex as plain text.

## Poetry::Ui::Collapsible

Collapsible family: the plain show/hide disclosure.

## Poetry::Ui::Combobox

Combobox family: the searchable select - a trigger, a filterable
listbox popup, and a native-select form story.

## Poetry::Ui::Command

Command family: the filterable command palette and its dialog form.

## Poetry::Ui::ContextMenu

ContextMenu family: the right-click/long-press menu.

## Poetry::Ui::DataTable

DataTable family: the sortable, filterable, paginated table plus
its URL-state object.

## Poetry::Ui::DateField

DateField family: the segmented date editor over a native input.

## Poetry::Ui::DatePicker

DatePicker family: a field-shaped trigger opening a calendar popover.

## Poetry::Ui::DateTimeField

Segmented date-and-time editors.

## Poetry::Ui::Deferred

Deferred family: lazy-loaded regions with loading and error states.

## Poetry::Ui::Dialog

Dialog family: the modal overlay on the native <dialog> element.

## Poetry::Ui::DictionaryFidelity

The dictionary half of the fidelity ledger: the structural utilities
a Style dictionary puts on each part, held against the classNames the
source puts on the same data-slot at the pin.

The theme ledger (ThemeFidelity) proves the cn-* rules; nothing proved
the Ruby strings that ride the same elements - a token there wins
over every theme's rule at once, which is how a classic-only sizing
chain and a demo width overrode nine themes without a failing gate.

Two sources are snapshotted per slot: the styled registry
(bases/base/ui, hooks plus structural utilities) is the contract the
dictionaries must match; the classic registry (new-york-v4/ui, every
visual utility inline) tells a classic-only token apart from a
poetry-only one. Hooks (cn-*) are the theme ledger's concern and are
left out of both sides.

Rendered previews supply poetry's side: for every element a component
owns (PartContract's ownership rule), the class tokens that belong to
the dictionary. Reconciliation against deviations.yml is exact and
two-way, like the theme ledger: an unrecorded difference fails, a
recorded one that no longer exists fails as stale.

### .current_diffs(root, rendered)

Every component's diff against the committed snapshot.

### .diff(source, rendered)

The per-component diff between the source's slots and poetry's
rendered, owned slots. Rendered values are { "tag" => String,
"tokens" => Set } or COMPOSED (another component's root wearing the
slot - present, but its tokens are that component's ledger).

### .extract(text)

Every element carrying data-slot in one source file, with the
structural tokens its className resolves to (string literals, cva
base and variant strings, render-prop classNames) and its tag.
Roots rendered through useRender are read from their props object.

### .snapshot(root)

The committed snapshot.

### .snapshot_path(root)

The committed snapshot's path - exactly one may exist.

### .verify(root, rendered)

Exact two-way reconciliation against deviations.yml. Returns a list
of finding strings; empty means the contract holds.

### .write_snapshot(root, checkout:, pin:)

Writes the frozen source snapshot from a pinned checkout: every
styled registry file's slots, with the classic registry's tokens for
the same slots beside them.

## Poetry::Ui::Drawer

Drawer family: the swipeable edge sheet on the dialog spine.

## Poetry::Ui::DropdownMenu

The DropdownMenu family - the button-triggered action menu.

## Poetry::Ui::Empty

The Empty family - the empty-state block for collections with nothing in them.

## Poetry::Ui::Engine

The Rails engine: wires the component classes, the poetry_* view
helpers, the Turbo Stream toast action, and the preview paths into
the host app.

## Poetry::Ui::Field

The Field family - the label/control/hint/error wrapper for one form control.

## Poetry::Ui::FieldGroup

The FieldGroup family - the stacking container for Fields.

## Poetry::Ui::FieldSeparator

The FieldSeparator family - the divider row between stacked fields.

## Poetry::Ui::Fieldset

The Fieldset family - a named group of related fields.

## Poetry::Ui::FileInput

The FileInput family - file selection as a plain control or a dropzone.

## Poetry::Ui::HoverCard

The HoverCard family - the pointer-hover link preview.

## Poetry::Ui::Icon

The Icon family - inline SVG icons from the configured icon set.

## Poetry::Ui::Input

The Input family - the single-line native text control.

## Poetry::Ui::InputGroup

The InputGroup family - one bordered field surface for a control plus addons.

## Poetry::Ui::InputOtp

The InputOtp family - fixed-length one-time-code entry.

## Poetry::Ui::Item

The Item family - the generic list row.

## Poetry::Ui::Kbd

The Kbd family - the keyboard-key chip.

## Poetry::Ui::Label

A caption for a form control.

## Poetry::Ui::Link

A navigation link.

## Poetry::Ui::LlmsController

Serves llms.txt / llms-full.txt generated live from the component
registry - the docs an LLM retrieves can never drift from the code.

### #full

GET /poetry/llms-full.txt - the full per-component reference.

### #index

GET /poetry/llms.txt - the catalog index.

## Poetry::Ui::Marker

A transcript divider or inline status line for chat UIs.

## Poetry::Ui::Menubar

A desktop-style menu bar of drop-down menus.

## Poetry::Ui::Message

One chat turn's row in a conversation transcript.

## Poetry::Ui::MessageScroller

A streaming-aware chat transcript scroller.

## Poetry::Ui::MetadataList

Labeled facts about one record, as a description list.

## Poetry::Ui::Meter

A quantity within a known range.

## Poetry::Ui::NativeSelect

A styled native <select>.

## Poetry::Ui::NavigationMenu

A site-navigation bar with disclosure panels.

## Poetry::Ui::NumberField

A numeric input with steppers and display formatting.

## Poetry::Ui::OptimisticFormBuilder

The optimistic-form builder (the hotwire_club-toolbox port):
renders the scaffolding poetry_optimistic_form's controller consumes -
<template> targets holding the PREDICTED state as turbo-stream(s)
(the same vocabulary the server answers in, so prediction and truth
never need bespoke DOM patching), and the hidden field carrying the
submitted value.

### #optimistic_hidden_field(attribute_name, value:)

Explicit placement of the submitted-value field; calling it
suppresses the helper's automatic injection.

### #optimistic_hidden_field_rendered?

### #optimistic_template(target = nil, template = nil, &block)

The predicted state, cloned into the DOM on submit. Positional form
wraps a turbo_stream update for you; block form authors the
stream(s) directly (several regions, other actions):

  form.optimistic_template dom_id(photo, "fav"), icon(!photo.favorite)
  form.optimistic_template { turbo_stream.update("cart-count") { @count + 1 } }

## Poetry::Ui::Pagination

Numbered page navigation.

## Poetry::Ui::Popover

A click-opened panel anchored to its trigger.

## Poetry::Ui::Progress

A determinate progress bar.

## Poetry::Ui::Questionnaire

A one-question-at-a-time survey form.

## Poetry::Ui::RadioGroup

A mutually exclusive option set: radio dots or selectable choice cards.

## Poetry::Ui::Recipes

The recipes channel: multi-file payloads served
through the registry beside components and blocks. Every definition's
files are LAZY callables over gem-shipped sources - the same files
the generators install - so the projection cannot drift from the
generator path. Names share the flat kebab namespace (RegistryIndex
collision-checks recipes against components and blocks at first
touch).

### .definitions

Every recipe definition, in registry order: the agent embed, the
three skill bundles, the scaffold-template set, and the screen
slices.

## Poetry::Ui::Resizable

A panel group divided by draggable splitter handles.

## Poetry::Ui::ScrollArea

A bounded scroll region with themed scrollbars.

## Poetry::Ui::SearchField

A search input with a leading glyph and a clear affordance.

## Poetry::Ui::Select

A single-select dropdown field: a combobox trigger opening a popup listbox.

## Poetry::Ui::SensitiveInput

A secret field that stays masked until deliberately revealed.

## Poetry::Ui::Separator

A thin divider line between content regions.

## Poetry::Ui::Sheet

A modal panel that slides in from a screen edge.

## Poetry::Ui::Sidebar

The app-shell frame: collapsible navigation column plus content inset.

## Poetry::Ui::Skeleton

A pulsing placeholder box shown while content loads.

## Poetry::Ui::Slider

A draggable numeric value (or range) on a continuous track.

## Poetry::Ui::Spinner

A spinning loader glyph that announces itself to assistive tech.

## Poetry::Ui::Stat

One KPI: a labelled value with an optional sentiment-aware delta.

## Poetry::Ui::Switch

Instant-effect on/off controls.

## Poetry::Ui::Table

Semantic data tables composed from part helpers.

## Poetry::Ui::Tabs

Tabbed views switched by a tablist.

## Poetry::Ui::TagGroup

Removable-chip collections.

## Poetry::Ui::Textarea

Multiline free-text inputs.

## Poetry::Ui::ThemeFidelity

The theme transcription-fidelity contract: every way a ported theme's
cn-* rules differ from their source at the port pin is recorded - with
a reason - in config/theme_fidelity/deviations.yml, and the gate
(css:verify_fidelity) holds the two in exact two-way agreement:

  - a theme edit that changes the diff fails until the deviation is
    recorded (no undocumented drift lands), and
  - a recorded deviation that no longer exists fails as stale (a
    receipt cannot outlive the code it describes).

The source side is a frozen parse snapshot
(config/theme_fidelity/upstream-<pin>.json) generated once from the
pinned checkout via css:fidelity_snapshot - the pin never moves under
a release, so the snapshot is immutable. Bumping the pin is a
deliberate ceremony: regenerate the snapshot, re-review the new diff,
re-reason the deviations file.

### .current_diffs(root)

Every theme's diff against the committed snapshot.

### .diff(upstream, poetry)

The per-theme diff between a source parse and a poetry parse:
selectors only one side has, and per shared selector the dropped
(source-only) and added (poetry-only) utilities and raw
declarations. Selectors with no difference are omitted.

### .parse_css(text)

Parses a theme stylesheet into { selector => { "apply" => [utils],
"raw" => [declarations] } }. Handles both shapes: poetry's flat
themes/<t>.css and the source's .style-<t> { ... } wrapper. Only
cn-* selectors participate; comments are stripped; a .dark ancestor
scopes the key with a "[dark] " prefix so both sides stay keyed
identically.

### .snapshot_path(root)

The committed snapshot's path - exactly one may exist.

### .verify(root)

Exact two-way reconciliation against deviations.yml. Returns a list
of finding strings; empty means the contract holds.

### .write_snapshot(root, checkout:, pin:)

Writes the frozen source snapshot from a pinned checkout - the
pin-bump ceremony's first step.

## Poetry::Ui::Themes

The theme roster + per-theme DESIGN.md metadata.

Every poetry theme shares ONE token source (poetry-core's DTCG file) -
a theme is a component-treatment layer (themes/<name>.css), never a
palette. What varies per theme, and therefore what this module knows:
the treatment provenance line and the typography PAIRING - which is
app-level metadata, not CSS (no poetry theme moves a font
token; upstream's create flow biases lyra to JetBrains Mono with
radius none and pairs sera with Noto Serif / Instrument Serif; the
poetry docs render system stacks keyed off the same story).

### .design_md_exports(components_count:, generator: "bin/rake design:export_all")

theme name -> serialized DESIGN.md, for every shipped theme - the
single builder rake design:export_all, the drift gate, and the
tests all share.

### .details(name)

The DESIGN.md metadata for one theme; unknown names (customized
host bytes) fall back to CUSTOM_DETAILS.

### .names

The shipped roster, from the fragment files themselves.

## Poetry::Ui::TimeField

Segmented time-of-day editors.

## Poetry::Ui::Timeline

Dated event sequences as ordered lists.

## Poetry::Ui::Toast

Transient notifications.

## Poetry::Ui::ToastStreamActions

The canonical server-side toast (the Toast contract's
Rails-native path):

  turbo_stream.poetry_toast(title: "Saved", variant: :success)
  turbo_stream.poetry_toast(title: "Deleted") { |toast| toast.with_action { "Undo" } }

An append into the data-turbo-permanent #poetry-toaster region -
usable from controller responses, form streams, and
Turbo::StreamsChannel.broadcast_append_to in jobs. Mixed into
Turbo::Streams::TagBuilder via the :turbo_streams_tag_builder load
hook (poetry-ui does not depend on turbo-rails; hosts that have it
get the action automatically - see the engine initializer).

### #poetry_toast(title:, description: nil, target: Poetry::Ui::Toaster::Component::DEFAULT_ID, **)

## Poetry::Ui::ToastTrigger

Client-side toast delivery.

## Poetry::Ui::Toaster

The toast viewport.

## Poetry::Ui::Toggle

Pressed-state buttons.

## Poetry::Ui::ToggleGroup

Exclusive or multi-select toggle sets.

## Poetry::Ui::Toolbar

Single-Tab-stop control strips.

## Poetry::Ui::Tooltip

Hover/focus text hints.

## Poetry::Ui::Tree

Hierarchical expandable lists.

## Poetry::Ui::Typeset

Prose containers for rendered markdown.

## Poetry::Ui::Webmcp

The declarative WebMCP form contract: the attribute vocabulary
Chrome's declarative API registers straight from markup - a <form>
carrying `toolname` + `tooldescription` IS an agent-callable tool,
its controls are the parameters (the browser synthesizes the JSON
Schema from control types, `required`, and select options, reading
each parameter's description from the associated <label>), and
`toolparamdescription` overrides a label where the label alone is
not agent-sufficient. No JavaScript, inert markup without an agent.

Safety by construction: `toolautosubmit` (the agent submits without
the user pressing Submit) is only allowed on GET forms - read-only
lookups. A mutating form keeps the user in the loop, always.

Consumers: {ComponentsHelper#poetry_webmcp_form} (the form), the
FormBuilder's `tool_description:` field option (the override), and
poetry-agent's imperative runtime, which shares this vocabulary.

### .form_attributes(tool, method: nil)

The <form> attributes for a declarative tool.

### .param_attributes(description)

The control attribute for a parameter-description override.