Read project activity once while sorting the sidebar (#24549)

Reuse the existing pure recent rank once per actual entry tuple within each synchronous sort; discard the lookup after sorting.
This commit is contained in:
Neil
2026-10-02 23:31:05 -07:00
committed by GitHub
parent 0b15a209b6
commit 7a3ce0b70e
3 changed files with 235 additions and 3 deletions
@@ -11,7 +11,7 @@ import type { SectionAppendContext } from './group-sections'
import type { OrderedGroupEntry } from './project-grouping'
import {
compareRecentRank,
recentRankForEntry,
createRecentRankLookup,
withRepoSectionDisplayLabels
} from './section-order'
import { buildFolderWorkspaceRow } from './row-builders'
@@ -40,8 +40,12 @@ export function appendProjectGroupSections(
const sortRepoEntriesWithinGroup = (entries: OrderedGroupEntry[]): OrderedGroupEntry[] => {
if (projectOrderBy === 'recent') {
if (entries.length < 2) {
return [...entries]
}
const getRecentRank = createRecentRankLookup()
return [...entries].sort((left, right) =>
compareRecentRank(recentRankForEntry(left), recentRankForEntry(right))
compareRecentRank(getRecentRank(left), getRecentRank(right))
)
}
// Manual: within a Project Group, projects order by their per-group rank
@@ -0,0 +1,211 @@
import { describe, expect, it } from 'vitest'
import { sortProjectEntries } from './section-order'
import { appendProjectGroupSections } from './project-group-sections'
import type { SectionAppendContext } from './group-sections'
import { PROJECT_GROUP_META } from './group-keys'
import type { OrderedGroupEntry } from './project-grouping'
import { repo, worktree } from '../../worktree-list-groups-test-fixtures'
function entry(
key: string,
label: string,
activities: number[],
addedAt = 0,
repoIds = [key]
): OrderedGroupEntry {
return [
key,
{
label,
repo: { ...repo, id: key, addedAt },
repoIds: new Set(repoIds),
items: activities.map((lastActivityAt, index) => ({
...worktree,
id: `${key}:${index}`,
lastActivityAt
}))
}
]
}
function countedEntries(): { entries: OrderedGroupEntry[]; readCount: () => number } {
let reads = 0
const entries: OrderedGroupEntry[] = Array.from({ length: 128 }, (_, group) => [
String(group),
{
label: String(group),
repoIds: new Set([String(group)]),
items: Array.from({ length: 32 }, (_, index) => ({
...worktree,
id: `${group}:${index}`,
get lastActivityAt() {
reads += 1
return (group * 73) % 128
}
}))
}
])
return { entries, readCount: () => reads }
}
describe('recent project rank scan budget', () => {
it('scans each project workspace list once per recent sort and preserves the complete order', () => {
const { entries, readCount } = countedEntries()
const sorted = sortProjectEntries(entries, 'recent', undefined)
const expected = entries.toSorted(
(left, right) => ((Number(right[0]) * 73) % 128) - ((Number(left[0]) * 73) % 128)
)
expect(sorted).toEqual(expected)
sorted.forEach((row, index) => expect(row).toBe(expected[index]))
expect(readCount()).toBe(128 * 33)
})
it('reuses workspace ranks within a project group while preserving every header', () => {
const { entries, readCount } = countedEntries()
const grouped: OrderedGroupEntry[] = entries.map(([key, group]) => [
key,
{ ...group, repo: { ...repo, id: key, path: `/${key}`, projectGroupId: 'parent' } }
])
const ctx: SectionAppendContext = {
result: [],
groupBy: 'repo',
collapsedGroups: new Set(grouped.map(([key]) => key)),
workspaceStatuses: [],
repoMap: new Map(),
defaultHostId: 'local',
hostLabelById: undefined,
projectIndex: null,
importedWorktreesByRepo: new Map(),
newExternalWorktreesInboxByRepo: new Map(),
pendingByRepo: new Map(),
mixedWorktreeHostContextLabels: undefined,
noticeHostContextLabelByRepoId: undefined,
lineageById: {},
worktreeMap: new Map(),
nestLineage: false,
cyclicLineageIds: new Set()
}
appendProjectGroupSections(ctx, {
orderedGroups: grouped,
projectGroups: [
{
id: 'parent',
name: 'parent',
parentPath: null,
parentGroupId: null,
createdFrom: 'manual',
tabOrder: 0,
isCollapsed: false,
color: null,
createdAt: 0,
updatedAt: 0
}
],
folderWorkspaces: [],
projectOrderBy: 'recent',
repoOrder: undefined
})
const expected = grouped.toSorted(
(left, right) => ((Number(right[0]) * 73) % 128) - ((Number(left[0]) * 73) % 128)
)
expect(ctx.result).toHaveLength(129)
expect(ctx.result[0]).toMatchObject({ type: 'header', count: 128, projectGroupDepth: 0 })
expect(ctx.result.slice(1)).toEqual(
expected.map(([key, group]) => ({
type: 'header',
key,
label: group.label,
count: 32,
tone: PROJECT_GROUP_META.tone,
icon: PROJECT_GROUP_META.icon,
repo: group.repo,
projectGroupDepth: 1
}))
)
expect(readCount()).toBe(128 * 33)
})
it('preserves identity collisions, manual ties, stable ties and missing activity fallbacks', () => {
const hottest = entry('collision', 'hottest', [10, 300, 20])
const hot = entry('collision', 'hot', [200])
const tieA = entry('a', 'same', [100], 0, ['tie'])
const tieB = entry('b', 'same', [100], 0, ['tie'])
const tieRank = entry('rank', 'later label', [100], 0, ['rank'])
const emptyNew = entry('new', 'new', [], 999)
const emptyOld = entry('old', 'old', [], 1)
const noRepo: OrderedGroupEntry = [
'no-repo',
{ label: 'absent', items: [], repoIds: new Set() }
]
const missing = entry('missing', 'missing', [0], 5)
Reflect.deleteProperty(missing[1].items[0], 'lastActivityAt')
const invalid = entry('invalid', 'invalid', [Number.NaN, Number.NEGATIVE_INFINITY], 6)
const infinite = entry('infinite', 'infinite', [Number.POSITIVE_INFINITY])
const entries = [
tieA,
hot,
emptyOld,
hottest,
noRepo,
invalid,
tieB,
missing,
emptyNew,
tieRank,
infinite,
tieA
]
const order = new Map([
['rank', 0],
['tie', 1]
])
const expected = [
infinite,
hottest,
hot,
tieRank,
tieA,
tieB,
tieA,
emptyNew,
invalid,
missing,
emptyOld,
noRepo
]
const actual = sortProjectEntries(entries, 'recent', order)
expect(actual).toEqual(expected)
actual.forEach((row, index) => expect(row).toBe(expected[index]))
expect(entries[0]).toBe(tieA)
})
it('reads current activity and fallback evidence on each new call and leaves manual mode intact', () => {
const first = entry('first', 'first', [100])
const second = entry('second', 'second', [200])
const old = entry('old', 'old', [], 1)
const newer = entry('newer', 'newer', [], 2)
const entries = [first, second, old, newer]
expect(sortProjectEntries(entries, 'recent', undefined)).toEqual([second, first, newer, old])
first[1].items[0].lastActivityAt = 300
if (!old[1].repo) {
throw new Error('Missing fixture repository')
}
old[1].repo.addedAt = 3
expect(sortProjectEntries(entries, 'recent', undefined)).toEqual([first, second, old, newer])
expect(sortProjectEntries(entries, 'manual', undefined)).toBe(entries)
expect(
sortProjectEntries(entries, 'manual', new Map(entries.map(([key], index) => [key, -index])))
).toEqual([newer, old, second, first])
})
it('copies empty and single-project inputs without scanning their workspace activity', () => {
const { entries, readCount } = countedEntries()
expect(sortProjectEntries([], 'recent', undefined)).toEqual([])
const single = entries.slice(0, 1)
const sorted = sortProjectEntries(single, 'recent', undefined)
expect(sorted).toEqual(single)
expect(sorted).not.toBe(single)
expect(sorted[0]).toBe(single[0])
expect(readCount()).toBe(0)
})
})
@@ -123,6 +123,19 @@ export function recentRankForEntry(entry: OrderedGroupEntry): RecentRank {
}
}
export function createRecentRankLookup(): (entry: OrderedGroupEntry) => RecentRank {
const ranks = new Map<OrderedGroupEntry, RecentRank>()
return (entry) => {
const existing = ranks.get(entry)
if (existing) {
return existing
}
const rank = recentRankForEntry(entry)
ranks.set(entry, rank)
return rank
}
}
export function compareRecentRank(a: RecentRank, b: RecentRank): number {
if (a.hasActivity !== b.hasActivity) {
return a.hasActivity ? -1 : 1
@@ -182,8 +195,12 @@ export function sortProjectEntries(
repoOrder: Map<string, number> | undefined
): OrderedGroupEntry[] {
if (projectOrderBy === 'recent') {
if (entries.length < 2) {
return [...entries]
}
const getRecentRank = createRecentRankLookup()
return [...entries].sort((a, b) => {
const byRecent = compareRecentRank(recentRankForEntry(a), recentRankForEntry(b))
const byRecent = compareRecentRank(getRecentRank(a), getRecentRank(b))
if (byRecent !== 0) {
return byRecent
}