dsh-quick-toc 0.7.3 → 0.7.4
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/CHANGELOG.en.md +8 -0
- package/CHANGELOG.md +8 -0
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/lib/client.js +90 -15
- package/package.json +1 -1
package/CHANGELOG.en.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to **dsh-quick-toc** are documented here. Chinese version: [CHANGELOG.md](CHANGELOG.md).
|
|
4
4
|
|
|
5
|
+
## [0.7.4] - 2026-09-23
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Opening the curtain had no animation on a long session**: opening the curtain made the outline's own auto-loader pull the whole session in at once (measured: DOM nodes 1365 to 46150, single long tasks of 275-897ms), so the curtain's 0.46s drop was over before a frame of it could paint. Automatic loading now requires a real reader gesture (a wheel tick on the list, a key on the panel, a press on the list); a geometric coincidence (a short list that happens to fit, a freshly opened curtain, a resized window) is no longer treated as a load request.
|
|
9
|
+
- **Switching the dock made the panel disappear on one side and slide in from the other**: the switch moved `left`, which is not in the transition list, so the box teleported while only the offset was tweened. The panel now always sits on one fixed anchor, and its open, collapsed, either-dock and curtain-handoff positions are all offsets on that anchor, so a dock switch is a pure translate animation.
|
|
10
|
+
- **The collapsed panel came to rest left of the position it opens from (8px on the left dock)**: the collapse clip was a fixed panel width (plus 8px on the left dock) while the box actually travelled a panel width plus 16px, so the visible edge drifted. The clip now matches the travel, which keeps the visible edge on the panel's own open edge line all the way out, the same on both docks.
|
|
11
|
+
- **A collapse occasionally came to rest in the wrong place**: the host re-measures the conversation area while the collapse is running, which moves the line the panel aims at (measured: the tuck-in landed at 1396 instead of 1526). The open-to-closed step now freezes the geometry that collapse started with; the next open reads the area afresh.
|
|
12
|
+
|
|
5
13
|
## [0.7.3] - 2026-09-23
|
|
6
14
|
|
|
7
15
|
### Changed
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
**dsh-quick-toc** 的重要变更都记录在这里。英文版见 [CHANGELOG.en.md](CHANGELOG.en.md)。
|
|
4
4
|
|
|
5
|
+
## [0.7.4] - 2026-09-23
|
|
6
|
+
|
|
7
|
+
### 修复
|
|
8
|
+
- **长会话里打开幕布没有展开动画**:打开幕布时,本插件自己的自动加载把整段会话一次性拉了进来(实测 DOM 节点 1365 → 46150、单帧长任务 275–897ms),0.46 秒的展开动画在第一帧画出来之前就播完了。自动加载现在必须由读者的真实操作触发(列表上的滚轮、面板上的按键、在列表上按下),几何上的巧合(短列表恰好装得下、刚打开幕布、窗口改变尺寸)不再被当成加载请求。
|
|
9
|
+
- **切换停靠边时面板先消失、再从另一边滑出来**:切换原本移动 `left`,而 `left` 不在过渡列表里——盒子瞬移,只有位移在补间。面板现在始终停在一个固定锚点上,展开、收起、两个停靠边、交给幕布这四个位置都是锚点上的位移,切换停靠边是一段纯位移动画。
|
|
10
|
+
- **收起后停的位置比展开位置偏左(左停靠实测 8px)**:收起的裁剪量原本是面板宽度(左停靠另加 8px),与盒子真正滑动的距离(左停靠为面板宽度 + 16px)不相等,可见边一路漂移。裁剪量改为与实际滑动距离一致后,可见边全程钉在面板自己那条开合线上,左右停靠对称。
|
|
11
|
+
- **收起时偶尔收偏**:宿主会在收起动画途中重新测量会话区,目标线随之改变(实测收进去的位置从 1526 跑到 1396)。收起开始的那一步现在冻结本次动画使用的几何,之后再展开才读取新几何。
|
|
12
|
+
|
|
5
13
|
## [0.7.3] - 2026-09-23
|
|
6
14
|
|
|
7
15
|
### 变更
|
package/README.en.md
CHANGED
|
@@ -42,7 +42,7 @@ A conversation TOC plugin for [DeepSeek Harness](https://github.com/deepseek-ai/
|
|
|
42
42
|
|
|
43
43
|
| Plugin | Supported DSH |
|
|
44
44
|
| --- | --- |
|
|
45
|
-
| **0.7.x** (latest, 0.7.
|
|
45
|
+
| **0.7.x** (latest, 0.7.4) | 0.1.5-rc.1, 0.1.5-rc.2, 0.1.5-rc.3, 0.1.7-alpha.1, 0.1.7-alpha.2 |
|
|
46
46
|
| 0.6.x (0.6.3) | 0.1.5-rc.1, 0.1.5-rc.2 |
|
|
47
47
|
| 0.5.x (0.5.1) | 0.1.5-rc.1, 0.1.5-rc.2 |
|
|
48
48
|
| 0.4.x (0.4.1) | 0.1.5-rc.1, 0.1.5-rc.2 |
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
| 插件版本 | 支持的 DSH 版本 |
|
|
44
44
|
| --- | --- |
|
|
45
|
-
| **0.7.x**(最新,0.7.
|
|
45
|
+
| **0.7.x**(最新,0.7.4) | 0.1.5-rc.1、0.1.5-rc.2、0.1.5-rc.3、0.1.7-alpha.1、0.1.7-alpha.2 |
|
|
46
46
|
| 0.6.x(0.6.3) | 0.1.5-rc.1、0.1.5-rc.2 |
|
|
47
47
|
| 0.5.x(0.5.1) | 0.1.5-rc.1、0.1.5-rc.2 |
|
|
48
48
|
| 0.4.x(0.4.1) | 0.1.5-rc.1、0.1.5-rc.2 |
|
package/lib/client.js
CHANGED
|
@@ -51,6 +51,10 @@ window.__ModuleLoader__.load({
|
|
|
51
51
|
// panel's own collapse duration (0.28s) plus a hair, so the curtain's drop only
|
|
52
52
|
// starts once the panel has actually finished travelling.
|
|
53
53
|
var PANEL_TUCK_MS = 300;
|
|
54
|
+
// How long a collapse keeps the edge it started from (see the geometry block): the
|
|
55
|
+
// panel's collapse takes 0.28s, and the host can re-measure the conversation area at
|
|
56
|
+
// any point while it runs, so the freeze outlives the motion by a frame or two.
|
|
57
|
+
var COLLAPSE_HOLD_MS = 420;
|
|
54
58
|
// The row-end control ("jump to the end of this section") lives in the last 24px
|
|
55
59
|
// of a row (a 20px button inset 4px). Every box a row can paint — the hover
|
|
56
60
|
// highlight, a group header's hover label, the right-aligned context text — stops
|
|
@@ -1719,8 +1723,16 @@ window.__ModuleLoader__.load({
|
|
|
1719
1723
|
var autoWatchRef = react.useRef(null); // did the pull we started actually land? (fallback click)
|
|
1720
1724
|
var autoProbeRef = react.useRef(0); // throttle for the on-scroll approach probe
|
|
1721
1725
|
var autoStampRef = react.useRef(0);
|
|
1726
|
+
var autoGestureRef = react.useRef(0); // last REAL reader gesture on the outline list
|
|
1722
1727
|
var autoLoadRef = react.useRef(true);
|
|
1723
1728
|
var panelVisibleRef = react.useRef(true);
|
|
1729
|
+
// A collapse keeps the geometry it started with: the host re-measures the
|
|
1730
|
+
// conversation area while our panel is sliding away, and reading the new numbers
|
|
1731
|
+
// live moves the line the panel tucks into — it then settles somewhere other than
|
|
1732
|
+
// the line it slid out from. While a collapse runs, the edge it aims at is frozen.
|
|
1733
|
+
var geoOpenRef = react.useRef(null);
|
|
1734
|
+
var geoHoldRef = react.useRef(0);
|
|
1735
|
+
var geoSnapRef = react.useRef(null);
|
|
1724
1736
|
// The reader's place in the outline: sampled by the scroll handler and right
|
|
1725
1737
|
// before any change of our own, restored in a LAYOUT effect (see the paging
|
|
1726
1738
|
// block). `anchorHintRef` remembers where the visible boundary was, so finding it
|
|
@@ -2586,6 +2598,15 @@ window.__ModuleLoader__.load({
|
|
|
2586
2598
|
var AUTO_WAIT_MS = 40; // beat while a page is in flight: the host is the limit
|
|
2587
2599
|
var AUTO_BURST_MAX = 60; // runaway guard only: a long gap must stay walkable
|
|
2588
2600
|
var AUTO_BURST_IDLE_MS = 3000; // quiet this long and the budget is fresh again
|
|
2601
|
+
// Loading is something the READER asks for, so every automatic pull must be
|
|
2602
|
+
// answered by a real gesture (a wheel tick, a press, a key) within this window.
|
|
2603
|
+
// Purely geometric triggers — a freshly laid out list whose few rows all fit the
|
|
2604
|
+
// box, a panel/curtain opened, a window resized — look exactly like "an unloaded
|
|
2605
|
+
// turn is in view" without anyone having asked: opening the curtain once made this
|
|
2606
|
+
// loader call `loadThrough` on the first ghost it found, which materialised the
|
|
2607
|
+
// rest of the session (46k nodes, 300–900ms long tasks) before the curtain's own
|
|
2608
|
+
// 0.46s drop animation could paint. A gesture-gated loader cannot do that.
|
|
2609
|
+
var AUTO_GESTURE_MS = 1500;
|
|
2589
2610
|
var ghostInView = function (el) {
|
|
2590
2611
|
var ghosts = el.querySelectorAll("[data-ghost-turn]");
|
|
2591
2612
|
if (ghosts.length === 0) return null;
|
|
@@ -2728,6 +2749,10 @@ window.__ModuleLoader__.load({
|
|
|
2728
2749
|
return true;
|
|
2729
2750
|
}
|
|
2730
2751
|
if (historyExhausted(face)) return false;
|
|
2752
|
+
// Nothing the reader did — no wheel, no press, no key — since the window above:
|
|
2753
|
+
// a geometric coincidence (a fresh list that happens to fit, a newly opened
|
|
2754
|
+
// curtain, a resized window) is not a request to load history.
|
|
2755
|
+
if (Date.now() - autoGestureRef.current > AUTO_GESTURE_MS) return false;
|
|
2731
2756
|
// A ghost the reader can actually see, or the gap coming up on them.
|
|
2732
2757
|
if (ghostTurn !== null || frontierApproaching(el)) {
|
|
2733
2758
|
// ARRIVED at an unloaded turn: cover it in one host call (`loadThrough`), which is
|
|
@@ -2815,6 +2840,7 @@ window.__ModuleLoader__.load({
|
|
|
2815
2840
|
// always refreshes older turns, even without a visible scrollbar
|
|
2816
2841
|
var onListWheel = function (e) {
|
|
2817
2842
|
outlineTouchRef.current = Date.now();
|
|
2843
|
+
autoGestureRef.current = Date.now(); // a wheel tick is a reader gesture
|
|
2818
2844
|
var el = listRef.current;
|
|
2819
2845
|
if (!el) return;
|
|
2820
2846
|
hoverEnd();
|
|
@@ -3964,9 +3990,23 @@ window.__ModuleLoader__.load({
|
|
|
3964
3990
|
// ---- nothing to show without headings ----
|
|
3965
3991
|
if (groups.length === 0) return null;
|
|
3966
3992
|
|
|
3967
|
-
// ---- geometry:
|
|
3968
|
-
//
|
|
3969
|
-
//
|
|
3993
|
+
// ---- geometry: ONE parked anchor; the dock side lives in the transform --------------
|
|
3994
|
+
// The box is parked at a single spot — off-area to the LEFT of the conversation — and
|
|
3995
|
+
// every position the panel takes (open, collapsed on either dock, handed to the
|
|
3996
|
+
// curtain) is a `transform` offset from there. Two reasons:
|
|
3997
|
+
// * a dock switch then animates the TRANSFORM alone, so the panel travels across the
|
|
3998
|
+
// screen on the compositor. Docking used to move `left` (which is not in the
|
|
3999
|
+
// transition list): the box teleported to the other side while only the transform
|
|
4000
|
+
// animated, so the panel vanished on one side and slid in from the other.
|
|
4001
|
+
// * `left` must never be animated: it re-lays out the whole panel — header, toolbar
|
|
4002
|
+
// and every rendered row — on every frame, and with a long session loaded that
|
|
4003
|
+
// layout cannot keep up (the wipe ran while the slide stalled, and the panel
|
|
4004
|
+
// flickered). Adding `left` to the transition for a dock switch was measured as
|
|
4005
|
+
// "very laggy and flashing" for exactly this reason.
|
|
4006
|
+
// The COLLAPSED spot is per dock (left: the anchor itself; right: one panel width past
|
|
4007
|
+
// the open position), which is what makes the collapse fly out toward its own edge.
|
|
4008
|
+
// (The scale preference leaves all of this alone: it acts on the panel's inner box,
|
|
4009
|
+
// so the panel itself keeps the width and height the reader dragged.)
|
|
3970
4010
|
var dockRight = dock === "right";
|
|
3971
4011
|
// Keep the OPEN panel inside the window whatever the conversation area does.
|
|
3972
4012
|
// The area is the host's to move: its transcript width grips rewrite the
|
|
@@ -3980,27 +4020,48 @@ window.__ModuleLoader__.load({
|
|
|
3980
4020
|
return Math.max(8, Math.min(Math.max(8, window.innerWidth - panelW - 8), x));
|
|
3981
4021
|
};
|
|
3982
4022
|
// While the panel is being handed over to (or back from) the curtain it is
|
|
3983
|
-
// rendered at its COLLAPSED geometry, so the same
|
|
4023
|
+
// rendered at its COLLAPSED geometry, so the same transform/clip transition that
|
|
3984
4024
|
// serves open/collapse also serves the handoff: no teleport, no flash.
|
|
3985
4025
|
var geoOpen = open && !panelAside;
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
// slide is compositable. `openLeft` is the clamped open position, `parkedLeft` the
|
|
3989
|
-
// off-area spot the collapsed panel occupies.
|
|
3990
|
-
var openLeft, parkedLeft;
|
|
4026
|
+
var anchorLeft = viewport ? viewport.left - panelW - 8 : -(panelW + 48);
|
|
4027
|
+
var openLeft;
|
|
3991
4028
|
if (dockRight) {
|
|
3992
4029
|
// RIGHT dock: keep the panel's VISIBLE right edge pinned at the SAME
|
|
3993
4030
|
// line when collapsed as when expanded (the expand edge is correct).
|
|
3994
4031
|
var openRight = window.innerWidth - (viewport ? viewport.right + 48 : 60) - panelW;
|
|
3995
4032
|
openLeft = clampLeft(openRight);
|
|
3996
|
-
parkedLeft = openRight + panelW;
|
|
3997
4033
|
} else {
|
|
3998
4034
|
// LEFT dock open (left dock is the confirmed-correct reference — untouched)
|
|
3999
4035
|
openLeft = clampLeft(viewport ? viewport.left + 8 : 8);
|
|
4000
|
-
parkedLeft = (viewport ? viewport.left - panelW - 8 : -(panelW + 48));
|
|
4001
4036
|
}
|
|
4002
|
-
|
|
4003
|
-
|
|
4037
|
+
// A collapse finishes at the line it started from. The area we park against can be
|
|
4038
|
+
// re-measured while the panel is still sliding away — the host rewrites it when the
|
|
4039
|
+
// transcript lays out, and the window itself can change — and aiming at the new
|
|
4040
|
+
// numbers mid-flight lands the panel somewhere other than the edge it came out of
|
|
4041
|
+
// (measured: with the area re-measured during the collapse, the last visible strip
|
|
4042
|
+
// sat at 1396 instead of the 1526 line the panel had slid out from). So the
|
|
4043
|
+
// open -> closed step freezes the edge the collapse heads for, and the next open —
|
|
4044
|
+
// which reads the area afresh — picks up the new geometry. A dock switch cancels the
|
|
4045
|
+
// freeze: that is the reader asking for the other side, not a re-measure. The first
|
|
4046
|
+
// render arms nothing: a panel that was never open has no edge to hold, and pinning
|
|
4047
|
+
// the pre-measurement fallback would leave the closed box where no such panel goes.
|
|
4048
|
+
if (geoOpenRef.current === null) {
|
|
4049
|
+
geoOpenRef.current = geoOpen;
|
|
4050
|
+
} else if (geoOpenRef.current !== geoOpen) {
|
|
4051
|
+
geoOpenRef.current = geoOpen;
|
|
4052
|
+
geoHoldRef.current = geoOpen ? 0 : Date.now() + COLLAPSE_HOLD_MS;
|
|
4053
|
+
geoSnapRef.current = geoOpen ? null : { anchorLeft: anchorLeft, openLeft: openLeft, dock: dock };
|
|
4054
|
+
}
|
|
4055
|
+
var geoFrozen = geoSnapRef.current !== null && geoSnapRef.current.dock === dock
|
|
4056
|
+
&& Date.now() < geoHoldRef.current;
|
|
4057
|
+
if (geoFrozen) {
|
|
4058
|
+
anchorLeft = geoSnapRef.current.anchorLeft;
|
|
4059
|
+
openLeft = geoSnapRef.current.openLeft;
|
|
4060
|
+
}
|
|
4061
|
+
var openOffset = openLeft - anchorLeft;
|
|
4062
|
+
var parkedOffset = dockRight ? (openLeft + panelW) - anchorLeft : 0;
|
|
4063
|
+
var panelLeft = anchorLeft;
|
|
4064
|
+
var panelOffset = geoOpen ? openOffset : parkedOffset;
|
|
4004
4065
|
var panelTransform = panelOffset === 0 ? "none" : "translateX(" + zLen(panelOffset) + ")";
|
|
4005
4066
|
var vMaxH = viewport ? Math.max(200, viewport.height - 28) : "72vh";
|
|
4006
4067
|
// same reasoning vertically: the draggable offset is stored in localStorage
|
|
@@ -4653,6 +4714,8 @@ window.__ModuleLoader__.load({
|
|
|
4653
4714
|
// the search box owns its own keys (it stops propagation for the ones it uses)
|
|
4654
4715
|
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || (target && target.isContentEditable)) return;
|
|
4655
4716
|
if (e.altKey || e.ctrlKey || e.metaKey) return;
|
|
4717
|
+
// a key on the panel is a reader gesture: the auto-loader may act on it
|
|
4718
|
+
autoGestureRef.current = Date.now();
|
|
4656
4719
|
var key = e.key;
|
|
4657
4720
|
if (key === "ArrowDown" || key === "ArrowUp") {
|
|
4658
4721
|
swallow(e);
|
|
@@ -4752,13 +4815,22 @@ window.__ModuleLoader__.load({
|
|
|
4752
4815
|
// right dock -> clipped from the right at the screen/sidebar line
|
|
4753
4816
|
// LEFT dock keeps its original clip (collapse left). RIGHT dock clips the
|
|
4754
4817
|
// LEFT side so the panel closes toward its right (dock) edge in place.
|
|
4818
|
+
//
|
|
4819
|
+
// The inset is the distance the box actually TRAVELS, not the panel's width. The
|
|
4820
|
+
// visible edge of the panel is the clip edge, and that edge is `box.left + inset`
|
|
4821
|
+
// (left dock) / `box.right - inset` (right dock) while the box slides; matching the
|
|
4822
|
+
// inset to the travel makes the two move at the same rate, so the edge stays pinned
|
|
4823
|
+
// on the panel's OWN open edge line the whole way out. With a fixed `panelW + 8`
|
|
4824
|
+
// against a `panelW + 16` travel the edge drifted 8px past that line, which the
|
|
4825
|
+
// reader saw as the panel tucking in slightly left of where it comes back out.
|
|
4826
|
+
var travelPx = Math.abs(openOffset - parkedOffset);
|
|
4755
4827
|
var panelClip = "inset(0 0 0 0px)";
|
|
4756
4828
|
if (!geoOpen) {
|
|
4757
4829
|
if (dockRight) {
|
|
4758
4830
|
// mirror of LEFT: clip the RIGHT side (panel flies right, shrinking from its right)
|
|
4759
|
-
panelClip = "inset(0 " + zLen(
|
|
4831
|
+
panelClip = "inset(0 " + zLen(travelPx) + " 0 0)";
|
|
4760
4832
|
} else {
|
|
4761
|
-
panelClip = "inset(0 0 0 " + zLen(
|
|
4833
|
+
panelClip = "inset(0 0 0 " + zLen(travelPx) + ")";
|
|
4762
4834
|
}
|
|
4763
4835
|
}
|
|
4764
4836
|
// faded out entirely while another center-column view is active
|
|
@@ -5434,6 +5506,9 @@ window.__ModuleLoader__.load({
|
|
|
5434
5506
|
},
|
|
5435
5507
|
onScroll: onListScroll,
|
|
5436
5508
|
onWheel: onListWheel,
|
|
5509
|
+
// a press on a row (or on the list) is a reader gesture too: click-to-load an
|
|
5510
|
+
// unloaded row and drag-scrolling must both keep working
|
|
5511
|
+
onPointerDown: function () { autoGestureRef.current = Date.now(); },
|
|
5437
5512
|
children: react_jsx_runtime.jsx("div", {
|
|
5438
5513
|
// the two-phase cross-fade of the questions-only switch rides on this
|
|
5439
5514
|
// wrapper (classes, never inline animation: React rewrites inline styles
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-quick-toc",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "Conversation TOC for DeepSeek Harness (DSH): a whole-session, turn-grouped Markdown outline with hover previews, a level filter, a full-width curtain, jump-to-end buttons, title/full-text/cross-session search and a Chinese/English interface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|