tina4ruby 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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +84 -0
  3. data/lib/tina4/ai.rb +32 -3
  4. data/lib/tina4/api.rb +5 -0
  5. data/lib/tina4/auto_crud.rb +62 -4
  6. data/lib/tina4/background.rb +112 -31
  7. data/lib/tina4/cache.rb +3 -2
  8. data/lib/tina4/cli.rb +55 -67
  9. data/lib/tina4/database.rb +97 -49
  10. data/lib/tina4/database_adapter.rb +169 -15
  11. data/lib/tina4/dev_admin.rb +137 -9
  12. data/lib/tina4/dispatch_pipeline.rb +145 -4
  13. data/lib/tina4/drivers/firebird_driver.rb +59 -12
  14. data/lib/tina4/drivers/mongodb_driver.rb +98 -14
  15. data/lib/tina4/drivers/mssql_driver.rb +39 -2
  16. data/lib/tina4/drivers/mysql_driver.rb +43 -3
  17. data/lib/tina4/drivers/odbc_driver.rb +36 -2
  18. data/lib/tina4/drivers/postgres_driver.rb +5 -0
  19. data/lib/tina4/drivers/sqlite_driver.rb +11 -1
  20. data/lib/tina4/env.rb +1 -1
  21. data/lib/tina4/error_overlay.rb +43 -49
  22. data/lib/tina4/field_types.rb +33 -16
  23. data/lib/tina4/frond.rb +24 -2
  24. data/lib/tina4/gallery/auth/src/routes/api/gallery_auth.rb +1 -1
  25. data/lib/tina4/gallery/templates/src/templates/gallery_page.twig +1 -1
  26. data/lib/tina4/graphql.rb +2 -2
  27. data/lib/tina4/log.rb +652 -485
  28. data/lib/tina4/mcp.rb +9 -1
  29. data/lib/tina4/messenger.rb +25 -0
  30. data/lib/tina4/middleware.rb +189 -76
  31. data/lib/tina4/migration.rb +47 -15
  32. data/lib/tina4/orm.rb +280 -59
  33. data/lib/tina4/port_takeover.rb +202 -0
  34. data/lib/tina4/public/js/tina4-dev-admin.min.js +23 -19
  35. data/lib/tina4/rack_app.rb +201 -59
  36. data/lib/tina4/realtime.rb +6 -1
  37. data/lib/tina4/request.rb +259 -51
  38. data/lib/tina4/router.rb +20 -2
  39. data/lib/tina4/seeder.rb +68 -19
  40. data/lib/tina4/shutdown.rb +4 -0
  41. data/lib/tina4/sql_translator.rb +115 -86
  42. data/lib/tina4/swagger.rb +19 -3
  43. data/lib/tina4/template.rb +61 -6
  44. data/lib/tina4/test_client.rb +49 -3
  45. data/lib/tina4/testing.rb +16 -11
  46. data/lib/tina4/validator.rb +7 -1
  47. data/lib/tina4/version.rb +1 -1
  48. data/lib/tina4/webserver.rb +28 -40
  49. data/lib/tina4.rb +12 -1
  50. metadata +3 -19
  51. data/lib/tina4/scss/tina4css/_alerts.scss +0 -34
  52. data/lib/tina4/scss/tina4css/_badges.scss +0 -22
  53. data/lib/tina4/scss/tina4css/_buttons.scss +0 -69
  54. data/lib/tina4/scss/tina4css/_cards.scss +0 -49
  55. data/lib/tina4/scss/tina4css/_forms.scss +0 -156
  56. data/lib/tina4/scss/tina4css/_grid.scss +0 -81
  57. data/lib/tina4/scss/tina4css/_modals.scss +0 -84
  58. data/lib/tina4/scss/tina4css/_nav.scss +0 -149
  59. data/lib/tina4/scss/tina4css/_pagination.scss +0 -63
  60. data/lib/tina4/scss/tina4css/_reset.scss +0 -94
  61. data/lib/tina4/scss/tina4css/_tables.scss +0 -54
  62. data/lib/tina4/scss/tina4css/_typography.scss +0 -55
  63. data/lib/tina4/scss/tina4css/_utilities.scss +0 -208
  64. data/lib/tina4/scss/tina4css/_variables.scss +0 -117
  65. data/lib/tina4/scss/tina4css/base.scss +0 -1
  66. data/lib/tina4/scss/tina4css/colors.scss +0 -48
  67. data/lib/tina4/scss/tina4css/tina4.scss +0 -18
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tina4
4
+ # Identity-checked port takeover, shared by the CLI and the runtime paths.
5
+ #
6
+ # `tina4 serve` reclaims a busy port so the edit-restart loop does not fail
7
+ # with "address already in use". The convenience has a sharp edge: "whatever is
8
+ # listening" is not always the old Tina4 server, and before this module BOTH
9
+ # takeover paths (the CLI #kill_process_on_port and the runtime bind-failure
10
+ # WebServer#free_port) SIGTERM'd whatever held the port, with NO check that the
11
+ # victim was a Tina4 dev server -- a foreign holder (another dev server, a
12
+ # database, a stray listener) was killed.
13
+ #
14
+ # This is the ONE takeover implementation both paths call (TAKEOVER-DEC-02), so
15
+ # the runtime path can never again be a weaker twin of the CLI path. It adds:
16
+ #
17
+ # - Identity (TAKEOVER-DEC-01): a Tina4 dev server writes a per-port PID file
18
+ # (`data/.tina4-serve-<port>.pid`) when it binds and removes it on clean
19
+ # exit. Takeover only signals a holder whose PID matches that file; a holder
20
+ # with no matching Tina4 PID file is REFUSED, never killed.
21
+ # - Dev gate + opt-out (TAKEOVER-DEC-03): takeover runs only in dev
22
+ # (`TINA4_DEBUG` truthy) and only when not opted out (`TINA4_NO_TAKEOVER` /
23
+ # `tina4 serve --no-kill`). A production bind never kills a port holder.
24
+ # - The existing PID safety filter and container guard, unchanged, on top.
25
+ #
26
+ # Refusing is always safe (the developer frees the port by hand); over-killing
27
+ # was the bug this fixes.
28
+ module PortTakeover
29
+ NOTHING = "nothing"
30
+ KILLED = "killed"
31
+ REFUSED_FOREIGN = "refused_foreign"
32
+ REFUSED_OPTOUT = "refused_optout"
33
+ REFUSED_PROD = "refused_prod"
34
+ SKIPPED_CONTAINER = "skipped_container"
35
+ REFUSALS = [REFUSED_FOREIGN, REFUSED_OPTOUT, REFUSED_PROD].freeze
36
+
37
+ # What a takeover attempt did, so each caller can react in its own idiom.
38
+ Result = Struct.new(:status, :port, :killed, :message) do
39
+ def reclaimed?
40
+ status == KILLED
41
+ end
42
+
43
+ def refused?
44
+ REFUSALS.include?(status)
45
+ end
46
+ end
47
+
48
+ module_function
49
+
50
+ def truthy?(value)
51
+ %w[true 1 yes on].include?(value.to_s.strip.downcase)
52
+ end
53
+
54
+ # Dev mode = TINA4_DEBUG truthy. Takeover runs only in dev.
55
+ def dev?
56
+ truthy?(ENV["TINA4_DEBUG"])
57
+ end
58
+
59
+ # True when takeover is disabled via TINA4_NO_TAKEOVER.
60
+ def no_takeover_opted_out?
61
+ truthy?(ENV["TINA4_NO_TAKEOVER"])
62
+ end
63
+
64
+ # True when this process is running inside a container. Reclaiming a port
65
+ # makes sense on a dev machine; inside a container the server IS the
66
+ # container, so there is no stale sibling to reclaim from.
67
+ def in_container?
68
+ return true if File.exist?("/.dockerenv") || File.exist?("/run/.containerenv")
69
+
70
+ blob = File.read("/proc/1/cgroup")
71
+ blob.include?("docker") || blob.include?("containerd") || blob.include?("kubepods")
72
+ rescue SystemCallError
73
+ false
74
+ end
75
+
76
+ # The PIDs from `lsof -ti` output that are safe to signal.
77
+ #
78
+ # Pure so the safety rule can be tested directly. A non-numeric field becomes
79
+ # 0 under `to_i`, and signalling PID 0 hits EVERY process in the caller's own
80
+ # process group -- the server kills itself. Accept only all-digit tokens;
81
+ # never PID 0 (our group), PID 1 (init), ourselves, or our own process group.
82
+ # This is the PID-SAFETY gate only; whether a survivor is a Tina4 server is
83
+ # the SEPARATE identity check in #take_over_port.
84
+ def selectable_pids(lsof_output, me, my_group = nil)
85
+ pids = []
86
+ lsof_output.split(/\s+/).each do |token|
87
+ next unless token.match?(/\A\d+\z/) # never coerce junk into a PID
88
+
89
+ pid = token.to_i
90
+ next if pid <= 1 || pid == me # 0 = our group, 1 = init, me = suicide
91
+ next if !my_group.nil? && pid == my_group
92
+
93
+ pids << pid unless pids.include?(pid)
94
+ end
95
+ pids
96
+ end
97
+
98
+ def runtime_dir(base_dir = nil)
99
+ base_dir || File.join(Dir.pwd, "data")
100
+ end
101
+
102
+ def pidfile_path(port, base_dir = nil)
103
+ File.join(runtime_dir(base_dir), ".tina4-serve-#{port}.pid")
104
+ end
105
+
106
+ # Record THIS process as the Tina4 dev server on *port* (best-effort).
107
+ def write_pidfile(port, base_dir = nil, pid = nil)
108
+ dir = runtime_dir(base_dir)
109
+ require "fileutils"
110
+ FileUtils.mkdir_p(dir)
111
+ File.write(pidfile_path(port, base_dir), (pid || Process.pid).to_s)
112
+ rescue SystemCallError
113
+ nil # identity is a convenience; never let it break the server
114
+ end
115
+
116
+ # The PID a Tina4 dev server recorded for *port*, or nil if none/garbage.
117
+ def read_pidfile(port, base_dir = nil)
118
+ token = File.read(pidfile_path(port, base_dir)).strip
119
+ token.match?(/\A\d+\z/) ? token.to_i : nil
120
+ rescue SystemCallError
121
+ nil
122
+ end
123
+
124
+ # Drop the PID file for *port* (clean shutdown, or after reclaiming it).
125
+ def remove_pidfile(port, base_dir = nil)
126
+ File.delete(pidfile_path(port, base_dir))
127
+ rescue SystemCallError
128
+ nil
129
+ end
130
+
131
+ # Raw lsof/netstat PID tokens for whatever holds *port*.
132
+ def port_holders(port)
133
+ if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
134
+ tokens = []
135
+ `netstat -ano 2>&1`.each_line do |line|
136
+ next unless line.include?(":#{port}") &&
137
+ (line.include?("LISTENING") || line.include?("ESTABLISHED"))
138
+
139
+ candidate = line.strip.split(/\s+/).last
140
+ tokens << candidate if candidate&.match?(/\A\d+\z/)
141
+ end
142
+ tokens
143
+ else
144
+ `lsof -ti :#{port} 2>/dev/null`.split
145
+ end
146
+ rescue StandardError
147
+ []
148
+ end
149
+
150
+ # Reclaim *port* ONLY from an identity-confirmed Tina4 dev server. The single
151
+ # guarded path for both the CLI (`tina4 serve`) and the runtime bind-failure
152
+ # fallback. `dev`/`no_takeover` are passed in so this stays pure and directly
153
+ # testable; callers resolve them from #dev? / #no_takeover_opted_out?.
154
+ def take_over_port(port, dev:, no_takeover:, base_dir: nil, grace: 0.5)
155
+ if no_takeover
156
+ return Result.new(REFUSED_OPTOUT, port, [],
157
+ "Port #{port} is in use and takeover is disabled " \
158
+ "(TINA4_NO_TAKEOVER/--no-kill) -- free it or choose another port.")
159
+ end
160
+ unless dev
161
+ return Result.new(REFUSED_PROD, port, [],
162
+ "Port #{port} is in use; takeover is disabled outside dev mode " \
163
+ "-- free it or choose another port.")
164
+ end
165
+ return Result.new(SKIPPED_CONTAINER, port, [], "") if in_container?
166
+
167
+ tokens = port_holders(port)
168
+ return Result.new(NOTHING, port, [], "") if tokens.empty?
169
+
170
+ me = Process.pid
171
+ my_group = begin
172
+ Process.getpgrp
173
+ rescue StandardError
174
+ nil
175
+ end
176
+ holders = selectable_pids(tokens.join(" "), me, my_group)
177
+ return Result.new(NOTHING, port, [], "") if holders.empty?
178
+
179
+ recorded = read_pidfile(port, base_dir)
180
+ tina4_holders = recorded.nil? ? [] : holders.select { |pid| pid == recorded }
181
+ if tina4_holders.empty?
182
+ return Result.new(REFUSED_FOREIGN, port, [],
183
+ "Port #{port} is held by a non-Tina4 process " \
184
+ "-- free it or choose another port.")
185
+ end
186
+
187
+ killed = []
188
+ tina4_holders.each do |pid|
189
+ Process.kill("TERM", pid)
190
+ killed << pid
191
+ rescue Errno::ESRCH, Errno::EPERM
192
+ # already gone or no permission
193
+ end
194
+ return Result.new(NOTHING, port, [], "") if killed.empty?
195
+
196
+ remove_pidfile(port, base_dir)
197
+ sleep(grace) if grace.positive?
198
+ Result.new(KILLED, port, killed,
199
+ "Reclaimed port #{port} from Tina4 dev server (PID: #{killed.join(', ')}).")
200
+ end
201
+ end
202
+ end
@@ -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}`)})})();