Repository navigation
Keep Radar Desktop on the same port so preferences survive restarts - #1907
Conversation
The webview's origin includes the server's port and browser storage is per origin, so a random port each launch emptied every browser-stored preference on restart. Desktop now records its port in ~/.radar/desktop-port and asks for it next time; the server falls back to an OS-assigned port inside the bind when it's taken. A port set in config.json is still used as-is.
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
PR Summary by QodoPreserve Desktop preferences with a stable server port
AI Description
Diagram
High-Level Assessment
Files changed (6)
|
Code Review by Qodo
1.
|
A launch that failed before its window appeared, or a second Desktop window that fell back because the first holds the port, replaced the remembered port. Record on the first DOM-ready instead, and keep the remembered port when another Radar is serving on it.
…nswer The window loaded localhost:<port>, which can resolve to [::1] where another service may hold the same port; it now loads 127.0.0.1:<port>, the address Radar bound. The remembered port is replaced only when something other than Radar answers on it: the probe uses the in-memory /api/connection, and a refused connection, a timeout or a slow Radar leave the record alone.
/api/connection lists every kubeconfig context unless asked not to; with a large kubeconfig the reply passed the probe's read limit and a real Radar was taken for something else.
A remembered port inside a reserved range (Windows reserves blocks for Hyper-V/WSL and can move them at boot) fails to bind on every launch with nothing listening there, so each launch fell back. When nothing answers on the remembered port and it still can't be bound, adopt the new one; if it binds again, it was only briefly busy and is kept. Dial errors are told apart by timeout only, since refusal codes differ across platforms.
Adds the v1.15.0 entry to the What's New catalog. This replaces #1877, which was stacked on the closed #1876; the dialog landed on main in #1902 instead. `scripts/check-whats-new.sh` (#1908) refuses to release v1.15.0 without this entry, so this must merge before the tag. ## Highlights A full-width lead card, then a 2x2 grid. Each secondary card has its own icon color, so they read apart. 1. **AI investigations that show their work** (lead, no CTA; the investigations workspace redirects home in run modes that can't host local agents). This recaps the v1.14.1 rework (#1766, #1802) on purpose: it's the first What's New dialog, and we want to put AI investigations in front of people. OpenCode (#1265) is the new v1.15 part. 2. **Full manifest diffs for Argo CD resources** (#1622) 3. **Rightsizing and cost data in MCP**: new agent tools, and risks lead before savings in the UI too (#1820, #1723) 4. **Deeper batch + AI/ML workloads support**: Kueue, JobSet and Ray pages; Job and JobSet details show why a queued job has no Pods (#1874, #1885, #1900, #1849, #1893, #1897) 5. **Canary and blue-green in Topology** (#1626) ## Also in this release Eight one-liners, paired by length because the grid fills row by row: table sort memory (#1428), OCI Helm installs (#1815), expiring TLS certificates in Checks (#1829), Radar Desktop keeping preferences across restarts (#1907), Pod vs template in Issues (#1848), PVC usage availability (#1828), Strimzi and admission webhook failures in Issues (#1840, #1830), `--listen-address` (#1845). Left out: security fixes, dependency bumps, refactors and CI, accessibility work (#1901, #1909), and narrow items (ALB action annotations, INSTALL_DIR). No usage-stats line is needed, because #1879's consent question already sits in this dialog. ## UI changes beyond the catalog - The heading names the release line (`v1.15`, via `releaseLine`), since patch releases show the same notes. The "You just updated from → to" line keeps exact versions. - An optional `tone` on highlights colors the icon tile (violet, teal, emerald, indigo). It uses Radar's existing `bg-X-500/10 text-X-600 dark:text-X-400` pattern, and was checked in light and dark themes. ## Verification - `scripts/check-whats-new.sh v1.15.0` → found notes - `make tsc` clean; What's New and usage-data tests: 39 passed - Previewed live with `VERSION=v1.15.0 make build`, opening `/?whats-new`, with #1879's consent question in place The matching public changelog is skyhook-dev/marketing#312. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Release-notes catalog and presentational What's New UI only; no auth, API, or data-handling changes. > > **Overview** > Adds the **v1.15.0** What's New catalog entry (five highlights with CTAs where appropriate, plus eight improvement one-liners) so `check-whats-new` can ship the tag. > > The dialog heading now shows the **minor release line** via new `releaseLine()` (e.g. `v1.15` instead of `v1.15.0`), while the upgrade subtitle still uses exact versions. Secondary highlight cards can set an optional **`tone`** (violet, teal, emerald, indigo) for distinct icon tiles; the lead card stays on the accent style. > > Tests cover `releaseLine` and the preview dialog title expectation. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f0038c3. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Radar Desktop forgot every browser-stored preference each time it restarted. That covers theme, favorites, the pinned nav rail, recent resources, log viewer settings, AI panel width/model/effort, timeline pinned lanes, and dismissed prompts.
The cause: Desktop's webview loads
http://localhost:<port>, and browser storage is kept per origin, which includes the port. Desktop picked a random port on every launch, so every launch started with empty storage.What changed
Desktop reuses its last port. After the server starts, Desktop records the port it bound in
~/.radar/desktop-port. On the next launch it asks for that port again, so the webview keeps the same origin and the same storage.Falling back is safe. The server has a new
PortFallbackoption (server.Config, wired throughapp.AppConfig). When the requested port is taken, it binds an OS-assigned port instead of failing. The fallback happens inside the bind, so there's no window where another process takes the port between a check and the listen. Desktop records whichever port it actually got.The port is recorded only once the window has loaded, on the first DOM-ready, so a launch that fails before its window appears changes nothing.
The port moves only when it's clearly lost. When a launch falls back, it checks the remembered port once the window is up:
/api/connection?contexts=0(a second Desktop window)The unbindable row covers Windows' reserved port ranges: Hyper-V, WSL2 and Docker Desktop reserve blocks, which a reboot can move. Dial errors are distinguished only by timeout, because refusal error codes differ between Windows and Unix.
The window loads
127.0.0.1:<port>, the address actually bound, instead oflocalhost:<port>.localhostcan resolve to[::1], where another service may hold the same port. This changes Desktop's origin once, which costs nothing, since storage was already reset on every launch.A port configured in
config.jsonis unchanged: used as-is, with no fallback and nothing recorded.The CLI is unaffected. It keeps 9280 by default and never falls back.
When the remembered port is taken (rare), that one session starts with empty storage, which is today's behavior every time. The next launch goes back to the recorded port.
Platform notes
%AppData%\<exe name>. Renaming the exe would reset storage; the self-updater keeps the name.radar.SO_REUSEADDRon Linux and macOS. Windows reuses recently closed ports by default. If a quick restart still misses the port, that one session falls back, and the next launch reclaims it.Testing
rememberDesktopPort: every row of the table above.loopbackPortBindable: held and free ports.portOwner: recognizes Radar; treats an unrelated HTTP server and a non-HTTP service as foreign; reports a free port as silent and a server too slow to answer as unknown.listenPreferringPort: a taken port fails without fallback and binds a different port with it; a free port is used as preferred.desktop-portround-trip, garbage values ignored, no temp files left.go test ./internal/server ./internal/app ./cmd/desktopall pass.127.0.0.1:51528(none tolocalhost). B, started while A ran, fell back to 51555 and left the record at 51528. After both quit, C bound 51528 again.HOME:Port 63239 is unavailable …; using an OS-assigned port, bound 63266 and recorded that