Move Qt/Tauri/full Web SPA under archive/, ship hermit as CLI/TUI plus libcstcore and cst-webui/1 HTTP+SSE (turn lifecycle hooks, capabilities). Co-authored-by: Cursor <cursoragent@cursor.com>
637 lines
20 KiB
CSS
637 lines
20 KiB
CSS
.root {
|
||
position: relative; /* positioning context for slot-owned absolute chrome */
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
min-width: 0;
|
||
background: var(--dsw-alias-bg-base);
|
||
}
|
||
|
||
/* 76px with its rule: the height of the Sidebar's tab strip and header row
|
||
(ui-dockkit `.tabStrip` 38px + the pane header's 38px), so the two rules
|
||
meet at the column edge. The rows add up to it — 10px top inset, the 30px
|
||
title row, the tab strip's 10px margin, and a 25px view row. When the views
|
||
and the More launcher do not fit on one row, the row wraps and the header
|
||
grows past 76px. */
|
||
.header {
|
||
display: grid;
|
||
grid-template-columns: auto minmax(0, 1fr);
|
||
flex: none;
|
||
box-sizing: border-box;
|
||
min-height: 76px;
|
||
padding: 10px 28px 0 20px;
|
||
border-bottom: 0.5px solid var(--dsw-alias-border-l3);
|
||
}
|
||
|
||
.header:where(:not(:has(.tabs))) {
|
||
min-height: 0;
|
||
padding-bottom: 10px;
|
||
}
|
||
|
||
/* Blank Sessions retain navigation controls without conversation title or tabs. */
|
||
.headerBlank {
|
||
min-height: 0;
|
||
padding-bottom: 0;
|
||
border-bottom: none;
|
||
}
|
||
|
||
.headerBlank .headerCorner {
|
||
margin-left: auto;
|
||
}
|
||
|
||
/* Only the macOS titlebar needs reserved drag space before a Session exists. */
|
||
:global(html:not([data-platform='darwin'])) .headerSessionless {
|
||
padding-top: 0;
|
||
}
|
||
|
||
:global(html:not([data-platform='darwin'])) .headerSessionless .titleRow {
|
||
min-height: 0;
|
||
}
|
||
|
||
/* macOS hiddenInset titlebar: the header block owns the window drag over its
|
||
own box — title row plus the view tab strip, so the draggable geometry is the
|
||
header's geometry and no frame band has to match its height. The header marks
|
||
itself `data-window-drag` (ConversationHeader); below it the title-row clusters
|
||
subtract their whole boxes, and individual controls are covered by the base.css
|
||
interactive-element rule. A covering overlay lives beside #root
|
||
(ui-settings-general), which puts it after this row in document order. */
|
||
:global([data-platform='darwin']) .headerLeading,
|
||
:global([data-platform='darwin']) .headerActions,
|
||
:global([data-platform='darwin']) .headerUtilities,
|
||
:global([data-platform='darwin']) .headerCorner {
|
||
-webkit-app-region: no-drag;
|
||
}
|
||
|
||
.titleRow {
|
||
grid-column: 2;
|
||
/* Anonymous inline-size container (CSS modules hash container-names per
|
||
module, so occupants match the nearest container): header occupants such
|
||
as the preset label and slot actions shed their text at row-width
|
||
breakpoints. The minmax(0, 1fr) column sizes the row, so inline-size
|
||
containment cannot collapse it. */
|
||
container-type: inline-size;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0;
|
||
min-height: 30px;
|
||
/* Clears the frame's window-chrome band (traffic lights + shell.leading
|
||
controls, ui-layout AppFrame) when it is published; the header already
|
||
pads 20px of it. Zero everywhere else (the fallback makes the max
|
||
negative). */
|
||
padding-inline-start: max(0px, calc(var(--dsh-frame-leading-clearance, 0px) - 20px));
|
||
}
|
||
|
||
/* Header actions do not shrink, so on a narrow row they overflow the cluster.
|
||
The cluster clips them, hit testing included, so the next control keeps
|
||
every click. The trailing padding spans the gap before that control and
|
||
holds the fade: content that fits ends before it, and only overflowing
|
||
content reaches it. The 4px block and start padding, offset by negative
|
||
margins, keeps focus rings inside the clip. `clip` rather than `hidden`
|
||
keeps the cluster from becoming a scroll container that focus could scroll. */
|
||
.titleCluster {
|
||
display: flex;
|
||
flex: 1;
|
||
align-items: center;
|
||
gap: 10px;
|
||
min-width: 0;
|
||
--title-cluster-fade: 20px;
|
||
margin: -4px calc(-1 * var(--title-cluster-fade)) -4px -4px;
|
||
padding: 4px var(--title-cluster-fade) 4px 4px;
|
||
overflow: clip;
|
||
mask-image: linear-gradient(to left, transparent calc(0.3 * var(--title-cluster-fade)), #000 var(--title-cluster-fade));
|
||
}
|
||
|
||
/* Without utilities the corner's 8px margin is the gap the fade spans. */
|
||
.titleCluster:has(+ .headerUtilities:empty) {
|
||
--title-cluster-fade: 8px;
|
||
}
|
||
|
||
/* The auto grid column collapses with an empty or CSS-hidden occupant.
|
||
The occupant owns its trailing gap so hidden controls reserve no space. */
|
||
.headerLeading {
|
||
grid-column: 1;
|
||
grid-row: 1;
|
||
display: flex;
|
||
flex: none;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.crumbs {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.crumbSeg {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.crumbSep {
|
||
/* figma: "/" separators are 14px caption gray (75:7903), one tint lighter than crumb text. */
|
||
color: var(--dsw-alias-label-caption);
|
||
font-size: 14px;
|
||
line-height: 20px;
|
||
}
|
||
|
||
.crumb {
|
||
display: inline-block;
|
||
max-width: 220px;
|
||
overflow: hidden;
|
||
padding: 4px 8px;
|
||
border: none;
|
||
border-radius: var(--dsw-radius-md);
|
||
background: transparent;
|
||
font-size: 14px;
|
||
line-height: 20px;
|
||
color: var(--dsw-alias-label-tertiary);
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.crumbSubagent {
|
||
font-size: 12px;
|
||
line-height: 18px;
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
button.crumb:hover {
|
||
background: var(--dsw-alias-interactive-bg-hover);
|
||
}
|
||
}
|
||
|
||
.crumbCurrent {
|
||
font-weight: 500;
|
||
color: var(--dsw-alias-label-primary);
|
||
cursor: default;
|
||
}
|
||
|
||
.headerActions {
|
||
display: flex;
|
||
flex: none;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.headerUtilities {
|
||
display: flex;
|
||
flex: none;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-left: 20px;
|
||
}
|
||
|
||
.headerUtilities:empty {
|
||
display: none;
|
||
}
|
||
|
||
/* The far-right corner seat reaches 16px into the header's 28px right padding,
|
||
so its control sits past the utilities' edge; it is laid out only while its
|
||
occupant renders something. Its left margin equals the utilities' 8px gap so
|
||
the header's trailing controls space evenly. */
|
||
.headerCorner {
|
||
display: flex;
|
||
flex: none;
|
||
align-items: center;
|
||
margin-left: 8px;
|
||
margin-right: -16px;
|
||
}
|
||
|
||
.headerCorner:empty {
|
||
display: none;
|
||
}
|
||
|
||
/* The tab row sits above the header paint so the active bar covers its rule. */
|
||
.tabs {
|
||
grid-column: 1 / -1;
|
||
position: relative;
|
||
z-index: 1;
|
||
display: flex;
|
||
gap: 16px 20px;
|
||
width: 100%;
|
||
min-width: 0;
|
||
box-sizing: border-box;
|
||
align-items: flex-start;
|
||
flex-wrap: wrap;
|
||
margin-top: 10px;
|
||
padding-left: 8px;
|
||
}
|
||
|
||
@media (max-width: 767px) {
|
||
:global(html:not([data-platform])) .tabs {
|
||
padding-left: 0;
|
||
}
|
||
}
|
||
|
||
.viewTabs {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 32px;
|
||
min-width: 0;
|
||
min-height: 25px;
|
||
}
|
||
|
||
/* The 16px text line retains 9px below it. The press target extends 10px
|
||
above the text into the strip's top margin and 10px to each side, matching
|
||
the inline padding of the Sidebar tab chips (ui-dockkit `.tab`); negative
|
||
margins keep the layout box at its 25px row and text width. The active bar
|
||
spans the text and reaches 1px past the tab box to sit over the header
|
||
rule. */
|
||
.tab {
|
||
--dsh-tab-hit-inline: 10px;
|
||
|
||
position: relative;
|
||
box-sizing: border-box;
|
||
min-height: 35px;
|
||
margin: -10px calc(-1 * var(--dsh-tab-hit-inline)) 0;
|
||
padding: 10px var(--dsh-tab-hit-inline) 9px;
|
||
border: none;
|
||
background: transparent;
|
||
font-size: 13px;
|
||
line-height: 16px;
|
||
font-weight: 500;
|
||
color: var(--dsw-alias-label-tertiary);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.tab::after {
|
||
content: '';
|
||
position: absolute;
|
||
right: var(--dsh-tab-hit-inline);
|
||
bottom: -1px;
|
||
left: var(--dsh-tab-hit-inline);
|
||
height: 2px;
|
||
border-radius: 2px;
|
||
background: transparent;
|
||
}
|
||
|
||
/* Selected tab is blue, not ink: brand-primary resolves to neutral black in
|
||
this token sheet, so the selected state rides the business blue — the
|
||
nearest semantic token that stays blue in both themes. */
|
||
.tabActive {
|
||
color: var(--dsw-alias-state-business-primary);
|
||
}
|
||
|
||
.tabActive::after {
|
||
background: var(--dsw-alias-state-business-primary);
|
||
}
|
||
|
||
/* The More launcher centers its 22px box on the tab's 16px text line. */
|
||
.viewsMoreAnchor {
|
||
flex: none;
|
||
}
|
||
|
||
.viewsMore {
|
||
width: 22px;
|
||
height: 22px;
|
||
margin-top: -3px;
|
||
padding: 0;
|
||
color: var(--dsw-alias-label-secondary);
|
||
}
|
||
|
||
.viewsMoreActive { color: var(--dsw-alias-state-business-primary); }
|
||
|
||
.tabLabel { display: block; max-width: 88px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
|
||
.viewsMore[aria-expanded='true'] {
|
||
background: var(--dsw-alias-interactive-bg-hover);
|
||
}
|
||
|
||
.viewsMoreMenu {
|
||
width: 288px;
|
||
max-width: calc(100vw - 24px);
|
||
}
|
||
|
||
.viewArea {
|
||
display: flex;
|
||
flex: 1;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
}
|
||
|
||
/* Width handles: 10px col-resize strips beside the transcript, absolute in
|
||
.body (NOT the scrollport: an absolute strip there would extend the
|
||
scrollable range; NOT .root: a strip from the column top would paint its
|
||
glow through the header's transparent background). The inner edge sits
|
||
24px outside the content column and the strip extends 10px outward; the
|
||
outer edge is clamped to keep a 24px safe zone against the
|
||
column edges (sidebar side and scrollbar side stay drag-free). When the
|
||
margin cannot fit inset + strip + safe zone the computed width goes
|
||
negative and the strip resolves to zero — no mispositioned hit area on
|
||
narrow columns. Known half-scrollbar (~4px) centering offset vs the
|
||
gutter-reserved content box is accepted: it consumes part of the 10px
|
||
strip but remains within it. */
|
||
.widthHandle {
|
||
position: absolute;
|
||
top: 0;
|
||
bottom: 0;
|
||
/* Concrete overflow content establishes z-index 1, while the sticky
|
||
composer remains above both layers. */
|
||
z-index: 0;
|
||
width: min(
|
||
10px,
|
||
calc((100% - var(--dsh-chat-content-width)) / 2 - 24px - 24px)
|
||
);
|
||
cursor: col-resize;
|
||
}
|
||
|
||
.widthHandle[data-side='left'] {
|
||
right: calc(50% + var(--dsh-chat-content-width) / 2 + 24px);
|
||
}
|
||
|
||
.widthHandle[data-side='right'] {
|
||
left: calc(50% + var(--dsh-chat-content-width) / 2 + 24px);
|
||
}
|
||
|
||
/* The glow: a short 2px bar following the captured pointer's Y, centered in the strip
|
||
(29px from the content column), not a full-height rule — a 16px solid core
|
||
centered on the pointer
|
||
(published during drag as --dsh-width-handle-pointer-y), fading to
|
||
transparent over 28px each side (72px total). A solid CORE, not a single
|
||
solid stop: a lone peak washes out over the fade. The resting scrollbar
|
||
tint keeps this secondary affordance quiet in both themes. Before the first
|
||
drag move the fallback centers the glow at 50% height; hover leaves its position unchanged. */
|
||
.widthHandle::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 2px;
|
||
border-radius: 2px;
|
||
background: linear-gradient(
|
||
to bottom,
|
||
transparent calc(var(--dsh-width-handle-pointer-y, 50%) - 36px),
|
||
var(--dsw-alias-scrollbar-bg-l1) calc(var(--dsh-width-handle-pointer-y, 50%) - 8px),
|
||
var(--dsw-alias-scrollbar-bg-l1) calc(var(--dsh-width-handle-pointer-y, 50%) + 8px),
|
||
transparent calc(var(--dsh-width-handle-pointer-y, 50%) + 36px)
|
||
);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.widthHandle[data-side='left']::after {
|
||
right: 4px;
|
||
}
|
||
|
||
.widthHandle[data-side='right']::after {
|
||
left: 4px;
|
||
}
|
||
|
||
.widthHandle[data-dragging]::after {
|
||
opacity: 1;
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
.widthHandle:hover::after {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
/* A drag starts above the composer band. Pointer capture keeps it active when
|
||
crossing the footer; lift only the active handle so its indicator remains
|
||
visible without making the resting footer gutter draggable. */
|
||
.widthHandle[data-dragging] {
|
||
z-index: 8;
|
||
}
|
||
|
||
/* Views that elect a composer overlay (trajectory etc.) own their full-bleed
|
||
scrollers; the strips would sit over their content and steal clicks. */
|
||
.root:has([data-conversation-composer-overlay]) .widthHandle {
|
||
display: none;
|
||
}
|
||
|
||
/* Composer context stack (Figma 9:937): standalone dock cards share one
|
||
rhythm above the input card. */
|
||
.composerStack {
|
||
/* Horizontal geometry (card width, clearance, dock inset) rides the shared
|
||
.root variables above so takeover siblings match the stack. */
|
||
--dsh-composer-stack-gap: 6px;
|
||
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--dsh-composer-stack-gap);
|
||
}
|
||
|
||
/* Common seat for the composer chain (fallback + elected overlay siblings). */
|
||
.composerSeat {
|
||
display: flex;
|
||
flex: none;
|
||
flex-direction: column;
|
||
/* One cap for every scrolling text region a composer seat can hold: the
|
||
InputBar draft (figma Input 75:8208 max 14 lines × 24px line) and the
|
||
takeover panels' bodies top out at the same height, so electing a
|
||
takeover never grows the footer past the card it replaces. Declared on
|
||
the seat because it is the chain's only shared ancestor — fallback and
|
||
elected overlay are siblings — and custom properties inherit down to
|
||
whichever entry is mounted. */
|
||
--dsh-composer-text-max-height: 336px;
|
||
}
|
||
|
||
/* Active phase: header is ordinary column chrome above the scrollport (not
|
||
sticky). The scroll body holds the transcript and the sticky composer seat
|
||
so wheel over the footer moves the flow. */
|
||
.root[data-phase='active'] {
|
||
overflow: hidden;
|
||
}
|
||
|
||
.root[data-phase='active'] .header {
|
||
flex: none;
|
||
}
|
||
|
||
/* Band below the header: the scrollport plus the width handles. Anchoring the
|
||
handles here keeps their full-height strips and glow under the header's
|
||
bottom edge at every header height. */
|
||
.body {
|
||
position: relative;
|
||
display: flex;
|
||
flex: 1;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
|
||
/* Shared width axis for the transcript, dock cards, composer, and takeover
|
||
cards. Main width controls publish the live body width and optional user
|
||
preference; embedded occurrences override this adaptive axis below. */
|
||
--dsh-chat-content-width: var(
|
||
--dsh-chat-user-width,
|
||
clamp(680px, calc(var(--dsh-conversation-column-width, 0px) * 0.64), 920px)
|
||
);
|
||
--dsh-composer-card-max-width: calc(var(--dsh-chat-content-width) + 32px);
|
||
--dsh-composer-side-clearance: 16px;
|
||
--dsh-composer-dock-inset: 8px;
|
||
}
|
||
|
||
.embeddedBody {
|
||
overflow: hidden;
|
||
--dsh-chat-content-width: min(calc(100% - 32px), 920px);
|
||
--dsh-composer-card-max-width: min(calc(100% - 16px), 952px);
|
||
--dsh-composer-side-clearance: 8px;
|
||
--dsh-composer-dock-inset: 8px;
|
||
}
|
||
|
||
.scrollBody {
|
||
display: flex;
|
||
flex: 1;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
/* The product-wide 2px scrollbar offset (ui-workspace WorkspaceBrowser
|
||
`.list`, ui-sidebar-files FilesBody `.body`): the bar sits 2px clear of
|
||
the column's edge instead of flush against it. */
|
||
margin-right: 2px;
|
||
overflow-y: auto;
|
||
/* Reserved unconditionally: the composer seat rides this box's content box in
|
||
Chat and its padding box under a view's composer overlay, so an `auto`
|
||
gutter moves the input card sideways by the bar's width whenever the two
|
||
differ ([decision](../../../../../../.agents/notes/archived/bug-fix/2026-08-04-composer-tab-gutter-reservation.md)). */
|
||
scrollbar-gutter: stable;
|
||
}
|
||
|
||
/* The bar also stops 2px short of the scroller's ends, matching its 2px edge
|
||
offset. WebKit-only: the Firefox path has no track to inset. */
|
||
.scrollBody::-webkit-scrollbar-track {
|
||
margin: 2px;
|
||
}
|
||
|
||
.root[data-phase='active'] .viewArea,
|
||
.embeddedBody[data-content-phase='active'] .viewArea {
|
||
flex: 1 0 auto;
|
||
min-height: auto;
|
||
}
|
||
|
||
.root[data-phase='active'] .composerSeat,
|
||
.embeddedBody[data-content-phase='active'] .composerSeat {
|
||
position: sticky;
|
||
bottom: 0;
|
||
/* Above markdown CodeBlock sticky banners (z-index 6) so the footer never
|
||
paints under a sticking code header while scrolling. The pinned compaction
|
||
header (MessageItem.module.css, ui-chat) and the turn-navigation rail slot
|
||
(TurnNavigator.module.css, ui-chat) also take 7 to clear the same banners.
|
||
Among elements of equal rank the later one in DOM order wins, so the
|
||
composer stays above the pinned header on the extreme small-viewport
|
||
overlap and the pinned header stays above the rail. */
|
||
z-index: 7;
|
||
/* Input mask (figma 1205:27463): transcript fades out under a FIXED 36px
|
||
band at the seat's top (the figma 24% of the resting ~150px composer),
|
||
solid below — px stops, not %, so a growing draft only widens the solid
|
||
region and the fade band never stretches. The 0px stop is bg-base at
|
||
zero alpha (not white, which the figma export hardcodes) so both themes
|
||
fade from their own base. */
|
||
background: linear-gradient(
|
||
180deg,
|
||
color-mix(in srgb, var(--dsw-alias-bg-base) 0%, transparent) 0px,
|
||
var(--dsw-alias-bg-base) 36px
|
||
);
|
||
}
|
||
|
||
/* An open @/slash menu (data-trigger-menu, ui-input-trigger) renders inside
|
||
the input card, so the seat's stacking context caps its z-index; lift the
|
||
seat above the back-to-bottom control (z-index 8, ui-chat ChatView) while
|
||
the menu is open. The steady state stays 7 so the control keeps painting
|
||
over the card. */
|
||
.root[data-phase='active'] .composerSeat:has([data-trigger-menu]) {
|
||
z-index: 9;
|
||
}
|
||
|
||
/* Views may opt into a composer overlay while ConversationRoot retains
|
||
ownership of the seat geometry and its active-phase precedence. */
|
||
.scrollBody:has([data-conversation-composer-overlay]) {
|
||
position: relative;
|
||
/* A clipping box nothing scrolls out of, stated as a scroll container on both
|
||
axes rather than `overflow: hidden`: a single-axis scroller computes the
|
||
other axis to `auto`, and the seat's absolute positioning below needs the
|
||
box to stay a containing block in every engine. */
|
||
overflow-x: hidden;
|
||
overflow-y: auto;
|
||
/* The view owns its own scrollers (the trajectory ledger scrolls inside the
|
||
view), so this box never scrolls here; reserving a gutter would only
|
||
narrow the view's content by the bar's width. Chat keeps the reservation
|
||
on the same element, so the seat below compensates with the bar's width
|
||
([decision](../../../../../../.agents/notes/archived/bug-fix/2026-08-04-composer-tab-gutter-reservation.md)). */
|
||
scrollbar-gutter: auto;
|
||
}
|
||
|
||
.scrollBody:has([data-conversation-composer-overlay]) > :global([data-slot='conversation.session']) > .viewArea {
|
||
flex: 1 1 0;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.scrollBody:has([data-conversation-composer-overlay]) > .composerSeat {
|
||
position: absolute;
|
||
/* Chat's seat rides this box's content box, which its own scrolling shortens
|
||
by the bar's width; the overlay seat is against the padding box, so it
|
||
must give back the same width to keep the input card in place across
|
||
tabs. Reads ui-theme's --dsh-scrollbar-width, which mirrors the WebKit
|
||
bar's layout width. Chat reserves the engine-resolved width, so on an
|
||
engine where the resolved width differs from this fixed 8px the card
|
||
drifts by half the difference — accepted residual risk, recorded in
|
||
([decision](../../../../../../.agents/notes/archived/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.md)). */
|
||
right: var(--dsh-scrollbar-width);
|
||
bottom: 0;
|
||
left: 0;
|
||
}
|
||
|
||
/* Hero phase: the composer stack (hero chrome + workspace row + card) is
|
||
flex-centered in the column; composer phase docks it at the bottom. Flex,
|
||
NOT absolute+transform: a transform would make this box the containing
|
||
block for position:fixed descendants (pickers/modals), shrinking them. */
|
||
.composerHero {
|
||
align-self: center;
|
||
/* figma 75:8208 drew 12 between all three rows; the workspace row now sits
|
||
8 above the card (its margin-top restores 12 under the hero chrome). */
|
||
gap: 8px;
|
||
/* Foot inside the centered box floats the stack a bit above true center. */
|
||
padding-bottom: 32px;
|
||
/* Card cap + both clearances: the hero input card lands at exactly the same
|
||
width as the docked composer at every viewport. */
|
||
width: min(calc(var(--dsh-composer-card-max-width) + 2 * var(--dsh-composer-side-clearance)), 100%);
|
||
z-index: 1;
|
||
}
|
||
|
||
.heroWorkspaceRow {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
min-width: 0;
|
||
margin-top: 4px;
|
||
/* figma drew px 8; left nudged +12 so the chip's folder glyph lines up
|
||
closer to the card's inner controls below; right keeps the trailing
|
||
preset seat off the card edge. */
|
||
padding: 0 16px 0 20px;
|
||
}
|
||
|
||
/* Plugin options share the row, pushed to its right edge. */
|
||
.heroOptions {
|
||
display: flex;
|
||
flex: none;
|
||
align-items: center;
|
||
gap: 2px;
|
||
min-width: 0;
|
||
margin-left: auto;
|
||
}
|
||
|
||
.heroOptions:empty {
|
||
display: none;
|
||
}
|
||
|
||
/* Hero: the composer sits inside the session scroll body; center there so
|
||
the tree seat matches active (sticky footer) without a Root remount. */
|
||
.root[data-phase='hero'] .scrollBody,
|
||
.embeddedBody[data-content-phase='hero'] .scrollBody {
|
||
justify-content: center;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* Settling (session replaying, hero/docked unknown): keep the composer seat
|
||
mounted but invisible so no wrong layout flashes before the phase lands. */
|
||
.root[data-phase='settling'] .composerSeat,
|
||
.embeddedBody[data-content-phase='settling'] .composerSeat {
|
||
visibility: hidden;
|
||
}
|