@chestnut23/dsh-conversation-outline 0.1.1 → 0.1.2
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 +14 -9
- package/README.zh.md +13 -9
- package/docs/troubleshooting.md +13 -1
- package/docs/usage.md +18 -3
- package/lib/client/OutlinePanel.js +8 -10
- package/lib/client/outline.js +11 -2
- package/lib/client.js +13 -8
- package/lib/client.js.map +1 -1
- package/lib/types/client/outline.d.ts +8 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@chestnut23/dsh-conversation-outline)
|
|
6
|
+
[](https://www.npmjs.com/package/@chestnut23/dsh-conversation-outline)
|
|
7
|
+
[](LICENSE)
|
|
7
8
|
[](https://nodejs.org/)
|
|
8
9
|
[](https://github.com/lzbaclz/dsh-conversation-outline)
|
|
9
10
|
[](https://github.com/deepseek-ai/DeepSeek-Harness)
|
|
@@ -25,10 +26,13 @@ a flash highlight so you cannot miss it. Bilingual zh-CN / en.
|
|
|
25
26
|
chronological order. Out of the way while you read; >60 questions fold into a `+N` marker.
|
|
26
27
|
- **Hover to preview** — hover the rail and the panel slides out with each question's
|
|
27
28
|
opening words (single-line truncated), `#turn` badge and `HH:MM` time. Collapses 240 ms
|
|
28
|
-
after you move away
|
|
29
|
-
|
|
29
|
+
after you move away. Pure overlay — your content never shifts.
|
|
30
|
+
- **Click to open as well** — clicking the strip (or a bar) pins the panel open until you
|
|
31
|
+
close it with `Esc`, the ×, or a click outside, so the rail also works on touch, pen and
|
|
32
|
+
automation where no hover event ever arrives.
|
|
30
33
|
- **Click-to-jump** — switches to the Chat view (even from Trajectory), scrolls to the
|
|
31
|
-
message and flashes it for 1.8 s. Respects `prefers-reduced-motion
|
|
34
|
+
message and flashes it for 1.8 s. Respects `prefers-reduced-motion`; a jump that cannot
|
|
35
|
+
reach the message reports it in the panel instead of failing silently.
|
|
32
36
|
- **Search & load older** — case-insensitive filtering, plus paging into older history.
|
|
33
37
|
- **Live** — new questions appear while the session runs; follows the current session and
|
|
34
38
|
collapses on switch.
|
|
@@ -72,14 +76,15 @@ dsh plugin --profile web list
|
|
|
72
76
|
```
|
|
73
77
|
|
|
74
78
|
Upgrade with the same `add` command (optionally pin a version:
|
|
75
|
-
`@chestnut23/dsh-conversation-outline@0.1.
|
|
79
|
+
`@chestnut23/dsh-conversation-outline@0.1.2`).
|
|
76
80
|
|
|
77
81
|
## Usage
|
|
78
82
|
|
|
79
83
|
Install, restart, open any session that already has messages — a thin strip appears on
|
|
80
|
-
the right edge. Hover it to preview questions
|
|
81
|
-
filter, `Load older` to page
|
|
82
|
-
[docs/usage.md](docs/usage.md). Something's not
|
|
84
|
+
the right edge. Hover it to preview questions; click the strip (or a bar) to open the
|
|
85
|
+
panel and keep it open; click a row to jump; search to filter, `Load older` to page
|
|
86
|
+
through history. Full walkthrough: [docs/usage.md](docs/usage.md). Something's not
|
|
87
|
+
showing up? [Troubleshooting](docs/troubleshooting.md).
|
|
83
88
|
|
|
84
89
|
## Preview
|
|
85
90
|
|
package/README.zh.md
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@chestnut23/dsh-conversation-outline)
|
|
6
|
+
[](https://www.npmjs.com/package/@chestnut23/dsh-conversation-outline)
|
|
7
|
+
[](LICENSE)
|
|
7
8
|
[](https://nodejs.org/)
|
|
8
9
|
[](https://github.com/lzbaclz/dsh-conversation-outline)
|
|
9
10
|
[](https://github.com/deepseek-ai/DeepSeek-Harness)
|
|
@@ -23,10 +24,13 @@
|
|
|
23
24
|
- **右侧细条(minimap)**——紧贴右边缘的窄竖条,按时间顺序每个问题一根小横条。
|
|
24
25
|
平时不占地方、不影响阅读;超过 60 个问题折叠为顶部 `+N` 标记。
|
|
25
26
|
- **悬停预览**——鼠标移到细条上,面板滑出:每个问题开头几个字(单行截断)、`#轮次`
|
|
26
|
-
徽标与 `HH:MM` 时间。移开 240ms
|
|
27
|
-
|
|
27
|
+
徽标与 `HH:MM` 时间。移开 240ms 后自动收起。纯覆盖层,**正文布局纹丝不动**。
|
|
28
|
+
- **点击也能打开**——点细条本身(或任意一根小横条)会把面板**钉住**,直到你用 `Esc`、
|
|
29
|
+
× 或点击面板外才关闭;因此触屏、手写笔、自动化脚本这类「永远没有 hover 事件」的场景
|
|
30
|
+
同样可用。
|
|
28
31
|
- **点击跳转**——即使停在「轨迹」视图也会自动切回聊天视图,滚动定位并闪烁 1.8s,
|
|
29
|
-
尊重 `prefers-reduced-motion
|
|
32
|
+
尊重 `prefers-reduced-motion`;万一目标消息不在已加载的历史窗口里,面板会明确告诉你,
|
|
33
|
+
而不是静默失败。
|
|
30
34
|
- **搜索 & 加载更早**——大小写不敏感过滤 + 翻历史分页。
|
|
31
35
|
- **实时**——会话进行中新问题自动出现;跟随当前会话,切换即收起。
|
|
32
36
|
- **运行时零依赖**——浏览器包只引用 React 等平台公共模块,其余全部内联并在构建期
|
|
@@ -67,13 +71,13 @@ dsh plugin --profile web add "link:$(pwd)"
|
|
|
67
71
|
dsh plugin --profile web list
|
|
68
72
|
```
|
|
69
73
|
|
|
70
|
-
升级用同一条 `add` 命令(可钉版本:`@chestnut23/dsh-conversation-outline@0.1.
|
|
74
|
+
升级用同一条 `add` 命令(可钉版本:`@chestnut23/dsh-conversation-outline@0.1.2`)。
|
|
71
75
|
|
|
72
76
|
## 使用
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
装好重启后,打开任何一个已有消息的会话,右侧边缘会出现细竖条:悬停预览问题;**点击细条
|
|
79
|
+
(或小横条)打开并钉住面板**;点列表行跳转;搜索过滤、`加载更早` 翻历史。
|
|
80
|
+
完整走查:[docs/usage.md](docs/usage.md)。没看到?先看 [常见问题](docs/troubleshooting.md)。
|
|
77
81
|
|
|
78
82
|
## 界面预览
|
|
79
83
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -14,10 +14,22 @@
|
|
|
14
14
|
Your page is still running an old bundle. Refresh the page (Ctrl/Cmd+R). If a stale
|
|
15
15
|
version persists, restart the DSH Web service and refresh again.
|
|
16
16
|
|
|
17
|
+
## The rail shows but the panel will not open
|
|
18
|
+
|
|
19
|
+
Click the strip (or any bar) — since v0.1.1 a click pins the panel open, so it no longer
|
|
20
|
+
depends on a hover event reaching the rail. If a click still does nothing at all:
|
|
21
|
+
|
|
22
|
+
1. Check the pointer is on the strip, not on the window edge outside the app frame.
|
|
23
|
+
2. Open the browser console: a failure inside the jump path logs the target key.
|
|
24
|
+
3. Refresh the page once — a bundle in the page cache can predate the fix.
|
|
25
|
+
4. If it still fails, report the console output with the DSH version and the plugin
|
|
26
|
+
version (`dsh plugin --profile web list`).
|
|
27
|
+
|
|
17
28
|
## Jumping does nothing / logs "jump failed"
|
|
18
29
|
|
|
19
30
|
- The target message must be in the **loaded history window** (the panel only lists
|
|
20
|
-
loaded questions). Click `Load older` first for deeper history, then jump again.
|
|
31
|
+
loaded questions). Click `Load older` first for deeper history, then jump again. The
|
|
32
|
+
panel now shows this notice itself instead of failing silently.
|
|
21
33
|
- Extremely long sessions: if the row does not render within 1.5 s after switching
|
|
22
34
|
views, the jump is abandoned (a console warning is logged). Retrying usually works.
|
|
23
35
|
|
package/docs/usage.md
CHANGED
|
@@ -19,8 +19,21 @@ A thin vertical strip appears on the right edge, vertically centered:
|
|
|
19
19
|
time. Mid-turn steering messages carry a `追问` / `steer` tag.
|
|
20
20
|
- Move the pointer away: the panel collapses after a 240 ms grace period (the grace
|
|
21
21
|
lets the pointer travel from the rail into the panel without flicker).
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
|
|
23
|
+
## Open by click (no hover required)
|
|
24
|
+
|
|
25
|
+
Click the rail itself — its padding, its background, or the `+N` marker — and the panel
|
|
26
|
+
opens **pinned**: it stays up until it is closed, so the strip is fully usable by click
|
|
27
|
+
alone (touch, pen, automation, or a pointer that never raised a hover event).
|
|
28
|
+
|
|
29
|
+
- Clicking a bar both opens the panel and jumps to that question's message; the panel
|
|
30
|
+
closes again once the message is on screen.
|
|
31
|
+
- A pinned panel keeps a highlighted border, so "pinned" and "hover preview" look
|
|
32
|
+
different.
|
|
33
|
+
- Closing it: `Esc`, the × button, a click anywhere outside the panel, or clicking the
|
|
34
|
+
strip again.
|
|
35
|
+
- If a jump cannot find the message (it sits outside the loaded history window), the
|
|
36
|
+
panel says so instead of failing silently — click `Load older`, then jump again.
|
|
24
37
|
|
|
25
38
|
## Jump to a message
|
|
26
39
|
|
|
@@ -30,11 +43,13 @@ Click a bar on the rail — or a row in the panel:
|
|
|
30
43
|
view first.
|
|
31
44
|
2. The chat scrolls so the target message sits ~96px below the top.
|
|
32
45
|
3. The message flashes for 1.8 s.
|
|
46
|
+
4. The panel closes once the jump landed.
|
|
33
47
|
|
|
34
48
|
Notes:
|
|
35
49
|
|
|
36
50
|
- Jumping works only for messages already inside the loaded history window (the panel
|
|
37
|
-
only lists loaded questions — use `Load older` first for deeper history).
|
|
51
|
+
only lists loaded questions — use `Load older` first for deeper history). A failed
|
|
52
|
+
jump is reported in the panel footer.
|
|
38
53
|
- `prefers-reduced-motion` disables the smooth scroll and the flash animation.
|
|
39
54
|
|
|
40
55
|
## Search
|
|
@@ -187,18 +187,16 @@ export function OutlinePanel({ sessions, useSessions, t }) {
|
|
|
187
187
|
cancelCollapse();
|
|
188
188
|
setHovered(true);
|
|
189
189
|
}, [cancelCollapse]);
|
|
190
|
-
/** Grid of candidate keys for one node: the anchor the row renders first. */
|
|
191
|
-
function jumpKeyCandidates(key) {
|
|
192
|
-
return key.includes('|') ? [key.split('|')[0], key] : [key];
|
|
193
|
-
}
|
|
194
190
|
/** Find the chat row for a node key (pure predicate over DOM rows). */
|
|
195
191
|
const findRow = useCallback((key) => {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
192
|
+
// Exact match on either anchor attribute the chat flow renders for a node:
|
|
193
|
+
// `data-chat-anchor-key` is the one the platform's own anchorElement() uses,
|
|
194
|
+
// `data-chat-flow-key` is its sibling on the same wrapper. No guessing at
|
|
195
|
+
// the key's internals — a wrong row is worse than a reported failure.
|
|
196
|
+
const rows = document.querySelectorAll('[data-chat-anchor-key], [data-chat-flow-key]');
|
|
197
|
+
for (const row of rows) {
|
|
198
|
+
if (isJumpTargetRow(row, key))
|
|
199
|
+
return row;
|
|
202
200
|
}
|
|
203
201
|
return null;
|
|
204
202
|
}, []);
|
package/lib/client/outline.js
CHANGED
|
@@ -77,7 +77,16 @@ export function formatTime(ms) {
|
|
|
77
77
|
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
78
78
|
return `${hh}:${mm}`;
|
|
79
79
|
}
|
|
80
|
-
/**
|
|
80
|
+
/** Attributes the chat flow renders a node's identity under. */
|
|
81
|
+
const JUMP_KEY_ATTRIBUTES = ['data-chat-anchor-key', 'data-chat-flow-key'];
|
|
82
|
+
/**
|
|
83
|
+
* Pure DOM predicate for the jump loop: is this row the target node?
|
|
84
|
+
*
|
|
85
|
+
* Exact match only, against either attribute the flow wrapper carries — the
|
|
86
|
+
* platform's own anchor lookup uses `data-chat-anchor-key`, and its sibling
|
|
87
|
+
* `data-chat-flow-key` holds the same value, so a row is still found when only
|
|
88
|
+
* one of the two is present.
|
|
89
|
+
*/
|
|
81
90
|
export function isJumpTargetRow(row, key) {
|
|
82
|
-
return row.getAttribute(
|
|
91
|
+
return JUMP_KEY_ATTRIBUTES.some((attribute) => row.getAttribute(attribute) === key);
|
|
83
92
|
}
|
package/lib/client.js
CHANGED
|
@@ -71,9 +71,18 @@ window.__ModuleLoader__.load({
|
|
|
71
71
|
const d = new Date(ms);
|
|
72
72
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
73
73
|
}
|
|
74
|
-
/**
|
|
74
|
+
/** Attributes the chat flow renders a node's identity under. */
|
|
75
|
+
const JUMP_KEY_ATTRIBUTES = ["data-chat-anchor-key", "data-chat-flow-key"];
|
|
76
|
+
/**
|
|
77
|
+
* Pure DOM predicate for the jump loop: is this row the target node?
|
|
78
|
+
*
|
|
79
|
+
* Exact match only, against either attribute the flow wrapper carries — the
|
|
80
|
+
* platform's own anchor lookup uses `data-chat-anchor-key`, and its sibling
|
|
81
|
+
* `data-chat-flow-key` holds the same value, so a row is still found when only
|
|
82
|
+
* one of the two is present.
|
|
83
|
+
*/
|
|
75
84
|
function isJumpTargetRow(row, key) {
|
|
76
|
-
return row.getAttribute(
|
|
85
|
+
return JUMP_KEY_ATTRIBUTES.some((attribute) => row.getAttribute(attribute) === key);
|
|
77
86
|
}
|
|
78
87
|
//#endregion
|
|
79
88
|
//#region lib/client/OutlinePanel.js
|
|
@@ -224,14 +233,10 @@ window.__ModuleLoader__.load({
|
|
|
224
233
|
cancelCollapse();
|
|
225
234
|
setHovered(true);
|
|
226
235
|
}, [cancelCollapse]);
|
|
227
|
-
/** Grid of candidate keys for one node: the anchor the row renders first. */
|
|
228
|
-
function jumpKeyCandidates(key) {
|
|
229
|
-
return key.includes("|") ? [key.split("|")[0], key] : [key];
|
|
230
|
-
}
|
|
231
236
|
/** Find the chat row for a node key (pure predicate over DOM rows). */
|
|
232
237
|
const findRow = (0, react.useCallback)((key) => {
|
|
233
|
-
const rows = document.querySelectorAll("[data-chat-anchor-key]");
|
|
234
|
-
for (const
|
|
238
|
+
const rows = document.querySelectorAll("[data-chat-anchor-key], [data-chat-flow-key]");
|
|
239
|
+
for (const row of rows) if (isJumpTargetRow(row, key)) return row;
|
|
235
240
|
return null;
|
|
236
241
|
}, []);
|
|
237
242
|
/** Scroll the row into view (96px headroom) and flash a highlight. */
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["_Fragment"],"sources":["client/outline.js","client/OutlinePanel.js","client/locales.js","client/styles.js","client/index.js"],"sourcesContent":["/**\n * Pure logic for the conversation outline (implementation-spec §2.3).\n *\n * Deliberately has NO DOM access and NO React imports so scripts/verify.mjs can\n * import it directly in Node from the tsc output (lib/client/outline.js) and\n * assert the derivations offline.\n *\n * Input shapes are structural \"lite\" views of the runtime snapshot types\n * (@deepseek-ai/dsh-client-runtime/client) — the real objects are assignable\n * to them, and Node can import this module without the browser type graph.\n */\nconst IMAGE_PLACEHOLDER = '[image]';\n/**\n * Flatten message content blocks into a single display string: text blocks are\n * joined, image blocks become a placeholder, and whitespace is trimmed and\n * collapsed.\n */\nexport function flattenQuestionText(content) {\n return content\n .map((block) => {\n if (block.type === 'text')\n return block.text ?? '';\n if (block.type === 'image')\n return IMAGE_PLACEHOLDER;\n return '';\n })\n .join(' ')\n .trim()\n .replace(/\\s+/g, ' ');\n}\n/** Turn number of a node location: step/turn → location.turn.turn, else undefined. */\nfunction turnOf(location) {\n if (!location)\n return undefined;\n if (location.kind === 'step' || location.kind === 'turn')\n return location.turn?.turn;\n return undefined;\n}\n/**\n * Walk the chat flow in order, keep user/steering nodes, skip empty text, and\n * produce chronological outline items (spec §2.3).\n */\nexport function collectQuestions(snapshot) {\n const items = [];\n for (const key of snapshot.chat.order) {\n const node = snapshot.chat.nodes.get(key);\n if (!node)\n continue;\n if (node.kind !== 'user' && node.kind !== 'steering')\n continue;\n const data = node.data;\n const text = flattenQuestionText(data?.content ?? []);\n if (!text)\n continue;\n items.push({\n key: node.key,\n kind: node.kind,\n seq: data?.seq ?? 0,\n time: data?.time ?? 0,\n turn: turnOf(node.location),\n text,\n });\n }\n return items;\n}\n/** Case-insensitive substring filter over the flattened question text. */\nexport function filterQuestions(items, query) {\n const q = query.trim().toLowerCase();\n if (!q)\n return items;\n return items.filter((item) => item.text.toLowerCase().includes(q));\n}\n/** Local `HH:MM` from a unix-ms timestamp (spec §2.3). */\nexport function formatTime(ms) {\n const d = new Date(ms);\n const hh = String(d.getHours()).padStart(2, '0');\n const mm = String(d.getMinutes()).padStart(2, '0');\n return `${hh}:${mm}`;\n}\n/** Pure DOM predicate for the jump loop: is this row the target node? */\nexport function isJumpTargetRow(row, key) {\n return row.getAttribute('data-chat-anchor-key') === key;\n}\n","import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from \"react/jsx-runtime\";\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react';\nimport { collectQuestions, filterQuestions, formatTime, isJumpTargetRow, } from \"./outline.js\";\n/** Stable getSnapshot value while no session is current (uSES contract). */\nconst NO_SESSION = null;\nconst JUMP_HEADROOM = 96;\nconst JUMP_TIMEOUT_MS = 1500;\nconst FLASH_MS = 1900;\nconst COPIED_MS = 1500;\n/** Grace period before the hover panel collapses (lets the pointer travel). */\nconst COLLAPSE_DELAY_MS = 240;\n/** Rail capacity; older questions fold into the \"+N\" marker. */\nconst MAX_BARS = 60;\n/**\n * The conversation view's header tablist, scoped to the conversation root:\n * walk up from the scrollport's PARENT (the scrollport itself may contain\n * other tablists, e.g. the trajectory event-details tabs) and return the\n * nearest ancestor containing a `[role=\"tablist\"]`. Never falls back to a\n * document-wide query, so other tablists (trajectory event details,\n * settings, cordis source viewer) are not hit.\n */\nfunction findConversationTablist(scrollport) {\n let node = scrollport.parentElement;\n while (node) {\n const tablist = node.querySelector('[role=\"tablist\"]');\n if (tablist)\n return tablist;\n node = node.parentElement;\n }\n return null;\n}\n/** RAF that prunes itself from the tracking ref once it fires. */\nfunction scheduleRaf(rafRef, fn) {\n const id = requestAnimationFrame(() => {\n rafRef.current = rafRef.current.filter((x) => x !== id);\n fn();\n });\n rafRef.current.push(id);\n}\n/** Timeout that prunes itself from the tracking ref once it fires. */\nfunction scheduleTimeout(timeoutRef, fn, ms) {\n const id = window.setTimeout(() => {\n timeoutRef.current = timeoutRef.current.filter((x) => x !== id);\n fn();\n }, ms);\n timeoutRef.current.push(id);\n}\nexport function OutlinePanel({ sessions, useSessions, t }) {\n const current = useSessions((s) => s.current);\n const session = current ? sessions.binding(current)?.session : undefined;\n // Conversation snapshot subscription (spec §1.5): live updates while the\n // session runs, load-older, blank detection — all ride this one store.\n const subscribe = useCallback((onChange) => (session ? session.subscribe(onChange) : () => { }), [session]);\n const getSnapshot = useCallback(() => (session ? session.getSnapshot() : NO_SESSION), [session]);\n const snapshot = useSyncExternalStore(subscribe, getSnapshot);\n // Two independent sources for the panel being open: `pinned` is a click\n // gesture (sticky until Escape / outside click / session change), `hovered`\n // is pointer presence over the rail or the panel. Deriving `open` from both\n // means a click on the rail opens the panel even when no hover event ever\n // fires (touch, synthetic/automation input, or a pointer that entered the\n // strip without crossing its boundary), and a hover preview still collapses\n // on its own once the pointer leaves.\n const [pinned, setPinned] = useState(false);\n const [hovered, setHovered] = useState(false);\n const open = pinned || hovered;\n const [query, setQuery] = useState('');\n const [copiedKey, setCopiedKey] = useState(null);\n // The question whose jump could not be completed; the panel says so instead\n // of failing silently (the row may sit outside the loaded history window).\n const [jumpFailed, setJumpFailed] = useState(null);\n const timeoutsRef = useRef([]);\n const rafsRef = useRef([]);\n const flashedRowRef = useRef(null);\n const collapseTimerRef = useRef(null);\n const railRef = useRef(null);\n const panelRef = useRef(null);\n // Cancel pending jump work (RAFs/timeouts), the flash highlight, and a\n // scheduled hover collapse.\n const clearPending = useCallback(() => {\n for (const id of rafsRef.current)\n window.cancelAnimationFrame(id);\n for (const id of timeoutsRef.current)\n window.clearTimeout(id);\n rafsRef.current = [];\n timeoutsRef.current = [];\n if (collapseTimerRef.current !== null) {\n window.clearTimeout(collapseTimerRef.current);\n collapseTimerRef.current = null;\n }\n if (flashedRowRef.current) {\n flashedRowRef.current.removeAttribute('data-dsh-outline-flash');\n flashedRowRef.current = null;\n }\n }, []);\n /** Close the panel from any trigger: drop the pin and the hover preview. */\n const closePanel = useCallback(() => {\n setPinned(false);\n setHovered(false);\n cancelCollapseRef.current();\n }, []);\n // Session change → cancel pending work and collapse (navigate closes the\n // panel, spec §2.1; a mid-jump poll must not target the stale session).\n useEffect(() => {\n clearPending();\n setPinned(false);\n setHovered(false);\n setJumpFailed(null);\n }, [current, clearPending]);\n // Escape closes the panel (hover-opened or click-pinned).\n useEffect(() => {\n if (!open)\n return;\n const onKey = (event) => {\n if (event.key === 'Escape')\n closePanel();\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [open, closePanel]);\n // A click anywhere outside the rail and the panel releases a pin, so the\n // panel behaves like every other dismissible overlay on the page.\n useEffect(() => {\n if (!pinned)\n return;\n const onPointerDown = (event) => {\n const target = event.target;\n if (!(target instanceof Node))\n return;\n if (railRef.current?.contains(target) || panelRef.current?.contains(target))\n return;\n closePanel();\n };\n window.addEventListener('mousedown', onPointerDown);\n return () => window.removeEventListener('mousedown', onPointerDown);\n }, [pinned, closePanel]);\n // Unmount cleanup: cancel pending flash/copied timeouts, jump RAFs, collapse\n // timer, and the flash attribute.\n useEffect(() => clearPending, [clearPending]);\n const allItems = useMemo(() => (snapshot ? collectQuestions(snapshot) : []), [snapshot]);\n const items = useMemo(() => filterQuestions(allItems, query), [allItems, query]);\n const blank = snapshot?.blank ?? true;\n const hasMore = snapshot?.hasMore ?? false;\n const loadingOlder = snapshot?.loadingOlder ?? false;\n // ---- open mechanics: click pins, hover previews, grace period between ---\n const cancelCollapse = useCallback(() => {\n if (collapseTimerRef.current !== null) {\n window.clearTimeout(collapseTimerRef.current);\n collapseTimerRef.current = null;\n }\n }, []);\n // `closePanel` above runs before this declaration; a ref keeps the two\n // callbacks independent of declaration order.\n const cancelCollapseRef = useRef(cancelCollapse);\n cancelCollapseRef.current = cancelCollapse;\n const scheduleCollapse = useCallback(() => {\n cancelCollapse();\n collapseTimerRef.current = window.setTimeout(() => {\n collapseTimerRef.current = null;\n // Only a preview collapses on its own: a pinned panel stays until it is\n // closed, and a pointer still inside the rail or the panel (Chromium\n // raises no further enter events while the pointer sits still) keeps the\n // preview up.\n if (railRef.current?.matches(':hover') || panelRef.current?.matches(':hover'))\n return;\n setHovered(false);\n }, COLLAPSE_DELAY_MS);\n }, [cancelCollapse]);\n /** Pointer entered the rail or the panel: cancel a pending collapse, preview. */\n const onEnter = useCallback(() => {\n cancelCollapse();\n setHovered(true);\n }, [cancelCollapse]);\n /** Pointer left: let the grace period decide, unless the panel is pinned. */\n const onLeave = useCallback(() => {\n if (pinned)\n return;\n scheduleCollapse();\n }, [pinned, scheduleCollapse]);\n /** A click on the rail (or any bar) opens the panel and keeps it open. */\n const togglePin = useCallback(() => {\n cancelCollapse();\n setPinned((value) => !value);\n setHovered(true);\n }, [cancelCollapse]);\n /** The panel's own controls: clicking inside never closes it. */\n const keepOpen = useCallback(() => {\n cancelCollapse();\n setHovered(true);\n }, [cancelCollapse]);\n /** Grid of candidate keys for one node: the anchor the row renders first. */\n function jumpKeyCandidates(key) {\n return key.includes('|') ? [key.split('|')[0], key] : [key];\n }\n /** Find the chat row for a node key (pure predicate over DOM rows). */\n const findRow = useCallback((key) => {\n const rows = document.querySelectorAll('[data-chat-anchor-key]');\n for (const candidate of jumpKeyCandidates(key)) {\n for (const row of rows) {\n if (isJumpTargetRow(row, candidate))\n return row;\n }\n }\n return null;\n }, []);\n /** Scroll the row into view (96px headroom) and flash a highlight. */\n const flashAndScroll = useCallback((row) => {\n const scrollport = row.closest('[data-conversation-scroll]') ?? document.querySelector('[data-conversation-scroll]');\n if (scrollport) {\n const flowTop = row.getBoundingClientRect().top - scrollport.getBoundingClientRect().top;\n const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n scrollport.scrollTo({\n top: scrollport.scrollTop + flowTop - JUMP_HEADROOM,\n behavior: reduced ? 'auto' : 'smooth',\n });\n }\n // A second jump within FLASH_MS: clear the previous row first.\n if (flashedRowRef.current && flashedRowRef.current !== row) {\n flashedRowRef.current.removeAttribute('data-dsh-outline-flash');\n }\n flashedRowRef.current = row;\n row.setAttribute('data-dsh-outline-flash', 'true');\n scheduleTimeout(timeoutsRef, () => {\n if (flashedRowRef.current === row) {\n row.removeAttribute('data-dsh-outline-flash');\n flashedRowRef.current = null;\n }\n }, FLASH_MS);\n }, []);\n /** Jump-to-message algorithm (spec §2.2). */\n const jumpTo = useCallback((key, label) => {\n // The conversation page must be mounted; abort silently otherwise.\n const scrollport = document.querySelector('[data-conversation-scroll]');\n if (!scrollport)\n return;\n setJumpFailed(null);\n // Ensure the Chat view is active: chat is order 0 — the FIRST tab of the\n // conversation-root tablist, when one exists (setView is idempotent).\n // A profile without extra views has no header tablist at all — that is\n // fine, the rows are already visible.\n const tablist = findConversationTablist(scrollport);\n if (tablist) {\n const firstTab = tablist.querySelector('button[role=\"tab\"]');\n if (firstTab)\n firstTab.click();\n }\n // Poll for the target row regardless of tablist presence — render timing\n // only; the row is guaranteed in the loaded window (it came from the\n // snapshot). Canceled on session change/unmount via clearPending.\n const deadline = Date.now() + JUMP_TIMEOUT_MS;\n const poll = () => {\n const row = findRow(key);\n if (row) {\n flashAndScroll(row);\n closePanel();\n return;\n }\n if (Date.now() < deadline) {\n scheduleRaf(rafsRef, poll);\n }\n else {\n // Never fail silently: the panel states what happened, and the\n // console keeps the key for bug reports.\n console.warn(t('jumpFailed'), { key });\n setJumpFailed(label ?? key);\n }\n };\n scheduleRaf(rafsRef, poll);\n }, [findRow, flashAndScroll, t, closePanel]);\n /** Copy a question's text (clipboard API with execCommand fallback). */\n const copyText = useCallback((text, key) => {\n const done = () => {\n setCopiedKey(key);\n scheduleTimeout(timeoutsRef, () => setCopiedKey((k) => (k === key ? null : k)), COPIED_MS);\n };\n const fallback = () => {\n try {\n const ta = document.createElement('textarea');\n ta.value = text;\n ta.setAttribute('readonly', '');\n ta.style.position = 'fixed';\n ta.style.opacity = '0';\n document.body.appendChild(ta);\n ta.select();\n document.execCommand('copy');\n ta.remove();\n done();\n }\n catch {\n // clipboard unavailable — ignore\n }\n };\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(text).then(done, fallback);\n }\n else {\n fallback();\n }\n }, []);\n // No current session, or a blank session → render nothing (rail included).\n // MUST stay below every hook: this overlay entry stays mounted across\n // session changes, and blank flips false on the first accepted prompt, so a\n // conditional hook would crash React (reviewer MUST-FIX #1).\n if (!current || !session || blank)\n return null;\n // Rail bars: chronological top→bottom, capped at MAX_BARS with the overflow\n // folded into a \"+N\" marker (the rail is a minimap, not the full list).\n const start = Math.max(0, allItems.length - MAX_BARS);\n const bars = allItems.slice(start);\n const overflow = start;\n return (_jsxs(_Fragment, { children: [_jsxs(\"div\", { ref: railRef, className: \"dso-rail\", role: \"group\", \"aria-label\": t('title'), tabIndex: 0, onMouseEnter: onEnter, onMouseLeave: onLeave, onFocus: onEnter, onBlur: onLeave, onClick: (event) => {\n // Any click on the strip pins the panel open — the container itself,\n // its padding, or the \"+N\" marker. Bars stop propagation and reach\n // the same pin through the shared jump gesture below.\n if (event.target === event.currentTarget || event.target instanceof HTMLSpanElement) {\n togglePin();\n }\n }, onKeyDown: (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n togglePin();\n }\n }, \"data-dso-open\": open ? 'true' : undefined, children: [overflow > 0 && (_jsxs(\"span\", { className: \"dso-rail-more\", \"aria-label\": t('moreBars', { count: overflow }), children: [\"+\", overflow] })), bars.map((item, index) => (_jsx(\"button\", { type: \"button\", className: \"dso-bar\", \"aria-label\": t('barLabel', { n: start + index + 1 }), onClick: (event) => {\n event.stopPropagation();\n // The bar's own job is the jump; the panel comes up pinned first\n // so a click always has a visible effect, then jumpTo collapses\n // it once the row is on screen.\n setPinned(true);\n setHovered(true);\n jumpTo(item.key, item.text);\n } }, item.key)))] }), open && (_jsxs(\"div\", { ref: panelRef, className: \"dso-panel\", role: \"region\", \"aria-label\": t('title'), \"data-dso-pinned\": pinned ? 'true' : undefined, onMouseEnter: onEnter, onMouseLeave: onLeave, onFocus: onEnter, onBlur: onLeave, onMouseDown: keepOpen, children: [_jsxs(\"header\", { className: \"dso-panel-header\", children: [_jsx(\"span\", { className: \"dso-panel-title\", children: t('title') }), _jsx(\"span\", { className: \"dso-panel-count\", children: t('count', { count: allItems.length }) }), _jsx(\"button\", { type: \"button\", className: \"dso-panel-close\", \"aria-label\": t('close'), onClick: closePanel, children: \"\\u00D7\" })] }), _jsx(\"input\", { className: \"dso-search\", type: \"search\", value: query, placeholder: t('searchPlaceholder'), \"aria-label\": t('searchPlaceholder'), onChange: (event) => setQuery(event.target.value) }), _jsx(\"div\", { className: \"dso-list\", children: items.length === 0 ? (_jsx(\"div\", { className: \"dso-empty\", children: t('empty') })) : (items.map((item) => (_jsxs(\"div\", { className: \"dso-row\", children: [_jsxs(\"button\", { type: \"button\", className: \"dso-row-main\", onClick: () => jumpTo(item.key, item.text), children: [_jsx(\"span\", { className: \"dso-turn\", children: item.turn !== undefined ? `#${item.turn}` : '' }), _jsx(\"span\", { className: \"dso-text\", children: item.text }), item.kind === 'steering' && _jsx(\"span\", { className: \"dso-steer\", children: t('steerTag') }), _jsx(\"span\", { className: \"dso-time\", children: formatTime(item.time) })] }), _jsx(\"button\", { type: \"button\", className: copiedKey === item.key ? 'dso-copy dso-copied' : 'dso-copy', \"aria-label\": copiedKey === item.key ? t('copied') : t('copy'), onClick: () => copyText(item.text, item.key), children: copiedKey === item.key ? t('copied') : t('copy') })] }, item.key)))) }), jumpFailed !== null && (_jsx(\"p\", { className: \"dso-jump-failed\", role: \"status\", \"data-dso-jump-failed\": \"true\", children: t('jumpFailed') })), hasMore && (_jsx(\"footer\", { className: \"dso-footer\", children: _jsx(\"button\", { type: \"button\", className: \"dso-load-older\", disabled: loadingOlder, onClick: () => {\n void session.loadOlder();\n }, children: loadingOlder ? t('loadingOlder') : t('loadOlder') }) }))] }))] }));\n}\n","export const NS = 'dsh-conversation-outline';\nexport const zh = {\n title: '会话大纲',\n count: '{count} 个问题',\n searchPlaceholder: '搜索问题…',\n empty: '当前会话还没有问题',\n loadOlder: '加载更早',\n loadingOlder: '加载中…',\n copy: '复制',\n copied: '已复制',\n steerTag: '追问',\n close: '关闭',\n jumpFailed: '跳转失败:未找到对应消息',\n barLabel: '跳到第 {n} 个问题',\n moreBars: '还有 {count} 个更早的问题',\n};\nexport const en = {\n title: 'Outline',\n count: '{count} questions',\n searchPlaceholder: 'Search questions…',\n empty: 'No questions in this conversation yet',\n loadOlder: 'Load older',\n loadingOlder: 'Loading…',\n copy: 'Copy',\n copied: 'Copied',\n steerTag: 'steer',\n close: 'Close',\n jumpFailed: 'Jump failed: target message not found',\n barLabel: 'Jump to question {n}',\n moreBars: '{count} more earlier questions',\n};\nexport const dictionaries = { zh, en };\n","/**\n * Outline rail + hover panel CSS (implementation-spec §1.8/§2.1, revised):\n * a thin right-edge rail (conversation minimap) that is always visible while\n * the session has questions, and a preview panel that expands on hover.\n * Plain CSS string injected via an HMR-safe\n * `<style data-plugin=\"dsh-conversation-outline\" data-plugin-css=\"...\">` tag.\n * Stable prefixed class names (`dso_*`), DSH theme vars (`--dsw-alias-*`) —\n * never hashed class names of other packages. No layout shift: the panel is a\n * pure overlay.\n */\nexport const panelCss = `\n/* ---- Right-edge rail (always-visible minimap) --------------------------- */\n.dso-rail {\n position: fixed;\n top: 50%;\n right: 0;\n transform: translateY(-50%);\n z-index: 2147483000;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 3px;\n width: 30px;\n max-height: 55vh;\n padding: 8px 10px;\n overflow: hidden;\n cursor: pointer;\n}\n.dso-rail-more {\n flex: none;\n font-size: 9px;\n font-weight: 600;\n line-height: 10px;\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n}\n.dso-bar {\n flex: none;\n width: 10px;\n height: 6px;\n padding: 0;\n border: none;\n border-radius: 3px;\n background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 45%, transparent);\n cursor: pointer;\n transition: background 0.12s, transform 0.12s;\n}\n.dso-bar:hover {\n background: var(--dsw-alias-state-business-primary);\n transform: scaleX(1.35);\n}\n.dso-rail:focus-visible,\n.dso-bar:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary);\n outline-offset: 2px;\n}\n/* Clickability affordance: a hovered or pinned strip lights its bars up, so\n it reads as a control rather than as decoration. */\n.dso-rail:hover .dso-bar,\n.dso-rail[data-dso-open] .dso-bar {\n background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 72%, transparent);\n}\n.dso-rail:hover .dso-bar:hover {\n background: var(--dsw-alias-state-business-primary);\n}\n\n/* ---- Hover-expanded preview panel --------------------------------------- */\n.dso-panel {\n position: fixed;\n top: 50%;\n right: 16px;\n transform: translateY(-50%);\n z-index: 2147483000;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n width: min(340px, calc(100vw - 24px));\n max-height: min(70vh, 600px);\n overflow: hidden;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 16px;\n background: color-mix(in srgb, var(--dsw-alias-bg-module-platform) 92%, transparent);\n backdrop-filter: blur(20px);\n box-shadow: 0 16px 48px color-mix(in srgb, var(--dsw-alias-label-primary) 18%, transparent);\n color: var(--dsw-alias-label-primary);\n animation: dso-panel-in 140ms ease-out;\n}\n/* A pinned panel (clicked open) keeps a defined edge, so it is obvious that it\n no longer collapses when the pointer leaves. */\n.dso-panel[data-dso-pinned] {\n border-color: var(--dsw-alias-state-business-primary);\n}\n@keyframes dso-panel-in {\n from { opacity: 0; transform: translateY(-50%) translateX(12px); }\n to { opacity: 1; transform: translateY(-50%) translateX(0); }\n}\n\n.dso-panel-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 14px 10px;\n border-bottom: 1px solid var(--dsw-alias-line-normal);\n}\n.dso-panel-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n.dso-panel-count {\n flex: 1;\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dso-panel-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 16px;\n line-height: 1;\n cursor: pointer;\n transition: background 0.12s;\n}\n.dso-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n\n.dso-panel-close:focus-visible,\n.dso-search:focus-visible,\n.dso-row-main:focus-visible,\n.dso-copy:focus-visible,\n.dso-load-older:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary);\n outline-offset: 2px;\n}\n\n.dso-search {\n margin: 10px 12px 4px;\n box-sizing: border-box;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n outline: none;\n}\n.dso-search::placeholder { color: var(--dsw-alias-label-tertiary); }\n\n/* ---- Question list ------------------------------------------------------ */\n.dso-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px;\n}\n/* Row container: two sibling buttons (jump + copy) — no nested interactive\n controls. Hover highlights the whole row. */\n.dso-row {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 2px;\n border-radius: 10px;\n transition: background 0.12s;\n}\n.dso-row:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n.dso-row-main {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 6px;\n height: 28px;\n padding: 2px 6px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background 0.12s;\n}\n.dso-row-main:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n\n.dso-turn {\n flex: none;\n min-width: 26px;\n height: 16px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: var(--dsw-alias-bg-fill-business);\n color: var(--dsw-alias-label-on-fill);\n font-size: 10px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n}\n/* Single-line truncation: shows the opening words of each question —\n anything longer simply ellipsizes (per user request). */\n.dso-text {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n line-height: 18px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.dso-time {\n flex: none;\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n}\n.dso-steer {\n flex: none;\n padding: 0 6px;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 999px;\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n}\n.dso-copy {\n flex: none;\n padding: 2px 6px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 11px;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.12s, color 0.12s, background 0.12s;\n}\n.dso-row:hover .dso-copy,\n.dso-row:focus-within .dso-copy,\n.dso-copy:focus-visible { opacity: 1; }\n.dso-copy:hover { background: var(--dsw-alias-bg-fill-neutral); }\n.dso-copy.dso-copied { color: var(--dsw-alias-state-success); }\n\n.dso-empty {\n padding: 28px 16px;\n text-align: center;\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ---- Jump failure notice ------------------------------------------------ */\n.dso-jump-failed {\n margin: 0;\n padding: 8px 14px;\n border-top: 1px solid var(--dsw-alias-line-normal);\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-warning, var(--dsw-alias-label-secondary));\n}\n\n/* ---- Footer / load older ------------------------------------------------ */\n.dso-footer {\n display: flex;\n justify-content: center;\n padding: 8px 12px 10px;\n border-top: 1px solid var(--dsw-alias-line-normal);\n}\n.dso-load-older {\n padding: 4px 14px;\n border: none;\n border-radius: 14px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 12px;\n cursor: pointer;\n}\n.dso-load-older:disabled { cursor: default; opacity: 0.6; }\n\n/* ---- Jump flash highlight ----------------------------------------------- */\n[data-dsh-outline-flash] {\n animation: dso-flash 1.8s ease-out;\n}\n@keyframes dso-flash {\n 0% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--dsw-alias-state-business-primary) 50%, transparent);\n background-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 24%, transparent);\n }\n 70% {\n box-shadow: 0 0 0 6px transparent;\n background-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 8%, transparent);\n }\n 100% {\n box-shadow: 0 0 0 0 transparent;\n background-color: transparent;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dso-panel { animation: none; }\n .dso-bar { transition: none; }\n [data-dsh-outline-flash] { animation: none; }\n}\n`;\n/** Inject the style tag, owned by the client fiber (removed on dispose/HMR). */\nexport function injectStyle(ctx) {\n ctx.effect(() => {\n const tag = document.createElement('style');\n tag.dataset.plugin = 'dsh-conversation-outline';\n tag.dataset.pluginCss = 'dsh-conversation-outline/panel.css';\n tag.textContent = panelCss;\n document.head.appendChild(tag);\n return () => {\n tag.remove();\n };\n }, 'dsh-conversation-outline: panel css');\n}\n","import { OutlinePanel } from \"./OutlinePanel.js\";\nimport { NS, dictionaries } from \"./locales.js\";\nimport { injectStyle } from \"./styles.js\";\n/**\n * Client entry (implementation-spec §1.1/§2.4): the browser half of the\n * plugin. Cordis services this fiber waits for before apply() runs.\n */\nexport const inject = ['slots', 'sessions', 'locale'];\nexport function apply(ctx) {\n // Locale dictionaries — the typed register form (both shipped locales\n // required, keys compile-checked). The disposer is owned by the fiber, so\n // HMR removes the namespace on unload.\n ctx.effect(() => ctx.locale.register(NS, dictionaries), 'dsh-conversation-outline: locale');\n // Panel CSS as an HMR-safe <style data-plugin> tag (effect-owned).\n injectStyle(ctx);\n // Right-edge rail + hover panel in the frame-wide overlay layer.\n // slots.inject waits for the layout's declaration of `shell.overlay` and\n // routes the registration (and its unload cascade) through this fiber.\n // `locale: NS` gives the component the typed `t` seat; the inject factory\n // passes the sessions service face.\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\n name: 'shell.overlay',\n id: 'dsh-conversation-outline.rail',\n locale: NS,\n inject: () => ({ sessions: ctx.sessions }),\n }, OutlinePanel));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["_Fragment"],"sources":["client/outline.js","client/OutlinePanel.js","client/locales.js","client/styles.js","client/index.js"],"sourcesContent":["/**\n * Pure logic for the conversation outline (implementation-spec §2.3).\n *\n * Deliberately has NO DOM access and NO React imports so scripts/verify.mjs can\n * import it directly in Node from the tsc output (lib/client/outline.js) and\n * assert the derivations offline.\n *\n * Input shapes are structural \"lite\" views of the runtime snapshot types\n * (@deepseek-ai/dsh-client-runtime/client) — the real objects are assignable\n * to them, and Node can import this module without the browser type graph.\n */\nconst IMAGE_PLACEHOLDER = '[image]';\n/**\n * Flatten message content blocks into a single display string: text blocks are\n * joined, image blocks become a placeholder, and whitespace is trimmed and\n * collapsed.\n */\nexport function flattenQuestionText(content) {\n return content\n .map((block) => {\n if (block.type === 'text')\n return block.text ?? '';\n if (block.type === 'image')\n return IMAGE_PLACEHOLDER;\n return '';\n })\n .join(' ')\n .trim()\n .replace(/\\s+/g, ' ');\n}\n/** Turn number of a node location: step/turn → location.turn.turn, else undefined. */\nfunction turnOf(location) {\n if (!location)\n return undefined;\n if (location.kind === 'step' || location.kind === 'turn')\n return location.turn?.turn;\n return undefined;\n}\n/**\n * Walk the chat flow in order, keep user/steering nodes, skip empty text, and\n * produce chronological outline items (spec §2.3).\n */\nexport function collectQuestions(snapshot) {\n const items = [];\n for (const key of snapshot.chat.order) {\n const node = snapshot.chat.nodes.get(key);\n if (!node)\n continue;\n if (node.kind !== 'user' && node.kind !== 'steering')\n continue;\n const data = node.data;\n const text = flattenQuestionText(data?.content ?? []);\n if (!text)\n continue;\n items.push({\n key: node.key,\n kind: node.kind,\n seq: data?.seq ?? 0,\n time: data?.time ?? 0,\n turn: turnOf(node.location),\n text,\n });\n }\n return items;\n}\n/** Case-insensitive substring filter over the flattened question text. */\nexport function filterQuestions(items, query) {\n const q = query.trim().toLowerCase();\n if (!q)\n return items;\n return items.filter((item) => item.text.toLowerCase().includes(q));\n}\n/** Local `HH:MM` from a unix-ms timestamp (spec §2.3). */\nexport function formatTime(ms) {\n const d = new Date(ms);\n const hh = String(d.getHours()).padStart(2, '0');\n const mm = String(d.getMinutes()).padStart(2, '0');\n return `${hh}:${mm}`;\n}\n/** Attributes the chat flow renders a node's identity under. */\nconst JUMP_KEY_ATTRIBUTES = ['data-chat-anchor-key', 'data-chat-flow-key'];\n/**\n * Pure DOM predicate for the jump loop: is this row the target node?\n *\n * Exact match only, against either attribute the flow wrapper carries — the\n * platform's own anchor lookup uses `data-chat-anchor-key`, and its sibling\n * `data-chat-flow-key` holds the same value, so a row is still found when only\n * one of the two is present.\n */\nexport function isJumpTargetRow(row, key) {\n return JUMP_KEY_ATTRIBUTES.some((attribute) => row.getAttribute(attribute) === key);\n}\n","import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from \"react/jsx-runtime\";\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react';\nimport { collectQuestions, filterQuestions, formatTime, isJumpTargetRow, } from \"./outline.js\";\n/** Stable getSnapshot value while no session is current (uSES contract). */\nconst NO_SESSION = null;\nconst JUMP_HEADROOM = 96;\nconst JUMP_TIMEOUT_MS = 1500;\nconst FLASH_MS = 1900;\nconst COPIED_MS = 1500;\n/** Grace period before the hover panel collapses (lets the pointer travel). */\nconst COLLAPSE_DELAY_MS = 240;\n/** Rail capacity; older questions fold into the \"+N\" marker. */\nconst MAX_BARS = 60;\n/**\n * The conversation view's header tablist, scoped to the conversation root:\n * walk up from the scrollport's PARENT (the scrollport itself may contain\n * other tablists, e.g. the trajectory event-details tabs) and return the\n * nearest ancestor containing a `[role=\"tablist\"]`. Never falls back to a\n * document-wide query, so other tablists (trajectory event details,\n * settings, cordis source viewer) are not hit.\n */\nfunction findConversationTablist(scrollport) {\n let node = scrollport.parentElement;\n while (node) {\n const tablist = node.querySelector('[role=\"tablist\"]');\n if (tablist)\n return tablist;\n node = node.parentElement;\n }\n return null;\n}\n/** RAF that prunes itself from the tracking ref once it fires. */\nfunction scheduleRaf(rafRef, fn) {\n const id = requestAnimationFrame(() => {\n rafRef.current = rafRef.current.filter((x) => x !== id);\n fn();\n });\n rafRef.current.push(id);\n}\n/** Timeout that prunes itself from the tracking ref once it fires. */\nfunction scheduleTimeout(timeoutRef, fn, ms) {\n const id = window.setTimeout(() => {\n timeoutRef.current = timeoutRef.current.filter((x) => x !== id);\n fn();\n }, ms);\n timeoutRef.current.push(id);\n}\nexport function OutlinePanel({ sessions, useSessions, t }) {\n const current = useSessions((s) => s.current);\n const session = current ? sessions.binding(current)?.session : undefined;\n // Conversation snapshot subscription (spec §1.5): live updates while the\n // session runs, load-older, blank detection — all ride this one store.\n const subscribe = useCallback((onChange) => (session ? session.subscribe(onChange) : () => { }), [session]);\n const getSnapshot = useCallback(() => (session ? session.getSnapshot() : NO_SESSION), [session]);\n const snapshot = useSyncExternalStore(subscribe, getSnapshot);\n // Two independent sources for the panel being open: `pinned` is a click\n // gesture (sticky until Escape / outside click / session change), `hovered`\n // is pointer presence over the rail or the panel. Deriving `open` from both\n // means a click on the rail opens the panel even when no hover event ever\n // fires (touch, synthetic/automation input, or a pointer that entered the\n // strip without crossing its boundary), and a hover preview still collapses\n // on its own once the pointer leaves.\n const [pinned, setPinned] = useState(false);\n const [hovered, setHovered] = useState(false);\n const open = pinned || hovered;\n const [query, setQuery] = useState('');\n const [copiedKey, setCopiedKey] = useState(null);\n // The question whose jump could not be completed; the panel says so instead\n // of failing silently (the row may sit outside the loaded history window).\n const [jumpFailed, setJumpFailed] = useState(null);\n const timeoutsRef = useRef([]);\n const rafsRef = useRef([]);\n const flashedRowRef = useRef(null);\n const collapseTimerRef = useRef(null);\n const railRef = useRef(null);\n const panelRef = useRef(null);\n // Cancel pending jump work (RAFs/timeouts), the flash highlight, and a\n // scheduled hover collapse.\n const clearPending = useCallback(() => {\n for (const id of rafsRef.current)\n window.cancelAnimationFrame(id);\n for (const id of timeoutsRef.current)\n window.clearTimeout(id);\n rafsRef.current = [];\n timeoutsRef.current = [];\n if (collapseTimerRef.current !== null) {\n window.clearTimeout(collapseTimerRef.current);\n collapseTimerRef.current = null;\n }\n if (flashedRowRef.current) {\n flashedRowRef.current.removeAttribute('data-dsh-outline-flash');\n flashedRowRef.current = null;\n }\n }, []);\n /** Close the panel from any trigger: drop the pin and the hover preview. */\n const closePanel = useCallback(() => {\n setPinned(false);\n setHovered(false);\n cancelCollapseRef.current();\n }, []);\n // Session change → cancel pending work and collapse (navigate closes the\n // panel, spec §2.1; a mid-jump poll must not target the stale session).\n useEffect(() => {\n clearPending();\n setPinned(false);\n setHovered(false);\n setJumpFailed(null);\n }, [current, clearPending]);\n // Escape closes the panel (hover-opened or click-pinned).\n useEffect(() => {\n if (!open)\n return;\n const onKey = (event) => {\n if (event.key === 'Escape')\n closePanel();\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [open, closePanel]);\n // A click anywhere outside the rail and the panel releases a pin, so the\n // panel behaves like every other dismissible overlay on the page.\n useEffect(() => {\n if (!pinned)\n return;\n const onPointerDown = (event) => {\n const target = event.target;\n if (!(target instanceof Node))\n return;\n if (railRef.current?.contains(target) || panelRef.current?.contains(target))\n return;\n closePanel();\n };\n window.addEventListener('mousedown', onPointerDown);\n return () => window.removeEventListener('mousedown', onPointerDown);\n }, [pinned, closePanel]);\n // Unmount cleanup: cancel pending flash/copied timeouts, jump RAFs, collapse\n // timer, and the flash attribute.\n useEffect(() => clearPending, [clearPending]);\n const allItems = useMemo(() => (snapshot ? collectQuestions(snapshot) : []), [snapshot]);\n const items = useMemo(() => filterQuestions(allItems, query), [allItems, query]);\n const blank = snapshot?.blank ?? true;\n const hasMore = snapshot?.hasMore ?? false;\n const loadingOlder = snapshot?.loadingOlder ?? false;\n // ---- open mechanics: click pins, hover previews, grace period between ---\n const cancelCollapse = useCallback(() => {\n if (collapseTimerRef.current !== null) {\n window.clearTimeout(collapseTimerRef.current);\n collapseTimerRef.current = null;\n }\n }, []);\n // `closePanel` above runs before this declaration; a ref keeps the two\n // callbacks independent of declaration order.\n const cancelCollapseRef = useRef(cancelCollapse);\n cancelCollapseRef.current = cancelCollapse;\n const scheduleCollapse = useCallback(() => {\n cancelCollapse();\n collapseTimerRef.current = window.setTimeout(() => {\n collapseTimerRef.current = null;\n // Only a preview collapses on its own: a pinned panel stays until it is\n // closed, and a pointer still inside the rail or the panel (Chromium\n // raises no further enter events while the pointer sits still) keeps the\n // preview up.\n if (railRef.current?.matches(':hover') || panelRef.current?.matches(':hover'))\n return;\n setHovered(false);\n }, COLLAPSE_DELAY_MS);\n }, [cancelCollapse]);\n /** Pointer entered the rail or the panel: cancel a pending collapse, preview. */\n const onEnter = useCallback(() => {\n cancelCollapse();\n setHovered(true);\n }, [cancelCollapse]);\n /** Pointer left: let the grace period decide, unless the panel is pinned. */\n const onLeave = useCallback(() => {\n if (pinned)\n return;\n scheduleCollapse();\n }, [pinned, scheduleCollapse]);\n /** A click on the rail (or any bar) opens the panel and keeps it open. */\n const togglePin = useCallback(() => {\n cancelCollapse();\n setPinned((value) => !value);\n setHovered(true);\n }, [cancelCollapse]);\n /** The panel's own controls: clicking inside never closes it. */\n const keepOpen = useCallback(() => {\n cancelCollapse();\n setHovered(true);\n }, [cancelCollapse]);\n /** Find the chat row for a node key (pure predicate over DOM rows). */\n const findRow = useCallback((key) => {\n // Exact match on either anchor attribute the chat flow renders for a node:\n // `data-chat-anchor-key` is the one the platform's own anchorElement() uses,\n // `data-chat-flow-key` is its sibling on the same wrapper. No guessing at\n // the key's internals — a wrong row is worse than a reported failure.\n const rows = document.querySelectorAll('[data-chat-anchor-key], [data-chat-flow-key]');\n for (const row of rows) {\n if (isJumpTargetRow(row, key))\n return row;\n }\n return null;\n }, []);\n /** Scroll the row into view (96px headroom) and flash a highlight. */\n const flashAndScroll = useCallback((row) => {\n const scrollport = row.closest('[data-conversation-scroll]') ?? document.querySelector('[data-conversation-scroll]');\n if (scrollport) {\n const flowTop = row.getBoundingClientRect().top - scrollport.getBoundingClientRect().top;\n const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n scrollport.scrollTo({\n top: scrollport.scrollTop + flowTop - JUMP_HEADROOM,\n behavior: reduced ? 'auto' : 'smooth',\n });\n }\n // A second jump within FLASH_MS: clear the previous row first.\n if (flashedRowRef.current && flashedRowRef.current !== row) {\n flashedRowRef.current.removeAttribute('data-dsh-outline-flash');\n }\n flashedRowRef.current = row;\n row.setAttribute('data-dsh-outline-flash', 'true');\n scheduleTimeout(timeoutsRef, () => {\n if (flashedRowRef.current === row) {\n row.removeAttribute('data-dsh-outline-flash');\n flashedRowRef.current = null;\n }\n }, FLASH_MS);\n }, []);\n /** Jump-to-message algorithm (spec §2.2). */\n const jumpTo = useCallback((key, label) => {\n // The conversation page must be mounted; abort silently otherwise.\n const scrollport = document.querySelector('[data-conversation-scroll]');\n if (!scrollport)\n return;\n setJumpFailed(null);\n // Ensure the Chat view is active: chat is order 0 — the FIRST tab of the\n // conversation-root tablist, when one exists (setView is idempotent).\n // A profile without extra views has no header tablist at all — that is\n // fine, the rows are already visible.\n const tablist = findConversationTablist(scrollport);\n if (tablist) {\n const firstTab = tablist.querySelector('button[role=\"tab\"]');\n if (firstTab)\n firstTab.click();\n }\n // Poll for the target row regardless of tablist presence — render timing\n // only; the row is guaranteed in the loaded window (it came from the\n // snapshot). Canceled on session change/unmount via clearPending.\n const deadline = Date.now() + JUMP_TIMEOUT_MS;\n const poll = () => {\n const row = findRow(key);\n if (row) {\n flashAndScroll(row);\n closePanel();\n return;\n }\n if (Date.now() < deadline) {\n scheduleRaf(rafsRef, poll);\n }\n else {\n // Never fail silently: the panel states what happened, and the\n // console keeps the key for bug reports.\n console.warn(t('jumpFailed'), { key });\n setJumpFailed(label ?? key);\n }\n };\n scheduleRaf(rafsRef, poll);\n }, [findRow, flashAndScroll, t, closePanel]);\n /** Copy a question's text (clipboard API with execCommand fallback). */\n const copyText = useCallback((text, key) => {\n const done = () => {\n setCopiedKey(key);\n scheduleTimeout(timeoutsRef, () => setCopiedKey((k) => (k === key ? null : k)), COPIED_MS);\n };\n const fallback = () => {\n try {\n const ta = document.createElement('textarea');\n ta.value = text;\n ta.setAttribute('readonly', '');\n ta.style.position = 'fixed';\n ta.style.opacity = '0';\n document.body.appendChild(ta);\n ta.select();\n document.execCommand('copy');\n ta.remove();\n done();\n }\n catch {\n // clipboard unavailable — ignore\n }\n };\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(text).then(done, fallback);\n }\n else {\n fallback();\n }\n }, []);\n // No current session, or a blank session → render nothing (rail included).\n // MUST stay below every hook: this overlay entry stays mounted across\n // session changes, and blank flips false on the first accepted prompt, so a\n // conditional hook would crash React (reviewer MUST-FIX #1).\n if (!current || !session || blank)\n return null;\n // Rail bars: chronological top→bottom, capped at MAX_BARS with the overflow\n // folded into a \"+N\" marker (the rail is a minimap, not the full list).\n const start = Math.max(0, allItems.length - MAX_BARS);\n const bars = allItems.slice(start);\n const overflow = start;\n return (_jsxs(_Fragment, { children: [_jsxs(\"div\", { ref: railRef, className: \"dso-rail\", role: \"group\", \"aria-label\": t('title'), tabIndex: 0, onMouseEnter: onEnter, onMouseLeave: onLeave, onFocus: onEnter, onBlur: onLeave, onClick: (event) => {\n // Any click on the strip pins the panel open — the container itself,\n // its padding, or the \"+N\" marker. Bars stop propagation and reach\n // the same pin through the shared jump gesture below.\n if (event.target === event.currentTarget || event.target instanceof HTMLSpanElement) {\n togglePin();\n }\n }, onKeyDown: (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n togglePin();\n }\n }, \"data-dso-open\": open ? 'true' : undefined, children: [overflow > 0 && (_jsxs(\"span\", { className: \"dso-rail-more\", \"aria-label\": t('moreBars', { count: overflow }), children: [\"+\", overflow] })), bars.map((item, index) => (_jsx(\"button\", { type: \"button\", className: \"dso-bar\", \"aria-label\": t('barLabel', { n: start + index + 1 }), onClick: (event) => {\n event.stopPropagation();\n // The bar's own job is the jump; the panel comes up pinned first\n // so a click always has a visible effect, then jumpTo collapses\n // it once the row is on screen.\n setPinned(true);\n setHovered(true);\n jumpTo(item.key, item.text);\n } }, item.key)))] }), open && (_jsxs(\"div\", { ref: panelRef, className: \"dso-panel\", role: \"region\", \"aria-label\": t('title'), \"data-dso-pinned\": pinned ? 'true' : undefined, onMouseEnter: onEnter, onMouseLeave: onLeave, onFocus: onEnter, onBlur: onLeave, onMouseDown: keepOpen, children: [_jsxs(\"header\", { className: \"dso-panel-header\", children: [_jsx(\"span\", { className: \"dso-panel-title\", children: t('title') }), _jsx(\"span\", { className: \"dso-panel-count\", children: t('count', { count: allItems.length }) }), _jsx(\"button\", { type: \"button\", className: \"dso-panel-close\", \"aria-label\": t('close'), onClick: closePanel, children: \"\\u00D7\" })] }), _jsx(\"input\", { className: \"dso-search\", type: \"search\", value: query, placeholder: t('searchPlaceholder'), \"aria-label\": t('searchPlaceholder'), onChange: (event) => setQuery(event.target.value) }), _jsx(\"div\", { className: \"dso-list\", children: items.length === 0 ? (_jsx(\"div\", { className: \"dso-empty\", children: t('empty') })) : (items.map((item) => (_jsxs(\"div\", { className: \"dso-row\", children: [_jsxs(\"button\", { type: \"button\", className: \"dso-row-main\", onClick: () => jumpTo(item.key, item.text), children: [_jsx(\"span\", { className: \"dso-turn\", children: item.turn !== undefined ? `#${item.turn}` : '' }), _jsx(\"span\", { className: \"dso-text\", children: item.text }), item.kind === 'steering' && _jsx(\"span\", { className: \"dso-steer\", children: t('steerTag') }), _jsx(\"span\", { className: \"dso-time\", children: formatTime(item.time) })] }), _jsx(\"button\", { type: \"button\", className: copiedKey === item.key ? 'dso-copy dso-copied' : 'dso-copy', \"aria-label\": copiedKey === item.key ? t('copied') : t('copy'), onClick: () => copyText(item.text, item.key), children: copiedKey === item.key ? t('copied') : t('copy') })] }, item.key)))) }), jumpFailed !== null && (_jsx(\"p\", { className: \"dso-jump-failed\", role: \"status\", \"data-dso-jump-failed\": \"true\", children: t('jumpFailed') })), hasMore && (_jsx(\"footer\", { className: \"dso-footer\", children: _jsx(\"button\", { type: \"button\", className: \"dso-load-older\", disabled: loadingOlder, onClick: () => {\n void session.loadOlder();\n }, children: loadingOlder ? t('loadingOlder') : t('loadOlder') }) }))] }))] }));\n}\n","export const NS = 'dsh-conversation-outline';\nexport const zh = {\n title: '会话大纲',\n count: '{count} 个问题',\n searchPlaceholder: '搜索问题…',\n empty: '当前会话还没有问题',\n loadOlder: '加载更早',\n loadingOlder: '加载中…',\n copy: '复制',\n copied: '已复制',\n steerTag: '追问',\n close: '关闭',\n jumpFailed: '跳转失败:未找到对应消息',\n barLabel: '跳到第 {n} 个问题',\n moreBars: '还有 {count} 个更早的问题',\n};\nexport const en = {\n title: 'Outline',\n count: '{count} questions',\n searchPlaceholder: 'Search questions…',\n empty: 'No questions in this conversation yet',\n loadOlder: 'Load older',\n loadingOlder: 'Loading…',\n copy: 'Copy',\n copied: 'Copied',\n steerTag: 'steer',\n close: 'Close',\n jumpFailed: 'Jump failed: target message not found',\n barLabel: 'Jump to question {n}',\n moreBars: '{count} more earlier questions',\n};\nexport const dictionaries = { zh, en };\n","/**\n * Outline rail + hover panel CSS (implementation-spec §1.8/§2.1, revised):\n * a thin right-edge rail (conversation minimap) that is always visible while\n * the session has questions, and a preview panel that expands on hover.\n * Plain CSS string injected via an HMR-safe\n * `<style data-plugin=\"dsh-conversation-outline\" data-plugin-css=\"...\">` tag.\n * Stable prefixed class names (`dso_*`), DSH theme vars (`--dsw-alias-*`) —\n * never hashed class names of other packages. No layout shift: the panel is a\n * pure overlay.\n */\nexport const panelCss = `\n/* ---- Right-edge rail (always-visible minimap) --------------------------- */\n.dso-rail {\n position: fixed;\n top: 50%;\n right: 0;\n transform: translateY(-50%);\n z-index: 2147483000;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 3px;\n width: 30px;\n max-height: 55vh;\n padding: 8px 10px;\n overflow: hidden;\n cursor: pointer;\n}\n.dso-rail-more {\n flex: none;\n font-size: 9px;\n font-weight: 600;\n line-height: 10px;\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n}\n.dso-bar {\n flex: none;\n width: 10px;\n height: 6px;\n padding: 0;\n border: none;\n border-radius: 3px;\n background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 45%, transparent);\n cursor: pointer;\n transition: background 0.12s, transform 0.12s;\n}\n.dso-bar:hover {\n background: var(--dsw-alias-state-business-primary);\n transform: scaleX(1.35);\n}\n.dso-rail:focus-visible,\n.dso-bar:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary);\n outline-offset: 2px;\n}\n/* Clickability affordance: a hovered or pinned strip lights its bars up, so\n it reads as a control rather than as decoration. */\n.dso-rail:hover .dso-bar,\n.dso-rail[data-dso-open] .dso-bar {\n background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 72%, transparent);\n}\n.dso-rail:hover .dso-bar:hover {\n background: var(--dsw-alias-state-business-primary);\n}\n\n/* ---- Hover-expanded preview panel --------------------------------------- */\n.dso-panel {\n position: fixed;\n top: 50%;\n right: 16px;\n transform: translateY(-50%);\n z-index: 2147483000;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n width: min(340px, calc(100vw - 24px));\n max-height: min(70vh, 600px);\n overflow: hidden;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 16px;\n background: color-mix(in srgb, var(--dsw-alias-bg-module-platform) 92%, transparent);\n backdrop-filter: blur(20px);\n box-shadow: 0 16px 48px color-mix(in srgb, var(--dsw-alias-label-primary) 18%, transparent);\n color: var(--dsw-alias-label-primary);\n animation: dso-panel-in 140ms ease-out;\n}\n/* A pinned panel (clicked open) keeps a defined edge, so it is obvious that it\n no longer collapses when the pointer leaves. */\n.dso-panel[data-dso-pinned] {\n border-color: var(--dsw-alias-state-business-primary);\n}\n@keyframes dso-panel-in {\n from { opacity: 0; transform: translateY(-50%) translateX(12px); }\n to { opacity: 1; transform: translateY(-50%) translateX(0); }\n}\n\n.dso-panel-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 14px 10px;\n border-bottom: 1px solid var(--dsw-alias-line-normal);\n}\n.dso-panel-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n.dso-panel-count {\n flex: 1;\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dso-panel-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 16px;\n line-height: 1;\n cursor: pointer;\n transition: background 0.12s;\n}\n.dso-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n\n.dso-panel-close:focus-visible,\n.dso-search:focus-visible,\n.dso-row-main:focus-visible,\n.dso-copy:focus-visible,\n.dso-load-older:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary);\n outline-offset: 2px;\n}\n\n.dso-search {\n margin: 10px 12px 4px;\n box-sizing: border-box;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n outline: none;\n}\n.dso-search::placeholder { color: var(--dsw-alias-label-tertiary); }\n\n/* ---- Question list ------------------------------------------------------ */\n.dso-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px;\n}\n/* Row container: two sibling buttons (jump + copy) — no nested interactive\n controls. Hover highlights the whole row. */\n.dso-row {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 2px;\n border-radius: 10px;\n transition: background 0.12s;\n}\n.dso-row:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n.dso-row-main {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 6px;\n height: 28px;\n padding: 2px 6px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background 0.12s;\n}\n.dso-row-main:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n\n.dso-turn {\n flex: none;\n min-width: 26px;\n height: 16px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: var(--dsw-alias-bg-fill-business);\n color: var(--dsw-alias-label-on-fill);\n font-size: 10px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n}\n/* Single-line truncation: shows the opening words of each question —\n anything longer simply ellipsizes (per user request). */\n.dso-text {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n line-height: 18px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.dso-time {\n flex: none;\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n}\n.dso-steer {\n flex: none;\n padding: 0 6px;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 999px;\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n}\n.dso-copy {\n flex: none;\n padding: 2px 6px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 11px;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.12s, color 0.12s, background 0.12s;\n}\n.dso-row:hover .dso-copy,\n.dso-row:focus-within .dso-copy,\n.dso-copy:focus-visible { opacity: 1; }\n.dso-copy:hover { background: var(--dsw-alias-bg-fill-neutral); }\n.dso-copy.dso-copied { color: var(--dsw-alias-state-success); }\n\n.dso-empty {\n padding: 28px 16px;\n text-align: center;\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ---- Jump failure notice ------------------------------------------------ */\n.dso-jump-failed {\n margin: 0;\n padding: 8px 14px;\n border-top: 1px solid var(--dsw-alias-line-normal);\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-warning, var(--dsw-alias-label-secondary));\n}\n\n/* ---- Footer / load older ------------------------------------------------ */\n.dso-footer {\n display: flex;\n justify-content: center;\n padding: 8px 12px 10px;\n border-top: 1px solid var(--dsw-alias-line-normal);\n}\n.dso-load-older {\n padding: 4px 14px;\n border: none;\n border-radius: 14px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 12px;\n cursor: pointer;\n}\n.dso-load-older:disabled { cursor: default; opacity: 0.6; }\n\n/* ---- Jump flash highlight ----------------------------------------------- */\n[data-dsh-outline-flash] {\n animation: dso-flash 1.8s ease-out;\n}\n@keyframes dso-flash {\n 0% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--dsw-alias-state-business-primary) 50%, transparent);\n background-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 24%, transparent);\n }\n 70% {\n box-shadow: 0 0 0 6px transparent;\n background-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 8%, transparent);\n }\n 100% {\n box-shadow: 0 0 0 0 transparent;\n background-color: transparent;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dso-panel { animation: none; }\n .dso-bar { transition: none; }\n [data-dsh-outline-flash] { animation: none; }\n}\n`;\n/** Inject the style tag, owned by the client fiber (removed on dispose/HMR). */\nexport function injectStyle(ctx) {\n ctx.effect(() => {\n const tag = document.createElement('style');\n tag.dataset.plugin = 'dsh-conversation-outline';\n tag.dataset.pluginCss = 'dsh-conversation-outline/panel.css';\n tag.textContent = panelCss;\n document.head.appendChild(tag);\n return () => {\n tag.remove();\n };\n }, 'dsh-conversation-outline: panel css');\n}\n","import { OutlinePanel } from \"./OutlinePanel.js\";\nimport { NS, dictionaries } from \"./locales.js\";\nimport { injectStyle } from \"./styles.js\";\n/**\n * Client entry (implementation-spec §1.1/§2.4): the browser half of the\n * plugin. Cordis services this fiber waits for before apply() runs.\n */\nexport const inject = ['slots', 'sessions', 'locale'];\nexport function apply(ctx) {\n // Locale dictionaries — the typed register form (both shipped locales\n // required, keys compile-checked). The disposer is owned by the fiber, so\n // HMR removes the namespace on unload.\n ctx.effect(() => ctx.locale.register(NS, dictionaries), 'dsh-conversation-outline: locale');\n // Panel CSS as an HMR-safe <style data-plugin> tag (effect-owned).\n injectStyle(ctx);\n // Right-edge rail + hover panel in the frame-wide overlay layer.\n // slots.inject waits for the layout's declaration of `shell.overlay` and\n // routes the registration (and its unload cascade) through this fiber.\n // `locale: NS` gives the component the typed `t` seat; the inject factory\n // passes the sessions service face.\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\n name: 'shell.overlay',\n id: 'dsh-conversation-outline.rail',\n locale: NS,\n inject: () => ({ sessions: ctx.sessions }),\n }, OutlinePanel));\n}\n"],"mappings":"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"}
|
|
@@ -78,5 +78,12 @@ export declare function formatTime(ms: number): string;
|
|
|
78
78
|
export interface ElementLike {
|
|
79
79
|
getAttribute(name: string): string | null;
|
|
80
80
|
}
|
|
81
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Pure DOM predicate for the jump loop: is this row the target node?
|
|
83
|
+
*
|
|
84
|
+
* Exact match only, against either attribute the flow wrapper carries — the
|
|
85
|
+
* platform's own anchor lookup uses `data-chat-anchor-key`, and its sibling
|
|
86
|
+
* `data-chat-flow-key` holds the same value, so a row is still found when only
|
|
87
|
+
* one of the two is present.
|
|
88
|
+
*/
|
|
82
89
|
export declare function isJumpTargetRow(row: ElementLike, key: string): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chestnut23/dsh-conversation-outline",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Codex-style conversation outline for DeepSeek Harness: a floating panel listing every user question in the current conversation with search, load-older and click-to-jump (scroll + highlight). Bilingual zh-CN / en.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|