From fb173067f527d421f167bf05ca3fabc542d586de Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sun, 12 Jul 2026 00:49:28 +0300 Subject: [PATCH] fix(worktree): refresh changed discovery metadata --- .../src/lib/worktrees/worktreeManager.test.ts | 12 +++++++++ .../ui/src/lib/worktrees/worktreeManager.ts | 25 +++++++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/packages/ui/src/lib/worktrees/worktreeManager.test.ts b/packages/ui/src/lib/worktrees/worktreeManager.test.ts index 677bfd9e..944c3f59 100644 --- a/packages/ui/src/lib/worktrees/worktreeManager.test.ts +++ b/packages/ui/src/lib/worktrees/worktreeManager.test.ts @@ -153,6 +153,18 @@ describe('worktreeMapsEqual', () => { expect(worktreeMapsEqual(a, b)).toBe(false); }); + test('returns false when head state changes without a branch change', () => { + const a = new Map([['/repo', [wt('/r/main', '', { headState: 'unborn' })]]]); + const b = new Map([['/repo', [wt('/r/main', '', { headState: 'detached' })]]]); + expect(worktreeMapsEqual(a, b)).toBe(false); + }); + + test('returns false when discovered display metadata changes', () => { + const a = new Map([['/repo', [wt('/r/main', '', { name: 'old', label: 'old' })]]]); + const b = new Map([['/repo', [wt('/r/main', '', { name: 'new', label: 'new' })]]]); + expect(worktreeMapsEqual(a, b)).toBe(false); + }); + test('returns false when paths differ', () => { const a = new Map([['/repo', [wt('/r/main', 'main')]]]); const b = new Map([['/repo', [wt('/r/other', 'main')]]]); diff --git a/packages/ui/src/lib/worktrees/worktreeManager.ts b/packages/ui/src/lib/worktrees/worktreeManager.ts index 4377a263..bccd773a 100644 --- a/packages/ui/src/lib/worktrees/worktreeManager.ts +++ b/packages/ui/src/lib/worktrees/worktreeManager.ts @@ -231,7 +231,7 @@ const toCreatePayload = (args: { /** * Compare two worktree-by-project maps for equality. - * Compares per-element `path` and `branch` (not reference equality) + * Compares discovery-owned metadata (not reference equality) * because readStableProjectWorktrees creates new object instances on * each call, making reference checks always report changed. * @@ -246,22 +246,27 @@ const toCreatePayload = (args: { * flow through `setStoredWorktreeStatus`, which writes a new Map * reference that the persist subscriber picks up directly. * - * Generic over `T extends { path: string; branch: string }` so the - * helper documents its equality contract at the type level and - * stays reusable for any future map-of-arrays shape that has both - * fields. */ -export const worktreeMapsEqual = ( - a: Map, - b: Map, +export const worktreeMapsEqual = ( + a: Map, + b: Map, ): boolean => { if (a.size !== b.size) return false; for (const [key, value] of a) { const existing = b.get(key); if (!existing || existing.length !== value.length) return false; for (let i = 0; i < value.length; i++) { - if (value[i].path !== existing[i].path) return false; - if (value[i].branch !== existing[i].branch) return false; + const next = value[i]; + const current = existing[i]; + if (next.path !== current.path + || next.branch !== current.branch + || next.name !== current.name + || next.label !== current.label + || next.projectDirectory !== current.projectDirectory + || next.worktreeRoot !== current.worktreeRoot + || next.headState !== current.headState + || next.worktreeSource !== current.worktreeSource + || next.source !== current.source) return false; } } return true;