const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/dialogs-DeLdzEM7.js","assets/index-rvuS68Al.js","assets/vendor-mini-lit-Cx886NBU.js","assets/vendor-lit-BtHfzs80.js","assets/vendor-lucide-DBEJLfdn.js","assets/vendor-marked-_iteVaMm.js","assets/vendor-sortable-B1o3v0Qs.js","assets/index-BXulo2kC.css"])))=>i.map(i=>d[i]);
import{W as zt,X as It,e as fe,Y as Ht,r,g as f,Z as wt,s as G,$ as Jt,a0 as kt,a1 as qt,_ as Yt,a2 as St,a3 as it,a4 as Xt,a5 as Zt,a6 as Le,a7 as st,a8 as Me,a9 as Qt,aa as je,ab as Vt,ac as es,ad as lt,ae as ts,af as ss,ag as as,ah as _t,ai as dt,aj as Tt,ak as At,al as ns,am as os,an as rs,ao as is,ap as ls}from"./index-rvuS68Al.js";import{b as o,r as ds,t as cs}from"./vendor-lit-BtHfzs80.js";import{D as us,c as ps,a as v,S as fs,B as Z}from"./vendor-mini-lit-Cx886NBU.js";import{loadWorkflowPageData as ms,renderWorkflowPage as gs}from"./workflow-page-CU9GxoWV.js";import{e as xs,s as vs}from"./config-scope-psrVBNWL.js";import{openOAuthDialog as bs}from"./dialogs-DeLdzEM7.js";import{ModelSelector as hs}from"./ModelSelector-DvyX4Tfh.js";import{ImageModelSelector as ys}from"./ImageModelSelector-DZVWQjXD.js";import{ae as Pt,X as W,z as $s,C as ws,an as ks,af as ct,r as Ss,ac as _s,P as ke,y as Ue,T as Ts}from"./vendor-lucide-DBEJLfdn.js";import"./vendor-marked-_iteVaMm.js";import"./vendor-sortable-B1o3v0Qs.js";import"./ident-CvQrffF1.js";import"./edit-CLPUkaSB.js";import"./Input-BnVujzeE.js";function As(e){const t=e.commands?Object.entries(e.commands).map(([n,a])=>({key:n,value:a})):[],s=e.config?Object.entries(e.config).map(([n,a])=>({key:n,value:a})):[];return{name:e.name,repo:e.repo,relative_path:e.relativePath??"",worktree_setup_command:e.worktreeSetupCommand??"",commands:t,config:s}}function Ps(e){const t={name:e.name,repo:e.repo||"."};if(e.relative_path&&(t.relative_path=e.relative_path),e.worktree_setup_command&&(t.worktree_setup_command=e.worktree_setup_command),e.commands.length>0){const s={};for(const{key:n,value:a}of e.commands)n.trim()&&a.trim()&&(s[n.trim()]=a);Object.keys(s).length>0&&(t.commands=s)}if(e.config&&e.config.length>0){const s={};for(const{key:n,value:a}of e.config)n.trim()&&a!==""&&(s[n.trim()]=a);Object.keys(s).length>0&&(t.config=s)}return t}function Rs(e,t,s){const n={components:e.map(Ps)};return s!==void 0&&(n.worktree_root=s),n}var Cs=Object.defineProperty,Ds=Object.getOwnPropertyDescriptor,Rt=(e,t,s,n)=>{for(var a=n>1?void 0:n?Ds(t,s):t,l=e.length-1,i;l>=0;l--)(i=e[l])&&(a=(n?i(t,s,a):i(a))||a);return n&&a&&Cs(t,s,a),a};let Se=class extends us{constructor(){super(...arguments),this.models=[],this.modalWidth="min(500px, 92vw)",this.modalHeight="min(640px, 90vh)"}static open(e){const t=new Se;return t.models=Array.isArray(e)?e.slice():[],t.open(),t}formatTokens(e){return!e||e<=0?"—":e>=1e6?`${(e/1e6).toFixed(0)}M`:e>=1e3?`${(e/1e3).toFixed(0)}K`:String(e)}strippedId(e){if(!e.toLowerCase().includes("claude"))return null;const t=e.indexOf("/");return t<=0?null:e.slice(t+1)}renderContent(){const e=this.models;return o`
			<div class="p-6 pb-4 border-b border-border flex-shrink-0">
				${ps({title:"Available Gateway Models"})}
				<p class="text-xs text-muted-foreground mt-2">
					Raw model IDs as reported by the AI Gateway's <code>/v1/models</code> endpoint.
				</p>
			</div>
			<div class="flex-1 overflow-y-auto" data-testid="aigw-models-list">
				${e.length===0?o`<div class="flex items-center justify-center py-8 text-muted-foreground text-sm">
							No models reported by the gateway.
						</div>`:e.map(t=>o`
								<div class="px-4 py-3 border-b border-border" data-model-id=${t.id}>
									<div class="flex items-center justify-between gap-2 mb-1">
										<span class="text-sm font-medium text-foreground truncate">${t.name||t.id}</span>
										<div class="flex items-center gap-2 text-xs text-muted-foreground shrink-0">
											${t.reasoning?o`<span class="px-1.5 py-0.5 rounded bg-secondary">Reasoning</span>`:""}
											<span>${this.formatTokens(t.contextWindow)} ctx</span>
										</div>
									</div>
									<div class="text-[11px] text-muted-foreground font-mono break-all">${t.id}</div>
									${this.strippedId(t.id)?o`<div class="text-[11px] text-muted-foreground mt-0.5">
												UI pref ID: <code class="font-mono">${this.strippedId(t.id)}</code>
											</div>`:""}
								</div>
							`)}
			</div>
			<div class="px-4 py-3 border-t border-border text-[11px] text-muted-foreground leading-relaxed flex-shrink-0">
				Claude model IDs have the <code>aws/</code> (or similar) provider prefix stripped
				when surfaced to the rest of Bobbit, so that Bedrock routing works transparently.
				If a stored default model looks like <code>aigw/aws/…</code>, it is a stale
				preference — clear and re-pick it.
			</div>
		`}};Rt([ds()],Se.prototype,"models",2);Se=Rt([cs("aigw-models-dialog")],Se);const Es="shortcuts",Ct=[{id:"shortcuts",label:"Shortcuts"},{id:"general",label:"General"},{id:"models",label:"Models"},{id:"directories",label:"Config Directories"},{id:"palette",label:"Color Palette"},{id:"account",label:"Account"},{id:"maintenance",label:"Maintenance"}],Dt=[{id:"general",label:"General"},{id:"project",label:"Commands"},{id:"components",label:"Components"},{id:"workflows",label:"Workflows"},{id:"directories",label:"Config Directories"},{id:"appearance",label:"Appearance"}];function te(){return wt().settingsScope??"system"}function Et(e){return e==="system"?Ct:Dt}function hn(e){const t=te();fe("settings",`${t}/${e}`)}function Ms(){const e=wt().settingsTab??Es,t=te(),s=Et(t);return s.some(n=>n.id===e)?e:s[0].id}let F=null,me=null,h=null,U=null,Y=!1,oe=!1,be=!1,ut=!1,re=!0,de=null;const Os=16384,pt=1024,ft=131072;let ie="";const H=new Map;let R="";const q=new Map,he=new Map,Ie=new Map;function yn(e){H.delete(e);const t=j.get(e);t&&!t.dirty&&j.delete(e)}function Mt(e){const t=H.get(e);t?.loaded||t||(H.set(e,{resolved:{},raw:{},loaded:!1}),(async()=>{try{const[s,n]=await Promise.all([f(`/api/projects/${e}/config/resolved`),f(`/api/projects/${e}/config`)]);if(s.ok&&n.ok){const a=await s.json(),l=await n.json();H.set(e,{resolved:a,raw:l,loaded:!0})}}catch{}r()})())}async function at(e,t){R="saving",r();try{const s=t._rootPath,n={...t};delete n._rootPath;const a=[];Object.keys(n).length>0&&a.push(f(`/api/projects/${e}/config`,{method:"PUT",body:JSON.stringify(n)})),s!==void 0&&a.push(f(`/api/projects/${e}`,{method:"PUT",body:JSON.stringify({rootPath:s})}));const l=await Promise.all(a);if(l.every(i=>i.ok)){if(R="saved",he.delete(e),H.delete(e),s!==void 0)try{const i=await f("/api/projects");i.ok&&kt(await i.json())}catch{}setTimeout(()=>{R="",r()},2e3)}else{R="error";for(const i of l)if(!i.ok&&i.status===400)try{const c=await i.clone().json();if(c&&c.field==="base_ref"&&typeof c.error=="string"){he.set(e,{error:c.error,details:Array.isArray(c.details)?c.details:void 0});break}}catch{}}}catch{R="error"}r()}async function Ls(e,t){const s=new Set(["config_directories","sandbox_tokens"]);await at(e,{[t]:s.has(t)?null:""})}let B=null,Be=!1,ne=!1,xe="",N=null,Xe=!1,mt="",z=null,gt=!1;const ye=new Map,X=new Map;function He(){Be||(Be=!0,as().then(e=>{B=e,G.sandboxStatus=e,r()}))}function Bs(){gt||(gt=!0,f("/api/sandbox/host-tokens").then(async e=>{e.ok?z=await e.json():z=[],r()}).catch(()=>{z=[],r()}))}function xt(){const e=te();if(Xe&&mt===e)return;Xe=!0,mt=e;const t=e!=="system"?`/api/worktree-pool?projectId=${encodeURIComponent(e)}`:"/api/worktree-pool";f(t).then(async s=>{if(s.ok){const n=await s.json();if(e==="system"&&n.pools){const a=Object.values(n.pools);a.length>0?N=a[0]:N={enabled:!1}}else N=n}else N={enabled:!1};r()}).catch(()=>{N={enabled:!1},r()})}function Ns(e,t){if(!ye.has(e)&&z!==null){const s=t.sandbox_tokens?.value,n=new Set((z||[]).map(l=>l.envVar));let a=null;if(Array.isArray(s))a=s;else if(typeof s=="string"&&s.length>0)try{const l=JSON.parse(s);Array.isArray(l)&&(a=l)}catch{a=null}if(a){const l=a.map(i=>({key:i.key||"",value:i.value==="__REDACTED__"?"":i.value||"",enabled:!!i.enabled,isHost:n.has(i.key),redacted:i.value==="__REDACTED__"}));for(const i of z||[])l.some(c=>c.key===i.envVar)||l.push({key:i.envVar,value:"",enabled:!1,isHost:!0,redacted:!1});ye.set(e,l)}else{const l=[],i=t.sandbox_host_token_overrides?.value||"";let c={};try{c=i?JSON.parse(i):{}}catch{}const p=t.sandbox_github_token?.value??"true";for(const d of z||[]){let g;c[d.envVar]!==void 0?g=c[d.envVar]!=="false":d.envVar==="GITHUB_TOKEN"?g=p!=="false":g=!0,l.push({key:d.envVar,value:"",enabled:g,isHost:!0,redacted:!1})}const u=t.sandbox_credentials?.value||"";try{if(u){const d=JSON.parse(u);if(typeof d=="object"&&d!==null&&!Array.isArray(d))for(const[g,x]of Object.entries(d)){const y=l.find($=>$.key===g);if(y){const $=String(x);y.value=$==="__REDACTED__"?"":$,y.enabled=!0,y.redacted=$==="__REDACTED__"}else{const $=String(x);l.push({key:g,value:$==="__REDACTED__"?"":$,enabled:!0,isHost:!1,redacted:$==="__REDACTED__"})}}}}catch{}ye.set(e,l)}}if(!X.has(e))try{const s=t.sandbox_mounts?.value||"";if(s){const n=JSON.parse(s);Array.isArray(n)?X.set(e,n):X.set(e,[])}else X.set(e,[])}catch{X.set(e,[])}}function ve(e,t){const s=e.filter(n=>n.key).map(n=>({key:n.key,value:n.redacted&&!n.value?"__REDACTED__":n.value,enabled:n.enabled}));t.sandbox_tokens=s.length>0?s:null}function js(e,t,s,n,a){He(),Ns(e,t);const l=s.sandbox??t.sandbox?.value??"none",i=s.sandbox_image??t.sandbox_image?.value??"bobbit-agent",c=ye.get(e)||[],p=X.get(e)||[];return o`
		<div class="flex flex-col gap-2">
			<div class="text-[11px] text-muted-foreground uppercase tracking-wider font-medium">Docker Sandbox</div>
			<p class="text-xs text-muted-foreground -mt-1">
				Run agent sessions in isolated Docker containers with restricted filesystem and network access.
			</p>

			<!-- Sandbox Mode -->
			<div class="flex items-center gap-3">
				<span class="${a}">Sandbox Mode</span>
				<select
					class="${n} max-w-48"
					.value=${l}
					@change=${u=>{s.sandbox=u.target.value,r()}}
				>
					<option value="none">none</option>
					<option value="docker">docker</option>
				</select>
			</div>

			<!-- Docker Status -->
			<div class="flex items-center gap-3">
				<span class="${a}">Docker Status</span>
				<div class="flex items-center gap-2 text-sm">
					${B===null?o`<span class="text-muted-foreground">Checking...</span>`:B.available?o`
								<span class="w-2 h-2 rounded-full bg-green-500"></span>
								<span class="text-foreground">Available${B.dockerVersion?` (v${B.dockerVersion})`:""}</span>
								${B.imageExists!==void 0?B.imageExists?o`<span class="text-xs text-muted-foreground ml-2">Image "${i}": found</span>`:o`<span class="text-xs text-orange-500 ml-2">Image "${i}": not found</span>
											${B.buildCommand?o`
												<div class="flex flex-col gap-1 ml-2">
													<div class="flex items-center gap-2">
														<code class="text-xs bg-secondary px-1.5 py-0.5 rounded font-mono">${B.buildCommand}</code>
														<button
															class="text-xs px-2 py-0.5 rounded border border-border hover:bg-secondary transition-colors disabled:opacity-50"
															?disabled=${ne}
															@click=${async()=>{ne=!0,xe="",r();try{const u=await f("/api/sandbox-image/build",{method:"POST"});let d={};try{d=await u.json()}catch{}u.ok&&d.success?(ne=!1,Be=!1,He()):(ne=!1,xe=d.error||"Build failed",r())}catch(u){ne=!1,xe=u.message||"Build failed",r()}}}
														>${ne?"Building...":"Build Image"}</button>
													</div>
													<span class="text-xs text-muted-foreground">Server restart required after build for sandbox pool.</span>
													${xe?o`<span class="text-xs text-red-500">${xe}</span>`:""}
												</div>
											`:""}`:""}
							`:o`
								<span class="w-2 h-2 rounded-full bg-red-500"></span>
								<span class="text-muted-foreground">Not available${B.error?` — ${B.error}`:""}</span>
							`}
					<button
						class="p-1 rounded-md text-muted-foreground hover:text-foreground hover:bg-secondary transition-colors ml-1"
						title="Refresh Docker status"
						@click=${()=>{Be=!1,He()}}
					>${v(Ue,"xs")}</button>
				</div>
			</div>

			<!-- Image Name -->
			<div class="flex items-center gap-3">
				<span class="${a}">Image Name</span>
				<input
					type="text"
					class="${n} max-w-64"
					placeholder="bobbit-agent"
					.value=${s.sandbox_image??t.sandbox_image?.value??""}
					@input=${u=>{s.sandbox_image=u.target.value}}
				/>
			</div>

			<!-- Tokens -->
			<div class="flex flex-col gap-2">
				<div class="text-[11px] text-muted-foreground uppercase tracking-wider font-medium">Tokens</div>
				<p class="text-xs text-muted-foreground -mt-1">
					Environment variables injected into sandbox containers. Empty values resolve from the host.
				</p>
				${(()=>{if(Bs(),z===null)return o`<span class="text-xs text-muted-foreground">Detecting...</span>`;const u=new Map((z||[]).map(d=>[d.envVar,d.available]));return c.map((d,g)=>{const x=u.get(d.key)===!0;return o`
						<div class="flex items-center gap-2 h-8">
							<span class="w-2 h-2 rounded-full shrink-0 ${x?"bg-green-500":"bg-zinc-400"}" title=${x?"Detected on host":"Not detected on host"}></span>
							<input
								type="checkbox"
								class="accent-primary shrink-0"
								.checked=${d.enabled}
								@change=${y=>{c[g].enabled=y.target.checked,ve(c,s),r()}}
							/>
							<input
								type="text"
								class="w-52 shrink-0 px-2 py-1 rounded-md border border-input bg-background text-sm font-mono
									focus:outline-none focus:ring-2 focus:ring-ring"
								placeholder="ENV_VAR"
								.value=${d.key}
								@input=${y=>{c[g].key=y.target.value,ve(c,s),r()}}
							/>
							${d.redacted&&!d.value?o`
									<div class="flex-1 min-w-0 flex items-center gap-2 px-2">
										<span class="text-sm font-mono text-muted-foreground tracking-widest select-none">••••••••</span>
										<button
											class="text-[11px] px-2 py-0.5 rounded border border-border text-muted-foreground hover:text-foreground hover:bg-secondary transition-colors shrink-0"
											@click=${()=>{c[g].redacted=!1,c[g].value="",ve(c,s),r()}}
										>Change</button>
									</div>
								`:o`<input
								type="text"
								class="flex-1 min-w-0 px-2 py-1 rounded-md border border-input bg-background text-sm font-mono
									focus:outline-none focus:ring-2 focus:ring-ring ${!d.value&&d.isHost?"text-muted-foreground italic":""}"
								placeholder=${d.isHost?"(from host)":"value"}
								.value=${d.value}
								@input=${y=>{c[g].value=y.target.value,ve(c,s),r()}}
							/>`}
							<button
								class="p-1 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0"
								title="Remove"
								@click=${()=>{c.splice(g,1),ve(c,s),r()}}
							>${v(W,"xs")}</button>
						</div>
					`})})()}
				<button
					class="flex items-center gap-1.5 px-2 py-1 text-xs text-muted-foreground hover:text-foreground
						hover:bg-muted rounded-md transition-colors self-start"
					@click=${()=>{c.push({key:"",value:"",enabled:!0,isHost:!1,redacted:!1}),r()}}
				>${v(ke,"xs")} Add token</button>
			</div>

			<!-- Additional Mounts -->
			<div class="flex flex-col gap-2">
				<div class="text-[11px] text-muted-foreground uppercase tracking-wider font-medium">Additional Mounts</div>
				${p.map((u,d)=>o`
					<div class="flex items-center gap-2 h-8">
						<input
							type="text"
							class="flex-1 min-w-0 px-2 py-1 rounded-md border border-input bg-background text-sm font-mono
								focus:outline-none focus:ring-2 focus:ring-ring"
							placeholder="/host/path:/container/path:ro"
							.value=${u}
							@input=${g=>{p[d]=g.target.value;const x=p.filter(Boolean);s.sandbox_mounts=x.length>0?JSON.stringify(x):"",r()}}
						/>
						<button
							class="p-1 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0"
							title="Remove"
							@click=${()=>{p.splice(d,1);const g=p.filter(Boolean);s.sandbox_mounts=g.length>0?JSON.stringify(g):"",r()}}
						>${v(W,"xs")}</button>
					</div>
				`)}
				<button
					class="flex items-center gap-1.5 px-2 py-1 text-xs text-muted-foreground hover:text-foreground
						hover:bg-muted rounded-md transition-colors self-start"
					@click=${()=>{p.push(""),r()}}
				>${v(ke,"xs")} Add mount</button>
			</div>

			</div>
		</div>
	`}function Us(e,t,s,n,a){const l=he.get(e);return o`
		<div class="flex flex-col gap-2">
			<div class="text-[11px] text-muted-foreground uppercase tracking-wider font-medium">Worktree</div>
			<p class="text-xs text-muted-foreground -mt-1">
				Each session and goal gets its own git worktree under a parent directory. Pre-built worktrees make new sessions start instantly.
			</p>

			<div class="flex items-center gap-3">
				<span class="${a}">Worktree Root</span>
				<input
					type="text"
					class="${n}"
					placeholder="<rootPath>-wt/ (default)"
					.value=${s.worktree_root??t.worktree_root?.value??""}
					@input=${i=>{s.worktree_root=i.target.value}}
				/>
			</div>
			<p class="text-[11px] text-muted-foreground -mt-1 ml-[calc(7rem+0.75rem)] sm:ml-[calc(11rem+0.75rem)]">Custom parent directory for goal/session worktrees. Absolute or relative to rootPath.</p>

			<div class="flex items-center gap-3">
				<span class="${a}">Base Ref</span>
				<input
					data-testid="base-ref-input"
					type="text"
					class="${n}"
					placeholder="origin/master (default)"
					.value=${s.base_ref??t.base_ref?.value??""}
					@input=${i=>{s.base_ref=i.target.value,he.has(e)&&(he.delete(e),r())}}
				/>
			</div>
			<p class="text-[11px] text-muted-foreground -mt-1 ml-[calc(7rem+0.75rem)] sm:ml-[calc(11rem+0.75rem)]">
				Branch ref (local or <span class="font-mono">origin/...</span>) that new worktrees are based on and the
				integration target for workflow gates. Empty = project primary. Per-component
				overrides are not supported.
			</p>
			${l?o`
				<div data-testid="base-ref-error" class="ml-[calc(7rem+0.75rem)] sm:ml-[calc(11rem+0.75rem)] -mt-1">
					<p class="text-xs text-destructive">${l.error}</p>
					${l.details&&l.details.length>0?o`
						<ul class="list-disc ml-5 text-xs text-destructive mt-1">
							${l.details.map(i=>o`<li><span class="font-mono">${i.component}</span>: ${i.message}</li>`)}
						</ul>
					`:""}
				</div>
			`:""}

			<div class="flex items-center gap-3 flex-wrap">
				<span class="${a}">Pool Size</span>
				<input
					type="number"
					min="0"
					class="px-2 py-1 rounded-md border border-input bg-background text-sm font-mono text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
					style="width: 5rem;"
					placeholder="2"
					.value=${s.worktree_pool_size??t.worktree_pool_size?.value??""}
					@input=${i=>{s.worktree_pool_size=i.target.value}}
				/>
				<span class="text-xs text-muted-foreground" style="max-width: 18rem;">Pre-built worktrees (0 = disable). Changes take effect on gateway restart.</span>
			</div>

			<div class="flex items-center gap-3">
				<span class="${a}">Pool Status</span>
				<div class="flex items-center gap-2 text-sm">
					${xt(),N===null?o`<span class="text-muted-foreground">Loading...</span>`:N.enabled?o`
							<span class="text-xs font-mono flex items-center gap-3">
								<span>Ready: <span class="text-foreground font-medium">${N.ready}</span></span>
								<span>Target: <span class="text-foreground font-medium">${N.target}</span></span>
								${N.filling?o`<span class="text-orange-500">Filling…</span>`:""}
							</span>
						`:o`<span class="text-muted-foreground">Pool disabled</span>`}
					<button
						class="p-1 rounded-md text-muted-foreground hover:text-foreground hover:bg-secondary transition-colors ml-1"
						title="Refresh pool status"
						@click=${()=>{Xe=!1,N=null,xt()}}
					>${v(Ue,"xs")}</button>
				</div>
			</div>
		</div>
	`}function ge(){F=null,me=null,h=null,U=null,Y=!1}function Fs(){Ht(["settings"],()=>fe("settings"))}function Ze(e){if(e.preventDefault(),e.stopPropagation(),["Control","Meta","Shift","Alt"].includes(e.key))return;if(e.key==="Escape"){ge(),r();return}const t=/Mac|iPhone|iPad/.test(navigator.platform||navigator.userAgent),s={key:e.key,ctrlOrMeta:t?e.metaKey:e.ctrlKey,shift:e.shiftKey,alt:e.altKey};if(F){const a=St().find(l=>l.id===F);if(a&&a.currentBindings.some(i=>st(i,s))){ge(),r();return}}const n=is(s,F??void 0);if(n){h=s,U=n,Y=!1,r();return}if(ls(s)){h=s,U=null,Y=!0,r();return}nt(s)}async function nt(e){F&&(me!==null?os(F,me,e):rs(F,e),ge(),await je(),r())}async function Ws(){if(!U||!h||!F)return;const e=U.currentBindings.findIndex(s=>st(s,h));e>=0&&At(U.id,e);const t=h;h=null,U=null,Y=!1,await nt(t)}async function Gs(){if(!h)return;const e=h;h=null,Y=!1,await nt(e)}async function Ks(e){ns(e),await je(),r()}async function zs(){Qt(),await je(),r()}async function Is(e,t){At(e,t),await je(),r()}function Je(e,t){F=e,me=t,h=null,U=null,Y=!1,r()}function Hs(){const e=F!==null&&!h&&!U&&!Y;e&&!oe?(window.addEventListener("keydown",Ze,!0),oe=!0):!e&&oe&&(window.removeEventListener("keydown",Ze,!0),oe=!1)}function Js(e,t=0){const s=F===e.id,n=s&&U!==null&&h!==null,a=s&&Y&&h!==null,l=e.currentBindings.length!==e.defaultBindings.length||!e.currentBindings.every((i,c)=>st(i,e.defaultBindings[c]));return o`
		<div class="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-secondary/30 transition-colors group ${t%2===0?"bg-secondary/50":""}">
			<span class="flex-1 text-sm text-foreground">${e.label}</span>
			<div class="flex items-center gap-1.5">
				${e.currentBindings.map((i,c)=>{const p=s&&me===c&&!h;return o`
						<span class="inline-flex items-center gap-0">
							<button
								class="inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded-l text-xs font-mono transition-all
									${p?"bg-primary/20 text-primary border border-primary animate-pulse":"bg-secondary text-secondary-foreground hover:bg-secondary/80 border border-transparent"}"
								@click=${()=>Je(e.id,c)}
								title=${p?"Press a key combo...":`Click to rebind (${Me(i)})`}
							>
								${p?"Press a key combo...":Me(i)}
							</button><button
								class="inline-flex items-center px-0.5 py-0.5 rounded-r text-xs text-muted-foreground hover:text-destructive hover:bg-destructive/10 border border-transparent transition-colors "
								@click=${()=>Is(e.id,c)}
								title="Remove binding"
							>${v(W,"xs")}</button>
						</span>
					`})}
				${s&&me===null&&!h?o`<button
							class="inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-xs font-mono bg-primary/20 text-primary border border-primary animate-pulse"
							title="Press a key combo to add a binding"
							@click=${()=>Je(e.id,null)}
						>Press a key combo...</button>`:o`<button
							class="inline-flex items-center p-0.5 rounded text-muted-foreground hover:text-foreground hover:bg-secondary/80 transition-colors "
							@click=${()=>Je(e.id,null)}
							title="Add binding"
						>${v(ke,"xs")}</button>`}
				${l?o`<button
							class="p-0.5 rounded text-muted-foreground hover:text-foreground hover:bg-secondary/80 transition-colors "
							@click=${()=>Ks(e.id)}
							title="Reset to default"
						>${v(Ue,"xs")}</button>`:""}
			</div>
		</div>
		${n?o`
					<div class="mx-2 mb-1 px-3 py-2 rounded-md bg-destructive/10 border border-destructive/20 text-sm">
						<p class="text-destructive mb-2">
							<strong>${Me(h)}</strong> is already bound to
							<strong>${U.label}</strong>.
						</p>
						<div class="flex gap-2">
							${Z({size:"sm",onClick:Ws,children:"Unbind & Assign"})}
							${Z({variant:"ghost",size:"sm",onClick:()=>{ge(),r()},children:"Cancel"})}
						</div>
					</div>
				`:""}
		${a?o`
					<div class="mx-2 mb-1 px-3 py-2 rounded-md bg-yellow-500/10 border border-yellow-500/20 text-sm">
						<p class="text-yellow-600 dark:text-yellow-400 mb-2">
							<strong>${Me(h)}</strong> may be intercepted by the browser.
						</p>
						<div class="flex gap-2">
							${Z({size:"sm",onClick:Gs,children:"Assign Anyway"})}
							${Z({variant:"ghost",size:"sm",onClick:()=>{ge(),r()},children:"Cancel"})}
						</div>
					</div>
				`:""}
	`}function qs(){const e=St(),t=new Map;for(const a of e){const l=t.get(a.category)||[];l.push(a),t.set(a.category,l)}const s=["Sessions","Navigation","Goals","UI"],n=[...t.entries()].sort((a,l)=>{const i=s.indexOf(a[0]),c=s.indexOf(l[0]);return(i===-1?999:i)-(c===-1?999:c)});return o`
		<div class="flex flex-col md:flex-row gap-4 md:gap-6 md:items-start">
			<div class="flex-1 min-w-0 flex flex-col gap-4">
				${n.map(([a,l])=>o`
						<div>
							<div class="text-[10px] text-muted-foreground uppercase tracking-wider font-medium mb-1.5 px-1">
								${a}
							</div>
							<div class="flex flex-col gap-0.5">
								${l.map((i,c)=>Js(i,c))}
							</div>
						</div>
					`)}
				<div class="pt-2 border-t border-border">
					${Z({variant:"ghost",size:"sm",onClick:zs,children:o`${v(Ue,"xs")}<span class="ml-1">Reset All Defaults</span>`})}
				</div>
			</div>
			<div class="w-full md:w-48 md:shrink-0 rounded-md border border-border/60 bg-secondary/30 p-3 text-xs text-muted-foreground leading-relaxed">
				<span class="font-medium text-foreground/80">Tip:</span> When running Bobbit as a browser tab, some shortcut combinations are intercepted by the browser. Install Bobbit as a PWA app to regain complete control.
			</div>
		</div>
	`}const Ot=[{id:"forest",name:"Forest"},{id:"ocean",name:"Ocean"},{id:"dusk",name:"Dusk"},{id:"ember",name:"Ember"},{id:"rose",name:"Rose"},{id:"slate",name:"Slate"},{id:"sand",name:"Sand"},{id:"teal",name:"Teal"},{id:"copper",name:"Copper"},{id:"mono",name:"Mono"}],Ys={forest:{light:"oklch(0.42 0.14 148)",dark:"oklch(0.72 0.12 140)"},ocean:{light:"oklch(0.42 0.14 230)",dark:"oklch(0.72 0.12 230)"},dusk:{light:"oklch(0.42 0.14 300)",dark:"oklch(0.72 0.12 300)"},ember:{light:"oklch(0.42 0.14 65)",dark:"oklch(0.72 0.12 65)"},rose:{light:"oklch(0.42 0.14 10)",dark:"oklch(0.72 0.12 10)"},slate:{light:"oklch(0.38 0.04 260)",dark:"oklch(0.72 0.06 260)"},sand:{light:"oklch(0.42 0.14 85)",dark:"oklch(0.72 0.12 85)"},teal:{light:"oklch(0.42 0.14 195)",dark:"oklch(0.72 0.12 195)"},copper:{light:"oklch(0.42 0.14 50)",dark:"oklch(0.72 0.12 50)"},mono:{light:"oklch(0.38 0 0)",dark:"oklch(0.72 0 0)"}};function vt(e){if(!e||e.startsWith("#"))return e||"#808080";try{const t=document.createElement("canvas");t.width=t.height=1;const s=t.getContext("2d");s.fillStyle=e,s.fillRect(0,0,1,1);const[n,a,l]=s.getImageData(0,0,1,1).data;return`#${n.toString(16).padStart(2,"0")}${a.toString(16).padStart(2,"0")}${l.toString(16).padStart(2,"0")}`}catch{return"#808080"}}function Xs(){return document.documentElement.dataset.palette||"forest"}async function Zs(e){e==="forest"?(delete document.documentElement.dataset.palette,localStorage.removeItem("palette")):(document.documentElement.dataset.palette=e,localStorage.setItem("palette",e));try{await f("/api/preferences",{method:"PUT",body:JSON.stringify({palette:e})})}catch{}r()}function Lt(e){const t=document.documentElement.classList.contains("dark");return o`
		<div
			data-palette=${e.id}
			class=${t?"dark":""}
			style="display:flex; width:100%; height:68px; border-radius:6px; overflow:hidden; border:1px solid var(--border); font-family:system-ui,sans-serif;"
		>
			<!-- Sidebar -->
			<div style="width:44px; background:var(--sidebar); border-right:1px solid var(--sidebar-border); display:flex; flex-direction:column; gap:4px; padding:7px 5px;">
				<div style="display:flex; align-items:center; gap:3px;">
					<div style="width:10px; height:10px; border-radius:50%; background:var(--primary); flex-shrink:0;"></div>
					<div style="height:4px; flex:1; border-radius:2px; background:var(--sidebar-accent);"></div>
				</div>
				<div style="display:flex; align-items:center; gap:3px; opacity:0.7;">
					<div style="width:10px; height:10px; border-radius:50%; background:var(--muted-foreground); flex-shrink:0;"></div>
					<div style="height:4px; flex:1; border-radius:2px; background:var(--sidebar-accent);"></div>
				</div>
				<div style="display:flex; align-items:center; gap:3px; opacity:0.4;">
					<div style="width:10px; height:10px; border-radius:50%; background:var(--muted-foreground); flex-shrink:0;"></div>
					<div style="height:4px; flex:1; border-radius:2px; background:var(--sidebar-accent);"></div>
				</div>
			</div>
			<!-- Chat area -->
			<div style="flex:1; background:var(--background); padding:6px 8px; display:flex; flex-direction:column; gap:4px; justify-content:center;">
				<!-- User message (mirrors .user-message-container) -->
				<div style="display:flex; align-items:center; gap:3px; background:linear-gradient(135deg, var(--user-msg-bg), var(--user-msg-bg2)); border-radius:4px; padding:2px 6px 2px 3px; box-shadow:0 1px 3px var(--user-msg-shadow);">
					<span style="color:var(--user-msg-accent); font-size:7px; font-weight:bold; line-height:1;">❯</span>
					<span style="font-size:7px; color:var(--foreground); line-height:1.3; white-space:nowrap; overflow:hidden;">How do I fix this?</span>
				</div>
				<!-- Assistant response (foreground text) -->
				<div style="padding-left:2px; display:flex; flex-direction:column; gap:2px;">
					<div style="height:4px; width:92%; border-radius:2px; background:var(--muted-foreground); opacity:0.25;"></div>
					<div style="height:4px; width:68%; border-radius:2px; background:var(--muted-foreground); opacity:0.15;"></div>
				</div>
				<!-- Input bar (mirrors real input area) -->
				<div style="display:flex; align-items:center; gap:3px; margin-top:auto;">
					<div style="flex:1; height:9px; border-radius:4px; border:1px solid var(--input); background:var(--background);"></div>
					<div style="width:16px; height:9px; border-radius:4px; background:var(--primary); display:flex; align-items:center; justify-content:center;">
						<span style="color:var(--primary-foreground); font-size:6px; line-height:1;">↑</span>
					</div>
				</div>
			</div>
		</div>
	`}function Qs(){const e=Xs();return o`
		<div class="flex flex-col gap-3">
			<p class="text-sm text-muted-foreground">
				Choose a color palette for the app theme.
			</p>
			<div class="grid gap-2" style="grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));">
				${Ot.map(t=>{const s=e===t.id;return o`
						<button
							class="flex flex-col gap-1.5 px-3 py-2.5 rounded-lg border transition-all cursor-pointer text-left w-full
								${s?"border-primary bg-primary/5 ring-1 ring-primary/30":"border-border hover:border-primary/40 hover:bg-secondary/30"}"
							title="Select ${t.name} palette"
							@click=${()=>Zs(t.id)}
						>
							${Lt(t)}
							<div class="flex items-center gap-1.5">
								<span class="text-sm font-medium ${s?"text-foreground":"text-muted-foreground"}">
									${t.name}
								</span>
								${s?o`<span class="text-xs text-primary">Active</span>`:""}
							</div>
						</button>
					`})}
			</div>
		</div>
	`}let O="",L="idle",k="",ee=!1,Te="",J=[],ot=!0,Ae="",Pe="",Re="",Ce="",Fe="",We="",Ge="",se=[],_e=[],Qe=!1,ce={},$e={};const Vs=3e4;async function ea(e){if(!e||$e[e])return;$e[e]=!0,r();const t=Date.now();try{const s=await f("/api/models/test",{method:"POST",body:JSON.stringify({pref:e})}),n=await s.json().catch(()=>({}));s.ok&&n?.ok?ce[e]={ok:!0,latencyMs:n.latencyMs??Date.now()-t,at:Date.now()}:ce[e]={ok:!1,error:n?.error||`HTTP ${s.status}`,at:Date.now()}}catch(s){ce[e]={ok:!1,error:s?.message||"Request failed",at:Date.now()}}delete $e[e],r()}function ta(e){return!e||se.length===0?!0:se.some(t=>`${t.provider}/${t.id}`===e)}function sa(e){return!e||_e.length===0?!0:_e.some(t=>`${t.provider}/${t.id}`===e)}function aa(){Se.open(J)}function na(){Qe||(Qe=!0,(async()=>{try{const[e,t,s,n]=await Promise.all([f("/api/aigw/status"),f("/api/preferences"),f("/api/models"),f("/api/image-models")]);if(e.ok){const a=await e.json();ee=a.configured,a.configured&&(Te=a.url,O=a.url,J=a.models||[])}if(t.ok){const a=await t.json();Ae=a["default.sessionModel"]||"",Pe=a["default.reviewModel"]||"",Re=a["default.namingModel"]||"",Ce=a["default.imageModel"]||"",Fe=a["default.sessionThinkingLevel"]||"",We=a["default.reviewThinkingLevel"]||"",Ge=a["default.namingThinkingLevel"]||"",ot=a["aigw.exclusive"]!==!1}if(s.ok){const a=await s.json();Array.isArray(a)&&(se=a)}if(n.ok){const a=await n.json();Array.isArray(a)&&(_e=a)}}catch{}r()})())}async function ae(e,t){try{await f("/api/preferences",{method:"PUT",body:JSON.stringify({[e]:t})})}catch{}}function $n(e){e.session!==void 0&&(Ae=e.session),e.review!==void 0&&(Pe=e.review),e.naming!==void 0&&(Re=e.naming),e.image!==void 0&&(Ce=e.image)}async function oa(e){Ae=e,await ae("default.sessionModel",e||null),r()}async function ra(e){Pe=e,await ae("default.reviewModel",e||null),r()}async function ia(e){Re=e,await ae("default.namingModel",e||null),r()}async function la(e){Ce=e,await ae("default.imageModel",e||null),r()}async function da(e){Fe=e,await ae("default.sessionThinkingLevel",e||null),r()}async function ca(e){We=e,await ae("default.reviewThinkingLevel",e||null),r()}async function ua(e){Ge=e,await ae("default.namingThinkingLevel",e||null),r()}async function pa(){if(O.trim()){L="testing",k="",r();try{const e=await f("/api/aigw/test",{method:"POST",body:JSON.stringify({url:O.trim()})}),t=await e.json();e.ok?(J=t.models||[],k=""):k=t.error||`HTTP ${e.status}`}catch(e){k=e.message||"Connection failed"}L="idle",r()}}async function fa(){if(O.trim()){L="saving",k="",r();try{const e=await f("/api/aigw/configure",{method:"POST",body:JSON.stringify({url:O.trim()})}),t=await e.json();e.ok?(ee=!0,Te=O.trim(),J=t.models||[],k=""):k=t.error||`HTTP ${e.status}`}catch(e){k=e.message||"Save failed"}L="idle",r()}}async function ma(e){ot=e;try{await f("/api/preferences",{method:"PUT",body:JSON.stringify({"aigw.exclusive":e?null:!1})})}catch{}try{const t=await f("/api/models");if(t.ok){const s=await t.json();Array.isArray(s)&&(se=s)}}catch{}r()}async function ga(){L="testing",k="",r();try{const e=await f("/api/aigw/refresh",{method:"POST"}),t=await e.json();e.ok?(J=t.models||[],k=""):k=t.error||`HTTP ${e.status}`}catch(e){k=e.message||"Refresh failed"}L="idle",r()}async function xa(){L="removing",k="",r();try{await f("/api/aigw/configure",{method:"DELETE"}),ee=!1,Te="",O="",J=[],k=""}catch(e){k=e.message||"Remove failed"}L="idle",r()}function Bt(e,t="Auto (best available)"){if(!e)return t;const s=e.indexOf("/");return s>0?e.slice(s+1):e}function va(e,t){let s=null;if(e){const n=e.indexOf("/");n>0&&(s={provider:e.slice(0,n),id:e.slice(n+1)})}hs.open(s,n=>{t(`${n.provider}/${n.id}`)})}function ba(e,t){let s=null;if(e){const n=e.indexOf("/");n>0&&(s={provider:e.slice(0,n),id:e.slice(n+1),name:"",api:"openai-images"})}ys.open(s,n=>{t(`${n.provider}/${n.id}`)})}function qe(e,t,s,n,a,l,i="medium",c){const p=Bt(s,c?.fallbackLabel);let u;s&&(u=se.find(C=>`${C.provider}/${C.id}`===s));const d=!!u&&!u.reasoning,g=u?zt(u):["off","minimal","low","medium","high"];let x=a;if(u&&a&&a!==""){const C=It(a,u,{allowEmpty:!0});C!==void 0&&C!==a&&(x=C,queueMicrotask(()=>l(C)))}const y={off:"Off",minimal:"Minimal",low:"Low",medium:"Medium",high:"High",xhigh:"Extra high"},$=ta(s),De=!!s&&se.length>0&&!$,Ee=!!$e[s],ze=s?ce[s]:void 0,w=ze&&Date.now()-ze.at<Vs?ze:void 0;return o`
		<div class="flex flex-col gap-1" data-testid="model-row" data-row-label=${e}>
			<div class="flex items-center gap-2">
				<span class="text-sm font-medium text-foreground shrink-0 w-14">${e}</span>
				<div class="flex items-center gap-1.5 rounded-lg border border-input bg-background px-1 py-1 flex-1 min-w-0">
					<!-- Model picker button -->
					<button
						class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-sm
							hover:bg-secondary transition-colors focus:outline-none focus:ring-2 focus:ring-ring
							flex-1 min-w-0 text-left
							${s?"text-foreground":"text-muted-foreground"}"
						title="Choose model"
						@click=${()=>va(s,n)}
					>
						<span class="text-muted-foreground shrink-0">${v(Pt,"sm")}</span>
						<span class="truncate">${p}</span>
					</button>
					${De?o`
						<span
							class="text-[10px] font-medium uppercase tracking-wider px-1.5 py-0.5 rounded bg-destructive/15 text-destructive shrink-0"
							title="This saved preference does not match any currently available model. It may be a stale ID from a previous gateway configuration."
							data-testid="model-unavailable-badge"
						>Unavailable</span>
					`:""}
					${s?o`
						<button
							class="p-1 rounded-md text-muted-foreground hover:text-foreground hover:bg-secondary transition-colors shrink-0"
							title=${De?"Clear unavailable model":"Reset model to auto"}
							data-testid="model-clear-btn"
							@click=${()=>{delete ce[s],n("")}}
						>${v(W,"xs")}</button>
					`:""}
					<!-- Test button -->
					${s?o`
						<button
							class="p-1 rounded-md shrink-0 transition-colors focus:outline-none focus:ring-2 focus:ring-ring
								${w?.ok?"text-green-600 hover:bg-green-500/10":""}
								${w&&!w.ok?"text-destructive hover:bg-destructive/10":""}
								${w?"":"text-muted-foreground hover:text-foreground hover:bg-secondary"}"
							title=${Ee?"Testing…":w?.ok?`OK (${w.latencyMs??0}ms)`:w?.error?`Failed: ${w.error}`:"Send a 'Reply with OK' test prompt to this model"}
							data-testid="model-test-btn"
							?disabled=${Ee||De}
							@click=${()=>ea(s)}
						>
							${Ee?o`<span class="inline-flex animate-spin">${v($s,"xs")}</span>`:w?.ok?v(ws,"xs"):w&&!w.ok?v(W,"xs"):v(ks,"xs")}
						</button>
					`:""}
					<!-- Divider -->
					<div class="w-px h-5 bg-border shrink-0"></div>
					<!-- Thinking picker -->
					<div class="shrink-0 ${d?"opacity-40 pointer-events-none":""}"
						title=${d?"Selected model does not support thinking":"Thinking level"}
					>
						${fs({value:x||(c?.fallbackLabel?"":i),options:[...c?.fallbackLabel?[{value:"",label:c.fallbackLabel,icon:v(ct,"sm")}]:[],...g.map(C=>({value:C,label:y[C],icon:v(ct,"sm")}))],onChange:C=>{l(C)},size:"sm",variant:"ghost",fitContent:!0})}
					</div>
				</div>
			</div>
			${w&&!Ee?o`
				<p class="text-xs ${w.ok?"text-green-600":"text-destructive"}" data-testid="model-test-result">
					${w.ok?`Test OK${w.latencyMs!=null?` (${w.latencyMs}ms)`:""}`:`Test failed: ${w.error}`}
				</p>
			`:""}
			${De?o`
				<p class="text-xs text-destructive">
					This model is not in the current available-models list. Clear it or pick another.
				</p>
			`:""}
			<p class="text-xs text-muted-foreground">${t}</p>
		</div>
	`}function ha(e,t,s,n){const a=s?Bt(s):"Auto (GPT Image 2)",l=sa(s),i=!!s&&_e.length>0&&!l;return o`
		<div class="flex flex-col gap-1" data-testid="image-model-row" data-row-label=${e}>
			<div class="flex items-center gap-2">
				<span class="text-sm font-medium text-foreground shrink-0 w-14">${e}</span>
				<div class="flex items-center gap-1.5 rounded-lg border border-input bg-background px-1 py-1 flex-1 min-w-0">
					<button
						class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-sm
							hover:bg-secondary transition-colors focus:outline-none focus:ring-2 focus:ring-ring
							flex-1 min-w-0 text-left
							${s?"text-foreground":"text-muted-foreground"}"
						title="Choose image generation model"
						@click=${()=>ba(s,n)}
					>
						<span class="text-muted-foreground shrink-0">${v(_s,"sm")}</span>
						<span class="truncate">${a}</span>
					</button>
					${i?o`
						<span
							class="text-[10px] font-medium uppercase tracking-wider px-1.5 py-0.5 rounded bg-destructive/15 text-destructive shrink-0"
							title="This saved preference does not match any currently available image model."
							data-testid="image-model-unavailable-badge"
						>Unavailable</span>
					`:""}
					${s?o`
						<button
							class="p-1 rounded-md text-muted-foreground hover:text-foreground hover:bg-secondary transition-colors shrink-0"
							title=${i?"Clear unavailable model":"Reset image model to auto"}
							data-testid="image-model-clear-btn"
							@click=${()=>n("")}
						>${v(W,"xs")}</button>
					`:""}
				</div>
			</div>
			${i?o`
				<p class="text-xs text-destructive">
					This image model is not in the current available-models list. Clear it or pick another.
				</p>
			`:""}
			<p class="text-xs text-muted-foreground">${t}</p>
		</div>
	`}function wn(e={}){Qe=!0,ee=e.aigwConfigured??!1,Te=e.aigwUrl??"",O=e.aigwUrl??"",J=e.aigwModels??[],se=e.allModels??[],_e=e.allImageModels??[],Ae=e.prefSessionModel??"",Pe=e.prefReviewModel??"",Re=e.prefNamingModel??"",Ce=e.prefImageModel??"",Fe="",We="",Ge="",ce={},$e={}}function ya(){na();const e=L!=="idle",t=J.length>0;return o`
		<div class="flex flex-col gap-6" data-testid="models-tab">

			<!-- AI Gateway section -->
			<div class="flex flex-col gap-4" data-testid="aigw-section">
				<h3 class="text-sm font-semibold text-foreground">AI Gateway</h3>
				<p class="text-sm text-muted-foreground">
					Connect to an AI Gateway for on-prem LLM access through a single
					OpenAI-compatible endpoint. When configured, only gateway models are shown.
				</p>

				<!-- URL input -->
				<div class="flex flex-col gap-2">
					<label class="text-sm font-medium text-foreground">Gateway URL</label>
					<div class="flex gap-2">
						<input
							type="text"
							class="flex-1 px-3 py-2 rounded-md border border-input bg-background text-foreground text-sm
								focus:outline-none focus:ring-2 focus:ring-ring"
							placeholder="http://gateway-host/v1"
							.value=${O}
							?disabled=${e}
							@input=${s=>{O=s.target.value}}
						/>
						<button
							class="px-3 py-2 text-sm rounded-md border border-input bg-background text-foreground
								hover:bg-secondary transition-colors disabled:opacity-50"
							title="Test gateway connection"
							?disabled=${e||!O.trim()}
							@click=${pa}
						>${L==="testing"?"Testing...":"Test"}</button>
					</div>
				</div>

				<!-- Error -->
				${k?o`
					<div class="text-sm text-destructive bg-destructive/10 px-3 py-2 rounded-md">
						${k}
					</div>
				`:""}

				<!-- Status badge -->
				${ee?o`
					<div class="flex items-center gap-2 px-3 py-2 rounded-md bg-green-500/10 border border-green-500/20">
						<span class="w-2 h-2 rounded-full bg-green-500"></span>
						<span class="text-sm text-foreground">Connected to <code class="text-xs">${Te}</code></span>
					</div>
					<label class="flex items-start gap-2 text-sm text-foreground cursor-pointer">
						<input
							type="checkbox"
							class="mt-0.5"
							.checked=${ot}
							?disabled=${e}
							@change=${s=>ma(s.target.checked)}
						/>
						<span class="flex flex-col">
							<span>Hide built-in providers while the gateway is configured</span>
							<span class="text-xs text-muted-foreground">
								When enabled (default), the model picker only shows gateway models
								and local custom providers. Turn this off for local dev when you
								want direct API access alongside a dev gateway.
							</span>
						</span>
					</label>
				`:""}

				<!-- Action buttons -->
				<div class="flex gap-2">
					<button
						class="px-4 py-2 text-sm rounded-md bg-primary text-primary-foreground
							hover:bg-primary/90 transition-colors disabled:opacity-50"
						title="Save gateway configuration"
						?disabled=${e||!O.trim()}
						@click=${fa}
					>${L==="saving"?"Saving...":ee?"Update":"Enable Gateway"}</button>
					${ee?o`
						<button
							class="px-4 py-2 text-sm rounded-md border border-destructive text-destructive
								hover:bg-destructive/10 transition-colors disabled:opacity-50"
							title="Disconnect gateway"
							?disabled=${e}
							@click=${xa}
						>${L==="removing"?"Removing...":"Disconnect"}</button>
						<button
							class="px-4 py-2 text-sm rounded-md border border-input bg-background text-foreground
								hover:bg-secondary transition-colors disabled:opacity-50"
							title="Refresh available models"
							?disabled=${e}
							@click=${ga}
						>Refresh Models</button>
					`:""}
				</div>

			</div>

			<!-- Default model preferences -->
			<div class="flex flex-col gap-4 pt-4 border-t border-border" data-testid="defaults-section">
				<h3 class="text-sm font-semibold text-foreground">Default Models</h3>
				${qe("Session","Model and thinking level for new sessions.",Ae,oa,Fe,da)}
				${qe("Review","Model and thinking for automated gate verification reviews.",Pe,ra,We,ca,"off")}
				${qe("Naming","Lightweight model for auto-generating session titles. If unset under an AI Gateway, Bobbit picks the cheapest Claude model automatically.",Re,ia,Ge,ua,"off")}
				${ha("Image","Image generation model used by the generate_image tool and as the default for new sessions.",Ce,la)}
				${t?o`
					<div>
						<button
							class="text-xs text-muted-foreground hover:text-foreground underline underline-offset-2"
							data-testid="view-aigw-models-btn"
							@click=${aa}
						>View available models… (${J.length})</button>
					</div>
				`:""}
			</div>
		</div>
	`}const $a={build_command:"Build",test_command:"Test",typecheck_command:"Type Check",test_unit_command:"Test (Unit)",test_e2e_command:"Test (E2E)",worktree_setup_command:"Worktree Setup",skill_directories:"Skill Dirs"};function wa(e){return $a[e]||e}function ka(){ut||(ut=!0,(async()=>{try{const e=await f("/api/preferences");if(e.ok){const t=await e.json();be=!!t.showTimestamps,re=t.playAgentFinishSound!==!1;const s=t.skillsCatalogBudget;de=typeof s=="number"&&Number.isFinite(s)?s:null,r()}}catch{}})())}async function Sa(){be=!be,r();try{await f("/api/preferences",{method:"PUT",body:JSON.stringify({showTimestamps:be})})}catch{}}async function _a(){ie="Working…",r();try{const e=await f("/api/system-prompt/customise",{method:"POST"});if(!e.ok)ie=`Failed (${e.status})`;else{const t=await e.json();ie=t.created?`Created ${t.path}`:`Already exists at ${t.path}`}}catch(e){ie=`Error: ${e.message}`}r()}async function Ta(e){if(!Number.isFinite(e))return;let t=Math.floor(e*1024);t<pt&&(t=pt),t>ft&&(t=ft),de=t,r();try{await f("/api/preferences",{method:"PUT",body:JSON.stringify({skillsCatalogBudget:t})})}catch{}}async function Aa(){de=null,r();try{await f("/api/preferences",{method:"PUT",body:JSON.stringify({skillsCatalogBudget:null})})}catch{}}async function Pa(){re=!re,document.documentElement.dataset.playAgentFinishSound=re?"true":"false",r();try{await f("/api/preferences",{method:"PUT",body:JSON.stringify({playAgentFinishSound:re})})}catch{}}function Ra(e){const t=Math.max(0,Math.min(Le.length-1,Math.round(e))),s=Le[t].value;try{localStorage.setItem(_t,String(s))}catch{}Tt(s),r()}function Ca(){try{localStorage.setItem(_t,String(dt))}catch{}Tt(dt),r()}function Da(){const e=Xt(),t=Zt(e),s=Le.findIndex(n=>n.id===t.id);return o`
		<div class="flex flex-col gap-1.5">
			<span class="text-sm font-medium text-foreground">Sidebar font size</span>
			<p class="text-xs text-muted-foreground">
				Scale all sidebar text proportionally. Affects only the sidebar — chat, header, and other surfaces are unchanged. Saved per browser.
			</p>
			<div class="flex items-center gap-3">
				<input
					type="range"
					min="0"
					max="${Le.length-1}"
					step="1"
					.value=${String(s)}
					data-testid="sidebar-font-scale-slider"
					class="flex-1 max-w-xs accent-primary cursor-pointer"
					@input=${n=>Ra(Number(n.target.value))}
				/>
				<span class="text-sm text-foreground tabular-nums min-w-16" data-testid="sidebar-font-scale-label">${t.label}</span>
				<button
					class="text-xs text-muted-foreground hover:text-foreground underline"
					data-testid="sidebar-font-scale-reset"
					@click=${Ca}
				>Reset to Default</button>
			</div>
		</div>
	`}function Ea(){return ka(),o`
		<div class="flex flex-col gap-4">
			<div class="flex flex-col gap-2">
				<h2 class="text-sm font-semibold text-foreground uppercase tracking-wider" data-testid="general-appearance-heading">Appearance</h2>
				${Da()}
			</div>
			<div class="flex flex-col gap-1.5">
				<label class="flex items-center gap-2 cursor-pointer">
					<input
						type="checkbox"
						class="w-4 h-4 rounded border-input accent-primary cursor-pointer"
						.checked=${be}
						@change=${Sa}
					/>
					<span class="text-sm font-medium text-foreground">Show message timestamps</span>
				</label>
				<p class="text-xs text-muted-foreground ml-6">
					Display timestamps next to user and assistant messages.
				</p>
			</div>
			<div class="flex flex-col gap-1.5">
				<label class="flex items-center gap-2 cursor-pointer">
					<input
						type="checkbox"
						class="w-4 h-4 rounded border-input accent-primary cursor-pointer"
						data-testid="general-play-finish-sound"
						.checked=${re}
						@change=${Pa}
					/>
					<span class="text-sm font-medium text-foreground">Play sound when an agent finishes</span>
				</label>
				<p class="text-xs text-muted-foreground ml-6">
					Play a short notification beep when an agent finishes its turn.
				</p>
			</div>
			<div class="flex flex-col gap-1.5">
				<span class="text-sm font-medium text-foreground">Skills catalog budget</span>
				<p class="text-xs text-muted-foreground">
					Maximum size (KB) of the "Available Skills" catalog injected into every agent system prompt.
					Larger budgets let agents see more skills before the alphabetical tail is truncated. Range: 1–128 KB.
				</p>
				<div class="flex items-center gap-3">
					<input
						type="number"
						min="1"
						max="128"
						step="1"
						data-testid="general-skills-catalog-budget"
						class="w-24 px-2 py-1 rounded border border-input bg-background text-sm"
						.value=${String(Math.round((de??Os)/1024))}
						@change=${e=>Ta(Number(e.target.value))}
					/>
					<span class="text-xs text-muted-foreground">KB${de===null?" (default)":""}</span>
					<button
						class="text-xs text-muted-foreground hover:text-foreground underline"
						data-testid="general-skills-catalog-budget-reset"
						?disabled=${de===null}
						@click=${Aa}
					>Reset to default</button>
				</div>
			</div>
			<div class="flex flex-col gap-1.5">
				<span class="text-sm font-medium text-foreground">System prompt</span>
				<p class="text-xs text-muted-foreground">
					Copy the shipped default to <code>.bobbit/config/system-prompt.md</code> so you can edit it.
					If the file already exists it is left unchanged.
				</p>
				<div class="flex items-center gap-3">
					<button
						class="px-3 py-1.5 rounded border border-input text-sm hover:bg-secondary"
						data-testid="general-customise-system-prompt"
						@click=${_a}
					>Customise system prompt</button>
					${ie?o`<span class="text-xs text-muted-foreground">${ie}</span>`:""}
				</div>
			</div>
		</div>
	`}let Q=[],ue=!1,V=!1,K="",M="",bt="",le="",b={skills:!1,mcp:!1,tools:!1,agents:!1};function rt(){const e=te();e!==bt&&(ue=!1,V=!1,K="",bt=e),!(ue||V||K)&&(V=!0,K="",(async()=>{try{const t=new URLSearchParams,s=te();s&&s!=="system"&&t.set("projectId",s);const n=await f(`/api/config-directories${t.toString()?"?"+t.toString():""}`);n.ok?(Q=await n.json(),ue=!0):K="Failed to load directory configuration"}catch{K="Failed to load directory configuration"}V=!1,r()})())}function Ma(){ue=!1,V=!1,K="",rt()}async function Oa(e){const t=Q.filter(s=>s.isRemovable&&s.path!==e).map(s=>({path:s.path,types:s.types}));await Nt(t)}async function ht(){const e=le.trim();if(!e)return;const t=[];if(b.skills&&t.push("skills"),b.mcp&&t.push("mcp"),b.tools&&t.push("tools"),b.agents&&t.push("agents"),t.length===0)return;const s=Q.filter(n=>n.isRemovable).map(n=>({path:n.path,types:n.types}));s.push({path:e,types:t}),await Nt(s),M!=="error"&&(le="",b={skills:!1,mcp:!1,tools:!1,agents:!1})}async function Nt(e){M="saving",r();try{const t=te(),s=t&&t!=="system"?`/api/projects/${t}/config`:"/api/project-config";(await f(s,{method:"PUT",body:JSON.stringify({config_directories:e,skill_directories:null})})).ok?(M="saved",r(),setTimeout(()=>{ue=!1,V=!1,K="",rt(),M="",r()},1500)):(M="error",setTimeout(()=>{M="",r()},3e3))}catch{M="error",setTimeout(()=>{M="",r()},3e3)}r()}function La(e){const s={"built-in":"bg-green-500/15 text-green-700 dark:text-green-400",user:"bg-purple-500/15 text-purple-700 dark:text-purple-400",project:"bg-blue-500/15 text-blue-700 dark:text-blue-400",custom:"bg-teal-500/15 text-teal-700 dark:text-teal-400"}[e]||"bg-muted text-muted-foreground";return o`<span class="inline-flex items-center px-1.5 py-0.5 text-[10px] font-medium rounded-full ${s}">${e}</span>`}function Ba(e){return o`<span class="inline-block w-2 h-2 rounded-full ${e?"bg-green-500":"bg-red-500"}" title="${e?"Directory exists":"Directory not found"}"></span>`}function Oe(e){return o`
		<div class="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-secondary/30 transition-colors">
			${Ba(e.exists)}
			<code class="flex-1 text-xs break-all min-w-0">${e.path}</code>
			${La(e.scope)}
			${e.types.map(t=>o`<span class="text-[10px] px-1 py-0.5 rounded bg-secondary text-secondary-foreground">${t}</span>`)}
			${e.isRemovable?o`
				<button
					class="p-1 rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors shrink-0"
					title="Remove directory"
					?disabled=${M==="saving"}
					@click=${()=>Oa(e.path)}
				>${v(W,"xs")}</button>
			`:o`<div class="w-6 shrink-0"></div>`}
		</div>
	`}function jt(){if(rt(),V&&!ue)return o`<div class="text-sm text-muted-foreground">Loading directory configuration…</div>`;if(K)return o`
			<div class="flex flex-col items-center justify-center py-12 gap-3">
				<p class="text-sm text-destructive">${K}</p>
				<button
					class="text-xs text-muted-foreground hover:text-foreground underline"
					@click=${Ma}
				>Retry</button>
			</div>
		`;const e=Q.filter(p=>p.types.includes("skills")),t=Q.filter(p=>p.types.includes("mcp")),s=Q.filter(p=>p.types.includes("tools")),n=Q.filter(p=>p.types.includes("agents")),a=b.skills||b.mcp||b.tools||b.agents,l=b.agents&&!b.skills&&!b.mcp&&!b.tools,i=b.agents&&(b.skills||b.mcp||b.tools),c=l?"~/path/to/AGENTS.md":i?"~/path/to/dir or file":"~/my-config-dir";return o`
		<div class="flex flex-col gap-5">
			<p class="text-sm text-muted-foreground">
				Locations Bobbit scans for configuration. Custom entries can be added or removed.
			</p>

			<!-- Skills -->
			<div class="flex flex-col gap-1">
				<div class="text-[11px] text-muted-foreground uppercase tracking-wider font-medium px-1">Skills</div>
				<div class="text-xs text-muted-foreground px-1 -mt-0.5 mb-0.5">Directories containing SKILL.md files — slash commands available in chat.</div>
				<div class="flex flex-col gap-0.5">
					${e.length>0?e.map(Oe):o`<div class="text-xs text-muted-foreground italic px-2">No skills directories.</div>`}
				</div>
			</div>

			<!-- MCP -->
			<div class="flex flex-col gap-1">
				<div class="text-[11px] text-muted-foreground uppercase tracking-wider font-medium px-1">MCP</div>
				<div class="text-xs text-muted-foreground px-1 -mt-0.5 mb-0.5">Config files defining MCP servers whose tools appear in Bobbit.</div>
				<div class="flex flex-col gap-0.5">
					${t.length>0?t.map(Oe):o`<div class="text-xs text-muted-foreground italic px-2">No MCP directories.</div>`}
				</div>
			</div>

			<!-- Tools -->
			<div class="flex flex-col gap-1">
				<div class="text-[11px] text-muted-foreground uppercase tracking-wider font-medium px-1">Tools</div>
				<div class="text-xs text-muted-foreground px-1 -mt-0.5 mb-0.5">Directories containing tool YAML definitions and extension code.</div>
				<div class="flex flex-col gap-0.5">
					${s.length>0?s.map(Oe):o`<div class="text-xs text-muted-foreground italic px-2">No tools directories.</div>`}
				</div>
			</div>

			<!-- Agents -->
			<div class="flex flex-col gap-1">
				<div class="text-[11px] text-muted-foreground uppercase tracking-wider font-medium px-1">Agents</div>
				<div class="text-xs text-muted-foreground px-1 -mt-0.5 mb-0.5">Markdown files (e.g. AGENTS.md) concatenated into the system prompt for every session. These are file paths, not directories.</div>
				<div class="flex flex-col gap-0.5">
					${n.length>0?n.map(Oe):o`<div class="text-xs text-muted-foreground italic px-2">No agent files.</div>`}
				</div>
			</div>

			<!-- Add directory form -->
			<div class="flex flex-col gap-2 pt-3 border-t border-border">
				<div class="text-[11px] text-muted-foreground uppercase tracking-wider font-medium">Add Custom Path</div>
				<div class="flex items-center gap-2">
					<input
						type="text"
						class="flex-1 px-3 py-1.5 rounded-md border border-input bg-background text-sm font-mono
							focus:outline-none focus:ring-2 focus:ring-ring"
						placeholder="${c}"
						.value=${le}
						@input=${p=>{le=p.target.value,r()}}
						@keydown=${p=>{p.key==="Enter"&&le.trim()&&a&&ht()}}
					/>
				</div>
				<div class="flex items-center gap-4">
					<label class="flex items-center gap-1.5 text-xs cursor-pointer">
						<input type="checkbox" class="accent-primary" .checked=${b.skills}
							@change=${p=>{b.skills=p.target.checked,r()}} />
						Skills
					</label>
					<label class="flex items-center gap-1.5 text-xs cursor-pointer">
						<input type="checkbox" class="accent-primary" .checked=${b.mcp}
							@change=${p=>{b.mcp=p.target.checked,r()}} />
						MCP
					</label>
					<label class="flex items-center gap-1.5 text-xs cursor-pointer">
						<input type="checkbox" class="accent-primary" .checked=${b.tools}
							@change=${p=>{b.tools=p.target.checked,r()}} />
						Tools
					</label>
					<label class="flex items-center gap-1.5 text-xs cursor-pointer">
						<input type="checkbox" class="accent-primary" .checked=${b.agents}
							@change=${p=>{b.agents=p.target.checked,r()}} />
						Agents
					</label>
					<button
						class="ml-auto px-3 py-1.5 text-sm rounded-md bg-primary text-primary-foreground
							hover:bg-primary/90 transition-colors disabled:opacity-50"
						?disabled=${!le.trim()||!a||M==="saving"}
						@click=${ht}
					>Add</button>
				</div>
				${M==="saved"?o`<span class="text-xs text-green-600">Saved successfully.</span>`:""}
				${M==="error"?o`<span class="text-xs text-destructive">Failed to save.</span>`:""}
			</div>
		</div>
	`}const Ve=[{id:"anthropic",title:"Anthropic OAuth",description:"OAuth credentials used by agent sessions to access the Anthropic API. Re-authenticate to refresh expired tokens or switch accounts.",authenticatedLabel:"Authenticated"},{id:"openai-codex",title:"OpenAI OAuth",description:"OAuth credentials used by agent sessions to access ChatGPT subscription GPT models through the OpenAI Codex provider.",authenticatedLabel:"Authenticated"}];let pe=null,we=!1,Ne=null;function Ut(){we||(we=!0,(async()=>{try{const e=await Promise.all(Ve.map(async t=>{try{const s=await f(`/api/oauth/status?provider=${encodeURIComponent(t.id)}`);return[t.id,s.ok?await s.json():{authenticated:!1}]}catch{return[t.id,{authenticated:!1}]}}));pe=Object.fromEntries(e)}catch{pe=Object.fromEntries(Ve.map(e=>[e.id,{authenticated:!1}]))}finally{we=!1,r()}})())}async function Na(e){Ne=e,r();try{await bs(e)&&(pe=null,Ut())}finally{Ne=null,r()}}function ja(){return!pe&&!we&&Ut(),we&&!pe?o`<p class="text-sm text-muted-foreground">Loading...</p>`:o`
		<div class="flex flex-col gap-4">
			${Ve.map(e=>{const t=pe?.[e.id],s=t?.authenticated??!1,n=t?.expires,a=n?new Date(n):null,l=n?Date.now()>n:!1,i=Ne===e.id;return o`
					<div class="flex flex-col gap-4">
						<div class="flex flex-col gap-1.5">
							<h3 class="text-sm font-semibold text-foreground">${e.title}</h3>
							<p class="text-xs text-muted-foreground">${e.description}</p>
						</div>

						<div class="flex flex-col gap-2 rounded-md border border-border p-3">
							<div class="flex items-center gap-2">
								<span class="text-sm font-medium text-foreground">Status:</span>
								${s?o`<span class="text-sm text-green-600 dark:text-green-400">${e.authenticatedLabel}</span>`:o`<span class="text-sm text-destructive">${l?"Expired":"Not authenticated"}</span>`}
							</div>
							${a?o`<div class="flex items-center gap-2">
									<span class="text-sm font-medium text-foreground">Expires:</span>
									<span class="text-sm ${l?"text-destructive":"text-muted-foreground"}">${a.toLocaleString()}</span>
								</div>`:""}
						</div>

						<div>
							${Z({variant:s?"outline":"default",size:"sm",disabled:Ne!==null,onClick:()=>Na(e.id),children:i?"Authenticating...":s?"Re-authenticate":"Log in"})}
						</div>
					</div>
				`})}
		</div>
	`}function Ua(e,t){const s=G.projects||[];return s.length===0?"":o`
		<div class="shrink-0 flex items-center gap-1 px-4 py-2 border-b border-border overflow-x-auto" style="scrollbar-width:thin;">
			<button
				class="px-3 py-1.5 text-sm rounded-md transition-colors whitespace-nowrap shrink-0
					${e==="system"?"bg-background text-foreground shadow-sm border border-border":"text-muted-foreground hover:text-foreground hover:bg-secondary/50"}"
				@click=${()=>{fe("settings",`system/${Ct[0].id}`,!0)}}
			>System</button>
			${s.map(n=>{const a=e===n.id,i=document.documentElement.classList.contains("dark")?n.colorDark||n.color||"var(--muted-foreground)":n.colorLight||n.color||"var(--muted-foreground)";return o`
					<button
						class="px-3 py-1.5 text-sm rounded-md transition-colors whitespace-nowrap shrink-0 flex items-center gap-1.5
							${a?"bg-background text-foreground shadow-sm border border-border":"text-muted-foreground hover:text-foreground hover:bg-secondary/50"}"
						@click=${()=>{fe("settings",`${n.id}/${Dt[0].id}`,!0)}}
					>
						<span class="inline-block w-2 h-2 rounded-full shrink-0" style="background:${i};"></span>
						${n.name}
					</button>
				`})}
		</div>
	`}function Fa(e){Mt(e);const t=H.get(e);if(!t?.loaded)return o`<div class="text-sm text-muted-foreground">Loading project configuration…</div>`;const s=t.resolved,n=t.raw,a=new Set(["sandbox","sandbox_image","sandbox_tokens","sandbox_credentials","sandbox_github_token","sandbox_host_token_overrides","sandbox_mounts","worktree_pool_size","config_directories","skill_directories","build_command","test_command","typecheck_command","test_unit_command","test_e2e_command","worktree_setup_command","qa_start_command","qa_build_command","qa_health_check","qa_browser_entry","qa_env","qa_max_duration_minutes","qa_max_scenarios"]),l="text-sm font-medium text-foreground w-28 sm:w-44 shrink-0",i=`w-full min-w-0 px-3 py-1.5 rounded-md border border-input bg-background text-sm
		font-mono focus:outline-none focus:ring-2 focus:ring-ring`;q.has(e)||q.set(e,{});const c=q.get(e),p=Array.from(new Set([...Object.keys(s),...Object.keys(c).filter(u=>!u.startsWith("_"))])).filter(u=>!a.has(u)).sort();return o`
		<div class="flex flex-col gap-4">
			${p.length>0?o`<div class="flex flex-col gap-2">
				<div class="text-[11px] text-muted-foreground uppercase tracking-wider font-medium">Other Commands</div>
				<div class="text-xs text-muted-foreground">Build, test, and lint commands now live on each component — see the <strong>Components</strong> tab.</div>
				${p.map(u=>{const d=s[u],g=d?d.source!=="project":!1,x=c[u]??n[u]??"";return o`
						<div class="flex items-center gap-3">
							<span class="${l}">${wa(u)}</span>
							<div class="flex-1 min-w-0 relative">
								<input
									type="text"
									class="${i} ${g?"text-muted-foreground":"text-foreground"}"
									placeholder=${g?d.value:""}
									.value=${x}
									@input=${y=>{c[u]=y.target.value}}
								/>
								${g?o`<span class="absolute right-2 top-1/2 -translate-y-1/2 text-[10px] text-muted-foreground/60 pointer-events-none">(inherited)</span>`:""}
							</div>
							${d&&!g?o`
								<button
									class="p-1 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0"
									title="Reset to inherited value"
									@click=${()=>Ls(e,u)}
								>${v(W,"xs")}</button>
							`:d?o`<div class="w-7 shrink-0"></div>`:o`
								<button
									class="p-1 rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors shrink-0"
									title="Discard pending key"
									@click=${()=>{delete c[u],r()}}
								>${v(W,"xs")}</button>
							`}
						</div>
					`})}
			</div>`:""}



			<!-- Custom keys: lets users add arbitrary project.yaml fields without -->
			<!-- editing the file by hand. Keeps the legacy KV editing surface alive. -->
			${(()=>{Ie.has(e)||Ie.set(e,{key:"",value:""});const u=Ie.get(e),d=u.key.trim(),g=/^[a-z][a-z0-9_]*$/i.test(d),x=d in s||d in c,y=g&&!x;return o`
						<details data-testid="custom-key-composer" class="border-t border-border pt-3">
							<summary class="text-xs text-muted-foreground cursor-pointer select-none font-medium">Add custom key</summary>
							<p class="text-[11px] text-muted-foreground mt-2 mb-3">Add an arbitrary <code class="font-mono">project.yaml</code> field. The new row will appear in the section above; click <strong>Save</strong> to persist.</p>
							<div class="flex items-center gap-3">
								<input
									type="text"
									class="${i} text-foreground"
									style="width: 11rem; flex: 0 0 auto;"
									placeholder="key_name"
									data-testid="custom-key-name"
									.value=${u.key}
									@input=${$=>{u.key=$.target.value,r()}}
								/>
								<div class="flex-1 min-w-0">
									<input
										type="text"
										class="${i} text-foreground"
										placeholder="value"
										data-testid="custom-key-value"
										.value=${u.value}
										@input=${$=>{u.value=$.target.value,r()}}
										@keydown=${$=>{$.key==="Enter"&&y&&(c[d]=u.value,u.key="",u.value="",r())}}
									/>
								</div>
								<button
									class="p-1 rounded-md text-muted-foreground hover:text-primary hover:bg-primary/10 transition-colors shrink-0 disabled:opacity-30 disabled:pointer-events-none"
									data-testid="custom-key-add"
									title="Add field"
									?disabled=${!y}
									@click=${()=>{c[d]=u.value,u.key="",u.value="",r()}}
								>${v(ke,"sm")}</button>
							</div>
							${d&&!g?o`<p class="text-[11px] text-destructive mt-2">Key must start with a letter and contain only letters, digits, and underscores.</p>`:""}
							${g&&x?o`<p class="text-[11px] text-amber-600 mt-2">A field named <code class="font-mono">${d}</code> already exists. Edit it above.</p>`:""}
						</details>
					`})()}

			<!-- Save -->
			<div class="flex items-center gap-3 pt-2 border-t border-border">
				<button
					class="px-4 py-2 text-sm rounded-md bg-primary text-primary-foreground
						hover:bg-primary/90 transition-colors disabled:opacity-50"
					?disabled=${R==="saving"}
					@click=${()=>{Object.keys(c).length>0&&at(e,c).then(()=>{q.delete(e),H.delete(e),r()})}}
				>${R==="saving"?"Saving...":"Save"}</button>
				${R==="saved"?o`<span class="text-xs text-green-600">Saved successfully.</span>`:""}
				${R==="error"?o`<span class="text-xs text-destructive">Failed to save.</span>`:""}
			</div>

		</div>
	`}function Wa(e){const t=(G.projects||[]).find(p=>p.id===e);q.has(e)||q.set(e,{});const s=q.get(e),n="flex-1 min-w-0 px-2 py-1 rounded-md border border-input bg-background text-sm font-mono text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring",a="text-sm font-medium text-foreground w-28 sm:w-44 shrink-0";Mt(e);const l=H.get(e),i=l?.loaded?l.resolved:{},c=Object.keys(s).length>0;return o`
		<div class="flex flex-col gap-6">
			<div class="flex flex-col gap-1">
				<h3 class="text-sm font-medium text-foreground">${t?.name||"Project"}</h3>
			</div>

			<!-- Working Directory -->
			<div class="flex flex-col gap-2">
				<div class="text-[11px] text-muted-foreground uppercase tracking-wider font-medium">Working Directory</div>
				<div class="flex items-center gap-3">
					<input
						type="text"
						class="${n} text-foreground"
						.value=${s._rootPath??t?.rootPath??""}
						@input=${p=>{s._rootPath=p.target.value}}
					/>
				</div>
				<p class="text-xs text-muted-foreground">
					The directory used when creating new sessions and goals for this project.
				</p>
			</div>

			<hr class="border-border" />

			<!-- Worktree (root override + pre-built pool) -->
			${Us(e,i,s,n,a)}

			<hr class="border-border" />

			<!-- Docker Sandbox -->
			${js(e,i,s,n,a)}

			<!-- Save -->
			${c?o`
				<div class="flex items-center gap-3 pt-2 border-t border-border">
					<button
						class="px-4 py-2 text-sm rounded-md bg-primary text-primary-foreground
							hover:bg-primary/90 transition-colors disabled:opacity-50"
						?disabled=${R==="saving"}
						@click=${()=>{c&&at(e,s).then(()=>{q.delete(e),ye.delete(e),X.delete(e),H.delete(e),r()})}}
					>${R==="saving"?"Saving...":"Save"}</button>
					${R==="saved"?o`<span class="text-xs text-green-600">Saved.</span>`:""}
					${R==="error"?o`<span class="text-xs text-destructive">Failed to save.</span>`:""}
				</div>
			`:""}

			<hr class="border-border" />
			<div class="flex flex-col gap-1">
				<div class="text-[11px] text-muted-foreground uppercase tracking-wider font-medium">Danger Zone</div>
			</div>
			<div class="flex items-center gap-3">
				<button
					class="px-4 py-2 text-sm rounded-md border border-input bg-background text-foreground
						hover:bg-destructive hover:text-destructive-foreground hover:border-destructive transition-colors"
					@click=${async()=>{if(!confirm("Remove project '"+(t?.name||"")+"' from this server? This won't delete any files on disk."))return;await Jt(e)&&(kt(await qt()),fe("settings","system/general",!0),r())}}
				>Remove Project</button>
				<span class="text-xs text-muted-foreground">Unregister this project. No files will be deleted.</span>
			</div>
		</div>
	`}const j=new Map;function Ga(){return{loaded:!1,components:[],workflows:{},worktreeRoot:"",dirty:!1,saving:"",errorMessage:"",workflowsExpanded:!1,expanded:new Set}}function Ka(e){const t=j.get(e);t?.loaded||t?.dirty||(t||j.set(e,Ga()),(async()=>{try{const s=await f(`/api/projects/${e}/structured`);if(!s.ok){const l=await s.json().catch(()=>({})),i=j.get(e);i.loaded=!0,i.errorMessage=l?.error||`Load failed (${s.status})`,r();return}const n=await s.json(),a=j.get(e);a.components=(n.components||[]).map(As),a.workflows=n.workflows||{},a.worktreeRoot=n.worktree_root||"",a.loaded=!0,r()}catch(s){const n=j.get(e);n.loaded=!0,n.errorMessage=s?.message||String(s),r()}})())}function T(e){const t=j.get(e);t&&(t.dirty=!0,t.saving="")}async function za(e){const t=j.get(e);if(t){t.saving="saving",t.errorMessage="",r();try{const s=Rs(t.components,t.workflows,t.worktreeRoot),n=await f(`/api/projects/${e}/config`,{method:"PUT",body:JSON.stringify(s)});if(n.ok)t.saving="saved",t.dirty=!1,setTimeout(()=>{const a=j.get(e);a&&(a.saving="",r())},2e3);else{const a=await n.json().catch(()=>({})),l=Array.isArray(a?.details)&&a.details.length>0?a.details.map(i=>i?.message??String(i)).join(`
`):"";t.saving="error",t.errorMessage=l?`${a?.error||"Validation failed"}
${l}`:a?.error||`Save failed (${n.status})`}}catch(s){t.saving="error",t.errorMessage=s?.message||String(s)}r()}}function Ia(e){Ka(e);const t=j.get(e);if(!t||!t.loaded)return o`<div class="text-sm text-muted-foreground">Loading components…</div>`;const s=a=>{t.expanded.has(a)?t.expanded.delete(a):t.expanded.add(a),r()},n=(a,l)=>{const i=t.expanded.has(l),c=[a.repo&&a.repo!=="."?a.repo:null,a.relative_path].filter(Boolean).join(" / ")||". (project root)",p=a.commands.length===0,u=p?"data-only":`${a.commands.length} cmd${a.commands.length===1?"":"s"}`;return o`
			<div class="wf-gate-card ${i?"expanded":""}" data-testid="component-card" data-component-name=${a.name}>
				<div class="wf-gate-header" @click=${()=>s(l)}>
					<span class="wf-gate-idx">${l+1}</span>
					<span class="wf-gate-chevron">▸</span>
					<span class="wf-gate-name">${a.name||"(unnamed)"}</span>
					<span class="wf-gate-pill" title=${c}>${c}</span>
					${p?o`<span class="wf-gate-pill" data-testid="data-only-hint">${u}</span>`:o`<span class="wf-gate-pill">${u}</span>`}
					<input type="checkbox" class="sr-only" tabindex="-1"
						data-testid="data-only-toggle" .checked=${p} disabled
						@click=${d=>d.stopPropagation()}/>
					<button
						class="wf-gate-delete"
						title="Remove component"
						data-testid="delete-component"
						@click=${d=>{d.stopPropagation(),confirm(`Delete component "${a.name}"?`)&&(t.components.splice(l,1),t.expanded.delete(l),T(e),r())}}
					>${v(Ts,"sm")}</button>
				</div>
				<div class="wf-gate-body">
					<div class="wf-gate-body-inner">
						<div class="wf-identity-row">
							<label class="wf-field-label">Name</label>
							<input class="wf-input" style="flex:1;min-width:0;" .value=${a.name} placeholder="Component name"
								data-testid="component-name"
								@click=${d=>d.stopPropagation()}
								@input=${d=>{a.name=d.target.value,T(e),r()}}/>
						</div>
						<div class="wf-identity-row">
							<label class="wf-field-label">Git repo</label>
							<input class="wf-input" style="width:160px;" .value=${a.repo} placeholder="."
								@click=${d=>d.stopPropagation()}
								@input=${d=>{a.repo=d.target.value,T(e),r()}}/>
							<label class="wf-field-label" style="margin-left:8px;">Component path</label>
							<input class="wf-input" style="flex:1;min-width:0;" .value=${a.relative_path??""} placeholder="e.g. packages/api"
								@click=${d=>d.stopPropagation()}
								@input=${d=>{a.relative_path=d.target.value,T(e),r()}}/>
						</div>
						<div class="wf-identity-row">
							<label class="wf-field-label">Worktree setup</label>
							<input class="wf-input" style="flex:1;min-width:0;" .value=${a.worktree_setup_command??""} placeholder="e.g. npm ci --prefer-offline"
								@click=${d=>d.stopPropagation()}
								@input=${d=>{a.worktree_setup_command=d.target.value,T(e),r()}}/>
						</div>
						<div class="wf-field" data-testid="commands-list">
								<span class="wf-verify-label">Commands (${a.commands.length})</span>
								<div class="flex flex-col gap-1.5">
									${a.commands.map((d,g)=>o`
										<div class="flex items-center gap-2" data-testid="command-row" @click=${x=>x.stopPropagation()}>
											<input type="text" class="wf-input" style="width:140px;" .value=${d.key} placeholder="name"
												data-testid="command-key"
												@input=${x=>{d.key=x.target.value,T(e),r()}}/>
											<input type="text" class="wf-input" style="flex:1;min-width:0;" .value=${d.value} placeholder="shell command"
												data-testid="command-value"
												@input=${x=>{d.value=x.target.value,T(e),r()}}/>
											<button
												class="wf-gate-delete"
												title="Remove command"
												@click=${()=>{a.commands.splice(g,1),T(e),r()}}
											>${v(W,"sm")}</button>
										</div>
									`)}
									<button
										class="wf-criteria-add-btn"
										data-testid="add-command"
										@click=${d=>{d.stopPropagation(),a.commands.push({key:"",value:""}),T(e),r()}}
								>Add Command</button>
							</div>
							${p?o`<div class="text-[11px] text-muted-foreground italic pl-1 mt-1">No commands defined — this is a data-only component (e.g. docs, fixtures, schemas).</div>`:""}
						</div>
						<div class="wf-field" data-testid=${`component-config-${a.name}`}>
							<span class="wf-verify-label">Config (${a.config.length})</span>
							<div class="text-[11px] text-muted-foreground italic pl-1 mb-1">Opaque key→string map consumed by skills (e.g. <code>qa_start_command</code>, <code>qa_health_check</code>, <code>qa_max_duration_minutes</code>).</div>
							<div class="flex flex-col gap-1.5">
								${a.config.map((d,g)=>o`
									<div class="flex items-center gap-2" data-testid="config-row" @click=${x=>x.stopPropagation()}>
										<input type="text" class="wf-input" style="width:200px;" .value=${d.key} placeholder="qa_start_command"
											data-testid="config-key"
											@input=${x=>{d.key=x.target.value,T(e),r()}}/>
										<input type="text" class="wf-input" style="flex:1;min-width:0;" .value=${d.value} placeholder="value"
											data-testid="config-value"
											@input=${x=>{d.value=x.target.value,T(e),r()}}/>
										<button
											class="wf-gate-delete"
											title="Remove config entry"
											data-testid="delete-config"
											@click=${()=>{a.config.splice(g,1),T(e),r()}}
										>${v(W,"sm")}</button>
									</div>
								`)}
								<button
									class="wf-criteria-add-btn"
									data-testid="add-config"
									@click=${d=>{d.stopPropagation(),a.config.push({key:"",value:""}),T(e),r()}}
								>Add Config Entry</button>
							</div>
						</div>
					</div>
				</div>
			</div>
		`};return o`
		<div class="flex flex-col gap-5" data-testid="components-tab">
			${t.errorMessage?o`<div class="text-sm text-destructive whitespace-pre-wrap" data-testid="components-error">${t.errorMessage}</div>`:""}

			<div class="flex items-start gap-4">
				<p class="text-sm text-muted-foreground flex-1 m-0">Components are the build targets in this project — each one has its own commands (build, test, check) and may live in its own git repo or sub-path. Workflow steps reference these commands so they stay in sync as the project evolves.</p>
				<div class="flex items-center gap-2 shrink-0">
					${Z({variant:"default",size:"sm",onClick:async()=>{const a=(G.projects||[]).find(i=>i.id===e);if(!a?.rootPath)return;const{createProjectAssistantSession:l}=await Yt(async()=>{const{createProjectAssistantSession:i}=await import("./dialogs-DeLdzEM7.js");return{createProjectAssistantSession:i}},__vite__mapDeps([0,1,2,3,4,5,6,7]));await l(a.rootPath,!1,{projectId:e,existingProjectName:a.name||""})},children:o`<span class="inline-flex items-center gap-1.5 font-semibold" data-testid="open-project-assistant">${v(Pt,"sm")} Open Project Assistant</span>`})}
				</div>
			</div>
			<div class="flex flex-col gap-3">
				${t.components.length===0?o`<div class="text-sm text-muted-foreground italic">No components defined. Use "Re-scan repos" or "Add Component" to get started.</div>`:t.components.map((a,l)=>n(a,l))}
				<button
					class="wf-add-card-btn"
					data-testid="add-component"
					@click=${()=>{t.components.push({name:`component-${t.components.length+1}`,repo:".",relative_path:"",worktree_setup_command:"",commands:[],config:[]}),t.expanded.add(t.components.length-1),T(e),r()}}
				>${v(ke,"sm")}<span>Add Component</span></button>
			</div>

			<div class="flex items-center gap-3 pt-2 border-t border-border">
				<button
					class="px-4 py-2 text-sm rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50"
					?disabled=${!t.dirty||t.saving==="saving"}
					data-testid="save-components"
					@click=${()=>za(e)}
				>${t.saving==="saving"?"Saving…":"Save"}</button>
				${t.saving==="saved"?o`<span class="text-xs text-green-600" data-testid="save-status">Saved.</span>`:""}
				${t.saving==="error"?o`<span class="text-xs text-destructive" data-testid="save-status">Failed.</span>`:""}
			</div>
		</div>
	`}function Ha(e){return jt()}let Ye=null;function Ja(e){return xs()!==e&&(vs(e),Ye=null),Ye!==e&&(Ye=e,ms()),o`<div data-testid="workflows-tab">${gs({embedded:!0})}</div>`}function qa(e){const t=(G.projects||[]).find(l=>l.id===e);if(!t)return o`<div class="text-sm text-muted-foreground">Project not found.</div>`;const s=t.palette||null,n=async(l,i,c)=>{const p={palette:l??null};i&&(p.colorLight=i),c&&(p.colorDark=c);try{const u=await f(`/api/projects/${e}`,{method:"PUT",body:JSON.stringify(p)});if(u.ok){const d=await u.json(),g=G.projects.findIndex(x=>x.id===e);g>=0&&(G.projects[g]=d),it(e),r()}}catch{}},a=async(l,i)=>{try{const c=await f(`/api/projects/${e}`,{method:"PUT",body:JSON.stringify({[l]:i})});if(c.ok){const p=await c.json(),u=G.projects.findIndex(d=>d.id===e);u>=0&&(G.projects[u]=p),it(e),r()}}catch{}};return o`
		<div class="flex flex-col gap-6">
			<!-- Palette Picker -->
			<div class="flex flex-col gap-3">
				<div>
					<h3 class="text-sm font-medium text-foreground">Color Palette</h3>
					<p class="text-xs text-muted-foreground mt-1">
						Override the global color palette when viewing this project's sessions and goals.
					</p>
				</div>
				<div class="grid gap-2" style="grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));">
					<!-- None option -->
					<button
						class="flex flex-col gap-1.5 px-3 py-2.5 rounded-lg border transition-all cursor-pointer text-left w-full
							${s===null?"border-primary bg-primary/5 ring-1 ring-primary/30":"border-border hover:border-primary/40 hover:bg-secondary/30"}"
						@click=${()=>n(void 0)}
					>
						<div class="flex items-center justify-center w-full rounded-md border border-dashed border-border" style="height:68px;">
							<span class="text-sm text-muted-foreground">No override</span>
						</div>
						<div class="flex items-center gap-1.5">
							<span class="text-sm font-medium ${s===null?"text-foreground":"text-muted-foreground"}">
								None (use global)
							</span>
							${s===null?o`<span class="text-xs text-primary">Active</span>`:""}
						</div>
					</button>
					<!-- Palette cards -->
					${Ot.map(l=>{const i=s===l.id,c=Ys[l.id];return o`
							<button
								class="flex flex-col gap-1.5 px-3 py-2.5 rounded-lg border transition-all cursor-pointer text-left w-full
									${i?"border-primary bg-primary/5 ring-1 ring-primary/30":"border-border hover:border-primary/40 hover:bg-secondary/30"}"
								title="Select ${l.name} palette"
								@click=${()=>n(l.id,c?.light,c?.dark)}
							>
								${Lt(l)}
								<div class="flex items-center gap-1.5">
									<span class="text-sm font-medium ${i?"text-foreground":"text-muted-foreground"}">
										${l.name}
									</span>
									${i?o`<span class="text-xs text-primary">Active</span>`:""}
								</div>
							</button>
						`})}
				</div>
			</div>

			<!-- Project Accent Color -->
			<div class="flex flex-col gap-3">
				<div>
					<h3 class="text-sm font-medium text-foreground">Project Accent Color</h3>
					<p class="text-xs text-muted-foreground mt-1">
						Used for the project header in the sidebar. Automatically seeded when you pick a palette.
					</p>
				</div>
				<div class="flex items-center gap-6">
					<div class="flex items-center gap-2">
						<label class="text-sm text-muted-foreground">Light mode</label>
						<input type="color"
							.value=${vt(t.colorLight||"")}
							@change=${l=>{const i=l.target.value;a("colorLight",i)}}
							class="w-10 h-8 rounded border border-input cursor-pointer"
							title="Light mode accent color"
						/>
						<span class="text-xs text-muted-foreground font-mono">${t.colorLight||""}</span>
					</div>
					<div class="flex items-center gap-2">
						<label class="text-sm text-muted-foreground">Dark mode</label>
						<input type="color"
							.value=${vt(t.colorDark||"")}
							@change=${l=>{const i=l.target.value;a("colorDark",i)}}
							class="w-10 h-8 rounded border border-input cursor-pointer"
							title="Dark mode accent color"
						/>
						<span class="text-xs text-muted-foreground font-mono">${t.colorDark||""}</span>
					</div>
				</div>
			</div>
		</div>
	`}let A=null,P=null,_=null,m=null,D=null,et=!1,E=null,I=null,yt=!1,S=null;function $t(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:e<1024*1024*1024?`${(e/(1024*1024)).toFixed(1)} MB`:`${(e/(1024*1024*1024)).toFixed(2)} GB`}function Ya(e){if(!e)return"never";try{return new Date(e).toLocaleString()}catch{return"unknown"}}function Ke(){return G.activeProjectId??void 0}async function tt(){m="search",r();try{D=await Vt(Ke()),et=!0}catch{D=null}m=null,r()}function Xa(){yt||(yt=!0,window.addEventListener("bobbit-index-event",e=>{const t=e.detail;t&&(t.type==="index:progress"?(E={completed:t.completed,total:t.total},I=null,r()):t.type==="index:complete"?(E=null,I=null,tt()):t.type==="index:error"&&(I=t.message||"Search index error",E=null,r()))}))}async function Za(){if(window.confirm("Rebuild the search index? This re-embeds every goal, session, message, and staff record for the active project. It runs in the background.")){m="search",r();try{const e=Ke(),t=await es(e);t.ok?(E={completed:0,total:0},I=null,lt({type:"index:progress",projectId:e??"",phase:"rebuild",total:0,completed:0,backlog:0})):t.status===503?(I=t.error||"Search unavailable",lt({type:"index:error",projectId:e??"",message:t.error||"Search unavailable",recoverable:!1})):I=t.error||`Rebuild failed (HTTP ${t.status})`}catch(e){I=e.message}m=null,r()}}async function Ft(){m="orphanRows",r();try{S=await ts(Ke())}catch{S={count:0,sample:[]}}m=null,r()}async function Qa(){m="orphanRows",r();try{await ss(Ke())}catch{}m=null,await Ft()}async function Wt(){m="worktrees",r();try{const e=await f("/api/maintenance/orphaned-worktrees");e.ok?A=(await e.json()).worktrees??[]:A=[]}catch{A=[]}m=null,r()}async function Va(){m="worktrees",r();try{await f("/api/maintenance/cleanup-worktrees",{method:"POST"})}catch{}m=null,await Wt()}async function Gt(){m="sessions",r();try{const e=await f("/api/maintenance/orphaned-sessions");e.ok?P=(await e.json()).sessions??[]:P=[]}catch{P=[]}m=null,r()}async function en(){m="sessions",r();try{await f("/api/maintenance/cleanup-sessions",{method:"POST"})}catch{}m=null,await Gt()}async function Kt(){m="archives",r();try{const e=await f("/api/maintenance/expired-archives");e.ok?_=await e.json():_={count:0,totalSizeBytes:0}}catch{_={count:0,totalSizeBytes:0}}m=null,r()}async function tn(){m="archives",r();try{await f("/api/maintenance/purge-archives",{method:"POST"})}catch{}m=null,await Kt()}function sn(){const e="px-3 py-1.5 text-sm rounded-md border border-input bg-background text-foreground hover:bg-secondary transition-colors disabled:opacity-50",t="px-3 py-1.5 text-sm rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50";Xa(),!et&&m!=="search"&&tt();const s=(()=>{if(!E)return null;const{completed:n,total:a}=E;return a<=0?0:Math.min(100,Math.round(n/a*100))})();return o`
		<div class="flex flex-col gap-6">
			<p class="text-sm text-muted-foreground">
				Review and clean up orphaned resources. No cleanup happens automatically on server restart — use these tools to manage resources manually.
			</p>

			<!-- Search Index -->
			<div class="flex flex-col gap-2 rounded-md border border-border p-4" data-section="search-index">
				<div class="flex items-center gap-2">
					<h3 class="text-sm font-semibold text-foreground">Search Index</h3>
					<search-status-dot></search-status-dot>
				</div>
				<p class="text-xs text-muted-foreground">
					Semantic + lexical search over goals, sessions, messages, and staff. Rebuild after upgrading or if results look stale.
				</p>
				${D?o`
					<div class="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-1 mt-1 text-xs">
						<div class="flex justify-between gap-2"><span class="text-muted-foreground">State</span><span class="text-foreground font-medium" data-search-state="${D.state}">${D.state}</span></div>
						<div class="flex justify-between gap-2"><span class="text-muted-foreground">Last rebuild</span><span class="text-foreground">${Ya(D.lastRebuildAt)}</span></div>
						<div class="flex justify-between gap-2"><span class="text-muted-foreground">Engine</span><span class="text-foreground font-mono truncate" title="${D.engine}">${D.engine} (${D.engineVersion})</span></div>
						<div class="flex justify-between gap-2"><span class="text-muted-foreground">Dataset size</span><span class="text-foreground">${$t(D.datasetBytes)}</span></div>
					</div>
					${Object.keys(D.rowCountsBySource||{}).length>0?o`
						<div class="flex flex-wrap gap-1.5 mt-1">
							${Object.entries(D.rowCountsBySource).map(([n,a])=>o`
								<span class="text-[10px] px-1.5 py-0.5 rounded bg-secondary text-secondary-foreground font-mono">${n}: ${a}</span>
							`)}
						</div>
					`:""}
				`:et?o`
					<p class="text-xs text-muted-foreground italic mt-1">Stats unavailable.</p>
				`:o`
					<p class="text-xs text-muted-foreground italic mt-1">Loading stats…</p>
				`}
				${E?o`
					<div class="mt-2" data-search-progress>
						<div class="flex items-center justify-between text-[11px] text-muted-foreground mb-1">
							<span>Rebuilding…</span>
							<span>${E.total>0?`${E.completed} / ${E.total}`:`${E.completed} items`}</span>
						</div>
						<div class="h-1.5 w-full rounded-full bg-secondary overflow-hidden">
							${s===0?o`
								<div class="h-full w-1/3 bg-amber-500 animate-pulse"></div>
							`:o`
								<div class="h-full bg-amber-500 transition-all" style="width: ${s}%"></div>
							`}
						</div>
					</div>
				`:""}
				${I?o`
					<p class="text-xs text-destructive mt-1" data-search-error>${I}</p>
				`:""}
				<div class="flex items-center gap-2 mt-2">
					<button
						class="${e}"
						?disabled=${m==="search"}
						@click=${tt}
						data-action="refresh-search-stats"
					>Refresh</button>
					<button
						class="${t}"
						?disabled=${m==="search"||!!E}
						@click=${Za}
						data-action="rebuild-search-index"
					>Rebuild Index</button>
				</div>
			</div>

			<!-- Orphaned Worktrees -->
			<div class="flex flex-col gap-2 rounded-md border border-border p-4">
				<h3 class="text-sm font-semibold text-foreground">Orphaned Worktrees</h3>
				<p class="text-xs text-muted-foreground">
					Session worktrees with no matching active session.
				</p>
				${A!==null&&A.length>0?o`
					<div class="flex flex-col gap-1 mt-1 max-h-40 overflow-y-auto">
						${A.map(n=>o`
							<div class="flex items-center gap-2 text-xs font-mono text-muted-foreground px-2 py-1 rounded bg-secondary/30">
								<span class="truncate flex-1">${n.path}</span>
								<span class="text-[10px] px-1.5 py-0.5 rounded bg-secondary text-secondary-foreground shrink-0">${n.branch}</span>
							</div>
						`)}
					</div>
				`:A!==null?o`
					<p class="text-xs text-muted-foreground italic mt-1">No orphaned worktrees found.</p>
				`:""}
				<div class="flex items-center gap-2 mt-2">
					<button
						class="${e}"
						?disabled=${m==="worktrees"}
						@click=${Wt}
					>${m==="worktrees"&&A===null?"Scanning...":"Scan"}</button>
					<button
						class="${t}"
						?disabled=${m==="worktrees"||!A||A.length===0}
						@click=${Va}
					>${m==="worktrees"&&A!==null?"Cleaning...":`Clean Up${A&&A.length>0?` (${A.length})`:""}`}</button>
				</div>
			</div>

			<!-- Orphaned Sessions -->
			<div class="flex flex-col gap-2 rounded-md border border-border p-4">
				<h3 class="text-sm font-semibold text-foreground">Orphaned Sessions</h3>
				<p class="text-xs text-muted-foreground">
					Non-interactive sessions (e.g. verification reviewers) with no tracking.
				</p>
				${P!==null&&P.length>0?o`
					<div class="flex flex-col gap-1 mt-1 max-h-40 overflow-y-auto">
						${P.map(n=>o`
							<div class="flex items-center gap-2 text-xs px-2 py-1 rounded bg-secondary/30">
								<span class="truncate flex-1 text-foreground">${n.title||"Untitled"}</span>
								<span class="text-[10px] font-mono text-muted-foreground shrink-0">${n.id.slice(0,8)}</span>
							</div>
						`)}
					</div>
				`:P!==null?o`
					<p class="text-xs text-muted-foreground italic mt-1">No orphaned sessions found.</p>
				`:""}
				<div class="flex items-center gap-2 mt-2">
					<button
						class="${e}"
						?disabled=${m==="sessions"}
						@click=${Gt}
					>${m==="sessions"&&P===null?"Scanning...":"Scan"}</button>
					<button
						class="${t}"
						?disabled=${m==="sessions"||!P||P.length===0}
						@click=${en}
					>${m==="sessions"&&P!==null?"Terminating...":`Terminate${P&&P.length>0?` (${P.length})`:""}`}</button>
				</div>
			</div>

			<!-- Expired Archives -->
			<div class="flex flex-col gap-2 rounded-md border border-border p-4">
				<h3 class="text-sm font-semibold text-foreground">Expired Archives</h3>
				<p class="text-xs text-muted-foreground">
					Archived sessions past the retention period.
				</p>
				${_!==null?o`
					<p class="text-sm text-foreground mt-1">
						${_.count>0?o`${_.count} session${_.count!==1?"s":""} (${$t(_.totalSizeBytes)})`:o`<span class="text-muted-foreground italic text-xs">No expired archives found.</span>`}
					</p>
				`:""}
				<div class="flex items-center gap-2 mt-2">
					<button
						class="${e}"
						?disabled=${m==="archives"}
						@click=${Kt}
					>${m==="archives"&&_===null?"Scanning...":"Scan"}</button>
					<button
						class="${t}"
						?disabled=${m==="archives"||!_||_.count===0}
						@click=${tn}
					>${m==="archives"&&_!==null?"Purging...":`Purge${_&&_.count>0?` (${_.count})`:""}`}</button>
				</div>
			</div>

			<!-- Orphaned Search-Index Rows -->
			<div class="flex flex-col gap-2 rounded-md border border-border p-4" data-section="orphaned-index-rows">
				<h3 class="text-sm font-semibold text-foreground">Orphaned Index Rows</h3>
				<p class="text-xs text-muted-foreground">
					Search-index rows whose source entity (goal, session, staff, or message) no longer exists.
				</p>
				${S!==null&&S.count>0?o`
					<p class="text-sm text-foreground mt-1">${S.count} orphaned row${S.count!==1?"s":""}.</p>
					${S.sample.length>0?o`
						<div class="flex flex-col gap-1 mt-1 max-h-40 overflow-y-auto">
							${S.sample.map(n=>o`
								<div class="flex items-center gap-2 text-xs font-mono text-muted-foreground px-2 py-1 rounded bg-secondary/30">
									<span class="truncate flex-1">${n.id}</span>
									<span class="text-[10px] px-1.5 py-0.5 rounded bg-secondary text-secondary-foreground shrink-0">${n.source_id}</span>
								</div>
							`)}
						</div>
					`:""}
				`:S!==null?o`
					<p class="text-xs text-muted-foreground italic mt-1">No orphaned rows found.</p>
				`:""}
				<div class="flex items-center gap-2 mt-2">
					<button
						class="${e}"
						?disabled=${m==="orphanRows"}
						@click=${Ft}
						data-action="scan-orphan-index-rows"
					>${m==="orphanRows"&&S===null?"Scanning...":"Scan"}</button>
					<button
						class="${t}"
						?disabled=${m==="orphanRows"||!S||S.count===0}
						@click=${Qa}
						data-action="cleanup-orphan-index-rows"
					>${m==="orphanRows"&&S!==null?"Cleaning...":`Remove Orphan Rows${S&&S.count>0?` (${S.count})`:""}`}</button>
				</div>
			</div>
		</div>
	`}function kn(){Hs();const e=te(),t=Et(e),s=Ms(),n=e!=="system";return o`
		<div class="flex-1 flex flex-col min-h-0 overflow-hidden">
			<!-- Header -->
			<div class="shrink-0 flex items-center gap-3 px-4 py-3 border-b border-border">
				<button
					class="p-1.5 rounded-md hover:bg-secondary transition-colors text-muted-foreground hover:text-foreground"
					@click=${()=>{ge(),an(),Fs()}}
					title="Back"
				>${v(Ss,"sm")}</button>
				<h1 class="text-lg font-semibold">Settings</h1>
			</div>
			<!-- Scope row -->
			${Ua(e)}
			<!-- Tab bar -->
			<div class="shrink-0 flex items-center gap-1 px-4 py-2 border-b border-border bg-secondary/20">
				${t.map(a=>o`
					<button
						class="px-3 py-1.5 text-sm rounded-md transition-colors
							${s===a.id?"bg-background text-foreground shadow-sm border border-border":"text-muted-foreground hover:text-foreground hover:bg-secondary/50"}"
						title="${a.label}"
						@click=${()=>{fe("settings",`${e}/${a.id}`,!0)}}
					>${a.label}</button>
				`)}
			</div>
			<!-- Tab content — every tab gets the same centered max-width column for
			     visual consistency, matching the Workflows tab feel. Workflows itself
			     self-centers via .wf-list so a wider outer wrapper is fine. -->
			<div class="flex-1 overflow-y-auto">
			 <div class="max-w-3xl mx-auto p-2 sm:p-4">
				<div>
					${n?o`
						${s==="general"?Wa(e):""}
						${s==="appearance"?qa(e):""}
						${s==="project"?Fa(e):""}
						${s==="components"?Ia(e):""}
						${s==="workflows"?Ja(e):""}
						${s==="directories"?Ha():""}
					`:o`
						${s==="general"?Ea():""}
						${s==="models"?ya():""}
						${s==="shortcuts"?qs():""}
						${s==="palette"?Qs():""}
						${s==="directories"?jt():""}
						${s==="account"?ja():""}
						${s==="maintenance"?sn():""}
					`}
				</div>
			 </div>
			</div>
		</div>
	`}function an(){oe&&(window.removeEventListener("keydown",Ze,!0),oe=!1)}export{wn as __testResetModelsTab,$n as __testSetPrefs,Bt as formatModelPref,yn as invalidateProjectScopeConfig,qe as renderModelRow,ya as renderModelsTab,kn as renderSettingsPage,hn as setActiveSettingsTab,Fs as toggleSettings};
