The interior theme ("Argus Dark") derives every color from the evolved palette. Orange block cursor is the brand signature — the only moment of Argus identity inside an otherwise neutral terminal canvas. ANSI semantics map directly to existing Argus tokens (red=error/dead, green=live/pass, yellow=warn/stale, blue=info) so the system is coherent without a lookup table.
Terminal background is #0D0D0D — seven points darker than the app shell (#141414). This creates a subtle inset perception: the canvas reads as slightly recessed behind the Argus chrome, without any box-shadow. Overlay bar gets a hairline orange top-border at 20% opacity — echoing the active-pane orange treatment everywhere else in the app. All animations are opacity-only (WebView-safe, no filter/blur).
// Argus Dark — xterm.js theme object const argusTheme = { // Canvas + cursor "background": "#0D0D0D", // slightly deeper than app bg — terminal feels inset "foreground": "#C8C8C0", // warm-shifted off-white — default text (no ANSI escape) "cursor": "#C15F3C", // brand orange — THE signature moment "cursorAccent": "#0D0D0D", // char under block cursor = bg color "selection": "rgba(193,95,60,0.25)", // orange tint, semi-transparent // ANSI base 8 "black": "#1E1E1E", // warm dark — not pure black "red": "#C0392B", // argus_red — errors, dead session, stderr "green": "#3FB950", // argus_green — live, success, pass "yellow": "#D29922", // argus_yellow — warnings, stale, filenames "blue": "#4D9EDA", // argus_blue — dirs, links, info "magenta": "#B06EC0", // warm purple — docker inspect, bold contexts "cyan": "#4DB8B0", // warm teal — k8s output, env vars "white": "#C8C8C0", // same as foreground // ANSI bright 8 "brightBlack": "#4E4E4E", // argus_dim — comments, timestamps, line numbers "brightRed": "#D9534F", // bold error emphasis "brightGreen": "#56D364", // bold success — pytest PASSED "brightYellow": "#E8BB3F", // warm gold — bold warning "brightBlue": "#79B8F8", // keyword syntax, type names, bold links "brightMagenta": "#C678DD", // bold magenta "brightCyan": "#6ACFCA", // bold teal — strings in some shell configs "brightWhite": "#F0F0E8" // near-white warm — highest contrast text }; // xterm.js config const termOptions = { theme: argusTheme, fontFamily: "'JetBrains Mono', ui-monospace, Menlo, monospace", cursorStyle: "block", cursorBlink: true };
/* ── Argus Overlay Bar ─────────────────────────── */ /* NOTE: use position:fixed in prod (this preview */ /* uses position:absolute). All else copy-paste. */ #argus-overlay { position: fixed; bottom: 0; left: 0; right: 0; z-index: 9999; height: 40px; background: rgba(12, 10, 8, 0.94); border-top: 1px solid rgba(193, 95, 60, 0.20); display: flex; align-items: center; padding: 0 12px 0 10px; gap: 10px; user-select: none; } #argus-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; background: #3FB950; animation: argus-pulse 2s ease-in-out infinite; } #argus-dot.detached { background: #D29922; animation: none; opacity: 0.75; } #argus-dot.dead { background: #4E4E4E; animation: none; opacity: 0.50; } @keyframes argus-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } } #argus-status { flex: 1; min-width: 0; font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; font-size: 11px; color: rgba(200, 200, 192, 0.65); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; letter-spacing: 0.01em; } /* Kotlin innerHTML: <strong>name</strong> · uptime */ #argus-status strong { color: #C15F3C; font-weight: 600; } #argus-morph { display: inline-flex; align-items: center; gap: 5px; padding: 0 10px; height: 32px; /* > 30px tap-target floor */ background: rgba(193, 95, 60, 0.10); border: 1px solid rgba(193, 95, 60, 0.28); border-radius: 3px; color: #C15F3C; font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; font-size: 11px; font-weight: 500; text-decoration: none; cursor: pointer; flex-shrink: 0; white-space: nowrap; transition: background 0.15s, border-color 0.15s; } #argus-morph:active { background: rgba(193, 95, 60, 0.20); border-color: rgba(193, 95, 60, 0.45); } /* REQUIRED: clear xterm canvas for overlay */ .xterm-screen { padding-bottom: 40px; }
/* REQUIRED — stops page competing with xterm scroll */ html, body { background: #0D0D0D; margin: 0; overflow: hidden; } /* OPTIONAL — warm vignette. pointer-events:none, no blur, no box-shadow — perf-safe in WebView. */ .argus-vignette { position: fixed; inset: 0; pointer-events: none; z-index: 9998; background: radial-gradient( ellipse 90% 80% at 50% 50%, transparent 55%, rgba(6, 4, 3, 0.50) 100% ); } /* OPTIONAL — scanlines at 3.5% opacity. Set opacity:0 to disable. background-attachment:fixed OMITTED — buggy WebView. */ .argus-scanlines { position: fixed; inset: 0; pointer-events: none; z-index: 9997; background-image: repeating-linear-gradient( 0deg, transparent 0px, transparent 3px, rgba(0,0,0,0.035) 3px, rgba(0,0,0,0.035) 4px ); } /* Add before </body>: <div class="argus-vignette"></div> <div class="argus-scanlines"></div> */
ui-monospace, Menlo, monospacecursorStyle:"block", cursorBlink:trueTerminal panes carry three visual identifiers: (1) a 3dp green left-edge stripe on the pane container, (2) a [TERMINAL] monospace badge in the header, and (3) an orange page-count chip showing open pane count. The stripe color tracks the session dot — green = live · yellow = detached · gray = dead. Code-server panes use a blue [CODE] badge; no stripe.
The keybar provides one-tap terminal keys above the system nav bar. Standard key chips (esc through slash) existed in v0.8. The new tmux argument chips (-s · -t · new-session · -A) insert tmux flags into the active pane. They use a lighter orange tint to signal "smart argument" vs plain key-send. Chips are horizontally scrollable on smaller form factors. Tap any chip below to preview the press state.
// Standard key → xterm.js action // esc → term.write('\x1b') // tab → term.write('\t') // ctrl → modifier latch (combinator) // alt → modifier latch // ↑↓←→ → '\x1b[A' '\x1b[B' '\x1b[D' '\x1b[C' // home → term.write('\x1b[H') // end → term.write('\x1b[F') // | → term.write('|') // - → term.write('-') // / → term.write('/') // Combinator (ctrl latch example) if (ctrlLatched) { const code = key.charCodeAt(0) - 64; term.write(String.fromCharCode(code)); ctrlLatched = false; }
// Tmux chips insert argument text + trailing space // -s → term.write('-s ') // -t → term.write('-t ') // new-session → term.write('new-session ') // -A → term.write('-A ') // Common build with chips: // [new-session] [-A] [-s] name // → "tmux new-session -A -s argus" /* Orange-tint visual diff from standard key: */ .kc-tmux { background: rgba(193, 95, 60, 0.07); border-color: rgba(193, 95, 60, 0.22); color: rgba(193, 95, 60, 0.85); font-size: 11sp; }
Tapping "+" opens the Add Pane bottom sheet. The Terminal tab shows a command/session field followed by the "SPAWN NEW AI AGENT SESSION" section — four agent archetype chips (radio-select) and four capability toggles. Leaving all chips deselected opens a plain terminal. Allow Write is the most consequential toggle and renders first. Tap the chips and switches below to interact.
// Bottom sheet — slides up from bottom edge // Corner radius: 8dp top-left + top-right only // Background: --panel (#1C1C1C) // Border: 1px solid --border (#272727) // Max-width: 420dp on tablet, full-width phone // ── Tab row ────────────────────────────────── // Active tab: color orange, border-bottom 2px orange // Inactive tab: color --muted, no underline // Font: JetBrains Mono 11sp, uppercase // ── Command/Session input ───────────────────── // Background: --panel2 (#242424) // Border: 1px solid --border // Border-radius: 4dp // Placeholder: "--muted" color, JetBrains Mono 12sp // ── Agent archetype chips — radio group ─────── // Options: Investigator · Developer · Generalist · Mechanic // Deselected: background: --panel2; border: 1px solid --border; color: --muted; // Selected (one at a time): background: rgba(193, 95, 60, 0.10); border-color: rgba(193, 95, 60, 0.40); color: --orange; // ── Capability toggles ──────────────────────── // Track ON: background --orange; border --orange // Thumb ON: #ffffff // Track OFF: background --panel3; border --border // Thumb OFF: --muted // Defaults: Allow Write = ON · rest = OFF // Row divider: 1px rgba(255,255,255,0.04) // ── Action buttons ──────────────────────────── // Cancel: ghost — border --border, color --muted // Open: filled — background --orange #C15F3C // text #fff, weight 600, border-radius 4dp
currentColor so it inherits orange from the parent without a fill attribute. If you prefer the current ↩ glyph, ignore this card entirely.When pane count reaches 3, the header bar enters dense mode: row height drops from 44dp to 28dp, the [TERMINAL]/[CODE] type badge is hidden to save space, the session label truncates to folder name only, and the page-count chip shrinks to 18dp. The 3dp left-edge stripe remains the sole session-state signal in dense mode.
// Dense mode activates when paneCount >= 3 paneTabRow.height: 28dp /* was 44dp */ sessionLabel.textSize: 10sp /* was 11sp */ sessionLabel.truncate: END /* folder name only */ typeBadge.visibility: GONE /* [TERMINAL]/[CODE] hidden */ pageChip.minSize: 18dp /* was 20dp */ pageChip.textSize: 8sp /* was 9sp */ // Unchanged in dense mode: stripe.width: 3dp /* live: #3FB950 · detached: #D29922 · dead: #4E4E4E */ paneIcon.size: 10dp /* down from 12dp for visual balance */
Two pane management gestures: (1) Long-press any pane tab → context menu anchored below the tab with Swap and Close options. (2) × button appears on the active pane tab and closes immediately. Swap reorders pane positions without touching the underlying tmux sessions — sessions stay alive, only the ViewPager order changes.
// ── Close pane ───────────────────────────────── // × button visible only on the ACTIVE (selected) pane tab // Tap × → immediate close, no confirmation dialog // Session stays alive in tmux — only the WebView detaches // If closing last pane → show empty-state dashboard // ── Swap pane ────────────────────────────────── // Trigger: long-press any pane tab — 400ms, haptic VIRTUAL_KEY // Context menu anchors directly below the long-pressed tab // "Swap with [other pane name]" → swaps visual positions only // Sessions NOT restarted — only ViewPager item order changes // If 3+ panes: "Swap with..." submenu lists all other panes // ── Context menu tokens ──────────────────────── menu.background: --panel2 (#242424) menu.topBorder: 2px solid --orange (#C15F3C) menu.borderRadius: 0 0 5dp 5dp /* flat top, rounded bottom */ swapItem.color: --orange (#C15F3C) closeItem.color: #D9534F /* red — destructive action */ row.padding: 9dp vertical · 14dp horizontal
When two panes share the screen in split layout, a 4dp invisible drag handle sits between them. Touch-and-hold activates after the Drag Activation Delay (Settings → Behavior, default 300ms). While dragging: the handle glows orange, a width-percentage tooltip tracks your finger, and both panes resize live. Release snaps to the nearest 5% increment. Minimum pane width: 30%.
dim hairline only
activates after delay
snaps to 5% on release
// ── Handle dimensions ────────────────────────── handle.width: 4dp handle.touchTarget: 20dp /* expanded touch zone, centered on handle */ // ── Idle (default) ──────────────────────────── handle.bg: transparent hairline.size: 2dp × 24dp, color --border (#272727) // ── Touch-hold (after activation delay) ─────── handle.bg: rgba(193, 95, 60, 0.15) hairline.color: rgba(193, 95, 60, 0.50) activation.delay: Settings → Drag Activation Delay (default 300ms) activation.haptic: VIRTUAL_KEY (short tick) // ── Active drag ─────────────────────────────── handle.bg: rgba(193, 95, 60, 0.25) handle.bar: 3dp × 30dp, color --orange, border-radius 1.5dp handle.glow: box-shadow 0 0 6dp rgba(193,95,60,0.30) tooltip.text: "38%" (left pane width as integer %) tooltip.bg: --orange, text #fff, 9sp mono 700 tooltip.offset: 8dp above touch point, horizontally centered on handle // ── Snap + constraints ──────────────────────── snap.increment: 5% pane.minWidth: 30% /* hard stop — refuse to drag below 30% */ release.haptic: CLOCK_TICK (snap confirmation)
The app UI theme (see 08-theme-architecture.html) switches colors.xml tokens. The terminal canvas uses a separate 21-key xterm.js theme object — two independent systems. Paste the active theme's object into ITerminalOptions.theme when the user changes app theme. No page reload required — xterm.js applies the palette on the next render cycle.
const solarizedDarkTheme = { "background": "#002B36", "foreground": "#839496", "cursor": "#CB4B16", // Solarized orange — brand signature "cursorAccent": "#002B36", "selection": "rgba(203, 75, 22, 0.25)", "black": "#073642", "red": "#DC322F", "green": "#859900", "yellow": "#B58900", "blue": "#268BD2", "magenta": "#D33682", "cyan": "#2AA198", "white": "#EEE8D5", "brightBlack": "#002B36", "brightRed": "#CB4B16", "brightGreen": "#586E75", "brightYellow": "#657B83", "brightBlue": "#839496", "brightMagenta": "#6C71C4", "brightCyan": "#93A1A1", "brightWhite": "#FDF6E3" };
const draculaTheme = { "background": "#282A36", "foreground": "#F8F8F2", "cursor": "#FF79C6", // Dracula pink — accent signature "cursorAccent": "#282A36", "selection": "rgba(255, 121, 198, 0.25)", "black": "#21222C", "red": "#FF5555", "green": "#50FA7B", "yellow": "#F1FA8C", "blue": "#BD93F9", "magenta": "#FF79C6", "cyan": "#8BE9FD", "white": "#F8F8F2", "brightBlack": "#6272A4", "brightRed": "#FF6E6E", "brightGreen": "#69FF94", "brightYellow": "#FFFFA5", "brightBlue": "#D6ACFF", "brightMagenta": "#FF92DF", "brightCyan": "#A4FFFF", "brightWhite": "#FFFFFF" };
const catppuccinMochaTheme = { "background": "#1E1E2E", "foreground": "#CDD6F4", "cursor": "#CBA6F7", // Mocha Mauve — accent signature "cursorAccent": "#1E1E2E", "selection": "rgba(203, 166, 247, 0.25)", "black": "#45475A", "red": "#F38BA8", "green": "#A6E3A1", "yellow": "#F9E2AF", "blue": "#89B4FA", "magenta": "#F5C2E7", "cyan": "#94E2D5", "white": "#BAC2DE", "brightBlack": "#585B70", "brightRed": "#F38BA8", "brightGreen": "#A6E3A1", "brightYellow": "#F9E2AF", "brightBlue": "#89B4FA", "brightMagenta": "#F5C2E7", "brightCyan": "#94E2D5", "brightWhite": "#A6ADC8" };
term.options.theme = solarizedDarkTheme;Call this via the WebView
JavascriptInterface when the user selects a theme in Settings. No page reload needed — xterm.js repaints on the next render cycle. App UI theme (colors.xml) and terminal theme (xterm.js object) must be switched together in a single coordinated call.Note: these are the terminal canvas ANSI palettes — separate from the app UI CSS tokens in 08-theme-architecture.html.