diff --git a/app/components/form/fields/SshKeysField.tsx b/app/components/form/fields/SshKeysField.tsx
index e03d4d2c7..390ee4c39 100644
--- a/app/components/form/fields/SshKeysField.tsx
+++ b/app/components/form/fields/SshKeysField.tsx
@@ -20,6 +20,7 @@ import { EmptyMessage } from '~/ui/lib/EmptyMessage'
import { FieldLabel } from '~/ui/lib/FieldLabel'
import { Message } from '~/ui/lib/Message'
import { TextInputHint } from '~/ui/lib/TextInput'
+import { isSubset } from '~/util/array'
import { CheckboxField } from './CheckboxField'
import { ErrorMessage } from './ErrorMessage'
@@ -73,7 +74,16 @@ export function SshKeysField({
},
})
- const allAreSelected = allKeys.length === selectedKeys.length
+ // do this with a subset instead of just counting the items because there's a
+ // weird window right after adding but before the invalidate has gone through
+ // where you can have the new one selected but it's not in the list of all
+ // keys, which can cause the counts to match even though the sets don't
+ const allAreSelected =
+ allKeys.length > 0 &&
+ isSubset(
+ allKeys.map((k) => k.id),
+ new Set(selectedKeys)
+ )
return (
diff --git a/app/util/array.spec.tsx b/app/util/array.spec.tsx
index 34dfcb6c0..3e0dd99ed 100644
--- a/app/util/array.spec.tsx
+++ b/app/util/array.spec.tsx
@@ -8,7 +8,14 @@
import type { JSX, ReactElement } from 'react'
import { expect, test } from 'vitest'
-import { groupBy, intersperse, isSetEqual, setDiff, setIntersection } from './array'
+import {
+ groupBy,
+ intersperse,
+ isSetEqual,
+ isSubset,
+ setDiff,
+ setIntersection,
+} from './array'
test('groupBy', () => {
expect(
@@ -73,6 +80,17 @@ test('isSetEqual', () => {
expect(isSetEqual(new Set([{}]), new Set([{}]))).toBe(false)
})
+test('isSubset', () => {
+ expect(isSubset(new Set(), new Set())).toBe(true)
+ expect(isSubset(new Set(), new Set(['a']))).toBe(true)
+ expect(isSubset(new Set(['a']), new Set(['a', 'b']))).toBe(true)
+ expect(isSubset(new Set(['a', 'b']), new Set(['a', 'b']))).toBe(true)
+
+ expect(isSubset(new Set(['a']), new Set())).toBe(false)
+ expect(isSubset(new Set(['a', 'b']), new Set(['a']))).toBe(false)
+ expect(isSubset(new Set(['c']), new Set(['a', 'b']))).toBe(false)
+})
+
test('setDiff', () => {
expect(setDiff(new Set(), new Set())).toEqual(new Set())
expect(setDiff(new Set(['a']), new Set())).toEqual(new Set(['a']))
diff --git a/app/util/array.ts b/app/util/array.ts
index 004ef00e2..c07c48849 100644
--- a/app/util/array.ts
+++ b/app/util/array.ts
@@ -49,6 +49,14 @@ export function isSetEqual(a: Set, b: Set): boolean {
return true
}
+/** Is `a ⊆ b`? */
+export function isSubset(a: Iterable, b: ReadonlySet): boolean {
+ for (const item of a) {
+ if (!b.has(item)) return false
+ }
+ return true
+}
+
/** Set `a - b` */
export function setDiff(a: ReadonlySet, b: ReadonlySet): Set {
return new Set([...a].filter((x) => !b.has(x)))