* fix(terminal): apply pane padding on all four edges
Move the configured inset onto xterm so the terminal fills its pane while the fit calculation accounts for both sides of each axis. Add a geometry golden that forces cell remainders and verifies dynamic padding without relying on renderer pixels.
* fix(terminal): normalize imported padding for fitting
* fix(terminal): align stored and fitted padding
* fix(terminal): inset the grid inside the xterm surface (#13252)
Padding X/Y was applied as start-edge container margin, so the cell grid
stayed flush on the trailing edges and a fractional background opacity
stacked a darker gutter around the viewport. Put the setting on .xterm
so FitAddon insets both axes and the themed background fills the pad.
* fix(terminal): normalize padding before fit
* fix(terminal): align stored and fitted padding
* test(terminal): lock padding before fit
* fix terminal padding opacity compositing
* fix live terminal padding backgrounds
* fix(terminal): preserve source-over alpha blending
* fix(terminal): restore WebGL alpha blending
* test(terminal): complete hidden retention pane fixture
#14397 split `shared/types.ts` into 46 per-domain modules but kept the path as
a re-export barrel so the import sites did not have to change. This removes
the barrel: every consumer now imports from the module that actually declares
the type, and `src/shared/types.ts` is deleted.
Barrels hide where a type lives, make every consumer look like it depends on
the whole domain, and let an unrelated edit invalidate a module that ~2,000
files transitively import.
2,323 import declarations across 2,321 files. Rewritten mechanically: each
specifier was resolved to an absolute path via the TypeScript AST and
recomputed, rather than string-substituted, so alias forms (`@/../../shared/
types`) and per-specifier `type` modifiers survive.
Four cases the mechanical pass had to handle, each found by a gate rather than
by reading the diff:
- Modules inside `src/shared` import the barrel as `./types`, not
`shared/types`. A pre-filter on the latter string skipped 176 of them and
left imports dangling at a deleted file, which surfaced as confusing
`Property 'x' is optional in type 'Repo' but required in Pick<Repo, ...>`
errors rather than "module not found".
- The barrel RENAMED one type on the way through
(`WorkspaceSource as WorkspaceCreateTelemetrySource`), so the original name
in the owning module has to be re-aliased at each consumer.
- Three test files put `;(globalThis as ...)` on the line after the import.
TypeScript parses that `;` as the import statement's terminator, so
replacing through `statement.getEnd()` deletes it and breaks ASI. The
rewrite now stops at the module specifier.
- A file that already imported directly from a module got a SECOND import
from it, because the barrel re-exported those same names — which trips
`import/no-duplicates` under `--deny-warnings`. A post-pass merges
declarations sharing a specifier and type-only-ness; the `import type` plus
`import` pair from one module is left alone, since that form is allowed.
Splitting one barrel import into several genuinely adds lines, which pushed
`terminal-layout-pty-ownership.ts` to 301 counted lines: its 107-character
import must wrap, and neither local type collapses onto one line (101 and 116
characters). Rather than contort a type declaration to fit a line budget,
`collectLeafIds` and `pruneLeaves` move to `terminal-pane-layout-tree.ts` —
they are pure structural operations on the layout tree and independent of PTY
ownership. `visible-worktrees.ts` similarly loses its own mini-barrel
re-export of `isDefaultBranchWorkspace`, with the four real consumers
repointed at the declaring module. No `max-lines` bypass added.
Verified: cold `tsc --noEmit` green on node, cli, and web (buildinfo deleted
first — these projects are `composite: true` and reuse stale caches); the full
`pnpm lint` green, not just bare oxlint — the narrower local check is what let
the duplicate imports reach CI; max-lines ratchet OK at 344.
* fix: address pr-bug-scan validated finding from #4213
On per-file read/size error, skip the file and continue merging instead of returning found:false; preserve first error for the all-failed case.
* add tests
---------
Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
xterm.js has no bold-color theme slot (upstream xtermjs/xterm.js#6032), so the Bold Text override was silently dropped — a false affordance. Makes the Settings copy and the Ghostty importer honest that it isn't applied yet, while preserving any persisted value for a future real implementation. No terminal-rendering change.
Closes#8595
Enable three unicorn rules — one correctness, two performance — and fix every
existing violation repo-wide so the rules pass as errors.
prefer-number-properties (76 sites)
- parseInt/parseFloat/NaN -> Number.* : safe aliases (autofixed).
- isNaN -> Number.isNaN (12 sites, hand-converted): global isNaN coerces its
argument, Number.isNaN does not. Verified every call site already passes a
number (Number.parseInt results, number-typed fields, Date.getTime()), so the
conversion is behavior-preserving today and guards against a future non-numeric
argument silently coercing.
prefer-array-find (26 sites)
- .filter(pred)[0] -> .find(pred); .filter(pred).at(-1) / .pop() -> .findLast(pred).
Drops the intermediate array and short-circuits.
prefer-array-index-of (5 sites)
- .findIndex(x => x === v) -> .indexOf(v).
Verified: typecheck (node/cli/web) clean, 53 affected suites pass (1679 tests),
oxlint clean repo-wide. mobile/ uses findLast safely (already ships ES2023
.toReversed()); config scripts and e2e helpers run on Node 24.
* feat(ghostty): import adjust-cell-height and dual-value window paddings
Two Ghostty config forms previously landed in unsupportedKeys:
- adjust-cell-height percentages (e.g. "35%") now map to
terminalLineHeight (1.35), the direct xterm equivalent. Pixel values
are still rejected because they depend on the rendered cell height.
- window-padding-x/y "top,bottom" / "left,right" pairs now import as
the rounded average, preserving the total padding along the axis.
parseStrictInt moves to numeric-config-values.ts alongside the new
pair parser to keep mapper.ts under the max-lines budget.
* fix(ghostty): tighten import edge cases
Co-authored-by: Orca <help@stably.ai>
* fix(settings): sentence-case account runtime copy
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* feat(ghostty): resolve theme references during config import
`theme = <name>` is how most Ghostty configs pick their colors, but the
importer treated the key as unsupported and silently dropped the entire
palette. Resolve the referenced theme file and merge its colors as
defaults, mirroring Ghostty's own precedence:
- Search user themes first (XDG, then the native macOS dir), then the
bundled themes inside the Ghostty install, and parse the file with the
existing parseGhosttyConfig (theme files are plain config subsets).
- Explicit config keys win over theme values; config palette entries are
appended after the theme's so per-index overrides apply naturally.
- Only color-bearing keys may flow from a theme into the import, a theme
file cannot smuggle font/window settings past the user's config.
- Theme names containing path separators are rejected (no traversal),
files over 256 KB are ignored, and light:/dark: pairs plus unresolvable
names surface as annotated unsupportedKeys instead of vanishing.
* fix(ghostty): align theme resolution with Ghostty lookup
Co-authored-by: Orca <help@stably.ai>
* test: stabilize ghostty import CI coverage
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules
Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.
Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):
error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse (19: copy-then-reverse -> toReversed)
warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type (aliasing footgun guard)
- typescript/no-unsafe-function-type (bans bare Function type)
- unicorn/prefer-array-flat-map (map().flat() -> flatMap())
- unicorn/prefer-regexp-test (.match() in bool ctx -> .test())
mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.
Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.
* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse
mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).
Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* feat(settings): add Ghostty config import
Add safe one-shot Ghostty import with preview and success summary,
and probe documented Ghostty config paths before applying changes.
Refs #958
* chore(git): ignore atl artifacts
* feat(settings): map font-weight, cursor-blink and focus-follows-mouse from Ghostty
Adds three safe direct-mapping Ghostty keys that have clear equivalents
in GlobalSettings: font-weight, cursor-style-blink, and focus-follows-mouse.
Refs #958
* feat(settings): expand Ghostty import to support colors, opacity and option-as-alt
- Add TerminalColorOverrides type grouping 21 optional xterm ITheme fields
- Add terminalBackgroundOpacity, terminalPanePaddingColor, terminalPaddingBalance
to GlobalSettings
- Extend parser to collect repeated keys as string[] (needed for palette lines)
- Map background-opacity, background, foreground, cursor-color,
selection-background/foreground, palette (0-15), window-padding-color,
window-padding-balance, macos-option-as-alt in mapper
- Merge terminalColorOverrides into xterm ITheme at theme resolution; apply
opacity as rgba() with allowTransparency enabled
- Accept hex colors with or without leading # (Ghostty omits it)
- Fix preview diff to use deep equality for object values so already-applied
color overrides no longer reappear on next import
* feat(settings): support background-blur-radius and window-padding-color extend in Ghostty import
- Map background-blur-radius > 0 to windowBackgroundBlur: true; apply
vibrancy on macOS and backgroundMaterial acrylic on Windows at window
creation (blur requires restart — no hot-reload IPC exists)
- Accept window-padding-color = extend/background as valid Ghostty values;
both map to default Orca padding behavior (undefined field) instead of
landing in unsupportedKeys
- Split mapper.test.ts into domain-scoped describes to stay under 300-line limit
* feat(settings): add Window section to Terminal settings panel
Expose terminalBackgroundOpacity, windowBackgroundBlur, terminalPaddingBalance,
terminalPanePaddingColor, and terminalColorOverrides in the settings UI so
imported Ghostty values can be viewed and changed manually.
- New TerminalWindowSection component (extracted from TerminalPane to stay
under the 400-line limit)
- Collapsible color overrides sub-section with ColorField for all 21 xterm
ITheme fields grouped as base, ANSI normal, and ANSI bright
- Reset button clears all color overrides at once
- Window blur toggle shows restart-required note (blur applies at window
creation, no hot-reload IPC exists)
- Search entries added for all new controls
* feat(settings): expand Ghostty import with scrollback, padding, divider, cursor and word-chars keys
Map 9 additional Ghostty keys to Orca settings:
- split-divider-color → terminalDividerColorDark + terminalDividerColorLight
(single value applies to both; Ghostty has no dark/light distinction)
- unfocused-split-opacity → terminalInactivePaneOpacity (direct float 0-1)
- scrollback-limit → terminalScrollbackLimit; applied to xterm scrollback option
- window-padding-x / window-padding-y → terminalPaddingX/Y; applied as CSS
vars --pane-padding-x / --pane-padding-y in terminal.css
- cursor-text → terminalColorOverrides.cursorAccent (xterm ITheme field)
- bold-color → terminalColorOverrides.bold (persisted; xterm ITheme has no
bold field yet — stored for future xterm upgrade)
- cursor-opacity → terminalCursorOpacity; blended into cursor rgba at theme
resolution time
- selection-word-chars → terminalWordSeparator; applied to xterm wordSeparator
- mouse-hide-while-typing → terminalMouseHideWhileTyping field added; renderer
application deferred (needs per-pane disposable + global mousemove listener)
* feat(settings): expose new Ghostty-imported settings in Terminal Settings UI
- Window section: scrollback limit, horizontal/vertical padding, hide mouse
while typing toggle, cursor text and bold color in Color Overrides
- Cursor section: cursor opacity NumberField
- Advanced section: word separators text input
- Search entries added for all new controls
- terminalDividerColorDark/Light and terminalInactivePaneOpacity skipped —
already present in Theme and Pane Styling sections respectively
* feat(terminal): implement mouse-hide-while-typing per pane
Register terminal.onData → cursor:none and mousemove → restore, scoped to
the pane container element. Uses the existing IDisposable per-pane pattern
(same as selectionDisposablesRef). Cleans up on pane close and effect teardown.
* refactor(settings): address ghostty import code review findings
- Centralize GhosttyImportPreview type in shared/types (remove duplicate from mapper)
- Fix parser to strip inline comments without breaking hex color values (#1a1a1a)
- Extract HEX_COLOR_RE to shared/color-validation to avoid duplication
- Remove redundant Number.isNaN checks after Number.isFinite (4 sites)
- Replace unsafe catch-all assignment with explicit font-family branch
- Migrate 280-line if-chain in mapGhosttyToOrca to FIELD_PARSERS registry
- Add human-readable setting labels in GhosttyImportModal via setting-labels map
- Add clarifying comment in index.ts re JSON.stringify undefined behavior
* fix(settings): harden ghostty import from judgment-day review
- Surface readFile errors in GhosttyImportPreview.error instead of
showing misleading 'No config found' on permission denied
- Guard handleApply against double-apply when already applied
- Strip surrounding quotes from parsed config values (font-family)
- Return null from palette handler when all entries fail validation
- Inform user when background-blur-radius radius is not preserved
- Add valuesEqual key-order stability via stableStringify
- Normalize hex colors to #-prefixed format across all color mappers
- Reject blank values before numeric parsing (Number('') === 0 trap)
- Remove selection-word-chars mapping (inverted xterm semantics)
- Guard window-padding-x/y against negative integers
- Reactive mouse-hide-while-typing on existing panes when setting toggles
- Merge terminalColorOverrides on import instead of replacing
* fix(ghostty): drop broken imports, tighten parsing, prompt restart for blur
Review found three high-impact issues in the Ghostty import: scrollback-limit
semantics are inverted/rescaled (Ghostty is bytes with 0=unlimited, xterm is
rows with 0=disabled), and window-padding-color + window-padding-balance set
CSS custom properties (--pane-padding-color, --pane-padding-balance) that
have no consuming rule anywhere in the tree — so users confirming "changes"
to those keys would see nothing happen.
Because none of the three keys have a safe mapping today, drop them from the
import and remove the dead UI controls + GlobalSettings fields + CSS var
plumbing. The mapper now lists them as unsupportedKeys alongside the
existing window-decoration / keybind / custom-shader entries.
Other fixes in the same review:
- allowTransparency now clears when background-opacity returns to 1 (prior
code only ever set it to true, leaving a stale flag with measurable render
cost).
- background-blur-radius = 0 no longer emits a misleading "radius value not
preserved" note (0 cleanly maps to blur=false with no radius to lose).
- Add a 1 MB size cap on the config read so a pathological or symlinked file
cannot OOM the main process.
- Make handleApply async and surface IPC errors inline in the modal instead
of flipping straight to "Import complete" on failure.
- Type settings.previewGhosttyImport as Promise<GhosttyImportPreview> in
preload so shape drift is caught at compile time.
- Make stableStringify recursive so future nested settings round-trip
cleanly through valuesEqual.
- Restrict window-padding-x/y and background-blur-radius to decimal ints;
prior code accepted exponent notation (1e10 sails through Number.isInteger)
and would have landed absurd values in the store.
- Window blur now shows a "Restart required" banner with a Restart now
button when the setting differs from the mount-time snapshot, mirroring
the ExperimentalPane daemon pattern. Blur only applies at BrowserWindow
creation on macOS/Windows.
Co-authored-by: Orca <help@stably.ai>
* feat(settings): move Ghostty import trigger to Terminal section header
Per review feedback: the "Import from Ghostty" row was taking its own slot
in the Terminal settings list alongside real configuration sections. Move
the trigger into the Terminal section's header (upper-right corner) as a
headerAction, next to the Terminal heading — it's a one-shot action, not a
setting.
- SettingsSection gains an optional `headerAction` slot rendered to the
right of the section title/description.
- The useGhosttyImport hook is lifted from TerminalPane into Settings.tsx
so the section header button (owned by Settings.tsx) and the modal
(still rendered inside TerminalPane) share one state instance.
- TerminalPane drops its own "Import" section + the TERMINAL_GHOSTTY_IMPORT
search entry group is no longer referenced there.
- Button carries the official Ghostty mark as a 16x16 icon so it reads
clearly as a cross-app import even before users parse the label.
useGhosttyImport now accepts `GlobalSettings | null` so the parent can call
it above the pre-load spinner guard without violating hook ordering; the
apply path no-ops until settings arrive. Related test file updated to
pass the new `ghostty` prop and to assert the trigger is *not* rendered
inside TerminalPane anymore.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>