@xrn1997/dsh-session-delete 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -25
- package/lib/client.js +972 -588
- package/lib/index.js +553 -286
- package/lib/types/api/dispatch.d.ts +2 -2
- package/lib/types/api/route-table.d.ts +1 -1
- package/lib/types/api/trash-rows.d.ts +13 -6
- package/lib/types/client/batch-bar.d.ts +6 -6
- package/lib/types/client/batch-confirm.d.ts +5 -3
- package/lib/types/client/batch-menu-item.d.ts +2 -2
- package/lib/types/client/batch-select.d.ts +7 -50
- package/lib/types/client/batch-selection-store.d.ts +30 -0
- package/lib/types/client/copy.d.ts +4 -1
- package/lib/types/client/delete-confirm.d.ts +2 -0
- package/lib/types/client/host-rows.d.ts +35 -0
- package/lib/types/client/main-view.d.ts +37 -0
- package/lib/types/client/receipt.d.ts +43 -0
- package/lib/types/client/remote.d.ts +16 -6
- package/lib/types/client/store.d.ts +17 -0
- package/lib/types/client/trash-panel.d.ts +1 -1
- package/lib/types/client/trash-store.d.ts +2 -0
- package/lib/types/client/ui/icons.d.ts +3 -3
- package/lib/types/client/undo-toast.d.ts +19 -3
- package/lib/types/client/use-column-resize.d.ts +2 -2
- package/lib/types/client/use-panel-width.d.ts +1 -1
- package/lib/types/client/use-trash-panel-controller.d.ts +55 -0
- package/lib/types/client/write-commit.d.ts +10 -0
- package/lib/types/deps.d.ts +14 -0
- package/lib/types/index.d.ts +6 -13
- package/lib/types/log/zstd-frames.d.ts +1 -1
- package/lib/types/ports/agent-handles.d.ts +25 -0
- package/lib/types/ports/home.d.ts +19 -0
- package/lib/types/ports/host-port.d.ts +58 -28
- package/lib/types/remote/map-error.d.ts +2 -2
- package/lib/types/shared/wire.d.ts +11 -9
- package/lib/types/trash/detail.d.ts +1 -1
- package/lib/types/trash/entry.d.ts +4 -4
- package/lib/types/trash/manifest.d.ts +2 -1
- package/lib/types/trash/reconcile.d.ts +2 -2
- package/lib/types/trash/storage-manifest.d.ts +12 -0
- package/lib/types/verbs/batch.d.ts +1 -1
- package/lib/types/verbs/delete-session.d.ts +8 -11
- package/lib/types/verbs/errors.d.ts +4 -0
- package/lib/types/verbs/member-journal.d.ts +4 -3
- package/lib/types/verbs/member-presence.d.ts +35 -0
- package/lib/types/verbs/orphan-subagents.d.ts +14 -38
- package/lib/types/verbs/permanent-removal.d.ts +113 -0
- package/lib/types/verbs/purge-trash.d.ts +9 -5
- package/lib/types/verbs/restore-session.d.ts +6 -6
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* 仍住这里,因为它们是**传输**的事。
|
|
16
16
|
*
|
|
17
17
|
* 信封(口径 ②):`{ok:true,value}` / `{ok:false,error:{code,message,details?}}`。
|
|
18
|
-
* `error.code` 走 `src/remote/map-error.ts`
|
|
18
|
+
* `error.code` 走 `src/remote/map-error.ts` 的**同一张表**(src/shared/wire.ts 的五个码),原文进
|
|
19
19
|
* `error.message`,结构化附带物进可选的 `error.details`。
|
|
20
20
|
*
|
|
21
21
|
* HTTP 状态不是第二套语义(口径 ③):**操作失败**时状态一律 200(含 `ok:false`)——我们与浏览器半之间的
|
|
@@ -31,5 +31,5 @@
|
|
|
31
31
|
* 由路由表说,不在这里列)。
|
|
32
32
|
*/
|
|
33
33
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
34
|
-
import type { DeleteDeps } from '../
|
|
34
|
+
import type { DeleteDeps } from '../deps.js';
|
|
35
35
|
export declare function createApiHandler(deps: DeleteDeps): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* `wire.ts`(两半的共同契约),这张表只回答"怎么处理"。
|
|
14
14
|
*/
|
|
15
15
|
import { ROUTES, type ApplyAction } from '../shared/wire.js';
|
|
16
|
-
import type { DeleteDeps } from '../
|
|
16
|
+
import type { DeleteDeps } from '../deps.js';
|
|
17
17
|
/** 请求体:`json` 才读下来(GET 一律不读;读两遍会把流消费掉)。 */
|
|
18
18
|
export type BodyKind = 'none' | 'json';
|
|
19
19
|
/** 到达序:`pure` 不进链;`chained` 进链;函数形式给 `/apply` 用(`count` 是纯读)。 */
|
|
@@ -2,23 +2,30 @@
|
|
|
2
2
|
* `GET /list` 那一行的装配:清册行(`TrashEntry`)→ 线上那一行(`TrashRowWire`),外加组视图的几个只读数。
|
|
3
3
|
*
|
|
4
4
|
* 它从 `api/dispatch.ts` 里搬出来,因为路由层的事是**传输**(栅栏、方法、纯度、信封),而"一行里放什么"
|
|
5
|
-
* 是这一层的事。「折进谁、谁露面」那条判据读 `trash/groups.ts`(ADR-0001
|
|
5
|
+
* 是这一层的事。「折进谁、谁露面」那条判据读 `trash/groups.ts`(ADR-0001);一组会释放多少、盘上还剩谁
|
|
6
|
+
* 归 `verbs/permanent-removal.ts` 的只读计划 —— 这里只做装配,不再自己枚举盘上后代。
|
|
6
7
|
*/
|
|
7
|
-
import type {
|
|
8
|
+
import type { CoreDeps } from '../deps.js';
|
|
8
9
|
import type { TrashEntry } from '../trash/entry.js';
|
|
9
10
|
import type { TrashRowWire } from '../shared/wire.js';
|
|
10
|
-
/**
|
|
11
|
-
|
|
11
|
+
/** 组视图那几个只读数的容器(行投影的第二个入参)。`releaseBytes` 必填(计划里实测好的那一格);
|
|
12
|
+
* 其余缺席 = 这次没算,不是 0。 */
|
|
13
|
+
type TrashRowReadouts = {
|
|
14
|
+
readonly releaseBytes: number;
|
|
15
|
+
} & Partial<Pick<TrashRowWire, 'subagentCount' | 'subagentBytes' | 'strandedSubagents' | 'strandedBytes' | 'workspaceMissing'>>;
|
|
12
16
|
/**
|
|
13
17
|
* 清册那一行 → 线上那一行。**显式字面量、不用 `...row`**:线上形状多一个必填字段时,这里编译不过,
|
|
14
18
|
* 于是"线上多了一格却没人填"不可能悄悄发生(`...row` 会把它带过去,看上去一切正常)。
|
|
15
19
|
*/
|
|
16
|
-
export declare function toWire(row: TrashEntry, readouts
|
|
20
|
+
export declare function toWire(row: TrashEntry, readouts: TrashRowReadouts): TrashRowWire;
|
|
17
21
|
/**
|
|
18
22
|
* 把清册的全部行装配成「一行 = 一组」:孩子折进父那一行、不单独列(宿主的客户端本来就藏孩子,
|
|
19
23
|
* 我们若单列就会冒出一堆点不到的行);父行不在时孩子自己站出来占一行,否则它既看不见也清不掉。
|
|
20
24
|
* 这些派生值只加在这次回信上,**不落清册**——清册该记的是文件里真有的事实,而"还剩几个孩子"
|
|
21
25
|
* "工作区还在不在"是随盘上随时变的读数。取数失败照抛(响亮失败,不端一份少了这栏的清单当没事发生)。
|
|
26
|
+
*
|
|
27
|
+
* 一组要释放多少、盘上还剩谁**只读计划一处算**(`planVisibleTrash`,与 purge 同一条口径);
|
|
28
|
+
* 这里不再自己枚举盘上后代,也不碰 `isLive`(活体准入只属于写动词)。
|
|
22
29
|
*/
|
|
23
|
-
export declare function assembleTrashRows(deps:
|
|
30
|
+
export declare function assembleTrashRows(deps: CoreDeps, rows: readonly TrashEntry[]): Promise<TrashRowWire[]>;
|
|
24
31
|
export {};
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 批量删除的浮条(2026-10-17 `6f24fd4`):**多选模式内常驻**(0 条也渲染,既显示模式又给退出入口),
|
|
3
3
|
* 汇总「已选 N 条」+(读得到时)「含 K 个子会话」;「查看已选」展开限高可滚动的标题清单
|
|
4
4
|
* (逐条可移除),回答"到底选了哪些"——清单默认收起,不把底部输入区盖住。
|
|
5
|
-
* 模式内的整行点击守卫也挂在这棵常驻宿主上(见 `
|
|
5
|
+
* 模式内的整行点击守卫也挂在这棵常驻宿主上(见 `host-rows.ts` 的 `useSelectionRowClickGuard`)。
|
|
6
6
|
*
|
|
7
|
-
* 为什么单独一个文件而不是并进 `batch-select.tsx`:那份是"
|
|
8
|
-
*
|
|
7
|
+
* 为什么单独一个文件而不是并进 `batch-select.tsx`:那份是"行开关控件",而浮条要点开确认框
|
|
8
|
+
* (要 import 它的现场)——两个方向合到一个文件上就是一圈。浮条住在
|
|
9
9
|
* `shell.overlay` 上,与单删的撤销提示/确认框同类(槽位原文:"a toast stack or a status pill
|
|
10
10
|
* all belong here")。
|
|
11
11
|
*
|
|
12
12
|
* 那个 K 是**勾选变化后防抖 300ms 的一次批量 count**(`/apply` 的 `action:'count'`,一组 id 一次),
|
|
13
|
-
*
|
|
14
|
-
* 读不到就整句不说——K
|
|
13
|
+
* 并且带**序号守卫**:在飞的旧响应不许盖掉新名单的读数(与 2026-10-17 `c66f082` 那条 `reloadSeq` 同一个道理)。
|
|
14
|
+
* 读不到就整句不说——K 是合计,缺一条就是假数(「不许拿 0 冒充没有」)。
|
|
15
15
|
*/
|
|
16
16
|
import { type ReactElement } from 'react';
|
|
17
17
|
import { type Translate } from './copy.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 批量删除的确认框(2026-10-17)——挂在 `shell.overlay` 上的宿主,加它读的那个模块级现场。
|
|
3
3
|
*
|
|
4
|
-
* 为什么又开一个文件而不是把 `delete-confirm.tsx`
|
|
4
|
+
* 为什么又开一个文件而不是把 `delete-confirm.tsx` 撑大:单删那张框是**已交付的面**(docs/ui-spec.md 状态 ②,
|
|
5
5
|
* 用例钉着),批量形与它只有三件不同——标题随条数、目标卡逐条列、执行是一次批量 `apply`。共用一个壳
|
|
6
6
|
* 意味着单删的 `busy` / 失败面要跟着学批量形,那是拿已交付的行为去换省一个文件。卡片样式(`TARGET_CARD`)
|
|
7
7
|
* 与 hint、子会话那两句**复用**单删的,一句不造第二个键。
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* `shell.overlay` 上——它必须活在行开关那棵子树之外(槽位行随宿主重排而卸载,见 `delete-confirm.tsx` 头注)。
|
|
11
11
|
*/
|
|
12
12
|
import { type ReactElement } from 'react';
|
|
13
|
-
import { type PickItem } from './batch-select.js';
|
|
14
13
|
import { type Translate } from './copy.js';
|
|
15
14
|
import { type SessionDeleteRemote } from './remote.js';
|
|
15
|
+
import { type PickItem } from './batch-selection-store.js';
|
|
16
16
|
export interface BatchDeleteRequest {
|
|
17
17
|
/** 每次发布递增:换一批就重挂载,busy / 失败面不带着上一批的记忆。 */
|
|
18
18
|
seq: number;
|
|
@@ -23,6 +23,8 @@ export declare function requestDeleteBatch(items?: readonly PickItem[]): void;
|
|
|
23
23
|
export declare function clearDeleteBatchRequest(): void;
|
|
24
24
|
/** 供浮条与装配面订阅。 */
|
|
25
25
|
export declare function useDeleteBatchRequest(): BatchDeleteRequest | null;
|
|
26
|
+
/** 测试专用:现场与 seq 归零,用例之间互不串味。 */
|
|
27
|
+
export declare function resetBatchConfirm(): void;
|
|
26
28
|
export type BatchConfirmDeps = Pick<SessionDeleteRemote, 'applyOutcomes'>;
|
|
27
29
|
/** 常驻的批量确认框宿主。没有待确认的批量删除时什么都不渲染。 */
|
|
28
30
|
export declare function BatchConfirmHost({ deps, t, }: {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 会话「⋯」菜单里的「多选」入口(2026-10-17)。
|
|
3
3
|
*
|
|
4
4
|
* 常驻勾选框被用户打回后,入口改成显式模式:按下这一行才进入多选态(再按一次退出),
|
|
5
|
-
* 模式内才渲染勾选框、才把整行点击变成"勾/取消"(守卫在 `
|
|
5
|
+
* 模式内才渲染勾选框、才把整行点击变成"勾/取消"(守卫在 `host-rows.ts`)。
|
|
6
6
|
* 菜单的开关归 owner(与 `delete-menu-item.tsx` 同一条契约):按下后先切模式、再收菜单。
|
|
7
7
|
*
|
|
8
8
|
* 文案复用回收站工具栏那两句(`trash.toolbar.select` / `selectExit`)——同一个"多选/退出多选"
|
|
@@ -1,69 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 宿主列表批量删除的**控件面**(2026-10-17,2026-10-20 拆出状态与行适配):行首勾选框、行尾兜底开关,
|
|
3
|
+
* 以及确认框/浮条读的那一份计数问句。
|
|
3
4
|
*
|
|
4
5
|
* 入口在每行的「⋯」菜单里(`batch-menu-item.tsx`):**按下才进入多选模式**,模式内才渲染勾选框、
|
|
5
|
-
* 才把整行点击变成"勾/取消"
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 四处隔着槽位边界,只有模块级 store 能让他们看见同一份名单。状态源用 React 自带的
|
|
9
|
-
* `useSyncExternalStore`,不为此引新的状态库。
|
|
6
|
+
* 才把整行点击变成"勾/取消";退出模式清空名单。选中集只住 `batch-selection-store.ts`(模式 + 名单一份
|
|
7
|
+
* 合并快照);"哪些行现在可见、点行算不算选中"只住 `host-rows.ts`。四处隔着槽位边界,只有模块级
|
|
8
|
+
* store 能让他们看见同一份名单;本文件不再持有任何现场。
|
|
10
9
|
*
|
|
11
10
|
* **为什么主勾选在行首**:宿主对行尾 `.rowActions` 是 `display:none`,只有 `row:hover` / 菜单打开
|
|
12
11
|
* 才出现(app.asar 0.2.0-rc.2 的 Rows 渲染);行首 16px 格在无状态点时是空的,模式内可见。
|
|
13
12
|
* 宿主在「有状态点(运行中/待确认/未读完成)」与归档行不渲染行首格 ⇒ 这些行走行尾悬停兜底,
|
|
14
|
-
* 但模式内**点整行**也能选(`useSelectionRowClickGuard`),所以不依赖那颗小框。
|
|
13
|
+
* 但模式内**点整行**也能选(`host-rows.ts` 的 `useSelectionRowClickGuard`),所以不依赖那颗小框。
|
|
15
14
|
* 两条勾选不会同时出现(`ui.css` 的 `:has` 兜底隐藏规则,靠宿主行的 `data-row-key`)。
|
|
16
|
-
*
|
|
17
|
-
* 名单只收槽位投影原样给的 `sessionId` / `displayTitle`(浏览器半不自己拼 id),**顺序 = 勾选顺序**:
|
|
18
|
-
* 宿主列表的 DOM 序不归我们读,`/apply` 那条「保持输入顺序」吃到的因此就是勾选序。
|
|
19
15
|
*/
|
|
20
16
|
import { type ReactElement } from 'react';
|
|
21
17
|
import { type Translate } from './copy.js';
|
|
22
18
|
import { type SessionDeleteRemote } from './remote.js';
|
|
23
|
-
export interface PickItem {
|
|
24
|
-
/** 宿主槽位投影的会话 id,原样透给 `applyOutcomes('delete', …)`。 */
|
|
25
|
-
sessionId: string;
|
|
26
|
-
/** 删除那一刻的会话名(投影的 `displayTitle`):只用于文案与确认框,不参与请求。 */
|
|
27
|
-
title: string;
|
|
28
|
-
}
|
|
29
|
-
export declare function getPicks(): readonly PickItem[];
|
|
30
|
-
export declare function isSelecting(): boolean;
|
|
31
|
-
/** 「⋯ → 多选」按下时进入;重复进入是幂等的。 */
|
|
32
|
-
export declare function enterSelection(): void;
|
|
33
|
-
/** 退出模式并清空名单:多选态和选中集是同一次交互的两半,退出后不该留下看不见的名单。 */
|
|
34
|
-
export declare function exitSelection(): void;
|
|
35
|
-
export declare function useSelecting(): boolean;
|
|
36
|
-
/** 翻转一条:没选过就排队尾,选过就摘掉(列表里永远不会有重复 id)。 */
|
|
37
|
-
export declare function togglePick(item: PickItem): void;
|
|
38
|
-
export declare function clearPicks(): void;
|
|
39
|
-
/** 全选:把当前可见可选行里还没选的按 DOM 序追加;原有选中保持在前。 */
|
|
40
|
-
export declare function selectAllVisible(): void;
|
|
41
|
-
/**
|
|
42
|
-
* 同一个切换口该显示「全选」还是「取消全选」:
|
|
43
|
-
* - 有选中、且当前可见可选行全都选中 ⇒ 取消全选;
|
|
44
|
-
* - 有选中、但当前没有可见可选行(全折叠/过滤掉)⇒ 只能清空,也显示取消全选;
|
|
45
|
-
* - 其余(含 0 条)⇒ 全选。
|
|
46
|
-
*/
|
|
47
|
-
export declare function allVisibleSelected(): boolean;
|
|
48
|
-
/** 执行后收缩:只留失败名单(空数组 = 全清)。成功的随通告从侧栏消失,不该留在名单里。 */
|
|
49
|
-
export declare function shrinkPicksToFailures(failedSessionIds: readonly string[]): void;
|
|
50
|
-
export declare function usePicks(): readonly PickItem[];
|
|
51
|
-
/**
|
|
52
|
-
* 多选态内的整行点击守卫(document 捕获阶段)。
|
|
53
|
-
*
|
|
54
|
-
* 为什么必须在这里拦:宿主的会话行自己带 `onClick = open(sessionId)`,第三方没有官方的
|
|
55
|
-
* "别切会话"开关;模式内点行要变成"勾/取消",只能抢在 React 根监听之前把事件吃掉。
|
|
56
|
-
* 边界:行里的按钮 / 链接 / 输入 / 菜单项一律放行(否则 ⋯ 菜单、归档、置顶、单删都点不动);
|
|
57
|
-
* 双击的第二下与 dblclick 不翻转(第一下已经选中,第二下不能又反掉,也不能打开宿主的重命名)。
|
|
58
|
-
* 认行靠 `data-row-key="session:…"`——宿主将来改掉这个属性,最差是模式内点行不生效,
|
|
59
|
-
* 勾选框本身仍在。
|
|
60
|
-
*/
|
|
61
|
-
export declare function useSelectionRowClickGuard(): void;
|
|
62
19
|
/**
|
|
63
20
|
* 一组会话的子会话计数(`/apply` 的 `action:'count'`,纯读、不进串行链)。
|
|
64
21
|
*
|
|
65
22
|
* **一条读不到就整份返回 null** ——界面上那两个数(浮条的 K、确认框的 K 与逐条 M)都是合计,
|
|
66
|
-
* 缺一条就是假数;「不许拿 0
|
|
23
|
+
* 缺一条就是假数;「不许拿 0 冒充没有」。
|
|
67
24
|
*/
|
|
68
25
|
export declare function fetchCounts(deps: Pick<SessionDeleteRemote, 'applyOutcomes'>, ids: readonly string[]): Promise<Map<string, number> | null>;
|
|
69
26
|
export interface BatchPickCheckboxProps {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface PickItem {
|
|
2
|
+
/** 宿主槽位投影的会话 id,原样透给 `applyOutcomes('delete', …)`。 */
|
|
3
|
+
readonly sessionId: string;
|
|
4
|
+
/** 删除那一刻的会话名(投影的 `displayTitle`):只用于文案与确认框,不参与请求。 */
|
|
5
|
+
readonly title: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SelectionState {
|
|
8
|
+
/** 多选模式:入口在每一行的「⋯」菜单里;只有模式内才渲染勾选框、才吃整行点击。 */
|
|
9
|
+
readonly selecting: boolean;
|
|
10
|
+
readonly picks: readonly PickItem[];
|
|
11
|
+
}
|
|
12
|
+
export declare function getSelection(): SelectionState;
|
|
13
|
+
export declare function getPicks(): readonly PickItem[];
|
|
14
|
+
export declare function isSelecting(): boolean;
|
|
15
|
+
/** 供组件订阅:模式与名单是同一次交互的两半,读它们的组件订同一份快照。 */
|
|
16
|
+
export declare function useSelection(): SelectionState;
|
|
17
|
+
/** 「⋯ → 多选」按下时进入;重复进入是幂等的(同一份快照,不发通告)。 */
|
|
18
|
+
export declare function enterSelection(): void;
|
|
19
|
+
/** 退出模式并清空名单:多选态和选中集是同一次交互的两半,退出后不该留下看不见的名单。 */
|
|
20
|
+
export declare function exitSelection(): void;
|
|
21
|
+
/** 翻转一条:没选过就排队尾,选过就摘掉(列表里永远不会有重复 id)。 */
|
|
22
|
+
export declare function togglePick(item: PickItem): void;
|
|
23
|
+
/** 清空名单但**留在多选态**(「取消全选」那颗);已经空了就不发通告。 */
|
|
24
|
+
export declare function clearPicks(): void;
|
|
25
|
+
/** 只追加还没选过的条目;名单序 = 原有选中在前、新条目按传入序接在后面。没有新条目就不发通告。 */
|
|
26
|
+
export declare function addPicks(items: readonly PickItem[]): void;
|
|
27
|
+
/** 执行后收缩:只留失败名单(空数组 = 全清)。成功的随通告从侧栏消失,不该留在名单里。 */
|
|
28
|
+
export declare function shrinkPicksToFailures(failedSessionIds: readonly string[]): void;
|
|
29
|
+
/** 测试专用:模式与名单一起回初值(订阅者随组件卸载自行退订)。 */
|
|
30
|
+
export declare function resetSelection(): void;
|
|
@@ -30,6 +30,8 @@ export declare function requestDelete(input: {
|
|
|
30
30
|
export declare function clearDeleteRequest(): void;
|
|
31
31
|
/** 供宿主组件订阅。 */
|
|
32
32
|
export declare function useDeleteRequest(): DeleteRequest | null;
|
|
33
|
+
/** 测试专用:现场与 seq 归零,用例之间互不串味。 */
|
|
34
|
+
export declare function resetDeleteRequest(): void;
|
|
33
35
|
/**
|
|
34
36
|
* 常驻的确认框宿主。挂 `shell.overlay`(框架级浮层;槽位说明原文 "a toast stack or a status
|
|
35
37
|
* pill all belong here"):没有待确认的删除时什么都不渲染。
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type PickItem } from './batch-selection-store.js';
|
|
2
|
+
export interface HostRowInfo {
|
|
3
|
+
/** 槽位投影的 `displayTitle`(行首槽只投 id,所以守卫靠行尾那颗常驻登记)。 */
|
|
4
|
+
readonly title: string;
|
|
5
|
+
/** 在跑的行点整行也不选:与置灰的勾选框同口径(级联遇在跑成员整组拒绝)。 */
|
|
6
|
+
readonly live: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** 行尾兜底勾选开关登记一行:模式内与模式外都要登记(守卫进模式的那一帧就得有标题)。 */
|
|
9
|
+
export declare function registerHostRowInfo(sessionId: string, info: HostRowInfo): void;
|
|
10
|
+
/**
|
|
11
|
+
* 「当前可见」的可选行:侧栏里**已渲染**的会话行(`data-row-key="session:…"`)——折叠组、
|
|
12
|
+
* 归档过滤外的行不在 DOM 里,宿主隐藏的 subagent 也不在;在跑的行跳过(与置灰勾选框同口径)。
|
|
13
|
+
* 标题/运行态来自行尾兜底登记的 `rowInfo`(模式内它对每个非空行都渲染);DOM 序就是侧栏顺序。
|
|
14
|
+
*/
|
|
15
|
+
export declare function readVisibleSelectableRows(): PickItem[];
|
|
16
|
+
/** 全选:把当前可见可选行按 DOM 序追加;已在名单里的由 `addPicks` 自己跳过,原有选中保持在前。 */
|
|
17
|
+
export declare function selectAllVisible(): void;
|
|
18
|
+
/**
|
|
19
|
+
* 同一个切换口该显示「全选」还是「取消全选」:
|
|
20
|
+
* - 有选中、且当前可见可选行全都选中 ⇒ 取消全选;
|
|
21
|
+
* - 有选中、但当前没有可见可选行(全折叠/过滤掉)⇒ 只能清空,也显示取消全选;
|
|
22
|
+
* - 其余(含 0 条)⇒ 全选。
|
|
23
|
+
*/
|
|
24
|
+
export declare function allVisibleSelected(): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* 多选态内的整行点击守卫(document 捕获阶段)。
|
|
27
|
+
*
|
|
28
|
+
* 边界:行里的按钮 / 链接 / 输入 / 菜单项一律放行(否则 ⋯ 菜单、归档、置顶、单删都点不动);
|
|
29
|
+
* 双击的第二下与 dblclick 不翻转(第一下已经选中,第二下不能又反掉,也不能打开宿主的重命名)。
|
|
30
|
+
* 认行靠 `data-row-key="session:…"`——宿主将来改掉这个属性,最差是模式内点行不生效,
|
|
31
|
+
* 勾选框本身仍在。模式外不挂监听:这时点行必须还是宿主的"打开会话"。
|
|
32
|
+
*/
|
|
33
|
+
export declare function useSelectionRowClickGuard(): void;
|
|
34
|
+
/** 测试专用:登记表清空(会话 id 不跨用例复用,名字/运行态别串味)。 */
|
|
35
|
+
export declare function resetHostRows(): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 宿主**主视图**的一处窄面:删掉当前打开的会话时把它关掉,撤销恢复后把它重开。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要:宿主客户端的常驻 `Session` 实例把 `removed` 记成**粘性**状态
|
|
5
|
+
* (`dsh-api-session-controller/lib/client.js` 的 `handleRemoved()` 只置位;`handleSessionAdded()`
|
|
6
|
+
* 只 merge 摘要,从不清它)。我们发 `api-session/removed` 之后,正在看的那个会话会一直停在
|
|
7
|
+
* `removed=true`:输入框禁用、占位文案是「会话不可用」。恢复再发 `api-session/added` 也不会把它
|
|
8
|
+
* 清掉——**除非把常驻实例本身丢掉**。宿主自己的归档走的就是这条路:`clearArchivedCurrent()`
|
|
9
|
+
* 发现当前会话进了归档集就 `clearMain()`(`dsh-client-ui-workspace/lib/client.js`)。
|
|
10
|
+
*
|
|
11
|
+
* 所以关闭/重开是**一段生命周期**(2026-10-21),不是一个 id:删除成功后 `afterDelete` 关掉当前
|
|
12
|
+
* 主视图并记下"这一条是我们关掉的";撤销成功后 `afterRestore` 只重开自己记过的那些。调用方不再
|
|
13
|
+
* 各自判定该不该关、也不再把会话 id 塞进撤销提示传一圈——判据全在这一处。两次重开排队时只有最新
|
|
14
|
+
* 那条会打开(token 守卫),`resetMainView()` 清掉这份记忆并让在等的重开作废。
|
|
15
|
+
*
|
|
16
|
+
* **不 import 宿主客户端包**(AGENTS.md 红线):只通过 `ctx.get('uiWorkspace')` 可选读服务,
|
|
17
|
+
* 形状不符/服务缺席都安静退回,绝不影响插件激活。字段与方法名来自上面两个包的产物原文;
|
|
18
|
+
* 宿主换代时先复核 `UiWorkspaceService` 的 `selection` / `sessions` / `clearMain` / `openSession`。
|
|
19
|
+
*/
|
|
20
|
+
import type { ClientContextLike } from './context.js';
|
|
21
|
+
/** 入口激活时绑一次;之后每次调用再读服务(客户端插件激活顺序早于 `uiWorkspace` 也没关系)。 */
|
|
22
|
+
export declare function bindMainView(ctx: ClientContextLike): void;
|
|
23
|
+
/** 测试专用:模块级现场归零(记忆清空、在等的重开作废),用例之间互不串味。 */
|
|
24
|
+
export declare function resetMainView(): void;
|
|
25
|
+
/**
|
|
26
|
+
* 删除成功后调用:当前主视图若在这批里,把它关掉并记下"这一条是我们关掉的"(供撤销重开)。
|
|
27
|
+
* 关不掉/不是当前会话都什么都不做——这只是界面收尾,绝不让删除结果变红。
|
|
28
|
+
*/
|
|
29
|
+
export declare function afterDelete(sessionIds: readonly string[]): void;
|
|
30
|
+
/**
|
|
31
|
+
* 撤销恢复成功后调用:把**我们自己关掉的**那几个会话重新打开(只认 `afterDelete` 记过的)。
|
|
32
|
+
*
|
|
33
|
+
* 一批里只有最后一条会开(`targets[targets.length - 1]`):主视图一次只装一个会话,而撤销恢复
|
|
34
|
+
* 的成功条数可能多于一条;`openWhenListed` 的 token 守卫保证前一条的等待不会后落地抢焦点。
|
|
35
|
+
* 任何更新的删除/恢复都会先作废旧等待;这里只负责「这一次要开哪一条」。
|
|
36
|
+
*/
|
|
37
|
+
export declare function afterRestore(sessionIds: readonly string[]): void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type ApplyAction, type ApplyOutcome, type ApplyValueByAction, type RemoteFailure } from '../shared/wire.js';
|
|
2
|
+
/**
|
|
3
|
+
* 一条 `/apply` 结果对一个请求 id 的含义。各写入面不许再各自推导:
|
|
4
|
+
* 缺 id 是通道错误,`null` 是幂等成功,orphan-clean 的 `not-found` 是中性提示而非失败。
|
|
5
|
+
*
|
|
6
|
+
* 各 action 可能出现的 kind(类型面故意保持同一个联合,消费方按 action 自己收窄):
|
|
7
|
+
* - delete / restore:`ok` / `already-gone` / `failed` / `missing`
|
|
8
|
+
* - count / purge:`ok` / `failed` / `missing`
|
|
9
|
+
* - orphan-clean:`ok` / `neutral` / `failed` / `missing`
|
|
10
|
+
*/
|
|
11
|
+
export type ReceiptVerdict<A extends ApplyAction> = {
|
|
12
|
+
readonly kind: 'ok';
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly value: ApplyValueByAction[A];
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: 'already-gone';
|
|
17
|
+
readonly id: string;
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: 'failed';
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly error: RemoteFailure;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: 'neutral';
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly error: RemoteFailure;
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: 'missing';
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly message: string;
|
|
30
|
+
};
|
|
31
|
+
export interface Receipt<A extends ApplyAction> {
|
|
32
|
+
readonly verdicts: readonly ReceiptVerdict<A>[];
|
|
33
|
+
readonly byId: ReadonlyMap<string, ReceiptVerdict<A>>;
|
|
34
|
+
}
|
|
35
|
+
export declare function missingOutcomeMessage(action: ApplyAction, id: string): string;
|
|
36
|
+
/** 三种“这一条没成”的 verdict;`failureMessage` 是它们原文取值的唯一入口。 */
|
|
37
|
+
export type FailureVerdict<A extends ApplyAction> = Extract<ReceiptVerdict<A>, {
|
|
38
|
+
kind: 'failed' | 'neutral' | 'missing';
|
|
39
|
+
}>;
|
|
40
|
+
export declare function isFailureVerdict<A extends ApplyAction>(verdict: ReceiptVerdict<A>): verdict is FailureVerdict<A>;
|
|
41
|
+
/** failed / neutral 取服务端原文 `error.message`;missing 取通道自己写的那句。 */
|
|
42
|
+
export declare function failureMessage<A extends ApplyAction>(verdict: FailureVerdict<A>): string;
|
|
43
|
+
export declare function readReceipt<A extends ApplyAction>(action: A, requestedIds: readonly string[], outcomes: readonly ApplyOutcome[]): Receipt<A>;
|
|
@@ -71,7 +71,7 @@ export type RemoteOutcome<T> = {
|
|
|
71
71
|
* code,其余按原文兜底)。
|
|
72
72
|
*/
|
|
73
73
|
export declare function callRemote<T>(run: () => Promise<T>): Promise<RemoteOutcome<T>>;
|
|
74
|
-
/** 本插件前缀路由那套**响应信封**的窄镜像(不是宿主服务 `ctx.remote.*`——那条 typert
|
|
74
|
+
/** 本插件前缀路由那套**响应信封**的窄镜像(不是宿主服务 `ctx.remote.*`——那条 typert 通道为什么用不了,见 `AGENTS.md` 的 `remote.<namespace>` 那条):
|
|
75
75
|
* 每个方法 resolve 成 `RemoteResult<T>`。
|
|
76
76
|
*
|
|
77
77
|
* 2026-10-17 批量重构:写动词收成**一个 `apply`**(一段管所有动作、一组 id 进、逐条结果出),
|
|
@@ -102,9 +102,9 @@ export declare function createHttpRemote(prefix?: string): RawSessionDeleteRemot
|
|
|
102
102
|
*
|
|
103
103
|
* 判据是**「拿到信封」而不是「操作成功」**:`{ok:false, error}` 也是一次成功的往返(路由在、依赖接上了、
|
|
104
104
|
* 错误映射也生效了),那时该做的是把入口显示出来、让原文上屏;只有往返本身失败才算通道缺席 ⇒
|
|
105
|
-
*
|
|
105
|
+
* 一个槽位贡献都不注册(docs/architecture.md §3「通道探测决定入口出现与否」)。
|
|
106
106
|
*
|
|
107
|
-
* 2026-10-17
|
|
107
|
+
* 2026-10-17:探测从"读一遍清单"改成这一问——**每次页面加载都跑全表对账**是白付的钱,
|
|
108
108
|
* 而"信封到不到"这件事一次零成本的 GET 就答得出来。
|
|
109
109
|
*/
|
|
110
110
|
export declare function probeChannel(raw: RawSessionDeleteRemote): Promise<boolean>;
|
|
@@ -115,10 +115,17 @@ export type ApplySuccess<A extends ApplyAction> = Extract<ApplyOutcome, {
|
|
|
115
115
|
action: A;
|
|
116
116
|
ok: true;
|
|
117
117
|
}>;
|
|
118
|
+
/**
|
|
119
|
+
* N=1 的成功支:`delete` / `restore` 的幂等 `null` 已被 `applyOne` 判成通道错,
|
|
120
|
+
* 所以它的 `value` 必然非空;其余动作就是 `ApplySuccess`。
|
|
121
|
+
*/
|
|
122
|
+
export type OneShotSuccess<A extends ApplyAction> = A extends 'delete' | 'restore' ? Omit<ApplySuccess<A>, 'value'> & {
|
|
123
|
+
readonly value: NonNullable<ApplySuccess<A>['value']>;
|
|
124
|
+
} : ApplySuccess<A>;
|
|
118
125
|
/**
|
|
119
126
|
* 注入面(`deps`)的 interface:**与六个段名一一对应的五个方法**(`ping` 只给探测用,留在 raw 层)。
|
|
120
127
|
*
|
|
121
|
-
* **不再有 N=1
|
|
128
|
+
* **不再有 N=1 的便捷方法**:`delete` / `restore` / `countSubagents` 曾是同一段
|
|
122
129
|
* `/apply` 的三种写法,于是一个 `module` 里同一个动作有两条路(回收站面板的 deps 同时躺着
|
|
123
130
|
* `applyOutcomes` 与 `restore`)。现在它们收成一个自由函数 {@link applyOne}——要旧形状就显式调它,
|
|
124
131
|
* 而不是让 interface 多背三个方法。
|
|
@@ -142,9 +149,12 @@ export type OneShotDeps = Pick<SessionDeleteRemote, 'applyOutcomes'>;
|
|
|
142
149
|
* 三条判据都在这一处:
|
|
143
150
|
* - **按发出去的那个 id** 取结果(取"第一条"会把别条的成败安在这条头上);
|
|
144
151
|
* - 一条没回是**通道的错**,不是"没做成"(后者住在 outcome 的 `ok:false` 里);
|
|
145
|
-
* - `null` 是批量里"已被同批前一条带走"
|
|
152
|
+
* - `null` 是批量里"已被同批前一条带走"的幂等值——N=1 永远不会命中它,回 `null` 只能是通道答错了,
|
|
153
|
+
* 所以同样按通道错抛,不冒充成功。
|
|
154
|
+
*
|
|
155
|
+
* 成/败/幂等/缺失的判读只走 {@link readReceipt}(一个家)。
|
|
146
156
|
*/
|
|
147
|
-
export declare function applyOne<A extends ApplyAction>(deps: OneShotDeps, action: A, id: string): Promise<
|
|
157
|
+
export declare function applyOne<A extends ApplyAction>(deps: OneShotDeps, action: A, id: string): Promise<OneShotSuccess<A>>;
|
|
148
158
|
/**
|
|
149
159
|
* 注入面的**唯一一处适配**:`RemoteResult` → 裸值。
|
|
150
160
|
*
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface ClientStore<T> {
|
|
2
|
+
get(): T;
|
|
3
|
+
set(next: T): void;
|
|
4
|
+
/** 回调回同一引用 ⇒ `false`(不发通告);新值 ⇒ `true`。 */
|
|
5
|
+
update(next: (current: T) => T): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* 以本次递增的 seq(从 1 起)造新值并通告,值没变也通告。
|
|
8
|
+
* **`make` 回的值必须把 seq 带上**(放进快照字段,如 `{ seq, items }`):订阅方拿它当 React key
|
|
9
|
+
* 才会在"同一段文案连续出现两次"时重挂载;丢了 seq,重挂载语义会静默消失。
|
|
10
|
+
*/
|
|
11
|
+
publish(make: (seq: number) => T): void;
|
|
12
|
+
subscribe(listener: () => void): () => void;
|
|
13
|
+
use(): T;
|
|
14
|
+
/** 测试专用:回初值并清零 seq。 */
|
|
15
|
+
reset(): void;
|
|
16
|
+
}
|
|
17
|
+
export declare function createStore<T>(initial: T): ClientStore<T>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Translate } from './copy.js';
|
|
2
2
|
import { type SessionDeleteRemote } from './remote.js';
|
|
3
3
|
/** 面板的依赖面:三个读(`list` / `detail` / `scanOrphans`)+ **一段 `apply`** + 清空。
|
|
4
|
-
*
|
|
4
|
+
* **没有第二条路**:「恢复一条」也走 `apply`(N=1),面板不再同时吃
|
|
5
5
|
* `applyOutcomes` 与一个便捷的 `restore`。 `scanOrphans` 是回收站之外那批无主子会话的纯读报告。 */
|
|
6
6
|
export type TrashPanelDeps = Pick<SessionDeleteRemote, 'list' | 'detail' | 'applyOutcomes' | 'emptyTrash' | 'scanOrphans'>;
|
|
7
7
|
export interface TrashPanelProps {
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* 随包发布的明文产物 `lib/index.js`;出处与改写规则见 `ui.css` 头注)。
|
|
4
4
|
*
|
|
5
5
|
* 只留本插件用到的:垃圾桶(删除动作 / 回收站)、警示(失败面)、打开的项目文件夹、
|
|
6
|
-
* 关闭叉(对话框)、圆圈对勾(撤销提示的 success 档)、对勾(行尾兜底勾选开关,2026-10-17
|
|
7
|
-
*
|
|
6
|
+
* 关闭叉(对话框)、圆圈对勾(撤销提示的 success 档)、对勾(行尾兜底勾选开关,2026-10-17 加;
|
|
7
|
+
* 行首主勾选走原生 Checkbox)、下折与右折 chevron(清单的分组折叠头,
|
|
8
8
|
* 2026-10-15 加:`DisclosureRow` 那套折叠行的两态 glyph)。「刷新」那枚随按钮一起删了
|
|
9
9
|
* (2026-10-16,见 `trash-panel.tsx` 工具排的注释)——本文件不留无人消费的几何。
|
|
10
10
|
* **几何、`viewBox`、`strokeWidth` 逐字不动**——它们是官方图标集的形状,改了就不再与宿主同屏同形。
|
|
@@ -25,7 +25,7 @@ export declare function IconFolderOpenOutlineRegular({ size }: IconProps): React
|
|
|
25
25
|
export declare function IconCloseOutlineRegular({ size }: IconProps): ReactElement;
|
|
26
26
|
/** 撤销提示 success 档的前导 glyph(官方 `Toast` 在 `tone="success"` 时自带的那一枚)。 */
|
|
27
27
|
export declare function IconCheckCircleOutlineRegular({ size }: IconProps): ReactElement;
|
|
28
|
-
/** 行尾兜底「勾选开关」那枚对勾(状态点/归档行;官方 `IconCheckOutlineRegular`,`viewBox`/`d` 逐字;2026-10-17
|
|
28
|
+
/** 行尾兜底「勾选开关」那枚对勾(状态点/归档行;官方 `IconCheckOutlineRegular`,`viewBox`/`d` 逐字;2026-10-17 加)。 */
|
|
29
29
|
export declare function IconCheckOutlineRegular({ size }: IconProps): ReactElement;
|
|
30
30
|
/** 分组头「展开中」那一枚(官方 `IconChevronDownOutlineRegular`,`viewBox`/`d` 逐字)。 */
|
|
31
31
|
export declare function IconChevronDownOutlineRegular({ size }: IconProps): ReactElement;
|
|
@@ -1,8 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 撤销提示(docs/ui-spec.md 状态 ③):删除成功后一条可撤销的横幅。
|
|
3
|
+
*
|
|
4
|
+
* 为什么是**独立组件 + 模块级通知台**,而不是死在菜单行里:宿主的菜单行随菜单**一起卸载**
|
|
5
|
+
* (真产物 Menu 的注释原文 "the rows unmount with the list"),而提示要在菜单关掉之后还活着。
|
|
6
|
+
* 宿主的官方配方同形——会话行的动作把请求写进一个模块级 store,`shell.overlay` 里的
|
|
7
|
+
* `workspace.row-toast` 读它(asar 原文:`inject("shell.overlay", … id: "workspace.row-toast" …
|
|
8
|
+
* inject: rowToastInjected … }, RowActionToast)`,而
|
|
9
|
+
* `rowToastInjected = () => ({ hooks: { toast: rowToast }, dismissToast, undoArchive: unarchiveSession })`)。
|
|
10
|
+
* 本文件就是那份 store + 那个常驻组件。
|
|
11
|
+
*
|
|
12
|
+
* 状态源走 `store.ts` 的 `createStore`(内部用 React 自带的 `useSyncExternalStore`)——
|
|
13
|
+
* 不引 `@deepseek-ai/dsh-client-store`(它在平台表内,但这里用不着;React 就够,少一条平台依赖)。
|
|
14
|
+
*/
|
|
1
15
|
import { type Translate } from './copy.js';
|
|
2
16
|
import { type OneShotDeps } from './remote.js';
|
|
3
17
|
export type UndoToastDeps = OneShotDeps;
|
|
4
18
|
/**
|
|
5
|
-
*
|
|
19
|
+
* 一次待撤销的事。两形(2026-10-17 起):单条删除(主语是会话名)/批量删除(主语是条数)。
|
|
6
20
|
* 同一个 Toast 宿主、同一停留时长;撤销动作按形走——单条 `restore`,批量**一次** `applyOutcomes`。
|
|
7
21
|
*/
|
|
8
22
|
export type UndoNotice = {
|
|
@@ -28,7 +42,7 @@ export declare function publishUndoNotice(input: {
|
|
|
28
42
|
entryId: string;
|
|
29
43
|
title: string;
|
|
30
44
|
}): void;
|
|
31
|
-
/**
|
|
45
|
+
/** 批量删除成功后发布一条撤销提示(2026-10-17):覆盖上一条,只报这一次移走的条数。 */
|
|
32
46
|
export declare function publishUndoBatchNotice(input: {
|
|
33
47
|
entryIds: readonly string[];
|
|
34
48
|
n: number;
|
|
@@ -37,7 +51,9 @@ export declare function publishUndoBatchNotice(input: {
|
|
|
37
51
|
export declare function clearUndoNotice(): void;
|
|
38
52
|
/** 供组件订阅。 */
|
|
39
53
|
export declare function useUndoNotice(): UndoNotice | null;
|
|
40
|
-
/**
|
|
54
|
+
/** 测试专用:现场与 seq 归零,用例之间互不串味。 */
|
|
55
|
+
export declare function resetUndoNotice(): void;
|
|
56
|
+
/** 提示停留时长。**6 秒** 定死在本行(比 Toast 默认的 3 秒长)——`docs/ui-spec.md` §2 状态 ③ 只指到这里,不抄这个数。 */
|
|
41
57
|
export declare const UNDO_HOLD_MS = 6000;
|
|
42
58
|
/**
|
|
43
59
|
* 常驻的撤销提示宿主。挂 `shell.overlay`(框架级浮层;槽位说明原文 "a toast stack or a status
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 清单列与详情列之间那根**可拖的宽度把手**(2026-10-15
|
|
2
|
+
* 清单列与详情列之间那根**可拖的宽度把手**(2026-10-15 加,`docs/ui-spec.md` 的「面板与清单」)。
|
|
3
3
|
*
|
|
4
4
|
* 三件事住在这里,因为它们互相咬合、分开测各自都不成立:
|
|
5
5
|
* ① **宽度夹在两个界之间**。下限由调用方给(回收站那条的来历写在 `trash-panel.tsx`:真 Chromium
|
|
6
6
|
* 上扫出来的,约束不是"两颗按钮放得下"——它们 `flex:none`,再窄也是文字列缩;而是「删除于 … ·
|
|
7
7
|
* 大小」那一行不再被省略号截断),上限 = 面板宽 − `reserve`。**`reserve` 的数值与来历都住在调用方**
|
|
8
|
-
* (`DETAIL_RESERVE
|
|
8
|
+
* (`DETAIL_RESERVE`,那三件东西怎么加出来的见 `trash-panel.tsx` 该常量的注释与几何用例):
|
|
9
9
|
* 这一层不复制那份算式——上一版在这里钉过一个"480 ⇒ 900 那档正好回 420",调用方改到 489 之后
|
|
10
10
|
* 它就成了假数,而 900 那档的真实读数是**清单被夹到 411**(比默认窄 9px,还能往里拖)。
|
|
11
11
|
* ② **夹在"读"的时候也做一次**。窗口变窄不触发任何写入,若只在写入时夹,之前拖出来的超宽
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type SessionDeleteRemote, type TrashEntryLike } from './remote.js';
|
|
2
|
+
import type { PendingAction } from './trash-confirm.js';
|
|
3
|
+
import type { TrashDetailState } from './trash-detail.js';
|
|
4
|
+
import type { TrashQuery } from './use-trash-query.js';
|
|
5
|
+
/** 面板的一枚全局闸:写动作在飞时 `busy` 为真,`around` 把一次任务整个包在"这一趟还算忙"里。 */
|
|
6
|
+
export interface TrashPanelGate {
|
|
7
|
+
readonly busy: boolean;
|
|
8
|
+
around<T>(task: () => Promise<T>): Promise<T>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 闸本体(从 `trash-panel.tsx` 的函数体原样收进来):任务落地或抛错都把 `busy` 放回 false。
|
|
12
|
+
* 不做引用计数:面板同一时刻只允许一趟写动作(按钮在 `busy` 期间全被按住),多一层计数只会
|
|
13
|
+
* 把"这一趟什么时候算完"变成第二个判据。
|
|
14
|
+
*
|
|
15
|
+
* 返回值经 `useMemo` 稳定:`gate` 进 `restore` / `confirmApply` 的 `useCallback` 依赖,每次渲染
|
|
16
|
+
* 换个新对象会让那两个回调跟着失效,进而把引用它们的 effect 也带着重跑。
|
|
17
|
+
*/
|
|
18
|
+
export declare function useTrashPanelGate(): TrashPanelGate;
|
|
19
|
+
/**
|
|
20
|
+
* 控制器要的面。`query` 只取三样:清单(选中从它里面找)、重读(失效通告与写动作之后用)、
|
|
21
|
+
* 清空(面板的写动作那一半)。`ordered` 是键盘/自动选中用的"屏幕上的顺序"(只含展开的组),
|
|
22
|
+
* 由面板算好传进来——布局常量不搬进控制器。
|
|
23
|
+
*/
|
|
24
|
+
export interface TrashPanelControllerInput {
|
|
25
|
+
deps: Pick<SessionDeleteRemote, 'detail' | 'applyOutcomes'>;
|
|
26
|
+
gate: TrashPanelGate;
|
|
27
|
+
query: Pick<TrashQuery, 'entries' | 'refresh' | 'emptyTrash'>;
|
|
28
|
+
ordered: readonly TrashEntryLike[];
|
|
29
|
+
canAutoSelect: boolean;
|
|
30
|
+
focusRow(entryId: string): void;
|
|
31
|
+
}
|
|
32
|
+
export interface TrashPanelController {
|
|
33
|
+
readonly busy: boolean;
|
|
34
|
+
readonly selected: TrashEntryLike | null;
|
|
35
|
+
readonly selectedId: string | null;
|
|
36
|
+
readonly drilled: boolean;
|
|
37
|
+
readonly detail: TrashDetailState;
|
|
38
|
+
/** 待确认的那一批(确认框的开合);确认落地后由控制器收起。 */
|
|
39
|
+
readonly pending: PendingAction | null;
|
|
40
|
+
/** 恢复/彻底删除/批量 的失败原文,按条目 id 存:切走再回来还在。 */
|
|
41
|
+
readonly actionFailure: Readonly<Record<string, string>>;
|
|
42
|
+
select(entry: TrashEntryLike, options?: {
|
|
43
|
+
drill?: boolean;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
retry(): void;
|
|
46
|
+
back(): void;
|
|
47
|
+
setPending(pending: PendingAction | null): void;
|
|
48
|
+
/** 单条恢复(`/apply` 的 N=1):成功后丢缓存并重读清单,失败原文落回这一行。 */
|
|
49
|
+
restore(entry: TrashEntryLike): Promise<void>;
|
|
50
|
+
/** 确认框上的「确认」:按 `pending.kind` 分派到空清或 `confirmApply`。 */
|
|
51
|
+
confirmPurge(): Promise<void>;
|
|
52
|
+
/** 一组 id 上的同一个动作:逐条成败落成行上原文/缓存失效,整批只重读一次清单。 */
|
|
53
|
+
confirmApply(action: 'purge' | 'restore', ids: readonly string[]): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
export declare function useTrashPanelController(input: TrashPanelControllerInput): TrashPanelController;
|