bloby-bot 0.68.4 → 0.69.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 +1 -1
- package/supervisor/workspace-guard.js +106 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
// Injected by the supervisor into the workspace dashboard HTML (and ONLY there — never the
|
|
3
|
-
// chat PWA or the supervisor's own interstitials).
|
|
3
|
+
// chat PWA or the supervisor's own interstitials). Six jobs, all aimed at non-technical users:
|
|
4
4
|
//
|
|
5
5
|
// 1. Backend-down auto-detect — when the workspace backend crash-loops and gives up, the
|
|
6
6
|
// supervisor serves a full "backend down" interstitial on the next navigation. Poll for
|
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
// handler starts a recovery poll against a server-side change counter
|
|
36
36
|
// (/__bloby/fe-stamp) and reloads ONLY when frontend files actually changed while the
|
|
37
37
|
// socket was dead.
|
|
38
|
+
//
|
|
39
|
+
// 6. Pull-to-refresh — shell mode cost installed PWAs their native drag-down refresh (the
|
|
40
|
+
// gesture lands in this iframe; the shell is overflow:hidden). Recreated here, scoped
|
|
41
|
+
// tighter than the native one: it reloads only the workspace iframe, never the chat.
|
|
38
42
|
if (window.__blobyWorkspaceGuard) return;
|
|
39
43
|
window.__blobyWorkspaceGuard = true;
|
|
40
44
|
|
|
@@ -317,6 +321,107 @@
|
|
|
317
321
|
});
|
|
318
322
|
})();
|
|
319
323
|
|
|
324
|
+
/* ── 6. Pull-to-refresh ───────────────────────────────────────────────── */
|
|
325
|
+
// Native pull-to-refresh belongs to the top-level document; in shell mode that's the
|
|
326
|
+
// overflow:hidden shell, and touch gestures land in this iframe — so installed PWAs lost
|
|
327
|
+
// drag-down refresh entirely. Recreate it here: a mostly-vertical downward drag that
|
|
328
|
+
// starts with every scrollable ancestor at its top pulls out an indicator; releasing past
|
|
329
|
+
// the threshold reloads ONLY this workspace document (through the forensics wrapper →
|
|
330
|
+
// splash), so the chat in the shell never blinks. Framed + touch devices only — legacy
|
|
331
|
+
// mode (BLOBY_NO_SHELL=1) keeps the browser's native behavior.
|
|
332
|
+
(function () {
|
|
333
|
+
var framed = false;
|
|
334
|
+
try { framed = !!window.parent && window.parent !== window; } catch (e) {}
|
|
335
|
+
if (!framed || !('ontouchstart' in window)) return;
|
|
336
|
+
|
|
337
|
+
var THRESHOLD = 75; // px of damped pull that arms the refresh
|
|
338
|
+
var ENGAGE = 10; // px of raw downward travel before we claim the gesture
|
|
339
|
+
var startX = null, startY = null, pulling = false, dist = 0;
|
|
340
|
+
var indicator = null, arrow = null;
|
|
341
|
+
|
|
342
|
+
var spinStyle = document.createElement('style');
|
|
343
|
+
spinStyle.textContent = '@keyframes bloby-ptr-spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}';
|
|
344
|
+
(document.head || document.documentElement).appendChild(spinStyle);
|
|
345
|
+
|
|
346
|
+
function ensureIndicator() {
|
|
347
|
+
if (indicator) return;
|
|
348
|
+
indicator = document.createElement('div');
|
|
349
|
+
// z-index 99996: above the app and the splash (9998) so the spinner stays visible
|
|
350
|
+
// through the pre-reload splash, below the shell's chat chrome (which is a separate
|
|
351
|
+
// document anyway).
|
|
352
|
+
indicator.style.cssText = 'position:fixed;top:-48px;left:50%;width:38px;height:38px;margin-left:-19px;z-index:99996;border-radius:50%;background:#18181b;border:1px solid #27272a;box-shadow:0 2px 12px rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;pointer-events:none';
|
|
353
|
+
indicator.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#4AEEFF" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="transition:transform .15s ease"><path d="M12 5v14"/><path d="M5 12l7 7 7-7"/></svg>';
|
|
354
|
+
document.body.appendChild(indicator);
|
|
355
|
+
arrow = indicator.firstChild;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function setPull(d) {
|
|
359
|
+
ensureIndicator();
|
|
360
|
+
indicator.style.transition = 'none';
|
|
361
|
+
indicator.style.transform = 'translateY(' + Math.min(d + 48, 140) + 'px)';
|
|
362
|
+
if (arrow) arrow.style.transform = d >= THRESHOLD ? 'rotate(180deg)' : 'rotate(0deg)';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function settle(triggered) {
|
|
366
|
+
if (!indicator) return;
|
|
367
|
+
indicator.style.transition = 'transform .25s ease';
|
|
368
|
+
if (triggered) {
|
|
369
|
+
indicator.style.transform = 'translateY(72px)';
|
|
370
|
+
if (arrow) { arrow.style.transition = 'none'; arrow.style.animation = 'bloby-ptr-spin .7s linear infinite'; }
|
|
371
|
+
} else {
|
|
372
|
+
indicator.style.transform = 'translateY(0)'; // back above the viewport edge
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// The pull only makes sense when the gesture couldn't have scrolled anything instead:
|
|
377
|
+
// every scrollable ancestor of the touch target (and the page itself) is at its top.
|
|
378
|
+
function ancestorsAtTop(el) {
|
|
379
|
+
var n = el;
|
|
380
|
+
while (n && n.nodeType === 1 && n !== document.documentElement) {
|
|
381
|
+
if (n.scrollTop > 0) return false;
|
|
382
|
+
n = n.parentElement;
|
|
383
|
+
}
|
|
384
|
+
var se = document.scrollingElement || document.documentElement;
|
|
385
|
+
return (se.scrollTop || 0) <= 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
window.addEventListener('touchstart', function (e) {
|
|
389
|
+
startX = startY = null; pulling = false; dist = 0;
|
|
390
|
+
if (e.touches.length !== 1) return;
|
|
391
|
+
if (!ancestorsAtTop(e.target)) return;
|
|
392
|
+
startX = e.touches[0].clientX;
|
|
393
|
+
startY = e.touches[0].clientY;
|
|
394
|
+
}, { passive: true });
|
|
395
|
+
|
|
396
|
+
window.addEventListener('touchmove', function (e) {
|
|
397
|
+
if (startY === null || e.touches.length !== 1) return;
|
|
398
|
+
var dx = e.touches[0].clientX - startX;
|
|
399
|
+
var dy = e.touches[0].clientY - startY;
|
|
400
|
+
if (!pulling) {
|
|
401
|
+
// Claim only a clearly-vertical downward drag; anything else stays a normal gesture.
|
|
402
|
+
if (dy < ENGAGE || Math.abs(dy) < Math.abs(dx) * 1.5) {
|
|
403
|
+
if (dy < -ENGAGE || Math.abs(dx) > 24) startY = null; // it's a scroll/swipe — let go for good
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
pulling = true;
|
|
407
|
+
}
|
|
408
|
+
e.preventDefault(); // claimed: keep the page from rubber-banding underneath the pull
|
|
409
|
+
dist = Math.max(0, (dy - ENGAGE) * 0.45); // damped, native-ish feel
|
|
410
|
+
setPull(dist);
|
|
411
|
+
}, { passive: false });
|
|
412
|
+
|
|
413
|
+
function release(cancelled) {
|
|
414
|
+
if (pulling) {
|
|
415
|
+
var go = !cancelled && dist >= THRESHOLD;
|
|
416
|
+
settle(go);
|
|
417
|
+
if (go) { flog('pull-to-refresh', 'triggered'); location.reload(); }
|
|
418
|
+
}
|
|
419
|
+
startX = startY = null; pulling = false; dist = 0;
|
|
420
|
+
}
|
|
421
|
+
window.addEventListener('touchend', function () { release(false); }, { passive: true });
|
|
422
|
+
window.addEventListener('touchcancel', function () { release(true); }, { passive: true });
|
|
423
|
+
})();
|
|
424
|
+
|
|
320
425
|
/* ── 1. Backend-down auto-detect ──────────────────────────────────────── */
|
|
321
426
|
var reloading = false;
|
|
322
427
|
function pollBackend() {
|