Skip to content

Add isolated dev build (make run-app-dev)#510

Closed
jeremybower wants to merge 6 commits into
supabitapp:mainfrom
jeremybower:feature/make-run-app-dev
Closed

Add isolated dev build (make run-app-dev)#510
jeremybower wants to merge 6 commits into
supabitapp:mainfrom
jeremybower:feature/make-run-app-dev

Conversation

@jeremybower

@jeremybower jeremybower commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Closes #635

Summary

Dogfooding an in-development build today means running it against your real ~/.supacode — the dev build and the daily-driver install share the bundle id, data directory, zmx sessions, deeplink routing, Sparkle updater, and CLI socket directory, so the safest workflow is "quit the app you use all day and hope nothing corrupts" (see #635 for the full failure-mode audit).

This adds Supacode Dev, an isolated copy that can run alongside a real install:

  • Own bundle id (app.supabit.supacode.dev), name/icon "Supacode Dev", and supacode-dev:// URL scheme — separate UserDefaults domain and LaunchServices/notification/deeplink identity.
  • All state under ~/.supacode-dev (settings, sidebar, layouts, worktree clones), so persisted files and restored zmx sessions never collide with prod.
  • Sparkle disabled for the dev build, so it can't self-update or be replaced mid-session.
  • Socket-dir namespacing: the dev app binds its CLI socket under /tmp/supacode-dev-<uid> instead of sharing /tmp/supacode-<uid>. The bundled CLI (shared binary, no separate product) derives dev-ness at runtime from its own executable path — the bundle id of the enclosing .app — resolves only its own build's socket directory, and cold-launches the app bundle it is embedded in. Each build's supacode always reaches its own app, and running both apps no longer breaks the CLI with "Multiple Supacode instances found".
  • Make targets for the loop: make run-app-dev (build + launch), make build-app-dev, make dev (watch, rebuild, relaunch via watchexec), make log-stream-dev.

Implementation notes:

  • The dev app is a dedicated Xcode target (supacode-dev) on the standard Debug configuration, produced by the same supacodeAppTarget factory as prod. A custom build configuration breaks the supacode-cli target (Tuist generates external SPM projects with only Debug/Release), and a command-line PRODUCT_NAME override collides the shared frameworks — both were tried. Frameworks, GhosttyKit, and the CLI stay single shared targets, so only the app sources compile twice, and the prod target is byte-for-byte unchanged (Info.plist reads name/icon/scheme via $(VAR:default=…)).
  • Dev behaviour is decided at runtime from the .dev bundle id suffix (SupacodePaths.isDevelopmentBuild), not a compile flag, which would force the broken custom configuration.
  • The dev app registers supacode-dev:// for OS routing but accepts both schemes, so the CLI's supacode:// payloads work against either build.

Type of change

  • Bug fix (the linked issue is a bug report)
  • Feature (the linked issue is a feature request marked ready)
  • Documentation
  • Other (please describe)

How was this tested?

Ran the full local gates on this branch after rebasing onto main, plus new unit tests for the CLI socket-directory resolution (SocketDiscoveryTests: enclosing-bundle detection, dev/prod directory choice, bare-binary and symlinked-CLI fallbacks). Verified manually that the dev app and a prod install run side by side with independent state and that each build's CLI reaches its own app.

  • make check passes (format + lint)
  • make test passes
  • I built and ran the app to confirm the change works

Checklist

  • This pull request is linked to an issue with Closes # above.
  • For a feature, the linked issue is labeled ready.
  • I am the author of this work and accountable for it; no commit is authored or co-authored by an AI agent.
  • I have read the Contributing guide and the Code of Conduct.

@jeremybower jeremybower changed the title Add isolated dev build (make run-app-dev) Add isolated dev build and Makefile targets Jun 28, 2026
@sbertix

sbertix commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

I don't know about this one, tbh 😅 do we really need this?

@jeremybower

Copy link
Copy Markdown
Contributor Author

Fair question 😄 — the itch this scratches: testing anything that touches persisted state (settings migrations, sidebar buckets, layout snapshots, worktree clone flows) means risking your real ~/.supacode and your actual worktrees. Today the only safe option is back up the dir / use a throwaway machine. The dev build gives you a parallel install with isolated state and no auto-update, so you can break things freely and rm -rf ~/.supacode-dev to reset.

There's also a stability angle: I daily-drive Supacode for agent orchestration, and I've noticed running terminal sessions get flaky when two instances of the same app are open (haven't debugged it yet). Keeping in-flight changes in a separate app means the prod build I actually rely on to do the work stays untouched. make dev (watch + rebuild + relaunch) is the day-to-day payoff — tight edit loop without clobbering the prod app I keep open.

On cost, I tried to keep it cheap and contained:

  • Prod target is unchangedInfo.plist just reads $(VAR:default=…), so with no overrides it's byte-for-byte the same Supacode.
  • It's one extra Xcode target sharing the same sources; frameworks/GhosttyKit/CLI are built once, so it's not a second full build.
  • Dev-vs-prod is a runtime bundle-id check, no compile flags or #if scattered around.

That said, if it's not worth the maintenance surface to you, I'm happy to trim. Options:

  1. Keep as-is.
  2. Drop the separate icon/scheme polish, keep just isolated ~/.supacode-dev state + make dev.
  3. Park it as a doc'd local-only recipe and close the PR.

Whatever you prefer — no attachment here.

@jeremybower
jeremybower marked this pull request as draft June 28, 2026 14:21
@jeremybower
jeremybower marked this pull request as ready for review June 28, 2026 14:29
@jeremybower
jeremybower marked this pull request as draft July 11, 2026 10:06
@jeremybower
jeremybower force-pushed the feature/make-run-app-dev branch from 187cbb3 to 83af553 Compare July 11, 2026 11:17
@github-actions github-actions Bot added the invalid Does not meet the contribution policy; closed automatically after a few days if left inactive. label Jul 11, 2026
@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown

Thanks for the pull request. It doesn't meet the contribution policy yet, so I've labeled it invalid. Please address the following, then push an update:

Nothing needs to be redone. A pull request left invalid is closed automatically after a few days of inactivity. See CONTRIBUTING.md for the full flow.

@jeremybower jeremybower changed the title Add isolated dev build and Makefile targets Add isolated dev build (make run-app-dev) Jul 11, 2026
@jeremybower
jeremybower force-pushed the feature/make-run-app-dev branch from 83af553 to d23034d Compare July 11, 2026 11:21
@jeremybower

Copy link
Copy Markdown
Contributor Author

@sbertix to bring this PR into compliance with the policies, I opened an issue for this feature and added more details on why it is necessary.

Build and launch "Supacode Dev" as a dedicated Xcode target alongside the
prod app: own bundle id (app.supabit.supacode.dev), name, blueprint icon,
and supacode-dev:// scheme. State lives under ~/.supacode-dev and Sparkle is
disabled, so it can be tested without touching a real install.

Dev behaviour is decided at runtime from the .dev bundle id
(SupacodePaths.isDevelopmentBuild) rather than a compile flag, which would
force a non-standard Xcode configuration and break SPM module resolution for
the CLI target. Both builds accept supacode:// so the embedded CLI keeps
working over the socket.
…rminal

zmx exports ZMX_SESSION=<name> into every shell it spawns (it marks which
session a process is running inside). Its `attach` command checks that env
var first: when ZMX_SESSION is set, it ignores the requested session id and
tries to *switch* to the env-named session instead.

Launching Supacode from inside a Supacode terminal (the common case when
running `make run-app-dev` / `make dev` from your daily-driver install) leaks
the parent surface's ZMX_SESSION into the new app process. Every surface the
new app spawns then runs `zmx attach supa-<new-id>`, but zmx reroutes it to
the inherited parent session `supa-<old-id>`, which it can't find — so the
surface dies with `error: session "supa-…" does not exist`. The two
mismatched UUIDs in that error (requested vs. inherited) are the tell.

Drop the inherited ZMX_SESSION once at app launch, before any surface spawns.
zmx re-injects the correct per-session value into each shell it spawns, so
removing the stale inherited one is safe and surfaces still get their own id.

Safe for prod: nothing in Supacode reads ZMX_SESSION; on the normal launch
path (Dock/Finder) the var isn't set so this is a no-op; the only path where
it's inherited is launch-from-inside-a-terminal, where prod has the same
latent bug this also fixes.
pkill immediately followed by open raced: the old Dev app was still
mid-termination, so open tried to reactivate the dying instance and
LaunchServices returned procNotFound (-600). open -n forces a new
instance instead.
The dev app now binds its CLI socket under /tmp/supacode-dev-<uid> instead
of sharing /tmp/supacode-<uid> with prod (gated on the same runtime
SupacodePaths.isDevelopmentBuild check as the rest of the dev isolation).

The CLI binary is shared between the two app targets, so it derives
dev-ness at runtime from its own executable path — the bundle id of the
enclosing .app — and resolves only its own build's socket directory. The
cold-launch fallback now opens the app bundle the CLI is embedded in
rather than 'open -a Supacode', so the dev CLI cold-launches the dev app.
With both apps running, 'supacode' outside a terminal session no longer
fails with 'Multiple Supacode instances found'.

The supacode-cli target moves from a buildable folder to an explicit
source glob so supacodeTests can compile SocketDiscovery.swift directly
(a command line tool has no importable module).
pkill by product name ('Supacode Dev.app/Contents/MacOS/') or by target
name ('watchexec.*run-app-dev') matches processes launched from any
worktree of this repo, so a relaunch or a new watcher in one worktree
killed the dev app / watcher belonging to another.

Anchor the relaunch kill on the worktree's own built-products path, and
put absolute --watch paths on the watchexec command line so the watcher
kill can anchor on the worktree directory.
@jeremybower
jeremybower force-pushed the feature/make-run-app-dev branch from 09fbac7 to a14f655 Compare July 12, 2026 14:39
@sbertix

sbertix commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

A variation of this, if not exactly this, is definitely coming.
Same as #604, the "stalling" is not about the code implementation but the direction/abstraction itself.

That said, to align expectations further: although I can confidently say this is eventually being merged, please do not expect this to be prioritized any further at this point ("it's not gonna happen today").

@github-actions

Copy link
Copy Markdown

Closing this because it has been labeled invalid and inactive for more than 3 days. Nothing is lost: address the points in the policy comment above and reopen it, and the checks will re-run. See CONTRIBUTING.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

invalid Does not meet the contribution policy; closed automatically after a few days if left inactive.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Isolated dev build for safely testing state-touching changes

2 participants