dsh-auto-collapse 0.1.7 → 0.2.1
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.en.md +54 -58
- package/README.md +65 -59
- package/assets/screenshot.png +0 -0
- package/lib/client.js +912 -2355
- package/lib/index.js +4 -46
- package/lib/types/client.d.ts +4 -1
- package/lib/types/controller.d.ts +50 -0
- package/lib/types/dom-view.d.ts +36 -0
- package/lib/types/fold.d.ts +2 -323
- package/lib/types/host-contract.d.ts +17 -0
- package/lib/types/index.d.ts +12 -11
- package/lib/types/scheduler.d.ts +10 -0
- package/lib/types/status-text.d.ts +14 -0
- package/lib/types/summary.d.ts +17 -0
- package/lib/types/work-groups.d.ts +30 -0
- package/lib/types/work-info.d.ts +4 -0
- package/lib/types/work-model.d.ts +24 -0
- package/package.json +14 -3
package/lib/index.js
CHANGED
|
@@ -2,55 +2,13 @@ import z from "@deepseek-ai/schemastery";
|
|
|
2
2
|
const name = "dsh-auto-collapse";
|
|
3
3
|
const inject = [];
|
|
4
4
|
const DEFAULT_STATUS_TEXT = "Deep sleeping...";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
const FIBER_DISPOSED = 4;
|
|
10
|
-
const FIBER_UNLOADING = 5;
|
|
11
|
-
function isUnloading(ctx) {
|
|
12
|
-
const state = ctx?.fiber?.state;
|
|
13
|
-
return state === FIBER_UNLOADING || state === FIBER_DISPOSED;
|
|
14
|
-
}
|
|
15
|
-
function installSettingsSection(ctx, ns, schema, entry, hooks) {
|
|
16
|
-
;
|
|
17
|
-
ctx.inject(["settings"], (sctx) => {
|
|
18
|
-
const scope = sctx.settings.register(ns, schema, {
|
|
19
|
-
base: entry,
|
|
20
|
-
...hooks.validate === void 0 ? {} : { validate: hooks.validate }
|
|
21
|
-
});
|
|
22
|
-
hooks.setSource(() => scope.get());
|
|
23
|
-
sctx.effect(() => () => {
|
|
24
|
-
if (isUnloading(ctx)) return;
|
|
25
|
-
hooks.setSource(() => entry);
|
|
26
|
-
hooks.onChange();
|
|
27
|
-
});
|
|
28
|
-
hooks.onChange();
|
|
29
|
-
scope.watch(() => {
|
|
30
|
-
if (isUnloading(ctx)) return;
|
|
31
|
-
hooks.onChange();
|
|
5
|
+
function apply(ctx, config = {}) {
|
|
6
|
+
ctx.inject(["settings"], (service) => {
|
|
7
|
+
service.settings.register(name, z.object({ statusText: z.string().default(DEFAULT_STATUS_TEXT) }), {
|
|
8
|
+
base: { statusText: config.statusText ?? DEFAULT_STATUS_TEXT }
|
|
32
9
|
});
|
|
33
10
|
});
|
|
34
11
|
}
|
|
35
|
-
function apply(ctx, config = {}) {
|
|
36
|
-
let current = () => ({ statusText: config.statusText ?? DEFAULT_STATUS_TEXT });
|
|
37
|
-
installSettingsSection(
|
|
38
|
-
ctx,
|
|
39
|
-
AUTO_COLLAPSE_SETTINGS_NAMESPACE,
|
|
40
|
-
AUTO_COLLAPSE_SETTINGS_SCHEMA,
|
|
41
|
-
{
|
|
42
|
-
statusText: config.statusText ?? DEFAULT_STATUS_TEXT
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
setSource: (source) => {
|
|
46
|
-
current = source;
|
|
47
|
-
},
|
|
48
|
-
onChange: () => {
|
|
49
|
-
void current;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
12
|
export {
|
|
55
13
|
apply,
|
|
56
14
|
inject,
|
package/lib/types/client.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export declare const name = "dsh-auto-collapse";
|
|
2
2
|
/**
|
|
3
3
|
* 不声明必需服务:slots/settingsScope 只是可选增强能力,不能阻止核心折叠
|
|
4
|
-
*
|
|
4
|
+
* 插件启动。下方通过 ctx.inject() 等待两个服务,支持晚于插件出现
|
|
5
|
+
* 以及卸载后重连;服务永久缺席时仅跳过设置卡片与配置订阅。
|
|
5
6
|
*/
|
|
6
7
|
export declare const inject: string[];
|
|
7
8
|
/** 客户端根上下文的最小结构化类型(仅用 cordis 标准 effect,无运行时依赖)。 */
|
|
@@ -9,5 +10,7 @@ export interface FoldClientCtx {
|
|
|
9
10
|
effect(fn: () => unknown, label?: string): unknown;
|
|
10
11
|
/** Cordis 动态客户端上下文提供的可选服务查询。 */
|
|
11
12
|
get?<T>(name: string): T | undefined;
|
|
13
|
+
/** 可选服务生命周期:服务齐备时进入,detach 时自动清理回调上的 effect。 */
|
|
14
|
+
inject?(services: readonly string[], setup: (ctx: FoldClientCtx) => void): void;
|
|
12
15
|
}
|
|
13
16
|
export declare function apply(ctx: FoldClientCtx): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Native L1 owns turns; this controller owns only mixed-work L2 groups. */
|
|
2
|
+
export declare class FoldController {
|
|
3
|
+
private flow;
|
|
4
|
+
private observer;
|
|
5
|
+
private view;
|
|
6
|
+
private readonly model;
|
|
7
|
+
private readonly state;
|
|
8
|
+
private readonly scheduler;
|
|
9
|
+
private readonly status;
|
|
10
|
+
private items;
|
|
11
|
+
private groups;
|
|
12
|
+
private infos;
|
|
13
|
+
private itemGroups;
|
|
14
|
+
private rowItems;
|
|
15
|
+
private seatGroups;
|
|
16
|
+
private turnGroups;
|
|
17
|
+
private controls;
|
|
18
|
+
private readonly dirtySeats;
|
|
19
|
+
private readonly dirtyItems;
|
|
20
|
+
private readonly dirtyGroups;
|
|
21
|
+
private modelDirty;
|
|
22
|
+
private discover;
|
|
23
|
+
private force;
|
|
24
|
+
private started;
|
|
25
|
+
private disposed;
|
|
26
|
+
private lastError;
|
|
27
|
+
private readonly stats;
|
|
28
|
+
private readonly ready;
|
|
29
|
+
private readonly visible;
|
|
30
|
+
private readonly reveal;
|
|
31
|
+
constructor(statusText?: () => string | undefined);
|
|
32
|
+
start(): void;
|
|
33
|
+
refresh(): void;
|
|
34
|
+
refreshStatus(): void;
|
|
35
|
+
/** Small diagnostics for regressions/performance, never rendered into the chat. */
|
|
36
|
+
diagnostics(): Readonly<typeof this.stats> & {
|
|
37
|
+
groups: number;
|
|
38
|
+
items: number;
|
|
39
|
+
};
|
|
40
|
+
private itemAt;
|
|
41
|
+
private dirtySeatGroups;
|
|
42
|
+
private mutations;
|
|
43
|
+
private switchFlow;
|
|
44
|
+
private rebuild;
|
|
45
|
+
private prepare;
|
|
46
|
+
private flush;
|
|
47
|
+
private toggle;
|
|
48
|
+
private revealAt;
|
|
49
|
+
stop(): void;
|
|
50
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { WorkItem } from './work-model.js';
|
|
2
|
+
import type { GroupSummary } from './summary.js';
|
|
3
|
+
export interface PreparedGroup {
|
|
4
|
+
id: string;
|
|
5
|
+
expanded: boolean;
|
|
6
|
+
summary: GroupSummary;
|
|
7
|
+
anchor?: HTMLElement;
|
|
8
|
+
desired: Set<HTMLElement>;
|
|
9
|
+
}
|
|
10
|
+
/** Own only group overlays and reversible hidden attributes, never native content or inline styles. */
|
|
11
|
+
export declare class GroupView {
|
|
12
|
+
private readonly flow;
|
|
13
|
+
private readonly toggle;
|
|
14
|
+
private readonly owner;
|
|
15
|
+
private readonly rows;
|
|
16
|
+
private readonly leases;
|
|
17
|
+
private readonly writes;
|
|
18
|
+
private readonly displays;
|
|
19
|
+
private readonly style;
|
|
20
|
+
constructor(flow: HTMLElement, toggle: (id: string) => void);
|
|
21
|
+
private writeHidden;
|
|
22
|
+
ownMutation(record: MutationRecord): boolean;
|
|
23
|
+
clearWrites(): void;
|
|
24
|
+
detachedGroups(nodes: readonly Node[]): string[];
|
|
25
|
+
beginRead(): void;
|
|
26
|
+
private fold;
|
|
27
|
+
private release;
|
|
28
|
+
private externallyHidden;
|
|
29
|
+
private create;
|
|
30
|
+
summarize(id: string, summary: GroupSummary, expanded: boolean): void;
|
|
31
|
+
prepare(id: string, items: readonly WorkItem[], expanded: boolean, summary: GroupSummary, turnCollapsed: boolean): PreparedGroup;
|
|
32
|
+
apply({ id, expanded, summary, anchor, desired }: PreparedGroup): void;
|
|
33
|
+
focus(id: string): void;
|
|
34
|
+
remove(id: string): void;
|
|
35
|
+
dispose(): void;
|
|
36
|
+
}
|
package/lib/types/fold.d.ts
CHANGED
|
@@ -1,323 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* 把会话流([data-chat-flow])里的“非正文 display”折叠成内联的一行,
|
|
5
|
-
* 折叠行实时显示**当前正在进行的工作**(与 Codex 对齐):
|
|
6
|
-
*
|
|
7
|
-
* - 块里有运行中的工具调用 → 标题 = "Running" + 工具名(Bash/Read/
|
|
8
|
-
* Search…,读 data-tool),摘要 = 正在执行的命令/路径/查询(读工具
|
|
9
|
-
* 卡片的 summary 行);标题与摘要带平滑呼吸动画(Pulse)。
|
|
10
|
-
* - 块里正在思考(think running)→ 标题 = "Thinking",摘要 = 思考的
|
|
11
|
-
* 最新一行(读 [data-follow-end],官方 ReasoningRow 的实时摘要锚点)。
|
|
12
|
-
* - 全部完成 → 标题 = 类型总结(编辑了文件 / 运行了命令 / 已思考 /
|
|
13
|
-
* 上下文注入),摘要清空;出错 → 红色,中断 → 琥珀。
|
|
14
|
-
*
|
|
15
|
-
* 另外把官方 ChatView 尾部的运行状态行文字(新版 "深度求索中..." / 旧版
|
|
16
|
-
* "Deep diving...",见 TURN_STATUS_COPY_RE)替换为可配置的状态提示词
|
|
17
|
-
* (默认 "Deep sleeping...";流光特效在 CSS 上,替换文本节点不影响,
|
|
18
|
-
* 宿主追加的用时后缀原样保留)。React 重渲染会恢复原文,pass() 每轮自愈改回。
|
|
19
|
-
* 设置为空时不替换,等价于恢复官方原文。
|
|
20
|
-
*
|
|
21
|
-
* 点击一行展开,再点收起;折叠态下若有行被选中(详情联动)自动展开。
|
|
22
|
-
*
|
|
23
|
-
* 折叠规则(沿用 dsh-web-archive 验证过的算法):每个回合合成一块——
|
|
24
|
-
* 某条消息的 Think 推理组与其后紧跟的工具组合并成一块(只有 think 或
|
|
25
|
-
* 只有工具组时各自成块),在块宿主**原位**插入 chip;带正文文本的消息
|
|
26
|
-
* 断开合并。结构保持 文本a - [折叠块] - 文本b - 文本c。
|
|
27
|
-
*
|
|
28
|
-
* 与 React 的关系:chip 插入 React 管理的 flow 子树内,但只做前置插入与
|
|
29
|
-
* style.display 切换(React 的 vdom diff 不会感知也不会清除 CSSOM 上的
|
|
30
|
-
* 手动样式);MutationObserver 每轮把结构变化合并到一次
|
|
31
|
-
* requestAnimationFrame 里重放(自愈:React 重渲染、切换会话、流式新
|
|
32
|
-
* 卡片都会自动跟上)。
|
|
33
|
-
*
|
|
34
|
-
* 零核心改动:不修改任何 slot 注册,不依赖任何 client 服务。
|
|
35
|
-
*/
|
|
36
|
-
export declare class FoldController {
|
|
37
|
-
private observer;
|
|
38
|
-
/** 防止同一个控制器重复注册 observer、定时器和可见性监听。 */
|
|
39
|
-
private started;
|
|
40
|
-
/** body 尚未创建时等待 DOMContentLoaded,再补一次启动。 */
|
|
41
|
-
private waitingForBody;
|
|
42
|
-
private readonly onDomContentLoaded;
|
|
43
|
-
private raf;
|
|
44
|
-
private timer;
|
|
45
|
-
private disposed;
|
|
46
|
-
private lastPassError;
|
|
47
|
-
private flow;
|
|
48
|
-
/** 稳定 block key → 当前 React 渲染中的 chip/host。 */
|
|
49
|
-
private chips;
|
|
50
|
-
private currentBlocks;
|
|
51
|
-
private blockExpanded;
|
|
52
|
-
/** host → 三级合并思考行(展开二级后连续思考合并显示为一个三级行)。 */
|
|
53
|
-
private mergedThinks;
|
|
54
|
-
/** 合并思考行的展开状态(true = 显示合并内容块)。 */
|
|
55
|
-
private mergedExpanded;
|
|
56
|
-
/** 合并内容缓存(首次从原生行读取后保存,pass 重建内容块时不再重新展开原生行)。 */
|
|
57
|
-
private mergedBodyTexts;
|
|
58
|
-
/** 合并行标题缓存(原生行展开态提取不到摘要时保持首次标题,不丢成“思考”)。 */
|
|
59
|
-
private mergedTitles;
|
|
60
|
-
/** 稳定 segment key → 一级折叠行与展开状态。 */
|
|
61
|
-
private segmentStates;
|
|
62
|
-
/** segment 首次观察到 running 的时间,用于没有官方时长的实时回合。 */
|
|
63
|
-
private runningSince;
|
|
64
|
-
/** 曾完成过的 segment key:段恢复运行时据此重开本地计时,防止重新结算
|
|
65
|
-
* 的本地时长吞掉完成间隙。 */
|
|
66
|
-
private completedOnce;
|
|
67
|
-
/** 插件改写 display 前的精确原值;受控集合用于分类漂移和 stop() 恢复。 */
|
|
68
|
-
private originalDisplay;
|
|
69
|
-
private controlledDisplay;
|
|
70
|
-
/** 元素 → 插件最后确保的 display 值:恢复前与当前内联值比对,漂移即视为
|
|
71
|
-
* 外部介入(镜像 turnStatusTexts 的 original/written 双快照守卫,issue #11)。 */
|
|
72
|
-
private writtenDisplay;
|
|
73
|
-
/** 被改写为状态提示词的原生状态文本:original = 宿主原文(卸载还原用),
|
|
74
|
-
* written = 插件最后一次写入的值(仅当节点仍等于它时才还原,避免覆盖
|
|
75
|
-
* 宿主在插件写入之后的状态更新)。 */
|
|
76
|
-
private turnStatusTexts;
|
|
77
|
-
/** 当前状态提示词读取器;返回空串时插件不替换状态行。 */
|
|
78
|
-
private statusTextProvider;
|
|
79
|
-
/** 正文判定缓存(消息元素 → 有无正文):流式期间只有被 mutation 命中的
|
|
80
|
-
* 消息失效重算,历史消息跨 pass 复用,避免每帧全量 TreeWalker。 */
|
|
81
|
-
private bodyTextCache;
|
|
82
|
-
/** 自上次 pass 以来子树发生变化的 flow 顶层消息;pass 开头统一失效。 */
|
|
83
|
-
private dirtyMessages;
|
|
84
|
-
/** 在途显示动画(元素 → 记录):冲突仲裁、记账对齐与生命周期清理的依据。
|
|
85
|
-
/** 在途显示动画(元素 → 记录):冲突仲裁、记账对齐与生命周期清理的依据。
|
|
86
|
-
* 用 Map 不用 WeakMap——switchFlow/stop 需要遍历全量 cancel。 */
|
|
87
|
-
private pendingAnims;
|
|
88
|
-
/** 手势点击的一次性可动画 block key;segment 级点击另保留中间正文的门控。 */
|
|
89
|
-
private animatableKeys;
|
|
90
|
-
/** segment 点击时只让点击前已存在的 block 播放 reveal;流式中新出现的
|
|
91
|
-
* 临时分裂块直接显示,避免分类收敛时留下半透明 stale chip。 */
|
|
92
|
-
private animatableSegmentBlocks;
|
|
93
|
-
/** 外部变更对账定时器句柄(自重排 setTimeout 链,见 armAuditLoop)。 */
|
|
94
|
-
private auditTimer;
|
|
95
|
-
/** 上一轮 pass 记录的关键元素内联 display,用于 audit 轻量检测漂移。
|
|
96
|
-
* audit 只读这份快照,不在页面稳定时重新执行完整 pass。 */
|
|
97
|
-
private auditDisplays;
|
|
98
|
-
/** 自上次 pass 以来 flow 子树发生过结构变化(childList)或正文判定翻转:
|
|
99
|
-
* 为 true 时 pass 重建分块快照,否则复用 currentBlocks——characterData/
|
|
100
|
-
* attributes 批次(流式文本、data-state 翻转)不改变块结构,跳过全量
|
|
101
|
-
* querySelectorAll 重扫(issue #14:长会话下每轮重扫造成主线程卡顿)。 */
|
|
102
|
-
private structureDirty;
|
|
103
|
-
/** 本轮 pass 的原生 turn 摘要快照(turn id → 开合态):每轮重建,不跨轮复用;
|
|
104
|
-
* data-open 翻转不改变块结构也能驱动重算,见 buildNativeTurnMap。 */
|
|
105
|
-
private nativeTurns;
|
|
106
|
-
/** 被折叠掏空后藏起的中间包装层(真机 44px vs 28px 真因:think 行全隐后
|
|
107
|
-
* 其父容器变零高度空壳,仍作为 flex item 参与父级 gap,凭空多出 16px)。
|
|
108
|
-
* 内容恢复(展开/流式追加/块转世)时同函数恢复显示;switchFlow 清空。 */
|
|
109
|
-
private emptiedWrappers;
|
|
110
|
-
/** 本轮 pass 内有过 display 实写(hide/restore 瞬时路径)。空洞发现的触发
|
|
111
|
-
* 条件之一:纯 display 收放不产生 childList,不触发 structureDirty。
|
|
112
|
-
* settle 触发的后续 pass 则由 settleFired 覆盖。读后即清(见发现调用点)。 */
|
|
113
|
-
private displayTouched;
|
|
114
|
-
/** 有 fade 自然结算过(chipSettle 跑过)。结算本身不写 display,但它意味着
|
|
115
|
-
* 某行刚变隐藏——空洞可能刚形成。 */
|
|
116
|
-
private settleFired;
|
|
117
|
-
/** 滚动稳定化(issue #14):flow 最近的滚动容器缓存(按 flow 身份失效)。 */
|
|
118
|
-
private scrollContainer;
|
|
119
|
-
private scrollContainerFlow;
|
|
120
|
-
/** 回到前台立即补一轮对账;后台 tab 由 document.hidden 门控跳过。 */
|
|
121
|
-
private readonly onVisibilityChange;
|
|
122
|
-
constructor(statusTextProvider?: () => string | undefined, options?: {
|
|
123
|
-
auditIntervalMs?: number;
|
|
124
|
-
});
|
|
125
|
-
private readonly auditIntervalMs;
|
|
126
|
-
/** 设置变更后重跑一轮,让状态提示词立即生效。 */
|
|
127
|
-
refresh(): void;
|
|
128
|
-
start(): void;
|
|
129
|
-
/** 外部显示变更对账循环(issue #11 Bug B):外部对宿主行的 style 写入不产生
|
|
130
|
-
* observer record(style 不在 attributeFilter 内,监听会因插件自身直写 style
|
|
131
|
-
* 自激),改用低频轻量对账兜底——发现漂移后才由 pass 收敛;
|
|
132
|
-
* 后台 tab 由 document.hidden 门控跳过,回前台由 visibilitychange 立即补一轮。
|
|
133
|
-
* 用自重排 setTimeout 链而非 setInterval:与 schedule 的兜底定时器同源,
|
|
134
|
-
* 测试桩 clearTimers 后链条自然熄灭。 */
|
|
135
|
-
private armAuditLoop;
|
|
136
|
-
private rearmAudit;
|
|
137
|
-
/** 低成本显示状态对账:只有发现外部漂移时才启动完整 pass。
|
|
138
|
-
*
|
|
139
|
-
* 外部 style.display 写入不会产生当前 observer 的 attribute 记录,
|
|
140
|
-
* 因此仍保留 audit;但稳定页面不应每秒重扫整个 flow。快照只覆盖
|
|
141
|
-
* flow 顶层行、插件控制中的宿主行和插件自有展示行,避免引入布局读取。 */
|
|
142
|
-
private audit;
|
|
143
|
-
stop(): void;
|
|
144
|
-
/** body 级 observer 只负责发现 flow 替换;已有 flow 外的文本变化不再触发全量扫描。 */
|
|
145
|
-
private shouldSchedule;
|
|
146
|
-
/** 判断 mutation 是否会影响宿主 flow;插件自有节点的回写直接忽略,
|
|
147
|
-
* 避免“pass 插入 chip → observer 再开一轮 pass”的自激循环。 */
|
|
148
|
-
private isRelevantMutation;
|
|
149
|
-
/** 记录本批 mutation 命中的 flow 顶层消息,供正文判定缓存定向失效。
|
|
150
|
-
* 从 record.target 沿 parentNode 走到 flow 的直接子级即所属消息。
|
|
151
|
-
* 失效粒度(issue #14):只有 childList 使分块快照失效;flow 直挂层的
|
|
152
|
-
* 插件节点/文本节点与 flow 外的混批记录不影响任何消息的正文判定,跳过
|
|
153
|
-
* 而非全量失效——旧逻辑把它们全部放大成 O(全会话) 的 TreeWalker 重扫,
|
|
154
|
-
* 长会话流式期间每帧如此。空批次仍保守全量失效(测试桩的调度通知)。 */
|
|
155
|
-
private markDirty;
|
|
156
|
-
private schedule;
|
|
157
|
-
/** 异步 observer 异常不能静默杀死协调器;保留非可视诊断并允许后续 mutation 重试。 */
|
|
158
|
-
private runPass;
|
|
159
|
-
private reportError;
|
|
160
|
-
/** 一轮重放:重算堆积 → 应用折叠/展开 → 摆放并更新 chip → 替换状态行。 */
|
|
161
|
-
private pass;
|
|
162
|
-
/** flow 最近的滚动容器(issue #14 滚动稳定化的测量基准):沿父链找第一个
|
|
163
|
-
* overflow-y 为 auto/scroll 且实际可滚动的祖先。结果按 flow 身份缓存;
|
|
164
|
-
* 找不到时不缓存(内容增长后祖先可能变为可滚动),每 pass 重探的代价
|
|
165
|
-
* 只是几次 clean-layout 的 computed style / scrollHeight 读取。 */
|
|
166
|
-
private findScrollContainer;
|
|
167
|
-
/** 几何写入前捕捉贴底意图(issue #14):视口距底 ≤ 上游 FOLLOW_THRESHOLD
|
|
168
|
-
* 时返回锚点,stabilizeScrollAfterFold 在写入后同帧钉回底部。远离底部
|
|
169
|
-
* (用户正在滚动浏览)时不干预——视口上方的高度变化由浏览器 scroll
|
|
170
|
-
* anchoring 补偿,视口下方的折叠不可见,插件再写 scrollTop 只会加入
|
|
171
|
-
* 上游吸底回写的拉锯。 */
|
|
172
|
-
private captureScrollAnchor;
|
|
173
|
-
/** 几何写入后把贴底视口钉回底部:折叠让 scrollHeight 缩小时若不在此帧
|
|
174
|
-
* 补写 scrollTop,宿主 ChatView 要到下一帧 ResizeObserver 才吸底回写,
|
|
175
|
-
* 中间的空档让触控板惯性滚动乘虚而入,反复折叠时表现为上下抽搐(用户
|
|
176
|
-
* 实测:长会话滚到底部附近无法稳定定位)。同一帧内钉回后,宿主的吸底
|
|
177
|
-
* 回写成为幂等 no-op,不再是第二个 scrollTop 写入方。 */
|
|
178
|
-
private stabilizeScrollAfterFold;
|
|
179
|
-
/** flow 元素变化即视为会话切换:完整恢复旧 flow,再从新 DOM 重建。 */
|
|
180
|
-
private switchFlow;
|
|
181
|
-
private createProcessedRow;
|
|
182
|
-
private syncProcessedRow;
|
|
183
|
-
private placeProcessedRow;
|
|
184
|
-
/** 原生收起时隐藏本块插件自有 overlay(chip/合并行+内容块):只做
|
|
185
|
-
* display 直写,不清钉住之外的账本、不删展开态,供原生再展开后复用。
|
|
186
|
-
* 在途 WAAPI 动画不追踪取消——收尾回调只做幂等终态对齐,无残留。 */
|
|
187
|
-
private hideOverlayForNativeCollapse;
|
|
188
|
-
private reconcileBlock;
|
|
189
|
-
/**
|
|
190
|
-
* 掏空包装层对账(真机 44px vs 28px 真因修复)。
|
|
191
|
-
*
|
|
192
|
-
* 折叠把某容器的子行全部 display:none 后,该容器变零高度空壳,但仍是
|
|
193
|
-
* 父级 flex 的 item 并参与 gap(如正文体 flex-column gap:16px),凭空多出
|
|
194
|
-
* 一份间距。隐藏这类空壳可让 gap 塌缩;子内容恢复可见(展开/流式追加/
|
|
195
|
-
* 块转世)时恢复显示。
|
|
196
|
-
*
|
|
197
|
-
* 安全边界:
|
|
198
|
-
* - 只看 block.host 内部、不含 host 本人(宿主显隐归 segment 逻辑);
|
|
199
|
-
* - 跳过结构 seat(data-chat-flow-kind / data-chat-anchor-key)与插件
|
|
200
|
-
* 自身 overlay(dshcf- 前缀类);
|
|
201
|
-
* - 只隐藏内容空洞(无可见子内容):零高度无文本的空壳 display:none 与
|
|
202
|
-
* 保持显示像素一致,只塌缩父级 gap;任何一方恢复内容即恢复显示。
|
|
203
|
-
*/
|
|
204
|
-
/**
|
|
205
|
-
* 空洞包装层兜底发现:按宿主直扫 div(仅结构变化轮次调用)。
|
|
206
|
-
* 只做隐藏侧:空洞即藏并记入 tracked;恢复侧由 syncEmptiedWrappers 的
|
|
207
|
-
* tracked 复核与 audit 承担。与按行 walk 共用同一安全边界(结构 seat /
|
|
208
|
-
* 插件 overlay / 宿主本人不碰)。
|
|
209
|
-
*/
|
|
210
|
-
private discoverHollowWrappers;
|
|
211
|
-
private syncEmptiedWrappers;
|
|
212
|
-
/** 内容空洞判定:无可见子内容(文本/元素任一可见即非空洞)。隐藏者是谁
|
|
213
|
-
* 不重要——零高度无文本节点 display:none 与保持显示像素一致(只塌缩父级
|
|
214
|
-
* gap,正是要修的幻影);任何一方恢复内容,下轮 pass/audit 即恢复显示。
|
|
215
|
-
* isDisplayed 走 getComputedStyle(桩内退化为内联,语义一致)。 */
|
|
216
|
-
private isHollow;
|
|
217
|
-
private ensureChip;
|
|
218
|
-
private suppressBlock;
|
|
219
|
-
private retainDisplayControl;
|
|
220
|
-
private cleanupStaleChips;
|
|
221
|
-
/** 连续思考合并行:插在第一个思考行前,标题用第一行思考内容;
|
|
222
|
-
* 点击切换显示/隐藏全部原始思考行。 */
|
|
223
|
-
private syncMergedThink;
|
|
224
|
-
/** 展开合并行:直接读各思考行文本合成内容块(不依赖原生行展开:
|
|
225
|
-
* 程序化 click 不触发 React 展开,且后台 tab 的 rAF 不执行)。
|
|
226
|
-
* 返回是否成功——思考行已不可读(parts 为空)时返回 false,调用方
|
|
227
|
-
* 据此保持收起态,避免展开状态与内容块脱节。 */
|
|
228
|
-
private expandMergedBody;
|
|
229
|
-
/** 创建/更新合并内容块(缓存优先,不重新展开原生行)。
|
|
230
|
-
* 返回内容块与其是否为本次新建(新建才走展开动画)。 */
|
|
231
|
-
private ensureMergedBody;
|
|
232
|
-
/** 清理合并 think 行(v12):状态 map 立即清除;DOM 在手势动画路径下
|
|
233
|
-
* 渐隐后移除(settle 回调),其余路径瞬删。渐隐中途被反向取消时元素
|
|
234
|
-
* 保留,由后续 pass 的 syncMergedThink 重建/复用。
|
|
235
|
-
* settle 透传给每个渐隐目标的移除回调之后(chip 间距钉住的结算探测点,
|
|
236
|
-
* AI 评审 P0:merged 行渐隐不走 block.rows,必须纳入同一钉住体系)。 */
|
|
237
|
-
private releaseMergedThink;
|
|
238
|
-
/** merged-body 展开高度动画(机制样板:插件全资 DOM)。
|
|
239
|
-
* 关键帧含 marginBottom 0→16px——其 CSS 有常量 margin-bottom:16px,
|
|
240
|
-
* 高度从 0 起步时这 16px 会先占位产生小跳变。fill:'forwards' 托住终态,
|
|
241
|
-
* onfinish 清内联后 cancel 释放,无闪烁窗口。收起由 collapseMergedBody
|
|
242
|
-
* 做镜像高度卷下(同款账本与身份守卫),开合对称。 */
|
|
243
|
-
private revealMergedBody;
|
|
244
|
-
/** 收起合并行:内容块高度卷下后移除——镜像 revealMergedBody 的唯一几何动画,
|
|
245
|
-
* 开合对称。插件全资静态文本 DOM、无 React 协调竞争,可安全做几何收起
|
|
246
|
-
* (与 seat 级拒绝盲卷的场景不同:那里是 React 混杂多卡片)。
|
|
247
|
-
* reduced-motion / 无 WAAPI / 零高度降级为同步 remove()。 */
|
|
248
|
-
private collapseMergedBody;
|
|
249
|
-
/** 当前宿主内的思考行(现取,React 重渲染后引用仍然有效)。 */
|
|
250
|
-
private currentThinkRows;
|
|
251
|
-
/** 移除合并思考行(二级收起 / 一级收起时),恢复行由 applyRows 控制。
|
|
252
|
-
* 合并内容块(btn 的兄弟节点)一并移除,避免宿主展开后残留文本。 */
|
|
253
|
-
private removeMergedThink;
|
|
254
|
-
/** 正文判定(带缓存):同一消息子树未变时直接复用上次结果。失效由
|
|
255
|
-
* markDirty(mutation 定向)与 switchFlow(整体重置)驱动;缓存的是
|
|
256
|
-
* 纯文本/媒体存在性判定,与 display 状态无关,插件自身的显隐切换
|
|
257
|
-
* 不会产生脏数据。 */
|
|
258
|
-
private hasBodyCached;
|
|
259
|
-
/** 本块是否有在途收起渐隐(rows/containers/merged 行/body 任一)。
|
|
260
|
-
* 基于 pendingAnims 账本无状态判定:onfinish/oncancel 都会即时清账,
|
|
261
|
-
* 取消路径天然解锁(计数器/最后注册者会卡死)。merged 行渐隐时已被
|
|
262
|
-
* releaseMergedThink 摘出 mergedThinks,按 DOM 类名现查。 */
|
|
263
|
-
private hasPendingCollapse;
|
|
264
|
-
/** 钉住 chip 与首行的 16px 间距(收起 fade 期间;内联优先于 aria=false 的 0)。
|
|
265
|
-
* flow-chip(context 等 before-mounted)豁免:其间距由宿主 row-gap 16px
|
|
266
|
-
* 提供、自身 CSS 恒 0,钉住 16px 会叠加成 32px(真机实测:收起上下文
|
|
267
|
-
* 注入时二级与三级间距瞬间扩大)。
|
|
268
|
-
*/
|
|
269
|
-
private pinChipMargin;
|
|
270
|
-
/** 解除钉住(aria=true 的 16px 或 aria=false 的 0 由 CSS 接管)。 */
|
|
271
|
-
private unpinChipMargin;
|
|
272
|
-
/** 外部介入检测(issue #11 Bug A):当前内联值 ≠ 插件最后确保值,或所有权
|
|
273
|
-
* 哨兵被 style 整体改写抹除。返回 true 时调用方放弃本次写回并交还账本——
|
|
274
|
-
* 属性级改写由值比对捕获,整体改写(cssText / setAttribute('style'))由
|
|
275
|
-
* 哨兵缺失捕获,两层合起来覆盖外部介入的两种形态。 */
|
|
276
|
-
private displayForeign;
|
|
277
|
-
/** 清空单个元素的显示账本(三账本 + 所有权哨兵)。 */
|
|
278
|
-
private releaseDisplayLedger;
|
|
279
|
-
/** 返回 true 表示启动了渐隐动画(调用方可据此决定内部元素的处置)。
|
|
280
|
-
* settle 在渐隐自然结束时调用(onfinish 链;反向取消不触发)。 */
|
|
281
|
-
private hideElement;
|
|
282
|
-
private restoreElement;
|
|
283
|
-
/** 是否可动画:WAAPI 特性检测 + reduced-motion 门控(均做 typeof 防桩缺失)。 */
|
|
284
|
-
private canAnimate;
|
|
285
|
-
/** 展开方向淡入(opacity + 4px 微位移):无高度分量、零布局读取。
|
|
286
|
-
* onfinish 按终态可见对齐账本(双删除)并 schedule() 幂等重同步;
|
|
287
|
-
* oncancel 只做身份守卫删除——取消方的终态写入自己负责。 */
|
|
288
|
-
private startReveal;
|
|
289
|
-
/** 同步取消在途动画并清账:收起动画需同时清锁高内联(height/overflow/
|
|
290
|
-
* marginBottom),否则取消方写完终态后元素仍被锁高裁剪一帧以上。 */
|
|
291
|
-
private cancelPendingSync;
|
|
292
|
-
private clearCollapseLock;
|
|
293
|
-
/** 祖先 seat 在途动画检测:沿 parentNode 走到 flow,任一祖先在 pendingAnims
|
|
294
|
-
* 即视为在途。分层规则——同一视觉变化只动画一层。 */
|
|
295
|
-
private hasAnimatingAncestor;
|
|
296
|
-
/** 收起方向渐隐动画(v11 定稿):镜像 reveal 的 opacity + 4px 微位移,
|
|
297
|
-
* 淡完 onfinish 写 display:none 并保持双条目(镜像 hideElement 终态契约)。
|
|
298
|
-
* fill:'forwards' 占位到终态写入后释放;无几何锁、无 gap 补偿。 */
|
|
299
|
-
private startFadeCollapse;
|
|
300
|
-
/**
|
|
301
|
-
* 收起 fade 终态结算(onfinish 与僵尸收割共用)。仅被同元素新动画
|
|
302
|
-
* supersede 时早退;被 sweep 删账(元素已断连)或收割时仍执行 settle——
|
|
303
|
-
* 回调经 controller 可达,动画必然触发 onfinish,早退会把 chip 内联 16px
|
|
304
|
-
* 钉住永久残留。重挂载同节点不可能(React 只建新节点),重启动的新动画
|
|
305
|
-
* 由第一条守卫覆盖,无误伤。
|
|
306
|
-
*/
|
|
307
|
-
private finishFadeCollapse;
|
|
308
|
-
/** 动画是否已播完(终态未结算):finish 事件丢失时的兜底判定。WAAPI 桩
|
|
309
|
-
* 无 playState/effect 时一律 false,走正常事件路径,测试行为不变。 */
|
|
310
|
-
private isAnimOverdue;
|
|
311
|
-
/** 轻量视觉 reveal(opacity + 4px 微位移):用于插件全资元素的即时显示
|
|
312
|
-
* 路径——chip(一级展开时出现)与 merged-think 行(二级展开时出现)。
|
|
313
|
-
* 这些元素的 display 完全由插件直写、无 React 协调竞争,因此不入
|
|
314
|
-
* pendingAnims 账本、无仲裁;收起同为直写 display:none,无 fill 的在途
|
|
315
|
-
* 动画残留在隐藏元素上自然失效。门控沿用 animate 布尔(手势路径才调)。 */
|
|
316
|
-
private revealVisual;
|
|
317
|
-
private restoreUnusedDisplays;
|
|
318
|
-
/** 收集 audit 需要观察的 display 集合;只读取内联样式,不触发布局计算。 */
|
|
319
|
-
private collectAuditDisplays;
|
|
320
|
-
/** 在完整 pass 完成后保存 display 基线,供下一次轻量 audit 比对。 */
|
|
321
|
-
private captureAuditDisplays;
|
|
322
|
-
private restoreAllDisplays;
|
|
323
|
-
}
|
|
1
|
+
/** Public controller name retained for source integrations; the legacy fold engine is removed. */
|
|
2
|
+
export { FoldController } from './controller.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** DSH 0.1.2 DOM contract. Keep host recognition separate from grouping and rendering. */
|
|
2
|
+
export declare const FLOW = "[data-chat-flow]";
|
|
3
|
+
export declare const SEAT = "[data-chat-flow-kind]";
|
|
4
|
+
export declare const OWNED = "[data-dshcf-group]";
|
|
5
|
+
export declare const FOLDED = "data-dshcf-folded";
|
|
6
|
+
export declare const NATIVE_HIDDEN = "[data-turn-process-hidden], [data-turn-process-inline]";
|
|
7
|
+
export declare function elementOf(node: Node): Element | null;
|
|
8
|
+
export declare function pluginOwned(node: Node): boolean;
|
|
9
|
+
export declare function seatOf(node: Node, flow: HTMLElement): HTMLElement | null;
|
|
10
|
+
export declare function seatKey(seat: HTMLElement): string | null;
|
|
11
|
+
export declare function nativeHidden(row: HTMLElement, flow: HTMLElement): boolean;
|
|
12
|
+
export declare function nativeOwnsHidden(element: HTMLElement): boolean;
|
|
13
|
+
export declare function findFlow(): HTMLElement | null;
|
|
14
|
+
export declare function nativeControls(flow: HTMLElement): Map<string, HTMLButtonElement>;
|
|
15
|
+
export declare function nativeCollapsed(button: HTMLButtonElement | undefined): boolean;
|
|
16
|
+
export declare function openNative(button: HTMLButtonElement | undefined): void;
|
|
17
|
+
export declare const OBSERVED_ATTRIBUTES: string[];
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
/** Host 插件名。 */
|
|
2
1
|
export declare const name = "dsh-auto-collapse";
|
|
3
|
-
/** Host 侧不注入额外服务;settings 是可选的运行能力,由下方接线惰性接入。 */
|
|
4
2
|
export declare const inject: string[];
|
|
5
|
-
/** 插件配置。 */
|
|
6
3
|
export interface Config {
|
|
7
|
-
/**
|
|
4
|
+
/** Empty text restores the official status copy. */
|
|
8
5
|
statusText?: string;
|
|
9
6
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
export interface SettingsHostContext {
|
|
8
|
+
inject(services: readonly string[], setup: (ctx: {
|
|
9
|
+
settings: {
|
|
10
|
+
register(namespace: string, schema: unknown, options: {
|
|
11
|
+
base: {
|
|
12
|
+
statusText: string;
|
|
13
|
+
};
|
|
14
|
+
}): unknown;
|
|
15
|
+
};
|
|
16
|
+
}) => void): unknown;
|
|
15
17
|
}
|
|
16
|
-
|
|
17
|
-
export declare function apply(ctx: unknown, config?: Config): void;
|
|
18
|
+
export declare function apply(ctx: SettingsHostContext, config?: Config): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** One coalesced frame, with a background-tab fallback; no polling/audit loop. */
|
|
2
|
+
export declare class Scheduler {
|
|
3
|
+
private readonly run;
|
|
4
|
+
private frame;
|
|
5
|
+
private timeout;
|
|
6
|
+
private disposed;
|
|
7
|
+
constructor(run: () => void);
|
|
8
|
+
schedule(): void;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Optional status copy is independent of work grouping and never reads message text. */
|
|
2
|
+
export declare class StatusTextController {
|
|
3
|
+
private readonly read;
|
|
4
|
+
private flow;
|
|
5
|
+
private observer;
|
|
6
|
+
private readonly texts;
|
|
7
|
+
private readonly scheduler;
|
|
8
|
+
constructor(read: () => string | undefined);
|
|
9
|
+
setFlow(flow: HTMLElement | null): void;
|
|
10
|
+
refresh(): void;
|
|
11
|
+
private update;
|
|
12
|
+
private restore;
|
|
13
|
+
dispose(): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { WorkKind } from './work-groups.js';
|
|
2
|
+
export type WorkStatus = 'running' | 'done' | 'error' | 'stopped' | 'waiting';
|
|
3
|
+
export interface WorkInfo {
|
|
4
|
+
kind: WorkKind;
|
|
5
|
+
action: string;
|
|
6
|
+
detail: string;
|
|
7
|
+
status: WorkStatus;
|
|
8
|
+
}
|
|
9
|
+
export interface GroupSummary {
|
|
10
|
+
text: string;
|
|
11
|
+
detail: string;
|
|
12
|
+
status: WorkStatus;
|
|
13
|
+
kind: WorkKind;
|
|
14
|
+
}
|
|
15
|
+
export declare function toolAction(tool: string): string;
|
|
16
|
+
export declare function normalizeStatus(value: string | null): WorkStatus;
|
|
17
|
+
export declare function summarize(infos: readonly WorkInfo[]): GroupSummary;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** DOM-independent, ordered work grouping. Prose and turn changes are boundaries. */
|
|
2
|
+
export type WorkKind = 'think' | 'tool' | 'context' | 'command';
|
|
3
|
+
export type FlowToken = {
|
|
4
|
+
type: 'work';
|
|
5
|
+
id: string;
|
|
6
|
+
turn: string | null;
|
|
7
|
+
kind: WorkKind;
|
|
8
|
+
} | {
|
|
9
|
+
type: 'boundary';
|
|
10
|
+
id: string;
|
|
11
|
+
turn: string | null;
|
|
12
|
+
hard: boolean;
|
|
13
|
+
};
|
|
14
|
+
export interface WorkGroup {
|
|
15
|
+
id: string;
|
|
16
|
+
turn: string | null;
|
|
17
|
+
items: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
/** Unscoped context can inherit an unambiguous adjacent turn, never across a user fence. */
|
|
20
|
+
export declare function resolveTurns(tokens: readonly FlowToken[]): FlowToken[];
|
|
21
|
+
export declare function groupWork(tokens: readonly FlowToken[]): WorkGroup[];
|
|
22
|
+
/** Expansion belongs to a work interval, not its current running item or DOM node. */
|
|
23
|
+
export declare class GroupState {
|
|
24
|
+
private groups;
|
|
25
|
+
private expanded;
|
|
26
|
+
reconcile(groups: readonly WorkGroup[]): void;
|
|
27
|
+
isExpanded(id: string): boolean;
|
|
28
|
+
setExpanded(id: string, value: boolean): void;
|
|
29
|
+
clear(): void;
|
|
30
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { WorkItem } from './work-model.js';
|
|
2
|
+
import { type WorkInfo } from './summary.js';
|
|
3
|
+
export declare function readWorkInfo(item: WorkItem): WorkInfo;
|
|
4
|
+
export declare function needsAttention(items: readonly WorkItem[], infos: ReadonlyMap<string, WorkInfo>): boolean;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FlowToken, WorkKind } from './work-groups.js';
|
|
2
|
+
export interface WorkItem {
|
|
3
|
+
id: string;
|
|
4
|
+
turn: string | null;
|
|
5
|
+
kind: WorkKind;
|
|
6
|
+
seat: HTMLElement;
|
|
7
|
+
row: HTMLElement;
|
|
8
|
+
/** Largest ancestor proved to contain only work, never a prose ancestor. */
|
|
9
|
+
cover: HTMLElement;
|
|
10
|
+
/** DSH leaves scoped system prompts outside its native process membership. */
|
|
11
|
+
followNativeTurn: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface WorkSnapshot {
|
|
14
|
+
tokens: FlowToken[];
|
|
15
|
+
items: Map<string, WorkItem>;
|
|
16
|
+
}
|
|
17
|
+
/** Cache structure per host seat. Text inside a work row does not invalidate this index. */
|
|
18
|
+
export declare class WorkModel {
|
|
19
|
+
private seats;
|
|
20
|
+
private identities;
|
|
21
|
+
private sequence;
|
|
22
|
+
read(flow: HTMLElement, dirty: ReadonlySet<HTMLElement>, force?: boolean): WorkSnapshot;
|
|
23
|
+
clear(): void;
|
|
24
|
+
}
|