dsh-quick-toc 0.7.2 → 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/README.en.md CHANGED
@@ -1,123 +1,123 @@
1
- # dsh-quick-toc
2
-
3
- > **English** | [中文](README.md)
4
-
5
- A conversation TOC plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH): it turns the Markdown headings (H1–H6) of AI replies into a navigable outline — the same list as a side panel or as a full-width curtain — grouped by conversation turn and covering the whole session (including turns that are not loaded yet), with title/full-text/cross-session search, hover previews, several ways to jump (a heading, a turn header, the end of a section), and a reading position that both follows and is remembered.
6
-
7
- ## Features
8
-
9
- - **Turn-grouped outline** — each user message plus its following AI replies form one group; the group header shows the turn's time and a first-line preview, and clicking it jumps to the start of that turn's model reply
10
- - **Whole-session coverage** — turns the conversation window has not loaded are listed too (tagged `未加载` / "Not loaded", with previews); clicking one loads that turn and jumps to it
11
- - **Failures are reported** — a turn with no reply at all (request timeout, upstream error) shows `请求失败` / "Request failed" plus the host's error text, and clicking it jumps to that error in the conversation
12
- - **A full-width curtain** — the droplet handle inside the tab strip along the top of the conversation (or the "curtain" round button in the panel's header) lays the outline out across the full width and drops it down: no scrim, nothing dimmed, larger type and looser rows, three parts per row (level badge / heading / the section's opening), and the search column sliding in from the right; click a row, press Esc or use the ✕ to close. It shares the one list with the docked panel, so the scroll position, the search and the keyboard cursor all carry over
13
- - **Jump to the end of a section** — hover a heading row, a group header or a result row and a round button fades in at its right end; a group header's button jumps to the end of the whole turn
14
- - **Cross-session search** — the third scope, "sessions", searches the message bodies of other sessions through the host's full-text index; clicking a hit switches to that session and keeps looking there, and archived / subagent / delisted sessions are skipped with a count
15
- - **Questions only** — one click folds the outline down to each turn's time and the first line of your prompt
16
- - **A remembered reading position** — reopening a session returns to the turn you were last reading (within half an hour; the card can switch it off)
17
- - **Keyboard navigation** — ↑/↓ move the cursor, Enter jumps, Home / End go to either end, Esc collapses; the cursor's position is announced to screen readers
18
- - **Bilingual** — the interface language can follow the host, or be forced to Chinese or English; Chinese stamps use `昨天` / `前天`, English reads yesterday as the word (`yesterday`) and carries the `YY-MM-DD HH:MM` date for anything older
19
- - **A plugin-configuration card** — a "Conversation Outline" card under Settings → Plugins → Plugin configuration: the language, the default docked edge, the heading levels shown, where the collapsed handle sits, the remembered reading position, fuzzy search, hover previews and the diagnostic switch; changed fields are marked "customized" and can be reset individually (returning a field to its default drops the mark), and the card and the panel stay in sync
20
- - **Turn stamps carry the day** — yesterday reads `昨天 15:04`, the day before `前天 15:04`, anything older `25-09-11 15:04`, so times never blur together in a session spanning days (with the interface in English every stamp older than today carries a `YY-MM-DD` date instead)
21
- - **Row subtitles** — under each heading, the first sentence of that section, so identically-titled headings can be told apart
22
- - **Hover previews** — hovering a heading row shows the section's opening, the turn time and the heading path
23
- - **Search** — title / full-text scopes; click any result row to locate and highlight that hit, `n/N` Enter stepping, Esc to close
24
- - **Search tolerance** — case, full-width/half-width and whitespace differences match automatically; the "fuzzy" switch also allows a little material wedged between keywords
25
- - **In-chat highlighting** — matched keywords are highlighted in the conversation; the current match is highlighted distinctly
26
- - **Sticky group headers** — while scrolling the outline, the current turn's header stays pinned at the top of the panel
27
- - **Heading level filter** — the round levels button in the header pops down H1–H6 switches for any combination
28
- - **Auto-follow** — the turn being read lights up in a closed blue box while you scroll the conversation; the outline follows on its own
29
- - **Jumping** — click a heading to jump to its position in the conversation, with the scroll animation handed to the browser's own smooth scrolling (advanced on the wall clock, so it takes the same time on a 60 Hz and a 240 Hz panel); when the host re-pages and moves the target mid-flight the jump re-aims, and when the host's own scrolling cancels the animation it is issued again, so a cross-page jump never stops halfway; a browser that carries the call out at once gets a frame-by-frame glide drawn by the plugin instead
30
- - **Back to the newest row** — after paging far up, a floating button in the list's lower-right corner returns to the newest entry in one click (it appears when the list is not at its bottom and disappears once it is)
31
- - **Back to where I left** — closing the panel or the curtain remembers the row that was at the top (each surface keeps its own, and neither overwrites the other); the next time that surface opens, a floating button in the list's upper-right corner returns that row to **where it sat**. It fades out — and forgets the record — once it is used, or as soon as you scroll back there yourself, until the next close. Under the same rule, opening the panel or the curtain puts the **row being read** at the top of the list (when that turn is one of the newest few the list is already at its end, so the row lands at the bottom)
32
- - **Dockable and resizable** — drag the top bar to move the panel, ◀ / ▶ to dock left or right, drag an edge to resize (no upper bound, down to 120px wide), and collapse it into an edge handle (its height is set by the card's "Handle position"); position and size are remembered per browser (top offset, width and height are adjusted by dragging only)
33
- - **Panel scale** — a slider in the settings card scales the **content** (text, icons, buttons and their spacing) from 50% to 200% in 5% steps while the **panel's own size stays exactly as dragged**; dragging only moves the readout, and the settings document is written once you let go. A narrow panel keeps its four header buttons on one line: the empty middle gives way first, then the whole row scrolls sideways (wheel over the header), and the grey grab bar fades out
34
- - **Curtain scale** — a second slider scales the **curtain's content** from 50% to 200% in 5% steps while the curtain's own size stays as it is; the panel scale and the curtain scale are independent of each other. The curtain's content starts one notch smaller than before (100% on the slider is 90% of the previous size), and only the outline inside the curtain is scaled — the conversation itself is untouched
35
- - **Paging** — the most recent groups show first; scrolling up both expands the index and loads older conversation
36
- - **Edge hints** — a brief hint at the bottom of the panel when you keep scrolling past the first or the last entry
37
- - **Markdown-aware** — inline markup in headings is stripped; `#` lines inside fenced code blocks are not headings
38
- - **Theme-aware** — adapts to dark and light themes; visible on the chat view only and fades out elsewhere
39
- - The panel hides itself when the conversation has neither headings nor readable times
40
-
41
- ## Compatibility
42
-
43
- | Plugin | Supported DSH |
44
- | --- | --- |
45
- | **0.7.x** (latest, 0.7.2) | 0.1.5-rc.1, 0.1.5-rc.2, 0.1.7-alpha.1 |
46
- | 0.6.x (0.6.3) | 0.1.5-rc.1, 0.1.5-rc.2 |
47
- | 0.5.x (0.5.1) | 0.1.5-rc.1, 0.1.5-rc.2 |
48
- | 0.4.x (0.4.1) | 0.1.5-rc.1, 0.1.5-rc.2 |
49
- | 0.3.x (0.3.3) | 0.1.5-rc.1 |
50
- | 0.2.x (0.2.2) | = 0.1.2-rc.1 |
51
-
52
- Each major line lists only its newest patch (the defects a new feature introduces are fixed in the patches that follow, so within one major line the newest patch is the one to use; older patches keep working — the plugin does not break existing interfaces).
53
-
54
- `engines.dsh` is declared as **`>=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0`**, with the peer ranges shaped the same way (a prerelease must be given an explicit branch: node-semver admits one only when some comparator in the range sits on that version's exact `major.minor.patch` tuple and carries a prerelease tag of its own, so a plain `>=0.1.5-rc.1` never matches `0.1.7-alpha.1`). 0.7.2 was tested on **0.1.5-rc.2** (the curtain scale was confirmed point by point: at three factors the curtain box keeps its width, height and position while only the content scales, both slider ends, the write-on-release behaviour and the default rendering were exercised, and the two scales were confirmed independent); 0.7.1 was tested on **0.1.5-rc.2** (the "back to where I left" floating button, the leave positions the panel and the curtain record separately, the row being read landing at the top when a surface opens, and the absence of the button after closing at the bottom were each confirmed); 0.7.0 was tested on the same release (the full-width curtain and its droplet handle, the jump-to-section-end buttons, cross-session search, keyboard navigation, the questions-only view and the remembered reading position were each exercised in a real browser; the panel scale, a dragged size reaching storage and coming back, and the collapsed handle's position had been measured before that); on **0.1.5-rc.1** the plugin's loading and client-module delivery were tested for real (the host boots without errors, `--dump-config` carries the quick-toc entry, the combined client bundle URL lists `dsh-quick-toc/client.js`, and the bytes it serves contain this release's curtain handle, jump-to-section-end and reading-position markers). The host interfaces it relies on — the `turnOutline` projection, the session-scoped `useProjection`, the client `sessions` service and its `loadThrough` jump loader (since 0.5.0), the `turn-error` node used for failed turns (since 0.5.1), and the host `settings` service (`installSection`), the client `settingsScope` and the `settings.plugin.item` card slot added in 0.6.0, and the client `sessions.search` that 0.7.0's cross-session search calls — were checked one by one against the installed 0.1.5-rc.1 and 0.1.5-rc.2 bundles, whose code is byte-identical between the two releases (`dsh-client-ui-chat` differs by a single CSS declaration unrelated to this plugin, and the package that carries `search`, `dsh-api-session-controller`, is byte-identical down to the same limits: at most 20 hits per search, 240 code points per snippet), so both releases are claimed. Older DSH versions are unverified and therefore not claimed; on an older DSH the newest usable plugin version is **0.3.2**. **0.1.7-alpha.1 passes too**, in a real instance inside an isolated `DSH_HOME`: the host boots without errors, and the panel and curtain, row jumps and older-history loading, the title/full-text/cross-session searches and switching sessions from a cross-session hit were each exercised in a real browser, with the settings document's reads and writes confirmed as well. To that end 0.7.2 speaks both host lines — on 0.1.5-rc.x it uses the host's `settings.installSection`, the client `settingsScope` and the `settings.plugin.item` card slot; on 0.1.7-alpha.x it uses `settings.configure`, the client `configForms` and the `plugins.bundle.config` card slot, and switches sessions through `uiWorkspace.openSession`. The plugin picks whichever exists at runtime, and **declares no settings service in `inject` at all** (declaring one a host does not provide parks the whole package, which on 0.1.7-alpha.1 stopped the entire web boot from finishing). On install or update, the market (dshmarket) reads `engines.dsh` (or `dsh.engines.dsh`) and the `@deepseek-ai/dsh-*` peer ranges present in the local install from the npm `latest` manifest, shows the host requirement a plugin declares on its card, and offers an optional filter that hides only entries confirmed to mismatch the running host; `dsh.compatibility.dshReleases` is this repository's own record, read by neither DSH nor the market.
55
-
56
- ## Install
57
-
58
- With the DSH CLI:
59
-
60
- ```
61
- dsh plugin --profile web add dsh-quick-toc
62
- ```
63
-
64
- Or from GitHub:
65
-
66
- ```
67
- dsh plugin --profile web add github:LyaxZ/dsh-quick-toc
68
- ```
69
-
70
- Or from a local folder:
71
-
72
- ```
73
- dsh plugin --profile web add <path-to-the-plugin-folder>
74
- ```
75
-
76
- After installing, restart DSH and open the Web UI. The panel starts collapsed: click the edge handle next to the conversation to expand the panel, or click the droplet handle in the tab strip along the top to drop the outline down across the full width.
77
-
78
- ## Usage
79
-
80
- - **Jumping**: click an outline heading or a group header to jump to it; entries tagged `未加载` / "Not loaded" load that turn first, and a failed turn's error row jumps to that error in the conversation
81
- - **Search**: open the box with the magnifier, click a result to locate and highlight it, Enter to step through matches, Esc to close
82
- - **Search tolerance**: full-width/half-width, case and whitespace differences match automatically; for looser matching turn on the "fuzzy" switch next to the search box
83
- - **Level filter**: click the round levels button in the header to pop down the H1–H6 switches
84
- - **Moving and docking**: drag the top bar to move, ◀ / ▶ to switch sides
85
- - **Resizing**: drag the right edge, bottom edge or bottom-right corner (no upper bound, down to 120px wide; a very narrow panel scrolls its header sideways)
86
- - **Loading older turns**: scroll up inside the outline (it both expands the index and loads older conversation)
87
- - **The curtain**: click the droplet handle in the tab strip along the top of the conversation (or the "curtain" round button in the panel's header) for the full-width outline; click any row, press Esc or use the ✕ to close
88
- - **Jump to the end of a section**: hover a heading row, a group header or a result row and click the round button at its right end
89
- - **Cross-session search**: click the scope button beside the search box twice to reach "sessions" and search other sessions through the host's full-text index; clicking a hit switches to that session and keeps looking there
90
- - **Keyboard**: with the panel open, ↑/↓ move the cursor, Enter jumps, Home / End go to either end and Esc collapses; inside the search box ↑/↓ step through the matches
91
- - **Questions only**: the speech-bubble button in the header folds the outline down to each turn's time and the first line of your prompt
92
- - **Back to the newest row**: after paging far up, click the floating button in the list's lower-right corner
93
- - **Back to where I left**: closing the panel or the curtain remembers the row you were looking at; the floating button in the upper-right corner takes you back the next time you open it
94
- - **Reading position**: scroll the conversation and the turn you are reading is boxed in blue; the outline follows
95
- - **Settings**: expand the "Conversation Outline" card under **Settings → Plugins → Plugin configuration** for the language, the default docked edge, the panel scale (a 50%–200% slider in 5% steps), where the collapsed handle sits (a 0%–100% slider in 1% steps: 0% at the bottom, 100% at the top, 50% centred by default), the heading levels shown, the remembered reading position and the fuzzy / hover / diagnostic switches; changed fields can be reset individually (returning one to its default drops the mark). The card and the panel are live in both directions, with no reload needed, and the preferences follow the DSH settings document
96
-
97
- ## Diagnostics
98
-
99
- Mounting the panel prints **nothing** by default. To check the host facilities, expand the "Conversation Outline" card under **Settings → Plugins → Plugin configuration** and tick "print the mount diagnostic to the console" — the line prints on the spot, no reload needed:
100
-
101
- ```
102
- [dsh-quick-toc] panel mounted · turnOutline=… · jumpLoader=… · lang=… · prefs=…
103
- ```
104
-
105
- A missing `turnOutline` means the host has no such projection, and the staged `jumpLoader` wording (`no-sessions-service` / `no-binding-api` / `no-binding-for-session` / `no-loadThrough` / `binding-threw`) pinpoints which link of the jump bridge is broken.
106
-
107
- ## Development
108
-
109
- - `lib/client.js` — all UI logic (browser side)
110
- - `lib/index.js` — the host half: registers the `dsh-quick-toc` settings namespace (a schemastery schema with range validation), which puts the preferences into DSH's settings document and provides the plugin-configuration entry
111
- - `cordis.patch.yml` — loader patch (official DSH bundle format)
112
- - The panel registers into the session-scoped `conversation.input.overlay` slot so it receives session-scoped hooks (`useChat`, `useSession`, `sessionId`, …), and renders itself through `createPortal` into `document.body` as a fixed floating dock; conversation data comes from `props.useChat` (`ChatSnapshot.order` and `nodes`; node shape: `kind: user/assistant-step`, `location.turn`, `data.blocks`)
113
- - The "unloaded turn" capability needs two host facilities: the `turnOutline` projection (the whole-session turn index, each entry carrying its `turn/start` seq) and the turn-jump loader (the client `sessions` service's `binding(sessionId).session.loadThrough(seq)`, reached through the client ctx's declaration-free `ctx.get("sessions")` lookup). Each degrades on its own: without the projection the outline lists loaded turns only, without the loader unloaded entries are shown but not jumped to, and nothing else in the panel is affected.
114
- - The failure row reads the host's `turn-error` conversation node (published when `turn/end` carries an `error` reason, with `message` and an optional `code`); on a host without that node the row is simply absent and nothing else changes.
115
- - Interface text lives in one string table at the top of `lib/client.js` (`DICTS`, one Chinese and one English copy) and the language setting picks between them: on `follow the host` the host's translate function (`ctx.locale.bind("dsh-quick-toc")`, fed by the same `DICTS` tables) is asked first and the built-in table is only the fallback. The two tables must stay key-aligned; the only Chinese-only entry is `time.beforeYesterday` (English carries the date for anything older than yesterday), while `time.yesterday` exists in both (`yesterday`).
116
- - Preferences sit in two layers behind one module-level store: the Host settings document (`ctx.settingsScope.bind({ namespace: "dsh-quick-toc" })` — language, docked edge, levels, scale, handle position, remembered reading position, fuzzy, hover and diagnostic; authoritative on a loopback page) and `localStorage` (the mirror, plus the real storage for top offset / width / height, which describe this screen and are saved per browser). Writes are routed per field: host fields go through `scope.mutate` (folded in locally, reconciled when the host answers), everything else goes to `localStorage`; a value that IS the field's default is written by removing that entry from the user layer instead (the same thing as "not customized"). On a non-loopback page DSH marks settings read-only, so those nine preferences fall back to the mirror and behave exactly as in 0.5.x; values left by a 0.5.x install are imported into the host layer once, the first time it answers (and only while the user layer is empty).
117
- - The curtain and the docked panel **share one list DOM**: the curtain only moves the panel itself into a full-width container that drops from the lower edge of the tab strip (in curtain mode the panel swaps to a different geometry — relative positioning, 100% width and height, no panel scale), and that container stays mounted as a zero-size pass-through while the curtain is up, so opening or closing it never rebuilds the list or loses the scroll position. Cross-session search goes through the client `ctx.get("sessions")` service's `search(query, signal)` (the host's session full-text index, at most 20 hits per search); each session's reading position lives in this browser (`dsh-quick-toc.readPos.v1`, valid for half an hour).
118
- - The configuration card registers into the `settings.plugin.item` slot (keyed on the plugin's namespace; the slot is dispatched only for namespaces the Host actually serves, so a deployment without the host half simply shows no card and the panel is unaffected). The card and the panel share the store above, which is what makes the two live in both directions.
119
- - Changes to `lib/client.js` show up after a page refresh (client modules are served under a content hash and DSH's client HMR pushes reloads); changes to `lib/index.js` (the host half) need a DSH restart
120
-
121
- ## License
122
-
123
- MIT © 2026 LyaxZ
1
+ # dsh-quick-toc
2
+
3
+ > **English** | [中文](README.md)
4
+
5
+ A conversation TOC plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH): it turns the Markdown headings (H1–H6) of AI replies into a navigable outline — the same list as a side panel or as a full-width curtain — grouped by conversation turn and covering the whole session (including turns that are not loaded yet), with title/full-text/cross-session search, hover previews, several ways to jump (a heading, a turn header, the end of a section), and a reading position that both follows and is remembered.
6
+
7
+ ## Features
8
+
9
+ - **Turn-grouped outline** — each user message plus its following AI replies form one group; the group header shows the turn's time and a first-line preview, and clicking it jumps to the start of that turn's model reply
10
+ - **Whole-session coverage** — turns the conversation window has not loaded are listed too (tagged `未加载` / "Not loaded", with previews); clicking one loads that turn and jumps to it
11
+ - **Failures are reported** — a turn with no reply at all (request timeout, upstream error) shows `请求失败` / "Request failed" plus the host's error text, and clicking it jumps to that error in the conversation
12
+ - **A full-width curtain** — the droplet handle inside the tab strip along the top of the conversation (or the "curtain" round button in the panel's header) lays the outline out across the full width and drops it down: no scrim, nothing dimmed, larger type and looser rows, three parts per row (level badge / heading / the section's opening), and the search column sliding in from the right; click a row, press Esc or use the ✕ to close. It shares the one list with the docked panel, so the scroll position, the search and the keyboard cursor all carry over
13
+ - **Jump to the end of a section** — hover a heading row, a group header or a result row and a round button fades in at its right end; a group header's button jumps to the end of the whole turn
14
+ - **Cross-session search** — the third scope, "sessions", searches the message bodies of other sessions through the host's full-text index; clicking a hit switches to that session and keeps looking there, and archived / subagent / delisted sessions are skipped with a count
15
+ - **Questions only** — one click folds the outline down to each turn's time and the first line of your prompt
16
+ - **A remembered reading position** — reopening a session returns to the turn you were last reading (within half an hour; the card can switch it off)
17
+ - **Keyboard navigation** — ↑/↓ move the cursor, Enter jumps, Home / End go to either end, Esc collapses; the cursor's position is announced to screen readers
18
+ - **Bilingual** — the interface language can follow the host, or be forced to Chinese or English; Chinese stamps use `昨天` / `前天`, English reads yesterday as the word (`yesterday`) and carries the `YY-MM-DD HH:MM` date for anything older
19
+ - **A plugin-configuration card** — a "Conversation Outline" card under Settings → Plugins → Plugin configuration: the language, the default docked edge, the heading levels shown, where the collapsed handle sits, the remembered reading position, fuzzy search, hover previews and the diagnostic switch; changed fields are marked "customized" and can be reset individually (returning a field to its default drops the mark), and the card and the panel stay in sync
20
+ - **Turn stamps carry the day** — yesterday reads `昨天 15:04`, the day before `前天 15:04`, anything older `25-09-11 15:04`, so times never blur together in a session spanning days (with the interface in English every stamp older than today carries a `YY-MM-DD` date instead)
21
+ - **Row subtitles** — under each heading, the first sentence of that section, so identically-titled headings can be told apart
22
+ - **Hover previews** — hovering a heading row shows the section's opening, the turn time and the heading path
23
+ - **Search** — title / full-text scopes; click any result row to locate and highlight that hit, `n/N` Enter stepping, Esc to close
24
+ - **Search tolerance** — case, full-width/half-width and whitespace differences match automatically; the "fuzzy" switch also allows a little material wedged between keywords
25
+ - **In-chat highlighting** — matched keywords are highlighted in the conversation; the current match is highlighted distinctly
26
+ - **Sticky group headers** — while scrolling the outline, the current turn's header stays pinned at the top of the panel
27
+ - **Heading level filter** — the round levels button in the header pops down H1–H6 switches for any combination
28
+ - **Auto-follow** — the turn being read lights up in a closed blue box while you scroll the conversation; the outline follows on its own
29
+ - **Jumping** — click a heading to jump to its position in the conversation, with the scroll animation handed to the browser's own smooth scrolling (advanced on the wall clock, so it takes the same time on a 60 Hz and a 240 Hz panel); when the host re-pages and moves the target mid-flight the jump re-aims, and when the host's own scrolling cancels the animation it is issued again, so a cross-page jump never stops halfway; a browser that carries the call out at once gets a frame-by-frame glide drawn by the plugin instead
30
+ - **Back to the newest row** — after paging far up, a floating button in the list's lower-right corner returns to the newest entry in one click (it appears when the list is not at its bottom and disappears once it is)
31
+ - **Back to where I left** — closing the panel or the curtain remembers the row that was at the top (each surface keeps its own, and neither overwrites the other); the next time that surface opens, a floating button in the list's upper-right corner returns that row to **where it sat**. It fades out — and forgets the record — once it is used, or as soon as you scroll back there yourself, until the next close. Under the same rule, opening the panel or the curtain puts the **row being read** at the top of the list (when that turn is one of the newest few the list is already at its end, so the row lands at the bottom)
32
+ - **Dockable and resizable** — drag the top bar to move the panel, ◀ / ▶ to dock left or right, drag an edge to resize (no upper bound, down to 120px wide), and collapse it into an edge handle (its height is set by the card's "Handle position"); position and size are remembered per browser (top offset, width and height are adjusted by dragging only)
33
+ - **Panel scale** — a slider in the settings card scales the **content** (text, icons, buttons and their spacing) from 50% to 200% in 5% steps while the **panel's own size stays exactly as dragged**; dragging only moves the readout, and the settings document is written once you let go. A narrow panel keeps its four header buttons on one line: the empty middle gives way first, then the whole row scrolls sideways (wheel over the header), and the grey grab bar fades out
34
+ - **Curtain scale** — a second slider scales the **curtain's content** from 50% to 200% in 5% steps while the curtain's own size stays as it is; the panel scale and the curtain scale are independent of each other. The curtain's content starts one notch smaller than before (100% on the slider is 90% of the previous size), and only the outline inside the curtain is scaled — the conversation itself is untouched
35
+ - **Paging** — the most recent groups show first; scrolling up both expands the index and loads older conversation
36
+ - **Edge hints** — a brief hint at the bottom of the panel when you keep scrolling past the first or the last entry
37
+ - **Markdown-aware** — inline markup in headings is stripped; `#` lines inside fenced code blocks are not headings
38
+ - **Theme-aware** — adapts to dark and light themes; visible on the chat view only and fades out elsewhere
39
+ - The panel hides itself when the conversation has neither headings nor readable times
40
+
41
+ ## Compatibility
42
+
43
+ | Plugin | Supported DSH |
44
+ | --- | --- |
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
+ | 0.6.x (0.6.3) | 0.1.5-rc.1, 0.1.5-rc.2 |
47
+ | 0.5.x (0.5.1) | 0.1.5-rc.1, 0.1.5-rc.2 |
48
+ | 0.4.x (0.4.1) | 0.1.5-rc.1, 0.1.5-rc.2 |
49
+ | 0.3.x (0.3.3) | 0.1.5-rc.1 |
50
+ | 0.2.x (0.2.2) | = 0.1.2-rc.1 |
51
+
52
+ Each major line lists only its newest patch (the defects a new feature introduces are fixed in the patches that follow, so within one major line the newest patch is the one to use; older patches keep working — the plugin does not break existing interfaces).
53
+
54
+ `engines.dsh` is declared as **`>=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0`**, with the peer ranges shaped the same way (a prerelease must be given an explicit branch: node-semver admits one only when some comparator in the range sits on that version's exact `major.minor.patch` tuple and carries a prerelease tag of its own, so a plain `>=0.1.5-rc.1` never matches `0.1.7-alpha.1`). 0.7.3 was tested on **0.1.5-rc.3** and **0.1.7-alpha.2** (the panel and the curtain, row jumps and older-history loading, the title/full-text/cross-session searches, switching sessions from a cross-session hit, the configuration card and the host settings document's reads and writes were each exercised), and neither release needed a code change — checked offline as well: rc.3 differs from rc.2 only in each package's `package.json` version (plus two sidebar files), and alpha.2's changes sit in the session controller and the chat renderer while every interface and node shape this plugin uses is still there (`turnOutline`, `turn-error`, `useChat`, `data-chat-anchor-key`, `sessions.search`, `loadThrough`, `settings.*` and both card slots; the two extra `data-conversation-scroll` hits in alpha.2 are stylesheet text, not a second scroll container). 0.7.2 was tested on **0.1.5-rc.2** (the curtain scale was confirmed point by point: at three factors the curtain box keeps its width, height and position while only the content scales, both slider ends, the write-on-release behaviour and the default rendering were exercised, and the two scales were confirmed independent); 0.7.1 was tested on **0.1.5-rc.2** (the "back to where I left" floating button, the leave positions the panel and the curtain record separately, the row being read landing at the top when a surface opens, and the absence of the button after closing at the bottom were each confirmed); 0.7.0 was tested on the same release (the full-width curtain and its droplet handle, the jump-to-section-end buttons, cross-session search, keyboard navigation, the questions-only view and the remembered reading position were each exercised in a real browser; the panel scale, a dragged size reaching storage and coming back, and the collapsed handle's position had been measured before that); on **0.1.5-rc.1** the plugin's loading and client-module delivery were tested for real (the host boots without errors, `--dump-config` carries the quick-toc entry, the combined client bundle URL lists `dsh-quick-toc/client.js`, and the bytes it serves contain this release's curtain handle, jump-to-section-end and reading-position markers). The host interfaces it relies on — the `turnOutline` projection, the session-scoped `useProjection`, the client `sessions` service and its `loadThrough` jump loader (since 0.5.0), the `turn-error` node used for failed turns (since 0.5.1), and the host `settings` service (`installSection`), the client `settingsScope` and the `settings.plugin.item` card slot added in 0.6.0, and the client `sessions.search` that 0.7.0's cross-session search calls — were checked one by one against the installed 0.1.5-rc.1 and 0.1.5-rc.2 bundles, whose code is byte-identical between the two releases (`dsh-client-ui-chat` differs by a single CSS declaration unrelated to this plugin, and the package that carries `search`, `dsh-api-session-controller`, is byte-identical down to the same limits: at most 20 hits per search, 240 code points per snippet), so both releases are claimed. Older DSH versions are unverified and therefore not claimed; on an older DSH the newest usable plugin version is **0.3.2**. **0.1.7-alpha.1 passes too**, in a real instance inside an isolated `DSH_HOME`: the host boots without errors, and the panel and curtain, row jumps and older-history loading, the title/full-text/cross-session searches and switching sessions from a cross-session hit were each exercised in a real browser, with the settings document's reads and writes confirmed as well. To that end 0.7.2 speaks both host lines — on 0.1.5-rc.x it uses the host's `settings.installSection`, the client `settingsScope` and the `settings.plugin.item` card slot; on 0.1.7-alpha.x it uses `settings.configure`, the client `configForms` and the `plugins.bundle.config` card slot, and switches sessions through `uiWorkspace.openSession`. The plugin picks whichever exists at runtime, and **declares no settings service in `inject` at all** (declaring one a host does not provide parks the whole package, which on 0.1.7-alpha.1 stopped the entire web boot from finishing). On install or update, the market (dshmarket) reads `engines.dsh` (or `dsh.engines.dsh`) and the `@deepseek-ai/dsh-*` peer ranges present in the local install from the npm `latest` manifest, shows the host requirement a plugin declares on its card, and offers an optional filter that hides only entries confirmed to mismatch the running host; `dsh.compatibility.dshReleases` is this repository's own record, read by neither DSH nor the market.
55
+
56
+ ## Install
57
+
58
+ With the DSH CLI:
59
+
60
+ ```
61
+ dsh plugin --profile web add dsh-quick-toc
62
+ ```
63
+
64
+ Or from GitHub:
65
+
66
+ ```
67
+ dsh plugin --profile web add github:LyaxZ/dsh-quick-toc
68
+ ```
69
+
70
+ Or from a local folder:
71
+
72
+ ```
73
+ dsh plugin --profile web add <path-to-the-plugin-folder>
74
+ ```
75
+
76
+ After installing, restart DSH and open the Web UI. The panel starts collapsed: click the edge handle next to the conversation to expand the panel, or click the droplet handle in the tab strip along the top to drop the outline down across the full width.
77
+
78
+ ## Usage
79
+
80
+ - **Jumping**: click an outline heading or a group header to jump to it; entries tagged `未加载` / "Not loaded" load that turn first, and a failed turn's error row jumps to that error in the conversation
81
+ - **Search**: open the box with the magnifier, click a result to locate and highlight it, Enter to step through matches, Esc to close
82
+ - **Search tolerance**: full-width/half-width, case and whitespace differences match automatically; for looser matching turn on the "fuzzy" switch next to the search box
83
+ - **Level filter**: click the round levels button in the header to pop down the H1–H6 switches
84
+ - **Moving and docking**: drag the top bar to move, ◀ / ▶ to switch sides
85
+ - **Resizing**: drag the right edge, bottom edge or bottom-right corner (no upper bound, down to 120px wide; a very narrow panel scrolls its header sideways)
86
+ - **Loading older turns**: scroll up inside the outline (it both expands the index and loads older conversation)
87
+ - **The curtain**: click the droplet handle in the tab strip along the top of the conversation (or the "curtain" round button in the panel's header) for the full-width outline; click any row, press Esc or use the ✕ to close
88
+ - **Jump to the end of a section**: hover a heading row, a group header or a result row and click the round button at its right end
89
+ - **Cross-session search**: click the scope button beside the search box twice to reach "sessions" and search other sessions through the host's full-text index; clicking a hit switches to that session and keeps looking there
90
+ - **Keyboard**: with the panel open, ↑/↓ move the cursor, Enter jumps, Home / End go to either end and Esc collapses; inside the search box ↑/↓ step through the matches
91
+ - **Questions only**: the speech-bubble button in the header folds the outline down to each turn's time and the first line of your prompt
92
+ - **Back to the newest row**: after paging far up, click the floating button in the list's lower-right corner
93
+ - **Back to where I left**: closing the panel or the curtain remembers the row you were looking at; the floating button in the upper-right corner takes you back the next time you open it
94
+ - **Reading position**: scroll the conversation and the turn you are reading is boxed in blue; the outline follows
95
+ - **Settings**: expand the "Conversation Outline" card under **Settings → Plugins → Plugin configuration** for the language, the default docked edge, the panel scale (a 50%–200% slider in 5% steps), where the collapsed handle sits (a 0%–100% slider in 1% steps: 0% at the bottom, 100% at the top, 50% centred by default), the heading levels shown, the remembered reading position and the fuzzy / hover / diagnostic switches; changed fields can be reset individually (returning one to its default drops the mark). The card and the panel are live in both directions, with no reload needed, and the preferences follow the DSH settings document
96
+
97
+ ## Diagnostics
98
+
99
+ Mounting the panel prints **nothing** by default. To check the host facilities, expand the "Conversation Outline" card under **Settings → Plugins → Plugin configuration** and tick "print the mount diagnostic to the console" — the line prints on the spot, no reload needed:
100
+
101
+ ```
102
+ [dsh-quick-toc] panel mounted · turnOutline=… · jumpLoader=… · lang=… · prefs=…
103
+ ```
104
+
105
+ A missing `turnOutline` means the host has no such projection, and the staged `jumpLoader` wording (`no-sessions-service` / `no-binding-api` / `no-binding-for-session` / `no-loadThrough` / `binding-threw`) pinpoints which link of the jump bridge is broken.
106
+
107
+ ## Development
108
+
109
+ - `lib/client.js` — all UI logic (browser side)
110
+ - `lib/index.js` — the host half: registers the `dsh-quick-toc` settings namespace (a schemastery schema with range validation), which puts the preferences into DSH's settings document and provides the plugin-configuration entry
111
+ - `cordis.patch.yml` — loader patch (official DSH bundle format)
112
+ - The panel registers into the session-scoped `conversation.input.overlay` slot so it receives session-scoped hooks (`useChat`, `useSession`, `sessionId`, …), and renders itself through `createPortal` into `document.body` as a fixed floating dock; conversation data comes from `props.useChat` (`ChatSnapshot.order` and `nodes`; node shape: `kind: user/assistant-step`, `location.turn`, `data.blocks`)
113
+ - The "unloaded turn" capability needs two host facilities: the `turnOutline` projection (the whole-session turn index, each entry carrying its `turn/start` seq) and the turn-jump loader (the client `sessions` service's `binding(sessionId).session.loadThrough(seq)`, reached through the client ctx's declaration-free `ctx.get("sessions")` lookup). Each degrades on its own: without the projection the outline lists loaded turns only, without the loader unloaded entries are shown but not jumped to, and nothing else in the panel is affected.
114
+ - The failure row reads the host's `turn-error` conversation node (published when `turn/end` carries an `error` reason, with `message` and an optional `code`); on a host without that node the row is simply absent and nothing else changes.
115
+ - Interface text lives in one string table at the top of `lib/client.js` (`DICTS`, one Chinese and one English copy) and the language setting picks between them: on `follow the host` the host's translate function (`ctx.locale.bind("dsh-quick-toc")`, fed by the same `DICTS` tables) is asked first and the built-in table is only the fallback. The two tables must stay key-aligned; the only Chinese-only entry is `time.beforeYesterday` (English carries the date for anything older than yesterday), while `time.yesterday` exists in both (`yesterday`).
116
+ - Preferences sit in two layers behind one module-level store: the Host settings document (language, docked edge, levels, scale, handle position, remembered reading position, fuzzy, hover and diagnostic; authoritative on a loopback page) and `localStorage` (the mirror, plus the real storage for top offset / width / height, which describe this screen and are saved per browser). **The host layer itself is one of two dialects, whichever the running host offers**: on 0.1.5-rc.x it is `ctx.settingsScope.bind({ namespace: "dsh-quick-toc" })` (addressed by namespace), while on 0.1.7-alpha.x it is `ctx.configForms.get(<profile entry id>)` (addressed by profile entry id, the client recognising its own entry by the schema the host serves). Both expose the same methods over the same snapshot fields, so the store stays one. Writes are routed per field: host fields go through `scope.mutate` (folded in locally, reconciled when the host answers), everything else goes to `localStorage`; a value that IS the field's default is written by removing that entry from the user layer instead (the same thing as "not customized"). On a non-loopback page DSH marks settings read-only, so those nine preferences fall back to the mirror and behave exactly as in 0.5.x; values left by a 0.5.x install are imported into the host layer once, the first time it answers (and only while the user layer is empty). Once the host has answered, that layer is authoritative — which is why a local key such as `localStorage['dsh-quick-toc.debug']` no longer takes effect on a loopback page: use the switch in the card.
117
+ - The curtain and the docked panel **share one list DOM**: the curtain only moves the panel itself into a full-width container that drops from the lower edge of the tab strip (in curtain mode the panel swaps to a different geometry — relative positioning, 100% width and height, no panel scale), and that container stays mounted as a zero-size pass-through while the curtain is up, so opening or closing it never rebuilds the list or loses the scroll position. Cross-session search goes through the client `ctx.get("sessions")` service's `search(query, signal)` (the host's session full-text index, at most 20 hits per search); each session's reading position lives in this browser (`dsh-quick-toc.readPos.v1`, valid for half an hour).
118
+ - The configuration card is registered into one of two slots, whichever the running host offers: on 0.1.5-rc.x it is `settings.plugin.item` (keyed on the plugin's namespace; the slot is dispatched only for namespaces the Host actually serves, so a deployment without the host half simply shows no card and the panel is unaffected), and on 0.1.7-alpha.x it is `plugins.bundle.config` (keyed on the npm package name, so the card appears on that bundle's page in the sidebar's Plugins list). Both registrations happen only once a settings scope has bound, and the card and the panel share the store above, which is what makes the two live in both directions.
119
+ - Changes to `lib/client.js` show up after a page refresh (client modules are served under a content hash and DSH's client HMR pushes reloads); changes to `lib/index.js` (the host half) need a DSH restart
120
+
121
+ ## License
122
+
123
+ MIT © 2026 LyaxZ
package/README.md CHANGED
@@ -1,123 +1,123 @@
1
- # dsh-quick-toc
2
-
3
- > [English](README.en.md) | **中文**
4
-
5
- **DeepSeek Harness(DSH)对话大纲插件**:把 AI 回复中的 Markdown 标题(H1–H6)提取成可导航的大纲——侧边面板与全屏幕布两种大纲视图,按对话回合分组、覆盖整段会话(含尚未加载进窗口的回合),支持标题/全文/跨会话三档检索、悬停预览、多种跳转(点标题、点组头、跳到本节末尾),阅读位置自动跟随并可记住。
6
-
7
- ## 功能
8
-
9
- - **按回合分组** —— 每条用户消息 + 其后续 AI 回复为一组,组头显示回合时间与首行预览,点击跳到该回合模型回答的开头
10
- - **覆盖整段会话** —— 未加载进对话窗口的回合也在列表里(带「未加载」标记与预览);点击即加载该回合并跳过去
11
- - **失败回合的报错** —— 请求超时 / 上游报错这类没有回复的回合显示 `请求失败` 与报错原文,点击跳到对话中的报错位置
12
- - **全屏「幕布」** —— 对话顶边标签条里的液滴把手(或面板标题栏的「幕布」圆钮)把大纲铺成整幅宽度落下:没有遮罩、不压暗对话,字号与行距都放大,每行三段(层级徽标 / 标题 / 该节正文开头),搜索栏从右侧推进来;点条目、Esc 或 ✕ 收起。它与停靠面板共用同一份列表,滚动位置、搜索、键盘光标全部延续
13
- - **跳到本节末尾** —— 悬停标题行 / 组头 / 结果行,右端渐显一枚圆钮,点击跳到该节内容结束处;组头这颗跳到整轮对话的末尾
14
- - **跨会话检索** —— 搜索范围第三档「会话」用宿主的全文索引搜其他会话的消息正文,点结果切过去并继续查找;已归档 / 子会话 / 不在列表里的会话会被略过并计数
15
- - **只看提问** —— 一键把大纲折成「每轮只留时间与你的提问首行」
16
- - **记住阅读位置** —— 重新打开会话回到上次读到的那一轮(半小时内有效;可在设置里关掉)
17
- - **键盘导航** —— ↑/↓ 移动光标、Enter 跳转、Home / End 到首尾、Esc 收起;光标位置有屏幕阅读器播报
18
- - **中英双语** —— 界面语言可设为跟随宿主、中文或 English;中文时间戳用 `昨天` / `前天`,英文昨天用 `yesterday`、更早用 `YY-MM-DD HH:MM` 日期
19
- - **插件配置卡片** —— 设置 → 插件 → 插件配置里的「对话大纲」卡片:语言、默认停靠边缘、显示的标题层级、收起把手的位置、记住阅读位置、模糊搜索、悬停预览与诊断开关;改过的字段标「已自定义」并可单独重置(改回默认值时标记自动消失),卡片与面板实时同步
20
- - **回合时间带日期** —— 昨天 `昨天 15:04`、前天 `前天 15:04`、更早 `25-09-11 15:04`,跨天的会话里时间不再重影
21
- - **标题行副标题** —— 每行标题下方显示该节正文的第一句,同名标题一眼可辨
22
- - **悬停预览** —— 悬停标题行显示该节开头、回合时间与层级路径
23
- - **搜索** —— 标题 / 全文两种范围,结果列表点击定位并高亮,`n/N` 回车逐处跳,Esc 关闭
24
- - **搜索容错** —— 大小写、全角半角、连续空白自动视为同一匹配;「模糊」开关可放宽到关键字中间夹字
25
- - **对话内高亮** —— 命中的关键字在对话中高亮,当前命中单独标亮
26
- - **组头吸顶** —— 滚动大纲时,当前回合的组头固定在面板顶部
27
- - **层级筛选** —— 标题栏的圆形层级按钮弹出 H1–H6 开关,任意组合显示
28
- - **自动跟随** —— 滚动对话时正在阅读的回合自动点亮(封闭蓝框),大纲自动跟随
29
- - **跳转** —— 点击标题跳到对话中该标题的位置,滚动动画交给浏览器自己的平滑滚动(按真实时间推进,60Hz 与 240Hz 屏幕上时长一致);飞行中目标被宿主翻页挪动时会重新瞄准、动画被宿主滚动打断时会重新发起,跨页远跳不会半途停下;若某个浏览器把平滑滚动直接执行成瞬移,插件改为自己逐帧绘制。面板与对话双向定位
30
- - **回到底部** —— 往上翻远之后,列表右下角的浮动按钮一步回到最新一条(不在底部时出现、到底后消失)
31
- - **回到我离开的位置** —— 关闭面板或幕布时记住列表顶部那一行(两者各记各的、互不覆盖),下次打开时列表右上角出现向上的浮动按钮,一步回到那一行的**原位置**;用过一次或自己滚回该处就渐隐并忘掉,直到下次关闭再记。同一规则下,打开面板或幕布时**正在阅读的那一行**会落在列表顶部(读的就在最新几轮时列表已经到底,那一行自然停在底部)
32
- - **可停靠、可缩放** —— 拖顶部横条移动,◀ / ▶ 切换左右停靠,拖边缘调宽高(尺寸没有上限,最窄可到 120px),收起后成为边缘把手(停在什么高度由设置里的「把手位置」决定);位置与尺寸按浏览器记住(顶边距、宽度、高度只通过拖拽调整)
33
- - **面板缩放** —— 设置里的滑块把**内容**(文字、图标、按钮及其间距)在 50%–200% 之间按 5% 一档缩放,**面板本身的尺寸不变**;拖动滑块时只移动读数,松手才写入设置。面板变窄时顶栏四颗按钮保持一行:先挤掉中间的空白,挤满后整行可横向滚动(在顶栏上滚滚轮即可),此时顶部的灰色拖拽横条渐隐消失
34
- - **幕布缩放** —— 另一个滑块,把**幕布内容**在 50%–200% 之间按 5% 一档缩放,幕布本身的尺寸不变;与面板缩放各自独立、互不影响。幕布内容的默认大小比此前小一档(滑块的 100% 即此前的 90%),且只缩放幕布里的大纲,不改变对话本体
35
- - **分页** —— 默认显示最近的若干组,向上滚动既展开索引也加载更早的对话
36
- - **边界提示** —— 滚到最早或最新再继续滚动时,面板底部短暂提示
37
- - **Markdown 感知** —— 标题中的行内标记会被剥离;围栏代码块里的 `#` 行不算标题
38
- - **主题适配** —— 深色 / 浅色主题自适应;仅对话视图显示,切到其他视图渐隐
39
- - 对话中没有标题也没有可识别时间时面板自动隐藏
40
-
41
- ## 兼容性
42
-
43
- | 插件版本 | 支持的 DSH 版本 |
44
- | --- | --- |
45
- | **0.7.x**(最新,0.7.2) | 0.1.5-rc.1、0.1.5-rc.2、0.1.7-alpha.1 |
46
- | 0.6.x(0.6.3) | 0.1.5-rc.1、0.1.5-rc.2 |
47
- | 0.5.x(0.5.1) | 0.1.5-rc.1、0.1.5-rc.2 |
48
- | 0.4.x(0.4.1) | 0.1.5-rc.1、0.1.5-rc.2 |
49
- | 0.3.x(0.3.3) | 0.1.5-rc.1 |
50
- | 0.2.x(0.2.2) | = 0.1.2-rc.1 |
51
-
52
- 每个大版本只列该系列最新的一个补丁版本(新功能引入的缺陷都在其后的补丁里修掉了,所以同一个大版本内直接用最新补丁即可;旧补丁仍可继续用,插件不破坏既有接口)。
53
-
54
- `engines.dsh` 声明为 **`>=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0`**,peer 范围同形(预发布版本必须显式留分支:node-semver 只在一个范围的某个比较符与该版本落在同一个 `major.minor.patch` 元组、且自身带预发布标签时才放行它,所以写成 `>=0.1.5-rc.1` 是匹配不到 `0.1.7-alpha.1` 的)。0.7.2 在 **0.1.5-rc.2** 上实测通过(幕布缩放逐项确认:三档因子下幕布盒子的宽高与位置不变、只有内容按比例缩放,50%–200% 两端、松手写入、以及默认值渲染都比对过;面板与幕布两套缩放各自独立);0.7.1 在 **0.1.5-rc.2** 上实测通过(「回到我离开的位置」浮动按钮、面板与幕布分别记录的离开位置、打开时落在正在阅读的那一行、以及在底部关闭再打开不出按钮都逐项确认);0.7.0 在同一版本上实测通过(全屏幕布与液滴把手、跳到本节末尾、跨会话检索、键盘导航、只看提问、记住阅读位置都在真实浏览器里逐项跑过;面板缩放、尺寸的落盘与读回、收起把手的位置等既有能力此前已实测);**0.1.5-rc.1** 上实测了插件加载与客户端模块下发(宿主启动无报错、`--dump-config` 里有 quick-toc 条目、客户端组合包 URL 里列出 `dsh-quick-toc/client.js`,取回的下发内容包含本版新增的幕布把手、跳到本节末尾与阅读位置等标记),它依赖的宿主接口——`turnOutline` 投影、会话级槽位的 `useProjection`、客户端 `sessions` 服务与 `loadThrough` 跳转加载器(0.5.0 起)、失败回合所用的 `turn-error` 节点(0.5.1 起)、以及 0.6.0 新增的宿主 `settings` 服务(`installSection`)、客户端 `settingsScope` 与 `settings.plugin.item` 配置卡片槽位、以及 0.7.0 跨会话检索所用的客户端 `sessions.search`——在 0.1.5-rc.1 与 0.1.5-rc.2 的安装包中逐个核对存在(这些宿主包的代码在两版之间逐字节相同,`dsh-client-ui-chat` 仅差一条与本插件无关的 CSS 声明;`search` 所在的 `dsh-api-session-controller` 两版逐字节相同,含「一次最多 20 条、片段上限 240 字符」的同一套上限),因此两版都声明兼容。更早的 DSH 版本未经验证,不作声明;在更早的 DSH 上可安装的最新插件版本是 **0.3.2**。**0.1.7-alpha.1 上也实测通过**(隔离的 `DSH_HOME` 里跑真实实例):宿主启动无报错、面板与幕布、点行跳转与翻历史、标题/全文/跨会话三档检索、跨会话命中切换会话,都在真实浏览器里逐项跑过,设置文档的读写也逐项确认。为此 0.7.2 同时支持两条宿主线——0.1.5-rc.x 上是宿主的 `settings.installSection` + 客户端 `settingsScope` + `settings.plugin.item` 卡片槽位,0.1.7-alpha.x 上换成 `settings.configure` + 客户端 `configForms` + `plugins.bundle.config` 卡片槽位、并用 `uiWorkspace.openSession` 切换会话;插件在运行时按服务是否存在二选一,且**不再把任一设置服务写进 `inject`**(声明一个宿主没有的服务会让整个包停在 pending,0.1.7-alpha.1 上这曾让整页 Web 起不来)。安装或更新时,市场(dshmarket)读取 npm 上 `latest` 版本的 `engines.dsh`(或 `dsh.engines.dsh`)与本机存在的 `@deepseek-ai/dsh-*` peer 范围,在卡片上显示该插件声明的宿主要求,并提供「只显示与当前宿主确认匹配的条目」这一可选过滤;`dsh.compatibility.dshReleases` 是本仓库自己的记录,DSH 与市场都不读取。
55
-
56
- ## 安装
57
-
58
- 通过 DSH CLI 安装:
59
-
60
- ```
61
- dsh plugin --profile web add dsh-quick-toc
62
- ```
63
-
64
- 也可以从 GitHub 安装:
65
-
66
- ```
67
- dsh plugin --profile web add github:LyaxZ/dsh-quick-toc
68
- ```
69
-
70
- 或以本地目录安装:
71
-
72
- ```
73
- dsh plugin --profile web add <插件目录路径>
74
- ```
75
-
76
- 安装后重启 DSH 并打开 Web UI。面板默认收起:点对话区边缘的把手展开面板,或点对话顶边标签条里的液滴把手直接把大纲铺成整幅宽度落下。
77
-
78
- ## 使用
79
-
80
- - **跳转**:点击大纲标题或组头跳到对应位置;带「未加载」标记的条目会先把该回合加载进来再跳;失败回合点报错行跳到对话中的报错位置
81
- - **搜索**:放大镜打开搜索框,输入后点结果定位并高亮,回车逐处跳,Esc 关闭
82
- - **搜索容错**:全角/半角、大小写、空格差异会自动匹配;需要更宽松时点搜索框右侧的「模糊」开关
83
- - **层级筛选**:点标题栏的圆形层级按钮弹出 H1–H6 开关,选择显示的层级
84
- - **移动与停靠**:拖顶部横条移动,◀ / ▶ 切换左右停靠
85
- - **调整大小**:拖右边缘、下边缘或右下角(没有上限,最窄到 120px;面板过窄时顶栏可横向滚动)
86
- - **加载更早**:在大纲中向上滚动(既展开索引,也加载更早的对话)
87
- - **幕布**:点对话顶边标签条里的液滴把手(或面板标题栏的「幕布」圆钮)展开全屏大纲;点任意条目、按 Esc 或点右上角的 ✕ 收起
88
- - **跳到本节末尾**:悬停标题行 / 组头 / 结果行,点右端出现的圆钮
89
- - **跨会话检索**:搜索框右侧的范围按钮点两下切到「会话」,用宿主的全文索引搜其他会话;点结果切到那个会话并继续查找
90
- - **键盘**:面板打开后 ↑/↓ 移动光标、Enter 跳转、Home / End 到首尾、Esc 收起;搜索框里 ↑/↓ 在命中之间切换
91
- - **只看提问**:标题栏的语音气泡按钮把大纲折成每轮的时间 + 你的提问首行
92
- - **回到底部**:翻远了之后点列表右下角的浮动按钮回到最新一条
93
- - **回到我离开的位置**:关掉面板或幕布时它记住你当时看的那一行;下次打开时右上角的浮动按钮一步带你回去
94
- - **阅读位置**:滚动对话,正在阅读的回合会以蓝框标出;大纲会自动跟随
95
- - **设置**:在 **设置 → 插件 → 插件配置** 里展开「对话大纲」卡片,可改语言、默认停靠边缘、面板缩放(50%–200% 滑块,5% 一档)、收起把手的位置(0%–100% 滑块,1% 一档;0% 在最下、100% 在最上,默认 50% 居中)、显示的标题层级、记住阅读位置与模糊/悬停/诊断开关;改过的字段可单独重置(改回默认值时标记自动消失)。卡片与面板实时同步,不需要刷新;偏好保存在 DSH 的设置里,跟随配置走
96
-
97
- ## 诊断
98
-
99
- 面板挂载时默认**不打印**任何日志。需要排查宿主能力时,在 **设置 → 插件 → 插件配置** 里展开「对话大纲」卡片、勾选「在控制台打印诊断日志」——打开后当下就会打印一行(无需刷新):
100
-
101
- ```
102
- [dsh-quick-toc] panel mounted · turnOutline=… · jumpLoader=… · lang=… · prefs=…
103
- ```
104
-
105
- `turnOutline` 缺失说明宿主没有该投影,`jumpLoader` 的分阶段措辞(`no-sessions-service` / `no-binding-api` / `no-binding-for-session` / `no-loadThrough` / `binding-threw`)可直接定位跳转桥断在哪一环。
106
-
107
- ## 开发
108
-
109
- - `lib/client.js` —— 全部 UI 逻辑(浏览器端)
110
- - `lib/index.js` —— 宿主半:注册 `dsh-quick-toc` 设置命名空间(schemastery schema,含取值范围校验),让偏好进入 DSH 的设置文档并提供插件配置卡片的入口
111
- - `cordis.patch.yml` —— loader patch(符合官方 bundle 规范)
112
- - 面板注册进会话级 `conversation.input.overlay` 槽以取得会话级 hook(`useChat`、`useSession`、`sessionId` 等),面板本体通过 `createPortal` 渲染到 `document.body` 成为固定浮层;对话数据来自 `props.useChat`(`ChatSnapshot.order` 与 `nodes`,节点形状:`kind: user/assistant-step`、`location.turn`、`data.blocks`)
113
- - 「未加载回合」能力依赖宿主的两样东西:`turnOutline` 投影(整段会话的回合索引,每条含 `turn/start` 的 seq)与会话跳转加载器(客户端 `sessions` 服务的 `binding(sessionId).session.loadThrough(seq)`,通过客户端 ctx 的 `ctx.get("sessions")` 免声明查找取得)。两者各自独立降级:没有投影时只列已加载回合,没有加载器时未加载条目只展示、不跳转,面板其余功能不受影响。
114
- - 失败回合的报错行读的是宿主的 `turn-error` 会话节点(宿主在 `turn/end` 的原因为 `error` 时发布,含 `message` 与可选的 `code`);宿主不提供该节点时只是不显示这一行,其余功能不受影响。
115
- - 界面文字来自 `lib/client.js` 顶部的一张字符串表(`DICTS`,中英各一份),语言设置决定用哪一份:`跟随宿主` 时优先问宿主的翻译函数(`ctx.locale.bind("dsh-quick-toc")`,注册的表就来自 `DICTS`),拿不到才回退到内置表。中英两份表的键必须对齐(仅 `time.beforeYesterday`(前天)是中文独有——英文对更早的时间直接用日期;`time.yesterday` 两语言都有,英文作 `yesterday`)。
116
- - 偏好存储分两层,由一个模块级 store 统一读出:宿主设置文档(`ctx.settingsScope.bind({ namespace: "dsh-quick-toc" })`,语言/停靠边/层级/缩放/把手位置/记住阅读位置/模糊/悬停/诊断九项,回环页面上是权威层)与 `localStorage`(镜像 + 顶边距/宽度/高度三项的正式存储——它们描述"这一块屏幕",按浏览器保存)。写入按字段路由:宿主字段走 `scope.mutate`(本地同步折叠、宿主应答后对账),其余写 `localStorage`;写入的值若正好是该字段的默认值,则改为把用户层里的这一项删掉(等同于"未自定义")。非回环页面 DSH 将设置标记为只读,此时这九项偏好退回本地镜像,行为与 0.5.x 一致;0.5.x 留下的本地值会在宿主层首次应答时导入一次(仅当用户层为空)。
117
- - 幕布与停靠面板**共用同一份列表 DOM**:幕布只是把面板本体搬进一个从标签条下沿落下的整宽容器(面板在幕布态换一套几何:相对定位、宽高 100%、不套用面板缩放),容器本身常驻挂载、收起时是一个 0×0 的直通盒,所以开关幕布不会重建列表、也不会丢掉滚动位置。跨会话检索走客户端 `ctx.get("sessions")` 的 `search(query, signal)`(走宿主的会话全文索引,一次最多 20 条);每个会话的阅读位置存在浏览器本地(`dsh-quick-toc.readPos.v1`,半小时内有效)。
118
- - 插件配置卡片注册进 `settings.plugin.item` 槽(`key` 为插件命名空间;该槽按宿主实际提供的命名空间派发,宿主半未加载时卡片自然不出现,面板不受影响);卡片与面板共享上面那个 store,因此两边实时互通。
119
- - 修改 `lib/client.js` 后刷新页面即可看到变化(客户端模块按内容哈希发版,DSH 的客户端 HMR 也会推送重载);改 `lib/index.js`(宿主半)需重启 DSH
120
-
121
- ## License
122
-
123
- MIT © 2026 LyaxZ
1
+ # dsh-quick-toc
2
+
3
+ > [English](README.en.md) | **中文**
4
+
5
+ **DeepSeek Harness(DSH)对话大纲插件**:把 AI 回复中的 Markdown 标题(H1–H6)提取成可导航的大纲——侧边面板与全屏幕布两种大纲视图,按对话回合分组、覆盖整段会话(含尚未加载进窗口的回合),支持标题/全文/跨会话三档检索、悬停预览、多种跳转(点标题、点组头、跳到本节末尾),阅读位置自动跟随并可记住。
6
+
7
+ ## 功能
8
+
9
+ - **按回合分组** —— 每条用户消息 + 其后续 AI 回复为一组,组头显示回合时间与首行预览,点击跳到该回合模型回答的开头
10
+ - **覆盖整段会话** —— 未加载进对话窗口的回合也在列表里(带「未加载」标记与预览);点击即加载该回合并跳过去
11
+ - **失败回合的报错** —— 请求超时 / 上游报错这类没有回复的回合显示 `请求失败` 与报错原文,点击跳到对话中的报错位置
12
+ - **全屏「幕布」** —— 对话顶边标签条里的液滴把手(或面板标题栏的「幕布」圆钮)把大纲铺成整幅宽度落下:没有遮罩、不压暗对话,字号与行距都放大,每行三段(层级徽标 / 标题 / 该节正文开头),搜索栏从右侧推进来;点条目、Esc 或 ✕ 收起。它与停靠面板共用同一份列表,滚动位置、搜索、键盘光标全部延续
13
+ - **跳到本节末尾** —— 悬停标题行 / 组头 / 结果行,右端渐显一枚圆钮,点击跳到该节内容结束处;组头这颗跳到整轮对话的末尾
14
+ - **跨会话检索** —— 搜索范围第三档「会话」用宿主的全文索引搜其他会话的消息正文,点结果切过去并继续查找;已归档 / 子会话 / 不在列表里的会话会被略过并计数
15
+ - **只看提问** —— 一键把大纲折成「每轮只留时间与你的提问首行」
16
+ - **记住阅读位置** —— 重新打开会话回到上次读到的那一轮(半小时内有效;可在设置里关掉)
17
+ - **键盘导航** —— ↑/↓ 移动光标、Enter 跳转、Home / End 到首尾、Esc 收起;光标位置有屏幕阅读器播报
18
+ - **中英双语** —— 界面语言可设为跟随宿主、中文或 English;中文时间戳用 `昨天` / `前天`,英文昨天用 `yesterday`、更早用 `YY-MM-DD HH:MM` 日期
19
+ - **插件配置卡片** —— 设置 → 插件 → 插件配置里的「对话大纲」卡片:语言、默认停靠边缘、显示的标题层级、收起把手的位置、记住阅读位置、模糊搜索、悬停预览与诊断开关;改过的字段标「已自定义」并可单独重置(改回默认值时标记自动消失),卡片与面板实时同步
20
+ - **回合时间带日期** —— 昨天 `昨天 15:04`、前天 `前天 15:04`、更早 `25-09-11 15:04`,跨天的会话里时间不再重影
21
+ - **标题行副标题** —— 每行标题下方显示该节正文的第一句,同名标题一眼可辨
22
+ - **悬停预览** —— 悬停标题行显示该节开头、回合时间与层级路径
23
+ - **搜索** —— 标题 / 全文两种范围,结果列表点击定位并高亮,`n/N` 回车逐处跳,Esc 关闭
24
+ - **搜索容错** —— 大小写、全角半角、连续空白自动视为同一匹配;「模糊」开关可放宽到关键字中间夹字
25
+ - **对话内高亮** —— 命中的关键字在对话中高亮,当前命中单独标亮
26
+ - **组头吸顶** —— 滚动大纲时,当前回合的组头固定在面板顶部
27
+ - **层级筛选** —— 标题栏的圆形层级按钮弹出 H1–H6 开关,任意组合显示
28
+ - **自动跟随** —— 滚动对话时正在阅读的回合自动点亮(封闭蓝框),大纲自动跟随
29
+ - **跳转** —— 点击标题跳到对话中该标题的位置,滚动动画交给浏览器自己的平滑滚动(按真实时间推进,60Hz 与 240Hz 屏幕上时长一致);飞行中目标被宿主翻页挪动时会重新瞄准、动画被宿主滚动打断时会重新发起,跨页远跳不会半途停下;若某个浏览器把平滑滚动直接执行成瞬移,插件改为自己逐帧绘制。面板与对话双向定位
30
+ - **回到底部** —— 往上翻远之后,列表右下角的浮动按钮一步回到最新一条(不在底部时出现、到底后消失)
31
+ - **回到我离开的位置** —— 关闭面板或幕布时记住列表顶部那一行(两者各记各的、互不覆盖),下次打开时列表右上角出现向上的浮动按钮,一步回到那一行的**原位置**;用过一次或自己滚回该处就渐隐并忘掉,直到下次关闭再记。同一规则下,打开面板或幕布时**正在阅读的那一行**会落在列表顶部(读的就在最新几轮时列表已经到底,那一行自然停在底部)
32
+ - **可停靠、可缩放** —— 拖顶部横条移动,◀ / ▶ 切换左右停靠,拖边缘调宽高(尺寸没有上限,最窄可到 120px),收起后成为边缘把手(停在什么高度由设置里的「把手位置」决定);位置与尺寸按浏览器记住(顶边距、宽度、高度只通过拖拽调整)
33
+ - **面板缩放** —— 设置里的滑块把**内容**(文字、图标、按钮及其间距)在 50%–200% 之间按 5% 一档缩放,**面板本身的尺寸不变**;拖动滑块时只移动读数,松手才写入设置。面板变窄时顶栏四颗按钮保持一行:先挤掉中间的空白,挤满后整行可横向滚动(在顶栏上滚滚轮即可),此时顶部的灰色拖拽横条渐隐消失
34
+ - **幕布缩放** —— 另一个滑块,把**幕布内容**在 50%–200% 之间按 5% 一档缩放,幕布本身的尺寸不变;与面板缩放各自独立、互不影响。幕布内容的默认大小比此前小一档(滑块的 100% 即此前的 90%),且只缩放幕布里的大纲,不改变对话本体
35
+ - **分页** —— 默认显示最近的若干组,向上滚动既展开索引也加载更早的对话
36
+ - **边界提示** —— 滚到最早或最新再继续滚动时,面板底部短暂提示
37
+ - **Markdown 感知** —— 标题中的行内标记会被剥离;围栏代码块里的 `#` 行不算标题
38
+ - **主题适配** —— 深色 / 浅色主题自适应;仅对话视图显示,切到其他视图渐隐
39
+ - 对话中没有标题也没有可识别时间时面板自动隐藏
40
+
41
+ ## 兼容性
42
+
43
+ | 插件版本 | 支持的 DSH 版本 |
44
+ | --- | --- |
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
+ | 0.6.x(0.6.3) | 0.1.5-rc.1、0.1.5-rc.2 |
47
+ | 0.5.x(0.5.1) | 0.1.5-rc.1、0.1.5-rc.2 |
48
+ | 0.4.x(0.4.1) | 0.1.5-rc.1、0.1.5-rc.2 |
49
+ | 0.3.x(0.3.3) | 0.1.5-rc.1 |
50
+ | 0.2.x(0.2.2) | = 0.1.2-rc.1 |
51
+
52
+ 每个大版本只列该系列最新的一个补丁版本(新功能引入的缺陷都在其后的补丁里修掉了,所以同一个大版本内直接用最新补丁即可;旧补丁仍可继续用,插件不破坏既有接口)。
53
+
54
+ `engines.dsh` 声明为 **`>=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0`**,peer 范围同形(预发布版本必须显式留分支:node-semver 只在一个范围的某个比较符与该版本落在同一个 `major.minor.patch` 元组、且自身带预发布标签时才放行它,所以写成 `>=0.1.5-rc.1` 是匹配不到 `0.1.7-alpha.1` 的)。0.7.3 在 **0.1.5-rc.3** 与 **0.1.7-alpha.2** 上实测通过(面板与幕布、点行跳转与翻历史、标题/全文/跨会话三档检索、跨会话命中切换会话、设置卡片与宿主设置文档的读写都逐项跑过),这两版不需要任何代码改动——离线核对:rc.3 相对 rc.2 只有各包 `package.json` 的版本号差异(外加侧边栏两个文件),alpha.2 的改动集中在会话控制器与聊天渲染,但插件依赖的接口与节点形状逐项核对后仍在(`turnOutline`、`turn-error`、`useChat`、`data-chat-anchor-key`、`sessions.search`、`loadThrough`、`settings.*` 与两张卡片槽位都在,`data-conversation-scroll` 在 alpha.2 里新增的两处命中是样式表文本、不是第二个滚动容器)。0.7.2 在 **0.1.5-rc.2** 上实测通过(幕布缩放逐项确认:三档因子下幕布盒子的宽高与位置不变、只有内容按比例缩放,50%–200% 两端、松手写入、以及默认值渲染都比对过;面板与幕布两套缩放各自独立);0.7.1 在 **0.1.5-rc.2** 上实测通过(「回到我离开的位置」浮动按钮、面板与幕布分别记录的离开位置、打开时落在正在阅读的那一行、以及在底部关闭再打开不出按钮都逐项确认);0.7.0 在同一版本上实测通过(全屏幕布与液滴把手、跳到本节末尾、跨会话检索、键盘导航、只看提问、记住阅读位置都在真实浏览器里逐项跑过;面板缩放、尺寸的落盘与读回、收起把手的位置等既有能力此前已实测);**0.1.5-rc.1** 上实测了插件加载与客户端模块下发(宿主启动无报错、`--dump-config` 里有 quick-toc 条目、客户端组合包 URL 里列出 `dsh-quick-toc/client.js`,取回的下发内容包含本版新增的幕布把手、跳到本节末尾与阅读位置等标记),它依赖的宿主接口——`turnOutline` 投影、会话级槽位的 `useProjection`、客户端 `sessions` 服务与 `loadThrough` 跳转加载器(0.5.0 起)、失败回合所用的 `turn-error` 节点(0.5.1 起)、以及 0.6.0 新增的宿主 `settings` 服务(`installSection`)、客户端 `settingsScope` 与 `settings.plugin.item` 配置卡片槽位、以及 0.7.0 跨会话检索所用的客户端 `sessions.search`——在 0.1.5-rc.1 与 0.1.5-rc.2 的安装包中逐个核对存在(这些宿主包的代码在两版之间逐字节相同,`dsh-client-ui-chat` 仅差一条与本插件无关的 CSS 声明;`search` 所在的 `dsh-api-session-controller` 两版逐字节相同,含「一次最多 20 条、片段上限 240 字符」的同一套上限),因此两版都声明兼容。更早的 DSH 版本未经验证,不作声明;在更早的 DSH 上可安装的最新插件版本是 **0.3.2**。**0.1.7-alpha.1 上也实测通过**(隔离的 `DSH_HOME` 里跑真实实例):宿主启动无报错、面板与幕布、点行跳转与翻历史、标题/全文/跨会话三档检索、跨会话命中切换会话,都在真实浏览器里逐项跑过,设置文档的读写也逐项确认。为此 0.7.2 同时支持两条宿主线——0.1.5-rc.x 上是宿主的 `settings.installSection` + 客户端 `settingsScope` + `settings.plugin.item` 卡片槽位,0.1.7-alpha.x 上换成 `settings.configure` + 客户端 `configForms` + `plugins.bundle.config` 卡片槽位、并用 `uiWorkspace.openSession` 切换会话;插件在运行时按服务是否存在二选一,且**不再把任一设置服务写进 `inject`**(声明一个宿主没有的服务会让整个包停在 pending,0.1.7-alpha.1 上这曾让整页 Web 起不来)。安装或更新时,市场(dshmarket)读取 npm 上 `latest` 版本的 `engines.dsh`(或 `dsh.engines.dsh`)与本机存在的 `@deepseek-ai/dsh-*` peer 范围,在卡片上显示该插件声明的宿主要求,并提供「只显示与当前宿主确认匹配的条目」这一可选过滤;`dsh.compatibility.dshReleases` 是本仓库自己的记录,DSH 与市场都不读取。
55
+
56
+ ## 安装
57
+
58
+ 通过 DSH CLI 安装:
59
+
60
+ ```
61
+ dsh plugin --profile web add dsh-quick-toc
62
+ ```
63
+
64
+ 也可以从 GitHub 安装:
65
+
66
+ ```
67
+ dsh plugin --profile web add github:LyaxZ/dsh-quick-toc
68
+ ```
69
+
70
+ 或以本地目录安装:
71
+
72
+ ```
73
+ dsh plugin --profile web add <插件目录路径>
74
+ ```
75
+
76
+ 安装后重启 DSH 并打开 Web UI。面板默认收起:点对话区边缘的把手展开面板,或点对话顶边标签条里的液滴把手直接把大纲铺成整幅宽度落下。
77
+
78
+ ## 使用
79
+
80
+ - **跳转**:点击大纲标题或组头跳到对应位置;带「未加载」标记的条目会先把该回合加载进来再跳;失败回合点报错行跳到对话中的报错位置
81
+ - **搜索**:放大镜打开搜索框,输入后点结果定位并高亮,回车逐处跳,Esc 关闭
82
+ - **搜索容错**:全角/半角、大小写、空格差异会自动匹配;需要更宽松时点搜索框右侧的「模糊」开关
83
+ - **层级筛选**:点标题栏的圆形层级按钮弹出 H1–H6 开关,选择显示的层级
84
+ - **移动与停靠**:拖顶部横条移动,◀ / ▶ 切换左右停靠
85
+ - **调整大小**:拖右边缘、下边缘或右下角(没有上限,最窄到 120px;面板过窄时顶栏可横向滚动)
86
+ - **加载更早**:在大纲中向上滚动(既展开索引,也加载更早的对话)
87
+ - **幕布**:点对话顶边标签条里的液滴把手(或面板标题栏的「幕布」圆钮)展开全屏大纲;点任意条目、按 Esc 或点右上角的 ✕ 收起
88
+ - **跳到本节末尾**:悬停标题行 / 组头 / 结果行,点右端出现的圆钮
89
+ - **跨会话检索**:搜索框右侧的范围按钮点两下切到「会话」,用宿主的全文索引搜其他会话;点结果切到那个会话并继续查找
90
+ - **键盘**:面板打开后 ↑/↓ 移动光标、Enter 跳转、Home / End 到首尾、Esc 收起;搜索框里 ↑/↓ 在命中之间切换
91
+ - **只看提问**:标题栏的语音气泡按钮把大纲折成每轮的时间 + 你的提问首行
92
+ - **回到底部**:翻远了之后点列表右下角的浮动按钮回到最新一条
93
+ - **回到我离开的位置**:关掉面板或幕布时它记住你当时看的那一行;下次打开时右上角的浮动按钮一步带你回去
94
+ - **阅读位置**:滚动对话,正在阅读的回合会以蓝框标出;大纲会自动跟随
95
+ - **设置**:在 **设置 → 插件 → 插件配置** 里展开「对话大纲」卡片,可改语言、默认停靠边缘、面板缩放(50%–200% 滑块,5% 一档)、收起把手的位置(0%–100% 滑块,1% 一档;0% 在最下、100% 在最上,默认 50% 居中)、显示的标题层级、记住阅读位置与模糊/悬停/诊断开关;改过的字段可单独重置(改回默认值时标记自动消失)。卡片与面板实时同步,不需要刷新;偏好保存在 DSH 的设置里,跟随配置走
96
+
97
+ ## 诊断
98
+
99
+ 面板挂载时默认**不打印**任何日志。需要排查宿主能力时,在 **设置 → 插件 → 插件配置** 里展开「对话大纲」卡片、勾选「在控制台打印诊断日志」——打开后当下就会打印一行(无需刷新):
100
+
101
+ ```
102
+ [dsh-quick-toc] panel mounted · turnOutline=… · jumpLoader=… · lang=… · prefs=…
103
+ ```
104
+
105
+ `turnOutline` 缺失说明宿主没有该投影,`jumpLoader` 的分阶段措辞(`no-sessions-service` / `no-binding-api` / `no-binding-for-session` / `no-loadThrough` / `binding-threw`)可直接定位跳转桥断在哪一环。
106
+
107
+ ## 开发
108
+
109
+ - `lib/client.js` —— 全部 UI 逻辑(浏览器端)
110
+ - `lib/index.js` —— 宿主半:注册 `dsh-quick-toc` 设置命名空间(schemastery schema,含取值范围校验),让偏好进入 DSH 的设置文档并提供插件配置卡片的入口
111
+ - `cordis.patch.yml` —— loader patch(符合官方 bundle 规范)
112
+ - 面板注册进会话级 `conversation.input.overlay` 槽以取得会话级 hook(`useChat`、`useSession`、`sessionId` 等),面板本体通过 `createPortal` 渲染到 `document.body` 成为固定浮层;对话数据来自 `props.useChat`(`ChatSnapshot.order` 与 `nodes`,节点形状:`kind: user/assistant-step`、`location.turn`、`data.blocks`)
113
+ - 「未加载回合」能力依赖宿主的两样东西:`turnOutline` 投影(整段会话的回合索引,每条含 `turn/start` 的 seq)与会话跳转加载器(客户端 `sessions` 服务的 `binding(sessionId).session.loadThrough(seq)`,通过客户端 ctx 的 `ctx.get("sessions")` 免声明查找取得)。两者各自独立降级:没有投影时只列已加载回合,没有加载器时未加载条目只展示、不跳转,面板其余功能不受影响。
114
+ - 失败回合的报错行读的是宿主的 `turn-error` 会话节点(宿主在 `turn/end` 的原因为 `error` 时发布,含 `message` 与可选的 `code`);宿主不提供该节点时只是不显示这一行,其余功能不受影响。
115
+ - 界面文字来自 `lib/client.js` 顶部的一张字符串表(`DICTS`,中英各一份),语言设置决定用哪一份:`跟随宿主` 时优先问宿主的翻译函数(`ctx.locale.bind("dsh-quick-toc")`,注册的表就来自 `DICTS`),拿不到才回退到内置表。中英两份表的键必须对齐(仅 `time.beforeYesterday`(前天)是中文独有——英文对更早的时间直接用日期;`time.yesterday` 两语言都有,英文作 `yesterday`)。
116
+ - 偏好存储分两层,由一个模块级 store 统一读出:宿主设置文档(语言/停靠边/层级/缩放/把手位置/记住阅读位置/模糊/悬停/诊断九项,回环页面上是权威层)与 `localStorage`(镜像 + 顶边距/宽度/高度三项的正式存储——它们描述"这一块屏幕",按浏览器保存)。**宿主那一层按运行中的宿主二选一**:0.1.5-rc.x 上是 `ctx.settingsScope.bind({ namespace: "dsh-quick-toc" })`(按命名空间寻址),0.1.7-alpha.x 上改成 `ctx.configForms.get(<profile entry id>)`(按 profile 条目 id 寻址,客户端按宿主实际服务的 schema 认出自己那一条);两者暴露的方法与快照字段相同,所以 store 只有一套。写入按字段路由:宿主字段走 `scope.mutate`(本地同步折叠、宿主应答后对账),其余写 `localStorage`;写入的值若正好是该字段的默认值,则改为把用户层里的这一项删掉(等同于"未自定义")。非回环页面 DSH 将设置标记为只读,此时这九项偏好退回本地镜像,行为与 0.5.x 一致;0.5.x 留下的本地值会在宿主层首次应答时导入一次(仅当用户层为空)。宿主应答之后宿主层就是权威层,因此 `localStorage['dsh-quick-toc.debug']` 这类本地键在回环页面上不再生效——诊断开关请用卡片里那一条。
117
+ - 幕布与停靠面板**共用同一份列表 DOM**:幕布只是把面板本体搬进一个从标签条下沿落下的整宽容器(面板在幕布态换一套几何:相对定位、宽高 100%、不套用面板缩放),容器本身常驻挂载、收起时是一个 0×0 的直通盒,所以开关幕布不会重建列表、也不会丢掉滚动位置。跨会话检索走客户端 `ctx.get("sessions")` 的 `search(query, signal)`(走宿主的会话全文索引,一次最多 20 条);每个会话的阅读位置存在浏览器本地(`dsh-quick-toc.readPos.v1`,半小时内有效)。
118
+ - 插件配置卡片也按宿主二选一注册:0.1.5-rc.x 上挂 `settings.plugin.item` 槽(`key` 为插件命名空间,该槽按宿主实际提供的命名空间派发,宿主半未加载时卡片自然不出现,面板不受影响);0.1.7-alpha.x 上改挂 `plugins.bundle.config` 槽(`key` 为 npm 包名,卡片出现在侧边栏「插件」页该 bundle 的页面上)。两条注册都只在设置作用域绑定成功后才做,卡片与面板共享上面那个 store,因此两边实时互通。
119
+ - 修改 `lib/client.js` 后刷新页面即可看到变化(客户端模块按内容哈希发版,DSH 的客户端 HMR 也会推送重载);改 `lib/index.js`(宿主半)需重启 DSH
120
+
121
+ ## License
122
+
123
+ MIT © 2026 LyaxZ