@iasiv5/dsh-quota-watch 0.1.19 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  ## ✨ 功能一览
16
16
 
17
- - **🚀 悬浮胶囊(默认界面)**:一行「GLM 15% · Copilot 1.8%」常驻屏幕(默认右下角安全区),单击展开面板——胶囊上没有任何隐藏手势(右键 / 长按菜单已于 0.1.14 退役)。拖拽跟手(保持抓取点,transform 渲染)、任意位置停放、位置跨重启记忆、越界自动钳回视口(距边 8px 安全边距);触摸设备直接拖拽(更宽的防误拖阈值)、点击热区 44px、自动避让 iPhone 刘海 / 小黑条(safe-area)。已用超过 80% 转橙、超过 95% 转红,任一 provider 超 95% 时轻微脉动(尊重系统「减少动态效果」设置)。悬停提示与 aria-label 实时给出摘要。
17
+ - **🚀 悬浮胶囊(默认界面)**:一行「GLM 15% · Copilot 1.8%」常驻屏幕(默认右下角安全区),单击展开面板——胶囊上没有任何隐藏手势(右键 / 长按菜单已于 0.1.14 退役)。拖拽跟手(保持抓取点,transform 渲染)、任意位置停放、位置跨重启记忆、越界自动钳回视口(距边 8px 安全边距),并自动避开 Windows Desktop 壳顶部的标题栏拖拽带(0.1.20:该带按布局吞掉指针输入、无视 z-index,胶囊/面板落进去就再也点不到、拖不回——现与壳自家浮窗同款消费 `--dsh-windows-titlebar-height` 让位,历史卡死位置在启动时自动钳回带外;Web 无此变量,行为零漂移);触摸设备直接拖拽(更宽的防误拖阈值)、点击热区 44px、自动避让 iPhone 刘海 / 小黑条(safe-area)。已用超过 80% 转橙、超过 95% 转红,任一 provider 超 95% 时轻微脉动(尊重系统「减少动态效果」设置)。悬停提示与 aria-label 实时给出摘要。
18
18
  - **🖥️ 详情面板**:半透明毛玻璃底,宽 `min(320px, 100vw−24px)`,靠边自动翻侧,滚动 / 缩放跟随;≤480px 窄屏自动改为底部弹层并避开 safe-area;Esc 或点击面板外关闭。概览列两个 provider 行,点击进入详情:
19
19
  - **GLM**:今日 Tokens / 调用次数双大数字 + 五列对齐窗口网格(MCP(月)→ 5 小时 → 每月 → 每周固定排序,周窗口未下发时显示「每周额度 · ♾️(无限)」占位)+ 分模型用量行;
20
20
  - **Copilot**:可用额度、本期已用(credits 消耗 + 弱化百分比)、重置时间三行。
@@ -71,4 +71,4 @@ dsh plugin --profile desktop add @iasiv5/dsh-quota-watch # desktop profile
71
71
 
72
72
  ## English summary
73
73
 
74
- DSH Quota Watch is a personal DSH quota monitor built around a persistent floating capsule: a one-line `GLM x% · Copilot y%` summary that turns amber past 80% and red past 95%. Dragging it is grab-accurate (transform-based, rAF-batched) and free to place anywhere on the screen, with position remembered across restarts and viewport boundaries automatically clamped. Touch devices drag directly (wider slop against accidental drags), the hit area is padded to 44px and placement respects `env(safe-area-inset-*)`. Clicking the capsule opens a floating glass panel whose overview reuses the classic 0.0.18 summary rows (label · bar · percent · extra · chevron) and whose detail pages replicate the 0.0.18 big numbers, window grid and per-model list; the panel closes via ✕, Esc or an outside click, and viewports ≤480px dock it to the bottom as a sheet. The panel footer holds two equal-width buttons — sidebar-card visibility and an immediate provider re-probe — replacing the retired right-click/long-press menu, so the capsule itself carries no hidden gestures. A sidebar card remains an opportunistic extra entry on web profiles, opted in from that footer button, and silently disappears whenever the sidebar does — collapsed web sidebars and the desktop profile are covered by the floating capsule alone. Credentials are resolved on every host-side probe, only normalized values reach the browser, no usage history is kept, and a Copilot balance is never inferred from credits used. UI copy follows the DSH language preference (mirrored live onto `<html lang>`, re-rendering on switch without a reload) and falls back to the browser language; window notes share one paren glyph per locale so the grid's left edges stay aligned. Colors ride DSH semantic theme tokens with hardcoded fallbacks; all UI lives inside a shadow root (ADR 0001).
74
+ DSH Quota Watch is a personal DSH quota monitor built around a persistent floating capsule: a one-line `GLM x% · Copilot y%` summary that turns amber past 80% and red past 95%. Dragging it is grab-accurate (transform-based, rAF-batched) and free to place anywhere on the screen, with position remembered across restarts and viewport boundaries automatically clamped. Since 0.1.20 placement also steers clear of the Windows Desktop shell titlebar drag band (a layout-level `-webkit-app-region:drag` strip that swallows pointer input regardless of z-index — the same trap dsh-m fixed in 0.9.4; the capsule and the panel header consume `--dsh-windows-titlebar-height` the way the shell's own floats do, and positions saved while stuck self-heal below the band on boot; Web, without the variable, is byte-identical). Touch devices drag directly (wider slop against accidental drags), the hit area is padded to 44px and placement respects `env(safe-area-inset-*)`. Clicking the capsule opens a floating glass panel whose overview reuses the classic 0.0.18 summary rows (label · bar · percent · extra · chevron) and whose detail pages replicate the 0.0.18 big numbers, window grid and per-model list; the panel closes via ✕, Esc or an outside click, and viewports ≤480px dock it to the bottom as a sheet. The panel footer holds two equal-width buttons — sidebar-card visibility and an immediate provider re-probe — replacing the retired right-click/long-press menu, so the capsule itself carries no hidden gestures. A sidebar card remains an opportunistic extra entry on web profiles, opted in from that footer button, and silently disappears whenever the sidebar does — collapsed web sidebars and the desktop profile are covered by the floating capsule alone. Credentials are resolved on every host-side probe, only normalized values reach the browser, no usage history is kept, and a Copilot balance is never inferred from credits used. UI copy follows the DSH language preference (mirrored live onto `<html lang>`, re-rendering on switch without a reload) and falls back to the browser language; window notes share one paren glyph per locale so the grid's left edges stay aligned. Colors ride DSH semantic theme tokens with hardcoded fallbacks; all UI lives inside a shadow root (ADR 0001).
package/lib/client.js CHANGED
@@ -107,6 +107,11 @@ function clampPoint(point, viewport, size = 0, insets = ZERO_INSETS) {
107
107
  y: Math.min(Math.max(point.y, minY), maxY)
108
108
  };
109
109
  }
110
+ function titlebarTopInset({ titlebar = false, fullscreen = false, height } = {}) {
111
+ if (!titlebar || fullscreen) return 0;
112
+ const value = Number.parseFloat(height);
113
+ return Number.isFinite(value) && value > 0 ? value : 0;
114
+ }
110
115
  function loadFloatPosition(storage) {
111
116
  try {
112
117
  const raw = storage.getItem(FLOAT_POSITION_KEY);
@@ -551,11 +556,23 @@ function mountQuotaCard({
551
556
  const safeAreaProbe = text(doc, "div", "", "");
552
557
  safeAreaProbe.style.cssText = "position:fixed;visibility:hidden;pointer-events:none;width:0;height:0;overflow:hidden;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);";
553
558
  floatRoot.append(safeAreaProbe);
559
+ const readTitlebarInset = () => {
560
+ try {
561
+ const html = doc.documentElement;
562
+ return titlebarTopInset({
563
+ titlebar: html.hasAttribute("data-windows-titlebar"),
564
+ fullscreen: html.hasAttribute("data-fullscreen"),
565
+ height: parseInset(win.getComputedStyle(html)?.getPropertyValue("--dsh-windows-titlebar-height"))
566
+ });
567
+ } catch {
568
+ return 0;
569
+ }
570
+ };
554
571
  const readInsets = () => {
555
572
  try {
556
573
  const style2 = win.getComputedStyle(safeAreaProbe);
557
574
  return {
558
- top: parseInset(style2?.paddingTop),
575
+ top: Math.max(parseInset(style2?.paddingTop), readTitlebarInset()),
559
576
  right: parseInset(style2?.paddingRight),
560
577
  bottom: parseInset(style2?.paddingBottom),
561
578
  left: parseInset(style2?.paddingLeft)
@@ -918,7 +935,8 @@ function mountQuotaCard({
918
935
  if (left + width > viewWidth - 8) left = rect.left - width - 8;
919
936
  if (left + width > viewWidth - 8) left = viewWidth - width - 8;
920
937
  left = Math.max(8, left);
921
- const top = Math.min(Math.max(rect.top, 8), Math.max(8, viewHeight - height - 8));
938
+ const minTop = MARGIN + readInsets().top;
939
+ const top = Math.min(Math.max(rect.top, minTop), Math.max(minTop, viewHeight - height - 8));
922
940
  panel.style.left = `${Math.round(left)}px`;
923
941
  panel.style.top = `${Math.round(top)}px`;
924
942
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iasiv5/dsh-quota-watch",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "DSH Web sidebar monitor for GLM Coding Plan quotas and GitHub Copilot credits",
5
5
  "type": "module",
6
6
  "main": "./src/host.mjs",
@@ -31,6 +31,20 @@ export function clampPoint(point, viewport, size = 0, insets = ZERO_INSETS) {
31
31
  }
32
32
  }
33
33
 
34
+ // Windows Desktop shell titlebar contract (0.1.20): the shell runs
35
+ // `titleBarStyle:hidden + titleBarOverlay`, so the top strip of the window is a
36
+ // full-width `-webkit-app-region:drag` band that swallows pointer input by
37
+ // LAYOUT — z-index and paint order are irrelevant — and a capsule parked inside
38
+ // it can never be grabbed or clicked again. The shell flags the band on
39
+ // <html> with `data-windows-titlebar` + `--dsh-windows-titlebar-height` and
40
+ // clears it under `data-fullscreen`; its own floats clamp to exactly this
41
+ // value. Everything else (DSH Web, browsers, jsdom) yields 0 — zero drift.
42
+ export function titlebarTopInset({ titlebar = false, fullscreen = false, height } = {}) {
43
+ if (!titlebar || fullscreen) return 0
44
+ const value = Number.parseFloat(height)
45
+ return Number.isFinite(value) && value > 0 ? value : 0
46
+ }
47
+
34
48
  export function loadFloatPosition(storage) {
35
49
  try {
36
50
  const raw = storage.getItem(FLOAT_POSITION_KEY)
package/src/client.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CLIENT_POLL_INTERVAL_MS, CLIENT_ROUTES } from './shared.mjs'
2
2
  import { remainingToUsed } from './core/adapters.mjs'
3
- import { MARGIN, clampPoint, loadFloatPosition, loadSurfaceFlags, saveFloatPosition, saveSurfaceFlags } from './client/prefs.mjs'
3
+ import { MARGIN, clampPoint, loadFloatPosition, loadSurfaceFlags, saveFloatPosition, saveSurfaceFlags, titlebarTopInset } from './client/prefs.mjs'
4
4
  import { DRAG_SLOP_MOUSE, DRAG_SLOP_TOUCH, parseInset, clampFrame, dragSlop, grabOffset } from './client/drag.mjs'
5
5
 
6
6
  export const name = 'quota-watch-client'
@@ -450,7 +450,8 @@ export function mountQuotaCard({
450
450
  const rect = surface.getBoundingClientRect()
451
451
  return rect.width > 0 ? { width: rect.width, height: rect.height } : FALLBACK_SIZE
452
452
  }
453
- /** Position = user preferred {x, y} clamped to viewport & safe-area insets. */
453
+ /** Position = user preferred {x, y} clamped to viewport, safe-area insets
454
+ * and the Windows shell titlebar band. */
454
455
  const applyPosition = () => {
455
456
  const viewWidth = win.innerWidth ?? 1024
456
457
  const viewHeight = win.innerHeight ?? 768
@@ -471,13 +472,37 @@ export function mountQuotaCard({
471
472
  const safeAreaProbe = text(doc, 'div', '', '')
472
473
  safeAreaProbe.style.cssText = 'position:fixed;visibility:hidden;pointer-events:none;width:0;height:0;overflow:hidden;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);'
473
474
  floatRoot.append(safeAreaProbe)
475
+ // Windows Desktop titlebar band (0.1.20): with `titleBarStyle:hidden +
476
+ // titleBarOverlay` the top strip of the window is the shell's full-width
477
+ // `-webkit-app-region:drag` band — it wins pointer input by layout, ignoring
478
+ // z-index, so a capsule dropped there is stuck for good (un-grabbable,
479
+ // un-clickable, and the persisted position kept re-parking it). Folding the
480
+ // band into the top inset routes it through every clamp path at once: live
481
+ // drag frames, release docking, persisted-position restore and the default
482
+ // dock — and mount-time clamping also self-heals positions saved by 0.1.19.
483
+ // Same contract the shell's own floats consume: `data-windows-titlebar` +
484
+ // `--dsh-windows-titlebar-height` on <html>, dropped under `data-fullscreen`.
485
+ // DSH Web / browsers have neither attribute nor variable → 0, zero drift.
486
+ const readTitlebarInset = () => {
487
+ try {
488
+ const html = doc.documentElement
489
+ return titlebarTopInset({
490
+ titlebar: html.hasAttribute('data-windows-titlebar'),
491
+ fullscreen: html.hasAttribute('data-fullscreen'),
492
+ height: parseInset(win.getComputedStyle(html)?.getPropertyValue('--dsh-windows-titlebar-height')),
493
+ })
494
+ } catch {
495
+ return 0
496
+ }
497
+ }
474
498
  /** Live env(safe-area-inset-*) reading — re-probed on every use, so window
475
- * chrome changes (rotation, URL-bar collapse) need no explicit invalidation. */
499
+ * chrome changes (rotation, URL-bar collapse) need no explicit invalidation.
500
+ * `top` is the larger of the safe-area inset and the shell titlebar band. */
476
501
  const readInsets = () => {
477
502
  try {
478
503
  const style = win.getComputedStyle(safeAreaProbe)
479
504
  return {
480
- top: parseInset(style?.paddingTop),
505
+ top: Math.max(parseInset(style?.paddingTop), readTitlebarInset()),
481
506
  right: parseInset(style?.paddingRight),
482
507
  bottom: parseInset(style?.paddingBottom),
483
508
  left: parseInset(style?.paddingLeft),
@@ -892,7 +917,11 @@ export function mountQuotaCard({
892
917
  if (left + width > viewWidth - 8) left = rect.left - width - 8
893
918
  if (left + width > viewWidth - 8) left = viewWidth - width - 8
894
919
  left = Math.max(8, left)
895
- const top = Math.min(Math.max(rect.top, 8), Math.max(8, viewHeight - height - 8))
920
+ // The panel header carries the close button: keep its top edge below the
921
+ // shell titlebar band (and safe-area top) for the same reason as the
922
+ // capsule — layout-level drag hits ignore z-index. Web fallback: 8px, as before.
923
+ const minTop = MARGIN + readInsets().top
924
+ const top = Math.min(Math.max(rect.top, minTop), Math.max(minTop, viewHeight - height - 8))
896
925
  panel.style.left = `${Math.round(left)}px`
897
926
  panel.style.top = `${Math.round(top)}px`
898
927
  }