Skip to content

feat(instance): restrict Kyde to one instance, opening later launches as project tabs - #75

Merged
kyle-ssg merged 2 commits into
mainfrom
feat/single-instance
Jul 27, 2026
Merged

feat(instance): restrict Kyde to one instance, opening later launches as project tabs#75
kyle-ssg merged 2 commits into
mainfrom
feat/single-instance

Conversation

@kyle-ssg

Copy link
Copy Markdown
Owner

Closes #72.

What

Every open project is already a tab, so a second kyde <path> from the terminal shouldn't start a second app. It now hands the path to the running instance, which opens it as another project tab and comes forward. Bare kyde just raises the window.

How

  • src/platform/instance.rs: a unix domain socket at $TMPDIR/kyde-<hash>.sock, hashed over USER|XDG_CONFIG_HOME so separate users and config profiles never collide.
  • A TMPDIR too deep for sun_path (~104 bytes — sandboxed/CI temp dirs hit this) falls back to /tmp, with the directory folded into the hash so temp dirs stay distinct. Without this, bind fails outright with "path must be shorter than SUN_LEN" and the guard silently does nothing — found while testing, not hypothetical.
  • main() tries to deliver the launch before Application::new(), so a forwarded launch exits in ~30ms having created no window and initialised no gpui. Nothing listening → we're the instance, and bind after the main window opens.
  • The accept thread can't touch gpui entities, so it only forwards a Request (Open(path) / Activate) over a futures::mpsc channel to a foreground pump — the terminal EventProxy / fs-watcher pattern — which calls open_project + activate_window.
  • A socket left behind by a crash or SIGTERM is detected (connect fails) and unlinked before the rebind; a clean exit unlinks it on Drop.
  • Opt-out: KYDE_SINGLE_INSTANCE=0, and automatically off under KYDE_SHOT so the screenshot suite can keep launching instances back to back.

Tests

  • Payload round-trip + safe degradation (malformed/unknown verbs raise the window rather than opening a bogus project).
  • The socket-path rule, including the long-TMPDIR fallback and per-profile separation.
  • A real listener handshake over a socket, including stale-socket cleanup.
  • Manually verified end to end: second launch exits 0 in ~30ms, one process remains, the running window gains the new project tab and comes forward; stale-socket recovery and bare-kyde activation both work.
  • cargo fmt + cargo clippy --workspace --all-targets --all-features -D warnings + cargo test --workspace all green.

🤖 Generated with Claude Code

… as tabs

Closes #72.

Every open project is already a tab, so a second `kyde <path>` from the
terminal should not start a second app — it now hands the path to the running
instance, which opens it as another project tab and comes forward.

* `platform/instance.rs`: a unix domain socket at `$TMPDIR/kyde-<hash>.sock`,
  hashed over `USER|XDG_CONFIG_HOME` so separate users and config profiles
  never collide. A `TMPDIR` too deep for `sun_path` (~104 bytes, e.g. sandboxed
  or CI temp dirs) falls back to `/tmp` with the directory folded into the hash
  — without this, bind fails outright with "path must be shorter than SUN_LEN".
* `main()` tries to deliver the launch before `Application::new()`, so a
  forwarded launch exits in ~30ms having created no window. Nothing listening
  means we are the instance, and bind after the main window opens.
* The accept thread cannot touch gpui entities, so it only forwards a request
  over a channel to a foreground pump (the terminal EventProxy / fs-watcher
  pattern), which calls `open_project` and activates the window.
* A socket left behind by a crash or SIGTERM is detected and unlinked before
  the rebind; a clean exit unlinks it on drop.
* Opt-out via `KYDE_SINGLE_INSTANCE=0`, and automatically off under `KYDE_SHOT`
  so the screenshot suite can keep launching instances back to back.

Tests: payload round-trip/degradation, the socket-path rule including the
long-TMPDIR fallback, and a real listener handshake (including stale-socket
cleanup).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@themis-gatacre-games

Copy link
Copy Markdown

Review failed after 2 attempt(s) (EngineError). Check the worker logs.

Resolves the CLAUDE.md conflict: both branches appended a per-feature
section at the same anchor (main's "Changelog window" from #74, this
branch's "Single instance").

Rather than keep both, CLAUDE.md is trimmed to rules-and-conventions
only (905 -> 327 lines). The per-feature sections narrated behaviour
that the code already states, drifted out of date, and were loaded into
context every session. Both conflicting sections are dropped under that
policy; the single-instance facts that are genuinely rules are kept --
instance.rs on the platform/ layout line, and the note that KYDE_SHOT
disables single-instance so the screenshot suite can launch back to back.

Also corrects accumulated rot found while trimming: 10 dead src/*.rs
paths that had moved to crates/, a views/ list missing 6 modules,
"eight gpui-free crates" (now ten), "all ten crates" (now eleven), the
missing kyde-local-history entry, and a stale gpui_platform reference
that contradicted the gotcha saying it never existed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kyle-ssg
kyle-ssg merged commit 8de700e into main Jul 27, 2026
5 checks passed
@kyle-ssg
kyle-ssg deleted the feat/single-instance branch July 27, 2026 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] Restrict kyde to 1 instance, opening kyde again from terminal should just open that project in another tab

1 participant