diff --git a/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.search.test.tsx b/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.search.test.tsx index f398b9c19ef..fb037ce8dc9 100644 --- a/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.search.test.tsx +++ b/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.search.test.tsx @@ -9,7 +9,9 @@ import WorkspaceKanbanDrawer from './WorkspaceKanbanDrawer' import type { WorkspaceKanbanLaneView } from './workspace-kanban-search' type HeaderCapture = { + selectedCount: number query: string + isFiltering: boolean matchCount: number totalCount: number onQueryChange: (query: string) => void @@ -42,7 +44,8 @@ const { headerState, gridState, pointerDragState, - selectionState + selectionState, + selectionScopeState } = vi.hoisted(() => ({ syncWorkspaceBoardTaskStatusesMock: vi.fn(() => Promise.resolve({ updated: 1, skipped: 0, failed: 0, messages: [] }) @@ -50,7 +53,8 @@ const { headerState: { current: null as HeaderCapture | null }, gridState: { current: null as GridCapture | null }, pointerDragState: { current: null as PointerDragCapture | null }, - selectionState: { current: [] as Worktree[] } + selectionState: { current: [] as Worktree[] }, + selectionScopeState: { current: [] as readonly Worktree[] } })) ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true @@ -88,15 +92,22 @@ vi.mock('./use-visible-workspace-kanban-worktree-ids', () => ({ })) vi.mock('./use-workspace-kanban-selection', () => ({ - useWorkspaceKanbanSelection: () => ({ - selectedWorktreeIds: new Set(selectionState.current.map((worktree) => worktree.id)), - selectedWorktrees: selectionState.current, - selectionAnchorId: null, - updateSelectionForGesture: vi.fn(), - updateSelectionForArea: vi.fn(), - clearSelection: vi.fn(), - selectForContextMenu: vi.fn(() => selectionState.current) - }) + useWorkspaceKanbanSelection: ( + _open: boolean, + boardWorktrees: readonly Worktree[], + renderedWorktrees?: readonly Worktree[] + ) => { + selectionScopeState.current = renderedWorktrees ?? boardWorktrees + return { + selectedWorktreeIds: new Set(selectionState.current.map((worktree) => worktree.id)), + selectedWorktrees: selectionState.current, + selectionAnchorId: null, + updateSelectionForGesture: vi.fn(), + updateSelectionForArea: vi.fn(), + clearSelection: vi.fn(), + selectForContextMenu: vi.fn(() => selectionState.current) + } + } })) vi.mock('./use-workspace-kanban-area-selection', () => ({ @@ -217,6 +228,7 @@ beforeEach(() => { gridState.current = null pointerDragState.current = null selectionState.current = [] + selectionScopeState.current = [] syncWorkspaceBoardTaskStatusesMock.mockClear() updateWorktreesMeta = vi.fn(() => Promise.resolve()) useAppStore.setState({ @@ -334,6 +346,34 @@ describe('WorkspaceKanbanDrawer search', () => { expect(gridState.current?.selectedWorktrees).toEqual([gamma]) }) + it('counts only the rendered cards in the header selection badge', () => { + selectionState.current = [alpha, gamma] + renderDrawer() + expect(headerState.current?.selectedCount).toBe(2) + + typeQuery('gamma') + + expect(headerState.current?.selectedCount).toBe(1) + }) + + it('scopes selection gestures to the rendered cards', () => { + renderDrawer() + expect(selectionScopeState.current).toHaveLength(5) + + typeQuery('gamma') + + expect(selectionScopeState.current).toEqual([gamma]) + }) + + it('reports a non-filtering query so the header withholds match counts', () => { + renderDrawer() + + typeQuery(' ') + + expect(headerState.current).toMatchObject({ isFiltering: false, matchCount: 5, totalCount: 5 }) + expect(laneIds('todo')).toHaveLength(4) + }) + it('ranks a drop into a filtered lane against the full lane, not the rendered one', () => { renderDrawer() typeQuery('gamma') diff --git a/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.tsx b/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.tsx index ae896297df5..8bee18ab590 100644 --- a/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.tsx +++ b/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.tsx @@ -204,6 +204,15 @@ export default function WorkspaceKanbanDrawer({ () => buildWorkspaceKanbanLaneViews({ worktreesByStatus, matchingWorktreeIds }), [matchingWorktreeIds, worktreesByStatus] ) + // Why: range and area gestures must index the cards the user can actually see, + // or a shift-click across a filtered gap silently selects hidden workspaces. + const renderedBoardWorktrees = useMemo( + () => + matchingWorktreeIds + ? boardWorktrees.filter((worktree) => matchingWorktreeIds.has(worktree.id)) + : boardWorktrees, + [boardWorktrees, matchingWorktreeIds] + ) const { selectedWorktreeIds, selectedWorktrees, @@ -212,7 +221,7 @@ export default function WorkspaceKanbanDrawer({ updateSelectionForArea, clearSelection, selectForContextMenu - } = useWorkspaceKanbanSelection(open, boardWorktrees) + } = useWorkspaceKanbanSelection(open, boardWorktrees, renderedBoardWorktrees) const { handleAreaSelectionPointerDown } = useWorkspaceKanbanAreaSelection({ open, boardRef, @@ -800,8 +809,11 @@ export default function WorkspaceKanbanDrawer({ }} > { }) it('renders the search field as a sibling of the sheet title, not inside it', () => { - const header = renderHeader(vi.fn(), { query: 'orca', matchCount: 2, totalCount: 15 }) + const header = renderHeader(vi.fn(), { + query: 'orca', + isFiltering: true, + matchCount: 2, + totalCount: 15 + }) const title = findByType(header, SheetTitle) expect(title).not.toBeNull() expect(findByType(title, WorkspaceKanbanSearchField)).toBeNull() const field = findByType(header, WorkspaceKanbanSearchField) - expect(field?.props).toMatchObject({ query: 'orca', matchCount: 2, totalCount: 15 }) + expect(field?.props).toMatchObject({ + query: 'orca', + isFiltering: true, + matchCount: 2, + totalCount: 15 + }) }) it('keeps the filter, settings, and close cluster reachable alongside the field', () => { - const header = renderHeader(vi.fn(), { query: 'orca', matchCount: 2, totalCount: 15 }) + const header = renderHeader(vi.fn(), { + query: 'orca', + isFiltering: true, + matchCount: 2, + totalCount: 15 + }) expect(findByType(header, SidebarFilter)).not.toBeNull() expect(findByType(header, WorkspaceKanbanSettingsMenu)).not.toBeNull() diff --git a/src/renderer/src/components/sidebar/WorkspaceKanbanDrawerHeader.tsx b/src/renderer/src/components/sidebar/WorkspaceKanbanDrawerHeader.tsx index 6d1b513f388..732adb8750a 100644 --- a/src/renderer/src/components/sidebar/WorkspaceKanbanDrawerHeader.tsx +++ b/src/renderer/src/components/sidebar/WorkspaceKanbanDrawerHeader.tsx @@ -11,6 +11,7 @@ import { translate } from '@/i18n/i18n' type WorkspaceKanbanDrawerHeaderProps = { selectedCount: number query: string + isFiltering: boolean matchCount: number totalCount: number onQueryChange: (query: string) => void @@ -31,6 +32,7 @@ type WorkspaceKanbanDrawerHeaderProps = { export default function WorkspaceKanbanDrawerHeader({ selectedCount, query, + isFiltering, matchCount, totalCount, onQueryChange, @@ -72,6 +74,7 @@ export default function WorkspaceKanbanDrawerHeader({ { root.render( { expect(clearButton()?.getAttribute('aria-label')).toBe('Clear search') }) + it('withholds counts for text that never narrows the board', () => { + renderField({ query: ' ', isFiltering: false, matchCount: 12, totalCount: 12 }) + + expect(container.querySelector('span[aria-hidden="true"]')).toBeNull() + expect(clearButton()).not.toBeNull() + + act(() => { + vi.advanceTimersByTime(400) + }) + expect(liveRegion().textContent).toBe('') + }) + it('announces match counts only after the query settles', () => { renderField({ query: 'orca', matchCount: 3, totalCount: 12 }) expect(liveRegion().textContent).toBe('') diff --git a/src/renderer/src/components/sidebar/WorkspaceKanbanSearchField.tsx b/src/renderer/src/components/sidebar/WorkspaceKanbanSearchField.tsx index 4d5e36ce2cf..a418d3fa288 100644 --- a/src/renderer/src/components/sidebar/WorkspaceKanbanSearchField.tsx +++ b/src/renderer/src/components/sidebar/WorkspaceKanbanSearchField.tsx @@ -9,6 +9,8 @@ const ANNOUNCE_DEBOUNCE_MS = 400 type WorkspaceKanbanSearchFieldProps = { query: string + /** False for text that never narrows the board (whitespace-only, over-bound). */ + isFiltering: boolean matchCount: number totalCount: number onQueryChange: (query: string) => void @@ -30,18 +32,19 @@ function formatAnnouncement(matchCount: number, totalCount: number): string { export default function WorkspaceKanbanSearchField({ query, + isFiltering, matchCount, totalCount, onQueryChange, onClear }: WorkspaceKanbanSearchFieldProps): React.JSX.Element { - const hasQuery = query !== '' + const hasText = query !== '' const [announcement, setAnnouncement] = useState('') // Why: the filter itself is undebounced, but a polite live region that changes // on every keystroke produces continuous speech and makes the field unusable. useEffect(() => { - if (!hasQuery) { + if (!isFiltering) { setAnnouncement('') return } @@ -50,7 +53,7 @@ export default function WorkspaceKanbanSearchField({ ANNOUNCE_DEBOUNCE_MS ) return () => window.clearTimeout(timer) - }, [hasQuery, matchCount, totalCount]) + }, [isFiltering, matchCount, totalCount]) return (
@@ -67,13 +70,13 @@ export default function WorkspaceKanbanSearchField({ )} className={cn( 'h-7 border-worktree-sidebar-border bg-background pl-7 text-xs', - hasQuery && 'pr-16' + hasText && (isFiltering ? 'pr-16' : 'pr-8') )} onChange={(event) => onQueryChange(event.target.value)} onKeyDown={(event) => { // Why: the board swallows Radix close requests, so an empty-field // Escape has nothing local to do and is left to bubble. - if (event.key !== 'Escape' || !hasQuery) { + if (event.key !== 'Escape' || !hasText) { return } event.preventDefault() @@ -81,11 +84,13 @@ export default function WorkspaceKanbanSearchField({ onClear() }} /> - {hasQuery ? ( + {hasText ? (
- + {isFiltering ? ( + + ) : null}
- {hasQuery ? `${items.length} / ${totalCount ?? items.length}` : items.length} + {isFiltered ? `${items.length} / ${laneTotalCount}` : items.length}
@@ -192,7 +201,7 @@ export default function WorkspaceKanbanStatusLane({ ) : (
- {hasQuery + {isFiltered ? translate( 'auto.components.sidebar.WorkspaceKanbanStatusLane.2df01a03ff', 'No matches' diff --git a/src/renderer/src/components/sidebar/WorktreeList.tsx b/src/renderer/src/components/sidebar/WorktreeList.tsx index 2abe607a1c2..3d315cb616f 100644 --- a/src/renderer/src/components/sidebar/WorktreeList.tsx +++ b/src/renderer/src/components/sidebar/WorktreeList.tsx @@ -170,6 +170,7 @@ import { getWorkspaceKanbanSidebarDropTarget, hasWorkspaceKanbanSidebarDropBoard, isWorkspaceKanbanSidebarDropPointInBoard, + resolveWorkspaceKanbanSidebarFullLaneDropIndex, updateWorkspaceKanbanSidebarDropTargetVisual } from './workspace-kanban-sidebar-drop' import { @@ -3220,7 +3221,12 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp onDropWorktreesOnWorkspaceBoard({ worktreeIds: drag.reorderDraggedIds, status: boardDropTarget.status, - dropIndex: boardDropTarget.dropIndex, + // Why: the target counts rendered cards, but the groups are the full + // lane. Board search can make those two differ. + dropIndex: resolveWorkspaceKanbanSidebarFullLaneDropIndex( + boardDropTarget.status, + boardDropTarget.dropIndex + ), groups: getWorkspaceKanbanSidebarDropGroups() }) } else { diff --git a/src/renderer/src/components/sidebar/use-workspace-kanban-selection.test.tsx b/src/renderer/src/components/sidebar/use-workspace-kanban-selection.test.tsx new file mode 100644 index 00000000000..c885df7888f --- /dev/null +++ b/src/renderer/src/components/sidebar/use-workspace-kanban-selection.test.tsx @@ -0,0 +1,114 @@ +// @vitest-environment happy-dom + +import React, { act } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, beforeEach, describe, expect, it } from 'vitest' +import type { Worktree } from '../../../../shared/types' +import { useWorkspaceKanbanSelection } from './use-workspace-kanban-selection' + +;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + +type Selection = ReturnType + +function worktree(id: string): Worktree { + return { id, repoId: 'repo-a', displayName: id } as Worktree +} + +const alpha = worktree('alpha') +const beta = worktree('beta') +const gamma = worktree('gamma') +const delta = worktree('delta') +const fullBoard = [alpha, beta, gamma, delta] + +let container: HTMLDivElement +let root: Root +let selection: Selection + +function Probe({ + board, + rendered +}: { + board: readonly Worktree[] + rendered: readonly Worktree[] +}): null { + selection = useWorkspaceKanbanSelection(true, board, rendered) + return null +} + +function renderSelection( + rendered: readonly Worktree[] = fullBoard, + board: readonly Worktree[] = fullBoard +): void { + act(() => { + root.render() + }) +} + +function click(worktreeId: string, shiftKey = false): void { + act(() => { + selection.updateSelectionForGesture( + { metaKey: false, ctrlKey: false, shiftKey } as React.MouseEvent, + worktreeId + ) + }) +} + +function selectedIds(): string[] { + return [...selection.selectedWorktreeIds].sort() +} + +beforeEach(() => { + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) +}) + +afterEach(() => { + act(() => { + root.unmount() + }) + container.remove() +}) + +describe('useWorkspaceKanbanSelection', () => { + it('ranges across the whole board when nothing is filtered', () => { + renderSelection() + + click(alpha.id) + click(gamma.id, true) + + expect(selectedIds()).toEqual(['alpha', 'beta', 'gamma']) + }) + + it('never ranges through cards a search has hidden', () => { + renderSelection([alpha, gamma]) + + click(alpha.id) + click(gamma.id, true) + + expect(selectedIds()).toEqual(['alpha', 'gamma']) + }) + + it('keeps a hidden card selected so clearing the search restores the selection', () => { + renderSelection() + click(alpha.id) + click(beta.id, true) + expect(selectedIds()).toEqual(['alpha', 'beta']) + + renderSelection([alpha]) + expect(selectedIds()).toEqual(['alpha', 'beta']) + + renderSelection() + expect(selectedIds()).toEqual(['alpha', 'beta']) + }) + + it('still prunes ids that leave the board entirely', () => { + renderSelection() + click(alpha.id) + click(gamma.id, true) + + renderSelection([alpha, beta], [alpha, beta]) + + expect(selectedIds()).toEqual(['alpha', 'beta']) + }) +}) diff --git a/src/renderer/src/components/sidebar/use-workspace-kanban-selection.ts b/src/renderer/src/components/sidebar/use-workspace-kanban-selection.ts index a73fad19796..4db4f132a4c 100644 --- a/src/renderer/src/components/sidebar/use-workspace-kanban-selection.ts +++ b/src/renderer/src/components/sidebar/use-workspace-kanban-selection.ts @@ -8,11 +8,22 @@ import { updateWorktreeSelection } from './worktree-multi-selection' -export function useWorkspaceKanbanSelection(open: boolean, boardWorktrees: readonly Worktree[]) { +// Why: board search hides cards without dropping them from the board, so range +// and area gestures index the rendered subset while pruning still spans the +// whole board — otherwise a query would silently discard hidden selections. +export function useWorkspaceKanbanSelection( + open: boolean, + boardWorktrees: readonly Worktree[], + renderedWorktrees: readonly Worktree[] = boardWorktrees +) { const boardWorktreeIds = useMemo( () => boardWorktrees.map((worktree) => worktree.id), [boardWorktrees] ) + const renderedWorktreeIds = useMemo( + () => renderedWorktrees.map((worktree) => worktree.id), + [renderedWorktrees] + ) const [selectedWorktreeIds, setSelectedWorktreeIds] = useState>(new Set()) const [selectionAnchorId, setSelectionAnchorId] = useState(null) const selectedWorktrees = useMemo( @@ -43,7 +54,7 @@ export function useWorkspaceKanbanSelection(open: boolean, boardWorktrees: reado (event: React.MouseEvent, worktreeId: string): boolean => { const intent = getWorktreeSelectionIntent(event, navigator.userAgent.includes('Mac')) const result = updateWorktreeSelection({ - visibleIds: boardWorktreeIds, + visibleIds: renderedWorktreeIds, previousSelectedIds: selectedWorktreeIds, previousAnchorId: selectionAnchorId, targetId: worktreeId, @@ -53,7 +64,7 @@ export function useWorkspaceKanbanSelection(open: boolean, boardWorktrees: reado setSelectionAnchorId(result.anchorId) return intent !== 'replace' }, - [boardWorktreeIds, selectedWorktreeIds, selectionAnchorId] + [renderedWorktreeIds, selectedWorktreeIds, selectionAnchorId] ) const selectForContextMenu = useCallback( @@ -76,7 +87,7 @@ export function useWorkspaceKanbanSelection(open: boolean, boardWorktrees: reado baseAnchorId: string | null = selectionAnchorId ): void => { const result = updateWorktreeAreaSelection({ - visibleIds: boardWorktreeIds, + visibleIds: renderedWorktreeIds, previousSelectedIds: baseSelectedIds, previousAnchorId: baseAnchorId, areaIds, @@ -89,7 +100,7 @@ export function useWorkspaceKanbanSelection(open: boolean, boardWorktrees: reado previous === result.anchorId ? previous : result.anchorId ) }, - [boardWorktreeIds, selectedWorktreeIds, selectionAnchorId] + [renderedWorktreeIds, selectedWorktreeIds, selectionAnchorId] ) const clearSelection = useCallback(() => { diff --git a/src/renderer/src/components/sidebar/workspace-kanban-sidebar-drop.test.ts b/src/renderer/src/components/sidebar/workspace-kanban-sidebar-drop.test.ts index 4bf51612f09..67fdd0f67e3 100644 --- a/src/renderer/src/components/sidebar/workspace-kanban-sidebar-drop.test.ts +++ b/src/renderer/src/components/sidebar/workspace-kanban-sidebar-drop.test.ts @@ -6,6 +6,7 @@ import { getWorkspaceKanbanSidebarDropGroups, getWorkspaceKanbanSidebarDropTarget, isWorkspaceKanbanSidebarDropPointInBoard, + resolveWorkspaceKanbanSidebarFullLaneDropIndex, updateWorkspaceKanbanSidebarDropTargetVisual } from './workspace-kanban-sidebar-drop' @@ -257,16 +258,33 @@ describe('workspace kanban sidebar drop DOM bridge', () => { ]) }) - it('translates the rendered drop index onto the full lane', () => { + it('keeps the tracked drop target in the rendered index space of the indicator', () => { + const { lane } = appendBoard() + lane.dataset.workspaceLaneFullIds = ['doing-x', 'doing-a', 'doing-y', 'doing-b'].join('\n') + setElementFromPoint(lane) + + expect(getWorkspaceKanbanSidebarDropTarget(24, 60)).toMatchObject({ + status: 'doing', + dropIndex: 1 + }) + }) + + it('translates a rendered drop index onto the full lane at the commit boundary', () => { const { lane } = appendBoard() lane.dataset.workspaceLaneFullIds = ['doing-x', 'doing-a', 'doing-y', 'doing-b'].join('\n') setElementFromPoint(lane) // Rendered index 1 means "before doing-b", which is index 3 in the full lane. - expect(getWorkspaceKanbanSidebarDropTarget(24, 60)).toMatchObject({ - status: 'doing', - dropIndex: 3 - }) + expect(resolveWorkspaceKanbanSidebarFullLaneDropIndex('doing', 1)).toBe(3) + // Why: a tracked target can be committed after the pointer left the lane, + // so the translation must not depend on the current pointer position. + expect(resolveWorkspaceKanbanSidebarFullLaneDropIndex('doing', 2)).toBe(4) + }) + + it('passes the drop index through for a lane it cannot find', () => { + appendBoard() + + expect(resolveWorkspaceKanbanSidebarFullLaneDropIndex('todo', 2)).toBe(2) }) it('marks and clears the external board hover target', () => { diff --git a/src/renderer/src/components/sidebar/workspace-kanban-sidebar-drop.ts b/src/renderer/src/components/sidebar/workspace-kanban-sidebar-drop.ts index f2a3361fca1..f5daca23b7f 100644 --- a/src/renderer/src/components/sidebar/workspace-kanban-sidebar-drop.ts +++ b/src/renderer/src/components/sidebar/workspace-kanban-sidebar-drop.ts @@ -111,24 +111,30 @@ export function getWorkspaceKanbanSidebarDropTarget( if (!board) { return { status: null, isPinDrop: false, dropIndex: 0 } } - const target = getCardDropTarget(board, x, y) - if (!target.status) { - return target - } - const lane = getStatusDropTargetElement(board, target.status) + return getCardDropTarget(board, x, y) +} + +/** + * Translates a tracked drop index — which counts *rendered* cards, matching the + * drop indicator — onto the full lane that `getWorkspaceKanbanSidebarDropGroups` + * reports. Call this once, at the commit boundary: a tracked target can be + * committed after the pointer has left the lane, so translating earlier would + * miss that path. + */ +export function resolveWorkspaceKanbanSidebarFullLaneDropIndex( + status: WorkspaceStatus, + renderedDropIndex: number +): number { + const board = getWorkspaceKanbanBoardElement() + const lane = board ? getStatusDropTargetElement(board, status) : null if (!lane) { - return target - } - // Why: getCardDropTarget counts rendered cards, but the groups above are the - // full lane. Committing the two together would interpolate the wrong rank. - return { - ...target, - dropIndex: resolveFullLaneDropIndex({ - fullLaneIds: getLaneFullWorktreeIds(lane), - renderedIds: getRenderedLaneCardIds(lane), - filteredDropIndex: target.dropIndex - }) + return renderedDropIndex } + return resolveFullLaneDropIndex({ + fullLaneIds: getLaneFullWorktreeIds(lane), + renderedIds: getRenderedLaneCardIds(lane), + filteredDropIndex: renderedDropIndex + }) } export function updateWorkspaceKanbanSidebarDropTargetVisual(args: { diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 648122ce0c0..1ef31c9d5a8 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -4366,7 +4366,7 @@ "WorkspaceKanbanStatusLane": { "8ad104642b": "Vacío", "3611d1ae7f": "Cambiar tamaño de columnas del tablero de espacios de trabajo", - "2df01a03ff": "No matches" + "2df01a03ff": "No hay coincidencias" }, "WorkspaceStatusAppearancePopover": { "514be2f569": "Establecer color de {{value0}} en {{value1}}", @@ -5004,11 +5004,11 @@ "parkTerminal": "Park terminal" }, "WorkspaceKanbanSearchField": { - "bdb753c78d": "No workspaces match", - "4d96c209d6": "{{value0}} of {{value1}} workspaces match", - "c0cd6bdf6c": "Search workspaces", - "49c266baaa": "Search", - "3b7ea51793": "Clear search" + "bdb753c78d": "Ningún espacio de trabajo coincide", + "4d96c209d6": "{{value0}} de {{value1}} espacios de trabajo coinciden", + "c0cd6bdf6c": "Buscar espacios de trabajo", + "49c266baaa": "Buscar", + "3b7ea51793": "Borrar búsqueda" } }, "shared": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 197c9d97362..9ba43069893 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -4347,7 +4347,7 @@ "WorkspaceKanbanStatusLane": { "8ad104642b": "空の", "3611d1ae7f": "ワークスペースボードの列のサイズを変更する", - "2df01a03ff": "No matches" + "2df01a03ff": "一致なし" }, "WorkspaceStatusAppearancePopover": { "514be2f569": "{{value0}} の色を {{value1}} に設定します", @@ -5004,11 +5004,11 @@ "parkTerminal": "Park terminal" }, "WorkspaceKanbanSearchField": { - "bdb753c78d": "No workspaces match", - "4d96c209d6": "{{value0}} of {{value1}} workspaces match", - "c0cd6bdf6c": "Search workspaces", - "49c266baaa": "Search", - "3b7ea51793": "Clear search" + "bdb753c78d": "一致するワークスペースはありません", + "4d96c209d6": "{{value1}} 件中 {{value0}} 件のワークスペースが一致します", + "c0cd6bdf6c": "ワークスペースの検索", + "49c266baaa": "検索", + "3b7ea51793": "検索をクリア" } }, "shared": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index c8fffed7bd1..1bfe9d0e3fa 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -4347,7 +4347,7 @@ "WorkspaceKanbanStatusLane": { "8ad104642b": "비어 있음", "3611d1ae7f": "워크스페이스 보드 열 크기 조정", - "2df01a03ff": "No matches" + "2df01a03ff": "일치하는 항목 없음" }, "WorkspaceStatusAppearancePopover": { "514be2f569": "{{value0}} 색상을 {{value1}}로 설정", @@ -5004,11 +5004,11 @@ "parkTerminal": "Park terminal" }, "WorkspaceKanbanSearchField": { - "bdb753c78d": "No workspaces match", - "4d96c209d6": "{{value0}} of {{value1}} workspaces match", - "c0cd6bdf6c": "Search workspaces", - "49c266baaa": "Search", - "3b7ea51793": "Clear search" + "bdb753c78d": "일치하는 워크스페이스가 없습니다", + "4d96c209d6": "워크스페이스 {{value1}}개 중 {{value0}}개 일치", + "c0cd6bdf6c": "워크스페이스 검색", + "49c266baaa": "검색", + "3b7ea51793": "검색 지우기" } }, "shared": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 0a5883e8bef..4ddcd3749cc 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -4347,7 +4347,7 @@ "WorkspaceKanbanStatusLane": { "8ad104642b": "空的", "3611d1ae7f": "调整工作区板列的大小", - "2df01a03ff": "No matches" + "2df01a03ff": "没有匹配项" }, "WorkspaceStatusAppearancePopover": { "514be2f569": "将 {{value0}} 颜色设置为 {{value1}}", @@ -5004,11 +5004,11 @@ "parkTerminal": "Park terminal" }, "WorkspaceKanbanSearchField": { - "bdb753c78d": "No workspaces match", - "4d96c209d6": "{{value0}} of {{value1}} workspaces match", - "c0cd6bdf6c": "Search workspaces", - "49c266baaa": "Search", - "3b7ea51793": "Clear search" + "bdb753c78d": "没有匹配的工作区", + "4d96c209d6": "{{value1}} 个工作区中有 {{value0}} 个匹配", + "c0cd6bdf6c": "搜索工作区", + "49c266baaa": "搜索", + "3b7ea51793": "清除搜索" } }, "shared": {