diff --git a/patch_archive_ui.py b/patch_archive_ui.py new file mode 100644 index 0000000..27baf76 --- /dev/null +++ b/patch_archive_ui.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +"""Patch opencode frontend for archive/unarchive UI.""" + +BASE = '/mnt/NVME/Docker/opencode-build/opencode/packages' + +# ── 1. layout.tsx ────────────────────────────────────────────────────────────── +layout = BASE + '/app/src/pages/layout.tsx' +c = open(layout).read() + +# Add unarchiveSession after archiveSession (right after the closing brace) +ARCHIVE_END = ''' if (session.id === params.id) { + if (nextSession) { + navigate(`/${params.dir}/session/${nextSession.id}`) + } else { + navigate(`/${params.dir}/session`) + } + } + }''' + +UNARCHIVE_FN = ''' + + async function unarchiveSession(session: Session) { + const [, setStore] = globalSync.child(session.directory) + await (globalSDK.client.session.update as unknown as Function)({ + directory: session.directory, + sessionID: session.id, + time: { archived: null }, + }) + setStore( + produce((draft) => { + const match = Binary.search(draft.session, session.id, (s) => s.id) + if (match.found) draft.session.splice(match.index, 1) + }), + ) + }''' + +assert c.count(ARCHIVE_END) == 1, f'ARCHIVE_END found {c.count(ARCHIVE_END)} times' +c = c.replace(ARCHIVE_END, ARCHIVE_END + UNARCHIVE_FN, 1) + +# Add unarchiveSession to workspaceSidebarCtx +OLD_CTX1 = ' archiveSession,\n workspaceName,' +NEW_CTX1 = ' archiveSession,\n unarchiveSession,\n workspaceName,' +assert c.count(OLD_CTX1) == 1, f'OLD_CTX1 found {c.count(OLD_CTX1)} times' +c = c.replace(OLD_CTX1, NEW_CTX1, 1) + +# Add to projectSidebarCtx.sessionProps +OLD_CTX2 = ' archiveSession,\n },\n }' +NEW_CTX2 = ' archiveSession,\n unarchiveSession,\n },\n }' +assert c.count(OLD_CTX2) == 1, f'OLD_CTX2 found {c.count(OLD_CTX2)} times' +c = c.replace(OLD_CTX2, NEW_CTX2, 1) + +open(layout, 'w').write(c) +print('layout.tsx patched OK') + +# ── 2. sidebar-workspace.tsx ─────────────────────────────────────────────────── +sw = BASE + '/app/src/pages/layout/sidebar-workspace.tsx' +c = open(sw).read() + +# Expand solid-js import +c = c.replace( + 'import { createEffect, createMemo, For, Show, type Accessor, type JSX } from "solid-js"', + 'import { createEffect, createMemo, createSignal, For, Show, type Accessor, type JSX } from "solid-js"', + 1 +) + +# Add A to router import +c = c.replace( + 'import { useNavigate, useParams } from "@solidjs/router"', + 'import { A, useNavigate, useParams } from "@solidjs/router"', + 1 +) + +# Add useGlobalSDK import +c = c.replace( + 'import { useGlobalSync, useQueryOptions } from "@/context/global-sync"', + 'import { useGlobalSync, useQueryOptions } from "@/context/global-sync"\nimport { useGlobalSDK } from "@/context/global-sdk"', + 1 +) + +# Add sessionTitle import +c = c.replace( + 'import { pathKey } from "@/utils/path-key"', + 'import { pathKey } from "@/utils/path-key"\nimport { sessionTitle } from "@/utils/session-title"', + 1 +) + +# Add unarchiveSession to WorkspaceSidebarContext type +c = c.replace( + ' archiveSession: (session: Session) => Promise\n workspaceName:', + ' archiveSession: (session: Session) => Promise\n unarchiveSession: (session: Session) => Promise\n workspaceName:', + 1 +) + +# Insert ArchivedSessionsSection component before WorkspaceSessionList +ARCHIVED_COMPONENT = '''const ArchivedSessionsSection = (props: { + directory: string + slug: Accessor + unarchiveSession: (session: Session) => Promise + language: ReturnType +}): JSX.Element => { + const globalSDK = useGlobalSDK() + const [open, setOpen] = createSignal(false) + const [archived, setArchived] = createSignal([]) + const [loading, setLoading] = createSignal(false) + + const load = async () => { + setLoading(true) + try { + const result = await (globalSDK.client.session.list as unknown as ( + q: Record, + ) => Promise<{ data: Session[] }>)({ + directory: props.directory, + archived: "true", + }) + setArchived((result?.data ?? []).filter((s) => !!s.time?.archived)) + } finally { + setLoading(false) + } + } + + return ( + { + setOpen(val) + if (val) void load() + }} + > + + + Archived + + + + + + + +
No archived sessions
+
+ + {(session) => ( +
+ + {sessionTitle(session.title)} + +
+ + { + event.preventDefault() + event.stopPropagation() + void props.unarchiveSession(session).then(() => void load()) + }} + /> + +
+
+ )} +
+
+
+ ) +} + +''' + +WORKSPACE_SESSION_LIST_MARKER = 'const WorkspaceSessionList = (' +assert c.count(WORKSPACE_SESSION_LIST_MARKER) == 1 +c = c.replace(WORKSPACE_SESSION_LIST_MARKER, ARCHIVED_COMPONENT + WORKSPACE_SESSION_LIST_MARKER, 1) + +# Add ArchivedSessionsSection inside SortableWorkspace Collapsible.Content +OLD_SORTABLE_CONTENT = ''' + ''' + +NEW_SORTABLE_CONTENT = ''' + + ''' + +assert c.count(OLD_SORTABLE_CONTENT) == 1, f'OLD_SORTABLE_CONTENT found {c.count(OLD_SORTABLE_CONTENT)} times' +c = c.replace(OLD_SORTABLE_CONTENT, NEW_SORTABLE_CONTENT, 1) + +# Add ArchivedSessionsSection inside LocalWorkspace +OLD_LOCAL_CONTENT = ''' false} + loading={loading} + sessions={sessions} + hasMore={hasMore} + loadMore={loadMore} + language={language} + /> + ''' + +NEW_LOCAL_CONTENT = ''' false} + loading={loading} + sessions={sessions} + hasMore={hasMore} + loadMore={loadMore} + language={language} + /> + + ''' + +assert c.count(OLD_LOCAL_CONTENT) == 1, f'OLD_LOCAL_CONTENT found {c.count(OLD_LOCAL_CONTENT)} times' +c = c.replace(OLD_LOCAL_CONTENT, NEW_LOCAL_CONTENT, 1) + +open(sw, 'w').write(c) +print('sidebar-workspace.tsx patched OK') + +print('All frontend patches applied!')