@xulongzhe/clawbench-desktop-linux-x64-payload 0.102.0 → 0.104.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.102.0",
3
+ "version": "0.104.0",
4
4
  "description": "ClawBench desktop client — Linux x64 incremental-upgrade payload (app files only, no Electron runtime)",
5
5
  "files": [
6
6
  "payload.json",
package/payload.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "electron": "44.4.3",
3
- "shell": "sha256-7a0d4e5e71bac0e161f65aa81fd51ec1800cb11901d85e9052e991ab903bbbb3"
3
+ "shell": "sha256-c6c6aeac1bb7aabd98de1d55a3127dff1c331139ae30f4dc7bfadc2e88caf73b"
4
4
  }
Binary file
@@ -29,7 +29,14 @@
29
29
  connection_failed: 'Connection failed: ',
30
30
  connect_error: 'Unable to connect. Check address and network.',
31
31
  wrong_password: 'Wrong password',
32
- too_many_attempts: 'Too many attempts'
32
+ too_many_attempts: 'Too many attempts',
33
+ // Splash overlay (?splash=1). Wording mirrors Android's splash_status_*
34
+ // strings so the two platforms read identically.
35
+ splash_connecting: 'Connecting…',
36
+ splash_loading: 'Loading page…',
37
+ splash_rendering: 'Rendering…',
38
+ splash_initializing: 'Initializing app…',
39
+ splash_cancel: 'Cancel connection'
33
40
  },
34
41
  zh: {
35
42
  slogan: '从终端到桌面',
@@ -53,7 +60,14 @@
53
60
  connection_failed: '连接失败: ',
54
61
  connect_error: '无法连接到服务器,请检查地址和网络。',
55
62
  wrong_password: '密码错误',
56
- too_many_attempts: '尝试次数过多'
63
+ too_many_attempts: '尝试次数过多',
64
+ // Splash overlay (?splash=1). Wording mirrors Android's splash_status_*
65
+ // strings so the two platforms read identically.
66
+ splash_connecting: '正在连接…',
67
+ splash_loading: '正在加载页面…',
68
+ splash_rendering: '正在渲染…',
69
+ splash_initializing: '正在初始化应用…',
70
+ splash_cancel: '取消连接'
57
71
  }
58
72
  };
59
73
  var LANG = 'en';
@@ -1272,9 +1286,122 @@
1272
1286
  padding-bottom: env(safe-area-inset-bottom);
1273
1287
  }
1274
1288
  }
1289
+ /* ── Splash overlay (?splash=1) ──────────────────────────────────────────
1290
+ Shown by the main process while connecting to a server and booting the
1291
+ app. Mirrors Android's native splash: a circular logo with a rotating
1292
+ sweep ring, a stage line, and a cancel button. The ring reproduces
1293
+ android/app/src/main/res/drawable/splash_sweep.xml (a dim full ring plus a
1294
+ ~90° bright arc spun linearly over 1.6s). */
1295
+ body.splash-mode .content { display: none; }
1296
+
1297
+ .splash {
1298
+ position: fixed;
1299
+ inset: 0;
1300
+ display: none;
1301
+ flex-direction: column;
1302
+ align-items: center;
1303
+ justify-content: center;
1304
+ background: var(--bg-primary);
1305
+ z-index: 100;
1306
+ }
1307
+ body.splash-mode .splash { display: flex; }
1308
+
1309
+ /* Fade-out hook, called by the main process just before it hides the view. */
1310
+ .splash.is-fading { opacity: 0; transition: opacity 200ms ease; }
1311
+
1312
+ .splash-logo-wrapper {
1313
+ position: relative;
1314
+ width: 132px;
1315
+ height: 132px;
1316
+ }
1317
+
1318
+ /* Sweep ring: a dim circle with one bright arc, spun by rotating the box. */
1319
+ .splash-sweep {
1320
+ position: absolute;
1321
+ inset: 0;
1322
+ border-radius: 50%;
1323
+ border: 2px solid var(--border-color);
1324
+ opacity: 0.4;
1325
+ animation: splash-spin 1.6s linear infinite;
1326
+ }
1327
+ .splash-sweep::after {
1328
+ content: '';
1329
+ position: absolute;
1330
+ inset: -2px;
1331
+ border-radius: 50%;
1332
+ border: 3px solid transparent;
1333
+ border-top-color: var(--accent-color);
1334
+ border-right-color: var(--accent-color);
1335
+ /* Two adjacent edges approximate the native 90° sweep segment without an
1336
+ SVG, so no extra asset ships for this. */
1337
+ transform: rotate(45deg);
1338
+ }
1339
+
1340
+ @keyframes splash-spin {
1341
+ from { transform: rotate(0deg); }
1342
+ to { transform: rotate(360deg); }
1343
+ }
1344
+
1345
+ .splash-logo {
1346
+ position: absolute;
1347
+ top: 12px;
1348
+ left: 12px;
1349
+ width: 108px;
1350
+ height: 108px;
1351
+ border-radius: 50%;
1352
+ object-fit: cover;
1353
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
1354
+ }
1355
+
1356
+ .splash-title {
1357
+ margin-top: 24px;
1358
+ font-size: 20px;
1359
+ font-weight: 500;
1360
+ color: var(--text-primary);
1361
+ letter-spacing: 0.06em;
1362
+ opacity: 0.85;
1363
+ }
1364
+
1365
+ .splash-stage {
1366
+ margin-top: 12px;
1367
+ font-size: 14px;
1368
+ font-weight: 500;
1369
+ color: var(--text-muted);
1370
+ opacity: 0.7;
1371
+ }
1372
+
1373
+ .splash-cancel {
1374
+ margin-top: 28px;
1375
+ padding: 8px 16px;
1376
+ font-size: 13px;
1377
+ font-family: inherit;
1378
+ color: var(--text-hint);
1379
+ background: transparent;
1380
+ border: 1px solid var(--border-color);
1381
+ border-radius: 6px;
1382
+ cursor: pointer;
1383
+ transition: background 150ms ease, color 150ms ease;
1384
+ }
1385
+ .splash-cancel:hover {
1386
+ background: var(--bg-tertiary);
1387
+ color: var(--text-primary);
1388
+ }
1275
1389
  </style>
1276
1390
  </head>
1277
1391
  <body>
1392
+ <!-- Splash overlay (?splash=1). Rendered by the main process as a native
1393
+ WebContentsView floating above the app while it connects and boots; see
1394
+ desktop/src/main/splash.ts. Hidden unless the query flag is present. -->
1395
+ <div class="splash" id="splash">
1396
+ <div class="splash-logo-wrapper">
1397
+ <div class="splash-sweep"></div>
1398
+ <img class="splash-logo" src="logo.png" alt="ClawBench">
1399
+ </div>
1400
+ <div class="splash-title">ClawBench</div>
1401
+ <div class="splash-stage" id="splashStage" data-i18n="splash_connecting">正在连接…</div>
1402
+ <button type="button" class="splash-cancel" id="splashCancel" data-i18n="splash_cancel">取消连接</button>
1403
+ </div>
1404
+
1278
1405
  <div class="bg-gradient"></div>
1279
1406
  <div class="bg-grid"></div>
1280
1407
 
@@ -1402,6 +1529,7 @@
1402
1529
  </div>
1403
1530
  </div>
1404
1531
 
1532
+ <script src="url-utils.js"></script>
1405
1533
  <script>
1406
1534
  var connecting = false;
1407
1535
  var savedServers = [];
@@ -1419,10 +1547,64 @@
1419
1547
  });
1420
1548
  }
1421
1549
 
1550
+ // ── Splash overlay (?splash=1) ──────────────────────────────────────────
1551
+ // The main process loads this same page with ?splash=1 as a native view
1552
+ // floating over the app while it connects and boots. In that mode the login
1553
+ // form is irrelevant: only the splash markup is shown, and the process drives
1554
+ // the stage text through __splashSetStage.
1555
+ var SPLASH_MODE = /(?:^|[?&])splash=1(?:&|$)/.test(window.location.search);
1556
+ var SPLASH_STAGE_KEYS = {
1557
+ connecting: 'splash_connecting',
1558
+ loading: 'splash_loading',
1559
+ rendering: 'splash_rendering',
1560
+ initializing: 'splash_initializing'
1561
+ };
1562
+
1563
+ /** Advance the splash stage text. Called from the main process. */
1564
+ window.__splashSetStage = function(stage) {
1565
+ var key = SPLASH_STAGE_KEYS[stage];
1566
+ if (!key) return;
1567
+ var el = document.getElementById('splashStage');
1568
+ if (el) el.textContent = t(key);
1569
+ };
1570
+
1571
+ /** Begin the splash fade-out. The main process hides the view shortly after. */
1572
+ window.__splashFadeOut = function() {
1573
+ var el = document.getElementById('splash');
1574
+ if (el) el.classList.add('is-fading');
1575
+ };
1576
+
1577
+ /**
1578
+ * Reset the overlay for a fresh show. The page is REUSED across connects
1579
+ * (the main process only loads it once), so the fade class left by the
1580
+ * previous dismissal must be cleared — otherwise the second connect renders
1581
+ * the overlay fully transparent.
1582
+ */
1583
+ window.__splashReset = function() {
1584
+ var el = document.getElementById('splash');
1585
+ if (el) el.classList.remove('is-fading');
1586
+ };
1587
+
1588
+ if (SPLASH_MODE) {
1589
+ document.body.classList.add('splash-mode');
1590
+ }
1591
+
1422
1592
  window.addEventListener('DOMContentLoaded', function() {
1423
1593
  // Apply i18n to all elements with data-i18n / data-i18n-placeholder
1424
1594
  applyI18n();
1425
1595
 
1596
+ if (SPLASH_MODE) {
1597
+ // Splash mode is display-only apart from the cancel button; skip the
1598
+ // login form's data loading entirely.
1599
+ var cancelBtn = document.getElementById('splashCancel');
1600
+ if (cancelBtn) {
1601
+ cancelBtn.addEventListener('click', function() {
1602
+ try { ClawBenchNative.cancelSplash(); } catch(e) {}
1603
+ });
1604
+ }
1605
+ return;
1606
+ }
1607
+
1426
1608
  // Load server list (single source of truth)
1427
1609
  loadServerList();
1428
1610
 
@@ -1511,6 +1693,28 @@
1511
1693
  }
1512
1694
  });
1513
1695
 
1696
+ // Event: blur on the host field -> normalize a full URL into protocol + port.
1697
+ // Blur, not paste/input: the field is only rewritten after the user leaves it,
1698
+ // so typing is never disturbed mid-keystroke. Reading .value (rather than
1699
+ // clipboard data) also sidesteps WebView clipboard restrictions.
1700
+ document.getElementById('addHost').addEventListener('blur', function() {
1701
+ var parsed = parseServerInput(document.getElementById('addHost').value);
1702
+ // Not a URL we understand -> leave the field exactly as the user typed it.
1703
+ if (!parsed) return;
1704
+
1705
+ hideError('addErrorMsg');
1706
+ document.getElementById('addHost').value = parsed.host;
1707
+ // Only override the port when the URL carried one explicitly; the field
1708
+ // keeps its default (20000) otherwise.
1709
+ if (parsed.port !== null) {
1710
+ document.getElementById('addPort').value = parsed.port;
1711
+ }
1712
+ // No scheme in the input -> leave the radio untouched.
1713
+ if (parsed.protocol !== null) {
1714
+ document.querySelector('input[name="addProtocol"][value="' + parsed.protocol + '"]').checked = true;
1715
+ }
1716
+ });
1717
+
1514
1718
  // Event: show add form
1515
1719
  document.getElementById('addServerBtn').addEventListener('click', function() {
1516
1720
  showAddForm();
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Parse a server address into protocol / host / port.
3
+ *
4
+ * Pure and dependency-free on purpose: it must be evaluable in a bare sandbox
5
+ * (no DOM, no ClawBenchNative). Deliberately NOT built on `new URL()`:
6
+ * new URL('192.168.1.100:8080') -> throws Invalid URL
7
+ * new URL('example.com:8080') -> protocol "example.com:", hostname ""
8
+ * so the scheme-less form users actually type cannot go through it. A single
9
+ * regex treats both "with scheme" and "without scheme" uniformly.
10
+ *
11
+ * Returns { protocol: 'http'|'https'|null, host: string, port: string|null },
12
+ * or null when the input is not a server address we understand. Callers must
13
+ * treat null as "leave the field alone".
14
+ */
15
+ function parseServerInput(text) {
16
+ if (typeof text !== 'string') return null;
17
+ var s = text.trim();
18
+ if (!s) return null;
19
+
20
+ var protocol = null;
21
+ var rest = s;
22
+
23
+ // Optional scheme. Anything other than http/https is not ours to interpret.
24
+ var schemeMatch = rest.match(/^([A-Za-z][A-Za-z0-9+.-]*):\/\//);
25
+ if (schemeMatch) {
26
+ var scheme = schemeMatch[1].toLowerCase();
27
+ if (scheme !== 'http' && scheme !== 'https') return null;
28
+ protocol = scheme;
29
+ rest = rest.slice(schemeMatch[0].length);
30
+ }
31
+
32
+ // Drop path, query and fragment — only scheme/host/port are meaningful here.
33
+ rest = rest.split(/[/?#]/)[0];
34
+
35
+ var host = rest;
36
+ var port = null;
37
+ var portMatch = rest.match(/^(.*):(\d+)$/);
38
+ if (portMatch) {
39
+ host = portMatch[1];
40
+ port = portMatch[2];
41
+ }
42
+
43
+ // Host must look like a hostname or IPv4 literal. This is also what rejects
44
+ // 'not a url', 'http://' (empty host) and 'user:pass@host' (contains '@').
45
+ if (!/^[A-Za-z0-9._-]+$/.test(host)) return null;
46
+
47
+ return { protocol: protocol, host: host, port: port };
48
+ }