## attachment (`poetry_attachment`)

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

Class: Poetry::Ui::Attachment::Component - BEM block `poetry-ui-attachment`.
- `orientation:` (symbol) - one of horizontal|vertical, default "horizontal", required - Row (:horizontal) or stacked thumbnail-card (:vertical) layout.
- `size:` (symbol) - one of default|sm|xs, default "default", required - The chip density axis.
- `state:` (symbol) - one of idle|uploading|processing|error|done, default "done" - The upload lifecycle state; flip it by re-render or Turbo Stream replace, never in JS.
Slots: media (Leading visual: :icon (default) boxes an icon tile, :image wraps the caller's <img>.; with_media yields NOTHING to the block - no |param|, write content directly; with_media keywords: variant: ONLY), title (The file name line. User content - never mark it html_safe.), description (Muted metadata under the title (size, type); in the error state, the failure explanation.), actions (Trailing icon actions - each renders a Button (ghost, icon-xs defaults) and requires label:.; many; with_action yields NOTHING to the block - no |param|, write content directly), trigger (Makes the whole chip the control - a stretched button (or anchor via tag: :a, href:) layered under the actions. Don't also wrap the chip in a link.; with_trigger yields NOTHING to the block - no |param|, write content directly).
- PART `attachment` - The chip root - the server-owned upload lifecycle rides here (flip data-upload-state by re-render / Turbo Stream replace) | states: data-upload-state=idle|uploading|processing|error|done (always - the resolved state); data-size=default|sm|xs (always - the resolved size); data-orientation=horizontal|vertical (always - the resolved orientation)
- PART `attachment-media` - The media slot's box - the icon tile or the caller's <img> | states: data-variant=icon|image (always - the media variant)
- PART `attachment-content` - Text column wrapping title/description - renders when either slot is set
- PART `attachment-title` - The file name line (title slot; user content, never html_safe)
- PART `attachment-description` - Muted metadata / failure copy under the title
- PART `attachment-actions` - Row of with_action poetry Buttons
- PART `attachment-trigger` - The whole-chip control (with_trigger: a button or tag: :a anchor) - wraps the picker/download affordance
- PART `attachment-status` - sr-only role=status announcement for the in-flight and error states (uploading/processing/error)
- RULE: State is server-owned: render data-upload-state and flip it by Turbo Stream replace - never toggle it in JS.
- RULE: with_media(variant: :image) wraps the caller's <img>; file names and URLs are user content - never render them html_safe.
- RULE: Actions are with_action(...) poetry Buttons (ghost/icon-xs defaults) - each needs label: (icon-only).
- RULE: with_trigger makes the whole chip the control (a stretched overlay UNDER the actions) - don't also wrap the chip in a link.
- RULE: error state needs a with_description explaining the failure - the tint alone is not the message.
