<!-- GENERATED by scripts/build-llms.mjs from llms/agent-tools.md — do not edit this file. -->

# `lr-tool-result-dialog`

- **Import** `import '@aceshooting/lyra-ui/components/lr-tool-result-dialog.js';` (stable tag alias; registers the tag)
- **Class** `LyraToolResultDialog`, also available unregistered from `@aceshooting/lyra-ui/components/agent-tools/tool-result-dialog/tool-result-dialog.class.js`
- **Family** `components/agent-tools/` — see `llms/index.md` for its siblings
- **Status** `stable` since `4.0.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 12 parts, 13 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-tool-result-dialog`

A full tool-call detail overlay: a status/duration header plus a `body` slot where a consumer
typically places a `<lr-tab-group>` with Input/Preview/JSON/Raw panels. First-party invention (no Web
Awesome equivalent). This component knows nothing about what's inside that slot — it only supplies
the modal chrome around it. It keeps its own shadow template rather than nesting a `<lr-dialog>`,
so slot-forwarding does not put a forwarding `<slot>` where a slotted `<lr-tab-group>`'s own light-DOM
child scan expects real projected content, while its modal behavior participates in the shared
overlay stack.

Assigning `accessibleLabel` directly names the inner dialog when no host `aria-label` is present. A
host `aria-label` retains its separate host ownership, and an empty direct value uses the tool
title.

**Properties:**

- `open: boolean = false` (reflected) — whether the dialog is open; set it directly or use the
  lifecycle methods below
- `lightDismiss: boolean = false` (attribute `light-dismiss`) — opt in to backdrop-click
  dismissal; Escape and the built-in close button remain available without it
- `accessibleLabel: string | null = null` (attribute `aria-label`) — a host attribute names the
  host itself, while the dialog panel remains labelled by its visible tool-name title instead of
  cloning that name. A direct property assignment made without the attribute can name the panel
- `toolName: string = ''` (attribute `tool-name`) — the tool's name, rendered prominently in the
  header
- `status: 'pending'|'running'|'success'|'error'|'denied' = 'pending'` (reflected) — drives the
  header's status badge; same status vocabulary as `<lr-tool-call-chip>`
- `durationMs?: number` (attribute `duration-ms`) — how long the call took, in milliseconds; omitted
  from the header entirely when unset
- `maximized: boolean = false` (reflected) — near-fullscreen presentation of the same open dialog

**Methods:** `show(): void` opens the dialog; `hide(reason: ToolResultDialogCloseReason = 'api'):
void` is the reasoned API dismissal;
`close(reason: ToolResultDialogCloseReason = 'api'): void` closes the dialog (no-op if already
closed), emits `lr-close` with `reason`, and returns focus to whatever had it before
the dialog opened. Built-in triggers call this with `'escape'`, `'backdrop'` when `lightDismiss` is
enabled, or `'close-button'`; a
consumer's own close affordance (e.g. a footer action button) should call it directly with its own
reason string so every dismissal path funnels through the same event.

**Events:** `lr-close` (`detail: ToolResultDialogCloseReason` — `'escape'|'backdrop'|
'close-button'|'api'|string`) fired exactly once per dismissal (`'backdrop'` requires
`lightDismiss`); `lr-maximize-change` (`detail:
{ readonly maximized: boolean }`, the new `maximized` state) fired when the header's
maximize/restore toggle is clicked.

**Slots:** `body` (the dialog's main content — typically a `<lr-tab-group>` with Input/Preview/JSON/Raw
panels, entirely consumer-assembled), `footer` (optional action buttons, rendered in a bottom row —
the footer row itself is hidden via `[hidden]` when nothing is slotted)

**CSS parts:** `backdrop`, `panel` (`role="dialog"` while open), `header`, `title` (wrapper around
tool name/status/duration), `tool-name`, `status`, `duration`, `header-actions`, `maximize-button`,
`close-button`, `body`, `footer`

**Themeable custom properties:** `--lr-tool-result-dialog-overlay-color` (default
`var(--lr-color-overlay)` — the backdrop scrim color, the shared token `<lr-dialog>` and
`<lr-widget>` also read, so one theme override restyles every scrim in the app),
`--lr-tool-result-dialog-maximized-inset` (default `max(var(--lr-space-l),
var(--lr-safe-area-*))` on each side — the inset applied to the panel while `[maximized]`, so the
panel clears a notch or home indicator; overridable e.g. to leave a persistent app rail visible), and
`--lr-tool-result-dialog-spin` (default `var(--lr-transition-ambient)`, i.e. `1.8s ease-in-out`,
and effectively stopped under reduced motion because that token collapses to `0.001ms linear`), plus shared
tokens `--lr-color-surface/-border/-text-quiet/-brand/-brand-quiet/-success/-success-quiet/
-danger/-danger-quiet/-warning/-warning-quiet`, `--lr-space-*`, `--lr-radius`, `--lr-shadow`,
`--lr-icon-button-size`, `--lr-focus-ring-*`, `--lr-transition-base`.

**Optional peer deps:** none.

```html
<lr-tool-result-dialog tool-name="run_query" status="success" duration-ms="1240">
  <lr-tab-group slot="body">
    <div slot="preview" label="Preview">…</div>
    <div slot="json" label="JSON"><lr-json-viewer></lr-json-viewer></div>
  </lr-tab-group>
  <button slot="footer">Rerun</button>
</lr-tool-result-dialog>
<script type="module">
  const dialog = document.querySelector("lr-tool-result-dialog");
  dialog.querySelector("lr-json-viewer").data = result;
  dialog.open = true;
  dialog.addEventListener("lr-close", () => (dialog.open = false));
  dialog.addEventListener("lr-maximize-change", (e) =>
    console.log("maximized:", e.detail.maximized)
  );
</script>
```

While open, `[part="panel"]` takes `role="dialog"` + `aria-modal="true"` with `aria-labelledby`
pointing at the tool-name element, document scroll is locked, and Tab/Shift+Tab are bounded to the
panel's own focusable content in header-buttons → `body` slot → `footer` slot order (resolved
shadow-piercingly, so a slotted custom element's real focusable target inside its own shadow root is
found too). On open, focus moves to the first focusable element (falling back to the panel itself);
on close, focus returns to whatever element triggered the open (captured at open time via the active
element, since the trigger typically lives entirely outside this component). `maximized` toggles
between the constrained modal size and a near-fullscreen size within the same open dialog and
open/close lifecycle — unlike `<lr-widget>`'s fullscreen mode there's no separate non-modal resting
state, so no additional scroll-lock/focus-trap bookkeeping is needed for that transition alone.
Backdrop clicks leave the dialog open by default; add `light-dismiss` to opt in, matching
`<lr-dialog>`, `<lr-drawer>`, and `<lr-lightbox>`.

**Known gotchas:**

- a reconnect that preserves the same element instance (e.g. a drag-and-drop reparent) resumes its
  shared overlay registration and re-acquires the scroll lock if `open` was still `true` across the
  move — `disconnectedCallback`/`connectedCallback` fire back-to-back with no intervening update, so
  `willUpdate` never reruns to notice `open` did not change
- this component deliberately does **not** compose `<lr-dialog>` internally; it keeps its own
  panel template so a slotted `<lr-tab-group>` (or any other light-DOM-scanning child) sees real
  projected content rather than a forwarding `<slot>`, while still sharing the overlay stack
- `close()` is a no-op when `open` is already `false` — calling it twice in a row only fires
  `lr-close` once
- the `maximize`/`close` buttons are always the first elements in the Tab order while open,
  regardless of visual position, followed by `body` then `footer` content

**Additional API surface:**

- `--lr-tool-result-dialog-running-color` — Running status foreground. Default: `var(--lr-color-brand)`.
- `--lr-tool-result-dialog-running-bg` — Running status background. Default: `var(--lr-color-brand-quiet)`.
- `--lr-tool-result-dialog-pending-color` — Pending status foreground. Default: `var(--lr-color-text-quiet)`.
- `--lr-tool-result-dialog-pending-bg` — Pending status background. Default: `transparent`.
- `--lr-tool-result-dialog-success-color` — Success status foreground. Default: `var(--lr-color-success)`.
- `--lr-tool-result-dialog-success-bg` — Success status background. Default: `var(--lr-color-success-quiet)`.
- `--lr-tool-result-dialog-error-color` — Error status foreground. Default: `var(--lr-color-danger)`.
- `--lr-tool-result-dialog-error-bg` — Error status background. Default: `var(--lr-color-danger-quiet)`.
- `--lr-tool-result-dialog-denied-color` — Denied status foreground. Default: `var(--lr-color-warning)`.
- `--lr-tool-result-dialog-denied-bg` — Denied status background. Default: `var(--lr-color-warning-quiet)`.

---
