diff --git a/src/main/app-controls/mcp/toolRegistry.test.ts b/src/main/app-controls/mcp/toolRegistry.test.ts index 2d76b5b09..811ef9ffe 100644 --- a/src/main/app-controls/mcp/toolRegistry.test.ts +++ b/src/main/app-controls/mcp/toolRegistry.test.ts @@ -594,7 +594,7 @@ describe("Poracode app control tools — threads", () => { it("update_thread dispatches the matching remote thread commands", async () => { const threads = [makeThread({ id: "a" })]; - const { ctx, emitRemoteThreadCommand } = context({ threads }); + const { ctx, emitRemoteThreadCommand, updatedRows } = context({ threads }); const result = (await dispatchTool( "update_thread", { threadId: "a", rename: "Renamed", done: true, archived: true }, @@ -612,6 +612,7 @@ describe("Poracode app control tools — threads", () => { done: true, }); expect(emitRemoteThreadCommand).toHaveBeenCalledWith({ kind: "archive", threadId: "a" }); + expect(updatedRows.at(-1)).toMatchObject({ archived: true, archivedAt: expect.any(String) }); }); it("send_to_thread interrupts first when requested then sends", async () => { diff --git a/src/main/app-controls/mcp/tools/threads.ts b/src/main/app-controls/mcp/tools/threads.ts index 887d35102..0b6a7c48d 100644 --- a/src/main/app-controls/mcp/tools/threads.ts +++ b/src/main/app-controls/mcp/tools/threads.ts @@ -509,7 +509,15 @@ export const threadTools: ToolDomain = { })); applyField(parsed.archived, "archived", (archived) => ({ command: { kind: archived ? "archive" : "unarchive", threadId }, - mutate: (thread) => ({ ...thread, archived, updatedAt: stamp() }), + mutate: (thread) => { + const now = stamp(); + return { + ...thread, + archived, + archivedAt: archived ? now : undefined, + updatedAt: now, + }; + }, })); if (parsed.acknowledge) { const command: RemoteThreadCommand = { kind: "acknowledge", threadId }; diff --git a/src/main/db.schema.ts b/src/main/db.schema.ts index 3de5082d4..1a7fdaf0e 100644 --- a/src/main/db.schema.ts +++ b/src/main/db.schema.ts @@ -47,6 +47,7 @@ export const threads = sqliteTable("threads", { /** Orchestrator thread that created this one via the Crossagents MCP. */ parentThreadId: text("parent_thread_id"), archived: integer("archived", { mode: "boolean" }).notNull().default(false), + archivedAt: text("archived_at"), done: integer("done", { mode: "boolean" }).notNull().default(false), doneAt: text("done_at"), starred: integer("starred", { mode: "boolean" }).notNull().default(false), diff --git a/src/main/db/connection.ts b/src/main/db/connection.ts index c4ef70855..812a87f50 100644 --- a/src/main/db/connection.ts +++ b/src/main/db/connection.ts @@ -131,6 +131,7 @@ export function initDatabase(dbPath: string) { worktree_branch TEXT, pr_number INTEGER, archived INTEGER NOT NULL DEFAULT 0, + archived_at TEXT, done INTEGER NOT NULL DEFAULT 0, done_at TEXT, sort_order INTEGER NOT NULL DEFAULT 0, diff --git a/src/main/db/migrations.test.ts b/src/main/db/migrations.test.ts index 86ce7a988..8b28c590f 100644 --- a/src/main/db/migrations.test.ts +++ b/src/main/db/migrations.test.ts @@ -39,8 +39,9 @@ describe("database migration registry", () => { [32, "pr watch blocked reason"], [33, "project GitHub account"], [34, "projects.icon"], + [35, "threads.archived_at"], ]); - expect(LATEST_SCHEMA_VERSION).toBe(34); + expect(LATEST_SCHEMA_VERSION).toBe(35); expect(() => validateMigrationRegistry()).not.toThrow(); }); diff --git a/src/main/db/migrations.ts b/src/main/db/migrations.ts index fdf4ee889..36e236ace 100644 --- a/src/main/db/migrations.ts +++ b/src/main/db/migrations.ts @@ -382,6 +382,16 @@ export const DATABASE_MIGRATIONS = [ name: "projects.icon", migrate: (sqlite) => addColumnIfMissing(sqlite, "projects", "icon", "TEXT"), }, + { + version: 35, + name: "threads.archived_at", + migrate: (sqlite) => { + addColumnIfMissing(sqlite, "threads", "archived_at", "TEXT"); + sqlite.exec( + "UPDATE threads SET archived_at = updated_at WHERE archived = 1 AND archived_at IS NULL", + ); + }, + }, ] as const satisfies readonly DatabaseMigration[]; export const LATEST_SCHEMA_VERSION = DATABASE_MIGRATIONS[DATABASE_MIGRATIONS.length - 1]!.version; @@ -452,6 +462,7 @@ const SAFE_COLUMN_REPAIRS = [ ["threads", "agent_instance_id", "TEXT"], ["threads", "thread_status_source", "TEXT"], ["threads", "parent_thread_id", "TEXT"], + ["threads", "archived_at", "TEXT"], ["threads", "active_turn_started_at", "TEXT"], ["threads", "last_turn_started_at", "TEXT"], ["threads", "last_turn_ended_at", "TEXT"], @@ -470,6 +481,9 @@ export function repairSafeSchemaDrift(sqlite: SqliteDatabase): void { for (const [table, column, definition] of SAFE_COLUMN_REPAIRS) { addColumnIfMissing(sqlite, table, column, definition); } + sqlite.exec( + "UPDATE threads SET archived_at = updated_at WHERE archived = 1 AND archived_at IS NULL", + ); })(); } @@ -514,6 +528,7 @@ const REQUIRED_COLUMNS = { "group_name", "parent_thread_id", "archived", + "archived_at", "done", "done_at", "starred", diff --git a/src/main/db/projectsThreads.test.ts b/src/main/db/projectsThreads.test.ts index c768d2c0d..b4abd8728 100644 --- a/src/main/db/projectsThreads.test.ts +++ b/src/main/db/projectsThreads.test.ts @@ -114,6 +114,37 @@ describe("projectsThreads (real sqlite round-trip)", () => { expect(dbGetThread("thread-1")?.threadStatusSource).toBeUndefined(); }); + it("round-trips and clears the thread archive timestamp", () => { + dbUpsertThread( + testThread({ + archived: true, + archivedAt: "2026-02-02T03:04:05.000Z", + }), + 0, + ); + expect(dbGetThread("thread-1")?.archivedAt).toBe("2026-02-02T03:04:05.000Z"); + + dbUpsertThread(testThread(), 0); + expect(dbGetThread("thread-1")?.archivedAt).toBeUndefined(); + }); + + it("backfills archive timestamps when upgrading schema v34", () => { + dbUpsertThread( + testThread({ + archived: true, + updatedAt: "2026-02-03T04:05:06.000Z", + }), + 0, + ); + dbSetState("schema_version", "34"); + + closeDatabase(); + initDatabase(join(dir, "state.sqlite")); + + expect(dbGetThread("thread-1")?.archivedAt).toBe("2026-02-03T04:05:06.000Z"); + expect(dbGetState("schema_version")).toBe(String(LATEST_SCHEMA_VERSION)); + }); + it("round-trips project MCP servers through the projects table", () => { dbUpsertProject( { @@ -263,7 +294,7 @@ describe("projectsThreads (real sqlite round-trip)", () => { initDatabase(databasePath); - expect(dbGetState("schema_version")).toBe("34"); + expect(dbGetState("schema_version")).toBe("35"); const legacyProject = dbGetProject("legacy-project"); expect(legacyProject).toMatchObject({ id: "legacy-project", @@ -372,6 +403,86 @@ describe("projectsThreads (real sqlite round-trip)", () => { }); }); + it("repairs and backfills archived_at when schema v35 is missing the column", () => { + closeDatabase(); + const databasePath = join(dir, "corrupt-v35.sqlite"); + const corrupt = nativeBindingEnv + ? new Database(databasePath, { nativeBinding: nativeBindingEnv }) + : new Database(databasePath); + corrupt.exec(` + CREATE TABLE projects ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + location_kind TEXT NOT NULL, + location_path TEXT, + location_distro TEXT, + location_linux_path TEXT, + location_unc_path TEXT, + last_draft_config TEXT, + scripts TEXT, + sort_order INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL + ); + CREATE TABLE threads ( + id TEXT PRIMARY KEY, + project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + title TEXT NOT NULL, + agent_kind TEXT NOT NULL, + agent_instance_id TEXT, + config TEXT NOT NULL, + status TEXT NOT NULL, + attention TEXT NOT NULL, + thread_status_source TEXT, + can_resume_with_config INTEGER NOT NULL DEFAULT 0, + session_ref TEXT, + terminal_prompt TEXT, + worktree_path TEXT, + worktree_branch TEXT, + pr_number INTEGER, + group_id TEXT, + group_name TEXT, + parent_thread_id TEXT, + archived INTEGER NOT NULL DEFAULT 0, + done INTEGER NOT NULL DEFAULT 0, + done_at TEXT, + starred INTEGER NOT NULL DEFAULT 0, + presentation_mode TEXT NOT NULL DEFAULT 'terminal', + sort_order INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + active_turn_started_at TEXT, + last_turn_started_at TEXT, + last_turn_ended_at TEXT + ); + CREATE TABLE app_state ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ); + INSERT INTO projects ( + id, name, location_kind, location_path, sort_order, created_at + ) VALUES ( + 'legacy-project', 'Legacy project', 'posix', '/tmp/legacy-project', 0, + '2026-01-01T00:00:00.000Z' + ); + INSERT INTO threads ( + id, project_id, title, agent_kind, config, status, attention, + can_resume_with_config, archived, done, starred, presentation_mode, + sort_order, created_at, updated_at + ) VALUES ( + 'legacy-archived', 'legacy-project', 'Legacy archived', 'claude', + '{"model":"sonnet"}', 'inactive', 'none', 0, 1, 0, 0, 'gui', 0, + '2026-01-01T00:00:00.000Z', '2026-02-03T04:05:06.000Z' + ); + INSERT INTO app_state (key, value) VALUES ('schema_version', '35'); + `); + corrupt.close(); + + initDatabase(databasePath); + + expect(dbGetThread("legacy-archived")?.archivedAt).toBe("2026-02-03T04:05:06.000Z"); + expect(dbGetState("schema_version")).toBe("35"); + }); + it("repairs blank legacy thread models from schema v29 without changing valid configs", () => { const sqlite = getSqlite(); const insert = sqlite.prepare(` diff --git a/src/main/db/projectsThreads.ts b/src/main/db/projectsThreads.ts index 6d7e84342..1ef68c814 100644 --- a/src/main/db/projectsThreads.ts +++ b/src/main/db/projectsThreads.ts @@ -115,6 +115,7 @@ export function dbUpsertThread(thread: Thread, sortOrder: number): void { groupName: thread.groupName ?? null, parentThreadId: thread.parentThreadId ?? null, archived: thread.archived, + archivedAt: thread.archivedAt ?? null, done: thread.done, doneAt: thread.doneAt ?? null, starred: thread.starred, @@ -145,6 +146,7 @@ export function dbUpsertThread(thread: Thread, sortOrder: number): void { groupName: thread.groupName ?? null, parentThreadId: thread.parentThreadId ?? null, archived: thread.archived, + archivedAt: thread.archivedAt ?? null, done: thread.done, doneAt: thread.doneAt ?? null, starred: thread.starred, diff --git a/src/main/db/rowMappers.ts b/src/main/db/rowMappers.ts index 885cfb767..231852f7f 100644 --- a/src/main/db/rowMappers.ts +++ b/src/main/db/rowMappers.ts @@ -90,6 +90,7 @@ export function rowToThread(row: typeof schema.threads.$inferSelect): Thread { ...(row.groupName ? { groupName: row.groupName } : {}), ...(row.parentThreadId ? { parentThreadId: row.parentThreadId } : {}), archived: row.archived, + ...(row.archivedAt ? { archivedAt: row.archivedAt } : {}), done: row.done, ...(row.doneAt ? { doneAt: row.doneAt } : {}), starred: row.starred, diff --git a/src/main/db/sync.ts b/src/main/db/sync.ts index 959852f24..e5972da81 100644 --- a/src/main/db/sync.ts +++ b/src/main/db/sync.ts @@ -144,13 +144,13 @@ function prepareThreadSyncStatement(sqlite: InstanceType): Sqli INSERT INTO threads ( id, project_id, title, agent_kind, agent_instance_id, config, status, attention, can_resume_with_config, session_ref, terminal_prompt, worktree_path, - worktree_branch, pr_number, group_id, group_name, parent_thread_id, archived, done, done_at, + worktree_branch, pr_number, group_id, group_name, parent_thread_id, archived, archived_at, done, done_at, starred, presentation_mode, sort_order, created_at, updated_at, active_turn_started_at, last_turn_started_at, last_turn_ended_at ) VALUES ( @id, @projectId, @title, @agentKind, @agentInstanceId, @config, @status, @attention, @canResumeWithConfig, @sessionRef, NULL, @worktreePath, - @worktreeBranch, @prNumber, @groupId, @groupName, @parentThreadId, @archived, @done, @doneAt, + @worktreeBranch, @prNumber, @groupId, @groupName, @parentThreadId, @archived, @archivedAt, @done, @doneAt, @starred, @presentationMode, @sortOrder, @createdAt, @updatedAt, @activeTurnStartedAt, @lastTurnStartedAt, @lastTurnEndedAt ) @@ -170,6 +170,7 @@ function prepareThreadSyncStatement(sqlite: InstanceType): Sqli group_name = excluded.group_name, parent_thread_id = excluded.parent_thread_id, archived = excluded.archived, + archived_at = excluded.archived_at, done = excluded.done, done_at = excluded.done_at, starred = excluded.starred, @@ -201,6 +202,7 @@ function runThreadSync(stmt: SqliteStatement, thread: Thread, sortOrder: number) groupName: thread.groupName ?? null, parentThreadId: thread.parentThreadId ?? null, archived: thread.archived ? 1 : 0, + archivedAt: thread.archivedAt ?? null, done: thread.done ? 1 : 0, doneAt: thread.doneAt ?? null, starred: thread.starred ? 1 : 0, diff --git a/src/main/remote/RemoteAccessServer.test.ts b/src/main/remote/RemoteAccessServer.test.ts index b2d22b6cc..af308bc0a 100644 --- a/src/main/remote/RemoteAccessServer.test.ts +++ b/src/main/remote/RemoteAccessServer.test.ts @@ -3097,7 +3097,10 @@ describe("RemoteAccessServer", () => { }); it("persists simple thread commands and mirrors them to the renderer", async () => { - const db = mockThreadDb([createTestThread()]); + const db = mockThreadDb([ + createTestThread({ worktreePath: "/repo/wt" }), + createTestThread({ id: "thread-2", worktreePath: "/repo/wt" }), + ]); const dispatched: unknown[] = []; let rendererAvailable = true; const callSupervisor = vi.fn( @@ -3171,15 +3174,14 @@ describe("RemoteAccessServer", () => { { method: "POST", headers, body: JSON.stringify({ kind: "archive" }) }, ); expect(archiveResponse.status).toBe(200); - expect(db.threads()[0]?.archived).toBe(true); + expect(db.threads()[0]).toMatchObject({ archived: true, archivedAt: expect.any(String) }); await expect(readWs()).resolves.toMatchObject({ type: "event", event: { type: "remote-threads-changed", threadIds: ["thread-1"] }, }); - rendererAvailable = true; - - const deleteWorktreeResponse = await fetch( + rendererAvailable = false; + const unavailableResponse = await fetch( new URL("/api/threads/thread-1/command", info.httpBaseUrl), { method: "POST", @@ -3192,18 +3194,13 @@ describe("RemoteAccessServer", () => { }), }, ); - expect(deleteWorktreeResponse.status).toBe(200); - expect(dispatched[2]).toEqual({ - kind: "delete-worktree-group", - threadId: "thread-1", - projectId: "project-1", - worktreePath: "/repo/wt", - threadIds: ["thread-1", "thread-2"], + expect(unavailableResponse.status).toBe(503); + await expect(unavailableResponse.json()).resolves.toMatchObject({ + error: { code: "desktop_unavailable" }, }); - expect(db.threads()).toEqual([]); - rendererAvailable = false; - const unavailableResponse = await fetch( + rendererAvailable = true; + const deleteWorktreeResponse = await fetch( new URL("/api/threads/thread-1/command", info.httpBaseUrl), { method: "POST", @@ -3212,17 +3209,68 @@ describe("RemoteAccessServer", () => { kind: "delete-worktree-group", projectId: "project-1", worktreePath: "/repo/wt", - threadIds: ["thread-1"], + threadIds: ["thread-1", "thread-2"], }), }, ); - expect(unavailableResponse.status).toBe(503); - await expect(unavailableResponse.json()).resolves.toMatchObject({ - error: { code: "desktop_unavailable" }, + expect(deleteWorktreeResponse.status).toBe(200); + expect(dispatched[2]).toEqual({ + kind: "delete-worktree-group", + threadId: "thread-1", + projectId: "project-1", + worktreePath: "/repo/wt", + threadIds: ["thread-1", "thread-2"], }); + expect(db.threads()).toEqual([]); ws.close(); }); + it("rejects deleting a worktree when its linked thread set changed", async () => { + const threads = [ + createTestThread({ worktreePath: "/repo/wt" }), + createTestThread({ id: "thread-2", worktreePath: "/repo/wt" }), + createTestThread({ id: "thread-3", worktreePath: "/repo/wt" }), + ]; + const db = mockThreadDb(threads); + const dispatchThreadCommand = vi.fn< + NonNullable + >(() => true); + const server = new RemoteAccessServer({ + appVersion: "1.0.0", + identity: { desktopId: "desktop-test", label: "Test Desktop" }, + host: "127.0.0.1", + port: 0, + callSupervisor: vi.fn( + async () => undefined as never, + ), + dispatchThreadCommand, + }); + servers.push(server); + const info = await server.start(); + const token = await issueAccessToken(info, ["session:operate"]); + + const response = await fetch(new URL("/api/threads/thread-1/command", info.httpBaseUrl), { + method: "POST", + headers: { + authorization: `Bearer ${token}`, + "content-type": "application/json", + }, + body: JSON.stringify({ + kind: "delete-worktree-group", + projectId: "project-1", + worktreePath: "/repo/wt", + threadIds: ["thread-1", "thread-2"], + }), + }); + + expect(response.status).toBe(409); + await expect(response.json()).resolves.toMatchObject({ + error: { code: "worktree_threads_changed" }, + }); + expect(dispatchThreadCommand).not.toHaveBeenCalled(); + expect(db.threads()).toEqual(threads); + }); + it("acknowledges a finished thread in the source DB and renderer", async () => { const db = mockThreadDb([createTestThread({ status: "finished" })]); const dispatched: unknown[] = []; diff --git a/src/main/remote/server/httpRouter.ts b/src/main/remote/server/httpRouter.ts index 2feeb43c1..780f76312 100644 --- a/src/main/remote/server/httpRouter.ts +++ b/src/main/remote/server/httpRouter.ts @@ -49,6 +49,7 @@ import { dbGetProject, dbGetProjectNotes, dbGetThread, + dbGetThreads, dbSetProjectNotes, dbTruncateThreadRuntimeAfter, } from "../../db"; @@ -876,6 +877,24 @@ export async function handleHttp( assertRemoteThreadCommandExperimentSafe(command); const dispatch = async () => { if (command.kind === "delete-worktree-group") { + const linkedThreadIds = dbGetThreads() + .filter( + (thread) => + thread.projectId === command.projectId && + thread.worktreePath === command.worktreePath, + ) + .map((thread) => thread.id); + const requestedThreadIds = new Set(command.threadIds); + if ( + requestedThreadIds.size !== linkedThreadIds.length || + linkedThreadIds.some((threadId) => !requestedThreadIds.has(threadId)) + ) { + throw new RemoteHttpError( + "worktree_threads_changed", + msg("remote.worktree.threadsChanged"), + 409, + ); + } if (ctx.options.dispatchThreadCommand?.(command) !== true) { throw new RemoteHttpError( "desktop_unavailable", diff --git a/src/main/remote/server/threadCommands.ts b/src/main/remote/server/threadCommands.ts index 7c4c8b882..55ddcb9ac 100644 --- a/src/main/remote/server/threadCommands.ts +++ b/src/main/remote/server/threadCommands.ts @@ -185,16 +185,21 @@ export async function applyRemoteThreadCommand( return false; case "archive": await closeThreadBestEffort(ctx, command.threadId); - updateRemoteThread(command.threadId, (thread) => ({ - ...thread, - archived: true, - updatedAt: new Date().toISOString(), - })); + { + const now = new Date().toISOString(); + updateRemoteThread(command.threadId, (thread) => ({ + ...thread, + archived: true, + archivedAt: now, + updatedAt: now, + })); + } return false; case "unarchive": updateRemoteThread(command.threadId, (thread) => ({ ...thread, archived: false, + archivedAt: undefined, updatedAt: new Date().toISOString(), })); return false; diff --git a/src/renderer/actions/threadActions.test.ts b/src/renderer/actions/threadActions.test.ts index 0ee3b2275..ecb3ad226 100644 --- a/src/renderer/actions/threadActions.test.ts +++ b/src/renderer/actions/threadActions.test.ts @@ -11,6 +11,7 @@ import { useWorktreeDeleteStore } from "@/renderer/state/worktreeDeleteStore"; import { archiveThread, deleteThread, + deleteThreadsAndOwnedWorktrees, requestDeleteThread, openNewThread, openThread, @@ -548,6 +549,7 @@ describe("threadActions", () => { resolveCommand(); await waitFor(() => expect(useAppStore.getState().threads[0]?.archived).toBe(true)); + expect(useAppStore.getState().threads[0]?.archivedAt).toEqual(expect.any(String)); }); it("tags a local thread with worktree metadata in the store", async () => { @@ -650,6 +652,40 @@ describe("threadActions", () => { expect(useWorktreeDeleteStore.getState().dialog).toBeNull(); }); + it("removes a worktree once when every linked thread is selected", () => { + const worktreePath = "/repo/.worktrees/feature"; + const firstThread = makeThread({ id: "thread-a", worktreePath }); + const secondThread = makeThread({ id: "thread-b", worktreePath }); + const standalone = makeThread({ id: "thread-c" }); + useAppStore.setState({ threads: [firstThread, secondThread, standalone] }); + + deleteThreadsAndOwnedWorktrees([firstThread, secondThread, standalone]); + + expect(deleteWorktreeGroup).toHaveBeenCalledExactlyOnceWith( + firstThread.projectId, + worktreePath, + [firstThread.id, secondThread.id], + ); + expect(useAppStore.getState().threads.map((thread) => thread.id)).toEqual([ + firstThread.id, + secondThread.id, + ]); + expect(bridge.closeThread).toHaveBeenCalledWith({ threadId: standalone.id }); + }); + + it("keeps a worktree when an unselected linked thread remains", () => { + const worktreePath = "/repo/.worktrees/feature"; + const selected = makeThread({ id: "thread-a", worktreePath }); + const remaining = makeThread({ id: "thread-b", worktreePath }); + useAppStore.setState({ threads: [selected, remaining] }); + + deleteThreadsAndOwnedWorktrees([selected]); + + expect(deleteWorktreeGroup).not.toHaveBeenCalled(); + expect(useAppStore.getState().threads).toEqual([remaining]); + expect(bridge.closeThread).toHaveBeenCalledWith({ threadId: selected.id }); + }); + it("prompts to remove the worktree when deleting the sole thread using it", () => { const worktreePath = "/repo/.worktrees/feature"; const thread = makeThread({ diff --git a/src/renderer/actions/threadActions.ts b/src/renderer/actions/threadActions.ts index 7ac4c38be..e3e0875b8 100644 --- a/src/renderer/actions/threadActions.ts +++ b/src/renderer/actions/threadActions.ts @@ -657,6 +657,37 @@ export function deleteThread(threadId: string, worktreePath?: string, projectId? deleteWorktreeGroup(project.id, worktreePath, [threadId]); } +export function deleteThreadsAndOwnedWorktrees(threads: readonly Thread[]): void { + const selectedThreadIds = new Set(threads.map((thread) => thread.id)); + const allThreads = useAppStore.getState().threads; + const deletedWorktrees = new Set(); + + for (const thread of threads) { + if (!thread.worktreePath) { + deleteThread(thread.id); + continue; + } + + const worktreeKey = `${thread.projectId}\0${thread.worktreePath}`; + if (deletedWorktrees.has(worktreeKey)) continue; + + const siblings = allThreads.filter( + (candidate) => + candidate.projectId === thread.projectId && candidate.worktreePath === thread.worktreePath, + ); + if (siblings.every((candidate) => selectedThreadIds.has(candidate.id))) { + deletedWorktrees.add(worktreeKey); + deleteWorktreeGroup( + thread.projectId, + thread.worktreePath, + siblings.map((candidate) => candidate.id), + ); + } else { + deleteThread(thread.id); + } + } +} + /** * Sidebar-initiated delete: asks first unless the user turned confirmation off, * then routes through `deleteThread`. Every thread is confirmed the same way, diff --git a/src/renderer/i18n/sharedMessages.ts b/src/renderer/i18n/sharedMessages.ts index b240a3181..3dd20d79f 100644 --- a/src/renderer/i18n/sharedMessages.ts +++ b/src/renderer/i18n/sharedMessages.ts @@ -236,6 +236,9 @@ const SHARED_MESSAGE_DESCRIPTORS: Record = { "remote.project.experimentsOwned": msg({ message: "Remove the project's experiments before removing the project.", }), + "remote.worktree.threadsChanged": msg({ + message: "The threads linked to this worktree changed. Refresh and try again.", + }), "remote.session.expired": msg({ message: "Pairing expired — pair again to reconnect.", }), diff --git a/src/renderer/locales/de/messages.po b/src/renderer/locales/de/messages.po index 4cfd82246..c37c5a94f 100644 --- a/src/renderer/locales/de/messages.po +++ b/src/renderer/locales/de/messages.po @@ -1439,6 +1439,10 @@ msgstr "Thread archivieren" msgid "Archive Thread" msgstr "Thread archivieren" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Archived at {archivedTime}" +msgstr "Archiviert um {archivedTime}" + #: src/mobile/settingsSections.ts #: src/mobile/views/SettingsView.tsx #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1486,6 +1490,10 @@ msgstr "Genehmigung anfordern" msgid "Ask Question" msgstr "Frage stellen" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Associated worktrees with no remaining threads will also be removed." +msgstr "Zugehörige Worktrees ohne verbleibende Threads werden ebenfalls entfernt." + #: src/renderer/views/SchedulesView/ScheduleEditor.tsx msgid "At" msgstr "Um" @@ -2460,6 +2468,14 @@ msgstr "Sauberer Arbeitsbaum" msgid "Cleanup script" msgstr "Bereinigungsskript" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear all" +msgstr "Alle löschen" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear archived threads from {dateLabel}" +msgstr "Archivierte Threads vom {dateLabel} löschen" + #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx msgid "Clear browsing history" msgstr "Browserverlauf löschen" @@ -2476,6 +2492,10 @@ msgstr "Cache leeren" msgid "Clear Cookies" msgstr "Cookies löschen" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear day" +msgstr "Tag leeren" + #: src/renderer/components/thread/ThreadGoalControls.tsx msgid "Clear goal" msgstr "Ziel löschen" @@ -3920,6 +3940,7 @@ msgstr "Delegieren Sie Aufgaben an andere KI-Agenten." #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts #: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" @@ -3960,6 +3981,14 @@ msgstr "<0>{0} löschen? Dies kann nicht rückgängig gemacht werden." #~ msgid "Delete all done threads" #~ msgstr "Alle erledigten Threads löschen" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived thread?" +msgstr "Archivierten Thread löschen?" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived threads?" +msgstr "Archivierte Threads löschen?" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Delete branch?" msgstr "Zweig löschen?" @@ -3974,8 +4003,8 @@ msgstr "Datei löschen" #. Tooltip: permanently delete the archived thread #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx -msgid "Delete permanently" -msgstr "Dauerhaft löschen" +#~ msgid "Delete permanently" +#~ msgstr "Dauerhaft löschen" #: src/renderer/views/GitHubActionsView/GitHubActionsView.tsx msgid "Delete run" @@ -6618,6 +6647,10 @@ msgstr "Suche nach Dev-Servern…" msgid "Losing worktrees and branches will be removed. Their session history will remain." msgstr "Die Worktrees und Branches der unterlegenen Kandidaten werden entfernt. Ihr Sitzungsverlauf bleibt erhalten." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Machine" +msgstr "Computer" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx msgid "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." msgstr "Die Seitenleiste durchscheinend machen — das Unschärfe-Material des Systems unter Windows 11, sonst eine durchscheinende Tönung." @@ -8709,6 +8742,22 @@ msgstr "noch ausstehende Überprüfung" msgid "Pending steer" msgstr "Ausstehende Lenkung" +#. placeholder {0}: clearTarget.threads.length +#. placeholder {1}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete {0} archived threads from {1}." +#~ msgstr "{0} archivierte Threads von {1} dauerhaft löschen." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Permanently delete the selected archive entries from {0}." +msgstr "Die ausgewählten Archiveinträge von {0} dauerhaft löschen." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete the selected archived threads from {0}." +#~ msgstr "Die ausgewählten archivierten Threads von {0} dauerhaft löschen." + #: src/renderer/components/thread/ThreadRuntimeRequestPanel/parts/PermissionDetailsLine.tsx msgid "permission" msgstr "Berechtigung" @@ -12145,6 +12194,10 @@ msgstr "Der Speicherort des Repositorys auf der Festplatte. Aktualisieren Sie ih #~ msgid "The selected file does not contain valid MCP server configurations." #~ msgstr "Die ausgewählte Datei enthält keine gültigen MCP-Server-Konfigurationen." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "The selected machine is unavailable. Reconnect it to manage archived threads." +msgstr "Der ausgewählte Computer ist nicht verfügbar. Stellen Sie die Verbindung wieder her, um archivierte Threads zu verwalten." + #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #~ msgid "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." #~ msgstr "Das ausgewählte Modell vergleicht anonymisierte Momentaufnahmen der Änderungen jedes Kandidaten. Es empfiehlt einen Gewinner, ohne etwas zusammenzuführen." @@ -12213,6 +12266,10 @@ msgstr "der Zielanbieter" #~ msgid "The thread will run in a separate worktree on a new branch." #~ msgstr "Der Thread wird in einem separaten Arbeitsbaum auf einem neuen Zweig ausgeführt." +#: src/renderer/i18n/sharedMessages.ts +msgid "The threads linked to this worktree changed. Refresh and try again." +msgstr "Die mit diesem Worktree verknüpften Threads wurden geändert. Aktualisieren Sie die Ansicht und versuchen Sie es erneut." + #: src/renderer/i18n/sharedMessages.ts msgid "The update operation failed." msgstr "Der Update-Vorgang ist fehlgeschlagen." @@ -12320,6 +12377,10 @@ msgstr "Diese HTTPS-Seite konnte den Desktop über einfaches HTTP nicht erreiche #~ msgid "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." #~ msgstr "Dieser Link öffnet die App, die Poracode in deinem Netzwerk bereitstellt. Die gehostete App wird über HTTPS ausgeliefert, was Browser daran hindert, einen Desktop über einfaches HTTP zu erreichen." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "This machine" +msgstr "Dieser Computer" + #: src/renderer/components/mcp/McpServerEditor.tsx msgid "This name is reserved by a built-in MCP server." msgstr "Dieser Name ist für einen integrierten MCP-Server reserviert." @@ -13921,6 +13982,10 @@ msgstr "Worktree-Threads werden als erledigt markiert, sobald Poracode das Zusam msgid "Worktree: {0}" msgstr "Worktree: {0}" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Worktree: {worktreeLabel}" +msgstr "Worktree: {worktreeLabel}" + #: src/renderer/views/ProjectSettingsOverlay/parts/ScriptsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx diff --git a/src/renderer/locales/en/messages.po b/src/renderer/locales/en/messages.po index 23dbc5ea8..63ecb9406 100644 --- a/src/renderer/locales/en/messages.po +++ b/src/renderer/locales/en/messages.po @@ -1439,6 +1439,10 @@ msgstr "Archive thread" msgid "Archive Thread" msgstr "Archive Thread" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Archived at {archivedTime}" +msgstr "Archived at {archivedTime}" + #: src/mobile/settingsSections.ts #: src/mobile/views/SettingsView.tsx #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1486,6 +1490,10 @@ msgstr "Ask for approval" msgid "Ask Question" msgstr "Ask Question" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Associated worktrees with no remaining threads will also be removed." +msgstr "Associated worktrees with no remaining threads will also be removed." + #: src/renderer/views/SchedulesView/ScheduleEditor.tsx msgid "At" msgstr "At" @@ -2460,6 +2468,14 @@ msgstr "Clean Worktree" msgid "Cleanup script" msgstr "Cleanup script" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear all" +msgstr "Clear all" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear archived threads from {dateLabel}" +msgstr "Clear archived threads from {dateLabel}" + #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx msgid "Clear browsing history" msgstr "Clear browsing history" @@ -2476,6 +2492,10 @@ msgstr "Clear Cache" msgid "Clear Cookies" msgstr "Clear Cookies" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear day" +msgstr "Clear day" + #: src/renderer/components/thread/ThreadGoalControls.tsx msgid "Clear goal" msgstr "Clear goal" @@ -3920,6 +3940,7 @@ msgstr "Delegate work to other AI agents." #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts #: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" @@ -3960,6 +3981,14 @@ msgstr "Delete <0>{0}? This cannot be undone." #~ msgid "Delete all done threads" #~ msgstr "Delete all done threads" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived thread?" +msgstr "Delete archived thread?" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived threads?" +msgstr "Delete archived threads?" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Delete branch?" msgstr "Delete branch?" @@ -3974,8 +4003,8 @@ msgstr "Delete file" #. Tooltip: permanently delete the archived thread #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx -msgid "Delete permanently" -msgstr "Delete permanently" +#~ msgid "Delete permanently" +#~ msgstr "Delete permanently" #: src/renderer/views/GitHubActionsView/GitHubActionsView.tsx msgid "Delete run" @@ -6618,6 +6647,10 @@ msgstr "Looking for dev servers…" msgid "Losing worktrees and branches will be removed. Their session history will remain." msgstr "Losing worktrees and branches will be removed. Their session history will remain." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Machine" +msgstr "Machine" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx msgid "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." msgstr "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." @@ -8709,6 +8742,22 @@ msgstr "pending review" msgid "Pending steer" msgstr "Pending steer" +#. placeholder {0}: clearTarget.threads.length +#. placeholder {1}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete {0} archived threads from {1}." +#~ msgstr "Permanently delete {0} archived threads from {1}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Permanently delete the selected archive entries from {0}." +msgstr "Permanently delete the selected archive entries from {0}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete the selected archived threads from {0}." +#~ msgstr "Permanently delete the selected archived threads from {0}." + #: src/renderer/components/thread/ThreadRuntimeRequestPanel/parts/PermissionDetailsLine.tsx msgid "permission" msgstr "permission" @@ -12145,6 +12194,10 @@ msgstr "The repository location on disk. Update it if you moved the folder." #~ msgid "The selected file does not contain valid MCP server configurations." #~ msgstr "The selected file does not contain valid MCP server configurations." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "The selected machine is unavailable. Reconnect it to manage archived threads." +msgstr "The selected machine is unavailable. Reconnect it to manage archived threads." + #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #~ msgid "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." #~ msgstr "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." @@ -12213,6 +12266,10 @@ msgstr "the target provider" #~ msgid "The thread will run in a separate worktree on a new branch." #~ msgstr "The thread will run in a separate worktree on a new branch." +#: src/renderer/i18n/sharedMessages.ts +msgid "The threads linked to this worktree changed. Refresh and try again." +msgstr "The threads linked to this worktree changed. Refresh and try again." + #: src/renderer/i18n/sharedMessages.ts msgid "The update operation failed." msgstr "The update operation failed." @@ -12320,6 +12377,10 @@ msgstr "This HTTPS page couldn't reach the desktop on plain HTTP. Allow local ne #~ msgid "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." #~ msgstr "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "This machine" +msgstr "This machine" + #: src/renderer/components/mcp/McpServerEditor.tsx msgid "This name is reserved by a built-in MCP server." msgstr "This name is reserved by a built-in MCP server." @@ -13921,6 +13982,10 @@ msgstr "Worktree threads are marked done as soon as Poracode sees their pull req msgid "Worktree: {0}" msgstr "Worktree: {0}" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Worktree: {worktreeLabel}" +msgstr "Worktree: {worktreeLabel}" + #: src/renderer/views/ProjectSettingsOverlay/parts/ScriptsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx diff --git a/src/renderer/locales/es/messages.po b/src/renderer/locales/es/messages.po index 838d7e01b..574c8bad4 100644 --- a/src/renderer/locales/es/messages.po +++ b/src/renderer/locales/es/messages.po @@ -1439,6 +1439,10 @@ msgstr "Archivar hilo" msgid "Archive Thread" msgstr "Archivar hilo" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Archived at {archivedTime}" +msgstr "Archivado a las {archivedTime}" + #: src/mobile/settingsSections.ts #: src/mobile/views/SettingsView.tsx #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1486,6 +1490,10 @@ msgstr "Pedir aprobación" msgid "Ask Question" msgstr "Hacer una pregunta" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Associated worktrees with no remaining threads will also be removed." +msgstr "Los worktrees asociados sin hilos restantes también se eliminarán." + #: src/renderer/views/SchedulesView/ScheduleEditor.tsx msgid "At" msgstr "A las" @@ -2460,6 +2468,14 @@ msgstr "Worktree limpio" msgid "Cleanup script" msgstr "Script de limpieza" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear all" +msgstr "Borrar todo" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear archived threads from {dateLabel}" +msgstr "Borrar hilos archivados del {dateLabel}" + #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx msgid "Clear browsing history" msgstr "Borrar historial de navegación" @@ -2476,6 +2492,10 @@ msgstr "Borrar caché" msgid "Clear Cookies" msgstr "Borrar cookies" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear day" +msgstr "Borrar día" + #: src/renderer/components/thread/ThreadGoalControls.tsx msgid "Clear goal" msgstr "Borrar objetivo" @@ -3920,6 +3940,7 @@ msgstr "Delega trabajo a otros agentes de IA." #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts #: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" @@ -3960,6 +3981,14 @@ msgstr "¿Eliminar <0>{0}? Esta acción no se puede deshacer." #~ msgid "Delete all done threads" #~ msgstr "Eliminar todos los hilos completados" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived thread?" +msgstr "¿Eliminar el hilo archivado?" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived threads?" +msgstr "¿Borrar los hilos archivados?" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Delete branch?" msgstr "¿Eliminar la rama?" @@ -3974,8 +4003,8 @@ msgstr "Eliminar archivo" #. Tooltip: permanently delete the archived thread #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx -msgid "Delete permanently" -msgstr "Eliminar permanentemente" +#~ msgid "Delete permanently" +#~ msgstr "Eliminar permanentemente" #: src/renderer/views/GitHubActionsView/GitHubActionsView.tsx msgid "Delete run" @@ -6618,6 +6647,10 @@ msgstr "Buscando servidores de desarrollo…" msgid "Losing worktrees and branches will be removed. Their session history will remain." msgstr "Se eliminarán los worktrees y las ramas de los candidatos no seleccionados. Se conservará el historial de sus sesiones." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Machine" +msgstr "Máquina" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx msgid "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." msgstr "Hace translúcido el panel lateral: el efecto de desenfoque del sistema en Windows 11 y un tinte translúcido en otros casos." @@ -8709,6 +8742,22 @@ msgstr "pendiente de revisión" msgid "Pending steer" msgstr "Dirección pendiente" +#. placeholder {0}: clearTarget.threads.length +#. placeholder {1}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete {0} archived threads from {1}." +#~ msgstr "Eliminar permanentemente {0} hilos archivados de {1}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Permanently delete the selected archive entries from {0}." +msgstr "Eliminar permanentemente las entradas archivadas seleccionadas de {0}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete the selected archived threads from {0}." +#~ msgstr "Eliminar permanentemente los hilos archivados seleccionados de {0}." + #: src/renderer/components/thread/ThreadRuntimeRequestPanel/parts/PermissionDetailsLine.tsx msgid "permission" msgstr "permiso" @@ -12145,6 +12194,10 @@ msgstr "La ubicación del repositorio en el disco. Actualízala si moviste la ca #~ msgid "The selected file does not contain valid MCP server configurations." #~ msgstr "El archivo seleccionado no contiene configuraciones de servidores MCP válidas." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "The selected machine is unavailable. Reconnect it to manage archived threads." +msgstr "La máquina seleccionada no está disponible. Vuelve a conectarla para gestionar los hilos archivados." + #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #~ msgid "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." #~ msgstr "El modelo seleccionado compara instantáneas anonimizadas de los cambios de cada candidato. Recomienda un ganador sin fusionar nada." @@ -12213,6 +12266,10 @@ msgstr "el proveedor de destino" #~ msgid "The thread will run in a separate worktree on a new branch." #~ msgstr "El hilo se ejecutará en un worktree separado en una nueva rama." +#: src/renderer/i18n/sharedMessages.ts +msgid "The threads linked to this worktree changed. Refresh and try again." +msgstr "Los hilos vinculados a este worktree han cambiado. Actualiza la vista y vuelve a intentarlo." + #: src/renderer/i18n/sharedMessages.ts msgid "The update operation failed." msgstr "La operación de actualización falló." @@ -12320,6 +12377,10 @@ msgstr "Esta página HTTPS no pudo conectar con el escritorio en HTTP simple. Pe #~ msgid "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." #~ msgstr "Este enlace abre la app que Poracode sirve en tu red. La app alojada se sirve por HTTPS, lo que impide que los navegadores lleguen a un escritorio en HTTP simple." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "This machine" +msgstr "Esta máquina" + #: src/renderer/components/mcp/McpServerEditor.tsx msgid "This name is reserved by a built-in MCP server." msgstr "Este nombre está reservado para un servidor MCP integrado." @@ -13921,6 +13982,10 @@ msgstr "Los hilos de worktree se marcan como hechos en cuanto Poracode detecta q msgid "Worktree: {0}" msgstr "Worktree: {0}" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Worktree: {worktreeLabel}" +msgstr "Worktree: {worktreeLabel}" + #: src/renderer/views/ProjectSettingsOverlay/parts/ScriptsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx diff --git a/src/renderer/locales/fr/messages.po b/src/renderer/locales/fr/messages.po index e3bba24c3..be6c6128e 100644 --- a/src/renderer/locales/fr/messages.po +++ b/src/renderer/locales/fr/messages.po @@ -1439,6 +1439,10 @@ msgstr "Archiver le fil de discussion" msgid "Archive Thread" msgstr "Archiver le fil de discussion" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Archived at {archivedTime}" +msgstr "Archivé à {archivedTime}" + #: src/mobile/settingsSections.ts #: src/mobile/views/SettingsView.tsx #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1486,6 +1490,10 @@ msgstr "Demander une approbation" msgid "Ask Question" msgstr "Poser une question" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Associated worktrees with no remaining threads will also be removed." +msgstr "Les arbres de travail associés ne contenant plus de fils seront également supprimés." + #: src/renderer/views/SchedulesView/ScheduleEditor.tsx msgid "At" msgstr "À" @@ -2460,6 +2468,14 @@ msgstr "Worktree propre" msgid "Cleanup script" msgstr "Script de nettoyage" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear all" +msgstr "Tout effacer" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear archived threads from {dateLabel}" +msgstr "Effacer les fils archivés du {dateLabel}" + #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx msgid "Clear browsing history" msgstr "Effacer l’historique de navigation" @@ -2476,6 +2492,10 @@ msgstr "Vider le cache" msgid "Clear Cookies" msgstr "Effacer les cookies" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear day" +msgstr "Effacer le jour" + #: src/renderer/components/thread/ThreadGoalControls.tsx msgid "Clear goal" msgstr "Effacer l'objectif" @@ -3920,6 +3940,7 @@ msgstr "Déléguez le travail à d’autres agents IA." #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts #: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" @@ -3960,6 +3981,14 @@ msgstr "Supprimer <0>{0} ? Cette action est irréversible." #~ msgid "Delete all done threads" #~ msgstr "Supprimer tous les fils terminés" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived thread?" +msgstr "Supprimer le fil archivé ?" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived threads?" +msgstr "Supprimer les fils archivés ?" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Delete branch?" msgstr "Supprimer la branche ?" @@ -3974,8 +4003,8 @@ msgstr "Supprimer le fichier" #. Tooltip: permanently delete the archived thread #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx -msgid "Delete permanently" -msgstr "Supprimer définitivement" +#~ msgid "Delete permanently" +#~ msgstr "Supprimer définitivement" #: src/renderer/views/GitHubActionsView/GitHubActionsView.tsx msgid "Delete run" @@ -6618,6 +6647,10 @@ msgstr "Recherche de serveurs de développement…" msgid "Losing worktrees and branches will be removed. Their session history will remain." msgstr "Les arbres de travail et les branches des candidats non retenus seront supprimés. L’historique de leurs sessions sera conservé." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Machine" +msgstr "Machine" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx msgid "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." msgstr "Rend la barre latérale translucide — le flou système sur Windows 11, une teinte translucide ailleurs." @@ -8708,6 +8741,22 @@ msgstr "en attente d'examen" msgid "Pending steer" msgstr "En attente de direction" +#. placeholder {0}: clearTarget.threads.length +#. placeholder {1}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete {0} archived threads from {1}." +#~ msgstr "Supprimer définitivement {0} fils archivés de {1}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Permanently delete the selected archive entries from {0}." +msgstr "Supprimer définitivement les entrées archivées sélectionnées de {0}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete the selected archived threads from {0}." +#~ msgstr "Supprimer définitivement les fils archivés sélectionnés de {0}." + #: src/renderer/components/thread/ThreadRuntimeRequestPanel/parts/PermissionDetailsLine.tsx msgid "permission" msgstr "autorisation" @@ -12144,6 +12193,10 @@ msgstr "L'emplacement du dépôt sur le disque. Mettez-le à jour si vous avez d #~ msgid "The selected file does not contain valid MCP server configurations." #~ msgstr "Le fichier sélectionné ne contient aucune configuration de serveur MCP valide." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "The selected machine is unavailable. Reconnect it to manage archived threads." +msgstr "La machine sélectionnée est indisponible. Reconnectez-la pour gérer les fils archivés." + #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #~ msgid "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." #~ msgstr "Le modèle sélectionné compare des instantanés anonymisés des modifications de chaque candidat. Il recommande un gagnant sans rien fusionner." @@ -12212,6 +12265,10 @@ msgstr "le fournisseur cible" #~ msgid "The thread will run in a separate worktree on a new branch." #~ msgstr "Le fil de discussion s'exécutera dans un worktree séparé sur une nouvelle branche." +#: src/renderer/i18n/sharedMessages.ts +msgid "The threads linked to this worktree changed. Refresh and try again." +msgstr "Les fils liés à ce worktree ont changé. Actualisez la page et réessayez." + #: src/renderer/i18n/sharedMessages.ts msgid "The update operation failed." msgstr "L’opération de mise à jour a échoué." @@ -12319,6 +12376,10 @@ msgstr "Cette page HTTPS n'a pas pu joindre l'ordinateur en HTTP simple. Autoris #~ msgid "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." #~ msgstr "Ce lien ouvre l'application que Poracode sert sur votre réseau. L'application hébergée est servie en HTTPS, ce qui empêche les navigateurs de joindre un ordinateur en HTTP simple." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "This machine" +msgstr "Cette machine" + #: src/renderer/components/mcp/McpServerEditor.tsx msgid "This name is reserved by a built-in MCP server." msgstr "Ce nom est réservé à un serveur MCP intégré." @@ -13920,6 +13981,10 @@ msgstr "Les fils d'arbre de travail sont marqués comme terminés dès que Porac msgid "Worktree: {0}" msgstr "Arbre de travail : {0}" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Worktree: {worktreeLabel}" +msgstr "Arbre de travail : {worktreeLabel}" + #: src/renderer/views/ProjectSettingsOverlay/parts/ScriptsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx diff --git a/src/renderer/locales/ja/messages.po b/src/renderer/locales/ja/messages.po index 65787ec87..2ef50e1e2 100644 --- a/src/renderer/locales/ja/messages.po +++ b/src/renderer/locales/ja/messages.po @@ -1438,6 +1438,10 @@ msgstr "スレッドをアーカイブ" msgid "Archive Thread" msgstr "アーカイブスレッド" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Archived at {archivedTime}" +msgstr "{archivedTime} にアーカイブ" + #: src/mobile/settingsSections.ts #: src/mobile/views/SettingsView.tsx #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1485,6 +1489,10 @@ msgstr "承認を求める" msgid "Ask Question" msgstr "質問する" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Associated worktrees with no remaining threads will also be removed." +msgstr "残っているスレッドがない関連ワークツリーも削除されます。" + #: src/renderer/views/SchedulesView/ScheduleEditor.tsx msgid "At" msgstr "時刻" @@ -2459,6 +2467,14 @@ msgstr "クリーンなワークツリー" msgid "Cleanup script" msgstr "クリーンアップスクリプト" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear all" +msgstr "すべて消去" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear archived threads from {dateLabel}" +msgstr "{dateLabel} のアーカイブ済みスレッドを消去" + #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx msgid "Clear browsing history" msgstr "閲覧履歴を消去" @@ -2475,6 +2491,10 @@ msgstr "キャッシュのクリア" msgid "Clear Cookies" msgstr "Cookie を消去" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear day" +msgstr "この日の分を消去" + #: src/renderer/components/thread/ThreadGoalControls.tsx msgid "Clear goal" msgstr "目標をクリア" @@ -3919,6 +3939,7 @@ msgstr "他の AI エージェントに作業を委任します。" #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts #: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" @@ -3959,6 +3980,14 @@ msgstr "<0>{0} を削除しますか?この操作は元に戻せません #~ msgid "Delete all done threads" #~ msgstr "完了したスレッドをすべて削除" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived thread?" +msgstr "アーカイブ済みスレッドを削除しますか?" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived threads?" +msgstr "アーカイブ済みスレッドを削除しますか?" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Delete branch?" msgstr "ブランチを削除しますか?" @@ -3973,8 +4002,8 @@ msgstr "ファイルの削除" #. Tooltip: permanently delete the archived thread #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx -msgid "Delete permanently" -msgstr "完全に削除" +#~ msgid "Delete permanently" +#~ msgstr "完全に削除" #: src/renderer/views/GitHubActionsView/GitHubActionsView.tsx msgid "Delete run" @@ -6617,6 +6646,10 @@ msgstr "開発サーバーを検索しています…" msgid "Losing worktrees and branches will be removed. Their session history will remain." msgstr "敗者のワークツリーとブランチは削除されます。セッション履歴は保持されます。" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Machine" +msgstr "マシン" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx msgid "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." msgstr "サイドバーを半透明にします。Windows 11ではシステムのぼかし素材を、それ以外では半透明の色付けを使用します。" @@ -8707,6 +8740,22 @@ msgstr "審査待ち" msgid "Pending steer" msgstr "保留中のステアリング" +#. placeholder {0}: clearTarget.threads.length +#. placeholder {1}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete {0} archived threads from {1}." +#~ msgstr "{1} から {0} 件のアーカイブ済みスレッドを完全に削除します。" + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Permanently delete the selected archive entries from {0}." +msgstr "{0} から選択したアーカイブ項目を完全に削除します。" + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete the selected archived threads from {0}." +#~ msgstr "{0} から選択したアーカイブ済みスレッドを完全に削除します。" + #: src/renderer/components/thread/ThreadRuntimeRequestPanel/parts/PermissionDetailsLine.tsx msgid "permission" msgstr "許可" @@ -12143,6 +12192,10 @@ msgstr "ディスク上のリポジトリの場所。フォルダーを移動し #~ msgid "The selected file does not contain valid MCP server configurations." #~ msgstr "選択したファイルに有効なMCPサーバー設定が含まれていません。" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "The selected machine is unavailable. Reconnect it to manage archived threads." +msgstr "選択したマシンは利用できません。アーカイブ済みスレッドを管理するには再接続してください。" + #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #~ msgid "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." #~ msgstr "選択したモデルは、各候補の変更内容を匿名化したスナップショットで比較します。何もマージせずに勝者を推奨します。" @@ -12211,6 +12264,10 @@ msgstr "対象プロバイダー" #~ msgid "The thread will run in a separate worktree on a new branch." #~ msgstr "スレッドは新しいブランチ上の別のワークツリーで実行されます。" +#: src/renderer/i18n/sharedMessages.ts +msgid "The threads linked to this worktree changed. Refresh and try again." +msgstr "このワークツリーに関連付けられたスレッドが変更されました。更新してもう一度お試しください。" + #: src/renderer/i18n/sharedMessages.ts msgid "The update operation failed." msgstr "アップデート操作に失敗しました。" @@ -12318,6 +12375,10 @@ msgstr "この HTTPS ページは平文 HTTP のデスクトップに接続で #~ msgid "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." #~ msgstr "このリンクは Poracode がネットワーク上で提供しているアプリを開きます。ホスト版のアプリは HTTPS で配信されるため、ブラウザは平文 HTTP のデスクトップに接続できません。" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "This machine" +msgstr "このマシン" + #: src/renderer/components/mcp/McpServerEditor.tsx msgid "This name is reserved by a built-in MCP server." msgstr "この名前は組み込みMCPサーバーによって予約されています。" @@ -13919,6 +13980,10 @@ msgstr "Poracode がプルリクエストのマージを検知すると、ワー msgid "Worktree: {0}" msgstr "ワークツリー:{0}" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Worktree: {worktreeLabel}" +msgstr "ワークツリー: {worktreeLabel}" + #: src/renderer/views/ProjectSettingsOverlay/parts/ScriptsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx diff --git a/src/renderer/locales/ko/messages.po b/src/renderer/locales/ko/messages.po index f083fb364..bc6e1cb2a 100644 --- a/src/renderer/locales/ko/messages.po +++ b/src/renderer/locales/ko/messages.po @@ -1439,6 +1439,10 @@ msgstr "스레드 보관" msgid "Archive Thread" msgstr "아카이브 스레드" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Archived at {archivedTime}" +msgstr "{archivedTime}에 보관됨" + #: src/mobile/settingsSections.ts #: src/mobile/views/SettingsView.tsx #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1486,6 +1490,10 @@ msgstr "승인 요청" msgid "Ask Question" msgstr "질문하기" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Associated worktrees with no remaining threads will also be removed." +msgstr "남은 스레드가 없는 연결된 작업 트리도 삭제됩니다." + #: src/renderer/views/SchedulesView/ScheduleEditor.tsx msgid "At" msgstr "시간" @@ -2460,6 +2468,14 @@ msgstr "클린 작업 트리" msgid "Cleanup script" msgstr "정리 스크립트" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear all" +msgstr "모두 지우기" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear archived threads from {dateLabel}" +msgstr "{dateLabel}의 보관된 스레드 지우기" + #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx msgid "Clear browsing history" msgstr "방문 기록 지우기" @@ -2476,6 +2492,10 @@ msgstr "캐시 지우기" msgid "Clear Cookies" msgstr "쿠키 지우기" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear day" +msgstr "이 날짜 지우기" + #: src/renderer/components/thread/ThreadGoalControls.tsx msgid "Clear goal" msgstr "목표 지우기" @@ -3920,6 +3940,7 @@ msgstr "다른 AI 에이전트에게 작업을 위임합니다." #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts #: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" @@ -3960,6 +3981,14 @@ msgstr "<0>{0}을(를) 삭제할까요? 이 작업은 되돌릴 수 없습 #~ msgid "Delete all done threads" #~ msgstr "완료된 모든 스레드 삭제" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived thread?" +msgstr "보관된 스레드를 삭제할까요?" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived threads?" +msgstr "보관된 스레드를 삭제할까요?" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Delete branch?" msgstr "브랜치를 삭제하시겠습니까?" @@ -3974,8 +4003,8 @@ msgstr "파일 삭제" #. Tooltip: permanently delete the archived thread #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx -msgid "Delete permanently" -msgstr "영구 삭제" +#~ msgid "Delete permanently" +#~ msgstr "영구 삭제" #: src/renderer/views/GitHubActionsView/GitHubActionsView.tsx msgid "Delete run" @@ -6618,6 +6647,10 @@ msgstr "개발 서버를 찾는 중…" msgid "Losing worktrees and branches will be removed. Their session history will remain." msgstr "선정되지 않은 작업 트리와 브랜치는 제거됩니다. 세션 기록은 유지됩니다." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Machine" +msgstr "기기" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx msgid "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." msgstr "사이드바를 반투명하게 만듭니다 — Windows 11에서는 시스템 블러 효과, 그 외 환경에서는 반투명 색조를 사용합니다." @@ -8709,6 +8742,22 @@ msgstr "검토 대기 중" msgid "Pending steer" msgstr "조정 대기 중" +#. placeholder {0}: clearTarget.threads.length +#. placeholder {1}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete {0} archived threads from {1}." +#~ msgstr "{1}에서 보관된 스레드 {0}개를 영구 삭제합니다." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Permanently delete the selected archive entries from {0}." +msgstr "{0}에서 선택한 보관 항목을 영구적으로 삭제합니다." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete the selected archived threads from {0}." +#~ msgstr "{0}에서 선택한 보관 스레드를 영구적으로 삭제합니다." + #: src/renderer/components/thread/ThreadRuntimeRequestPanel/parts/PermissionDetailsLine.tsx msgid "permission" msgstr "권한" @@ -12145,6 +12194,10 @@ msgstr "디스크에 있는 저장소 위치입니다. 폴더를 옮겼다면 #~ msgid "The selected file does not contain valid MCP server configurations." #~ msgstr "선택한 파일에 유효한 MCP 서버 구성이 없습니다." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "The selected machine is unavailable. Reconnect it to manage archived threads." +msgstr "선택한 기기를 사용할 수 없습니다. 보관된 스레드를 관리하려면 다시 연결하세요." + #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #~ msgid "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." #~ msgstr "선택한 모델은 각 후보 변경 사항의 익명화된 스냅샷을 비교합니다. 아무것도 병합하지 않고 우승자를 추천합니다." @@ -12213,6 +12266,10 @@ msgstr "대상 공급자" #~ msgid "The thread will run in a separate worktree on a new branch." #~ msgstr "스레드가 새 브랜치의 별도 작업 트리에서 실행됩니다." +#: src/renderer/i18n/sharedMessages.ts +msgid "The threads linked to this worktree changed. Refresh and try again." +msgstr "이 worktree에 연결된 스레드가 변경되었습니다. 새로 고친 후 다시 시도하세요." + #: src/renderer/i18n/sharedMessages.ts msgid "The update operation failed." msgstr "업데이트 작업에 실패했습니다." @@ -12320,6 +12377,10 @@ msgstr "이 HTTPS 페이지는 일반 HTTP 데스크톱에 연결할 수 없었 #~ msgid "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." #~ msgstr "이 링크는 Poracode가 네트워크에서 제공하는 앱을 엽니다. 호스팅된 앱은 HTTPS로 제공되므로 브라우저가 일반 HTTP 데스크톱에 연결하는 것을 차단합니다." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "This machine" +msgstr "이 기기" + #: src/renderer/components/mcp/McpServerEditor.tsx msgid "This name is reserved by a built-in MCP server." msgstr "이 이름은 기본 제공 MCP 서버용으로 예약되어 있습니다." @@ -13921,6 +13982,10 @@ msgstr "Poracode가 풀 리퀘스트 병합을 감지하면 작업 트리 스레 msgid "Worktree: {0}" msgstr "작업 트리:{0}" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Worktree: {worktreeLabel}" +msgstr "작업 트리: {worktreeLabel}" + #: src/renderer/views/ProjectSettingsOverlay/parts/ScriptsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx diff --git a/src/renderer/locales/pl/messages.po b/src/renderer/locales/pl/messages.po index b4fb9af3d..13131e8c6 100644 --- a/src/renderer/locales/pl/messages.po +++ b/src/renderer/locales/pl/messages.po @@ -1439,6 +1439,10 @@ msgstr "Archiwizuj wątek" msgid "Archive Thread" msgstr "Archiwum wątku" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Archived at {archivedTime}" +msgstr "Zarchiwizowano o {archivedTime}" + #: src/mobile/settingsSections.ts #: src/mobile/views/SettingsView.tsx #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1486,6 +1490,10 @@ msgstr "Poproś o zatwierdzenie" msgid "Ask Question" msgstr "Zadaj pytanie" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Associated worktrees with no remaining threads will also be removed." +msgstr "Powiązane drzewa robocze, w których nie pozostały żadne wątki, również zostaną usunięte." + #: src/renderer/views/SchedulesView/ScheduleEditor.tsx msgid "At" msgstr "O" @@ -2460,6 +2468,14 @@ msgstr "Czyste drzewo robocze" msgid "Cleanup script" msgstr "Skrypt czyszczący" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear all" +msgstr "Wyczyść wszystko" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear archived threads from {dateLabel}" +msgstr "Wyczyść zarchiwizowane wątki z {dateLabel}" + #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx msgid "Clear browsing history" msgstr "Wyczyść historię przeglądania" @@ -2476,6 +2492,10 @@ msgstr "Wyczyść pamięć podręczną" msgid "Clear Cookies" msgstr "Wyczyść pliki cookie" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear day" +msgstr "Wyczyść dzień" + #: src/renderer/components/thread/ThreadGoalControls.tsx msgid "Clear goal" msgstr "Wyczyść cel" @@ -3920,6 +3940,7 @@ msgstr "Deleguj zadania innym agentom AI." #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts #: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" @@ -3960,6 +3981,14 @@ msgstr "Usunąć <0>{0}? Tej operacji nie można cofnąć." #~ msgid "Delete all done threads" #~ msgstr "Usuń wszystkie ukończone wątki" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived thread?" +msgstr "Usunąć zarchiwizowany wątek?" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived threads?" +msgstr "Usunąć zarchiwizowane wątki?" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Delete branch?" msgstr "Usunąć gałąź?" @@ -3974,8 +4003,8 @@ msgstr "Usuń plik" #. Tooltip: permanently delete the archived thread #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx -msgid "Delete permanently" -msgstr "Usuń trwale" +#~ msgid "Delete permanently" +#~ msgstr "Usuń trwale" #: src/renderer/views/GitHubActionsView/GitHubActionsView.tsx msgid "Delete run" @@ -6618,6 +6647,10 @@ msgstr "Szukanie serwerów deweloperskich…" msgid "Losing worktrees and branches will be removed. Their session history will remain." msgstr "Drzewa robocze i gałęzie odrzuconych kandydatów zostaną usunięte. Historia ich sesji zostanie zachowana." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Machine" +msgstr "Komputer" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx msgid "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." msgstr "Uczyń pasek boczny półprzezroczystym — systemowy efekt rozmycia w Windows 11, półprzezroczysty odcień w pozostałych przypadkach." @@ -8709,6 +8742,22 @@ msgstr "w oczekiwaniu na recenzję" msgid "Pending steer" msgstr "Oczekujące sterowanie" +#. placeholder {0}: clearTarget.threads.length +#. placeholder {1}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete {0} archived threads from {1}." +#~ msgstr "Trwale usuń {0} zarchiwizowanych wątków z {1}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Permanently delete the selected archive entries from {0}." +msgstr "Trwale usuń wybrane wpisy archiwum z {0}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete the selected archived threads from {0}." +#~ msgstr "Trwale usuń wybrane zarchiwizowane wątki z {0}." + #: src/renderer/components/thread/ThreadRuntimeRequestPanel/parts/PermissionDetailsLine.tsx msgid "permission" msgstr "pozwolenie" @@ -12145,6 +12194,10 @@ msgstr "Lokalizacja repozytorium na dysku. Zaktualizuj ją, jeśli przeniesiono #~ msgid "The selected file does not contain valid MCP server configurations." #~ msgstr "Wybrany plik nie zawiera prawidłowych konfiguracji serwerów MCP." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "The selected machine is unavailable. Reconnect it to manage archived threads." +msgstr "Wybrany komputer jest niedostępny. Połącz go ponownie, aby zarządzać zarchiwizowanymi wątkami." + #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #~ msgid "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." #~ msgstr "Wybrany model porównuje zanonimizowane migawki zmian każdego kandydata. Rekomenduje zwycięzcę bez scalania czegokolwiek." @@ -12213,6 +12266,10 @@ msgstr "docelowego dostawcy" #~ msgid "The thread will run in a separate worktree on a new branch." #~ msgstr "Wątek będzie działać w osobnym drzewie roboczym na nowej gałęzi." +#: src/renderer/i18n/sharedMessages.ts +msgid "The threads linked to this worktree changed. Refresh and try again." +msgstr "Wątki powiązane z tym worktree uległy zmianie. Odśwież i spróbuj ponownie." + #: src/renderer/i18n/sharedMessages.ts msgid "The update operation failed." msgstr "Operacja aktualizacji nie powiodła się." @@ -12320,6 +12377,10 @@ msgstr "Ta strona HTTPS nie mogła połączyć się z desktopem po zwykłym HTTP #~ msgid "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." #~ msgstr "Ten link otwiera aplikację, którą Poracode udostępnia w twojej sieci. Hostowana aplikacja jest serwowana przez HTTPS, co blokuje przeglądarkom dostęp do desktopa po zwykłym HTTP." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "This machine" +msgstr "Ten komputer" + #: src/renderer/components/mcp/McpServerEditor.tsx msgid "This name is reserved by a built-in MCP server." msgstr "Ta nazwa jest zastrzeżona przez wbudowany serwer MCP." @@ -13921,6 +13982,10 @@ msgstr "Wątki drzewa roboczego są oznaczane jako gotowe, gdy Poracode zauważy msgid "Worktree: {0}" msgstr "Drzewo robocze: {0}" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Worktree: {worktreeLabel}" +msgstr "Drzewo robocze: {worktreeLabel}" + #: src/renderer/views/ProjectSettingsOverlay/parts/ScriptsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx diff --git a/src/renderer/locales/pt-BR/messages.po b/src/renderer/locales/pt-BR/messages.po index 9c8dab65c..015fdbd64 100644 --- a/src/renderer/locales/pt-BR/messages.po +++ b/src/renderer/locales/pt-BR/messages.po @@ -1439,6 +1439,10 @@ msgstr "Arquivar tópico" msgid "Archive Thread" msgstr "Arquivar Tópico" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Archived at {archivedTime}" +msgstr "Arquivada às {archivedTime}" + #: src/mobile/settingsSections.ts #: src/mobile/views/SettingsView.tsx #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1486,6 +1490,10 @@ msgstr "Pedir aprovação" msgid "Ask Question" msgstr "Faça uma pergunta" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Associated worktrees with no remaining threads will also be removed." +msgstr "Árvores de trabalho associadas sem threads restantes também serão removidas." + #: src/renderer/views/SchedulesView/ScheduleEditor.tsx msgid "At" msgstr "Às" @@ -2460,6 +2468,14 @@ msgstr "Árvore de trabalho limpa" msgid "Cleanup script" msgstr "Script de limpeza" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear all" +msgstr "Limpar tudo" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear archived threads from {dateLabel}" +msgstr "Limpar threads arquivadas de {dateLabel}" + #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx msgid "Clear browsing history" msgstr "Limpar histórico de navegação" @@ -2476,6 +2492,10 @@ msgstr "Limpar Cache" msgid "Clear Cookies" msgstr "Limpar cookies" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear day" +msgstr "Limpar dia" + #: src/renderer/components/thread/ThreadGoalControls.tsx msgid "Clear goal" msgstr "Limpar objetivo" @@ -3920,6 +3940,7 @@ msgstr "Delegue o trabalho a outros agentes de IA." #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts #: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" @@ -3960,6 +3981,14 @@ msgstr "Excluir <0>{0}? Isso não pode ser desfeito." #~ msgid "Delete all done threads" #~ msgstr "Excluir todas as threads concluídas" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived thread?" +msgstr "Excluir thread arquivada?" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived threads?" +msgstr "Excluir threads arquivadas?" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Delete branch?" msgstr "Excluir branch?" @@ -3974,8 +4003,8 @@ msgstr "Excluir arquivo" #. Tooltip: permanently delete the archived thread #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx -msgid "Delete permanently" -msgstr "Excluir permanentemente" +#~ msgid "Delete permanently" +#~ msgstr "Excluir permanentemente" #: src/renderer/views/GitHubActionsView/GitHubActionsView.tsx msgid "Delete run" @@ -6618,6 +6647,10 @@ msgstr "Procurando servidores de desenvolvimento…" msgid "Losing worktrees and branches will be removed. Their session history will remain." msgstr "As árvores de trabalho e as ramificações dos candidatos não selecionados serão removidas. O histórico das sessões será mantido." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Machine" +msgstr "Máquina" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx msgid "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." msgstr "Torna a barra lateral translúcida — o material de desfoque do sistema no Windows 11, um tom translúcido nas demais plataformas." @@ -8709,6 +8742,22 @@ msgstr "revisão pendente" msgid "Pending steer" msgstr "Orientação pendente" +#. placeholder {0}: clearTarget.threads.length +#. placeholder {1}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete {0} archived threads from {1}." +#~ msgstr "Excluir permanentemente {0} threads arquivadas de {1}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Permanently delete the selected archive entries from {0}." +msgstr "Excluir permanentemente as entradas arquivadas selecionadas de {0}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete the selected archived threads from {0}." +#~ msgstr "Excluir permanentemente as threads arquivadas selecionadas de {0}." + #: src/renderer/components/thread/ThreadRuntimeRequestPanel/parts/PermissionDetailsLine.tsx msgid "permission" msgstr "permissão" @@ -12145,6 +12194,10 @@ msgstr "A localização do repositório no disco. Atualize-a se você moveu a pa #~ msgid "The selected file does not contain valid MCP server configurations." #~ msgstr "O arquivo selecionado não contém configurações válidas de servidores MCP." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "The selected machine is unavailable. Reconnect it to manage archived threads." +msgstr "A máquina selecionada está indisponível. Reconecte-a para gerenciar as threads arquivadas." + #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #~ msgid "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." #~ msgstr "O modelo selecionado compara snapshots anonimizados das alterações de cada candidato. Ele recomenda um vencedor sem fazer nenhuma mesclagem." @@ -12213,6 +12266,10 @@ msgstr "o provedor alvo" #~ msgid "The thread will run in a separate worktree on a new branch." #~ msgstr "O tópico será executado em uma árvore de trabalho separada em um novo branch." +#: src/renderer/i18n/sharedMessages.ts +msgid "The threads linked to this worktree changed. Refresh and try again." +msgstr "As threads vinculadas a este worktree mudaram. Atualize e tente novamente." + #: src/renderer/i18n/sharedMessages.ts msgid "The update operation failed." msgstr "A operação de atualização falhou." @@ -12320,6 +12377,10 @@ msgstr "Esta página HTTPS não conseguiu alcançar o desktop em HTTP simples. P #~ msgid "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." #~ msgstr "Este link abre o app que o Poracode serve na sua rede. O app hospedado é servido por HTTPS, o que impede os navegadores de alcançar um desktop em HTTP simples." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "This machine" +msgstr "Esta máquina" + #: src/renderer/components/mcp/McpServerEditor.tsx msgid "This name is reserved by a built-in MCP server." msgstr "Este nome é reservado por um servidor MCP integrado." @@ -13921,6 +13982,10 @@ msgstr "Os tópicos de árvore de trabalho são marcados como concluídos assim msgid "Worktree: {0}" msgstr "Árvore de trabalho: {0}" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Worktree: {worktreeLabel}" +msgstr "Árvore de trabalho: {worktreeLabel}" + #: src/renderer/views/ProjectSettingsOverlay/parts/ScriptsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx diff --git a/src/renderer/locales/ru/messages.po b/src/renderer/locales/ru/messages.po index 37fdd6088..9cd14833a 100644 --- a/src/renderer/locales/ru/messages.po +++ b/src/renderer/locales/ru/messages.po @@ -1439,6 +1439,10 @@ msgstr "Архивировать тред" msgid "Archive Thread" msgstr "Архивировать тред" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Archived at {archivedTime}" +msgstr "Архивировано в {archivedTime}" + #: src/mobile/settingsSections.ts #: src/mobile/views/SettingsView.tsx #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1486,6 +1490,10 @@ msgstr "Запрашивать подтверждение" msgid "Ask Question" msgstr "Задать вопрос" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Associated worktrees with no remaining threads will also be removed." +msgstr "Связанные worktree, в которых не осталось потоков, также будут удалены." + #: src/renderer/views/SchedulesView/ScheduleEditor.tsx msgid "At" msgstr "В" @@ -2460,6 +2468,14 @@ msgstr "Чистый worktree" msgid "Cleanup script" msgstr "Скрипт очистки" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear all" +msgstr "Очистить всё" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear archived threads from {dateLabel}" +msgstr "Удалить архивные потоки за {dateLabel}" + #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx msgid "Clear browsing history" msgstr "Очистить историю просмотров" @@ -2476,6 +2492,10 @@ msgstr "Очистить кэш" msgid "Clear Cookies" msgstr "Очистить cookies" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear day" +msgstr "Очистить день" + #: src/renderer/components/thread/ThreadGoalControls.tsx msgid "Clear goal" msgstr "Очистить цель" @@ -3920,6 +3940,7 @@ msgstr "Делегируйте работу другим ИИ-агентам." #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts #: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" @@ -3960,6 +3981,14 @@ msgstr "Удалить <0>{0}? Это действие нельзя отме #~ msgid "Delete all done threads" #~ msgstr "Удалить все завершенные потоки" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived thread?" +msgstr "Удалить архивный тред?" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived threads?" +msgstr "Удалить архивные потоки?" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Delete branch?" msgstr "Удалить ветку?" @@ -3974,8 +4003,8 @@ msgstr "Удалить файл" #. Tooltip: permanently delete the archived thread #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx -msgid "Delete permanently" -msgstr "Удалить безвозвратно" +#~ msgid "Delete permanently" +#~ msgstr "Удалить безвозвратно" #: src/renderer/views/GitHubActionsView/GitHubActionsView.tsx msgid "Delete run" @@ -6618,6 +6647,10 @@ msgstr "Поиск серверов разработки…" msgid "Losing worktrees and branches will be removed. Their session history will remain." msgstr "Worktree и ветки проигравших кандидатов будут удалены. История их сеансов сохранится." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Machine" +msgstr "Компьютер" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx msgid "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." msgstr "Сделать боковую панель полупрозрачной — системное размытие в Windows 11, полупрозрачный оттенок в остальных системах." @@ -8709,6 +8742,22 @@ msgstr "ожидает проверки" msgid "Pending steer" msgstr "Ожидающее направление" +#. placeholder {0}: clearTarget.threads.length +#. placeholder {1}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete {0} archived threads from {1}." +#~ msgstr "Навсегда удалить {0} архивных потоков из {1}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Permanently delete the selected archive entries from {0}." +msgstr "Навсегда удалить выбранные архивные записи с устройства {0}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete the selected archived threads from {0}." +#~ msgstr "Навсегда удалить выбранные архивные треды с устройства {0}." + #: src/renderer/components/thread/ThreadRuntimeRequestPanel/parts/PermissionDetailsLine.tsx msgid "permission" msgstr "разрешение" @@ -12145,6 +12194,10 @@ msgstr "Расположение репозитория на диске. Обн #~ msgid "The selected file does not contain valid MCP server configurations." #~ msgstr "Выбранный файл не содержит допустимых конфигураций серверов MCP." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "The selected machine is unavailable. Reconnect it to manage archived threads." +msgstr "Выбранный компьютер недоступен. Подключите его снова, чтобы управлять архивными потоками." + #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #~ msgid "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." #~ msgstr "Выбранная модель сравнивает анонимизированные снимки изменений каждого кандидата. Она рекомендует победителя, ничего не объединяя." @@ -12213,6 +12266,10 @@ msgstr "целевой провайдер" #~ msgid "The thread will run in a separate worktree on a new branch." #~ msgstr "Тред будет работать в отдельном worktree на новой ветке." +#: src/renderer/i18n/sharedMessages.ts +msgid "The threads linked to this worktree changed. Refresh and try again." +msgstr "Потоки, связанные с этим worktree, изменились. Обновите страницу и попробуйте снова." + #: src/renderer/i18n/sharedMessages.ts msgid "The update operation failed." msgstr "Не удалось выполнить операцию обновления." @@ -12320,6 +12377,10 @@ msgstr "Эта HTTPS-страница не смогла обратиться к #~ msgid "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." #~ msgstr "Эта ссылка открывает приложение, которое Poracode раздаёт в вашей сети. Размещённое приложение работает по HTTPS, поэтому браузеры блокируют обращение к десктопу по обычному HTTP." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "This machine" +msgstr "Этот компьютер" + #: src/renderer/components/mcp/McpServerEditor.tsx msgid "This name is reserved by a built-in MCP server." msgstr "Это имя зарезервировано встроенным сервером MCP." @@ -13921,6 +13982,10 @@ msgstr "Треды worktree отмечаются выполненными, ка msgid "Worktree: {0}" msgstr "Worktree: {0}" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Worktree: {worktreeLabel}" +msgstr "Worktree: {worktreeLabel}" + #: src/renderer/views/ProjectSettingsOverlay/parts/ScriptsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx diff --git a/src/renderer/locales/tr/messages.po b/src/renderer/locales/tr/messages.po index 19441f094..01c3ec0d1 100644 --- a/src/renderer/locales/tr/messages.po +++ b/src/renderer/locales/tr/messages.po @@ -1439,6 +1439,10 @@ msgstr "Konuyu arşivle" msgid "Archive Thread" msgstr "İleti Dizisini Arşivle" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Archived at {archivedTime}" +msgstr "{archivedTime} saatinde arşivlendi" + #: src/mobile/settingsSections.ts #: src/mobile/views/SettingsView.tsx #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1486,6 +1490,10 @@ msgstr "Onay iste" msgid "Ask Question" msgstr "Soru Sor" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Associated worktrees with no remaining threads will also be removed." +msgstr "Kalan ileti dizisi olmayan ilişkili çalışma ağaçları da kaldırılacaktır." + #: src/renderer/views/SchedulesView/ScheduleEditor.tsx msgid "At" msgstr "Saat" @@ -2460,6 +2468,14 @@ msgstr "Temiz çalışma ağacı" msgid "Cleanup script" msgstr "Temizleme komut dosyası" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear all" +msgstr "Tümünü temizle" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear archived threads from {dateLabel}" +msgstr "{dateLabel} tarihli arşivlenmiş ileti dizilerini temizle" + #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx msgid "Clear browsing history" msgstr "Tarama geçmişini temizle" @@ -2476,6 +2492,10 @@ msgstr "Önbelleği Temizle" msgid "Clear Cookies" msgstr "Çerezleri Temizle" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear day" +msgstr "Günü temizle" + #: src/renderer/components/thread/ThreadGoalControls.tsx msgid "Clear goal" msgstr "Hedefi temizle" @@ -3920,6 +3940,7 @@ msgstr "İşi diğer yapay zeka aracılarına devredin." #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts #: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" @@ -3960,6 +3981,14 @@ msgstr "<0>{0} silinsin mi? Bu işlem geri alınamaz." #~ msgid "Delete all done threads" #~ msgstr "Tamamlanan tüm iş parçacıklarını sil" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived thread?" +msgstr "Arşivlenmiş konu silinsin mi?" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived threads?" +msgstr "Arşivlenmiş ileti dizileri silinsin mi?" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Delete branch?" msgstr "Şube silinsin mi?" @@ -3974,8 +4003,8 @@ msgstr "Dosyayı sil" #. Tooltip: permanently delete the archived thread #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx -msgid "Delete permanently" -msgstr "Kalıcı olarak sil" +#~ msgid "Delete permanently" +#~ msgstr "Kalıcı olarak sil" #: src/renderer/views/GitHubActionsView/GitHubActionsView.tsx msgid "Delete run" @@ -6618,6 +6647,10 @@ msgstr "Geliştirme sunucuları aranıyor…" msgid "Losing worktrees and branches will be removed. Their session history will remain." msgstr "Seçilmeyen adayların çalışma ağaçları ve şubeleri kaldırılacak. Oturum geçmişleri korunacak." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Machine" +msgstr "Makine" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx msgid "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." msgstr "Kenar çubuğunu yarı saydam yapın — Windows 11'de sistem bulanıklık materyali, diğerlerinde yarı saydam bir ton." @@ -8709,6 +8742,22 @@ msgstr "inceleme bekleniyor" msgid "Pending steer" msgstr "Yönlendirme bekleniyor" +#. placeholder {0}: clearTarget.threads.length +#. placeholder {1}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete {0} archived threads from {1}." +#~ msgstr "{1} üzerindeki {0} arşivlenmiş ileti dizisini kalıcı olarak sil." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Permanently delete the selected archive entries from {0}." +msgstr "{0} üzerindeki seçili arşiv kayıtlarını kalıcı olarak sil." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete the selected archived threads from {0}." +#~ msgstr "{0} üzerindeki seçili arşivlenmiş konuları kalıcı olarak sil." + #: src/renderer/components/thread/ThreadRuntimeRequestPanel/parts/PermissionDetailsLine.tsx msgid "permission" msgstr "izin" @@ -12145,6 +12194,10 @@ msgstr "Deponun diskteki konumu. Klasörü taşıdıysanız güncelleyin." #~ msgid "The selected file does not contain valid MCP server configurations." #~ msgstr "Seçilen dosyada geçerli MCP sunucusu yapılandırmaları yok." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "The selected machine is unavailable. Reconnect it to manage archived threads." +msgstr "Seçilen makine kullanılamıyor. Arşivlenmiş ileti dizilerini yönetmek için yeniden bağlayın." + #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #~ msgid "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." #~ msgstr "Seçilen model, her adayın değişikliklerinin anonimleştirilmiş anlık görüntülerini karşılaştırır. Hiçbir şeyi birleştirmeden bir kazanan önerir." @@ -12213,6 +12266,10 @@ msgstr "hedef sağlayıcı" #~ msgid "The thread will run in a separate worktree on a new branch." #~ msgstr "Konu, yeni bir şubede ayrı bir çalışma ağacında çalışacak." +#: src/renderer/i18n/sharedMessages.ts +msgid "The threads linked to this worktree changed. Refresh and try again." +msgstr "Bu worktree'ye bağlı iş parçacıkları değişti. Yenileyip tekrar deneyin." + #: src/renderer/i18n/sharedMessages.ts msgid "The update operation failed." msgstr "Güncelleme işlemi başarısız oldu." @@ -12320,6 +12377,10 @@ msgstr "Bu HTTPS sayfası düz HTTP kullanan masaüstüne ulaşamadı. Bu siteye #~ msgid "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." #~ msgstr "Bu bağlantı, Poracode'un ağınızda sunduğu uygulamayı açar. Barındırılan uygulama HTTPS üzerinden sunulduğu için tarayıcılar düz HTTP kullanan bir masaüstüne erişimi engeller." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "This machine" +msgstr "Bu makine" + #: src/renderer/components/mcp/McpServerEditor.tsx msgid "This name is reserved by a built-in MCP server." msgstr "Bu ad, yerleşik bir MCP sunucusu için ayrılmıştır." @@ -13921,6 +13982,10 @@ msgstr "Çalışma ağacı konuları, Poracode pull request'in birleştirildiği msgid "Worktree: {0}" msgstr "Çalışma Ağacı: {0}" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Worktree: {worktreeLabel}" +msgstr "Çalışma ağacı: {worktreeLabel}" + #: src/renderer/views/ProjectSettingsOverlay/parts/ScriptsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx diff --git a/src/renderer/locales/uk/messages.po b/src/renderer/locales/uk/messages.po index f1870f468..a8544d68c 100644 --- a/src/renderer/locales/uk/messages.po +++ b/src/renderer/locales/uk/messages.po @@ -1439,6 +1439,10 @@ msgstr "Архівувати тред" msgid "Archive Thread" msgstr "Архівувати тред" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Archived at {archivedTime}" +msgstr "Архівовано о {archivedTime}" + #: src/mobile/settingsSections.ts #: src/mobile/views/SettingsView.tsx #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1486,6 +1490,10 @@ msgstr "Запитувати підтвердження" msgid "Ask Question" msgstr "Поставити запитання" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Associated worktrees with no remaining threads will also be removed." +msgstr "Пов’язані worktree, у яких не залишилося потоків, також буде видалено." + #: src/renderer/views/SchedulesView/ScheduleEditor.tsx msgid "At" msgstr "О" @@ -2460,6 +2468,14 @@ msgstr "Чистий worktree" msgid "Cleanup script" msgstr "Скрипт очищення" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear all" +msgstr "Очистити все" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear archived threads from {dateLabel}" +msgstr "Видалити архівні потоки за {dateLabel}" + #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx msgid "Clear browsing history" msgstr "Очистити історію перегляду" @@ -2476,6 +2492,10 @@ msgstr "Очистити кеш" msgid "Clear Cookies" msgstr "Очистити cookies" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear day" +msgstr "Очистити день" + #: src/renderer/components/thread/ThreadGoalControls.tsx msgid "Clear goal" msgstr "Очистити мету" @@ -3920,6 +3940,7 @@ msgstr "Делегуйте роботу іншим ШІ-агентам." #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts #: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" @@ -3960,6 +3981,14 @@ msgstr "Видалити <0>{0}? Цю дію не можна скасува #~ msgid "Delete all done threads" #~ msgstr "Видалити всі завершені потоки" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived thread?" +msgstr "Видалити архівну гілку?" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived threads?" +msgstr "Видалити архівні потоки?" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Delete branch?" msgstr "Видалити гілку?" @@ -3974,8 +4003,8 @@ msgstr "Видалити файл" #. Tooltip: permanently delete the archived thread #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx -msgid "Delete permanently" -msgstr "Видалити назавжди" +#~ msgid "Delete permanently" +#~ msgstr "Видалити назавжди" #: src/renderer/views/GitHubActionsView/GitHubActionsView.tsx msgid "Delete run" @@ -6618,6 +6647,10 @@ msgstr "Пошук серверів розробки…" msgid "Losing worktrees and branches will be removed. Their session history will remain." msgstr "Worktree та гілки кандидатів, які не перемогли, буде видалено. Історію їхніх сеансів буде збережено." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Machine" +msgstr "Комп’ютер" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx msgid "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." msgstr "Зробіть бічну панель напівпрозорою — системне розмиття у Windows 11 і напівпрозоре тонування в інших системах." @@ -8709,6 +8742,22 @@ msgstr "очікує перевірки" msgid "Pending steer" msgstr "Очікуване скерування" +#. placeholder {0}: clearTarget.threads.length +#. placeholder {1}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete {0} archived threads from {1}." +#~ msgstr "Назавжди видалити {0} архівних потоків із {1}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Permanently delete the selected archive entries from {0}." +msgstr "Назавжди видалити вибрані архівні записи з пристрою {0}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete the selected archived threads from {0}." +#~ msgstr "Назавжди видалити вибрані архівні гілки з пристрою {0}." + #: src/renderer/components/thread/ThreadRuntimeRequestPanel/parts/PermissionDetailsLine.tsx msgid "permission" msgstr "дозвіл" @@ -12145,6 +12194,10 @@ msgstr "Розташування репозиторію на диску. Оно #~ msgid "The selected file does not contain valid MCP server configurations." #~ msgstr "Вибраний файл не містить допустимих конфігурацій серверів MCP." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "The selected machine is unavailable. Reconnect it to manage archived threads." +msgstr "Вибраний комп’ютер недоступний. Підключіть його знову, щоб керувати архівними потоками." + #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #~ msgid "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." #~ msgstr "Вибрана модель порівнює анонімізовані знімки змін кожного кандидата. Вона рекомендує переможця, нічого не об’єднуючи." @@ -12213,6 +12266,10 @@ msgstr "цільовий провайдер" #~ msgid "The thread will run in a separate worktree on a new branch." #~ msgstr "Тред працюватиме в окремому worktree на новій гілці." +#: src/renderer/i18n/sharedMessages.ts +msgid "The threads linked to this worktree changed. Refresh and try again." +msgstr "Потоки, пов’язані з цим worktree, змінилися. Оновіть сторінку й спробуйте ще раз." + #: src/renderer/i18n/sharedMessages.ts msgid "The update operation failed." msgstr "Не вдалося виконати операцію оновлення." @@ -12320,6 +12377,10 @@ msgstr "Ця HTTPS-сторінка не змогла звернутися до #~ msgid "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." #~ msgstr "Це посилання відкриває застосунок, який Poracode роздає у вашій мережі. Розміщений застосунок працює через HTTPS, тому браузери блокують звернення до десктопа через звичайний HTTP." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "This machine" +msgstr "Цей комп’ютер" + #: src/renderer/components/mcp/McpServerEditor.tsx msgid "This name is reserved by a built-in MCP server." msgstr "Цю назву зарезервовано вбудованим сервером MCP." @@ -13921,6 +13982,10 @@ msgstr "Треди worktree позначаються виконаними, що msgid "Worktree: {0}" msgstr "Worktree: {0}" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Worktree: {worktreeLabel}" +msgstr "Worktree: {worktreeLabel}" + #: src/renderer/views/ProjectSettingsOverlay/parts/ScriptsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx diff --git a/src/renderer/locales/vi/messages.po b/src/renderer/locales/vi/messages.po index 486708fd7..8bf8296db 100644 --- a/src/renderer/locales/vi/messages.po +++ b/src/renderer/locales/vi/messages.po @@ -1439,6 +1439,10 @@ msgstr "Lưu trữ luồng" msgid "Archive Thread" msgstr "Lưu trữ luồng" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Archived at {archivedTime}" +msgstr "Đã lưu trữ lúc {archivedTime}" + #: src/mobile/settingsSections.ts #: src/mobile/views/SettingsView.tsx #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1486,6 +1490,10 @@ msgstr "Yêu cầu phê duyệt" msgid "Ask Question" msgstr "Đặt câu hỏi" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Associated worktrees with no remaining threads will also be removed." +msgstr "Các cây làm việc liên kết không còn luồng nào cũng sẽ bị xóa." + #: src/renderer/views/SchedulesView/ScheduleEditor.tsx msgid "At" msgstr "Lúc" @@ -2460,6 +2468,14 @@ msgstr "Cây làm việc sạch" msgid "Cleanup script" msgstr "Tập lệnh dọn dẹp" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear all" +msgstr "Xóa tất cả" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear archived threads from {dateLabel}" +msgstr "Xóa các luồng đã lưu trữ từ {dateLabel}" + #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx msgid "Clear browsing history" msgstr "Xóa lịch sử duyệt web" @@ -2476,6 +2492,10 @@ msgstr "Xóa bộ nhớ đệm" msgid "Clear Cookies" msgstr "Xóa cookie" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear day" +msgstr "Xóa ngày" + #: src/renderer/components/thread/ThreadGoalControls.tsx msgid "Clear goal" msgstr "Xóa mục tiêu" @@ -3920,6 +3940,7 @@ msgstr "Giao việc cho các tác nhân AI khác." #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts #: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" @@ -3960,6 +3981,14 @@ msgstr "Xóa <0>{0}? Không thể hoàn tác thao tác này." #~ msgid "Delete all done threads" #~ msgstr "Xóa tất cả luồng đã xong" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived thread?" +msgstr "Xóa luồng đã lưu trữ?" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived threads?" +msgstr "Xóa các luồng đã lưu trữ?" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Delete branch?" msgstr "Xóa nhánh?" @@ -3974,8 +4003,8 @@ msgstr "Xóa tập tin" #. Tooltip: permanently delete the archived thread #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx -msgid "Delete permanently" -msgstr "Xóa vĩnh viễn" +#~ msgid "Delete permanently" +#~ msgstr "Xóa vĩnh viễn" #: src/renderer/views/GitHubActionsView/GitHubActionsView.tsx msgid "Delete run" @@ -6618,6 +6647,10 @@ msgstr "Đang tìm máy chủ phát triển…" msgid "Losing worktrees and branches will be removed. Their session history will remain." msgstr "Các worktree và nhánh không được chọn sẽ bị xóa. Lịch sử phiên của chúng sẽ được giữ lại." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Machine" +msgstr "Máy" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx msgid "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." msgstr "Làm thanh bên trong suốt — hiệu ứng làm mờ của hệ thống trên Windows 11, lớp màu mờ trong suốt ở nơi khác." @@ -8709,6 +8742,22 @@ msgstr "đang chờ xem xét" msgid "Pending steer" msgstr "Đang chờ chỉ đạo" +#. placeholder {0}: clearTarget.threads.length +#. placeholder {1}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete {0} archived threads from {1}." +#~ msgstr "Xóa vĩnh viễn {0} luồng đã lưu trữ khỏi {1}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Permanently delete the selected archive entries from {0}." +msgstr "Xóa vĩnh viễn các mục lưu trữ đã chọn khỏi {0}." + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete the selected archived threads from {0}." +#~ msgstr "Xóa vĩnh viễn các luồng đã lưu trữ đã chọn khỏi {0}." + #: src/renderer/components/thread/ThreadRuntimeRequestPanel/parts/PermissionDetailsLine.tsx msgid "permission" msgstr "sự cho phép" @@ -12145,6 +12194,10 @@ msgstr "Vị trí của kho lưu trữ trên đĩa. Hãy cập nhật nếu bạ #~ msgid "The selected file does not contain valid MCP server configurations." #~ msgstr "Tệp đã chọn không chứa cấu hình máy chủ MCP hợp lệ." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "The selected machine is unavailable. Reconnect it to manage archived threads." +msgstr "Máy đã chọn hiện không khả dụng. Hãy kết nối lại để quản lý các luồng đã lưu trữ." + #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #~ msgid "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." #~ msgstr "Mô hình đã chọn so sánh các bản chụp nhanh ẩn danh về thay đổi của từng ứng viên. Mô hình đề xuất người thắng mà không hợp nhất bất kỳ nội dung nào." @@ -12213,6 +12266,10 @@ msgstr "nhà cung cấp mục tiêu" #~ msgid "The thread will run in a separate worktree on a new branch." #~ msgstr "Luồng sẽ chạy trong một cây làm việc riêng trên một nhánh mới." +#: src/renderer/i18n/sharedMessages.ts +msgid "The threads linked to this worktree changed. Refresh and try again." +msgstr "Các luồng liên kết với worktree này đã thay đổi. Hãy làm mới và thử lại." + #: src/renderer/i18n/sharedMessages.ts msgid "The update operation failed." msgstr "Thao tác cập nhật không thành công." @@ -12320,6 +12377,10 @@ msgstr "Trang HTTPS này không thể kết nối tới desktop dùng HTTP thu #~ msgid "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." #~ msgstr "Liên kết này mở ứng dụng mà Poracode phục vụ trong mạng của bạn. Ứng dụng được host chạy qua HTTPS, nên trình duyệt chặn việc kết nối tới desktop dùng HTTP thuần." +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "This machine" +msgstr "Máy này" + #: src/renderer/components/mcp/McpServerEditor.tsx msgid "This name is reserved by a built-in MCP server." msgstr "Tên này được dành riêng cho máy chủ MCP tích hợp sẵn." @@ -13921,6 +13982,10 @@ msgstr "Các luồng worktree được đánh dấu xong ngay khi Poracode thấ msgid "Worktree: {0}" msgstr "Cây làm việc: {0}" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Worktree: {worktreeLabel}" +msgstr "Cây làm việc: {worktreeLabel}" + #: src/renderer/views/ProjectSettingsOverlay/parts/ScriptsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx diff --git a/src/renderer/locales/zh-CN/messages.po b/src/renderer/locales/zh-CN/messages.po index 58fa46c28..98d5d53c4 100644 --- a/src/renderer/locales/zh-CN/messages.po +++ b/src/renderer/locales/zh-CN/messages.po @@ -1439,6 +1439,10 @@ msgstr "归档线程" msgid "Archive Thread" msgstr "存档线程" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Archived at {archivedTime}" +msgstr "归档于 {archivedTime}" + #: src/mobile/settingsSections.ts #: src/mobile/views/SettingsView.tsx #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1486,6 +1490,10 @@ msgstr "请求批准" msgid "Ask Question" msgstr "提问" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Associated worktrees with no remaining threads will also be removed." +msgstr "没有剩余线程的关联工作树也将被删除。" + #: src/renderer/views/SchedulesView/ScheduleEditor.tsx msgid "At" msgstr "时间" @@ -2460,6 +2468,14 @@ msgstr "干净的工作树" msgid "Cleanup script" msgstr "清理脚本" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear all" +msgstr "全部清除" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear archived threads from {dateLabel}" +msgstr "清除 {dateLabel} 的已归档线程" + #: src/renderer/views/MainView/parts/RightPanel/parts/BrowserPanel/parts/BrowserToolbar.tsx msgid "Clear browsing history" msgstr "清除浏览历史记录" @@ -2476,6 +2492,10 @@ msgstr "清除缓存" msgid "Clear Cookies" msgstr "清除 Cookie" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Clear day" +msgstr "清除当天" + #: src/renderer/components/thread/ThreadGoalControls.tsx msgid "Clear goal" msgstr "清除目标" @@ -3920,6 +3940,7 @@ msgstr "将工作委派给其他 AI 智能体。" #: src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx #: src/renderer/views/SchedulesView/SchedulesView.tsx #: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx #: src/renderer/views/SettingsOverlay/parts/settingsOptions.ts #: src/renderer/views/SettingsOverlay/parts/WorkspacesSettings.tsx msgid "Delete" @@ -3960,6 +3981,14 @@ msgstr "删除 <0>{0}?此操作无法撤销。" #~ msgid "Delete all done threads" #~ msgstr "删除所有已完成的线程" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived thread?" +msgstr "删除已归档线程?" + +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Delete archived threads?" +msgstr "删除已归档线程?" + #: src/renderer/components/common/BranchSelector/BranchSelector.tsx msgid "Delete branch?" msgstr "删除分支?" @@ -3974,8 +4003,8 @@ msgstr "删除文件" #. Tooltip: permanently delete the archived thread #: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx -msgid "Delete permanently" -msgstr "永久删除" +#~ msgid "Delete permanently" +#~ msgstr "永久删除" #: src/renderer/views/GitHubActionsView/GitHubActionsView.tsx msgid "Delete run" @@ -6618,6 +6647,10 @@ msgstr "正在查找开发服务器…" msgid "Losing worktrees and branches will be removed. Their session history will remain." msgstr "未胜出的工作树和分支将被移除。其会话历史将保留。" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Machine" +msgstr "设备" + #: src/renderer/views/SettingsOverlay/parts/AppearanceSettings.tsx msgid "Make the sidebar translucent — the system blur material on Windows 11, a translucent tint elsewhere." msgstr "将侧边栏设为半透明——在Windows 11上使用系统模糊材质,在其他平台上使用半透明着色。" @@ -8708,6 +8741,22 @@ msgstr "待审查" msgid "Pending steer" msgstr "等待引导" +#. placeholder {0}: clearTarget.threads.length +#. placeholder {1}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete {0} archived threads from {1}." +#~ msgstr "永久删除 {1} 中的 {0} 个已归档线程。" + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Permanently delete the selected archive entries from {0}." +msgstr "永久删除 {0} 中选定的归档条目。" + +#. placeholder {0}: clearTarget.label +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +#~ msgid "Permanently delete the selected archived threads from {0}." +#~ msgstr "永久删除 {0} 中选定的已归档线程。" + #: src/renderer/components/thread/ThreadRuntimeRequestPanel/parts/PermissionDetailsLine.tsx msgid "permission" msgstr "权限" @@ -12144,6 +12193,10 @@ msgstr "存储库在磁盘上的位置。如果您移动了该文件夹,请更 #~ msgid "The selected file does not contain valid MCP server configurations." #~ msgstr "所选文件不包含有效的 MCP 服务器配置。" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "The selected machine is unavailable. Reconnect it to manage archived threads." +msgstr "所选设备不可用。请重新连接以管理已归档线程。" + #: src/renderer/views/ExperimentView/parts/ExperimentJudgeDialog.tsx #~ msgid "The selected model compares anonymized snapshots of each candidate's changes. It recommends a winner without merging anything." #~ msgstr "所选模型会比较每个候选方案更改的匿名快照,并在不合并任何内容的情况下推荐获胜者。" @@ -12212,6 +12265,10 @@ msgstr "目标提供商" #~ msgid "The thread will run in a separate worktree on a new branch." #~ msgstr "线程将在新分支上的独立工作树中运行。" +#: src/renderer/i18n/sharedMessages.ts +msgid "The threads linked to this worktree changed. Refresh and try again." +msgstr "与此工作树关联的线程已更改。请刷新后重试。" + #: src/renderer/i18n/sharedMessages.ts msgid "The update operation failed." msgstr "更新操作失败。" @@ -12319,6 +12376,10 @@ msgstr "此 HTTPS 页面无法连接使用纯 HTTP 的桌面。请允许该站 #~ msgid "This link opens the app Poracode serves on your network. The hosted app is served over HTTPS, which browsers block from reaching a plain-HTTP desktop." #~ msgstr "此链接会打开 Poracode 在你网络中提供的应用。托管版应用通过 HTTPS 提供,浏览器会因此阻止它连接使用纯 HTTP 的桌面。" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "This machine" +msgstr "此设备" + #: src/renderer/components/mcp/McpServerEditor.tsx msgid "This name is reserved by a built-in MCP server." msgstr "此名称由内置 MCP 服务器保留。" @@ -13920,6 +13981,10 @@ msgstr "一旦 Poracode 检测到拉取请求已合并,工作树线程就会 msgid "Worktree: {0}" msgstr "工作树:{0}" +#: src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +msgid "Worktree: {worktreeLabel}" +msgstr "工作树:{worktreeLabel}" + #: src/renderer/views/ProjectSettingsOverlay/parts/ScriptsSection.tsx #: src/renderer/views/ProjectSettingsOverlay/parts/SettingsSidebar.tsx #: src/renderer/views/SettingsOverlay/parts/SettingsSidebar.tsx diff --git a/src/renderer/state/appStore.test.ts b/src/renderer/state/appStore.test.ts index c52f3793c..cf6ab611f 100644 --- a/src/renderer/state/appStore.test.ts +++ b/src/renderer/state/appStore.test.ts @@ -130,7 +130,53 @@ describe("appStore runtime config sync", () => { ); expect(useAppStore.getState().provisioningWorktreeThreadIds[thread.id]).toBeUndefined(); expect(resolved.view).toMatchObject({ kind: "thread", panes: [thread.id] }); - expect(useAppStore.persist.getOptions().version).toBe(4); + expect(useAppStore.persist.getOptions().version).toBe(5); + }); + + it("keeps remote archived threads out of persisted state", () => { + const project = useAppStore.getState().addProject({ kind: "windows", path: "C:\\repo" }); + const local = useAppStore.getState().createThread({ + projectId: project.id, + agentKind: "codex", + config: { model: "gpt-5.4" }, + prompt: "local", + }); + useAppStore.setState({ + threads: [ + { ...local, archived: true, archivedAt: local.updatedAt }, + { + ...local, + id: "remote:d1:thread:archived", + remoteServerId: "d1", + remoteId: "archived", + archived: true, + archivedAt: local.updatedAt, + }, + ], + }); + + const partialize = useAppStore.persist.getOptions().partialize!; + const persisted = partialize(useAppStore.getState()) as Pick; + + expect(persisted.threads.map((thread) => thread.id)).toEqual([local.id]); + }); + + it("migrates legacy archived threads to store version 5", async () => { + const project = useAppStore.getState().addProject({ kind: "windows", path: "C:\\repo" }); + const thread = useAppStore.getState().createThread({ + projectId: project.id, + agentKind: "codex", + config: { model: "gpt-5.4" }, + prompt: "legacy", + }); + const migrate = useAppStore.persist.getOptions().migrate!; + + const migrated = (await migrate({ threads: [{ ...thread, archived: true }] }, 4)) as Pick< + AppStoreState, + "threads" + >; + + expect(migrated.threads[0]?.archivedAt).toBe(thread.updatedAt); }); it("clears provisional worktree launch state when deleting its project", () => { @@ -276,6 +322,38 @@ describe("appStore runtime config sync", () => { expect(stored?.doneAt).toBe("2026-05-10T12:00:00.000Z"); }); + it("records the actual automatic archive time", () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-05-10T12:00:00.000Z")); + const project = useAppStore.getState().addProject({ kind: "windows", path: "C:\\repo" }); + const thread = useAppStore.getState().createThread({ + projectId: project.id, + agentKind: "codex", + config: { model: "gpt-5.4" }, + prompt: "hello", + }); + useAppStore.setState((state) => ({ + threads: state.threads.map((candidate) => + candidate.id === thread.id + ? { + ...candidate, + done: true, + doneAt: "2026-04-01T00:00:00.000Z", + updatedAt: "2026-04-01T00:00:00.000Z", + } + : candidate, + ), + })); + + useAppStore.getState().archiveOldDoneThreads(7); + + expect(useAppStore.getState().threads[0]).toMatchObject({ + archived: true, + archivedAt: "2026-05-10T12:00:00.000Z", + updatedAt: "2026-04-01T00:00:00.000Z", + }); + }); + it("preserves updatedAt when renaming a thread", () => { const project = useAppStore.getState().addProject({ kind: "windows", diff --git a/src/renderer/state/appStore.ts b/src/renderer/state/appStore.ts index c95675c85..6dafca759 100644 --- a/src/renderer/state/appStore.ts +++ b/src/renderer/state/appStore.ts @@ -34,8 +34,23 @@ export const useAppStore = create()( }), { name: "poracode-app-v2", - version: 4, + version: 5, storage: createDbStorage(), + migrate: (persistedState) => { + const state = persistedState as Partial & { threads?: Thread[] }; + return { + ...state, + ...(state.threads + ? { + threads: state.threads.map((thread) => + thread.archived && !thread.archivedAt + ? { ...thread, archivedAt: thread.updatedAt } + : thread, + ), + } + : {}), + }; + }, merge: (persistedState, currentState) => { const state = (persistedState as (Partial & { threads?: Thread[] }) | undefined) ?? @@ -43,6 +58,7 @@ export const useAppStore = create()( const threads = (state.threads ?? currentState.threads).map((t) => ({ ...normalizeStoredThreadStatus(t), + ...(t.archived ? { archivedAt: t.archivedAt ?? t.updatedAt } : {}), done: t.done ?? false, doneAt: t.done ? (t.doneAt ?? t.updatedAt) : undefined, })); diff --git a/src/renderer/state/dbStorage.test.ts b/src/renderer/state/dbStorage.test.ts index f452af161..7bc01c582 100644 --- a/src/renderer/state/dbStorage.test.ts +++ b/src/renderer/state/dbStorage.test.ts @@ -47,18 +47,18 @@ describe("createDbStorage", () => { await storage.setItem("poracode-app-v2", { state: { projects, threads, view, groupLayouts }, - version: 4, + version: 5, }); await storage.setItem("poracode-app-v2", { state: { projects, threads, view, groupLayouts }, - version: 4, + version: 5, }); expect(bridge.dbSyncAll).toHaveBeenCalledTimes(1); await storage.setItem("poracode-app-v2", { state: { projects, threads: [...threads], view, groupLayouts }, - version: 4, + version: 5, }); expect(bridge.dbSyncAll).toHaveBeenCalledTimes(2); @@ -106,7 +106,7 @@ describe("createDbStorage", () => { view: { kind: "home" }, groupLayouts: {}, }, - version: 4, + version: 5, } as never); expect(bridge.dbSyncAll).not.toHaveBeenCalled(); @@ -133,7 +133,7 @@ describe("dbStorage persistence error reporting", () => { await storage.setItem("poracode-app-v2", { state: { projects: [], threads: [], view: { kind: "home" }, groupLayouts: {} }, - version: 4, + version: 5, } as never); await flushMicrotasks(); diff --git a/src/renderer/state/dbStorage.ts b/src/renderer/state/dbStorage.ts index 5e518c7bd..75153cf3d 100644 --- a/src/renderer/state/dbStorage.ts +++ b/src/renderer/state/dbStorage.ts @@ -126,7 +126,7 @@ async function loadAppStore(): Promise | null> { return rememberStorageValue(APP_STORE_NAME, { state: { projects, threads, view, groupLayouts }, - version: 4, + version: 5, }); } diff --git a/src/renderer/state/slices/threadSlice.ts b/src/renderer/state/slices/threadSlice.ts index 8c9f4c1b1..d9cdbb506 100644 --- a/src/renderer/state/slices/threadSlice.ts +++ b/src/renderer/state/slices/threadSlice.ts @@ -514,8 +514,9 @@ export const createThreadSlice: SliceCreator = (set) => ({ const thread = state.threads.find((t) => t.id === threadId); if (!thread || thread.archived) return {}; + const now = new Date().toISOString(); const threads = state.threads.map((t) => - t.id === threadId ? { ...t, archived: true, updatedAt: new Date().toISOString() } : t, + t.id === threadId ? { ...t, archived: true, archivedAt: now, updatedAt: now } : t, ); let nextView = state.view; @@ -534,7 +535,9 @@ export const createThreadSlice: SliceCreator = (set) => ({ return { threads: state.threads.map((t) => - t.id === threadId ? { ...t, archived: false, updatedAt: new Date().toISOString() } : t, + t.id === threadId + ? { ...t, archived: false, archivedAt: undefined, updatedAt: new Date().toISOString() } + : t, ), }; }), @@ -588,7 +591,7 @@ export const createThreadSlice: SliceCreator = (set) => ({ set((state) => { const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000; const nextThreads = state.threads.filter( - (t) => !t.archived || new Date(t.updatedAt).getTime() > cutoff, + (t) => !t.archived || new Date(t.archivedAt ?? t.updatedAt).getTime() > cutoff, ); if (nextThreads.length === state.threads.length) return {}; return { threads: nextThreads }; @@ -600,12 +603,13 @@ export const createThreadSlice: SliceCreator = (set) => ({ let changed = false; const visiblePanes = state.view.kind === "thread" ? new Set(state.view.panes) : new Set(); + const archivedAt = new Date().toISOString(); const threads = state.threads.map((t) => { if (!t.done || t.archived || t.starred) return t; if (new Date(t.doneAt ?? t.updatedAt).getTime() > cutoff) return t; changed = true; - return { ...t, archived: true }; + return { ...t, archived: true, archivedAt }; }); if (!changed) return {}; diff --git a/src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.test.tsx b/src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.test.tsx index 24ab57011..2183e9d6c 100644 --- a/src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.test.tsx +++ b/src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.test.tsx @@ -9,15 +9,10 @@ import { SidebarThreadRow } from "./SidebarThreadRow"; const threadActions = vi.hoisted(() => ({ archiveThread: vi.fn<(threadId: string) => void>(), - deleteThread: vi.fn<(threadId: string) => void>(), -})); -const worktreeActions = vi.hoisted(() => ({ - deleteWorktreeGroup: - vi.fn<(projectId: string, worktreePath: string, threadIds: string[]) => void>(), + deleteThreadsAndOwnedWorktrees: vi.fn<(threads: readonly Thread[]) => void>(), })); vi.mock("@/renderer/actions/threadActions", () => threadActions); -vi.mock("@/renderer/actions/worktreeActions", () => worktreeActions); vi.mock("./SidebarThreadGroup", () => ({ SidebarThreadGroup: () => null })); vi.mock("./SidebarWorktreeGroup", () => ({ SidebarWorktreeGroup: () => null })); vi.mock("./SortableThreadItem/SortableThreadItem", () => ({ SortableThreadItem: () => null })); @@ -75,8 +70,7 @@ function renderDoneRow(options?: { describe("SidebarThreadRow Done section action", () => { beforeEach(() => { threadActions.archiveThread.mockReset(); - threadActions.deleteThread.mockReset(); - worktreeActions.deleteWorktreeGroup.mockReset(); + threadActions.deleteThreadsAndOwnedWorktrees.mockReset(); useSharedSettings.setState({ threadRemoveAction: "archive" }); }); @@ -99,7 +93,7 @@ describe("SidebarThreadRow Done section action", () => { fireEvent.click(trigger); fireEvent.click(screen.getByRole("button", { name: "Archive" })); expect(threadActions.archiveThread.mock.calls).toEqual([["done-1"], ["done-2"]]); - expect(threadActions.deleteThread).not.toHaveBeenCalled(); + expect(threadActions.deleteThreadsAndOwnedWorktrees).not.toHaveBeenCalled(); }); it("confirms and permanently deletes every done thread", () => { @@ -112,7 +106,10 @@ describe("SidebarThreadRow Done section action", () => { ).toBeInTheDocument(); fireEvent.click(screen.getByRole("button", { name: "Delete" })); - expect(threadActions.deleteThread.mock.calls).toEqual([["done-1"], ["done-2"]]); + expect(threadActions.deleteThreadsAndOwnedWorktrees).toHaveBeenCalledWith([ + expect.objectContaining({ id: "done-1" }), + expect.objectContaining({ id: "done-2" }), + ]); expect(threadActions.archiveThread).not.toHaveBeenCalled(); }); @@ -126,12 +123,7 @@ describe("SidebarThreadRow Done section action", () => { fireEvent.click(screen.getByRole("button", { name: "Delete done threads" })); fireEvent.click(screen.getByRole("button", { name: "Delete" })); - expect(worktreeActions.deleteWorktreeGroup).toHaveBeenCalledWith( - project.id, - thread.worktreePath, - [thread.id], - ); - expect(threadActions.deleteThread).not.toHaveBeenCalled(); + expect(threadActions.deleteThreadsAndOwnedWorktrees).toHaveBeenCalledWith([thread]); }); it("removes a worktree when every sibling is done", () => { @@ -146,11 +138,7 @@ describe("SidebarThreadRow Done section action", () => { fireEvent.click(screen.getByRole("button", { name: "Delete done threads" })); fireEvent.click(screen.getByRole("button", { name: "Delete" })); - expect(worktreeActions.deleteWorktreeGroup).toHaveBeenCalledWith(project.id, worktreePath, [ - "done-1", - "done-2", - ]); - expect(threadActions.deleteThread).not.toHaveBeenCalled(); + expect(threadActions.deleteThreadsAndOwnedWorktrees).toHaveBeenCalledWith(threads); }); it("preserves a worktree that still has a retained sibling", () => { @@ -163,8 +151,7 @@ describe("SidebarThreadRow Done section action", () => { fireEvent.click(screen.getByRole("button", { name: "Delete done threads" })); fireEvent.click(screen.getByRole("button", { name: "Delete" })); - expect(threadActions.deleteThread).toHaveBeenCalledWith(doneThread.id); - expect(worktreeActions.deleteWorktreeGroup).not.toHaveBeenCalled(); + expect(threadActions.deleteThreadsAndOwnedWorktrees).toHaveBeenCalledWith([doneThread]); }); it("uses one visible keyboard trigger and restores focus after Escape", async () => { diff --git a/src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx b/src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx index 0e5e37883..77ed429f8 100644 --- a/src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx +++ b/src/renderer/views/MainView/parts/Sidebar/parts/SidebarThreadRow.tsx @@ -1,14 +1,12 @@ import { useState } from "react"; import { useLingui } from "@lingui/react/macro"; import { Archive, ChevronDown, Trash2 } from "lucide-react"; -import type { Project, Thread } from "@/shared/contracts"; -import { archiveThread, deleteThread } from "@/renderer/actions/threadActions"; -import { deleteWorktreeGroup } from "@/renderer/actions/worktreeActions"; +import type { Project } from "@/shared/contracts"; +import { archiveThread, deleteThreadsAndOwnedWorktrees } from "@/renderer/actions/threadActions"; import { Button } from "@/renderer/components/common/Button"; import { ConfirmationPopover } from "@/renderer/components/common/ConfirmationPopover"; import { SidebarButton } from "@/renderer/components/common/SidebarButton"; import { chatRowRailClass } from "@/renderer/components/thread/ChatPane/parts/items/chatRow"; -import { useAppStore } from "@/renderer/state/appStore"; import { useSharedSettings } from "@/renderer/state/sharedSettingsStore"; import type { SidebarRow } from "./sidebarProjectRows"; import { SidebarThreadGroup } from "./SidebarThreadGroup"; @@ -27,37 +25,6 @@ export function SeeMoreThreadsButton(props: { onPress: () => void }) { ); } -function deleteDoneThreads(doneThreads: Thread[]): void { - const doneThreadIds = new Set(doneThreads.map((thread) => thread.id)); - const allThreads = useAppStore.getState().threads; - const deletedWorktrees = new Set(); - - for (const thread of doneThreads) { - if (!thread.worktreePath) { - deleteThread(thread.id); - continue; - } - - const worktreeKey = `${thread.projectId}\0${thread.worktreePath}`; - if (deletedWorktrees.has(worktreeKey)) continue; - - const siblings = allThreads.filter( - (candidate) => - candidate.projectId === thread.projectId && candidate.worktreePath === thread.worktreePath, - ); - if (siblings.every((candidate) => doneThreadIds.has(candidate.id))) { - deletedWorktrees.add(worktreeKey); - deleteWorktreeGroup( - thread.projectId, - thread.worktreePath, - siblings.map((candidate) => candidate.id), - ); - } else { - deleteThread(thread.id); - } - } -} - function DoneSectionLabel(props: { row: Extract }) { const { row } = props; const { t } = useLingui(); @@ -70,7 +37,7 @@ function DoneSectionLabel(props: { row: Extract ({ + deleteThreadsAndOwnedWorktrees: vi.fn<(threads: readonly Thread[]) => void>(), + unarchiveThread: vi.fn<(threadId: string) => void>(), +})); + +vi.mock("@/renderer/actions/threadActions", () => actions); +vi.mock("@/renderer/components/providers/ThreadProviderIcon", () => ({ + ThreadProviderIcon: () => , +})); + +import { ArchivedThreadsSettings } from "./ArchivedThreadsSettings"; + +const project: Project = { + id: "local-project", + name: "Local project", + location: { kind: "windows", path: "C:\\work\\local" }, + createdAt: "2026-08-01T00:00:00.000Z", +}; + +function thread( + id: string, + title: string, + updatedAt: string, + remote?: { desktopId: string; projectId: string }, +): Thread { + return { + id, + projectId: remote?.projectId ?? project.id, + title, + agentKind: "claude", + config: { model: "default" }, + status: "inactive", + attention: "none", + canResumeWithConfig: false, + archived: true, + archivedAt: updatedAt, + done: false, + starred: false, + createdAt: "2026-08-01T00:00:00.000Z", + updatedAt, + ...(remote ? { remoteServerId: remote.desktopId, remoteId: `remote-${id}` } : {}), + }; +} + +describe("ArchivedThreadsSettings", () => { + beforeEach(() => { + actions.deleteThreadsAndOwnedWorktrees.mockReset(); + actions.unarchiveThread.mockReset(); + useAppStore.setState({ + projects: [ + project, + { + ...project, + id: "remote:d1:project:remote-project", + name: "Remote project", + remoteServerId: "d1", + remoteId: "remote-project", + location: { kind: "posix", path: "/work/remote", remoteServerId: "d1" }, + }, + ], + threads: [ + { + ...thread("local-new", "Local newer", "2026-08-22T18:30:00.000Z"), + updatedAt: "2026-01-01T00:00:00.000Z", + }, + { + ...thread("local-old", "Local older", "2026-08-21T12:00:00.000Z"), + worktreePath: "C:\\worktrees\\feature-archive", + worktreeBranch: "feature/archive", + }, + thread("remote-row", "Remote archive", "2026-08-22T19:00:00.000Z", { + desktopId: "d1", + projectId: "remote:d1:project:remote-project", + }), + ], + }); + useRemoteServersStore.setState({ + servers: [ + { + desktopId: "d1", + label: "Studio Mac", + endpoint: "https://remote.test", + accessToken: "test-token", + scopes: [], + }, + ], + runtime: { + d1: { status: "online", projects: [], threads: [] }, + }, + }); + }); + + it("separates local and remote archives by machine", async () => { + render(); + + expect(screen.getByText("Local newer")).toBeInTheDocument(); + expect(screen.queryByText("Remote archive")).not.toBeInTheDocument(); + + const machineTrigger = screen.getByRole("button", { name: "Machine: This machine" }); + expect(machineTrigger).toHaveClass("rounded-3xl", "justify-start"); + expect(machineTrigger.querySelector(".lucide-monitor")).not.toBeNull(); + fireEvent.click(machineTrigger); + fireEvent.click(await screen.findByRole("menuitemradio", { name: "Studio Mac" })); + + expect(screen.getByText("Remote archive")).toBeInTheDocument(); + expect(screen.queryByText("Local newer")).not.toBeInTheDocument(); + expect(machineTrigger.querySelector(".lucide-server")).not.toBeNull(); + expect(machineTrigger).toHaveAccessibleName("Machine: Studio Mac"); + }); + + it("groups archives by day and clears only the selected day", () => { + render(); + + const clearDayButtons = screen.getAllByRole("button", { name: /Clear archived threads from/ }); + expect(clearDayButtons).toHaveLength(2); + fireEvent.click(clearDayButtons[0]!); + + expect(actions.deleteThreadsAndOwnedWorktrees).not.toHaveBeenCalled(); + fireEvent.click(screen.getByRole("button", { name: "Delete" })); + + expect(actions.deleteThreadsAndOwnedWorktrees).toHaveBeenCalledTimes(1); + expect( + actions.deleteThreadsAndOwnedWorktrees.mock.calls[0]?.[0].map((candidate) => candidate.id), + ).toEqual(["local-new"]); + }); + + it("shows the archive time and clears every archive on the selected machine", () => { + render(); + + expect(screen.getAllByText(/Archived at/)).toHaveLength(2); + expect(screen.getByText("Worktree: feature/archive")).toBeInTheDocument(); + const clearAll = screen.getByRole("button", { name: "Clear all" }); + fireEvent.click(clearAll); + + expect(screen.getByText(/Associated worktrees with no remaining threads/i)).toBeInTheDocument(); + expect(actions.deleteThreadsAndOwnedWorktrees).not.toHaveBeenCalled(); + fireEvent.click(screen.getByRole("button", { name: "Delete" })); + + expect(actions.deleteThreadsAndOwnedWorktrees).toHaveBeenCalledTimes(1); + expect( + actions.deleteThreadsAndOwnedWorktrees.mock.calls[0]?.[0].map((candidate) => candidate.id), + ).toEqual(["local-new", "local-old"]); + }); + + it("confirms a single archived thread and discloses worktree removal", () => { + render(); + + fireEvent.click(screen.getAllByRole("button", { name: "Delete thread" })[1]!); + + expect(actions.deleteThreadsAndOwnedWorktrees).not.toHaveBeenCalled(); + expect( + screen.getByText(/Permanently delete the selected archive entries/i), + ).toBeInTheDocument(); + expect(screen.getByText(/Associated worktrees with no remaining threads/i)).toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "Delete" })); + + expect(actions.deleteThreadsAndOwnedWorktrees).toHaveBeenCalledWith([ + expect.objectContaining({ id: "local-old" }), + ]); + }); + + it("disables archive mutations for an unavailable remote machine", async () => { + useRemoteServersStore.setState((state) => ({ + runtime: { + ...state.runtime, + d1: { status: "offline", projects: [], threads: [] }, + }, + })); + render(); + + fireEvent.click(screen.getByRole("button", { name: /Machine:/ })); + fireEvent.click(await screen.findByRole("menuitemradio", { name: "Studio Mac" })); + + expect(screen.getByText(/selected machine is unavailable/i)).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Clear all" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Restore thread" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Delete thread" })).toBeDisabled(); + }); + + it("keeps archive mutations available after a reachable remote error", async () => { + useRemoteServersStore.setState((state) => ({ + runtime: { + ...state.runtime, + d1: { status: "error", message: "Previous request failed", projects: [], threads: [] }, + }, + })); + render(); + + fireEvent.click(screen.getByRole("button", { name: /Machine:/ })); + fireEvent.click(await screen.findByRole("menuitemradio", { name: "Studio Mac" })); + + expect(screen.queryByText(/selected machine is unavailable/i)).not.toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Clear all" })).toBeEnabled(); + expect(screen.getByRole("button", { name: "Restore thread" })).toBeEnabled(); + expect(screen.getByRole("button", { name: "Delete thread" })).toBeEnabled(); + }); +}); diff --git a/src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx b/src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx index c4e9ca759..0f1b6de63 100644 --- a/src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx +++ b/src/renderer/views/SettingsOverlay/parts/ArchivedThreadsSettings.tsx @@ -1,78 +1,327 @@ -import { Button, Surface, Tooltip } from "@heroui/react"; -import { ArchiveRestore, Trash2 } from "lucide-react"; +import { useState } from "react"; +import { Button, Dropdown, Label, Surface, Tooltip } from "@heroui/react"; +import { ArchiveRestore, ChevronsUpDown, GitFork, Monitor, Trash2 } from "lucide-react"; import { Trans, useLingui } from "@lingui/react/macro"; +import type { Thread } from "@/shared/contracts"; +import { getBasename } from "@/shared/pathUtils"; +import { desktopTitle } from "@/shared/remote/desktopLabel"; +import { ConfirmationPopover } from "@/renderer/components/common/ConfirmationPopover"; +import { ProjectRemoteServerIcon } from "@/renderer/components/common/ProjectRemoteServer"; import { useAppStore } from "@/renderer/state/appStore"; +import { useRemoteServersStore } from "@/renderer/state/remoteServersStore"; import { ThreadProviderIcon } from "@/renderer/components/providers/ThreadProviderIcon"; -import { deleteThread, unarchiveThread } from "@/renderer/actions/threadActions"; +import { deleteThreadsAndOwnedWorktrees, unarchiveThread } from "@/renderer/actions/threadActions"; import { SettingsPage } from "./SettingsForm"; +const LOCAL_MACHINE_ID = "local"; + +interface ArchivedThreadGroup { + key: string; + date: Date; + threads: Thread[]; +} + +interface ClearTarget { + key: string; + label: string; + threads: Thread[]; +} + +function groupArchivedThreads(threads: readonly Thread[]): ArchivedThreadGroup[] { + const groups = new Map(); + for (const thread of [...threads].sort((a, b) => + (b.archivedAt ?? b.updatedAt).localeCompare(a.archivedAt ?? a.updatedAt), + )) { + const date = new Date(thread.archivedAt ?? thread.updatedAt); + const key = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`; + const group = groups.get(key); + if (group) group.threads.push(thread); + else groups.set(key, { key, date, threads: [thread] }); + } + return [...groups.values()]; +} + export function ArchivedThreadsSettings() { - const { t } = useLingui(); + const { i18n, t } = useLingui(); const threads = useAppStore((s) => s.threads); const projects = useAppStore((s) => s.projects); - const archivedThreads = threads.filter((thread) => thread.archived); + const remoteServers = useRemoteServersStore((s) => s.servers); + const remoteRuntimes = useRemoteServersStore((s) => s.runtime); + const [machineId, setMachineId] = useState(LOCAL_MACHINE_ID); + const [clearTarget, setClearTarget] = useState(null); + const selectedMachineId = + machineId === LOCAL_MACHINE_ID || remoteServers.some((server) => server.desktopId === machineId) + ? machineId + : LOCAL_MACHINE_ID; + const archivedThreads = threads.filter( + (thread) => + thread.archived && + (selectedMachineId === LOCAL_MACHINE_ID + ? thread.remoteServerId === undefined + : thread.remoteServerId === selectedMachineId), + ); + const groups = groupArchivedThreads(archivedThreads); + const machineOptions = [ + { id: LOCAL_MACHINE_ID, label: t`This machine`, icon: }, + ...remoteServers.map((server) => ({ + id: server.desktopId, + label: desktopTitle(server.label), + icon: ( + + ), + })), + ]; + const selectedMachine = + machineOptions.find((option) => option.id === selectedMachineId) ?? machineOptions[0]!; + const selectedMachineLabel = selectedMachine.label; + const selectedMachineUnavailable = + selectedMachineId !== LOCAL_MACHINE_ID && + remoteRuntimes[selectedMachineId]?.status !== "online" && + remoteRuntimes[selectedMachineId]?.status !== "error"; + const dateFormatter = new Intl.DateTimeFormat(i18n.locale, { + weekday: "long", + year: "numeric", + month: "long", + day: "numeric", + }); + const timeFormatter = new Intl.DateTimeFormat(i18n.locale, { + hour: "numeric", + minute: "2-digit", + }); + const clearThreads = (threadsToClear: readonly Thread[]) => { + setClearTarget(null); + deleteThreadsAndOwnedWorktrees(threadsToClear); + }; + const clearConfirmationBody = clearTarget ? ( + <> + {t`Permanently delete the selected archive entries from ${clearTarget.label}.`} + {clearTarget.threads.some((thread) => thread.worktreePath) ? ( + + Associated worktrees with no remaining threads will also be removed. + + ) : null} + + ) : null; return ( - + + setClearTarget( + isOpen ? { key: "all", label: selectedMachineLabel, threads: archivedThreads } : null, + ) + } + title={t`Delete archived threads?`} + body={clearConfirmationBody} + placement="bottom end" + actions={[ + { + label: t`Delete`, + variant: "danger", + onPress: () => clearTarget && clearThreads(clearTarget.threads), + }, + ]} + trigger={ + + } + /> + } + > +
+ + + + setMachineId(String(key))} + > + {machineOptions.map((option) => ( + + {option.icon} + + + + ))} + + + +
+ {selectedMachineUnavailable ? ( +

+ + The selected machine is unavailable. Reconnect it to manage archived threads. + +

+ ) : null} {archivedThreads.length === 0 ? (

No archived threads.

) : ( - - {archivedThreads.map((thread) => { - const project = projects.find((p) => p.id === thread.projectId); +
+ {groups.map((group) => { + const dateLabel = dateFormatter.format(group.date); return ( -
- -
-

{thread.title}

- {project &&

{project.name}

} -
-
- - +
+
+

{dateLabel}

+ + setClearTarget( + isOpen + ? { key: group.key, label: dateLabel, threads: group.threads } + : null, + ) + } + title={t`Delete archived threads?`} + body={clearConfirmationBody} + actions={[ + { + label: t`Delete`, + variant: "danger", + onPress: () => clearTarget && clearThreads(clearTarget.threads), + }, + ]} + trigger={ - - - Restore thread - - - - - - - - - Delete permanently - - - + } + />
-
+ + {group.threads.map((thread) => { + const project = projects.find((p) => p.id === thread.projectId); + const archivedTime = timeFormatter.format( + new Date(thread.archivedAt ?? thread.updatedAt), + ); + const worktreeLabel = thread.worktreePath + ? (thread.worktreeBranch ?? getBasename(thread.worktreePath)) + : undefined; + return ( +
+ +
+

+ {thread.title} +

+

+ {project ? `${project.name} · ` : ""} + {t`Archived at ${archivedTime}`} +

+ {worktreeLabel ? ( +

+ + {t`Worktree: ${worktreeLabel}`} +

+ ) : null} +
+
+ + + + + + Restore thread + + + + setClearTarget( + isOpen + ? { + key: `thread:${thread.id}`, + label: selectedMachineLabel, + threads: [thread], + } + : null, + ) + } + title={t`Delete archived thread?`} + body={clearConfirmationBody} + actions={[ + { + label: t`Delete`, + variant: "danger", + onPress: () => clearTarget && clearThreads(clearTarget.threads), + }, + ]} + trigger={ + + } + /> +
+
+ ); + })} +
+ ); })} - +
)} ); diff --git a/src/shared/contracts/thread.ts b/src/shared/contracts/thread.ts index e2c9fce6b..49153ce26 100644 --- a/src/shared/contracts/thread.ts +++ b/src/shared/contracts/thread.ts @@ -42,6 +42,7 @@ export const threadSchema = z.object({ groupId: z.string().optional(), groupName: z.string().optional(), archived: z.boolean().default(false), + archivedAt: z.string().min(1).optional(), done: z.boolean().default(false), doneAt: z.string().min(1).optional(), starred: z.boolean().default(false), diff --git a/src/shared/messages.test.ts b/src/shared/messages.test.ts index 79cf84987..2359a4a6c 100644 --- a/src/shared/messages.test.ts +++ b/src/shared/messages.test.ts @@ -84,6 +84,15 @@ describe("friendlyErrorWithDetail", () => { }); }); + it("maps worktree concurrency conflicts to a shared message", () => { + const error = Object.assign(new Error("stale worktree thread list"), { + code: "worktree_threads_changed", + }); + expect(friendlyError(error)).toBe( + "The threads linked to this worktree changed. Refresh and try again.", + ); + }); + it("maps helper bootstrap failures to shared messages", () => { expect(friendlyError(new Error("Poracode Helper probe returned HTTP 503."))).toBe( "Poracode Helper is not ready yet (HTTP 503).", diff --git a/src/shared/messages.ts b/src/shared/messages.ts index 1f1e9107d..2b2418946 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -156,6 +156,8 @@ const messages = { "remote.project.runningThreads": "Stop the project's running threads before changing its folder.", "remote.project.experimentsOwned": "Remove the project's experiments before removing the project.", + "remote.worktree.threadsChanged": + "The threads linked to this worktree changed. Refresh and try again.", "remote.session.expired": "Pairing expired — pair again to reconnect.", "remote.server.unreachable": "Can't reach the remote server. Check that it is online, then reconnect it.", @@ -315,6 +317,7 @@ const remoteErrorMessageKeys: Readonly> = { project_not_found: "remote.project.notFound", project_has_running_threads: "remote.project.runningThreads", experiment_owned: "remote.project.experimentsOwned", + worktree_threads_changed: "remote.worktree.threadsChanged", }; function remoteErrorMessageKey(error: unknown): MessageKey | undefined { diff --git a/src/shared/remote/protocol.ts b/src/shared/remote/protocol.ts index f5fecc87f..c78f6e1c8 100644 --- a/src/shared/remote/protocol.ts +++ b/src/shared/remote/protocol.ts @@ -15,10 +15,9 @@ import { persistedCompletedTurnSchema, persistedRuntimeItemSchema } from "../ipc import { gitStateInterestSchema, gitStatePatchSchema, gitStateSnapshotSchema } from "../gitState"; import { sharedSettingsSchema } from "../settings"; -// v4 carries project icon metadata and project-icon update patches, plus -// per-project GitHub account selection through remote Actions calls and project -// updates, so older hosts cannot silently discard either scope. -export const PORACODE_REMOTE_PROTOCOL_VERSION = 4; +// v5 carries the persisted thread archive timestamp. Older hosts cannot retain +// or report it accurately across archive, snapshot, and reconnect boundaries. +export const PORACODE_REMOTE_PROTOCOL_VERSION = 5; export const REMOTE_COMMAND_ID_HEADER = "x-poracode-command-id"; export const remoteAccessScopeSchema = z.enum([