All notable changes to SystemTrayTerminal are documented here.
- Bell Notification — Plays a sound (Purr) and flashes the tray icon 3× when the terminal rings BEL (ASCII 7) and the window is not in focus. Toggle in Settings (default on) with a TEST button. Also accessible via quickBar command. Fully localized in all 10 languages.
- Inactivity Alert — Automatically triggers a bell notification after N seconds of terminal silence following user input. Useful for detecting when Claude CLI or other tools are waiting for a response. Delay configurable: 5s / 8s (default) / 15s / 30s.
- Docked window position not persisted — Window position is now saved to
UserDefaultsasdockedWindowX/dockedWindowYduring drag and restored on next launch. Position is not overwritten by the tray-centered fallback during show/hide cycles. Reset button clears the saved position. - Window position reset on hide/show — Debounce timer cancelled at the start of
showWindowAnimated(), preventing a race where the tray-centered position could overwrite the user's saved position. - Restore without screen clamp — Saved docked position is restored without forcing the window fully on-screen, allowing intentional partial off-screen positioning.
- Arrow offset after edge snap — Left/right edge double-click snaps now use
pad = 24, keeping the tray arrow correctly centered over the tray icon.
- Text Editor Tab — Open a full text editor tab alongside terminal tabs. Long-press
+→ "Text Editor" or press⌘E. Supports open (⌘O), save (⌘S), and save-as (⌘⇧S) with native sheet panels. - Syntax Highlighting — Live token coloring auto-detected from file extension: JSON, HTML/HTM, CSS, JavaScript/TypeScript (JS/MJS/CJS/TS/TSX/JSX), XML, Markdown, Shell, Python, YAML, TOML, Swift, SQL, INI/Dockerfile. Regex-based engine, debounced at 150ms. Colors adapt to dark/light theme automatically.
- Live Preview — Split-pane live preview for HTML, SVG, Markdown, and CSV files. Preview updates as you type with no manual refresh. Powered by WebKit. Toggle from the editor header.
- File Drop on Tab Header — Drag any text file from Finder onto the tab bar to open it in a new editor tab with syntax highlighting applied automatically.
- Editor Modes — Three input modes selectable via footer buttons:
NORMAL(plain NSTextView),NANO(Ctrl+S/X/K/Ushortcuts with shortcut strip),VIM(modal hjkl/insert/dd/yy/p/:/wq with mode indicator). - Vim Mode — Minimal modal editing:
hjkl+ arrow key navigation,i/a/oinsert,dddelete line,yyyank,ppaste,0/$line start/end,:w/:q/:wqfile operations. Status bar shows── NORMAL ──/── INSERT ──. - Nano Mode — Shortcut bar with
^S Save ^X Close ^K Cut Line ^U Paste. Keys intercepted at window level. - Unsaved Changes Indicator — Tab name gets a
•dot prefix whenever the editor content has unsaved changes. Dot disappears after saving. - Custom Dark Alert Modal — Replaces the system
NSAlertwhen closing a tab with unsaved content. Renders in the same dark style as quickBAR: dim overlay,⚠️ warning icon, three buttons (Save / Discard / Cancel) with hover color, press state, and pointing-hand cursor. Localized in all 10 languages. - SF Symbol Header Buttons — Open, Save, and Save As editor buttons use SF Symbols (
folder,square.and.arrow.down,square.and.arrow.down.on.square) — fully language-independent. - Custom Line Number Gutter —
LineGutterView(pureNSView, 44 px wide, noNSRulerView). Line numbers drawn viaNSLayoutManager, right-aligned with 8 px padding at 10 pt monospaced system font. Synced to editor scroll viaNSView.boundsDidChangeNotification. Colors adapt automatically to dark/light theme. - Print Modal — Printer button (SF Symbol
printer) in the footer bar. Tap to open a dark modal panel with context-aware print options: terminal tab → styled HTML; Markdown / HTML / SVG / CSV editor tabs → formatted preview + source-code options; all other editors → source-code only. HTML is built lazily (only when the user confirms). Prints via native macOS dialog (NSPrintOperationfor source,WKWebView.printOperationfor rendered HTML). - Search & Replace —
⌘Ffind with match highlighting,⌘Hfind & replace panel. - Window Size Persistence — Last window dimensions saved on every resize and restored on next launch. Saved size is clamped to the visible screen area on startup.
- Window Always On Screen —
clampFrameToScreen()ensures the window never extends outside the visible screen area, at launch and when restoring detached position. - Edge Double-Click Expand (docked-aware) — Double-click any window edge to maximize in that direction. Docked: left expands left (right edge stays over tray icon), right expands right, bottom expands down. Detached: snaps to the respective screen half. Full-screen from docked mode is explicitly blocked.
- Session Persistence — Editor tabs (open file URL and editor mode) saved and restored across restarts.
- Theme Sync — Editor background and text color automatically follow the active color theme.
- 10-Language Localization — All editor and modal strings fully translated: EN, DE, TR, ES, FR, IT, AR, JA, ZH, RU.
- Window opens at screen center when docked — Startup height clamping now uses
screenVis.height - 80instead of× 0.95. Bad saved heights are corrected and persisted back toUserDefaultson first launch. positionWindowUnderTrayIconsilent no-op on oversized window —guard fallbackY > 0 else { return }replaced: window is now resized to fit and positioned correctly instead of doing nothing.- Detached window could restore off-screen —
restoreDetachedWindowStatenow passes the restored frame throughclampFrameToScreenbefore applying it. - Window positioning flash on launch — Docked window waits 400 ms (increased from 200 ms) for status-bar item coordinates to stabilize before calling
showWindowAnimated(). - Multiple editor tabs background darkening — Each new editor tab no longer composites on top of the previous one; views are properly hidden before the new one is shown.
- File panels appearing behind window —
NSOpenPanel/NSSavePanelnow usebeginSheetModal(for:), attaching them as sheets to the window. - Version button not clickable — Tab content views are re-added below the version button in z-order after each tab creation.
- Text cursor over editor header buttons —
SymbolHoverButton/AlertButtonoverridecursorUpdateto setNSCursor.pointingHand; tracking areas use.cursorUpdate+.activeAlways. - Hover states on alert buttons not firing — Replaced transparent hit-area overlay (unreliable without a layer) with
AlertButtonthat IS the background,wantsLayer = truefrom init,.activeAlwaystracking. +→ Terminal opened editor — Removed incorrect branch inaddTab()that calledcreateEditorTab()when the active tab was an editor.- Vim cursor invisible in normal mode —
isEditablestaystruein all Vim sub-modes; key blocking handled entirely byBorderlessWindow.sendEvent. - Vim normal mode typing — All unrecognized keyDown events in normal mode consumed by
sendEventbefore reaching NSTextView.
- SSH Manager — Floating sidebar for SSH profile management. Save connections (label, user@host, port, identity file), connect via new tab with one click, delete profiles. Profiles stored in UserDefaults as JSON.
SSHProfile.connectCommandbuilds the correctsshinvocation automatically. - Keyboard Shortcuts: Tab Navigation —
Ctrl+1–9to switch directly to any tab.Ctrl+Shift+1–9to trigger inline rename for that tab. - Keyboard Shortcuts: Window Presets —
Ctrl+⌥+1(compact 620×340),Ctrl+⌥+2(medium 860×480),Ctrl+⌥+3(large 1200×680) — animated spring resize. - Color Themes — 4 terminal color schemes in Settings: Dark (default), Light, OLED Black, System (auto-follows macOS Dark/Light Mode). System theme observes
AppleInterfaceThemeChangedNotificationfor live switching. - Follow All Spaces — New setting: window appears on all macOS Spaces simultaneously. Toggle in Settings or via tray right-click menu.
- Tray Detach / Reattach — Right-click tray icon → "Detach Window" floats the terminal freely on the desktop. Detached window is fully resizable from all 8 edges/corners. "Reattach Window" snaps it back under the tray icon. State survives hide/show cycles.
- Terminal Right-Click Context Menu — Right-click: Copy, Paste, Select All (respects mouse-tracking mode; falls through to app when tracking is active).
- Sidebar Right-Click — Right-click on any header panel button (Git, WebPicker, SSH) to toggle that panel without opening quickBAR.
- Full 10-Language Localization Update — All new UI strings (
showHide,detachWindow,reattachWindow,quitApp) added to all 10 language dictionaries: EN, DE, TR, ES, FR, IT, AR, JA, ZH, RU.
- Updater: SHA256 integrity check — Downloads a
.sha256sidecar from GitHub Releases and verifies the ZIP before installation. Absent sidecar falls back gracefully without blocking the update. - Updater: HTTPS + host allowlist — Both download and checksum URLs are enforced to use
https://and restricted togithub.com/objects.githubusercontent.com. Redirects to any other host are rejected. - Updater: Bundle ID verification — Extracted
.appmust match the current app'sCFBundleIdentifierbefore installation proceeds.
- Header gap when detached — Floating window no longer shows a 4–5 px empty strip at the top. Arrow view is hidden and
headerView.framerepositions flush to the window top edge. - Terminal area when detached —
termFrame()now useseffectiveArrowH = 0when detached, so the terminal expands to fill the recovered space. - Sidebar drag moved window —
isMovableByWindowBackgroundremoved entirely; drag-to-move is now handled exclusively inHeaderBarView.mouseDraggedand only activates whenisWindowDetached == true. - Diagonal resize when detached —
BorderlessWindow.edgeAt()now exposes top-left and top-right corner resize zones whenisDetached == true. - First-click on sidebar divider —
GitPanelDividerView.acceptsFirstMousereturnstrue, fixing the two-click interaction when the window is not yet frontmost. - Window position saved while detached —
windowDidMove/windowDidResizeguard againstisWindowDetachedto prevent the desktop position from overwriting the tray-snap coordinates. - Reattach position —
toggleDetach()clearswindowX/windowYfrom UserDefaults before reattaching, sopositionWindowUnderTrayIcon()always recalculates from the current tray icon position. - Detach state not preserved on hide/show —
toggleWindow()now preserves the detached state; showing a hidden detached window no longer auto-reattaches it. - Updater: parse error vs. "up to date" — HTTP errors, missing data, and JSON parse failures now return
.failure(error)instead of silently reporting no update available. - Updater: background-thread install —
installUpdateruns onDispatchQueue.global(qos: .utility), eliminating UI freeze during extraction and file operations. - Updater: relaunch exit guarded by open exit code —
exit(0)is only called when/usr/bin/openreturns exit code 0. A failed relaunch no longer terminates the running process. - Updater: backup preserved until relaunch confirmed — Old
.appbackup is deleted only afteropensucceeds, retaining rollback capability on relaunch failure. - Auto-Check Updates: toggle reschedules timer — Enabling/disabling in Settings now immediately schedules or cancels the repeating timer.
- Startup window fade —
showWindowAnimated()is now used on first launch, ensuring consistent fade-in and correcthideOnClickOutsidemonitor setup.
- WebPicker — Chrome DevTools Protocol (CDP) based DOM element picker. Connects to Chrome via WebSocket, lets you hover-select any element on any webpage, copies
outerHTMLto clipboard and auto-pastes into terminal. Floating sidebar with Connect/Disconnect toggle, live hostname display, and element preview. - Onboarding Video — First-launch intro panel (480×300, centered). Plays
SystemTrayTerminal.mp4once using AVKit, auto-closes when done, has "✕ Skip" button. Never shown again after first view (UserDefaults flag). - Full English UI — All UI strings translated to English: Git panel, WebPicker, GitHub auth, feedback toasts, error messages, Claude API strings.
- Demo GIF —
SystemTrayTerminal.gifadded to README (MP4 → GIF, 700px, 2.4 MB, auto-plays on GitHub).
- WebSocket silent death —
ChromeCDPClient.receiveLoopnow firesonDisconnectedcallback on.failure, preventing the UI from getting stuck in "connected" state when Chrome crashes or the tab is killed externally. - pollTimer not reset on reconnect —
connect()now invalidatespollTimerbefore starting a new session, preventing duplicate polling timers when the user reconnects without disconnecting first. - Tab closed externally —
refreshTabTitlereturningnil(tab not found in Chrome's/json/list) now triggers a fullhandleUnexpectedDisconnect(message: "Tab was closed")instead of incorrectly showing "Navigating" state. - Stale onDisconnected closure —
disconnect()now setscdp.onDisconnected = nilbefore closing, preventing a stale closure race where a final WebSocket.failureafter manual disconnect could re-trigger disconnect logic. - Teardown duplication — Disconnect/cleanup logic was duplicated in 3 places. Extracted into
handleUnexpectedDisconnect(message:)with aguard isConnectedgate to prevent double-firing. - titlePollTimer churn —
startTitlePolling()was being recreated on every HTTP callback. Extracted into a dedicated method called once after connect, preventing timer accumulation. - targetId extraction — Replaced hand-rolled
wsURL.components(separatedBy: "/").lastwithURL(string: wsURL)?.lastPathComponentfor correct and reliable target ID extraction.
HTMLPickerSidebarView→WebPickerSidebarViewhtmlPickerSidebarView→webPickerSidebarView(AppDelegate)onHTMLPickerToggle→onWebPickerToggle(HeaderBarView)setHTMLPickerActive→setWebPickerActivehtmlPickerRightDivider→webPickerRightDividertoggleHTMLPicker→toggleWebPickerhtmlPickerBrowser→webPickerBrowser(UserDefaults key)htmlBtn→webPickerBtn(HeaderBarView)- Removed dead
HTMLPickerPanelclass (282 lines) - Removed all
print("[CDP]...")debug statements
- Auto-updater — Clickable toast notification for update install.
.appguard prevents update on non-bundled binary.
- Git Panel — 7 new features: branch display, changed files, diff viewer, staged changes, commit history, GitHub API CI status, panel position toggle (right/bottom).
- Claude Code Usage Badge — Live session & weekly limits in footer. Auto-connects via local credentials, color-coded, click for detail popover.
- Drag & Drop — Drop files/images from Finder → shell-escaped path inserted at cursor.
- Custom Tab Names — Double-click tab to rename; persists across sessions.
- Session restore — tabs, shells, splits, working directories.
- quickBAR — 40-command Spotlight-style palette.
- Multi-tab with color coding and drag-to-reorder.
- Split panes — vertical and horizontal with draggable divider.
- Auto-updater — GitHub Releases check every 72h.
- VT100/VT220/xterm terminal emulator from scratch (13-state FSM parser).
- 60 FPS CGContext rendering. 24-bit TrueColor. Sixel graphics.
- Menu bar app, global hotkey Ctrl+<.
- Single Swift file, zero dependencies, 4.8 MB app bundle.