Files
orca/src/renderer/src/assets/main.css
T
Jinjing 2ac23c29be Add parent worktree selection when creating workspace (#15420)
* feat: select parent worktree for nesting when creating workspace

Enable users to pick a parent workspace in the composer's Advanced drawer,
organizing newly created worktrees hierarchically in the sidebar. The backend
validates lineage relationships and gracefully retries without the parent if
it becomes unavailable during creation.

* feat: select parent worktree for nesting when creating workspace

- Record app-picked parents as manual actions, not CLI-flag equivalents,
  ensuring the same user action carries consistent cleanup semantics across hosts
- Gracefully retry without parent if the selection goes stale, warn the
  user instead of failing
- Refactor create into modules: parent resolution, payload building,
  state merge

* Allow selecting parent worktree when creating workspace

- New parent worktree picker filtered by execution host and project
- Preserve concurrent local writes to lineage by comparing per-record state instead of key-set membership

* Allow selecting parent worktree when creating workspace

- Rename "Parent workspace" label to "Parent worktree"
- Filter candidates by execution host and project to prevent nesting across hosts
- Wire parentWorktreeId through composer state and creation request pipeline
- Update translations and add new copy for nesting-related messages
2026-08-27 19:38:01 -07:00

3024 lines
92 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@import 'tailwindcss';
@import 'tw-animate-css';
@import '@xterm/xterm/css/xterm.css';
@import 'katex/dist/katex.min.css';
@import './rich-markdown-editor.css';
@import './markdown-preview.css';
@import './terminal.css';
@import './mobile-page.css';
@font-face {
font-family: 'Geist';
src: url('./fonts/Geist-Variable.woff2') format('woff2');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Orca Nerd Font Symbols';
src: url('./fonts/SymbolsNerdFontMono-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+E000-F8FF, U+F0000-FFFFD, U+100000-10FFFD;
}
@custom-variant dark (&:is(.dark *));
/* Why: hover-reveal controls (sidebar/row action buttons) hide with `opacity-0`
and fade in via `group-hover`. Touch devices have no hover, so those controls
stay invisible. Gate the hide behind `can-hover:` so touch (hover: none) keeps
them visible while pointer devices preserve the reveal-on-hover behavior. */
@custom-variant can-hover (@media (hover: hover));
.theme-transition-disabled *,
.theme-transition-disabled *::before,
.theme-transition-disabled *::after {
/* Why: theme swaps flip many CSS variables at once; transitions make parts of
the UI fade at different speeds. Animations stay enabled for spinners. */
transition: none !important;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-agent-question: var(--agent-question);
--color-agent-question-text: var(--agent-question-text);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-worktree-sidebar: var(--worktree-sidebar);
--color-worktree-sidebar-foreground: var(--worktree-sidebar-foreground);
--color-worktree-sidebar-accent: var(--worktree-sidebar-accent);
--color-worktree-sidebar-accent-foreground: var(--worktree-sidebar-accent-foreground);
--color-worktree-sidebar-border: var(--worktree-sidebar-border);
--color-worktree-sidebar-ring: var(--worktree-sidebar-ring);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--color-terminal-pane-title-on-dark-fg: var(--terminal-pane-title-on-dark-fg);
--color-terminal-pane-title-on-dark-input-fg: var(--terminal-pane-title-on-dark-input-fg);
--color-terminal-pane-title-on-dark-placeholder: var(--terminal-pane-title-on-dark-placeholder);
--color-terminal-pane-title-on-dark-button-fg: var(--terminal-pane-title-on-dark-button-fg);
--color-terminal-pane-title-on-dark-button-hover-fg: var(
--terminal-pane-title-on-dark-button-hover-fg
);
--color-terminal-pane-title-on-dark-input-bg: var(--terminal-pane-title-on-dark-input-bg);
--color-terminal-pane-title-on-dark-separator: var(--terminal-pane-title-on-dark-separator);
--color-terminal-pane-title-on-light-fg: var(--terminal-pane-title-on-light-fg);
--color-terminal-pane-title-on-light-input-fg: var(--terminal-pane-title-on-light-input-fg);
--color-terminal-pane-title-on-light-placeholder: var(--terminal-pane-title-on-light-placeholder);
--color-terminal-pane-title-on-light-button-fg: var(--terminal-pane-title-on-light-button-fg);
--color-terminal-pane-title-on-light-button-hover-fg: var(
--terminal-pane-title-on-light-button-hover-fg
);
--color-terminal-pane-title-on-light-input-bg: var(--terminal-pane-title-on-light-input-bg);
--color-terminal-pane-title-on-light-separator: var(--terminal-pane-title-on-light-separator);
--color-ai-action-accent: var(--ai-action-accent);
--color-status-success: var(--status-success);
--color-status-success-background: var(--status-success-background);
--color-status-success-border: var(--status-success-border);
--color-workspace-status-done: var(--workspace-status-done);
--color-workspace-status-review: var(--workspace-status-review);
--color-workspace-status-progress: var(--workspace-status-progress);
--color-tab-group-split-divider: var(--tab-group-split-divider);
--color-tab-group-split-divider-strong: var(--tab-group-split-divider-strong);
--color-git-graph-ref: var(--git-graph-ref);
--color-git-graph-remote-ref: var(--git-graph-remote-ref);
--color-git-graph-base-ref: var(--git-graph-base-ref);
--color-git-graph-lane-1: var(--git-graph-lane-1);
--color-git-graph-lane-2: var(--git-graph-lane-2);
--color-git-graph-lane-3: var(--git-graph-lane-3);
--color-git-graph-lane-4: var(--git-graph-lane-4);
--color-git-graph-lane-5: var(--git-graph-lane-5);
--color-annotation-highlight: var(--annotation-highlight);
--shadow-floating: 0 10px 24px rgb(0 0 0 / 0.18);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
--radius-2xl: calc(var(--radius) * 1.8);
--radius-3xl: calc(var(--radius) * 2.2);
--radius-4xl: calc(var(--radius) * 2.6);
}
/* ── Light Mode ──────────────────────────────────────── */
:root {
--app-font-family: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-sans: var(--app-font-family);
--font-mono:
'SF Mono', SFMono-Regular, ui-monospace, 'Cascadia Code', Menlo, Consolas, 'Liberation Mono',
monospace;
--radius: 0.625rem;
--orca-security-background: #fff;
--orca-security-foreground: #0a0a0a;
--orca-security-card: #fff;
--orca-security-card-foreground: #0a0a0a;
--orca-security-popover: #fff;
--orca-security-popover-foreground: #0a0a0a;
--orca-security-primary: #171717;
--orca-security-primary-foreground: #fafafa;
--orca-security-secondary: #f5f5f5;
--orca-security-secondary-foreground: #171717;
--orca-security-muted: #f5f5f5;
--orca-security-muted-foreground: #737373;
--orca-security-accent: #f5f5f5;
--orca-security-accent-foreground: #171717;
--orca-security-border: #e5e5e5;
--orca-security-input: #e5e5e5;
--orca-security-ring: #a1a1a1;
--background: #fff;
--editor-surface: #ffffff;
--foreground: #0a0a0a;
--card: #fff;
--card-foreground: #0a0a0a;
--popover: #fff;
--popover-foreground: #0a0a0a;
--primary: #171717;
--primary-foreground: #fafafa;
--secondary: #f5f5f5;
--secondary-foreground: #171717;
--muted: #f5f5f5;
--muted-foreground: #737373;
--accent: #f5f5f5;
--accent-foreground: #171717;
--destructive: #e40014;
--destructive-foreground: #fcf3f3;
--border: #e5e5e5;
--input: #e5e5e5;
--ring: #a1a1a1;
--terminal-pane-locate: var(--color-blue-600);
--ai-action-accent: var(--color-violet-500);
/* "An agent is asking you something" — one hue for every surface that shows
it (sidebar, tabs, dashboard, agent map). Orange, not amber: on the map it
has to stay separable from working-yellow at a glance. */
--agent-question: var(--color-orange-600);
/* Legible weight for text/glyphs on a tinted --agent-question surface. */
--agent-question-text: var(--color-orange-700);
--status-success: #15803d;
--status-success-background: color-mix(in srgb, var(--status-success) 10%, transparent);
--status-success-border: color-mix(in srgb, var(--status-success) 25%, transparent);
/* Workspace PR-state tones: same hue in both themes so a status keeps its
identity when the theme flips. */
--workspace-status-done: #c7a594;
--workspace-status-review: #16a34a;
--workspace-status-progress: #d4a300;
--chart-1: var(--color-blue-300);
--chart-2: var(--color-blue-500);
--chart-3: var(--color-blue-600);
--chart-4: var(--color-blue-700);
--chart-5: var(--color-blue-800);
--worktree-sidebar: #f5f5f5;
--worktree-sidebar-foreground: #0a0a0a;
--worktree-sidebar-accent: #eaeaea;
--worktree-sidebar-accent-foreground: #171717;
--worktree-sidebar-border: #e5e5e5;
--worktree-sidebar-ring: #a1a1a1;
--sidebar: #fafafa;
--sidebar-foreground: #0a0a0a;
--sidebar-primary: #171717;
--sidebar-primary-foreground: #fafafa;
--sidebar-accent: #f5f5f5;
--sidebar-accent-foreground: #171717;
--sidebar-border: #e5e5e5;
--sidebar-ring: #a1a1a1;
--terminal-pane-title-on-dark-fg: rgb(255 255 255 / 0.52);
--terminal-pane-title-on-dark-input-fg: rgb(255 255 255 / 0.7);
--terminal-pane-title-on-dark-placeholder: rgb(255 255 255 / 0.38);
--terminal-pane-title-on-dark-button-fg: rgb(255 255 255);
--terminal-pane-title-on-dark-button-hover-fg: rgb(255 255 255);
--terminal-pane-title-on-dark-input-bg: rgb(255 255 255 / 0.04);
--terminal-pane-title-on-dark-separator: rgb(255 255 255 / 0.06);
--terminal-pane-title-on-light-fg: rgb(24 24 27 / 0.64);
--terminal-pane-title-on-light-input-fg: rgb(24 24 27 / 0.82);
--terminal-pane-title-on-light-placeholder: rgb(24 24 27 / 0.48);
--terminal-pane-title-on-light-button-fg: rgb(24 24 27);
--terminal-pane-title-on-light-button-hover-fg: rgb(24 24 27);
--terminal-pane-title-on-light-input-bg: rgb(24 24 27 / 0.05);
--terminal-pane-title-on-light-separator: rgb(24 24 27 / 0.1);
--git-decoration-added: #587c0c;
--git-decoration-modified: #895503;
--git-decoration-deleted: #ad0707;
--git-decoration-renamed: #007acc;
--git-decoration-untracked: #007100;
--git-decoration-copied: #007acc;
--git-decoration-ignored: #8c8c8c;
--git-graph-ref: #007acc;
--git-graph-remote-ref: #b66dff;
--git-graph-base-ref: #ea5c00;
--git-graph-lane-1: #ffb000;
--git-graph-lane-2: #dc267f;
--git-graph-lane-3: #994f00;
--git-graph-lane-4: #40b0a6;
--git-graph-lane-5: #b66dff;
--annotation-highlight: #f59e0b;
--markdown-search-match: #facc15;
--markdown-search-match-active: #fb923c;
/* Why: tab-group splits use a dedicated divider — `--border` is too faint on
card/editor surfaces when panes are side-by-side or stacked. Default must
meet >=3:1 against `--card`, not only the hover/drag strong state. */
--tab-group-split-divider: #868690;
--tab-group-split-divider-strong: #71717a;
}
/* ── Dark Mode ───────────────────────────────────────── */
.dark {
--orca-security-background: #0a0a0a;
--orca-security-foreground: #fafafa;
--orca-security-card: #171717;
--orca-security-card-foreground: #fafafa;
--orca-security-popover: #171717;
--orca-security-popover-foreground: #fafafa;
--orca-security-primary: #e5e5e5;
--orca-security-primary-foreground: #171717;
--orca-security-secondary: #262626;
--orca-security-secondary-foreground: #fafafa;
--orca-security-muted: #262626;
--orca-security-muted-foreground: #a1a1a1;
--orca-security-accent: #404040;
--orca-security-accent-foreground: #fafafa;
--orca-security-border: rgb(255 255 255 / 0.07);
--orca-security-input: rgb(255 255 255 / 0.15);
--orca-security-ring: #737373;
--background: #0a0a0a;
--editor-surface: #1e1e1e;
--foreground: #fafafa;
--card: #171717;
--card-foreground: #fafafa;
--popover: #171717;
--popover-foreground: #fafafa;
--primary: #e5e5e5;
--primary-foreground: #171717;
--secondary: #262626;
--secondary-foreground: #fafafa;
--muted: #262626;
--muted-foreground: #a1a1a1;
--accent: #404040;
--accent-foreground: #fafafa;
--destructive: #ff6568;
--destructive-foreground: #df2225;
--border: rgb(255 255 255 / 0.07);
--input: rgb(255 255 255 / 0.15);
--ring: #737373;
--terminal-pane-locate: var(--color-blue-400);
--ai-action-accent: var(--color-violet-400);
--agent-question: var(--color-orange-500);
--agent-question-text: var(--color-orange-300);
--status-success: #86efac;
--status-success-background: color-mix(in srgb, var(--status-success) 10%, transparent);
--status-success-border: color-mix(in srgb, var(--status-success) 25%, transparent);
--workspace-status-done: #c7a594;
--workspace-status-review: #16a34a;
--workspace-status-progress: #d4a300;
--chart-1: var(--color-blue-300);
--chart-2: var(--color-blue-500);
--chart-3: var(--color-blue-600);
--chart-4: var(--color-blue-700);
--chart-5: var(--color-blue-800);
/* Why: light mode sets worktree-sidebar to secondary (#f5f5f5); mirror that
lift in dark mode so the panel reads clearly above #0a0a0a canvas. */
--worktree-sidebar: #2a2a2a;
--worktree-sidebar-foreground: #fafafa;
--worktree-sidebar-accent: #353535;
--worktree-sidebar-accent-foreground: #fafafa;
/* Match --border / --sidebar-border (7%) so the left sidebar's divider lines
aren't brighter than the rest of the UI in dark mode (#5906). */
--worktree-sidebar-border: rgb(255 255 255 / 0.07);
--worktree-sidebar-ring: #737373;
--sidebar: #171717;
--sidebar-foreground: #fafafa;
--sidebar-primary: #1447e6;
--sidebar-primary-foreground: #fafafa;
--sidebar-accent: #262626;
--sidebar-accent-foreground: #fafafa;
--sidebar-border: rgb(255 255 255 / 0.07);
--sidebar-ring: #525252;
--terminal-pane-title-on-dark-fg: rgb(255 255 255 / 0.52);
--terminal-pane-title-on-dark-input-fg: rgb(255 255 255 / 0.7);
--terminal-pane-title-on-dark-placeholder: rgb(255 255 255 / 0.38);
--terminal-pane-title-on-dark-button-fg: rgb(255 255 255);
--terminal-pane-title-on-dark-button-hover-fg: rgb(255 255 255);
--terminal-pane-title-on-dark-input-bg: rgb(255 255 255 / 0.04);
--terminal-pane-title-on-dark-separator: rgb(255 255 255 / 0.06);
--terminal-pane-title-on-light-fg: rgb(24 24 27 / 0.64);
--terminal-pane-title-on-light-input-fg: rgb(24 24 27 / 0.82);
--terminal-pane-title-on-light-placeholder: rgb(24 24 27 / 0.48);
--terminal-pane-title-on-light-button-fg: rgb(24 24 27);
--terminal-pane-title-on-light-button-hover-fg: rgb(24 24 27);
--terminal-pane-title-on-light-input-bg: rgb(24 24 27 / 0.05);
--terminal-pane-title-on-light-separator: rgb(24 24 27 / 0.1);
--git-decoration-added: #81b88b;
--git-decoration-modified: #e2c08d;
--git-decoration-deleted: #c74e39;
--git-decoration-renamed: #73c991;
--git-decoration-untracked: #73c991;
--git-decoration-copied: #73c991;
--git-decoration-ignored: #6e6e6e;
--git-graph-ref: #3794ff;
--git-graph-remote-ref: #b66dff;
--git-graph-base-ref: #ea5c00;
--git-graph-lane-1: #ffb000;
--git-graph-lane-2: #dc267f;
--git-graph-lane-3: #ce9178;
--git-graph-lane-4: #40b0a6;
--git-graph-lane-5: #b66dff;
--annotation-highlight: #fbbf24;
--markdown-search-match: #facc15;
--markdown-search-match-active: #fb923c;
/* Brighter than terminal defaults so the always-visible line clears 3:1 on `--card`. */
--tab-group-split-divider: #71717a;
--tab-group-split-divider-strong: #a1a1aa;
}
.plugin-security-chrome {
/* Why: plugin themes may style the app, but provenance and consent must
retain host-owned contrast so a pack cannot disguise a trust decision. */
--background: var(--orca-security-background);
--foreground: var(--orca-security-foreground);
--card: var(--orca-security-card);
--card-foreground: var(--orca-security-card-foreground);
--popover: var(--orca-security-popover);
--popover-foreground: var(--orca-security-popover-foreground);
--primary: var(--orca-security-primary);
--primary-foreground: var(--orca-security-primary-foreground);
--secondary: var(--orca-security-secondary);
--secondary-foreground: var(--orca-security-secondary-foreground);
--muted: var(--orca-security-muted);
--muted-foreground: var(--orca-security-muted-foreground);
--accent: var(--orca-security-accent);
--accent-foreground: var(--orca-security-accent-foreground);
--border: var(--orca-security-border);
--input: var(--orca-security-input);
--ring: var(--orca-security-ring);
}
.linear-priority-bars {
--linear-priority-bar-inactive-fill: color-mix(in srgb, lch(39.576 1.25 282) 34%, transparent);
}
.dark .linear-priority-bars {
--linear-priority-bar-inactive-fill: color-mix(in srgb, lch(39.576 1.25 282) 62%, transparent);
}
[data-sonner-toaster] {
font-family: var(--font-sans);
}
[data-sonner-toaster] [data-sonner-toast][data-styled='true'] {
align-items: flex-start;
flex-wrap: wrap;
gap: 10px;
padding: 14px 16px;
box-shadow: var(--shadow-floating);
}
[data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-icon] {
margin-top: 2px;
}
[data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-content] {
min-width: 0;
flex: 1 1 calc(100% - 30px);
gap: 6px;
}
[data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-button] {
/* Why: Sonner's native action slots otherwise share the copy row and squeeze
long notices; indent the wrapped footer to align with text after the icon. */
margin-top: 2px;
margin-left: 26px;
margin-right: 0;
}
[data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-button] ~ [data-button] {
margin-left: 0;
}
[data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-title] {
line-height: 1.35;
}
[data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-description] {
/* Why: match the 80% body-copy opacity used by the custom-React toast bodies
(delete-worktree-failure, local-base-ref-suggestion) so string and JSX
descriptions read at the same weight. */
color: color-mix(in srgb, var(--popover-foreground) 80%, transparent);
line-height: 1.45;
}
[data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-close-button] {
/* Why: Sonner's dark-theme defaults can resolve the close icon and button
background to the same color; keep the X readable on both Orca themes. */
color: var(--popover-foreground);
background: var(--popover);
border-color: rgb(0 0 0 / 0.14);
}
[data-sonner-toaster][data-sonner-theme='dark']
[data-sonner-toast][data-styled='true']
[data-close-button] {
color: var(--popover-foreground);
background: var(--popover);
border-color: rgb(255 255 255 / 0.14);
}
.dark [data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-close-button] {
border-color: rgb(255 255 255 / 0.14);
}
[data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-close-button]:hover {
color: var(--accent-foreground);
background: var(--accent);
}
/* ── Base Layer ──────────────────────────────────────── */
@layer base {
*,
*::before,
*::after {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
margin: 0;
padding: 0;
overflow: hidden;
/* Why: mobile browser chrome changes the visible viewport while open. */
height: 100dvh;
font-family: var(
--app-font-family,
'Geist',
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
sans-serif
);
letter-spacing: 0.01em;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
ul {
list-style: none;
}
:where(button:not(:disabled)) {
cursor: pointer;
}
:where(button:disabled) {
cursor: default;
}
}
/* ── Sleek scrollbar (VS Code-like) ─────────────────── */
.scrollbar-sleek {
scrollbar-width: thin;
scrollbar-color: color-mix(in srgb, var(--muted-foreground, #737373) 34%, transparent) transparent;
}
.scrollbar-sleek::-webkit-scrollbar {
width: 12px;
height: 12px;
}
.scrollbar-sleek::-webkit-scrollbar-track {
background: transparent;
}
.scrollbar-sleek::-webkit-scrollbar-thumb {
background: color-mix(in srgb, var(--muted-foreground, #737373) 28%, transparent);
border: 3px solid transparent;
border-radius: 0;
background-clip: padding-box;
min-height: 28px;
}
/* Thicker variant for dense data tables where the default 6px-thumb is hard to grab. */
.scrollbar-sleek-lg {
scrollbar-width: auto;
}
.scrollbar-sleek-lg::-webkit-scrollbar {
width: 16px;
height: 16px;
}
.scrollbar-sleek-lg::-webkit-scrollbar-thumb {
border-width: 3px;
min-height: 36px;
}
.scrollbar-sleek::-webkit-scrollbar-thumb:hover {
background-color: color-mix(in srgb, var(--muted-foreground, #737373) 48%, transparent);
}
.scrollbar-sleek::-webkit-scrollbar-thumb:active {
background-color: color-mix(in srgb, var(--foreground, #171717) 36%, transparent);
}
.scrollbar-sleek-parent:hover .scrollbar-sleek,
.scrollbar-sleek:hover {
scrollbar-color: color-mix(in srgb, var(--muted-foreground, #737373) 48%, transparent) transparent;
}
/* Why: no reserved `scrollbar-gutter` — paired with `overflow-y: auto`, a list
that fits shows no scrollbar (and no Windows scrollbar arrow buttons), so its
rows stay flush with the fixed header controls, matching macOS. The gutter is
only taken while actually scrolling. Thumb still fades in on hover. */
.worktree-sidebar-scrollbar {
padding-right: 4px;
scrollbar-color: transparent transparent;
}
.worktree-sidebar-scrollbar::-webkit-scrollbar {
width: 8px;
}
.worktree-sidebar-scrollbar::-webkit-scrollbar-thumb {
background-color: transparent;
/* Why: no right inset — a 3px transparent right border left a visible strip
beside the sidebar resize seam that read like a gap in the drag handle. */
border-width: 3px 0 3px 0;
}
.scrollbar-sleek-parent:hover .worktree-sidebar-scrollbar,
.worktree-sidebar-scrollbar:hover {
scrollbar-color: color-mix(in srgb, var(--muted-foreground, #737373) 48%, transparent) transparent;
}
.scrollbar-sleek-parent:hover .worktree-sidebar-scrollbar::-webkit-scrollbar-thumb,
.worktree-sidebar-scrollbar:hover::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--muted-foreground, #737373) 28%, transparent);
}
.scrollbar-sleek-parent:hover .worktree-sidebar-scrollbar::-webkit-scrollbar-thumb:hover,
.worktree-sidebar-scrollbar:hover::-webkit-scrollbar-thumb:hover {
background-color: color-mix(in srgb, var(--muted-foreground, #737373) 48%, transparent);
}
.scrollbar-sleek-parent:hover .worktree-sidebar-scrollbar::-webkit-scrollbar-thumb:active,
.worktree-sidebar-scrollbar:hover::-webkit-scrollbar-thumb:active {
background-color: color-mix(in srgb, var(--foreground, #171717) 36%, transparent);
}
[data-slot='popover-content'] {
max-height: var(--radix-popover-content-available-height);
}
/* Radix owns the available-height variable on PopoverContent. Make that same
node scrollable so Chromium scrollbar hit-testing stays inside the layer. */
.popover-scroll-content {
color-scheme: dark;
max-height: min(15rem, var(--radix-popover-content-available-height, 15rem));
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
}
/* ── Editor-style scrollbar (matches Monaco) ────────── */
.scrollbar-editor,
.xterm .xterm-viewport {
scrollbar-width: thin;
scrollbar-color: rgba(121, 121, 121, 0.4) transparent;
}
.scrollbar-editor::-webkit-scrollbar,
.xterm .xterm-viewport::-webkit-scrollbar {
width: 14px;
}
.scrollbar-editor::-webkit-scrollbar-track,
.xterm .xterm-viewport::-webkit-scrollbar-track {
background: transparent;
}
.scrollbar-editor::-webkit-scrollbar-thumb,
.xterm .xterm-viewport::-webkit-scrollbar-thumb {
background: rgba(121, 121, 121, 0.4);
border: 3px solid transparent;
border-radius: 7px;
background-clip: padding-box;
}
.scrollbar-editor::-webkit-scrollbar-thumb:hover,
.xterm .xterm-viewport::-webkit-scrollbar-thumb:hover {
background: rgba(121, 121, 121, 0.7);
border: 3px solid transparent;
background-clip: padding-box;
}
.combined-diff-scroll-container {
-ms-overflow-style: none;
scrollbar-width: none;
}
.combined-diff-scroll-container::-webkit-scrollbar {
width: 0;
height: 0;
}
/* Why: horizontal tab strips scroll via chevrons, wheel, and trackpad; a
native scrollbar thumb overlays tab labels and looks broken. */
.terminal-tab-strip,
.project-view-tab-strip {
-ms-overflow-style: none;
scrollbar-width: none;
}
.terminal-tab-strip::-webkit-scrollbar,
.project-view-tab-strip::-webkit-scrollbar {
width: 0;
height: 0;
}
/* Why: mask fades avoid painting card-colored overlays that mismatch active tabs. */
.terminal-tab-strip--fade-start.terminal-tab-strip--fade-end {
-webkit-mask-image: linear-gradient(
to right,
transparent,
#000 1.5rem,
#000 calc(100% - 1.5rem),
transparent
);
mask-image: linear-gradient(
to right,
transparent,
#000 1.5rem,
#000 calc(100% - 1.5rem),
transparent
);
}
.terminal-tab-strip--fade-start:not(.terminal-tab-strip--fade-end) {
-webkit-mask-image: linear-gradient(to right, transparent, #000 1.5rem, #000 100%);
mask-image: linear-gradient(to right, transparent, #000 1.5rem, #000 100%);
}
.terminal-tab-strip--fade-end:not(.terminal-tab-strip--fade-start) {
-webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 1.5rem), transparent);
mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 1.5rem), transparent);
}
/* Tab activity affordance (amber wash) is rendered as a React DOM child in
SortableTab.tsx rather than via ::after here, so the drop-indicator
::before/::after pseudo-elements stay free for drag-and-drop feedback. See
drop-indicator.ts and SortableTab.tsx for the implementation. */
/* ── Layout ──────────────────────────────────────────── */
#root {
height: 100dvh;
width: 100vw;
overflow: hidden;
}
.app-layout {
display: flex;
flex-direction: column;
height: 100dvh;
width: 100vw;
overflow: hidden;
}
/* Why: native windows have a stable content box and need no wake-time viewport reflow. */
html.native-shell,
html.native-shell body,
html.native-shell #root,
html.native-shell .app-layout {
height: 100%;
}
/* Tab-group split resize handle — wide hit area, visible center line. */
.tab-group-split-resize-handle {
flex-shrink: 0;
position: relative;
background: transparent;
}
.tab-group-split-resize-handle.is-vertical {
width: 6px;
cursor: col-resize;
}
.tab-group-split-resize-handle.is-horizontal {
height: 6px;
cursor: row-resize;
}
.tab-group-split-resize-handle::after {
content: '';
position: absolute;
border-radius: 1px;
background: var(--tab-group-split-divider);
transition: background 100ms ease;
}
.tab-group-split-resize-handle.is-vertical::after {
top: 0;
bottom: 0;
left: 50%;
width: 3px;
transform: translateX(-50%);
}
.tab-group-split-resize-handle.is-horizontal::after {
left: 0;
right: 0;
top: 50%;
height: 3px;
transform: translateY(-50%);
}
.tab-group-split-resize-handle:hover::after,
.tab-group-split-resize-handle.is-dragging::after {
background: var(--tab-group-split-divider-strong);
}
/* ── Titlebar ────────────────────────────────────────── */
/* Why: Radix portals (dropdowns, popovers, dialogs, sheets) render at the
document root. Electron's OS-level drag hit-test ignores z-index — portaled
content that visually overlaps a -webkit-app-region: drag element is
unclickable without an explicit no-drag. These rules cover every Radix
portal variant: popper wrappers (dropdowns, popovers), dialog/sheet
overlays, and their content panels. */
[data-radix-popper-content-wrapper],
[data-slot='sheet-overlay'],
[data-slot='sheet-content'],
[data-slot='dialog-overlay'],
[data-slot='dialog-content'] {
-webkit-app-region: no-drag;
}
.titlebar {
height: 36px;
min-height: 36px;
display: flex;
flex-direction: row;
align-items: center;
background: var(--bg-titlebar, var(--card));
border-bottom: 1px solid var(--border);
-webkit-app-region: drag;
user-select: none;
}
/* Why: workspace view still needs a full titlebar-height strip above the
sidebar so the native macOS traffic lights and sidebar chrome sit in the
same vertical band they use in settings/landing. The center and right
workspace columns are offset separately to align with this 36px baseline
(4px drag strip + 32px tab row). */
.titlebar-left {
height: 36px;
min-height: 36px;
display: flex;
flex-direction: row;
align-items: center;
overflow: hidden;
flex-shrink: 0;
/* Why: in workspace view this strip sits directly above the worktree
sidebar — match its surface so the left column reads as one panel. */
background: var(--worktree-sidebar);
/* Why: draw the divider via inset shadow instead of border-bottom so the
flex content area spans the full 36px. With border-bottom + border-box,
the content area shrinks to 35px and flex-centered controls land at
y=17.5, a half-pixel below the native traffic-light center at y=18
(set via setWindowButtonPosition in createMainWindow.ts). The shadow
paints the same 1px seam without stealing layout space, so the sidebar
toggle, agent badge, and back/forward arrows align pixel-perfectly
with the traffic lights. */
box-shadow: inset 0 -1px 0 var(--border);
-webkit-app-region: drag;
user-select: none;
}
/* Why: when the sidebar is collapsed this header floats over the tab strip,
so keep the standard titlebar surface instead of the sidebar tint. */
.titlebar-left-floating {
background: var(--bg-titlebar, var(--card));
}
.titlebar-traffic-light-pad {
width: calc(80px / var(--ui-zoom-factor, 1));
flex-shrink: 0;
}
/* Why: small identity anchor on desktop custom titlebars where native window
chrome is hidden. Sized to sit comfortably in the 36px titlebar with a
little horizontal breathing room. The SVG fill is white; light mode inverts
it to black so it reads on the light titlebar background. */
.titlebar-logo {
height: 16px;
flex-shrink: 0;
margin: 0 4px 0 10px;
opacity: 0.75;
}
/* Why: .dark is toggled on <html> (the :root), so :not(.dark) on a descendant
always matches. Scope to :root:not(.dark) to correctly target light mode only. */
:root:not(.dark) .titlebar-logo {
filter: invert(1);
}
/* Why: desktop custom title bar buttons replace hidden native OS chrome.
Anchored to the top-right corner of the viewport so they overlay any layout
mode (full titlebar, workspace split, etc.) and stay accessible at all times. */
.window-controls {
position: fixed;
top: 0;
right: 0;
z-index: 9999;
display: flex;
flex-direction: row;
height: 36px;
-webkit-app-region: no-drag;
}
.window-controls-btn {
display: flex;
align-items: center;
justify-content: center;
width: 46px;
height: 36px;
background: transparent;
border: none;
color: var(--muted-foreground);
cursor: pointer;
transition:
background 100ms,
color 100ms;
}
.window-controls-btn:hover {
background: var(--accent);
color: var(--foreground);
}
.window-controls-close:hover {
background: #c42b1c;
color: #fff;
}
/* Why: flex spacer that reserves the width occupied by the three window-control
buttons (3 × 46px) so that the preceding titlebar content is not obscured by
the fixed-position overlay when custom desktop chrome is active. */
.window-controls-titlebar-spacer {
flex-shrink: 0;
width: var(--window-controls-width, 0px);
}
.right-sidebar-header-drag {
-webkit-app-region: drag;
user-select: none;
}
[data-terminal-focus-release-surface='true'] {
-webkit-app-region: drag;
}
/* Why: Electron drag regions swallow renderer pointer events. While regular
xterm owns focus, make the next top-chrome click observable so it can release
terminal zoom ownership; after release these surfaces become draggable again. */
:root[data-regular-terminal-input-focused] .titlebar,
:root[data-regular-terminal-input-focused] .titlebar-left,
:root[data-regular-terminal-input-focused] .window-controls-titlebar-spacer,
:root[data-regular-terminal-input-focused] .right-sidebar-header-drag,
:root[data-regular-terminal-input-focused] [data-terminal-focus-release-surface='true'] {
-webkit-app-region: no-drag;
}
.right-sidebar-header-no-drag {
-webkit-app-region: no-drag;
}
/* Why: the right sidebar header extends to the window's right edge. When custom
desktop chrome is active, the fixed-position window-controls overlay sits on
top of that edge, so we add matching padding-right so the header's close
button (pr-1) isn't hidden behind the overlay. */
.right-sidebar-header-inset {
padding-right: var(--window-controls-width, 0px);
}
/* Why: in side-bar activity-bar mode the icon strip sits flush against the
right window edge. The fixed-position window-controls overlay (height 36px,
width 138px) covers the top-right corner when custom desktop chrome is
active, so the topmost sidebar icons are unreachable without this offset. */
.side-activity-bar-windows-inset {
padding-top: var(--window-controls-height, 0px);
}
/* Why: in side activity-bar mode the panel header does NOT extend to the window
edge — the 40px icon strip sits to its right. But the window-controls overlay
is 138px wide when custom desktop chrome is active, so it still overlaps the
panel by (138 - 40) = 98px. Apply exactly that remainder as padding-right so
the close button clears the minimize button. max(0px, ...) keeps this a no-op
when the var is 0px and the subtraction would otherwise go negative. */
.right-sidebar-header-side-inset {
padding-right: max(0px, calc(var(--window-controls-width, 0px) - 40px));
}
.sidebar-toggle {
-webkit-app-region: no-drag;
background: none;
border: none;
color: var(--muted-foreground);
cursor: pointer;
padding: 4px 8px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: background 150ms;
}
.sidebar-toggle:hover {
background: var(--accent);
color: var(--foreground);
}
/* Why: tighter padding for the back/forward pair so the arrows sit
close together as a single navigation group, while the standard
.sidebar-toggle spacing stays unchanged for other titlebar icons. */
.sidebar-toggle-compact {
padding: 4px 5px;
color: var(--foreground);
}
.sidebar-toggle-compact:disabled {
color: var(--muted-foreground);
}
.sidebar-toggle:disabled {
cursor: default;
opacity: 0.45;
}
.sidebar-toggle:disabled:hover {
background: none;
color: var(--muted-foreground);
}
.titlebar-app-name {
-webkit-app-region: no-drag;
display: inline-flex;
align-items: center;
gap: 4px;
height: 24px;
max-width: min(280px, 32vw);
margin-left: 2px;
margin-right: 4px;
padding: 0 6px;
border-radius: 4px;
color: var(--muted-foreground);
font-size: 12px;
font-weight: 600;
line-height: 24px;
white-space: nowrap;
overflow: hidden;
cursor: pointer;
}
/* Why: when the app name sits inside the worktree sidebar header, it should
inherit that panel's contrast instead of the generic titlebar text color. */
.titlebar-left:not(.titlebar-left-floating) .titlebar-app-name {
color: var(--worktree-sidebar-foreground);
}
.titlebar-app-name-main {
flex: 0 0 auto;
}
.titlebar-icon-button {
-webkit-app-region: no-drag;
background: none;
border: none;
color: var(--muted-foreground);
cursor: pointer;
padding: 4px;
margin-right: 8px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: background 150ms;
}
.titlebar-icon-button:hover {
background: var(--accent);
color: var(--foreground);
}
.titlebar-icon-button:disabled {
cursor: default;
opacity: 0.5;
}
.titlebar-icon-button:disabled:hover {
background: none;
color: var(--muted-foreground);
}
.editor-header {
display: flex;
align-items: center;
/* Keep adjacent actions aligned with the app's compact toolbar spacing. */
gap: 4px;
min-height: 36px;
padding: 6px 14px;
background: var(--editor-surface);
border-bottom: 1px solid color-mix(in srgb, var(--border) 72%, transparent);
flex-shrink: 0;
}
.editor-header-text {
min-width: 0;
flex: 1;
}
.editor-header-path {
display: block;
min-width: 0;
flex: 0 1 auto;
padding: 0;
max-width: 100%;
border: none;
background: transparent;
color: var(--muted-foreground);
cursor: pointer;
font-size: 12px;
line-height: 1.2;
font-family:
ui-monospace,
SFMono-Regular,
SF Mono,
Menlo,
Consolas,
monospace;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
}
.editor-header-path:hover,
.editor-header-path:focus-visible {
color: var(--foreground);
}
.editor-header-path--static {
cursor: default;
}
.editor-header-path--static:hover,
.editor-header-path--static:focus-visible {
color: var(--muted-foreground);
}
.editor-header-path-row {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.editor-header-copy-toast {
opacity: 0;
transform: translateY(2px);
transition:
opacity 120ms ease,
transform 120ms ease;
pointer-events: none;
flex-shrink: 0;
padding: 2px 6px;
border-radius: 999px;
background: color-mix(in srgb, var(--accent) 72%, transparent);
color: var(--foreground);
font-size: 11px;
line-height: 1.2;
white-space: nowrap;
}
.editor-header-copy-toast.is-visible {
opacity: 1;
transform: translateY(0);
}
/* ── Content ─────────────────────────────────────────── */
.content-area {
display: flex;
flex-direction: row;
flex: 1;
overflow: hidden;
}
/* ── Jump palette (Cmd+J) ─────────────────────────────── */
/* Why the extra qualifier: CommandItem also sets data-[selected=true]:bg-accent;
this selector must beat that utility so light-mode selection is actually visible.
The role='option' arm covers virtualized rows that render outside cmdk. */
[data-slot='command-item'].jump-palette-item[data-selected='true'],
[role='option'].jump-palette-item[data-selected='true'] {
/* Why: flat --accent (#f5f5f5) on the palette surface (#fff) is nearly invisible
in light mode — the keyboard cursor has to read as a solid selected row.
Mix foreground into background and draw an inset ring; expose the surface
as a var so status-pip cutouts can match without hardcoding. */
--jump-palette-selection-surface: color-mix(in srgb, var(--foreground) 12%, var(--background));
background: var(--jump-palette-selection-surface);
border-color: transparent;
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--foreground) 18%, transparent);
color: var(--foreground);
}
.dark [data-slot='command-item'].jump-palette-item[data-selected='true'],
.dark [role='option'].jump-palette-item[data-selected='true'] {
/* Why accent, not the light-mode mix: --accent (#404040) already reads clearly on
dark surfaces; a low-%% foreground mix was a visible step down from the old row. */
--jump-palette-selection-surface: var(--accent);
background: var(--jump-palette-selection-surface);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--foreground) 16%, transparent);
color: var(--accent-foreground);
}
/* ── Sidebar ─────────────────────────────────────────── */
[data-file-explorer-row][data-selected='true'] {
/* Why: flat bg-accent ≈ panel background in light mode, so selection mixes
foreground into accent and adds an inset ring to stay legible in both themes. */
background: color-mix(in srgb, var(--foreground) 8%, var(--accent));
box-shadow: inset 0 0 0 1px var(--border);
color: var(--accent-foreground);
}
[data-file-explorer-row][data-selected='true']:hover {
background: color-mix(in srgb, var(--foreground) 10%, var(--accent));
}
.dark [data-file-explorer-row][data-selected='true'] {
background: color-mix(in srgb, var(--accent) 70%, transparent);
}
.dark [data-file-explorer-row][data-selected='true']:hover {
background: color-mix(in srgb, var(--accent) 80%, transparent);
}
.sidebar {
width: 240px;
flex-shrink: 0;
background: var(--sidebar);
border-right: 1px solid var(--sidebar-border, var(--border));
overflow-y: auto;
overflow-x: hidden;
transition: width 200ms ease;
}
/* Why: light-mode sidebar-accent/40 is nearly invisible on the sidebar, while
agent rows stack on top of the card hover. Keep nested rows quieter than the
workspace card without changing the established dark-mode balance. */
.worktree-sidebar-card-hover:hover {
background: color-mix(in srgb, var(--sidebar-foreground) 4%, transparent);
}
.dark .worktree-sidebar-card-hover:hover {
background: color-mix(in srgb, var(--sidebar-accent) 40%, transparent);
}
/* Why: the brighter border carries the selected state; a translucent wash keeps
card text legible instead of lifting the surface toward the foreground color. */
[data-worktree-card-surface][data-worktree-card-active='primary'] {
border-color: color-mix(in srgb, var(--worktree-sidebar-border) 40%, transparent);
background: color-mix(in srgb, var(--worktree-sidebar-foreground) 8%, transparent);
box-shadow: 0 1px 2px color-mix(in srgb, var(--worktree-sidebar-foreground) 4%, transparent);
}
.dark [data-worktree-card-surface][data-worktree-card-active='primary'] {
border-color: color-mix(
in srgb,
var(--worktree-sidebar-foreground) 18%,
var(--worktree-sidebar-border)
);
background: color-mix(in srgb, var(--worktree-sidebar-foreground) 10%, transparent);
box-shadow: 0 1px 2px color-mix(in srgb, var(--worktree-sidebar-foreground) 3%, transparent);
}
[data-worktree-card-surface][data-worktree-card-active='secondary'] {
border-color: color-mix(in srgb, var(--sidebar-ring) 25%, transparent);
background: color-mix(in srgb, var(--sidebar-accent) 45%, transparent);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--sidebar-ring) 15%, transparent);
}
.dark [data-worktree-card-surface][data-worktree-card-active='secondary'] {
border-color: color-mix(in srgb, var(--sidebar-ring) 28%, transparent);
background: color-mix(in srgb, var(--sidebar-accent) 34%, transparent);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--sidebar-ring) 18%, transparent);
}
.worktree-agent-row-hover:hover {
background: color-mix(in srgb, var(--sidebar-foreground) 1.25%, transparent);
}
.worktree-agent-row-hover[data-focused-agent-pane='true'] {
/* Why: a borderless fill. --sidebar-accent ≈ --sidebar in light mode, so the
selection wash mixes sidebar-foreground into the accent to stay legible
without going so dark that muted text (timestamp) drops out. */
background: color-mix(in srgb, var(--sidebar-foreground) 12%, var(--sidebar-accent));
}
.dark .worktree-agent-row-hover:hover {
background: color-mix(in srgb, var(--accent) 18%, transparent);
}
.dark .worktree-agent-row-hover[data-focused-agent-pane='true'] {
background: color-mix(in srgb, var(--accent) 70%, transparent);
}
/* Why: one-shot sidebar education cards need stronger separation than
worktree-sidebar-accent, which sits almost on top of the sidebar fill. */
.worktree-sidebar-notice-card {
position: relative;
border: 1px solid
color-mix(in srgb, var(--worktree-sidebar-foreground) 24%, var(--worktree-sidebar-border));
background: color-mix(in srgb, var(--worktree-sidebar-foreground) 9%, var(--worktree-sidebar));
box-shadow:
0 0 0 1px color-mix(in srgb, var(--worktree-sidebar-foreground) 8%, transparent),
0 1px 2px color-mix(in srgb, var(--worktree-sidebar-foreground) 10%, transparent);
}
.dark .worktree-sidebar-notice-card {
border-color: color-mix(
in srgb,
var(--worktree-sidebar-foreground) 32%,
var(--worktree-sidebar-border)
);
background: color-mix(in srgb, var(--worktree-sidebar-foreground) 14%, var(--worktree-sidebar));
box-shadow:
0 0 0 1px color-mix(in srgb, var(--worktree-sidebar-foreground) 12%, transparent),
0 1px 4px rgb(0 0 0 / 0.28);
}
/* Why: the project-order notice sits directly under the Projects header; a
caret aligned to that label reads as a callout instead of a random card. */
.worktree-sidebar-notice-card--to-section-title::before {
content: '';
position: absolute;
top: -5px;
left: 1.75rem;
width: 10px;
height: 10px;
transform: rotate(45deg);
border-left: 1px solid
color-mix(in srgb, var(--worktree-sidebar-foreground) 24%, var(--worktree-sidebar-border));
border-top: 1px solid
color-mix(in srgb, var(--worktree-sidebar-foreground) 24%, var(--worktree-sidebar-border));
background: color-mix(in srgb, var(--worktree-sidebar-foreground) 9%, var(--worktree-sidebar));
}
.dark .worktree-sidebar-notice-card--to-section-title::before {
border-left-color: color-mix(
in srgb,
var(--worktree-sidebar-foreground) 32%,
var(--worktree-sidebar-border)
);
border-top-color: color-mix(
in srgb,
var(--worktree-sidebar-foreground) 32%,
var(--worktree-sidebar-border)
);
background: color-mix(in srgb, var(--worktree-sidebar-foreground) 14%, var(--worktree-sidebar));
}
/* Why: one-shot status-bar callout is portaled fixed above the meters.
Opaque popover surface (not glass) so titlebar/terminal chrome cannot
bleed through; use design tokens + the documented floating elevation. */
.status-bar-change-notice-card {
border: 1px solid var(--border);
background: var(--popover);
color: var(--popover-foreground);
/* STYLEGUIDE: floating elevation for popovers that escape the editor surface. */
box-shadow: var(--shadow-floating);
}
/* Why: caret points down at the usage meters this notice explains. */
.status-bar-change-notice-card::after {
content: '';
position: absolute;
left: 1.25rem;
bottom: -6px;
width: 12px;
height: 12px;
transform: rotate(45deg);
border-right: 1px solid var(--border);
border-bottom: 1px solid var(--border);
background: var(--popover);
}
/* Why: the first-run Mobile Emulator intro sits directly under that menu row;
an upward caret aligned to the phone icon reads as a callout, not a footer. */
.mobile-emulator-tab-intro-callout--menu {
position: relative;
}
.mobile-emulator-tab-intro-callout--menu::before {
content: '';
position: absolute;
top: -7px;
left: 0.8125rem;
border-style: solid;
border-width: 0 7px 7px;
/* Why: use foreground + border to create a subtle shadow that adapts to theme */
border-color: transparent transparent color-mix(in srgb, var(--foreground) 10%, var(--border))
transparent;
}
.mobile-emulator-tab-intro-callout--menu::after {
content: '';
position: absolute;
top: -6px;
left: 0.8125rem;
border-style: solid;
border-width: 0 6px 6px;
border-color: transparent transparent var(--muted) transparent;
}
.dark .mobile-emulator-tab-intro-callout--menu::before {
/* Why: use foreground + border to create a subtle shadow that adapts to theme */
border-bottom-color: color-mix(in srgb, var(--foreground) 12%, var(--border));
}
.dark .mobile-emulator-tab-intro-callout--menu::after {
border-bottom-color: color-mix(in srgb, var(--accent) 80%, transparent);
}
/* Why: anchor the optional setup card over the emulator without blurring the
device preview, which read as a rendering glitch. */
.mobile-emulator-agent-setup-guide-scrim {
background: linear-gradient(
to top,
color-mix(in srgb, var(--background) 92%, transparent),
transparent
);
pointer-events: none;
}
/* Why: the detected command is editable, but a pure app-background fill reads
like a black box in dark mode and overpowers the prompt card. */
.setup-script-prompt-command {
background: color-mix(in srgb, var(--sidebar-foreground) 2%, var(--sidebar));
}
.dark .setup-script-prompt-command {
background: color-mix(in srgb, var(--sidebar-foreground) 8%, var(--sidebar));
}
.worktree-agent-lineage-parent-row {
/* Why: inline orchestration trees have two chevron levels; the manager row
needs a structural cue that is quieter than the workspace card chrome. */
background: color-mix(in srgb, var(--sidebar-foreground) 3%, var(--sidebar));
box-shadow:
inset 2px 0 0 color-mix(in srgb, var(--sidebar-foreground) 38%, transparent),
inset 0 0 0 1px color-mix(in srgb, var(--sidebar-foreground) 10%, transparent);
}
.worktree-agent-lineage-parent-row.worktree-agent-row-hover:hover {
background: color-mix(in srgb, var(--sidebar-foreground) 6%, var(--sidebar));
}
.dark .worktree-agent-lineage-parent-row {
background: color-mix(in srgb, var(--accent) 22%, var(--sidebar));
box-shadow:
inset 2px 0 0 color-mix(in srgb, var(--accent) 58%, transparent),
inset 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent);
}
.dark .worktree-agent-lineage-parent-row.worktree-agent-row-hover:hover {
background: color-mix(in srgb, var(--accent) 30%, var(--sidebar));
}
.worktree-agent-lineage-child-row {
position: relative;
background: color-mix(in srgb, var(--sidebar) 74%, var(--background));
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sidebar-foreground) 7%, transparent);
}
.worktree-agent-lineage-child-row.worktree-agent-row-hover:hover {
background: color-mix(in srgb, var(--sidebar-foreground) 4%, var(--sidebar));
}
.worktree-agent-lineage-child-row::before {
content: '';
position: absolute;
left: 0.15rem;
top: 50%;
width: 0.35rem;
border-top: 1px solid color-mix(in srgb, var(--sidebar-foreground) 20%, transparent);
}
.dark .worktree-agent-lineage-child-row {
background: color-mix(in srgb, var(--accent) 10%, var(--sidebar));
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 16%, transparent);
}
.dark .worktree-agent-lineage-child-row.worktree-agent-row-hover:hover {
background: color-mix(in srgb, var(--accent) 18%, var(--sidebar));
}
.dark .worktree-agent-lineage-child-row::before {
border-top-color: color-mix(in srgb, var(--accent) 42%, transparent);
}
.worktree-agent-lineage-children {
margin-left: 0.75rem;
padding-left: 0.25rem;
border-left: 1px solid color-mix(in srgb, var(--sidebar-foreground) 22%, transparent);
}
.dark .worktree-agent-lineage-children {
border-left-color: color-mix(in srgb, var(--accent) 28%, transparent);
}
.worktree-agent-send-target-button {
/* Why: the row remains an ordinary agent row during send-target mode; the
action itself carries the AI target outline so the clickable affordance
is explicit without boxing the whole session. */
border-color: color-mix(in srgb, var(--ai-action-accent) 72%, transparent);
background: color-mix(in srgb, var(--ai-action-accent) 12%, transparent);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--ai-action-accent) 22%, transparent);
color: var(--foreground);
}
.worktree-agent-send-target-button:hover {
border-color: color-mix(in srgb, var(--ai-action-accent) 88%, transparent);
background: color-mix(in srgb, var(--ai-action-accent) 18%, transparent);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--ai-action-accent) 34%, transparent);
}
.worktree-agent-send-target-button:focus-visible {
outline: none;
box-shadow: 0 0 0 2px color-mix(in srgb, var(--ring) 50%, transparent);
}
/* Why: the virtualized card row is height-measured; animate the grid track
instead of explicit height so the reveal stays CSS-owned and interruptible. */
.compact-agent-expansion-grid {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 180ms cubic-bezier(0.16, 1, 0.3, 1);
}
.compact-agent-expansion-grid-expanded {
grid-template-rows: 1fr;
}
/* Why: must stay a compositor-driven CSS animation — a JS clock writing
per-element transforms blocks the renderer input thread (STA-3328: 41
spinners ⇒ ~490 style writes/s, keystroke p90 363ms). The component anchors
each animation's startTime once; steps(12) preserves the retired cadence. */
@keyframes agent-spinner-rotate {
to {
transform: rotate(360deg);
}
}
.agent-working-spinner {
animation: agent-spinner-rotate 1s steps(12, end) infinite;
}
@media (prefers-reduced-motion: reduce) {
.agent-working-spinner {
animation: none;
}
}
/* Why: keyframes run when the content mounts already in its final state. */
@keyframes compact-agent-expansion-reveal {
from {
opacity: 0;
transform: translateY(-2px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.compact-agent-expansion-grid-expanded .compact-agent-expansion-content {
animation: compact-agent-expansion-reveal 180ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
/* Why: the collapsed summary still uses pill spacing, while expanded compact
agents read as a quiet tree inside the existing worktree card. */
.compact-agent-summary-panel {
padding: 2px;
border-radius: var(--radius-sm, 0.25rem);
background: transparent;
box-shadow: inset 0 0 0 1px transparent;
transition:
background 180ms cubic-bezier(0.16, 1, 0.3, 1),
box-shadow 180ms cubic-bezier(0.16, 1, 0.3, 1);
}
.compact-agent-summary-panel-expanded {
background: transparent;
box-shadow: inset 0 0 0 1px transparent;
}
[data-compact-agent-list='true'] {
margin-inline-start: -0.5rem;
width: calc(100% + 0.5rem);
}
/* Why: compact expanded agents live inside an already-framed worktree card.
Keep the tree legible with indentation and interaction states instead of
stacking row cards and shaded panels. */
[data-compact-agent-list='true'] .compact-agent-row.worktree-agent-lineage-parent-row,
[data-compact-agent-list='true'] .compact-agent-row.worktree-agent-lineage-child-row {
background: transparent;
box-shadow: none;
}
[data-compact-agent-list='true'] .compact-agent-row.worktree-agent-lineage-child-row::before {
content: none;
}
[data-compact-agent-list='true']
.compact-agent-row.worktree-agent-lineage-parent-row.worktree-agent-row-hover:hover,
[data-compact-agent-list='true']
.compact-agent-row.worktree-agent-lineage-child-row.worktree-agent-row-hover:hover {
background: color-mix(in srgb, var(--sidebar-foreground) 1.25%, transparent);
}
.dark
[data-compact-agent-list='true']
.compact-agent-row.worktree-agent-lineage-parent-row.worktree-agent-row-hover:hover,
.dark
[data-compact-agent-list='true']
.compact-agent-row.worktree-agent-lineage-child-row.worktree-agent-row-hover:hover {
background: color-mix(in srgb, var(--accent) 18%, transparent);
}
[data-compact-agent-list='true']
.compact-agent-row.worktree-agent-row-hover[data-focused-agent-pane='true'] {
background: color-mix(in srgb, var(--sidebar-foreground) 12%, var(--sidebar-accent));
}
.dark
[data-compact-agent-list='true']
.compact-agent-row.worktree-agent-row-hover[data-focused-agent-pane='true'] {
background: color-mix(in srgb, var(--accent) 70%, transparent);
}
[data-compact-agent-list='true'] .worktree-agent-lineage-children {
border-left-color: color-mix(in srgb, var(--sidebar-foreground) 16%, transparent);
}
.dark [data-compact-agent-list='true'] .worktree-agent-lineage-children {
border-left-color: color-mix(in srgb, var(--accent) 20%, transparent);
}
@media (prefers-reduced-motion: reduce) {
.compact-agent-expansion-grid {
transition-duration: 0ms;
}
.compact-agent-expansion-grid-expanded .compact-agent-expansion-content {
animation-duration: 0ms;
}
.compact-agent-summary-panel {
transition-duration: 0ms;
}
}
.scroll-to-current-workspace-reveal-highlight {
position: relative;
isolation: isolate;
overflow: visible;
}
.scroll-to-current-workspace-reveal-highlight::before {
content: '';
position: absolute;
inset: 0;
z-index: 30;
pointer-events: none;
border: 1.5px solid color-mix(in srgb, var(--terminal-pane-locate) 68%, transparent);
border-radius: inherit;
box-shadow:
0 0 0 1px color-mix(in srgb, var(--terminal-pane-locate) 34%, transparent),
0 0 14px color-mix(in srgb, var(--terminal-pane-locate) 36%, transparent);
animation: scroll-to-current-workspace-reveal-glow 1.5s ease-out forwards;
}
.scroll-to-current-workspace-reveal-highlight--ai::before {
border-color: color-mix(in srgb, var(--ai-action-accent) 68%, transparent);
box-shadow:
0 0 0 1px color-mix(in srgb, var(--ai-action-accent) 34%, transparent),
0 0 14px color-mix(in srgb, var(--ai-action-accent) 36%, transparent);
}
@keyframes scroll-to-current-workspace-reveal-glow {
0% {
opacity: 1;
transform: scale(0.992);
}
24% {
opacity: 0.72;
transform: scale(1);
}
60% {
opacity: 0.28;
transform: scale(1.006);
}
100% {
opacity: 0;
transform: scale(1.006);
}
}
@media (prefers-reduced-motion: reduce) {
.scroll-to-current-workspace-reveal-highlight::before {
animation: none;
opacity: 1;
transform: none;
}
}
@keyframes settings-shell-enter {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.settings-view-shell {
animation: settings-shell-enter 180ms ease-out;
}
.repo-icon-emoji-picker .EmojiPickerReact {
--epr-bg-color: var(--card);
--epr-category-label-bg-color: var(--card);
--epr-text-color: var(--card-foreground);
--epr-search-input-bg-color: var(--input);
--epr-search-input-bg-color-active: var(--input);
--epr-search-border-color: var(--border);
--epr-search-border-color-active: var(--ring);
--epr-hover-bg-color: var(--accent);
--epr-highlight-color: var(--ring);
--epr-picker-border-color: var(--border);
border: none;
}
/* The library sets `font-family: sans-serif` on every descendant, so inheriting from the root never lands. */
.repo-icon-emoji-picker .EmojiPickerReact * {
font-family: var(--font-sans);
}
.repo-icon-emoji-picker .epr-body {
scrollbar-width: thin;
scrollbar-color: color-mix(in srgb, var(--muted-foreground, #737373) 34%, transparent) transparent;
}
.collapsible-height-content {
overflow: hidden;
}
.collapsible-height-content[data-state='open'] {
animation: collapsible-down 200ms ease-out;
}
.collapsible-height-content[data-state='closed'] {
animation: collapsible-up 180ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
.collapsible-height-content[data-state='open'],
.collapsible-height-content[data-state='closed'] {
animation: none;
}
}
@keyframes collapsible-down {
from {
height: 0;
opacity: 0;
}
to {
height: var(--radix-collapsible-content-height);
opacity: 1;
}
}
@keyframes collapsible-up {
from {
height: var(--radix-collapsible-content-height);
opacity: 1;
}
to {
height: 0;
opacity: 0;
}
}
.sidebar.collapsed {
width: 0;
border-right: none;
overflow: hidden;
}
/* Why: the workspace board is offset to the sidebar edge. The generic left
sheet translates by its full width, which makes an offset board fly in from
far offscreen; this reveals it from the sidebar boundary instead. */
.workspace-kanban-sheet-content[data-state='open'] {
animation-name: workspace-kanban-sheet-in;
animation-duration: 300ms;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.workspace-kanban-sheet-content[data-state='closed'] {
animation-name: workspace-kanban-sheet-out;
animation-duration: 200ms;
animation-timing-function: cubic-bezier(0.7, 0, 0.84, 0);
}
/* Why: this board is attached to the worktree sidebar, not floating above it;
the generic Sheet elevation casts a haze over the sidebar when left open. */
.workspace-kanban-sheet-content {
box-shadow: inset 1px 0 0 color-mix(in srgb, var(--worktree-sidebar-border) 80%, transparent);
backdrop-filter: none;
}
/* Why: a sidebar-card drag should advertise the board as a possible drop
surface without fully committing the companion panel until the pointer enters it. */
.workspace-kanban-sheet-content[data-workspace-board-drag-preview='true'] {
background: color-mix(in srgb, var(--worktree-sidebar) 88%, var(--background));
box-shadow:
inset 0 0 0 1px color-mix(in srgb, var(--sidebar-ring) 58%, transparent),
0 10px 24px color-mix(in srgb, var(--foreground) 12%, transparent);
}
.workspace-kanban-sheet-content[data-workspace-board-drag-preview='true'] > * {
opacity: 0.42;
}
/* Why: while a card drag previews the board, pulse the toolbar trigger with the
same ring the drawer uses so it reads as another way to open the board. */
@keyframes workspace-board-trigger-preview-pulse {
0% {
box-shadow: 0 0 0 0 color-mix(in srgb, var(--sidebar-ring) 55%, transparent);
}
70%,
100% {
box-shadow: 0 0 0 5px color-mix(in srgb, var(--sidebar-ring) 0%, transparent);
}
}
[data-workspace-board-trigger][data-workspace-board-preview='true'] {
animation: workspace-board-trigger-preview-pulse 1.4s ease-out infinite;
}
@media (prefers-reduced-motion: reduce) {
[data-workspace-board-trigger][data-workspace-board-preview='true'] {
animation: none;
}
}
@keyframes workspace-kanban-sheet-in {
from {
clip-path: inset(0 100% 0 0);
}
to {
clip-path: inset(0 0 0 0);
}
}
@keyframes workspace-kanban-sheet-out {
from {
clip-path: inset(0 0 0 0);
}
to {
clip-path: inset(0 100% 0 0);
}
}
/* Why: board-origin cards use pointer drag so Shift+scroll can keep firing;
these replace the native drag cursor and ghost affordance. */
[data-workspace-board-pointer-draggable='true'] {
cursor: grab;
}
[data-workspace-board-card-pointer-dragging='true'] {
outline: 1px solid var(--sidebar-ring);
outline-offset: 1px;
box-shadow: 0 0 0 1px color-mix(in srgb, var(--sidebar-ring) 36%, transparent);
}
[data-workspace-board-card-pointer-dragging='true'],
[data-workspace-board-card-pointer-dragging='true'] *,
:root[data-workspace-board-pointer-dragging] * {
cursor: grabbing !important;
}
[data-workspace-board-card-drag-preview='true'] {
z-index: 2147483647;
overflow: visible;
border-radius: var(--radius);
outline: 1px solid var(--sidebar-ring);
outline-offset: 1px;
box-shadow: 0 10px 24px color-mix(in srgb, var(--foreground) 16%, transparent);
opacity: 0.96;
will-change: transform;
}
[data-workspace-board-card-drag-preview='true'][data-workspace-board-card-drag-stack='true'] {
isolation: isolate;
}
[data-workspace-board-card-drag-card='true'] {
width: 100%;
height: 100%;
}
[data-workspace-board-card-drag-preview='true'][data-workspace-board-card-drag-stack='true']::before,
[data-workspace-board-card-drag-preview='true'][data-workspace-board-card-drag-stack='true']::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: var(--radius);
border: 1px solid color-mix(in srgb, var(--sidebar-ring) 44%, transparent);
background: var(--sidebar);
}
[data-workspace-board-card-drag-preview='true'][data-workspace-board-card-drag-stack='true']::before {
transform: translate(4px, 4px);
opacity: 0.72;
}
[data-workspace-board-card-drag-preview='true'][data-workspace-board-card-drag-stack='true']::after {
transform: translate(8px, 8px);
opacity: 0.44;
}
[data-workspace-board-card-drag-preview='true'] * {
cursor: grabbing !important;
}
[data-worktree-sidebar-drag-preview='true'] {
z-index: 2147483000;
overflow: visible;
border-radius: var(--radius);
opacity: 0.96;
filter: drop-shadow(0 10px 24px color-mix(in srgb, var(--foreground) 18%, transparent));
will-change: transform;
}
[data-worktree-sidebar-drag-preview='true'] * {
cursor: grabbing !important;
}
[data-worktree-sidebar-drag-count='true'] {
position: absolute;
top: -6px;
right: 8px;
display: inline-flex;
min-width: 18px;
height: 18px;
align-items: center;
justify-content: center;
border-radius: 9999px;
background: var(--sidebar-ring);
color: var(--sidebar);
font-size: 10px;
font-weight: 700;
box-shadow: 0 0 0 2px var(--sidebar);
}
[data-workspace-board-card-drop-indicator='true'] {
z-index: 2147483646;
height: 3px;
border-radius: 9999px;
background: var(--sidebar-ring);
box-shadow:
0 0 0 1px color-mix(in srgb, var(--sidebar-ring) 24%, transparent),
0 8px 20px color-mix(in srgb, var(--sidebar-ring) 36%, transparent);
opacity: 0;
transition:
transform 120ms cubic-bezier(0.2, 0.8, 0.2, 1),
width 120ms cubic-bezier(0.2, 0.8, 0.2, 1),
opacity 80ms ease-out;
will-change: transform, width, opacity;
}
[data-workspace-board-card-drop-indicator='true']::before,
[data-workspace-board-card-drop-indicator='true']::after {
content: '';
position: absolute;
top: 50%;
width: 7px;
height: 7px;
border-radius: 9999px;
background: var(--sidebar-ring);
transform: translateY(-50%);
}
[data-workspace-board-card-drop-indicator='true']::before {
left: -1px;
}
[data-workspace-board-card-drop-indicator='true']::after {
right: -1px;
}
[data-workspace-board-card-drag-count='true'] {
position: absolute;
top: -8px;
right: -8px;
z-index: 2;
display: flex;
min-width: 18px;
height: 18px;
align-items: center;
justify-content: center;
border-radius: 9999px;
border: 1px solid var(--sidebar-border);
background: var(--foreground);
color: var(--background);
font-size: 10px;
font-weight: 600;
line-height: 1;
}
.sidebar-header {
padding: 12px 16px 8px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted-foreground);
}
.worktree-list {
padding: 0 4px;
}
.worktree-item {
display: flex;
flex-direction: column;
gap: 2px;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
transition: background 120ms;
}
.worktree-item:hover {
background: var(--accent);
}
.worktree-item.active {
background: var(--accent);
}
.worktree-branch {
font-size: 13px;
font-family: inherit;
color: var(--foreground);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.worktree-path {
font-size: 11px;
color: var(--muted-foreground);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ── Misc ────────────────────────────────────────────── */
.number-input-clean {
appearance: textfield;
}
.number-input-clean::-webkit-outer-spin-button,
.number-input-clean::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* ── Landing ─────────────────────────────────────────── */
.landing {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: var(--background);
}
.landing-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.landing-title {
font-size: 48px;
font-weight: 700;
color: var(--muted-foreground);
letter-spacing: -0.02em;
}
.landing-action {
background: var(--secondary);
border: 1px solid var(--border);
color: var(--foreground);
font-family: inherit;
font-size: 14px;
padding: 8px 24px;
border-radius: 6px;
cursor: pointer;
transition: background 150ms;
}
.landing-action:hover {
background: var(--accent);
}
.landing-hint {
font-size: 12px;
color: var(--muted-foreground);
}
/* ── fix: ensure Monaco find widget layout is not reversed ── */
.monaco-editor .find-widget,
.monaco-editor .find-widget .monaco-inputbox,
.monaco-editor .find-widget .matchesCount {
direction: ltr !important;
flex-direction: row !important;
}
.monaco-editor .find-widget .button,
.monaco-editor .find-widget .monaco-button,
.monaco-editor .find-widget .monaco-find-input .controls .button {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
padding: 0 !important;
width: 20px !important;
height: 20px !important;
line-height: normal !important;
box-sizing: border-box !important;
vertical-align: middle !important;
border: none !important;
box-shadow: none !important;
transition: none !important;
}
/* Why: the expand/collapse toggle (the chevron on the far left) is positioned
absolutely and relies on Monaco's default height:100% to vertically center
its icon over the find row. The generic .button override above forces a fixed
20px height, which pins the chevron to the top. Restore Monaco's full-height
18px toggle so the chevron centers like the stock find widget. */
.monaco-editor .find-widget .button.toggle {
width: 18px !important;
height: 100% !important;
top: 0 !important;
}
.monaco-editor .find-widget .button > .codicon,
.monaco-editor .find-widget .button > i {
display: flex !important;
align-items: center !important;
justify-content: center !important;
width: 100% !important;
height: 100% !important;
}
.monaco-editor .monaco-search-result-highlight {
background: color-mix(in srgb, var(--warning, #f59e0b) 34%, transparent);
border-radius: 3px;
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--warning, #f59e0b) 52%, transparent);
}
.monaco-editor .monaco-markdown-doc-link {
color: #58a6ff !important;
text-decoration: underline;
text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
text-underline-offset: 2px;
}
/* Why: Monaco's faded scrollbar uses the class `invisible`, which collides with
Tailwind's `.invisible` utility (visibility: hidden) — so also restore visibility. */
.monaco-editor
.reference-zone-widget
.monaco-scrollable-element
> .scrollbar.vertical.invisible.fade {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
/* Why: Monaco leaves the peek preview container `inline-block` and only sizes the
editor child, so the container shrink-wraps the child in BOTH axes while the
inherited automaticLayout ResizeObserver measures the container — a feedback
loop that ratchets the preview to blank at fractional zoom/DPI (issue #7405).
Pinning both dimensions to the splitview slot severs the loop. */
.monaco-editor .reference-zone-widget .preview.inline {
height: 100%;
width: 100%;
}
/* ── Git conflict decorations ───────────────────────────────────── */
.monaco-editor .orca-conflict-marker-line {
background: color-mix(in srgb, var(--destructive) 12%, transparent);
}
.monaco-editor .orca-conflict-section-line {
border-left: 3px solid transparent;
}
.monaco-editor .orca-conflict-current-line {
background: color-mix(in srgb, var(--git-decoration-modified) 12%, transparent);
border-left-color: color-mix(in srgb, var(--git-decoration-modified) 72%, transparent);
}
.monaco-editor .orca-conflict-base-line {
background: color-mix(in srgb, var(--muted-foreground) 7%, transparent);
border-left-color: color-mix(in srgb, var(--muted-foreground) 36%, transparent);
}
.monaco-editor .orca-conflict-incoming-line {
background: color-mix(in srgb, var(--git-decoration-renamed) 11%, transparent);
border-left-color: color-mix(in srgb, var(--git-decoration-renamed) 68%, transparent);
}
.monaco-editor .orca-conflict-line-decoration {
width: 4px !important;
margin-left: 3px;
background: var(--destructive);
border-radius: 999px;
}
.monaco-editor .orca-conflict-margin {
background: color-mix(in srgb, var(--destructive) 8%, transparent);
}
.monaco-editor .orca-conflict-marker-label {
color: var(--destructive);
font-family: var(--font-sans, system-ui, sans-serif);
font-size: 11px;
font-weight: 600;
font-style: normal;
opacity: 0.92;
}
/* Why: Monaco's find-widget button tooltips (Close, Find in Selection, etc.)
render inside a `.context-view` wrapper → `.workbench-hover-container` →
`.workbench-hover.compact`, positioned above the target button. The wrapper's
bounding box (z-index 2576, `pointer-events: auto`) overlaps the button hit
area, so as the tooltip appears it fires `mouseleave` on the button → 200ms
debounce → hover disposes → mouse is again "over" the button → `mouseover`
re-fires a new tooltip → loop. The visible effect is an infinitely flashing
tooltip that also blocks clicks on the button beneath it. The overlap is
only reliably reproducible when the right sidebar is open, because the find
widget's x-position shifts the wrapper onto the button rect; with the
sidebar closed the wrapper often lands slightly offset and doesn't intercept.
These are compact tooltips (`HoverStyle.Pointer` → `.workbench-hover.compact`)
configured with `hideOnHover: true`, so the CompositeMouseTracker only tracks
the target element — not the wrapper. Making the entire wrapper chain
non-interactive breaks the overlap feedback loop without affecting tracker
logic, keyboard focus traps, or interactive markdown hovers (which are never
`.compact`). The `:has()` selector ensures we only disable pointer events on
context-views that actually contain a compact hover, leaving menus, dropdown
popups and other context-view consumers untouched. */
.context-view:has(> .workbench-hover-container > .workbench-hover.compact),
.workbench-hover-container:has(> .workbench-hover.compact) {
pointer-events: none;
}
/* Why: compact hovers for simple label strings (e.g. "Find in Selection (⌥⌘L)")
get `white-space: pre-wrap` inline-set by Monaco, so when the tooltip lands
near the viewport edge its max-width clamps and the label wraps to 2–3 lines,
pushing the tooltip down over the find-widget buttons. These labels are
single-line by design; forcing nowrap keeps the tooltip on one line and lets
the positioning logic flip/shift as needed to fit. Only applies to compact
hovers — markdown hovers still wrap normally. */
.workbench-hover.compact .hover-contents {
white-space: nowrap !important;
}
/* ── Update card animations ───────────────────────────────────────── */
@keyframes update-card-enter {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes update-card-exit {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.animate-update-card-enter {
animation: update-card-enter 200ms ease-out both;
}
/* Why: `skills update` reports no parseable progress, so the skill-update bar is
deliberately indeterminate rather than a faked percentage. */
@keyframes skill-update-slide {
from {
transform: translateX(-100%);
}
to {
transform: translateX(365%);
}
}
.animate-update-card-exit {
animation: update-card-exit 150ms ease-in both;
}
/* Waveform bars in the FeatureTipsModal voice tip — purely decorative. */
@keyframes waveform {
0%,
100% {
transform: scaleY(0.4);
opacity: 0.6;
}
50% {
transform: scaleY(1);
opacity: 1;
}
}
.animate-waveform {
animation: waveform 1.4s ease-in-out infinite;
transform-origin: center;
}
/* CLI feature tip — terminal cursor inside the agent-control command. */
@keyframes cli-tip-caret {
0%,
45% {
opacity: 1;
}
46%,
100% {
opacity: 0;
}
}
.animate-cli-tip-caret {
animation: cli-tip-caret 0.9s steps(1, end) infinite;
}
@keyframes cli-tip-command-line {
0% {
opacity: 0;
transform: translateY(2px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.animate-cli-tip-command-line {
animation: cli-tip-command-line 160ms ease-out both;
}
@media (prefers-reduced-motion: reduce) {
.animate-cli-tip-caret {
animation: none;
opacity: 1;
transform: none;
}
.animate-cli-tip-command-line {
animation: none;
}
}
.cli-tip-orchestration-frame .feature-wall-orch-stage {
--feature-wall-workspace-title-size: 11.5px;
--feature-wall-agent-message-size: 10.5px;
--feature-wall-agent-row-gap: 5px;
--feature-wall-agent-status-col: 10px;
--feature-wall-agent-icon-col: 12px;
--feature-wall-agent-status-box: 10px;
--feature-wall-agent-status-icon: 9px;
--feature-wall-agent-icon-box: 12px;
--feature-wall-child-indent: 22px;
align-content: start !important;
padding-top: 10px !important;
padding-left: 12px !important;
padding-right: 52px !important;
}
.cli-tip-orchestration-frame .feature-wall-agent-icon svg {
height: 11px;
width: 11px;
}
.cli-tip-orchestration-frame .feature-wall-agent-status [aria-label] {
height: 10px;
width: 10px;
}
.cli-tip-orchestration-frame .feature-wall-agent-status [aria-label] > span {
height: 6px;
width: 6px;
border-width: 1.5px;
}
/* Cmd+J feature tip — JS-driven sequence (keypress → palette → typing → results).
The keypress press-down and palette entrance are handled inline by React state
transitions; this file only defines the caret blink and the results fade-in. */
/* Blinking caret for the simulated command-palette input while "typing". */
@keyframes cmd-j-tip-caret {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
.animate-cmd-j-tip-caret {
animation: cmd-j-tip-caret 1s step-end infinite;
}
/* Worktree rows fade in when the palette opens — mirrors the real palette,
which shows recent worktrees on empty query and filters synchronously. */
@keyframes cmd-j-tip-result-in {
0% {
opacity: 0;
transform: translateY(2px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.animate-cmd-j-tip-result-in {
animation: cmd-j-tip-result-in 120ms ease-out both;
}
@media (prefers-reduced-motion: reduce) {
.animate-cmd-j-tip-caret {
animation: none;
opacity: 1;
}
.animate-cmd-j-tip-result-in {
animation: none;
opacity: 1;
transform: none;
}
}
/* ── Feature wall: agents-orchestration animations ───────────────── */
/* Why: drives the supported-agents marquee on page 1 of the agents tile.
Track is duplicated in the DOM so a -50% translate loops seamlessly. */
@keyframes feature-wall-marquee-flow {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
.feature-wall-marquee-track {
animation: feature-wall-marquee-flow 36s linear infinite;
}
/* Shared click pulse for feature-wall fake cursors. Matches the Editor tour
ring so every animated mouse click reads as the same interaction. */
.feature-wall-click-ring {
animation: feature-wall-click-ring 460ms ease-out forwards;
}
@keyframes feature-wall-click-ring {
0% {
transform: scale(0.4);
opacity: 0.9;
}
100% {
transform: scale(1.4);
opacity: 0;
}
}
/* Why: smooth reveal for the Tasks-tile workspace card so it eases in once the
workspace flips from "Creating" to "Ready" instead of popping in. */
@keyframes tasks-workspace-in {
0% {
opacity: 0;
transform: translateY(4px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/* Why: orchestrator → child connector arrows. Faint dashed strokes drawn in
JS so endpoints follow the real card geometry as cards animate between
phase 1 (parent + nested child) and phase 2 (parent + spawned peer). */
.feature-wall-orch-arrows {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 1;
transition: opacity 280ms ease;
}
.feature-wall-orch-arrows[data-fading='true'] {
opacity: 0;
}
.feature-wall-orch-arrows path {
fill: none;
stroke: color-mix(in srgb, var(--foreground) 32%, transparent);
stroke-width: 1.5;
stroke-dasharray: 4 4;
}
/* Message envelope that travels along the dashed connector. Position is
driven by `offset-path` so the bubble visibly follows the same shape the
connector drew. */
.feature-wall-bubble {
position: absolute;
top: 0;
left: 0;
z-index: 3;
width: 24px;
height: 24px;
border-radius: 999px;
background: var(--foreground);
color: var(--background);
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow:
0 6px 16px rgba(24, 24, 27, 0.22),
0 1px 2px rgba(24, 24, 27, 0.12);
opacity: 0;
offset-rotate: 0deg;
offset-distance: 0%;
pointer-events: none;
will-change: offset-distance, opacity, transform;
transform: translate(-12px, -12px) scale(0.7);
}
.feature-wall-bubble svg {
width: 13px;
height: 13px;
}
.feature-wall-bubble.in-flight {
opacity: 1;
offset-distance: 100%;
transform: translate(-12px, -12px) scale(1);
transition:
offset-distance 1600ms cubic-bezier(0.25, 0.6, 0.25, 1),
transform 280ms ease,
opacity 280ms ease;
}
.feature-wall-bubble.landed {
opacity: 0;
offset-distance: 100%;
transform: translate(-12px, -12px) scale(0.85);
transition: opacity 320ms ease 80ms;
}
/* When a message just lands, briefly tint the recipient line so the new
text reads as a fresh inbound message rather than ambient text. */
@keyframes feature-wall-msg-pop {
0% {
color: rgb(180 83 9);
}
70% {
color: rgb(180 83 9);
}
100% {
color: var(--foreground);
}
}
.feature-wall-msg-received {
animation: feature-wall-msg-pop 1100ms ease-out;
}
/* Children container fades in once the orchestrator finishes creating. */
.feature-wall-children-wrapper {
opacity: 0;
transform: translateY(6px);
transition:
opacity 320ms ease,
transform 320ms ease;
}
.feature-wall-children-wrapper[data-visible='true'] {
opacity: 1;
transform: none;
}
@keyframes feature-wall-child-card-in {
0% {
opacity: 0;
transform: translateY(4px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.feature-wall-child-card-shell {
animation: feature-wall-child-card-in 260ms ease-out both;
}
/* Each child agent row stays collapsed until *its* dispatch bubble lands on
it, so the child workspace visibly gains an agent as work arrives. */
.feature-wall-spawn-row {
overflow: hidden;
opacity: 1;
max-height: 60px;
transform: none;
transition:
opacity 320ms ease 80ms,
transform 320ms ease 80ms,
max-height 320ms cubic-bezier(0.4, 0, 0.2, 1) 80ms,
margin 320ms cubic-bezier(0.4, 0, 0.2, 1) 80ms,
padding 320ms cubic-bezier(0.4, 0, 0.2, 1) 80ms;
}
.feature-wall-spawn-row[data-pending='true'] {
opacity: 0;
transform: translateY(2px);
pointer-events: none;
max-height: 0;
margin-top: 0;
margin-bottom: 0;
padding-top: 0;
padding-bottom: 0;
transition:
opacity 220ms ease,
transform 220ms ease,
max-height 320ms cubic-bezier(0.4, 0, 0.2, 1),
margin 320ms cubic-bezier(0.4, 0, 0.2, 1),
padding 320ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* Brief one-shot pulse when the Codex limits popover swaps accounts and the
"0% used" label flashes from red to green. */
@keyframes feature-wall-meta-pulse {
0% {
color: rgb(220 38 38);
transform: scale(1);
}
20% {
color: rgb(22 163 74);
transform: scale(1.08);
}
100% {
color: rgb(22 163 74);
transform: scale(1);
}
}
.feature-wall-meta-pulse {
color: rgb(22 163 74);
font-weight: 600;
animation: feature-wall-meta-pulse 1100ms ease-out 1;
}
.orca-contextual-tour-target-rings {
border-radius: 9999px;
pointer-events: none;
}
.orca-contextual-tour-target-rings::before {
content: '';
position: absolute;
inset: -2px;
border: 1px solid color-mix(in srgb, var(--ring) 72%, transparent);
border-radius: inherit;
box-shadow: 0 0 0 1px color-mix(in srgb, var(--ring) 18%, transparent);
opacity: 0;
transform-origin: center;
animation: contextual-tour-target-ring 1.8s ease-out infinite;
}
@keyframes contextual-tour-target-ring {
0% {
opacity: 0.72;
transform: scale(0.68);
}
72%,
100% {
opacity: 0;
transform: scale(2.4);
}
}
@media (prefers-reduced-motion: reduce) {
.orca-contextual-tour-target-rings::before {
animation: none;
opacity: 0.72;
transform: scale(1.28);
}
}
/* ── Diff comment decorations ────────────────────────────────────── */
/* Why: the "+" button is appended into Monaco's editor DOM node by
useDiffCommentDecorator. Keep it visually subtle until hovered so it does
not distract from the diff itself. Left position overlaps the glyph margin
so the affordance reads as "add comment on this line". */
.orca-diff-comment-add-btn {
position: absolute;
left: 4px;
width: 18px;
height: 18px;
display: none;
align-items: center;
justify-content: center;
padding: 0;
border: 1px solid color-mix(in srgb, var(--foreground) 22%, var(--border));
border-radius: 4px;
background: color-mix(in srgb, var(--foreground) 5%, var(--editor-surface));
color: color-mix(in srgb, var(--foreground) 78%, var(--muted-foreground));
cursor: pointer;
z-index: 5;
opacity: 1;
box-shadow: 0 1px 2px color-mix(in srgb, var(--foreground) 12%, transparent);
transition:
color 100ms ease,
background-color 100ms ease,
border-color 100ms ease,
box-shadow 100ms ease;
}
.orca-diff-comment-add-btn:hover {
color: var(--primary);
border-color: color-mix(in srgb, var(--primary) 52%, var(--border));
background: color-mix(in srgb, var(--primary) 12%, var(--background));
box-shadow: 0 1px 4px color-mix(in srgb, var(--foreground) 16%, transparent);
}
.orca-diff-comment-add-btn:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 1px;
}
.orca-diff-comment-range-highlight {
background: rgba(59, 130, 246, 0.14);
}
.dark .orca-diff-comment-range-highlight {
background: rgba(96, 165, 250, 0.18);
}
.orca-diff-comment-inline {
width: 100%;
/* Why: Monaco's view-zone node already starts after the diff gutter. Avoid
adding a second left inset so saved notes sit flush with the code column. */
padding: 4px 24px 6px 0;
box-sizing: border-box;
}
.orca-diff-comment-inline > .orca-diff-comment-card {
max-width: 420px;
}
/* Why: the saved note used to blend into the editor background because it
reused the same muted/background mix the editor itself uses. Stay
grayscale to match the rest of the UI, but lift the card off the editor
with a foreground-tinted fill, a solid left accent bar, a soft shadow,
and a bolded uppercase meta label. Dark mode uses 6% (vs light's 5%)
to produce roughly the same absolute luminance delta above its editor
surface — the foreground color is different (white vs black) so equal
percentages would not give equal contrast. Override lives in `.dark`. */
.orca-diff-comment-card {
position: relative;
/* Why: Monaco's view-lines overlay (data-mprt=8) is positioned absolutely on
top of view-zone DOM. Without an explicit z-index here, clicks on
interior controls (Cancel/Save in edit mode, the textarea, etc.) hit the
overlay first and never reach our buttons. Lifting the whole card creates
a stacking context that keeps every nested control clickable. */
z-index: 1;
display: flex;
gap: 12px;
border: 1px solid color-mix(in srgb, var(--foreground) 10%, var(--border));
border-radius: 8px;
background: var(--background);
padding: 12px 14px;
font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.04),
0 4px 12px rgba(0, 0, 0, 0.03);
transition:
border-color 150ms ease,
box-shadow 150ms ease,
background-color 150ms ease;
}
.dark .orca-diff-comment-card {
border-color: color-mix(in srgb, var(--foreground) 12%, var(--border));
background: color-mix(in srgb, var(--foreground) 2%, var(--editor-surface));
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.2),
0 4px 16px rgba(0, 0, 0, 0.15);
}
/* Content Column */
.orca-diff-comment-content-col {
flex: 1 1 auto;
min-width: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
/* Header Row */
.orca-diff-comment-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-width: 0;
}
/* Meta Group (Name, line, timestamp) */
.orca-diff-comment-meta-group {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
min-width: 0;
font-size: 13px;
line-height: 1.4;
font-weight: 600;
color: var(--foreground);
}
/* Action button pill: segmented control like Notion's Resolve / ... */
.orca-diff-comment-actions-pill {
display: inline-flex;
align-items: center;
gap: 2px;
padding: 0 2px;
border: 1px solid color-mix(in srgb, var(--foreground) 10%, var(--border));
border-radius: 6px;
background: var(--background);
overflow: hidden;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
}
.dark .orca-diff-comment-actions-pill {
border-color: color-mix(in srgb, var(--foreground) 14%, var(--border));
background: color-mix(in srgb, var(--foreground) 4%, var(--editor-surface));
}
.orca-diff-comment-pill-btn {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 28px;
padding: 4px 7px;
font-size: 11px;
font-weight: 500;
color: var(--muted-foreground);
border: none;
background: transparent;
cursor: pointer;
height: 24px;
transition:
background-color 100ms ease,
color 100ms ease;
}
.orca-diff-comment-edit {
min-width: 28px;
height: 24px;
padding: 4px 7px;
border-radius: 4px;
}
.orca-diff-comment-pill-btn:hover {
background: color-mix(in srgb, var(--foreground) 6%, transparent);
color: var(--foreground);
}
.orca-diff-comment-pill-btn-danger:hover {
background: color-mix(in srgb, var(--destructive) 10%, transparent);
color: var(--destructive);
}
.orca-diff-comment-pill-divider {
width: 1px;
height: 12px;
background: color-mix(in srgb, var(--foreground) 12%, var(--border));
flex-shrink: 0;
}
/* Quote/Reference block: Warm, gorgeous Notion-like callout */
.orca-diff-comment-quote {
display: flex;
align-items: flex-start;
gap: 8px;
margin: 2px 0 4px 0;
padding-left: 8px;
border-left: 2px solid color-mix(in srgb, #e7b10a 35%, transparent);
color: var(--muted-foreground);
font-size: 12.5px;
line-height: 1.35;
}
.orca-diff-comment-quote-text {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Comment Body */
.orca-diff-comment-body {
font-size: 13.5px;
line-height: 1.5;
color: var(--foreground);
white-space: pre-wrap;
word-break: break-word;
margin-top: 2px;
}
/* Popover for adding new comment */
.orca-diff-comment-popover {
position: absolute;
left: 56px;
right: 24px;
max-width: 420px;
z-index: 1000;
padding: 12px 14px;
border: 1px solid color-mix(in srgb, var(--foreground) 10%, var(--border));
border-radius: 8px;
background: var(--background);
color: var(--foreground);
box-shadow:
0 10px 32px rgba(0, 0, 0, 0.12),
0 4px 12px rgba(0, 0, 0, 0.08);
}
.dark .orca-diff-comment-popover {
border-color: color-mix(in srgb, var(--foreground) 14%, var(--border));
background: color-mix(in srgb, var(--foreground) 2%, var(--editor-surface));
box-shadow:
0 10px 32px rgba(0, 0, 0, 0.3),
0 4px 16px rgba(0, 0, 0, 0.2);
}
.orca-diff-comment-popover-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--muted-foreground);
margin-bottom: 2px;
}
.orca-diff-comment-popover-textarea {
width: 100%;
min-height: 56px;
max-height: 240px;
overflow-y: auto;
resize: none;
padding: 8px 12px;
border: 1px solid color-mix(in srgb, var(--foreground) 12%, var(--border));
border-radius: 6px;
background: var(--background);
color: var(--foreground);
font-family: inherit;
font-size: 13px;
line-height: 1.45;
outline: none;
transition:
border-color 150ms ease,
box-shadow 150ms ease;
}
.dark .orca-diff-comment-popover-textarea {
border-color: color-mix(in srgb, var(--foreground) 10%, var(--border));
background: color-mix(in srgb, var(--foreground) 3%, var(--editor-surface));
}
.orca-diff-comment-popover-textarea:focus {
border-color: color-mix(in srgb, var(--ring) 70%, var(--border));
box-shadow: 0 0 0 2px color-mix(in srgb, var(--ring) 18%, transparent);
}
.dark .orca-diff-comment-popover-textarea:focus {
border-color: color-mix(in srgb, var(--ring) 70%, var(--border));
box-shadow: 0 0 0 2px color-mix(in srgb, var(--ring) 12%, transparent);
}
.orca-diff-comment-popover-textarea::-webkit-scrollbar {
width: 12px;
}
.orca-diff-comment-popover-textarea::-webkit-scrollbar-track {
background: transparent;
}
.orca-diff-comment-popover-textarea::-webkit-scrollbar-thumb {
background: color-mix(in srgb, var(--muted-foreground, #737373) 28%, transparent);
border: 3px solid transparent;
background-clip: padding-box;
}
.orca-diff-comment-popover-footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
margin-top: 4px;
}
/* Why: tours need a distinct item surface without highlighting the underlying target. */
.orca-contextual-tour-panel {
--contextual-tour-panel-surface: color-mix(in srgb, var(--foreground) 7%, var(--popover));
--contextual-tour-panel-border: color-mix(in srgb, var(--foreground) 14%, var(--border));
background: var(--contextual-tour-panel-surface);
border-color: var(--contextual-tour-panel-border);
box-shadow:
inset 0 1px 0 0 color-mix(in srgb, var(--foreground) 10%, transparent),
0 10px 24px rgba(0, 0, 0, 0.18);
}
.dark .orca-contextual-tour-panel {
--contextual-tour-panel-surface: color-mix(in srgb, var(--foreground) 10%, var(--popover));
--contextual-tour-panel-border: color-mix(in srgb, var(--foreground) 16%, var(--border));
box-shadow:
inset 0 1px 0 0 color-mix(in srgb, var(--foreground) 5%, transparent),
0 10px 24px rgba(0, 0, 0, 0.18);
}