From 338d73f09240504e4dff9b76d86408ecbb6b56c7 Mon Sep 17 00:00:00 2001 From: Serhii Vecherenko Date: Thu, 30 Jul 2026 14:02:14 -0700 Subject: [PATCH] feat(search): unify command palette with project-wide search - Replace thread search with commands, settings, threads, and file results - Add indexed project file search, focus handling, shortcuts, analytics, and smoke coverage --- .../scripts/poracode-integration-smoke.mjs | 279 ++++++++++- .../scripts/seed-poracode-smoke-db.mjs | 4 +- .../scripts/smoke-scenarios.mjs | 9 +- src/main/keybindingsFile.test.ts | 34 ++ src/main/keybindingsFile.ts | 19 +- src/renderer/analytics/posthog.ts | 12 +- src/renderer/app.tsx | 20 +- src/renderer/commands/CommandPalette.tsx | 473 +++++++++++++----- .../commands/EverythingSearchResultRow.tsx | 141 ++++++ .../commands/EverythingSearchResults.tsx | 143 ++++++ .../commands/commandPaletteStore.test.ts | 32 ++ src/renderer/commands/commandPaletteStore.ts | 11 +- .../commands/defaultKeybindings.test.ts | 99 +++- .../commands/everythingSearch.test.ts | 101 ++++ src/renderer/commands/everythingSearch.ts | 77 +++ src/renderer/commands/focusedSurface.ts | 5 + src/renderer/commands/registry.ts | 60 ++- src/renderer/commands/shortcutCatalog.test.ts | 7 +- .../commands/useEverythingFileSearch.ts | 88 ++++ .../components/find/findController.test.ts | 21 +- .../components/find/findController.ts | 15 +- src/renderer/devBridge.ts | 2 + src/renderer/locales/de/messages.po | 52 +- src/renderer/locales/en/messages.po | 52 +- src/renderer/locales/es/messages.po | 52 +- src/renderer/locales/fr/messages.po | 52 +- src/renderer/locales/ja/messages.po | 52 +- src/renderer/locales/ko/messages.po | 52 +- src/renderer/locales/pl/messages.po | 52 +- src/renderer/locales/pt-BR/messages.po | 52 +- src/renderer/locales/ru/messages.po | 52 +- src/renderer/locales/tr/messages.po | 52 +- src/renderer/locales/uk/messages.po | 52 +- src/renderer/locales/vi/messages.po | 52 +- src/renderer/locales/zh-CN/messages.po | 52 +- src/renderer/state/panelStore.test.ts | 7 +- src/renderer/state/panelStore.ts | 29 +- src/renderer/views/MainView/MainView.tsx | 22 +- .../views/MainView/parts/Sidebar/Sidebar.tsx | 9 +- .../MainView/parts/SidebarHeaderControls.tsx | 3 +- .../SettingsOverlay/SettingsOverlay.test.tsx | 47 +- .../views/SettingsOverlay/SettingsOverlay.tsx | 32 +- .../parts/settingsSearchIndex.test.ts | 6 + .../parts/settingsSearchIndex.ts | 26 +- .../ThreadSearchOverlay.tsx | 161 ------ .../parts/ThreadSearchResultRow.tsx | 54 -- src/shared/analytics/posthogPrivacy.ts | 2 +- src/shared/contracts/projectTree.ts | 1 + src/shared/keybindings.test.ts | 7 +- src/shared/keybindings.ts | 2 +- src/supervisor/ProjectSearchIndex.test.ts | 117 +++++ src/supervisor/ProjectSearchIndex.ts | 38 +- 52 files changed, 2191 insertions(+), 700 deletions(-) create mode 100644 src/renderer/commands/EverythingSearchResultRow.tsx create mode 100644 src/renderer/commands/EverythingSearchResults.tsx create mode 100644 src/renderer/commands/commandPaletteStore.test.ts create mode 100644 src/renderer/commands/everythingSearch.test.ts create mode 100644 src/renderer/commands/everythingSearch.ts create mode 100644 src/renderer/commands/useEverythingFileSearch.ts delete mode 100644 src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx delete mode 100644 src/renderer/views/ThreadSearchOverlay/parts/ThreadSearchResultRow.tsx create mode 100644 src/supervisor/ProjectSearchIndex.test.ts diff --git a/.agents/skills/interactive-testing/scripts/poracode-integration-smoke.mjs b/.agents/skills/interactive-testing/scripts/poracode-integration-smoke.mjs index 4976f26eb..ebc0b4705 100644 --- a/.agents/skills/interactive-testing/scripts/poracode-integration-smoke.mjs +++ b/.agents/skills/interactive-testing/scripts/poracode-integration-smoke.mjs @@ -177,8 +177,8 @@ async function runSmoke(plan) { if (plan.automated.includes("schedules")) { await runScenario(report, "schedules", () => schedulesScenario(client)); } - if (plan.automated.includes("thread-search")) { - await runScenario(report, "thread-search", () => threadSearchScenario(client)); + if (plan.automated.includes("everything-search")) { + await runScenario(report, "everything-search", () => everythingSearchScenario(client)); } if (plan.automated.includes("browser")) { await runScenario(report, "browser", () => browserScenario(client)); @@ -1105,31 +1105,276 @@ function isPillGeometry(geometry) { return geometry !== null && geometry.radius >= geometry.height / 2; } -async function threadSearchScenario(client) { +async function everythingSearchScenario(client) { await evaluate( client, - `window.__poracodeDev.stores.panel.setState({ threadSearchOpen: true }); new Promise((resolve) => setTimeout(resolve, 80))`, + `(() => { + const dev = window.__poracodeDev; + const app = dev.stores.app.getState(); + const project = app.projects.find((candidate) => candidate.id === "smoke-project"); + if (project) app.openDraft(project.id); + const target = document.activeElement instanceof HTMLElement ? document.activeElement : document.body; + target.dispatchEvent( + new KeyboardEvent("keydown", { + key: "k", + ctrlKey: window.poracode.platform !== "darwin", + metaKey: window.poracode.platform === "darwin", + bubbles: true, + cancelable: true, + }), + ); + return new Promise((resolve) => setTimeout(resolve, 120)); + })()`, true, ); const state = await waitForValue( () => evaluate( client, - `(() => ({ - dialog: Boolean(document.querySelector('[role="dialog"]')), - searchInput: Boolean(document.querySelector('input[placeholder]')), - crash: /renderer crash|rendered more hooks/i.test(document.body.innerText), - }))()`, + `(() => { + const dialog = [...document.querySelectorAll('[role="dialog"]')].find((candidate) => + candidate.querySelector('input[aria-label="Search"]'), + ); + return { + dialog: Boolean(dialog), + searchInput: Boolean(dialog?.querySelector('input[aria-label="Search"]')), + modal: + dialog?.getAttribute("data-slot") === "modal-dialog" && + Boolean(dialog.closest('[data-slot="modal-container"]')) && + Boolean(document.querySelector('[data-slot="modal-backdrop"]')), + combobox: + dialog?.querySelector('input[aria-label="Search"]')?.getAttribute("role") === + "combobox", + categories: [...(dialog?.querySelectorAll('button[aria-pressed]') ?? [])].map((button) => + button.textContent?.trim(), + ), + optionCount: dialog?.querySelectorAll('[role="option"]').length ?? 0, + activeDescendant: + dialog?.querySelector('input[aria-label="Search"]')?.getAttribute( + "aria-activedescendant", + ) ?? null, + activeExists: Boolean( + document.getElementById( + dialog?.querySelector('input[aria-label="Search"]')?.getAttribute( + "aria-activedescendant", + ) ?? "", + ), + ), + text: dialog?.textContent ?? "", + crash: /renderer crash|rendered more hooks/i.test(document.body.innerText), + }; + })()`, ), - (candidate) => candidate.dialog && candidate.searchInput, - "thread search overlay", + (candidate) => candidate.dialog && candidate.searchInput && candidate.optionCount > 0, + "everything search overlay", + ); + const expectedCategories = ["All", "Threads", "Commands", "Settings", "Files", "Actions"]; + assert(state.dialog && state.searchInput, "everything search overlay did not render"); + assert(state.modal && state.combobox, "everything search modal semantics are missing"); + assert( + state.activeDescendant && state.activeExists, + "everything search active result is missing", + ); + assert( + expectedCategories.every((category) => state.categories.includes(category)), + `everything search categories missing: ${state.categories.join(", ")}`, + ); + assert(state.text.includes("Smoke check"), "project action did not appear in everything search"); + assert(!state.crash, "everything search rendered a crash screen"); + + const arrowSent = await evaluate( + client, + `(() => { + const input = document.querySelector('input[aria-label="Search"]'); + input?.dispatchEvent( + new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true, cancelable: true }), + ); + return Boolean(input); + })()`, ); - assert(state.dialog && state.searchInput, "thread search overlay did not render"); - assert(!state.crash, "thread search rendered a crash screen"); - const screenshotPath = join(outDir, "smoke-03-thread-search.png"); + assert(arrowSent, "everything search input was unavailable for keyboard selection"); + const keyboardSelection = await waitForValue( + () => + evaluate( + client, + `(() => { + const input = document.querySelector('input[aria-label="Search"]'); + const active = input?.getAttribute("aria-activedescendant") ?? null; + return { active, activeExists: Boolean(active && document.getElementById(active)) }; + })()`, + ), + (candidate) => candidate.activeExists && candidate.active === "everything-search-result-1", + "everything search keyboard selection", + ); + assert(keyboardSelection.activeExists, "keyboard selection did not identify its active result"); + + const fileResult = await waitForValue( + () => + evaluate( + client, + `(() => { + const dialog = [...document.querySelectorAll('[role="dialog"]')].find((candidate) => + candidate.querySelector('input[aria-label="Search"]'), + ); + const input = dialog?.querySelector('input[aria-label="Search"]'); + if (input && input.value !== "README") { + Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value").set.call( + input, + "README", + ); + input.dispatchEvent(new Event("input", { bubbles: true })); + } + return { + path: [...(dialog?.querySelectorAll('[role="option"]') ?? [])] + .map((option) => option.textContent ?? "") + .find((text) => text.includes("README.md")) ?? null, + }; + })()`, + ), + (candidate) => candidate.path !== null, + "everything search file result", + ); + assert( + fileResult.path?.includes("README.md"), + "project file did not appear in everything search", + ); + + const screenshotPath = join(outDir, "smoke-03-everything-search.png"); await screenshot(client, screenshotPath); - await evaluate(client, "window.__poracodeDev.stores.panel.setState({ threadSearchOpen: false })"); - return { ...state, screenshotPath }; + + await evaluate( + client, + `(() => { + const dialog = [...document.querySelectorAll('[role="dialog"]')].find((candidate) => + candidate.querySelector('input[aria-label="Search"]'), + ); + const settings = [...(dialog?.querySelectorAll('button[aria-pressed]') ?? [])].find( + (button) => button.textContent?.trim() === "Settings", + ); + settings?.click(); + const input = dialog?.querySelector('input[aria-label="Search"]'); + if (input) { + Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value").set.call( + input, + "Match your system", + ); + input.dispatchEvent(new Event("input", { bubbles: true })); + } + })()`, + ); + const settingResult = await waitForValue( + () => + evaluate( + client, + `(() => { + const dialog = [...document.querySelectorAll('[role="dialog"]')].find((candidate) => + candidate.querySelector('input[aria-label="Search"]'), + ); + const option = [...(dialog?.querySelectorAll('[role="option"]') ?? [])].find((candidate) => + candidate.textContent?.includes("Mode"), + ); + return { found: Boolean(option) }; + })()`, + ), + (candidate) => candidate.found, + "everything search setting result", + ); + assert(settingResult.found, "setting did not appear in everything search"); + await evaluate( + client, + `(() => { + const dialog = [...document.querySelectorAll('[role="dialog"]')].find((candidate) => + candidate.querySelector('input[aria-label="Search"]'), + ); + const option = [...(dialog?.querySelectorAll('[role="option"]') ?? [])].find((candidate) => + candidate.textContent?.includes("Mode"), + ); + option?.click(); + })()`, + ); + const deepLink = await waitForValue( + () => + evaluate( + client, + `(() => { + const target = document.querySelector('[data-settings-anchor="appearance.mode"]'); + return { + settingsOpen: window.__poracodeDev.stores.panel.getState().settingsOpen, + target: Boolean(target), + highlighted: target?.classList.contains("poracode-setting-highlight") ?? false, + }; + })()`, + ), + (candidate) => candidate.settingsOpen && candidate.target && candidate.highlighted, + "everything search settings deep link", + ); + assert( + deepLink.target && deepLink.highlighted, + "setting result did not scroll to and highlight its setting row", + ); + await evaluate(client, "window.__poracodeDev.closeSettings()"); + await evaluate( + client, + `document.body.dispatchEvent( + new KeyboardEvent("keydown", { + key: "g", + ctrlKey: window.poracode.platform !== "darwin", + metaKey: window.poracode.platform === "darwin", + bubbles: true, + cancelable: true, + }), + )`, + ); + const reopened = await waitForValue( + () => + evaluate( + client, + `(() => { + const dialog = [...document.querySelectorAll('[role="dialog"]')].find((candidate) => + candidate.querySelector('input[aria-label="Search"]'), + ); + return { + open: window.__poracodeDev.stores.commandPalette.getState().isOpen, + category: Boolean(dialog?.querySelector('button[aria-pressed]')), + }; + })()`, + ), + (candidate) => candidate.open && candidate.category, + "everything search Ctrl+G shortcut", + ); + assert(reopened.open && reopened.category, "Ctrl+G did not reopen everything search"); + const escapeSent = await evaluate( + client, + `(() => { + const dialog = [...document.querySelectorAll('[role="dialog"]')].find((candidate) => + candidate.querySelector('input[aria-label="Search"]'), + ); + const category = dialog?.querySelector('button[aria-pressed]'); + category?.focus(); + category?.dispatchEvent( + new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true }), + ); + return Boolean(category); + })()`, + ); + assert(escapeSent, "everything search category was unavailable for Escape dismissal"); + const escapeClosed = await waitForValue( + () => + evaluate( + client, + `({ closed: !window.__poracodeDev.stores.commandPalette.getState().isOpen })`, + ), + (candidate) => candidate.closed, + "everything search escape dismissal", + ); + return { + ...state, + keyboardSelection, + fileResult: fileResult.path, + deepLink, + escapeClosed, + screenshotPath, + }; } async function browserScenario(client) { @@ -1489,7 +1734,7 @@ async function resetDrivenState(client) { client, `(() => { window.__poracodeDev?.closeSettings(); - window.__poracodeDev?.stores?.panel?.setState({ threadSearchOpen: false }); + window.__poracodeDev?.stores?.commandPalette?.getState().close(); window.__poracodeDev?.reset(); })()`, ); diff --git a/.agents/skills/interactive-testing/scripts/seed-poracode-smoke-db.mjs b/.agents/skills/interactive-testing/scripts/seed-poracode-smoke-db.mjs index aee9a0b64..02075b460 100644 --- a/.agents/skills/interactive-testing/scripts/seed-poracode-smoke-db.mjs +++ b/.agents/skills/interactive-testing/scripts/seed-poracode-smoke-db.mjs @@ -212,7 +212,9 @@ try { locationDistro: projectLocation.kind === "wsl" ? projectLocation.distro : null, locationLinuxPath: projectLocation.kind === "wsl" ? projectLocation.linuxPath : null, locationUncPath: projectLocation.kind === "wsl" ? projectLocation.uncPath : null, - scripts: JSON.stringify({ actions: [] }), + scripts: JSON.stringify({ + actions: [{ id: "smoke-check", name: "Smoke check", command: "echo smoke" }], + }), createdAt, }); setState.run({ diff --git a/.agents/skills/interactive-testing/scripts/smoke-scenarios.mjs b/.agents/skills/interactive-testing/scripts/smoke-scenarios.mjs index 308dd040b..ddb8001a0 100644 --- a/.agents/skills/interactive-testing/scripts/smoke-scenarios.mjs +++ b/.agents/skills/interactive-testing/scripts/smoke-scenarios.mjs @@ -42,9 +42,16 @@ export const functionalAreas = [ id: "threads-chat", title: "Thread draft, composer, chat, history, and runtime requests", patterns: [/thread/i, /ChatPane/, /composer/i, /runtimeEvent/i, /session/i], - automated: ["baseline", "thread-search"], + automated: ["baseline", "everything-search"], manual: ["provider-live", "runtime-requests"], }, + { + id: "everything-search", + title: "Global search across threads, commands, settings, files, and actions", + patterns: [/CommandPalette/, /EverythingSearch/, /everythingSearch/, /commandPaletteStore/], + automated: ["everything-search"], + manual: [], + }, { id: "terminal-pty", title: "Terminal presentation and PTY lifecycle", diff --git a/src/main/keybindingsFile.test.ts b/src/main/keybindingsFile.test.ts index c51a52cda..a3de44628 100644 --- a/src/main/keybindingsFile.test.ts +++ b/src/main/keybindingsFile.test.ts @@ -271,6 +271,40 @@ describe("readKeybindingsFile", () => { ]); }); + it("moves legacy thread-search bindings to the unified Search command", () => { + tempDir = mkdtempSync(join(tmpdir(), "poracode-keybindings-")); + const path = join(tempDir, "keybindings.json"); + writeFileSync( + path, + `${JSON.stringify({ + version: 1, + keybindings: [ + { command: "palette.open", key: "Ctrl+K", mac: "Meta+K" }, + { + command: "thread.search.open", + key: "Ctrl+Alt+G", + mac: "Meta+Alt+G", + when: "notTyping", + }, + ], + })}\n`, + "utf8", + ); + + const result = readKeybindingsFile(path).file; + + expect(result.keybindings.some((binding) => binding.command === "thread.search.open")).toBe( + false, + ); + expect(result.keybindings).toContainEqual({ + command: "palette.open", + key: "Ctrl+Alt+G", + mac: "Meta+Alt+G", + when: "notTyping", + }); + expect(readFileSync(path, "utf8")).toEqual(`${JSON.stringify(result, null, 2)}\n`); + }); + it("does not override a customized composer binding", () => { tempDir = mkdtempSync(join(tmpdir(), "poracode-keybindings-")); const path = join(tempDir, "keybindings.json"); diff --git a/src/main/keybindingsFile.ts b/src/main/keybindingsFile.ts index e64ede836..1ebf88e8d 100644 --- a/src/main/keybindingsFile.ts +++ b/src/main/keybindingsFile.ts @@ -18,7 +18,9 @@ export function readKeybindingsFile(keybindingsPath: string): KeybindingsConfig const raw = readFileSync(keybindingsPath, "utf8"); const parsed = keybindingsFileSchema.parse(JSON.parse(raw)); - const migrated = migrateToggleFastOffFind(backfillNewDefaults(parsed)); + const migrated = migrateThreadSearchCommand( + migrateToggleFastOffFind(backfillNewDefaults(parsed)), + ); if (migrated !== parsed) { writeFileAtomic(keybindingsPath, `${JSON.stringify(migrated, null, 2)}\n`, { encoding: "utf8", @@ -70,6 +72,21 @@ function migrateToggleFastOffFind(file: KeybindingsFile): KeybindingsFile { return changed ? { ...file, keybindings } : file; } +/** + * The former thread-only search now opens the unified Search palette. Rekey + * every legacy entry so existing custom chords remain editable on the visible + * `palette.open` shortcut instead of becoming hidden bindings. + */ +function migrateThreadSearchCommand(file: KeybindingsFile): KeybindingsFile { + let changed = false; + const keybindings = file.keybindings.map((binding) => { + if (binding.command !== "thread.search.open") return binding; + changed = true; + return { ...binding, command: "palette.open" }; + }); + return changed ? { ...file, keybindings } : file; +} + export function writeKeybindingsFile( keybindingsPath: string, file: KeybindingsFile, diff --git a/src/renderer/analytics/posthog.ts b/src/renderer/analytics/posthog.ts index 0334875e4..ffd67896e 100644 --- a/src/renderer/analytics/posthog.ts +++ b/src/renderer/analytics/posthog.ts @@ -11,6 +11,7 @@ import { useFileEditorStore } from "@/renderer/state/fileEditorStore"; import { usePanelStore } from "@/renderer/state/panelStore"; import { useSharedSettings } from "@/renderer/state/sharedSettingsStore"; import { useSidebarOverlayStore } from "@/renderer/state/sidebarOverlayStore"; +import { useCommandPaletteStore } from "@/renderer/commands/commandPaletteStore"; import { captureProductEvent, configureProductAnalytics, @@ -176,13 +177,18 @@ function installStoreSubscriptions(): () => void { if (state.settingsOpen && !prevState.settingsOpen) { captureProductEvent("settings.opened"); } - if (state.threadSearchOpen !== prevState.threadSearchOpen) { - captureProductEvent("ui.thread_search_toggled", { open: state.threadSearchOpen }); - } captureRightPanelChange(); }), ); + disposers.push( + useCommandPaletteStore.subscribe((state, prevState) => { + if (state.isOpen !== prevState.isOpen) { + captureProductEvent("ui.everything_search_toggled", { open: state.isOpen }); + } + }), + ); + disposers.push( useDevTerminalStore.subscribe(() => { captureRightPanelChange(); diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index 80812d195..d01247c85 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -1,7 +1,7 @@ import { toast } from "@heroui/react"; import { msg as linguiMsg } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; -import { Suspense, useEffect, useState } from "react"; +import { useEffect } from "react"; import { PixelLoader } from "./components/common/PixelLoader"; import { msg } from "@/shared/messages"; import type { RuntimeEvent } from "@/shared/contracts"; @@ -50,12 +50,10 @@ import { runWorktreeSetupScript, startThreadFromDraft, } from "@/renderer/views/MainView/parts/AppContent/AppContent"; -import { useCommandPaletteStore } from "@/renderer/commands/commandPaletteStore"; import { BrowserPanel } from "@/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/BrowserPanel"; import { useBrowserSync } from "@/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/hooks/useBrowserSync"; import { captureAppStarted, installProductAnalytics } from "@/renderer/analytics/posthog"; import { flushProductAnalytics } from "@/renderer/analytics/productAnalytics"; -import { DeferredCommandPalette as PrewarmedCommandPalette } from "@/renderer/deferredFeatures"; // ── Module-level IPC listeners ────────────────────────────────── // Subscribes to supervisor events as soon as the module loads, @@ -519,23 +517,7 @@ function MainApp() { return ( - ); } - -function DeferredCommandPalette() { - const open = useCommandPaletteStore((state) => state.isOpen); - const [enabled, setEnabled] = useState(open); - - useEffect(() => { - if (open) setEnabled(true); - }, [open]); - - return enabled ? ( - - - - ) : null; -} diff --git a/src/renderer/commands/CommandPalette.tsx b/src/renderer/commands/CommandPalette.tsx index cc6145620..28a283522 100644 --- a/src/renderer/commands/CommandPalette.tsx +++ b/src/renderer/commands/CommandPalette.tsx @@ -1,12 +1,38 @@ -import { useEffect, useRef, useState } from "react"; -import { Input, Label, Modal } from "@heroui/react"; -import { Trans, useLingui } from "@lingui/react/macro"; +import { + useEffect, + useRef, + useState, + type KeyboardEvent as ReactKeyboardEvent, + type ReactNode, +} from "react"; +import { Button, Input, Modal } from "@heroui/react"; +import { useLingui } from "@lingui/react/macro"; import type { MessageDescriptor } from "@lingui/core"; -import { Command, Search } from "lucide-react"; -import { readBridge } from "@/renderer/bridge"; +import { + Command, + File as FileIcon, + ListFilter, + LoaderCircle, + MessageSquare, + Search, + Settings, + Zap, +} from "lucide-react"; +import { readBridge, isDevApp, isRemoteSession } from "@/renderer/bridge"; +import { useDragSource } from "@/renderer/dnd"; +import { openThread } from "@/renderer/actions/threadActions"; +import { showFilesPanel } from "@/renderer/actions/panelActions"; +import { resolveActivePaneId, resolveProjectIdForView } from "@/renderer/actions/currentProject"; import { useAppStore } from "@/renderer/state/appStore"; import { useFileEditorStore } from "@/renderer/state/fileEditorStore"; import { usePanelStore } from "@/renderer/state/panelStore"; +import { openFileInEditor, resolveWorktreeBranch } from "@/renderer/utils/gitHelpers"; +import { + SETTINGS_SEARCH_INDEX, + searchSettings, + type SettingsSearchResult, +} from "@/renderer/views/SettingsOverlay/parts/settingsSearchIndex"; +import { useShallow } from "zustand/shallow"; import { useCommandPaletteStore } from "./commandPaletteStore"; import { useKeybindingStore } from "./keybindingStore"; import { bindingForPlatform, formatKeybinding } from "./keybindingMatcher"; @@ -16,162 +42,381 @@ import { isCommandAvailable, type AppCommand, } from "./registry"; +import type { CommandWhenContext } from "./when"; +import { + filterCommandsForSearch, + filterThreadsForSearch, + type EverythingSearchCategory, +} from "./everythingSearch"; +import { + EverythingSearchResults, + type EverythingSearchResult, + type EverythingSearchSection, +} from "./EverythingSearchResults"; +import { useEverythingFileSearch } from "./useEverythingFileSearch"; -const MAX_VISIBLE_COMMANDS = 80; +const ALL_CATEGORY_LIMITS = { + threads: 12, + actions: 8, + commands: 12, + settings: 8, + files: 20, +} as const; +const CATEGORY_RESULT_LIMIT = 80; export function CommandPalette() { const { t } = useLingui(); const isOpen = useCommandPaletteStore((state) => state.isOpen); + const originTarget = useCommandPaletteStore((state) => state.originTarget); const close = useCommandPaletteStore((state) => state.close); const keybindings = useKeybindingStore((state) => state.keybindings); + const projects = useAppStore(useShallow((state) => state.projects)); + const threads = useAppStore(useShallow((state) => state.threads)); + const view = useAppStore((state) => state.view); + const focusedPaneId = useAppStore((state) => state.focusedPaneId); + usePanelStore((state) => state.filesPanelContext); + useFileEditorStore((state) => state.rootContext); + useFileEditorStore((state) => state.activePath); + const [query, setQuery] = useState(""); + const [category, setCategory] = useState("all"); const [activeIndex, setActiveIndex] = useState(0); + const [originContext, setOriginContext] = useState(() => buildWhenContext()); const inputRef = useRef(null); - useAppStore((state) => state.projects); - useAppStore((state) => state.threads); - useAppStore((state) => state.view); - useAppStore((state) => state.focusedPaneId); - usePanelStore((state) => state.filesPanelContext); - useFileEditorStore((state) => state.rootContext); - useFileEditorStore((state) => state.activePath); + const projectsById = new Map(projects.map((project) => [project.id, project])); + const projectId = resolveProjectIdForView(view, threads, focusedPaneId); + const activeProject = projectId ? projectsById.get(projectId) : undefined; + const activePaneId = + view.kind === "thread" ? resolveActivePaneId(view.panes, focusedPaneId) : undefined; + const activeThread = activePaneId + ? threads.find((thread) => thread.id === activePaneId) + : undefined; + const worktreePath = activeThread?.worktreePath; + const worktreeBranch = activeThread?.worktreeBranch; + + const fileSearch = useEverythingFileSearch({ + project: activeProject, + worktreePath, + worktreeBranch, + query, + enabled: isOpen && (category === "all" || category === "files"), + }); - const whenContext = buildWhenContext(); - const commands = buildCommandRegistry().filter((command) => - isCommandAvailable(command, whenContext), - ); const resolve = (value: string | MessageDescriptor): string => typeof value === "string" ? value : t(value); - const filteredCommands = filterCommands(commands, query, resolve).slice(0, MAX_VISIBLE_COMMANDS); - const activeCommand = filteredCommands[activeIndex]; + const searchesCommands = + isOpen && (category === "all" || category === "commands" || category === "actions"); + const availableCommands = searchesCommands + ? buildCommandRegistry().filter((command) => isCommandAvailable(command, originContext)) + : []; + const threadMatches = + isOpen && (category === "all" || category === "threads") + ? filterThreadsForSearch(threads, projectsById, query) + : []; + const commandMatches = + searchesCommands && category !== "actions" + ? filterCommandsForSearch(availableCommands, query, resolve, "command") + : []; + const actionMatches = + searchesCommands && category !== "commands" + ? filterCommandsForSearch(availableCommands, query, resolve, "action") + : []; + const settingMatches = + isOpen && (category === "all" || category === "settings") + ? resolveSettingMatches(query, category, t) + : []; + + const sections = ( + [ + { + category: "threads", + label: t`Threads`, + results: threadMatches.slice(0, resultLimit(category, "threads")).map((thread) => ({ + key: `thread:${thread.id}`, + kind: "thread", + thread, + project: projectsById.get(thread.projectId), + })), + }, + { + category: "actions", + label: t`Actions`, + results: actionMatches + .slice(0, resultLimit(category, "actions")) + .map((command) => commandSearchResult(command, "action", resolve, keybindings)), + }, + { + category: "commands", + label: t`Commands`, + results: commandMatches + .slice(0, resultLimit(category, "commands")) + .map((command) => commandSearchResult(command, "command", resolve, keybindings)), + }, + { + category: "settings", + label: t`Settings`, + results: settingMatches.slice(0, resultLimit(category, "settings")).map((setting) => ({ + key: `setting:${setting.anchor}`, + kind: "setting", + setting, + })), + }, + { + category: "files", + label: t`Files`, + results: fileSearch.entries.slice(0, resultLimit(category, "files")).map((entry) => ({ + key: `file:${entry.path}`, + kind: "file", + entry, + })), + }, + ] satisfies EverythingSearchSection[] + ).filter((section) => category === "all" || section.category === category); + + const results = sections.flatMap((section) => section.results); + const activeResult = results[activeIndex]; + const emptyMessage = + category === "files" + ? !activeProject + ? t`Select project` + : !query.trim() + ? t`Type to search files` + : fileSearch.failed + ? t`File search unavailable` + : undefined + : undefined; + + const dragSource = useDragSource(); + const draggingThread = + dragSource?.type === "thread" && threads.some((thread) => thread.id === dragSource.threadId); + const wasDraggingRef = useRef(false); useEffect(() => { if (!isOpen) { setQuery(""); + setCategory("all"); setActiveIndex(0); return; } + setOriginContext(buildWhenContext(originTarget)); const id = requestAnimationFrame(() => inputRef.current?.focus()); return () => cancelAnimationFrame(id); - }, [isOpen]); + }, [isOpen, originTarget]); useEffect(() => { - if (activeIndex >= filteredCommands.length) { - setActiveIndex(Math.max(0, filteredCommands.length - 1)); + setActiveIndex(0); + }, [query, category]); + + useEffect(() => { + if (activeIndex >= results.length) { + setActiveIndex(Math.max(0, results.length - 1)); } - }, [activeIndex, filteredCommands.length]); + }, [activeIndex, results.length]); - function runCommand(command: AppCommand | undefined) { - if (!command) return; + useEffect(() => { + if (draggingThread) { + wasDraggingRef.current = true; + return; + } + if (wasDraggingRef.current) { + wasDraggingRef.current = false; + close(); + } + }, [draggingThread, close]); + + function activate(result: EverythingSearchResult | undefined) { + if (!result) return; + if (result.kind === "thread") { + openThread(result.thread.id); + close(); + return; + } + if (result.kind === "command" || result.kind === "action") { + const command = result.command; + const target = originTarget; + close(); + requestAnimationFrame(() => void command.run(undefined, { target })); + return; + } + if (result.kind === "setting") { + close(); + usePanelStore.getState().openSettingsSection(result.setting.section, result.setting.anchor); + return; + } + if (result.kind !== "file" || !activeProject) return; + + showFilesPanel(activeProject.id, worktreePath); + const root = useFileEditorStore.getState().rootContext; + if (root?.projectId !== activeProject.id || root.worktreePath !== worktreePath) { + return; + } close(); - void command.run(); + void openFileInEditor( + activeProject, + worktreePath, + worktreePath + ? resolveWorktreeBranch(activeProject.id, worktreePath, worktreeBranch) + : undefined, + result.entry.path, + ); + } + + function selectCategory(next: EverythingSearchCategory) { + setCategory(next); + requestAnimationFrame(() => inputRef.current?.focus()); + } + + const categories: { + id: EverythingSearchCategory; + label: string; + icon: ReactNode; + }[] = [ + { id: "all", label: t`All`, icon: }, + { id: "threads", label: t`Threads`, icon: }, + { id: "commands", label: t`Commands`, icon: }, + { id: "settings", label: t`Settings`, icon: }, + { id: "files", label: t`Files`, icon: }, + { id: "actions", label: t`Actions`, icon: }, + ]; + + function handleDialogKeyDown(event: ReactKeyboardEvent) { + if (event.key === "ArrowDown") { + event.preventDefault(); + if (results.length > 0) { + setActiveIndex((index) => Math.min(index + 1, results.length - 1)); + } + } else if (event.key === "ArrowUp") { + event.preventDefault(); + setActiveIndex((index) => Math.max(index - 1, 0)); + } } return ( { if (!open) close(); }} > - - -
- - { - setQuery(event.target.value); - setActiveIndex(0); - }} - onKeyDown={(event) => { - if (event.key === "ArrowDown") { - event.preventDefault(); - setActiveIndex((idx) => Math.min(idx + 1, filteredCommands.length - 1)); - } else if (event.key === "ArrowUp") { - event.preventDefault(); - setActiveIndex((idx) => Math.max(idx - 1, 0)); - } else if (event.key === "Enter") { - event.preventDefault(); - runCommand(activeCommand); - } - }} - className="min-w-0 flex-1 border-0 bg-transparent px-0 shadow-none" - /> -
-
- {filteredCommands.length > 0 ? ( -
- {filteredCommands.map((command, index) => { - const shortcut = shortcutForCommand(command.id, keybindings); - return ( - - ); - })} + + +
{ + event.preventDefault(); + activate(activeResult); + }} + > +
+
+ + setQuery(event.target.value)} + onKeyDown={handleDialogKeyDown} + className="min-w-0 flex-1 border-0 bg-transparent px-0 shadow-none" + spellCheck={false} + autoComplete="off" + />
- ) : ( -
- No commands found +
+ {categories.map((item) => ( + + ))}
- )} -
+
+ +
); } -function filterCommands( - commands: AppCommand[], +function resultLimit( + selectedCategory: EverythingSearchCategory, + resultCategory: EverythingSearchSection["category"], +): number { + return selectedCategory === "all" ? ALL_CATEGORY_LIMITS[resultCategory] : CATEGORY_RESULT_LIMIT; +} + +function commandSearchResult( + command: AppCommand, + kind: "command" | "action", + resolveMessage: (value: string | MessageDescriptor) => string, + bindings: readonly { command: string }[], +): EverythingSearchResult { + return { + key: `${kind}:${command.id}`, + kind, + command, + title: resolveMessage(command.title), + subtitle: resolveMessage(command.subtitle ?? command.group), + shortcut: shortcutForCommand(command.id, bindings), + }; +} + +function resolveSettingMatches( query: string, - resolve: (value: string | MessageDescriptor) => string, -): AppCommand[] { - const normalized = query.trim().toLowerCase(); - if (!normalized) return commands; - const terms = normalized.split(/\s+/); - return commands.filter((command) => { - const haystack = [ - command.id, - resolve(command.title), - resolve(command.group), - command.subtitle ? resolve(command.subtitle) : "", - ...(command.keywords ?? []), - ] - .join(" ") - .toLowerCase(); - return terms.every((term) => haystack.includes(term)); - }); + category: EverythingSearchCategory, + t: (descriptor: MessageDescriptor) => string, +): SettingsSearchResult[] { + const options = { devMode: isDevApp(), remoteSession: isRemoteSession() }; + if (query.trim()) return searchSettings(query, t, options); + if (category !== "settings") return []; + return SETTINGS_SEARCH_INDEX.filter( + (entry) => + (!entry.devOnly || options.devMode) && (!entry.desktopOnly || !options.remoteSession), + ).map((entry) => ({ + section: entry.section, + anchor: entry.anchor, + title: t(entry.title), + snippet: entry.description ? t(entry.description) : null, + })); } function shortcutForCommand( diff --git a/src/renderer/commands/EverythingSearchResultRow.tsx b/src/renderer/commands/EverythingSearchResultRow.tsx new file mode 100644 index 000000000..4e931be42 --- /dev/null +++ b/src/renderer/commands/EverythingSearchResultRow.tsx @@ -0,0 +1,141 @@ +import { useRef, type ReactNode } from "react"; +import { Button } from "@heroui/react"; +import { useLingui } from "@lingui/react/macro"; +import { useDraggable } from "@dnd-kit/react"; +import { GripVertical } from "lucide-react"; +import type { Project, Thread } from "@/shared/contracts"; +import type { DragSourceData } from "@/renderer/dnd"; +import { ThreadProviderIcon } from "@/renderer/components/providers/ThreadProviderIcon"; + +interface ResultRowProps { + icon: ReactNode; + title: string; + subtitle?: string | undefined; + shortcut?: string | undefined; + index: number; + isSelected: boolean; + onActivate: () => void; + onHover: () => void; +} + +export function EverythingSearchResultRow(props: ResultRowProps) { + const stateClass = props.isSelected + ? "bg-[var(--row-active)] text-foreground" + : "text-foreground/85 hover:bg-[var(--row-hover)] hover:text-foreground"; + + return ( + + ); +} + +export function EverythingSearchThreadRow(props: { + thread: Thread; + project: Project | undefined; + index: number; + isSelected: boolean; + onActivate: () => void; + onHover: () => void; +}) { + const { t } = useLingui(); + const rowRef = useRef(null); + + const { handleRef } = useDraggable({ + id: `everything-search:${props.thread.id}`, + type: "thread", + data: { + type: "thread", + threadId: props.thread.id, + projectId: props.thread.projectId, + ...(props.thread.worktreePath != null ? { worktreePath: props.thread.worktreePath } : {}), + } satisfies DragSourceData, + element: rowRef, + }); + + const stateClass = props.isSelected + ? "bg-[var(--row-active)] text-foreground" + : "text-foreground/85 hover:bg-[var(--row-hover)] hover:text-foreground"; + + const context = [ + props.project?.name, + props.thread.worktreeBranch ?? props.thread.worktreePath, + ].filter((value): value is string => Boolean(value)); + + return ( +
+ + +
+ ); +} diff --git a/src/renderer/commands/EverythingSearchResults.tsx b/src/renderer/commands/EverythingSearchResults.tsx new file mode 100644 index 000000000..d0dc08579 --- /dev/null +++ b/src/renderer/commands/EverythingSearchResults.tsx @@ -0,0 +1,143 @@ +import { useEffect, useRef, type ReactNode } from "react"; +import { Trans, useLingui } from "@lingui/react/macro"; +import { Command, File as FileIcon, LoaderCircle, Settings, Zap } from "lucide-react"; +import type { Project, ProjectTreeEntry, Thread } from "@/shared/contracts"; +import type { SettingsSearchResult } from "@/renderer/views/SettingsOverlay/parts/settingsSearchIndex"; +import type { AppCommand } from "./registry"; +import type { EverythingSearchCategory } from "./everythingSearch"; +import { EverythingSearchResultRow, EverythingSearchThreadRow } from "./EverythingSearchResultRow"; + +export type EverythingSearchResult = + | { key: string; kind: "thread"; thread: Thread; project: Project | undefined } + | { + key: string; + kind: "command" | "action"; + command: AppCommand; + title: string; + subtitle: string; + shortcut: string; + } + | { key: string; kind: "setting"; setting: SettingsSearchResult } + | { key: string; kind: "file"; entry: ProjectTreeEntry }; + +export interface EverythingSearchSection { + category: Exclude; + label: string; + results: EverythingSearchResult[]; +} + +export function EverythingSearchResults(props: { + sections: EverythingSearchSection[]; + activeIndex: number; + loading: boolean; + emptyMessage?: string | undefined; + onActivate: (result: EverythingSearchResult) => void; + onHover: (index: number) => void; +}) { + const { t } = useLingui(); + const listRef = useRef(null); + const results = props.sections.flatMap((section) => section.results); + const activeResultKey = results[props.activeIndex]?.key; + + useEffect(() => { + const row = listRef.current?.querySelector( + `[data-search-index="${props.activeIndex}"]`, + ); + row?.scrollIntoView({ block: "nearest" }); + }, [props.activeIndex, activeResultKey]); + + let nextSectionStart = 0; + const sectionsWithStart = props.sections.map((section) => { + const start = nextSectionStart; + nextSectionStart += section.results.length; + return { section, start }; + }); + + return ( +
+ {results.length > 0 ? ( + sectionsWithStart.map(({ section, start }) => { + if (section.results.length === 0) return null; + return ( +
+
+ {section.label} +
+ {section.results.map((result, sectionIndex) => { + const index = start + sectionIndex; + if (result.kind === "thread") { + return ( + props.onActivate(result)} + onHover={() => props.onHover(index)} + /> + ); + } + return ( + props.onActivate(result)} + onHover={() => props.onHover(index)} + /> + ); + })} +
+ ); + }) + ) : props.loading ? ( +
+ +
+ ) : ( +
+ {props.emptyMessage ?? No results} +
+ )} +
+ ); +} + +function resultIcon(result: Exclude): ReactNode { + if (result.kind === "command") return ; + if (result.kind === "action") return ; + if (result.kind === "setting") return ; + return ; +} + +function resultTitle(result: Exclude): string { + if (result.kind === "command" || result.kind === "action") return result.title; + if (result.kind === "setting") return result.setting.title; + if (result.kind !== "file") return ""; + return result.entry.name; +} + +function resultSubtitle( + result: Exclude, + settingsLabel: string, +): string { + if (result.kind === "command" || result.kind === "action") return result.subtitle; + if (result.kind === "setting") return result.setting.snippet ?? settingsLabel; + if (result.kind !== "file") return ""; + return result.entry.path; +} diff --git a/src/renderer/commands/commandPaletteStore.test.ts b/src/renderer/commands/commandPaletteStore.test.ts new file mode 100644 index 000000000..02ce40964 --- /dev/null +++ b/src/renderer/commands/commandPaletteStore.test.ts @@ -0,0 +1,32 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { useCommandPaletteStore } from "./commandPaletteStore"; + +describe("useCommandPaletteStore", () => { + afterEach(() => { + document.body.innerHTML = ""; + useCommandPaletteStore.setState({ isOpen: false, originTarget: null }); + }); + + it("captures the element that owned focus when search opens", () => { + document.body.innerHTML = ''; + const origin = document.getElementById("origin"); + origin?.focus(); + + useCommandPaletteStore.getState().open(); + document.getElementById("palette")?.focus(); + + expect(useCommandPaletteStore.getState().originTarget).toBe(origin); + }); + + it("does not replace the origin when an open palette is opened again", () => { + document.body.innerHTML = ''; + const origin = document.getElementById("origin"); + origin?.focus(); + useCommandPaletteStore.getState().open(); + + document.getElementById("palette")?.focus(); + useCommandPaletteStore.getState().open(); + + expect(useCommandPaletteStore.getState().originTarget).toBe(origin); + }); +}); diff --git a/src/renderer/commands/commandPaletteStore.ts b/src/renderer/commands/commandPaletteStore.ts index c02fbb621..df50e7c6a 100644 --- a/src/renderer/commands/commandPaletteStore.ts +++ b/src/renderer/commands/commandPaletteStore.ts @@ -1,7 +1,9 @@ import { create } from "zustand"; +import { resolveFocusElement } from "./focusedSurface"; interface CommandPaletteState { isOpen: boolean; + originTarget: Element | null; open: () => void; close: () => void; toggle: () => void; @@ -9,7 +11,12 @@ interface CommandPaletteState { export const useCommandPaletteStore = create((set) => ({ isOpen: false, - open: () => set({ isOpen: true }), + originTarget: null, + open: () => + set((state) => (state.isOpen ? {} : { isOpen: true, originTarget: resolveFocusElement() })), close: () => set({ isOpen: false }), - toggle: () => set((state) => ({ isOpen: !state.isOpen })), + toggle: () => + set((state) => + state.isOpen ? { isOpen: false } : { isOpen: true, originTarget: resolveFocusElement() }, + ), })); diff --git a/src/renderer/commands/defaultKeybindings.test.ts b/src/renderer/commands/defaultKeybindings.test.ts index e2cf2f862..fa95b7507 100644 --- a/src/renderer/commands/defaultKeybindings.test.ts +++ b/src/renderer/commands/defaultKeybindings.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { COMPOSER_CONTROL_COMMAND_IDS, DEFAULT_KEYBINDINGS, @@ -6,6 +6,9 @@ import { } from "@/shared/keybindings"; import { getCurrentProjectId } from "@/renderer/actions/currentProject"; import { useAppStore } from "@/renderer/state/appStore"; +import { useDevTerminalStore } from "@/renderer/state/devTerminalStore"; +import { useFileEditorStore } from "@/renderer/state/fileEditorStore"; +import { useFindFocusStore } from "@/renderer/state/findFocusStore"; import { bindingForPlatform, canonicalizeKeybinding, type PlatformName } from "./keybindingMatcher"; import { buildCommandRegistry, buildWhenContext } from "./registry"; import { evaluateWhenClause } from "./when"; @@ -112,21 +115,21 @@ describe("default keybindings", () => { composerFocus: true, }), ).toBe(false); - expect(evaluateWhenClause(bindings["thread.search.open"]?.when, idleThreadContext)).toBe(true); + expect(evaluateWhenClause(bindings["palette.open"]?.when, idleThreadContext)).toBe(true); expect( - evaluateWhenClause(bindings["thread.search.open"]?.when, { + evaluateWhenClause(bindings["palette.open"]?.when, { ...idleThreadContext, panelFocus: true, }), ).toBe(false); expect( - evaluateWhenClause(bindings["thread.search.open"]?.when, { + evaluateWhenClause(bindings["palette.open"]?.when, { ...idleThreadContext, browserFocus: true, }), ).toBe(false); expect( - evaluateWhenClause(bindings["thread.search.open"]?.when, { + evaluateWhenClause(bindings["palette.open"]?.when, { ...idleThreadContext, composerFocus: true, }), @@ -235,3 +238,89 @@ describe("default keybindings", () => { expect(evaluateWhenClause(starCommand?.when, context)).toBe(true); }); }); + +describe("command execution context", () => { + afterEach(() => { + document.body.innerHTML = ""; + useFileEditorStore.setState({ tabs: [], activePath: null }); + useFindFocusStore.setState({ settingsFocusToken: 0, treeFocusToken: 0 }); + useDevTerminalStore.setState({ + activeProjectId: null, + activeWorktreePath: null, + tabs: [], + activeTabId: null, + }); + }); + + it("cycles editor tabs from the originating target after focus moves", () => { + document.body.innerHTML = ` +
+ + `; + const origin = document.getElementById("origin"); + document.getElementById("palette")?.focus(); + useFileEditorStore.setState({ tabs: ["one.ts", "two.ts"], activePath: "one.ts" }); + + const command = buildCommandRegistry().find((item) => item.id === "tab.next"); + void command?.run(undefined, { target: origin }); + + expect(useFileEditorStore.getState().activePath).toBe("two.ts"); + }); + + it("opens Find on the originating surface after focus moves", () => { + document.body.innerHTML = ` +
+ + `; + const origin = document.getElementById("origin"); + document.getElementById("palette")?.focus(); + + const command = buildCommandRegistry().find((item) => item.id === "find.open"); + void command?.run(undefined, { target: origin }); + + expect(useFindFocusStore.getState().settingsFocusToken).toBe(1); + }); + + it("cycles terminal tabs from the originating target after focus moves", () => { + document.body.innerHTML = ` +
+ + `; + const origin = document.getElementById("origin"); + document.getElementById("palette")?.focus(); + useDevTerminalStore.setState({ + activeProjectId: "project-1", + activeWorktreePath: null, + tabs: [ + { id: "one", projectId: "project-1", title: "One", createdAt: "2026-01-01" }, + { id: "two", projectId: "project-1", title: "Two", createdAt: "2026-01-02" }, + ], + activeTabId: "two", + }); + + const command = buildCommandRegistry().find((item) => item.id === "tab.previous"); + void command?.run(undefined, { target: origin }); + + expect(useDevTerminalStore.getState().activeTabId).toBe("one"); + }); + + it("focuses the address bar in the originating browser instance", () => { + document.body.innerHTML = ` +
+ + +
+ + `; + const origin = document.getElementById("origin"); + const address = document.querySelector("[data-poracode-browser-address]"); + document.getElementById("palette")?.focus(); + + const command = buildCommandRegistry().find((item) => item.id === "browser.focus-address-bar"); + void command?.run(undefined, { target: origin }); + + expect(document.activeElement).toBe(address); + expect(address?.selectionStart).toBe(0); + expect(address?.selectionEnd).toBe(address?.value.length); + }); +}); diff --git a/src/renderer/commands/everythingSearch.test.ts b/src/renderer/commands/everythingSearch.test.ts new file mode 100644 index 000000000..a256f858c --- /dev/null +++ b/src/renderer/commands/everythingSearch.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it, vi } from "vitest"; +import type { Project, Thread } from "@/shared/contracts"; +import type { AppCommand } from "./registry"; +import { filterCommandsForSearch, filterThreadsForSearch } from "./everythingSearch"; + +describe("filterThreadsForSearch", () => { + const project: Project = { + id: "project-1", + name: "Poracode", + location: { kind: "windows", path: "C:\\repo" }, + createdAt: "2026-07-15T00:00:00.000Z", + }; + const projects = new Map([[project.id, project]]); + + it("matches thread and project/worktree metadata with AND semantics", () => { + const threads = [ + makeThread({ + id: "match", + title: "Fix file search", + worktreeBranch: "feature/everything", + }), + makeThread({ id: "miss", title: "Fix file search" }), + ]; + + expect( + filterThreadsForSearch(threads, projects, "poracode everything").map((t) => t.id), + ).toEqual(["match"]); + }); + + it("excludes archived threads and keeps starred threads ahead of newer threads", () => { + const threads = [ + makeThread({ id: "new", updatedAt: "2026-07-15T03:00:00.000Z" }), + makeThread({ id: "starred", starred: true, updatedAt: "2026-07-14T03:00:00.000Z" }), + makeThread({ id: "archived", archived: true, starred: true }), + ]; + + expect(filterThreadsForSearch(threads, projects, "").map((t) => t.id)).toEqual([ + "starred", + "new", + ]); + }); +}); + +describe("filterCommandsForSearch", () => { + const resolve = (value: AppCommand["title"]): string => + typeof value === "string" ? value : String(value.message ?? value.id); + + it("keeps project actions out of command results and actions out of command results", () => { + const commands = [ + makeCommand("files.open", "Open Files", "Project"), + makeCommand("script.test.run", "Run tests", "Scripts"), + ]; + + expect(filterCommandsForSearch(commands, "", resolve, "command").map((c) => c.id)).toEqual([ + "files.open", + ]); + expect(filterCommandsForSearch(commands, "", resolve, "action").map((c) => c.id)).toEqual([ + "script.test.run", + ]); + }); + + it("matches every query term across command metadata and hides non-runnable results", () => { + const commands = [ + { + ...makeCommand("git.open", "Open review", "Project"), + keywords: ["changes"], + }, + { ...makeCommand("palette.open", "Open palette", "Poracode"), showInPalette: false }, + ]; + + expect( + filterCommandsForSearch(commands, "project changes", resolve, "command").map( + (command) => command.id, + ), + ).toEqual(["git.open"]); + expect(filterCommandsForSearch(commands, "palette", resolve, "command")).toEqual([]); + }); +}); + +function makeThread(overrides: Partial): Thread { + return { + id: "thread-1", + projectId: "project-1", + title: "Thread", + agentKind: "codex", + config: { model: "gpt-5" }, + status: "idle", + attention: "none", + canResumeWithConfig: false, + archived: false, + done: false, + starred: false, + createdAt: "2026-07-15T00:00:00.000Z", + updatedAt: "2026-07-15T00:00:00.000Z", + ...overrides, + }; +} + +function makeCommand(id: string, title: string, group: string): AppCommand { + return { id, title, group, run: vi.fn<() => void>() }; +} diff --git a/src/renderer/commands/everythingSearch.ts b/src/renderer/commands/everythingSearch.ts new file mode 100644 index 000000000..d96fa7a47 --- /dev/null +++ b/src/renderer/commands/everythingSearch.ts @@ -0,0 +1,77 @@ +import type { MessageDescriptor } from "@lingui/core"; +import type { Project, Thread } from "@/shared/contracts"; +import type { AppCommand } from "./registry"; + +export type EverythingSearchCategory = + | "all" + | "threads" + | "commands" + | "settings" + | "files" + | "actions"; + +type ResolveMessage = (value: string | MessageDescriptor) => string; + +export function filterThreadsForSearch( + threads: readonly Thread[], + projectsById: ReadonlyMap, + query: string, +): Thread[] { + const terms = searchTerms(query); + return threads + .filter((thread) => { + if (thread.archived) return false; + const project = projectsById.get(thread.projectId); + return matchesTerms( + [ + thread.title, + project?.name ?? "", + thread.worktreeBranch ?? "", + thread.worktreePath ?? "", + thread.agentKind, + ], + terms, + ); + }) + .toSorted((a, b) => { + if (a.starred !== b.starred) return a.starred ? -1 : 1; + return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); + }); +} + +export function filterCommandsForSearch( + commands: readonly AppCommand[], + query: string, + resolve: ResolveMessage, + kind: "command" | "action", +): AppCommand[] { + const terms = searchTerms(query); + return commands.filter((command) => { + if (command.showInPalette === false) return false; + if (isProjectActionCommand(command) !== (kind === "action")) return false; + return matchesTerms( + [ + command.id, + resolve(command.title), + resolve(command.group), + command.subtitle ? resolve(command.subtitle) : "", + ...(command.keywords ?? []), + ], + terms, + ); + }); +} + +export function isProjectActionCommand(command: Pick): boolean { + return command.id.startsWith("script.") && command.id.endsWith(".run"); +} + +function searchTerms(query: string): string[] { + return query.trim().toLocaleLowerCase().split(/\s+/u).filter(Boolean); +} + +function matchesTerms(values: readonly string[], terms: readonly string[]): boolean { + if (terms.length === 0) return true; + const haystack = values.join(" ").toLocaleLowerCase(); + return terms.every((term) => haystack.includes(term)); +} diff --git a/src/renderer/commands/focusedSurface.ts b/src/renderer/commands/focusedSurface.ts index 9819d3b39..b6499c39d 100644 --- a/src/renderer/commands/focusedSurface.ts +++ b/src/renderer/commands/focusedSurface.ts @@ -7,6 +7,11 @@ const EDITOR_FOCUS_SELECTOR = ".monaco-editor"; const TERMINAL_FOCUS_SELECTOR = ".xterm"; +export function resolveFocusElement(target?: EventTarget | null): Element | null { + const element = target instanceof Element ? target : document.activeElement; + return element instanceof Element ? element : null; +} + /** True when `element` is inside a Monaco editor surface. */ export function isEditorFocusElement(element: Element | null | undefined): boolean { return Boolean(element?.closest(EDITOR_FOCUS_SELECTOR)); diff --git a/src/renderer/commands/registry.ts b/src/renderer/commands/registry.ts index 4ede8de0e..da8dc1fca 100644 --- a/src/renderer/commands/registry.ts +++ b/src/renderer/commands/registry.ts @@ -33,13 +33,16 @@ import { cycleRecentThread } from "@/renderer/actions/recentThreadCycle"; import { useAppStore } from "@/renderer/state/appStore"; import { useDevTerminalStore } from "@/renderer/state/devTerminalStore"; import { useFileEditorStore } from "@/renderer/state/fileEditorStore"; -import { usePanelStore } from "@/renderer/state/panelStore"; import { useSharedSettings } from "@/renderer/state/sharedSettingsStore"; import { toggleSidebar } from "@/renderer/state/sidebarOverlayStore"; import { useSidebarUiStore } from "@/renderer/state/sidebarUiStore"; import { startShellWithToast, writeScriptToShell } from "@/renderer/utils/shellUtils"; import { openFindForActiveSurface } from "@/renderer/components/find/findController"; -import { isEditorFocusElement, isTerminalFocusElement } from "./focusedSurface"; +import { + isEditorFocusElement, + isTerminalFocusElement, + resolveFocusElement, +} from "./focusedSurface"; import { useCommandPaletteStore } from "./commandPaletteStore"; import type { CommandWhenContext } from "./when"; import { evaluateWhenClause } from "./when"; @@ -60,7 +63,14 @@ export interface AppCommand { */ keys?: string[]; showInShortcuts?: boolean; - run: (args?: unknown) => void | Promise; + /** Hide commands that should not be offered as executable palette results. */ + showInPalette?: boolean; + run: (args?: unknown, context?: AppCommandExecutionContext) => void | Promise; +} + +export interface AppCommandExecutionContext { + /** The element that owned focus before a command surface took it. */ + target: EventTarget | null; } interface ActiveContext { @@ -78,7 +88,7 @@ export function buildWhenContext( const terminal = useDevTerminalStore.getState(); const paletteOpen = useCommandPaletteStore.getState().isOpen; const active = resolveActiveContext(); - const element = target instanceof Element ? target : document.activeElement; + const element = resolveFocusElement(target); const inputFocus = isTextInputElement(element); const editorFocus = isEditorFocusElement(element); const terminalFocus = isTerminalFocusElement(element); @@ -124,8 +134,9 @@ function baseCommands(): AppCommand[] { return [ { id: "palette.open", - title: msg`Open Command Palette`, + title: msg`Search`, group: "Poracode", + showInPalette: false, run: () => useCommandPaletteStore.getState().open(), }, { @@ -148,7 +159,7 @@ function baseCommands(): AppCommand[] { subtitle: msg`Search the current view`, group: "Poracode", keywords: ["find", "search", "filter"], - run: openFindForActiveSurface, + run: (_args, context) => openFindForActiveSurface(context?.target), }, { id: "sidebar.toggle", @@ -196,9 +207,11 @@ function baseCommands(): AppCommand[] { }, { id: "thread.search.open", - title: msg`Search Threads`, - group: msg`Thread`, - run: () => usePanelStore.getState().openThreadSearch(), + title: msg`Search`, + group: "Poracode", + showInPalette: false, + showInShortcuts: false, + run: () => useCommandPaletteStore.getState().open(), }, { id: "thread.archive", @@ -303,6 +316,7 @@ function baseCommands(): AppCommand[] { title: msg`Run Terminal Command`, group: msg`Terminal`, when: "hasProject", + showInPalette: false, run: (args) => runTerminalCommand(args), }, { @@ -388,7 +402,7 @@ function baseCommands(): AppCommand[] { // same chords elsewhere but stand down inside the editor/terminal (see // their `when`), leaving them free here. when: "editorFocus || terminalFocus", - run: () => switchFocusedSurfaceTab("next"), + run: (_args, context) => switchFocusedSurfaceTab("next", context?.target), }, { id: "tab.previous", @@ -396,7 +410,7 @@ function baseCommands(): AppCommand[] { subtitle: msg`Switch to the previous tab`, group: "Poracode", when: "editorFocus || terminalFocus", - run: () => switchFocusedSurfaceTab("previous"), + run: (_args, context) => switchFocusedSurfaceTab("previous", context?.target), }, { id: "browser.focus-address-bar", @@ -406,7 +420,7 @@ function baseCommands(): AppCommand[] { // Only while the in-app browser holds focus — same scope as the other // browser shortcuts, and avoids swallowing Ctrl+L elsewhere in the app. when: "browserFocus", - run: focusBrowserAddressBar, + run: (_args, context) => focusBrowserAddressBar(context?.target), }, { id: "browser.toggle", @@ -504,24 +518,26 @@ function openNewBrowserTab(): void { .catch(() => {}); } -function focusBrowserAddressBar(): void { +function focusBrowserAddressBar(target?: EventTarget | null): void { // The browser panel can be mounted twice (right panel + overlay), so target - // the address bar inside the browser that currently holds focus, falling back - // to the first mounted instance. - const active = document.activeElement; + // the address bar inside the browser where the command originated, falling + // back to the active or first mounted instance. + const active = resolveFocusElement(target); const container = - (active instanceof Element ? active.closest("[data-poracode-browser]") : null) ?? - document.querySelector("[data-poracode-browser]"); + active?.closest("[data-poracode-browser]") ?? document.querySelector("[data-poracode-browser]"); const input = container?.querySelector("[data-poracode-browser-address]"); if (!input) return; input.focus(); input.select(); } -function switchFocusedSurfaceTab(direction: "next" | "previous"): void { - // The binding's `when` (editorFocus || terminalFocus) guarantees one of these - // surfaces holds focus when this runs; cycle that surface's own tab strip. - const element = document.activeElement; +function switchFocusedSurfaceTab( + direction: "next" | "previous", + target?: EventTarget | null, +): void { + // The command's availability context guarantees one of these surfaces owned + // focus when invoked; cycle that originating surface's own tab strip. + const element = resolveFocusElement(target); if (isTerminalFocusElement(element)) { useDevTerminalStore.getState().cycleTab(direction); return; diff --git a/src/renderer/commands/shortcutCatalog.test.ts b/src/renderer/commands/shortcutCatalog.test.ts index 68059e999..9176e409a 100644 --- a/src/renderer/commands/shortcutCatalog.test.ts +++ b/src/renderer/commands/shortcutCatalog.test.ts @@ -361,10 +361,11 @@ describe("shortcut catalog", () => { translate, ); - // `terminal.toggle` (group "Terminal") and `thread.search.open` (group - // "Thread", no `when`) categorize purely off their group token. + // `terminal.toggle` is terminal-scoped by its group, while the unified + // search command is global and the legacy thread-search alias stays hidden. expect(rows.find((row) => row.id === "terminal.toggle")?.contexts).toContain("terminal"); - expect(rows.find((row) => row.id === "thread.search.open")?.contexts).toContain("thread"); + expect(rows.find((row) => row.id === "palette.open")?.contexts).toContain("global"); + expect(rows.find((row) => row.id === "thread.search.open")).toBeUndefined(); }); it("does not show conflicting shortcuts inside a context", () => { diff --git a/src/renderer/commands/useEverythingFileSearch.ts b/src/renderer/commands/useEverythingFileSearch.ts new file mode 100644 index 000000000..4435b1fdd --- /dev/null +++ b/src/renderer/commands/useEverythingFileSearch.ts @@ -0,0 +1,88 @@ +import { useEffect, useState } from "react"; +import type { Project, ProjectTreeEntry } from "@/shared/contracts"; +import { resolveSearchConfig } from "@/shared/searchExclude"; +import { readBridge } from "@/renderer/bridge"; +import { useSharedSettings } from "@/renderer/state/sharedSettingsStore"; +import { buildFileEditorContext, resolveWorktreeBranch } from "@/renderer/utils/gitHelpers"; + +interface FileSearchState { + entries: ProjectTreeEntry[]; + loading: boolean; + failed: boolean; +} + +const EMPTY_STATE: FileSearchState = { entries: [], loading: false, failed: false }; + +export function useEverythingFileSearch(input: { + project: Project | undefined; + worktreePath: string | undefined; + worktreeBranch: string | undefined; + query: string; + enabled: boolean; +}): FileSearchState { + const globalUseIgnoreFiles = useSharedSettings((state) => state.searchUseIgnoreFiles); + const globalExclude = useSharedSettings((state) => state.searchExclude); + const [state, setState] = useState(EMPTY_STATE); + + useEffect(() => { + const trimmed = input.query.trim(); + if (!input.enabled || !input.project || !trimmed) { + setState(EMPTY_STATE); + return; + } + + const project = input.project; + const context = buildFileEditorContext( + project, + input.worktreePath, + input.worktreePath + ? resolveWorktreeBranch(project.id, input.worktreePath, input.worktreeBranch) + : undefined, + ); + const searchConfig = resolveSearchConfig({ + globalUseIgnoreFiles, + globalExclude, + projectUseIgnoreFiles: project.searchSettings?.useIgnoreFiles, + projectExclude: project.searchSettings?.exclude, + }); + let cancelled = false; + setState({ entries: [], loading: true, failed: false }); + + const handle = window.setTimeout(() => { + void readBridge() + .searchProjectTree({ + projectLocation: context.projectLocation, + query: trimmed, + limit: 80, + entryType: "file", + searchConfig, + }) + .then((result) => { + if (cancelled) return; + setState({ + entries: result.entries, + loading: false, + failed: false, + }); + }) + .catch(() => { + if (!cancelled) setState({ entries: [], loading: false, failed: true }); + }); + }, 120); + + return () => { + cancelled = true; + window.clearTimeout(handle); + }; + }, [ + input.enabled, + input.project, + input.query, + input.worktreeBranch, + input.worktreePath, + globalExclude, + globalUseIgnoreFiles, + ]); + + return state; +} diff --git a/src/renderer/components/find/findController.test.ts b/src/renderer/components/find/findController.test.ts index bf1287b4c..573f53460 100644 --- a/src/renderer/components/find/findController.test.ts +++ b/src/renderer/components/find/findController.test.ts @@ -1,8 +1,9 @@ import { afterEach, describe, expect, it } from "vitest"; import { useCommandPaletteStore } from "@/renderer/commands/commandPaletteStore"; import { useAppStore } from "@/renderer/state/appStore"; +import { useFindFocusStore } from "@/renderer/state/findFocusStore"; import { usePanelStore } from "@/renderer/state/panelStore"; -import { resolveFindTarget } from "./findController"; +import { openFindForActiveSurface, resolveFindTarget } from "./findController"; function resetFindRoutingState(): void { document.body.innerHTML = ""; @@ -12,11 +13,11 @@ function resetFindRoutingState(): void { projectSettingsId: null, gitOverlayOpen: false, prReviewContext: null, - threadSearchOpen: false, createProjectModalOpen: false, cloneProjectModalOpen: false, }); useAppStore.setState({ view: { kind: "home" } }); + useFindFocusStore.setState({ settingsFocusToken: 0, treeFocusToken: 0 }); } describe("resolveFindTarget", () => { @@ -44,4 +45,20 @@ describe("resolveFindTarget", () => { expect(resolveFindTarget()).toBeNull(); }); + + it("routes from a provided originating target after focus moves", () => { + document.body.innerHTML = ` +
+ + `; + const origin = document.getElementById("origin"); + document.getElementById("palette")?.focus(); + useCommandPaletteStore.setState({ isOpen: true }); + + expect(resolveFindTarget()).toBeNull(); + expect(resolveFindTarget(origin)).toBe("settings"); + + openFindForActiveSurface(origin); + expect(useFindFocusStore.getState().settingsFocusToken).toBe(1); + }); }); diff --git a/src/renderer/components/find/findController.ts b/src/renderer/components/find/findController.ts index b9b3ff259..5e29d2c14 100644 --- a/src/renderer/components/find/findController.ts +++ b/src/renderer/components/find/findController.ts @@ -5,7 +5,11 @@ import { useFindFocusStore } from "@/renderer/state/findFocusStore"; import { useGitFindStore } from "@/renderer/state/gitFindStore"; import { usePanelStore } from "@/renderer/state/panelStore"; import { useCommandPaletteStore } from "@/renderer/commands/commandPaletteStore"; -import { isEditorFocusElement, isTerminalFocusElement } from "@/renderer/commands/focusedSurface"; +import { + isEditorFocusElement, + isTerminalFocusElement, + resolveFocusElement, +} from "@/renderer/commands/focusedSurface"; import { openEditorFind } from "./editorFindBridge"; import { openTerminalFind } from "./terminalFindBridge"; @@ -18,8 +22,8 @@ export type FindTarget = "editor" | "terminal" | "settings" | "git" | "tree" | " * fallback. Returns null when nothing is searchable (e.g. a blocking modal owns * its own input, or the home view). */ -export function resolveFindTarget(): FindTarget | null { - const element = document.activeElement instanceof Element ? document.activeElement : null; +export function resolveFindTarget(target?: EventTarget | null): FindTarget | null { + const element = resolveFocusElement(target); if (isEditorFocusElement(element)) return "editor"; if (isTerminalFocusElement(element)) return "terminal"; const scope = element @@ -34,7 +38,6 @@ export function resolveFindTarget(): FindTarget | null { // Blocking modals trap their own input — leave Ctrl+F to them. if ( useCommandPaletteStore.getState().isOpen || - panel.threadSearchOpen || panel.createProjectModalOpen || panel.cloneProjectModalOpen ) { @@ -48,8 +51,8 @@ export function resolveFindTarget(): FindTarget | null { } /** Entry point for the `find.open` command: open Find on the active surface. */ -export function openFindForActiveSurface(): void { - const target = resolveFindTarget(); +export function openFindForActiveSurface(originTarget?: EventTarget | null): void { + const target = resolveFindTarget(originTarget); if (!target) return; switch (target) { case "editor": diff --git a/src/renderer/devBridge.ts b/src/renderer/devBridge.ts index f0cfe9c09..d88e81ab7 100644 --- a/src/renderer/devBridge.ts +++ b/src/renderer/devBridge.ts @@ -12,6 +12,7 @@ */ import { useAppStore } from "@/renderer/state/appStore"; import { useAgentStatusesStore } from "@/renderer/state/agentStatusesStore"; +import { useCommandPaletteStore } from "@/renderer/commands/commandPaletteStore"; import { usePanelStore } from "@/renderer/state/panelStore"; import { useSharedSettings } from "@/renderer/state/sharedSettingsStore"; import { useSidebarUiStore } from "@/renderer/state/sidebarUiStore"; @@ -31,6 +32,7 @@ export function installDevBridge(): void { update: useUpdateStore, app: useAppStore, agentStatuses: useAgentStatusesStore, + commandPalette: useCommandPaletteStore, panel: usePanelStore, sidebarUi: useSidebarUiStore, sharedSettings: useSharedSettings, diff --git a/src/renderer/locales/de/messages.po b/src/renderer/locales/de/messages.po index 7cd1b4439..e8bd14dab 100644 --- a/src/renderer/locales/de/messages.po +++ b/src/renderer/locales/de/messages.po @@ -568,6 +568,7 @@ msgid "Action name" msgstr "Aktionsname" #: src/mobile/views/pr/PrOverviewPage.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/ActionsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx @@ -848,6 +849,7 @@ msgstr "KI-Anbieter" msgid "Alerts when threads need you" msgstr "Benachrichtigungen, wenn Threads dich brauchen" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/commands/shortcutCatalog.ts #: src/renderer/components/skills/SkillsManager.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -1946,7 +1948,6 @@ msgid "Close scanner" msgstr "Scanner schließen" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Close search" msgstr "Suche schließen" @@ -2048,8 +2049,6 @@ msgstr "Terminal-Composer einklappen" msgid "Collapse todo dock" msgstr "Aufgaben-Dock einklappen" -#. Accessible label for the command palette search input -#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/mcp/McpServerEditor.tsx msgid "Command" msgstr "Befehl" @@ -3648,6 +3647,10 @@ msgstr "Die Datei ist zu groß für die Vorschau." msgid "File no longer exists on disk." msgstr "Die Datei ist nicht mehr auf der Festplatte vorhanden." +#: src/renderer/commands/CommandPalette.tsx +msgid "File search unavailable" +msgstr "Die Dateisuche ist nicht verfügbar" + #. placeholder {0}: entry.insertions + entry.deletions #. placeholder {0}: file.insertions + file.deletions #: src/renderer/views/GitReviewOverlay/parts/GitDiffContent/parts/DiffSection.tsx @@ -3661,6 +3664,7 @@ msgid "File uses an unsupported encoding." msgstr "Die Datei verwendet eine nicht unterstützte Codierung." #: src/mobile/views/WorkspaceView.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/layout/UnifiedRightPanel.tsx msgid "Files" msgstr "Dateien" @@ -5243,6 +5247,11 @@ msgstr "Browser in Fenster verschieben" msgid "Move changes to a new worktree" msgstr "Änderungen in einen neuen Arbeitsbaum verschieben" +#. placeholder {0}: props.thread.title +#: src/renderer/commands/EverythingSearchResultRow.tsx +msgid "Move thread {0}" +msgstr "Thread {0} verschieben" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Verschieben Sie das ToDo-Dock in den rechten Bereich" @@ -5552,8 +5561,8 @@ msgid "No checks reported for this PR." msgstr "Für diese PR wurden keine Checks gemeldet." #: src/renderer/commands/CommandPalette.tsx -msgid "No commands found" -msgstr "Keine Befehle gefunden" +#~ msgid "No commands found" +#~ msgstr "Keine Befehle gefunden" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "No commits found." @@ -5666,7 +5675,6 @@ msgstr "Keine passenden Zeitpläne." #~ msgstr "Keine passenden Aufgaben." #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "No matching threads" msgstr "Keine passenden Threads" @@ -5757,6 +5765,7 @@ msgstr "Noch keine Remote-Umgebungen verbunden." msgid "No repositories found." msgstr "Keine Repositorys gefunden." +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/find/FindBar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx msgid "No results" @@ -5812,8 +5821,8 @@ msgid "No thread selected" msgstr "Kein Thread ausgewählt" #: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx -msgid "No threads" -msgstr "Keine Threads" +#~ msgid "No threads" +#~ msgstr "Keine Threads" #: src/mobile/views/ThreadsView.tsx msgid "No threads in this project" @@ -6031,8 +6040,8 @@ msgid "Open check" msgstr "Offene Prüfung" #: src/renderer/commands/registry.ts -msgid "Open Command Palette" -msgstr "Öffnen Sie die Befehlspalette" +#~ msgid "Open Command Palette" +#~ msgstr "Öffnen Sie die Befehlspalette" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "Open commit on GitHub" @@ -7709,6 +7718,9 @@ msgstr "Scrollgeschwindigkeitsmultiplikator für den Terminal-Scrollback-Puffer. msgid "Scroll to bottom" msgstr "Nach unten scrollen" +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx +#: src/renderer/commands/registry.ts #: src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts #: src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -7811,15 +7823,14 @@ msgstr "Die aktuelle Ansicht durchsuchen" #: src/mobile/NarrowShell.tsx #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Search threads" msgstr "Threads durchsuchen" #: src/renderer/commands/registry.ts -msgid "Search Threads" -msgstr "Threads durchsuchen" +#~ msgid "Search Threads" +#~ msgstr "Threads durchsuchen" -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +#: src/renderer/commands/CommandPalette.tsx msgid "Search…" msgstr "Suchen…" @@ -7914,6 +7925,7 @@ msgstr "Gerät auswählen" msgid "Select model" msgstr "Modell auswählen" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/thread/ProjectSwitchMenu.tsx msgid "Select project" msgstr "Projekt auswählen" @@ -7970,6 +7982,8 @@ msgstr "Tastenkürzel festlegen" #: src/mobile/chrome.ts #: src/mobile/NarrowShell.tsx +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -9212,9 +9226,9 @@ msgstr "Sortierreihenfolge der Threads" msgid "Thread todo dock" msgstr "Thread-Aufgaben-Dock" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/ThreadSettings.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Threads" msgstr "Threads" @@ -9377,8 +9391,8 @@ msgid "Type" msgstr "Typ" #: src/renderer/commands/CommandPalette.tsx -msgid "Type a command" -msgstr "Geben Sie einen Befehl ein" +#~ msgid "Type a command" +#~ msgstr "Geben Sie einen Befehl ein" #: src/mobile/views/BrowserView.tsx msgid "Type into the page" @@ -9388,6 +9402,10 @@ msgstr "In die Seite tippen" msgid "Type into the page (tap a field first)" msgstr "In die Seite tippen (zuerst ein Feld antippen)" +#: src/renderer/commands/CommandPalette.tsx +msgid "Type to search files" +msgstr "Zum Suchen von Dateien Text eingeben" + #. placeholder {0}: agent.label #. placeholder {0}: agentStatus.label #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx diff --git a/src/renderer/locales/en/messages.po b/src/renderer/locales/en/messages.po index f6bd67292..7bc0703dd 100644 --- a/src/renderer/locales/en/messages.po +++ b/src/renderer/locales/en/messages.po @@ -568,6 +568,7 @@ msgid "Action name" msgstr "Action name" #: src/mobile/views/pr/PrOverviewPage.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/ActionsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx @@ -848,6 +849,7 @@ msgstr "AI providers" msgid "Alerts when threads need you" msgstr "Alerts when threads need you" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/commands/shortcutCatalog.ts #: src/renderer/components/skills/SkillsManager.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -1946,7 +1948,6 @@ msgid "Close scanner" msgstr "Close scanner" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Close search" msgstr "Close search" @@ -2048,8 +2049,6 @@ msgstr "Collapse terminal composer" msgid "Collapse todo dock" msgstr "Collapse todo dock" -#. Accessible label for the command palette search input -#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/mcp/McpServerEditor.tsx msgid "Command" msgstr "Command" @@ -3648,6 +3647,10 @@ msgstr "File is too large to preview." msgid "File no longer exists on disk." msgstr "File no longer exists on disk." +#: src/renderer/commands/CommandPalette.tsx +msgid "File search unavailable" +msgstr "File search unavailable" + #. placeholder {0}: entry.insertions + entry.deletions #. placeholder {0}: file.insertions + file.deletions #: src/renderer/views/GitReviewOverlay/parts/GitDiffContent/parts/DiffSection.tsx @@ -3661,6 +3664,7 @@ msgid "File uses an unsupported encoding." msgstr "File uses an unsupported encoding." #: src/mobile/views/WorkspaceView.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/layout/UnifiedRightPanel.tsx msgid "Files" msgstr "Files" @@ -5243,6 +5247,11 @@ msgstr "Move browser to window" msgid "Move changes to a new worktree" msgstr "Move changes to a new worktree" +#. placeholder {0}: props.thread.title +#: src/renderer/commands/EverythingSearchResultRow.tsx +msgid "Move thread {0}" +msgstr "Move thread {0}" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Move todo dock to right panel" @@ -5552,8 +5561,8 @@ msgid "No checks reported for this PR." msgstr "No checks reported for this PR." #: src/renderer/commands/CommandPalette.tsx -msgid "No commands found" -msgstr "No commands found" +#~ msgid "No commands found" +#~ msgstr "No commands found" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "No commits found." @@ -5666,7 +5675,6 @@ msgstr "No matching schedules." #~ msgstr "No matching tasks." #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "No matching threads" msgstr "No matching threads" @@ -5757,6 +5765,7 @@ msgstr "No remote environments connected yet." msgid "No repositories found." msgstr "No repositories found." +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/find/FindBar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx msgid "No results" @@ -5812,8 +5821,8 @@ msgid "No thread selected" msgstr "No thread selected" #: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx -msgid "No threads" -msgstr "No threads" +#~ msgid "No threads" +#~ msgstr "No threads" #: src/mobile/views/ThreadsView.tsx msgid "No threads in this project" @@ -6031,8 +6040,8 @@ msgid "Open check" msgstr "Open check" #: src/renderer/commands/registry.ts -msgid "Open Command Palette" -msgstr "Open Command Palette" +#~ msgid "Open Command Palette" +#~ msgstr "Open Command Palette" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "Open commit on GitHub" @@ -7709,6 +7718,9 @@ msgstr "Scroll speed multiplier for the terminal scrollback buffer." msgid "Scroll to bottom" msgstr "Scroll to bottom" +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx +#: src/renderer/commands/registry.ts #: src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts #: src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -7811,15 +7823,14 @@ msgstr "Search the current view" #: src/mobile/NarrowShell.tsx #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Search threads" msgstr "Search threads" #: src/renderer/commands/registry.ts -msgid "Search Threads" -msgstr "Search Threads" +#~ msgid "Search Threads" +#~ msgstr "Search Threads" -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +#: src/renderer/commands/CommandPalette.tsx msgid "Search…" msgstr "Search…" @@ -7914,6 +7925,7 @@ msgstr "Select device" msgid "Select model" msgstr "Select model" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/thread/ProjectSwitchMenu.tsx msgid "Select project" msgstr "Select project" @@ -7970,6 +7982,8 @@ msgstr "Set shortcut" #: src/mobile/chrome.ts #: src/mobile/NarrowShell.tsx +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -9212,9 +9226,9 @@ msgstr "Thread sort order" msgid "Thread todo dock" msgstr "Thread todo dock" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/ThreadSettings.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Threads" msgstr "Threads" @@ -9377,8 +9391,8 @@ msgid "Type" msgstr "Type" #: src/renderer/commands/CommandPalette.tsx -msgid "Type a command" -msgstr "Type a command" +#~ msgid "Type a command" +#~ msgstr "Type a command" #: src/mobile/views/BrowserView.tsx msgid "Type into the page" @@ -9388,6 +9402,10 @@ msgstr "Type into the page" msgid "Type into the page (tap a field first)" msgstr "Type into the page (tap a field first)" +#: src/renderer/commands/CommandPalette.tsx +msgid "Type to search files" +msgstr "Type to search files" + #. placeholder {0}: agent.label #. placeholder {0}: agentStatus.label #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx diff --git a/src/renderer/locales/es/messages.po b/src/renderer/locales/es/messages.po index 8738a1f3a..738649a2c 100644 --- a/src/renderer/locales/es/messages.po +++ b/src/renderer/locales/es/messages.po @@ -568,6 +568,7 @@ msgid "Action name" msgstr "Nombre de la acción" #: src/mobile/views/pr/PrOverviewPage.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/ActionsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx @@ -848,6 +849,7 @@ msgstr "Proveedores de IA" msgid "Alerts when threads need you" msgstr "Alertas cuando los hilos necesitan tu atención" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/commands/shortcutCatalog.ts #: src/renderer/components/skills/SkillsManager.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -1946,7 +1948,6 @@ msgid "Close scanner" msgstr "Cerrar escáner" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Close search" msgstr "Cerrar búsqueda" @@ -2048,8 +2049,6 @@ msgstr "Contraer el redactor del terminal" msgid "Collapse todo dock" msgstr "Contraer panel de tareas" -#. Accessible label for the command palette search input -#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/mcp/McpServerEditor.tsx msgid "Command" msgstr "Comando" @@ -3648,6 +3647,10 @@ msgstr "El archivo es demasiado grande para previsualizar." msgid "File no longer exists on disk." msgstr "El archivo ya no existe en el disco." +#: src/renderer/commands/CommandPalette.tsx +msgid "File search unavailable" +msgstr "La búsqueda de archivos no está disponible" + #. placeholder {0}: entry.insertions + entry.deletions #. placeholder {0}: file.insertions + file.deletions #: src/renderer/views/GitReviewOverlay/parts/GitDiffContent/parts/DiffSection.tsx @@ -3661,6 +3664,7 @@ msgid "File uses an unsupported encoding." msgstr "El archivo usa una codificación no compatible." #: src/mobile/views/WorkspaceView.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/layout/UnifiedRightPanel.tsx msgid "Files" msgstr "Archivos" @@ -5243,6 +5247,11 @@ msgstr "Mover navegador a una ventana" msgid "Move changes to a new worktree" msgstr "Mover cambios a un nuevo worktree" +#. placeholder {0}: props.thread.title +#: src/renderer/commands/EverythingSearchResultRow.tsx +msgid "Move thread {0}" +msgstr "Mover hilo {0}" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Mover el dock de tareas al panel derecho" @@ -5552,8 +5561,8 @@ msgid "No checks reported for this PR." msgstr "No se reportaron comprobaciones para este PR." #: src/renderer/commands/CommandPalette.tsx -msgid "No commands found" -msgstr "No se encontraron comandos" +#~ msgid "No commands found" +#~ msgstr "No se encontraron comandos" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "No commits found." @@ -5666,7 +5675,6 @@ msgstr "No hay programaciones coincidentes." #~ msgstr "No hay tareas coincidentes." #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "No matching threads" msgstr "No hay hilos coincidentes" @@ -5757,6 +5765,7 @@ msgstr "Todavía no hay entornos remotos conectados." msgid "No repositories found." msgstr "No se encontraron repositorios." +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/find/FindBar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx msgid "No results" @@ -5812,8 +5821,8 @@ msgid "No thread selected" msgstr "No hay hilo seleccionado" #: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx -msgid "No threads" -msgstr "Sin hilos" +#~ msgid "No threads" +#~ msgstr "Sin hilos" #: src/mobile/views/ThreadsView.tsx msgid "No threads in this project" @@ -6031,8 +6040,8 @@ msgid "Open check" msgstr "Abrir comprobación" #: src/renderer/commands/registry.ts -msgid "Open Command Palette" -msgstr "Abrir paleta de comandos" +#~ msgid "Open Command Palette" +#~ msgstr "Abrir paleta de comandos" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "Open commit on GitHub" @@ -7709,6 +7718,9 @@ msgstr "Multiplicador de la velocidad de desplazamiento del búfer de historial msgid "Scroll to bottom" msgstr "Desplazar al final" +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx +#: src/renderer/commands/registry.ts #: src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts #: src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -7811,15 +7823,14 @@ msgstr "Buscar en la vista actual" #: src/mobile/NarrowShell.tsx #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Search threads" msgstr "Buscar hilos" #: src/renderer/commands/registry.ts -msgid "Search Threads" -msgstr "Buscar hilos" +#~ msgid "Search Threads" +#~ msgstr "Buscar hilos" -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +#: src/renderer/commands/CommandPalette.tsx msgid "Search…" msgstr "Buscar…" @@ -7914,6 +7925,7 @@ msgstr "Seleccionar dispositivo" msgid "Select model" msgstr "Seleccionar modelo" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/thread/ProjectSwitchMenu.tsx msgid "Select project" msgstr "Seleccionar proyecto" @@ -7970,6 +7982,8 @@ msgstr "Asignar atajo" #: src/mobile/chrome.ts #: src/mobile/NarrowShell.tsx +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -9212,9 +9226,9 @@ msgstr "Orden de los hilos" msgid "Thread todo dock" msgstr "Panel de tareas del hilo" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/ThreadSettings.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Threads" msgstr "Hilos" @@ -9377,8 +9391,8 @@ msgid "Type" msgstr "Tipo" #: src/renderer/commands/CommandPalette.tsx -msgid "Type a command" -msgstr "Escribe un comando" +#~ msgid "Type a command" +#~ msgstr "Escribe un comando" #: src/mobile/views/BrowserView.tsx msgid "Type into the page" @@ -9388,6 +9402,10 @@ msgstr "Escribir en la página" msgid "Type into the page (tap a field first)" msgstr "Escribe en la página (toca primero un campo)" +#: src/renderer/commands/CommandPalette.tsx +msgid "Type to search files" +msgstr "Escribe para buscar archivos" + #. placeholder {0}: agent.label #. placeholder {0}: agentStatus.label #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx diff --git a/src/renderer/locales/fr/messages.po b/src/renderer/locales/fr/messages.po index e0202ca50..dc3fc1923 100644 --- a/src/renderer/locales/fr/messages.po +++ b/src/renderer/locales/fr/messages.po @@ -568,6 +568,7 @@ msgid "Action name" msgstr "Nom de l'action" #: src/mobile/views/pr/PrOverviewPage.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/ActionsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx @@ -848,6 +849,7 @@ msgstr "Fournisseurs d'IA" msgid "Alerts when threads need you" msgstr "Alertes lorsque les fils ont besoin de vous" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/commands/shortcutCatalog.ts #: src/renderer/components/skills/SkillsManager.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -1946,7 +1948,6 @@ msgid "Close scanner" msgstr "Fermer le scanner" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Close search" msgstr "Fermer la recherche" @@ -2048,8 +2049,6 @@ msgstr "Réduire le compositeur du terminal" msgid "Collapse todo dock" msgstr "Réduire le dock des tâches" -#. Accessible label for the command palette search input -#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/mcp/McpServerEditor.tsx msgid "Command" msgstr "Commande" @@ -3648,6 +3647,10 @@ msgstr "Le fichier est trop volumineux pour être prévisualisé." msgid "File no longer exists on disk." msgstr "Le fichier n'existe plus sur le disque." +#: src/renderer/commands/CommandPalette.tsx +msgid "File search unavailable" +msgstr "La recherche de fichiers est indisponible" + #. placeholder {0}: entry.insertions + entry.deletions #. placeholder {0}: file.insertions + file.deletions #: src/renderer/views/GitReviewOverlay/parts/GitDiffContent/parts/DiffSection.tsx @@ -3661,6 +3664,7 @@ msgid "File uses an unsupported encoding." msgstr "Le fichier utilise un encodage non pris en charge." #: src/mobile/views/WorkspaceView.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/layout/UnifiedRightPanel.tsx msgid "Files" msgstr "Fichiers" @@ -5242,6 +5246,11 @@ msgstr "Déplacer le navigateur vers une fenêtre" msgid "Move changes to a new worktree" msgstr "Déplacer les modifications vers un nouvel arbre de travail" +#. placeholder {0}: props.thread.title +#: src/renderer/commands/EverythingSearchResultRow.tsx +msgid "Move thread {0}" +msgstr "Déplacer le fil de discussion {0}" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Déplacer le dock Todo vers le panneau de droite" @@ -5551,8 +5560,8 @@ msgid "No checks reported for this PR." msgstr "Aucun contrôle signalé pour cette PR." #: src/renderer/commands/CommandPalette.tsx -msgid "No commands found" -msgstr "Aucune commande trouvée" +#~ msgid "No commands found" +#~ msgstr "Aucune commande trouvée" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "No commits found." @@ -5665,7 +5674,6 @@ msgstr "Aucune planification correspondante." #~ msgstr "Aucune tâche correspondante." #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "No matching threads" msgstr "Aucun fil correspondant" @@ -5756,6 +5764,7 @@ msgstr "Aucun environnement distant n’est encore connecté." msgid "No repositories found." msgstr "Aucun dépôt trouvé." +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/find/FindBar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx msgid "No results" @@ -5811,8 +5820,8 @@ msgid "No thread selected" msgstr "Aucun fil sélectionné" #: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx -msgid "No threads" -msgstr "Aucun fil de discussion" +#~ msgid "No threads" +#~ msgstr "Aucun fil de discussion" #: src/mobile/views/ThreadsView.tsx msgid "No threads in this project" @@ -6030,8 +6039,8 @@ msgid "Open check" msgstr "Ouvrir la vérification" #: src/renderer/commands/registry.ts -msgid "Open Command Palette" -msgstr "Ouvrir la palette de commandes" +#~ msgid "Open Command Palette" +#~ msgstr "Ouvrir la palette de commandes" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "Open commit on GitHub" @@ -7708,6 +7717,9 @@ msgstr "Multiplicateur de vitesse de défilement pour le tampon de défilement d msgid "Scroll to bottom" msgstr "Faire défiler vers le bas" +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx +#: src/renderer/commands/registry.ts #: src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts #: src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -7810,15 +7822,14 @@ msgstr "Rechercher dans la vue actuelle" #: src/mobile/NarrowShell.tsx #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Search threads" msgstr "Rechercher des fils" #: src/renderer/commands/registry.ts -msgid "Search Threads" -msgstr "Rechercher des fils" +#~ msgid "Search Threads" +#~ msgstr "Rechercher des fils" -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +#: src/renderer/commands/CommandPalette.tsx msgid "Search…" msgstr "Rechercher…" @@ -7913,6 +7924,7 @@ msgstr "Sélectionner un appareil" msgid "Select model" msgstr "Sélectionnez le modèle" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/thread/ProjectSwitchMenu.tsx msgid "Select project" msgstr "Sélectionnez un projet" @@ -7969,6 +7981,8 @@ msgstr "Définir un raccourci" #: src/mobile/chrome.ts #: src/mobile/NarrowShell.tsx +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -9211,9 +9225,9 @@ msgstr "Ordre de tri des fils de discussion" msgid "Thread todo dock" msgstr "Dock des tâches du fil" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/ThreadSettings.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Threads" msgstr "Fils de discussion" @@ -9376,8 +9390,8 @@ msgid "Type" msgstr "Type" #: src/renderer/commands/CommandPalette.tsx -msgid "Type a command" -msgstr "Tapez une commande" +#~ msgid "Type a command" +#~ msgstr "Tapez une commande" #: src/mobile/views/BrowserView.tsx msgid "Type into the page" @@ -9387,6 +9401,10 @@ msgstr "Saisir dans la page" msgid "Type into the page (tap a field first)" msgstr "Saisir dans la page (touchez d'abord un champ)" +#: src/renderer/commands/CommandPalette.tsx +msgid "Type to search files" +msgstr "Saisissez du texte pour rechercher des fichiers" + #. placeholder {0}: agent.label #. placeholder {0}: agentStatus.label #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx diff --git a/src/renderer/locales/ja/messages.po b/src/renderer/locales/ja/messages.po index f3a43bac8..c094efa22 100644 --- a/src/renderer/locales/ja/messages.po +++ b/src/renderer/locales/ja/messages.po @@ -567,6 +567,7 @@ msgid "Action name" msgstr "アクション名" #: src/mobile/views/pr/PrOverviewPage.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/ActionsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx @@ -847,6 +848,7 @@ msgstr "AI プロバイダー" msgid "Alerts when threads need you" msgstr "スレッドが対応を必要としたら通知" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/commands/shortcutCatalog.ts #: src/renderer/components/skills/SkillsManager.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -1945,7 +1947,6 @@ msgid "Close scanner" msgstr "スキャナーを閉じる" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Close search" msgstr "検索を閉じる" @@ -2047,8 +2048,6 @@ msgstr "ターミナルコンポーザーを折りたたむ" msgid "Collapse todo dock" msgstr "todo ドックを折りたたむ" -#. Accessible label for the command palette search input -#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/mcp/McpServerEditor.tsx msgid "Command" msgstr "コマンド" @@ -3647,6 +3646,10 @@ msgstr "ファイルが大きすぎてプレビューできません。" msgid "File no longer exists on disk." msgstr "ファイルはディスク上に存在しません。" +#: src/renderer/commands/CommandPalette.tsx +msgid "File search unavailable" +msgstr "ファイル検索を利用できません" + #. placeholder {0}: entry.insertions + entry.deletions #. placeholder {0}: file.insertions + file.deletions #: src/renderer/views/GitReviewOverlay/parts/GitDiffContent/parts/DiffSection.tsx @@ -3660,6 +3663,7 @@ msgid "File uses an unsupported encoding." msgstr "ファイルはサポートされていないエンコーディングを使用しています。" #: src/mobile/views/WorkspaceView.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/layout/UnifiedRightPanel.tsx msgid "Files" msgstr "ファイル" @@ -5241,6 +5245,11 @@ msgstr "ブラウザーをウィンドウに移動" msgid "Move changes to a new worktree" msgstr "変更を新しいワークツリーに移動する" +#. placeholder {0}: props.thread.title +#: src/renderer/commands/EverythingSearchResultRow.tsx +msgid "Move thread {0}" +msgstr "スレッド「{0}」を移動" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Todo ドックを右側のパネルに移動します" @@ -5550,8 +5559,8 @@ msgid "No checks reported for this PR." msgstr "この PR についてはチェックが報告されていません。" #: src/renderer/commands/CommandPalette.tsx -msgid "No commands found" -msgstr "コマンドが見つかりません" +#~ msgid "No commands found" +#~ msgstr "コマンドが見つかりません" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "No commits found." @@ -5664,7 +5673,6 @@ msgstr "一致するスケジュールはありません。" #~ msgstr "一致するタスクはありません。" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "No matching threads" msgstr "一致するスレッドがありません" @@ -5755,6 +5763,7 @@ msgstr "接続済みのリモート環境はまだありません。" msgid "No repositories found." msgstr "リポジトリが見つかりません。" +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/find/FindBar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx msgid "No results" @@ -5810,8 +5819,8 @@ msgid "No thread selected" msgstr "スレッドが選択されていません" #: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx -msgid "No threads" -msgstr "スレッドがありません" +#~ msgid "No threads" +#~ msgstr "スレッドがありません" #: src/mobile/views/ThreadsView.tsx msgid "No threads in this project" @@ -6029,8 +6038,8 @@ msgid "Open check" msgstr "チェックを開く" #: src/renderer/commands/registry.ts -msgid "Open Command Palette" -msgstr "コマンドパレットを開く" +#~ msgid "Open Command Palette" +#~ msgstr "コマンドパレットを開く" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "Open commit on GitHub" @@ -7707,6 +7716,9 @@ msgstr "端末のスクロールバック バッファのスクロール速度 msgid "Scroll to bottom" msgstr "一番下までスクロール" +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx +#: src/renderer/commands/registry.ts #: src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts #: src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -7809,15 +7821,14 @@ msgstr "現在のビューを検索" #: src/mobile/NarrowShell.tsx #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Search threads" msgstr "スレッドを検索する" #: src/renderer/commands/registry.ts -msgid "Search Threads" -msgstr "スレッドの検索" +#~ msgid "Search Threads" +#~ msgstr "スレッドの検索" -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +#: src/renderer/commands/CommandPalette.tsx msgid "Search…" msgstr "検索…" @@ -7912,6 +7923,7 @@ msgstr "デバイスを選択" msgid "Select model" msgstr "モデルを選択してください" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/thread/ProjectSwitchMenu.tsx msgid "Select project" msgstr "プロジェクトの選択" @@ -7968,6 +7980,8 @@ msgstr "ショートカットを設定" #: src/mobile/chrome.ts #: src/mobile/NarrowShell.tsx +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -9210,9 +9224,9 @@ msgstr "スレッドのソート順" msgid "Thread todo dock" msgstr "スレッドのやることドック" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/ThreadSettings.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Threads" msgstr "スレッド" @@ -9375,8 +9389,8 @@ msgid "Type" msgstr "タイプ" #: src/renderer/commands/CommandPalette.tsx -msgid "Type a command" -msgstr "コマンドを入力してください" +#~ msgid "Type a command" +#~ msgstr "コマンドを入力してください" #: src/mobile/views/BrowserView.tsx msgid "Type into the page" @@ -9386,6 +9400,10 @@ msgstr "ページに入力" msgid "Type into the page (tap a field first)" msgstr "ページに入力(先にフィールドをタップ)" +#: src/renderer/commands/CommandPalette.tsx +msgid "Type to search files" +msgstr "入力してファイルを検索" + #. placeholder {0}: agent.label #. placeholder {0}: agentStatus.label #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx diff --git a/src/renderer/locales/ko/messages.po b/src/renderer/locales/ko/messages.po index b0cec78ae..3331770d8 100644 --- a/src/renderer/locales/ko/messages.po +++ b/src/renderer/locales/ko/messages.po @@ -568,6 +568,7 @@ msgid "Action name" msgstr "액션 이름" #: src/mobile/views/pr/PrOverviewPage.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/ActionsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx @@ -848,6 +849,7 @@ msgstr "AI 공급자" msgid "Alerts when threads need you" msgstr "스레드에 응답이 필요할 때 알림" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/commands/shortcutCatalog.ts #: src/renderer/components/skills/SkillsManager.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -1946,7 +1948,6 @@ msgid "Close scanner" msgstr "스캐너 닫기" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Close search" msgstr "검색 닫기" @@ -2048,8 +2049,6 @@ msgstr "터미널 작성기 접기" msgid "Collapse todo dock" msgstr "할 일 도크 접기" -#. Accessible label for the command palette search input -#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/mcp/McpServerEditor.tsx msgid "Command" msgstr "명령" @@ -3648,6 +3647,10 @@ msgstr "파일이 너무 커서 미리 볼 수 없습니다." msgid "File no longer exists on disk." msgstr "파일이 더 이상 디스크에 존재하지 않습니다." +#: src/renderer/commands/CommandPalette.tsx +msgid "File search unavailable" +msgstr "파일 검색을 사용할 수 없습니다" + #. placeholder {0}: entry.insertions + entry.deletions #. placeholder {0}: file.insertions + file.deletions #: src/renderer/views/GitReviewOverlay/parts/GitDiffContent/parts/DiffSection.tsx @@ -3661,6 +3664,7 @@ msgid "File uses an unsupported encoding." msgstr "파일이 지원되지 않는 인코딩을 사용합니다." #: src/mobile/views/WorkspaceView.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/layout/UnifiedRightPanel.tsx msgid "Files" msgstr "파일" @@ -5243,6 +5247,11 @@ msgstr "브라우저를 창으로 이동" msgid "Move changes to a new worktree" msgstr "변경 사항을 새 작업 트리로 이동" +#. placeholder {0}: props.thread.title +#: src/renderer/commands/EverythingSearchResultRow.tsx +msgid "Move thread {0}" +msgstr "스레드 {0} 이동" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "할 일 도크를 오른쪽 패널로 이동" @@ -5552,8 +5561,8 @@ msgid "No checks reported for this PR." msgstr "이 PR에 대해 보고된 검사가 없습니다." #: src/renderer/commands/CommandPalette.tsx -msgid "No commands found" -msgstr "명령을 찾을 수 없습니다." +#~ msgid "No commands found" +#~ msgstr "명령을 찾을 수 없습니다." #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "No commits found." @@ -5666,7 +5675,6 @@ msgstr "일치하는 일정이 없습니다." #~ msgstr "일치하는 작업이 없습니다." #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "No matching threads" msgstr "일치하는 스레드 없음" @@ -5757,6 +5765,7 @@ msgstr "아직 연결된 원격 환경이 없습니다." msgid "No repositories found." msgstr "저장소를 찾을 수 없습니다." +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/find/FindBar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx msgid "No results" @@ -5812,8 +5821,8 @@ msgid "No thread selected" msgstr "선택된 스레드 없음" #: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx -msgid "No threads" -msgstr "스레드 없음" +#~ msgid "No threads" +#~ msgstr "스레드 없음" #: src/mobile/views/ThreadsView.tsx msgid "No threads in this project" @@ -6031,8 +6040,8 @@ msgid "Open check" msgstr "체크 열기" #: src/renderer/commands/registry.ts -msgid "Open Command Palette" -msgstr "명령 팔레트 열기" +#~ msgid "Open Command Palette" +#~ msgstr "명령 팔레트 열기" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "Open commit on GitHub" @@ -7709,6 +7718,9 @@ msgstr "터미널 스크롤백 버퍼의 스크롤 속도 승수입니다." msgid "Scroll to bottom" msgstr "맨 아래로 스크롤" +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx +#: src/renderer/commands/registry.ts #: src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts #: src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -7811,15 +7823,14 @@ msgstr "현재 보기에서 검색" #: src/mobile/NarrowShell.tsx #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Search threads" msgstr "스레드 검색" #: src/renderer/commands/registry.ts -msgid "Search Threads" -msgstr "스레드 검색" +#~ msgid "Search Threads" +#~ msgstr "스레드 검색" -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +#: src/renderer/commands/CommandPalette.tsx msgid "Search…" msgstr "검색…" @@ -7914,6 +7925,7 @@ msgstr "기기 선택" msgid "Select model" msgstr "모델 선택" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/thread/ProjectSwitchMenu.tsx msgid "Select project" msgstr "프로젝트 선택" @@ -7970,6 +7982,8 @@ msgstr "단축키 설정" #: src/mobile/chrome.ts #: src/mobile/NarrowShell.tsx +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -9212,9 +9226,9 @@ msgstr "스레드 정렬 순서" msgid "Thread todo dock" msgstr "스레드 할 일 도크" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/ThreadSettings.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Threads" msgstr "스레드" @@ -9377,8 +9391,8 @@ msgid "Type" msgstr "유형" #: src/renderer/commands/CommandPalette.tsx -msgid "Type a command" -msgstr "명령을 입력하세요" +#~ msgid "Type a command" +#~ msgstr "명령을 입력하세요" #: src/mobile/views/BrowserView.tsx msgid "Type into the page" @@ -9388,6 +9402,10 @@ msgstr "페이지에 입력" msgid "Type into the page (tap a field first)" msgstr "페이지에 입력(먼저 필드를 탭하세요)" +#: src/renderer/commands/CommandPalette.tsx +msgid "Type to search files" +msgstr "파일을 검색하려면 입력하세요" + #. placeholder {0}: agent.label #. placeholder {0}: agentStatus.label #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx diff --git a/src/renderer/locales/pl/messages.po b/src/renderer/locales/pl/messages.po index a86673edd..dccfe340f 100644 --- a/src/renderer/locales/pl/messages.po +++ b/src/renderer/locales/pl/messages.po @@ -568,6 +568,7 @@ msgid "Action name" msgstr "Nazwa akcji" #: src/mobile/views/pr/PrOverviewPage.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/ActionsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx @@ -848,6 +849,7 @@ msgstr "Dostawcy AI" msgid "Alerts when threads need you" msgstr "Alerty, gdy wątki wymagają Twojej uwagi" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/commands/shortcutCatalog.ts #: src/renderer/components/skills/SkillsManager.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -1946,7 +1948,6 @@ msgid "Close scanner" msgstr "Zamknij skaner" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Close search" msgstr "Zamknij wyszukiwanie" @@ -2048,8 +2049,6 @@ msgstr "Zwiń kompozytor terminala" msgid "Collapse todo dock" msgstr "Zwiń dok zadań do wykonania" -#. Accessible label for the command palette search input -#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/mcp/McpServerEditor.tsx msgid "Command" msgstr "Polecenie" @@ -3648,6 +3647,10 @@ msgstr "Plik jest zbyt duży, aby wyświetlić podgląd." msgid "File no longer exists on disk." msgstr "Plik nie istnieje już na dysku." +#: src/renderer/commands/CommandPalette.tsx +msgid "File search unavailable" +msgstr "Wyszukiwanie plików jest niedostępne" + #. placeholder {0}: entry.insertions + entry.deletions #. placeholder {0}: file.insertions + file.deletions #: src/renderer/views/GitReviewOverlay/parts/GitDiffContent/parts/DiffSection.tsx @@ -3661,6 +3664,7 @@ msgid "File uses an unsupported encoding." msgstr "Plik używa nieobsługiwanego kodowania." #: src/mobile/views/WorkspaceView.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/layout/UnifiedRightPanel.tsx msgid "Files" msgstr "Pliki" @@ -5243,6 +5247,11 @@ msgstr "Przenieś przeglądarkę do okna" msgid "Move changes to a new worktree" msgstr "Przenieś zmiany do nowego drzewa roboczego" +#. placeholder {0}: props.thread.title +#: src/renderer/commands/EverythingSearchResultRow.tsx +msgid "Move thread {0}" +msgstr "Przenieś wątek {0}" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Przenieś dok zadań do prawego panelu" @@ -5552,8 +5561,8 @@ msgid "No checks reported for this PR." msgstr "Dla tego PR nie zgłoszono żadnych kontroli." #: src/renderer/commands/CommandPalette.tsx -msgid "No commands found" -msgstr "Nie znaleziono żadnych poleceń" +#~ msgid "No commands found" +#~ msgstr "Nie znaleziono żadnych poleceń" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "No commits found." @@ -5666,7 +5675,6 @@ msgstr "Brak pasujących harmonogramów." #~ msgstr "Brak pasujących zadań." #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "No matching threads" msgstr "Brak pasujących wątków" @@ -5757,6 +5765,7 @@ msgstr "Nie połączono jeszcze żadnych środowisk zdalnych." msgid "No repositories found." msgstr "Nie znaleziono repozytoriów." +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/find/FindBar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx msgid "No results" @@ -5812,8 +5821,8 @@ msgid "No thread selected" msgstr "Nie wybrano wątku" #: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx -msgid "No threads" -msgstr "Brak wątków" +#~ msgid "No threads" +#~ msgstr "Brak wątków" #: src/mobile/views/ThreadsView.tsx msgid "No threads in this project" @@ -6031,8 +6040,8 @@ msgid "Open check" msgstr "Otwórz kontrolę" #: src/renderer/commands/registry.ts -msgid "Open Command Palette" -msgstr "Otwórz paletę poleceń" +#~ msgid "Open Command Palette" +#~ msgstr "Otwórz paletę poleceń" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "Open commit on GitHub" @@ -7709,6 +7718,9 @@ msgstr "Mnożnik prędkości przewijania dla bufora przewijania terminala." msgid "Scroll to bottom" msgstr "Przewiń w dół" +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx +#: src/renderer/commands/registry.ts #: src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts #: src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -7811,15 +7823,14 @@ msgstr "Przeszukaj bieżący widok" #: src/mobile/NarrowShell.tsx #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Search threads" msgstr "Przeszukaj wątki" #: src/renderer/commands/registry.ts -msgid "Search Threads" -msgstr "Przeszukaj wątki" +#~ msgid "Search Threads" +#~ msgstr "Przeszukaj wątki" -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +#: src/renderer/commands/CommandPalette.tsx msgid "Search…" msgstr "Szukaj…" @@ -7914,6 +7925,7 @@ msgstr "Wybierz urządzenie" msgid "Select model" msgstr "Wybierz model" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/thread/ProjectSwitchMenu.tsx msgid "Select project" msgstr "Wybierz projekt" @@ -7970,6 +7982,8 @@ msgstr "Ustaw skrót" #: src/mobile/chrome.ts #: src/mobile/NarrowShell.tsx +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -9212,9 +9226,9 @@ msgstr "Kolejność sortowania wątków" msgid "Thread todo dock" msgstr "Dok zadań wątku" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/ThreadSettings.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Threads" msgstr "Wątki" @@ -9377,8 +9391,8 @@ msgid "Type" msgstr "Typ" #: src/renderer/commands/CommandPalette.tsx -msgid "Type a command" -msgstr "Wpisz polecenie" +#~ msgid "Type a command" +#~ msgstr "Wpisz polecenie" #: src/mobile/views/BrowserView.tsx msgid "Type into the page" @@ -9388,6 +9402,10 @@ msgstr "Pisz na stronie" msgid "Type into the page (tap a field first)" msgstr "Pisz na stronie (najpierw stuknij pole)" +#: src/renderer/commands/CommandPalette.tsx +msgid "Type to search files" +msgstr "Wpisz, aby wyszukać pliki" + #. placeholder {0}: agent.label #. placeholder {0}: agentStatus.label #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx diff --git a/src/renderer/locales/pt-BR/messages.po b/src/renderer/locales/pt-BR/messages.po index 001bc8cd7..2389288bd 100644 --- a/src/renderer/locales/pt-BR/messages.po +++ b/src/renderer/locales/pt-BR/messages.po @@ -568,6 +568,7 @@ msgid "Action name" msgstr "Nome da ação" #: src/mobile/views/pr/PrOverviewPage.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/ActionsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx @@ -848,6 +849,7 @@ msgstr "Provedores de IA" msgid "Alerts when threads need you" msgstr "Alertas quando threads precisam de você" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/commands/shortcutCatalog.ts #: src/renderer/components/skills/SkillsManager.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -1946,7 +1948,6 @@ msgid "Close scanner" msgstr "Fechar scanner" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Close search" msgstr "Fechar pesquisa" @@ -2048,8 +2049,6 @@ msgstr "Recolher o compositor do terminal" msgid "Collapse todo dock" msgstr "Recolher dock de tarefas" -#. Accessible label for the command palette search input -#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/mcp/McpServerEditor.tsx msgid "Command" msgstr "Comando" @@ -3648,6 +3647,10 @@ msgstr "O arquivo é muito grande para ser visualizado." msgid "File no longer exists on disk." msgstr "O arquivo não existe mais no disco." +#: src/renderer/commands/CommandPalette.tsx +msgid "File search unavailable" +msgstr "A pesquisa de arquivos não está disponível" + #. placeholder {0}: entry.insertions + entry.deletions #. placeholder {0}: file.insertions + file.deletions #: src/renderer/views/GitReviewOverlay/parts/GitDiffContent/parts/DiffSection.tsx @@ -3661,6 +3664,7 @@ msgid "File uses an unsupported encoding." msgstr "O arquivo usa uma codificação não suportada." #: src/mobile/views/WorkspaceView.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/layout/UnifiedRightPanel.tsx msgid "Files" msgstr "Arquivos" @@ -5243,6 +5247,11 @@ msgstr "Mover navegador para uma janela" msgid "Move changes to a new worktree" msgstr "Mover alterações para uma nova árvore de trabalho" +#. placeholder {0}: props.thread.title +#: src/renderer/commands/EverythingSearchResultRow.tsx +msgid "Move thread {0}" +msgstr "Mover tópico {0}" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Mover dock de tarefas para o painel direito" @@ -5552,8 +5561,8 @@ msgid "No checks reported for this PR." msgstr "Nenhuma verificação relatada para este PR." #: src/renderer/commands/CommandPalette.tsx -msgid "No commands found" -msgstr "Nenhum comando encontrado" +#~ msgid "No commands found" +#~ msgstr "Nenhum comando encontrado" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "No commits found." @@ -5666,7 +5675,6 @@ msgstr "Nenhum agendamento correspondente." #~ msgstr "Nenhuma tarefa correspondente." #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "No matching threads" msgstr "Não há tópicos correspondentes" @@ -5757,6 +5765,7 @@ msgstr "Ainda não há ambientes remotos conectados." msgid "No repositories found." msgstr "Nenhum repositório encontrado." +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/find/FindBar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx msgid "No results" @@ -5812,8 +5821,8 @@ msgid "No thread selected" msgstr "Nenhuma thread selecionada" #: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx -msgid "No threads" -msgstr "Sem tópicos" +#~ msgid "No threads" +#~ msgstr "Sem tópicos" #: src/mobile/views/ThreadsView.tsx msgid "No threads in this project" @@ -6031,8 +6040,8 @@ msgid "Open check" msgstr "Abrir verificação" #: src/renderer/commands/registry.ts -msgid "Open Command Palette" -msgstr "Abrir paleta de comandos" +#~ msgid "Open Command Palette" +#~ msgstr "Abrir paleta de comandos" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "Open commit on GitHub" @@ -7709,6 +7718,9 @@ msgstr "Multiplicador de velocidade de rolagem para o buffer de rolagem do termi msgid "Scroll to bottom" msgstr "Rolar até o fim" +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx +#: src/renderer/commands/registry.ts #: src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts #: src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -7811,15 +7823,14 @@ msgstr "Pesquisar na visualização atual" #: src/mobile/NarrowShell.tsx #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Search threads" msgstr "Pesquisar tópicos" #: src/renderer/commands/registry.ts -msgid "Search Threads" -msgstr "Pesquisar tópicos" +#~ msgid "Search Threads" +#~ msgstr "Pesquisar tópicos" -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +#: src/renderer/commands/CommandPalette.tsx msgid "Search…" msgstr "Pesquisar…" @@ -7914,6 +7925,7 @@ msgstr "Selecionar dispositivo" msgid "Select model" msgstr "Selecione o modelo" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/thread/ProjectSwitchMenu.tsx msgid "Select project" msgstr "Selecione o projeto" @@ -7970,6 +7982,8 @@ msgstr "Definir atalho" #: src/mobile/chrome.ts #: src/mobile/NarrowShell.tsx +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -9212,9 +9226,9 @@ msgstr "Ordem de classificação de tópicos" msgid "Thread todo dock" msgstr "Doca de tarefas do tópico" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/ThreadSettings.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Threads" msgstr "Tópicos" @@ -9377,8 +9391,8 @@ msgid "Type" msgstr "Tipo" #: src/renderer/commands/CommandPalette.tsx -msgid "Type a command" -msgstr "Digite um comando" +#~ msgid "Type a command" +#~ msgstr "Digite um comando" #: src/mobile/views/BrowserView.tsx msgid "Type into the page" @@ -9388,6 +9402,10 @@ msgstr "Digitar na página" msgid "Type into the page (tap a field first)" msgstr "Digite na página (toque primeiro em um campo)" +#: src/renderer/commands/CommandPalette.tsx +msgid "Type to search files" +msgstr "Digite para pesquisar arquivos" + #. placeholder {0}: agent.label #. placeholder {0}: agentStatus.label #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx diff --git a/src/renderer/locales/ru/messages.po b/src/renderer/locales/ru/messages.po index d91f436c6..a7ab40ed5 100644 --- a/src/renderer/locales/ru/messages.po +++ b/src/renderer/locales/ru/messages.po @@ -568,6 +568,7 @@ msgid "Action name" msgstr "Имя действия" #: src/mobile/views/pr/PrOverviewPage.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/ActionsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx @@ -848,6 +849,7 @@ msgstr "Провайдеры ИИ" msgid "Alerts when threads need you" msgstr "Оповещения, когда потокам нужно ваше внимание" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/commands/shortcutCatalog.ts #: src/renderer/components/skills/SkillsManager.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -1946,7 +1948,6 @@ msgid "Close scanner" msgstr "Закрыть сканер" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Close search" msgstr "Закрыть поиск" @@ -2048,8 +2049,6 @@ msgstr "Сворачивать поле ввода терминала" msgid "Collapse todo dock" msgstr "Свернуть панель задач" -#. Accessible label for the command palette search input -#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/mcp/McpServerEditor.tsx msgid "Command" msgstr "Команда" @@ -3648,6 +3647,10 @@ msgstr "Файл слишком большой для предпросмотра msgid "File no longer exists on disk." msgstr "Файл больше не существует на диске." +#: src/renderer/commands/CommandPalette.tsx +msgid "File search unavailable" +msgstr "Поиск файлов недоступен" + #. placeholder {0}: entry.insertions + entry.deletions #. placeholder {0}: file.insertions + file.deletions #: src/renderer/views/GitReviewOverlay/parts/GitDiffContent/parts/DiffSection.tsx @@ -3661,6 +3664,7 @@ msgid "File uses an unsupported encoding." msgstr "Файл использует неподдерживаемую кодировку." #: src/mobile/views/WorkspaceView.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/layout/UnifiedRightPanel.tsx msgid "Files" msgstr "Файлы" @@ -5243,6 +5247,11 @@ msgstr "Переместить браузер в окно" msgid "Move changes to a new worktree" msgstr "Переместить изменения в новый worktree" +#. placeholder {0}: props.thread.title +#: src/renderer/commands/EverythingSearchResultRow.tsx +msgid "Move thread {0}" +msgstr "Переместить тред {0}" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Переместить панель задач в правую панель" @@ -5552,8 +5561,8 @@ msgid "No checks reported for this PR." msgstr "Для этого PR не сообщено о проверках." #: src/renderer/commands/CommandPalette.tsx -msgid "No commands found" -msgstr "Команды не найдены" +#~ msgid "No commands found" +#~ msgstr "Команды не найдены" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "No commits found." @@ -5666,7 +5675,6 @@ msgstr "Подходящих расписаний нет." #~ msgstr "Нет подходящих задач." #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "No matching threads" msgstr "Подходящих тредов нет" @@ -5757,6 +5765,7 @@ msgstr "Удалённые среды ещё не подключены." msgid "No repositories found." msgstr "Репозитории не найдены." +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/find/FindBar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx msgid "No results" @@ -5812,8 +5821,8 @@ msgid "No thread selected" msgstr "Поток не выбран" #: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx -msgid "No threads" -msgstr "Нет тредов" +#~ msgid "No threads" +#~ msgstr "Нет тредов" #: src/mobile/views/ThreadsView.tsx msgid "No threads in this project" @@ -6031,8 +6040,8 @@ msgid "Open check" msgstr "Открыть проверку" #: src/renderer/commands/registry.ts -msgid "Open Command Palette" -msgstr "Открыть палитру команд" +#~ msgid "Open Command Palette" +#~ msgstr "Открыть палитру команд" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "Open commit on GitHub" @@ -7709,6 +7718,9 @@ msgstr "Множитель скорости прокрутки для буфер msgid "Scroll to bottom" msgstr "Прокрутить вниз" +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx +#: src/renderer/commands/registry.ts #: src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts #: src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -7811,15 +7823,14 @@ msgstr "Поиск в текущем виде" #: src/mobile/NarrowShell.tsx #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Search threads" msgstr "Поиск тредов" #: src/renderer/commands/registry.ts -msgid "Search Threads" -msgstr "Поиск тредов" +#~ msgid "Search Threads" +#~ msgstr "Поиск тредов" -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +#: src/renderer/commands/CommandPalette.tsx msgid "Search…" msgstr "Поиск…" @@ -7914,6 +7925,7 @@ msgstr "Выбрать устройство" msgid "Select model" msgstr "Выбрать модель" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/thread/ProjectSwitchMenu.tsx msgid "Select project" msgstr "Выбрать проект" @@ -7970,6 +7982,8 @@ msgstr "Назначить сочетание клавиш" #: src/mobile/chrome.ts #: src/mobile/NarrowShell.tsx +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -9212,9 +9226,9 @@ msgstr "Порядок сортировки тредов" msgid "Thread todo dock" msgstr "Панель задач треда" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/ThreadSettings.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Threads" msgstr "Треды" @@ -9377,8 +9391,8 @@ msgid "Type" msgstr "Тип" #: src/renderer/commands/CommandPalette.tsx -msgid "Type a command" -msgstr "Введите команду" +#~ msgid "Type a command" +#~ msgstr "Введите команду" #: src/mobile/views/BrowserView.tsx msgid "Type into the page" @@ -9388,6 +9402,10 @@ msgstr "Вводить на странице" msgid "Type into the page (tap a field first)" msgstr "Вводите на странице (сначала коснитесь поля)" +#: src/renderer/commands/CommandPalette.tsx +msgid "Type to search files" +msgstr "Введите запрос для поиска файлов" + #. placeholder {0}: agent.label #. placeholder {0}: agentStatus.label #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx diff --git a/src/renderer/locales/tr/messages.po b/src/renderer/locales/tr/messages.po index a786d5715..ca34eea57 100644 --- a/src/renderer/locales/tr/messages.po +++ b/src/renderer/locales/tr/messages.po @@ -568,6 +568,7 @@ msgid "Action name" msgstr "İşlem adı" #: src/mobile/views/pr/PrOverviewPage.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/ActionsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx @@ -848,6 +849,7 @@ msgstr "Yapay zeka sağlayıcıları" msgid "Alerts when threads need you" msgstr "İş parçacıkları size ihtiyaç duyduğunda uyarılar" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/commands/shortcutCatalog.ts #: src/renderer/components/skills/SkillsManager.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -1946,7 +1948,6 @@ msgid "Close scanner" msgstr "Tarayıcıyı kapat" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Close search" msgstr "Aramayı kapat" @@ -2048,8 +2049,6 @@ msgstr "Terminal yazma alanını daralt" msgid "Collapse todo dock" msgstr "Yapılacaklar panelini daralt" -#. Accessible label for the command palette search input -#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/mcp/McpServerEditor.tsx msgid "Command" msgstr "Komut" @@ -3648,6 +3647,10 @@ msgstr "Dosya önizlenemeyecek kadar büyük." msgid "File no longer exists on disk." msgstr "Dosya artık diskte mevcut değil." +#: src/renderer/commands/CommandPalette.tsx +msgid "File search unavailable" +msgstr "Dosya arama kullanılamıyor" + #. placeholder {0}: entry.insertions + entry.deletions #. placeholder {0}: file.insertions + file.deletions #: src/renderer/views/GitReviewOverlay/parts/GitDiffContent/parts/DiffSection.tsx @@ -3661,6 +3664,7 @@ msgid "File uses an unsupported encoding." msgstr "Dosya desteklenmeyen bir kodlama kullanıyor." #: src/mobile/views/WorkspaceView.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/layout/UnifiedRightPanel.tsx msgid "Files" msgstr "Dosyalar" @@ -5243,6 +5247,11 @@ msgstr "Tarayıcıyı pencereye taşı" msgid "Move changes to a new worktree" msgstr "Değişiklikleri yeni bir çalışma ağacına taşı" +#. placeholder {0}: props.thread.title +#: src/renderer/commands/EverythingSearchResultRow.tsx +msgid "Move thread {0}" +msgstr "Konuyu taşı: {0}" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Yapılacaklar panelini sağ panele taşı" @@ -5552,8 +5561,8 @@ msgid "No checks reported for this PR." msgstr "Bu PR için herhangi bir kontrol raporlanmadı." #: src/renderer/commands/CommandPalette.tsx -msgid "No commands found" -msgstr "Hiçbir komut bulunamadı" +#~ msgid "No commands found" +#~ msgstr "Hiçbir komut bulunamadı" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "No commits found." @@ -5666,7 +5675,6 @@ msgstr "Eşleşen zamanlama yok." #~ msgstr "Eşleşen görev yok." #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "No matching threads" msgstr "Eşleşen konu yok" @@ -5757,6 +5765,7 @@ msgstr "Henüz bağlı uzak ortam yok." msgid "No repositories found." msgstr "Hiçbir depo bulunamadı." +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/find/FindBar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx msgid "No results" @@ -5812,8 +5821,8 @@ msgid "No thread selected" msgstr "İş parçacığı seçilmedi" #: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx -msgid "No threads" -msgstr "Konu yok" +#~ msgid "No threads" +#~ msgstr "Konu yok" #: src/mobile/views/ThreadsView.tsx msgid "No threads in this project" @@ -6031,8 +6040,8 @@ msgid "Open check" msgstr "Kontrolü aç" #: src/renderer/commands/registry.ts -msgid "Open Command Palette" -msgstr "Komut Paletini Aç" +#~ msgid "Open Command Palette" +#~ msgstr "Komut Paletini Aç" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "Open commit on GitHub" @@ -7709,6 +7718,9 @@ msgstr "Terminal geri kaydırma arabelleği için kaydırma hızı çarpanı." msgid "Scroll to bottom" msgstr "Aşağıya doğru kaydır" +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx +#: src/renderer/commands/registry.ts #: src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts #: src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -7811,15 +7823,14 @@ msgstr "Geçerli görünümde ara" #: src/mobile/NarrowShell.tsx #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Search threads" msgstr "Konuları ara" #: src/renderer/commands/registry.ts -msgid "Search Threads" -msgstr "Konuları Ara" +#~ msgid "Search Threads" +#~ msgstr "Konuları Ara" -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +#: src/renderer/commands/CommandPalette.tsx msgid "Search…" msgstr "Ara…" @@ -7914,6 +7925,7 @@ msgstr "Cihaz seç" msgid "Select model" msgstr "Modeli seçin" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/thread/ProjectSwitchMenu.tsx msgid "Select project" msgstr "Proje seçin" @@ -7970,6 +7982,8 @@ msgstr "Kısayol belirle" #: src/mobile/chrome.ts #: src/mobile/NarrowShell.tsx +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -9212,9 +9226,9 @@ msgstr "Konu sıralama düzeni" msgid "Thread todo dock" msgstr "Konu yapılacaklar yuvası" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/ThreadSettings.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Threads" msgstr "Konular" @@ -9377,8 +9391,8 @@ msgid "Type" msgstr "Tür" #: src/renderer/commands/CommandPalette.tsx -msgid "Type a command" -msgstr "Bir komut yazın" +#~ msgid "Type a command" +#~ msgstr "Bir komut yazın" #: src/mobile/views/BrowserView.tsx msgid "Type into the page" @@ -9388,6 +9402,10 @@ msgstr "Sayfaya yaz" msgid "Type into the page (tap a field first)" msgstr "Sayfaya yazın (önce bir alana dokunun)" +#: src/renderer/commands/CommandPalette.tsx +msgid "Type to search files" +msgstr "Dosya aramak için yazın" + #. placeholder {0}: agent.label #. placeholder {0}: agentStatus.label #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx diff --git a/src/renderer/locales/uk/messages.po b/src/renderer/locales/uk/messages.po index f89153719..8e1db0975 100644 --- a/src/renderer/locales/uk/messages.po +++ b/src/renderer/locales/uk/messages.po @@ -568,6 +568,7 @@ msgid "Action name" msgstr "Назва дії" #: src/mobile/views/pr/PrOverviewPage.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/ActionsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx @@ -848,6 +849,7 @@ msgstr "Постачальники AI" msgid "Alerts when threads need you" msgstr "Сповіщення, коли потокам потрібна ваша увага" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/commands/shortcutCatalog.ts #: src/renderer/components/skills/SkillsManager.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -1946,7 +1948,6 @@ msgid "Close scanner" msgstr "Закрити сканер" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Close search" msgstr "Закрити пошук" @@ -2048,8 +2049,6 @@ msgstr "Згорнути поле вводу термінала" msgid "Collapse todo dock" msgstr "Згорнути панель завдань" -#. Accessible label for the command palette search input -#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/mcp/McpServerEditor.tsx msgid "Command" msgstr "Команда" @@ -3648,6 +3647,10 @@ msgstr "Файл занадто великий для попереднього msgid "File no longer exists on disk." msgstr "Файл більше не існує на диску." +#: src/renderer/commands/CommandPalette.tsx +msgid "File search unavailable" +msgstr "Пошук файлів недоступний" + #. placeholder {0}: entry.insertions + entry.deletions #. placeholder {0}: file.insertions + file.deletions #: src/renderer/views/GitReviewOverlay/parts/GitDiffContent/parts/DiffSection.tsx @@ -3661,6 +3664,7 @@ msgid "File uses an unsupported encoding." msgstr "Файл використовує непідтримуване кодування." #: src/mobile/views/WorkspaceView.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/layout/UnifiedRightPanel.tsx msgid "Files" msgstr "Файли" @@ -5243,6 +5247,11 @@ msgstr "Перемістити браузер у вікно" msgid "Move changes to a new worktree" msgstr "Перемістити зміни в новий worktree" +#. placeholder {0}: props.thread.title +#: src/renderer/commands/EverythingSearchResultRow.tsx +msgid "Move thread {0}" +msgstr "Перемістити тред {0}" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Перемістити панель завдань у праву панель" @@ -5552,8 +5561,8 @@ msgid "No checks reported for this PR." msgstr "Для цього PR не повідомлено про перевірки." #: src/renderer/commands/CommandPalette.tsx -msgid "No commands found" -msgstr "Команд не знайдено" +#~ msgid "No commands found" +#~ msgstr "Команд не знайдено" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "No commits found." @@ -5666,7 +5675,6 @@ msgstr "Відповідних розкладів немає." #~ msgstr "Немає відповідних завдань." #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "No matching threads" msgstr "Відповідних тредів немає" @@ -5757,6 +5765,7 @@ msgstr "Віддалені середовища ще не підключено." msgid "No repositories found." msgstr "Репозиторії не знайдено." +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/find/FindBar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx msgid "No results" @@ -5812,8 +5821,8 @@ msgid "No thread selected" msgstr "Потік не вибрано" #: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx -msgid "No threads" -msgstr "Немає тредів" +#~ msgid "No threads" +#~ msgstr "Немає тредів" #: src/mobile/views/ThreadsView.tsx msgid "No threads in this project" @@ -6031,8 +6040,8 @@ msgid "Open check" msgstr "Відкрити перевірку" #: src/renderer/commands/registry.ts -msgid "Open Command Palette" -msgstr "Відкрити палітру команд" +#~ msgid "Open Command Palette" +#~ msgstr "Відкрити палітру команд" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "Open commit on GitHub" @@ -7709,6 +7718,9 @@ msgstr "Множник швидкості прокручування буфер msgid "Scroll to bottom" msgstr "Прокрутити вниз" +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx +#: src/renderer/commands/registry.ts #: src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts #: src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -7811,15 +7823,14 @@ msgstr "Пошук у поточному поданні" #: src/mobile/NarrowShell.tsx #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Search threads" msgstr "Пошук тредів" #: src/renderer/commands/registry.ts -msgid "Search Threads" -msgstr "Пошук тредів" +#~ msgid "Search Threads" +#~ msgstr "Пошук тредів" -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +#: src/renderer/commands/CommandPalette.tsx msgid "Search…" msgstr "Пошук…" @@ -7914,6 +7925,7 @@ msgstr "Вибрати пристрій" msgid "Select model" msgstr "Вибрати модель" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/thread/ProjectSwitchMenu.tsx msgid "Select project" msgstr "Вибрати проєкт" @@ -7970,6 +7982,8 @@ msgstr "Призначити комбінацію клавіш" #: src/mobile/chrome.ts #: src/mobile/NarrowShell.tsx +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -9212,9 +9226,9 @@ msgstr "Порядок сортування тредів" msgid "Thread todo dock" msgstr "Панель завдань треда" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/ThreadSettings.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Threads" msgstr "Треди" @@ -9377,8 +9391,8 @@ msgid "Type" msgstr "Тип" #: src/renderer/commands/CommandPalette.tsx -msgid "Type a command" -msgstr "Введіть команду" +#~ msgid "Type a command" +#~ msgstr "Введіть команду" #: src/mobile/views/BrowserView.tsx msgid "Type into the page" @@ -9388,6 +9402,10 @@ msgstr "Вводить на странице" msgid "Type into the page (tap a field first)" msgstr "Вводите на странице (сначала коснитесь поля)" +#: src/renderer/commands/CommandPalette.tsx +msgid "Type to search files" +msgstr "Введіть запит для пошуку файлів" + #. placeholder {0}: agent.label #. placeholder {0}: agentStatus.label #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx diff --git a/src/renderer/locales/vi/messages.po b/src/renderer/locales/vi/messages.po index 8f8afbaee..f0a8e49d9 100644 --- a/src/renderer/locales/vi/messages.po +++ b/src/renderer/locales/vi/messages.po @@ -568,6 +568,7 @@ msgid "Action name" msgstr "Tên hành động" #: src/mobile/views/pr/PrOverviewPage.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/ActionsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx @@ -848,6 +849,7 @@ msgstr "Nhà cung cấp AI" msgid "Alerts when threads need you" msgstr "Cảnh báo khi luồng cần bạn" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/commands/shortcutCatalog.ts #: src/renderer/components/skills/SkillsManager.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -1946,7 +1948,6 @@ msgid "Close scanner" msgstr "Đóng trình quét" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Close search" msgstr "Đóng tìm kiếm" @@ -2048,8 +2049,6 @@ msgstr "Thu gọn trình soạn thảo thiết bị đầu cuối" msgid "Collapse todo dock" msgstr "Thu gọn thanh việc cần làm" -#. Accessible label for the command palette search input -#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/mcp/McpServerEditor.tsx msgid "Command" msgstr "Lệnh" @@ -3648,6 +3647,10 @@ msgstr "Tệp quá lớn để xem trước." msgid "File no longer exists on disk." msgstr "Tệp không còn tồn tại trên đĩa." +#: src/renderer/commands/CommandPalette.tsx +msgid "File search unavailable" +msgstr "Tính năng tìm kiếm tệp không khả dụng" + #. placeholder {0}: entry.insertions + entry.deletions #. placeholder {0}: file.insertions + file.deletions #: src/renderer/views/GitReviewOverlay/parts/GitDiffContent/parts/DiffSection.tsx @@ -3661,6 +3664,7 @@ msgid "File uses an unsupported encoding." msgstr "Tệp sử dụng mã hóa không được hỗ trợ." #: src/mobile/views/WorkspaceView.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/layout/UnifiedRightPanel.tsx msgid "Files" msgstr "Tập tin" @@ -5243,6 +5247,11 @@ msgstr "Chuyển trình duyệt sang cửa sổ" msgid "Move changes to a new worktree" msgstr "Di chuyển các thay đổi sang một cây làm việc mới" +#. placeholder {0}: props.thread.title +#: src/renderer/commands/EverythingSearchResultRow.tsx +msgid "Move thread {0}" +msgstr "Di chuyển luồng {0}" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "Di chuyển khay việc cần làm sang bảng bên phải" @@ -5552,8 +5561,8 @@ msgid "No checks reported for this PR." msgstr "Không có kiểm tra nào được báo cáo cho PR này." #: src/renderer/commands/CommandPalette.tsx -msgid "No commands found" -msgstr "Không tìm thấy lệnh nào" +#~ msgid "No commands found" +#~ msgstr "Không tìm thấy lệnh nào" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "No commits found." @@ -5666,7 +5675,6 @@ msgstr "Không có lịch phù hợp." #~ msgstr "Không có tác vụ phù hợp." #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "No matching threads" msgstr "Không có luồng nào phù hợp" @@ -5757,6 +5765,7 @@ msgstr "Chưa có môi trường từ xa nào được kết nối." msgid "No repositories found." msgstr "Không tìm thấy kho lưu trữ nào." +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/find/FindBar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx msgid "No results" @@ -5812,8 +5821,8 @@ msgid "No thread selected" msgstr "Chưa chọn luồng" #: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx -msgid "No threads" -msgstr "Không có luồng nào" +#~ msgid "No threads" +#~ msgstr "Không có luồng nào" #: src/mobile/views/ThreadsView.tsx msgid "No threads in this project" @@ -6031,8 +6040,8 @@ msgid "Open check" msgstr "Mở kiểm tra" #: src/renderer/commands/registry.ts -msgid "Open Command Palette" -msgstr "Mở bảng lệnh" +#~ msgid "Open Command Palette" +#~ msgstr "Mở bảng lệnh" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "Open commit on GitHub" @@ -7709,6 +7718,9 @@ msgstr "Hệ số nhân tốc độ cuộn cho bộ đệm cuộn ngược đầ msgid "Scroll to bottom" msgstr "Cuộn xuống dưới cùng" +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx +#: src/renderer/commands/registry.ts #: src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts #: src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -7811,15 +7823,14 @@ msgstr "Tìm kiếm trong chế độ xem hiện tại" #: src/mobile/NarrowShell.tsx #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Search threads" msgstr "Tìm kiếm luồng" #: src/renderer/commands/registry.ts -msgid "Search Threads" -msgstr "Tìm kiếm luồng" +#~ msgid "Search Threads" +#~ msgstr "Tìm kiếm luồng" -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +#: src/renderer/commands/CommandPalette.tsx msgid "Search…" msgstr "Tìm kiếm…" @@ -7914,6 +7925,7 @@ msgstr "Chọn thiết bị" msgid "Select model" msgstr "Chọn mô hình" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/thread/ProjectSwitchMenu.tsx msgid "Select project" msgstr "Chọn dự án" @@ -7970,6 +7982,8 @@ msgstr "Đặt phím tắt" #: src/mobile/chrome.ts #: src/mobile/NarrowShell.tsx +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -9212,9 +9226,9 @@ msgstr "Thứ tự sắp xếp luồng" msgid "Thread todo dock" msgstr "Dock việc cần làm của luồng" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/ThreadSettings.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Threads" msgstr "luồng" @@ -9377,8 +9391,8 @@ msgid "Type" msgstr "Loại" #: src/renderer/commands/CommandPalette.tsx -msgid "Type a command" -msgstr "Nhập lệnh" +#~ msgid "Type a command" +#~ msgstr "Nhập lệnh" #: src/mobile/views/BrowserView.tsx msgid "Type into the page" @@ -9388,6 +9402,10 @@ msgstr "Nhập vào trang" msgid "Type into the page (tap a field first)" msgstr "Nhập vào trang (chạm vào trường trước)" +#: src/renderer/commands/CommandPalette.tsx +msgid "Type to search files" +msgstr "Nhập để tìm kiếm tệp" + #. placeholder {0}: agent.label #. placeholder {0}: agentStatus.label #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx diff --git a/src/renderer/locales/zh-CN/messages.po b/src/renderer/locales/zh-CN/messages.po index 50dd3bfaf..bf41e4c49 100644 --- a/src/renderer/locales/zh-CN/messages.po +++ b/src/renderer/locales/zh-CN/messages.po @@ -568,6 +568,7 @@ msgid "Action name" msgstr "操作名称" #: src/mobile/views/pr/PrOverviewPage.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/common/BranchSelector/parts/BranchFooterActions.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/ActionsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx @@ -848,6 +849,7 @@ msgstr "AI 提供商" msgid "Alerts when threads need you" msgstr "线程需要你时提醒" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/commands/shortcutCatalog.ts #: src/renderer/components/skills/SkillsManager.tsx #: src/renderer/views/PullRequestsView/PullRequestsView.tsx @@ -1946,7 +1948,6 @@ msgid "Close scanner" msgstr "关闭扫描器" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Close search" msgstr "关闭搜索" @@ -2048,8 +2049,6 @@ msgstr "折叠终端输入框" msgid "Collapse todo dock" msgstr "折叠待办事项面板" -#. Accessible label for the command palette search input -#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/mcp/McpServerEditor.tsx msgid "Command" msgstr "命令" @@ -3648,6 +3647,10 @@ msgstr "文件太大,无法预览。" msgid "File no longer exists on disk." msgstr "文件不再存在于磁盘上。" +#: src/renderer/commands/CommandPalette.tsx +msgid "File search unavailable" +msgstr "文件搜索不可用" + #. placeholder {0}: entry.insertions + entry.deletions #. placeholder {0}: file.insertions + file.deletions #: src/renderer/views/GitReviewOverlay/parts/GitDiffContent/parts/DiffSection.tsx @@ -3661,6 +3664,7 @@ msgid "File uses an unsupported encoding." msgstr "文件使用不受支持的编码。" #: src/mobile/views/WorkspaceView.tsx +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/layout/UnifiedRightPanel.tsx msgid "Files" msgstr "文件" @@ -5242,6 +5246,11 @@ msgstr "将浏览器移到窗口" msgid "Move changes to a new worktree" msgstr "将更改移至新工作树" +#. placeholder {0}: props.thread.title +#: src/renderer/commands/EverythingSearchResultRow.tsx +msgid "Move thread {0}" +msgstr "移动线程 {0}" + #: src/renderer/components/thread/ThreadTodoDock.tsx msgid "Move todo dock to right panel" msgstr "将待办事项停靠栏移至右侧面板" @@ -5551,8 +5560,8 @@ msgid "No checks reported for this PR." msgstr "没有为此 PR 报告检查。" #: src/renderer/commands/CommandPalette.tsx -msgid "No commands found" -msgstr "没有找到命令" +#~ msgid "No commands found" +#~ msgstr "没有找到命令" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "No commits found." @@ -5665,7 +5674,6 @@ msgstr "没有匹配的计划。" #~ msgstr "没有匹配的任务。" #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "No matching threads" msgstr "没有匹配的线程" @@ -5756,6 +5764,7 @@ msgstr "尚未连接远程环境。" msgid "No repositories found." msgstr "未找到存储库。" +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/find/FindBar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx msgid "No results" @@ -5811,8 +5820,8 @@ msgid "No thread selected" msgstr "未选择线程" #: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx -msgid "No threads" -msgstr "没有线程" +#~ msgid "No threads" +#~ msgstr "没有线程" #: src/mobile/views/ThreadsView.tsx msgid "No threads in this project" @@ -6030,8 +6039,8 @@ msgid "Open check" msgstr "打开检查" #: src/renderer/commands/registry.ts -msgid "Open Command Palette" -msgstr "打开命令面板" +#~ msgid "Open Command Palette" +#~ msgstr "打开命令面板" #: src/renderer/views/PrReviewOverlay/parts/PrCommitsTab.tsx msgid "Open commit on GitHub" @@ -7708,6 +7717,9 @@ msgstr "终端回滚缓冲区的滚动速度倍增器。" msgid "Scroll to bottom" msgstr "滚动到底部" +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx +#: src/renderer/commands/registry.ts #: src/renderer/components/thread/ChatPane/parts/items/commandSummary.ts #: src/renderer/components/thread/ChatPane/parts/items/toolDisplay.ts #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -7810,15 +7822,14 @@ msgstr "搜索当前视图" #: src/mobile/NarrowShell.tsx #: src/mobile/views/ThreadsView.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Search threads" msgstr "搜索线程" #: src/renderer/commands/registry.ts -msgid "Search Threads" -msgstr "搜索线程" +#~ msgid "Search Threads" +#~ msgstr "搜索线程" -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +#: src/renderer/commands/CommandPalette.tsx msgid "Search…" msgstr "搜索..." @@ -7913,6 +7924,7 @@ msgstr "选择设备" msgid "Select model" msgstr "选择模型" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/components/thread/ProjectSwitchMenu.tsx msgid "Select project" msgstr "选择项目" @@ -7969,6 +7981,8 @@ msgstr "设置快捷键" #: src/mobile/chrome.ts #: src/mobile/NarrowShell.tsx +#: src/renderer/commands/CommandPalette.tsx +#: src/renderer/commands/EverythingSearchResults.tsx #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx #: src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -9211,9 +9225,9 @@ msgstr "线程排序顺序" msgid "Thread todo dock" msgstr "线程待办事项停靠栏" +#: src/renderer/commands/CommandPalette.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/ThreadSettings.tsx -#: src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx msgid "Threads" msgstr "线程" @@ -9376,8 +9390,8 @@ msgid "Type" msgstr "类型" #: src/renderer/commands/CommandPalette.tsx -msgid "Type a command" -msgstr "输入命令" +#~ msgid "Type a command" +#~ msgstr "输入命令" #: src/mobile/views/BrowserView.tsx msgid "Type into the page" @@ -9387,6 +9401,10 @@ msgstr "在页面中输入" msgid "Type into the page (tap a field first)" msgstr "在页面中输入(请先点按一个字段)" +#: src/renderer/commands/CommandPalette.tsx +msgid "Type to search files" +msgstr "输入内容以搜索文件" + #. placeholder {0}: agent.label #. placeholder {0}: agentStatus.label #: src/renderer/components/thread/ThreadAuthRequiredDock.tsx diff --git a/src/renderer/state/panelStore.test.ts b/src/renderer/state/panelStore.test.ts index 53babc77b..12bad82b5 100644 --- a/src/renderer/state/panelStore.test.ts +++ b/src/renderer/state/panelStore.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { selectAnyObstructingOverlayOpen, usePanelStore } from "./panelStore"; import { useFileEditorStore } from "./fileEditorStore"; +import { useCommandPaletteStore } from "@/renderer/commands/commandPaletteStore"; const initialPanelState = usePanelStore.getState(); const initialFileEditorState = useFileEditorStore.getState(); @@ -17,8 +18,8 @@ function resetPanelStore() { browserOverlayOpen: false, settingsOpen: false, projectSettingsId: null, - threadSearchOpen: false, }); + useCommandPaletteStore.setState({ isOpen: false }); } function resetFileEditorStore() { @@ -64,8 +65,8 @@ describe("selectAnyObstructingOverlayOpen", () => { expect(selectAnyObstructingOverlayOpen()).toBe(true); }); - it("returns true when the thread search overlay is open", () => { - usePanelStore.setState({ threadSearchOpen: true }); + it("returns true when everything search is open", () => { + useCommandPaletteStore.setState({ isOpen: true }); expect(selectAnyObstructingOverlayOpen()).toBe(true); }); diff --git a/src/renderer/state/panelStore.ts b/src/renderer/state/panelStore.ts index 192df1594..247426889 100644 --- a/src/renderer/state/panelStore.ts +++ b/src/renderer/state/panelStore.ts @@ -1,6 +1,7 @@ import { create } from "zustand"; import { persistStoreSlice, readPersistedSlice } from "@/renderer/utils/persistStoreSlice"; import type { ThreadSortMode } from "@/renderer/views/MainView/parts/Sidebar/parts/sortMode"; +import { useCommandPaletteStore } from "@/renderer/commands/commandPaletteStore"; import { useFileEditorStore } from "./fileEditorStore"; export interface GitReviewContext { @@ -48,9 +49,10 @@ interface PanelState { settingsOpen: boolean; /** When the overlay is opened deep-linked to a section (e.g. "usage"); else null. */ settingsSection: string | null; + /** Optional setting row to scroll to after opening a deep-linked section. */ + settingsAnchor: string | null; projectSettingsId: string | null; threadSortMode: ThreadSortMode; - threadSearchOpen: boolean; /** Whether the "Start from scratch" create-project modal is open. */ createProjectModalOpen: boolean; /** Whether the "Clone a repository" modal is open. */ @@ -72,13 +74,11 @@ interface PanelState { setBrowserOverlayDrawerWidth: (v: number) => void; openBrowserPanel: () => void; openSettings: () => void; - openSettingsSection: (section: string) => void; + openSettingsSection: (section: string, anchor?: string) => void; clearSettingsSection: () => void; closeSettings: () => void; openProjectSettings: (projectId: string) => void; closeProjectSettings: () => void; - openThreadSearch: () => void; - closeThreadSearch: () => void; openCreateProjectModal: () => void; closeCreateProjectModal: () => void; openCloneProjectModal: () => void; @@ -147,9 +147,9 @@ export const usePanelStore = create()((set) => ({ ), settingsOpen: false, settingsSection: null, + settingsAnchor: null, projectSettingsId: null, threadSortMode: "updated", - threadSearchOpen: false, createProjectModalOpen: false, cloneProjectModalOpen: false, @@ -260,22 +260,23 @@ export const usePanelStore = create()((set) => ({ set((state) => (state.threadSortMode === mode ? {} : { threadSortMode: mode })), openSettings: () => set((state) => - state.settingsOpen && state.settingsSection === null + state.settingsOpen && state.settingsSection === null && state.settingsAnchor === null ? {} - : { settingsOpen: true, settingsSection: null }, + : { settingsOpen: true, settingsSection: null, settingsAnchor: null }, ), - openSettingsSection: (section) => set({ settingsOpen: true, settingsSection: section }), + openSettingsSection: (section, anchor) => + set({ settingsOpen: true, settingsSection: section, settingsAnchor: anchor ?? null }), clearSettingsSection: () => - set((state) => (state.settingsSection === null ? {} : { settingsSection: null })), + set((state) => + state.settingsSection === null && state.settingsAnchor === null + ? {} + : { settingsSection: null, settingsAnchor: null }, + ), closeSettings: () => set((state) => (state.settingsOpen ? { settingsOpen: false } : {})), openProjectSettings: (projectId) => set((state) => (state.projectSettingsId === projectId ? {} : { projectSettingsId: projectId })), closeProjectSettings: () => set((state) => (state.projectSettingsId === null ? {} : { projectSettingsId: null })), - openThreadSearch: () => - set((state) => (state.threadSearchOpen ? {} : { threadSearchOpen: true })), - closeThreadSearch: () => - set((state) => (state.threadSearchOpen ? { threadSearchOpen: false } : {})), openCreateProjectModal: () => set((state) => (state.createProjectModalOpen ? {} : { createProjectModalOpen: true })), closeCreateProjectModal: () => @@ -335,7 +336,7 @@ export function selectAnyObstructingOverlayOpen(): boolean { p.projectSettingsId !== null || p.gitOverlayOpen || p.prReviewContext !== null || - p.threadSearchOpen + useCommandPaletteStore.getState().isOpen ) { return true; } diff --git a/src/renderer/views/MainView/MainView.tsx b/src/renderer/views/MainView/MainView.tsx index af0f8b858..96e426c4a 100644 --- a/src/renderer/views/MainView/MainView.tsx +++ b/src/renderer/views/MainView/MainView.tsx @@ -1,4 +1,4 @@ -import { startTransition, useEffect } from "react"; +import { Suspense, startTransition, useEffect, useState } from "react"; import type { AgentStatus } from "@/shared/contracts"; import { buildPaneLayoutFromLegacy } from "@/shared/paneLayout"; import { readBridge } from "@/renderer/bridge"; @@ -10,6 +10,8 @@ import { useWelcomeGateStore } from "@/renderer/state/welcomeGateStore"; import { buildWslProjectDistrosKey, parseWslProjectDistrosKey } from "@/renderer/state/projectKeys"; import { useSharedSettings } from "@/renderer/state/sharedSettingsStore"; import { AppDndProvider } from "@/renderer/dnd"; +import { useCommandPaletteStore } from "@/renderer/commands/commandPaletteStore"; +import { DeferredCommandPalette as PrewarmedCommandPalette } from "@/renderer/deferredFeatures"; import { useKeyboardShortcuts } from "@/renderer/hooks/useKeyboardShortcuts"; import { useGitRefresh } from "@/renderer/hooks/useGitRefresh"; @@ -21,7 +23,6 @@ import { AppOverlays } from "@/renderer/views/MainView/parts/AppOverlays"; import { WorktreeDeleteDialogs } from "@/renderer/views/MainView/parts/WorktreeDeleteDialogs"; import { PullFromSourceDialog } from "@/renderer/views/MainView/parts/PullFromSourceDialog"; import { MainPageLayout, StalePanelCleanup } from "@/renderer/views/MainView/parts/MainPageLayout"; -import { ThreadSearchOverlayHost } from "@/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay"; function findMissingWslDistro(distros: readonly string[], statuses: readonly AgentStatus[]) { const cachedDistros = new Set( @@ -111,7 +112,7 @@ export function MainView(props: { storeHydrated: boolean; loadT0: number }) { } > startTransition(() => openHome())} /> - + @@ -120,3 +121,18 @@ export function MainView(props: { storeHydrated: boolean; loadT0: number }) { ); } + +function DeferredCommandPaletteHost() { + const open = useCommandPaletteStore((state) => state.isOpen); + const [enabled, setEnabled] = useState(open); + + useEffect(() => { + if (open) setEnabled(true); + }, [open]); + + return enabled ? ( + + + + ) : null; +} diff --git a/src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx b/src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx index 0c90804aa..ad4696053 100644 --- a/src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx +++ b/src/renderer/views/MainView/parts/Sidebar/Sidebar.tsx @@ -49,6 +49,7 @@ import { import { useScrollFade } from "@/renderer/hooks/useScrollFade"; import { useAppStore } from "@/renderer/state/appStore"; import { usePanelStore } from "@/renderer/state/panelStore"; +import { useCommandPaletteStore } from "@/renderer/commands/commandPaletteStore"; import { useSidebarUiStore } from "@/renderer/state/sidebarUiStore"; import { useSharedSettings } from "@/renderer/state/sharedSettingsStore"; import { useUpdateStore } from "@/renderer/state/updateStore"; @@ -233,8 +234,8 @@ export function Sidebar() { // lights up for every other section. const remoteAccessSettingsActive = settingsOpen && settingsSection === "remoteAccess"; const otherSettingsActive = settingsOpen && !remoteAccessSettingsActive; - const threadSearchOpen = usePanelStore((s) => s.threadSearchOpen); - const openThreadSearch = usePanelStore((s) => s.openThreadSearch); + const searchOpen = useCommandPaletteStore((state) => state.isOpen); + const openSearch = useCommandPaletteStore((state) => state.open); const isHomeProjectCollapsed = useSidebarUiStore((s) => homeProject ? (s.collapsedProjects[homeProject.id] ?? false) : false, ); @@ -336,8 +337,8 @@ export function Sidebar() { iconOnly icon={} label={t`Search`} - isActive={threadSearchOpen} - onPress={openThreadSearch} + isActive={searchOpen} + onPress={openSearch} />
diff --git a/src/renderer/views/MainView/parts/SidebarHeaderControls.tsx b/src/renderer/views/MainView/parts/SidebarHeaderControls.tsx index d5c0410ba..8a16b7e58 100644 --- a/src/renderer/views/MainView/parts/SidebarHeaderControls.tsx +++ b/src/renderer/views/MainView/parts/SidebarHeaderControls.tsx @@ -2,6 +2,7 @@ import { FolderPlus, Globe, Search } from "lucide-react"; import { Button, Dropdown, Label, Tooltip } from "@heroui/react"; import { Trans, useLingui } from "@lingui/react/macro"; import { usePanelStore } from "@/renderer/state/panelStore"; +import { useCommandPaletteStore } from "@/renderer/commands/commandPaletteStore"; import { toggleBrowserPanel } from "@/renderer/actions/panelActions"; import { type ThreadSortMode, @@ -28,7 +29,7 @@ export function SidebarHeaderControls() { size="sm" variant="ghost" className="size-6 min-w-0 text-muted hover:text-foreground" - onPress={() => usePanelStore.getState().openThreadSearch()} + onPress={() => useCommandPaletteStore.getState().open()} > diff --git a/src/renderer/views/SettingsOverlay/SettingsOverlay.test.tsx b/src/renderer/views/SettingsOverlay/SettingsOverlay.test.tsx index 31b40f498..0e5605468 100644 --- a/src/renderer/views/SettingsOverlay/SettingsOverlay.test.tsx +++ b/src/renderer/views/SettingsOverlay/SettingsOverlay.test.tsx @@ -1,4 +1,4 @@ -import { act, fireEvent, screen, within } from "@testing-library/react"; +import { act, fireEvent, screen, waitFor, within } from "@testing-library/react"; import { renderWithI18n as render } from "@/renderer/testUtils/i18n"; import type { ReactNode } from "react"; import { beforeEach, describe, expect, it, vi } from "vitest"; @@ -109,11 +109,11 @@ vi.mock("@/renderer/components/thread/AgentDiscoveryScreen", () => ({ })); vi.mock("./parts/GeneralSettings", () => ({ - GeneralSettings: () =>
General
, + GeneralSettings: () =>
General
, })); vi.mock("./parts/AppearanceSettings", () => ({ - AppearanceSettings: () =>
Appearance
, + AppearanceSettings: () =>
Appearance
, })); vi.mock("./parts/TerminalSettings", () => ({ @@ -174,6 +174,7 @@ vi.mock("./parts/SingleAgentSettings", () => ({ })); import { SettingsOverlay } from "./SettingsOverlay"; +import { usePanelStore } from "@/renderer/state/panelStore"; const baseCapabilities = { models: [], @@ -209,6 +210,11 @@ describe("SettingsOverlay", () => { resetDiscoveredAgentsMock.mockReset(); refreshAgentStatusesMock.mockReset(); refreshAgentStatusesMock.mockResolvedValue(undefined); + usePanelStore.setState({ + settingsOpen: false, + settingsSection: null, + settingsAnchor: null, + }); }); it("keeps WSL-only installed agents reachable from the sidebar", () => { @@ -339,6 +345,41 @@ describe("SettingsOverlay", () => { expect(within(screen.getByRole("main")).getByText("Usage")).toBeInTheDocument(); }); + it("scrolls to an initially requested setting anchor", async () => { + const scrollIntoView = vi.fn<(options?: ScrollIntoViewOptions | boolean) => void>(); + HTMLElement.prototype.scrollIntoView = scrollIntoView; + usePanelStore.getState().openSettingsSection("general", "general.language"); + + render( undefined} />); + + await waitFor(() => + expect(scrollIntoView).toHaveBeenCalledWith({ behavior: "smooth", block: "start" }), + ); + expect(within(screen.getByRole("main")).getByText("General")).toHaveClass( + "poracode-setting-highlight", + ); + expect(usePanelStore.getState().settingsSection).toBeNull(); + expect(usePanelStore.getState().settingsAnchor).toBeNull(); + }); + + it("scrolls to a setting anchor requested while the overlay is open", async () => { + const scrollIntoView = vi.fn<(options?: ScrollIntoViewOptions | boolean) => void>(); + HTMLElement.prototype.scrollIntoView = scrollIntoView; + render( undefined} />); + + act(() => { + usePanelStore.getState().openSettingsSection("appearance", "appearance.mode"); + }); + + expect(within(screen.getByRole("main")).getByText("Appearance")).toBeInTheDocument(); + await waitFor(() => + expect(scrollIntoView).toHaveBeenCalledWith({ behavior: "smooth", block: "start" }), + ); + expect(within(screen.getByRole("main")).getByText("Appearance")).toHaveClass( + "poracode-setting-highlight", + ); + }); + it("marks agents that need attention in the sidebar", () => { statusesState.agentStatuses = [ makeStatus("acp-generic:factory-droid", { diff --git a/src/renderer/views/SettingsOverlay/SettingsOverlay.tsx b/src/renderer/views/SettingsOverlay/SettingsOverlay.tsx index b4dd4b20d..763bb892a 100644 --- a/src/renderer/views/SettingsOverlay/SettingsOverlay.tsx +++ b/src/renderer/views/SettingsOverlay/SettingsOverlay.tsx @@ -87,33 +87,33 @@ export function SettingsOverlay(props: { onClose: () => void }) { const { onClose } = props; const { t } = useLingui(); const requestedSection = usePanelStore((s) => s.settingsSection); + const requestedAnchor = usePanelStore((s) => s.settingsAnchor); const clearSettingsSection = usePanelStore((s) => s.clearSettingsSection); const [activeSection, setActiveSection] = useState( (requestedSection as SettingsSection | null) ?? "general", ); - // Apply a deep-link request (e.g. clicking a sidebar usage circle) and clear - // it so it doesn't re-fire on the next open. - useEffect(() => { - if (requestedSection) { - setActiveSection(requestedSection as SettingsSection); - clearSettingsSection(); - } - }, [requestedSection, clearSettingsSection]); - // Pending scroll-to-setting target, set when a settings search result is - // clicked. The token re-fires the effect when the same setting is picked - // twice. Local (not a store): only this overlay coordinates the scroll, and it - // has to land *after* the section content remounts (`key={activeSection}`). - const [scrollTarget, setScrollTarget] = useState<{ anchor: string; token: number } | null>(null); - const scrollTokenRef = useRef(0); + // clicked. Local (not a store): only this overlay coordinates the scroll, and + // it has to land *after* the section content remounts (`key={activeSection}`). + const [scrollTarget, setScrollTarget] = useState<{ anchor: string } | null>(null); const navigateToSection = useCallback((section: SettingsSection, anchor?: string) => { setActiveSection(section); if (anchor) { - scrollTokenRef.current += 1; - setScrollTarget({ anchor, token: scrollTokenRef.current }); + setScrollTarget({ anchor }); + } else { + setScrollTarget(null); } }, []); + // Apply a deep-link request (e.g. clicking a sidebar usage circle or an + // everything-search setting result) and clear it so the same target can be + // requested again while this overlay remains open. + useEffect(() => { + if (!requestedSection) return; + navigateToSection(requestedSection as SettingsSection, requestedAnchor ?? undefined); + clearSettingsSection(); + }, [requestedSection, requestedAnchor, clearSettingsSection, navigateToSection]); + // After the target section mounts, scroll its anchor into view and flash it. // Runs on rAF (with a short retry) so the freshly-remounted row is in the DOM. useEffect(() => { diff --git a/src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.test.ts b/src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.test.ts index 192c74971..3bda96d27 100644 --- a/src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.test.ts +++ b/src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.test.ts @@ -99,6 +99,12 @@ describe("searchSettings", () => { expect(lower).toContain("browser.allowDataAccess"); }); + it("matches every query term across title, description, and keywords", () => { + const anchors = searchSettings("appearance mode", t).map((result) => result.anchor); + expect(anchors).toContain("appearance.mode"); + expect(searchSettings("appearance missing", t)).toEqual([]); + }); + it("finds the Skills and MCP settings", () => { expect(searchSettings("skills", t).map((result) => result.anchor)).toContain("skills.manage"); expect(searchSettings("shared", t).map((result) => result.anchor)).toContain("skills.manage"); diff --git a/src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts b/src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts index d219c5da5..65b03a1f1 100644 --- a/src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts +++ b/src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts @@ -539,17 +539,17 @@ function truncate(text: string, max = SNIPPET_MAX): string { } /** - * Match `query` against the index. Case-insensitive substring over the localized - * title, then description, then English `keywords` — same semantics as the - * sidebar's existing section-label filter. Returns `[]` for a blank query. + * Match `query` against the index. Every whitespace-delimited term must match + * the localized title, description, or English `keywords`. Returns `[]` for a + * blank query. */ export function searchSettings( query: string, t: Translate, opts?: { devMode?: boolean; remoteSession?: boolean; index?: readonly SettingsSearchEntry[] }, ): SettingsSearchResult[] { - const needle = query.trim().toLowerCase(); - if (needle === "") return []; + const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean); + if (terms.length === 0) return []; const index = opts?.index ?? SETTINGS_SEARCH_INDEX; const results: SettingsSearchResult[] = []; for (const entry of index) { @@ -557,11 +557,17 @@ export function searchSettings( if (entry.desktopOnly && opts?.remoteSession) continue; const title = t(entry.title); const description = entry.description ? t(entry.description) : ""; - const titleMatch = title.toLowerCase().includes(needle); - const descMatch = description !== "" && description.toLowerCase().includes(needle); - const keywordMatch = - entry.keywords !== undefined && entry.keywords.toLowerCase().includes(needle); - if (!titleMatch && !descMatch && !keywordMatch) continue; + const normalizedTitle = title.toLowerCase(); + const normalizedDescription = description.toLowerCase(); + const normalizedKeywords = entry.keywords?.toLowerCase() ?? ""; + const titleMatch = terms.every((term) => normalizedTitle.includes(term)); + const matches = terms.every( + (term) => + normalizedTitle.includes(term) || + normalizedDescription.includes(term) || + normalizedKeywords.includes(term), + ); + if (!matches) continue; results.push({ section: entry.section, anchor: entry.anchor, diff --git a/src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx b/src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx deleted file mode 100644 index c196c279e..000000000 --- a/src/renderer/views/ThreadSearchOverlay/ThreadSearchOverlay.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from "react"; -import { Search } from "lucide-react"; -import { Trans, useLingui } from "@lingui/react/macro"; -import { useShallow } from "zustand/shallow"; -import { useAppStore } from "@/renderer/state/appStore"; -import { usePanelStore } from "@/renderer/state/panelStore"; -import { useDragSource } from "@/renderer/dnd"; -import { openThread } from "@/renderer/actions/threadActions"; -import { ThreadSearchResultRow } from "./parts/ThreadSearchResultRow"; - -const RESULT_LIMIT = 50; - -export function ThreadSearchOverlay(props: { onClose: () => void }) { - const { onClose } = props; - const { t } = useLingui(); - const [query, setQuery] = useState(""); - const [selectedIndex, setSelectedIndex] = useState(0); - const inputRef = useRef(null); - const listRef = useRef(null); - - const threads = useAppStore(useShallow((s) => s.threads)); - const projects = useAppStore(useShallow((s) => s.projects)); - - const dragSource = useDragSource(); - const isDraggingThreadFromSearch = - dragSource?.type === "thread" && threads.some((thread) => thread.id === dragSource.threadId); - const wasDraggingRef = useRef(false); - - // When a drag started from this overlay ends, close the overlay. - useEffect(() => { - if (isDraggingThreadFromSearch) { - wasDraggingRef.current = true; - return; - } - if (wasDraggingRef.current) { - wasDraggingRef.current = false; - onClose(); - } - }, [isDraggingThreadFromSearch, onClose]); - - useEffect(() => { - inputRef.current?.focus(); - }, []); - - const projectsById = useMemo(() => { - const map = new Map(); - for (const project of projects) map.set(project.id, project); - return map; - }, [projects]); - - const results = useMemo(() => { - const q = query.trim().toLowerCase(); - const candidates = threads.filter((thread) => !thread.archived); - const filtered = q - ? candidates.filter((thread) => thread.title.toLowerCase().includes(q)) - : candidates; - return filtered - .slice() - .sort((a, b) => { - if (a.starred !== b.starred) return a.starred ? -1 : 1; - return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); - }) - .slice(0, RESULT_LIMIT); - }, [threads, query]); - - useEffect(() => { - setSelectedIndex(0); - }, [query]); - - function activateAt(index: number) { - const thread = results[index]; - if (!thread) return; - openThread(thread.id); - onClose(); - } - - function onKeyDown(e: React.KeyboardEvent) { - if (e.key === "ArrowDown") { - e.preventDefault(); - setSelectedIndex((i) => Math.min(i + 1, Math.max(0, results.length - 1))); - } else if (e.key === "ArrowUp") { - e.preventDefault(); - setSelectedIndex((i) => Math.max(0, i - 1)); - } else if (e.key === "Enter") { - e.preventDefault(); - activateAt(selectedIndex); - } else if (e.key === "Escape") { - e.preventDefault(); - onClose(); - } - } - - // Hide the overlay visually while a drag is alive so the dragged thread can - // be dropped onto a pane underneath, without unmounting the dnd source. - const hidden = isDraggingThreadFromSearch; - - return ( - - ); -} - -export function ThreadSearchOverlayHost() { - const open = usePanelStore((s) => s.threadSearchOpen); - if (!open) return null; - return usePanelStore.getState().closeThreadSearch()} />; -} diff --git a/src/renderer/views/ThreadSearchOverlay/parts/ThreadSearchResultRow.tsx b/src/renderer/views/ThreadSearchOverlay/parts/ThreadSearchResultRow.tsx deleted file mode 100644 index 1d4695435..000000000 --- a/src/renderer/views/ThreadSearchOverlay/parts/ThreadSearchResultRow.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useRef } from "react"; -import type { Project, Thread } from "@/shared/contracts"; -import { ThreadProviderIcon } from "@/renderer/components/providers/ThreadProviderIcon"; -import { useDraggable } from "@dnd-kit/react"; -import type { DragSourceData } from "@/renderer/dnd"; -import { handleKeyActivate } from "@/renderer/utils/a11y"; - -export function ThreadSearchResultRow(props: { - thread: Thread; - project: Project | undefined; - isSelected: boolean; - onActivate: () => void; - onHover: () => void; -}) { - const { thread, project, isSelected, onActivate, onHover } = props; - const rowRef = useRef(null); - - useDraggable({ - id: `thread-search:${thread.id}`, - type: "thread", - data: { - type: "thread", - threadId: thread.id, - projectId: thread.projectId, - ...(thread.worktreePath != null ? { worktreePath: thread.worktreePath } : {}), - } satisfies DragSourceData, - element: rowRef, - }); - - const stateClass = isSelected - ? "bg-[var(--row-active)] text-foreground" - : "text-foreground/85 hover:bg-[var(--row-hover)] hover:text-foreground"; - - return ( -
handleKeyActivate(e, onActivate)} - > - - - {thread.title} - - {project ? ( - {project.name} - ) : null} -
- ); -} diff --git a/src/shared/analytics/posthogPrivacy.ts b/src/shared/analytics/posthogPrivacy.ts index 657584671..f9f96858d 100644 --- a/src/shared/analytics/posthogPrivacy.ts +++ b/src/shared/analytics/posthogPrivacy.ts @@ -16,11 +16,11 @@ export const PRODUCT_ANALYTICS_EVENT_NAMES = [ "thread.started", "thread.turn_completed", "ui.project_group_toggled", + "ui.everything_search_toggled", "ui.right_panel_toggled", "ui.right_panel_tab_changed", "ui.sidebar_toggled", "ui.thread_list_show_more", - "ui.thread_search_toggled", "ui.worktree_group_toggled", ] as const; diff --git a/src/shared/contracts/projectTree.ts b/src/shared/contracts/projectTree.ts index d7ca2fafa..fc7da2795 100644 --- a/src/shared/contracts/projectTree.ts +++ b/src/shared/contracts/projectTree.ts @@ -92,6 +92,7 @@ export const searchProjectTreePayloadSchema = z.object({ projectLocation: projectLocationSchema, query: z.string().default(""), limit: z.number().int().min(1).max(200).default(50), + entryType: z.enum(["file", "directory"]).optional(), searchConfig: searchConfigSchema.optional(), }); export type SearchProjectTreePayload = z.infer; diff --git a/src/shared/keybindings.test.ts b/src/shared/keybindings.test.ts index 3a615dc4b..de9ac0a34 100644 --- a/src/shared/keybindings.test.ts +++ b/src/shared/keybindings.test.ts @@ -17,8 +17,11 @@ describe("DEFAULT_KEYBINDINGS", () => { expect(byCommand["pane.close"]?.when).toContain("!browserFocus"); expect(byCommand["pane.close"]?.when).toContain("!composerFocus"); expect(byCommand["editor.save"]?.when).toBe("editorFocus"); - expect(byCommand["thread.search.open"]?.when).toContain("!inputFocus"); - expect(byCommand["thread.search.open"]?.when).toContain("!panelFocus"); + const constrainedSearch = DEFAULT_KEYBINDINGS.keybindings.find( + (binding) => binding.command === "palette.open" && binding.when, + ); + expect(constrainedSearch?.when).toContain("!inputFocus"); + expect(constrainedSearch?.when).toContain("!panelFocus"); expect(byCommand["thread.star"]?.when).toContain("draftView"); expect(byCommand["thread.star"]?.when).toContain("!inputFocus"); expect(byCommand["thread.star"]?.when).toContain("!terminalFocus"); diff --git a/src/shared/keybindings.ts b/src/shared/keybindings.ts index a664f7dbc..8598a8d91 100644 --- a/src/shared/keybindings.ts +++ b/src/shared/keybindings.ts @@ -156,7 +156,7 @@ export const DEFAULT_KEYBINDINGS: KeybindingsFile = { when: "hasProject", }, { - command: "thread.search.open", + command: "palette.open", key: "Ctrl+G", mac: "Meta+G", when: NOT_TYPING, diff --git a/src/supervisor/ProjectSearchIndex.test.ts b/src/supervisor/ProjectSearchIndex.test.ts new file mode 100644 index 000000000..fd36fe211 --- /dev/null +++ b/src/supervisor/ProjectSearchIndex.test.ts @@ -0,0 +1,117 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { ProjectLocation } from "@/shared/contracts"; +import type { WslBridgeClient } from "./wsl/bridge/client"; + +const execGitMock = vi.hoisted(() => vi.fn<() => Promise>()); + +vi.mock("./git", async () => { + const actual = await vi.importActual("./git"); + return { + ...actual, + execGit: execGitMock, + }; +}); + +import { ProjectSearchIndex } from "./ProjectSearchIndex"; + +const location: Extract = { + kind: "wsl", + distro: "Ubuntu", + linuxPath: "/home/user/project", + uncPath: "\\\\wsl.localhost\\Ubuntu\\home\\user\\project", +}; + +describe("ProjectSearchIndex caching", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("uses separate cache entries when useIgnoreFiles changes", async () => { + execGitMock.mockResolvedValue("src/tracked.ts\n"); + const find = vi.fn().mockResolvedValue({ + entries: [{ path: "ignored/generated.ts", name: "generated.ts", type: "file" }], + truncated: false, + }); + const index = new ProjectSearchIndex(() => ""); + index.setWslClient({ find } as unknown as WslBridgeClient); + const excludePatterns = ["**/.git"]; + + const respectingIgnoreFiles = await index.searchProjectTree({ + projectLocation: location, + query: "tracked", + limit: 10, + searchConfig: { useIgnoreFiles: true, excludePatterns }, + }); + const includingIgnoredFiles = await index.searchProjectTree({ + projectLocation: location, + query: "generated", + limit: 10, + searchConfig: { useIgnoreFiles: false, excludePatterns }, + }); + + expect(respectingIgnoreFiles.entries.map((entry) => entry.path)).toEqual(["src/tracked.ts"]); + expect(includingIgnoredFiles.entries.map((entry) => entry.path)).toEqual([ + "ignored/generated.ts", + ]); + expect(execGitMock).toHaveBeenCalledOnce(); + expect(find).toHaveBeenCalledOnce(); + }); + + it("uses separate cache entries for comma-separated and distinct exclude patterns", async () => { + const find = vi.fn().mockResolvedValue({ + entries: [{ path: "src/keep.ts", name: "keep.ts", type: "file" }], + truncated: false, + }); + const index = new ProjectSearchIndex(() => ""); + index.setWslClient({ find } as unknown as WslBridgeClient); + + await index.searchProjectTree({ + projectLocation: location, + query: "keep", + limit: 10, + searchConfig: { useIgnoreFiles: false, excludePatterns: ["a,b"] }, + }); + await index.searchProjectTree({ + projectLocation: location, + query: "keep", + limit: 10, + searchConfig: { useIgnoreFiles: false, excludePatterns: ["a", "b"] }, + }); + + expect(find).toHaveBeenCalledTimes(2); + }); + + it("filters entry types before applying the result limit", async () => { + execGitMock.mockResolvedValue("src/match.ts\n"); + const index = new ProjectSearchIndex(() => ""); + + const result = await index.searchProjectTree({ + projectLocation: location, + query: "src", + limit: 1, + entryType: "file", + searchConfig: { useIgnoreFiles: true, excludePatterns: [] }, + }); + + expect(result.entries.map((entry) => entry.path)).toEqual(["src/match.ts"]); + }); + + it("matches reordered terms across an entry name and path", async () => { + execGitMock.mockResolvedValue( + "src/components/search/CommandPalette.tsx\nsrc/components/search/Other.tsx\n", + ); + const index = new ProjectSearchIndex(() => ""); + + const result = await index.searchProjectTree({ + projectLocation: location, + query: "palette components", + limit: 10, + entryType: "file", + searchConfig: { useIgnoreFiles: true, excludePatterns: [] }, + }); + + expect(result.entries.map((entry) => entry.path)).toEqual([ + "src/components/search/CommandPalette.tsx", + ]); + }); +}); diff --git a/src/supervisor/ProjectSearchIndex.ts b/src/supervisor/ProjectSearchIndex.ts index e5c0c4560..27464d13b 100644 --- a/src/supervisor/ProjectSearchIndex.ts +++ b/src/supervisor/ProjectSearchIndex.ts @@ -25,7 +25,10 @@ function joinRelativePath(parentPath: string, name: string): string { } function cacheKeyForSearchConfig(config: SearchConfigPayload): string { - return [...config.excludePatterns].sort().join(","); + return JSON.stringify({ + useIgnoreFiles: config.useIgnoreFiles, + excludePatterns: [...config.excludePatterns].sort(), + }); } /** @@ -109,7 +112,7 @@ export class ProjectSearchIndex { const config = payload.searchConfig ?? { useIgnoreFiles: true, excludePatterns: [] }; const { entries } = await this.getOrBuildSearchIndex(payload.projectLocation, config); return { - entries: this.rankEntries(entries, query, payload.limit), + entries: this.rankEntries(entries, query, payload.limit, payload.entryType), }; } @@ -117,7 +120,7 @@ export class ProjectSearchIndex { location: ProjectLocation, config: SearchConfigPayload, ): Promise<{ entries: ProjectTreeEntry[] }> { - const key = `${getLocationIdentity(location)}|${cacheKeyForSearchConfig(config)}`; + const key = `${JSON.stringify(getLocationIdentity(location))}:${cacheKeyForSearchConfig(config)}`; const cached = this.cache.get(key); if (cached && Date.now() - cached.createdAt < CACHE_TTL_MS) { this.cache.delete(key); @@ -251,16 +254,31 @@ export class ProjectSearchIndex { return results; } - rankEntries(entries: ProjectTreeEntry[], query: string, limit: number): ProjectTreeEntry[] { + rankEntries( + entries: ProjectTreeEntry[], + query: string, + limit: number, + entryType?: ProjectTreeEntry["type"], + ): ProjectTreeEntry[] { + const terms = query.split(/\s+/).filter(Boolean); + if (terms.length === 0) return []; const scored: { entry: ProjectTreeEntry; score: number }[] = []; for (const entry of entries) { + if (entryType && entry.type !== entryType) continue; const nameLower = entry.name.toLowerCase(); const pathLower = entry.path.toLowerCase(); let score = 0; - if (nameLower.startsWith(query)) score = 3; - else if (nameLower.includes(query)) score = 2; - else if (pathLower.includes(query)) score = 1; - if (score > 0) scored.push({ entry, score }); + let matches = true; + for (const term of terms) { + if (nameLower.startsWith(term)) score += 3; + else if (nameLower.includes(term)) score += 2; + else if (pathLower.includes(term)) score += 1; + else { + matches = false; + break; + } + } + if (matches) scored.push({ entry, score }); } scored.sort((a, b) => { @@ -279,9 +297,9 @@ export class ProjectSearchIndex { } invalidateCaches(location: ProjectLocation): void { - const prefix = `${getLocationIdentity(location)}|`; + const prefix = `${JSON.stringify(getLocationIdentity(location))}:`; for (const key of this.cache.keys()) { - if (key === getLocationIdentity(location) || key.startsWith(prefix)) { + if (key.startsWith(prefix)) { this.cache.delete(key); } }