Skip to content

Harbor 1.0.0

Latest

Choose a tag to compare

@ftchvs ftchvs released this 10 Jul 06:28
· 4 commits to main since this release

Changelog

All notable changes to Harbor are documented here.

The format is based on Keep a Changelog, and Harbor aims to adhere to Semantic Versioning.

Unreleased

1.0.0 - 2026-07-09

Added

  • App icon and logo: a mooring bollard with a hemp rope — a harbor holds vessels safely in place, the way Harbor holds your dev servers in view — with sea-glass signal arcs for a live, listening port. Vector masters in Design/ (full icon, small-size simplified variant for 16–64 px, light/dark logo lockups), a complete macOS AppIcon.appiconset wired via ASSETCATALOG_COMPILER_APPICON_NAME, and a standalone Design/Harbor.icns.
  • Well-known-service resolver (HarborCore): identifies the port of a "Show all" listener the framework detector can't name — a curated table of macOS-specific ports (SSH, DNS, HTTP/HTTPS, SMB, AFP, Apple Remote Desktop, Bonjour/mDNS, Screen Sharing/VNC 5900–5910, AirPlay Receiver, iTunes/Device Sync) checked first, then a getservbyport(3)//etc/services fallback restricted to ports below 1024 (registered-range entries like :3000 → "hbci" collide with common dev ports and would be actively misleading). Surfaced next to the port for framework-less rows (e.g. "AirPlay Receiver :7000") and, when resolved, as a "Service" field in the detail inspector.
  • Multi-select batch kill (AND-1079): ⌘-click toggles rows in and out of a selection, Shift-click extends a contiguous range from the anchor, and a plain click collapses back to a single selection. With 2+ rows selected a "Kill N Selected" control appears in the footer; it freezes the chosen entries, shows the same enumerated "Terminate N processes?" confirmation as Kill All (gated by the same confirm preference), excludes Harbor's own process, terminates unique PIDs through the shared aggregator, and names any survivors on partial failure. Esc cancels a pending batch kill without closing the panel; arrow-key navigation returns to single-selection, and filter/exposure/refresh changes prune vanished rows from the selection.
  • Vitals sparklines (AND-1069): the detail inspector now draws CPU and memory trend sparklines for the expanded row, backed by a fixed-capacity 60-sample rolling history retained per (pid, start time) in the refresh coordinator. History accumulates across scans for a stable listener, starts a fresh series when a process restarts (its start time changes), and is dropped once the listener disappears; it is in-memory only and never survives relaunch. The Canvas-drawn sparkline scales to the retained window and renders safely for empty (muted baseline) and single-sample (flat line) series.
  • Per-port detail inspector (AND-1068): a disclosure chevron — or Space on the selected row — expands an inline, read-only accordion beneath any row, surfacing the full command line, working directory, owner app (+ bundle id), tunnel provider/endpoint, Docker/k8s origin, bind scope, address families, health, PID, and uptime. Full argv and the working directory are read lazily on a row's first expansion (never during the refresh scan); every field hides gracefully when unavailable, arrow-key selection keeps working with a panel open, and each field carries a VoiceOver label with the expansion state announced.
  • Project kickoff (2026-07-03): initial vision, roadmap, design, and engineering documentation; root scaffolding (README, CLAUDE.md, AGENTS.md, CONTRIBUTING.md, ARCHITECTURE.md, .gitignore).
  • Defined the six-milestone plan — M0 Foundation · Prove the Hard Parts, M1 Alpha · See Every Port, M2 Beta · Act on Ports, M3 Polish · Native Feel, M4 Release · Notarize & Ship, M5 Differentiate · Post-1.0 (backlog) — and the eleven epics (E1–E11), mapped 1:1 to Linear.
  • Established the five-module architecture: HarborApp, HarborCore, HarborServices, HarborUI, HarborDesignSystem.
  • Implemented the M0-M2 local beta lane: native listening-port enumeration with lsof fallback, process vitals and framework detection, menu-bar panel shell, row actions, browser open, kill/Kill All, terminal activation baseline, manual QA matrix, and green verify/smoke/release-preflight scripts.
  • Added release preflight packaging checks for a universal ad-hoc Release app, DMG creation, and SHA256 checksum generation.
  • Added a Settings surface with persisted refresh cadence and general preference controls.
  • Added production-readiness gates for product source cleanliness, local Sparkle update/install proof, Release-like GUI smoke, RC sign-off evidence, manual QA evidence validation, release-build guardrails, static-site validation, and public release-asset deploy guarding.
  • Kill All safety: Harbor's own process is excluded from every kill path, the confirmation lists the exact targets (name, port, PID), partial failures name the surviving processes, and permission errors are explained instead of showing a raw errno.
  • Rebindable global hotkey: a recorder in Settings › Shortcut captures a custom summon shortcut (modifier required, Esc cancels, inline conflict warning against Harbor's panel shortcuts) with Reset to Default and live re-registration; replaces the fixed three-preset picker.
  • "Pause refresh when panel is closed" toggle in Settings › Refresh (default on); turning it off keeps the scan stream live so reopening shows current data instantly.
  • Footer shows a live "Updated Ns ago" timestamp; removed the unreachable duplicate arrow-key handler; hotkey controller teardown uses isolated deinit.
  • New HarborAppSupport module extracts the composition root's pure logic (Sparkle gating, badge title, pause-refresh semantics, launch-at-login decisions) so it is unit-testable — the architecture is now six modules.
  • Copy suite: row context-menu actions Copy URL (⌘C), Copy PID, Copy Port, Copy Command, and Copy as curl, with IPv6-safe URL forms (2026-07-07, M5).
  • harbor:// URL scheme: show, open?port=N (opens the browser), and kill?port=N (opens Harbor's kill confirmation — never kills headlessly).
  • harbor-cli companion executable: list [--json] (works with the app closed, stable JSON output) and kill <port|pid> (prompts unless --force).
  • harbor-cli v2: watch [--json] [--interval s] streams added/removed/changed listener events until Ctrl-C (exit 0); a versioned JSON schema ({ schemaVersion, ports } envelope for list --json, one { schemaVersion, kind, port, previous? } event per line for watch --json) documented in docs/06-engineering/harbor-cli-json-schema.md; an open <port|pid> verb that opens the listener's browser URL; and committed zsh/bash/fish completions under Sources/harbor-cli/completions/.
  • Project/workspace grouping: listeners cluster under collapsible project headers derived from each process's working directory (nearest .git/package.json/Cargo.toml/… ancestor); Settings toggle, default off.
  • Notifications (both default off): "new listener appears" (exactly once per new listener, silent on launch) and "server stays high-energy" (fires only after a sustained window, kill cancels it), with 30-second coalescing and actions that open Harbor or its confirm UI.
  • Additional framework detectors — Astro, Remix, FastAPI, Phoenix, Spring Boot, Laravel — plus per-framework quick actions in the row context menu (FastAPI /docs, Django /admin, Rails /rails/info/routes, Phoenix /dev/dashboard, Spring Boot /actuator/health).
  • Docker and Kubernetes port-forward awareness: docker-proxy/Docker.app listeners show container origin (via docker ps when available), kubectl port-forward listeners show their target resource; degrades gracefully without the CLIs.
  • Tunnel awareness: ngrok (public endpoint from the local agent API), cloudflared, and ssh -R/-L tunnels are labeled with their public endpoint or target.
  • Pins, favorites & custom names: ⌘P pins rows to a stable top section (persisted), Rename… sets a custom primary label that survives restarts, and a pinned-but-stopped port shows a dimmed "Not running" placeholder.
  • Port history & quick relaunch: a "Recently stopped" section lists the last 10 killed servers with their exact command; Relaunch confirms the command and cwd before respawning, refuses occupied ports, and can optionally persist across launches.
  • Network-exposure alerts: an amber "Exposed" chip on listeners bound beyond loopback, a click-to-filter "N exposed" header count, and an optional (default-off) notification for newly exposed listeners.
  • HTTP health peek (opt-in, default off): a per-row liveness dot — green 2xx/3xx, amber 4xx/5xx, gray timeout/non-HTTP — from bounded 1-second HEAD / probes that never run while the panel is closed and refresh is paused.
  • Credentialed-release runbook (docs/06-engineering/release-runbook.md): exact one-time credential setup and per-release command sequence from green readiness lane to notarized, published 1.0.
  • Panel popover auto-resize (PR #15): the popover sizes itself to the visible port count, grows up to a maximum height and then scrolls its content internally, and live-adjusts while the panel stays open as listeners come and go.
  • Owner-app attribution: each row is labeled with the GUI application that launched the listener (iTerm2, Warp, VS Code, …) by walking process ancestry to the nearest .app bundle, with an "Unknown" fallback and VoiceOver support.
  • "Show all" toggle in the panel header (persisted, default on): switching it off curates the list to dev-relevant listeners — known framework, project, exposed, tunneled, container-forwarded, or pinned — hiding background system noise.
  • Settings › General mirrors the "Show all listeners" toggle, bound to the same preference as the panel header so either surface updates the list live and the choice persists across relaunch.
  • Menu-bar right-click quick menu: right-click (or Control-click) the status item to act without opening the panel — up to 8 listening ports each with a confirmed Kill (an alert names process, port, and PID), Kill All (enumerated confirmation gated by the confirmKillAll preference), Settings…, and Quit Harbor; left-click still toggles the panel. Harbor's own process is excluded, and the menu reuses the existing snapshot path without starting a second scan (AND-1078).
  • First-run onboarding & permission priming (AND-1074): a one-time welcome tour explains what Harbor does, primes notifications with an in-app rationale before any OS request (accepting routes through the shared notification seam, declining stays silent), frames Accessibility as optional with a direct link to its System Settings pane, and offers launch-at-login with its error path handled without blocking completion. Skipping or closing at any step completes it, a persisted flag stops it reappearing, and Settings › General adds "Show onboarding again".
  • Adaptive refresh cadence: the poll interval backs off step-wise (up to base × 4) while the listening-port set is stable and snaps to a short fast window — decaying back to base — after a panel-open or a user action (kill, Kill All, relaunch). The Settings interval stays the base cadence and still applies without restart; manual and pause-when-closed modes are unchanged. See docs/06-engineering/adaptive-refresh.md for why polling stays (no unprivileged system-wide socket-listen event exists on macOS).
  • Extended jump-to-terminal beyond Terminal.app/iTerm2 to Warp, Ghostty, WezTerm, kitty, Alacritty, and the VS Code/Cursor integrated terminals — recognized by bundle identifier (name/path fallback) by reusing the owner-app ancestry walk; a server owned by an unrecognized .app now surfaces a graceful "isn't a terminal Harbor can focus" error naming the app instead of a generic unresolved error.
  • Opt-in UDP & non-listening socket view (AND-1073): a Settings › Refresh › Advanced toggle "Also show UDP and non-listening sockets" (default off) surfaces UDP bound sockets and stuck non-LISTEN TCP sockets (CLOSE_WAIT, FIN_WAIT) in a collapsed, de-emphasized section for "the port is taken but nothing's listening" debugging; each row shows a UDP/state badge, open-in-browser is suppressed for UDP, and Kill remains. Enumeration options thread through the scan seam (native libproc reads SOCKINFO_IN/non-LISTEN states; the lsof fallback widens to -i with state parsing), so with the toggle off the port list is byte-for-byte the historical TCP-LISTEN-only set.

Fixed

  • Panel self-sizing layout livelock (AND-1114): the port list and detail inspector no longer use lazy stacks inside the self-sizing panel, ending a layout feedback loop that could pin a CPU core while the panel was open.
  • "Show all off" curation now filters real noise: ProjectRootLocator requires a recognized project marker (.git, package.json, Cargo.toml, …) before treating a working directory as a project root, instead of falling back to the raw cwd. A listener whose cwd has no marker no longer counts as dev-relevant, so it is hidden when "Show all" is off; project grouping keeps such rows in the "Other" section (AND-1081).
  • Hardened destructive action safety by freezing Kill All targets at confirmation time and revalidating process identity before termination escalation.
  • Kill path now fails closed: when a process's identity cannot be verified (no sampled start time and no fresh read), Harbor refuses to signal the PID instead of risking a reused-PID kill.
  • Port scanning no longer reports "no servers" when both enumerators are broken — fallback enumeration failures are surfaced instead of silently swallowed.
  • Memory column reads ri_phys_footprint (Activity Monitor-comparable) instead of raw resident size.
  • Framework detection uses token-boundary matching, so arguments like server-expression-parser.js no longer misdetect as Express (same for Django/Flask substrings).
  • Command-line argument parsing tolerates non-UTF-8 bytes per argument and bounds argc against the buffer size.
  • Permission/error panel states now suppress the accessibility onboarding banner (contradictory copy); Esc dismisses the panel while the filter field has focus; Kill All via ⌘⇧K respects the "Confirm Kill All" preference like the footer button.
  • Readiness and run scripts only terminate Harbor instances built from this repository, never an installed Harbor.app (HARBOR_KILL_EXISTING=1 overrides).

Changed

  • "Show all" rows without a detected framework no longer show a noisy "Unknown · " title prefix — the process/owner name (e.g. "ControlCenter", "IPNExtension") is the title on its own; dev-framework rows (Vite, Next.js, …) are unchanged.
  • Vitals sparklines redesigned as discrete Canvas-drawn bars (one per retained sample, trailing-aligned so the newest sample is always rightmost) instead of a smooth interpolated area chart — the old chart misled at a glance: it stretched idle noise to full height (a 0% CPU process looked like dramatic hills), min-anchored memory so a stable value appeared to swing, and implied continuity between discrete 5-second samples it didn't have. Bars are now always zero-anchored; CPU scales against a 20% floor (so idle noise stays calm) but lets the observed max win above it since CPU can exceed 100% on multi-core work, and memory has no floor beyond zero-anchoring but gets ~10% headroom above its observed max so bars don't kiss the top. A zero/near-zero sample still draws a small visible baseline stub rather than vanishing. The scaling/layout math is factored into a pure, unit-tested VitalsSparklineGeometry helper; the detail inspector's CPU/memory rows now expose VoiceOver value text with a coarse trend word (rising/falling/steady) alongside the current reading.
  • Owner-app attribution is now memoized per (pid, process start time): the ancestry walk and Info.plist disk read run once per process instead of once per listener per refresh. A cached "Unknown" is served without re-walking, pid reuse (changed start time) re-resolves, and the store is LRU-bounded so it cannot grow unbounded (AND-1070).
  • All GitHub Actions workflows are temporarily workflow_dispatch-only (no Actions credits); the equivalent gates run locally via Scripts/verify.sh, Scripts/smoke.sh, Scripts/readiness.sh, and Scripts/product-readiness.sh.
  • The Site workflow additionally triggers on release: published, so publishing a GitHub release redeploys the landing page with fresh download links (no credit cost until a release exists).
  • Settings window opens via SwiftUI's openSettings environment action instead of the showSettingsWindow: responder-chain selector, which raced the accessory activation policy.
  • Docs: the roadmap now records the M5 Differentiate backlog as shipped (2026-07-07, PRs #9–13) and adds the post-1.0 milestones M6 Insight · Deep Visibility (v1.1), M7 Reach · Breadth & Onboarding (v1.2), and M8 Worldly · Localization & Power Use (v1.3) with epics E12–E14, mirrored across CLAUDE.md/AGENTS.md and the MoSCoW prioritization doc.

Notes

  • Harbor is not public-production-ready until the remaining external and human gates are complete: Developer ID signing, Apple notarization/stapling, production-hosted Sparkle update proof, live Pages deployment, clean-machine GUI/accessibility QA, real terminal-focus QA, and release-candidate dogfooding.