…
@media (max-width:600px){.th-cap textarea{min-height:calc(4 * 1.45em + 18px)}.th-cap .th-keys{display:none}.th-pop{left:0!important;width:100%!important}}
@media (prefers-reduced-motion:reduce){.th-mic.on,.th-card.th-flash{animation:none}}
-</style></head>
+.th-thread{display:flex;flex-direction:column;max-height:85vh}.th-thead{display:flex;gap:8px;align-items:center;margin:-2px 0 8px}.th-thead b{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.th-thread .th-tbody{max-height:58vh;min-height:4em}.th-tfoot{margin-top:10px;padding-top:10px;border-top:1px solid var(--line)}.th-tfoot textarea{width:100%;font:calc(15px*var(--fz,1))/1.45 var(--body);padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--paper);color:var(--ink);resize:vertical;min-height:4.5em}.th-tfoot textarea:focus{outline:2px solid var(--accent);outline-offset:-1px}.th-tfoot .th-acts{display:flex;gap:8px;align-items:center;margin-top:6px}.th-thread.th-tbig{box-shadow:0 10px 40px rgba(0,0,0,.3)}.th-thread.th-tbig .th-tbody{max-height:65vh}.th-capcol.th-threading>*:not(.th-thread){display:none!important}.th-thread.th-incap{flex:1 1 auto}.th-thread.th-incap .th-tbody{max-height:65vh}.th-thread.th-incap .th-tbody{max-height:16em;min-height:6em}.th-thread.th-incap .th-tfoot textarea{min-height:3.2em}.th-thread .th-tbody{overflow-y:auto;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--paper);font-size:calc(15px*var(--fz,1));overflow-wrap:anywhere}</style></head>
<body>
<div class="wrap">
…
<textarea id="th-text" rows="2" placeholder="A thought… #tags tag it, [[links]] link to topics" aria-label="A new thought"></textarea>
<p class="th-interim" id="th-interim" hidden></p>
+<div class="th-acts"><span class="th-voice" id="th-voice" hidden><button type="button" class="btn th-mic" id="th-mic" aria-label="Dictate" title="Dictate: say ok and a phrase to act (ok new bug, ok tag word, ok save, ok send it, ok stop)">🎙</button></span><button class="btn sm primary" id="th-save" type="button">Save</button><button type="button" class="btn sm primary th-send" id="th-send" data-flow="" hidden>Send ▾</button><button type="button" class="th-chip th-to" id="th-to" hidden title="Save to another of your projects"></button><button class="btn sm primary th-edbtn" id="th-eans" type="button">Send answer</button><button class="btn sm primary th-edbtn" id="th-esave" type="button">Save</button><button class="btn sm th-edbtn" id="th-ecancel" type="button">Cancel</button><button class="btn sm th-edbtn" id="th-earch" type="button">Archive</button><span class="th-keys">⌘↵ saves</span></div>
-<div class="th-acts"><span class="th-voice" id="th-voice" hidden><button type="button" class="btn th-mic" id="th-mic" aria-label="Dictate" title="Dictate: say ok and a phrase to act (ok new bug, ok tag word, ok save, ok send it, ok stop)">🎙</button></span><button class="btn sm primary" id="th-save" type="button">Save</button><button type="button" class="btn sm primary th-send" id="th-send" data-flow="" hidden>Send ▾</button><button type="button" class="th-chip th-to" id="th-to" hidden title="Save to another of your projects"></button><button class="btn sm primary th-edbtn" id="th-eans" type="button">Send answer</button><button class="btn sm primary th-edbtn" id="th-esave" type="button">Save</button><button class="btn sm th-edbtn" id="th-ecancel" type="button">Cancel</button><span class="th-keys">⌘↵ saves</span></div>
<p class="small th-net" id="th-net" role="status" hidden>d2 connection lost — retrying</p>
<p class="small" id="th-msg" role="status"></p><p class="small th-heard" id="th-heard" hidden></p>
…
const extraF=plainView?FOC.filter(f=>!shown.has(f.key)&&(!Q.project||f.key.startsWith(Q.project+"/"))).map(f=>{const i=f.key.indexOf("/");return {project:f.key.slice(0,i),name:f.key.slice(i+1),title:f.title,tags:[]}}):[];
const pin=plainView?`<article class="th-card foc pin" data-inbox="1" tabindex="0"><span class="th-fmark">FOCUS · INBOX · ${n}</span><h3 class="th-title">Inbox</h3><p class="th-prev">Fills itself with your pending bugs, features and ideas${n?"":". Empty now"}.</p></article>`:"";
-return part("th-threads","Threads",th,"thread")+part("th-inbox","Inbox",st,"streak",extra,under)+part("th-focuses","Focuses",fo.concat(extraF),"focus","","",pin)+part("th-others","Others",ot,"other")}
+return part("th-threads","Threads",th,"thread")+(n?part("th-inbox","Inbox",st,"streak",extra,under):`<section class="th-sec" id="th-inbox"><h2>Inbox <span class="n">0</span></h2><p class="small th-empty">Empty now: your pending bugs, features and ideas land here.</p></section>`)+part("th-focuses","Focuses",fo.concat(extraF),"focus","","",pin)+part("th-others","Others",ot,"other")}
function render(flash){if(!M)return;const was=new Map($$("#th-body .th-card[data-key]").map(c=>[c.dataset.key,c.dataset.ver]));byKey.clear();
$("#th-side").innerHTML=side();
…
catch(e){if(!isNet(e)){if(!first){pend--;netLine();if(line)line.hidden=true}throw e}if(first){first=false;pend++;netLine();if(line)line.hidden=false}await sleep(5000)}}}
const grow=()=>{box.style.height="auto";if(box.scrollHeight)box.style.height=Math.max(box.scrollHeight+2,box.offsetHeight)+"px"};
+const setBox=t=>{box.value=t;if(window.d2brain)window.d2brain.follow(t);box.style.height="";if(t)grow();syncSend()};
-const setBox=t=>{box.value=t;if(window.d2brain)window.d2brain.follow(t);box.style.height="";grow();syncSend()};
const chip=$("#th-to");chip.onclick=()=>{const s=pick(to,p=>{to=p;chip.textContent="to "+p;chip.hidden=to===M.project;s.replaceWith(chip)});s.setAttribute("aria-label","Save to project");chip.replaceWith(s);s.focus()};
const pick=(cur,f)=>{const s=mk("select","th-pick");for(const p of mine){const o=mk("option","",p);o.value=p;o.selected=p===cur;s.append(o)}s.onchange=()=>f(s.value);return s};
const asking=on=>{$("#th-cap").classList.toggle("th-asking",on);$("#th-esave").classList.toggle("primary",!on)};
// Expand (⤢) opens a card in the top box; what was being typed there parks as a chip above it (P-1144, option 1), never lost
-function maximize(t,text=answer(t.text),close=()=>{}){close();if(!ed)aside=box.value;ed={k:key(t),n:t.name,v:t.ver,p:t.project,ask:isAsk(t),t0:text};asking(ed.ask);setBox(text);
+function maximize(t,text=answer(t.text),close=()=>{}){close();if(!ed)aside=box.value;ed={k:key(t),n:t.name,v:t.ver,p:t.project,ask:isAsk(t),t0:text,a:arch(t)};$("#th-earch").textContent=ed.a?"Unarchive":"Archive";asking(ed.ask);setBox(text);
const ti=(text.split("\n").find(l=>l.trim())||"").replace(/^#+\s*/,"").trim(),md=$("#th-mode");$("#th-cap").classList.add("th-editing");
md.textContent="Editing "+(ti.length>60?ti.slice(0,59)+"…":ti);md.hidden=false;const dr=$("#th-draft");dr.hidden=!aside.trim();$("#th-dtext").textContent="“"+aside.trim().replace(/\s+/g," ").slice(0,80)+"”";
$("#th-cap").scrollIntoView({block:"start",behavior:"smooth"});box.focus();box.selectionStart=box.selectionEnd=box.value.length}
-function endEd(){ed=null;asking(false);setBox(aside);aside="";$("#th-cap").classList.remove("th-editing");$("#th-mode").hidden=true;$("#th-draft").hidden=true}
+function endEd(){ed=null;asking(false);$("#th-cap").classList.remove("th-editing");setBox(aside);aside="";$("#th-mode").hidden=true;$("#th-draft").hidden=true}
$("#th-dback").onclick=async()=>{if(ed&&box.value!==ed.t0&&!(await confirmBox({title:"Back to your draft?",text:"Your changes to this thought aren't saved.",ok:"Leave them"})))return;endEd();box.focus()};
const isAsk=t=>!has(t,"sent")&&!has(t,"working")&&["ai-question","waiting"].some(g=>has(t,g));
…
const snd=$("#th-send");sendBtn(snd,box,()=>capSend(),setBox);const syncSend=()=>{if(snd.sync)snd.sync();const ea=$("#th-eans");if(ea&&ea.sync)ea.sync()};
sendBtn($("#th-eans"),box,()=>capSend(),setBox,"Send answer");$("#th-eans").dataset.flow="1";
+$("#th-save").onclick=()=>cap(false);$("#th-esave").onclick=()=>saveEd();$("#th-earch").onclick=async()=>{const e=ed;if(!e)return;try{await api("POST","/api/v2/thoughts/"+enc(e.n)+"/archive"+pq(e.p),{on:!e.a});endEd();say("");await refresh()}catch(x){say(msgOf(x))}};$("#th-ecancel").onclick=async()=>{if(ed&&box.value!==ed.t0&&!(await confirmBox({title:"Cancel this edit?",text:"Your changes to this thought aren't saved.",ok:"Discard them"})))return;endEd()};
-$("#th-save").onclick=()=>cap(false);$("#th-esave").onclick=()=>saveEd();$("#th-ecancel").onclick=async()=>{if(ed&&box.value!==ed.t0&&!(await confirmBox({title:"Cancel this edit?",text:"Your changes to this thought aren't saved.",ok:"Discard them"})))return;endEd()};
box.addEventListener("keydown",e=>{if(e.key==="Enter"&&(e.ctrlKey||e.metaKey)){e.preventDefault();cap(false)}});box.addEventListener("input",grow);
if(!(matchMedia&&matchMedia("(max-width:720px)").matches))box.focus();
…
// ---- an open card: its editor floats over it; Save and Send in the row, the rest behind ⋮ (P-1140 1) ----
let pop=null;
+// a thread opens read-only, like a pipeline item: the conversation rendered, ✎ Edit at its top, your answer box at its foot (razie, chat 6aca0fe8, 2026-10-10)
+const isThread=t=>has(t,"thread")||t.text.includes(MK)||opensInView(t);
+function threadView(c,t){const main=$("#th-main"),n=t.name,p=t.project,q=pq(p),home=p===M.project?"":(t.home||""),who=t.to||"designer";
+const P=mk("div","th-pop th-thread");P.setAttribute("role","dialog");P.setAttribute("aria-label","Thread");
+const s=answer(t.text),i=s.lastIndexOf(MK),body=i<0?s:s.slice(0,i);
+const hd=mk("div","th-thead"),ttl=mk("b","",(body.split("\n").find(l=>l.trim())||"").replace(/(^|\s)#[A-Za-z0-9][\w-]*/g,"").replace(/^#+\s*/,"").trim().slice(0,90));
+const eb=mk("button","btn sm","✎ Edit");eb.type="button";eb.onclick=e=>{e.stopPropagation();close();maximize(t)};
+const x=mk("button","ibtn","×");x.type="button";x.setAttribute("aria-label","Close");x.onclick=e=>{e.stopPropagation();close()};
+hd.append(ttl,mk("span","th-chip","with "+who),x);
+const vw=mk("div","th-view md th-tbody");vw.innerHTML=md(body,home);
+const ft=mk("div","th-tfoot"),ta=mk("textarea"),acts=mk("div","th-acts"),em=mk("p","small bad");em.hidden=true;ta.rows=3;ta.placeholder="Your answer…";ta.setAttribute("aria-label","Your answer");
+const send=mk("button","btn sm primary","Send answer → "+who);send.type="button";
+send.onclick=async e=>{e.stopPropagation();if(!ta.value.trim())return ta.focus();em.hidden=true;try{const base=i<0?s.trimEnd()+"\n\n"+MK+"\n":s.trimEnd()+"\n";await retrying(()=>api("POST","/api/v2/thoughts/"+enc(n)+"/send"+q,{text:base+ta.value.trim()+"\n",ver:t.ver}));close();await refresh(key(t))}catch(er){em.textContent=msgOf(er);em.hidden=false}};
+const ar=mk("button","btn sm",arch(t)?"Unarchive":"Archive");ar.type="button";ar.onclick=async e=>{e.stopPropagation();try{await api("POST","/api/v2/thoughts/"+enc(n)+"/archive"+q,{on:!arch(t)});close();await refresh()}catch(er){em.textContent=msgOf(er);em.hidden=false}};
+acts.append(send,ar,eb,mk("span","th-keys","⌘↵ sends"));ft.append(ta,acts,em);P.append(hd,vw,ft);
+ta.addEventListener("keydown",e=>{if(e.key==="Enter"&&(e.ctrlKey||e.metaKey)){e.preventDefault();send.click()}});
+const onKey=e=>{if(e.key==="Escape"&&!ta.value.trim())close()},onOut=e=>{if(P.classList.contains("th-incap")||P.contains(e.target)||ta.value.trim())return;close()};
+const close=()=>{P.remove();const cp=$("#th-cap .th-capcol");if(cp)cp.classList.remove("th-threading");removeEventListener("keydown",onKey);document.removeEventListener("pointerdown",onOut,true);if(pop&&pop.el===P)pop=null};
+const cap=$("#th-cap .th-capcol"),big=s.length>LONG&&!!cap;if(big){P.classList.remove("th-pop");P.classList.add("th-incap");cap.classList.add("th-threading");cap.prepend(P);cap.scrollIntoView({block:"start",behavior:"smooth"})}else{const r=c.getBoundingClientRect(),m=main.getBoundingClientRect();P.style.top=Math.max(0,r.top-m.top)+"px";main.append(P)}
+pop={k:key(t),el:P,close,dirty:()=>!!ta.value.trim()};addEventListener("keydown",onKey);document.addEventListener("pointerdown",onOut,true);ta.focus({preventScroll:true})}
function edit(c){const t=byKey.get(c.dataset.key);if(!t)return;if(pop){if(pop.k===key(t)||pop.dirty())return;pop.close()}
-if(t.text.length>LONG)return maximize(t);
+if(isThread(t))return threadView(c,t);if(t.text.length>LONG)return maximize(t);
const main=$("#th-main"),n=t.name,v=t.ver,isA=arch(t),p=t.project,q=pq(p),here=p===M.project,home=here?"":(t.home||"");
const P=mk("div","th-pop"),d=mk("div","th-edit");P.append(d);P.setAttribute("role","dialog");P.setAttribute("aria-label","Edit the thought");
…
const svb=b("Save",ask?"":"primary",async()=>{await retrying(()=>api("PUT","/api/v2/thoughts/"+enc(n)+q,{text:ta.value,ver:v}),net);await done()});const sv=sa||svb;
if(FLOW&&!ask){const sb=b("Send","primary th-send",sendIt);sb.dataset.flow="1";const s1=sb.onclick;sendBtn(sb,ta,()=>s1({stopPropagation(){}}),setTa)}
+b(isA?"Unarchive":"Archive","",async()=>{await api("POST","/api/v2/thoughts/"+enc(n)+"/archive"+q,{on:!isA});await done(false)});const keys=mk("span","th-keys","⌘↵ "+(ask?"sends":"saves"));acts.append(keys);
-const keys=mk("span","th-keys","⌘↵ "+(ask?"sends":"saves"));acts.append(keys);
// ⋮: everything else
const kb=mk("button","ibtn th-kebab","⋮");kb.type="button";kb.title="More";kb.setAttribute("aria-label","More actions");kb.setAttribute("aria-haspopup","menu");kb.setAttribute("aria-expanded","false");acts.append(kb);
…
setInterval(poll,3000);document.addEventListener("visibilitychange",()=>{if(!document.hidden)poll()});
if("IntersectionObserver" in window)new IntersectionObserver(es=>{if(es.some(x=>x.isIntersecting))more()},{rootMargin:"400px"}).observe($("#th-more"));
-Object.assign(window.d2th,{edit:c=>edit(typeof c==="string"?$(`.th-card[data-key="${c}"]`):c),maximize,tick,load,nav,openConcept,state:()=>({Q,next,n:cards.length,skip,flow:FLOW,to,ed:ed&&ed.k,pop:pop&&pop.k,concept:concept&&concept.id})});
+Object.assign(window.d2th,{edit:c=>edit(typeof c==="string"?$(`.th-card[data-key="${c}"]`):c),maximize,tick,load,nav,openConcept,state:()=>({Q,next,n:cards.length,skip,flow:FLOW,to,ed:ed&&ed.k,pop:pop&&pop.k,concept:concept&&concept.id})});window.d2state=window.d2th.state;
new MutationObserver(()=>fit($("#th-body"))).observe($("#th-body"),{childList:true,subtree:true});addEventListener("resize",()=>fit($("#th-body")));
load().then(()=>{if(PARAMS.concept)openConcept(/^[cf]:/.test(PARAMS.concept)?PARAMS.concept:"x:"+encodeURIComponent(PARAMS.concept))}).catch(e=>{$("#th-body").innerHTML=`<p class="bad">Your thoughts didn't load: ${esc(msgOf(e))}</p><p><button type="button" class="btn sm" id="th-retry">Try again</button></p>`;$("#th-retry").onclick=()=>location.reload()});
…