dsh-smooth-stream 0.4.2 → 0.4.3

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,144 +1,140 @@
1
- # dsh-smooth-stream — a silky-smooth streaming renderer
1
+ # dsh-smooth-stream
2
2
 
3
- > No more frame jumps in AI streaming output. **Silky streaming · level-free scrolling · 0-reflow follow · 0-jank reveal** — a streaming rendering plugin for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) Web UI.
3
+ > **Transform jumpy AI outputs into a calm, teleprompter-smooth reading experience.**
4
+ > Physics-based stream rendering and zero-reflow viewport tracking for the DeepSeek Harness (`dsh`) Web UI.
4
5
 
5
- English | [中文](README.md)
6
+ English · [中文](README.md) · [Homepage](https://laplace-bit.github.io/dsh-smooth-stream/) · [How It Works](https://laplace-bit.github.io/dsh-smooth-stream/how-it-works.html) · [npm](https://www.npmjs.com/package/dsh-smooth-stream)
6
7
 
7
- Model output arrives in bursts: one chunk can deliver hundreds of characters within a few milliseconds, the next one 200 ms later. Bind the renderer directly to that stream and you see the two classic failures — **text pops out in whole paragraphs, or every frame relayouts and the page judders**. dsh-smooth-stream splits rendering and scrolling into two engines, each maintains continuous state integrated once per frame, and a single ref couples them: **no step is ever discrete; everything evolves continuously frame by frame**.
8
+ ---
8
9
 
9
- Project homepage: <https://laplace-bit.github.io/dsh-smooth-stream/>
10
+ ## The Silky Smooth Feel
10
11
 
11
- [Install guide](https://laplace-bit.github.io/dsh-smooth-stream/install.html) · [How it works & reproducible benchmark](https://laplace-bit.github.io/dsh-smooth-stream/how-it-works.html) · [npm](https://www.npmjs.com/package/dsh-smooth-stream)
12
+ Whether reviewing hundreds of lines of complex reasoning or watching fast-paced code generation, `dsh-smooth-stream` delivers a **calm, continuous, and fatigue-free** reading experience:
12
13
 
13
- ## Why it feels smooth: three zeros
14
+ - **Organic, fluid text expansion**: No more walls of text abruptly snapping onto your screen. Words glide in with an organic rhythm that feels alive yet unhurried.
15
+ - **Effortless eye tracking**: The viewport glides as if on a precision-damped rail. Line wraps and code blocks no longer jar your eyes, eliminating cognitive friction.
16
+ - **Adaptive cadence**: Leisurely during slow arrivals, smoothly accelerating during high-volume bursts—keeping your screen composed no matter how fast tokens arrive.
14
17
 
15
- - **0-reflow follow.** The follow path writes only `transform` (compositor layer) with `will-change` prepared up front — it never writes `top/left/width` frame by frame. Layout stays out of the loop, so the scroll compensation never triggers a reflow storm. That is why long replies stay smooth and keep following the tail.
16
- - **0-jank reveal and wrap.** Reveal speed adapts to queue pressure — measured for slow output, catching up with fast output — and any single-frame visual displacement from a wrap or new block is capped at **≤8px** (`FOLLOW_PAINT_SHIFT_MAX_STEP_PX`). Even high-speed output never jumps.
17
- - **0-burst completion.** When a turn finishes, the queue drains at one fixed velocity: the ending does not "dump", it does not grab an extra frame, and body / reasoning / tool output hand off cleanly.
18
+ ---
18
19
 
19
- The animation path runs entirely on the compositor (`transform`/`opacity`), so the main thread is never repainting every frame; the experience also yields automatically to `prefers-reduced-motion` and low frame rates (<30 fps).
20
+ ## Why this exists
20
21
 
21
- ## Level-free scrolling: position is integrated, not snapped
22
+ Large language models emit tokens in discrete network bursts: hundreds of characters can arrive within milliseconds, followed by tens of milliseconds of silence.
22
23
 
23
- The default approach hammers `scrollTop` to the bottom on every growth event — one hard jump per write; even `scroll-behavior: smooth` restarts its easing curve on every write, so the easing never finishes and a fast stream reads like a stuttering slow-motion. dsh-smooth-stream instead is **level-free**:
24
+ Traditional chat UIs bind DOM rendering and scrolling directly to arrival events, causing two jarring failure modes:
25
+ 1. **Visual snapping**: Text blocks, tables, and code snippets pop in abruptly, forcing the reader's eye to constantly re-acquire focus.
26
+ 2. **Scroll jitter and reflow storms**: Hard `scrollTop = scrollHeight` jumps or interrupted `scroll-behavior: smooth` animations restart their easing curves on every chunk, leading to sluggish lag and severe layout thrashing.
24
27
 
25
- - The **reveal engine** decides "how much to reveal this frame" from backlog pressure, carrying fractional character debt between frames.
26
- - The **follow engine** holds the current height and the rendered position in a damped spring (**stiffness/damping/mass ≈ 130/24/1**) integrated every frame; wraps, code blocks, tables, and tool results all feed one continuous trajectory.
27
- - After a long main-thread stall, physical time is clamped instead of replaying the whole missed interval in a single paint — so the settle time is nearly identical on 60 Hz and 120 Hz: **refresh-rate-independent feel**.
28
+ `dsh-smooth-stream` decouples **text reveal cadence** from **viewport motion** into two independent dynamical systems, integrating them per animation frame (`requestAnimationFrame`) to ensure uninterrupted continuity.
28
29
 
29
- ## Preview
30
+ ---
31
+
32
+ ## Architecture & Mechanics
33
+
34
+ ```
35
+ [ Model SSE Stream ]
36
+ │
37
+ ▼
38
+ ┌─────────────────┐ Backpressure Damping (0.55x ~ 1.0x) ┌─────────────────┐
39
+ │ Reveal Engine │ ◄────────────────────────────────────────────── │ Follow Engine │
40
+ └────────┬────────┘ └────────┬────────┘
41
+ │ Fractional character debt integration │ 2nd-order damped spring (k=130, c=24)
42
+ ▼ ▼
43
+ [ Progressive DOM Reveal ] ────────────────────────────────────────► [ GPU Compositor Transform ]
44
+ (Per-frame visual delta ≤ 8px) (Zero Reflow / Pure Composite)
45
+ ```
46
+
47
+ ### 1. Dynamic Adaptive Reveal Engine
48
+ - **Fractional character debt**: Evaluates reveal velocity from current backlog ($v = 90 + \text{backlog}^{1.25} \times P$). Leisurely when arrival is slow; accelerates smoothly during bursts without ever dumping text walls.
49
+ - **Wrap smoothing**: Caps per-frame visual displacement to $\le 8\text{px}$ during line wraps and new block arrivals, spreading sudden $24\text{--}28\text{px}$ layout steps across several frames.
50
+ - **Uniform completion drain**: When the generation finishes, the residual queue drains at a steady speed, creating clean transitions between body, reasoning, and tool calls.
51
+
52
+ ### 2. GPU-Driven Damped Spring Follower
53
+ - **Second-order spring physics**: Uses a sub-stepped damped spring ($k=130, c=24, m=1$) to convert discrete height changes into a continuous trajectory.
54
+ - **Zero-reflow viewport tracking**: Keeps the real scrollport pinned to the bottom while absorbing residual visual lag entirely via `transform: translate3d` on the message container. No layout-triggering properties are touched during follow.
55
+ - **Closed-loop backpressure**: If visual lag fills the predictive runway ($\approx 72\text{px}$), the follower throttles reveal speed (down to $0.55\times$), ensuring text expansion never outpaces the viewport spring.
56
+ - **Stall resilience & ProMotion parity**: Clamps elapsed physical time ($\Delta t \le 32\text{ms}$) during main-thread stalls to prevent teleporting catches. Settling dynamics are identical across 60Hz and 120Hz (ProMotion) displays.
57
+
58
+ ### 3. Turn Lifecycle Auto-Collapse
59
+ - Reasoning and tool executions remain expanded while streaming.
60
+ - Once a turn settles, intermediate processes cleanly fold behind a minimalist `Processed in Xs` summary row, keeping the conversation view focused on final answers.
61
+
62
+ ---
63
+
64
+ ## Visual Comparison
30
65
 
31
66
  Left: default Web UI. Right: dsh-smooth-stream.
32
67
 
33
- ![Left: without the plugin. Right: with dsh-smooth-stream.](docs/compare.gif)
68
+ ![Left: default Web UI. Right: dsh-smooth-stream.](docs/compare.gif)
34
69
 
35
- ## Core experience
70
+ ---
36
71
 
37
- - **Fluid rendering.** Text appears as it arrives while Markdown structure stays active, keeping headings, lists, code blocks, and tables readable throughout the stream.
38
- - **Silky scrolling.** As the content grows, the page follows one continuous scroll path and keeps the reader close to the generated output.
39
- - **Consistent transitions.** Line wraps, code blocks, tables, and tool results use the same motion treatment, so text, reasoning, and tools flow together.
40
- - **Adaptive cadence.** Reveal speed responds to arrival rate and pending content, staying measured for slow output and catching up with fast output.
72
+ ## Performance & Test Benchmarks
41
73
 
42
- ## Reproducible benchmark
74
+ Verified by local browser-level audit suites:
43
75
 
44
- "Silky" is not a marketing adjective here; it is a number you can reproduce. The repo ships a browser-level audit harness with gates that run locally (`run-render-audit`, `verify-overflow`, `probe-tailbob`). Full green before every release:
76
+ | Gate | Command | Passing Standard |
77
+ | :--- | :--- | :--- |
78
+ | **Stream Render Audit** | `node scripts/run-render-audit.mjs` | 10/10 clean across 5 streaming patterns; zero regressions |
79
+ | **Overflow & Rebound Gate** | `node scripts/verify-overflow.mjs` | Zero over-scroll, zero bounce under burst load |
80
+ | **Tail Vibration Probe** | `pnpm test` | Single-frame shift $\le 30\text{px}$, 7-frame amplitude $\le 32\text{px}$ |
45
81
 
46
- | Gate | Result |
47
- | --- | --- |
48
- | Render audit `run-render-audit` (5 streaming scenarios) | **10/10 clean**, zero movement regression |
49
- | Overflow gate `verify-overflow` | **3/3** no over-scroll, no rebound |
50
- | Tail smoothing `probe-tailbob` | single-frame ≤30px, 7-frame amplitude ≤32px **PASS** |
51
- | Inline-code wrap `probe:overlap` | Issue #13 fixture across static/reveal/engine arms **PASS** |
82
+ - Core ESM bundle is approximately **4.7 kB** (gzipped). See [How It Works](https://laplace-bit.github.io/dsh-smooth-stream/how-it-works.html) for full benchmarks.
52
83
 
53
- Core ESM output gzip ≈ **4.7 kB**. Details in [How it works & benchmark](https://laplace-bit.github.io/dsh-smooth-stream/how-it-works.html).
84
+ ---
54
85
 
55
- ## Install
86
+ ## Quick Start
56
87
 
57
- From a DeepSeek Harness source checkout:
88
+ ### Installation
89
+
90
+ Inside your DeepSeek Harness repository checkout:
58
91
 
59
92
  ```sh
60
93
  pnpm dsh plugin --profile web add dsh-smooth-stream
61
94
  ```
62
95
 
63
- If `dsh` is already on your `PATH`:
96
+ If `dsh` is in your system `PATH`:
64
97
 
65
98
  ```sh
66
99
  dsh plugin --profile web add dsh-smooth-stream
67
100
  ```
68
101
 
69
- The npm package ships prebuilt `lib/`, so no pnpm ≥10 build-script allowance is needed.
70
-
71
- Start the UI:
102
+ Start the interface:
72
103
 
73
104
  ```sh
74
105
  pnpm dsh web
75
106
  ```
76
107
 
77
- The Host log should include `[dsh-smooth-stream] plugin loaded!`.
78
-
79
- Remove it with `pnpm dsh plugin --profile web remove dsh-smooth-stream` (or `dsh plugin --profile web remove dsh-smooth-stream`).
80
-
81
- ## Configuration
82
-
83
- The bundle installs with `preset: balanced`. Change it in the profile `cordis.patch.yml` if you want a different cadence:
108
+ Verify that `[dsh-smooth-stream] plugin loaded!` appears in the host startup logs.
84
109
 
85
- | `preset` | Feel |
86
- | --- | --- |
87
- | `realtime` | Keeps closer to the model |
88
- | `balanced` | Default |
89
- | `silky` | More buffer, slower catch-up |
110
+ To uninstall: `pnpm dsh plugin --profile web remove dsh-smooth-stream`.
90
111
 
91
- Legacy `mode`, `revealCharsPerSec`, `scrollSpeedPxPerSec`, and `maxScrollSpeedPxPerSec` fields are still accepted so existing profiles keep loading; the current adaptive engine uses only `preset` to tune cadence.
112
+ ---
92
113
 
93
- ## User settings
114
+ ## Presets & Configuration
94
115
 
95
- In the Web UI, open **Settings → Plugins → Plugin configuration** to find a **Smooth stream** card with:
116
+ The plugin defaults to `preset: balanced`. You can tune the cadence in your profile's `cordis.patch.yml`:
96
117
 
97
- - **Enable smooth streaming** (on by default): lets this plugin own reply and tool-row rendering and follow. Turn it off to return rendering completely to the built-in Harness UI.
98
- - **Auto-expand thinking**: controls whether reasoning opens while it streams. This preference has no effect while the master toggle is off.
99
- - **Collapse finished work** (on by default): once a turn finishes processing, its thinking, tool calls, context injection, and intermediate output fold behind one "Processed" summary row so only the final answer shows. Click the summary any time to expand or re-collapse the full process.
100
- - **Show render diagnostics** (off by default): opens a chat-side panel with live rendering, frame-rate, and scroll-follow measurements, plus controls for reveal and spring behavior.
118
+ | `preset` | Characteristics |
119
+ | :--- | :--- |
120
+ | `realtime` | Low buffer; closely follows model token arrival |
121
+ | `balanced` | Recommended default; balances smoothness and latency |
122
+ | `silky` | Generous buffer with gentler acceleration curves |
101
123
 
102
- With "Auto-expand thinking" on, reasoning blocks open while streaming and collapse when thinking ends. With it off, reasoning stays collapsed; you can still open a block by hand, and the stream state will not wrestle it back.
124
+ ---
103
125
 
104
- "Collapse finished work" never interferes while a reply streams — output expands live, and folding happens only after the turn settles with all work complete. The switch works independently of "Enable smooth streaming": folding applies whether this plugin or the built-in renderer owns the conversation. Plain replies without thinking or tools get no summary row. Content embedded by other plugins through fully custom tool views (such as `dsh-pianist`'s piano card) is never folded — only calls rendered as native tool cards participate. This feature supersedes the standalone `dsh-auto-collapse` plugin; do not run both at once, or they will fight over the same DOM nodes and overlap text.
126
+ ## User Preferences
105
127
 
106
- These are durable, user-level preferences that apply live without a restart, and are written to the DeepSeek Harness user-settings document rather than the plugin's composed configuration.
128
+ Open **Settings → Plugins → Plugin Configuration** in the Web UI:
107
129
 
108
- Diagnostics controls apply as you move them: reveal multiplier, queue pressure, maximum reveal rate, spring stiffness/damping/mass, predictive runway, runway response time, and minimum backpressure scale. The panel reports FPS, frame time, character backlog, effective reveal rate, progress, visual lag, scroll velocity, and available follow room. You can **Save** a combination, **Discard** unsaved edits, **Restore defaults**, or copy the current tuning and measurements. Turning diagnostics off immediately returns the renderer to its production defaults.
130
+ - **Enable smooth streaming** (default on): Toggles custom stream rendering and follow. Disabling instantly falls back to built-in Harness rendering.
131
+ - **Auto-expand thinking**: Controls whether reasoning opens automatically while streaming.
132
+ - **Collapse finished work** (default on): Folds thoughts and tool steps into a summary line once the turn settles.
133
+ - **Show render diagnostics** (default off): Opens a live HUD on the right side to inspect FPS, character backlog, spring state, and adjust physical parameters in real time.
109
134
 
110
- ## About & updates
111
-
112
- - **Version / homepage / license**: see the top of this page and the `version`, `homepage`, `repository`, and `license` fields in [package.json](package.json). Installed plugins are listed under **Settings → Plugins → All**.
113
- - **Updates**: the card shows the version loaded by the Host. When the active profile declares `dsh-smooth-stream` as an npm dependency, its **Update** button runs the same fixed package update for that profile and then asks you to restart Harness. A `link:` or `file:` development install is shown as a development version and deliberately leaves the button disabled, so it cannot replace your checkout.
114
-
115
- You can also update an npm-installed profile from the command line:
116
-
117
- ```sh
118
- dsh plugin --profile web update dsh-smooth-stream
119
- ```
120
-
121
- (`dsh plugin --profile web outdated` shows whether a newer version exists.)
122
-
123
- ## FAQ
124
-
125
- **Is this an official DeepSeek plugin?**
126
- No. It is independently maintained, MIT-licensed software for the DeepSeek Harness (`dsh`) Web UI and is not affiliated with DeepSeek.
127
-
128
- **Is "0 reflow" literal?**
129
- It refers to the animation path. The follow writes only a compositor `transform` and never reads or writes layout properties, so the follow phase causes no reflow/repaint storm; text reveal still writes character increments (the only layout that new content genuinely needs), but reveal cadence is capped so single-frame visual displacement stays ≤8px with no jumps.
130
-
131
- **How do I install a DeepSeek Harness plugin?**
132
- Use the built-in plugin command: `dsh plugin --profile web add dsh-smooth-stream` from a dsh source checkout (see [Install](#install)).
133
-
134
- **Can I install it from npm?**
135
- Yes — `dsh-smooth-stream` is published to [npm](https://www.npmjs.com/package/dsh-smooth-stream). `dsh plugin --profile web add dsh-smooth-stream` installs the prebuilt package.
136
-
137
- **Does it respect `prefers-reduced-motion`?**
138
- Yes. With reduced motion enabled the finished text is shown at once and the plugin does not take over follow. If the frame rate drops below 30 fps while the reply is off-screen, reveal pauses and catches up later.
139
-
140
- [![featured on dsh-suite](https://img.shields.io/badge/featured%20on-dsh--suite-4d6bfe)](https://whyihaveyou.github.io/dsh-suite/)
135
+ ---
141
136
 
142
137
  ## License
143
138
 
144
139
  [MIT](LICENSE)
140
+
package/README.md CHANGED
@@ -1,144 +1,139 @@
1
- # dsh-smooth-stream — 丝滑流式渲染引擎
1
+ # dsh-smooth-stream
2
2
 
3
- > AI 流式输出不再跳帧。**丝滑流式 · 无级滚动 · 0 重排跟随 · 0 跳变** —— 为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)Web UI 打造的流式渲染插件。
3
+ > **让 AI 的长篇生成如提词器般温润流淌。**
4
+ > 为 DeepSeek Harness(`dsh`)打造的二阶弹簧物理流式渲染与零重排跟随引擎。
4
5
 
5
- [English](README.en.md) | 中文
6
+ [English](README.en.md) · [项目主页](https://laplace-bit.github.io/dsh-smooth-stream/) · [工作原理与基准](https://laplace-bit.github.io/dsh-smooth-stream/how-it-works.html) · [npm](https://www.npmjs.com/package/dsh-smooth-stream)
6
7
 
7
- 大模型输出是分块到达的:一个块可能在几毫秒内带来几百个字符,下一个块 200ms 后才来。把渲染直接绑在这条流上,屏幕上的表现就是两种失败——**要么整段文字猛地冒出来,要么每一帧都在重排、页面跟着抖**。dsh-smooth-stream 把「渲染」和「滚动」拆成两个独立的引擎,各自维护一段连续状态、每帧积分一次,再用一个 ref 把它们耦合起来:**没有任何一步是跳变的,一切都在逐帧连续演化**。
8
+ ---
8
9
 
9
- 项目主页:<https://laplace-bit.github.io/dsh-smooth-stream/>
10
+ ## 极致丝滑的阅读手感
10
11
 
11
- [安装指南](https://laplace-bit.github.io/dsh-smooth-stream/install.html) · [工作原理与可复现基准](https://laplace-bit.github.io/dsh-smooth-stream/how-it-works.html) · [npm](https://www.npmjs.com/package/dsh-smooth-stream)
12
+ 无论是阅读数百行的长篇推演,还是紧盯高速吐字的代码生成,`dsh-smooth-stream` 带来的是一种**沉浸、连贯且零视觉负担**的阅读质感:
12
13
 
13
- ## 为什么丝滑:三个「0」
14
+ - **如水流般自然铺展**:告别大段文本突然“砸”在屏幕上的视觉压迫,字句如打字机般富有呼吸感地逐字涌现;
15
+ - **视线无需追赶跳动**:视口如同搭载了高精度阻尼滑轨,随文字增长平稳匀速推移,彻底终结换行时的突发踢移;
16
+ - **呼吸感与实时性的平衡**:慢速输出时从容优雅,高并发爆发时平稳追赶,无论模型吐字多快,画面始终从容自若。
14
17
 
15
- - **0 重排的滚动跟随。** 跟随路径只写 `transform`(合成器合成层)并预置 `will-change`,从不逐帧写 `top/left/width`。布局零参与,滚动补偿不会引发重排风暴——这也正是「越滚越跟手、长回复不抖」的原因。
16
- - **0 跳变的揭示与换行。** 揭示速度按队列积压自适应,慢速输出从容呈现、快速输出及时追上;换行和新块的单帧视觉位移被限幅到 ≤8px(`FOLLOW_PAINT_SHIFT_MAX_STEP_PX`),高速输出同样无跳帧。
17
- - **0 突进的完成收敛。** 回合结束时以固定速度匀速倾倒队列,结尾「不倾泻」、不多抓一帧,正文与思考/工具输出衔接干净。
18
+ ---
18
19
 
19
- 动画路径全程走合成器(`transform`/`opacity`),不触发主线程逐帧重绘;阅读体验对 `prefers-reduced-motion` 与低帧率(<30 fps)自动让步。
20
+ ## 为什么需要它?
20
21
 
21
- ## 无级滚动:位置是「积分」出来的,不是「拨」出来的
22
+ 大模型输出是通过网络分块到达的。一个数据包可能在几毫秒内送达数百字符,下一个分块却需要数十毫秒。
22
23
 
23
- 在默认实现里,每次内容变高就 `scrollTop` 拨到底——一次硬跳;哪怕用 `scroll-behavior: smooth`,浏览器也会对每次写入重启一次平滑动画,缓动曲线永远跑不完,高速流看起来像卡顿的慢放。dsh-smooth-stream 的解法是**无级(level-free)**:
24
+ 如果直接将 DOM 渲染和视口滚动绑定在离散的到达事件上,通常会引发两个阅读体验问题:
25
+ 1. **视觉跳跃与撕裂**:段落、代码块和表格整段突发呈现,视线被迫频繁重新寻焦;
26
+ 2. **滚动抖动与重排风暴**:依靠 `scrollTop = scrollHeight` 进行瞬时硬跳;即使用 CSS `scroll-behavior: smooth`,高频写入也会不断重置缓动曲线,导致动画永远无法收敛,高速流下伴随剧烈的主线程重排。
24
27
 
25
- - **揭示引擎**负责「每帧呈现多少新内容」,用积压压力调节速度,携带跨帧的小数字符债务。
26
- - **跟随引擎**用一个阻尼弹簧(stiffness/damping/mass ≈ **130/24/1**)持有当前高度与现实位移,逐帧积分;换行、代码块、表格、工具结果全都喂进同一条连续轨迹。
27
- - 主线程卡顿后物理时间被 clamp,而不是把错过的一整段间隔在一帧里补完——所以 60Hz 与 120Hz 下收束时间几乎一致,**帧率无关的手感**。
28
+ `dsh-smooth-stream` 将**内容呈现**与**视口运动**拆分为两个独立的物理状态机,在每一帧动画回调(rAF)中连续积分,彻底消除跳跃感。
28
29
 
29
- ## 效果
30
+ ---
31
+
32
+ ## 核心机理与架构
33
+
34
+ ```
35
+ [ 模型 SSE 分块流 ]
36
+ │
37
+ ▼
38
+ ┌─────────────────┐ 背压阻尼 (0.55x ~ 1.0x) ┌─────────────────┐
39
+ │ 揭示节奏引擎 │ ◄──────────────────────────────── │ 弹簧跟随引擎 │
40
+ │ (Reveal Engine) │ │ (Follow Engine) │
41
+ └────────┬────────┘ └────────┬────────┘
42
+ │ 字符积压与分数积分 │ 二阶阻尼弹簧积分 (k=130, c=24)
43
+ ▼ ▼
44
+ [ 逐帧平滑展开 DOM ] ───────────────────────────────► [ 合成层 Transform 补偿 ]
45
+ (单帧视觉位移 ≤ 8px) (0 Reflow / 纯 GPU 合成)
46
+ ```
47
+
48
+ ### 1. 动态自适应的揭示引擎(Reveal Engine)
49
+ - **分数级字符积分**:根据积压队列深度自适应调节速度($v = 90 + \text{backlog}^{1.25} \times P$),低负载时从容自然,高积压时平稳追赶,绝不倾泻整段文本。
50
+ - **折行限幅平滑**:长文本和代码块发生换行时,单帧视觉位移被限制在 8px 以内,将原本 24~28px 的单帧跳跃平摊至数帧完成。
51
+ - **尾部平稳收束**:回合结束标记到达后,残余缓冲区以恒定速率释放,正文、思考链与工具调用之间自然衔接。
52
+
53
+ ### 2. 纯合成层驱动的跟随引擎(Follow Engine)
54
+ - **二阶阻尼物理系统**:采用 $k=130, c=24, m=1$ 的亚步进物理弹簧,将内容高度的变化转化为平滑连续的速度与位移轨迹。
55
+ - **零重排(Zero-Reflow)位移补偿**:真实滚动容器始终锚定在底部,剩余的视觉滞后完全由外层 DOM 的 `transform: translate3d` 吸收。跟随过程不读写任何触发 Layout 的属性,杜绝重排风暴。
56
+ - **闭环背压控制(Closed-Loop Backpressure)**:当跟随滞后接近预留空间时,反向向揭示引擎施加阻尼(最低降至 0.55 倍速),防止文本增长超出视口弹簧范围。
57
+ - **掉帧自愈与跨刷新率一致**:主线程卡顿(Stall)时自动钳位物理时间($\le 32\text{ms}$),避免画面恢复后的突进瞬移;在 60Hz 与 120Hz(ProMotion)屏幕下拥有近乎一致的物理收敛时间。
58
+
59
+ ### 3. 会话生命周期自动折叠(Turn Auto-Collapse)
60
+ - 对话进行时,思考链与工具调用保持实时展开;
61
+ - 回合结算完成并稳定后,自动将执行过程收敛为一行极简的 `已处理 X 秒` 摘要,保持工作区专注;支持随时点击无缝展开。
62
+
63
+ ---
64
+
65
+ ## 效果对比
30
66
 
31
67
  左:默认 Web UI。右:dsh-smooth-stream。
32
68
 
33
69
  ![左:未使用插件。右:使用 dsh-smooth-stream。](docs/compare.gif)
34
70
 
35
- ## 核心体验
71
+ ---
36
72
 
37
- - **流畅渲染。** 文字边到边呈现,Markdown 结构持续更新,标题、列表、代码块和表格在流式过程中保持自然的阅读状态。
38
- - **丝滑滚动。** 内容逐步变高时,页面沿连续的滚动轨迹平稳跟随,视线始终贴着正在生成的内容。
39
- - **统一过渡。** 换行、代码块、表格和工具结果使用一致的过渡方式,正文、思考过程与工具输出衔接自然。
40
- - **自适应节奏。** 渲染速度会根据输出速度和待显示内容调整,让慢速输出从容呈现,快速输出及时跟上。
73
+ ## 性能与基准
41
74
 
42
- ## 可复现基准
75
+ 平滑度与稳定性基于本地自动化审计台架验证:
43
76
 
44
- 「丝滑」不是形容词,是可以用脚本复现的数值:仓库自带浏览器级审计台架,所有闸门本地可跑(`run-render-audit`、`verify-overflow`、`probe-tailbob`)。发布前全量绿灯:
77
+ | 验证闸门 | 测试项目 | 指标要求 |
78
+ | :--- | :--- | :--- |
79
+ | **流式渲染审计** | `node scripts/run-render-audit.mjs` | 5 种典型生成场景下 10/10 Clean,零位移回退 |
80
+ | **视口溢出闸门** | `node scripts/verify-overflow.mjs` | 极限输出场景下零过滚、零反弹 |
81
+ | **尾行晃动抑制** | `pnpm test` | 单帧跳变 ≤30px,7 帧振幅峰值 ≤32px |
45
82
 
46
- | 闸门 | 结果 |
47
- | --- | --- |
48
- | 渲染审计 `run-render-audit`(5 种流式场景) | **10/10 clean**,零位移突变、零回归 |
49
- | 溢出闸门 `verify-overflow` | **3/3** 无过滚、无回弹 |
50
- | 尾行平滑 `probe-tailbob` | 单帧位移 ≤30px、7 帧振幅 ≤32px **PASS** |
51
- | 行内代码折行 `probe:overlap` | Issue #13 fixture,静态/揭示/引擎三臂 **PASS** |
83
+ - 核心 ESM 产物经 Gzip 压缩后仅约 **4.7 kB**。详见[工作原理与基准](https://laplace-bit.github.io/dsh-smooth-stream/how-it-works.html)。
52
84
 
53
- 核心 ESM 产物 gzip 约 **4.7 kB**。详见[工作原理与基准](https://laplace-bit.github.io/dsh-smooth-stream/how-it-works.html)。
85
+ ---
54
86
 
55
- ## 安装
87
+ ## 安装与使用
56
88
 
57
- 在 DeepSeek Harness 源码仓库里:
89
+ 在 DeepSeek Harness 源码根目录运行:
58
90
 
59
91
  ```sh
60
92
  pnpm dsh plugin --profile web add dsh-smooth-stream
61
93
  ```
62
94
 
63
- 如果 `PATH` 上已经有 `dsh`:
95
+ 如果系统 `PATH` 中已有 `dsh`:
64
96
 
65
97
  ```sh
66
98
  dsh plugin --profile web add dsh-smooth-stream
67
99
  ```
68
100
 
69
- npm 包带预构建的 `lib/`,无需 pnpm ≥10 的构建脚本授权,直接可装。
70
-
71
101
  启动界面:
72
102
 
73
103
  ```sh
74
104
  pnpm dsh web
75
105
  ```
76
106
 
77
- Host 日志里应出现 `[dsh-smooth-stream] plugin loaded!`。
78
-
79
- 卸载:`pnpm dsh plugin --profile web remove dsh-smooth-stream`(或 `dsh plugin --profile web remove dsh-smooth-stream`)。
80
-
81
- ## 配置
82
-
83
- 组合包默认 `preset: balanced`。要换节拍,在 profile 的 `cordis.patch.yml` 里改:
84
-
85
- | `preset` | 手感 |
86
- | --- | --- |
87
- | `realtime` | 更贴模型到达 |
88
- | `balanced` | 默认 |
89
- | `silky` | 缓冲更大,追上更慢 |
107
+ Host 日志中显示 `[dsh-smooth-stream] plugin loaded!` 即表示已成功加载。
90
108
 
91
- 旧版的 `mode`、`revealCharsPerSec`、`scrollSpeedPxPerSec` 和 `maxScrollSpeedPxPerSec` 字段仍可被加载,以兼容已有 profile;当前自适应引擎仅使用 `preset` 调整节拍。
109
+ 卸载命令:`pnpm dsh plugin --profile web remove dsh-smooth-stream`。
92
110
 
93
- ## 用户设置
111
+ ---
94
112
 
95
- 在 Web 界面打开 **设置 → 插件 → 插件配置**,会看到一张 **丝滑流式(Smooth stream)** 卡片,其中包含:
113
+ ## 配置与手感预设
96
114
 
97
- - **启用丝滑流式渲染**(默认开启):开启时由本插件接管回复和工具行的渲染与跟随;关闭后会撤销接管,完整使用 Harness 内置渲染。
98
- - **自动展开思考**:控制思考块在流式期间是否自动展开。主开关关闭时此选项不会生效。
99
- - **完成后自动折叠**(默认开启):回合处理完成后,把思考过程、工具调用、上下文注入和中间输出折叠为一行「已处理 X秒」摘要,只展示最终回复;点击摘要可随时展开或收起完整工作过程。
100
- - **显示渲染调试面板**(默认关闭):在聊天页右侧显示实时渲染、帧率和滚动跟随数据,并开放流式揭示与滚动弹簧参数。
115
+ 插件默认采用 `preset: balanced`。如需切换手感,可在对应 profile 的 `cordis.patch.yml` 中修改:
101
116
 
102
- 「自动展开思考」开启时,思考块会在流式时自动展开、思考结束后收起;关闭后思考块保持折叠,仍可手动点开,且不会被流式状态抢回控制。
117
+ | `preset` | 动态特性 |
118
+ | :--- | :--- |
119
+ | `realtime` | 紧跟模型分块到达节奏,缓冲更小 |
120
+ | `balanced` | 默认推荐,兼顾阅读流畅度与响应延迟 |
121
+ | `silky` | 增大缓冲区,追赶更平缓柔和 |
103
122
 
104
- 「完成后自动折叠」在流式期间不干预——回复实时展开输出;只有回合结束(出现结束标记且全部工作完成)才折叠。该开关独立于「启用丝滑流式渲染」:无论由本插件还是内置渲染器负责对话,折叠都照常工作。纯文本回复(无思考、无工具)不会生成摘要行。其他插件通过自定义工具视图嵌入的内容(如 `dsh-pianist` 的钢琴卡片)不会被折叠——只有渲染为原生工具卡样式的调用才参与折叠。此功能取代外挂的 `dsh-auto-collapse` 插件,二者不要同时安装,否则会互相抢夺同一批 DOM 节点造成文字重叠。
123
+ ---
105
124
 
106
- 这些设置是用户级的持久化偏好,改完即生效,无需重启;会写进 DeepSeek Harness 的用户设置文档,而不是插件的组合配置。
125
+ ## 用户偏好设置
107
126
 
108
- 调试面板中的参数在拖动时实时生效,包括揭示倍率、队列压力、最大揭示速度、弹簧刚度/阻尼/质量、预测 runway、runway 响应时间和最低背压倍率。面板同时显示 FPS、帧耗时、积压字符、实际揭示速度、渲染进度、视觉滞后、滚动速度和可用跟随空间;可随时**保存**当前组合、**放弃**未保存修改、**恢复默认**,或复制当前参数与指标。关闭调试开关后,渲染引擎立即恢复正式默认参数。
127
+ 在 Web 界面打开 **设置 → 插件 → 插件配置**,可对 **丝滑流式(Smooth stream)** 卡片进行个性化调节:
109
128
 
110
- ## 关于与更新
129
+ - **启用丝滑流式渲染**(默认开启):接管回复和工具行的渲染与跟随;关闭后即时恢复 Harness 内置渲染。
130
+ - **自动展开思考**:流式生成期间是否自动展开思考过程。
131
+ - **完成后自动折叠**(默认开启):回合处理完成后,将思考过程与工具调用折叠为摘要行。
132
+ - **显示渲染调试面板**(默认关闭):在界面右侧开启实时 HUD,观测 FPS、字符积压、弹性曲线并微调物理参数。
111
133
 
112
- - **版本 / 主页 / 许可证**:见本页顶部与 [package.json](package.json) 的 `version`、`homepage`、`repository`、`license` 字段;安装的插件列表可在 **设置 → 插件 → 全部** 里查看。
113
- - **更新**:卡片会显示 Host 当前加载的版本。只有当前 profile 明确把 `dsh-smooth-stream` 声明为 npm 依赖时,**更新**按钮才会对该 profile 执行固定的包更新,并提示重启 Harness。`link:` 或 `file:` 本地开发安装会显示为开发版本,更新按钮会保持禁用,避免覆盖你的源码目录。
114
-
115
- 也可以通过命令行更新 npm 安装的 profile:
116
-
117
- ```sh
118
- dsh plugin --profile web update dsh-smooth-stream
119
- ```
120
-
121
- (也可用 `dsh plugin --profile web outdated` 查看是否有新版本。)
122
-
123
- ## 常见问题
124
-
125
- **这是 DeepSeek 官方插件吗?**
126
- 不是。它是面向 DeepSeek Harness(`dsh`)Web UI 的独立维护项目,采用 MIT 许可证,和 DeepSeek 没有从属关系。
127
-
128
- **「0 重排」是字面意思吗?**
129
- 指动画路径本身。滚动跟随只写合成层 `transform`,不读写布局属性,因此跟随阶段不会引发重排/重绘风暴;文本揭示仍按字符增量写入(这是产生新内容的唯一必要布局),但揭示节奏被限幅,单帧视觉位移 ≤8px,避免跳帧。
130
-
131
- **dsh 插件怎么安装?**
132
- 用内置插件命令:在 dsh 源码目录运行 `dsh plugin --profile web add dsh-smooth-stream`(见[安装](#安装))。
133
-
134
- **能用 npm 安装吗?**
135
- 能。`dsh-smooth-stream` 已发布到 [npm](https://www.npmjs.com/package/dsh-smooth-stream),`dsh plugin --profile web add dsh-smooth-stream` 安装的就是预构建的 npm 包。
136
-
137
- **支持 `prefers-reduced-motion` 吗?**
138
- 支持。系统开启减少动态效果时直接显示完整文本、不接管跟随;帧率低于 30 fps 且回复在屏外时,揭示自动暂停、恢复后再补上。
139
-
140
- [![featured on dsh-suite](https://img.shields.io/badge/featured%20on-dsh--suite-4d6bfe)](https://whyihaveyou.github.io/dsh-suite/)
134
+ ---
141
135
 
142
136
  ## 许可证
143
137
 
144
138
  [MIT](LICENSE)
139
+
package/lib/client.js CHANGED
@@ -23,27 +23,27 @@ window.__ModuleLoader__.load({
23
23
  tag.textContent = css$2;
24
24
  }
25
25
  var TypewriterAssistantNodeView_module_css_default = {
26
- "thinkSummary": "I17U7q_thinkSummary",
27
- "thinkChevron": "I17U7q_thinkChevron",
26
+ "thinkTitle": "I17U7q_thinkTitle",
27
+ "thinkLeading": "I17U7q_thinkLeading",
28
+ "thinkSeparator": "I17U7q_thinkSeparator",
29
+ "disclosureRoot": "I17U7q_disclosureRoot",
30
+ "dsh-smooth-stream-think-sweep": "I17U7q_dsh-smooth-stream-think-sweep",
28
31
  "root": "I17U7q_root",
29
- "disclosureIconIdle": "I17U7q_disclosureIconIdle",
30
- "disclosureChevronHover": "I17U7q_disclosureChevronHover",
32
+ "disclosureTitle": "I17U7q_disclosureTitle",
31
33
  "visuallyHidden": "I17U7q_visuallyHidden",
32
- "thinkSeparator": "I17U7q_thinkSeparator",
33
- "disclosureLeading": "I17U7q_disclosureLeading",
34
- "thinkRow": "I17U7q_thinkRow",
34
+ "follow": "I17U7q_follow",
35
35
  "disclosureContent": "I17U7q_disclosureContent",
36
- "disclosureRoot": "I17U7q_disclosureRoot",
37
- "thinkTitle": "I17U7q_thinkTitle",
38
- "think": "I17U7q_think",
39
36
  "stopped": "I17U7q_stopped",
40
- "thinkBody": "I17U7q_thinkBody",
41
- "body": "I17U7q_body",
37
+ "thinkSummary": "I17U7q_thinkSummary",
42
38
  "disclosureRow": "I17U7q_disclosureRow",
43
- "dsh-smooth-stream-think-sweep": "I17U7q_dsh-smooth-stream-think-sweep",
44
- "thinkLeading": "I17U7q_thinkLeading",
45
- "disclosureTitle": "I17U7q_disclosureTitle",
46
- "follow": "I17U7q_follow"
39
+ "disclosureIconIdle": "I17U7q_disclosureIconIdle",
40
+ "thinkRow": "I17U7q_thinkRow",
41
+ "thinkChevron": "I17U7q_thinkChevron",
42
+ "think": "I17U7q_think",
43
+ "body": "I17U7q_body",
44
+ "disclosureLeading": "I17U7q_disclosureLeading",
45
+ "disclosureChevronHover": "I17U7q_disclosureChevronHover",
46
+ "thinkBody": "I17U7q_thinkBody"
47
47
  };
48
48
  //#endregion
49
49
  //#region src/client/AnimatedDisclosure.tsx
@@ -3899,32 +3899,32 @@ window.__ModuleLoader__.load({
3899
3899
  tag.textContent = css$1;
3900
3900
  }
3901
3901
  var SmoothStreamCard_module_css_default = {
3902
+ "fieldHead": "XNGeRG_fieldHead",
3903
+ "label": "XNGeRG_label",
3904
+ "toggle": "XNGeRG_toggle",
3905
+ "cardOpen": "XNGeRG_cardOpen",
3906
+ "chevron": "XNGeRG_chevron",
3907
+ "header": "XNGeRG_header",
3908
+ "field": "XNGeRG_field",
3902
3909
  "failure": "XNGeRG_failure",
3910
+ "discard": "XNGeRG_discard",
3911
+ "headText": "XNGeRG_headText",
3903
3912
  "updateRow": "XNGeRG_updateRow",
3904
3913
  "footer": "XNGeRG_footer",
3905
- "update": "XNGeRG_update",
3906
- "cardOpen": "XNGeRG_cardOpen",
3907
3914
  "fieldDisabled": "XNGeRG_fieldDisabled",
3908
- "header": "XNGeRG_header",
3909
- "chevronOpen": "XNGeRG_chevronOpen",
3910
- "field": "XNGeRG_field",
3911
- "version": "XNGeRG_version",
3912
3915
  "pending": "XNGeRG_pending",
3913
- "chevron": "XNGeRG_chevron",
3916
+ "body": "XNGeRG_body",
3917
+ "save": "XNGeRG_save",
3918
+ "update": "XNGeRG_update",
3919
+ "chevronOpen": "XNGeRG_chevronOpen",
3914
3920
  "hint": "XNGeRG_hint",
3915
3921
  "failed": "XNGeRG_failed",
3916
- "description": "XNGeRG_description",
3917
- "label": "XNGeRG_label",
3918
- "fieldHead": "XNGeRG_fieldHead",
3919
3922
  "updateCopy": "XNGeRG_updateCopy",
3920
- "save": "XNGeRG_save",
3921
- "discard": "XNGeRG_discard",
3922
- "body": "XNGeRG_body",
3923
- "name": "XNGeRG_name",
3923
+ "description": "XNGeRG_description",
3924
3924
  "card": "XNGeRG_card",
3925
+ "version": "XNGeRG_version",
3925
3926
  "readOnly": "XNGeRG_readOnly",
3926
- "headText": "XNGeRG_headText",
3927
- "toggle": "XNGeRG_toggle"
3927
+ "name": "XNGeRG_name"
3928
3928
  };
3929
3929
  //#endregion
3930
3930
  //#region src/client/SmoothStreamCard.tsx
@@ -4425,34 +4425,34 @@ window.__ModuleLoader__.load({
4425
4425
  tag.textContent = css;
4426
4426
  }
4427
4427
  var DebugPanel_module_css_default = {
4428
- "section": "m4_tdG_section",
4429
- "panelHeader": "m4_tdG_panelHeader",
4430
- "statusDot": "m4_tdG_statusDot",
4431
- "iconButton": "m4_tdG_iconButton",
4432
- "unsaved": "m4_tdG_unsaved",
4433
- "infoButton": "m4_tdG_infoButton",
4434
4428
  "triggerActive": "m4_tdG_triggerActive",
4435
- "number": "m4_tdG_number",
4436
- "primaryButton": "m4_tdG_primaryButton",
4437
- "statusLive": "m4_tdG_statusLive",
4429
+ "guide": "m4_tdG_guide",
4438
4430
  "metric": "m4_tdG_metric",
4439
- "control": "m4_tdG_control",
4440
- "secondaryButton": "m4_tdG_secondaryButton",
4431
+ "range": "m4_tdG_range",
4432
+ "iconButton": "m4_tdG_iconButton",
4433
+ "unsaved": "m4_tdG_unsaved",
4434
+ "section": "m4_tdG_section",
4435
+ "metrics": "m4_tdG_metrics",
4441
4436
  "panel": "m4_tdG_panel",
4437
+ "unit": "m4_tdG_unit",
4438
+ "trigger": "m4_tdG_trigger",
4439
+ "state": "m4_tdG_state",
4440
+ "secondaryButton": "m4_tdG_secondaryButton",
4441
+ "title": "m4_tdG_title",
4442
4442
  "controlHead": "m4_tdG_controlHead",
4443
- "guide": "m4_tdG_guide",
4444
4443
  "controlLabel": "m4_tdG_controlLabel",
4445
4444
  "numberWrap": "m4_tdG_numberWrap",
4445
+ "primaryButton": "m4_tdG_primaryButton",
4446
+ "panelHeader": "m4_tdG_panelHeader",
4447
+ "statusLive": "m4_tdG_statusLive",
4448
+ "scrollArea": "m4_tdG_scrollArea",
4449
+ "footerSpacer": "m4_tdG_footerSpacer",
4446
4450
  "visuallyHidden": "m4_tdG_visuallyHidden",
4451
+ "statusDot": "m4_tdG_statusDot",
4452
+ "infoButton": "m4_tdG_infoButton",
4447
4453
  "footer": "m4_tdG_footer",
4448
- "trigger": "m4_tdG_trigger",
4449
- "title": "m4_tdG_title",
4450
- "metrics": "m4_tdG_metrics",
4451
- "unit": "m4_tdG_unit",
4452
- "footerSpacer": "m4_tdG_footerSpacer",
4453
- "range": "m4_tdG_range",
4454
- "state": "m4_tdG_state",
4455
- "scrollArea": "m4_tdG_scrollArea"
4454
+ "number": "m4_tdG_number",
4455
+ "control": "m4_tdG_control"
4456
4456
  };
4457
4457
  //#endregion
4458
4458
  //#region src/client/DebugPanel.tsx
package/lib/index.js CHANGED
@@ -1,4 +1,3 @@
1
- import { settingsNamespace } from "@deepseek-ai/dsh-settings";
2
1
  import Schema from "@deepseek-ai/schemastery";
3
2
  import { readFileSync } from "node:fs";
4
3
  import { basename, dirname, isAbsolute, join } from "node:path";
@@ -229,7 +228,8 @@ function apply(ctx, config) {
229
228
  httpCtx.effect(() => httpCtx.webServer.tapIndex((html) => injectStreamConfig(html, config)), "dsh-smooth-stream: boot config bridge");
230
229
  });
231
230
  ctx.inject(["settings"], (settingsCtx) => {
232
- const scope = settingsCtx.settings.register(settingsNamespace(STREAM_SETTINGS_NS), StreamSettingsSchema, { applies: "live" });
231
+ const settingsNamespace = STREAM_SETTINGS_NS;
232
+ const scope = settingsCtx.settings.register(settingsNamespace, StreamSettingsSchema, { applies: "live" });
233
233
  settingsCtx.inject(["connection"], (connectionCtx) => {
234
234
  let upgrade;
235
235
  const view = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-smooth-stream",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Fluid streaming rendering and silky scrolling for DeepSeek Harness replies, including Markdown, code blocks, tables, and tool results.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/plugin.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import type { Context } from '@deepseek-ai/cordis'
2
2
  import type {} from '@deepseek-ai/dsh-host-webserver'
3
3
  import type { ConnectionRpcHandler } from '@deepseek-ai/dsh-client-connection'
4
- import { settingsNamespace } from '@deepseek-ai/dsh-settings'
4
+ // Type-only: erased at runtime, so the host entry never link-fails on kernels
5
+ // whose dsh-settings no longer ships the value-side helper (issue #17).
6
+ import type { SettingsNamespace } from '@deepseek-ai/dsh-settings'
5
7
  import Schema from '@deepseek-ai/schemastery'
6
8
  import { DEFAULT_STREAM_CONFIG, type StreamConfig } from './config.ts'
7
9
  import { injectStreamConfig } from './boot-config.ts'
@@ -93,8 +95,14 @@ export function apply(ctx: Context, config: Config): void {
93
95
  // the durable provider as the authority, but expose this one schema through
94
96
  // the plugin's own loopback-only connection channel instead.
95
97
  ctx.inject(['settings'], (settingsCtx) => {
98
+ // 0.1.2 kernels dropped the `settingsNamespace()` helper — a validating
99
+ // identity on ≤ 0.1.1 — and take the raw string, so the rc-era brand is
100
+ // reproduced locally instead of statically importing a removed symbol.
101
+ // The namespace is a compile-time constant matching the kernel's
102
+ // /^[a-z][a-z0-9-]*$/ pattern.
103
+ const settingsNamespace = STREAM_SETTINGS_NS as SettingsNamespace
96
104
  const scope = settingsCtx.settings.register(
97
- settingsNamespace(STREAM_SETTINGS_NS),
105
+ settingsNamespace,
98
106
  StreamSettingsSchema,
99
107
  { applies: 'live' },
100
108
  )