@tstax/coding-tab 0.2.2 → 0.2.3

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/browser.js CHANGED
@@ -1,110 +1,110 @@
1
- "use strict";var CodingTab=(()=>{var H=Object.defineProperty;var gt=Object.getOwnPropertyDescriptor;var ht=Object.getOwnPropertyNames;var pt=Object.prototype.hasOwnProperty;var mt=(d,o)=>{for(var l in o)H(d,l,{get:o[l],enumerable:!0})},ft=(d,o,l,i)=>{if(o&&typeof o=="object"||typeof o=="function")for(let g of ht(o))!pt.call(d,g)&&g!==l&&H(d,g,{get:()=>o[g],enumerable:!(i=gt(o,g))||i.enumerable});return d};var bt=d=>ft(H({},"__esModule",{value:!0}),d);var Tt={};mt(Tt,{mountCodingTab:()=>Y});function u(d){return d.replace(/[&<>"']/g,o=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[o])}function A(d){let o=[],l=/`([^`\n]+)`/g,i=0,g;for(;(g=l.exec(d))!==null;)o.push(J(d.slice(i,g.index))),o.push(`<code>${g[1]}</code>`),i=g.index+g[0].length;return o.push(J(d.slice(i))),o.join("")}function J(d){return d.replace(/\*\*([^*\n]+)\*\*/g,"<strong>$1</strong>").replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g,"$1<em>$2</em>").replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g,(o,l,i)=>`<a href="${/^(https?:|mailto:|\/|#)/i.test(i)?i:"#"}" target="_blank" rel="noreferrer noopener">${l}</a>`)}function W(d,o){if(d.length===0)return;let l=d.join(" ").trim();d.length=0,l&&o.push(`<p>${A(l)}</p>`)}function V(d,o){for(;d.length>0;){let l=d.pop();o.push(`<${l.type}>${l.items.join("")}</${l.type}>`)}}function z(d){let l=u(d).replace(/\r\n/g,`
1
+ "use strict";var CodingTab=(()=>{var N=Object.defineProperty;var vt=Object.getOwnPropertyDescriptor;var _t=Object.getOwnPropertyNames;var yt=Object.prototype.hasOwnProperty;var $t=(l,i)=>{for(var u in i)N(l,u,{get:i[u],enumerable:!0})},wt=(l,i,u,r)=>{if(i&&typeof i=="object"||typeof i=="function")for(let h of _t(i))!yt.call(l,h)&&h!==u&&N(l,h,{get:()=>i[h],enumerable:!(r=vt(i,h))||r.enumerable});return l};var kt=l=>wt(N({},"__esModule",{value:!0}),l);var Lt={};$t(Lt,{mountCodingTab:()=>Z});function g(l){return l.replace(/[&<>"']/g,i=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[i])}function q(l){let i=[],u=/`([^`\n]+)`/g,r=0,h;for(;(h=u.exec(l))!==null;)i.push(V(l.slice(r,h.index))),i.push(`<code>${h[1]}</code>`),r=h.index+h[0].length;return i.push(V(l.slice(r))),i.join("")}function V(l){return l.replace(/\*\*([^*\n]+)\*\*/g,"<strong>$1</strong>").replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g,"$1<em>$2</em>").replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g,(i,u,r)=>`<a href="${/^(https?:|mailto:|\/|#)/i.test(r)?r:"#"}" target="_blank" rel="noreferrer noopener">${u}</a>`)}function z(l,i){if(l.length===0)return;let u=l.join(" ").trim();l.length=0,u&&i.push(`<p>${q(u)}</p>`)}function K(l,i){for(;l.length>0;){let u=l.pop();i.push(`<${u.type}>${u.items.join("")}</${u.type}>`)}}function Y(l){let u=g(l).replace(/\r\n/g,`
2
2
  `).split(`
3
- `),i=[],g=[],p=[],a=!1,f="",m=[],k=()=>{W(g,i),V(p,i)};for(let S=0;S<l.length;S++){let b=l[S],M=b.match(/^\s*```(\w*)\s*$/);if(M){if(a){let _=f?` class="lang-${f}"`:"";i.push(`<pre><code${_}>${m.join(`
4
- `)}</code></pre>`),m.length=0,f="",a=!1}else k(),a=!0,f=M[1]??"";continue}if(a){m.push(b);continue}if(!b.trim()){k();continue}if(/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(b)){k(),i.push("<hr />");continue}let C=b.match(/^(#{1,6})\s+(.*)$/);if(C){k();let _=Math.min(6,C[1].length);i.push(`<h${_}>${A(C[2].trim())}</h${_}>`);continue}let y=b.match(/^(\s*)[-*]\s+(.*)$/),c=b.match(/^(\s*)(\d+)\.\s+(.*)$/);if(y||c){W(g,i);let _=(y?y[1]:c[1]).length,I=y?"ul":"ol",L=y?y[2]:c[3];for(;p.length>0&&p[p.length-1].indent>_;){let E=p.pop(),w=p[p.length-1]?.items,P=`<${E.type}>${E.items.join("")}</${E.type}>`;w&&w.length>0?w[w.length-1]=w[w.length-1].replace(/<\/li>$/,`${P}</li>`):i.push(P)}let T=p[p.length-1];!T||T.indent<_||T.type!==I?p.push({type:I,indent:_,items:[`<li>${A(L)}</li>`]}):T.items.push(`<li>${A(L)}</li>`);continue}p.length>0&&V(p,i),g.push(b.trim())}return a?i.push(`<pre><code>${m.join(`
5
- `)}</code></pre>`):k(),i.join(`
6
- `)}var F='<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8a8 8 0 005.47 7.59c.4.07.55-.17.55-.38v-1.34c-2.23.48-2.7-1.07-2.7-1.07-.36-.92-.89-1.16-.89-1.16-.73-.5.05-.49.05-.49.81.06 1.23.83 1.23.83.72 1.23 1.88.87 2.34.66.07-.52.28-.87.5-1.07-1.78-.2-3.65-.89-3.65-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.13 0 0 .67-.21 2.2.82a7.65 7.65 0 014 0c1.53-1.04 2.2-.82 2.2-.82.44 1.11.16 1.93.08 2.13.51.56.82 1.28.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.74.54 1.49v2.21c0 .21.15.46.55.38A8 8 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>',K='<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M8 3v10M3 8h10"/></svg>',_t='<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"><path d="M3 4h10M6 4V2.5a.5.5 0 01.5-.5h3a.5.5 0 01.5.5V4M5 4l.7 9.1a.9.9 0 00.9.9h2.8a.9.9 0 00.9-.9L11 4"/></svg>',vt='<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M11 2.5l2.5 2.5L6 12.5 3 13l.5-3z"/></svg>',$t='<svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M2.5 4h11M2.5 8h11M2.5 12h11"/></svg>',yt='<svg class="ct-chevron" width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 2l4 3-4 3"/></svg>';function $(d){return u(d)}function O(d){let o=d.match(/github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?\/?$/);return o?`${o[1]}/${o[2]}`:d}function wt(d){let o=d.match(/github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?\/?$/);return o?`https://github.com/${o[1]}/${o[2]}`:d.replace(/\.git\/?$/,"")}function kt(d){return`${wt(d)}/pulls`}function v(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():Math.random().toString(36).slice(2)}function St(d){let o=Date.now()-d;return o<6e4?"just now":o<36e5?`${Math.round(o/6e4)}m`:o<864e5?`${Math.round(o/36e5)}h`:o<6048e5?`${Math.round(o/864e5)}d`:new Date(d).toLocaleDateString()}function Y(d,o){let l=o.apiBase.replace(/\/$/,""),i=document.createElement("div");i.className="coding-tab",d.innerHTML="",d.appendChild(i);let g=!1;function p(){if(g||(g=!0,document.querySelector('link[data-coding-tab="style"]')))return;let t=document.createElement("link");t.rel="stylesheet",t.href=`${l}/style.css`,t.dataset.codingTab="style",document.head.appendChild(t)}p();let a={me:null,models:[],mode:o.defaultMode??"plan",model:o.defaultModel??"sonnet",repoUrl:o.defaultRepo??"",repoLocked:!1,chats:[],activeChatId:null,sidebarOpen:window.innerWidth>=720,expandedTools:new Set,mergeStates:new Map};function f(){return a.chats.find(t=>t.meta.id===a.activeChatId)??null}async function m(t,n){let e=await fetch(`${l}${t}`,{credentials:"include",headers:{"Content-Type":"application/json",...n?.headers??{}},...n});if(!e.ok)throw Object.assign(new Error(`${e.status} ${e.statusText}`),{status:e.status});return await e.json()}async function k(){try{let{chats:t}=await m("/chats"),n=new Map(a.chats.map(e=>[e.meta.id,e]));a.chats=t.map(e=>{let r=n.get(e.id);return r?{...r,meta:e}:{meta:e,loaded:!1,isStreaming:!1,activeRunId:null,abort:null}})}catch(t){console.error("[coding-tab] /chats list failed",t),a.chats=[]}}async function S(t){let n=a.chats.find(e=>e.meta.id===t);if(!n)return null;if(n.loaded)return n;try{let{chat:e}=await m(`/chats/${t}`);return n.turns=e.turns,n.loaded=!0,n.meta={id:e.id,title:e.title,mode:e.mode,model:e.model,createdAt:e.createdAt,updatedAt:e.updatedAt},a.mode=e.mode,a.model=e.model,n}catch(e){return console.error(`[coding-tab] /chats/${t} load failed`,e),null}}async function b(){try{let{chat:t}=await m("/chats",{method:"POST",body:JSON.stringify({mode:a.mode,model:a.model,repoUrl:a.repoLocked?void 0:a.repoUrl||void 0})}),n={meta:{id:t.id,title:t.title,mode:t.mode,model:t.model,createdAt:t.createdAt,updatedAt:t.updatedAt},turns:[],loaded:!0,isStreaming:!1,activeRunId:null,abort:null};return a.chats.unshift(n),a.activeChatId=n.meta.id,n}catch(t){return console.error("[coding-tab] create chat failed",t),null}}async function M(t){a.activeChatId=t,window.innerWidth<720&&(a.sidebarOpen=!1),c(),await S(t),c()}async function C(t){let n=a.chats.find(e=>e.meta.id===t);if(n&&confirm(`Delete chat "${n.meta.title}"? This cannot be undone.`)){n.abort?.abort();try{await m(`/chats/${t}`,{method:"DELETE"})}catch(e){console.error(`[coding-tab] delete chat ${t} failed`,e);return}a.chats=a.chats.filter(e=>e.meta.id!==t),a.activeChatId===t&&(a.activeChatId=a.chats[0]?.meta.id??null),c()}}async function y(t){let n=a.chats.find(s=>s.meta.id===t);if(!n)return;let e=prompt("Rename chat",n.meta.title);if(e===null)return;let r=e.trim();if(!(!r||r===n.meta.title))try{let{chat:s}=await m(`/chats/${t}`,{method:"PATCH",body:JSON.stringify({title:r})});n.meta={...n.meta,title:s.title,updatedAt:s.updatedAt},c()}catch(s){console.error(`[coding-tab] rename chat ${t} failed`,s)}}function c(){if(!a.me){i.innerHTML=`
3
+ `),r=[],h=[],p=[],a=!1,f="",m=[],k=()=>{z(h,r),K(p,r)};for(let C=0;C<u.length;C++){let b=u[C],P=b.match(/^\s*```(\w*)\s*$/);if(P){if(a){let v=f?` class="lang-${f}"`:"";r.push(`<pre><code${v}>${m.join(`
4
+ `)}</code></pre>`),m.length=0,f="",a=!1}else k(),a=!0,f=P[1]??"";continue}if(a){m.push(b);continue}if(!b.trim()){k();continue}if(/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(b)){k(),r.push("<hr />");continue}let M=b.match(/^(#{1,6})\s+(.*)$/);if(M){k();let v=Math.min(6,M[1].length);r.push(`<h${v}>${q(M[2].trim())}</h${v}>`);continue}let $=b.match(/^(\s*)[-*]\s+(.*)$/),c=b.match(/^(\s*)(\d+)\.\s+(.*)$/);if($||c){z(h,r);let v=($?$[1]:c[1]).length,R=$?"ul":"ol",A=$?$[2]:c[3];for(;p.length>0&&p[p.length-1].indent>v;){let I=p.pop(),w=p[p.length-1]?.items,H=`<${I.type}>${I.items.join("")}</${I.type}>`;w&&w.length>0?w[w.length-1]=w[w.length-1].replace(/<\/li>$/,`${H}</li>`):r.push(H)}let E=p[p.length-1];!E||E.indent<v||E.type!==R?p.push({type:R,indent:v,items:[`<li>${q(A)}</li>`]}):E.items.push(`<li>${q(A)}</li>`);continue}p.length>0&&K(p,r),h.push(b.trim())}return a?r.push(`<pre><code>${m.join(`
5
+ `)}</code></pre>`):k(),r.join(`
6
+ `)}var Q='<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8a8 8 0 005.47 7.59c.4.07.55-.17.55-.38v-1.34c-2.23.48-2.7-1.07-2.7-1.07-.36-.92-.89-1.16-.89-1.16-.73-.5.05-.49.05-.49.81.06 1.23.83 1.23.83.72 1.23 1.88.87 2.34.66.07-.52.28-.87.5-1.07-1.78-.2-3.65-.89-3.65-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.13 0 0 .67-.21 2.2.82a7.65 7.65 0 014 0c1.53-1.04 2.2-.82 2.2-.82.44 1.11.16 1.93.08 2.13.51.56.82 1.28.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.74.54 1.49v2.21c0 .21.15.46.55.38A8 8 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>',X='<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M8 3v10M3 8h10"/></svg>',St='<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"><path d="M3 4h10M6 4V2.5a.5.5 0 01.5-.5h3a.5.5 0 01.5.5V4M5 4l.7 9.1a.9.9 0 00.9.9h2.8a.9.9 0 00.9-.9L11 4"/></svg>',Tt='<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M11 2.5l2.5 2.5L6 12.5 3 13l.5-3z"/></svg>',Ct='<svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M2.5 4h11M2.5 8h11M2.5 12h11"/></svg>',Et='<svg class="ct-chevron" width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 2l4 3-4 3"/></svg>';function y(l){return g(l)}function U(l){let i=l.match(/github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?\/?$/);return i?`${i[1]}/${i[2]}`:l}function xt(l){let i=l.match(/github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?\/?$/);return i?`https://github.com/${i[1]}/${i[2]}`:l.replace(/\.git\/?$/,"")}function Mt(l){return`${xt(l)}/pulls`}function _(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():Math.random().toString(36).slice(2)}function It(l){let i=Date.now()-l;return i<6e4?"just now":i<36e5?`${Math.round(i/6e4)}m`:i<864e5?`${Math.round(i/36e5)}h`:i<6048e5?`${Math.round(i/864e5)}d`:new Date(l).toLocaleDateString()}function Z(l,i){let u=i.apiBase.replace(/\/$/,""),r=document.createElement("div");r.className="coding-tab",l.innerHTML="",l.appendChild(r);let h=!1;function p(){if(h||(h=!0,document.querySelector('link[data-coding-tab="style"]')))return;let t=document.createElement("link");t.rel="stylesheet",t.href=`${u}/style.css`,t.dataset.codingTab="style",document.head.appendChild(t)}p();let a={me:null,models:[],mode:i.defaultMode??"plan",model:i.defaultModel??"sonnet",repoUrl:i.defaultRepo??"",repoLocked:!1,chats:[],activeChatId:null,sidebarOpen:window.innerWidth>=720,expandedTools:new Set,mergeStates:new Map};function f(){return a.chats.find(t=>t.meta.id===a.activeChatId)??null}async function m(t,e){let n=await fetch(`${u}${t}`,{credentials:"include",headers:{"Content-Type":"application/json",...e?.headers??{}},...e});if(!n.ok)throw Object.assign(new Error(`${n.status} ${n.statusText}`),{status:n.status});return await n.json()}async function k(){try{let{chats:t}=await m("/chats"),e=new Map(a.chats.map(n=>[n.meta.id,n]));a.chats=t.map(n=>{let s=e.get(n.id);return s?{...s,meta:n}:{meta:n,loaded:!1,isStreaming:!1,activeRunId:null,abort:null,isReconnecting:!1,pollTurnId:null,pollPromise:null}})}catch(t){console.error("[coding-tab] /chats list failed",t),a.chats=[]}}async function C(t){let e=a.chats.find(n=>n.meta.id===t);if(!e)return null;if(e.loaded)return e;try{let{chat:n}=await m(`/chats/${t}`);return e.turns=n.turns,e.loaded=!0,e.meta={id:n.id,title:n.title,mode:n.mode,model:n.model,createdAt:n.createdAt,updatedAt:n.updatedAt},a.mode=n.mode,a.model=n.model,e}catch(n){return console.error(`[coding-tab] /chats/${t} load failed`,n),null}}async function b(){try{let{chat:t}=await m("/chats",{method:"POST",body:JSON.stringify({mode:a.mode,model:a.model,repoUrl:a.repoLocked?void 0:a.repoUrl||void 0})}),e={meta:{id:t.id,title:t.title,mode:t.mode,model:t.model,createdAt:t.createdAt,updatedAt:t.updatedAt},turns:[],loaded:!0,isStreaming:!1,activeRunId:null,abort:null,isReconnecting:!1,pollTurnId:null,pollPromise:null};return a.chats.unshift(e),a.activeChatId=e.meta.id,e}catch(t){return console.error("[coding-tab] create chat failed",t),null}}async function P(t){a.activeChatId=t,window.innerWidth<720&&(a.sidebarOpen=!1),c(),await C(t),c()}async function M(t){let e=a.chats.find(n=>n.meta.id===t);if(e&&confirm(`Delete chat "${e.meta.title}"? This cannot be undone.`)){e.abort?.abort();try{await m(`/chats/${t}`,{method:"DELETE"})}catch(n){console.error(`[coding-tab] delete chat ${t} failed`,n);return}a.chats=a.chats.filter(n=>n.meta.id!==t),a.activeChatId===t&&(a.activeChatId=a.chats[0]?.meta.id??null),c()}}async function $(t){let e=a.chats.find(o=>o.meta.id===t);if(!e)return;let n=prompt("Rename chat",e.meta.title);if(n===null)return;let s=n.trim();if(!(!s||s===e.meta.title))try{let{chat:o}=await m(`/chats/${t}`,{method:"PATCH",body:JSON.stringify({title:s})});e.meta={...e.meta,title:o.title,updatedAt:o.updatedAt},c()}catch(o){console.error(`[coding-tab] rename chat ${t} failed`,o)}}function c(){if(!a.me){r.innerHTML=`
7
7
  <div class="coding-tab__signin">
8
8
  <h2>Coding Tab</h2>
9
9
  <p>Sign in with GitHub to start a coding session against your repo. Your token is used to clone, push, and open pull requests on your behalf.</p>
10
- <a href="${l}/auth/login">${F}<span>Sign in with GitHub</span></a>
10
+ <a href="${u}/auth/login">${Q}<span>Sign in with GitHub</span></a>
11
11
  </div>
12
12
  `;return}let t=`
13
13
  <div class="coding-tab__header">
14
- <button class="coding-tab__hamburger" data-role="toggle-sidebar" aria-label="Toggle chat list">${$t}</button>
14
+ <button class="coding-tab__hamburger" data-role="toggle-sidebar" aria-label="Toggle chat list">${Ct}</button>
15
15
  <select data-role="model" title="Model">
16
- ${a.models.map(h=>`<option value="${h.choice}" ${a.model===h.choice?"selected":""}>${u(h.displayName)}</option>`).join("")}
16
+ ${a.models.map(d=>`<option value="${d.choice}" ${a.model===d.choice?"selected":""}>${g(d.displayName)}</option>`).join("")}
17
17
  </select>
18
18
  <div class="coding-tab__mode">
19
19
  <button data-mode="plan" class="${a.mode==="plan"?"is-active":""}">Plan</button>
20
20
  <button data-mode="agent" class="${a.mode==="agent"?"is-active":""}">Agent</button>
21
21
  </div>
22
- ${a.repoLocked&&a.repoUrl?`<a class="coding-tab__repo-locked" href="${$(kt(a.repoUrl))}" target="_blank" rel="noreferrer" title="Open ${$(O(a.repoUrl))} pull requests on GitHub">${F}<span>${u(O(a.repoUrl))}</span></a>`:`<input data-role="repo" placeholder="https://github.com/org/repo" value="${$(a.repoUrl)}" style="flex:1;min-width:200px" />`}
22
+ ${a.repoLocked&&a.repoUrl?`<a class="coding-tab__repo-locked" href="${y(Mt(a.repoUrl))}" target="_blank" rel="noreferrer" title="Open ${y(U(a.repoUrl))} pull requests on GitHub">${Q}<span>${g(U(a.repoUrl))}</span></a>`:`<input data-role="repo" placeholder="https://github.com/org/repo" value="${y(a.repoUrl)}" style="flex:1;min-width:200px" />`}
23
23
  <div class="coding-tab__user">
24
24
  ${a.me.avatarUrl?`<img src="${a.me.avatarUrl}" alt="" />`:""}
25
- <span class="coding-tab__user-name">@${u(a.me.githubLogin)}</span>
25
+ <span class="coding-tab__user-name">@${g(a.me.githubLogin)}</span>
26
26
  <button data-role="logout">Sign out</button>
27
27
  </div>
28
28
  </div>
29
- `,n=`
29
+ `,e=`
30
30
  <aside class="coding-tab__sidebar ${a.sidebarOpen?"is-open":""}">
31
- <button class="coding-tab__btn primary coding-tab__new-chat" data-role="new-chat">${K}<span>New chat</span></button>
31
+ <button class="coding-tab__btn primary coding-tab__new-chat" data-role="new-chat">${X}<span>New chat</span></button>
32
32
  <div class="coding-tab__chat-list">
33
- ${a.chats.length===0?'<div class="coding-tab__chat-empty">No chats yet \u2014 start one with the button above.</div>':a.chats.map(h=>_(h)).join("")}
33
+ ${a.chats.length===0?'<div class="coding-tab__chat-empty">No chats yet \u2014 start one with the button above.</div>':a.chats.map(d=>v(d)).join("")}
34
34
  </div>
35
35
  </aside>
36
- `,e=f(),r=`
36
+ `,n=f(),s=`
37
37
  <main class="coding-tab__pane">
38
- ${e?L(e):I()}
38
+ ${n?A(n):R()}
39
39
  </main>
40
- `;i.innerHTML=`
40
+ `;r.innerHTML=`
41
41
  ${t}
42
42
  <div class="coding-tab__body">
43
- ${n}
44
- ${r}
43
+ ${e}
44
+ ${s}
45
45
  </div>
46
- `;let s=i.querySelector("[data-role=thread]");s&&(s.scrollTop=s.scrollHeight),nt()}function _(t){let n=t.meta.id===a.activeChatId?" is-active":"",e=t.isStreaming?'<span class="coding-tab__chat-row-dot" title="Streaming"></span>':"";return`
47
- <div class="coding-tab__chat-row${n}" data-chat-id="${t.meta.id}">
46
+ `;let o=r.querySelector("[data-role=thread]");o&&(o.scrollTop=o.scrollHeight),ot()}function v(t){let e=t.meta.id===a.activeChatId?" is-active":"",n=t.isStreaming?`<span class="coding-tab__chat-row-dot" title="${t.isReconnecting?"Reconnecting":"Streaming"}"></span>`:"";return`
47
+ <div class="coding-tab__chat-row${e}" data-chat-id="${t.meta.id}">
48
48
  <button class="coding-tab__chat-row-main" data-role="select-chat" data-chat-id="${t.meta.id}">
49
- ${e}
50
- <span class="coding-tab__chat-row-title">${u(t.meta.title)}</span>
51
- <span class="coding-tab__chat-row-meta">${t.meta.mode==="plan"?"Plan":"Agent"} \xB7 ${St(t.meta.updatedAt)}</span>
49
+ ${n}
50
+ <span class="coding-tab__chat-row-title">${g(t.meta.title)}</span>
51
+ <span class="coding-tab__chat-row-meta">${t.meta.mode==="plan"?"Plan":"Agent"} \xB7 ${It(t.meta.updatedAt)}</span>
52
52
  </button>
53
53
  <div class="coding-tab__chat-row-actions">
54
- <button data-role="rename-chat" data-chat-id="${t.meta.id}" title="Rename">${vt}</button>
55
- <button data-role="delete-chat" data-chat-id="${t.meta.id}" title="Delete">${_t}</button>
54
+ <button data-role="rename-chat" data-chat-id="${t.meta.id}" title="Rename">${Tt}</button>
55
+ <button data-role="delete-chat" data-chat-id="${t.meta.id}" title="Delete">${St}</button>
56
56
  </div>
57
57
  </div>
58
- `}function I(){return`
58
+ `}function R(){return`
59
59
  <div class="coding-tab__pane-empty">
60
60
  <h3>Start a new chat</h3>
61
61
  <p>Pick a mode (Plan or Agent), choose a model, and click <strong>New chat</strong> to begin. Each chat keeps its own context.</p>
62
- <button class="coding-tab__btn primary" data-role="new-chat">${K}<span>New chat</span></button>
62
+ <button class="coding-tab__btn primary" data-role="new-chat">${X}<span>New chat</span></button>
63
63
  </div>
64
- `}function L(t){let n=t.turns??[],e=t.loaded?n.length===0?'<div class="coding-tab__notice info">Type a message below to kick off this chat.</div>':n.map(s=>T(s)).join(""):'<div class="coding-tab__notice info">Loading\u2026</div>',r=n.length===0?'Ask anything (e.g. "add dark mode that follows the OS setting")':a.mode==="plan"?"Refine the plan, or ask another planning question":"Send another instruction";return`
65
- <div class="coding-tab__thread" data-role="thread">${e}</div>
64
+ `}function A(t){let e=t.turns??[],n=t.loaded?e.length===0?'<div class="coding-tab__notice info">Type a message below to kick off this chat.</div>':e.map(d=>E(d)).join(""):'<div class="coding-tab__notice info">Loading\u2026</div>',s=e.length===0?'Ask anything (e.g. "add dark mode that follows the OS setting")':a.mode==="plan"?"Refine the plan, or ask another planning question":"Send another instruction",o=t.isReconnecting?"Reconnecting\u2026":t.isStreaming?"Working\u2026":"Send";return`
65
+ <div class="coding-tab__thread" data-role="thread">${n}</div>
66
66
  <div class="coding-tab__composer">
67
- <textarea data-role="prompt" placeholder="${$(r)}" rows="2"></textarea>
68
- <button class="coding-tab__btn primary" data-role="send" ${t.isStreaming?"disabled":""}>${t.isStreaming?"Working\u2026":"Send"}</button>
67
+ <textarea data-role="prompt" placeholder="${y(s)}" rows="2"></textarea>
68
+ <button class="coding-tab__btn primary" data-role="send" ${t.isStreaming?"disabled":""}>${g(o)}</button>
69
69
  ${t.isStreaming?'<button class="coding-tab__btn danger" data-role="cancel">Stop</button>':""}
70
70
  </div>
71
- `}function T(t){if(t.role==="user"){let h=t.prompt??et(t);return`<div class="coding-tab__msg user">
71
+ `}function E(t){if(t.role==="user"){let x=t.prompt??st(t);return`<div class="coding-tab__msg user">
72
72
  <div class="coding-tab__msg-role">You \xB7 ${t.isPlan?"Plan":"Agent"}</div>
73
- <div class="coding-tab__msg-body">${u(h)}</div>
74
- </div>`}let n=t.events.length===0&&t.status==="running"?'<div class="coding-tab__msg-pending">Working\u2026</div>':t.events.map(h=>E(h,t.isPlan)).join(""),e=t.status&&t.status!=="finished"?`<div class="coding-tab__status">${u(t.status)}</div>`:"",r=t.showExecute&&!f()?.isStreaming?`<div class="coding-tab__plan-actions">
73
+ <div class="coding-tab__msg-body">${g(x)}</div>
74
+ </div>`}let s=f()?.turns?.at(-1)?.id===t.id&&f()?.isReconnecting===!0?`<div class="coding-tab__msg-pending coding-tab__msg-reconnecting">Reconnecting\u2026 (the page lost the live stream \u2014 the agent is still working on Cursor's side, fetching latest progress)</div>`:"",o=t.events.length===0&&t.status==="running"?'<div class="coding-tab__msg-pending">Working\u2026</div>':t.events.map(x=>I(x,t.isPlan)).join(""),d=t.status&&t.status!=="finished"?`<div class="coding-tab__status">${g(t.status)}</div>`:"",S=t.showExecute&&!f()?.isStreaming?`<div class="coding-tab__plan-actions">
75
75
  <button class="coding-tab__btn primary" data-role="execute" data-turn="${t.id}">Execute plan</button>
76
- </div>`:"",s=t.pr?tt(t.pr):t.branch?Z(t.branch):"";return`<div class="coding-tab__msg assistant">
77
- <div class="coding-tab__msg-role">Coding Tab \xB7 ${t.isPlan?"Plan":"Agent"} ${e}</div>
78
- <div class="coding-tab__msg-body">${n}${r}${s}</div>
79
- </div>`}function E(t,n){if(t.kind==="text"){let e=t.text.trim();return e?`<div class="coding-tab__md">${n?z(e):w(e)}</div>`:""}return P(t)}function w(t){return u(t).split(/\n\s*\n/).map(n=>n.replace(/`([^`\n]+)`/g,"<code>$1</code>").replace(/\*\*([^*\n]+)\*\*/g,"<strong>$1</strong>").replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g,(e,r,s)=>`<a href="${/^(https?:|mailto:|\/|#)/i.test(s)?s:"#"}" target="_blank" rel="noreferrer noopener">${r}</a>`).replace(/(^|[\s(])(https?:\/\/[^\s<>"')\]]+)/g,(e,r,s)=>`${r}<a href="${s}" target="_blank" rel="noreferrer noopener">${s}</a>`).replace(/\n/g,"<br />")).map(n=>`<p>${n}</p>`).join("")}function P(t){let n=Q(t.name,t.args),e=a.expandedTools.has(t.id),r=e?X(t):"";return`
76
+ </div>`:"",T=t.pr?at(t.pr):t.branch?nt(t.branch):"";return`<div class="coding-tab__msg assistant">
77
+ <div class="coding-tab__msg-role">Coding Tab \xB7 ${t.isPlan?"Plan":"Agent"} ${d}</div>
78
+ <div class="coding-tab__msg-body">${o}${s}${S}${T}</div>
79
+ </div>`}function I(t,e){if(t.kind==="text"){let n=t.text.trim();return n?`<div class="coding-tab__md">${e?Y(n):w(n)}</div>`:""}return H(t)}function w(t){return g(t).split(/\n\s*\n/).map(e=>e.replace(/`([^`\n]+)`/g,"<code>$1</code>").replace(/\*\*([^*\n]+)\*\*/g,"<strong>$1</strong>").replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g,(n,s,o)=>`<a href="${/^(https?:|mailto:|\/|#)/i.test(o)?o:"#"}" target="_blank" rel="noreferrer noopener">${s}</a>`).replace(/(^|[\s(])(https?:\/\/[^\s<>"')\]]+)/g,(n,s,o)=>`${s}<a href="${o}" target="_blank" rel="noreferrer noopener">${o}</a>`).replace(/\n/g,"<br />")).map(e=>`<p>${e}</p>`).join("")}function H(t){let e=tt(t.name,t.args),n=a.expandedTools.has(t.id),s=n?et(t):"";return`
80
80
  <div class="coding-tab__tool" data-status="${t.status}" data-tool-id="${t.id}">
81
- <button class="coding-tab__tool-header" data-role="toggle-tool" data-tool-id="${t.id}" aria-expanded="${e}">
81
+ <button class="coding-tab__tool-header" data-role="toggle-tool" data-tool-id="${t.id}" aria-expanded="${n}">
82
82
  <span class="coding-tab__tool-dot"></span>
83
- ${yt}
84
- <span class="coding-tab__tool-name">${u(t.name)}</span>
85
- ${n?`<span class="coding-tab__tool-summary">${u(n)}</span>`:""}
83
+ ${Et}
84
+ <span class="coding-tab__tool-name">${g(t.name)}</span>
85
+ ${e?`<span class="coding-tab__tool-summary">${g(e)}</span>`:""}
86
86
  </button>
87
- ${r}
87
+ ${s}
88
88
  </div>
89
- `}function Q(t,n){if(!n||typeof n!="object")return"";let e=n,r=t.toLowerCase();if(r.includes("grep")){let s=typeof e.pattern=="string"?e.pattern:typeof e.query=="string"?e.query:"";return s?`"${s}"`:""}if(r.includes("read")&&typeof e.path=="string"){let s=typeof e.offset=="number"||typeof e.limit=="number"?`:${e.offset??""}-${Number(e.offset??0)+Number(e.limit??0)||""}`:"";return`${e.path}${s}`}return(r.includes("glob")||r.includes("file_search"))&&typeof e.glob_pattern=="string"?e.glob_pattern:r==="shell"&&typeof e.command=="string"?e.command.slice(0,80):r==="task"&&typeof e.description=="string"?e.description:r.includes("write")&&typeof e.path=="string"||r.includes("edit")&&typeof e.path=="string"||r.includes("delete")&&typeof e.path=="string"?e.path:""}function X(t){let n=t.args!==void 0&&t.args!==null?`<div class="coding-tab__tool-section"><div class="coding-tab__tool-label">Args</div><pre>${u(q(t.args))}</pre></div>`:"",e=t.result!==void 0&&t.result!==null?`<div class="coding-tab__tool-section"><div class="coding-tab__tool-label">Result</div><pre>${u(q(t.result))}</pre></div>`:t.status==="running"?'<div class="coding-tab__tool-section"><div class="coding-tab__tool-label">Result</div><pre class="coding-tab__tool-pending">\u2026running</pre></div>':"";return`<div class="coding-tab__tool-detail">${n}${e}</div>`}function q(t){if(typeof t=="string")return t;try{return JSON.stringify(t,null,2)}catch{return String(t)}}function Z(t){let n=O(t.repoUrl);return`<div class="coding-tab__pr coding-tab__pr--branch">
90
- <div class="coding-tab__pr-title">Branch pushed: <code>${u(t.branch)}</code></div>
89
+ `}function tt(t,e){if(!e||typeof e!="object")return"";let n=e,s=t.toLowerCase();if(s.includes("grep")){let o=typeof n.pattern=="string"?n.pattern:typeof n.query=="string"?n.query:"";return o?`"${o}"`:""}if(s.includes("read")&&typeof n.path=="string"){let o=typeof n.offset=="number"||typeof n.limit=="number"?`:${n.offset??""}-${Number(n.offset??0)+Number(n.limit??0)||""}`:"";return`${n.path}${o}`}return(s.includes("glob")||s.includes("file_search"))&&typeof n.glob_pattern=="string"?n.glob_pattern:s==="shell"&&typeof n.command=="string"?n.command.slice(0,80):s==="task"&&typeof n.description=="string"?n.description:s.includes("write")&&typeof n.path=="string"||s.includes("edit")&&typeof n.path=="string"||s.includes("delete")&&typeof n.path=="string"?n.path:""}function et(t){let e=t.args!==void 0&&t.args!==null?`<div class="coding-tab__tool-section"><div class="coding-tab__tool-label">Args</div><pre>${g(j(t.args))}</pre></div>`:"",n=t.result!==void 0&&t.result!==null?`<div class="coding-tab__tool-section"><div class="coding-tab__tool-label">Result</div><pre>${g(j(t.result))}</pre></div>`:t.status==="running"?'<div class="coding-tab__tool-section"><div class="coding-tab__tool-label">Result</div><pre class="coding-tab__tool-pending">\u2026running</pre></div>':"";return`<div class="coding-tab__tool-detail">${e}${n}</div>`}function j(t){if(typeof t=="string")return t;try{return JSON.stringify(t,null,2)}catch{return String(t)}}function nt(t){let e=U(t.repoUrl);return`<div class="coding-tab__pr coding-tab__pr--branch">
90
+ <div class="coding-tab__pr-title">Branch pushed: <code>${g(t.branch)}</code></div>
91
91
  <div class="coding-tab__pr-status">No PR was opened automatically. Click below to create one yourself on GitHub.</div>
92
92
  <div class="coding-tab__pr-actions">
93
- <a class="coding-tab__btn primary" href="${$(t.compareUrl)}" target="_blank" rel="noreferrer">Create PR on GitHub</a>
94
- <a class="coding-tab__btn" href="${$(t.repoUrl)}/pulls" target="_blank" rel="noreferrer">View ${u(n)} PRs</a>
93
+ <a class="coding-tab__btn primary" href="${y(t.compareUrl)}" target="_blank" rel="noreferrer">Create PR on GitHub</a>
94
+ <a class="coding-tab__btn" href="${y(t.repoUrl)}/pulls" target="_blank" rel="noreferrer">View ${g(e)} PRs</a>
95
95
  </div>
96
- </div>`}function tt(t){let n=a.mergeStates.get(t.url)??{state:"idle"},e,r="";switch(n.state){case"loading":e='<button class="coding-tab__btn primary" data-state="loading" disabled>Merging\u2026</button>',r='<div class="coding-tab__pr-status">Merging this PR and triggering Railway redeploy\u2026</div>';break;case"success":e='<button class="coding-tab__btn success" disabled>Merged \u2713</button>',r=`<div class="coding-tab__pr-status is-success">Merged commit ${u(n.sha.slice(0,7))}. Railway should redeploy on the next push hook.</div>`;break;case"error":e=`<button class="coding-tab__btn primary" data-role="merge" data-pr="${$(t.url)}">Try merge again</button>`,r=`<div class="coding-tab__pr-status is-error">Merge failed: ${u(n.message)}</div>`;break;default:e=`<button class="coding-tab__btn primary" data-role="merge" data-pr="${$(t.url)}">Merge & Redeploy</button>`}return`<div class="coding-tab__pr">
97
- <div class="coding-tab__pr-title">PR #${t.number} opened in ${u(t.owner)}/${u(t.repo)}</div>
98
- ${t.title?`<div>${u(t.title)}</div>`:""}
96
+ </div>`}function at(t){let e=a.mergeStates.get(t.url)??{state:"idle"},n,s="";switch(e.state){case"loading":n='<button class="coding-tab__btn primary" data-state="loading" disabled>Merging\u2026</button>',s='<div class="coding-tab__pr-status">Merging this PR and triggering Railway redeploy\u2026</div>';break;case"success":n='<button class="coding-tab__btn success" disabled>Merged \u2713</button>',s=`<div class="coding-tab__pr-status is-success">Merged commit ${g(e.sha.slice(0,7))}. Railway should redeploy on the next push hook.</div>`;break;case"error":n=`<button class="coding-tab__btn primary" data-role="merge" data-pr="${y(t.url)}">Try merge again</button>`,s=`<div class="coding-tab__pr-status is-error">Merge failed: ${g(e.message)}</div>`;break;default:n=`<button class="coding-tab__btn primary" data-role="merge" data-pr="${y(t.url)}">Merge & Redeploy</button>`}return`<div class="coding-tab__pr">
97
+ <div class="coding-tab__pr-title">PR #${t.number} opened in ${g(t.owner)}/${g(t.repo)}</div>
98
+ ${t.title?`<div>${g(t.title)}</div>`:""}
99
99
  <div class="coding-tab__pr-actions">
100
- <a class="coding-tab__btn" href="${$(t.url)}" target="_blank" rel="noreferrer">Open in GitHub</a>
101
- ${e}
100
+ <a class="coding-tab__btn" href="${y(t.url)}" target="_blank" rel="noreferrer">Open in GitHub</a>
101
+ ${n}
102
102
  </div>
103
- ${r}
104
- </div>`}function et(t){return t.events.filter(n=>n.kind==="text").map(n=>n.text).join(`
103
+ ${s}
104
+ </div>`}function st(t){return t.events.filter(e=>e.kind==="text").map(e=>e.text).join(`
105
105
 
106
- `)}function nt(){i.querySelector('[data-role="model"]')?.addEventListener("change",t=>{a.model=t.target.value}),i.querySelectorAll("[data-mode]").forEach(t=>{t.addEventListener("click",()=>{a.mode=t.dataset.mode,c()})}),i.querySelector('[data-role="repo"]')?.addEventListener("change",t=>{a.repoUrl=t.target.value.trim()}),i.querySelector('[data-role="logout"]')?.addEventListener("click",async()=>{await fetch(`${l}/auth/logout`,{method:"POST",credentials:"include"}).catch(()=>{});for(let t of a.chats)t.abort?.abort();a.me=null,a.chats=[],a.activeChatId=null,c()}),i.querySelector('[data-role="toggle-sidebar"]')?.addEventListener("click",()=>{a.sidebarOpen=!a.sidebarOpen,c()}),i.querySelectorAll('[data-role="new-chat"]').forEach(t=>t.addEventListener("click",async()=>{await b()&&window.innerWidth<720&&(a.sidebarOpen=!1),c()})),i.querySelectorAll('[data-role="select-chat"]').forEach(t=>t.addEventListener("click",()=>{let n=t.dataset.chatId;M(n)})),i.querySelectorAll('[data-role="rename-chat"]').forEach(t=>t.addEventListener("click",n=>{n.stopPropagation(),y(t.dataset.chatId)})),i.querySelectorAll('[data-role="delete-chat"]').forEach(t=>t.addEventListener("click",n=>{n.stopPropagation(),C(t.dataset.chatId)})),i.querySelector('[data-role="send"]')?.addEventListener("click",()=>N()),i.querySelector('[data-role="prompt"]')?.addEventListener("keydown",t=>{let n=t;n.key==="Enter"&&(n.metaKey||n.ctrlKey)&&(n.preventDefault(),N())}),i.querySelector('[data-role="cancel"]')?.addEventListener("click",()=>st()),i.querySelectorAll('[data-role="execute"]').forEach(t=>t.addEventListener("click",()=>at())),i.querySelectorAll('[data-role="merge"]').forEach(t=>{t.addEventListener("click",()=>rt(t.dataset.pr))}),i.querySelectorAll('[data-role="toggle-tool"]').forEach(t=>t.addEventListener("click",()=>{let n=t.dataset.toolId;a.expandedTools.has(n)?a.expandedTools.delete(n):a.expandedTools.add(n),c()}))}async function N(){let t=f();if(!t&&(t=await b(),!t)||t.isStreaming)return;let n=i.querySelector('[data-role="prompt"]'),e=n?.value.trim()??"";if(!e)return;n&&(n.value=""),t.turns=t.turns??[];let r={id:v(),chatId:t.meta.id,seq:t.turns.length,role:"user",isPlan:a.mode==="plan",status:"finished",events:[{kind:"text",id:v(),text:e}],prompt:e,createdAt:Date.now()};t.turns.push(r);let s={id:v(),chatId:t.meta.id,seq:t.turns.length,role:"assistant",isPlan:a.mode==="plan",status:"running",events:[],createdAt:Date.now()};t.turns.push(s),t.isStreaming=!0,t.meta.title==="New chat"&&(t.meta={...t.meta,title:e.length>60?`${e.slice(0,57)}\u2026`:e}),t.meta={...t.meta,mode:a.mode,model:a.model,updatedAt:Date.now()},c();try{await U("/agent/send",{chatId:t.meta.id,prompt:e,mode:a.mode},t,s)}catch(h){h.name!=="AbortError"&&j(s,h),s.status="error"}finally{t.isStreaming=!1,t.activeRunId=null,t.abort=null,c()}}async function at(){let t=f();if(!t||t.isStreaming)return;t.turns=t.turns??[];let n={id:v(),chatId:t.meta.id,seq:t.turns.length,role:"assistant",isPlan:!1,status:"running",events:[],createdAt:Date.now()};t.turns.push(n),t.isStreaming=!0,c();try{await U("/agent/execute",{chatId:t.meta.id},t,n)}catch(e){e.name!=="AbortError"&&j(n,e),n.status="error"}finally{t.isStreaming=!1,t.activeRunId=null,t.abort=null,c()}}async function st(){let t=f();if(t){if(t.activeRunId)try{await m("/agent/cancel",{method:"POST",body:JSON.stringify({chatId:t.meta.id,runId:t.activeRunId})})}catch(n){console.error("[coding-tab] cancel failed",n)}t.abort?.abort()}}async function rt(t){let n=f();if(n&&a.mergeStates.get(t)?.state!=="loading"){a.mergeStates.set(t,{state:"loading"}),c(),n.turns=n.turns??[];try{let e=await m("/pr/merge",{method:"POST",body:JSON.stringify({prUrl:t,mergeMethod:"squash"})});e.merged?(a.mergeStates.set(t,{state:"success",sha:e.sha}),n.turns.push({id:v(),chatId:n.meta.id,seq:n.turns.length,role:"assistant",isPlan:!1,status:"finished",events:[{kind:"text",id:v(),text:`Merged commit \`${e.sha.slice(0,7)}\` into the default branch. Railway should redeploy on the next push hook.`}],createdAt:Date.now()})):a.mergeStates.set(t,{state:"error",message:"GitHub returned merged=false"})}catch(e){let r=e instanceof Error?e.message:String(e);a.mergeStates.set(t,{state:"error",message:r})}finally{c()}}}function j(t,n){let e=n instanceof Error?n.message:String(n);t.events.push({kind:"text",id:v(),text:`[error] ${e}`})}async function U(t,n,e,r){e.abort=new AbortController;let s=await fetch(`${l}${t}`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(n),signal:e.abort.signal});if(!s.ok||!s.body)throw new Error(`${s.status} ${s.statusText}`);let h=s.body.getReader(),dt=new TextDecoder,R="",B=!1;for(;;){let{value:lt,done:ct}=await h.read();if(ct)break;R+=dt.decode(lt,{stream:!0});let D=R.split(`
106
+ `)}function ot(){r.querySelector('[data-role="model"]')?.addEventListener("change",t=>{a.model=t.target.value}),r.querySelectorAll("[data-mode]").forEach(t=>{t.addEventListener("click",()=>{a.mode=t.dataset.mode,c()})}),r.querySelector('[data-role="repo"]')?.addEventListener("change",t=>{a.repoUrl=t.target.value.trim()}),r.querySelector('[data-role="logout"]')?.addEventListener("click",async()=>{await fetch(`${u}/auth/logout`,{method:"POST",credentials:"include"}).catch(()=>{});for(let t of a.chats)t.abort?.abort();a.me=null,a.chats=[],a.activeChatId=null,c()}),r.querySelector('[data-role="toggle-sidebar"]')?.addEventListener("click",()=>{a.sidebarOpen=!a.sidebarOpen,c()}),r.querySelectorAll('[data-role="new-chat"]').forEach(t=>t.addEventListener("click",async()=>{await b()&&window.innerWidth<720&&(a.sidebarOpen=!1),c()})),r.querySelectorAll('[data-role="select-chat"]').forEach(t=>t.addEventListener("click",()=>{let e=t.dataset.chatId;P(e)})),r.querySelectorAll('[data-role="rename-chat"]').forEach(t=>t.addEventListener("click",e=>{e.stopPropagation(),$(t.dataset.chatId)})),r.querySelectorAll('[data-role="delete-chat"]').forEach(t=>t.addEventListener("click",e=>{e.stopPropagation(),M(t.dataset.chatId)})),r.querySelector('[data-role="send"]')?.addEventListener("click",()=>B()),r.querySelector('[data-role="prompt"]')?.addEventListener("keydown",t=>{let e=t;e.key==="Enter"&&(e.metaKey||e.ctrlKey)&&(e.preventDefault(),B())}),r.querySelector('[data-role="cancel"]')?.addEventListener("click",()=>rt()),r.querySelectorAll('[data-role="execute"]').forEach(t=>t.addEventListener("click",()=>it())),r.querySelectorAll('[data-role="merge"]').forEach(t=>{t.addEventListener("click",()=>lt(t.dataset.pr))}),r.querySelectorAll('[data-role="toggle-tool"]').forEach(t=>t.addEventListener("click",()=>{let e=t.dataset.toolId;a.expandedTools.has(e)?a.expandedTools.delete(e):a.expandedTools.add(e),c()}))}async function B(){let t=f();if(!t&&(t=await b(),!t)||t.isStreaming)return;let e=r.querySelector('[data-role="prompt"]'),n=e?.value.trim()??"";if(!n)return;e&&(e.value=""),t.turns=t.turns??[];let s={id:_(),chatId:t.meta.id,seq:t.turns.length,role:"user",isPlan:a.mode==="plan",status:"finished",events:[{kind:"text",id:_(),text:n}],prompt:n,createdAt:Date.now()};t.turns.push(s);let o={id:_(),chatId:t.meta.id,seq:t.turns.length,role:"assistant",isPlan:a.mode==="plan",status:"running",events:[],createdAt:Date.now()};t.turns.push(o),t.isStreaming=!0,t.meta.title==="New chat"&&(t.meta={...t.meta,title:n.length>60?`${n.slice(0,57)}\u2026`:n}),t.meta={...t.meta,mode:a.mode,model:a.model,updatedAt:Date.now()},c();try{await G("/agent/send",{chatId:t.meta.id,prompt:n,mode:a.mode},t,o)}finally{t.isReconnecting||(t.isStreaming=!1,t.activeRunId=null,t.abort=null),c()}}async function it(){let t=f();if(!t||t.isStreaming)return;t.turns=t.turns??[];let e={id:_(),chatId:t.meta.id,seq:t.turns.length,role:"assistant",isPlan:!1,status:"running",events:[],createdAt:Date.now()};t.turns.push(e),t.isStreaming=!0,c();try{await G("/agent/execute",{chatId:t.meta.id},t,e)}finally{t.isReconnecting||(t.isStreaming=!1,t.activeRunId=null,t.abort=null),c()}}async function rt(){let t=f();if(t){if(t.activeRunId)try{await m("/agent/cancel",{method:"POST",body:JSON.stringify({chatId:t.meta.id,runId:t.activeRunId})})}catch(e){console.error("[coding-tab] cancel failed",e)}t.abort?.abort()}}async function lt(t){let e=f();if(e&&a.mergeStates.get(t)?.state!=="loading"){a.mergeStates.set(t,{state:"loading"}),c(),e.turns=e.turns??[];try{let n=await m("/pr/merge",{method:"POST",body:JSON.stringify({prUrl:t,mergeMethod:"squash"})});n.merged?(a.mergeStates.set(t,{state:"success",sha:n.sha}),e.turns.push({id:_(),chatId:e.meta.id,seq:e.turns.length,role:"assistant",isPlan:!1,status:"finished",events:[{kind:"text",id:_(),text:`Merged commit \`${n.sha.slice(0,7)}\` into the default branch. Railway should redeploy on the next push hook.`}],createdAt:Date.now()})):a.mergeStates.set(t,{state:"error",message:"GitHub returned merged=false"})}catch(n){let s=n instanceof Error?n.message:String(n);a.mergeStates.set(t,{state:"error",message:s})}finally{c()}}}function dt(t,e){let n=e instanceof Error?e.message:String(e);t.events.push({kind:"text",id:_(),text:`[error] ${n}`})}function ct(t){if(!(t instanceof Error)||t.name==="AbortError")return!1;let e=t.message.toLowerCase();return e.includes("load failed")||e.includes("network")||e.includes("failed to fetch")||e.includes("the network connection was lost")||e.includes("err_network")||e.includes("connection")||e==="fetch failed"}async function D(t,e){let n=Date.now(),s=15*6e4,o=2e3;t.isReconnecting=!0,t.pollTurnId=e,c();try{for(;Date.now()-n<s;){if(!a.chats.find(d=>d.meta.id===t.meta.id))return;try{let{chat:d}=await m(`/chats/${t.meta.id}`);t.turns=d.turns,t.meta={id:d.id,title:d.title,mode:d.mode,model:d.model,createdAt:d.createdAt,updatedAt:d.updatedAt};let S=d.turns.find(T=>T.id===e);if(!S||S.status&&S.status!=="running")return;c()}catch(d){console.warn("[coding-tab] poll tick failed",d)}await ut(o)}}finally{t.isReconnecting=!1,t.pollTurnId=null,t.pollPromise=null,t.isStreaming=!1,t.activeRunId=null,t.abort=null,c()}}function ut(t){return new Promise(e=>setTimeout(e,t))}async function G(t,e,n,s){try{await gt(t,e,n,s)}catch(o){if(o.name==="AbortError")return;if(ct(o)){let d=D(n,s.id);n.pollPromise=d,await d;return}dt(s,o),s.status="error"}}async function gt(t,e,n,s){n.abort=new AbortController;let o=await fetch(`${u}${t}`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(e),signal:n.abort.signal});if(!o.ok||!o.body)throw new Error(`${o.status} ${o.statusText}`);let d=o.body.getReader(),S=new TextDecoder,T="",x=!1;for(;;){let{value:pt,done:ft}=await d.read();if(ft)break;T+=S.decode(pt,{stream:!0});let J=T.split(`
107
107
 
108
- `);R=D.pop()??"";for(let ut of D){let G=ut.split(`
109
- `).find(x=>x.startsWith("data: "));if(G)try{let x=JSON.parse(G.slice(6));B=ot(x,e,r,B),c()}catch(x){console.warn("[coding-tab] bad sse event",x)}}}}function ot(t,n,e,r){switch(t.kind){case"ready":return n.activeRunId=t.runId,!1;case"text":{let s=e.events[e.events.length-1];return r&&s&&s.kind==="text"?s.text+=t.text:e.events.push({kind:"text",id:v(),text:t.text}),!0}case"thinking":return r;case"tool":{let s=e.events.find(h=>h.kind==="tool"&&h.callId===t.callId);return s?(s.status=t.status,t.args!==void 0&&(s.args=t.args),t.result!==void 0&&(s.result=t.result)):e.events.push({kind:"tool",id:v(),callId:t.callId,name:t.name,status:t.status,args:t.args,result:t.result}),!1}case"status":return r;case"result":return e.status=t.status,t.pr&&(e.pr=t.pr),t.branch&&!t.pr&&(e.branch=t.branch),e.isPlan&&t.status==="finished"&&(e.showExecute=!0),n.activeRunId=null,!1;case"error":return e.events.push({kind:"text",id:v(),text:`[error] ${t.message}`}),e.status="error",n.activeRunId=null,!1}}async function it(){try{let t=await m("/auth/me");a.me=t,t.defaultRepoUrl&&(a.repoUrl=t.defaultRepoUrl,a.repoLocked=!0)}catch(t){let n=t.status;if(n===401||n===403){a.me=null,c();return}console.error("[coding-tab] /auth/me failed",t),a.me=null,c();return}try{let{models:t}=await m("/models");a.models=t,t.length>0&&!t.find(n=>n.choice===a.model)&&(a.model=t[0].choice)}catch(t){console.error("[coding-tab] /models failed",t),a.models=[{choice:"sonnet",cursorModelId:"auto",displayName:"Sonnet (fallback)"},{choice:"opus",cursorModelId:"auto",displayName:"Opus (fallback)"}]}await k(),a.chats.length>0&&!a.activeChatId&&(a.activeChatId=a.chats[0].meta.id,S(a.activeChatId).then(()=>c())),c()}return it(),{destroy(){for(let t of a.chats)t.abort?.abort();d.removeChild(i)}}}typeof window<"u"&&(window.CodingTab={mountCodingTab:Y});return bt(Tt);})();
108
+ `);T=J.pop()??"";for(let bt of J){let W=bt.split(`
109
+ `).find(L=>L.startsWith("data: "));if(W)try{let L=JSON.parse(W.slice(6));x=ht(L,n,s,x),c()}catch(L){console.warn("[coding-tab] bad sse event",L)}}}}function ht(t,e,n,s){switch(t.kind){case"ready":return e.activeRunId=t.runId,!1;case"text":{let o=n.events[n.events.length-1];return s&&o&&o.kind==="text"?o.text+=t.text:n.events.push({kind:"text",id:_(),text:t.text}),!0}case"thinking":return s;case"tool":{let o=n.events.find(d=>d.kind==="tool"&&d.callId===t.callId);return o?(o.status=t.status,t.args!==void 0&&(o.args=t.args),t.result!==void 0&&(o.result=t.result)):n.events.push({kind:"tool",id:_(),callId:t.callId,name:t.name,status:t.status,args:t.args,result:t.result}),!1}case"status":return s;case"result":return n.status=t.status,t.pr&&(n.pr=t.pr),t.branch&&!t.pr&&(n.branch=t.branch),n.isPlan&&t.status==="finished"&&(n.showExecute=!0),e.activeRunId=null,!1;case"error":return n.events.push({kind:"text",id:_(),text:`[error] ${t.message}`}),n.status="error",e.activeRunId=null,!1}}async function mt(){try{let t=await m("/auth/me");a.me=t,t.defaultRepoUrl&&(a.repoUrl=t.defaultRepoUrl,a.repoLocked=!0)}catch(t){let e=t.status;if(e===401||e===403){a.me=null,c();return}console.error("[coding-tab] /auth/me failed",t),a.me=null,c();return}try{let{models:t}=await m("/models");a.models=t,t.length>0&&!t.find(e=>e.choice===a.model)&&(a.model=t[0].choice)}catch(t){console.error("[coding-tab] /models failed",t),a.models=[{choice:"sonnet",cursorModelId:"auto",displayName:"Sonnet (fallback)"},{choice:"opus",cursorModelId:"auto",displayName:"Opus (fallback)"}]}await k(),a.chats.length>0&&!a.activeChatId?(a.activeChatId=a.chats[0].meta.id,C(a.activeChatId).then(()=>{O(),c()})):O(),c()}function O(){for(let t of a.chats){if(t.pollPromise)continue;let e=t.turns?.at(-1);if(!e||e.role!=="assistant"||e.status&&e.status!=="running")continue;t.isStreaming=!0;let n=D(t,e.id);t.pollPromise=n,n.catch(s=>console.warn("[coding-tab] resume poll failed",s))}}let F=()=>{if(document.visibilityState==="visible"){if(a.activeChatId){let t=a.chats.find(e=>e.meta.id===a.activeChatId);if(t){m(`/chats/${t.meta.id}`).then(({chat:e})=>{t.turns=e.turns,t.meta={id:e.id,title:e.title,mode:e.mode,model:e.model,createdAt:e.createdAt,updatedAt:e.updatedAt},O(),c()}).catch(e=>console.warn("[coding-tab] visibility refresh failed",e));return}}O()}};return document.addEventListener("visibilitychange",F),mt(),{destroy(){document.removeEventListener("visibilitychange",F);for(let t of a.chats)t.abort?.abort();l.removeChild(r)}}}typeof window<"u"&&(window.CodingTab={mountCodingTab:Z});return kt(Lt);})();
110
110
  //# sourceMappingURL=browser.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client/tab.ts","../src/client/markdown.ts"],"sourcesContent":["import { escapeHtml, renderMarkdown } from \"./markdown.js\";\nimport type {\n BranchInfo,\n ChatListItem,\n ChatMode,\n FullChat,\n MeResponse,\n ModelChoice,\n ModelOption,\n PrInfo,\n StoredChat,\n StoredTurn,\n StreamEvent,\n TimelineEvent,\n TurnStatus,\n} from \"../shared/types.js\";\n\ninterface MountOptions {\n apiBase: string;\n defaultRepo?: string;\n defaultRef?: string;\n defaultMode?: ChatMode;\n defaultModel?: ModelChoice;\n}\n\ninterface MountHandle {\n destroy(): void;\n}\n\n/**\n * Visible state of a \"Merge & Redeploy\" button. Stored per PR-url so the\n * feedback survives re-renders (which happen frequently during streaming) and\n * so multiple PRs in the same chat can have independent states.\n */\ntype MergeState =\n | { state: \"idle\" }\n | { state: \"loading\" }\n | { state: \"success\"; sha: string }\n | { state: \"error\"; message: string };\n\n/** Client-side ephemeral wrapper around a `StoredChat`. */\ninterface ChatThread {\n meta: ChatListItem;\n /** Loaded turns (after `/chats/:id` fetch). Undefined means not yet hydrated. */\n turns?: StoredTurn[];\n loaded: boolean;\n isStreaming: boolean;\n activeRunId: string | null;\n abort: AbortController | null;\n}\n\nconst ICON_GITHUB = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M8 0C3.58 0 0 3.58 0 8a8 8 0 005.47 7.59c.4.07.55-.17.55-.38v-1.34c-2.23.48-2.7-1.07-2.7-1.07-.36-.92-.89-1.16-.89-1.16-.73-.5.05-.49.05-.49.81.06 1.23.83 1.23.83.72 1.23 1.88.87 2.34.66.07-.52.28-.87.5-1.07-1.78-.2-3.65-.89-3.65-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.13 0 0 .67-.21 2.2.82a7.65 7.65 0 014 0c1.53-1.04 2.2-.82 2.2-.82.44 1.11.16 1.93.08 2.13.51.56.82 1.28.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.74.54 1.49v2.21c0 .21.15.46.55.38A8 8 0 0016 8c0-4.42-3.58-8-8-8z\"/></svg>`;\n\nconst ICON_PLUS = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\"><path d=\"M8 3v10M3 8h10\"/></svg>`;\nconst ICON_TRASH = `<svg width=\"13\" height=\"13\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"><path d=\"M3 4h10M6 4V2.5a.5.5 0 01.5-.5h3a.5.5 0 01.5.5V4M5 4l.7 9.1a.9.9 0 00.9.9h2.8a.9.9 0 00.9-.9L11 4\"/></svg>`;\nconst ICON_PENCIL = `<svg width=\"13\" height=\"13\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M11 2.5l2.5 2.5L6 12.5 3 13l.5-3z\"/></svg>`;\nconst ICON_MENU = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"><path d=\"M2.5 4h11M2.5 8h11M2.5 12h11\"/></svg>`;\nconst ICON_CHEVRON = `<svg class=\"ct-chevron\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 2l4 3-4 3\"/></svg>`;\n\nfunction escapeAttr(s: string): string {\n return escapeHtml(s);\n}\n\nfunction repoSlug(url: string): string {\n const m = url.match(/github\\.com[/:]([^/]+)\\/([^/]+?)(?:\\.git)?\\/?$/);\n return m ? `${m[1]}/${m[2]}` : url;\n}\n\nfunction repoWebUrl(url: string): string {\n // Strip a trailing `.git` and SSH-style `git@github.com:owner/repo.git` so\n // the URL is safe to drop into an `<a href>`.\n const m = url.match(/github\\.com[/:]([^/]+)\\/([^/]+?)(?:\\.git)?\\/?$/);\n return m ? `https://github.com/${m[1]}/${m[2]}` : url.replace(/\\.git\\/?$/, \"\");\n}\n\nfunction githubPullsUrl(url: string): string {\n return `${repoWebUrl(url)}/pulls`;\n}\n\nfunction cryptoRandomId(): string {\n return typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : Math.random().toString(36).slice(2);\n}\n\nfunction relativeTime(ts: number): string {\n const diff = Date.now() - ts;\n if (diff < 60_000) return \"just now\";\n if (diff < 3_600_000) return `${Math.round(diff / 60_000)}m`;\n if (diff < 86_400_000) return `${Math.round(diff / 3_600_000)}h`;\n if (diff < 604_800_000) return `${Math.round(diff / 86_400_000)}d`;\n return new Date(ts).toLocaleDateString();\n}\n\nexport function mountCodingTab(el: HTMLElement, options: MountOptions): MountHandle {\n const apiBase = options.apiBase.replace(/\\/$/, \"\");\n const root = document.createElement(\"div\");\n root.className = \"coding-tab\";\n el.innerHTML = \"\";\n el.appendChild(root);\n\n let stylesheetInjected = false;\n function injectStylesheet() {\n if (stylesheetInjected) return;\n stylesheetInjected = true;\n if (document.querySelector(`link[data-coding-tab=\"style\"]`)) return;\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = `${apiBase}/style.css`;\n link.dataset.codingTab = \"style\";\n document.head.appendChild(link);\n }\n injectStylesheet();\n\n const state: {\n me: MeResponse | null;\n models: ModelOption[];\n mode: ChatMode;\n model: ModelChoice;\n repoUrl: string;\n repoLocked: boolean;\n chats: ChatThread[];\n activeChatId: string | null;\n sidebarOpen: boolean;\n /** Tracks expanded tool events across re-renders (keyed by `event.id`). */\n expandedTools: Set<string>;\n /** Per-PR merge state so the button shows loading/success/error feedback. */\n mergeStates: Map<string, MergeState>;\n } = {\n me: null,\n models: [],\n mode: options.defaultMode ?? \"plan\",\n model: options.defaultModel ?? \"sonnet\",\n repoUrl: options.defaultRepo ?? \"\",\n repoLocked: false,\n chats: [],\n activeChatId: null,\n sidebarOpen: window.innerWidth >= 720,\n expandedTools: new Set(),\n mergeStates: new Map(),\n };\n\n function activeChat(): ChatThread | null {\n return state.chats.find((c) => c.meta.id === state.activeChatId) ?? null;\n }\n\n async function api<T = unknown>(path: string, init?: RequestInit): Promise<T> {\n const resp = await fetch(`${apiBase}${path}`, {\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\", ...(init?.headers ?? {}) },\n ...init,\n });\n if (!resp.ok)\n throw Object.assign(new Error(`${resp.status} ${resp.statusText}`), { status: resp.status });\n return (await resp.json()) as T;\n }\n\n // ───────── chat lifecycle ─────────\n\n async function loadChatList(): Promise<void> {\n try {\n const { chats } = await api<{ chats: ChatListItem[] }>(\"/chats\");\n // Preserve runtime state for chats we already had.\n const existing = new Map(state.chats.map((c) => [c.meta.id, c]));\n state.chats = chats.map((meta) => {\n const prev = existing.get(meta.id);\n return prev\n ? { ...prev, meta }\n : {\n meta,\n loaded: false,\n isStreaming: false,\n activeRunId: null,\n abort: null,\n };\n });\n } catch (err) {\n console.error(\"[coding-tab] /chats list failed\", err);\n state.chats = [];\n }\n }\n\n async function ensureChatLoaded(chatId: string): Promise<ChatThread | null> {\n const t = state.chats.find((c) => c.meta.id === chatId);\n if (!t) return null;\n if (t.loaded) return t;\n try {\n const { chat } = await api<{ chat: FullChat }>(`/chats/${chatId}`);\n t.turns = chat.turns;\n t.loaded = true;\n t.meta = {\n id: chat.id,\n title: chat.title,\n mode: chat.mode,\n model: chat.model,\n createdAt: chat.createdAt,\n updatedAt: chat.updatedAt,\n };\n // Sync composer state to whatever this chat last used.\n state.mode = chat.mode;\n state.model = chat.model;\n return t;\n } catch (err) {\n console.error(`[coding-tab] /chats/${chatId} load failed`, err);\n return null;\n }\n }\n\n async function createChat(): Promise<ChatThread | null> {\n try {\n const { chat } = await api<{ chat: StoredChat }>(\"/chats\", {\n method: \"POST\",\n body: JSON.stringify({\n mode: state.mode,\n model: state.model,\n repoUrl: state.repoLocked ? undefined : state.repoUrl || undefined,\n }),\n });\n const thread: ChatThread = {\n meta: {\n id: chat.id,\n title: chat.title,\n mode: chat.mode,\n model: chat.model,\n createdAt: chat.createdAt,\n updatedAt: chat.updatedAt,\n },\n turns: [],\n loaded: true,\n isStreaming: false,\n activeRunId: null,\n abort: null,\n };\n state.chats.unshift(thread);\n state.activeChatId = thread.meta.id;\n return thread;\n } catch (err) {\n console.error(\"[coding-tab] create chat failed\", err);\n return null;\n }\n }\n\n async function switchChat(chatId: string): Promise<void> {\n state.activeChatId = chatId;\n if (window.innerWidth < 720) state.sidebarOpen = false;\n render();\n await ensureChatLoaded(chatId);\n render();\n }\n\n async function deleteChat(chatId: string): Promise<void> {\n const thread = state.chats.find((c) => c.meta.id === chatId);\n if (!thread) return;\n if (!confirm(`Delete chat \"${thread.meta.title}\"? This cannot be undone.`)) return;\n thread.abort?.abort();\n try {\n await api(`/chats/${chatId}`, { method: \"DELETE\" });\n } catch (err) {\n console.error(`[coding-tab] delete chat ${chatId} failed`, err);\n return;\n }\n state.chats = state.chats.filter((c) => c.meta.id !== chatId);\n if (state.activeChatId === chatId) {\n state.activeChatId = state.chats[0]?.meta.id ?? null;\n }\n render();\n }\n\n async function renameChat(chatId: string): Promise<void> {\n const thread = state.chats.find((c) => c.meta.id === chatId);\n if (!thread) return;\n const next = prompt(\"Rename chat\", thread.meta.title);\n if (next === null) return;\n const trimmed = next.trim();\n if (!trimmed || trimmed === thread.meta.title) return;\n try {\n const { chat } = await api<{ chat: StoredChat }>(`/chats/${chatId}`, {\n method: \"PATCH\",\n body: JSON.stringify({ title: trimmed }),\n });\n thread.meta = { ...thread.meta, title: chat.title, updatedAt: chat.updatedAt };\n render();\n } catch (err) {\n console.error(`[coding-tab] rename chat ${chatId} failed`, err);\n }\n }\n\n // ───────── render ─────────\n\n function render() {\n if (!state.me) {\n root.innerHTML = `\n <div class=\"coding-tab__signin\">\n <h2>Coding Tab</h2>\n <p>Sign in with GitHub to start a coding session against your repo. Your token is used to clone, push, and open pull requests on your behalf.</p>\n <a href=\"${apiBase}/auth/login\">${ICON_GITHUB}<span>Sign in with GitHub</span></a>\n </div>\n `;\n return;\n }\n\n const headerHtml = `\n <div class=\"coding-tab__header\">\n <button class=\"coding-tab__hamburger\" data-role=\"toggle-sidebar\" aria-label=\"Toggle chat list\">${ICON_MENU}</button>\n <select data-role=\"model\" title=\"Model\">\n ${state.models\n .map(\n (m) =>\n `<option value=\"${m.choice}\" ${state.model === m.choice ? \"selected\" : \"\"}>${escapeHtml(m.displayName)}</option>`,\n )\n .join(\"\")}\n </select>\n <div class=\"coding-tab__mode\">\n <button data-mode=\"plan\" class=\"${state.mode === \"plan\" ? \"is-active\" : \"\"}\">Plan</button>\n <button data-mode=\"agent\" class=\"${state.mode === \"agent\" ? \"is-active\" : \"\"}\">Agent</button>\n </div>\n ${\n state.repoLocked && state.repoUrl\n ? `<a class=\"coding-tab__repo-locked\" href=\"${escapeAttr(githubPullsUrl(state.repoUrl))}\" target=\"_blank\" rel=\"noreferrer\" title=\"Open ${escapeAttr(repoSlug(state.repoUrl))} pull requests on GitHub\">${ICON_GITHUB}<span>${escapeHtml(repoSlug(state.repoUrl))}</span></a>`\n : `<input data-role=\"repo\" placeholder=\"https://github.com/org/repo\" value=\"${escapeAttr(state.repoUrl)}\" style=\"flex:1;min-width:200px\" />`\n }\n <div class=\"coding-tab__user\">\n ${state.me.avatarUrl ? `<img src=\"${state.me.avatarUrl}\" alt=\"\" />` : \"\"}\n <span class=\"coding-tab__user-name\">@${escapeHtml(state.me.githubLogin)}</span>\n <button data-role=\"logout\">Sign out</button>\n </div>\n </div>\n `;\n\n const sidebarHtml = `\n <aside class=\"coding-tab__sidebar ${state.sidebarOpen ? \"is-open\" : \"\"}\">\n <button class=\"coding-tab__btn primary coding-tab__new-chat\" data-role=\"new-chat\">${ICON_PLUS}<span>New chat</span></button>\n <div class=\"coding-tab__chat-list\">\n ${\n state.chats.length === 0\n ? `<div class=\"coding-tab__chat-empty\">No chats yet — start one with the button above.</div>`\n : state.chats.map((c) => renderChatRow(c)).join(\"\")\n }\n </div>\n </aside>\n `;\n\n const chat = activeChat();\n const paneHtml = `\n <main class=\"coding-tab__pane\">\n ${chat ? renderChatPane(chat) : renderEmptyPane()}\n </main>\n `;\n\n root.innerHTML = `\n ${headerHtml}\n <div class=\"coding-tab__body\">\n ${sidebarHtml}\n ${paneHtml}\n </div>\n `;\n\n const thread = root.querySelector(\"[data-role=thread]\") as HTMLElement | null;\n if (thread) thread.scrollTop = thread.scrollHeight;\n\n bindEvents();\n }\n\n function renderChatRow(c: ChatThread): string {\n const active = c.meta.id === state.activeChatId ? \" is-active\" : \"\";\n const streaming = c.isStreaming ? `<span class=\"coding-tab__chat-row-dot\" title=\"Streaming\"></span>` : \"\";\n return `\n <div class=\"coding-tab__chat-row${active}\" data-chat-id=\"${c.meta.id}\">\n <button class=\"coding-tab__chat-row-main\" data-role=\"select-chat\" data-chat-id=\"${c.meta.id}\">\n ${streaming}\n <span class=\"coding-tab__chat-row-title\">${escapeHtml(c.meta.title)}</span>\n <span class=\"coding-tab__chat-row-meta\">${c.meta.mode === \"plan\" ? \"Plan\" : \"Agent\"} · ${relativeTime(c.meta.updatedAt)}</span>\n </button>\n <div class=\"coding-tab__chat-row-actions\">\n <button data-role=\"rename-chat\" data-chat-id=\"${c.meta.id}\" title=\"Rename\">${ICON_PENCIL}</button>\n <button data-role=\"delete-chat\" data-chat-id=\"${c.meta.id}\" title=\"Delete\">${ICON_TRASH}</button>\n </div>\n </div>\n `;\n }\n\n function renderEmptyPane(): string {\n return `\n <div class=\"coding-tab__pane-empty\">\n <h3>Start a new chat</h3>\n <p>Pick a mode (Plan or Agent), choose a model, and click <strong>New chat</strong> to begin. Each chat keeps its own context.</p>\n <button class=\"coding-tab__btn primary\" data-role=\"new-chat\">${ICON_PLUS}<span>New chat</span></button>\n </div>\n `;\n }\n\n function renderChatPane(chat: ChatThread): string {\n const turns = chat.turns ?? [];\n const threadHtml =\n !chat.loaded\n ? `<div class=\"coding-tab__notice info\">Loading…</div>`\n : turns.length === 0\n ? `<div class=\"coding-tab__notice info\">Type a message below to kick off this chat.</div>`\n : turns.map((t) => renderTurn(t)).join(\"\");\n\n const placeholder =\n turns.length === 0\n ? `Ask anything (e.g. \"add dark mode that follows the OS setting\")`\n : state.mode === \"plan\"\n ? \"Refine the plan, or ask another planning question\"\n : \"Send another instruction\";\n\n return `\n <div class=\"coding-tab__thread\" data-role=\"thread\">${threadHtml}</div>\n <div class=\"coding-tab__composer\">\n <textarea data-role=\"prompt\" placeholder=\"${escapeAttr(placeholder)}\" rows=\"2\"></textarea>\n <button class=\"coding-tab__btn primary\" data-role=\"send\" ${chat.isStreaming ? \"disabled\" : \"\"}>${chat.isStreaming ? \"Working…\" : \"Send\"}</button>\n ${chat.isStreaming ? `<button class=\"coding-tab__btn danger\" data-role=\"cancel\">Stop</button>` : \"\"}\n </div>\n `;\n }\n\n function renderTurn(turn: StoredTurn): string {\n if (turn.role === \"user\") {\n const text = turn.prompt ?? extractText(turn);\n return `<div class=\"coding-tab__msg user\">\n <div class=\"coding-tab__msg-role\">You · ${turn.isPlan ? \"Plan\" : \"Agent\"}</div>\n <div class=\"coding-tab__msg-body\">${escapeHtml(text)}</div>\n </div>`;\n }\n\n const eventsHtml =\n turn.events.length === 0 && turn.status === \"running\"\n ? `<div class=\"coding-tab__msg-pending\">Working…</div>`\n : turn.events.map((evt) => renderEvent(evt, turn.isPlan)).join(\"\");\n\n const statusBadge = turn.status && turn.status !== \"finished\"\n ? `<div class=\"coding-tab__status\">${escapeHtml(turn.status)}</div>`\n : \"\";\n\n const planActions =\n turn.showExecute && !activeChat()?.isStreaming\n ? `<div class=\"coding-tab__plan-actions\">\n <button class=\"coding-tab__btn primary\" data-role=\"execute\" data-turn=\"${turn.id}\">Execute plan</button>\n </div>`\n : \"\";\n\n const prHtml = turn.pr\n ? renderPr(turn.pr)\n : turn.branch\n ? renderBranchFallback(turn.branch)\n : \"\";\n\n return `<div class=\"coding-tab__msg assistant\">\n <div class=\"coding-tab__msg-role\">Coding Tab · ${turn.isPlan ? \"Plan\" : \"Agent\"} ${statusBadge}</div>\n <div class=\"coding-tab__msg-body\">${eventsHtml}${planActions}${prHtml}</div>\n </div>`;\n }\n\n function renderEvent(evt: TimelineEvent, isPlan: boolean): string {\n if (evt.kind === \"text\") {\n const text = evt.text.trim();\n if (!text) return \"\";\n const html = isPlan ? renderMarkdown(text) : renderInlineText(text);\n return `<div class=\"coding-tab__md\">${html}</div>`;\n }\n return renderToolEvent(evt);\n }\n\n function renderInlineText(text: string): string {\n // Lightweight rendering for Agent-mode messages: keep paragraph breaks,\n // inline `code`, **bold**, [text](url) markdown links, and bare-URL\n // autolinks. We deliberately skip headings/lists so the model's casual\n // streaming output doesn't get over-formatted.\n return escapeHtml(text)\n .split(/\\n\\s*\\n/)\n .map((para) =>\n para\n .replace(/`([^`\\n]+)`/g, \"<code>$1</code>\")\n .replace(/\\*\\*([^*\\n]+)\\*\\*/g, \"<strong>$1</strong>\")\n // Markdown links first so we don't double-link the URL inside.\n .replace(/\\[([^\\]\\n]+)\\]\\(([^)\\s]+)\\)/g, (_m, label: string, url: string) => {\n const safe = /^(https?:|mailto:|\\/|#)/i.test(url) ? url : \"#\";\n return `<a href=\"${safe}\" target=\"_blank\" rel=\"noreferrer noopener\">${label}</a>`;\n })\n // Then autolink bare URLs that aren't already inside an href=\"...\".\n .replace(/(^|[\\s(])(https?:\\/\\/[^\\s<>\"')\\]]+)/g, (_m, lead: string, url: string) => {\n return `${lead}<a href=\"${url}\" target=\"_blank\" rel=\"noreferrer noopener\">${url}</a>`;\n })\n .replace(/\\n/g, \"<br />\"),\n )\n .map((p) => `<p>${p}</p>`)\n .join(\"\");\n }\n\n function renderToolEvent(\n evt: Extract<TimelineEvent, { kind: \"tool\" }>,\n ): string {\n const summary = summarizeTool(evt.name, evt.args);\n const expanded = state.expandedTools.has(evt.id);\n const detailHtml = expanded ? renderToolDetail(evt) : \"\";\n return `\n <div class=\"coding-tab__tool\" data-status=\"${evt.status}\" data-tool-id=\"${evt.id}\">\n <button class=\"coding-tab__tool-header\" data-role=\"toggle-tool\" data-tool-id=\"${evt.id}\" aria-expanded=\"${expanded}\">\n <span class=\"coding-tab__tool-dot\"></span>\n ${ICON_CHEVRON}\n <span class=\"coding-tab__tool-name\">${escapeHtml(evt.name)}</span>\n ${summary ? `<span class=\"coding-tab__tool-summary\">${escapeHtml(summary)}</span>` : \"\"}\n </button>\n ${detailHtml}\n </div>\n `;\n }\n\n function summarizeTool(name: string, args: unknown): string {\n if (!args || typeof args !== \"object\") return \"\";\n const a = args as Record<string, unknown>;\n const lower = name.toLowerCase();\n if (lower.includes(\"grep\")) {\n const pattern = typeof a.pattern === \"string\" ? a.pattern : typeof a.query === \"string\" ? a.query : \"\";\n return pattern ? `\"${pattern}\"` : \"\";\n }\n if (lower.includes(\"read\") && typeof a.path === \"string\") {\n const lines =\n typeof a.offset === \"number\" || typeof a.limit === \"number\"\n ? `:${a.offset ?? \"\"}-${(Number(a.offset ?? 0) + Number(a.limit ?? 0)) || \"\"}`\n : \"\";\n return `${a.path}${lines}`;\n }\n if ((lower.includes(\"glob\") || lower.includes(\"file_search\")) && typeof a.glob_pattern === \"string\") {\n return a.glob_pattern as string;\n }\n if (lower === \"shell\" && typeof a.command === \"string\") {\n return (a.command as string).slice(0, 80);\n }\n if (lower === \"task\" && typeof a.description === \"string\") {\n return a.description as string;\n }\n if (lower.includes(\"write\") && typeof a.path === \"string\") {\n return a.path as string;\n }\n if (lower.includes(\"edit\") && typeof a.path === \"string\") {\n return a.path as string;\n }\n if (lower.includes(\"delete\") && typeof a.path === \"string\") {\n return a.path as string;\n }\n return \"\";\n }\n\n function renderToolDetail(evt: Extract<TimelineEvent, { kind: \"tool\" }>): string {\n const argsBlock =\n evt.args !== undefined && evt.args !== null\n ? `<div class=\"coding-tab__tool-section\"><div class=\"coding-tab__tool-label\">Args</div><pre>${escapeHtml(formatBlob(evt.args))}</pre></div>`\n : \"\";\n const resultBlock =\n evt.result !== undefined && evt.result !== null\n ? `<div class=\"coding-tab__tool-section\"><div class=\"coding-tab__tool-label\">Result</div><pre>${escapeHtml(formatBlob(evt.result))}</pre></div>`\n : evt.status === \"running\"\n ? `<div class=\"coding-tab__tool-section\"><div class=\"coding-tab__tool-label\">Result</div><pre class=\"coding-tab__tool-pending\">…running</pre></div>`\n : \"\";\n return `<div class=\"coding-tab__tool-detail\">${argsBlock}${resultBlock}</div>`;\n }\n\n function formatBlob(value: unknown): string {\n if (typeof value === \"string\") return value;\n try {\n return JSON.stringify(value, null, 2);\n } catch {\n return String(value);\n }\n }\n\n function renderBranchFallback(branch: BranchInfo): string {\n // Surfaced when the agent pushed commits but Cursor's auto-PR step didn't\n // create a PR (typically because of an account/team-level setting). Lets\n // the user open the PR themselves with one click instead of being stuck.\n const slug = repoSlug(branch.repoUrl);\n return `<div class=\"coding-tab__pr coding-tab__pr--branch\">\n <div class=\"coding-tab__pr-title\">Branch pushed: <code>${escapeHtml(branch.branch)}</code></div>\n <div class=\"coding-tab__pr-status\">No PR was opened automatically. Click below to create one yourself on GitHub.</div>\n <div class=\"coding-tab__pr-actions\">\n <a class=\"coding-tab__btn primary\" href=\"${escapeAttr(branch.compareUrl)}\" target=\"_blank\" rel=\"noreferrer\">Create PR on GitHub</a>\n <a class=\"coding-tab__btn\" href=\"${escapeAttr(branch.repoUrl)}/pulls\" target=\"_blank\" rel=\"noreferrer\">View ${escapeHtml(slug)} PRs</a>\n </div>\n </div>`;\n }\n\n function renderPr(pr: PrInfo): string {\n const merge = state.mergeStates.get(pr.url) ?? { state: \"idle\" as const };\n let mergeButton: string;\n let statusLine = \"\";\n switch (merge.state) {\n case \"loading\":\n mergeButton = `<button class=\"coding-tab__btn primary\" data-state=\"loading\" disabled>Merging…</button>`;\n statusLine = `<div class=\"coding-tab__pr-status\">Merging this PR and triggering Railway redeploy…</div>`;\n break;\n case \"success\":\n mergeButton = `<button class=\"coding-tab__btn success\" disabled>Merged ✓</button>`;\n statusLine = `<div class=\"coding-tab__pr-status is-success\">Merged commit ${escapeHtml(merge.sha.slice(0, 7))}. Railway should redeploy on the next push hook.</div>`;\n break;\n case \"error\":\n mergeButton = `<button class=\"coding-tab__btn primary\" data-role=\"merge\" data-pr=\"${escapeAttr(pr.url)}\">Try merge again</button>`;\n statusLine = `<div class=\"coding-tab__pr-status is-error\">Merge failed: ${escapeHtml(merge.message)}</div>`;\n break;\n default:\n mergeButton = `<button class=\"coding-tab__btn primary\" data-role=\"merge\" data-pr=\"${escapeAttr(pr.url)}\">Merge & Redeploy</button>`;\n }\n return `<div class=\"coding-tab__pr\">\n <div class=\"coding-tab__pr-title\">PR #${pr.number} opened in ${escapeHtml(pr.owner)}/${escapeHtml(pr.repo)}</div>\n ${pr.title ? `<div>${escapeHtml(pr.title)}</div>` : \"\"}\n <div class=\"coding-tab__pr-actions\">\n <a class=\"coding-tab__btn\" href=\"${escapeAttr(pr.url)}\" target=\"_blank\" rel=\"noreferrer\">Open in GitHub</a>\n ${mergeButton}\n </div>\n ${statusLine}\n </div>`;\n }\n\n function extractText(turn: StoredTurn): string {\n return turn.events\n .filter((e): e is Extract<TimelineEvent, { kind: \"text\" }> => e.kind === \"text\")\n .map((e) => e.text)\n .join(\"\\n\\n\");\n }\n\n // ───────── events ─────────\n\n function bindEvents() {\n root.querySelector(`[data-role=\"model\"]`)?.addEventListener(\"change\", (e) => {\n state.model = (e.target as HTMLSelectElement).value as ModelChoice;\n });\n root.querySelectorAll(`[data-mode]`).forEach((b) => {\n b.addEventListener(\"click\", () => {\n state.mode = (b as HTMLElement).dataset.mode as ChatMode;\n render();\n });\n });\n root.querySelector(`[data-role=\"repo\"]`)?.addEventListener(\"change\", (e) => {\n state.repoUrl = (e.target as HTMLInputElement).value.trim();\n });\n root.querySelector(`[data-role=\"logout\"]`)?.addEventListener(\"click\", async () => {\n await fetch(`${apiBase}/auth/logout`, { method: \"POST\", credentials: \"include\" }).catch(() => {});\n for (const c of state.chats) c.abort?.abort();\n state.me = null;\n state.chats = [];\n state.activeChatId = null;\n render();\n });\n root.querySelector(`[data-role=\"toggle-sidebar\"]`)?.addEventListener(\"click\", () => {\n state.sidebarOpen = !state.sidebarOpen;\n render();\n });\n\n root.querySelectorAll(`[data-role=\"new-chat\"]`).forEach((b) =>\n b.addEventListener(\"click\", async () => {\n const t = await createChat();\n if (t && window.innerWidth < 720) state.sidebarOpen = false;\n render();\n }),\n );\n root.querySelectorAll(`[data-role=\"select-chat\"]`).forEach((b) =>\n b.addEventListener(\"click\", () => {\n const id = (b as HTMLElement).dataset.chatId!;\n switchChat(id);\n }),\n );\n root.querySelectorAll(`[data-role=\"rename-chat\"]`).forEach((b) =>\n b.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n renameChat((b as HTMLElement).dataset.chatId!);\n }),\n );\n root.querySelectorAll(`[data-role=\"delete-chat\"]`).forEach((b) =>\n b.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n deleteChat((b as HTMLElement).dataset.chatId!);\n }),\n );\n\n root.querySelector(`[data-role=\"send\"]`)?.addEventListener(\"click\", () => onSend());\n root.querySelector(`[data-role=\"prompt\"]`)?.addEventListener(\"keydown\", (e: Event) => {\n const ev = e as KeyboardEvent;\n if (ev.key === \"Enter\" && (ev.metaKey || ev.ctrlKey)) {\n ev.preventDefault();\n onSend();\n }\n });\n root.querySelector(`[data-role=\"cancel\"]`)?.addEventListener(\"click\", () => onCancel());\n root.querySelectorAll(`[data-role=\"execute\"]`).forEach((b) =>\n b.addEventListener(\"click\", () => onExecute()),\n );\n root.querySelectorAll(`[data-role=\"merge\"]`).forEach((b) => {\n b.addEventListener(\"click\", () => onMerge((b as HTMLElement).dataset.pr!));\n });\n\n root.querySelectorAll(`[data-role=\"toggle-tool\"]`).forEach((b) =>\n b.addEventListener(\"click\", () => {\n const id = (b as HTMLElement).dataset.toolId!;\n if (state.expandedTools.has(id)) state.expandedTools.delete(id);\n else state.expandedTools.add(id);\n render();\n }),\n );\n }\n\n // ───────── send / execute / cancel / merge ─────────\n\n async function onSend() {\n let chat = activeChat();\n if (!chat) {\n chat = await createChat();\n if (!chat) return;\n }\n if (chat.isStreaming) return;\n\n const promptEl = root.querySelector(`[data-role=\"prompt\"]`) as HTMLTextAreaElement | null;\n const prompt = promptEl?.value.trim() ?? \"\";\n if (!prompt) return;\n if (promptEl) promptEl.value = \"\";\n\n chat.turns = chat.turns ?? [];\n const userTurn: StoredTurn = {\n id: cryptoRandomId(),\n chatId: chat.meta.id,\n seq: chat.turns.length,\n role: \"user\",\n isPlan: state.mode === \"plan\",\n status: \"finished\",\n events: [{ kind: \"text\", id: cryptoRandomId(), text: prompt }],\n prompt,\n createdAt: Date.now(),\n };\n chat.turns.push(userTurn);\n\n const assistantTurn: StoredTurn = {\n id: cryptoRandomId(),\n chatId: chat.meta.id,\n seq: chat.turns.length,\n role: \"assistant\",\n isPlan: state.mode === \"plan\",\n status: \"running\",\n events: [],\n createdAt: Date.now(),\n };\n chat.turns.push(assistantTurn);\n chat.isStreaming = true;\n if (chat.meta.title === \"New chat\") {\n chat.meta = { ...chat.meta, title: prompt.length > 60 ? `${prompt.slice(0, 57)}…` : prompt };\n }\n chat.meta = { ...chat.meta, mode: state.mode, model: state.model, updatedAt: Date.now() };\n render();\n\n try {\n await streamSse(\n \"/agent/send\",\n {\n chatId: chat.meta.id,\n prompt,\n mode: state.mode,\n },\n chat,\n assistantTurn,\n );\n } catch (err) {\n if ((err as Error).name !== \"AbortError\") {\n appendErrorToTurn(assistantTurn, err);\n }\n assistantTurn.status = \"error\";\n } finally {\n chat.isStreaming = false;\n chat.activeRunId = null;\n chat.abort = null;\n render();\n }\n }\n\n async function onExecute() {\n const chat = activeChat();\n if (!chat || chat.isStreaming) return;\n chat.turns = chat.turns ?? [];\n const turn: StoredTurn = {\n id: cryptoRandomId(),\n chatId: chat.meta.id,\n seq: chat.turns.length,\n role: \"assistant\",\n isPlan: false,\n status: \"running\",\n events: [],\n createdAt: Date.now(),\n };\n chat.turns.push(turn);\n chat.isStreaming = true;\n render();\n try {\n await streamSse(\"/agent/execute\", { chatId: chat.meta.id }, chat, turn);\n } catch (err) {\n if ((err as Error).name !== \"AbortError\") {\n appendErrorToTurn(turn, err);\n }\n turn.status = \"error\";\n } finally {\n chat.isStreaming = false;\n chat.activeRunId = null;\n chat.abort = null;\n render();\n }\n }\n\n async function onCancel() {\n const chat = activeChat();\n if (!chat) return;\n if (chat.activeRunId) {\n try {\n await api(\"/agent/cancel\", {\n method: \"POST\",\n body: JSON.stringify({ chatId: chat.meta.id, runId: chat.activeRunId }),\n });\n } catch (err) {\n console.error(\"[coding-tab] cancel failed\", err);\n }\n }\n chat.abort?.abort();\n }\n\n async function onMerge(prUrl: string) {\n const chat = activeChat();\n if (!chat) return;\n if (state.mergeStates.get(prUrl)?.state === \"loading\") return;\n state.mergeStates.set(prUrl, { state: \"loading\" });\n render();\n\n chat.turns = chat.turns ?? [];\n try {\n const result = await api<{ sha: string; merged: boolean }>(\"/pr/merge\", {\n method: \"POST\",\n body: JSON.stringify({ prUrl, mergeMethod: \"squash\" }),\n });\n if (result.merged) {\n state.mergeStates.set(prUrl, { state: \"success\", sha: result.sha });\n chat.turns.push({\n id: cryptoRandomId(),\n chatId: chat.meta.id,\n seq: chat.turns.length,\n role: \"assistant\",\n isPlan: false,\n status: \"finished\",\n events: [\n {\n kind: \"text\",\n id: cryptoRandomId(),\n text: `Merged commit \\`${result.sha.slice(0, 7)}\\` into the default branch. Railway should redeploy on the next push hook.`,\n },\n ],\n createdAt: Date.now(),\n });\n } else {\n state.mergeStates.set(prUrl, {\n state: \"error\",\n message: \"GitHub returned merged=false\",\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n state.mergeStates.set(prUrl, { state: \"error\", message });\n } finally {\n render();\n }\n }\n\n function appendErrorToTurn(turn: StoredTurn, err: unknown): void {\n const message = err instanceof Error ? err.message : String(err);\n turn.events.push({\n kind: \"text\",\n id: cryptoRandomId(),\n text: `[error] ${message}`,\n });\n }\n\n // ───────── streaming ─────────\n\n async function streamSse(\n path: string,\n body: unknown,\n chat: ChatThread,\n turn: StoredTurn,\n ): Promise<void> {\n chat.abort = new AbortController();\n const resp = await fetch(`${apiBase}${path}`, {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\", Accept: \"text/event-stream\" },\n body: JSON.stringify(body),\n signal: chat.abort.signal,\n });\n if (!resp.ok || !resp.body) {\n throw new Error(`${resp.status} ${resp.statusText}`);\n }\n const reader = resp.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n /** Track whether the last applied event was a `text` event so we know\n * whether to merge subsequent text deltas into the same paragraph. */\n let lastWasText = false;\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const events = buffer.split(\"\\n\\n\");\n buffer = events.pop() ?? \"\";\n for (const raw of events) {\n const dataLine = raw.split(\"\\n\").find((l) => l.startsWith(\"data: \"));\n if (!dataLine) continue;\n try {\n const evt = JSON.parse(dataLine.slice(6)) as StreamEvent;\n lastWasText = applyStreamEvent(evt, chat, turn, lastWasText);\n render();\n } catch (e) {\n console.warn(\"[coding-tab] bad sse event\", e);\n }\n }\n }\n }\n\n /**\n * Apply an SSE event into the active turn's timeline. Returns whether the\n * last applied event was a streaming text event (so the next text chunk can\n * be merged with it).\n */\n function applyStreamEvent(\n evt: StreamEvent,\n chat: ChatThread,\n turn: StoredTurn,\n lastWasText: boolean,\n ): boolean {\n switch (evt.kind) {\n case \"ready\":\n chat.activeRunId = evt.runId;\n return false;\n case \"text\": {\n // Each `text` SSE corresponds to one logical block from the model.\n // Within a single render burst the server sends one event per block,\n // so we always start a new paragraph here. (If the SDK ever streams\n // sub-block deltas, the server-side TurnBuffer will have already\n // coalesced them into one event for us.)\n const last = turn.events[turn.events.length - 1];\n if (lastWasText && last && last.kind === \"text\") {\n // Same delta train — append.\n last.text += evt.text;\n } else {\n turn.events.push({\n kind: \"text\",\n id: cryptoRandomId(),\n text: evt.text,\n });\n }\n return true;\n }\n case \"thinking\":\n return lastWasText;\n case \"tool\": {\n const existing = turn.events.find(\n (e): e is Extract<TimelineEvent, { kind: \"tool\" }> =>\n e.kind === \"tool\" && e.callId === evt.callId,\n );\n if (existing) {\n existing.status = evt.status;\n if (evt.args !== undefined) existing.args = evt.args;\n if (evt.result !== undefined) existing.result = evt.result;\n } else {\n turn.events.push({\n kind: \"tool\",\n id: cryptoRandomId(),\n callId: evt.callId,\n name: evt.name,\n status: evt.status,\n args: evt.args,\n result: evt.result,\n });\n }\n return false;\n }\n case \"status\":\n return lastWasText;\n case \"result\":\n turn.status = evt.status as TurnStatus;\n if (evt.pr) turn.pr = evt.pr;\n if (evt.branch && !evt.pr) turn.branch = evt.branch;\n if (turn.isPlan && evt.status === \"finished\") turn.showExecute = true;\n chat.activeRunId = null;\n return false;\n case \"error\":\n turn.events.push({\n kind: \"text\",\n id: cryptoRandomId(),\n text: `[error] ${evt.message}`,\n });\n turn.status = \"error\";\n chat.activeRunId = null;\n return false;\n }\n }\n\n // ───────── bootstrap ─────────\n\n async function bootstrap() {\n try {\n const me = await api<MeResponse>(\"/auth/me\");\n state.me = me;\n if (me.defaultRepoUrl) {\n state.repoUrl = me.defaultRepoUrl;\n state.repoLocked = true;\n }\n } catch (err) {\n const status = (err as { status?: number }).status;\n if (status === 401 || status === 403) {\n state.me = null;\n render();\n return;\n }\n console.error(\"[coding-tab] /auth/me failed\", err);\n state.me = null;\n render();\n return;\n }\n try {\n const { models } = await api<{ models: ModelOption[] }>(\"/models\");\n state.models = models;\n if (models.length > 0 && !models.find((m) => m.choice === state.model)) {\n state.model = models[0]!.choice;\n }\n } catch (err) {\n console.error(\"[coding-tab] /models failed\", err);\n state.models = [\n { choice: \"sonnet\", cursorModelId: \"auto\", displayName: \"Sonnet (fallback)\" },\n { choice: \"opus\", cursorModelId: \"auto\", displayName: \"Opus (fallback)\" },\n ];\n }\n await loadChatList();\n if (state.chats.length > 0 && !state.activeChatId) {\n state.activeChatId = state.chats[0]!.meta.id;\n ensureChatLoaded(state.activeChatId).then(() => render());\n }\n render();\n }\n\n bootstrap();\n\n return {\n destroy() {\n for (const c of state.chats) c.abort?.abort();\n el.removeChild(root);\n },\n };\n}\n\nif (typeof window !== \"undefined\") {\n (window as unknown as { CodingTab?: { mountCodingTab: typeof mountCodingTab } }).CodingTab = {\n mountCodingTab,\n };\n}\n","/**\n * Tiny zero-dependency markdown renderer used for Plan-mode assistant turns.\n *\n * Supports headings (`#`/`##`/`###`/`####`), unordered (`-`/`*`) and ordered\n * (`1.`) lists, fenced code blocks, inline `code`, `**bold**`, `*italic*`,\n * `[link](url)`, horizontal rules (`---`), paragraphs, and hard breaks.\n *\n * All text is HTML-escaped before any markdown substitutions are applied so\n * the output is safe to inject via `innerHTML` even when the upstream model\n * dumps unsanitised user input back to us.\n */\n\nexport function escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) =>\n (\n {\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n } as Record<string, string>\n )[c]!,\n );\n}\n\nfunction applyInline(text: string): string {\n // Inline code first — anything inside backticks should be inert.\n const segments: string[] = [];\n const codeRe = /`([^`\\n]+)`/g;\n let lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = codeRe.exec(text)) !== null) {\n segments.push(applyInlineRest(text.slice(lastIndex, m.index)));\n segments.push(`<code>${m[1]!}</code>`);\n lastIndex = m.index + m[0].length;\n }\n segments.push(applyInlineRest(text.slice(lastIndex)));\n return segments.join(\"\");\n}\n\nfunction applyInlineRest(text: string): string {\n return text\n .replace(/\\*\\*([^*\\n]+)\\*\\*/g, \"<strong>$1</strong>\")\n .replace(/(^|[^*])\\*([^*\\n]+)\\*(?!\\*)/g, \"$1<em>$2</em>\")\n .replace(/\\[([^\\]\\n]+)\\]\\(([^)\\s]+)\\)/g, (_match, label: string, url: string) => {\n const safeUrl = /^(https?:|mailto:|\\/|#)/i.test(url) ? url : \"#\";\n return `<a href=\"${safeUrl}\" target=\"_blank\" rel=\"noreferrer noopener\">${label}</a>`;\n });\n}\n\ninterface ListContext {\n type: \"ul\" | \"ol\";\n indent: number;\n items: string[];\n}\n\nfunction flushParagraph(buf: string[], out: string[]) {\n if (buf.length === 0) return;\n const joined = buf.join(\" \").trim();\n buf.length = 0;\n if (!joined) return;\n out.push(`<p>${applyInline(joined)}</p>`);\n}\n\nfunction flushList(stack: ListContext[], out: string[]) {\n while (stack.length > 0) {\n const ctx = stack.pop()!;\n out.push(`<${ctx.type}>${ctx.items.join(\"\")}</${ctx.type}>`);\n }\n}\n\nexport function renderMarkdown(input: string): string {\n // Escape first so list markers / fences are still recognised but any HTML in\n // the source becomes inert.\n const escaped = escapeHtml(input).replace(/\\r\\n/g, \"\\n\");\n const lines = escaped.split(\"\\n\");\n\n const out: string[] = [];\n const paragraph: string[] = [];\n const listStack: ListContext[] = [];\n let inCode = false;\n let codeLang = \"\";\n const codeBuf: string[] = [];\n\n const closeOpenBlocks = () => {\n flushParagraph(paragraph, out);\n flushList(listStack, out);\n };\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n\n // Fenced code blocks\n const fence = line.match(/^\\s*```(\\w*)\\s*$/);\n if (fence) {\n if (inCode) {\n const cls = codeLang ? ` class=\"lang-${codeLang}\"` : \"\";\n out.push(`<pre><code${cls}>${codeBuf.join(\"\\n\")}</code></pre>`);\n codeBuf.length = 0;\n codeLang = \"\";\n inCode = false;\n } else {\n closeOpenBlocks();\n inCode = true;\n codeLang = fence[1] ?? \"\";\n }\n continue;\n }\n if (inCode) {\n codeBuf.push(line);\n continue;\n }\n\n // Blank line: paragraph break, list break.\n if (!line.trim()) {\n closeOpenBlocks();\n continue;\n }\n\n // Horizontal rule.\n if (/^\\s*(-{3,}|\\*{3,}|_{3,})\\s*$/.test(line)) {\n closeOpenBlocks();\n out.push(\"<hr />\");\n continue;\n }\n\n // Headings.\n const h = line.match(/^(#{1,6})\\s+(.*)$/);\n if (h) {\n closeOpenBlocks();\n const level = Math.min(6, h[1]!.length);\n out.push(`<h${level}>${applyInline(h[2]!.trim())}</h${level}>`);\n continue;\n }\n\n // List items.\n const ul = line.match(/^(\\s*)[-*]\\s+(.*)$/);\n const ol = line.match(/^(\\s*)(\\d+)\\.\\s+(.*)$/);\n if (ul || ol) {\n flushParagraph(paragraph, out);\n const indent = (ul ? ul[1] : ol![1])!.length;\n const type: \"ul\" | \"ol\" = ul ? \"ul\" : \"ol\";\n const content = (ul ? ul[2] : ol![3])!;\n // Pop deeper lists.\n while (listStack.length > 0 && listStack[listStack.length - 1]!.indent > indent) {\n const ctx = listStack.pop()!;\n const prev = listStack[listStack.length - 1]?.items;\n const html = `<${ctx.type}>${ctx.items.join(\"\")}</${ctx.type}>`;\n if (prev && prev.length > 0) {\n prev[prev.length - 1] = prev[prev.length - 1]!.replace(\n /<\\/li>$/,\n `${html}</li>`,\n );\n } else {\n out.push(html);\n }\n }\n const top = listStack[listStack.length - 1];\n if (!top || top.indent < indent || top.type !== type) {\n listStack.push({ type, indent, items: [`<li>${applyInline(content)}</li>`] });\n } else {\n top.items.push(`<li>${applyInline(content)}</li>`);\n }\n continue;\n }\n\n // If we were in a list and hit a non-list line, close the list.\n if (listStack.length > 0) {\n flushList(listStack, out);\n }\n paragraph.push(line.trim());\n }\n\n if (inCode) {\n out.push(`<pre><code>${codeBuf.join(\"\\n\")}</code></pre>`);\n } else {\n closeOpenBlocks();\n }\n\n return out.join(\"\\n\");\n}\n"],"mappings":"ucAAA,IAAAA,GAAA,GAAAC,GAAAD,GAAA,oBAAAE,ICYO,SAASC,EAAWC,EAAmB,CAC5C,OAAOA,EAAE,QAAQ,WAAaC,IAE1B,CACE,IAAK,QACL,IAAK,OACL,IAAK,OACL,IAAK,SACL,IAAK,OACP,GACAA,CAAC,CACL,CACF,CAEA,SAASC,EAAYC,EAAsB,CAEzC,IAAMC,EAAqB,CAAC,EACtBC,EAAS,eACXC,EAAY,EACZC,EACJ,MAAQA,EAAIF,EAAO,KAAKF,CAAI,KAAO,MACjCC,EAAS,KAAKI,EAAgBL,EAAK,MAAMG,EAAWC,EAAE,KAAK,CAAC,CAAC,EAC7DH,EAAS,KAAK,SAASG,EAAE,CAAC,CAAE,SAAS,EACrCD,EAAYC,EAAE,MAAQA,EAAE,CAAC,EAAE,OAE7B,OAAAH,EAAS,KAAKI,EAAgBL,EAAK,MAAMG,CAAS,CAAC,CAAC,EAC7CF,EAAS,KAAK,EAAE,CACzB,CAEA,SAASI,EAAgBL,EAAsB,CAC7C,OAAOA,EACJ,QAAQ,qBAAsB,qBAAqB,EACnD,QAAQ,+BAAgC,eAAe,EACvD,QAAQ,+BAAgC,CAACM,EAAQC,EAAeC,IAExD,YADS,2BAA2B,KAAKA,CAAG,EAAIA,EAAM,GACnC,+CAA+CD,CAAK,MAC/E,CACL,CAQA,SAASE,EAAeC,EAAeC,EAAe,CACpD,GAAID,EAAI,SAAW,EAAG,OACtB,IAAME,EAASF,EAAI,KAAK,GAAG,EAAE,KAAK,EAClCA,EAAI,OAAS,EACRE,GACLD,EAAI,KAAK,MAAMZ,EAAYa,CAAM,CAAC,MAAM,CAC1C,CAEA,SAASC,EAAUC,EAAsBH,EAAe,CACtD,KAAOG,EAAM,OAAS,GAAG,CACvB,IAAMC,EAAMD,EAAM,IAAI,EACtBH,EAAI,KAAK,IAAII,EAAI,IAAI,IAAIA,EAAI,MAAM,KAAK,EAAE,CAAC,KAAKA,EAAI,IAAI,GAAG,CAC7D,CACF,CAEO,SAASC,EAAeC,EAAuB,CAIpD,IAAMC,EADUtB,EAAWqB,CAAK,EAAE,QAAQ,QAAS;AAAA,CAAI,EACjC,MAAM;AAAA,CAAI,EAE1BN,EAAgB,CAAC,EACjBQ,EAAsB,CAAC,EACvBC,EAA2B,CAAC,EAC9BC,EAAS,GACTC,EAAW,GACTC,EAAoB,CAAC,EAErBC,EAAkB,IAAM,CAC5Bf,EAAeU,EAAWR,CAAG,EAC7BE,EAAUO,EAAWT,CAAG,CAC1B,EAEA,QAASc,EAAI,EAAGA,EAAIP,EAAM,OAAQO,IAAK,CACrC,IAAMC,EAAOR,EAAMO,CAAC,EAGdE,EAAQD,EAAK,MAAM,kBAAkB,EAC3C,GAAIC,EAAO,CACT,GAAIN,EAAQ,CACV,IAAMO,EAAMN,EAAW,gBAAgBA,CAAQ,IAAM,GACrDX,EAAI,KAAK,aAAaiB,CAAG,IAAIL,EAAQ,KAAK;AAAA,CAAI,CAAC,eAAe,EAC9DA,EAAQ,OAAS,EACjBD,EAAW,GACXD,EAAS,EACX,MACEG,EAAgB,EAChBH,EAAS,GACTC,EAAWK,EAAM,CAAC,GAAK,GAEzB,QACF,CACA,GAAIN,EAAQ,CACVE,EAAQ,KAAKG,CAAI,EACjB,QACF,CAGA,GAAI,CAACA,EAAK,KAAK,EAAG,CAChBF,EAAgB,EAChB,QACF,CAGA,GAAI,+BAA+B,KAAKE,CAAI,EAAG,CAC7CF,EAAgB,EAChBb,EAAI,KAAK,QAAQ,EACjB,QACF,CAGA,IAAMkB,EAAIH,EAAK,MAAM,mBAAmB,EACxC,GAAIG,EAAG,CACLL,EAAgB,EAChB,IAAMM,EAAQ,KAAK,IAAI,EAAGD,EAAE,CAAC,EAAG,MAAM,EACtClB,EAAI,KAAK,KAAKmB,CAAK,IAAI/B,EAAY8B,EAAE,CAAC,EAAG,KAAK,CAAC,CAAC,MAAMC,CAAK,GAAG,EAC9D,QACF,CAGA,IAAMC,EAAKL,EAAK,MAAM,oBAAoB,EACpCM,EAAKN,EAAK,MAAM,uBAAuB,EAC7C,GAAIK,GAAMC,EAAI,CACZvB,EAAeU,EAAWR,CAAG,EAC7B,IAAMsB,GAAUF,EAAKA,EAAG,CAAC,EAAIC,EAAI,CAAC,GAAI,OAChCE,EAAoBH,EAAK,KAAO,KAChCI,EAAWJ,EAAKA,EAAG,CAAC,EAAIC,EAAI,CAAC,EAEnC,KAAOZ,EAAU,OAAS,GAAKA,EAAUA,EAAU,OAAS,CAAC,EAAG,OAASa,GAAQ,CAC/E,IAAMlB,EAAMK,EAAU,IAAI,EACpBgB,EAAOhB,EAAUA,EAAU,OAAS,CAAC,GAAG,MACxCiB,EAAO,IAAItB,EAAI,IAAI,IAAIA,EAAI,MAAM,KAAK,EAAE,CAAC,KAAKA,EAAI,IAAI,IACxDqB,GAAQA,EAAK,OAAS,EACxBA,EAAKA,EAAK,OAAS,CAAC,EAAIA,EAAKA,EAAK,OAAS,CAAC,EAAG,QAC7C,UACA,GAAGC,CAAI,OACT,EAEA1B,EAAI,KAAK0B,CAAI,CAEjB,CACA,IAAMC,EAAMlB,EAAUA,EAAU,OAAS,CAAC,EACtC,CAACkB,GAAOA,EAAI,OAASL,GAAUK,EAAI,OAASJ,EAC9Cd,EAAU,KAAK,CAAE,KAAAc,EAAM,OAAAD,EAAQ,MAAO,CAAC,OAAOlC,EAAYoC,CAAO,CAAC,OAAO,CAAE,CAAC,EAE5EG,EAAI,MAAM,KAAK,OAAOvC,EAAYoC,CAAO,CAAC,OAAO,EAEnD,QACF,CAGIf,EAAU,OAAS,GACrBP,EAAUO,EAAWT,CAAG,EAE1BQ,EAAU,KAAKO,EAAK,KAAK,CAAC,CAC5B,CAEA,OAAIL,EACFV,EAAI,KAAK,cAAcY,EAAQ,KAAK;AAAA,CAAI,CAAC,eAAe,EAExDC,EAAgB,EAGXb,EAAI,KAAK;AAAA,CAAI,CACtB,CDlIA,IAAM4B,EAAc,okBAEdC,EAAY,+JACZC,GAAa,kPACbC,GAAc,0MACdC,GAAY,6KACZC,GAAe,wMAErB,SAASC,EAAWC,EAAmB,CACrC,OAAOC,EAAWD,CAAC,CACrB,CAEA,SAASE,EAASC,EAAqB,CACrC,IAAMC,EAAID,EAAI,MAAM,gDAAgD,EACpE,OAAOC,EAAI,GAAGA,EAAE,CAAC,CAAC,IAAIA,EAAE,CAAC,CAAC,GAAKD,CACjC,CAEA,SAASE,GAAWF,EAAqB,CAGvC,IAAMC,EAAID,EAAI,MAAM,gDAAgD,EACpE,OAAOC,EAAI,sBAAsBA,EAAE,CAAC,CAAC,IAAIA,EAAE,CAAC,CAAC,GAAKD,EAAI,QAAQ,YAAa,EAAE,CAC/E,CAEA,SAASG,GAAeH,EAAqB,CAC3C,MAAO,GAAGE,GAAWF,CAAG,CAAC,QAC3B,CAEA,SAASI,GAAyB,CAChC,OAAO,OAAO,OAAW,KAAe,eAAgB,OACpD,OAAO,WAAW,EAClB,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CACxC,CAEA,SAASC,GAAaC,EAAoB,CACxC,IAAMC,EAAO,KAAK,IAAI,EAAID,EAC1B,OAAIC,EAAO,IAAe,WACtBA,EAAO,KAAkB,GAAG,KAAK,MAAMA,EAAO,GAAM,CAAC,IACrDA,EAAO,MAAmB,GAAG,KAAK,MAAMA,EAAO,IAAS,CAAC,IACzDA,EAAO,OAAoB,GAAG,KAAK,MAAMA,EAAO,KAAU,CAAC,IACxD,IAAI,KAAKD,CAAE,EAAE,mBAAmB,CACzC,CAEO,SAASE,EAAeC,EAAiBC,EAAoC,CAClF,IAAMC,EAAUD,EAAQ,QAAQ,QAAQ,MAAO,EAAE,EAC3CE,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY,aACjBH,EAAG,UAAY,GACfA,EAAG,YAAYG,CAAI,EAEnB,IAAIC,EAAqB,GACzB,SAASC,GAAmB,CAG1B,GAFID,IACJA,EAAqB,GACjB,SAAS,cAAc,+BAA+B,GAAG,OAC7D,IAAME,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,IAAM,aACXA,EAAK,KAAO,GAAGJ,CAAO,aACtBI,EAAK,QAAQ,UAAY,QACzB,SAAS,KAAK,YAAYA,CAAI,CAChC,CACAD,EAAiB,EAEjB,IAAME,EAcF,CACF,GAAI,KACJ,OAAQ,CAAC,EACT,KAAMN,EAAQ,aAAe,OAC7B,MAAOA,EAAQ,cAAgB,SAC/B,QAASA,EAAQ,aAAe,GAChC,WAAY,GACZ,MAAO,CAAC,EACR,aAAc,KACd,YAAa,OAAO,YAAc,IAClC,cAAe,IAAI,IACnB,YAAa,IAAI,GACnB,EAEA,SAASO,GAAgC,CACvC,OAAOD,EAAM,MAAM,KAAME,GAAMA,EAAE,KAAK,KAAOF,EAAM,YAAY,GAAK,IACtE,CAEA,eAAeG,EAAiBC,EAAcC,EAAgC,CAC5E,IAAMC,EAAO,MAAM,MAAM,GAAGX,CAAO,GAAGS,CAAI,GAAI,CAC5C,YAAa,UACb,QAAS,CAAE,eAAgB,mBAAoB,GAAIC,GAAM,SAAW,CAAC,CAAG,EACxE,GAAGA,CACL,CAAC,EACD,GAAI,CAACC,EAAK,GACR,MAAM,OAAO,OAAO,IAAI,MAAM,GAAGA,EAAK,MAAM,IAAIA,EAAK,UAAU,EAAE,EAAG,CAAE,OAAQA,EAAK,MAAO,CAAC,EAC7F,OAAQ,MAAMA,EAAK,KAAK,CAC1B,CAIA,eAAeC,GAA8B,CAC3C,GAAI,CACF,GAAM,CAAE,MAAAC,CAAM,EAAI,MAAML,EAA+B,QAAQ,EAEzDM,EAAW,IAAI,IAAIT,EAAM,MAAM,IAAKE,GAAM,CAACA,EAAE,KAAK,GAAIA,CAAC,CAAC,CAAC,EAC/DF,EAAM,MAAQQ,EAAM,IAAKE,GAAS,CAChC,IAAMC,EAAOF,EAAS,IAAIC,EAAK,EAAE,EACjC,OAAOC,EACH,CAAE,GAAGA,EAAM,KAAAD,CAAK,EAChB,CACE,KAAAA,EACA,OAAQ,GACR,YAAa,GACb,YAAa,KACb,MAAO,IACT,CACN,CAAC,CACH,OAASE,EAAK,CACZ,QAAQ,MAAM,kCAAmCA,CAAG,EACpDZ,EAAM,MAAQ,CAAC,CACjB,CACF,CAEA,eAAea,EAAiBC,EAA4C,CAC1E,IAAMC,EAAIf,EAAM,MAAM,KAAME,GAAMA,EAAE,KAAK,KAAOY,CAAM,EACtD,GAAI,CAACC,EAAG,OAAO,KACf,GAAIA,EAAE,OAAQ,OAAOA,EACrB,GAAI,CACF,GAAM,CAAE,KAAAC,CAAK,EAAI,MAAMb,EAAwB,UAAUW,CAAM,EAAE,EACjE,OAAAC,EAAE,MAAQC,EAAK,MACfD,EAAE,OAAS,GACXA,EAAE,KAAO,CACP,GAAIC,EAAK,GACT,MAAOA,EAAK,MACZ,KAAMA,EAAK,KACX,MAAOA,EAAK,MACZ,UAAWA,EAAK,UAChB,UAAWA,EAAK,SAClB,EAEAhB,EAAM,KAAOgB,EAAK,KAClBhB,EAAM,MAAQgB,EAAK,MACZD,CACT,OAASH,EAAK,CACZ,eAAQ,MAAM,uBAAuBE,CAAM,eAAgBF,CAAG,EACvD,IACT,CACF,CAEA,eAAeK,GAAyC,CACtD,GAAI,CACF,GAAM,CAAE,KAAAD,CAAK,EAAI,MAAMb,EAA0B,SAAU,CACzD,OAAQ,OACR,KAAM,KAAK,UAAU,CACnB,KAAMH,EAAM,KACZ,MAAOA,EAAM,MACb,QAASA,EAAM,WAAa,OAAYA,EAAM,SAAW,MAC3D,CAAC,CACH,CAAC,EACKkB,EAAqB,CACzB,KAAM,CACJ,GAAIF,EAAK,GACT,MAAOA,EAAK,MACZ,KAAMA,EAAK,KACX,MAAOA,EAAK,MACZ,UAAWA,EAAK,UAChB,UAAWA,EAAK,SAClB,EACA,MAAO,CAAC,EACR,OAAQ,GACR,YAAa,GACb,YAAa,KACb,MAAO,IACT,EACA,OAAAhB,EAAM,MAAM,QAAQkB,CAAM,EAC1BlB,EAAM,aAAekB,EAAO,KAAK,GAC1BA,CACT,OAASN,EAAK,CACZ,eAAQ,MAAM,kCAAmCA,CAAG,EAC7C,IACT,CACF,CAEA,eAAeO,EAAWL,EAA+B,CACvDd,EAAM,aAAec,EACjB,OAAO,WAAa,MAAKd,EAAM,YAAc,IACjDoB,EAAO,EACP,MAAMP,EAAiBC,CAAM,EAC7BM,EAAO,CACT,CAEA,eAAeC,EAAWP,EAA+B,CACvD,IAAMI,EAASlB,EAAM,MAAM,KAAME,GAAMA,EAAE,KAAK,KAAOY,CAAM,EAC3D,GAAKI,GACA,QAAQ,gBAAgBA,EAAO,KAAK,KAAK,2BAA2B,EACzE,CAAAA,EAAO,OAAO,MAAM,EACpB,GAAI,CACF,MAAMf,EAAI,UAAUW,CAAM,GAAI,CAAE,OAAQ,QAAS,CAAC,CACpD,OAASF,EAAK,CACZ,QAAQ,MAAM,4BAA4BE,CAAM,UAAWF,CAAG,EAC9D,MACF,CACAZ,EAAM,MAAQA,EAAM,MAAM,OAAQE,GAAMA,EAAE,KAAK,KAAOY,CAAM,EACxDd,EAAM,eAAiBc,IACzBd,EAAM,aAAeA,EAAM,MAAM,CAAC,GAAG,KAAK,IAAM,MAElDoB,EAAO,EACT,CAEA,eAAeE,EAAWR,EAA+B,CACvD,IAAMI,EAASlB,EAAM,MAAM,KAAME,GAAMA,EAAE,KAAK,KAAOY,CAAM,EAC3D,GAAI,CAACI,EAAQ,OACb,IAAMK,EAAO,OAAO,cAAeL,EAAO,KAAK,KAAK,EACpD,GAAIK,IAAS,KAAM,OACnB,IAAMC,EAAUD,EAAK,KAAK,EAC1B,GAAI,GAACC,GAAWA,IAAYN,EAAO,KAAK,OACxC,GAAI,CACF,GAAM,CAAE,KAAAF,CAAK,EAAI,MAAMb,EAA0B,UAAUW,CAAM,GAAI,CACnE,OAAQ,QACR,KAAM,KAAK,UAAU,CAAE,MAAOU,CAAQ,CAAC,CACzC,CAAC,EACDN,EAAO,KAAO,CAAE,GAAGA,EAAO,KAAM,MAAOF,EAAK,MAAO,UAAWA,EAAK,SAAU,EAC7EI,EAAO,CACT,OAASR,EAAK,CACZ,QAAQ,MAAM,4BAA4BE,CAAM,UAAWF,CAAG,CAChE,CACF,CAIA,SAASQ,GAAS,CAChB,GAAI,CAACpB,EAAM,GAAI,CACbJ,EAAK,UAAY;AAAA;AAAA;AAAA;AAAA,qBAIFD,CAAO,gBAAgBrB,CAAW;AAAA;AAAA,QAGjD,MACF,CAEA,IAAMmD,EAAa;AAAA;AAAA,yGAEkF/C,EAAS;AAAA;AAAA,YAEtGsB,EAAM,OACL,IACEf,GACC,kBAAkBA,EAAE,MAAM,KAAKe,EAAM,QAAUf,EAAE,OAAS,WAAa,EAAE,IAAIH,EAAWG,EAAE,WAAW,CAAC,WAC1G,EACC,KAAK,EAAE,CAAC;AAAA;AAAA;AAAA,4CAGuBe,EAAM,OAAS,OAAS,YAAc,EAAE;AAAA,6CACvCA,EAAM,OAAS,QAAU,YAAc,EAAE;AAAA;AAAA,UAG5EA,EAAM,YAAcA,EAAM,QACtB,4CAA4CpB,EAAWO,GAAea,EAAM,OAAO,CAAC,CAAC,kDAAkDpB,EAAWG,EAASiB,EAAM,OAAO,CAAC,CAAC,6BAA6B1B,CAAW,SAASQ,EAAWC,EAASiB,EAAM,OAAO,CAAC,CAAC,cAC9P,4EAA4EpB,EAAWoB,EAAM,OAAO,CAAC,qCAC3G;AAAA;AAAA,YAEIA,EAAM,GAAG,UAAY,aAAaA,EAAM,GAAG,SAAS,cAAgB,EAAE;AAAA,iDACjClB,EAAWkB,EAAM,GAAG,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA,MAMvE0B,EAAc;AAAA,0CACkB1B,EAAM,YAAc,UAAY,EAAE;AAAA,4FACgBzB,CAAS;AAAA;AAAA,YAGzFyB,EAAM,MAAM,SAAW,EACnB,iGACAA,EAAM,MAAM,IAAKE,GAAMyB,EAAczB,CAAC,CAAC,EAAE,KAAK,EAAE,CACtD;AAAA;AAAA;AAAA,MAKAc,EAAOf,EAAW,EAClB2B,EAAW;AAAA;AAAA,UAEXZ,EAAOa,EAAeb,CAAI,EAAIc,EAAgB,CAAC;AAAA;AAAA,MAIrDlC,EAAK,UAAY;AAAA,QACb6B,CAAU;AAAA;AAAA,UAERC,CAAW;AAAA,UACXE,CAAQ;AAAA;AAAA,MAId,IAAMV,EAAStB,EAAK,cAAc,oBAAoB,EAClDsB,IAAQA,EAAO,UAAYA,EAAO,cAEtCa,GAAW,CACb,CAEA,SAASJ,EAAczB,EAAuB,CAC5C,IAAM8B,EAAS9B,EAAE,KAAK,KAAOF,EAAM,aAAe,aAAe,GAC3DiC,EAAY/B,EAAE,YAAc,mEAAqE,GACvG,MAAO;AAAA,wCAC6B8B,CAAM,mBAAmB9B,EAAE,KAAK,EAAE;AAAA,0FACgBA,EAAE,KAAK,EAAE;AAAA,YACvF+B,CAAS;AAAA,qDACgCnD,EAAWoB,EAAE,KAAK,KAAK,CAAC;AAAA,oDACzBA,EAAE,KAAK,OAAS,OAAS,OAAS,OAAO,SAAMb,GAAaa,EAAE,KAAK,SAAS,CAAC;AAAA;AAAA;AAAA,0DAGvEA,EAAE,KAAK,EAAE,oBAAoBzB,EAAW;AAAA,0DACxCyB,EAAE,KAAK,EAAE,oBAAoB1B,EAAU;AAAA;AAAA;AAAA,KAI/F,CAEA,SAASsD,GAA0B,CACjC,MAAO;AAAA;AAAA;AAAA;AAAA,uEAI4DvD,CAAS;AAAA;AAAA,KAG9E,CAEA,SAASsD,EAAeb,EAA0B,CAChD,IAAMkB,EAAQlB,EAAK,OAAS,CAAC,EACvBmB,EACHnB,EAAK,OAEFkB,EAAM,SAAW,EACf,yFACAA,EAAM,IAAKnB,GAAMqB,EAAWrB,CAAC,CAAC,EAAE,KAAK,EAAE,EAHzC,2DAKAsB,EACJH,EAAM,SAAW,EACb,kEACAlC,EAAM,OAAS,OACb,oDACA,2BAER,MAAO;AAAA,2DACgDmC,CAAU;AAAA;AAAA,oDAEjBvD,EAAWyD,CAAW,CAAC;AAAA,mEACRrB,EAAK,YAAc,WAAa,EAAE,IAAIA,EAAK,YAAc,gBAAa,MAAM;AAAA,UACrIA,EAAK,YAAc,0EAA4E,EAAE;AAAA;AAAA,KAGzG,CAEA,SAASoB,EAAWE,EAA0B,CAC5C,GAAIA,EAAK,OAAS,OAAQ,CACxB,IAAMC,EAAOD,EAAK,QAAUE,GAAYF,CAAI,EAC5C,MAAO;AAAA,qDACqCA,EAAK,OAAS,OAAS,OAAO;AAAA,4CACpCxD,EAAWyD,CAAI,CAAC;AAAA,aAExD,CAEA,IAAME,EACJH,EAAK,OAAO,SAAW,GAAKA,EAAK,SAAW,UACxC,2DACAA,EAAK,OAAO,IAAKI,GAAQC,EAAYD,EAAKJ,EAAK,MAAM,CAAC,EAAE,KAAK,EAAE,EAE/DM,EAAcN,EAAK,QAAUA,EAAK,SAAW,WAC/C,mCAAmCxD,EAAWwD,EAAK,MAAM,CAAC,SAC1D,GAEEO,EACJP,EAAK,aAAe,CAACrC,EAAW,GAAG,YAC/B;AAAA,qFAC2EqC,EAAK,EAAE;AAAA,kBAElF,GAEAQ,EAASR,EAAK,GAChBS,GAAST,EAAK,EAAE,EAChBA,EAAK,OACHU,EAAqBV,EAAK,MAAM,EAChC,GAEN,MAAO;AAAA,0DAC4CA,EAAK,OAAS,OAAS,OAAO,IAAIM,CAAW;AAAA,0CAC1DH,CAAU,GAAGI,CAAW,GAAGC,CAAM;AAAA,WAEzE,CAEA,SAASH,EAAYD,EAAoBO,EAAyB,CAChE,GAAIP,EAAI,OAAS,OAAQ,CACvB,IAAMH,EAAOG,EAAI,KAAK,KAAK,EAC3B,OAAKH,EAEE,+BADMU,EAASC,EAAeX,CAAI,EAAIY,EAAiBZ,CAAI,CACxB,SAFxB,EAGpB,CACA,OAAOa,EAAgBV,CAAG,CAC5B,CAEA,SAASS,EAAiBZ,EAAsB,CAK9C,OAAOzD,EAAWyD,CAAI,EACnB,MAAM,SAAS,EACf,IAAKc,GACJA,EACG,QAAQ,eAAgB,iBAAiB,EACzC,QAAQ,qBAAsB,qBAAqB,EAEnD,QAAQ,+BAAgC,CAACC,EAAIC,EAAevE,IAEpD,YADM,2BAA2B,KAAKA,CAAG,EAAIA,EAAM,GACnC,+CAA+CuE,CAAK,MAC5E,EAEA,QAAQ,uCAAwC,CAACD,EAAIE,EAAcxE,IAC3D,GAAGwE,CAAI,YAAYxE,CAAG,+CAA+CA,CAAG,MAChF,EACA,QAAQ,MAAO,QAAQ,CAC5B,EACC,IAAKyE,GAAM,MAAMA,CAAC,MAAM,EACxB,KAAK,EAAE,CACZ,CAEA,SAASL,EACPV,EACQ,CACR,IAAMgB,EAAUC,EAAcjB,EAAI,KAAMA,EAAI,IAAI,EAC1CkB,EAAW5D,EAAM,cAAc,IAAI0C,EAAI,EAAE,EACzCmB,EAAaD,EAAWE,EAAiBpB,CAAG,EAAI,GACtD,MAAO;AAAA,mDACwCA,EAAI,MAAM,mBAAmBA,EAAI,EAAE;AAAA,wFACEA,EAAI,EAAE,oBAAoBkB,CAAQ;AAAA;AAAA,YAE9GjF,EAAY;AAAA,gDACwBG,EAAW4D,EAAI,IAAI,CAAC;AAAA,YACxDgB,EAAU,0CAA0C5E,EAAW4E,CAAO,CAAC,UAAY,EAAE;AAAA;AAAA,UAEvFG,CAAU;AAAA;AAAA,KAGlB,CAEA,SAASF,EAAcI,EAAcC,EAAuB,CAC1D,GAAI,CAACA,GAAQ,OAAOA,GAAS,SAAU,MAAO,GAC9C,IAAMC,EAAID,EACJE,EAAQH,EAAK,YAAY,EAC/B,GAAIG,EAAM,SAAS,MAAM,EAAG,CAC1B,IAAMC,EAAU,OAAOF,EAAE,SAAY,SAAWA,EAAE,QAAU,OAAOA,EAAE,OAAU,SAAWA,EAAE,MAAQ,GACpG,OAAOE,EAAU,IAAIA,CAAO,IAAM,EACpC,CACA,GAAID,EAAM,SAAS,MAAM,GAAK,OAAOD,EAAE,MAAS,SAAU,CACxD,IAAMG,EACJ,OAAOH,EAAE,QAAW,UAAY,OAAOA,EAAE,OAAU,SAC/C,IAAIA,EAAE,QAAU,EAAE,IAAK,OAAOA,EAAE,QAAU,CAAC,EAAI,OAAOA,EAAE,OAAS,CAAC,GAAM,EAAE,GAC1E,GACN,MAAO,GAAGA,EAAE,IAAI,GAAGG,CAAK,EAC1B,CACA,OAAKF,EAAM,SAAS,MAAM,GAAKA,EAAM,SAAS,aAAa,IAAM,OAAOD,EAAE,cAAiB,SAClFA,EAAE,aAEPC,IAAU,SAAW,OAAOD,EAAE,SAAY,SACpCA,EAAE,QAAmB,MAAM,EAAG,EAAE,EAEtCC,IAAU,QAAU,OAAOD,EAAE,aAAgB,SACxCA,EAAE,YAEPC,EAAM,SAAS,OAAO,GAAK,OAAOD,EAAE,MAAS,UAG7CC,EAAM,SAAS,MAAM,GAAK,OAAOD,EAAE,MAAS,UAG5CC,EAAM,SAAS,QAAQ,GAAK,OAAOD,EAAE,MAAS,SACzCA,EAAE,KAEJ,EACT,CAEA,SAASH,EAAiBpB,EAAuD,CAC/E,IAAM2B,EACJ3B,EAAI,OAAS,QAAaA,EAAI,OAAS,KACnC,4FAA4F5D,EAAWwF,EAAW5B,EAAI,IAAI,CAAC,CAAC,eAC5H,GACA6B,EACJ7B,EAAI,SAAW,QAAaA,EAAI,SAAW,KACvC,8FAA8F5D,EAAWwF,EAAW5B,EAAI,MAAM,CAAC,CAAC,eAChIA,EAAI,SAAW,UACb,wJACA,GACR,MAAO,wCAAwC2B,CAAS,GAAGE,CAAW,QACxE,CAEA,SAASD,EAAWE,EAAwB,CAC1C,GAAI,OAAOA,GAAU,SAAU,OAAOA,EACtC,GAAI,CACF,OAAO,KAAK,UAAUA,EAAO,KAAM,CAAC,CACtC,MAAQ,CACN,OAAO,OAAOA,CAAK,CACrB,CACF,CAEA,SAASxB,EAAqByB,EAA4B,CAIxD,IAAMC,EAAO3F,EAAS0F,EAAO,OAAO,EACpC,MAAO;AAAA,+DACoD3F,EAAW2F,EAAO,MAAM,CAAC;AAAA;AAAA;AAAA,mDAGrC7F,EAAW6F,EAAO,UAAU,CAAC;AAAA,2CACrC7F,EAAW6F,EAAO,OAAO,CAAC,iDAAiD3F,EAAW4F,CAAI,CAAC;AAAA;AAAA,WAGpI,CAEA,SAAS3B,GAAS4B,EAAoB,CACpC,IAAMC,EAAQ5E,EAAM,YAAY,IAAI2E,EAAG,GAAG,GAAK,CAAE,MAAO,MAAgB,EACpEE,EACAC,EAAa,GACjB,OAAQF,EAAM,MAAO,CACnB,IAAK,UACHC,EAAc,+FACdC,EAAa,iGACb,MACF,IAAK,UACHD,EAAc,0EACdC,EAAa,+DAA+DhG,EAAW8F,EAAM,IAAI,MAAM,EAAG,CAAC,CAAC,CAAC,yDAC7G,MACF,IAAK,QACHC,EAAc,sEAAsEjG,EAAW+F,EAAG,GAAG,CAAC,6BACtGG,EAAa,6DAA6DhG,EAAW8F,EAAM,OAAO,CAAC,SACnG,MACF,QACEC,EAAc,sEAAsEjG,EAAW+F,EAAG,GAAG,CAAC,6BAC1G,CACA,MAAO;AAAA,8CACmCA,EAAG,MAAM,cAAc7F,EAAW6F,EAAG,KAAK,CAAC,IAAI7F,EAAW6F,EAAG,IAAI,CAAC;AAAA,QACxGA,EAAG,MAAQ,QAAQ7F,EAAW6F,EAAG,KAAK,CAAC,SAAW,EAAE;AAAA;AAAA,2CAEjB/F,EAAW+F,EAAG,GAAG,CAAC;AAAA,UACnDE,CAAW;AAAA;AAAA,QAEbC,CAAU;AAAA,WAEhB,CAEA,SAAStC,GAAYF,EAA0B,CAC7C,OAAOA,EAAK,OACT,OAAQyC,GAAqDA,EAAE,OAAS,MAAM,EAC9E,IAAKA,GAAMA,EAAE,IAAI,EACjB,KAAK;AAAA;AAAA,CAAM,CAChB,CAIA,SAAShD,IAAa,CACpBnC,EAAK,cAAc,qBAAqB,GAAG,iBAAiB,SAAWmF,GAAM,CAC3E/E,EAAM,MAAS+E,EAAE,OAA6B,KAChD,CAAC,EACDnF,EAAK,iBAAiB,aAAa,EAAE,QAASoF,GAAM,CAClDA,EAAE,iBAAiB,QAAS,IAAM,CAChChF,EAAM,KAAQgF,EAAkB,QAAQ,KACxC5D,EAAO,CACT,CAAC,CACH,CAAC,EACDxB,EAAK,cAAc,oBAAoB,GAAG,iBAAiB,SAAWmF,GAAM,CAC1E/E,EAAM,QAAW+E,EAAE,OAA4B,MAAM,KAAK,CAC5D,CAAC,EACDnF,EAAK,cAAc,sBAAsB,GAAG,iBAAiB,QAAS,SAAY,CAChF,MAAM,MAAM,GAAGD,CAAO,eAAgB,CAAE,OAAQ,OAAQ,YAAa,SAAU,CAAC,EAAE,MAAM,IAAM,CAAC,CAAC,EAChG,QAAWO,KAAKF,EAAM,MAAOE,EAAE,OAAO,MAAM,EAC5CF,EAAM,GAAK,KACXA,EAAM,MAAQ,CAAC,EACfA,EAAM,aAAe,KACrBoB,EAAO,CACT,CAAC,EACDxB,EAAK,cAAc,8BAA8B,GAAG,iBAAiB,QAAS,IAAM,CAClFI,EAAM,YAAc,CAACA,EAAM,YAC3BoB,EAAO,CACT,CAAC,EAEDxB,EAAK,iBAAiB,wBAAwB,EAAE,QAASoF,GACvDA,EAAE,iBAAiB,QAAS,SAAY,CAC5B,MAAM/D,EAAW,GAClB,OAAO,WAAa,MAAKjB,EAAM,YAAc,IACtDoB,EAAO,CACT,CAAC,CACH,EACAxB,EAAK,iBAAiB,2BAA2B,EAAE,QAASoF,GAC1DA,EAAE,iBAAiB,QAAS,IAAM,CAChC,IAAMC,EAAMD,EAAkB,QAAQ,OACtC7D,EAAW8D,CAAE,CACf,CAAC,CACH,EACArF,EAAK,iBAAiB,2BAA2B,EAAE,QAASoF,GAC1DA,EAAE,iBAAiB,QAAUD,GAAM,CACjCA,EAAE,gBAAgB,EAClBzD,EAAY0D,EAAkB,QAAQ,MAAO,CAC/C,CAAC,CACH,EACApF,EAAK,iBAAiB,2BAA2B,EAAE,QAASoF,GAC1DA,EAAE,iBAAiB,QAAUD,GAAM,CACjCA,EAAE,gBAAgB,EAClB1D,EAAY2D,EAAkB,QAAQ,MAAO,CAC/C,CAAC,CACH,EAEApF,EAAK,cAAc,oBAAoB,GAAG,iBAAiB,QAAS,IAAMsF,EAAO,CAAC,EAClFtF,EAAK,cAAc,sBAAsB,GAAG,iBAAiB,UAAYmF,GAAa,CACpF,IAAMI,EAAKJ,EACPI,EAAG,MAAQ,UAAYA,EAAG,SAAWA,EAAG,WAC1CA,EAAG,eAAe,EAClBD,EAAO,EAEX,CAAC,EACDtF,EAAK,cAAc,sBAAsB,GAAG,iBAAiB,QAAS,IAAMwF,GAAS,CAAC,EACtFxF,EAAK,iBAAiB,uBAAuB,EAAE,QAASoF,GACtDA,EAAE,iBAAiB,QAAS,IAAMK,GAAU,CAAC,CAC/C,EACAzF,EAAK,iBAAiB,qBAAqB,EAAE,QAASoF,GAAM,CAC1DA,EAAE,iBAAiB,QAAS,IAAMM,GAASN,EAAkB,QAAQ,EAAG,CAAC,CAC3E,CAAC,EAEDpF,EAAK,iBAAiB,2BAA2B,EAAE,QAASoF,GAC1DA,EAAE,iBAAiB,QAAS,IAAM,CAChC,IAAMC,EAAMD,EAAkB,QAAQ,OAClChF,EAAM,cAAc,IAAIiF,CAAE,EAAGjF,EAAM,cAAc,OAAOiF,CAAE,EACzDjF,EAAM,cAAc,IAAIiF,CAAE,EAC/B7D,EAAO,CACT,CAAC,CACH,CACF,CAIA,eAAe8D,GAAS,CACtB,IAAIlE,EAAOf,EAAW,EAKtB,GAJI,CAACe,IACHA,EAAO,MAAMC,EAAW,EACpB,CAACD,IAEHA,EAAK,YAAa,OAEtB,IAAMuE,EAAW3F,EAAK,cAAc,sBAAsB,EACpD4F,EAASD,GAAU,MAAM,KAAK,GAAK,GACzC,GAAI,CAACC,EAAQ,OACTD,IAAUA,EAAS,MAAQ,IAE/BvE,EAAK,MAAQA,EAAK,OAAS,CAAC,EAC5B,IAAMyE,EAAuB,CAC3B,GAAIrG,EAAe,EACnB,OAAQ4B,EAAK,KAAK,GAClB,IAAKA,EAAK,MAAM,OAChB,KAAM,OACN,OAAQhB,EAAM,OAAS,OACvB,OAAQ,WACR,OAAQ,CAAC,CAAE,KAAM,OAAQ,GAAIZ,EAAe,EAAG,KAAMoG,CAAO,CAAC,EAC7D,OAAAA,EACA,UAAW,KAAK,IAAI,CACtB,EACAxE,EAAK,MAAM,KAAKyE,CAAQ,EAExB,IAAMC,EAA4B,CAChC,GAAItG,EAAe,EACnB,OAAQ4B,EAAK,KAAK,GAClB,IAAKA,EAAK,MAAM,OAChB,KAAM,YACN,OAAQhB,EAAM,OAAS,OACvB,OAAQ,UACR,OAAQ,CAAC,EACT,UAAW,KAAK,IAAI,CACtB,EACAgB,EAAK,MAAM,KAAK0E,CAAa,EAC7B1E,EAAK,YAAc,GACfA,EAAK,KAAK,QAAU,aACtBA,EAAK,KAAO,CAAE,GAAGA,EAAK,KAAM,MAAOwE,EAAO,OAAS,GAAK,GAAGA,EAAO,MAAM,EAAG,EAAE,CAAC,SAAMA,CAAO,GAE7FxE,EAAK,KAAO,CAAE,GAAGA,EAAK,KAAM,KAAMhB,EAAM,KAAM,MAAOA,EAAM,MAAO,UAAW,KAAK,IAAI,CAAE,EACxFoB,EAAO,EAEP,GAAI,CACF,MAAMuE,EACJ,cACA,CACE,OAAQ3E,EAAK,KAAK,GAClB,OAAAwE,EACA,KAAMxF,EAAM,IACd,EACAgB,EACA0E,CACF,CACF,OAAS9E,EAAK,CACPA,EAAc,OAAS,cAC1BgF,EAAkBF,EAAe9E,CAAG,EAEtC8E,EAAc,OAAS,OACzB,QAAE,CACA1E,EAAK,YAAc,GACnBA,EAAK,YAAc,KACnBA,EAAK,MAAQ,KACbI,EAAO,CACT,CACF,CAEA,eAAeiE,IAAY,CACzB,IAAMrE,EAAOf,EAAW,EACxB,GAAI,CAACe,GAAQA,EAAK,YAAa,OAC/BA,EAAK,MAAQA,EAAK,OAAS,CAAC,EAC5B,IAAMsB,EAAmB,CACvB,GAAIlD,EAAe,EACnB,OAAQ4B,EAAK,KAAK,GAClB,IAAKA,EAAK,MAAM,OAChB,KAAM,YACN,OAAQ,GACR,OAAQ,UACR,OAAQ,CAAC,EACT,UAAW,KAAK,IAAI,CACtB,EACAA,EAAK,MAAM,KAAKsB,CAAI,EACpBtB,EAAK,YAAc,GACnBI,EAAO,EACP,GAAI,CACF,MAAMuE,EAAU,iBAAkB,CAAE,OAAQ3E,EAAK,KAAK,EAAG,EAAGA,EAAMsB,CAAI,CACxE,OAAS1B,EAAK,CACPA,EAAc,OAAS,cAC1BgF,EAAkBtD,EAAM1B,CAAG,EAE7B0B,EAAK,OAAS,OAChB,QAAE,CACAtB,EAAK,YAAc,GACnBA,EAAK,YAAc,KACnBA,EAAK,MAAQ,KACbI,EAAO,CACT,CACF,CAEA,eAAegE,IAAW,CACxB,IAAMpE,EAAOf,EAAW,EACxB,GAAKe,EACL,IAAIA,EAAK,YACP,GAAI,CACF,MAAMb,EAAI,gBAAiB,CACzB,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,OAAQa,EAAK,KAAK,GAAI,MAAOA,EAAK,WAAY,CAAC,CACxE,CAAC,CACH,OAASJ,EAAK,CACZ,QAAQ,MAAM,6BAA8BA,CAAG,CACjD,CAEFI,EAAK,OAAO,MAAM,EACpB,CAEA,eAAesE,GAAQO,EAAe,CACpC,IAAM7E,EAAOf,EAAW,EACxB,GAAKe,GACDhB,EAAM,YAAY,IAAI6F,CAAK,GAAG,QAAU,UAC5C,CAAA7F,EAAM,YAAY,IAAI6F,EAAO,CAAE,MAAO,SAAU,CAAC,EACjDzE,EAAO,EAEPJ,EAAK,MAAQA,EAAK,OAAS,CAAC,EAC5B,GAAI,CACF,IAAM8E,EAAS,MAAM3F,EAAsC,YAAa,CACtE,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,MAAA0F,EAAO,YAAa,QAAS,CAAC,CACvD,CAAC,EACGC,EAAO,QACT9F,EAAM,YAAY,IAAI6F,EAAO,CAAE,MAAO,UAAW,IAAKC,EAAO,GAAI,CAAC,EAClE9E,EAAK,MAAM,KAAK,CACd,GAAI5B,EAAe,EACnB,OAAQ4B,EAAK,KAAK,GAClB,IAAKA,EAAK,MAAM,OAChB,KAAM,YACN,OAAQ,GACR,OAAQ,WACR,OAAQ,CACN,CACE,KAAM,OACN,GAAI5B,EAAe,EACnB,KAAM,mBAAmB0G,EAAO,IAAI,MAAM,EAAG,CAAC,CAAC,4EACjD,CACF,EACA,UAAW,KAAK,IAAI,CACtB,CAAC,GAED9F,EAAM,YAAY,IAAI6F,EAAO,CAC3B,MAAO,QACP,QAAS,8BACX,CAAC,CAEL,OAASjF,EAAK,CACZ,IAAMmF,EAAUnF,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,EAC/DZ,EAAM,YAAY,IAAI6F,EAAO,CAAE,MAAO,QAAS,QAAAE,CAAQ,CAAC,CAC1D,QAAE,CACA3E,EAAO,CACT,EACF,CAEA,SAASwE,EAAkBtD,EAAkB1B,EAAoB,CAC/D,IAAMmF,EAAUnF,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,EAC/D0B,EAAK,OAAO,KAAK,CACf,KAAM,OACN,GAAIlD,EAAe,EACnB,KAAM,WAAW2G,CAAO,EAC1B,CAAC,CACH,CAIA,eAAeJ,EACbvF,EACA4F,EACAhF,EACAsB,EACe,CACftB,EAAK,MAAQ,IAAI,gBACjB,IAAMV,EAAO,MAAM,MAAM,GAAGX,CAAO,GAAGS,CAAI,GAAI,CAC5C,OAAQ,OACR,YAAa,UACb,QAAS,CAAE,eAAgB,mBAAoB,OAAQ,mBAAoB,EAC3E,KAAM,KAAK,UAAU4F,CAAI,EACzB,OAAQhF,EAAK,MAAM,MACrB,CAAC,EACD,GAAI,CAACV,EAAK,IAAM,CAACA,EAAK,KACpB,MAAM,IAAI,MAAM,GAAGA,EAAK,MAAM,IAAIA,EAAK,UAAU,EAAE,EAErD,IAAM2F,EAAS3F,EAAK,KAAK,UAAU,EAC7B4F,GAAU,IAAI,YAChBC,EAAS,GAGTC,EAAc,GAElB,OAAa,CACX,GAAM,CAAE,MAAA5B,GAAO,KAAA6B,EAAK,EAAI,MAAMJ,EAAO,KAAK,EAC1C,GAAII,GAAM,MACVF,GAAUD,GAAQ,OAAO1B,GAAO,CAAE,OAAQ,EAAK,CAAC,EAChD,IAAM8B,EAASH,EAAO,MAAM;AAAA;AAAA,CAAM,EAClCA,EAASG,EAAO,IAAI,GAAK,GACzB,QAAWC,MAAOD,EAAQ,CACxB,IAAME,EAAWD,GAAI,MAAM;AAAA,CAAI,EAAE,KAAME,GAAMA,EAAE,WAAW,QAAQ,CAAC,EACnE,GAAKD,EACL,GAAI,CACF,IAAM9D,EAAM,KAAK,MAAM8D,EAAS,MAAM,CAAC,CAAC,EACxCJ,EAAcM,GAAiBhE,EAAK1B,EAAMsB,EAAM8D,CAAW,EAC3DhF,EAAO,CACT,OAAS2D,EAAG,CACV,QAAQ,KAAK,6BAA8BA,CAAC,CAC9C,CACF,CACF,CACF,CAOA,SAAS2B,GACPhE,EACA1B,EACAsB,EACA8D,EACS,CACT,OAAQ1D,EAAI,KAAM,CAChB,IAAK,QACH,OAAA1B,EAAK,YAAc0B,EAAI,MAChB,GACT,IAAK,OAAQ,CAMX,IAAMiE,EAAOrE,EAAK,OAAOA,EAAK,OAAO,OAAS,CAAC,EAC/C,OAAI8D,GAAeO,GAAQA,EAAK,OAAS,OAEvCA,EAAK,MAAQjE,EAAI,KAEjBJ,EAAK,OAAO,KAAK,CACf,KAAM,OACN,GAAIlD,EAAe,EACnB,KAAMsD,EAAI,IACZ,CAAC,EAEI,EACT,CACA,IAAK,WACH,OAAO0D,EACT,IAAK,OAAQ,CACX,IAAM3F,EAAW6B,EAAK,OAAO,KAC1ByC,GACCA,EAAE,OAAS,QAAUA,EAAE,SAAWrC,EAAI,MAC1C,EACA,OAAIjC,GACFA,EAAS,OAASiC,EAAI,OAClBA,EAAI,OAAS,SAAWjC,EAAS,KAAOiC,EAAI,MAC5CA,EAAI,SAAW,SAAWjC,EAAS,OAASiC,EAAI,SAEpDJ,EAAK,OAAO,KAAK,CACf,KAAM,OACN,GAAIlD,EAAe,EACnB,OAAQsD,EAAI,OACZ,KAAMA,EAAI,KACV,OAAQA,EAAI,OACZ,KAAMA,EAAI,KACV,OAAQA,EAAI,MACd,CAAC,EAEI,EACT,CACA,IAAK,SACH,OAAO0D,EACT,IAAK,SACH,OAAA9D,EAAK,OAASI,EAAI,OACdA,EAAI,KAAIJ,EAAK,GAAKI,EAAI,IACtBA,EAAI,QAAU,CAACA,EAAI,KAAIJ,EAAK,OAASI,EAAI,QACzCJ,EAAK,QAAUI,EAAI,SAAW,aAAYJ,EAAK,YAAc,IACjEtB,EAAK,YAAc,KACZ,GACT,IAAK,QACH,OAAAsB,EAAK,OAAO,KAAK,CACf,KAAM,OACN,GAAIlD,EAAe,EACnB,KAAM,WAAWsD,EAAI,OAAO,EAC9B,CAAC,EACDJ,EAAK,OAAS,QACdtB,EAAK,YAAc,KACZ,EACX,CACF,CAIA,eAAe4F,IAAY,CACzB,GAAI,CACF,IAAMC,EAAK,MAAM1G,EAAgB,UAAU,EAC3CH,EAAM,GAAK6G,EACPA,EAAG,iBACL7G,EAAM,QAAU6G,EAAG,eACnB7G,EAAM,WAAa,GAEvB,OAASY,EAAK,CACZ,IAAMkG,EAAUlG,EAA4B,OAC5C,GAAIkG,IAAW,KAAOA,IAAW,IAAK,CACpC9G,EAAM,GAAK,KACXoB,EAAO,EACP,MACF,CACA,QAAQ,MAAM,+BAAgCR,CAAG,EACjDZ,EAAM,GAAK,KACXoB,EAAO,EACP,MACF,CACA,GAAI,CACF,GAAM,CAAE,OAAA2F,CAAO,EAAI,MAAM5G,EAA+B,SAAS,EACjEH,EAAM,OAAS+G,EACXA,EAAO,OAAS,GAAK,CAACA,EAAO,KAAM9H,GAAMA,EAAE,SAAWe,EAAM,KAAK,IACnEA,EAAM,MAAQ+G,EAAO,CAAC,EAAG,OAE7B,OAASnG,EAAK,CACZ,QAAQ,MAAM,8BAA+BA,CAAG,EAChDZ,EAAM,OAAS,CACb,CAAE,OAAQ,SAAU,cAAe,OAAQ,YAAa,mBAAoB,EAC5E,CAAE,OAAQ,OAAQ,cAAe,OAAQ,YAAa,iBAAkB,CAC1E,CACF,CACA,MAAMO,EAAa,EACfP,EAAM,MAAM,OAAS,GAAK,CAACA,EAAM,eACnCA,EAAM,aAAeA,EAAM,MAAM,CAAC,EAAG,KAAK,GAC1Ca,EAAiBb,EAAM,YAAY,EAAE,KAAK,IAAMoB,EAAO,CAAC,GAE1DA,EAAO,CACT,CAEA,OAAAwF,GAAU,EAEH,CACL,SAAU,CACR,QAAW1G,KAAKF,EAAM,MAAOE,EAAE,OAAO,MAAM,EAC5CT,EAAG,YAAYG,CAAI,CACrB,CACF,CACF,CAEI,OAAO,OAAW,MACnB,OAAgF,UAAY,CAC3F,eAAAJ,CACF","names":["tab_exports","__export","mountCodingTab","escapeHtml","s","c","applyInline","text","segments","codeRe","lastIndex","m","applyInlineRest","_match","label","url","flushParagraph","buf","out","joined","flushList","stack","ctx","renderMarkdown","input","lines","paragraph","listStack","inCode","codeLang","codeBuf","closeOpenBlocks","i","line","fence","cls","h","level","ul","ol","indent","type","content","prev","html","top","ICON_GITHUB","ICON_PLUS","ICON_TRASH","ICON_PENCIL","ICON_MENU","ICON_CHEVRON","escapeAttr","s","escapeHtml","repoSlug","url","m","repoWebUrl","githubPullsUrl","cryptoRandomId","relativeTime","ts","diff","mountCodingTab","el","options","apiBase","root","stylesheetInjected","injectStylesheet","link","state","activeChat","c","api","path","init","resp","loadChatList","chats","existing","meta","prev","err","ensureChatLoaded","chatId","t","chat","createChat","thread","switchChat","render","deleteChat","renameChat","next","trimmed","headerHtml","sidebarHtml","renderChatRow","paneHtml","renderChatPane","renderEmptyPane","bindEvents","active","streaming","turns","threadHtml","renderTurn","placeholder","turn","text","extractText","eventsHtml","evt","renderEvent","statusBadge","planActions","prHtml","renderPr","renderBranchFallback","isPlan","renderMarkdown","renderInlineText","renderToolEvent","para","_m","label","lead","p","summary","summarizeTool","expanded","detailHtml","renderToolDetail","name","args","a","lower","pattern","lines","argsBlock","formatBlob","resultBlock","value","branch","slug","pr","merge","mergeButton","statusLine","e","b","id","onSend","ev","onCancel","onExecute","onMerge","promptEl","prompt","userTurn","assistantTurn","streamSse","appendErrorToTurn","prUrl","result","message","body","reader","decoder","buffer","lastWasText","done","events","raw","dataLine","l","applyStreamEvent","last","bootstrap","me","status","models"]}
1
+ {"version":3,"sources":["../src/client/tab.ts","../src/client/markdown.ts"],"sourcesContent":["import { escapeHtml, renderMarkdown } from \"./markdown.js\";\nimport type {\n BranchInfo,\n ChatListItem,\n ChatMode,\n FullChat,\n MeResponse,\n ModelChoice,\n ModelOption,\n PrInfo,\n StoredChat,\n StoredTurn,\n StreamEvent,\n TimelineEvent,\n TurnStatus,\n} from \"../shared/types.js\";\n\ninterface MountOptions {\n apiBase: string;\n defaultRepo?: string;\n defaultRef?: string;\n defaultMode?: ChatMode;\n defaultModel?: ModelChoice;\n}\n\ninterface MountHandle {\n destroy(): void;\n}\n\n/**\n * Visible state of a \"Merge & Redeploy\" button. Stored per PR-url so the\n * feedback survives re-renders (which happen frequently during streaming) and\n * so multiple PRs in the same chat can have independent states.\n */\ntype MergeState =\n | { state: \"idle\" }\n | { state: \"loading\" }\n | { state: \"success\"; sha: string }\n | { state: \"error\"; message: string };\n\n/** Client-side ephemeral wrapper around a `StoredChat`. */\ninterface ChatThread {\n meta: ChatListItem;\n /** Loaded turns (after `/chats/:id` fetch). Undefined means not yet hydrated. */\n turns?: StoredTurn[];\n loaded: boolean;\n isStreaming: boolean;\n activeRunId: string | null;\n abort: AbortController | null;\n /**\n * True while we lost the SSE stream (typically because mobile Safari/iOS\n * suspended the page) and are polling `/chats/:id` to catch up. The agent\n * is still running on Cursor's side; we just can't see live deltas.\n */\n isReconnecting: boolean;\n /** ID of the assistant turn we're polling for, so we know when to stop. */\n pollTurnId: string | null;\n /** Resolves when the active poller exits, used so callers can await it. */\n pollPromise: Promise<void> | null;\n}\n\nconst ICON_GITHUB = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M8 0C3.58 0 0 3.58 0 8a8 8 0 005.47 7.59c.4.07.55-.17.55-.38v-1.34c-2.23.48-2.7-1.07-2.7-1.07-.36-.92-.89-1.16-.89-1.16-.73-.5.05-.49.05-.49.81.06 1.23.83 1.23.83.72 1.23 1.88.87 2.34.66.07-.52.28-.87.5-1.07-1.78-.2-3.65-.89-3.65-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.13 0 0 .67-.21 2.2.82a7.65 7.65 0 014 0c1.53-1.04 2.2-.82 2.2-.82.44 1.11.16 1.93.08 2.13.51.56.82 1.28.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.74.54 1.49v2.21c0 .21.15.46.55.38A8 8 0 0016 8c0-4.42-3.58-8-8-8z\"/></svg>`;\n\nconst ICON_PLUS = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\"><path d=\"M8 3v10M3 8h10\"/></svg>`;\nconst ICON_TRASH = `<svg width=\"13\" height=\"13\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\"><path d=\"M3 4h10M6 4V2.5a.5.5 0 01.5-.5h3a.5.5 0 01.5.5V4M5 4l.7 9.1a.9.9 0 00.9.9h2.8a.9.9 0 00.9-.9L11 4\"/></svg>`;\nconst ICON_PENCIL = `<svg width=\"13\" height=\"13\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M11 2.5l2.5 2.5L6 12.5 3 13l.5-3z\"/></svg>`;\nconst ICON_MENU = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"><path d=\"M2.5 4h11M2.5 8h11M2.5 12h11\"/></svg>`;\nconst ICON_CHEVRON = `<svg class=\"ct-chevron\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 2l4 3-4 3\"/></svg>`;\n\nfunction escapeAttr(s: string): string {\n return escapeHtml(s);\n}\n\nfunction repoSlug(url: string): string {\n const m = url.match(/github\\.com[/:]([^/]+)\\/([^/]+?)(?:\\.git)?\\/?$/);\n return m ? `${m[1]}/${m[2]}` : url;\n}\n\nfunction repoWebUrl(url: string): string {\n // Strip a trailing `.git` and SSH-style `git@github.com:owner/repo.git` so\n // the URL is safe to drop into an `<a href>`.\n const m = url.match(/github\\.com[/:]([^/]+)\\/([^/]+?)(?:\\.git)?\\/?$/);\n return m ? `https://github.com/${m[1]}/${m[2]}` : url.replace(/\\.git\\/?$/, \"\");\n}\n\nfunction githubPullsUrl(url: string): string {\n return `${repoWebUrl(url)}/pulls`;\n}\n\nfunction cryptoRandomId(): string {\n return typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : Math.random().toString(36).slice(2);\n}\n\nfunction relativeTime(ts: number): string {\n const diff = Date.now() - ts;\n if (diff < 60_000) return \"just now\";\n if (diff < 3_600_000) return `${Math.round(diff / 60_000)}m`;\n if (diff < 86_400_000) return `${Math.round(diff / 3_600_000)}h`;\n if (diff < 604_800_000) return `${Math.round(diff / 86_400_000)}d`;\n return new Date(ts).toLocaleDateString();\n}\n\nexport function mountCodingTab(el: HTMLElement, options: MountOptions): MountHandle {\n const apiBase = options.apiBase.replace(/\\/$/, \"\");\n const root = document.createElement(\"div\");\n root.className = \"coding-tab\";\n el.innerHTML = \"\";\n el.appendChild(root);\n\n let stylesheetInjected = false;\n function injectStylesheet() {\n if (stylesheetInjected) return;\n stylesheetInjected = true;\n if (document.querySelector(`link[data-coding-tab=\"style\"]`)) return;\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = `${apiBase}/style.css`;\n link.dataset.codingTab = \"style\";\n document.head.appendChild(link);\n }\n injectStylesheet();\n\n const state: {\n me: MeResponse | null;\n models: ModelOption[];\n mode: ChatMode;\n model: ModelChoice;\n repoUrl: string;\n repoLocked: boolean;\n chats: ChatThread[];\n activeChatId: string | null;\n sidebarOpen: boolean;\n /** Tracks expanded tool events across re-renders (keyed by `event.id`). */\n expandedTools: Set<string>;\n /** Per-PR merge state so the button shows loading/success/error feedback. */\n mergeStates: Map<string, MergeState>;\n } = {\n me: null,\n models: [],\n mode: options.defaultMode ?? \"plan\",\n model: options.defaultModel ?? \"sonnet\",\n repoUrl: options.defaultRepo ?? \"\",\n repoLocked: false,\n chats: [],\n activeChatId: null,\n sidebarOpen: window.innerWidth >= 720,\n expandedTools: new Set(),\n mergeStates: new Map(),\n };\n\n function activeChat(): ChatThread | null {\n return state.chats.find((c) => c.meta.id === state.activeChatId) ?? null;\n }\n\n async function api<T = unknown>(path: string, init?: RequestInit): Promise<T> {\n const resp = await fetch(`${apiBase}${path}`, {\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\", ...(init?.headers ?? {}) },\n ...init,\n });\n if (!resp.ok)\n throw Object.assign(new Error(`${resp.status} ${resp.statusText}`), { status: resp.status });\n return (await resp.json()) as T;\n }\n\n // ───────── chat lifecycle ─────────\n\n async function loadChatList(): Promise<void> {\n try {\n const { chats } = await api<{ chats: ChatListItem[] }>(\"/chats\");\n // Preserve runtime state for chats we already had.\n const existing = new Map(state.chats.map((c) => [c.meta.id, c]));\n state.chats = chats.map((meta) => {\n const prev = existing.get(meta.id);\n return prev\n ? { ...prev, meta }\n : {\n meta,\n loaded: false,\n isStreaming: false,\n activeRunId: null,\n abort: null,\n isReconnecting: false,\n pollTurnId: null,\n pollPromise: null,\n };\n });\n } catch (err) {\n console.error(\"[coding-tab] /chats list failed\", err);\n state.chats = [];\n }\n }\n\n async function ensureChatLoaded(chatId: string): Promise<ChatThread | null> {\n const t = state.chats.find((c) => c.meta.id === chatId);\n if (!t) return null;\n if (t.loaded) return t;\n try {\n const { chat } = await api<{ chat: FullChat }>(`/chats/${chatId}`);\n t.turns = chat.turns;\n t.loaded = true;\n t.meta = {\n id: chat.id,\n title: chat.title,\n mode: chat.mode,\n model: chat.model,\n createdAt: chat.createdAt,\n updatedAt: chat.updatedAt,\n };\n // Sync composer state to whatever this chat last used.\n state.mode = chat.mode;\n state.model = chat.model;\n return t;\n } catch (err) {\n console.error(`[coding-tab] /chats/${chatId} load failed`, err);\n return null;\n }\n }\n\n async function createChat(): Promise<ChatThread | null> {\n try {\n const { chat } = await api<{ chat: StoredChat }>(\"/chats\", {\n method: \"POST\",\n body: JSON.stringify({\n mode: state.mode,\n model: state.model,\n repoUrl: state.repoLocked ? undefined : state.repoUrl || undefined,\n }),\n });\n const thread: ChatThread = {\n meta: {\n id: chat.id,\n title: chat.title,\n mode: chat.mode,\n model: chat.model,\n createdAt: chat.createdAt,\n updatedAt: chat.updatedAt,\n },\n turns: [],\n loaded: true,\n isStreaming: false,\n activeRunId: null,\n abort: null,\n isReconnecting: false,\n pollTurnId: null,\n pollPromise: null,\n };\n state.chats.unshift(thread);\n state.activeChatId = thread.meta.id;\n return thread;\n } catch (err) {\n console.error(\"[coding-tab] create chat failed\", err);\n return null;\n }\n }\n\n async function switchChat(chatId: string): Promise<void> {\n state.activeChatId = chatId;\n if (window.innerWidth < 720) state.sidebarOpen = false;\n render();\n await ensureChatLoaded(chatId);\n render();\n }\n\n async function deleteChat(chatId: string): Promise<void> {\n const thread = state.chats.find((c) => c.meta.id === chatId);\n if (!thread) return;\n if (!confirm(`Delete chat \"${thread.meta.title}\"? This cannot be undone.`)) return;\n thread.abort?.abort();\n try {\n await api(`/chats/${chatId}`, { method: \"DELETE\" });\n } catch (err) {\n console.error(`[coding-tab] delete chat ${chatId} failed`, err);\n return;\n }\n state.chats = state.chats.filter((c) => c.meta.id !== chatId);\n if (state.activeChatId === chatId) {\n state.activeChatId = state.chats[0]?.meta.id ?? null;\n }\n render();\n }\n\n async function renameChat(chatId: string): Promise<void> {\n const thread = state.chats.find((c) => c.meta.id === chatId);\n if (!thread) return;\n const next = prompt(\"Rename chat\", thread.meta.title);\n if (next === null) return;\n const trimmed = next.trim();\n if (!trimmed || trimmed === thread.meta.title) return;\n try {\n const { chat } = await api<{ chat: StoredChat }>(`/chats/${chatId}`, {\n method: \"PATCH\",\n body: JSON.stringify({ title: trimmed }),\n });\n thread.meta = { ...thread.meta, title: chat.title, updatedAt: chat.updatedAt };\n render();\n } catch (err) {\n console.error(`[coding-tab] rename chat ${chatId} failed`, err);\n }\n }\n\n // ───────── render ─────────\n\n function render() {\n if (!state.me) {\n root.innerHTML = `\n <div class=\"coding-tab__signin\">\n <h2>Coding Tab</h2>\n <p>Sign in with GitHub to start a coding session against your repo. Your token is used to clone, push, and open pull requests on your behalf.</p>\n <a href=\"${apiBase}/auth/login\">${ICON_GITHUB}<span>Sign in with GitHub</span></a>\n </div>\n `;\n return;\n }\n\n const headerHtml = `\n <div class=\"coding-tab__header\">\n <button class=\"coding-tab__hamburger\" data-role=\"toggle-sidebar\" aria-label=\"Toggle chat list\">${ICON_MENU}</button>\n <select data-role=\"model\" title=\"Model\">\n ${state.models\n .map(\n (m) =>\n `<option value=\"${m.choice}\" ${state.model === m.choice ? \"selected\" : \"\"}>${escapeHtml(m.displayName)}</option>`,\n )\n .join(\"\")}\n </select>\n <div class=\"coding-tab__mode\">\n <button data-mode=\"plan\" class=\"${state.mode === \"plan\" ? \"is-active\" : \"\"}\">Plan</button>\n <button data-mode=\"agent\" class=\"${state.mode === \"agent\" ? \"is-active\" : \"\"}\">Agent</button>\n </div>\n ${\n state.repoLocked && state.repoUrl\n ? `<a class=\"coding-tab__repo-locked\" href=\"${escapeAttr(githubPullsUrl(state.repoUrl))}\" target=\"_blank\" rel=\"noreferrer\" title=\"Open ${escapeAttr(repoSlug(state.repoUrl))} pull requests on GitHub\">${ICON_GITHUB}<span>${escapeHtml(repoSlug(state.repoUrl))}</span></a>`\n : `<input data-role=\"repo\" placeholder=\"https://github.com/org/repo\" value=\"${escapeAttr(state.repoUrl)}\" style=\"flex:1;min-width:200px\" />`\n }\n <div class=\"coding-tab__user\">\n ${state.me.avatarUrl ? `<img src=\"${state.me.avatarUrl}\" alt=\"\" />` : \"\"}\n <span class=\"coding-tab__user-name\">@${escapeHtml(state.me.githubLogin)}</span>\n <button data-role=\"logout\">Sign out</button>\n </div>\n </div>\n `;\n\n const sidebarHtml = `\n <aside class=\"coding-tab__sidebar ${state.sidebarOpen ? \"is-open\" : \"\"}\">\n <button class=\"coding-tab__btn primary coding-tab__new-chat\" data-role=\"new-chat\">${ICON_PLUS}<span>New chat</span></button>\n <div class=\"coding-tab__chat-list\">\n ${\n state.chats.length === 0\n ? `<div class=\"coding-tab__chat-empty\">No chats yet — start one with the button above.</div>`\n : state.chats.map((c) => renderChatRow(c)).join(\"\")\n }\n </div>\n </aside>\n `;\n\n const chat = activeChat();\n const paneHtml = `\n <main class=\"coding-tab__pane\">\n ${chat ? renderChatPane(chat) : renderEmptyPane()}\n </main>\n `;\n\n root.innerHTML = `\n ${headerHtml}\n <div class=\"coding-tab__body\">\n ${sidebarHtml}\n ${paneHtml}\n </div>\n `;\n\n const thread = root.querySelector(\"[data-role=thread]\") as HTMLElement | null;\n if (thread) thread.scrollTop = thread.scrollHeight;\n\n bindEvents();\n }\n\n function renderChatRow(c: ChatThread): string {\n const active = c.meta.id === state.activeChatId ? \" is-active\" : \"\";\n const streaming = c.isStreaming\n ? `<span class=\"coding-tab__chat-row-dot\" title=\"${c.isReconnecting ? \"Reconnecting\" : \"Streaming\"}\"></span>`\n : \"\";\n return `\n <div class=\"coding-tab__chat-row${active}\" data-chat-id=\"${c.meta.id}\">\n <button class=\"coding-tab__chat-row-main\" data-role=\"select-chat\" data-chat-id=\"${c.meta.id}\">\n ${streaming}\n <span class=\"coding-tab__chat-row-title\">${escapeHtml(c.meta.title)}</span>\n <span class=\"coding-tab__chat-row-meta\">${c.meta.mode === \"plan\" ? \"Plan\" : \"Agent\"} · ${relativeTime(c.meta.updatedAt)}</span>\n </button>\n <div class=\"coding-tab__chat-row-actions\">\n <button data-role=\"rename-chat\" data-chat-id=\"${c.meta.id}\" title=\"Rename\">${ICON_PENCIL}</button>\n <button data-role=\"delete-chat\" data-chat-id=\"${c.meta.id}\" title=\"Delete\">${ICON_TRASH}</button>\n </div>\n </div>\n `;\n }\n\n function renderEmptyPane(): string {\n return `\n <div class=\"coding-tab__pane-empty\">\n <h3>Start a new chat</h3>\n <p>Pick a mode (Plan or Agent), choose a model, and click <strong>New chat</strong> to begin. Each chat keeps its own context.</p>\n <button class=\"coding-tab__btn primary\" data-role=\"new-chat\">${ICON_PLUS}<span>New chat</span></button>\n </div>\n `;\n }\n\n function renderChatPane(chat: ChatThread): string {\n const turns = chat.turns ?? [];\n const threadHtml =\n !chat.loaded\n ? `<div class=\"coding-tab__notice info\">Loading…</div>`\n : turns.length === 0\n ? `<div class=\"coding-tab__notice info\">Type a message below to kick off this chat.</div>`\n : turns.map((t) => renderTurn(t)).join(\"\");\n\n const placeholder =\n turns.length === 0\n ? `Ask anything (e.g. \"add dark mode that follows the OS setting\")`\n : state.mode === \"plan\"\n ? \"Refine the plan, or ask another planning question\"\n : \"Send another instruction\";\n\n const sendLabel = chat.isReconnecting\n ? \"Reconnecting…\"\n : chat.isStreaming\n ? \"Working…\"\n : \"Send\";\n\n return `\n <div class=\"coding-tab__thread\" data-role=\"thread\">${threadHtml}</div>\n <div class=\"coding-tab__composer\">\n <textarea data-role=\"prompt\" placeholder=\"${escapeAttr(placeholder)}\" rows=\"2\"></textarea>\n <button class=\"coding-tab__btn primary\" data-role=\"send\" ${chat.isStreaming ? \"disabled\" : \"\"}>${escapeHtml(sendLabel)}</button>\n ${chat.isStreaming ? `<button class=\"coding-tab__btn danger\" data-role=\"cancel\">Stop</button>` : \"\"}\n </div>\n `;\n }\n\n function renderTurn(turn: StoredTurn): string {\n if (turn.role === \"user\") {\n const text = turn.prompt ?? extractText(turn);\n return `<div class=\"coding-tab__msg user\">\n <div class=\"coding-tab__msg-role\">You · ${turn.isPlan ? \"Plan\" : \"Agent\"}</div>\n <div class=\"coding-tab__msg-body\">${escapeHtml(text)}</div>\n </div>`;\n }\n\n const isLatest = activeChat()?.turns?.at(-1)?.id === turn.id;\n const reconnecting = isLatest && activeChat()?.isReconnecting === true;\n const reconnectingHtml = reconnecting\n ? `<div class=\"coding-tab__msg-pending coding-tab__msg-reconnecting\">Reconnecting… (the page lost the live stream — the agent is still working on Cursor's side, fetching latest progress)</div>`\n : \"\";\n const eventsHtml =\n turn.events.length === 0 && turn.status === \"running\"\n ? `<div class=\"coding-tab__msg-pending\">Working…</div>`\n : turn.events.map((evt) => renderEvent(evt, turn.isPlan)).join(\"\");\n\n const statusBadge = turn.status && turn.status !== \"finished\"\n ? `<div class=\"coding-tab__status\">${escapeHtml(turn.status)}</div>`\n : \"\";\n\n const planActions =\n turn.showExecute && !activeChat()?.isStreaming\n ? `<div class=\"coding-tab__plan-actions\">\n <button class=\"coding-tab__btn primary\" data-role=\"execute\" data-turn=\"${turn.id}\">Execute plan</button>\n </div>`\n : \"\";\n\n const prHtml = turn.pr\n ? renderPr(turn.pr)\n : turn.branch\n ? renderBranchFallback(turn.branch)\n : \"\";\n\n return `<div class=\"coding-tab__msg assistant\">\n <div class=\"coding-tab__msg-role\">Coding Tab · ${turn.isPlan ? \"Plan\" : \"Agent\"} ${statusBadge}</div>\n <div class=\"coding-tab__msg-body\">${eventsHtml}${reconnectingHtml}${planActions}${prHtml}</div>\n </div>`;\n }\n\n function renderEvent(evt: TimelineEvent, isPlan: boolean): string {\n if (evt.kind === \"text\") {\n const text = evt.text.trim();\n if (!text) return \"\";\n const html = isPlan ? renderMarkdown(text) : renderInlineText(text);\n return `<div class=\"coding-tab__md\">${html}</div>`;\n }\n return renderToolEvent(evt);\n }\n\n function renderInlineText(text: string): string {\n // Lightweight rendering for Agent-mode messages: keep paragraph breaks,\n // inline `code`, **bold**, [text](url) markdown links, and bare-URL\n // autolinks. We deliberately skip headings/lists so the model's casual\n // streaming output doesn't get over-formatted.\n return escapeHtml(text)\n .split(/\\n\\s*\\n/)\n .map((para) =>\n para\n .replace(/`([^`\\n]+)`/g, \"<code>$1</code>\")\n .replace(/\\*\\*([^*\\n]+)\\*\\*/g, \"<strong>$1</strong>\")\n // Markdown links first so we don't double-link the URL inside.\n .replace(/\\[([^\\]\\n]+)\\]\\(([^)\\s]+)\\)/g, (_m, label: string, url: string) => {\n const safe = /^(https?:|mailto:|\\/|#)/i.test(url) ? url : \"#\";\n return `<a href=\"${safe}\" target=\"_blank\" rel=\"noreferrer noopener\">${label}</a>`;\n })\n // Then autolink bare URLs that aren't already inside an href=\"...\".\n .replace(/(^|[\\s(])(https?:\\/\\/[^\\s<>\"')\\]]+)/g, (_m, lead: string, url: string) => {\n return `${lead}<a href=\"${url}\" target=\"_blank\" rel=\"noreferrer noopener\">${url}</a>`;\n })\n .replace(/\\n/g, \"<br />\"),\n )\n .map((p) => `<p>${p}</p>`)\n .join(\"\");\n }\n\n function renderToolEvent(\n evt: Extract<TimelineEvent, { kind: \"tool\" }>,\n ): string {\n const summary = summarizeTool(evt.name, evt.args);\n const expanded = state.expandedTools.has(evt.id);\n const detailHtml = expanded ? renderToolDetail(evt) : \"\";\n return `\n <div class=\"coding-tab__tool\" data-status=\"${evt.status}\" data-tool-id=\"${evt.id}\">\n <button class=\"coding-tab__tool-header\" data-role=\"toggle-tool\" data-tool-id=\"${evt.id}\" aria-expanded=\"${expanded}\">\n <span class=\"coding-tab__tool-dot\"></span>\n ${ICON_CHEVRON}\n <span class=\"coding-tab__tool-name\">${escapeHtml(evt.name)}</span>\n ${summary ? `<span class=\"coding-tab__tool-summary\">${escapeHtml(summary)}</span>` : \"\"}\n </button>\n ${detailHtml}\n </div>\n `;\n }\n\n function summarizeTool(name: string, args: unknown): string {\n if (!args || typeof args !== \"object\") return \"\";\n const a = args as Record<string, unknown>;\n const lower = name.toLowerCase();\n if (lower.includes(\"grep\")) {\n const pattern = typeof a.pattern === \"string\" ? a.pattern : typeof a.query === \"string\" ? a.query : \"\";\n return pattern ? `\"${pattern}\"` : \"\";\n }\n if (lower.includes(\"read\") && typeof a.path === \"string\") {\n const lines =\n typeof a.offset === \"number\" || typeof a.limit === \"number\"\n ? `:${a.offset ?? \"\"}-${(Number(a.offset ?? 0) + Number(a.limit ?? 0)) || \"\"}`\n : \"\";\n return `${a.path}${lines}`;\n }\n if ((lower.includes(\"glob\") || lower.includes(\"file_search\")) && typeof a.glob_pattern === \"string\") {\n return a.glob_pattern as string;\n }\n if (lower === \"shell\" && typeof a.command === \"string\") {\n return (a.command as string).slice(0, 80);\n }\n if (lower === \"task\" && typeof a.description === \"string\") {\n return a.description as string;\n }\n if (lower.includes(\"write\") && typeof a.path === \"string\") {\n return a.path as string;\n }\n if (lower.includes(\"edit\") && typeof a.path === \"string\") {\n return a.path as string;\n }\n if (lower.includes(\"delete\") && typeof a.path === \"string\") {\n return a.path as string;\n }\n return \"\";\n }\n\n function renderToolDetail(evt: Extract<TimelineEvent, { kind: \"tool\" }>): string {\n const argsBlock =\n evt.args !== undefined && evt.args !== null\n ? `<div class=\"coding-tab__tool-section\"><div class=\"coding-tab__tool-label\">Args</div><pre>${escapeHtml(formatBlob(evt.args))}</pre></div>`\n : \"\";\n const resultBlock =\n evt.result !== undefined && evt.result !== null\n ? `<div class=\"coding-tab__tool-section\"><div class=\"coding-tab__tool-label\">Result</div><pre>${escapeHtml(formatBlob(evt.result))}</pre></div>`\n : evt.status === \"running\"\n ? `<div class=\"coding-tab__tool-section\"><div class=\"coding-tab__tool-label\">Result</div><pre class=\"coding-tab__tool-pending\">…running</pre></div>`\n : \"\";\n return `<div class=\"coding-tab__tool-detail\">${argsBlock}${resultBlock}</div>`;\n }\n\n function formatBlob(value: unknown): string {\n if (typeof value === \"string\") return value;\n try {\n return JSON.stringify(value, null, 2);\n } catch {\n return String(value);\n }\n }\n\n function renderBranchFallback(branch: BranchInfo): string {\n // Surfaced when the agent pushed commits but Cursor's auto-PR step didn't\n // create a PR (typically because of an account/team-level setting). Lets\n // the user open the PR themselves with one click instead of being stuck.\n const slug = repoSlug(branch.repoUrl);\n return `<div class=\"coding-tab__pr coding-tab__pr--branch\">\n <div class=\"coding-tab__pr-title\">Branch pushed: <code>${escapeHtml(branch.branch)}</code></div>\n <div class=\"coding-tab__pr-status\">No PR was opened automatically. Click below to create one yourself on GitHub.</div>\n <div class=\"coding-tab__pr-actions\">\n <a class=\"coding-tab__btn primary\" href=\"${escapeAttr(branch.compareUrl)}\" target=\"_blank\" rel=\"noreferrer\">Create PR on GitHub</a>\n <a class=\"coding-tab__btn\" href=\"${escapeAttr(branch.repoUrl)}/pulls\" target=\"_blank\" rel=\"noreferrer\">View ${escapeHtml(slug)} PRs</a>\n </div>\n </div>`;\n }\n\n function renderPr(pr: PrInfo): string {\n const merge = state.mergeStates.get(pr.url) ?? { state: \"idle\" as const };\n let mergeButton: string;\n let statusLine = \"\";\n switch (merge.state) {\n case \"loading\":\n mergeButton = `<button class=\"coding-tab__btn primary\" data-state=\"loading\" disabled>Merging…</button>`;\n statusLine = `<div class=\"coding-tab__pr-status\">Merging this PR and triggering Railway redeploy…</div>`;\n break;\n case \"success\":\n mergeButton = `<button class=\"coding-tab__btn success\" disabled>Merged ✓</button>`;\n statusLine = `<div class=\"coding-tab__pr-status is-success\">Merged commit ${escapeHtml(merge.sha.slice(0, 7))}. Railway should redeploy on the next push hook.</div>`;\n break;\n case \"error\":\n mergeButton = `<button class=\"coding-tab__btn primary\" data-role=\"merge\" data-pr=\"${escapeAttr(pr.url)}\">Try merge again</button>`;\n statusLine = `<div class=\"coding-tab__pr-status is-error\">Merge failed: ${escapeHtml(merge.message)}</div>`;\n break;\n default:\n mergeButton = `<button class=\"coding-tab__btn primary\" data-role=\"merge\" data-pr=\"${escapeAttr(pr.url)}\">Merge & Redeploy</button>`;\n }\n return `<div class=\"coding-tab__pr\">\n <div class=\"coding-tab__pr-title\">PR #${pr.number} opened in ${escapeHtml(pr.owner)}/${escapeHtml(pr.repo)}</div>\n ${pr.title ? `<div>${escapeHtml(pr.title)}</div>` : \"\"}\n <div class=\"coding-tab__pr-actions\">\n <a class=\"coding-tab__btn\" href=\"${escapeAttr(pr.url)}\" target=\"_blank\" rel=\"noreferrer\">Open in GitHub</a>\n ${mergeButton}\n </div>\n ${statusLine}\n </div>`;\n }\n\n function extractText(turn: StoredTurn): string {\n return turn.events\n .filter((e): e is Extract<TimelineEvent, { kind: \"text\" }> => e.kind === \"text\")\n .map((e) => e.text)\n .join(\"\\n\\n\");\n }\n\n // ───────── events ─────────\n\n function bindEvents() {\n root.querySelector(`[data-role=\"model\"]`)?.addEventListener(\"change\", (e) => {\n state.model = (e.target as HTMLSelectElement).value as ModelChoice;\n });\n root.querySelectorAll(`[data-mode]`).forEach((b) => {\n b.addEventListener(\"click\", () => {\n state.mode = (b as HTMLElement).dataset.mode as ChatMode;\n render();\n });\n });\n root.querySelector(`[data-role=\"repo\"]`)?.addEventListener(\"change\", (e) => {\n state.repoUrl = (e.target as HTMLInputElement).value.trim();\n });\n root.querySelector(`[data-role=\"logout\"]`)?.addEventListener(\"click\", async () => {\n await fetch(`${apiBase}/auth/logout`, { method: \"POST\", credentials: \"include\" }).catch(() => {});\n for (const c of state.chats) c.abort?.abort();\n state.me = null;\n state.chats = [];\n state.activeChatId = null;\n render();\n });\n root.querySelector(`[data-role=\"toggle-sidebar\"]`)?.addEventListener(\"click\", () => {\n state.sidebarOpen = !state.sidebarOpen;\n render();\n });\n\n root.querySelectorAll(`[data-role=\"new-chat\"]`).forEach((b) =>\n b.addEventListener(\"click\", async () => {\n const t = await createChat();\n if (t && window.innerWidth < 720) state.sidebarOpen = false;\n render();\n }),\n );\n root.querySelectorAll(`[data-role=\"select-chat\"]`).forEach((b) =>\n b.addEventListener(\"click\", () => {\n const id = (b as HTMLElement).dataset.chatId!;\n switchChat(id);\n }),\n );\n root.querySelectorAll(`[data-role=\"rename-chat\"]`).forEach((b) =>\n b.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n renameChat((b as HTMLElement).dataset.chatId!);\n }),\n );\n root.querySelectorAll(`[data-role=\"delete-chat\"]`).forEach((b) =>\n b.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n deleteChat((b as HTMLElement).dataset.chatId!);\n }),\n );\n\n root.querySelector(`[data-role=\"send\"]`)?.addEventListener(\"click\", () => onSend());\n root.querySelector(`[data-role=\"prompt\"]`)?.addEventListener(\"keydown\", (e: Event) => {\n const ev = e as KeyboardEvent;\n if (ev.key === \"Enter\" && (ev.metaKey || ev.ctrlKey)) {\n ev.preventDefault();\n onSend();\n }\n });\n root.querySelector(`[data-role=\"cancel\"]`)?.addEventListener(\"click\", () => onCancel());\n root.querySelectorAll(`[data-role=\"execute\"]`).forEach((b) =>\n b.addEventListener(\"click\", () => onExecute()),\n );\n root.querySelectorAll(`[data-role=\"merge\"]`).forEach((b) => {\n b.addEventListener(\"click\", () => onMerge((b as HTMLElement).dataset.pr!));\n });\n\n root.querySelectorAll(`[data-role=\"toggle-tool\"]`).forEach((b) =>\n b.addEventListener(\"click\", () => {\n const id = (b as HTMLElement).dataset.toolId!;\n if (state.expandedTools.has(id)) state.expandedTools.delete(id);\n else state.expandedTools.add(id);\n render();\n }),\n );\n }\n\n // ───────── send / execute / cancel / merge ─────────\n\n async function onSend() {\n let chat = activeChat();\n if (!chat) {\n chat = await createChat();\n if (!chat) return;\n }\n if (chat.isStreaming) return;\n\n const promptEl = root.querySelector(`[data-role=\"prompt\"]`) as HTMLTextAreaElement | null;\n const prompt = promptEl?.value.trim() ?? \"\";\n if (!prompt) return;\n if (promptEl) promptEl.value = \"\";\n\n chat.turns = chat.turns ?? [];\n const userTurn: StoredTurn = {\n id: cryptoRandomId(),\n chatId: chat.meta.id,\n seq: chat.turns.length,\n role: \"user\",\n isPlan: state.mode === \"plan\",\n status: \"finished\",\n events: [{ kind: \"text\", id: cryptoRandomId(), text: prompt }],\n prompt,\n createdAt: Date.now(),\n };\n chat.turns.push(userTurn);\n\n const assistantTurn: StoredTurn = {\n id: cryptoRandomId(),\n chatId: chat.meta.id,\n seq: chat.turns.length,\n role: \"assistant\",\n isPlan: state.mode === \"plan\",\n status: \"running\",\n events: [],\n createdAt: Date.now(),\n };\n chat.turns.push(assistantTurn);\n chat.isStreaming = true;\n if (chat.meta.title === \"New chat\") {\n chat.meta = { ...chat.meta, title: prompt.length > 60 ? `${prompt.slice(0, 57)}…` : prompt };\n }\n chat.meta = { ...chat.meta, mode: state.mode, model: state.model, updatedAt: Date.now() };\n render();\n\n try {\n await runStreamWithFallback(\n \"/agent/send\",\n {\n chatId: chat.meta.id,\n prompt,\n mode: state.mode,\n },\n chat,\n assistantTurn,\n );\n } finally {\n // pollChatUntilTurnDone resets these in its own finally, so only do it\n // here when we didn't fall through to polling.\n if (!chat.isReconnecting) {\n chat.isStreaming = false;\n chat.activeRunId = null;\n chat.abort = null;\n }\n render();\n }\n }\n\n async function onExecute() {\n const chat = activeChat();\n if (!chat || chat.isStreaming) return;\n chat.turns = chat.turns ?? [];\n const turn: StoredTurn = {\n id: cryptoRandomId(),\n chatId: chat.meta.id,\n seq: chat.turns.length,\n role: \"assistant\",\n isPlan: false,\n status: \"running\",\n events: [],\n createdAt: Date.now(),\n };\n chat.turns.push(turn);\n chat.isStreaming = true;\n render();\n try {\n await runStreamWithFallback(\"/agent/execute\", { chatId: chat.meta.id }, chat, turn);\n } finally {\n if (!chat.isReconnecting) {\n chat.isStreaming = false;\n chat.activeRunId = null;\n chat.abort = null;\n }\n render();\n }\n }\n\n async function onCancel() {\n const chat = activeChat();\n if (!chat) return;\n if (chat.activeRunId) {\n try {\n await api(\"/agent/cancel\", {\n method: \"POST\",\n body: JSON.stringify({ chatId: chat.meta.id, runId: chat.activeRunId }),\n });\n } catch (err) {\n console.error(\"[coding-tab] cancel failed\", err);\n }\n }\n chat.abort?.abort();\n }\n\n async function onMerge(prUrl: string) {\n const chat = activeChat();\n if (!chat) return;\n if (state.mergeStates.get(prUrl)?.state === \"loading\") return;\n state.mergeStates.set(prUrl, { state: \"loading\" });\n render();\n\n chat.turns = chat.turns ?? [];\n try {\n const result = await api<{ sha: string; merged: boolean }>(\"/pr/merge\", {\n method: \"POST\",\n body: JSON.stringify({ prUrl, mergeMethod: \"squash\" }),\n });\n if (result.merged) {\n state.mergeStates.set(prUrl, { state: \"success\", sha: result.sha });\n chat.turns.push({\n id: cryptoRandomId(),\n chatId: chat.meta.id,\n seq: chat.turns.length,\n role: \"assistant\",\n isPlan: false,\n status: \"finished\",\n events: [\n {\n kind: \"text\",\n id: cryptoRandomId(),\n text: `Merged commit \\`${result.sha.slice(0, 7)}\\` into the default branch. Railway should redeploy on the next push hook.`,\n },\n ],\n createdAt: Date.now(),\n });\n } else {\n state.mergeStates.set(prUrl, {\n state: \"error\",\n message: \"GitHub returned merged=false\",\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n state.mergeStates.set(prUrl, { state: \"error\", message });\n } finally {\n render();\n }\n }\n\n function appendErrorToTurn(turn: StoredTurn, err: unknown): void {\n const message = err instanceof Error ? err.message : String(err);\n turn.events.push({\n kind: \"text\",\n id: cryptoRandomId(),\n text: `[error] ${message}`,\n });\n }\n\n /**\n * True for errors that mean the SSE socket died on us (mobile Safari\n * suspending the page, network drop, server restart) — as opposed to a\n * real authoritative error from the agent. We DON'T want to surface\n * these as turn errors: the agent on Cursor's side keeps running and the\n * server keeps persisting events, so we can recover by polling.\n */\n function isStreamDisconnect(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n if (err.name === \"AbortError\") return false;\n const m = err.message.toLowerCase();\n return (\n m.includes(\"load failed\") ||\n m.includes(\"network\") ||\n m.includes(\"failed to fetch\") ||\n m.includes(\"the network connection was lost\") ||\n m.includes(\"err_network\") ||\n m.includes(\"connection\") ||\n m === \"fetch failed\"\n );\n }\n\n /**\n * Poll `/chats/:id` until the assistant turn is no longer `running`. The\n * server's `streamRun` persists every event with a debounced `patchTurn`,\n * so polling reconstructs the timeline as the agent makes progress —\n * even if our SSE socket got killed by mobile Safari backgrounding.\n *\n * The chat thread keeps `isStreaming = true` for the duration so the\n * composer stays disabled and the UI shows a \"Reconnecting…\" indicator\n * instead of looking idle.\n */\n async function pollChatUntilTurnDone(chat: ChatThread, turnId: string): Promise<void> {\n const startedAt = Date.now();\n const maxMs = 15 * 60_000; // give up after 15 minutes of no completion\n const intervalMs = 2_000;\n chat.isReconnecting = true;\n chat.pollTurnId = turnId;\n render();\n try {\n while (Date.now() - startedAt < maxMs) {\n // Stop early if the user deleted the chat or signed out.\n if (!state.chats.find((c) => c.meta.id === chat.meta.id)) return;\n try {\n const { chat: full } = await api<{ chat: FullChat }>(`/chats/${chat.meta.id}`);\n chat.turns = full.turns;\n chat.meta = {\n id: full.id,\n title: full.title,\n mode: full.mode,\n model: full.model,\n createdAt: full.createdAt,\n updatedAt: full.updatedAt,\n };\n const target = full.turns.find((t) => t.id === turnId);\n if (!target || (target.status && target.status !== \"running\")) {\n // Done — fall through and let the finally block reset state.\n return;\n }\n render();\n } catch (err) {\n // Transient fetch error while polling — keep trying. The user is\n // probably still backgrounded; we'll catch up next tick.\n console.warn(\"[coding-tab] poll tick failed\", err);\n }\n await sleep(intervalMs);\n }\n } finally {\n chat.isReconnecting = false;\n chat.pollTurnId = null;\n chat.pollPromise = null;\n chat.isStreaming = false;\n chat.activeRunId = null;\n chat.abort = null;\n render();\n }\n }\n\n function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Run an SSE stream and, if it dies with a network-class error, fall back\n * to polling instead of marking the turn as failed. Real authoritative\n * errors (4xx/5xx, agent error events) still surface as turn errors.\n */\n async function runStreamWithFallback(\n path: string,\n body: unknown,\n chat: ChatThread,\n turn: StoredTurn,\n ): Promise<void> {\n try {\n await streamSse(path, body, chat, turn);\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n // User cancelled — leave the turn as-is.\n return;\n }\n if (isStreamDisconnect(err)) {\n // Network died (typical: iOS suspended the page). The agent is still\n // running on Cursor's side and the server keeps persisting; switch\n // to polling instead of declaring the turn failed.\n const poll = pollChatUntilTurnDone(chat, turn.id);\n chat.pollPromise = poll;\n await poll;\n return;\n }\n // Real error — surface it.\n appendErrorToTurn(turn, err);\n turn.status = \"error\";\n }\n }\n\n // ───────── streaming ─────────\n\n async function streamSse(\n path: string,\n body: unknown,\n chat: ChatThread,\n turn: StoredTurn,\n ): Promise<void> {\n chat.abort = new AbortController();\n const resp = await fetch(`${apiBase}${path}`, {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\", Accept: \"text/event-stream\" },\n body: JSON.stringify(body),\n signal: chat.abort.signal,\n });\n if (!resp.ok || !resp.body) {\n throw new Error(`${resp.status} ${resp.statusText}`);\n }\n const reader = resp.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n /** Track whether the last applied event was a `text` event so we know\n * whether to merge subsequent text deltas into the same paragraph. */\n let lastWasText = false;\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const events = buffer.split(\"\\n\\n\");\n buffer = events.pop() ?? \"\";\n for (const raw of events) {\n const dataLine = raw.split(\"\\n\").find((l) => l.startsWith(\"data: \"));\n if (!dataLine) continue;\n try {\n const evt = JSON.parse(dataLine.slice(6)) as StreamEvent;\n lastWasText = applyStreamEvent(evt, chat, turn, lastWasText);\n render();\n } catch (e) {\n console.warn(\"[coding-tab] bad sse event\", e);\n }\n }\n }\n }\n\n /**\n * Apply an SSE event into the active turn's timeline. Returns whether the\n * last applied event was a streaming text event (so the next text chunk can\n * be merged with it).\n */\n function applyStreamEvent(\n evt: StreamEvent,\n chat: ChatThread,\n turn: StoredTurn,\n lastWasText: boolean,\n ): boolean {\n switch (evt.kind) {\n case \"ready\":\n chat.activeRunId = evt.runId;\n return false;\n case \"text\": {\n // Each `text` SSE corresponds to one logical block from the model.\n // Within a single render burst the server sends one event per block,\n // so we always start a new paragraph here. (If the SDK ever streams\n // sub-block deltas, the server-side TurnBuffer will have already\n // coalesced them into one event for us.)\n const last = turn.events[turn.events.length - 1];\n if (lastWasText && last && last.kind === \"text\") {\n // Same delta train — append.\n last.text += evt.text;\n } else {\n turn.events.push({\n kind: \"text\",\n id: cryptoRandomId(),\n text: evt.text,\n });\n }\n return true;\n }\n case \"thinking\":\n return lastWasText;\n case \"tool\": {\n const existing = turn.events.find(\n (e): e is Extract<TimelineEvent, { kind: \"tool\" }> =>\n e.kind === \"tool\" && e.callId === evt.callId,\n );\n if (existing) {\n existing.status = evt.status;\n if (evt.args !== undefined) existing.args = evt.args;\n if (evt.result !== undefined) existing.result = evt.result;\n } else {\n turn.events.push({\n kind: \"tool\",\n id: cryptoRandomId(),\n callId: evt.callId,\n name: evt.name,\n status: evt.status,\n args: evt.args,\n result: evt.result,\n });\n }\n return false;\n }\n case \"status\":\n return lastWasText;\n case \"result\":\n turn.status = evt.status as TurnStatus;\n if (evt.pr) turn.pr = evt.pr;\n if (evt.branch && !evt.pr) turn.branch = evt.branch;\n if (turn.isPlan && evt.status === \"finished\") turn.showExecute = true;\n chat.activeRunId = null;\n return false;\n case \"error\":\n turn.events.push({\n kind: \"text\",\n id: cryptoRandomId(),\n text: `[error] ${evt.message}`,\n });\n turn.status = \"error\";\n chat.activeRunId = null;\n return false;\n }\n }\n\n // ───────── bootstrap ─────────\n\n async function bootstrap() {\n try {\n const me = await api<MeResponse>(\"/auth/me\");\n state.me = me;\n if (me.defaultRepoUrl) {\n state.repoUrl = me.defaultRepoUrl;\n state.repoLocked = true;\n }\n } catch (err) {\n const status = (err as { status?: number }).status;\n if (status === 401 || status === 403) {\n state.me = null;\n render();\n return;\n }\n console.error(\"[coding-tab] /auth/me failed\", err);\n state.me = null;\n render();\n return;\n }\n try {\n const { models } = await api<{ models: ModelOption[] }>(\"/models\");\n state.models = models;\n if (models.length > 0 && !models.find((m) => m.choice === state.model)) {\n state.model = models[0]!.choice;\n }\n } catch (err) {\n console.error(\"[coding-tab] /models failed\", err);\n state.models = [\n { choice: \"sonnet\", cursorModelId: \"auto\", displayName: \"Sonnet (fallback)\" },\n { choice: \"opus\", cursorModelId: \"auto\", displayName: \"Opus (fallback)\" },\n ];\n }\n await loadChatList();\n if (state.chats.length > 0 && !state.activeChatId) {\n state.activeChatId = state.chats[0]!.meta.id;\n ensureChatLoaded(state.activeChatId).then(() => {\n resumePollingForUnfinishedTurns();\n render();\n });\n } else {\n resumePollingForUnfinishedTurns();\n }\n render();\n }\n\n /**\n * On bootstrap (page load) and on visibilitychange (tab returning to focus),\n * any chat with a `running` assistant turn at the tail likely means the\n * agent is still in flight on Cursor's side but we're not subscribed to a\n * live stream. Hand off to the poller so the UI catches up. Idempotent —\n * a chat already polling won't get a second poller.\n */\n function resumePollingForUnfinishedTurns(): void {\n for (const chat of state.chats) {\n if (chat.pollPromise) continue;\n const tail = chat.turns?.at(-1);\n if (!tail) continue;\n if (tail.role !== \"assistant\") continue;\n if (tail.status && tail.status !== \"running\") continue;\n chat.isStreaming = true;\n const poll = pollChatUntilTurnDone(chat, tail.id);\n chat.pollPromise = poll;\n poll.catch((err) => console.warn(\"[coding-tab] resume poll failed\", err));\n }\n }\n\n // When the user comes back to the page (or unlocks their phone), iOS may\n // have killed our SSE socket. Rather than leave the chat looking idle,\n // immediately reload + start polling any chat that's still mid-turn.\n const onVisibilityChange = () => {\n if (document.visibilityState !== \"visible\") return;\n // Refresh the active chat from the server so we catch up on whatever\n // happened while we were backgrounded, then start polling if needed.\n if (state.activeChatId) {\n const chat = state.chats.find((c) => c.meta.id === state.activeChatId);\n if (chat) {\n api<{ chat: FullChat }>(`/chats/${chat.meta.id}`)\n .then(({ chat: full }) => {\n chat.turns = full.turns;\n chat.meta = {\n id: full.id,\n title: full.title,\n mode: full.mode,\n model: full.model,\n createdAt: full.createdAt,\n updatedAt: full.updatedAt,\n };\n resumePollingForUnfinishedTurns();\n render();\n })\n .catch((err) => console.warn(\"[coding-tab] visibility refresh failed\", err));\n return;\n }\n }\n resumePollingForUnfinishedTurns();\n };\n document.addEventListener(\"visibilitychange\", onVisibilityChange);\n\n bootstrap();\n\n return {\n destroy() {\n document.removeEventListener(\"visibilitychange\", onVisibilityChange);\n for (const c of state.chats) c.abort?.abort();\n el.removeChild(root);\n },\n };\n}\n\nif (typeof window !== \"undefined\") {\n (window as unknown as { CodingTab?: { mountCodingTab: typeof mountCodingTab } }).CodingTab = {\n mountCodingTab,\n };\n}\n","/**\n * Tiny zero-dependency markdown renderer used for Plan-mode assistant turns.\n *\n * Supports headings (`#`/`##`/`###`/`####`), unordered (`-`/`*`) and ordered\n * (`1.`) lists, fenced code blocks, inline `code`, `**bold**`, `*italic*`,\n * `[link](url)`, horizontal rules (`---`), paragraphs, and hard breaks.\n *\n * All text is HTML-escaped before any markdown substitutions are applied so\n * the output is safe to inject via `innerHTML` even when the upstream model\n * dumps unsanitised user input back to us.\n */\n\nexport function escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) =>\n (\n {\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n } as Record<string, string>\n )[c]!,\n );\n}\n\nfunction applyInline(text: string): string {\n // Inline code first — anything inside backticks should be inert.\n const segments: string[] = [];\n const codeRe = /`([^`\\n]+)`/g;\n let lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = codeRe.exec(text)) !== null) {\n segments.push(applyInlineRest(text.slice(lastIndex, m.index)));\n segments.push(`<code>${m[1]!}</code>`);\n lastIndex = m.index + m[0].length;\n }\n segments.push(applyInlineRest(text.slice(lastIndex)));\n return segments.join(\"\");\n}\n\nfunction applyInlineRest(text: string): string {\n return text\n .replace(/\\*\\*([^*\\n]+)\\*\\*/g, \"<strong>$1</strong>\")\n .replace(/(^|[^*])\\*([^*\\n]+)\\*(?!\\*)/g, \"$1<em>$2</em>\")\n .replace(/\\[([^\\]\\n]+)\\]\\(([^)\\s]+)\\)/g, (_match, label: string, url: string) => {\n const safeUrl = /^(https?:|mailto:|\\/|#)/i.test(url) ? url : \"#\";\n return `<a href=\"${safeUrl}\" target=\"_blank\" rel=\"noreferrer noopener\">${label}</a>`;\n });\n}\n\ninterface ListContext {\n type: \"ul\" | \"ol\";\n indent: number;\n items: string[];\n}\n\nfunction flushParagraph(buf: string[], out: string[]) {\n if (buf.length === 0) return;\n const joined = buf.join(\" \").trim();\n buf.length = 0;\n if (!joined) return;\n out.push(`<p>${applyInline(joined)}</p>`);\n}\n\nfunction flushList(stack: ListContext[], out: string[]) {\n while (stack.length > 0) {\n const ctx = stack.pop()!;\n out.push(`<${ctx.type}>${ctx.items.join(\"\")}</${ctx.type}>`);\n }\n}\n\nexport function renderMarkdown(input: string): string {\n // Escape first so list markers / fences are still recognised but any HTML in\n // the source becomes inert.\n const escaped = escapeHtml(input).replace(/\\r\\n/g, \"\\n\");\n const lines = escaped.split(\"\\n\");\n\n const out: string[] = [];\n const paragraph: string[] = [];\n const listStack: ListContext[] = [];\n let inCode = false;\n let codeLang = \"\";\n const codeBuf: string[] = [];\n\n const closeOpenBlocks = () => {\n flushParagraph(paragraph, out);\n flushList(listStack, out);\n };\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n\n // Fenced code blocks\n const fence = line.match(/^\\s*```(\\w*)\\s*$/);\n if (fence) {\n if (inCode) {\n const cls = codeLang ? ` class=\"lang-${codeLang}\"` : \"\";\n out.push(`<pre><code${cls}>${codeBuf.join(\"\\n\")}</code></pre>`);\n codeBuf.length = 0;\n codeLang = \"\";\n inCode = false;\n } else {\n closeOpenBlocks();\n inCode = true;\n codeLang = fence[1] ?? \"\";\n }\n continue;\n }\n if (inCode) {\n codeBuf.push(line);\n continue;\n }\n\n // Blank line: paragraph break, list break.\n if (!line.trim()) {\n closeOpenBlocks();\n continue;\n }\n\n // Horizontal rule.\n if (/^\\s*(-{3,}|\\*{3,}|_{3,})\\s*$/.test(line)) {\n closeOpenBlocks();\n out.push(\"<hr />\");\n continue;\n }\n\n // Headings.\n const h = line.match(/^(#{1,6})\\s+(.*)$/);\n if (h) {\n closeOpenBlocks();\n const level = Math.min(6, h[1]!.length);\n out.push(`<h${level}>${applyInline(h[2]!.trim())}</h${level}>`);\n continue;\n }\n\n // List items.\n const ul = line.match(/^(\\s*)[-*]\\s+(.*)$/);\n const ol = line.match(/^(\\s*)(\\d+)\\.\\s+(.*)$/);\n if (ul || ol) {\n flushParagraph(paragraph, out);\n const indent = (ul ? ul[1] : ol![1])!.length;\n const type: \"ul\" | \"ol\" = ul ? \"ul\" : \"ol\";\n const content = (ul ? ul[2] : ol![3])!;\n // Pop deeper lists.\n while (listStack.length > 0 && listStack[listStack.length - 1]!.indent > indent) {\n const ctx = listStack.pop()!;\n const prev = listStack[listStack.length - 1]?.items;\n const html = `<${ctx.type}>${ctx.items.join(\"\")}</${ctx.type}>`;\n if (prev && prev.length > 0) {\n prev[prev.length - 1] = prev[prev.length - 1]!.replace(\n /<\\/li>$/,\n `${html}</li>`,\n );\n } else {\n out.push(html);\n }\n }\n const top = listStack[listStack.length - 1];\n if (!top || top.indent < indent || top.type !== type) {\n listStack.push({ type, indent, items: [`<li>${applyInline(content)}</li>`] });\n } else {\n top.items.push(`<li>${applyInline(content)}</li>`);\n }\n continue;\n }\n\n // If we were in a list and hit a non-list line, close the list.\n if (listStack.length > 0) {\n flushList(listStack, out);\n }\n paragraph.push(line.trim());\n }\n\n if (inCode) {\n out.push(`<pre><code>${codeBuf.join(\"\\n\")}</code></pre>`);\n } else {\n closeOpenBlocks();\n }\n\n return out.join(\"\\n\");\n}\n"],"mappings":"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","names":["tab_exports","__export","mountCodingTab","escapeHtml","s","c","applyInline","text","segments","codeRe","lastIndex","m","applyInlineRest","_match","label","url","flushParagraph","buf","out","joined","flushList","stack","ctx","renderMarkdown","input","lines","paragraph","listStack","inCode","codeLang","codeBuf","closeOpenBlocks","i","line","fence","cls","h","level","ul","ol","indent","type","content","prev","html","top","ICON_GITHUB","ICON_PLUS","ICON_TRASH","ICON_PENCIL","ICON_MENU","ICON_CHEVRON","escapeAttr","s","escapeHtml","repoSlug","url","m","repoWebUrl","githubPullsUrl","cryptoRandomId","relativeTime","ts","diff","mountCodingTab","el","options","apiBase","root","stylesheetInjected","injectStylesheet","link","state","activeChat","c","api","path","init","resp","loadChatList","chats","existing","meta","prev","err","ensureChatLoaded","chatId","t","chat","createChat","thread","switchChat","render","deleteChat","renameChat","next","trimmed","headerHtml","sidebarHtml","renderChatRow","paneHtml","renderChatPane","renderEmptyPane","bindEvents","active","streaming","turns","threadHtml","renderTurn","placeholder","sendLabel","turn","text","extractText","reconnectingHtml","eventsHtml","evt","renderEvent","statusBadge","planActions","prHtml","renderPr","renderBranchFallback","isPlan","renderMarkdown","renderInlineText","renderToolEvent","para","_m","label","lead","p","summary","summarizeTool","expanded","detailHtml","renderToolDetail","name","args","a","lower","pattern","lines","argsBlock","formatBlob","resultBlock","value","branch","slug","pr","merge","mergeButton","statusLine","e","b","id","onSend","ev","onCancel","onExecute","onMerge","promptEl","prompt","userTurn","assistantTurn","runStreamWithFallback","prUrl","result","message","appendErrorToTurn","isStreamDisconnect","pollChatUntilTurnDone","turnId","startedAt","maxMs","intervalMs","full","target","sleep","ms","resolve","body","streamSse","poll","reader","decoder","buffer","lastWasText","done","events","raw","dataLine","l","applyStreamEvent","last","bootstrap","me","status","models","resumePollingForUnfinishedTurns","tail","onVisibilityChange"]}
package/dist/style.css CHANGED
@@ -354,6 +354,17 @@
354
354
  color: var(--ct-fg-dim);
355
355
  font-style: italic;
356
356
  }
357
+ .coding-tab__msg-reconnecting {
358
+ margin-top: 8px;
359
+ padding: 8px 10px;
360
+ border-radius: 6px;
361
+ background: rgba(255, 159, 67, 0.10);
362
+ border: 1px dashed rgba(255, 159, 67, 0.35);
363
+ color: var(--ct-fg);
364
+ font-size: 12px;
365
+ font-style: normal;
366
+ line-height: 1.4;
367
+ }
357
368
 
358
369
  @media (max-width: 720px) {
359
370
  .coding-tab__thread { padding: 12px; gap: 12px; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstax/coding-tab",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Drop-in Cursor-style coding agent tab for any Express app. Multiple persistent chats, plan/agent modes, in-line tool inspection, GitHub OAuth, opens PRs against your repo via the Cursor SDK cloud runtime.",
5
5
  "type": "module",
6
6
  "exports": {