@bivy/bivy 0.17.3-staging.13 → 0.17.3-staging.15
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/dist/apps/inspector.js +19 -28
- package/dist/apps/styles.css +3 -2
- package/dist/server.js +22 -4
- package/package.json +1 -1
package/dist/apps/inspector.js
CHANGED
|
@@ -32,35 +32,20 @@ const selector=el=>{
|
|
|
32
32
|
}
|
|
33
33
|
return parts.join(' > ')||el.localName;
|
|
34
34
|
};
|
|
35
|
-
let box=null,
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
// A second listener, so stopping (which the shell does right after a pick)
|
|
42
|
-
// doesn't also drop the guard on the rest of the tap. The tap's click ends it.
|
|
43
|
-
const aftermath=e=>{swallow(e);if(e.type==='click')listen(false,aftermath);};
|
|
44
|
-
const listen=(on,handler=swallow)=>{
|
|
45
|
-
const method=on?addEventListener:removeEventListener;
|
|
46
|
-
if(handler===swallow){method('pointermove',hover,true);method('pointerup',pick,true);method('keydown',key,true);}
|
|
47
|
-
for(const type of SWALLOW)method(type,handler,true);
|
|
48
|
-
};
|
|
49
|
-
const stop=()=>{box?.remove();box=null;target=null;listen(false);};
|
|
35
|
+
let box=null,layer=null,onPicked=post,host=null;
|
|
36
|
+
// While pointing, a clear layer over the page takes every tap itself, so the
|
|
37
|
+
// app never sees one and no browser's event order matters (iOS sends no click
|
|
38
|
+
// to a window listener for most taps). The element is looked up beneath it.
|
|
39
|
+
const stop=()=>{box?.remove();layer?.remove();box=layer=null;removeEventListener('keydown',key,true);};
|
|
40
|
+
const under=e=>document.elementsFromPoint(e.clientX,e.clientY).find(el=>el!==layer&&el!==box&&el!==host);
|
|
50
41
|
const hover=e=>{
|
|
51
|
-
const el=
|
|
52
|
-
if(!el||el===box||el===host)return;target=el;const r=el.getBoundingClientRect();
|
|
42
|
+
const el=under(e);if(!el)return;const r=el.getBoundingClientRect();
|
|
53
43
|
Object.assign(box.style,{left:r.left+'px',top:r.top+'px',width:r.width+'px',height:r.height+'px'});
|
|
54
44
|
};
|
|
55
45
|
const pick=e=>{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const el=e.target instanceof Element?e.target:document.elementFromPoint(e.clientX,e.clientY)||target;
|
|
60
|
-
if(!el)return;stop();
|
|
61
|
-
// Keep eating the click (and iOS's delayed mouse events) this tap still fires.
|
|
62
|
-
listen(true,aftermath);setTimeout(()=>listen(false,aftermath),500);
|
|
63
|
-
if(el===host){onPicked({type:'picked',cancelled:true});return;}const r=el.getBoundingClientRect();
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
const el=under(e);if(!el)return;stop();
|
|
48
|
+
const r=el.getBoundingClientRect();
|
|
64
49
|
onPicked({type:'picked',selector:selector(el),tag:el.localName,text:(el.innerText||el.getAttribute('aria-label')||el.getAttribute('alt')||'').trim().replace(/\\s+/g,' ').slice(0,200),
|
|
65
50
|
rect:{x:Math.round(r.left),y:Math.round(r.top),width:Math.round(r.width),height:Math.round(r.height)},
|
|
66
51
|
viewport:{width:innerWidth,height:innerHeight},path:location.pathname+location.search+location.hash});
|
|
@@ -71,8 +56,13 @@ function startPointing(){
|
|
|
71
56
|
box=document.createElement('div');
|
|
72
57
|
box.setAttribute('aria-hidden','true');
|
|
73
58
|
Object.assign(box.style,{position:'fixed',zIndex:2147483647,pointerEvents:'none',outline:'2px solid Highlight',outlineOffset:'2px',borderRadius:'4px',transition:'all 60ms ease-out'});
|
|
74
|
-
|
|
75
|
-
|
|
59
|
+
// One below the reviewer dock, so its controls stay usable while pointing.
|
|
60
|
+
layer=document.createElement('div');
|
|
61
|
+
layer.setAttribute('aria-hidden','true');
|
|
62
|
+
Object.assign(layer.style,{position:'fixed',inset:0,zIndex:2147483646,cursor:'crosshair',background:'transparent',touchAction:'manipulation',userSelect:'none',webkitUserSelect:'none',webkitTouchCallout:'none'});
|
|
63
|
+
layer.addEventListener('pointermove',hover);layer.addEventListener('pointerdown',hover);layer.addEventListener('click',pick);
|
|
64
|
+
document.documentElement.append(layer,box);
|
|
65
|
+
addEventListener('keydown',key,true);
|
|
76
66
|
}
|
|
77
67
|
addEventListener('message',e=>{
|
|
78
68
|
if(!framed||e.origin!==SHELL||e.source!==parent||e.data?.type!=='bivy:point')return;
|
|
@@ -87,7 +77,8 @@ if(REVIEWER&&!framed){
|
|
|
87
77
|
+'button{border:1px solid ButtonBorder;background:ButtonFace;color:ButtonText;border-radius:999px;padding:10px 14px;font-weight:600;cursor:pointer;min-height:44px}'
|
|
88
78
|
+'.primary{background:CanvasText;color:Canvas;border-color:CanvasText}'
|
|
89
79
|
+'form{width:min(340px,calc(100vw - 24px));background:Canvas;color:CanvasText;border:1px solid GrayText;border-radius:14px;padding:12px;box-shadow:0 8px 30px rgb(0 0 0/.25)}'
|
|
90
|
-
|
|
80
|
+
// 16px: smaller, and iOS zooms the page in when the note box is focused.
|
|
81
|
+
+'textarea{width:100%;min-height:5em;margin:8px 0;border:1px solid GrayText;border-radius:8px;padding:8px;background:Canvas;color:CanvasText;resize:vertical;font-size:16px}'
|
|
91
82
|
+'.row{display:flex;justify-content:flex-end;gap:8px}.muted{color:GrayText;font-size:12px;overflow-wrap:anywhere}[hidden]{display:none!important}</style>'
|
|
92
83
|
+'<div class="dock"><p class="muted" role="status" id="status" hidden></p>'
|
|
93
84
|
+'<form id="form" hidden><label for="note">Your note</label><div class="muted" id="about"></div><textarea id="note" required maxlength="1000"></textarea>'
|
package/dist/apps/styles.css
CHANGED
|
@@ -1271,6 +1271,7 @@ button.sheet-action:last-child { border-bottom: 0; }
|
|
|
1271
1271
|
.fork-select-field select { width: 100%; padding: 10px 12px; border: 1px solid var(--line-strong); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); }
|
|
1272
1272
|
.fork-toggle { display: flex; align-items: center; gap: 8px; padding: 10px 12px; font-size: 13px; color: var(--ink); }
|
|
1273
1273
|
.fork-toggle input { width: 16px; height: 16px; }
|
|
1274
|
+
.fork-note { margin: 0; padding: var(--space-2) var(--space-3); color: var(--muted); font-size: var(--text-sm); line-height: var(--lh-sm); }
|
|
1274
1275
|
.fork-error { margin: 4px 12px; padding: 8px 10px; border-radius: var(--radius-md); background: var(--surface-2); color: var(--danger); font-size: 12px; }
|
|
1275
1276
|
.fork-warn { margin: 4px 12px; padding: 8px 10px; border-radius: var(--radius-md); background: var(--surface-2); color: var(--muted); font-size: 12px; }
|
|
1276
1277
|
.fork-submit { width: calc(100% - 24px); margin: var(--space-2) var(--space-3); }
|
|
@@ -3202,7 +3203,7 @@ select.picker-search { appearance: auto; }
|
|
|
3202
3203
|
.automation-history-view .autom-section-head > strong { min-width: 0; line-height: var(--lh-lg); }
|
|
3203
3204
|
}
|
|
3204
3205
|
|
|
3205
|
-
/*
|
|
3206
|
-
itself, with one line of
|
|
3206
|
+
/* Handoff notes above the composer (HandoffBanner, StandbyNotice): shaped like
|
|
3207
|
+
the composer itself, with one line of another device's draft. */
|
|
3207
3208
|
.handoff-banner { border-radius: var(--radius-md); }
|
|
3208
3209
|
.handoff-preview { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
package/dist/server.js
CHANGED
|
@@ -1834,6 +1834,20 @@ const replication = new ReplicationService({
|
|
|
1834
1834
|
},
|
|
1835
1835
|
log: (m) => console.error(`[replication] ${m}`),
|
|
1836
1836
|
});
|
|
1837
|
+
/** Take over a replicated session on this node (manual promotion). Once it
|
|
1838
|
+
* wins the epoch it is this node's session, not a standby copy: retag it so
|
|
1839
|
+
* every client stops offering "Continue here", and push the new list. */
|
|
1840
|
+
async function promoteReplicaHere(sessionId) {
|
|
1841
|
+
const epoch = await replication.promote(sessionId, identity.nodeId);
|
|
1842
|
+
if (epoch === undefined)
|
|
1843
|
+
return undefined;
|
|
1844
|
+
const source = metadata.getSession(sessionId)?.source;
|
|
1845
|
+
if (source?.startsWith("replica"))
|
|
1846
|
+
metadata.upsertSession({ id: sessionId, source: source.replace(/^replica/, "promoted") });
|
|
1847
|
+
broadcastSessionsList();
|
|
1848
|
+
scheduleAdvertise();
|
|
1849
|
+
return epoch;
|
|
1850
|
+
}
|
|
1837
1851
|
// Retire the legacy overlay sidecars. The append-only log is the sole overlay
|
|
1838
1852
|
// store now and every session was migrated into it by the prior release's boot
|
|
1839
1853
|
// migration (.migrated-overlays-v1), verified complete on-disk before this landed.
|
|
@@ -1973,6 +1987,12 @@ const appService = new AppService(appRegistry, appGateway ?? remotePreview, {
|
|
|
1973
1987
|
has: (id) => terminals.has(id),
|
|
1974
1988
|
close: (id) => { terminals.close(id); },
|
|
1975
1989
|
}, { screenshots: { enabled: appScreenshotsEnabled }, displays: appDisplays });
|
|
1990
|
+
// A reviewer's note shows up in an open Apps sheet without reopening it.
|
|
1991
|
+
appRegistry.on("notes", (viewId) => {
|
|
1992
|
+
const entry = appRegistry.getView(viewId);
|
|
1993
|
+
if (entry)
|
|
1994
|
+
broadcast({ type: "apps.changed", sessionId: entry.app.sessionId });
|
|
1995
|
+
});
|
|
1976
1996
|
const RELAY_COMMANDS = {
|
|
1977
1997
|
...createAppCommands(appService, (id) => { const record = resolveSession(id); return record ? harnessDirFor(record) : undefined; }, (app) => {
|
|
1978
1998
|
const record = resolveSession(app.sessionId);
|
|
@@ -2779,10 +2799,9 @@ const RELAY_COMMANDS = {
|
|
|
2779
2799
|
const sessionId = String(msg.sessionId ?? "").trim();
|
|
2780
2800
|
if (!sessionId)
|
|
2781
2801
|
return ctx.reply({ type: "session.promote.result", requestId, ok: false, error: "Missing sessionId" });
|
|
2782
|
-
const epoch = await
|
|
2802
|
+
const epoch = await promoteReplicaHere(sessionId);
|
|
2783
2803
|
if (epoch === undefined)
|
|
2784
2804
|
return ctx.reply({ type: "session.promote.result", requestId, ok: false, error: "Promotion lost the epoch race" });
|
|
2785
|
-
scheduleAdvertise();
|
|
2786
2805
|
ctx.reply({ type: "session.promote.result", requestId, ok: true, sessionId, epoch });
|
|
2787
2806
|
},
|
|
2788
2807
|
// Ephemeral provisioning transport (node-broker path). A remote device that
|
|
@@ -11265,10 +11284,9 @@ app.post("/api/session/promote", async (req, res, next) => {
|
|
|
11265
11284
|
const sessionId = String(req.body?.sessionId ?? "").trim();
|
|
11266
11285
|
if (!sessionId)
|
|
11267
11286
|
return res.status(400).json({ error: "Missing sessionId" });
|
|
11268
|
-
const epoch = await
|
|
11287
|
+
const epoch = await promoteReplicaHere(sessionId);
|
|
11269
11288
|
if (epoch === undefined)
|
|
11270
11289
|
return res.status(409).json({ error: "Promotion lost the epoch race (another node owns it, or it isn't replicated)" });
|
|
11271
|
-
scheduleAdvertise();
|
|
11272
11290
|
res.json({ ok: true, epoch });
|
|
11273
11291
|
}
|
|
11274
11292
|
catch (error) {
|
package/package.json
CHANGED