mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
fix(mobile): roll the custom-keys mirror back when the store refuses (OTA phase C, C7.7 round 2)
CodeRabbit. `saveCustomKeys` notes the write in the mirror before it persists, because a reader is answered from the map rather than from the store and the shell builds `init` synchronously from that map. On a refused write the note stood: the page's next `init` carried the value native had rejected, and every native reader of the key saw it too. The previous mirrored value is captured and put back on the failure path, and the error still goes to the caller so `addKey` keeps withholding the key. Red first: with the store refusing, the mirror held the rejected value where the pre-save value belonged. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
@@ -12,7 +12,7 @@ import {
|
||||
type TerminalShortcutSpecialKey
|
||||
} from '../terminal/terminal-accessory-keys'
|
||||
import { customKeyModalStyles as styles } from './CustomKeyModal.styles'
|
||||
import { noteMirroredWrite } from '../storage/mirrored-storage-keys'
|
||||
import { noteMirroredWrite, readMirroredStorage } from '../storage/mirrored-storage-keys'
|
||||
|
||||
const CUSTOM_ACCESSORY_KEYS_STORAGE_KEY = 'orca:custom-accessory-keys'
|
||||
|
||||
@@ -79,8 +79,19 @@ export async function saveCustomKeys(keys: CustomKey[]): Promise<void> {
|
||||
const value = JSON.stringify(keys)
|
||||
// Noted before it is persisted: the hybrid shell hands this key to the page on every `init`,
|
||||
// built synchronously, so a write that only reached the store would be one `init` behind.
|
||||
const held = readMirroredStorage([CUSTOM_ACCESSORY_KEYS_STORAGE_KEY])[
|
||||
CUSTOM_ACCESSORY_KEYS_STORAGE_KEY
|
||||
]
|
||||
noteMirroredWrite(CUSTOM_ACCESSORY_KEYS_STORAGE_KEY, value)
|
||||
await AsyncStorage.setItem(CUSTOM_ACCESSORY_KEYS_STORAGE_KEY, value)
|
||||
try {
|
||||
await AsyncStorage.setItem(CUSTOM_ACCESSORY_KEYS_STORAGE_KEY, value)
|
||||
} catch (error) {
|
||||
// Rolled back, because the note is what the next `init` reads: on the page a write over the
|
||||
// cap rejects rather than dropping, and a note left standing would hand the page — and every
|
||||
// native reader answered from this map — the value the store refused.
|
||||
noteMirroredWrite(CUSTOM_ACCESSORY_KEYS_STORAGE_KEY, held ?? null)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortcuts }: Props) {
|
||||
|
||||
@@ -53,6 +53,14 @@ vi.mock('lucide-react-native', () => ({ ChevronLeft: hosts.View }))
|
||||
vi.mock('./BottomDrawer', () => ({ BottomDrawer: hosts.View }))
|
||||
|
||||
import { CustomKeyModal } from './CustomKeyModal'
|
||||
import { readMirroredStorage } from '../storage/mirrored-storage-keys'
|
||||
|
||||
const CUSTOM_KEYS = 'orca:custom-accessory-keys'
|
||||
|
||||
/** What a later `init` would carry for this key, which is the map and not the store. */
|
||||
function mirrored(): string | undefined {
|
||||
return readMirroredStorage([CUSTOM_KEYS])[CUSTOM_KEYS]
|
||||
}
|
||||
|
||||
/** The one label a node renders, flattened, without walking a fiber into a cycle. */
|
||||
function labelOf(node: { props: { children?: unknown } }): string {
|
||||
@@ -117,6 +125,7 @@ beforeEach(() => {
|
||||
describe('adding a custom key when the store refuses the write', () => {
|
||||
it('does not let the refusal escape as an unhandled rejection', async () => {
|
||||
store.refuse = true
|
||||
const before = mirrored()
|
||||
const unhandled = vi.fn()
|
||||
process.on('unhandledRejection', unhandled)
|
||||
const onKeysChanged = vi.fn()
|
||||
@@ -140,6 +149,10 @@ describe('adding a custom key when the store refuses the write', () => {
|
||||
// is the failure the allowlist exists to avoid.
|
||||
expect(onKeysChanged).not.toHaveBeenCalled()
|
||||
expect(onClose).not.toHaveBeenCalled()
|
||||
// And the mirror is back where it started. `saveCustomKeys` notes the write before it
|
||||
// persists, because a reader is answered from the map; a refused write that left the note
|
||||
// standing would put the key the store rejected into the next `init`.
|
||||
expect(mirrored()).toBe(before)
|
||||
})
|
||||
|
||||
it('reports the key and closes when the store takes it, so the case above is the refusal', async () => {
|
||||
|
||||
Reference in New Issue
Block a user