@xulongzhe/clawbench-desktop-linux-x64-payload 0.130.0 → 0.132.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.130.0",
3
+ "version": "0.132.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-1cb428de31b5f0bc3d7bc1630d517ca988f097e571284eae2acc3034038dc6a8"
3
+ "shell": "sha256-8f8a03cd18c8ee4b2e71da17976967d66f4d3c1fe784b5724fe1f14596868002"
4
4
  }
@@ -0,0 +1,335 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>ClawBench 标注</title>
7
+ <!--
8
+ Annotation overlay document.
9
+
10
+ Loaded by main/annotateOverlay.ts into a transparent, always-on-top window
11
+ that covers the shared display. It draws on a canvas over a fully
12
+ transparent page, so the desktop underneath stays visible.
13
+
14
+ Every stroke is ALSO reported to the main process. On Windows/macOS the
15
+ window is excluded from the screen capture and those frames are what the
16
+ audience draws from; on Linux the window is part of the picture and the
17
+ main process drops them (forwarding would double every stroke). The page
18
+ does not need to know which — it always reports.
19
+
20
+ `?hideBar=0` (set by main/annotateOverlay.ts where the window is excluded
21
+ from the capture) keeps the toolbar on screen permanently: the audience
22
+ cannot see it there, so fading it out would only hide the tool from the
23
+ person using it. On Linux the bar IS part of the shared picture, so it
24
+ still fades — and any of these brings it back: drawing, moving the pointer
25
+ near the bottom edge, focusing the window, or clicking 标注画面 again in
26
+ the meeting footer (relayed as `annotate:wake`).
27
+
28
+ Self-contained on purpose: no bundler, no fonts, no network. The window
29
+ must paint the instant it is shown.
30
+ -->
31
+ <style>
32
+ :root {
33
+ color-scheme: dark;
34
+ --bar-bg: rgba(22, 24, 28, 0.92);
35
+ --bar-border: rgba(255, 255, 255, 0.16);
36
+ --bar-fg: #f5f6f8;
37
+ }
38
+ html,
39
+ body {
40
+ margin: 0;
41
+ padding: 0;
42
+ width: 100%;
43
+ height: 100%;
44
+ overflow: hidden;
45
+ background: transparent;
46
+ cursor: crosshair;
47
+ user-select: none;
48
+ -webkit-user-select: none;
49
+ font-family: system-ui, -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
50
+ }
51
+ #ink {
52
+ position: fixed;
53
+ inset: 0;
54
+ width: 100%;
55
+ height: 100%;
56
+ touch-action: none;
57
+ }
58
+ /* The toolbar floats at the bottom and fades out while the publisher is
59
+ drawing, so it does not sit in the middle of the shared picture (it is
60
+ visible to the audience on platforms where the window is captured).
61
+ See the `hideBar` query flag above: where it is NOT captured it stays. */
62
+ #bar {
63
+ position: fixed;
64
+ left: 50%;
65
+ bottom: 24px;
66
+ transform: translateX(-50%);
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 6px;
70
+ padding: 6px 10px;
71
+ border-radius: 999px;
72
+ background: var(--bar-bg);
73
+ border: 1px solid var(--bar-border);
74
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
75
+ transition: opacity 0.18s ease;
76
+ cursor: default;
77
+ }
78
+ #bar.hidden {
79
+ opacity: 0;
80
+ pointer-events: none;
81
+ }
82
+ #bar button {
83
+ display: inline-flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ min-width: 30px;
87
+ height: 30px;
88
+ padding: 0 8px;
89
+ border: none;
90
+ border-radius: 999px;
91
+ background: rgba(255, 255, 255, 0.1);
92
+ color: var(--bar-fg);
93
+ font-size: 12px;
94
+ cursor: pointer;
95
+ }
96
+ #bar button:hover {
97
+ background: rgba(255, 255, 255, 0.2);
98
+ }
99
+ #bar button.on {
100
+ background: rgba(88, 166, 255, 0.85);
101
+ color: #fff;
102
+ }
103
+ .swatch {
104
+ width: 18px;
105
+ height: 18px;
106
+ min-width: 18px;
107
+ padding: 0;
108
+ border-radius: 50%;
109
+ border: 2px solid transparent;
110
+ }
111
+ .swatch.on {
112
+ border-color: #fff;
113
+ }
114
+ .sep {
115
+ width: 1px;
116
+ height: 18px;
117
+ background: var(--bar-border);
118
+ }
119
+ #close {
120
+ background: rgba(207, 34, 46, 0.85);
121
+ }
122
+ </style>
123
+ </head>
124
+ <body>
125
+ <canvas id="ink"></canvas>
126
+ <div id="bar">
127
+ <button id="pen" class="on" title="画笔">✎</button>
128
+ <button id="laser" title="激光笔">◉</button>
129
+ <span class="sep"></span>
130
+ <button class="swatch on" data-color="#ff3b30" style="background: #ff3b30" title="红"></button>
131
+ <button class="swatch" data-color="#ffd60a" style="background: #ffd60a" title="黄"></button>
132
+ <button class="swatch" data-color="#34c759" style="background: #34c759" title="绿"></button>
133
+ <button class="swatch" data-color="#0a84ff" style="background: #0a84ff" title="蓝"></button>
134
+ <span class="sep"></span>
135
+ <button id="undo" title="撤销">↶</button>
136
+ <button id="clear" title="清空">✕</button>
137
+ <button id="close" title="关闭标注">关闭</button>
138
+ </div>
139
+ <script>
140
+ (() => {
141
+ const canvas = document.getElementById('ink')
142
+ const ctx = canvas.getContext('2d')
143
+ const bar = document.getElementById('bar')
144
+ const host = window.AnnotateHost || { ink() {}, close() {} }
145
+ /**
146
+ * Whether the bar may fade out. False when the overlay is excluded from
147
+ * the capture: the audience cannot see the bar there, so hiding it only
148
+ * makes the tool vanish for the host (and a faded bar cannot be
149
+ * clicked — the window is the whole screen, there is nothing else to
150
+ * click).
151
+ */
152
+ const autoHideBar = new URLSearchParams(location.search).get('hideBar') !== '0'
153
+
154
+ let mode = 'pen'
155
+ let color = '#ff3b30'
156
+ const width = 4
157
+ /** Finished strokes, in normalised 0..1 coordinates. */
158
+ let strokes = []
159
+ let current = null
160
+ /** Laser marker: {x, y, at} or null. */
161
+ let laser = null
162
+ let dpr = 1
163
+
164
+ function resize() {
165
+ dpr = window.devicePixelRatio || 1
166
+ canvas.width = Math.round(window.innerWidth * dpr)
167
+ canvas.height = Math.round(window.innerHeight * dpr)
168
+ redraw()
169
+ }
170
+
171
+ function drawStroke(s) {
172
+ if (s.points.length === 0) return
173
+ ctx.strokeStyle = s.color
174
+ ctx.lineWidth = width * dpr
175
+ ctx.lineCap = 'round'
176
+ ctx.lineJoin = 'round'
177
+ ctx.beginPath()
178
+ const p0 = s.points[0]
179
+ ctx.moveTo(p0.x * canvas.width, p0.y * canvas.height)
180
+ for (let i = 1; i < s.points.length; i++) {
181
+ const p = s.points[i]
182
+ ctx.lineTo(p.x * canvas.width, p.y * canvas.height)
183
+ }
184
+ ctx.stroke()
185
+ }
186
+
187
+ function redraw() {
188
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
189
+ for (const s of strokes) drawStroke(s)
190
+ if (current) drawStroke(current)
191
+ if (laser) {
192
+ ctx.beginPath()
193
+ ctx.fillStyle = 'rgba(255, 59, 48, 0.9)'
194
+ ctx.arc(laser.x * canvas.width, laser.y * canvas.height, 9 * dpr, 0, Math.PI * 2)
195
+ ctx.fill()
196
+ }
197
+ }
198
+
199
+ function send(msg) {
200
+ host.ink(JSON.stringify(msg))
201
+ }
202
+
203
+ function normalise(ev) {
204
+ return {
205
+ x: Math.min(1, Math.max(0, ev.clientX / window.innerWidth)),
206
+ y: Math.min(1, Math.max(0, ev.clientY / window.innerHeight)),
207
+ }
208
+ }
209
+
210
+ function hideBarSoon() {
211
+ if (!autoHideBar) return
212
+ clearTimeout(hideBarSoon.timer)
213
+ hideBarSoon.timer = setTimeout(() => bar.classList.add('hidden'), 2600)
214
+ }
215
+
216
+ function wake() {
217
+ clearTimeout(hideBarSoon.timer)
218
+ bar.classList.remove('hidden')
219
+ hideBarSoon()
220
+ }
221
+
222
+ canvas.addEventListener('pointerdown', (ev) => {
223
+ canvas.setPointerCapture(ev.pointerId)
224
+ wake()
225
+ const p = normalise(ev)
226
+ if (mode === 'laser') {
227
+ laser = { x: p.x, y: p.y }
228
+ send({ t: 'laser', x: p.x, y: p.y, on: true })
229
+ redraw()
230
+ return
231
+ }
232
+ current = { color, points: [p] }
233
+ send({ t: 'ink', phase: 'start', x: p.x, y: p.y, color, width })
234
+ redraw()
235
+ })
236
+
237
+ canvas.addEventListener('pointermove', (ev) => {
238
+ if (mode === 'laser') {
239
+ if (!laser) return
240
+ const p = normalise(ev)
241
+ laser = { x: p.x, y: p.y }
242
+ send({ t: 'laser', x: p.x, y: p.y, on: true })
243
+ redraw()
244
+ return
245
+ }
246
+ if (!current) return
247
+ const p = normalise(ev)
248
+ current.points.push(p)
249
+ send({ t: 'ink', phase: 'move', x: p.x, y: p.y, color, width })
250
+ redraw()
251
+ })
252
+
253
+ function endStroke(ev) {
254
+ if (mode === 'laser') {
255
+ if (!laser) return
256
+ send({ t: 'laser', x: 0, y: 0, on: false })
257
+ laser = null
258
+ redraw()
259
+ return
260
+ }
261
+ if (!current) return
262
+ const p = ev && ev.clientX !== undefined ? normalise(ev) : current.points[current.points.length - 1]
263
+ send({ t: 'ink', phase: 'end', x: p.x, y: p.y, color, width })
264
+ strokes.push(current)
265
+ current = null
266
+ redraw()
267
+ }
268
+
269
+ canvas.addEventListener('pointerup', endStroke)
270
+ canvas.addEventListener('pointercancel', endStroke)
271
+
272
+ document.getElementById('pen').addEventListener('click', (e) => {
273
+ mode = 'pen'
274
+ e.currentTarget.classList.add('on')
275
+ document.getElementById('laser').classList.remove('on')
276
+ wake()
277
+ })
278
+ document.getElementById('laser').addEventListener('click', (e) => {
279
+ mode = 'laser'
280
+ e.currentTarget.classList.add('on')
281
+ document.getElementById('pen').classList.remove('on')
282
+ wake()
283
+ })
284
+ for (const sw of document.querySelectorAll('.swatch')) {
285
+ sw.addEventListener('click', () => {
286
+ color = sw.dataset.color
287
+ for (const other of document.querySelectorAll('.swatch')) other.classList.remove('on')
288
+ sw.classList.add('on')
289
+ mode = 'pen'
290
+ document.getElementById('pen').classList.add('on')
291
+ document.getElementById('laser').classList.remove('on')
292
+ wake()
293
+ })
294
+ }
295
+ document.getElementById('undo').addEventListener('click', () => {
296
+ strokes.pop()
297
+ send({ t: 'ink-undo' })
298
+ redraw()
299
+ wake()
300
+ })
301
+ document.getElementById('clear').addEventListener('click', () => {
302
+ strokes = []
303
+ current = null
304
+ send({ t: 'ink-clear' })
305
+ redraw()
306
+ wake()
307
+ })
308
+ document.getElementById('close').addEventListener('click', () => host.close())
309
+
310
+ document.addEventListener('keydown', (ev) => {
311
+ if (ev.key === 'Escape') host.close()
312
+ if ((ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === 'z') {
313
+ strokes.pop()
314
+ send({ t: 'ink-undo' })
315
+ redraw()
316
+ }
317
+ })
318
+
319
+ // Keep the toolbar reachable without a permanent strip of chrome: any
320
+ // pointer movement near the bottom edge brings it back. The meeting
321
+ // footer's 标注画面 button does the same from outside (onWake), and so
322
+ // does focusing the window — the host may come back from another app.
323
+ document.addEventListener('pointermove', (ev) => {
324
+ if (ev.clientY > window.innerHeight - 120) wake()
325
+ })
326
+ window.addEventListener('focus', wake)
327
+ if (typeof host.onWake === 'function') host.onWake(wake)
328
+
329
+ window.addEventListener('resize', resize)
330
+ resize()
331
+ hideBarSoon()
332
+ })()
333
+ </script>
334
+ </body>
335
+ </html>
Binary file
@@ -38,11 +38,28 @@
38
38
  a disc whenever the window is left taller than the content. */
39
39
  height: auto;
40
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%;
41
+ /* max-content + no shrink, NOT `fit-content` and NOT `max-width: 100%`.
42
+
43
+ The page reports its natural size so the window can hug the capsule
44
+ (`measure()` reads `scrollWidth`). Both of those older declarations clamp
45
+ the pill to the CURRENT window width, and that makes the measurement
46
+ self-confirming: while the window is too narrow the pill is squeezed, its
47
+ right padding is eaten by the overflowing row, `scrollWidth` then reports
48
+ the squeezed width, and the page asks for exactly the width it already
49
+ has — the capsule can never grow back.
50
+
51
+ Measured (Playwright, the bundled Chromium, "执行中 3 · 未读 2"): growing
52
+ from a 120px window converged on 162px instead of 177px, leaving the last
53
+ glyph 0.4px past the pill's edge — the text visibly touching / spilling
54
+ over the capsule wall. With max-content it requests 177px and keeps the
55
+ documented 15px text gap, and it still shrinks (400 → 88) when the
56
+ counters drop to zero.
57
+
58
+ `flex: 0 0 auto` is the same invariant from the other side: `#pill` is a
59
+ flex item of `#hit`, so without it the flex base size could still be
60
+ shrunk and `max-content` would be advisory only. */
61
+ width: max-content;
62
+ flex: 0 0 auto;
46
63
  display: flex; align-items: center; gap: 7px;
47
64
  /* Asymmetric on purpose. Do NOT "fix" this back to symmetric.
48
65
 
@@ -64,18 +81,29 @@
64
81
  border-radius: 999px;
65
82
  background: var(--bg);
66
83
  color: var(--text);
67
- box-shadow: 0 6px 22px rgba(0, 0, 0, .38), 0 0 0 1px rgba(255, 255, 255, .07) inset;
84
+ /* Inset ring ONLY — no drop shadow, ever.
85
+
86
+ The window is sized to the pill plus a 4px margin (`measure()` reports
87
+ `scrollWidth + 8`), and a transparent window is still rectangular to the
88
+ compositor. A shadow with any blur radius therefore cannot fall away: it
89
+ is clipped at the window edge and paints that 4px ring, which reads as a
90
+ grey semi-transparent box framing the capsule on anything but a dark
91
+ wallpaper. The surround must stay fully transparent — the pill's own
92
+ translucent background is the only thing that should be visible. */
93
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, .07) inset;
68
94
  /* Deliberately NOT `-webkit-app-region: drag`: that hands the surface to the
69
95
  OS as a title bar, so right-click opens the Windows system menu instead of
70
96
  ours and no DOM click reaches the page. Dragging is driven from the main
71
97
  process instead (see main/floating.ts). */
72
98
  transition: transform .12s ease, box-shadow .12s ease;
73
99
  }
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; }
100
+ /* Feedback without a halo: the lift is kept, the ring just brightens. */
101
+ body.hover #pill { transform: translateY(-1px); box-shadow: 0 0 0 1px rgba(255, 255, 255, .16) inset; }
75
102
  /* Awaiting approval is the one state worth flagging on the frame itself: a
76
103
  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); }
104
+ glance from across the desk. The 1px ring follows the pill's own outline —
105
+ unlike a blurred shadow it never paints the margin as a box. */
106
+ body.needs-me #pill { box-shadow: 0 0 0 1px var(--pending); }
79
107
 
80
108
  #logo {
81
109
  flex: 0 0 auto;
Binary file