Follow-up to #1254. The lockfile tracks skill source/hash metadata which
now lives alongside the skill sources in the internal delivery repo;
keeping a parallel copy here in the public tree is misleading (entries
can drift) without being useful to anyone.
- Remove skills-lock.json from the tree.
- Gitignore it so local tooling can still write one without making
git status dirty.
Co-authored-by: Orca <help@stably.ai>
Private skill files are now stored and versioned in a separate internal
repo; a setup hook populates .claude/skills and .agents/skills with
machine-local symlinks instead. This keeps contributor-facing patterns
(agent skills in the tree) while allowing some skills to be developed
privately.
- Remove tracked skill files (62 files across 7 skill dirs).
- Gitignore the now machine-local skill directories.
- Add an env-gated hook to orca.yaml scripts.setup. Runs a setup script
whose path lives in $ORCA_INTERNAL_DEV_SETUP when present; silently
no-ops for public contributors.
No new contributor-facing requirements: the hook is optional, the env
var is only set by internal tooling, and the setup itself runs in the
worktree that Orca just created.
Co-authored-by: Orca <help@stably.ai>
* chore: update .gitignore to include stackdump and .serena, enhance pre-commit script
* fix(win32): resolve EPERM on userData writes and batch-file spawn failures
Three Windows-specific issues prevented Orca from running correctly on
machines where Chromium resets the userData DACL during startup:
1. **EPERM on userData writes** — Chromium's BrowserWindow constructor calls
SetNamedSecurityInfo on the userData folder with a Protected DACL. When
propagated to child directories the ACEs carry the Inherit-Only flag,
meaning they apply to children-of-children but NOT to the directories
themselves. Any file write inside codex-runtime-home, agent-hooks, or
similar subdirectories fails with EPERM.
Fix: grant an explicit Full Control ACE (OI)(CI)(F) on userData and all
existing children before BrowserWindow is created (icacls /T /C).
Explicit ACEs survive future DACL propagation from the parent. Per-write
EPERM retries in fs-utils and installer-utils serve as the backstop for
directories created after startup.
2. **Batch-file spawn failures** — resolveCodexCommand() can return a .cmd
or .bat path (e.g. codex.cmd installed via npm). Node's spawn() cannot
execute batch scripts directly without shell:true, but shell:true with an
args array triggers DEP0190 because args are concatenated rather than
escaped. Both service.ts and codex-fetcher.ts were affected.
Fix: detect .cmd/.bat paths and route through cmd.exe /c explicitly,
which is equivalent to what shell:true does internally but avoids the
deprecation warning and arg-escaping hazard.
3. **Native dep rebuild failure** — electron-builder install-app-deps does
not expose the ignoreModules option. On Windows dev machines without the
full VC++ / Python toolchain, cpu-features (an optional dep of ssh2) fails
to build with node-gyp, aborting the entire postinstall step.
Fix: replace electron-builder install-app-deps with a thin wrapper script
(scripts/rebuild-native-deps.mjs) that calls @electron/rebuild's JS API
directly with ignoreModules: ['cpu-features'] on Windows. ssh2 detects
the missing native module and falls back to pure-JS automatically.
Refactoring: extract shared win32-utils.ts with getIcaclsExePath(),
getCmdExePath(), isWindowsBatchScript(), isPermissionError(), grantDirAcl(),
and getSpawnArgsForWindows() to eliminate five instances of duplicated
SystemRoot path construction and two near-identical EPERM retry blocks.
Reduce startup icacls calls from three sequential blocking /T invocations
to one, removing up to 20 s of potential startup delay.
* fix(win32): address review feedback on ACL and spawn helpers
- Fall back to SID via `whoami /user` when `USERNAME` is unset so
`grantDirAcl` works under services, CI, and hardened envs instead of
silently no-op'ing.
- Use a 60s timeout for recursive `icacls /T` walks; the 10s cap could
starve on large userData trees and silently fail the startup grant.
- Pass `windowsHide: true` to `icacls` and the cmd.exe-routed Codex
spawns so no console window flashes in the packaged GUI app.
- Add `/d` to `cmd.exe /c` invocations to disable AutoRun registry
commands — safer default for background spawns.
- Drop unused `createRequire`/`require` from rebuild-native-deps.mjs.
- Add `@electron/rebuild` as an explicit devDependency; relying on the
electron-builder transitive was brittle under pnpm.
- Fix two misleading "Re-enable inheritance" comments that describe
behavior opposite to what the code actually does (explicit ACL grant).
- Add unit tests for `isWindowsBatchScript`, `getSpawnArgsForWindows`,
and `isPermissionError` to lock in Windows batch detection + cmd.exe
routing.
Co-authored-by: Orca <help@stably.ai>
* fix(win32): unify PTY spawn through /d and document cmd.exe safety
- fetchViaPty now uses getCmdExePath() and /d /c, matching the rest of
the codebase instead of hand-rolling 'cmd.exe' + ['/c', ...].
- getSpawnArgsForWindows gains a SAFETY note: when the .cmd/.bat branch
is taken, cmd.exe re-parses the combined command line, so callers
must only pass trusted/literal args.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
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>
* feat(editor): preserve Cmd+B for bold in markdown editor
Carve out bare Cmd/Ctrl+B from the main-process before-input-event
interceptor when the TipTap markdown editor is focused, so its bold
keymap can run instead of toggling the left sidebar. Focus state is
mirrored from renderer to main via a one-way IPC send, with default-deny
resets on crash/navigate/destroy and sender validation so only the main
window's webContents can mutate the flag.
* fix: add oxlint max-lines disable to createMainWindow.ts
Move build resources to resources/build/, icon source files to
resources/icon-source/, scripts to config/scripts/, and patches
to config/patches/ for a cleaner top-level directory layout.
* feat: use Enter to submit and Shift+Enter for line break in comment editor
Closes#48
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add CLAUDE.md and gitignore package-lock.json
Enforce pnpm usage for AI agents and prevent stray npm lock files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Swap out eslint + 6 plugins for oxlint and prettier for oxfmt (oxc toolchain).
Set up husky + lint-staged to run linter and formatter on pre-commit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>