SIGN IN SIGN UP

fix: shortcuts work from terminal, editor, and browser focus (#128)

* chore: bump version to 0.3.4

* chore: update sidebar version to 0.3.4

* fix: replace env var pubkey with hardcoded ed25519 public key

* ui: add close button to AppPreferences and ProjectSettings headers

* chore(release): prepare v0.3.6 (#121)

* feat: terminal open file paths on ctrl/cmd click (#110)

* feat(terminal): open file paths on ctrl/cmd click

* fix(terminal): improve file path link resolution

Return structured terminal path open failures so the terminal component owns user-facing toasts, preserve first-candidate resolution while probing paths in parallel, avoid full-file reads in the Tauri file info hot path, and prevent URL fragments from being misclassified as file links.

* fix(terminal): handle file-link open failures and preserve cursor position

Return structured open-failed results from terminal file-link activation and apply :line[:column] suffixes to editor cursor placement when opening files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test: fix dev baseline regressions

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Ginanjar Noviawan <ginanjar@bunnydogstudio.com>

* fix: terminal stability (#109)

* chore: open roadmap tracking branch for terminal continuity issues

* feat(terminal): instrument continuity lifecycle

* fix(terminal): harden transcript replay semantics

* fix(terminal): relax retention truncation

* test(terminal): add continuity regression coverage

* perf(terminal): reduce fit and resize churn via dimension-gated performFit

* fix(terminal): strengthen WebGL recovery determinism and add instrumentation

* feat(terminal): add safe renderer preference toggle (auto/webgl/canvas) on mainline

* test(terminal): add xterm 5.5 baseline performance benchmarks

* docs(guardrail): codify xterm 6.0 branch exclusion from production rollout

* Fix terminal continuity follow-up findings

* Fix benchmark and restore follow-ups

* Fix benchmark churn and typecheck issues

* Fix PR validation test and lint issues

* Stabilize updater fallback test

* feat: built in browser tab foundation (#112)

* feat: browser tab foundation (ADR-005 Phase 1)

Add native browser tabs backed by Tauri child webviews.

- Backend: BrowserTabManager with webview lifecycle (create/navigate/resize/show/hide/destroy)
- Frontend: BrowserPanel with URL bar, back/forward/reload via BrowserControls
- Integration: WorkspaceTab union extended with 'browser'; draggable in WorkspaceTabBar
- Persistence: browser tab refs serialize/deserialize in useEditorPersistence
- Security boundary: deferred URL allowlist to security ADR

Relates to ADR-005 §8.1

* feat: wire browser tab creation UI (dropdown, command palette, shortcut)

- Add Globe icon button in WorkspaceTabBar for new browser tab
- Wire onNewBrowserTab through PaneRenderer/PaneContent component tree
- Add Ctrl+Shift+N keyboard shortcut for new browser tab
- Add 'New Browser Tab' command to CommandPalette

Relates to ADR-005 §8.1

* fix: wire onNewBrowserTab through PaneRenderer leaf/split branches

- Add onNewBrowserTab to PaneLeafRenderer props, destructuring, and PaneContent call
- Add onNewBrowserTab to PaneSplitRenderer props, destructuring, and PaneRendererPanel call
- Ensures Globe button appears in WorkspaceTabBar for all pane types

Relates to ADR-005 §8.1

* fix: loading indicator stuck after page loaded

Problem: window.load event unreliable for SPAs (React, Vue, Angular).
Google is a SPA — navigation uses history.pushState without triggering load.

Solution — Robust polling + timeout fallback:
- JS poller injected into webview now polls every 400ms:
  - Reports URL when location.href changes
  - Reports 'loaded' when document.readyState transitions to 'complete'
  - Hooks history.pushState/replaceState and popstate for SPA navigation
  - Resets loaded flag on each URL change
- Safety timeout: 6-second auto-clear if no loaded signal received
- Added Rust debug logging for browser_tab_report_* commands

Relates to ADR-005 §8.1

* ui: unify terminal and browser tab creation buttons

- Replace separate Plus+Globe buttons with a single unified dropdown:
  - Terminal icon: click to create terminal with default shell
  - Chevron dropdown: reveals both shell picker (Terminal section)
    and Browser Tab option
- Dropdown muncul di SEMUA pane (termasuk yang di-split), solved
  the 'globe button hilang' issue by changing prop contract
- Replaced separate onNewTerminal/onNewTerminalWithShell/onNewBrowserTab
  props with unified onAddTerminal(paneId, shell?) + onAddBrowserTab()
- WorkspaceTabBar.test.tsx updated to use new prop names

Relates to ADR-005 §8.1

* ui: pin terminal and browser buttons to far-right of pane header

- Move add buttons into a dedicated right-side action rail
- Keep exactly 2 buttons visible: Terminal and Browser
- Terminal icon now opens shell menu directly
- Browser icon opens browser tab directly
- Remove previous combined dropdown layout next to tabs

Matches requested pane-header layout from screenshot reference

* fix: keep browser action visible in split panes

PaneSplitRenderer was not forwarding onAddBrowserTab into PaneRendererPanel,
so nested/split panes only showed the terminal action.

This restores the Globe button in all split panes by passing the prop through
all PaneRenderer branches consistently.

* fix: hide active browser webview while terminal menu is open

Child webviews render above normal DOM, causing the terminal dropdown to be
covered when focus is in a browser tab. While the terminal menu is open,
hide browser webviews in that pane; restore the active browser webview when
menu closes.

* fix: restore CI expectations for terminal action title/click behavior

- Keep title 'New terminal (default shell)' for existing tests
- Make terminal action invoke default terminal creation on click
- Still toggles shell menu so current UX remains intact
- Local targeted tests for WorkspaceTabBar + WorkspaceLayout now pass

* fix: address valid review findings and remove local codex artifacts

Applied fixes:
- ignore and remove .codex local files from repo
- verify browser report events against calling webview label
- destroy browser webviews on app shutdown
- avoid duplicate browser navigate from BrowserControls
- keep browser tab creation pane-aware
- preserve browser tabs during syncEditorTabs rebuild
- subscribe browser session changes for persistence
- preserve loading state on updateUrl and re-arm watchdog per navigation
- make browser event subscriptions synchronously disposable
- add browser action wiring test

Skipped as not applied:
- least-privilege webview capability narrowing (needs exact supported permission ids verification)
- terminal button spawn-on-click review suggestion conflicts with current approved UX/menu behavior

* fix: address follow-up valid review findings

Applied:
- redact URL from browser_tab_report_url debug log
- show browser tab title/host instead of raw browserTabId
- support Ctrl+W for active browser tabs
- wire CommandPalette terminal action through pane-aware handler
- strengthen WorkspaceTabBar shell selection test payload assertion
- document/refactor browser webview z-order workaround
- simplify sync browser event subscription cleanup
- add missing hook deps for browser webview effects

Skipped:
- none from this review batch; all still-valid findings were applied

* ui: make terminal add-tab dropdown more compact

- Reduce dropdown width and item height
- Match shell item text size to tabbar title sizing
- Shrink section header, skeleton rows, and '(default)' label
- Keep behavior unchanged

* fix: address latest valid review findings

Applied:
- propagate browser tab emit failures from Rust commands
- subscribe browser tab labels to session store updates
- redact terminal menu tooltip to match behavior
- sync urlRef on browser navigation events to avoid duplicate navigate
- add mount token guard for browserTabCreate resolution
- update tests to current tooltip behavior and shell payload assertion

Skipped with reason:
- CommandPalette handleAddTerminal binding: already pane-aware enough for current API (no ShellInfo path from palette today)

* fix(macos): platform-aware keyboard shortcuts, native traffic lights & error resilience (#114)

* feat(keyboard): platform-aware modifier system (Phase 1)

- New src/renderer/lib/platform.ts: isMac, isWindows, getPlatformModifier, isPlatformModifier helpers
- normalizeKeyEvent: separate ctrlKey→'ctrl' and metaKey→'cmd' (was conflating both to 'ctrl')
- matchesShortcut: macOS cross-modifier aliasing (cmd+k matches ctrl+k config)
- formatKeyForDisplay: 'cmd' token renders as ⌘ on macOS, 'ctrl' as ⌃
- Backward compatible: existing 'ctrl+...' config entries work on all platforms
- Tests updated for new behavior

* fix(keyboard): hardcoded shortcuts now use platform modifier (Phase 2)

- Ctrl+S/Save, Ctrl+B/File explorer: use isPlatformModifier (⌘ on macOS, Ctrl on Windows)
- Ctrl+W/Close tab: macOS uses ⌘+W, Ctrl+W in terminal passes through to shell (backward-kill-word)
- Project switch (Ctrl/Cmd+1-9): already worked, unchanged
- Fixes: ⌘+S, ⌘+W, ⌘+B now work on macOS
- Fixes: Ctrl+W in terminal no longer intercepted on macOS
- Use ref for handleCloseTerminal to avoid declaration-order TS error

* fix(terminal): macOS copy/paste convention + Ctrl passthrough (Phase 3)

- Clipboard operations (copy/paste/select-all) use platform modifier:
  macOS: ⌘+C/V/A, Windows/Linux: Ctrl+C/V/A
- Ctrl+C without selection still sends SIGINT on all platforms
- On macOS terminal, Ctrl+key shortcuts pass through to shell:
  Ctrl+R → reverse-i-search (not intercepted by app command history)
  Ctrl+W → backward-kill-word (not intercepted by close tab)
- ⌘+key shortcuts still intercepted by app as expected

* feat(ui): add React ErrorBoundary at root and per-pane level (Phase 4a)

- New ErrorBoundary component with default fallback UI and retry button
- ErrorFallback in separate file (react-refresh compliance)
- Wraps entire app root in TauriApp to prevent blank screen on crash
- Wraps each PaneContent in PaneRenderer to isolate pane-level errors
- Catches WebGL terminal crashes, browser webview mount errors, etc.
- Logs error + component stack to console for debugging

* fix(macos): Alt/Option key no longer intercepted on macOS (Phase 4b)

- usePreventAltMenu now only activates on Windows
- On macOS, Alt/Option is needed for typing special characters (@, €, £, etc.)
- Fixes inability to type special chars in terminals and editors on macOS

* chore(logging): improve default log levels for debug/release (Phase 4d)

- Debug builds: default to 'debug' level
- Release builds: default to 'warn' level
- Both overridable via RUST_LOG env var
- File-based logging deferred to v0.4.1 (requires fern/log4rs crate)

* test: update clipboard test for platform-aware modifier

- Test now uses platform-appropriate modifier (Ctrl on non-mac, Cmd on mac)
- Fixes test failure in jsdom where isMac=false and metaKey is not the clipboard modifier

* feat(keyboard): make close tab, save file, and file explorer shortcuts customizable

- Add closeTab (Ctrl+W), saveFile (Ctrl+S), toggleFileExplorer (Ctrl+B)
  to DEFAULT_KEYBOARD_SHORTCUTS — now user-customizable via settings
- Replace hardcoded key checks in WorkspaceLayout with matchesShortcut()
- macOS Ctrl+W terminal passthrough still works (isMac check preserved)
- Update test mock to include new shortcut entries

* feat(macos): native traffic lights via Rust setup hook

- In Rust setup (#[cfg(target_os = "macos")]):
  set_decorations(true) + set_title_bar_style(Overlay) before window is shown
- TitleBar.tsx: hide custom minimize/maximize/close on macOS
- Add 70px left spacer for native traffic lights on macOS
- Windows/Linux: custom window controls unchanged
- Rust compile clean, all 920 tests pass

* fix: CodeRabbit review fixes + browser tab panic

CodeRabbit findings:
- lib.rs: log decoration/titlebar setup failures instead of swallowing
- lib.rs: remove redundant `use tauri::Manager` (already in scope)
- matchesShortcut: reject ctrl+... on macOS before exact-match check,
  preventing double-fire (shell passthrough + app shortcut) on macOS
- tests: add shell passthrough guard test, split platform branches

Browser tab panic:
- Replace `tokio::spawn` with `tauri::async_runtime::spawn` in
  ExitRequested handler — the run callback fires on threads without
  a Tokio reactor (e.g. macOS WKWebView delegate threads)

* feat(browser): web annotation tool (#113)

* feat: browser annotation MVP

- Add annotation mode toggle to browser tabs with drag-to-rect capture
- Inject JS overlay into webview for viewport-relative region selection
- Zustand annotation store keyed by normalized URL with intent/severity taxonomy
- Annotation panel sidebar with inline editing (intent, severity, description)
- Export modal with Markdown (compact/standard/detailed) and JSON tabs
- Webview hide/show on export modal to prevent occlusion by native webview
- URL poller extended to report title changes
- IPC commands: inject_annotation, remove_overlay, report_region_captured
- Session-scoped annotations with cleanup on tab close
- 12 unit tests for annotation store

* feat(browser): add element-selector annotation tool

- Element selector mode alongside rectangle draw mode
- Hover highlight + click capture with deterministic CSS selector generation
- Data minimization: attribute allowlist, size caps, sensitive-element exclusion
- Mode-aware overlay reinjection with fallback on failure
- Capture-time metadata in IPC payload (prevents navigation races)
- selectorConfidence tracking (unique-id / unique-class / fallback)
- Markdown/JSON export with escaping and truncation
- Sanitization: control-char stripping, markdown escaping
- Event authenticity: isTrusted verification
- Tests: store, export, session-store sub-mode

* feat(browser): add visual markers for annotations with bidirectional focus

* fix: resolve lint errors and clippy warnings for annotation feature

* fix: resolve infinite loop from useShallow on mutable Map reference

* feat(browser): move annotation tools into panel header

Moves draw/select/note/export controls from the separate AnnotationToolbar
(below the address bar) into the AnnotationPanel header. This keeps all
annotation actions and the annotation list co-located on the right side,
reducing eye/mouse travel and making the workflow more accessible.

- AnnotationPanel: adds compact icon-button toolbar with keyboard nav
- BrowserPanel: lifts annotation callbacks and export modal from controls
- BrowserControls: stripped to single annotation-mode toggle

* feat(annotation): Agentation-inspired UI polish + AFS export adapter

Spec A — UI Polish:
- BrowserControls: tooltip + aria-pressed + ring active state + motion-safe
- AnnotationPanel: slide-in, grouped tools, card hierarchy, selected ring
- Remove dead AnnotationToolbar component
- BrowserPanel: loading overlay fade-in
- use-annotation-markers: fix RAF pending reset on cleanup
- annotation-overlay: CSS class toggles for marker selection, reduced motion
- New tests: 42 (AnnotationPanel, BrowserControls, use-annotation-markers)

Spec B — AFS Export Adapter:
- exportAnnotationsToAfsJson() with per-type field mapping
- AFS tab in AnnotationExportModal alongside Markdown/JSON
- Explicit omit-list for unsupported lifecycle/thread/React/style fields
- New tests: 12 AFS mapping + 5 modal integration tests

Total: 59/59 tests pass, zero type errors

* fix(annotation): address trusted-event guards, lifecycle, and state sync issues

- annotation-overlay.js: gate draw handlers on e.isTrusted; keep marker
  styles across __termul_remove_markers, remove only on teardown
- browser_tab_manager.rs: initialize lastTitle to '' so initial title is
  always emitted
- commands.rs: redact raw page titles from debug logs
- AnnotationExportModal: handle clipboard writeText errors
- BrowserPanel: verify isVisible before restoring webview on export close
- use-annotation-capture: use capture-origin viewport from payload
- use-annotation-markers: sync prevSelectedRef in annotationsChanged branch
- annotation-export: use ?? instead of || for title/url
- annotation-store: no-op updateAnnotation when id missing; clean empty
  buckets; clear stale selection in clearAnnotationsForTab
- browser-session-store: short-circuit annotation mode/submode when unchanged

* fix: address security review findings

- browser_tab_manager.rs: probe JS overlay before using annotation_injected;
  invalidate on navigate/reload/go_back/go_forward and from report_url/loaded
- commands.rs: redact selector from element-captured debug log
- AnnotationExportModal.tsx: show error state UI when copy fails
- use-annotation-markers.ts: reset prev refs on page reload / overlay teardown
- annotation-export.ts: guard viewportWidth > 0 before normalizing coords

* feat: add close button to AppPreferences and ProjectSettings headers (#118)

* fix(mermaid): text invisible due to DOMPurify stripping style & foreignObject (#119)

DOMPurify.sanitize() removes <style> blocks and strips all content
inside <foreignObject> elements. Mermaid v11+ relies on both for
correct text and node rendering, causing invisible text in the editor.

Remove DOMPurify sanitization for Mermaid SVG since the output is
generated by the trusted mermaid library from validated diagram syntax.

* chore: bump version to 0.3.6 (#120)

* ui: add close button to AppPreferences and ProjectSettings headers

* chore: bump version to 0.3.6

* ci: trigger AUR on tag push and add macOS build to release

---------

Co-authored-by: laurensiusjonathan-ID <laurensius92@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Anaz S. Aji <aji.anaz@gmail.com>

* docs: add CHANGELOG.md with release notes for v0.3.4 and v0.3.6

* fix: shortcuts work from terminal/editor/browser focus

## Problem

App-level keyboard shortcuts did not work when focus was inside the
terminal (xterm), the code editor (CodeMirror/BlockNote), or the
browser tab. Users had to click outside to make shortcuts like
Ctrl+K, Ctrl+R, Ctrl+T, etc. respond. Pressing shortcuts in the
terminal also printed raw control sequences (e.g. ^K^K^K) instead
of triggering the app action.

## Root causes

1. **xterm helper textarea misclassified as generic input** — xterm
   captures keyboard events via a hidden <textarea>. WorkspaceLayout
   was checking  before checking ,
   so terminal focus appeared as generic text-entry focus, silently
   dropping all shortcuts.

2. **Global shortcuts blocked from editor** — A single early-return
   guard () killed every shortcut
   below it, including command palette, command history, new terminal,
   tab cycling, zoom, and project switching.

3. **Overlay focus not stolen from terminal/editor** — When an overlay
   (command palette, command history) opened while terminal or editor
   held focus, the overlay rendered but focus stayed in the background
   element; users had to click into the search input manually.

## Fix

###
- Extract  helper: checks
  **first**, then tags input/editor only if not inside xterm. Ensures
  terminal ancestry wins before generic suppression.
- Restructure shortcut handler into three explicit tiers:
  - **Always** (saveFile, closeTab): fire from any context.
  - **Global**: commandPalette, commandPaletteAlt, commandHistory,
    newProject, newTerminal, newBrowserTab, tab cycling, zoom, project
    switch — all lifted above the isInInput/isInEditor guard so they
    work from terminal, editor, and browser.
  - **Contextual** (toggleFileExplorer, sidebarToggle): intentionally
    blocked in editor/input to avoid conflicts with editor bindings.
- Blur  before opening overlays when focus is
  in a terminal or editor, so focus is released before React sets state.

###
- Add  helper using  (the
  same normalised matching used by WorkspaceLayout) instead of raw
   string equality. This keeps both routing layers
  on the same ownership contract, including macOS cmd/ctrl aliasing.
- Return  (bubble to app) for any configured app shortcut;
  return  (pass to PTY) for everything else.

###
- Add  and explicit  call when  becomes true.
- Blur  first so xterm/editor releases focus
  before the input tries to claim it.

###
- Same blur-then-focus pattern; reduce timeout to 0 ms (next microtask)
  so focus transfer happens reliably after render without a 50 ms race.

## Tests

- : new cases for xterm textarea focus routing
  (sidebar works, Ctrl+K opens palette, Ctrl+R opens history from
  terminal focus); existing input-suppression cases preserved.
- : new cases for app-owned shortcut bubbling
  (Ctrl+K, Ctrl+Shift+B return false) and non-owned key passthrough.

Fixes: shortcuts from terminal focus · shortcuts from editor focus ·
shortcuts from browser focus · overlay focus steal on open

* fix: address review findings on shortcut routing

- ConnectedTerminal: move isAppOwnedTerminalShortcut after all imports
  (was incorrectly placed mid-imports, breaking conventional order)
- ConnectedTerminal: add readline passthrough whitelist for Windows/Linux
  (ctrl+a/e/k/r/f/b/w/u/p/n/l/d always pass through to PTY regardless
  of whether a matching app shortcut exists; fixes readline bindings
  being swallowed on non-macOS platforms)
- WorkspaceLayout: replace inline browser-tab creation with
  handleNewBrowserTab() callback and add it to effect deps
- WorkspaceLayout: guard Ctrl/Cmd+1-9 project switch against Shift/Alt
  so Ctrl+Shift+1 etc. do not trigger project switching
- WorkspaceLayout.test: replace duplicate xterm-textarea test with a
  distinct scenario that clears the mock and verifies call count
- ConnectedTerminal.test: update Ctrl+K and Ctrl+R tests to assert
  passthrough (true) now that readline whitelist applies on all platforms

Skipped findings:
- LandingPage.tsx hardcoded year / i18n: file removed from this branch

---------

Co-authored-by: laurensiusjonathan-ID <laurensius92@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Anaz S. Aji <aji.anaz@gmail.com>
G
Ginanjar Noviawan committed
ca87f8afdb01e08c17022850457111dfca97873b
Parent: adb8f3b
Committed by GitHub <noreply@github.com> on 5/9/2026, 7:57:59 AM