@artooi/ag-ui-web-component 0.34.0 → 0.35.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +517 -1
  2. package/README.md +243 -13
  3. package/dist/ag-ui-web-component.bundle.js +614 -96
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +76 -3
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +37 -2
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/dom/animations.d.ts +14 -0
  10. package/dist/dom/animations.d.ts.map +1 -1
  11. package/dist/dom/highlight_overlay.d.ts +47 -0
  12. package/dist/dom/highlight_overlay.d.ts.map +1 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1886 -320
  16. package/dist/index.js.map +4 -4
  17. package/dist/tools/chat_surface_tools.d.ts +96 -0
  18. package/dist/tools/chat_surface_tools.d.ts.map +1 -0
  19. package/dist/tools/page_action_tools.d.ts +2 -0
  20. package/dist/tools/page_action_tools.d.ts.map +1 -1
  21. package/dist/ui/clamp_launcher.d.ts +10 -5
  22. package/dist/ui/clamp_launcher.d.ts.map +1 -1
  23. package/dist/ui/clamp_panel.d.ts +2 -2
  24. package/dist/ui/clamp_panel.d.ts.map +1 -1
  25. package/dist/ui/launcher_drag.d.ts +2 -2
  26. package/dist/ui/launcher_drag.d.ts.map +1 -1
  27. package/dist/ui/launcher_placement.d.ts +14 -1
  28. package/dist/ui/launcher_placement.d.ts.map +1 -1
  29. package/dist/ui/panel_drag.d.ts.map +1 -1
  30. package/dist/ui/place_widget.d.ts +9 -1
  31. package/dist/ui/place_widget.d.ts.map +1 -1
  32. package/dist/ui/run_notice.d.ts +14 -3
  33. package/dist/ui/run_notice.d.ts.map +1 -1
  34. package/dist/ui/styles.d.ts +1 -1
  35. package/dist/ui/styles.d.ts.map +1 -1
  36. package/dist/ui/thread_drawer.d.ts +21 -0
  37. package/dist/ui/thread_drawer.d.ts.map +1 -1
  38. package/dist/ui/ui_strings.d.ts +16 -0
  39. package/dist/ui/ui_strings.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/src/constants.ts +82 -3
  42. package/src/core/ag_ui_chat.ts +965 -55
  43. package/src/dom/animations.ts +30 -0
  44. package/src/dom/highlight_overlay.ts +256 -0
  45. package/src/index.ts +12 -0
  46. package/src/tools/chat_surface_tools.ts +207 -0
  47. package/src/tools/page_action_tools.ts +2 -0
  48. package/src/ui/clamp_launcher.ts +25 -7
  49. package/src/ui/clamp_panel.ts +10 -4
  50. package/src/ui/launcher_drag.ts +11 -2
  51. package/src/ui/launcher_placement.ts +34 -8
  52. package/src/ui/panel_drag.ts +4 -0
  53. package/src/ui/place_widget.ts +11 -3
  54. package/src/ui/run_notice.ts +32 -3
  55. package/src/ui/styles.ts +563 -45
  56. package/src/ui/thread_drawer.ts +138 -8
  57. package/src/ui/ui_strings.ts +24 -0
  58. package/src/version.ts +1 -1
@@ -1,2 +1,2 @@
1
- export declare const STYLES = "\n/* \u2500\u2500 Token defaults \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Every public --ag-ui-* token is read here into a private --_* alias, and\n only the alias is used by the rules below.\n\n The indirection is what makes ancestor theming work. Declaring the public\n name on :host would set it on the host element, and a value on an element\n always beats one inherited from an ancestor, so tokens put on a wrapper\n would have no effect. Reading the public name with the default as a var()\n fallback leaves it undeclared on the element, so an ancestor's value\n inherits normally while one aimed at the element still wins.\n\n Two invariants hold this together:\n 1. No rule outside this file's :host blocks may reference a public name\n directly. A public name read outside these blocks resolves to nothing and\n silently drops its declaration rather than erroring, so the miss shows up\n as a missing colour, not a failure.\n 2. Every --_* alias in use is declared here. Aliases inherit like any custom\n property, so an undeclared one would pick up a same-named property from\n the host page; declaring it on :host shields the shadow tree from that. */\n:host {\n /* Colors */\n --_bg: var(--ag-ui-bg, #ffffff);\n --_fg: var(--ag-ui-fg, #1a1a2e);\n --_accent: var(--ag-ui-accent, #4f46e5);\n --_user-bg: var(--ag-ui-user-bg, #4f46e5);\n --_user-fg: var(--ag-ui-user-fg, #ffffff);\n --_assistant-bg: var(--ag-ui-assistant-bg, #f1f1f6);\n --_hover: var(--ag-ui-hover, #e7e7ee);\n --_input-bg: var(--ag-ui-input-bg, var(--_bg));\n --_tool-bg: var(--ag-ui-tool-bg, var(--_assistant-bg));\n --_tool-fg: var(--ag-ui-tool-fg, var(--_accent));\n --_header-bg: var(--ag-ui-header-bg, var(--_accent));\n --_header-fg: var(--ag-ui-header-fg, #ffffff);\n --_border: var(--ag-ui-border, #e2e2ec);\n --_radius: var(--ag-ui-radius, 12px);\n\n /* Body text and raised chrome, read only by the code-block copy button.\n The defaults restate what it inherits, so a host that sets neither sees\n no repaint \u2014 see the note on .code-copy. */\n --_text: var(--ag-ui-text, var(--_fg));\n --_surface: var(--ag-ui-surface, transparent);\n\n /* Message action row: the control box and the mark inside it. The box has a\n floor of 24px so it stays a reliable target at every density. */\n --_action-size: var(--ag-ui-action-size, 28px);\n --_action-icon-size: var(--ag-ui-action-icon-size, 15px);\n --_tooltip-bg: var(--ag-ui-tooltip-bg, #1f2430);\n --_tooltip-fg: var(--ag-ui-tooltip-fg, #f5f6fa);\n\n /* Text and marks drawn on top of the accent and danger fills. Separate\n tokens because they are not free: a host that themes the accent to a pale\n colour has white-on-pale with no way to correct it, which is the one\n theming change that makes a control unreadable rather than merely off. */\n --_on-accent: var(--ag-ui-on-accent, #ffffff);\n --_on-danger: var(--ag-ui-on-danger, #ffffff);\n\n /* Resize grips: the corner squares and the edge strips between them. Wide\n enough to hit without being wide enough to swallow a click on the content\n underneath, and adjustable for a host with a coarser pointer. */\n --_grip-corner: var(--ag-ui-grip-corner, 14px);\n --_grip-edge: var(--ag-ui-grip-edge, 6px);\n --_grip-edge-docked: var(--ag-ui-grip-edge-docked, 8px);\n /* The mark drawn inside a grip, as opposed to the area it answers to. */\n --_grip-mark-length: var(--ag-ui-grip-mark-length, 28px);\n --_grip-mark-thickness: var(--ag-ui-grip-mark-thickness, 3px);\n\n /* Status accents for tool-call cards. */\n --_success: var(--ag-ui-success, #15803d);\n --_danger: var(--ag-ui-danger, #b91c1c);\n --_muted: var(--ag-ui-muted, #6b7280);\n\n /* Tool-call status icon glyphs (override to re-theme) + spinner speed.\n The pending state is the animated ring; the settled states use these. */\n --_tool-icon-done: var(--ag-ui-tool-icon-done, \"\u2713\");\n --_tool-icon-error: var(--ag-ui-tool-icon-error, \"\u2715\");\n --_tool-icon-declined: var(--ag-ui-tool-icon-declined, \"\u2298\");\n\n /* Disclosure marks on every expandable row. Tokenised for the same reason\n the status icons above are: a host re-theming one set and not the other\n ends up with two vocabularies in one transcript. */\n --_disclosure-collapsed: var(--ag-ui-disclosure-collapsed, \"\u25B8\");\n --_disclosure-expanded: var(--ag-ui-disclosure-expanded, \"\u25BE\");\n --_tool-spin-duration: var(--ag-ui-tool-spin-duration, 0.7s);\n\n /* Answer well (opt-in via data-answer-well) \u2014 boxes a whole assistant turn. */\n --_well-bg: var(--ag-ui-well-bg, transparent);\n --_well-border: var(--ag-ui-well-border, var(--_border));\n\n /* Surface \u2014 set --ag-ui-shadow: none for a flush, embedded panel. */\n --_shadow: var(--ag-ui-shadow, 0 12px 32px rgba(20, 20, 50, 0.18));\n --_font: var(--ag-ui-font, inherit);\n --_font-size: var(--ag-ui-font-size, 14px);\n --_code-font: var(--ag-ui-code-font, ui-monospace, \"SF Mono\", Menlo, monospace);\n\n /* Header / launcher icon box. */\n --_icon-size: var(--ag-ui-icon-size, 22px);\n --_icon-radius: var(--ag-ui-icon-radius, 4px);\n\n /* Composer \u2014 one bordered surface holding the field and its tool row. */\n --_composer-radius: var(--ag-ui-composer-radius, 14px);\n --_composer-max-height: var(--ag-ui-composer-max-height, 40vh);\n --_tool-btn-size: var(--ag-ui-tool-btn-size, 30px);\n --_send-size: var(--ag-ui-send-size, 30px);\n --_glyph-size: var(--ag-ui-glyph-size, 18px);\n --_glyph-stroke: var(--ag-ui-glyph-stroke, 1.75);\n\n /* How wide a chart is allowed to get. A cap rather than a width: below it\n the chart fills its column, and above it a wider panel is just a wider\n panel. */\n --_chart-max-width: var(--ag-ui-chart-max-width, 480px);\n\n /* Unread badge on the launcher. */\n --_badge-bg: var(--ag-ui-badge-bg, var(--_danger));\n --_badge-fg: var(--ag-ui-badge-fg, #ffffff);\n --_badge-size: var(--ag-ui-badge-size, 18px);\n --_badge-font-size: var(--ag-ui-badge-font-size, 11px);\n\n /* The floating launcher a collapsed widget shrinks to. */\n --_launcher-size: var(--ag-ui-launcher-size, 56px);\n --_launcher-bg: var(--ag-ui-launcher-bg, var(--_header-bg));\n --_launcher-fg: var(--ag-ui-launcher-fg, var(--_header-fg));\n --_launcher-radius: var(--ag-ui-launcher-radius, 50%);\n --_launcher-icon-size: var(--ag-ui-launcher-icon-size, 26px);\n --_launcher-inset: var(--ag-ui-launcher-inset, auto 0 0 auto);\n\n /* Motion. One duration and two curves drive every collapse, expand and\n slide-over, so the widget accelerates and settles as one thing. The\n default curve decelerates into place; the pop curve overshoots slightly,\n for something arriving. */\n --_motion: var(--ag-ui-motion, 0.28s);\n --_ease: var(--ag-ui-ease, cubic-bezier(0.32, 0.72, 0, 1));\n --_ease-pop: var(--ag-ui-ease-pop, cubic-bezier(0.34, 1.36, 0.64, 1));\n\n /* Spacing \u2014 the density preset overrides these. */\n --_space: var(--ag-ui-space, 10px);\n --_pad: var(--ag-ui-pad, 16px);\n --_msg-pad: var(--ag-ui-msg-pad, 8px 12px);\n --_msg-radius: var(--ag-ui-msg-radius, 14px);\n\n /* Layout \u2014 override from outside to dock the widget anywhere.\n Set --ag-ui-position: static (and place this element in your own\n grid/flex layout) to embed it in the page flow instead of floating. */\n --_position: var(--ag-ui-position, fixed);\n --_z-index: var(--ag-ui-z-index, 2147483000);\n --_width: var(--ag-ui-width, 380px);\n --_height: var(--ag-ui-height, 560px);\n --_inset: var(--ag-ui-inset, auto 24px 24px auto);\n --_max-width: var(--ag-ui-max-width, calc(100vw - 48px));\n --_max-height: var(--ag-ui-max-height, calc(100vh - 48px));\n /* Reading-column width for placement=\"page\" (full-bleed, centred content). */\n --_content-max-width: var(--ag-ui-content-max-width, 820px);\n /* Slim rail the sidebar placement collapses to. Only that placement reads\n it, but it is declared here so every alias has a default in one place. */\n --_rail-width: var(--ag-ui-rail-width, 52px);\n\n position: var(--_position);\n inset: var(--_inset);\n z-index: var(--_z-index);\n width: var(--_width);\n max-width: var(--_max-width);\n height: var(--_height);\n max-height: var(--_max-height);\n display: flex;\n font-family: var(--_font);\n font-size: var(--_font-size);\n color: var(--_fg);\n}\n\n/* \u2500\u2500 Themes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Themes only re-set the colour variables; layout/spacing are unaffected.\n theme=\"auto\" follows the OS via prefers-color-scheme. */\n:host([theme=\"dark\"]) {\n --_bg: var(--ag-ui-bg, #15151f);\n --_fg: var(--ag-ui-fg, #e8e8f2);\n --_assistant-bg: var(--ag-ui-assistant-bg, #25253a);\n --_hover: var(--ag-ui-hover, #303049);\n --_header-bg: var(--ag-ui-header-bg, #1f1f30);\n --_header-fg: var(--ag-ui-header-fg, #e8e8f2);\n --_border: var(--ag-ui-border, #33334a);\n --_muted: var(--ag-ui-muted, #9aa0b4);\n --_shadow: var(--ag-ui-shadow, 0 12px 32px rgba(0, 0, 0, 0.5));\n}\n\n@media (prefers-color-scheme: dark) {\n :host([theme=\"auto\"]) {\n --_bg: var(--ag-ui-bg, #15151f);\n --_fg: var(--ag-ui-fg, #e8e8f2);\n --_assistant-bg: var(--ag-ui-assistant-bg, #25253a);\n --_hover: var(--ag-ui-hover, #303049);\n --_header-bg: var(--ag-ui-header-bg, #1f1f30);\n --_header-fg: var(--ag-ui-header-fg, #e8e8f2);\n --_border: var(--ag-ui-border, #33334a);\n --_muted: var(--ag-ui-muted, #9aa0b4);\n --_shadow: var(--ag-ui-shadow, 0 12px 32px rgba(0, 0, 0, 0.5));\n }\n}\n\n/* A terminal-flavoured \"code\" theme: dark, monospace, green accent. */\n:host([theme=\"code\"]) {\n --_bg: var(--ag-ui-bg, #0d1117);\n --_fg: var(--ag-ui-fg, #c9d1d9);\n --_accent: var(--ag-ui-accent, #3fb950);\n --_user-bg: var(--ag-ui-user-bg, #238636);\n --_assistant-bg: var(--ag-ui-assistant-bg, #161b22);\n --_hover: var(--ag-ui-hover, #21262d);\n --_header-bg: var(--ag-ui-header-bg, #010409);\n --_header-fg: var(--ag-ui-header-fg, #c9d1d9);\n --_border: var(--ag-ui-border, #30363d);\n --_muted: var(--ag-ui-muted, #8b949e);\n --_font: var(--ag-ui-font, var(--_code-font));\n --_shadow: var(--ag-ui-shadow, 0 12px 32px rgba(0, 0, 0, 0.6));\n}\n\n/* \u2500\u2500 Density \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host([density=\"compact\"]) {\n --_font-size: var(--ag-ui-font-size, 13px);\n --_space: var(--ag-ui-space, 6px);\n --_pad: var(--ag-ui-pad, 10px);\n --_msg-pad: var(--ag-ui-msg-pad, 5px 9px);\n --_msg-radius: var(--ag-ui-msg-radius, 10px);\n}\n\n/* \u2500\u2500 Placement presets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host([placement=\"bottom-left\"]) {\n --_inset: var(--ag-ui-inset, auto auto 24px 24px);\n}\n\n:host([placement=\"side\"]) {\n --_inset: var(--ag-ui-inset, 0 0 0 auto);\n --_width: var(--ag-ui-width, 420px);\n --_height: var(--ag-ui-height, 100vh);\n --_max-height: var(--ag-ui-max-height, 100vh);\n --_radius: var(--ag-ui-radius, 0);\n}\n\n:host([placement=\"full\"]) {\n --_inset: var(--ag-ui-inset, 0);\n --_width: var(--ag-ui-width, 100vw);\n --_height: var(--ag-ui-height, 100vh);\n --_max-width: var(--ag-ui-max-width, 100vw);\n --_max-height: var(--ag-ui-max-height, 100vh);\n --_radius: var(--ag-ui-radius, 0);\n}\n\n/* Page: full-bleed background with a centred reading column capped at\n --ag-ui-content-max-width, where \"full\" is edge-to-edge and left-aligned.\n The column comes from symmetric auto padding on the scroll area and\n composer rather than a per-row wrapper, so user pills still right-align and\n the assistant well spans the column. */\n:host([placement=\"page\"]) {\n --_inset: var(--ag-ui-inset, 0);\n --_width: var(--ag-ui-width, 100vw);\n --_height: var(--ag-ui-height, 100vh);\n --_max-width: var(--ag-ui-max-width, 100vw);\n --_max-height: var(--ag-ui-max-height, 100vh);\n --_radius: var(--ag-ui-radius, 0);\n}\n\n:host([placement=\"page\"]) .messages {\n padding-inline: max(var(--_pad), calc((100% - var(--_content-max-width)) / 2));\n}\n\n:host([placement=\"page\"]) .input-row {\n padding-inline: max(12px, calc((100% - var(--_content-max-width)) / 2));\n}\n\n/* The rows between the message list and the composer line up with the column\n too. Chips and tray are padding-based while palette and hint are\n margin-based, so each gets its own inline axis nudged by the same gutter. */\n:host([placement=\"page\"]) .skill-chips,\n:host([placement=\"page\"]) .attachment-tray {\n padding-inline: max(12px, calc((100% - var(--_content-max-width)) / 2));\n}\n\n:host([placement=\"page\"]) .skill-palette,\n:host([placement=\"page\"]) .skill-hint {\n margin-inline: max(12px, calc((100% - var(--_content-max-width)) / 2));\n}\n\n/* In the reading column the assistant well uses the full width; the user\n message stays a right-aligned pill (its default align-self + max-width). */\n:host([placement=\"page\"]) .message--assistant {\n max-width: 100%;\n}\n\n/* Sidebar: a full-height docked panel that slides open/closed and\n collapses to a slim icon rail (not the floating launcher). Docked right by\n default; data-side=\"left\" docks it left. Overlay by default \u2014 set\n --ag-ui-position: static (and place this element in your own layout) for a\n host-managed push instead. */\n:host([placement=\"sidebar\"]) {\n --_inset: var(--ag-ui-inset, 0 0 0 auto);\n --_width: var(--ag-ui-width, 420px);\n --_height: var(--ag-ui-height, 100vh);\n --_max-height: var(--ag-ui-max-height, 100vh);\n --_radius: var(--ag-ui-radius, 0);\n transition: width var(--_motion) var(--_ease);\n}\n\n:host([placement=\"sidebar\"][data-side=\"left\"]) {\n --_inset: var(--ag-ui-inset, 0 auto 0 0);\n}\n\n/* The docked panel is pinned to the edge it docks against rather than filling\n the host as a flex child. Collapsing shrinks the host to the rail width, and\n a flex child would be squashed to 52px on the way out instead of sliding out\n at full width. */\n:host([placement=\"sidebar\"]) .chat {\n position: absolute;\n inset: 0 0 0 auto;\n width: var(--_width);\n transform-origin: right center;\n}\n\n:host([placement=\"sidebar\"][data-side=\"left\"]) .chat {\n inset: 0 auto 0 0;\n transform-origin: left center;\n}\n\n/* Collapsed sidebar: shrink the host to the rail width and slide the panel out\n through the edge it docks against. Higher specificity than the generic\n collapse rules, so it wins regardless of source order. */\n:host([placement=\"sidebar\"][collapsed]) {\n width: var(--_rail-width);\n height: 100vh;\n max-height: 100vh;\n bottom: 0;\n pointer-events: auto;\n}\n\n:host([placement=\"sidebar\"][collapsed]) .chat {\n transform: translateX(100%);\n}\n\n:host([placement=\"sidebar\"][data-side=\"left\"][collapsed]) .chat {\n transform: translateX(-100%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host([placement=\"sidebar\"]),\n :host([placement=\"sidebar\"]) .chat {\n transition: none;\n }\n}\n\n/* \u2500\u2500 Launcher \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n What a collapsed widget shrinks to: a round floating button by default, the\n sidebar's slim edge rail under that placement. A sibling of the panel, so it\n survives the panel being hidden, and the only part of a collapsed widget\n that takes pointer events. */\n.launcher {\n display: flex;\n position: absolute;\n inset: var(--_launcher-inset);\n align-items: center;\n justify-content: center;\n width: var(--_launcher-size);\n height: var(--_launcher-size);\n padding: 0;\n border: none;\n border-radius: var(--_launcher-radius);\n background: var(--_launcher-bg);\n color: var(--_launcher-fg);\n box-shadow: var(--_shadow);\n font: inherit;\n cursor: pointer;\n pointer-events: auto;\n /* A touch drag on the launcher moves it; without this the page scrolls under\n the finger instead and the launcher never moves at all. */\n touch-action: none;\n opacity: 0;\n transform: scale(0.4);\n visibility: hidden;\n transition:\n opacity var(--_motion) var(--_ease),\n transform var(--_motion) var(--_ease-pop),\n visibility var(--_motion) var(--_ease);\n}\n\n/* The launcher grows out of the corner the panel shrank into. It stays laid\n out at rest rather than display:none, which is what lets it animate both in\n and out: an unrendered element has no before-change style to transition\n from, and one flipping display to none cannot transition at all. visibility\n keeps it unpaintable, untabbable and unclickable in between, so the expanded\n panel's own controls underneath stay reachable. */\n:host([collapsed]) .launcher {\n opacity: 1;\n transform: none;\n visibility: visible;\n}\n\n:host([collapsed]) .launcher:hover {\n transform: scale(1.06);\n}\n\n:host([collapsed]) .launcher:active {\n transform: scale(0.94);\n}\n\n/* A drag is a press and a hover at the same time, so both scale rules above are\n live throughout it. Cancelling them is what keeps the launcher the size of\n the thing under the pointer while it travels; the position itself comes from\n inset, which nothing here transitions, so it tracks the pointer exactly. */\n:host([collapsed]) .launcher[data-dragging] {\n transform: none;\n cursor: grabbing;\n}\n\n.launcher .icon-holder {\n width: var(--_launcher-icon-size);\n height: var(--_launcher-icon-size);\n}\n\n/* The unread badge rides the launcher's top-right corner. The ring in the\n widget's own background is what separates it from the launcher underneath,\n whatever colour the host themes either one. */\n.launcher-badge {\n position: absolute;\n top: -2px;\n right: -2px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n min-width: var(--_badge-size);\n height: var(--_badge-size);\n padding: 0 5px;\n border-radius: 999px;\n background: var(--_badge-bg);\n color: var(--_badge-fg);\n font-size: var(--_badge-font-size);\n font-weight: 600;\n line-height: 1;\n box-shadow: 0 0 0 2px var(--_bg);\n}\n\n.launcher-badge[hidden] {\n display: none;\n}\n\n/* The sidebar collapses to an edge rail instead: full height, square, flush\n against the dock. It slides in with the panel rather than popping. */\n:host([placement=\"sidebar\"][collapsed]) .launcher {\n inset: 0;\n width: auto;\n height: auto;\n align-items: flex-start;\n padding-top: 16px;\n border: 1px solid var(--_border);\n border-radius: 0;\n background: var(--_header-bg);\n color: var(--_header-fg);\n box-shadow: none;\n transform: none;\n}\n\n/* Every transition in this file is timed by --_motion, so collapsing it to a\n frame is the whole reduced-motion story: states still change (and display\n still flips at the end of its discrete transition), nothing travels. */\n@media (prefers-reduced-motion: reduce) {\n :host {\n --_motion: var(--ag-ui-motion, 0.001s);\n }\n}\n\n/* Embedded: drop the floating chrome and the high z-index stacking context so\n the widget lives in the host's own layout (fixes overlay/z-index clashes). */\n:host([placement=\"embedded\"]) {\n --_position: var(--ag-ui-position, static);\n --_width: var(--ag-ui-width, 100%);\n --_height: var(--ag-ui-height, 100%);\n --_max-width: var(--ag-ui-max-width, 100%);\n --_max-height: var(--ag-ui-max-height, 100%);\n --_shadow: var(--ag-ui-shadow, none);\n --_z-index: var(--ag-ui-z-index, auto);\n}\n\n.chat {\n position: relative;\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n background: var(--_bg);\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n box-shadow: var(--_shadow);\n overflow: hidden;\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--_border);\n background: var(--_header-bg);\n color: var(--_header-fg);\n}\n\n/* The header is the panel's title bar: on the placements that let the widget\n be moved it drags the whole thing, matching the launcher's own drag. The\n selector lists those placements rather than asking JS to stamp an attribute,\n because a cursor that appears one frame late reads as a control that only\n sometimes exists. touch-action keeps a finger drag here moving the panel\n instead of scrolling the page behind it. */\n:host(:not([placement])) .header,\n:host([placement=\"\"]) .header,\n:host([placement=\"floating\"]) .header,\n:host([placement=\"bottom-left\"]) .header {\n cursor: move;\n touch-action: none;\n}\n\n/* A host that turned the drag off keeps a header that says so. */\n:host([data-launcher-drag=\"false\"]) .header {\n cursor: auto;\n touch-action: auto;\n}\n\n/* :host is carried for the specificity, not the scope: the placement rules\n above are a host selector plus a class plus an attribute, and a bare\n .header[data-dragging] loses to them -- silently, because the drag still\n works and only the cursor is wrong. */\n:host .header[data-dragging] {\n cursor: grabbing;\n}\n\n/* The cursor inherits, so a control a host slots into the header would show\n the drag cursor over something the drag deliberately ignores. Named to\n match the controls panel_drag steps aside for. */\n.header ::slotted(:is(button, a, input, select, textarea)) {\n cursor: pointer;\n}\n\n.header-title {\n flex: 1;\n min-width: 0;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Header / launcher icon holder: a slot, with a data-icon-url <img>\n fallback, sized via --ag-ui-icon-size. */\n.icon-holder {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: var(--_icon-size);\n height: var(--_icon-size);\n line-height: 1;\n}\n\n.icon-img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n border-radius: var(--_icon-radius);\n}\n\n.header-controls {\n display: flex;\n gap: 2px;\n flex: none;\n}\n\n.header-btn {\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n line-height: 1;\n padding: 4px 7px;\n border-radius: 6px;\n cursor: pointer;\n opacity: 0.85;\n}\n\n.header-btn:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.18);\n}\n\n/* \u2500\u2500 Collapse \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Collapsing shrinks the widget to the round floating launcher: the panel\n scales toward the launcher's corner and fades, the launcher pops in from the\n same point. Both halves are transform and opacity only, so the morph runs on\n the compositor and never reflows the host page.\n\n The host box keeps its expanded size, since animating it would animate\n layout and a dragged --ag-ui-width would fight the launcher's own size.\n Nothing paints there once the panel is gone, so the box only has to stop\n swallowing clicks: pointer events go to none and the launcher takes them.\n\n Two placements collapse differently: \"sidebar\" slides to its rail (below),\n while \"embedded\" and \"page\" keep the header bar, having no corner for a\n floating circle that would escape the host's layout. */\n:host([collapsed]) {\n pointer-events: none;\n /* A collapsed host has to be allowed to shrink, and in the layout hosts\n actually use it is not. Every collapse path here works by letting the host\n size to its content -- the in-flow ones set height: auto, the floating one\n leaves only the launcher -- and a flex or grid parent whose align-items is\n the default stretch value overrides all of it. The panel then hides and the box\n it occupied stays: a header bar over several hundred pixels of nothing.\n Every known consumer hit this, because putting the element in a flex column\n beside the page content is the obvious way to embed it. */\n align-self: start;\n}\n\n:host([collapsed]) .chat {\n opacity: 0;\n transform: scale(0.94);\n visibility: hidden;\n}\n\n/* visibility keeps the panel out of the tab order and the a11y tree at rest\n without display:none killing the transition. It interpolates so any progress\n below 1 still counts as visible: the panel stays on screen for the whole\n collapse, flips hidden exactly at the end, and on expand is visible from the\n first frame. */\n.chat {\n transform-origin: bottom right;\n transition:\n opacity var(--_motion) var(--_ease),\n transform var(--_motion) var(--_ease),\n visibility var(--_motion) var(--_ease);\n}\n\n:host([placement=\"bottom-left\"]) .chat {\n transform-origin: bottom left;\n}\n\n/* Once the launcher has been dragged the element places itself, and stamps the\n corner it chose to open away from. The morph has to start at that same\n corner: scaling out of the one the placement originally guessed reads as the\n panel leaping across the screen before it opens.\n\n Equal specificity to the placement rule above, so source order is what lets\n the stamped value win -- the same arrangement the resize grip uses, and the\n same trap. Write these with = on the whole hyphenated token; a ~= would match\n whitespace-separated words and so could never match at all. */\n:host([data-expand-corner=\"top-left\"]) .chat {\n transform-origin: top left;\n}\n\n:host([data-expand-corner=\"top-right\"]) .chat {\n transform-origin: top right;\n}\n\n:host([data-expand-corner=\"bottom-left\"]) .chat {\n transform-origin: bottom left;\n}\n\n:host([data-expand-corner=\"bottom-right\"]) .chat {\n transform-origin: bottom right;\n}\n\n/* The two in-flow placements keep the original collapse: hide the body, let\n the host shrink to the header bar. */\n:host([collapsed]:is([placement=\"embedded\"], [placement=\"page\"])) {\n height: auto;\n max-height: none;\n pointer-events: auto;\n}\n\n:host([collapsed]:is([placement=\"embedded\"], [placement=\"page\"])) .chat {\n opacity: 1;\n transform: none;\n visibility: visible;\n}\n\n/* These two keep the header bar, so the launcher must stay out of the way: an\n embedded host is position: static, which would let an absolutely-positioned\n circle escape the layout and land against whatever the page positions. */\n:host([collapsed]:is([placement=\"embedded\"], [placement=\"page\"])) .launcher {\n visibility: hidden;\n opacity: 0;\n}\n\n:host([collapsed]:is([placement=\"embedded\"], [placement=\"page\"])) .messages-wrap,\n:host([collapsed]:is([placement=\"embedded\"], [placement=\"page\"])) .messages,\n:host([collapsed]:is([placement=\"embedded\"], [placement=\"page\"])) .input-row,\n:host([collapsed]:is([placement=\"embedded\"], [placement=\"page\"])) .skill-chips,\n:host([collapsed]:is([placement=\"embedded\"], [placement=\"page\"])) .skill-palette,\n:host([collapsed]:is([placement=\"embedded\"], [placement=\"page\"])) .skill-hint {\n display: none;\n}\n\n/* Jump-to-latest: shown only once the reader has scrolled away *and* missed\n something. Anchored to the panel rather than the list so it does not scroll\n with the content it is offering to scroll to. */\n/* The transcript's own box, and the only one whose foot is the transcript's\n foot. The panel's foot is below the composer, the chips and the footer. */\n.messages-wrap {\n position: relative;\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n.jump-latest {\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n bottom: var(--_pad);\n z-index: 2;\n display: none;\n align-items: center;\n gap: 0.35em;\n padding: 0.4em 0.9em;\n border: 1px solid var(--_border);\n border-radius: 999px;\n /* A raised surface, not the panel's own background. Reusing --_bg made the\n pill the same colour as everything behind it, leaving a 1px border and a\n shadow to carry the whole affordance -- and a dark-on-dark shadow carries\n nothing. --_hover is the token that already means \"lifted off the panel\",\n and it separates in both themes without competing with the accent the send\n button owns. */\n background: var(--_hover);\n color: var(--_text);\n font: inherit;\n font-size: 0.85em;\n cursor: pointer;\n box-shadow: 0 2px 10px rgb(0 0 0 / 0.18);\n}\n\n.jump-latest[data-missed=\"true\"] {\n display: flex;\n}\n\n.jump-latest:hover {\n border-color: var(--_accent);\n}\n\n/* The offer to quote a selection. Positioned in script against the transcript\n box, which is the only ancestor whose top and foot are the transcript's --\n the same reason .jump-latest lives here. The translate is the half the\n script does not do: script sets the point the offer hangs from, CSS decides\n which corner of the offer that point is. */\n.quote-selection {\n position: absolute;\n z-index: 2;\n transform: translate(-50%, -100%);\n padding: 0.25em 0.7em;\n border: 1px solid var(--_border);\n border-radius: 999px;\n background: var(--_hover);\n color: var(--_text);\n font: inherit;\n font-size: 0.8em;\n line-height: 1.6;\n white-space: nowrap;\n cursor: pointer;\n box-shadow: 0 2px 10px rgb(0 0 0 / 0.18);\n}\n\n/* Flipped under the selection when there was no room above it. Only the\n vertical half of the translate changes: it still hangs from its own centre\n horizontally. */\n.quote-selection[data-below=\"true\"] {\n transform: translate(-50%, 0);\n}\n\n.quote-selection:hover {\n border-color: var(--_accent);\n}\n\n/* Screen-reader-only status region. Off-screen rather than display:none or\n visibility:hidden, both of which take the element out of the accessibility\n tree entirely -- a hidden live region announces nothing at all, which is the\n classic way this pattern is written wrong.\n\n The 1px box with clip-path, rather than width/height 0, is the shape that\n survives: a zero-sized element is dropped from the tree by some engines. */\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n overflow: hidden;\n white-space: nowrap;\n clip-path: inset(50%);\n}\n\n.messages {\n flex: 1;\n overflow-y: auto;\n /* The browser's own scroll anchoring competes with the scroller for the same\n job and wins unpredictably -- it can hold the view still exactly when we\n want to follow. Turned off so following is decided in one place. Safari\n does not implement it, which is itself a reason not to depend on it. */\n overflow-anchor: none;\n padding: var(--_pad);\n display: flex;\n flex-direction: column;\n gap: var(--_space);\n}\n\n/* Empty-state region (slot): centred while it's the only thing in the\n list, hidden as soon as a message, card, or pending indicator renders. */\n.empty {\n margin: auto;\n text-align: center;\n color: var(--_muted);\n}\n\n.empty[hidden] {\n display: none;\n}\n\n/* \u2500\u2500 Answer group / well \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n One .answer per assistant turn wraps the streamed text, its tool cards,\n and the pending indicator so a whole answer reads (and can be boxed) as one\n unit. A flex column on the message-list gap, stretched to the list width so\n its children keep their own left/right alignment. data-answer-well opts into\n the bordered \"well\"; without it the turn renders as a flat stack. */\n.answer {\n display: flex;\n flex-direction: column;\n gap: var(--_space);\n align-self: stretch;\n min-width: 0;\n}\n\n:host([data-answer-well]) .answer {\n padding: var(--_pad);\n background: var(--_well-bg);\n border: 1px solid var(--_well-border);\n border-radius: var(--_radius);\n}\n\n.message {\n max-width: 80%;\n padding: var(--_msg-pad);\n border-radius: var(--_msg-radius);\n line-height: 1.4;\n white-space: pre-wrap;\n /* overflow-wrap, not word-break, and the difference is the whole reason a\n markdown table used to be unreadable.\n\n Both stop one long unbroken token blowing out the bubble, which is all this\n is for. But word-break: break-word is the legacy spelling of \"break\n anywhere\", and breaking anywhere drops the *min-content* width of every\n descendant to one character. A table's column algorithm takes min-content\n as an input, so the table always fitted max-width: 100%, the overflow-x:\n auto below it never had anything to scroll, and the columns absorbed the\n pressure by rendering one letter per line instead. A seven-column header\n came out 162px tall.\n\n overflow-wrap: break-word breaks a word only when it would otherwise\n overflow its line, and leaves min-content alone -- so the bubble is still\n protected and the table is free to be wider than the panel and scroll. */\n overflow-wrap: break-word;\n}\n\n/* \u2500\u2500 Incoming-text animations (data-text-animation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* .message--restored (rehydrated history) is excluded: entrance animations are\n for freshly-arriving messages, not the whole transcript replaying on reload.\n Word mode is excluded implicitly \u2014 restored bubbles aren't wrapped. */\n:host([data-text-animation=\"fade\"]) .message--assistant:not(.message--restored) {\n animation: ag-ui-msg-fade 0.25s ease both;\n}\n\n@keyframes ag-ui-msg-fade {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: none; }\n}\n\n.message--assistant .word {\n animation: ag-ui-word-in 0.3s ease both;\n animation-delay: calc(var(--ag-ui-word-index, 0) * 35ms);\n}\n\n@keyframes ag-ui-word-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host([data-text-animation=\"fade\"]) .message--assistant,\n .message--assistant .word {\n animation: none;\n }\n}\n\n.message--user {\n align-self: flex-end;\n background: var(--_user-bg);\n color: var(--_user-fg);\n border-bottom-right-radius: 4px;\n}\n\n.message--assistant {\n align-self: flex-start;\n background: var(--_assistant-bg);\n border-bottom-left-radius: 4px;\n /* Assistant bubbles hold rendered markdown/HTML, so collapse the source\n whitespace the renderer leaves between block tags. */\n white-space: normal;\n}\n\n/* Rendered-markdown elements inside an assistant bubble. */\n.message--assistant > :first-child {\n margin-top: 0;\n}\n\n.message--assistant > :last-child {\n margin-bottom: 0;\n}\n\n.message--assistant p,\n.message--assistant ul,\n.message--assistant ol,\n.message--assistant blockquote,\n.message--assistant pre {\n margin: 0.5em 0;\n}\n\n.message--assistant ul,\n.message--assistant ol {\n padding-left: 1.3em;\n}\n\n.message--assistant a {\n color: var(--_accent);\n text-decoration: underline;\n}\n\n.message--assistant code {\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n font-size: 0.92em;\n background: rgba(127, 127, 127, 0.16);\n padding: 1px 4px;\n border-radius: 4px;\n}\n\n.message--assistant pre {\n padding: 8px 10px;\n overflow: auto;\n background: var(--_bg);\n border: 1px solid var(--_border);\n border-radius: 6px;\n}\n\n.message--assistant pre code {\n background: none;\n padding: 0;\n}\n\n/* The copy button sits inside the block, so it scrolls with wide code rather\n than floating over the bubble. Positioning is on the pre; the button only\n appears once one has been attached. */\n.message--assistant pre.has-copy {\n position: relative;\n}\n\n/* The only reader of --ag-ui-surface and --ag-ui-text. Their defaults are\n transparent and the body foreground, which is what this button rendered as\n before either token existed; changing them repaints only this control. */\n.code-copy {\n position: absolute;\n top: 4px;\n right: 4px;\n padding: 2px 8px;\n font: inherit;\n font-size: 0.75em;\n line-height: 1.6;\n color: var(--_muted);\n background: var(--_surface);\n border: 1px solid var(--_border);\n border-radius: 4px;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.12s ease;\n}\n\n/* Revealed on hover or keyboard focus. Focus matters as much as hover here:\n hidden-until-hover is invisible to a keyboard user otherwise. */\n.message--assistant pre.has-copy:hover .code-copy,\n.code-copy:focus-visible {\n opacity: 1;\n}\n\n.code-copy:hover {\n color: var(--_text);\n background: var(--_hover);\n}\n\n.code-copy[data-state=\"copied\"] {\n opacity: 1;\n color: var(--_text);\n}\n\n.code-copy[data-state=\"failed\"] {\n opacity: 1;\n color: var(--_danger, var(--_text));\n}\n\n.message--assistant blockquote {\n padding-left: 10px;\n border-left: 3px solid var(--_border);\n color: var(--_muted);\n}\n\n/* Markdown tables. table/thead/tbody/tr/th/td are all in the sanitizer's\n ALLOWED_TAGS, so an agent can emit one. A wide table must scroll inside its\n own box rather than stretch the message: the bubble is width-constrained, so\n without this the columns either crush or push the layout sideways. */\n.message--assistant table {\n display: block;\n width: fit-content;\n max-width: 100%;\n overflow-x: auto;\n border-collapse: collapse;\n font-size: 0.95em;\n}\n\n.message--assistant th,\n.message--assistant td {\n padding: 6px 10px;\n border: 1px solid var(--_border);\n text-align: left;\n vertical-align: top;\n}\n\n.message--assistant th {\n background: var(--_hover);\n font-weight: 600;\n}\n\n.pending {\n align-self: flex-start;\n display: inline-flex;\n gap: 4px;\n align-items: center;\n padding: 12px 14px;\n background: var(--_assistant-bg);\n border-radius: 14px;\n border-bottom-left-radius: 4px;\n}\n\n.pending-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--_muted);\n animation: ag-ui-pending 1.2s infinite ease-in-out both;\n}\n\n.pending-dot:nth-child(2) {\n animation-delay: 0.16s;\n}\n\n.pending-dot:nth-child(3) {\n animation-delay: 0.32s;\n}\n\n@keyframes ag-ui-pending {\n 0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }\n 40% { opacity: 1; transform: translateY(-3px); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .pending-dot {\n animation: none;\n opacity: 0.6;\n }\n}\n\n/* \u2500\u2500 Thoughts region \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A muted, collapsible chain-of-thought at the top of the answer group: open\n while the model reasons, folded once the answer text starts. */\n.thoughts {\n align-self: stretch;\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 12px;\n color: var(--_muted);\n}\n\n.thoughts-toggle {\n align-self: flex-start;\n border: none;\n padding: 0;\n background: none;\n font: inherit;\n font-size: 12px;\n font-weight: 600;\n color: var(--_muted);\n cursor: pointer;\n}\n\n.thoughts-toggle::before {\n content: var(--_disclosure-expanded) \" \";\n}\n\n.thoughts-toggle[aria-expanded=\"false\"]::before {\n content: var(--_disclosure-collapsed) \" \";\n}\n\n/* A gentle pulse on the label while reasoning is still streaming. */\n.thoughts[data-streaming] .thoughts-label {\n animation: ag-ui-thoughts-pulse 1.4s ease-in-out infinite;\n}\n\n@keyframes ag-ui-thoughts-pulse {\n 0%, 100% { opacity: 0.55; }\n 50% { opacity: 1; }\n}\n\n.thoughts-body {\n margin: 0;\n padding: 4px 0 4px 10px;\n border-left: 2px solid var(--_border);\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: inherit;\n}\n\n.thoughts-body[hidden] {\n display: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .thoughts[data-streaming] .thoughts-label {\n animation: none;\n }\n}\n\n.tool-call {\n align-self: flex-start;\n max-width: 80%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-size: 12px;\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--_tool-bg);\n border: 1px solid var(--_border);\n color: var(--_tool-fg);\n}\n\n/* Wraps, because the name is the only flexible child and every badge the row\n gains is taken out of it. An approved call adds a third fixed badge, which in\n a sidebar-width panel left the name 37px and broke it mid-word. Badges drop to\n their own row instead. */\n.tool-call-head {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.tool-call-name {\n /* An auto basis, and a min-width floor rather than zero: the name may shrink,\n but not below something readable, so wrapping moves a badge instead of\n shredding a word. Breaking anywhere still applies to a name that cannot fit\n on a line of its own, which is what keeps a long unbroken tool name inside\n the card. */\n flex: 1 1 auto;\n min-width: 6ch;\n font-weight: 600;\n overflow-wrap: anywhere;\n}\n\n/* Leading status icon. Empty in the DOM \u2014 the glyph/spinner is drawn\n here from the card's data-status, so it stays themeable. */\n.tool-call-icon {\n flex: none;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n font-size: 12px;\n line-height: 1;\n}\n\n/* Pending: a real spinning ring. Speed is tunable; reduced motion stops it. */\n.tool-call[data-status=\"pending\"] .tool-call-icon {\n border: 2px solid var(--_muted);\n border-top-color: transparent;\n border-radius: 50%;\n animation: ag-ui-tool-spin var(--_tool-spin-duration) linear infinite;\n}\n\n@keyframes ag-ui-tool-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Deferred: no spinner, because nothing is spinning. A steady accent dot, since\n the state is waiting-on-you rather than an outcome. */\n.tool-call[data-status=\"deferred\"] .tool-call-icon {\n border-radius: 50%;\n background: var(--_accent);\n}\n\n/* Settled: a themeable glyph coloured by outcome. */\n.tool-call[data-status=\"done\"] .tool-call-icon::before {\n content: var(--_tool-icon-done);\n color: var(--_success);\n}\n\n.tool-call[data-status=\"error\"] .tool-call-icon::before {\n content: var(--_tool-icon-error);\n color: var(--_danger);\n}\n\n.tool-call[data-status=\"declined\"] .tool-call-icon::before {\n content: var(--_tool-icon-declined);\n color: var(--_muted);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tool-call[data-status=\"pending\"] .tool-call-icon {\n animation: none;\n }\n}\n\n/* Inline display mode: the lightest card. Drop the box chrome so the status row\n reads as one line of the answer; the result toggle still expands below it. */\n:host([data-tool-display=\"inline\"]) .tool-call {\n max-width: 100%;\n background: transparent;\n border: none;\n padding: 2px 0;\n gap: 2px;\n}\n\n.tool-call-status {\n flex: none;\n padding: 1px 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n background: rgba(127, 127, 127, 0.16);\n color: var(--_muted);\n}\n\n.tool-call[data-status=\"deferred\"] .tool-call-status {\n color: var(--_accent);\n}\n\n.tool-call[data-status=\"done\"] .tool-call-status {\n color: var(--_success);\n}\n\n.tool-call[data-status=\"error\"] .tool-call-status {\n color: var(--_danger);\n}\n\n.tool-call[data-status=\"declined\"] .tool-call-status {\n color: var(--_muted);\n}\n\n.tool-call-body {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.tool-call-section {\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n\n/* The heading that tells the two payloads apart. Without it the arguments and\n the result were one run of text and a reader had to guess the boundary. */\n.tool-call-section-label {\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n color: var(--_muted);\n}\n\n.skill-item-token {\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n font-size: 0.92em;\n color: var(--_accent);\n margin-right: 6px;\n}\n\n/* The lasting record of a human decision on a gated call \u2014 without it an\n approved call looks exactly like one that was never gated. */\n.tool-call-decision {\n flex: none;\n font-size: 11px;\n font-style: italic;\n color: var(--_muted);\n}\n\n.tool-call-args,\n.tool-call-result {\n margin: 0;\n padding: 6px 8px;\n max-height: 160px;\n overflow: auto;\n background: var(--_bg);\n border: 1px solid var(--_border);\n border-radius: 6px;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--_fg);\n}\n\n/* A region a host formatter took over, marked by the card. Preformatted\n whitespace is what makes the built-in block read as written, and it is the one\n thing a host cannot want: a table inherits it as mangled cell spacing, and a\n sentence as line breaks nobody typed.\n\n Whitespace only. The card's own face, frame, padding and scroll cap stay,\n because the card is one visual object -- the head row and the status pill are\n monospaced too -- and a region that dropped the family would be the only part\n of it wearing a different one. A host that wants that restyles the\n tool-card-result part, which does not need the formatter at all. */\n.tool-call-args[data-formatted],\n.tool-call-result[data-formatted] {\n white-space: normal;\n}\n\n/* Display modes are pure visibility over one DOM shape, selected from the host\n attribute rather than a value stamped on the card at build time, so flipping\n data-tool-display re-styles cards already on screen. See ToolCallCard.\n\n Default (no attribute) is the full mode: arguments always visible, result\n behind the toggle. */\n.tool-call[data-expanded=\"false\"] .tool-call-section--result {\n display: none;\n}\n\n/* Compact: one toggle over both regions, so a settled card is a single line\n until asked. */\n:host([data-tool-display=\"compact\"]) .tool-call[data-expanded=\"false\"] .tool-call-section {\n display: none;\n}\n\n/* Inline: the result only; the call's arguments are noise at this density. */\n:host([data-tool-display=\"inline\"]) .tool-call .tool-call-section--args {\n display: none;\n}\n\n/* Minimal: the status row and nothing else, so there is no toggle to press. */\n:host([data-tool-display=\"minimal\"]) .tool-call .tool-call-toggle,\n:host([data-tool-display=\"minimal\"]) .tool-call .tool-call-body {\n display: none;\n}\n\n/* A pending card has no result yet, and in the modes where the arguments are\n hidden too there is nothing behind the toggle. Hide the control rather than\n offer one that expands onto nothing. A deferred card is the same, and its\n arguments are shown unconditionally by the rules below. */\n.tool-call[data-status=\"pending\"] .tool-call-toggle,\n.tool-call[data-status=\"deferred\"] .tool-call-toggle,\n:host([data-tool-display=\"inline\"]) .tool-call[data-status=\"pending\"] .tool-call-toggle {\n display: none;\n}\n\n/* The approval prompt for a gated call, rendered inside that call's own card.\n Empty on every card nobody is being asked about, so it collapses instead of\n adding a gap to each one. */\n.tool-call-approval:empty {\n display: none;\n}\n\n.tool-call-approval {\n margin-top: 8px;\n}\n\n/* A card that is asking a question shows what it is asking about, in every\n display mode. Three gated calls of one tool ask the same words, so the\n arguments are the only thing telling them apart, and a density setting must\n not be able to hide the answer to \"which one is this\". */\n:host([data-tool-display=\"minimal\"]) .tool-call[data-status=\"deferred\"] .tool-call-body {\n display: flex;\n}\n\n/* The arguments region only, never every section: the result region carries the\n hidden attribute until a result exists, and a display value here overrides it,\n framing an empty RESULT heading under the question. */\n:host([data-tool-display=\"compact\"]) .tool-call[data-status=\"deferred\"] .tool-call-section--args,\n:host([data-tool-display=\"inline\"]) .tool-call[data-status=\"deferred\"] .tool-call-section--args {\n display: flex;\n}\n\n/* A delegated sub-agent's progress, inside the card that delegated. Empty on\n every card that delegated nothing, so it collapses rather than adding a gap\n to each one -- the same shape the approval slot uses. */\n.tool-call-subagent:empty {\n display: none;\n}\n\n.tool-call-subagent {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.subagent {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n/* The collapsed row is the status and the expander at once, which is what keeps\n a ten-step child one row until somebody opens it. Full width and left-aligned,\n because it is a line of the card rather than a button on it. */\n.subagent-row {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n box-sizing: border-box;\n padding: 2px 0;\n border: none;\n background: none;\n font: inherit;\n text-align: left;\n color: var(--_muted);\n cursor: pointer;\n}\n\n/* Nothing behind the row yet -- a delegation that failed before calling\n anything. Drop the affordances rather than offer a control that expands onto\n an empty region, which is the refusal the card's own toggle already makes. */\n.subagent-row:disabled {\n cursor: default;\n}\n\n.subagent-row::after {\n content: var(--_disclosure-collapsed);\n flex: none;\n margin-left: auto;\n color: var(--_accent);\n}\n\n.subagent-row[aria-expanded=\"true\"]::after {\n content: var(--_disclosure-expanded);\n}\n\n.subagent-row:disabled::after {\n display: none;\n}\n\n/* Empty in the DOM; the glyph is drawn here from the panel's data-phase, so a\n host re-themes it through the same tool-icon custom properties the card uses. */\n.subagent-icon {\n flex: none;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 10px;\n height: 10px;\n font-size: 10px;\n line-height: 1;\n}\n\n/* Anything that is not a terminal phase is the child still working. Selected by\n what it is not, so a phase this client has not heard of still spins rather\n than rendering as a blank. */\n.subagent[data-phase]:not([data-phase=\"finished\"]):not([data-phase=\"failed\"]) .subagent-icon {\n border: 2px solid var(--_muted);\n border-top-color: transparent;\n border-radius: 50%;\n animation: ag-ui-tool-spin var(--_tool-spin-duration) linear infinite;\n}\n\n.subagent[data-phase=\"finished\"] .subagent-icon::before {\n content: var(--_tool-icon-done);\n color: var(--_success);\n}\n\n.subagent[data-phase=\"failed\"] .subagent-icon::before {\n content: var(--_tool-icon-error);\n color: var(--_danger);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .subagent .subagent-icon {\n animation: none;\n }\n}\n\n/* The server's own pre-rendered line. Shrinks and wraps rather than pushing the\n chevron out of the card, which is what a fixed-width sibling in a flex row\n does to a panel at sidebar width. */\n.subagent-status {\n flex: 1 1 auto;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n/* The child's own calls. Indented and ruled, so the nesting is visible without\n a second card frame around it. */\n.subagent-steps {\n display: flex;\n flex-direction: column;\n gap: 2px;\n margin-left: 4px;\n padding-left: 10px;\n border-left: 1px solid var(--_border);\n}\n\n.subagent-steps[hidden] {\n display: none;\n}\n\n.subagent-step {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n color: var(--_muted);\n}\n\n.subagent-step-icon {\n flex: none;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 8px;\n height: 8px;\n font-size: 9px;\n line-height: 1;\n}\n\n/* No outcome yet: the wire says null while the call is in flight, and the\n absence of the attribute is how that arrives here. A hollow ring, not a\n spinner -- several can be on screen at once and the row above already spins. */\n.subagent-step:not([data-ok]) .subagent-step-icon {\n border: 1px solid var(--_muted);\n border-radius: 50%;\n}\n\n.subagent-step[data-ok=\"true\"] .subagent-step-icon::before {\n content: var(--_tool-icon-done);\n color: var(--_success);\n}\n\n.subagent-step[data-ok=\"false\"] .subagent-step-icon::before {\n content: var(--_tool-icon-error);\n color: var(--_danger);\n}\n\n.subagent-step-name {\n flex: 1 1 auto;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n.tool-call-toggle {\n align-self: flex-start;\n border: none;\n padding: 0;\n background: none;\n font: inherit;\n font-weight: 600;\n color: var(--_accent);\n cursor: pointer;\n}\n\n.tool-call-toggle::before {\n content: var(--_disclosure-collapsed) \" \";\n}\n\n.tool-call-toggle[aria-expanded=\"true\"]::before {\n content: var(--_disclosure-expanded) \" \";\n}\n\n/* Resize grips: every edge and every corner, so the panel can be dragged from\n whichever side the reader is already near. Absent entirely where the\n placement is full-bleed, since there is nothing to drag.\n\n The whole set is always laid out. Which edges the *layout* pins is not a\n question the stylesheet needs to answer any more -- it decided where the one\n grip went, and there is no longer one grip. The element still measures and\n stamps data-resize-anchor, because it decides what a drag on a pinned edge\n costs in position and which grip carries the tab stop, but no rule here\n reads it. */\n.resize-handle {\n position: absolute;\n z-index: 2;\n background: transparent;\n touch-action: none;\n}\n\n.resize-handle:focus-visible {\n outline: 2px solid var(--_accent);\n outline-offset: -2px;\n}\n\n/* Corners first in the file and last in the DOM, so they take the pointer\n where they overlap an edge strip. */\n.resize-handle--top-left,\n.resize-handle--top-right,\n.resize-handle--bottom-left,\n.resize-handle--bottom-right {\n width: var(--_grip-corner);\n height: var(--_grip-corner);\n}\n\n.resize-handle--top-left {\n top: 0;\n left: 0;\n cursor: nwse-resize;\n}\n\n.resize-handle--top-right {\n top: 0;\n right: 0;\n cursor: nesw-resize;\n}\n\n.resize-handle--bottom-left {\n bottom: 0;\n left: 0;\n cursor: nesw-resize;\n}\n\n.resize-handle--bottom-right {\n bottom: 0;\n right: 0;\n cursor: nwse-resize;\n}\n\n/* Edge strips, held clear of the corners at both ends so a corner drag is\n never swallowed by the edge next to it. */\n.resize-handle--left,\n.resize-handle--right {\n top: var(--_grip-corner);\n bottom: var(--_grip-corner);\n width: var(--_grip-edge);\n cursor: ew-resize;\n}\n\n.resize-handle--left {\n left: 0;\n}\n\n.resize-handle--right {\n right: 0;\n}\n\n.resize-handle--top,\n.resize-handle--bottom {\n left: var(--_grip-corner);\n right: var(--_grip-corner);\n height: var(--_grip-edge);\n cursor: ns-resize;\n}\n\n.resize-handle--top {\n top: 0;\n}\n\n.resize-handle--bottom {\n bottom: 0;\n}\n\n/* Docked: the placement owns the height, so the horizontal edges and every\n corner are inert and must not advertise a drag that does nothing. The two\n vertical edges remain, which is the same affordance these placements had\n when there was one grip -- now on both sides, since either may be the inner\n one depending on which side the rail is docked to. */\n:host([placement=\"sidebar\"]) .resize-handle--top,\n:host([placement=\"sidebar\"]) .resize-handle--bottom,\n:host([placement=\"sidebar\"]) .resize-handle--top-left,\n:host([placement=\"sidebar\"]) .resize-handle--top-right,\n:host([placement=\"sidebar\"]) .resize-handle--bottom-left,\n:host([placement=\"sidebar\"]) .resize-handle--bottom-right,\n:host([placement=\"side\"]) .resize-handle--top,\n:host([placement=\"side\"]) .resize-handle--bottom,\n:host([placement=\"side\"]) .resize-handle--top-left,\n:host([placement=\"side\"]) .resize-handle--top-right,\n:host([placement=\"side\"]) .resize-handle--bottom-left,\n:host([placement=\"side\"]) .resize-handle--bottom-right {\n display: none;\n}\n\n/* The vertical edges run the full height once no corner shares them. */\n:host([placement=\"sidebar\"]) .resize-handle--left,\n:host([placement=\"sidebar\"]) .resize-handle--right,\n:host([placement=\"side\"]) .resize-handle--left,\n:host([placement=\"side\"]) .resize-handle--right {\n top: 0;\n bottom: 0;\n width: var(--_grip-edge-docked);\n}\n\n/* Full-bleed: nothing to drag. */\n:host([placement=\"full\"]) .resize-handle,\n:host([placement=\"page\"]) .resize-handle {\n display: none;\n}\n\n/* The visible mark on a grip.\n\n Filling the whole strip was what the single corner grip did, and at 14px\n square nobody ever saw it. On a strip running the length of an edge the same\n fill reads as a border the panel grew -- square ended, stopping short of the\n corner radius at both ends, and easily mistaken for a rendering fault rather\n than for something to grab.\n\n So the hit area stays the full strip and the mark is a short pill centred on\n it, which cannot be read as an edge of anything and never meets the radius.\n\n It shows on hover and focus as well as during the drag. With eight grips, a\n mark that appears only once you are already dragging is a mark that never\n told anyone the grips were there. */\n.resize-handle::after {\n content: \"\";\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n border-radius: 999px;\n background: var(--_accent);\n opacity: 0;\n transition: opacity var(--_motion) var(--_ease);\n}\n\n.resize-handle--top::after,\n.resize-handle--bottom::after {\n width: var(--_grip-mark-length);\n height: var(--_grip-mark-thickness);\n}\n\n.resize-handle--left::after,\n.resize-handle--right::after {\n width: var(--_grip-mark-thickness);\n height: var(--_grip-mark-length);\n}\n\n/* A corner has no length to run along, so it gets a dot instead of a pill. */\n.resize-handle--top-left::after,\n.resize-handle--top-right::after,\n.resize-handle--bottom-left::after,\n.resize-handle--bottom-right::after {\n width: var(--_grip-mark-thickness);\n height: var(--_grip-mark-thickness);\n}\n\n.resize-handle:hover::after,\n.resize-handle:focus-visible::after {\n opacity: 0.5;\n}\n\n/* Equal specificity to the pair above, so source order is what makes the drag\n the stronger of the two states. */\n.resize-handle[data-dragging]::after {\n opacity: 0.9;\n}\n\n/* \u2500\u2500 Composer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n One surface owns the border, the background and the focus ring; the field\n and its tool row sit inside it, rather than being siblings stretched to the\n textarea's height. */\n.input-row {\n display: flex;\n padding: 12px;\n border-top: 1px solid var(--_border);\n}\n\n.composer {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 6px 6px 6px 10px;\n background: var(--_input-bg);\n border: 1px solid var(--_border);\n border-radius: var(--_composer-radius);\n transition: border-color var(--_motion) var(--_ease);\n}\n\n.composer:focus-within {\n border-color: var(--_accent);\n}\n\n/* The field grows with its content (sized by #autoGrow) up to the ceiling,\n then scrolls. border-box keeps that measurement stable: with content-box the\n padding would be added to every scrollHeight read and the field would creep\n taller on each keystroke. */\n.input {\n box-sizing: border-box;\n resize: none;\n max-height: var(--_composer-max-height);\n overflow-y: auto;\n padding: 6px 4px 2px;\n background: transparent;\n border: none;\n font: inherit;\n color: inherit;\n outline: none;\n}\n\n.composer-tools {\n display: flex;\n align-items: center;\n gap: 2px;\n}\n\n/* Icon buttons: quiet at rest, so the field is what the eye lands on. */\n.attach-btn,\n.voice-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--_tool-btn-size);\n height: var(--_tool-btn-size);\n padding: 0;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--_muted);\n font: inherit;\n line-height: 1;\n cursor: pointer;\n transition:\n background-color var(--_motion) var(--_ease),\n color var(--_motion) var(--_ease);\n}\n\n.attach-btn:hover,\n.voice-btn:hover {\n background: var(--_hover);\n color: var(--_fg);\n}\n\n.attach-btn:disabled,\n.voice-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n/* The same trap the attachment tray carries a note about, two rules along: an\n author display beats the UA stylesheet's rule for the hidden property, so a\n button the element has explicitly hidden keeps laying out and painting. The\n clip is hidden until a host supplies an upload handler or an attachments URL,\n and without this it is a visible control that cannot do anything.\n\n The mic needs no such rule, and the asymmetry is worth knowing before adding\n one: it is not hidden when unconfigured, it is never built. The voice wiring\n returns before constructing the button, leaving only an empty voice slot that\n is display: contents. A hidden-state rule for the mic would match nothing. */\n.attach-btn[hidden] {\n display: none;\n}\n\n/* Send closes the row on the right: a circle, the only filled control in the\n composer, so \"the thing that acts\" reads at a glance. */\n.send {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: var(--_send-size);\n height: var(--_send-size);\n padding: 0;\n border: none;\n border-radius: 50%;\n background: var(--_accent);\n color: var(--_on-accent);\n font: inherit;\n line-height: 1;\n cursor: pointer;\n transition:\n background-color var(--_motion) var(--_ease),\n transform var(--_motion) var(--_ease-pop);\n}\n\n.send:hover {\n transform: scale(1.08);\n}\n\n.send:active {\n transform: scale(0.92);\n}\n\n.send:disabled {\n opacity: 0.5;\n cursor: default;\n transform: none;\n}\n\n/* The composer button doubles as the Stop control while a run is in flight \u2014\n same circle, different glyph, so nothing moves when a run starts. */\n.send[data-state=\"running\"] {\n background: var(--_muted);\n}\n\n.send[data-state=\"idle\"] .send-stop,\n.send[data-state=\"running\"] .send-send {\n display: none;\n}\n\n/* Glyphs \u2014 one class, painted from the button's own colour so every state\n (hover, recording, running) carries the icon with it. */\n.glyph {\n width: var(--_glyph-size);\n height: var(--_glyph-size);\n fill: none;\n stroke: currentColor;\n stroke-width: var(--_glyph-stroke);\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n\n.glyph--solid {\n fill: currentColor;\n stroke: none;\n}\n\n/* In an icon holder (header brand, launcher) the glyph takes the holder's size\n rather than the composer's. */\n.icon-holder .glyph {\n width: 100%;\n height: 100%;\n}\n\n/* \u2500\u2500 File attachments \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* The picker button sits in the composer's tool row; hidden until\n data-attachments-url. */\n.attach-input {\n display: none;\n}\n\n/* The mic button's mount point; filled only once #wireVoice mounts the\n control. */\n.voice-slot {\n display: contents;\n}\n\n/* Recording: a red tint + a gentle pulse so it's clearly \"live\". */\n.voice-btn[data-state=\"recording\"] {\n background: var(--_danger);\n color: var(--_on-danger);\n animation: ag-ui-voice-pulse 1.2s ease-in-out infinite;\n}\n\n.voice-btn[data-state=\"recording\"]:hover {\n background: var(--_danger);\n color: var(--_on-danger);\n}\n\n@keyframes ag-ui-voice-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.6; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .voice-btn[data-state=\"recording\"] {\n animation: none;\n }\n}\n\n/* Pending-attachments tray, above the input row; collapses (hidden) when empty. */\n.attachment-slot {\n display: contents;\n}\n\n/* The padding is the only separation the tray gets: its slot is display:\n contents, so the tray is a direct child of the .chat column, and that column\n sets no gap. The bottom value keeps a chip off the composer's top edge.\n The inline value here is only the default: the page placement overrides\n padding-inline further down to compute its reading-column gutter, and wins\n on specificity whichever way this declaration is written. Read the two\n together before changing either. */\n.attachment-tray {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 8px 12px;\n}\n\n/* The tray sets the hidden property while empty, and an author display beats\n the UA stylesheet's rule for it, so without this the empty tray keeps\n laying out and its padding is permanent dead space above the composer. */\n.attachment-tray[hidden] {\n display: none;\n}\n\n.attachment-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n\n/* Charts.\n *\n * The SVG scales to the column and carries no colours of its own beyond the\n * series palette, so a host restyles it the same way it restyles everything\n * else. Series colours are custom properties with fallbacks rather than fixed\n * values, and the axis furniture inherits currentColor at low opacity so it\n * reads correctly in either theme without a second palette.\n */\n/* A chart is sized like a message, not like a panel: it takes the width it\n needs and stops, rather than stretching into whatever room the transcript\n has. A message bubble caps at 80% for the same reason -- widening the panel\n should not resize what is already in it. The cap is a token so a host with a\n wide panel and a real reason can raise it; the default is the width this\n renderer has always drawn at. Below the cap the block still fills its column,\n which is what a narrow panel needs. */\n.chart-block {\n align-self: flex-start;\n width: 100%;\n max-width: var(--_chart-max-width);\n margin: 6px 0;\n color: var(--_fg);\n}\n\n/* The drawing fills the block's width and carries its own height in the\n viewBox, so it is laid out rather than magnified: block display keeps the\n inline baseline gap from adding a stripe under every chart. */\n.chart-block svg {\n display: block;\n width: 100%;\n height: auto;\n}\n\n.chart-title {\n margin-bottom: 2px;\n font-size: 0.85em;\n font-weight: 600;\n opacity: 0.85;\n}\n\n.chart-legend {\n display: flex;\n flex-wrap: wrap;\n gap: 4px 12px;\n margin-top: 4px;\n font-size: 0.78em;\n opacity: 0.75;\n}\n\n.chart-legend-item {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n}\n\n.chart-legend-swatch {\n width: 9px;\n height: 9px;\n border-radius: 2px;\n flex: 0 0 auto;\n}\n\n.run-notice {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n align-self: flex-start;\n max-width: 100%;\n margin: 2px 0;\n padding: 3px 10px;\n border: 1px dashed var(--_border);\n border-radius: 999px;\n background: transparent;\n color: var(--_muted);\n font-size: 0.8em;\n line-height: 1.4;\n}\n\n.run-notice-icon {\n flex: none;\n opacity: 0.75;\n}\n\n.run-notice-text {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n/* A chip carries its own text colour because it carries its own background.\n The same chip renders in two places with opposite inherited colours: the\n composer tray gives it the panel's, a sent user bubble gives it the user\n foreground, which is white on the stock light theme and near-invisible\n against the chip. Pairing the colour with the background it belongs to makes\n both placements read alike. Overridden below for an errored chip, which\n keeps its red. */\n.attachment-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n /* Without this the 100% below caps the content box, so the chip still\n overflows its container by its own padding and border. Invisible while a\n character cap kept names short; routine once the container is what bounds\n the name. */\n box-sizing: border-box;\n max-width: 100%;\n padding: 4px 8px;\n border: 1px solid var(--_border);\n border-radius: 999px;\n background: var(--_assistant-bg);\n color: var(--_text);\n font-size: 0.85em;\n position: relative;\n}\n\n.attachment-chip--error {\n border-color: var(--_danger);\n color: var(--_danger);\n}\n\n/* The type mark. Painted from currentColor, so it carries the chip's state\n with it and turns red along with an errored one; muted by opacity rather\n than a colour, which is what keeps that true. Sized from the chip's own text\n rather than --ag-ui-glyph-size, the way an icon holder's glyph takes the\n holder's size: the composer's 18px buttons would make a chip button-height. */\n.attachment-chip-icon {\n display: inline-flex;\n flex: none;\n opacity: 0.75;\n}\n\n.attachment-chip-icon .glyph {\n width: 1.25em;\n height: 1.25em;\n}\n\n/* No character cap: the chip is already max-width 100%, so its container is\n what bounds the name, and a fixed cap only truncated names the chip had room\n for. min-width: 0 is what lets the flex item shrink past its content, so the\n ellipsis appears at the container edge instead of the chip overflowing. */\n.attachment-chip-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n\n.attachment-chip-size {\n color: var(--_muted);\n white-space: nowrap;\n}\n\n.attachment-chip--error .attachment-chip-size {\n color: var(--_danger);\n}\n\n/* The progress bar fills as the file uploads. */\n.attachment-chip-bar {\n flex-basis: 100%;\n height: 3px;\n border-radius: 2px;\n background: var(--_border);\n overflow: hidden;\n}\n\n.attachment-chip-bar-fill {\n height: 100%;\n background: var(--_accent);\n transition: width 0.15s ease;\n}\n\n.attachment-chip-remove,\n.attachment-chip-retry {\n border: none;\n background: none;\n color: inherit;\n cursor: pointer;\n padding: 0;\n line-height: 1;\n opacity: 0.7;\n}\n\n.attachment-chip-remove:hover,\n.attachment-chip-retry:hover {\n opacity: 1;\n}\n\n/* A subtle outline while a file is dragged over the shell. */\n.chat--dragover {\n outline: 2px dashed var(--_accent);\n outline-offset: -4px;\n}\n\n/* Muted \"\u23F9 Stopped\" line after a cancelled run \u2014 a note, not an error bubble. */\n.stopped-note {\n align-self: flex-start;\n color: var(--_muted);\n font-size: 12px;\n padding: 2px 4px;\n}\n\n/* Inline confirmation card \u2014 lives in the transcript, no focus-stealing overlay. */\n.confirm {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--_bg);\n border: 1px solid var(--_accent);\n border-radius: 10px;\n}\n\n.confirm[data-resolved] {\n opacity: 0.7;\n border-color: var(--_border);\n}\n\n.confirm-body {\n font-weight: 600;\n}\n\n.confirm-args {\n margin: 0;\n padding: 8px 10px;\n max-height: 140px;\n overflow: auto;\n font-size: 12px;\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n background: var(--_assistant-bg);\n border-radius: 8px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.confirm-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.confirm-btn {\n border: 1px solid var(--_border);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--_bg);\n color: var(--_fg);\n}\n\n.confirm-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.confirm-btn--confirm {\n border-color: var(--_accent);\n background: var(--_accent);\n color: var(--_on-accent);\n}\n\n/* The session waiver. Deliberately the quietest of the three: it is the widest\n decision on the card, so it should be reachable without being the one the eye\n lands on when the user means to say yes once. */\n.confirm-btn--always {\n font-weight: 500;\n opacity: 0.85;\n}\n\n.confirm-btn--always:hover,\n.confirm-btn--always:focus-visible {\n opacity: 1;\n}\n\n/* Editable arguments on an approval card. A plain field rather than a code\n editor: it holds the JSON a card already displays, and the only interaction\n is correcting a value before letting the call run. */\n.approval-edit {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.approval-args {\n box-sizing: border-box;\n width: 100%;\n resize: vertical;\n border: 1px solid var(--_border);\n border-radius: 8px;\n padding: 8px;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.85em;\n background: var(--_bg);\n color: var(--_fg);\n}\n\n.approval-args:focus-visible {\n border-color: var(--_accent);\n outline: none;\n}\n\n.approval-error {\n font-size: 0.85em;\n color: var(--_danger);\n}\n\n/* Message action row. Sits under a finished assistant bubble.\n\n The wrap is insurance rather than a fix: these buttons are glyph-only, so at\n every width tested they fit on one line and removing the wrap changes\n nothing. It is here because the confirmation row one release earlier did\n overflow when it gained a third button, off the left edge and outside its own\n card, and the difference between the two rows is only that this one's labels\n are icons today. */\n.message-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n /* Negative, and that is the point. The answer group is a flex column with its\n own gap, so a positive margin here pushes the row further from the message\n it acts on than the next card is below it -- the buttons then read as\n belonging to whatever follows. Pulling back inside the gap is what makes\n them the message's own. */\n margin-top: -6px;\n}\n\n/* The row's controls are icon-only, so the box is the whole target. Sized from\n a variable a host can raise, with a floor rather than a fixed height: the\n compact density shrinks the font, and a target that shrinks with it lands\n under the 24px minimum that makes a control reliably tappable. It used to,\n at roughly 20px square. */\n.message-action {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n min-width: var(--_action-size);\n min-height: var(--_action-size);\n padding: 4px;\n border: none;\n border-radius: 6px;\n font: inherit;\n line-height: 1.2;\n cursor: pointer;\n background: transparent;\n color: var(--_muted);\n opacity: 0.75;\n /* The tooltip below is positioned against this box. */\n position: relative;\n}\n\n.message-action:hover,\n.message-action:focus-visible {\n opacity: 1;\n background: var(--_border);\n}\n\n.message-action-icon {\n display: inline-flex;\n width: var(--_action-icon-size);\n height: var(--_action-icon-size);\n}\n\n.message-action-icon .glyph {\n width: 100%;\n height: 100%;\n}\n\n/* The label, drawn rather than left to the browser.\n\n A title attribute is the usual answer and covers only half the readers: it\n never appears on keyboard focus, so tabbing onto an icon-only control shows\n nothing at all. The attribute stays for the pointer users who expect it, and\n this shows the same words on hover and on focus alike.\n\n Left-aligned rather than centred because the row sits at the left edge of an\n answer inside a scrolling column, and a centred tooltip on the first control\n is clipped by that column. Growing rightward keeps it inside.\n\n Not exposed to assistive technology: the button already carries the same\n string as its accessible name, and this would be a second copy of it. */\n.message-action::after {\n content: attr(data-tooltip);\n position: absolute;\n bottom: calc(100% + 4px);\n left: 0;\n z-index: 3;\n padding: 3px 6px;\n border-radius: 4px;\n background: var(--_tooltip-bg);\n color: var(--_tooltip-fg);\n font-size: 11px;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: none;\n opacity: 0;\n transition: opacity var(--_motion) var(--_ease);\n}\n\n.message-action:hover::after,\n.message-action:focus-visible::after {\n opacity: 1;\n}\n\n/* Touch has no hover to reveal it, and a tooltip that latches open under a\n finger covers the answer it belongs to. */\n@media (hover: none) {\n .message-action::after {\n content: none;\n }\n}\n\n.message-action[aria-pressed=\"true\"] {\n opacity: 1;\n color: var(--_accent);\n}\n\n.message-action--confirmed {\n opacity: 1;\n color: var(--_accent);\n}\n\n/* Approval card \u2014 the server-side-tool gate (approve/deny an interrupt). */\n.approval {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--_bg);\n border: 1px solid var(--_accent);\n border-radius: 10px;\n}\n\n.approval[data-resolved] {\n opacity: 0.7;\n border-color: var(--_border);\n}\n\n.approval-body {\n font-weight: 600;\n}\n\n.approval-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.approval-btn {\n border: 1px solid var(--_border);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--_bg);\n color: var(--_fg);\n}\n\n.approval-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.approval-btn--approve {\n border-color: var(--_accent);\n background: var(--_accent);\n color: var(--_on-accent);\n}\n\n/* Question card \u2014 the built-in ask_user prompt (radios and/or free text). */\n.question {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--_bg);\n border: 1px solid var(--_accent);\n border-radius: 10px;\n}\n\n.question[data-resolved] {\n opacity: 0.7;\n border-color: var(--_border);\n}\n\n.question-body {\n font-weight: 600;\n}\n\n.question-options {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.question-choice {\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n}\n\n.question-input {\n box-sizing: border-box;\n width: 100%;\n padding: 8px 10px;\n font: inherit;\n color: var(--_fg);\n background: var(--_bg);\n border: 1px solid var(--_border);\n border-radius: 8px;\n}\n\n.question-input:disabled {\n opacity: 0.6;\n}\n\n.question-actions {\n display: flex;\n justify-content: flex-end;\n}\n\n.question-btn {\n border: 1px solid var(--_accent);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--_accent);\n color: var(--_on-accent);\n}\n\n.question-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n/* Skills \u2014 chips row + the /-command palette, above the input. */\n.skill-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 10px 12px;\n}\n\n.skill-chip {\n border: 1px solid var(--_border);\n border-radius: 999px;\n padding: 4px 12px;\n font: inherit;\n font-size: 0.9em;\n cursor: pointer;\n background: var(--_assistant-bg);\n color: var(--_fg);\n}\n\n.skill-chip:hover {\n border-color: var(--_accent);\n}\n\n/* Follow-up suggestion chips. Deliberately the skill chips' shape rather than a\n second chip vocabulary -- both are \"a question you could ask\", and the only\n difference is who chose it. Inside the transcript, so they scroll with the\n answer they follow instead of hovering above the composer. */\n.suggestions {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-self: stretch;\n}\n\n.suggestion-chip {\n border: 1px solid var(--_border);\n border-radius: 999px;\n padding: 4px 12px;\n font: inherit;\n font-size: 0.9em;\n text-align: left;\n cursor: pointer;\n background: var(--_assistant-bg);\n color: var(--_fg);\n}\n\n.suggestion-chip:hover,\n.suggestion-chip:focus-visible {\n border-color: var(--_accent);\n}\n\n.skill-palette {\n margin: 8px 12px 0;\n display: flex;\n flex-direction: column;\n max-height: 220px;\n overflow: auto;\n background: var(--_bg);\n border: 1px solid var(--_border);\n border-radius: 8px;\n box-shadow: 0 8px 24px rgba(20, 20, 50, 0.16);\n}\n\n.skill-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n align-items: flex-start;\n padding: 8px 12px;\n border: none;\n background: none;\n font: inherit;\n text-align: left;\n cursor: pointer;\n color: var(--_fg);\n}\n\n.skill-item[aria-selected=\"true\"] {\n background: var(--_assistant-bg);\n}\n\n.skill-item-title {\n font-weight: 600;\n}\n\n.skill-item-desc {\n font-size: 0.85em;\n color: var(--_muted);\n}\n\n/* The hint sits directly above the composer's top border, so a zero bottom\n margin left the text touching the divider. */\n.skill-hint {\n margin: 8px 12px;\n font-size: 0.85em;\n line-height: 1.4;\n color: var(--_danger);\n}\n\n/* Chat-history drawer \u2014 a slide-over within the chat panel.\n The hidden attribute stays the single source of truth for open/closed (no\n JS-driven animation state): display is transitioned discretely, so on close\n the drawer stays displayed for the whole exit and is removed only at the end,\n which is what lets the backdrop fade and the panel slide out. */\n.drawer {\n position: absolute;\n inset: 0;\n z-index: 5;\n display: flex;\n transition: visibility var(--_motion) var(--_ease);\n}\n\n/* Closed. The overlay keeps its box (display, not none) so the backdrop and\n panel inside it stay rendered and can transition both ways; visibility is\n what takes the whole subtree out of the tab order, the a11y tree and hit\n testing at rest, and it holds off until the slide has finished. */\n.drawer[hidden] {\n display: flex;\n visibility: hidden;\n pointer-events: none;\n}\n\n.drawer[hidden] .drawer-backdrop {\n opacity: 0;\n}\n\n.drawer[hidden] .drawer-panel {\n transform: translateX(-100%);\n}\n\n.checkpoints {\n position: absolute;\n inset-block-start: 3rem;\n inset-inline: 0.75rem;\n z-index: 6;\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n padding: 0.5rem;\n border: 1px solid var(--_border);\n border-radius: 0.5rem;\n background: var(--_assistant-bg);\n box-shadow: 0 6px 24px rgb(0 0 0 / 12%);\n max-height: 60%;\n overflow-y: auto;\n transform-origin: top center;\n transition:\n opacity var(--_motion) var(--_ease),\n transform var(--_motion) var(--_ease),\n visibility var(--_motion) var(--_ease);\n}\n\n/* Same idiom as the drawer: laid out at rest, hidden by visibility, so the\n popover can animate open and closed. */\n.checkpoints[hidden] {\n display: flex;\n visibility: hidden;\n pointer-events: none;\n opacity: 0;\n transform: scale(0.96) translateY(-6px);\n}\n\n.checkpoints-title {\n font-size: 0.75rem;\n font-weight: 600;\n opacity: 0.7;\n}\n\n.checkpoints-empty {\n padding: 0.5rem 0.25rem;\n font-size: 0.8125rem;\n opacity: 0.7;\n}\n\n/* A row is a label and two buttons, and nothing about the row itself is\n pressable. It used to light up on hover, which is the affordance of something\n clickable and made the buttons look like decoration on a clickable strip. The\n resting surface groups the row instead, so hover can mean what it says: only\n the buttons respond to it.\n\n It wraps for the same reason the tool-call head does. Every child but the label\n is fixed-width, so in a narrow panel the label is the only thing that can give\n -- and a flex-basis of zero lets it give everything. Adding the run id was\n enough to crush \"just now\" to zero pixels: present, correct, and invisible.\n Wrapping puts the buttons on their own line instead. */\n.checkpoint-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 0.5rem;\n padding: 0.3125rem 0.4375rem;\n border-radius: 0.375rem;\n background: var(--_hover);\n}\n\n/* Grows into spare room, and refuses to shrink past the shortest thing it ever\n says. A time is short and bounded, so there is no case for eliding it. */\n.checkpoint-label {\n flex: 1 1 auto;\n min-width: 7ch;\n font-size: 0.8125rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* When the label holds the run's first message, the time moves here: still worth\n showing, no longer what identifies the row. Muted and unshrinkable, so it does\n not compete with the words beside it. */\n.checkpoint-time {\n flex: 0 0 auto;\n font-size: 0.6875rem;\n opacity: 0.7;\n white-space: nowrap;\n}\n\n/* Enough of the run id to tell two runs apart when both say \"just now\". Muted\n and monospaced: it is a reference, not a name. */\n.checkpoint-id {\n flex: 0 0 auto;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.6875rem;\n opacity: 0.55;\n}\n\n/* On the panel's own surface, not the row's: the row now paints the hover token\n itself, and a badge the same colour as what it sits on is not a badge. */\n.checkpoint-branch {\n font-size: 0.6875rem;\n padding: 0 0.375rem;\n border-radius: 999px;\n background: var(--_assistant-bg);\n opacity: 0.8;\n}\n\n/* The two things in the row that actually do something, so they are the two\n things that look like it: a filled surface at rest rather than a transparent\n outline, which on top of the old row highlight was nearly invisible. */\n.checkpoint-action {\n font: inherit;\n font-size: 0.75rem;\n line-height: 1.4;\n cursor: pointer;\n padding: 0.1875rem 0.5625rem;\n border: 1px solid var(--_border);\n border-radius: 0.375rem;\n background: var(--_bg);\n color: inherit;\n transition:\n background var(--_motion) var(--_ease),\n border-color var(--_motion) var(--_ease),\n transform var(--_motion) var(--_ease);\n}\n\n/* Resume is what a reader wants nine times in ten; fork is the deliberate choice\n beside it. Filled and outlined, the same pair the confirmation and approval\n cards already use for their primary and secondary action. */\n.checkpoint-resume {\n font-weight: 600;\n border-color: var(--_accent);\n background: var(--_accent);\n color: var(--_on-accent);\n}\n\n.checkpoint-fork:hover {\n background: var(--_hover);\n border-color: var(--_accent);\n}\n\n/* The filled one cannot go lighter on hover without losing its contrast with the\n white label, so it dims instead. */\n.checkpoint-resume:hover {\n opacity: 0.88;\n}\n\n/* Pressed: a pixel down, so the click is felt as well as seen. */\n.checkpoint-action:active {\n transform: translateY(1px);\n}\n\n/* Keyboard focus was invisible here, in a panel that traps focus and is reached\n by Tab -- so the one navigation path guaranteed to land on these buttons was\n the one with nothing to show for it. */\n.checkpoint-action:focus-visible {\n outline: 2px solid var(--_accent);\n outline-offset: 2px;\n}\n\n.drawer-backdrop {\n position: absolute;\n inset: 0;\n background: rgba(20, 20, 50, 0.32);\n opacity: 1;\n transition: opacity var(--_motion) var(--_ease);\n}\n\n.drawer-panel {\n position: relative;\n display: flex;\n flex-direction: column;\n width: min(300px, 85%);\n height: 100%;\n background: var(--_bg);\n border-right: 1px solid var(--_border);\n box-shadow: var(--_shadow);\n overflow: hidden;\n transform: none;\n transition: transform var(--_motion) var(--_ease);\n}\n\n.drawer-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--_space);\n padding: var(--_pad);\n border-bottom: 1px solid var(--_border);\n}\n\n.drawer-title {\n font-weight: 600;\n}\n\n.drawer-new {\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n background: var(--_bg);\n color: var(--_accent);\n padding: 4px 10px;\n font: inherit;\n font-size: 0.85em;\n cursor: pointer;\n}\n\n.drawer-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n}\n\n.drawer-empty {\n padding: var(--_pad);\n font-size: 0.9em;\n color: var(--_muted);\n}\n\n.drawer-row {\n display: flex;\n align-items: stretch;\n border-bottom: 1px solid var(--_border);\n}\n\n.drawer-row--active {\n background: var(--_assistant-bg);\n}\n\n.drawer-row-select {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.drawer-row-title {\n font-weight: 600;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.drawer-row-time {\n font-size: 0.72em;\n color: var(--_muted);\n}\n\n.drawer-row-preview {\n font-size: 0.8em;\n color: var(--_muted);\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.drawer-row-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 0 6px;\n}\n\n.drawer-row-rename,\n.drawer-row-delete {\n border: none;\n background: none;\n color: var(--_muted);\n font-size: 0.9em;\n padding: 4px;\n cursor: pointer;\n}\n\n.drawer-rename-input {\n flex: 1;\n min-width: 0;\n margin: 6px 10px;\n padding: 4px 8px;\n border: 1px solid var(--_accent);\n border-radius: 6px;\n background: var(--_input-bg);\n color: var(--_fg);\n font: inherit;\n}\n\n.drawer-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n font-size: 0.85em;\n}\n\n.drawer-confirm-label {\n color: var(--_danger);\n}\n\n.drawer-confirm-yes {\n border: none;\n border-radius: 6px;\n background: var(--_danger);\n color: var(--_on-danger);\n padding: 3px 10px;\n font: inherit;\n cursor: pointer;\n}\n\n.drawer-confirm-no {\n border: 1px solid var(--_border);\n border-radius: 6px;\n background: none;\n color: inherit;\n padding: 3px 10px;\n font: inherit;\n cursor: pointer;\n}\n\n/* Embedded placement: an inline, flush side panel rather than a dimmed,\n floating slide-over. */\n:host([placement=\"embedded\"]) .drawer-backdrop {\n background: none;\n}\n\n:host([placement=\"embedded\"]) .drawer-panel {\n width: 100%;\n border-right: none;\n box-shadow: none;\n}\n";
1
+ export declare const STYLES = "\n/* \u2500\u2500 Token defaults \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Every public --ag-ui-* token is read here into a private --_* alias, and\n only the alias is used by the rules below.\n\n The indirection is what makes ancestor theming work. Declaring the public\n name on :host would set it on the host element, and a value on an element\n always beats one inherited from an ancestor, so tokens put on a wrapper\n would have no effect. Reading the public name with the default as a var()\n fallback leaves it undeclared on the element, so an ancestor's value\n inherits normally while one aimed at the element still wins.\n\n Two invariants hold this together:\n 1. No rule outside this file's :host blocks may reference a public name\n directly. A public name read outside these blocks resolves to nothing and\n silently drops its declaration rather than erroring, so the miss shows up\n as a missing colour, not a failure.\n 2. Every --_* alias in use is declared here. Aliases inherit like any custom\n property, so an undeclared one would pick up a same-named property from\n the host page; declaring it on :host shields the shadow tree from that. */\n:host {\n /* Colors */\n --_bg: var(--ag-ui-bg, #ffffff);\n --_fg: var(--ag-ui-fg, #1a1a2e);\n --_accent: var(--ag-ui-accent, #4f46e5);\n --_user-bg: var(--ag-ui-user-bg, #4f46e5);\n --_user-fg: var(--ag-ui-user-fg, #ffffff);\n --_assistant-bg: var(--ag-ui-assistant-bg, #f1f1f6);\n --_hover: var(--ag-ui-hover, #e7e7ee);\n --_input-bg: var(--ag-ui-input-bg, var(--_bg));\n --_tool-bg: var(--ag-ui-tool-bg, var(--_assistant-bg));\n --_tool-fg: var(--ag-ui-tool-fg, var(--_accent));\n --_header-bg: var(--ag-ui-header-bg, var(--_accent));\n --_header-fg: var(--ag-ui-header-fg, #ffffff);\n --_border: var(--ag-ui-border, #e2e2ec);\n --_radius: var(--ag-ui-radius, 12px);\n\n /* Body text and raised chrome, read only by the code-block copy button.\n The defaults restate what it inherits, so a host that sets neither sees\n no repaint \u2014 see the note on .code-copy. */\n --_text: var(--ag-ui-text, var(--_fg));\n --_surface: var(--ag-ui-surface, transparent);\n\n /* Message action row: the control box and the mark inside it. The box has a\n floor of 24px so it stays a reliable target at every density. */\n --_action-size: var(--ag-ui-action-size, 28px);\n /* The header's own controls. Sized rather than left to their contents: a row\n of buttons each as wide as the glyph inside it comes out uneven, and uneven\n buttons two pixels apart read as one smudge rather than five controls. */\n --_header-btn-size: var(--ag-ui-header-btn-size, 30px);\n --_header-gap: var(--ag-ui-header-gap, 4px);\n --_action-icon-size: var(--ag-ui-action-icon-size, 15px);\n --_tooltip-bg: var(--ag-ui-tooltip-bg, #1f2430);\n --_tooltip-fg: var(--ag-ui-tooltip-fg, #f5f6fa);\n\n /* Text and marks drawn on top of the accent and danger fills. Separate\n tokens because they are not free: a host that themes the accent to a pale\n colour has white-on-pale with no way to correct it, which is the one\n theming change that makes a control unreadable rather than merely off. */\n --_on-accent: var(--ag-ui-on-accent, #ffffff);\n --_on-danger: var(--ag-ui-on-danger, #ffffff);\n\n /* Resize grips: the corner squares and the edge strips between them. Wide\n enough to hit without being wide enough to swallow a click on the content\n underneath, and adjustable for a host with a coarser pointer. */\n --_grip-corner: var(--ag-ui-grip-corner, 14px);\n --_grip-edge: var(--ag-ui-grip-edge, 6px);\n --_grip-edge-docked: var(--ag-ui-grip-edge-docked, 8px);\n /* The mark drawn inside a grip, as opposed to the area it answers to. */\n --_grip-mark-length: var(--ag-ui-grip-mark-length, 28px);\n --_grip-mark-thickness: var(--ag-ui-grip-mark-thickness, 3px);\n\n /* Status accents for tool-call cards. */\n --_success: var(--ag-ui-success, #15803d);\n --_danger: var(--ag-ui-danger, #b91c1c);\n --_muted: var(--ag-ui-muted, #6b7280);\n\n /* Tool-call status icon glyphs (override to re-theme) + spinner speed.\n The pending state is the animated ring; the settled states use these. */\n --_tool-icon-done: var(--ag-ui-tool-icon-done, \"\u2713\");\n --_tool-icon-error: var(--ag-ui-tool-icon-error, \"\u2715\");\n --_tool-icon-declined: var(--ag-ui-tool-icon-declined, \"\u2298\");\n\n /* Disclosure marks on every expandable row. Tokenised for the same reason\n the status icons above are: a host re-theming one set and not the other\n ends up with two vocabularies in one transcript. */\n --_disclosure-collapsed: var(--ag-ui-disclosure-collapsed, \"\u25B8\");\n --_disclosure-expanded: var(--ag-ui-disclosure-expanded, \"\u25BE\");\n --_tool-spin-duration: var(--ag-ui-tool-spin-duration, 0.7s);\n\n /* Answer well (opt-in via data-answer-well) \u2014 boxes a whole assistant turn. */\n --_well-bg: var(--ag-ui-well-bg, transparent);\n --_well-border: var(--ag-ui-well-border, var(--_border));\n\n /* Surface \u2014 set --ag-ui-shadow: none for a flush, embedded panel. */\n --_shadow: var(--ag-ui-shadow, 0 12px 32px rgba(20, 20, 50, 0.18));\n --_font: var(--ag-ui-font, inherit);\n --_font-size: var(--ag-ui-font-size, 14px);\n --_code-font: var(--ag-ui-code-font, ui-monospace, \"SF Mono\", Menlo, monospace);\n\n /* Header / launcher icon box. */\n --_icon-size: var(--ag-ui-icon-size, 22px);\n --_icon-radius: var(--ag-ui-icon-radius, 4px);\n\n /* Composer \u2014 one bordered surface holding the field and its tool row. */\n --_composer-radius: var(--ag-ui-composer-radius, 14px);\n --_composer-max-height: var(--ag-ui-composer-max-height, 40vh);\n --_tool-btn-size: var(--ag-ui-tool-btn-size, 30px);\n --_send-size: var(--ag-ui-send-size, 30px);\n --_glyph-size: var(--ag-ui-glyph-size, 18px);\n --_glyph-stroke: var(--ag-ui-glyph-stroke, 1.75);\n\n /* How wide a chart is allowed to get. A cap rather than a width: below it\n the chart fills its column, and above it a wider panel is just a wider\n panel. */\n --_chart-max-width: var(--ag-ui-chart-max-width, 480px);\n\n /* Unread badge on the launcher. */\n --_badge-bg: var(--ag-ui-badge-bg, var(--_danger));\n --_badge-fg: var(--ag-ui-badge-fg, #ffffff);\n --_badge-size: var(--ag-ui-badge-size, 18px);\n --_badge-font-size: var(--ag-ui-badge-font-size, 11px);\n\n /* The floating launcher a collapsed widget shrinks to. */\n --_launcher-size: var(--ag-ui-launcher-size, 56px);\n --_launcher-bg: var(--ag-ui-launcher-bg, var(--_header-bg));\n --_launcher-fg: var(--ag-ui-launcher-fg, var(--_header-fg));\n --_launcher-radius: var(--ag-ui-launcher-radius, 50%);\n --_launcher-icon-size: var(--ag-ui-launcher-icon-size, 26px);\n --_launcher-inset: var(--ag-ui-launcher-inset, auto 0 0 auto);\n\n /* Motion. One duration and two curves drive every collapse, expand and\n slide-over, so the widget accelerates and settles as one thing. The\n default curve decelerates into place; the pop curve overshoots slightly,\n for something arriving. */\n --_motion: var(--ag-ui-motion, 0.28s);\n --_ease: var(--ag-ui-ease, cubic-bezier(0.32, 0.72, 0, 1));\n --_ease-pop: var(--ag-ui-ease-pop, cubic-bezier(0.34, 1.36, 0.64, 1));\n\n /* Spacing \u2014 the density preset overrides these. */\n --_space: var(--ag-ui-space, 10px);\n --_pad: var(--ag-ui-pad, 16px);\n --_msg-pad: var(--ag-ui-msg-pad, 8px 12px);\n --_msg-radius: var(--ag-ui-msg-radius, 14px);\n\n /* Edges of the viewport the host has already spent, and that a fixed\n placement must therefore stay out of: a sticky nav bar, a docked toolbar,\n a device's safe area. Four longhands rather than one shorthand because a\n custom property is a token stream and CSS cannot index one -- the height\n arithmetic below needs the vertical pair on its own.\n\n Every fixed placement derives from these, and the heights subtract them in\n one place. A host that reserved its chrome by restating --ag-ui-inset per\n placement had to keep --ag-ui-height in step by hand, and forgetting it\n overflowed the panel past the bottom of the screen with nothing to say so.\n\n These take env(safe-area-inset-*) verbatim, which is what a full-bleed\n placement wants on a device with a notch. */\n --_viewport-inset-top: var(--ag-ui-viewport-inset-top, 0px);\n --_viewport-inset-right: var(--ag-ui-viewport-inset-right, 0px);\n --_viewport-inset-bottom: var(--ag-ui-viewport-inset-bottom, 0px);\n --_viewport-inset-left: var(--ag-ui-viewport-inset-left, 0px);\n /* What is left of the viewport once the host's chrome is taken out, on both\n axes, so a placement never composes this itself and no host can subtract\n an edge from the position and forget it in the height.\n\n Settable in their own right, and that is not only convention: an on-screen\n keyboard changes no viewport-percentage length -- not vh, not dvh, not svh\n -- so a full-bleed panel on a phone has to be told the height rather than\n deriving it. The value to publish there is the visual viewport's. */\n --_viewport-height: var(--ag-ui-viewport-height, var(--_visual-viewport-height));\n /* The measured height of the part of the screen the user can actually see,\n written by the element from the visual viewport and falling back to the\n layout viewport where nothing has measured yet.\n\n This is the on-screen keyboard, and it needs measuring because no CSS\n length describes it: an on-screen keyboard has no effect on any\n viewport-percentage unit, so 100vh, 100dvh and 100svh are all the same\n number with the keyboard up as without it. A full-bleed panel sized from\n any of them puts its composer behind the keyboard the user is typing into.\n\n Separate from the token above so a host that states the usable height\n outright still wins: the element writes this one inline, and an inline\n value would otherwise outrank the host's own rule. */\n --_visual-viewport-height: var(\n --ag-ui-visual-viewport-height,\n calc(100vh - var(--_viewport-inset-top) - var(--_viewport-inset-bottom))\n );\n /* How much of the layout viewport is hidden below the visible one, measured\n and written by the element alongside the height above.\n\n A shorter panel is not enough on its own for anything anchored to the\n bottom. A floating widget is positioned against the layout viewport, so\n with a keyboard up its bottom edge -- and the launcher that lives at that\n corner -- sits behind the keyboard however tall the panel is. This is what\n lifts it clear. */\n /* Two tokens, for the same reason the height above has two: the element\n writes the measurement inline, and a host that states its own value needs\n a knob that outranks that write rather than one the next write replaces.\n A host wanting no keyboard lift at all sets --ag-ui-keyboard-inset: 0px. */\n --_keyboard-inset: var(--ag-ui-keyboard-inset, var(--_visual-viewport-inset-bottom));\n --_visual-viewport-inset-bottom: var(--ag-ui-visual-viewport-inset-bottom, 0px);\n --_viewport-width: var(\n --ag-ui-viewport-width,\n calc(100vw - var(--_viewport-inset-left) - var(--_viewport-inset-right))\n );\n\n /* Layout \u2014 override from outside to dock the widget anywhere.\n Set --ag-ui-position: static (and place this element in your own\n grid/flex layout) to embed it in the page flow instead of floating. */\n --_position: var(--ag-ui-position, fixed);\n --_z-index: var(--ag-ui-z-index, 2147483000);\n --_width: var(--ag-ui-width, 380px);\n --_height: var(--ag-ui-height, 560px);\n /* The gutter a resting floating panel keeps between itself and the edge of\n the box the host left free. One number, because the inset below spends it\n and the cap beneath has to know it was spent. */\n --_edge-gutter: var(--ag-ui-edge-gutter, 24px);\n --_inset: var(\n --ag-ui-inset,\n auto calc(var(--_edge-gutter) + var(--_viewport-inset-right))\n calc(var(--_edge-gutter) + var(--_viewport-inset-bottom) + var(--_keyboard-inset)) auto\n );\n /* The cap is what the host left free, less the *one* gutter the inset above\n spends on the anchored edge -- not two, and not none.\n\n Two was the first answer and it was felt on one axis only. The arithmetic\n says why: the default panel is 560 tall against a cap of the viewport\n minus 48, which on an 800px screen with a header reserved is 72px of\n headroom, so the height reaches its cap almost immediately; the default\n width is 380 against a cap near 1230, which is 850px nothing ever reaches.\n Once the size is capped a grip cannot grow the panel, so a pull on the\n anchored edge is written as position instead and the panel travels, and a\n pull on the free edge stops a whole gutter short of an edge a drag can\n reach.\n\n None was the correction, and it overshot: the panel is anchored bottom-\n right with a gutter already spent there, so a cap of the full usable\n height puts the far edge exactly one gutter outside it. On an 800px screen\n with nothing reserved that is a top of -24 -- the header, and every\n control in it, off the top of the window. Reserve a 120px header and it\n sits inside that instead, which is the one thing the reservation exists to\n prevent.\n\n One is the fixed point of both complaints. A resting panel grown to the\n cap runs from the usable near edge to its gutter on the far one, so the\n near edge is reachable by a resize exactly as it is by a drag, and neither\n can put any part of the panel outside the box. */\n --_max-width: var(--ag-ui-max-width, calc(var(--_viewport-width) - var(--_edge-gutter)));\n --_max-height: var(--ag-ui-max-height, calc(var(--_viewport-height) - var(--_edge-gutter)));\n /* Reading-column width for placement=\"page\" (full-bleed, centred content). */\n --_content-max-width: var(--ag-ui-content-max-width, 820px);\n /* Slim rail the sidebar placement collapses to. Only that placement reads\n it, but it is declared here so every alias has a default in one place. */\n --_rail-width: var(--ag-ui-rail-width, 52px);\n /* Width of the docked conversation list on a full-page chat. */\n --_threads-rail-width: var(--ag-ui-threads-rail-width, 280px);\n\n position: var(--_position);\n inset: var(--_inset);\n z-index: var(--_z-index);\n width: var(--_width);\n max-width: var(--_max-width);\n height: var(--_height);\n max-height: var(--_max-height);\n display: flex;\n font-family: var(--_font);\n font-size: var(--_font-size);\n color: var(--_fg);\n}\n\n/* \u2500\u2500 Themes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Themes only re-set the colour variables; layout/spacing are unaffected.\n theme=\"auto\" follows the OS via prefers-color-scheme. */\n:host([theme=\"dark\"]) {\n --_bg: var(--ag-ui-bg, #15151f);\n --_fg: var(--ag-ui-fg, #e8e8f2);\n --_assistant-bg: var(--ag-ui-assistant-bg, #25253a);\n --_hover: var(--ag-ui-hover, #303049);\n --_header-bg: var(--ag-ui-header-bg, #1f1f30);\n --_header-fg: var(--ag-ui-header-fg, #e8e8f2);\n --_border: var(--ag-ui-border, #33334a);\n --_muted: var(--ag-ui-muted, #9aa0b4);\n --_shadow: var(--ag-ui-shadow, 0 12px 32px rgba(0, 0, 0, 0.5));\n}\n\n@media (prefers-color-scheme: dark) {\n :host([theme=\"auto\"]) {\n --_bg: var(--ag-ui-bg, #15151f);\n --_fg: var(--ag-ui-fg, #e8e8f2);\n --_assistant-bg: var(--ag-ui-assistant-bg, #25253a);\n --_hover: var(--ag-ui-hover, #303049);\n --_header-bg: var(--ag-ui-header-bg, #1f1f30);\n --_header-fg: var(--ag-ui-header-fg, #e8e8f2);\n --_border: var(--ag-ui-border, #33334a);\n --_muted: var(--ag-ui-muted, #9aa0b4);\n --_shadow: var(--ag-ui-shadow, 0 12px 32px rgba(0, 0, 0, 0.5));\n }\n}\n\n/* A terminal-flavoured \"code\" theme: dark, monospace, green accent. */\n:host([theme=\"code\"]) {\n --_bg: var(--ag-ui-bg, #0d1117);\n --_fg: var(--ag-ui-fg, #c9d1d9);\n --_accent: var(--ag-ui-accent, #3fb950);\n --_user-bg: var(--ag-ui-user-bg, #238636);\n --_assistant-bg: var(--ag-ui-assistant-bg, #161b22);\n --_hover: var(--ag-ui-hover, #21262d);\n --_header-bg: var(--ag-ui-header-bg, #010409);\n --_header-fg: var(--ag-ui-header-fg, #c9d1d9);\n --_border: var(--ag-ui-border, #30363d);\n --_muted: var(--ag-ui-muted, #8b949e);\n --_font: var(--ag-ui-font, var(--_code-font));\n --_shadow: var(--ag-ui-shadow, 0 12px 32px rgba(0, 0, 0, 0.6));\n}\n\n/* \u2500\u2500 Density \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host([density=\"compact\"]) {\n --_font-size: var(--ag-ui-font-size, 13px);\n --_space: var(--ag-ui-space, 6px);\n --_pad: var(--ag-ui-pad, 10px);\n --_msg-pad: var(--ag-ui-msg-pad, 5px 9px);\n --_msg-radius: var(--ag-ui-msg-radius, 10px);\n}\n\n/* \u2500\u2500 Placement presets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host([placement=\"bottom-left\"]) {\n --_inset: var(\n --ag-ui-inset,\n auto auto calc(var(--_edge-gutter) + var(--_viewport-inset-bottom) + var(--_keyboard-inset))\n calc(var(--_edge-gutter) + var(--_viewport-inset-left))\n );\n}\n\n:host([placement=\"side\"]) {\n --_inset: var(--ag-ui-inset, var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);\n --_width: var(--ag-ui-width, 420px);\n --_height: var(--ag-ui-height, var(--_viewport-height));\n --_max-height: var(--ag-ui-max-height, var(--_viewport-height));\n --_radius: var(--ag-ui-radius, 0);\n}\n\n:host([placement=\"full\"]) {\n --_inset: var(\n --ag-ui-inset,\n var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) var(--_viewport-inset-left)\n );\n --_width: var(--ag-ui-width, var(--_viewport-width));\n --_height: var(--ag-ui-height, var(--_viewport-height));\n --_max-width: var(--ag-ui-max-width, var(--_viewport-width));\n --_max-height: var(--ag-ui-max-height, var(--_viewport-height));\n --_radius: var(--ag-ui-radius, 0);\n}\n\n/* Page: full-bleed background with a centred reading column capped at\n --ag-ui-content-max-width, where \"full\" is edge-to-edge and left-aligned.\n The column comes from symmetric auto padding on the scroll area and\n composer rather than a per-row wrapper, so user pills still right-align and\n the assistant well spans the column. */\n:host([placement=\"page\"]) {\n --_inset: var(\n --ag-ui-inset,\n var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) var(--_viewport-inset-left)\n );\n --_width: var(--ag-ui-width, var(--_viewport-width));\n --_height: var(--ag-ui-height, var(--_viewport-height));\n --_max-width: var(--ag-ui-max-width, var(--_viewport-width));\n --_max-height: var(--ag-ui-max-height, var(--_viewport-height));\n --_radius: var(--ag-ui-radius, 0);\n}\n\n:host([placement=\"page\"]) .messages {\n padding-inline: max(var(--_pad), calc((100% - var(--_content-max-width)) / 2));\n}\n\n:host([placement=\"page\"]) .input-row {\n padding-inline: max(12px, calc((100% - var(--_content-max-width)) / 2));\n}\n\n/* The rows between the message list and the composer line up with the column\n too. Chips and tray are padding-based while palette and hint are\n margin-based, so each gets its own inline axis nudged by the same gutter. */\n:host([placement=\"page\"]) .skill-chips,\n:host([placement=\"page\"]) .attachment-tray {\n padding-inline: max(12px, calc((100% - var(--_content-max-width)) / 2));\n}\n\n:host([placement=\"page\"]) .skill-palette,\n:host([placement=\"page\"]) .skill-hint {\n margin-inline: max(12px, calc((100% - var(--_content-max-width)) / 2));\n}\n\n/* In the reading column the assistant well uses the full width; the user\n message stays a right-aligned pill (its default align-self + max-width). */\n:host([placement=\"page\"]) .message--assistant {\n max-width: 100%;\n}\n\n/* Small viewports: one shape, reached from whichever placement the host chose.\n\n A phone is not an eighth placement, it is an override that collapses the\n others onto one of them. The host picked a placement for the desktop it was\n designing; a 380x560 panel with a 24px margin is not a smaller version of\n that decision, it is most of the screen with a frame drawn round it.\n\n The breakpoint is a literal because a custom property cannot be read in a\n media query -- which is exactly why there is an opt-out. Everything the block\n sets is a token a host can re-state, but the *trigger* is unreachable, so a\n host whose layout wants a different threshold, or none, sets\n data-small-viewport=\"off\" and keeps its desktop shape at every width. 600px is above every common phone in portrait and below every\n tablet in landscape, and it is a width rather than a pointer test on purpose:\n a touch laptop is coarse-pointered and wide, a narrow desktop window is\n fine-pointered and small, and conflating the two gets both wrong. What the\n pointer decides is which controls make sense, further down.\n\n Two placements are left alone. \"page\" is already this shape. \"embedded\" sits\n in a box the host sized and placed, and taking that over would break the app\n shell it was embedded into -- the host is the only party that knows whether\n its column should become the whole screen. */\n@media (max-width: 600px) {\n :host([placement=\"floating\"]:not([data-small-viewport=\"off\"])),\n :host([placement=\"bottom-left\"]:not([data-small-viewport=\"off\"])),\n :host([placement=\"sidebar\"]:not([data-small-viewport=\"off\"])),\n :host([placement=\"side\"]:not([data-small-viewport=\"off\"])),\n :host(:not([placement]):not([data-small-viewport=\"off\"])),\n :host([placement=\"\"]:not([data-small-viewport=\"off\"])) {\n --_inset: var(\n --ag-ui-inset,\n var(--_viewport-inset-top) var(--_viewport-inset-right)\n calc(var(--_viewport-inset-bottom) + var(--_keyboard-inset))\n var(--_viewport-inset-left)\n );\n --_width: var(--ag-ui-width, var(--_viewport-width));\n --_height: var(--ag-ui-height, var(--_viewport-height));\n --_max-width: var(--ag-ui-max-width, var(--_viewport-width));\n --_max-height: var(--ag-ui-max-height, var(--_viewport-height));\n --_radius: var(--ag-ui-radius, 0);\n --_shadow: var(--ag-ui-shadow, none);\n }\n\n /* Nothing to resize once the panel is the screen, and the grips would sit\n under the thumbs holding the phone. */\n :host([placement=\"floating\"]:not([data-small-viewport=\"off\"])) .resize-handle,\n :host([placement=\"bottom-left\"]:not([data-small-viewport=\"off\"])) .resize-handle,\n :host([placement=\"sidebar\"]:not([data-small-viewport=\"off\"])) .resize-handle,\n :host([placement=\"side\"]:not([data-small-viewport=\"off\"])) .resize-handle,\n :host(:not([placement]):not([data-small-viewport=\"off\"])) .resize-handle,\n :host([placement=\"\"]:not([data-small-viewport=\"off\"])) .resize-handle {\n display: none;\n }\n}\n\n/* Sidebar: a full-height docked panel that slides open/closed and\n collapses to a slim icon rail (not the floating launcher). Docked right by\n default; data-side=\"left\" docks it left. Overlay by default \u2014 set\n --ag-ui-position: static (and place this element in your own layout) for a\n host-managed push instead. */\n:host([placement=\"sidebar\"]) {\n --_inset: var(--ag-ui-inset, var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);\n --_width: var(--ag-ui-width, 420px);\n --_height: var(--ag-ui-height, var(--_viewport-height));\n --_max-height: var(--ag-ui-max-height, var(--_viewport-height));\n --_radius: var(--ag-ui-radius, 0);\n transition: width var(--_motion) var(--_ease);\n}\n\n:host([placement=\"sidebar\"][data-side=\"left\"]) {\n --_inset: var(--ag-ui-inset, var(--_viewport-inset-top) auto var(--_viewport-inset-bottom) var(--_viewport-inset-left));\n}\n\n/* The docked panel is pinned to the edge it docks against rather than filling\n the host as a flex child. Collapsing shrinks the host to the rail width, and\n a flex child would be squashed to 52px on the way out instead of sliding out\n at full width. */\n/* The panel is taken out of flow so the collapse can slide it out at full\n width; see the note above. That makes the host its containing block, and the\n host is only one by accident: it is position: fixed by default. A host that\n takes the documented route to a pushed layout instead of an overlay sets\n --ag-ui-position: static, and a static element establishes nothing -- so the\n panel resolved against the initial containing block, landed at the document\n origin, and scrolled away with the page. Docked left it pinned to the\n document's left edge rather than the host's.\n\n It looked correct wherever it was first tried, because a full-height column\n at the top of an unscrolled document is exactly where those two answers\n coincide. Containment makes the host a containing block whatever its\n position, so the panel stays in the box the host was given. It is a no-op in\n the default case, which is already positioned. */\n:host([placement=\"sidebar\"]) {\n contain: layout;\n}\n\n:host([placement=\"sidebar\"]) .chat {\n position: absolute;\n inset: 0 0 0 auto;\n width: var(--_width);\n transform-origin: right center;\n}\n\n:host([placement=\"sidebar\"][data-side=\"left\"]) .chat {\n inset: 0 auto 0 0;\n transform-origin: left center;\n}\n\n/* Collapsed sidebar: shrink the host to the rail width and slide the panel out\n through the edge it docks against. Higher specificity than the generic\n collapse rules, so it wins regardless of source order. */\n/* The rail is the full height of what the host left, not of the screen. It\n said 100vh and pinned its own bottom, which put it under any chrome the host\n had reserved -- and the icon lives at the top of the rail, so the one control\n that reopens the panel was the first thing to disappear behind a sticky\n header. */\n:host([placement=\"sidebar\"][collapsed]) {\n width: var(--_rail-width);\n height: var(--_viewport-height);\n max-height: var(--_viewport-height);\n pointer-events: auto;\n}\n\n:host([placement=\"sidebar\"][collapsed]) .chat {\n transform: translateX(100%);\n}\n\n:host([placement=\"sidebar\"][data-side=\"left\"][collapsed]) .chat {\n transform: translateX(-100%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host([placement=\"sidebar\"]),\n :host([placement=\"sidebar\"]) .chat {\n transition: none;\n }\n}\n\n/* \u2500\u2500 Launcher \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n What a collapsed widget shrinks to: a round floating button by default, the\n sidebar's slim edge rail under that placement. A sibling of the panel, so it\n survives the panel being hidden, and the only part of a collapsed widget\n that takes pointer events. */\n.launcher {\n display: flex;\n position: absolute;\n inset: var(--_launcher-inset);\n align-items: center;\n justify-content: center;\n width: var(--_launcher-size);\n height: var(--_launcher-size);\n padding: 0;\n border: none;\n border-radius: var(--_launcher-radius);\n background: var(--_launcher-bg);\n color: var(--_launcher-fg);\n box-shadow: var(--_shadow);\n font: inherit;\n cursor: pointer;\n pointer-events: auto;\n /* A touch drag on the launcher moves it; without this the page scrolls under\n the finger instead and the launcher never moves at all. */\n touch-action: none;\n opacity: 0;\n transform: scale(0.4);\n visibility: hidden;\n transition:\n opacity var(--_motion) var(--_ease),\n transform var(--_motion) var(--_ease-pop),\n visibility var(--_motion) var(--_ease);\n}\n\n/* The launcher grows out of the corner the panel shrank into. It stays laid\n out at rest rather than display:none, which is what lets it animate both in\n and out: an unrendered element has no before-change style to transition\n from, and one flipping display to none cannot transition at all. visibility\n keeps it unpaintable, untabbable and unclickable in between, so the expanded\n panel's own controls underneath stay reachable. */\n:host([collapsed]) .launcher {\n opacity: 1;\n transform: none;\n visibility: visible;\n}\n\n:host([collapsed]) .launcher:hover {\n transform: scale(1.06);\n}\n\n:host([collapsed]) .launcher:active {\n transform: scale(0.94);\n}\n\n/* A drag is a press and a hover at the same time, so both scale rules above are\n live throughout it. Cancelling them is what keeps the launcher the size of\n the thing under the pointer while it travels; the position itself comes from\n inset, which nothing here transitions, so it tracks the pointer exactly. */\n:host([collapsed]) .launcher[data-dragging] {\n transform: none;\n cursor: grabbing;\n}\n\n.launcher .icon-holder {\n width: var(--_launcher-icon-size);\n height: var(--_launcher-icon-size);\n}\n\n/* The unread badge rides the launcher's top-right corner. The ring in the\n widget's own background is what separates it from the launcher underneath,\n whatever colour the host themes either one. */\n.launcher-badge {\n position: absolute;\n top: -2px;\n right: -2px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n min-width: var(--_badge-size);\n height: var(--_badge-size);\n padding: 0 5px;\n border-radius: 999px;\n background: var(--_badge-bg);\n color: var(--_badge-fg);\n font-size: var(--_badge-font-size);\n font-weight: 600;\n line-height: 1;\n box-shadow: 0 0 0 2px var(--_bg);\n}\n\n.launcher-badge[hidden] {\n display: none;\n}\n\n/* The sidebar collapses to an edge rail instead: full height, square, flush\n against the dock. It slides in with the panel rather than popping. */\n/* The edge rail. It reads as the docked edge of a panel rather than a coloured\n stripe: the surface the panel is made of, a border on the side it docks\n against, and the accent kept for the icon -- a full-height slab of accent is\n the loudest thing on the page and says the least about itself.\n\n Content sits at the top rather than centred, because a control floating in\n the middle of a screen-high column has nothing to belong to. */\n:host([placement=\"sidebar\"][collapsed]) .launcher {\n inset: 0;\n width: auto;\n height: auto;\n flex-direction: column;\n align-items: center;\n justify-content: flex-start;\n gap: 12px;\n padding-top: 14px;\n border: 0;\n border-inline-start: 1px solid var(--_border);\n border-radius: 0;\n background: var(--_bg);\n color: var(--_fg);\n box-shadow: none;\n transform: none;\n}\n\n:host([placement=\"sidebar\"][data-side=\"left\"][collapsed]) .launcher {\n border-inline-start: 0;\n border-inline-end: 1px solid var(--_border);\n}\n\n/* The icon keeps the accent, so there is one obvious thing to press.\n\n The glyph is sized here rather than left to fill the holder. A glyph in an\n icon holder takes the holder's whole box by default, which is right where the\n holder is only a box and wrong the moment it becomes a filled circle: the\n mark then runs edge to edge and reads as a square crammed into a disc. The\n proportion is the floating launcher's own -- a 26px glyph in a 56px bubble --\n so the two collapsed states look like the same widget. */\n:host([placement=\"sidebar\"][collapsed]) .launcher .icon-holder {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n border-radius: 50%;\n background: var(--_header-bg);\n color: var(--_header-fg);\n}\n\n:host([placement=\"sidebar\"][collapsed]) .launcher .icon-holder .glyph,\n:host([placement=\"sidebar\"][collapsed]) .launcher .icon-holder .icon-img {\n width: 16px;\n height: 16px;\n}\n\n/* Written down the rail, which is the only direction it fits. Reading upward\n is the convention for a right-hand edge and matches how a docked panel's\n label is set everywhere it appears. */\n.rail-label {\n display: none;\n}\n\n:host([placement=\"sidebar\"][collapsed]) .rail-label {\n display: block;\n writing-mode: vertical-rl;\n transform: rotate(180deg);\n max-height: calc(100% - 96px);\n overflow: hidden;\n font-size: 0.9em;\n font-weight: 600;\n letter-spacing: 0.02em;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n:host([placement=\"sidebar\"][data-side=\"left\"][collapsed]) .rail-label {\n writing-mode: vertical-rl;\n transform: none;\n}\n\n/* Every transition in this file is timed by --_motion, so collapsing it to a\n frame is the whole reduced-motion story: states still change (and display\n still flips at the end of its discrete transition), nothing travels. */\n@media (prefers-reduced-motion: reduce) {\n :host {\n --_motion: var(--ag-ui-motion, 0.001s);\n }\n}\n\n/* Embedded: drop the floating chrome and the high z-index stacking context so\n the widget lives in the host's own layout (fixes overlay/z-index clashes). */\n:host([placement=\"embedded\"]) {\n --_position: var(--ag-ui-position, static);\n --_width: var(--ag-ui-width, 100%);\n --_height: var(--ag-ui-height, 100%);\n --_max-width: var(--ag-ui-max-width, 100%);\n --_max-height: var(--ag-ui-max-height, 100%);\n --_shadow: var(--ag-ui-shadow, none);\n --_z-index: var(--ag-ui-z-index, auto);\n}\n\n.chat {\n position: relative;\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n background: var(--_bg);\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n box-shadow: var(--_shadow);\n overflow: hidden;\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--_border);\n background: var(--_header-bg);\n color: var(--_header-fg);\n}\n\n/* The header is the panel's title bar: on the placements that let the widget\n be moved it drags the whole thing, matching the launcher's own drag. The\n selector lists those placements rather than asking JS to stamp an attribute,\n because a cursor that appears one frame late reads as a control that only\n sometimes exists. touch-action keeps a finger drag here moving the panel\n instead of scrolling the page behind it. */\n:host(:not([placement])) .header,\n:host([placement=\"\"]) .header,\n:host([placement=\"floating\"]) .header,\n:host([placement=\"bottom-left\"]) .header {\n cursor: move;\n touch-action: none;\n}\n\n/* A host that turned the drag off keeps a header that says so. */\n:host([data-launcher-drag=\"false\"]) .header {\n cursor: auto;\n touch-action: auto;\n}\n\n/* :host is carried for the specificity, not the scope: the placement rules\n above are a host selector plus a class plus an attribute, and a bare\n .header[data-dragging] loses to them -- silently, because the drag still\n works and only the cursor is wrong. */\n:host .header[data-dragging] {\n cursor: grabbing;\n}\n\n/* The cursor inherits, so a control a host slots into the header would show\n the drag cursor over something the drag deliberately ignores. Named to\n match the controls panel_drag steps aside for. */\n.header ::slotted(:is(button, a, input, select, textarea)) {\n cursor: pointer;\n}\n\n.header-title {\n flex: 1;\n min-width: 0;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Header / launcher icon holder: a slot, with a data-icon-url <img>\n fallback, sized via --ag-ui-icon-size. */\n.icon-holder {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: var(--_icon-size);\n height: var(--_icon-size);\n line-height: 1;\n}\n\n.icon-img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n border-radius: var(--_icon-radius);\n}\n\n.header-controls {\n display: flex;\n gap: var(--_header-gap);\n flex: none;\n}\n\n.header-btn {\n display: inline-flex;\n flex: 0 0 auto;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: var(--_header-btn-size);\n height: var(--_header-btn-size);\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n line-height: 1;\n padding: 0;\n border-radius: 6px;\n cursor: pointer;\n opacity: 0.85;\n}\n\n.header-btn:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.18);\n}\n\n/* \u2500\u2500 Collapse \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Collapsing shrinks the widget to the round floating launcher: the panel\n scales toward the launcher's corner and fades, the launcher pops in from the\n same point. Both halves are transform and opacity only, so the morph runs on\n the compositor and never reflows the host page.\n\n The host box keeps its expanded size, since animating it would animate\n layout and a dragged --ag-ui-width would fight the launcher's own size.\n Nothing paints there once the panel is gone, so the box only has to stop\n swallowing clicks: pointer events go to none and the launcher takes them.\n\n Two placements do not use it: \"sidebar\" slides to its rail (below), and\n \"embedded\" keeps its header bar, having no corner for a floating circle that\n would escape the host's layout. \"page\" has no collapsed state at all. */\n:host([collapsed]) {\n pointer-events: none;\n /* A collapsed host has to be allowed to shrink, and in the layout hosts\n actually use it is not. Every collapse path here works by letting the host\n size to its content -- the in-flow ones set height: auto, the floating one\n leaves only the launcher -- and a flex or grid parent whose align-items is\n the default stretch value overrides all of it. The panel then hides and the box\n it occupied stays: a header bar over several hundred pixels of nothing.\n Every known consumer hit this, because putting the element in a flex column\n beside the page content is the obvious way to embed it. */\n align-self: start;\n}\n\n:host([collapsed]) .chat {\n opacity: 0;\n transform: scale(0.94);\n visibility: hidden;\n}\n\n/* visibility keeps the panel out of the tab order and the a11y tree at rest\n without display:none killing the transition. It interpolates so any progress\n below 1 still counts as visible: the panel stays on screen for the whole\n collapse, flips hidden exactly at the end, and on expand is visible from the\n first frame. */\n.chat {\n transform-origin: bottom right;\n transition:\n opacity var(--_motion) var(--_ease),\n transform var(--_motion) var(--_ease),\n visibility var(--_motion) var(--_ease);\n}\n\n:host([placement=\"bottom-left\"]) .chat {\n transform-origin: bottom left;\n}\n\n/* Once the launcher has been dragged the element places itself, and stamps the\n corner it chose to open away from. The morph has to start at that same\n corner: scaling out of the one the placement originally guessed reads as the\n panel leaping across the screen before it opens.\n\n Equal specificity to the placement rule above, so source order is what lets\n the stamped value win -- the same arrangement the resize grip uses, and the\n same trap. Write these with = on the whole hyphenated token; a ~= would match\n whitespace-separated words and so could never match at all. */\n:host([data-expand-corner=\"top-left\"]) .chat {\n transform-origin: top left;\n}\n\n:host([data-expand-corner=\"top-right\"]) .chat {\n transform-origin: top right;\n}\n\n:host([data-expand-corner=\"bottom-left\"]) .chat {\n transform-origin: bottom left;\n}\n\n:host([data-expand-corner=\"bottom-right\"]) .chat {\n transform-origin: bottom right;\n}\n\n/* The in-flow placement keeps the original collapse: hide the body, let the\n host shrink to the header bar. */\n:host([collapsed][placement=\"embedded\"]) {\n height: auto;\n max-height: none;\n pointer-events: auto;\n}\n\n:host([collapsed][placement=\"embedded\"]) .chat {\n opacity: 1;\n transform: none;\n visibility: visible;\n}\n\n/* It keeps the header bar, so the launcher must stay out of the way: an\n embedded host is position: static, which would let an absolutely-positioned\n circle escape the layout and land against whatever the page positions. */\n:host([collapsed][placement=\"embedded\"]) .launcher {\n visibility: hidden;\n opacity: 0;\n}\n\n:host([collapsed][placement=\"embedded\"]) .messages-wrap,\n:host([collapsed][placement=\"embedded\"]) .messages,\n:host([collapsed][placement=\"embedded\"]) .input-row,\n:host([collapsed][placement=\"embedded\"]) .skill-chips,\n:host([collapsed][placement=\"embedded\"]) .skill-palette,\n:host([collapsed][placement=\"embedded\"]) .skill-hint {\n display: none;\n}\n\n/* A docked conversation list: beside the transcript rather than over it.\n\n A full-page chat is the one surface with width to spare, and covering the\n conversation to show the list of conversations is the wrong trade there --\n it hides the thing you are trying to get back to. Everywhere else the panel\n is a few hundred pixels wide and a docked list would leave a column of\n transcript narrower than the messages in it, so this is the only placement\n that gets it.\n\n The transcript is moved by padding on the shell rather than by making the\n list a flex sibling: the drawer is the last child of the panel, and no\n selector reaches backwards from it to the rows that have to shift. That is\n also why the state is stamped on the host. */\n:host([data-threads-docked]) .drawer {\n /* Not a scrim: the page behind it is still the user's to work in. */\n pointer-events: none;\n}\n\n:host([data-threads-docked]) .drawer-backdrop {\n display: none;\n}\n\n:host([data-threads-docked]) .drawer-panel {\n width: var(--_threads-rail-width);\n pointer-events: auto;\n border-inline-end: 1px solid var(--_border);\n box-shadow: none;\n}\n\n:host([data-threads-docked]) .messages,\n:host([data-threads-docked]) .input-row,\n:host([data-threads-docked]) .skill-chips,\n:host([data-threads-docked]) .attachment-tray {\n padding-inline-start: calc(\n var(--_threads-rail-width) + max(var(--_pad), (100% - var(--_content-max-width)) / 2)\n );\n}\n\n:host([data-threads-docked]) .header {\n padding-inline-start: calc(var(--_threads-rail-width) + var(--_pad));\n}\n\n/* The page placement has no collapsed state, so it offers no control for one.\n A dedicated route has no \"away\" to go to: shrinking it left a strip of\n application chrome fixed over a route that no longer had an owner, and this\n is the one placement where the launcher that would bring it back is hidden.\n\n Hiding the control is only half of it. The state has another way in -- the\n attribute can be written straight onto the element, and a value stored under\n a different placement is restored on connect -- and the JS guards that catch\n those cannot see an attribute set directly. So the placement neutralises the\n state here as well: with the collapse rules above no longer naming \"page\", an\n unguarded collapsed attribute would otherwise fall through to the generic\n rule that scales the panel away and drops pointer events, leaving nothing on\n screen and no launcher to press. */\n:host([placement=\"page\"]) .header-btn--collapse {\n display: none;\n}\n\n:host([placement=\"page\"][collapsed]) {\n pointer-events: auto;\n align-self: auto;\n}\n\n:host([placement=\"page\"][collapsed]) .chat {\n opacity: 1;\n transform: none;\n visibility: visible;\n}\n\n/* Jump-to-latest: shown only once the reader has scrolled away *and* missed\n something. Anchored to the panel rather than the list so it does not scroll\n with the content it is offering to scroll to. */\n/* The transcript's own box, and the only one whose foot is the transcript's\n foot. The panel's foot is below the composer, the chips and the footer. */\n.messages-wrap {\n position: relative;\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n.jump-latest {\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n bottom: var(--_pad);\n z-index: 2;\n display: none;\n align-items: center;\n gap: 0.35em;\n padding: 0.4em 0.9em;\n border: 1px solid var(--_border);\n border-radius: 999px;\n /* A raised surface, not the panel's own background. Reusing --_bg made the\n pill the same colour as everything behind it, leaving a 1px border and a\n shadow to carry the whole affordance -- and a dark-on-dark shadow carries\n nothing. --_hover is the token that already means \"lifted off the panel\",\n and it separates in both themes without competing with the accent the send\n button owns. */\n background: var(--_hover);\n color: var(--_text);\n font: inherit;\n font-size: 0.85em;\n cursor: pointer;\n box-shadow: 0 2px 10px rgb(0 0 0 / 0.18);\n}\n\n.jump-latest[data-missed=\"true\"] {\n display: flex;\n}\n\n.jump-latest:hover {\n border-color: var(--_accent);\n}\n\n/* The offer to quote a selection. Positioned in script against the transcript\n box, which is the only ancestor whose top and foot are the transcript's --\n the same reason .jump-latest lives here. The translate is the half the\n script does not do: script sets the point the offer hangs from, CSS decides\n which corner of the offer that point is. */\n.quote-selection {\n position: absolute;\n z-index: 2;\n transform: translate(-50%, -100%);\n padding: 0.25em 0.7em;\n border: 1px solid var(--_border);\n border-radius: 999px;\n background: var(--_hover);\n color: var(--_text);\n font: inherit;\n font-size: 0.8em;\n line-height: 1.6;\n white-space: nowrap;\n cursor: pointer;\n box-shadow: 0 2px 10px rgb(0 0 0 / 0.18);\n}\n\n/* Flipped under the selection when there was no room above it. Only the\n vertical half of the translate changes: it still hangs from its own centre\n horizontally. */\n.quote-selection[data-below=\"true\"] {\n transform: translate(-50%, 0);\n}\n\n.quote-selection:hover {\n border-color: var(--_accent);\n}\n\n/* Screen-reader-only status region. Off-screen rather than display:none or\n visibility:hidden, both of which take the element out of the accessibility\n tree entirely -- a hidden live region announces nothing at all, which is the\n classic way this pattern is written wrong.\n\n The 1px box with clip-path, rather than width/height 0, is the shape that\n survives: a zero-sized element is dropped from the tree by some engines. */\n/* A used-value reader for the four viewport-inset tokens, and the only\n reliable one. getComputedStyle().getPropertyValue() on an unregistered\n custom property hands back the substituted token stream, not a length: it\n returns \"4rem\" verbatim, and \"calc(56px + env(safe-area-inset-top))\" as\n \"calc(56px + 0px)\" -- which parses as NaN and takes the whole inset with it.\n Padding is a real property, so the same tokens come back resolved to px.\n\n visibility rather than display: none, because a box that generates no\n layout has no used values to read. Zero-sized, absolutely positioned and\n inert, so it costs nothing but the read. */\n.viewport-probe {\n position: absolute;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n visibility: hidden;\n pointer-events: none;\n padding: var(--_viewport-inset-top) var(--_viewport-inset-right)\n var(--_viewport-inset-bottom) var(--_viewport-inset-left);\n}\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n overflow: hidden;\n white-space: nowrap;\n clip-path: inset(50%);\n}\n\n.messages {\n flex: 1;\n overflow-y: auto;\n /* Scrolling past the end of this must not scroll the page behind it. */\n overscroll-behavior: contain;\n /* The browser's own scroll anchoring competes with the scroller for the same\n job and wins unpredictably -- it can hold the view still exactly when we\n want to follow. Turned off so following is decided in one place. Safari\n does not implement it, which is itself a reason not to depend on it. */\n overflow-anchor: none;\n padding: var(--_pad);\n display: flex;\n flex-direction: column;\n gap: var(--_space);\n}\n\n/* Empty-state region (slot): centred while it's the only thing in the\n list, hidden as soon as a message, card, or pending indicator renders. */\n.empty {\n margin: auto;\n text-align: center;\n color: var(--_muted);\n}\n\n.empty[hidden] {\n display: none;\n}\n\n/* \u2500\u2500 Answer group / well \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n One .answer per assistant turn wraps the streamed text, its tool cards,\n and the pending indicator so a whole answer reads (and can be boxed) as one\n unit. A flex column on the message-list gap, stretched to the list width so\n its children keep their own left/right alignment. data-answer-well opts into\n the bordered \"well\"; without it the turn renders as a flat stack. */\n.answer {\n display: flex;\n flex-direction: column;\n gap: var(--_space);\n align-self: stretch;\n min-width: 0;\n}\n\n:host([data-answer-well]) .answer {\n padding: var(--_pad);\n background: var(--_well-bg);\n border: 1px solid var(--_well-border);\n border-radius: var(--_radius);\n}\n\n.message {\n max-width: 80%;\n padding: var(--_msg-pad);\n border-radius: var(--_msg-radius);\n line-height: 1.4;\n white-space: pre-wrap;\n /* overflow-wrap, not word-break, and the difference is the whole reason a\n markdown table used to be unreadable.\n\n Both stop one long unbroken token blowing out the bubble, which is all this\n is for. But word-break: break-word is the legacy spelling of \"break\n anywhere\", and breaking anywhere drops the *min-content* width of every\n descendant to one character. A table's column algorithm takes min-content\n as an input, so the table always fitted max-width: 100%, the overflow-x:\n auto below it never had anything to scroll, and the columns absorbed the\n pressure by rendering one letter per line instead. A seven-column header\n came out 162px tall.\n\n overflow-wrap: break-word breaks a word only when it would otherwise\n overflow its line, and leaves min-content alone -- so the bubble is still\n protected and the table is free to be wider than the panel and scroll. */\n overflow-wrap: break-word;\n}\n\n/* \u2500\u2500 Incoming-text animations (data-text-animation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* .message--restored (rehydrated history) is excluded: entrance animations are\n for freshly-arriving messages, not the whole transcript replaying on reload.\n Word mode is excluded implicitly \u2014 restored bubbles aren't wrapped. */\n:host([data-text-animation=\"fade\"]) .message--assistant:not(.message--restored) {\n animation: ag-ui-msg-fade 0.25s ease both;\n}\n\n@keyframes ag-ui-msg-fade {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: none; }\n}\n\n.message--assistant .word {\n animation: ag-ui-word-in 0.3s ease both;\n animation-delay: calc(var(--ag-ui-word-index, 0) * 35ms);\n}\n\n@keyframes ag-ui-word-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host([data-text-animation=\"fade\"]) .message--assistant,\n .message--assistant .word {\n animation: none;\n }\n}\n\n.message--user {\n align-self: flex-end;\n background: var(--_user-bg);\n color: var(--_user-fg);\n border-bottom-right-radius: 4px;\n}\n\n.message--assistant {\n align-self: flex-start;\n background: var(--_assistant-bg);\n border-bottom-left-radius: 4px;\n /* Assistant bubbles hold rendered markdown/HTML, so collapse the source\n whitespace the renderer leaves between block tags. */\n white-space: normal;\n}\n\n/* Rendered-markdown elements inside an assistant bubble. */\n.message--assistant > :first-child {\n margin-top: 0;\n}\n\n.message--assistant > :last-child {\n margin-bottom: 0;\n}\n\n.message--assistant p,\n.message--assistant ul,\n.message--assistant ol,\n.message--assistant blockquote,\n.message--assistant pre {\n margin: 0.5em 0;\n}\n\n.message--assistant ul,\n.message--assistant ol {\n padding-left: 1.3em;\n}\n\n.message--assistant a {\n color: var(--_accent);\n text-decoration: underline;\n}\n\n.message--assistant code {\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n font-size: 0.92em;\n background: rgba(127, 127, 127, 0.16);\n padding: 1px 4px;\n border-radius: 4px;\n}\n\n.message--assistant pre {\n padding: 8px 10px;\n overflow: auto;\n /* Scrolling past the end of this must not scroll the page behind it. */\n overscroll-behavior: contain;\n background: var(--_bg);\n border: 1px solid var(--_border);\n border-radius: 6px;\n}\n\n.message--assistant pre code {\n background: none;\n padding: 0;\n}\n\n/* The copy button sits inside the block, so it scrolls with wide code rather\n than floating over the bubble. Positioning is on the pre; the button only\n appears once one has been attached. */\n.message--assistant pre.has-copy {\n position: relative;\n}\n\n/* The only reader of --ag-ui-surface and --ag-ui-text. Their defaults are\n transparent and the body foreground, which is what this button rendered as\n before either token existed; changing them repaints only this control. */\n.code-copy {\n position: absolute;\n top: 4px;\n right: 4px;\n padding: 2px 8px;\n font: inherit;\n font-size: 0.75em;\n line-height: 1.6;\n color: var(--_muted);\n background: var(--_surface);\n border: 1px solid var(--_border);\n border-radius: 4px;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.12s ease;\n}\n\n/* Revealed on hover or keyboard focus. Focus matters as much as hover here:\n hidden-until-hover is invisible to a keyboard user otherwise. */\n.message--assistant pre.has-copy:hover .code-copy,\n.code-copy:focus-visible {\n opacity: 1;\n}\n\n.code-copy:hover {\n color: var(--_text);\n background: var(--_hover);\n}\n\n.code-copy[data-state=\"copied\"] {\n opacity: 1;\n color: var(--_text);\n}\n\n.code-copy[data-state=\"failed\"] {\n opacity: 1;\n color: var(--_danger, var(--_text));\n}\n\n.message--assistant blockquote {\n padding-left: 10px;\n border-left: 3px solid var(--_border);\n color: var(--_muted);\n}\n\n/* Markdown tables. table/thead/tbody/tr/th/td are all in the sanitizer's\n ALLOWED_TAGS, so an agent can emit one. A wide table must scroll inside its\n own box rather than stretch the message: the bubble is width-constrained, so\n without this the columns either crush or push the layout sideways. */\n.message--assistant table {\n display: block;\n width: fit-content;\n max-width: 100%;\n overflow-x: auto;\n border-collapse: collapse;\n font-size: 0.95em;\n}\n\n.message--assistant th,\n.message--assistant td {\n padding: 6px 10px;\n border: 1px solid var(--_border);\n text-align: left;\n vertical-align: top;\n}\n\n.message--assistant th {\n background: var(--_hover);\n font-weight: 600;\n}\n\n.pending {\n align-self: flex-start;\n display: inline-flex;\n gap: 4px;\n align-items: center;\n padding: 12px 14px;\n background: var(--_assistant-bg);\n border-radius: 14px;\n border-bottom-left-radius: 4px;\n}\n\n.pending-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--_muted);\n animation: ag-ui-pending 1.2s infinite ease-in-out both;\n}\n\n.pending-dot:nth-child(2) {\n animation-delay: 0.16s;\n}\n\n.pending-dot:nth-child(3) {\n animation-delay: 0.32s;\n}\n\n@keyframes ag-ui-pending {\n 0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }\n 40% { opacity: 1; transform: translateY(-3px); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .pending-dot {\n animation: none;\n opacity: 0.6;\n }\n}\n\n/* \u2500\u2500 Thoughts region \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n A muted, collapsible chain-of-thought at the top of the answer group: open\n while the model reasons, folded once the answer text starts. */\n.thoughts {\n align-self: stretch;\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 12px;\n color: var(--_muted);\n}\n\n.thoughts-toggle {\n align-self: flex-start;\n border: none;\n padding: 0;\n background: none;\n font: inherit;\n font-size: 12px;\n font-weight: 600;\n color: var(--_muted);\n cursor: pointer;\n}\n\n.thoughts-toggle::before {\n content: var(--_disclosure-expanded) \" \";\n}\n\n.thoughts-toggle[aria-expanded=\"false\"]::before {\n content: var(--_disclosure-collapsed) \" \";\n}\n\n/* A gentle pulse on the label while reasoning is still streaming. */\n.thoughts[data-streaming] .thoughts-label {\n animation: ag-ui-thoughts-pulse 1.4s ease-in-out infinite;\n}\n\n@keyframes ag-ui-thoughts-pulse {\n 0%, 100% { opacity: 0.55; }\n 50% { opacity: 1; }\n}\n\n.thoughts-body {\n margin: 0;\n padding: 4px 0 4px 10px;\n border-left: 2px solid var(--_border);\n max-height: 220px;\n overflow: auto;\n /* Scrolling past the end of this must not scroll the page behind it. */\n overscroll-behavior: contain;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: inherit;\n}\n\n.thoughts-body[hidden] {\n display: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .thoughts[data-streaming] .thoughts-label {\n animation: none;\n }\n}\n\n.tool-call {\n align-self: flex-start;\n max-width: 80%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-size: 12px;\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--_tool-bg);\n border: 1px solid var(--_border);\n color: var(--_tool-fg);\n}\n\n/* Wraps, because the name is the only flexible child and every badge the row\n gains is taken out of it. An approved call adds a third fixed badge, which in\n a sidebar-width panel left the name 37px and broke it mid-word. Badges drop to\n their own row instead. */\n.tool-call-head {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.tool-call-name {\n /* An auto basis, and a min-width floor rather than zero: the name may shrink,\n but not below something readable, so wrapping moves a badge instead of\n shredding a word. Breaking anywhere still applies to a name that cannot fit\n on a line of its own, which is what keeps a long unbroken tool name inside\n the card. */\n flex: 1 1 auto;\n min-width: 6ch;\n font-weight: 600;\n overflow-wrap: anywhere;\n}\n\n/* Leading status icon. Empty in the DOM \u2014 the glyph/spinner is drawn\n here from the card's data-status, so it stays themeable. */\n.tool-call-icon {\n flex: none;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n font-size: 12px;\n line-height: 1;\n}\n\n/* Pending: a real spinning ring. Speed is tunable; reduced motion stops it. */\n.tool-call[data-status=\"pending\"] .tool-call-icon {\n border: 2px solid var(--_muted);\n border-top-color: transparent;\n border-radius: 50%;\n animation: ag-ui-tool-spin var(--_tool-spin-duration) linear infinite;\n}\n\n@keyframes ag-ui-tool-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Deferred: no spinner, because nothing is spinning. A steady accent dot, since\n the state is waiting-on-you rather than an outcome. */\n.tool-call[data-status=\"deferred\"] .tool-call-icon {\n border-radius: 50%;\n background: var(--_accent);\n}\n\n/* Settled: a themeable glyph coloured by outcome. */\n.tool-call[data-status=\"done\"] .tool-call-icon::before {\n content: var(--_tool-icon-done);\n color: var(--_success);\n}\n\n.tool-call[data-status=\"error\"] .tool-call-icon::before {\n content: var(--_tool-icon-error);\n color: var(--_danger);\n}\n\n.tool-call[data-status=\"declined\"] .tool-call-icon::before {\n content: var(--_tool-icon-declined);\n color: var(--_muted);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tool-call[data-status=\"pending\"] .tool-call-icon {\n animation: none;\n }\n}\n\n/* Inline display mode: the lightest card. Drop the box chrome so the status row\n reads as one line of the answer; the result toggle still expands below it. */\n:host([data-tool-display=\"inline\"]) .tool-call {\n max-width: 100%;\n background: transparent;\n border: none;\n padding: 2px 0;\n gap: 2px;\n}\n\n.tool-call-status {\n flex: none;\n padding: 1px 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n background: rgba(127, 127, 127, 0.16);\n color: var(--_muted);\n}\n\n.tool-call[data-status=\"deferred\"] .tool-call-status {\n color: var(--_accent);\n}\n\n.tool-call[data-status=\"done\"] .tool-call-status {\n color: var(--_success);\n}\n\n.tool-call[data-status=\"error\"] .tool-call-status {\n color: var(--_danger);\n}\n\n.tool-call[data-status=\"declined\"] .tool-call-status {\n color: var(--_muted);\n}\n\n.tool-call-body {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.tool-call-section {\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n\n/* The heading that tells the two payloads apart. Without it the arguments and\n the result were one run of text and a reader had to guess the boundary. */\n.tool-call-section-label {\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n color: var(--_muted);\n}\n\n.skill-item-token {\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n font-size: 0.92em;\n color: var(--_accent);\n margin-right: 6px;\n}\n\n/* The lasting record of a human decision on a gated call \u2014 without it an\n approved call looks exactly like one that was never gated. */\n.tool-call-decision {\n flex: none;\n font-size: 11px;\n font-style: italic;\n color: var(--_muted);\n}\n\n.tool-call-args,\n.tool-call-result {\n margin: 0;\n padding: 6px 8px;\n max-height: 160px;\n overflow: auto;\n /* Scrolling past the end of this must not scroll the page behind it. */\n overscroll-behavior: contain;\n background: var(--_bg);\n border: 1px solid var(--_border);\n border-radius: 6px;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--_fg);\n}\n\n/* A region a host formatter took over, marked by the card. Preformatted\n whitespace is what makes the built-in block read as written, and it is the one\n thing a host cannot want: a table inherits it as mangled cell spacing, and a\n sentence as line breaks nobody typed.\n\n Whitespace only. The card's own face, frame, padding and scroll cap stay,\n because the card is one visual object -- the head row and the status pill are\n monospaced too -- and a region that dropped the family would be the only part\n of it wearing a different one. A host that wants that restyles the\n tool-card-result part, which does not need the formatter at all. */\n.tool-call-args[data-formatted],\n.tool-call-result[data-formatted] {\n white-space: normal;\n}\n\n/* Display modes are pure visibility over one DOM shape, selected from the host\n attribute rather than a value stamped on the card at build time, so flipping\n data-tool-display re-styles cards already on screen. See ToolCallCard.\n\n Default (no attribute) is the full mode: arguments always visible, result\n behind the toggle. */\n.tool-call[data-expanded=\"false\"] .tool-call-section--result {\n display: none;\n}\n\n/* Compact: one toggle over both regions, so a settled card is a single line\n until asked. */\n:host([data-tool-display=\"compact\"]) .tool-call[data-expanded=\"false\"] .tool-call-section {\n display: none;\n}\n\n/* Inline: the result only; the call's arguments are noise at this density. */\n:host([data-tool-display=\"inline\"]) .tool-call .tool-call-section--args {\n display: none;\n}\n\n/* Minimal: the status row and nothing else, so there is no toggle to press. */\n:host([data-tool-display=\"minimal\"]) .tool-call .tool-call-toggle,\n:host([data-tool-display=\"minimal\"]) .tool-call .tool-call-body {\n display: none;\n}\n\n/* A pending card has no result yet, and in the modes where the arguments are\n hidden too there is nothing behind the toggle. Hide the control rather than\n offer one that expands onto nothing. A deferred card is the same, and its\n arguments are shown unconditionally by the rules below. */\n.tool-call[data-status=\"pending\"] .tool-call-toggle,\n.tool-call[data-status=\"deferred\"] .tool-call-toggle,\n:host([data-tool-display=\"inline\"]) .tool-call[data-status=\"pending\"] .tool-call-toggle {\n display: none;\n}\n\n/* The approval prompt for a gated call, rendered inside that call's own card.\n Empty on every card nobody is being asked about, so it collapses instead of\n adding a gap to each one. */\n.tool-call-approval:empty {\n display: none;\n}\n\n.tool-call-approval {\n margin-top: 8px;\n}\n\n/* A card that is asking a question shows what it is asking about, in every\n display mode. Three gated calls of one tool ask the same words, so the\n arguments are the only thing telling them apart, and a density setting must\n not be able to hide the answer to \"which one is this\". */\n:host([data-tool-display=\"minimal\"]) .tool-call[data-status=\"deferred\"] .tool-call-body {\n display: flex;\n}\n\n/* The arguments region only, never every section: the result region carries the\n hidden attribute until a result exists, and a display value here overrides it,\n framing an empty RESULT heading under the question. */\n:host([data-tool-display=\"compact\"]) .tool-call[data-status=\"deferred\"] .tool-call-section--args,\n:host([data-tool-display=\"inline\"]) .tool-call[data-status=\"deferred\"] .tool-call-section--args {\n display: flex;\n}\n\n/* A delegated sub-agent's progress, inside the card that delegated. Empty on\n every card that delegated nothing, so it collapses rather than adding a gap\n to each one -- the same shape the approval slot uses. */\n.tool-call-subagent:empty {\n display: none;\n}\n\n.tool-call-subagent {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.subagent {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n/* The collapsed row is the status and the expander at once, which is what keeps\n a ten-step child one row until somebody opens it. Full width and left-aligned,\n because it is a line of the card rather than a button on it. */\n.subagent-row {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n box-sizing: border-box;\n padding: 2px 0;\n border: none;\n background: none;\n font: inherit;\n text-align: left;\n color: var(--_muted);\n cursor: pointer;\n}\n\n/* Nothing behind the row yet -- a delegation that failed before calling\n anything. Drop the affordances rather than offer a control that expands onto\n an empty region, which is the refusal the card's own toggle already makes. */\n.subagent-row:disabled {\n cursor: default;\n}\n\n.subagent-row::after {\n content: var(--_disclosure-collapsed);\n flex: none;\n margin-left: auto;\n color: var(--_accent);\n}\n\n.subagent-row[aria-expanded=\"true\"]::after {\n content: var(--_disclosure-expanded);\n}\n\n.subagent-row:disabled::after {\n display: none;\n}\n\n/* Empty in the DOM; the glyph is drawn here from the panel's data-phase, so a\n host re-themes it through the same tool-icon custom properties the card uses. */\n.subagent-icon {\n flex: none;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 10px;\n height: 10px;\n font-size: 10px;\n line-height: 1;\n}\n\n/* Anything that is not a terminal phase is the child still working. Selected by\n what it is not, so a phase this client has not heard of still spins rather\n than rendering as a blank. */\n.subagent[data-phase]:not([data-phase=\"finished\"]):not([data-phase=\"failed\"]) .subagent-icon {\n border: 2px solid var(--_muted);\n border-top-color: transparent;\n border-radius: 50%;\n animation: ag-ui-tool-spin var(--_tool-spin-duration) linear infinite;\n}\n\n.subagent[data-phase=\"finished\"] .subagent-icon::before {\n content: var(--_tool-icon-done);\n color: var(--_success);\n}\n\n.subagent[data-phase=\"failed\"] .subagent-icon::before {\n content: var(--_tool-icon-error);\n color: var(--_danger);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .subagent .subagent-icon {\n animation: none;\n }\n}\n\n/* The server's own pre-rendered line. Shrinks and wraps rather than pushing the\n chevron out of the card, which is what a fixed-width sibling in a flex row\n does to a panel at sidebar width. */\n.subagent-status {\n flex: 1 1 auto;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n/* The child's own calls. Indented and ruled, so the nesting is visible without\n a second card frame around it. */\n.subagent-steps {\n display: flex;\n flex-direction: column;\n gap: 2px;\n margin-left: 4px;\n padding-left: 10px;\n border-left: 1px solid var(--_border);\n}\n\n.subagent-steps[hidden] {\n display: none;\n}\n\n.subagent-step {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n color: var(--_muted);\n}\n\n.subagent-step-icon {\n flex: none;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 8px;\n height: 8px;\n font-size: 9px;\n line-height: 1;\n}\n\n/* No outcome yet: the wire says null while the call is in flight, and the\n absence of the attribute is how that arrives here. A hollow ring, not a\n spinner -- several can be on screen at once and the row above already spins. */\n.subagent-step:not([data-ok]) .subagent-step-icon {\n border: 1px solid var(--_muted);\n border-radius: 50%;\n}\n\n.subagent-step[data-ok=\"true\"] .subagent-step-icon::before {\n content: var(--_tool-icon-done);\n color: var(--_success);\n}\n\n.subagent-step[data-ok=\"false\"] .subagent-step-icon::before {\n content: var(--_tool-icon-error);\n color: var(--_danger);\n}\n\n.subagent-step-name {\n flex: 1 1 auto;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n.tool-call-toggle {\n align-self: flex-start;\n border: none;\n padding: 0;\n background: none;\n font: inherit;\n font-weight: 600;\n color: var(--_accent);\n cursor: pointer;\n}\n\n.tool-call-toggle::before {\n content: var(--_disclosure-collapsed) \" \";\n}\n\n.tool-call-toggle[aria-expanded=\"true\"]::before {\n content: var(--_disclosure-expanded) \" \";\n}\n\n/* Resize grips: every edge and every corner, so the panel can be dragged from\n whichever side the reader is already near. Absent entirely where the\n placement is full-bleed, since there is nothing to drag.\n\n The whole set is always laid out. Which edges the *layout* pins is not a\n question the stylesheet needs to answer any more -- it decided where the one\n grip went, and there is no longer one grip. The element still measures and\n stamps data-resize-anchor, because it decides what a drag on a pinned edge\n costs in position and which grip carries the tab stop, but no rule here\n reads it. */\n.resize-handle {\n position: absolute;\n z-index: 2;\n background: transparent;\n touch-action: none;\n}\n\n.resize-handle:focus-visible {\n outline: 2px solid var(--_accent);\n outline-offset: -2px;\n}\n\n/* Corners first in the file and last in the DOM, so they take the pointer\n where they overlap an edge strip. */\n.resize-handle--top-left,\n.resize-handle--top-right,\n.resize-handle--bottom-left,\n.resize-handle--bottom-right {\n width: var(--_grip-corner);\n height: var(--_grip-corner);\n}\n\n.resize-handle--top-left {\n top: 0;\n left: 0;\n cursor: nwse-resize;\n}\n\n.resize-handle--top-right {\n top: 0;\n right: 0;\n cursor: nesw-resize;\n}\n\n.resize-handle--bottom-left {\n bottom: 0;\n left: 0;\n cursor: nesw-resize;\n}\n\n.resize-handle--bottom-right {\n bottom: 0;\n right: 0;\n cursor: nwse-resize;\n}\n\n/* Edge strips, held clear of the corners at both ends so a corner drag is\n never swallowed by the edge next to it. */\n.resize-handle--left,\n.resize-handle--right {\n top: var(--_grip-corner);\n bottom: var(--_grip-corner);\n width: var(--_grip-edge);\n cursor: ew-resize;\n}\n\n.resize-handle--left {\n left: 0;\n}\n\n.resize-handle--right {\n right: 0;\n}\n\n.resize-handle--top,\n.resize-handle--bottom {\n left: var(--_grip-corner);\n right: var(--_grip-corner);\n height: var(--_grip-edge);\n cursor: ns-resize;\n}\n\n.resize-handle--top {\n top: 0;\n}\n\n.resize-handle--bottom {\n bottom: 0;\n}\n\n/* Docked: the placement owns the height, so the horizontal edges and every\n corner are inert and must not advertise a drag that does nothing. The two\n vertical edges remain, which is the same affordance these placements had\n when there was one grip -- now on both sides, since either may be the inner\n one depending on which side the rail is docked to. */\n:host([placement=\"sidebar\"]) .resize-handle--top,\n:host([placement=\"sidebar\"]) .resize-handle--bottom,\n:host([placement=\"sidebar\"]) .resize-handle--top-left,\n:host([placement=\"sidebar\"]) .resize-handle--top-right,\n:host([placement=\"sidebar\"]) .resize-handle--bottom-left,\n:host([placement=\"sidebar\"]) .resize-handle--bottom-right,\n:host([placement=\"side\"]) .resize-handle--top,\n:host([placement=\"side\"]) .resize-handle--bottom,\n:host([placement=\"side\"]) .resize-handle--top-left,\n:host([placement=\"side\"]) .resize-handle--top-right,\n:host([placement=\"side\"]) .resize-handle--bottom-left,\n:host([placement=\"side\"]) .resize-handle--bottom-right {\n display: none;\n}\n\n/* The vertical edges run the full height once no corner shares them. */\n:host([placement=\"sidebar\"]) .resize-handle--left,\n:host([placement=\"sidebar\"]) .resize-handle--right,\n:host([placement=\"side\"]) .resize-handle--left,\n:host([placement=\"side\"]) .resize-handle--right {\n top: 0;\n bottom: 0;\n width: var(--_grip-edge-docked);\n}\n\n/* Full-bleed: nothing to drag. */\n:host([placement=\"full\"]) .resize-handle,\n:host([placement=\"page\"]) .resize-handle {\n display: none;\n}\n\n/* The visible mark on a grip.\n\n Filling the whole strip was what the single corner grip did, and at 14px\n square nobody ever saw it. On a strip running the length of an edge the same\n fill reads as a border the panel grew -- square ended, stopping short of the\n corner radius at both ends, and easily mistaken for a rendering fault rather\n than for something to grab.\n\n So the hit area stays the full strip and the mark is a short pill centred on\n it, which cannot be read as an edge of anything and never meets the radius.\n\n It shows on hover and focus as well as during the drag. With eight grips, a\n mark that appears only once you are already dragging is a mark that never\n told anyone the grips were there. */\n.resize-handle::after {\n content: \"\";\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n border-radius: 999px;\n background: var(--_accent);\n opacity: 0;\n transition: opacity var(--_motion) var(--_ease);\n}\n\n.resize-handle--top::after,\n.resize-handle--bottom::after {\n width: var(--_grip-mark-length);\n height: var(--_grip-mark-thickness);\n}\n\n.resize-handle--left::after,\n.resize-handle--right::after {\n width: var(--_grip-mark-thickness);\n height: var(--_grip-mark-length);\n}\n\n/* A corner has no length to run along, so it gets a dot instead of a pill. */\n.resize-handle--top-left::after,\n.resize-handle--top-right::after,\n.resize-handle--bottom-left::after,\n.resize-handle--bottom-right::after {\n width: var(--_grip-mark-thickness);\n height: var(--_grip-mark-thickness);\n}\n\n.resize-handle:hover::after,\n.resize-handle:focus-visible::after {\n opacity: 0.5;\n}\n\n/* Equal specificity to the pair above, so source order is what makes the drag\n the stronger of the two states. */\n.resize-handle[data-dragging]::after {\n opacity: 0.9;\n}\n\n/* \u2500\u2500 Composer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n One surface owns the border, the background and the focus ring; the field\n and its tool row sit inside it, rather than being siblings stretched to the\n textarea's height. */\n.input-row {\n display: flex;\n padding: 12px;\n border-top: 1px solid var(--_border);\n}\n\n.composer {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 6px 6px 6px 10px;\n background: var(--_input-bg);\n border: 1px solid var(--_border);\n border-radius: var(--_composer-radius);\n transition: border-color var(--_motion) var(--_ease);\n}\n\n.composer:focus-within {\n border-color: var(--_accent);\n}\n\n/* The field grows with its content (sized by #autoGrow) up to the ceiling,\n then scrolls. border-box keeps that measurement stable: with content-box the\n padding would be added to every scrollHeight read and the field would creep\n taller on each keystroke. */\n.input {\n box-sizing: border-box;\n resize: none;\n max-height: var(--_composer-max-height);\n overflow-y: auto;\n /* Scrolling past the end of this must not scroll the page behind it. */\n overscroll-behavior: contain;\n padding: 6px 4px 2px;\n background: transparent;\n border: none;\n font: inherit;\n color: inherit;\n outline: none;\n}\n\n.composer-tools {\n display: flex;\n align-items: center;\n gap: 2px;\n}\n\n/* Icon buttons: quiet at rest, so the field is what the eye lands on. */\n.attach-btn,\n.voice-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--_tool-btn-size);\n height: var(--_tool-btn-size);\n padding: 0;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--_muted);\n font: inherit;\n line-height: 1;\n cursor: pointer;\n transition:\n background-color var(--_motion) var(--_ease),\n color var(--_motion) var(--_ease);\n}\n\n.attach-btn:hover,\n.voice-btn:hover {\n background: var(--_hover);\n color: var(--_fg);\n}\n\n.attach-btn:disabled,\n.voice-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n/* The same trap the attachment tray carries a note about, two rules along: an\n author display beats the UA stylesheet's rule for the hidden property, so a\n button the element has explicitly hidden keeps laying out and painting. The\n clip is hidden until a host supplies an upload handler or an attachments URL,\n and without this it is a visible control that cannot do anything.\n\n The mic needs no such rule, and the asymmetry is worth knowing before adding\n one: it is not hidden when unconfigured, it is never built. The voice wiring\n returns before constructing the button, leaving only an empty voice slot that\n is display: contents. A hidden-state rule for the mic would match nothing. */\n.attach-btn[hidden] {\n display: none;\n}\n\n/* Send closes the row on the right: a circle, the only filled control in the\n composer, so \"the thing that acts\" reads at a glance. */\n.send {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: var(--_send-size);\n height: var(--_send-size);\n padding: 0;\n border: none;\n border-radius: 50%;\n background: var(--_accent);\n color: var(--_on-accent);\n font: inherit;\n line-height: 1;\n cursor: pointer;\n transition:\n background-color var(--_motion) var(--_ease),\n transform var(--_motion) var(--_ease-pop);\n}\n\n.send:hover {\n transform: scale(1.08);\n}\n\n.send:active {\n transform: scale(0.92);\n}\n\n.send:disabled {\n opacity: 0.5;\n cursor: default;\n transform: none;\n}\n\n/* The composer button doubles as the Stop control while a run is in flight \u2014\n same circle, different glyph, so nothing moves when a run starts. */\n.send[data-state=\"running\"] {\n background: var(--_muted);\n}\n\n.send[data-state=\"idle\"] .send-stop,\n.send[data-state=\"running\"] .send-send {\n display: none;\n}\n\n/* Glyphs \u2014 one class, painted from the button's own colour so every state\n (hover, recording, running) carries the icon with it. */\n.glyph {\n width: var(--_glyph-size);\n height: var(--_glyph-size);\n fill: none;\n stroke: currentColor;\n stroke-width: var(--_glyph-stroke);\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n\n.glyph--solid {\n fill: currentColor;\n stroke: none;\n}\n\n/* In an icon holder (header brand, launcher) the glyph takes the holder's size\n rather than the composer's. */\n.icon-holder .glyph {\n width: 100%;\n height: 100%;\n}\n\n/* \u2500\u2500 File attachments \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* The picker button sits in the composer's tool row; hidden until\n data-attachments-url. */\n.attach-input {\n display: none;\n}\n\n/* The mic button's mount point; filled only once #wireVoice mounts the\n control. */\n.voice-slot {\n display: contents;\n}\n\n/* Recording: a red tint + a gentle pulse so it's clearly \"live\". */\n.voice-btn[data-state=\"recording\"] {\n background: var(--_danger);\n color: var(--_on-danger);\n animation: ag-ui-voice-pulse 1.2s ease-in-out infinite;\n}\n\n.voice-btn[data-state=\"recording\"]:hover {\n background: var(--_danger);\n color: var(--_on-danger);\n}\n\n@keyframes ag-ui-voice-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.6; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .voice-btn[data-state=\"recording\"] {\n animation: none;\n }\n}\n\n/* Pending-attachments tray, above the input row; collapses (hidden) when empty. */\n.attachment-slot {\n display: contents;\n}\n\n/* The padding is the only separation the tray gets: its slot is display:\n contents, so the tray is a direct child of the .chat column, and that column\n sets no gap. The bottom value keeps a chip off the composer's top edge.\n The inline value here is only the default: the page placement overrides\n padding-inline further down to compute its reading-column gutter, and wins\n on specificity whichever way this declaration is written. Read the two\n together before changing either. */\n.attachment-tray {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 8px 12px;\n}\n\n/* The tray sets the hidden property while empty, and an author display beats\n the UA stylesheet's rule for it, so without this the empty tray keeps\n laying out and its padding is permanent dead space above the composer. */\n.attachment-tray[hidden] {\n display: none;\n}\n\n.attachment-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n\n/* Charts.\n *\n * The SVG scales to the column and carries no colours of its own beyond the\n * series palette, so a host restyles it the same way it restyles everything\n * else. Series colours are custom properties with fallbacks rather than fixed\n * values, and the axis furniture inherits currentColor at low opacity so it\n * reads correctly in either theme without a second palette.\n */\n/* A chart is sized like a message, not like a panel: it takes the width it\n needs and stops, rather than stretching into whatever room the transcript\n has. A message bubble caps at 80% for the same reason -- widening the panel\n should not resize what is already in it. The cap is a token so a host with a\n wide panel and a real reason can raise it; the default is the width this\n renderer has always drawn at. Below the cap the block still fills its column,\n which is what a narrow panel needs. */\n.chart-block {\n align-self: flex-start;\n width: 100%;\n max-width: var(--_chart-max-width);\n margin: 6px 0;\n color: var(--_fg);\n}\n\n/* The drawing fills the block's width and carries its own height in the\n viewBox, so it is laid out rather than magnified: block display keeps the\n inline baseline gap from adding a stripe under every chart. */\n.chart-block svg {\n display: block;\n width: 100%;\n height: auto;\n}\n\n.chart-title {\n margin-bottom: 2px;\n font-size: 0.85em;\n font-weight: 600;\n opacity: 0.85;\n}\n\n.chart-legend {\n display: flex;\n flex-wrap: wrap;\n gap: 4px 12px;\n margin-top: 4px;\n font-size: 0.78em;\n opacity: 0.75;\n}\n\n.chart-legend-item {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n}\n\n.chart-legend-swatch {\n width: 9px;\n height: 9px;\n border-radius: 2px;\n flex: 0 0 auto;\n}\n\n.run-notice {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n align-self: flex-start;\n max-width: 100%;\n margin: 2px 0;\n padding: 3px 10px;\n border: 1px dashed var(--_border);\n border-radius: 999px;\n background: transparent;\n color: var(--_muted);\n font-size: 0.8em;\n line-height: 1.4;\n}\n\n.run-notice-icon {\n flex: none;\n opacity: 0.75;\n}\n\n/* The one control a notice may carry. Quiet, because it reports something\n already done rather than asking for a decision. */\n.run-notice-undo {\n flex: 0 0 auto;\n margin-inline-start: auto;\n padding: 2px 8px;\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n background: var(--_bg);\n color: var(--_accent);\n font: inherit;\n font-size: 0.9em;\n cursor: pointer;\n}\n\n.run-notice-undo:hover:not(:disabled) {\n background: var(--_hover);\n}\n\n.run-notice-undo:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.run-notice-text {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n/* A chip carries its own text colour because it carries its own background.\n The same chip renders in two places with opposite inherited colours: the\n composer tray gives it the panel's, a sent user bubble gives it the user\n foreground, which is white on the stock light theme and near-invisible\n against the chip. Pairing the colour with the background it belongs to makes\n both placements read alike. Overridden below for an errored chip, which\n keeps its red. */\n.attachment-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n /* Without this the 100% below caps the content box, so the chip still\n overflows its container by its own padding and border. Invisible while a\n character cap kept names short; routine once the container is what bounds\n the name. */\n box-sizing: border-box;\n max-width: 100%;\n padding: 4px 8px;\n border: 1px solid var(--_border);\n border-radius: 999px;\n background: var(--_assistant-bg);\n color: var(--_text);\n font-size: 0.85em;\n position: relative;\n}\n\n.attachment-chip--error {\n border-color: var(--_danger);\n color: var(--_danger);\n}\n\n/* The type mark. Painted from currentColor, so it carries the chip's state\n with it and turns red along with an errored one; muted by opacity rather\n than a colour, which is what keeps that true. Sized from the chip's own text\n rather than --ag-ui-glyph-size, the way an icon holder's glyph takes the\n holder's size: the composer's 18px buttons would make a chip button-height. */\n.attachment-chip-icon {\n display: inline-flex;\n flex: none;\n opacity: 0.75;\n}\n\n.attachment-chip-icon .glyph {\n width: 1.25em;\n height: 1.25em;\n}\n\n/* No character cap: the chip is already max-width 100%, so its container is\n what bounds the name, and a fixed cap only truncated names the chip had room\n for. min-width: 0 is what lets the flex item shrink past its content, so the\n ellipsis appears at the container edge instead of the chip overflowing. */\n.attachment-chip-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n\n.attachment-chip-size {\n color: var(--_muted);\n white-space: nowrap;\n}\n\n.attachment-chip--error .attachment-chip-size {\n color: var(--_danger);\n}\n\n/* The progress bar fills as the file uploads. */\n.attachment-chip-bar {\n flex-basis: 100%;\n height: 3px;\n border-radius: 2px;\n background: var(--_border);\n overflow: hidden;\n}\n\n.attachment-chip-bar-fill {\n height: 100%;\n background: var(--_accent);\n transition: width 0.15s ease;\n}\n\n.attachment-chip-remove,\n.attachment-chip-retry {\n border: none;\n background: none;\n color: inherit;\n cursor: pointer;\n padding: 0;\n line-height: 1;\n opacity: 0.7;\n}\n\n.attachment-chip-remove:hover,\n.attachment-chip-retry:hover {\n opacity: 1;\n}\n\n/* A subtle outline while a file is dragged over the shell. */\n.chat--dragover {\n outline: 2px dashed var(--_accent);\n outline-offset: -4px;\n}\n\n/* Muted \"\u23F9 Stopped\" line after a cancelled run \u2014 a note, not an error bubble. */\n.stopped-note {\n align-self: flex-start;\n color: var(--_muted);\n font-size: 12px;\n padding: 2px 4px;\n}\n\n/* Inline confirmation card \u2014 lives in the transcript, no focus-stealing overlay. */\n.confirm {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--_bg);\n border: 1px solid var(--_accent);\n border-radius: 10px;\n}\n\n.confirm[data-resolved] {\n opacity: 0.7;\n border-color: var(--_border);\n}\n\n.confirm-body {\n font-weight: 600;\n}\n\n.confirm-args {\n margin: 0;\n padding: 8px 10px;\n max-height: 140px;\n overflow: auto;\n /* Scrolling past the end of this must not scroll the page behind it. */\n overscroll-behavior: contain;\n font-size: 12px;\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n background: var(--_assistant-bg);\n border-radius: 8px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.confirm-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.confirm-btn {\n border: 1px solid var(--_border);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--_bg);\n color: var(--_fg);\n}\n\n.confirm-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.confirm-btn--confirm {\n border-color: var(--_accent);\n background: var(--_accent);\n color: var(--_on-accent);\n}\n\n/* The session waiver. Deliberately the quietest of the three: it is the widest\n decision on the card, so it should be reachable without being the one the eye\n lands on when the user means to say yes once. */\n.confirm-btn--always {\n font-weight: 500;\n opacity: 0.85;\n}\n\n.confirm-btn--always:hover,\n.confirm-btn--always:focus-visible {\n opacity: 1;\n}\n\n/* Editable arguments on an approval card. A plain field rather than a code\n editor: it holds the JSON a card already displays, and the only interaction\n is correcting a value before letting the call run. */\n.approval-edit {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.approval-args {\n box-sizing: border-box;\n width: 100%;\n resize: vertical;\n border: 1px solid var(--_border);\n border-radius: 8px;\n padding: 8px;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.85em;\n background: var(--_bg);\n color: var(--_fg);\n}\n\n.approval-args:focus-visible {\n border-color: var(--_accent);\n outline: none;\n}\n\n.approval-error {\n font-size: 0.85em;\n color: var(--_danger);\n}\n\n/* Message action row. Sits under a finished assistant bubble.\n\n The wrap is insurance rather than a fix: these buttons are glyph-only, so at\n every width tested they fit on one line and removing the wrap changes\n nothing. It is here because the confirmation row one release earlier did\n overflow when it gained a third button, off the left edge and outside its own\n card, and the difference between the two rows is only that this one's labels\n are icons today. */\n.message-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n /* Negative, and that is the point. The answer group is a flex column with its\n own gap, so a positive margin here pushes the row further from the message\n it acts on than the next card is below it -- the buttons then read as\n belonging to whatever follows. Pulling back inside the gap is what makes\n them the message's own. */\n margin-top: -6px;\n}\n\n/* The row's controls are icon-only, so the box is the whole target. Sized from\n a variable a host can raise, with a floor rather than a fixed height: the\n compact density shrinks the font, and a target that shrinks with it lands\n under the 24px minimum that makes a control reliably tappable. It used to,\n at roughly 20px square. */\n.message-action {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n min-width: var(--_action-size);\n min-height: var(--_action-size);\n padding: 4px;\n border: none;\n border-radius: 6px;\n font: inherit;\n line-height: 1.2;\n cursor: pointer;\n background: transparent;\n color: var(--_muted);\n opacity: 0.75;\n /* The tooltip below is positioned against this box. */\n position: relative;\n}\n\n.message-action:hover,\n.message-action:focus-visible {\n opacity: 1;\n background: var(--_border);\n}\n\n.message-action-icon {\n display: inline-flex;\n width: var(--_action-icon-size);\n height: var(--_action-icon-size);\n}\n\n.message-action-icon .glyph {\n width: 100%;\n height: 100%;\n}\n\n/* The label, drawn rather than left to the browser.\n\n A title attribute is the usual answer and covers only half the readers: it\n never appears on keyboard focus, so tabbing onto an icon-only control shows\n nothing at all. The attribute stays for the pointer users who expect it, and\n this shows the same words on hover and on focus alike.\n\n Left-aligned rather than centred because the row sits at the left edge of an\n answer inside a scrolling column, and a centred tooltip on the first control\n is clipped by that column. Growing rightward keeps it inside.\n\n Not exposed to assistive technology: the button already carries the same\n string as its accessible name, and this would be a second copy of it. */\n.message-action::after {\n content: attr(data-tooltip);\n position: absolute;\n bottom: calc(100% + 4px);\n left: 0;\n z-index: 3;\n padding: 3px 6px;\n border-radius: 4px;\n background: var(--_tooltip-bg);\n color: var(--_tooltip-fg);\n font-size: 11px;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: none;\n opacity: 0;\n transition: opacity var(--_motion) var(--_ease);\n}\n\n.message-action:hover::after,\n.message-action:focus-visible::after {\n opacity: 1;\n}\n\n/* Touch has no hover to reveal it, and a tooltip that latches open under a\n finger covers the answer it belongs to. */\n@media (hover: none) {\n .message-action::after {\n content: none;\n }\n}\n\n/* Touch. Separate from the width breakpoint above on purpose: width decides\n the layout, the pointer decides which controls make sense, and a touch\n laptop is coarse-pointered and wide while a narrow desktop window is\n fine-pointered and small. */\n@media (pointer: coarse) {\n /* A 6px edge strip is not a control, it is a trap that eats a scroll. The\n corners are 14px, still under the 24px floor a target is meant to clear,\n and the panel has other ways to be resized on a device that has a pointer\n precise enough to grab one. */\n .resize-handle {\n display: none;\n }\n\n /* Platform guidance is 44pt on iOS and 48dp on Android. These sit at 28-30px,\n which clears the WCAG minimum and misses both. */\n :host {\n --_action-size: var(--ag-ui-action-size, 44px);\n --_tool-btn-size: var(--ag-ui-tool-btn-size, 44px);\n --_send-size: var(--ag-ui-send-size, 44px);\n /* Missed the first time these were raised, which left the header -- the\n widget's primary controls, and the only way to reach history, a new chat\n or the collapse -- at half the size of everything beside it. */\n --_header-btn-size: var(--ag-ui-header-btn-size, 44px);\n --_header-gap: var(--ag-ui-header-gap, 6px);\n }\n\n /* iOS Safari zooms the page when a control under 16px takes focus, which\n drags the whole fixed panel with it and leaves the user pinching back out\n of a chat they only wanted to type into. The composer inherits the widget\n font, which is 14px by default and 13px at compact density, so it has to\n say 16 outright -- and only ever upward, so a host that has deliberately\n set something larger keeps it. */\n .input {\n font-size: max(16px, var(--_font-size));\n }\n}\n\n.message-action[aria-pressed=\"true\"] {\n opacity: 1;\n color: var(--_accent);\n}\n\n.message-action--confirmed {\n opacity: 1;\n color: var(--_accent);\n}\n\n/* Approval card \u2014 the server-side-tool gate (approve/deny an interrupt). */\n.approval {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--_bg);\n border: 1px solid var(--_accent);\n border-radius: 10px;\n}\n\n.approval[data-resolved] {\n opacity: 0.7;\n border-color: var(--_border);\n}\n\n.approval-body {\n font-weight: 600;\n}\n\n.approval-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.approval-btn {\n border: 1px solid var(--_border);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--_bg);\n color: var(--_fg);\n}\n\n.approval-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.approval-btn--approve {\n border-color: var(--_accent);\n background: var(--_accent);\n color: var(--_on-accent);\n}\n\n/* Question card \u2014 the built-in ask_user prompt (radios and/or free text). */\n.question {\n align-self: stretch;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--_bg);\n border: 1px solid var(--_accent);\n border-radius: 10px;\n}\n\n.question[data-resolved] {\n opacity: 0.7;\n border-color: var(--_border);\n}\n\n.question-body {\n font-weight: 600;\n}\n\n.question-options {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.question-choice {\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n}\n\n.question-input {\n box-sizing: border-box;\n width: 100%;\n padding: 8px 10px;\n font: inherit;\n color: var(--_fg);\n background: var(--_bg);\n border: 1px solid var(--_border);\n border-radius: 8px;\n}\n\n.question-input:disabled {\n opacity: 0.6;\n}\n\n.question-actions {\n display: flex;\n justify-content: flex-end;\n}\n\n.question-btn {\n border: 1px solid var(--_accent);\n border-radius: 8px;\n padding: 8px 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n background: var(--_accent);\n color: var(--_on-accent);\n}\n\n.question-btn:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n/* Skills \u2014 chips row + the /-command palette, above the input. */\n.skill-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 10px 12px;\n}\n\n.skill-chip {\n border: 1px solid var(--_border);\n border-radius: 999px;\n padding: 4px 12px;\n font: inherit;\n font-size: 0.9em;\n cursor: pointer;\n background: var(--_assistant-bg);\n color: var(--_fg);\n}\n\n.skill-chip:hover {\n border-color: var(--_accent);\n}\n\n/* Follow-up suggestion chips. Deliberately the skill chips' shape rather than a\n second chip vocabulary -- both are \"a question you could ask\", and the only\n difference is who chose it. Inside the transcript, so they scroll with the\n answer they follow instead of hovering above the composer. */\n.suggestions {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-self: stretch;\n}\n\n.suggestion-chip {\n border: 1px solid var(--_border);\n border-radius: 999px;\n padding: 4px 12px;\n font: inherit;\n font-size: 0.9em;\n text-align: left;\n cursor: pointer;\n background: var(--_assistant-bg);\n color: var(--_fg);\n}\n\n.suggestion-chip:hover,\n.suggestion-chip:focus-visible {\n border-color: var(--_accent);\n}\n\n.skill-palette {\n margin: 8px 12px 0;\n display: flex;\n flex-direction: column;\n max-height: 220px;\n overflow: auto;\n /* Scrolling past the end of this must not scroll the page behind it. */\n overscroll-behavior: contain;\n background: var(--_bg);\n border: 1px solid var(--_border);\n border-radius: 8px;\n box-shadow: 0 8px 24px rgba(20, 20, 50, 0.16);\n}\n\n.skill-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n align-items: flex-start;\n padding: 8px 12px;\n border: none;\n background: none;\n font: inherit;\n text-align: left;\n cursor: pointer;\n color: var(--_fg);\n}\n\n.skill-item[aria-selected=\"true\"] {\n background: var(--_assistant-bg);\n}\n\n.skill-item-title {\n font-weight: 600;\n}\n\n.skill-item-desc {\n font-size: 0.85em;\n color: var(--_muted);\n}\n\n/* The hint sits directly above the composer's top border, so a zero bottom\n margin left the text touching the divider. */\n.skill-hint {\n margin: 8px 12px;\n font-size: 0.85em;\n line-height: 1.4;\n color: var(--_danger);\n}\n\n/* Chat-history drawer \u2014 a slide-over within the chat panel.\n The hidden attribute stays the single source of truth for open/closed (no\n JS-driven animation state): display is transitioned discretely, so on close\n the drawer stays displayed for the whole exit and is removed only at the end,\n which is what lets the backdrop fade and the panel slide out. */\n.drawer {\n position: absolute;\n inset: 0;\n z-index: 5;\n display: flex;\n transition: visibility var(--_motion) var(--_ease);\n}\n\n/* Closed. The overlay keeps its box (display, not none) so the backdrop and\n panel inside it stay rendered and can transition both ways; visibility is\n what takes the whole subtree out of the tab order, the a11y tree and hit\n testing at rest, and it holds off until the slide has finished. */\n.drawer[hidden] {\n display: flex;\n visibility: hidden;\n pointer-events: none;\n}\n\n.drawer[hidden] .drawer-backdrop {\n opacity: 0;\n}\n\n.drawer[hidden] .drawer-panel {\n transform: translateX(-100%);\n}\n\n.checkpoints {\n position: absolute;\n inset-block-start: 3rem;\n inset-inline: 0.75rem;\n z-index: 6;\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n padding: 0.5rem;\n border: 1px solid var(--_border);\n border-radius: 0.5rem;\n background: var(--_assistant-bg);\n box-shadow: 0 6px 24px rgb(0 0 0 / 12%);\n max-height: 60%;\n overflow-y: auto;\n /* Scrolling past the end of this must not scroll the page behind it. */\n overscroll-behavior: contain;\n transform-origin: top center;\n transition:\n opacity var(--_motion) var(--_ease),\n transform var(--_motion) var(--_ease),\n visibility var(--_motion) var(--_ease);\n}\n\n/* Same idiom as the drawer: laid out at rest, hidden by visibility, so the\n popover can animate open and closed. */\n.checkpoints[hidden] {\n display: flex;\n visibility: hidden;\n pointer-events: none;\n opacity: 0;\n transform: scale(0.96) translateY(-6px);\n}\n\n.checkpoints-title {\n font-size: 0.75rem;\n font-weight: 600;\n opacity: 0.7;\n}\n\n.checkpoints-empty {\n padding: 0.5rem 0.25rem;\n font-size: 0.8125rem;\n opacity: 0.7;\n}\n\n/* A row is a label and two buttons, and nothing about the row itself is\n pressable. It used to light up on hover, which is the affordance of something\n clickable and made the buttons look like decoration on a clickable strip. The\n resting surface groups the row instead, so hover can mean what it says: only\n the buttons respond to it.\n\n It wraps for the same reason the tool-call head does. Every child but the label\n is fixed-width, so in a narrow panel the label is the only thing that can give\n -- and a flex-basis of zero lets it give everything. Adding the run id was\n enough to crush \"just now\" to zero pixels: present, correct, and invisible.\n Wrapping puts the buttons on their own line instead. */\n.checkpoint-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 0.5rem;\n padding: 0.3125rem 0.4375rem;\n border-radius: 0.375rem;\n background: var(--_hover);\n}\n\n/* Grows into spare room, and refuses to shrink past the shortest thing it ever\n says. A time is short and bounded, so there is no case for eliding it. */\n.checkpoint-label {\n flex: 1 1 auto;\n min-width: 7ch;\n font-size: 0.8125rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* When the label holds the run's first message, the time moves here: still worth\n showing, no longer what identifies the row. Muted and unshrinkable, so it does\n not compete with the words beside it. */\n.checkpoint-time {\n flex: 0 0 auto;\n font-size: 0.6875rem;\n opacity: 0.7;\n white-space: nowrap;\n}\n\n/* Enough of the run id to tell two runs apart when both say \"just now\". Muted\n and monospaced: it is a reference, not a name. */\n.checkpoint-id {\n flex: 0 0 auto;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.6875rem;\n opacity: 0.55;\n}\n\n/* On the panel's own surface, not the row's: the row now paints the hover token\n itself, and a badge the same colour as what it sits on is not a badge. */\n.checkpoint-branch {\n font-size: 0.6875rem;\n padding: 0 0.375rem;\n border-radius: 999px;\n background: var(--_assistant-bg);\n opacity: 0.8;\n}\n\n/* The two things in the row that actually do something, so they are the two\n things that look like it: a filled surface at rest rather than a transparent\n outline, which on top of the old row highlight was nearly invisible. */\n.checkpoint-action {\n font: inherit;\n font-size: 0.75rem;\n line-height: 1.4;\n cursor: pointer;\n padding: 0.1875rem 0.5625rem;\n border: 1px solid var(--_border);\n border-radius: 0.375rem;\n background: var(--_bg);\n color: inherit;\n transition:\n background var(--_motion) var(--_ease),\n border-color var(--_motion) var(--_ease),\n transform var(--_motion) var(--_ease);\n}\n\n/* Resume is what a reader wants nine times in ten; fork is the deliberate choice\n beside it. Filled and outlined, the same pair the confirmation and approval\n cards already use for their primary and secondary action. */\n.checkpoint-resume {\n font-weight: 600;\n border-color: var(--_accent);\n background: var(--_accent);\n color: var(--_on-accent);\n}\n\n.checkpoint-fork:hover {\n background: var(--_hover);\n border-color: var(--_accent);\n}\n\n/* The filled one cannot go lighter on hover without losing its contrast with the\n white label, so it dims instead. */\n.checkpoint-resume:hover {\n opacity: 0.88;\n}\n\n/* Pressed: a pixel down, so the click is felt as well as seen. */\n.checkpoint-action:active {\n transform: translateY(1px);\n}\n\n/* Keyboard focus was invisible here, in a panel that traps focus and is reached\n by Tab -- so the one navigation path guaranteed to land on these buttons was\n the one with nothing to show for it. */\n.checkpoint-action:focus-visible {\n outline: 2px solid var(--_accent);\n outline-offset: 2px;\n}\n\n.drawer-backdrop {\n position: absolute;\n inset: 0;\n background: rgba(20, 20, 50, 0.32);\n opacity: 1;\n transition: opacity var(--_motion) var(--_ease);\n}\n\n.drawer-panel {\n position: relative;\n display: flex;\n flex-direction: column;\n width: min(300px, 85%);\n height: 100%;\n background: var(--_bg);\n border-right: 1px solid var(--_border);\n box-shadow: var(--_shadow);\n overflow: hidden;\n transform: none;\n transition: transform var(--_motion) var(--_ease);\n}\n\n.drawer-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--_space);\n padding: var(--_pad);\n border-bottom: 1px solid var(--_border);\n}\n\n.drawer-title {\n /* Takes the slack so the two controls group at the trailing edge. With\n space-between and three items the middle one floats, which reads as the\n title and the close button being a pair with New chat wedged between\n them. Truncates rather than pushing them off the row. */\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 600;\n}\n\n.drawer-new {\n flex: 0 0 auto;\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n background: var(--_bg);\n color: var(--_accent);\n padding: 4px 10px;\n font: inherit;\n font-size: 0.85em;\n cursor: pointer;\n}\n\n/* Narrows the list. Sits under the header rather than in it: the header's two\n controls act on the conversation, and a field that filters what is below it\n belongs with what it filters. */\n/* What is waiting for the run to finish. Above the composer, beside the\n attachment tray, because both are things already handed over and not yet\n sent. Each chip is a button: pressing it takes that message back. */\n.queued {\n display: flex;\n flex-wrap: wrap;\n gap: var(--_space);\n padding: 0 var(--_pad) var(--_space);\n}\n\n.queued-chip {\n max-width: 100%;\n overflow: hidden;\n padding: 4px 10px;\n border: 1px dashed var(--_border);\n border-radius: var(--_msg-radius);\n background: var(--_bg);\n color: var(--_muted);\n font: inherit;\n font-size: 0.9em;\n text-overflow: ellipsis;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.queued-chip:hover {\n border-style: solid;\n color: var(--_fg);\n}\n\n.drawer-filter {\n flex: 0 0 auto;\n box-sizing: border-box;\n width: calc(100% - var(--_pad) * 2);\n margin: var(--_space) var(--_pad) 0;\n padding: 6px 10px;\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n background: var(--_input-bg);\n color: var(--_fg);\n font: inherit;\n font-size: 0.9em;\n}\n\n.drawer-filter::placeholder {\n color: var(--_muted);\n}\n\n.drawer-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n /* Scrolling past the end of this must not scroll the page behind it. */\n overscroll-behavior: contain;\n}\n\n.drawer-empty {\n padding: var(--_pad);\n font-size: 0.9em;\n color: var(--_muted);\n}\n\n.drawer-row {\n display: flex;\n align-items: stretch;\n border-bottom: 1px solid var(--_border);\n}\n\n.drawer-row--active {\n background: var(--_assistant-bg);\n}\n\n.drawer-row-select {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.drawer-row-title {\n font-weight: 600;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.drawer-row-time {\n font-size: 0.72em;\n color: var(--_muted);\n}\n\n.drawer-row-preview {\n font-size: 0.8em;\n color: var(--_muted);\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.drawer-row-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 0 6px;\n}\n\n.drawer-row-rename,\n.drawer-row-delete {\n border: none;\n background: none;\n color: var(--_muted);\n font-size: 0.9em;\n padding: 4px;\n cursor: pointer;\n}\n\n.drawer-rename-input {\n flex: 1;\n min-width: 0;\n margin: 6px 10px;\n padding: 4px 8px;\n border: 1px solid var(--_accent);\n border-radius: 6px;\n background: var(--_input-bg);\n color: var(--_fg);\n font: inherit;\n}\n\n.drawer-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n font-size: 0.85em;\n}\n\n.drawer-confirm-label {\n color: var(--_danger);\n}\n\n.drawer-confirm-yes {\n border: none;\n border-radius: 6px;\n background: var(--_danger);\n color: var(--_on-danger);\n padding: 3px 10px;\n font: inherit;\n cursor: pointer;\n}\n\n.drawer-confirm-no {\n border: 1px solid var(--_border);\n border-radius: 6px;\n background: none;\n color: inherit;\n padding: 3px 10px;\n font: inherit;\n cursor: pointer;\n}\n\n/* Embedded placement: an inline, flush side panel rather than a dimmed,\n floating slide-over. */\n:host([placement=\"embedded\"]) .drawer-backdrop {\n background: none;\n}\n\n/* The way back out of the list. It sits beside New chat, which is the control\n it must not be mistaken for: one returns you to the conversation you were\n reading, the other replaces it. */\n.drawer-close {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: 0;\n border-radius: 6px;\n background: none;\n color: inherit;\n font: inherit;\n font-size: 18px;\n line-height: 1;\n cursor: pointer;\n}\n\n.drawer-close:hover {\n background: var(--_hover);\n}\n\n:host([placement=\"embedded\"]) .drawer-panel {\n width: 100%;\n border-right: none;\n box-shadow: none;\n}\n";
2
2
  //# sourceMappingURL=styles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/ui/styles.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,MAAM,8m1FA48FlB,CAAC"}
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/ui/styles.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,MAAM,g7iHAk9GlB,CAAC"}
@@ -1,6 +1,13 @@
1
1
  import type { ThreadMeta } from "../core/conversation_store.js";
2
2
  import { type RelativeTimeFormatter } from "./relative_time.js";
3
3
  import { type UiStrings } from "./ui_strings.js";
4
+ /**
5
+ * How many conversations there have to be before the filter appears.
6
+ *
7
+ * A search box above a list you can already read in one glance is a control
8
+ * asking to be used on nothing.
9
+ */
10
+ export declare const FILTER_FROM = 8;
4
11
  /** Actions the host ({@link AgUiChat}) wires to the drawer's rows. */
5
12
  export interface ThreadDrawerCallbacks {
6
13
  /** A row was picked — load that thread and make it active. */
@@ -11,6 +18,15 @@ export interface ThreadDrawerCallbacks {
11
18
  readonly onRename: (threadId: string, title: string) => void;
12
19
  /** A row was deleted (after the inline confirm). */
13
20
  readonly onDelete: (threadId: string) => void;
21
+ /**
22
+ * The list opened or closed.
23
+ *
24
+ * There are five ways out of it -- the close control, the backdrop, Escape,
25
+ * picking a row and starting a new chat -- and only two of them go through
26
+ * the host. A docked list moves the transcript over, so the host has to know
27
+ * about all five or the transcript stays shifted around nothing.
28
+ */
29
+ readonly onVisibility?: (open: boolean) => void;
14
30
  }
15
31
  /**
16
32
  * The chat-history drawer: a slide-over listing the user's threads (title,
@@ -40,6 +56,11 @@ export declare class ThreadDrawer {
40
56
  */
41
57
  setRelativeTimeFormatter(format: RelativeTimeFormatter | null): void;
42
58
  setStrings(strings: UiStrings): void;
59
+ /**
60
+ * Whether the list is a dialog over the conversation or a rail beside it.
61
+ * The host decides, because only the placement knows whether there is room.
62
+ */
63
+ setModal(modal: boolean): void;
43
64
  isOpen(): boolean;
44
65
  open(): void;
45
66
  close(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"thread_drawer.d.ts","sourceRoot":"","sources":["../../src/ui/thread_drawer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,KAAK,qBAAqB,EAAgB,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,YAAY;;IACvB,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IAcjC,YAAY,SAAS,EAAE,qBAAqB,EAAE,OAAO,GAAE,SAA8B,EA+CpF;IAED,iFAAiF;IACjF;;;;;;;OAOG;IACH,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,GAAG,IAAI,CAEnE;IASD,UAAU,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,CAMnC;IAED,MAAM,IAAI,OAAO,CAEhB;IAED,IAAI,IAAI,IAAI,CASX;IAED,KAAK,IAAI,IAAI,CAOZ;IAED,MAAM,IAAI,IAAI,CAMb;IAgCD,4EAA4E;IAC5E,UAAU,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAIjE;CAiJF"}
1
+ {"version":3,"file":"thread_drawer.d.ts","sourceRoot":"","sources":["../../src/ui/thread_drawer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,KAAK,qBAAqB,EAAgB,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE;;;;;GAKG;AACH,eAAO,MAAM,WAAW,IAAI,CAAC;AAE7B,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACjD;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,YAAY;;IACvB,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IAgCjC,YAAY,SAAS,EAAE,qBAAqB,EAAE,OAAO,GAAE,SAA8B,EA2EpF;IAED,iFAAiF;IACjF;;;;;;;OAOG;IACH,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,GAAG,IAAI,CAEnE;IASD,UAAU,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,CAUnC;IAED;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAS7B;IAED,MAAM,IAAI,OAAO,CAEhB;IAED,IAAI,IAAI,IAAI,CAYX;IAED,KAAK,IAAI,IAAI,CAYZ;IAED,MAAM,IAAI,IAAI,CAMb;IAoCD,4EAA4E;IAC5E,UAAU,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAIjE;CAqLF"}
@@ -14,6 +14,22 @@ export interface UiStrings {
14
14
  title: string;
15
15
  /** History button + drawer dialog label. */
16
16
  chatHistory: string;
17
+ /** Dismiss the chat-history list and return to the conversation. */
18
+ closeHistory: string;
19
+ /** Placeholder in the conversation-list filter. */
20
+ searchConversations: string;
21
+ /** Names the row of messages waiting for the run to finish. */
22
+ queued: string;
23
+ /** Drops one waiting message. `{text}` is the message. */
24
+ removeQueued: string;
25
+ /** Shown when a filter matches nothing, unlike having no conversations. */
26
+ noMatches: string;
27
+ /** Notice text when the agent moved its own panel out of the way. */
28
+ chatMoved: string;
29
+ /** Notice text when the agent collapsed its own panel. */
30
+ chatMinimised: string;
31
+ /** The one control a run notice may carry. */
32
+ undo: string;
17
33
  /** New-chat button (header + drawer). */
18
34
  newChat: string;
19
35
  /** Collapse button. */