From 409245cb52628888fcb8cf1887f98e07090d36fe Mon Sep 17 00:00:00 2001 From: Hare Date: Tue, 1 Sep 2026 21:00:26 +0900 Subject: [PATCH] fix: fence disabled Composer mutations --- .../workspace/console/ComposerInput.svelte | 14 ++++++++++++- .../console/worker-console.ui.test.ts | 4 ++++ .../workers/[workerId]/console/+page.svelte | 20 +++++++++++++++---- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/web/workspace/src/lib/workspace/console/ComposerInput.svelte b/web/workspace/src/lib/workspace/console/ComposerInput.svelte index 6d0b0321..36672a8c 100644 --- a/web/workspace/src/lib/workspace/console/ComposerInput.svelte +++ b/web/workspace/src/lib/workspace/console/ComposerInput.svelte @@ -280,6 +280,18 @@ extensions: [ history(), Prec.highest(keymap.of([ + { + key: "Mod-z", + run: (currentView) => currentView.state.readOnly, + }, + { + key: "Mod-Shift-z", + run: (currentView) => currentView.state.readOnly, + }, + { + key: "Mod-y", + run: (currentView) => currentView.state.readOnly, + }, { key: "Backspace", run: (currentView) => @@ -397,7 +409,7 @@ } export function replaceRange(from: number, to: number, content: string): void { - if (!view) return; + if (!view || view.state.readOnly) return; view.dispatch({ changes: { from, to, insert: content }, selection: EditorSelection.cursor(from + content.length), diff --git a/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts b/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts index eac18d19..9959e841 100644 --- a/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts +++ b/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts @@ -608,7 +608,11 @@ Deno.test("Worker Console paste chips preserve typed draft and target authority" "composerDeletionRange(selection, pastes, direction)", ) && composerInput.includes("EditorState.readOnly.of(isDisabled)") && + composerInput.includes('key: "Mod-z"') && + composerInput.includes("if (!view || view.state.readOnly) return") && composerInput.includes("if (currentView.state.readOnly) return false") && + consolePage.includes("activeComposerTargetKey !== targetKey") && + consolePage.includes("if (!composerEditable) return") && composerInput.includes('chip.setAttribute("aria-label", label)') && composerInput.includes("preserveExactText = false") && consolePage.includes("buildComposerSegmentsRequest(value.segments, {") && diff --git a/web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte index fb1018ea..d8d9cdf4 100644 --- a/web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte +++ b/web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte @@ -353,10 +353,13 @@ } async function applyComposerCompletion() { - if (!composerInputElement) return; + if (!composerEditable || !composerInputElement) return; + const input = composerInputElement; + const targetKey = activeComposerTargetKey; + const document = draft.document; const token = completionTokenAt( - draft.document, - composerInputElement.cursor(), + document, + input.cursor(), ); completionToken = token; completionError = null; @@ -368,12 +371,20 @@ completionBusy = true; try { const entries = await resolveCompletionEntries(token); + if ( + !composerEditable || + composerInputElement !== input || + activeComposerTargetKey !== targetKey || + draft.document !== document + ) { + return; + } completionEntries = entries; if (entries.length === 0) { completionError = `No completions for ${token.sigil}${token.prefix}`; return; } - composerInputElement.replaceRange( + input.replaceRange( token.start, token.end, `${entries[0].value} `, @@ -433,6 +444,7 @@ return; } event.preventDefault(); + if (!composerEditable) return; void applyComposerCompletion(); }