tina4-nodejs 3.13.97 → 3.13.99

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.
Files changed (96) hide show
  1. package/CLAUDE.md +60 -25
  2. package/package.json +1 -2
  3. package/packages/cli/dist/bin.js +20620 -18995
  4. package/packages/cli/src/bin.ts +28 -71
  5. package/packages/cli/src/commands/migrate.ts +36 -75
  6. package/packages/cli/src/commands/migrateRollback.ts +10 -1
  7. package/packages/cli/src/commands/test.ts +92 -21
  8. package/packages/core/dist/index.js +20459 -18815
  9. package/packages/core/public/js/tina4-dev-admin.min.js +23 -19
  10. package/packages/core/src/ai.ts +28 -12
  11. package/packages/core/src/api.ts +13 -5
  12. package/packages/core/src/background.ts +9 -3
  13. package/packages/core/src/devAdmin.ts +135 -20
  14. package/packages/core/src/dispatchPipeline.ts +185 -1
  15. package/packages/core/src/docs.ts +33 -5
  16. package/packages/core/src/env.ts +1 -1
  17. package/packages/core/src/errorOverlay.ts +39 -48
  18. package/packages/core/src/fakeData.ts +15 -0
  19. package/packages/core/src/index.ts +17 -6
  20. package/packages/core/src/logger.ts +892 -572
  21. package/packages/core/src/mcp.ts +9 -1
  22. package/packages/core/src/messenger.ts +31 -4
  23. package/packages/core/src/middleware.ts +169 -43
  24. package/packages/core/src/portTakeover.ts +232 -0
  25. package/packages/core/src/request.ts +57 -8
  26. package/packages/core/src/response.ts +67 -0
  27. package/packages/core/src/router.ts +35 -7
  28. package/packages/core/src/server.ts +450 -190
  29. package/packages/core/src/static.ts +81 -12
  30. package/packages/core/src/testClient.ts +126 -137
  31. package/packages/core/src/testing.ts +16 -12
  32. package/packages/core/src/types.ts +21 -9
  33. package/packages/core/src/version.ts +66 -0
  34. package/packages/core/src/websocket.ts +2 -2
  35. package/packages/core/src/websocketBackplane.ts +2 -2
  36. package/packages/frond/dist/index.js +31 -13
  37. package/packages/frond/src/engine.ts +39 -7
  38. package/packages/orm/dist/index.js +10879 -9258
  39. package/packages/orm/src/adapters/firebird.ts +200 -27
  40. package/packages/orm/src/adapters/mongodb.ts +160 -10
  41. package/packages/orm/src/adapters/mssql.ts +38 -11
  42. package/packages/orm/src/adapters/mysql.ts +24 -1
  43. package/packages/orm/src/adapters/odbc.ts +127 -29
  44. package/packages/orm/src/adapters/postgres.ts +18 -0
  45. package/packages/orm/src/adapters/sqlite.ts +93 -14
  46. package/packages/orm/src/autoCrud.ts +72 -8
  47. package/packages/orm/src/baseModel.ts +323 -71
  48. package/packages/orm/src/cachedDatabase.ts +48 -1
  49. package/packages/orm/src/database.ts +162 -59
  50. package/packages/orm/src/fakeData.ts +6 -2
  51. package/packages/orm/src/index.ts +4 -1
  52. package/packages/orm/src/migration.ts +95 -52
  53. package/packages/orm/src/query.ts +16 -4
  54. package/packages/orm/src/seeder.ts +43 -25
  55. package/packages/orm/src/sqlTranslator.ts +104 -19
  56. package/packages/orm/src/types.ts +97 -21
  57. package/packages/orm/src/validation.ts +5 -1
  58. package/packages/swagger/dist/index.js +3 -2
  59. package/packages/swagger/src/generator.ts +19 -4
  60. package/packages/swagger/src/ui.ts +6 -4
  61. package/types/cli/src/bin.d.ts +0 -22
  62. package/types/core/src/api.d.ts +11 -4
  63. package/types/core/src/background.d.ts +5 -2
  64. package/types/core/src/devAdmin.d.ts +35 -0
  65. package/types/core/src/dispatchPipeline.d.ts +41 -1
  66. package/types/core/src/errorOverlay.d.ts +13 -13
  67. package/types/core/src/index.d.ts +9 -6
  68. package/types/core/src/logger.d.ts +111 -185
  69. package/types/core/src/middleware.d.ts +40 -5
  70. package/types/core/src/portTakeover.d.ts +50 -0
  71. package/types/core/src/request.d.ts +15 -0
  72. package/types/core/src/response.d.ts +29 -0
  73. package/types/core/src/server.d.ts +92 -0
  74. package/types/core/src/testClient.d.ts +29 -3
  75. package/types/core/src/testing.d.ts +16 -12
  76. package/types/core/src/types.d.ts +21 -9
  77. package/types/core/src/version.d.ts +11 -0
  78. package/types/core/src/websocketBackplane.d.ts +1 -1
  79. package/types/frond/src/engine.d.ts +10 -0
  80. package/types/orm/src/adapters/firebird.d.ts +61 -2
  81. package/types/orm/src/adapters/mongodb.d.ts +20 -0
  82. package/types/orm/src/adapters/mssql.d.ts +11 -0
  83. package/types/orm/src/adapters/mysql.d.ts +11 -0
  84. package/types/orm/src/adapters/odbc.d.ts +35 -4
  85. package/types/orm/src/adapters/postgres.d.ts +11 -0
  86. package/types/orm/src/adapters/sqlite.d.ts +23 -4
  87. package/types/orm/src/baseModel.d.ts +45 -25
  88. package/types/orm/src/cachedDatabase.d.ts +27 -1
  89. package/types/orm/src/database.d.ts +56 -6
  90. package/types/orm/src/index.d.ts +3 -2
  91. package/types/orm/src/migration.d.ts +23 -5
  92. package/types/orm/src/query.d.ts +3 -0
  93. package/types/orm/src/seeder.d.ts +15 -2
  94. package/types/orm/src/sqlTranslator.d.ts +17 -4
  95. package/types/orm/src/types.d.ts +75 -16
  96. package/packages/core/src/errorOverlay.test.ts +0 -122
@@ -1,4 +1,4 @@
1
- var i2=Object.defineProperty;var n2=(Ei,L,Q)=>L in Ei?i2(Ei,L,{enumerable:!0,configurable:!0,writable:!0,value:Q}):Ei[L]=Q;var xa=(Ei,L,Q)=>n2(Ei,typeof L!="symbol"?L+"":L,Q);(function(){"use strict";const Ei="/__dev/api";async function L(i,e="GET",t){const n={method:e,headers:{}};t&&(n.headers["Content-Type"]="application/json",n.body=JSON.stringify(t));const r=await fetch(Ei+i,n),s=await r.text();let o;if(s)try{o=JSON.parse(s)}catch{o=s}if(!r.ok){const a=o&&typeof o=="object"?o.error||o.message||JSON.stringify(o):typeof o=="string"&&o?o.slice(0,200):"";throw new Error(a?`${r.status}: ${a}`:`HTTP ${r.status}`)}return o}function Q(i){const e=document.createElement("span");return e.textContent=i,e.innerHTML}const kd={python:{color:"#3b82f6",name:"Python"},php:{color:"#8b5cf6",name:"PHP"},ruby:{color:"#ef4444",name:"Ruby"},nodejs:{color:"#22c55e",name:"Node.js"}};function oy(){const i=document.getElementById("app"),e=(i==null?void 0:i.dataset.framework)??"python",t=i==null?void 0:i.dataset.color,n=kd[e]??kd.python;return{framework:e,color:t??n.color,name:n.name}}function ay(i){const e=document.documentElement;e.style.setProperty("--primary",i.color),e.style.setProperty("--bg","#0f172a"),e.style.setProperty("--surface","#1e293b"),e.style.setProperty("--border","#334155"),e.style.setProperty("--text","#e2e8f0"),e.style.setProperty("--muted","#94a3b8"),e.style.setProperty("--success","#22c55e"),e.style.setProperty("--danger","#ef4444"),e.style.setProperty("--warn","#f59e0b"),e.style.setProperty("--info","#3b82f6")}const ly=`
1
+ var n2=Object.defineProperty;var r2=(Ei,L,Q)=>L in Ei?n2(Ei,L,{enumerable:!0,configurable:!0,writable:!0,value:Q}):Ei[L]=Q;var xa=(Ei,L,Q)=>r2(Ei,typeof L!="symbol"?L+"":L,Q);(function(){"use strict";const Ei="/__dev/api";async function L(i,e="GET",t){const n={method:e,headers:{}};t&&(n.headers["Content-Type"]="application/json",n.body=JSON.stringify(t));const r=await fetch(Ei+i,n),s=await r.text();let o;if(s)try{o=JSON.parse(s)}catch{o=s}if(!r.ok){const a=o&&typeof o=="object"?o.error||o.message||JSON.stringify(o):typeof o=="string"&&o?o.slice(0,200):"";throw new Error(a?`${r.status}: ${a}`:`HTTP ${r.status}`)}return o}function Q(i){const e=document.createElement("span");return e.textContent=i,e.innerHTML}const kd={python:{color:"#3b82f6",name:"Python"},php:{color:"#8b5cf6",name:"PHP"},ruby:{color:"#ef4444",name:"Ruby"},nodejs:{color:"#22c55e",name:"Node.js"}};function oy(){const i=document.getElementById("app"),e=(i==null?void 0:i.dataset.framework)??"python",t=i==null?void 0:i.dataset.color,n=kd[e]??kd.python;return{framework:e,color:t??n.color,name:n.name}}function ay(i){const e=document.documentElement;e.style.setProperty("--primary",i.color),e.style.setProperty("--bg","#0f172a"),e.style.setProperty("--surface","#1e293b"),e.style.setProperty("--border","#334155"),e.style.setProperty("--text","#e2e8f0"),e.style.setProperty("--muted","#94a3b8"),e.style.setProperty("--success","#22c55e"),e.style.setProperty("--danger","#ef4444"),e.style.setProperty("--warn","#f59e0b"),e.style.setProperty("--info","#3b82f6")}const ly=`
2
2
  * { margin: 0; padding: 0; box-sizing: border-box; }
3
3
  body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--text); }
4
4
 
@@ -580,7 +580,7 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
580
580
  </div>
581
581
  </div>
582
582
  </div>
583
- `,UZ(),Bz(),Ue(".").then(()=>ZZ()),Mi("plan_current",{}).then(e=>{$a(e.ok&&e.result||null)}),EQ(),vQ(),bz(yz()),wz(),GQ(),Tz().catch(()=>{});try{localStorage.removeItem("tina4.editor.chatHistory.v1")}catch{}}const TQ="tina4.editor.state";function nd(){try{const i={openPaths:ee.map(e=>e.path),activeFile:B,activeDir:ss,expandedDirs:Array.from(fi)};localStorage.setItem(TQ,JSON.stringify(i))}catch{}}async function ZZ(){let i;try{i=JSON.parse(localStorage.getItem(TQ)||"{}")}catch{return}for(const e of i.expandedDirs||[])fi.add(e),Kn.has(e)||await Ue(e);i.activeDir&&(ss=i.activeDir);for(const e of i.openPaths||[])try{await ui(e)}catch{}i.activeFile&&ee.some(e=>e.path===i.activeFile)&&ls(i.activeFile),os()}async function Ue(i){try{const e=await L(`/files?path=${encodeURIComponent(i)}`);if(e.branch){const t=document.getElementById("editor-branch");t&&(t.textContent=`⎇ ${e.branch}`)}Kn.set(i,e.entries||[]),os()}catch(e){console.error("Failed to load file tree:",e)}}function os(){const i=document.getElementById("editor-file-tree");i&&(i.innerHTML=XQ(".",0))}async function zZ(){const i=document.getElementById("editor-file-tree");if(!i)return;const e=Array.from(new Set([".",...Array.from(Kn.keys()),...Array.from(fi)])),t=await Promise.all(e.map(async o=>{try{const a=await L(`/files?path=${encodeURIComponent(o)}`);if(a!=null&&a.branch){const l=document.getElementById("editor-branch");l&&(l.textContent=`⎇ ${a.branch}`)}return[o,(a==null?void 0:a.entries)||[]]}catch{return null}})),n=new Map;for(const o of t){if(!o)continue;const[a,l]=o;Kn.set(a,l);for(const O of l)n.set(O.path,O.git_status||"clean")}const r=["git-clean","git-untracked","git-modified","git-added","git-deleted"],s=i.querySelectorAll(".tree-item[data-path]");for(const o of Array.from(s)){const a=o.dataset.path;if(!a)continue;const l=n.get(a);l!==void 0&&(o.classList.remove(...r),o.classList.add(`git-${l}`),qZ(o,l))}}function qZ(i,e){const n={untracked:"U",modified:"M",added:"A",deleted:"D"}[e];let r=i.querySelector(".tree-git-dot");if(!n){r&&r.remove();return}r||(r=document.createElement("span"),r.className="tree-git-dot",i.appendChild(r)),r.title=e,r.textContent=n}let rd=0,as=null;function vQ(){const e=`${location.protocol==="https:"?"wss":"ws"}://${location.host}/__dev_reload`;try{as=new WebSocket(e),as.addEventListener("message",t=>{try{const n=typeof t.data=="string"?JSON.parse(t.data):null;n&&(n.type==="reload"||n.type==="change")&&od()}catch{od()}}),as.addEventListener("close",()=>{as=null,setTimeout(()=>vQ(),5e3)})}catch{as=null}setInterval(async()=>{try{const t=await fetch("/__dev/api/mtime");if(!t.ok)return;const n=await t.json(),r=typeof n.mtime=="number"?n.mtime:0;r>rd&&(rd>0&&od(),rd=r)}catch{}},3e3)}let sd=null;function od(){sd===null&&(sd=window.setTimeout(async()=>{sd=null,await zZ().catch(()=>{})},300))}function XQ(i,e){const t=Kn.get(i);if(!t)return"";fi.has(i);let n="";const r=[...t].sort((s,o)=>s.is_dir!==o.is_dir?s.is_dir?-1:1:s.name.localeCompare(o.name));for(const s of r){const o=`git-${s.git_status||"clean"}`,a=e*16,l=Q(s.path);if(s.is_dir){const O=fi.has(s.path),d=s.has_children!==!1?O?"▾":"▸":" ",h=_Q(s.git_status),f=ss===s.path;n+=`<div class="tree-item tree-dir ${o} ${f?"active":""}" style="padding-left:${a}px"
583
+ `,UZ(),Nz(),Ue(".").then(()=>ZZ()),Mi("plan_current",{}).then(e=>{$a(e.ok&&e.result||null)}),EQ(),vQ(),Sz(bz()),Pz(),GQ(),vz().catch(()=>{});try{localStorage.removeItem("tina4.editor.chatHistory.v1")}catch{}}const TQ="tina4.editor.state";function nd(){try{const i={openPaths:ee.map(e=>e.path),activeFile:B,activeDir:ss,expandedDirs:Array.from(fi)};localStorage.setItem(TQ,JSON.stringify(i))}catch{}}async function ZZ(){let i;try{i=JSON.parse(localStorage.getItem(TQ)||"{}")}catch{return}for(const e of i.expandedDirs||[])fi.add(e),Kn.has(e)||await Ue(e);i.activeDir&&(ss=i.activeDir);for(const e of i.openPaths||[])try{await ui(e)}catch{}i.activeFile&&ee.some(e=>e.path===i.activeFile)&&ls(i.activeFile),os()}async function Ue(i){try{const e=await L(`/files?path=${encodeURIComponent(i)}`);if(e.branch){const t=document.getElementById("editor-branch");t&&(t.textContent=`⎇ ${e.branch}`)}Kn.set(i,e.entries||[]),os()}catch(e){console.error("Failed to load file tree:",e)}}function os(){const i=document.getElementById("editor-file-tree");i&&(i.innerHTML=XQ(".",0))}async function zZ(){const i=document.getElementById("editor-file-tree");if(!i)return;const e=Array.from(new Set([".",...Array.from(Kn.keys()),...Array.from(fi)])),t=await Promise.all(e.map(async o=>{try{const a=await L(`/files?path=${encodeURIComponent(o)}`);if(a!=null&&a.branch){const l=document.getElementById("editor-branch");l&&(l.textContent=`⎇ ${a.branch}`)}return[o,(a==null?void 0:a.entries)||[]]}catch{return null}})),n=new Map;for(const o of t){if(!o)continue;const[a,l]=o;Kn.set(a,l);for(const O of l)n.set(O.path,O.git_status||"clean")}const r=["git-clean","git-untracked","git-modified","git-added","git-deleted"],s=i.querySelectorAll(".tree-item[data-path]");for(const o of Array.from(s)){const a=o.dataset.path;if(!a)continue;const l=n.get(a);l!==void 0&&(o.classList.remove(...r),o.classList.add(`git-${l}`),qZ(o,l))}}function qZ(i,e){const n={untracked:"U",modified:"M",added:"A",deleted:"D"}[e];let r=i.querySelector(".tree-git-dot");if(!n){r&&r.remove();return}r||(r=document.createElement("span"),r.className="tree-git-dot",i.appendChild(r)),r.title=e,r.textContent=n}let rd=0,as=null;function vQ(){const e=`${location.protocol==="https:"?"wss":"ws"}://${location.host}/__dev_reload`;try{as=new WebSocket(e),as.addEventListener("message",t=>{try{const n=typeof t.data=="string"?JSON.parse(t.data):null;n&&(n.type==="reload"||n.type==="change")&&od()}catch{od()}}),as.addEventListener("close",()=>{as=null,setTimeout(()=>vQ(),5e3)})}catch{as=null}setInterval(async()=>{try{const t=await fetch("/__dev/api/mtime");if(!t.ok)return;const n=await t.json(),r=typeof n.mtime=="number"?n.mtime:0;r>rd&&(rd>0&&od(),rd=r)}catch{}},3e3)}let sd=null;function od(){sd===null&&(sd=window.setTimeout(async()=>{sd=null,await zZ().catch(()=>{})},300))}function XQ(i,e){const t=Kn.get(i);if(!t)return"";fi.has(i);let n="";const r=[...t].sort((s,o)=>s.is_dir!==o.is_dir?s.is_dir?-1:1:s.name.localeCompare(o.name));for(const s of r){const o=`git-${s.git_status||"clean"}`,a=e*16,l=Q(s.path);if(s.is_dir){const O=fi.has(s.path),d=s.has_children!==!1?O?"▾":"▸":" ",h=_Q(s.git_status),f=ss===s.path;n+=`<div class="tree-item tree-dir ${o} ${f?"active":""}" style="padding-left:${a}px"
584
584
  data-path="${l}"
585
585
  onclick="window.__editorToggleDir('${l}')"
586
586
  oncontextmenu="event.preventDefault();window.__editorCtxMenu(event,'${l}',true)">
@@ -972,7 +972,7 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
972
972
  <span style="opacity:0.6">${t}/${n}${s?` · ${s}%`:""}</span>
973
973
  </span>
974
974
  <button class="btn btn-sm" onclick="window.__editorPlanRun('${Q(i.current)}')" ${o?"disabled":""} title="Hand this plan to the Rust supervisor and let it execute remaining steps" style="font-size:0.65rem;padding:1px 6px;line-height:1;${o?"opacity:0.35;cursor:not-allowed":""}">▶</button>
975
- </span>`}let mi=null;async function LZ(i){mi==null||mi.abort(),mi=new AbortController;const e=document.getElementById("editor-ai-messages");if(!e)return;const t=document.createElement("div");t.className="ai-msg ai-bot",t.style.cssText="padding:0.4rem 0.6rem;font-size:0.72rem;border-left:2px solid var(--info,#89b4fa);background:rgba(137,180,250,0.08)",t.innerHTML=`▶ <strong>Executing plan</strong> <code>plan/${Q(i)}</code> via Rust supervisor… <button class="btn btn-sm" onclick="window.__editorPlanStop()" style="font-size:0.6rem;padding:1px 6px;margin-left:0.5rem">Stop</button>`,e.appendChild(t);const n=document.createElement("div");n.className="ai-msg ai-bot",n.style.cssText="padding:0.35rem 0.6rem;font-size:0.7rem;opacity:0.8;font-family:var(--font-mono,monospace)",n.textContent="starting…",e.appendChild(n);const r=[];try{await VS(`plan/${i}`,o=>{var a;if(o.event==="status")n.textContent=`[${o.agent||"…"}] ${o.text||""}`;else if(o.event==="message"){const l=document.createElement("div");l.className="ai-msg ai-bot",l.style.cssText="padding:0.45rem 0.6rem;font-size:0.75rem";const O=(o.content||"").replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/\n/g,"<br>");l.innerHTML=O,e.appendChild(l),(a=o.files_changed)!=null&&a.length&&r.push(...o.files_changed)}else o.event==="error"&&(n.textContent=`✗ ${o.text||"agent error"}`,n.style.color="var(--danger,#f38ba8)");e.scrollTop=e.scrollHeight},{signal:mi.signal}),n.textContent=r.length?`✓ done — ${r.length} file${r.length===1?"":"s"} touched`:"✓ done",n.style.color="var(--success,#a6e3a1)",await Ue(".");const s=await Mi("plan_current",{});$a(s.ok&&s.result||null)}catch(s){(s==null?void 0:s.name)==="AbortError"?n.textContent="⏹ stopped by user":(n.textContent=`✗ ${(s==null?void 0:s.message)||s}`,n.style.color="var(--danger,#f38ba8)")}finally{mi=null}}function IZ(){mi==null||mi.abort()}const CQ="tina4.editor.dismissedThoughts";function YQ(){try{const i=localStorage.getItem(CQ);if(!i)return new Set;const e=JSON.parse(i);return new Set(Array.isArray(e)?e:[])}catch{return new Set}}function GZ(i){const e=Array.from(i).slice(-200);try{localStorage.setItem(CQ,JSON.stringify(e))}catch{}}const DZ=new Set(["hey","hi","hello","there","team","folks","everyone","i","we","you","they","our","us","my","your","their","noticed","notice","saw","see","spotted","found","thought","that","this","these","those","it","its","the","a","an","and","or","but","for","of","to","in","on","at","with","as","by","from","have","has","had","having","is","are","was","were","be","been","being","do","does","did","does","done","can","could","should","would","will","may","might","must","so","just","even","still","also","too","not","no","yes","project","projects","file","files","new","old","make","makes","made","making","know","knows","knew","known","tricky","bit","exactly","some","any","all","each","every","what","which","why","how","when","where","who","if","then","else","than","because","set","up","down","over","under","out","into","onto","developer","developers","dev","devs","user","users","environment","env"]);function WQ(i){const e=(i||"").toLowerCase().replace(/[^a-z0-9._/-]+/g," ").split(/\s+/).map(s=>s.replace(/^[._\-/]+|[._\-/]+$/g,"")).filter(s=>s.length>1),t=e.filter(s=>s.includes(".")||s.includes("/"));if(t.length)return Array.from(new Set(t)).sort().slice(0,6).join("|");const n=e.filter(s=>s.length>2&&!DZ.has(s));return Array.from(new Set(n)).sort().slice(0,4).join("|")}async function EQ(){const i=document.getElementById("editor-thoughts-banner"),e=document.getElementById("thoughts-empty-state"),t=document.getElementById("thoughts-toolbar"),n=document.getElementById("thoughts-toolbar-count");if(!i)return;let r=[];try{r=await CS()}catch{r=[]}const s=YQ(),o=new Set,a=[];for(const l of r){const O=WQ(l.message);s.has(O)||o.has(O)||(o.add(O),a.push(l))}if(Sz("thoughts",a.length||null,a.length>0),!a.length){i.style.display="none",i.innerHTML="",e&&(e.style.display=""),t&&t.setAttribute("hidden","");return}e&&(e.style.display="none"),t&&(t.removeAttribute("hidden"),n&&(n.textContent=`${a.length} observation${a.length===1?"":"s"}`)),i.style.display="flex",i.innerHTML=a.slice(0,20).map(l=>{const O=(l.message||"").slice(0,180);return`
975
+ </span>`}let mi=null;async function LZ(i){mi==null||mi.abort(),mi=new AbortController;const e=document.getElementById("editor-ai-messages");if(!e)return;const t=document.createElement("div");t.className="ai-msg ai-bot",t.style.cssText="padding:0.4rem 0.6rem;font-size:0.72rem;border-left:2px solid var(--info,#89b4fa);background:rgba(137,180,250,0.08)",t.innerHTML=`▶ <strong>Executing plan</strong> <code>plan/${Q(i)}</code> via Rust supervisor… <button class="btn btn-sm" onclick="window.__editorPlanStop()" style="font-size:0.6rem;padding:1px 6px;margin-left:0.5rem">Stop</button>`,e.appendChild(t);const n=document.createElement("div");n.className="ai-msg ai-bot",n.style.cssText="padding:0.35rem 0.6rem;font-size:0.7rem;opacity:0.8;font-family:var(--font-mono,monospace)",n.textContent="starting…",e.appendChild(n);const r=[];try{await VS(`plan/${i}`,o=>{var a;if(o.event==="status")n.textContent=`[${o.agent||"…"}] ${o.text||""}`;else if(o.event==="message"){const l=document.createElement("div");l.className="ai-msg ai-bot",l.style.cssText="padding:0.45rem 0.6rem;font-size:0.75rem";const O=(o.content||"").replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/\n/g,"<br>");l.innerHTML=O,e.appendChild(l),(a=o.files_changed)!=null&&a.length&&r.push(...o.files_changed)}else o.event==="error"&&(n.textContent=`✗ ${o.text||"agent error"}`,n.style.color="var(--danger,#f38ba8)");e.scrollTop=e.scrollHeight},{signal:mi.signal}),n.textContent=r.length?`✓ done — ${r.length} file${r.length===1?"":"s"} touched`:"✓ done",n.style.color="var(--success,#a6e3a1)",await Ue(".");const s=await Mi("plan_current",{});$a(s.ok&&s.result||null)}catch(s){(s==null?void 0:s.name)==="AbortError"?n.textContent="⏹ stopped by user":(n.textContent=`✗ ${(s==null?void 0:s.message)||s}`,n.style.color="var(--danger,#f38ba8)")}finally{mi=null}}function IZ(){mi==null||mi.abort()}const CQ="tina4.editor.dismissedThoughts";function YQ(){try{const i=localStorage.getItem(CQ);if(!i)return new Set;const e=JSON.parse(i);return new Set(Array.isArray(e)?e:[])}catch{return new Set}}function GZ(i){const e=Array.from(i).slice(-200);try{localStorage.setItem(CQ,JSON.stringify(e))}catch{}}const DZ=new Set(["hey","hi","hello","there","team","folks","everyone","i","we","you","they","our","us","my","your","their","noticed","notice","saw","see","spotted","found","thought","that","this","these","those","it","its","the","a","an","and","or","but","for","of","to","in","on","at","with","as","by","from","have","has","had","having","is","are","was","were","be","been","being","do","does","did","does","done","can","could","should","would","will","may","might","must","so","just","even","still","also","too","not","no","yes","project","projects","file","files","new","old","make","makes","made","making","know","knows","knew","known","tricky","bit","exactly","some","any","all","each","every","what","which","why","how","when","where","who","if","then","else","than","because","set","up","down","over","under","out","into","onto","developer","developers","dev","devs","user","users","environment","env"]);function WQ(i){const e=(i||"").toLowerCase().replace(/[^a-z0-9._/-]+/g," ").split(/\s+/).map(s=>s.replace(/^[._\-/]+|[._\-/]+$/g,"")).filter(s=>s.length>1),t=e.filter(s=>s.includes(".")||s.includes("/"));if(t.length)return Array.from(new Set(t)).sort().slice(0,6).join("|");const n=e.filter(s=>s.length>2&&!DZ.has(s));return Array.from(new Set(n)).sort().slice(0,4).join("|")}async function EQ(){const i=document.getElementById("editor-thoughts-banner"),e=document.getElementById("thoughts-empty-state"),t=document.getElementById("thoughts-toolbar"),n=document.getElementById("thoughts-toolbar-count");if(!i)return;let r=[];try{r=await CS()}catch{r=[]}const s=YQ(),o=new Set,a=[];for(const l of r){const O=WQ(l.message);s.has(O)||o.has(O)||(o.add(O),a.push(l))}if(wz("thoughts",a.length||null,a.length>0),!a.length){i.style.display="none",i.innerHTML="",e&&(e.style.display=""),t&&t.setAttribute("hidden","");return}e&&(e.style.display="none"),t&&(t.removeAttribute("hidden"),n&&(n.textContent=`${a.length} observation${a.length===1?"":"s"}`)),i.style.display="flex",i.innerHTML=a.slice(0,20).map(l=>{const O=(l.message||"").slice(0,180);return`
976
976
  <div class="editor-thought-chip" data-id="${Q(l.id)}" data-hash="${Q(WQ(l.message))}">
977
977
  <span style="opacity:0.7">💡</span>
978
978
  <div class="editor-thought-chip-body" onclick="window.__editorThoughtAct('${Q(l.id)}')">${Q(O)}${l.message.length>180?"…":""}</div>
@@ -1012,28 +1012,32 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
1012
1012
  <span>severity: ${Q(e.severity||"-")}</span>
1013
1013
  </div>
1014
1014
  <div style="margin-top:0.3rem">${Q(e.summary||"")}</div>
1015
- </div>`}catch{return fs(i)}}async function cz(){try{const i=await cs();Pe.push(i),nt.set(i.id,[]),await Sa(i.id)}catch(i){console.error("threadsNew failed",i)}}async function dz(){if(!Re)return;const i=Pe.find(t=>t.id===Re);if(!i)return;const e=window.prompt("Rename thread:",i.title);if(!(e==null||e.trim()===""||e===i.title))try{const t=await Qa(Re,{title:e.trim()});i.title=t.title;const n=document.getElementById("threads-detail-title");n&&(n.textContent=t.title),er()}catch(t){console.error("rename failed",t)}}function hz(i){return i?i.status_hint==="done"||i.status_hint==="wont_do"||i.closure_reason==="done"||i.closure_reason==="wont_do":!1}async function $d(){const i=document.getElementById("threads-reply-input");if(!i)return;let e=i.value.trim();if(!e)return;i.value="";const t=e.match(/^new topic:\s*(.*)$/is);if(t){const o=t[1].trim();try{const a=await cs(o.slice(0,80)||void 0);if(Pe.push(a),nt.set(a.id,[]),await Sa(a.id),!o)return;e=o}catch(a){console.error("New Topic spawn failed",a);return}}if(!t&&Re&&hz(Pe.find(o=>o.id===Re)))try{const o=await cs(e.slice(0,80)||void 0);Pe.push(o),nt.set(o.id,[]),await Sa(o.id)}catch(o){console.error("auto-new-thread on done reply failed",o);return}if(!Re)try{const o=await cs(e.slice(0,80));Pe.push(o),nt.set(o.id,[]),Re=o.id}catch(o){console.error("auto-create failed",o);return}const n=Re,r=nt.get(n)||[];r.push({id:`local-${Date.now()}`,role:"user",content:e,timestamp:new Date().toISOString(),thread_id:n}),nt.set(n,r),md(n),ga.add(n),er();const s=document.getElementById("threads-chat");Jn==null||Jn.abort(),Jn=new AbortController;try{await gz(e,n,Jn.signal,s)}catch(o){if((o==null?void 0:o.name)!=="AbortError"){const a=document.createElement("div");a.className="ai-msg ai-bot",a.style.color="var(--danger,#f38ba8)",a.textContent=`Connection failed: ${(o==null?void 0:o.message)||o}`,s==null||s.appendChild(a)}}finally{ga.delete(n),Jn=null;try{await jQ(n,!0)}catch{}await ya(),md(n);const o=Pe.find(l=>l.id===n),a=document.getElementById("threads-detail-meta");if(o&&a){const l=o.sender?`<span>📨 from ${Q(o.sender)}</span>`:"";a.innerHTML=`${fd(o.status_hint||"idle")} <span>${Q(ud(o.last_message_at))}</span> ${l}`}}}queueMicrotask(()=>{const i=document.getElementById("threads-reply-form");i&&i.addEventListener("submit",t=>{t.preventDefault(),$d()});const e=document.getElementById("threads-reply-input");e&&e.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),t.stopPropagation(),$d())})}),window.__threadsShowList=()=>ba(),window.__threadsShowDetail=i=>{Sa(i)},window.__threadsNew=()=>{cz()},window.__threadsRetry=()=>{ya(2).then(er)},window.__threadsRenameActive=()=>{dz()};let un=!1;async function LQ(){const i=document.getElementById("plans-panel"),e=document.getElementById("plans-toggle-btn");i&&(un=!un,i.hidden=!un,e==null||e.classList.toggle("active",un),un&&await fz())}async function fz(){const i=document.getElementById("plans-rows");if(i){i.innerHTML='<div class="threads-empty">Loading plans…</div>';try{const e=await Mi("plan_list",{}),t=e.ok&&Array.isArray(e.result)?e.result:[];if(!t.length){i.innerHTML='<div class="threads-empty">No plans yet — they appear here when the planner agent creates one.</div>';return}const n=[...t].sort((r,s)=>String(s.name||s.file||"").localeCompare(String(r.name||r.file||"")));i.innerHTML=n.map(r=>{var h,f;const s=String(r.name||r.file||""),o=String(r.path||`plan/${s}`),a=String(r.title||s).slice(0,60),l=r.steps_done??((h=r.progress)==null?void 0:h.done)??0,O=r.steps_total??((f=r.progress)==null?void 0:f.total)??0,c=O>0?`${l}/${O} steps`:"",d=r.is_current||r.current?" · ★ current":"";return`<div class="plan-row" onclick="window.__plansOpen('${Q(o)}')" title="Open ${Q(o)} in editor">
1015
+ </div>`}catch{return fs(i)}}async function cz(){try{const i=await cs();Pe.push(i),nt.set(i.id,[]),await Sa(i.id)}catch(i){console.error("threadsNew failed",i)}}async function dz(){if(!Re)return;const i=Pe.find(t=>t.id===Re);if(!i)return;const e=window.prompt("Rename thread:",i.title);if(!(e==null||e.trim()===""||e===i.title))try{const t=await Qa(Re,{title:e.trim()});i.title=t.title;const n=document.getElementById("threads-detail-title");n&&(n.textContent=t.title),er()}catch(t){console.error("rename failed",t)}}function hz(i){return i?i.status_hint==="done"||i.status_hint==="wont_do"||i.closure_reason==="done"||i.closure_reason==="wont_do":!1}async function $d(){const i=document.getElementById("threads-reply-input");if(!i)return;let e=i.value.trim();if(!e)return;i.value="";const t=e.match(/^new topic:\s*(.*)$/is);if(t){const o=t[1].trim();try{const a=await cs(o.slice(0,80)||void 0);if(Pe.push(a),nt.set(a.id,[]),await Sa(a.id),!o)return;e=o}catch(a){console.error("New Topic spawn failed",a);return}}if(!t&&Re&&hz(Pe.find(o=>o.id===Re)))try{const o=await cs(e.slice(0,80)||void 0);Pe.push(o),nt.set(o.id,[]),await Sa(o.id)}catch(o){console.error("auto-new-thread on done reply failed",o);return}if(!Re)try{const o=await cs(e.slice(0,80));Pe.push(o),nt.set(o.id,[]),Re=o.id}catch(o){console.error("auto-create failed",o);return}const n=Re,r=nt.get(n)||[];r.push({id:`local-${Date.now()}`,role:"user",content:e,timestamp:new Date().toISOString(),thread_id:n}),nt.set(n,r),md(n),ga.add(n),er();const s=document.getElementById("threads-chat");Jn==null||Jn.abort(),Jn=new AbortController;try{await Qz(e,n,Jn.signal,s)}catch(o){if((o==null?void 0:o.name)!=="AbortError"){const a=document.createElement("div");a.className="ai-msg ai-bot",a.style.color="var(--danger,#f38ba8)",a.textContent=`Connection failed: ${(o==null?void 0:o.message)||o}`,s==null||s.appendChild(a)}}finally{ga.delete(n),Jn=null;try{await jQ(n,!0)}catch{}await ya(),md(n);const o=Pe.find(l=>l.id===n),a=document.getElementById("threads-detail-meta");if(o&&a){const l=o.sender?`<span>📨 from ${Q(o.sender)}</span>`:"";a.innerHTML=`${fd(o.status_hint||"idle")} <span>${Q(ud(o.last_message_at))}</span> ${l}`}}}queueMicrotask(()=>{const i=document.getElementById("threads-reply-form");i&&i.addEventListener("submit",t=>{t.preventDefault(),$d()});const e=document.getElementById("threads-reply-input");e&&e.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),t.stopPropagation(),$d())})}),window.__threadsShowList=()=>ba(),window.__threadsShowDetail=i=>{Sa(i)},window.__threadsNew=()=>{cz()},window.__threadsRetry=()=>{ya(2).then(er)},window.__threadsRenameActive=()=>{dz()};let un=!1;async function LQ(){const i=document.getElementById("plans-panel"),e=document.getElementById("plans-toggle-btn");i&&(un=!un,i.hidden=!un,e==null||e.classList.toggle("active",un),un&&await fz())}async function fz(){const i=document.getElementById("plans-rows");if(i){i.innerHTML='<div class="threads-empty">Loading plans…</div>';try{const e=await Mi("plan_list",{}),t=e.ok&&Array.isArray(e.result)?e.result:[];if(!t.length){i.innerHTML='<div class="threads-empty">No plans yet — they appear here when the planner agent creates one.</div>';return}const n=[...t].sort((r,s)=>String(s.name||s.file||"").localeCompare(String(r.name||r.file||"")));i.innerHTML=n.map(r=>{var h,f;const s=String(r.name||r.file||""),o=String(r.path||`plan/${s}`),a=String(r.title||s).slice(0,60),l=r.steps_done??((h=r.progress)==null?void 0:h.done)??0,O=r.steps_total??((f=r.progress)==null?void 0:f.total)??0,c=O>0?`${l}/${O} steps`:"",d=r.is_current||r.current?" · ★ current":"";return`<div class="plan-row" onclick="window.__plansOpen('${Q(o)}')" title="Open ${Q(o)} in editor">
1016
1016
  <div class="plan-name">${Q(a)}</div>
1017
1017
  <div class="plan-meta">${Q(s)}${c?" · "+Q(c):""}${d}</div>
1018
- </div>`}).join("")}catch{i.innerHTML='<div class="threads-empty" style="color:var(--danger,#f38ba8)">Failed to load plans</div>'}}}async function uz(i){var t;try{await ui(i)}catch(n){console.error("plansOpen failed",n)}un=!1;const e=document.getElementById("plans-panel");e&&(e.hidden=!0),(t=document.getElementById("plans-toggle-btn"))==null||t.classList.remove("active")}window.__plansToggle=()=>{LQ()},window.__plansOpen=i=>{uz(i)};let ds=!1;async function pz(){const i=document.getElementById("grounding-panel"),e=document.getElementById("grounding-toggle-btn");i&&(un&&LQ(),ds=!ds,i.hidden=!ds,e==null||e.classList.toggle("active",ds),ds&&await IQ())}async function IQ(){const i=document.getElementById("grounding-body");if(!i)return;i.innerHTML='<div class="threads-empty">Loading…</div>';let e={};try{const r=await fetch("/__dev/api/grounding/status");r.ok&&(e=await r.json())}catch{}const t=Q(e.url||"https://mcp.tina4.com"),n=e.configured?`<span style="color:var(--success,#a6e3a1)">&#9679; Configured</span> <span style="opacity:0.6">(…${Q(e.last4||"")})</span>`:'<span style="color:var(--warn,#f9e2af)">&#9675; Not set</span> — using local corpus fallback';i.innerHTML=`
1018
+ </div>`}).join("")}catch{i.innerHTML='<div class="threads-empty" style="color:var(--danger,#f38ba8)">Failed to load plans</div>'}}}async function uz(i){var t;try{await ui(i)}catch(n){console.error("plansOpen failed",n)}un=!1;const e=document.getElementById("plans-panel");e&&(e.hidden=!0),(t=document.getElementById("plans-toggle-btn"))==null||t.classList.remove("active")}window.__plansToggle=()=>{LQ()},window.__plansOpen=i=>{uz(i)};let ds=!1;async function pz(){const i=document.getElementById("grounding-panel"),e=document.getElementById("grounding-toggle-btn");i&&(un&&LQ(),ds=!ds,i.hidden=!ds,e==null||e.classList.toggle("active",ds),ds&&await IQ())}function mz(i){const e=Q(i.url||"https://mcp.tina4.com"),t=i.source||(i.configured?"personal":"none");if(t==="personal")return{source:t,stateHtml:`<span style="color:var(--success,#a6e3a1)">&#9679; Your token</span> <span style="opacity:0.6">(…${Q(i.last4||"")})</span>`,nudgeHtml:""};if(t==="free"){const n=i.dev_email?` Identified to the server as <code>${Q(i.dev_email)}</code>.`:"";return{source:t,stateHtml:`<span style="color:var(--warn,#f9e2af)">&#127873; Free trial</span> — grounding via <code>${e}</code> on the shared <code>FREE-TOKEN</code>.`,nudgeHtml:`<div style="margin:0.4rem 0;padding:0.4rem 0.55rem;border:1px solid var(--warn,#f9e2af);border-radius:6px;background:color-mix(in srgb, var(--warn,#f9e2af) 12%, transparent)">
1019
+ You're trying Tina4 grounding for free.${n} Register for your <strong>own</strong> token — higher limits, no shared rate cap.
1020
+ <a href="https://profile.tina4.com" target="_blank" rel="noopener" style="color:var(--accent,#89b4fa);font-weight:600">Register at profile.tina4.com &rarr;</a>
1021
+ </div>`}}return{source:t,stateHtml:'<span style="color:var(--warn,#f9e2af)">&#9675; Not set</span> — using local corpus fallback',nudgeHtml:""}}async function IQ(){const i=document.getElementById("grounding-body");if(!i)return;i.innerHTML='<div class="threads-empty">Loading…</div>';let e={};try{const s=await fetch("/__dev/api/grounding/status");s.ok&&(e=await s.json())}catch{}const t=Q(e.url||"https://mcp.tina4.com"),{stateHtml:n,nudgeHtml:r}=mz(e);i.innerHTML=`
1019
1022
  <div style="font-weight:600;margin-bottom:0.35rem">Framework grounding</div>
1020
1023
  <div style="opacity:0.85;margin-bottom:0.5rem">Ground the coder against <code>${t}</code> (version-current Tina4 API) instead of the local fallback.</div>
1021
1024
  <div style="margin-bottom:0.5rem">${n}</div>
1025
+ ${r}
1022
1026
  <div style="display:flex;gap:4px">
1023
- <input type="password" id="grounding-token-input" class="input" placeholder="Paste TINA4_MCP_TOKEN…"
1027
+ <input type="password" id="grounding-token-input" class="input" placeholder="Paste your own TINA4_MCP_TOKEN…"
1024
1028
  style="flex:1;font-size:0.72rem;padding:4px 8px;height:28px" autocomplete="off" />
1025
1029
  <button type="button" class="btn btn-sm btn-primary" style="font-size:0.65rem;padding:2px 10px"
1026
1030
  onclick="window.__groundingSave()">Save</button>
1027
1031
  </div>
1028
1032
  <div id="grounding-result" style="margin-top:0.4rem;min-height:1.1em"></div>
1029
1033
  <div style="opacity:0.6;margin-top:0.4rem">Get a free token at <code>profile.tina4.com</code>. Stored in project <code>.env</code>; takes effect next turn.</div>
1030
- `}async function mz(){const i=document.getElementById("grounding-token-input"),e=document.getElementById("grounding-result"),t=i==null?void 0:i.value.trim();if(!t){e&&(e.innerHTML='<span style="color:var(--warn,#f9e2af)">Paste a token first.</span>');return}e&&(e.innerHTML='<span style="opacity:0.7">Saving…</span>');try{const n=await fetch("/__dev/api/grounding/token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:t})}),r=await n.json().catch(()=>({}));n.ok&&r.ok?(i&&(i.value=""),e&&(e.innerHTML=`<span style="color:var(--success,#a6e3a1)">&#10003; Saved (…${Q(String(r.last4||""))}). Grounding now uses mcp.tina4.com.</span>`),setTimeout(()=>{IQ()},1200)):e&&(e.innerHTML=`<span style="color:var(--danger,#f38ba8)">Failed: ${Q(String(r.error||n.status))}</span>`)}catch{e&&(e.innerHTML='<span style="color:var(--danger,#f38ba8)">Agent unreachable — is <code>tina4 serve</code> running?</span>')}}window.__groundingToggle=()=>{pz()},window.__groundingSave=()=>{mz()};async function $z(i){const e=document.getElementById("threads-chat")||document.getElementById("editor-ai-messages"),t=document.createElement("div");t.className="ai-msg ai-bot";const n=document.createElement("div");n.style.cssText="font-size:0.72rem;opacity:0.85;font-family:var(--font-mono,monospace)",n.textContent="▶ Building…",t.appendChild(n),e==null||e.appendChild(t);const r=i.includes("/")?i:`plan/${i}`;try{const s=await fetch("/__dev/api/execute",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({plan_file:r})});if(!s.ok||!s.body){n.textContent=`✗ execute ${s.status}`;return}const o=s.body.getReader(),a=new TextDecoder;let l="";for(;;){const{done:O,value:c}=await o.read();if(O)break;l+=a.decode(c,{stream:!0});let d;for(;(d=l.indexOf(`
1034
+ `}async function $z(){const i=document.getElementById("grounding-token-input"),e=document.getElementById("grounding-result"),t=i==null?void 0:i.value.trim();if(!t){e&&(e.innerHTML='<span style="color:var(--warn,#f9e2af)">Paste a token first.</span>');return}e&&(e.innerHTML='<span style="opacity:0.7">Saving…</span>');try{const n=await fetch("/__dev/api/grounding/token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:t})}),r=await n.json().catch(()=>({}));n.ok&&r.ok?(i&&(i.value=""),e&&(e.innerHTML=`<span style="color:var(--success,#a6e3a1)">&#10003; Saved (…${Q(String(r.last4||""))}). Grounding now uses mcp.tina4.com.</span>`),setTimeout(()=>{IQ()},1200)):e&&(e.innerHTML=`<span style="color:var(--danger,#f38ba8)">Failed: ${Q(String(r.error||n.status))}</span>`)}catch{e&&(e.innerHTML='<span style="color:var(--danger,#f38ba8)">Agent unreachable — is <code>tina4 serve</code> running?</span>')}}window.__groundingToggle=()=>{pz()},window.__groundingSave=()=>{$z()};async function gz(i){const e=document.getElementById("threads-chat")||document.getElementById("editor-ai-messages"),t=document.createElement("div");t.className="ai-msg ai-bot";const n=document.createElement("div");n.style.cssText="font-size:0.72rem;opacity:0.85;font-family:var(--font-mono,monospace)",n.textContent="▶ Building…",t.appendChild(n),e==null||e.appendChild(t);const r=i.includes("/")?i:`plan/${i}`;try{const s=await fetch("/__dev/api/execute",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({plan_file:r})});if(!s.ok||!s.body){n.textContent=`✗ execute ${s.status}`;return}const o=s.body.getReader(),a=new TextDecoder;let l="";for(;;){const{done:O,value:c}=await o.read();if(O)break;l+=a.decode(c,{stream:!0});let d;for(;(d=l.indexOf(`
1031
1035
 
1032
1036
  `))!==-1;){const h=l.slice(0,d);l=l.slice(d+2);let f="message",p="";for(const m of h.split(`
1033
- `))m.startsWith("event:")?f=m.slice(6).trim():m.startsWith("data:")&&(p+=m.slice(5).trim());if(p){try{const m=JSON.parse(p);if(f==="status")n.textContent=`[${m.agent||"…"}] ${m.text||""}`;else if(f==="message"&&m.content){const $=document.createElement("div");$.style.cssText="font-size:0.75rem;margin-top:0.25rem",$.innerHTML=Q(String(m.content)).replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/\\n|\n/g,"<br>"),t.appendChild($)}}catch{}e&&(e.scrollTop=e.scrollHeight)}}}n.textContent="✓ Build complete — check the file tree / run the app"}catch(s){n.textContent=`✗ ${(s==null?void 0:s.message)||s}`}}window.__buildPlanNow=i=>{$z(i)},window.__threadsArchiveActive=()=>{sz()},window.__threadsArchiveFromList=i=>{oz(i)},queueMicrotask(()=>{nz()});async function gz(i,e,t,n){const r=n??document.getElementById("threads-chat")??document.getElementById("editor-ai-messages");if(!r)return;const s=document.createElement("div");s.className="ai-msg ai-bot";const o=document.createElement("div");o.style.cssText="font-size:0.72rem;opacity:0.7;margin-bottom:0.3rem;font-family:var(--font-mono,monospace)",o.innerHTML='<span style="opacity:0.6">→ supervisor: thinking…</span>';const a=document.createElement("div");s.appendChild(o),s.appendChild(a),r.appendChild(s),r.scrollTop=r.scrollHeight;const l=(()=>{if(!B)return null;const p=ee.find($=>$.path===B);if(!p)return null;const m=p.content||"";return m.length>6e4?{path:p.path,language:p.language,content:"<too large to inline — ask the user or call file_read>"}:{path:p.path,language:p.language,content:m}})(),O=await fetch("/__dev/api/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:i,thread_id:e,active_file:l}),signal:t});if(!O.ok){let p="";try{p=(await O.text()).slice(0,400)}catch{}o.remove(),a.innerHTML=`<span style="color:var(--danger,#f38ba8)">Supervisor unavailable (HTTP ${O.status}).</span>`+(p?`<pre style="font-size:0.7rem;opacity:0.7;margin-top:0.3rem;white-space:pre-wrap">${Q(p)}</pre>`:"")+'<div style="font-size:0.75rem;opacity:0.7;margin-top:0.3rem">Run <code>tina4 serve</code> (auto-spawns the agent) or set <code>TINA4_SUPERVISOR_URL</code>.</div>';return}if(!O.body){o.remove(),a.innerHTML='<span style="color:var(--danger,#f38ba8)">Supervisor returned no body.</span>';return}const c=O.body.getReader(),d=new TextDecoder;let h="",f=[];for(;;){const{value:p,done:m}=await c.read();if(m)break;h+=d.decode(p,{stream:!0});let $;for(;($=h.indexOf(`
1037
+ `))m.startsWith("event:")?f=m.slice(6).trim():m.startsWith("data:")&&(p+=m.slice(5).trim());if(p){try{const m=JSON.parse(p);if(f==="status")n.textContent=`[${m.agent||"…"}] ${m.text||""}`;else if(f==="message"&&m.content){const $=document.createElement("div");$.style.cssText="font-size:0.75rem;margin-top:0.25rem",$.innerHTML=Q(String(m.content)).replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/\\n|\n/g,"<br>"),t.appendChild($)}}catch{}e&&(e.scrollTop=e.scrollHeight)}}}n.textContent="✓ Build complete — check the file tree / run the app"}catch(s){n.textContent=`✗ ${(s==null?void 0:s.message)||s}`}}window.__buildPlanNow=i=>{gz(i)},window.__threadsArchiveActive=()=>{sz()},window.__threadsArchiveFromList=i=>{oz(i)},queueMicrotask(()=>{nz()});async function Qz(i,e,t,n){const r=n??document.getElementById("threads-chat")??document.getElementById("editor-ai-messages");if(!r)return;const s=document.createElement("div");s.className="ai-msg ai-bot";const o=document.createElement("div");o.style.cssText="font-size:0.72rem;opacity:0.7;margin-bottom:0.3rem;font-family:var(--font-mono,monospace)",o.innerHTML='<span style="opacity:0.6">→ supervisor: thinking…</span>';const a=document.createElement("div");s.appendChild(o),s.appendChild(a),r.appendChild(s),r.scrollTop=r.scrollHeight;const l=(()=>{if(!B)return null;const p=ee.find($=>$.path===B);if(!p)return null;const m=p.content||"";return m.length>6e4?{path:p.path,language:p.language,content:"<too large to inline — ask the user or call file_read>"}:{path:p.path,language:p.language,content:m}})(),O=await fetch("/__dev/api/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:i,thread_id:e,active_file:l}),signal:t});if(!O.ok){let p="";try{p=(await O.text()).slice(0,400)}catch{}o.remove(),a.innerHTML=`<span style="color:var(--danger,#f38ba8)">Supervisor unavailable (HTTP ${O.status}).</span>`+(p?`<pre style="font-size:0.7rem;opacity:0.7;margin-top:0.3rem;white-space:pre-wrap">${Q(p)}</pre>`:"")+'<div style="font-size:0.75rem;opacity:0.7;margin-top:0.3rem">Run <code>tina4 serve</code> (auto-spawns the agent) or set <code>TINA4_SUPERVISOR_URL</code>.</div>';return}if(!O.body){o.remove(),a.innerHTML='<span style="color:var(--danger,#f38ba8)">Supervisor returned no body.</span>';return}const c=O.body.getReader(),d=new TextDecoder;let h="",f=[];for(;;){const{value:p,done:m}=await c.read();if(m)break;h+=d.decode(p,{stream:!0});let $;for(;($=h.indexOf(`
1034
1038
 
1035
1039
  `))!==-1;){const g=h.slice(0,$);h=h.slice($+2);let y="message",S="";for(const w of g.split(`
1036
- `))w.startsWith("event:")?y=w.slice(6).trim():w.startsWith("data:")&&(S+=w.slice(5).trim());if(!S)continue;let x;try{x=JSON.parse(S)}catch{x={text:S,content:S}}if(y==="status"){const w=String(x.agent||"supervisor"),R=String(x.text||"");o.innerHTML=`<span style="color:var(--info,#89b4fa)">[${Q(w)}]</span> ${Q(R)}`,x.backup&&(o.innerHTML+=` <span style="opacity:0.5">(backup: ${Q(String(x.backup))})</span>`)}else if(y==="message"){const w=String(x.agent||"supervisor"),R=String(x.content||"");o.innerHTML=`<span style="opacity:0.6">↳ ${Q(w)}</span>`,a.innerHTML=fs(R);const _=Array.isArray(x.suggested_replies)?x.suggested_replies.map(A=>String(A)).filter(Boolean):[];s.querySelectorAll(".action-pills").forEach(A=>A.remove()),_.length&&pd(s,_),Array.isArray(x.files_changed)&&(f=x.files_changed.map(A=>String(A)))}else if(y==="plan"){const w=String(x.content||""),R=String(x.file||"");if(o.innerHTML=`<span style="opacity:0.6">↳ planner</span> · plan saved to <code>${Q(R)}</code>`,a.innerHTML=fs(w),x.approve!==!1){if(s.querySelectorAll(".action-pills").forEach(_=>_.remove()),R){const _=document.createElement("div");_.className="action-pills";const A=document.createElement("button");A.type="button",A.className="action-pill",A.style.cssText="background:rgba(166,227,161,0.16);border-color:#a6e3a1;font-weight:600",A.textContent="⚡ Build it now",A.addEventListener("click",()=>{A.setAttribute("disabled",""),window.__buildPlanNow(R)}),_.appendChild(A),s.appendChild(_)}pd(s,["Make changes","Cancel"])}}else if(y==="error"){const w=String(x.message||"Supervisor error");o.innerHTML='<span style="color:var(--danger,#f38ba8)">✗ error</span>',a.innerHTML=`<span style="color:var(--danger,#f38ba8)">${Q(w)}</span>`}r.scrollTop=r.scrollHeight}}for(const p of f)try{await qz(p)}catch{}}const Qz="tina4.editor.session.mode";function yz(){return localStorage.getItem(Qz)==="qa"?"qa":"supervisor"}function bz(i){const e=document.getElementById("session-mode-toggle");if(!e)return;e.querySelectorAll(".mode-btn").forEach(n=>{n.classList.toggle("active",n.dataset.mode===i)});const t=document.getElementById("editor-ai-input");t&&(t.placeholder=i==="qa"?"Ask a question…":"Describe the change…")}function Sz(i,e,t=!1){const n=document.getElementById(`tab-badge-${i}`),r=document.querySelector(`.session-tab[data-tab="${i}"]`);if(n){if(!e){n.textContent="",r==null||r.classList.remove("has-alert");return}n.textContent=String(e),r==null||r.classList.toggle("has-alert",t)}}function GQ(){const i=document.getElementById("completion-toggle");if(i){if(i.classList.remove("disabled","on-plan","off-plan"),!VQ){i.classList.add("disabled"),i.setAttribute("title","Completion off — click to enable");return}Os?(i.classList.add("on-plan"),i.setAttribute("title",`Completion on-plan: ${Os.slice(0,80)}`)):(i.classList.add("off-plan"),i.setAttribute("title","Completion on (off-plan — no intent boost)"))}}let DQ=null;async function BQ(){const i=[{key:"chat",url:tr.chat.endpoint},{key:"vision",url:tr.vision.endpoint},{key:"embed",url:tr.embed.endpoint},{key:"image",url:tr.image.endpoint},{key:"rag",url:tr.rag.endpoint}];await Promise.all(i.map(async({key:e,url:t})=>{const n=await Wy(t),r=document.querySelector(`.model-dot[data-model="${e}"]`);r&&(r.classList.toggle("up",n),r.classList.toggle("down",!n))}))}function wz(){DQ||(BQ(),DQ=window.setInterval(BQ,3e4))}function NQ(i,e){const t=document.getElementById("session-title"),n=document.getElementById("session-meta");t&&i!==null&&(t.textContent=i),n&&(n.textContent=e||"")}let It=null,re=null;const gd="tina4.editor.session.id";function Pz(i){It=i,localStorage.removeItem(gd),Qd(),yd()}function Qd(){if(!It){NQ("No active session","");return}const i=(re==null?void 0:re.files.length)??0,e=(re==null?void 0:re.commits.length)??0,t=(re==null?void 0:re.warnings.length)??0,n=It.title||`session ${It.id.slice(0,8)}`,r=[];e&&r.push(`${e} commit${e===1?"":"s"}`),i&&r.push(`${i} file${i===1?"":"s"}`),t&&r.push(`⚠ ${t}`),NQ(n,r.join(" · "))}function yd(){const i=document.getElementById("btn-session-revise"),e=document.getElementById("btn-session-apply"),t=document.getElementById("btn-session-cancel"),n=!!It,r=((re==null?void 0:re.files.length)??0)>0;i&&(i.disabled=!n),e&&(e.disabled=!r),t&&(t.disabled=!n)}function xz(){const i=document.getElementById("diff-empty-state"),e=document.getElementById("diff-content-area");if(!i||!e)return;if(!It||!re||re.files.length===0){i.style.display="",e.setAttribute("hidden","");return}i.style.display="none",e.removeAttribute("hidden");const t=document.getElementById("diff-summary");if(t){const o=re.files.length,a=re.commits.length;t.innerHTML=`
1040
+ `))w.startsWith("event:")?y=w.slice(6).trim():w.startsWith("data:")&&(S+=w.slice(5).trim());if(!S)continue;let x;try{x=JSON.parse(S)}catch{x={text:S,content:S}}if(y==="status"){const w=String(x.agent||"supervisor"),R=String(x.text||"");o.innerHTML=`<span style="color:var(--info,#89b4fa)">[${Q(w)}]</span> ${Q(R)}`,x.backup&&(o.innerHTML+=` <span style="opacity:0.5">(backup: ${Q(String(x.backup))})</span>`)}else if(y==="message"){const w=String(x.agent||"supervisor"),R=String(x.content||"");o.innerHTML=`<span style="opacity:0.6">↳ ${Q(w)}</span>`,a.innerHTML=fs(R);const _=Array.isArray(x.suggested_replies)?x.suggested_replies.map(A=>String(A)).filter(Boolean):[];s.querySelectorAll(".action-pills").forEach(A=>A.remove()),_.length&&pd(s,_),Array.isArray(x.files_changed)&&(f=x.files_changed.map(A=>String(A)))}else if(y==="plan"){const w=String(x.content||""),R=String(x.file||"");if(o.innerHTML=`<span style="opacity:0.6">↳ planner</span> · plan saved to <code>${Q(R)}</code>`,a.innerHTML=fs(w),x.approve!==!1){if(s.querySelectorAll(".action-pills").forEach(_=>_.remove()),R){const _=document.createElement("div");_.className="action-pills";const A=document.createElement("button");A.type="button",A.className="action-pill",A.style.cssText="background:rgba(166,227,161,0.16);border-color:#a6e3a1;font-weight:600",A.textContent="⚡ Build it now",A.addEventListener("click",()=>{A.setAttribute("disabled",""),window.__buildPlanNow(R)}),_.appendChild(A),s.appendChild(_)}pd(s,["Make changes","Cancel"])}}else if(y==="error"){const w=String(x.message||"Supervisor error");o.innerHTML='<span style="color:var(--danger,#f38ba8)">✗ error</span>',a.innerHTML=`<span style="color:var(--danger,#f38ba8)">${Q(w)}</span>`}r.scrollTop=r.scrollHeight}}for(const p of f)try{await Vz(p)}catch{}}const yz="tina4.editor.session.mode";function bz(){return localStorage.getItem(yz)==="qa"?"qa":"supervisor"}function Sz(i){const e=document.getElementById("session-mode-toggle");if(!e)return;e.querySelectorAll(".mode-btn").forEach(n=>{n.classList.toggle("active",n.dataset.mode===i)});const t=document.getElementById("editor-ai-input");t&&(t.placeholder=i==="qa"?"Ask a question…":"Describe the change…")}function wz(i,e,t=!1){const n=document.getElementById(`tab-badge-${i}`),r=document.querySelector(`.session-tab[data-tab="${i}"]`);if(n){if(!e){n.textContent="",r==null||r.classList.remove("has-alert");return}n.textContent=String(e),r==null||r.classList.toggle("has-alert",t)}}function GQ(){const i=document.getElementById("completion-toggle");if(i){if(i.classList.remove("disabled","on-plan","off-plan"),!VQ){i.classList.add("disabled"),i.setAttribute("title","Completion off — click to enable");return}Os?(i.classList.add("on-plan"),i.setAttribute("title",`Completion on-plan: ${Os.slice(0,80)}`)):(i.classList.add("off-plan"),i.setAttribute("title","Completion on (off-plan — no intent boost)"))}}let DQ=null;async function BQ(){const i=[{key:"chat",url:tr.chat.endpoint},{key:"vision",url:tr.vision.endpoint},{key:"embed",url:tr.embed.endpoint},{key:"image",url:tr.image.endpoint},{key:"rag",url:tr.rag.endpoint}];await Promise.all(i.map(async({key:e,url:t})=>{const n=await Wy(t),r=document.querySelector(`.model-dot[data-model="${e}"]`);r&&(r.classList.toggle("up",n),r.classList.toggle("down",!n))}))}function Pz(){DQ||(BQ(),DQ=window.setInterval(BQ,3e4))}function NQ(i,e){const t=document.getElementById("session-title"),n=document.getElementById("session-meta");t&&i!==null&&(t.textContent=i),n&&(n.textContent=e||"")}let It=null,re=null;const gd="tina4.editor.session.id";function xz(i){It=i,localStorage.removeItem(gd),Qd(),yd()}function Qd(){if(!It){NQ("No active session","");return}const i=(re==null?void 0:re.files.length)??0,e=(re==null?void 0:re.commits.length)??0,t=(re==null?void 0:re.warnings.length)??0,n=It.title||`session ${It.id.slice(0,8)}`,r=[];e&&r.push(`${e} commit${e===1?"":"s"}`),i&&r.push(`${i} file${i===1?"":"s"}`),t&&r.push(`⚠ ${t}`),NQ(n,r.join(" · "))}function yd(){const i=document.getElementById("btn-session-revise"),e=document.getElementById("btn-session-apply"),t=document.getElementById("btn-session-cancel"),n=!!It,r=((re==null?void 0:re.files.length)??0)>0;i&&(i.disabled=!n),e&&(e.disabled=!r),t&&(t.disabled=!n)}function kz(){const i=document.getElementById("diff-empty-state"),e=document.getElementById("diff-content-area");if(!i||!e)return;if(!It||!re||re.files.length===0){i.style.display="",e.setAttribute("hidden","");return}i.style.display="none",e.removeAttribute("hidden");const t=document.getElementById("diff-summary");if(t){const o=re.files.length,a=re.commits.length;t.innerHTML=`
1037
1041
  <strong>${o}</strong> file${o===1?"":"s"} across
1038
1042
  <strong>${a}</strong> commit${a===1?"":"s"} on
1039
1043
  <span class="diff-sha">${Q(re.branch)}</span>
@@ -1059,11 +1063,11 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
1059
1063
  <span class="diff-commit-subject">${Q(a.subject)}</span>
1060
1064
  ${l?`<span class="diff-commit-agent">${Q(l)}</span>`:""}
1061
1065
  </div>
1062
- `}).join("");s.innerHTML=`<div class="diff-commits-header">commits</div>${o}`}}async function kz(){if(!It){re=null,Qd(),yd();return}try{re=await AS(It.id)}catch(i){if(re=null,String(i).includes("not found")){Pz(null);return}}Qd(),yd(),vz(),xz()}async function Tz(){const i=localStorage.getItem(gd);if(i){try{const t=(await US()).find(n=>n.id===i);if(t){It=t,await kz();return}}catch{}localStorage.removeItem(gd)}}function vz(){const i=document.getElementById("session-summary-strip"),e=document.getElementById("summary-chip-session");if(!i||!e)return;if(!It||!re||re.files.length===0){i.setAttribute("hidden","");return}const t=re.files.length,n=re.commits.length,r=re.warnings.length,s=[`<strong>${t}</strong> file${t===1?"":"s"}`,`<strong>${n}</strong> commit${n===1?"":"s"}`];r>0&&s.push(`<strong style="color:var(--warning,#f9e2af)">⚠ ${r}</strong>`),e.innerHTML=s.join(" · "),i.removeAttribute("hidden")}let Xz=0;const hs=new Map;function _z(i){const e=i.split(`
1063
- `,1)[0]||"",t=e.match(/^\s*(?:\/\/|#|<!--)\s*([\w./\-]+\.\w+)\s*(?:-->)?\s*$/);return t&&t[1].includes("/")?{path:t[1],rest:i.slice(e.length+1)}:{path:null,rest:i}}function Rz(i){const e=l=>Q(l).replace(/`([^`]+)`/g,'<code style="background:rgba(0,0,0,0.3);padding:0.1rem 0.3rem;border-radius:0.2rem">$1</code>').replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/(^|[^*])\*([^*\n]+)\*/g,"$1<em>$2</em>"),t=i.split(`
1066
+ `}).join("");s.innerHTML=`<div class="diff-commits-header">commits</div>${o}`}}async function Tz(){if(!It){re=null,Qd(),yd();return}try{re=await AS(It.id)}catch(i){if(re=null,String(i).includes("not found")){xz(null);return}}Qd(),yd(),Xz(),kz()}async function vz(){const i=localStorage.getItem(gd);if(i){try{const t=(await US()).find(n=>n.id===i);if(t){It=t,await Tz();return}}catch{}localStorage.removeItem(gd)}}function Xz(){const i=document.getElementById("session-summary-strip"),e=document.getElementById("summary-chip-session");if(!i||!e)return;if(!It||!re||re.files.length===0){i.setAttribute("hidden","");return}const t=re.files.length,n=re.commits.length,r=re.warnings.length,s=[`<strong>${t}</strong> file${t===1?"":"s"}`,`<strong>${n}</strong> commit${n===1?"":"s"}`];r>0&&s.push(`<strong style="color:var(--warning,#f9e2af)">⚠ ${r}</strong>`),e.innerHTML=s.join(" · "),i.removeAttribute("hidden")}let _z=0;const hs=new Map;function Rz(i){const e=i.split(`
1067
+ `,1)[0]||"",t=e.match(/^\s*(?:\/\/|#|<!--)\s*([\w./\-]+\.\w+)\s*(?:-->)?\s*$/);return t&&t[1].includes("/")?{path:t[1],rest:i.slice(e.length+1)}:{path:null,rest:i}}function Zz(i){const e=l=>Q(l).replace(/`([^`]+)`/g,'<code style="background:rgba(0,0,0,0.3);padding:0.1rem 0.3rem;border-radius:0.2rem">$1</code>').replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/(^|[^*])\*([^*\n]+)\*/g,"$1<em>$2</em>"),t=i.split(`
1064
1068
  `),n=[];let r=null,s=!1;const o=()=>{s&&(n.push("</ul>"),s=!1)},a=()=>{o(),r&&(n.push(`</${r}>`),r=null)};for(const l of t){const O=l.replace(/\s+$/,"");if(!O.trim()){a();continue}const c=O.match(/^(#{1,6})\s+(.*)$/);if(c){a(),n.push(`<h${c[1].length} style="margin:0.4rem 0 0.2rem;font-size:${1.05-c[1].length*.05}rem">${e(c[2])}</h${c[1].length}>`);continue}const d=O.match(/^(?:\s{2,}|\t)[-*]\s+(.*)$/);if(d&&r){s||(n.push('<ul style="margin:0.1rem 0 0.1rem 1.2rem">'),s=!0),n.push(`<li>${e(d[1])}</li>`);continue}const h=O.match(/^\s*\d+\.\s+(.*)$/);if(h){r!=="ol"?(a(),n.push('<ol style="margin:0.2rem 0 0.2rem 1.2rem">'),r="ol"):o(),n.push(`<li>${e(h[1])}</li>`);continue}const f=O.match(/^\s*[-*]\s+(.*)$/);if(f){r!=="ul"?(a(),n.push('<ul style="margin:0.2rem 0 0.2rem 1.2rem">'),r="ul"):o(),n.push(`<li>${e(f[1])}</li>`);continue}a(),n.push(`<p style="margin:0.2rem 0">${e(O)}</p>`)}return a(),n.join("")}function fs(i){let e=i.replace(/\\n/g,`
1065
1069
  `);return e=e.replace(/```tool_call\s*\n[\s\S]*?(?:```|$)/g,""),e=e.replace(/```tool_result[^\n]*\n[\s\S]*?```/g,""),e=e.replace(/\n{2,}/g,`
1066
- `).trim(),e.replace(/\s+/g,"").length||(e=""),e=e.replace(/```(\w*)\n?([\s\S]*?)```/g,(t,n,r)=>{const{path:s,rest:o}=_z(r),a=`aiblk_${++Xz}`;hs.set(a,{code:o,path:s,lang:n||""});const l=/^(markdown|md)$/i.test(n||""),O=l?Rz(o):o.split(`
1070
+ `).trim(),e.replace(/\s+/g,"").length||(e=""),e=e.replace(/```(\w*)\n?([\s\S]*?)```/g,(t,n,r)=>{const{path:s,rest:o}=Rz(r),a=`aiblk_${++_z}`;hs.set(a,{code:o,path:s,lang:n||""});const l=/^(markdown|md)$/i.test(n||""),O=l?Zz(o):o.split(`
1067
1071
  `).map(f=>f.startsWith("+")?`<span class="ai-diff-add">${Q(f)}</span>`:f.startsWith("-")?`<span class="ai-diff-del">${Q(f)}</span>`:Q(f)).join(`
1068
1072
  `),c=s?Q(s):"active file",d=!s&&!B;return s?`<div class="ai-codeblock ai-codeblock-collapsed" data-block-id="${a}" data-auto-apply="1">
1069
1073
  <div class="ai-codeblock-bar">
@@ -1087,7 +1091,7 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
1087
1091
  </span>
1088
1092
  </div>
1089
1093
  <pre${l?' class="ai-codeblock-md"':""}>${l?O:`<code>${O}</code>`}</pre>
1090
- </div>`}),e=e.replace(/`([^`]+)`/g,'<code style="background:rgba(0,0,0,0.3);padding:0.1rem 0.3rem;border-radius:0.2rem">$1</code>'),e=e.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>"),e=e.replace(/\n/g,"<br>"),e=e.replace(/(<div class="ai-codeblock"[\s\S]*?<\/div>)(<br>)+/g,"$1"),e=e.replace(/(<br>)+(<div class="ai-codeblock")/g,"$2"),e}async function Zz(i){const e=hs.get(i);if(e)try{await navigator.clipboard.writeText(e.code),$i(i,"Copied")}catch{$i(i,"Copy failed",!0)}}function zz(i){const e=hs.get(i);if(!e)return;const t=ee.find(n=>n.path===B);if(!(t!=null&&t.view)){$i(i,"Open a file first",!0);return}t.view.dispatch(t.view.state.replaceSelection(e.code)),t.dirty=!0,Wi(),$i(i,"Inserted")}async function qz(i){try{await Ue(".")}catch{}if(!i)return;const e=ee.find(t=>t.path===i);if(e)try{const t=await L(`/file?path=${encodeURIComponent(i)}`),n=(t==null?void 0:t.content)??"";e.view&&e.view.dispatch({changes:{from:0,to:e.view.state.doc.length,insert:n}}),e.content=n,e.dirty=!1,Wi(),pi(`Reloaded ${i} after tool call`)}catch{}}function Vz(i){const e=document.querySelector(`.ai-codeblock[data-block-id="${i}"]`);if(!e)return;const t=e.querySelector("pre");t&&(t.style.display=t.style.display==="none"?"":"none")}function bd(i,e,t){const n=document.querySelector(`.ai-codeblock[data-block-id="${i}"] [data-status]`);n&&(n.textContent=e,t&&(n.style.color=t))}async function Cz(i){const e=hs.get(i);if(!e)return;const t=e.path||B;if(!t){bd(i,"✗","var(--danger,#f38ba8)"),$i(i,"No target file",!0);return}try{await L("/file/save","POST",{path:t,content:e.code}),bd(i,"✓","var(--success,#a6e3a1)"),$i(i,"Applied");const n=ee.find(r=>r.path===t);n!=null&&n.view?(n.view.dispatch({changes:{from:0,to:n.view.state.doc.length,insert:e.code}}),n.content=e.code,n.dirty=!1,Wi(),pi(`Applied AI suggestion to ${t}`)):await ui(t),await Ue(".")}catch(n){bd(i,"✗","var(--danger,#f38ba8)"),$i(i,`Apply failed: ${(n==null?void 0:n.message)||n}`,!0)}}async function Yz(i){const e=hs.get(i);if(!e)return;const t=e.path||"src/new-file",n=prompt("Save to path (relative to project root):",t);if(n)try{await L("/file/save","POST",{path:n,content:e.code}),$i(i,`Saved → ${n}`),await Ue("."),await ui(n)}catch(r){$i(i,`Save failed: ${(r==null?void 0:r.message)||r}`,!0)}}function $i(i,e,t=!1){const n=document.querySelector(`.ai-codeblock[data-block-id="${i}"] .ai-codeblock-lang`);if(!n)return;const r=n.textContent;n.textContent=e,n.style.color=t?"var(--danger, #f38ba8)":"var(--success, #a6e3a1)",setTimeout(()=>{n.textContent=r,n.style.color=""},1500)}let wa=null;function Wz(i,e,t){pn();const n=document.createElement("div");n.className="editor-ctx-menu",n.style.cssText=`position:fixed;left:${i.clientX}px;top:${i.clientY}px;z-index:200`,t?n.innerHTML=`
1094
+ </div>`}),e=e.replace(/`([^`]+)`/g,'<code style="background:rgba(0,0,0,0.3);padding:0.1rem 0.3rem;border-radius:0.2rem">$1</code>'),e=e.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>"),e=e.replace(/\n/g,"<br>"),e=e.replace(/(<div class="ai-codeblock"[\s\S]*?<\/div>)(<br>)+/g,"$1"),e=e.replace(/(<br>)+(<div class="ai-codeblock")/g,"$2"),e}async function zz(i){const e=hs.get(i);if(e)try{await navigator.clipboard.writeText(e.code),$i(i,"Copied")}catch{$i(i,"Copy failed",!0)}}function qz(i){const e=hs.get(i);if(!e)return;const t=ee.find(n=>n.path===B);if(!(t!=null&&t.view)){$i(i,"Open a file first",!0);return}t.view.dispatch(t.view.state.replaceSelection(e.code)),t.dirty=!0,Wi(),$i(i,"Inserted")}async function Vz(i){try{await Ue(".")}catch{}if(!i)return;const e=ee.find(t=>t.path===i);if(e)try{const t=await L(`/file?path=${encodeURIComponent(i)}`),n=(t==null?void 0:t.content)??"";e.view&&e.view.dispatch({changes:{from:0,to:e.view.state.doc.length,insert:n}}),e.content=n,e.dirty=!1,Wi(),pi(`Reloaded ${i} after tool call`)}catch{}}function Cz(i){const e=document.querySelector(`.ai-codeblock[data-block-id="${i}"]`);if(!e)return;const t=e.querySelector("pre");t&&(t.style.display=t.style.display==="none"?"":"none")}function bd(i,e,t){const n=document.querySelector(`.ai-codeblock[data-block-id="${i}"] [data-status]`);n&&(n.textContent=e,t&&(n.style.color=t))}async function Yz(i){const e=hs.get(i);if(!e)return;const t=e.path||B;if(!t){bd(i,"✗","var(--danger,#f38ba8)"),$i(i,"No target file",!0);return}try{await L("/file/save","POST",{path:t,content:e.code}),bd(i,"✓","var(--success,#a6e3a1)"),$i(i,"Applied");const n=ee.find(r=>r.path===t);n!=null&&n.view?(n.view.dispatch({changes:{from:0,to:n.view.state.doc.length,insert:e.code}}),n.content=e.code,n.dirty=!1,Wi(),pi(`Applied AI suggestion to ${t}`)):await ui(t),await Ue(".")}catch(n){bd(i,"✗","var(--danger,#f38ba8)"),$i(i,`Apply failed: ${(n==null?void 0:n.message)||n}`,!0)}}async function Wz(i){const e=hs.get(i);if(!e)return;const t=e.path||"src/new-file",n=prompt("Save to path (relative to project root):",t);if(n)try{await L("/file/save","POST",{path:n,content:e.code}),$i(i,`Saved → ${n}`),await Ue("."),await ui(n)}catch(r){$i(i,`Save failed: ${(r==null?void 0:r.message)||r}`,!0)}}function $i(i,e,t=!1){const n=document.querySelector(`.ai-codeblock[data-block-id="${i}"] .ai-codeblock-lang`);if(!n)return;const r=n.textContent;n.textContent=e,n.style.color=t?"var(--danger, #f38ba8)":"var(--success, #a6e3a1)",setTimeout(()=>{n.textContent=r,n.style.color=""},1500)}let wa=null;function Ez(i,e,t){pn();const n=document.createElement("div");n.className="editor-ctx-menu",n.style.cssText=`position:fixed;left:${i.clientX}px;top:${i.clientY}px;z-index:200`,t?n.innerHTML=`
1091
1095
  <div class="ctx-item" onclick="window.__editorNewFile('${Q(e)}')">📄 New File <span class="ctx-shortcut">Ctrl+N</span></div>
1092
1096
  <div class="ctx-item" onclick="window.__editorNewFolder('${Q(e)}')">📁 New Folder <span class="ctx-shortcut">Ctrl+Shift+N</span></div>
1093
1097
  <div class="ctx-sep"></div>
@@ -1100,19 +1104,19 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
1100
1104
  <div class="ctx-item" onclick="window.__editorDuplicate('${Q(e)}')">📋 Duplicate</div>
1101
1105
  <div class="ctx-sep"></div>
1102
1106
  <div class="ctx-item ctx-danger" onclick="window.__editorDelete('${Q(e)}',false)">🗑️ Delete <span class="ctx-shortcut">Del</span></div>
1103
- `,document.body.appendChild(n),wa=n,setTimeout(()=>{document.addEventListener("click",pn,{once:!0})},0)}function pn(){wa&&(wa.remove(),wa=null)}async function FQ(i){pn();const e=prompt("New file name:");if(!e)return;const t=i==="."?e:`${i}/${e}`;try{await L("/file/save","POST",{path:t,content:""}),await Ue(i),ui(t)}catch(n){alert("Failed: "+n.message)}}async function HQ(i){pn();const e=prompt("New folder name:");if(!e)return;const t=i==="."?e:`${i}/${e}`;try{await L("/file/save","POST",{path:`${t}/.gitkeep`,content:""}),fi.add(t),await Ue(i)}catch(n){alert("Failed: "+n.message)}}async function KQ(i,e){pn();const t=i.split("/"),n=t.pop()||"",r=t.join("/")||".",s=prompt("Rename to:",n);if(!s||s===n)return;const o=r==="."?s:`${r}/${s}`;try{await L("/file/rename","POST",{from:i,to:o});const a=ee.findIndex(l=>l.path===i);a>=0&&(ee[a].path=o,B===i&&(B=o),Wi()),await Ue(r)}catch(a){alert("Rename failed: "+a.message)}}async function JQ(i,e){if(pn(),!!confirm(`Delete ${e?"folder":"file"} "${i}"?`))try{await L("/file/delete","POST",{path:i,is_dir:e}),ee.findIndex(s=>s.path===i)>=0&&fn(i);const r=i.split("/").slice(0,-1).join("/")||".";await Ue(r)}catch(n){alert("Delete failed: "+n.message)}}async function Ez(i){pn();const e=i.split("/"),t=e.pop()||"",n=t.includes(".")?"."+t.split(".").pop():"",s=`${n?t.slice(0,-n.length):t}-copy${n}`,o=e.join("/")||".",a=o==="."?s:`${o}/${s}`;try{const l=await L(`/file?path=${encodeURIComponent(i)}`);await L("/file/save","POST",{path:a,content:l.content}),await Ue(o),ui(a)}catch(l){alert("Duplicate failed: "+l.message)}}function Az(){const i=document.getElementById("editor-menu-dropdown");if(!i)return;const e=i.style.display==="none";i.style.display=e?"block":"none",e&&setTimeout(()=>{const t=n=>{var r;(r=n.target)!=null&&r.closest(".editor-menu-wrapper")||(i.style.display="none"),document.removeEventListener("click",t)};document.addEventListener("click",t)},0)}async function ey(){var s;const i=document.getElementById("deps-search-input"),e=(s=i==null?void 0:i.value)==null?void 0:s.trim();if(!e)return;const t=document.getElementById("deps-search-results");t&&(t.innerHTML='<div class="text-sm text-muted" style="padding:8px;text-align:center">Searching...</div>');const n=(B==null?void 0:B.split("/").pop())||"",r=ad[n];if(r)try{const o=new AbortController,a=setTimeout(()=>o.abort(),1e4),l=await fetch(`/__dev/api/deps/search?q=${encodeURIComponent(e)}&registry=${r.registry}`,{signal:o.signal});clearTimeout(a);const c=(await l.json()).packages||[];if(!t)return;if(c.length===0){t.innerHTML='<div class="text-sm text-muted" style="padding:8px;text-align:center">No packages found</div>';return}t.innerHTML=c.map(d=>`<div class="deps-item">
1107
+ `,document.body.appendChild(n),wa=n,setTimeout(()=>{document.addEventListener("click",pn,{once:!0})},0)}function pn(){wa&&(wa.remove(),wa=null)}async function FQ(i){pn();const e=prompt("New file name:");if(!e)return;const t=i==="."?e:`${i}/${e}`;try{await L("/file/save","POST",{path:t,content:""}),await Ue(i),ui(t)}catch(n){alert("Failed: "+n.message)}}async function HQ(i){pn();const e=prompt("New folder name:");if(!e)return;const t=i==="."?e:`${i}/${e}`;try{await L("/file/save","POST",{path:`${t}/.gitkeep`,content:""}),fi.add(t),await Ue(i)}catch(n){alert("Failed: "+n.message)}}async function KQ(i,e){pn();const t=i.split("/"),n=t.pop()||"",r=t.join("/")||".",s=prompt("Rename to:",n);if(!s||s===n)return;const o=r==="."?s:`${r}/${s}`;try{await L("/file/rename","POST",{from:i,to:o});const a=ee.findIndex(l=>l.path===i);a>=0&&(ee[a].path=o,B===i&&(B=o),Wi()),await Ue(r)}catch(a){alert("Rename failed: "+a.message)}}async function JQ(i,e){if(pn(),!!confirm(`Delete ${e?"folder":"file"} "${i}"?`))try{await L("/file/delete","POST",{path:i,is_dir:e}),ee.findIndex(s=>s.path===i)>=0&&fn(i);const r=i.split("/").slice(0,-1).join("/")||".";await Ue(r)}catch(n){alert("Delete failed: "+n.message)}}async function Az(i){pn();const e=i.split("/"),t=e.pop()||"",n=t.includes(".")?"."+t.split(".").pop():"",s=`${n?t.slice(0,-n.length):t}-copy${n}`,o=e.join("/")||".",a=o==="."?s:`${o}/${s}`;try{const l=await L(`/file?path=${encodeURIComponent(i)}`);await L("/file/save","POST",{path:a,content:l.content}),await Ue(o),ui(a)}catch(l){alert("Duplicate failed: "+l.message)}}function Uz(){const i=document.getElementById("editor-menu-dropdown");if(!i)return;const e=i.style.display==="none";i.style.display=e?"block":"none",e&&setTimeout(()=>{const t=n=>{var r;(r=n.target)!=null&&r.closest(".editor-menu-wrapper")||(i.style.display="none"),document.removeEventListener("click",t)};document.addEventListener("click",t)},0)}async function ey(){var s;const i=document.getElementById("deps-search-input"),e=(s=i==null?void 0:i.value)==null?void 0:s.trim();if(!e)return;const t=document.getElementById("deps-search-results");t&&(t.innerHTML='<div class="text-sm text-muted" style="padding:8px;text-align:center">Searching...</div>');const n=(B==null?void 0:B.split("/").pop())||"",r=ad[n];if(r)try{const o=new AbortController,a=setTimeout(()=>o.abort(),1e4),l=await fetch(`/__dev/api/deps/search?q=${encodeURIComponent(e)}&registry=${r.registry}`,{signal:o.signal});clearTimeout(a);const c=(await l.json()).packages||[];if(!t)return;if(c.length===0){t.innerHTML='<div class="text-sm text-muted" style="padding:8px;text-align:center">No packages found</div>';return}t.innerHTML=c.map(d=>`<div class="deps-item">
1104
1108
  <div class="deps-item-name">${Q(d.name)}</div>
1105
1109
  <div class="deps-item-desc">${Q(d.description||"")}</div>
1106
1110
  <div class="deps-item-meta">
1107
1111
  <span>${Q(d.version||"")}</span>
1108
1112
  <button class="btn btn-sm" style="font-size:0.6rem;padding:2px 8px;color:var(--success);border-color:var(--success)" onclick="window.__depsInstall('${Q(d.name)}','${Q(d.version||"")}')">+ Install</button>
1109
1113
  </div>
1110
- </div>`).join("")}catch(o){t&&(t.innerHTML=`<div class="text-sm" style="padding:8px;color:var(--danger)">${Q(o.message||"Search failed")}</div>`)}}async function Uz(i,e){const t=(B==null?void 0:B.split("/").pop())||"",n=ad[t];if(!n)return;const r=document.getElementById("deps-search-results");r&&(r.innerHTML=`<div class="text-sm text-muted" style="padding:8px;text-align:center">Installing ${Q(i)}...</div>`);const s=document.getElementById("deps-dev-toggle"),o=!!(s!=null&&s.checked);try{const a=await L("/deps/install","POST",{name:i,version:e,registry:n.registry,file:B,dev:o});if(r&&(r.innerHTML=`<div class="text-sm" style="padding:8px;color:var(--success)">✔ ${Q(a.message||`Installed ${i}`)}</div>`),B){const l=await L(`/file?path=${encodeURIComponent(B)}`),O=ee.find(c=>c.path===B);O&&l.content&&(O.content=l.content,O.dirty=!1,ld(),RQ(n))}}catch(a){r&&(r.innerHTML=`<div class="text-sm" style="padding:8px;color:var(--danger)">✗ ${Q(a.message||"Install failed")}</div>`)}}window.__depsSearch=ey,window.__depsInstall=Uz;async function jz(i){const e=prompt(`Name for the new ${i}:`);if(!e)return;const t=document.getElementById("scaffold-output");t&&(t.style.display="block",t.textContent=`Generating ${i} "${e}"...`);try{const n=await L("/scaffold/run","POST",{kind:i,name:e}),r=n.path||Gz(n.output);t&&(t.innerHTML=`<span style="color:var(--success)">✔</span> ${Q(`Created ${i}: ${e}`)}`,n.output&&(t.innerHTML+=`
1114
+ </div>`).join("")}catch(o){t&&(t.innerHTML=`<div class="text-sm" style="padding:8px;color:var(--danger)">${Q(o.message||"Search failed")}</div>`)}}async function jz(i,e){const t=(B==null?void 0:B.split("/").pop())||"",n=ad[t];if(!n)return;const r=document.getElementById("deps-search-results");r&&(r.innerHTML=`<div class="text-sm text-muted" style="padding:8px;text-align:center">Installing ${Q(i)}...</div>`);const s=document.getElementById("deps-dev-toggle"),o=!!(s!=null&&s.checked);try{const a=await L("/deps/install","POST",{name:i,version:e,registry:n.registry,file:B,dev:o});if(r&&(r.innerHTML=`<div class="text-sm" style="padding:8px;color:var(--success)">✔ ${Q(a.message||`Installed ${i}`)}</div>`),B){const l=await L(`/file?path=${encodeURIComponent(B)}`),O=ee.find(c=>c.path===B);O&&l.content&&(O.content=l.content,O.dirty=!1,ld(),RQ(n))}}catch(a){r&&(r.innerHTML=`<div class="text-sm" style="padding:8px;color:var(--danger)">✗ ${Q(a.message||"Install failed")}</div>`)}}window.__depsSearch=ey,window.__depsInstall=jz;async function Mz(i){const e=prompt(`Name for the new ${i}:`);if(!e)return;const t=document.getElementById("scaffold-output");t&&(t.style.display="block",t.textContent=`Generating ${i} "${e}"...`);try{const n=await L("/scaffold/run","POST",{kind:i,name:e}),r=n.path||Dz(n.output);t&&(t.innerHTML=`<span style="color:var(--success)">✔</span> ${Q(`Created ${i}: ${e}`)}`,n.output&&(t.innerHTML+=`
1111
1115
  <span style="opacity:0.7">${Q(String(n.output).trim())}</span>`),r&&(t.innerHTML+=`
1112
- <span style="color:var(--info);cursor:pointer;text-decoration:underline" onclick="window.__editorOpenFile('${Q(r)}')">${Q(r)}</span>`)),Ue("."),r&&setTimeout(()=>ui(r),500)}catch(n){t&&(t.innerHTML=`<span style="color:var(--danger)">✗</span> ${Q(n.message||"Failed")}`)}}const Mz={migrate:"/migrate",test:"/test",seed:"/seed/run"};async function Lz(i){const e=document.getElementById("scaffold-output"),t=Mz[i];if(!t){e&&(e.style.display="block",e.innerHTML=`<span style="color:var(--danger)">✗</span> Unknown command: ${Q(i)}`);return}e&&(e.style.display="block",e.textContent=`Running ${i}...`);try{const n=await L(t,"POST",{});e&&(e.innerHTML=`<span style="color:var(--success)">✔</span> ${Q(Iz(i,n))}`),(i==="migrate"||i==="seed")&&Ue(".")}catch(n){e&&(e.innerHTML=`<span style="color:var(--danger)">✗</span> ${Q(n.message||"Failed")}`)}}function Iz(i,e){var t,n,r;if(!e||typeof e!="object")return`${i} complete`;if(i==="migrate"){const s=((t=e.applied)==null?void 0:t.length)??0,o=((n=e.skipped)==null?void 0:n.length)??0,a=((r=e.failed)==null?void 0:r.length)??0;return`Migrate: ${s} applied, ${o} skipped${a?`, ${a} failed`:""}`}if(i==="seed"){const s=e.seeded??0,o=e.failed??0;return`Seed: ${s} rows${o?`, ${o} failed`:""}`}return i==="test"?`Tests ${e.ok!==!1&&e.code===0?"passed":"failed"}${e.output?`
1113
- ${String(e.output).trim().slice(-600)}`:""}`:e.output||e.message||`${i} complete`}function Gz(i){if(!i)return;const e=String(i).match(/((?:src|migrations|routes|models)\/[\w./-]+)/);return e?e[1]:void 0}window.__scaffold=jz,window.__scaffoldRun=Lz;const Sd="tina4.editor.sidebar-width",wd="tina4.editor.right-panel-width";function Dz(){const i=localStorage.getItem(Sd),e=localStorage.getItem(wd),t=document.getElementById("editor-sidebar"),n=document.getElementById("editor-right-panel");t&&i&&(t.style.width=i+"px"),n&&e&&(n.style.width=e+"px")}function Bz(){const i=document.querySelector(".editor-layout"),e=document.getElementById("editor-sidebar"),t=document.getElementById("editor-right-panel"),n=document.getElementById("editor-splitter-left"),r=document.getElementById("editor-splitter-right");!i||!e||!t||!n||!r||(Dz(),ty(n,s=>{const o=parseFloat(getComputedStyle(e).width);return a=>{const l=Math.max(160,Math.min(600,o+a));return e.style.width=l+"px",l}},s=>localStorage.setItem(Sd,String(Math.round(s)))),ty(r,()=>{const s=parseFloat(getComputedStyle(t).width);return o=>{const a=Math.max(200,Math.min(800,s-o));return t.style.width=a+"px",a}},s=>localStorage.setItem(wd,String(Math.round(s)))))}function ty(i,e,t){i.addEventListener("mousedown",n=>{n.preventDefault(),i.classList.add("dragging"),document.body.style.userSelect="none",document.body.style.cursor="col-resize";const r=n.clientX,s=e(r);let o=0;const a=O=>{o=s(O.clientX-r)},l=()=>{document.removeEventListener("mousemove",a),document.removeEventListener("mouseup",l),i.classList.remove("dragging"),document.body.style.userSelect="",document.body.style.cursor="",o&&t(o)};document.addEventListener("mousemove",a),document.addEventListener("mouseup",l)}),i.addEventListener("dblclick",()=>{const n=document.getElementById("editor-sidebar"),r=document.getElementById("editor-right-panel");i.id==="editor-splitter-left"&&n&&(n.style.width="",localStorage.removeItem(Sd)),i.id==="editor-splitter-right"&&r&&(r.style.width="",localStorage.removeItem(wd))})}function Nz(i,e){document.querySelectorAll(".tab-ctx-menu").forEach(h=>h.remove());const t=ee.findIndex(h=>h.path===e),n=t>0,r=t>=0&&t<ee.length-1,s=ee.length>1,o=(h,f,p)=>`<div class="tab-ctx-item ${p?"":"disabled"}"${p?` data-action="${f}"`:""}>
1116
+ <span style="color:var(--info);cursor:pointer;text-decoration:underline" onclick="window.__editorOpenFile('${Q(r)}')">${Q(r)}</span>`)),Ue("."),r&&setTimeout(()=>ui(r),500)}catch(n){t&&(t.innerHTML=`<span style="color:var(--danger)">✗</span> ${Q(n.message||"Failed")}`)}}const Lz={migrate:"/migrate",test:"/test",seed:"/seed/run"};async function Iz(i){const e=document.getElementById("scaffold-output"),t=Lz[i];if(!t){e&&(e.style.display="block",e.innerHTML=`<span style="color:var(--danger)">✗</span> Unknown command: ${Q(i)}`);return}e&&(e.style.display="block",e.textContent=`Running ${i}...`);try{const n=await L(t,"POST",{});e&&(e.innerHTML=`<span style="color:var(--success)">✔</span> ${Q(Gz(i,n))}`),(i==="migrate"||i==="seed")&&Ue(".")}catch(n){e&&(e.innerHTML=`<span style="color:var(--danger)">✗</span> ${Q(n.message||"Failed")}`)}}function Gz(i,e){var t,n,r;if(!e||typeof e!="object")return`${i} complete`;if(i==="migrate"){const s=((t=e.applied)==null?void 0:t.length)??0,o=((n=e.skipped)==null?void 0:n.length)??0,a=((r=e.failed)==null?void 0:r.length)??0;return`Migrate: ${s} applied, ${o} skipped${a?`, ${a} failed`:""}`}if(i==="seed"){const s=e.seeded??0,o=e.failed??0;return`Seed: ${s} rows${o?`, ${o} failed`:""}`}return i==="test"?`Tests ${e.ok!==!1&&e.code===0?"passed":"failed"}${e.output?`
1117
+ ${String(e.output).trim().slice(-600)}`:""}`:e.output||e.message||`${i} complete`}function Dz(i){if(!i)return;const e=String(i).match(/((?:src|migrations|routes|models)\/[\w./-]+)/);return e?e[1]:void 0}window.__scaffold=Mz,window.__scaffoldRun=Iz;const Sd="tina4.editor.sidebar-width",wd="tina4.editor.right-panel-width";function Bz(){const i=localStorage.getItem(Sd),e=localStorage.getItem(wd),t=document.getElementById("editor-sidebar"),n=document.getElementById("editor-right-panel");t&&i&&(t.style.width=i+"px"),n&&e&&(n.style.width=e+"px")}function Nz(){const i=document.querySelector(".editor-layout"),e=document.getElementById("editor-sidebar"),t=document.getElementById("editor-right-panel"),n=document.getElementById("editor-splitter-left"),r=document.getElementById("editor-splitter-right");!i||!e||!t||!n||!r||(Bz(),ty(n,s=>{const o=parseFloat(getComputedStyle(e).width);return a=>{const l=Math.max(160,Math.min(600,o+a));return e.style.width=l+"px",l}},s=>localStorage.setItem(Sd,String(Math.round(s)))),ty(r,()=>{const s=parseFloat(getComputedStyle(t).width);return o=>{const a=Math.max(200,Math.min(800,s-o));return t.style.width=a+"px",a}},s=>localStorage.setItem(wd,String(Math.round(s)))))}function ty(i,e,t){i.addEventListener("mousedown",n=>{n.preventDefault(),i.classList.add("dragging"),document.body.style.userSelect="none",document.body.style.cursor="col-resize";const r=n.clientX,s=e(r);let o=0;const a=O=>{o=s(O.clientX-r)},l=()=>{document.removeEventListener("mousemove",a),document.removeEventListener("mouseup",l),i.classList.remove("dragging"),document.body.style.userSelect="",document.body.style.cursor="",o&&t(o)};document.addEventListener("mousemove",a),document.addEventListener("mouseup",l)}),i.addEventListener("dblclick",()=>{const n=document.getElementById("editor-sidebar"),r=document.getElementById("editor-right-panel");i.id==="editor-splitter-left"&&n&&(n.style.width="",localStorage.removeItem(Sd)),i.id==="editor-splitter-right"&&r&&(r.style.width="",localStorage.removeItem(wd))})}function Fz(i,e){document.querySelectorAll(".tab-ctx-menu").forEach(h=>h.remove());const t=ee.findIndex(h=>h.path===e),n=t>0,r=t>=0&&t<ee.length-1,s=ee.length>1,o=(h,f,p)=>`<div class="tab-ctx-item ${p?"":"disabled"}"${p?` data-action="${f}"`:""}>
1114
1118
  <span>${h}</span>
1115
- </div>`,a=document.createElement("div");a.className="tab-ctx-menu",a.innerHTML=o("Close","close",!0)+o("Close Others","close-others",s)+'<div class="tab-ctx-sep"></div>'+o("Close to the Left","close-left",n)+o("Close to the Right","close-right",r)+'<div class="tab-ctx-sep"></div>'+o("Close All","close-all",ee.length>0),a.addEventListener("click",h=>{const f=h.target.closest("[data-action]");if(!f)return;switch(f.dataset.action){case"close":fn(e);break;case"close-others":Fz(e);break;case"close-left":Hz(e);break;case"close-right":Kz(e);break;case"close-all":Jz();break}a.remove()}),document.body.appendChild(a);const l=Math.min(i.clientX,window.innerWidth-190),O=Math.min(i.clientY,window.innerHeight-a.offsetHeight-10);a.style.left=l+"px",a.style.top=O+"px";const c=h=>{h&&a.contains(h.target)||(a.remove(),document.removeEventListener("mousedown",c,!0),document.removeEventListener("keydown",d,!0),document.removeEventListener("scroll",c,!0))},d=h=>{h.key==="Escape"&&c()};setTimeout(()=>{document.addEventListener("mousedown",c,!0),document.addEventListener("keydown",d,!0),document.addEventListener("scroll",c,!0)},0)}function Fz(i){ee.filter(t=>t.path!==i).map(t=>t.path).forEach(t=>fn(t))}function Hz(i){const e=ee.findIndex(n=>n.path===i);if(e<1)return;ee.slice(0,e).map(n=>n.path).forEach(n=>fn(n))}function Kz(i){const e=ee.findIndex(n=>n.path===i);if(e<0||e>=ee.length-1)return;ee.slice(e+1).map(n=>n.path).forEach(n=>fn(n))}function Jz(){ee.map(e=>e.path).forEach(e=>fn(e))}window.__editorToggleMenu=Az,window.__editorCtxMenu=Wz,window.__editorNewFile=FQ,window.__editorNewFolder=HQ,window.__editorRename=KQ,window.__editorDelete=JQ,window.__editorDuplicate=Ez,window.__editorToggleDir=CZ,window.__editorOpenFile=ui,window.__editorSwitchFile=ls,window.__editorCloseFile=fn,window.__editorTabCtxMenu=Nz,window.__editorPopOut=EZ,window.__editorToggleAI=jZ,window.__editorPlanSwitch=NZ,window.__editorPlanOpen=FZ,window.__editorPlanCreate=HZ,window.__editorPlanRun=LZ,window.__editorPlanStop=IZ,window.__editorThoughtDismiss=BZ,window.__aiBlockCopy=Zz,window.__aiBlockInsert=zz,window.__aiBlockApply=Cz,window.__aiBlockSaveAs=Yz,window.__aiBlockToggle=Vz,document.addEventListener("keydown",i=>{(i.ctrlKey||i.metaKey)&&i.key==="s"&&B&&(i.preventDefault(),qQ())}),document.addEventListener("keydown",i=>{const e=i.target;(e==null?void 0:e.id)==="deps-search-input"&&i.key==="Enter"&&(i.preventDefault(),ey())}),document.addEventListener("keydown",i=>{var t,n,r;if(!(!B&&!document.querySelector(".editor-layout")||((t=i.target)==null?void 0:t.tagName)==="INPUT"||((n=i.target)==null?void 0:n.tagName)==="TEXTAREA")){if((i.ctrlKey||i.metaKey)&&i.key==="n"&&!i.shiftKey){i.preventDefault();const s=B&&B.split("/").slice(0,-1).join("/")||".";FQ(s)}if((i.ctrlKey||i.metaKey)&&i.key==="N"&&i.shiftKey){i.preventDefault();const s=B&&B.split("/").slice(0,-1).join("/")||".";HQ(s)}i.key==="F2"&&B&&(i.preventDefault(),KQ(B)),i.key==="Delete"&&B&&!((r=i.target)!=null&&r.closest(".cm-editor"))&&(i.preventDefault(),JQ(B,!1))}}),(function(){const e=()=>{document.querySelectorAll(".tina4-fb-btn, .tina4-fb-modal").forEach(t=>t.remove()),window.__tina4FeedbackLoaded=!0};e(),requestAnimationFrame(e),setTimeout(e,500),setTimeout(e,2e3)})();const iy=document.createElement("style");iy.textContent=ly,document.head.appendChild(iy);const Pa=oy();ay(Pa);let ny=[{id:"editor",label:"Code With Me",render:RZ},...[{id:"routes",label:"Routes",render:Oy},{id:"database",label:"Database",render:cy},{id:"graphql",label:"GraphQL",render:Ry},{id:"queue",label:"Queue",render:Vy},{id:"errors",label:"Errors",render:yy},{id:"metrics",label:"Metrics",render:Ty},{id:"system",label:"System",render:Py}]],Pd="editor";function e2(){const i=document.getElementById("app");if(!i)return;i.innerHTML=`
1119
+ </div>`,a=document.createElement("div");a.className="tab-ctx-menu",a.innerHTML=o("Close","close",!0)+o("Close Others","close-others",s)+'<div class="tab-ctx-sep"></div>'+o("Close to the Left","close-left",n)+o("Close to the Right","close-right",r)+'<div class="tab-ctx-sep"></div>'+o("Close All","close-all",ee.length>0),a.addEventListener("click",h=>{const f=h.target.closest("[data-action]");if(!f)return;switch(f.dataset.action){case"close":fn(e);break;case"close-others":Hz(e);break;case"close-left":Kz(e);break;case"close-right":Jz(e);break;case"close-all":e2();break}a.remove()}),document.body.appendChild(a);const l=Math.min(i.clientX,window.innerWidth-190),O=Math.min(i.clientY,window.innerHeight-a.offsetHeight-10);a.style.left=l+"px",a.style.top=O+"px";const c=h=>{h&&a.contains(h.target)||(a.remove(),document.removeEventListener("mousedown",c,!0),document.removeEventListener("keydown",d,!0),document.removeEventListener("scroll",c,!0))},d=h=>{h.key==="Escape"&&c()};setTimeout(()=>{document.addEventListener("mousedown",c,!0),document.addEventListener("keydown",d,!0),document.addEventListener("scroll",c,!0)},0)}function Hz(i){ee.filter(t=>t.path!==i).map(t=>t.path).forEach(t=>fn(t))}function Kz(i){const e=ee.findIndex(n=>n.path===i);if(e<1)return;ee.slice(0,e).map(n=>n.path).forEach(n=>fn(n))}function Jz(i){const e=ee.findIndex(n=>n.path===i);if(e<0||e>=ee.length-1)return;ee.slice(e+1).map(n=>n.path).forEach(n=>fn(n))}function e2(){ee.map(e=>e.path).forEach(e=>fn(e))}window.__editorToggleMenu=Uz,window.__editorCtxMenu=Ez,window.__editorNewFile=FQ,window.__editorNewFolder=HQ,window.__editorRename=KQ,window.__editorDelete=JQ,window.__editorDuplicate=Az,window.__editorToggleDir=CZ,window.__editorOpenFile=ui,window.__editorSwitchFile=ls,window.__editorCloseFile=fn,window.__editorTabCtxMenu=Fz,window.__editorPopOut=EZ,window.__editorToggleAI=jZ,window.__editorPlanSwitch=NZ,window.__editorPlanOpen=FZ,window.__editorPlanCreate=HZ,window.__editorPlanRun=LZ,window.__editorPlanStop=IZ,window.__editorThoughtDismiss=BZ,window.__aiBlockCopy=zz,window.__aiBlockInsert=qz,window.__aiBlockApply=Yz,window.__aiBlockSaveAs=Wz,window.__aiBlockToggle=Cz,document.addEventListener("keydown",i=>{(i.ctrlKey||i.metaKey)&&i.key==="s"&&B&&(i.preventDefault(),qQ())}),document.addEventListener("keydown",i=>{const e=i.target;(e==null?void 0:e.id)==="deps-search-input"&&i.key==="Enter"&&(i.preventDefault(),ey())}),document.addEventListener("keydown",i=>{var t,n,r;if(!(!B&&!document.querySelector(".editor-layout")||((t=i.target)==null?void 0:t.tagName)==="INPUT"||((n=i.target)==null?void 0:n.tagName)==="TEXTAREA")){if((i.ctrlKey||i.metaKey)&&i.key==="n"&&!i.shiftKey){i.preventDefault();const s=B&&B.split("/").slice(0,-1).join("/")||".";FQ(s)}if((i.ctrlKey||i.metaKey)&&i.key==="N"&&i.shiftKey){i.preventDefault();const s=B&&B.split("/").slice(0,-1).join("/")||".";HQ(s)}i.key==="F2"&&B&&(i.preventDefault(),KQ(B)),i.key==="Delete"&&B&&!((r=i.target)!=null&&r.closest(".cm-editor"))&&(i.preventDefault(),JQ(B,!1))}}),(function(){const e=()=>{document.querySelectorAll(".tina4-fb-btn, .tina4-fb-modal").forEach(t=>t.remove()),window.__tina4FeedbackLoaded=!0};e(),requestAnimationFrame(e),setTimeout(e,500),setTimeout(e,2e3)})();const iy=document.createElement("style");iy.textContent=ly,document.head.appendChild(iy);const Pa=oy();ay(Pa);let ny=[{id:"editor",label:"Code With Me",render:RZ},...[{id:"routes",label:"Routes",render:Oy},{id:"database",label:"Database",render:cy},{id:"graphql",label:"GraphQL",render:Ry},{id:"queue",label:"Queue",render:Vy},{id:"errors",label:"Errors",render:yy},{id:"metrics",label:"Metrics",render:Ty},{id:"system",label:"System",render:Py}]],Pd="editor";function t2(){const i=document.getElementById("app");if(!i)return;i.innerHTML=`
1116
1120
  <div class="dev-admin">
1117
1121
  <div class="dev-header">
1118
1122
  <h1><span>Tina4</span> Dev Admin</h1>
@@ -1124,4 +1128,4 @@ ${String(e.output).trim().slice(-600)}`:""}`:e.output||e.message||`${i} complete
1124
1128
  <div class="dev-tabs" id="tab-bar"></div>
1125
1129
  <div class="dev-content" id="tab-content"></div>
1126
1130
  </div>
1127
- `;const e=document.getElementById("tab-bar");e.innerHTML=ny.map(t=>`<button class="dev-tab ${t.id===Pd?"active":""}" data-tab="${t.id}" onclick="window.__switchTab('${t.id}')">${t.label}</button>`).join(""),ry(Pd)}function ry(i){Pd=i;const e=document.querySelector(".dev-header"),t=document.getElementById("tab-bar"),n=document.querySelector(".dev-admin"),r=i==="editor";e&&(e.style.display=r?"none":""),t&&(t.style.display=r?"none":""),n&&n.classList.toggle("fullscreen-editor",r),document.querySelectorAll(".dev-tab").forEach(l=>{l.classList.toggle("active",l.dataset.tab===i)});const s=document.getElementById("tab-content");if(!s)return;const o=document.createElement("div");o.className="dev-panel active",s.innerHTML="",s.appendChild(o);const a=ny.find(l=>l.id===i);a&&a.render(o)}function t2(){if(window.parent!==window)try{const i=window.parent.document.getElementById("tina4-dev-panel");i&&i.remove()}catch{document.body.style.display="none"}}window.__closeDevAdmin=t2,window.__switchTab=ry,e2(),L("/system").then(i=>{const e=document.getElementById("version-label"),t=i.version||(typeof i.framework=="object"?i.framework.version:null)||(typeof i.framework=="string"?i.framework:null);e&&t&&(e.innerHTML=`${Pa.name} &bull; v${Q(t)}`)}).catch(()=>{const i=document.getElementById("version-label");i&&(i.innerHTML=`${Pa.name}`)})})();
1131
+ `;const e=document.getElementById("tab-bar");e.innerHTML=ny.map(t=>`<button class="dev-tab ${t.id===Pd?"active":""}" data-tab="${t.id}" onclick="window.__switchTab('${t.id}')">${t.label}</button>`).join(""),ry(Pd)}function ry(i){Pd=i;const e=document.querySelector(".dev-header"),t=document.getElementById("tab-bar"),n=document.querySelector(".dev-admin"),r=i==="editor";e&&(e.style.display=r?"none":""),t&&(t.style.display=r?"none":""),n&&n.classList.toggle("fullscreen-editor",r),document.querySelectorAll(".dev-tab").forEach(l=>{l.classList.toggle("active",l.dataset.tab===i)});const s=document.getElementById("tab-content");if(!s)return;const o=document.createElement("div");o.className="dev-panel active",s.innerHTML="",s.appendChild(o);const a=ny.find(l=>l.id===i);a&&a.render(o)}function i2(){if(window.parent!==window)try{const i=window.parent.document.getElementById("tina4-dev-panel");i&&i.remove()}catch{document.body.style.display="none"}}window.__closeDevAdmin=i2,window.__switchTab=ry,t2(),L("/system").then(i=>{const e=document.getElementById("version-label"),t=i.version||(typeof i.framework=="object"?i.framework.version:null)||(typeof i.framework=="string"?i.framework:null);e&&t&&(e.innerHTML=`${Pa.name} &bull; v${Q(t)}`)}).catch(()=>{const i=document.getElementById("version-label");i&&(i.innerHTML=`${Pa.name}`)})})();
@@ -116,6 +116,12 @@ function skillsRef(): string {
116
116
  * The child prints a JSON array of the URLs it fetched successfully; any fetch
117
117
  * failure is skipped, never fatal.
118
118
  *
119
+ * MEASURED (2026-08-13): a real GitHub raw-content fetch occasionally drops a
120
+ * request under load (transient DNS/TLS hiccup, not a missing file — every
121
+ * URL here resolves fine on its own) while its siblings in the same batch
122
+ * succeed. One retry pass over just the stragglers, still inside the same
123
+ * child process, fixes that for real installer users too, not only the test.
124
+ *
119
125
  * @param jobs one entry per unique URL, with every file path it should land in
120
126
  * @returns the set of URLs that were fetched and written to disk
121
127
  */
@@ -125,27 +131,37 @@ function downloadSkillsSync(jobs: { url: string; dests: string[] }[]): Set<strin
125
131
  const jobs = JSON.parse(process.argv[1]);
126
132
  const fs = require("node:fs");
127
133
  const path = require("node:path");
134
+ async function fetchOne(job) {
135
+ const resp = await fetch(job.url, { signal: AbortSignal.timeout(15000) });
136
+ if (!resp.ok) throw new Error("HTTP " + resp.status);
137
+ const buf = Buffer.from(await resp.arrayBuffer());
138
+ for (const dest of job.dests) {
139
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
140
+ fs.writeFileSync(dest, buf);
141
+ }
142
+ }
128
143
  (async () => {
129
144
  const ok = [];
130
- await Promise.all(jobs.map(async (job) => {
131
- try {
132
- const resp = await fetch(job.url, { signal: AbortSignal.timeout(15000) });
133
- if (!resp.ok) return;
134
- const buf = Buffer.from(await resp.arrayBuffer());
135
- for (const dest of job.dests) {
136
- fs.mkdirSync(path.dirname(dest), { recursive: true });
137
- fs.writeFileSync(dest, buf);
145
+ let pending = jobs;
146
+ for (let attempt = 0; attempt < 2 && pending.length > 0; attempt++) {
147
+ const failed = [];
148
+ await Promise.all(pending.map(async (job) => {
149
+ try {
150
+ await fetchOne(job);
151
+ ok.push(job.url);
152
+ } catch {
153
+ failed.push(job);
138
154
  }
139
- ok.push(job.url);
140
- } catch { /* skip this file */ }
141
- }));
155
+ }));
156
+ pending = failed;
157
+ }
142
158
  process.stdout.write(JSON.stringify(ok));
143
159
  })();
144
160
  `;
145
161
  try {
146
162
  const out = execFileSync(process.execPath, ["-e", child, JSON.stringify(jobs)], {
147
163
  encoding: "utf-8",
148
- timeout: 45000,
164
+ timeout: 50000,
149
165
  maxBuffer: 8 * 1024 * 1024,
150
166
  });
151
167
  return new Set<string>(JSON.parse(out || "[]"));
@@ -20,6 +20,7 @@ import { randomBytes } from "node:crypto";
20
20
  import { promises as fsp, createWriteStream } from "node:fs";
21
21
  import { basename } from "node:path";
22
22
  import { pipeline } from "node:stream/promises";
23
+ import { TINA4_VERSION } from "./version.js";
23
24
 
24
25
  export interface ApiResult {
25
26
  http_code: number | null;
@@ -617,10 +618,17 @@ export class Api {
617
618
  }
618
619
 
619
620
  /**
620
- * Build the request headers (auth + cookie jar + extras) and serialize the
621
- * body to a Buffer. Shared by every verb, upload, and download so the wire
622
- * shape is identical and the transport seam sees exactly what the network
623
- * path would.
621
+ * Build the request headers (default User-Agent + auth + cookie jar +
622
+ * extras) and serialize the body to a Buffer. Shared by every verb,
623
+ * upload, and download so the wire shape is identical and the transport
624
+ * seam sees exactly what the network path would.
625
+ *
626
+ * VERSION-DEC-03 (feature 130): every outbound request carries a default
627
+ * `Tina4/<version>` User-Agent. `this.headers` is spread AFTER the
628
+ * default, and `extraHeaders` after that, so a caller-supplied
629
+ * `User-Agent` (via the constructor's `headers` option, `addHeaders()`,
630
+ * or a per-call `extraHeaders`) always wins -- this is a default, never a
631
+ * clobber.
624
632
  */
625
633
  private buildRequest(
626
634
  method: string,
@@ -628,7 +636,7 @@ export class Api {
628
636
  body: unknown,
629
637
  extraHeaders?: Record<string, string>,
630
638
  ): { headers: Record<string, string>; data: Buffer | undefined } {
631
- const headers: Record<string, string> = { ...this.headers };
639
+ const headers: Record<string, string> = { "User-Agent": `Tina4/${TINA4_VERSION}`, ...this.headers };
632
640
  if (this.authHeader) {
633
641
  headers["Authorization"] = this.authHeader;
634
642
  }
@@ -50,12 +50,15 @@ const _tasks: BackgroundTask[] = [];
50
50
  *
51
51
  * @param callback Function to call (sync or async, no arguments).
52
52
  * @param intervalSeconds Seconds between invocations (default: 1).
53
- * @returns A handle whose `stop()` clears just this one task.
53
+ * @returns A handle whose `stop()` clears just this one task and returns whether
54
+ * it removed a live task (true) or was already stopped (false). This is
55
+ * the ONE background surface — a stop-handle plus a count — shared with
56
+ * Python/PHP/Ruby (`handle.stop()` -> bool, `backgroundTaskCount()`).
54
57
  */
55
58
  export function background(
56
59
  callback: () => unknown | Promise<unknown>,
57
60
  intervalSeconds = 1,
58
- ): { stop: () => void } {
61
+ ): { stop: () => boolean } {
59
62
  if (typeof callback !== "function") {
60
63
  throw new TypeError("background(callback, interval): callback must be a function");
61
64
  }
@@ -99,11 +102,14 @@ export function background(
99
102
  _tasks.push(task);
100
103
 
101
104
  return {
102
- stop: () => {
105
+ stop: (): boolean => {
106
+ // Idempotent: true the first time (a live task was removed), false after.
107
+ if (task.stopped) return false;
103
108
  task.stopped = true;
104
109
  clearTimeout(task.timer);
105
110
  const idx = _tasks.indexOf(task);
106
111
  if (idx !== -1) _tasks.splice(idx, 1);
112
+ return true;
107
113
  },
108
114
  };
109
115
  }