mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
Fix manual project ordering for identity headers (#5933)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -361,6 +361,82 @@ describe('buildRows with pinned worktrees', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('orders project identity headers by the manual repo order anchor', () => {
|
||||
const analyticsProject: Project = {
|
||||
...project,
|
||||
id: 'github:stablyai/analytics',
|
||||
displayName: 'Analytics',
|
||||
sourceRepoIds: ['repo-analytics']
|
||||
}
|
||||
const analyticsRepo: Repo = {
|
||||
...repo,
|
||||
id: 'repo-analytics',
|
||||
path: '/tmp/analytics',
|
||||
displayName: 'analytics',
|
||||
upstream: { owner: 'stablyai', repo: 'analytics' }
|
||||
}
|
||||
const analyticsWorktree: Worktree = {
|
||||
...worktree,
|
||||
id: 'wt-analytics',
|
||||
repoId: analyticsRepo.id,
|
||||
displayName: 'analytics'
|
||||
}
|
||||
const analyticsSetup: ProjectHostSetup = {
|
||||
...projectHostSetups[0]!,
|
||||
id: analyticsRepo.id,
|
||||
projectId: analyticsProject.id,
|
||||
repoId: analyticsRepo.id,
|
||||
path: analyticsRepo.path,
|
||||
displayName: analyticsRepo.displayName
|
||||
}
|
||||
const repoOrder = new Map([
|
||||
[repo.id, 0],
|
||||
[remoteRepo.id, 1],
|
||||
[analyticsRepo.id, 2]
|
||||
])
|
||||
|
||||
const rows = buildRows(
|
||||
'repo',
|
||||
[worktree, analyticsWorktree, remoteWorktree],
|
||||
new Map([
|
||||
[repo.id, repo],
|
||||
[remoteRepo.id, remoteRepo],
|
||||
[analyticsRepo.id, analyticsRepo]
|
||||
]),
|
||||
null,
|
||||
new Set(),
|
||||
repoOrder,
|
||||
undefined,
|
||||
'manual',
|
||||
{},
|
||||
new Map([
|
||||
[worktree.id, worktree],
|
||||
[remoteWorktree.id, remoteWorktree],
|
||||
[analyticsWorktree.id, analyticsWorktree]
|
||||
]),
|
||||
false,
|
||||
undefined,
|
||||
[],
|
||||
new Set(),
|
||||
new Map(),
|
||||
[],
|
||||
{
|
||||
projects: [project, analyticsProject],
|
||||
projectHostSetups: [...projectHostSetups, analyticsSetup]
|
||||
}
|
||||
)
|
||||
|
||||
const headers = rows.filter((row) => row.type === 'header')
|
||||
expect(headers.map((row) => row.key)).toEqual([
|
||||
'project:github:stablyai/orca',
|
||||
'project:github:stablyai/analytics'
|
||||
])
|
||||
expect(headers[0]).toMatchObject({
|
||||
key: 'project:github:stablyai/orca',
|
||||
repo: { id: repo.id, badgeColor: repo.badgeColor }
|
||||
})
|
||||
})
|
||||
|
||||
it('splits same-host checkouts of one project into separate per-setup groups', () => {
|
||||
// Why: multiple local clones/worktrees of one repo share the GitHub slug, so
|
||||
// collapsing to the project would merge them into one arbitrarily-named group.
|
||||
|
||||
@@ -673,9 +673,39 @@ function manualRankForEntry(
|
||||
repoOrder: Map<string, number> | undefined
|
||||
): number {
|
||||
const key = entry[0]
|
||||
const repoId = key.startsWith('repo:') ? key.slice('repo:'.length) : key
|
||||
const rank = repoOrder?.get(repoId)
|
||||
return rank === undefined ? Number.POSITIVE_INFINITY : rank
|
||||
const repoIds =
|
||||
entry[1].repoIds.size > 0
|
||||
? [...entry[1].repoIds]
|
||||
: [key.startsWith('repo:') ? key.slice('repo:'.length) : key]
|
||||
let rank = Number.POSITIVE_INFINITY
|
||||
for (const repoId of repoIds) {
|
||||
const repoRank = repoOrder?.get(repoId)
|
||||
if (repoRank !== undefined && repoRank < rank) {
|
||||
rank = repoRank
|
||||
}
|
||||
}
|
||||
return rank
|
||||
}
|
||||
|
||||
function getManualOrderAnchorRepo(
|
||||
group: WorktreeGroupEntry,
|
||||
repoMap: Map<string, Repo>,
|
||||
repoOrder: Map<string, number> | undefined
|
||||
): Repo | undefined {
|
||||
let anchor = group.repo
|
||||
let anchorRank = anchor ? (repoOrder?.get(anchor.id) ?? Number.POSITIVE_INFINITY) : undefined
|
||||
for (const repoId of group.repoIds) {
|
||||
const repo = repoMap.get(repoId)
|
||||
if (!repo) {
|
||||
continue
|
||||
}
|
||||
const rank = repoOrder?.get(repoId) ?? Number.POSITIVE_INFINITY
|
||||
if (!anchor || rank < (anchorRank ?? Number.POSITIVE_INFINITY)) {
|
||||
anchor = repo
|
||||
anchorRank = rank
|
||||
}
|
||||
}
|
||||
return anchor
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -904,6 +934,12 @@ export function buildRows(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const group of grouped.values()) {
|
||||
// Why: logical project headers can contain multiple host setup repos.
|
||||
// Use the repo that anchors manual order so drag and actions target the
|
||||
// same persisted order source the row sorter reads.
|
||||
group.repo = getManualOrderAnchorRepo(group, repoMap, repoOrder)
|
||||
}
|
||||
// Why: project header order is its own user choice (projectOrderBy),
|
||||
// decoupled from workspace sortBy. Manual uses the canonical repoOrder so
|
||||
// header drag has a stable source of truth; Recent follows activity.
|
||||
|
||||
Reference in New Issue
Block a user