* Optimize agent picker menu updates and pointer highlighting
- Keep catalog hydration re-renders scoped to open menu content
- Preserve unchanged picker rows and fix menu highlights after scrolling or submenu flicks
* Improve menu pointer handoffs and picker update performance
- Preserve focus during scrolling and portaled setup interactions
- Open rested submenu targets without an extra pointer move
- Avoid unnecessary picker row updates while keeping translations current
* Fix inventory subscriber assertions to identify agents
* Defer launch picker inventory updates to passive effects
* Add menu pointer highlight component fixtures and tests
* Fix checkbox menu item test accessibility state
* Reset retained menu pointer tracking on reopen
* Keep menu highlights in sync while scrolling submenus
* Remove synthetic submenu pointer handoff
- Preserve Radix hover and keyboard navigation without triggering clicks
- Keep wheel-scroll highlight synchronization responsive to current cursor position
* Preserve submenu pointer grace after wheel scrolling
* Preserve prevented menu pointer movement during scrolling
* Fix menu pointer tracking observer cleanup
* Recognize component fixture specs as Playwright tests
* feat(sidebar): collapse sub-agent lists by default and group idle sub-agents
Child agent lists no longer auto-expand when a parent spawns sub-agents, so
cards don't grow and shift the sidebar. A collapsed parent shows +N plus a
state dot when hidden children are doing live work. Two or more idle children
fold behind an expandable 'N idle' row.
* test(sidebar): build typed agent rows instead of casting
* fix(sidebar): use a data attribute instead of an unknown class for the idle group hook
* fix(sidebar): keep hidden sub-agents reachable and visible
- Send-target picker opens every lineage level and idle group so child agents stay targetable
- Full display mode shows the live-state dot beside a collapsed +N, like compact
- Collapsed signal considers all descendants, not just direct children
- A collapsed parent highlights when it hides the focused agent
- Idle group row joins the agent tree as a treeitem
* fix(sidebar): sync idle-group strings; no toggles or folding while picking a send target
Right-clicking a File Explorer row now offers an "Open in" submenu
listing the apps from Settings > Open In, matching Source Control.
The submenu moves out of the Source Control entry menu into a shared
OpenInContextSubmenu used by both menus, keeping the existing
getOpenInEntryAvailability gating for SSH, remote runtime, and
unresolved owners.
Open failures for a file row are worded as a path rather than the
workspace (new pathInvalid/pathNotFound strings; remoteLaunchFailed
renamed to pathLaunchFailed since it now covers local paths too).
Fixes#9742
Co-authored-by: Neil <neil@stably.ai>
* feat(themes): make Match Terminal a real app theme painted at :root
Match Terminal used to be an inline override on three left-column
elements, so the tab bar, right sidebar, status bar, titlebar, menus and
editors kept the built-in palette, and its fixed color mixes became
unreadable on low-contrast palettes.
It is now a light/dark app theme slot value synthesized from the active
terminal palette (read through resolveTerminalPalette, so each mode's
color overrides apply) and painted through the plugin theme pipeline
(derivation, contrast gate, startup cache, Monaco theme). The document
mode follows the terminal background, including onboarding's restore.
The legacy leftSidebarAppearanceMode='match-terminal' value fills the
empty slots on load and on writes (desktop and web), and it is kept
mirrored while a slot matches the terminal so older Orca still matches
its sidebar. The pickers mark the option with a muted "(dynamic)" label.
* fix(onboarding): sync the painted-theme ref in an effect, not during render
* feat(themes): use a lightning icon for Match Terminal's dynamic label
* perf(themes): reuse the Match Terminal theme when its content is unchanged
* fix(wsl): host-row Start follows the same precedence as the other surfaces; Start's reload doesn't wake
The sidebar host menu offered Start whenever the distro was stopped and the
WSL setting was on. It now uses the same rule as the editor, Explorer and
Source Control: stopped, and the host can run. Missing server files keep
their own notice, and a setting-off user whose saved host still serves gets
Start. After Start, the editor reloads the file without asking to wake the
distro again; the click already did.
* i18n(wsl): translate the host offer's project-not-found message
* test(wsl): run the stopped-host hook tests through the real unavailable-reason path
* fix(agents): open a shell, not a chat, in empty workspaces when the default agent is Auto
* fix(agents): use the host launch preferences type for the empty-workspace default
A switch over runtime RPC (the phone's Accounts screen) changed the
selection but never reached the window's running-session handling, which
only the status bar and Settings called after their own IPC switch. The
RPC path now reports the lane and outgoing/incoming account to the main
window, which runs the same handler. The web client handles its own
switches and opts out.
* fix(wsl): turning the WSL hosts setting off keeps saved hosts serving; it stops only new hosts and moves
* chore(wsl): drop the stale run-refusal doc comment
* feat(skills): auto-update unedited Orca skills; ask only for edited copies
Planner already hashes each installed SKILL.md against every shipped release
digest (legacy installs included). When a plan's only writes replace such
hash-verified copies, the freshness nudge now applies it silently. Edited
copies and repairs still go through the consent toast; edited copies now raise
it too instead of only showing attention in settings.
* feat(skills): decide silent skill updates per skill and per location
Add skills.autoUpdateBundledOrca (capability-gated): the host plans the named
installed skills under their locks and applies only hash-verified released-copy
updates, re-checking each location before writing. The nudge auto-updates each
unedited skill and names only edited, unknown or repair-needing skills; the
try-once guard is keyed per skill and observed state, so one failure falls back
to the toast for that skill alone.
* fix(skills): surface preserved edits, share run ownership, re-show retracted nudges
- A silent update that moved a racing edit to a backup now shows a warning
naming the skill and backup path instead of passing as success.
- Silent updates run under skill-update-run-store's per-target ownership, so
manual and automatic runs never interleave and the dialog shows busy.
- Programmatic toast retraction releases its session marker; only a user
dismissal suppresses the nudge.
* fix(skills): report stranded edits, isolate silent runs, bound attempt memory
- An old copy whose late edits cannot be backed up now fails the op with
strandedPath naming where the edits remain; the silent caller warns with that
path, and the next plan's backup cleanup is nudged, never deleted.
- Silent runs never start over a running or unacknowledged foreground result
and never surface in the status bar.
- Auto-update attempts are keyed per target and pruned to the states the
current scan observes; unchanged states stay suppressed.
* fix(skills): warn when a silent update's result is lost
Any rejected or timed-out auto-update request may follow a committed swap
whose racing edit went to a backup. Show a persistent warning naming the
target and its skill backup directory, and settle the attempt so the same
state is handed to the nudge instead of retried.
* test(skills): guard version skippers; ledger also walks release tags
Audit of every v* tag and HEAD history found no SKILL.md bytes missing from
released-digests.json. The generator now also walks release tags (falling back
to HEAD) so a release cut off-branch can never ship unrecognized bytes. Add a
real-disk test that installs the oldest shipped orchestration (v1.3.24-rc.2) in
the npx-skills and agent-folder layouts and auto-updates it silently.
* test(skills): type the nudge mock's error without an assertion
* chore(i18n): add the silent skill update warnings to the English catalog
* fix(plugins): refuse main-window permission requests from plugin frames
Plugin srcdoc frames share the main window's session, so a plugin could ask for
camera, microphone, fullscreen or pointer lock with the app's trust. Requests from a
frame registered as a plugin frame at creation, or nested in one, are now refused.
Chat visuals and other host frames keep their permissions.
* fix(plugins): type host frame identity instead of object
* perf(diff): stop combined diff sections re-rendering on unrelated settings writes
Combined diff viewers and EditorPanel subscribed to the whole settings object,
and DiffSectionBody built a fresh Monaco options object each render, so any
settings write re-rendered every mounted section and called updateOptions on
each. Select only the settings each component reads, let DiffSectionItem read
its diff display settings directly, and memoize the DiffEditor options.
* test(diff): drop type assertion in options identity test
* perf(editor): select resolved typography so inactive fallback inputs do not re-render editors
* fix(editor): resolve typography when no cached entry exists
* feat(telemetry): A/B holdout for the Chat UI launch tip
Freeze a 50/50 chat_ui_launch_tip_v1 cohort for profiles still due the tip on
official builds with telemetry not disabled; control never sees the tip. Emit
chat_ui_feature_tip_exposure in both arms and agent_default_surface_changed
from the tip and from Settings -> Agents.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(telemetry): hold the Chat UI launch tip until the telemetry banner resolves
Enroll only when consent is enabled. While the banner is pending, the tip is
skipped for that launch without being marked seen, so its exposure is not
spent unmeasured and the arm cannot influence the consent choice.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(telemetry): document best-effort Chat UI tip exposure on rejected sends
Co-Authored-By: Claude <noreply@anthropic.com>
* test(telemetry): pass the exposure test through a hoisted mock instead of in-test importActual
Co-Authored-By: Claude <noreply@anthropic.com>
* test(telemetry): justify the fake Store assertion in the telemetry harness
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(editor): choose a code editor theme independently of the app theme
* fix(editor): paint the diff overview ruler with the editor theme
* chore(i18n): add editor theme strings to the English catalog
* fix(editor): editor-theme settings search and Orca line tints in a separate editor theme
Searching 'editor theme' now opens the Interface section that holds the rows.
Diff comment-range bands and the conflict base-section tint use the editor
theme's line-number color, so they stay visible when the app's tokens don't
contrast with a differently based editor background.
* fix(editor): an editor-only theme paints the diff colors it would paint as the app theme
Main's diff tokens reach Monaco only where a theme painted them, and omitted tints
come from main.css fallbacks. An editor-only theme now reads its tokens through the
same fallbacks, so its diff colors match the ones it paints as the app theme.
* fix(mobile): run the repo's archive hook when deleting a workspace from the phone
Mobile's delete sent worktree.rm without runHooks, so the host skipped the
orca.yaml archive hook that desktop runs. The phone now does what desktop's
ensureHooksConfirmed('archive') does: it asks to trust an unapproved shared
archive script, sends runHooks with desktop's reply timeout, and when the hook
fails it shows the host's error with Delete Anyway, which resends with
allowFailedArchiveHook.
repo.hooks now reports archiveTrust (hash and content of the shared orca.yaml
archive script, or null when none needs trust). Older hosts omit it, and the
phone then keeps skipping the hook rather than run a script it cannot check.
* fix(mobile): share one Modal for delete drawers and pin each prompt to its host
Delete's confirm, archive trust prompt and hook failure now share one
BottomDrawerModalHost so iOS never presents over a closing drawer. A pending
prompt or failure keeps the clients it started on, the row hides as soon as
Delete is tapped, repo-wide trust counts server-owned repos, and folder repos
report no archive trust since their removal runs no hook.
* fix(mobile): recheck archive trust before Delete Anyway and count server repos once
Delete Anyway goes back through the trust decision, so a script changed since
approval is asked about again. Repo-wide trust counts each server repo once
rather than once per workspace, and restoring a row never duplicates it.
* fix(mobile): honour repo-wide archive trust only with the desktop catalog loaded
Until the catalog loads, a same-id repo on another host cannot be ruled out,
so only an approval of that exact script counts. The trust drawer's actions
take the pending request synchronously so a double tap cannot both run and
skip the hook.
* fix(mobile): drop archive prompts and failures from another desktop
A trust prompt or hook failure belongs to the desktop it started on. After a
host switch it is no longer shown or acted on, so a retry can never pair one
desktop's trust with another desktop's repo catalog.
* refactor(mobile): one archive trust check and an optional drawer busy flag
* fix(mobile): file delete-failure strings under the host screen overlays
Also pins the host's archive hash as a literal digest so the node test no
longer imports renderer code.
* fix(mobile): carry delete haptics and automations onto the renamed hook trust drawer
The phone's liveness probe read a full status.get (~12 KB, ~2.3 KB gzipped)
and threw it away on every idle and foreground probe. It now sends
`status.get {probe: true}` and the desktop answers `{runtimeId}` alone,
skipping the machine-name wait. status.get keeps accepting any params, as
when it declared none; older desktops ignore the flag and answer the full
status, which still proves liveness.
terminal.list takes `includePreview: false`. The phone's session inventory
and automation pane lookup never read the 300-char output tail, so they ask
the desktop to drop it; older desktops strip the unknown key and keep
sending it.
Compressed terminal binary frames now use gzip level 1. On recorded-style
terminal output it keeps 93-99% of the level-6 saving (57-95% of bytes
saved) at 1.2-4x less main-thread CPU; text replies keep the default level,
where level 1 costs ~45% more bytes on the wire.
* fix(mobile): guard catalog snapshots against late replies and trim the snapshot paths
A slow unchanged or delta reply that predates a client switch (A→B→A)
could re-point the paired-server snapshot token at rows it no longer
matched; the next row delta then rebuilt onto the wrong base. Pending
reads now carry the token they asked with, and admit drops any non-full
reply whose token is no longer held.
Single-flight reads with one trailing join move into
WorktreeCatalogSnapshotClient.read(), so paired-server rows get the same
protection home had; home's own copy goes away. Server rows use one read
path for old and new desktops and drop snapshots for servers that leave.
The host screen asks for a first page only while nothing is shown and
the full read is not failing, and drops it on an invalid reply. The
shell no longer seeds the page with a status proven on a connection
that has since dropped.
Cleanup: drop exactActivity (unreleased; home only ranks by coarsened
activity and its exact rows defeated delta reuse), fold the legacy opt-in
check into the snapshot resolvers, remove unused worktree.ps result
types and a duplicate page subscription, and re-record the affected
RPC goldens.
* fix(mobile): keep server-row snapshots per client and skip first pages for loaded empty hosts
A shared per-host snapshot let a slow read on a replaced client reclaim
ownership from the new one, discarding the new client's reply. Snapshots
are now kept per client, and a delta rebuild retry runs only for the
current owner. An empty host that already loaded no longer asks for a
first page on every poll.
* feat(editor): click to zoom image viewer like Chromium, Alt-drag to pan
* fix(editor): anchor click zoom on the current view; tolerate near-fit pinch; Alt-press never zooms
* fix(editor): treat a near-100% fit as actual size; track Alt in capture phase
* test(editor): diagram viewer pans on Alt-drag
* fix(emulator): orient iOS streams using helper metadata
* fix(emulator): read serve-sim's pushed orientation instead of polling /config
serve-sim already sends {width, height, orientation} on the control socket the
pane holds, on connect and on every rotate, fold or resize. Reading it there
drops the main-process 1 s /config poll and the frame IPC/preload plumbing.
* fix(emulator): keep the reported orientation while the control socket pauses
A stream error briefly disables the socket; dropping the orientation then
turned the last frame sideways until it reconnected.
---------
Co-authored-by: Gemini Wen <coffeesherk@gmail.com>
* fix(new-workspace): offer project switch for cross-repo GitHub links from upstream-only projects
* fix(new-workspace): keep the cross-repo fallback retryable; drop test cast
* fix(pi): preserve session titles after turns
* test(pi): preserve title batching coverage after synthesis opt-out
* test(pi): move title batching coverage to Hermes and drop duplicate headless test
Pi no longer synthesizes titles, so the renderer batching tests use Hermes,
which still does. The shared profile test and the runtime spec already pin
that Pi keeps its own title.
* test(pi): stop expecting a synthetic Pi ready title in typed-identity specs
---------
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* fix(emulator): restore scrcpy control encoders and track the video size
Bring back the scrcpy v2.4 control-message encoders removed as unwired
in #13528 (touch, keycode, text), re-checked against control_msg.c and
the server's ControlMessageReader: generic-finger pointer id, the
official u16 pressure fixed point, and text split into <=300-byte
messages on code point boundaries.
The server drops touch events whose screen size differs from the
current video size. v2.4 reports the size once in its codec meta; after
a rotation the new size is only in the next SPS, so the session reads it
from every config packet. ScrcpyStreamSession.sendControl() returns,
the control socket disables Nagle, and the registry keeps the live
session's control target next to its video.
* fix(emulator): send Android input through the scrcpy control socket
Android pane input ran one `adb shell input` process per action (~150 ms
each on a USB phone) and replayed drags only on pointer-up as a fixed
300 ms straight swipe between the endpoints.
Pane pointer phases now go to main as they happen over a new
fire-and-forget `emulator:scrcpyTouch` IPC and are written to the live
session's scrcpy control socket, so drags and scrolls track the pointer.
CLI and agent tap, gesture, type and button use the same socket whenever
a stream is live; `adb shell input` remains the fallback without one.
Input routing and the `wm size` cache the fallback needs move into
AndroidDeviceInput. iOS is unchanged.
* test(emulator): keep the adb fallback test from leaking a failing sendControl
* docs(emulator): explain why pane touches have no per-phase adb fallback
* fix(emulator): keep iOS touch callback stable across interact changes
sendTouch depended on canInteract only for the scrcpy branch, but the new
dep also rebuilt it for iOS, so a canInteract flip mid-drag ran the frame's
cleanup, ended the live touch early and replayed the drag over RPC. The
frame already gates touch begins on canInteract.
* fix(emulator): narrow scrcpy touch payloads without a type assertion
The changed-code casting gate forbids `as`; use `in` narrowing like
the other IPC payload guards.
---------
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
Terminal padding was applied as start-edge container margin but subtracted only once from the container size, so the grid stayed flush with the right and bottom pane edges. Subtract it from both edges so the inset is symmetric, keeping the band painted by the pane rather than xterm.
Fixes#36760
The question title and option descriptions used the default white-space,
so newlines from AskUserQuestion collapsed into one run of words. Use
whitespace-pre-wrap like the approval card description already does.
Co-authored-by: BingZ <11211020+bbingz@users.noreply.github.com>
* fix(wsl-host): a pane parked by a stopped host reopens once Start brings the host back
* fix(wsl-host): clear only the returned host's stopped refusal; test the strip and the bridge
The phone gave a direct socket 5 s from open to authenticated, while
the desktop keeps an unauthenticated socket for 10 s. On a slow link
(a handshake is two round trips, so ~2.5 s RTT is enough) the phone
dropped the socket the desktop would have accepted, then redialed into
the same deadline forever: the 'Can't connect' loop a test documented.
Both ends now share PAIRED_E2EE_HANDSHAKE_DEADLINE_MS (10 s). A link
that needs 6 s to authenticate now connects on the first dial; one that
never authenticates still escalates the same way, 5 s later. Relay
dials are unaffected: they keep their own stage budgets and race the
direct dial after 2.5 s.
The host screen re-read repo.list every minute and on every repo or SSH
event. Each reply carried every repo's uploaded icon inline as a data URL
(up to 400 KB each, barely compressible), although repos rarely change.
repo.list now takes an optional afterSnapshotId and answers unchanged
when the caller already holds the exact reply. The id is content-derived
(the hashing moves to content-snapshot-id.ts, shared with worktree.ps).
The phone keeps the last catalog per client, reuses it on unchanged
replies and still refreshes SSH host labels and health.
repo.list declared no params, so older hosts ignore the field and answer
the full catalog as before; callers that send nothing get the byte-exact
legacy reply. Icons still render from the same data, at the same size.
* fix(agent-picker): render the agent setup panel above the new tab menu
The setup panel opened from an agent row's arrow used the base popover
layer (z-60), below the menu it opens from (z-70), so the menu's drop
shadow fell across the panel and it looked tucked behind the menu.
Sharing the menu layer stacks it by open order, like a submenu, and
keeps the panel's own permission dropdown above it.
* test(agent-picker): pin the setup panel to the menu layer
The host screen re-read every paired server's full worktree catalog on
each 3 s poll, through the desktop's mobileRelay.hosts.worktrees, even
when nothing changed: the same cost worktree.ps had before row deltas.
mobileRelay.hosts.worktrees now answers snapshot and row-delta reads with
the same resolver as worktree.ps (made generic over row type). The phone
keeps one WorktreeCatalogSnapshotClient per server, which now takes the
catalog request it snapshots, so server rows reuse the same token,
rebuild and copy-on-admit logic as desktop rows.
The params schema is strict, so the phone sends the snapshot fields only
to a desktop advertising mobile.relay-host-worktree-deltas.v1; older
desktops keep getting plain reads. Callers that send neither field, and
servers whose rows are not fetched yet, get the plain reply.
#36653 added five lines to federated-worker-start.ts, which was already
near the 300-line cap, so main's Lint step fails (305 lines) and every
PR based on main fails static analysis.
Recording a ready remote dispatch's resources and its input-accepted
stage moves, unchanged, into federated-worker-ready-record.ts.
An app theme may declare one validated raster image (png/jpeg/webp, <=2 MB, >=640x360, inside the plugin) served through orca-plugin-image, painted as one static body layer under translucent terminals. Older Orca ignores the field; a bad image drops only the image.
* Agent message cards keep their sender's agent icon when the agent can't be found
A card names its sender by a snapshot written with the message, but drew the
icon only from a live lookup, so a sender the lookup missed (a worker on
another host, a terminal after restart, a closed agent) showed "?". Every
writer (worker start, mail, task, federated start) now saves the sender's
agent kind beside its name, and the card draws it when no live row names
the agent. Live rows stay the source of icon and status whenever they match.
* Preserve known worker agents on federated cards and mail
* Retain acquired worker launch identity for later sender snapshots
* Prune repeated worker sender contract checks
* Combine worker command contract projection imports
* Use the sender snapshot resolver in main's new receiving-account tests
* Show agent message cards in phone chats
* test: type the phone markdown mock with its component props
* Fix phone agent message cards and sender navigation
* Open native chats on the phone in place
* Preserve desktop host and agent when opening chats
* Separate phone cards from shared chat opening foundation
* Show phone agent cards through shared sender opening
* Preserve chat opening and captured lifecycle contracts
* test: cover sender focus and unconfirmed chat close outcomes
* test: exercise structured worker CLI lifecycle through runtime socket
* test: use checked parked mail observation in CLI integration
* test: validate provider notification correlation identities
* test: construct checked lifecycle and navigation fixtures
* Keep cross-program session proofs in their test program
* Localize mobile chat opening feedback and complete runtime contracts
* Use the mocked text component identity in History feedback proof
* Temporarily expose grown E2E type diagnostics
* Keep E2E timer and fetch fixtures in their runtime domains
* Reconcile decreased E2E wait pattern counts
* Preserve live test clocks and expose remaining type diagnostics
* Keep mobile session data types independent of native views
* Complete structured session wire surface fixtures
* Expose existing grown E2E type diagnostics temporarily
* Preserve checked wire fixture tab mock signatures
* Preserve worker redrive when automatic close is refused
* Honor automatic close admission before settlement and retirement
* test: retain History reveal and close publication race
* Match automation reveal callback to activation result
* Align CLI integration fixture with current settings and output
* Prevent retained chats from reopening removed workspaces
* Import notification types directly from their owner
* Unify local removal fences for accepted worktree aliases
* Keep cross-surface authority tests in the combined test project
* Initialize coordinator mail test workspace ownership
* Register the retained automation chat test workspace
* Use checked inputs in worker start failure test
* Preserve the automation test repository host literal
* Open phone card senders through shared caller navigation
* Carry the phone sender deadline through shared admission
* Finish empty phone card measurement and consumer coverage
* test(mobile): target native hosts in image-only card regression
* fix(mobile): fade folded agent message edges
Unchecked boxes used --input (15% white) on dark surfaces, leaving the outline nearly invisible. Use 60% muted-foreground for the unchecked border and fill indeterminate boxes like checked ones.
* feat(themes): public tokens for diffs, git status, tabs, dividers, rows, links
Adds 29 public app theme tokens (diff, git decorations, tab bar, split
divider, current workspace row, host badge, chat bubble, text link), each
falling back to today's built-in color or CSS chain so existing themes and
built-ins render the same. Monaco's diff editor now paints from the diff
tokens, with sharper default diff tints (from #34698) and a readable dark
ignored-file color. The contrast gate learns one-sided drops, translucent
surfaces checked over their base, and main.css chains for half-set pairs.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(themes): check omitted diff tints, chat bubbles and dark host badges as painted
The contrast gate now derives an omitted diff tint from the theme's hue (and
drops the hue when its tint hides code), checks chat bubbles against chat's
mixed foreground on the chat canvas, and models the dark host badge at 80%
accent. The main.css fallback table moves to plugin-theme-css-fallbacks.ts.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(themes): blame a diff hue only for tints it painted, and check chat diff rows
A diff tint the theme set itself no longer drops the theme's hue when it
fails, and chat diff rows (hue-mixed text on the line tint over the chat
canvas) are now checked too.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(themes): new theme tokens only expose colors; unthemed surfaces paint exactly as before
Every new token now falls back to the exact value its surface used before:
- Diff hues default to the git decoration colors at the previous per-side
percentages, and chat diff text keeps its 70% mix.
- Monaco's diff view, overview ruler and links keep Monaco's stock colors
unless the theme painted a diff or link token. The colors go through the
derived Monaco theme instead of a global CSS override.
- --text-link has no :root default; each link rule keeps its old literal as
the fallback, including the markdown-light/-dark preview split and the
always-#58a6ff Monaco doc link.
- Tab bar, tab text and indicator fall back per element (bg-card,
text-foreground, ...) instead of through :root aliases.
- Dark --git-decoration-ignored goes back to #6e6e6e (#35910 ships separately).
The contrast gate skips checks that have no theme token to drop, so themes
that set only older tokens pay no extra probes.
* perf(themes): contrast check skips ring checks the theme didn't set and precomputes drop lists
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(editor): route editor typography through one resolver and add opt-in weight, line height, ligatures, size and macOS smoothing
Monaco sites built font options ad hoc and never received fontWeight,
fontLigatures or lineHeight; the editor size was tied to the terminal.
editor-typography.ts now feeds every Monaco and DOM code surface. New
optional settings default to today's rendering. A macOS-only Font
Smoothing setting rebuilds WebGL terminal renderers so the change
reaches their shared glyph atlases.
* fix: satisfy changed-code quality gates
* fix(terminal): cancel WebGL atlas warm-up on dispose so rebuilt renderers stop leaking canvases
The warm-up queue kept drawing into a disposed atlas, which re-appended its
scratch canvas to the terminal element. Every WebGL renderer rebuild (GPU
toggle, context-loss retry, font smoothing toggle) leaked one canvas per pane.
* feat(editor): read pixel and zero line heights like code editors and let markdown code follow an explicit editor font