chiral-pulse 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/cordis.patch.yml +10 -0
- package/lib/client.js +876 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +11 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CHIRAL PULSE contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# CHIRAL PULSE · 手性脉冲
|
|
2
|
+
|
|
3
|
+
> A Death Stranding skin + BB vital-signs monitor for the
|
|
4
|
+
> [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) web UI.
|
|
5
|
+
> The **whole interface** is re-skinned in the Death Stranding look (the
|
|
6
|
+
> DeepSeek whale mark keeps its brand blue), and a live **heartbeat paper
|
|
7
|
+
> feed** runs under the composer stats — its pulse accelerates the moment
|
|
8
|
+
> the agent starts thinking or a tool begins executing.
|
|
9
|
+
|
|
10
|
+
`dsh-plugin` · `deepseek-harness` · `ui-plugin` · `death-stranding` · `bb-pod` · `ecg` · `theme`
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 这是什么
|
|
15
|
+
|
|
16
|
+
**CHIRAL PULSE(手性脉冲)** 是一款 DeepSeek Harness Web 界面插件,两层设计:
|
|
17
|
+
|
|
18
|
+
### 1. 全局皮肤 —— 整个界面都是死亡搁浅
|
|
19
|
+
|
|
20
|
+
不再是小框:插件重映射整个 `--dsw-*` 设计平台调色板,所有组件(侧栏、气泡、
|
|
21
|
+
输入区、设置页)自动换装,不动任何布局结构:
|
|
22
|
+
|
|
23
|
+
- **暗色主题**:DS 蓝黑机身(`#05070a` 起) + 琥珀发丝线边框 + 琥珀悬停微光;
|
|
24
|
+
- **亮色主题**:集装箱沙纸色调("冥滩白"),同样琥珀描边;
|
|
25
|
+
- **氛围层**:全屏 CRT 扫描线、手性晶格(60°/120° 交错)、四角渐晕,
|
|
26
|
+
全部 pointer-transparent,不挡任何操作;
|
|
27
|
+
- **鲸鱼 logo 保留**:DeepSeek 品牌蓝原样不动,左上角标识一切照旧;
|
|
28
|
+
- 主题切换(亮/暗/跟随系统)完全兼容,两套皮肤都做齐了。
|
|
29
|
+
|
|
30
|
+
### 2. 心跳走纸 —— 融入输入区,不突兀
|
|
31
|
+
|
|
32
|
+
挂在数据行(`conversation.composer.dock` 的 StatsLine)下方的一条 26px
|
|
33
|
+
"监护仪走纸":滚动的心电波形是主角,右侧小字 BPM 与状态轮播。
|
|
34
|
+
**不重复任何数据** —— turns/tokens 那些 StatsLine 本来就有。
|
|
35
|
+
|
|
36
|
+
**BPM 是实时心跳,跟随 Agent 的每一次活动:**
|
|
37
|
+
|
|
38
|
+
| 状态 | 来源 | BPM 加成 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| 模型正在思考/生成 | 快照 `partial` 非空 | +38 |
|
|
41
|
+
| 工具正在执行 | 快照 `runningCalls` 非空 | +52 |
|
|
42
|
+
| 会话回合进行中 | 快照 `running` | +10 |
|
|
43
|
+
| 空闲 | 10 秒步骤窗口速率 | 基准(~42 入睡) |
|
|
44
|
+
|
|
45
|
+
BPM 目标用 lerp 平滑——思考一开始,脉搏立刻拉高;工作落地,缓缓回落。
|
|
46
|
+
上限 150,下限 42。
|
|
47
|
+
|
|
48
|
+
## 安装
|
|
49
|
+
|
|
50
|
+
### 发布后(推荐)
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
dsh plugin --profile web chiral-pulse
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
该包同时声明 `dsh.bundle`(内含 `cordis.patch.yml` roster 行)与
|
|
57
|
+
`dsh.client`(浏览器半边),`dsh plugin` 安装后会自动加入 profile 的
|
|
58
|
+
bundle 层,无需手写配置。
|
|
59
|
+
|
|
60
|
+
### 本地开发安装
|
|
61
|
+
|
|
62
|
+
把包链接进 profile 的 `node_modules`,再在
|
|
63
|
+
`$DSH_HOME/profiles/web/cordis.patch.yml` 追加一行:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
- insert:
|
|
67
|
+
- id: ui-chiral-pulse
|
|
68
|
+
name: 'chiral-pulse'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```powershell
|
|
72
|
+
New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\web\node_modules\@dsh-plugins\chiral-pulse" -Target "D:\path\to\chiral-pulse"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
用户 patch 层是热加载的:无需重启 `dsh web`。之后**刷新浏览器页面**即可
|
|
76
|
+
(新增插件行不在旧页面的启动图里,HMR 只重载已有行)。
|
|
77
|
+
|
|
78
|
+
### 卸载
|
|
79
|
+
|
|
80
|
+
从 profile patch 中移除该行并删除 junction;发布版则
|
|
81
|
+
`dsh plugin --profile web remove chiral-pulse`。
|
|
82
|
+
|
|
83
|
+
## 构建
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
pnpm install # 构建只需要 tsdown;也可直接复用本机已有的 tsdown 二进制
|
|
87
|
+
pnpm bundle # 产出 lib/index.js (node 半边) + lib/client.js (浏览器半边)
|
|
88
|
+
pnpm typecheck # tsc --noEmit(tsconfig 已用 paths 指向 harness 的类型产物)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`tsdown.config.ts` 完整复刻了 harness 仓库内共享预设的产物契约:
|
|
92
|
+
`lib/client.js` 是懒 CJS 闭包工厂,通过
|
|
93
|
+
`window.__ModuleLoader__.load({ id, factory })` 注册;仅平台种子模块
|
|
94
|
+
(`react` 家族等)保持 external,由加载器模块表应答;其余全部内联。
|
|
95
|
+
**绝不值导入其他插件包** —— 模块表无法应答,纯类型导入会被擦除,安全。
|
|
96
|
+
|
|
97
|
+
## 发布
|
|
98
|
+
|
|
99
|
+
1. `pnpm publish --access public`(发布到 npm);
|
|
100
|
+
2. 把仓库托管到 GitHub,并**为你的插件仓库添加
|
|
101
|
+
[`dsh-plugin`](https://github.com/topics/dsh-plugin) 话题**,便于被发现
|
|
102
|
+
(参见 [deepseek-harness 的插件指南](https://github.com/deepseek-ai/deepseek-harness))。
|
|
103
|
+
|
|
104
|
+
## 工作原理
|
|
105
|
+
|
|
106
|
+
- **主题**:ui-theme 的设计平台里 alias 令牌引用静态令牌,插件只重映射
|
|
107
|
+
`body[data-ds-dark-theme]` / `body:not([data-ds-dark-theme])` 两套静态色,
|
|
108
|
+
全应用自动跟随;brand 蓝(鲸鱼)刻意不动。
|
|
109
|
+
- **数据**:走纸条零自持状态 —— `useProjection('sessionStats')` 提供步骤
|
|
110
|
+
窗口,BPM 实时加成来自 `useSession` 快照的 `partial` / `runningCalls` /
|
|
111
|
+
`running`;波形由纯函数 ECG 合成器(`src/client/ecg.ts`,PQRST 高斯凸包)
|
|
112
|
+
按当前 BPM 滚动生成,rAF 循环直写 SVG 属性,React 只在快照/秒针/尺寸
|
|
113
|
+
变化时重渲染。
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# CHIRAL PULSE 鈥?BB pod vital-signs monitor.
|
|
2
|
+
#
|
|
3
|
+
# This bundle patch adds the browser-roster row. The browser half of the
|
|
4
|
+
# package is discovered through its package.json `dsh.client` declaration;
|
|
5
|
+
# the empty node half is what the loader row mounts. Applied as a profile
|
|
6
|
+
# bundle layer after the base/web-app layers, so the row composes into the
|
|
7
|
+
# same tree as every shipped client plugin.
|
|
8
|
+
- insert:
|
|
9
|
+
- id: ui-chiral-pulse
|
|
10
|
+
name: 'chiral-pulse'
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "chiral-pulse",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
//#region src/client/ecg.ts
|
|
10
|
+
/**
|
|
11
|
+
* CHIRAL PULSE — ECG waveform synthesis.
|
|
12
|
+
*
|
|
13
|
+
* A cardiac cycle is a pure function of beat phase in [0,1): the classic
|
|
14
|
+
* P-QRS-T complex as a sum of wrapped gaussian bumps. The monitor line is a
|
|
15
|
+
* scrolling window over the time axis: the right edge shows the current
|
|
16
|
+
* instant, the window spans `cycles` beats of history. BPM is the phase
|
|
17
|
+
* clock speed, so the whole rhythm accelerates and slows with activity.
|
|
18
|
+
*/
|
|
19
|
+
/** One wrapped gaussian bump: peak at `center` with `width`, amplitude `amp`. */
|
|
20
|
+
function bump(phase, center, width, amp) {
|
|
21
|
+
let d = phase - center;
|
|
22
|
+
d -= Math.round(d);
|
|
23
|
+
return amp * Math.exp(-(d * d) / (2 * width * width));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Sample one cardiac cycle at beat phase in [0,1). Output range ≈ [-0.35, 1].
|
|
27
|
+
* @param phase - beat phase, any real value (wrapping is internal).
|
|
28
|
+
* @returns the waveform amplitude at that phase.
|
|
29
|
+
*/
|
|
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);
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/client/HeartLine.tsx
|
|
35
|
+
/**
|
|
36
|
+
* HeartLine — the CHIRAL PULSE monitor strip, docked above the composer
|
|
37
|
+
* (`conversation.input.dock`). A 26px "monitor paper feed": the scrolling
|
|
38
|
+
* ECG waveform is the hero, flanked by the BPM read and the status word.
|
|
39
|
+
* No duplicated figures — StatsLine already shows turns/tokens.
|
|
40
|
+
*
|
|
41
|
+
* The pulse is LIVE, not decorative:
|
|
42
|
+
* - `partial` non-null → the model is thinking/generating → +38 BPM
|
|
43
|
+
* - `runningCalls` non-empty → a tool is executing → +52 BPM
|
|
44
|
+
* - `running` (session turn in flight) → +10 BPM
|
|
45
|
+
* - otherwise the 10s step-window activity rate sets the base (~42 idle)
|
|
46
|
+
* The BPM target is smoothed with a lerp; the paper speed stays FIXED and
|
|
47
|
+
* only the beat density changes — hospital monitor semantics.
|
|
48
|
+
*
|
|
49
|
+
* Rendering: a single <canvas> redrawn per rAF at full frame rate. Fixed
|
|
50
|
+
* memory (one canvas the size of the strip), no DOM attribute churn, no
|
|
51
|
+
* string building — the trace is ~width straight segments per frame, which
|
|
52
|
+
* is far cheaper than SVG polyline swaps and cannot stutter from throttling.
|
|
53
|
+
*/
|
|
54
|
+
/** Monitor view height, CSS px. */
|
|
55
|
+
const ECG_HEIGHT = 22;
|
|
56
|
+
/** Seconds of signal shown across the window (fixed paper speed). */
|
|
57
|
+
const ECG_WINDOW = 5;
|
|
58
|
+
/** Activity window for the step-rate base, ms. */
|
|
59
|
+
const ACTIVITY_WINDOW_MS = 1e4;
|
|
60
|
+
/** Rotating status lines (locale keys), one every STATUS_ROTATE_S ticks. */
|
|
61
|
+
const STATUS_KEYS = [
|
|
62
|
+
"status.stable",
|
|
63
|
+
"status.bonded",
|
|
64
|
+
"status.chiral",
|
|
65
|
+
"status.doom",
|
|
66
|
+
"status.keep",
|
|
67
|
+
"status.voidout",
|
|
68
|
+
"status.odradek"
|
|
69
|
+
];
|
|
70
|
+
const STATUS_ROTATE_S = 4;
|
|
71
|
+
/** BPM boost while the model is streaming a partial (thinking/generating). */
|
|
72
|
+
const BOOST_THINKING = 38;
|
|
73
|
+
/** BPM boost while a tool call is running. */
|
|
74
|
+
const BOOST_TOOL = 52;
|
|
75
|
+
/** BPM boost while the session turn is simply in flight. */
|
|
76
|
+
const BOOST_RUNNING = 10;
|
|
77
|
+
/** BPM floor (a resting BB) and ceiling. */
|
|
78
|
+
const BPM_FLOOR = 42;
|
|
79
|
+
const BPM_CEIL = 150;
|
|
80
|
+
/** Trace color by activity mode: idle amber, thinking cyan, tool orange, run warm. */
|
|
81
|
+
const MODE_COLOR = {
|
|
82
|
+
idle: "#ffb454",
|
|
83
|
+
think: "#6fdbe2",
|
|
84
|
+
tool: "#ff7a4d",
|
|
85
|
+
run: "#ffc46b"
|
|
86
|
+
};
|
|
87
|
+
/** Tail of the model's in-flight output: last non-empty text/reasoning block, whitespace-flattened. */
|
|
88
|
+
function streamingTail(blocks) {
|
|
89
|
+
for (let i = blocks.length - 1; i >= 0; i -= 1) {
|
|
90
|
+
const text = blocks[i].text;
|
|
91
|
+
if (text !== void 0 && text.trim() !== "") return text.replace(/\s+/g, " ").trim();
|
|
92
|
+
}
|
|
93
|
+
return "";
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The CHIRAL PULSE dock entry.
|
|
97
|
+
* @param props - runtime seat (useSession, useProjection) plus the locale seat.
|
|
98
|
+
* @returns the monitor strip.
|
|
99
|
+
*/
|
|
100
|
+
function HeartLine({ useSession, useProjection, t }) {
|
|
101
|
+
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
|
+
}));
|
|
109
|
+
const steps = stats?.steps ?? 0;
|
|
110
|
+
const bpmRef = (0, react.useRef)(BPM_FLOOR);
|
|
111
|
+
const targetRef = (0, react.useRef)(BPM_FLOOR);
|
|
112
|
+
const samplesRef = (0, react.useRef)([]);
|
|
113
|
+
const lastStepsRef = (0, react.useRef)(steps);
|
|
114
|
+
const liveRef = (0, react.useRef)(live);
|
|
115
|
+
liveRef.current = live;
|
|
116
|
+
const modeRef = (0, react.useRef)("idle");
|
|
117
|
+
const [ui, setUi] = (0, react.useState)({
|
|
118
|
+
bpm: BPM_FLOOR,
|
|
119
|
+
elapsed: 0,
|
|
120
|
+
mode: "idle"
|
|
121
|
+
});
|
|
122
|
+
(0, react.useEffect)(() => {
|
|
123
|
+
if (steps !== lastStepsRef.current) {
|
|
124
|
+
lastStepsRef.current = steps;
|
|
125
|
+
samplesRef.current.push({
|
|
126
|
+
t: performance.now(),
|
|
127
|
+
steps
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}, [steps]);
|
|
131
|
+
(0, react.useEffect)(() => {
|
|
132
|
+
const id = window.setInterval(() => {
|
|
133
|
+
const now = performance.now();
|
|
134
|
+
const samples = samplesRef.current;
|
|
135
|
+
while (samples.length > 0 && now - samples[0].t > ACTIVITY_WINDOW_MS) samples.shift();
|
|
136
|
+
const first = samples[0];
|
|
137
|
+
const span = first === void 0 ? 0 : now - first.t;
|
|
138
|
+
const delta = first === void 0 ? 0 : lastStepsRef.current - first.steps;
|
|
139
|
+
const perMinute = span > 0 ? delta / span * 6e4 : 0;
|
|
140
|
+
const base = Math.min(BPM_CEIL, Math.max(BPM_FLOOR, 42 + perMinute * 6));
|
|
141
|
+
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";
|
|
145
|
+
modeRef.current = mode;
|
|
146
|
+
setUi((current) => ({
|
|
147
|
+
bpm: Math.round(bpmRef.current),
|
|
148
|
+
elapsed: current.elapsed + 1,
|
|
149
|
+
mode
|
|
150
|
+
}));
|
|
151
|
+
}, 1e3);
|
|
152
|
+
return () => {
|
|
153
|
+
window.clearInterval(id);
|
|
154
|
+
};
|
|
155
|
+
}, []);
|
|
156
|
+
const canvasRef = (0, react.useRef)(null);
|
|
157
|
+
const ctxRef = (0, react.useRef)(null);
|
|
158
|
+
const widthRef = (0, react.useRef)(640);
|
|
159
|
+
const dprRef = (0, react.useRef)(1);
|
|
160
|
+
(0, react.useEffect)(() => {
|
|
161
|
+
const canvas = canvasRef.current;
|
|
162
|
+
if (canvas === null) return;
|
|
163
|
+
const ctx = canvas.getContext("2d");
|
|
164
|
+
if (ctx === null) return;
|
|
165
|
+
ctxRef.current = ctx;
|
|
166
|
+
const applySize = () => {
|
|
167
|
+
const dpr = window.devicePixelRatio || 1;
|
|
168
|
+
dprRef.current = dpr;
|
|
169
|
+
canvas.width = Math.max(120, Math.round(widthRef.current * dpr));
|
|
170
|
+
canvas.height = Math.round(ECG_HEIGHT * dpr);
|
|
171
|
+
};
|
|
172
|
+
applySize();
|
|
173
|
+
const observer = new ResizeObserver((entries) => {
|
|
174
|
+
const width = Math.max(120, Math.round(entries[0]?.contentRect.width ?? 640));
|
|
175
|
+
if (width !== widthRef.current) {
|
|
176
|
+
widthRef.current = width;
|
|
177
|
+
applySize();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
observer.observe(canvas);
|
|
181
|
+
let lastBpmFrame = 0;
|
|
182
|
+
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);
|
|
187
|
+
const c = canvasRef.current;
|
|
188
|
+
const g = ctxRef.current;
|
|
189
|
+
if (c === null || g === null) return;
|
|
190
|
+
const w = widthRef.current;
|
|
191
|
+
const h = ECG_HEIGHT;
|
|
192
|
+
const dpr = dprRef.current;
|
|
193
|
+
g.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
194
|
+
g.clearRect(0, 0, w, h);
|
|
195
|
+
const tNow = now / 1e3;
|
|
196
|
+
const secondsPerPixel = ECG_WINDOW / w;
|
|
197
|
+
const mid = h / 2;
|
|
198
|
+
const amp = h * .44;
|
|
199
|
+
const wander = .05 * Math.sin(tNow * .6) + .035 * Math.sin(tNow * 1.7 + 1.3);
|
|
200
|
+
const bpm = bpmRef.current;
|
|
201
|
+
const yAt = (x) => {
|
|
202
|
+
return mid - (ecgValue(((tNow - (w - x) * secondsPerPixel) * (bpm / 60) % 1 + 1) % 1) + wander) * amp;
|
|
203
|
+
};
|
|
204
|
+
g.beginPath();
|
|
205
|
+
for (let x = 0; x <= w; x += 1) {
|
|
206
|
+
const y = yAt(x);
|
|
207
|
+
if (x === 0) g.moveTo(x + 3, y);
|
|
208
|
+
else g.lineTo(x + 3, y);
|
|
209
|
+
}
|
|
210
|
+
g.strokeStyle = "rgba(111, 219, 226, 0.18)";
|
|
211
|
+
g.lineWidth = 1;
|
|
212
|
+
g.stroke();
|
|
213
|
+
g.beginPath();
|
|
214
|
+
for (let x = 0; x <= w; x += 1) {
|
|
215
|
+
const y = yAt(x);
|
|
216
|
+
if (x === 0) g.moveTo(x, y);
|
|
217
|
+
else g.lineTo(x, y);
|
|
218
|
+
}
|
|
219
|
+
g.strokeStyle = MODE_COLOR[modeRef.current];
|
|
220
|
+
g.lineWidth = 1.4;
|
|
221
|
+
g.lineJoin = "round";
|
|
222
|
+
g.lineCap = "round";
|
|
223
|
+
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();
|
|
234
|
+
};
|
|
235
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
236
|
+
paint(performance.now());
|
|
237
|
+
return () => {
|
|
238
|
+
observer.disconnect();
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
let raf = 0;
|
|
242
|
+
const loop = (now) => {
|
|
243
|
+
raf = requestAnimationFrame(loop);
|
|
244
|
+
paint(now);
|
|
245
|
+
};
|
|
246
|
+
raf = requestAnimationFrame(loop);
|
|
247
|
+
return () => {
|
|
248
|
+
observer.disconnect();
|
|
249
|
+
cancelAnimationFrame(raf);
|
|
250
|
+
};
|
|
251
|
+
}, []);
|
|
252
|
+
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);
|
|
254
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
255
|
+
className: "cp-line",
|
|
256
|
+
role: "group",
|
|
257
|
+
"aria-label": t("line.aria"),
|
|
258
|
+
"data-chiral-pulse": true,
|
|
259
|
+
"data-mode": ui.mode,
|
|
260
|
+
children: [
|
|
261
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
262
|
+
className: "cp-lineBpm",
|
|
263
|
+
children: [ui.bpm, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
264
|
+
className: "cp-lineBpmUnit",
|
|
265
|
+
children: t("bpm.unit")
|
|
266
|
+
})]
|
|
267
|
+
}),
|
|
268
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("canvas", {
|
|
269
|
+
ref: canvasRef,
|
|
270
|
+
className: "cp-lineEcg",
|
|
271
|
+
"aria-hidden": true
|
|
272
|
+
}),
|
|
273
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
274
|
+
className: "cp-lineReadout",
|
|
275
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
276
|
+
className: "cp-lineStatus",
|
|
277
|
+
title: status,
|
|
278
|
+
children: status
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
]
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/client/locales.ts
|
|
286
|
+
/**
|
|
287
|
+
* CHIRAL PULSE — dictionary namespace.
|
|
288
|
+
*
|
|
289
|
+
* The DS monitor idiom stays English in both locales (it is part of the
|
|
290
|
+
* aesthetic: "LINK STABLE", "TIME TO COMPLETION"); the zh side translates
|
|
291
|
+
* the labels a user actually reads.
|
|
292
|
+
*/
|
|
293
|
+
/** Dictionary namespace owned by this plugin. */
|
|
294
|
+
const NS = "chiral";
|
|
295
|
+
/** English dictionary. */
|
|
296
|
+
const en = {
|
|
297
|
+
"line.aria": "BB vital-signs strip — CHIRAL PULSE",
|
|
298
|
+
"bpm.unit": "BPM",
|
|
299
|
+
"status.stable": "LINK STABLE",
|
|
300
|
+
"status.bonded": "BB BONDED",
|
|
301
|
+
"status.chiral": "CHIRAL DENSITY: NOMINAL",
|
|
302
|
+
"status.doom": "DOOMS LEVEL: 0",
|
|
303
|
+
"status.keep": "KEEP ON KEEPING ON",
|
|
304
|
+
"status.voidout": "NO VOIDOUT DETECTED",
|
|
305
|
+
"status.odradek": "ODRADEK SYNC: OK"
|
|
306
|
+
};
|
|
307
|
+
/** Chinese dictionary. */
|
|
308
|
+
const zh = {
|
|
309
|
+
"line.aria": "BB 生命体征走纸 — CHIRAL PULSE 手性脉冲",
|
|
310
|
+
"bpm.unit": "次/分",
|
|
311
|
+
"status.stable": "链路稳定",
|
|
312
|
+
"status.bonded": "BB 连接完成",
|
|
313
|
+
"status.chiral": "手性密度:正常",
|
|
314
|
+
"status.doom": "DOOMS 等级:0",
|
|
315
|
+
"status.keep": "继续前进 · KEEP ON KEEPING ON",
|
|
316
|
+
"status.voidout": "未检测到虚爆",
|
|
317
|
+
"status.odradek": "奥卓克同步:正常"
|
|
318
|
+
};
|
|
319
|
+
//#endregion
|
|
320
|
+
//#region src/client/style.ts
|
|
321
|
+
/**
|
|
322
|
+
* CHIRAL PULSE — the Death Stranding sheet, two layers.
|
|
323
|
+
*
|
|
324
|
+
* LAYER 1 — the global skin. The whole app paints from `--dsw-*` variables
|
|
325
|
+
* (ui-theme's design platform: alias tokens reference static tokens, so
|
|
326
|
+
* remapping the palette re-skins every component without touching its
|
|
327
|
+
* structure). This sheet FORCES the DS look under BOTH theme modes: a deep
|
|
328
|
+
* blue-black machine body, cold blue-grey hairlines, amber reserved for
|
|
329
|
+
* emphasis (the heartbeat waveform, hover blooms) — and the deepseek brand
|
|
330
|
+
* blues are left untouched, so the whale mark stays DeepSeek blue.
|
|
331
|
+
*
|
|
332
|
+
* LAYER 2 — the atmosphere. A fixed full-viewport CRT scanline weave, a
|
|
333
|
+
* faint chiral lattice, and a vignette, all pointer-transparent. Plus the
|
|
334
|
+
* BB vital-signs strip that docks under the composer stats: a 26px monitor
|
|
335
|
+
* paper feed whose scrolling ECG is the hero, with the BPM and status read.
|
|
336
|
+
*
|
|
337
|
+
* Every rule is scoped under `.cp-*` (except the token remap, which must
|
|
338
|
+
* target `body`), rides one owned <style data-plugin> tag, and the loader
|
|
339
|
+
* removes it on unload.
|
|
340
|
+
*/
|
|
341
|
+
const CHIRAL_CSS = `
|
|
342
|
+
/* ────────────────────────────────────────────────────────────────────────
|
|
343
|
+
LAYER 1 · global DS skin — dark blue-black, both theme modes
|
|
344
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
345
|
+
|
|
346
|
+
/* Alias-level remap: independent of the static scale's role flip between
|
|
347
|
+
themes, so the DS look is identical under light and dark settings. */
|
|
348
|
+
body[data-ds-dark-theme],
|
|
349
|
+
body:not([data-ds-dark-theme]) {
|
|
350
|
+
/* machine body — blue-grey with air, not a black void */
|
|
351
|
+
--dsw-alias-bg-base: rgb(13, 17, 23);
|
|
352
|
+
--dsw-alias-bg-layer-1: rgb(17, 22, 29);
|
|
353
|
+
--dsw-alias-bg-layer-2: rgb(21, 27, 35);
|
|
354
|
+
--dsw-alias-bg-layer-3: rgb(26, 33, 42);
|
|
355
|
+
--dsw-alias-bg-overlay: rgb(31, 40, 51);
|
|
356
|
+
--dsw-alias-bg-mask-1: rgba(0, 0, 0, 0.5);
|
|
357
|
+
--dsw-alias-bg-mask-2: rgba(0, 0, 0, 0.22);
|
|
358
|
+
--dsw-alias-bg-mask-3: rgba(0, 0, 0, 0.5);
|
|
359
|
+
--dsw-alias-bg-mask-drop: rgba(10, 14, 19, 0.72);
|
|
360
|
+
--dsw-alias-bg-skeleton: rgba(150, 170, 200, 0.07);
|
|
361
|
+
|
|
362
|
+
/* text: cold blue-grey */
|
|
363
|
+
--dsw-alias-label-primary: rgb(235, 240, 246);
|
|
364
|
+
--dsw-alias-label-secondary: rgb(178, 190, 205);
|
|
365
|
+
--dsw-alias-label-tertiary: rgb(140, 153, 170);
|
|
366
|
+
--dsw-alias-label-caption: rgb(108, 122, 141);
|
|
367
|
+
--dsw-alias-label-dimmed: rgb(94, 108, 127);
|
|
368
|
+
--dsw-alias-label-primary-dimmed: rgb(205, 213, 222);
|
|
369
|
+
--dsw-alias-label-primary-inverted: rgb(235, 240, 246);
|
|
370
|
+
--dsw-alias-label-primary-foreground: rgb(13, 17, 23);
|
|
371
|
+
--dsw-alias-brand-text: rgb(235, 240, 246);
|
|
372
|
+
--dsw-alias-brand-primary: rgb(103, 158, 254);
|
|
373
|
+
|
|
374
|
+
/* hairlines: cold blue, readable against the body */
|
|
375
|
+
--dsw-alias-border-l1: rgba(140, 170, 215, 0.15);
|
|
376
|
+
--dsw-alias-border-l2: rgba(140, 170, 215, 0.24);
|
|
377
|
+
--dsw-alias-border-l2-darkmode-thin: rgba(140, 170, 215, 0.19);
|
|
378
|
+
--dsw-alias-border-l3: rgba(140, 170, 215, 0.34);
|
|
379
|
+
--dsw-alias-border-l4: rgba(140, 170, 215, 0.46);
|
|
380
|
+
--dsw-alias-border-inverted: rgba(255, 255, 255, 0.09);
|
|
381
|
+
--dsw-alias-border-inverted2: rgba(255, 255, 255, 0.11);
|
|
382
|
+
|
|
383
|
+
/* hovers: amber bloom, kept subtle */
|
|
384
|
+
--dsw-alias-interactive-bg-hover: rgba(255, 180, 84, 0.08);
|
|
385
|
+
--dsw-alias-interactive-bg-active: rgba(255, 180, 84, 0.12);
|
|
386
|
+
--dsw-alias-interactive-bg-hover-accent: rgba(255, 180, 84, 0.14);
|
|
387
|
+
--dsw-alias-interactive-bg-hover-solid: rgb(22, 29, 38);
|
|
388
|
+
--dsw-alias-interactive-bg-hover-danger: rgba(242, 90, 90, 0.14);
|
|
389
|
+
|
|
390
|
+
/* buttons: brand blue stays the primary action */
|
|
391
|
+
--dsw-alias-button-primary-dimmed: rgb(30, 40, 53);
|
|
392
|
+
--dsw-alias-button-primary-hover: rgb(124, 172, 255);
|
|
393
|
+
--dsw-alias-button-ghost-active-fill: rgb(22, 29, 38);
|
|
394
|
+
--dsw-alias-button-ghost-active-hover: rgb(27, 35, 46);
|
|
395
|
+
--dsw-alias-button-ghost-active-border: rgb(140, 170, 215);
|
|
396
|
+
--dsw-alias-button-floating-fill: rgb(19, 25, 33);
|
|
397
|
+
--dsw-alias-button-floating-hover: rgb(24, 31, 41);
|
|
398
|
+
--dsw-alias-button-elevated-fill: rgb(22, 29, 38);
|
|
399
|
+
--dsw-alias-button-contrast-fill: rgb(205, 213, 222);
|
|
400
|
+
--dsw-alias-button-tool-bar-fill: rgba(140, 170, 215, 0.24);
|
|
401
|
+
--dsw-alias-button-tool-bar-fill-invisible: rgba(140, 170, 215, 0.13);
|
|
402
|
+
--dsw-alias-button-tool-bar-hover: rgba(140, 170, 215, 0.32);
|
|
403
|
+
|
|
404
|
+
/* surfaces */
|
|
405
|
+
--dsw-specific-sidebar-fill: rgb(9, 12, 17);
|
|
406
|
+
--dsw-specific-sidebar-nav-item-active: rgb(20, 27, 36);
|
|
407
|
+
--dsw-specific-sidebar-nav-item-active-accent: rgb(27, 36, 48);
|
|
408
|
+
--dsw-specific-sidebar-nav-item-hover: rgb(15, 20, 28);
|
|
409
|
+
--dsw-specific-bubble: rgb(18, 24, 32);
|
|
410
|
+
--dsw-specific-bubble-highlight: rgb(24, 32, 42);
|
|
411
|
+
--dsw-specific-input-major: rgb(15, 20, 27);
|
|
412
|
+
--dsw-specific-login-input: rgb(12, 16, 22);
|
|
413
|
+
--dsw-specific-menu: rgb(21, 27, 35);
|
|
414
|
+
--dsw-specific-selector: rgb(20, 26, 34);
|
|
415
|
+
--dsw-specific-tip: rgb(16, 21, 28);
|
|
416
|
+
--dsw-alias-markdown-code-block: rgb(10, 14, 19);
|
|
417
|
+
--dsw-alias-markdown-code-block-banner: rgb(13, 17, 23);
|
|
418
|
+
--dsw-alias-markdown-inline-code: rgb(18, 24, 32);
|
|
419
|
+
--dsw-alias-markdown-code-segment-selected: rgb(16, 21, 28);
|
|
420
|
+
--dsw-alias-markdown-code-segment-unselected: rgb(12, 16, 22);
|
|
421
|
+
--dsw-alias-markdown-placeholder: rgb(16, 21, 28);
|
|
422
|
+
--dsw-alias-markdown-tag: rgb(18, 24, 32);
|
|
423
|
+
--dsw-alias-markdown-citation: rgb(22, 29, 38);
|
|
424
|
+
|
|
425
|
+
/* floats */
|
|
426
|
+
--dsw-alias-toast-bg: rgb(22, 29, 38);
|
|
427
|
+
--dsw-alias-tooltip-bg: rgb(20, 26, 34);
|
|
428
|
+
--dsw-alias-scrollbar-bg-l1: rgb(13, 18, 25);
|
|
429
|
+
--dsw-alias-scrollbar-bg-l2: rgb(17, 23, 31);
|
|
430
|
+
--dsw-alias-scrollbar-hover-l1: rgb(34, 44, 58);
|
|
431
|
+
--dsw-alias-scrollbar-hover-l2: rgb(42, 54, 70);
|
|
432
|
+
|
|
433
|
+
/* status: amber stays the warn/emphasis hue; success leans chiral cyan */
|
|
434
|
+
--dsw-alias-state-warn-primary: rgb(245, 158, 11);
|
|
435
|
+
--dsw-alias-state-warn-secondary: rgb(247, 173, 49);
|
|
436
|
+
--dsw-alias-state-warn-label: rgb(221, 134, 41);
|
|
437
|
+
--dsw-alias-state-warn-tertiary: rgb(39, 36, 31);
|
|
438
|
+
--dsw-alias-state-success-primary: rgb(52, 205, 168);
|
|
439
|
+
--dsw-alias-state-success-secondary: rgb(94, 222, 189);
|
|
440
|
+
--dsw-alias-state-success-tertiary: rgb(12, 28, 24);
|
|
441
|
+
--dsw-static-green-400: rgb(94, 222, 189);
|
|
442
|
+
--dsw-static-green-500: rgb(52, 205, 168);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/* Focus ring: amber, the DS highlight color. */
|
|
446
|
+
:focus-visible {
|
|
447
|
+
outline: 1px solid rgba(255, 180, 84, 0.65) !important;
|
|
448
|
+
outline-offset: 2px;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/* Ambient bloom behind the app. */
|
|
452
|
+
body {
|
|
453
|
+
background-image:
|
|
454
|
+
radial-gradient(1100px 620px at 12% -8%, rgba(111, 219, 226, 0.04), transparent 60%),
|
|
455
|
+
radial-gradient(900px 560px at 108% 112%, rgba(103, 158, 254, 0.05), transparent 60%);
|
|
456
|
+
background-attachment: fixed;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/* ────────────────────────────────────────────────────────────────────────
|
|
460
|
+
LAYER 2 · atmosphere overlays (injected as fixed elements)
|
|
461
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
462
|
+
.cp-atmo {
|
|
463
|
+
position: fixed;
|
|
464
|
+
inset: 0;
|
|
465
|
+
pointer-events: none;
|
|
466
|
+
z-index: 2147483000;
|
|
467
|
+
}
|
|
468
|
+
.cp-atmo-scanlines {
|
|
469
|
+
background: repeating-linear-gradient(
|
|
470
|
+
0deg,
|
|
471
|
+
rgba(255, 255, 255, 0.024) 0 1px,
|
|
472
|
+
transparent 1px 3px
|
|
473
|
+
);
|
|
474
|
+
mix-blend-mode: overlay;
|
|
475
|
+
}
|
|
476
|
+
.cp-atmo-lattice {
|
|
477
|
+
opacity: 0.55;
|
|
478
|
+
background:
|
|
479
|
+
repeating-linear-gradient(60deg, transparent 0 17px, rgba(103, 158, 254, 0.03) 17px 18px),
|
|
480
|
+
repeating-linear-gradient(120deg, transparent 0 17px, rgba(111, 219, 226, 0.028) 17px 18px);
|
|
481
|
+
}
|
|
482
|
+
.cp-atmo-vignette {
|
|
483
|
+
background: radial-gradient(120% 100% at 50% 40%, transparent 55%, rgba(0, 0, 0, 0.24) 100%);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/* ────────────────────────────────────────────────────────────────────────
|
|
487
|
+
BB vital-signs strip · the heartbeat paper feed
|
|
488
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
489
|
+
.cp-line {
|
|
490
|
+
--cp-amber: #ffb454;
|
|
491
|
+
--cp-amber-bright: #ffd9a0;
|
|
492
|
+
--cp-cyan: #6fdbe2;
|
|
493
|
+
--cp-dim: #64727f;
|
|
494
|
+
display: flex;
|
|
495
|
+
align-items: center;
|
|
496
|
+
gap: 12px;
|
|
497
|
+
height: 26px;
|
|
498
|
+
margin: 3px 0 4px;
|
|
499
|
+
padding: 0 10px;
|
|
500
|
+
border: 1px solid rgba(140, 170, 215, 0.26);
|
|
501
|
+
border-radius: 0;
|
|
502
|
+
background:
|
|
503
|
+
linear-gradient(115deg, rgba(140, 190, 255, 0.06) 0%, transparent 30%),
|
|
504
|
+
linear-gradient(180deg, rgba(14, 19, 26, 0.92), rgba(9, 13, 18, 0.94));
|
|
505
|
+
box-shadow:
|
|
506
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.05),
|
|
507
|
+
inset 0 0 16px rgba(103, 158, 254, 0.06);
|
|
508
|
+
color: #c9d3dc;
|
|
509
|
+
font-family: ui-monospace, "Cascadia Mono", "JetBrains Mono", Consolas, "Courier New", monospace;
|
|
510
|
+
overflow: hidden;
|
|
511
|
+
clip-path: polygon(
|
|
512
|
+
7px 0,
|
|
513
|
+
100% 0,
|
|
514
|
+
100% calc(100% - 7px),
|
|
515
|
+
calc(100% - 7px) 100%,
|
|
516
|
+
0 100%,
|
|
517
|
+
0 7px
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.cp-lineBpm {
|
|
522
|
+
flex: none;
|
|
523
|
+
min-width: 42px;
|
|
524
|
+
font-size: 11px;
|
|
525
|
+
line-height: 1;
|
|
526
|
+
letter-spacing: 0.5px;
|
|
527
|
+
color: var(--cp-amber-bright);
|
|
528
|
+
text-shadow: 0 0 10px rgba(255, 180, 84, 0.5);
|
|
529
|
+
font-variant-numeric: tabular-nums;
|
|
530
|
+
white-space: nowrap;
|
|
531
|
+
}
|
|
532
|
+
.cp-lineBpmUnit {
|
|
533
|
+
font-size: 7px;
|
|
534
|
+
letter-spacing: 1px;
|
|
535
|
+
color: var(--cp-dim);
|
|
536
|
+
margin-left: 2px;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.cp-lineEcg {
|
|
540
|
+
flex: 1 1 auto;
|
|
541
|
+
min-width: 100px;
|
|
542
|
+
height: 22px;
|
|
543
|
+
display: block;
|
|
544
|
+
border-radius: 0;
|
|
545
|
+
border: 1px solid rgba(140, 170, 215, 0.16);
|
|
546
|
+
/* Static paper grid lives in CSS; the canvas above it only paints the trace. */
|
|
547
|
+
background:
|
|
548
|
+
repeating-linear-gradient(0deg, rgba(140, 170, 215, 0.07) 0 1px, transparent 1px 11px),
|
|
549
|
+
repeating-linear-gradient(90deg, rgba(140, 170, 215, 0.06) 0 1px, transparent 1px 11px),
|
|
550
|
+
rgba(7, 10, 15, 0.55);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.cp-lineReadout {
|
|
554
|
+
flex: none;
|
|
555
|
+
width: 132px;
|
|
556
|
+
display: flex;
|
|
557
|
+
align-items: center;
|
|
558
|
+
justify-content: flex-end;
|
|
559
|
+
overflow: hidden;
|
|
560
|
+
}
|
|
561
|
+
.cp-lineStatus {
|
|
562
|
+
font-size: 8px;
|
|
563
|
+
letter-spacing: 1.8px;
|
|
564
|
+
text-transform: uppercase;
|
|
565
|
+
color: var(--cp-cyan);
|
|
566
|
+
white-space: nowrap;
|
|
567
|
+
overflow: hidden;
|
|
568
|
+
text-overflow: ellipsis;
|
|
569
|
+
max-width: 100%;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
@media (prefers-reduced-motion: reduce) {
|
|
573
|
+
.cp-lineEcg {
|
|
574
|
+
opacity: 0.9;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/* ────────────────────────────────────────────────────────────────────────
|
|
579
|
+
ECG paper grid + activity-mode color coupling
|
|
580
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
581
|
+
.cp-line[data-mode="think"] .cp-lineBpm {
|
|
582
|
+
color: #7fe3e8;
|
|
583
|
+
text-shadow: 0 0 10px rgba(111, 219, 226, 0.55);
|
|
584
|
+
}
|
|
585
|
+
.cp-line[data-mode="tool"] .cp-lineBpm {
|
|
586
|
+
color: #ff9b7a;
|
|
587
|
+
text-shadow: 0 0 10px rgba(255, 122, 77, 0.6);
|
|
588
|
+
}
|
|
589
|
+
.cp-line[data-mode="run"] .cp-lineBpm {
|
|
590
|
+
color: #ffd9a0;
|
|
591
|
+
}
|
|
592
|
+
.cp-line[data-mode="think"] .cp-lineStatus,
|
|
593
|
+
.cp-line[data-mode="tool"] .cp-lineStatus {
|
|
594
|
+
color: #9fe8ec;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/* ────────────────────────────────────────────────────────────────────────
|
|
598
|
+
Message-flow dressing — DS glyphs on each node kind
|
|
599
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
600
|
+
[data-chat-flow-kind] {
|
|
601
|
+
position: relative;
|
|
602
|
+
}
|
|
603
|
+
[data-chat-flow-kind="assistant"] {
|
|
604
|
+
padding-left: 18px;
|
|
605
|
+
}
|
|
606
|
+
[data-chat-flow-kind="assistant"]::before {
|
|
607
|
+
content: "✦";
|
|
608
|
+
position: absolute;
|
|
609
|
+
left: 4px;
|
|
610
|
+
top: 12px;
|
|
611
|
+
color: rgba(255, 180, 84, 0.85);
|
|
612
|
+
font-size: 11px;
|
|
613
|
+
line-height: 1;
|
|
614
|
+
text-shadow: 0 0 8px rgba(255, 180, 84, 0.6);
|
|
615
|
+
}
|
|
616
|
+
[data-chat-flow-kind="assistant"]::after {
|
|
617
|
+
content: "";
|
|
618
|
+
position: absolute;
|
|
619
|
+
left: 0;
|
|
620
|
+
top: 0;
|
|
621
|
+
bottom: 0;
|
|
622
|
+
width: 1px;
|
|
623
|
+
background: linear-gradient(180deg, transparent, rgba(255, 180, 84, 0.35), transparent);
|
|
624
|
+
}
|
|
625
|
+
[data-chat-flow-kind="user"],
|
|
626
|
+
[data-chat-flow-kind="steering"] {
|
|
627
|
+
padding-right: 18px;
|
|
628
|
+
}
|
|
629
|
+
[data-chat-flow-kind="user"]::before,
|
|
630
|
+
[data-chat-flow-kind="steering"]::before {
|
|
631
|
+
content: "▸▸";
|
|
632
|
+
position: absolute;
|
|
633
|
+
right: 2px;
|
|
634
|
+
top: 4px;
|
|
635
|
+
color: rgba(120, 150, 195, 0.75);
|
|
636
|
+
font-size: 10px;
|
|
637
|
+
line-height: 1;
|
|
638
|
+
letter-spacing: -1px;
|
|
639
|
+
}
|
|
640
|
+
[data-chat-flow-kind="context"] {
|
|
641
|
+
padding-left: 16px;
|
|
642
|
+
}
|
|
643
|
+
[data-chat-flow-kind="context"]::before {
|
|
644
|
+
content: "⇢";
|
|
645
|
+
position: absolute;
|
|
646
|
+
left: 2px;
|
|
647
|
+
top: 12px;
|
|
648
|
+
color: rgba(111, 219, 226, 0.7);
|
|
649
|
+
font-size: 11px;
|
|
650
|
+
line-height: 1;
|
|
651
|
+
}
|
|
652
|
+
[data-variant="think"] {
|
|
653
|
+
border-left: 2px solid rgba(111, 219, 226, 0.35);
|
|
654
|
+
padding-left: 10px;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/* ────────────────────────────────────────────────────────────────────────
|
|
658
|
+
Tool-card chassis — the ui-primitives block family gets a DS case
|
|
659
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
660
|
+
[data-tool],
|
|
661
|
+
[data-search],
|
|
662
|
+
[data-read],
|
|
663
|
+
[data-web],
|
|
664
|
+
[data-diff],
|
|
665
|
+
[data-terminal],
|
|
666
|
+
[data-context-injection-body] {
|
|
667
|
+
border: 1px solid rgba(140, 170, 215, 0.24) !important;
|
|
668
|
+
border-radius: 0 !important;
|
|
669
|
+
background:
|
|
670
|
+
linear-gradient(115deg, rgba(140, 190, 255, 0.06) 0%, transparent 30%),
|
|
671
|
+
linear-gradient(180deg, rgba(15, 20, 27, 0.88), rgba(9, 13, 18, 0.92)) !important;
|
|
672
|
+
box-shadow:
|
|
673
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.05),
|
|
674
|
+
inset 0 0 18px rgba(103, 158, 254, 0.05);
|
|
675
|
+
position: relative;
|
|
676
|
+
clip-path: polygon(
|
|
677
|
+
7px 0,
|
|
678
|
+
100% 0,
|
|
679
|
+
100% calc(100% - 7px),
|
|
680
|
+
calc(100% - 7px) 100%,
|
|
681
|
+
0 100%,
|
|
682
|
+
0 7px
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
[data-terminal]::before {
|
|
686
|
+
content: "❯_";
|
|
687
|
+
position: absolute;
|
|
688
|
+
right: 8px;
|
|
689
|
+
top: 6px;
|
|
690
|
+
color: rgba(111, 219, 226, 0.35);
|
|
691
|
+
font-size: 10px;
|
|
692
|
+
font-family: ui-monospace, Consolas, monospace;
|
|
693
|
+
}
|
|
694
|
+
[data-read]::before {
|
|
695
|
+
content: "▤";
|
|
696
|
+
position: absolute;
|
|
697
|
+
right: 8px;
|
|
698
|
+
top: 6px;
|
|
699
|
+
color: rgba(120, 150, 195, 0.4);
|
|
700
|
+
font-size: 11px;
|
|
701
|
+
}
|
|
702
|
+
[data-search]::before {
|
|
703
|
+
content: "⌕";
|
|
704
|
+
position: absolute;
|
|
705
|
+
right: 8px;
|
|
706
|
+
top: 5px;
|
|
707
|
+
color: rgba(255, 180, 84, 0.4);
|
|
708
|
+
font-size: 13px;
|
|
709
|
+
}
|
|
710
|
+
[data-web]::before {
|
|
711
|
+
content: "⌖";
|
|
712
|
+
position: absolute;
|
|
713
|
+
right: 8px;
|
|
714
|
+
top: 5px;
|
|
715
|
+
color: rgba(111, 219, 226, 0.4);
|
|
716
|
+
font-size: 12px;
|
|
717
|
+
}
|
|
718
|
+
[data-diff]::before {
|
|
719
|
+
content: "⇄";
|
|
720
|
+
position: absolute;
|
|
721
|
+
right: 8px;
|
|
722
|
+
top: 5px;
|
|
723
|
+
color: rgba(120, 150, 195, 0.4);
|
|
724
|
+
font-size: 12px;
|
|
725
|
+
}
|
|
726
|
+
[data-tool]::before {
|
|
727
|
+
content: "⚙";
|
|
728
|
+
position: absolute;
|
|
729
|
+
right: 8px;
|
|
730
|
+
top: 5px;
|
|
731
|
+
color: rgba(255, 180, 84, 0.4);
|
|
732
|
+
font-size: 11px;
|
|
733
|
+
}
|
|
734
|
+
[data-context-injection-body]::before {
|
|
735
|
+
content: "⇢";
|
|
736
|
+
position: absolute;
|
|
737
|
+
right: 8px;
|
|
738
|
+
top: 5px;
|
|
739
|
+
color: rgba(111, 219, 226, 0.4);
|
|
740
|
+
font-size: 11px;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/* ────────────────────────────────────────────────────────────────────────
|
|
744
|
+
Composer details
|
|
745
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
746
|
+
[data-composer-seat] textarea {
|
|
747
|
+
caret-color: #ffb454;
|
|
748
|
+
letter-spacing: 0.01em;
|
|
749
|
+
}
|
|
750
|
+
[data-composer-seat] textarea:focus {
|
|
751
|
+
caret-color: #ffd9a0;
|
|
752
|
+
}
|
|
753
|
+
[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
|
+
border-radius: 0;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/* ────────────────────────────────────────────────────────────────────────
|
|
762
|
+
Dialogs, menus, tooltips, toasts — the floating DS surfaces
|
|
763
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
764
|
+
[role="dialog"] {
|
|
765
|
+
border: 1px solid rgba(255, 180, 84, 0.32) !important;
|
|
766
|
+
border-radius: 0 !important;
|
|
767
|
+
background: linear-gradient(180deg, rgba(15, 20, 27, 0.98), rgba(10, 14, 19, 0.99)) !important;
|
|
768
|
+
box-shadow:
|
|
769
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.07),
|
|
770
|
+
inset 0 0 26px rgba(103, 158, 254, 0.06) !important;
|
|
771
|
+
}
|
|
772
|
+
[role="menu"] {
|
|
773
|
+
border: 1px solid rgba(140, 170, 215, 0.3) !important;
|
|
774
|
+
border-radius: 0 !important;
|
|
775
|
+
background: rgba(13, 18, 25, 0.97) !important;
|
|
776
|
+
}
|
|
777
|
+
[role="menuitem"]:hover {
|
|
778
|
+
background: rgba(255, 180, 84, 0.08) !important;
|
|
779
|
+
}
|
|
780
|
+
[role="tooltip"] {
|
|
781
|
+
border: 1px solid rgba(140, 170, 215, 0.32) !important;
|
|
782
|
+
border-radius: 0 !important;
|
|
783
|
+
background: rgba(15, 20, 27, 0.97) !important;
|
|
784
|
+
}
|
|
785
|
+
[role="alert"] {
|
|
786
|
+
border: 1px solid rgba(255, 180, 84, 0.38) !important;
|
|
787
|
+
border-radius: 0 !important;
|
|
788
|
+
background: rgba(15, 20, 27, 0.97) !important;
|
|
789
|
+
clip-path: polygon(
|
|
790
|
+
8px 0,
|
|
791
|
+
100% 0,
|
|
792
|
+
100% calc(100% - 8px),
|
|
793
|
+
calc(100% - 8px) 100%,
|
|
794
|
+
0 100%,
|
|
795
|
+
0 8px
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/* ────────────────────────────────────────────────────────────────────────
|
|
800
|
+
DS chamfer everywhere else: kill the round-corner language
|
|
801
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
802
|
+
button,
|
|
803
|
+
input,
|
|
804
|
+
textarea,
|
|
805
|
+
select,
|
|
806
|
+
[role="tab"],
|
|
807
|
+
[role="menuitem"],
|
|
808
|
+
[role="treeitem"] {
|
|
809
|
+
border-radius: 0 !important;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
`;
|
|
813
|
+
//#endregion
|
|
814
|
+
//#region src/client/index.ts
|
|
815
|
+
/** Required services: the slot registry and the locale service. */
|
|
816
|
+
const inject = ["slots", "locale"];
|
|
817
|
+
/**
|
|
818
|
+
* Client plugin body: register dictionaries, inject the DS sheet and the
|
|
819
|
+
* atmosphere overlays, and dock the heartbeat strip under the composer.
|
|
820
|
+
* @param ctx - client root context.
|
|
821
|
+
*/
|
|
822
|
+
function apply(ctx) {
|
|
823
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
824
|
+
zh,
|
|
825
|
+
en
|
|
826
|
+
}), "chiral-pulse: dictionaries");
|
|
827
|
+
ctx.effect(() => {
|
|
828
|
+
const tag = document.createElement("style");
|
|
829
|
+
tag.dataset.plugin = "chiral-pulse";
|
|
830
|
+
tag.textContent = CHIRAL_CSS;
|
|
831
|
+
document.head.appendChild(tag);
|
|
832
|
+
return () => {
|
|
833
|
+
tag.remove();
|
|
834
|
+
};
|
|
835
|
+
}, "chiral-pulse: styles");
|
|
836
|
+
ctx.effect(() => {
|
|
837
|
+
const nodes = [
|
|
838
|
+
{
|
|
839
|
+
className: "cp-atmo cp-atmo-scanlines",
|
|
840
|
+
label: "scanlines"
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
className: "cp-atmo cp-atmo-lattice",
|
|
844
|
+
label: "lattice"
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
className: "cp-atmo cp-atmo-vignette",
|
|
848
|
+
label: "vignette"
|
|
849
|
+
}
|
|
850
|
+
].map(({ className }) => {
|
|
851
|
+
const el = document.createElement("div");
|
|
852
|
+
el.className = className;
|
|
853
|
+
el.setAttribute("aria-hidden", "true");
|
|
854
|
+
document.body.appendChild(el);
|
|
855
|
+
return el;
|
|
856
|
+
});
|
|
857
|
+
return () => {
|
|
858
|
+
for (const el of nodes) el.remove();
|
|
859
|
+
};
|
|
860
|
+
}, "chiral-pulse: atmosphere");
|
|
861
|
+
ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
|
|
862
|
+
name: "conversation.input.dock",
|
|
863
|
+
id: "chiral-pulse",
|
|
864
|
+
order: 20,
|
|
865
|
+
locale: NS
|
|
866
|
+
}, HeartLine));
|
|
867
|
+
}
|
|
868
|
+
//#endregion
|
|
869
|
+
exports.HeartLine = HeartLine;
|
|
870
|
+
exports.apply = apply;
|
|
871
|
+
exports.inject = inject;
|
|
872
|
+
return module.exports;
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../src/client/ecg.ts","../src/client/HeartLine.tsx","../src/client/locales.ts","../src/client/style.ts","../src/client/index.ts"],"sourcesContent":["/**\n * CHIRAL PULSE — ECG waveform synthesis.\n *\n * A cardiac cycle is a pure function of beat phase in [0,1): the classic\n * P-QRS-T complex as a sum of wrapped gaussian bumps. The monitor line is a\n * scrolling window over the time axis: the right edge shows the current\n * instant, the window spans `cycles` beats of history. BPM is the phase\n * clock speed, so the whole rhythm accelerates and slows with activity.\n */\n\n/** One wrapped gaussian bump: peak at `center` with `width`, amplitude `amp`. */\nfunction bump(phase: number, center: number, width: number, amp: number): number {\n let d = phase - center\n d -= Math.round(d)\n return amp * Math.exp(-(d * d) / (2 * width * width))\n}\n\n/**\n * Sample one cardiac cycle at beat phase in [0,1). Output range ≈ [-0.35, 1].\n * @param phase - beat phase, any real value (wrapping is internal).\n * @returns the waveform amplitude at that phase.\n */\nexport function ecgValue(phase: number): number {\n return (\n bump(phase, 0.14, 0.030, 0.16) // P wave\n - bump(phase, 0.30, 0.011, 0.26) // Q dip\n + bump(phase, 0.335, 0.012, 1.0) // R spike (wide enough to survive sampling)\n - bump(phase, 0.375, 0.011, 0.34) // S dip\n + bump(phase, 0.52, 0.048, 0.26) // T wave\n + bump(phase, 0.80, 0.012, 0.05) // U ripple\n )\n}\n\n/** One rendered frame of the scrolling monitor line. */\nexport interface EcgFrame {\n /** `x,y` pairs for an SVG polyline `points` attribute, rightmost = now. */\n points: string\n /** The y of the leading (rightmost) sample, in view units. */\n headY: number\n}\n\n/**\n * Build the polyline points for the scrolling window.\n *\n * Hospital monitor semantics: the paper moves at a FIXED speed — every pixel\n * represents a fixed amount of wall-clock time, so the trace scrolls left at\n * a constant rate regardless of heart rate. What changes with BPM is the\n * density of QRS complexes across that fixed window: a fast heart packs more\n * beats onto the screen, a resting one spaces them out.\n *\n * @param nowMs - wall-clock sample time (drives the scan).\n * @param bpm - current heart rate (beat density only; never the scan speed).\n * @param width - view width in user units (x spans 0..width).\n * @param height - view height in user units.\n * @param windowSeconds - how many seconds of signal the window shows.\n * @param step - x sampling step in user units.\n * @returns the frame.\n */\nexport function buildEcgFrame(\n nowMs: number,\n bpm: number,\n width: number,\n height: number,\n windowSeconds: number,\n step = 2,\n): EcgFrame {\n const mid = height / 2\n const amp = height * 0.44\n // Fixed paper speed: seconds per user-unit pixel.\n const secondsPerPixel = windowSeconds / width\n const tNow = nowMs / 1_000\n // Slow baseline wander, like a real monitor: two incommensurate sines keep\n // the resting trace from freezing into a straight flatline.\n const wander = 0.05 * Math.sin(tNow * 0.6)\n + 0.035 * Math.sin(tNow * 1.7 + 1.3)\n const points: string[] = []\n let headY = mid\n for (let x = 0; x <= width; x += step) {\n // x → absolute time: the right edge is \"now\", leftward is the past at a\n // constant rate. Sub-step peak guard: also sample the midpoints so a\n // narrow R spike between two samples still paints at full height.\n const tX = tNow - (width - x) * secondsPerPixel\n const phase = ((tX * (bpm / 60)) % 1 + 1) % 1\n let v = ecgValue(phase)\n if (step > 1) {\n const tMid = tX - secondsPerPixel * step * 0.5\n const phaseMid = ((tMid * (bpm / 60)) % 1 + 1) % 1\n v = Math.max(v, ecgValue(phaseMid))\n }\n const y = mid - (v + wander) * amp\n points.push(`${x.toFixed(1)},${y.toFixed(1)}`)\n if (x === width - step) headY = y\n }\n return { points: points.join(' '), headY }\n}\n\n/**\n * Steady-state BPM for a measured activity rate.\n * @param stepsPerMinute - measured step cadence (steps / minute over a window).\n * @returns target BPM within [42, 150] — a resting BB sleeps at 42, full\n * sprint peaks at 150.\n */\nexport function bpmForActivity(stepsPerMinute: number): number {\n return Math.min(150, Math.max(42, 42 + stepsPerMinute * 6))\n}\n","/**\n * HeartLine — the CHIRAL PULSE monitor strip, docked above the composer\n * (`conversation.input.dock`). A 26px \"monitor paper feed\": the scrolling\n * ECG waveform is the hero, flanked by the BPM read and the status word.\n * No duplicated figures — StatsLine already shows turns/tokens.\n *\n * The pulse is LIVE, not decorative:\n * - `partial` non-null → the model is thinking/generating → +38 BPM\n * - `runningCalls` non-empty → a tool is executing → +52 BPM\n * - `running` (session turn in flight) → +10 BPM\n * - otherwise the 10s step-window activity rate sets the base (~42 idle)\n * The BPM target is smoothed with a lerp; the paper speed stays FIXED and\n * only the beat density changes — hospital monitor semantics.\n *\n * Rendering: a single <canvas> redrawn per rAF at full frame rate. Fixed\n * memory (one canvas the size of the strip), no DOM attribute churn, no\n * string building — the trace is ~width straight segments per frame, which\n * is far cheaper than SVG polyline swaps and cannot stutter from throttling.\n */\nimport { useEffect, useRef, useState } from 'react'\nimport type {\n PropsLocale, PropsRuntime,\n} from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SessionProjectionMap } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only: merges the sessionStats key into SessionProjectionMap.\nimport type {} from '@deepseek-ai/dsh-session-stats/client'\nimport { ecgValue } from './ecg.ts'\nimport type { ChiralKey } from './locales.ts'\nimport { NS } from './locales.ts'\n\n/** Full props: the input-dock runtime seat plus the locale seat. */\nexport type HeartLineProps = PropsRuntime<'conversation.input.dock'> & PropsLocale<typeof NS>\n\n/** Monitor view height, CSS px. */\nconst ECG_HEIGHT = 22\n/** Seconds of signal shown across the window (fixed paper speed). */\nconst ECG_WINDOW = 5\n/** Activity window for the step-rate base, ms. */\nconst ACTIVITY_WINDOW_MS = 10_000\n/** Rotating status lines (locale keys), one every STATUS_ROTATE_S ticks. */\nconst STATUS_KEYS: readonly ChiralKey[] = [\n 'status.stable', 'status.bonded', 'status.chiral', 'status.doom',\n 'status.keep', 'status.voidout', 'status.odradek',\n]\nconst STATUS_ROTATE_S = 4\n/** BPM boost while the model is streaming a partial (thinking/generating). */\nconst BOOST_THINKING = 38\n/** BPM boost while a tool call is running. */\nconst BOOST_TOOL = 52\n/** BPM boost while the session turn is simply in flight. */\nconst BOOST_RUNNING = 10\n/** BPM floor (a resting BB) and ceiling. */\nconst BPM_FLOOR = 42\nconst BPM_CEIL = 150\n\n/** Trace color by activity mode: idle amber, thinking cyan, tool orange, run warm. */\nconst MODE_COLOR = {\n idle: '#ffb454',\n think: '#6fdbe2',\n tool: '#ff7a4d',\n run: '#ffc46b',\n} as const\ntype Mode = keyof typeof MODE_COLOR\n\n/** One activity sample: (time, steps) at a projection update. */\ninterface StepSample {\n t: number\n steps: number\n}\n\n/** Tail of the model's in-flight output: last non-empty text/reasoning block, whitespace-flattened. */\nfunction streamingTail(blocks: readonly { kind: string; text?: string }[]): string {\n for (let i = blocks.length - 1; i >= 0; i -= 1) {\n const block = blocks[i]\n const text = block.text\n if (text !== undefined && text.trim() !== '') {\n return text.replace(/\\s+/g, ' ').trim()\n }\n }\n return ''\n}\n\n/**\n * The CHIRAL PULSE dock entry.\n * @param props - runtime seat (useSession, useProjection) plus the locale seat.\n * @returns the monitor strip.\n */\nexport function HeartLine({ useSession, useProjection, t }: HeartLineProps) {\n const stats = useProjection('sessionStats') as SessionProjectionMap['sessionStats'] | undefined\n // Live activity + real model state: streaming output tail, running tool\n // name, turn in flight, and the last agent error.\n const live = useSession(s => ({\n partial: s.partial !== null,\n partialText: s.partial === null ? '' : streamingTail(s.partial.blocks),\n toolName: s.runningCalls[0]?.name ?? null,\n running: s.running,\n error: s.lastAgentError,\n }))\n\n const steps = stats?.steps ?? 0\n\n // ── BPM engine: step-window base + live activity boost ────────────────\n // targetRef updates once per second (activity readout); bpmRef eases toward\n // it EVERY FRAME inside paint, so the trace phase never jumps — a stepped\n // BPM would snap the whole waveform sideways at every tick.\n const bpmRef = useRef(BPM_FLOOR)\n const targetRef = useRef(BPM_FLOOR)\n const samplesRef = useRef<StepSample[]>([])\n const lastStepsRef = useRef(steps)\n const liveRef = useRef(live)\n liveRef.current = live\n const modeRef = useRef<Mode>('idle')\n const [ui, setUi] = useState({ bpm: BPM_FLOOR, elapsed: 0, mode: 'idle' as Mode })\n useEffect(() => {\n if (steps !== lastStepsRef.current) {\n lastStepsRef.current = steps\n samplesRef.current.push({ t: performance.now(), steps })\n }\n }, [steps])\n\n useEffect(() => {\n const id = window.setInterval(() => {\n const now = performance.now()\n const samples = samplesRef.current\n while (samples.length > 0 && now - samples[0].t > ACTIVITY_WINDOW_MS) samples.shift()\n const first = samples[0]\n const span = first === undefined ? 0 : now - first.t\n const delta = first === undefined ? 0 : lastStepsRef.current - first.steps\n const perMinute = span > 0 ? (delta / span) * 60_000 : 0\n const base = Math.min(BPM_CEIL, Math.max(BPM_FLOOR, 42 + perMinute * 6))\n const act = liveRef.current\n const boost = (act.toolName !== null ? BOOST_TOOL : 0)\n + (act.partial ? BOOST_THINKING : 0)\n + (act.running ? BOOST_RUNNING : 0)\n targetRef.current = Math.min(BPM_CEIL, Math.max(BPM_FLOOR, base + boost))\n const mode: Mode = act.toolName !== null ? 'tool' : act.partial ? 'think' : act.running ? 'run' : 'idle'\n modeRef.current = mode\n setUi(current => ({\n bpm: Math.round(bpmRef.current),\n elapsed: current.elapsed + 1,\n mode,\n }))\n }, 1_000)\n return () => { window.clearInterval(id) }\n }, [])\n\n // ── Canvas trace: one fixed-size canvas, redrawn per rAF ──────────────\n const canvasRef = useRef<HTMLCanvasElement>(null)\n const ctxRef = useRef<CanvasRenderingContext2D | null>(null)\n const widthRef = useRef(640)\n const dprRef = useRef(1)\n\n useEffect(() => {\n const canvas = canvasRef.current\n if (canvas === null) return\n const ctx = canvas.getContext('2d')\n if (ctx === null) return\n ctxRef.current = ctx\n\n const applySize = (): void => {\n const dpr = window.devicePixelRatio || 1\n dprRef.current = dpr\n canvas.width = Math.max(120, Math.round(widthRef.current * dpr))\n canvas.height = Math.round(ECG_HEIGHT * dpr)\n }\n applySize()\n const observer = new ResizeObserver((entries) => {\n const width = Math.max(120, Math.round(entries[0]?.contentRect.width ?? 640))\n if (width !== widthRef.current) {\n widthRef.current = width\n applySize()\n }\n })\n observer.observe(canvas)\n\n let lastBpmFrame = 0\n const paint = (now: number): void => {\n // Frame-rate BPM easing: the target updates once per second, but the\n // trace phase must move continuously or the whole waveform snaps.\n if (lastBpmFrame === 0) lastBpmFrame = now\n const dt = (now - lastBpmFrame) / 1_000\n lastBpmFrame = now\n bpmRef.current += (targetRef.current - bpmRef.current) * Math.min(1, dt * 0.8)\n const c = canvasRef.current\n const g = ctxRef.current\n if (c === null || g === null) return\n const w = widthRef.current\n const h = ECG_HEIGHT\n const dpr = dprRef.current\n g.setTransform(dpr, 0, 0, dpr, 0, 0)\n g.clearRect(0, 0, w, h)\n\n const tNow = now / 1_000\n const secondsPerPixel = ECG_WINDOW / w\n const mid = h / 2\n const amp = h * 0.44\n const wander = 0.05 * Math.sin(tNow * 0.6) + 0.035 * Math.sin(tNow * 1.7 + 1.3)\n const bpm = bpmRef.current\n const yAt = (x: number): number => {\n const tX = tNow - (w - x) * secondsPerPixel\n const phase = ((tX * (bpm / 60)) % 1 + 1) % 1\n return mid - (ecgValue(phase) + wander) * amp\n }\n\n // Chiral ghost: the same trace offset by 3px, faint cyan.\n g.beginPath()\n for (let x = 0; x <= w; x += 1) {\n const y = yAt(x)\n if (x === 0) g.moveTo(x + 3, y)\n else g.lineTo(x + 3, y)\n }\n g.strokeStyle = 'rgba(111, 219, 226, 0.18)'\n g.lineWidth = 1\n g.stroke()\n\n // Main trace.\n g.beginPath()\n for (let x = 0; x <= w; x += 1) {\n const y = yAt(x)\n if (x === 0) g.moveTo(x, y)\n else g.lineTo(x, y)\n }\n g.strokeStyle = MODE_COLOR[modeRef.current]\n g.lineWidth = 1.4\n g.lineJoin = 'round'\n g.lineCap = 'round'\n g.stroke()\n\n // Scan head: halo + dot at the leading edge.\n const headY = yAt(w)\n g.fillStyle = MODE_COLOR[modeRef.current]\n g.globalAlpha = 0.22\n g.beginPath()\n g.arc(w - 2, headY, 5, 0, Math.PI * 2)\n g.fill()\n g.globalAlpha = 1\n g.beginPath()\n g.arc(w - 2, headY, 1.6, 0, Math.PI * 2)\n g.fill()\n }\n\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {\n paint(performance.now()) // one static frame\n return () => { observer.disconnect() }\n }\n let raf = 0\n const loop = (now: number): void => {\n raf = requestAnimationFrame(loop)\n paint(now)\n }\n raf = requestAnimationFrame(loop)\n return () => {\n observer.disconnect()\n cancelAnimationFrame(raf)\n }\n }, [])\n\n // Status word: real model state wins; the flavor rotation only plays while idle.\n const flavor = STATUS_KEYS[Math.floor(ui.elapsed / STATUS_ROTATE_S) % STATUS_KEYS.length]\n const status = live.error !== null\n ? `⚠ ${live.error.slice(0, 16)}`\n : live.toolName !== null\n ? `EXEC · ${live.toolName}`\n : live.partialText !== ''\n ? `⇢ ${live.partialText.slice(-18)}`\n : t(flavor)\n\n return (\n <div className=\"cp-line\" role=\"group\" aria-label={t('line.aria')} data-chiral-pulse data-mode={ui.mode}>\n <div className=\"cp-lineBpm\">\n {ui.bpm}\n <span className=\"cp-lineBpmUnit\">{t('bpm.unit')}</span>\n </div>\n\n <canvas ref={canvasRef} className=\"cp-lineEcg\" aria-hidden />\n\n <div className=\"cp-lineReadout\">\n <div className=\"cp-lineStatus\" title={status}>{status}</div>\n </div>\n </div>\n )\n}\n","/**\n * CHIRAL PULSE — dictionary namespace.\n *\n * The DS monitor idiom stays English in both locales (it is part of the\n * aesthetic: \"LINK STABLE\", \"TIME TO COMPLETION\"); the zh side translates\n * the labels a user actually reads.\n */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'chiral'\n\n/** Dictionary keys of the `chiral` namespace (string-literal union). */\nexport type ChiralKey =\n | 'line.aria'\n | 'bpm.unit'\n | 'status.stable'\n | 'status.bonded'\n | 'status.chiral'\n | 'status.doom'\n | 'status.keep'\n | 'status.voidout'\n | 'status.odradek'\n\n/** English dictionary. */\nexport const en: Record<ChiralKey, string> = {\n 'line.aria': 'BB vital-signs strip — CHIRAL PULSE',\n 'bpm.unit': 'BPM',\n 'status.stable': 'LINK STABLE',\n 'status.bonded': 'BB BONDED',\n 'status.chiral': 'CHIRAL DENSITY: NOMINAL',\n 'status.doom': 'DOOMS LEVEL: 0',\n 'status.keep': 'KEEP ON KEEPING ON',\n 'status.voidout': 'NO VOIDOUT DETECTED',\n 'status.odradek': 'ODRADEK SYNC: OK',\n}\n\n/** Chinese dictionary. */\nexport const zh: Record<ChiralKey, string> = {\n 'line.aria': 'BB 生命体征走纸 — CHIRAL PULSE 手性脉冲',\n 'bpm.unit': '次/分',\n 'status.stable': '链路稳定',\n 'status.bonded': 'BB 连接完成',\n 'status.chiral': '手性密度:正常',\n 'status.doom': 'DOOMS 等级:0',\n 'status.keep': '继续前进 · KEEP ON KEEPING ON',\n 'status.voidout': '未检测到虚爆',\n 'status.odradek': '奥卓克同步:正常',\n}\n","/**\n * CHIRAL PULSE — the Death Stranding sheet, two layers.\n *\n * LAYER 1 — the global skin. The whole app paints from `--dsw-*` variables\n * (ui-theme's design platform: alias tokens reference static tokens, so\n * remapping the palette re-skins every component without touching its\n * structure). This sheet FORCES the DS look under BOTH theme modes: a deep\n * blue-black machine body, cold blue-grey hairlines, amber reserved for\n * emphasis (the heartbeat waveform, hover blooms) — and the deepseek brand\n * blues are left untouched, so the whale mark stays DeepSeek blue.\n *\n * LAYER 2 — the atmosphere. A fixed full-viewport CRT scanline weave, a\n * faint chiral lattice, and a vignette, all pointer-transparent. Plus the\n * BB vital-signs strip that docks under the composer stats: a 26px monitor\n * paper feed whose scrolling ECG is the hero, with the BPM and status read.\n *\n * Every rule is scoped under `.cp-*` (except the token remap, which must\n * target `body`), rides one owned <style data-plugin> tag, and the loader\n * removes it on unload.\n */\n\nexport const CHIRAL_CSS = `\n/* ────────────────────────────────────────────────────────────────────────\n LAYER 1 · global DS skin — dark blue-black, both theme modes\n ──────────────────────────────────────────────────────────────────────── */\n\n/* Alias-level remap: independent of the static scale's role flip between\n themes, so the DS look is identical under light and dark settings. */\nbody[data-ds-dark-theme],\nbody:not([data-ds-dark-theme]) {\n /* machine body — blue-grey with air, not a black void */\n --dsw-alias-bg-base: rgb(13, 17, 23);\n --dsw-alias-bg-layer-1: rgb(17, 22, 29);\n --dsw-alias-bg-layer-2: rgb(21, 27, 35);\n --dsw-alias-bg-layer-3: rgb(26, 33, 42);\n --dsw-alias-bg-overlay: rgb(31, 40, 51);\n --dsw-alias-bg-mask-1: rgba(0, 0, 0, 0.5);\n --dsw-alias-bg-mask-2: rgba(0, 0, 0, 0.22);\n --dsw-alias-bg-mask-3: rgba(0, 0, 0, 0.5);\n --dsw-alias-bg-mask-drop: rgba(10, 14, 19, 0.72);\n --dsw-alias-bg-skeleton: rgba(150, 170, 200, 0.07);\n\n /* text: cold blue-grey */\n --dsw-alias-label-primary: rgb(235, 240, 246);\n --dsw-alias-label-secondary: rgb(178, 190, 205);\n --dsw-alias-label-tertiary: rgb(140, 153, 170);\n --dsw-alias-label-caption: rgb(108, 122, 141);\n --dsw-alias-label-dimmed: rgb(94, 108, 127);\n --dsw-alias-label-primary-dimmed: rgb(205, 213, 222);\n --dsw-alias-label-primary-inverted: rgb(235, 240, 246);\n --dsw-alias-label-primary-foreground: rgb(13, 17, 23);\n --dsw-alias-brand-text: rgb(235, 240, 246);\n --dsw-alias-brand-primary: rgb(103, 158, 254);\n\n /* hairlines: cold blue, readable against the body */\n --dsw-alias-border-l1: rgba(140, 170, 215, 0.15);\n --dsw-alias-border-l2: rgba(140, 170, 215, 0.24);\n --dsw-alias-border-l2-darkmode-thin: rgba(140, 170, 215, 0.19);\n --dsw-alias-border-l3: rgba(140, 170, 215, 0.34);\n --dsw-alias-border-l4: rgba(140, 170, 215, 0.46);\n --dsw-alias-border-inverted: rgba(255, 255, 255, 0.09);\n --dsw-alias-border-inverted2: rgba(255, 255, 255, 0.11);\n\n /* hovers: amber bloom, kept subtle */\n --dsw-alias-interactive-bg-hover: rgba(255, 180, 84, 0.08);\n --dsw-alias-interactive-bg-active: rgba(255, 180, 84, 0.12);\n --dsw-alias-interactive-bg-hover-accent: rgba(255, 180, 84, 0.14);\n --dsw-alias-interactive-bg-hover-solid: rgb(22, 29, 38);\n --dsw-alias-interactive-bg-hover-danger: rgba(242, 90, 90, 0.14);\n\n /* buttons: brand blue stays the primary action */\n --dsw-alias-button-primary-dimmed: rgb(30, 40, 53);\n --dsw-alias-button-primary-hover: rgb(124, 172, 255);\n --dsw-alias-button-ghost-active-fill: rgb(22, 29, 38);\n --dsw-alias-button-ghost-active-hover: rgb(27, 35, 46);\n --dsw-alias-button-ghost-active-border: rgb(140, 170, 215);\n --dsw-alias-button-floating-fill: rgb(19, 25, 33);\n --dsw-alias-button-floating-hover: rgb(24, 31, 41);\n --dsw-alias-button-elevated-fill: rgb(22, 29, 38);\n --dsw-alias-button-contrast-fill: rgb(205, 213, 222);\n --dsw-alias-button-tool-bar-fill: rgba(140, 170, 215, 0.24);\n --dsw-alias-button-tool-bar-fill-invisible: rgba(140, 170, 215, 0.13);\n --dsw-alias-button-tool-bar-hover: rgba(140, 170, 215, 0.32);\n\n /* surfaces */\n --dsw-specific-sidebar-fill: rgb(9, 12, 17);\n --dsw-specific-sidebar-nav-item-active: rgb(20, 27, 36);\n --dsw-specific-sidebar-nav-item-active-accent: rgb(27, 36, 48);\n --dsw-specific-sidebar-nav-item-hover: rgb(15, 20, 28);\n --dsw-specific-bubble: rgb(18, 24, 32);\n --dsw-specific-bubble-highlight: rgb(24, 32, 42);\n --dsw-specific-input-major: rgb(15, 20, 27);\n --dsw-specific-login-input: rgb(12, 16, 22);\n --dsw-specific-menu: rgb(21, 27, 35);\n --dsw-specific-selector: rgb(20, 26, 34);\n --dsw-specific-tip: rgb(16, 21, 28);\n --dsw-alias-markdown-code-block: rgb(10, 14, 19);\n --dsw-alias-markdown-code-block-banner: rgb(13, 17, 23);\n --dsw-alias-markdown-inline-code: rgb(18, 24, 32);\n --dsw-alias-markdown-code-segment-selected: rgb(16, 21, 28);\n --dsw-alias-markdown-code-segment-unselected: rgb(12, 16, 22);\n --dsw-alias-markdown-placeholder: rgb(16, 21, 28);\n --dsw-alias-markdown-tag: rgb(18, 24, 32);\n --dsw-alias-markdown-citation: rgb(22, 29, 38);\n\n /* floats */\n --dsw-alias-toast-bg: rgb(22, 29, 38);\n --dsw-alias-tooltip-bg: rgb(20, 26, 34);\n --dsw-alias-scrollbar-bg-l1: rgb(13, 18, 25);\n --dsw-alias-scrollbar-bg-l2: rgb(17, 23, 31);\n --dsw-alias-scrollbar-hover-l1: rgb(34, 44, 58);\n --dsw-alias-scrollbar-hover-l2: rgb(42, 54, 70);\n\n /* status: amber stays the warn/emphasis hue; success leans chiral cyan */\n --dsw-alias-state-warn-primary: rgb(245, 158, 11);\n --dsw-alias-state-warn-secondary: rgb(247, 173, 49);\n --dsw-alias-state-warn-label: rgb(221, 134, 41);\n --dsw-alias-state-warn-tertiary: rgb(39, 36, 31);\n --dsw-alias-state-success-primary: rgb(52, 205, 168);\n --dsw-alias-state-success-secondary: rgb(94, 222, 189);\n --dsw-alias-state-success-tertiary: rgb(12, 28, 24);\n --dsw-static-green-400: rgb(94, 222, 189);\n --dsw-static-green-500: rgb(52, 205, 168);\n}\n\n/* Focus ring: amber, the DS highlight color. */\n:focus-visible {\n outline: 1px solid rgba(255, 180, 84, 0.65) !important;\n outline-offset: 2px;\n}\n\n/* Ambient bloom behind the app. */\nbody {\n background-image:\n radial-gradient(1100px 620px at 12% -8%, rgba(111, 219, 226, 0.04), transparent 60%),\n radial-gradient(900px 560px at 108% 112%, rgba(103, 158, 254, 0.05), transparent 60%);\n background-attachment: fixed;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n LAYER 2 · atmosphere overlays (injected as fixed elements)\n ──────────────────────────────────────────────────────────────────────── */\n.cp-atmo {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 2147483000;\n}\n.cp-atmo-scanlines {\n background: repeating-linear-gradient(\n 0deg,\n rgba(255, 255, 255, 0.024) 0 1px,\n transparent 1px 3px\n );\n mix-blend-mode: overlay;\n}\n.cp-atmo-lattice {\n opacity: 0.55;\n background:\n repeating-linear-gradient(60deg, transparent 0 17px, rgba(103, 158, 254, 0.03) 17px 18px),\n repeating-linear-gradient(120deg, transparent 0 17px, rgba(111, 219, 226, 0.028) 17px 18px);\n}\n.cp-atmo-vignette {\n background: radial-gradient(120% 100% at 50% 40%, transparent 55%, rgba(0, 0, 0, 0.24) 100%);\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n BB vital-signs strip · the heartbeat paper feed\n ──────────────────────────────────────────────────────────────────────── */\n.cp-line {\n --cp-amber: #ffb454;\n --cp-amber-bright: #ffd9a0;\n --cp-cyan: #6fdbe2;\n --cp-dim: #64727f;\n display: flex;\n align-items: center;\n gap: 12px;\n height: 26px;\n margin: 3px 0 4px;\n padding: 0 10px;\n border: 1px solid rgba(140, 170, 215, 0.26);\n border-radius: 0;\n background:\n linear-gradient(115deg, rgba(140, 190, 255, 0.06) 0%, transparent 30%),\n linear-gradient(180deg, rgba(14, 19, 26, 0.92), rgba(9, 13, 18, 0.94));\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.05),\n inset 0 0 16px rgba(103, 158, 254, 0.06);\n color: #c9d3dc;\n font-family: ui-monospace, \"Cascadia Mono\", \"JetBrains Mono\", Consolas, \"Courier New\", monospace;\n overflow: hidden;\n clip-path: polygon(\n 7px 0,\n 100% 0,\n 100% calc(100% - 7px),\n calc(100% - 7px) 100%,\n 0 100%,\n 0 7px\n );\n}\n\n.cp-lineBpm {\n flex: none;\n min-width: 42px;\n font-size: 11px;\n line-height: 1;\n letter-spacing: 0.5px;\n color: var(--cp-amber-bright);\n text-shadow: 0 0 10px rgba(255, 180, 84, 0.5);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n.cp-lineBpmUnit {\n font-size: 7px;\n letter-spacing: 1px;\n color: var(--cp-dim);\n margin-left: 2px;\n}\n\n.cp-lineEcg {\n flex: 1 1 auto;\n min-width: 100px;\n height: 22px;\n display: block;\n border-radius: 0;\n border: 1px solid rgba(140, 170, 215, 0.16);\n /* Static paper grid lives in CSS; the canvas above it only paints the trace. */\n background:\n repeating-linear-gradient(0deg, rgba(140, 170, 215, 0.07) 0 1px, transparent 1px 11px),\n repeating-linear-gradient(90deg, rgba(140, 170, 215, 0.06) 0 1px, transparent 1px 11px),\n rgba(7, 10, 15, 0.55);\n}\n\n.cp-lineReadout {\n flex: none;\n width: 132px;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n overflow: hidden;\n}\n.cp-lineStatus {\n font-size: 8px;\n letter-spacing: 1.8px;\n text-transform: uppercase;\n color: var(--cp-cyan);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .cp-lineEcg {\n opacity: 0.9;\n }\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n ECG paper grid + activity-mode color coupling\n ──────────────────────────────────────────────────────────────────────── */\n.cp-line[data-mode=\"think\"] .cp-lineBpm {\n color: #7fe3e8;\n text-shadow: 0 0 10px rgba(111, 219, 226, 0.55);\n}\n.cp-line[data-mode=\"tool\"] .cp-lineBpm {\n color: #ff9b7a;\n text-shadow: 0 0 10px rgba(255, 122, 77, 0.6);\n}\n.cp-line[data-mode=\"run\"] .cp-lineBpm {\n color: #ffd9a0;\n}\n.cp-line[data-mode=\"think\"] .cp-lineStatus,\n.cp-line[data-mode=\"tool\"] .cp-lineStatus {\n color: #9fe8ec;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Message-flow dressing — DS glyphs on each node kind\n ──────────────────────────────────────────────────────────────────────── */\n[data-chat-flow-kind] {\n position: relative;\n}\n[data-chat-flow-kind=\"assistant\"] {\n padding-left: 18px;\n}\n[data-chat-flow-kind=\"assistant\"]::before {\n content: \"✦\";\n position: absolute;\n left: 4px;\n top: 12px;\n color: rgba(255, 180, 84, 0.85);\n font-size: 11px;\n line-height: 1;\n text-shadow: 0 0 8px rgba(255, 180, 84, 0.6);\n}\n[data-chat-flow-kind=\"assistant\"]::after {\n content: \"\";\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 1px;\n background: linear-gradient(180deg, transparent, rgba(255, 180, 84, 0.35), transparent);\n}\n[data-chat-flow-kind=\"user\"],\n[data-chat-flow-kind=\"steering\"] {\n padding-right: 18px;\n}\n[data-chat-flow-kind=\"user\"]::before,\n[data-chat-flow-kind=\"steering\"]::before {\n content: \"▸▸\";\n position: absolute;\n right: 2px;\n top: 4px;\n color: rgba(120, 150, 195, 0.75);\n font-size: 10px;\n line-height: 1;\n letter-spacing: -1px;\n}\n[data-chat-flow-kind=\"context\"] {\n padding-left: 16px;\n}\n[data-chat-flow-kind=\"context\"]::before {\n content: \"⇢\";\n position: absolute;\n left: 2px;\n top: 12px;\n color: rgba(111, 219, 226, 0.7);\n font-size: 11px;\n line-height: 1;\n}\n[data-variant=\"think\"] {\n border-left: 2px solid rgba(111, 219, 226, 0.35);\n padding-left: 10px;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Tool-card chassis — the ui-primitives block family gets a DS case\n ──────────────────────────────────────────────────────────────────────── */\n[data-tool],\n[data-search],\n[data-read],\n[data-web],\n[data-diff],\n[data-terminal],\n[data-context-injection-body] {\n border: 1px solid rgba(140, 170, 215, 0.24) !important;\n border-radius: 0 !important;\n background:\n linear-gradient(115deg, rgba(140, 190, 255, 0.06) 0%, transparent 30%),\n linear-gradient(180deg, rgba(15, 20, 27, 0.88), rgba(9, 13, 18, 0.92)) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.05),\n inset 0 0 18px rgba(103, 158, 254, 0.05);\n position: relative;\n clip-path: polygon(\n 7px 0,\n 100% 0,\n 100% calc(100% - 7px),\n calc(100% - 7px) 100%,\n 0 100%,\n 0 7px\n );\n}\n[data-terminal]::before {\n content: \"❯_\";\n position: absolute;\n right: 8px;\n top: 6px;\n color: rgba(111, 219, 226, 0.35);\n font-size: 10px;\n font-family: ui-monospace, Consolas, monospace;\n}\n[data-read]::before {\n content: \"▤\";\n position: absolute;\n right: 8px;\n top: 6px;\n color: rgba(120, 150, 195, 0.4);\n font-size: 11px;\n}\n[data-search]::before {\n content: \"⌕\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(255, 180, 84, 0.4);\n font-size: 13px;\n}\n[data-web]::before {\n content: \"⌖\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(111, 219, 226, 0.4);\n font-size: 12px;\n}\n[data-diff]::before {\n content: \"⇄\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(120, 150, 195, 0.4);\n font-size: 12px;\n}\n[data-tool]::before {\n content: \"⚙\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(255, 180, 84, 0.4);\n font-size: 11px;\n}\n[data-context-injection-body]::before {\n content: \"⇢\";\n position: absolute;\n right: 8px;\n top: 5px;\n color: rgba(111, 219, 226, 0.4);\n font-size: 11px;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Composer details\n ──────────────────────────────────────────────────────────────────────── */\n[data-composer-seat] textarea {\n caret-color: #ffb454;\n letter-spacing: 0.01em;\n}\n[data-composer-seat] textarea:focus {\n caret-color: #ffd9a0;\n}\n[data-composer-seat] {\n box-shadow:\n inset 0 0 0 1px rgba(140, 170, 215, 0.22),\n inset 0 1px 0 rgba(255, 255, 255, 0.06),\n inset 0 -12px 30px rgba(0, 0, 0, 0.25);\n border-radius: 0;\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n Dialogs, menus, tooltips, toasts — the floating DS surfaces\n ──────────────────────────────────────────────────────────────────────── */\n[role=\"dialog\"] {\n border: 1px solid rgba(255, 180, 84, 0.32) !important;\n border-radius: 0 !important;\n background: linear-gradient(180deg, rgba(15, 20, 27, 0.98), rgba(10, 14, 19, 0.99)) !important;\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.07),\n inset 0 0 26px rgba(103, 158, 254, 0.06) !important;\n}\n[role=\"menu\"] {\n border: 1px solid rgba(140, 170, 215, 0.3) !important;\n border-radius: 0 !important;\n background: rgba(13, 18, 25, 0.97) !important;\n}\n[role=\"menuitem\"]:hover {\n background: rgba(255, 180, 84, 0.08) !important;\n}\n[role=\"tooltip\"] {\n border: 1px solid rgba(140, 170, 215, 0.32) !important;\n border-radius: 0 !important;\n background: rgba(15, 20, 27, 0.97) !important;\n}\n[role=\"alert\"] {\n border: 1px solid rgba(255, 180, 84, 0.38) !important;\n border-radius: 0 !important;\n background: rgba(15, 20, 27, 0.97) !important;\n clip-path: polygon(\n 8px 0,\n 100% 0,\n 100% calc(100% - 8px),\n calc(100% - 8px) 100%,\n 0 100%,\n 0 8px\n );\n}\n\n/* ────────────────────────────────────────────────────────────────────────\n DS chamfer everywhere else: kill the round-corner language\n ──────────────────────────────────────────────────────────────────────── */\nbutton,\ninput,\ntextarea,\nselect,\n[role=\"tab\"],\n[role=\"menuitem\"],\n[role=\"treeitem\"] {\n border-radius: 0 !important;\n}\n\n`\n","/**\n * CHIRAL PULSE, browser half: the Death Stranding skin plus the BB\n * vital-signs strip under the composer stats.\n *\n * Two contributions:\n * 1. The global DS skin 鈥?a `--dsw-*` token remap (blue-black machine body,\n * amber hairlines, sand-paper light variant), the DeepSeek whale mark's\n * brand blues untouched, plus three pointer-transparent atmosphere\n * overlays (CRT scanlines, chiral lattice, vignette).\n * 2. The heartbeat strip on `conversation.composer.dock` 鈥?a 26px monitor\n * paper feed whose scrolling ECG is the hero and whose BPM follows the\n * session's live activity (model streaming, tools executing).\n *\n * The plugin owns no state of its own beyond the component's local beat\n * engine; every figure arrives through the session standard kit. All styles\n * ride one owned <style data-plugin> tag so the loader removes them on\n * unload/reload.\n */\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the ui-conversation SlotMap merge (the composer.dock entry).\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport { HeartLine } from './HeartLine.tsx'\nimport { en, NS, zh, type ChiralKey } from './locales.ts'\nimport { CHIRAL_CSS } from './style.ts'\n\nexport { HeartLine } from './HeartLine.tsx'\nexport type { HeartLineProps } from './HeartLine.tsx'\nexport type { ChiralKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The BB monitor strip's copy. */\n chiral: ChiralKey\n }\n}\n\n/** Required services: the slot registry and the locale service. */\nexport const inject = ['slots', 'locale']\n\n/**\n * Client plugin body: register dictionaries, inject the DS sheet and the\n * atmosphere overlays, and dock the heartbeat strip under the composer.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'chiral-pulse: dictionaries')\n\n ctx.effect(() => {\n const tag = document.createElement('style')\n tag.dataset.plugin = 'chiral-pulse'\n tag.textContent = CHIRAL_CSS\n document.head.appendChild(tag)\n return () => { tag.remove() }\n }, 'chiral-pulse: styles')\n\n // Atmosphere overlays: scanlines + chiral lattice + vignette, all\n // pointer-transparent, riding the top of the stacking order.\n ctx.effect(() => {\n const layers = [\n { className: 'cp-atmo cp-atmo-scanlines', label: 'scanlines' },\n { className: 'cp-atmo cp-atmo-lattice', label: 'lattice' },\n { className: 'cp-atmo cp-atmo-vignette', label: 'vignette' },\n ]\n const nodes = layers.map(({ className }) => {\n const el = document.createElement('div')\n el.className = className\n el.setAttribute('aria-hidden', 'true')\n document.body.appendChild(el)\n return el\n })\n return () => {\n for (const el of nodes) el.remove()\n }\n }, 'chiral-pulse: atmosphere')\n\n ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\n name: 'conversation.input.dock',\n id: 'chiral-pulse',\n // Above the composer card, under the goal strip: the pulse feed rides\n // with the input it monitors.\n order: 20,\n locale: NS,\n }, HeartLine))\n}\n"],"mappings":"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"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* CHIRAL PULSE, node half. Pure UI plugin: the empty apply exists so the
|
|
4
|
+
* package can be a row in the host cordis tree / Loader; the browser half
|
|
5
|
+
* ships via exports["./client"], discovered through the package.json
|
|
6
|
+
* `dsh.client` declaration.
|
|
7
|
+
*/
|
|
8
|
+
/** Host plugin body — no host-side behavior for this surface plugin. */
|
|
9
|
+
function apply() {}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { apply };
|
package/package.json
ADDED
|
@@ -0,0 +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
|
+
}
|