deepseek-harness-background 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/README.zh.md +4 -4
- package/lib/client.js +178 -52
- package/lib/client.js.map +1 -1
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -50,9 +50,9 @@ The look (fixed wallpaper layer + theme-aware scrim + translucent glass panels d
|
|
|
50
50
|
- **Frosted glass (whitelisted)** — while a background is active, only the surfaces that float as small cards over the wallpaper turn into translucent glass (specular sheen + `backdrop-filter`): the composer card and message bubbles, code blocks / terminal / diff / tool-IO cards / skill & MCP call cards and inline code, the agent task strips and their takeover panels (approval / question / plan review), the chrome buttons (new session, composer plus, scroll-to-bottom), the load-earlier history button, the subagent lineage popover, the sidebar build badge, and the home hero "preview" badge — every glassed surface carries the full recipe (fill + sheen + blur), never translucency without frost. Reading surfaces — dialogs, the settings UI, menus, tooltips, toasts, hover fills and every accent (the send button stays blue) — keep their **official opaque paints** so nothing legible turns washy (the one documented exception: the turn rail's hover preview, which is rail chrome over the art and joins the glass sheet). The blur radius is driven by the glass-blur slider; `panelOpacity` at 100% restores the official paints on the whitelisted list too.
|
|
51
51
|
- **Third-party glass registry** — any plugin can register its own panels into the same frosted-glass system through the published bridge (`window.__DSH_BACKGROUND_GLASS__` global + the `dsh-background-glass:ready` event): *token* mode adds the missing sheen + blur chain when the panel already fills with an overridden `--dsw-*` token, *fill* mode takes the fill over as well; every rule sits under the `data-dsh-bg-glass` gate so it toggles with the glass automatically. Consumers take zero dependencies and degrade gracefully when this plugin is absent; the whole bridge tears down with it. See [docs/GLASS_API.md](docs/GLASS_API.md).
|
|
52
52
|
- **Conversation timeline** — the DeepSeek-web turn-navigation rail at the right edge of long conversations, one tick per question, with a hover/focus preview of that turn's prompt. Two frontends, one backend, chosen by what the running kernel supports:
|
|
53
|
-
- **dsh ≥ 0.1.2 (enhancement)** — the kernel ships this rail already, so the plugin renders nothing of its own and only fixes its behaviour
|
|
53
|
+
- **dsh ≥ 0.1.2 (enhancement)** — the kernel ships this rail already, so the plugin renders nothing of its own and only fixes its behaviour, scoped to what the running rail still lacks (a capability check on the rail's own published metrics, never a version compare). Clicks on a **loaded** mark become a **smooth glide** instead of the stock instant jump (ease-in-out, distance-paced, cancelled the moment the reader scrolls, `prefers-reduced-motion` places directly). On **0.1.2-alpha.1/2** the rail lists only loaded turns, so reaching for it also pages older history in — turns behind "load earlier" get a tick and become reachable, stopping at the rail's own uncompressed capacity, past which the official stylesheet would squeeze the marks into an unaimable bar. On **0.1.2-alpha.3+** the official rail rebuilt itself (fixed-pitch marks scrolling inside a frame, fed by the whole-log turn outline: every turn renders, unloaded ones page on demand), so the plugin narrows to the smooth glide alone — click indexes are resolved against the same merged ladder the kernel renders, marks outside the loaded window are left to the kernel's own jump, and the warm-up does not run. Because the official hover preview card paints an opaque fill, it joins the glass sheet under the wallpaper gate instead (same explicit-fill recipe as the composer card, driven by the glass-blur slider). The rail's active-mark state stays the kernel's own — the glide scrolls the real scrollport, so the stock scroll handler keeps updating it and the persisted scroll position.
|
|
54
54
|
- **dsh ≤ 0.1.1 (ported rail)** — no official rail exists, so the plugin renders its own port of it (identical metrics, plus the same glass treatment), fed by the same backend.
|
|
55
|
-
In both cases the bottom-follow state is detached one frame before the glide (streaming growth / turn-end re-renders cannot yank it back to the floor), messages withdrawn by a rewind are dropped automatically (the target parser accepts `seq 42` / `#42` / english-chinese verb forms plus structured fields, and recognizes third-party `data-dsh-rewind-hidden` node markers), and history
|
|
55
|
+
In both cases the bottom-follow state is detached one frame before the glide (streaming growth / turn-end re-renders cannot yank it back to the floor), messages withdrawn by a rewind are dropped automatically (the target parser accepts `seq 42` / `#42` / english-chinese verb forms plus structured fields, and recognizes third-party `data-dsh-rewind-hidden` node markers), and wherever this plugin pages history itself (the ported rail and the alpha.1/2 rail) every page compensates the prepended height so the reader's view never moves. Toggle it off with the timeline switch in the row.
|
|
56
56
|
- **Persisted in the official settings document** (`$DSH_HOME/settings.yaml`), waits out restarts.
|
|
57
57
|
- **Clean teardown** — disabling, clearing or uninstalling restores the original background exactly; the plugin only ever removes what it wrote.
|
|
58
58
|
|
|
@@ -103,7 +103,7 @@ dsh --profile web
|
|
|
103
103
|
| 毛玻璃模糊 / Glass blur | `0..40px` `backdrop-filter` blur on the translucent surfaces (1px steps). |
|
|
104
104
|
| 壁纸模糊 / Wallpaper blur | `0..60px` blur of the wallpaper image itself (2px steps). |
|
|
105
105
|
| 填充方式 / Fit | `cover` or `contain`. |
|
|
106
|
-
| 会话时间线 / Timeline | on/off switch for the conversation timeline (default on). On dsh ≥ 0.1.2 the row is labelled 会话时间线增强 / *enhancement*: the official rail stays and this only improves its behaviour; turning it off restores stock behaviour. On older kernels the row is 会话时间线 / *timeline* and the switch controls the plugin's own ported rail. |
|
|
106
|
+
| 会话时间线 / Timeline | on/off switch for the conversation timeline (default on). On dsh ≥ 0.1.2 the row is labelled 会话时间线增强 / *enhancement*: the official rail stays and this only improves its behaviour; turning it off restores stock behaviour. The copy follows the kernel generation — full enhancement on alpha.1/2, smooth-jump-only on alpha.3+. On older kernels the row is 会话时间线 / *timeline* and the switch controls the plugin's own ported rail. |
|
|
107
107
|
|
|
108
108
|
5. **清除背景** removes the background and restores the stock look.
|
|
109
109
|
|
|
@@ -112,7 +112,7 @@ dsh --profile web
|
|
|
112
112
|
- The **settings row** lives in the official General settings section (`settings.general.item` slot), next to the Appearance row. Its chrome uses only `--dsw-alias-*` design tokens (buttons / pills / segmented control / slider track match the official shell); sliders are native `input[type=range]` with 5% / 1–2px steps and release-commit.
|
|
113
113
|
- The plugin's own host routes (`/api/bg-wallpaper/*`: `settings`, `upload`, `image/<id>`) read/write the section and serve uploads with same-origin + size caps + MIME/signature checks + a path-escape fence. A custom route family is used because the api-proxy settings allowlist does not expose third-party namespaces over the settings RPC.
|
|
114
114
|
- The background is drawn as a fixed `z-index:-2` wallpaper layer plus a `z-index:-1` scrim on `body`, toggled by the `data-dsh-bg` attribute; the scrim switches white/black by `data-ds-dark-theme` in the injected stylesheet. Frosted glass is whitelist-scoped: only the whitelisted surfaces get `--dsw-*` surface-token overrides, while everything else is painted by explicit `data-dsh-bg-glass`-gated rules carrying the FULL recipe (fill + sheen + blur chain): the three chrome buttons, the load-earlier history button, the composer dock family (agent task strips TodoPanel / GoalBar / QueueDock and their takeover panels — approval, question, plan review — whose tokens turn translucent and need the blur added), the subagent lineage popover, the home hero preview badge and the sidebar build badge — every other official token and reading surface (menus, dialogs, tooltips, toasts) stays untouched.
|
|
115
|
-
- The **timeline** is registered into the `conversation.input.dock` slot (per-session lifecycle). Mode detection is a capability check on the slot props rather than a version compare: the official rail is rendered from the very index `ui-chat` publishes as a session hook (`useChat(s => s.navigation.items())`, dsh >= 0.1.2), so the presence of that hook *is* the presence of the official rail — and it survives pre-releases, forks and deployments that mount a different conversation target. With the hook the plugin renders nothing and intercepts the official rail clicks in the **capture** phase (React 18 dispatches `onClick` from the root container during the bubble phase, so a capture listener on the rail runs first and `stopImmediatePropagation()` keeps the stock handler from ever firing); without
|
|
115
|
+
- The **timeline** is registered into the `conversation.input.dock` slot (per-session lifecycle). Mode detection is a capability check on the slot props rather than a version compare: the official rail is rendered from the very index `ui-chat` publishes as a session hook (`useChat(s => s.navigation.items())`, dsh >= 0.1.2), so the presence of that hook *is* the presence of the official rail — and it survives pre-releases, forks and deployments that mount a different conversation target. With the hook the plugin renders nothing and intercepts the official rail clicks in the **capture** phase (React 18 dispatches `onClick` from the root container during the bubble phase, so a capture listener on the rail runs first and `stopImmediatePropagation()` keeps the stock handler from ever firing) — resolving the click index against the ladder the kernel actually renders: the loaded items alone on the compressing alpha.1/2 rail, and on the alpha.3 frame-style rail the same outline-plus-loaded merge the kernel renders, with marks outside the loaded window (no anchor key) left to the kernel's own load-through jump — and while one of those kernel jumps is still paging (a mark pulses `aria-busy`) the plugin stands down entirely, because the kernel's loaded branch cancels its own pending jump before landing and an interception would bypass that cancellation; without the hook the plugin renders its own port of the rail, portaled to `body`. Both run the same jump engine. Data comes, fastest first, from a host-side session projection (`bgTimeline`, registered in `src/projection.ts`) that enumerates every user message of the whole session — including turns the conversation has not paged in yet — with the loaded chat-node window as fallback; that window is read duck-typed across both kernel generations, because 0.1.2 moved the Chat snapshot from `session.getSnapshot().chat` to `useChat()`, the one breaking change for this plugin. History warm-up runs only when the reader reaches for the rail — and only where it is still wanted: the compressing alpha.1/2 rail and the ported one, stopping at the rail uncompressed tick capacity (`floor((height - 12) / 10) + 1`); the alpha.3 frame-style rail lists every turn already and pages on demand itself, so warming would only fight the kernel's pager. Every page compensates the prepended height, because the host own prepend anchor is unreachable from outside. The ported rail carries the same **glass treatment as the enhanced official one** — the hover preview joins the frosted-glass sheet under the wallpaper gate (same explicit-fill recipe as the composer card) — so both frontends look identical to the reader.
|
|
116
116
|
- The **third-party glass registry** (`src/client/glass-registry.ts`) publishes its bridge on client apply — before any repaint can race it: `register({ plugin, selectors, mode })` is idempotent per (plugin, mode, selector) triple and synthesizes explicit `body[data-dsh-bg-glass]`-gated recipe rules into a dedicated `<style data-plugin-css>` tag; selectors are structurally validated first (no `{ } ; @ < > ,`, no backslashes, 500-char cap) with per-selector warn-and-drop that never blocks valid siblings; disposing the client fiber retracts the whole bridge (stylesheet, entries, window key). Contract docs in `docs/GLASS_API.md` / `GLASS_API.zh.md`; behavior locked by `tests/glass-registry.spec.ts`.
|
|
117
117
|
- Uploads live under `$DSH_HOME/deepseek-harness-background/` (content-addressed ids). Switching to a new image or clearing the background deletes the superseded upload file, so the directory does not accumulate dead images in normal use. (An upload that is never saved into the section — e.g. the tab closes right after an upload — can leave one orphaned file behind.) Disable / uninstall leaves nothing behind.
|
|
118
118
|
|
|
@@ -144,7 +144,7 @@ deepseek-harness-background/ # the plugin repo (package name stays the
|
|
|
144
144
|
│ ├── glass-registry.ts # third-party glass registry (window bridge + gated rule synthesis)
|
|
145
145
|
│ ├── timeline/
|
|
146
146
|
│ │ ├── index.tsx # dock entry: mode dispatch + re-exports
|
|
147
|
-
│ │ ├── official-enhance.tsx # dsh>=0.1.2:
|
|
147
|
+
│ │ ├── official-enhance.tsx # dsh>=0.1.2: smooth-jump interception (+ warm-up on the alpha.1/2 rail)
|
|
148
148
|
│ │ ├── legacy-rail.tsx # dsh<=0.1.1: ported official turn rail
|
|
149
149
|
│ │ ├── legacy-rail-css.ts # rail stylesheet (dsbt- prefixed, official metrics)
|
|
150
150
|
│ │ ├── jump.ts # shared jump engine (paging + glide + compensation)
|
package/README.zh.md
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
- **主题自适应遮罩** —— 浅色主题用白色纱帘(把图片提亮保持深色文字对比度),深色主题自动换成黑色纱帘(压暗图片保持浅色文字对比度)。
|
|
50
50
|
- **毛玻璃(白名单制)** —— 启用背景后,只有以小卡片/小按钮形态悬浮在壁纸上的表面才会变成半透明玻璃(顶部白色高光渐变 + `backdrop-filter`):输入框卡片与消息气泡、代码块 / 终端 / diff / 工具 IO 卡 / 技能与 MCP 调用卡与行内代码、agent 任务条及其接管面板(审批 / 提问 / 计划评审)、三个铬件按钮(新会话、输入框加号、回到底部)、「加载更早」历史按钮、标题栏展开的子代理列表面板、侧栏构建徽标,以及首页右上角的「预览版」徽标——每块玻璃面都带完整配方(填充 + 高光 + 模糊),绝无"只透明不磨砂"的残缺面。阅读型表面——对话框、设置界面、菜单、Tooltip、Toast、悬停填充与所有强调色(发送键保持品牌蓝)——一律保留**官方不透明样式**,保证可读性(唯一例外:时间线导航轨的悬浮预览卡,它是画在壁纸上的导航铬件,加入玻璃配方)。模糊半径由「毛玻璃模糊」滑块驱动;「面板不透明度」调至 100% 时白名单表面也恢复官方原样。
|
|
51
51
|
- **第三方毛玻璃接口** —— 内置毛玻璃注册表(`window.__DSH_BACKGROUND_GLASS__` 全局 + `dsh-background-glass:ready` 事件):任何插件都可把自家面板的选择器注册进来,套上与内置表面完全一致的配方——`token` 模式为已使用被覆盖 `--dsw-*` 填充的面板补齐高光+模糊链,`fill` 模式连填充一并接管;规则统一挂在 `data-dsh-bg-glass` 门控下随玻璃自动开关。消费方零依赖、未安装本插件时优雅降级,本插件卸载时整桥拆除。详见 [docs/GLASS_API.zh.md](docs/GLASS_API.zh.md)。
|
|
52
|
-
- **会话时间线** —— 长会话右缘的 DeepSeek 官网风格轮次导航轨:每一轮一个刻度,悬停或聚焦时预览该轮的问题。 两套前端、同一套后端,按当前内核支持的能力自动选择: - **dsh ≥ 0.1.2(增强)** ——
|
|
52
|
+
- **会话时间线** —— 长会话右缘的 DeepSeek 官网风格轮次导航轨:每一轮一个刻度,悬停或聚焦时预览该轮的问题。 两套前端、同一套后端,按当前内核支持的能力自动选择: - **dsh ≥ 0.1.2(增强)** —— 内核已经自带这条导航轨,因此插件不渲染任何自己的界面,只修正它仍欠缺的行为(按导航轨自己发布的度量做能力探测,绝不比较版本号): 对**已加载**轮次的点击由官方的瞬时跳转改为**平滑滑动**(缓入缓出、按距离定时长、读者一滚动立刻接管、`prefers-reduced-motion` 下直接定位)。 在 **0.1.2-alpha.1/2** 上导航轨只列出已加载的轮次,因此读者把手伸向导航轨时还会自动往回翻页加载历史,让“需要点加载更多才出现”的轮次也长出刻度、可以直接跳转 —— 补到导航轨自身不压缩的刻度上限为止(再往后官方样式表会把刻度挤成无法瞄准的实心条)。 在 **0.1.2-alpha.3+** 上官方重构了导航轨(固定间距刻度在框内滚动,数据来自全会话轮次大纲:每一轮都在轨上,未加载的轮次由内核自行翻页),插件相应收窄为只保留平滑滑动 —— 点击索引按内核渲染的同一份合并阶梯解析,已加载窗口之外的刻度交给内核自己的跳转机器,历史预热不再运行。官方悬浮预览卡是实心底,在壁纸门控下改为与输入卡同配方的毛玻璃(受「毛玻璃模糊」滑块驱动);刻度列加上 DeepSeek 网页版式的上下渐隐(仅绘制层,不影响命中测试)。导航轨的高亮状态仍归内核自己维护——滑动走的是真实滚动容器,官方滚动处理会持续更新活动轮次与持久化滚动位置。 - **dsh ≤ 0.1.1(移植版)** —— 没有官方导航轨,插件渲染自己移植的同一套界面(完全相同的尺寸,同样带上玻璃 + 上下渐隐), 数据来自同一套后端。 两种情况下,滑动前都会先解除宿主的底部跟随状态(流式增长、轮次结束重渲染都不会把视图拽回底部);被 rewind 撤回的消息自动从轨道剔除; 凡插件自己翻页加载历史的地方(移植轨与 alpha.1/2 轨),每一页都会补偿前置内容的高度,读者视野纹丝不动。用设置行里的时间线开关可关闭。
|
|
53
53
|
- **持久化到官方设置文档** —— 存于 `$DSH_HOME/settings.yaml`,跨重启保留。
|
|
54
54
|
- **干净卸载** —— 关闭、清除或卸载后完整恢复原背景;插件只移除自己写过的内容。
|
|
55
55
|
|
|
@@ -100,14 +100,14 @@ dsh --profile web
|
|
|
100
100
|
| 毛玻璃模糊 | `0..40px` 半透明表面上的 `backdrop-filter` 模糊(1px 步进)。 |
|
|
101
101
|
| 壁纸模糊 | `0..60px` 壁纸图片本身的模糊(2px 步进)。 |
|
|
102
102
|
| 填充方式 | `cover`(铺满)或 `contain`(完整)。 |
|
|
103
|
-
| 会话时间线 | 会话时间线的开关(默认开启)。dsh ≥ 0.1.2
|
|
103
|
+
| 会话时间线 | 会话时间线的开关(默认开启)。dsh ≥ 0.1.2 上该项显示为**会话时间线增强**:官方导航轨保留,插件只优化其行为,关掉即恢复官方原样(说明文案随内核代际分两版——alpha.1/2 完整增强,alpha.3+ 仅平滑跳转);旧内核上该项为**会话时间线**,开关控制插件自己移植的导航轨。 |
|
|
104
104
|
|
|
105
105
|
5. 点 **清除背景** 移除背景,恢复默认外观。
|
|
106
106
|
|
|
107
107
|
## 原理
|
|
108
108
|
|
|
109
109
|
- **设置行**位于官方「通用」设置分区的 `settings.general.item` 槽中,紧挨「外观」行。控件样式全部使用 `--dsw-alias-*` 设计 token(按钮 / 胶囊 / 分段控件 / 滑块轨道与官方 chrome 一致),滑块为原生 `input[type=range]` 的 5% / 1–2px 步进 + 松手提交。
|
|
110
|
-
- **会话时间线**注册进 `conversation.input.dock` 槽位(绑定每会话生命周期)。模式判定是对槽位 props 的能力探测,而不是比较版本号: 官方导航轨正是由 `ui-chat` 以会话 hook 形式发布的同一份索引渲染的(`useChat(s => s.navigation.items())`,dsh ≥ 0.1.2), 所以这个 hook 存在**就是**官方导航轨存在 —— 而且它能扛住预发布版、fork 以及挂载了其它会话目标的部署。 hook 存在时插件什么都不渲染,只在**捕获阶段**拦截官方导航轨的点击(React 18 在冒泡阶段由根容器派发 `onClick`, 所以导航轨上的捕获监听先执行,`stopImmediatePropagation()`
|
|
110
|
+
- **会话时间线**注册进 `conversation.input.dock` 槽位(绑定每会话生命周期)。模式判定是对槽位 props 的能力探测,而不是比较版本号: 官方导航轨正是由 `ui-chat` 以会话 hook 形式发布的同一份索引渲染的(`useChat(s => s.navigation.items())`,dsh ≥ 0.1.2), 所以这个 hook 存在**就是**官方导航轨存在 —— 而且它能扛住预发布版、fork 以及挂载了其它会话目标的部署。 hook 存在时插件什么都不渲染,只在**捕获阶段**拦截官方导航轨的点击(React 18 在冒泡阶段由根容器派发 `onClick`, 所以导航轨上的捕获监听先执行,`stopImmediatePropagation()` 让官方处理器根本收不到事件)——点击索引按内核实际渲染的阶梯解析:压缩式 alpha.1/2 轨上是已加载条目本身,alpha.3 帧式轨上则是与内核同构的「大纲 + 已加载」合并阶梯,已加载窗口之外(无锚键)的刻度交给内核自己的穿越加载跳转——且当内核跳转仍在翻页(标记以 `aria-busy` 脉冲)时插件整体让位:内核的已加载分支落点前会先取消自己的在途跳转,拦截会绕过这一取消;hook 不存在时插件渲染自己移植的导航轨,portal 到 `body`。 两者跑同一个跳转引擎。数据最快的来源是宿主侧会话投影(`bgTimeline`,注册于 `src/projection.ts`), 它枚举整个会话的每一条用户消息(含会话视图尚未分页载人的轮次),已加载的 chat 节点窗口作为兜底; 该窗口以鸭子类型同时兼容两代内核,因为 0.1.2 把 Chat 快照从 `session.getSnapshot().chat` 迁到了 `useChat()` —— 这是本插件唯一一处破坏性变更。 历史预热只在读者把手伸向导航轨时运行——而且只在其仍有意义的地方运行:压缩式 alpha.1/2 轨与移植轨,并停在导航轨不压缩的刻度上限(`floor((高度 - 12) / 10) + 1`); alpha.3 帧式轨已列出每一轮并自行按需翻页,预热只会与内核的翻页器相互踩踏,故不运行。 每一页都补偿前置内容的高度,因为宿主的 prepend 锚点在插件侧无法触及。移植版导航轨与增强后的官方轨采用完全一致的**玻璃 + 上下渐隐处理** —— 悬浮预览在壁纸门控下加入毛玻璃配方(与输入卡片同一显式填充配方),刻度列带 DeepSeek 网页版式上下渐隐,两个前端对读者的视觉完全一致。
|
|
111
111
|
- 插件自有的 host 路由(`/api/bg-wallpaper/*`:`settings`、`upload`、`image/<id>`)负责读写设置与提供上传图片,带同源校验、大小上限、MIME/签名校验与路径穿越防护。使用自定义路由族,是因为 api-proxy 的 settings 白名单不向第三方命名空间开放 settings RPC。
|
|
112
112
|
- 背景以 `body` 上一张固定的 `z-index:-2` 壁纸层 + `z-index:-1` 遮罩绘制,由 `data-dsh-bg` 属性开关;遮罩在注入样式表里按 `data-ds-dark-theme` 切换白/黑纱帘。毛玻璃为白名单制:仅对输入/气泡/代码/任务条等白名单表面覆盖 `--dsw-*` surface token,其余表面通过 `data-dsh-bg-glass` 门控的显式规则补齐整套配方(填充 + 高光 + 模糊滤镜链):三个 chrome 按钮、「加载更早」历史按钮、composer 坞列家族——agent 任务条(TodoPanel / GoalBar / QueueDock)与接管面板(审批 / 提问 / 计划评审,token 变半透明后由这里补上模糊)、子代理列表弹出层、首页「预览版」徽标、侧栏构建徽章——全部官方 token 与阅读型界面(菜单/对话框/tooltip/toast)保持原样。
|
|
113
113
|
- **第三方玻璃注册表**(`src/client/glass-registry.ts`):客户端 apply 时即在 `window.__DSH_BACKGROUND_GLASS__` 发布注册 api 并派发 `dsh-background-glass:ready`;`register({ plugin, selectors, mode })` 按 `(plugin, mode, selector)` 三元组幂等登记,把 `body[data-dsh-bg-glass]` 门控的显式配方规则合成进独立的 `<style data-plugin-css>` 标签;选择器先做结构性校验(禁 `{} ; @ < > ,` 与反斜杠、500 字符上限),违规条目逐条警告并丢弃、不影响兄弟条目;fiber dispose 时整桥拆除(样式表、条目、window 键全清)。契约文档见 `docs/GLASS_API.md` / `GLASS_API.zh.md`,行为由 `tests/glass-registry.spec.ts` 锁定。
|
|
@@ -141,7 +141,7 @@ deepseek-harness-background/ # 插件仓库(包名保留 npm 风格 i
|
|
|
141
141
|
│ ├── glass-registry.ts # 第三方毛玻璃注册表(window 桥 + 门控规则合成)
|
|
142
142
|
│ ├── timeline/
|
|
143
143
|
│ │ ├── index.tsx # 停靠入口:模式分发与再导出
|
|
144
|
-
│ │ ├── official-enhance.tsx # dsh>=0.1.2
|
|
144
|
+
│ │ ├── official-enhance.tsx # dsh>=0.1.2:平滑跳转拦截(+ alpha.1/2 轨历史预热)
|
|
145
145
|
│ │ ├── legacy-rail.tsx # dsh<=0.1.1:移植的官方轮次导航轨
|
|
146
146
|
│ │ ├── legacy-rail-css.ts # 导航轨样式(dsbt- 前缀,官方尺寸)
|
|
147
147
|
│ │ ├── jump.ts # 共享跳转引擎(翻页 + 滑动 + 补偿)
|
package/lib/client.js
CHANGED
|
@@ -969,6 +969,7 @@ window.__ModuleLoader__.load({
|
|
|
969
969
|
"background.timelineHint": "右侧轮次导航轨,悬停预览该轮问题与直达更早历史",
|
|
970
970
|
"background.timelineEnhance": "会话时间线增强",
|
|
971
971
|
"background.timelineEnhanceHint": "为官方会话时间线补上平滑跳转,并可直达需要“加载更多”的更早轮次",
|
|
972
|
+
"background.timelineEnhanceNarrowHint": "为官方会话时间线的已加载轮次补上平滑跳转",
|
|
972
973
|
"timeline.railLabel": "轮次导航",
|
|
973
974
|
"timeline.noText": "(无文本内容)",
|
|
974
975
|
"timeline.jump": "跳转到第 {turn} 轮"
|
|
@@ -1003,6 +1004,7 @@ window.__ModuleLoader__.load({
|
|
|
1003
1004
|
"background.timelineHint": "Turn rail at the right edge; hover to preview a turn and reach earlier history",
|
|
1004
1005
|
"background.timelineEnhance": "Conversation timeline enhancement",
|
|
1005
1006
|
"background.timelineEnhanceHint": "Adds smooth jumps and direct reach into turns that still need “load earlier”",
|
|
1007
|
+
"background.timelineEnhanceNarrowHint": "Adds smooth jumps for the rail's loaded turns",
|
|
1006
1008
|
"timeline.railLabel": "Turn navigation",
|
|
1007
1009
|
"timeline.noText": "(no text)",
|
|
1008
1010
|
"timeline.jump": "Jump to turn {turn}"
|
|
@@ -1165,41 +1167,41 @@ window.__ModuleLoader__.load({
|
|
|
1165
1167
|
document.head.appendChild(tag);
|
|
1166
1168
|
}
|
|
1167
1169
|
var SettingsRow_module_css_default = {
|
|
1168
|
-
"
|
|
1169
|
-
"
|
|
1170
|
+
"header": "-ozqdq_header",
|
|
1171
|
+
"urlRow": "-ozqdq_urlRow",
|
|
1172
|
+
"previewImg": "-ozqdq_previewImg",
|
|
1173
|
+
"segGroup": "-ozqdq_segGroup",
|
|
1174
|
+
"switchKnob": "-ozqdq_switchKnob",
|
|
1175
|
+
"hint": "-ozqdq_hint",
|
|
1176
|
+
"slider": "-ozqdq_slider",
|
|
1177
|
+
"segLabel": "-ozqdq_segLabel",
|
|
1178
|
+
"segActive": "-ozqdq_segActive",
|
|
1170
1179
|
"desc": "-ozqdq_desc",
|
|
1171
|
-
"
|
|
1172
|
-
"section": "-ozqdq_section",
|
|
1180
|
+
"switch": "-ozqdq_switch",
|
|
1173
1181
|
"sliderLabel": "-ozqdq_sliderLabel",
|
|
1174
|
-
"
|
|
1175
|
-
"previewGlass": "-ozqdq_previewGlass",
|
|
1176
|
-
"urlInput": "-ozqdq_urlInput",
|
|
1177
|
-
"segGroup": "-ozqdq_segGroup",
|
|
1182
|
+
"previewScrim": "-ozqdq_previewScrim",
|
|
1178
1183
|
"segRow": "-ozqdq_segRow",
|
|
1179
|
-
"
|
|
1180
|
-
"clearRow": "-ozqdq_clearRow",
|
|
1181
|
-
"previewImg": "-ozqdq_previewImg",
|
|
1182
|
-
"slider": "-ozqdq_slider",
|
|
1183
|
-
"btnDanger": "-ozqdq_btnDanger",
|
|
1184
|
-
"sourceRow": "-ozqdq_sourceRow",
|
|
1185
|
-
"previewEmpty": "-ozqdq_previewEmpty",
|
|
1184
|
+
"titleRow": "-ozqdq_titleRow",
|
|
1186
1185
|
"error": "-ozqdq_error",
|
|
1187
|
-
"switch": "-ozqdq_switch",
|
|
1188
|
-
"sliderRow": "-ozqdq_sliderRow",
|
|
1189
|
-
"segLabel": "-ozqdq_segLabel",
|
|
1190
|
-
"sectionLabel": "-ozqdq_sectionLabel",
|
|
1191
|
-
"header": "-ozqdq_header",
|
|
1192
|
-
"clearBtn": "-ozqdq_clearBtn",
|
|
1193
1186
|
"preview": "-ozqdq_preview",
|
|
1187
|
+
"btnDanger": "-ozqdq_btnDanger",
|
|
1188
|
+
"section": "-ozqdq_section",
|
|
1194
1189
|
"previewGlassText": "-ozqdq_previewGlassText",
|
|
1195
|
-
"
|
|
1190
|
+
"previewGlass": "-ozqdq_previewGlass",
|
|
1191
|
+
"sliderValue": "-ozqdq_sliderValue",
|
|
1192
|
+
"sourceRow": "-ozqdq_sourceRow",
|
|
1193
|
+
"btn": "-ozqdq_btn",
|
|
1194
|
+
"sliderRow": "-ozqdq_sliderRow",
|
|
1196
1195
|
"segBtn": "-ozqdq_segBtn",
|
|
1197
|
-
"segActive": "-ozqdq_segActive",
|
|
1198
|
-
"titleRow": "-ozqdq_titleRow",
|
|
1199
|
-
"switchKnob": "-ozqdq_switchKnob",
|
|
1200
1196
|
"row": "-ozqdq_row",
|
|
1201
|
-
"
|
|
1202
|
-
"
|
|
1197
|
+
"title": "-ozqdq_title",
|
|
1198
|
+
"enabledBadge": "-ozqdq_enabledBadge",
|
|
1199
|
+
"sectionLabel": "-ozqdq_sectionLabel",
|
|
1200
|
+
"btnPrimary": "-ozqdq_btnPrimary",
|
|
1201
|
+
"clearRow": "-ozqdq_clearRow",
|
|
1202
|
+
"urlInput": "-ozqdq_urlInput",
|
|
1203
|
+
"clearBtn": "-ozqdq_clearBtn",
|
|
1204
|
+
"previewEmpty": "-ozqdq_previewEmpty"
|
|
1203
1205
|
};
|
|
1204
1206
|
//#endregion
|
|
1205
1207
|
//#region src/client/SettingsRow.tsx
|
|
@@ -1366,8 +1368,8 @@ window.__ModuleLoader__.load({
|
|
|
1366
1368
|
function BackgroundSettingsRow({ t }) {
|
|
1367
1369
|
const snapshot = (0, react.useSyncExternalStore)(settingsClient.subscribe, readSnapshot);
|
|
1368
1370
|
const navMode = (0, react.useSyncExternalStore)(subscribeTimelineMode, timelineMode);
|
|
1369
|
-
const timelineLabelKey = navMode === "
|
|
1370
|
-
const timelineHintKey = navMode === "enhance" ? "background.timelineEnhanceHint" : "background.timelineHint";
|
|
1371
|
+
const timelineLabelKey = navMode === "legacy" ? "background.timeline" : "background.timelineEnhance";
|
|
1372
|
+
const timelineHintKey = navMode === "narrow" ? "background.timelineEnhanceNarrowHint" : navMode === "enhance" ? "background.timelineEnhanceHint" : "background.timelineHint";
|
|
1371
1373
|
const [draft, setDraft] = (0, react.useState)(DEFAULTS);
|
|
1372
1374
|
const [uploading, setUploading] = (0, react.useState)(false);
|
|
1373
1375
|
const [urlText, setUrlText] = (0, react.useState)("");
|
|
@@ -2032,7 +2034,8 @@ window.__ModuleLoader__.load({
|
|
|
2032
2034
|
for (;;) {
|
|
2033
2035
|
if (rowRenderedInDom(anchorKey)) break;
|
|
2034
2036
|
const snapshot = session.getSnapshot();
|
|
2035
|
-
|
|
2037
|
+
const keyedNodeOf = (store) => store !== null && typeof store === "object" && typeof store.get === "function" ? store.get(anchorKey) : void 0;
|
|
2038
|
+
if ((keyedNodeOf(snapshot?.chat?.nodes) ?? keyedNodeOf(snapshot?.nodes)) !== void 0) break;
|
|
2036
2039
|
if (snapshot?.hasMore !== true) break;
|
|
2037
2040
|
if (Date.now() >= deadline) break;
|
|
2038
2041
|
if (snapshot.loadingOlder === true) {
|
|
@@ -2133,9 +2136,11 @@ window.__ModuleLoader__.load({
|
|
|
2133
2136
|
return Math.max(2, Math.floor(usable / 10) + 1);
|
|
2134
2137
|
}
|
|
2135
2138
|
/**
|
|
2136
|
-
* Resolve the entry under a pointer Y — the official
|
|
2137
|
-
* (`itemAtPointer` in
|
|
2138
|
-
*
|
|
2139
|
+
* Resolve the entry under a pointer Y on the COMPRESSING rail — the official
|
|
2140
|
+
* alpha.1/2 geometry verbatim (`itemAtPointer` in that generation's
|
|
2141
|
+
* TurnNavigator.tsx), so this plugin lands on exactly the mark the official
|
|
2142
|
+
* rail would have picked. The legacy port (whose own rail never scrolls
|
|
2143
|
+
* internally) uses the same mapping, where ratio and fixed pitch coincide.
|
|
2139
2144
|
* @param count - number of entries the rail shows.
|
|
2140
2145
|
* @param rail - the rail element.
|
|
2141
2146
|
* @param clientY - pointer viewport Y.
|
|
@@ -2148,11 +2153,45 @@ window.__ModuleLoader__.load({
|
|
|
2148
2153
|
return Math.round(ratio * (count - 1));
|
|
2149
2154
|
}
|
|
2150
2155
|
/**
|
|
2156
|
+
* Whether `rail` is the frame-style rail (dsh >= 0.1.2-alpha.3): marks sit
|
|
2157
|
+
* at a fixed pitch and scroll INSIDE the frame, whose scroller offset the
|
|
2158
|
+
* component publishes inline as `--turn-scroll-top` — every render, resting
|
|
2159
|
+
* at `0px` included. The compressing alpha.1/2 rail never publishes that
|
|
2160
|
+
* property, so its presence is the generation test. The legacy port paints
|
|
2161
|
+
* its own `--dsbt-*` properties and can never trip this.
|
|
2162
|
+
* @param rail - the rail element (or null).
|
|
2163
|
+
*/
|
|
2164
|
+
function isFrameRail(rail) {
|
|
2165
|
+
return rail !== null && rail.style.getPropertyValue("--turn-scroll-top") !== "";
|
|
2166
|
+
}
|
|
2167
|
+
/**
|
|
2168
|
+
* Resolve the entry under a pointer Y on the frame-style rail — the official
|
|
2169
|
+
* alpha.3 geometry verbatim (`itemAtPointer` in that generation's
|
|
2170
|
+
* TurnNavigator.tsx): marks live in the scrolled ladder at a fixed 10px
|
|
2171
|
+
* pitch, so the click offset adds the scroller offset. Read off the published
|
|
2172
|
+
* `--turn-scroll-top` var rather than the scroller element itself — the same
|
|
2173
|
+
* metric the official preview positioning uses, so any one-frame render lag
|
|
2174
|
+
* affects both identically.
|
|
2175
|
+
* @param count - number of entries the rail shows.
|
|
2176
|
+
* @param rail - the rail element.
|
|
2177
|
+
* @param clientY - pointer viewport Y.
|
|
2178
|
+
*/
|
|
2179
|
+
function frameIndexAtPointer(count, rail, clientY) {
|
|
2180
|
+
const rect = rail.getBoundingClientRect();
|
|
2181
|
+
const inset = railInsetOf(rail);
|
|
2182
|
+
const raw = Number.parseFloat(rail.style.getPropertyValue("--turn-scroll-top"));
|
|
2183
|
+
const scrollTop = Number.isFinite(raw) ? raw : 0;
|
|
2184
|
+
const offset = clientY - rect.top + scrollTop - inset;
|
|
2185
|
+
return Math.max(0, Math.min(count - 1, Math.round(offset / 10)));
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2151
2188
|
* Resolve the gesture that produced a rail click to an entry index.
|
|
2152
2189
|
*
|
|
2153
2190
|
* Keyboard activation targets the focused mark button (recovered by its
|
|
2154
2191
|
* position among the rail's buttons); a mouse click carries coordinates and
|
|
2155
|
-
* resolves through the rail geometry
|
|
2192
|
+
* resolves through the rail geometry — the frame-style alpha.3 mapping when
|
|
2193
|
+
* the rail publishes `--turn-scroll-top`, the compressing alpha.1/2 mapping
|
|
2194
|
+
* otherwise. Returns -1 when neither applies.
|
|
2156
2195
|
*
|
|
2157
2196
|
* Assumption (both frontends): the mark ticks are the ONLY buttons under the
|
|
2158
2197
|
* rail — the official rail renders marks exclusively and its preview tooltip
|
|
@@ -2172,7 +2211,8 @@ window.__ModuleLoader__.load({
|
|
|
2172
2211
|
}
|
|
2173
2212
|
}
|
|
2174
2213
|
const count = rail.querySelectorAll("button").length;
|
|
2175
|
-
|
|
2214
|
+
if (count === 0) return -1;
|
|
2215
|
+
return isFrameRail(rail) ? frameIndexAtPointer(count, rail, event.clientY) : indexAtPointer(count, rail, event.clientY);
|
|
2176
2216
|
}
|
|
2177
2217
|
/**
|
|
2178
2218
|
* Validate the official navigation array crossing the slot boundary. The
|
|
@@ -2200,6 +2240,63 @@ window.__ModuleLoader__.load({
|
|
|
2200
2240
|
}
|
|
2201
2241
|
return out;
|
|
2202
2242
|
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Validate one `turnOutline` wire value into its entries. The value is the
|
|
2245
|
+
* host projection's wire view (an array of `{ turn, seq, prompt, response }`
|
|
2246
|
+
* — see `dsh-session-turn-outline`) read through `useProjection`, so it may
|
|
2247
|
+
* be absent (pre-baseline, or an older kernel that never registers the key —
|
|
2248
|
+
* which reads `undefined`), partial, or damaged. `turn` and `seq` are the
|
|
2249
|
+
* load-bearing fields — a mark cannot exist or page without them — and a
|
|
2250
|
+
* damaged one drops the entry; the previews are decorative and degrade to ''.
|
|
2251
|
+
* @param value - the raw `useProjection('turnOutline')` value.
|
|
2252
|
+
*/
|
|
2253
|
+
function normalizeOutlineItems(value) {
|
|
2254
|
+
if (!Array.isArray(value)) return [];
|
|
2255
|
+
const out = [];
|
|
2256
|
+
for (const item of value) {
|
|
2257
|
+
if (item === null || typeof item !== "object") continue;
|
|
2258
|
+
const rec = item;
|
|
2259
|
+
if (typeof rec.turn !== "number" || !Number.isSafeInteger(rec.turn) || rec.turn < 0) continue;
|
|
2260
|
+
if (typeof rec.seq !== "number" || !Number.isSafeInteger(rec.seq) || rec.seq < 0) continue;
|
|
2261
|
+
out.push({
|
|
2262
|
+
turn: rec.turn,
|
|
2263
|
+
seq: rec.seq,
|
|
2264
|
+
prompt: typeof rec.prompt === "string" ? rec.prompt : "",
|
|
2265
|
+
response: typeof rec.response === "string" ? rec.response : ""
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
return out;
|
|
2269
|
+
}
|
|
2270
|
+
/**
|
|
2271
|
+
* Rebuild the rail's full mark ladder exactly as the official ChatView does
|
|
2272
|
+
* (a mirror of `mergeTurnRailItems` in dsh-client-ui-chat): outline entries
|
|
2273
|
+
* seed every known turn as unloaded, loaded items then override with their
|
|
2274
|
+
* anchor key and richer previews, ascending by turn. The enhancer maps a
|
|
2275
|
+
* click to a mark by INDEX, so its ladder must match the RENDERED marks —
|
|
2276
|
+
* which are exactly this merge on an alpha.3 kernel; without the outline
|
|
2277
|
+
* value the merge degrades to the loaded items (the alpha.1/2 ladder).
|
|
2278
|
+
* @param loaded - the validated loaded-window items.
|
|
2279
|
+
* @param outline - the raw turnOutline wire value.
|
|
2280
|
+
*/
|
|
2281
|
+
function mergeRailItems(loaded, outline) {
|
|
2282
|
+
const byTurn = /* @__PURE__ */ new Map();
|
|
2283
|
+
for (const entry of normalizeOutlineItems(outline)) byTurn.set(entry.turn, {
|
|
2284
|
+
turn: entry.turn,
|
|
2285
|
+
prompt: entry.prompt,
|
|
2286
|
+
response: entry.response
|
|
2287
|
+
});
|
|
2288
|
+
for (const item of loaded) {
|
|
2289
|
+
const preview = byTurn.get(item.turn);
|
|
2290
|
+
byTurn.set(item.turn, {
|
|
2291
|
+
turn: item.turn,
|
|
2292
|
+
prompt: item.prompt !== "" ? item.prompt : preview?.prompt ?? "",
|
|
2293
|
+
response: item.response !== "" ? item.response : preview?.response ?? "",
|
|
2294
|
+
anchorKey: item.anchorKey
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
if (byTurn.size === 0) return [];
|
|
2298
|
+
return [...byTurn.values()].sort((left, right) => left.turn - right.turn);
|
|
2299
|
+
}
|
|
2203
2300
|
//#endregion
|
|
2204
2301
|
//#region src/client/timeline/source.ts
|
|
2205
2302
|
/**
|
|
@@ -2935,18 +3032,31 @@ window.__ModuleLoader__.load({
|
|
|
2935
3032
|
* turn navigator (dsh >= 0.1.2, `TurnNavigator` in dsh-client-ui-chat).
|
|
2936
3033
|
*
|
|
2937
3034
|
* This component renders NOTHING. It borrows the official rail exactly as the
|
|
2938
|
-
* kernel painted it and fixes
|
|
3035
|
+
* kernel painted it and fixes its behaviours, scoped to what the running rail
|
|
3036
|
+
* generation still lacks (told apart by capability — see isFrameRail — never
|
|
3037
|
+
* by version compare):
|
|
2939
3038
|
*
|
|
2940
|
-
* 1. Smooth jump. The official `navigateToTurn` is one
|
|
2941
|
-
* (`el.scrollTop += flowTop(row, el) - 24`); a click across a
|
|
2942
|
-
* transcript teleports. Clicks are intercepted in the
|
|
2943
|
-
* re-run through the shared glide instead.
|
|
2944
|
-
*
|
|
2945
|
-
*
|
|
2946
|
-
*
|
|
2947
|
-
*
|
|
2948
|
-
*
|
|
2949
|
-
*
|
|
3039
|
+
* 1. Smooth jump (every generation). The official `navigateToTurn` is one
|
|
3040
|
+
* assignment (`el.scrollTop += flowTop(row, el) - 24`); a click across a
|
|
3041
|
+
* long transcript teleports. Clicks on LOADED marks are intercepted in the
|
|
3042
|
+
* capture phase and re-run through the shared glide instead. Since
|
|
3043
|
+
* 0.1.2-alpha.3 the rail also renders marks for turns OUTSIDE the loaded
|
|
3044
|
+
* window (fed by the whole-log `turnOutline` projection); those jumps
|
|
3045
|
+
* page history through the kernel's own loadThrough machinery — marks
|
|
3046
|
+
* without an anchor key are left to it, and the click index is resolved
|
|
3047
|
+
* against the same merged ladder the kernel renders. While one of those
|
|
3048
|
+
* kernel jumps is still paging (a mark pulses `aria-busy`), the plugin
|
|
3049
|
+
* stands down entirely: the kernel's loaded branch cancels its own pending
|
|
3050
|
+
* jump before landing, and an interception would bypass that cancellation.
|
|
3051
|
+
* 2. Reachable history (compressing rail only — dsh 0.1.2-alpha.1/2). That
|
|
3052
|
+
* rail only lists turns the client has already loaded, so anything behind
|
|
3053
|
+
* the "load earlier" button has no mark at all. Hovering or focusing the
|
|
3054
|
+
* rail pages older history in — only up to the rail's own uncompressed
|
|
3055
|
+
* capacity, so the marks never degrade into a solid bar — with the scroll
|
|
3056
|
+
* position compensated so the reader's view never moves. The alpha.3
|
|
3057
|
+
* frame-style rail lists every turn already and pages on demand itself, so
|
|
3058
|
+
* warming there would fight the kernel's pager for nothing and does not
|
|
3059
|
+
* run.
|
|
2950
3060
|
*
|
|
2951
3061
|
* State-ownership note (why intercepting the official click loses nothing):
|
|
2952
3062
|
* the kernel's scrollport (`data-conversation-scroll`, ChatView's
|
|
@@ -2972,7 +3082,12 @@ window.__ModuleLoader__.load({
|
|
|
2972
3082
|
* Nothing here touches the official DOM: no nodes are injected into a
|
|
2973
3083
|
* React-owned subtree and no official class or paint is overridden.
|
|
2974
3084
|
*/
|
|
2975
|
-
/**
|
|
3085
|
+
/**
|
|
3086
|
+
* Structural anchor of the official rail — its inline style carries the rail's
|
|
3087
|
+
* own metrics, which BOTH generations publish (`--turn-natural-height`), so
|
|
3088
|
+
* one selector finds either and the generation itself is told apart by
|
|
3089
|
+
* isFrameRail afterwards.
|
|
3090
|
+
*/
|
|
2976
3091
|
const OFFICIAL_RAIL_SELECTOR = "[data-conversation-scroll] nav[style*=\"--turn-natural-height\"]";
|
|
2977
3092
|
/**
|
|
2978
3093
|
* Rails claimed by an enhancer instance, keyed by the owning instance's
|
|
@@ -2988,20 +3103,26 @@ window.__ModuleLoader__.load({
|
|
|
2988
3103
|
/** How often the official rail is looked for (it mounts with the chat view). */
|
|
2989
3104
|
const RAIL_POLL_MS = 400;
|
|
2990
3105
|
/**
|
|
2991
|
-
* The enhancer: no visual output, only the
|
|
3106
|
+
* The enhancer: no visual output, only the behaviour fixes the running rail
|
|
3107
|
+
* generation lacks.
|
|
2992
3108
|
* @param props - dock slot props plus the enabled gate.
|
|
2993
3109
|
* @returns null (always).
|
|
2994
3110
|
*/
|
|
2995
3111
|
function OfficialTimelineEnhancer(props) {
|
|
2996
|
-
const { sessionId, sessionsService, useChat, enabled } = props;
|
|
3112
|
+
const { sessionId, sessionsService, useChat, useProjection, enabled } = props;
|
|
2997
3113
|
const items = normalizeNavigationItems(useChat === void 0 ? void 0 : useChat((snapshot) => {
|
|
2998
3114
|
const nav = snapshot?.navigation;
|
|
2999
3115
|
return typeof nav?.items === "function" ? nav.items() : void 0;
|
|
3000
3116
|
}));
|
|
3117
|
+
const ladder = mergeRailItems(items, useProjection === void 0 ? void 0 : useProjection("turnOutline"));
|
|
3001
3118
|
const session = sessionId !== void 0 && sessionsService !== void 0 ? sessionsService.binding(sessionId)?.session : void 0;
|
|
3002
3119
|
const [rail, setRail] = react.default.useState(null);
|
|
3120
|
+
/** Frame-style rail (dsh >= 0.1.2-alpha.3): decides the warm-up's presence. */
|
|
3121
|
+
const [narrow, setNarrow] = react.default.useState(false);
|
|
3003
3122
|
const itemsRef = react.default.useRef(items);
|
|
3004
3123
|
itemsRef.current = items;
|
|
3124
|
+
const ladderRef = react.default.useRef(ladder);
|
|
3125
|
+
ladderRef.current = ladder;
|
|
3005
3126
|
const railRef = react.default.useRef(rail);
|
|
3006
3127
|
railRef.current = rail;
|
|
3007
3128
|
const aliveRef = react.default.useRef(true);
|
|
@@ -3029,6 +3150,8 @@ window.__ModuleLoader__.load({
|
|
|
3029
3150
|
}
|
|
3030
3151
|
claimedRails.set(found, claimToken);
|
|
3031
3152
|
setRail(found);
|
|
3153
|
+
setNarrow(isFrameRail(found));
|
|
3154
|
+
reportTimelineMode(isFrameRail(found) ? "narrow" : "enhance");
|
|
3032
3155
|
};
|
|
3033
3156
|
check();
|
|
3034
3157
|
const timer = window.setInterval(check, RAIL_POLL_MS);
|
|
@@ -3038,6 +3161,7 @@ window.__ModuleLoader__.load({
|
|
|
3038
3161
|
};
|
|
3039
3162
|
}, [claimToken]);
|
|
3040
3163
|
const navigate = react.default.useCallback((item) => {
|
|
3164
|
+
if (item.anchorKey === void 0) return;
|
|
3041
3165
|
if (sessionsService === void 0 || sessionId === void 0) return;
|
|
3042
3166
|
const scrollport = rail === null ? null : rail.closest("[data-conversation-scroll]");
|
|
3043
3167
|
jumpToMessage(sessionsService, sessionId, item.anchorKey, {
|
|
@@ -3075,9 +3199,10 @@ window.__ModuleLoader__.load({
|
|
|
3075
3199
|
react.default.useEffect(() => {
|
|
3076
3200
|
if (!enabled || rail === null) return;
|
|
3077
3201
|
const onClick = (event) => {
|
|
3202
|
+
if (rail.querySelector("[aria-busy=\"true\"]") !== null) return;
|
|
3078
3203
|
const index = indexForEvent(rail, event);
|
|
3079
|
-
const item =
|
|
3080
|
-
if (item === void 0) return;
|
|
3204
|
+
const item = ladderRef.current[index];
|
|
3205
|
+
if (item === void 0 || item.anchorKey === void 0) return;
|
|
3081
3206
|
event.preventDefault();
|
|
3082
3207
|
event.stopImmediatePropagation();
|
|
3083
3208
|
navigate(item);
|
|
@@ -3092,7 +3217,7 @@ window.__ModuleLoader__.load({
|
|
|
3092
3217
|
navigate
|
|
3093
3218
|
]);
|
|
3094
3219
|
react.default.useEffect(() => {
|
|
3095
|
-
if (!enabled || rail === null) return;
|
|
3220
|
+
if (!enabled || narrow || rail === null) return;
|
|
3096
3221
|
const start = () => {
|
|
3097
3222
|
warm();
|
|
3098
3223
|
};
|
|
@@ -3104,6 +3229,7 @@ window.__ModuleLoader__.load({
|
|
|
3104
3229
|
};
|
|
3105
3230
|
}, [
|
|
3106
3231
|
enabled,
|
|
3232
|
+
narrow,
|
|
3107
3233
|
rail,
|
|
3108
3234
|
warm
|
|
3109
3235
|
]);
|