From 93a814031b8e60cf7d2395129e55c1b2ed059b9d Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Wed, 3 Jun 2026 09:26:09 +0800 Subject: [PATCH] refactor(app): extract prompt-input commands+mode into commands-mode.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice 2 of the prompt-input.tsx slimming line. Pure extraction, no behavior/DOM/aria/copy/storage-key change. Moves the file-pick action, mode switch, and the command.register( "prompt-input", ...) registration into createPromptCommandsAndMode(deps), returning only { pick } (setMode and the mode keybinds have no external consumers). The factory is invoked synchronously at the cluster's original position so command.register's onCleanup stays bound to the component owner and registration order is unchanged. addPickedPaths is produced by createPromptAttachments later in the file, so it is injected as the accessor thunk () => addPickedPaths and resolved as addPickedPaths() inside pick — preserving the pre-existing forward-reference closure timing. editorRef and the file-input click are injected as getters/ callbacks. The only textual changes are those three injection seams. prompt-input.tsx 637 -> 601. --- packages/app/src/components/prompt-input.tsx | 62 +++----------- .../components/prompt-input/commands-mode.ts | 84 +++++++++++++++++++ 2 files changed, 97 insertions(+), 49 deletions(-) create mode 100644 packages/app/src/components/prompt-input/commands-mode.ts diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 6fab945ff..d2faa1893 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -20,7 +20,7 @@ import { SendButton } from "./prompt-input/send-button" import { useCommand } from "@/context/command" import { usePermission } from "@/context/permission" import { useLanguage } from "@/context/language" -import { canUseNativeFilePicker, usePlatform } from "@/context/platform" +import { usePlatform } from "@/context/platform" import { useSessionLayout } from "@/pages/session/session-layout" import { setCursorPosition } from "./prompt-input/editor-dom" import { createEditorImperatives } from "./prompt-input/editor-imperatives" @@ -34,10 +34,10 @@ import { import { createPromptKeydownHandler } from "./prompt-input/keydown" import { PromptModelControl } from "./prompt-input/model-controls" import { createPromptAttachments } from "./prompt-input/attachments" -import { pickAttachments } from "./prompt-input/pick-attachments" import { ACCEPTED_FILE_TYPES } from "./prompt-input/files" import { promptLength } from "./prompt-input/history" import { createPromptDerivedState } from "./prompt-input/derived-state" +import { createPromptCommandsAndMode } from "./prompt-input/commands-mode" import type { PromptStore } from "./prompt-input/store-types" import type { FollowupDraft } from "./prompt-input/followup-draft" import { createPromptSubmit } from "./prompt-input/submit" @@ -186,53 +186,17 @@ export const PromptInput: Component = (props) => { ), ) - const pick = () => { - if (!actionReady()) return - const openFilePickerDialog = platform.openFilePickerDialog - void pickAttachments({ - openFilePickerDialog: canUseNativeFilePicker(platform) ? openFilePickerDialog : undefined, - addPickedPaths, - fallbackInputClick: () => fileInputRef?.click(), - isReady: actionReady, - }) - } - - const setMode = (mode: "normal" | "shell") => { - if (!actionReady()) return - setStore("mode", mode) - setStore("popover", null) - requestAnimationFrame(() => editorRef?.focus()) - } - - const shellModeKey = "mod+shift+x" - const normalModeKey = "mod+shift+e" - - command.register("prompt-input", () => [ - { - id: "file.attach", - title: language.t("prompt.action.attachFile"), - category: language.t("command.category.file"), - keybind: "mod+u", - disabled: store.mode !== "normal" || !actionReady(), - onSelect: pick, - }, - { - id: "prompt.mode.shell", - title: language.t("command.prompt.mode.shell"), - category: language.t("command.category.session"), - keybind: shellModeKey, - disabled: store.mode === "shell" || !actionReady(), - onSelect: () => setMode("shell"), - }, - { - id: "prompt.mode.normal", - title: language.t("command.prompt.mode.normal"), - category: language.t("command.category.session"), - keybind: normalModeKey, - disabled: store.mode === "normal" || !actionReady(), - onSelect: () => setMode("normal"), - }, - ]) + const { pick } = createPromptCommandsAndMode({ + command, + language, + platform, + store, + setStore, + actionReady, + addPickedPaths: () => addPickedPaths, + editorRef: () => editorRef, + fallbackInputClick: () => fileInputRef?.click(), + }) const closePopover = () => setStore("popover", null) diff --git a/packages/app/src/components/prompt-input/commands-mode.ts b/packages/app/src/components/prompt-input/commands-mode.ts new file mode 100644 index 000000000..bfa6a3839 --- /dev/null +++ b/packages/app/src/components/prompt-input/commands-mode.ts @@ -0,0 +1,84 @@ +// Owns the prompt-input file-pick action, mode switch, and command registration. +// Extracted from prompt-input.tsx. The factory must be called synchronously inside +// the component owner so command.register's onCleanup is scoped correctly. + +import type { Accessor } from "solid-js" +import type { SetStoreFunction } from "solid-js/store" +import { canUseNativeFilePicker, type usePlatform } from "@/context/platform" +import type { useCommand } from "@/context/command" +import type { useLanguage } from "@/context/language" +import { pickAttachments } from "./pick-attachments" +import type { PromptStore } from "./store-types" + +export interface PromptCommandsAndModeDeps { + command: ReturnType + language: ReturnType + platform: ReturnType + store: PromptStore + setStore: SetStoreFunction + actionReady: Accessor + // Late-bound: addPickedPaths is produced by createPromptAttachments after this + // factory is constructed, so it is injected as an accessor resolved at call time. + addPickedPaths: () => (paths: string[]) => Promise + editorRef: () => HTMLDivElement | undefined + fallbackInputClick: () => void +} + +export interface PromptCommandsAndMode { + pick: () => void +} + +export function createPromptCommandsAndMode(deps: PromptCommandsAndModeDeps): PromptCommandsAndMode { + const { command, language, platform, store, setStore, actionReady, addPickedPaths, editorRef, fallbackInputClick } = + deps + + const pick = () => { + if (!actionReady()) return + const openFilePickerDialog = platform.openFilePickerDialog + void pickAttachments({ + openFilePickerDialog: canUseNativeFilePicker(platform) ? openFilePickerDialog : undefined, + addPickedPaths: addPickedPaths(), + fallbackInputClick, + isReady: actionReady, + }) + } + + const setMode = (mode: "normal" | "shell") => { + if (!actionReady()) return + setStore("mode", mode) + setStore("popover", null) + requestAnimationFrame(() => editorRef()?.focus()) + } + + const shellModeKey = "mod+shift+x" + const normalModeKey = "mod+shift+e" + + command.register("prompt-input", () => [ + { + id: "file.attach", + title: language.t("prompt.action.attachFile"), + category: language.t("command.category.file"), + keybind: "mod+u", + disabled: store.mode !== "normal" || !actionReady(), + onSelect: pick, + }, + { + id: "prompt.mode.shell", + title: language.t("command.prompt.mode.shell"), + category: language.t("command.category.session"), + keybind: shellModeKey, + disabled: store.mode === "shell" || !actionReady(), + onSelect: () => setMode("shell"), + }, + { + id: "prompt.mode.normal", + title: language.t("command.prompt.mode.normal"), + category: language.t("command.category.session"), + keybind: normalModeKey, + disabled: store.mode === "normal" || !actionReady(), + onSelect: () => setMode("normal"), + }, + ]) + + return { pick } +}