Skip to content

feat: show externally-created worktrees in UI - #62

Merged
centdix merged 4 commits into
mainfrom
workmux-ui-update
Mar 3, 2026
Merged

feat: show externally-created worktrees in UI#62
centdix merged 4 commits into
mainfrom
workmux-ui-update

Conversation

@centdix

@centdix centdix commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Extract initWorktreeEnv() from addWorktree() so env/hooks initialization can be reused when opening externally-created worktrees
  • Lazy-init on open: apiOpenWorktree() now checks for .env.local and initializes ports, profile, agent, and Claude hooks if missing
  • Show all worktrees in sidebar: removed the mux === "✓" filter so CLI-created worktrees appear (dimmed with "closed" label); clicking one opens it and transitions to active

Test plan

  • Create a worktree via CLI (workmux add -b test-branch) — confirm it appears dimmed with "closed" label in the sidebar
  • Click the closed worktree — should show "opening..." then become active with terminal
  • Create a worktree via the web UI "+" button — should work as before
  • Keyboard navigation (Cmd+Up/Down) works across both open and closed worktrees
  • Verify backend type-checks (npx tsc --noEmit) and frontend type-checks (npx svelte-check)

🤖 Generated with Claude Code

Worktrees created via `workmux add` CLI now appear in the sidebar
(dimmed with "closed" label). Clicking opens them with auto-initialized
env/hooks so they work seamlessly without manual setup.

- Extract initWorktreeEnv() from addWorktree() for reuse
- Lazy-init .env.local and Claude hooks in apiOpenWorktree()
- Remove mux === "✓" filter so all worktrees are visible
- Add visual distinction (opacity, labels) for closed/opening states

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@centdix
centdix force-pushed the workmux-ui-update branch from 954b0ae to 46d54a7 Compare March 3, 2026 13:01
centdix and others added 2 commits March 3, 2026 14:37
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@centdix

centdix commented Mar 3, 2026

Copy link
Copy Markdown
Contributor Author

@claude review this PR

@claude

claude Bot commented Mar 3, 2026

Copy link
Copy Markdown

Claude finished @centdix's task in 2m 14s —— View job


PR Review

  • Gather context
  • Read changed files
  • Analyze code
  • Post review feedback

Overall this is a clean, well-scoped implementation. The extraction of initWorktreeEnv, the lazy-init pattern, and the UI dimming are all solid. A few issues worth addressing:

Issues

🔴 Hard-coded "main" branch filter — potential breakage on non-main repos

frontend/src/App.svelte:102

let visibleWorktrees = $derived(worktrees.filter((w) => w.branch !== "main"));

The previous filter (mux === "✓") filtered to open worktrees. This new filter hard-codes "main" — if a repo's default branch is master, develop, or anything else, it would show up in the sidebar as a "closed" worktree (since it'll have mux !== "✓"). The original intent was probably to hide the main working tree, not only branches literally named "main".

It's worth verifying whether workmux list already excludes the main working tree from its output (in which case this filter is redundant and safe to keep). If it does, great — but a comment explaining that assumption would help. If it doesn't, the filter should either be driven by config or a dedicated isMainWorktree flag on the type.

Fix this →


🟡 No user-facing error when opening a closed worktree fails

frontend/src/App.svelte:362–364

} catch (err) {
  console.error("Failed to open worktree:", err);
}

If openWorktree fails, the worktree dims back to "closed" silently. The user gets no feedback explaining what went wrong. Every other error path in this file uses alert(...). This should be consistent:

} catch (err) {
  console.error("Failed to open worktree:", err);
  alert(`Failed to open worktree: ${errorMessage(err)}`);
}

Fix this →


🟡 PROFILE/AGENT silently overwrite keys from envOverrides

backend/src/workmux.ts:255

const defaults: Record<string, string> = { ...portAssignments, ...opts?.envOverrides, PROFILE: profile, AGENT: agent };

Because PROFILE and AGENT are placed after opts?.envOverrides, any caller that passes PROFILE or AGENT in envOverrides (e.g., a future startup env override) will have those silently ignored. The spread order should be swapped so envOverrides takes highest precedence:

const defaults: Record<string, string> = { ...portAssignments, PROFILE: profile, AGENT: agent, ...opts?.envOverrides };

Fix this →


🟡 Concurrent opening of two closed worktrees loses tracking

frontend/src/App.svelte:101

let openingBranch = $state<string | null>(null);

openingBranch is a single nullable string. If a user clicks a second closed worktree while the first is still opening, openingBranch gets overwritten — the first item's "opening..." label disappears (and pointer-events-none is released), while two concurrent openWorktree calls race in the background. removingBranches uses a Set for exactly this reason.

For correctness, consider using Set<string> here too:

let openingBranches = $state<Set<string>>(new Set());

Fix this →


Minor / Nits

backend/src/workmux.ts:371–377git worktree list --porcelain is run twice in addWorktree after the refactor: once inside initWorktreeEnv and once right after it. The second call is needed to get wtDir for env reading, which is fair, but a comment noting this is intentional (and initWorktreeEnv doesn't return the path) would help future readers.

backend/src/workmux.ts:272–283 — The settings.local.json write is unconditional even when env.PROFILE already exists (the guard in apiOpenWorktree prevents reaching initWorktreeEnv in that case, but addWorktree always calls it). This is harmless for the PR's use case but worth noting.


What's Good

  • The initWorktreeEnv extraction is clean — the function is idempotent (won't overwrite existing keys) and handles corrupted JSON gracefully.
  • The lazy-init check in apiOpenWorktree (if (!env.PROFILE)) is a lightweight, stable sentinel to detect uninitialized worktrees.
  • The UI flow (dim → "opening..." → active) is intuitive and consistent with how removal works.
  • The canConnect guard correctly prevents attaching a terminal before the worktree's tmux window exists.

- Filter main working tree in backend (not hard-coded branch name)
- Show alert on open-worktree failure for user feedback
- Let envOverrides take precedence over default PROFILE/AGENT
- Use Set<string> for openingBranches to handle concurrent opens

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@centdix
centdix merged commit 5b3c8c2 into main Mar 3, 2026
1 check passed
@centdix
centdix deleted the workmux-ui-update branch March 3, 2026 13:53
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.

1 participant