From 17183882be00a85a29989fecb11d8f33aa137816 Mon Sep 17 00:00:00 2001 From: Bowen Liang Date: Sun, 13 Sep 2026 09:41:30 +0800 Subject: [PATCH] refactor(client): move pure pseudo-state styling to Tailwind state variants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 20 hover/focus rules that are leaf styles (no state class re-asserts them) become hover:/focus:/group-hover: variants on the elements' own class lists, and their sheet rules are deleted: row and button hover tints, retry/close/gate brightness, input focus borders, underline hints, the three styled-tip reveals (named group/tip), the plugin-info row underlines (group/pi), and the agent halo's hover/focus wash (group/agent — the React hover state keeps driving the inspector; lc-agent-hover stays as a test anchor). The hover/lit(-on) cascade pyramids stay hand-written in the sheets — the gran/rich-seg/rich-copy/fa-chip toggles and the settings card's open state need ordered overrides over their own hover rules, several of those state classes double as test anchors and DOM query hooks, and moving them would mean a chip factory for zero visual change. Transitions, animations and all harness --dsw-* tokens stay in sheets. Behavior notes: variant utilities carry (0,2,0) specificity, so they keep beating the sheet base rules exactly as the old :hover rules did; Tailwind media-guards hover behind (hover: hover), so touch devices no longer get sticky hover styling (dsh web is desktop-first). --- src/client/components/agentGraph.tsx | 8 +++++++- src/client/components/browser.tsx | 8 ++++---- src/client/components/contextJump.tsx | 2 +- src/client/components/contextModal.tsx | 2 +- src/client/components/detailNote.tsx | 2 +- src/client/components/errorBoundary.tsx | 2 +- src/client/components/fetchOnMiss.tsx | 2 +- src/client/components/fileCard.tsx | 12 ++++++------ src/client/components/images.tsx | 2 +- src/client/components/pluginInfo.tsx | 10 +++++----- src/client/components/requestDetail.tsx | 8 ++++---- src/client/components/settingsCard.tsx | 2 +- src/client/components/statsContext.tsx | 6 +++--- src/client/components/trendChart.tsx | 2 +- src/client/components/upgradeGate.tsx | 4 ++-- src/client/styles/agentGraph.css | 4 ++-- src/client/styles/attachments.css | 1 - src/client/styles/base.css | 8 ++++---- src/client/styles/browser.css | 3 --- src/client/styles/detailSections.css | 1 - src/client/styles/fileCard.css | 5 ----- src/client/styles/jump.css | 1 - src/client/styles/modal.css | 2 -- src/client/styles/requestDetail.css | 5 +---- src/client/styles/settings.css | 2 -- src/client/styles/stats.css | 6 +----- src/client/styles/tailwind.css | 15 +++++++++------ src/client/styles/trendChart.css | 1 - tests/client/components/browser.spec.ts | 2 +- 29 files changed, 57 insertions(+), 71 deletions(-) diff --git a/src/client/components/agentGraph.tsx b/src/client/components/agentGraph.tsx index 422df2a..2aac7f3 100644 --- a/src/client/components/agentGraph.tsx +++ b/src/client/components/agentGraph.tsx @@ -249,6 +249,9 @@ function AgentNodeView(props: NodeViewProps): ReactElement { + (node.completed && !node.running ? ' lc-agent-done' : '') + (props.hovered ? ' lc-agent-hover' : '') + (node.isCurrent ? '' : ' lc-agent-clickable') + // The halo's hover/focus wash rides group variants on the node (the React + // hover state only drives the inspector; lc-agent-hover stays as a test anchor). + + ' group/agent' return ( { props.onHover(null) }} > {/* Halo carries the state: wash for self, breathing green while running, faint green for done. */} - + {segs.map(seg => ( ) => { props.onChange(ev.target.value) }} @@ -321,7 +321,7 @@ function ToolSchema(props: {
{jsonOpen ?
{schemaJson}
: null} @@ -820,7 +820,7 @@ export function makeContextBrowser( const open = openElem === key return (
- diff --git a/src/client/components/contextModal.tsx b/src/client/components/contextModal.tsx index e8e1080..981f195 100644 --- a/src/client/components/contextModal.tsx +++ b/src/client/components/contextModal.tsx @@ -116,7 +116,7 @@ export function makeContextModal(ctx: ClientCtx, kit: ViewKit): (props: ContextM
{ ev.stopPropagation() }}>
{t('tab')} - +
{data === null || head === null ? ( diff --git a/src/client/components/detailNote.tsx b/src/client/components/detailNote.tsx index c460254..191cd4d 100644 --- a/src/client/components/detailNote.tsx +++ b/src/client/components/detailNote.tsx @@ -30,7 +30,7 @@ export function makeDetailNote(kit: ViewKit): (props: { return (
{props.onRetry !== undefined - ? + ? : t('detail.loadFailed')}
) diff --git a/src/client/components/errorBoundary.tsx b/src/client/components/errorBoundary.tsx index b45752c..608dae4 100644 --- a/src/client/components/errorBoundary.tsx +++ b/src/client/components/errorBoundary.tsx @@ -29,7 +29,7 @@ export function makeErrorBoundary(t: Translate): ComponentType<{ children?: Reac {error.message}
diff --git a/src/client/components/fetchOnMiss.tsx b/src/client/components/fetchOnMiss.tsx index f123938..56fe594 100644 --- a/src/client/components/fetchOnMiss.tsx +++ b/src/client/components/fetchOnMiss.tsx @@ -77,7 +77,7 @@ export function fetchMissNote( if (state === 'absent') return t('browser.notInLog') if (state === 'failed') { return ( - ) diff --git a/src/client/components/fileCard.tsx b/src/client/components/fileCard.tsx index cff5a3a..322ca38 100644 --- a/src/client/components/fileCard.tsx +++ b/src/client/components/fileCard.tsx @@ -173,7 +173,7 @@ export function makeFileCard(kit: ViewKit, settings: ContextSettings): Component ))}
) => { setQuery(ev.target.value) }} @@ -183,9 +183,9 @@ export function makeFileCard(kit: ViewKit, settings: ContextSettings): Component {t('files.files', { n: activity.entries.length })} {activity.totals.added + activity.totals.removed > 0 ? ( /* The one styled tip of the card: it lives OUTSIDE the scrolling list, so the bubble never clips. */ - + - {t('files.deltaTip')} + {t('files.deltaTip')} ) : null} @@ -225,7 +225,7 @@ export function makeFileCard(kit: ViewKit, settings: ContextSettings): Component
diff --git a/src/client/components/requestDetail.tsx b/src/client/components/requestDetail.tsx index 269010d..9e65e0d 100644 --- a/src/client/components/requestDetail.tsx +++ b/src/client/components/requestDetail.tsx @@ -53,9 +53,9 @@ export function makeRequestDetail( }): ReactElement { const inner = ( <> - + {props.tag} - {props.tagTip} + {props.tagTip} {props.children} @@ -65,7 +65,7 @@ export function makeRequestDetail( const onLocate = props.onLocate const locate = (): void => { onLocate(node, props.isResponse) } return ( - ) @@ -160,7 +160,7 @@ export function makeRequestDetail( return ( diff --git a/src/client/components/settingsCard.tsx b/src/client/components/settingsCard.tsx index 944fce1..54b2a30 100644 --- a/src/client/components/settingsCard.tsx +++ b/src/client/components/settingsCard.tsx @@ -44,7 +44,7 @@ function PrefRow(props: PrefRowProps): ReactElement { anchor={( +
{t('gate.body', { minimum: props.minimum })}
@@ -66,7 +66,7 @@ export function makeUpgradeGate(kit: ViewKit): (props: UpgradeGateProps) => Reac
- +
diff --git a/src/client/styles/agentGraph.css b/src/client/styles/agentGraph.css index b71ba6b..ead6c6d 100644 --- a/src/client/styles/agentGraph.css +++ b/src/client/styles/agentGraph.css @@ -16,9 +16,9 @@ @keyframes lc-agent-flow { to { stroke-dashoffset: -9; } } .lc-agent-node { outline: none; } .lc-agent-clickable { cursor: pointer; } -/* The halo carries node state: hover wash, brand tint for self, breathing green while running, a faint green rest for done. */ +/* The halo carries node state: the hover/focus wash rides `group-hover/agent`/`group-focus-visible/agent` + variants in agentGraph.tsx, brand tint for self, breathing green while running, a faint green rest for done. */ .lc-agent-halo { fill: transparent; transition: fill var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -.lc-agent-hover .lc-agent-halo, .lc-agent-node:focus-visible .lc-agent-halo { fill: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2)); } .lc-agent-self .lc-agent-halo { fill: var(--dsw-alias-bg-layer-2); } .lc-agent-running .lc-agent-halo { fill: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); animation: lc-agent-glow 1.8s ease-in-out infinite; } .lc-agent-done .lc-agent-halo { fill: color-mix(in srgb, var(--dsw-alias-state-success-primary) 7%, transparent); } diff --git a/src/client/styles/attachments.css b/src/client/styles/attachments.css index 15a8f58..5111f4c 100644 --- a/src/client/styles/attachments.css +++ b/src/client/styles/attachments.css @@ -1,7 +1,6 @@ /* Attachment grids and the image lightbox (browser.tsx inline grid + images.tsx lightbox). */ .lc-att-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; padding: 8px 10px; } .lc-att-item { display: flex; gap: 8px; align-items: center; min-width: 0; width: 100%; box-sizing: border-box; padding: 6px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; background: transparent; font: inherit; text-align: left; cursor: pointer; transition: border-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out), background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -.lc-att-item:hover { border-color: var(--dsw-alias-label-dimmed); background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2)); } .lc-att-thumb { width: 64px; height: 64px; flex: none; padding: 0; display: flex; align-items: center; justify-content: center; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; overflow: hidden; background: var(--dsw-alias-bg-layer-1); } .lc-att-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; } .lc-att-ph, .lc-att-err { font-size: 10px; color: var(--dsw-alias-label-secondary); text-align: center; padding: 2px; } diff --git a/src/client/styles/base.css b/src/client/styles/base.css index 36e9fe8..836e064 100644 --- a/src/client/styles/base.css +++ b/src/client/styles/base.css @@ -78,9 +78,10 @@ .lc-gran-on .lc-kind-n { opacity: 0.85; } /* Shared tooltip-bubble chrome: pair `.lc-tip` with the owner's own tip class, - then position locally (top/bottom anchor, width cap, whitespace, reveal rule). - The component sheets carry the four shippers (.lc-stat-tip / .lc-brief-tip / - .lc-fa-meta-tip / .lc-bar-tip); a new tip never re-declares this chrome. + then position locally (top/bottom anchor, width cap, whitespace); the reveal + rides a `group-hover/tip` variant on the owner. The component sheets carry + the four shippers (.lc-stat-tip / .lc-brief-tip / .lc-fa-meta-tip / .lc-bar-tip); + a new tip never re-declares this chrome. */ .lc-tip { position: absolute; z-index: 6; width: max-content; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 6px 10px; font-size: 12px; color: var(--dsw-alias-label-primary); box-shadow: var(--dsw-shadow-lv3, 0 2px 8px rgba(0, 0, 0, 0.18)); pointer-events: none; opacity: 0; transition: opacity var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } .lc-events, .lc-fa-list { display: flex; flex-direction: column; gap: 2px; height: 320px; overflow-y: auto; } @@ -127,7 +128,6 @@ .lc-error { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 40px 16px; } .lc-error-msg { font-family: var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; color: var(--dsw-alias-state-error-primary); background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 4px 8px; max-width: 100%; overflow-wrap: anywhere; } .lc-error-retry { border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; padding: 4px 14px; border-radius: 6px; cursor: pointer; transition: border-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -.lc-error-retry:hover { border-color: var(--dsw-alias-label-primary); } .lc-foot { color: var(--dsw-alias-label-secondary); font-size: 12px; margin-top: 4px; } [data-conversation-scroll]:has(.lc-root) > [data-composer-seat]:not(:has([data-approval-key], [data-question-key], [data-plan-review-key])) { display: none; } /* diff --git a/src/client/styles/browser.css b/src/client/styles/browser.css index 5befa30..33c604f 100644 --- a/src/client/styles/browser.css +++ b/src/client/styles/browser.css @@ -14,12 +14,10 @@ .lc-br-meta .lc-est { margin-left: auto; } .lc-br-note { margin-top: 8px; color: var(--dsw-alias-label-secondary); font-size: 12px; } .lc-br-retry { border: 0; background: transparent; padding: 0; color: var(--dsw-alias-brand-primary); font: inherit; font-size: 12px; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; transition: filter var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -.lc-br-retry:hover { filter: brightness(1.15); } .lc-br-cats { margin-top: 10px; display: flex; flex-direction: column; gap: 4px; } .lc-br-cat { border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; background: var(--dsw-alias-bg-layer-2); overflow: hidden; } .lc-br-cat-empty { opacity: 0.55; } .lc-br-cat-row { display: flex; align-items: center; gap: 8px; width: 100%; border: 0; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; padding: 7px 10px; cursor: pointer; text-align: left; transition: background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -.lc-br-cat-row:hover { background: var(--dsw-alias-interactive-bg-hover); } /* Current-composition hover echo: the category row lit by the shared hover link looks hovered — same tint as a physical hover, plus a ring on its color dot. */ @@ -48,7 +46,6 @@ .lc-br-elem { border-radius: 6px; } .lc-br-elem-on { background: var(--dsw-alias-interactive-bg-active); } .lc-br-elem-row { display: flex; align-items: center; gap: 8px; width: 100%; border: 0; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; padding: 5px 6px; cursor: pointer; text-align: left; border-radius: 6px; transition: background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -.lc-br-elem-row:hover { background: var(--dsw-alias-interactive-bg-hover); } /* Failed tool result: the chat's red run-state dot (6px core on a 10px halo) leads the collapsed row, so the failure scans without expanding. */ .lc-br-err-dot { flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--dsw-alias-state-error-primary); box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent); } diff --git a/src/client/styles/detailSections.css b/src/client/styles/detailSections.css index 37a6363..d59faab 100644 --- a/src/client/styles/detailSections.css +++ b/src/client/styles/detailSections.css @@ -48,7 +48,6 @@ .lc-ts-arg-val { color: var(--dsw-alias-label-primary); font-family: var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; max-height: 200px; overflow-y: auto; scrollbar-width: thin; } .lc-ts-json { display: flex; flex-direction: column; gap: 4px; } .lc-ts-json-toggle { align-self: flex-start; border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 11px; padding: 0; cursor: pointer; transition: color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -.lc-ts-json-toggle:hover { color: var(--dsw-alias-label-primary); text-decoration: underline; } .lc-rich-seg { margin-left: auto; display: flex; gap: 2px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l2); border-radius: 6px; padding: 1px; } .lc-rich-seg-btn { border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 1; padding: 3px 8px; border-radius: 5px; cursor: pointer; font-family: inherit; transition: color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } .lc-rich-seg-btn:hover { color: var(--dsw-alias-label-primary); } diff --git a/src/client/styles/fileCard.css b/src/client/styles/fileCard.css index 0fbd37c..e9aa386 100644 --- a/src/client/styles/fileCard.css +++ b/src/client/styles/fileCard.css @@ -8,19 +8,16 @@ .lc-fa-ctl .lc-gran { margin-left: 0; } .lc-fa-n { margin-left: 4px; font-weight: 700; opacity: 0.75; } .lc-fa-search, .lc-br-tool-search { flex: 1; min-width: 80px; font: inherit; font-size: 12px; color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l2); border-radius: 6px; padding: 3px 8px; outline: none; } -.lc-fa-search:focus, .lc-br-tool-search:focus { border-color: var(--dsw-alias-label-dimmed); } .lc-fa-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 6px; color: var(--dsw-alias-label-secondary); font-size: 12px; } /* The sort toggle rides the meta strip's right edge — the shared .lc-gran's own auto margin does the push. */ .lc-fa-meta .lc-fa-sort { flex: none; } /* The aggregate delta pill carries the card's one styled tip (the shared `.lc-tip` chrome) — it sits outside the scrolling list, so the bubble never clips. */ .lc-fa-meta-delta { position: relative; } .lc-fa-meta-tip { top: calc(100% + 6px); left: 0; max-width: 260px; white-space: normal; line-height: 1.5; } -.lc-fa-meta-delta:hover .lc-fa-meta-tip { opacity: 1; } /* An expanded file reads as one contained card: layer-2 fill inside a hairline frame. */ .lc-fa-item { border: 1px solid transparent; border-radius: 8px; transition: background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out), border-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } .lc-fa-item-on { background: var(--dsw-alias-bg-layer-2); border-color: var(--dsw-alias-border-l2); } .lc-fa-row { display: flex; align-items: center; gap: 7px; width: 100%; border: 0; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; padding: 6px 8px; cursor: pointer; text-align: left; border-radius: 7px; transition: background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -.lc-fa-row:hover { background: var(--dsw-alias-interactive-bg-hover); } .lc-fa-form { flex: none; width: 20px; height: 14px; display: flex; align-items: center; justify-content: center; font-size: 11px; opacity: 0.85; } .lc-fa-lang { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 13px; padding: 0 2px; border-radius: 4px; font-size: 8px; font-weight: 700; line-height: 1; } /* The chevron sits close to the icon: only half the row gap stays visible. */ @@ -42,7 +39,6 @@ .lc-fa-ctl .lc-gran { flex-wrap: wrap; } } .lc-fa-file { cursor: pointer; } -.lc-fa-file:hover { text-decoration: underline; } .lc-fa-path em { font-style: normal; color: var(--dsw-alias-label-secondary); } /* Per-purpose count badges: pill chips, one fixed tint per purpose (read = brand, write = success, search = warn). */ .lc-fa-badge { flex: none; display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; padding: 1px 7px; border-radius: 999px; white-space: nowrap; } @@ -65,7 +61,6 @@ .lc-fa-ops { margin: 0 8px 8px 15px; padding: 2px 0 2px 10px; border-left: 2px solid var(--dsw-alias-border-l1); display: flex; flex-direction: column; gap: 1px; } .lc-fa-op { display: flex; align-items: center; gap: 8px; min-width: 0; border: 0; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; font-size: 11px; padding: 3px 6px; text-align: left; border-radius: 5px; transition: background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } .lc-fa-op-link { cursor: pointer; } -.lc-fa-op-link:hover { background: var(--dsw-alias-interactive-bg-hover); } .lc-fa-op-time { flex: none; margin-left: auto; color: var(--dsw-alias-label-secondary); font-variant-numeric: tabular-nums; } .lc-fa-op-tool { flex: none; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; padding: 0 6px; border-radius: 999px; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); color: var(--dsw-alias-label-secondary); } .lc-fa-op-detail { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary); } diff --git a/src/client/styles/jump.css b/src/client/styles/jump.css index c4b5dfa..d92a5df 100644 --- a/src/client/styles/jump.css +++ b/src/client/styles/jump.css @@ -2,4 +2,3 @@ /* The chat action-row jump button — mirrors the harness MessageIconActions `.action` chrome so it sits flush beside the shipped icons. */ .lc-jump { display: inline-flex; align-items: center; justify-content: center; width: calc(28px + var(--dsh-content-font-delta, 0px)); height: calc(28px + var(--dsh-content-font-delta, 0px)); padding: 6px; border: 0; border-radius: 28px; background: transparent; color: var(--dsw-alias-label-tertiary); cursor: pointer; transition: background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out), color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } .lc-jump svg { width: calc(16px + var(--dsh-content-font-delta, 0px)); height: calc(16px + var(--dsh-content-font-delta, 0px)); } -.lc-jump:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-secondary); } diff --git a/src/client/styles/modal.css b/src/client/styles/modal.css index 74e313b..419872b 100644 --- a/src/client/styles/modal.css +++ b/src/client/styles/modal.css @@ -6,7 +6,6 @@ .lc-modal-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 12px; } .lc-modal-title { font-weight: 600; font-size: 14px; } .lc-modal-close { margin-left: auto; border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font-size: 18px; line-height: 1; padding: 2px 6px; border-radius: 6px; cursor: pointer; font-family: inherit; transition: color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out), background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -.lc-modal-close:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-2); } /* ---- the baseline-gate dialog (upgradeGate.tsx): narrower card, version rows, one primary action ---- */ .lc-gate-card { width: min(440px, calc(100vw - 48px)); } @@ -19,4 +18,3 @@ .lc-gate-or-newer { margin-left: 6px; font-family: inherit; font-weight: 400; font-size: 11px; color: var(--dsw-alias-label-secondary); } .lc-gate-actions { display: flex; justify-content: flex-end; margin-top: 16px; } .lc-gate-ok { border: 0; border-radius: 6px; padding: 6px 16px; font-size: 12px; font-family: inherit; cursor: pointer; background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground); transition: filter var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -.lc-gate-ok:hover { filter: brightness(1.1); } diff --git a/src/client/styles/requestDetail.css b/src/client/styles/requestDetail.css index e823bc6..f55f00d 100644 --- a/src/client/styles/requestDetail.css +++ b/src/client/styles/requestDetail.css @@ -24,12 +24,10 @@ .lc-brief-row { display: flex; align-items: center; gap: 8px; min-width: 0; padding: 3px 6px; margin-left: -6px; margin-right: -6px; border-radius: 6px; border: 0; background: transparent; font-family: inherit; font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-primary); text-align: left; transition: background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } /* The link class marks a hoverable ROW (buttons and the chip-carrying inputs row alike); the pointer cursor belongs only to the actual click targets — button rows and individual chips. */ -.lc-brief-row-link:hover { background: var(--dsw-alias-interactive-bg-hover); } button.lc-brief-row-link { cursor: pointer; } .lc-brief-tag { flex: none; box-sizing: border-box; min-width: 56px; text-align: center; font-size: 11px; padding: 0 6px; border-radius: 4px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); color: var(--dsw-alias-label-secondary); position: relative; } -/* The tag's explanation bubble — the shared `.lc-tip` chrome: instant styled reveal on tag hover, never intercepts the pointer. */ +/* The tag's explanation bubble — the shared `.lc-tip` chrome: instant styled reveal on tag hover (a `group-hover/tip` variant), never intercepts the pointer. */ .lc-brief-tip { bottom: calc(100% + 6px); left: 0; max-width: 240px; white-space: normal; text-align: left; line-height: 1.5; font-weight: 400; } -.lc-brief-tag:hover .lc-brief-tip { opacity: 1; } .lc-brief-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* The empty-state In row's placeholder: same anatomy as the preview text, muted. */ .lc-brief-empty { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary); } @@ -42,7 +40,6 @@ button.lc-brief-row-link { cursor: pointer; } .lc-brief-chip-tag { flex: none; max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; padding: 0 6px; border-radius: 4px; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); color: var(--dsw-alias-label-secondary); } .lc-brief-chip-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .lc-brief-chip-link { cursor: pointer; } -.lc-brief-chip-link:hover { background: var(--dsw-alias-interactive-bg-hover); } .lc-brief-more { flex: none; font-size: 11px; color: var(--dsw-alias-label-secondary); } .lc-detail-row { display: flex; align-items: center; gap: 8px; border-radius: 4px; /* Padding + matching negative margin: the highlight paints past the text while the layout stays put. */ padding: 1px 4px; margin: -1px -4px; transition: background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } /* Category hover-link: the shared hover lights the matching detail row — same pairing as the legend chips and the diff --git a/src/client/styles/settings.css b/src/client/styles/settings.css index 18ba4ef..95489fd 100644 --- a/src/client/styles/settings.css +++ b/src/client/styles/settings.css @@ -12,6 +12,4 @@ .lc-settings-row { display: flex; align-items: center; gap: 8px; padding: 8px 0; } .lc-settings-label { flex: 1; min-width: 0; font-size: 14px; color: var(--dsw-alias-label-primary); } .lc-settings-select { display: inline-flex; align-items: center; gap: 12px; height: 36px; padding: 0 14px; border: none; border-radius: 18px; background: var(--dsw-alias-bg-module-platform); font: inherit; font-size: 14px; line-height: 22px; color: var(--dsw-alias-label-primary); cursor: pointer; transition: background-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -.lc-settings-select:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); } -.lc-settings-select:disabled { opacity: .5; cursor: default; } .lc-settings-note { color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 1.5; margin: 12px 0 4px; } diff --git a/src/client/styles/stats.css b/src/client/styles/stats.css index 8942e58..62b8dd6 100644 --- a/src/client/styles/stats.css +++ b/src/client/styles/stats.css @@ -16,13 +16,11 @@ .lc-stat-value { color: var(--dsw-alias-label-primary); font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; } .lc-stat-sub { color: var(--dsw-alias-label-secondary); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* - Stats cell tooltip (cost cell): styled DOM bubble because native `title` can't hold the multi-row price breakdown; a '?' signals the affordance and hovering anywhere on the cell reveals the bubble below (the cell is the tip's position frame). Chrome rides the shared `.lc-tip`. + Stats cell tooltip (cost cell): styled DOM bubble because native `title` can't hold the multi-row price breakdown; a '?' signals the affordance and hovering anywhere on the cell reveals the bubble below (the cell is the tip's position frame). Chrome rides the shared `.lc-tip`; the reveal and the badge's brighten ride `group-hover/tip` variants (`group/tip` on the cell). */ .lc-stat-tipped { position: relative; } .lc-stat-q { display: inline-block; margin-left: 4px; width: 11px; height: 11px; line-height: 11px; border-radius: 50%; text-align: center; font-size: 9px; font-weight: 700; font-style: normal; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l2); cursor: help; transition: color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out), border-color var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease-in-out); } -.lc-stat-tipped:hover .lc-stat-q { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-label-primary); } .lc-stat-tip { top: calc(100% + 6px); left: 0; max-width: 280px; line-height: 1.5; font-weight: 400; } -.lc-stat-tipped:hover .lc-stat-tip { opacity: 1; } .lc-stat-tip-prices { display: block; margin-top: 5px; border-top: 1px dashed var(--dsw-alias-border-l1); padding-top: 5px; } .lc-stat-tip-head { display: block; font-weight: 600; } .lc-stat-tip-row { display: block; margin-top: 2px; } @@ -92,9 +90,7 @@ /* The settings-entry row is a button wearing the link-row chrome — reset the button chrome the shared row style doesn't cover. */ .lc-pi-row-btn { appearance: none; border: 0; background: none; font: inherit; color: inherit; text-align: left; padding: 0; cursor: pointer; width: 100%; } .lc-pi-row { display: flex; flex-direction: row; justify-content: space-between; align-items: baseline; gap: 12px; min-width: 0; text-decoration: none; color: inherit; } -.lc-pi-row:hover .lc-pi-value { text-decoration: underline; } .lc-pi-hint { text-decoration: none; } -.lc-pi-hint:hover { text-decoration: underline; } .lc-pi-update { color: var(--dsw-alias-state-warn-primary); font-size: 11px; margin-left: 6px; white-space: nowrap; } .lc-pi-label { flex: none; color: var(--dsw-alias-label-secondary); font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* diff --git a/src/client/styles/tailwind.css b/src/client/styles/tailwind.css index 6503f79..5ec2897 100644 --- a/src/client/styles/tailwind.css +++ b/src/client/styles/tailwind.css @@ -1,10 +1,13 @@ /* - * Tailwind entry: compiles (tsdown.config.ts, @tailwindcss/node) the layout - * utilities carried by the plugin's JSX class lists — box layout, flex/grid, - * spacing, sizing, positioning, overflow, and the @container responsive - * variants. The sibling sheets keep everything Tailwind does not own here: - * the harness's `--dsw-*` color/typography chrome, hover/focus states, - * contextual selectors, animations. + * Tailwind entry: compiles (tsdown.config.ts, @tailwindcss/node) the utilities + * carried by the plugin's JSX class lists — box layout, flex/grid, spacing, + * sizing, positioning, overflow, the @container responsive variants, and the + * pure `:hover`/`:focus` pseudo-state styling (hover/focus/group-hover + * variants; Tailwind media-guards hover behind `(hover: hover)`). The sibling + * sheets keep everything the variants cannot own here: the harness's + * `--dsw-*` color/typography chrome, the JS-state class systems (the lit/dim/ + * selected markers, several of which double as test anchors and DOM query + * hooks), their contextual overrides, and animations. * * Deliberate deviations from a stock Tailwind setup, all for cohabitation * with the harness host page: diff --git a/src/client/styles/trendChart.css b/src/client/styles/trendChart.css index 039dd10..d198738 100644 --- a/src/client/styles/trendChart.css +++ b/src/client/styles/trendChart.css @@ -45,7 +45,6 @@ .lc-chart-tip { position: absolute; bottom: calc(100% + 4px); left: 0; z-index: 5; display: flex; flex-direction: column; gap: 2px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 3px 8px; font-size: 12px; color: var(--dsw-alias-label-primary); box-shadow: 0 2px 8px rgba(0,0,0,0.18); pointer-events: none; } /* The metric row reads secondary to the identity row. */ .lc-chart-tip span + span { color: var(--dsw-alias-label-secondary); } -.lc-bar:hover { background: var(--dsw-alias-bg-layer-2); } /* Hovering previews a bar with a thin dashed frame; a CLICKED (locked) bar must always show the strong solid frame, so the selected rule is diff --git a/tests/client/components/browser.spec.ts b/tests/client/components/browser.spec.ts index 06096c6..a1228fc 100644 --- a/tests/client/components/browser.spec.ts +++ b/tests/client/components/browser.spec.ts @@ -879,7 +879,7 @@ describe('ContextBrowser tool schemas', () => { assert.equal(text(query(rows[0], '.lc-br-preview')), 'write', 'tool name leads') const chip = chips[0] as HTMLElement assert.equal(chip.previousElementSibling, query(rows[0], '.lc-br-preview'), 'plugin chip sits right after the tool name') - assert.equal(chip.parentElement!.className, 'lc-br-elem-row', 'plugin chip is a single frame, a direct row child') + assert.ok(chip.parentElement!.classList.contains('lc-br-elem-row'), 'plugin chip is a single frame, a direct row child') assert.equal(queryAll(chip, '.lc-br-tag').length, 0, 'no nested tag wrapper') await m.unmount() })