diff --git a/packages/ui/src/sync/__tests__/event-reducer.test.ts b/packages/ui/src/sync/__tests__/event-reducer.test.ts index bef5e324..449eb17e 100644 --- a/packages/ui/src/sync/__tests__/event-reducer.test.ts +++ b/packages/ui/src/sync/__tests__/event-reducer.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import type { Event, Part } from "@opencode-ai/sdk/v2/client" +import type { Event, Part, PermissionRequest, QuestionRequest } from "@opencode-ai/sdk/v2/client" import { applyDirectoryEvent } from "../event-reducer" import { INITIAL_STATE, type State } from "../types" @@ -86,4 +86,61 @@ describe("applyDirectoryEvent", () => { expect(draft.part.msg_1.map((item) => item.id)).toEqual(["prt_1"]) expect(result).toBe(true) }) + + test("updates permission request arrays immutably", () => { + const initialPermissions = [ + { id: "perm_1", sessionID: "ses_1" } as PermissionRequest, + ] + const draft = state({ permission: { ses_1: initialPermissions } }) + + applyDirectoryEvent(draft, { + type: "permission.asked", + properties: { id: "perm_2", sessionID: "ses_1" } as PermissionRequest, + } as Event) + + expect(draft.permission.ses_1).not.toBe(initialPermissions) + expect(draft.permission.ses_1.map((item) => item.id)).toEqual(["perm_1", "perm_2"]) + + const afterAsk = draft.permission.ses_1 + applyDirectoryEvent(draft, { + type: "permission.replied", + properties: { sessionID: "ses_1", requestID: "perm_1" }, + } as Event) + + expect(draft.permission.ses_1).not.toBe(afterAsk) + expect(draft.permission.ses_1.map((item) => item.id)).toEqual(["perm_2"]) + }) + + test("updates question request arrays immutably", () => { + const initialQuestions = [ + { id: "ques_1", sessionID: "ses_1" } as QuestionRequest, + ] + const draft = state({ question: { ses_1: initialQuestions } }) + + applyDirectoryEvent(draft, { + type: "question.asked", + properties: { id: "ques_2", sessionID: "ses_1" } as QuestionRequest, + } as Event) + + expect(draft.question.ses_1).not.toBe(initialQuestions) + expect(draft.question.ses_1.map((item) => item.id)).toEqual(["ques_1", "ques_2"]) + + const afterAsk = draft.question.ses_1 + applyDirectoryEvent(draft, { + type: "question.replied", + properties: { sessionID: "ses_1", requestID: "ques_1" }, + } as Event) + + expect(draft.question.ses_1).not.toBe(afterAsk) + expect(draft.question.ses_1.map((item) => item.id)).toEqual(["ques_2"]) + + const afterReply = draft.question.ses_1 + applyDirectoryEvent(draft, { + type: "question.rejected", + properties: { sessionID: "ses_1", requestID: "ques_2" }, + } as Event) + + expect(draft.question.ses_1).not.toBe(afterReply) + expect(draft.question.ses_1).toEqual([]) + }) }) diff --git a/packages/ui/src/sync/event-reducer.ts b/packages/ui/src/sync/event-reducer.ts index 5ef37a85..f1004d48 100644 --- a/packages/ui/src/sync/event-reducer.ts +++ b/packages/ui/src/sync/event-reducer.ts @@ -400,13 +400,14 @@ export function applyDirectoryEvent( case "permission.asked": { const permission = event.properties as PermissionRequest const permissions = draft.permission[permission.sessionID] ?? [] - draft.permission[permission.sessionID] = permissions - const result = Binary.search(permissions, permission.id, (p) => p.id) + const next = [...permissions] + const result = Binary.search(next, permission.id, (p) => p.id) if (result.found) { - permissions[result.index] = permission + next[result.index] = permission } else { - permissions.splice(result.index, 0, permission) + next.splice(result.index, 0, permission) } + draft.permission[permission.sessionID] = next return true } @@ -416,7 +417,9 @@ export function applyDirectoryEvent( if (!permissions) return false const result = Binary.search(permissions, props.requestID, (p) => p.id) if (result.found) { - permissions.splice(result.index, 1) + const next = [...permissions] + next.splice(result.index, 1) + draft.permission[props.sessionID] = next return true } return false @@ -425,13 +428,14 @@ export function applyDirectoryEvent( case "question.asked": { const question = event.properties as QuestionRequest const questions = draft.question[question.sessionID] ?? [] - draft.question[question.sessionID] = questions - const result = Binary.search(questions, question.id, (q) => q.id) + const next = [...questions] + const result = Binary.search(next, question.id, (q) => q.id) if (result.found) { - questions[result.index] = question + next[result.index] = question } else { - questions.splice(result.index, 0, question) + next.splice(result.index, 0, question) } + draft.question[question.sessionID] = next return true } @@ -442,7 +446,9 @@ export function applyDirectoryEvent( if (!questions) return false const result = Binary.search(questions, props.requestID, (q) => q.id) if (result.found) { - questions.splice(result.index, 1) + const next = [...questions] + next.splice(result.index, 1) + draft.question[props.sessionID] = next return true } return false