mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 16:03:47 +00:00
fix(store): keep setup-script dismissal array identity on no-op repo fetches
filterSetupScriptPromptDismissalsToValidRepos always allocated a new array, so fetchRepos, fetchRuntimeEnvironmentRepos, and validateRepoScopedUi replaced setupScriptPromptDismissedRepoIds even when every entry was already a valid host-identity key. SetupScriptPromptCard Object.is-subscribes to that array, so a no-op catalog refresh missed 100% of the time. Return the original store array when the filtered result is element-wise identical to the input. Allocate only when an entry is dropped, rewritten from a legacy generation-v1:repoId key, or deduped. Call sites already assign the helper result. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -202,6 +202,59 @@ describe('setup script prompt inspection', () => {
|
||||
).toEqual([getSetupScriptPromptDismissalKey(localIdentity)])
|
||||
})
|
||||
|
||||
it('reuses the input array when it is empty', () => {
|
||||
const input: string[] = []
|
||||
expect(
|
||||
filterSetupScriptPromptDismissalsToValidRepos(
|
||||
input,
|
||||
new Set([getRepoHostIdentityForParts('repo-1', 'local')])
|
||||
)
|
||||
).toBe(input)
|
||||
})
|
||||
|
||||
it('reuses the input array when every dismissal is already a valid host-identity key', () => {
|
||||
const localIdentity = getRepoHostIdentityForParts('repo-1', 'local')
|
||||
const remoteIdentity = getRepoHostIdentityForParts('repo-2', 'ssh:host-a')
|
||||
const input = [
|
||||
getSetupScriptPromptDismissalKey(localIdentity),
|
||||
getSetupScriptPromptDismissalKey(remoteIdentity)
|
||||
]
|
||||
expect(
|
||||
filterSetupScriptPromptDismissalsToValidRepos(
|
||||
input,
|
||||
new Set([localIdentity, remoteIdentity])
|
||||
)
|
||||
).toBe(input)
|
||||
})
|
||||
|
||||
it('allocates when a legacy repo-id dismissal is rewritten to a host identity', () => {
|
||||
const localIdentity = getRepoHostIdentityForParts('repo-1', 'local')
|
||||
const input = [getSetupScriptPromptDismissalKey('repo-1')]
|
||||
const result = filterSetupScriptPromptDismissalsToValidRepos(input, new Set([localIdentity]))
|
||||
expect(result).not.toBe(input)
|
||||
expect(result).toEqual([getSetupScriptPromptDismissalKey(localIdentity)])
|
||||
})
|
||||
|
||||
it('allocates when a stale dismissal is dropped', () => {
|
||||
const localIdentity = getRepoHostIdentityForParts('repo-1', 'local')
|
||||
const input = [
|
||||
getSetupScriptPromptDismissalKey(localIdentity),
|
||||
getSetupScriptPromptDismissalKey(getRepoHostIdentityForParts('gone', 'local'))
|
||||
]
|
||||
const result = filterSetupScriptPromptDismissalsToValidRepos(input, new Set([localIdentity]))
|
||||
expect(result).not.toBe(input)
|
||||
expect(result).toEqual([getSetupScriptPromptDismissalKey(localIdentity)])
|
||||
})
|
||||
|
||||
it('allocates when a duplicate valid dismissal is deduped', () => {
|
||||
const localIdentity = getRepoHostIdentityForParts('repo-1', 'local')
|
||||
const key = getSetupScriptPromptDismissalKey(localIdentity)
|
||||
const input = [key, key]
|
||||
const result = filterSetupScriptPromptDismissalsToValidRepos(input, new Set([localIdentity]))
|
||||
expect(result).not.toBe(input)
|
||||
expect(result).toEqual([key])
|
||||
})
|
||||
|
||||
it('drops a legacy repo-id dismissal when that id exists on multiple hosts', () => {
|
||||
const localIdentity = getRepoHostIdentityForParts('repo-1', 'local')
|
||||
const remoteIdentity = getRepoHostIdentityForParts('repo-1', 'runtime:windows')
|
||||
|
||||
@@ -117,6 +117,18 @@ export function filterSetupScriptPromptDismissalsToValidRepos(
|
||||
}
|
||||
}
|
||||
}
|
||||
// Why: fetchRepos / fetchRuntimeEnvironmentRepos / validateRepoScopedUi assign
|
||||
// this into set() on every catalog refresh. SetupScriptPromptCard Object.is-
|
||||
// subscribes to the array, so a fresh copy on a no-op prune is a guaranteed miss.
|
||||
// Compare against the original input, not the sanitize copy — sanitize always
|
||||
// allocates, including for [] and already-valid host-identity keys.
|
||||
if (
|
||||
Array.isArray(value) &&
|
||||
value.length === next.length &&
|
||||
value.every((entry, index) => entry === next[index])
|
||||
) {
|
||||
return value
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { Project, Repo } from '../../../../shared/types'
|
||||
import { getSetupScriptPromptDismissalKey } from '../../lib/setup-script-prompt'
|
||||
import { getRepoHostIdentityForParts } from './repo-host-identity'
|
||||
import { createTestStore } from './store-test-helpers'
|
||||
|
||||
// Why: every field here is load-bearing. A scalar-only repo reconciles even when the structural
|
||||
@@ -281,3 +283,21 @@ describe('repo filter identity across catalog refreshes', () => {
|
||||
expect(store.getState().filterRepoIds).toBe(first)
|
||||
})
|
||||
})
|
||||
|
||||
describe('setup-script dismissal identity across catalog refreshes', () => {
|
||||
it('keeps the dismissal array when a refetch prunes nothing', async () => {
|
||||
const store = createTestStore()
|
||||
store.setState({
|
||||
setupScriptPromptDismissedRepoIds: [
|
||||
getSetupScriptPromptDismissalKey(getRepoHostIdentityForParts(repo.id, 'local'))
|
||||
]
|
||||
})
|
||||
const first = store.getState().setupScriptPromptDismissedRepoIds
|
||||
|
||||
await store.getState().fetchRepos()
|
||||
|
||||
// Why: SetupScriptPromptCard Object.is-subscribes to this array. A no-op
|
||||
// catalog refresh must not allocate just because the helper rebuilt next.
|
||||
expect(store.getState().setupScriptPromptDismissedRepoIds).toBe(first)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user