chiral-pulse 0.1.0 → 1.2.4

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 CHANGED
@@ -9,6 +9,21 @@
9
9
 
10
10
  `dsh-plugin` · `deepseek-harness` · `ui-plugin` · `death-stranding` · `bb-pod` · `ecg` · `theme`
11
11
 
12
+ ![CHIRAL PULSE 新会话界面](assets/chiral-pulse-hero.png)
13
+
14
+ ---
15
+
16
+ ## 版本
17
+
18
+ | 版本 | npm | GitHub Release | 亮点 |
19
+ |---|---|---|---|
20
+ | **1.2.3** | ✅ [chiral-pulse@1.2.3](https://www.npmjs.com/package/chiral-pulse) | [v1.2.3](https://github.com/MoonShadow1976/chiral-pulse/releases/tag/v1.2.3) | 真·erase-bar 扫掠(冻结像素缓存,扫描线就地刷新);重试卡顿→心跳停跳;英雄页/徽章修复 |
21
+ | **1.2.2** | — | [v1.2.2](https://github.com/MoonShadow1976/chiral-pulse/releases/tag/v1.2.2) | sweep 模式过渡版本(被 1.2.3 取代) |
22
+ | **1.2.0** | — | [v1.2.0](https://github.com/MoonShadow1976/chiral-pulse/releases/tag/v1.2.0) | 恒定走纸、切角语言、全局暗蓝皮肤定稿 |
23
+ | **0.1.0** | ✅ 已发布 | [v0.1.0](https://github.com/MoonShadow1976/chiral-pulse/releases/tag/v0.1.0) | 首个版本 |
24
+
25
+ > 发版流程:打 tag(`v*.*.*`)→ GitHub Actions 自动构建并发布 npm(见 `.github/workflows/release.yml`)。
26
+
12
27
  ---
13
28
 
14
29
  ## 这是什么
@@ -69,7 +84,7 @@ bundle 层,无需手写配置。
69
84
  ```
70
85
 
71
86
  ```powershell
72
- New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\web\node_modules\@dsh-plugins\chiral-pulse" -Target "D:\path\to\chiral-pulse"
87
+ New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\web\node_modules\chiral-pulse" -Target "D:\path\to\chiral-pulse"
73
88
  ```
74
89
 
75
90
  用户 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, .012, 1) - bump(phase, .375, .011, .34) + bump(phase, .52, .048, .26) + bump(phase, .8, .012, .05);
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
- /** Seconds of signal shown across the window (fixed paper speed). */
57
- const ECG_WINDOW = 5;
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) {
@@ -99,13 +113,21 @@ window.__ModuleLoader__.load({
99
113
  */
100
114
  function HeartLine({ useSession, useProjection, t }) {
101
115
  const stats = useProjection("sessionStats");
102
- const live = useSession((s) => ({
103
- partial: s.partial !== null,
104
- partialText: s.partial === null ? "" : streamingTail(s.partial.blocks),
105
- toolName: s.runningCalls[0]?.name ?? null,
106
- running: s.running,
107
- error: s.lastAgentError
108
- }));
116
+ const live = {
117
+ partial: useSession((s) => s.partial !== null),
118
+ partialText: useSession((s) => s.partial === null ? "" : streamingTail(s.partial.blocks)),
119
+ toolName: useSession((s) => s.runningCalls[0]?.name ?? null),
120
+ running: useSession((s) => s.running),
121
+ error: useSession((s) => s.lastAgentError),
122
+ retrying: useSession((s) => {
123
+ const nodes = s.chat.legacy.nodes;
124
+ for (let i = nodes.length - 1; i >= 0; i -= 1) {
125
+ const n = nodes[i];
126
+ if (n.kind === "model-retry") return n.retryState === "scheduled" && n.time > Date.now() - 12e4;
127
+ }
128
+ return false;
129
+ })
130
+ };
109
131
  const steps = stats?.steps ?? 0;
110
132
  const bpmRef = (0, react.useRef)(BPM_FLOOR);
111
133
  const targetRef = (0, react.useRef)(BPM_FLOOR);
@@ -139,9 +161,8 @@ window.__ModuleLoader__.load({
139
161
  const perMinute = span > 0 ? delta / span * 6e4 : 0;
140
162
  const base = Math.min(BPM_CEIL, Math.max(BPM_FLOOR, 42 + perMinute * 6));
141
163
  const act = liveRef.current;
142
- const boost = (act.toolName !== null ? BOOST_TOOL : 0) + (act.partial ? BOOST_THINKING : 0) + (act.running ? BOOST_RUNNING : 0);
143
- targetRef.current = Math.min(BPM_CEIL, Math.max(BPM_FLOOR, base + boost));
144
- const mode = act.toolName !== null ? "tool" : act.partial ? "think" : act.running ? "run" : "idle";
164
+ 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)));
165
+ const mode = act.retrying ? "flat" : act.toolName !== null ? "tool" : act.partial ? "think" : act.running ? "run" : "idle";
145
166
  modeRef.current = mode;
146
167
  setUi((current) => ({
147
168
  bpm: Math.round(bpmRef.current),
@@ -163,6 +184,7 @@ window.__ModuleLoader__.load({
163
184
  const ctx = canvas.getContext("2d");
164
185
  if (ctx === null) return;
165
186
  ctxRef.current = ctx;
187
+ const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
166
188
  const applySize = () => {
167
189
  const dpr = window.devicePixelRatio || 1;
168
190
  dprRef.current = dpr;
@@ -175,44 +197,89 @@ window.__ModuleLoader__.load({
175
197
  if (width !== widthRef.current) {
176
198
  widthRef.current = width;
177
199
  applySize();
200
+ if (reduced) paint(performance.now());
178
201
  }
179
202
  });
180
203
  observer.observe(canvas);
181
- let lastBpmFrame = 0;
204
+ let displayNow = 0;
205
+ let lastPaintReal = 0;
206
+ let framePeriodMs = 16.7;
207
+ let traceCache = [];
208
+ let lastScanX = -1;
182
209
  const paint = (now) => {
183
- if (lastBpmFrame === 0) lastBpmFrame = now;
184
- const dt = (now - lastBpmFrame) / 1e3;
185
- lastBpmFrame = now;
186
- bpmRef.current += (targetRef.current - bpmRef.current) * Math.min(1, dt * .8);
210
+ if (lastPaintReal === 0) {
211
+ lastPaintReal = now;
212
+ displayNow = now;
213
+ }
214
+ const realDt = Math.max(0, (now - lastPaintReal) / 1e3);
215
+ lastPaintReal = now;
216
+ if (realDt > 0 && realDt < .05) framePeriodMs = realDt * 1e3;
217
+ const dt = framePeriodMs / 1e3;
218
+ displayNow += dt * 1e3;
219
+ const diff = targetRef.current - bpmRef.current;
220
+ const step = BPM_RAMP_PER_SECOND * dt;
221
+ if (diff > step) bpmRef.current += step;
222
+ else if (diff < -step) bpmRef.current -= step;
223
+ else bpmRef.current = targetRef.current;
187
224
  const c = canvasRef.current;
188
225
  const g = ctxRef.current;
189
226
  if (c === null || g === null) return;
227
+ const dpr = dprRef.current;
190
228
  const w = widthRef.current;
191
229
  const h = ECG_HEIGHT;
192
- const dpr = dprRef.current;
230
+ const wantW = Math.round(w * dpr);
231
+ const wantH = Math.round(h * dpr);
232
+ if (c.width !== wantW || c.height !== wantH) {
233
+ c.width = wantW;
234
+ c.height = wantH;
235
+ }
193
236
  g.setTransform(dpr, 0, 0, dpr, 0, 0);
194
237
  g.clearRect(0, 0, w, h);
195
- const tNow = now / 1e3;
196
- const secondsPerPixel = ECG_WINDOW / w;
238
+ const tNow = displayNow / 1e3;
239
+ const secondsPerPixel = 1 / PAPER_SPEED_PX_PER_SECOND;
197
240
  const mid = h / 2;
198
- const amp = h * .44;
241
+ const amp = h * .5;
199
242
  const wander = .05 * Math.sin(tNow * .6) + .035 * Math.sin(tNow * 1.7 + 1.3);
200
243
  const bpm = bpmRef.current;
201
- const yAt = (x) => {
202
- return mid - (ecgValue(((tNow - (w - x) * secondsPerPixel) * (bpm / 60) % 1 + 1) % 1) + wander) * amp;
244
+ const flatline = targetRef.current === 0;
245
+ const sweepPeriod = w / PAPER_SPEED_PX_PER_SECOND;
246
+ const tInSweep = (tNow % sweepPeriod + sweepPeriod) % sweepPeriod;
247
+ const scanX = w - tInSweep * PAPER_SPEED_PX_PER_SECOND;
248
+ const scanXInt = Math.round(scanX);
249
+ const yNow = (x) => {
250
+ if (flatline) return mid;
251
+ let v = -Infinity;
252
+ for (let i = 0; i < 4; i += 1) {
253
+ const s = ecgValue(((tNow - tInSweep - (w - (x + i * .25)) * secondsPerPixel) * (bpm / 60) % 1 + 1) % 1);
254
+ if (s > v) v = s;
255
+ }
256
+ return mid - (v + wander) * amp;
203
257
  };
258
+ if (traceCache.length !== w + 1) {
259
+ traceCache = new Array(w + 1);
260
+ for (let x = 0; x <= w; x += 1) traceCache[x] = yNow(x);
261
+ lastScanX = scanXInt;
262
+ } else if (lastScanX > scanXInt) {
263
+ for (let x = scanXInt; x <= lastScanX && x <= w; x += 1) traceCache[x] = yNow(x);
264
+ lastScanX = scanXInt;
265
+ } else if (lastScanX < scanXInt) {
266
+ traceCache[scanXInt] = yNow(scanXInt);
267
+ lastScanX = scanXInt;
268
+ } else lastScanX = scanXInt;
204
269
  g.beginPath();
205
270
  for (let x = 0; x <= w; x += 1) {
206
- const y = yAt(x);
271
+ const y = traceCache[x];
207
272
  if (x === 0) g.moveTo(x + 3, y);
208
273
  else g.lineTo(x + 3, y);
209
274
  }
210
- g.strokeStyle = "rgba(111, 219, 226, 0.18)";
275
+ g.globalAlpha = .1;
276
+ g.strokeStyle = "rgba(111, 219, 226, 1)";
211
277
  g.lineWidth = 1;
212
278
  g.stroke();
279
+ g.globalAlpha = 1;
213
280
  g.beginPath();
214
281
  for (let x = 0; x <= w; x += 1) {
215
- const y = yAt(x);
282
+ const y = traceCache[x];
216
283
  if (x === 0) g.moveTo(x, y);
217
284
  else g.lineTo(x, y);
218
285
  }
@@ -221,18 +288,12 @@ window.__ModuleLoader__.load({
221
288
  g.lineJoin = "round";
222
289
  g.lineCap = "round";
223
290
  g.stroke();
224
- const headY = yAt(w);
225
- g.fillStyle = MODE_COLOR[modeRef.current];
226
- g.globalAlpha = .22;
227
- g.beginPath();
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();
291
+ g.fillStyle = "rgba(255, 180, 84, 0.16)";
292
+ g.fillRect(scanX - 5, 0, 10, h);
293
+ g.fillStyle = "rgba(255, 224, 190, 0.95)";
294
+ g.fillRect(scanX - 1, 0, 2, h);
234
295
  };
235
- if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
296
+ if (reduced) {
236
297
  paint(performance.now());
237
298
  return () => {
238
299
  observer.disconnect();
@@ -250,25 +311,26 @@ window.__ModuleLoader__.load({
250
311
  };
251
312
  }, []);
252
313
  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);
314
+ 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
315
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
255
316
  className: "cp-line",
256
317
  role: "group",
257
318
  "aria-label": t("line.aria"),
258
319
  "data-chiral-pulse": true,
259
320
  "data-mode": ui.mode,
321
+ "data-rev": "20",
260
322
  children: [
261
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
323
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
262
324
  className: "cp-lineBpm",
263
- children: [ui.bpm, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
264
- className: "cp-lineBpmUnit",
265
- children: t("bpm.unit")
266
- })]
325
+ children: ui.bpm
267
326
  }),
268
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("canvas", {
269
- ref: canvasRef,
270
- className: "cp-lineEcg",
271
- "aria-hidden": true
327
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
328
+ className: "cp-lineEcgWrap",
329
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("canvas", {
330
+ ref: canvasRef,
331
+ className: "cp-lineEcg",
332
+ "aria-hidden": true
333
+ })
272
334
  }),
273
335
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
274
336
  className: "cp-lineReadout",
@@ -302,7 +364,8 @@ window.__ModuleLoader__.load({
302
364
  "status.doom": "DOOMS LEVEL: 0",
303
365
  "status.keep": "KEEP ON KEEPING ON",
304
366
  "status.voidout": "NO VOIDOUT DETECTED",
305
- "status.odradek": "ODRADEK SYNC: OK"
367
+ "status.odradek": "ODRADEK SYNC: OK",
368
+ "status.flatline": "♥ FLATLINE"
306
369
  };
307
370
  /** Chinese dictionary. */
308
371
  const zh = {
@@ -314,7 +377,8 @@ window.__ModuleLoader__.load({
314
377
  "status.doom": "DOOMS 等级:0",
315
378
  "status.keep": "继续前进 · KEEP ON KEEPING ON",
316
379
  "status.voidout": "未检测到虚爆",
317
- "status.odradek": "奥卓克同步:正常"
380
+ "status.odradek": "奥卓克同步:正常",
381
+ "status.flatline": "♥ 心脏停跳"
318
382
  };
319
383
  //#endregion
320
384
  //#region src/client/style.ts
@@ -358,6 +422,12 @@ body:not([data-ds-dark-theme]) {
358
422
  --dsw-alias-bg-mask-3: rgba(0, 0, 0, 0.5);
359
423
  --dsw-alias-bg-mask-drop: rgba(10, 14, 19, 0.72);
360
424
  --dsw-alias-bg-skeleton: rgba(150, 170, 200, 0.07);
425
+ /* Setting rows / select chips (language, agent preset, model, permissions…)
426
+ paint from these; without an override they stay the LIGHT theme's
427
+ near-white and produce white-on-white text. */
428
+ --dsw-alias-bg-module-platform: rgb(15, 20, 27);
429
+ --dsw-alias-bg-multi-select: rgb(18, 24, 32);
430
+ --dsw-alias-fill-tsp-secondary: rgb(18, 24, 32);
361
431
 
362
432
  /* text: cold blue-grey */
363
433
  --dsw-alias-label-primary: rgb(235, 240, 246);
@@ -366,10 +436,12 @@ body:not([data-ds-dark-theme]) {
366
436
  --dsw-alias-label-caption: rgb(108, 122, 141);
367
437
  --dsw-alias-label-dimmed: rgb(94, 108, 127);
368
438
  --dsw-alias-label-primary-dimmed: rgb(205, 213, 222);
369
- --dsw-alias-label-primary-inverted: rgb(235, 240, 246);
439
+ --dsw-alias-label-primary-inverted: rgb(20, 26, 35);
370
440
  --dsw-alias-label-primary-foreground: rgb(13, 17, 23);
441
+ --dsw-alias-label-primary-bluish: rgb(200, 214, 232);
371
442
  --dsw-alias-brand-text: rgb(235, 240, 246);
372
443
  --dsw-alias-brand-primary: rgb(103, 158, 254);
444
+ --dsw-alias-brand-primary-invert: rgb(235, 240, 246);
373
445
 
374
446
  /* hairlines: cold blue, readable against the body */
375
447
  --dsw-alias-border-l1: rgba(140, 170, 215, 0.15);
@@ -396,6 +468,9 @@ body:not([data-ds-dark-theme]) {
396
468
  --dsw-alias-button-floating-fill: rgb(19, 25, 33);
397
469
  --dsw-alias-button-floating-hover: rgb(24, 31, 41);
398
470
  --dsw-alias-button-elevated-fill: rgb(22, 29, 38);
471
+ /* Contrast fill (attachment rail): pale case + DARK inverted ink — the
472
+ wordmark badge and toasts also ride label-primary-inverted, so the pair
473
+ (pale fill, dark ink) stays readable everywhere. */
399
474
  --dsw-alias-button-contrast-fill: rgb(205, 213, 222);
400
475
  --dsw-alias-button-tool-bar-fill: rgba(140, 170, 215, 0.24);
401
476
  --dsw-alias-button-tool-bar-fill-invisible: rgba(140, 170, 215, 0.13);
@@ -438,6 +513,9 @@ body:not([data-ds-dark-theme]) {
438
513
  --dsw-alias-state-success-primary: rgb(52, 205, 168);
439
514
  --dsw-alias-state-success-secondary: rgb(94, 222, 189);
440
515
  --dsw-alias-state-success-tertiary: rgb(12, 28, 24);
516
+ /* Business tint (hero "preview" badge et al.): dark case so the pale
517
+ primary-bluish ink stays readable — the light-theme default is near-white. */
518
+ --dsw-alias-state-business-tertiary: rgb(26, 34, 46);
441
519
  --dsw-static-green-400: rgb(94, 222, 189);
442
520
  --dsw-static-green-500: rgb(52, 205, 168);
443
521
  }
@@ -491,10 +569,14 @@ body {
491
569
  --cp-amber-bright: #ffd9a0;
492
570
  --cp-cyan: #6fdbe2;
493
571
  --cp-dim: #64727f;
572
+ /* flex: none — the hero (blank-session) composer column squeezes its
573
+ children on short viewports; the monitor strip must never shrink. */
574
+ flex: none;
494
575
  display: flex;
495
576
  align-items: center;
496
577
  gap: 12px;
497
578
  height: 26px;
579
+ min-height: 26px;
498
580
  margin: 3px 0 4px;
499
581
  padding: 0 10px;
500
582
  border: 1px solid rgba(140, 170, 215, 0.26);
@@ -519,9 +601,13 @@ body {
519
601
  }
520
602
 
521
603
  .cp-lineBpm {
604
+ /* Fixed width: a 3-digit readout (42 → 150) must not widen the block and
605
+ squeeze the paper area — that would shrink the trace window and pull the
606
+ left edge rightward as the rate climbs. */
522
607
  flex: none;
523
- min-width: 42px;
524
- font-size: 11px;
608
+ width: 48px;
609
+ text-align: center;
610
+ font-size: 16px;
525
611
  line-height: 1;
526
612
  letter-spacing: 0.5px;
527
613
  color: var(--cp-amber-bright);
@@ -529,18 +615,15 @@ body {
529
615
  font-variant-numeric: tabular-nums;
530
616
  white-space: nowrap;
531
617
  }
532
- .cp-lineBpmUnit {
533
- font-size: 7px;
534
- letter-spacing: 1px;
535
- color: var(--cp-dim);
536
- margin-left: 2px;
537
- }
538
618
 
539
- .cp-lineEcg {
540
- flex: 1 1 auto;
619
+ .cp-lineEcgWrap {
620
+ /* basis 0 + grow: the paper area takes exactly the flex-allocated width;
621
+ never shrink (a squeezed strip would shrink the canvas bitmap and make
622
+ the trace speed depend on the window width). */
623
+ flex: 1 1 0;
541
624
  min-width: 100px;
542
625
  height: 22px;
543
- display: block;
626
+ position: relative;
544
627
  border-radius: 0;
545
628
  border: 1px solid rgba(140, 170, 215, 0.16);
546
629
  /* Static paper grid lives in CSS; the canvas above it only paints the trace. */
@@ -549,6 +632,17 @@ body {
549
632
  repeating-linear-gradient(90deg, rgba(140, 170, 215, 0.06) 0 1px, transparent 1px 11px),
550
633
  rgba(7, 10, 15, 0.55);
551
634
  }
635
+ /* The canvas fills its wrapper exactly (absolute), so its intrinsic size can
636
+ never distort the flex layout or the trace during remounts. */
637
+ .cp-lineEcg {
638
+ position: absolute;
639
+ inset: 0;
640
+ width: 100%;
641
+ height: 100%;
642
+ display: block;
643
+ border: 0;
644
+ background: transparent;
645
+ }
552
646
 
553
647
  .cp-lineReadout {
554
648
  flex: none;
@@ -745,16 +839,13 @@ body {
745
839
  ──────────────────────────────────────────────────────────────────────── */
746
840
  [data-composer-seat] textarea {
747
841
  caret-color: #ffb454;
748
- letter-spacing: 0.01em;
749
842
  }
750
843
  [data-composer-seat] textarea:focus {
751
844
  caret-color: #ffd9a0;
752
845
  }
846
+ /* Composer seat: squared, no extra frame — a visible outline on the big hero
847
+ card read as a jarring border. */
753
848
  [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
849
  border-radius: 0;
759
850
  }
760
851
 
@@ -762,7 +853,10 @@ body {
762
853
  Dialogs, menus, tooltips, toasts — the floating DS surfaces
763
854
  ──────────────────────────────────────────────────────────────────────── */
764
855
  [role="dialog"] {
765
- border: 1px solid rgba(255, 180, 84, 0.32) !important;
856
+ border: 1px solid rgba(255, 180, 84, 0.35) !important;
857
+ /* Inner hairline frame — the DS double-cased panel. */
858
+ outline: 1px solid rgba(140, 170, 215, 0.22);
859
+ outline-offset: -6px;
766
860
  border-radius: 0 !important;
767
861
  background: linear-gradient(180deg, rgba(15, 20, 27, 0.98), rgba(10, 14, 19, 0.99)) !important;
768
862
  box-shadow:
@@ -796,6 +890,76 @@ body {
796
890
  );
797
891
  }
798
892
 
893
+ /* Toast (the only alert portaled straight onto body): DS gold badge —
894
+ amber case, dark ink, chamfered. Inline error rows keep the dark case
895
+ above; this rule wins for the fixed top-center banner. */
896
+ body > [role="alert"] {
897
+ border: 1px solid rgba(255, 196, 120, 0.7) !important;
898
+ border-radius: 0 !important;
899
+ background: linear-gradient(180deg, #ffbe6b, #e09a3c) !important;
900
+ color: rgb(28, 18, 6) !important;
901
+ box-shadow:
902
+ inset 0 1px 0 rgba(255, 255, 255, 0.4),
903
+ 0 10px 32px rgba(0, 0, 0, 0.5) !important;
904
+ clip-path: polygon(
905
+ 10px 0,
906
+ 100% 0,
907
+ 100% calc(100% - 10px),
908
+ calc(100% - 10px) 100%,
909
+ 0 100%,
910
+ 0 10px
911
+ );
912
+ }
913
+
914
+ /* Session-header action + utility buttons (Session log, jobs…):
915
+ DS chamfered buttons. Session log lives in .utilities, jobs in .actions. */
916
+ [data-slot="conversation.session.header.actions"] button,
917
+ [data-slot="conversation.session.header.utilities"] button {
918
+ border-radius: 0 !important;
919
+ clip-path: polygon(
920
+ 6px 0,
921
+ 100% 0,
922
+ 100% calc(100% - 6px),
923
+ calc(100% - 6px) 100%,
924
+ 0 100%,
925
+ 0 6px
926
+ );
927
+ }
928
+
929
+ /* ────────────────────────────────────────────────────────────────────────
930
+ Sidebar: DS hairline on the conversation-history column
931
+ ──────────────────────────────────────────────────────────────────────── */
932
+ [data-sidebar-collapsed] > div:first-child {
933
+ border-right: 1px solid rgba(140, 170, 215, 0.22);
934
+ box-shadow: inset -1px 0 0 rgba(255, 180, 84, 0.06);
935
+ }
936
+
937
+ /* Sidebar buttons (New Session etc.): DS chamfered corners. */
938
+ [data-slot="sidebar"] button {
939
+ border-radius: 0 !important;
940
+ clip-path: polygon(
941
+ 6px 0,
942
+ 100% 0,
943
+ 100% calc(100% - 6px),
944
+ calc(100% - 6px) 100%,
945
+ 0 100%,
946
+ 0 6px
947
+ );
948
+ }
949
+
950
+ /* Workspace rows (workspaces, sessions, groups): DS chamfered entries. */
951
+ [role="treeitem"] {
952
+ border-radius: 0 !important;
953
+ clip-path: polygon(
954
+ 5px 0,
955
+ 100% 0,
956
+ 100% calc(100% - 5px),
957
+ calc(100% - 5px) 100%,
958
+ 0 100%,
959
+ 0 5px
960
+ );
961
+ }
962
+
799
963
  /* ────────────────────────────────────────────────────────────────────────
800
964
  DS chamfer everywhere else: kill the round-corner language
801
965
  ──────────────────────────────────────────────────────────────────────── */
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":";;;;;;;;;;;;;;;;;;;EAWA,SAAS,KAAK,OAAe,QAAgB,OAAe,KAAqB;GAC/E,IAAI,IAAI,QAAQ;GAChB,KAAK,KAAK,MAAM,CAAC;GACjB,OAAO,MAAM,KAAK,IAAI,EAAE,IAAI,MAAM,IAAI,QAAQ,MAAM;EACtD;;;;;;EAOA,SAAgB,SAAS,OAAuB;GAC9C,OACE,KAAK,OAAO,KAAM,KAAO,GAAI,IAC3B,KAAK,OAAO,IAAM,MAAO,GAAI,IAC7B,KAAK,OAAO,MAAO,MAAO,CAAG,IAC7B,KAAK,OAAO,MAAO,MAAO,GAAI,IAC9B,KAAK,OAAO,KAAM,MAAO,GAAI,IAC7B,KAAK,OAAO,IAAM,MAAO,GAAI;EAEnC;;;;;;;;;;;;;;;;;;;;;;;ECGA,MAAM,aAAa;;EAEnB,MAAM,aAAa;;EAEnB,MAAM,qBAAqB;;EAE3B,MAAM,cAAoC;GACxC;GAAiB;GAAiB;GAAiB;GACnD;GAAe;GAAkB;EACnC;EACA,MAAM,kBAAkB;;EAExB,MAAM,iBAAiB;;EAEvB,MAAM,aAAa;;EAEnB,MAAM,gBAAgB;;EAEtB,MAAM,YAAY;EAClB,MAAM,WAAW;;EAGjB,MAAM,aAAa;GACjB,MAAM;GACN,OAAO;GACP,MAAM;GACN,KAAK;EACP;;EAUA,SAAS,cAAc,QAA4D;GACjF,KAAK,IAAI,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG;IAE9C,MAAM,OADQ,OAAO,EACH,CAAC;IACnB,IAAI,SAAS,KAAA,KAAa,KAAK,KAAK,MAAM,IACxC,OAAO,KAAK,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;GAE1C;GACA,OAAO;EACT;;;;;;EAOA,SAAgB,UAAU,EAAE,YAAY,eAAe,KAAqB;GAC1E,MAAM,QAAQ,cAAc,cAAc;GAG1C,MAAM,OAAO,YAAW,OAAM;IAC5B,SAAS,EAAE,YAAY;IACvB,aAAa,EAAE,YAAY,OAAO,KAAK,cAAc,EAAE,QAAQ,MAAM;IACrE,UAAU,EAAE,aAAa,EAAE,EAAE,QAAQ;IACrC,SAAS,EAAE;IACX,OAAO,EAAE;GACX,EAAE;GAEF,MAAM,QAAQ,OAAO,SAAS;GAM9B,MAAM,UAAA,GAAA,MAAA,OAAA,CAAgB,SAAS;GAC/B,MAAM,aAAA,GAAA,MAAA,OAAA,CAAmB,SAAS;GAClC,MAAM,cAAA,GAAA,MAAA,OAAA,CAAkC,CAAC,CAAC;GAC1C,MAAM,gBAAA,GAAA,MAAA,OAAA,CAAsB,KAAK;GACjC,MAAM,WAAA,GAAA,MAAA,OAAA,CAAiB,IAAI;GAC3B,QAAQ,UAAU;GAClB,MAAM,WAAA,GAAA,MAAA,OAAA,CAAuB,MAAM;GACnC,MAAM,CAAC,IAAI,UAAA,GAAA,MAAA,SAAA,CAAkB;IAAE,KAAK;IAAW,SAAS;IAAG,MAAM;GAAe,CAAC;GACjF,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,IAAI,UAAU,aAAa,SAAS;KAClC,aAAa,UAAU;KACvB,WAAW,QAAQ,KAAK;MAAE,GAAG,YAAY,IAAI;MAAG;KAAM,CAAC;IACzD;GACF,GAAG,CAAC,KAAK,CAAC;GAEV,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,MAAM,KAAK,OAAO,kBAAkB;KAClC,MAAM,MAAM,YAAY,IAAI;KAC5B,MAAM,UAAU,WAAW;KAC3B,OAAO,QAAQ,SAAS,KAAK,MAAM,QAAQ,EAAE,CAAC,IAAI,oBAAoB,QAAQ,MAAM;KACpF,MAAM,QAAQ,QAAQ;KACtB,MAAM,OAAO,UAAU,KAAA,IAAY,IAAI,MAAM,MAAM;KACnD,MAAM,QAAQ,UAAU,KAAA,IAAY,IAAI,aAAa,UAAU,MAAM;KACrE,MAAM,YAAY,OAAO,IAAK,QAAQ,OAAQ,MAAS;KACvD,MAAM,OAAO,KAAK,IAAI,UAAU,KAAK,IAAI,WAAW,KAAK,YAAY,CAAC,CAAC;KACvE,MAAM,MAAM,QAAQ;KACpB,MAAM,SAAS,IAAI,aAAa,OAAO,aAAa,MAC/C,IAAI,UAAU,iBAAiB,MAC/B,IAAI,UAAU,gBAAgB;KACnC,UAAU,UAAU,KAAK,IAAI,UAAU,KAAK,IAAI,WAAW,OAAO,KAAK,CAAC;KACxE,MAAM,OAAa,IAAI,aAAa,OAAO,SAAS,IAAI,UAAU,UAAU,IAAI,UAAU,QAAQ;KAClG,QAAQ,UAAU;KAClB,OAAM,aAAY;MAChB,KAAK,KAAK,MAAM,OAAO,OAAO;MAC9B,SAAS,QAAQ,UAAU;MAC3B;KACF,EAAE;IACJ,GAAG,GAAK;IACR,aAAa;KAAE,OAAO,cAAc,EAAE;IAAE;GAC1C,GAAG,CAAC,CAAC;GAGL,MAAM,aAAA,GAAA,MAAA,OAAA,CAAsC,IAAI;GAChD,MAAM,UAAA,GAAA,MAAA,OAAA,CAAiD,IAAI;GAC3D,MAAM,YAAA,GAAA,MAAA,OAAA,CAAkB,GAAG;GAC3B,MAAM,UAAA,GAAA,MAAA,OAAA,CAAgB,CAAC;GAEvB,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,MAAM,SAAS,UAAU;IACzB,IAAI,WAAW,MAAM;IACrB,MAAM,MAAM,OAAO,WAAW,IAAI;IAClC,IAAI,QAAQ,MAAM;IAClB,OAAO,UAAU;IAEjB,MAAM,kBAAwB;KAC5B,MAAM,MAAM,OAAO,oBAAoB;KACvC,OAAO,UAAU;KACjB,OAAO,QAAQ,KAAK,IAAI,KAAK,KAAK,MAAM,SAAS,UAAU,GAAG,CAAC;KAC/D,OAAO,SAAS,KAAK,MAAM,aAAa,GAAG;IAC7C;IACA,UAAU;IACV,MAAM,WAAW,IAAI,gBAAgB,YAAY;KAC/C,MAAM,QAAQ,KAAK,IAAI,KAAK,KAAK,MAAM,QAAQ,EAAE,EAAE,YAAY,SAAS,GAAG,CAAC;KAC5E,IAAI,UAAU,SAAS,SAAS;MAC9B,SAAS,UAAU;MACnB,UAAU;KACZ;IACF,CAAC;IACD,SAAS,QAAQ,MAAM;IAEvB,IAAI,eAAe;IACnB,MAAM,SAAS,QAAsB;KAGnC,IAAI,iBAAiB,GAAG,eAAe;KACvC,MAAM,MAAM,MAAM,gBAAgB;KAClC,eAAe;KACf,OAAO,YAAY,UAAU,UAAU,OAAO,WAAW,KAAK,IAAI,GAAG,KAAK,EAAG;KAC7E,MAAM,IAAI,UAAU;KACpB,MAAM,IAAI,OAAO;KACjB,IAAI,MAAM,QAAQ,MAAM,MAAM;KAC9B,MAAM,IAAI,SAAS;KACnB,MAAM,IAAI;KACV,MAAM,MAAM,OAAO;KACnB,EAAE,aAAa,KAAK,GAAG,GAAG,KAAK,GAAG,CAAC;KACnC,EAAE,UAAU,GAAG,GAAG,GAAG,CAAC;KAEtB,MAAM,OAAO,MAAM;KACnB,MAAM,kBAAkB,aAAa;KACrC,MAAM,MAAM,IAAI;KAChB,MAAM,MAAM,IAAI;KAChB,MAAM,SAAS,MAAO,KAAK,IAAI,OAAO,EAAG,IAAI,OAAQ,KAAK,IAAI,OAAO,MAAM,GAAG;KAC9E,MAAM,MAAM,OAAO;KACnB,MAAM,OAAO,MAAsB;MAGjC,OAAO,OAAO,WAFH,QAAQ,IAAI,KAAK,oBACN,MAAM,MAAO,IAAI,KAAK,CAChB,IAAI,UAAU;KAC5C;KAGA,EAAE,UAAU;KACZ,KAAK,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG;MAC9B,MAAM,IAAI,IAAI,CAAC;MACf,IAAI,MAAM,GAAG,EAAE,OAAO,IAAI,GAAG,CAAC;WACzB,EAAE,OAAO,IAAI,GAAG,CAAC;KACxB;KACA,EAAE,cAAc;KAChB,EAAE,YAAY;KACd,EAAE,OAAO;KAGT,EAAE,UAAU;KACZ,KAAK,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG;MAC9B,MAAM,IAAI,IAAI,CAAC;MACf,IAAI,MAAM,GAAG,EAAE,OAAO,GAAG,CAAC;WACrB,EAAE,OAAO,GAAG,CAAC;KACpB;KACA,EAAE,cAAc,WAAW,QAAQ;KACnC,EAAE,YAAY;KACd,EAAE,WAAW;KACb,EAAE,UAAU;KACZ,EAAE,OAAO;KAGT,MAAM,QAAQ,IAAI,CAAC;KACnB,EAAE,YAAY,WAAW,QAAQ;KACjC,EAAE,cAAc;KAChB,EAAE,UAAU;KACZ,EAAE,IAAI,IAAI,GAAG,OAAO,GAAG,GAAG,KAAK,KAAK,CAAC;KACrC,EAAE,KAAK;KACP,EAAE,cAAc;KAChB,EAAE,UAAU;KACZ,EAAE,IAAI,IAAI,GAAG,OAAO,KAAK,GAAG,KAAK,KAAK,CAAC;KACvC,EAAE,KAAK;IACT;IAEA,IAAI,OAAO,WAAW,kCAAkC,CAAC,CAAC,SAAS;KACjE,MAAM,YAAY,IAAI,CAAC;KACvB,aAAa;MAAE,SAAS,WAAW;KAAE;IACvC;IACA,IAAI,MAAM;IACV,MAAM,QAAQ,QAAsB;KAClC,MAAM,sBAAsB,IAAI;KAChC,MAAM,GAAG;IACX;IACA,MAAM,sBAAsB,IAAI;IAChC,aAAa;KACX,SAAS,WAAW;KACpB,qBAAqB,GAAG;IAC1B;GACF,GAAG,CAAC,CAAC;GAGL,MAAM,SAAS,YAAY,KAAK,MAAM,GAAG,UAAU,eAAe,IAAI,YAAY;GAClF,MAAM,SAAS,KAAK,UAAU,OAC1B,KAAK,KAAK,MAAM,MAAM,GAAG,EAAE,MAC3B,KAAK,aAAa,OAChB,UAAU,KAAK,aACf,KAAK,gBAAgB,KACnB,KAAK,KAAK,YAAY,MAAM,GAAG,MAC/B,EAAE,MAAM;GAEhB,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;IAAK,WAAU;IAAU,MAAK;IAAQ,cAAY,EAAE,WAAW;IAAG,qBAAA;IAAkB,aAAW,GAAG;cAAlG;KACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;MAAK,WAAU;gBAAf,CACG,GAAG,KACJ,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;OAAM,WAAU;iBAAkB,EAAE,UAAU;MAAQ,CAAA,CACnD;;KAEL,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;MAAQ,KAAK;MAAW,WAAU;MAAa,eAAA;KAAa,CAAA;KAE5D,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;MAAK,WAAU;gBACb,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;OAAK,WAAU;OAAgB,OAAO;iBAAS;MAAY,CAAA;KACxD,CAAA;IACF;;EAET;;;;;;;;;;;EChRA,MAAa,KAAK;;EAelB,MAAa,KAAgC;GAC3C,aAAa;GACb,YAAY;GACZ,iBAAiB;GACjB,iBAAiB;GACjB,iBAAiB;GACjB,eAAe;GACf,eAAe;GACf,kBAAkB;GAClB,kBAAkB;EACpB;;EAGA,MAAa,KAAgC;GAC3C,aAAa;GACb,YAAY;GACZ,iBAAiB;GACjB,iBAAiB;GACjB,iBAAiB;GACjB,eAAe;GACf,eAAe;GACf,kBAAkB;GAClB,kBAAkB;EACpB;;;;;;;;;;;;;;;;;;;;;;;EC1BA,MAAa,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECkB1B,MAAa,SAAS,CAAC,SAAS,QAAQ;;;;;;EAOxC,SAAgB,MAAM,KAA0B;GAC9C,IAAI,aAAa,IAAI,OAAO,SAAS,IAAI;IAAE;IAAI;GAAG,CAAC,GAAG,4BAA4B;GAElF,IAAI,aAAa;IACf,MAAM,MAAM,SAAS,cAAc,OAAO;IAC1C,IAAI,QAAQ,SAAS;IACrB,IAAI,cAAc;IAClB,SAAS,KAAK,YAAY,GAAG;IAC7B,aAAa;KAAE,IAAI,OAAO;IAAE;GAC9B,GAAG,sBAAsB;GAIzB,IAAI,aAAa;IAMf,MAAM,QAAQ;KAJZ;MAAE,WAAW;MAA6B,OAAO;KAAY;KAC7D;MAAE,WAAW;MAA2B,OAAO;KAAU;KACzD;MAAE,WAAW;MAA4B,OAAO;KAAW;IAE1C,CAAC,CAAC,KAAK,EAAE,gBAAgB;KAC1C,MAAM,KAAK,SAAS,cAAc,KAAK;KACvC,GAAG,YAAY;KACf,GAAG,aAAa,eAAe,MAAM;KACrC,SAAS,KAAK,YAAY,EAAE;KAC5B,OAAO;IACT,CAAC;IACD,aAAa;KACX,KAAK,MAAM,MAAM,OAAO,GAAG,OAAO;IACpC;GACF,GAAG,0BAA0B;GAE7B,IAAI,MAAM,OAAO,iCAAiC,IAAI,MAAM,SAAS;IACnE,MAAM;IACN,IAAI;IAGJ,OAAO;IACP,QAAQ;GACV,GAAG,SAAS,CAAC;EACf"}
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 // One primitive-returning selector per signal: each returns a stable value\n // (boolean / string / null), so the component only re-renders when that\n // signal actually changes — a single object selector re-rendered on every\n // snapshot flush, which is far too often while streaming.\n const partial = useSession(s => s.partial !== null)\n const partialText = useSession(s => (s.partial === null ? '' : streamingTail(s.partial.blocks)))\n const toolName = useSession(s => (s.runningCalls[0]?.name ?? null))\n const running = useSession(s => s.running)\n const error = useSession(s => s.lastAgentError)\n // Flatline only on a LIVE retry stall. The retry chain keeps every attempt;\n // older attempts can linger in 'scheduled' forever (a retry superseded\n // without a retry-started event), so only the LAST attempt counts, within a\n // freshness window — a session merely waiting for user input must never\n // read as a stopped heart. Back-to-front scan stops at the first retry node\n // (which is the last one), so cost is O(distance from the tail), not O(n).\n const retrying = useSession(s => {\n const nodes = s.chat.legacy.nodes\n for (let i = nodes.length - 1; i >= 0; i -= 1) {\n const n = nodes[i]\n if (n.kind === 'model-retry') {\n return n.retryState === 'scheduled' && n.time > Date.now() - 120_000\n }\n }\n return false\n })\n const live = { partial, partialText, toolName, running, error, retrying }\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 // Size is maintained by the ResizeObserver (widthRef / canvas.width);\n // paint reads it directly — no per-frame getBoundingClientRect, so no\n // forced synchronous layout. Fixed logical height keeps the amplitude\n // independent of the live layout (the first paint once saw height 0 and\n // collapsed the trace to a flat line).\n const dpr = dprRef.current\n const w = widthRef.current\n const h = ECG_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 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.5\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 // Flatline is a MODE, not a smoothed value: as soon as the target is a\n // stopped heart, draw the line — don't wait for the 6 BPM/s ramp to\n // cross an arbitrary threshold.\n const flatline = targetRef.current === 0\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 // Sweep sample: pixel x is (re)written when the bar crosses it, so the\n // sample must be anchored to the sweep, not the frame. The frame-time\n // lookback `tNow - (w - x)·secondsPerPixel` evaluated at a crossing\n // cancels its own offset — the bar reaches x exactly (w - x) px into\n // the sweep, so the lookback always lands on the sweep-start instant\n // k·sweepPeriod and EVERY swept pixel receives the SAME sample → the\n // trace flattens to one level once the bar has crossed the strip.\n // Anchoring to the sweep start (`(tNow - tInSweep) - (w - x)·spp`, with\n // tNow - tInSweep = k·sweepPeriod) keeps the right edge pinned to the\n // sweep start (\"now\") and each pixel's sample x-dependent — the swept\n // region redraws as a real, time-normal waveform window.\n for (let i = 0; i < 4; i += 1) {\n const tX = (tNow - tInSweep) - (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 if (lastScanX < scanXInt) {\n // Wrap: the bar jumped back to the right edge. Refresh its new head\n // pixel so the trace continues from the current instant instead of\n // leaving stale data at the right edge (the reported seam).\n traceCache[scanXInt] = yNow(scanXInt)\n lastScanX = scanXInt\n } else {\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=\"20\">\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
- "name": "chiral-pulse",
3
- "version": "0.1.0",
4
- "description": "CHIRAL PULSE 鈥?a Death Stranding-styled BB pod vital-signs monitor for the DeepSeek Harness web UI: the session's 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
- }
1
+ {
2
+ "name": "chiral-pulse",
3
+ "version": "1.2.4",
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
+ }