@khorsheed/dsh-message-timeline 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,11 +1,22 @@
1
1
  # 变更记录
2
2
 
3
+ ## 0.2.7(2026-10-26)
4
+
5
+ - **README 重写**:改为面向用户的简短说明。无功能变更。
6
+
7
+ ## 0.2.6(2026-10-24)
8
+
9
+ - 窄布局下退化为刻度条而不是整个隐藏;测量轨道高度时跳过隐藏的输入卡片;面板高度贴合行数,不再撑满整条轨道。
10
+
3
11
  ## 0.2.5(2026-10-22)
4
12
 
5
13
  - **整会话一次列出,首条消息即刻可达**:列表改读宿主 `turnOutline` 投影(每轮一条:轮次号、`turn/start` 序号、50 字首条预览),与已加载窗口合并——不再靠逐页翻历史才能看到更早消息。未加载轮次的行点击后经会话 jump loader(`ISession.loadThrough`)回翻历史再落位;行物化即落位,翻页停滞或轮次不可达时落到其后最近可见行。
6
14
  - **静止态对比度调整**:所有行统一压淡灰刻度(不再区分已加载/未加载),只有当前阅读位置为蓝色加长刻度加粗行文字(无选中时默认最新一条);回翻中的行以刻度脉动提示。
7
15
  - **无文本预览的消息保留展示**(标记「无文本预览」),不再过滤;**修复自动轮次点击无反应**:重启恢复、定时任务等轮次在聊天视图渲染为 turn-trigger 卡片而非 user 行,原落位只找 user 行导致永远落不到。落位改为官方同款 `user ?? loaded[0]`:优先该轮首个 user 行,没有则落该轮第一个可见节点(触发卡片、图片气泡均可达)。
8
16
  - **自动轮次成为一等行,选中不再跑偏**:turn-trigger 纳入时间轴行 kind——已加载时直接显示触发正文,未加载时标记「第 N 轮」;阅读位置锚点也认触发卡片,跳转后选中保持在该轮,不再跳到下一条用户消息。图片/附件消息分别标记「图片消息」「附件消息」。
17
+ - **窄布局不再隐藏时间轴**:右侧栏打开或窗口窄导致左沟槽不足最小宽度时,不再整条隐藏,而是退为左缘刻度条(24px,落在消息流留白内),悬停/聚焦时展开带底色的浮层;宽布局仍按沟槽封顶宽度。修复 Room 会话右侧栏展开时时间轴消失的问题。
18
+ - **修复 Room 会话时间轴高度为零**:Room 输入框以 overlay 接管输入区时,官方输入栏仍以 `display:none` 留在 DOM 里,测高误取它的零尺寸卡片,面板被压成几像素(只露一截蓝色刻度、打开时一闪而过)。现在跳过未布局的卡片,退到输入区顶边。
19
+ - **悬停浮层高度随消息条数**:窄布局悬停展开时,底色不再铺满从标签条到输入框的整段高度;面板高度随行数、在原位置垂直居中,条数多时才铺满并滚动。
9
20
  - 装配未挂载 turn-outline 单元时退回原窗口分页行为(打开预取 + 面板滚顶翻页),`initialPages` 只作用于该兜底路径。
10
21
  - 注入面变化:`jumpTo` 现在返回是否命中(供落位重试),新增 `loadThrough`。
11
22
  - **适配宿主 0.2.1 线**:`@deepseek-ai/dsh-*` peer 区间追加 `^0.2.1-rc.1`(`^0.2.0-rc.1` 在 npm/pnpm 严格解析下不覆盖 0.2.1 预发布三元组);宿主 0.2.1 移除 invariants 体系,随包 invariant 伴生退役、兼容判定改由 package.json `engines.dsh` 声明;`dsh.compat.verifiedHost` 前移至 `0.2.1-rc.1`(全量构建+测试在该树通过,无其它表面变化)。
package/README.en.md CHANGED
@@ -1,91 +1,46 @@
1
- # dsh-message-timeline
1
+ # @khorsheed/dsh-message-timeline
2
2
 
3
3
  English | [中文](README.md)
4
4
 
5
- Every message you sent in a long conversation — visible at a glance, one click to jump back.
5
+ Every message you sent in a long conversation, visible at a glance and one click away.
6
6
 
7
- Once a session gets long, finding that requirement you mentioned three messages ago means scrolling up forever. This plugin parks a timeline on the chat's left edge: one row per message you sent, with a one-line preview. At rest it's just a strip of dimmed ticks that stays out of the way; hover to expand it, and clicking a row scrolls the transcript straight to that message. It only reads the session and never sends anything — the model doesn't notice it at all.
7
+ Once a session gets long, finding that request you made earlier means scrolling all the way up. With Message Timeline, a thin rail appears on the left of the conversation: one tick per message you sent, faded so it stays out of the way. Hover it and it opens into one-line previews; click a line and the conversation scrolls right to it.
8
8
 
9
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/message-timeline1.png" width="640" alt="the expanded message timeline on the chat's left edge: one row per user message, click a row to jump to it">
9
+ <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/message-timeline1.png" width="560" alt="the message timeline expanded on the conversation's left edge: one row per user message; click a row to jump to it">
10
10
 
11
- ## Features
11
+ ## What it does
12
12
 
13
- - **One row per user message, the whole session listed from the first one** — tick plus ellipsized one-line preview; the host `turnOutline` projection names every started turn directly, no paging required. Clicking a not-yet-loaded older message pages history back to that turn before landing (its tick pulses while paging). Steering messages count too (configurable off), and message-tools edited/restored bubbles keep a row. Withdrawn originals are not listed (their transcript row is hidden, so a click could never reach them).
14
- - **Textless rows stay listed and jumpable** — automated turns (restart recovery, scheduled jobs) show their trigger card's text once loaded, and fall back to a "Turn N" label while unloaded; image/attachment-only inputs are labelled "Image message" / "Attachment message". After jumping to an automated turn, the selection stays on that turn instead of moving to the next user message.
15
- - **Ambient rest state** — only dimmed grey ticks show until you hover the strip or focus the list.
16
- - **Reading position tracking** — the current position's tick stays blue and lengthened with bold text (defaulting to the latest message while nothing is selected); every other row rests grey. A long answer anchors the lit tick to the user message it replies to.
17
- - **Click to jump** — a loaded row scrolls the transcript straight to it; an outline row goes through the session's jump loader and lands on the freshly loaded message.
18
- - **Long-history friendly** — a short list centers vertically; a long one scrolls, its bottom-most row flush with the chat input box; goal/todo dock cards never push the timeline up. Assemblies without the turn-outline unit fall back to window paging: a few pages prefetch on open, and older history loads when the panel is scrolled to its top.
19
- - **Never covers the message flow** — the panel's width is capped by the scrollport's left gutter; a gutter too small for the minimum width hides it rather than overlapping the transcript.
13
+ - **Everything from the first message**: every message you sent in the session is listed with a one-line preview; click an early one that isn't loaded yet and it pages history back first, then lands there.
14
+ - **One click to jump back**: click a line and the conversation scrolls to that message.
15
+ - **Know where you are**: the tick for your current spot is blue, longer and bold, the rest stay gray; inside a very long reply, it marks the message that reply answers.
16
+ - **Messages without text still get a line**: image messages, attachment messages, and automatic turns (restart recovery, scheduled tasks and so on) are labelled and clickable too.
17
+ - **Quiet until you need it**: at rest it is a row of faint gray ticks; text appears only on hover or keyboard focus.
18
+ - **Works in narrow windows**: with the right sidebar open or the window narrow, it shrinks to a rail on the left and floats out on hover.
20
19
 
21
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/message-timeline2.png" width="640" alt="the timeline at rest: a strip of dimmed ticks that stays out of the way, expanding on hover">
20
+ <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/message-timeline2.png" width="560" alt="the timeline at rest: a faint strip of ticks that stays out of the way and expands on hover">
22
21
 
23
22
  ## Install
24
23
 
25
- ```sh
26
- dsh plugin --profile web add @khorsheed/dsh-message-timeline
27
- ```
28
-
29
- Then restart the web instance. Uninstall:
24
+ In DSH, open Plugins → Add plugin, enter the package name below, and click install:
30
25
 
31
- ```sh
32
- dsh plugin --profile web remove @khorsheed/dsh-message-timeline
26
+ ```
27
+ @khorsheed/dsh-basic
33
28
  ```
34
29
 
35
- ## Config
36
-
37
- | Field | Default | Meaning |
38
- | --- | --- | --- |
39
- | `enabled` | `true` | Master switch; false hides the panel entirely. |
40
- | `includeSteering` | `true` | Count steering messages (user text admitted mid-turn) as rows. |
41
- | `panelWidth` | `360` | Panel width in px (clamped 120–640); shrinks to fit a narrow column, hides entirely when the gutter is too small. |
42
- | `initialPages` | `5` | Window-paging fallback only: history pages (50 events each) prefetched on open (clamped 1–20). When the host serves the turnOutline projection the list starts whole and this does nothing. |
43
-
44
- ## Compatibility
45
-
46
- - npm release line (`@deepseek-ai/dsh@0.1.2-rc.1`): ✅ full — baseline moved to the 0.1.2-rc.1 API surface (single-arm 0.1.2 API consumption; the 0.1.1-rc.2 runtime arm is retired), full build+test green; minHost moves up to 0.1.2-rc.1 — older hosts stay on the previous release line.
47
- - source line (deepseek-harness master): ✅ (verifiedHost: 0.2.1-rc.1) — host-021 wave: full build+test green against the 0.2.1-rc.1 tree, no other surface changes.
48
- - Degraded item: the whole-session list relies on the host `turnOutline` projection (`@deepseek-ai/dsh-session-turn-outline`, mounted by the official web bundle since 0.1.2-alpha.3 — below the minHost floor). An assembly without the unit falls back to window paging: older history loads page by page, fully functional, but the first message appears only once paged in.
49
-
50
- **Version line mapping**: 0.2.0 and up support host `0.1.2-rc.1` and later; hosts on `0.1.0-rc.6` ~ `0.1.1-rc.2` stay on the 0.1.x release line (last release `0.1.0`).
51
-
52
- ## Known Limitations
53
-
54
- - **DOM probe coupling** — the panel targets official row attributes and the `[data-conversation-scroll]` scrollport; if the official DOM changes, it hides itself (one `console.warn`) until the probe is updated.
55
- - **Best-effort width fallback** — while the message-flow probe is unanswered, the panel renders at a capped fraction of the scrollport instead of hiding, so it may overlap the flow until the probe recovers; with a healthy probe the width never crosses the flow's left edge.
56
- - **Single rendered session** — rows and jumps address only the currently rendered conversation.
57
- - **The outline is turn-grained** — the projection keeps only each turn's first-prompt preview: steering rows appear once their turn pages into the window; a turn whose first message was withdrawn stays listed per the outline (matching the official turn rail), and confirming it lands on the turn's nearest visible row.
58
- - **No full index view yet** — a searchable message-index tab is planned.
59
-
60
- ## How it works
61
-
62
- <details>
63
- <summary>Internals (click to expand)</summary>
64
-
65
- The plugin is purely additive and modifies no official code.
66
-
67
- - `src/client/index.ts` — plugin body (`apply`/`inject`)
68
- - `src/client/rail-tracker.ts` — the only DOM the plugin touches: read-only probes, the reading-position resolution, plus the jump's scroll write
69
- - `src/client/TimelineRail.tsx` — the panel component
70
- - `src/client/outline.ts` — the `turnOutline` wire narrow (absent or damaged values both read as "capability absent")
71
- - `src/client/rows.ts` — the merge of loaded-window rows with outline rows (single interleaving pass, transcript-ordered)
72
- - `src/client/timeline-kinds.ts` — the single source of truth for which node kinds are rows and which carry a hidden span
73
- - `src/client/hidden-spans.ts` — folds the message-tools withdraw/edit spans so covered originals are dropped as dead rows
74
- - `src/client/preview.ts` — message content to one-line preview text
75
- - `src/index.ts` — empty host `apply`, anchors the plugin into the host Loader
76
-
77
- **Mount & data** — one entry in the official `conversation.session.header.utilities` slot anchors the plugin into the session scope; the panel renders through a body portal with fixed geometry measured from the official scrollport. Rows merge two sources: the host `turnOutline` projection (the `useProjection` standard seat — one entry per started turn: its number, its `turn/start` seq, and a 50-char first-prompt preview) lists every turn of the session, and the loaded window (`useChat`'s `order` / `nodes`) overlays richer previews, steering rows, and message-tools bubbles for the turns it covers (`message-tools-edited`/`message-tools-restored` bubbles the host order does not always surface are appended from the node store). A bubble for a withdrawn original is dropped (its row is hidden by the DOM hider, so a click could never reach it), and appended bubbles are sorted by their anchor seq so the newest message is always last. Without the projection, the window rows alone carry the list.
78
-
79
- **Jump & degradation** — clicking a loaded row finds the transcript row by the official `data-chat-anchor-key` attribute and writes `scrollTop`; the official ChatView treats that as a normal reader move (bottom-follow and scroll memory keep working). Clicking an outline row goes through the session's jump loader (`ISession.loadThrough`, since 0.1.2-alpha.3): history pages back through the turn's `turn/start` seq, and the rail lands the moment the row materializes; when paging stalls or the turn is unreachable (e.g. its first message was withdrawn), it lands on the nearest visible row after the turn instead. The probed attributes are official render output, not a declared API: when they change, the panel hides itself with one `console.warn`; nothing throws and the boot never fails. The panel's width is capped by the scrollport's left gutter so it never covers the message flow; while the flow probe is unanswered (an official structure change) the width degrades to a capped fraction of the scrollport as a best-effort fallback — the panel may then overlap the flow until the probe recovers.
30
+ Message Timeline comes with the Basic Essentials suite. Once installed, open any session with a few turns and you'll see it on the left. It only reads the conversation and never sends anything, so the agent doesn't know it exists.
80
31
 
81
- **Model experience: none.** The panel reads the session snapshot and scrolls the transcript; it never sends prompts, appends session events, or enters the session log. KV cache effect: none. The `enabled` config turns the plugin off entirely; removing it from cordis.yml removes every surface it adds.
32
+ ## Known limits
82
33
 
83
- </details>
34
+ - It recognizes the official page layout; if a DSH update changes it beyond recognition, the timeline hides itself until the plugin is updated.
35
+ - It only works on the session that is currently open.
36
+ - A message you sent mid-turn shows up in the list once that turn is loaded.
84
37
 
85
- ## Development
38
+ ## Compatibility
86
39
 
87
- Part of the [dsh-plugins](https://github.com/Khorsheed/dsh-plugins) monorepo (`packages/message-timeline`). Issues and contributions welcome there.
40
+ Needs DSH 0.1.2-rc.1 or later; verified on 0.2.1-rc.1.
88
41
 
89
- ## Changelog
42
+ ## For developers
90
43
 
91
- See [CHANGELOG.md](CHANGELOG.md).
44
+ - Where rows come from, how jumping works, the config fields (`enabled`, `includeSteering`, `panelWidth`, `initialPages`), page probes and degradation, and the internals are in the [full reference](https://github.com/Khorsheed/dsh-plugins/blob/main/packages/message-timeline/docs/reference.en.md).
45
+ - Entry points: `src/client/index.ts` (the plugin body), `src/client/rail-tracker.ts` (the only place that touches the page structure). Standalone install: `dsh plugin --profile web add @khorsheed/dsh-message-timeline`.
46
+ - Part of [dsh-plugins](https://github.com/Khorsheed/dsh-plugins); open issues and contributions there. Changes: [CHANGELOG.md](CHANGELOG.md).
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # last confirmed-consistent state. Both languages carry equal authority; after
3
3
  # editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/message-timeline/README.en.md
5
- packages/message-timeline/README.en.md: bfd6e90fef2a0358c2c0d2156d2e2c07b7507636
6
- packages/message-timeline/README.md: 6baf27211ccf3573bc364666d37de772e6e80750
5
+ packages/message-timeline/README.en.md: 57f086cd2a8d2e8866ad4363ea93f144fbdded20
6
+ packages/message-timeline/README.md: b4d28b2b38e3b4dc75ab545801aca79b6f3e4f4a
package/README.md CHANGED
@@ -4,88 +4,43 @@
4
4
 
5
5
  长对话里你说过的每句话,一眼看到、一点就跳回去。
6
6
 
7
- 会话一长,想找回三条消息前提过的那个要求,就只能一路往上滚。这个插件在对话左缘放了一条时间轴:你说过的每句话占一行,带单行预览;平时它收成一排压淡的刻度,不占视线,鼠标一悬停才展开,点哪一行就把会话滚到哪一句。它只读会话、不发任何消息,模型完全无感。
7
+ 会话一长,想找回前面提过的那个要求,只能一路往上滚。装上消息时间轴,对话左边多一条细细的刻度:你说过的每句话占一格,平时压得很淡、不挡视线;鼠标移上去就展开成一行行预览,点哪一行,会话就滚到哪一句。
8
8
 
9
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/message-timeline1.png" width="640" alt="对话左缘展开的消息时间轴:每行一条用户消息,点行即跳转到对应消息">
9
+ <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/message-timeline1.png" width="560" alt="对话左缘展开的消息时间轴:每行一条用户消息,点行即跳转到对应消息">
10
10
 
11
- ## 特性
11
+ ## 能做什么
12
12
 
13
- - **每行一条用户消息,从第一句起全量列出**——竖刻度加单行省略预览;宿主 `turnOutline` 投影直接给出整段会话的每一轮(含第一条消息),无需翻页。点击尚未加载的更早消息会先把历史回翻到那一轮再落位(回翻期间该行的刻度脉动提示)。steering 消息也计入(可配置关闭),message-tools 的编辑/恢复气泡也保留一行。被撤回的原消息不展示(其会话行被隐藏,点击也到不了)。
14
- - **无文本预览的消息也有行、也能跳**——自动轮次(重启恢复、定时任务等)已加载时直接显示触发卡片正文,未加载时按「第 N 轮」标记;图片/附件消息分别标记为「图片消息」「附件消息」。点击自动轮次落到触发卡片后,选中保持在该轮,不会跳到下一条用户消息。
15
- - **环境化静止态**——只显示压淡的灰色刻度,悬停刻度条或聚焦列表才展开文字。
16
- - **阅读位置跟踪**——当前位置的刻度保持蓝色、加长且文字加粗(未选中时默认最新一条),其余行一律灰色;停在超长回复中时锚定在所回答的用户消息上。
17
- - **点击跳转**——点击已加载的行直接滚动到位;点击未加载的行经会话的 jump loader 回翻历史后落位。
18
- - **长历史友好**——列表短时垂直居中;长时隐形滚动,最底部贴着聊天输入框;目标/任务等 dock 卡片不会把时间轴顶上去。投影缺席的装配(未挂载 turn-outline 单元)退回窗口分页:打开时预取数页,更早历史在面板滚动到顶部时按需加载。
19
- - **绝不遮住消息流**——面板宽度受滚动区左缘沟槽约束;沟槽放不下最小宽度时隐藏而不是盖住会话。
13
+ - **从第一句起全都在**:整个会话里你说过的每句话都列出来,带一行预览;还没加载的早期消息,点一下它会先把历史翻回去再落位。
14
+ - **一点就跳回去**:点哪一行,会话就滚到那一句。
15
+ - **知道自己读到哪了**:当前位置那一格是蓝色、加长加粗,其余都是灰的;停在一段很长的回复里时,标的是你问的那一句。
16
+ - **没字的消息也有一格**:图片消息、附件消息、自动触发的轮次(重启恢复、定时任务等)都会标出来,也能点。
17
+ - **平时不打扰**:静止时只是一排淡灰刻度,悬停或用键盘聚焦才展开文字。
18
+ - **窄窗口也能用**:右侧栏打开、窗口变窄时,它收成左边一条刻度,悬停时浮出来。
20
19
 
21
- <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/message-timeline2.png" width="640" alt="时间轴的静止态:收成一排压淡的刻度条,不挡视线,悬停才展开">
20
+ <img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/message-timeline2.png" width="560" alt="时间轴的静止态:收成一排压淡的刻度条,不挡视线,悬停才展开">
22
21
 
23
22
  ## 安装
24
23
 
25
- ```sh
26
- dsh plugin --profile web add @khorsheed/dsh-message-timeline
27
- ```
28
-
29
- 然后重启 web 实例。卸载:
24
+ 在 DSH 的「插件 → 添加插件」里输入下面的包名,点安装:
30
25
 
31
- ```sh
32
- dsh plugin --profile web remove @khorsheed/dsh-message-timeline
26
+ ```
27
+ @khorsheed/dsh-basic
33
28
  ```
34
29
 
35
- ## 配置
36
-
37
- | 字段 | 默认 | 含义 |
38
- | --- | --- | --- |
39
- | `enabled` | `true` | 总开关;false 时面板完全不渲染。 |
40
- | `includeSteering` | `true` | 回合中插入的用户消息(steering)是否也算行。 |
41
- | `panelWidth` | `360` | 面板宽度(px,限 120–640);窄列自动收缩,左缘沟槽放不下时整条隐藏。 |
42
- | `initialPages` | `5` | 窗口分页兜底路径下,面板打开时预取的历史页数(每页 50 条事件;限 1–20)。宿主提供 turnOutline 投影时列表一次到位,此配置不生效。 |
43
-
44
- ## Compatibility
45
-
46
- - npm 发布线(`@deepseek-ai/dsh@0.1.2-rc.1`):✅ 完整——基线迁移至 0.1.2-rc.1 API 面(单臂消费 0.1.2 API,0.1.1-rc.2 运行臂已退役),全量构建测试通过;minHost 前移至 0.1.2-rc.1,旧宿主请停留在旧发布线。
47
- - 源码线(deepseek-harness master):✅(verifiedHost: 0.2.1-rc.1)——host-021 波对 0.2.1-rc.1 树全量 build+test 绿,无其它表面变化。
48
- - 降级项:整会话列表依赖宿主 `turnOutline` 投影(`@deepseek-ai/dsh-session-turn-outline`,官方 web bundle 自 0.1.2-alpha.3 起默认挂载,早于 minHost)。装配未挂载该单元时面板退回窗口分页——逐页加载更早历史,功能完整但首条消息要翻到才出现。
49
-
50
- **版本线对照**:0.2.0 起支持宿主 `0.1.2-rc.1` 及以后;宿主 `0.1.0-rc.6` ~ `0.1.1-rc.2` 的用户请停留在 0.1.x 发布线(末版 `0.1.0`)。
30
+ 消息时间轴会跟着「基础体验」一起装好。装完打开任何一个有几轮对话的会话,左边就能看到它。它只读会话、不发任何消息,agent 完全不知道它的存在。
51
31
 
52
32
  ## 已知限制
53
33
 
54
- - **依赖官方 DOM 探针** —— 面板针对官方行属性与 `[data-conversation-scroll]` 滚动区;官方结构变化时面板自行隐藏(console.warn 一次)直到探针更新。
55
- - **best-effort 宽度兜底** —— 消息流探针无应答时,面板以滚动区受限比例渲染而非隐藏,可能压到消息流直到探针恢复;探针健康时宽度绝不越过消息流左缘。
56
- - **单会话渲染** —— 行与跳转只作用于当前渲染的会话。
57
- - **大纲按轮次归组** —— 投影每轮只留首条用户消息的预览:steering 行在其所在轮次加载进窗口后才出现;被撤回消息的轮次仍按大纲列出(与官方轮次轨道一致),点击后落位到该轮最近可见行。
58
- - **暂无整页导览** —— 可搜索的消息索引标签页在计划中。
59
-
60
- ## 实现原理
34
+ - 它认的是官方界面的结构;DSH 改版后如果认不出来,时间轴会先自己隐藏,等插件更新。
35
+ - 只对当前打开的那个会话起作用。
36
+ - 回合中途插进去的话,要等那一轮加载出来才会出现在列表里。
61
37
 
62
- <details>
63
- <summary>内部结构(点击展开)</summary>
64
-
65
- 插件纯增量、不改任何官方代码。
66
-
67
- - `src/client/index.ts` —— 插件主体(`apply`/`inject`)
68
- - `src/client/rail-tracker.ts` —— 插件唯一接触的 DOM:只读探针、阅读位置解析,加跳转时的滚动写入
69
- - `src/client/TimelineRail.tsx` —— 面板组件
70
- - `src/client/outline.ts` —— `turnOutline` 投影值的线路收窄(缺席/损坏一律读作"能力不在")
71
- - `src/client/rows.ts` —— 已加载窗口行与大纲行的合并(单趟交错,保持会话序)
72
- - `src/client/timeline-kinds.ts` —— 哪些 kind 算行、哪些 kind 携带隐藏范围,的唯一权威
73
- - `src/client/hidden-spans.ts` —— 折叠 message-tools 撤回/编辑范围,把被覆盖的原消息作为死行丢弃
74
- - `src/client/preview.ts` —— 消息内容转单行预览文本
75
- - `src/index.ts` —— 空的宿主 `apply`,只负责把插件锚定进宿主 Loader
76
-
77
- **挂载与数据** —— 一个条目注册进官方 `conversation.session.header.utilities` 槽位,把插件锚定进会话作用域;面板通过 body portal 以固定几何渲染,数据从官方滚动区实测。行来自两路合并:宿主 `turnOutline` 投影(`useProjection` 标准座席,整会话每轮一条:轮次号、`turn/start` 序号、首条用户消息的 50 字预览)列出每一轮;已加载窗口(`useChat` 的 `order` / `nodes`)为其覆盖的轮次提供更丰富的预览、steering 行和 message-tools 气泡(宿主 order 未必补上的 `message-tools-edited`/`message-tools-restored` 气泡从节点库追加)。被撤回原消息的气泡会被丢弃(其会话行已被 DOM hider 隐藏,点击到不了),追加的气泡按锚定 seq 排序,保证最新消息恒为最后一行。投影缺席时退回纯窗口行。
78
-
79
- **跳转与降级** —— 点击已加载行按官方 `data-chat-anchor-key` 属性找到会话行并写 `scrollTop`;官方 ChatView 把这种程序化滚动当正常读者移动处理(底部跟随与滚动记忆照常工作)。点击大纲行则经会话的 jump loader(`ISession.loadThrough`,0.1.2-alpha.3 起)把历史回翻到该轮的 `turn/start` 序号,行一物化即落位;翻页停滞或该轮不可达(如首条消息已被撤回)时落位到其后最近的可行。探测的属性是官方渲染产物而非契约 API:官方改结构时面板自行隐藏并 console.warn 一次,不抛错、boot 永不失败。面板宽度受左缘沟槽约束、不越过消息流左缘;流探针无应答(官方结构变化)时宽度退化为滚动区受限比例以 best-effort 兜底——此时面板可能压到消息流,直到探针恢复。
80
-
81
- **模型体验:无。** 面板只读会话快照并滚动会话,不发送提示词、不追加会话事件、不进会话日志。KV 缓存影响:无。`enabled` 配置可整体关闭插件;从 cordis.yml 移除本插件即移除它添加的所有界面。
82
-
83
- </details>
84
-
85
- ## 开发
38
+ ## Compatibility
86
39
 
87
- 隶属 [dsh-plugins](https://github.com/Khorsheed/dsh-plugins) monorepo(`packages/message-timeline`)。问题与贡献请移步该仓库。
40
+ 需要 DSH 0.1.2-rc.1 或更新版本,已在 0.2.1-rc.1 上验证。
88
41
 
89
- ## 变更记录
42
+ ## 给开发者
90
43
 
91
- 见 [CHANGELOG.md](CHANGELOG.md)。
44
+ - 行从哪来、怎么跳转、配置项(`enabled`、`includeSteering`、`panelWidth`、`initialPages`)、界面探针与降级、实现原理,都在[完整参考](https://github.com/Khorsheed/dsh-plugins/blob/main/packages/message-timeline/docs/reference.md)里。
45
+ - 入口:`src/client/index.ts`(插件主体)、`src/client/rail-tracker.ts`(唯一接触页面结构的地方)。也可以单独安装:`dsh plugin --profile web add @khorsheed/dsh-message-timeline`。
46
+ - 隶属 [dsh-plugins](https://github.com/Khorsheed/dsh-plugins),问题与贡献请到该仓库;变更见 [CHANGELOG.md](CHANGELOG.md)。
package/lib/client.js CHANGED
@@ -95,6 +95,25 @@ window.__ModuleLoader__.load({
95
95
  chatView: false
96
96
  };
97
97
  /**
98
+ * The rect of the first laid-out chat input card in the scrollport. A
99
+ * business composer (the Room composer) takes the `conversation.composer`
100
+ * chain as an overlay: the official input bar stays mounted under a
101
+ * `display: none` fallback wrapper, so its card is still the first marker in
102
+ * the DOM but measures as a zero rect at y=0 — ending the box there collapses
103
+ * the panel to zero height. Unlaid-out cards are skipped, so such a layout
104
+ * falls through to the seat top.
105
+ * @param scrollport - the official conversation scrollport element.
106
+ * @returns the card's rect, or null when no laid-out card is rendered.
107
+ */
108
+ function laidOutCard(scrollport) {
109
+ for (const card of scrollport.querySelectorAll("[data-composer-card]")) {
110
+ const rect = card.getBoundingClientRect();
111
+ if (rect.width === 0 && rect.height === 0) continue;
112
+ return rect;
113
+ }
114
+ return null;
115
+ }
116
+ /**
98
117
  * Measure the panel's viewport box from one scrollport: its rect inset by the
99
118
  * panel padding, minus the chat input card at the bottom and the conversation
100
119
  * tab strip at the top. The tabs render just above the scrollport, but
@@ -104,7 +123,7 @@ window.__ModuleLoader__.load({
104
123
  * whole `[data-composer-seat]` top: dock cards (goal/todo/queue) sit above
105
124
  * the input inside the seat, and counting them would push the timeline up off
106
125
  * the conversation. Falls back to the seat top, then the column bottom, when
107
- * the markers are absent.
126
+ * no laid-out card is rendered (see {@link laidOutCard}).
108
127
  * The scrollport's own width rides along for the width-cap fallback when the
109
128
  * message-flow probe is unanswered.
110
129
  * @param scrollport - the official conversation scrollport element.
@@ -122,9 +141,9 @@ window.__ModuleLoader__.load({
122
141
  break;
123
142
  }
124
143
  }
125
- const card = scrollport.querySelector("[data-composer-card]");
144
+ const card = laidOutCard(scrollport);
126
145
  const seat = scrollport.querySelector("[data-composer-seat]");
127
- const bottom = card !== null ? card.getBoundingClientRect().top - RAIL_VERTICAL_PADDING : seat !== null ? seat.getBoundingClientRect().top - RAIL_VERTICAL_PADDING : rect.bottom - RAIL_VERTICAL_PADDING;
146
+ const bottom = card !== null ? card.top - RAIL_VERTICAL_PADDING : seat !== null ? seat.getBoundingClientRect().top - RAIL_VERTICAL_PADDING : rect.bottom - RAIL_VERTICAL_PADDING;
128
147
  return {
129
148
  left: rect.left + RAIL_LEFT_INSET,
130
149
  top: rect.top + topInset,
@@ -532,7 +551,7 @@ window.__ModuleLoader__.load({
532
551
  }
533
552
  //#endregion
534
553
  //#region \0dsh-css:src/client/TimelineRail.module.css.mjs
535
- const css = "._8u17xW_panel{scrollbar-width:none;z-index:100;pointer-events:none;flex-direction:column;padding:4px 8px;display:flex;position:fixed;overflow-y:auto}._8u17xW_panel:before,._8u17xW_panel:after{content:\"\";margin:auto}._8u17xW_panel::-webkit-scrollbar{display:none}._8u17xW_item{width:100%;max-width:20px;color:var(--dsw-alias-label-tertiary);text-align:left;cursor:pointer;-webkit-user-select:none;user-select:none;pointer-events:auto;opacity:.55;background:0 0;border:none;flex:none;align-items:center;gap:8px;padding:5px 0;font-size:13px;line-height:1.5;transition:opacity .12s;display:flex}._8u17xW_panel:hover ._8u17xW_item,._8u17xW_panel:focus-within ._8u17xW_item{opacity:1;max-width:100%}._8u17xW_tick{background:var(--dsw-alias-label-tertiary);border-radius:2px;flex:none;width:3px;height:14px}._8u17xW_itemCurrent{color:var(--dsw-static-deepseek-500);opacity:1;font-weight:600}._8u17xW_tickCurrent{background:var(--dsw-static-deepseek-500);height:18px}._8u17xW_itemText{white-space:nowrap;text-overflow:ellipsis;opacity:0;transition:opacity .12s;overflow:hidden}._8u17xW_panel:hover ._8u17xW_itemText,._8u17xW_panel:focus-within ._8u17xW_itemText{opacity:1;transition-delay:90ms}._8u17xW_itemFocused{color:var(--dsw-alias-label-primary);font-weight:600}._8u17xW_item:focus:not(:focus-visible){outline:none}._8u17xW_item:focus-visible{outline:1px solid var(--dsw-alias-state-business-primary);outline-offset:1px}._8u17xW_itemPending ._8u17xW_tick{background:var(--dsw-static-deepseek-400);animation:1s ease-in-out infinite _8u17xW_railPending}@keyframes _8u17xW_railPending{50%{opacity:.2}}@media (prefers-reduced-motion:reduce){._8u17xW_itemPending ._8u17xW_tick{animation:none}}";
554
+ const css = "._8u17xW_panel{scrollbar-width:none;z-index:100;pointer-events:none;flex-direction:column;padding:4px 8px;display:flex;position:fixed;overflow-y:auto}._8u17xW_panel::-webkit-scrollbar{display:none}._8u17xW_item{width:100%;max-width:20px;color:var(--dsw-alias-label-tertiary);text-align:left;cursor:pointer;-webkit-user-select:none;user-select:none;pointer-events:auto;opacity:.55;background:0 0;border:none;flex:none;align-items:center;gap:8px;padding:5px 0;font-size:13px;line-height:1.5;transition:opacity .12s;display:flex}._8u17xW_panel:hover ._8u17xW_item,._8u17xW_panel:focus-within ._8u17xW_item{opacity:1;max-width:100%}._8u17xW_tick{background:var(--dsw-alias-label-tertiary);border-radius:2px;flex:none;width:3px;height:14px}._8u17xW_itemCurrent{color:var(--dsw-static-deepseek-500);opacity:1;font-weight:600}._8u17xW_tickCurrent{background:var(--dsw-static-deepseek-500);height:18px}._8u17xW_itemText{white-space:nowrap;text-overflow:ellipsis;opacity:0;transition:opacity .12s;overflow:hidden}._8u17xW_panel:hover ._8u17xW_itemText,._8u17xW_panel:focus-within ._8u17xW_itemText{opacity:1;transition-delay:90ms}._8u17xW_itemFocused{color:var(--dsw-alias-label-primary);font-weight:600}._8u17xW_item:focus:not(:focus-visible){outline:none}._8u17xW_item:focus-visible{outline:1px solid var(--dsw-alias-state-business-primary);outline-offset:1px}._8u17xW_itemPending ._8u17xW_tick{background:var(--dsw-static-deepseek-400);animation:1s ease-in-out infinite _8u17xW_railPending}@keyframes _8u17xW_railPending{50%{opacity:.2}}@media (prefers-reduced-motion:reduce){._8u17xW_itemPending ._8u17xW_tick{animation:none}}._8u17xW_panelNarrow{z-index:101}._8u17xW_panelNarrow:hover,._8u17xW_panelNarrow:focus-within{background:var(--dsw-alias-bg-layer-1);border-radius:8px;box-shadow:0 4px 16px #0000002e;width:var(--panel-wide)!important}";
536
555
  const tagId = "@khorsheed/dsh-message-timeline/TimelineRail.module.css";
537
556
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
538
557
  const tag = document.createElement("style");
@@ -542,15 +561,16 @@ window.__ModuleLoader__.load({
542
561
  document.head.appendChild(tag);
543
562
  }
544
563
  var TimelineRail_module_css_default = {
564
+ "itemFocused": "_8u17xW_itemFocused",
545
565
  "itemText": "_8u17xW_itemText",
546
566
  "railPending": "_8u17xW_railPending",
567
+ "tick": "_8u17xW_tick",
547
568
  "panel": "_8u17xW_panel",
548
- "tickCurrent": "_8u17xW_tickCurrent",
549
- "item": "_8u17xW_item",
550
569
  "itemCurrent": "_8u17xW_itemCurrent",
551
570
  "itemPending": "_8u17xW_itemPending",
552
- "tick": "_8u17xW_tick",
553
- "itemFocused": "_8u17xW_itemFocused"
571
+ "panelNarrow": "_8u17xW_panelNarrow",
572
+ "tickCurrent": "_8u17xW_tickCurrent",
573
+ "item": "_8u17xW_item"
554
574
  };
555
575
  //#endregion
556
576
  //#region src/client/TimelineRail.tsx
@@ -613,6 +633,10 @@ window.__ModuleLoader__.load({
613
633
  const PANEL_GAP = 16;
614
634
  /** Degraded width cap (fraction of the scrollport) while the flow probe is unanswered. */
615
635
  const DEGRADED_WIDTH_RATIO = .4;
636
+ /** Rest width of the narrow-layout tick strip (px) — fits the flow's left padding. */
637
+ const PANEL_NARROW_REST_WIDTH = 24;
638
+ /** Expanded width of the narrow-layout hover overlay (fraction of the scrollport). */
639
+ const NARROW_WIDE_RATIO = .6;
616
640
  /**
617
641
  * Frames an outline jump retries the DOM scroll after its row materializes in
618
642
  * the chat store: the transcript renders the new page in the same commit or a
@@ -746,8 +770,9 @@ window.__ModuleLoader__.load({
746
770
  const active = rail.chatView && rail.ready && rail.sessionId === sessionId;
747
771
  const visible = active && items.length > 0;
748
772
  const gutter = rail.flowLeft === null ? null : rail.flowLeft - rail.left - PANEL_PADDING_X - PANEL_GAP;
749
- const width = gutter === null ? Math.min(panelWidth, Math.max(120, rail.scrollportWidth * DEGRADED_WIDTH_RATIO)) : Math.min(panelWidth, Math.max(0, gutter));
750
- const tooNarrow = gutter !== null && width < 120;
773
+ const narrow = gutter !== null && gutter < 120;
774
+ const width = gutter === null ? Math.min(panelWidth, Math.max(120, rail.scrollportWidth * DEGRADED_WIDTH_RATIO)) : narrow ? PANEL_NARROW_REST_WIDTH : Math.min(panelWidth, gutter);
775
+ const wideWidth = Math.min(panelWidth, Math.max(120, rail.scrollportWidth * NARROW_WIDE_RATIO));
751
776
  (0, react.useEffect)(() => {
752
777
  panelRef.current?.querySelector(`[data-item-key=${JSON.stringify(current)}]`)?.scrollIntoView({ block: "nearest" });
753
778
  }, [current]);
@@ -792,19 +817,21 @@ window.__ModuleLoader__.load({
792
817
  fromHead: null
793
818
  });
794
819
  };
795
- if (!visible || tooNarrow) return null;
820
+ if (!visible) return null;
796
821
  return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
797
822
  ref: panelRef,
798
- className: TimelineRail_module_css_default.panel,
823
+ className: `${TimelineRail_module_css_default.panel} ${narrow ? TimelineRail_module_css_default.panelNarrow : ""}`,
799
824
  role: "navigation",
800
825
  "aria-label": t("rail.panel"),
801
826
  tabIndex: 0,
802
827
  "data-timeline-panel": "",
803
828
  style: {
804
829
  left: rail.left,
805
- top: rail.top,
806
- height: rail.height,
807
- width
830
+ top: rail.top + rail.height / 2,
831
+ maxHeight: rail.height,
832
+ transform: "translateY(-50%)",
833
+ width,
834
+ ...narrow ? { "--panel-wide": `${wideWidth}px` } : {}
808
835
  },
809
836
  onScroll: onPanelScroll,
810
837
  onKeyDown: (event) => {
@@ -74,6 +74,10 @@ const PANEL_PADDING_X = 8;
74
74
  const PANEL_GAP = 16;
75
75
  /** Degraded width cap (fraction of the scrollport) while the flow probe is unanswered. */
76
76
  const DEGRADED_WIDTH_RATIO = 0.4;
77
+ /** Rest width of the narrow-layout tick strip (px) — fits the flow's left padding. */
78
+ const PANEL_NARROW_REST_WIDTH = 24;
79
+ /** Expanded width of the narrow-layout hover overlay (fraction of the scrollport). */
80
+ const NARROW_WIDE_RATIO = 0.6;
77
81
  /**
78
82
  * Frames an outline jump retries the DOM scroll after its row materializes in
79
83
  * the chat store: the transcript renders the new page in the same commit or a
@@ -260,20 +264,21 @@ export function TimelineRail({ sessionId, includeSteering, panelWidth, initialPa
260
264
  // pages until one materializes — hence `active` splits from `visible`.
261
265
  const active = rail.chatView && rail.ready && rail.sessionId === sessionId;
262
266
  const visible = active && items.length > 0;
263
- // The panel must never cover the message flow: its width is the configured
264
- // preferred width capped by the scrollport's left gutter — the message
265
- // flow's left edge minus the panel's left edge, less the panel's right
266
- // padding and the visible breathing gap. A gutter too small for the minimum
267
- // usable width hides the panel entirely (the timeline is an overlay
268
- // affordance; squeezed into nothing it only intercepts the transcript).
269
- // When the flow probe is unanswered (official structure change), the width
270
- // degrades to a fraction of the scrollport instead — never throws, never
271
- // covers more than the fallback.
267
+ // The panel must never cover the message flow AT REST: its width is the
268
+ // configured preferred width capped by the scrollport's left gutter — the
269
+ // message flow's left edge minus the panel's left edge, less the panel's
270
+ // right padding and the visible breathing gap. A gutter too small for the
271
+ // minimum width no longer hides the panel: it degrades to a tick strip in
272
+ // the flow's left padding and expands over the flow on hover/focus with a
273
+ // solid background (right sidebars and narrow windows keep the rail
274
+ // usable). When the flow probe is unanswered (official structure change),
275
+ // the width degrades to a fraction of the scrollport instead.
272
276
  const gutter = rail.flowLeft === null ? null : rail.flowLeft - rail.left - PANEL_PADDING_X - PANEL_GAP;
277
+ const narrow = gutter !== null && gutter < PANEL_WIDTH_MIN;
273
278
  const width = gutter === null
274
279
  ? Math.min(panelWidth, Math.max(PANEL_WIDTH_MIN, rail.scrollportWidth * DEGRADED_WIDTH_RATIO))
275
- : Math.min(panelWidth, Math.max(0, gutter));
276
- const tooNarrow = gutter !== null && width < PANEL_WIDTH_MIN;
280
+ : narrow ? PANEL_NARROW_REST_WIDTH : Math.min(panelWidth, gutter);
281
+ const wideWidth = Math.min(panelWidth, Math.max(PANEL_WIDTH_MIN, rail.scrollportWidth * NARROW_WIDE_RATIO));
277
282
  // Keep the lit row in view: the panel follows the reading position (a new
278
283
  // message scrolls its row in), and mouse browsing is never yanked because
279
284
  // the hovered row is the current one and always visible under the pointer.
@@ -326,9 +331,20 @@ export function TimelineRail({ sessionId, includeSteering, panelWidth, initialPa
326
331
  // landing scroll. Re-clicking another outline row retargets the jump.
327
332
  setPendingJump({ sessionId, turn: row.turn, seq: row.seq, requested: false, awaiting: false, fromHead: null });
328
333
  };
329
- if (!visible || tooNarrow)
334
+ if (!visible)
330
335
  return null;
331
- return createPortal(_jsx("div", { ref: panelRef, className: css.panel, role: "navigation", "aria-label": t('rail.panel'), tabIndex: 0, "data-timeline-panel": "", style: { left: rail.left, top: rail.top, height: rail.height, width }, onScroll: onPanelScroll, onKeyDown: (event) => {
336
+ return createPortal(_jsx("div", { ref: panelRef, className: `${css.panel} ${narrow ? css.panelNarrow : ''}`, role: "navigation", "aria-label": t('rail.panel'), tabIndex: 0, "data-timeline-panel": "", style: {
337
+ // The box fits its rows (up to the rail height) and centers on the
338
+ // rail's midline, so the narrow-mode hover background wraps the list
339
+ // instead of painting the whole column; a longer list fills the rail
340
+ // and scrolls.
341
+ left: rail.left,
342
+ top: rail.top + rail.height / 2,
343
+ maxHeight: rail.height,
344
+ transform: 'translateY(-50%)',
345
+ width,
346
+ ...(narrow ? { '--panel-wide': `${wideWidth}px` } : {}),
347
+ }, onScroll: onPanelScroll, onKeyDown: (event) => {
332
348
  if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
333
349
  event.preventDefault();
334
350
  const anchor = items.findIndex(item => item.id === current);
@@ -25,7 +25,7 @@ import type { TimelineRailState } from './slots.ts';
25
25
  * whole `[data-composer-seat]` top: dock cards (goal/todo/queue) sit above
26
26
  * the input inside the seat, and counting them would push the timeline up off
27
27
  * the conversation. Falls back to the seat top, then the column bottom, when
28
- * the markers are absent.
28
+ * no laid-out card is rendered (see {@link laidOutCard}).
29
29
  * The scrollport's own width rides along for the width-cap fallback when the
30
30
  * message-flow probe is unanswered.
31
31
  * @param scrollport - the official conversation scrollport element.
@@ -14,6 +14,26 @@ const IDLE = {
14
14
  sessionId: undefined, ready: false, left: 0, top: 0, height: 0, scrollportWidth: 0, flowLeft: null,
15
15
  activeKey: null, chatView: false,
16
16
  };
17
+ /**
18
+ * The rect of the first laid-out chat input card in the scrollport. A
19
+ * business composer (the Room composer) takes the `conversation.composer`
20
+ * chain as an overlay: the official input bar stays mounted under a
21
+ * `display: none` fallback wrapper, so its card is still the first marker in
22
+ * the DOM but measures as a zero rect at y=0 — ending the box there collapses
23
+ * the panel to zero height. Unlaid-out cards are skipped, so such a layout
24
+ * falls through to the seat top.
25
+ * @param scrollport - the official conversation scrollport element.
26
+ * @returns the card's rect, or null when no laid-out card is rendered.
27
+ */
28
+ function laidOutCard(scrollport) {
29
+ for (const card of scrollport.querySelectorAll('[data-composer-card]')) {
30
+ const rect = card.getBoundingClientRect();
31
+ if (rect.width === 0 && rect.height === 0)
32
+ continue;
33
+ return rect;
34
+ }
35
+ return null;
36
+ }
17
37
  /**
18
38
  * Measure the panel's viewport box from one scrollport: its rect inset by the
19
39
  * panel padding, minus the chat input card at the bottom and the conversation
@@ -24,7 +44,7 @@ const IDLE = {
24
44
  * whole `[data-composer-seat]` top: dock cards (goal/todo/queue) sit above
25
45
  * the input inside the seat, and counting them would push the timeline up off
26
46
  * the conversation. Falls back to the seat top, then the column bottom, when
27
- * the markers are absent.
47
+ * no laid-out card is rendered (see {@link laidOutCard}).
28
48
  * The scrollport's own width rides along for the width-cap fallback when the
29
49
  * message-flow probe is unanswered.
30
50
  * @param scrollport - the official conversation scrollport element.
@@ -44,10 +64,10 @@ export function measureGeometry(scrollport) {
44
64
  break;
45
65
  }
46
66
  }
47
- const card = scrollport.querySelector('[data-composer-card]');
67
+ const card = laidOutCard(scrollport);
48
68
  const seat = scrollport.querySelector('[data-composer-seat]');
49
69
  const bottom = card !== null
50
- ? card.getBoundingClientRect().top - RAIL_VERTICAL_PADDING
70
+ ? card.top - RAIL_VERTICAL_PADDING
51
71
  : seat !== null
52
72
  ? seat.getBoundingClientRect().top - RAIL_VERTICAL_PADDING
53
73
  : rect.bottom - RAIL_VERTICAL_PADDING;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@khorsheed/dsh-message-timeline",
3
3
  "description": "Message timeline: a flat floating list over the conversation scrollport that jumps to any user message, with a tick-only rest state",
4
- "version": "0.2.5",
4
+ "version": "0.2.7",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",