@xulongzhe/clawbench-desktop-linux-x64-payload 0.127.0 → 0.128.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xulongzhe/clawbench-desktop-linux-x64-payload",
3
- "version": "0.127.0",
3
+ "version": "0.128.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/clawbench-dev/clawbench.git"
package/payload.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "electron": "44.4.3",
3
- "shell": "sha256-c6c6aeac1bb7aabd98de1d55a3127dff1c331139ae30f4dc7bfadc2e88caf73b"
3
+ "shell": "sha256-1cb428de31b5f0bc3d7bc1630d517ca988f097e571284eae2acc3034038dc6a8"
4
4
  }
Binary file
@@ -0,0 +1,258 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <!-- The capsule is a fixed, self-contained document: no remote content, no forms,
6
+ no navigation. `file:` is allowed only for the app icon shipped next to
7
+ login.html; counters arrive over IPC, never over the network. -->
8
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' file: data:; style-src 'unsafe-inline'; script-src 'unsafe-inline'">
9
+ <title>ClawBench</title>
10
+ <style>
11
+ :root {
12
+ --bg: rgba(24, 26, 31, 0.92);
13
+ --text: #f2f4f8;
14
+ --muted: #9aa3b2;
15
+ --accent: #4c8dff;
16
+ /* Status colors are fixed semantic colors on both platforms — they do NOT
17
+ follow the theme, so a running session reads the same on any of the 20+
18
+ themes. See docs/plans/2026-08-28-android-floating-panel.md. */
19
+ --running: #35c05e;
20
+ --pending: #f2b415;
21
+ --unread: #3d8bfd;
22
+ }
23
+ * { box-sizing: border-box; }
24
+ html, body {
25
+ margin: 0; padding: 0; width: 100%; height: 100%;
26
+ background: transparent; overflow: hidden;
27
+ user-select: none; -webkit-user-select: none; cursor: default;
28
+ }
29
+ body { font-family: system-ui, "Segoe UI", "Microsoft YaHei UI", "PingFang SC", sans-serif; }
30
+
31
+ /* A transparent window is rectangular to the compositor: the padding ring is
32
+ dead space, so keep it small and let the pill own the hit area. */
33
+ #hit { width: 100%; height: 100%; padding: 4px; display: flex; align-items: center; }
34
+
35
+ #pill {
36
+ /* Intrinsic height, not 100%: a box stretched to the window would be
37
+ measured as the window (the width case below) and the capsule would grow into
38
+ a disc whenever the window is left taller than the content. */
39
+ height: auto;
40
+ padding-top: 5px; padding-bottom: 5px;
41
+ /* fit-content, not the default block width: the page reports its natural
42
+ size so the window can hug the capsule, and a box stretched to the window
43
+ would measure the window instead — the capsule could then grow but never
44
+ shrink back after the counters drop to zero. */
45
+ width: fit-content; max-width: 100%;
46
+ display: flex; align-items: center; gap: 7px;
47
+ /* Asymmetric on purpose. Do NOT "fix" this back to symmetric.
48
+
49
+ The two ends hold different KINDS of thing, and a filled shape tolerates
50
+ a tighter edge than a glyph does. On the left sits a 26px solid disc
51
+ whose own outline is the visual boundary, so 6px reads as flush. On the
52
+ right sits text, where the same gap makes the last glyph look pinned to
53
+ the capsule wall — the eye measures to the glyph's ink, not to the
54
+ element box.
55
+
56
+ The end radius is NOT the reason: at 36px tall the 18px curve insets
57
+ only 1.6px at the text's top and bottom edges, so geometry cannot
58
+ account for the difference. It is optical weight.
59
+
60
+ Verified by measuring the DOM in all three states (counters / idle only /
61
+ all three): the icon keeps a 6px gap and the text a 15px gap regardless
62
+ of how wide the content gets. */
63
+ padding-left: 6px; padding-right: 15px;
64
+ border-radius: 999px;
65
+ background: var(--bg);
66
+ color: var(--text);
67
+ box-shadow: 0 6px 22px rgba(0, 0, 0, .38), 0 0 0 1px rgba(255, 255, 255, .07) inset;
68
+ /* Deliberately NOT `-webkit-app-region: drag`: that hands the surface to the
69
+ OS as a title bar, so right-click opens the Windows system menu instead of
70
+ ours and no DOM click reaches the page. Dragging is driven from the main
71
+ process instead (see main/floating.ts). */
72
+ transition: transform .12s ease, box-shadow .12s ease;
73
+ }
74
+ body.hover #pill { transform: translateY(-1px); box-shadow: 0 9px 26px rgba(0,0,0,.46), 0 0 0 1px rgba(255,255,255,.12) inset; }
75
+ /* Awaiting approval is the one state worth flagging on the frame itself: a
76
+ blocked session is otherwise indistinguishable from a running one at a
77
+ glance from across the desk. */
78
+ body.needs-me #pill { box-shadow: 0 6px 22px rgba(0, 0, 0, .38), 0 0 0 1px var(--pending); }
79
+
80
+ #logo {
81
+ flex: 0 0 auto;
82
+ width: 26px; height: 26px; border-radius: 50%;
83
+ background: color-mix(in srgb, var(--text) 88%, transparent);
84
+ display: flex; align-items: center; justify-content: center;
85
+ overflow: hidden;
86
+ }
87
+ #logo img { width: 100%; height: 100%; object-fit: contain; }
88
+ #logo i { width: 11px; height: 11px; border-radius: 3px; background: #14161b; display: block; }
89
+
90
+ #lines { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
91
+ .row { display: flex; align-items: center; gap: 5px; white-space: nowrap; font-variant-numeric: tabular-nums; }
92
+ .row.primary { font-size: 13px; font-weight: 650; line-height: 1.15; }
93
+ .row.secondary { font-size: 12px; font-weight: 500; color: var(--muted); line-height: 1.15; }
94
+ .seg { display: inline-flex; align-items: center; gap: 4px; }
95
+ /* A zero counter is hidden rather than shown greyed: the capsule's whole job
96
+ is "is there something to look at", and three zeros is noise. Matches the
97
+ Android capsule ("为 0 的计数不显示"). */
98
+ .seg[hidden] { display: none; }
99
+ .dot { flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; }
100
+ .dot.running { background: var(--running); }
101
+ .dot.pending { background: var(--pending); }
102
+ .dot.unread { background: var(--unread); }
103
+ .n { color: var(--text); font-weight: 650; }
104
+ .sep { opacity: .45; padding: 0 1px; }
105
+ .sep[hidden] { display: none; }
106
+ #idle { color: var(--muted); font-size: 13px; font-weight: 500; }
107
+ #idle[hidden] { display: none; }
108
+ .row[hidden] { display: none; }
109
+
110
+ /* Running breathes and pending pulses: both mean "come back soon", and a
111
+ static dot out of the corner of the eye is easy to miss. */
112
+ body.busy .dot.running { animation: breathe 2s ease-in-out infinite; }
113
+ body.needs-me .dot.pending { animation: pulse 1.5s ease-in-out infinite; }
114
+ @keyframes breathe {
115
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(53, 192, 94, .45); }
116
+ 50% { box-shadow: 0 0 0 4px rgba(53, 192, 94, 0); }
117
+ }
118
+ @keyframes pulse {
119
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(242, 180, 21, .55); }
120
+ 50% { box-shadow: 0 0 0 5px rgba(242, 180, 21, 0); }
121
+ }
122
+ </style>
123
+ </head>
124
+ <body>
125
+ <div id="hit">
126
+ <div id="pill">
127
+ <div id="logo"><i></i></div>
128
+ <div id="lines">
129
+ <div class="row primary" id="row-primary">
130
+ <span class="seg" id="seg-running"><span class="dot running"></span><span id="lbl-running">Running</span> <span class="n" id="n-running">0</span></span>
131
+ <span class="seg" id="seg-pending"><span class="dot pending"></span><span id="lbl-pending">Pending</span> <span class="n" id="n-pending">0</span></span>
132
+ <span class="seg" id="seg-unread"><span class="dot unread"></span><span id="lbl-unread">Unread</span> <span class="n" id="n-unread">0</span></span>
133
+ <span id="idle">Idle</span>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ <script>
139
+ (function () {
140
+ 'use strict';
141
+ var host = window.FloatingHost || {};
142
+ var $ = function (id) { return document.getElementById(id); };
143
+ var els = {
144
+ pill: $('pill'), hit: $('hit'), logo: $('logo'),
145
+ running: $('seg-running'), pending: $('seg-pending'), unread: $('seg-unread'),
146
+ nRunning: $('n-running'), nPending: $('n-pending'), nUnread: $('n-unread'),
147
+ lRunning: $('lbl-running'), lPending: $('lbl-pending'), lUnread: $('lbl-unread'),
148
+ idle: $('idle')
149
+ };
150
+
151
+ function hexToRgba(color, alpha) {
152
+ if (typeof color !== 'string') return null;
153
+ var m = /^#([0-9a-f]{3,8})$/i.exec(color.trim());
154
+ if (!m) return null;
155
+ var h = m[1];
156
+ if (h.length === 3 || h.length === 4) h = h.split('').map(function (x) { return x + x; }).join('');
157
+ return 'rgba(' + parseInt(h.slice(0, 2), 16) + ',' + parseInt(h.slice(2, 4), 16) +
158
+ ',' + parseInt(h.slice(4, 6), 16) + ',' + alpha + ')';
159
+ }
160
+
161
+ function apply(state) {
162
+ if (!state) return;
163
+ var s = state.stats || {};
164
+ var l = state.labels || {};
165
+ var c = state.colors || {};
166
+ var n = { running: s.running | 0, pending: s.pending | 0, unread: s.unread | 0 };
167
+
168
+ els.nRunning.textContent = n.running;
169
+ els.nPending.textContent = n.pending;
170
+ els.nUnread.textContent = n.unread;
171
+ if (l.running) els.lRunning.textContent = l.running;
172
+ if (l.pending) els.lPending.textContent = l.pending;
173
+ if (l.unread) els.lUnread.textContent = l.unread;
174
+ if (l.idle) els.idle.textContent = l.idle;
175
+ document.documentElement.lang = state.lang === 'en' ? 'en' : 'zh';
176
+ if (l.hint) els.pill.title = l.hint;
177
+
178
+ var idle = !n.running && !n.pending && !n.unread;
179
+ els.running.hidden = idle || n.running === 0;
180
+ els.pending.hidden = n.pending === 0;
181
+ els.unread.hidden = n.unread === 0;
182
+ els.idle.hidden = !idle;
183
+ document.body.classList.toggle('needs-me', n.pending > 0);
184
+ document.body.classList.toggle('busy', n.running > 0);
185
+
186
+ var root = document.documentElement.style;
187
+ if (c.text) root.setProperty('--text', c.text);
188
+ if (c.textSecondary) root.setProperty('--muted', c.textSecondary);
189
+ if (c.accent) root.setProperty('--accent', c.accent);
190
+ // The Android bubble uses a solid theme background; a capsule floating over
191
+ // the desktop needs translucency or its hard edge reads as a sticker.
192
+ root.setProperty('--bg', (c.bg && hexToRgba(c.bg, 0.93)) || 'rgba(24, 26, 31, 0.92)');
193
+
194
+ if (state.logoUrl && !els.logo.dataset.set) {
195
+ els.logo.dataset.set = '1';
196
+ var img = document.createElement('img');
197
+ img.src = state.logoUrl;
198
+ img.alt = '';
199
+ img.onerror = function () { delete els.logo.dataset.set; img.remove(); };
200
+ els.logo.appendChild(img);
201
+ }
202
+ measure();
203
+ }
204
+
205
+ /** Tell the main process our natural size so the window hugs the capsule. */
206
+ function measure() {
207
+ var w = Math.ceil(els.pill.scrollWidth + 8);
208
+ var h = Math.ceil(els.pill.scrollHeight + 8);
209
+ var key = w + 'x' + h;
210
+ if (key === measure.last) return;
211
+ measure.last = key;
212
+ if (typeof host.resize === 'function') host.resize(w, h);
213
+ }
214
+
215
+ if (typeof host.onState === 'function') {
216
+ host.onState(apply);
217
+ // Listeners are installed: replay anything the main process queued during load.
218
+ if (typeof host.ready === 'function') host.ready();
219
+ }
220
+ els.pill.addEventListener('mouseenter', function () { document.body.classList.add('hover'); });
221
+ els.pill.addEventListener('mouseleave', function () { document.body.classList.remove('hover'); });
222
+
223
+ // Dragging: the page reports only where the grab happened. The main process
224
+ // follows the OS cursor and decides on release whether this was a drag or a
225
+ // click — it is the only side that can tell "the window moved" from "the mouse
226
+ // moved", and a click must open the app rather than do nothing.
227
+ //
228
+ // Pointer capture matters: the capsule is small, so the cursor leaves it almost
229
+ // immediately and without capture the release can land on another window and
230
+ // never reach this document — leaving the capsule glued to the cursor. The main
231
+ // process has a stillness backstop for the same case.
232
+ var dragging = false;
233
+ function endGesture() {
234
+ if (!dragging) return;
235
+ dragging = false;
236
+ if (typeof host.dragEnd === 'function') host.dragEnd();
237
+ }
238
+ els.pill.addEventListener('pointerdown', function (e) {
239
+ if (e.button !== 0) return;
240
+ e.preventDefault();
241
+ dragging = true;
242
+ try { els.pill.setPointerCapture(e.pointerId); } catch (err) { /* unsupported */ }
243
+ if (typeof host.dragStart === 'function') host.dragStart(e.clientX, e.clientY);
244
+ });
245
+ els.pill.addEventListener('pointerup', endGesture);
246
+ els.pill.addEventListener('pointercancel', endGesture);
247
+ els.pill.addEventListener('lostpointercapture', endGesture);
248
+ window.addEventListener('mouseup', endGesture);
249
+ window.addEventListener('blur', endGesture);
250
+ els.pill.addEventListener('contextmenu', function (e) {
251
+ e.preventDefault();
252
+ if (typeof host.context === 'function') host.context();
253
+ });
254
+ measure();
255
+ })();
256
+ </script>
257
+ </body>
258
+ </html>