From dafe844a50eff1fb2c5d576c37160fdc9aeedcb4 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Mon, 29 Jun 2026 00:21:34 -0700 Subject: [PATCH] Keep the mobile worktree unread bell cleared after opening a worktree (#6673) Co-authored-by: Orca --- src/main/runtime/orca-runtime.test.ts | 141 ++++++++++++++++++++++++++ src/main/runtime/orca-runtime.ts | 7 ++ 2 files changed, 148 insertions(+) diff --git a/src/main/runtime/orca-runtime.test.ts b/src/main/runtime/orca-runtime.test.ts index 583d40d9ae2..e5100249d0f 100644 --- a/src/main/runtime/orca-runtime.test.ts +++ b/src/main/runtime/orca-runtime.test.ts @@ -12344,6 +12344,147 @@ describe('OrcaRuntimeService', () => { ]) }) + it('clears unread metadata on mobile worktree activation without focusing desktop clients', async () => { + const metaById: Record = { + [TEST_WORKTREE_ID]: makeWorktreeMeta({ isUnread: true }) + } + const setWorktreeMeta = vi.fn((worktreeId: string, meta: Partial) => { + metaById[worktreeId] = { ...(metaById[worktreeId] ?? makeWorktreeMeta()), ...meta } + return metaById[worktreeId] + }) + const activateWorktree = vi.fn() + const worktreesChanged = vi.fn() + const runtime = new OrcaRuntimeService({ + ...store, + getAllWorktreeMeta: () => metaById, + getWorktreeMeta: (worktreeId: string) => metaById[worktreeId], + setWorktreeMeta + } as never) + runtime.setNotifier({ + worktreesChanged, + reposChanged: vi.fn(), + activateWorktree, + createTerminal: vi.fn(), + revealTerminalSession: vi.fn(), + splitTerminal: vi.fn(), + renameTerminal: vi.fn(), + focusTerminal: vi.fn(), + closeTerminal: vi.fn(), + sleepWorktree: vi.fn(), + terminalFitOverrideChanged: vi.fn(), + terminalDriverChanged: vi.fn() + }) + + runtime.attachWindow(TEST_WINDOW_ID) + runtime.markGraphReady(TEST_WINDOW_ID) + + await runtime.activateManagedWorktree(`id:${TEST_WORKTREE_ID}`, { notifyClients: false }) + + expect(setWorktreeMeta).toHaveBeenCalledWith(TEST_WORKTREE_ID, { isUnread: false }) + expect(metaById[TEST_WORKTREE_ID]?.isUnread).toBe(false) + expect(worktreesChanged).toHaveBeenCalledWith(TEST_REPO_ID) + expect(activateWorktree).not.toHaveBeenCalled() + }) + + it('does not rewrite unread metadata when a mobile activation finds the worktree already read', async () => { + // Why: seed instanceId so worktree resolution does not emit its own + // metadata-stamp write, isolating the assertion to the unread clear. + const metaById: Record = { + [TEST_WORKTREE_ID]: makeWorktreeMeta({ isUnread: false, instanceId: 'wt-instance' }) + } + const setWorktreeMeta = vi.fn((worktreeId: string, meta: Partial) => { + metaById[worktreeId] = { ...(metaById[worktreeId] ?? makeWorktreeMeta()), ...meta } + return metaById[worktreeId] + }) + const worktreesChanged = vi.fn() + const runtime = new OrcaRuntimeService({ + ...store, + getAllWorktreeMeta: () => metaById, + getWorktreeMeta: (worktreeId: string) => metaById[worktreeId], + setWorktreeMeta + } as never) + runtime.setNotifier({ + worktreesChanged, + reposChanged: vi.fn(), + activateWorktree: vi.fn(), + createTerminal: vi.fn(), + revealTerminalSession: vi.fn(), + splitTerminal: vi.fn(), + renameTerminal: vi.fn(), + focusTerminal: vi.fn(), + closeTerminal: vi.fn(), + sleepWorktree: vi.fn(), + terminalFitOverrideChanged: vi.fn(), + terminalDriverChanged: vi.fn() + }) + + runtime.attachWindow(TEST_WINDOW_ID) + runtime.markGraphReady(TEST_WINDOW_ID) + + await runtime.activateManagedWorktree(`id:${TEST_WORKTREE_ID}`, { notifyClients: false }) + + expect(setWorktreeMeta).not.toHaveBeenCalled() + expect(worktreesChanged).not.toHaveBeenCalled() + + metaById[TEST_WORKTREE_ID] = makeWorktreeMeta({ isUnread: true, instanceId: 'wt-instance' }) + setWorktreeMeta.mockClear() + worktreesChanged.mockClear() + + await runtime.activateManagedWorktree(`id:${TEST_WORKTREE_ID}`, { notifyClients: false }) + await runtime.activateManagedWorktree(`id:${TEST_WORKTREE_ID}`, { notifyClients: false }) + + expect(setWorktreeMeta).toHaveBeenCalledTimes(1) + expect(setWorktreeMeta).toHaveBeenCalledWith(TEST_WORKTREE_ID, { isUnread: false }) + expect(worktreesChanged).toHaveBeenCalledTimes(1) + expect(worktreesChanged).toHaveBeenCalledWith(TEST_REPO_ID) + }) + + it('returns unread:false from worktree.ps after a mobile activation clears the flag', async () => { + const metaById: Record = { + [TEST_WORKTREE_ID]: makeWorktreeMeta({ isUnread: true }) + } + const setWorktreeMeta = vi.fn((worktreeId: string, meta: Partial) => { + metaById[worktreeId] = { ...(metaById[worktreeId] ?? makeWorktreeMeta()), ...meta } + return metaById[worktreeId] + }) + const runtime = new OrcaRuntimeService({ + ...store, + getAllWorktreeMeta: () => metaById, + getWorktreeMeta: (worktreeId: string) => metaById[worktreeId], + setWorktreeMeta + } as never) + runtime.setNotifier({ + worktreesChanged: vi.fn(), + reposChanged: vi.fn(), + activateWorktree: vi.fn(), + createTerminal: vi.fn(), + revealTerminalSession: vi.fn(), + splitTerminal: vi.fn(), + renameTerminal: vi.fn(), + focusTerminal: vi.fn(), + closeTerminal: vi.fn(), + sleepWorktree: vi.fn(), + terminalFitOverrideChanged: vi.fn(), + terminalDriverChanged: vi.fn() + }) + + runtime.attachWindow(TEST_WINDOW_ID) + runtime.markGraphReady(TEST_WINDOW_ID) + + const beforeActivation = await runtime.getWorktreePs() + expect( + beforeActivation.worktrees.find((worktree) => worktree.worktreeId === TEST_WORKTREE_ID) + ?.unread + ).toBe(true) + + await runtime.activateManagedWorktree(`id:${TEST_WORKTREE_ID}`, { notifyClients: false }) + + const afterActivation = await runtime.getWorktreePs() + expect( + afterActivation.worktrees.find((worktree) => worktree.worktreeId === TEST_WORKTREE_ID)?.unread + ).toBe(false) + }) + it('materializes pending mobile session terminals without focusing desktop clients', async () => { const persistedPtyId = `${TEST_WORKTREE_ID}@@mobile-only-pty` const spawn = vi.fn().mockResolvedValue({ id: persistedPtyId }) diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index a78069106b8..8b2d9936802 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -11807,6 +11807,13 @@ export class OrcaRuntimeService { throw new Error('repo_not_found') } + if (opts.notifyClients === false && this.store?.getWorktreeMeta(worktree.id)?.isUnread) { + // Why: mobile/web session activation intentionally bypasses renderer + // selection, so the runtime must acknowledge the unread state itself. + this.store.setWorktreeMeta(worktree.id, { isUnread: false }) + this.notifyWorktreesChanged(repo.id) + } + if (opts.notifyClients !== false) { // Why: inactive worktree terminal panes are renderer-owned and may not have // live PTYs until the desktop activates the worktree and mounts them.