# poetry-core API

The engine: the Component base class, the option / style / part / use_stimulus DSLs, the style dictionary, tokens, icons, and the registry.

## Poetry::Core::Component

Base component class for all Poetry components.

This class serves as the foundation for all Poetry components, providing:
- ActiveModel integration for attributes, assignment, and validations
- Style attribute management with variants and proc defaults
- HTML attribute handling and merging
- Translation and wrapping helpers
- Classname merging functionality
- Component metadata and identification methods

### .component_identifier

Returns the component identifier with path segments joined by double dashes.
Useful for CSS class names and HTML data attributes.

### .component_module

Returns the component module name without the "::Component" suffix.

### .component_path

Returns the component name in underscored path format.
Removes the "::Component" or "Component" suffix and converts to snake_case.

### .component_title

Returns the last segment of the component path as the title.

### .config

The current Poetry::Core configuration - a shortcut to
{Poetry::Core::Config.current} for components and their templates.

### .internal_component!

Marks this class (and its descendants) as an implementation
detail - full machinery, no registry entry.

### .required_content

The declared content-block hint, inherited like other DSL state.

### .requires_content(hint)

Declares that this component cannot render without a content block,
with a hint naming what the block is (Avatar: "the initials
fallback"). ONE declaration feeds both enforcement layers: the
component raises via #ensure_content! at render, and the registry
emits `requires_content` so poetry check flags the omission
statically.

### #attributes

Returns all component attributes, ensuring proc defaults are evaluated.

This method overrides ActiveModel's attributes method to trigger evaluation
of any proc-based default values that haven't been explicitly set.

### #classnames(*classnames)

Merges multiple class name values into a single string.

Uses the configured classname merger (typically Tailwind Merge) to
intelligently combine CSS class names, handling conflicts and duplicates.

### #component_data_attributes

The self-identification markup contract, the convention every
component follows: `data-component` on the component root maps live DOM
back to the component that rendered it - the hook agents, the
Verifier, and the browser-verification loop key on.

### #dom_id_token(value)

Reduces a value to a token safe for a DOM id and a CSS selector:
`[A-Za-z0-9_-]` only. A user-controlled id would otherwise break out
of the `<style>` block or the id attribute it is interpolated into.
Returns nil when nothing safe remains (callers fall back to a random
token), preserving the id attribute / JS-selector match by using the
same reduced value on both sides.

### #ensure_content!

Enforces the class-level requires_content declaration - call from
before_render. The message is built from the declaration so the
runtime raise and the registry's static contract can never disagree.

### #html_attributes

Returns HTML attributes with merged CSS classes.

Combines the component's CSS classes (from the `css` method) with any
additional classes passed via the `:class` HTML attribute.

### #initialize(attributes = {})

Initializes a new component instance with the given attributes.

This method:
1. Initializes the registered_styles set for tracking explicitly set attributes
2. Marks style attributes with static defaults as initialized
3. Tracks which style attributes are being explicitly initialized via parameters
4. Separates component attributes from HTML attributes
5. Assigns the component attributes to their respective instance variables

The base component intentionally does not chain to ViewComponent::Base#initialize:
it fully manages its own ActiveModel-backed attribute setup.

### #persisted?

Indicates whether this component instance is persisted.
Always returns false as components are not persisted entities.

### #poetry_instance_id(prefix)

The instance-id ladder: an explicit caller root id wins; a key:
derives a stable component-namespaced token (Turbo morph pairs it
across renders, cached fragments stay composable); otherwise
random - unkeyed components over-replace under morph, they never
falsely retain. Call sites memoize (`@instance_id ||=`); this
stays pure.

### #script_json(json)

HTML-safe JSON for embedding in a `<script type="application/json">`
data island. Escapes the script-terminating characters (`<` `>` `&`,
plus the JS line separators U+2028/U+2029) to their JSON `\uXXXX`
forms, INDEPENDENT of the host's
`ActiveSupport.escape_html_entities_in_json` setting: that flag
defaults to true (which escapes them for us) but is host-overridable
to false (legitimately, e.g. in API apps), and a component must not
depend on a global it neither sets nor checks. `</script>` closes a
script element regardless of its `type`, so an unescaped value
carrying it would break out of the island into live HTML. Idempotent
when the host already escapes (the `\uXXXX` forms carry no literal
`<`/`>`/`&`), and JSON.parse decodes the escapes back to the original
text. Accepts a pre-serialized JSON string or any `to_json`-able object.

### #slot_data_attributes(part)

`data-slot` for a named part of the component's anatomy
(skeleton parts carry their role: icon, label, spinner, ...).

### #stable_key

The caller-supplied semantic identity (key:), if any.

### #to_html

Renders the component to an HTML string.

Creates a minimal controller and view context to render the component
outside of a normal request cycle. Useful for testing and debugging.

## Poetry::Core::Concerns::Options

The Options concern provides a DSL for defining typed attributes in components.
It extends the basic attribute functionality with support for ActiveModel types,
proc defaults, and tracking of which attributes have been explicitly set vs using defaults.

The registration, tracking, and hierarchy machinery lives in
DeclaredAttributes (shared with Styles); this concern owns the
option-specific surface: ActiveModel types and value formats.

Unlike Styles, Options:
- Do not have variants
- Do not generate CSS
- Support all ActiveModel types (string, integer, boolean, float, etc.)

### .has_option_attribute?(name)

Checks if the given name is a defined option attribute.

### .option(name, type, **options)

Defines an option attribute for the component.

### .option_attributes

Returns all option attributes defined on this component and its ancestors.

### .option_attributes_with_defaults

Returns all option attributes that have default values (static or proc).

### .option_attributes_with_proc_defaults

Returns option attributes that have proc default values.
Proc defaults allow dynamic defaults that can reference other attributes.

### .option_attributes_with_static_defaults

Returns option attributes that have static (non-proc) default values.

### .option_docs

The doc: strings declared on this component's options,
hierarchy-wide (nearest declaration wins).

### .option_format(name)

Returns the declared value format for a given option attribute
(e.g. :"icon-name") - the machine-checkable value contract the
registry and poetry check read. Nil when the option is free-form.

### .option_type(name)

Returns the type for a given option attribute.

### #initialized_option_attributes

Returns only the option attributes that have been explicitly set (not using defaults).

### #option_attribute?(name)

Checks if the given attribute is an option attribute.

### #option_attribute_initialized?(name)

Checks if an option attribute has been explicitly initialized.

### #option_attributes

Returns all option attributes defined on this component's class.

### #option_attributes_status

Returns all option attributes with their initialization status.

### #options

Returns a hash of all option attributes with their current values.

## Poetry::Core::Concerns::Styles

The Styles concern provides a powerful DSL for defining style attributes in components.
It extends the basic attribute functionality with support for variants, proc defaults,
and tracking of which attributes have been explicitly set vs using defaults.

The registration, tracking, and hierarchy machinery lives in
DeclaredAttributes (shared with Options); this concern owns the
style-specific surface: variants, inclusion validation, and CSS
emission.

### .has_style_attribute?(name)

Checks if the given name is a defined style attribute.

### .style(name, **options)

Defines a style attribute for the component.

### .style_attributes

Returns all style attributes defined on this component and its ancestors.

### .style_attributes_with_defaults

Returns all style attributes that have default values (static or proc).

### .style_attributes_with_proc_defaults

Returns style attributes that have proc default values.
Proc defaults allow dynamic defaults that can reference other attributes.

### .style_attributes_with_static_defaults

Returns style attributes that have static (non-proc) default values.

### .style_class

Automatically determines the corresponding Style class for this component.
Example: Poetry::Core::Dot::Component -> Poetry::Core::Dot::Style

### .style_docs

The doc: strings declared on this component's styles,
hierarchy-wide (nearest declaration wins).

### #bem(element = nil, **overrides)

The BEM token IR for this component (the pipeline's Step 2): the
block class plus one modifier class per style value - symbols as
`block--attr-value`, booleans as presence modifiers (`block--attr`).
A named element returns `block__element`.

### #bem_block

The component's BEM block name - the stable, framework-agnostic
class contract of the token IR ("poetry/core/dot" -> "poetry-core-dot").

### #css(element = nil, **options, &)

Generates CSS classes based on style attributes and additional options.

The emission is governed by `css_mode`: `:tailwind` (default)
resolves the style values to utility classes through the sidecar
Style dictionary; `:bem` emits the stable BEM token IR instead, for
hosts that bring their own CSS (styled against the generated
reference stylesheet). Override per call with `css_mode:`, or
globally via `Poetry::Core::Config.current.css_mode`.

### #initialized_style_attributes

Returns only the style attributes that have been explicitly set (not using defaults).

### #style_attribute?(name)

Checks if the given attribute is a style attribute.

### #style_attribute_initialized?(name)

Checks if a style attribute has been explicitly initialized.

### #style_attributes

Returns all style attributes defined on this component's class.

### #style_attributes_status

Returns all style attributes with their initialization status.

### #styler

Returns the style class for this component.

### #styles

Returns a hash of all style attributes with their current values.

## Poetry::Core::Concerns::Parts

The part contract: a
hand-authored, machine-verified declaration of the component's
styling surface - the data-slot parts its DOM exposes, the state
attributes each part carries (and when), and the CSS custom
properties that seam the part to themes and controllers.

  part "dialog-content",
       "The <dialog> panel - the positioning and animation surface",
       states: {
         "data-open" => "panel is open (setState pairs it with data-closed)",
         "data-closed" => "panel is closed or animating out"
       }

Binding such a contract with types alone would keep the keys from
drifting while leaving every description and condition as
unverified prose. poetry binds the declaration to RENDERED DOM
instead - PartContract.verify reconciles it against every preview
in both directions (rendered-but-undeclared, declared-but-never-
rendered), so the published contract cannot lie about the anatomy.

Declarations are OWN-CLASS ONLY, deliberately not inherited: Sheet
and Drawer subclass Dialog::Component yet share none of its part
names (sheet-content vs dialog-content) - inheritance would leak
phantom parts into every subclass with renamed anatomy.

### .part(name, description, states: {}, vars: {})

Declares one part of the component's rendered anatomy.

### .part_definitions

The declared contract, registry-shaped (plain string keys, so
the YAML round-trips byte-identical). Own-class only - see the
module docs for why subclasses never inherit anatomy.

## Poetry::Core::Concerns::Stimulus

The use_stimulus contract: a class-level, element-major declaration
of the component's Stimulus wiring, replacing both the hand-rolled
`<element>_stimulus_attributes` methods and the previous
`stimulated_with` DSL (controller-major, root-only - it could not
express multi-element wiring, and no component ever adopted it).

  use_stimulus do
    on :root do
      controller :hover_card do
        register
        value :open
        value :open_delay, unless: -> { open_delay.nil? }
      end
      controller :popper do
        register
        value :side
      end
    end
    on :trigger do
      controller :hover_card do
        action :pointer_enter, on: :pointerenter
      end
      controller :popper do
        target :anchor
      end
    end
  end

Render side: `stimulus_attributes_for(:trigger)` returns the
element's merged attribute hash (public - templates and slot
lambdas call it directly); `stimulus_action(:open)` /
`stimulus_event(:change)` build validated descriptor strings for
forwarding; `stimulus_attributes(:a, :b) { |a, b| ... }` is the
escape hatch for wiring too dynamic to declare - every builder
shares ONE Attributes instance, so multi-controller merges are
correct by construction.

Declarations validate against the controllers manifest at CLASS
LOAD (unknown controller/value/action/target/event raises at boot,
not first render) and are declared once per element: a subclass
redeclaring an element REPLACES it wholesale (the Ruby-override
intuition; Sheet/Drawer re-controller their roots this way), while
`on :root, extend: true` merges into the inherited element
(date_field -> time_field adds values). Untouched elements inherit.

### .own_stimulus_elements

This class's own declarations, element name -> Element.

### .resolve_stimulus_identifier(identifier)

Resolves a declaration-style identifier (Symbol suffix, String,
or Array) to its full manifest identifier - see
{Poetry::Core::Stimulus::Declarations.resolve_identifier}.

### .stimulus_action(*args, on: nil, at: nil)

Descriptor builders are STATIC facts of the declarations, so
they exist at class level - helpers, generator templates, and
test selectors consume them without an instance. on:/at: build
the evented token ("click->id#method"); without on: the bare
descriptor (element-default event).

### .stimulus_definitions

The registry-shaped projection of the RESOLVED wiring (post-
inheritance, so Sheet publishes sheet controllers) - plain data
for the registry, skill text, and docs tables.

### .stimulus_elements

The effective wiring after inheritance: walk the superclass
chain root-first, folding each class's declarations over the
inherited set - redeclared elements replace wholesale unless
declared with extend: true, which appends to the inherited
element's wirings.

### .stimulus_event(*args)

A validated event-name string for listening markup;
stimulus_event(:change) resolves across the declared
controllers, stimulus_event(:controller, :change) pins one.

### .stimulus_identifiers

Every controller identifier declared anywhere on the class, in
declaration order - the search space for unqualified
stimulus_action / stimulus_event resolution.

### .use_stimulus(&block)

Declares (part of) the component's stimulus wiring. Multiple
blocks compose additively within a class; shared wiring modules
call this from their `included` hook.

### #stimulus_action(*, on: nil, at: nil)

Delegates to the class-level builder (descriptors are static
facts of the declarations); stimulus_action(:open) resolves
across declared controllers, on:/at: build the evented token.

### #stimulus_attributes(*controllers)

The escape hatch for wiring too dynamic to declare: yields one
Builder per controller, all sharing ONE Attributes instance.
Controllers resolve like declarations (Symbol -> manifest,
String/Array -> verbatim).

### #stimulus_attributes_for(element_name)

The declared wiring for one element as a plain attributes hash,
ready to merge into the element's tag or forward as component
kwargs. Public by design - templates call it, ending the
`public :inner_stimulus_attributes` juggling.

### #stimulus_event(*)

Delegates to the class-level builder: stimulus_event(:change)
resolves across the declared controllers,
stimulus_event(:controller, :change) pins one.

## Poetry::Core::Style

The sidecar style class for a component: the dictionary from the
component's style surface (declared with `style :attr, variants:` on the
component) to CSS utility classes, resolved through the in-tree
{CSS::Resolver}.

Defaults are NOT declared here - they live in exactly one place, the
component's `style :attr, default:` (the single source of truth; the
component's ActiveModel attributes resolve them before render). A
`defaults` call raises to enforce that.

### .base(classes)

Declares the root element's always-present utility classes - the
dictionary's base layer, emitted before any variant classes.

### .bem_block

The BEM block this dictionary belongs to, derived from the sibling
component ("poetry/core/dot" -> "poetry-core-dot").

### .capsule

The capsule digest of this dictionary (the :bem leak-guard).

### .component_class

The sibling component class by convention
(Poetry::Core::Dot::Style -> Poetry::Core::Dot::Component).

### .compound(criteria, classes)

Declares classes emitted only when EVERY criteria pair matches the
resolved style values - the cross-axis refinement a single
variant cannot express.

### .css(element = nil, **options)

Resolves utility classes for the given criteria (and optional
element). The `class:` option appends caller classes, which win on
Tailwind conflicts.

### .defaults(*)

Single-source-of-defaults enforcement: defaults belong on the
component (`style :attr, default:`), never in the style dictionary.

### .element(name, classes)

Declares the classes of a named inner element of the component's
anatomy, resolved with `css(:name)` at render.

### .resolver

Each Style class owns a resolver; subclasses extend a copy of their
parent's dictionary.

### .variant(attr, mapping)

Declares one style axis: the classes each value of the component's
matching `style :attr` declaration resolves to.

### .variant_options

{ attr => [values] } - the declared variant space, for previews,
docs, and the registry.

### #css(...)

Instance-level mirror so `styler.css(...)` keeps working from the
Styles concern.

## Poetry::Core::Stimulus::Declarations

The use_stimulus declaration model (the DSL behind
Concerns::Stimulus). Declarations build at class-load time and are
validated against the controllers manifest THERE - an unknown
controller, value, action method, target, or event name raises at
boot/test-collection instead of first render. The Builder still
guards emission for wiring built outside declarations.

Element-major by design: every controller wired to one element
builds into ONE HTML::Attributes instance at render, so a plain
Hash#merge of two builds can never clobber data-controller -
correct by construction, not by convention.

See {Poetry::Core::Concerns::Stimulus} for the component-facing
contract and a full declaration example.

### .event_name(controller, name)

A validated cross-controller event name for action `on:` sources,
resolved to the manifest's REAL emitted name ("poetry:calendar:change";
layer controllers keep the identifier prefix) - ends the hand-written
string seam between dispatching and listening controllers.

### .resolve_identifier(identifier)

Symbols resolve against the manifest by unique suffix
(:hover_card -> "poetry--core--hover-card"), so declarations
never hand-write gem namespaces; strings and arrays pass through
the Builder's existing policy (strict for poetry--*, unvalidated
for host-app controllers).

## Poetry::Core::Tokens

The canonical design-token model. Loads tokens/tokens.dtcg.json -
the single source of truth - and exposes the semantic color roles per
mode plus the radius dimension. Everything else (tokens.css,
tailwind-theme.css, the DESIGN.md front matter) is generated from an
instance of this class; the AAA-contrast gate asserts against it.

### .default_path

The gem's canonical token file path ({DEFAULT_RELATIVE_PATH}
under the gem root).

### .load(path = default_path)

Loads a DTCG token file into a Tokens instance.

### #color(mode, name)

The color of one semantic role in one mode.

### #color_names(mode)

The semantic color-role names of one mode, in file order.

### #data

Returns the value of attribute data.

### #initialize(data)

Wraps one parsed DTCG document; {.load} is the file-backed form.

### #modes

Mode names ("light", "dark"), skipping DTCG $-metadata keys.

### #radius_css

The radius dimension as CSS ("0.625rem").

## Poetry::Core::Icons

The pluggable icon-set registry (Lucide default, per-set
adapters). An icon set is anything responding to #include?(name),
#fetch(name) -> inner SVG markup, and #names. Sets register themselves
on require (poetry-lucide does); the active set is selected by
`config.icon_library` and can be overridden per render.

SECURITY: #fetch's return value is rendered `html_safe` by the Icon
component. The shipped sets vendor SVGs sanitized AT VENDOR TIME
(poetry-lucide's fetch script strips <script>/<foreignObject>/handlers
/external-href <use>/<image>), so render never parses untrusted markup.
A custom set registered by a host MUST pre-sanitize its SVGs the same
way - the vendored pipeline is the reference; a set that serves raw,
attacker-influenced SVG is an XSS sink.

### .register(key, set)

Registers an icon set under a library key - the extension point
an icon gem calls on require. A set is any object responding to
`#include?(name)`, `#fetch(name)` (returning inner SVG markup),
and `#names`. The set contract: `#fetch` raises
{Poetry::Core::IconNotFound} for any name it cannot serve - the
Icon component's missing-icon policy rescues exactly that class.
See the SECURITY note above for the pre-sanitization requirement
on custom sets.

### .registry

The registered icon sets, library key => set object. Icon gems
add themselves here on require.

### .set(library = nil)

The set for the given library key, defaulting to
config.icon_library. Raises with the fix when unregistered.

### .suggest(name, names)

Did-you-mean for icon names. The reversed-compound form is
checked before edit distance: Lucide v1 swapped modifier and noun
(alert-circle -> circle-alert, x-circle -> circle-x), a rename class
DidYouMean's checker misses every time - the reversal IS the fix.

## Poetry::Core::Registry

The generated component registry: the machine-readable index of
every component's public surface, built entirely FROM SOURCE - the
prop_definitions introspection shim + the Style dictionaries -
and CI-verified against a fresh build (a committed registry that
can never drift from the code).

Consumers: the docs site, the MCP server, the agent skill, and
the A2UI / WebMCP projections - one contract, every surface.

### .committed(root)

Loads the committed registry YAML boot-free.

### #blocks

The block catalog and the root template paths resolve against -
LlmsText reads both to inline block source into llms-full.txt.

### #components

The discovered component classes (the registry's working set).

### #entries

The full registry payload's "components" section: one contract hash
per discovered component, keyed by component path.

### #form_builder

The FormBuilder surface (optional section) - LlmsText renders it as
the Forms section.

### #generate!(root: @source_root)

Writes the registry YAML to its committed location under root.

### #initialize(components: nil, source_root: Poetry::Core.root, # rubocop:disable Metrics/ParameterLists helpers: nil, blocks: nil, helper_args: nil, descriptions: nil, form_builder: nil)

### #source_root

The block catalog and the root template paths resolve against -
LlmsText reads both to inline block source into llms-full.txt.

### #to_yaml

The complete registry serialized as plain-data YAML (components plus
the optional helpers/blocks/helper_args/form_builder sections),
headed by the do-not-edit banner.

### #verified?(root: @source_root)

False when the committed registry does not match a fresh build.

## ActiveModel

Reopened to register poetry's option types alongside Rails' own.

## ActiveModel::Type

Reopened: poetry registers its list (and symbol) attribute types here
so the option/style DSLs can declare them.

## ActiveModel::Type::List

A string-array option type (Accordion's open:, future multi-selects).
Casts scalars to one-element arrays and stringifies members, so
open: :shipping and open: %w[a b] both normalize.

### #cast(value)

Normalizes any value to an array of strings: nil becomes [], a
scalar becomes a one-element array, and members are stringified.

### #type

Reports :list to introspection (prop_definitions, the registry).

## ActiveModel::Type::Symbol

A symbol option type for the option DSL: casts anything responding
to #to_sym and reports :symbol to introspection.

Strict by design: a value that cannot become a Symbol raises at
assignment - poetry surfaces bad option values where they are
written, not as a validation error nothing reads at render.
Leniency is List's job, not this type's.

### #cast(value)

Casts a value to a Symbol; nil stays nil.

### #type

Without this the type reports :string (inherited) - introspection
(prop_definitions, the registry) must see :symbol.

## Poetry

Poetry::Core module provides extensions and enhancements for Rails views and components.

## Poetry::Core

The framework layer of poetry: the Rails engine, the component DSL, and the
shared primitives. Concrete components live in poetry-ui.

### .loader

The dedicated Zeitwerk loader for poetry-core's lib/ tree.

### .root

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

## Poetry::Core::Box

Namespace of the Box primitive.

## Poetry::Core::Box::Component

The polymorphic-tag primitive: one element, any tag, the full
poetry attribute machinery. Box exists for the bare styled
element a real component would be too much for - a spacer, a
semantic wrapper, a grid cell - while keeping the class merger,
Stimulus `data:` merging, and self-identification that a raw
`content_tag` skips. Void elements (`br`, `hr`, `img`, ...)
self-close and take no content block.

**Anatomy** (`data-slot` parts):

- `box` - The rendered element itself - the whole component is one part

### #call

Emits the chosen tag: self-closing for void elements, wrapping
the content block otherwise. A void element with a content
block raises - the content would be silently unrenderable.

### #

The element to render ('div', 'section', 'span', ...); void elements (br, hr, img, ...) self-close and take no content block.

Constructor option `html_tag:`.

## Poetry::Core::CSS

The CSS tooling namespace: class-name merging and the compiled-build gates.

## Poetry::Core::CSS::BemMerger

The BEM-mode classname merger: the classname_merger a host pairs
with `css_mode = :bem`. Same contract as {TailwindMerger}
(flatten, stringify, drop blanks, nil for empty input) with
exactly two behaviors on top: order-preserving token dedupe and
a space join - no Tailwind conflict semantics applied to a BEM
host's classes.

Deliberately NO modifier-axis conflict resolution: the BEM
modifier grammar is dictionary-dependent at the string level
(values carry dashes - `--align-inline-start` - and names carry
underscores), so a string merger guessing axes would be wrong.
Style axes are driven through component options; conflicts
between raw caller classes belong to the host stylesheet's
cascade.

### #merge(*classes)

Merges class lists BEM-style: normalize, dedupe at the token
level (first occurrence keeps its position), join.

## Poetry::Core::Concerns

The concerns composed into {Poetry::Core::Component}: styles, options,
slots, Stimulus wiring, introspection, and part declarations.

## Poetry::Core::Concerns::AgentTools

The agent-tool contract: class-level declarations of the
component's agent-callable tools - the "operate" projection of the
component's Stimulus surface. A tool names one action an in-page
agent may invoke on a rendered instance (WebMCP's
`document.modelContext`, or any client that reads the registry),
described in MCP `Tool` shape: name, description, JSON-Schema
input, and safety annotations.

  tool :set_value,
       description: "Select the option whose value matches.",
       params: { value: { type: "string", required: true,
                          description: "The option value to select." } },
       executes: :set_value,
       mutating: true

Declarations are validated at CLASS LOAD, like use_stimulus:
`executes:` resolves through {Concerns::Stimulus#stimulus_action}
against the component's declared controllers and the controllers
manifest, so a tool can never name an action the JS does not
define - declare `use_stimulus` before `tool`. The resolved
descriptor ("poetry--core--combobox#setValue") is the tool's wire
form: registration runtimes dispatch it verbatim.

Safety doctrine (non-negotiable): tools are read-only unless
declared `mutating: true` (`annotations.readOnlyHint` inverts it),
`untrusted_content: true` marks tools whose output carries
user-authored content, and declaring a tool exposes NOTHING by
itself - emission is opt-in per rendered instance, owned by the
registration runtime, never default-on.

Projection: {ClassMethods#tool_definitions} feeds the registry's
per-component `tools` section (plain strings, YAML-safe), which
the agent surfaces (llms.txt, MCP server, skills, docs) and the
WebMCP registration payload all read - one contract, every
surface.

### .own_tools

This class's own tool declarations, name => {Tool}.

### .resolve_tool_action(tool)

The bare Stimulus descriptor a tool dispatches on THIS class.

### .tool(name, description:, executes:, # rubocop:disable Metrics/ParameterLists -- one keyword per Tool field params: nil, input_schema: nil, title: nil, mutating: false, untrusted_content: false)

Declares one agent-callable tool of this component.

### .tool_definitions

The registry-shaped projection of the resolved tools: MCP
`Tool` fields (name / title / description / inputSchema /
annotations) plus the `executes` dispatch descriptor - plain
string keys, YAML round-trippable.

`executes` resolves HERE, against the projecting class: a
subclass that re-controllers its root (Sheet over Dialog)
projects its own controller's descriptor for an inherited
bare `executes: :open`, and a pinned controller the subclass
no longer wires raises instead of projecting a descriptor the
rendered DOM cannot dispatch.

### .tools

The effective tools after inheritance: superclass chain
root-first, a subclass redeclaring a name replaces it.

### #webmcp_enabled?

Whether this rendered instance opted into WebMCP registration
(`webmcp: true` or `webmcp: "name"` on the call).

### #webmcp_name

The instance name the registrar composes tool names from
(`poetry.{name}.{tool}`): the explicit `webmcp:` string, else the
component title (give instances explicit names when a page
renders several of one component - the browser rejects
duplicate tool names).

### #webmcp_tool_definition(definition)

The per-instance enrichment hook - override to refine a tool's
projected definition with what only the rendered instance knows.
Must return a JSON-serializable Hash with the same keys.

### #webmcp_tools

The instance's registration payload: its resolved tool
definitions, each passed through {#webmcp_tool_definition} so a
component can enrich a schema with rendered facts (Tabs adds the
rendered tab values as an enum).

## Poetry::Core::Concerns::AgentTools::Tool

One declared tool. `executes` is the declared action spec
(`[method]` or `[controller, method]`) - resolved to a bare
Stimulus descriptor per PROJECTING class (see
{ClassMethods#tool_definitions}); `input_schema` is the
normalized JSON-Schema object (string keys) or nil for a
parameterless tool.

### #description

Returns the value of attribute description

### #description=(value)

Sets the attribute description

### #executes

Returns the value of attribute executes

### #executes=(value)

Sets the attribute executes

### #input_schema

Returns the value of attribute input_schema

### #input_schema=(value)

Sets the attribute input_schema

### #mutating

Returns the value of attribute mutating

### #mutating=(value)

Sets the attribute mutating

### #name

Returns the value of attribute name

### #name=(value)

Sets the attribute name

### #title

Returns the value of attribute title

### #title=(value)

Sets the attribute title

### #untrusted_content

Returns the value of attribute untrusted_content

### #untrusted_content=(value)

Sets the attribute untrusted_content

## Poetry::Core::Concerns::AgentTools::ToolError

Raised at class load for an invalid tool declaration.

## Poetry::Core::Concerns::DeclaredAttributes

The declared-attribute engine shared by the Styles and Options DSLs.
Each DSL is one "kind" of declared attribute: registration lands in
per-kind class-level collections (@_<kind>_attributes,
@_<kind>_attributes_with_defaults, @_<kind>_proc_defaults), explicit
initialization is tracked per instance through the registered_<kind>s
class_attribute the owning concern declares, and hierarchy-wide
queries walk the ancestry up to the base component class.
Kind-specific surface - variants and CSS emission for styles,
ActiveModel types and value formats for options - stays in the
owning concern.

## Poetry::Core::Concerns::Introspection

The prop-introspection shim: a machine-readable
description of a component's public surface - style attributes,
options, and slots - derived from the metadata the Styles/Options
DSLs and ViewComponent already carry. This is the single source the
generated registry, the docs tables, and the MCP prop schema are
built from; nothing here is hand-authored.

### .hand_rolled_setters(klass, definitions)

Every own with_* method that is neither a slot-generated setter
(with_<name>/<singular>/<type> and their _content twins) nor
inherited - NavigationMenu#with_link, PieChart's with_py.

### .prop_definitions

The component's full declared surface - styles, options, slots
(with descriptions), required slots, and requires_any groups -
as the registry generator serializes it.

### .renders_many(slot_name, callable = nil, **opts)

ViewComponent's renders_many with the same keyword surface as
{renders_one}; doc: describes the collection contract and is
keyed by the plural declared name, exactly like {slot_doc}.

### .renders_one(slot_name, callable = nil, **opts)

ViewComponent's renders_one, with the doc riding the
declaration: doc: is lifted into {slot_doc}, renders: passes
the callable as a keyword so the doc can come first, and the
polymorphic types: form is re-formed into the positional hash
ViewComponent expects - the ViewComponent surface underneath
is unchanged. Unknown keywords raise at class load, and a
positional callable cannot be combined with renders:/types:.

### .required_slots_surface(klass, definitions)

The validated REQUIRED_SLOTS declaration of a slot-owning class:
each key must name a setter the given slot definitions actually
generate (the slot itself, a collection's singular, or a
polymorphic type).

### .requires_any_surface(klass, definitions)

The validated REQUIRES_ANY declaration: each group needs
a hint plus at least one alternative, and slot alternatives must
name setters the definitions actually generate.

### .slot_doc(name, text)

The component's full prop surface.

Documents a slot declared with renders_one/renders_many. The
string travels the same road as option/style doc: params - the
registry, the agent surface, and the generated API docs.
Prefer the doc: keyword on the declaration itself (it lands
here); call slot_doc directly only when the doc and the
declaration live in different modules.

### .slot_docs

The slot_doc strings, hierarchy-wide (nearest wins).

### .slot_surface(klass, seen: [])

The registry-shaped slot contracts of one slot-owning class -
see the walker notes above for every emitted key.

## Poetry::Core::Config

Manages configuration settings for the Poetry::Core module.

This class provides a flexible configuration system using ActiveSupport::OrderedOptions
under the hood, allowing access to configuration values using either hash-style or
method-style syntax. It supports both a singleton pattern via {.current} for global
configuration and the ability to create custom configuration instances.

The configuration system is designed to be easily extensible while providing sensible
defaults for all Poetry::Core components.

### .current

Returns the global singleton configuration instance.

This method provides access to the shared configuration used throughout the
application. The instance is created lazily on first access and persists for
the lifetime of the application.

### .default

Creates a new configuration instance with default settings.

This is aliased from the standard {#initialize} method to provide a more
semantic way to create default configurations.

### .defaults

Returns the default configuration values.

These defaults are used when initializing new configuration instances and
define the standard behavior for all Poetry::Core components.

The keys and their defaults:

- `classname_merger` ({Poetry::Core::CSS::TailwindMerger}) - resolves
  conflicting utility classes when caller classes meet component
  classes.
- `stimulus_merger` ({Poetry::Core::Stimulus::Merger}) - combines
  Stimulus data attributes without duplicating controllers or
  actions.
- `css_mode` (`:tailwind`) - `:tailwind` emits resolved utility
  classes; `:bem` emits the BEM token IR for bring-your-own-CSS
  hosts.
- `icon_library` (`:lucide`) - the active icon set, by the key it
  registered under ({Poetry::Core::Icons.register}).
- `raise_on_missing_icon` (`nil`) - the policy for a dynamic icon
  name that resolves to nothing: nil raises in local environments
  and degrades to the fallback elsewhere; true/false force one
  behavior.
- `icon_fallback` (`:"circle-question-mark"`) - rendered instead
  of a missing icon when not raising; nil re-raises.
- `on_missing_icon` (`nil`) - an optional callable
  `(name:, library:, error:)` fired before the fallback renders.
- `webmcp_registration_budget` (`20`) - the per-document cap on
  WebMCP tool registrations a page's opted-in instances may make;
  poetry-agent's `registration_budget` setting writes through.
- `stable_id_mode` (`:off`) - the opt-in `:sequence` mode seeds a
  per-request deterministic id sequence (read the hazards in
  StableId before enabling).
- `stable_id_seed` - the request-to-seed callable for that mode
  (defaults to the request path).

### #classname_merger

The merger that resolves conflicting utility classes when caller
classes meet component classes.

### #classname_merger=(merger)

Replaces the class merger.

### #css_mode

The class emission mode: `:tailwind` resolves style values to
utility classes, `:bem` emits the BEM token IR.

### #css_mode=(mode)

Sets the class emission mode.

### #icon_fallback

The icon rendered instead of a missing one when not raising; nil
re-raises. Must exist in every registered set.

### #icon_fallback=(name)

Sets the fallback icon.

### #icon_library

The key of the active icon set ({Poetry::Core::Icons.register}).

### #icon_library=(key)

Selects the active icon set.

### #initialize

Initializes a new configuration instance with default values.

The new instance gets a clone of the default configuration, ensuring each
configuration object is independent and modifications won't affect the defaults
or other instances.

### #on_missing_icon

The instrumentation hook fired before a fallback icon renders,
called with `name:`, `library:`, and `error:`.

### #on_missing_icon=(callable)

Sets the missing-icon hook.

### #raise_on_missing_icon

The policy for a dynamic icon name that resolves to nothing: nil
raises in local environments and degrades to the fallback
elsewhere; true/false force one behavior.

### #raise_on_missing_icon=(policy)

Sets the missing-icon policy.

### #stable_id_mode

The StableId sequence mode: `:off`, or the opt-in `:sequence`.

### #stable_id_mode=(mode)

Sets the StableId sequence mode.

### #stable_id_seed

The request-to-seed callable the `:sequence` mode derives its
per-request id sequence from.

### #stable_id_seed=(callable)

Sets the seed callable.

### #stimulus_merger

The merger that combines Stimulus data attributes without
duplicating controllers or actions.

### #stimulus_merger=(merger)

Replaces the Stimulus attribute merger.

### #webmcp_registration_budget

The per-document WebMCP registration budget the registrar
enforces (default 20).

### #webmcp_registration_budget=(count)

Sets the per-document WebMCP registration budget.

## Poetry::Core::Contrib

Opt-in helper mixins shipped alongside the core.

## Poetry::Core::Contrib::WrappedHelper

Provides a convenient method to wrap components with custom HTML code.
Adapted from an MIT-licensed source (source and license in
THIRD_PARTY_NOTICES.md).

This module adds the `#wrapped` method to components, allowing them to be
easily wrapped with a {Poetry::Core::Wrapper::Component}. The wrapper component
enables adding custom HTML around a component without modifying the
component itself, and respects the component's `render?` conditional logic.

### #wrapped

Wraps the current component instance in a {Poetry::Core::Wrapper::Component}.

This creates a wrapper that can be rendered with custom HTML surrounding
the component. The wrapper respects the wrapped component's `render?`
method, only rendering if it returns true.

## Poetry::Core::Engine

The Rails engine: wires the component autoload paths, previews,
importmap pins, asset paths, and the StableId / TagHelper mixins into
a host app at boot.

## Poetry::Core::Error

Base error class for all Poetry::Core-related errors.

All custom errors in the Poetry::Core module inherit from this class, which in turn
inherits from Ruby's StandardError. This provides a common ancestor for
rescuing all Poetry::Core-specific exceptions.

## Poetry::Core::HTML

HTML attribute plumbing: the merge-aware attributes hash.

## Poetry::Core::HTML::Attributes

A specialized hash for managing HTML attributes with intelligent merging capabilities.

This class extends ActiveSupport::HashWithIndifferentAccess to provide enhanced
functionality for handling HTML attributes, particularly CSS classes, Stimulus
controllers/actions, data attributes, and ARIA attributes.

Features:
- Smart merging of CSS classes without duplication
- Intelligent merging of Stimulus controllers and actions
- Automatic flattening of nested data and aria attributes
- Proper handling of HTML boolean attributes
- Both mutating (!) and non-mutating versions of merge methods

### .merged(*hashes)

The safe way to combine component wiring with caller-supplied
attributes into a plain hash for content_tag / button_to: every
hash flows through one Attributes instance, so stimulus keys
(data-controller / data-action, either spelling) concatenate
instead of clobbering, classes tailwind-merge, and to_attributes
unifies double-spelled slots deterministically. Plain Hash#merge
of wiring with caller options silently drops one side's wiring -
never do that; call this.

### #get_attribute(key, nested_key = nil)

Gets an attribute value, handling both flat and nested formats.

### #has_attribute?(key, nested_key = nil)

Checks if an attribute is set, handling both flat and nested formats.

This method is smart about data and aria attributes, checking both
the nested and flat formats.

### #merge_classes(*classnames)

Merges CSS classes into the attributes, returning a new instance.

This non-mutating method creates a deep copy of the attributes and merges
the provided classnames intelligently, avoiding duplicates and handling
conditional classes.

### #merge_classes!(*classnames)

Merges CSS classes into the attributes, mutating the current instance.

This mutating method modifies the current attributes object by merging
the provided classnames.

### #merge_if_not_set(other_hash)

Merges attributes only if they are not already set, returning a new instance.

This method intelligently merges attributes by only adding keys that don't exist
in the current attributes. It handles both flat and nested data attributes:
- "data-controller" and data: { controller: "..." } are treated as the same
- "aria-label" and aria: { label: "..." } are treated as the same

### #merge_if_not_set!(other_hash)

Merges attributes only if they are not already set, mutating the current instance.

### #merge_stimulus(*stimulus_hash, &)

Merges Stimulus data attributes, returning a new instance.

This method intelligently merges data attributes, handling controllers,
actions, and other data attributes appropriately.

### #merge_stimulus!(*stimulus_hash, &)

Merges Stimulus data attributes, mutating the current instance.

### #merge_stimulus_actions(*actions)

Merges Stimulus actions into the data-action attribute, returning a new instance.

### #merge_stimulus_actions!(*actions)

Merges Stimulus actions into the data-action attribute, mutating the current instance.

### #merge_stimulus_controllers(*controllers)

Merges Stimulus controllers into the data-controller attribute, returning a new instance.

### #merge_stimulus_controllers!(*controllers)

Merges Stimulus controllers into the data-controller attribute, mutating the current instance.

### #to_attributes

Converts the attributes hash to a flat hash suitable for HTML rendering.

This method performs several transformations:
- Flattens nested data attributes (data: { id: 1 } => "data-id" => "1")
- Flattens nested aria attributes (aria: { label: "Close" } => "aria-label" => "Close")
- Handles boolean attributes (disabled: true => "disabled" => "disabled")
- Skips nil values for all attributes
- Converts complex values to JSON strings when appropriate

When both spellings of the same attribute coexist in the store
(flat "data-x" AND nested data: { x: }), the output is
deterministic instead of insertion-order roulette: stimulus keys
(data-controller / data-action) CONCATENATE so wiring is never
lost, and every other duplicate resolves flat-spelling-wins (the
normalize_flat_attributes! convention).

## Poetry::Core::IconNotFound

Raised by an icon set's #fetch for a name it cannot serve -
malformed, or simply not in the set. This is the public rescue
point of the icon system: the Icon component's missing-icon policy
rescues exactly this class, so a custom set registered via
{Poetry::Core::Icons.register} must raise it too (that is the set
contract).

### #initialize(message, name:, suggestion: nil)

Carries the requested name (and the did-you-mean fix, when one
exists) alongside the message.

### #name

### #suggestion

## Poetry::Core::Icons::FileSet

A directory of vendored, pre-sanitized icon files - one
`<name>.svg` per icon holding the INNER markup (the component owns
the <svg> wrapper). Reads are memory-cached; names are validated
against a strict format before touching the filesystem (icon names
can carry user input - no path traversal).

### #dir

Returns the value of attribute dir.

### #fetch(name)

The inner SVG markup of one icon, read once and cached.

### #include?(name)

Whether the set has an icon of this name: the name must be
well-formed and its SVG present on disk.

### #initialize(dir:)

A set over one directory of sanitized SVG files, one file per
icon name (`circle-alert.svg`); each icon is read once and cached.

### #names

Every icon name in the set, sorted.

## Poetry::Core::Registry::Committed

The committed-registry view: the four generated sections
plus the root they resolve against, read straight from the YAML a
`registry:generate` run committed - no component classes, no Rails.
It satisfies every LlmsText/SkillText read (entries / blocks /
source_root), so boot-free consumers (the MCP server, runtime skill
delivery) share one loader instead of each parsing the payload.

### #blocks

Returns the value of attribute blocks.

### #entries

Returns the value of attribute entries.

### #form_builder

Returns the value of attribute form_builder.

### #helper_args

Returns the value of attribute helper_args.

### #helpers

Returns the value of attribute helpers.

### #initialize(entries:, blocks:, helpers:, helper_args:, source_root:, form_builder: nil)

Holds the sections of one committed registry file;
{Registry.committed} is the loader.

### #source_root

Returns the value of attribute source_root.

## Poetry::Core::RegistryAddress

The uniform registry address scheme:
no --from flag, ONE classifier for every
generator argument and every registryDependencies entry. An address is
exactly one of:

  https://acme.dev/r/fancy-chart.json   :url        any endpoint
  ./registry/fancy-chart.json           :file       a local item file
  @acme/fancy-chart                     :namespace  a configured registry
  button / Button / input_group         :bare       the installed gems

Item names normalize to kebab-case everywhere (InputGroup and
input_group are both input-group) - the block catalog's existing
naming, and the wider registry ecosystem's.

### .normalize(name)

CamelCase / snake_case / kebab-case all land on the kebab item name.

### .parse(raw)

Parses one address into its kind: `http(s)://` is a :url, `@x/y` a
:namespace, a `.json` path or a `./`, `../`, `/`, `~` prefix a
:file, and a plain item name a :bare address.

### .parse_namespaced(raw)

Parses an `@namespace/item-name` address.

### #initialize(kind:, raw:, namespace: nil, name: nil, location: nil)

Builds a frozen address; {.parse} is the usual entry point.

### #kind

Returns the value of attribute kind.

### #location

Returns the value of attribute location.

### #name

Returns the value of attribute name.

### #namespace

Returns the value of attribute namespace.

### #raw

Returns the value of attribute raw.

### #remote?

Whether the item has to be fetched rather than found among the
installed gems - every kind but :bare.

### #sibling(dep_name)

The address of a bare dependency named inside a parent item - the
sibling convention: an @acme item's bare deps are @acme items; a
url/file item's bare deps sit next to it.

## Poetry::Core::Stimulus

The Stimulus layer: builders, declarations, and attribute merging.

## Poetry::Core::Stimulus::Builder

Builder class for constructing Stimulus controller HTML attributes in a Ruby-friendly way.

This class provides a clean API for adding Stimulus data attributes to HTML elements
without manually constructing attribute strings. It handles:
- Controller registration
- Values (data passed to controllers)
- CSS class references (for Stimulus classes API)
- Outlets (connections to other controllers)
- Actions (event listeners)
- Custom parameters

### .format_identifier(identifier)

Formats an identifier by converting underscores to dashes

### #action(method, on: nil, at: nil)

Builds a Stimulus action descriptor string without adding it to the
attributes ({#with_action} adds it).

### #event(event)

Builds a custom Stimulus event name

### #html_attributes

### #identifier

### #initialize(identifier, html_attributes, options = {})

Creates a new Stimulus builder instance

### #param_attribute_name(name)

Returns the attribute name for a parameter

### #register_controller

Registers this controller in the data-controller attribute

### #target(name)

Validates and returns the JS target name

### #target_attribute_name

Returns the attribute name for a Stimulus target

### #with_action(method, on: nil, at: nil)

Adds a Stimulus action (event listener) to the HTML attributes

### #with_class(name, value)

Adds a Stimulus class reference to the HTML attributes

Classes are used to reference CSS classes that the controller can toggle.

### #with_outlet(name, value)

Adds a Stimulus outlet reference to the HTML attributes

Outlets allow one controller to reference and interact with other controllers.

### #with_param(name, value)

Adds an action parameter attribute (`data-<identifier>-<name>-param`),
which the controller reads from `event.params`.

### #with_target(name)

Adds a Stimulus target attribute to the HTML attributes

### #with_value(name, value)

Adds a Stimulus value to the HTML attributes

Values are used to pass data from HTML to Stimulus controllers.

## Poetry::Core::Stimulus::Declarations::DeclarationError

Raised at class load for an invalid or manifest-unknown
declaration.

## Poetry::Core::Stimulus::Declarations::Element

One declared element (:root or a named part) and the per-controller
wirings attached to it.

### #conditions

Returns the value of attribute conditions

### #conditions=(value)

Sets the attribute conditions

### #extend_inherited

Returns the value of attribute extend_inherited

### #extend_inherited=(value)

Sets the attribute extend_inherited

### #name

Returns the value of attribute name

### #name=(value)

Sets the attribute name

### #wirings

Returns the value of attribute wirings

### #wirings=(value)

Sets the attribute wirings

## Poetry::Core::Stimulus::Declarations::ElementDSL

Inside `on :element do ... end`: {#controller} attaches one
controller's wiring to the element.

### #controller(identifier, **options, &block)

Wires one Stimulus controller to this element.

### #event(controller, name)

A validated cross-controller event name - see
{Declarations.event_name}.

### #initialize(declaring, element)

Binds the DSL to the element its block fills.

## Poetry::Core::Stimulus::Declarations::Entry

kind: :register | :value | :action | :target. Values carry
source {type: :implicit|:literal|:method, value:}; actions carry
on:/at:. conditions is nil or {if:/unless: Symbol|Proc}.

### #at

Returns the value of attribute at

### #at=(value)

Sets the attribute at

### #conditions

Returns the value of attribute conditions

### #conditions=(value)

Sets the attribute conditions

### #kind

Returns the value of attribute kind

### #kind=(value)

Sets the attribute kind

### #name

Returns the value of attribute name

### #name=(value)

Sets the attribute name

### #on

Returns the value of attribute on

### #on=(value)

Sets the attribute on

### #source

Returns the value of attribute source

### #source=(value)

Sets the attribute source

## Poetry::Core::Stimulus::Declarations::RootDSL

Evaluates one use_stimulus block; #elements is the harvest. The
block's vocabulary is {#on} (declare an element) and {#event}
(build a validated event name).

### #elements

Returns the value of attribute elements.

### #event(controller, name)

A validated cross-controller event name - see
{Declarations.event_name}.

### #initialize(declaring)

Starts an empty harvest for one declaring class.

### #on(name, extend: false, **options, &block)

Declares the wiring of one element of the component's anatomy.
`:root` is the component root; other names match the element
keys templates read back through `stimulus_attributes_for`.
Redeclaring an element in a subclass replaces it wholesale
unless `extend: true` merges into the inherited wiring.

## Poetry::Core::Stimulus::Declarations::Wiring

:entries shadows Enumerable#entries, which Wiring never uses - the
member is literally a list of Entry structs, so the natural name wins.

### #conditions

Returns the value of attribute conditions

### #conditions=(value)

Sets the attribute conditions

### #entries

Returns the value of attribute entries

### #entries=(value)

Sets the attribute entries

### #identifier

Returns the value of attribute identifier

### #identifier=(value)

Sets the attribute identifier

## Poetry::Core::Stimulus::Declarations::WiringDSL

Inside `controller :name do ... end`: the wiring vocabulary.
{#register} boots the controller on the element; {#value},
{#action}, and {#target} declare the data attributes the render
emits - each name validated against the controllers manifest at
class load.

### #action(method, on: nil, at: nil, **options)

Declares one action the render emits into this element's
data-action. on: nil declares a BARE descriptor (Stimulus
element-default event - the forwarding shape:
"poetry--core--x#method").

### #event(controller, name)

A validated cross-controller event name - see
{Declarations.event_name}.

### #initialize(declaring, wiring)

Binds the DSL to one controller wiring and looks up the
controller's manifest definition the entries validate against.

### #register(**options)

Emits the controller's identifier into this element's
data-controller - a controller instance boots here. Value,
action, and target entries alone never register a controller.

### #target(name, **options)

Marks this element as one of the controller's named targets.

### #value(name, *literal, from: nil, **options)

Declares one Stimulus value the render emits as
`data-<identifier>-<name>-value`. Three source shapes:

    value :open                          # reads the same-named method/option
    value :orientation, :horizontal      # literal
    value :selected, from: :selected_iso # named method reference

Literal presence is arity-detected, so `value :x, false` and
`value :x, nil` stay literals.

## Poetry::Core::Stimulus::Manifest

The controllers manifest: the JS-side API surface (targets / values /
classes / methods) introspected from the live controller classes in
CI (test/javascript/controllers_manifest.test.js, regenerated with
`npm run manifest`) and committed at config/controllers_manifest.json.

The Builder validates every name it emits against this, so a
renamed controller method can never silently strand gem-rendered
wiring - the Ruby<->JS seam is guarded at render time.

Policy: poetry-namespaced identifiers ("poetry--*") are validated
strictly (unknown one raises); host-app controllers are unknown to
poetry and pass through unvalidated.

### .catalog

The merged controller catalog, identifier => definition
(`{"targets" =>, "values" =>, "classes" =>, "methods" =>}`),
loaded from poetry-core's committed manifest on first read.

### .definition(identifier)

The catalog definition of one controller.

### .register(path)

Other poetry gems merge their committed manifests here.

## Poetry::Core::Stimulus::Manifest::UnknownController

Raised for a poetry-- identifier the manifest does not know.

## Poetry::Core::Stimulus::Manifest::UnknownName

Raised for a target/value/action name the controller's manifest
entry does not list.

## Poetry::Core::Stimulus::Merger

Intelligently merges Stimulus controller data attributes from multiple sources.

This class handles the complex task of combining Stimulus data attributes
(controllers, actions, targets, values, classes, etc.) without duplicating
controllers or actions. This is particularly useful when building components
that may have Stimulus attributes from multiple concerns or sources.

### #merge(*hashes, &)

Merges multiple stimulus data hashes with special handling for controllers and actions.

This is the main merging method that intelligently combines stimulus data hashes.
Controller and action values are deduplicated using their respective merge methods,
while other data attributes are merged normally.

### #merge_actions(*actions)

Merges multiple action strings into a single deduplicated string.

Action strings are flattened, filtered for presence, deduplicated, and joined.
Unlike controllers, actions maintain their insertion order.

### #merge_attributes(attributes, *other_attributes)

Merges stimulus attributes non-destructively by deep duplicating the original.

### #merge_attributes!(attributes, *other_attributes)

Merges stimulus attributes in place, modifying the original attributes hash.

### #merge_controllers(*controllers)

Merges multiple controller strings into a single deduplicated string.

Controller names are split on spaces, deduplicated, and rejoined.
Blank or nil values are ignored.

## Poetry::Core::TagHelper

The view-helper seam: included into ActionView by the engine
(initializer "poetry_core.tag_helper"). Carries no helpers in core.

## Poetry::Core::Tokens::Color

An OKLCH color value (with optional alpha) plus the conversion and
contrast math the AAA-contrast gate is built on:

  OKLCH -> OKLab -> linear sRGB -> gamma sRGB   (Björn Ottosson's matrices)
  WCAG 2.x relative luminance + contrast ratio
  browser-style alpha compositing (gamma-encoded sRGB blend)

Pure Ruby, no dependencies - cheap enough to run on every CI build.

### .from_dtcg(value)

Build from a DTCG color $value: {"colorSpace" => "oklch",
"components" => [l, c, h], "alpha" => 0.1 (optional)}.

### .from_srgb(srgb, alpha: 1.0)

Gamma-encoded sRGB [0,1] triplet -> Color, via Ottosson's inverse
path (linear sRGB -> LMS -> OKLab -> LCH). Components round to the
3-decimal precision distributed theme files publish.

### .parse(css)

Parse a CSS color string into a Color, or nil for anything else
(named colors, var() refs, gradients) - the DESIGN.md importer
DROPS what it cannot parse, never guesses. oklch input keeps its
components verbatim so poetry-authored values round-trip
byte-exact through parse -> css.

### #alpha

Returns the value of attribute alpha.

### #c

Returns the value of attribute c.

### #composite_over(background)

Alpha-composite this color over an opaque background, the way a
browser blends (per-channel, gamma-encoded). Returns a Blend.

### #css

The CSS serialization, matching the distributed themes' formatting:
"oklch(0.577 0.245 27.325)" / "oklch(1 0 0 / 10%)".

### #h

Returns the value of attribute h.

### #initialize(l:, c: 0.0, h: 0.0, alpha: 1.0)

An OKLCH color; every component is stored as a Float.

### #l

Returns the value of attribute l.

### #srgb

Gamma-encoded sRGB components, each clamped to [0, 1].

### #with(l: self.l, c: self.c, h: self.h, alpha: self.alpha)

A copy with any component replaced. The import AA-walk moves L in
fixed steps while chroma holds - deterministic.

## Poetry::Core::Tokens::Color::Blend

The result of alpha-compositing one color over another: a plain
gamma-encoded sRGB triplet that still knows how to measure contrast.

### #srgb

Returns the value of attribute srgb

### #srgb=(value)

Sets the attribute srgb

## Poetry::Core::Tokens::Color::Contrast

WCAG 2.x contrast shared by Color and Blend: relative luminance is
computed from gamma-encoded sRGB (the value a browser actually paints).

### #contrast_ratio(other)

The WCAG contrast ratio against another color, from 1 to 21.

### #luminance

WCAG 2.x relative luminance of the painted color.

## Poetry::Core::Wrapper

Namespace of the conditional wrapper: {Wrapper::Component} renders
outer HTML around a child component only when the child itself
renders.

## Poetry::Core::Wrapper::Component

Wraps any component with custom HTML.
The whole wrapper is only rendered when the child component's #render? returns true,
so it can conditionally render the outer HTML for a component without
conditionals in templates.

Adapted from an MIT-licensed source (source and license in
THIRD_PARTY_NOTICES.md).

The child's render? is consulted before the child gains a view
context: a render? that calls view helpers works standalone but
not under the wrapper.

### #call

Returns the block's output, which must have rendered the child -
a block that skips {#component} would drop the child silently,
so it teaches instead. (An alias couldn't be used here:
ViewComponent checks method presence when choosing between
#call and a template.)

### #component

Returns the rendered child component.
The name is chosen for convenient usage in templates,
so `= wrapper.component` reads naturally at the spot where the
child belongs.

### #component_instance

Returns the value of attribute component_instance.

### #initialize(component)

Wraps a single child component; intentionally does not chain to
ViewComponent::Base#initialize (it only needs the child reference).

## Poetry::Core::Wrapper::Component::DoubleRenderError

Raised when the block calls #component more than once - each
wrapper renders its child exactly one time.

### #initialize(component)

Names the child in the message.

## Poetry::Core::Wrapper::Component::UnrenderedChildError

Raised when the wrapper's block never rendered the child - the
wrap would silently drop it.

### #initialize(component)

Names the dropped child in the message.