@tstax/coding-tab 0.2.0 → 0.2.1

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,9 +1,9 @@
1
- "use strict";var CodingTab=(()=>{var O=Object.defineProperty;var ct=Object.getOwnPropertyDescriptor;var ut=Object.getOwnPropertyNames;var ht=Object.prototype.hasOwnProperty;var gt=(d,r)=>{for(var l in r)O(d,l,{get:r[l],enumerable:!0})},pt=(d,r,l,o)=>{if(r&&typeof r=="object"||typeof r=="function")for(let u of ut(r))!ht.call(d,u)&&u!==l&&O(d,u,{get:()=>r[u],enumerable:!(o=ct(r,u))||o.enumerable});return d};var mt=d=>pt(O({},"__esModule",{value:!0}),d);var wt={};gt(wt,{mountCodingTab:()=>F});function g(d){return d.replace(/[&<>"']/g,r=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[r])}function P(d){let r=[],l=/`([^`\n]+)`/g,o=0,u;for(;(u=l.exec(d))!==null;)r.push(J(d.slice(o,u.index))),r.push(`<code>${u[1]}</code>`),o=u.index+u[0].length;return r.push(J(d.slice(o))),r.join("")}function J(d){return d.replace(/\*\*([^*\n]+)\*\*/g,"<strong>$1</strong>").replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g,"$1<em>$2</em>").replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g,(r,l,o)=>`<a href="${/^(https?:|mailto:|\/|#)/i.test(o)?o:"#"}" target="_blank" rel="noreferrer noopener">${l}</a>`)}function G(d,r){if(d.length===0)return;let l=d.join(" ").trim();d.length=0,l&&r.push(`<p>${P(l)}</p>`)}function W(d,r){for(;d.length>0;){let l=d.pop();r.push(`<${l.type}>${l.items.join("")}</${l.type}>`)}}function z(d){let l=g(d).replace(/\r\n/g,`
1
+ "use strict";var CodingTab=(()=>{var O=Object.defineProperty;var ct=Object.getOwnPropertyDescriptor;var ut=Object.getOwnPropertyNames;var gt=Object.prototype.hasOwnProperty;var ht=(d,r)=>{for(var l in r)O(d,l,{get:r[l],enumerable:!0})},mt=(d,r,l,s)=>{if(r&&typeof r=="object"||typeof r=="function")for(let u of ut(r))!gt.call(d,u)&&u!==l&&O(d,u,{get:()=>r[u],enumerable:!(s=ct(r,u))||s.enumerable});return d};var pt=d=>mt(O({},"__esModule",{value:!0}),d);var wt={};ht(wt,{mountCodingTab:()=>F});function g(d){return d.replace(/[&<>"']/g,r=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[r])}function P(d){let r=[],l=/`([^`\n]+)`/g,s=0,u;for(;(u=l.exec(d))!==null;)r.push(J(d.slice(s,u.index))),r.push(`<code>${u[1]}</code>`),s=u.index+u[0].length;return r.push(J(d.slice(s))),r.join("")}function J(d){return d.replace(/\*\*([^*\n]+)\*\*/g,"<strong>$1</strong>").replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g,"$1<em>$2</em>").replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g,(r,l,s)=>`<a href="${/^(https?:|mailto:|\/|#)/i.test(s)?s:"#"}" target="_blank" rel="noreferrer noopener">${l}</a>`)}function G(d,r){if(d.length===0)return;let l=d.join(" ").trim();d.length=0,l&&r.push(`<p>${P(l)}</p>`)}function W(d,r){for(;d.length>0;){let l=d.pop();r.push(`<${l.type}>${l.items.join("")}</${l.type}>`)}}function z(d){let l=g(d).replace(/\r\n/g,`
2
2
  `).split(`
3
- `),o=[],u=[],p=[],a=!1,b="",m=[],w=()=>{G(u,o),W(p,o)};for(let k=0;k<l.length;k++){let v=l[k],M=v.match(/^\s*```(\w*)\s*$/);if(M){if(a){let y=b?` class="lang-${b}"`:"";o.push(`<pre><code${y}>${m.join(`
4
- `)}</code></pre>`),m.length=0,b="",a=!1}else w(),a=!0,b=M[1]??"";continue}if(a){m.push(v);continue}if(!v.trim()){w();continue}if(/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(v)){w(),o.push("<hr />");continue}let T=v.match(/^(#{1,6})\s+(.*)$/);if(T){w();let y=Math.min(6,T[1].length);o.push(`<h${y}>${P(T[2].trim())}</h${y}>`);continue}let $=v.match(/^(\s*)[-*]\s+(.*)$/),c=v.match(/^(\s*)(\d+)\.\s+(.*)$/);if($||c){G(u,o);let y=($?$[1]:c[1]).length,I=$?"ul":"ol",L=$?$[2]:c[3];for(;p.length>0&&p[p.length-1].indent>y;){let C=p.pop(),_=p[p.length-1]?.items,A=`<${C.type}>${C.items.join("")}</${C.type}>`;_&&_.length>0?_[_.length-1]=_[_.length-1].replace(/<\/li>$/,`${A}</li>`):o.push(A)}let S=p[p.length-1];!S||S.indent<y||S.type!==I?p.push({type:I,indent:y,items:[`<li>${P(L)}</li>`]}):S.items.push(`<li>${P(L)}</li>`);continue}p.length>0&&W(p,o),u.push(v.trim())}return a?o.push(`<pre><code>${m.join(`
5
- `)}</code></pre>`):w(),o.join(`
6
- `)}var K='<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>',V='<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>',ft='<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>',bt='<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>',vt='<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 E(d){return g(d)}function $t(d){let r=d.match(/github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?\/?$/);return r?`${r[1]}/${r[2]}`:d}function f(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():Math.random().toString(36).slice(2)}function _t(d){let r=Date.now()-d;return r<6e4?"just now":r<36e5?`${Math.round(r/6e4)}m`:r<864e5?`${Math.round(r/36e5)}h`:r<6048e5?`${Math.round(r/864e5)}d`:new Date(d).toLocaleDateString()}function F(d,r){let l=r.apiBase.replace(/\/$/,""),o=document.createElement("div");o.className="coding-tab",d.innerHTML="",d.appendChild(o);let u=!1;function p(){if(u||(u=!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:r.defaultMode??"plan",model:r.defaultModel??"sonnet",repoUrl:r.defaultRepo??"",repoLocked:!1,chats:[],activeChatId:null,sidebarOpen:window.innerWidth>=720,expandedTools:new Set};function b(){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 w(){try{let{chats:t}=await m("/chats"),n=new Map(a.chats.map(e=>[e.meta.id,e]));a.chats=t.map(e=>{let i=n.get(e.id);return i?{...i,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 k(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 v(){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 k(t),c()}async function T(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 $(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 i=e.trim();if(!(!i||i===n.meta.title))try{let{chat:s}=await m(`/chats/${t}`,{method:"PATCH",body:JSON.stringify({title:i})});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){o.innerHTML=`
3
+ `),s=[],u=[],m=[],a=!1,f="",p=[],w=()=>{G(u,s),W(m,s)};for(let S=0;S<l.length;S++){let b=l[S],M=b.match(/^\s*```(\w*)\s*$/);if(M){if(a){let v=f?` class="lang-${f}"`:"";s.push(`<pre><code${v}>${p.join(`
4
+ `)}</code></pre>`),p.length=0,f="",a=!1}else w(),a=!0,f=M[1]??"";continue}if(a){p.push(b);continue}if(!b.trim()){w();continue}if(/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(b)){w(),s.push("<hr />");continue}let C=b.match(/^(#{1,6})\s+(.*)$/);if(C){w();let v=Math.min(6,C[1].length);s.push(`<h${v}>${P(C[2].trim())}</h${v}>`);continue}let y=b.match(/^(\s*)[-*]\s+(.*)$/),c=b.match(/^(\s*)(\d+)\.\s+(.*)$/);if(y||c){G(u,s);let v=(y?y[1]:c[1]).length,L=y?"ul":"ol",I=y?y[2]:c[3];for(;m.length>0&&m[m.length-1].indent>v;){let E=m.pop(),$=m[m.length-1]?.items,A=`<${E.type}>${E.items.join("")}</${E.type}>`;$&&$.length>0?$[$.length-1]=$[$.length-1].replace(/<\/li>$/,`${A}</li>`):s.push(A)}let k=m[m.length-1];!k||k.indent<v||k.type!==L?m.push({type:L,indent:v,items:[`<li>${P(I)}</li>`]}):k.items.push(`<li>${P(I)}</li>`);continue}m.length>0&&W(m,s),u.push(b.trim())}return a?s.push(`<pre><code>${p.join(`
5
+ `)}</code></pre>`):w(),s.join(`
6
+ `)}var K='<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>',V='<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>',ft='<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>',bt='<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>',vt='<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>',_t='<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 T(d){return g(d)}function yt(d){let r=d.match(/github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?\/?$/);return r?`${r[1]}/${r[2]}`:d}function _(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():Math.random().toString(36).slice(2)}function $t(d){let r=Date.now()-d;return r<6e4?"just now":r<36e5?`${Math.round(r/6e4)}m`:r<864e5?`${Math.round(r/36e5)}h`:r<6048e5?`${Math.round(r/864e5)}d`:new Date(d).toLocaleDateString()}function F(d,r){let l=r.apiBase.replace(/\/$/,""),s=document.createElement("div");s.className="coding-tab",d.innerHTML="",d.appendChild(s);let u=!1;function m(){if(u||(u=!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)}m();let a={me:null,models:[],mode:r.defaultMode??"plan",model:r.defaultModel??"sonnet",repoUrl:r.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 p(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 w(){try{let{chats:t}=await p("/chats"),n=new Map(a.chats.map(e=>[e.meta.id,e]));a.chats=t.map(e=>{let o=n.get(e.id);return o?{...o,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 p(`/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 p("/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 p(`/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(i=>i.meta.id===t);if(!n)return;let e=prompt("Rename chat",n.meta.title);if(e===null)return;let o=e.trim();if(!(!o||o===n.meta.title))try{let{chat:i}=await p(`/chats/${t}`,{method:"PATCH",body:JSON.stringify({title:o})});n.meta={...n.meta,title:i.title,updatedAt:i.updatedAt},c()}catch(i){console.error(`[coding-tab] rename chat ${t} failed`,i)}}function c(){if(!a.me){s.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>
@@ -19,10 +19,10 @@
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="${E(a.repoUrl)}" target="_blank" rel="noreferrer" title="Locked to this app's repo (set by the server)">${K}<span>${g($t(a.repoUrl))}</span></a>`:`<input data-role="repo" placeholder="https://github.com/org/repo" value="${E(a.repoUrl)}" style="flex:1;min-width:200px" />`}
22
+ ${a.repoLocked&&a.repoUrl?`<a class="coding-tab__repo-locked" href="${T(a.repoUrl)}" target="_blank" rel="noreferrer" title="Locked to this app's repo (set by the server)">${K}<span>${g(yt(a.repoUrl))}</span></a>`:`<input data-role="repo" placeholder="https://github.com/org/repo" value="${T(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>@${g(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>
@@ -30,73 +30,74 @@
30
30
  <aside class="coding-tab__sidebar ${a.sidebarOpen?"is-open":""}">
31
31
  <button class="coding-tab__btn primary coding-tab__new-chat" data-role="new-chat">${V}<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=>y(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(h=>v(h)).join("")}
34
34
  </div>
35
35
  </aside>
36
- `,e=b(),i=`
36
+ `,e=f(),o=`
37
37
  <main class="coding-tab__pane">
38
- ${e?L(e):I()}
38
+ ${e?I(e):L()}
39
39
  </main>
40
- `;o.innerHTML=`
40
+ `;s.innerHTML=`
41
41
  ${t}
42
42
  <div class="coding-tab__body">
43
43
  ${n}
44
- ${i}
44
+ ${o}
45
45
  </div>
46
- `;let s=o.querySelector("[data-role=thread]");s&&(s.scrollTop=s.scrollHeight),tt()}function y(t){let n=t.meta.id===a.activeChatId?" is-active":"",e=t.isStreaming?'<span class="coding-tab__chat-row-dot" title="Streaming"></span>':"";return`
46
+ `;let i=s.querySelector("[data-role=thread]");i&&(i.scrollTop=i.scrollHeight),tt()}function v(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
47
  <div class="coding-tab__chat-row${n}" 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
49
  ${e}
50
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 ${_t(t.meta.updatedAt)}</span>
51
+ <span class="coding-tab__chat-row-meta">${t.meta.mode==="plan"?"Plan":"Agent"} \xB7 ${$t(t.meta.updatedAt)}</span>
52
52
  </button>
53
53
  <div class="coding-tab__chat-row-actions">
54
54
  <button data-role="rename-chat" data-chat-id="${t.meta.id}" title="Rename">${bt}</button>
55
55
  <button data-role="delete-chat" data-chat-id="${t.meta.id}" title="Delete">${ft}</button>
56
56
  </div>
57
57
  </div>
58
- `}function I(){return`
58
+ `}function L(){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
62
  <button class="coding-tab__btn primary" data-role="new-chat">${V}<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=>S(s)).join(""):'<div class="coding-tab__notice info">Loading\u2026</div>',i=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`
64
+ `}function I(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(i=>k(i)).join(""):'<div class="coding-tab__notice info">Loading\u2026</div>',o=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
65
  <div class="coding-tab__thread" data-role="thread">${e}</div>
66
66
  <div class="coding-tab__composer">
67
- <textarea data-role="prompt" placeholder="${E(i)}" rows="2"></textarea>
67
+ <textarea data-role="prompt" placeholder="${T(o)}" rows="2"></textarea>
68
68
  <button class="coding-tab__btn primary" data-role="send" ${t.isStreaming?"disabled":""}>${t.isStreaming?"Working\u2026":"Send"}</button>
69
69
  ${t.isStreaming?'<button class="coding-tab__btn danger" data-role="cancel">Stop</button>':""}
70
70
  </div>
71
- `}function S(t){if(t.role==="user"){let h=t.prompt??Z(t);return`<div class="coding-tab__msg user">
71
+ `}function k(t){if(t.role==="user"){let h=t.prompt??Z(t);return`<div class="coding-tab__msg user">
72
72
  <div class="coding-tab__msg-role">You \xB7 ${t.isPlan?"Plan":"Agent"}</div>
73
73
  <div class="coding-tab__msg-body">${g(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=>C(h,t.isPlan)).join(""),e=t.status&&t.status!=="finished"?`<div class="coding-tab__status">${g(t.status)}</div>`:"",i=t.showExecute&&!b()?.isStreaming?`<div class="coding-tab__plan-actions">
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">${g(t.status)}</div>`:"",o=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?X(t.pr):"";return`<div class="coding-tab__msg assistant">
76
+ </div>`:"",i=t.pr?X(t.pr):"";return`<div class="coding-tab__msg assistant">
77
77
  <div class="coding-tab__msg-role">Coding Tab \xB7 ${t.isPlan?"Plan":"Agent"} ${e}</div>
78
- <div class="coding-tab__msg-body">${n}${i}${s}</div>
79
- </div>`}function C(t,n){if(t.kind==="text"){let e=t.text.trim();return e?`<div class="coding-tab__md">${n?z(e):_(e)}</div>`:""}return A(t)}function _(t){return g(t).split(/\n\s*\n/).map(n=>n.replace(/`([^`\n]+)`/g,"<code>$1</code>").replace(/\*\*([^*\n]+)\*\*/g,"<strong>$1</strong>").replace(/\n/g,"<br />")).map(n=>`<p>${n}</p>`).join("")}function A(t){let n=Y(t.name,t.args),e=a.expandedTools.has(t.id),i=e?Q(t):"";return`
78
+ <div class="coding-tab__msg-body">${n}${o}${i}</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):$(e)}</div>`:""}return A(t)}function $(t){return g(t).split(/\n\s*\n/).map(n=>n.replace(/`([^`\n]+)`/g,"<code>$1</code>").replace(/\*\*([^*\n]+)\*\*/g,"<strong>$1</strong>").replace(/\n/g,"<br />")).map(n=>`<p>${n}</p>`).join("")}function A(t){let n=Y(t.name,t.args),e=a.expandedTools.has(t.id),o=e?Q(t):"";return`
80
80
  <div class="coding-tab__tool" data-status="${t.status}" data-tool-id="${t.id}">
81
81
  <button class="coding-tab__tool-header" data-role="toggle-tool" data-tool-id="${t.id}" aria-expanded="${e}">
82
82
  <span class="coding-tab__tool-dot"></span>
83
- ${yt}
83
+ ${_t}
84
84
  <span class="coding-tab__tool-name">${g(t.name)}</span>
85
85
  ${n?`<span class="coding-tab__tool-summary">${g(n)}</span>`:""}
86
86
  </button>
87
- ${i}
87
+ ${o}
88
88
  </div>
89
- `}function Y(t,n){if(!n||typeof n!="object")return"";let e=n,i=t.toLowerCase();if(i.includes("grep")){let s=typeof e.pattern=="string"?e.pattern:typeof e.query=="string"?e.query:"";return s?`"${s}"`:""}if(i.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(i.includes("glob")||i.includes("file_search"))&&typeof e.glob_pattern=="string"?e.glob_pattern:i==="shell"&&typeof e.command=="string"?e.command.slice(0,80):i==="task"&&typeof e.description=="string"?e.description:i.includes("write")&&typeof e.path=="string"||i.includes("edit")&&typeof e.path=="string"||i.includes("delete")&&typeof e.path=="string"?e.path:""}function Q(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>${g(H(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>${g(H(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 H(t){if(typeof t=="string")return t;try{return JSON.stringify(t,null,2)}catch{return String(t)}}function X(t){return`<div class="coding-tab__pr">
89
+ `}function Y(t,n){if(!n||typeof n!="object")return"";let e=n,o=t.toLowerCase();if(o.includes("grep")){let i=typeof e.pattern=="string"?e.pattern:typeof e.query=="string"?e.query:"";return i?`"${i}"`:""}if(o.includes("read")&&typeof e.path=="string"){let i=typeof e.offset=="number"||typeof e.limit=="number"?`:${e.offset??""}-${Number(e.offset??0)+Number(e.limit??0)||""}`:"";return`${e.path}${i}`}return(o.includes("glob")||o.includes("file_search"))&&typeof e.glob_pattern=="string"?e.glob_pattern:o==="shell"&&typeof e.command=="string"?e.command.slice(0,80):o==="task"&&typeof e.description=="string"?e.description:o.includes("write")&&typeof e.path=="string"||o.includes("edit")&&typeof e.path=="string"||o.includes("delete")&&typeof e.path=="string"?e.path:""}function Q(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>${g(H(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>${g(H(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 H(t){if(typeof t=="string")return t;try{return JSON.stringify(t,null,2)}catch{return String(t)}}function X(t){let n=a.mergeStates.get(t.url)??{state:"idle"},e,o="";switch(n.state){case"loading":e='<button class="coding-tab__btn primary" data-state="loading" disabled>Merging\u2026</button>',o='<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>',o=`<div class="coding-tab__pr-status is-success">Merged commit ${g(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(t.url)}">Try merge again</button>`,o=`<div class="coding-tab__pr-status is-error">Merge failed: ${g(n.message)}</div>`;break;default:e=`<button class="coding-tab__btn primary" data-role="merge" data-pr="${T(t.url)}">Merge & Redeploy</button>`}return`<div class="coding-tab__pr">
90
90
  <div class="coding-tab__pr-title">PR #${t.number} opened in ${g(t.owner)}/${g(t.repo)}</div>
91
91
  ${t.title?`<div>${g(t.title)}</div>`:""}
92
- <div style="display:flex;gap:8px;flex-wrap:wrap">
93
- <a class="coding-tab__btn" href="${E(t.url)}" target="_blank" rel="noreferrer">Open in GitHub</a>
94
- <button class="coding-tab__btn primary" data-role="merge" data-pr="${E(t.url)}">Merge & Redeploy</button>
92
+ <div class="coding-tab__pr-actions">
93
+ <a class="coding-tab__btn" href="${T(t.url)}" target="_blank" rel="noreferrer">Open in GitHub</a>
94
+ ${e}
95
95
  </div>
96
+ ${o}
96
97
  </div>`}function Z(t){return t.events.filter(n=>n.kind==="text").map(n=>n.text).join(`
97
98
 
98
- `)}function tt(){o.querySelector('[data-role="model"]')?.addEventListener("change",t=>{a.model=t.target.value}),o.querySelectorAll("[data-mode]").forEach(t=>{t.addEventListener("click",()=>{a.mode=t.dataset.mode,c()})}),o.querySelector('[data-role="repo"]')?.addEventListener("change",t=>{a.repoUrl=t.target.value.trim()}),o.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()}),o.querySelector('[data-role="toggle-sidebar"]')?.addEventListener("click",()=>{a.sidebarOpen=!a.sidebarOpen,c()}),o.querySelectorAll('[data-role="new-chat"]').forEach(t=>t.addEventListener("click",async()=>{await v()&&window.innerWidth<720&&(a.sidebarOpen=!1),c()})),o.querySelectorAll('[data-role="select-chat"]').forEach(t=>t.addEventListener("click",()=>{let n=t.dataset.chatId;M(n)})),o.querySelectorAll('[data-role="rename-chat"]').forEach(t=>t.addEventListener("click",n=>{n.stopPropagation(),$(t.dataset.chatId)})),o.querySelectorAll('[data-role="delete-chat"]').forEach(t=>t.addEventListener("click",n=>{n.stopPropagation(),T(t.dataset.chatId)})),o.querySelector('[data-role="send"]')?.addEventListener("click",()=>q()),o.querySelector('[data-role="prompt"]')?.addEventListener("keydown",t=>{let n=t;n.key==="Enter"&&(n.metaKey||n.ctrlKey)&&(n.preventDefault(),q())}),o.querySelector('[data-role="cancel"]')?.addEventListener("click",()=>nt()),o.querySelectorAll('[data-role="execute"]').forEach(t=>t.addEventListener("click",()=>et())),o.querySelectorAll('[data-role="merge"]').forEach(t=>{t.addEventListener("click",()=>at(t.dataset.pr))}),o.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 q(){let t=b();if(!t&&(t=await v(),!t)||t.isStreaming)return;let n=o.querySelector('[data-role="prompt"]'),e=n?.value.trim()??"";if(!e)return;n&&(n.value=""),t.turns=t.turns??[];let i={id:f(),chatId:t.meta.id,seq:t.turns.length,role:"user",isPlan:a.mode==="plan",status:"finished",events:[{kind:"text",id:f(),text:e}],prompt:e,createdAt:Date.now()};t.turns.push(i);let s={id:f(),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 j("/agent/send",{chatId:t.meta.id,prompt:e,mode:a.mode},t,s)}catch(h){h.name!=="AbortError"&&N(s,h),s.status="error"}finally{t.isStreaming=!1,t.activeRunId=null,t.abort=null,c()}}async function et(){let t=b();if(!t||t.isStreaming)return;t.turns=t.turns??[];let n={id:f(),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 j("/agent/execute",{chatId:t.meta.id},t,n)}catch(e){e.name!=="AbortError"&&N(n,e),n.status="error"}finally{t.isStreaming=!1,t.activeRunId=null,t.abort=null,c()}}async function nt(){let t=b();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 at(t){let n=b();if(n){n.turns=n.turns??[];try{let e=await m("/pr/merge",{method:"POST",body:JSON.stringify({prUrl:t,mergeMethod:"squash"})}),i=e.merged?`Merged commit \`${e.sha.slice(0,7)}\` into the default branch. Railway should redeploy on the next push hook.`:"Merge call returned, but `merged=false`. Check the PR on GitHub.";n.turns.push({id:f(),chatId:n.meta.id,seq:n.turns.length,role:"assistant",isPlan:!1,status:e.merged?"finished":"error",events:[{kind:"text",id:f(),text:i}],createdAt:Date.now()})}catch(e){let i=e instanceof Error?e.message:String(e);n.turns.push({id:f(),chatId:n.meta.id,seq:n.turns.length,role:"assistant",isPlan:!1,status:"error",events:[{kind:"text",id:f(),text:`[merge failed] ${i}`}],createdAt:Date.now()})}finally{c()}}}function N(t,n){let e=n instanceof Error?n.message:String(n);t.events.push({kind:"text",id:f(),text:`[error] ${e}`})}async function j(t,n,e,i){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(),st=new TextDecoder,R="",U=!1;for(;;){let{value:it,done:dt}=await h.read();if(dt)break;R+=st.decode(it,{stream:!0});let D=R.split(`
99
+ `)}function tt(){s.querySelector('[data-role="model"]')?.addEventListener("change",t=>{a.model=t.target.value}),s.querySelectorAll("[data-mode]").forEach(t=>{t.addEventListener("click",()=>{a.mode=t.dataset.mode,c()})}),s.querySelector('[data-role="repo"]')?.addEventListener("change",t=>{a.repoUrl=t.target.value.trim()}),s.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()}),s.querySelector('[data-role="toggle-sidebar"]')?.addEventListener("click",()=>{a.sidebarOpen=!a.sidebarOpen,c()}),s.querySelectorAll('[data-role="new-chat"]').forEach(t=>t.addEventListener("click",async()=>{await b()&&window.innerWidth<720&&(a.sidebarOpen=!1),c()})),s.querySelectorAll('[data-role="select-chat"]').forEach(t=>t.addEventListener("click",()=>{let n=t.dataset.chatId;M(n)})),s.querySelectorAll('[data-role="rename-chat"]').forEach(t=>t.addEventListener("click",n=>{n.stopPropagation(),y(t.dataset.chatId)})),s.querySelectorAll('[data-role="delete-chat"]').forEach(t=>t.addEventListener("click",n=>{n.stopPropagation(),C(t.dataset.chatId)})),s.querySelector('[data-role="send"]')?.addEventListener("click",()=>q()),s.querySelector('[data-role="prompt"]')?.addEventListener("keydown",t=>{let n=t;n.key==="Enter"&&(n.metaKey||n.ctrlKey)&&(n.preventDefault(),q())}),s.querySelector('[data-role="cancel"]')?.addEventListener("click",()=>nt()),s.querySelectorAll('[data-role="execute"]').forEach(t=>t.addEventListener("click",()=>et())),s.querySelectorAll('[data-role="merge"]').forEach(t=>{t.addEventListener("click",()=>at(t.dataset.pr))}),s.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 q(){let t=f();if(!t&&(t=await b(),!t)||t.isStreaming)return;let n=s.querySelector('[data-role="prompt"]'),e=n?.value.trim()??"";if(!e)return;n&&(n.value=""),t.turns=t.turns??[];let o={id:_(),chatId:t.meta.id,seq:t.turns.length,role:"user",isPlan:a.mode==="plan",status:"finished",events:[{kind:"text",id:_(),text:e}],prompt:e,createdAt:Date.now()};t.turns.push(o);let i={id:_(),chatId:t.meta.id,seq:t.turns.length,role:"assistant",isPlan:a.mode==="plan",status:"running",events:[],createdAt:Date.now()};t.turns.push(i),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 j("/agent/send",{chatId:t.meta.id,prompt:e,mode:a.mode},t,i)}catch(h){h.name!=="AbortError"&&N(i,h),i.status="error"}finally{t.isStreaming=!1,t.activeRunId=null,t.abort=null,c()}}async function et(){let t=f();if(!t||t.isStreaming)return;t.turns=t.turns??[];let n={id:_(),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 j("/agent/execute",{chatId:t.meta.id},t,n)}catch(e){e.name!=="AbortError"&&N(n,e),n.status="error"}finally{t.isStreaming=!1,t.activeRunId=null,t.abort=null,c()}}async function nt(){let t=f();if(t){if(t.activeRunId)try{await p("/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 at(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 p("/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:_(),chatId:n.meta.id,seq:n.turns.length,role:"assistant",isPlan:!1,status:"finished",events:[{kind:"text",id:_(),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 o=e instanceof Error?e.message:String(e);a.mergeStates.set(t,{state:"error",message:o})}finally{c()}}}function N(t,n){let e=n instanceof Error?n.message:String(n);t.events.push({kind:"text",id:_(),text:`[error] ${e}`})}async function j(t,n,e,o){e.abort=new AbortController;let i=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(!i.ok||!i.body)throw new Error(`${i.status} ${i.statusText}`);let h=i.body.getReader(),rt=new TextDecoder,R="",U=!1;for(;;){let{value:it,done:dt}=await h.read();if(dt)break;R+=rt.decode(it,{stream:!0});let B=R.split(`
99
100
 
100
- `);R=D.pop()??"";for(let lt of D){let B=lt.split(`
101
- `).find(x=>x.startsWith("data: "));if(B)try{let x=JSON.parse(B.slice(6));U=ot(x,e,i,U),c()}catch(x){console.warn("[coding-tab] bad sse event",x)}}}}function ot(t,n,e,i){switch(t.kind){case"ready":return n.activeRunId=t.runId,!1;case"text":{let s=e.events[e.events.length-1];return i&&s&&s.kind==="text"?s.text+=t.text:e.events.push({kind:"text",id:f(),text:t.text}),!0}case"thinking":return i;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:f(),callId:t.callId,name:t.name,status:t.status,args:t.args,result:t.result}),!1}case"status":return i;case"result":return e.status=t.status,t.pr&&(e.pr=t.pr),e.isPlan&&t.status==="finished"&&(e.showExecute=!0),n.activeRunId=null,!1;case"error":return e.events.push({kind:"text",id:f(),text:`[error] ${t.message}`}),e.status="error",n.activeRunId=null,!1}}async function rt(){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 w(),a.chats.length>0&&!a.activeChatId&&(a.activeChatId=a.chats[0].meta.id,k(a.activeChatId).then(()=>c())),c()}return rt(),{destroy(){for(let t of a.chats)t.abort?.abort();d.removeChild(o)}}}typeof window<"u"&&(window.CodingTab={mountCodingTab:F});return mt(wt);})();
101
+ `);R=B.pop()??"";for(let lt of B){let D=lt.split(`
102
+ `).find(x=>x.startsWith("data: "));if(D)try{let x=JSON.parse(D.slice(6));U=st(x,e,o,U),c()}catch(x){console.warn("[coding-tab] bad sse event",x)}}}}function st(t,n,e,o){switch(t.kind){case"ready":return n.activeRunId=t.runId,!1;case"text":{let i=e.events[e.events.length-1];return o&&i&&i.kind==="text"?i.text+=t.text:e.events.push({kind:"text",id:_(),text:t.text}),!0}case"thinking":return o;case"tool":{let i=e.events.find(h=>h.kind==="tool"&&h.callId===t.callId);return i?(i.status=t.status,t.args!==void 0&&(i.args=t.args),t.result!==void 0&&(i.result=t.result)):e.events.push({kind:"tool",id:_(),callId:t.callId,name:t.name,status:t.status,args:t.args,result:t.result}),!1}case"status":return o;case"result":return e.status=t.status,t.pr&&(e.pr=t.pr),e.isPlan&&t.status==="finished"&&(e.showExecute=!0),n.activeRunId=null,!1;case"error":return e.events.push({kind:"text",id:_(),text:`[error] ${t.message}`}),e.status="error",n.activeRunId=null,!1}}async function ot(){try{let t=await p("/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 p("/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 w(),a.chats.length>0&&!a.activeChatId&&(a.activeChatId=a.chats[0].meta.id,S(a.activeChatId).then(()=>c())),c()}return ot(),{destroy(){for(let t of a.chats)t.abort?.abort();d.removeChild(s)}}}typeof window<"u"&&(window.CodingTab={mountCodingTab:F});return pt(wt);})();
102
103
  //# 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 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/** 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 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 } = {\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 };\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(state.repoUrl)}\" target=\"_blank\" rel=\"noreferrer\" title=\"Locked to this app's repo (set by the server)\">${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>@${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 ? renderPr(turn.pr) : \"\";\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` and **bold**, but 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 .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 renderPr(pr: PrInfo): string {\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 style=\"display:flex;gap:8px;flex-wrap:wrap\">\n <a class=\"coding-tab__btn\" href=\"${escapeAttr(pr.url)}\" target=\"_blank\" rel=\"noreferrer\">Open in GitHub</a>\n <button class=\"coding-tab__btn primary\" data-role=\"merge\" data-pr=\"${escapeAttr(pr.url)}\">Merge & Redeploy</button>\n </div>\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 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 const text = result.merged\n ? `Merged commit \\`${result.sha.slice(0, 7)}\\` into the default branch. Railway should redeploy on the next push hook.`\n : `Merge call returned, but \\`merged=false\\`. Check the PR on GitHub.`;\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: result.merged ? \"finished\" : \"error\",\n events: [{ kind: \"text\", id: cryptoRandomId(), text }],\n createdAt: Date.now(),\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\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: \"error\",\n events: [{ kind: \"text\", id: cryptoRandomId(), text: `[merge failed] ${message}` }],\n createdAt: Date.now(),\n });\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 (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":"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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","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","isPlan","renderMarkdown","renderInlineText","renderToolEvent","para","p","summary","summarizeTool","expanded","detailHtml","renderToolDetail","name","args","a","lower","pattern","lines","argsBlock","formatBlob","resultBlock","value","pr","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 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 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(state.repoUrl)}\" target=\"_blank\" rel=\"noreferrer\" title=\"Locked to this app's repo (set by the server)\">${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 ? renderPr(turn.pr) : \"\";\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` and **bold**, but 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 .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 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 (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":"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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","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","isPlan","renderMarkdown","renderInlineText","renderToolEvent","para","p","summary","summarizeTool","expanded","detailHtml","renderToolDetail","name","args","a","lower","pattern","lines","argsBlock","formatBlob","resultBlock","value","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"]}
package/dist/style.css CHANGED
@@ -19,10 +19,14 @@
19
19
  display: flex;
20
20
  flex-direction: column;
21
21
  height: 100%;
22
- min-height: 480px;
22
+ min-height: 0;
23
+ min-width: 0;
23
24
  border-radius: var(--ct-radius);
24
25
  overflow: hidden;
25
26
  border: 1px solid var(--ct-border);
27
+ /* iOS Safari sometimes needs an explicit minimum so the inner thread can
28
+ shrink to viewport width when embedded inside a flex container. */
29
+ max-width: 100%;
26
30
  }
27
31
  @media (prefers-color-scheme: light) {
28
32
  .coding-tab {
@@ -48,6 +52,7 @@
48
52
  border-bottom: 1px solid var(--ct-border);
49
53
  background: var(--ct-bg-alt);
50
54
  flex-wrap: wrap;
55
+ min-width: 0;
51
56
  }
52
57
  .coding-tab__header select,
53
58
  .coding-tab__header input,
@@ -66,12 +71,14 @@
66
71
  align-items: center;
67
72
  justify-content: center;
68
73
  padding: 4px 7px;
74
+ flex-shrink: 0;
69
75
  }
70
76
  .coding-tab__mode {
71
77
  display: inline-flex;
72
78
  border: 1px solid var(--ct-border);
73
79
  border-radius: 6px;
74
80
  overflow: hidden;
81
+ flex-shrink: 0;
75
82
  }
76
83
  .coding-tab__mode button {
77
84
  border: 0;
@@ -98,6 +105,7 @@
98
105
  font-size: 0.92em;
99
106
  flex: 0 1 auto;
100
107
  min-width: 0;
108
+ max-width: 100%;
101
109
  }
102
110
  .coding-tab__repo-locked:hover { border-color: var(--ct-accent); color: var(--ct-fg); }
103
111
  .coding-tab__repo-locked span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -108,8 +116,10 @@
108
116
  display: flex;
109
117
  align-items: center;
110
118
  gap: 8px;
119
+ min-width: 0;
111
120
  }
112
- .coding-tab__user img { width: 22px; height: 22px; border-radius: 50%; }
121
+ .coding-tab__user img { width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0; }
122
+ .coding-tab__user-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
113
123
  .coding-tab__user button {
114
124
  background: transparent;
115
125
  border: 0;
@@ -117,6 +127,7 @@
117
127
  cursor: pointer;
118
128
  padding: 2px 6px;
119
129
  border-radius: 4px;
130
+ flex-shrink: 0;
120
131
  }
121
132
  .coding-tab__user button:hover { color: var(--ct-fg); background: var(--ct-bg); }
122
133
 
@@ -125,6 +136,8 @@
125
136
  flex: 1;
126
137
  display: flex;
127
138
  min-height: 0;
139
+ min-width: 0;
140
+ position: relative;
128
141
  }
129
142
  .coding-tab__sidebar {
130
143
  width: 240px;
@@ -134,6 +147,7 @@
134
147
  border-right: 1px solid var(--ct-border);
135
148
  background: var(--ct-bg-alt);
136
149
  overflow: hidden;
150
+ min-width: 0;
137
151
  }
138
152
  .coding-tab__new-chat {
139
153
  display: inline-flex;
@@ -149,6 +163,7 @@
149
163
  display: flex;
150
164
  flex-direction: column;
151
165
  gap: 2px;
166
+ min-width: 0;
152
167
  }
153
168
  .coding-tab__chat-empty {
154
169
  font-size: 12px;
@@ -162,6 +177,7 @@
162
177
  border-radius: 6px;
163
178
  overflow: hidden;
164
179
  transition: background 0.1s;
180
+ min-width: 0;
165
181
  }
166
182
  .coding-tab__chat-row:hover { background: var(--ct-bg-row); }
167
183
  .coding-tab__chat-row.is-active { background: var(--ct-bg-row); }
@@ -205,6 +221,7 @@
205
221
  align-items: center;
206
222
  padding-right: 6px;
207
223
  gap: 2px;
224
+ flex-shrink: 0;
208
225
  }
209
226
  .coding-tab__chat-row:hover .coding-tab__chat-row-actions,
210
227
  .coding-tab__chat-row.is-active .coding-tab__chat-row-actions {
@@ -227,8 +244,14 @@
227
244
  flex: 1;
228
245
  display: flex;
229
246
  flex-direction: column;
247
+ /* CRITICAL: without this, any wide child (long URL in tool args, code block,
248
+ overflowing pre) blows out the flex container width and forces the whole
249
+ thread to scroll horizontally — making text wrap word-by-word in the tiny
250
+ visible strip. */
230
251
  min-width: 0;
252
+ min-height: 0;
231
253
  background: var(--ct-bg);
254
+ overflow: hidden;
232
255
  }
233
256
  .coding-tab__pane-empty {
234
257
  margin: auto;
@@ -257,20 +280,41 @@
257
280
  box-shadow: 4px 0 16px rgba(0, 0, 0, 0.3);
258
281
  }
259
282
  .coding-tab__sidebar.is-open { transform: translateX(0); }
260
- .coding-tab__body { position: relative; }
283
+ /* Hide the repo pill on mobile — it's mostly redundant since the chat is
284
+ already locked to that repo by the server. */
285
+ .coding-tab__repo-locked { display: none; }
286
+ /* Collapse the user block to just avatar + sign-out, drop the @login text. */
287
+ .coding-tab__user-name { display: none; }
288
+ .coding-tab__user { gap: 4px; }
289
+ .coding-tab__header { gap: 6px; padding: 6px 10px; }
290
+ .coding-tab__header select { max-width: 130px; }
291
+ }
292
+
293
+ @media (max-width: 480px) {
294
+ .coding-tab__user img { display: none; }
295
+ .coding-tab__user button { padding: 4px 8px; font-size: 11px; }
261
296
  }
262
297
 
263
298
  /* ───────── thread / messages ───────── */
264
299
  .coding-tab__thread {
265
300
  flex: 1;
266
301
  overflow-y: auto;
302
+ overflow-x: hidden;
303
+ -webkit-overflow-scrolling: touch;
267
304
  padding: 16px;
268
305
  display: flex;
269
306
  flex-direction: column;
270
307
  gap: 14px;
308
+ min-width: 0;
271
309
  }
272
310
 
273
- .coding-tab__msg { display: flex; flex-direction: column; gap: 4px; }
311
+ .coding-tab__msg {
312
+ display: flex;
313
+ flex-direction: column;
314
+ gap: 4px;
315
+ min-width: 0;
316
+ max-width: 100%;
317
+ }
274
318
  .coding-tab__msg-role {
275
319
  font-size: 11px;
276
320
  text-transform: uppercase;
@@ -283,9 +327,12 @@
283
327
  border-radius: 8px;
284
328
  padding: 10px 12px;
285
329
  word-wrap: break-word;
330
+ overflow-wrap: anywhere;
286
331
  display: flex;
287
332
  flex-direction: column;
288
333
  gap: 8px;
334
+ min-width: 0;
335
+ max-width: 100%;
289
336
  }
290
337
  .coding-tab__msg.user .coding-tab__msg-body {
291
338
  background: var(--ct-accent);
@@ -295,6 +342,7 @@
295
342
  max-width: 85%;
296
343
  white-space: pre-wrap;
297
344
  display: block;
345
+ word-break: break-word;
298
346
  }
299
347
  .coding-tab__msg.user { align-items: flex-end; }
300
348
  .coding-tab__msg-pending {
@@ -302,8 +350,14 @@
302
350
  font-style: italic;
303
351
  }
304
352
 
353
+ @media (max-width: 720px) {
354
+ .coding-tab__thread { padding: 12px; gap: 12px; }
355
+ .coding-tab__msg-body { padding: 9px 11px; }
356
+ .coding-tab__msg.user .coding-tab__msg-body { max-width: 92%; }
357
+ }
358
+
305
359
  /* ───────── markdown ───────── */
306
- .coding-tab__md { display: block; }
360
+ .coding-tab__md { display: block; min-width: 0; max-width: 100%; }
307
361
  .coding-tab__md > * + * { margin-top: 8px; }
308
362
  .coding-tab__md p { margin: 0; }
309
363
  .coding-tab__md h1,
@@ -313,6 +367,7 @@
313
367
  margin: 4px 0 4px;
314
368
  font-weight: 600;
315
369
  line-height: 1.25;
370
+ word-break: break-word;
316
371
  }
317
372
  .coding-tab__md h1 { font-size: 1.3em; }
318
373
  .coding-tab__md h2 { font-size: 1.18em; border-bottom: 1px solid var(--ct-border); padding-bottom: 4px; }
@@ -340,6 +395,7 @@
340
395
  border-radius: 4px;
341
396
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
342
397
  font-size: 0.9em;
398
+ word-break: break-word;
343
399
  }
344
400
  .coding-tab__md pre {
345
401
  background: rgba(127, 127, 127, 0.12);
@@ -347,18 +403,23 @@
347
403
  border-radius: 6px;
348
404
  padding: 10px 12px;
349
405
  margin: 0;
406
+ max-width: 100%;
350
407
  overflow-x: auto;
408
+ -webkit-overflow-scrolling: touch;
351
409
  }
352
410
  .coding-tab__md pre code {
353
411
  background: transparent;
354
412
  padding: 0;
355
413
  font-size: 0.88em;
356
414
  line-height: 1.45;
415
+ word-break: normal;
416
+ white-space: pre;
357
417
  }
358
418
  .coding-tab__md a {
359
419
  color: var(--ct-accent);
360
420
  text-decoration: underline;
361
421
  text-underline-offset: 2px;
422
+ word-break: break-all;
362
423
  }
363
424
 
364
425
  /* ───────── tool calls (expandable) ───────── */
@@ -369,6 +430,8 @@
369
430
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
370
431
  font-size: 12px;
371
432
  overflow: hidden;
433
+ min-width: 0;
434
+ max-width: 100%;
372
435
  }
373
436
  .coding-tab__tool-header {
374
437
  width: 100%;
@@ -382,6 +445,7 @@
382
445
  cursor: pointer;
383
446
  text-align: left;
384
447
  font: inherit;
448
+ min-width: 0;
385
449
  }
386
450
  .coding-tab__tool-header:hover { background: rgba(127, 127, 127, 0.08); }
387
451
  .coding-tab__tool-dot {
@@ -414,6 +478,7 @@
414
478
  overflow: hidden;
415
479
  text-overflow: ellipsis;
416
480
  min-width: 0;
481
+ flex: 1 1 auto;
417
482
  }
418
483
  .coding-tab__tool-detail {
419
484
  padding: 8px 12px 10px;
@@ -422,8 +487,10 @@
422
487
  flex-direction: column;
423
488
  gap: 8px;
424
489
  background: rgba(127, 127, 127, 0.04);
490
+ min-width: 0;
491
+ max-width: 100%;
425
492
  }
426
- .coding-tab__tool-section { display: flex; flex-direction: column; gap: 4px; }
493
+ .coding-tab__tool-section { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
427
494
  .coding-tab__tool-label {
428
495
  font-size: 10px;
429
496
  letter-spacing: 0.05em;
@@ -437,9 +504,14 @@
437
504
  border: 1px solid var(--ct-border);
438
505
  border-radius: 4px;
439
506
  max-height: 360px;
507
+ max-width: 100%;
440
508
  overflow: auto;
509
+ -webkit-overflow-scrolling: touch;
510
+ /* Wrap long lines within the visible width so they don't push the parent
511
+ wider on mobile; horizontal scroll still works for monolithic code. */
441
512
  white-space: pre-wrap;
442
513
  word-break: break-word;
514
+ overflow-wrap: anywhere;
443
515
  font-size: 11px;
444
516
  line-height: 1.4;
445
517
  }
@@ -451,6 +523,13 @@
451
523
  font-style: italic;
452
524
  }
453
525
 
526
+ @media (max-width: 720px) {
527
+ .coding-tab__tool { font-size: 11.5px; }
528
+ .coding-tab__tool-header { padding: 7px 8px; gap: 6px; }
529
+ .coding-tab__tool-detail { padding: 6px 8px 8px; }
530
+ .coding-tab__tool-detail pre { max-height: 240px; padding: 6px 8px; font-size: 10.5px; }
531
+ }
532
+
454
533
  @keyframes ct-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
455
534
 
456
535
  /* ───────── plan + PR + composer ───────── */
@@ -458,6 +537,7 @@
458
537
  display: flex;
459
538
  gap: 8px;
460
539
  margin-top: 4px;
540
+ flex-wrap: wrap;
461
541
  }
462
542
  .coding-tab__btn {
463
543
  font: inherit;
@@ -470,11 +550,30 @@
470
550
  display: inline-flex;
471
551
  align-items: center;
472
552
  gap: 6px;
553
+ position: relative;
554
+ transition: opacity 0.15s, background 0.15s, border-color 0.15s;
473
555
  }
474
556
  .coding-tab__btn:hover { border-color: var(--ct-accent); }
475
557
  .coding-tab__btn.primary { background: var(--ct-accent); color: var(--ct-accent-fg); border-color: transparent; }
476
558
  .coding-tab__btn.danger { color: var(--ct-danger); }
477
- .coding-tab__btn:disabled { opacity: 0.5; cursor: not-allowed; }
559
+ .coding-tab__btn.success { background: var(--ct-success); color: var(--ct-accent-fg); border-color: transparent; }
560
+ .coding-tab__btn:disabled { opacity: 0.6; cursor: not-allowed; }
561
+ .coding-tab__btn[data-state="loading"] { padding-left: 30px; }
562
+ .coding-tab__btn[data-state="loading"]::before {
563
+ content: "";
564
+ position: absolute;
565
+ left: 12px; top: 50%;
566
+ width: 12px; height: 12px;
567
+ margin-top: -6px;
568
+ border: 2px solid currentColor;
569
+ border-right-color: transparent;
570
+ border-radius: 50%;
571
+ animation: ct-spin 0.7s linear infinite;
572
+ opacity: 0.85;
573
+ }
574
+ @keyframes ct-spin {
575
+ to { transform: rotate(360deg); }
576
+ }
478
577
 
479
578
  .coding-tab__pr {
480
579
  margin-top: 4px;
@@ -485,13 +584,29 @@
485
584
  display: flex;
486
585
  flex-direction: column;
487
586
  gap: 8px;
587
+ min-width: 0;
588
+ max-width: 100%;
488
589
  }
489
590
  .coding-tab__pr-title {
490
591
  font-weight: 600;
491
592
  display: flex;
492
593
  align-items: center;
493
594
  gap: 6px;
595
+ flex-wrap: wrap;
596
+ word-break: break-word;
597
+ }
598
+ .coding-tab__pr-actions {
599
+ display: flex;
600
+ gap: 8px;
601
+ flex-wrap: wrap;
602
+ }
603
+ .coding-tab__pr-status {
604
+ font-size: 12px;
605
+ color: var(--ct-fg-dim);
606
+ font-style: italic;
494
607
  }
608
+ .coding-tab__pr-status.is-success { color: var(--ct-success); font-style: normal; }
609
+ .coding-tab__pr-status.is-error { color: var(--ct-danger); font-style: normal; }
495
610
 
496
611
  .coding-tab__composer {
497
612
  display: flex;
@@ -499,9 +614,11 @@
499
614
  padding: 10px 12px;
500
615
  border-top: 1px solid var(--ct-border);
501
616
  background: var(--ct-bg-alt);
617
+ min-width: 0;
502
618
  }
503
619
  .coding-tab__composer textarea {
504
620
  flex: 1;
621
+ min-width: 0;
505
622
  min-height: 44px;
506
623
  max-height: 200px;
507
624
  resize: vertical;
@@ -512,7 +629,13 @@
512
629
  border-radius: 6px;
513
630
  padding: 8px 10px;
514
631
  }
515
- .coding-tab__composer button { align-self: flex-end; }
632
+ .coding-tab__composer button { align-self: flex-end; flex-shrink: 0; }
633
+
634
+ @media (max-width: 720px) {
635
+ .coding-tab__composer { padding: 8px 10px; gap: 6px; }
636
+ .coding-tab__composer textarea { font-size: 16px; /* iOS won't auto-zoom inputs >= 16px */ }
637
+ .coding-tab__btn { padding: 7px 12px; }
638
+ }
516
639
 
517
640
  /* ───────── sign in / notices ───────── */
518
641
  .coding-tab__signin {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstax/coding-tab",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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": {