From 577aca04371092c4160daaf2308a6b45d07bee26 Mon Sep 17 00:00:00 2001 From: Serhii Dziupin Date: Tue, 4 Aug 2026 13:21:04 +0300 Subject: [PATCH 1/2] feat: add restore/unarchive for archived sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Archived sessions had no way back to the active list: the only available action was "Delete permanently". Add restore per session (sidebar context menu, Archive page row) and in bulk (sidebar selection bar). The OpenCode server cannot clear time.archived over HTTP — session.update only applies the field for a finite number, so an omitted key is a no-op and null is silently ignored (verified against opencode 1.18.12). Restore therefore writes time.archived = 0: every client-side reader classifies archive state by truthiness, so 0 reads as active in the UI, the event reducer, and the OpenCode app/TUI. The server's time_archived IS NULL list filter still excludes such rows, so the global session cache no longer issues an archived:false request for its active list. Full and per-directory loads now fetch once with the inclusive flag and split client-side via splitGlobalSessionsByArchived, which also halves per-directory refresh requests. Directory bootstrap keeps the server filter because live child stores must not hold archived sessions; a restored session re-enters its live store through the authoritative session.updated event. unarchiveSession/unarchiveSessions follow the archiveSession contract: wait for server confirmation before reconciling stores, runtime-guard every reconciliation, preserve partial batch results, and fail loudly when the server keeps the session archived instead of toasting a successful no-op. Closes #2346 --- .../src/components/session/SessionSidebar.tsx | 9 ++ .../session/sidebar/BulkActionBar.tsx | 18 +++ .../session/sidebar/DOCUMENTATION.md | 2 +- .../session/sidebar/SessionNodeItem.tsx | 9 ++ .../sidebar/hooks/useSessionActions.ts | 14 +++ .../sidebar/hooks/useSidebarBulkActions.ts | 20 ++++ .../ui/src/components/views/ArchiveView.tsx | 25 +++- packages/ui/src/lib/i18n/messages/en.ts | 8 ++ packages/ui/src/lib/i18n/messages/es.ts | 8 ++ packages/ui/src/lib/i18n/messages/fr.ts | 8 ++ packages/ui/src/lib/i18n/messages/ja.ts | 8 ++ packages/ui/src/lib/i18n/messages/ko.ts | 8 ++ packages/ui/src/lib/i18n/messages/pl.ts | 8 ++ packages/ui/src/lib/i18n/messages/pt-BR.ts | 8 ++ packages/ui/src/lib/i18n/messages/uk.ts | 8 ++ packages/ui/src/lib/i18n/messages/zh-CN.ts | 8 ++ packages/ui/src/lib/i18n/messages/zh-TW.ts | 8 ++ packages/ui/src/stores/DOCUMENTATION.md | 4 +- packages/ui/src/stores/globalSessions.test.ts | 36 +++++- packages/ui/src/stores/globalSessions.ts | 35 +++++- .../useGlobalSessionsStore-races.test.ts | 61 ++++++---- .../ui/src/stores/useGlobalSessionsStore.ts | 60 ++++------ packages/ui/src/sync/DOCUMENTATION.md | 30 ++++- packages/ui/src/sync/session-actions.test.ts | 110 ++++++++++++++++++ packages/ui/src/sync/session-actions.ts | 86 ++++++++++++++ packages/ui/src/sync/session-ui-store.test.js | 9 ++ packages/ui/src/sync/session-ui-store.ts | 9 ++ 27 files changed, 548 insertions(+), 69 deletions(-) diff --git a/packages/ui/src/components/session/SessionSidebar.tsx b/packages/ui/src/components/session/SessionSidebar.tsx index d92659d8..acd52fad 100644 --- a/packages/ui/src/components/session/SessionSidebar.tsx +++ b/packages/ui/src/components/session/SessionSidebar.tsx @@ -827,6 +827,8 @@ const SessionSidebarComponent: React.FC = ({ const deleteSessions = useSessionUIStore((state) => state.deleteSessions); const archiveSession = useSessionUIStore((state) => state.archiveSession); const archiveSessions = useSessionUIStore((state) => state.archiveSessions); + const unarchiveSession = useSessionUIStore((state) => state.unarchiveSession); + const unarchiveSessions = useSessionUIStore((state) => state.unarchiveSessions); const { copiedSessionId, @@ -839,6 +841,7 @@ const SessionSidebarComponent: React.FC = ({ handleCopySessionId, handleUnshareSession, handleDeleteSession, + handleRestoreSession, confirmDeleteSession, } = useSessionActions({ mobileVariant, @@ -858,6 +861,7 @@ const SessionSidebarComponent: React.FC = ({ deleteSessions, archiveSession, archiveSessions, + unarchiveSession, childrenMap, showDeletionDialog, setDeleteSessionConfirm, @@ -916,6 +920,7 @@ const SessionSidebarComponent: React.FC = ({ const stableHandleCopySessionId = useStableRenderCallback(handleCopySessionId); const stableHandleUnshareSession = useStableRenderCallback(handleUnshareSession); const stableHandleDeleteSession = useStableRenderCallback(handleDeleteSession); + const stableHandleRestoreSession = useStableRenderCallback(handleRestoreSession); const stableCreateFolderAndStartRename = useStableRenderCallback(createFolderAndStartRename); const showMoreGroupSessions = React.useCallback((groupId: string, currentVisibleCount: number) => { @@ -1579,6 +1584,7 @@ const SessionSidebarComponent: React.FC = ({ createFolderAndStartRename={stableCreateFolderAndStartRename} openContextPanelTab={openContextPanelTab} handleDeleteSession={stableHandleDeleteSession} + handleRestoreSession={stableHandleRestoreSession} mobileVariant={mobileVariant} alwaysShowActions={alwaysShowSidebarActions} renderSessionNode={renderSessionNode} @@ -1752,6 +1758,7 @@ const SessionSidebarComponent: React.FC = ({ handleBulkCreateFolderAndMove, handleBulkRemoveFromFolder, handleBulkDelete, + handleBulkRestore, confirmBulkDelete, } = useSidebarBulkActions({ isInlineEditing, @@ -1762,6 +1769,7 @@ const SessionSidebarComponent: React.FC = ({ removeSessionsFromFolders, createFolderAndStartRename, archiveSessions, + unarchiveSessions, deleteSessions, setBulkDeleteConfirm, }); @@ -1909,6 +1917,7 @@ const SessionSidebarComponent: React.FC = ({ onCreateFolderAndMove={handleBulkCreateFolderAndMove} onRemoveFromFolder={handleBulkRemoveFromFolder} canRemoveFromFolder={bulkCanRemoveFromFolder} + onRestore={handleBulkRestore} onDelete={handleBulkDelete} onDone={handleExitSelectionMode} /> diff --git a/packages/ui/src/components/session/sidebar/BulkActionBar.tsx b/packages/ui/src/components/session/sidebar/BulkActionBar.tsx index f42e3b82..d58e7034 100644 --- a/packages/ui/src/components/session/sidebar/BulkActionBar.tsx +++ b/packages/ui/src/components/session/sidebar/BulkActionBar.tsx @@ -21,6 +21,7 @@ type Props = { onCreateFolderAndMove: () => void; onRemoveFromFolder: () => void; canRemoveFromFolder: boolean; + onRestore: () => void; onDelete: () => void; onDone: () => void; }; @@ -34,6 +35,7 @@ export const BulkActionBar: React.FC = ({ onCreateFolderAndMove, onRemoveFromFolder, canRemoveFromFolder, + onRestore, onDelete, onDone, }) => { @@ -98,6 +100,22 @@ export const BulkActionBar: React.FC = ({ ) : null} + {archivedBucket ? ( + + + + +

{t('sessions.sidebar.bulkActions.restore')}

+
+ ) : null} +