@xrn1997/dsh-session-delete 0.4.0 → 0.6.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 +39 -10
- package/cordis.patch.yml +4 -0
- package/icon.svg +18 -18
- package/lib/client.js +1897 -859
- package/lib/index.js +1803 -1058
- package/lib/types/api/attachment-orphans.d.ts +9 -3
- package/lib/types/api/attachment.d.ts +17 -7
- package/lib/types/api/connection-route.d.ts +23 -0
- package/lib/types/api/dispatch.d.ts +25 -14
- package/lib/types/api/route-table.d.ts +47 -11
- package/lib/types/api/trash-rows.d.ts +3 -3
- package/lib/types/client/batch-bar.d.ts +1 -1
- package/lib/types/client/batch-pick.d.ts +19 -0
- package/lib/types/client/batch-select.d.ts +7 -5
- package/lib/types/client/busy-gate.d.ts +6 -2
- package/lib/types/client/confirm-close.d.ts +4 -1
- package/lib/types/client/context.d.ts +3 -2
- package/lib/types/client/copy.d.ts +54 -4
- package/lib/types/client/delete-confirm.d.ts +1 -1
- package/lib/types/client/delete-menu-item.d.ts +1 -1
- package/lib/types/client/host-rows.d.ts +29 -30
- package/lib/types/client/main-view.d.ts +2 -1
- package/lib/types/client/markdown.d.ts +6 -7
- package/lib/types/client/orphan-maintenance.d.ts +10 -25
- package/lib/types/client/pending-open.d.ts +17 -0
- package/lib/types/client/receipt.d.ts +3 -1
- package/lib/types/client/remote.d.ts +21 -18
- package/lib/types/client/store.d.ts +15 -10
- package/lib/types/client/trash-detail.d.ts +48 -6
- package/lib/types/client/trash-entry.d.ts +1 -1
- package/lib/types/client/trash-nav.d.ts +12 -8
- package/lib/types/client/ui/Toast.d.ts +3 -1
- package/lib/types/client/ui/icons.d.ts +1 -1
- package/lib/types/client/undo-toast.d.ts +2 -1
- package/lib/types/client/use-maintenance-section.d.ts +52 -0
- package/lib/types/client/use-maintenance.d.ts +7 -35
- package/lib/types/client/use-trash-panel-controller.d.ts +35 -14
- package/lib/types/client/use-trash-query.d.ts +10 -8
- package/lib/types/client/use-trash-selection.d.ts +15 -12
- package/lib/types/client/write-seats.d.ts +32 -16
- package/lib/types/client/write-settlement.d.ts +1 -1
- package/lib/types/client/write.d.ts +13 -3
- package/lib/types/config.d.ts +45 -0
- package/lib/types/deps.d.ts +8 -3
- package/lib/types/domain/closure.d.ts +47 -0
- package/lib/types/index.d.ts +13 -12
- package/lib/types/log/zstd-frames.d.ts +2 -3
- package/lib/types/ports/attachments-port.d.ts +40 -0
- package/lib/types/ports/fs-port.d.ts +15 -12
- package/lib/types/ports/host-port.d.ts +4 -5
- package/lib/types/ports/presence-port.d.ts +9 -0
- package/lib/types/shared/wire.d.ts +137 -31
- package/lib/types/trash/attachments.d.ts +10 -1
- package/lib/types/trash/detail.d.ts +32 -22
- package/lib/types/trash/entry.d.ts +7 -1
- package/lib/types/trash/groups.d.ts +6 -1
- package/lib/types/trash/manifest.d.ts +4 -4
- package/lib/types/verbs/batch-carried.d.ts +1 -3
- package/lib/types/verbs/live-admission.d.ts +1 -1
- package/lib/types/verbs/member-presence.d.ts +3 -1
- package/lib/types/verbs/move-group.d.ts +2 -1
- package/lib/types/verbs/orphan-subagents.d.ts +12 -5
- package/lib/types/verbs/permanent-removal.d.ts +39 -10
- package/lib/types/verbs/trash-scope.d.ts +36 -0
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +13 -3
- package/lib/types/api/request-trust.d.ts +0 -45
- package/lib/types/ports/connection-port.d.ts +0 -33
- package/lib/types/verbs/purge-trash.d.ts +0 -35
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
* 孤儿附件的报告段:`GET /attachment-orphans`。
|
|
3
3
|
*
|
|
4
4
|
* 报告把扫描计划折成数量/字节 + 对象清单(`id`/`bytes`,供设置页预览缩略图;最多
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
5
|
+
* `src/config.ts` 的 `orphanPreviewLimit`(默认 200,ADR-0037),清理不受预览条数限制)。
|
|
6
|
+
* 扫描跳过(引用源或对象列表读不出)时**报告必须失败**——回一份 0 会让界面说"没有孤儿",
|
|
7
|
+
* 而这类失败恰恰是"不知道",不是"没有"。
|
|
7
8
|
* 清理是 `POST /attachment-clean`,由路由直接接到 `cleanOrphanImageAttachments`。
|
|
8
9
|
*/
|
|
9
10
|
import { type AttachmentReadDeps } from '../trash/attachments.js';
|
|
11
|
+
import type { SessionDeleteConfig } from '../config.js';
|
|
10
12
|
import type { AttachmentOrphanReportWire } from '../shared/wire.js';
|
|
11
|
-
|
|
13
|
+
/** 报告比扫描多读一只旋钮(预览条数);扫描本体仍只认 `maxJsonRefBytes`。 */
|
|
14
|
+
export interface AttachmentOrphanReportDeps extends AttachmentReadDeps {
|
|
15
|
+
config: AttachmentReadDeps['config'] & Pick<SessionDeleteConfig, 'orphanPreviewLimit'>;
|
|
16
|
+
}
|
|
17
|
+
export declare function reportAttachmentOrphans(deps: AttachmentOrphanReportDeps): Promise<AttachmentOrphanReportWire>;
|
|
@@ -2,22 +2,32 @@
|
|
|
2
2
|
* 附件原始字节(`GET /attachment`,全仓唯一的二进制响应段)。
|
|
3
3
|
*
|
|
4
4
|
* 四条硬口径:
|
|
5
|
-
* ① **寻址只认 sha256 id**:`id` 必须 `sha256:<64 位小写 hex
|
|
6
|
-
*
|
|
5
|
+
* ① **寻址只认 sha256 id**:`id` 必须 `sha256:<64 位小写 hex>`,路径不来自请求里的任何一截
|
|
6
|
+
* (`name` 只做下载名;媒体元数据只给宿主 `attachments` 服务做引用校验)。
|
|
7
7
|
* ② **kind 只认 image / file**:分别对应宿主附件库的 `objects`(图片)与 `file-objects`(普通文件);
|
|
8
8
|
* 不认 v2、tmp、cache,也不给"读任意文件"留入口。
|
|
9
|
-
* ③ **单文件 ≤ 20 MiB
|
|
10
|
-
*
|
|
11
|
-
* ④
|
|
12
|
-
* `attachment
|
|
9
|
+
* ③ **单文件 ≤ `maxAttachmentBytes`(默认 20 MiB,可配,ADR-0037)**:所有路径共用同一道
|
|
10
|
+
* `assertWithinLimit`;服务路径先按引用字节拦一道、再按实际字节复核,流式读在累计处停下。
|
|
11
|
+
* ④ **直接 fs 路径**按**魔数**给图片类型(PNG / JPEG / GIF / WebP),认不出就按下载处理
|
|
12
|
+
* (`application/octet-stream` + `attachment`),不猜、也不拿请求里的名字当类型来源;服务路径用
|
|
13
|
+
* 宿主归一化后的 `mediaType`(ADR-0035)。
|
|
14
|
+
*
|
|
15
|
+
* **读取优先走宿主 `attachments` 服务**(ADR-0035):`readImage` / `readFileStream` 会按记录里的
|
|
16
|
+
* 引用校验字节;`attachments` 缺席或请求里没有足够的引用元数据时,才退回直接 fs(旧宿主)。
|
|
17
|
+
* 两种"接不了这次读"(服务缺席 / 元数据不够)在调用方看来是同一个 `undefined`,不各写一条 fallback。
|
|
18
|
+
* 直接 fs 的路径仍只由 `attachmentPathOf(home, kind, id)` 推导。
|
|
13
19
|
*
|
|
14
20
|
* 请求本身不对(id/kind 形状)⇒ 400;对象不在 / 超限 / 读失败 ⇒ 200 + 信封(与其余段同一条错误面)。
|
|
15
21
|
*/
|
|
16
22
|
import type { FsPort } from '../ports/fs-port.js';
|
|
23
|
+
import type { SessionDeleteConfig } from '../config.js';
|
|
24
|
+
import type { AttachmentsPort } from '../ports/attachments-port.js';
|
|
17
25
|
import type { BinaryResponse } from './route-table.js';
|
|
18
|
-
/** 读附件只需要 stat + read
|
|
26
|
+
/** 读附件只需要 stat + read、宿主 `attachments` 读面与响应上限;执行面(removeDir/removeFile)不给。 */
|
|
19
27
|
export interface AttachmentReadPorts {
|
|
20
28
|
fs: Pick<FsPort, 'statFile' | 'readFile'>;
|
|
21
29
|
home: string;
|
|
30
|
+
attachments: AttachmentsPort;
|
|
31
|
+
config: Pick<SessionDeleteConfig, 'maxAttachmentBytes'>;
|
|
22
32
|
}
|
|
23
33
|
export declare function readAttachmentResponse(deps: AttachmentReadPorts, query: URLSearchParams): Promise<BinaryResponse>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Dispatch } from './dispatch.js';
|
|
2
|
+
/** 请求体字节上限:这条通道只收几个 id,1MiB 是"够用且不给自己开一条内存放大器"的量级。 */
|
|
3
|
+
export declare const JSON_BODY_MAX_BYTES: number;
|
|
4
|
+
/** 宿主 `connection.fetch.register` 的入参形状(只声明本适配器用到的成员)。 */
|
|
5
|
+
export interface ConnectionFetchRouteLike {
|
|
6
|
+
readonly path: string;
|
|
7
|
+
readonly methods: readonly ('GET' | 'HEAD' | 'POST')[];
|
|
8
|
+
readonly requestBody: 'buffered' | 'streaming';
|
|
9
|
+
readonly fetch: (request: Request) => Promise<Response>;
|
|
10
|
+
}
|
|
11
|
+
/** 宿主 `connection` 服务的窄镜像:只要求 exact Fetch 注册点。 */
|
|
12
|
+
export interface ConnectionFetchServiceLike {
|
|
13
|
+
readonly fetch?: {
|
|
14
|
+
register(route: ConnectionFetchRouteLike): () => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 注册九段 exact Fetch 路由。返回一个组合 disposer;调用方把它挂在自己的 effect 上。
|
|
19
|
+
* `connection` 是插件的硬依赖(web UI / 桌面端都装 `client-connection`);`fetch.register`
|
|
20
|
+
* 缺席属于宿主形状漂移,直接抛——静默返回空 disposer 会让插件「在但没有入口」。
|
|
21
|
+
* `dispatch` 与分发器同源。
|
|
22
|
+
*/
|
|
23
|
+
export declare function registerConnectionRoutes(service: ConnectionFetchServiceLike, dispatch: Dispatch): () => Promise<void>;
|
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* /dsh-session-delete-api 的前缀路由分发:只负责传输。
|
|
3
|
-
*
|
|
4
|
-
* 三件事:请求栅栏 → 查路由表 → 回信封。方法、纯度、body 预读与读面的判据都在
|
|
5
|
-
* src/api/route-table.ts(这里只按判据执行);行投影在 src/api/trash-rows.ts。
|
|
6
|
-
*
|
|
7
|
-
* - 请求本身不对用 4xx(400 请求体 / 405 方法 / 404 未知路由 / 403 栅栏拒绝);
|
|
8
|
-
* 操作失败回 200 + {ok:false,error}。浏览器半先解信封,再看状态。
|
|
9
|
-
* - 请求栅栏(归属 / 身份)归 `src/api/request-trust.ts`,这里只按它的裁决写 403。
|
|
10
|
-
* - purity 为 chained 的请求串行执行,避免读清册/动文件/写清册交错。
|
|
11
|
-
*/
|
|
12
|
-
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
13
1
|
import type { DeleteDeps } from '../deps.js';
|
|
14
|
-
import { type
|
|
15
|
-
|
|
2
|
+
import { type BinaryResponse, type JsonBody, type RouteSpec, type SliceDeps } from './route-table.js';
|
|
3
|
+
/** 适配器归一后的一次请求:`rest` 是插件前缀之后的段名(如 `/list`)。 */
|
|
4
|
+
export interface ApiInput {
|
|
5
|
+
readonly method: string;
|
|
6
|
+
readonly rest: string;
|
|
7
|
+
readonly search: URLSearchParams;
|
|
8
|
+
readonly body: JsonBody;
|
|
9
|
+
}
|
|
10
|
+
/** 适配器归一后的一次响应:JSON 信封或二进制字节,状态码与头由适配器写。 */
|
|
11
|
+
export type ApiResult = {
|
|
12
|
+
readonly kind: 'json';
|
|
13
|
+
readonly status: number;
|
|
14
|
+
readonly body: unknown;
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: 'binary';
|
|
17
|
+
readonly body: BinaryResponse;
|
|
18
|
+
};
|
|
19
|
+
/** 段名的 404 与方法的 405 只在这里判一次;空段名按插件前缀显示,避免 404 原文出现一个空串。 */
|
|
20
|
+
export declare function resolveRoute(method: string, rest: string): RouteSpec;
|
|
21
|
+
/** 四格只建一次:收窄对象在 dispatcher 构造期固定,请求路径只做引用选择。 */
|
|
22
|
+
export declare function slicesOf(full: DeleteDeps): SliceDeps;
|
|
23
|
+
/** 分发函数:查段 → 方法闸 → 读面 → 处理。 */
|
|
24
|
+
export type Dispatch = (input: ApiInput) => Promise<ApiResult>;
|
|
25
|
+
/** 路由执行器:查段 → 方法闸 → 读面 → 处理;返回归一结果。组合根只建一次。 */
|
|
26
|
+
export declare function createDispatcher(deps: DeleteDeps): Dispatch;
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 路由表(ADR-0004):段名 → { 方法, 纯度, 读面, body, 处理 }。一条记录,两个消费者。
|
|
2
|
+
* 路由表(ADR-0004;deps 切片见 ADR-0032):段名 → { 方法, 纯度, 读面, body, deps 切片, 处理 }。一条记录,两个消费者。
|
|
3
3
|
*
|
|
4
4
|
* 一致性要求:
|
|
5
5
|
* - 该纯的判成写 ⇒ 探测/正文读/扫描被堵在串行链后;
|
|
6
6
|
* - 该写的判成纯 ⇒ 写动作绕开到达序链,与对账交错;
|
|
7
|
-
* - body 该读没读 ⇒ 下游拿到 null;读两遍 ⇒
|
|
7
|
+
* - body 该读没读 ⇒ 下游拿到 null;读两遍 ⇒ 流已被消费;
|
|
8
|
+
* - 切片声明宽了 ⇒ 危险能力(删目录 / 时钟)漏进读段的 run;窄了 ⇒ 写段的处理编译不过。
|
|
8
9
|
*
|
|
9
10
|
* 这张表不放 shared/wire.ts:处理函数会 import 动词与 node:fs,而浏览器半要 import wire.ts。
|
|
10
|
-
*
|
|
11
|
+
* 段名、方法与动作仍住 wire.ts(`ROUTES` 每项 `{ path, method }`,本表的 method 只投影、不写第二份字面量);
|
|
12
|
+
* 这张表只回答“怎么处理”。
|
|
11
13
|
*/
|
|
12
|
-
import {
|
|
13
|
-
import type { DeleteDeps } from '../deps.js';
|
|
14
|
+
import { type ApplyAction, type RouteName } from '../shared/wire.js';
|
|
15
|
+
import type { CoreDeps, DeleteDeps, RemovalDeps } from '../deps.js';
|
|
16
|
+
import type { AttachmentsPort } from '../ports/attachments-port.js';
|
|
14
17
|
import type { TrashEntry } from '../trash/entry.js';
|
|
15
18
|
/** 请求体:`json` 才读下来(GET 一律不读;读两遍会把流消费掉)。 */
|
|
16
19
|
export type BodyKind = 'none' | 'json';
|
|
@@ -31,19 +34,52 @@ export interface BinaryResponse {
|
|
|
31
34
|
export interface RouteRead {
|
|
32
35
|
rows(): Promise<readonly TrashEntry[]>;
|
|
33
36
|
}
|
|
34
|
-
|
|
37
|
+
/** deps 切片(ADR-0032):每一段只声明自己要的那一面。读与探测五段拿基础面,`attachment` 段另多一条
|
|
38
|
+
* 宿主 `attachments` 读面,两个删盘段多删除能力,`/apply` 是唯一拿全量面的行——段的 run 里长出超面
|
|
39
|
+
* 调用就是编译错,dispatch 也只按这一格注入(运行期收窄住 `src/api/dispatch.ts` 的 `slicesOf`)。 */
|
|
40
|
+
export type RouteSlice = 'core' | 'removal' | 'full' | 'attachment';
|
|
41
|
+
/** 切片名 → 依赖面:{@link RouteSpec.run} 的 deps 参数类型与 `depsFor` 的返回类型都从这一格取。 */
|
|
42
|
+
export type SliceDeps = {
|
|
43
|
+
core: CoreDeps;
|
|
44
|
+
removal: RemovalDeps;
|
|
45
|
+
full: DeleteDeps;
|
|
46
|
+
attachment: CoreDeps & {
|
|
47
|
+
readonly attachments: AttachmentsPort;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
interface BaseRouteSpec<S extends RouteSlice> {
|
|
51
|
+
/** 这一行吃哪一格 deps:dispatch 按它注入,`run` 的 deps 参数类型也跟着它走。 */
|
|
52
|
+
readonly slice: S;
|
|
35
53
|
readonly method: 'GET' | 'POST';
|
|
36
54
|
readonly body: BodyKind;
|
|
37
55
|
readonly purity: Purity;
|
|
38
56
|
readonly read: ReadKind;
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
57
|
+
/** 同源取数闸(ADR-0033):只有能被别的页面渲染消费的段需要;判据住 `src/api/connection-route.ts`。
|
|
58
|
+
* 缺省不设闸——JSON 响应跨源读不到(没有 CORS),不需要它。 */
|
|
59
|
+
readonly fetchGate?: 'same-origin';
|
|
42
60
|
}
|
|
61
|
+
/** JSON 段:处理函数拿请求体与读面;`query` 不在这里(九行里只有二进制段需要查询串)。 */
|
|
62
|
+
export interface JsonRouteSpec<S extends RouteSlice = RouteSlice> extends BaseRouteSpec<S> {
|
|
63
|
+
readonly response: 'json';
|
|
64
|
+
/** 声明成函数属性而不是方法:方法签名是双变的,显式标注更宽的 deps 能绕过切片闸;属性按逆变校验。 */
|
|
65
|
+
readonly run: (deps: SliceDeps[S], body: JsonBody, read: RouteRead) => Promise<unknown>;
|
|
66
|
+
}
|
|
67
|
+
/** 二进制段:只有 `/attachment`;处理函数直接拿查询串,交回字节与安全头。 */
|
|
68
|
+
export interface BinaryRouteSpec<S extends RouteSlice = RouteSlice> extends BaseRouteSpec<S> {
|
|
69
|
+
readonly response: 'binary';
|
|
70
|
+
readonly run: (deps: SliceDeps[S], query: URLSearchParams) => Promise<BinaryResponse>;
|
|
71
|
+
}
|
|
72
|
+
export type RouteSpec<S extends RouteSlice = RouteSlice> = JsonRouteSpec<S> | BinaryRouteSpec<S>;
|
|
73
|
+
/** 九行的并集(表的值类型):每行由自己的 `slice` 与 `response` 字面量定型,`run` 的 deps 面随之收窄。 */
|
|
74
|
+
export type AnyRouteSpec = {
|
|
75
|
+
[S in RouteSlice]: RouteSpec<S>;
|
|
76
|
+
}[RouteSlice];
|
|
43
77
|
export declare const APPLY_ACTIONS: readonly ApplyAction[];
|
|
44
78
|
/**
|
|
45
|
-
* 九个段名各一行。`Record<段名,
|
|
79
|
+
* 九个段名各一行。`Record<段名, AnyRouteSpec>` 是闸:`wire.ts` 加了段名而这里没加,编译不过;
|
|
80
|
+
* 每行的 `slice` 又把该行的 `run` 钉在自己那一格 deps 上(ADR-0032)。
|
|
46
81
|
*/
|
|
47
|
-
export declare const ROUTE_TABLE: Record<
|
|
82
|
+
export declare const ROUTE_TABLE: Record<RouteName, AnyRouteSpec>;
|
|
48
83
|
/** 段名 → 那一行;不认识的段名回 `undefined`(调用方抛 404,并带出原路径)。 */
|
|
49
84
|
export declare function routeOf(rest: string): RouteSpec | undefined;
|
|
85
|
+
export {};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 归 `verbs/permanent-removal.ts` 的只读计划 —— 这里只做装配,不再自己枚举盘上后代。
|
|
7
7
|
*/
|
|
8
8
|
import type { CoreDeps } from '../deps.js';
|
|
9
|
-
import type
|
|
9
|
+
import { type TrashEntry } from '../trash/entry.js';
|
|
10
10
|
import type { TrashRowWire } from '../shared/wire.js';
|
|
11
11
|
/** 组视图那几个只读数的容器(行投影的第二个入参)。`releaseBytes` 必填(计划里实测好的那一格);
|
|
12
12
|
* 其余缺席 = 这次没算,不是 0。 */
|
|
@@ -14,8 +14,8 @@ type TrashRowReadouts = {
|
|
|
14
14
|
readonly releaseBytes: number;
|
|
15
15
|
} & Partial<Pick<TrashRowWire, 'subagentCount' | 'subagentBytes' | 'strandedSubagents' | 'strandedBytes' | 'workspaceMissing'>>;
|
|
16
16
|
/**
|
|
17
|
-
* 清册那一行 →
|
|
18
|
-
*
|
|
17
|
+
* 清册那一行 → 线上那一行。清册字段走 `entryWire` 那份显式字面量(线上形状多一个必填字段时编译不过,
|
|
18
|
+
* `...row` 会把它带过去),这里只补 `/list` 的只读数。
|
|
19
19
|
*/
|
|
20
20
|
export declare function toWire(row: TrashEntry, readouts: TrashRowReadouts): TrashRowWire;
|
|
21
21
|
/**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 批量删除的浮条:**多选模式内常驻**(0 条也渲染,既显示模式又给退出入口),
|
|
3
3
|
* 汇总「已选 N 条」+(读得到时)「含 K 个子会话」;「查看已选」展开限高可滚动的标题清单
|
|
4
4
|
* (逐条可移除),回答"到底选了哪些"——清单默认收起,不把底部输入区盖住。
|
|
5
|
-
* 模式内的整行点击守卫也挂在这棵常驻宿主上(见 `
|
|
5
|
+
* 模式内的整行点击守卫也挂在这棵常驻宿主上(见 `batch-pick.ts` 的 `useSelectionRowClickGuard`)。
|
|
6
6
|
*
|
|
7
7
|
* 为什么单独一个文件而不是并进 `batch-select.tsx`:那份是"行开关控件",而浮条要点开确认框
|
|
8
8
|
* (要 import 它的现场)——两个方向合到一个文件上就是一圈。浮条住在
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type PickItem } from './batch-selection-store.js';
|
|
2
|
+
/** 全选:把当前可见可选行按登记序追加;已在名单里的由 `addPicks` 自己跳过,原有选中保持在前。 */
|
|
3
|
+
export declare function selectAllVisible(): void;
|
|
4
|
+
/**
|
|
5
|
+
* 同一个切换口该显示「全选」还是「取消全选」:
|
|
6
|
+
* - 有选中、且当前可见可选行全都选中 ⇒ 取消全选;
|
|
7
|
+
* - 有选中、但当前没有可见可选行(全折叠/过滤掉)⇒ 只能清空,也显示取消全选;
|
|
8
|
+
* - 其余(含 0 条)⇒ 全选。
|
|
9
|
+
*/
|
|
10
|
+
export declare function areAllVisibleRowsSelected(rows: readonly PickItem[], picks: readonly PickItem[]): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* 多选态内的整行点击守卫(document 捕获阶段)。
|
|
13
|
+
*
|
|
14
|
+
* 边界:行里的按钮 / 链接 / 输入 / 菜单项一律放行(否则 ⋯ 菜单、归档、置顶、单删都点不动);
|
|
15
|
+
* 双击的第二下与 dblclick 不翻转(第一下已经选中,第二下不能又反掉,也不能打开宿主的重命名)。
|
|
16
|
+
* 认行靠 `data-row-key="session:…"`——**这是本文件唯一一处 DOM 读**(ADR-0034):宿主将来改掉这个
|
|
17
|
+
* 属性,最差是模式内点行不生效,勾选框本身仍在。模式外不挂监听:这时点行必须还是宿主的"打开会话"。
|
|
18
|
+
*/
|
|
19
|
+
export declare function useSelectionRowClickGuard(): void;
|
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 入口在每行的「⋯」菜单里(`batch-menu-item.tsx`):**按下才进入多选模式**,模式内才渲染勾选框、
|
|
6
6
|
* 才把整行点击变成"勾/取消";退出模式清空名单。选中集只住 `batch-selection-store.ts`(模式 + 名单一份
|
|
7
|
-
* 合并快照);"
|
|
8
|
-
* store
|
|
7
|
+
* 合并快照);"哪些行现在可见"住 `host-rows.ts`,"点行算不算选中"住 `batch-pick.ts`。四处隔着
|
|
8
|
+
* 槽位边界,只有模块级 store 能让他们看见同一份名单;本文件不持有任何现场。
|
|
9
9
|
*
|
|
10
10
|
* **为什么主勾选在行首**:宿主对行尾 `.rowActions` 是 `display:none`,只有 `row:hover` / 菜单打开
|
|
11
11
|
* 才出现(app.asar 0.2.0-rc.2 的 Rows 渲染);行首 16px 格在无状态点时是空的,模式内可见。
|
|
12
12
|
* 宿主在「有状态点(运行中/待确认/未读完成)」与归档行不渲染行首格 ⇒ 这些行走行尾悬停兜底,
|
|
13
|
-
* 但模式内**点整行**也能选(`
|
|
13
|
+
* 但模式内**点整行**也能选(`batch-pick.ts` 的 `useSelectionRowClickGuard`),所以不依赖那颗小框。
|
|
14
14
|
* 两条勾选不会同时出现(`ui.css` 的 `:has` 兜底隐藏规则,靠宿主行的 `data-row-key`)。
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
16
|
+
import type { ReactElement } from 'react';
|
|
17
17
|
import { type Translate } from './copy.js';
|
|
18
18
|
import { type SessionDeleteRemote } from './remote.js';
|
|
19
19
|
/**
|
|
@@ -27,6 +27,8 @@ export interface BatchPickCheckboxProps {
|
|
|
27
27
|
sessionId: string;
|
|
28
28
|
/** 槽位只投 `sessionId`;标题由宿主侧的 root hook 读出来再传进来(见 `index.tsx` 的 `PickLeadingRow`)。 */
|
|
29
29
|
title: string;
|
|
30
|
+
/** 运行态同样由 root hook 读:登记表里的 `live` 与行尾兜底保持同一口径。 */
|
|
31
|
+
live?: boolean;
|
|
30
32
|
/** 文案(缺省 = 内置 zh,见 `copy.ts`)。 */
|
|
31
33
|
t?: Translate;
|
|
32
34
|
}
|
|
@@ -37,7 +39,7 @@ export interface BatchPickCheckboxProps {
|
|
|
37
39
|
* (行首格在宿主行那条 `onClick` 里面)。宿主对「有状态点」和归档行不渲染这一格,
|
|
38
40
|
* 那些行走 `BatchPickButton` 兜底;两条不会同时出现(见 `ui.css` 的兜底隐藏规则)。
|
|
39
41
|
*/
|
|
40
|
-
export declare function BatchPickCheckbox({ sessionId, title, t, }: BatchPickCheckboxProps): ReactElement | null;
|
|
42
|
+
export declare function BatchPickCheckbox({ sessionId, title, live, t, }: BatchPickCheckboxProps): ReactElement | null;
|
|
41
43
|
export interface BatchPickButtonProps {
|
|
42
44
|
sessionId: string;
|
|
43
45
|
title: string;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export interface BusyGate {
|
|
2
2
|
readonly busy: boolean;
|
|
3
|
-
/** 把一次任务整个包进「这一趟还算忙」;落地与抛错都放回 false。
|
|
4
|
-
|
|
3
|
+
/** 把一次任务整个包进「这一趟还算忙」;落地与抛错都放回 false。
|
|
4
|
+
* 同一枚闸已有任务在飞时这一次**不执行**(同步判重),立即回 `undefined`。类型里带上 `undefined`
|
|
5
|
+
* 不是将就,是判据:挡回的那一趟什么都没跑,把它伪装成一枚 `T` 就是把「没跑」当成「跑完且结果是
|
|
6
|
+
* 那个值」——消费返回值的调用方(写入座位、清空)会照着编出失败原文或宣布成功。怎么收尾由调用方
|
|
7
|
+
* 定:这一趟的界面后果归在飞的那一趟,挡回来的那一份什么都不该改。 */
|
|
8
|
+
around: <T>(task: () => Promise<T>) => Promise<T | undefined>;
|
|
5
9
|
}
|
|
6
10
|
export declare function useBusyGate(): BusyGate;
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* 返回的 `closeDisabled` 与 `close` 同源:取消键的置灰读数也从这里取,各组件不再各写一遍
|
|
6
6
|
* `disabled={busy}`——否则"拦住"这件事会有两个判据。 */
|
|
7
7
|
export interface IdleClose {
|
|
8
|
-
close
|
|
8
|
+
/** 属性声明而不是方法简写:实现体不读 `this`,调用方会把 `close` 摘下来直接交给 `Modal`/
|
|
9
|
+
* `Button`(`onClose={close}`);方法简写会给它隐式 `this: IdleClose`,`unbound-method`
|
|
10
|
+
* 就会把这种正常交接报成可疑。形状仍是同一个函数,零运行时改动。 */
|
|
11
|
+
close: () => void;
|
|
9
12
|
readonly closeDisabled: boolean;
|
|
10
13
|
}
|
|
11
14
|
export declare function useIdleClose(busy: boolean, onClose: () => void): IdleClose;
|
|
@@ -26,6 +26,7 @@ export interface ClientContextLike {
|
|
|
26
26
|
slots: SlotsLike;
|
|
27
27
|
/** 可选服务的读面(宿主自己的写法:`ctx.get('…')?.…`)。**缺省调用**:老宿主上没有这个方法。 */
|
|
28
28
|
get?(name: string): unknown;
|
|
29
|
-
/** 挂一个随本插件卸载而撤销的副作用(返回的清理函数由框架接管)。
|
|
30
|
-
|
|
29
|
+
/** 挂一个随本插件卸载而撤销的副作用(返回的清理函数由框架接管)。
|
|
30
|
+
* 属性声明:测试会把它当成员读(`expect(ctx.effect).not.toHaveBeenCalled()`),实现不读 `this`。 */
|
|
31
|
+
effect: (execute: () => unknown, label?: string) => unknown;
|
|
31
32
|
}
|
|
@@ -102,7 +102,7 @@ declare const ZH: {
|
|
|
102
102
|
readonly 'detail.notSelected': "选中一条会话,看它讲了什么";
|
|
103
103
|
readonly 'detail.loading': "正在读这条会话…";
|
|
104
104
|
readonly 'detail.readFailed': "这条会话的正文读不出来";
|
|
105
|
-
readonly 'detail.noBody': "
|
|
105
|
+
readonly 'detail.noBody': "这条会话没有可显示的内容";
|
|
106
106
|
readonly 'detail.field.created': "创建";
|
|
107
107
|
readonly 'detail.field.lastActivity': "最后活动";
|
|
108
108
|
readonly 'detail.field.deletedAt': "删除于";
|
|
@@ -111,8 +111,9 @@ declare const ZH: {
|
|
|
111
111
|
readonly 'detail.field.size': "大小";
|
|
112
112
|
readonly 'detail.field.turns': "轮数";
|
|
113
113
|
readonly 'detail.field.userMessages': "提问";
|
|
114
|
-
readonly 'detail.field.assistantMessages': "
|
|
114
|
+
readonly 'detail.field.assistantMessages': "助手消息";
|
|
115
115
|
readonly 'detail.field.toolCalls': "工具调用";
|
|
116
|
+
readonly 'detail.field.injections': "注入上下文";
|
|
116
117
|
readonly 'detail.field.models': "模型";
|
|
117
118
|
readonly 'detail.field.project': "项目";
|
|
118
119
|
readonly 'detail.field.sessionId': "会话 id";
|
|
@@ -120,10 +121,36 @@ declare const ZH: {
|
|
|
120
121
|
readonly 'detail.field.wasArchived': "原本归档";
|
|
121
122
|
readonly 'detail.value.yes': "是";
|
|
122
123
|
readonly 'detail.value.no': "否";
|
|
124
|
+
readonly 'detail.value.spanLessThanHour': "不足 1 小时";
|
|
123
125
|
readonly 'detail.value.spanHours': "{n} 小时";
|
|
124
126
|
readonly 'detail.value.spanDays': "{n} 天";
|
|
125
|
-
readonly 'detail.
|
|
126
|
-
readonly 'detail.
|
|
127
|
+
readonly 'detail.block.reasoning': "思考";
|
|
128
|
+
readonly 'detail.block.tool': "工具调用";
|
|
129
|
+
readonly 'detail.block.injection': "上下文注入";
|
|
130
|
+
readonly 'detail.block.system': "系统消息";
|
|
131
|
+
readonly 'detail.block.compaction': "上下文已压缩";
|
|
132
|
+
readonly 'detail.block.args': "参数";
|
|
133
|
+
readonly 'detail.block.result': "结果";
|
|
134
|
+
readonly 'detail.block.noResult': "日志在调用结束前结束,没有结果";
|
|
135
|
+
readonly 'detail.block.status.ok': "成功";
|
|
136
|
+
readonly 'detail.block.status.error': "失败";
|
|
137
|
+
readonly 'detail.block.status.unfinished': "未完成";
|
|
138
|
+
readonly 'detail.block.truncated': "已截断";
|
|
139
|
+
readonly 'detail.loadOlder': "加载更早";
|
|
140
|
+
readonly 'detail.loadOlder.loading': "正在加载更早…";
|
|
141
|
+
readonly 'detail.expandAll': "全部展开";
|
|
142
|
+
readonly 'detail.collapseAll': "全部折叠";
|
|
143
|
+
readonly 'detail.injection.runtimeContext': "运行上下文";
|
|
144
|
+
readonly 'detail.injection.timeContext': "时间上下文";
|
|
145
|
+
readonly 'detail.injection.agentInstructions': "Agent 指令";
|
|
146
|
+
readonly 'detail.injection.skillCatalog': "技能目录";
|
|
147
|
+
readonly 'detail.injection.agentMessage': "子会话消息";
|
|
148
|
+
readonly 'detail.injection.subagentSettled': "子会话结束";
|
|
149
|
+
readonly 'detail.injection.toolJobs': "后台任务";
|
|
150
|
+
readonly 'detail.injection.skillInvocation': "技能调用";
|
|
151
|
+
readonly 'detail.injection.userApproval': "用户批准";
|
|
152
|
+
readonly 'detail.injection.goal': "目标";
|
|
153
|
+
readonly 'detail.injection.other': "其他上下文";
|
|
127
154
|
readonly 'detail.image': "[图片]";
|
|
128
155
|
readonly 'detail.image.alt': "[图片 · {alt}]";
|
|
129
156
|
readonly 'detail.attachment.file': "文件";
|
|
@@ -135,6 +162,9 @@ declare const ZH: {
|
|
|
135
162
|
readonly 'detail.attachment.saving': "保存中…";
|
|
136
163
|
readonly 'detail.attachment.saveFailed': "保存失败:{message}";
|
|
137
164
|
readonly 'detail.back': "← 返回清单";
|
|
165
|
+
readonly 'detail.subagents.header': "子会话({n}) · {size}";
|
|
166
|
+
readonly 'detail.subagents.truncated': "仅列出前 {shown} 条,共 {total} 条";
|
|
167
|
+
readonly 'detail.backToParent': "← 返回父会话";
|
|
138
168
|
readonly 'detail.list.label': "回收站清单";
|
|
139
169
|
readonly 'detail.role.user': "你";
|
|
140
170
|
readonly 'detail.role.assistant': "助手";
|
|
@@ -160,6 +190,7 @@ declare const ZH: {
|
|
|
160
190
|
readonly 'undo.moved': "{title} 已移入回收站";
|
|
161
191
|
readonly 'undo.session': "会话";
|
|
162
192
|
readonly 'undo.undo': "撤销";
|
|
193
|
+
readonly 'undo.failedBatch': "{n} 条会话未能撤销:{reasons}";
|
|
163
194
|
};
|
|
164
195
|
export type CopyKey = keyof typeof ZH;
|
|
165
196
|
export type CopyParams = Readonly<Record<string, string | number>>;
|
|
@@ -174,6 +205,25 @@ export declare const zhTranslate: Translate;
|
|
|
174
205
|
* 而且同一命名空间的同一语言**只能注册一次**——effect 没挂,HMR 重挂就撞 "already has locale"。
|
|
175
206
|
*/
|
|
176
207
|
export declare function createTranslate(ctx: ClientContextLike): Translate;
|
|
208
|
+
/**
|
|
209
|
+
* `MarkdownBody`(`markdown.tsx`)memo comparator 的**语言探针**:借一句固定键的译文,当
|
|
210
|
+
* 「当前是哪种语言」的读数。为什么借译文而不问 locale 服务:宿主 locale 服务的窄镜像不暴露
|
|
211
|
+
* 当前语言(受窄镜像形状约束,不做 `locale()` 探针),当前语言只能从 `t` 的输出里读出来。
|
|
212
|
+
*
|
|
213
|
+
* 为什么必须在**渲染时**现算、由调用方存进 props:宿主 `t` 读的是**当前** locale,若在
|
|
214
|
+
* comparator 里现调,prev/next 那一拍都取到新值、恒等,换语言就漏了——只有渲染那一刻算好的
|
|
215
|
+
* 那份才是换语言那一拍的读数。
|
|
216
|
+
*
|
|
217
|
+
* 为什么拿 `t` 的输出当读数、而不是拿 `t` 的身份:宿主 `t` 是 `bind` 出来的**稳定身份**
|
|
218
|
+
* (`createTranslate` 一次客户端激活只造一个),换语言后同一个 `t` 吐另一套词——身份不变、
|
|
219
|
+
* 词变了,所以判据必须是这个词(探针值)而不是身份本身。
|
|
220
|
+
*
|
|
221
|
+
* 「借哪句」的知识只住这里:调用方与测试一律传 `probe={localeProbe(t)}`,不各自记得借哪句。
|
|
222
|
+
*
|
|
223
|
+
* **失效模式**:借的那句若在两种语言下译成同一个字面,探针就分不出换语言——memo 恒等、旧语言滞留。
|
|
224
|
+
* 这条由 `tests/client/markdown.test.tsx` 的换语言用例兜住;换借句或改那句译文时先看它红不红。
|
|
225
|
+
*/
|
|
226
|
+
export declare function localeProbe(t: Translate): string;
|
|
177
227
|
/**
|
|
178
228
|
* 把一句话里内嵌的那一段拆出来:`'…文件,之后没有任何副本可以恢复。'` 交给调用面用 `<b>` 包中间那段。
|
|
179
229
|
*
|
|
@@ -16,7 +16,7 @@ export declare const TARGET_CARD: {
|
|
|
16
16
|
export interface DeleteRequest {
|
|
17
17
|
/** 每次发布递增:换一个会话就重挂载确认框,busy / failure 不带着上一条请求的记忆。 */
|
|
18
18
|
seq: number;
|
|
19
|
-
/** 要删的会话 id
|
|
19
|
+
/** 要删的会话 id——原样透给删除写入(`apply` 的 `delete` 动作;段名与动作住 `shared/wire.ts`)。 */
|
|
20
20
|
sessionId: string;
|
|
21
21
|
/** 删除那一刻的会话名(槽位投影的 `displayTitle`):只用于确认框文案与撤销提示。 */
|
|
22
22
|
title: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Translate } from './copy.js';
|
|
2
2
|
export interface DeleteMenuItemProps {
|
|
3
|
-
/** 宿主菜单槽投影的会话 id
|
|
3
|
+
/** 宿主菜单槽投影的会话 id,原样透给删除写入(`apply` 的 `delete` 动作;段名与动作住 `shared/wire.ts`,浏览器半不自己拼 id)。 */
|
|
4
4
|
sessionId: string;
|
|
5
5
|
/** 宿主菜单槽投影的 `displayTitle`:只用于确认框与提示文案,不参与任何请求。 */
|
|
6
6
|
title: string;
|
|
@@ -1,50 +1,49 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PickItem } from './batch-selection-store.js';
|
|
2
2
|
export interface HostRowInfo {
|
|
3
|
-
/**
|
|
3
|
+
/** 槽位宿主投影的标题(`PickLeadingRow` / `PickRow` 从 root hook 读);守卫只读登记,不读 DOM 文本。 */
|
|
4
4
|
readonly title: string;
|
|
5
5
|
/** 在跑的行点整行也不选:与置灰的勾选框同口径(级联遇在跑成员整组拒绝)。 */
|
|
6
6
|
readonly live: boolean;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* 行槽位开关登记一行:模式内与模式外都要登记(守卫进模式的那一帧就得有标题)。
|
|
10
|
+
* 每次登记算**多一个挂载实例**;不给 info 时只占位,随后由 {@link updateHostRowInfo} 填值。
|
|
11
11
|
*/
|
|
12
|
-
export declare function registerHostRowInfo(sessionId: string, info
|
|
12
|
+
export declare function registerHostRowInfo(sessionId: string, info?: HostRowInfo): void;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
* 标题/运行态变了就地更新:**不注销、不重登记**,否则这一行会被挪到登记序末尾(ADR-0034 决定 2)。
|
|
15
|
+
*/
|
|
16
|
+
export declare function updateHostRowInfo(sessionId: string, info: HostRowInfo): void;
|
|
17
|
+
/**
|
|
18
|
+
* 槽位组件共用的登记生命周期:挂载登记、卸载注销;标题/运行态变化只走更新口。
|
|
19
|
+
* 两次 effect 分工是顺序契约:先占住首次登记序,再填值。
|
|
20
|
+
*/
|
|
21
|
+
export declare function useHostRowRegistration(sessionId: string, info: HostRowInfo): void;
|
|
22
|
+
/** 行槽位开关卸载时调用:那一行已经离开渲染树,登记跟着走。
|
|
23
|
+
* 还有别的挂载实例在用同一行(重复行)就只减计数,不摘登记。 */
|
|
24
|
+
export declare function unregisterHostRowInfo(sessionId: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* 守卫与全选共用的一行读数:这一行此刻可勾选时交回登记(含标题);未登记或在跑回 `undefined`。
|
|
27
|
+
* 登记表是可见行的事实源,所以"没登记"就是"这一行此刻不在可见集合里"。
|
|
28
|
+
*/
|
|
29
|
+
export declare function pickableRowOf(sessionId: string): HostRowInfo | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* 「当前可见」的可选行:**已登记**的会话行(槽位开关还挂在渲染树上)——折叠组、归档过滤外的行
|
|
32
|
+
* 没有挂载,因此没有登记;在跑的行跳过(与置灰勾选框同口径)。标题/运行态来自登记值;
|
|
33
|
+
* 顺序是登记顺序(宿主按列表顺序挂载行)。
|
|
17
34
|
*
|
|
18
35
|
* 这是**当场读**:事件路径(点「全选」那一刻的页面)与测试用它;渲染侧读下面的快照。
|
|
19
36
|
*/
|
|
20
37
|
export declare function readVisibleSelectableRows(): PickItem[];
|
|
21
38
|
/**
|
|
22
|
-
*
|
|
39
|
+
* 渲染侧的快照:脏了才重读登记;干净时回同一个引用(React 的 `useSyncExternalStore` 契约)。
|
|
23
40
|
* 没变化时连续两次调用必须同引用,否则订阅方会被自己的快照更新推成死循环。
|
|
24
41
|
*/
|
|
25
42
|
export declare function getVisibleSelectableRowsSnapshot(): readonly PickItem[];
|
|
26
43
|
/**
|
|
27
|
-
* 渲染侧只读快照 +
|
|
28
|
-
*
|
|
44
|
+
* 渲染侧只读快照 + 登记订阅;`enabled === false` 不订阅也不读登记——行列表只随多选态存在,
|
|
45
|
+
* 模式外不必付订阅的成本。
|
|
29
46
|
*/
|
|
30
47
|
export declare function useVisibleSelectableRows(enabled: boolean): readonly PickItem[];
|
|
31
|
-
/**
|
|
32
|
-
export declare function selectAllVisible(): void;
|
|
33
|
-
/**
|
|
34
|
-
* 同一个切换口该显示「全选」还是「取消全选」:
|
|
35
|
-
* - 有选中、且当前可见可选行全都选中 ⇒ 取消全选;
|
|
36
|
-
* - 有选中、但当前没有可见可选行(全折叠/过滤掉)⇒ 只能清空,也显示取消全选;
|
|
37
|
-
* - 其余(含 0 条)⇒ 全选。
|
|
38
|
-
*/
|
|
39
|
-
export declare function areAllVisibleRowsSelected(rows: readonly PickItem[], picks: readonly PickItem[]): boolean;
|
|
40
|
-
/**
|
|
41
|
-
* 多选态内的整行点击守卫(document 捕获阶段)。
|
|
42
|
-
*
|
|
43
|
-
* 边界:行里的按钮 / 链接 / 输入 / 菜单项一律放行(否则 ⋯ 菜单、归档、置顶、单删都点不动);
|
|
44
|
-
* 双击的第二下与 dblclick 不翻转(第一下已经选中,第二下不能又反掉,也不能打开宿主的重命名)。
|
|
45
|
-
* 认行靠 `data-row-key="session:…"`——宿主将来改掉这个属性,最差是模式内点行不生效,
|
|
46
|
-
* 勾选框本身仍在。模式外不挂监听:这时点行必须还是宿主的"打开会话"。
|
|
47
|
-
*/
|
|
48
|
-
export declare function useSelectionRowClickGuard(): void;
|
|
49
|
-
/** 测试专用:登记表清空并标脏(会话 id 不跨用例复用,名字/运行态别串味);观察者归订阅者管。 */
|
|
48
|
+
/** 测试专用:登记表清空并标脏(会话 id 不跨用例复用,名字/运行态别串味)。 */
|
|
50
49
|
export declare function resetHostRows(): void;
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* 所以关闭/重开是**一段生命周期**,不是一个 id:删除成功后 `afterDelete` 关掉当前
|
|
12
12
|
* 主视图并记下"这一条是我们关掉的";撤销成功后 `afterRestore` 只重开自己记过的那些。调用方不再
|
|
13
13
|
* 各自判定该不该关、也不再把会话 id 塞进撤销提示传一圈——判据全在这一处。两次重开排队时只有最新
|
|
14
|
-
*
|
|
14
|
+
* 那条会打开(等待本身住 `src/client/pending-open.ts` 的票与订阅),`resetMainView()` 清掉这份记忆
|
|
15
|
+
* 并取消在等的重开。
|
|
15
16
|
*
|
|
16
17
|
* **不 import 宿主客户端包**(AGENTS.md 红线):只通过 `ctx.get('uiWorkspace')` 可选读服务,
|
|
17
18
|
* 形状不符/服务缺席都安静退回,绝不影响插件激活。字段与方法名来自上面两个包的产物原文;
|
|
@@ -2,18 +2,17 @@ import type { Translate } from './copy.js';
|
|
|
2
2
|
import './ui/styles.js';
|
|
3
3
|
/**
|
|
4
4
|
* `memo` 是必需的而不是优化:`react-markdown@10.1.0` 里**一处 memoization 都没有**(读实
|
|
5
|
-
* `node_modules/react-markdown/lib/index.js
|
|
5
|
+
* `node_modules/react-markdown/lib/index.js`——`Markdown()` 每次调用都 `createProcessor(options)`
|
|
6
6
|
* 再 `runSync(parse(file))`,`MarkdownHooks` 那一支也一样),而面板任何一次重渲(悬停、↑↓、
|
|
7
7
|
* 「刚刚删除」那句要读 `Date.now()`)都会走到这里 ⇒ 不按住就是每条助手回复每拍重解析一遍。
|
|
8
|
-
* 按住的条件是 `text` 与**探针 `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* prev/next 那一拍都取到新值、恒等;只有渲染时算好存进 props 的那份才是换语言那一刻的读数。
|
|
8
|
+
* 按住的条件是 `text` 与**探针 `probe`**:一句按当前 locale 现算的译文,专给 comparator 当
|
|
9
|
+
* 「换语言了没」的读数。为什么必须渲染时现算存进 props、为什么宿主 `t` 只能当稳定身份而不是
|
|
10
|
+
* 语言读数、「借哪句」,这些都住 `copy.ts` 的 `localeProbe`——调用方与测试一律
|
|
11
|
+
* `probe={localeProbe(t)}`,「借哪句」的知识不散到各调用方。
|
|
13
12
|
* 探针没变 = 同语言同 `t` ⇒ 跳过重解析;变了(换语言或换 `t`)⇒ 重解析。
|
|
14
13
|
*/
|
|
15
14
|
export declare const MarkdownBody: import("react").NamedExoticComponent<{
|
|
16
15
|
text: string;
|
|
17
16
|
t: Translate;
|
|
18
|
-
|
|
17
|
+
probe: string;
|
|
19
18
|
}>;
|