dsh-zen-remote 2.0.1 → 2.0.3

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.
@@ -1,2 +1,2 @@
1
- export declare const COMPAT_CSS = " /* ---------- dsh-web-ui family compatibility ----------\n The linxin666 plugin suite extends the shell frame directly:\n - aionui-panel appends two trailing grid columns (explorer / preview)\n plus absolute drag handles to [data-dsh-frame]; its 5-track inline\n grid is already overridden above, but the handles and columns would\n still float over the main UI. On mobile the columns leave the grid\n as floating bottom sheets and keep their own visibility state \u2014\n the suite's collapse chevron / preview tabs still work, so no\n feature is lost. The task-board / ssh plugins inject sidebar\n entries and center-column takeover panels; the entries need\n spacing and the kanban needs scrollable columns. */\n\n /* Touch devices: the drag handles are useless \u2014 the floating expand\n button is the opener. */\n .aionui-explorer-handle,\n .aionui-preview-handle {\n display: none !important;\n }\n\n /* Shared base: both columns leave the grid as floating panels. The\n explorer is gated shut by default (its own persisted expanded state\n must never cover the mobile UI on load); the header Files action opens\n it via the frame marker below, and the sheet's own collapse chevron\n clears it. Preview stays owned by the suite (hidden while no tab is\n open). The per-column rules below override the geometry. */\n [data-aionui-explorer-col],\n [data-aionui-preview-col] {\n position: fixed !important;\n z-index: 55 !important;\n background: var(--aion-bg-base, #ffffff) !important;\n border-left: none !important;\n }\n /* Explorer (file tree) bottom sheet: bottom edge aligned exactly with\n the composer card's bottom line \u2014 the card sits 36px above the\n viewport bottom (8px composer padding + the 28px stats strip below\n the card), so the sheet uses the same 36px bottom offset. */\n [data-aionui-explorer-col] {\n visibility: hidden !important;\n left: 8px !important;\n right: 8px !important;\n top: auto !important;\n bottom: 36px !important;\n width: auto !important;\n height: min(55dvh, 460px) !important;\n max-height: calc(100dvh - 44px) !important;\n border-radius: 14px !important;\n overflow: hidden !important;\n box-shadow: 0 -4px 28px rgba(0, 0, 0, .18) !important;\n animation: dsh-mobile-nav-sheet-up .24s var(--ds-ease-out, ease-in-out) !important;\n }\n /* Preview (file content) bottom sheet. Gated shut by default: the suite\n persists open preview tabs in localStorage and restores them on load,\n which would pop the sheet over the fresh UI. The client only sets the\n frame marker after the user taps a file row in the explorer; the\n suite's own collapse chevron clears it via the visibility watcher. */\n [data-aionui-preview-col] {\n visibility: hidden !important;\n position: fixed !important;\n left: 8px !important;\n right: 8px !important;\n top: auto !important;\n bottom: 40px !important;\n width: auto !important;\n height: min(50dvh, 420px) !important;\n max-height: calc(100dvh - 48px) !important;\n border-radius: 14px !important;\n overflow: hidden !important;\n box-shadow: 0 -4px 28px rgba(0, 0, 0, .18) !important;\n z-index: 56 !important;\n animation: dsh-mobile-nav-sheet-up .24s var(--ds-ease-out, ease-in-out) !important;\n /* Fullscreen toggle (issue #8): animate the geometry change instead of\n snapping. visibility is deliberately not listed, so opening/closing\n the sheet stays instant; the open/close keyframes own transform. */\n transition:\n left .24s var(--ds-ease-out, ease-in-out),\n right .24s var(--ds-ease-out, ease-in-out),\n top .24s var(--ds-ease-out, ease-in-out),\n bottom .24s var(--ds-ease-out, ease-in-out),\n width .24s var(--ds-ease-out, ease-in-out),\n height .24s var(--ds-ease-out, ease-in-out),\n border-radius .24s var(--ds-ease-out, ease-in-out),\n box-shadow .24s var(--ds-ease-out, ease-in-out),\n padding-top .24s var(--ds-ease-out, ease-in-out) !important;\n }\n /* User-opened preview sheet (frame marker, set on file-row tap). */\n [data-mobile-nav=\"frame\"][data-aionui-preview-open] [data-aionui-preview-col] {\n visibility: visible !important;\n }\n /* The Files action opens the explorer sheet (frame marker). */\n [data-mobile-nav=\"frame\"][data-aionui-explorer-open] [data-aionui-explorer-col] {\n visibility: visible !important;\n }\n /* While the preview sheet is up, the explorer sheet yields (two stacked\n bottom sheets would read as one broken overlay). Closing the preview\n via its collapse chevron / tab close clears the marker, and the\n explorer sheet returns. Same specificity as the explorer-open rule, so\n this must stay AFTER it. */\n [data-mobile-nav=\"frame\"][data-aionui-preview-open] [data-aionui-explorer-col] {\n visibility: hidden !important;\n }\n /* The open drawer must never sit under a sheet: while the frame is in the\n narrow-expanded state both sheets yield (later in the file than the\n open marker rule, so it wins at equal specificity). The fullscreen\n toggle has its own drawer-open rule at the end of its section. */\n [data-mobile-nav=\"frame\"]:not([data-sidebar-collapsed]) [data-aionui-explorer-col],\n [data-mobile-nav=\"frame\"]:not([data-sidebar-collapsed]) [data-aionui-preview-col] {\n visibility: hidden !important;\n display: none !important;\n }\n /* The suite's own expand button reads the store state we bypass on\n mobile \u2014 hide it; the header Files action is the opener. */\n .aionui-floating-expand {\n display: none !important;\n }\n\n /* Preview sheet fullscreen toggle (issue #8): a fixed button parked in the\n sheet's titlebar row, just left of the suite's collapse chevron (24px at\n right:8px of the sheet, and the sheet spans 8px..(100vw-8px)). The top\n calc mirrors the sheet geometry above (bottom 40px + min(50dvh, 420px));\n when the frame carries \"data-mobile-preview-full\" the sheet goes\n fullscreen and the button moves to the viewport corner. */\n [data-mobile-nav=\"preview-full-toggle\"] {\n position: absolute !important;\n right: 36px !important;\n top: 8px !important;\n z-index: 57 !important;\n display: none !important;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--aion-text-secondary, var(--dsw-alias-label-secondary, inherit));\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n /* Native look: same size/radius/hover language as the suite's tab-bar\n icon buttons (the 20px panelCollapse next to it). The button lives\n INSIDE the preview column, so it rides the sheet's own open\n animation and geometry transition \u2014 no curve matching needed. */\n transition: background-color .15s, top .24s var(--ds-ease-out, ease-in-out);\n }\n [data-mobile-nav=\"preview-full-toggle\"]:hover {\n background: var(--aion-bg-3, rgba(0, 0, 0, .22));\n }\n [data-mobile-nav=\"preview-full-toggle\"]:active {\n background: var(--aion-bg-active, rgba(0, 0, 0, .28));\n }\n [data-mobile-nav=\"preview-full-toggle\"]:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);\n outline-offset: 2px;\n }\n [data-mobile-nav=\"preview-full-toggle\"] svg {\n width: 14px;\n height: 14px;\n }\n /* Keep the last tab (and the \"+\" URL-tab trigger) from sliding under the\n fullscreen toggle: reserve the right end of the preview tab row. */\n [data-aionui-preview-col] [class$=\"_tabScroll\"] {\n padding-right: 34px !important;\n }\n /* Visible only while the preview sheet is open. Visibility itself is\n inherited from the column, so the sheet's own hide rules (collapse,\n drawer open) cover the button too. */\n [data-mobile-nav=\"frame\"][data-aionui-preview-open] [data-aionui-preview-col] [data-mobile-nav=\"preview-full-toggle\"] {\n display: inline-flex !important;\n }\n /* Icon swap on the frame fullscreen marker. */\n [data-mobile-nav=\"preview-full-toggle\"] .dsh-mobile-nav-full-out {\n display: none !important;\n }\n [data-mobile-nav=\"frame\"][data-mobile-preview-full] [data-aionui-preview-col] [data-mobile-nav=\"preview-full-toggle\"] .dsh-mobile-nav-full-in {\n display: none !important;\n }\n [data-mobile-nav=\"frame\"][data-mobile-preview-full] [data-aionui-preview-col] [data-mobile-nav=\"preview-full-toggle\"] .dsh-mobile-nav-full-out {\n display: inline !important;\n }\n /* Fullscreen preview: the sheet fills the whole viewport (notch included);\n the safe-area padding drops the titlebar row below the status bar, and\n the toggle follows the titlebar into the top corner. */\n [data-mobile-nav=\"frame\"][data-aionui-preview-open][data-mobile-preview-full] [data-aionui-preview-col] {\n inset: 0 !important;\n left: 0 !important;\n right: 0 !important;\n top: 0 !important;\n bottom: 0 !important;\n width: 100% !important;\n height: 100dvh !important;\n max-height: none !important;\n box-sizing: border-box !important;\n padding-top: var(--mnav-sat) !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n z-index: 57 !important;\n animation: none !important;\n }\n /* Fullscreen: the column fills the viewport, so the button follows the\n titlebar row down below the notch. */\n [data-mobile-nav=\"frame\"][data-mobile-preview-full] [data-aionui-preview-col] [data-mobile-nav=\"preview-full-toggle\"] {\n top: calc(var(--mnav-sat) + 8px) !important;\n }\n @media (prefers-reduced-motion: reduce) {\n [data-aionui-preview-col],\n [data-mobile-nav=\"preview-full-toggle\"] {\n transition: none !important;\n animation: none !important;\n }\n }\n\n /* dsh-web-ui sidebar entries (task board / ssh) sit flush against each\n other \u2014 give the injected rows breathing room. */\n button[data-dsh-taskboard-entry],\n button[data-dsh-ssh-entry] {\n margin-bottom: 8px !important;\n }\n\n /* Task board: five kanban columns at minmax(0,1fr) crush into ~78px phone\n strips. Give every column a usable minimum and let the row scroll. */\n [data-dsh-taskboard-board] > [class$=\"_columns\"] {\n grid-template-columns: repeat(5, minmax(240px, 1fr)) !important;\n overflow-x: auto !important;\n }\n /* The floating button must not float over a takeover panel (task board /\n ssh own the center column while active). */\n html[data-dsh-taskboard-active] [data-mobile-nav=\"fab\"],\n html[data-dsh-ssh-active] [data-mobile-nav=\"fab\"],\n html[data-dsh-taskboard-active] [data-mobile-nav=\"backdrop\"],\n html[data-dsh-ssh-active] [data-mobile-nav=\"backdrop\"] {\n display: none !important;\n }\n /* Board header: let the search field take the slack instead of squeezing\n the action buttons. */\n [data-dsh-taskboard-board] > [class$=\"_boardHeader\"] [class$=\"_search\"] {\n flex: 1 1 auto !important;\n min-width: 80px !important;\n }\n\n /* ---------- dsh-web-ui polish: plugin market search ----------\n The market tab row (Discover / Themes / Installed + the plugin search\n box) is a no-wrap flex: at 390px the tabs plus the ~218px search box\n (~475px total) overflow the ~334px sheet and the search box runs off\n the right edge of the screen (it also forces a horizontal scrollbar on\n the sheet's options area). Let the row wrap: the tabs keep the first\n line and the search box gets its own full-width second line. */\n\n [aria-modal=\"true\"] [class$=\"_tabs\"] {\n flex-wrap: wrap !important;\n row-gap: 8px !important;\n }\n [aria-modal=\"true\"] [class$=\"_searchInline\"] {\n flex: 1 1 100% !important;\n width: 100% !important;\n max-width: 100% !important;\n }\n\n /* ---------- dsh-usage-stats polish: usage & balance panel ----------\n The panel's stats row shows three token counters side by side\n (today / month / total). The counters use tabular nowrap figures whose\n min-content width overflows the ~336px panel body on a phone: figures\n clip at the row's edges and the panel grows a horizontal scrollbar.\n Stack the three counters vertically \u2014 full-width rows, so the figures\n always fit. */\n\n [class*=\"usg_\"][class$=\"_statsRow\"] {\n flex-direction: column !important;\n }\n [class*=\"usg_\"][class$=\"_stat\"] {\n flex: 0 0 auto !important;\n width: 100% !important;\n min-width: 0 !important;\n }\n\n /* ---------- dsh-web-ui polish: settings sheet ----------\n The official dialog is a desktop two-column form; on a phone the\n label/control split leaves a huge dead gap and long descriptions wrap\n into tall stacks. Stack each row (text above, control full-width) and\n keep the nav tabs on ONE horizontally scrolling row. */\n\n /* Nav tabs: single scrolling row instead of the 3-per-row grid \u2014 seven\n categories wrap into three rows on a phone (~130px of sheet height);\n one row with a thin scrollbar keeps every tab reachable and returns\n that space to the options area (user feedback 2026-08-16). An earlier\n one-row attempt had no scroll affordance and silently cut the last\n tab off; the thin scrollbar IS the affordance. Scoped to the frame\n marker: the desktop dialog keeps its official vertical nav column. */\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) > :first-child [class$=\"_navList\"] {\n display: flex !important;\n flex-wrap: nowrap !important;\n overflow-x: auto !important;\n overflow-y: hidden !important;\n gap: 6px !important;\n width: 100% !important;\n scrollbar-width: thin !important;\n -webkit-overflow-scrolling: touch !important;\n }\n /* Hairline scrollbar for the tab row: the default WebKit scrollbar reads\n fat on a phone; 2px keeps the scroll affordance without the bulk. */\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navList\"]::-webkit-scrollbar {\n height: 2px !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navList\"]::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-border-l2, rgba(0, 0, 0, .22)) !important;\n border-radius: 1px !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navList\"]::-webkit-scrollbar-track {\n background: transparent !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navCell\"] {\n flex: 0 0 auto !important;\n white-space: nowrap !important;\n padding: 6px 8px !important;\n gap: 6px !important;\n font-size: 13px !important;\n justify-content: flex-start !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navCell\"] svg {\n width: 14px !important;\n height: 14px !important;\n flex: none !important;\n }\n /* Content toolbar: the \"Open configuration file\" button is hidden on\n mobile \u2014 it is rarely needed on a phone and steals ~180px from the\n tab row's scroll area (user feedback 2026-08-16). Only the close \u2715\n stays, flush right in the nav row. Desktop untouched (frame scoped). */\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_header\"] [class$=\"_actions\"] {\n display: none !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_header\"] [class$=\"_actions\"] [class$=\"_action\"] {\n font-size: 13px !important;\n padding: 6px 12px !important;\n min-height: 0 !important;\n }\n /* Setting rows: text on top, control below at full width. */\n [aria-modal=\"true\"] [class$=\"_section\"] [class$=\"_row\"] {\n flex-direction: column !important;\n align-items: stretch !important;\n gap: 8px !important;\n }\n [aria-modal=\"true\"] [class$=\"_section\"] [class$=\"_row\"] > :first-child {\n width: 100% !important;\n max-width: none !important;\n }\n [aria-modal=\"true\"] [class$=\"_section\"] [class$=\"_row\"] > :last-child {\n width: 100% !important;\n max-width: none !important;\n }\n /* Appearance mode group: give the cube row a consistent bordered\n segmented look (the official borders differ per state). */\n [aria-modal=\"true\"] [class$=\"_cubeRow\"] > * {\n border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12)) !important;\n }\n\n /* ---------- dsh-web-ui polish: explorer sheet ----------\n The aionui explorer was designed for a desktop side column: compact the\n header, search box and tree rows so a phone shows more entries, and pad\n the scroll bottom so the last row never sits flush on the edge. */\n\n [data-aionui-explorer-col] [class$=\"_tabBar\"] {\n height: 36px !important;\n }\n [data-aionui-explorer-col] [class$=\"_tabBtn\"],\n [data-aionui-explorer-col] [class$=\"_tabBtnActive\"] {\n padding: 0 12px !important;\n font-size: 13px !important;\n }\n [data-aionui-explorer-col] [class$=\"_searchBox\"] {\n height: 32px !important;\n font-size: 13px !important;\n }\n [data-aionui-explorer-col] [class*=\"_treeRow\"] {\n height: 30px !important;\n font-size: 13px !important;\n }\n [data-aionui-explorer-col] [class*=\"_treeRow\"] svg {\n width: 14px !important;\n height: 14px !important;\n }\n [data-aionui-explorer-col] [class$=\"_scrollArea\"] {\n padding-bottom: 28px !important;\n }\n\n /* ---------- dsh-web-ui polish: drawer footer ----------\n The injected footer actions (Files + Session log) become two equal pill\n buttons instead of text-width capsules. */\n\n /* The official footerActions row also hosts the remote-web-ui entry\n row (two icon buttons); without wrapping the two groups squeeze each\n other on one line. Wrap so each group gets its own full-width row. */\n [data-mobile-nav=\"frame\"] [class$=\"_footerActions\"] {\n flex-wrap: wrap !important;\n gap: 6px !important;\n }\n [data-mobile-nav=\"drawer-actions\"] {\n width: 100% !important;\n }\n [data-mobile-nav=\"drawer-actions\"] > button {\n flex: 1 1 0 !important;\n padding: 0 8px !important;\n white-space: nowrap !important;\n }\n\n /* ---------- dsh-web-ui polish: floating pet ----------\n The whale-girl pet (dsh-pet) floats at the viewport corner with a\n persisted, draggable position. On phones the pet is scaled down so\n it does not dominate the screen; the plugin's own drag + persist\n still work (the position itself is left alone \u2014 the mobile default\n position is seeded via the pet API to just above the composer). */\n\n body > [class$=\"_float\"]:has([class$=\"_sprite\"][role=\"button\"]) {\n transform: scale(.66);\n transform-origin: bottom right;\n }\n /* While a modal dialog (settings sheet / export) owns the screen the pet\n floats ABOVE it and covers the dialog content; modal semantics say the\n background is inert, so hide the pet for the modal's lifetime. */\n body:has([aria-modal=\"true\"]) > [class$=\"_float\"]:has([class$=\"_sprite\"][role=\"button\"]) {\n display: none !important;\n }\n\n /* ---------- conversation stats line ----------\n The official session-status row (turns / steps / LLM time / TTFT /\n cache) is long. It is the single entry the official StatsLine puts in\n conversation.composer.dock, so the structural anchor below reaches it\n without any DOM marking (S3 deleted the text-matching effect that used\n to set [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"]). Layout: ONE fixed-height (28px) flex\n strip that scrolls horizontally \u2014 the full metrics stream stays\n reachable by swiping, the row never grows vertically, no ellipsis or\n fade, 12px gaps between metric groups, a 2px scrollbar as the swipe\n affordance. The phone breakpoint hides the strip outright instead\n (styles/composer.css.ts \u2014 its data moves into the session info card). */\n\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"] {\n display: flex !important;\n flex-flow: row nowrap !important;\n align-items: center !important;\n width: 100% !important;\n max-width: 100% !important;\n min-width: 0 !important;\n height: 28px !important;\n min-height: 28px !important;\n max-height: 28px !important;\n box-sizing: border-box !important;\n white-space: nowrap !important;\n overflow-x: auto !important;\n overflow-y: hidden !important;\n -webkit-overflow-scrolling: touch;\n overscroll-behavior-x: contain;\n scrollbar-width: thin !important;\n scrollbar-color: var(--dsw-alias-border-l1, rgba(0, 0, 0, .28)) transparent !important;\n padding: 0 0 4px !important;\n line-height: 20px !important;\n font-size: 12px !important;\n }\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"]::-webkit-scrollbar {\n height: 2px !important;\n }\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"]::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .3)) !important;\n border-radius: 2px !important;\n }\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"]::-webkit-scrollbar-track {\n background: transparent !important;\n }\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"] > * {\n display: flex !important;\n flex: 0 0 auto !important;\n flex-flow: row nowrap !important;\n align-items: center !important;\n width: max-content !important;\n min-width: max-content !important;\n max-width: none !important;\n white-space: nowrap !important;\n margin-right: 12px !important;\n padding: 0 !important;\n }\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"] > *:last-child {\n margin-right: 0 !important;\n }\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"] * {\n white-space: nowrap !important;\n }\n\n /* ---------- dsh-genui panel dock ----------\n The genui panel docks above the composer (conversation.input.dock,\n id genui-panel). On a phone its business-blue outline, generous chrome\n and single-line ellipsis read as an unfinished artifact: long titles\n truncate mid-word (\"\u2026default b\u00B7\u00B7\u00B7\") with the chevron glued to the\n ellipsis, and the pill crowds the composer. Mobile treatment: neutral\n card border matching the composer, tighter chrome so the full title\n fits, chevron with breathing room. Scoped to the mobile frame marker \u2014\n desktop keeps genui's own styling untouched. */\n\n [data-mobile-nav=\"frame\"] [data-genui-panel] {\n margin: 6px 12px 4px !important;\n border-color: var(--dsw-alias-border-l1, rgba(0, 0, 0, .12)) !important;\n border-radius: 12px !important;\n }\n [data-mobile-nav=\"frame\"] [data-genui-panel] [class*=\"_panelToggle\"] {\n padding: 7px 12px !important;\n gap: 8px !important;\n }\n [data-mobile-nav=\"frame\"] [data-genui-panel] [class*=\"_panelBadge\"] {\n padding: 0 7px !important;\n border-radius: 5px !important;\n font-size: 10.5px !important;\n line-height: 1.7 !important;\n }\n [data-mobile-nav=\"frame\"] [data-genui-panel] [class*=\"_panelTitle\"] {\n flex: 1 1 auto !important;\n min-width: 0 !important;\n font-size: 12.5px !important;\n line-height: 1.45 !important;\n }\n [data-mobile-nav=\"frame\"] [data-genui-panel] [class*=\"_panelChevron\"] {\n flex: none !important;\n margin-left: 0 !important;\n padding-left: 4px !important;\n }\n\n /* ---------- git-graph branch chip: inside the composer card ----------\n The branch chip (conversation.input.dock) floats between the dock rows\n and the input card; on a phone it reads as a stray capsule crowding the\n composer. A client effect (MobileNavOverlay) reparents the chip INTO\n the composer card; these rules pin it to the card's top-left and give\n the card a dedicated chip row. The card is position: relative by the\n official stylesheet, so the absolute anchor resolves against it. The\n plugin's own sheet sets all four offsets on the anchor, so right/bottom\n must be neutralized too. Scope is the frame marker + the anchor\n attribute (NOT the dock slot \u2014 the reparenting moves the chip out of\n the dock's subtree). Desktop untouched: the frame marker only exists\n below 1024px, and the effect restores the chip to the dock when the\n viewport widens. Chip row geometry (2026-08-16, user feedback): 48px\n padding left a 16px dead gap between the chip and the input line and\n made the composer read too tall; the row is now 40px = chip (24px) at\n top 12px + ~4px to the textarea \u2014 the chip sits slightly lower and\n the gap is compressed without touching the official height budget\n further. */\n\n [data-mobile-nav=\"frame\"] [data-gitgraph-chip-anchor] {\n position: absolute !important;\n top: 12px !important;\n left: 12px !important;\n right: auto !important;\n bottom: auto !important;\n z-index: 1 !important;\n }\n [data-mobile-nav=\"frame\"] [class$=\"_card\"]:has([data-gitgraph-chip-anchor]) {\n padding-top: 40px !important;\n }\n\n /* ---------- dsh-better-sidebar: safe area (S2.1, 2026-08-17) ----------\n LEGACY \u2014 this and the two better-sidebar blocks after it (toggle-cluster\n hide, phone close pill) are written against dsh-better-sidebar \u2264 0.18,\n which drew its own right panel (`_panel` / `_panelHidden`, a\n `_toggleCluster`). 0.19 retired that panel: its tabs live in DSH 0.1.5's\n native right sidebar (which the host itself takes full screen on a\n phone, with its own collapse control), and what remains plugin-owned is\n a bottom workbench (`_bottomPanel`) the phone never opens. Every\n selector below therefore matches NOTHING on 0.19+ \u2014 by design, not by\n accident (issue #11, verified live 2026-09-21); they stay for the older\n combination and are inert otherwise, like every other compat rule here.\n THIRD-PARTY COMPAT RULE \u2014 dsh-better-sidebar (the workbench the session\n header's panel button opens). Its shell is viewport-fixed and starts at\n y=0: the panel at inset 0 (100vw drawer below 768px, a right column\n above it) and the toggle cluster at top:3px. Neither knows about\n env(safe-area-inset-*), so on a notched iPhone the whole tab strip \u2014\n including the one button that CLOSES the panel \u2014 sits behind the status\n bar and cannot be tapped: the user opened the workbench and was stuck\n there (real-device report, 2026-08-17).\n Applied across the plugin's whole mobile band, not just <768px: the\n panel is fixed at top:0 in the 768-1023px range too, so the same notch\n covers the same tab strip. Zero effect wherever the inset is 0 (every\n desktop browser, every non-notched device) \u2014 desktop is >=1024px and\n out of this media block entirely.\n Anchors: the plugin's own mount marker [data-dsh-better-sidebar]\n (index.tsx) plus class-suffix selectors, per this repo's hashed-class\n convention. --dsh-title-bar-strip is the plugin's own title-bar-compat\n offset (set only while that mode is on, 0px fallback otherwise): adding\n to it keeps both offsets rather than clobbering theirs.\n !important because their :global(body[...]) rules outrank a plain\n attribute selector.\n No box-sizing here on purpose: the panel is position:fixed with BOTH\n top and bottom set, so its used height already resolves to\n \"containing block - insets - padding - border\" (CSS 2.1 10.6.4) and the\n padding shrinks the content box without any help. Forcing border-box\n also folds their 1px left border into the inline width \u2014 measured as a\n 1px panel-width change at 768px, i.e. a regression outside this\n hotfix's remit. */\n [data-dsh-better-sidebar] [class$=\"_panel\"],\n [data-dsh-better-sidebar] [class$=\"_panelHidden\"] {\n padding-top: calc(var(--mnav-sat) + var(--dsh-title-bar-strip, 0px)) !important;\n }\n /* Only the tablet/desktop range still shows the cluster (see the hide\n rule below) \u2014 the notch offset is now dead weight below 768px, so it\n is scoped out rather than left applying invisibly. min-width:768px\n rather than the more common max-width pairing: this offset was\n already a harmless no-op at >=1024px before S3.1 (--mnav-sat is 0 on\n every desktop browser), so narrowing its floor to 768px changes\n nothing there either \u2014 it only stops evaluating on phone. */\n @media (min-width: 768px) {\n [data-dsh-better-sidebar] [class$=\"_toggleCluster\"] {\n top: calc(var(--mnav-sat) + var(--dsh-title-bar-strip, 0px) + 3px) !important;\n }\n }\n\n /* ---------- dsh-better-sidebar: hide the phone toggle cluster (S3.1, 2026-08-17) ----------\n Real-device round 2 feedback: this fixed top-right cluster duplicates\n \u2014 and visually overlaps \u2014 the session header's own workbench button\n (MobileSessionHeader.tsx), which already opens/closes the same panel\n by clicking this cluster's toggle button through. Hidden below 768px\n only; the 768-1023px tablet range has no workbench button (header.css.ts\n scopes that entire reflow to <768px) and still depends on this cluster\n as its only entry point, so it stays exactly as v1.0.0/S2.1 shipped it\n there. The cluster also holds the panel's own close affordance \u2014 see\n [data-mobile-nav=\"better-sidebar-close\"] below for the phone\n replacement, wired up in MobileSessionHeader.tsx. */\n @media (max-width: 767px) {\n [data-dsh-better-sidebar] [class$=\"_toggleCluster\"] {\n display: none !important;\n }\n }\n\n /* ---------- dsh-better-sidebar: phone close button (S3.1 follow-up, 2026-08-17) ----------\n Appended to document.body by MobileSessionHeader.tsx's\n MobileHeaderUtilities effect \u2014 never inside the panel's own subtree\n (the third party's React re-renders would wipe it) and never under any\n transformed/backdrop-filter ancestor (the S4 info-card WebKit lesson in\n AGENTS.md: position:fixed re-anchors to the nearest such ancestor\n instead of the viewport). Default hidden \u2014 belt-and-braces, same\n reasoning as header.css.ts's [data-mobile-nav=\"header-info\"] etc list:\n React does not know about media queries. Shown only below 768px AND\n only while the panel is actually open: the panel's own class name ends\n in \"_panel\" exclusively in the open state (the \"_panelHidden\" suffix is\n appended once closed, so the string no longer ends in \"_panel\") \u2014 a\n pure-CSS :has() open/closed read, no MutationObserver required.\n\n Bottom-center pill, not a top-right circle (real-device follow-up,\n 2026-08-17): a top-right position collided with the panel's own\n per-tab toolbar controls \u2014 measured live at 390px with the explorer\n tab open, the panel's Refresh button sits at x:354-382 y:93-121, and a\n 44px circle at top:8px+safe-area/right:8px lands at x:338-382\n y:(safe-area+8)-(safe-area+52), a direct overlap once the safe-area\n offset is small (or zero on non-notched phones). Every per-tab toolbar\n (explorer/git/tabBar) lives at the panel's TOP; nothing in the\n default explorer or git tabs reaches the bottom 90px of the viewport\n (checked live, both tabs, 2026-08-17), so bottom-center is clear\n regardless of which tab is open \u2014 one fixed position that does not\n need per-tab-type coordinates to dodge. */\n /* Default-hidden for this pill lives in header.css.ts, NOT here: this\n whole file sits inside the shared (max-width: 1023px) block, so a rule\n here can never hide anything at desktop widths (learned the hard way,\n 2026-08-17 desktop leak). */\n @media (max-width: 767px) {\n body:has([data-dsh-better-sidebar] [class$=\"_panel\"]) [data-mobile-nav=\"better-sidebar-close\"] {\n display: flex !important;\n position: fixed;\n left: 50%;\n bottom: calc(var(--mnav-sab) + 12px);\n transform: translateX(-50%);\n z-index: 70;\n align-items: center;\n gap: 6px;\n height: 44px;\n padding: 0 18px;\n border: none;\n border-radius: 999px;\n background: var(--dsw-alias-bg-base, #fff);\n /* Drop shadow alone does not draw an edge: the fill is bg-base, which in\n dark theme is the same colour as the surface behind it, and a black\n shadow on a dark ground is invisible \u2014 the pill read as floating text\n with no boundary. The inset hairline is the edge; the drop shadow only\n lifts it. Same token the session rows use for their dark-mode ring. */\n box-shadow:\n inset 0 0 0 1px var(--dsw-alias-border-l2, rgba(0, 0, 0, .14)),\n 0 4px 16px rgba(0, 0, 0, .2);\n color: var(--dsw-alias-label-primary, inherit);\n font-family: inherit;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n body:has([data-dsh-better-sidebar] [class$=\"_panel\"]) [data-mobile-nav=\"better-sidebar-close\"] svg {\n width: 14px;\n height: 14px;\n flex: none;\n }\n body:has([data-dsh-better-sidebar] [class$=\"_panel\"]) [data-mobile-nav=\"better-sidebar-close\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n }\n\n /* ---------- dsh-agent-teams: phone floater reposition (2026-08-18) ----------\n THIRD-PARTY COMPAT RULE \u2014 @nanmicoder/dsh-agent-teams (the AgentTeams\n activity floater: a collapsed badge + expandable panel, body-portaled\n under its own mount marker div[data-agent-teams-host]). Its own CSS\n fixes both at top:56px (<=640px) viewport coordinates and never reads\n env(safe-area-inset-*), so on a notched iPhone the badge lands INSIDE\n the safe-area + this plugin's 76px session header band \u2014 overlapping\n the info and workbench buttons (real-device report, 2026-08-18).\n Move both below the header instead: safe-area + 76px header chrome\n (48px title row + 28px view-switch row, header.css.ts) + 8px gap.\n Anchored on the plugin's own mount marker + class-suffix selectors per\n this repo's hashed-class convention \u2014 when dsh-agent-teams is not\n installed the marker never exists and this whole section is inert\n (same presence-gating as the dsh-better-sidebar rules above).\n !important because their positions come from html-level CSS vars\n (--agent-teams-panel-top et al.) whose style tag order vs ours is not\n guaranteed. z-index: their 2147483000 would float the badge over every\n one of this plugin's own overlays (drawer 40, info sheet 55-57, close\n pill 70); 30 keeps it above content but below all of them. */\n @media (max-width: 767px) {\n [data-agent-teams-host] [class$=\"_badge\"],\n [data-agent-teams-host] [class$=\"_panel\"] {\n top: calc(var(--mnav-sat) + 84px) !important;\n right: 10px !important;\n z-index: 30 !important;\n }\n [data-agent-teams-host] [class$=\"_panel\"] {\n left: 10px !important;\n width: auto !important;\n /* Their min-height:min(560px, calc(100dvh - 56 - 56)) resolves against\n the OLD top var \u2014 on short phones it exceeds the space left below\n the moved-down top edge and would force overflow; content-sized with\n a hard cap replaces it. 76px bottom clearance keeps the collapsed\n composer's top edge visible under the panel. */\n min-height: 0 !important;\n max-height: calc(100dvh - var(--mnav-sat) - 84px - var(--mnav-sab) - 76px) !important;\n }\n /* The floater belongs to the SESSION page: on the home list it hovered\n over the session rows (user feedback, 2026-08-18). The phone page\n stack is this plugin's own overlay \u2014 the official conversation (and\n with it the floater's \"current session\") stays mounted underneath, so\n agent-teams keeps rendering it; hide it whenever the home level is the\n visible one (data-view flips to \"session\" inside a session, and the\n badge/panel come right back). :has() is already a documented\n requirement of this plugin (Chromium 105+, docs/interface.md). */\n body:has([data-mobile-nav=\"home\"][data-view=\"home\"]) [data-agent-teams-host] [class$=\"_badge\"],\n body:has([data-mobile-nav=\"home\"][data-view=\"home\"]) [data-agent-teams-host] [class$=\"_panel\"] {\n display: none !important;\n }\n }\n\n /* ---------- dsh-at-file compatibility ----------\n dsh-at-file registers its own `conversation.input.dock` entry\n ([data-at-file-dock]) and renders one row per `@path` token in the\n draft. That is exactly the same source this plugin's attachment chips\n read, so on a phone a single uploaded image produced TWO entries on the\n composer rail: our 48px thumbnail tile AND at-file's file-name row for\n the same token (reported 2026-08-22).\n\n Drop at-file's row for the upload directory only. Everything else the\n user @-mentions by hand is at-file's own job and stays \u2014 this is not a\n blanket hide. The path is readable from the row's own title attribute\n (FilesDock passes `title={mention.relative}`), which is a rendered\n contract rather than a hashed class name.\n\n Phone only, and deliberately so: at >= 768px our chips are hidden by\n composer.css.ts, which leaves at-file's row as the ONLY thing on screen\n representing the attachment. Hiding it there would make an uploaded file\n invisible.\n\n When every row was an upload the container itself is left empty, and an\n empty flex item still claims the rail's 6px gap \u2014 so the dock goes too\n unless at least one non-upload row survives. */\n @media (max-width: 767px) {\n [data-at-file-dock] [data-at-file-row]:has([title^=\".dsh-uploads/\"]) {\n display: none !important;\n }\n [data-at-file-dock]:not(:has([data-at-file-row] [title]:not([title^=\".dsh-uploads/\"]))) {\n display: none !important;\n }\n }\n /* ---------- @ace-zone/dsh-market: modal header fits a phone (2026-08-22) ----------\n THIRD-PARTY COMPAT RULE \u2014 @ace-zone/dsh-market (the plugin market:\n a settings-page entry that opens its own body-level modal,\n .dshm-overlay > .dshm-panel, over the settings sheet).\n\n Its header (.dshm-head) is a plain flex row with no wrapping and no\n shrinking: title, a badge strip whose slogan is white-space:nowrap,\n a version chip, an \"official site\" link, a language toggle, and only\n then the \u00D7 that closes the modal. Flex items default to\n min-width:auto, so at phone widths the row keeps its intrinsic size,\n .dshm-panel clips it (overflow:hidden), and the \u00D7 \u2014 the only way out\n of the modal on a touch screen, which has no Esc key \u2014 is off screen\n (reported 2026-08-22).\n\n Drop the three decorative items and let the title ellipsis instead.\n The language toggle stays: it is a real control, and title + toggle +\n \u00D7 fit inside 320px once the title can shrink. The title rule also\n covers the job / help overlays, which reuse .dshm-head with a long\n \"<action> \u00B7 <package>\" title and no flexible item of their own.\n Nothing here is desktop-side: at >= 768px the row has the width it\n was written for. */\n @media (max-width: 767px) {\n body:has(.dshm-ver[title=\"@ace-zone/dsh-market\"]) .dshm-head > .dshm-head-badges,\n body:has(.dshm-ver[title=\"@ace-zone/dsh-market\"]) .dshm-head > .dshm-ver,\n body:has(.dshm-ver[title=\"@ace-zone/dsh-market\"]) .dshm-head > a.dshm-viewbtn {\n display: none !important;\n }\n body:has(.dshm-ver[title=\"@ace-zone/dsh-market\"]) .dshm-head > .dshm-title {\n flex: 1 1 auto !important;\n min-width: 0 !important;\n overflow: hidden !important;\n text-overflow: ellipsis !important;\n white-space: nowrap !important;\n }\n /* 44px touch target: their 20px glyph in 2px/6px padding is ~30px. */\n body:has(.dshm-ver[title=\"@ace-zone/dsh-market\"]) .dshm-head > .dshm-close {\n flex: 0 0 auto !important;\n font-size: 24px !important;\n padding: 6px 12px !important;\n }\n }\n\n /* ---------- dsh-auto-approve compatibility ----------\n The composer.css.ts phone rules collapse the permission trigger to an\n icon-only pill, but the official permissionGlyphs table only draws\n icons for read-only / workspace-write \u2014 the auto-approve plugin's\n \"auto\" preset gets none, so its pill showed a bare chevron. The plugin\n injects its shield+sparkle svg into the MENU items itself (DOM,\n content-anchored), but the collapsed TRIGGER re-renders on every\n preset change, so a CSS pseudo-element is the survives-re-render fix\n (same reasoning as the old model Sparkle ::before above).\n\n Presence gates, honestly stated (2026-08-25 review):\n - Trigger rule: aria-label is t('input.accessMode', {name}) with the\n raw preset display name embedded untranslated and (zh/en verified)\n at the END of the string \u2014 [aria-label$=\"Auto\"] matches exactly the\n auto-approve preset being selected, and cannot match a future\n \"Autopilot\"-style preset. Plugin absent \u2192 the name never renders \u2192\n inert. This is a DIRECT gate on the plugin's own preset name.\n - Menu-item rule: INDIRECT gate only. It matches \"permission-menu item\n without an official icon span\"; the official three presets all ship\n icon spans in the menu (verified live), so with the plugin absent\n nothing matches. Known boundary: another plugin's icon-less preset,\n or a host redesign dropping icon spans, would wrongly receive this\n shield. A direct gate is not possible: menu items carry no\n id/data attributes to anchor on, CSS cannot match text, and gating\n on the plugin's own JS-injected markers would kill this fallback in\n exactly the JS-did-not-run case it exists for. Paths are the plugin's own\n shield outline + four-point sparkle (permission-glyph.ts), stroke\n shield + filled sparkle, drawn as a currentColor mask. */\n @media (max-width: 767px) {\n /* Fallback for the MENU items too (real-phone report 2026-08-25: the\n plugin's DOM injection did not run in that page instance while this\n CSS did \u2014 bundle load timing differs per plugin). Any permission-menu\n item WITHOUT the official icon span gets the shield drawn by CSS;\n today that is exactly the plugin \"auto\" preset (the official three\n all ship icons in the menu). Self-coordinating with the plugin's own\n injection: once its svg lands it arrives inside a cloned icon span,\n the :not() stops matching, no double icon. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"] button[role=\"menuitem\"]:not(:has([class*=\"_itemIcon_\"]))::before,\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] button[class$=\"_trigger\"][aria-label$=\"Auto\"]::before {\n content: '';\n width: 16px;\n height: 16px;\n flex: none;\n background: currentColor;\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8.20554 0.899994L14.7901 3.36857V7.01026C14.7901 12 11.0466 14.2103 8.20554 15.3C5.36446 14.2103 1.62012 12 1.62012 7.01026V3.36857L8.20554 0.899994Z' fill='none' stroke='black' stroke-width='1.31831' stroke-linejoin='round'/%3E%3Cpath d='M8.35 5.4C8.35 7.2 7.15 8.4 5.35 8.4C7.15 8.4 8.35 9.6 8.35 11.4C8.35 9.6 9.55 8.4 11.35 8.4C9.55 8.4 8.35 7.2 8.35 5.4Z'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8.20554 0.899994L14.7901 3.36857V7.01026C14.7901 12 11.0466 14.2103 8.20554 15.3C5.36446 14.2103 1.62012 12 1.62012 7.01026V3.36857L8.20554 0.899994Z' fill='none' stroke='black' stroke-width='1.31831' stroke-linejoin='round'/%3E%3Cpath d='M8.35 5.4C8.35 7.2 7.15 8.4 5.35 8.4C7.15 8.4 8.35 9.6 8.35 11.4C8.35 9.6 9.55 8.4 11.35 8.4C9.55 8.4 8.35 7.2 8.35 5.4Z'/%3E%3C/svg%3E\");\n -webkit-mask-size: contain;\n mask-size: contain;\n -webkit-mask-repeat: no-repeat;\n mask-repeat: no-repeat;\n }\n }\n\n /* ---------- dsh-vision-router: composer vision toggle (2026-08-26) ----------\n THIRD-PARTY COMPAT RULE \u2014 dsh-vision-router registers a Vision mode\n toggle into conversation.input.right (slot id\n \"vision-router-mode-toggle\"): a bordered pill sized by INLINE styles \u2014\n minHeight 28, padding 4px 8px, fontSize 12, white-space nowrap \u2014\n roughly 90px of unshrinkable width.\n\n Its children changed shape in 2.1.x (re-measured on the real UI\n 2026-09-04, plugin 2.1.1). Both forms are handled:\n - text form (<= 2.0.x): <span>\uD83D\uDC41</span><span>label</span> plus a\n <span>\u2713</span> while active;\n - svg form (2.1.x, lib/client-presentation-boundary.js): a 14px eye\n <svg>, <span>label</span>, plus a SECOND 12px check <svg> while\n active.\n The 2.1.1 update is what the user hit: the label span was still hidden\n but the new eye svg was not, so it rendered NEXT TO our ::before eye \u2014\n two eyes in a 28px button \u2014 and the active check svg had nothing hiding\n it at all, eating width the phone row does not have.\n\n The phone composer row is nowrap by design (composer.css.ts section 1)\n and the model seat is its only shrinkable item, so that pill squeezes\n the model name out of the row (user report, 2026-08-26).\n\n Phone treatment: a 28x28 round icon button in the attach button's\n language (same size, same neutral fill), showing exactly ONE 16px eye\n and nothing else. Every span is hidden (label, and the text form's\n emoji and check); of the svg form's icons only the first survives, the\n trailing check svg is hidden by a sibling combinator so the count is\n never assumed. Our own ::before eye is now a FALLBACK, gated on\n :has(svg) so it draws only for the text form \u2014 the plugin ships its own\n icon from 2.1.x on, and drawing over it is what produced the double\n eye. Generated content rather than a DOM-injected icon, for the same\n survives-React-re-render reason as the auto-approve shield below: the\n button re-renders on every state flip, so an injected node would need\n an observer while ::before does not. The\n accessible name is unaffected: the button ships aria-label + title with\n the full enable/disable copy, and aria-pressed keeps conveying the\n state; visually the plugin's own inline styles still flip border +\n background + color to the brand token while active (only the inactive\n look is restyled to match the row).\n\n Presence gate: [data-vision-router-mode-toggle] is rendered by the\n plugin itself (the exact attribute from its createElement call) \u2014 with\n the plugin absent the attribute never exists and this whole section is\n inert, not one selector matching (same convention as the\n dsh-better-sidebar rules). !important throughout because the geometry\n it replaces is inline style. Phone only (max-width: 767px): desktop\n keeps the plugin's full pill untouched, and the 768-1023px tablet row\n has the width for it. order:5 restates composer.css.ts's generic\n input.right rule with the same value \u2014 kept here so the toggle can\n never fall back to order 0 (far left, before the model) if that\n structural selector ever stops matching. */\n @media (max-width: 767px) {\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle] {\n order: 5 !important;\n flex: 0 0 auto !important;\n box-sizing: border-box !important;\n width: 28px !important;\n min-width: 28px !important;\n max-width: 28px !important;\n height: 28px !important;\n min-height: 28px !important;\n padding: 0 !important;\n gap: 0 !important;\n justify-content: center !important;\n border-radius: 999px !important;\n touch-action: manipulation !important;\n -webkit-tap-highlight-color: transparent;\n }\n /* Inactive look: neutral round fill like the attach button \u2014 the\n plugin's own inactive state would draw a lonely outline circle that\n reads as broken next to the row's filled icon buttons. */\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle]:not([aria-pressed=\"true\"]) {\n border-color: transparent !important;\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;\n box-shadow: none !important;\n }\n /* Text form: emoji + label + active check are all spans \u2014 all out. */\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle] > span {\n display: none !important;\n }\n /* Svg form (2.1.x): the first svg IS the eye, so keep it and match the\n row's other icons at 16px. */\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle] > svg:first-of-type {\n width: 16px !important;\n height: 16px !important;\n }\n /* ...and drop every svg after it \u2014 today that is the active-state check\n mark. A sibling combinator rather than :nth-child(2): the count is the\n plugin's business, this only says \"one icon, the first one\". */\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle] > svg ~ svg {\n display: none !important;\n }\n /* The one svg: eye outline + iris as a currentColor mask \u2014 brand while\n active, dimmed with the button by the plugin's own disabled opacity. */\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle]:has(svg)::before {\n content: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle]::before {\n content: '';\n width: 16px;\n height: 16px;\n flex: none;\n background: currentColor;\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M1.5 8C3 5.2 5.3 3.5 8 3.5s5 1.7 6.5 4.5c-1.5 2.8-3.8 4.5-6.5 4.5S3 10.8 1.5 8Z' fill='none' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3Ccircle cx='8' cy='8' r='2'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M1.5 8C3 5.2 5.3 3.5 8 3.5s5 1.7 6.5 4.5c-1.5 2.8-3.8 4.5-6.5 4.5S3 10.8 1.5 8Z' fill='none' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3Ccircle cx='8' cy='8' r='2'/%3E%3C/svg%3E\");\n -webkit-mask-size: contain;\n mask-size: contain;\n -webkit-mask-repeat: no-repeat;\n mask-repeat: no-repeat;\n }\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle]:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);\n outline-offset: 2px;\n }\n }\n";
1
+ export declare const COMPAT_CSS = " /* ---------- dsh-web-ui family compatibility ----------\n The linxin666 plugin suite extends the shell frame directly:\n - aionui-panel appends two trailing grid columns (explorer / preview)\n plus absolute drag handles to [data-dsh-frame]; its 5-track inline\n grid is already overridden above, but the handles and columns would\n still float over the main UI. On mobile the columns leave the grid\n as floating bottom sheets and keep their own visibility state \u2014\n the suite's collapse chevron / preview tabs still work, so no\n feature is lost. The task-board / ssh plugins inject sidebar\n entries and center-column takeover panels; the entries need\n spacing and the kanban needs scrollable columns. */\n\n /* Touch devices: the drag handles are useless \u2014 the floating expand\n button is the opener. */\n .aionui-explorer-handle,\n .aionui-preview-handle {\n display: none !important;\n }\n\n /* Shared base: both columns leave the grid as floating panels. The\n explorer is gated shut by default (its own persisted expanded state\n must never cover the mobile UI on load); the header Files action opens\n it via the frame marker below, and the sheet's own collapse chevron\n clears it. Preview stays owned by the suite (hidden while no tab is\n open). The per-column rules below override the geometry. */\n [data-aionui-explorer-col],\n [data-aionui-preview-col] {\n position: fixed !important;\n z-index: 55 !important;\n background: var(--aion-bg-base, #ffffff) !important;\n border-left: none !important;\n }\n /* Explorer (file tree) bottom sheet: bottom edge aligned exactly with\n the composer card's bottom line \u2014 the card sits 36px above the\n viewport bottom (8px composer padding + the 28px stats strip below\n the card), so the sheet uses the same 36px bottom offset. */\n [data-aionui-explorer-col] {\n visibility: hidden !important;\n left: 8px !important;\n right: 8px !important;\n top: auto !important;\n bottom: 36px !important;\n width: auto !important;\n height: min(55dvh, 460px) !important;\n max-height: calc(100dvh - 44px) !important;\n border-radius: 14px !important;\n overflow: hidden !important;\n box-shadow: 0 -4px 28px rgba(0, 0, 0, .18) !important;\n animation: dsh-mobile-nav-sheet-up .24s var(--ds-ease-out, ease-in-out) !important;\n }\n /* Preview (file content) bottom sheet. Gated shut by default: the suite\n persists open preview tabs in localStorage and restores them on load,\n which would pop the sheet over the fresh UI. The client only sets the\n frame marker after the user taps a file row in the explorer; the\n suite's own collapse chevron clears it via the visibility watcher. */\n [data-aionui-preview-col] {\n visibility: hidden !important;\n position: fixed !important;\n left: 8px !important;\n right: 8px !important;\n top: auto !important;\n bottom: 40px !important;\n width: auto !important;\n height: min(50dvh, 420px) !important;\n max-height: calc(100dvh - 48px) !important;\n border-radius: 14px !important;\n overflow: hidden !important;\n box-shadow: 0 -4px 28px rgba(0, 0, 0, .18) !important;\n z-index: 56 !important;\n animation: dsh-mobile-nav-sheet-up .24s var(--ds-ease-out, ease-in-out) !important;\n /* Fullscreen toggle (issue #8): animate the geometry change instead of\n snapping. visibility is deliberately not listed, so opening/closing\n the sheet stays instant; the open/close keyframes own transform. */\n transition:\n left .24s var(--ds-ease-out, ease-in-out),\n right .24s var(--ds-ease-out, ease-in-out),\n top .24s var(--ds-ease-out, ease-in-out),\n bottom .24s var(--ds-ease-out, ease-in-out),\n width .24s var(--ds-ease-out, ease-in-out),\n height .24s var(--ds-ease-out, ease-in-out),\n border-radius .24s var(--ds-ease-out, ease-in-out),\n box-shadow .24s var(--ds-ease-out, ease-in-out),\n padding-top .24s var(--ds-ease-out, ease-in-out) !important;\n }\n /* User-opened preview sheet (frame marker, set on file-row tap). */\n [data-mobile-nav=\"frame\"][data-aionui-preview-open] [data-aionui-preview-col] {\n visibility: visible !important;\n }\n /* The Files action opens the explorer sheet (frame marker). */\n [data-mobile-nav=\"frame\"][data-aionui-explorer-open] [data-aionui-explorer-col] {\n visibility: visible !important;\n }\n /* While the preview sheet is up, the explorer sheet yields (two stacked\n bottom sheets would read as one broken overlay). Closing the preview\n via its collapse chevron / tab close clears the marker, and the\n explorer sheet returns. Same specificity as the explorer-open rule, so\n this must stay AFTER it. */\n [data-mobile-nav=\"frame\"][data-aionui-preview-open] [data-aionui-explorer-col] {\n visibility: hidden !important;\n }\n /* The open drawer must never sit under a sheet: while the frame is in the\n narrow-expanded state both sheets yield (later in the file than the\n open marker rule, so it wins at equal specificity). The fullscreen\n toggle has its own drawer-open rule at the end of its section. */\n [data-mobile-nav=\"frame\"]:not([data-sidebar-collapsed]) [data-aionui-explorer-col],\n [data-mobile-nav=\"frame\"]:not([data-sidebar-collapsed]) [data-aionui-preview-col] {\n visibility: hidden !important;\n display: none !important;\n }\n /* The suite's own expand button reads the store state we bypass on\n mobile \u2014 hide it; the header Files action is the opener. */\n .aionui-floating-expand {\n display: none !important;\n }\n\n /* Preview sheet fullscreen toggle (issue #8): a fixed button parked in the\n sheet's titlebar row, just left of the suite's collapse chevron (24px at\n right:8px of the sheet, and the sheet spans 8px..(100vw-8px)). The top\n calc mirrors the sheet geometry above (bottom 40px + min(50dvh, 420px));\n when the frame carries \"data-mobile-preview-full\" the sheet goes\n fullscreen and the button moves to the viewport corner. */\n [data-mobile-nav=\"preview-full-toggle\"] {\n position: absolute !important;\n right: 36px !important;\n top: 8px !important;\n z-index: 57 !important;\n display: none !important;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--aion-text-secondary, var(--dsw-alias-label-secondary, inherit));\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n /* Native look: same size/radius/hover language as the suite's tab-bar\n icon buttons (the 20px panelCollapse next to it). The button lives\n INSIDE the preview column, so it rides the sheet's own open\n animation and geometry transition \u2014 no curve matching needed. */\n transition: background-color .15s, top .24s var(--ds-ease-out, ease-in-out);\n }\n [data-mobile-nav=\"preview-full-toggle\"]:hover {\n background: var(--aion-bg-3, rgba(0, 0, 0, .22));\n }\n [data-mobile-nav=\"preview-full-toggle\"]:active {\n background: var(--aion-bg-active, rgba(0, 0, 0, .28));\n }\n [data-mobile-nav=\"preview-full-toggle\"]:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);\n outline-offset: 2px;\n }\n [data-mobile-nav=\"preview-full-toggle\"] svg {\n width: 14px;\n height: 14px;\n }\n /* Keep the last tab (and the \"+\" URL-tab trigger) from sliding under the\n fullscreen toggle: reserve the right end of the preview tab row. */\n [data-aionui-preview-col] [class$=\"_tabScroll\"] {\n padding-right: 34px !important;\n }\n /* Visible only while the preview sheet is open. Visibility itself is\n inherited from the column, so the sheet's own hide rules (collapse,\n drawer open) cover the button too. */\n [data-mobile-nav=\"frame\"][data-aionui-preview-open] [data-aionui-preview-col] [data-mobile-nav=\"preview-full-toggle\"] {\n display: inline-flex !important;\n }\n /* Icon swap on the frame fullscreen marker. */\n [data-mobile-nav=\"preview-full-toggle\"] .dsh-mobile-nav-full-out {\n display: none !important;\n }\n [data-mobile-nav=\"frame\"][data-mobile-preview-full] [data-aionui-preview-col] [data-mobile-nav=\"preview-full-toggle\"] .dsh-mobile-nav-full-in {\n display: none !important;\n }\n [data-mobile-nav=\"frame\"][data-mobile-preview-full] [data-aionui-preview-col] [data-mobile-nav=\"preview-full-toggle\"] .dsh-mobile-nav-full-out {\n display: inline !important;\n }\n /* Fullscreen preview: the sheet fills the whole viewport (notch included);\n the safe-area padding drops the titlebar row below the status bar, and\n the toggle follows the titlebar into the top corner. */\n [data-mobile-nav=\"frame\"][data-aionui-preview-open][data-mobile-preview-full] [data-aionui-preview-col] {\n inset: 0 !important;\n left: 0 !important;\n right: 0 !important;\n top: 0 !important;\n bottom: 0 !important;\n width: 100% !important;\n height: 100dvh !important;\n max-height: none !important;\n box-sizing: border-box !important;\n padding-top: var(--mnav-sat) !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n z-index: 57 !important;\n animation: none !important;\n }\n /* Fullscreen: the column fills the viewport, so the button follows the\n titlebar row down below the notch. */\n [data-mobile-nav=\"frame\"][data-mobile-preview-full] [data-aionui-preview-col] [data-mobile-nav=\"preview-full-toggle\"] {\n top: calc(var(--mnav-sat) + 8px) !important;\n }\n @media (prefers-reduced-motion: reduce) {\n [data-aionui-preview-col],\n [data-mobile-nav=\"preview-full-toggle\"] {\n transition: none !important;\n animation: none !important;\n }\n }\n\n /* dsh-web-ui sidebar entries (task board / ssh) sit flush against each\n other \u2014 give the injected rows breathing room. */\n button[data-dsh-taskboard-entry],\n button[data-dsh-ssh-entry] {\n margin-bottom: 8px !important;\n }\n\n /* Task board: five kanban columns at minmax(0,1fr) crush into ~78px phone\n strips. Give every column a usable minimum and let the row scroll. */\n [data-dsh-taskboard-board] > [class$=\"_columns\"] {\n grid-template-columns: repeat(5, minmax(240px, 1fr)) !important;\n overflow-x: auto !important;\n }\n /* The floating button must not float over a takeover panel (task board /\n ssh own the center column while active). */\n html[data-dsh-taskboard-active] [data-mobile-nav=\"fab\"],\n html[data-dsh-ssh-active] [data-mobile-nav=\"fab\"],\n html[data-dsh-taskboard-active] [data-mobile-nav=\"backdrop\"],\n html[data-dsh-ssh-active] [data-mobile-nav=\"backdrop\"] {\n display: none !important;\n }\n /* Board header: let the search field take the slack instead of squeezing\n the action buttons. */\n [data-dsh-taskboard-board] > [class$=\"_boardHeader\"] [class$=\"_search\"] {\n flex: 1 1 auto !important;\n min-width: 80px !important;\n }\n\n /* ---------- dsh-web-ui polish: plugin market search ----------\n The market tab row (Discover / Themes / Installed + the plugin search\n box) is a no-wrap flex: at 390px the tabs plus the ~218px search box\n (~475px total) overflow the ~334px sheet and the search box runs off\n the right edge of the screen (it also forces a horizontal scrollbar on\n the sheet's options area). Let the row wrap: the tabs keep the first\n line and the search box gets its own full-width second line. */\n\n [aria-modal=\"true\"] [class$=\"_tabs\"] {\n flex-wrap: wrap !important;\n row-gap: 8px !important;\n }\n [aria-modal=\"true\"] [class$=\"_searchInline\"] {\n flex: 1 1 100% !important;\n width: 100% !important;\n max-width: 100% !important;\n }\n\n /* ---------- dsh-usage-stats polish: usage & balance panel ----------\n The panel's stats row shows three token counters side by side\n (today / month / total). The counters use tabular nowrap figures whose\n min-content width overflows the ~336px panel body on a phone: figures\n clip at the row's edges and the panel grows a horizontal scrollbar.\n Stack the three counters vertically \u2014 full-width rows, so the figures\n always fit. */\n\n [class*=\"usg_\"][class$=\"_statsRow\"] {\n flex-direction: column !important;\n }\n [class*=\"usg_\"][class$=\"_stat\"] {\n flex: 0 0 auto !important;\n width: 100% !important;\n min-width: 0 !important;\n }\n\n /* ---------- dsh-web-ui polish: settings sheet ----------\n The official dialog is a desktop two-column form; on a phone the\n label/control split leaves a huge dead gap and long descriptions wrap\n into tall stacks. Stack each row (text above, control full-width) and\n keep the nav tabs on ONE horizontally scrolling row. */\n\n /* Nav tabs: single scrolling row instead of the 3-per-row grid \u2014 seven\n categories wrap into three rows on a phone (~130px of sheet height);\n one row with a thin scrollbar keeps every tab reachable and returns\n that space to the options area (user feedback 2026-08-16). An earlier\n one-row attempt had no scroll affordance and silently cut the last\n tab off; the thin scrollbar IS the affordance. Scoped to the frame\n marker: the desktop dialog keeps its official vertical nav column. */\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"]:has(> :first-child > :last-child > button):not(:has([role=\"navigation\"])):not(:has([class*=\"ZuhsRW\"])) > :first-child [class$=\"_navList\"] {\n display: flex !important;\n flex-wrap: nowrap !important;\n overflow-x: auto !important;\n overflow-y: hidden !important;\n gap: 6px !important;\n width: 100% !important;\n scrollbar-width: thin !important;\n -webkit-overflow-scrolling: touch !important;\n }\n /* Hairline scrollbar for the tab row: the default WebKit scrollbar reads\n fat on a phone; 2px keeps the scroll affordance without the bulk. */\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navList\"]::-webkit-scrollbar {\n height: 2px !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navList\"]::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-border-l2, rgba(0, 0, 0, .22)) !important;\n border-radius: 1px !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navList\"]::-webkit-scrollbar-track {\n background: transparent !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navCell\"] {\n flex: 0 0 auto !important;\n white-space: nowrap !important;\n padding: 6px 8px !important;\n gap: 6px !important;\n font-size: 13px !important;\n justify-content: flex-start !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_navCell\"] svg {\n width: 14px !important;\n height: 14px !important;\n flex: none !important;\n }\n /* Content toolbar: the \"Open configuration file\" button is hidden on\n mobile \u2014 it is rarely needed on a phone and steals ~180px from the\n tab row's scroll area (user feedback 2026-08-16). Only the close \u2715\n stays, flush right in the nav row. Desktop untouched (frame scoped). */\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_header\"] [class$=\"_actions\"] {\n display: none !important;\n }\n [data-mobile-nav=\"frame\"] [aria-modal=\"true\"] [class$=\"_header\"] [class$=\"_actions\"] [class$=\"_action\"] {\n font-size: 13px !important;\n padding: 6px 12px !important;\n min-height: 0 !important;\n }\n /* Setting rows: text on top, control below at full width. */\n [aria-modal=\"true\"] [class$=\"_section\"] [class$=\"_row\"] {\n flex-direction: column !important;\n align-items: stretch !important;\n gap: 8px !important;\n }\n [aria-modal=\"true\"] [class$=\"_section\"] [class$=\"_row\"] > :first-child {\n width: 100% !important;\n max-width: none !important;\n }\n [aria-modal=\"true\"] [class$=\"_section\"] [class$=\"_row\"] > :last-child {\n width: 100% !important;\n max-width: none !important;\n }\n /* Appearance mode group: give the cube row a consistent bordered\n segmented look (the official borders differ per state). */\n [aria-modal=\"true\"] [class$=\"_cubeRow\"] > * {\n border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12)) !important;\n }\n\n /* ---------- dsh-web-ui polish: explorer sheet ----------\n The aionui explorer was designed for a desktop side column: compact the\n header, search box and tree rows so a phone shows more entries, and pad\n the scroll bottom so the last row never sits flush on the edge. */\n\n [data-aionui-explorer-col] [class$=\"_tabBar\"] {\n height: 36px !important;\n }\n [data-aionui-explorer-col] [class$=\"_tabBtn\"],\n [data-aionui-explorer-col] [class$=\"_tabBtnActive\"] {\n padding: 0 12px !important;\n font-size: 13px !important;\n }\n [data-aionui-explorer-col] [class$=\"_searchBox\"] {\n height: 32px !important;\n font-size: 13px !important;\n }\n [data-aionui-explorer-col] [class*=\"_treeRow\"] {\n height: 30px !important;\n font-size: 13px !important;\n }\n [data-aionui-explorer-col] [class*=\"_treeRow\"] svg {\n width: 14px !important;\n height: 14px !important;\n }\n [data-aionui-explorer-col] [class$=\"_scrollArea\"] {\n padding-bottom: 28px !important;\n }\n\n /* ---------- dsh-web-ui polish: drawer footer ----------\n The injected footer actions (Files + Session log) become two equal pill\n buttons instead of text-width capsules. */\n\n /* The official footerActions row also hosts the remote-web-ui entry\n row (two icon buttons); without wrapping the two groups squeeze each\n other on one line. Wrap so each group gets its own full-width row. */\n [data-mobile-nav=\"frame\"] [class$=\"_footerActions\"] {\n flex-wrap: wrap !important;\n gap: 6px !important;\n }\n [data-mobile-nav=\"drawer-actions\"] {\n width: 100% !important;\n }\n [data-mobile-nav=\"drawer-actions\"] > button {\n flex: 1 1 0 !important;\n padding: 0 8px !important;\n white-space: nowrap !important;\n }\n\n /* ---------- dsh-web-ui polish: floating pet ----------\n The whale-girl pet (dsh-pet) floats at the viewport corner with a\n persisted, draggable position. On phones the pet is scaled down so\n it does not dominate the screen; the plugin's own drag + persist\n still work (the position itself is left alone \u2014 the mobile default\n position is seeded via the pet API to just above the composer). */\n\n body > [class$=\"_float\"]:has([class$=\"_sprite\"][role=\"button\"]) {\n transform: scale(.66);\n transform-origin: bottom right;\n }\n /* While a modal dialog (settings sheet / export) owns the screen the pet\n floats ABOVE it and covers the dialog content; modal semantics say the\n background is inert, so hide the pet for the modal's lifetime. */\n body:has([aria-modal=\"true\"]) > [class$=\"_float\"]:has([class$=\"_sprite\"][role=\"button\"]) {\n display: none !important;\n }\n\n /* ---------- conversation stats line ----------\n The official session-status row (turns / steps / LLM time / TTFT /\n cache) is long. It is the single entry the official StatsLine puts in\n conversation.composer.dock, so the structural anchor below reaches it\n without any DOM marking (S3 deleted the text-matching effect that used\n to set the dock-entry marker). Layout: ONE fixed-height (28px) flex\n strip that scrolls horizontally \u2014 the full metrics stream stays\n reachable by swiping, the row never grows vertically, no ellipsis or\n fade, 12px gaps between metric groups, a 2px scrollbar as the swipe\n affordance. The phone breakpoint hides the strip outright instead\n (styles/composer.css.ts \u2014 its data moves into the session info card). */\n\n /* Marker set is deliberately dual: 0.2.0-rc.2 renders the strip root with\n data-composer-stats (plural, dsh-client-ui-chat rc.2 lib), 0.2.1-alpha.2\n dropped the strip wrapper and registers activity/usage as separate dock\n entries whose pill spans carry data-composer-stat (singular). Keeping\n both lets this plugin style the strip on a host that has NOT upgraded to\n alpha.2 \u2014 verified against both npm tarballs 2026-10-09. */\n [data-slot=\"conversation.composer.dock\"] > :is([data-composer-stat], [data-composer-stats]) {\n display: flex !important;\n flex-flow: row nowrap !important;\n align-items: center !important;\n width: 100% !important;\n max-width: 100% !important;\n min-width: 0 !important;\n height: 28px !important;\n min-height: 28px !important;\n max-height: 28px !important;\n box-sizing: border-box !important;\n white-space: nowrap !important;\n overflow-x: auto !important;\n overflow-y: hidden !important;\n -webkit-overflow-scrolling: touch;\n overscroll-behavior-x: contain;\n scrollbar-width: thin !important;\n scrollbar-color: var(--dsw-alias-border-l1, rgba(0, 0, 0, .28)) transparent !important;\n padding: 0 0 4px !important;\n line-height: 20px !important;\n font-size: 12px !important;\n }\n [data-slot=\"conversation.composer.dock\"] > :is([data-composer-stat], [data-composer-stats])::-webkit-scrollbar {\n height: 2px !important;\n }\n [data-slot=\"conversation.composer.dock\"] > :is([data-composer-stat], [data-composer-stats])::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .3)) !important;\n border-radius: 2px !important;\n }\n [data-slot=\"conversation.composer.dock\"] > :is([data-composer-stat], [data-composer-stats])::-webkit-scrollbar-track {\n background: transparent !important;\n }\n [data-slot=\"conversation.composer.dock\"] > :is([data-composer-stat], [data-composer-stats]) > * {\n display: flex !important;\n flex: 0 0 auto !important;\n flex-flow: row nowrap !important;\n align-items: center !important;\n width: max-content !important;\n min-width: max-content !important;\n max-width: none !important;\n white-space: nowrap !important;\n margin-right: 12px !important;\n padding: 0 !important;\n }\n [data-slot=\"conversation.composer.dock\"] > :is([data-composer-stat], [data-composer-stats]) > *:last-child {\n margin-right: 0 !important;\n }\n [data-slot=\"conversation.composer.dock\"] > :is([data-composer-stat], [data-composer-stats]) * {\n white-space: nowrap !important;\n }\n\n /* ---------- dsh-genui panel dock ----------\n The genui panel docks above the composer (conversation.input.dock,\n id genui-panel). On a phone its business-blue outline, generous chrome\n and single-line ellipsis read as an unfinished artifact: long titles\n truncate mid-word (\"\u2026default b\u00B7\u00B7\u00B7\") with the chevron glued to the\n ellipsis, and the pill crowds the composer. Mobile treatment: neutral\n card border matching the composer, tighter chrome so the full title\n fits, chevron with breathing room. Scoped to the mobile frame marker \u2014\n desktop keeps genui's own styling untouched. */\n\n [data-mobile-nav=\"frame\"] [data-genui-panel] {\n margin: 6px 12px 4px !important;\n border-color: var(--dsw-alias-border-l1, rgba(0, 0, 0, .12)) !important;\n border-radius: 12px !important;\n }\n [data-mobile-nav=\"frame\"] [data-genui-panel] [class*=\"_panelToggle\"] {\n padding: 7px 12px !important;\n gap: 8px !important;\n }\n [data-mobile-nav=\"frame\"] [data-genui-panel] [class*=\"_panelBadge\"] {\n padding: 0 7px !important;\n border-radius: 5px !important;\n font-size: 10.5px !important;\n line-height: 1.7 !important;\n }\n [data-mobile-nav=\"frame\"] [data-genui-panel] [class*=\"_panelTitle\"] {\n flex: 1 1 auto !important;\n min-width: 0 !important;\n font-size: 12.5px !important;\n line-height: 1.45 !important;\n }\n [data-mobile-nav=\"frame\"] [data-genui-panel] [class*=\"_panelChevron\"] {\n flex: none !important;\n margin-left: 0 !important;\n padding-left: 4px !important;\n }\n\n /* ---------- git-graph branch chip: inside the composer card ----------\n The branch chip (conversation.input.dock) floats between the dock rows\n and the input card; on a phone it reads as a stray capsule crowding the\n composer. A client effect (MobileNavOverlay) reparents the chip INTO\n the composer card; these rules pin it to the card's top-left and give\n the card a dedicated chip row. The card is position: relative by the\n official stylesheet, so the absolute anchor resolves against it. The\n plugin's own sheet sets all four offsets on the anchor, so right/bottom\n must be neutralized too. Scope is the frame marker + the anchor\n attribute (NOT the dock slot \u2014 the reparenting moves the chip out of\n the dock's subtree). Desktop untouched: the frame marker only exists\n below 1024px, and the effect restores the chip to the dock when the\n viewport widens. Chip row geometry (2026-08-16, user feedback): 48px\n padding left a 16px dead gap between the chip and the input line and\n made the composer read too tall; the row is now 40px = chip (24px) at\n top 12px + ~4px to the textarea \u2014 the chip sits slightly lower and\n the gap is compressed without touching the official height budget\n further. */\n\n [data-mobile-nav=\"frame\"] [data-gitgraph-chip-anchor] {\n position: absolute !important;\n top: 12px !important;\n left: 12px !important;\n right: auto !important;\n bottom: auto !important;\n z-index: 1 !important;\n }\n [data-mobile-nav=\"frame\"] [class$=\"_card\"]:has([data-gitgraph-chip-anchor]) {\n padding-top: 40px !important;\n }\n\n /* ---------- dsh-better-sidebar: safe area (S2.1, 2026-08-17) ----------\n LEGACY \u2014 this and the two better-sidebar blocks after it (toggle-cluster\n hide, phone close pill) are written against dsh-better-sidebar \u2264 0.18,\n which drew its own right panel (`_panel` / `_panelHidden`, a\n `_toggleCluster`). 0.19 retired that panel: its tabs live in DSH 0.1.5's\n native right sidebar (which the host itself takes full screen on a\n phone, with its own collapse control), and what remains plugin-owned is\n a bottom workbench (`_bottomPanel`) the phone never opens. Every\n selector below therefore matches NOTHING on 0.19+ \u2014 by design, not by\n accident (issue #11, verified live 2026-09-21); they stay for the older\n combination and are inert otherwise, like every other compat rule here.\n THIRD-PARTY COMPAT RULE \u2014 dsh-better-sidebar (the workbench the session\n header's panel button opens). Its shell is viewport-fixed and starts at\n y=0: the panel at inset 0 (100vw drawer below 768px, a right column\n above it) and the toggle cluster at top:3px. Neither knows about\n env(safe-area-inset-*), so on a notched iPhone the whole tab strip \u2014\n including the one button that CLOSES the panel \u2014 sits behind the status\n bar and cannot be tapped: the user opened the workbench and was stuck\n there (real-device report, 2026-08-17).\n Applied across the plugin's whole mobile band, not just <768px: the\n panel is fixed at top:0 in the 768-1023px range too, so the same notch\n covers the same tab strip. Zero effect wherever the inset is 0 (every\n desktop browser, every non-notched device) \u2014 desktop is >=1024px and\n out of this media block entirely.\n Anchors: the plugin's own mount marker [data-dsh-better-sidebar]\n (index.tsx) plus class-suffix selectors, per this repo's hashed-class\n convention. --dsh-title-bar-strip is the plugin's own title-bar-compat\n offset (set only while that mode is on, 0px fallback otherwise): adding\n to it keeps both offsets rather than clobbering theirs.\n !important because their :global(body[...]) rules outrank a plain\n attribute selector.\n No box-sizing here on purpose: the panel is position:fixed with BOTH\n top and bottom set, so its used height already resolves to\n \"containing block - insets - padding - border\" (CSS 2.1 10.6.4) and the\n padding shrinks the content box without any help. Forcing border-box\n also folds their 1px left border into the inline width \u2014 measured as a\n 1px panel-width change at 768px, i.e. a regression outside this\n hotfix's remit. */\n [data-dsh-better-sidebar] [class$=\"_panel\"],\n [data-dsh-better-sidebar] [class$=\"_panelHidden\"] {\n padding-top: calc(var(--mnav-sat) + var(--dsh-title-bar-strip, 0px)) !important;\n }\n /* Only the tablet/desktop range still shows the cluster (see the hide\n rule below) \u2014 the notch offset is now dead weight below 768px, so it\n is scoped out rather than left applying invisibly. min-width:768px\n rather than the more common max-width pairing: this offset was\n already a harmless no-op at >=1024px before S3.1 (--mnav-sat is 0 on\n every desktop browser), so narrowing its floor to 768px changes\n nothing there either \u2014 it only stops evaluating on phone. */\n @media (min-width: 768px) {\n [data-dsh-better-sidebar] [class$=\"_toggleCluster\"] {\n top: calc(var(--mnav-sat) + var(--dsh-title-bar-strip, 0px) + 3px) !important;\n }\n }\n\n /* ---------- dsh-better-sidebar: hide the phone toggle cluster (S3.1, 2026-08-17) ----------\n Real-device round 2 feedback: this fixed top-right cluster duplicates\n \u2014 and visually overlaps \u2014 the session header's own workbench button\n (MobileSessionHeader.tsx), which already opens/closes the same panel\n by clicking this cluster's toggle button through. Hidden below 768px\n only; the 768-1023px tablet range has no workbench button (header.css.ts\n scopes that entire reflow to <768px) and still depends on this cluster\n as its only entry point, so it stays exactly as v1.0.0/S2.1 shipped it\n there. The cluster also holds the panel's own close affordance \u2014 see\n [data-mobile-nav=\"better-sidebar-close\"] below for the phone\n replacement, wired up in MobileSessionHeader.tsx. */\n @media (max-width: 767px) {\n [data-dsh-better-sidebar] [class$=\"_toggleCluster\"] {\n display: none !important;\n }\n }\n\n /* ---------- dsh-better-sidebar: phone close button (S3.1 follow-up, 2026-08-17) ----------\n Appended to document.body by MobileSessionHeader.tsx's\n MobileHeaderUtilities effect \u2014 never inside the panel's own subtree\n (the third party's React re-renders would wipe it) and never under any\n transformed/backdrop-filter ancestor (the S4 info-card WebKit lesson in\n AGENTS.md: position:fixed re-anchors to the nearest such ancestor\n instead of the viewport). Default hidden \u2014 belt-and-braces, same\n reasoning as header.css.ts's [data-mobile-nav=\"header-info\"] etc list:\n React does not know about media queries. Shown only below 768px AND\n only while the panel is actually open: the panel's own class name ends\n in \"_panel\" exclusively in the open state (the \"_panelHidden\" suffix is\n appended once closed, so the string no longer ends in \"_panel\") \u2014 a\n pure-CSS :has() open/closed read, no MutationObserver required.\n\n Bottom-center pill, not a top-right circle (real-device follow-up,\n 2026-08-17): a top-right position collided with the panel's own\n per-tab toolbar controls \u2014 measured live at 390px with the explorer\n tab open, the panel's Refresh button sits at x:354-382 y:93-121, and a\n 44px circle at top:8px+safe-area/right:8px lands at x:338-382\n y:(safe-area+8)-(safe-area+52), a direct overlap once the safe-area\n offset is small (or zero on non-notched phones). Every per-tab toolbar\n (explorer/git/tabBar) lives at the panel's TOP; nothing in the\n default explorer or git tabs reaches the bottom 90px of the viewport\n (checked live, both tabs, 2026-08-17), so bottom-center is clear\n regardless of which tab is open \u2014 one fixed position that does not\n need per-tab-type coordinates to dodge. */\n /* Default-hidden for this pill lives in header.css.ts, NOT here: this\n whole file sits inside the shared (max-width: 1023px) block, so a rule\n here can never hide anything at desktop widths (learned the hard way,\n 2026-08-17 desktop leak). */\n @media (max-width: 767px) {\n body:has([data-dsh-better-sidebar] [class$=\"_panel\"]) [data-mobile-nav=\"better-sidebar-close\"] {\n display: flex !important;\n position: fixed;\n left: 50%;\n bottom: calc(var(--mnav-sab) + 12px);\n transform: translateX(-50%);\n z-index: 70;\n align-items: center;\n gap: 6px;\n height: 44px;\n padding: 0 18px;\n border: none;\n border-radius: 999px;\n background: var(--dsw-alias-bg-base, #fff);\n /* Drop shadow alone does not draw an edge: the fill is bg-base, which in\n dark theme is the same colour as the surface behind it, and a black\n shadow on a dark ground is invisible \u2014 the pill read as floating text\n with no boundary. The inset hairline is the edge; the drop shadow only\n lifts it. Same token the session rows use for their dark-mode ring. */\n box-shadow:\n inset 0 0 0 1px var(--dsw-alias-border-l2, rgba(0, 0, 0, .14)),\n 0 4px 16px rgba(0, 0, 0, .2);\n color: var(--dsw-alias-label-primary, inherit);\n font-family: inherit;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n body:has([data-dsh-better-sidebar] [class$=\"_panel\"]) [data-mobile-nav=\"better-sidebar-close\"] svg {\n width: 14px;\n height: 14px;\n flex: none;\n }\n body:has([data-dsh-better-sidebar] [class$=\"_panel\"]) [data-mobile-nav=\"better-sidebar-close\"]:active {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));\n }\n }\n\n /* ---------- dsh-agent-teams: phone floater reposition (2026-08-18) ----------\n THIRD-PARTY COMPAT RULE \u2014 @nanmicoder/dsh-agent-teams (the AgentTeams\n activity floater: a collapsed badge + expandable panel, body-portaled\n under its own mount marker div[data-agent-teams-host]). Its own CSS\n fixes both at top:56px (<=640px) viewport coordinates and never reads\n env(safe-area-inset-*), so on a notched iPhone the badge lands INSIDE\n the safe-area + this plugin's 76px session header band \u2014 overlapping\n the info and workbench buttons (real-device report, 2026-08-18).\n Move both below the header instead: safe-area + 76px header chrome\n (48px title row + 28px view-switch row, header.css.ts) + 8px gap.\n Anchored on the plugin's own mount marker + class-suffix selectors per\n this repo's hashed-class convention \u2014 when dsh-agent-teams is not\n installed the marker never exists and this whole section is inert\n (same presence-gating as the dsh-better-sidebar rules above).\n !important because their positions come from html-level CSS vars\n (--agent-teams-panel-top et al.) whose style tag order vs ours is not\n guaranteed. z-index: their 2147483000 would float the badge over every\n one of this plugin's own overlays (drawer 40, info sheet 55-57, close\n pill 70); 30 keeps it above content but below all of them. */\n @media (max-width: 767px) {\n [data-agent-teams-host] [class$=\"_badge\"],\n [data-agent-teams-host] [class$=\"_panel\"] {\n top: calc(var(--mnav-sat) + 84px) !important;\n right: 10px !important;\n z-index: 30 !important;\n }\n [data-agent-teams-host] [class$=\"_panel\"] {\n left: 10px !important;\n width: auto !important;\n /* Their min-height:min(560px, calc(100dvh - 56 - 56)) resolves against\n the OLD top var \u2014 on short phones it exceeds the space left below\n the moved-down top edge and would force overflow; content-sized with\n a hard cap replaces it. 76px bottom clearance keeps the collapsed\n composer's top edge visible under the panel. */\n min-height: 0 !important;\n max-height: calc(100dvh - var(--mnav-sat) - 84px - var(--mnav-sab) - 76px) !important;\n }\n /* The floater belongs to the SESSION page: on the home list it hovered\n over the session rows (user feedback, 2026-08-18). The phone page\n stack is this plugin's own overlay \u2014 the official conversation (and\n with it the floater's \"current session\") stays mounted underneath, so\n agent-teams keeps rendering it; hide it whenever the home level is the\n visible one (data-view flips to \"session\" inside a session, and the\n badge/panel come right back). :has() is already a documented\n requirement of this plugin (Chromium 105+, docs/interface.md). */\n body:has([data-mobile-nav=\"home\"][data-view=\"home\"]) [data-agent-teams-host] [class$=\"_badge\"],\n body:has([data-mobile-nav=\"home\"][data-view=\"home\"]) [data-agent-teams-host] [class$=\"_panel\"] {\n display: none !important;\n }\n }\n\n /* ---------- dsh-at-file compatibility ----------\n dsh-at-file registers its own `conversation.input.dock` entry\n ([data-at-file-dock]) and renders one row per `@path` token in the\n draft. That is exactly the same source this plugin's attachment chips\n read, so on a phone a single uploaded image produced TWO entries on the\n composer rail: our 48px thumbnail tile AND at-file's file-name row for\n the same token (reported 2026-08-22).\n\n Drop at-file's row for the upload directory only. Everything else the\n user @-mentions by hand is at-file's own job and stays \u2014 this is not a\n blanket hide. The path is readable from the row's own title attribute\n (FilesDock passes `title={mention.relative}`), which is a rendered\n contract rather than a hashed class name.\n\n Phone only, and deliberately so: at >= 768px our chips are hidden by\n composer.css.ts, which leaves at-file's row as the ONLY thing on screen\n representing the attachment. Hiding it there would make an uploaded file\n invisible.\n\n When every row was an upload the container itself is left empty, and an\n empty flex item still claims the rail's 6px gap \u2014 so the dock goes too\n unless at least one non-upload row survives. */\n @media (max-width: 767px) {\n [data-at-file-dock] [data-at-file-row]:has([title^=\".dsh-uploads/\"]) {\n display: none !important;\n }\n [data-at-file-dock]:not(:has([data-at-file-row] [title]:not([title^=\".dsh-uploads/\"]))) {\n display: none !important;\n }\n }\n /* ---------- @ace-zone/dsh-market: modal header fits a phone (2026-08-22) ----------\n THIRD-PARTY COMPAT RULE \u2014 @ace-zone/dsh-market (the plugin market:\n a settings-page entry that opens its own body-level modal,\n .dshm-overlay > .dshm-panel, over the settings sheet).\n\n Its header (.dshm-head) is a plain flex row with no wrapping and no\n shrinking: title, a badge strip whose slogan is white-space:nowrap,\n a version chip, an \"official site\" link, a language toggle, and only\n then the \u00D7 that closes the modal. Flex items default to\n min-width:auto, so at phone widths the row keeps its intrinsic size,\n .dshm-panel clips it (overflow:hidden), and the \u00D7 \u2014 the only way out\n of the modal on a touch screen, which has no Esc key \u2014 is off screen\n (reported 2026-08-22).\n\n Drop the three decorative items and let the title ellipsis instead.\n The language toggle stays: it is a real control, and title + toggle +\n \u00D7 fit inside 320px once the title can shrink. The title rule also\n covers the job / help overlays, which reuse .dshm-head with a long\n \"<action> \u00B7 <package>\" title and no flexible item of their own.\n Nothing here is desktop-side: at >= 768px the row has the width it\n was written for. */\n @media (max-width: 767px) {\n body:has(.dshm-ver[title=\"@ace-zone/dsh-market\"]) .dshm-head > .dshm-head-badges,\n body:has(.dshm-ver[title=\"@ace-zone/dsh-market\"]) .dshm-head > .dshm-ver,\n body:has(.dshm-ver[title=\"@ace-zone/dsh-market\"]) .dshm-head > a.dshm-viewbtn {\n display: none !important;\n }\n body:has(.dshm-ver[title=\"@ace-zone/dsh-market\"]) .dshm-head > .dshm-title {\n flex: 1 1 auto !important;\n min-width: 0 !important;\n overflow: hidden !important;\n text-overflow: ellipsis !important;\n white-space: nowrap !important;\n }\n /* 44px touch target: their 20px glyph in 2px/6px padding is ~30px. */\n body:has(.dshm-ver[title=\"@ace-zone/dsh-market\"]) .dshm-head > .dshm-close {\n flex: 0 0 auto !important;\n font-size: 24px !important;\n padding: 6px 12px !important;\n }\n }\n\n /* ---------- dsh-auto-approve compatibility ----------\n The composer.css.ts phone rules collapse the permission trigger to an\n icon-only pill, but the official permissionGlyphs table only draws\n icons for read-only / workspace-write \u2014 the auto-approve plugin's\n \"auto\" preset gets none, so its pill showed a bare chevron. The plugin\n injects its shield+sparkle svg into the MENU items itself (DOM,\n content-anchored), but the collapsed TRIGGER re-renders on every\n preset change, so a CSS pseudo-element is the survives-re-render fix\n (same reasoning as the old model Sparkle ::before above).\n\n Presence gates, honestly stated (2026-08-25 review):\n - Trigger rule: aria-label is t('input.accessMode', {name}) with the\n raw preset display name embedded untranslated and (zh/en verified)\n at the END of the string \u2014 [aria-label$=\"Auto\"] matches exactly the\n auto-approve preset being selected, and cannot match a future\n \"Autopilot\"-style preset. Plugin absent \u2192 the name never renders \u2192\n inert. This is a DIRECT gate on the plugin's own preset name.\n - Menu-item rule: INDIRECT gate only. It matches \"permission-menu item\n without an official icon span\"; the official three presets all ship\n icon spans in the menu (verified live), so with the plugin absent\n nothing matches. Known boundary: another plugin's icon-less preset,\n or a host redesign dropping icon spans, would wrongly receive this\n shield. A direct gate is not possible: menu items carry no\n id/data attributes to anchor on, CSS cannot match text, and gating\n on the plugin's own JS-injected markers would kill this fallback in\n exactly the JS-did-not-run case it exists for. Paths are the plugin's own\n shield outline + four-point sparkle (permission-glyph.ts), stroke\n shield + filled sparkle, drawn as a currentColor mask. */\n @media (max-width: 767px) {\n /* Fallback for the MENU items too (real-phone report 2026-08-25: the\n plugin's DOM injection did not run in that page instance while this\n CSS did \u2014 bundle load timing differs per plugin). Any permission-menu\n item WITHOUT the official icon span gets the shield drawn by CSS;\n today that is exactly the plugin \"auto\" preset (the official three\n all ship icons in the menu). Self-coordinating with the plugin's own\n injection: once its svg lands it arrives inside a cloned icon span,\n the :not() stops matching, no double icon. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"] button[role=\"menuitem\"]:not(:has([class*=\"_itemIcon_\"]))::before,\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] button[class$=\"_trigger\"][aria-label$=\"Auto\"]::before {\n content: '';\n width: 16px;\n height: 16px;\n flex: none;\n background: currentColor;\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8.20554 0.899994L14.7901 3.36857V7.01026C14.7901 12 11.0466 14.2103 8.20554 15.3C5.36446 14.2103 1.62012 12 1.62012 7.01026V3.36857L8.20554 0.899994Z' fill='none' stroke='black' stroke-width='1.31831' stroke-linejoin='round'/%3E%3Cpath d='M8.35 5.4C8.35 7.2 7.15 8.4 5.35 8.4C7.15 8.4 8.35 9.6 8.35 11.4C8.35 9.6 9.55 8.4 11.35 8.4C9.55 8.4 8.35 7.2 8.35 5.4Z'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8.20554 0.899994L14.7901 3.36857V7.01026C14.7901 12 11.0466 14.2103 8.20554 15.3C5.36446 14.2103 1.62012 12 1.62012 7.01026V3.36857L8.20554 0.899994Z' fill='none' stroke='black' stroke-width='1.31831' stroke-linejoin='round'/%3E%3Cpath d='M8.35 5.4C8.35 7.2 7.15 8.4 5.35 8.4C7.15 8.4 8.35 9.6 8.35 11.4C8.35 9.6 9.55 8.4 11.35 8.4C9.55 8.4 8.35 7.2 8.35 5.4Z'/%3E%3C/svg%3E\");\n -webkit-mask-size: contain;\n mask-size: contain;\n -webkit-mask-repeat: no-repeat;\n mask-repeat: no-repeat;\n }\n }\n\n /* ---------- dsh-vision-router: composer vision toggle (2026-08-26) ----------\n THIRD-PARTY COMPAT RULE \u2014 dsh-vision-router registers a Vision mode\n toggle into conversation.input.right (slot id\n \"vision-router-mode-toggle\"): a bordered pill sized by INLINE styles \u2014\n minHeight 28, padding 4px 8px, fontSize 12, white-space nowrap \u2014\n roughly 90px of unshrinkable width.\n\n Its children changed shape in 2.1.x (re-measured on the real UI\n 2026-09-04, plugin 2.1.1). Both forms are handled:\n - text form (<= 2.0.x): <span>\uD83D\uDC41</span><span>label</span> plus a\n <span>\u2713</span> while active;\n - svg form (2.1.x, lib/client-presentation-boundary.js): a 14px eye\n <svg>, <span>label</span>, plus a SECOND 12px check <svg> while\n active.\n The 2.1.1 update is what the user hit: the label span was still hidden\n but the new eye svg was not, so it rendered NEXT TO our ::before eye \u2014\n two eyes in a 28px button \u2014 and the active check svg had nothing hiding\n it at all, eating width the phone row does not have.\n\n The phone composer row is nowrap by design (composer.css.ts section 1)\n and the model seat is its only shrinkable item, so that pill squeezes\n the model name out of the row (user report, 2026-08-26).\n\n Phone treatment: a 28x28 round icon button in the attach button's\n language (same size, same neutral fill), showing exactly ONE 16px eye\n and nothing else. Every span is hidden (label, and the text form's\n emoji and check); of the svg form's icons only the first survives, the\n trailing check svg is hidden by a sibling combinator so the count is\n never assumed. Our own ::before eye is now a FALLBACK, gated on\n :has(svg) so it draws only for the text form \u2014 the plugin ships its own\n icon from 2.1.x on, and drawing over it is what produced the double\n eye. Generated content rather than a DOM-injected icon, for the same\n survives-React-re-render reason as the auto-approve shield below: the\n button re-renders on every state flip, so an injected node would need\n an observer while ::before does not. The\n accessible name is unaffected: the button ships aria-label + title with\n the full enable/disable copy, and aria-pressed keeps conveying the\n state; visually the plugin's own inline styles still flip border +\n background + color to the brand token while active (only the inactive\n look is restyled to match the row).\n\n Presence gate: [data-vision-router-mode-toggle] is rendered by the\n plugin itself (the exact attribute from its createElement call) \u2014 with\n the plugin absent the attribute never exists and this whole section is\n inert, not one selector matching (same convention as the\n dsh-better-sidebar rules). !important throughout because the geometry\n it replaces is inline style. Phone only (max-width: 767px): desktop\n keeps the plugin's full pill untouched, and the 768-1023px tablet row\n has the width for it. order:5 restates composer.css.ts's generic\n input.right rule with the same value \u2014 kept here so the toggle can\n never fall back to order 0 (far left, before the model) if that\n structural selector ever stops matching. */\n @media (max-width: 767px) {\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle] {\n order: 5 !important;\n flex: 0 0 auto !important;\n box-sizing: border-box !important;\n width: 28px !important;\n min-width: 28px !important;\n max-width: 28px !important;\n height: 28px !important;\n min-height: 28px !important;\n padding: 0 !important;\n gap: 0 !important;\n justify-content: center !important;\n border-radius: 999px !important;\n touch-action: manipulation !important;\n -webkit-tap-highlight-color: transparent;\n }\n /* Inactive look: neutral round fill like the attach button \u2014 the\n plugin's own inactive state would draw a lonely outline circle that\n reads as broken next to the row's filled icon buttons. */\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle]:not([aria-pressed=\"true\"]) {\n border-color: transparent !important;\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;\n box-shadow: none !important;\n }\n /* Text form: emoji + label + active check are all spans \u2014 all out. */\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle] > span {\n display: none !important;\n }\n /* Svg form (2.1.x): the first svg IS the eye, so keep it and match the\n row's other icons at 16px. */\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle] > svg:first-of-type {\n width: 16px !important;\n height: 16px !important;\n }\n /* ...and drop every svg after it \u2014 today that is the active-state check\n mark. A sibling combinator rather than :nth-child(2): the count is the\n plugin's business, this only says \"one icon, the first one\". */\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle] > svg ~ svg {\n display: none !important;\n }\n /* The one svg: eye outline + iris as a currentColor mask \u2014 brand while\n active, dimmed with the button by the plugin's own disabled opacity. */\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle]:has(svg)::before {\n content: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle]::before {\n content: '';\n width: 16px;\n height: 16px;\n flex: none;\n background: currentColor;\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M1.5 8C3 5.2 5.3 3.5 8 3.5s5 1.7 6.5 4.5c-1.5 2.8-3.8 4.5-6.5 4.5S3 10.8 1.5 8Z' fill='none' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3Ccircle cx='8' cy='8' r='2'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M1.5 8C3 5.2 5.3 3.5 8 3.5s5 1.7 6.5 4.5c-1.5 2.8-3.8 4.5-6.5 4.5S3 10.8 1.5 8Z' fill='none' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3Ccircle cx='8' cy='8' r='2'/%3E%3C/svg%3E\");\n -webkit-mask-size: contain;\n mask-size: contain;\n -webkit-mask-repeat: no-repeat;\n mask-repeat: no-repeat;\n }\n [data-slot=\"conversation.composer.bar\"] [data-vision-router-mode-toggle]:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);\n outline-offset: 2px;\n }\n }\n";
2
2
  //# sourceMappingURL=compat.css.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compat.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/compat.css.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,UAAU,26lDA29BtB,CAAA"}
1
+ {"version":3,"file":"compat.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/compat.css.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,UAAU,ginDAi+BtB,CAAA"}
@@ -1,2 +1,2 @@
1
- export declare const COMPOSER_CSS = "/* ---------- phone composer (< 768px) ---------- */\n\n/* The file picker the attachment button drives (S7) \u2014 hidden at EVERY width,\n deliberately OUTSIDE the phone media block below. A bare <input type=\"file\">\n renders as a native \"Choose Files\" control, and the slot wrapper around it is\n `display: contents`, so an un-hidden one becomes a flex item of the official\n tool row. Scoping this to < 768px once put that control in the DESKTOP\n composer (caught on a live deploy, 2026-08-17): the button that drives it is\n phone-only, but the input it drives is in the DOM at all widths. */\n[data-mobile-nav=\"attach-picker\"] {\n display: none !important;\n}\n\n@media (max-width: 767px) {\n /* --- 1. flatten the two official groups ---\n `_tools` and `_trailing` only exist to cluster controls left/right.\n display:contents dissolves them into `_row`, so the six leaf controls\n become siblings in one flex line and `order` alone drives the layout:\n [attach \u00B7 + \u00B7 permission \u00B7 model] \u2026elastic gap\u2026 [context ring \u00B7 send]. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] {\n justify-content: flex-start !important;\n gap: 5px !important;\n padding: 2px 8px 8px !important;\n /* One line, always: the model pill is the only shrinkable item (its\n label ellipsizes), so wrapping would only ever push the send button\n to a second row (real-device report, 2026-08-25). */\n flex-wrap: nowrap !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] {\n display: contents !important;\n }\n\n /* --- 2. the running order ---\n The attachment button (S3 placeholder, S7 wires it up) sits leftmost;\n the official \"+\" command menu keeps its seat right next to it, where\n the two \"insert something\" affordances read as one cluster. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] [data-mobile-nav=\"attach\"] {\n order: 1 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_add\"] {\n order: 2 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] {\n order: 3 !important;\n /* Rigid on purpose. This seat holds the permission chip, whose own\n wrapper (the plugin's element inside _modes) is `flex: 0 0 auto` and\n will NOT follow the seat down \u2014 so a shrinkable seat does not make the\n chip narrower, it just slices the seat out from under a chip that\n stays 44px wide, and the overflow lands on top of the model pill.\n Measured 2026-09-04 on DSH 0.1.2 at 390px: seat 90-120 (29.7px) with\n the chip still 90-134, model starting at 125 \u2014 a 9px overlap, exactly\n the \"the two chips touch\" report. With the seat rigid: seat 90-134,\n model 139-246, overlap 0; and at 320px the model pill shrinks to 52px\n and still nothing overlaps.\n This also restores what section 1 says out loud \u2014 the model pill is\n the ONLY shrinkable item in the row. `min-width: 0` is gone with the\n shrink: it only ever mattered as the companion of `flex: 0 1`. */\n flex: 0 0 auto !important;\n gap: 6px !important;\n }\n /* The elastic gap used to live here as `margin-right: auto`, which broke\n the moment a session had no model pill: a subagent session has none, so\n the gap vanished and the stop / send buttons bunched up in the middle of\n the row instead of sitting at the right edge (reported 2026-09-06). The\n gap belongs to the RIGHT group, not to the last item of the left one \u2014\n see the two rules below the order list. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] {\n order: 4 !important;\n /* A definite flex-basis (not auto) breaks the circular sizing between\n this seat and the trigger's max-width:100%: the seat's width comes\n from flex resolution, the trigger fills up to it, the label\n ellipsizes inside. Under nowrap pressure this is the only\n min-width:0 seat, so it absorbs all shrink. */\n flex: 0 1 min(48vw, 200px) !important;\n min-width: 0 !important;\n max-width: min(48vw, 200px) !important;\n }\n /* Third-party input.right entries park next to the ring rather than\n landing at order 0 (= far left) once the groups are flattened. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.right\"] > * {\n order: 5 !important;\n }\n /* ContextMeter. 0.1.7 moved it out of the row into ROOT's `_dock`, a\n line UNDER the card \u2014 on the phone that dropped the ring to the very\n bottom of the screen (user report, 2026-09-28). Lifted back beside send\n without moving any official node: ROOT becomes a one-cell grid and the\n dock shares the card's cell, pinned to the card's bottom-right corner,\n over a placeholder the row reserves in front of send (row::before at\n order 6, below). Anchoring to the card \u2014 not to ROOT's bottom edge \u2014\n matters: ROOT's bottom padding follows the safe-area inset, so an\n absolute `bottom` offset measured in the emulator sat low on a real\n iPhone (user screenshot, 2026-09-29). Vertical: the row's 8px bottom\n pad, 34px tall = the send button's box. Horizontal: row pad 8 + send 34\n + gap 5. The percentage is dropped on the phone (user call): ring only. */\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) {\n display: grid !important;\n grid-template-columns: minmax(0, 1fr) !important;\n justify-items: center !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) > [class$=\"_card\"] {\n grid-area: 1 / 1 !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] > [class$=\"_dock\"] {\n grid-area: 1 / 1 !important;\n align-self: end !important;\n justify-self: end !important;\n position: relative !important;\n z-index: 1;\n height: 34px !important;\n margin: 0 47px 8px 0 !important;\n padding: 0 !important;\n gap: 0 !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] > [class$=\"_dock\"] button[class$=\"_trigger\"] {\n padding: 8px !important;\n gap: 0 !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] > [class$=\"_dock\"] button[class$=\"_trigger\"] > span {\n display: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) > [class$=\"_card\"] > [class$=\"_row\"]::before {\n content: '';\n order: 6;\n flex: 0 0 30px;\n margin-left: auto;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n order: 7 !important;\n }\n /* The elastic gap. It sits in front of whichever of the two right-hand\n items comes first, so the group hugs the right edge no matter what the\n left side happens to contain \u2014 with a model pill, without one (subagent\n sessions), with or without the third-party entries at order 5.\n Send carries `margin-left: auto` on its own; when the lifted ring's\n placeholder (row::before, above) precedes it, the placeholder carries\n the auto margin instead and the rule below takes it back off send \u2014\n two auto margins would SHARE the free space and open a second gap\n between the ring and send. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n margin-left: auto !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) > [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n margin-left: 0 !important;\n }\n\n /* --- 3. permission as icon-only pill; model as name+effort pill ---\n (real-device round 2, 2026-08-17; model pill reworked 2026-08-25:\n the user wants to SEE which model/effort is active, so only the\n permission trigger stays icon-only \u2014 its label is hidden and it\n collapses to a plain ~44x30 icon button. This AGREES with (rather than fights) the official\n container query (`@container (width <= 460px) { .trigger:has(.triggerIcon)\n .triggerLabel { display: none } }`) that S3 had to override \u2014 no need\n to override it back. Accessible name is unaffected: both official\n triggers already ship a descriptive aria-label independent of the\n visible text (PermissionSelect: t('input.accessMode', {name}); the\n model trigger: t('trigger.aria'/'trigger.ariaEffort')) \u2014 verified live\n in dsh-client-ui-conversation / dsh-client-ui-model-selection lib/\n client.js, 2026-08-17 \u2014 so hiding the label costs nothing for screen\n readers. The permission trigger only renders an icon for the\n \"read-only\" / \"workspace-write\" presets (permissionGlyphs in\n dsh-client-ui-conversation) \u2014 \"Full access\" and any host-configured\n preset name fall back to chevron-only, a known gap in the official\n markup this plugin cannot fill without inventing new icon meaning.\n The model trigger never renders an icon at all (only label + optional\n effort text + chevron), so its ::before below draws one from a\n primitives icon path (Sparkle \u2014 the closest existing \"model\" glyph) as\n a CSS-only pseudo-element: it survives React re-renders for free\n (unlike a DOM-injected node, which would need a MutationObserver, see\n the preview-full-toggle pitfall in AGENTS.md) and does not touch\n accessible-name computation (empty generated content). S3's\n rtl-ellipsis trick on the model label is simply inert under\n display:none now; left alone rather than unpicked. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [class$=\"_triggerLabel\"] {\n display: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] button[class$=\"_trigger\"] {\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;\n width: 44px !important;\n height: 30px !important;\n max-width: none !important;\n min-width: 0 !important;\n padding: 0 !important;\n gap: 2px !important;\n justify-content: center !important;\n border-radius: 999px !important;\n touch-action: manipulation !important;\n }\n /* Model trigger: name + effort pill (2026-08-25 user request \u2014 the\n icon-only form hid which model was active). The effort text must stay\n whole; the model NAME is the elastic part and ellipsizes. max-width\n caps the pill so the row's elastic gap survives extreme model ids;\n the label rules below override the <=1023px grow rules in\n layout.css.ts (flex 1 1 auto) that would otherwise pad the pill. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] {\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;\n height: 30px !important;\n width: auto !important;\n max-width: 100% !important;\n min-width: 0 !important;\n padding: 0 10px !important;\n gap: 4px !important;\n justify-content: flex-start !important;\n border-radius: 999px !important;\n touch-action: manipulation !important;\n font-size: 12px !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] > [class$=\"_triggerLabel\"] {\n display: block !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n overflow: hidden !important;\n text-overflow: ellipsis !important;\n white-space: nowrap !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] > [class$=\"_triggerEffort\"] {\n display: block !important;\n flex: none !important;\n white-space: nowrap !important;\n }\n /* PermissionSelect wraps its trigger in the Menu primitive's root span,\n which must shrink to the icon button's fixed width. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] > span:has(> button[class$=\"_trigger\"]) {\n flex: 0 0 auto !important;\n min-width: 0 !important;\n }\n /* (2026-08-25) The model trigger's CSS-only Sparkle ::before icon is gone:\n the pill now shows the model name + effort text itself, so an icon would\n just eat label width. */\n\n /* --- 4. both menus become bottom sheets ---\n The permission menu is the Menu primitive (role=menu, absolute, side=top)\n and the model menu is ModelSelect's own `_menu` (absolute, bottom+right\n under the pill on \u2264 0.1.2; a body portal on 0.1.5+ \u2014 see MODEL_MENU).\n Neither has a transformed ancestor between it and the viewport, so\n position:fixed re-anchors both to the screen edge. Only the shell moves \u2014\n the items, the two-level model panes and every selection handler stay\n official. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"],\n /* T70: the SAME permission menu, portaled to body on DSH 0.2.0 \u2014 the\n descendant selector above cannot reach it there. The marker is written\n by effects/permission-sheet.ts: the trigger's click arms a short window\n and the first body-level menu to land inside it is ours \u2014 the host has\n many other portal Menus (preset pickers, preference rows, the sidebar's\n \u2026, open-in-app, deliverables, \u2026), and none opens through this trigger\n while no marked menu is up. */\n body > div[role=\"menu\"][data-zen-sheet=\"perm\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) {\n position: fixed !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n top: auto !important;\n transform: none !important;\n width: 100% !important;\n min-width: 0 !important;\n max-width: none !important;\n max-height: min(70dvh, 520px) !important;\n box-sizing: border-box !important;\n border-radius: 16px 16px 0 0 !important;\n border-bottom: none !important;\n padding: 8px 8px calc(8px + var(--mnav-sab)) !important;\n /* 60 for the body-portaled menu too: the host gives it 1100 for desktop\n stacking, but nothing of ours or the host's sits between 60 and the\n sheet on a phone (hit-tested at four points on 0.1.5-rc.2, 2026-09-21),\n and 60 keeps it under the session-info sheet's 70 like the PERM one. */\n z-index: 60 !important;\n box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;\n }\n /* 44pt+ rows in both sheets (Menu items, model options, and the model\n sheet's two root cells that drill into the model / effort panes).\n The model panes are matched by ROLE too: on DSH 0.2.0 the selected\n option's class list ends in `_selected` (`\u2026_option \u2026_selected`), so the\n class-suffix selector missed exactly the checked row \u2014 it fell back to\n the host's 34px pill while its siblings got 48px. The root cells are\n role=menuitem, the options role=menuitemradio; the class forms stay for\n older hosts. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"] [role=\"menuitem\"],\n body > div[role=\"menu\"][data-zen-sheet=\"perm\"] [role=\"menuitem\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [role=\"menuitem\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [role=\"menuitemradio\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [class$=\"_option\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [class$=\"_cell\"] {\n min-height: 48px !important;\n border-radius: 12px !important;\n font-size: 15px !important;\n }\n /* --- 4a. third-party composer entries live in the model sheet ---\n effects/model-sheet-extras.ts parks the `conversation.input.right`\n container inside the model menu while that menu is open and puts it back\n when it closes; these rules are the two halves of that trip.\n\n In the row: hidden, but ONLY once the effect has marked the container as\n one it manages. Keying the hide off the marker rather than off \"not\n currently parked\" is what makes the failure direction safe \u2014 if the\n effect declines to act (nothing in the slot but the vision toggle, so\n nothing worth moving) or never runs at all, the marker is absent, this\n rule does not match, and the controls stay exactly where the host put\n them. The inverse spelling hid them in the row whenever they were not in\n the sheet, which in those same cases left them reachable from neither\n place (caught in review, 2026-09-06).\n Hiding by ancestor rather than by a display rule on the container itself\n is what lets the parked copy style itself freely \u2014 same element, two\n homes, two rule sets. `!important` because the slot container carries an\n inline `display: contents`. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.right\"][data-zen-sheet-extras] {\n display: none !important;\n }\n /* Parked in the sheet: a column of full-width rows in the sheet's own\n language, so a relocated control reads as a sibling of \u6A21\u578B and \u63A8\u7406\u7B49\u7EA7\n rather than a chip that wandered in. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] {\n display: flex !important;\n flex-direction: column !important;\n gap: 2px !important;\n width: 100% !important;\n }\n /* Undo the row's compaction on every parked control: back to full width and\n the 48px the sheet's own cells use. `> * > *` reaches both shapes without\n naming either plugin \u2014 subscriptions wraps its trigger in a positioning\n div, vision-router puts its button straight in the slot. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] > *,\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] > * > button {\n width: 100% !important;\n max-width: none !important;\n min-width: 0 !important;\n justify-content: flex-start !important;\n }\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] button {\n box-sizing: border-box !important;\n min-height: 48px !important;\n height: auto !important;\n padding: 0 12px !important;\n gap: 10px !important;\n border-radius: 12px !important;\n border: none !important;\n background: transparent !important;\n font-size: 15px !important;\n /* Both plugins center their chip label, which is right for a pill in the\n row and wrong for a full-width row: the sheet's own cells start their\n label at the left edge. justify-content covers the flex triggers,\n text-align the ones that are not flex containers. */\n text-align: left !important;\n justify-content: flex-start !important;\n }\n /* Order comes from the row (compat.css.ts puts the vision toggle at 5 so it\n parks beside the context ring), and that rule still matches in here on\n \u2264 0.1.2 \u2014 the inline sheet is a DOM descendant of the composer bar even\n though it paints as a fixed layer (the 0.1.5 body portal is not, where\n this is a harmless no-op). Reset it so the sheet follows DOM order\n instead of inheriting a decision that was about a different layout. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] > * {\n order: 0 !important;\n }\n /* The vision toggle is squeezed to a 28px icon in the row (compat.css.ts);\n in the sheet it is a row, so its label comes back and the icon leads. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] [data-vision-router-mode-toggle] > span {\n display: inline !important;\n }\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] [data-vision-router-mode-toggle] > svg:first-of-type {\n width: 18px !important;\n height: 18px !important;\n }\n /* The speed chip opens a menu of its own, anchored `bottom: 100%` to its\n trigger. Inside the sheet \u2014 which is `overflow: hidden` so its rounded\n top clips its content \u2014 that menu would be cut off. Same escape section 4\n uses for the host's two menus: position: fixed leaves the clip behind\n (the sheet sets `transform: none`, so nothing between here and the\n viewport captures fixed positioning) and it lands as its own sheet on top\n of this one. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] [role=\"menu\"] {\n position: fixed !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n top: auto !important;\n margin: 0 !important;\n width: 100% !important;\n min-width: 0 !important;\n max-width: none !important;\n max-height: min(70dvh, 520px) !important;\n box-sizing: border-box !important;\n border-radius: 16px 16px 0 0 !important;\n border-bottom: none !important;\n padding: 8px 8px calc(8px + var(--mnav-sab)) !important;\n /* Above the model sheet it sits on (60, section 4), below the session-info\n sheet's 70 (info.css.ts) \u2014 it is a child of the model sheet, not a peer\n of the page's own layers. */\n z-index: 61 !important;\n box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;\n }\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] [role=\"menu\"] [role=\"menuitemradio\"] {\n min-height: 48px !important;\n border-radius: 12px !important;\n font-size: 15px !important;\n }\n\n /* --- 4b. the official scroll-to-bottom button must not poke through the\n sheet (real-device follow-up, 2026-08-17) ---\n ChatView's own \"jump to latest\" button (aria-label t(\"chat.toBottom\"))\n is `position: sticky; z-index: 8` inside the message column, not\n `position: fixed` \u2014 it never escapes to the same top-level stacking\n context our sheets get promoted to, so raising the sheet's z-index\n further does nothing (measured live: it still rendered on top at\n z-index 60 vs 8). Rather than chase engine-specific stacking-context\n semantics (Chromium and WebKit do not always agree here \u2014 see AGENTS.md\n CDP/document.hidden lesson for another instance of that), this hides\n the button outright while either sheet is open and lets it reappear on\n close: a plain `display: none` behind a live `:has()` read is correct\n regardless of which stacking rules the engine happens to apply.\n Selector: no hashed classes (dsh-client-ui-conversation lib/client.js,\n verified 2026-08-17) \u2014 `data-chat-flow` is the one stable attribute on\n the message column, and the button's wrapper is its only sibling\n (ChatView only ever renders the column and, conditionally, this one\n slot), so the adjacent-sibling combinator pins it precisely. */\n body:has([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"]) [data-chat-flow] + div,\n body:has([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"]) [data-chat-flow] + div,\n body:has(> [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [data-chat-flow] + div,\n /* T70: the portaled permission menu (marked by effects/permission-sheet.ts). */\n body:has(> div[role=\"menu\"][data-zen-sheet=\"perm\"]) [data-chat-flow] + div {\n display: none !important;\n }\n /* --- 5. input box: two lines minimum, five lines maximum ---\n The official autosizer drives the box off the hidden mirror's height, so\n a min-height on the mirror IS the min-height of the field; the scroll cap\n rides the official custom property. 52px = 2 x 24px line box + 4px pad,\n 124px = 5 lines. The hero card keeps its own one-line collapse (misc.css\n pins _scroll/_grow while the placeholder shows). */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] [class$=\"_mirror\"] {\n min-height: 52px !important;\n }\n [class$=\"_composerSeat\"] {\n --dsh-composer-text-max-height: 124px;\n }\n /* Pull the text in from the card's left border. Upstream insets the editing\n host by 16px while the control row below it starts 8px in (row padding,\n section 1), so the placeholder sat noticeably further out than the\n buttons under it and the field read as over-padded on a phone (reported\n 2026-09-06). 12px squares it with the 12px of clear space on the right,\n giving the text a symmetric box, and closes most of the gap with the\n button row. Anchored on the editing host's own contract attribute rather\n than a hashed class. */\n [data-slot=\"conversation.composer.bar\"] [data-composer-input] {\n padding-left: 12px !important;\n }\n\n /* --- 6. no divider above OR below the message list ---\n Instead of a rule the messages butt against, they fade near both edges.\n This used to be ONE mask-image on the message scroller; iOS WebKit\n turned that into fog over the header (user report 2026-08-23): a mask\n on a scroll container is applied in CONTENT coordinates there (the fade\n bands scroll away with the messages) and, worse, it breaks the\n container's own overflow clipping \u2014 content scrolled out of view keeps\n painting, straight over the header and status bar. Engine bug with no\n iOS escape hatch (every iOS browser is WebKit), so the fades moved off\n the scroller entirely:\n\n - fade-IN under the header: the header's own ::after, restyled into a\n 20px gradient strip \u2014 styles/header.css.ts (\"no header bottom line\").\n - fade-OUT above the composer: nothing of ours. The official composer\n seat already carries `background: linear-gradient(transparent 0,\n bg 36px)` on its sticky self (dsh-client-ui-conversation lib/\n client.js .wSkVaW_composerSeat) and that is the entire visible\n effect; the mask's own bottom band landed inside the safe-area\n padding below the card and painted nothing. If the bottom edge ever\n reads harsh, hang a ::before strip off the seat the same way the\n header does \u2014 do NOT put a mask back on the scroller.\n\n Painted strips instead of a mask is a fair trade only because the page\n background is one flat color \u2014 which it is (--dsw-alias-bg-base). */\n [class$=\"_composerSeat\"],\n [class$=\"_composerStack\"] {\n border-top: none !important;\n }\n\n /* --- 7. dock entries become mini chips above the input card ---\n conversation.input.dock is display:contents (inline style), so its\n entries are stacked rows of the composer column. Forcing the slot itself\n to flex turns them into ONE scrollable chip line outside and above the\n card. Written against the slot, not against any particular plugin's chip\n (the git branch chip and the todo panel are third-party and may not be\n installed at all). */\n [data-slot=\"conversation.input.dock\"] {\n display: flex !important;\n flex-flow: row nowrap !important;\n align-items: center !important;\n gap: 6px !important;\n min-width: 0 !important;\n margin: 0 16px !important;\n overflow-x: auto !important;\n overflow-y: hidden !important;\n scrollbar-width: none !important;\n /* The dock is itself a flex ITEM of the composer stack, and the\n new-session hero pins that stack to a fixed height. overflow-x:auto\n above makes this a scroll container, which drops a flex item's\n automatic minimum size to 0 \u2014 so the hero squeezed the dock to ~5px\n and the attachment chips slid under the card drawn after it (only the\n top sliver of a thumbnail showed; reported from a real phone,\n 2026-08-26). Refuse the squeeze: natural height, the stack centers a\n few px lower instead. In-chat the column is not height-pinned and this\n is a no-op. */\n flex-shrink: 0 !important;\n }\n [data-slot=\"conversation.input.dock\"]::-webkit-scrollbar {\n display: none !important;\n }\n [data-slot=\"conversation.input.dock\"] > * {\n flex: 0 0 auto !important;\n max-width: 70% !important;\n min-height: 26px !important;\n max-height: 26px !important;\n border-radius: 999px !important;\n font-size: 11.5px !important;\n line-height: 18px !important;\n overflow: hidden !important;\n }\n /* The git-graph chip carries a 34px tablet target from misc.css; that\n selector is more specific, so restate it at the phone breakpoint. */\n [data-slot=\"conversation.input.dock\"] [data-gitgraph-chip-anchor] [data-gitgraph-chip] {\n min-height: 26px !important;\n padding: 0 10px !important;\n font-size: 11.5px !important;\n }\n /* --- 7a. the native to-do card opts OUT of the 26px pill cage ---\n conversation.input.dock is not only a chip rail: DSH's own TodoPanel\n (dsh-client-ui-conversation) registers here, and it is an expandable\n card, not a pill \u2014 tapping its header renders the to-do list INSIDE\n itself. The cage's max-height:26px + overflow:hidden left that list\n rendered but clipped to nothing, so on a phone the bar looked dead:\n aria-expanded flipped, 8 rows mounted at 216px, and the card still\n painted 26px tall (measured live, 2026-08-20). Give it the official\n geometry back \u2014 full row, natural height, 12px radius. Its list caps\n itself at 180px with its own scroller, so this cannot run into the\n composer. Anchored on the official data-testid, a stable contract\n marker rather than a hashed class name. */\n [data-slot=\"conversation.input.dock\"] > [data-testid=\"todo-panel\"] {\n /* A whole line to itself. It shared line 0 with the pills until the goal\n bar turned up (2026-09-06): two full-width things on one nowrap line\n meant the to-do header shrank to a stub and the goal bar ran off the\n right edge. Both are status strips you read at a glance, so they stack\n \u2014 see the line policy in 7c. */\n flex: 1 0 100% !important;\n order: 1 !important;\n min-width: 0 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n overflow: visible !important;\n border-radius: 12px !important;\n /* The cage shrinks dock text to 11.5px/18px; the card's rows inherit it\n and turn cramped. Restore the official 13px body scale. */\n font-size: 13px !important;\n line-height: normal !important;\n }\n\n /* --- 7b. attachment chips (S7.1) ---\n Our own dock entry opts OUT of the 26px pill cage above: an image chip is\n a 48px tile. The row sits tight under the card's top edge, so the chips\n read as part of the composer rather than as a floating strip.\n\n It also takes the LAST line of its own (line policy in 7c): a 48px tile\n beside the to-do chip on one nowrap line squeezed that chip down to an\n unreadable stub, with a long file name pushing the rest off-screen\n (reported 2026-08-22). Its own chips wrap rather than overflow, so\n several attachments stack instead of scrolling out of reach. */\n [data-slot=\"conversation.input.dock\"] > [data-mobile-nav=\"attach-chips\"] {\n display: flex !important;\n flex: 1 0 100% !important;\n flex-wrap: wrap !important;\n order: 4 !important;\n align-items: center !important;\n gap: 6px !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n border-radius: 0 !important;\n overflow: visible !important;\n margin-bottom: -2px;\n }\n\n /* --- 7b2. the T34 remote-status banner opts OUT of the cage ---\n Same class of entry as the to-do card (7a): a full-width status strip,\n not a pill. dsh-zen-remote's remote banner registers into this dock at\n order 15 when a remote session is offline or closed (T34) \u2014 the 26px\n cage would clip the copy to one unreadable sliver. A whole line of its\n own, natural height. */\n [data-slot=\"conversation.input.dock\"] > [data-zen-remote=\"remote-banner\"] {\n flex: 1 0 100% !important;\n order: 2 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n overflow: visible !important;\n border-radius: 12px !important;\n font-size: 12px !important;\n line-height: 18px !important;\n }\n\n /* --- 7c. the queue strip opts OUT of the cage, on a line of its own ---\n Third entry that is not a pill. conversation.input.dock is a `list` slot\n and DSH's own QueueDock registers into it at order 20 (id `queue`,\n dsh-client-ui-conversation) \u2014 a 36px row per queued message, or a\n collapsible \"N \u6761\u6392\u961F\u6D88\u606F\" header once there is more than one. The 26px\n cage above clipped it to a sliver: on a phone a queued message showed as\n a grey stub poking out from behind the input card, unreadable and with\n its edit / delete / send buttons cut off (reported 2026-08-22, same\n failure the to-do card hit in 7a).\n\n Two things are restored. Geometry: the official row height back, and the\n dock's own composer-column sizing dropped \u2014 its width/max-width are\n calc()s over the composer card variables and its bottom margin is\n negative by design (upstream tucks the strip UNDER the card), none of\n which survives being a flex item of our chip rail.\n\n Layout \u2014 the line policy for the whole rail, now that three things want\n room on it. The pills (the to-do card, a branch chip, whatever else\n registers) keep order 0 and share the top line, still scrolling\n horizontally. The queue takes the next line (order 1). The attachment\n chips take the last one (order 2, 7b), so what the user is about to send\n sits closest to the input it will be sent from. The rail only wraps\n while one of the two full-width entries exists, so a session with\n neither keeps the original single scrolling chip line. */\n [data-slot=\"conversation.input.dock\"]:has(> [data-queue-dock], > [data-mobile-nav=\"attach-chips\"], > [data-testid=\"todo-panel\"], > [data-goal-bar]) {\n flex-wrap: wrap !important;\n }\n [data-slot=\"conversation.input.dock\"] > [data-queue-dock] {\n flex: 1 0 100% !important;\n order: 3 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n margin: 0 !important;\n padding: 0 !important;\n border-radius: 0 !important;\n overflow: visible !important;\n font-size: 13px !important;\n line-height: normal !important;\n }\n /* dsh-client-ui-goal's status strip (its own `data-goal-bar` marker; it\n registers into this dock at order 10). Fourth thing here that is not a\n pill: a row carrying the objective text plus pause / edit / delete\n buttons, so the 26px cage clipped its buttons off the right edge and the\n nowrap line pushed what was left off screen (reported 2026-09-06). Same\n treatment the queue gets \u2014 a line of its own, official geometry back. */\n [data-slot=\"conversation.input.dock\"] > [data-goal-bar] {\n flex: 1 0 100% !important;\n order: 2 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n border-radius: 12px !important;\n overflow: visible !important;\n font-size: 13px !important;\n line-height: normal !important;\n }\n\n /* The panel is the dock's only child; upstream rounds its top corners only\n and drops the bottom border, because there it is half-hidden behind the\n card. On its own line it is a free-standing strip, so close it up. */\n [data-slot=\"conversation.input.dock\"] > [data-queue-dock] > * {\n border-radius: 12px !important;\n }\n [data-slot=\"conversation.input.dock\"] > [data-queue-dock] > *::after {\n border-bottom: 1px solid var(--dsw-alias-border-l1) !important;\n }\n\n [data-mobile-nav=\"attach-chip\"] {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n max-width: 62vw;\n background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12));\n color: var(--dsw-alias-label-primary, inherit);\n }\n [data-mobile-nav=\"attach-chip\"][data-kind=\"file\"] {\n height: 26px;\n padding: 0 4px 0 8px;\n border-radius: 999px;\n font-size: 11.5px;\n line-height: 18px;\n }\n /* An image chip is the thumbnail: no name line, the filename lives in the\n title attribute (and in the draft text right below). */\n [data-mobile-nav=\"attach-chip\"][data-kind=\"image\"] {\n width: 48px;\n height: 48px;\n padding: 0;\n border-radius: 10px;\n overflow: hidden;\n }\n [data-mobile-nav=\"attach-chip-art\"] {\n position: relative;\n display: grid;\n place-items: center;\n flex: none;\n overflow: hidden;\n }\n [data-kind=\"file\"] > [data-mobile-nav=\"attach-chip-art\"] {\n width: 16px;\n height: 16px;\n opacity: .7;\n }\n [data-kind=\"image\"] > [data-mobile-nav=\"attach-chip-art\"] {\n width: 100%;\n height: 100%;\n }\n /* The <img> is stacked over the paperclip, not swapped for it: a format the\n engine cannot decode (HEIC outside WebKit) simply paints nothing and the\n icon shows through \u2014 no onError state to carry. */\n [data-mobile-nav=\"attach-chip-art\"] img {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, .18));\n }\n [data-mobile-nav=\"attach-chip-name\"] {\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n }\n [data-mobile-nav=\"attach-chip-remove\"] {\n display: grid;\n place-items: center;\n flex: none;\n width: 18px;\n height: 18px;\n padding: 0;\n border: none;\n border-radius: 999px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n touch-action: manipulation;\n }\n /* On a tile the \u00D7 floats in the corner over the picture, so it needs its own\n ground to stay legible against an arbitrary photo. */\n [data-kind=\"image\"] > [data-mobile-nav=\"attach-chip-remove\"] {\n position: absolute;\n top: 2px;\n right: 2px;\n background: rgba(0, 0, 0, .55);\n color: #fff;\n }\n [data-mobile-nav=\"attach-chip-remove\"]:active {\n transform: scale(.9);\n }\n\n /* --- 8. the official stats strip leaves the composer ---\n Its data moves into the session info card (S4). The row is the composer\n dock's own `_root` entry; the slot itself stays live for later entries. */\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"] {\n display: none !important;\n }\n\n /* --- 9. the attachment button (S7; its file input is hidden at the top of\n this file, at every width) --- */\n [data-mobile-nav=\"attach\"] {\n position: relative;\n width: 28px !important;\n height: 28px !important;\n flex: none !important;\n display: grid !important;\n place-items: center !important;\n padding: 0 !important;\n border: none !important;\n border-radius: 999px !important;\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12));\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n }\n [data-mobile-nav=\"attach\"]:active {\n transform: scale(.94);\n transition: transform .12s;\n }\n /* Busy: a ring sweeps around the paperclip. Drawn with a conic gradient in\n a pseudo-element rather than a spinner node, so the official React tree\n around us has nothing extra to re-render (same reasoning as the model\n pill's mask icon, S3.1). */\n [data-mobile-nav=\"attach\"][data-busy]::after {\n content: \"\";\n position: absolute;\n inset: -2px;\n border-radius: 999px;\n background: conic-gradient(from 0deg, transparent 0 65%, currentColor 100%);\n mask: radial-gradient(closest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));\n -webkit-mask: radial-gradient(closest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));\n animation: mnav-attach-spin .9s linear infinite;\n pointer-events: none;\n }\n @keyframes mnav-attach-spin {\n to { transform: rotate(1turn); }\n }\n @media (prefers-reduced-motion: reduce) {\n [data-mobile-nav=\"attach\"][data-busy]::after {\n animation-duration: 3s;\n }\n }\n /* Failure note: one line above the button, tap-to-dismiss. Sits on the\n composer card (z 2) rather than in an overlay layer \u2014 nothing here needs\n to clear the permission/model sheets. */\n [data-mobile-nav=\"attach-error\"] {\n position: absolute;\n bottom: calc(100% + 6px);\n left: 0;\n z-index: 2;\n max-width: 62vw;\n padding: 5px 8px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, .82));\n color: var(--dsw-alias-label-primary, #fff);\n box-shadow: 0 2px 8px rgba(0, 0, 0, .24);\n }\n\n /* --- 9. home-indicator clearance (S4.1, 2026-08-17) ---\n Owned HERE, not by the gateway half. That plugin used to carry\n [data-slot=\"conversation.composer\"] { padding-bottom: max(env(safe-area-inset-bottom), 8px) }\n in both pwa/app.css and the gateway's inline DEVICE_CSS. Both were\n INERT and had always been: that slot element is `display: contents`\n (measured 2026-08-17 \u2014 the slot wrapper generates no box, so padding on\n it is discarded), which is why raising it never moved anything. The rule\n is deleted on the PWA side and restated here on an element that actually\n lays out, reading --mnav-sab so ?mobile-nav-inset=54,34 can regress it\n off-device (env() is hard 0 on desktop \u2014 the whole reason S2.1 exists).\n\n Target: the card should look EQUALLY inset on all four sides. This same\n element carries the card's side inset (`padding: 0 16px` officially,\n measured 16px left and 16px right at 390px), so the bottom gap is simply\n capped at that same 16px. Clearing the FULL 34px inset \u2014 the naive\n max(sab, 8px) \u2014 is what read as \"too thick\": it is more than double the\n side margin, so the card looked shoved up off the bottom edge.\n\n clamp() says all three requirements at once:\n - floor 8px -> a device with no home indicator (sab: 0) keeps the\n official 8px exactly, so this is a strict no-op there;\n - track sab -> a shallower inset than 16px is honoured as-is;\n - cap 16px -> a full-size indicator (sab: 34) lands on 16px, equal\n to the side inset, which is the look being asked for.\n Keep the 16px in step with the side padding above if that ever changes;\n that equality IS the spec here, not a coincidence. */\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] {\n padding-bottom: clamp(8px, var(--mnav-sab), 16px) !important;\n }\n\n /* S10 keyboard avoid: effects/keyboard-avoid.ts mirrors the band of the\n layout viewport hidden behind the software keyboard into --mnav-kb-lift\n and stamps the html attribute while (and only while) it is non-zero \u2014\n a resting transform, even translateY(0), would silently become the\n containing block for any fixed-position descendant of the bar. In every\n environment where the browser's own focus-reveal works the attribute is\n simply never set.\n The target is the slot's _root CHILD, not the slot wrapper: the wrapper\n is display:contents \u2014 it generates no box, so a transform on it is a\n silent no-op (found live on-device via CDP, 2026-08-21; the variable and\n attribute were set and nothing moved). */\n html[data-mnav-kb] [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] {\n transform: translateY(calc(-1 * var(--mnav-kb-lift, 0px)));\n }\n}\n\n/* The attachment button and its preview row only exist for the phone shell.\n Both render at every width (the slots are not breakpoint-aware), so both\n need hiding here \u2014 the dock-row rules that shape the chips live in the\n < 768px block and would otherwise leave a bare, unstyled chip line in the\n desktop composer. */\n@media (min-width: 768px) {\n [data-mobile-nav=\"attach\"],\n [data-mobile-nav=\"attach-chips\"] {\n display: none !important;\n }\n}\n";
1
+ export declare const COMPOSER_CSS = "/* ---------- phone composer (< 768px) ---------- */\n\n/* The file picker the attachment button drives (S7) \u2014 hidden at EVERY width,\n deliberately OUTSIDE the phone media block below. A bare <input type=\"file\">\n renders as a native \"Choose Files\" control, and the slot wrapper around it is\n `display: contents`, so an un-hidden one becomes a flex item of the official\n tool row. Scoping this to < 768px once put that control in the DESKTOP\n composer (caught on a live deploy, 2026-08-17): the button that drives it is\n phone-only, but the input it drives is in the DOM at all widths. */\n[data-mobile-nav=\"attach-picker\"] {\n display: none !important;\n}\n\n@media (max-width: 767px) {\n /* --- 1. flatten the two official groups ---\n `_tools` and `_trailing` only exist to cluster controls left/right.\n display:contents dissolves them into `_row`, so the six leaf controls\n become siblings in one flex line and `order` alone drives the layout:\n [attach \u00B7 + \u00B7 permission \u00B7 model] \u2026elastic gap\u2026 [context ring \u00B7 send]. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] {\n justify-content: flex-start !important;\n gap: 5px !important;\n padding: 2px 8px 8px !important;\n /* One line, always: the model pill is the only shrinkable item (its\n label ellipsizes), so wrapping would only ever push the send button\n to a second row (real-device report, 2026-08-25). */\n flex-wrap: nowrap !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"],\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] {\n display: contents !important;\n }\n\n /* --- 2. the running order ---\n The attachment button (S3 placeholder, S7 wires it up) sits leftmost;\n the official \"+\" command menu keeps its seat right next to it, where\n the two \"insert something\" affordances read as one cluster. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] [data-mobile-nav=\"attach\"] {\n order: 1 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_add\"] {\n order: 2 !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] {\n order: 3 !important;\n /* Rigid on purpose. This seat holds the permission chip, whose own\n wrapper (the plugin's element inside _modes) is `flex: 0 0 auto` and\n will NOT follow the seat down \u2014 so a shrinkable seat does not make the\n chip narrower, it just slices the seat out from under a chip that\n stays 44px wide, and the overflow lands on top of the model pill.\n Measured 2026-09-04 on DSH 0.1.2 at 390px: seat 90-120 (29.7px) with\n the chip still 90-134, model starting at 125 \u2014 a 9px overlap, exactly\n the \"the two chips touch\" report. With the seat rigid: seat 90-134,\n model 139-246, overlap 0; and at 320px the model pill shrinks to 52px\n and still nothing overlaps.\n This also restores what section 1 says out loud \u2014 the model pill is\n the ONLY shrinkable item in the row. `min-width: 0` is gone with the\n shrink: it only ever mattered as the companion of `flex: 0 1`. */\n flex: 0 0 auto !important;\n gap: 6px !important;\n }\n /* The elastic gap used to live here as `margin-right: auto`, which broke\n the moment a session had no model pill: a subagent session has none, so\n the gap vanished and the stop / send buttons bunched up in the middle of\n the row instead of sitting at the right edge (reported 2026-09-06). The\n gap belongs to the RIGHT group, not to the last item of the left one \u2014\n see the two rules below the order list. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] {\n order: 4 !important;\n /* A definite flex-basis (not auto) breaks the circular sizing between\n this seat and the trigger's max-width:100%: the seat's width comes\n from flex resolution, the trigger fills up to it, the label\n ellipsizes inside. Under nowrap pressure this is the only\n min-width:0 seat, so it absorbs all shrink. */\n flex: 0 1 min(48vw, 200px) !important;\n min-width: 0 !important;\n max-width: min(48vw, 200px) !important;\n }\n /* Third-party input.right entries park next to the ring rather than\n landing at order 0 (= far left) once the groups are flattened. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.right\"] > * {\n order: 5 !important;\n }\n /* ContextMeter. 0.1.7 moved it out of the row into ROOT's `_dock`, a\n line UNDER the card \u2014 on the phone that dropped the ring to the very\n bottom of the screen (user report, 2026-09-28). Lifted back beside send\n without moving any official node: ROOT becomes a one-cell grid and the\n dock shares the card's cell, pinned to the card's bottom-right corner,\n over a placeholder the row reserves in front of send (row::before at\n order 6, below). Anchoring to the card \u2014 not to ROOT's bottom edge \u2014\n matters: ROOT's bottom padding follows the safe-area inset, so an\n absolute `bottom` offset measured in the emulator sat low on a real\n iPhone (user screenshot, 2026-09-29). Vertical: the row's 8px bottom\n pad, 34px tall = the send button's box. Horizontal: row pad 8 + send 34\n + gap 5. The percentage is dropped on the phone (user call): ring only. */\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) {\n display: grid !important;\n grid-template-columns: minmax(0, 1fr) !important;\n justify-items: center !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) > [class$=\"_card\"] {\n grid-area: 1 / 1 !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] > [class$=\"_dock\"] {\n grid-area: 1 / 1 !important;\n align-self: end !important;\n justify-self: end !important;\n position: relative !important;\n z-index: 1;\n height: 34px !important;\n margin: 0 47px 8px 0 !important;\n padding: 0 !important;\n gap: 0 !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] > [class$=\"_dock\"] button[class$=\"_trigger\"] {\n padding: 8px !important;\n gap: 0 !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] > [class$=\"_dock\"] button[class$=\"_trigger\"] > span {\n display: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) > [class$=\"_card\"] > [class$=\"_row\"]::before {\n content: '';\n order: 6;\n flex: 0 0 30px;\n margin-left: auto;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n order: 7 !important;\n }\n /* The elastic gap. It sits in front of whichever of the two right-hand\n items comes first, so the group hugs the right edge no matter what the\n left side happens to contain \u2014 with a model pill, without one (subagent\n sessions), with or without the third-party entries at order 5.\n Send carries `margin-left: auto` on its own; when the lifted ring's\n placeholder (row::before, above) precedes it, the placeholder carries\n the auto margin instead and the rule below takes it back off send \u2014\n two auto margins would SHARE the free space and open a second gap\n between the ring and send. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n margin-left: auto !important;\n }\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"]:has(> [class$=\"_dock\"] button) > [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_primary\"] {\n margin-left: 0 !important;\n }\n\n /* --- 3. permission as icon-only pill; model as name+effort pill ---\n (real-device round 2, 2026-08-17; model pill reworked 2026-08-25:\n the user wants to SEE which model/effort is active, so only the\n permission trigger stays icon-only \u2014 its label is hidden and it\n collapses to a plain ~44x30 icon button. This AGREES with (rather than fights) the official\n container query (`@container (width <= 460px) { .trigger:has(.triggerIcon)\n .triggerLabel { display: none } }`) that S3 had to override \u2014 no need\n to override it back. Accessible name is unaffected: both official\n triggers already ship a descriptive aria-label independent of the\n visible text (PermissionSelect: t('input.accessMode', {name}); the\n model trigger: t('trigger.aria'/'trigger.ariaEffort')) \u2014 verified live\n in dsh-client-ui-conversation / dsh-client-ui-model-selection lib/\n client.js, 2026-08-17 \u2014 so hiding the label costs nothing for screen\n readers. The permission trigger only renders an icon for the\n \"read-only\" / \"workspace-write\" presets (permissionGlyphs in\n dsh-client-ui-conversation) \u2014 \"Full access\" and any host-configured\n preset name fall back to chevron-only, a known gap in the official\n markup this plugin cannot fill without inventing new icon meaning.\n The model trigger never renders an icon at all (only label + optional\n effort text + chevron), so its ::before below draws one from a\n primitives icon path (Sparkle \u2014 the closest existing \"model\" glyph) as\n a CSS-only pseudo-element: it survives React re-renders for free\n (unlike a DOM-injected node, which would need a MutationObserver, see\n the preview-full-toggle pitfall in AGENTS.md) and does not touch\n accessible-name computation (empty generated content). S3's\n rtl-ellipsis trick on the model label is simply inert under\n display:none now; left alone rather than unpicked. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [class$=\"_triggerLabel\"] {\n display: none !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] button[class$=\"_trigger\"] {\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;\n width: 44px !important;\n height: 30px !important;\n max-width: none !important;\n min-width: 0 !important;\n padding: 0 !important;\n gap: 2px !important;\n justify-content: center !important;\n border-radius: 999px !important;\n touch-action: manipulation !important;\n }\n /* Model trigger: name + effort pill (2026-08-25 user request \u2014 the\n icon-only form hid which model was active). The effort text must stay\n whole; the model NAME is the elastic part and ellipsizes. max-width\n caps the pill so the row's elastic gap survives extreme model ids;\n the label rules below override the <=1023px grow rules in\n layout.css.ts (flex 1 1 auto) that would otherwise pad the pill. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] {\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;\n height: 30px !important;\n width: auto !important;\n max-width: 100% !important;\n min-width: 0 !important;\n padding: 0 10px !important;\n gap: 4px !important;\n justify-content: flex-start !important;\n border-radius: 999px !important;\n touch-action: manipulation !important;\n font-size: 12px !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] > [class$=\"_triggerLabel\"] {\n display: block !important;\n flex: 0 1 auto !important;\n min-width: 0 !important;\n overflow: hidden !important;\n text-overflow: ellipsis !important;\n white-space: nowrap !important;\n }\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_trigger\"] > [class$=\"_triggerEffort\"] {\n display: block !important;\n flex: none !important;\n white-space: nowrap !important;\n }\n /* PermissionSelect wraps its trigger in the Menu primitive's root span,\n which must shrink to the icon button's fixed width. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] > span:has(> button[class$=\"_trigger\"]) {\n flex: 0 0 auto !important;\n min-width: 0 !important;\n }\n /* (2026-08-25) The model trigger's CSS-only Sparkle ::before icon is gone:\n the pill now shows the model name + effort text itself, so an icon would\n just eat label width. */\n\n /* --- 4. both menus become bottom sheets ---\n The permission menu is the Menu primitive (role=menu, absolute, side=top)\n and the model menu is ModelSelect's own `_menu` (absolute, bottom+right\n under the pill on \u2264 0.1.2; a body portal on 0.1.5+ \u2014 see MODEL_MENU).\n Neither has a transformed ancestor between it and the viewport, so\n position:fixed re-anchors both to the screen edge. Only the shell moves \u2014\n the items, the two-level model panes and every selection handler stay\n official. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"],\n /* T70: the SAME permission menu, portaled to body on DSH 0.2.0 \u2014 the\n descendant selector above cannot reach it there. The marker is written\n by effects/permission-sheet.ts: the trigger's click arms a short window\n and the first body-level menu to land inside it is ours \u2014 the host has\n many other portal Menus (preset pickers, preference rows, the sidebar's\n \u2026, open-in-app, deliverables, \u2026), and none opens through this trigger\n while no marked menu is up. */\n body > div[role=\"menu\"][data-zen-sheet=\"perm\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) {\n position: fixed !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n top: auto !important;\n transform: none !important;\n width: 100% !important;\n min-width: 0 !important;\n max-width: none !important;\n max-height: min(70dvh, 520px) !important;\n box-sizing: border-box !important;\n border-radius: 16px 16px 0 0 !important;\n border-bottom: none !important;\n padding: 8px 8px calc(8px + var(--mnav-sab)) !important;\n /* 60 for the body-portaled menu too: the host gives it 1100 for desktop\n stacking, but nothing of ours or the host's sits between 60 and the\n sheet on a phone (hit-tested at four points on 0.1.5-rc.2, 2026-09-21),\n and 60 keeps it under the session-info sheet's 70 like the PERM one. */\n z-index: 60 !important;\n box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;\n }\n /* 44pt+ rows in both sheets (Menu items, model options, and the model\n sheet's two root cells that drill into the model / effort panes).\n The model panes are matched by ROLE too: on DSH 0.2.0 the selected\n option's class list ends in `_selected` (`\u2026_option \u2026_selected`), so the\n class-suffix selector missed exactly the checked row \u2014 it fell back to\n the host's 34px pill while its siblings got 48px. The root cells are\n role=menuitem, the options role=menuitemradio; the class forms stay for\n older hosts. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"] [role=\"menuitem\"],\n body > div[role=\"menu\"][data-zen-sheet=\"perm\"] [role=\"menuitem\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [role=\"menuitem\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [role=\"menuitemradio\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [class$=\"_option\"],\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [class$=\"_cell\"] {\n min-height: 48px !important;\n border-radius: 12px !important;\n font-size: 15px !important;\n }\n /* --- 4a. third-party composer entries live in the model sheet ---\n effects/model-sheet-extras.ts parks the `conversation.input.right`\n container inside the model menu while that menu is open and puts it back\n when it closes; these rules are the two halves of that trip.\n\n In the row: hidden, but ONLY once the effect has marked the container as\n one it manages. Keying the hide off the marker rather than off \"not\n currently parked\" is what makes the failure direction safe \u2014 if the\n effect declines to act (nothing in the slot but the vision toggle, so\n nothing worth moving) or never runs at all, the marker is absent, this\n rule does not match, and the controls stay exactly where the host put\n them. The inverse spelling hid them in the row whenever they were not in\n the sheet, which in those same cases left them reachable from neither\n place (caught in review, 2026-09-06).\n Hiding by ancestor rather than by a display rule on the container itself\n is what lets the parked copy style itself freely \u2014 same element, two\n homes, two rule sets. `!important` because the slot container carries an\n inline `display: contents`. */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.right\"][data-zen-sheet-extras] {\n display: none !important;\n }\n /* Parked in the sheet: a column of full-width rows in the sheet's own\n language, so a relocated control reads as a sibling of \u6A21\u578B and \u63A8\u7406\u7B49\u7EA7\n rather than a chip that wandered in. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] {\n display: flex !important;\n flex-direction: column !important;\n gap: 2px !important;\n width: 100% !important;\n }\n /* Undo the row's compaction on every parked control: back to full width and\n the 48px the sheet's own cells use. `> * > *` reaches both shapes without\n naming either plugin \u2014 subscriptions wraps its trigger in a positioning\n div, vision-router puts its button straight in the slot. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] > *,\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] > * > button {\n width: 100% !important;\n max-width: none !important;\n min-width: 0 !important;\n justify-content: flex-start !important;\n }\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] button {\n box-sizing: border-box !important;\n min-height: 48px !important;\n height: auto !important;\n padding: 0 12px !important;\n gap: 10px !important;\n border-radius: 12px !important;\n border: none !important;\n background: transparent !important;\n font-size: 15px !important;\n /* Both plugins center their chip label, which is right for a pill in the\n row and wrong for a full-width row: the sheet's own cells start their\n label at the left edge. justify-content covers the flex triggers,\n text-align the ones that are not flex containers. */\n text-align: left !important;\n justify-content: flex-start !important;\n }\n /* Order comes from the row (compat.css.ts puts the vision toggle at 5 so it\n parks beside the context ring), and that rule still matches in here on\n \u2264 0.1.2 \u2014 the inline sheet is a DOM descendant of the composer bar even\n though it paints as a fixed layer (the 0.1.5 body portal is not, where\n this is a harmless no-op). Reset it so the sheet follows DOM order\n instead of inheriting a decision that was about a different layout. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] > * {\n order: 0 !important;\n }\n /* The vision toggle is squeezed to a 28px icon in the row (compat.css.ts);\n in the sheet it is a row, so its label comes back and the icon leads. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] [data-vision-router-mode-toggle] > span {\n display: inline !important;\n }\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] [data-vision-router-mode-toggle] > svg:first-of-type {\n width: 18px !important;\n height: 18px !important;\n }\n /* The speed chip opens a menu of its own, anchored `bottom: 100%` to its\n trigger. Inside the sheet \u2014 which is `overflow: hidden` so its rounded\n top clips its content \u2014 that menu would be cut off. Same escape section 4\n uses for the host's two menus: position: fixed leaves the clip behind\n (the sheet sets `transform: none`, so nothing between here and the\n viewport captures fixed positioning) and it lands as its own sheet on top\n of this one. */\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] [role=\"menu\"] {\n position: fixed !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n top: auto !important;\n margin: 0 !important;\n width: 100% !important;\n min-width: 0 !important;\n max-width: none !important;\n max-height: min(70dvh, 520px) !important;\n box-sizing: border-box !important;\n border-radius: 16px 16px 0 0 !important;\n border-bottom: none !important;\n padding: 8px 8px calc(8px + var(--mnav-sab)) !important;\n /* Above the model sheet it sits on (60, section 4), below the session-info\n sheet's 70 (info.css.ts) \u2014 it is a child of the model sheet, not a peer\n of the page's own layers. */\n z-index: 61 !important;\n box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;\n }\n :is([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"], body > [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) > [data-zen-sheet-extras] [role=\"menu\"] [role=\"menuitemradio\"] {\n min-height: 48px !important;\n border-radius: 12px !important;\n font-size: 15px !important;\n }\n\n /* --- 4b. the official scroll-to-bottom button must not poke through the\n sheet (real-device follow-up, 2026-08-17) ---\n ChatView's own \"jump to latest\" button (aria-label t(\"chat.toBottom\"))\n is `position: sticky; z-index: 8` inside the message column, not\n `position: fixed` \u2014 it never escapes to the same top-level stacking\n context our sheets get promoted to, so raising the sheet's z-index\n further does nothing (measured live: it still rendered on top at\n z-index 60 vs 8). Rather than chase engine-specific stacking-context\n semantics (Chromium and WebKit do not always agree here \u2014 see AGENTS.md\n CDP/document.hidden lesson for another instance of that), this hides\n the button outright while either sheet is open and lets it reappear on\n close: a plain `display: none` behind a live `:has()` read is correct\n regardless of which stacking rules the engine happens to apply.\n Selector: no hashed classes (dsh-client-ui-conversation lib/client.js,\n verified 2026-08-17) \u2014 `data-chat-flow` is the one stable attribute on\n the message column, and the button's wrapper is its only sibling\n (ChatView only ever renders the column and, conditionally, this one\n slot), so the adjacent-sibling combinator pins it precisely. */\n body:has([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_tools\"] > [class$=\"_modes\"] [role=\"menu\"]) [data-chat-flow] + div,\n body:has([data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] > [class$=\"_row\"] > [class$=\"_trailing\"] > [class$=\"_standardControls\"] > [data-slot=\"conversation.input.model\"] > [class$=\"_root\"] > [class$=\"_menu\"]) [data-chat-flow] + div,\n body:has(> [id$=\"-menu\"][class$=\"_menu\"]:is([role=\"menu\"], [role=\"group\"])) [data-chat-flow] + div,\n /* T70: the portaled permission menu (marked by effects/permission-sheet.ts). */\n body:has(> div[role=\"menu\"][data-zen-sheet=\"perm\"]) [data-chat-flow] + div {\n display: none !important;\n }\n /* --- 5. input box: two lines minimum, five lines maximum ---\n The official autosizer drives the box off the hidden mirror's height, so\n a min-height on the mirror IS the min-height of the field; the scroll cap\n rides the official custom property. 52px = 2 x 24px line box + 4px pad,\n 124px = 5 lines. The hero card keeps its own one-line collapse (misc.css\n pins _scroll/_grow while the placeholder shows). */\n [data-slot=\"conversation.composer.bar\"] [class$=\"_card\"] [class$=\"_mirror\"] {\n min-height: 52px !important;\n }\n [class$=\"_composerSeat\"] {\n --dsh-composer-text-max-height: 124px;\n }\n /* Pull the text in from the card's left border. Upstream insets the editing\n host by 16px while the control row below it starts 8px in (row padding,\n section 1), so the placeholder sat noticeably further out than the\n buttons under it and the field read as over-padded on a phone (reported\n 2026-09-06). 12px squares it with the 12px of clear space on the right,\n giving the text a symmetric box, and closes most of the gap with the\n button row. Anchored on the editing host's own contract attribute rather\n than a hashed class. */\n [data-slot=\"conversation.composer.bar\"] [data-composer-input] {\n padding-left: 12px !important;\n }\n\n /* --- 6. no divider above OR below the message list ---\n Instead of a rule the messages butt against, they fade near both edges.\n This used to be ONE mask-image on the message scroller; iOS WebKit\n turned that into fog over the header (user report 2026-08-23): a mask\n on a scroll container is applied in CONTENT coordinates there (the fade\n bands scroll away with the messages) and, worse, it breaks the\n container's own overflow clipping \u2014 content scrolled out of view keeps\n painting, straight over the header and status bar. Engine bug with no\n iOS escape hatch (every iOS browser is WebKit), so the fades moved off\n the scroller entirely:\n\n - fade-IN under the header: the header's own ::after, restyled into a\n 20px gradient strip \u2014 styles/header.css.ts (\"no header bottom line\").\n - fade-OUT above the composer: nothing of ours. The official composer\n seat already carries `background: linear-gradient(transparent 0,\n bg 36px)` on its sticky self (dsh-client-ui-conversation lib/\n client.js .wSkVaW_composerSeat) and that is the entire visible\n effect; the mask's own bottom band landed inside the safe-area\n padding below the card and painted nothing. If the bottom edge ever\n reads harsh, hang a ::before strip off the seat the same way the\n header does \u2014 do NOT put a mask back on the scroller.\n\n Painted strips instead of a mask is a fair trade only because the page\n background is one flat color \u2014 which it is (--dsw-alias-bg-base). */\n [class$=\"_composerSeat\"],\n [class$=\"_composerStack\"] {\n border-top: none !important;\n }\n\n /* --- 7. dock entries become mini chips above the input card ---\n conversation.input.dock is display:contents (inline style), so its\n entries are stacked rows of the composer column. Forcing the slot itself\n to flex turns them into ONE scrollable chip line outside and above the\n card. Written against the slot, not against any particular plugin's chip\n (the git branch chip and the todo panel are third-party and may not be\n installed at all). */\n [data-slot=\"conversation.input.dock\"] {\n display: flex !important;\n flex-flow: row nowrap !important;\n align-items: center !important;\n gap: 6px !important;\n min-width: 0 !important;\n margin: 0 16px !important;\n overflow-x: auto !important;\n overflow-y: hidden !important;\n scrollbar-width: none !important;\n /* The dock is itself a flex ITEM of the composer stack, and the\n new-session hero pins that stack to a fixed height. overflow-x:auto\n above makes this a scroll container, which drops a flex item's\n automatic minimum size to 0 \u2014 so the hero squeezed the dock to ~5px\n and the attachment chips slid under the card drawn after it (only the\n top sliver of a thumbnail showed; reported from a real phone,\n 2026-08-26). Refuse the squeeze: natural height, the stack centers a\n few px lower instead. In-chat the column is not height-pinned and this\n is a no-op. */\n flex-shrink: 0 !important;\n }\n [data-slot=\"conversation.input.dock\"]::-webkit-scrollbar {\n display: none !important;\n }\n [data-slot=\"conversation.input.dock\"] > * {\n flex: 0 0 auto !important;\n max-width: 70% !important;\n min-height: 26px !important;\n max-height: 26px !important;\n border-radius: 999px !important;\n font-size: 11.5px !important;\n line-height: 18px !important;\n overflow: hidden !important;\n }\n /* The git-graph chip carries a 34px tablet target from misc.css; that\n selector is more specific, so restate it at the phone breakpoint. */\n [data-slot=\"conversation.input.dock\"] [data-gitgraph-chip-anchor] [data-gitgraph-chip] {\n min-height: 26px !important;\n padding: 0 10px !important;\n font-size: 11.5px !important;\n }\n /* --- 7a. the native to-do card opts OUT of the 26px pill cage ---\n conversation.input.dock is not only a chip rail: DSH's own TodoPanel\n (dsh-client-ui-conversation) registers here, and it is an expandable\n card, not a pill \u2014 tapping its header renders the to-do list INSIDE\n itself. The cage's max-height:26px + overflow:hidden left that list\n rendered but clipped to nothing, so on a phone the bar looked dead:\n aria-expanded flipped, 8 rows mounted at 216px, and the card still\n painted 26px tall (measured live, 2026-08-20). Give it the official\n geometry back \u2014 full row, natural height, 12px radius. Its list caps\n itself at 180px with its own scroller, so this cannot run into the\n composer. Anchored on the official data-testid, a stable contract\n marker rather than a hashed class name. */\n [data-slot=\"conversation.input.dock\"] > [data-testid=\"todo-panel\"] {\n /* A whole line to itself. It shared line 0 with the pills until the goal\n bar turned up (2026-09-06): two full-width things on one nowrap line\n meant the to-do header shrank to a stub and the goal bar ran off the\n right edge. Both are status strips you read at a glance, so they stack\n \u2014 see the line policy in 7c. */\n flex: 1 0 100% !important;\n order: 1 !important;\n min-width: 0 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n overflow: visible !important;\n border-radius: 12px !important;\n /* The cage shrinks dock text to 11.5px/18px; the card's rows inherit it\n and turn cramped. Restore the official 13px body scale. */\n font-size: 13px !important;\n line-height: normal !important;\n }\n\n /* --- 7b. attachment chips (S7.1) ---\n Our own dock entry opts OUT of the 26px pill cage above: an image chip is\n a 48px tile. The row sits tight under the card's top edge, so the chips\n read as part of the composer rather than as a floating strip.\n\n It also takes the LAST line of its own (line policy in 7c): a 48px tile\n beside the to-do chip on one nowrap line squeezed that chip down to an\n unreadable stub, with a long file name pushing the rest off-screen\n (reported 2026-08-22). Its own chips wrap rather than overflow, so\n several attachments stack instead of scrolling out of reach. */\n [data-slot=\"conversation.input.dock\"] > [data-mobile-nav=\"attach-chips\"] {\n display: flex !important;\n flex: 1 0 100% !important;\n flex-wrap: wrap !important;\n order: 4 !important;\n align-items: center !important;\n gap: 6px !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n border-radius: 0 !important;\n overflow: visible !important;\n margin-bottom: -2px;\n }\n\n /* --- 7b2. the T34 remote-status banner opts OUT of the cage ---\n Same class of entry as the to-do card (7a): a full-width status strip,\n not a pill. dsh-zen-remote's remote banner registers into this dock at\n order 15 when a remote session is offline or closed (T34) \u2014 the 26px\n cage would clip the copy to one unreadable sliver. A whole line of its\n own, natural height. */\n [data-slot=\"conversation.input.dock\"] > [data-zen-remote=\"remote-banner\"] {\n flex: 1 0 100% !important;\n order: 2 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n overflow: visible !important;\n border-radius: 12px !important;\n font-size: 12px !important;\n line-height: 18px !important;\n }\n\n /* --- 7c. the queue strip opts OUT of the cage, on a line of its own ---\n Third entry that is not a pill. conversation.input.dock is a `list` slot\n and DSH's own QueueDock registers into it at order 20 (id `queue`,\n dsh-client-ui-conversation) \u2014 a 36px row per queued message, or a\n collapsible \"N \u6761\u6392\u961F\u6D88\u606F\" header once there is more than one. The 26px\n cage above clipped it to a sliver: on a phone a queued message showed as\n a grey stub poking out from behind the input card, unreadable and with\n its edit / delete / send buttons cut off (reported 2026-08-22, same\n failure the to-do card hit in 7a).\n\n Two things are restored. Geometry: the official row height back, and the\n dock's own composer-column sizing dropped \u2014 its width/max-width are\n calc()s over the composer card variables and its bottom margin is\n negative by design (upstream tucks the strip UNDER the card), none of\n which survives being a flex item of our chip rail.\n\n Layout \u2014 the line policy for the whole rail, now that three things want\n room on it. The pills (the to-do card, a branch chip, whatever else\n registers) keep order 0 and share the top line, still scrolling\n horizontally. The queue takes the next line (order 1). The attachment\n chips take the last one (order 2, 7b), so what the user is about to send\n sits closest to the input it will be sent from. The rail only wraps\n while one of the two full-width entries exists, so a session with\n neither keeps the original single scrolling chip line. */\n [data-slot=\"conversation.input.dock\"]:has(> [data-queue-dock], > [data-mobile-nav=\"attach-chips\"], > [data-testid=\"todo-panel\"], > [data-goal-bar]) {\n flex-wrap: wrap !important;\n }\n [data-slot=\"conversation.input.dock\"] > [data-queue-dock] {\n flex: 1 0 100% !important;\n order: 3 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n margin: 0 !important;\n padding: 0 !important;\n border-radius: 0 !important;\n overflow: visible !important;\n font-size: 13px !important;\n line-height: normal !important;\n }\n /* dsh-client-ui-goal's status strip (its own `data-goal-bar` marker; it\n registers into this dock at order 10). Fourth thing here that is not a\n pill: a row carrying the objective text plus pause / edit / delete\n buttons, so the 26px cage clipped its buttons off the right edge and the\n nowrap line pushed what was left off screen (reported 2026-09-06). Same\n treatment the queue gets \u2014 a line of its own, official geometry back. */\n [data-slot=\"conversation.input.dock\"] > [data-goal-bar] {\n flex: 1 0 100% !important;\n order: 2 !important;\n max-width: none !important;\n min-height: 0 !important;\n max-height: none !important;\n border-radius: 12px !important;\n overflow: visible !important;\n font-size: 13px !important;\n line-height: normal !important;\n }\n\n /* The panel is the dock's only child; upstream rounds its top corners only\n and drops the bottom border, because there it is half-hidden behind the\n card. On its own line it is a free-standing strip, so close it up. */\n [data-slot=\"conversation.input.dock\"] > [data-queue-dock] > * {\n border-radius: 12px !important;\n }\n [data-slot=\"conversation.input.dock\"] > [data-queue-dock] > *::after {\n border-bottom: 1px solid var(--dsw-alias-border-l1) !important;\n }\n\n [data-mobile-nav=\"attach-chip\"] {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n max-width: 62vw;\n background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12));\n color: var(--dsw-alias-label-primary, inherit);\n }\n [data-mobile-nav=\"attach-chip\"][data-kind=\"file\"] {\n height: 26px;\n padding: 0 4px 0 8px;\n border-radius: 999px;\n font-size: 11.5px;\n line-height: 18px;\n }\n /* An image chip is the thumbnail: no name line, the filename lives in the\n title attribute (and in the draft text right below). */\n [data-mobile-nav=\"attach-chip\"][data-kind=\"image\"] {\n width: 48px;\n height: 48px;\n padding: 0;\n border-radius: 10px;\n overflow: hidden;\n }\n [data-mobile-nav=\"attach-chip-art\"] {\n position: relative;\n display: grid;\n place-items: center;\n flex: none;\n overflow: hidden;\n }\n [data-kind=\"file\"] > [data-mobile-nav=\"attach-chip-art\"] {\n width: 16px;\n height: 16px;\n opacity: .7;\n }\n [data-kind=\"image\"] > [data-mobile-nav=\"attach-chip-art\"] {\n width: 100%;\n height: 100%;\n }\n /* The <img> is stacked over the paperclip, not swapped for it: a format the\n engine cannot decode (HEIC outside WebKit) simply paints nothing and the\n icon shows through \u2014 no onError state to carry. */\n [data-mobile-nav=\"attach-chip-art\"] img {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, .18));\n }\n [data-mobile-nav=\"attach-chip-name\"] {\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n }\n [data-mobile-nav=\"attach-chip-remove\"] {\n display: grid;\n place-items: center;\n flex: none;\n width: 18px;\n height: 18px;\n padding: 0;\n border: none;\n border-radius: 999px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n touch-action: manipulation;\n }\n /* On a tile the \u00D7 floats in the corner over the picture, so it needs its own\n ground to stay legible against an arbitrary photo. */\n [data-kind=\"image\"] > [data-mobile-nav=\"attach-chip-remove\"] {\n position: absolute;\n top: 2px;\n right: 2px;\n background: rgba(0, 0, 0, .55);\n color: #fff;\n }\n [data-mobile-nav=\"attach-chip-remove\"]:active {\n transform: scale(.9);\n }\n\n /* --- 8. the official stats strip leaves the composer ---\n Its data moves into the session info card (S4). The row is the composer\n dock's own `_root` entry; the slot itself stays live for later entries. */\n [data-slot=\"conversation.composer.dock\"] > [class$=\"_root\"],\n [data-slot=\"conversation.composer.dock\"] > [data-composer-stat] {\n display: none !important;\n }\n\n /* --- 9. the attachment button (S7; its file input is hidden at the top of\n this file, at every width) --- */\n [data-mobile-nav=\"attach\"] {\n position: relative;\n width: 28px !important;\n height: 28px !important;\n flex: none !important;\n display: grid !important;\n place-items: center !important;\n padding: 0 !important;\n border: none !important;\n border-radius: 999px !important;\n background: var(--dsw-specific-selector, rgba(127, 127, 127, .12));\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n touch-action: manipulation;\n }\n [data-mobile-nav=\"attach\"]:active {\n transform: scale(.94);\n transition: transform .12s;\n }\n /* Busy: a ring sweeps around the paperclip. Drawn with a conic gradient in\n a pseudo-element rather than a spinner node, so the official React tree\n around us has nothing extra to re-render (same reasoning as the model\n pill's mask icon, S3.1). */\n [data-mobile-nav=\"attach\"][data-busy]::after {\n content: \"\";\n position: absolute;\n inset: -2px;\n border-radius: 999px;\n background: conic-gradient(from 0deg, transparent 0 65%, currentColor 100%);\n mask: radial-gradient(closest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));\n -webkit-mask: radial-gradient(closest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));\n animation: mnav-attach-spin .9s linear infinite;\n pointer-events: none;\n }\n @keyframes mnav-attach-spin {\n to { transform: rotate(1turn); }\n }\n @media (prefers-reduced-motion: reduce) {\n [data-mobile-nav=\"attach\"][data-busy]::after {\n animation-duration: 3s;\n }\n }\n /* Failure note: one line above the button, tap-to-dismiss. Sits on the\n composer card (z 2) rather than in an overlay layer \u2014 nothing here needs\n to clear the permission/model sheets. */\n [data-mobile-nav=\"attach-error\"] {\n position: absolute;\n bottom: calc(100% + 6px);\n left: 0;\n z-index: 2;\n max-width: 62vw;\n padding: 5px 8px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, .82));\n color: var(--dsw-alias-label-primary, #fff);\n box-shadow: 0 2px 8px rgba(0, 0, 0, .24);\n }\n\n /* --- 9. home-indicator clearance (S4.1, 2026-08-17) ---\n Owned HERE, not by the gateway half. That plugin used to carry\n [data-slot=\"conversation.composer\"] { padding-bottom: max(env(safe-area-inset-bottom), 8px) }\n in both pwa/app.css and the gateway's inline DEVICE_CSS. Both were\n INERT and had always been: that slot element is `display: contents`\n (measured 2026-08-17 \u2014 the slot wrapper generates no box, so padding on\n it is discarded), which is why raising it never moved anything. The rule\n is deleted on the PWA side and restated here on an element that actually\n lays out, reading --mnav-sab so ?mobile-nav-inset=54,34 can regress it\n off-device (env() is hard 0 on desktop \u2014 the whole reason S2.1 exists).\n\n Target: the card should look EQUALLY inset on all four sides. This same\n element carries the card's side inset (`padding: 0 16px` officially,\n measured 16px left and 16px right at 390px), so the bottom gap is simply\n capped at that same 16px. Clearing the FULL 34px inset \u2014 the naive\n max(sab, 8px) \u2014 is what read as \"too thick\": it is more than double the\n side margin, so the card looked shoved up off the bottom edge.\n\n clamp() says all three requirements at once:\n - floor 8px -> a device with no home indicator (sab: 0) keeps the\n official 8px exactly, so this is a strict no-op there;\n - track sab -> a shallower inset than 16px is honoured as-is;\n - cap 16px -> a full-size indicator (sab: 34) lands on 16px, equal\n to the side inset, which is the look being asked for.\n Keep the 16px in step with the side padding above if that ever changes;\n that equality IS the spec here, not a coincidence. */\n [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] {\n padding-bottom: clamp(8px, var(--mnav-sab), 16px) !important;\n }\n\n /* S10 keyboard avoid: effects/keyboard-avoid.ts mirrors the band of the\n layout viewport hidden behind the software keyboard into --mnav-kb-lift\n and stamps the html attribute while (and only while) it is non-zero \u2014\n a resting transform, even translateY(0), would silently become the\n containing block for any fixed-position descendant of the bar. In every\n environment where the browser's own focus-reveal works the attribute is\n simply never set.\n The target is the slot's _root CHILD, not the slot wrapper: the wrapper\n is display:contents \u2014 it generates no box, so a transform on it is a\n silent no-op (found live on-device via CDP, 2026-08-21; the variable and\n attribute were set and nothing moved). */\n html[data-mnav-kb] [data-slot=\"conversation.composer.bar\"] > [class$=\"_root\"] {\n transform: translateY(calc(-1 * var(--mnav-kb-lift, 0px)));\n }\n}\n\n/* The attachment button and its preview row only exist for the phone shell.\n Both render at every width (the slots are not breakpoint-aware), so both\n need hiding here \u2014 the dock-row rules that shape the chips live in the\n < 768px block and would otherwise leave a bare, unstyled chip line in the\n desktop composer. */\n@media (min-width: 768px) {\n [data-mobile-nav=\"attach\"],\n [data-mobile-nav=\"attach-chips\"] {\n display: none !important;\n }\n}\n";
2
2
  //# sourceMappingURL=composer.css.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"composer.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/composer.css.ts"],"names":[],"mappings":"AAkDA,eAAO,MAAM,YAAY,mniDAi3BxB,CAAA"}
1
+ {"version":3,"file":"composer.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/composer.css.ts"],"names":[],"mappings":"AAkDA,eAAO,MAAM,YAAY,yriDAk3BxB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"intercept.d.ts","sourceRoot":"","sources":["../../src/intercept.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"intercept.d.ts","sourceRoot":"","sources":["../../src/intercept.ts"],"names":[],"mappings":"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"}
@@ -99,7 +99,11 @@ export declare class RelayError extends Error {
99
99
  code: string;
100
100
  status?: number;
101
101
  reason?: string;
102
- constructor(code: string, message?: string, httpStatus?: number, reason?: string);
102
+ /** The host error's sanitized `details` (invoke path): the UI maps
103
+ * `details.reason` codes like `MODEL_DOES_NOT_SUPPORT_IMAGES` onto a
104
+ * user-facing line — absent details stay undefined. */
105
+ details?: Record<string, unknown>;
106
+ constructor(code: string, message?: string, httpStatus?: number, reason?: string, details?: Record<string, unknown>);
103
107
  }
104
108
  export interface CreateRelayClientOptions {
105
109
  /** The normalized server address (no trailing slash), read live per
@@ -1 +1 @@
1
- {"version":3,"file":"relay-client.d.ts","sourceRoot":"","sources":["../../src/relay-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"relay-client.d.ts","sourceRoot":"","sources":["../../src/relay-client.ts"],"names":[],"mappings":"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"}