refactor: archive GUI clients; keep minimal TUI with hooks/API
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>
This commit is contained in:
@@ -0,0 +1,458 @@
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
/* Side pads ride the shared clearance (figma Input_Bottom drew L32/R32/B8;
|
||||
the sides narrow with the shared width axis); the bottom gradient mask
|
||||
is owned by the chat scroller. No top pad: the composer stack's gap owns
|
||||
the space above; the status strip still carries its own margin. */
|
||||
padding: 0 var(--dsh-composer-side-clearance) 4px;
|
||||
}
|
||||
|
||||
.dock {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
max-width: 100%;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.hero .dock:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hero {
|
||||
/* No bottom pad in the centered hero, but the side clearance must survive:
|
||||
the hero wrapper is full-width on narrow viewports, so this padding is
|
||||
the only thing keeping the card off the edges there. */
|
||||
padding: 0 var(--dsh-composer-side-clearance);
|
||||
}
|
||||
|
||||
.notice {
|
||||
width: 100%;
|
||||
max-width: var(--dsh-composer-card-max-width);
|
||||
margin-bottom: 6px;
|
||||
padding: 4px 8px;
|
||||
border-radius: var(--dsw-radius-md);
|
||||
background: var(--dsw-alias-interactive-bg-hover);
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.card {
|
||||
box-sizing: border-box;
|
||||
position: relative; /* overlay anchor positioning context */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* figma Input 75:8208: 12px between the text area and the button row; the
|
||||
card's top pad before .InputText tightened from the drawn 10 to 8. */
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
max-width: var(--dsh-composer-card-max-width);
|
||||
padding-top: 8px;
|
||||
/* Input stroke: the light neutral (l2, one step above the menus' l1),
|
||||
drawn as the elevation hairline so it costs no layout; the shadow is the
|
||||
soft elevation tier. Fill: the input surface token (elevated in dark). */
|
||||
border: 0;
|
||||
--dsw-elevation-stroke-color: var(--dsw-alias-border-l2);
|
||||
border-radius: var(--dsw-radius-panel);
|
||||
background: var(--dsw-specific-input-major);
|
||||
box-shadow: var(--dsw-elevation-soft);
|
||||
/* The draft text follows the Settings font-size preference (the textarea,
|
||||
mirror, and backdrop all inherit from here, so the three layers stay in
|
||||
step); the line height keeps the default 10px leading via the shared px
|
||||
delta. */
|
||||
font-size: var(--dsh-content-font-size, 14px);
|
||||
line-height: calc(24px + var(--dsh-content-font-delta, 0px));
|
||||
/* Elevated surface in dark, same as the menus: the draft scrollport inside
|
||||
scrolls once the composer hits its height cap, so the thumb takes the l2
|
||||
pair. Declared on the card because the elevation belongs to the surface,
|
||||
and the custom properties inherit down to the box that actually scrolls
|
||||
(see ui-theme styles/scrollbar.css for the rebinding contract). */
|
||||
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
||||
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
||||
}
|
||||
|
||||
@media (pointer: coarse) {
|
||||
.card {
|
||||
font-size: max(16px, var(--dsh-content-font-size, 14px));
|
||||
}
|
||||
}
|
||||
|
||||
/* No-workspace trigger state: an inset 1px dashed outline marks the card as a
|
||||
workspace picker rather than a live composer. Keeping the complete stroke
|
||||
inside the rounded box prevents clipped corner pixels from reading heavier. */
|
||||
.cardWorkspaceTrigger {
|
||||
--dsw-elevation-stroke-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cardWorkspaceTrigger::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
box-sizing: border-box;
|
||||
border: 1px dashed currentColor;
|
||||
border-radius: var(--dsw-radius-panel);
|
||||
color: var(--dsw-alias-border-l4);
|
||||
transition: color 100ms ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Disabled toolbar controls neither receive nor swallow clicks in the trigger
|
||||
state: pointer events fall through to the card's own click handler, making
|
||||
the whole card one pick target. */
|
||||
.cardWorkspaceTrigger :disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.cardWorkspaceTrigger:hover::after {
|
||||
color: var(--dsw-alias-state-business-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.accessory {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px 0;
|
||||
}
|
||||
|
||||
/* Floating overlay anchor (menu / popupSelect shell): entries position
|
||||
themselves against the card (bottom: 100% + gap); closed entries render null. */
|
||||
.overlayAnchor {
|
||||
position: absolute;
|
||||
inset: 0 0 auto;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* The draft's scrollport, and the ONLY scrolling box in the composer. The
|
||||
contenteditable grows with its content inside; the 14-line cap lives here
|
||||
because this is the box the cap describes. */
|
||||
.scroll {
|
||||
max-height: var(--dsh-composer-text-max-height);
|
||||
overflow-y: auto;
|
||||
/* Keeps the bar off the card's right border; .input's right pad gives the
|
||||
4px back so the text column is unchanged. */
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* The card's corner arc still overlaps the scrollport's top (8px top
|
||||
pad reaches only y=8): start the thumb's travel below the arc so it never
|
||||
pokes outside the capsule. WebKit-path only; Firefox's thin bar has no
|
||||
track margin, an accepted remainder. */
|
||||
.scroll::-webkit-scrollbar-track {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* Auto-grow anchor: the contenteditable is in normal flow and sets the
|
||||
draft's height; the placeholder rides it absolutely. */
|
||||
.grow {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Machine pending dot (adjudicating / submitting). */
|
||||
.pending {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
corner-shape: round;
|
||||
background: var(--dsw-alias-state-business-primary);
|
||||
animation: input-pending 1s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes input-pending {
|
||||
from { opacity: 0.35; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
/* The contenteditable draft surface (grows with its content; .scroll caps
|
||||
and scrolls it). figma .InputText 34:10434 drew pl 16 / pr 12 / pt 4; the
|
||||
left pad tightened to 14, and the right pad is 8 here plus .scroll's 4px
|
||||
bar inset. */
|
||||
.input {
|
||||
box-sizing: border-box;
|
||||
/* Docked floor: one 24px line + 4px top pad = 28 content height; 36 leaves
|
||||
an 8px breath below the line (the hero keeps its own 52px floor below). */
|
||||
min-height: 36px;
|
||||
padding: 4px 8px 0 14px;
|
||||
font-family: var(--dsw-font-family);
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
outline: none;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
/* Business blue, not brand-primary: that token resolves to ink in this sheet. */
|
||||
caret-color: var(--dsw-alias-state-business-primary);
|
||||
}
|
||||
|
||||
/* Lexical paragraphs are <p> blocks: strip the UA margins so the surface
|
||||
keeps the textarea's line rhythm. */
|
||||
.input p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Claim ghost hint as generated content after the last paragraph: the bar
|
||||
sets --dsh-composer-hint (a quoted string) while the claim's args are
|
||||
blank; without the variable the declaration is invalid and nothing shows. */
|
||||
.input p:last-child::after {
|
||||
content: var(--dsh-composer-hint);
|
||||
color: var(--dsw-alias-label-caption);
|
||||
}
|
||||
|
||||
.input[data-composer-composing] p:last-child::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
/* A selected command chip stands in for the caret until the next key. */
|
||||
.input[data-composer-chip-selected] {
|
||||
caret-color: transparent;
|
||||
}
|
||||
|
||||
.input[data-composer-composing] + .placeholder {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* figma 34:10434: #ADB2B8 light / #81858C dark — the caption pair exactly.
|
||||
Insets mirror .input's pads (the other right 4px sits on .scroll). One
|
||||
ellipsized line: a wrapped second line would clip against the docked
|
||||
36px floor. */
|
||||
.placeholder {
|
||||
position: absolute;
|
||||
inset: 4px 8px auto 14px;
|
||||
color: var(--dsw-alias-label-caption);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.inputDisabled {
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.input[aria-haspopup='menu'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Hero (centered empty-state) keeps the 2-line floor (figma min-h 52 = ~2 × 24
|
||||
line + 4pt); the docked composer collapses to the content height. */
|
||||
.hero .input {
|
||||
min-height: 52px;
|
||||
}
|
||||
|
||||
/* The hero's 52px floor fits two lines, so its placeholder clamps at two
|
||||
instead of the docked single line. */
|
||||
.hero .placeholder {
|
||||
white-space: normal;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
}
|
||||
|
||||
/* Toolbar: attach + Plan + Read-only on the left; model + send on the right
|
||||
(figma Input_Bottom chrome). */
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
/* 2px moved from the bottom pad to the top: the whole control row sits 2px
|
||||
lower in the card (it read too high against the textarea) while the
|
||||
controls' own centering stays untouched. */
|
||||
padding: 2px 8px 6px;
|
||||
min-width: 0;
|
||||
/* Size container so the chips inside can collapse to icon-only when the
|
||||
card runs out of row width (PermissionSelect @container rule). Anonymous
|
||||
on purpose: CSS modules hash container-name per module, so a name declared
|
||||
here can never match a query in another module's sheet. */
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.row[data-model-compact] {
|
||||
--dsh-composer-model-text-display: none;
|
||||
--dsh-composer-model-icon-display: block;
|
||||
}
|
||||
|
||||
.tools,
|
||||
.modes,
|
||||
.trailing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* figma 75:8208 drew 16 between + and the mode chips and 4 between Plan /
|
||||
Read-only; both gaps settled on 12 so the pill chips read as separate
|
||||
controls without drifting from the + control. */
|
||||
.tools {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.modes {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.trailing {
|
||||
flex: none;
|
||||
/* Wrap keeps the left mode chips and the right controls apart when the card
|
||||
runs out of row width: the trailing group (model + send) moves to its own
|
||||
line instead of the left group shrinking until its chip overlaps the
|
||||
model trigger (external:107). The auto margin re-anchors it right on the
|
||||
wrapped line; on a single line space-between already pins it right. */
|
||||
margin-left: auto;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* Compact spacing preserves room for the fixed-width controls in narrow cards. */
|
||||
@container (max-width: 560px) {
|
||||
.tools,
|
||||
.modes,
|
||||
.trailing {
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Attach circle (figma + control): 28px, selector fill, primary glyph. */
|
||||
.add {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex: none;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
corner-shape: round;
|
||||
background: var(--dsw-specific-selector);
|
||||
color: var(--dsw-alias-label-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.add:hover:not(:disabled) {
|
||||
background: var(--dsw-alias-interactive-bg-hover-solid);
|
||||
}
|
||||
}
|
||||
|
||||
.add:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Plan / Read-only / model — native <select>, chip-like closed chrome
|
||||
(figma ToggleButton: 13/20 medium secondary, 12px chevron). */
|
||||
.select {
|
||||
max-width: 220px;
|
||||
height: 28px;
|
||||
padding: 0 20px 0 8px;
|
||||
border: none;
|
||||
border-radius: var(--dsw-radius-sm);
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 4px center;
|
||||
background-size: 12px 12px;
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.select:hover:not(:disabled) {
|
||||
background-color: var(--dsw-alias-interactive-bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.select:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Primary send (figma IconButton 34:10465): 34px circle, #3964FE light /
|
||||
#679EFE dark — the info-fill pair (500→400), NOT button-primary (ink);
|
||||
white glyph; empty text = 0.4 opacity. */
|
||||
.primary {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex: none;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
corner-shape: round;
|
||||
background: var(--dsw-alias-button-info-fill);
|
||||
/* Static white, not the foreground token: the arrow stays white on the blue
|
||||
fill in both themes (design 34:10465). */
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
transition: background-color 100ms ease;
|
||||
/* Opts out of the row's 2px downward shift (.row top pad): the send circle
|
||||
keeps its original seat while the smaller chips sit lower. Transform, not
|
||||
margin, so flex centering math is untouched. */
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.primary:hover:not(:disabled) {
|
||||
background: var(--dsw-alias-button-info-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.primary:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Sending: the message waits for the Host to accept it. The circle keeps its
|
||||
full fill rather than the disabled fade, and an arc turns where the arrow was. */
|
||||
.primary[data-sending]:disabled {
|
||||
opacity: 1;
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
.sendingSpinner {
|
||||
animation: input-sending 800ms linear infinite;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.sendingSpinner {
|
||||
animation-duration: 2400ms;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes input-sending {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.retry {
|
||||
margin-left: 8px;
|
||||
padding: 1px 8px;
|
||||
border: 1px solid currentColor;
|
||||
border-radius: var(--dsw-radius-sm);
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.standardControls { display: flex; align-items: center; gap: 12px; min-width: 0; }
|
||||
.tools[hidden], .standardControls[hidden] { display: none; }
|
||||
.trailingActive { flex: 1; margin-left: 0; }
|
||||
.activity { display: flex; align-items: center; flex: none; }
|
||||
.activity:empty { display: none; }
|
||||
.activityExpanded { flex: 1; min-width: 0; }
|
||||
Reference in New Issue
Block a user