dsh-smooth-stream 0.6.0 → 0.7.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.
- package/README.en.md +4 -1
- package/README.md +5 -1
- package/lib/client.js +1757 -282
- package/lib/client.js.map +1 -1
- package/lib/index.js +299 -3
- package/package.json +1 -1
- package/src/client/AgentRowEntrance.module.css +1 -4
- package/src/client/AnimatedDisclosure.tsx +3 -3
- package/src/client/DebugPanel.tsx +34 -15
- package/src/client/FrameCoordinator.ts +180 -0
- package/src/client/LogarithmicFade.module.css +160 -0
- package/src/client/SmoothStreamCard.tsx +41 -3
- package/src/client/StreamBuffer.ts +145 -0
- package/src/client/TypewriterAssistantNodeView.module.css +82 -12
- package/src/client/TypewriterAssistantNodeView.tsx +232 -104
- package/src/client/debugRuntime.ts +58 -0
- package/src/client/harnessIcons.ts +27 -0
- package/src/client/index.ts +153 -11
- package/src/client/locales.ts +90 -0
- package/src/client/smooth-stream-card-controller.ts +10 -3
- package/src/client/smooth-stream-settings-api.ts +7 -3
- package/src/client/teleprompterGlide.ts +499 -70
- package/src/client/useDecoupledMarkdown.ts +173 -0
- package/src/client/useFpsGuard.ts +9 -6
- package/src/client/useLogarithmicFade.ts +672 -0
- package/src/client/useProgressiveDomText.ts +27 -11
- package/src/client/useSmoothStreamContent.ts +5 -13
- package/src/config.ts +1 -1
- package/src/plugin.ts +99 -12
- package/src/settings-api.ts +4 -0
- package/src/settings-channel.ts +303 -0
- package/src/settings.ts +14 -0
package/lib/client.js
CHANGED
|
@@ -4,46 +4,85 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __create = Object.create;
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
16
|
+
key = keys[i];
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
18
|
+
get: ((k) => from[k]).bind(null, key),
|
|
19
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
25
|
+
value: mod,
|
|
26
|
+
enumerable: true
|
|
27
|
+
}) : target, mod));
|
|
28
|
+
//#endregion
|
|
7
29
|
let react = require("react");
|
|
8
30
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
31
|
+
_deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
|
|
9
32
|
let _deepseek_ai_dsh_client_ui_attachment = require("@deepseek-ai/dsh-client-ui-attachment");
|
|
10
33
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
11
34
|
let react_dom = require("react-dom");
|
|
12
|
-
//#region
|
|
13
|
-
const
|
|
14
|
-
|
|
35
|
+
//#region src/client/harnessIcons.ts
|
|
36
|
+
const icons = _deepseek_ai_dsh_client_ui_primitives;
|
|
37
|
+
function resolveIcon(currentName, legacyName) {
|
|
38
|
+
const icon = icons[currentName] ?? icons[legacyName];
|
|
39
|
+
if (icon === void 0) throw new Error(`dsh-smooth-stream: Harness icon export is missing (${currentName}, ${legacyName})`);
|
|
40
|
+
return icon;
|
|
41
|
+
}
|
|
42
|
+
const IconChevronDown = resolveIcon("IconChevronDownOutlineRegular", "IconChevronDownOutline14");
|
|
43
|
+
const IconClose = resolveIcon("IconCloseOutlineRegular", "IconCloseOutline16");
|
|
44
|
+
const IconCode = resolveIcon("IconCodeOutlineRegular", "IconCodeOutline16");
|
|
45
|
+
const IconCopy = resolveIcon("IconCopyOutlineRegular", "IconCopyOutline16");
|
|
46
|
+
const IconQuestion = resolveIcon("IconQuestionOutlineRegular", "IconQuestionOutline14");
|
|
47
|
+
const IconRefresh = resolveIcon("IconRefreshOutlineRegular", "IconRefreshOutline14");
|
|
48
|
+
const IconRefreshSmall = resolveIcon("IconRefreshOutlineRegular", "IconRefreshOutline16");
|
|
49
|
+
const IconThink = resolveIcon("IconThinkOutlineRegular", "IconThinkOutline14");
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region \0dsh-css:/Users/dzlin/.codex/worktrees/pr-39-review/dsh-smooth-stream/src/client/TypewriterAssistantNodeView.module.css.mjs
|
|
52
|
+
const css$4 = ".ydEAna_root{min-width:0;color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size,16px);line-height:calc(24px + var(--dsh-content-font-delta,4px));flex-direction:column;display:flex}.ydEAna_body{flex-direction:column;gap:16px;min-width:0;display:flex}.ydEAna_body>[data-turn-process-inline][hidden]{margin-bottom:-16px}.ydEAna_think{flex-direction:column;display:flex}.ydEAna_thinkRow{position:relative;overflow:hidden}.ydEAna_think[data-state=running] .ydEAna_thinkRow:after{content:\"\";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite ydEAna_dsh-smooth-stream-think-sweep;position:absolute;left:0}@keyframes ydEAna_dsh-smooth-stream-think-sweep{0%{left:-300px}90%,to{left:100%}}.ydEAna_thinkLeading{flex-shrink:0}.ydEAna_thinkChevron{color:var(--dsw-alias-label-secondary)}.ydEAna_thinkTitle{font-weight:400}.ydEAna_thinkSeparator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.ydEAna_thinkSummary{min-width:0;color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,14px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,4px));text-overflow:ellipsis;white-space:nowrap;flex:auto;overflow:hidden}.ydEAna_thinkSummary[data-follow-end]{text-overflow:clip}.ydEAna_thinkBody{padding:4px 0 4px calc(22px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,14px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,4px));white-space:pre-wrap;word-break:break-word;max-height:var(--dsh-thinking-max-height,300px);overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-border-l4,#80808066) transparent;contain:layout;will-change:scroll-position;overflow-y:auto;transform:translateZ(0)}.ydEAna_thinkBody::-webkit-scrollbar{width:6px}.ydEAna_thinkBody::-webkit-scrollbar-track{background:0 0}.ydEAna_thinkBody::-webkit-scrollbar-thumb{background-color:var(--dsw-alias-border-l4,#80808066);border-radius:3px}.ydEAna_thinkBody::-webkit-scrollbar-thumb:hover{background-color:var(--dsw-alias-border-l3,#80808099)}.ydEAna_disclosureRoot{flex-direction:column;width:100%;min-width:0;display:flex}.ydEAna_disclosureRow{height:calc(24px + var(--dsh-content-font-delta,0px));cursor:pointer;align-items:center;min-width:0;display:flex;position:relative;overflow:hidden}.ydEAna_disclosureLeading{width:calc(16px + var(--dsh-content-font-delta,0px));height:calc(16px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:6px;display:inline-flex;position:relative}.ydEAna_disclosureLeading svg:not([data-state]){width:calc(14px + var(--dsh-content-font-delta,0px));height:calc(14px + var(--dsh-content-font-delta,0px))}.ydEAna_disclosureIconIdle{opacity:1;transition:opacity .1s;display:inline-flex}.ydEAna_disclosureChevronHover{opacity:0;margin:auto;transition:opacity .1s;position:absolute;inset:0}.ydEAna_disclosureRow:hover .ydEAna_disclosureIconIdle{opacity:0}.ydEAna_disclosureRow:hover .ydEAna_disclosureChevronHover{opacity:1}.ydEAna_disclosureTitle{font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-secondary);flex:none}.ydEAna_disclosureContent{visibility:visible;transition:grid-template-rows var(--ds-transition-duration,.2s) var(--ds-ease-in-out,cubic-bezier(.4, 0, .2, 1)), visibility 0s;grid-template-rows:1fr;display:grid;overflow:hidden}.ydEAna_disclosureContent[data-collapsed]{visibility:hidden;transition:grid-template-rows var(--ds-transition-duration,.2s) var(--ds-ease-in-out,cubic-bezier(.4, 0, .2, 1)), visibility 0s var(--ds-transition-duration,.2s);grid-template-rows:0fr}.ydEAna_disclosureContent[data-collapsed]>*{padding-top:0!important;padding-bottom:0!important}.ydEAna_disclosureContent[data-no-transition]{transition:none}.ydEAna_disclosureContent>*{min-height:0}.ydEAna_disclosureContent[data-collapsed]>*{overflow:hidden}@media (prefers-reduced-motion:reduce){.ydEAna_think[data-state=running] .ydEAna_thinkRow:after{animation:none}.ydEAna_disclosureContent,.ydEAna_disclosureContent[data-collapsed]{transition:none}}.ydEAna_stopped{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary);border-radius:6px;align-self:flex-start;padding:0 6px;font-size:11px;line-height:18px}.ydEAna_visuallyHidden{clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.ydEAna_follow{contain:layout style;min-width:0}.ydEAna_root :not(pre)>code{line-height:inherit;white-space:normal;overflow-wrap:anywhere;vertical-align:baseline;display:inline}@supports (text-box-trim:trim-both){.ydEAna_root :is(p,h1,h2,h3,h4,h5,h6,blockquote){text-box-trim:trim-both;text-box-edge:text}}";
|
|
53
|
+
const tagId$4 = "dsh-smooth-stream/TypewriterAssistantNodeView.module.css";
|
|
15
54
|
if (typeof document !== "undefined") {
|
|
16
|
-
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$
|
|
55
|
+
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]");
|
|
17
56
|
if (tag === null) {
|
|
18
57
|
tag = document.createElement("style");
|
|
19
58
|
tag.dataset.plugin = "dsh-smooth-stream";
|
|
20
|
-
tag.dataset.pluginCss = tagId$
|
|
59
|
+
tag.dataset.pluginCss = tagId$4;
|
|
21
60
|
document.head.appendChild(tag);
|
|
22
61
|
}
|
|
23
|
-
tag.textContent = css$
|
|
62
|
+
tag.textContent = css$4;
|
|
24
63
|
}
|
|
25
64
|
var TypewriterAssistantNodeView_module_css_default = {
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"thinkSeparator": "
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"disclosureTitle": "
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
65
|
+
"think": "ydEAna_think",
|
|
66
|
+
"thinkRow": "ydEAna_thinkRow",
|
|
67
|
+
"thinkSummary": "ydEAna_thinkSummary",
|
|
68
|
+
"disclosureLeading": "ydEAna_disclosureLeading",
|
|
69
|
+
"thinkSeparator": "ydEAna_thinkSeparator",
|
|
70
|
+
"root": "ydEAna_root",
|
|
71
|
+
"thinkLeading": "ydEAna_thinkLeading",
|
|
72
|
+
"dsh-smooth-stream-think-sweep": "ydEAna_dsh-smooth-stream-think-sweep",
|
|
73
|
+
"thinkTitle": "ydEAna_thinkTitle",
|
|
74
|
+
"disclosureTitle": "ydEAna_disclosureTitle",
|
|
75
|
+
"thinkChevron": "ydEAna_thinkChevron",
|
|
76
|
+
"disclosureContent": "ydEAna_disclosureContent",
|
|
77
|
+
"disclosureIconIdle": "ydEAna_disclosureIconIdle",
|
|
78
|
+
"thinkBody": "ydEAna_thinkBody",
|
|
79
|
+
"stopped": "ydEAna_stopped",
|
|
80
|
+
"disclosureRoot": "ydEAna_disclosureRoot",
|
|
81
|
+
"body": "ydEAna_body",
|
|
82
|
+
"disclosureChevronHover": "ydEAna_disclosureChevronHover",
|
|
83
|
+
"visuallyHidden": "ydEAna_visuallyHidden",
|
|
84
|
+
"follow": "ydEAna_follow",
|
|
85
|
+
"disclosureRow": "ydEAna_disclosureRow"
|
|
47
86
|
};
|
|
48
87
|
//#endregion
|
|
49
88
|
//#region src/client/AnimatedDisclosure.tsx
|
|
@@ -78,10 +117,10 @@ window.__ModuleLoader__.load({
|
|
|
78
117
|
children: [
|
|
79
118
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
80
119
|
className: cx(TypewriterAssistantNodeView_module_css_default.disclosureLeading, leadingClassName),
|
|
81
|
-
children: open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
120
|
+
children: open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { className: chevronClassName }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
82
121
|
className: TypewriterAssistantNodeView_module_css_default.disclosureIconIdle,
|
|
83
122
|
children: icon
|
|
84
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
123
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { className: cx(chevronClassName, TypewriterAssistantNodeView_module_css_default.disclosureChevronHover) })] })
|
|
85
124
|
}),
|
|
86
125
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
87
126
|
className: cx(TypewriterAssistantNodeView_module_css_default.disclosureTitle, titleClassName),
|
|
@@ -126,8 +165,10 @@ window.__ModuleLoader__.load({
|
|
|
126
165
|
const DEFAULT_STREAM_SETTINGS = {
|
|
127
166
|
enabled: true,
|
|
128
167
|
controlScroll: true,
|
|
168
|
+
preset: "silky",
|
|
129
169
|
motionPreference: "auto",
|
|
130
170
|
thinkAutoExpand: true,
|
|
171
|
+
logarithmicFade: true,
|
|
131
172
|
debugEnabled: false,
|
|
132
173
|
debugTuning: DEFAULT_STREAM_DEBUG_TUNING
|
|
133
174
|
};
|
|
@@ -196,6 +237,21 @@ window.__ModuleLoader__.load({
|
|
|
196
237
|
* the renderer and the chat-side panel: renderer loops publish measurements,
|
|
197
238
|
* while panel edits are staged through the settings-card controller.
|
|
198
239
|
*/
|
|
240
|
+
/**
|
|
241
|
+
* Allocation-free read of the newest stream metric. The follower polls this
|
|
242
|
+
* every frame to learn whether the producer has stopped while reveal work
|
|
243
|
+
* remains, and `getSnapshot()` would allocate a fresh state object per frame
|
|
244
|
+
* just to answer one boolean.
|
|
245
|
+
*/
|
|
246
|
+
function readNewestStreamMetric() {
|
|
247
|
+
let newest;
|
|
248
|
+
for (const candidate of streamMetrics.values()) if (newest === void 0 || candidate.updatedAt > newest.updatedAt) newest = candidate;
|
|
249
|
+
if (newest === void 0) return null;
|
|
250
|
+
return {
|
|
251
|
+
producerComplete: newest.producerComplete,
|
|
252
|
+
backlog: newest.backlog
|
|
253
|
+
};
|
|
254
|
+
}
|
|
199
255
|
const EMPTY_METRICS = {
|
|
200
256
|
fps: null,
|
|
201
257
|
frameMs: null,
|
|
@@ -213,6 +269,12 @@ window.__ModuleLoader__.load({
|
|
|
213
269
|
followRevealScale: 1,
|
|
214
270
|
followFollowing: false,
|
|
215
271
|
followConstrained: false,
|
|
272
|
+
followTerminalPhase: "live",
|
|
273
|
+
followRunwayPx: 0,
|
|
274
|
+
followTerminalBudgetPx: 0,
|
|
275
|
+
followBaselineShiftPx: 0,
|
|
276
|
+
followAnchorDeltaPx: null,
|
|
277
|
+
followRemainingRevealChars: 0,
|
|
216
278
|
scrollTop: null,
|
|
217
279
|
scrollHeight: null,
|
|
218
280
|
clientHeight: null,
|
|
@@ -273,6 +335,12 @@ window.__ModuleLoader__.load({
|
|
|
273
335
|
followRevealScale: follow?.revealScale ?? 1,
|
|
274
336
|
followFollowing: follow?.following ?? false,
|
|
275
337
|
followConstrained: follow?.constrained ?? false,
|
|
338
|
+
followTerminalPhase: follow?.terminalPhase ?? "live",
|
|
339
|
+
followRunwayPx: follow?.runwayPx ?? 0,
|
|
340
|
+
followTerminalBudgetPx: follow?.terminalBudgetPx ?? 0,
|
|
341
|
+
followBaselineShiftPx: follow?.baselineShiftPx ?? 0,
|
|
342
|
+
followAnchorDeltaPx: follow?.readingAnchorDeltaPx ?? null,
|
|
343
|
+
followRemainingRevealChars: follow?.remainingRevealChars ?? 0,
|
|
276
344
|
scrollTop: follow?.scrollTop ?? null,
|
|
277
345
|
scrollHeight: follow?.scrollHeight ?? null,
|
|
278
346
|
clientHeight: follow?.clientHeight ?? null,
|
|
@@ -435,6 +503,123 @@ window.__ModuleLoader__.load({
|
|
|
435
503
|
}
|
|
436
504
|
};
|
|
437
505
|
//#endregion
|
|
506
|
+
//#region src/client/FrameCoordinator.ts
|
|
507
|
+
var FrameCoordinator = class FrameCoordinator {
|
|
508
|
+
static coordinators = /* @__PURE__ */ new WeakMap();
|
|
509
|
+
tasks = /* @__PURE__ */ new Map();
|
|
510
|
+
rafId = null;
|
|
511
|
+
lastTs = null;
|
|
512
|
+
/**
|
|
513
|
+
* Owed layout read. Starts false: a fresh coordinator has no unread writes,
|
|
514
|
+
* and starting dirty would keep the clock armed forever after the first
|
|
515
|
+
* frame. Consumers declare reads via {@link markLayoutDirty}.
|
|
516
|
+
*/
|
|
517
|
+
layoutDirty = false;
|
|
518
|
+
/** One id per consumer, so an unregister cannot evict a re-registered task. */
|
|
519
|
+
nextTaskId = 0;
|
|
520
|
+
doc;
|
|
521
|
+
constructor(doc) {
|
|
522
|
+
this.doc = doc;
|
|
523
|
+
}
|
|
524
|
+
static forDocument(doc = document) {
|
|
525
|
+
let coordinator = FrameCoordinator.coordinators.get(doc);
|
|
526
|
+
if (coordinator === void 0) {
|
|
527
|
+
coordinator = new FrameCoordinator(doc);
|
|
528
|
+
FrameCoordinator.coordinators.set(doc, coordinator);
|
|
529
|
+
}
|
|
530
|
+
return coordinator;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Mark that a DOM or geometry change requires a layout read next frame.
|
|
534
|
+
*/
|
|
535
|
+
markLayoutDirty() {
|
|
536
|
+
this.layoutDirty = true;
|
|
537
|
+
this.ensureLoop();
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Declare that geometry written since the last frame must be re-read at the
|
|
541
|
+
* top of the next one, and keep the clock running for it.
|
|
542
|
+
*/
|
|
543
|
+
requestRead() {
|
|
544
|
+
this.markLayoutDirty();
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Register or replace an animation/render task. Returns the task id to hand
|
|
548
|
+
* back to {@link unregisterTask}.
|
|
549
|
+
*/
|
|
550
|
+
registerTask(task) {
|
|
551
|
+
const id = task.id ?? `frame-task-${this.nextTaskId++}`;
|
|
552
|
+
this.tasks.set(id, {
|
|
553
|
+
...task,
|
|
554
|
+
id
|
|
555
|
+
});
|
|
556
|
+
this.ensureLoop();
|
|
557
|
+
return id;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Unregister a task when its stream completes or the element unmounts.
|
|
561
|
+
*/
|
|
562
|
+
unregisterTask(id) {
|
|
563
|
+
if (id === null || id === void 0) return;
|
|
564
|
+
this.tasks.delete(id);
|
|
565
|
+
if (this.tasks.size === 0 && !this.layoutDirty) this.stopLoop();
|
|
566
|
+
}
|
|
567
|
+
ensureLoop() {
|
|
568
|
+
if (this.rafId !== null) return;
|
|
569
|
+
const win = this.doc.defaultView ?? (typeof window !== "undefined" ? window : null);
|
|
570
|
+
if (win === null) return;
|
|
571
|
+
const tick = (now) => {
|
|
572
|
+
this.rafId = null;
|
|
573
|
+
if (this.lastTs === null) {
|
|
574
|
+
this.lastTs = now;
|
|
575
|
+
this.ensureLoop();
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const frameIntervalMs = Math.max(0, now - this.lastTs);
|
|
579
|
+
const dtMs = Math.max(1, Math.min(frameIntervalMs, 100));
|
|
580
|
+
this.lastTs = now;
|
|
581
|
+
if (this.layoutDirty) {
|
|
582
|
+
this.layoutDirty = false;
|
|
583
|
+
for (const task of this.tasks.values()) task.onRead?.(now);
|
|
584
|
+
}
|
|
585
|
+
let anyActive = false;
|
|
586
|
+
for (const task of this.tasks.values()) if (task.onSimulate?.(dtMs, now) === true) anyActive = true;
|
|
587
|
+
for (const task of this.tasks.values()) task.onWrite?.(now);
|
|
588
|
+
if ((anyActive || this.layoutDirty) && this.rafId === null) this.ensureLoop();
|
|
589
|
+
else if (!anyActive && !this.layoutDirty) this.lastTs = null;
|
|
590
|
+
};
|
|
591
|
+
this.rafId = win.requestAnimationFrame(tick);
|
|
592
|
+
}
|
|
593
|
+
stopLoop() {
|
|
594
|
+
if (this.rafId !== null) {
|
|
595
|
+
(this.doc.defaultView ?? (typeof window !== "undefined" ? window : null))?.cancelAnimationFrame(this.rafId);
|
|
596
|
+
this.rafId = null;
|
|
597
|
+
}
|
|
598
|
+
this.lastTs = null;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Whether this document currently owns a live, still-needed animation
|
|
602
|
+
* frame. A frame that is merely draining an owed read with no registered
|
|
603
|
+
* task left is not considered active work.
|
|
604
|
+
*/
|
|
605
|
+
get active() {
|
|
606
|
+
return this.rafId !== null && (this.tasks.size > 0 || this.layoutDirty);
|
|
607
|
+
}
|
|
608
|
+
/** Number of registered tasks; used by tests to pin the single-clock contract. */
|
|
609
|
+
get taskCount() {
|
|
610
|
+
return this.tasks.size;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Release every task, drop any owed read, and stop the clock. Used by tests
|
|
614
|
+
* for isolation; production consumers unregister their own task.
|
|
615
|
+
*/
|
|
616
|
+
shutdown() {
|
|
617
|
+
this.tasks.clear();
|
|
618
|
+
this.layoutDirty = false;
|
|
619
|
+
this.stopLoop();
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
//#endregion
|
|
438
623
|
//#region src/client/teleprompterGlide.ts
|
|
439
624
|
/**
|
|
440
625
|
* Conversation-port follow while an assistant reply streams.
|
|
@@ -673,7 +858,7 @@ window.__ModuleLoader__.load({
|
|
|
673
858
|
setDirectShift(element, px);
|
|
674
859
|
}
|
|
675
860
|
function turnStatusOf(port) {
|
|
676
|
-
return port.querySelector("[data-chat-turn-status], [data-chat-flow] > [role=\"status\"]");
|
|
861
|
+
return port.querySelector("[data-chat-turn-status], [data-chat-flow] > [role=\"status\"], [data-chat-flow] > [data-chat-running]");
|
|
677
862
|
}
|
|
678
863
|
const FOLLOW_FLOW_FILL_SYMBOL = Symbol.for("dsh-smooth-stream.follow-flow-fill");
|
|
679
864
|
const followFlowFillHost = globalThis;
|
|
@@ -686,6 +871,10 @@ window.__ModuleLoader__.load({
|
|
|
686
871
|
function flowElementOf(port) {
|
|
687
872
|
return port.querySelector("[data-chat-transcript]") ?? port.querySelector("[data-chat-flow]");
|
|
688
873
|
}
|
|
874
|
+
/** Keep completion padding on the stable flow wrapper across row replacement. */
|
|
875
|
+
function flowPadElementOf(port) {
|
|
876
|
+
return port.querySelector("[data-chat-flow]") ?? flowElementOf(port);
|
|
877
|
+
}
|
|
689
878
|
function ensureFlowFillsPort(port) {
|
|
690
879
|
const element = flowElementOf(port);
|
|
691
880
|
const owned = followFlowFills.get(port);
|
|
@@ -745,6 +934,8 @@ window.__ModuleLoader__.load({
|
|
|
745
934
|
followRunwayRegistry[FOLLOW_RUNWAYS_SYMBOL] = followRunways;
|
|
746
935
|
const followPaintLimits = /* @__PURE__ */ new WeakMap();
|
|
747
936
|
const followHadChrome = /* @__PURE__ */ new WeakSet();
|
|
937
|
+
/** A status row that existed during this turn makes its removal a host cascade. */
|
|
938
|
+
const followHadStatus = /* @__PURE__ */ new WeakSet();
|
|
748
939
|
/** Last painted shift per port, to spread a wrap's one-line step over frames. */
|
|
749
940
|
const followLastShiftPx = /* @__PURE__ */ new WeakMap();
|
|
750
941
|
/** Last settled floor per port, to size the shift decay bound against extent collapse. */
|
|
@@ -858,7 +1049,8 @@ window.__ModuleLoader__.load({
|
|
|
858
1049
|
});
|
|
859
1050
|
return null;
|
|
860
1051
|
}
|
|
861
|
-
const
|
|
1052
|
+
const scrollDelta = scrollTop - stored.scrollTop;
|
|
1053
|
+
const delta = top - stored.top + scrollDelta - (shift - stored.shift) + (pad - stored.pad);
|
|
862
1054
|
if (stored.element === anchor) {
|
|
863
1055
|
followGuardAnchors.set(port, {
|
|
864
1056
|
element: anchor,
|
|
@@ -937,7 +1129,7 @@ window.__ModuleLoader__.load({
|
|
|
937
1129
|
return false;
|
|
938
1130
|
}
|
|
939
1131
|
function setFlowPad(port, px) {
|
|
940
|
-
const flow =
|
|
1132
|
+
const flow = flowPadElementOf(port);
|
|
941
1133
|
if (flow === null) return;
|
|
942
1134
|
const existing = followSettlePads.get(port);
|
|
943
1135
|
const original = existing?.original ?? flow.style.paddingBottom;
|
|
@@ -975,12 +1167,83 @@ window.__ModuleLoader__.load({
|
|
|
975
1167
|
if (!traceActive()) return;
|
|
976
1168
|
console.log(`[dsh-follow] ${event}`, JSON.stringify(detail));
|
|
977
1169
|
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Scroll length observed at the previous measured frame, per port. The
|
|
1172
|
+
* completion handoff uses the delta to seed the growth credit from geometry the
|
|
1173
|
+
* page actually committed, instead of assuming the full runway was earned.
|
|
1174
|
+
*/
|
|
1175
|
+
const followObservedContentHeight = /* @__PURE__ */ new WeakMap();
|
|
1176
|
+
/** Growth this commit added, as last measured by `applyVisual`. */
|
|
1177
|
+
function environmentCommitGrowthPx(port) {
|
|
1178
|
+
const previous = followObservedContentHeight.get(port);
|
|
1179
|
+
if (previous === void 0) return 0;
|
|
1180
|
+
return Math.max(0, port.scrollHeight - previous);
|
|
1181
|
+
}
|
|
978
1182
|
function hostShOf(port) {
|
|
979
1183
|
return port.scrollHeight;
|
|
980
1184
|
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Reveal characters the smoother still owes, read from the stream ledger the
|
|
1187
|
+
* smoother publishes each frame. Only meaningful together with
|
|
1188
|
+
* `producerComplete`: a non-zero backlog while the producer runs is ordinary
|
|
1189
|
+
* streaming, the same backlog after it stops is the `terminal-drain` phase.
|
|
1190
|
+
*/
|
|
1191
|
+
let followRemainingRevealChars = 0;
|
|
1192
|
+
function refreshTerminalRevealLedger() {
|
|
1193
|
+
const stream = readNewestStreamMetric();
|
|
1194
|
+
followRemainingRevealChars = stream?.backlog ?? 0;
|
|
1195
|
+
return {
|
|
1196
|
+
producerComplete: stream?.producerComplete ?? false,
|
|
1197
|
+
drain: (stream?.producerComplete ?? false) && (stream?.backlog ?? 0) > 0
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Screen-space reading-anchor delta for diagnostics only. Gated on the debug
|
|
1202
|
+
* runtime so the production hot path pays nothing, and it reuses the shared
|
|
1203
|
+
* `measureReadingAnchor` ledger, which already excludes the engine's own glide
|
|
1204
|
+
* and only reports motion the follower did not author.
|
|
1205
|
+
*/
|
|
1206
|
+
function measureAnchorDeltaForTelemetry(port) {
|
|
1207
|
+
if (!debugRuntime.isEnabled()) return null;
|
|
1208
|
+
return measureReadingAnchor(port)?.delta ?? null;
|
|
1209
|
+
}
|
|
981
1210
|
function invalidatePaintLimit(port) {
|
|
982
1211
|
followPaintLimits.delete(port);
|
|
983
1212
|
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Terminal-follow phase ledger. `terminal-drain` (producer stopped, reveal
|
|
1215
|
+
* queue still owes text) and `host-cascade` (host is swapping status rows,
|
|
1216
|
+
* collapsing Think, mounting the tail) demand opposite corrections: drain may
|
|
1217
|
+
* only continue upward toward the natural floor, while a cascade may need
|
|
1218
|
+
* temporary credit to hold the reading anchor. Keeping them in one readable
|
|
1219
|
+
* field is what lets the settle path stop treating ordinary completion as a
|
|
1220
|
+
* hostile host transaction.
|
|
1221
|
+
*/
|
|
1222
|
+
const followTerminalPhases = /* @__PURE__ */ new WeakMap();
|
|
1223
|
+
/**
|
|
1224
|
+
* Measured terminal budget per port: the owned bottom space this port is
|
|
1225
|
+
* allowed to keep while the producer has stopped and reveal work is still
|
|
1226
|
+
* draining. It is seeded from a real measurement (the owned offset plus the
|
|
1227
|
+
* length the last commit actually added) and may only shrink afterwards — a
|
|
1228
|
+
* budget that grows re-opens speculative blank space below the reply, which is
|
|
1229
|
+
* exactly the "lift" the terminal phase exists to remove.
|
|
1230
|
+
*/
|
|
1231
|
+
const followTerminalBudgets = /* @__PURE__ */ new WeakMap();
|
|
1232
|
+
/**
|
|
1233
|
+
* COMPLETION GROWTH CREDIT. Pixels of floor growth the reader actually
|
|
1234
|
+
* received in the completion window, which a shift rise may be funded from.
|
|
1235
|
+
*
|
|
1236
|
+
* This is a ledger rather than a per-frame clamp because growth and the
|
|
1237
|
+
* matching shift rise do not have to land on the same frame: a completion whose
|
|
1238
|
+
* final append covers several wraps commits its growth over two or three
|
|
1239
|
+
* frames, and a clamp that only ever compared against the PREVIOUS frame's
|
|
1240
|
+
* growth banked nothing across that gap. The uncovered remainder then had no
|
|
1241
|
+
* legal way to rise, so it survived as a real offset and was released later by
|
|
1242
|
+
* the pad retirement — the visible release-then-return this credit exists to
|
|
1243
|
+
* prevent. Credit is still bounded by growth the reader actually got, so a
|
|
1244
|
+
* rise can never manufacture motion that did not come from content.
|
|
1245
|
+
*/
|
|
1246
|
+
const followCompletionGrowthCredit = /* @__PURE__ */ new WeakMap();
|
|
984
1247
|
/** Logical position and velocity survive a React owner handoff and finish. */
|
|
985
1248
|
const followMotionStates = /* @__PURE__ */ new WeakMap();
|
|
986
1249
|
const followReaderHolds = /* @__PURE__ */ new WeakMap();
|
|
@@ -1129,6 +1392,7 @@ window.__ModuleLoader__.load({
|
|
|
1129
1392
|
if (last === void 0) return 0;
|
|
1130
1393
|
const status = turnStatusOf(port);
|
|
1131
1394
|
const composer = port.querySelector("[data-composer-seat]");
|
|
1395
|
+
if (status !== null) followHadStatus.add(port);
|
|
1132
1396
|
if (status !== null || composer !== null) followHadChrome.add(port);
|
|
1133
1397
|
const cached = followPaintLimits.get(port);
|
|
1134
1398
|
if (cached !== void 0 && performance.now() - cached.measuredAtMs <= 250 && cached.clientHeight === port.clientHeight && cached.surface === last && cached.status === status && cached.composer === composer) return cached.limit;
|
|
@@ -1203,6 +1467,35 @@ window.__ModuleLoader__.load({
|
|
|
1203
1467
|
function applyVisual(port, animatedH, reservePx, velocityPxPerSec = 0, runwayPx = 72, shiftCeilingPx = Number.POSITIVE_INFINITY, promoteAtRest = false, trajectoryShiftPx, dtMs = 16.7, writeScrollTop = true) {
|
|
1204
1468
|
const surfaces = shiftSurfacesOf(port);
|
|
1205
1469
|
ensureFlowFillsPort(port);
|
|
1470
|
+
if (followTerminalPhases.get(port) === "terminal-drain" && turnStatusOf(port) === null && !followHadStatus.has(port)) {
|
|
1471
|
+
restoreRunway(port);
|
|
1472
|
+
setFlowPad(port, 0);
|
|
1473
|
+
const contentHeight = Math.max(0, port.scrollHeight);
|
|
1474
|
+
const floor = Math.max(0, contentHeight - port.clientHeight);
|
|
1475
|
+
if (port.style.overflowAnchor !== "none") port.style.overflowAnchor = "none";
|
|
1476
|
+
if (port.style.scrollBehavior !== "auto") port.style.scrollBehavior = "auto";
|
|
1477
|
+
if (writeScrollTop) setFollowScrollTop(port, floor);
|
|
1478
|
+
followRunwayOffsetHistory.set(port, 0);
|
|
1479
|
+
followObservedContentHeight.set(port, contentHeight);
|
|
1480
|
+
followLastFloorPx.set(port, floor);
|
|
1481
|
+
followLastShiftPx.set(port, 0);
|
|
1482
|
+
followMotionStates.set(port, {
|
|
1483
|
+
capacityPx: Number.POSITIVE_INFINITY,
|
|
1484
|
+
constrained: false,
|
|
1485
|
+
extent: contentHeight,
|
|
1486
|
+
lagPx: 0,
|
|
1487
|
+
reservePx: 0,
|
|
1488
|
+
velocityPxPerSec: 0,
|
|
1489
|
+
terminalPhase: "terminal-drain",
|
|
1490
|
+
runwayPx: 0,
|
|
1491
|
+
baselineShiftPx: 0,
|
|
1492
|
+
anchorDeltaPx: measureAnchorDeltaForTelemetry(port),
|
|
1493
|
+
remainingRevealChars: followRemainingRevealChars
|
|
1494
|
+
});
|
|
1495
|
+
for (const surface of surfaces) setShift(surface, 0);
|
|
1496
|
+
FrameCoordinator.forDocument().markLayoutDirty();
|
|
1497
|
+
return contentHeight;
|
|
1498
|
+
}
|
|
1206
1499
|
{
|
|
1207
1500
|
const preFloor = Math.max(0, port.scrollHeight - port.clientHeight);
|
|
1208
1501
|
const lastFloorSeen = followFloorHistory.get(port);
|
|
@@ -1214,7 +1507,7 @@ window.__ModuleLoader__.load({
|
|
|
1214
1507
|
}
|
|
1215
1508
|
if (preFloor !== lastFloorSeen) followFloorHistory.set(port, preFloor);
|
|
1216
1509
|
}
|
|
1217
|
-
ensureRunway(port, surfaces, reservePx);
|
|
1510
|
+
ensureRunway(port, surfaces, followTerminalPhases.has(port) ? Math.max(Math.min(72, followTerminalBudgets.get(port) ?? 72), reservePx) : 72);
|
|
1218
1511
|
const contentHeight2 = Math.max(0, port.scrollHeight);
|
|
1219
1512
|
const runwayOffset2 = runwayOffsetOf(port);
|
|
1220
1513
|
const prevOffset2 = followRunwayOffsetHistory.get(port);
|
|
@@ -1222,6 +1515,7 @@ window.__ModuleLoader__.load({
|
|
|
1222
1515
|
followRunwayOffsetHistory.set(port, runwayOffset2);
|
|
1223
1516
|
const contentHeight = contentHeight2;
|
|
1224
1517
|
const runwayOffset = runwayOffset2;
|
|
1518
|
+
followObservedContentHeight.set(port, contentHeight2);
|
|
1225
1519
|
const targetHeight = Math.max(0, contentHeight - runwayOffset);
|
|
1226
1520
|
const floor = Math.max(0, contentHeight - port.clientHeight);
|
|
1227
1521
|
const extent = Math.min(targetHeight, Math.max(0, animatedH));
|
|
@@ -1260,7 +1554,15 @@ window.__ModuleLoader__.load({
|
|
|
1260
1554
|
if (previousShift !== void 0 && shift < previousShift - maxDecayPx) shift = previousShift - maxDecayPx;
|
|
1261
1555
|
if (followCompletionSettle.has(port) && previousShift !== void 0 && previousFloor !== void 0) {
|
|
1262
1556
|
const confirmedGrowthPx = Math.max(0, floor - previousFloor);
|
|
1263
|
-
|
|
1557
|
+
let credit = (followCompletionGrowthCredit.get(port) ?? 0) + confirmedGrowthPx;
|
|
1558
|
+
if (shift > previousShift) {
|
|
1559
|
+
const rise = shift - previousShift;
|
|
1560
|
+
const spend = Math.min(Math.min(rise, 24), credit);
|
|
1561
|
+
const unfunded = rise - spend;
|
|
1562
|
+
if (unfunded > 0) shift -= unfunded;
|
|
1563
|
+
credit -= spend;
|
|
1564
|
+
}
|
|
1565
|
+
followCompletionGrowthCredit.set(port, credit);
|
|
1264
1566
|
}
|
|
1265
1567
|
followLastShiftPx.set(port, shift);
|
|
1266
1568
|
const requestedShift = trajectoryShiftPx ?? baselineShift + requestedLag;
|
|
@@ -1275,11 +1577,17 @@ window.__ModuleLoader__.load({
|
|
|
1275
1577
|
extent: effectiveExtent,
|
|
1276
1578
|
lagPx: effectiveLag,
|
|
1277
1579
|
reservePx: visibleReserve,
|
|
1278
|
-
velocityPxPerSec
|
|
1580
|
+
velocityPxPerSec,
|
|
1581
|
+
terminalPhase: followTerminalPhases.get(port) ?? "live",
|
|
1582
|
+
runwayPx: runwayOffset,
|
|
1583
|
+
baselineShiftPx: baselineShift,
|
|
1584
|
+
anchorDeltaPx: followTerminalPhases.has(port) ? measureAnchorDeltaForTelemetry(port) : null,
|
|
1585
|
+
remainingRevealChars: followRemainingRevealChars
|
|
1279
1586
|
});
|
|
1280
1587
|
for (const surface of surfaces) setShift(surface, shift);
|
|
1281
1588
|
const status = turnStatusOf(port);
|
|
1282
1589
|
if (status !== null) setShift(status, 0);
|
|
1590
|
+
FrameCoordinator.forDocument().markLayoutDirty();
|
|
1283
1591
|
return effectiveExtent;
|
|
1284
1592
|
}
|
|
1285
1593
|
function clearMotion(port) {
|
|
@@ -1305,8 +1613,10 @@ window.__ModuleLoader__.load({
|
|
|
1305
1613
|
element.style.clipPath = "";
|
|
1306
1614
|
}
|
|
1307
1615
|
/** Remove equal offsets, land on the floor, then retire the compositor quietly. */
|
|
1308
|
-
function finishAtNaturalFloor(port, retainCompositor = true, writeScrollTop = true) {
|
|
1616
|
+
function finishAtNaturalFloor(port, retainCompositor = true, writeScrollTop = true, deferCompositor = false) {
|
|
1309
1617
|
followCompletionSettle.delete(port);
|
|
1618
|
+
followTerminalPhases.set(port, "natural");
|
|
1619
|
+
followCompletionGrowthCredit.delete(port);
|
|
1310
1620
|
followTraceUntilMs = Math.max(followTraceUntilMs, performance.now() + 1e4);
|
|
1311
1621
|
followTrace("finish-enter", {
|
|
1312
1622
|
sh: hostShOf(port),
|
|
@@ -1329,6 +1639,10 @@ window.__ModuleLoader__.load({
|
|
|
1329
1639
|
port.removeAttribute(FOLLOW_OWNED_ATTR);
|
|
1330
1640
|
port.style.overflowAnchor = "";
|
|
1331
1641
|
port.style.scrollBehavior = "";
|
|
1642
|
+
if (deferCompositor) {
|
|
1643
|
+
followMotionStates.delete(port);
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1332
1646
|
for (const surface of surfaces) if (promotedSet.has(surface)) holdCompositorAtRest(surface);
|
|
1333
1647
|
else setShift(surface, 0);
|
|
1334
1648
|
if (status !== null) {
|
|
@@ -1382,13 +1696,20 @@ window.__ModuleLoader__.load({
|
|
|
1382
1696
|
const startedAsEntrance = entrance;
|
|
1383
1697
|
const owner = {};
|
|
1384
1698
|
const generation = ++followGeneration;
|
|
1385
|
-
let rafId = 0;
|
|
1386
1699
|
let last = performance.now();
|
|
1387
1700
|
let following = true;
|
|
1388
1701
|
let primed = false;
|
|
1389
1702
|
let animatedH = 0;
|
|
1390
1703
|
let reservePx = 0;
|
|
1391
1704
|
let velocityPxPerSec = 0;
|
|
1705
|
+
/**
|
|
1706
|
+
* Scroll length the most recent observed commit actually added. This is the
|
|
1707
|
+
* honest measure of how much room the still-draining text needs, and it is
|
|
1708
|
+
* what seeds the terminal budget in place of the fixed 72px streaming
|
|
1709
|
+
* runway.
|
|
1710
|
+
*/
|
|
1711
|
+
let lastCommitGrowthPx = 0;
|
|
1712
|
+
let lastObservedContentHeight = 0;
|
|
1392
1713
|
let interacting = false;
|
|
1393
1714
|
let readerGestureIntent = false;
|
|
1394
1715
|
let readerReleased = false;
|
|
@@ -1412,7 +1733,28 @@ window.__ModuleLoader__.load({
|
|
|
1412
1733
|
const revealPhase = new FollowRevealPhaseTracker();
|
|
1413
1734
|
let holding = null;
|
|
1414
1735
|
let entrancePending = entranceRef.current;
|
|
1736
|
+
/**
|
|
1737
|
+
* Tall-row entrance clamp. A surface shift can only mask lag up to the
|
|
1738
|
+
* real gap to status/composer chrome; a row that commits more than that
|
|
1739
|
+
* (an Edit call with its diff, a wide tool card) repaints the remainder
|
|
1740
|
+
* as an instant upward step — and the 0.2.x host's order-length tail
|
|
1741
|
+
* snap pins scrollTop to the new floor in the same task, so no
|
|
1742
|
+
* scroll-domain lag can be carried either. Reveal the excess through the
|
|
1743
|
+
* wrapper's REAL height instead: clamp it to 0 at prime and raise it at
|
|
1744
|
+
* the spring cadence, so the floor grows gradually and every bottom
|
|
1745
|
+
* writer (this engine and the host snap alike) glides the row in.
|
|
1746
|
+
*/
|
|
1747
|
+
let entranceClamp = null;
|
|
1748
|
+
const releaseEntranceClamp = () => {
|
|
1749
|
+
if (entranceClamp === null) return;
|
|
1750
|
+
const clamp = entranceClamp;
|
|
1751
|
+
entranceClamp = null;
|
|
1752
|
+
clamp.element.style.maxHeight = clamp.maxHeight;
|
|
1753
|
+
clamp.element.style.overflow = clamp.overflow;
|
|
1754
|
+
};
|
|
1755
|
+
const entranceClampLagPx = () => entranceClamp === null ? 0 : Math.max(0, entranceClamp.extent - entranceClamp.revealedPx);
|
|
1415
1756
|
const finishEntrance = () => {
|
|
1757
|
+
releaseEntranceClamp();
|
|
1416
1758
|
if (!entrancePending) return;
|
|
1417
1759
|
entrancePending = false;
|
|
1418
1760
|
onEntranceSettledRef.current?.();
|
|
@@ -1435,6 +1777,8 @@ window.__ModuleLoader__.load({
|
|
|
1435
1777
|
};
|
|
1436
1778
|
const reportFollow = (next, isActive) => {
|
|
1437
1779
|
const state = followMotionStates.get(next);
|
|
1780
|
+
const phase = followTerminalPhases.get(next) ?? (isActive ? "live" : "natural");
|
|
1781
|
+
const runwayPx = state?.runwayPx ?? runwayOffsetOf(next);
|
|
1438
1782
|
debugRuntime.reportFollow(next, {
|
|
1439
1783
|
lagPx: state ? state.lagPx : -1,
|
|
1440
1784
|
velocityPxPerSec: state?.velocityPxPerSec ?? 0,
|
|
@@ -1446,7 +1790,13 @@ window.__ModuleLoader__.load({
|
|
|
1446
1790
|
scrollTop: next.scrollTop,
|
|
1447
1791
|
scrollHeight: next.scrollHeight,
|
|
1448
1792
|
clientHeight: next.clientHeight,
|
|
1449
|
-
active: isActive
|
|
1793
|
+
active: isActive,
|
|
1794
|
+
terminalPhase: phase,
|
|
1795
|
+
runwayPx,
|
|
1796
|
+
terminalBudgetPx: followTerminalBudgets.get(next) ?? runwayPx,
|
|
1797
|
+
baselineShiftPx: state?.baselineShiftPx ?? Math.max(0, runwayPx - (state?.reservePx ?? 0)),
|
|
1798
|
+
readingAnchorDeltaPx: state?.anchorDeltaPx ?? null,
|
|
1799
|
+
remainingRevealChars: followRemainingRevealChars
|
|
1450
1800
|
});
|
|
1451
1801
|
};
|
|
1452
1802
|
const isLeader = (next) => followLeaders.get(next)?.owner === owner;
|
|
@@ -1679,23 +2029,29 @@ window.__ModuleLoader__.load({
|
|
|
1679
2029
|
observedTail = tail;
|
|
1680
2030
|
if (tail !== null) resize.observe(tail);
|
|
1681
2031
|
};
|
|
2032
|
+
const coordinator = FrameCoordinator.forDocument();
|
|
2033
|
+
const frameTaskRef = { id: null };
|
|
2034
|
+
const stopFollowTask = () => {
|
|
2035
|
+
if (frameTaskRef.id === null) return;
|
|
2036
|
+
coordinator.unregisterTask(frameTaskRef.id);
|
|
2037
|
+
frameTaskRef.id = null;
|
|
2038
|
+
};
|
|
1682
2039
|
const frame = (now) => {
|
|
1683
|
-
rafId = requestAnimationFrame(frame);
|
|
1684
2040
|
const elapsedMs = Math.max(.001, now - last);
|
|
1685
2041
|
const dt = Math.min(32, elapsedMs);
|
|
1686
2042
|
const tuning = debugRuntime.activeTuning();
|
|
1687
2043
|
last = now;
|
|
1688
2044
|
const root = rootRef.current;
|
|
1689
|
-
if (root === null) return;
|
|
2045
|
+
if (root === null) return activeRef.current;
|
|
1690
2046
|
const nextPort = root.closest("[data-conversation-scroll]");
|
|
1691
|
-
if (nextPort === null) return;
|
|
2047
|
+
if (nextPort === null) return activeRef.current;
|
|
1692
2048
|
bindPort(nextPort);
|
|
1693
2049
|
observeTailSurface();
|
|
1694
2050
|
resetHostScrollOwnershipForNewTurn(nextPort);
|
|
1695
2051
|
hostOwnsScroll = followHostScrollPorts.has(nextPort);
|
|
1696
2052
|
if (activeRef.current) followActivePorts.add(nextPort);
|
|
1697
2053
|
else followActivePorts.delete(nextPort);
|
|
1698
|
-
if (nextPort.clientHeight <= 0) return;
|
|
2054
|
+
if (nextPort.clientHeight <= 0) return true;
|
|
1699
2055
|
const floor = Math.max(0, nextPort.scrollHeight - nextPort.clientHeight);
|
|
1700
2056
|
const reportedLag = floor - nextPort.scrollTop;
|
|
1701
2057
|
const extent = Math.min(nextPort.scrollHeight, Math.max(0, nextPort.scrollHeight - reportedLag));
|
|
@@ -1703,14 +2059,37 @@ window.__ModuleLoader__.load({
|
|
|
1703
2059
|
if (completionSettleGuardsPort(nextPort)) {
|
|
1704
2060
|
primed = true;
|
|
1705
2061
|
following = false;
|
|
1706
|
-
return;
|
|
2062
|
+
return activeRef.current;
|
|
2063
|
+
}
|
|
2064
|
+
if (activeRef.current) {
|
|
2065
|
+
followTerminalPhases.delete(nextPort);
|
|
2066
|
+
followTerminalBudgets.delete(nextPort);
|
|
2067
|
+
followCompletionGrowthCredit.delete(nextPort);
|
|
2068
|
+
followHadStatus.delete(nextPort);
|
|
1707
2069
|
}
|
|
1708
2070
|
const inherited = nextPort.hasAttribute(FOLLOW_OWNED_ATTR) ? followMotionStates.get(nextPort) : void 0;
|
|
1709
2071
|
if (inherited === void 0) {
|
|
1710
2072
|
const entranceExtent = entrancePending ? entranceExtentRef?.current ?? entranceExtentOf(root) : 0;
|
|
1711
2073
|
const predictGrowth = predictiveRef?.current ?? predictive;
|
|
1712
2074
|
animatedH = entrancePending ? Math.max(0, nextPort.scrollHeight - entranceExtent) : nextPort.scrollHeight;
|
|
2075
|
+
if (entrancePending && entranceExtent > safeShiftLimit(nextPort, shiftSurfacesOf(nextPort)) + .25) {
|
|
2076
|
+
const fullPx = root.offsetHeight;
|
|
2077
|
+
if (fullPx > 0) {
|
|
2078
|
+
const basePx = Math.max(0, fullPx - entranceExtent);
|
|
2079
|
+
entranceClamp = {
|
|
2080
|
+
element: root,
|
|
2081
|
+
extent: entranceExtent,
|
|
2082
|
+
basePx,
|
|
2083
|
+
revealedPx: 0,
|
|
2084
|
+
maxHeight: root.style.maxHeight,
|
|
2085
|
+
overflow: root.style.overflow
|
|
2086
|
+
};
|
|
2087
|
+
root.style.overflow = "hidden";
|
|
2088
|
+
root.style.maxHeight = `${basePx}px`;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
1713
2091
|
const hasStatus = turnStatusOf(nextPort) !== null;
|
|
2092
|
+
if (hasStatus) followHadStatus.add(nextPort);
|
|
1714
2093
|
reservePx = Math.max(ownedBottomSpaceOf(nextPort), predictGrowth && (hasStatus || speedCpsRef.current > 90) ? computeFollowReserve(speedCpsRef.current, tuning.runwayPx) : 0);
|
|
1715
2094
|
if (!completionSettleGuardsPort(nextPort)) setFlowPad(nextPort, 0);
|
|
1716
2095
|
statusWasPresent = hasStatus;
|
|
@@ -1745,11 +2124,11 @@ window.__ModuleLoader__.load({
|
|
|
1745
2124
|
updateRevealScale(nextPort, elapsedMs);
|
|
1746
2125
|
reportFollow(nextPort, activeRef.current);
|
|
1747
2126
|
const runwayOffset = runwayOffsetOf(nextPort);
|
|
1748
|
-
if (Math.max(0, nextPort.scrollHeight - animatedH - runwayOffset) <= .25) finishEntrance();
|
|
2127
|
+
if (Math.max(0, nextPort.scrollHeight - animatedH - runwayOffset) + entranceClampLagPx() <= .25) finishEntrance();
|
|
1749
2128
|
} else finishEntrance();
|
|
1750
2129
|
} else finishEntrance();
|
|
1751
2130
|
primed = true;
|
|
1752
|
-
return;
|
|
2131
|
+
return activeRef.current;
|
|
1753
2132
|
}
|
|
1754
2133
|
if (!following && (!interacting || readerReleased && !readerGestureIntent && reportedLag <= 1) && reportedLag <= (readerReleased ? 1 : 25)) {
|
|
1755
2134
|
following = true;
|
|
@@ -1775,22 +2154,23 @@ window.__ModuleLoader__.load({
|
|
|
1775
2154
|
if (!activeRef.current || !following) {
|
|
1776
2155
|
followScrollLedgers.set(nextPort, nextPort.scrollTop);
|
|
1777
2156
|
reportFollow(nextPort, activeRef.current);
|
|
1778
|
-
return;
|
|
2157
|
+
return activeRef.current;
|
|
1779
2158
|
}
|
|
1780
2159
|
detectHostScroll(nextPort, floor);
|
|
1781
2160
|
if (hostOwnsScroll) {
|
|
1782
2161
|
followScrollLedgers.set(nextPort, nextPort.scrollTop);
|
|
1783
2162
|
reportFollow(nextPort, false);
|
|
1784
|
-
return;
|
|
2163
|
+
return false;
|
|
1785
2164
|
}
|
|
1786
2165
|
hold(nextPort);
|
|
1787
2166
|
if (!isLeader(nextPort)) {
|
|
1788
2167
|
finishEntrance();
|
|
1789
|
-
return;
|
|
2168
|
+
return true;
|
|
1790
2169
|
}
|
|
1791
2170
|
const predictGrowth = predictiveRef?.current ?? predictive;
|
|
1792
2171
|
const statusElement = turnStatusOf(nextPort);
|
|
1793
2172
|
const hasStatus = statusElement !== null;
|
|
2173
|
+
if (hasStatus) followHadStatus.add(nextPort);
|
|
1794
2174
|
if (statusElement !== null) lastStatusHeightPx = statusElement.offsetHeight;
|
|
1795
2175
|
const statusJustRemoved = predictGrowth && statusWasPresent === true && !hasStatus;
|
|
1796
2176
|
if (statusJustRemoved) reservePx = Math.max(reservePx, tuning.runwayPx) + lastStatusHeightPx;
|
|
@@ -1800,10 +2180,25 @@ window.__ModuleLoader__.load({
|
|
|
1800
2180
|
const effectiveReserveTarget = Math.max(reservePx, pressureReserveTarget);
|
|
1801
2181
|
const reserveStep = 1 - Math.exp(-elapsedMs / tuning.reserveResponseMs);
|
|
1802
2182
|
reservePx += (effectiveReserveTarget - reservePx) * reserveStep;
|
|
1803
|
-
if (
|
|
2183
|
+
if (refreshTerminalRevealLedger().drain) {
|
|
2184
|
+
if (!followTerminalPhases.has(nextPort)) {
|
|
2185
|
+
const seeded = Math.min(72, Math.max(runwayOffsetOf(nextPort), 0) + Math.max(0, lastCommitGrowthPx));
|
|
2186
|
+
followTerminalPhases.set(nextPort, "terminal-drain");
|
|
2187
|
+
followTerminalBudgets.set(nextPort, seeded);
|
|
2188
|
+
followTrace("terminal-drain", {
|
|
2189
|
+
budget: Math.round(seeded),
|
|
2190
|
+
reserve: Math.round(reservePx),
|
|
2191
|
+
backlog: followRemainingRevealChars
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
const terminalBudget = followTerminalPhases.has(nextPort) ? Math.max(Math.min(followTerminalBudgets.get(nextPort) ?? 72, 72), reservePx) : 72;
|
|
2196
|
+
if (predictGrowth || runwayOffsetOf(nextPort) > .5) ensureRunway(nextPort, shiftSurfacesOf(nextPort), terminalBudget);
|
|
1804
2197
|
const runwayOffset = runwayOffsetOf(nextPort);
|
|
1805
2198
|
const contentHeight = nextPort.scrollHeight;
|
|
1806
2199
|
const floorNow = Math.max(0, contentHeight - nextPort.clientHeight);
|
|
2200
|
+
lastCommitGrowthPx = lastObservedContentHeight > 0 ? Math.max(0, contentHeight - lastObservedContentHeight) : 0;
|
|
2201
|
+
lastObservedContentHeight = contentHeight;
|
|
1807
2202
|
const trajectoryActive = predictive && root.querySelector("[data-variant=\"think\"]") === null && runwayOffset > 0;
|
|
1808
2203
|
let trajectoryShift;
|
|
1809
2204
|
if (trajectoryActive) {
|
|
@@ -1861,18 +2256,38 @@ window.__ModuleLoader__.load({
|
|
|
1861
2256
|
trajectoryPositionPx = null;
|
|
1862
2257
|
trajectoryWasActive = false;
|
|
1863
2258
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
2259
|
+
if (entranceClamp !== null) {
|
|
2260
|
+
const clamp = entranceClamp;
|
|
2261
|
+
const remainingPx = Math.max(0, clamp.extent - clamp.revealedPx);
|
|
2262
|
+
const clampStep = computeFollowStep(dt, {
|
|
2263
|
+
lag: remainingPx,
|
|
2264
|
+
speedEma: speedCpsRef.current,
|
|
2265
|
+
velocityPxPerSec
|
|
2266
|
+
}, tuning);
|
|
2267
|
+
if (remainingPx <= .1) {
|
|
2268
|
+
clamp.revealedPx = clamp.extent;
|
|
2269
|
+
clamp.element.style.maxHeight = `${clamp.basePx + clamp.extent}px`;
|
|
2270
|
+
velocityPxPerSec = 0;
|
|
2271
|
+
} else {
|
|
2272
|
+
clamp.revealedPx = Math.min(clamp.extent, clamp.revealedPx + clampStep.advancePx);
|
|
2273
|
+
clamp.element.style.maxHeight = `${clamp.basePx + clamp.revealedPx}px`;
|
|
2274
|
+
velocityPxPerSec = clampStep.velocityPxPerSec;
|
|
2275
|
+
}
|
|
1871
2276
|
animatedH = contentHeight - runwayOffset;
|
|
1872
|
-
velocityPxPerSec = 0;
|
|
1873
2277
|
} else {
|
|
1874
|
-
|
|
1875
|
-
|
|
2278
|
+
const lag = Math.max(0, contentHeight - animatedH - runwayOffset);
|
|
2279
|
+
const step = computeFollowStep(dt, {
|
|
2280
|
+
lag,
|
|
2281
|
+
speedEma: speedCpsRef.current,
|
|
2282
|
+
velocityPxPerSec
|
|
2283
|
+
}, tuning);
|
|
2284
|
+
if (lag <= .1) {
|
|
2285
|
+
animatedH = contentHeight - runwayOffset;
|
|
2286
|
+
velocityPxPerSec = 0;
|
|
2287
|
+
} else {
|
|
2288
|
+
animatedH = Math.min(contentHeight - runwayOffset, animatedH + step.advancePx);
|
|
2289
|
+
velocityPxPerSec = step.velocityPxPerSec;
|
|
2290
|
+
}
|
|
1876
2291
|
}
|
|
1877
2292
|
}
|
|
1878
2293
|
animatedH = applyVisual(nextPort, animatedH, reservePx, velocityPxPerSec, tuning.runwayPx, Number.POSITIVE_INFINITY, !predictGrowth, trajectoryShift, elapsedMs, !hostOwnsScroll);
|
|
@@ -1883,12 +2298,15 @@ window.__ModuleLoader__.load({
|
|
|
1883
2298
|
if (!activeRef.current) enforceReadingAnchor(nextPort);
|
|
1884
2299
|
else measureReadingAnchor(nextPort);
|
|
1885
2300
|
}
|
|
1886
|
-
if (Math.max(0, nextPort.scrollHeight - animatedH - runwayOffsetOf(nextPort)) <= .25) finishEntrance();
|
|
2301
|
+
if (Math.max(0, nextPort.scrollHeight - animatedH - runwayOffsetOf(nextPort)) + entranceClampLagPx() <= .25) finishEntrance();
|
|
2302
|
+
return true;
|
|
1887
2303
|
};
|
|
2304
|
+
frameTaskRef.id = coordinator.registerTask({ onSimulate: (_dtMs, now) => frame(now) });
|
|
1888
2305
|
frame(performance.now());
|
|
1889
2306
|
return () => {
|
|
2307
|
+
releaseEntranceClamp();
|
|
1890
2308
|
if (!controlScrollRef.current) {
|
|
1891
|
-
|
|
2309
|
+
stopFollowTask();
|
|
1892
2310
|
if (port !== null) followActivePorts.delete(port);
|
|
1893
2311
|
unsubscribeCommit?.();
|
|
1894
2312
|
resize?.disconnect();
|
|
@@ -1904,7 +2322,7 @@ window.__ModuleLoader__.load({
|
|
|
1904
2322
|
releaseRevealScale();
|
|
1905
2323
|
return;
|
|
1906
2324
|
}
|
|
1907
|
-
|
|
2325
|
+
stopFollowTask();
|
|
1908
2326
|
if (port !== null) followActivePorts.delete(port);
|
|
1909
2327
|
unsubscribeCommit?.();
|
|
1910
2328
|
if (interactTimer !== null) clearTimeout(interactTimer);
|
|
@@ -1935,18 +2353,30 @@ window.__ModuleLoader__.load({
|
|
|
1935
2353
|
}
|
|
1936
2354
|
let settleQuietMs = 0;
|
|
1937
2355
|
let settleSig = "";
|
|
1938
|
-
|
|
1939
|
-
const currentRunway = runwayOffsetOf(host);
|
|
1940
|
-
const currentShift = Math.abs(currentShiftOf(shiftSurfacesOf(host).at(-1) ?? host));
|
|
1941
|
-
if (!activeRef.current && lagBeforeCompletionPaint <= 25 && currentRunway <= .25 && currentShift <= .25 && flowPadOf(host) <= .25) {
|
|
2356
|
+
if (!activeRef.current) {
|
|
1942
2357
|
followTraceUntilMs = Math.max(followTraceUntilMs, performance.now() + 1e4);
|
|
1943
2358
|
followTrace("fast-gate", {
|
|
1944
2359
|
sh: host.scrollHeight,
|
|
1945
2360
|
st: Math.round(host.scrollTop),
|
|
1946
2361
|
pad: Math.round(flowPadOf(host))
|
|
1947
2362
|
});
|
|
1948
|
-
|
|
2363
|
+
const stableTail = turnStatusOf(host) === null && followTerminalPhases.get(host) !== "host-cascade" && !followHadStatus.has(host);
|
|
2364
|
+
const ownedRunway = runwayOffsetOf(host);
|
|
2365
|
+
if (stableTail && (ownedRunway > .25 || flowPadOf(host) > .25)) {
|
|
2366
|
+
restoreRunway(host);
|
|
2367
|
+
setFlowPad(host, 0);
|
|
2368
|
+
finishAtNaturalFloor(host, !startedAsEntrance, true);
|
|
2369
|
+
followLeaders.delete(host);
|
|
2370
|
+
followCompletionSettle.delete(host);
|
|
2371
|
+
releaseRevealScale();
|
|
2372
|
+
debugRuntime.reportFollow(host, null);
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
restoreRunway(host);
|
|
2376
|
+
setFlowPad(host, 0);
|
|
2377
|
+
finishAtNaturalFloor(host, !startedAsEntrance, true);
|
|
1949
2378
|
followLeaders.delete(host);
|
|
2379
|
+
followCompletionSettle.delete(host);
|
|
1950
2380
|
releaseRevealScale();
|
|
1951
2381
|
debugRuntime.reportFollow(host, null);
|
|
1952
2382
|
return;
|
|
@@ -1963,6 +2393,9 @@ window.__ModuleLoader__.load({
|
|
|
1963
2393
|
return;
|
|
1964
2394
|
}
|
|
1965
2395
|
followTraceUntilMs = performance.now() + 15e3;
|
|
2396
|
+
followTerminalPhases.set(host, statusWasPresent === true || turnStatusOf(host) !== null || followHadStatus.has(host) ? "host-cascade" : "terminal-drain");
|
|
2397
|
+
if (!followTerminalBudgets.has(host)) followTerminalBudgets.set(host, Math.min(72, Math.max(0, reservePx)));
|
|
2398
|
+
followCompletionGrowthCredit.set(host, Math.max(0, environmentCommitGrowthPx(host)));
|
|
1966
2399
|
if (pruneDeadRunway(host)) {
|
|
1967
2400
|
followTrace("cleanup-dead-margin", {
|
|
1968
2401
|
sh: host.scrollHeight,
|
|
@@ -1989,7 +2422,14 @@ window.__ModuleLoader__.load({
|
|
|
1989
2422
|
return;
|
|
1990
2423
|
}
|
|
1991
2424
|
for (const name of GESTURE_EVENTS) host.addEventListener(name, markGesture, { passive: true });
|
|
2425
|
+
const settleTaskRef = { id: null };
|
|
2426
|
+
const stopSettleTask = () => {
|
|
2427
|
+
if (settleTaskRef.id === null) return;
|
|
2428
|
+
coordinator.unregisterTask(settleTaskRef.id);
|
|
2429
|
+
settleTaskRef.id = null;
|
|
2430
|
+
};
|
|
1992
2431
|
const stopSettleListeners = () => {
|
|
2432
|
+
stopSettleTask();
|
|
1993
2433
|
for (const name of GESTURE_EVENTS) host.removeEventListener(name, markGesture);
|
|
1994
2434
|
resize?.disconnect();
|
|
1995
2435
|
mutations?.disconnect();
|
|
@@ -2023,7 +2463,7 @@ window.__ModuleLoader__.load({
|
|
|
2023
2463
|
const settleFrame = (now) => {
|
|
2024
2464
|
if (!isLeader(host)) {
|
|
2025
2465
|
stopSettleListeners();
|
|
2026
|
-
return;
|
|
2466
|
+
return false;
|
|
2027
2467
|
}
|
|
2028
2468
|
if (interacting && (readerGestureIntent || readerScrolledUp(host))) {
|
|
2029
2469
|
readerGestureIntent = false;
|
|
@@ -2034,7 +2474,7 @@ window.__ModuleLoader__.load({
|
|
|
2034
2474
|
releaseRevealScale();
|
|
2035
2475
|
debugRuntime.reportFollow(host, null);
|
|
2036
2476
|
stopSettleListeners();
|
|
2037
|
-
return;
|
|
2477
|
+
return false;
|
|
2038
2478
|
}
|
|
2039
2479
|
const dt = Math.min(32, Math.max(0, now - settleLast));
|
|
2040
2480
|
const tuning = debugRuntime.activeTuning();
|
|
@@ -2049,17 +2489,22 @@ window.__ModuleLoader__.load({
|
|
|
2049
2489
|
releaseRevealScale();
|
|
2050
2490
|
debugRuntime.reportFollow(host, null);
|
|
2051
2491
|
stopSettleListeners();
|
|
2052
|
-
return;
|
|
2492
|
+
return false;
|
|
2053
2493
|
}
|
|
2054
2494
|
const guardDelta = !settleRetiring && !followActivePorts.has(host) ? enforceReadingAnchor(host, true) : null;
|
|
2055
2495
|
settleQuietMs = !settleRetiring && (guardDelta === null || Math.abs(guardDelta) <= .5) ? settleQuietMs + dt : 0;
|
|
2496
|
+
if (followTerminalPhases.get(host) !== "host-cascade") followTerminalPhases.set(host, turnStatusOf(host) === null ? "terminal-drain" : "host-cascade");
|
|
2056
2497
|
const settleStatus = turnStatusOf(host);
|
|
2057
2498
|
const ownedRunwayPx = runwayOffsetOf(host);
|
|
2058
2499
|
if (ownedRunwayPx > .25) {
|
|
2500
|
+
const terminalDrain = followTerminalPhases.get(host) === "terminal-drain";
|
|
2501
|
+
const earnedBudgetPx = Math.min(72, Math.max(followTerminalBudgets.get(host) ?? 72, reservePx));
|
|
2059
2502
|
const requestedTransferPx = settleStatus === null ? ownedRunwayPx : Math.min(reservePx, (tuning.runwayPx || 72) / FOLLOW_RUNWAY_RETIRE_MS * dt);
|
|
2060
|
-
const
|
|
2503
|
+
const cappedTransferPx = terminalDrain ? Math.min(requestedTransferPx, Math.max(0, ownedRunwayPx - earnedBudgetPx)) : requestedTransferPx;
|
|
2504
|
+
const transferredPx = transferRunwayToFlowPad(host, cappedTransferPx);
|
|
2061
2505
|
if (transferredPx > 0) setFlowPad(host, Math.max(0, flowPadOf(host) - transferredPx));
|
|
2062
2506
|
reservePx = Math.max(0, reservePx - transferredPx);
|
|
2507
|
+
followTerminalBudgets.set(host, Math.max(reservePx, Math.min(earnedBudgetPx, runwayOffsetOf(host))));
|
|
2063
2508
|
animatedH += transferredPx;
|
|
2064
2509
|
}
|
|
2065
2510
|
const runwayOffset = runwayOffsetOf(host);
|
|
@@ -2093,7 +2538,7 @@ window.__ModuleLoader__.load({
|
|
|
2093
2538
|
releaseRevealScale();
|
|
2094
2539
|
debugRuntime.reportFollow(host, null);
|
|
2095
2540
|
stopSettleListeners();
|
|
2096
|
-
return;
|
|
2541
|
+
return false;
|
|
2097
2542
|
}
|
|
2098
2543
|
const step = computeFollowStep(dt, {
|
|
2099
2544
|
lag,
|
|
@@ -2119,9 +2564,9 @@ window.__ModuleLoader__.load({
|
|
|
2119
2564
|
}
|
|
2120
2565
|
}
|
|
2121
2566
|
reportFollow(host, false);
|
|
2122
|
-
|
|
2567
|
+
return true;
|
|
2123
2568
|
};
|
|
2124
|
-
|
|
2569
|
+
settleTaskRef.id = coordinator.registerTask({ onSimulate: (_dtMs, now) => settleFrame(now) });
|
|
2125
2570
|
};
|
|
2126
2571
|
}, [
|
|
2127
2572
|
active,
|
|
@@ -2432,7 +2877,8 @@ window.__ModuleLoader__.load({
|
|
|
2432
2877
|
speedCps: revealSpeedCps,
|
|
2433
2878
|
targetChars: targetCount,
|
|
2434
2879
|
displayedChars: displayedCount,
|
|
2435
|
-
active: !producerComplete
|
|
2880
|
+
active: !producerComplete || backlog > 0,
|
|
2881
|
+
producerComplete
|
|
2436
2882
|
});
|
|
2437
2883
|
if (holdBackRef.current?.() === true) {
|
|
2438
2884
|
rafRef.current = requestAnimationFrame(tick);
|
|
@@ -2548,14 +2994,12 @@ window.__ModuleLoader__.load({
|
|
|
2548
2994
|
const elementRef = (0, react.useRef)(null);
|
|
2549
2995
|
(0, react.useEffect)(() => {
|
|
2550
2996
|
if (!active) return;
|
|
2551
|
-
let rafId = 0;
|
|
2552
2997
|
let lastDebugReport = 0;
|
|
2553
2998
|
const frame = (now) => {
|
|
2554
|
-
rafId = requestAnimationFrame(frame);
|
|
2555
2999
|
const fps = fpsRef.current;
|
|
2556
3000
|
if (fps.lastMs === 0) {
|
|
2557
3001
|
fps.lastMs = now;
|
|
2558
|
-
return;
|
|
3002
|
+
return true;
|
|
2559
3003
|
}
|
|
2560
3004
|
const delta = Math.min(MAX_FRAME_MS, Math.max(1, now - fps.lastMs));
|
|
2561
3005
|
fps.lastMs = now;
|
|
@@ -2572,10 +3016,12 @@ window.__ModuleLoader__.load({
|
|
|
2572
3016
|
debugRuntime.reportFps(currentFps, fps.emaMs, fps.degraded);
|
|
2573
3017
|
lastDebugReport = now;
|
|
2574
3018
|
}
|
|
3019
|
+
return true;
|
|
2575
3020
|
};
|
|
2576
|
-
|
|
3021
|
+
const coordinator = FrameCoordinator.forDocument();
|
|
3022
|
+
const taskId = coordinator.registerTask({ onSimulate: (_dtMs, now) => frame(now) });
|
|
2577
3023
|
return () => {
|
|
2578
|
-
|
|
3024
|
+
coordinator.unregisterTask(taskId);
|
|
2579
3025
|
fpsRef.current = {
|
|
2580
3026
|
emaMs: 0,
|
|
2581
3027
|
lastMs: 0,
|
|
@@ -2605,6 +3051,716 @@ window.__ModuleLoader__.load({
|
|
|
2605
3051
|
};
|
|
2606
3052
|
}
|
|
2607
3053
|
//#endregion
|
|
3054
|
+
//#region \0dsh-css:/Users/dzlin/.codex/worktrees/pr-39-review/dsh-smooth-stream/src/client/LogarithmicFade.module.css.mjs
|
|
3055
|
+
const css$3 = ".Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-0),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-0){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 0.0%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-1),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-1){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 3.22581%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-2),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-2){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 6.45161%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-3),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-3){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 9.67742%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-4),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-4){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 12.9032%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-5),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-5){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 16.129%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-6),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-6){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 19.3548%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-7),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-7){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 22.5807%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-8),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-8){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 25.8065%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-9),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-9){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 29.0323%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-10),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-10){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 32.2581%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-11),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-11){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 35.4839%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-12),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-12){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 38.7097%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-13),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-13){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 41.9355%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-14),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-14){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 45.1613%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-15),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-15){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 48.3871%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-16),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-16){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 51.6129%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-17),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-17){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 54.8387%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-18),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-18){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 58.0645%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-19),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-19){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 61.2903%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-20),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-20){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 64.5161%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-21),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-21){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 67.7419%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-22),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-22){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 70.9677%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-23),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-23){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 74.1936%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-24),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-24){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 77.4194%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-25),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-25){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 80.6452%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-26),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-26){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 83.871%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-27),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-27){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 87.0968%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-28),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-28){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 90.3226%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-29),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-29){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 93.5484%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-30),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-30){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 96.7742%, transparent)}.Sv_6BW_scope::highlight(Sv_6BW_dsh-smooth-stream-log-fade-31),.Sv_6BW_scope ::highlight(Sv_6BW_dsh-smooth-stream-log-fade-31){color:color-mix(in srgb, var(--dsh-smooth-stream-fade-color,currentColor) 100.0%, transparent)}";
|
|
3056
|
+
const tagId$3 = "dsh-smooth-stream/LogarithmicFade.module.css";
|
|
3057
|
+
if (typeof document !== "undefined") {
|
|
3058
|
+
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]");
|
|
3059
|
+
if (tag === null) {
|
|
3060
|
+
tag = document.createElement("style");
|
|
3061
|
+
tag.dataset.plugin = "dsh-smooth-stream";
|
|
3062
|
+
tag.dataset.pluginCss = tagId$3;
|
|
3063
|
+
document.head.appendChild(tag);
|
|
3064
|
+
}
|
|
3065
|
+
tag.textContent = css$3;
|
|
3066
|
+
}
|
|
3067
|
+
var LogarithmicFade_module_css_default = {
|
|
3068
|
+
"dsh-smooth-stream-log-fade-30": "Sv_6BW_dsh-smooth-stream-log-fade-30",
|
|
3069
|
+
"dsh-smooth-stream-log-fade-26": "Sv_6BW_dsh-smooth-stream-log-fade-26",
|
|
3070
|
+
"dsh-smooth-stream-log-fade-3": "Sv_6BW_dsh-smooth-stream-log-fade-3",
|
|
3071
|
+
"dsh-smooth-stream-log-fade-14": "Sv_6BW_dsh-smooth-stream-log-fade-14",
|
|
3072
|
+
"dsh-smooth-stream-log-fade-28": "Sv_6BW_dsh-smooth-stream-log-fade-28",
|
|
3073
|
+
"dsh-smooth-stream-log-fade-6": "Sv_6BW_dsh-smooth-stream-log-fade-6",
|
|
3074
|
+
"dsh-smooth-stream-log-fade-7": "Sv_6BW_dsh-smooth-stream-log-fade-7",
|
|
3075
|
+
"dsh-smooth-stream-log-fade-24": "Sv_6BW_dsh-smooth-stream-log-fade-24",
|
|
3076
|
+
"dsh-smooth-stream-log-fade-12": "Sv_6BW_dsh-smooth-stream-log-fade-12",
|
|
3077
|
+
"dsh-smooth-stream-log-fade-16": "Sv_6BW_dsh-smooth-stream-log-fade-16",
|
|
3078
|
+
"scope": "Sv_6BW_scope",
|
|
3079
|
+
"dsh-smooth-stream-log-fade-0": "Sv_6BW_dsh-smooth-stream-log-fade-0",
|
|
3080
|
+
"dsh-smooth-stream-log-fade-8": "Sv_6BW_dsh-smooth-stream-log-fade-8",
|
|
3081
|
+
"dsh-smooth-stream-log-fade-10": "Sv_6BW_dsh-smooth-stream-log-fade-10",
|
|
3082
|
+
"dsh-smooth-stream-log-fade-5": "Sv_6BW_dsh-smooth-stream-log-fade-5",
|
|
3083
|
+
"dsh-smooth-stream-log-fade-11": "Sv_6BW_dsh-smooth-stream-log-fade-11",
|
|
3084
|
+
"dsh-smooth-stream-log-fade-1": "Sv_6BW_dsh-smooth-stream-log-fade-1",
|
|
3085
|
+
"dsh-smooth-stream-log-fade-9": "Sv_6BW_dsh-smooth-stream-log-fade-9",
|
|
3086
|
+
"dsh-smooth-stream-log-fade-21": "Sv_6BW_dsh-smooth-stream-log-fade-21",
|
|
3087
|
+
"dsh-smooth-stream-log-fade-31": "Sv_6BW_dsh-smooth-stream-log-fade-31",
|
|
3088
|
+
"dsh-smooth-stream-log-fade-20": "Sv_6BW_dsh-smooth-stream-log-fade-20",
|
|
3089
|
+
"dsh-smooth-stream-log-fade-15": "Sv_6BW_dsh-smooth-stream-log-fade-15",
|
|
3090
|
+
"dsh-smooth-stream-log-fade-25": "Sv_6BW_dsh-smooth-stream-log-fade-25",
|
|
3091
|
+
"dsh-smooth-stream-log-fade-18": "Sv_6BW_dsh-smooth-stream-log-fade-18",
|
|
3092
|
+
"dsh-smooth-stream-log-fade-4": "Sv_6BW_dsh-smooth-stream-log-fade-4",
|
|
3093
|
+
"dsh-smooth-stream-log-fade-23": "Sv_6BW_dsh-smooth-stream-log-fade-23",
|
|
3094
|
+
"dsh-smooth-stream-log-fade-27": "Sv_6BW_dsh-smooth-stream-log-fade-27",
|
|
3095
|
+
"dsh-smooth-stream-log-fade-13": "Sv_6BW_dsh-smooth-stream-log-fade-13",
|
|
3096
|
+
"dsh-smooth-stream-log-fade-17": "Sv_6BW_dsh-smooth-stream-log-fade-17",
|
|
3097
|
+
"dsh-smooth-stream-log-fade-22": "Sv_6BW_dsh-smooth-stream-log-fade-22",
|
|
3098
|
+
"dsh-smooth-stream-log-fade-2": "Sv_6BW_dsh-smooth-stream-log-fade-2",
|
|
3099
|
+
"dsh-smooth-stream-log-fade-29": "Sv_6BW_dsh-smooth-stream-log-fade-29",
|
|
3100
|
+
"dsh-smooth-stream-log-fade-19": "Sv_6BW_dsh-smooth-stream-log-fade-19"
|
|
3101
|
+
};
|
|
3102
|
+
const FADE_STEPS = 32;
|
|
3103
|
+
const PREFIX = "dsh-smooth-stream-log-fade-";
|
|
3104
|
+
const COLOR_PROPERTY = "--dsh-smooth-stream-fade-color";
|
|
3105
|
+
const highlightName = (index) => LogarithmicFade_module_css_default[`${PREFIX}${index}`] ?? `${PREFIX}${index}`;
|
|
3106
|
+
const EXCLUDED = "pre,code,math,.katex,.katex-display,mjx-container,svg,script,style,textarea,input,button,select,[role=\"button\"],[contenteditable],[hidden],[aria-hidden=\"true\"],[aria-live]";
|
|
3107
|
+
const EXCLUDED_ATTRIBUTES = [
|
|
3108
|
+
"hidden",
|
|
3109
|
+
"aria-hidden",
|
|
3110
|
+
"role",
|
|
3111
|
+
"contenteditable",
|
|
3112
|
+
"class"
|
|
3113
|
+
];
|
|
3114
|
+
const APPEARANCE_ATTRIBUTES = [
|
|
3115
|
+
"class",
|
|
3116
|
+
"style",
|
|
3117
|
+
"data-theme",
|
|
3118
|
+
"data-color-scheme",
|
|
3119
|
+
"data-appearance"
|
|
3120
|
+
];
|
|
3121
|
+
function logarithmicOpacity(progress) {
|
|
3122
|
+
return 0 + 1 * (1 - Math.log1p(5 * (1 - (Number.isFinite(progress) ? Math.max(0, Math.min(1, progress)) : 1))) / Math.log(6));
|
|
3123
|
+
}
|
|
3124
|
+
function fadeTailSize(speedCps) {
|
|
3125
|
+
return Math.min(160, Math.max(24, Math.ceil((Number.isFinite(speedCps) ? Math.max(0, speedCps) : 0) * 240 / 1e3)));
|
|
3126
|
+
}
|
|
3127
|
+
const schedulers = /* @__PURE__ */ new WeakMap();
|
|
3128
|
+
const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
3129
|
+
/**
|
|
3130
|
+
* Hot-path counters for the streaming benchmark. Increments only — this is the
|
|
3131
|
+
* evidence that the per-frame pass count collapsed, not a control input.
|
|
3132
|
+
*/
|
|
3133
|
+
const fadeHotPathStats = {
|
|
3134
|
+
/** Observer batches that reached reconcile(). */
|
|
3135
|
+
reconcileCalls: 0,
|
|
3136
|
+
/** Reconciles that did real work (the rest are dirty-check no-ops). */
|
|
3137
|
+
reconcilePasses: 0,
|
|
3138
|
+
/** Full text-node table rebuilds (structural DOM change). */
|
|
3139
|
+
nodeScans: 0,
|
|
3140
|
+
/** Forced style resolutions for newly faded elements. */
|
|
3141
|
+
styleReads: 0
|
|
3142
|
+
};
|
|
3143
|
+
function schedule(scheduler) {
|
|
3144
|
+
if (scheduler.taskId !== null || scheduler.pending.size === 0) return;
|
|
3145
|
+
scheduler.taskId = scheduler.coordinator.registerTask({ onSimulate: (_dtMs, now) => {
|
|
3146
|
+
for (const client of scheduler.pending) if (!client.paint(now)) scheduler.pending.delete(client);
|
|
3147
|
+
if (scheduler.pending.size === 0) {
|
|
3148
|
+
scheduler.taskId = null;
|
|
3149
|
+
return false;
|
|
3150
|
+
}
|
|
3151
|
+
return true;
|
|
3152
|
+
} });
|
|
3153
|
+
}
|
|
3154
|
+
/**
|
|
3155
|
+
* Whether an attribute record actually moved the attribute's value. jsdom (and
|
|
3156
|
+
* some engines) report a write even when the serialized value is unchanged, and
|
|
3157
|
+
* this controller writes the scope class on every enable/disable transition —
|
|
3158
|
+
* counting a no-op record as a DOM change would re-enter the observer forever.
|
|
3159
|
+
* Requires `attributeOldValue` on the observing call.
|
|
3160
|
+
* @param record - attribute mutation record from either observer.
|
|
3161
|
+
* @returns true only when the value differs from the recorded old value.
|
|
3162
|
+
*/
|
|
3163
|
+
function attributeChanged(record) {
|
|
3164
|
+
const attribute = record.attributeName;
|
|
3165
|
+
if (attribute === null) return true;
|
|
3166
|
+
return record.oldValue !== record.target.getAttribute(attribute);
|
|
3167
|
+
}
|
|
3168
|
+
/**
|
|
3169
|
+
* One appearance watcher per document. A theme switch arrives either as an OS
|
|
3170
|
+
* media-query flip (the app's default `preference: system`) or as an attribute
|
|
3171
|
+
* write on <html>/<body>, so both are observed; the fade only ever writes
|
|
3172
|
+
* custom properties on its own root, never on those elements, so this watcher
|
|
3173
|
+
* cannot observe the controller's own output.
|
|
3174
|
+
* @param scheduler - document-scoped registry notified when the look changes.
|
|
3175
|
+
* @returns teardown for the last client's dispose.
|
|
3176
|
+
*/
|
|
3177
|
+
function watchAppearance(scheduler) {
|
|
3178
|
+
const win = scheduler.window;
|
|
3179
|
+
const doc = win.document;
|
|
3180
|
+
const invalidate = () => {
|
|
3181
|
+
for (const client of scheduler.clients) client.invalidateColors();
|
|
3182
|
+
};
|
|
3183
|
+
const observer = new win.MutationObserver((records) => {
|
|
3184
|
+
if (records.some(attributeChanged)) invalidate();
|
|
3185
|
+
});
|
|
3186
|
+
const watched = doc.body === null ? [doc.documentElement] : [doc.documentElement, doc.body];
|
|
3187
|
+
for (const element of watched) observer.observe(element, {
|
|
3188
|
+
attributes: true,
|
|
3189
|
+
attributeFilter: APPEARANCE_ATTRIBUTES,
|
|
3190
|
+
attributeOldValue: true
|
|
3191
|
+
});
|
|
3192
|
+
const query = typeof win.matchMedia === "function" ? win.matchMedia("(prefers-color-scheme: dark)") : null;
|
|
3193
|
+
if (query !== null) query.addEventListener("change", invalidate);
|
|
3194
|
+
return () => {
|
|
3195
|
+
observer.disconnect();
|
|
3196
|
+
query?.removeEventListener("change", invalidate);
|
|
3197
|
+
};
|
|
3198
|
+
}
|
|
3199
|
+
function schedulerFor(root) {
|
|
3200
|
+
const doc = root.ownerDocument;
|
|
3201
|
+
const win = doc.defaultView;
|
|
3202
|
+
if (win === null) return null;
|
|
3203
|
+
const realm = win;
|
|
3204
|
+
if (typeof realm.Highlight !== "function" || !realm.CSS?.highlights || !realm.CSS.supports("color", "color-mix(in srgb, currentColor 15%, transparent)")) return null;
|
|
3205
|
+
let scheduler = schedulers.get(doc);
|
|
3206
|
+
if (scheduler === void 0) {
|
|
3207
|
+
const highlights = Array.from({ length: FADE_STEPS }, () => new realm.Highlight());
|
|
3208
|
+
for (const [index, highlight] of highlights.entries()) realm.CSS.highlights.set(highlightName(index), highlight);
|
|
3209
|
+
scheduler = {
|
|
3210
|
+
highlights,
|
|
3211
|
+
clients: /* @__PURE__ */ new Set(),
|
|
3212
|
+
pending: /* @__PURE__ */ new Set(),
|
|
3213
|
+
taskId: null,
|
|
3214
|
+
registry: realm.CSS.highlights,
|
|
3215
|
+
window: win,
|
|
3216
|
+
coordinator: FrameCoordinator.forDocument(doc),
|
|
3217
|
+
unwatchAppearance: () => {}
|
|
3218
|
+
};
|
|
3219
|
+
scheduler.unwatchAppearance = watchAppearance(scheduler);
|
|
3220
|
+
schedulers.set(doc, scheduler);
|
|
3221
|
+
}
|
|
3222
|
+
return scheduler;
|
|
3223
|
+
}
|
|
3224
|
+
/**
|
|
3225
|
+
* Owns ranges only: React retains ownership of every element and Text node.
|
|
3226
|
+
*
|
|
3227
|
+
* The controller used to re-derive its whole view of the subtree (textContent,
|
|
3228
|
+
* prefix walk, full TreeWalker, an O(tail x #nodes) filter) on *every* commit
|
|
3229
|
+
* AND on every observer batch for that same commit. It now keeps an
|
|
3230
|
+
* incremental text-node table, reconciles at most once per observed DOM
|
|
3231
|
+
* change, and preserves the per-element colour memo across passes.
|
|
3232
|
+
*/
|
|
3233
|
+
var LogarithmicFadeController = class LogarithmicFadeController {
|
|
3234
|
+
root;
|
|
3235
|
+
scheduler;
|
|
3236
|
+
previous = "";
|
|
3237
|
+
characters = [];
|
|
3238
|
+
colors = /* @__PURE__ */ new Map();
|
|
3239
|
+
enabled = false;
|
|
3240
|
+
active = false;
|
|
3241
|
+
speedCps = 100;
|
|
3242
|
+
pausedAt = null;
|
|
3243
|
+
disposed = false;
|
|
3244
|
+
observer;
|
|
3245
|
+
/** Text nodes of the root with their source offsets; survives data-only edits. */
|
|
3246
|
+
nodes = [];
|
|
3247
|
+
/** Ineligible nodes in nodes[0..index): a span is fadeable when its bounds match. */
|
|
3248
|
+
ineligiblePrefix = [0];
|
|
3249
|
+
/**
|
|
3250
|
+
* `closest(EXCLUDED)` derived once per element instead of once per Text node:
|
|
3251
|
+
* exclusion is inherited from the ancestor chain, so a memo stays usable only
|
|
3252
|
+
* while the element keeps its own parent. Moving a subtree into `code` — or
|
|
3253
|
+
* into anything else that matches — invalidates exactly the moved chain the
|
|
3254
|
+
* next time it is read.
|
|
3255
|
+
*/
|
|
3256
|
+
excludedElements = /* @__PURE__ */ new WeakMap();
|
|
3257
|
+
/** Own tag/attribute match per element; dropped when an exclusion attribute moves. */
|
|
3258
|
+
ownExcluded = /* @__PURE__ */ new WeakMap();
|
|
3259
|
+
/** Bumped by every observed mutation; reconcile() no-ops while it does not move. */
|
|
3260
|
+
domRevision = 0;
|
|
3261
|
+
/** Bumped only by structural (childList) mutations; the node table is rebuilt then. */
|
|
3262
|
+
structureRevision = 0;
|
|
3263
|
+
scannedStructureRevision = -1;
|
|
3264
|
+
reconciledRevision = -1;
|
|
3265
|
+
reconciledEnabled = false;
|
|
3266
|
+
reconciledActive = false;
|
|
3267
|
+
reconciledPaused = false;
|
|
3268
|
+
reconciledTailSize = 0;
|
|
3269
|
+
colorGeneration = 0;
|
|
3270
|
+
constructor(root, scheduler) {
|
|
3271
|
+
this.root = root;
|
|
3272
|
+
this.scheduler = scheduler;
|
|
3273
|
+
scheduler.clients.add(this);
|
|
3274
|
+
const win = root.ownerDocument.defaultView;
|
|
3275
|
+
this.observer = new win.MutationObserver((records) => {
|
|
3276
|
+
this.onDomMutation(records);
|
|
3277
|
+
});
|
|
3278
|
+
this.observer.observe(root, {
|
|
3279
|
+
subtree: true,
|
|
3280
|
+
childList: true,
|
|
3281
|
+
characterData: true,
|
|
3282
|
+
attributes: true,
|
|
3283
|
+
attributeFilter: EXCLUDED_ATTRIBUTES,
|
|
3284
|
+
attributeOldValue: true
|
|
3285
|
+
});
|
|
3286
|
+
}
|
|
3287
|
+
static create(root) {
|
|
3288
|
+
const scheduler = schedulerFor(root);
|
|
3289
|
+
return scheduler === null ? null : new LogarithmicFadeController(root, scheduler);
|
|
3290
|
+
}
|
|
3291
|
+
update(enabled, active, speedCps = 100, paused = false) {
|
|
3292
|
+
const now = this.scheduler.window.performance.now();
|
|
3293
|
+
if (paused && this.pausedAt === null) this.pausedAt = now;
|
|
3294
|
+
if (!paused && this.pausedAt !== null) {
|
|
3295
|
+
const pauseDuration = now - this.pausedAt;
|
|
3296
|
+
for (const character of this.characters) character.born += pauseDuration;
|
|
3297
|
+
this.pausedAt = null;
|
|
3298
|
+
}
|
|
3299
|
+
this.enabled = enabled;
|
|
3300
|
+
this.active = active;
|
|
3301
|
+
this.speedCps = speedCps;
|
|
3302
|
+
this.reconcile();
|
|
3303
|
+
}
|
|
3304
|
+
/** Invalidate and reconcile from one observer batch (one batch per commit). */
|
|
3305
|
+
onDomMutation(records) {
|
|
3306
|
+
let changed = false;
|
|
3307
|
+
let exclusionAttributesMoved = false;
|
|
3308
|
+
for (const record of records) {
|
|
3309
|
+
if (record.type === "childList") {
|
|
3310
|
+
this.structureRevision += 1;
|
|
3311
|
+
changed = true;
|
|
3312
|
+
continue;
|
|
3313
|
+
}
|
|
3314
|
+
if (record.type === "characterData") {
|
|
3315
|
+
changed = true;
|
|
3316
|
+
continue;
|
|
3317
|
+
}
|
|
3318
|
+
if (!attributeChanged(record)) continue;
|
|
3319
|
+
exclusionAttributesMoved = true;
|
|
3320
|
+
changed = true;
|
|
3321
|
+
}
|
|
3322
|
+
if (!changed) return;
|
|
3323
|
+
if (exclusionAttributesMoved) {
|
|
3324
|
+
this.excludedElements = /* @__PURE__ */ new WeakMap();
|
|
3325
|
+
this.ownExcluded = /* @__PURE__ */ new WeakMap();
|
|
3326
|
+
this.structureRevision += 1;
|
|
3327
|
+
}
|
|
3328
|
+
this.domRevision += 1;
|
|
3329
|
+
this.reconcile();
|
|
3330
|
+
}
|
|
3331
|
+
/**
|
|
3332
|
+
* Whether this element matches a SKIP rule itself or inherits one from an
|
|
3333
|
+
* ancestor — the memoised form of `element.closest(EXCLUDED) !== null`.
|
|
3334
|
+
* Memoised per element and revalidated through the parent chain, so a node
|
|
3335
|
+
* that moves into `code` — or whose ancestor does — is re-decided without
|
|
3336
|
+
* paying the selector walk once per Text node per pass.
|
|
3337
|
+
* @param element - element owning the Text node being classified.
|
|
3338
|
+
* @returns true when text under this element must stay out of the fade.
|
|
3339
|
+
*/
|
|
3340
|
+
elementExcluded(element) {
|
|
3341
|
+
const cached = this.excludedElements.get(element);
|
|
3342
|
+
if (cached !== void 0 && cached.parent === element.parentElement) return cached.excluded;
|
|
3343
|
+
const parent = element.parentElement;
|
|
3344
|
+
const inherited = parent === null ? false : this.elementExcluded(parent);
|
|
3345
|
+
let own = this.ownExcluded.get(element);
|
|
3346
|
+
if (own === void 0) {
|
|
3347
|
+
own = element.matches(EXCLUDED);
|
|
3348
|
+
this.ownExcluded.set(element, own);
|
|
3349
|
+
}
|
|
3350
|
+
const excluded = inherited || own;
|
|
3351
|
+
this.excludedElements.set(element, {
|
|
3352
|
+
parent,
|
|
3353
|
+
excluded
|
|
3354
|
+
});
|
|
3355
|
+
return excluded;
|
|
3356
|
+
}
|
|
3357
|
+
/** Rebuild the text-node table after a structural change. */
|
|
3358
|
+
scanNodes() {
|
|
3359
|
+
const nodes = [];
|
|
3360
|
+
const walker = this.root.ownerDocument.createTreeWalker(this.root, NodeFilter.SHOW_TEXT);
|
|
3361
|
+
for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
|
|
3362
|
+
const text = node;
|
|
3363
|
+
const parent = text.parentElement;
|
|
3364
|
+
const eligible = parent !== null && !this.elementExcluded(parent);
|
|
3365
|
+
nodes.push({
|
|
3366
|
+
node: text,
|
|
3367
|
+
start: 0,
|
|
3368
|
+
end: 0,
|
|
3369
|
+
eligible
|
|
3370
|
+
});
|
|
3371
|
+
}
|
|
3372
|
+
this.nodes = nodes;
|
|
3373
|
+
this.scannedStructureRevision = this.structureRevision;
|
|
3374
|
+
fadeHotPathStats.nodeScans += 1;
|
|
3375
|
+
}
|
|
3376
|
+
/**
|
|
3377
|
+
* Re-derive the source text, the per-node spans and the eligibility prefix
|
|
3378
|
+
* sums from the cached table. No DOM read, no selector match, no allocation
|
|
3379
|
+
* beyond the string itself.
|
|
3380
|
+
*/
|
|
3381
|
+
readText() {
|
|
3382
|
+
if (this.scannedStructureRevision !== this.structureRevision) this.scanNodes();
|
|
3383
|
+
for (const entry of this.nodes) {
|
|
3384
|
+
if (entry.node.parentNode !== null) continue;
|
|
3385
|
+
this.scanNodes();
|
|
3386
|
+
break;
|
|
3387
|
+
}
|
|
3388
|
+
const nodes = this.nodes;
|
|
3389
|
+
const prefix = this.ineligiblePrefix;
|
|
3390
|
+
if (prefix.length < nodes.length + 1) prefix.length = nodes.length + 1;
|
|
3391
|
+
prefix[0] = 0;
|
|
3392
|
+
let text = "";
|
|
3393
|
+
let offset = 0;
|
|
3394
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
3395
|
+
const entry = nodes[index];
|
|
3396
|
+
const data = entry.node.data;
|
|
3397
|
+
entry.start = offset;
|
|
3398
|
+
offset += data.length;
|
|
3399
|
+
entry.end = offset;
|
|
3400
|
+
text += data;
|
|
3401
|
+
prefix[index + 1] = prefix[index] + (entry.eligible ? 0 : 1);
|
|
3402
|
+
}
|
|
3403
|
+
prefix.length = nodes.length + 1;
|
|
3404
|
+
this.ineligiblePrefix = prefix;
|
|
3405
|
+
return text;
|
|
3406
|
+
}
|
|
3407
|
+
/** First cached node whose span ends after `offset` (binary search). */
|
|
3408
|
+
firstNodeEndingAfter(offset) {
|
|
3409
|
+
const nodes = this.nodes;
|
|
3410
|
+
let low = 0;
|
|
3411
|
+
let high = nodes.length;
|
|
3412
|
+
while (low < high) {
|
|
3413
|
+
const mid = low + high >> 1;
|
|
3414
|
+
if (nodes[mid].end > offset) high = mid;
|
|
3415
|
+
else low = mid + 1;
|
|
3416
|
+
}
|
|
3417
|
+
return low;
|
|
3418
|
+
}
|
|
3419
|
+
/** Last cached node whose span starts before `offset` (binary search). */
|
|
3420
|
+
lastNodeStartingBefore(offset) {
|
|
3421
|
+
const nodes = this.nodes;
|
|
3422
|
+
let low = -1;
|
|
3423
|
+
let high = nodes.length - 1;
|
|
3424
|
+
while (low < high) {
|
|
3425
|
+
const mid = low + high + 1 >> 1;
|
|
3426
|
+
if (nodes[mid].start < offset) low = mid;
|
|
3427
|
+
else high = mid - 1;
|
|
3428
|
+
}
|
|
3429
|
+
return low;
|
|
3430
|
+
}
|
|
3431
|
+
clearRanges() {
|
|
3432
|
+
for (const character of this.characters) this.scheduler.highlights[character.bucket]?.delete(character.range);
|
|
3433
|
+
this.characters = [];
|
|
3434
|
+
}
|
|
3435
|
+
restoreColor(element, preserved) {
|
|
3436
|
+
if (preserved.value === "") element.style.removeProperty(COLOR_PROPERTY);
|
|
3437
|
+
else element.style.setProperty(COLOR_PROPERTY, preserved.value, preserved.priority);
|
|
3438
|
+
}
|
|
3439
|
+
rootColorSet = false;
|
|
3440
|
+
ensureRootColor() {
|
|
3441
|
+
if (this.rootColorSet) return;
|
|
3442
|
+
const color = this.scheduler.window.getComputedStyle(this.root).color || "currentColor";
|
|
3443
|
+
this.root.style.setProperty(COLOR_PROPERTY, color);
|
|
3444
|
+
this.rootColorSet = true;
|
|
3445
|
+
}
|
|
3446
|
+
restoreColors() {
|
|
3447
|
+
if (this.rootColorSet) {
|
|
3448
|
+
this.root.style.removeProperty(COLOR_PROPERTY);
|
|
3449
|
+
this.rootColorSet = false;
|
|
3450
|
+
}
|
|
3451
|
+
for (const [element, preserved] of this.colors) this.restoreColor(element, preserved);
|
|
3452
|
+
this.colors.clear();
|
|
3453
|
+
}
|
|
3454
|
+
/**
|
|
3455
|
+
* The document's appearance moved (theme switch, restyle): the captured ink
|
|
3456
|
+
* colour is stale, so drop it and let the next pass read the new one. Costs
|
|
3457
|
+
* nothing while no colour is captured, which is the common case — the read
|
|
3458
|
+
* still happens only when text is actually fading.
|
|
3459
|
+
*/
|
|
3460
|
+
invalidateColors() {
|
|
3461
|
+
if (this.disposed || !this.rootColorSet && this.colors.size === 0) return;
|
|
3462
|
+
this.restoreColors();
|
|
3463
|
+
this.domRevision += 1;
|
|
3464
|
+
this.reconcile();
|
|
3465
|
+
}
|
|
3466
|
+
/**
|
|
3467
|
+
* Refresh the fade ranges. Two triggers used to run this 2-3x per frame with
|
|
3468
|
+
* no dirty check: the commit-driven `update()` and the observer batch for the
|
|
3469
|
+
* very same DOM write. Both converge here, and the pass is skipped unless the
|
|
3470
|
+
* DOM revision, the gate, the fade window or the pause state actually moved.
|
|
3471
|
+
*/
|
|
3472
|
+
reconcile() {
|
|
3473
|
+
if (this.disposed) return;
|
|
3474
|
+
fadeHotPathStats.reconcileCalls += 1;
|
|
3475
|
+
const tailSize = fadeTailSize(this.speedCps);
|
|
3476
|
+
const paused = this.pausedAt !== null;
|
|
3477
|
+
const domChanged = this.domRevision !== this.reconciledRevision;
|
|
3478
|
+
const paramsChanged = this.enabled !== this.reconciledEnabled || this.active !== this.reconciledActive || paused !== this.reconciledPaused || tailSize !== this.reconciledTailSize && this.characters.length > 0;
|
|
3479
|
+
if (!domChanged && !paramsChanged) return;
|
|
3480
|
+
fadeHotPathStats.reconcilePasses += 1;
|
|
3481
|
+
this.reconciledRevision = this.domRevision;
|
|
3482
|
+
this.reconciledEnabled = this.enabled;
|
|
3483
|
+
this.reconciledActive = this.active;
|
|
3484
|
+
this.reconciledPaused = paused;
|
|
3485
|
+
this.reconciledTailSize = tailSize;
|
|
3486
|
+
const text = this.readText();
|
|
3487
|
+
const previous = this.previous;
|
|
3488
|
+
this.previous = text;
|
|
3489
|
+
const old = this.characters;
|
|
3490
|
+
this.clearRanges();
|
|
3491
|
+
if (!this.enabled) {
|
|
3492
|
+
this.root.classList.remove(LogarithmicFade_module_css_default.scope);
|
|
3493
|
+
this.restoreColors();
|
|
3494
|
+
this.scheduler.pending.delete(this);
|
|
3495
|
+
this.stopIfIdle();
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3498
|
+
this.root.classList.add(LogarithmicFade_module_css_default.scope);
|
|
3499
|
+
this.ensureRootColor();
|
|
3500
|
+
const now = this.pausedAt ?? this.scheduler.window.performance.now();
|
|
3501
|
+
const appended = text.startsWith(previous);
|
|
3502
|
+
let prefix = appended ? previous.length : 0;
|
|
3503
|
+
if (!appended) while (prefix < previous.length && prefix < text.length && previous[prefix] === text[prefix]) prefix += 1;
|
|
3504
|
+
const nodes = this.nodes;
|
|
3505
|
+
const ineligible = this.ineligiblePrefix;
|
|
3506
|
+
const oldestLiveStart = old.reduce((start, character) => now - character.born < 240 && character.end <= prefix ? Math.min(start, character.start) : start, Infinity);
|
|
3507
|
+
const retainedBySpan = /* @__PURE__ */ new Map();
|
|
3508
|
+
for (const character of old) retainedBySpan.set(`${String(character.start)}:${String(character.end)}`, character);
|
|
3509
|
+
this.colorGeneration += 1;
|
|
3510
|
+
const windowStart = Math.max(0, Math.min(text.length - 320, oldestLiveStart < Infinity ? oldestLiveStart : text.length));
|
|
3511
|
+
const tailText = windowStart > 0 ? text.slice(windowStart) : text;
|
|
3512
|
+
const segments = segmenter.segment(tailText);
|
|
3513
|
+
let end = text.length;
|
|
3514
|
+
for (let count = 0; count < 160 && end > 0 && (count < tailSize || end > oldestLiveStart); count += 1) {
|
|
3515
|
+
const localEnd = end - windowStart;
|
|
3516
|
+
if (localEnd <= 0) break;
|
|
3517
|
+
const segment = segments.containing(localEnd - 1);
|
|
3518
|
+
if (segment === void 0) break;
|
|
3519
|
+
const start = segment.index + windowStart;
|
|
3520
|
+
const born = (end <= prefix ? retainedBySpan.get(`${String(start)}:${String(end)}`) : void 0)?.born ?? (this.active && appended && start >= previous.length ? now : null);
|
|
3521
|
+
if (born !== null && now - born < 240 && segment.segment.trim() !== "") {
|
|
3522
|
+
const first = this.firstNodeEndingAfter(start);
|
|
3523
|
+
const last = this.lastNodeStartingBefore(end);
|
|
3524
|
+
if (last >= first && first < nodes.length && nodes[last].end > start && ineligible[last + 1] === ineligible[first]) {
|
|
3525
|
+
const firstPart = nodes[first];
|
|
3526
|
+
const lastPart = nodes[last];
|
|
3527
|
+
const range = this.root.ownerDocument.createRange();
|
|
3528
|
+
range.setStart(firstPart.node, start - firstPart.start);
|
|
3529
|
+
range.setEnd(lastPart.node, end - lastPart.start);
|
|
3530
|
+
this.characters.push({
|
|
3531
|
+
start,
|
|
3532
|
+
end,
|
|
3533
|
+
born,
|
|
3534
|
+
range,
|
|
3535
|
+
bucket: -1
|
|
3536
|
+
});
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
end = start;
|
|
3540
|
+
}
|
|
3541
|
+
if (this.paint(now)) {
|
|
3542
|
+
if (this.pausedAt === null) {
|
|
3543
|
+
this.scheduler.pending.add(this);
|
|
3544
|
+
schedule(this.scheduler);
|
|
3545
|
+
} else {
|
|
3546
|
+
this.scheduler.pending.delete(this);
|
|
3547
|
+
this.stopIfIdle();
|
|
3548
|
+
}
|
|
3549
|
+
} else {
|
|
3550
|
+
this.scheduler.pending.delete(this);
|
|
3551
|
+
this.stopIfIdle();
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
paint(now) {
|
|
3555
|
+
this.characters = this.characters.filter((character) => {
|
|
3556
|
+
const progress = (now - character.born) / 240;
|
|
3557
|
+
if (progress >= 1 || !this.root.isConnected || !this.root.contains(character.range.startContainer)) {
|
|
3558
|
+
this.scheduler.highlights[character.bucket]?.delete(character.range);
|
|
3559
|
+
return false;
|
|
3560
|
+
}
|
|
3561
|
+
const bucket = Math.min(31, Math.round((logarithmicOpacity(progress) - 0) / 1 * 31));
|
|
3562
|
+
if (bucket !== character.bucket) {
|
|
3563
|
+
this.scheduler.highlights[character.bucket]?.delete(character.range);
|
|
3564
|
+
this.scheduler.highlights[bucket].add(character.range);
|
|
3565
|
+
character.bucket = bucket;
|
|
3566
|
+
}
|
|
3567
|
+
return true;
|
|
3568
|
+
});
|
|
3569
|
+
if (this.characters.length === 0) this.restoreColors();
|
|
3570
|
+
return this.characters.length > 0;
|
|
3571
|
+
}
|
|
3572
|
+
stopIfIdle() {
|
|
3573
|
+
if (this.scheduler.pending.size !== 0) return;
|
|
3574
|
+
if (this.scheduler.taskId === null) return;
|
|
3575
|
+
this.scheduler.coordinator.unregisterTask(this.scheduler.taskId);
|
|
3576
|
+
this.scheduler.taskId = null;
|
|
3577
|
+
}
|
|
3578
|
+
dispose() {
|
|
3579
|
+
if (this.disposed) return;
|
|
3580
|
+
this.disposed = true;
|
|
3581
|
+
this.observer.disconnect();
|
|
3582
|
+
this.clearRanges();
|
|
3583
|
+
this.restoreColors();
|
|
3584
|
+
this.nodes = [];
|
|
3585
|
+
this.scannedStructureRevision = -1;
|
|
3586
|
+
this.root.classList.remove(LogarithmicFade_module_css_default.scope);
|
|
3587
|
+
this.scheduler.pending.delete(this);
|
|
3588
|
+
this.scheduler.clients.delete(this);
|
|
3589
|
+
this.stopIfIdle();
|
|
3590
|
+
if (this.scheduler.clients.size === 0) {
|
|
3591
|
+
for (const [index, highlight] of this.scheduler.highlights.entries()) {
|
|
3592
|
+
const name = highlightName(index);
|
|
3593
|
+
if (this.scheduler.registry.get(name) === highlight) this.scheduler.registry.delete(name);
|
|
3594
|
+
}
|
|
3595
|
+
this.scheduler.unwatchAppearance();
|
|
3596
|
+
schedulers.delete(this.root.ownerDocument);
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
};
|
|
3600
|
+
/** active admits new characters; enabled=false also cancels completion linger. */
|
|
3601
|
+
function useLogarithmicFade(rootRef, enabled, active, speedCpsRef, paused = false) {
|
|
3602
|
+
const controller = (0, react.useRef)(null);
|
|
3603
|
+
const committed = (0, react.useRef)(false);
|
|
3604
|
+
(0, react.useLayoutEffect)(() => {
|
|
3605
|
+
return () => {
|
|
3606
|
+
controller.current?.dispose();
|
|
3607
|
+
controller.current = null;
|
|
3608
|
+
};
|
|
3609
|
+
}, [rootRef]);
|
|
3610
|
+
(0, react.useLayoutEffect)(() => {
|
|
3611
|
+
const root = rootRef.current;
|
|
3612
|
+
if (!enabled) {
|
|
3613
|
+
if (controller.current !== null) {
|
|
3614
|
+
controller.current.dispose();
|
|
3615
|
+
controller.current = null;
|
|
3616
|
+
}
|
|
3617
|
+
committed.current = true;
|
|
3618
|
+
return;
|
|
3619
|
+
}
|
|
3620
|
+
if (controller.current === null && root !== null && active) {
|
|
3621
|
+
controller.current = LogarithmicFadeController.create(root);
|
|
3622
|
+
if (committed.current) controller.current?.update(false, false);
|
|
3623
|
+
}
|
|
3624
|
+
controller.current?.update(enabled, active, speedCpsRef?.current, paused);
|
|
3625
|
+
committed.current = true;
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
3628
|
+
//#endregion
|
|
3629
|
+
//#region src/client/useDecoupledMarkdown.ts
|
|
3630
|
+
/**
|
|
3631
|
+
* Detect whether the given markdown text ends inside an unclosed code block (fence).
|
|
3632
|
+
* Supports both backtick (```) and tilde (~~~) fences per CommonMark rules.
|
|
3633
|
+
*
|
|
3634
|
+
* Rules:
|
|
3635
|
+
* 1. An opening fence begins on a line with 0-3 leading spaces, followed by 3+ backticks or tildes.
|
|
3636
|
+
* 2. An opening backtick fence info-string cannot contain backticks.
|
|
3637
|
+
* 3. A closing fence must have at least as many characters as the opening fence, matching fence char,
|
|
3638
|
+
* and cannot have non-whitespace content after the fence characters.
|
|
3639
|
+
* 4. While inside a code block, all lines belong to the block until a matching closing fence is found.
|
|
3640
|
+
*/
|
|
3641
|
+
function hasUnclosedCodeFence(text) {
|
|
3642
|
+
if (!text.includes("```") && !text.includes("~~~")) return false;
|
|
3643
|
+
let inFence = false;
|
|
3644
|
+
let fenceChar = "";
|
|
3645
|
+
let fenceLen = 0;
|
|
3646
|
+
let start = 0;
|
|
3647
|
+
const len = text.length;
|
|
3648
|
+
while (start < len) {
|
|
3649
|
+
let end = text.indexOf("\n", start);
|
|
3650
|
+
if (end === -1) end = len;
|
|
3651
|
+
let line = text.slice(start, end);
|
|
3652
|
+
if (line.endsWith("\r")) line = line.slice(0, -1);
|
|
3653
|
+
let indent = 0;
|
|
3654
|
+
while (indent < line.length && line[indent] === " " && indent < 4) indent++;
|
|
3655
|
+
if (indent < 4) {
|
|
3656
|
+
const rest = line.slice(indent);
|
|
3657
|
+
const firstChar = rest[0];
|
|
3658
|
+
if (firstChar === "`" || firstChar === "~") {
|
|
3659
|
+
let count = 0;
|
|
3660
|
+
while (count < rest.length && rest[count] === firstChar) count++;
|
|
3661
|
+
if (count >= 3) {
|
|
3662
|
+
const afterFence = rest.slice(count);
|
|
3663
|
+
if (!inFence) {
|
|
3664
|
+
if (firstChar !== "`" || !afterFence.includes("`")) {
|
|
3665
|
+
inFence = true;
|
|
3666
|
+
fenceChar = firstChar;
|
|
3667
|
+
fenceLen = count;
|
|
3668
|
+
}
|
|
3669
|
+
} else if (firstChar === fenceChar && count >= fenceLen) {
|
|
3670
|
+
if (afterFence.trim() === "") {
|
|
3671
|
+
inFence = false;
|
|
3672
|
+
fenceChar = "";
|
|
3673
|
+
fenceLen = 0;
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
start = end + 1;
|
|
3680
|
+
}
|
|
3681
|
+
return inFence;
|
|
3682
|
+
}
|
|
3683
|
+
/**
|
|
3684
|
+
* Decouple the smooth 60 FPS animation clock from the heavyweight Markdown AST compilation clock.
|
|
3685
|
+
*
|
|
3686
|
+
* Outside unclosed code blocks (or when settled), Markdown renders 1:1 with `shown`.
|
|
3687
|
+
* Inside unclosed code blocks (where host incremental parsing degenerates to O(n) full-text compilation),
|
|
3688
|
+
* Markdown re-renders are throttled to ~33ms (30Hz).
|
|
3689
|
+
* Upon code block closure or stream end, updates are synchronized immediately without latency.
|
|
3690
|
+
*/
|
|
3691
|
+
function useDecoupledMarkdown(shown, live, options) {
|
|
3692
|
+
const throttleMs = options?.throttleMs ?? 33;
|
|
3693
|
+
const [markdownShown, setMarkdownShown] = (0, react.useState)(shown);
|
|
3694
|
+
const lastCommitTimeRef = (0, react.useRef)(0);
|
|
3695
|
+
const timerRef = (0, react.useRef)(null);
|
|
3696
|
+
const shownRef = (0, react.useRef)(shown);
|
|
3697
|
+
shownRef.current = shown;
|
|
3698
|
+
const wasUnclosedRef = (0, react.useRef)(false);
|
|
3699
|
+
(0, react.useEffect)(() => {
|
|
3700
|
+
if (!live) {
|
|
3701
|
+
if (timerRef.current !== null) {
|
|
3702
|
+
clearTimeout(timerRef.current);
|
|
3703
|
+
timerRef.current = null;
|
|
3704
|
+
}
|
|
3705
|
+
setMarkdownShown(shown);
|
|
3706
|
+
lastCommitTimeRef.current = performance.now();
|
|
3707
|
+
wasUnclosedRef.current = false;
|
|
3708
|
+
return;
|
|
3709
|
+
}
|
|
3710
|
+
const unclosed = hasUnclosedCodeFence(shown);
|
|
3711
|
+
const now = performance.now();
|
|
3712
|
+
if (wasUnclosedRef.current && !unclosed) {
|
|
3713
|
+
if (timerRef.current !== null) {
|
|
3714
|
+
clearTimeout(timerRef.current);
|
|
3715
|
+
timerRef.current = null;
|
|
3716
|
+
}
|
|
3717
|
+
wasUnclosedRef.current = false;
|
|
3718
|
+
setMarkdownShown(shown);
|
|
3719
|
+
lastCommitTimeRef.current = now;
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
wasUnclosedRef.current = unclosed;
|
|
3723
|
+
if (!unclosed) {
|
|
3724
|
+
if (timerRef.current !== null) {
|
|
3725
|
+
clearTimeout(timerRef.current);
|
|
3726
|
+
timerRef.current = null;
|
|
3727
|
+
}
|
|
3728
|
+
setMarkdownShown(shown);
|
|
3729
|
+
lastCommitTimeRef.current = now;
|
|
3730
|
+
return;
|
|
3731
|
+
}
|
|
3732
|
+
const elapsed = now - lastCommitTimeRef.current;
|
|
3733
|
+
if (elapsed >= throttleMs) {
|
|
3734
|
+
if (timerRef.current !== null) {
|
|
3735
|
+
clearTimeout(timerRef.current);
|
|
3736
|
+
timerRef.current = null;
|
|
3737
|
+
}
|
|
3738
|
+
setMarkdownShown(shown);
|
|
3739
|
+
lastCommitTimeRef.current = now;
|
|
3740
|
+
} else if (timerRef.current === null) {
|
|
3741
|
+
const delay = Math.max(16, throttleMs - elapsed);
|
|
3742
|
+
timerRef.current = setTimeout(() => {
|
|
3743
|
+
timerRef.current = null;
|
|
3744
|
+
setMarkdownShown(shownRef.current);
|
|
3745
|
+
lastCommitTimeRef.current = performance.now();
|
|
3746
|
+
}, delay);
|
|
3747
|
+
}
|
|
3748
|
+
}, [
|
|
3749
|
+
shown,
|
|
3750
|
+
live,
|
|
3751
|
+
throttleMs
|
|
3752
|
+
]);
|
|
3753
|
+
(0, react.useEffect)(() => {
|
|
3754
|
+
return () => {
|
|
3755
|
+
if (timerRef.current !== null) {
|
|
3756
|
+
clearTimeout(timerRef.current);
|
|
3757
|
+
timerRef.current = null;
|
|
3758
|
+
}
|
|
3759
|
+
};
|
|
3760
|
+
}, []);
|
|
3761
|
+
return live ? markdownShown : shown;
|
|
3762
|
+
}
|
|
3763
|
+
//#endregion
|
|
2608
3764
|
//#region src/client/FollowHost.tsx
|
|
2609
3765
|
/**
|
|
2610
3766
|
* Document-flow host that owns conversation-port follow while `active`.
|
|
@@ -2658,7 +3814,7 @@ window.__ModuleLoader__.load({
|
|
|
2658
3814
|
/** Defaults shared by the Host schema and the client-side fallback. */
|
|
2659
3815
|
const DEFAULT_STREAM_CONFIG = {
|
|
2660
3816
|
mode: "typewriter",
|
|
2661
|
-
preset: "
|
|
3817
|
+
preset: "silky",
|
|
2662
3818
|
revealCharsPerSec: 80,
|
|
2663
3819
|
scrollSpeedPxPerSec: 48,
|
|
2664
3820
|
maxScrollSpeedPxPerSec: 1e3
|
|
@@ -2707,79 +3863,24 @@ window.__ModuleLoader__.load({
|
|
|
2707
3863
|
else width += emPx;
|
|
2708
3864
|
return width;
|
|
2709
3865
|
}
|
|
2710
|
-
function measurePendingTextGeometry(root, visibleText) {
|
|
2711
|
-
if (typeof document.createTreeWalker !== "function" || typeof NodeFilter === "undefined") return {
|
|
2712
|
-
root,
|
|
2713
|
-
visibleText,
|
|
2714
|
-
fontSize: 14,
|
|
2715
|
-
wrapThresholdWidth: null
|
|
2716
|
-
};
|
|
2717
|
-
const rootRect = root.getBoundingClientRect();
|
|
2718
|
-
const rootWidth = Math.max(0, rootRect.width, rootRect.right - rootRect.left, root.clientWidth);
|
|
2719
|
-
if (rootWidth <= 0) return {
|
|
2720
|
-
root,
|
|
2721
|
-
visibleText,
|
|
2722
|
-
fontSize: 14,
|
|
2723
|
-
wrapThresholdWidth: null
|
|
2724
|
-
};
|
|
2725
|
-
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
2726
|
-
let tail = null;
|
|
2727
|
-
for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) if ((node.textContent ?? "").length > 0) tail = node;
|
|
2728
|
-
const parent = tail?.parentElement ?? root;
|
|
2729
|
-
const fontSize = Number.parseFloat(getComputedStyle(parent).fontSize) || 14;
|
|
2730
|
-
if (tail === null || typeof document.createRange !== "function") return {
|
|
2731
|
-
root,
|
|
2732
|
-
visibleText,
|
|
2733
|
-
fontSize,
|
|
2734
|
-
wrapThresholdWidth: rootWidth
|
|
2735
|
-
};
|
|
2736
|
-
try {
|
|
2737
|
-
const length = tail.textContent?.length ?? 0;
|
|
2738
|
-
if (length <= 0) return {
|
|
2739
|
-
root,
|
|
2740
|
-
visibleText,
|
|
2741
|
-
fontSize,
|
|
2742
|
-
wrapThresholdWidth: rootWidth
|
|
2743
|
-
};
|
|
2744
|
-
const range = document.createRange();
|
|
2745
|
-
range.setStart(tail, Math.max(0, length - 1));
|
|
2746
|
-
range.setEnd(tail, length);
|
|
2747
|
-
const tailRect = range.getBoundingClientRect();
|
|
2748
|
-
const contentRight = rootRect.right;
|
|
2749
|
-
if (!Number.isFinite(tailRect.right) || tailRect.right <= rootRect.left || contentRight <= rootRect.left) return {
|
|
2750
|
-
root,
|
|
2751
|
-
visibleText,
|
|
2752
|
-
fontSize,
|
|
2753
|
-
wrapThresholdWidth: rootWidth
|
|
2754
|
-
};
|
|
2755
|
-
return {
|
|
2756
|
-
root,
|
|
2757
|
-
visibleText,
|
|
2758
|
-
fontSize,
|
|
2759
|
-
wrapThresholdWidth: Math.max(0, contentRight - tailRect.right) + fontSize * .35
|
|
2760
|
-
};
|
|
2761
|
-
} catch {
|
|
2762
|
-
return {
|
|
2763
|
-
root,
|
|
2764
|
-
visibleText,
|
|
2765
|
-
fontSize,
|
|
2766
|
-
wrapThresholdWidth: rootWidth
|
|
2767
|
-
};
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
3866
|
/** Whether buffered source can reach a new visual line before it drains. */
|
|
2771
3867
|
function pendingTextCanGrow(root, visibleText, pending, geometryRef) {
|
|
2772
3868
|
if (pending === "") return false;
|
|
2773
3869
|
if (/[\r\n]/u.test(pending)) return true;
|
|
2774
|
-
|
|
2775
|
-
if (root === null) return
|
|
3870
|
+
if ([...pending].length >= PREDICTIVE_WRAP_FALLBACK_CHARS) return true;
|
|
3871
|
+
if (root === null) return false;
|
|
2776
3872
|
let geometry = geometryRef.current;
|
|
2777
|
-
if (geometry?.root !== root
|
|
2778
|
-
|
|
3873
|
+
if (geometry?.root !== root) {
|
|
3874
|
+
const rootWidth = root.clientWidth || 600;
|
|
3875
|
+
geometry = {
|
|
3876
|
+
root,
|
|
3877
|
+
visibleText,
|
|
3878
|
+
fontSize: 14,
|
|
3879
|
+
wrapThresholdWidth: Math.max(120, rootWidth * .4)
|
|
3880
|
+
};
|
|
2779
3881
|
geometryRef.current = geometry;
|
|
2780
3882
|
}
|
|
2781
|
-
|
|
2782
|
-
return approximateInlineWidth(pending, geometry.fontSize) >= geometry.wrapThresholdWidth;
|
|
3883
|
+
return approximateInlineWidth(pending, geometry.fontSize) >= (geometry.wrapThresholdWidth ?? geometry.fontSize * PREDICTIVE_WRAP_FALLBACK_CHARS);
|
|
2783
3884
|
}
|
|
2784
3885
|
function announcementChunkEnd(source, start) {
|
|
2785
3886
|
const hardEnd = Math.min(source.length, start + STREAM_ANNOUNCEMENT_MAX_CHARS);
|
|
@@ -2896,12 +3997,12 @@ window.__ModuleLoader__.load({
|
|
|
2896
3997
|
* and rendered by the Harness `MarkdownText`
|
|
2897
3998
|
* streaming arm (incremental parse, frozen non-tail blocks), so there is no
|
|
2898
3999
|
* raw-text tail and no text-to-markdown swap: the tree stays markdown
|
|
2899
|
-
* throughout. The
|
|
2900
|
-
*
|
|
2901
|
-
* drains, the settled full parse (KaTeX math, fence highlighting, file
|
|
4000
|
+
* throughout. The outer assistant owner follows all text growth, including
|
|
4001
|
+
* the final drain, so wraps glide without a completion handoff. Once the
|
|
4002
|
+
* queue drains, the settled full parse (KaTeX math, fence highlighting, file
|
|
2902
4003
|
* mentions) swaps in exactly once.
|
|
2903
4004
|
*/
|
|
2904
|
-
function AnimatedMarkdownText({ text, labels, fileMentions, streaming,
|
|
4005
|
+
function AnimatedMarkdownText({ text, labels, fileMentions, streaming, logarithmicFade, motionReduced, followSpeedCpsRef, followRevealedCharsRef, followRevealScaleRef, onPredictiveChange, onRevealActivityChange, preset, shouldHoldBack }) {
|
|
2905
4006
|
const reduced = motionReduced;
|
|
2906
4007
|
const [typing, setTyping] = (0, react.useState)(streaming);
|
|
2907
4008
|
const localSpeedCpsRef = (0, react.useRef)(35);
|
|
@@ -2924,6 +4025,8 @@ window.__ModuleLoader__.load({
|
|
|
2924
4025
|
});
|
|
2925
4026
|
const shown = reduced ? text : displayed;
|
|
2926
4027
|
const live = typing && !reduced;
|
|
4028
|
+
const markdownShown = useDecoupledMarkdown(shown, live);
|
|
4029
|
+
useLogarithmicFade(followRootRef, logarithmicFade && !reduced, live, speedCpsRef);
|
|
2927
4030
|
(0, react.useEffect)(() => {
|
|
2928
4031
|
const root = followRootRef.current;
|
|
2929
4032
|
if (root === null || typeof ResizeObserver === "undefined") return;
|
|
@@ -2950,6 +4053,9 @@ window.__ModuleLoader__.load({
|
|
|
2950
4053
|
streaming,
|
|
2951
4054
|
text
|
|
2952
4055
|
]);
|
|
4056
|
+
(0, react.useLayoutEffect)(() => {
|
|
4057
|
+
onRevealActivityChange?.(live);
|
|
4058
|
+
}, [live, onRevealActivityChange]);
|
|
2953
4059
|
(0, react.useEffect)(() => {
|
|
2954
4060
|
if (typing && !streaming && shown.length === text.length) setTyping(false);
|
|
2955
4061
|
}, [
|
|
@@ -2961,16 +4067,13 @@ window.__ModuleLoader__.load({
|
|
|
2961
4067
|
(0, react.useEffect)(() => {
|
|
2962
4068
|
if (streaming) setTyping(true);
|
|
2963
4069
|
}, [streaming]);
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
predictive: streaming,
|
|
2970
|
-
controlScroll,
|
|
2971
|
-
hostRef: followRootRef,
|
|
4070
|
+
if (!streaming && !live && text.trim() === "") return null;
|
|
4071
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4072
|
+
ref: followRootRef,
|
|
4073
|
+
className: TypewriterAssistantNodeView_module_css_default.follow,
|
|
4074
|
+
"data-follow-text": "",
|
|
2972
4075
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
|
|
2973
|
-
text: live ?
|
|
4076
|
+
text: live ? markdownShown : text,
|
|
2974
4077
|
streaming: live,
|
|
2975
4078
|
labels,
|
|
2976
4079
|
fileMentions: live ? void 0 : fileMentions
|
|
@@ -2991,7 +4094,7 @@ window.__ModuleLoader__.load({
|
|
|
2991
4094
|
};
|
|
2992
4095
|
}
|
|
2993
4096
|
/**
|
|
2994
|
-
* Apply searchable hidden state without unmounting a stable subtree
|
|
4097
|
+
* Apply searchable hidden state without unmounting a stable subtree 鈥?the
|
|
2995
4098
|
* Host's completion fold hides the answer-inline reasoning this way, so the
|
|
2996
4099
|
* takeover renderer must reproduce it to stay inside the contract: the row
|
|
2997
4100
|
* disappears into the Host's process summary and comes back on find-in-page.
|
|
@@ -3048,21 +4151,29 @@ window.__ModuleLoader__.load({
|
|
|
3048
4151
|
* click stay on the disclosure chrome, which the plugin's AnimatedDisclosure
|
|
3049
4152
|
* renders with a height-animated body (the harness primitive would mount and
|
|
3050
4153
|
* unmount it, which cannot glide). The row opens only while this block is
|
|
3051
|
-
* the streaming tail and closes as soon as thinking ends
|
|
3052
|
-
* or the assistant node settling
|
|
4154
|
+
* the streaming tail and closes as soon as thinking ends 鈥?a later block,
|
|
4155
|
+
* or the assistant node settling 鈥?not when the rest of the reply is
|
|
3053
4156
|
* still streaming.
|
|
3054
4157
|
*/
|
|
3055
|
-
function AnimatedReasoning({ text, running, preset, thinkAutoExpand, motionReduced, shouldHoldBack, followSpeedCpsRef, followRevealScaleRef, t }) {
|
|
4158
|
+
function AnimatedReasoning({ text, running, preset, thinkAutoExpand, motionReduced, logarithmicFade, shouldHoldBack, followSpeedCpsRef, followRevealScaleRef, snapAutoCollapse, t }) {
|
|
3056
4159
|
const reduced = motionReduced;
|
|
3057
4160
|
const [expanded, setExpanded] = (0, react.useState)(running && thinkAutoExpand);
|
|
3058
4161
|
const [autoClosed, setAutoClosed] = (0, react.useState)(false);
|
|
3059
4162
|
const summaryRef = (0, react.useRef)(null);
|
|
4163
|
+
const fadeRootRef = (0, react.useRef)(null);
|
|
4164
|
+
const thinkBodyRef = fadeRootRef;
|
|
4165
|
+
const localFadeSpeedRef = (0, react.useRef)(35);
|
|
4166
|
+
const fadeSpeedRef = followSpeedCpsRef ?? localFadeSpeedRef;
|
|
4167
|
+
const userScrolledRef = (0, react.useRef)(false);
|
|
4168
|
+
const rafIdRef = (0, react.useRef)(0);
|
|
4169
|
+
const followActiveRef = (0, react.useRef)(false);
|
|
4170
|
+
followActiveRef.current = running && expanded;
|
|
3060
4171
|
const commitAnchorRef = (0, react.useRef)(null);
|
|
3061
4172
|
const displayed = useSmoothStreamContent(text, {
|
|
3062
4173
|
enabled: running && !reduced,
|
|
3063
4174
|
preset,
|
|
3064
4175
|
shouldHoldBack,
|
|
3065
|
-
speedCpsRef:
|
|
4176
|
+
speedCpsRef: fadeSpeedRef,
|
|
3066
4177
|
revealScaleRef: followRevealScaleRef,
|
|
3067
4178
|
onRevealCommit: () => {
|
|
3068
4179
|
notifyFollowCommit(commitAnchorRef.current);
|
|
@@ -3070,13 +4181,70 @@ window.__ModuleLoader__.load({
|
|
|
3070
4181
|
});
|
|
3071
4182
|
const shown = running && !reduced ? displayed : text;
|
|
3072
4183
|
const summary = running ? latestLine(shown) : firstLine(text);
|
|
4184
|
+
useLogarithmicFade(fadeRootRef, logarithmicFade && !reduced && expanded, running, fadeSpeedRef);
|
|
3073
4185
|
(0, react.useLayoutEffect)(() => {
|
|
3074
4186
|
if (thinkAutoExpand) {
|
|
3075
4187
|
setExpanded(running);
|
|
3076
4188
|
setAutoClosed(!running);
|
|
3077
4189
|
}
|
|
4190
|
+
if (running) userScrolledRef.current = false;
|
|
3078
4191
|
notifyFollowCommit(commitAnchorRef.current);
|
|
3079
4192
|
}, [running, thinkAutoExpand]);
|
|
4193
|
+
(0, react.useEffect)(() => {
|
|
4194
|
+
if (!running || !expanded || userScrolledRef.current) return;
|
|
4195
|
+
const el = thinkBodyRef.current;
|
|
4196
|
+
if (el === null) return;
|
|
4197
|
+
if (rafIdRef.current === 0) rafIdRef.current = requestAnimationFrame(() => {
|
|
4198
|
+
rafIdRef.current = 0;
|
|
4199
|
+
if (!followActiveRef.current || userScrolledRef.current || el === null) return;
|
|
4200
|
+
if (el.scrollHeight - el.scrollTop - el.clientHeight > 2) el.scrollTop = Number.MAX_SAFE_INTEGER;
|
|
4201
|
+
});
|
|
4202
|
+
}, [
|
|
4203
|
+
running,
|
|
4204
|
+
expanded,
|
|
4205
|
+
shown
|
|
4206
|
+
]);
|
|
4207
|
+
(0, react.useEffect)(() => {
|
|
4208
|
+
return () => {
|
|
4209
|
+
if (rafIdRef.current !== 0) {
|
|
4210
|
+
cancelAnimationFrame(rafIdRef.current);
|
|
4211
|
+
rafIdRef.current = 0;
|
|
4212
|
+
}
|
|
4213
|
+
};
|
|
4214
|
+
}, []);
|
|
4215
|
+
(0, react.useEffect)(() => {
|
|
4216
|
+
const el = thinkBodyRef.current;
|
|
4217
|
+
if (el === null) return;
|
|
4218
|
+
let isPointerDown = false;
|
|
4219
|
+
const onWheel = (e) => {
|
|
4220
|
+
if (e.deltaY < 0) userScrolledRef.current = true;
|
|
4221
|
+
else if (e.deltaY > 0) {
|
|
4222
|
+
if (el.scrollHeight - el.scrollTop - el.clientHeight <= 30) userScrolledRef.current = false;
|
|
4223
|
+
}
|
|
4224
|
+
};
|
|
4225
|
+
const onPointerDown = () => {
|
|
4226
|
+
isPointerDown = true;
|
|
4227
|
+
};
|
|
4228
|
+
const onPointerUp = () => {
|
|
4229
|
+
isPointerDown = false;
|
|
4230
|
+
};
|
|
4231
|
+
const onScroll = () => {
|
|
4232
|
+
if (el.scrollHeight - el.scrollTop - el.clientHeight <= 30) userScrolledRef.current = false;
|
|
4233
|
+
else if (isPointerDown) userScrolledRef.current = true;
|
|
4234
|
+
};
|
|
4235
|
+
el.addEventListener("wheel", onWheel, { passive: true });
|
|
4236
|
+
el.addEventListener("pointerdown", onPointerDown, { passive: true });
|
|
4237
|
+
window.addEventListener("pointerup", onPointerUp, { passive: true });
|
|
4238
|
+
window.addEventListener("pointercancel", onPointerUp, { passive: true });
|
|
4239
|
+
el.addEventListener("scroll", onScroll, { passive: true });
|
|
4240
|
+
return () => {
|
|
4241
|
+
el.removeEventListener("wheel", onWheel);
|
|
4242
|
+
el.removeEventListener("pointerdown", onPointerDown);
|
|
4243
|
+
window.removeEventListener("pointerup", onPointerUp);
|
|
4244
|
+
window.removeEventListener("pointercancel", onPointerUp);
|
|
4245
|
+
el.removeEventListener("scroll", onScroll);
|
|
4246
|
+
};
|
|
4247
|
+
}, []);
|
|
3080
4248
|
(0, react.useEffect)(() => {
|
|
3081
4249
|
const element = summaryRef.current;
|
|
3082
4250
|
if (element === null) return;
|
|
@@ -3097,14 +4265,14 @@ window.__ModuleLoader__.load({
|
|
|
3097
4265
|
leadingClassName: TypewriterAssistantNodeView_module_css_default.thinkLeading,
|
|
3098
4266
|
titleClassName: TypewriterAssistantNodeView_module_css_default.thinkTitle,
|
|
3099
4267
|
chevronClassName: TypewriterAssistantNodeView_module_css_default.thinkChevron,
|
|
3100
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3101
|
-
title: "
|
|
4268
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconThink, { size: 14 }),
|
|
4269
|
+
title: t("message.think"),
|
|
3102
4270
|
open: expanded,
|
|
3103
4271
|
onToggle: () => {
|
|
3104
4272
|
setAutoClosed(false);
|
|
3105
4273
|
setExpanded((value) => !value);
|
|
3106
4274
|
},
|
|
3107
|
-
bodyTransition: !autoClosed,
|
|
4275
|
+
bodyTransition: !autoClosed || !snapAutoCollapse,
|
|
3108
4276
|
collapsedContent: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3109
4277
|
className: TypewriterAssistantNodeView_module_css_default.thinkSeparator,
|
|
3110
4278
|
"aria-hidden": true
|
|
@@ -3115,6 +4283,7 @@ window.__ModuleLoader__.load({
|
|
|
3115
4283
|
children: summary
|
|
3116
4284
|
})] }),
|
|
3117
4285
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4286
|
+
ref: fadeRootRef,
|
|
3118
4287
|
className: TypewriterAssistantNodeView_module_css_default.thinkBody,
|
|
3119
4288
|
children: shown
|
|
3120
4289
|
})
|
|
@@ -3127,14 +4296,17 @@ window.__ModuleLoader__.load({
|
|
|
3127
4296
|
* streaming is revealed by the smoother through the Harness Markdown
|
|
3128
4297
|
* renderer at a rate that tracks arrival. Reasoning blocks keep the
|
|
3129
4298
|
* built-in Think disclosure and only receive a smoothed text feed; the
|
|
3130
|
-
* outer node owns conversation-port follow
|
|
3131
|
-
*
|
|
3132
|
-
*
|
|
3133
|
-
* renders with the full
|
|
4299
|
+
* outer node owns conversation-port follow through both streaming and the
|
|
4300
|
+
* final text drain. Keeping one owner avoids a lifecycle handoff that would
|
|
4301
|
+
* reopen and later retire a second runway. The FPS guard holds offscreen
|
|
4302
|
+
* reveals when the frame rate is degraded. Settled text renders with the full
|
|
4303
|
+
* Markdown pipeline.
|
|
3134
4304
|
*/
|
|
3135
|
-
const TypewriterAssistantNodeView = (0, react.memo)(function TypewriterAssistantNodeView({ mode: _mode = DEFAULT_STREAM_CONFIG.mode, preset = DEFAULT_STREAM_CONFIG.preset, revealCharsPerSec: _revealCharsPerSec = DEFAULT_STREAM_CONFIG.revealCharsPerSec, scrollSpeedPxPerSec: _scrollSpeedPxPerSec = DEFAULT_STREAM_CONFIG.scrollSpeedPxPerSec, maxScrollSpeedPxPerSec: _maxScrollSpeedPxPerSec = DEFAULT_STREAM_CONFIG.maxScrollSpeedPxPerSec, thinkAutoExpand = DEFAULT_STREAM_SETTINGS.thinkAutoExpand, controlScroll = true, motionPreference = DEFAULT_STREAM_SETTINGS.motionPreference, node, useTurnData, openFile, loadImage, fileMentions, turnProcess, t }) {
|
|
4305
|
+
const TypewriterAssistantNodeView = (0, react.memo)(function TypewriterAssistantNodeView({ mode: _mode = DEFAULT_STREAM_CONFIG.mode, preset = DEFAULT_STREAM_CONFIG.preset, revealCharsPerSec: _revealCharsPerSec = DEFAULT_STREAM_CONFIG.revealCharsPerSec, scrollSpeedPxPerSec: _scrollSpeedPxPerSec = DEFAULT_STREAM_CONFIG.scrollSpeedPxPerSec, maxScrollSpeedPxPerSec: _maxScrollSpeedPxPerSec = DEFAULT_STREAM_CONFIG.maxScrollSpeedPxPerSec, thinkAutoExpand = DEFAULT_STREAM_SETTINGS.thinkAutoExpand, logarithmicFade = DEFAULT_STREAM_SETTINGS.logarithmicFade, controlScroll = true, motionPreference = DEFAULT_STREAM_SETTINGS.motionPreference, node, groupPart, useTurnData, openFile, loadImage, fileMentions, turnProcess, t }) {
|
|
3136
4306
|
const data = node.data;
|
|
3137
4307
|
const streaming = data.status === "running";
|
|
4308
|
+
const blocks = groupPart === "reasoning" ? data.blocks.filter((block) => block.kind === "reasoning") : groupPart === "response" ? data.blocks.filter((block) => block.kind !== "reasoning") : data.blocks;
|
|
4309
|
+
const rowOwnsPort = groupPart !== "reasoning";
|
|
3138
4310
|
const reduced = useMotionReduced(motionPreference);
|
|
3139
4311
|
const reasoningHidden = turnProcess !== void 0 && turnProcess.foldable && turnProcess.spec.answerStep === data.step && turnProcess.spec.inlineReasoning && !turnProcess.open;
|
|
3140
4312
|
const revealProcess = (0, react.useCallback)(() => {
|
|
@@ -3144,7 +4316,16 @@ window.__ModuleLoader__.load({
|
|
|
3144
4316
|
const rootSpeedRef = (0, react.useRef)(35);
|
|
3145
4317
|
const rootRevealedCharsRef = (0, react.useRef)(0);
|
|
3146
4318
|
const rootRevealScaleRef = (0, react.useRef)(1);
|
|
3147
|
-
const
|
|
4319
|
+
const previousStreamingRef = (0, react.useRef)(streaming);
|
|
4320
|
+
const [textRevealActive, setTextRevealActive] = (0, react.useState)(false);
|
|
4321
|
+
const completionCandidate = !streaming && previousStreamingRef.current && blocks.some((block) => block.kind === "text" && block.text.trim() !== "");
|
|
4322
|
+
(0, react.useLayoutEffect)(() => {
|
|
4323
|
+
previousStreamingRef.current = streaming;
|
|
4324
|
+
}, [streaming]);
|
|
4325
|
+
const updateTextRevealActivity = (0, react.useCallback)((active) => {
|
|
4326
|
+
setTextRevealActive((previous) => previous === active ? previous : active);
|
|
4327
|
+
}, []);
|
|
4328
|
+
const reasoningTailIndex = streaming && blocks[blocks.length - 1]?.kind === "reasoning" ? blocks.length - 1 : -1;
|
|
3148
4329
|
const reasoningOwnsSpeed = reasoningTailIndex !== -1;
|
|
3149
4330
|
const rootPredictiveRef = (0, react.useRef)(false);
|
|
3150
4331
|
const previousReasoningTailRef = (0, react.useRef)(-1);
|
|
@@ -3183,34 +4364,38 @@ window.__ModuleLoader__.load({
|
|
|
3183
4364
|
const imageLoader = loadImage ?? (async () => {
|
|
3184
4365
|
throw new Error(t("image.serviceUnavailable"));
|
|
3185
4366
|
});
|
|
3186
|
-
if (!(streaming || data.status === "interrupted" ||
|
|
3187
|
-
const announcementText =
|
|
4367
|
+
if (!(streaming || data.status === "interrupted" || blocks.some((block) => block.kind !== "tool-call"))) return null;
|
|
4368
|
+
const announcementText = blocks.filter((block) => block.kind === "text").map((block) => block.text).join("\n");
|
|
4369
|
+
const stepTailIsReasoning = data.blocks[data.blocks.length - 1]?.kind === "reasoning";
|
|
3188
4370
|
const rendered = [];
|
|
3189
|
-
const last =
|
|
4371
|
+
const last = blocks.length - 1;
|
|
3190
4372
|
let lastFollow = -1;
|
|
3191
|
-
|
|
3192
|
-
|
|
4373
|
+
let lastText = -1;
|
|
4374
|
+
for (let index = 0; index < blocks.length; index += 1) {
|
|
4375
|
+
const kind = blocks[index]?.kind;
|
|
3193
4376
|
if (kind === "text" || kind === "reasoning") lastFollow = index;
|
|
4377
|
+
if (kind === "text") lastText = index;
|
|
3194
4378
|
}
|
|
3195
|
-
for (let index = 0; index <
|
|
3196
|
-
const block =
|
|
4379
|
+
for (let index = 0; index < blocks.length; index += 1) {
|
|
4380
|
+
const block = blocks[index];
|
|
3197
4381
|
if (block === void 0) continue;
|
|
3198
4382
|
switch (block.kind) {
|
|
3199
4383
|
case "text":
|
|
4384
|
+
if (!streaming && block.text.trim() === "") break;
|
|
3200
4385
|
rendered.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnimatedMarkdownText, {
|
|
3201
4386
|
text: block.text,
|
|
3202
4387
|
labels: markdownLabels,
|
|
3203
4388
|
fileMentions: mentions,
|
|
3204
4389
|
streaming,
|
|
4390
|
+
logarithmicFade: logarithmicFade && data.status !== "interrupted",
|
|
3205
4391
|
motionReduced: reduced,
|
|
3206
|
-
ownFollow: !streaming && index === lastFollow,
|
|
3207
4392
|
followSpeedCpsRef: index === lastFollow ? rootSpeedRef : void 0,
|
|
3208
4393
|
followRevealedCharsRef: index === lastFollow ? rootRevealedCharsRef : void 0,
|
|
3209
4394
|
followRevealScaleRef: index === lastFollow ? rootRevealScaleRef : void 0,
|
|
3210
4395
|
onPredictiveChange: index === lastFollow ? updateTextPrediction : void 0,
|
|
4396
|
+
onRevealActivityChange: index === lastText ? updateTextRevealActivity : void 0,
|
|
3211
4397
|
preset,
|
|
3212
|
-
shouldHoldBack
|
|
3213
|
-
controlScroll
|
|
4398
|
+
shouldHoldBack
|
|
3214
4399
|
}, index));
|
|
3215
4400
|
break;
|
|
3216
4401
|
case "reasoning":
|
|
@@ -3219,13 +4404,15 @@ window.__ModuleLoader__.load({
|
|
|
3219
4404
|
reveal: revealProcess,
|
|
3220
4405
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnimatedReasoning, {
|
|
3221
4406
|
text: block.text,
|
|
3222
|
-
running: streaming && index === last,
|
|
4407
|
+
running: streaming && index === last && (groupPart !== "reasoning" || stepTailIsReasoning),
|
|
3223
4408
|
preset,
|
|
3224
4409
|
thinkAutoExpand,
|
|
4410
|
+
logarithmicFade: logarithmicFade && data.status !== "interrupted",
|
|
3225
4411
|
motionReduced: reduced,
|
|
3226
4412
|
shouldHoldBack,
|
|
3227
4413
|
followSpeedCpsRef: reasoningOwnsSpeed && index === last ? rootSpeedRef : void 0,
|
|
3228
4414
|
followRevealScaleRef: reasoningOwnsSpeed && index === last ? rootRevealScaleRef : void 0,
|
|
4415
|
+
snapAutoCollapse: groupPart === void 0,
|
|
3229
4416
|
t
|
|
3230
4417
|
})
|
|
3231
4418
|
}, index));
|
|
@@ -3233,8 +4420,8 @@ window.__ModuleLoader__.load({
|
|
|
3233
4420
|
case "image": {
|
|
3234
4421
|
const start = index;
|
|
3235
4422
|
const group = [block];
|
|
3236
|
-
while (index + 1 <
|
|
3237
|
-
const next =
|
|
4423
|
+
while (index + 1 < blocks.length) {
|
|
4424
|
+
const next = blocks[index + 1];
|
|
3238
4425
|
if (next === void 0 || next.kind !== "image") break;
|
|
3239
4426
|
group.push(next);
|
|
3240
4427
|
index += 1;
|
|
@@ -3263,12 +4450,12 @@ window.__ModuleLoader__.load({
|
|
|
3263
4450
|
text: announcementText,
|
|
3264
4451
|
active: streaming && !reduced
|
|
3265
4452
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FollowHost, {
|
|
3266
|
-
active:
|
|
4453
|
+
active: !reduced && (streaming || completionCandidate || textRevealActive),
|
|
3267
4454
|
speedCpsRef: rootSpeedRef,
|
|
3268
4455
|
revealedCharsRef: rootRevealedCharsRef,
|
|
3269
4456
|
revealScaleRef: rootRevealScaleRef,
|
|
3270
4457
|
predictiveRef: rootPredictiveRef,
|
|
3271
|
-
controlScroll,
|
|
4458
|
+
controlScroll: controlScroll && rowOwnsPort,
|
|
3272
4459
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3273
4460
|
className: TypewriterAssistantNodeView_module_css_default.body,
|
|
3274
4461
|
children: [rendered, data.status === "interrupted" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -3371,12 +4558,18 @@ window.__ModuleLoader__.load({
|
|
|
3371
4558
|
}
|
|
3372
4559
|
const pending = /* @__PURE__ */ new Set();
|
|
3373
4560
|
const internalWrites = /* @__PURE__ */ new WeakMap();
|
|
3374
|
-
let
|
|
4561
|
+
let frameTaskId = null;
|
|
3375
4562
|
let lastFrame = null;
|
|
3376
4563
|
let debt = 0;
|
|
3377
4564
|
let stopped = false;
|
|
3378
4565
|
let announcedSettled = false;
|
|
3379
4566
|
const streamId = `dom-${Math.random().toString(36).slice(2)}`;
|
|
4567
|
+
const coordinator = FrameCoordinator.forDocument();
|
|
4568
|
+
const stopFrameTask = () => {
|
|
4569
|
+
if (frameTaskId === null) return;
|
|
4570
|
+
coordinator.unregisterTask(frameTaskId);
|
|
4571
|
+
frameTaskId = null;
|
|
4572
|
+
};
|
|
3380
4573
|
const announceSettled = () => {
|
|
3381
4574
|
lastFrame = null;
|
|
3382
4575
|
debt = 0;
|
|
@@ -3425,21 +4618,20 @@ window.__ModuleLoader__.load({
|
|
|
3425
4618
|
});
|
|
3426
4619
|
};
|
|
3427
4620
|
const scheduleFrame = () => {
|
|
3428
|
-
if (stopped || pending.size === 0 ||
|
|
3429
|
-
|
|
4621
|
+
if (stopped || pending.size === 0 || frameTaskId !== null) return;
|
|
4622
|
+
frameTaskId = coordinator.registerTask({ onSimulate: (_dtMs, now) => frame(now) });
|
|
3430
4623
|
};
|
|
3431
4624
|
const frame = (now) => {
|
|
3432
|
-
|
|
3433
|
-
if (stopped) return;
|
|
4625
|
+
if (stopped) return false;
|
|
3434
4626
|
if (pending.size === 0) {
|
|
3435
4627
|
announceSettled();
|
|
3436
|
-
|
|
4628
|
+
stopFrameTask();
|
|
4629
|
+
return false;
|
|
3437
4630
|
}
|
|
3438
4631
|
announcedSettled = false;
|
|
3439
4632
|
if (lastFrame === null) {
|
|
3440
4633
|
lastFrame = now;
|
|
3441
|
-
|
|
3442
|
-
return;
|
|
4634
|
+
return true;
|
|
3443
4635
|
}
|
|
3444
4636
|
const elapsed = Math.max(0, now - lastFrame);
|
|
3445
4637
|
lastFrame = now;
|
|
@@ -3489,10 +4681,11 @@ window.__ModuleLoader__.load({
|
|
|
3489
4681
|
speedCps: step.speedCps,
|
|
3490
4682
|
targetChars,
|
|
3491
4683
|
displayedChars,
|
|
3492
|
-
active: pending.size > 0
|
|
4684
|
+
active: pending.size > 0,
|
|
4685
|
+
producerComplete: false
|
|
3493
4686
|
});
|
|
3494
4687
|
if (pending.size === 0) announceSettled();
|
|
3495
|
-
|
|
4688
|
+
return pending.size > 0;
|
|
3496
4689
|
};
|
|
3497
4690
|
visit(root, revealInitial);
|
|
3498
4691
|
const observer = typeof MutationObserver === "undefined" ? null : new MutationObserver((mutations) => {
|
|
@@ -3521,7 +4714,7 @@ window.__ModuleLoader__.load({
|
|
|
3521
4714
|
else scheduleFrame();
|
|
3522
4715
|
return () => {
|
|
3523
4716
|
stopped = true;
|
|
3524
|
-
|
|
4717
|
+
stopFrameTask();
|
|
3525
4718
|
observer?.disconnect();
|
|
3526
4719
|
for (const [node, record] of records) {
|
|
3527
4720
|
const controlled = record.chars.slice(0, record.shown).join("");
|
|
@@ -3540,8 +4733,8 @@ window.__ModuleLoader__.load({
|
|
|
3540
4733
|
]);
|
|
3541
4734
|
}
|
|
3542
4735
|
//#endregion
|
|
3543
|
-
//#region \0dsh-css:/Users/dzlin/
|
|
3544
|
-
const css$2 = ".
|
|
4736
|
+
//#region \0dsh-css:/Users/dzlin/.codex/worktrees/pr-39-review/dsh-smooth-stream/src/client/AgentRowEntrance.module.css.mjs
|
|
4737
|
+
const css$2 = ".Mhu9Iq_surface{min-width:0}.Mhu9Iq_surface[data-entrance=active]{will-change:opacity;animation:.22s cubic-bezier(.2,.8,.2,1) both Mhu9Iq_dsh-smooth-stream-agent-row-in}@keyframes Mhu9Iq_dsh-smooth-stream-agent-row-in{0%{opacity:0;clip-path:inset(0 100% 0 0)}55%{opacity:1;clip-path:inset(0 18% 0 0)}to{opacity:1;clip-path:inset(0)}}@media (prefers-reduced-motion:reduce){.Mhu9Iq_surface[data-entrance=active]{opacity:1;clip-path:none;will-change:auto;animation:none}}";
|
|
3545
4738
|
const tagId$2 = "dsh-smooth-stream/AgentRowEntrance.module.css";
|
|
3546
4739
|
if (typeof document !== "undefined") {
|
|
3547
4740
|
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]");
|
|
@@ -3554,8 +4747,8 @@ window.__ModuleLoader__.load({
|
|
|
3554
4747
|
tag.textContent = css$2;
|
|
3555
4748
|
}
|
|
3556
4749
|
var AgentRowEntrance_module_css_default = {
|
|
3557
|
-
"
|
|
3558
|
-
"
|
|
4750
|
+
"surface": "Mhu9Iq_surface",
|
|
4751
|
+
"dsh-smooth-stream-agent-row-in": "Mhu9Iq_dsh-smooth-stream-agent-row-in"
|
|
3559
4752
|
};
|
|
3560
4753
|
//#endregion
|
|
3561
4754
|
//#region src/client/TypewriterToolNodeView.tsx
|
|
@@ -3733,8 +4926,8 @@ window.__ModuleLoader__.load({
|
|
|
3733
4926
|
};
|
|
3734
4927
|
}
|
|
3735
4928
|
//#endregion
|
|
3736
|
-
//#region \0dsh-css:/Users/dzlin/
|
|
3737
|
-
const css$1 = ".
|
|
4929
|
+
//#region \0dsh-css:/Users/dzlin/.codex/worktrees/pr-39-review/dsh-smooth-stream/src/client/SmoothStreamCard.module.css.mjs
|
|
4930
|
+
const css$1 = ".v5eDwW_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;list-style:none;transition:border-color .16s,background .16s;display:flex}.v5eDwW_card:hover{border-color:var(--dsw-alias-label-dimmed)}.v5eDwW_cardOpen{border-color:var(--dsw-alias-label-dimmed);background:var(--dsw-alias-bg-layer-2)}.v5eDwW_header{appearance:none;cursor:pointer;text-align:left;width:100%;font:inherit;color:inherit;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.v5eDwW_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.v5eDwW_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.v5eDwW_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.v5eDwW_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.v5eDwW_version{text-overflow:ellipsis;white-space:nowrap;max-width:16rem;color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px;line-height:1.5;overflow:hidden}.v5eDwW_pending{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.v5eDwW_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.v5eDwW_chevronOpen{transform:rotate(180deg)}.v5eDwW_body{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;margin:0 16px;padding-bottom:8px;display:flex}.v5eDwW_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.v5eDwW_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.v5eDwW_field+.v5eDwW_field{border-top:1px solid var(--dsw-alias-border-l2)}.v5eDwW_fieldDisabled{opacity:.5}.v5eDwW_fieldHead{justify-content:space-between;align-items:center;gap:8px;display:flex}.v5eDwW_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.v5eDwW_toggle{width:16px;height:16px;accent-color:var(--dsw-alias-brand-primary);cursor:pointer}.v5eDwW_toggle:disabled{cursor:default}.v5eDwW_toggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.v5eDwW_choiceRow{flex-wrap:wrap;gap:6px 14px;margin:2px 0 0;padding:0;display:flex}.v5eDwW_choice{color:var(--dsw-alias-label-secondary);cursor:pointer;align-items:center;gap:5px;font-size:12px;line-height:1.5;display:flex}.v5eDwW_choiceInput{width:14px;height:14px;accent-color:var(--dsw-alias-brand-primary);cursor:pointer}.v5eDwW_choice:has(.v5eDwW_choiceInput:disabled){cursor:default;opacity:.55}.v5eDwW_choiceInput:disabled{cursor:default}.v5eDwW_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.v5eDwW_failure{justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.v5eDwW_updateRow{border-top:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.v5eDwW_updateCopy{flex-direction:column;gap:4px;min-width:0;display:flex}.v5eDwW_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.v5eDwW_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.v5eDwW_save,.v5eDwW_discard,.v5eDwW_update{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.v5eDwW_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.v5eDwW_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.v5eDwW_discard:hover:not(:disabled),.v5eDwW_update:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed);color:var(--dsw-alias-label-primary)}.v5eDwW_update{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0;flex:none;align-items:center;gap:6px;display:inline-flex}.v5eDwW_discard:disabled,.v5eDwW_save:disabled,.v5eDwW_update:disabled{opacity:.4;cursor:default}.v5eDwW_discard:focus-visible,.v5eDwW_save:focus-visible,.v5eDwW_update:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}";
|
|
3738
4931
|
const tagId$1 = "dsh-smooth-stream/SmoothStreamCard.module.css";
|
|
3739
4932
|
if (typeof document !== "undefined") {
|
|
3740
4933
|
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]");
|
|
@@ -3747,35 +4940,35 @@ window.__ModuleLoader__.load({
|
|
|
3747
4940
|
tag.textContent = css$1;
|
|
3748
4941
|
}
|
|
3749
4942
|
var SmoothStreamCard_module_css_default = {
|
|
3750
|
-
"
|
|
3751
|
-
"
|
|
3752
|
-
"
|
|
3753
|
-
"
|
|
3754
|
-
"
|
|
3755
|
-
"
|
|
3756
|
-
"
|
|
3757
|
-
"
|
|
3758
|
-
"description": "
|
|
3759
|
-
"
|
|
3760
|
-
"
|
|
3761
|
-
"
|
|
3762
|
-
"
|
|
3763
|
-
"
|
|
3764
|
-
"
|
|
3765
|
-
"
|
|
3766
|
-
"
|
|
3767
|
-
"
|
|
3768
|
-
"
|
|
3769
|
-
"header": "
|
|
3770
|
-
"
|
|
3771
|
-
"
|
|
3772
|
-
"
|
|
3773
|
-
"
|
|
3774
|
-
"
|
|
3775
|
-
"
|
|
3776
|
-
"
|
|
3777
|
-
"
|
|
3778
|
-
"
|
|
4943
|
+
"footer": "v5eDwW_footer",
|
|
4944
|
+
"name": "v5eDwW_name",
|
|
4945
|
+
"body": "v5eDwW_body",
|
|
4946
|
+
"fieldHead": "v5eDwW_fieldHead",
|
|
4947
|
+
"fieldDisabled": "v5eDwW_fieldDisabled",
|
|
4948
|
+
"choice": "v5eDwW_choice",
|
|
4949
|
+
"chevronOpen": "v5eDwW_chevronOpen",
|
|
4950
|
+
"update": "v5eDwW_update",
|
|
4951
|
+
"description": "v5eDwW_description",
|
|
4952
|
+
"card": "v5eDwW_card",
|
|
4953
|
+
"pending": "v5eDwW_pending",
|
|
4954
|
+
"updateRow": "v5eDwW_updateRow",
|
|
4955
|
+
"readOnly": "v5eDwW_readOnly",
|
|
4956
|
+
"chevron": "v5eDwW_chevron",
|
|
4957
|
+
"cardOpen": "v5eDwW_cardOpen",
|
|
4958
|
+
"headText": "v5eDwW_headText",
|
|
4959
|
+
"choiceRow": "v5eDwW_choiceRow",
|
|
4960
|
+
"failure": "v5eDwW_failure",
|
|
4961
|
+
"failed": "v5eDwW_failed",
|
|
4962
|
+
"header": "v5eDwW_header",
|
|
4963
|
+
"save": "v5eDwW_save",
|
|
4964
|
+
"toggle": "v5eDwW_toggle",
|
|
4965
|
+
"field": "v5eDwW_field",
|
|
4966
|
+
"choiceInput": "v5eDwW_choiceInput",
|
|
4967
|
+
"version": "v5eDwW_version",
|
|
4968
|
+
"updateCopy": "v5eDwW_updateCopy",
|
|
4969
|
+
"label": "v5eDwW_label",
|
|
4970
|
+
"discard": "v5eDwW_discard",
|
|
4971
|
+
"hint": "v5eDwW_hint"
|
|
3779
4972
|
};
|
|
3780
4973
|
//#endregion
|
|
3781
4974
|
//#region src/client/SmoothStreamCard.tsx
|
|
@@ -3820,7 +5013,7 @@ window.__ModuleLoader__.load({
|
|
|
3820
5013
|
className: SmoothStreamCard_module_css_default.pending,
|
|
3821
5014
|
children: t("unsaved")
|
|
3822
5015
|
}) : null,
|
|
3823
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
5016
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { className: open ? `${SmoothStreamCard_module_css_default.chevron} ${SmoothStreamCard_module_css_default.chevronOpen}` : SmoothStreamCard_module_css_default.chevron })
|
|
3824
5017
|
]
|
|
3825
5018
|
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3826
5019
|
className: SmoothStreamCard_module_css_default.body,
|
|
@@ -3891,6 +5084,27 @@ window.__ModuleLoader__.load({
|
|
|
3891
5084
|
children: t("controlScrollHint")
|
|
3892
5085
|
})]
|
|
3893
5086
|
}),
|
|
5087
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5088
|
+
className: state.enabled ? SmoothStreamCard_module_css_default.field : `${SmoothStreamCard_module_css_default.field} ${SmoothStreamCard_module_css_default.fieldDisabled}`,
|
|
5089
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5090
|
+
className: SmoothStreamCard_module_css_default.fieldHead,
|
|
5091
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5092
|
+
className: SmoothStreamCard_module_css_default.label,
|
|
5093
|
+
children: t("logarithmicFade")
|
|
5094
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5095
|
+
type: "checkbox",
|
|
5096
|
+
className: SmoothStreamCard_module_css_default.toggle,
|
|
5097
|
+
checked: state.logarithmicFade,
|
|
5098
|
+
disabled: !state.writable || state.saving || !state.enabled,
|
|
5099
|
+
onChange: (event) => {
|
|
5100
|
+
props.edit({ logarithmicFade: event.target.checked });
|
|
5101
|
+
}
|
|
5102
|
+
})]
|
|
5103
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5104
|
+
className: SmoothStreamCard_module_css_default.hint,
|
|
5105
|
+
children: t("logarithmicFadeHint")
|
|
5106
|
+
})]
|
|
5107
|
+
}),
|
|
3894
5108
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3895
5109
|
className: state.enabled ? SmoothStreamCard_module_css_default.field : `${SmoothStreamCard_module_css_default.field} ${SmoothStreamCard_module_css_default.fieldDisabled}`,
|
|
3896
5110
|
children: [
|
|
@@ -3942,6 +5156,57 @@ window.__ModuleLoader__.load({
|
|
|
3942
5156
|
})
|
|
3943
5157
|
]
|
|
3944
5158
|
}),
|
|
5159
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5160
|
+
className: state.enabled ? SmoothStreamCard_module_css_default.field : `${SmoothStreamCard_module_css_default.field} ${SmoothStreamCard_module_css_default.fieldDisabled}`,
|
|
5161
|
+
children: [
|
|
5162
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5163
|
+
className: SmoothStreamCard_module_css_default.fieldHead,
|
|
5164
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5165
|
+
className: SmoothStreamCard_module_css_default.label,
|
|
5166
|
+
children: t("preset")
|
|
5167
|
+
})
|
|
5168
|
+
}),
|
|
5169
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5170
|
+
className: SmoothStreamCard_module_css_default.hint,
|
|
5171
|
+
children: t("presetHint")
|
|
5172
|
+
}),
|
|
5173
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5174
|
+
className: SmoothStreamCard_module_css_default.choiceRow,
|
|
5175
|
+
role: "radiogroup",
|
|
5176
|
+
"aria-label": t("preset"),
|
|
5177
|
+
children: [
|
|
5178
|
+
[
|
|
5179
|
+
"realtime",
|
|
5180
|
+
"presetRealtime",
|
|
5181
|
+
"presetRealtimeHint"
|
|
5182
|
+
],
|
|
5183
|
+
[
|
|
5184
|
+
"balanced",
|
|
5185
|
+
"presetBalanced",
|
|
5186
|
+
"presetBalancedHint"
|
|
5187
|
+
],
|
|
5188
|
+
[
|
|
5189
|
+
"silky",
|
|
5190
|
+
"presetSilky",
|
|
5191
|
+
"presetSilkyHint"
|
|
5192
|
+
]
|
|
5193
|
+
].map(([value, label, hint]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5194
|
+
className: SmoothStreamCard_module_css_default.choice,
|
|
5195
|
+
title: t(hint),
|
|
5196
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5197
|
+
type: "radio",
|
|
5198
|
+
className: SmoothStreamCard_module_css_default.choiceInput,
|
|
5199
|
+
name: "smooth-stream-preset",
|
|
5200
|
+
checked: state.preset === value,
|
|
5201
|
+
disabled: !state.writable || state.saving || !state.enabled,
|
|
5202
|
+
onChange: () => {
|
|
5203
|
+
props.edit({ preset: value });
|
|
5204
|
+
}
|
|
5205
|
+
}), t(label)]
|
|
5206
|
+
}, value))
|
|
5207
|
+
})
|
|
5208
|
+
]
|
|
5209
|
+
}),
|
|
3945
5210
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3946
5211
|
className: state.enabled ? SmoothStreamCard_module_css_default.field : `${SmoothStreamCard_module_css_default.field} ${SmoothStreamCard_module_css_default.fieldDisabled}`,
|
|
3947
5212
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -4003,7 +5268,7 @@ window.__ModuleLoader__.load({
|
|
|
4003
5268
|
onClick: props.upgrade,
|
|
4004
5269
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4005
5270
|
"aria-hidden": "true",
|
|
4006
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
5271
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconRefresh, {})
|
|
4007
5272
|
}), t(state.upgrading ? "updating" : "update")]
|
|
4008
5273
|
})]
|
|
4009
5274
|
}),
|
|
@@ -4084,12 +5349,14 @@ window.__ModuleLoader__.load({
|
|
|
4084
5349
|
hooks: { smoothStreamCard: this.store },
|
|
4085
5350
|
edit: (patch) => {
|
|
4086
5351
|
if (this.saving) return;
|
|
4087
|
-
if (patch.enabled !== void 0 || patch.controlScroll !== void 0 || patch.motionPreference !== void 0 || patch.thinkAutoExpand !== void 0) this.stagedBase = {
|
|
5352
|
+
if (patch.enabled !== void 0 || patch.controlScroll !== void 0 || patch.preset !== void 0 || patch.motionPreference !== void 0 || patch.thinkAutoExpand !== void 0 || patch.logarithmicFade !== void 0) this.stagedBase = {
|
|
4088
5353
|
...this.baseValues(),
|
|
4089
5354
|
...patch.enabled === void 0 ? {} : { enabled: patch.enabled },
|
|
4090
5355
|
...patch.controlScroll === void 0 ? {} : { controlScroll: patch.controlScroll },
|
|
5356
|
+
...patch.preset === void 0 ? {} : { preset: patch.preset },
|
|
4091
5357
|
...patch.motionPreference === void 0 ? {} : { motionPreference: patch.motionPreference },
|
|
4092
|
-
...patch.thinkAutoExpand === void 0 ? {} : { thinkAutoExpand: patch.thinkAutoExpand }
|
|
5358
|
+
...patch.thinkAutoExpand === void 0 ? {} : { thinkAutoExpand: patch.thinkAutoExpand },
|
|
5359
|
+
...patch.logarithmicFade === void 0 ? {} : { logarithmicFade: patch.logarithmicFade }
|
|
4093
5360
|
};
|
|
4094
5361
|
if (this.loadedDebug !== void 0 && (patch.debugEnabled !== void 0 || patch.debugTuning !== void 0)) this.stagedDebug = {
|
|
4095
5362
|
...this.debugValues(),
|
|
@@ -4142,8 +5409,10 @@ window.__ModuleLoader__.load({
|
|
|
4142
5409
|
return this.stagedBase ?? {
|
|
4143
5410
|
enabled: this.loaded?.enabled ?? DEFAULT_STREAM_SETTINGS.enabled,
|
|
4144
5411
|
controlScroll: this.loaded?.controlScroll ?? DEFAULT_STREAM_SETTINGS.controlScroll,
|
|
5412
|
+
preset: this.loaded?.preset ?? DEFAULT_STREAM_SETTINGS.preset,
|
|
4145
5413
|
motionPreference: this.loaded?.motionPreference ?? DEFAULT_STREAM_SETTINGS.motionPreference,
|
|
4146
|
-
thinkAutoExpand: this.loaded?.thinkAutoExpand ?? DEFAULT_STREAM_SETTINGS.thinkAutoExpand
|
|
5414
|
+
thinkAutoExpand: this.loaded?.thinkAutoExpand ?? DEFAULT_STREAM_SETTINGS.thinkAutoExpand,
|
|
5415
|
+
logarithmicFade: this.loaded?.logarithmicFade ?? DEFAULT_STREAM_SETTINGS.logarithmicFade
|
|
4147
5416
|
};
|
|
4148
5417
|
}
|
|
4149
5418
|
debugValues() {
|
|
@@ -4263,12 +5532,20 @@ window.__ModuleLoader__.load({
|
|
|
4263
5532
|
"npm",
|
|
4264
5533
|
"development",
|
|
4265
5534
|
"unmanaged"
|
|
4266
|
-
].includes(data.installation) || typeof data.writable !== "boolean" || typeof data.enabled !== "boolean" || typeof data.controlScroll !== "boolean" || ![
|
|
5535
|
+
].includes(data.installation) || typeof data.writable !== "boolean" || typeof data.enabled !== "boolean" || typeof data.controlScroll !== "boolean" || data.preset !== void 0 && ![
|
|
5536
|
+
"realtime",
|
|
5537
|
+
"balanced",
|
|
5538
|
+
"silky"
|
|
5539
|
+
].includes(data.preset) || ![
|
|
4267
5540
|
"auto",
|
|
4268
5541
|
"force-smooth",
|
|
4269
5542
|
"force-reduced"
|
|
4270
|
-
].includes(data.motionPreference) || typeof data.thinkAutoExpand !== "boolean" || typeof data.canUpgrade !== "boolean") throw new Error("dsh-smooth-stream: malformed settings response");
|
|
4271
|
-
return
|
|
5543
|
+
].includes(data.motionPreference) || typeof data.thinkAutoExpand !== "boolean" || data.logarithmicFade !== void 0 && typeof data.logarithmicFade !== "boolean" || typeof data.canUpgrade !== "boolean") throw new Error("dsh-smooth-stream: malformed settings response");
|
|
5544
|
+
return {
|
|
5545
|
+
...data,
|
|
5546
|
+
preset: data.preset ?? "balanced",
|
|
5547
|
+
logarithmicFade: data.logarithmicFade ?? true
|
|
5548
|
+
};
|
|
4272
5549
|
}
|
|
4273
5550
|
function upgradeView(value) {
|
|
4274
5551
|
if (record(value)?.restartRequired !== true) throw new Error("dsh-smooth-stream: malformed update response");
|
|
@@ -4297,8 +5574,10 @@ window.__ModuleLoader__.load({
|
|
|
4297
5574
|
return settingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.write, {
|
|
4298
5575
|
enabled: settings.enabled,
|
|
4299
5576
|
controlScroll: settings.controlScroll,
|
|
5577
|
+
preset: settings.preset,
|
|
4300
5578
|
motionPreference: settings.motionPreference,
|
|
4301
5579
|
thinkAutoExpand: settings.thinkAutoExpand,
|
|
5580
|
+
logarithmicFade: settings.logarithmicFade,
|
|
4302
5581
|
...settings.debugEnabled === void 0 || settings.debugTuning === void 0 ? {} : {
|
|
4303
5582
|
debugEnabled: settings.debugEnabled,
|
|
4304
5583
|
debugTuning: settings.debugTuning
|
|
@@ -4320,8 +5599,8 @@ window.__ModuleLoader__.load({
|
|
|
4320
5599
|
};
|
|
4321
5600
|
}
|
|
4322
5601
|
//#endregion
|
|
4323
|
-
//#region \0dsh-css:/Users/dzlin/
|
|
4324
|
-
const css = ".
|
|
5602
|
+
//#region \0dsh-css:/Users/dzlin/.codex/worktrees/pr-39-review/dsh-smooth-stream/src/client/DebugPanel.module.css.mjs
|
|
5603
|
+
const css = "._6TTfZq_trigger,._6TTfZq_iconButton{appearance:none;width:32px;height:32px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;justify-content:center;align-items:center;padding:0;display:inline-flex}._6TTfZq_trigger:hover,._6TTfZq_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._6TTfZq_iconButton:disabled{opacity:.45;cursor:not-allowed}._6TTfZq_triggerActive{border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-state-business-primary)}._6TTfZq_trigger:focus-visible,._6TTfZq_iconButton:focus-visible,._6TTfZq_secondaryButton:focus-visible,._6TTfZq_primaryButton:focus-visible,._6TTfZq_number:focus-visible,._6TTfZq_range:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}._6TTfZq_panel{z-index:30;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);width:min(328px,100vw - 32px);min-height:280px;box-shadow:0 12px 32px color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent);color:var(--dsw-alias-label-primary);border-radius:8px;flex-direction:column;display:flex;position:fixed;top:88px;bottom:16px;right:16px;overflow:hidden}._6TTfZq_panelHeader{border-bottom:1px solid var(--dsw-alias-border-l2);flex:none;align-items:center;gap:8px;min-height:48px;padding:0 8px 0 14px;display:flex}._6TTfZq_statusDot{background:var(--dsw-alias-label-caption);border-radius:50%;flex:none;width:7px;height:7px}._6TTfZq_statusLive{background:var(--dsw-alias-state-success-primary);box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent)}._6TTfZq_title{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:14px;font-weight:600;line-height:20px;overflow:hidden}._6TTfZq_state,._6TTfZq_unsaved{color:var(--dsw-alias-label-tertiary);white-space:nowrap;flex:none;font-size:10px;line-height:16px}._6TTfZq_unsaved{color:var(--dsw-alias-state-warn-primary)}._6TTfZq_scrollArea{overscroll-behavior:contain;min-height:0;overflow-y:auto}._6TTfZq_section{padding:14px}._6TTfZq_guide{border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);margin:0;padding:12px 14px;font-size:11px;line-height:1.55}._6TTfZq_section+._6TTfZq_section{border-top:1px solid var(--dsw-alias-border-l2)}._6TTfZq_section h2{color:var(--dsw-alias-label-secondary);margin:0 0 10px;font-size:12px;font-weight:600;line-height:18px}._6TTfZq_metrics{grid-template-columns:repeat(2,minmax(0,1fr));gap:0 16px;margin:0;display:grid}._6TTfZq_metric{border-bottom:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 65%, transparent);justify-content:space-between;align-items:baseline;gap:8px;min-width:0;padding:5px 0;display:flex}._6TTfZq_metric dt{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;line-height:16px;overflow:hidden}._6TTfZq_metric dd{min-width:0;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;text-overflow:ellipsis;white-space:nowrap;margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;line-height:16px;overflow:hidden}._6TTfZq_metric dd[data-tone=good]{color:var(--dsw-alias-state-success-primary)}._6TTfZq_metric dd[data-tone=warn]{color:var(--dsw-alias-state-warn-primary)}._6TTfZq_control{flex-direction:column;gap:5px;padding:6px 0;display:flex}._6TTfZq_controlHead{color:var(--dsw-alias-label-secondary);justify-content:space-between;align-items:center;gap:10px;font-size:11px;line-height:18px;display:flex}._6TTfZq_controlLabel{align-items:center;gap:4px;min-width:0;display:inline-flex}._6TTfZq_infoButton{appearance:none;width:20px;height:20px;color:var(--dsw-alias-label-tertiary);cursor:help;background:0 0;border:0;border-radius:50%;justify-content:center;align-items:center;padding:0;display:inline-flex}._6TTfZq_infoButton:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._6TTfZq_infoButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}._6TTfZq_numberWrap{flex:none;align-items:center;gap:4px;display:inline-flex}._6TTfZq_number{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);width:68px;height:26px;color:var(--dsw-alias-label-primary);font:inherit;font-variant-numeric:tabular-nums;text-align:right;border-radius:5px;padding:2px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}._6TTfZq_unit{width:30px;color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px}._6TTfZq_range{width:100%;height:18px;accent-color:var(--dsw-alias-state-business-primary);cursor:pointer;margin:0}._6TTfZq_range:disabled,._6TTfZq_number:disabled{opacity:.5;cursor:default}._6TTfZq_footer{border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);flex:none;align-items:center;gap:6px;min-height:50px;padding:8px 10px;display:flex}._6TTfZq_footerSpacer{flex:1}._6TTfZq_secondaryButton,._6TTfZq_primaryButton{appearance:none;border:1px solid var(--dsw-alias-border-l2);min-height:30px;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border-radius:6px;justify-content:center;align-items:center;gap:5px;padding:4px 9px;font-size:11px;line-height:18px;display:inline-flex}._6TTfZq_primaryButton{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:#0000}._6TTfZq_secondaryButton:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}._6TTfZq_secondaryButton:disabled,._6TTfZq_primaryButton:disabled{opacity:.4;cursor:default}._6TTfZq_visuallyHidden{clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}@media (width<=900px){._6TTfZq_trigger,._6TTfZq_iconButton{width:44px;height:44px}._6TTfZq_panel{top:auto;right:12px;bottom:max(12px, env(safe-area-inset-bottom));width:auto;max-height:min(70dvh,640px);left:12px}._6TTfZq_secondaryButton,._6TTfZq_primaryButton{min-height:44px}}@media (prefers-reduced-motion:reduce){._6TTfZq_statusLive{box-shadow:none}}";
|
|
4325
5604
|
const tagId = "dsh-smooth-stream/DebugPanel.module.css";
|
|
4326
5605
|
if (typeof document !== "undefined") {
|
|
4327
5606
|
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]");
|
|
@@ -4334,37 +5613,49 @@ window.__ModuleLoader__.load({
|
|
|
4334
5613
|
tag.textContent = css;
|
|
4335
5614
|
}
|
|
4336
5615
|
var DebugPanel_module_css_default = {
|
|
4337
|
-
"
|
|
4338
|
-
"
|
|
4339
|
-
"
|
|
4340
|
-
"
|
|
4341
|
-
"
|
|
4342
|
-
"
|
|
4343
|
-
"
|
|
4344
|
-
"
|
|
4345
|
-
"
|
|
4346
|
-
"
|
|
4347
|
-
"
|
|
4348
|
-
"
|
|
4349
|
-
"
|
|
4350
|
-
"
|
|
4351
|
-
"
|
|
4352
|
-
"
|
|
4353
|
-
"
|
|
4354
|
-
"
|
|
4355
|
-
"
|
|
4356
|
-
"
|
|
4357
|
-
"
|
|
4358
|
-
"
|
|
4359
|
-
"
|
|
4360
|
-
"
|
|
4361
|
-
"
|
|
4362
|
-
"
|
|
4363
|
-
"
|
|
4364
|
-
"
|
|
5616
|
+
"footerSpacer": "_6TTfZq_footerSpacer",
|
|
5617
|
+
"primaryButton": "_6TTfZq_primaryButton",
|
|
5618
|
+
"statusDot": "_6TTfZq_statusDot",
|
|
5619
|
+
"section": "_6TTfZq_section",
|
|
5620
|
+
"numberWrap": "_6TTfZq_numberWrap",
|
|
5621
|
+
"unsaved": "_6TTfZq_unsaved",
|
|
5622
|
+
"trigger": "_6TTfZq_trigger",
|
|
5623
|
+
"metric": "_6TTfZq_metric",
|
|
5624
|
+
"panel": "_6TTfZq_panel",
|
|
5625
|
+
"range": "_6TTfZq_range",
|
|
5626
|
+
"iconButton": "_6TTfZq_iconButton",
|
|
5627
|
+
"panelHeader": "_6TTfZq_panelHeader",
|
|
5628
|
+
"footer": "_6TTfZq_footer",
|
|
5629
|
+
"controlLabel": "_6TTfZq_controlLabel",
|
|
5630
|
+
"triggerActive": "_6TTfZq_triggerActive",
|
|
5631
|
+
"number": "_6TTfZq_number",
|
|
5632
|
+
"statusLive": "_6TTfZq_statusLive",
|
|
5633
|
+
"controlHead": "_6TTfZq_controlHead",
|
|
5634
|
+
"guide": "_6TTfZq_guide",
|
|
5635
|
+
"unit": "_6TTfZq_unit",
|
|
5636
|
+
"control": "_6TTfZq_control",
|
|
5637
|
+
"state": "_6TTfZq_state",
|
|
5638
|
+
"scrollArea": "_6TTfZq_scrollArea",
|
|
5639
|
+
"visuallyHidden": "_6TTfZq_visuallyHidden",
|
|
5640
|
+
"title": "_6TTfZq_title",
|
|
5641
|
+
"metrics": "_6TTfZq_metrics",
|
|
5642
|
+
"secondaryButton": "_6TTfZq_secondaryButton",
|
|
5643
|
+
"infoButton": "_6TTfZq_infoButton"
|
|
4365
5644
|
};
|
|
4366
5645
|
//#endregion
|
|
4367
5646
|
//#region src/client/DebugPanel.tsx
|
|
5647
|
+
/**
|
|
5648
|
+
* Locale key per terminal-follow phase. `terminal-drain` and `host-cascade`
|
|
5649
|
+
* are the two that must never be confused: the first is the engine finishing
|
|
5650
|
+
* ordinary text, the second is a hostile host commit that needs temporary
|
|
5651
|
+
* credit.
|
|
5652
|
+
*/
|
|
5653
|
+
const PHASE_LABEL = {
|
|
5654
|
+
"live": "debugPhaseLive",
|
|
5655
|
+
"terminal-drain": "debugPhaseDrain",
|
|
5656
|
+
"host-cascade": "debugPhaseCascade",
|
|
5657
|
+
"natural": "debugPhaseNatural"
|
|
5658
|
+
};
|
|
4368
5659
|
const REVEAL_CONTROLS = [
|
|
4369
5660
|
{
|
|
4370
5661
|
key: "revealScale",
|
|
@@ -4491,7 +5782,7 @@ window.__ModuleLoader__.load({
|
|
|
4491
5782
|
type: "button",
|
|
4492
5783
|
"aria-label": label,
|
|
4493
5784
|
title: t(control.tip),
|
|
4494
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
5785
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconQuestion, {})
|
|
4495
5786
|
})
|
|
4496
5787
|
})]
|
|
4497
5788
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -4587,7 +5878,7 @@ window.__ModuleLoader__.load({
|
|
|
4587
5878
|
onClick: () => {
|
|
4588
5879
|
copyDiagnostics();
|
|
4589
5880
|
},
|
|
4590
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
5881
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCopy, {})
|
|
4591
5882
|
}),
|
|
4592
5883
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4593
5884
|
className: DebugPanel_module_css_default.iconButton,
|
|
@@ -4600,7 +5891,7 @@ window.__ModuleLoader__.load({
|
|
|
4600
5891
|
props.edit({ debugEnabled: false });
|
|
4601
5892
|
props.save();
|
|
4602
5893
|
},
|
|
4603
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
5894
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconClose, {})
|
|
4604
5895
|
}),
|
|
4605
5896
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4606
5897
|
className: DebugPanel_module_css_default.visuallyHidden,
|
|
@@ -4668,6 +5959,28 @@ window.__ModuleLoader__.load({
|
|
|
4668
5959
|
label: t("debugCapacity"),
|
|
4669
5960
|
value: `${fixed(metrics.followCapacityPx)} px`
|
|
4670
5961
|
}),
|
|
5962
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
5963
|
+
label: t("debugTerminalPhase"),
|
|
5964
|
+
value: t(PHASE_LABEL[metrics.followTerminalPhase]),
|
|
5965
|
+
tone: metrics.followTerminalPhase === "host-cascade" ? "warn" : void 0
|
|
5966
|
+
}),
|
|
5967
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
5968
|
+
label: t("debugOwnedRunway"),
|
|
5969
|
+
value: `${fixed(metrics.followRunwayPx)} px`
|
|
5970
|
+
}),
|
|
5971
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
5972
|
+
label: t("debugTerminalBudget"),
|
|
5973
|
+
value: `${fixed(metrics.followTerminalBudgetPx)} px`
|
|
5974
|
+
}),
|
|
5975
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
5976
|
+
label: t("debugBaselineShift"),
|
|
5977
|
+
value: `${fixed(metrics.followBaselineShiftPx)} px`
|
|
5978
|
+
}),
|
|
5979
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
5980
|
+
label: t("debugAnchorDelta"),
|
|
5981
|
+
value: metrics.followAnchorDeltaPx === null ? "—" : `${fixed(metrics.followAnchorDeltaPx)} px`,
|
|
5982
|
+
tone: (metrics.followAnchorDeltaPx ?? 0) > .35 ? "warn" : void 0
|
|
5983
|
+
}),
|
|
4671
5984
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
4672
5985
|
label: t("debugAppliedScale"),
|
|
4673
5986
|
value: `${fixed(metrics.followRevealScale, 2)}x`
|
|
@@ -4713,7 +6026,7 @@ window.__ModuleLoader__.load({
|
|
|
4713
6026
|
type: "button",
|
|
4714
6027
|
disabled: !state.writable,
|
|
4715
6028
|
onClick: props.reset,
|
|
4716
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
6029
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconRefreshSmall, {}), t("debugReset")]
|
|
4717
6030
|
}),
|
|
4718
6031
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: DebugPanel_module_css_default.footerSpacer }),
|
|
4719
6032
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -4743,7 +6056,7 @@ window.__ModuleLoader__.load({
|
|
|
4743
6056
|
onClick: () => {
|
|
4744
6057
|
setOpen((current) => !current);
|
|
4745
6058
|
},
|
|
4746
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
6059
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCode, {})
|
|
4747
6060
|
}), typeof document === "undefined" || panel === null ? null : (0, react_dom.createPortal)(panel, document.body)] });
|
|
4748
6061
|
}
|
|
4749
6062
|
//#endregion
|
|
@@ -4751,6 +6064,37 @@ window.__ModuleLoader__.load({
|
|
|
4751
6064
|
/** Locale bundles for the smooth-stream plugin configuration card. */
|
|
4752
6065
|
/** Dictionary namespace owned by this plugin's settings card. */
|
|
4753
6066
|
const NS = "settings.smoothStream";
|
|
6067
|
+
/**
|
|
6068
|
+
* Namespace owning the conversation keys THIS PLUGIN must be able to render
|
|
6069
|
+
* but the Harness does not always provide.
|
|
6070
|
+
*
|
|
6071
|
+
* The plugin consumes keys from two Harness namespaces, and which one owns a
|
|
6072
|
+
* given key is not stable across Harness versions:
|
|
6073
|
+
*
|
|
6074
|
+
* - `conversation` (`dsh-client-ui-conversation`) owns the `image.*` family in
|
|
6075
|
+
* every version, and also owned `message.stopped`, `message.unknownBlock`
|
|
6076
|
+
* and `json.truncated` before those moved to `chat`.
|
|
6077
|
+
* - `chat` (`dsh-client-ui-chat`, present from 0.1.5 on) owns `message.think`
|
|
6078
|
+
* and now owns the three keys above. It does not exist at all in rc.6, the
|
|
6079
|
+
* version this package still pins in devDependencies.
|
|
6080
|
+
*
|
|
6081
|
+
* Neither namespace alone can serve every key this plugin renders, so
|
|
6082
|
+
* `index.ts` layers them. This plugin-owned namespace is the final layer; it
|
|
6083
|
+
* exists because a plugin may NOT register into `chat` or `conversation` —
|
|
6084
|
+
* `LocaleRuntime.register` throws when the (namespace, locale) pair already
|
|
6085
|
+
* has an owner, and the Harness owns both.
|
|
6086
|
+
*/
|
|
6087
|
+
const CHAT_NS = "smoothStream.chat";
|
|
6088
|
+
/** English fallback for {@link SmoothStreamChatKey}. */
|
|
6089
|
+
const chatEn = {
|
|
6090
|
+
"message.think": "Think",
|
|
6091
|
+
"image.serviceUnavailable": "Image service unavailable"
|
|
6092
|
+
};
|
|
6093
|
+
/** Simplified Chinese fallback for {@link SmoothStreamChatKey}. */
|
|
6094
|
+
const chatZh = {
|
|
6095
|
+
"message.think": "思考",
|
|
6096
|
+
"image.serviceUnavailable": "图片服务不可用"
|
|
6097
|
+
};
|
|
4754
6098
|
/** English copy. */
|
|
4755
6099
|
const en = {
|
|
4756
6100
|
title: "Smooth stream",
|
|
@@ -4759,6 +6103,14 @@ window.__ModuleLoader__.load({
|
|
|
4759
6103
|
enabledHint: "Let this plugin render and follow streaming replies. Turn off to use the built-in Harness renderer.",
|
|
4760
6104
|
controlScroll: "Take over scrolling",
|
|
4761
6105
|
controlScrollHint: "On by default; smooth-stream writes the conversation scroll position. Turn off to leave bottom-follow to Harness.",
|
|
6106
|
+
preset: "Pacing preset",
|
|
6107
|
+
presetHint: "Smoothing cadence profile for text reveal and glide.",
|
|
6108
|
+
presetRealtime: "Realtime",
|
|
6109
|
+
presetRealtimeHint: "Aggressive cadence, low latency, tracks output closely.",
|
|
6110
|
+
presetBalanced: "Balanced",
|
|
6111
|
+
presetBalancedHint: "Default balance between pace and visual rhythm.",
|
|
6112
|
+
presetSilky: "Silky",
|
|
6113
|
+
presetSilkyHint: "Higher backlog buffer, gentle EMA damping, maximum fluidity.",
|
|
4762
6114
|
motionPreference: "Motion",
|
|
4763
6115
|
motionPreferenceHint: "How streaming responds to the system reduce-motion setting.",
|
|
4764
6116
|
motionAuto: "Follow system",
|
|
@@ -4768,6 +6120,8 @@ window.__ModuleLoader__.load({
|
|
|
4768
6120
|
motionForceReduced: "Always raw",
|
|
4769
6121
|
motionForceReducedHint: "Render raw text even when the system allows motion.",
|
|
4770
6122
|
thinkAutoExpand: "Auto-expand thinking",
|
|
6123
|
+
logarithmicFade: "Logarithmic fade",
|
|
6124
|
+
logarithmicFadeHint: "Fade new answer and expanded thinking text into view. Follows the motion preference.",
|
|
4771
6125
|
thinkAutoExpandHint: "Open the thinking block while it streams. Turn off to keep it collapsed.",
|
|
4772
6126
|
debugEnabled: "Show render diagnostics",
|
|
4773
6127
|
debugEnabledHint: "Show live streaming and scroll metrics on the right side of the chat. Tune values there, then save them here.",
|
|
@@ -4800,6 +6154,15 @@ window.__ModuleLoader__.load({
|
|
|
4800
6154
|
debugReserve: "Reserve",
|
|
4801
6155
|
debugCapacity: "Capacity",
|
|
4802
6156
|
debugAppliedScale: "Applied scale",
|
|
6157
|
+
debugTerminalPhase: "Terminal phase",
|
|
6158
|
+
debugOwnedRunway: "Owned runway",
|
|
6159
|
+
debugTerminalBudget: "Terminal budget",
|
|
6160
|
+
debugBaselineShift: "Baseline shift",
|
|
6161
|
+
debugAnchorDelta: "Anchor Δ",
|
|
6162
|
+
debugPhaseLive: "live",
|
|
6163
|
+
debugPhaseDrain: "terminal-drain",
|
|
6164
|
+
debugPhaseCascade: "host-cascade",
|
|
6165
|
+
debugPhaseNatural: "natural",
|
|
4803
6166
|
debugRevealMultiplier: "Reveal multiplier",
|
|
4804
6167
|
debugQueuePressure: "Queue pressure",
|
|
4805
6168
|
debugMaxReveal: "Maximum reveal",
|
|
@@ -4846,6 +6209,14 @@ window.__ModuleLoader__.load({
|
|
|
4846
6209
|
enabledHint: "由本插件渲染并跟随流式回复;关闭后使用 Harness 内置渲染。",
|
|
4847
6210
|
controlScroll: "接管滚动",
|
|
4848
6211
|
controlScrollHint: "默认开启,由丝滑流式写入会话滚动位置;关闭后贴底滚动交给 Harness。",
|
|
6212
|
+
preset: "手感预设",
|
|
6213
|
+
presetHint: "文字吐字节奏与视口跟随阻尼风格。",
|
|
6214
|
+
presetRealtime: "激进快速 (realtime)",
|
|
6215
|
+
presetRealtimeHint: "缓冲低、流速快,紧跟模型生成。",
|
|
6216
|
+
presetBalanced: "均衡自然 (balanced)",
|
|
6217
|
+
presetBalancedHint: "官方默认,平衡流速与节奏感。",
|
|
6218
|
+
presetSilky: "极致丝滑 (silky)",
|
|
6219
|
+
presetSilkyHint: "字符积压缓冲加大、阻尼更高、EMA 平滑度更好,视觉呼吸感最强。",
|
|
4849
6220
|
motionPreference: "动效偏好",
|
|
4850
6221
|
motionPreferenceHint: "流式渲染如何响应系统的「减少动态效果」设置。",
|
|
4851
6222
|
motionAuto: "跟随系统",
|
|
@@ -4855,6 +6226,8 @@ window.__ModuleLoader__.load({
|
|
|
4855
6226
|
motionForceReduced: "始终原始",
|
|
4856
6227
|
motionForceReducedHint: "即使系统允许动效,也直接呈现原始文本。",
|
|
4857
6228
|
thinkAutoExpand: "自动展开思考",
|
|
6229
|
+
logarithmicFade: "对数淡入",
|
|
6230
|
+
logarithmicFadeHint: "让回答正文和展开的思考文字由淡变实,遵循动效偏好。",
|
|
4858
6231
|
thinkAutoExpandHint: "思考块在流式时自动展开;关闭后保持折叠,可手动展开。",
|
|
4859
6232
|
debugEnabled: "显示渲染调试面板",
|
|
4860
6233
|
debugEnabledHint: "在聊天右侧显示流式渲染和滚动的实时参数,可在面板中调节并在这里保存。",
|
|
@@ -4887,6 +6260,15 @@ window.__ModuleLoader__.load({
|
|
|
4887
6260
|
debugReserve: "预留空间",
|
|
4888
6261
|
debugCapacity: "安全容量",
|
|
4889
6262
|
debugAppliedScale: "实际倍率",
|
|
6263
|
+
debugTerminalPhase: "收尾阶段",
|
|
6264
|
+
debugOwnedRunway: "实体留白",
|
|
6265
|
+
debugTerminalBudget: "收尾预算",
|
|
6266
|
+
debugBaselineShift: "基线位移",
|
|
6267
|
+
debugAnchorDelta: "锚点位移",
|
|
6268
|
+
debugPhaseLive: "流式中",
|
|
6269
|
+
debugPhaseDrain: "收尾排空",
|
|
6270
|
+
debugPhaseCascade: "宿主交接",
|
|
6271
|
+
debugPhaseNatural: "自然态",
|
|
4890
6272
|
debugRevealMultiplier: "揭示倍率",
|
|
4891
6273
|
debugQueuePressure: "队列压力",
|
|
4892
6274
|
debugMaxReveal: "最大揭示速度",
|
|
@@ -4947,12 +6329,33 @@ window.__ModuleLoader__.load({
|
|
|
4947
6329
|
* generic follow boundary. This is deliberately keyed by the owner that
|
|
4948
6330
|
* provides the renderer, not by individual tool names, so new Context,
|
|
4949
6331
|
* Command, and Tool rows are covered automatically.
|
|
6332
|
+
*
|
|
6333
|
+
* Host chrome and controllers are excluded on a different rule: `turn-process`
|
|
6334
|
+
* (the completed-turn process summary control) and `turn-tail` (the turn usage
|
|
6335
|
+
* footer) are not Agent output. The Host renders them as bare controls and
|
|
6336
|
+
* flips their visibility itself, so wrapping them has three measured costs and
|
|
6337
|
+
* no benefit:
|
|
6338
|
+
*
|
|
6339
|
+
* 1. The wrapper element defeats ChatView's `.flowItem:empty { display: none }`
|
|
6340
|
+
* rule. A `turn-process` renderer returns null whenever the Host's fold is
|
|
6341
|
+
* unavailable (e.g. `processWindowReady === false`), and that null render is
|
|
6342
|
+
* exactly what the `:empty` rule exists to erase; with a wrapper the row
|
|
6343
|
+
* stays in flow and still consumes the 16px column gap, painting an empty
|
|
6344
|
+
* band where the fold control should be.
|
|
6345
|
+
* 2. The summary label (`N tool calls · M messages`) is not streamed model
|
|
6346
|
+
* text, so pushing it through the character-reveal engine is wrong by
|
|
6347
|
+
* construction.
|
|
6348
|
+
* 3. The row would inherit the generic entrance animation (opacity + clip-path)
|
|
6349
|
+
* on a row the Host may mount and hide inside a single commit.
|
|
4950
6350
|
*/
|
|
4951
6351
|
const SKIP_WRAP = /* @__PURE__ */ new Set([
|
|
4952
6352
|
"assistant-step",
|
|
4953
6353
|
"user",
|
|
4954
6354
|
"steering",
|
|
4955
|
-
"command-input"
|
|
6355
|
+
"command-input",
|
|
6356
|
+
"turn-process",
|
|
6357
|
+
"turn-tail",
|
|
6358
|
+
"tool-call"
|
|
4956
6359
|
]);
|
|
4957
6360
|
/** React function/class or an exotic component such as memo/forwardRef/lazy. */
|
|
4958
6361
|
function isWrappableComponent(value) {
|
|
@@ -5048,7 +6451,7 @@ window.__ModuleLoader__.load({
|
|
|
5048
6451
|
refresh() {
|
|
5049
6452
|
const next = this.read();
|
|
5050
6453
|
const pending = this.card?.getSnapshot().status === "loading";
|
|
5051
|
-
if (pending === this.pending && next.enabled === this.value.enabled && next.controlScroll === this.value.controlScroll && next.motionPreference === this.value.motionPreference && next.thinkAutoExpand === this.value.thinkAutoExpand && next.debugEnabled === this.value.debugEnabled && next.debugTuning === this.value.debugTuning) return;
|
|
6454
|
+
if (pending === this.pending && next.enabled === this.value.enabled && next.controlScroll === this.value.controlScroll && next.preset === this.value.preset && next.motionPreference === this.value.motionPreference && next.thinkAutoExpand === this.value.thinkAutoExpand && next.logarithmicFade === this.value.logarithmicFade && next.debugEnabled === this.value.debugEnabled && next.debugTuning === this.value.debugTuning) return;
|
|
5052
6455
|
this.pending = pending;
|
|
5053
6456
|
this.value = next;
|
|
5054
6457
|
for (const listener of this.listeners) listener();
|
|
@@ -5057,6 +6460,15 @@ window.__ModuleLoader__.load({
|
|
|
5057
6460
|
takeoverEnabled() {
|
|
5058
6461
|
return !this.pending && this.value.enabled;
|
|
5059
6462
|
}
|
|
6463
|
+
/**
|
|
6464
|
+
* True only once an attached settings service has produced its loaded values.
|
|
6465
|
+
* Until then — and when no settings service is composed at all — `value` is
|
|
6466
|
+
* still the shared client default, so callers that must not let that default
|
|
6467
|
+
* shadow the install-time config have to ask before trusting it.
|
|
6468
|
+
*/
|
|
6469
|
+
ready() {
|
|
6470
|
+
return this.card?.getSnapshot().status === "ready";
|
|
6471
|
+
}
|
|
5060
6472
|
getSnapshot = () => this.value;
|
|
5061
6473
|
subscribe = (listener) => {
|
|
5062
6474
|
this.listeners.add(listener);
|
|
@@ -5080,6 +6492,49 @@ window.__ModuleLoader__.load({
|
|
|
5080
6492
|
const config = readBootConfig();
|
|
5081
6493
|
const settings = new SettingsCell();
|
|
5082
6494
|
const useControlScroll = () => (0, react.useSyncExternalStore)(settings.subscribe, () => settings.getSnapshot().controlScroll, () => settings.getSnapshot().controlScroll);
|
|
6495
|
+
/**
|
|
6496
|
+
* Layered `t` for the assistant renderer.
|
|
6497
|
+
*
|
|
6498
|
+
* The renderer's keys have no single owner: `conversation` owns the
|
|
6499
|
+
* `image.*` family in every Harness version, `chat` (0.1.5+) owns
|
|
6500
|
+
* `message.think`, and three `message.*` keys MOVED from `conversation` to
|
|
6501
|
+
* `chat` between the version this package pins and the current one. Binding
|
|
6502
|
+
* the slot to either namespace alone degrades a real slice of the UI to raw
|
|
6503
|
+
* keys, which is the defect this replaces.
|
|
6504
|
+
*
|
|
6505
|
+
* The order is deliberate: `conversation` first, so the pinned Harness keeps
|
|
6506
|
+
* resolving the keys it still owns there; then `chat` for what moved or is
|
|
6507
|
+
* new; then this plugin's own namespace for keys no Harness version
|
|
6508
|
+
* provides. A namespace that is not registered returns its key unchanged
|
|
6509
|
+
* (`LocaleRuntime.bind` does not throw), so an older Harness without `chat`
|
|
6510
|
+
* falls straight through.
|
|
6511
|
+
*
|
|
6512
|
+
* The reference is built once and held stable: the seat feeds a memoized
|
|
6513
|
+
* renderer, and a fresh identity per render would defeat that memoization.
|
|
6514
|
+
* Until the locale service arrives the seat's own binding stays in use.
|
|
6515
|
+
*/
|
|
6516
|
+
let assistantT;
|
|
6517
|
+
ctx.inject(["locale"], (localeCtx) => {
|
|
6518
|
+
localeCtx.effect(() => localeCtx.locale.register(CHAT_NS, {
|
|
6519
|
+
zh: chatZh,
|
|
6520
|
+
en: chatEn
|
|
6521
|
+
}), "dsh-smooth-stream: conversation fallback dictionary");
|
|
6522
|
+
const conversationT = localeCtx.locale.bind("conversation");
|
|
6523
|
+
const chatT = localeCtx.locale.bind("chat");
|
|
6524
|
+
const fallbackT = localeCtx.locale.bind(CHAT_NS);
|
|
6525
|
+
const merged = (key, params) => {
|
|
6526
|
+
const primary = conversationT(key, params);
|
|
6527
|
+
if (primary !== key) return primary;
|
|
6528
|
+
const secondary = chatT(key, params);
|
|
6529
|
+
if (secondary !== key) return secondary;
|
|
6530
|
+
return fallbackT(key, params);
|
|
6531
|
+
};
|
|
6532
|
+
const bound = merged;
|
|
6533
|
+
assistantT = bound;
|
|
6534
|
+
return () => {
|
|
6535
|
+
if (assistantT === bound) assistantT = void 0;
|
|
6536
|
+
};
|
|
6537
|
+
});
|
|
5083
6538
|
ctx.inject([
|
|
5084
6539
|
"slots",
|
|
5085
6540
|
"locale",
|
|
@@ -5142,18 +6597,21 @@ window.__ModuleLoader__.load({
|
|
|
5142
6597
|
const preferences = (0, react.useSyncExternalStore)(settings.subscribe, settings.getSnapshot, settings.getSnapshot);
|
|
5143
6598
|
return (0, react.createElement)(TypewriterAssistantNodeView, {
|
|
5144
6599
|
...props,
|
|
6600
|
+
...assistantT === void 0 ? {} : { t: assistantT },
|
|
5145
6601
|
mode: config.mode,
|
|
5146
|
-
preset: config.preset,
|
|
6602
|
+
preset: settings.ready() ? preferences.preset ?? config.preset : config.preset,
|
|
5147
6603
|
revealCharsPerSec: config.revealCharsPerSec,
|
|
5148
6604
|
scrollSpeedPxPerSec: config.scrollSpeedPxPerSec,
|
|
5149
6605
|
maxScrollSpeedPxPerSec: config.maxScrollSpeedPxPerSec,
|
|
5150
6606
|
thinkAutoExpand: preferences.thinkAutoExpand,
|
|
6607
|
+
logarithmicFade: preferences.logarithmicFade,
|
|
5151
6608
|
controlScroll: preferences.controlScroll,
|
|
5152
6609
|
motionPreference: preferences.motionPreference
|
|
5153
6610
|
});
|
|
5154
6611
|
};
|
|
5155
6612
|
ctx.slots.inject("conversation.chat.node", () => {
|
|
5156
6613
|
let releaseTakeover;
|
|
6614
|
+
let usedSwap = false;
|
|
5157
6615
|
const syncTakeover = () => {
|
|
5158
6616
|
if (!settings.takeoverEnabled()) {
|
|
5159
6617
|
releaseTakeover?.();
|
|
@@ -5162,7 +6620,16 @@ window.__ModuleLoader__.load({
|
|
|
5162
6620
|
}
|
|
5163
6621
|
if (releaseTakeover !== void 0) return;
|
|
5164
6622
|
const unwrap = wrapAgentChatRows(ctx, useControlScroll);
|
|
5165
|
-
const
|
|
6623
|
+
const builtin = ctx.slots.entries("conversation.chat.node").find((entry) => entry.options.key === "assistant-step" && entry.options.registrant !== "dsh-smooth-stream" && entry.component !== configured);
|
|
6624
|
+
let unshadow;
|
|
6625
|
+
if (builtin !== void 0 && isWrappableComponent(builtin.component)) {
|
|
6626
|
+
usedSwap = true;
|
|
6627
|
+
const inner = builtin.component;
|
|
6628
|
+
builtin.component = configured;
|
|
6629
|
+
unshadow = () => {
|
|
6630
|
+
if (builtin.component === configured) builtin.component = inner;
|
|
6631
|
+
};
|
|
6632
|
+
} else unshadow = ctx.slots.register({
|
|
5166
6633
|
name: "conversation.chat.node",
|
|
5167
6634
|
key: "assistant-step",
|
|
5168
6635
|
priority: -100,
|
|
@@ -5174,10 +6641,18 @@ window.__ModuleLoader__.load({
|
|
|
5174
6641
|
unshadow();
|
|
5175
6642
|
};
|
|
5176
6643
|
};
|
|
6644
|
+
const offSlotChange = ctx.on("slots/changed", (key) => {
|
|
6645
|
+
if (key !== "conversation.chat.node" || usedSwap || releaseTakeover === void 0) return;
|
|
6646
|
+
if (!settings.takeoverEnabled()) return;
|
|
6647
|
+
releaseTakeover();
|
|
6648
|
+
releaseTakeover = void 0;
|
|
6649
|
+
syncTakeover();
|
|
6650
|
+
});
|
|
5177
6651
|
const unsubscribe = settings.subscribe(syncTakeover);
|
|
5178
6652
|
syncTakeover();
|
|
5179
6653
|
return () => {
|
|
5180
6654
|
unsubscribe();
|
|
6655
|
+
offSlotChange();
|
|
5181
6656
|
releaseTakeover?.();
|
|
5182
6657
|
};
|
|
5183
6658
|
});
|