#!/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!')