Adds File > Export as PDF... menu item (Cmd+Shift+E) and an overflow
menu entry that renders the active markdown preview through a sandboxed
Electron BrowserWindow and writes it to disk via printToPDF.
- New main-side IPC handler (src/main/ipc/export.ts) and html-to-pdf
helper that loads a CSP-locked HTML document in a sandboxed, context-
isolated window with javascript enabled only for image-ready polling.
- Renderer helpers clone the rendered markdown subtree, inline all
computed styles through a curated allowlist, and ship the resulting
HTML fragment over IPC.
- Ref-counted listener registration so split-pane layouts install
exactly one IPC subscription and survive panel churn.
Anchor-only links (#heading) now scroll to the matching heading in both
the markdown preview and rich editor. Preview uses rehype-slug to stamp
heading ids; the rich editor walks headings with the same stateful
GithubSlugger for parity (including duplicate-heading suffixes).
Merge decoration update and selection into a single ProseMirror
transaction so scrollIntoView is not lost. After dispatch, manually
scroll the outer flex container using coordsAtPos since
tr.scrollIntoView cannot reach the non-overflowing editor wrapper.
Also change active match highlight from orange to blue for better
contrast.
The rich-mode unsupported-content check ran canRoundTripRichMarkdown()
unconditionally — synchronously creating a throwaway TipTap Editor,
parsing the entire document, and serializing it back — all on the main
thread during React render. For a 120KB file this blocked for ~10s.
Redesign: run cheap regex checks first (reference links, footnotes,
HTML). If no unsupported syntax is detected, allow rich mode immediately
without any round-trip. The expensive round-trip is now only invoked
when HTML is detected and the file is under 50K chars.
Move the Cmd+N handler above the new-workspace early-return so the
shortcut opens the composer modal from the tasks page. Add an
activeModal guard to the page-level Esc handler so it yields to the
composer modal's own Esc dismissal.
When the active worktree is the repo root, linked worktrees are nested
subdirectories. rg --files listed files from every worktree instead of
just the active one. Pass sibling worktree paths as --glob exclusions.
Also wrap scroll-to-top in rAF so it runs after cmdk's scroll-into-view.
Adds an opt-in terminal setting that automatically copies the current
selection to the system clipboard as the user selects, mirroring X11 /
gnome-terminal behavior. xterm.js has no native option for this, so the
renderer hooks `onSelectionChange` per pane and writes via the existing
clipboard IPC. Defaults to false so existing users keep the explicit
Cmd/Ctrl+Shift+C copy flow.
Closes#860
Autosave writes to disk echo back as fs:changed events that were treated as
external edits, triggering a setContent reload mid-typing that reset the TipTap
selection to the document end (and could drop unsaved keystrokes). Stamp each
self-write in a registry so useEditorExternalWatch ignores its own echo, and
preserve selection when genuine external edits do arrive.
* fix(agents): detect opencode/pi CLIs installed under ~/.opencode/bin and ~/.vite-plus/bin
Packaged Electron inherits a minimal PATH without shell rc files, so
agent install-script fallback dirs stay invisible to `which` probes —
the Agents settings page then shows OpenCode/Pi as "Not installed"
even when the user can run them from Terminal.
Add ~/bin, ~/.opencode/bin, and ~/.vite-plus/bin to the packaged PATH
augmentation so preflight detection matches shell behavior.
Fixes#829
* feat(agents): hydrate PATH from user's login shell + Agents Refresh button
Packaged Electron inherits a minimal launchd PATH that misses whatever
the user's shell rc files append — ~/.opencode/bin, ~/.cargo/bin, nvm
shims, pyenv, custom tool dirs. The preceding commit hardcoded two known
install locations; this replaces that whack-a-mole pattern with a
generic approach.
On packaged startup (non-Windows), spawn `${SHELL} -ilc 'echo $PATH'`
with a 5s timeout, parse the delimited PATH, and prepend any new
segments to process.env.PATH. The result is cached for the app session
so we pay the shell-init cost at most once.
Surface a Refresh button in Settings > Agents that forces a re-probe
and re-detects installed agents — handy right after installing a new
CLI, no restart needed.
Live-verified that a `zsh -ilc` spawn with a minimal launchd-style env
still resolves the user's full PATH (32+ segments including the
rc-appended dirs).
* refactor(hydrate-shell-path): simplify dedup with Set + Set.difference
Set preserves insertion order, so PATH first-match-wins semantics are
preserved without manual tracking. Set.prototype.difference (Node 22+)
expresses the new-segments calculation in mergePathSegments as the
set-difference operation it always was.
Adds a + button on the tasks page that opens a dialog to create a new
GitHub issue in the selected repository. Wires createIssue through the
main/preload IPC using gh api, and refreshes the tasks list after
creation so the new issue shows up immediately.
Also clears a stray Radix pointer-events lock on GitHubItemDrawer mount
so Close/open-in-GitHub buttons remain clickable after the New Issue
dialog closes.
* fix(browser): debounce find-in-page query to stop per-keystroke flash
Why: findInPage re-highlights the active match on every call, which flashed
as the user typed. Debounce (200ms) so the highlight only re-runs once typing
settles. Enter still uses the live query for immediate next/previous nav.
* fix(browser): auto-select top suggestion, preserve address-bar typing
- Top history suggestion is auto-selected so Enter navigates to the best
match without an extra ArrowDown keypress.
- URL syncs from the store and webview navigation events no longer clobber
the address bar while the user is actively typing in it. Previously,
opening a fresh tab and starting to type could be overridden when the
configured default URL (e.g. google.com) finished resolving.
Why: macOS atomic writes (Claude Code Edit, vim :w, VSCode save) deliver
a delete followed by a same-path create in back-to-back fs:changed
payloads. The tab flashed struck-through for one render tick before the
follow-up create cleared it.
Debounces only the 'deleted' signal by 75ms, keyed by absolute path. A
same-path create in the next payload cancels the pending tombstone
before it paints. Naked deletes still resolve to 'deleted' after the
window; single-payload rename correlation is unchanged.
Extends the prior fix — bold, italic, strike, and link were still
rendering with an active highlight. Per the same rationale, toolbar
buttons are action triggers, not style indicators, so none of them
should surface active state.
Block buttons (paragraph, headings, lists, blockquote) are action
triggers that transform the current block — they aren't status
indicators. Highlighting them as "active" was misleading. Only mark
toggles (bold, italic, strike, link), which represent styles applied
to a selection, continue to expose active state.