@bivy/bivy 0.17.3-staging.22 → 0.17.3-staging.24
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 +21 -6
- package/dist/apps/preview-shell.js +62 -9
- package/dist/apps/styles.css +4 -0
- package/package.json +1 -1
package/dist/apps/inspector.js
CHANGED
|
@@ -42,14 +42,27 @@ const hover=e=>{
|
|
|
42
42
|
const el=under(e);if(!el)return;const r=el.getBoundingClientRect();
|
|
43
43
|
Object.assign(box.style,{left:r.left+'px',top:r.top+'px',width:r.width+'px',height:r.height+'px'});
|
|
44
44
|
};
|
|
45
|
-
const
|
|
46
|
-
e.preventDefault();
|
|
47
|
-
const el=under(e);if(!el)return;stop();
|
|
45
|
+
const report=(el,hold)=>{
|
|
48
46
|
const r=el.getBoundingClientRect();
|
|
49
47
|
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),
|
|
50
48
|
rect:{x:Math.round(r.left),y:Math.round(r.top),width:Math.round(r.width),height:Math.round(r.height)},
|
|
51
|
-
viewport:{width:innerWidth,height:innerHeight},path:location.pathname+location.search+location.hash});
|
|
49
|
+
viewport:{width:innerWidth,height:innerHeight},path:location.pathname+location.search+location.hash,hold});
|
|
50
|
+
};
|
|
51
|
+
// A long press picks at once and says so (the shell starts listening); the
|
|
52
|
+
// layer stays until the finger lifts, so the release never reaches the app.
|
|
53
|
+
let press=null,held=false;
|
|
54
|
+
const pick=e=>{
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
if(held){held=false;stop();return;}
|
|
57
|
+
const el=under(e);if(!el)return;stop();report(el,false);
|
|
58
|
+
};
|
|
59
|
+
const down=e=>{
|
|
60
|
+
hover(e);clearTimeout(press?.timer);held=false;
|
|
61
|
+
const x=e.clientX,y=e.clientY;
|
|
62
|
+
press={x,y,timer:setTimeout(()=>{const el=under({clientX:x,clientY:y});if(!el)return;held=true;box?.remove();report(el,true);},450)};
|
|
52
63
|
};
|
|
64
|
+
const move=e=>{hover(e);if(press&&Math.hypot(e.clientX-press.x,e.clientY-press.y)>10)clearTimeout(press.timer);};
|
|
65
|
+
const up=()=>{clearTimeout(press?.timer);press=null;if(held){post({type:'release'});setTimeout(()=>{if(held){held=false;stop();}},0);}};
|
|
53
66
|
const key=e=>{if(e.key==='Escape'){stop();onPicked({type:'picked',cancelled:true});}};
|
|
54
67
|
function startPointing(){
|
|
55
68
|
stop();
|
|
@@ -60,13 +73,15 @@ function startPointing(){
|
|
|
60
73
|
layer=document.createElement('div');
|
|
61
74
|
layer.setAttribute('aria-hidden','true');
|
|
62
75
|
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',
|
|
76
|
+
layer.addEventListener('pointermove',move);layer.addEventListener('pointerdown',down);layer.addEventListener('pointerup',up);layer.addEventListener('pointercancel',up);layer.addEventListener('click',pick);
|
|
77
|
+
layer.addEventListener('contextmenu',e=>e.preventDefault());
|
|
64
78
|
document.documentElement.append(layer,box);
|
|
65
79
|
addEventListener('keydown',key,true);
|
|
66
80
|
}
|
|
67
81
|
addEventListener('message',e=>{
|
|
68
82
|
if(!framed||e.origin!==SHELL||e.source!==parent||e.data?.type!=='bivy:point')return;
|
|
69
|
-
|
|
83
|
+
// Held: the layer goes when the finger lifts (see up).
|
|
84
|
+
if(e.data.on)startPointing();else if(!held)stop();
|
|
70
85
|
});
|
|
71
86
|
if(REVIEWER&&!framed){
|
|
72
87
|
// A shadow root keeps the app's CSS off these controls (and ours off the app).
|
|
@@ -43,6 +43,15 @@ nav .btn[aria-pressed="true"] { background:var(--accent-soft); color:var(--accen
|
|
|
43
43
|
#entries li { display:flex; gap:var(--space-2); align-items:baseline; padding:var(--space-1) 0; border-top:thin solid var(--line); overflow-wrap:anywhere; }
|
|
44
44
|
/* Keeps .field's 16px: smaller, and iOS zooms the shell on focus and stays zoomed. */
|
|
45
45
|
#draft-text { width:100%; min-height:5em; box-sizing:border-box; resize:vertical; }
|
|
46
|
+
#draft-row { display:flex; align-items:flex-start; gap:var(--space-2); }
|
|
47
|
+
#draft-row #draft-text { flex:1; min-width:0; }
|
|
48
|
+
/* Hold to talk: a phone-sized target that never selects text or opens a callout. */
|
|
49
|
+
#mic { flex:none; inline-size:44px; block-size:44px; padding:0; border-radius:var(--radius-full); touch-action:none; user-select:none; -webkit-user-select:none; -webkit-touch-callout:none; }
|
|
50
|
+
#mic[aria-pressed="true"] { background:var(--accent); border-color:var(--accent); color:var(--accent-contrast); }
|
|
51
|
+
#voice-status { font-size:var(--text-xs); margin:var(--space-2) 0 0; }
|
|
52
|
+
#voice-status:empty { display:none; }
|
|
53
|
+
/* The panel scrolls on a phone; its actions stay in reach at the bottom. */
|
|
54
|
+
#draft .panel-actions { position:sticky; bottom:calc(-1 * var(--space-3)); margin:var(--space-2) calc(-1 * var(--space-3)) calc(-1 * var(--space-3)); padding:var(--space-2) var(--space-3) var(--space-3); background:var(--surface); border-top:thin solid var(--line); }
|
|
46
55
|
#draft-hint { font-size:var(--text-xs); margin:var(--space-2) 0 var(--space-1); }
|
|
47
56
|
#draft-context { margin:0; padding:var(--space-2); background:var(--surface-2); border-radius:var(--radius-md); font-family:var(--font-mono); font-size:var(--text-xs); white-space:pre-wrap; overflow-wrap:anywhere; color:var(--muted); }
|
|
48
57
|
#show { position:fixed; right:var(--space-3); bottom:calc(var(--space-3) + env(safe-area-inset-bottom)); z-index:var(--z-sticky); border-radius:var(--radius-full); box-shadow:var(--shadow-lg); }
|
|
@@ -67,12 +76,14 @@ nav .btn[aria-pressed="true"] { background:var(--accent-soft); color:var(--accen
|
|
|
67
76
|
</section>
|
|
68
77
|
<section class="panel" id="draft" hidden aria-labelledby="draft-title">
|
|
69
78
|
<h2 id="draft-title">Draft for the agent</h2>
|
|
70
|
-
<textarea class="field" id="draft-text" aria-label="What should change?" placeholder="What should change?"></textarea>
|
|
79
|
+
<div id="draft-row"><textarea class="field" id="draft-text" aria-label="What should change?" placeholder="What should change?"></textarea>
|
|
80
|
+
<button class="btn" id="mic" type="button" hidden aria-pressed="false" aria-label="Speak" title="Hold to speak, or tap to start and stop"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 15.5a3 3 0 003-3V6a3 3 0 10-6 0v6.5a3 3 0 003 3z"/><path d="M6 11.5v1a6 6 0 0012 0v-1M12 18.5V21M9 21h6"/></svg></button></div>
|
|
81
|
+
<p class="muted" id="voice-status" role="status"></p>
|
|
71
82
|
<p class="muted" id="draft-hint">Sent with this context from the preview. Nothing is sent until you send it in the chat.</p>
|
|
72
83
|
<pre id="draft-context"></pre>
|
|
73
84
|
<div class="panel-actions"><button class="btn sm ghost" id="draft-cancel">Cancel</button><button class="btn sm primary" id="draft-add">Add to chat</button></div>
|
|
74
85
|
</section>
|
|
75
|
-
<p class="banner inline" data-tone="accent" id="pointing" role="status" hidden>Tap anything in the app to point at it. Press Escape or Point again to stop.</p>
|
|
86
|
+
<p class="banner inline" data-tone="accent" id="pointing" role="status" hidden>Tap anything in the app to point at it. <span id="pointing-voice" hidden>Hold to point and speak. </span>Press Escape or Point again to stop.</p>
|
|
76
87
|
<nav aria-label="Bivy preview controls">
|
|
77
88
|
<button class="btn sm ghost" id="back" aria-label="Back to chat">‹ <span class="label-wide">Back to chat</span><span class="label-narrow">Chat</span></button>
|
|
78
89
|
<span id="name"><span id="title">App preview</span><span id="stamp" class="muted" role="status"></span></span>
|
|
@@ -115,6 +126,8 @@ function show(data,launch){
|
|
|
115
126
|
for(const b of [reload,$('point'),$('errors')])b.disabled=false;
|
|
116
127
|
status.textContent='Loading app…';
|
|
117
128
|
frame.onload=()=>{status.hidden=true;void loadCompare();if(revision===null){revision=-1;watch();}else if(wake)wake();};
|
|
129
|
+
// A Bivy client framing the shell says whether it can take dictation.
|
|
130
|
+
if(embedded&&data.returnTo)toBivy({type:'hello'});
|
|
118
131
|
// Store navigation metadata only, never tickets or cookies.
|
|
119
132
|
try{sessionStorage.setItem(storageKey,JSON.stringify(data));}catch{}
|
|
120
133
|
}
|
|
@@ -143,15 +156,49 @@ function toChat(text){
|
|
|
143
156
|
const to=new URL(metadata.returnTo),session=to.pathname.split('/').pop();
|
|
144
157
|
location.assign(to.origin+'/share?session='+encodeURIComponent(session)+'&text='+encodeURIComponent(text));
|
|
145
158
|
}
|
|
146
|
-
function draft(context){
|
|
159
|
+
function draft(context,listen){
|
|
147
160
|
panels(null);
|
|
148
161
|
$('draft').hidden=false;
|
|
149
162
|
$('draft-add').textContent=metadata.returnTo?'Add to chat':'Copy';
|
|
150
163
|
$('draft-context').textContent=context;
|
|
151
|
-
const box=$('draft-text');box.value='';
|
|
164
|
+
const box=$('draft-text');box.value='';
|
|
165
|
+
// Speaking right away: don't raise the keyboard over the draft.
|
|
166
|
+
if(listen)startListening();else box.focus();
|
|
152
167
|
}
|
|
153
|
-
|
|
168
|
+
// Point and speak. The Bivy client framing this shell does the listening:
|
|
169
|
+
// audio never reaches this origin, and only the transcript comes back, into
|
|
170
|
+
// the draft box, where it stays editable. Hold to talk, or tap to start and
|
|
171
|
+
// tap again to stop.
|
|
172
|
+
const mic=$('mic'),voiceStatus=$('voice-status');
|
|
173
|
+
let voice=false,listening=false,heldAt=0;
|
|
174
|
+
const sayVoice=text=>{voiceStatus.textContent=text;};
|
|
175
|
+
function setListening(on){listening=on;mic.setAttribute('aria-pressed',String(on));mic.setAttribute('aria-label',on?'Stop and add what you said':'Speak');}
|
|
176
|
+
function startListening(){if(!voice||listening)return;setListening(true);sayVoice('Listening… let go, or tap the mic again, to add what you said.');toBivy({type:'listen',state:'start'});}
|
|
177
|
+
function stopListening(){if(!listening)return;sayVoice('Turning speech into text…');toBivy({type:'listen',state:'stop'});}
|
|
178
|
+
function cancelListening(){if(listening)toBivy({type:'listen',state:'cancel'});setListening(false);sayVoice('');}
|
|
179
|
+
function addSpoken(text){
|
|
180
|
+
const box=$('draft-text'),said=String(text||'').trim().slice(0,4000);
|
|
181
|
+
if(!said)return;
|
|
182
|
+
box.value=box.value.trim()?box.value.trimEnd()+' '+said:said;
|
|
183
|
+
sayVoice('Added what you said. Edit it, or Add to chat.');
|
|
184
|
+
}
|
|
185
|
+
mic.onpointerdown=e=>{
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
if(listening){stopListening();heldAt=0;return;}
|
|
188
|
+
heldAt=Date.now();try{mic.setPointerCapture(e.pointerId);}catch{}
|
|
189
|
+
startListening();
|
|
190
|
+
};
|
|
191
|
+
// A short tap keeps listening until the next tap; a hold stops on release.
|
|
192
|
+
mic.onpointerup=mic.onpointercancel=()=>{if(heldAt&&Date.now()-heldAt>400)stopListening();heldAt=0;};
|
|
193
|
+
mic.onclick=e=>{if(e.detail===0){if(listening)stopListening();else startListening();}};
|
|
194
|
+
function fromBivy(d){
|
|
195
|
+
if(d.type==='voice'){voice=d.available===true;mic.hidden=!voice;$('pointing-voice').hidden=!voice;if(!voice)cancelListening();}
|
|
196
|
+
else if(d.type==='transcript'){setListening(false);if(typeof d.error==='string')sayVoice(d.error.slice(0,200));else addSpoken(d.text);}
|
|
197
|
+
else if(d.type==='listening'&&d.on===false&&listening){setListening(false);if(voiceStatus.textContent.startsWith('Listening'))sayVoice('');}
|
|
198
|
+
}
|
|
199
|
+
$('draft-cancel').onclick=()=>{cancelListening();$('draft').hidden=true;};
|
|
154
200
|
$('draft-add').onclick=async()=>{
|
|
201
|
+
cancelListening();
|
|
155
202
|
const note=$('draft-text').value.trim(),text=(note?note+'\\n\\n':'')+$('draft-context').textContent;
|
|
156
203
|
if(metadata.returnTo)return toChat(text);
|
|
157
204
|
try{await navigator.clipboard.writeText(text);$('draft-add').textContent='Copied';}catch{$('draft-text').select();}
|
|
@@ -171,23 +218,26 @@ $('clear').onclick=resetConsole;
|
|
|
171
218
|
$('send-errors').onclick=()=>draft('Console output in the app preview "'+metadata.name+'" (page '+currentPath+'):\\n'+entries.slice(-20).map(e=>'- '+e.level+': '+e.text).join('\\n'));
|
|
172
219
|
// Point and tell
|
|
173
220
|
const point=$('point');
|
|
174
|
-
|
|
221
|
+
// Pointing again starts a new draft: panels over the app step aside.
|
|
222
|
+
function pointing(on){if(on)panels(null);point.setAttribute('aria-pressed',String(on));$('pointing').hidden=!on;frame.contentWindow?.postMessage({type:'bivy:point',on},metadata.origin);}
|
|
175
223
|
point.onclick=()=>pointing(point.getAttribute('aria-pressed')!=='true');
|
|
176
224
|
function picked(d){
|
|
177
225
|
pointing(false);
|
|
178
226
|
if(d.cancelled)return;
|
|
227
|
+
// A long press: the draft opens listening, and letting go stops.
|
|
228
|
+
if(d.hold&&voice)heldAt=Date.now();
|
|
179
229
|
const r=d.rect||{},v=d.viewport||{};
|
|
180
230
|
const errors=entries.filter(e=>e.level==='error').slice(-5);
|
|
181
231
|
draft('In the app preview "'+metadata.name+'" (page '+safePath(d.path)+', viewport '+v.width+'×'+v.height+'):\\n'
|
|
182
232
|
+'Element: '+String(d.selector).slice(0,300)+(d.text?' ("'+String(d.text).slice(0,200)+'")':'')+', '+r.width+'×'+r.height+' at '+r.x+','+r.y
|
|
183
|
-
+(errors.length?'\\nRecent errors:\\n'+errors.map(e=>'- '+e.text).join('\\n'):''));
|
|
233
|
+
+(errors.length?'\\nRecent errors:\\n'+errors.map(e=>'- '+e.text).join('\\n'):''),d.hold===true&&voice);
|
|
184
234
|
}
|
|
185
235
|
// Compare: screenshots around the agent's last change (agent screenshots on).
|
|
186
236
|
const compareBtn=$('compare-btn');let shots=[],urls=[];
|
|
187
237
|
async function loadCompare(){
|
|
188
238
|
try{const r=await fetch(metadata.origin+'/__bivy/compare',{credentials:'include',cache:'no-store'});if(!r.ok)return;shots=(await r.json()).shots||[];compareBtn.hidden=shots.length<2;}catch{}
|
|
189
239
|
}
|
|
190
|
-
function panels(open){for(const [id,btn] of [['console','errors'],['compare','compare-btn']]){$(id).hidden=id!==open;$(btn).setAttribute('aria-pressed',String(id===open));}$('draft').hidden=true;}
|
|
240
|
+
function panels(open){if(listening)cancelListening();for(const [id,btn] of [['console','errors'],['compare','compare-btn']]){$(id).hidden=id!==open;$(btn).setAttribute('aria-pressed',String(id===open));}$('draft').hidden=true;}
|
|
191
241
|
compareBtn.onclick=async()=>{
|
|
192
242
|
if(!$('compare').hidden)return panels(null);
|
|
193
243
|
panels('compare');
|
|
@@ -212,6 +262,8 @@ for(const b of $('lens').querySelectorAll('button'))b.onclick=()=>{
|
|
|
212
262
|
const down=$('down'),downText=$('down-text'),ask=$('ask');
|
|
213
263
|
let downPort=0;
|
|
214
264
|
addEventListener('message',e=>{
|
|
265
|
+
// The Bivy client framing this shell (Peek): voice only.
|
|
266
|
+
if(embedded&&metadata?.returnTo&&e.source===parent&&e.origin===new URL(metadata.returnTo).origin){if(e.data?.source==='bivy')fromBivy(e.data);return;}
|
|
215
267
|
if(!metadata||e.origin!==metadata.origin||e.source!==frame.contentWindow)return;
|
|
216
268
|
const d=e.data||{};
|
|
217
269
|
if(d.type==='bivy:access'&&d.state==='blocked'&&embedded&&metadata.returnTo)toBivy({type:'blocked'});
|
|
@@ -224,10 +276,11 @@ addEventListener('message',e=>{
|
|
|
224
276
|
if(d.type==='route')currentPath=safePath(d.path);
|
|
225
277
|
else if(d.type==='console'&&(d.level==='error'||d.level==='warn')){entries.push({level:d.level,text:String(d.text).slice(0,500)});if(entries.length>50)entries.shift();renderConsole();}
|
|
226
278
|
else if(d.type==='picked')picked(d);
|
|
279
|
+
else if(d.type==='release'&&heldAt){heldAt=0;stopListening();}
|
|
227
280
|
}
|
|
228
281
|
});
|
|
229
282
|
ask.onclick=()=>toChat('The app preview "'+metadata.name+'" isn’t loading: nothing is answering on port '+downPort+'. Please find out why the server stopped, restart it, and tell me when it’s back.');
|
|
230
|
-
addEventListener('keydown',e=>{if(e.key==='Escape'){if(point.getAttribute('aria-pressed')==='true')pointing(false);else panels(null);}});
|
|
283
|
+
addEventListener('keydown',e=>{if(e.key==='Escape'){if(listening)cancelListening();else if(point.getAttribute('aria-pressed')==='true')pointing(false);else panels(null);}});
|
|
231
284
|
back.onclick=()=>{if(metadata?.returnTo){window.close();setTimeout(()=>location.replace(metadata.returnTo),100);}else{window.close();status.hidden=false;status.textContent='You can close this tab to return to Bivy.';}};
|
|
232
285
|
reload.onclick=()=>{if(metadata)open(currentPath,'Reloading app…');};
|
|
233
286
|
(async()=>{
|
package/dist/apps/styles.css
CHANGED
|
@@ -3088,6 +3088,10 @@ select.picker-search { appearance: auto; }
|
|
|
3088
3088
|
.apps-foot { margin: 0; padding: 0 var(--space-1); color: var(--muted); font-size: var(--text-xs); line-height: var(--lh-xs); }
|
|
3089
3089
|
/* Peek: an app preview framed in a drawer over the chat (PreviewPeek.tsx). */
|
|
3090
3090
|
.preview-peek { display: block; inline-size: 100%; block-size: 100%; border: 0; border-radius: var(--radius-md); background: var(--bg); }
|
|
3091
|
+
.preview-peek-stage { position: relative; block-size: 100%; }
|
|
3092
|
+
/* Point and speak: the recording bar floats over the top of the preview while
|
|
3093
|
+
the finger stays on the shell's mic, so nothing under it moves. */
|
|
3094
|
+
.preview-voice { position: absolute; inset-inline: var(--space-2); top: var(--space-2); z-index: var(--z-sticky); padding: var(--space-1) var(--space-2); border: 1px solid var(--line-strong); border-radius: var(--radius-full); background: var(--surface); box-shadow: var(--shadow-lg); }
|
|
3091
3095
|
.app-terminal-status { padding: var(--space-2) var(--space-4); margin: 0; color: var(--muted); font-size: var(--text-sm); }
|
|
3092
3096
|
.artifacts-group { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
|
|
3093
3097
|
.artifacts-group:last-child { margin-bottom: 0; }
|
package/package.json
CHANGED