fix(sidebar): bound create-grace floor to its window; stamp createdAt on CLI-created worktrees

Two follow-ups to the Recent-sort grace-window fix:

1. effectiveRecentActivity bounded to the grace window. Previously it
   returned max(lastActivityAt, createdAt + CREATE_GRACE_MS) regardless
   of `now`, so a worktree created days ago and never touched would
   keep ranking as `createdAt + 5min` forever, masking genuinely
   fresher worktrees. Now the floor only applies while
   `now < createdAt + CREATE_GRACE_MS`; afterwards it's plain
   lastActivityAt. Comparator threads `now` into both call sites.

2. createManagedWorktree (CLI/runtime path) now stamps createdAt
   alongside lastActivityAt, matching createLocalWorktree /
   createRemoteWorktree. Without this, a worktree created via the CLI
   missed the grace window entirely and could be outranked by ambient
   PTY bumps on other worktrees in the seconds after creation.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
brennanb2025
2026-05-05 15:31:16 -07:00
co-authored by Orca
parent f90eecfa00
commit c723f1b744
4 changed files with 83 additions and 12 deletions
+47
View File
@@ -1062,6 +1062,53 @@ describe('OrcaRuntimeService', () => {
expect(activateWorktree).toHaveBeenCalledWith('repo-1', expect.any(String), undefined)
})
it('stamps createdAt alongside lastActivityAt so CLI-created worktrees get the Recent-sort grace window', async () => {
// Why: parity with createLocalWorktree / createRemoteWorktree. Without
// createdAt, ambient PTY bumps in OTHER worktrees during the few seconds
// after creation can push the new worktree below them in Recent sort.
const runtime = new OrcaRuntimeService(store)
runtime.setNotifier({
worktreesChanged: vi.fn(),
reposChanged: vi.fn(),
activateWorktree: vi.fn(),
createTerminal: vi.fn(),
splitTerminal: vi.fn(),
renameTerminal: vi.fn(),
focusTerminal: vi.fn(),
closeTerminal: vi.fn(),
sleepWorktree: vi.fn(),
terminalFitOverrideChanged: vi.fn()
})
runtime.attachWindow(1)
computeWorktreePathMock.mockReturnValue('/tmp/workspaces/runtime-grace')
ensurePathWithinWorkspaceMock.mockReturnValue('/tmp/workspaces/runtime-grace')
vi.mocked(getEffectiveHooks).mockReturnValue({ scripts: {} })
vi.mocked(listWorktrees).mockResolvedValueOnce([
{
path: '/tmp/workspaces/runtime-grace',
head: 'def',
branch: 'runtime-grace',
isBare: false,
isMainWorktree: false
}
])
const before = Date.now()
const result = await runtime.createManagedWorktree({
repoSelector: 'id:repo-1',
name: 'runtime-grace'
})
const after = Date.now()
expect(result.worktree.createdAt).toBeDefined()
expect(result.worktree.createdAt).toBeGreaterThanOrEqual(before)
expect(result.worktree.createdAt).toBeLessThanOrEqual(after)
// Both fields must be stamped from the same `now` so the grace-window
// math (max(lastActivityAt, createdAt + GRACE_MS)) is well-defined.
expect(result.worktree.createdAt).toBe(result.worktree.lastActivityAt)
})
it('skips archive hooks for CLI worktree removal by default', async () => {
const runtime = new OrcaRuntimeService(store)
vi.mocked(getEffectiveHooks).mockReturnValue({
+7 -1
View File
@@ -2099,8 +2099,14 @@ export class OrcaRuntimeService {
}
const worktreeId = `${repo.id}::${created.path}`
const now = Date.now()
const meta = this.store.setWorktreeMeta(worktreeId, {
lastActivityAt: Date.now(),
lastActivityAt: now,
// See createRemoteWorktree: createdAt grants the new worktree a grace
// window in Recent sort so ambient PTY bumps in OTHER worktrees can't
// push it down before the user has had a chance to notice it. Smart-sort
// uses max(lastActivityAt, createdAt + CREATE_GRACE_MS).
createdAt: now,
...(shouldSetDisplayName(requestedName, branchName, sanitizedName)
? { displayName: requestedName }
: {}),
@@ -522,12 +522,12 @@ describe('buildWorktreeComparator — recent (lastActivityAt)', () => {
describe('effectiveRecentActivity — create-grace floor', () => {
it('returns lastActivityAt when createdAt is absent', () => {
const wt = makeWorktree({ id: 'old', lastActivityAt: 12345 })
expect(effectiveRecentActivity(wt)).toBe(12345)
expect(effectiveRecentActivity(wt, NOW)).toBe(12345)
})
it('returns createdAt + CREATE_GRACE_MS when grace window exceeds lastActivityAt', () => {
const wt = makeWorktree({ id: 'fresh', lastActivityAt: NOW, createdAt: NOW })
expect(effectiveRecentActivity(wt)).toBe(NOW + CREATE_GRACE_MS)
expect(effectiveRecentActivity(wt, NOW)).toBe(NOW + CREATE_GRACE_MS)
})
it('returns lastActivityAt when grace window has elapsed', () => {
@@ -536,7 +536,7 @@ describe('effectiveRecentActivity — create-grace floor', () => {
createdAt: NOW - CREATE_GRACE_MS - 60_000,
lastActivityAt: NOW - 1000
})
expect(effectiveRecentActivity(wt)).toBe(NOW - 1000)
expect(effectiveRecentActivity(wt, NOW)).toBe(NOW - 1000)
})
it('returns lastActivityAt when real activity has surpassed the grace floor', () => {
@@ -545,7 +545,17 @@ describe('effectiveRecentActivity — create-grace floor', () => {
const createdAt = NOW - 3 * 60 * 1000
const wt = makeWorktree({ id: 'used', createdAt, lastActivityAt: NOW - 60_000 })
// createdAt + GRACE_MS = NOW + 2min, which exceeds lastActivityAt (NOW - 1min).
expect(effectiveRecentActivity(wt)).toBe(createdAt + CREATE_GRACE_MS)
expect(effectiveRecentActivity(wt, NOW)).toBe(createdAt + CREATE_GRACE_MS)
})
it('returns lastActivityAt once the grace window has elapsed even when no other activity has occurred', () => {
// Bug-fix case: a worktree created days ago that was never touched after
// creation. Without the time-bound check, the floor would still apply and
// the worktree would rank as `createdAt + 5min` forever, masking truly
// fresher worktrees.
const createdAt = NOW - CREATE_GRACE_MS - 1
const wt = makeWorktree({ id: 'untouched', createdAt, lastActivityAt: createdAt })
expect(effectiveRecentActivity(wt, NOW)).toBe(createdAt)
})
})
@@ -21,13 +21,20 @@ export const CREATE_GRACE_MS = 5 * 60 * 1000
/**
* Rank a worktree in Recent sort using `lastActivityAt`, but with a floor of
* `createdAt + CREATE_GRACE_MS` while that window is active. Returns
* `lastActivityAt` unchanged for worktrees without `createdAt` (discovered on
* disk, or persisted before this field existed).
* `createdAt + CREATE_GRACE_MS` *only during* the grace window (i.e. while
* `now < createdAt + CREATE_GRACE_MS`). Once the window has elapsed, returns
* `lastActivityAt` unchanged. Returns `lastActivityAt` unchanged for worktrees
* without `createdAt` (discovered on disk, or persisted before this field
* existed).
*/
export function effectiveRecentActivity(worktree: Worktree): number {
export function effectiveRecentActivity(worktree: Worktree, now: number): number {
const { lastActivityAt, createdAt } = worktree
if (createdAt === undefined) {
// Why bound by now: a worktree with createdAt set but no subsequent activity
// should not retain artificially-high recency forever; the floor exists to
// absorb the noisy creation window only. Without this bound, a worktree
// created days ago and never touched would keep ranking as if its activity
// were `createdAt + 5min`, masking truly fresher worktrees indefinitely.
if (createdAt === undefined || now >= createdAt + CREATE_GRACE_MS) {
return lastActivityAt
}
return Math.max(lastActivityAt, createdAt + CREATE_GRACE_MS)
@@ -301,7 +308,8 @@ export function buildWorktreeComparator(
)
return (
scoreB - scoreA ||
effectiveRecentActivity(smartB.worktree) - effectiveRecentActivity(smartA.worktree) ||
effectiveRecentActivity(smartB.worktree, now) -
effectiveRecentActivity(smartA.worktree, now) ||
a.displayName.localeCompare(b.displayName)
)
}
@@ -318,7 +326,7 @@ export function buildWorktreeComparator(
// signal — bumped by bumpWorktreeActivity (PTY spawn, background
// events) and by meaningful meta edits (comment, isUnread).
return (
effectiveRecentActivity(b) - effectiveRecentActivity(a) ||
effectiveRecentActivity(b, now) - effectiveRecentActivity(a, now) ||
a.displayName.localeCompare(b.displayName)
)
case 'repo': {