dsh-smooth-stream 0.4.3 → 0.6.0

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.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Turn-process fold face for the DSH 0.1.2 completion-collapse contract.
3
+ *
4
+ * The Host seat hands every keyed Chat renderer a `turnProcess` owner prop
5
+ * when the node belongs to a projected Turn: the fold decision lives in the
6
+ * conversation snapshot (turn closed + compact transcript), and the renderer
7
+ * is expected to hide its answer-inline reasoning while folded. The pinned
8
+ * type-only dependency predates the field, so the face is declared here and
9
+ * merged into the owner-props interface; erases at compile time.
10
+ */
11
+ export interface TurnProcessOwnerFace {
12
+ /** Answer anchor of the projection; null while the answer has not landed. */
13
+ readonly spec: { readonly answerStep: number | null, readonly inlineReasoning: boolean }
14
+ /** Whether this node's process content may fold (turn closed + compact mode). */
15
+ readonly foldable: boolean
16
+ /** Current fold state, persisted by the Host across sessions. */
17
+ readonly open: boolean
18
+ /** Fold or unfold from the renderer side (summary click, find-in-page reveal). */
19
+ setOpen(open: boolean): void
20
+ }
21
+
22
+ declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
23
+ interface ChatNodeOwnerProps {
24
+ /** Present only when the node belongs to a projected Turn (0.1.2+). */
25
+ turnProcess?: TurnProcessOwnerFace | undefined
26
+ }
27
+ }
package/src/plugin.ts CHANGED
@@ -55,8 +55,13 @@ export const Config: Schema<Config> = Schema.object({
55
55
  */
56
56
  export const StreamSettingsSchema: Schema<StreamSettings> = Schema.object({
57
57
  enabled: Schema.boolean().default(DEFAULT_STREAM_SETTINGS.enabled),
58
+ controlScroll: Schema.boolean().default(DEFAULT_STREAM_SETTINGS.controlScroll),
59
+ motionPreference: Schema.union([
60
+ Schema.const('auto'),
61
+ Schema.const('force-smooth'),
62
+ Schema.const('force-reduced'),
63
+ ] as const).default(DEFAULT_STREAM_SETTINGS.motionPreference),
58
64
  thinkAutoExpand: Schema.boolean().default(DEFAULT_STREAM_SETTINGS.thinkAutoExpand),
59
- autoCollapse: Schema.boolean().default(DEFAULT_STREAM_SETTINGS.autoCollapse),
60
65
  debugEnabled: Schema.boolean().default(DEFAULT_STREAM_SETTINGS.debugEnabled),
61
66
  debugTuning: Schema.object({
62
67
  revealScale: Schema.number().min(0.25).max(2).default(DEFAULT_STREAM_SETTINGS.debugTuning.revealScale),
@@ -117,8 +122,9 @@ export function apply(ctx: Context, config: Config): void {
117
122
  installation: installation.kind,
118
123
  writable: connectionCtx.settings.writable,
119
124
  enabled: settings.enabled,
125
+ controlScroll: settings.controlScroll,
126
+ motionPreference: settings.motionPreference,
120
127
  thinkAutoExpand: settings.thinkAutoExpand,
121
- autoCollapse: settings.autoCollapse,
122
128
  canUpgrade: installation.kind === 'npm',
123
129
  }
124
130
  }
@@ -159,13 +165,14 @@ export function apply(ctx: Context, config: Config): void {
159
165
  if (endpoint === STREAM_SETTINGS_RPC.write) {
160
166
  if (typeof payload !== 'object' || payload === null || Array.isArray(payload)
161
167
  || typeof (payload as { enabled?: unknown }).enabled !== 'boolean'
168
+ || typeof (payload as { controlScroll?: unknown }).controlScroll !== 'boolean'
162
169
  || typeof (payload as { thinkAutoExpand?: unknown }).thinkAutoExpand !== 'boolean'
163
- || typeof (payload as { autoCollapse?: unknown }).autoCollapse !== 'boolean') {
170
+ ) {
164
171
  return {
165
172
  ok: false,
166
173
  error: {
167
174
  code: 'settings-rejected',
168
- message: 'enabled, thinkAutoExpand and autoCollapse must be booleans',
175
+ message: 'enabled, controlScroll and thinkAutoExpand must be booleans',
169
176
  details: { ns: STREAM_SETTINGS_NS },
170
177
  },
171
178
  }
@@ -183,11 +190,27 @@ export function apply(ctx: Context, config: Config): void {
183
190
  try {
184
191
  const next = payload as {
185
192
  enabled: boolean
193
+ controlScroll: boolean
194
+ motionPreference?: unknown
186
195
  thinkAutoExpand: boolean
187
- autoCollapse: boolean
188
196
  debugEnabled?: unknown
189
197
  debugTuning?: unknown
190
198
  }
199
+ if (
200
+ next.motionPreference !== undefined
201
+ && next.motionPreference !== 'auto'
202
+ && next.motionPreference !== 'force-smooth'
203
+ && next.motionPreference !== 'force-reduced'
204
+ ) {
205
+ return {
206
+ ok: false,
207
+ error: {
208
+ code: 'settings-rejected',
209
+ message: 'motionPreference must be one of auto | force-smooth | force-reduced',
210
+ details: { ns: STREAM_SETTINGS_NS },
211
+ },
212
+ }
213
+ }
191
214
  const hasDebug = next.debugEnabled !== undefined || next.debugTuning !== undefined
192
215
  if (hasDebug && (typeof next.debugEnabled !== 'boolean' || !validDebugTuning(next.debugTuning))) {
193
216
  return {
@@ -201,8 +224,9 @@ export function apply(ctx: Context, config: Config): void {
201
224
  }
202
225
  await scope.update({
203
226
  enabled: next.enabled,
227
+ controlScroll: next.controlScroll,
228
+ ...(next.motionPreference === undefined ? {} : { motionPreference: next.motionPreference }),
204
229
  thinkAutoExpand: next.thinkAutoExpand,
205
- autoCollapse: next.autoCollapse,
206
230
  ...(hasDebug ? { debugEnabled: next.debugEnabled, debugTuning: next.debugTuning } : {}),
207
231
  })
208
232
  } catch {
@@ -25,10 +25,12 @@ export interface StreamSettingsView {
25
25
  writable: boolean
26
26
  /** Whether smooth-stream currently owns conversation rendering. */
27
27
  enabled: boolean
28
+ /** Whether smooth-stream also owns conversation bottom-follow. */
29
+ controlScroll: boolean
30
+ /** How the reveal honors the OS reduced-motion preference. */
31
+ motionPreference: import('./settings.ts').StreamMotionPreference
28
32
  /** Current resolved preference. */
29
33
  thinkAutoExpand: boolean
30
- /** Whether finished turns fold their work process behind a summary row. */
31
- autoCollapse: boolean
32
34
  /** Whether a fixed npm update command is safe to offer. */
33
35
  canUpgrade: boolean
34
36
  }
package/src/settings.ts CHANGED
@@ -45,6 +45,18 @@ export const DEFAULT_STREAM_DEBUG_TUNING: StreamDebugTuning = {
45
45
  backpressureMinScale: 0.55,
46
46
  }
47
47
 
48
+ /**
49
+ * How the typewriter reveal responds to the OS reduced-motion preference.
50
+ * Credit: three-state design proposed by @Zn-Dk in #21/#22.
51
+ */
52
+ export type StreamMotionPreference =
53
+ /** Follow the OS preference: reduced-motion users get raw text (accessibility first). */
54
+ | 'auto'
55
+ /** Always run the smoothing engine, ignoring the OS preference. */
56
+ | 'force-smooth'
57
+ /** Always render raw text, ignoring the OS preference. */
58
+ | 'force-reduced'
59
+
48
60
  /**
49
61
  * Preferences a user may set. Deliberately separate from {@link StreamConfig}
50
62
  * because the two change at different times: composition-time values go
@@ -56,19 +68,24 @@ export interface StreamSettings {
56
68
  * Off returns all rendering ownership to the built-in UI.
57
69
  */
58
70
  enabled: boolean
71
+ /**
72
+ * Whether this plugin also takes over conversation bottom-follow. Off leaves
73
+ * scroll ownership to the Harness; text reveal still runs.
74
+ */
75
+ controlScroll: boolean
76
+ /**
77
+ * How the reveal honors the OS reduced-motion preference. `auto` preserves
78
+ * the accessibility-first default; `force-smooth` keeps the engine on
79
+ * machines where a system-wide reduce-motion switch (performance tooling,
80
+ * RDP sessions, forced browser flags) would otherwise bypass it silently.
81
+ */
82
+ motionPreference: StreamMotionPreference
59
83
  /**
60
84
  * Whether a reasoning ("Think") block auto-expands while it is the
61
85
  * streaming tail. Off keeps the block collapsed — the user can still open
62
86
  * it by hand — and stops the running state from re-owning the disclosure.
63
87
  */
64
88
  thinkAutoExpand: boolean
65
- /**
66
- * Whether a finished turn folds its work process (thinking, tool calls,
67
- * context injection, intermediate replies) behind one "已处理" summary row,
68
- * leaving only the final answer visible. The summary row can always be
69
- * toggled by hand, and turns still expand live while they stream.
70
- */
71
- autoCollapse: boolean
72
89
  /** Whether the live renderer diagnostics panel is enabled. */
73
90
  debugEnabled: boolean
74
91
  /** Values edited by the diagnostics panel. */
@@ -78,8 +95,9 @@ export interface StreamSettings {
78
95
  /** Defaults shared by the Host schema and the client-side fallback. */
79
96
  export const DEFAULT_STREAM_SETTINGS: StreamSettings = {
80
97
  enabled: true,
98
+ controlScroll: true,
99
+ motionPreference: 'auto',
81
100
  thinkAutoExpand: true,
82
- autoCollapse: true,
83
101
  debugEnabled: false,
84
102
  debugTuning: DEFAULT_STREAM_DEBUG_TUNING,
85
103
  }