feat(sidebar): replicate WorkBuddy task context menu (TODO 01)

Replace the sidebar delete button with the original task menu: pin/unpin,
rename, open in a separate window, open folder, archive and delete.

Source: REA ev_f5e9d71a676c453daab40928acfb33aeabae68ba57ffda342380cead7f1a1fe2
(WorkBuddy 5.8.3 task menu, open state) supplies the container/item geometry,
the two dividers, danger colour and every item icon.

Verified by scripts/verify-task-menu.cjs: 33 assertions in a real isolated
Electron app (real preload/IPC/filesystem), including menu geometry, anchor
offset, pin/rename/archive persistence, open-folder target and the separate
window query. typecheck, test:conversation and build pass.
This commit is contained in:
xiaohan.yuan committed 2026-10-17 04:43:56 +08:00
1 parent e365e830cf
commit e335588826
15 files changed
+821 -31

No files matched your search

+34 -2
View File
@@ -93,8 +93,40 @@ async function persist(): Promise<void> {
export async function listSessions(): Promise<SessionMeta[]> {
const store = await load()
return [...store.sessions]
.sort((a, b) => b.updatedAt - a.updatedAt)
.map(({ id, title, createdAt, updatedAt, workspaceId, cwd, modelId, expertId, agentMode: mode, thinkingLevel: level }) => ({ id, title, createdAt, updatedAt, workspaceId, cwd, modelId, expertId, agentMode: agentMode(mode), thinkingLevel: level }))
// 置顶任务优先(最近置顶在前),其余按更新时间倒序。
.sort((a, b) => Number(b.pinnedAt ?? 0) - Number(a.pinnedAt ?? 0) || b.updatedAt - a.updatedAt)
.map(({ id, title, createdAt, updatedAt, workspaceId, cwd, modelId, expertId, agentMode: mode, thinkingLevel: level, pinnedAt, archivedAt }) => ({ id, title, createdAt, updatedAt, workspaceId, cwd, modelId, expertId, agentMode: agentMode(mode), thinkingLevel: level, pinnedAt, archivedAt }))
}
/** 侧栏可见任务:默认排除已归档;`includeArchived` 供归档视图使用。 */
export async function listVisibleSessions(includeArchived = false): Promise<SessionMeta[]> {
const sessions = await listSessions()
return includeArchived ? sessions : sessions.filter((session) => session.archivedAt === undefined)
}
export async function setSessionPinned(sessionId: string, pinned: boolean): Promise<SessionMeta> {
const store = await load()
const session = store.sessions.find((item) => item.id === sessionId)
if (!session) throw new Error('会话不存在')
const previous = session.pinnedAt
session.pinnedAt = pinned ? Date.now() : undefined
try { await persist() } catch (error) { session.pinnedAt = previous; throw error }
return meta(session)
}
export async function setSessionArchived(sessionId: string, archived: boolean): Promise<SessionMeta> {
const store = await load()
const session = store.sessions.find((item) => item.id === sessionId)
if (!session) throw new Error('会话不存在')
const previous = session.archivedAt
session.archivedAt = archived ? Date.now() : undefined
try { await persist() } catch (error) { session.archivedAt = previous; throw error }
return meta(session)
}
function meta(session: SessionRecord): SessionMeta {
const { id, title, createdAt, updatedAt, workspaceId, cwd, modelId, expertId, agentMode: mode, thinkingLevel: level, pinnedAt, archivedAt } = session
return { id, title, createdAt, updatedAt, workspaceId, cwd, modelId, expertId, agentMode: agentMode(mode), thinkingLevel: level, pinnedAt, archivedAt }
}
export async function createSession(title = '新对话', context: SessionContext = {}): Promise<SessionMeta> {