dsh-stall-guard 0.1.4 → 0.1.5

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/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@
5
5
  格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
6
6
  版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
7
7
 
8
+ ## [0.1.5] - 2026-10-21
9
+
10
+ ### 界面
11
+
12
+ - **浮动模式变成鱼缸**:图标外框现在是一缸水 —— 半透明蓝色水体、顶部水面亮线、
13
+ 缓慢左右摆动的波光、向下扫过的波纹,以及向上浮动的水泡。
14
+ 全部用 CSS 关键帧实现,空闲时不占用 JavaScript;`prefers-reduced-motion` 下自动停止。
15
+ - **卡顿水花**:警报时一圈水花炸开 + 5 滴水珠飞散,播一次后自动清除,不会循环闪。
16
+ - **图标恢复为最初的圆鲸鱼**:圆身体、翘尾鳍、顶鳍、大眼与微笑。这是用户偏好的形态,
17
+ 之前两次重画(侧游小鱼、官方剪影风)都已撤掉。仅补上尾鳍/顶鳍的动画钩子。
18
+ - 尾鳍随状态摆动:闲置慢摆(5.4 秒)、监视中小幅(3.2 秒)、卡顿急摆(0.3 秒)。
19
+ - 水泡改为按比例尺寸,小图标下也看得见。
20
+
21
+ ### 修复
22
+
23
+ - **切到浮动后图标消失**:标题栏座位的守卫 `supervisorOff || floating` 把 overlay 座位
24
+ 也挡掉了(它收到的 floating 恒为 true)。现区分「本座位该画的形态」与「用户选择的形态」。
25
+ - **拖动图标会带动标题栏**:浮动态改由 `shell.overlay` 渲染,脱离文档流。
26
+ - **调整大小时看不到实时预览**:设置面板从标题栏下方展开(`top:118px`),不再压住图标。
27
+ 外观状态统一走 `useAppearance()`(`useSyncExternalStore`),三个消费点同步重绘。
28
+ - **气泡点击会打开面板**:改为关闭提示(`acknowledge`)。点鱼图标才是打开设置。
29
+ - **气泡被挤成竖排**:固定宽度 200px,最多 4 行。
30
+ - **气泡在 `<button>` 内部**:移到按钮外作为兄弟节点,独立处理点击与键盘激活。
31
+
8
32
  ## [0.1.4] - 2026-10-21
9
33
 
10
34
  ### 修复
package/README.md CHANGED
@@ -124,7 +124,18 @@ $env:DSH_PROFILE # 桌面版一般是 desktop
124
124
  |---|---|---|
125
125
  | `dot` | 状态栏 6px 半透明点 | 变红脉冲 |
126
126
  | `icon` | 静止图标 | 弹跳 + 发光 |
127
- | `avatar` | 闭眼小鲸鱼 | 弹跳 + 气泡说出原因 |
127
+ | `avatar` | 闭眼圆鲸鱼 | 变红、尾鳍急摆、水花炸开 |
128
+
129
+ ### 鱼缸(浮动模式)
130
+
131
+ 把图标设为「浮动」后,它外面会套一个鱼缸:
132
+
133
+ - 半透明蓝色水体、顶部水面亮线(呼吸)、缓慢左右摆的波光、向下扫的波纹
134
+ - 5 颗按比例缩放的**上浮水泡**
135
+ - 卡顿时一圈**水花炸开 + 5 滴水珠飞散**,播一次就停,不循环闪
136
+
137
+ 全部是 CSS 关键帧,空闲时不跑 JavaScript;系统开启「减少动态效果」时自动停止。
138
+ 图标本身也有动作:尾鳍闲置慢摆、监视中小幅、卡顿急摆。
128
139
 
129
140
  ### 显示开关
130
141
 
package/client.js CHANGED
@@ -931,6 +931,9 @@ window.__ModuleLoader__.load({
931
931
  /* ---- supervisor seat ---- */
932
932
  '.dsg-seat{position:relative;display:inline-flex;flex:none;align-items:center;justify-content:center;pointer-events:auto;overflow:visible}',
933
933
  '.dsg-orb{box-sizing:border-box;display:inline-flex;position:relative;align-items:center;justify-content:center;margin:0;padding:0;border:none;background:transparent;font-family:inherit;cursor:pointer;overflow:visible;border-radius:var(--dsw-radius-sm,6px);pointer-events:auto}',
934
+ /* The floating figure is a tank: clip the water to its frame and stack the whale above it. */
935
+ '.dsg-orb[data-floating="yes"]{overflow:hidden}',
936
+ '.dsg-orb[data-floating="yes"]>svg{position:relative;z-index:1}',
934
937
  '.dsg-orb:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:2px}',
935
938
  '.dsg-orb[data-display="dot"]{width:22px;height:22px}',
936
939
  '.dsg-orb[data-display="icon"]{width:24px;height:24px}',
@@ -1052,6 +1055,37 @@ window.__ModuleLoader__.load({
1052
1055
  '@keyframes dsg-glow{0%,100%{filter:drop-shadow(0 0 0 rgba(236,19,19,0))}50%{filter:drop-shadow(0 0 5px rgba(236,19,19,.85))}}',
1053
1056
  '@keyframes dsg-sway{0%,100%{transform:rotate(-6deg)}50%{transform:rotate(6deg)}}',
1054
1057
  '@keyframes dsg-bounce-big{0%,100%{transform:translateY(0) scale(1)}30%{transform:translateY(-8px) scale(1.06)}60%{transform:translateY(0) scale(1)}80%{transform:translateY(-3px) scale(1)}}',
1058
+ /* ---- aquarium: the floating posture reads as a fish tank ---- */
1059
+ /* Three layers behind the whale: a translucent water body, a caustic shimmer that
1060
+ * suggests light through water, and bubbles rising on their own keyframes. All are
1061
+ * pointer-events:none so the tank never eats a click meant for the figure. */
1062
+ '.dsg-tank{position:absolute;inset:0;display:block;pointer-events:none;border-radius:var(--dsw-radius-sm,10px);overflow:hidden;background:linear-gradient(180deg,rgba(86,140,220,.28) 0%,rgba(40,88,170,.42) 60%,rgba(18,46,110,.5) 100%);border:1px solid rgba(150,200,255,.35);box-shadow:inset 0 2px 10px rgba(0,20,60,.45),0 2px 12px rgba(0,0,0,.32)}',
1063
+ '.dsg-tank::before{content:"";position:absolute;inset:0;pointer-events:none;background:radial-gradient(ellipse 60% 40% at 50% 0%,rgba(190,225,255,.35),transparent 70%);mix-blend-mode:screen;animation:dsg-caustic 7s ease-in-out infinite}',
1064
+ '.dsg-tank::after{content:"";position:absolute;left:0;right:0;top:-40%;height:34%;pointer-events:none;background:linear-gradient(180deg,transparent,rgba(150,205,255,.22),transparent);animation:dsg-wave 5.5s linear infinite}',
1065
+ '.dsg-tank-waterline{position:absolute;left:0;right:0;top:0;height:2px;pointer-events:none;background:linear-gradient(90deg,transparent,rgba(190,230,255,.75),transparent);opacity:.85;animation:dsg-waterline 3.6s ease-in-out infinite}',
1066
+ '.dsg-tank-bubble{position:absolute;bottom:6%;border-radius:50%;pointer-events:none;background:radial-gradient(circle at 32% 28%,rgba(255,255,255,.9),rgba(180,220,255,.35) 55%,rgba(120,180,255,.18) 100%);box-shadow:0 0 3px rgba(180,220,255,.5);animation:dsg-rise linear infinite}',
1067
+ '@keyframes dsg-rise{0%{transform:translateY(0) scale(.7);opacity:0}12%{opacity:.85}70%{opacity:.7}100%{transform:translateY(-260%) scale(1.05);opacity:0}}',
1068
+ '@keyframes dsg-caustic{0%,100%{opacity:.5;transform:translateX(-4%)}50%{opacity:.85;transform:translateX(4%)}}',
1069
+ '@keyframes dsg-wave{0%{transform:translateY(0)}100%{transform:translateY(320%)}}',
1070
+ '@keyframes dsg-waterline{0%,100%{opacity:.55}50%{opacity:1}}',
1071
+ /* One-shot splash for an alarm: droplets fly outward, then the class is removed. */
1072
+ '.dsg-splash{position:absolute;inset:0;pointer-events:none;border-radius:50%;border:2px solid rgba(190,232,255,.85);opacity:0;animation:dsg-splash-out .62s ease-out forwards}',
1073
+ '.dsg-splash-drop{position:absolute;left:50%;top:50%;width:3px;height:3px;border-radius:50%;pointer-events:none;background:rgba(215,240,255,.95);animation:dsg-drop-out .62s ease-out forwards}',
1074
+ '@keyframes dsg-splash-out{0%{opacity:.9;transform:scale(.35);border-width:3px}100%{opacity:0;transform:scale(1.15);border-width:1px}}',
1075
+ '@keyframes dsg-drop-out{0%{opacity:1;transform:translate(-50%,-50%) scale(1)}100%{opacity:0;transform:translate(calc(-50% + var(--dx)),calc(-50% + var(--dy))) scale(.4)}}',
1076
+ '@media (prefers-reduced-motion:reduce){.dsg-tank::before,.dsg-tank::after,.dsg-tank-waterline,.dsg-tank-bubble,.dsg-splash,.dsg-splash-drop{animation:none}}',
1077
+ /* ---- the whale: gentle when calm, a flick when alarmed ---- */
1078
+ '.dsg-avatar{overflow:visible}',
1079
+ '.dsg-avatar[data-mode="idle"] .dsg-whale-tail{animation:dsg-whale-drift 5.4s ease-in-out infinite;transform-origin:19.6px 13px}',
1080
+ '.dsg-avatar[data-mode="watching"] .dsg-whale-tail{animation:dsg-whale-swish 3.2s ease-in-out infinite;transform-origin:19.6px 13px}',
1081
+ '.dsg-avatar[data-mode="alert"] .dsg-whale-tail{animation:dsg-whale-flick .3s ease-in-out infinite;transform-origin:19.6px 13px}',
1082
+ '.dsg-avatar[data-mode="watching"] .dsg-whale-fin{animation:dsg-fin-wave 2.6s ease-in-out infinite;transform-origin:14px 8px}',
1083
+ '.dsg-avatar[data-mode="alert"] .dsg-whale-fin{animation:dsg-fin-wave .8s ease-in-out infinite;transform-origin:14px 8px}',
1084
+ '@keyframes dsg-whale-drift{0%,100%{transform:rotate(-4deg)}50%{transform:rotate(5deg)}}',
1085
+ '@keyframes dsg-whale-swish{0%,100%{transform:rotate(-8deg)}50%{transform:rotate(9deg)}}',
1086
+ '@keyframes dsg-whale-flick{0%,100%{transform:rotate(-22deg) scaleY(1)}50%{transform:rotate(22deg) scaleY(.85)}}',
1087
+ '@keyframes dsg-fin-wave{0%,100%{transform:rotate(-4deg)}50%{transform:rotate(6deg)}}',
1088
+ '@media (prefers-reduced-motion:reduce){.dsg-whale-tail,.dsg-whale-fin{animation:none!important}}',
1055
1089
  '@keyframes dsg-bubble-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}',
1056
1090
  '@media (prefers-reduced-motion:reduce){.dsg-orb[data-mode] .dsg-dot,.dsg-orb[data-mode] .dsg-glyph,.dsg-orb[data-mode] .dsg-avatar,.dsg-bubble{animation:none}}',
1057
1091
  ].join('\n');
@@ -1101,6 +1135,21 @@ window.__ModuleLoader__.load({
1101
1135
  * 28px original DeepSeek-blue whale. Eyes are closed with a line while the
1102
1136
  * supervisor is idle and open dots otherwise; nothing external is loaded.
1103
1137
  */
1138
+ /**
1139
+ * A fish, drawn for the tank.
1140
+ *
1141
+ * Three moods, driven by `mode`:
1142
+ * idle - slow bob, closed eye, tail drifting
1143
+ * watching - upright, eye open, gentle fin motion
1144
+ * alert - red body, mouth open, fast tail flick and a small scale burst
1145
+ */
1146
+ /**
1147
+ * The whale, as originally drawn.
1148
+ *
1149
+ * A plump 28x28 whale: round body, raised tail fluke, top fin, one eye, a small smile.
1150
+ * The user preferred this silhouette over later redraws, so it is kept verbatim; only
1151
+ * class names are added to the two fins so the tank animations can target them.
1152
+ */
1104
1153
  function WhaleGlyph(props) {
1105
1154
  const size = props.size || 28;
1106
1155
  const asleep = props.mode === 'idle';
@@ -1115,14 +1164,17 @@ window.__ModuleLoader__.load({
1115
1164
  xmlns: 'http://www.w3.org/2000/svg',
1116
1165
  'aria-hidden': 'true',
1117
1166
  focusable: 'false',
1167
+ 'data-mode': props.mode || 'idle',
1118
1168
  },
1119
1169
  /* tail fluke */
1120
1170
  h('path', {
1171
+ className: 'dsg-whale-tail',
1121
1172
  d: 'M19.6 12.4c2.9-3.4 5.1-3.1 5.1.5 0 3.3-2.4 3.7-5.1.6z',
1122
1173
  fill: fin,
1123
1174
  }),
1124
1175
  /* top fin */
1125
1176
  h('path', {
1177
+ className: 'dsg-whale-fin',
1126
1178
  d: 'M11.8 6.4c2.3-2.5 4.4-2.5 4.8 1.1-2.1 1-3.9.6-4.8-1.1z',
1127
1179
  fill: BLUE_LIGHT,
1128
1180
  }),
@@ -1223,12 +1275,22 @@ window.__ModuleLoader__.load({
1223
1275
  const [bubbleBelow, setBubbleBelow] = useState(false);
1224
1276
  const appearance = useAppearance();
1225
1277
  const [dragging, setDragging] = useState(false);
1278
+ /* The splash is a one-shot: raised when the alarm starts, cleared on animation end. */
1279
+ const [splash, setSplash] = useState(false);
1280
+ const previousModeRef = useRef(null);
1226
1281
 
1227
1282
  useEffect(() => store.start(), []);
1228
1283
 
1229
1284
  const config = snapshot.config;
1230
1285
  const supervisorOff = config !== null && config.supervisor.show === false;
1231
1286
  const mode = snapshot.state === null ? 'idle' : snapshot.state.supervisor.mode;
1287
+ /* Raised once per alarm. Declared after `mode` on purpose: a hook that reads a
1288
+ * `const` before its declaration is a TDZ error that takes the whole module down. */
1289
+ useEffect(() => {
1290
+ if (previousModeRef.current === mode) return;
1291
+ previousModeRef.current = mode;
1292
+ if (mode === 'alert') setSplash(true);
1293
+ }, [mode]);
1232
1294
  const display = config === null ? 'avatar' : config.supervisor.display;
1233
1295
  const message = snapshot.state === null ? '' : snapshot.state.supervisor.message;
1234
1296
  const bubbleOn = config === null ? true : config.supervisor.bubble !== false;
@@ -1305,7 +1367,9 @@ window.__ModuleLoader__.load({
1305
1367
  top: appearance.y < 0 ? 'auto' : appearance.y + 'px',
1306
1368
  right: appearance.x < 0 ? '14px' : 'auto',
1307
1369
  bottom: appearance.y < 0 ? '96px' : 'auto',
1308
- zIndex: 68,
1370
+ /* Above the settings panel (70): the figure must stay visible and draggable
1371
+ * even when it is dragged under the panel. */
1372
+ zIndex: 75,
1309
1373
  }
1310
1374
  : undefined;
1311
1375
 
@@ -1334,7 +1398,32 @@ window.__ModuleLoader__.load({
1334
1398
  display === 'dot' ? h('span', { className: 'dsg-dot' }) : null,
1335
1399
  display === 'icon' ? h(ShieldGlyph, { size: Math.round(size * 0.64) }) : null,
1336
1400
  display === 'avatar' ? h(WhaleGlyph, { size: Math.round(size * 0.82), mode }) : null,
1401
+ /* One-shot splash per alarm. `onAnimationEnd` clears it so it never loops. */
1402
+ splash
1403
+ ? h('span', { className: 'dsg-splash', 'aria-hidden': 'true',
1404
+ onAnimationEnd: () => setSplash(false),
1405
+ },
1406
+ h('span', { className: 'dsg-splash-drop', style: { '--dx': '-14px', '--dy': '-16px' } }),
1407
+ h('span', { className: 'dsg-splash-drop', style: { '--dx': '13px', '--dy': '-18px' } }),
1408
+ h('span', { className: 'dsg-splash-drop', style: { '--dx': '-16px', '--dy': '8px' } }),
1409
+ h('span', { className: 'dsg-splash-drop', style: { '--dx': '17px', '--dy': '6px' } }),
1410
+ h('span', { className: 'dsg-splash-drop', style: { '--dx': '0px', '--dy': '-22px' } }),
1411
+ )
1412
+ : null,
1337
1413
  ),
1414
+ /* Aquarium: only in the floating posture, where the rounded frame reads as a tank.
1415
+ * Everything here paints behind the whale and ignores pointer events. */
1416
+ floating
1417
+ ? h('span', { className: 'dsg-tank', 'aria-hidden': 'true' },
1418
+ h('span', { className: 'dsg-tank-waterline' }),
1419
+ h('span', { className: 'dsg-tank-bubble', style: { width: '15%', height: '15%', left: '16%', animationDuration: '4.2s', animationDelay: '0s' } }),
1420
+ h('span', { className: 'dsg-tank-bubble', style: { width: '11%', height: '11%', left: '33%', animationDuration: '5.6s', animationDelay: '1.1s' } }),
1421
+ h('span', { className: 'dsg-tank-bubble', style: { width: '18%', height: '18%', left: '51%', animationDuration: '4.8s', animationDelay: '0.4s' } }),
1422
+ h('span', { className: 'dsg-tank-bubble', style: { width: '10%', height: '10%', left: '69%', animationDuration: '6.2s', animationDelay: '2.0s' } }),
1423
+ h('span', { className: 'dsg-tank-bubble', style: { width: '14%', height: '14%', left: '83%', animationDuration: '5.1s', animationDelay: '1.6s' } })
1424
+ )
1425
+ : null,
1426
+
1338
1427
  /* The bubble is a SIBLING of the button, not a child: interactive content inside a
1339
1428
  * button is invalid and browsers route its clicks unpredictably, and content painted
1340
1429
  * outside the button box is not part of its hit area. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-stall-guard",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "DSH 卡顿守卫:下载类命令(git clone / pnpm install / pip / curl)超时后自动中止,并把「换个下载源重试」的提示写进工具结果给智能体看,解决 GitHub 在国内网络不稳导致智能体一直干等的问题。另可按工具类型自定义超时秒数,并附一个只在卡顿时唤醒的 DeepSeek 监督员。",
5
5
  "keywords": [
6
6
  "dsh",