chiral-pulse 0.1.0 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/lib/client.js +220 -64
- package/lib/client.js.map +1 -1
- package/package.json +68 -68
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
`dsh-plugin` · `deepseek-harness` · `ui-plugin` · `death-stranding` · `bb-pod` · `ecg` · `theme`
|
|
11
11
|
|
|
12
|
+

|
|
13
|
+
|
|
12
14
|
---
|
|
13
15
|
|
|
14
16
|
## 这是什么
|
|
@@ -69,7 +71,7 @@ bundle 层,无需手写配置。
|
|
|
69
71
|
```
|
|
70
72
|
|
|
71
73
|
```powershell
|
|
72
|
-
New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\web\node_modules
|
|
74
|
+
New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\web\node_modules\chiral-pulse" -Target "D:\path\to\chiral-pulse"
|
|
73
75
|
```
|
|
74
76
|
|
|
75
77
|
用户 patch 层是热加载的:无需重启 `dsh web`。之后**刷新浏览器页面**即可
|
package/lib/client.js
CHANGED
|
@@ -28,7 +28,7 @@ window.__ModuleLoader__.load({
|
|
|
28
28
|
* @returns the waveform amplitude at that phase.
|
|
29
29
|
*/
|
|
30
30
|
function ecgValue(phase) {
|
|
31
|
-
return bump(phase, .14, .03, .16) - bump(phase, .3, .011, .26) + bump(phase, .335, .
|
|
31
|
+
return bump(phase, .14, .03, .16) - bump(phase, .3, .011, .26) + bump(phase, .335, .016, 1) - bump(phase, .375, .011, .34) + bump(phase, .52, .048, .26) + bump(phase, .8, .012, .05);
|
|
32
32
|
}
|
|
33
33
|
//#endregion
|
|
34
34
|
//#region src/client/HeartLine.tsx
|
|
@@ -53,8 +53,14 @@ window.__ModuleLoader__.load({
|
|
|
53
53
|
*/
|
|
54
54
|
/** Monitor view height, CSS px. */
|
|
55
55
|
const ECG_HEIGHT = 22;
|
|
56
|
-
/**
|
|
57
|
-
|
|
56
|
+
/**
|
|
57
|
+
* FIXED paper speed in px/second — the real hospital-monitor invariant.
|
|
58
|
+
* The trace scrolls at this absolute rate no matter the strip width; the
|
|
59
|
+
* width only decides how much history fits on screen. A rate change (42→90)
|
|
60
|
+
* therefore only densifies the beats — it never speeds the paper up, and
|
|
61
|
+
* resizing the window cannot make the trace run faster either.
|
|
62
|
+
*/
|
|
63
|
+
const PAPER_SPEED_PX_PER_SECOND = 30;
|
|
58
64
|
/** Activity window for the step-rate base, ms. */
|
|
59
65
|
const ACTIVITY_WINDOW_MS = 1e4;
|
|
60
66
|
/** Rotating status lines (locale keys), one every STATUS_ROTATE_S ticks. */
|
|
@@ -77,12 +83,20 @@ window.__ModuleLoader__.load({
|
|
|
77
83
|
/** BPM floor (a resting BB) and ceiling. */
|
|
78
84
|
const BPM_FLOOR = 42;
|
|
79
85
|
const BPM_CEIL = 150;
|
|
86
|
+
/**
|
|
87
|
+
* How fast the displayed heart rate ramps toward its target, in BPM/second.
|
|
88
|
+
* A hospital monitor updates its HR figure on a ~2-3s rolling average and
|
|
89
|
+
* the trace follows gradually — the rate change reads as a slow ramp, not a
|
|
90
|
+
* snap: 42 → 90 takes (90-42)/6 = 8 seconds of visible densification.
|
|
91
|
+
*/
|
|
92
|
+
const BPM_RAMP_PER_SECOND = 6;
|
|
80
93
|
/** Trace color by activity mode: idle amber, thinking cyan, tool orange, run warm. */
|
|
81
94
|
const MODE_COLOR = {
|
|
82
95
|
idle: "#ffb454",
|
|
83
96
|
think: "#6fdbe2",
|
|
84
97
|
tool: "#ff7a4d",
|
|
85
|
-
run: "#ffc46b"
|
|
98
|
+
run: "#ffc46b",
|
|
99
|
+
flat: "#c0483c"
|
|
86
100
|
};
|
|
87
101
|
/** Tail of the model's in-flight output: last non-empty text/reasoning block, whitespace-flattened. */
|
|
88
102
|
function streamingTail(blocks) {
|
|
@@ -104,7 +118,8 @@ window.__ModuleLoader__.load({
|
|
|
104
118
|
partialText: s.partial === null ? "" : streamingTail(s.partial.blocks),
|
|
105
119
|
toolName: s.runningCalls[0]?.name ?? null,
|
|
106
120
|
running: s.running,
|
|
107
|
-
error: s.lastAgentError
|
|
121
|
+
error: s.lastAgentError,
|
|
122
|
+
retrying: s.chat.legacy.nodes.some((n) => n.kind === "model-retry" && n.retryState === "scheduled")
|
|
108
123
|
}));
|
|
109
124
|
const steps = stats?.steps ?? 0;
|
|
110
125
|
const bpmRef = (0, react.useRef)(BPM_FLOOR);
|
|
@@ -139,9 +154,8 @@ window.__ModuleLoader__.load({
|
|
|
139
154
|
const perMinute = span > 0 ? delta / span * 6e4 : 0;
|
|
140
155
|
const base = Math.min(BPM_CEIL, Math.max(BPM_FLOOR, 42 + perMinute * 6));
|
|
141
156
|
const act = liveRef.current;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const mode = act.toolName !== null ? "tool" : act.partial ? "think" : act.running ? "run" : "idle";
|
|
157
|
+
targetRef.current = act.retrying ? 0 : Math.min(BPM_CEIL, Math.max(BPM_FLOOR, base + (act.toolName !== null ? BOOST_TOOL : 0) + (act.partial ? BOOST_THINKING : 0) + (act.running ? BOOST_RUNNING : 0)));
|
|
158
|
+
const mode = act.retrying ? "flat" : act.toolName !== null ? "tool" : act.partial ? "think" : act.running ? "run" : "idle";
|
|
145
159
|
modeRef.current = mode;
|
|
146
160
|
setUi((current) => ({
|
|
147
161
|
bpm: Math.round(bpmRef.current),
|
|
@@ -163,6 +177,7 @@ window.__ModuleLoader__.load({
|
|
|
163
177
|
const ctx = canvas.getContext("2d");
|
|
164
178
|
if (ctx === null) return;
|
|
165
179
|
ctxRef.current = ctx;
|
|
180
|
+
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
166
181
|
const applySize = () => {
|
|
167
182
|
const dpr = window.devicePixelRatio || 1;
|
|
168
183
|
dprRef.current = dpr;
|
|
@@ -175,44 +190,88 @@ window.__ModuleLoader__.load({
|
|
|
175
190
|
if (width !== widthRef.current) {
|
|
176
191
|
widthRef.current = width;
|
|
177
192
|
applySize();
|
|
193
|
+
if (reduced) paint(performance.now());
|
|
178
194
|
}
|
|
179
195
|
});
|
|
180
196
|
observer.observe(canvas);
|
|
181
|
-
let
|
|
197
|
+
let displayNow = 0;
|
|
198
|
+
let lastPaintReal = 0;
|
|
199
|
+
let framePeriodMs = 16.7;
|
|
200
|
+
let traceCache = [];
|
|
201
|
+
let lastScanX = -1;
|
|
182
202
|
const paint = (now) => {
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
203
|
+
if (lastPaintReal === 0) {
|
|
204
|
+
lastPaintReal = now;
|
|
205
|
+
displayNow = now;
|
|
206
|
+
}
|
|
207
|
+
const realDt = Math.max(0, (now - lastPaintReal) / 1e3);
|
|
208
|
+
lastPaintReal = now;
|
|
209
|
+
if (realDt > 0 && realDt < .05) framePeriodMs = realDt * 1e3;
|
|
210
|
+
const dt = framePeriodMs / 1e3;
|
|
211
|
+
displayNow += dt * 1e3;
|
|
212
|
+
const diff = targetRef.current - bpmRef.current;
|
|
213
|
+
const step = BPM_RAMP_PER_SECOND * dt;
|
|
214
|
+
if (diff > step) bpmRef.current += step;
|
|
215
|
+
else if (diff < -step) bpmRef.current -= step;
|
|
216
|
+
else bpmRef.current = targetRef.current;
|
|
187
217
|
const c = canvasRef.current;
|
|
188
218
|
const g = ctxRef.current;
|
|
189
219
|
if (c === null || g === null) return;
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
const
|
|
220
|
+
const dpr = window.devicePixelRatio || 1;
|
|
221
|
+
const rect = c.getBoundingClientRect();
|
|
222
|
+
const w = Math.max(120, Math.round(rect.width));
|
|
223
|
+
const h = Math.max(1, Math.round(rect.height));
|
|
224
|
+
const wantW = Math.round(w * dpr);
|
|
225
|
+
const wantH = Math.round(h * dpr);
|
|
226
|
+
if (c.width !== wantW || c.height !== wantH) {
|
|
227
|
+
c.width = wantW;
|
|
228
|
+
c.height = wantH;
|
|
229
|
+
}
|
|
230
|
+
widthRef.current = w;
|
|
231
|
+
dprRef.current = dpr;
|
|
193
232
|
g.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
194
233
|
g.clearRect(0, 0, w, h);
|
|
195
|
-
const tNow =
|
|
196
|
-
const secondsPerPixel =
|
|
234
|
+
const tNow = displayNow / 1e3;
|
|
235
|
+
const secondsPerPixel = 1 / PAPER_SPEED_PX_PER_SECOND;
|
|
197
236
|
const mid = h / 2;
|
|
198
237
|
const amp = h * .44;
|
|
199
238
|
const wander = .05 * Math.sin(tNow * .6) + .035 * Math.sin(tNow * 1.7 + 1.3);
|
|
200
239
|
const bpm = bpmRef.current;
|
|
201
|
-
const
|
|
202
|
-
|
|
240
|
+
const flatline = bpm < 1;
|
|
241
|
+
const sweepPeriod = w / PAPER_SPEED_PX_PER_SECOND;
|
|
242
|
+
const scanX = w - (tNow % sweepPeriod + sweepPeriod) % sweepPeriod * PAPER_SPEED_PX_PER_SECOND;
|
|
243
|
+
const scanXInt = Math.round(scanX);
|
|
244
|
+
const yNow = (x) => {
|
|
245
|
+
if (flatline) return mid;
|
|
246
|
+
let v = -Infinity;
|
|
247
|
+
for (let i = 0; i < 4; i += 1) {
|
|
248
|
+
const s = ecgValue(((tNow - (w - (x + i * .25)) * secondsPerPixel) * (bpm / 60) % 1 + 1) % 1);
|
|
249
|
+
if (s > v) v = s;
|
|
250
|
+
}
|
|
251
|
+
return mid - (v + wander) * amp;
|
|
203
252
|
};
|
|
253
|
+
if (traceCache.length !== w + 1) {
|
|
254
|
+
traceCache = new Array(w + 1);
|
|
255
|
+
for (let x = 0; x <= w; x += 1) traceCache[x] = yNow(x);
|
|
256
|
+
lastScanX = scanXInt;
|
|
257
|
+
} else if (lastScanX > scanXInt) {
|
|
258
|
+
for (let x = scanXInt; x <= lastScanX && x <= w; x += 1) traceCache[x] = yNow(x);
|
|
259
|
+
lastScanX = scanXInt;
|
|
260
|
+
} else lastScanX = scanXInt;
|
|
204
261
|
g.beginPath();
|
|
205
262
|
for (let x = 0; x <= w; x += 1) {
|
|
206
|
-
const y =
|
|
263
|
+
const y = traceCache[x];
|
|
207
264
|
if (x === 0) g.moveTo(x + 3, y);
|
|
208
265
|
else g.lineTo(x + 3, y);
|
|
209
266
|
}
|
|
210
|
-
g.
|
|
267
|
+
g.globalAlpha = .1;
|
|
268
|
+
g.strokeStyle = "rgba(111, 219, 226, 1)";
|
|
211
269
|
g.lineWidth = 1;
|
|
212
270
|
g.stroke();
|
|
271
|
+
g.globalAlpha = 1;
|
|
213
272
|
g.beginPath();
|
|
214
273
|
for (let x = 0; x <= w; x += 1) {
|
|
215
|
-
const y =
|
|
274
|
+
const y = traceCache[x];
|
|
216
275
|
if (x === 0) g.moveTo(x, y);
|
|
217
276
|
else g.lineTo(x, y);
|
|
218
277
|
}
|
|
@@ -221,18 +280,12 @@ window.__ModuleLoader__.load({
|
|
|
221
280
|
g.lineJoin = "round";
|
|
222
281
|
g.lineCap = "round";
|
|
223
282
|
g.stroke();
|
|
224
|
-
|
|
225
|
-
g.
|
|
226
|
-
g.
|
|
227
|
-
g.
|
|
228
|
-
g.arc(w - 2, headY, 5, 0, Math.PI * 2);
|
|
229
|
-
g.fill();
|
|
230
|
-
g.globalAlpha = 1;
|
|
231
|
-
g.beginPath();
|
|
232
|
-
g.arc(w - 2, headY, 1.6, 0, Math.PI * 2);
|
|
233
|
-
g.fill();
|
|
283
|
+
g.fillStyle = "rgba(255, 180, 84, 0.16)";
|
|
284
|
+
g.fillRect(scanX - 5, 0, 10, h);
|
|
285
|
+
g.fillStyle = "rgba(255, 224, 190, 0.95)";
|
|
286
|
+
g.fillRect(scanX - 1, 0, 2, h);
|
|
234
287
|
};
|
|
235
|
-
if (
|
|
288
|
+
if (reduced) {
|
|
236
289
|
paint(performance.now());
|
|
237
290
|
return () => {
|
|
238
291
|
observer.disconnect();
|
|
@@ -250,25 +303,26 @@ window.__ModuleLoader__.load({
|
|
|
250
303
|
};
|
|
251
304
|
}, []);
|
|
252
305
|
const flavor = STATUS_KEYS[Math.floor(ui.elapsed / STATUS_ROTATE_S) % STATUS_KEYS.length];
|
|
253
|
-
const status = live.error !== null ? `⚠ ${live.error.slice(0, 16)}` : live.toolName !== null ? `EXEC · ${live.toolName}` : live.partialText !== "" ? `⇢ ${live.partialText.slice(-18)}` : t(flavor);
|
|
306
|
+
const status = live.error !== null ? `⚠ ${live.error.slice(0, 16)}` : live.retrying ? t("status.flatline") : live.toolName !== null ? `EXEC · ${live.toolName}` : live.partialText !== "" ? `⇢ ${live.partialText.slice(-18)}` : t(flavor);
|
|
254
307
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
255
308
|
className: "cp-line",
|
|
256
309
|
role: "group",
|
|
257
310
|
"aria-label": t("line.aria"),
|
|
258
311
|
"data-chiral-pulse": true,
|
|
259
312
|
"data-mode": ui.mode,
|
|
313
|
+
"data-rev": "18",
|
|
260
314
|
children: [
|
|
261
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
315
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
262
316
|
className: "cp-lineBpm",
|
|
263
|
-
children:
|
|
264
|
-
className: "cp-lineBpmUnit",
|
|
265
|
-
children: t("bpm.unit")
|
|
266
|
-
})]
|
|
317
|
+
children: ui.bpm
|
|
267
318
|
}),
|
|
268
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
319
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
320
|
+
className: "cp-lineEcgWrap",
|
|
321
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("canvas", {
|
|
322
|
+
ref: canvasRef,
|
|
323
|
+
className: "cp-lineEcg",
|
|
324
|
+
"aria-hidden": true
|
|
325
|
+
})
|
|
272
326
|
}),
|
|
273
327
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
274
328
|
className: "cp-lineReadout",
|
|
@@ -302,7 +356,8 @@ window.__ModuleLoader__.load({
|
|
|
302
356
|
"status.doom": "DOOMS LEVEL: 0",
|
|
303
357
|
"status.keep": "KEEP ON KEEPING ON",
|
|
304
358
|
"status.voidout": "NO VOIDOUT DETECTED",
|
|
305
|
-
"status.odradek": "ODRADEK SYNC: OK"
|
|
359
|
+
"status.odradek": "ODRADEK SYNC: OK",
|
|
360
|
+
"status.flatline": "♥ FLATLINE"
|
|
306
361
|
};
|
|
307
362
|
/** Chinese dictionary. */
|
|
308
363
|
const zh = {
|
|
@@ -314,7 +369,8 @@ window.__ModuleLoader__.load({
|
|
|
314
369
|
"status.doom": "DOOMS 等级:0",
|
|
315
370
|
"status.keep": "继续前进 · KEEP ON KEEPING ON",
|
|
316
371
|
"status.voidout": "未检测到虚爆",
|
|
317
|
-
"status.odradek": "奥卓克同步:正常"
|
|
372
|
+
"status.odradek": "奥卓克同步:正常",
|
|
373
|
+
"status.flatline": "♥ 心脏停跳"
|
|
318
374
|
};
|
|
319
375
|
//#endregion
|
|
320
376
|
//#region src/client/style.ts
|
|
@@ -358,6 +414,12 @@ body:not([data-ds-dark-theme]) {
|
|
|
358
414
|
--dsw-alias-bg-mask-3: rgba(0, 0, 0, 0.5);
|
|
359
415
|
--dsw-alias-bg-mask-drop: rgba(10, 14, 19, 0.72);
|
|
360
416
|
--dsw-alias-bg-skeleton: rgba(150, 170, 200, 0.07);
|
|
417
|
+
/* Setting rows / select chips (language, agent preset, model, permissions…)
|
|
418
|
+
paint from these; without an override they stay the LIGHT theme's
|
|
419
|
+
near-white and produce white-on-white text. */
|
|
420
|
+
--dsw-alias-bg-module-platform: rgb(15, 20, 27);
|
|
421
|
+
--dsw-alias-bg-multi-select: rgb(18, 24, 32);
|
|
422
|
+
--dsw-alias-fill-tsp-secondary: rgb(18, 24, 32);
|
|
361
423
|
|
|
362
424
|
/* text: cold blue-grey */
|
|
363
425
|
--dsw-alias-label-primary: rgb(235, 240, 246);
|
|
@@ -366,10 +428,12 @@ body:not([data-ds-dark-theme]) {
|
|
|
366
428
|
--dsw-alias-label-caption: rgb(108, 122, 141);
|
|
367
429
|
--dsw-alias-label-dimmed: rgb(94, 108, 127);
|
|
368
430
|
--dsw-alias-label-primary-dimmed: rgb(205, 213, 222);
|
|
369
|
-
--dsw-alias-label-primary-inverted: rgb(
|
|
431
|
+
--dsw-alias-label-primary-inverted: rgb(20, 26, 35);
|
|
370
432
|
--dsw-alias-label-primary-foreground: rgb(13, 17, 23);
|
|
433
|
+
--dsw-alias-label-primary-bluish: rgb(200, 214, 232);
|
|
371
434
|
--dsw-alias-brand-text: rgb(235, 240, 246);
|
|
372
435
|
--dsw-alias-brand-primary: rgb(103, 158, 254);
|
|
436
|
+
--dsw-alias-brand-primary-invert: rgb(235, 240, 246);
|
|
373
437
|
|
|
374
438
|
/* hairlines: cold blue, readable against the body */
|
|
375
439
|
--dsw-alias-border-l1: rgba(140, 170, 215, 0.15);
|
|
@@ -396,6 +460,9 @@ body:not([data-ds-dark-theme]) {
|
|
|
396
460
|
--dsw-alias-button-floating-fill: rgb(19, 25, 33);
|
|
397
461
|
--dsw-alias-button-floating-hover: rgb(24, 31, 41);
|
|
398
462
|
--dsw-alias-button-elevated-fill: rgb(22, 29, 38);
|
|
463
|
+
/* Contrast fill (attachment rail): pale case + DARK inverted ink — the
|
|
464
|
+
wordmark badge and toasts also ride label-primary-inverted, so the pair
|
|
465
|
+
(pale fill, dark ink) stays readable everywhere. */
|
|
399
466
|
--dsw-alias-button-contrast-fill: rgb(205, 213, 222);
|
|
400
467
|
--dsw-alias-button-tool-bar-fill: rgba(140, 170, 215, 0.24);
|
|
401
468
|
--dsw-alias-button-tool-bar-fill-invisible: rgba(140, 170, 215, 0.13);
|
|
@@ -438,6 +505,9 @@ body:not([data-ds-dark-theme]) {
|
|
|
438
505
|
--dsw-alias-state-success-primary: rgb(52, 205, 168);
|
|
439
506
|
--dsw-alias-state-success-secondary: rgb(94, 222, 189);
|
|
440
507
|
--dsw-alias-state-success-tertiary: rgb(12, 28, 24);
|
|
508
|
+
/* Business tint (hero "preview" badge et al.): dark case so the pale
|
|
509
|
+
primary-bluish ink stays readable — the light-theme default is near-white. */
|
|
510
|
+
--dsw-alias-state-business-tertiary: rgb(26, 34, 46);
|
|
441
511
|
--dsw-static-green-400: rgb(94, 222, 189);
|
|
442
512
|
--dsw-static-green-500: rgb(52, 205, 168);
|
|
443
513
|
}
|
|
@@ -491,10 +561,14 @@ body {
|
|
|
491
561
|
--cp-amber-bright: #ffd9a0;
|
|
492
562
|
--cp-cyan: #6fdbe2;
|
|
493
563
|
--cp-dim: #64727f;
|
|
564
|
+
/* flex: none — the hero (blank-session) composer column squeezes its
|
|
565
|
+
children on short viewports; the monitor strip must never shrink. */
|
|
566
|
+
flex: none;
|
|
494
567
|
display: flex;
|
|
495
568
|
align-items: center;
|
|
496
569
|
gap: 12px;
|
|
497
570
|
height: 26px;
|
|
571
|
+
min-height: 26px;
|
|
498
572
|
margin: 3px 0 4px;
|
|
499
573
|
padding: 0 10px;
|
|
500
574
|
border: 1px solid rgba(140, 170, 215, 0.26);
|
|
@@ -519,9 +593,13 @@ body {
|
|
|
519
593
|
}
|
|
520
594
|
|
|
521
595
|
.cp-lineBpm {
|
|
596
|
+
/* Fixed width: a 3-digit readout (42 → 150) must not widen the block and
|
|
597
|
+
squeeze the paper area — that would shrink the trace window and pull the
|
|
598
|
+
left edge rightward as the rate climbs. */
|
|
522
599
|
flex: none;
|
|
523
|
-
|
|
524
|
-
|
|
600
|
+
width: 48px;
|
|
601
|
+
text-align: center;
|
|
602
|
+
font-size: 16px;
|
|
525
603
|
line-height: 1;
|
|
526
604
|
letter-spacing: 0.5px;
|
|
527
605
|
color: var(--cp-amber-bright);
|
|
@@ -529,18 +607,15 @@ body {
|
|
|
529
607
|
font-variant-numeric: tabular-nums;
|
|
530
608
|
white-space: nowrap;
|
|
531
609
|
}
|
|
532
|
-
.cp-lineBpmUnit {
|
|
533
|
-
font-size: 7px;
|
|
534
|
-
letter-spacing: 1px;
|
|
535
|
-
color: var(--cp-dim);
|
|
536
|
-
margin-left: 2px;
|
|
537
|
-
}
|
|
538
610
|
|
|
539
|
-
.cp-
|
|
540
|
-
|
|
611
|
+
.cp-lineEcgWrap {
|
|
612
|
+
/* basis 0 + grow: the paper area takes exactly the flex-allocated width;
|
|
613
|
+
never shrink (a squeezed strip would shrink the canvas bitmap and make
|
|
614
|
+
the trace speed depend on the window width). */
|
|
615
|
+
flex: 1 1 0;
|
|
541
616
|
min-width: 100px;
|
|
542
617
|
height: 22px;
|
|
543
|
-
|
|
618
|
+
position: relative;
|
|
544
619
|
border-radius: 0;
|
|
545
620
|
border: 1px solid rgba(140, 170, 215, 0.16);
|
|
546
621
|
/* Static paper grid lives in CSS; the canvas above it only paints the trace. */
|
|
@@ -549,6 +624,17 @@ body {
|
|
|
549
624
|
repeating-linear-gradient(90deg, rgba(140, 170, 215, 0.06) 0 1px, transparent 1px 11px),
|
|
550
625
|
rgba(7, 10, 15, 0.55);
|
|
551
626
|
}
|
|
627
|
+
/* The canvas fills its wrapper exactly (absolute), so its intrinsic size can
|
|
628
|
+
never distort the flex layout or the trace during remounts. */
|
|
629
|
+
.cp-lineEcg {
|
|
630
|
+
position: absolute;
|
|
631
|
+
inset: 0;
|
|
632
|
+
width: 100%;
|
|
633
|
+
height: 100%;
|
|
634
|
+
display: block;
|
|
635
|
+
border: 0;
|
|
636
|
+
background: transparent;
|
|
637
|
+
}
|
|
552
638
|
|
|
553
639
|
.cp-lineReadout {
|
|
554
640
|
flex: none;
|
|
@@ -745,16 +831,13 @@ body {
|
|
|
745
831
|
──────────────────────────────────────────────────────────────────────── */
|
|
746
832
|
[data-composer-seat] textarea {
|
|
747
833
|
caret-color: #ffb454;
|
|
748
|
-
letter-spacing: 0.01em;
|
|
749
834
|
}
|
|
750
835
|
[data-composer-seat] textarea:focus {
|
|
751
836
|
caret-color: #ffd9a0;
|
|
752
837
|
}
|
|
838
|
+
/* Composer seat: squared, no extra frame — a visible outline on the big hero
|
|
839
|
+
card read as a jarring border. */
|
|
753
840
|
[data-composer-seat] {
|
|
754
|
-
box-shadow:
|
|
755
|
-
inset 0 0 0 1px rgba(140, 170, 215, 0.22),
|
|
756
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.06),
|
|
757
|
-
inset 0 -12px 30px rgba(0, 0, 0, 0.25);
|
|
758
841
|
border-radius: 0;
|
|
759
842
|
}
|
|
760
843
|
|
|
@@ -762,7 +845,10 @@ body {
|
|
|
762
845
|
Dialogs, menus, tooltips, toasts — the floating DS surfaces
|
|
763
846
|
──────────────────────────────────────────────────────────────────────── */
|
|
764
847
|
[role="dialog"] {
|
|
765
|
-
border: 1px solid rgba(255, 180, 84, 0.
|
|
848
|
+
border: 1px solid rgba(255, 180, 84, 0.35) !important;
|
|
849
|
+
/* Inner hairline frame — the DS double-cased panel. */
|
|
850
|
+
outline: 1px solid rgba(140, 170, 215, 0.22);
|
|
851
|
+
outline-offset: -6px;
|
|
766
852
|
border-radius: 0 !important;
|
|
767
853
|
background: linear-gradient(180deg, rgba(15, 20, 27, 0.98), rgba(10, 14, 19, 0.99)) !important;
|
|
768
854
|
box-shadow:
|
|
@@ -796,6 +882,76 @@ body {
|
|
|
796
882
|
);
|
|
797
883
|
}
|
|
798
884
|
|
|
885
|
+
/* Toast (the only alert portaled straight onto body): DS gold badge —
|
|
886
|
+
amber case, dark ink, chamfered. Inline error rows keep the dark case
|
|
887
|
+
above; this rule wins for the fixed top-center banner. */
|
|
888
|
+
body > [role="alert"] {
|
|
889
|
+
border: 1px solid rgba(255, 196, 120, 0.7) !important;
|
|
890
|
+
border-radius: 0 !important;
|
|
891
|
+
background: linear-gradient(180deg, #ffbe6b, #e09a3c) !important;
|
|
892
|
+
color: rgb(28, 18, 6) !important;
|
|
893
|
+
box-shadow:
|
|
894
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.4),
|
|
895
|
+
0 10px 32px rgba(0, 0, 0, 0.5) !important;
|
|
896
|
+
clip-path: polygon(
|
|
897
|
+
10px 0,
|
|
898
|
+
100% 0,
|
|
899
|
+
100% calc(100% - 10px),
|
|
900
|
+
calc(100% - 10px) 100%,
|
|
901
|
+
0 100%,
|
|
902
|
+
0 10px
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/* Session-header action + utility buttons (Session log, jobs…):
|
|
907
|
+
DS chamfered buttons. Session log lives in .utilities, jobs in .actions. */
|
|
908
|
+
[data-slot="conversation.session.header.actions"] button,
|
|
909
|
+
[data-slot="conversation.session.header.utilities"] button {
|
|
910
|
+
border-radius: 0 !important;
|
|
911
|
+
clip-path: polygon(
|
|
912
|
+
6px 0,
|
|
913
|
+
100% 0,
|
|
914
|
+
100% calc(100% - 6px),
|
|
915
|
+
calc(100% - 6px) 100%,
|
|
916
|
+
0 100%,
|
|
917
|
+
0 6px
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/* ────────────────────────────────────────────────────────────────────────
|
|
922
|
+
Sidebar: DS hairline on the conversation-history column
|
|
923
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
924
|
+
[data-sidebar-collapsed] > div:first-child {
|
|
925
|
+
border-right: 1px solid rgba(140, 170, 215, 0.22);
|
|
926
|
+
box-shadow: inset -1px 0 0 rgba(255, 180, 84, 0.06);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/* Sidebar buttons (New Session etc.): DS chamfered corners. */
|
|
930
|
+
[data-slot="sidebar"] button {
|
|
931
|
+
border-radius: 0 !important;
|
|
932
|
+
clip-path: polygon(
|
|
933
|
+
6px 0,
|
|
934
|
+
100% 0,
|
|
935
|
+
100% calc(100% - 6px),
|
|
936
|
+
calc(100% - 6px) 100%,
|
|
937
|
+
0 100%,
|
|
938
|
+
0 6px
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/* Workspace rows (workspaces, sessions, groups): DS chamfered entries. */
|
|
943
|
+
[role="treeitem"] {
|
|
944
|
+
border-radius: 0 !important;
|
|
945
|
+
clip-path: polygon(
|
|
946
|
+
5px 0,
|
|
947
|
+
100% 0,
|
|
948
|
+
100% calc(100% - 5px),
|
|
949
|
+
calc(100% - 5px) 100%,
|
|
950
|
+
0 100%,
|
|
951
|
+
0 5px
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
|
|
799
955
|
/* ────────────────────────────────────────────────────────────────────────
|
|
800
956
|
DS chamfer everywhere else: kill the round-corner language
|
|
801
957
|
──────────────────────────────────────────────────────────────────────── */
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../src/client/ecg.ts","../src/client/HeartLine.tsx","../src/client/locales.ts","../src/client/style.ts","../src/client/index.ts"],"sourcesContent":["/**\n * CHIRAL PULSE — ECG waveform synthesis.\n *\n * A cardiac cycle is a pure function of beat phase in [0,1): the classic\n * P-QRS-T complex as a sum of wrapped gaussian bumps. The monitor line is a\n * scrolling window over the time axis: the right edge shows the current\n * instant, the window spans `cycles` beats of history. BPM is the phase\n * clock speed, so the whole rhythm accelerates and slows with activity.\n */\n\n/** One wrapped gaussian bump: peak at `center` with `width`, amplitude `amp`. */\nfunction bump(phase: number, center: number, width: number, amp: number): number {\n let d = phase - center\n d -= Math.round(d)\n return amp * Math.exp(-(d * d) / (2 * width * width))\n}\n\n/**\n * Sample one cardiac cycle at beat phase in [0,1). Output range ≈ [-0.35, 1].\n * @param phase - beat phase, any real value (wrapping is internal).\n * @returns the waveform amplitude at that phase.\n */\nexport function ecgValue(phase: number): number {\n return (\n bump(phase, 0.14, 0.030, 0.16) // P wave\n - bump(phase, 0.30, 0.011, 0.26) // Q dip\n + bump(phase, 0.335, 0.012, 1.0) // R spike (wide enough to survive sampling)\n - bump(phase, 0.375, 0.011, 0.34) // S dip\n + bump(phase, 0.52, 0.048, 0.26) // T wave\n + bump(phase, 0.80, 0.012, 0.05) // U ripple\n )\n}\n\n/** One rendered frame of the scrolling monitor line. */\nexport interface EcgFrame {\n /** `x,y` pairs for an SVG polyline `points` attribute, rightmost = now. */\n points: string\n /** The y of the leading (rightmost) sample, in view units. */\n headY: number\n}\n\n/**\n * Build the polyline points for the scrolling window.\n *\n * Hospital monitor semantics: the paper moves at a FIXED speed — every pixel\n * represents a fixed amount of wall-clock time, so the trace scrolls left at\n * a constant rate regardless of heart rate. What changes with BPM is the\n * density of QRS complexes across that fixed window: a fast heart packs more\n * beats onto the screen, a resting one spaces them out.\n *\n * @param nowMs - wall-clock sample time (drives the scan).\n * @param bpm - current heart rate (beat density only; never the scan speed).\n * @param width - view width in user units (x spans 0..width).\n * @param height - view height in user units.\n * @param windowSeconds - how many seconds of signal the window shows.\n * @param step - x sampling step in user units.\n * @returns the frame.\n */\nexport function buildEcgFrame(\n nowMs: number,\n bpm: number,\n width: number,\n height: number,\n windowSeconds: number,\n step = 2,\n): EcgFrame {\n const mid = height / 2\n const amp = height * 0.44\n // Fixed paper speed: seconds per user-unit pixel.\n const secondsPerPixel = windowSeconds / width\n const tNow = nowMs / 1_000\n // Slow baseline wander, like a real monitor: two incommensurate sines keep\n // the resting trace from freezing into a straight flatline.\n const wander = 0.05 * Math.sin(tNow * 0.6)\n + 0.035 * Math.sin(tNow * 1.7 + 1.3)\n const points: string[] = []\n let headY = mid\n for (let x = 0; x <= width; x += step) {\n // x → absolute time: the right edge is \"now\", leftward is the past at a\n // constant rate. Sub-step peak guard: also sample the midpoints so a\n // narrow R spike between two samples still paints at full height.\n const tX = tNow - (width - x) * secondsPerPixel\n const phase = ((tX * (bpm / 60)) % 1 + 1) % 1\n let v = ecgValue(phase)\n if (step > 1) {\n const tMid = tX - secondsPerPixel * step * 0.5\n const phaseMid = ((tMid * (bpm / 60)) % 1 + 1) % 1\n v = Math.max(v, ecgValue(phaseMid))\n }\n const y = mid - (v + wander) * amp\n points.push(`${x.toFixed(1)},${y.toFixed(1)}`)\n if (x === width - step) headY = y\n }\n return { points: points.join(' '), headY }\n}\n\n/**\n * Steady-state BPM for a measured activity rate.\n * @param stepsPerMinute - measured step cadence (steps / minute over a window).\n * @returns target BPM within [42, 150] — a resting BB sleeps at 42, full\n * sprint peaks at 150.\n */\nexport function bpmForActivity(stepsPerMinute: number): number {\n return Math.min(150, Math.max(42, 42 + stepsPerMinute * 6))\n}\n","/**\n * HeartLine — the CHIRAL PULSE monitor strip, docked above the composer\n * (`conversation.input.dock`). A 26px \"monitor paper feed\": the scrolling\n * ECG waveform is the hero, flanked by the BPM read and the status word.\n * No duplicated figures — StatsLine already shows turns/tokens.\n *\n * The pulse is LIVE, not decorative:\n * - `partial` non-null → the model is thinking/generating → +38 BPM\n * - `runningCalls` non-empty → a tool is executing → +52 BPM\n * - `running` (session turn in flight) → +10 BPM\n * - otherwise the 10s step-window activity rate sets the base (~42 idle)\n * The BPM target is smoothed with a lerp; the paper speed stays FIXED and\n * only the beat density changes — hospital monitor semantics.\n *\n * Rendering: a single <canvas> redrawn per rAF at full frame rate. Fixed\n * memory (one canvas the size of the strip), no DOM attribute churn, no\n * string building — the trace is ~width straight segments per frame, which\n * is far cheaper than SVG polyline swaps and cannot stutter from throttling.\n */\nimport { useEffect, useRef, useState } from 'react'\nimport type {\n PropsLocale, PropsRuntime,\n} from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SessionProjectionMap } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only: merges the sessionStats key into SessionProjectionMap.\nimport type {} from '@deepseek-ai/dsh-session-stats/client'\nimport { ecgValue } from './ecg.ts'\nimport type { ChiralKey } from './locales.ts'\nimport { NS } from './locales.ts'\n\n/** Full props: the input-dock runtime seat plus the locale seat. */\nexport type HeartLineProps = PropsRuntime<'conversation.input.dock'> & PropsLocale<typeof NS>\n\n/** Monitor view height, CSS px. */\nconst ECG_HEIGHT = 22\n/** Seconds of signal shown across the window (fixed paper speed). */\nconst ECG_WINDOW = 5\n/** Activity window for the step-rate base, ms. */\nconst ACTIVITY_WINDOW_MS = 10_000\n/** Rotating status lines (locale keys), one every STATUS_ROTATE_S ticks. */\nconst STATUS_KEYS: readonly ChiralKey[] = [\n 'status.stable', 'status.bonded', 'status.chiral', 'status.doom',\n 'status.keep', 'status.voidout', 'status.odradek',\n]\nconst STATUS_ROTATE_S = 4\n/** BPM boost while the model is streaming a partial (thinking/generating). */\nconst BOOST_THINKING = 38\n/** BPM boost while a tool call is running. */\nconst BOOST_TOOL = 52\n/** BPM boost while the session turn is simply in flight. */\nconst BOOST_RUNNING = 10\n/** BPM floor (a resting BB) and ceiling. */\nconst BPM_FLOOR = 42\nconst BPM_CEIL = 150\n\n/** Trace color by activity mode: idle amber, thinking cyan, tool orange, run warm. */\nconst MODE_COLOR = {\n idle: '#ffb454',\n think: '#6fdbe2',\n tool: '#ff7a4d',\n run: '#ffc46b',\n} as const\ntype Mode = keyof typeof MODE_COLOR\n\n/** One activity sample: (time, steps) at a projection update. */\ninterface StepSample {\n t: number\n steps: number\n}\n\n/** Tail of the model's in-flight output: last non-empty text/reasoning block, whitespace-flattened. */\nfunction streamingTail(blocks: readonly { kind: string; text?: string }[]): string {\n for (let i = blocks.length - 1; i >= 0; i -= 1) {\n const block = blocks[i]\n const text = block.text\n if (text !== undefined && text.trim() !== '') {\n return text.replace(/\\s+/g, ' ').trim()\n }\n }\n return ''\n}\n\n/**\n * The CHIRAL PULSE dock entry.\n * @param props - runtime seat (useSession, useProjection) plus the locale seat.\n * @returns the monitor strip.\n */\nexport function HeartLine({ useSession, useProjection, t }: HeartLineProps) {\n const stats = useProjection('sessionStats') as SessionProjectionMap['sessionStats'] | undefined\n // Live activity + real model state: streaming output tail, running tool\n // name, turn in flight, and the last agent error.\n const live = useSession(s => ({\n partial: s.partial !== null,\n partialText: s.partial === null ? '' : streamingTail(s.partial.blocks),\n toolName: s.runningCalls[0]?.name ?? null,\n running: s.running,\n error: s.lastAgentError,\n }))\n\n const steps = stats?.steps ?? 0\n\n // ── BPM engine: step-window base + live activity boost ────────────────\n // targetRef updates once per second (activity readout); bpmRef eases toward\n // it EVERY FRAME inside paint, so the trace phase never jumps — a stepped\n // BPM would snap the whole waveform sideways at every tick.\n const bpmRef = useRef(BPM_FLOOR)\n const targetRef = useRef(BPM_FLOOR)\n const samplesRef = useRef<StepSample[]>([])\n const lastStepsRef = useRef(steps)\n const liveRef = useRef(live)\n liveRef.current = live\n const modeRef = useRef<Mode>('idle')\n const [ui, setUi] = useState({ bpm: BPM_FLOOR, elapsed: 0, mode: 'idle' as Mode })\n useEffect(() => {\n if (steps !== lastStepsRef.current) {\n lastStepsRef.current = steps\n samplesRef.current.push({ t: performance.now(), steps })\n }\n }, [steps])\n\n useEffect(() => {\n const id = window.setInterval(() => {\n const now = performance.now()\n const samples = samplesRef.current\n while (samples.length > 0 && now - samples[0].t > ACTIVITY_WINDOW_MS) samples.shift()\n const first = samples[0]\n const span = first === undefined ? 0 : now - first.t\n const delta = first === undefined ? 0 : lastStepsRef.current - first.steps\n const perMinute = span > 0 ? (delta / span) * 60_000 : 0\n const base = Math.min(BPM_CEIL, Math.max(BPM_FLOOR, 42 + perMinute * 6))\n const act = liveRef.current\n const boost = (act.toolName !== null ? BOOST_TOOL : 0)\n + (act.partial ? BOOST_THINKING : 0)\n + (act.running ? BOOST_RUNNING : 0)\n targetRef.current = Math.min(BPM_CEIL, Math.max(BPM_FLOOR, base + boost))\n const mode: Mode = act.toolName !== null ? 'tool' : act.partial ? 'think' : act.running ? 'run' : 'idle'\n modeRef.current = mode\n setUi(current => ({\n bpm: Math.round(bpmRef.current),\n elapsed: current.elapsed + 1,\n mode,\n }))\n }, 1_000)\n return () => { window.clearInterval(id) }\n }, [])\n\n // ── Canvas trace: one fixed-size canvas, redrawn per rAF ──────────────\n const canvasRef = useRef<HTMLCanvasElement>(null)\n const ctxRef = useRef<CanvasRenderingContext2D | null>(null)\n const widthRef = useRef(640)\n const dprRef = useRef(1)\n\n useEffect(() => {\n const canvas = canvasRef.current\n if (canvas === null) return\n const ctx = canvas.getContext('2d')\n if (ctx === null) return\n ctxRef.current = ctx\n\n const applySize = (): void => {\n const dpr = window.devicePixelRatio || 1\n dprRef.current = dpr\n canvas.width = Math.max(120, Math.round(widthRef.current * dpr))\n canvas.height = Math.round(ECG_HEIGHT * dpr)\n }\n applySize()\n const observer = new ResizeObserver((entries) => {\n const width = Math.max(120, Math.round(entries[0]?.contentRect.width ?? 640))\n if (width !== widthRef.current) {\n widthRef.current = width\n applySize()\n }\n })\n observer.observe(canvas)\n\n let lastBpmFrame = 0\n const paint = (now: number): void => {\n // Frame-rate BPM easing: the target updates once per second, but the\n // trace phase must move continuously or the whole waveform snaps.\n if (lastBpmFrame === 0) lastBpmFrame = now\n const dt = (now - lastBpmFrame) / 1_000\n lastBpmFrame = now\n bpmRef.current += (targetRef.current - bpmRef.current) * Math.min(1, dt * 0.8)\n const c = canvasRef.current\n const g = ctxRef.current\n if (c === null || g === null) return\n const w = widthRef.current\n const h = ECG_HEIGHT\n const dpr = dprRef.current\n g.setTransform(dpr, 0, 0, dpr, 0, 0)\n g.clearRect(0, 0, w, h)\n\n const tNow = now / 1_000\n const secondsPerPixel = ECG_WINDOW / w\n const mid = h / 2\n const amp = h * 0.44\n const wander = 0.05 * Math.sin(tNow * 0.6) + 0.035 * Math.sin(tNow * 1.7 + 1.3)\n const bpm = bpmRef.current\n const yAt = (x: number): number => {\n const tX = tNow - (w - x) * secondsPerPixel\n const phase = ((tX * (bpm / 60)) % 1 + 1) % 1\n return mid - (ecgValue(phase) + wander) * amp\n }\n\n // Chiral ghost: the same trace offset by 3px, faint cyan.\n g.beginPath()\n for (let x = 0; x <= w; x += 1) {\n const y = yAt(x)\n if (x === 0) g.moveTo(x + 3, y)\n else g.lineTo(x + 3, y)\n }\n g.strokeStyle = 'rgba(111, 219, 226, 0.18)'\n g.lineWidth = 1\n g.stroke()\n\n // Main trace.\n g.beginPath()\n for (let x = 0; x <= w; x += 1) {\n const y = yAt(x)\n if (x === 0) g.moveTo(x, y)\n else g.lineTo(x, y)\n }\n g.strokeStyle = MODE_COLOR[modeRef.current]\n g.lineWidth = 1.4\n g.lineJoin = 'round'\n g.lineCap = 'round'\n g.stroke()\n\n // Scan head: halo + dot at the leading edge.\n const headY = yAt(w)\n g.fillStyle = MODE_COLOR[modeRef.current]\n g.globalAlpha = 0.22\n g.beginPath()\n g.arc(w - 2, headY, 5, 0, Math.PI * 2)\n g.fill()\n g.globalAlpha = 1\n g.beginPath()\n g.arc(w - 2, headY, 1.6, 0, Math.PI * 2)\n g.fill()\n }\n\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {\n paint(performance.now()) // one static frame\n return () => { observer.disconnect() }\n }\n let raf = 0\n const loop = (now: number): void => {\n raf = requestAnimationFrame(loop)\n paint(now)\n }\n raf = requestAnimationFrame(loop)\n return () => {\n observer.disconnect()\n cancelAnimationFrame(raf)\n }\n }, [])\n\n // Status word: real model state wins; the flavor rotation only plays while idle.\n const flavor = STATUS_KEYS[Math.floor(ui.elapsed / STATUS_ROTATE_S) % STATUS_KEYS.length]\n const status = live.error !== null\n ? `⚠ ${live.error.slice(0, 16)}`\n : live.toolName !== null\n ? `EXEC · ${live.toolName}`\n : live.partialText !== ''\n ? `⇢ ${live.partialText.slice(-18)}`\n : t(flavor)\n\n return (\n <div className=\"cp-line\" role=\"group\" aria-label={t('line.aria')} data-chiral-pulse data-mode={ui.mode}>\n <div className=\"cp-lineBpm\">\n {ui.bpm}\n <span className=\"cp-lineBpmUnit\">{t('bpm.unit')}</span>\n </div>\n\n <canvas ref={canvasRef} className=\"cp-lineEcg\" aria-hidden />\n\n <div className=\"cp-lineReadout\">\n <div className=\"cp-lineStatus\" title={status}>{status}</div>\n </div>\n </div>\n )\n}\n","/**\n * CHIRAL PULSE — dictionary namespace.\n *\n * The DS monitor idiom stays English in both locales (it is part of the\n * aesthetic: \"LINK STABLE\", \"TIME TO COMPLETION\"); the zh side translates\n * the labels a user actually reads.\n */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'chiral'\n\n/** Dictionary keys of the `chiral` namespace (string-literal union). */\nexport type ChiralKey =\n | 'line.aria'\n | 'bpm.unit'\n | 'status.stable'\n | 'status.bonded'\n | 'status.chiral'\n | 'status.doom'\n | 'status.keep'\n | 'status.voidout'\n | 'status.odradek'\n\n/** English dictionary. */\nexport const en: Record<ChiralKey, string> = {\n 'line.aria': 'BB vital-signs strip — CHIRAL PULSE',\n 'bpm.unit': 'BPM',\n 'status.stable': 'LINK STABLE',\n 'status.bonded': 'BB BONDED',\n 'status.chiral': 'CHIRAL DENSITY: NOMINAL',\n 'status.doom': 'DOOMS LEVEL: 0',\n 'status.keep': 'KEEP ON KEEPING ON',\n 'status.voidout': 'NO VOIDOUT DETECTED',\n 'status.odradek': 'ODRADEK SYNC: OK',\n}\n\n/** Chinese dictionary. */\nexport const zh: Record<ChiralKey, string> = {\n 'line.aria': 'BB 生命体征走纸 — CHIRAL PULSE 手性脉冲',\n 'bpm.unit': '次/分',\n 'status.stable': '链路稳定',\n 'status.bonded': 'BB 连接完成',\n 'status.chiral': '手性密度:正常',\n 'status.doom': 'DOOMS 等级:0',\n 'status.keep': '继续前进 · KEEP ON KEEPING ON',\n 'status.voidout': '未检测到虚爆',\n 'status.odradek': '奥卓克同步:正常',\n}\n","/**\n * CHIRAL PULSE — the Death Stranding sheet, two layers.\n *\n * LAYER 1 — the global skin. The whole app paints from `--dsw-*` variables\n * (ui-theme's design platform: alias tokens reference static tokens, so\n * remapping the palette re-skins every component without touching its\n * structure). This sheet FORCES the DS look under BOTH theme modes: a deep\n * blue-black machine body, cold blue-grey hairlines, amber reserved for\n * emphasis (the heartbeat waveform, hover blooms) — and the deepseek brand\n * blues are left untouched, so the whale mark stays DeepSeek blue.\n *\n * LAYER 2 — the atmosphere. A fixed full-viewport CRT scanline weave, a\n * faint chiral lattice, and a vignette, all pointer-transparent. Plus the\n * BB vital-signs strip that docks under the composer stats: a 26px monitor\n * paper feed whose scrolling ECG is the hero, with the BPM and status read.\n *\n * Every rule is scoped under `.cp-*` (except the token remap, which must\n * target `body`), rides one owned <style data-plugin> tag, and the loader\n * removes it on unload.\n */\n\nexport const CHIRAL_CSS = `\n/* ────────────────────────────────────────────────────────────────────────\n LAYER 1 · global DS skin — dark blue-black, both theme modes\n ──────────────────────────────────────────────────────────────────────── */\n\n/* Alias-level remap: independent of the static scale's role flip between\n themes, so the DS look is identical under light and dark settings. */\nbody[data-ds-dark-theme],\nbody:not([data-ds-dark-theme]) {\n /* machine body — blue-grey with air, not a black void */\n --dsw-alias-bg-base: rgb(13, 17, 23);\n --dsw-alias-bg-layer-1: rgb(17, 22, 29);\n --dsw-alias-bg-layer-2: rgb(21, 27, 35);\n --dsw-alias-bg-layer-3: rgb(26, 33, 42);\n --dsw-alias-bg-overlay: rgb(31, 40, 51);\n --dsw-alias-bg-mask-1: rgba(0, 0, 0, 0.5);\n --dsw-alias-bg-mask-2: rgba(0, 0, 0, 0.22);\n --dsw-alias-bg-mask-3: rgba(0, 0, 0, 0.5);\n --dsw-alias-bg-mask-drop: rgba(10, 14, 19, 0.72);\n --dsw-alias-bg-skeleton: rgba(150, 170, 200, 0.07);\n\n /* text: cold blue-grey */\n --dsw-alias-label-primary: rgb(235, 240, 246);\n --dsw-alias-label-secondary: rgb(178, 190, 205);\n --dsw-alias-label-tertiary: rgb(140, 153, 170);\n --dsw-alias-label-caption: rgb(108, 122, 141);\n --dsw-alias-label-dimmed: rgb(94, 108, 127);\n --dsw-alias-label-primary-dimmed: rgb(205, 213, 222);\n --dsw-alias-label-primary-inverted: rgb(235, 240, 246);\n --dsw-alias-label-primary-foreground: rgb(13, 17, 23);\n --dsw-alias-brand-text: rgb(235, 240, 246);\n --dsw-alias-brand-primary: rgb(103, 158, 254);\n\n /* hairlines: cold blue, readable against the body */\n --dsw-alias-border-l1: rgba(140, 170, 215, 0.15);\n --dsw-alias-border-l2: rgba(140, 170, 215, 0.24);\n --dsw-alias-border-l2-darkmode-thin: rgba(140, 170, 215, 0.19);\n --dsw-alias-border-l3: rgba(140, 170, 215, 0.34);\n --dsw-alias-border-l4: rgba(140, 170, 215, 0.46);\n --dsw-alias-border-inverted: rgba(255, 255, 255, 0.09);\n --dsw-alias-border-inverted2: rgba(255, 255, 255, 0.11);\n\n /* hovers: amber bloom, kept subtle */\n --dsw-alias-interactive-bg-hover: rgba(255, 180, 84, 0.08);\n --dsw-alias-interactive-bg-active: rgba(255, 180, 84, 0.12);\n --dsw-alias-interactive-bg-hover-accent: rgba(255, 180, 84, 0.14);\n --dsw-alias-interactive-bg-hover-solid: rgb(22, 29, 38);\n --dsw-alias-interactive-bg-hover-danger: rgba(242, 90, 90, 0.14);\n\n /* buttons: brand blue stays the primary action */\n --dsw-alias-button-primary-dimmed: rgb(30, 40, 53);\n --dsw-alias-button-primary-hover: rgb(124, 172, 255);\n --dsw-alias-button-ghost-active-fill: rgb(22, 29, 38);\n --dsw-alias-button-ghost-active-hover: rgb(27, 35, 46);\n --dsw-alias-button-ghost-active-border: rgb(140, 170, 215);\n --dsw-alias-button-floating-fill: rgb(19, 25, 33);\n --dsw-alias-button-floating-hover: rgb(24, 31, 41);\n --dsw-alias-button-elevated-fill: rgb(22, 29, 38);\n --dsw-alias-button-contrast-fill: rgb(205, 213, 222);\n --dsw-alias-button-tool-bar-fill: rgba(140, 170, 215, 0.24);\n --dsw-alias-button-tool-bar-fill-invisible: rgba(140, 170, 215, 0.13);\n --dsw-alias-button-tool-bar-hover: rgba(140, 170, 215, 0.32);\n\n /* surfaces */\n --dsw-specific-sidebar-fill: rgb(9, 12, 17);\n --dsw-specific-sidebar-nav-item-active: rgb(20, 27, 36);\n --dsw-specific-sidebar-nav-item-active-accent: rgb(27, 36, 48);\n --dsw-specific-sidebar-nav-item-hover: rgb(15, 20, 28);\n --dsw-specific-bubble: rgb(18, 24, 32);\n --dsw-specific-bubble-highlight: rgb(24, 32, 42);\n --dsw-specific-input-major: rgb(15, 20, 27);\n --dsw-specific-login-input: rgb(12, 16, 22);\n --dsw-specific-menu: rgb(21, 27, 35);\n --dsw-specific-selector: rgb(20, 26, 34);\n --dsw-specific-tip: rgb(16, 21, 28);\n --dsw-alias-markdown-code-block: rgb(10, 14, 19);\n --dsw-alias-markdown-code-block-banner: rgb(13, 17, 23);\n --dsw-alias-markdown-inline-code: rgb(18, 24, 32);\n --dsw-alias-markdown-code-segment-selected: rgb(16, 21, 28);\n --dsw-alias-markdown-code-segment-unselected: rgb(12, 16, 22);\n --dsw-alias-markdown-placeholder: rgb(16, 21, 28);\n --dsw-alias-markdown-tag: rgb(18, 24, 32);\n --dsw-alias-markdown-citation: rgb(22, 29, 38);\n\n /* floats */\n --dsw-alias-toast-bg: rgb(22, 29, 38);\n --dsw-alias-tooltip-bg: rgb(20, 26, 34);\n --dsw-alias-scrollbar-bg-l1: rgb(13, 18, 25);\n --dsw-alias-scrollbar-bg-l2: rgb(17, 23, 31);\n --dsw-alias-scrollbar-hover-l1: rgb(34, 44, 58);\n --dsw-alias-scrollbar-hover-l2: rgb(42, 54, 70);\n\n /* status: amber stays the warn/emphasis hue; success leans chiral cyan */\n --dsw-alias-state-warn-primary: rgb(245, 158, 11);\n --dsw-alias-state-warn-secondary: rgb(247, 173, 49);\n --dsw-alias-state-warn-label: rgb(221, 134, 41);\n --dsw-alias-state-warn-tertiary: rgb(39, 36, 31);\n --dsw-alias-state-success-primary: rgb(52, 205, 168);\n --dsw-alias-state-success-secondary: rgb(94, 222, 189);\n --dsw-alias-state-success-tertiary: rgb(12, 28, 24);\n --dsw-static-green-400: rgb(94, 222, 189);\n --dsw-static-green-500: rgb(52, 205, 168);\n}\n\n/* Focus ring: amber, the DS highlight color. */\n:focus-visible {\n outline: 1px solid rgba(255, 180, 84, 0.65) !important;\n outline-offset: 2px;\n}\n\n/* Ambient bloom behind the app. */\nbody {\n background-image:\n radial-gradient(1100px 620px at 12% -8%, rgba(111, 219, 226, 0.04), transparent 60%),\n radial-gradient(900px 560px at 108% 112%, rgba(103, 158, 254, 0.05), transparent 60%);\n background-attachment: fixed;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n LAYER 2 · atmosphere overlays (injected as fixed elements)\n ──────────────────────────────────────────────────────────────────────── */\n.cp-atmo {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 2147483000;\n}\n.cp-atmo-scanlines {\n background: repeating-linear-gradient(\n 0deg,\n rgba(255, 255, 255, 0.024) 0 1px,\n transparent 1px 3px\n );\n mix-blend-mode: overlay;\n}\n.cp-atmo-lattice {\n opacity: 0.55;\n background:\n repeating-linear-gradient(60deg, transparent 0 17px, rgba(103, 158, 254, 0.03) 17px 18px),\n repeating-linear-gradient(120deg, transparent 0 17px, rgba(111, 219, 226, 0.028) 17px 18px);\n}\n.cp-atmo-vignette {\n background: radial-gradient(120% 100% at 50% 40%, transparent 55%, rgba(0, 0, 0, 0.24) 100%);\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n BB vital-signs strip · the heartbeat paper feed\n ──────────────────────────────────────────────────────────────────────── */\n.cp-line {\n --cp-amber: #ffb454;\n --cp-amber-bright: #ffd9a0;\n --cp-cyan: #6fdbe2;\n --cp-dim: #64727f;\n display: flex;\n align-items: center;\n gap: 12px;\n height: 26px;\n margin: 3px 0 4px;\n padding: 0 10px;\n border: 1px solid rgba(140, 170, 215, 0.26);\n border-radius: 0;\n background:\n linear-gradient(115deg, rgba(140, 190, 255, 0.06) 0%, transparent 30%),\n linear-gradient(180deg, rgba(14, 19, 26, 0.92), rgba(9, 13, 18, 0.94));\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.05),\n inset 0 0 16px rgba(103, 158, 254, 0.06);\n color: #c9d3dc;\n font-family: ui-monospace, \"Cascadia Mono\", \"JetBrains Mono\", Consolas, \"Courier New\", monospace;\n overflow: hidden;\n clip-path: polygon(\n 7px 0,\n 100% 0,\n 100% calc(100% - 7px),\n calc(100% - 7px) 100%,\n 0 100%,\n 0 7px\n );\n}\n\n.cp-lineBpm {\n flex: none;\n min-width: 42px;\n font-size: 11px;\n line-height: 1;\n letter-spacing: 0.5px;\n color: var(--cp-amber-bright);\n text-shadow: 0 0 10px rgba(255, 180, 84, 0.5);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n.cp-lineBpmUnit {\n font-size: 7px;\n letter-spacing: 1px;\n color: var(--cp-dim);\n margin-left: 2px;\n}\n\n.cp-lineEcg {\n flex: 1 1 auto;\n min-width: 100px;\n height: 22px;\n display: block;\n border-radius: 0;\n border: 1px solid rgba(140, 170, 215, 0.16);\n /* Static paper grid lives in CSS; the canvas above it only paints the trace. */\n background:\n repeating-linear-gradient(0deg, rgba(140, 170, 215, 0.07) 0 1px, transparent 1px 11px),\n repeating-linear-gradient(90deg, rgba(140, 170, 215, 0.06) 0 1px, transparent 1px 11px),\n rgba(7, 10, 15, 0.55);\n}\n\n.cp-lineReadout {\n flex: none;\n width: 132px;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n overflow: hidden;\n}\n.cp-lineStatus {\n font-size: 8px;\n letter-spacing: 1.8px;\n text-transform: uppercase;\n color: var(--cp-cyan);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .cp-lineEcg {\n opacity: 0.9;\n }\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n ECG paper grid + activity-mode color coupling\n ──────────────────────────────────────────────────────────────────────── */\n.cp-line[data-mode=\"think\"] .cp-lineBpm {\n color: #7fe3e8;\n text-shadow: 0 0 10px rgba(111, 219, 226, 0.55);\n}\n.cp-line[data-mode=\"tool\"] .cp-lineBpm {\n color: #ff9b7a;\n text-shadow: 0 0 10px rgba(255, 122, 77, 0.6);\n}\n.cp-line[data-mode=\"run\"] .cp-lineBpm {\n color: #ffd9a0;\n}\n.cp-line[data-mode=\"think\"] .cp-lineStatus,\n.cp-line[data-mode=\"tool\"] .cp-lineStatus {\n color: #9fe8ec;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Message-flow dressing — DS glyphs on each node kind\n ──────────────────────────────────────────────────────────────────────── */\n[data-chat-flow-kind] {\n position: relative;\n}\n[data-chat-flow-kind=\"assistant\"] {\n padding-left: 18px;\n}\n[data-chat-flow-kind=\"assistant\"]::before {\n content: \"✦\";\n position: absolute;\n left: 4px;\n top: 12px;\n color: rgba(255, 180, 84, 0.85);\n font-size: 11px;\n line-height: 1;\n text-shadow: 0 0 8px rgba(255, 180, 84, 0.6);\n}\n[data-chat-flow-kind=\"assistant\"]::after {\n content: \"\";\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 1px;\n background: linear-gradient(180deg, transparent, rgba(255, 180, 84, 0.35), transparent);\n}\n[data-chat-flow-kind=\"user\"],\n[data-chat-flow-kind=\"steering\"] {\n padding-right: 18px;\n}\n[data-chat-flow-kind=\"user\"]::before,\n[data-chat-flow-kind=\"steering\"]::before {\n content: \"▸▸\";\n position: absolute;\n right: 2px;\n top: 4px;\n color: rgba(120, 150, 195, 0.75);\n font-size: 10px;\n line-height: 1;\n letter-spacing: -1px;\n}\n[data-chat-flow-kind=\"context\"] {\n padding-left: 16px;\n}\n[data-chat-flow-kind=\"context\"]::before {\n content: \"⇢\";\n position: absolute;\n left: 2px;\n top: 12px;\n color: rgba(111, 219, 226, 0.7);\n font-size: 11px;\n line-height: 1;\n}\n[data-variant=\"think\"] {\n border-left: 2px solid rgba(111, 219, 226, 0.35);\n padding-left: 10px;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Tool-card chassis — the ui-primitives block family gets a DS case\n ──────────────────────────────────────────────────────────────────────── */\n[data-tool],\n[data-search],\n[data-read],\n[data-web],\n[data-diff],\n[data-terminal],\n[data-context-injection-body] {\n border: 1px solid rgba(140, 170, 215, 0.24) !important;\n border-radius: 0 !important;\n background:\n linear-gradient(115deg, rgba(140, 190, 255, 0.06) 0%, transparent 30%),\n linear-gradient(180deg, rgba(15, 20, 27, 0.88), rgba(9, 13, 18, 0.92)) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.05),\n inset 0 0 18px rgba(103, 158, 254, 0.05);\n position: relative;\n clip-path: polygon(\n 7px 0,\n 100% 0,\n 100% calc(100% - 7px),\n calc(100% - 7px) 100%,\n 0 100%,\n 0 7px\n );\n}\n[data-terminal]::before {\n content: \"❯_\";\n position: absolute;\n right: 8px;\n top: 6px;\n color: rgba(111, 219, 226, 0.35);\n font-size: 10px;\n font-family: ui-monospace, Consolas, monospace;\n}\n[data-read]::before {\n content: \"▤\";\n position: absolute;\n right: 8px;\n top: 6px;\n color: rgba(120, 150, 195, 0.4);\n font-size: 11px;\n}\n[data-search]::before {\n content: \"⌕\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(255, 180, 84, 0.4);\n font-size: 13px;\n}\n[data-web]::before {\n content: \"⌖\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(111, 219, 226, 0.4);\n font-size: 12px;\n}\n[data-diff]::before {\n content: \"⇄\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(120, 150, 195, 0.4);\n font-size: 12px;\n}\n[data-tool]::before {\n content: \"⚙\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(255, 180, 84, 0.4);\n font-size: 11px;\n}\n[data-context-injection-body]::before {\n content: \"⇢\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(111, 219, 226, 0.4);\n font-size: 11px;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Composer details\n ──────────────────────────────────────────────────────────────────────── */\n[data-composer-seat] textarea {\n caret-color: #ffb454;\n letter-spacing: 0.01em;\n}\n[data-composer-seat] textarea:focus {\n caret-color: #ffd9a0;\n}\n[data-composer-seat] {\n box-shadow:\n inset 0 0 0 1px rgba(140, 170, 215, 0.22),\n inset 0 1px 0 rgba(255, 255, 255, 0.06),\n inset 0 -12px 30px rgba(0, 0, 0, 0.25);\n border-radius: 0;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Dialogs, menus, tooltips, toasts — the floating DS surfaces\n ──────────────────────────────────────────────────────────────────────── */\n[role=\"dialog\"] {\n border: 1px solid rgba(255, 180, 84, 0.32) !important;\n border-radius: 0 !important;\n background: linear-gradient(180deg, rgba(15, 20, 27, 0.98), rgba(10, 14, 19, 0.99)) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.07),\n inset 0 0 26px rgba(103, 158, 254, 0.06) !important;\n}\n[role=\"menu\"] {\n border: 1px solid rgba(140, 170, 215, 0.3) !important;\n border-radius: 0 !important;\n background: rgba(13, 18, 25, 0.97) !important;\n}\n[role=\"menuitem\"]:hover {\n background: rgba(255, 180, 84, 0.08) !important;\n}\n[role=\"tooltip\"] {\n border: 1px solid rgba(140, 170, 215, 0.32) !important;\n border-radius: 0 !important;\n background: rgba(15, 20, 27, 0.97) !important;\n}\n[role=\"alert\"] {\n border: 1px solid rgba(255, 180, 84, 0.38) !important;\n border-radius: 0 !important;\n background: rgba(15, 20, 27, 0.97) !important;\n clip-path: polygon(\n 8px 0,\n 100% 0,\n 100% calc(100% - 8px),\n calc(100% - 8px) 100%,\n 0 100%,\n 0 8px\n );\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n DS chamfer everywhere else: kill the round-corner language\n ──────────────────────────────────────────────────────────────────────── */\nbutton,\ninput,\ntextarea,\nselect,\n[role=\"tab\"],\n[role=\"menuitem\"],\n[role=\"treeitem\"] {\n border-radius: 0 !important;\n}\n\n`\n","/**\n * CHIRAL PULSE, browser half: the Death Stranding skin plus the BB\n * vital-signs strip under the composer stats.\n *\n * Two contributions:\n * 1. The global DS skin 鈥?a `--dsw-*` token remap (blue-black machine body,\n * amber hairlines, sand-paper light variant), the DeepSeek whale mark's\n * brand blues untouched, plus three pointer-transparent atmosphere\n * overlays (CRT scanlines, chiral lattice, vignette).\n * 2. The heartbeat strip on `conversation.composer.dock` 鈥?a 26px monitor\n * paper feed whose scrolling ECG is the hero and whose BPM follows the\n * session's live activity (model streaming, tools executing).\n *\n * The plugin owns no state of its own beyond the component's local beat\n * engine; every figure arrives through the session standard kit. All styles\n * ride one owned <style data-plugin> tag so the loader removes them on\n * unload/reload.\n */\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the ui-conversation SlotMap merge (the composer.dock entry).\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport { HeartLine } from './HeartLine.tsx'\nimport { en, NS, zh, type ChiralKey } from './locales.ts'\nimport { CHIRAL_CSS } from './style.ts'\n\nexport { HeartLine } from './HeartLine.tsx'\nexport type { HeartLineProps } from './HeartLine.tsx'\nexport type { ChiralKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The BB monitor strip's copy. */\n chiral: ChiralKey\n }\n}\n\n/** Required services: the slot registry and the locale service. */\nexport const inject = ['slots', 'locale']\n\n/**\n * Client plugin body: register dictionaries, inject the DS sheet and the\n * atmosphere overlays, and dock the heartbeat strip under the composer.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'chiral-pulse: dictionaries')\n\n ctx.effect(() => {\n const tag = document.createElement('style')\n tag.dataset.plugin = 'chiral-pulse'\n tag.textContent = CHIRAL_CSS\n document.head.appendChild(tag)\n return () => { tag.remove() }\n }, 'chiral-pulse: styles')\n\n // Atmosphere overlays: scanlines + chiral lattice + vignette, all\n // pointer-transparent, riding the top of the stacking order.\n ctx.effect(() => {\n const layers = [\n { className: 'cp-atmo cp-atmo-scanlines', label: 'scanlines' },\n { className: 'cp-atmo cp-atmo-lattice', label: 'lattice' },\n { className: 'cp-atmo cp-atmo-vignette', label: 'vignette' },\n ]\n const nodes = layers.map(({ className }) => {\n const el = document.createElement('div')\n el.className = className\n el.setAttribute('aria-hidden', 'true')\n document.body.appendChild(el)\n return el\n })\n return () => {\n for (const el of nodes) el.remove()\n }\n }, 'chiral-pulse: atmosphere')\n\n ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\n name: 'conversation.input.dock',\n id: 'chiral-pulse',\n // Above the composer card, under the goal strip: the pulse feed rides\n // with the input it monitors.\n order: 20,\n locale: NS,\n }, HeartLine))\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../src/client/ecg.ts","../src/client/HeartLine.tsx","../src/client/locales.ts","../src/client/style.ts","../src/client/index.ts"],"sourcesContent":["/**\n * CHIRAL PULSE — ECG waveform synthesis.\n *\n * A cardiac cycle is a pure function of beat phase in [0,1): the classic\n * P-QRS-T complex as a sum of wrapped gaussian bumps. The monitor line is a\n * scrolling window over the time axis: the right edge shows the current\n * instant, the window spans `cycles` beats of history. BPM is the phase\n * clock speed, so the whole rhythm accelerates and slows with activity.\n */\n\n/** One wrapped gaussian bump: peak at `center` with `width`, amplitude `amp`. */\nfunction bump(phase: number, center: number, width: number, amp: number): number {\n let d = phase - center\n d -= Math.round(d)\n return amp * Math.exp(-(d * d) / (2 * width * width))\n}\n\n/**\n * Sample one cardiac cycle at beat phase in [0,1). Output range ≈ [-0.35, 1].\n * @param phase - beat phase, any real value (wrapping is internal).\n * @returns the waveform amplitude at that phase.\n */\nexport function ecgValue(phase: number): number {\n return (\n bump(phase, 0.14, 0.030, 0.16) // P wave\n - bump(phase, 0.30, 0.011, 0.26) // Q dip\n + bump(phase, 0.335, 0.016, 1.0) // R spike (wide enough to survive sampling)\n - bump(phase, 0.375, 0.011, 0.34) // S dip\n + bump(phase, 0.52, 0.048, 0.26) // T wave\n + bump(phase, 0.80, 0.012, 0.05) // U ripple\n )\n}\n\n/** One rendered frame of the scrolling monitor line. */\nexport interface EcgFrame {\n /** `x,y` pairs for an SVG polyline `points` attribute, rightmost = now. */\n points: string\n /** The y of the leading (rightmost) sample, in view units. */\n headY: number\n}\n\n/**\n * Build the polyline points for the scrolling window.\n *\n * Hospital monitor semantics: the paper moves at a FIXED speed — every pixel\n * represents a fixed amount of wall-clock time, so the trace scrolls left at\n * a constant rate regardless of heart rate. What changes with BPM is the\n * density of QRS complexes across that fixed window: a fast heart packs more\n * beats onto the screen, a resting one spaces them out.\n *\n * @param nowMs - wall-clock sample time (drives the scan).\n * @param bpm - current heart rate (beat density only; never the scan speed).\n * @param width - view width in user units (x spans 0..width).\n * @param height - view height in user units.\n * @param windowSeconds - how many seconds of signal the window shows.\n * @param step - x sampling step in user units.\n * @returns the frame.\n */\nexport function buildEcgFrame(\n nowMs: number,\n bpm: number,\n width: number,\n height: number,\n windowSeconds: number,\n step = 2,\n): EcgFrame {\n const mid = height / 2\n const amp = height * 0.44\n // Fixed paper speed: seconds per user-unit pixel.\n const secondsPerPixel = windowSeconds / width\n const tNow = nowMs / 1_000\n // Slow baseline wander, like a real monitor: two incommensurate sines keep\n // the resting trace from freezing into a straight flatline.\n const wander = 0.05 * Math.sin(tNow * 0.6)\n + 0.035 * Math.sin(tNow * 1.7 + 1.3)\n const points: string[] = []\n let headY = mid\n for (let x = 0; x <= width; x += step) {\n // x → absolute time: the right edge is \"now\", leftward is the past at a\n // constant rate. Sub-step peak guard: also sample the midpoints so a\n // narrow R spike between two samples still paints at full height.\n const tX = tNow - (width - x) * secondsPerPixel\n const phase = ((tX * (bpm / 60)) % 1 + 1) % 1\n let v = ecgValue(phase)\n if (step > 1) {\n const tMid = tX - secondsPerPixel * step * 0.5\n const phaseMid = ((tMid * (bpm / 60)) % 1 + 1) % 1\n v = Math.max(v, ecgValue(phaseMid))\n }\n const y = mid - (v + wander) * amp\n points.push(`${x.toFixed(1)},${y.toFixed(1)}`)\n if (x === width - step) headY = y\n }\n return { points: points.join(' '), headY }\n}\n\n/**\n * Steady-state BPM for a measured activity rate.\n * @param stepsPerMinute - measured step cadence (steps / minute over a window).\n * @returns target BPM within [42, 150] — a resting BB sleeps at 42, full\n * sprint peaks at 150.\n */\nexport function bpmForActivity(stepsPerMinute: number): number {\n return Math.min(150, Math.max(42, 42 + stepsPerMinute * 6))\n}\n","/**\n * HeartLine — the CHIRAL PULSE monitor strip, docked above the composer\n * (`conversation.input.dock`). A 26px \"monitor paper feed\": the scrolling\n * ECG waveform is the hero, flanked by the BPM read and the status word.\n * No duplicated figures — StatsLine already shows turns/tokens.\n *\n * The pulse is LIVE, not decorative:\n * - `partial` non-null → the model is thinking/generating → +38 BPM\n * - `runningCalls` non-empty → a tool is executing → +52 BPM\n * - `running` (session turn in flight) → +10 BPM\n * - otherwise the 10s step-window activity rate sets the base (~42 idle)\n * The BPM target is smoothed with a lerp; the paper speed stays FIXED and\n * only the beat density changes — hospital monitor semantics.\n *\n * Rendering: a single <canvas> redrawn per rAF at full frame rate. Fixed\n * memory (one canvas the size of the strip), no DOM attribute churn, no\n * string building — the trace is ~width straight segments per frame, which\n * is far cheaper than SVG polyline swaps and cannot stutter from throttling.\n */\nimport { useEffect, useRef, useState } from 'react'\nimport type {\n PropsLocale, PropsRuntime,\n} from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SessionProjectionMap } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only: merges the sessionStats key into SessionProjectionMap.\nimport type {} from '@deepseek-ai/dsh-session-stats/client'\nimport { ecgValue } from './ecg.ts'\nimport type { ChiralKey } from './locales.ts'\nimport { NS } from './locales.ts'\n\n/** Full props: the input-dock runtime seat plus the locale seat. */\nexport type HeartLineProps = PropsRuntime<'conversation.input.dock'> & PropsLocale<typeof NS>\n\n/** Monitor view height, CSS px. */\nconst ECG_HEIGHT = 22\n/**\n * FIXED paper speed in px/second — the real hospital-monitor invariant.\n * The trace scrolls at this absolute rate no matter the strip width; the\n * width only decides how much history fits on screen. A rate change (42→90)\n * therefore only densifies the beats — it never speeds the paper up, and\n * resizing the window cannot make the trace run faster either.\n */\nconst PAPER_SPEED_PX_PER_SECOND = 30\n/** Activity window for the step-rate base, ms. */\nconst ACTIVITY_WINDOW_MS = 10_000\n/** Rotating status lines (locale keys), one every STATUS_ROTATE_S ticks. */\nconst STATUS_KEYS: readonly ChiralKey[] = [\n 'status.stable', 'status.bonded', 'status.chiral', 'status.doom',\n 'status.keep', 'status.voidout', 'status.odradek',\n]\nconst STATUS_ROTATE_S = 4\n/** BPM boost while the model is streaming a partial (thinking/generating). */\nconst BOOST_THINKING = 38\n/** BPM boost while a tool call is running. */\nconst BOOST_TOOL = 52\n/** BPM boost while the session turn is simply in flight. */\nconst BOOST_RUNNING = 10\n/** BPM floor (a resting BB) and ceiling. */\nconst BPM_FLOOR = 42\nconst BPM_CEIL = 150\n/**\n * How fast the displayed heart rate ramps toward its target, in BPM/second.\n * A hospital monitor updates its HR figure on a ~2-3s rolling average and\n * the trace follows gradually — the rate change reads as a slow ramp, not a\n * snap: 42 → 90 takes (90-42)/6 = 8 seconds of visible densification.\n */\nconst BPM_RAMP_PER_SECOND = 6\n\n/** Trace color by activity mode: idle amber, thinking cyan, tool orange, run warm. */\nconst MODE_COLOR = {\n idle: '#ffb454',\n think: '#6fdbe2',\n tool: '#ff7a4d',\n run: '#ffc46b',\n flat: '#c0483c',\n} as const\ntype Mode = keyof typeof MODE_COLOR\n\n/** One activity sample: (time, steps) at a projection update. */\ninterface StepSample {\n t: number\n steps: number\n}\n\n/** Tail of the model's in-flight output: last non-empty text/reasoning block, whitespace-flattened. */\nfunction streamingTail(blocks: readonly { kind: string; text?: string }[]): string {\n for (let i = blocks.length - 1; i >= 0; i -= 1) {\n const block = blocks[i]\n const text = block.text\n if (text !== undefined && text.trim() !== '') {\n return text.replace(/\\s+/g, ' ').trim()\n }\n }\n return ''\n}\n\n/**\n * The CHIRAL PULSE dock entry.\n * @param props - runtime seat (useSession, useProjection) plus the locale seat.\n * @returns the monitor strip.\n */\nexport function HeartLine({ useSession, useProjection, t }: HeartLineProps) {\n const stats = useProjection('sessionStats') as SessionProjectionMap['sessionStats'] | undefined\n // Live activity + real model state: streaming output tail, running tool\n // name, turn in flight, and the last agent error.\n const live = useSession(s => ({\n partial: s.partial !== null,\n partialText: s.partial === null ? '' : streamingTail(s.partial.blocks),\n toolName: s.runningCalls[0]?.name ?? null,\n running: s.running,\n error: s.lastAgentError,\n // A failed step waiting on a model retry: the whale flatlines.\n retrying: s.chat.legacy.nodes.some(\n n => n.kind === 'model-retry' && n.retryState === 'scheduled',\n ),\n }))\n\n const steps = stats?.steps ?? 0\n\n // ── BPM engine: step-window base + live activity boost ────────────────\n // targetRef updates once per second (activity readout); bpmRef eases toward\n // it EVERY FRAME inside paint, so the trace phase never jumps — a stepped\n // BPM would snap the whole waveform sideways at every tick.\n const bpmRef = useRef(BPM_FLOOR)\n const targetRef = useRef(BPM_FLOOR)\n const samplesRef = useRef<StepSample[]>([])\n const lastStepsRef = useRef(steps)\n const liveRef = useRef(live)\n liveRef.current = live\n const modeRef = useRef<Mode>('idle')\n const [ui, setUi] = useState({ bpm: BPM_FLOOR, elapsed: 0, mode: 'idle' as Mode })\n useEffect(() => {\n if (steps !== lastStepsRef.current) {\n lastStepsRef.current = steps\n samplesRef.current.push({ t: performance.now(), steps })\n }\n }, [steps])\n\n useEffect(() => {\n const id = window.setInterval(() => {\n const now = performance.now()\n const samples = samplesRef.current\n while (samples.length > 0 && now - samples[0].t > ACTIVITY_WINDOW_MS) samples.shift()\n const first = samples[0]\n const span = first === undefined ? 0 : now - first.t\n const delta = first === undefined ? 0 : lastStepsRef.current - first.steps\n const perMinute = span > 0 ? (delta / span) * 60_000 : 0\n const base = Math.min(BPM_CEIL, Math.max(BPM_FLOOR, 42 + perMinute * 6))\n const act = liveRef.current\n // Retry stall → flatline: target 0, the trace flattens and the whale's\n // heart stops until the retry starts.\n targetRef.current = act.retrying\n ? 0\n : Math.min(BPM_CEIL, Math.max(BPM_FLOOR, base\n + (act.toolName !== null ? BOOST_TOOL : 0)\n + (act.partial ? BOOST_THINKING : 0)\n + (act.running ? BOOST_RUNNING : 0)))\n const mode: Mode = act.retrying ? 'flat'\n : act.toolName !== null ? 'tool'\n : act.partial ? 'think'\n : act.running ? 'run' : 'idle'\n modeRef.current = mode\n setUi(current => ({\n bpm: Math.round(bpmRef.current),\n elapsed: current.elapsed + 1,\n mode,\n }))\n }, 1_000)\n return () => { window.clearInterval(id) }\n }, [])\n\n // ── Canvas trace: one fixed-size canvas, redrawn per rAF ──────────────\n const canvasRef = useRef<HTMLCanvasElement>(null)\n const ctxRef = useRef<CanvasRenderingContext2D | null>(null)\n const widthRef = useRef(640)\n const dprRef = useRef(1)\n\n useEffect(() => {\n const canvas = canvasRef.current\n if (canvas === null) return\n const ctx = canvas.getContext('2d')\n if (ctx === null) return\n ctxRef.current = ctx\n const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches\n\n const applySize = (): void => {\n const dpr = window.devicePixelRatio || 1\n dprRef.current = dpr\n canvas.width = Math.max(120, Math.round(widthRef.current * dpr))\n canvas.height = Math.round(ECG_HEIGHT * dpr)\n }\n applySize()\n const observer = new ResizeObserver((entries) => {\n const width = Math.max(120, Math.round(entries[0]?.contentRect.width ?? 640))\n if (width !== widthRef.current) {\n widthRef.current = width\n applySize()\n if (reduced) paint(performance.now()) // static mode repaints at the new width\n }\n })\n observer.observe(canvas)\n\n // Smooth display clock: the trace advances at most CLAMP_PER_FRAME worth\n // of time per frame, so a busy main thread (streaming markdown, mode\n // switches) that delays rAF can never make the paper jump forward.\n let displayNow = 0\n let lastPaintReal = 0\n // Refresh-synced display clock. Each NORMAL frame advances the trace by\n // exactly that frame's real interval — the paper speed is then a constant\n // 1× by construction, with zero lag. A DELAYED frame (busy main thread)\n // reuses the last normal interval instead, so the trace never jumps, just\n // runs a touch slow and resumes. No averaging: a smoothed period lags\n // frame-rate changes and the speed visibly surges when the frame rate\n // recovers (the \"left edge accelerates then settles\" artifact).\n let framePeriodMs = 16.7\n // Frozen pixel cache for true erase-bar rendering: the trace image is a\n // static buffer; only the pixels the sweep bar has just passed are\n // refreshed, everything else stays frozen. The image therefore never\n // scrolls, never jumps as a whole, and is frame-rate independent.\n let traceCache: number[] = []\n let lastScanX = -1\n const paint = (now: number): void => {\n if (lastPaintReal === 0) {\n lastPaintReal = now\n displayNow = now\n }\n const realDt = Math.max(0, (now - lastPaintReal) / 1_000)\n lastPaintReal = now\n if (realDt > 0 && realDt < 0.05) framePeriodMs = realDt * 1_000\n const dt = framePeriodMs / 1_000\n displayNow += dt * 1_000\n // Constant-rate ramp (hospital monitor cadence): the rate eases toward\n // its target at BPM_RAMP_PER_SECOND, so a rate change takes seconds and\n // the beat spacing visibly densifies beat by beat.\n const diff = targetRef.current - bpmRef.current\n const step = BPM_RAMP_PER_SECOND * dt\n if (diff > step) bpmRef.current += step\n else if (diff < -step) bpmRef.current -= step\n else bpmRef.current = targetRef.current\n const c = canvasRef.current\n const g = ctxRef.current\n if (c === null || g === null) return\n // Self-healing size: measure the live layout every frame so any\n // remount or hero resize is corrected within one frame, and the trace\n // always fills the visible strip.\n const dpr = window.devicePixelRatio || 1\n const rect = c.getBoundingClientRect()\n const w = Math.max(120, Math.round(rect.width))\n const h = Math.max(1, Math.round(rect.height))\n const wantW = Math.round(w * dpr)\n const wantH = Math.round(h * dpr)\n if (c.width !== wantW || c.height !== wantH) {\n c.width = wantW\n c.height = wantH\n }\n widthRef.current = w\n dprRef.current = dpr\n g.setTransform(dpr, 0, 0, dpr, 0, 0)\n g.clearRect(0, 0, w, h)\n\n const tNow = displayNow / 1_000\n // Absolute paper speed: px per second is constant, width-independent.\n const secondsPerPixel = 1 / PAPER_SPEED_PX_PER_SECOND\n const mid = h / 2\n const amp = h * 0.44\n const wander = 0.05 * Math.sin(tNow * 0.6) + 0.035 * Math.sin(tNow * 1.7 + 1.3)\n const bpm = bpmRef.current\n const flatline = bpm < 1\n\n // Erase-bar sweep: the bar moves right → left; pixels it has just\n // passed are re-sampled (frozen update), the rest of the image is\n // untouched. The right edge is pinned to \"now\"; beat spacing is\n // width-independent (fixed paper speed).\n const sweepPeriod = w / PAPER_SPEED_PX_PER_SECOND\n const tInSweep = ((tNow % sweepPeriod) + sweepPeriod) % sweepPeriod\n const scanX = w - tInSweep * PAPER_SPEED_PX_PER_SECOND // w → 0\n const scanXInt = Math.round(scanX)\n const yNow = (x: number): number => {\n if (flatline) return mid // the whale's heart has stopped — a flat line\n let v = -Infinity\n for (let i = 0; i < 4; i += 1) {\n const tX = tNow - (w - (x + i * 0.25)) * secondsPerPixel\n const phase = ((tX * (bpm / 60)) % 1 + 1) % 1\n const s = ecgValue(phase)\n if (s > v) v = s\n }\n return mid - (v + wander) * amp\n }\n if (traceCache.length !== w + 1) {\n // Width changed: rebuild the buffer, right-anchored, repaint once.\n traceCache = new Array<number>(w + 1)\n for (let x = 0; x <= w; x += 1) traceCache[x] = yNow(x)\n lastScanX = scanXInt\n } else if (lastScanX > scanXInt) {\n // Refresh exactly the pixels the sweep has just passed.\n for (let x = scanXInt; x <= lastScanX && x <= w; x += 1) {\n traceCache[x] = yNow(x)\n }\n lastScanX = scanXInt\n } else {\n // Sweep reset (bar jumped back to the right): no pixels refreshed.\n lastScanX = scanXInt\n }\n\n // Chiral ghost over the frozen image, faint.\n g.beginPath()\n for (let x = 0; x <= w; x += 1) {\n const y = traceCache[x]\n if (x === 0) g.moveTo(x + 3, y)\n else g.lineTo(x + 3, y)\n }\n g.globalAlpha = 0.1\n g.strokeStyle = 'rgba(111, 219, 226, 1)'\n g.lineWidth = 1\n g.stroke()\n g.globalAlpha = 1\n\n // The frozen trace, whole window.\n g.beginPath()\n for (let x = 0; x <= w; x += 1) {\n const y = traceCache[x]\n if (x === 0) g.moveTo(x, y)\n else g.lineTo(x, y)\n }\n g.strokeStyle = MODE_COLOR[modeRef.current]\n g.lineWidth = 1.4\n g.lineJoin = 'round'\n g.lineCap = 'round'\n g.stroke()\n\n // The sweep bar itself: bright core + soft halo.\n g.fillStyle = 'rgba(255, 180, 84, 0.16)'\n g.fillRect(scanX - 5, 0, 10, h)\n g.fillStyle = 'rgba(255, 224, 190, 0.95)'\n g.fillRect(scanX - 1, 0, 2, h)\n }\n\n if (reduced) {\n paint(performance.now()) // one static frame\n return () => { observer.disconnect() }\n }\n let raf = 0\n const loop = (now: number): void => {\n raf = requestAnimationFrame(loop)\n paint(now)\n }\n raf = requestAnimationFrame(loop)\n return () => {\n observer.disconnect()\n cancelAnimationFrame(raf)\n }\n }, [])\n\n // Status word: real model state wins; the flavor rotation only plays while idle.\n const flavor = STATUS_KEYS[Math.floor(ui.elapsed / STATUS_ROTATE_S) % STATUS_KEYS.length]\n const status = live.error !== null\n ? `⚠ ${live.error.slice(0, 16)}`\n : live.retrying\n ? t('status.flatline')\n : live.toolName !== null\n ? `EXEC · ${live.toolName}`\n : live.partialText !== ''\n ? `⇢ ${live.partialText.slice(-18)}`\n : t(flavor)\n\n return (\n <div className=\"cp-line\" role=\"group\" aria-label={t('line.aria')} data-chiral-pulse data-mode={ui.mode} data-rev=\"18\">\n <div className=\"cp-lineBpm\">\n {ui.bpm}\n </div>\n\n <div className=\"cp-lineEcgWrap\">\n <canvas ref={canvasRef} className=\"cp-lineEcg\" aria-hidden />\n </div>\n\n <div className=\"cp-lineReadout\">\n <div className=\"cp-lineStatus\" title={status}>{status}</div>\n </div>\n </div>\n )\n}\n","/**\n * CHIRAL PULSE — dictionary namespace.\n *\n * The DS monitor idiom stays English in both locales (it is part of the\n * aesthetic: \"LINK STABLE\", \"TIME TO COMPLETION\"); the zh side translates\n * the labels a user actually reads.\n */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'chiral'\n\n/** Dictionary keys of the `chiral` namespace (string-literal union). */\nexport type ChiralKey =\n | 'line.aria'\n | 'bpm.unit'\n | 'status.stable'\n | 'status.bonded'\n | 'status.chiral'\n | 'status.doom'\n | 'status.keep'\n | 'status.voidout'\n | 'status.odradek'\n | 'status.flatline'\n\n/** English dictionary. */\nexport const en: Record<ChiralKey, string> = {\n 'line.aria': 'BB vital-signs strip — CHIRAL PULSE',\n 'bpm.unit': 'BPM',\n 'status.stable': 'LINK STABLE',\n 'status.bonded': 'BB BONDED',\n 'status.chiral': 'CHIRAL DENSITY: NOMINAL',\n 'status.doom': 'DOOMS LEVEL: 0',\n 'status.keep': 'KEEP ON KEEPING ON',\n 'status.voidout': 'NO VOIDOUT DETECTED',\n 'status.odradek': 'ODRADEK SYNC: OK',\n 'status.flatline': '♥ FLATLINE',\n}\n\n/** Chinese dictionary. */\nexport const zh: Record<ChiralKey, string> = {\n 'line.aria': 'BB 生命体征走纸 — CHIRAL PULSE 手性脉冲',\n 'bpm.unit': '次/分',\n 'status.stable': '链路稳定',\n 'status.bonded': 'BB 连接完成',\n 'status.chiral': '手性密度:正常',\n 'status.doom': 'DOOMS 等级:0',\n 'status.keep': '继续前进 · KEEP ON KEEPING ON',\n 'status.voidout': '未检测到虚爆',\n 'status.odradek': '奥卓克同步:正常',\n 'status.flatline': '♥ 心脏停跳',\n}\n","/**\n * CHIRAL PULSE — the Death Stranding sheet, two layers.\n *\n * LAYER 1 — the global skin. The whole app paints from `--dsw-*` variables\n * (ui-theme's design platform: alias tokens reference static tokens, so\n * remapping the palette re-skins every component without touching its\n * structure). This sheet FORCES the DS look under BOTH theme modes: a deep\n * blue-black machine body, cold blue-grey hairlines, amber reserved for\n * emphasis (the heartbeat waveform, hover blooms) — and the deepseek brand\n * blues are left untouched, so the whale mark stays DeepSeek blue.\n *\n * LAYER 2 — the atmosphere. A fixed full-viewport CRT scanline weave, a\n * faint chiral lattice, and a vignette, all pointer-transparent. Plus the\n * BB vital-signs strip that docks under the composer stats: a 26px monitor\n * paper feed whose scrolling ECG is the hero, with the BPM and status read.\n *\n * Every rule is scoped under `.cp-*` (except the token remap, which must\n * target `body`), rides one owned <style data-plugin> tag, and the loader\n * removes it on unload.\n */\n\nexport const CHIRAL_CSS = `\n/* ────────────────────────────────────────────────────────────────────────\n LAYER 1 · global DS skin — dark blue-black, both theme modes\n ──────────────────────────────────────────────────────────────────────── */\n\n/* Alias-level remap: independent of the static scale's role flip between\n themes, so the DS look is identical under light and dark settings. */\nbody[data-ds-dark-theme],\nbody:not([data-ds-dark-theme]) {\n /* machine body — blue-grey with air, not a black void */\n --dsw-alias-bg-base: rgb(13, 17, 23);\n --dsw-alias-bg-layer-1: rgb(17, 22, 29);\n --dsw-alias-bg-layer-2: rgb(21, 27, 35);\n --dsw-alias-bg-layer-3: rgb(26, 33, 42);\n --dsw-alias-bg-overlay: rgb(31, 40, 51);\n --dsw-alias-bg-mask-1: rgba(0, 0, 0, 0.5);\n --dsw-alias-bg-mask-2: rgba(0, 0, 0, 0.22);\n --dsw-alias-bg-mask-3: rgba(0, 0, 0, 0.5);\n --dsw-alias-bg-mask-drop: rgba(10, 14, 19, 0.72);\n --dsw-alias-bg-skeleton: rgba(150, 170, 200, 0.07);\n /* Setting rows / select chips (language, agent preset, model, permissions…)\n paint from these; without an override they stay the LIGHT theme's\n near-white and produce white-on-white text. */\n --dsw-alias-bg-module-platform: rgb(15, 20, 27);\n --dsw-alias-bg-multi-select: rgb(18, 24, 32);\n --dsw-alias-fill-tsp-secondary: rgb(18, 24, 32);\n\n /* text: cold blue-grey */\n --dsw-alias-label-primary: rgb(235, 240, 246);\n --dsw-alias-label-secondary: rgb(178, 190, 205);\n --dsw-alias-label-tertiary: rgb(140, 153, 170);\n --dsw-alias-label-caption: rgb(108, 122, 141);\n --dsw-alias-label-dimmed: rgb(94, 108, 127);\n --dsw-alias-label-primary-dimmed: rgb(205, 213, 222);\n --dsw-alias-label-primary-inverted: rgb(20, 26, 35);\n --dsw-alias-label-primary-foreground: rgb(13, 17, 23);\n --dsw-alias-label-primary-bluish: rgb(200, 214, 232);\n --dsw-alias-brand-text: rgb(235, 240, 246);\n --dsw-alias-brand-primary: rgb(103, 158, 254);\n --dsw-alias-brand-primary-invert: rgb(235, 240, 246);\n\n /* hairlines: cold blue, readable against the body */\n --dsw-alias-border-l1: rgba(140, 170, 215, 0.15);\n --dsw-alias-border-l2: rgba(140, 170, 215, 0.24);\n --dsw-alias-border-l2-darkmode-thin: rgba(140, 170, 215, 0.19);\n --dsw-alias-border-l3: rgba(140, 170, 215, 0.34);\n --dsw-alias-border-l4: rgba(140, 170, 215, 0.46);\n --dsw-alias-border-inverted: rgba(255, 255, 255, 0.09);\n --dsw-alias-border-inverted2: rgba(255, 255, 255, 0.11);\n\n /* hovers: amber bloom, kept subtle */\n --dsw-alias-interactive-bg-hover: rgba(255, 180, 84, 0.08);\n --dsw-alias-interactive-bg-active: rgba(255, 180, 84, 0.12);\n --dsw-alias-interactive-bg-hover-accent: rgba(255, 180, 84, 0.14);\n --dsw-alias-interactive-bg-hover-solid: rgb(22, 29, 38);\n --dsw-alias-interactive-bg-hover-danger: rgba(242, 90, 90, 0.14);\n\n /* buttons: brand blue stays the primary action */\n --dsw-alias-button-primary-dimmed: rgb(30, 40, 53);\n --dsw-alias-button-primary-hover: rgb(124, 172, 255);\n --dsw-alias-button-ghost-active-fill: rgb(22, 29, 38);\n --dsw-alias-button-ghost-active-hover: rgb(27, 35, 46);\n --dsw-alias-button-ghost-active-border: rgb(140, 170, 215);\n --dsw-alias-button-floating-fill: rgb(19, 25, 33);\n --dsw-alias-button-floating-hover: rgb(24, 31, 41);\n --dsw-alias-button-elevated-fill: rgb(22, 29, 38);\n /* Contrast fill (attachment rail): pale case + DARK inverted ink — the\n wordmark badge and toasts also ride label-primary-inverted, so the pair\n (pale fill, dark ink) stays readable everywhere. */\n --dsw-alias-button-contrast-fill: rgb(205, 213, 222);\n --dsw-alias-button-tool-bar-fill: rgba(140, 170, 215, 0.24);\n --dsw-alias-button-tool-bar-fill-invisible: rgba(140, 170, 215, 0.13);\n --dsw-alias-button-tool-bar-hover: rgba(140, 170, 215, 0.32);\n\n /* surfaces */\n --dsw-specific-sidebar-fill: rgb(9, 12, 17);\n --dsw-specific-sidebar-nav-item-active: rgb(20, 27, 36);\n --dsw-specific-sidebar-nav-item-active-accent: rgb(27, 36, 48);\n --dsw-specific-sidebar-nav-item-hover: rgb(15, 20, 28);\n --dsw-specific-bubble: rgb(18, 24, 32);\n --dsw-specific-bubble-highlight: rgb(24, 32, 42);\n --dsw-specific-input-major: rgb(15, 20, 27);\n --dsw-specific-login-input: rgb(12, 16, 22);\n --dsw-specific-menu: rgb(21, 27, 35);\n --dsw-specific-selector: rgb(20, 26, 34);\n --dsw-specific-tip: rgb(16, 21, 28);\n --dsw-alias-markdown-code-block: rgb(10, 14, 19);\n --dsw-alias-markdown-code-block-banner: rgb(13, 17, 23);\n --dsw-alias-markdown-inline-code: rgb(18, 24, 32);\n --dsw-alias-markdown-code-segment-selected: rgb(16, 21, 28);\n --dsw-alias-markdown-code-segment-unselected: rgb(12, 16, 22);\n --dsw-alias-markdown-placeholder: rgb(16, 21, 28);\n --dsw-alias-markdown-tag: rgb(18, 24, 32);\n --dsw-alias-markdown-citation: rgb(22, 29, 38);\n\n /* floats */\n --dsw-alias-toast-bg: rgb(22, 29, 38);\n --dsw-alias-tooltip-bg: rgb(20, 26, 34);\n --dsw-alias-scrollbar-bg-l1: rgb(13, 18, 25);\n --dsw-alias-scrollbar-bg-l2: rgb(17, 23, 31);\n --dsw-alias-scrollbar-hover-l1: rgb(34, 44, 58);\n --dsw-alias-scrollbar-hover-l2: rgb(42, 54, 70);\n\n /* status: amber stays the warn/emphasis hue; success leans chiral cyan */\n --dsw-alias-state-warn-primary: rgb(245, 158, 11);\n --dsw-alias-state-warn-secondary: rgb(247, 173, 49);\n --dsw-alias-state-warn-label: rgb(221, 134, 41);\n --dsw-alias-state-warn-tertiary: rgb(39, 36, 31);\n --dsw-alias-state-success-primary: rgb(52, 205, 168);\n --dsw-alias-state-success-secondary: rgb(94, 222, 189);\n --dsw-alias-state-success-tertiary: rgb(12, 28, 24);\n /* Business tint (hero \"preview\" badge et al.): dark case so the pale\n primary-bluish ink stays readable — the light-theme default is near-white. */\n --dsw-alias-state-business-tertiary: rgb(26, 34, 46);\n --dsw-static-green-400: rgb(94, 222, 189);\n --dsw-static-green-500: rgb(52, 205, 168);\n}\n\n/* Focus ring: amber, the DS highlight color. */\n:focus-visible {\n outline: 1px solid rgba(255, 180, 84, 0.65) !important;\n outline-offset: 2px;\n}\n\n/* Ambient bloom behind the app. */\nbody {\n background-image:\n radial-gradient(1100px 620px at 12% -8%, rgba(111, 219, 226, 0.04), transparent 60%),\n radial-gradient(900px 560px at 108% 112%, rgba(103, 158, 254, 0.05), transparent 60%);\n background-attachment: fixed;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n LAYER 2 · atmosphere overlays (injected as fixed elements)\n ──────────────────────────────────────────────────────────────────────── */\n.cp-atmo {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 2147483000;\n}\n.cp-atmo-scanlines {\n background: repeating-linear-gradient(\n 0deg,\n rgba(255, 255, 255, 0.024) 0 1px,\n transparent 1px 3px\n );\n mix-blend-mode: overlay;\n}\n.cp-atmo-lattice {\n opacity: 0.55;\n background:\n repeating-linear-gradient(60deg, transparent 0 17px, rgba(103, 158, 254, 0.03) 17px 18px),\n repeating-linear-gradient(120deg, transparent 0 17px, rgba(111, 219, 226, 0.028) 17px 18px);\n}\n.cp-atmo-vignette {\n background: radial-gradient(120% 100% at 50% 40%, transparent 55%, rgba(0, 0, 0, 0.24) 100%);\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n BB vital-signs strip · the heartbeat paper feed\n ──────────────────────────────────────────────────────────────────────── */\n.cp-line {\n --cp-amber: #ffb454;\n --cp-amber-bright: #ffd9a0;\n --cp-cyan: #6fdbe2;\n --cp-dim: #64727f;\n /* flex: none — the hero (blank-session) composer column squeezes its\n children on short viewports; the monitor strip must never shrink. */\n flex: none;\n display: flex;\n align-items: center;\n gap: 12px;\n height: 26px;\n min-height: 26px;\n margin: 3px 0 4px;\n padding: 0 10px;\n border: 1px solid rgba(140, 170, 215, 0.26);\n border-radius: 0;\n background:\n linear-gradient(115deg, rgba(140, 190, 255, 0.06) 0%, transparent 30%),\n linear-gradient(180deg, rgba(14, 19, 26, 0.92), rgba(9, 13, 18, 0.94));\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.05),\n inset 0 0 16px rgba(103, 158, 254, 0.06);\n color: #c9d3dc;\n font-family: ui-monospace, \"Cascadia Mono\", \"JetBrains Mono\", Consolas, \"Courier New\", monospace;\n overflow: hidden;\n clip-path: polygon(\n 7px 0,\n 100% 0,\n 100% calc(100% - 7px),\n calc(100% - 7px) 100%,\n 0 100%,\n 0 7px\n );\n}\n\n.cp-lineBpm {\n /* Fixed width: a 3-digit readout (42 → 150) must not widen the block and\n squeeze the paper area — that would shrink the trace window and pull the\n left edge rightward as the rate climbs. */\n flex: none;\n width: 48px;\n text-align: center;\n font-size: 16px;\n line-height: 1;\n letter-spacing: 0.5px;\n color: var(--cp-amber-bright);\n text-shadow: 0 0 10px rgba(255, 180, 84, 0.5);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.cp-lineEcgWrap {\n /* basis 0 + grow: the paper area takes exactly the flex-allocated width;\n never shrink (a squeezed strip would shrink the canvas bitmap and make\n the trace speed depend on the window width). */\n flex: 1 1 0;\n min-width: 100px;\n height: 22px;\n position: relative;\n border-radius: 0;\n border: 1px solid rgba(140, 170, 215, 0.16);\n /* Static paper grid lives in CSS; the canvas above it only paints the trace. */\n background:\n repeating-linear-gradient(0deg, rgba(140, 170, 215, 0.07) 0 1px, transparent 1px 11px),\n repeating-linear-gradient(90deg, rgba(140, 170, 215, 0.06) 0 1px, transparent 1px 11px),\n rgba(7, 10, 15, 0.55);\n}\n/* The canvas fills its wrapper exactly (absolute), so its intrinsic size can\n never distort the flex layout or the trace during remounts. */\n.cp-lineEcg {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n display: block;\n border: 0;\n background: transparent;\n}\n\n.cp-lineReadout {\n flex: none;\n width: 132px;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n overflow: hidden;\n}\n.cp-lineStatus {\n font-size: 8px;\n letter-spacing: 1.8px;\n text-transform: uppercase;\n color: var(--cp-cyan);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .cp-lineEcg {\n opacity: 0.9;\n }\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n ECG paper grid + activity-mode color coupling\n ──────────────────────────────────────────────────────────────────────── */\n.cp-line[data-mode=\"think\"] .cp-lineBpm {\n color: #7fe3e8;\n text-shadow: 0 0 10px rgba(111, 219, 226, 0.55);\n}\n.cp-line[data-mode=\"tool\"] .cp-lineBpm {\n color: #ff9b7a;\n text-shadow: 0 0 10px rgba(255, 122, 77, 0.6);\n}\n.cp-line[data-mode=\"run\"] .cp-lineBpm {\n color: #ffd9a0;\n}\n.cp-line[data-mode=\"think\"] .cp-lineStatus,\n.cp-line[data-mode=\"tool\"] .cp-lineStatus {\n color: #9fe8ec;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Message-flow dressing — DS glyphs on each node kind\n ──────────────────────────────────────────────────────────────────────── */\n[data-chat-flow-kind] {\n position: relative;\n}\n[data-chat-flow-kind=\"assistant\"] {\n padding-left: 18px;\n}\n[data-chat-flow-kind=\"assistant\"]::before {\n content: \"✦\";\n position: absolute;\n left: 4px;\n top: 12px;\n color: rgba(255, 180, 84, 0.85);\n font-size: 11px;\n line-height: 1;\n text-shadow: 0 0 8px rgba(255, 180, 84, 0.6);\n}\n[data-chat-flow-kind=\"assistant\"]::after {\n content: \"\";\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 1px;\n background: linear-gradient(180deg, transparent, rgba(255, 180, 84, 0.35), transparent);\n}\n[data-chat-flow-kind=\"user\"],\n[data-chat-flow-kind=\"steering\"] {\n padding-right: 18px;\n}\n[data-chat-flow-kind=\"user\"]::before,\n[data-chat-flow-kind=\"steering\"]::before {\n content: \"▸▸\";\n position: absolute;\n right: 2px;\n top: 4px;\n color: rgba(120, 150, 195, 0.75);\n font-size: 10px;\n line-height: 1;\n letter-spacing: -1px;\n}\n[data-chat-flow-kind=\"context\"] {\n padding-left: 16px;\n}\n[data-chat-flow-kind=\"context\"]::before {\n content: \"⇢\";\n position: absolute;\n left: 2px;\n top: 12px;\n color: rgba(111, 219, 226, 0.7);\n font-size: 11px;\n line-height: 1;\n}\n[data-variant=\"think\"] {\n border-left: 2px solid rgba(111, 219, 226, 0.35);\n padding-left: 10px;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Tool-card chassis — the ui-primitives block family gets a DS case\n ──────────────────────────────────────────────────────────────────────── */\n[data-tool],\n[data-search],\n[data-read],\n[data-web],\n[data-diff],\n[data-terminal],\n[data-context-injection-body] {\n border: 1px solid rgba(140, 170, 215, 0.24) !important;\n border-radius: 0 !important;\n background:\n linear-gradient(115deg, rgba(140, 190, 255, 0.06) 0%, transparent 30%),\n linear-gradient(180deg, rgba(15, 20, 27, 0.88), rgba(9, 13, 18, 0.92)) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.05),\n inset 0 0 18px rgba(103, 158, 254, 0.05);\n position: relative;\n clip-path: polygon(\n 7px 0,\n 100% 0,\n 100% calc(100% - 7px),\n calc(100% - 7px) 100%,\n 0 100%,\n 0 7px\n );\n}\n[data-terminal]::before {\n content: \"❯_\";\n position: absolute;\n right: 8px;\n top: 6px;\n color: rgba(111, 219, 226, 0.35);\n font-size: 10px;\n font-family: ui-monospace, Consolas, monospace;\n}\n[data-read]::before {\n content: \"▤\";\n position: absolute;\n right: 8px;\n top: 6px;\n color: rgba(120, 150, 195, 0.4);\n font-size: 11px;\n}\n[data-search]::before {\n content: \"⌕\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(255, 180, 84, 0.4);\n font-size: 13px;\n}\n[data-web]::before {\n content: \"⌖\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(111, 219, 226, 0.4);\n font-size: 12px;\n}\n[data-diff]::before {\n content: \"⇄\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(120, 150, 195, 0.4);\n font-size: 12px;\n}\n[data-tool]::before {\n content: \"⚙\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(255, 180, 84, 0.4);\n font-size: 11px;\n}\n[data-context-injection-body]::before {\n content: \"⇢\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(111, 219, 226, 0.4);\n font-size: 11px;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Composer details\n ──────────────────────────────────────────────────────────────────────── */\n[data-composer-seat] textarea {\n caret-color: #ffb454;\n}\n[data-composer-seat] textarea:focus {\n caret-color: #ffd9a0;\n}\n/* Composer seat: squared, no extra frame — a visible outline on the big hero\n card read as a jarring border. */\n[data-composer-seat] {\n border-radius: 0;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Dialogs, menus, tooltips, toasts — the floating DS surfaces\n ──────────────────────────────────────────────────────────────────────── */\n[role=\"dialog\"] {\n border: 1px solid rgba(255, 180, 84, 0.35) !important;\n /* Inner hairline frame — the DS double-cased panel. */\n outline: 1px solid rgba(140, 170, 215, 0.22);\n outline-offset: -6px;\n border-radius: 0 !important;\n background: linear-gradient(180deg, rgba(15, 20, 27, 0.98), rgba(10, 14, 19, 0.99)) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.07),\n inset 0 0 26px rgba(103, 158, 254, 0.06) !important;\n}\n[role=\"menu\"] {\n border: 1px solid rgba(140, 170, 215, 0.3) !important;\n border-radius: 0 !important;\n background: rgba(13, 18, 25, 0.97) !important;\n}\n[role=\"menuitem\"]:hover {\n background: rgba(255, 180, 84, 0.08) !important;\n}\n[role=\"tooltip\"] {\n border: 1px solid rgba(140, 170, 215, 0.32) !important;\n border-radius: 0 !important;\n background: rgba(15, 20, 27, 0.97) !important;\n}\n[role=\"alert\"] {\n border: 1px solid rgba(255, 180, 84, 0.38) !important;\n border-radius: 0 !important;\n background: rgba(15, 20, 27, 0.97) !important;\n clip-path: polygon(\n 8px 0,\n 100% 0,\n 100% calc(100% - 8px),\n calc(100% - 8px) 100%,\n 0 100%,\n 0 8px\n );\n}\n\n/* Toast (the only alert portaled straight onto body): DS gold badge —\n amber case, dark ink, chamfered. Inline error rows keep the dark case\n above; this rule wins for the fixed top-center banner. */\nbody > [role=\"alert\"] {\n border: 1px solid rgba(255, 196, 120, 0.7) !important;\n border-radius: 0 !important;\n background: linear-gradient(180deg, #ffbe6b, #e09a3c) !important;\n color: rgb(28, 18, 6) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.4),\n 0 10px 32px rgba(0, 0, 0, 0.5) !important;\n clip-path: polygon(\n 10px 0,\n 100% 0,\n 100% calc(100% - 10px),\n calc(100% - 10px) 100%,\n 0 100%,\n 0 10px\n );\n}\n\n/* Session-header action + utility buttons (Session log, jobs…):\n DS chamfered buttons. Session log lives in .utilities, jobs in .actions. */\n[data-slot=\"conversation.session.header.actions\"] button,\n[data-slot=\"conversation.session.header.utilities\"] button {\n border-radius: 0 !important;\n clip-path: polygon(\n 6px 0,\n 100% 0,\n 100% calc(100% - 6px),\n calc(100% - 6px) 100%,\n 0 100%,\n 0 6px\n );\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Sidebar: DS hairline on the conversation-history column\n ──────────────────────────────────────────────────────────────────────── */\n[data-sidebar-collapsed] > div:first-child {\n border-right: 1px solid rgba(140, 170, 215, 0.22);\n box-shadow: inset -1px 0 0 rgba(255, 180, 84, 0.06);\n}\n\n/* Sidebar buttons (New Session etc.): DS chamfered corners. */\n[data-slot=\"sidebar\"] button {\n border-radius: 0 !important;\n clip-path: polygon(\n 6px 0,\n 100% 0,\n 100% calc(100% - 6px),\n calc(100% - 6px) 100%,\n 0 100%,\n 0 6px\n );\n}\n\n/* Workspace rows (workspaces, sessions, groups): DS chamfered entries. */\n[role=\"treeitem\"] {\n border-radius: 0 !important;\n clip-path: polygon(\n 5px 0,\n 100% 0,\n 100% calc(100% - 5px),\n calc(100% - 5px) 100%,\n 0 100%,\n 0 5px\n );\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n DS chamfer everywhere else: kill the round-corner language\n ──────────────────────────────────────────────────────────────────────── */\nbutton,\ninput,\ntextarea,\nselect,\n[role=\"tab\"],\n[role=\"menuitem\"],\n[role=\"treeitem\"] {\n border-radius: 0 !important;\n}\n\n`\n","/**\n * CHIRAL PULSE, browser half: the Death Stranding skin plus the BB\n * vital-signs strip under the composer stats.\n *\n * Two contributions:\n * 1. The global DS skin 鈥?a `--dsw-*` token remap (blue-black machine body,\n * amber hairlines, sand-paper light variant), the DeepSeek whale mark's\n * brand blues untouched, plus three pointer-transparent atmosphere\n * overlays (CRT scanlines, chiral lattice, vignette).\n * 2. The heartbeat strip on `conversation.composer.dock` 鈥?a 26px monitor\n * paper feed whose scrolling ECG is the hero and whose BPM follows the\n * session's live activity (model streaming, tools executing).\n *\n * The plugin owns no state of its own beyond the component's local beat\n * engine; every figure arrives through the session standard kit. All styles\n * ride one owned <style data-plugin> tag so the loader removes them on\n * unload/reload.\n */\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the ui-conversation SlotMap merge (the composer.dock entry).\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport { HeartLine } from './HeartLine.tsx'\nimport { en, NS, zh, type ChiralKey } from './locales.ts'\nimport { CHIRAL_CSS } from './style.ts'\n\nexport { HeartLine } from './HeartLine.tsx'\nexport type { HeartLineProps } from './HeartLine.tsx'\nexport type { ChiralKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The BB monitor strip's copy. */\n chiral: ChiralKey\n }\n}\n\n/** Required services: the slot registry and the locale service. */\nexport const inject = ['slots', 'locale']\n\n/**\n * Client plugin body: register dictionaries, inject the DS sheet and the\n * atmosphere overlays, and dock the heartbeat strip under the composer.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'chiral-pulse: dictionaries')\n\n ctx.effect(() => {\n const tag = document.createElement('style')\n tag.dataset.plugin = 'chiral-pulse'\n tag.textContent = CHIRAL_CSS\n document.head.appendChild(tag)\n return () => { tag.remove() }\n }, 'chiral-pulse: styles')\n\n // Atmosphere overlays: scanlines + chiral lattice + vignette, all\n // pointer-transparent, riding the top of the stacking order.\n ctx.effect(() => {\n const layers = [\n { className: 'cp-atmo cp-atmo-scanlines', label: 'scanlines' },\n { className: 'cp-atmo cp-atmo-lattice', label: 'lattice' },\n { className: 'cp-atmo cp-atmo-vignette', label: 'vignette' },\n ]\n const nodes = layers.map(({ className }) => {\n const el = document.createElement('div')\n el.className = className\n el.setAttribute('aria-hidden', 'true')\n document.body.appendChild(el)\n return el\n })\n return () => {\n for (const el of nodes) el.remove()\n }\n }, 'chiral-pulse: atmosphere')\n\n ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\n name: 'conversation.input.dock',\n id: 'chiral-pulse',\n // Above the composer card, under the goal strip: the pulse feed rides\n // with the input it monitors.\n order: 20,\n locale: NS,\n }, HeartLine))\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "chiral-pulse",
|
|
3
|
+
"version": "1.2.3",
|
|
4
|
+
"description": "CHIRAL PULSE 闂?a Death Stranding-styled BB pod vital-signs monitor for the DeepSeek Harness web UI: the session\u0027s heartbeat waveform is the hero, and the pulse reacts to real agent activity.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"dsh-plugin",
|
|
8
|
+
"deepseek-harness",
|
|
9
|
+
"death-stranding",
|
|
10
|
+
"bb-pod",
|
|
11
|
+
"heartbeat",
|
|
12
|
+
"ecg",
|
|
13
|
+
"ui-plugin"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "lib/index.js",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./lib/index.js",
|
|
19
|
+
"./client": "./lib/client.js",
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"lib/index.js",
|
|
24
|
+
"lib/client.js",
|
|
25
|
+
"lib/client.js.map",
|
|
26
|
+
"cordis.patch.yml"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"bundle": "tsdown",
|
|
30
|
+
"watch": "tsdown --watch",
|
|
31
|
+
"typecheck": "tsc --noEmit"
|
|
32
|
+
},
|
|
33
|
+
"dsh": {
|
|
34
|
+
"bundle": {
|
|
35
|
+
"patch": "./cordis.patch.yml"
|
|
36
|
+
},
|
|
37
|
+
"client": {
|
|
38
|
+
"platform": "web",
|
|
39
|
+
"inject": [
|
|
40
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
41
|
+
"@deepseek-ai/dsh-client-locale",
|
|
42
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@deepseek-ai/cordis": "workspace:^",
|
|
48
|
+
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
|
49
|
+
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
|
|
51
|
+
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
|
|
52
|
+
"react": "^18.2.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@deepseek-ai/cordis": "workspace:^",
|
|
56
|
+
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
|
57
|
+
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
|
|
60
|
+
"@deepseek-ai/dsh-session-stats": "workspace:^",
|
|
61
|
+
"@deepseek-ai/dsh-token-meter": "workspace:^",
|
|
62
|
+
"@types/react": "~18.3.1",
|
|
63
|
+
"react": "^18.2.0",
|
|
64
|
+
"tsdown": "^0.22.2",
|
|
65
|
+
"typescript": "^6.0.3"
|
|
66
|
+
},
|
|
67
|
+
"license": "MIT"
|
|
68
|
+
}
|