Files
gstack/extension/sidepanel.css
T
4dfd83bf8c v1.91.60.0 fix(windows): setup degrades instead of dying on a CSO failure, detects Smart App Control, Consolas terminal font (#3079)
* Fix spaced-out terminal text on Windows: put a Latin monospace before the CJK fallbacks

On Windows none of the Mac/Linux monospace fonts in the stack are
installed, so the first font that resolves is Malgun Gothic (the Windows
Korean UI font). xterm.js sizes its character cell from that font's
advance width, and Malgun Gothic advances Latin glyphs in a full-width
CJK cell — doubling every cell and rendering the whole terminal (and the
sidepanel mono UI) as spaced-out text.

Insert Consolas (present on every Windows install) and the Cascadia
faces ahead of the CJK fonts in both the xterm fontFamily and the
--font-mono CSS var. The CJK fonts stay last for actual CJK coverage.
No bundled font, no user install needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKj8Nr8T4GvryTcWBJRMyC

* fix(windows): every compiled binary answers --version before any side effect

browse --version started a server and exited 1, pdf printed usage and
exited 1, gstack-global-discover rejected the flag, and find-browse ran
discovery. A launch probe for Smart App Control (#2595) needs one
side-effect-free command that exits 0 on a healthy build, so browse,
find-browse, make-pdf and gstack-global-discover now answer --version
first (design already did).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(extension): pin Consolas ahead of the CJK fallbacks in both mono stacks (#2287)

Regression test for @tomfluff's fix: the xterm fontFamily and --font-mono
must list Consolas before Noto Sans Mono CJK KR and Malgun Gothic, or
Windows renders the sidebar terminal double-width.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(cso): record each CSO build's outcome and stop arming the parent trap across the native publisher exec (#3071)

scripts/build-cso.sh now writes bin/.gstack-cso-build-result: result,
failed stage (build or publish), reason, this revision, the revision of
the generation still installed, whether its launcher is usable, and the
diagnostic log. An 'interrupted' entry is written before each stage so a
killed run is visible. A failed publish that restores the old generation
records it as still installed.

Before exec'ing the native publisher, the parent re-checks the locker,
clears its EXIT trap and enables execfail: the locked child's own trap
owns the stage. On Git Bash an exec of a native program may not replace
the shell, so the parent's trap could otherwise delete the stage under
the running child (the vanishing files in #3071; root cause unproven).
If the exec itself fails the parent cleans the stage and records it.

The Windows build prefers PowerShell 7 (pwsh) over Windows PowerShell
5.1, which crashed outright on the reporter's machine.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): an optional CSO build or publish failure no longer aborts setup (#3071)

CSO was optional at probe time but fatal at build time: scripts/build.sh
ran build-cso.sh under set -e before the browse server bundle and the
completion stamp, and setup exited when the CSO artifact set was
incomplete. A failed CSO step now prints 'CSO unavailable: the <stage>
step failed' with its log (bin/.gstack-cso-build.log) and retry, and the
build and setup finish. GSTACK_STRICT_BUILD=1 keeps it fatal; the free-tests,
make-pdf-gate and windows-setup-e2e builds set it, so regressions still
fail CI.

setup's summary reads the build-result record instead of the pre-build
availability flag: probe failures keep their prerequisite instruction;
build and publish failures name the stage, the diagnostic and
'bun run build:cso && ./setup', and a restored earlier helper is
reported as 'previous CSO kept: <revision>'. setup records probe
failures in the same file.

The Windows prerequisite probe prefers pwsh, and a probe that fails for
any reason other than a missing toolchain or a compile error keeps its
exit status and first output line (windows-toolchain-probe) instead of
being labeled windows-msvc-toolchain. Reported and diagnosed by
@dviolante.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(windows): detect Smart App Control-blocked binaries in setup and gstack-doctor (#2595, #2124)

Smart App Control refuses the five unsigned binaries gstack compiles
locally, and Git Bash reports it as 'Permission denied', so setup's
[ -x ] gate passed and skills failed later. bin/gstack-launch-probe.sh
runs each binary's side-effect-free --version and classifies it:
native, blocked (spawn error, exit 126/127, or an application-control
message), broken (launched, exited non-zero) or missing. It shows the
raw first output line, since the exact Windows text is unverified.

On Windows, setup names each blocked binary and the skills that need it,
cites #2595, says which shell ran setup and whether WSL is installed,
gives today's workarounds (WSL, or turning Smart App Control off), how to
undo, and the new troubleshooting section. gstack-doctor's browse row now
says 'blocked at launch' instead of passing, a binaries row covers the
other four, and a cso row reads the build-result record.

windows-setup-e2e builds with GSTACK_STRICT_BUILD=1, checks all five
binaries answer --version, and adds two forced CSO failures: fatal under
strict mode, and a completed setup that reports the failure and a doctor
cso warning under GSTACK_STRICT_BUILD=0.

No shim or launcher: it could not be called from PowerShell and would
load the caller's project .env/bunfig. Signing is the follow-up in
TODOS.md. Diagnosis credit: @knetics9000 (#2595, #2596), @smartjelic-sys
(#2595), @pstilwell90 (#2124), @cko32002 (#2265), @salluexez (#2127).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: Windows Smart App Control known issue, optional CSO failures, doctor rows

README's Windows section says /cso failures no longer stop setup
(GSTACK_STRICT_BUILD=1 keeps them fatal), documents Smart App Control as
a known issue with today's workarounds (WSL, or turning it off) and the
Consolas fix. troubleshooting.md gains a Windows section for blocked
binaries and an entry for CSO build/publish failures. TODOS.md replaces
the Node CLI lane item with Windows code signing (needs a signing
account), the #3071 root-cause follow-up and SAC text verification.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): never write the CSO build record into a source checkout that needed no build

Setup wrote bin/.gstack-cso-build-result for every probe-stage outcome,
so a run that built nothing still changed the source checkout's bin/
(caught by setup-codex-scope's unchanged-source checks). A missing
prerequisite now comes from the run's own probe, as before, and setup
only removes a stale record alongside the removed helper.
scripts/build-cso.sh remains the only writer. The doctor's cso row
reports 'native helper not built' with no record.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(extension): move the Consolas font-stack check into sidebar-ux (#2287)

The standalone file read extension sources without importing product
code, which the test-of-test ratchet rejects; sidebar-ux already owns
the sidepanel.css structural checks.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.43.0 fix(windows): setup degrades instead of dying on a CSO failure, detects Smart App Control, Consolas terminal font

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: Yotam <yotsurfing@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-08 15:43:32 +00:00

1558 lines
35 KiB
CSS

/* gstack browse — Side Panel
* Design system: DESIGN.md (Industrial/Utilitarian, amber accent, zinc neutrals)
*/
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
/* Brand — amber accent, rare and meaningful */
--amber-400: #FBBF24;
--amber-500: #F59E0B;
--amber-600: #D97706;
/* Neutrals — cool zinc */
--zinc-50: #FAFAFA;
--zinc-400: #A1A1AA;
--zinc-600: #52525B;
--zinc-800: #27272A;
/* Surfaces */
--bg-base: #0C0C0C;
--bg-surface: #141414;
--bg-hover: #1a1a1a;
--border: #262626;
--border-subtle: #1f1f1f;
/* Text hierarchy */
--text-heading: #FAFAFA;
--text-body: #e0e0e0;
--text-label: #A1A1AA;
--text-meta: #52525B;
--text-disabled: #3f3f46;
/* Semantic */
--success: #22C55E;
--warning: #F59E0B;
--error: #EF4444;
--info: #3B82F6;
/* Typography */
--font-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
/* Consolas (always on Windows) before the CJK fallbacks — else Latin text resolves to
Malgun Gothic and renders full-width/spaced-out. Same fix as the xterm fontFamily. */
--font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Mono', 'Cascadia Code', Consolas, 'DejaVu Sans Mono', 'Noto Sans Mono CJK KR', 'Malgun Gothic', monospace;
/* Radius */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
}
/* ─── Connection Banner ─────────────────────────────────────────── */
.conn-banner {
padding: 6px 10px;
font-size: 10px;
font-family: var(--font-mono);
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.conn-banner.reconnecting {
background: rgba(245, 158, 11, 0.1);
border-bottom: 1px solid rgba(245, 158, 11, 0.2);
color: var(--amber-400);
}
.conn-banner.dead {
background: rgba(239, 68, 68, 0.1);
border-bottom: 1px solid rgba(239, 68, 68, 0.2);
color: var(--error);
}
.conn-banner.reconnected {
background: rgba(34, 197, 94, 0.1);
border-bottom: 1px solid rgba(34, 197, 94, 0.2);
color: var(--success);
animation: fadeOut 3s ease forwards;
animation-delay: 2s;
}
@keyframes fadeOut {
to { opacity: 0; height: 0; padding: 0; overflow: hidden; }
}
.conn-banner-text {
flex: 1;
}
/* The .security-banner component (Variant A, 2026-04-19) was removed with
its producer: no JS has created a .security-banner element since the
chat-path rip, and the hidden shield it guarded against went in #2557.
Restore from git history if the banner UX returns. */
.conn-btn {
font-size: 9px;
font-family: var(--font-mono);
padding: 2px 8px;
border-radius: var(--radius-sm);
cursor: pointer;
border: 1px solid var(--border);
background: var(--bg-surface);
color: var(--text-label);
transition: all 150ms;
}
.conn-btn:hover {
background: var(--bg-hover);
color: var(--text-heading);
}
.conn-copy {
color: var(--text-meta);
font-style: italic;
}
body {
background: var(--bg-base);
color: var(--text-body);
font-family: var(--font-system);
font-size: 12px;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Grain texture overlay */
body::after {
content: '';
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
pointer-events: none;
z-index: 9999;
opacity: 0.03;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* ─── Status Dot ──────────────────────────────────────── */
.dot {
width: 8px; height: 8px;
border-radius: var(--radius-full);
background: var(--text-disabled);
flex-shrink: 0;
transition: background 150ms;
}
.dot.connected { background: var(--success); }
.dot.reconnecting {
background: var(--amber-500);
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.4; }
50% { opacity: 1; }
}
/* ─── Chat Messages ───────────────────────────────────── */
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 12px;
display: flex;
flex-direction: column;
gap: 8px;
}
.chat-loading {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
height: 100%;
text-align: left;
color: var(--text-meta);
gap: 12px;
font-size: 13px;
padding: 24px;
}
.chat-loading-spinner {
width: 24px;
height: 24px;
border: 2px solid var(--border);
border-top-color: var(--amber-500);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.chat-welcome {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
height: 100%;
text-align: left;
color: var(--text-label);
gap: 8px;
padding: 24px;
}
.chat-welcome-icon {
width: 40px;
height: 40px;
background: var(--amber-500);
color: #000;
font-weight: 800;
font-size: 22px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
}
.chat-welcome .muted { color: var(--text-meta); font-size: 12px; }
.chat-bubble {
max-width: 90%;
padding: 6px 10px;
border-radius: var(--radius-lg);
font-size: 11px;
line-height: 1.4;
word-break: break-word;
animation: slideIn 150ms ease-out;
}
.chat-bubble.user {
align-self: flex-end;
background: var(--amber-500);
color: #000;
border-bottom-right-radius: var(--radius-sm);
}
.chat-notification {
text-align: left;
font-size: 11px;
color: var(--text-meta);
padding: 4px 12px;
font-family: var(--font-mono);
}
.chat-bubble.assistant {
align-self: flex-start;
background: var(--bg-surface);
color: var(--text-body);
border: 1px solid var(--border);
border-bottom-left-radius: var(--radius-sm);
}
.chat-bubble.assistant pre {
background: var(--bg-base);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 6px 8px;
margin: 6px 0;
overflow-x: auto;
font-family: var(--font-mono);
font-size: 12px;
white-space: pre-wrap;
}
.chat-bubble .chat-time, .agent-response > .chat-time {
font-size: 9px;
opacity: 0.4;
margin-top: 2px;
display: block;
}
/* ─── Agent Streaming Response ─────────────────────────── */
.agent-response {
align-self: flex-start;
max-width: 95%;
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
border-bottom-left-radius: var(--radius-sm);
padding: 6px 8px;
display: flex;
flex-direction: column;
gap: 3px;
animation: slideIn 150ms ease-out;
}
.tool-icon {
flex-shrink: 0;
font-size: 11px;
line-height: 1.5;
}
.tool-description {
color: var(--text-body);
line-height: 1.5;
word-break: break-word;
}
/* Legacy classes kept for compat */
.tool-name {
color: var(--amber-500);
font-weight: 600;
flex-shrink: 0;
}
.tool-input {
color: var(--text-disabled);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.agent-text {
color: var(--text-body);
font-size: 12.5px;
line-height: 1.5;
word-break: break-word;
padding: 2px 0;
}
.agent-text pre {
background: var(--bg-base);
border: 1px solid var(--border-subtle);
border-radius: 3px;
padding: 4px 6px;
margin: 4px 0;
overflow-x: auto;
font-family: var(--font-mono);
font-size: 10px;
white-space: pre-wrap;
}
.agent-error {
color: var(--error);
font-size: 12px;
font-family: var(--font-mono);
}
/* Thinking dots animation */
.agent-thinking {
display: flex;
gap: 4px;
padding: 4px 0;
}
.thinking-dot {
width: 4px;
height: 4px;
background: var(--text-disabled);
border-radius: 50%;
animation: thinkingPulse 1.4s ease-in-out infinite;
}
.thinking-dot:nth-child(2) { animation-delay: 0.2s; }
.thinking-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes thinkingPulse {
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
40% { opacity: 1; transform: scale(1); }
}
/* ─── Footer Buttons ──────────────────────────────────── */
.footer-left {
display: flex;
gap: 4px;
}
.footer-btn, .debug-toggle {
background: none;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text-meta);
font-family: var(--font-mono);
font-size: 10px;
padding: 2px 6px;
cursor: pointer;
transition: all 150ms;
}
.footer-btn:hover, .debug-toggle:hover {
color: var(--text-label);
border-color: var(--zinc-600);
}
.debug-toggle.active {
color: var(--amber-400);
border-color: var(--amber-500);
}
.debug-tabs {
border-top: 1px solid var(--border);
}
.close-debug {
width: 36px;
flex: none !important;
font-size: 16px;
color: var(--text-meta) !important;
}
.close-debug:hover { color: var(--text-label) !important; }
/* ─── Tab Bar ─────────────────────────────────────────── */
.tabs {
height: 36px;
background: var(--bg-surface);
border-bottom: 1px solid var(--border);
display: flex;
flex-shrink: 0;
}
.tab {
flex: 1;
background: none;
border: none;
color: var(--text-label);
font-size: 12px;
font-weight: 500;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 150ms;
}
.tab:hover:not(.disabled) { color: var(--zinc-50); }
.tab.active {
color: var(--text-heading);
border-bottom-color: var(--amber-500);
}
.tab.disabled {
color: var(--text-disabled);
cursor: not-allowed;
}
/* ─── Tab Content ─────────────────────────────────────── */
.tab-content {
display: none;
flex: 1;
overflow-y: auto;
overflow-x: hidden;
}
.tab-content.active { display: flex; flex-direction: column; }
/* ─── Terminal Tab ────────────────────────────────────────────── */
/* The Terminal pane manages its own scrolling (xterm has a viewport with
scrollback). The default .tab-content rules above set overflow-y: auto,
which collapses min-height for nested flex children — that's why
.terminal-mount couldn't grow to fill available space. Override here. */
#tab-terminal {
background: #0a0a0a;
padding: 0;
overflow: hidden;
min-height: 0;
}
#tab-terminal.active {
display: flex;
flex-direction: column;
}
.terminal-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
padding: 4px 8px;
border-bottom: 1px solid #1a1a1a;
background: #0a0a0a;
flex-shrink: 0;
}
.terminal-toolbar-actions {
display: flex;
gap: 4px;
flex-wrap: wrap;
}
.terminal-toolbar-btn {
background: transparent;
border: 1px solid #27272a;
color: #a1a1aa;
padding: 3px 10px;
font-size: 11px;
font-family: 'JetBrains Mono', monospace;
border-radius: 3px;
cursor: pointer;
}
.terminal-toolbar-btn:hover {
color: #f59e0b;
border-color: #f59e0b;
}
.terminal-bootstrap {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
color: #71717a;
padding: 24px;
}
.terminal-bootstrap-icon {
font-size: 32px;
color: #f59e0b;
margin-bottom: 8px;
}
.terminal-bootstrap p { margin: 4px 0; }
.terminal-install-card {
margin: 24px;
padding: 16px;
border: 1px solid #27272a;
border-radius: 6px;
text-align: center;
}
.terminal-install-card a { color: #f59e0b; }
.install-retry-btn {
margin-top: 12px;
padding: 6px 14px;
background: #f59e0b;
color: #0a0a0a;
border: none;
border-radius: 4px;
font-family: inherit;
font-size: 12px;
cursor: pointer;
}
.install-retry-btn:hover { opacity: 0.9; }
.terminal-mount {
/* min-height: 0 is the standard flex-overflow fix — without it, a flex
item with overflowing content can't shrink below its content size,
so flex:1 refuses to expand into available space and xterm renders
into whatever the content happens to be (i.e. its own initial 2x2
measurement). With min-height:0 the item respects the flex parent's
remaining space and xterm grows to fill it. */
flex: 1 1 0;
min-height: 0;
width: 100%;
background: #0a0a0a;
padding: 8px;
box-sizing: border-box;
/* position: relative so xterm's absolutely-positioned helpers (the
hidden textarea for input) anchor inside us, not on body. */
position: relative;
}
.terminal-mount .xterm,
.terminal-mount .xterm .xterm-viewport,
.terminal-mount .xterm .xterm-screen {
height: 100% !important;
}
.terminal-ended {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #71717a;
padding: 24px;
}
/* ─── Activity Feed ───────────────────────────────────── */
#activity-feed { flex: 1; }
.activity-entry {
padding: 8px 12px;
border-left: 3px solid var(--border);
border-bottom: 1px solid var(--border-subtle);
cursor: pointer;
transition: background 150ms;
animation: slideIn 150ms ease-out;
}
.activity-entry:hover { background: var(--bg-hover); }
@media (prefers-reduced-motion: reduce) {
.activity-entry { animation: none; }
}
@keyframes slideIn {
from { transform: translateY(8px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
/* Left border colors by type */
.activity-entry.nav { border-left-color: var(--info); }
.activity-entry.interaction { border-left-color: var(--success); }
.activity-entry.observe { border-left-color: var(--amber-400); }
.activity-entry.error { border-left-color: var(--error); }
.activity-entry.pending {
border-left-color: var(--amber-500);
animation: slideIn 150ms ease-out, borderPulse 2s ease-in-out infinite;
}
@keyframes borderPulse {
0%, 100% { border-left-color: rgba(245, 158, 11, 0.3); }
50% { border-left-color: rgba(245, 158, 11, 1); }
}
.entry-header {
display: flex;
align-items: baseline;
gap: 8px;
}
.entry-time {
color: var(--text-meta);
font-family: var(--font-mono);
font-size: 11px;
flex-shrink: 0;
}
.entry-command {
color: var(--text-heading);
font-family: var(--font-mono);
font-size: 13px;
font-weight: 600;
}
.entry-args {
color: var(--text-label);
font-family: var(--font-mono);
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 2px;
}
.entry-status {
font-size: 11px;
margin-top: 2px;
display: flex;
align-items: center;
gap: 4px;
}
.entry-status .ok { color: var(--success); }
.entry-status .err { color: var(--error); }
.entry-status .duration { color: var(--text-meta); }
/* Expanded state */
.entry-detail {
display: none;
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed var(--border);
}
.activity-entry.expanded .entry-detail { display: block; }
.activity-entry.expanded .entry-args { white-space: normal; }
.entry-result {
color: var(--zinc-400);
font-family: var(--font-mono);
font-size: 12px;
white-space: pre-wrap;
word-break: break-word;
}
/* ─── Refs Tab ────────────────────────────────────────── */
.ref-row {
height: 32px;
display: flex;
align-items: center;
gap: 8px;
padding: 0 12px;
border-bottom: 1px solid var(--border-subtle);
font-size: 12px;
}
.ref-id {
color: var(--amber-400);
font-family: var(--font-mono);
font-weight: 600;
min-width: 32px;
}
.ref-role {
color: var(--text-label);
min-width: 60px;
}
.ref-name {
color: var(--text-body);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.refs-footer {
padding: 8px 12px;
color: var(--text-meta);
font-size: 11px;
border-top: 1px solid var(--border);
}
/* ─── Session Placeholder ─────────────────────────────── */
.session-placeholder {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
height: 100%;
text-align: left;
color: var(--text-label);
padding: 24px;
gap: 8px;
}
.session-placeholder .muted { color: var(--text-meta); font-size: 12px; }
/* ─── Empty State ─────────────────────────────────────── */
.empty-state {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding: 40px 24px;
text-align: left;
color: var(--text-label);
gap: 4px;
}
.empty-state .muted { color: var(--text-meta); font-size: 12px; }
.empty-state code {
background: var(--bg-surface);
padding: 2px 6px;
border-radius: var(--radius-sm);
font-family: var(--font-mono);
font-size: 12px;
}
/* ─── Gap Banner ──────────────────────────────────────── */
.gap-banner {
background: rgba(245, 158, 11, 0.08);
border-bottom: 1px solid var(--amber-500);
color: var(--amber-400);
font-size: 11px;
padding: 6px 12px;
animation: bannerSlide 250ms ease-out;
}
@keyframes bannerSlide {
from { transform: translateY(-100%); }
to { transform: translateY(0); }
}
/* ─── Command Bar ─────────────────────────────────────── */
/* ─── Quick Actions Toolbar ─────────────────────────────── */
.quick-actions {
display: flex;
gap: 6px;
padding: 4px 8px;
background: var(--bg-surface);
border-top: 1px solid var(--border-subtle);
flex-shrink: 0;
}
.quick-action-btn {
display: flex;
align-items: center;
gap: 4px;
height: 26px;
padding: 0 10px;
background: none;
border: 1px solid var(--zinc-600);
border-radius: var(--radius-sm);
color: var(--text-label);
font-family: var(--font-system);
font-size: 11px;
cursor: pointer;
transition: all 150ms;
}
.quick-action-btn:hover {
background: rgba(255, 255, 255, 0.05);
color: var(--text-body);
border-color: var(--zinc-400);
}
.quick-action-btn:active {
transform: scale(0.96);
}
.quick-action-btn.disabled, .inspector-action-btn.disabled {
pointer-events: none;
opacity: 0.3;
cursor: not-allowed;
}
.quick-action-btn.loading {
pointer-events: none;
opacity: 0.5;
}
.quick-action-btn.loading::after {
content: '';
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid var(--zinc-600);
border-top-color: var(--amber-400);
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
.command-bar {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
background: var(--bg-surface);
border-top: 1px solid var(--border);
flex-shrink: 0;
}
.command-prompt {
color: var(--amber-500);
font-family: var(--font-mono);
font-size: 12px;
font-weight: 700;
flex-shrink: 0;
user-select: none;
}
.command-input {
flex: 1;
background: var(--bg-base);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: 6px 8px;
color: var(--text-heading);
font-family: var(--font-system);
font-size: 11px;
outline: none;
transition: border-color 150ms;
}
.command-input:focus { border-color: var(--amber-500); }
.command-input::placeholder { color: var(--text-disabled); font-size: 10px; }
.command-input.sent {
border-color: var(--success);
transition: border-color 150ms;
}
.command-input.error {
border-color: var(--error);
animation: shake 300ms ease;
}
.command-input.error::placeholder {
color: var(--error);
opacity: 0.8;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-4px); }
75% { transform: translateX(4px); }
}
.send-btn {
width: 26px;
height: 26px;
background: var(--amber-500);
border: none;
border-radius: var(--radius-sm);
color: #000;
font-size: 14px;
font-weight: 700;
cursor: pointer;
flex-shrink: 0;
transition: all 150ms;
display: flex;
align-items: center;
justify-content: center;
}
.send-btn:hover { background: var(--amber-400); }
.send-btn:active { transform: scale(0.93); }
.send-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
/* ─── Footer ──────────────────────────────────────────── */
footer {
height: 28px;
background: var(--bg-surface);
border-top: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 8px;
font-size: 10px;
color: var(--text-meta);
flex-shrink: 0;
}
#footer-url {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 50%;
}
.footer-right {
display: flex;
align-items: center;
gap: 6px;
}
.footer-port {
color: var(--text-meta);
font-family: var(--font-mono);
font-size: 11px;
cursor: pointer;
transition: color 150ms;
}
.footer-port:hover { color: var(--text-label); }
.footer-mem {
color: var(--text-meta);
font-family: var(--font-mono);
font-size: 11px;
margin-right: 6px;
padding: 1px 6px;
border-radius: var(--radius-sm);
transition: color 150ms;
}
.footer-mem.warn {
color: #f59e0b;
}
.footer-mem.bad {
color: #ef4444;
}
/* ─── Memory pressure toast ─────────────────────────────────── */
.mem-toast {
position: fixed;
left: 12px;
right: 12px;
bottom: 44px;
z-index: 9999;
background: var(--bg-elevated, #1f1f23);
border: 1px solid #ef4444;
border-radius: var(--radius-md, 6px);
padding: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
font-family: var(--font-system); /* --font-sans was never defined; the invalid var fell back to inheritance by luck */
font-size: 12px;
}
.mem-toast-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.mem-toast-header strong {
color: var(--text-heading);
font-size: 13px;
}
.mem-toast-close {
background: transparent;
border: none;
color: var(--text-meta);
cursor: pointer;
font-size: 18px;
line-height: 1;
padding: 0 4px;
}
.mem-toast-close:hover { color: var(--text-heading); }
.mem-toast-body {
margin-bottom: 8px;
color: var(--text-body);
line-height: 1.4;
}
.mem-toast-body .mem-toast-row {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
}
.mem-toast-body .mem-toast-row label {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.mem-toast-body .mem-toast-size {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-meta);
width: 70px;
text-align: right;
}
.mem-toast-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.mem-toast-btn {
background: var(--bg-base);
border: 1px solid var(--zinc-600);
border-radius: var(--radius-sm, 4px);
color: var(--text-body);
cursor: pointer;
font-size: 12px;
padding: 4px 12px;
}
.mem-toast-btn:hover { background: var(--zinc-700, #3F3F46); } /* --zinc-700 is undefined in :root; without the fallback this hover computed to transparent */
.mem-toast-btn.primary {
background: #ef4444;
border-color: #ef4444;
color: #fff;
}
.mem-toast-btn.primary:hover { background: #dc2626; }
.port-input {
width: 56px;
padding: 2px 6px;
background: var(--bg-base);
border: 1px solid var(--zinc-600);
border-radius: var(--radius-sm);
color: var(--text-heading);
font-family: var(--font-mono);
font-size: 11px;
outline: none;
transition: border-color 150ms;
}
.port-input:focus { border-color: var(--amber-500); }
/* ─── Browser Tab Bar ─────────────────────────────────── */
.browser-tabs {
display: flex;
gap: 1px;
padding: 4px 8px;
background: var(--bg-base);
border-bottom: 1px solid var(--border);
overflow-x: auto;
flex-shrink: 0;
scrollbar-width: none;
}
.browser-tabs::-webkit-scrollbar { display: none; }
.browser-tab {
padding: 4px 10px;
font-size: 11px;
font-family: var(--font-system);
color: var(--text-meta);
background: transparent;
border: 1px solid transparent;
border-radius: var(--radius-sm);
cursor: pointer;
white-space: nowrap;
max-width: 140px;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 0;
transition: background 100ms, color 100ms;
}
.browser-tab:hover {
background: var(--bg-hover);
color: var(--text-label);
}
.browser-tab.active {
background: var(--bg-surface);
color: var(--text-body);
border-color: var(--border);
}
/* ─── Inspector Tab ──────────────────────────────────── */
.inspector-toolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
background: var(--bg-surface);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.inspector-pick-btn {
display: flex;
align-items: center;
gap: 4px;
height: 28px;
padding: 0 10px;
background: none;
border: 1px solid var(--amber-500);
border-radius: var(--radius-sm);
color: var(--amber-500);
font-family: var(--font-system);
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 150ms;
flex-shrink: 0;
}
.inspector-pick-btn:hover {
background: rgba(245, 158, 11, 0.1);
color: var(--amber-400);
}
.inspector-pick-btn.active {
background: var(--amber-500);
color: #000;
}
.inspector-pick-icon {
font-size: 14px;
line-height: 1;
}
/* ─── Action Buttons (Cleanup, Screenshot) ─────────────────── */
.inspector-action-btn {
display: flex;
align-items: center;
justify-content: center;
height: 28px;
width: 28px;
padding: 0;
background: none;
border: 1px solid var(--zinc-600);
border-radius: var(--radius-sm);
color: var(--text-label);
font-size: 14px;
cursor: pointer;
transition: all 150ms;
flex-shrink: 0;
}
.inspector-action-btn:hover {
background: rgba(255, 255, 255, 0.05);
color: var(--text-body);
border-color: var(--zinc-400);
}
.inspector-action-btn:active {
transform: scale(0.95);
}
.inspector-action-btn.loading {
pointer-events: none;
opacity: 0.5;
position: relative;
}
.inspector-action-btn.loading::after {
content: '';
position: absolute;
width: 12px;
height: 12px;
border: 2px solid var(--zinc-600);
border-top-color: var(--amber-400);
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.inspector-selected {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-body);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
min-width: 0;
}
.inspector-mode-badge {
font-family: var(--font-mono);
font-size: 10px;
padding: 1px 6px;
border-radius: var(--radius-sm);
flex-shrink: 0;
}
.inspector-mode-badge.basic {
background: var(--zinc-800);
color: var(--zinc-400);
}
.inspector-mode-badge.cdp {
background: rgba(34, 197, 94, 0.15);
color: var(--success);
}
/* Inspector content area */
.inspector-content {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
}
/* Empty state */
.inspector-empty {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding: 40px 24px;
text-align: left;
gap: 6px;
}
.inspector-empty-icon {
font-size: 24px;
color: var(--zinc-600);
margin-bottom: 4px;
}
.inspector-empty p {
color: var(--zinc-400);
font-size: 13px;
margin: 0;
}
.inspector-empty .muted {
color: var(--zinc-600);
font-size: 12px;
}
/* Loading state */
.inspector-loading {
padding: 16px 12px;
}
.inspector-loading-text {
font-size: 12px;
color: var(--amber-500);
margin-bottom: 12px;
animation: pulse 2s ease-in-out infinite;
}
.inspector-skeleton {
display: flex;
flex-direction: column;
gap: 8px;
}
.inspector-skeleton-bar {
height: 12px;
background: var(--zinc-800);
border-radius: var(--radius-sm);
animation: shimmer 1.5s ease-in-out infinite;
}
.inspector-skeleton-bar:nth-child(1) { width: 80%; }
.inspector-skeleton-bar:nth-child(2) { width: 60%; }
.inspector-skeleton-bar:nth-child(3) { width: 70%; }
@keyframes shimmer {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.7; }
}
/* Error state */
.inspector-error {
padding: 16px 12px;
color: var(--error);
font-size: 12px;
font-family: var(--font-mono);
}
/* Inspector sections */
.inspector-section {
border-bottom: 1px solid var(--border-subtle);
}
.inspector-section-header {
font-family: var(--font-system);
font-size: 13px;
font-weight: 600;
color: var(--zinc-400);
padding: 8px 12px 4px;
}
.inspector-section-toggle {
display: flex;
align-items: center;
gap: 6px;
width: 100%;
padding: 8px 12px;
background: none;
border: none;
font-family: var(--font-system);
font-size: 13px;
font-weight: 600;
color: var(--zinc-400);
cursor: pointer;
text-align: left;
transition: color 150ms;
}
.inspector-section-toggle:hover {
color: var(--text-body);
}
.inspector-toggle-arrow {
font-size: 10px;
color: var(--zinc-400);
flex-shrink: 0;
width: 12px;
}
.inspector-section-body {
padding: 4px 12px 8px;
}
.inspector-section-body.collapsed {
display: none;
}
.inspector-rule-count {
font-size: 11px;
font-weight: 400;
color: var(--zinc-600);
margin-left: 4px;
}
.inspector-no-data {
color: var(--zinc-600);
font-size: 11px;
font-style: italic;
padding: 4px 0;
}
/* ─── Box Model ──────────────────────────────────────── */
.inspector-boxmodel {
padding: 8px 12px 12px;
}
.boxmodel-margin,
.boxmodel-border,
.boxmodel-padding,
.boxmodel-content {
position: relative;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed;
text-align: center;
}
.boxmodel-margin {
background: rgba(245, 158, 11, 0.08);
border-color: rgba(245, 158, 11, 0.3);
padding: 14px 20px;
border-radius: var(--radius-sm);
}
.boxmodel-border {
background: rgba(161, 161, 170, 0.08);
border-color: rgba(161, 161, 170, 0.3);
padding: 14px 20px;
width: 100%;
}
.boxmodel-padding {
background: rgba(34, 197, 94, 0.08);
border-color: rgba(34, 197, 94, 0.3);
padding: 14px 20px;
width: 100%;
}
.boxmodel-content {
background: rgba(59, 130, 246, 0.08);
border-color: rgba(59, 130, 246, 0.3);
padding: 8px 12px;
width: 100%;
min-height: 28px;
}
.boxmodel-content span {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-body);
}
.boxmodel-label {
position: absolute;
top: 1px;
left: 4px;
font-family: var(--font-mono);
font-size: 10px;
color: var(--zinc-400);
pointer-events: none;
}
.boxmodel-value {
position: absolute;
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-body);
}
.boxmodel-value.boxmodel-top { top: 1px; left: 50%; transform: translateX(-50%); }
.boxmodel-value.boxmodel-right { right: 4px; top: 50%; transform: translateY(-50%); }
.boxmodel-value.boxmodel-bottom { bottom: 1px; left: 50%; transform: translateX(-50%); }
.boxmodel-value.boxmodel-left { left: 4px; top: 50%; transform: translateY(-50%); }
/* ─── Matched Rules ──────────────────────────────────── */
.inspector-rule {
padding: 6px 0;
border-bottom: 1px solid var(--border-subtle);
}
.inspector-rule:last-child {
border-bottom: none;
}
.inspector-rule-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 2px;
}
.inspector-selector {
font-family: var(--font-mono);
font-size: 12px;
color: var(--amber-400);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 35ch;
}
.inspector-specificity {
font-family: var(--font-mono);
font-size: 10px;
background: var(--zinc-600);
color: var(--zinc-400);
padding: 0 4px;
border-radius: var(--radius-sm);
flex-shrink: 0;
}
.inspector-rule-props {
padding-left: 12px;
}
.inspector-prop {
font-family: var(--font-mono);
font-size: 12px;
line-height: 1.6;
}
.inspector-prop.overridden {
text-decoration: line-through;
opacity: 0.5;
}
.inspector-prop-name {
color: var(--zinc-400);
}
.inspector-prop-value {
color: var(--text-body);
}
.inspector-important {
color: var(--error);
font-size: 10px;
}
.inspector-rule-source {
font-family: var(--font-mono);
font-size: 11px;
color: var(--zinc-600);
margin-top: 2px;
}
/* UA rules */
.inspector-ua-rules {
margin-top: 4px;
}
.inspector-ua-toggle {
display: flex;
align-items: center;
gap: 4px;
background: none;
border: none;
font-family: var(--font-mono);
font-size: 11px;
color: var(--zinc-600);
cursor: pointer;
padding: 4px 0;
transition: color 150ms;
}
.inspector-ua-toggle:hover {
color: var(--zinc-400);
}
.inspector-ua-body.collapsed {
display: none;
}
/* ─── Computed Styles ────────────────────────────────── */
.inspector-computed-row {
font-family: var(--font-mono);
font-size: 12px;
line-height: 1.6;
padding: 0 0 0 4px;
}
.inspector-computed-row .inspector-prop-name {
color: var(--zinc-400);
}
.inspector-computed-row .inspector-prop-value {
color: var(--text-body);
}
/* ─── Quick Edit ─────────────────────────────────────── */
.inspector-quickedit-list {
display: flex;
flex-direction: column;
gap: 2px;
}
.inspector-quickedit-row {
font-family: var(--font-mono);
font-size: 12px;
line-height: 1.6;
display: flex;
align-items: center;
gap: 4px;
}
.inspector-quickedit-row .inspector-prop-name {
color: var(--zinc-400);
flex-shrink: 0;
}
.inspector-quickedit-value {
color: var(--text-body);
cursor: pointer;
padding: 1px 4px;
border-radius: 2px;
transition: background 150ms;
min-width: 40px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.inspector-quickedit-value:hover {
background: var(--bg-hover);
}
.inspector-quickedit-input {
font-family: var(--font-mono);
font-size: 12px;
background: var(--bg-base);
border: 1px solid var(--amber-500);
border-radius: 2px;
color: var(--text-heading);
padding: 1px 4px;
outline: none;
width: 100%;
}
/* ─── Send to Agent ──────────────────────────────────── */
.inspector-send {
padding: 8px 12px;
background: var(--bg-surface);
border-top: 1px solid var(--border);
flex-shrink: 0;
position: sticky;
bottom: 0;
}
.inspector-send-btn {
width: 100%;
height: 32px;
background: var(--amber-500);
border: none;
border-radius: var(--radius-md);
color: #000;
font-family: var(--font-system);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 150ms;
}
.inspector-send-btn:hover {
background: var(--amber-400);
}
.inspector-send-btn:active {
transform: scale(0.98);
}
/* ─── Accessibility ───────────────────────────────────── */
:focus-visible {
outline: 2px solid var(--amber-500);
outline-offset: 1px;
}