…
.btn.talk{margin-top:8px}.btn.talk:disabled{opacity:.45;cursor:default;filter:grayscale(1)}.btn.talk:not(:disabled){background:var(--lv);border-color:var(--lv);color:#fff}
.btn.meld{background:var(--lv);border-color:var(--lv);color:#fff}.btn.meld[aria-pressed="true"]{background:var(--ok);border-color:var(--ok)}
+.mm{display:inline-flex;vertical-align:-3px;padding:2px;border-radius:50%;border:1px solid currentColor}
+.mwrap{position:relative;display:inline-block;max-width:100%}
+.pop{position:absolute;left:0;top:calc(100% + 8px);z-index:5;width:min(320px,calc(100vw - 32px));padding:12px 30px 12px 14px;border-radius:12px;background:var(--card);border:1px solid var(--lv);box-shadow:0 6px 24px rgba(0,0,0,.18);font-size:14px;line-height:1.45}
+.pop b:first-of-type{display:block;margin-bottom:4px}.pop::before{content:"";position:absolute;top:-7px;left:22px;width:12px;height:12px;background:var(--card);border-left:1px solid var(--lv);border-top:1px solid var(--lv);transform:rotate(45deg)}
+.pop .mm{color:var(--lv)}.px{position:absolute;top:6px;right:8px;border:0;background:none;font-size:18px;line-height:1;color:var(--soft);cursor:pointer}
.also{margin-top:10px;padding:12px 14px;border-radius:12px;border:1px solid color-mix(in srgb,var(--ok) 45%,var(--line));background:color-mix(in srgb,var(--ok) 8%,var(--card))}.also>b{display:block}
.dot{display:inline-block;width:10px;height:10px;border-radius:50%;background:var(--line);margin-right:6px;vertical-align:0}.dot.on{background:var(--ok)}
…
<h2>How the loop works</h2>
<ul>
+<li><b>Where you talk:</b> the chat panel on every d2 page, not your AI's app.<br><button class="btn talk" id="talk" type="button" disabled title="Lights up when your AI's loop is listening">💬 TurboChat</button> <span class="small" id="tsay"></span></li>
-<li><b>Where you talk:</b> the chat panel on every d2 page, not your AI's app.<br><button class="btn talk" id="talk" type="button" disabled title="Lights up when your AI's loop is listening">💬 Chat with your AI here</button> <span class="small" id="tsay"></span></li>
<li><b>How it asks you things:</b> TurboChat. Its options come as a pad: tap to pick, one <b>CONTINUE</b> sends your answer.</li>
<li><b>While it runs:</b> keep that AI chat open (computer or phone); it uses your AI as usual, only while you chat.</li>
…
<li><b>Off by default</b>, per tab: the <b>Mind Meld</b> button in the top bar turns it on or off any time, and so does <b>Meld</b> in the chat panel.</li>
</ul>
+<div class="mwrap"><button class="btn meld" id="meld" type="button" aria-pressed="false"><span class="mm"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="2"/><path d="M7.8 7.8a6 6 0 0 0 0 8.4M16.2 7.8a6 6 0 0 1 0 8.4"/><path d="M4.9 4.9a10 10 0 0 0 0 14.2M19.1 4.9a10 10 0 0 1 0 14.2"/></svg></span> <span id="mlabel">Turn on Mind Meld</span></button> <span class="small" id="msay"></span>
+<div class="pop" id="mpop" role="dialog" aria-label="Start a chat first" hidden><button class="px" id="mpx" type="button" aria-label="Close">×</button>
-<p><button class="btn meld" id="meld" type="button" aria-pressed="false">Turn on Mind Meld</button> <span class="small" id="msay"></span></p>
+<b>Start your TurboChat first</b>Press <b>💬 TurboChat</b> above, under <i>Where you talk</i>; once its chat is open, click <span class="mm"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="2"/><path d="M7.8 7.8a6 6 0 0 0 0 8.4M16.2 7.8a6 6 0 0 1 0 8.4"/><path d="M4.9 4.9a10 10 0 0 0 0 14.2M19.1 4.9a10 10 0 0 1 0 14.2"/></svg></span> to turn on Mind Meld for it.</div></div>
<h2>If it doesn't connect</h2>
<ul>
…
try{await api("POST","/api/v2/project/home",{to:"dashboard"})}catch(e){if(!/E_SCOPE|E_FORBIDDEN|403/.test(String(e&&e.message||e))&&!/E_NOT_FOUND/.test(String(e&&e.message||e))){say.textContent=msg(e);b.disabled=false;return}}
go("/")};
+let LOOP="",CHAT=false;const PROJECT=(D2.project||location.hostname.split(".")[0]);let since=new Date().toISOString(),busy=false,until=0;
-let LOOP="";const PROJECT=(D2.project||location.hostname.split(".")[0]);let since=new Date().toISOString(),busy=false,until=0;
const make=async()=>{if(busy)return;busy=true;$("#lsay").textContent="";$("#ln").textContent="Making your loop line…";
try{const r=await api("POST","/api/v2/onboard/setup",{project:PROJECT,kind:"app-loop"});$("#ln").textContent=(r&&r.line)||"No line came back.";
…
// Mind Meld: the same as the panel's Meld button: keep the open chat, then the top bar's switch (#d2esp); its state is per tab (sessionStorage d2esp)
const meldOn=()=>{try{return sessionStorage.getItem("d2esp")==="1"}catch(e){return false}};
+const meldDraw=()=>{const on=meldOn();$("#meld").setAttribute("aria-pressed",String(on));$("#mlabel").textContent=on?"Mind Meld is on ✓ (turn off)":"Turn on Mind Meld"};
+// a chat must be open first: this page's chat button, or any open chat in the panel
+const chatOpen=()=>{if(CHAT)return true;try{const st=window.d2chat&&window.d2chat.state&&window.d2chat.state();return !!(st&&(st.chats||[]).some(c=>c.state==="live"||c.state==="asked"))}catch(_){return false}};
+$("#mpx").onclick=()=>{$("#mpop").hidden=true};
+document.addEventListener("click",e=>{if(!$("#mpop").hidden&&!e.target.closest(".mwrap"))$("#mpop").hidden=true});
+document.addEventListener("keydown",e=>{if(e.key==="Escape")$("#mpop").hidden=true});
-const meldDraw=()=>{const on=meldOn();$("#meld").setAttribute("aria-pressed",String(on));$("#meld").textContent=on?"Mind Meld is on ✓ (turn off)":"Turn on Mind Meld"};
$("#meld").onclick=()=>{const esp=document.getElementById("d2esp");$("#msay").textContent="";
+ if(!meldOn()&&!chatOpen()){$("#mpop").hidden=false;return}
+ $("#mpop").hidden=true;
if(!esp){$("#msay").textContent="Turn it on with Mind Meld in the top bar.";return}
esp.click();setTimeout(meldDraw,300)};
…
const subject={kind:"page",ref:location.pathname};
if(window.d2chat&&window.d2chat.start)await window.d2chat.start({role,agent:LOOP,subject});
- else{await api("POST","/api/v2/chats",{role,agent:LOOP,subject});$("#tsay").textContent="Chat started: open it from 💬, bottom right."}
+ else{await api("POST","/api/v2/chats",{role,agent:LOOP,subject});$("#tsay").textContent="TurboChat started: open it from 💬, bottom right."}
+ b.textContent="💬 TurboChat open";CHAT=true;$("#mpop").hidden=true}
- b.textContent="💬 Chat open";}
catch(x){$("#tsay").textContent=msg(x);b.disabled=false}};
$("#cpsl").onclick=async e=>{try{await navigator.clipboard.writeText("start loop");e.target.textContent="Copied"}catch(_){e.target.textContent="Select and copy"}};
…