Changelog
All notable changes to NexTerm are documented here.
Format follows Keep a Changelog; versioning follows Semantic Versioning.
1.8.32026-09-03
Fixed
- The Buy button pointed at a test-mode checkout. The Lemon Squeezy product had only ever been created in test mode, so the checkout looked real and quoted $49 but could never take a payment, and any licence key it issued would not activate a production build. The live product now exists and the app and site point at its checkout.
1.8.22026-09-02
Fixed
- Terminal output landed on the wrong rows in a persistent (tmux) session. Text was painted over lines that were never cleared —
grub over config-4.15.0-213-generic left grubig-4.15.0-213-generic — and it worsened the longer a session ran. Root cause was in the terminal emulator, not NexTerm: SwiftTerm created the *alternate* screen buffer with marginRight = 0, so the canScroll test in its linefeed handler was false for any cursor column but 0, and a linefeed at the bottom of a scrolling region was silently dropped. tmux lives on the alternate screen and parks the cursor at the last column before its scrolling linefeed, which is exactly the case that broke. Plain shells never touch either. Fixed by requiring SwiftTerm 1.20.0.
- The PTY opened at a hard-coded 220×50 and applied the real terminal size in a detached task that the startup command did not wait for — so tmux could lay its window out against the placeholder. The size is now awaited, and the PTY opens at the size the view has already reported.
terminalModeReset never reset the scrolling region or origin mode, so a stale DECSTBM could survive a reconnect. Both are reset now.
- The release script shipped the retired v1 landing page instead of the current design, and generated a download page in the old palette. Both fixed.
Changed
- nexterm.io redesigned, and extended with Terms, Privacy and Refund pages at extensionless URLs.
- Auto-updates are live: the Sparkle feed and notarized build are published at nexterm.io/updates.
1.8.12026-09-01
Fixed
- Couldn't scroll back in a persistent (tmux) session. Two causes, both fixed. tmux owns the history in a persistent session, and SwiftTerm's wheel handling only ever scrolled its own (empty) buffer without telling the application — so a trackpad scroll did nothing. NexTerm now forwards wheel/trackpad scrolling to the application whenever it has asked for mouse tracking (tmux, less, vim…), and launches tmux with
mouse on and a 50,000-line history, set before the session is created so it applies to the first pane. Scroll up to read, q to snap back.
- Plain (non-tmux) sessions keep 10,000 lines of local scrollback instead of SwiftTerm's 500.
- The real Lemon Squeezy checkout URL is wired into the app and landing page; price shown as $49.
1.8.02026-08-28
Real row editing in the database grid — and a MySQL bug it flushed out.
Added
- Add Row: a form per column with type/PK/default hints; blank fields are omitted from the INSERT so column defaults and auto-increment keys apply naturally. Runs against the live connection and refreshes the grid.
- Delete Rows: select rows → Delete → confirm. Strictly primary-key-keyed; tables without a primary key refuse row deletion (matching on every column is how you delete more than you selected) with a pointer to the SQL editor. This replaces the buttons removed in 1.1.2, whose delete flow confirmed and then did nothing.
- Row editing works on PostgreSQL, MySQL, SQLite, and DB2. MongoDB hides the buttons — its editor takes Mongo commands, not SQL. SQL generation is in a view-free builder with tests, and the whole path was verified end-to-end against live MySQL and SQLite databases (including quote-heavy values like
O'Brien's "Guide").
Fixed
- Browsing any MySQL table was broken since 1.3.0 — the grid quotes identifiers the SQL-standard way (
"schema"."table"), which stock MySQL rejects (double quotes need ANSI_QUOTES). The driver now enables ANSI_QUOTES for its session on every connect path. Found because the new row-editing tests exercised the grid's *generated* SQL, which the original driver tests had not.
1.7.02026-08-28
Session persistence for the rest of the app, and real support channels.
Added
- SFTP tabs keep their connection across tab switches — and reopen in the folder you were in, not back at
/. The connection closes when the tab actually closes. The dual-pane's second browser (view-local by design) still closes with the view, and in-flight transfers always keep their connection until the copy finishes.
- Database tabs keep their connection and loaded schema across tab switches. The tab already owned a persistent session object; the view was discarding it and rebuilding a fresh one — every switch away and back meant reconnect + full schema reload.
- Help menu: NexTerm User Guide, Community Forum, and Contact Support… — which opens a pre-addressed email with app version, macOS version, and license state pre-filled (never keys, hosts, or vault contents; it's a plain mail draft the user sends themselves). The activation window gained a "Trouble activating?" link that does the same.
- Community forum at github.com/cale324/nexterm-community (GitHub Discussions), linked from the Help menu, the landing page footer, and the guide.
- Landing-page FAQ: lost license keys point at Lemon Squeezy's self-serve order portal.
Deferred
- ⌘F search in terminal scrollback: SwiftTerm has a SearchService but doesn't expose it publicly — needs an upstream PR or a custom buffer search. Tracked, not hacked around.
1.6.02026-08-28
The AI copilot no longer requires pasting an API key when your Mac already has an Anthropic
credential.
Added
- Anthropic credential auto-detection. NexTerm now resolves a credential in order: a key pasted in Settings →
ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN from your shell (including the login shell, since Finder-launched apps don't inherit exports) → an Anthropic CLI sign-in (ant auth login). OAuth tokens use the proper Authorization: Bearer + OAuth beta header and are re-fetched per request since they're short-lived. Settings → AI shows which source is active, with a re-check button. The resolution ladder and header shapes are covered by tests.
- Detected credentials are billed to the user's own Anthropic platform account. **claude.ai consumer subscriptions have no third-party path and are deliberately not touched** — the UI says so rather than implying otherwise.
Changed
- The consent gate is unchanged and still off by default: detection never sends anything anywhere until "Allow sending context to Anthropic" is turned on.
- Default AI model updated from the retired
claude-sonnet-4-5 to claude-sonnet-5.
Fixed
- The auto-update paragraph in the User Guide had landed in the AI article instead of Settings.
1.5.02026-08-28
Onboarding: keys that actually work, and your existing hosts in one click.
Added
- Full SSH key support. ed25519 and RSA private keys, passphrase-protected or not, with automatic fallback to
~/.ssh/id_ed25519 then ~/.ssh/id_rsa when a connection doesn't name a key. Verified against a matrix of real ssh-keygen keys — including the failure messages: a missing passphrase, a wrong passphrase, a missing file, and an unsupported format each say exactly what to fix. Previously only unencrypted ed25519 keys worked — the single likeliest way for a trial to fail in its first five minutes.
- Key passphrase and key path fields in the connection forms (passphrases live in the encrypted vault). The Edit form previously had no way to set a key path at all.
- Import from ~/.ssh/config. Connection menu → Import: parses your OpenSSH config (HostName, User, Port, IdentityFile, ProxyJump — including
Key = value syntax and quoted values), skips wildcard patterns, shows a checklist with already-added hosts flagged, and creates connections in an "Imported" group with jump hosts wired up. The parser ships with tests.
- Grouped connection-type picker (Remote Access / Databases / Containers / Web & Cloud) and type-specific forms: databases ask for server + credentials + database name (no SSH-key picker), MongoDB marks credentials optional, DB2 states its Java requirement, Cloudron asks only for a host, Docker asks for nothing, SSH/SFTP get auth-aware key fields.
- Changelog page for nexterm.io, generated from this file (
scripts/make-changelog-page.py).
Changed
- Everything now points at nexterm.io: the update feed (
nexterm.io/updates/appcast.xml), the download link, and support email. Set before any copy shipped, so no migration needed.
1.4.02026-08-28
Auto-update. The last piece of launch infrastructure: customers no longer need to watch a
website for new versions.
Added
- Sparkle 2 auto-update. NexTerm checks a static release feed daily and offers one-click install-in-place with release notes. Manual check from the NexTerm menu ("Check for Updates…") or Settings → General → Updates, which also shows the current version, last check time, and a real automatic-checks toggle (the fake one was removed in 1.1.2; this one is wired to the updater). Licenses, connections, and settings are untouched by updates.
- Every update archive is EdDSA-signed at release time and verified against the public key embedded in the app — a compromised download host cannot ship a tampered update.
scripts/release.sh now signs Sparkle's nested helpers for the hardened runtime and regenerates the signed appcast.xml after stapling; docs/DISTRIBUTION.md documents the ship-an-update runbook and the signing-key backup warning.
1.3.02026-08-28
Three new database drivers. Every engine offered in the picker now actually connects — all three
drivers were verified against real servers before shipping (MySQL 8 and MongoDB 7 in containers,
SQLite against a live file: introspection, foreign keys, queries, NULL/blob handling, error paths).
Added
- MySQL / MariaDB (native Swift driver, MySQLNIO): SQL editor, schema tree with row counts and primary keys, table grid, foreign keys / ER diagram, export/import. TLS mirrors the Postgres semantics — opportunistic encryption by default, full verification with the "Require valid TLS certificate" toggle, plaintext fallback for servers without TLS. SSH tunnelling supported via a loopback forward through the existing tunnel engine.
- SQLite: point a connection at a local .sqlite/.db file with a file picker — no host, no credentials. Full introspection (PRAGMA-based, including foreign keys), queries, and DML with affected-row counts.
- MongoDB (MongoKitten): collections appear as tables with fields inferred from sampled documents; the data grid works unchanged. The editor takes Mongo commands rather than SQL —
find <collection> {JSON filter}, count <collection>, collections — and says so if you type SQL at it. SSH tunnelling supported.
- MySQL, SQLite, and MongoDB are back in the connection-type picker.
Fixed
- A long ANSI string-concatenation chain started timing out Swift's type-checker once the new driver packages' operator overloads were in scope.
1.2.02026-08-28
Product simplification ahead of launch: cut what was personal, hide what isn't real yet, and drop
risky surface that added no sales value. The pitch tightens to what the app actually does best.
Removed
- Outline notes integration. It was a personal-workflow feature wired to a self-hosted wiki via a fragile SSO workaround. Existing Outline connections migrate automatically to Website connections — the generic type covers the same use for any notes tool (Outline, Notion, Obsidian Publish…). Per-connection notes (the field on every connection) are unchanged.
- DigitalOcean lifecycle actions (power on/off, reboot, destroy). NexTerm's job is connecting, not destroying infrastructure; a destroy button in an SSH client is all liability. Droplets still list live with one-click SSH/SFTP, and each row links to the DigitalOcean console for management.
- The sidebar "Notes" group (nothing can populate it anymore).
Changed
- MySQL, SQLite, and MongoDB are no longer offered when adding a connection. They had no live driver — picking one ended in "isn't connectable yet". They return when a real driver ships (saved connections of those types still decode fine). The database story is honestly: PostgreSQL and DB2.
- Guide, README, and the landing page updated to match — no surface promises anything the app doesn't do.
1.1.22026-08-28
Pre-release UAT: ten findings, all fixed. Full audit in docs/UAT-1.1.md.
Fixed
- Reconnect after a clean
exit reattached to a dead output stream — the terminal stayed silent while showing *connected*. Clean exits now fully tear the session down so Reconnect builds a fresh one.
- Switching away from an SFTP tab stranded a live SSH connection on the server, every time. The connection now closes on view teardown (kept alive while a transfer is running so background copies finish).
- Closing a database tab never closed the PostgreSQL/DB2 connection.
- A split terminal pane's session was orphaned on tab switch.
- Multi-gigabyte local→local copies ran on the main thread and froze the UI.
- A Lemon Squeezy outage could lock out a paying customer: unexpected response bodies decoded as "invalid" instead of entering the 14-day offline grace window.
- A Mac whose activation was released remotely was told *"This license is active."* — now it says the activation was released and to re-enter the key.
- Inline rename accepted
. and ..; CSV/dump SQL identifiers didn't escape embedded quotes.
Removed
- Two hard-wired Settings toggles ("Check for updates automatically", "Send anonymous crash reports") that had no machinery behind them.
1.1.12026-08-28
Trial hardening and the missing countdown.
Added
- Trial countdown banner in the last 7 days of the trial, urgent in the last 3, with buy and enter-key actions. Silent before that. Previously the trial was only visible in Settings, so day 15 arrived without warning.
- Support for expiring license keys, so a $0 / 14-day Lemon Squeezy variant can be used as an email-capturing trial (or to grant someone an extended evaluation). Expiry is enforced locally as well as by the API, so going offline can't extend a lapsed key.
docs/DISTRIBUTION.md now documents both trial models and when to use each.
Changed
- The trial clock is no longer resettable by clearing preferences. It's stamped into both preferences and the encrypted vault, earliest wins, and both are re-synced on read. Deleting NexTerm's data still resets it — nothing is hidden outside the app's own container.
- An expired key now says *"This license key expired on <date>. Buy a license to keep using NexTerm."* instead of a generic invalid-license message.
1.1.02026-08-12
Commercial release: licensing, distribution tooling, and a cleanup pass over shipped surfaces.
Added
- Licensing (Lemon Squeezy). 14-day full-feature trial, then key activation. One key activates one Mac; *Settings → License → Deactivate* frees the seat for another machine. The key and its activation id are stored in the encrypted vault, re-validated periodically, with a 14-day offline grace window so a bad network never locks out a paying user. No store API token ships in the app.
- Settings → License tab showing status, licensed email, renewal date, and buy/restore actions.
- Activation window shown when the trial ends — blocking, but non-destructive (connections and settings are untouched) and offers Buy / Quit.
- Mac App Store receipts entitle a copy automatically, so a store build never asks for a key.
docs/DISTRIBUTION.md — release runbook for both channels, plus a specific App Store feasibility assessment.
- User Guide article: License & Activation.
Changed
- First launch no longer seeds three fake sample connections (
prod.example.com and friends). New users start with just the local "My Mac" entries.
- Connection types with no implementation (AWS, Cloudflare) are no longer offered in the type pickers. The cases remain for saved-data compatibility and planned support.
- Version bumped to 1.1.0 (build 2).
Removed
- Add Row / Delete Rows buttons in the database table view. The delete flow showed a confirmation reading "This permanently deletes the selected rows" and then did nothing — both callbacks were empty TODOs. Row selection and the row count remain.
- Unused types
ConnectionStatus, DockerStats, CloudronLoginResponse.
BETA_README.md, superseded by README.md and docs/DISTRIBUTION.md.
1.0.02026-07-08
First tracked release. NexTerm is a native macOS app combining SSH terminals, SFTP, database management, container/cloud management, and an embedded web browser in one window.
Added — Terminals
- Full SSH terminals via SwiftTerm, with horizontal/vertical split panes, themes, and detachable windows.
- Real local shell ("My Mac") with no SSH round-trip.
- Session persistence: automatic in-place reconnect after sleep, network changes, or idle timeouts — same tab, scrollback preserved, no re-authentication.
- Network-aware reconnect: retries wait for a usable network path instead of burning attempts while offline; restoring a network or waking the Mac revives every dead session automatically.
- 60-second keepalive heartbeat that keeps NAT/firewall mappings alive and detects half-open connections.
- Persistent sessions (tmux): opt-in per connection — the shell runs inside
tmux new -A -s nexterm, so reconnects re-attach and running processes survive a closed laptop.
- SSH port forwarding / local tunnels, with rules saved per connection.
- Jump host (bastion / ProxyJump) support for both SSH and SFTP.
- Broadcast input across split terminal panes.
- AI copilot (opt-in): plain English → shell command.
Added — Files (SFTP)
- Dual-pane SFTP browser with drag-and-drop between panes and to/from Finder.
- Recursive folder copy — Mac → server, server → Mac, and local → local, with per-file progress.
- Pipelined transfers: many concurrent SFTP chunk requests instead of one-at-a-time, several times faster on non-LAN links.
- Configurable, sortable columns (Name, Date Modified, Size, Kind, Permissions) with per-user column selection persisted; defaults to newest-first.
- Inline Finder-style rename (select name, edit in place, Return to commit, Escape to cancel).
- Remote file editor with syntax highlighting, line numbers, and code completion.
- Native previews for images, PDFs, video/audio, and documents; large files gated behind an explicit download.
- Transfer progress bar with filename, phase, bytes, and percent.
Added — Databases
- PostgreSQL support (PostgresNIO) with SQL editor, schema tree, and table data grid.
- DB2 support via an auto-provisioned JDBC helper process.
- SSH-tunnelled database connections — the Citadel direct-TCP channel is handed straight to PostgresNIO, so no local port forwarder is needed.
- Table structure editing (add/rename/retype/drop columns) with live DDL and destructive-action confirmation.
- ER diagram view with draggable nodes and foreign-key relationship lines.
- Export/import, saved queries, query history, and optional natural-language → SQL.
Added — Containers & cloud
- Docker and Cloudron workspaces; Cloudron authenticates with an API token (bypasses MFA).
- Shell into a Cloudron app's container (
docker exec over host SSH) and browse its data directory over SFTP.
- DigitalOcean droplet management: list, power on/off/reboot, destroy with typed-name confirmation, and one-click SSH/SFTP into a droplet.
Added — App
- Command palette (⌘K).
- Workspaces — save the current set of tabs and reopen them together.
- Snippets library.
- Website connections: embed dashboards and web tools as native tabs, each with an isolated data store.
- Sidebar with nested groups, drag-to-regroup, favorites, and per-connection custom icons.
- Built-in User Guide with contextual "?" buttons beside individual features.
- Signed + notarized DMG release pipeline (
scripts/release.sh, scripts/make-dmg.sh).
Security
- Local encrypted vault for all secrets; on Secure Enclave Macs the data key is hardware-wrapped (ECIES → HKDF → AES-GCM), with automatic migration from the legacy plaintext key file.
- SSH host-key TOFU pinning replaces accept-anything, with a fingerprint approval sheet on first use or key change.
- Touch ID gate before connecting, with a short reuse window.
- Backup key derivation moved from HKDF to PBKDF2-HMAC-SHA256 at 600k iterations (versioned envelope; legacy backups still decode).
- Remote file previews written 0600 inside a 0700 cache directory and swept at launch.
- AI features gated behind an explicit consent toggle *and* an API key; command/output context is redacted before being sent.
- Cloudron TLS validation on by default (self-signed certs are an explicit per-connection opt-in); PostgreSQL direct connections use opportunistic TLS or full verification.
- Shell-command injection hardening for container exec; identifier/DDL allowlisting in table structure editing.
- Per-website isolated
WKWebsiteDataStore.
Fixed
- Tab switches no longer reset SSH connections. Live sessions are keyed by tab id and reused, and SwiftTerm views are cached per session so the terminal buffer and scrollback survive the view being torn down and rebuilt.
- Redundant
SIGWINCH suppression — a tab switch that re-reports the same terminal size no longer makes full-screen TUIs clear and repaint.
- Forced repaint after a terminal view remounts, so tmux/vim/htop redraw immediately instead of showing a blank pane.
- Terminal mode reset on reconnect — a dead session's TUI could leave mouse tracking enabled, causing mouse movement to type coordinate escape sequences at the new prompt.
- PTY size race that left remote terminals stuck at the initial size, causing wrapping errors in full-screen apps.
- Right-clicking a connection inside a sidebar folder showed the folder's menu instead of the connection's (Connect/Edit/Duplicate/Delete). Folder actions moved to an explicit "⋯" button.
- Database password could not be edited when a connection's auth method was stale; database connections now always show the password field.
- SFTP list no longer jumps scroll position when selecting a file (replaced
List with ScrollView + LazyVStack).
- File sizes were truncated by integer division ("1 GB" for a 1.49 GB file) and the transfer bar used binary units while the list used another. Both now use Finder-identical decimal formatting.
- Window no longer opens clipped when macOS restores a stale frame smaller than the content's minimum.
- Release builds crashed at launch with "No Observable object of type AppState found" — caused by
-disable-reflection-metadata, which @Observable + @Environment lookups require.
- Split-pane beachball caused by
HSplitView/VSplitView negotiating layout forever with SwiftTerm's intrinsic content size.
- Transfer progress bar flicker and backwards movement (now monotonic and throttled).
- Quick Look preview crashes replaced with native image/PDF renderers.
- Embedded Outline SSO: OAuth callbacks were being hijacked by the installed web app via Universal Links; callbacks are now re-issued programmatically.
- SSH sessions are properly torn down on tab close/detach so no connections dangle.
---
Development history
Pre-1.0 milestones, for context:
- 2026-06-02 — First working app: SSH terminal, SFTP browser, database workspace, Docker/Cloudron. Real PostgreSQL connectivity with SSH tunnelling; DB2 via JDBC helper. Split-pane hang and session lifecycle fixed.
- 2026-06-03 — Sidebar redesign (nested groups, drag-to-regroup, group boxes), home page as a connection browser, SFTP rich previews, remote file editor + code completion, embedded Outline SSO solved.
- 2026-06-16 — Security phases 1–3 (host-key pinning, Secure Enclave vault, injection hardening, AI consent). Cloud management v1 (DigitalOcean). Renamed Vertex → NexTerm, preserving existing data. Ten ranked feature additions shipped (command palette, tunnels, jump host, AI copilot, broadcast, workspaces, key manager, DB export/import, NL→SQL, transfer progress).
- 2026-06-19 — SFTP multi-column file list, sidebar folder context-menu fix, SSH session reuse across tab switches.
- 2026-06-24 — SFTP inline rename.
- 2026-07-01 — Full SSH session-persistence stack: in-place auto-reconnect, keepalive, sleep/wake probe, tmux persistent sessions.
- 2026-07-02 — Terminal mode reset on reconnect, network-aware reconnect, recursive SFTP folder copy.
- 2026-07-08 — Window sizing guard, Finder-accurate file sizes.