Sign In

@codeyam-editor/codeyam-editor

Package Overview
Dependencies
Maintainers
1
Versions
42
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@codeyam-editor/codeyam-editor - npm Package Compare versions

Comparing version
0.1.0-staging.g296f8a2
to
0.1.0-staging.g3858572
+59
npm/compose-conflict.js
'use strict';
// npm/compose-conflict.js — classify a failed `docker compose up -d` on a fleet
// VM, so `cloud:up` can tell "another run beat me to this container" apart from
// "provisioning genuinely broke".
//
// Why this exists: on 2026-08-07 two concurrent `cloud:up` runs raced to start
// the editor container on one fresh VM. The loser's `up -d` exited non-zero with
// the docker daemon's container-name conflict, `cloud.js` flattened that into a
// generic "docker compose up -d failed on the VM", and the abort handler
// escalated it all the way to deleting a VM the winner had already brought up
// and rostered. A name conflict is the opposite of a failure — it is proof the
// shared goal (container running) was reached by someone else.
//
// Matching a daemon error string is exactly the kind of thing that rots when
// docker rephrases it, so it lives here as a named, unit-tested function with
// the real observed message pinned as a fixture — not as an inline regex at the
// call site.
// The stable fragments of the daemon's conflict message. The container id and
// name vary per run and docker's exact phrasing has changed across versions, so
// we require all three fragments rather than the whole sentence. Observed
// verbatim in the VM-4 add log:
//
// Error response from daemon: Conflict. The container name
// "/codeyam-editor-editor-1" is already in use by container "c9d448c6…".
// You have to remove (or rename) that container to be able to reuse that name.
//
// Compose emits it twice (once prefixed `service:editor:1`, once bare) and
// interleaves its own progress lines, so the match is substring-based over the
// whole captured stream rather than line- or anchor-based.
const NAME_CONFLICT_FRAGMENTS = [/\bConflict\./i, /container name/i, /is already in use/i];
/** Classify the outcome of `docker compose up -d`.
*
* Returns one of:
* - `'ok'` — the command succeeded; there is no failure to classify. Guards
* against a caller that classifies unconditionally, so a zero status can
* never be read as a conflict.
* - `'name-conflict'` — the daemon refused because a container of that name
* already exists, i.e. a concurrent run already started it. The caller
* re-probes for a running container and converges instead of throwing.
* - `'failed'` — anything else. Existing behavior: a real provisioning
* failure.
*
* Pure: `{ status, stderr } → tag`, with no I/O, so the whole matrix is
* testable without a VM. `stderr` may be null/undefined (the pre-fix call site
* captured nothing), which classifies as `'failed'` — the conservative
* direction, since converging requires positive evidence of a conflict. */
function classifyComposeUpFailure({ status, stderr } = {}) {
if (status === 0) return 'ok';
const text = String(stderr == null ? '' : stderr);
if (NAME_CONFLICT_FRAGMENTS.every((re) => re.test(text))) {
return 'name-conflict';
}
return 'failed';
}
module.exports = { classifyComposeUpFailure };
import{j as e}from"./markdown-C9tbsVHX.js";import{b as a}from"./react-nrLBr15I.js";import{u as O}from"./useEvents-S3Qk1Y_d.js";import{b as E}from"./index-BPLKgwon.js";function P({connected:t}){return e.jsx("span",{className:`inline-block rounded-full px-2 py-0.5 text-xs font-medium text-[var(--text-primary)] ${t?"bg-[var(--accent-green)]":"bg-[var(--accent-red)]"}`,children:t?"connected":"reconnecting…"})}const re=Object.freeze(Object.defineProperty({__proto__:null,ConnectionBadge:P},Symbol.toStringTag,{value:"Module"}));function N(){const[t,r]=a.useState(null),[o,s]=a.useState(!0),{events:l}=O(),n=a.useCallback(async()=>{try{const c=await(await fetch("/api/inspector/status")).json();r(c)}catch{}finally{s(!1)}},[]);return a.useEffect(()=>{n()},[n]),a.useEffect(()=>{const i=l[l.length-1];i&&i.type==="scenario_switch"&&n()},[l,n]),{scenario:(t==null?void 0:t.scenario)??null,previewScenario:(t==null?void 0:t.previewScenario)??null,routes:(t==null?void 0:t.routes)??[],routeCount:(t==null?void 0:t.routeCount)??0,loading:o,refetch:n}}function v({title:t}){return e.jsx("h2",{style:{fontSize:14,fontWeight:600,color:"#f0f6fc",margin:"0 0 8px 0",borderBottom:"1px solid #30363d",paddingBottom:8},children:t})}function f({color:t,children:r}){return e.jsx("span",{style:{background:`${t}22`,color:t,border:`1px solid ${t}55`,borderRadius:4,padding:"2px 8px",fontSize:11,fontWeight:600,letterSpacing:"0.5px",whiteSpace:"nowrap"},children:r})}const $={GET:"#D7FF63",POST:"#238636",PUT:"#d29922",DELETE:"#da3633",PATCH:"#bc8cff"};function k({method:t}){const r=$[t.toUpperCase()]??"#8b949e";return e.jsx("span",{style:{color:r,fontWeight:600,fontSize:11,minWidth:48,display:"inline-block"},children:t.toUpperCase()})}function C({color:t,children:r}){return e.jsx("div",{style:{padding:"12px 16px",borderRadius:6,border:`1px solid ${t}44`,background:`${t}11`,color:t},children:r})}const u={padding:"8px 12px",textAlign:"center",color:"#8b949e",fontWeight:500,fontSize:11,textTransform:"uppercase",letterSpacing:"0.5px"},h={padding:"6px 12px"},S={background:"#161b22",border:"1px solid #30363d",borderRadius:6,color:"#c9d1d9",padding:"6px 12px",fontSize:13,fontFamily:"inherit"},R={...S,appearance:"none",paddingRight:24,backgroundImage:`url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238b949e' d='M6 8L1 3h10z'/%3E%3C/svg%3E")`,backgroundRepeat:"no-repeat",backgroundPosition:"right 8px center"},oe=Object.freeze(Object.defineProperty({__proto__:null,Badge:f,MethodBadge:k,SectionHeader:v,StatusBox:C,inputStyle:S,selectStyle:R,tdStyle:h,thStyle:u},Symbol.toStringTag,{value:"Module"}));function z({routes:t}){return e.jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:12},children:[e.jsx("thead",{children:e.jsxs("tr",{style:{borderBottom:"1px solid #30363d"},children:[e.jsx("th",{style:u,children:"Method"}),e.jsx("th",{style:{...u,textAlign:"left"},children:"Pattern"}),e.jsx("th",{style:u,children:"Status"})]})}),e.jsx("tbody",{children:t.map((r,o)=>e.jsxs("tr",{style:{borderBottom:"1px solid #21262d"},children:[e.jsx("td",{style:{...h,textAlign:"center"},children:e.jsx(k,{method:r.method})}),e.jsx("td",{style:h,children:r.pattern}),e.jsx("td",{style:{...h,textAlign:"center",color:"#8b949e"},children:r.status})]},o))})]})}const se=Object.freeze(Object.defineProperty({__proto__:null,InspectorRouteTable:z},Symbol.toStringTag,{value:"Module"}));function A(){const{scenario:t,previewScenario:r,routes:o,routeCount:s,loading:l}=N();return e.jsxs("section",{children:[e.jsx(v,{title:"Active Scenario"}),l?e.jsx(C,{color:"#8b949e",children:"Loading..."}):t?e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:12},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[e.jsx(f,{color:"#238636",children:"ACTIVE"}),e.jsx("span",{style:{color:"#f0f6fc",fontWeight:500},children:t.name}),e.jsxs("span",{style:{color:"#8b949e"},children:["(",t.slug,")"]})]}),e.jsxs("div",{style:{color:"#8b949e",fontSize:12},children:[s," mock route",s!==1?"s":""," loaded"]}),o.length>0&&e.jsx(z,{routes:o})]}):r?e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:12},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[e.jsx(f,{color:"#D7FF63",children:"PREVIEW"}),e.jsx("span",{style:{color:"#f0f6fc",fontWeight:500},children:r.name}),e.jsxs("span",{style:{color:"#8b949e"},children:["(",r.slug,")"]})]}),e.jsx("div",{style:{color:"#8b949e",fontSize:12},children:"Component preview — no HTTP mocks active"})]}):e.jsx(C,{color:"#d29922",children:"No scenario active — all requests pass through to upstream"})]})}const ne=Object.freeze(Object.defineProperty({__proto__:null,InspectorScenarioSection:A},Symbol.toStringTag,{value:"Module"}));function F({textUrlFilter:t,onTextUrlFilterChange:r,methodFilter:o,onMethodFilterChange:s,sourceFilter:l,onSourceFilterChange:n}){return e.jsxs("div",{style:{display:"flex",gap:8,marginBottom:12,alignItems:"center"},children:[e.jsx("input",{type:"text",placeholder:"Filter by URL...",value:t,onChange:i=>r(i.target.value),style:{...S,flex:1,minWidth:0}}),e.jsxs("select",{value:o,onChange:i=>s(i.target.value),style:R,children:[e.jsx("option",{value:"ALL",children:"All Methods"}),e.jsx("option",{value:"GET",children:"GET"}),e.jsx("option",{value:"POST",children:"POST"}),e.jsx("option",{value:"PUT",children:"PUT"}),e.jsx("option",{value:"DELETE",children:"DELETE"}),e.jsx("option",{value:"PATCH",children:"PATCH"})]}),e.jsxs("select",{value:l,onChange:i=>n(i.target.value),style:R,children:[e.jsx("option",{value:"ALL",children:"All Sources"}),e.jsx("option",{value:"MOCKED",children:"Mocked"}),e.jsx("option",{value:"PASSTHROUGH",children:"Passthrough"})]})]})}const ie=Object.freeze(Object.defineProperty({__proto__:null,InspectorTrafficFilterBar:F},Symbol.toStringTag,{value:"Module"})),G={color:"#8b949e",fontSize:11,fontWeight:600,textTransform:"uppercase",letterSpacing:"0.5px",marginBottom:4},M={background:"#161b22",border:"1px solid #30363d",borderRadius:4,padding:"8px 12px",margin:0,fontSize:11,color:"#c9d1d9",fontFamily:"monospace",maxHeight:200,overflow:"auto",whiteSpace:"pre-wrap",wordBreak:"break-all"};function _({label:t,children:r}){return e.jsxs("div",{style:{marginBottom:12},children:[e.jsx("div",{style:G,children:t}),r]})}function B({event:t}){let r=[];try{const n=new URL(t.url,"http://localhost");r=Array.from(n.searchParams.entries())}catch{}const o=t.requestHeaders&&Object.keys(t.requestHeaders).length>0,s=t.requestBody!=null,l=t.responseBody!=null;return e.jsxs("div",{style:{background:"#0d1117",padding:"12px 16px",borderTop:"1px solid #30363d"},children:[r.length>0&&e.jsx(_,{label:"Query Parameters",children:e.jsx("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"2px 12px",fontSize:12},children:r.map(([n,i],c)=>e.jsxs("div",{style:{display:"contents"},children:[e.jsx("span",{style:{color:"#D7FF63",fontFamily:"monospace"},children:n}),e.jsx("span",{style:{color:"#c9d1d9",fontFamily:"monospace"},children:i})]},c))})}),o&&e.jsx(_,{label:"Request Headers",children:e.jsx("pre",{style:M,children:Object.entries(t.requestHeaders).map(([n,i])=>`${n}: ${i}`).join(`
`)})}),s&&e.jsx(_,{label:"Request Body",children:e.jsx("pre",{style:M,children:typeof t.requestBody=="string"?t.requestBody:JSON.stringify(t.requestBody,null,2)})}),l&&e.jsx(_,{label:"Response Body",children:e.jsx("pre",{style:M,children:typeof t.responseBody=="string"?t.responseBody:JSON.stringify(t.responseBody,null,2)})}),r.length===0&&!o&&!s&&!l&&e.jsx("div",{style:{color:"#8b949e",fontSize:12},children:"No additional details available for this request"})]})}const le=Object.freeze(Object.defineProperty({__proto__:null,DetailSection:_,InspectorTrafficDetail:B},Symbol.toStringTag,{value:"Module"}));function L({event:t}){const[r,o]=a.useState(!1),s=new Date(t.timestampMs).toLocaleTimeString(),l=t.mocked;return e.jsxs(e.Fragment,{children:[e.jsxs("tr",{onClick:()=>o(!r),style:{borderBottom:"1px solid #21262d",background:l?"transparent":"rgba(210, 153, 34, 0.05)",cursor:"pointer"},children:[e.jsxs("td",{style:{...h,color:"#8b949e",textAlign:"center"},children:[e.jsx("span",{style:{display:"inline-block",width:12,marginRight:4,fontSize:10,color:"#8b949e",transition:"transform 0.15s",transform:r?"rotate(90deg)":"rotate(0deg)"},children:"▶"}),s]}),e.jsx("td",{style:{...h,textAlign:"center"},children:e.jsx(k,{method:t.method})}),e.jsx("td",{style:{...h,maxWidth:400,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:t.url}),e.jsx("td",{style:{...h,textAlign:"center",color:"#8b949e"},children:t.status??"---"}),e.jsxs("td",{style:{...h,textAlign:"center",color:"#8b949e"},children:[t.durationMs,"ms"]}),e.jsx("td",{style:{...h,textAlign:"center"},children:l?t.mockSource==="default"?e.jsx(f,{color:"#1f6feb",children:"MOCKED (default)"}):e.jsx(f,{color:"#238636",children:"MOCKED (scenario)"}):e.jsx(f,{color:"#d29922",children:"PASSTHROUGH"})})]}),r&&e.jsx("tr",{children:e.jsx("td",{colSpan:6,style:{padding:0},children:e.jsx(B,{event:t})})})]})}const ae=Object.freeze(Object.defineProperty({__proto__:null,InspectorTrafficRow:L},Symbol.toStringTag,{value:"Module"}));function D({events:t}){return e.jsx("div",{style:{maxHeight:300,overflowY:"auto",border:"1px solid #30363d",borderRadius:6},children:e.jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:12},children:[e.jsx("thead",{children:e.jsxs("tr",{style:{borderBottom:"1px solid #30363d",position:"sticky",top:0,background:"#0d1117"},children:[e.jsx("th",{style:u,children:"Time"}),e.jsx("th",{style:u,children:"Method"}),e.jsx("th",{style:{...u,textAlign:"left"},children:"URL"}),e.jsx("th",{style:u,children:"Status"}),e.jsx("th",{style:u,children:"Duration"}),e.jsx("th",{style:u,children:"Source"})]})}),e.jsx("tbody",{children:t.map(r=>e.jsx(L,{event:r},r.id))})]})})}const ce=Object.freeze(Object.defineProperty({__proto__:null,InspectorTrafficTable:D},Symbol.toStringTag,{value:"Module"}));function K(t){if(t===void 0)return"checked";if(t==="checked")return"excluded"}function I({chips:t,chipStates:r,onChipStatesChange:o}){if(t.length===0)return null;const s=n=>{const i=new Map(r),c=K(i.get(n));c===void 0?i.delete(n):i.set(n,c),o(i)},l=()=>{o(new Map)};return e.jsxs("div",{style:{display:"flex",flexWrap:"wrap",gap:6,marginBottom:12},children:[r.size>0&&e.jsx("button",{onClick:l,style:{background:"#da363322",color:"#da3633",border:"1px solid #da363355",borderRadius:4,padding:"2px 8px",fontSize:11,fontWeight:600,cursor:"pointer",fontFamily:"inherit"},children:"Clear all"}),t.map(({path:n,count:i})=>{const c=r.get(n),d=c==="checked",g=c==="excluded";let m="#30363d",b="#8b949e",T="#30363d",y="";return d?(m="#D7FF6322",b="#D7FF63",T="#D7FF6355",y="✓ "):g&&(m="#da363322",b="#da3633",T="#da363355",y="✗ "),e.jsxs("button",{onClick:()=>s(n),style:{background:m,color:b,border:`1px solid ${T}`,borderRadius:4,padding:"2px 8px",fontSize:11,fontFamily:"monospace",cursor:"pointer",whiteSpace:"nowrap"},children:[y,n,e.jsxs("span",{style:{marginLeft:4,opacity:.6,fontFamily:"inherit"},children:["(",i,")"]})]},n)})]})}const de=Object.freeze(Object.defineProperty({__proto__:null,InspectorTrafficUrlChips:I},Symbol.toStringTag,{value:"Module"}));function H(){const{events:t}=O(),[r,o]=a.useState(""),[s,l]=a.useState(()=>new Map),[n,i]=a.useState("ALL"),[c,d]=a.useState("ALL"),g=t.filter(p=>p.type==="http_request"),m=a.useMemo(()=>{const p=new Map;for(const x of g){const j=E(x.url);p.set(j,(p.get(j)||0)+1)}return Array.from(p.entries()).sort((x,j)=>j[1]-x[1]).slice(0,10).map(([x,j])=>({path:x,count:j}))},[g]),b=a.useMemo(()=>Array.from(s.values()).some(p=>p==="checked"),[s]),y=g.filter(p=>{if(r&&!p.url.toLowerCase().includes(r.toLowerCase()))return!1;const x=E(p.url);return!(s.get(x)==="excluded"||b&&s.get(x)!=="checked"||n!=="ALL"&&p.method.toUpperCase()!==n||c==="MOCKED"&&!p.mocked||c==="PASSTHROUGH"&&p.mocked)}).slice(-50).reverse();return e.jsxs("section",{children:[e.jsx(v,{title:"Live Traffic"}),e.jsx(F,{textUrlFilter:r,onTextUrlFilterChange:o,methodFilter:n,onMethodFilterChange:i,sourceFilter:c,onSourceFilterChange:p=>d(p)}),e.jsx(I,{chips:m,chipStates:s,onChipStatesChange:l}),y.length===0?e.jsx(C,{color:"#8b949e",children:g.length>0?"No matching traffic — try adjusting the filters":"No HTTP traffic yet — requests will appear here in real-time"}):e.jsx(D,{events:y})]})}const pe=Object.freeze(Object.defineProperty({__proto__:null,InspectorTrafficSection:H},Symbol.toStringTag,{value:"Module"}));function J(){const[t,r]=a.useState(null),[o,s]=a.useState(!1);return{explain:a.useCallback(async(n,i)=>{s(!0);try{const d=await(await fetch("/api/inspector/explain",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({method:n,url:i})})).json();r(d)}catch{}finally{s(!1)}},[]),result:t,loading:o}}function U({result:t}){return e.jsxs("div",{style:{border:"1px solid #30363d",borderRadius:6,padding:12},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,marginBottom:12},children:[e.jsxs("span",{style:{color:"#f0f6fc",fontWeight:500},children:[t.method," ",t.url]}),t.wouldMatch?e.jsx(f,{color:"#238636",children:"WOULD MATCH"}):e.jsx(f,{color:"#da3633",children:"NO MATCH"})]}),t.attempts.length===0?e.jsx("div",{style:{color:"#8b949e"},children:"No routes loaded to match against"}):e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:6},children:t.attempts.map((r,o)=>e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,padding:"4px 8px",borderRadius:4,background:r.failure?"transparent":"rgba(35, 134, 54, 0.1)"},children:[e.jsx("span",{style:{color:r.failure?"#da3633":"#238636",fontWeight:600,width:16},children:r.failure?"x":"v"}),e.jsx(k,{method:r.method}),e.jsx("span",{children:r.pattern}),r.failure&&e.jsx("span",{style:{color:"#8b949e",fontSize:11},children:r.failure.reason==="methodMismatch"?`method: expected ${r.failure.expected}, got ${r.failure.actual}`:`path "${r.failure.path}" does not match "${r.failure.pattern}"`})]},o))})]})}const ue=Object.freeze(Object.defineProperty({__proto__:null,InspectorExplainerResult:U},Symbol.toStringTag,{value:"Module"}));function W(){const{explain:t,result:r,loading:o}=J(),[s,l]=a.useState("GET"),[n,i]=a.useState(""),c=d=>{d.preventDefault(),n.trim()&&t(s,n.trim())};return e.jsxs("section",{children:[e.jsx(v,{title:"Match Explainer"}),e.jsxs("form",{onSubmit:c,style:{display:"flex",gap:8,marginBottom:12},children:[e.jsx("select",{value:s,onChange:d=>l(d.target.value),style:{...S,width:100},children:["GET","POST","PUT","DELETE","PATCH"].map(d=>e.jsx("option",{value:d,children:d},d))}),e.jsx("input",{type:"text",value:n,onChange:d=>i(d.target.value),placeholder:"/api/users",style:{...S,flex:1}}),e.jsx("button",{type:"submit",disabled:o||!n.trim(),style:{background:"#238636",color:"#fff",border:"none",borderRadius:6,padding:"6px 16px",cursor:"pointer",fontSize:12,fontWeight:500,opacity:o||!n.trim()?.5:1},children:o?"Checking...":"Explain"})]}),r&&e.jsx(U,{result:r})]})}const he=Object.freeze(Object.defineProperty({__proto__:null,InspectorExplainerSection:W},Symbol.toStringTag,{value:"Module"})),w={"/api/health":"Health check — confirms the proxy and mock engine are running","/api/events":"SSE stream of real-time events (traffic, errors) powering this Inspector","/api/config":"Returns current CodeYam configuration for the project","/api/editor-dev-server":"Polls/controls the dev server status (start, stop, restart)","/api/inspector/status":"Returns active scenario and loaded mock routes","/api/inspector/explain":"Tests which mock route would match a given method + URL","/api/scenarios":"Lists all defined scenarios with metadata","/api/tests":"Returns the AI-maintained test registry joined with per-test-evidence, grouped by file","/api/test-results":"Returns latest test execution results","/api/screenshots/*":"Serves captured scenario screenshots","/api/session-info":"Returns current editor session metadata","/api/data-structure":"Returns the app's data structure definition","/api/dependency-graph":"Returns the project's component dependency graph","/api/app-route-entities":"Lists app routes and their associated entities","/api/glossary":"Returns the project's component glossary","/api/step":"Returns current editor workflow step","/api/editor-commands":"Lists available editor commands"};function V(t){if(w[t])return w[t];for(const[r,o]of Object.entries(w))if(r.endsWith("/*")){const s=r.slice(0,-1);if(t.startsWith(s))return o}}function Y(t){const r=new Set([...t,...Object.keys(w)]),o=[];for(const s of Array.from(r).sort()){if(s.endsWith("/*"))continue;const l=V(s);o.push({path:s,description:l??"Application endpoint"})}return o}function q(){const{events:t}=O(),r=a.useMemo(()=>{const o=[];for(const s of t)s.type==="http_request"&&o.push(E(s.url));return Y(o)},[t]);return r.length===0?null:e.jsxs("section",{children:[e.jsx(v,{title:"URL Glossary"}),e.jsx("div",{style:{border:"1px solid #30363d",borderRadius:6,overflow:"hidden"},children:e.jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:12},children:[e.jsx("thead",{children:e.jsxs("tr",{style:{borderBottom:"1px solid #30363d",background:"#161b22"},children:[e.jsx("th",{style:{padding:"6px 12px",textAlign:"left",color:"#8b949e",fontWeight:500,fontSize:11,textTransform:"uppercase",letterSpacing:"0.5px",width:"30%"},children:"Path"}),e.jsx("th",{style:{padding:"6px 12px",textAlign:"left",color:"#8b949e",fontWeight:500,fontSize:11,textTransform:"uppercase",letterSpacing:"0.5px"},children:"Description"})]})}),e.jsx("tbody",{children:r.map(({path:o,description:s})=>e.jsxs("tr",{style:{borderBottom:"1px solid #21262d"},children:[e.jsx("td",{style:{padding:"4px 12px",fontFamily:"monospace",color:"#D7FF63",fontSize:11},children:o}),e.jsx("td",{style:{padding:"4px 12px",color:"#8b949e"},children:s})]},o))})]})})]})}const xe=Object.freeze(Object.defineProperty({__proto__:null,InspectorUrlGlossary:q},Symbol.toStringTag,{value:"Module"}));function Q(){const{connected:t}=O();return e.jsxs("div",{style:{background:"#0d1117",color:"#c9d1d9",minHeight:"100vh",fontFamily:"'SF Mono', 'Fira Code', 'Cascadia Code', Menlo, monospace",fontSize:13,padding:24,display:"flex",flexDirection:"column",gap:24},children:[e.jsxs("header",{style:{display:"flex",alignItems:"center",gap:12},children:[e.jsx("h1",{style:{fontSize:18,fontWeight:600,color:"#f0f6fc",margin:0},children:"Container Inspector"}),e.jsx("span",{style:{color:"#8b949e",fontSize:12},children:"Read-only view of proxy and mock engine state"}),e.jsx(P,{connected:t})]}),e.jsx(A,{}),e.jsx(H,{}),e.jsx(W,{}),e.jsx(q,{})]})}const fe=Object.freeze(Object.defineProperty({__proto__:null,ContainerInspector:Q},Symbol.toStringTag,{value:"Module"}));export{f as B,Q as C,_ as D,L as I,k as M,v as S,xe as _,C as a,q as b,I as c,D as d,F as e,B as f,U as g,W as h,H as i,z as j,A as k,P as l,de as m,ce as n,pe as o,ae as p,ie as q,le as r,ne as s,u as t,se as u,oe as v,he as w,ue as x,fe as y,re as z};

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

import{j as e}from"./markdown-C9tbsVHX.js";import{b as o}from"./react-nrLBr15I.js";import{C as _,a as ce,u as le}from"./useServerIdentity-CVuHTzjg.js";function ie(t){return t.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")}function de(t){if(!t)return{base:null,sep:"/"};const r=t.lastIndexOf("/"),n=t.lastIndexOf("\\"),a=Math.max(r,n);if(a<0)return{base:null,sep:"/"};const c=a===n?"\\":"/";return{base:t.slice(0,a),sep:c}}function R({suggestedNewProjectPath:t,creating:r=!1,createError:n=null,onCreate:a,onClose:c}){const[i,x]=o.useState(""),[l,f]=o.useState(""),j=o.useRef(!1),{base:y,sep:m}=de(t),p=y?`${y}${m}`:"",w=`${p}${ie(i)}`,g=j.current?l:w;o.useEffect(()=>{const b=S=>{S.key==="Escape"&&!r&&c()};return window.addEventListener("keydown",b),()=>window.removeEventListener("keydown",b)},[r,c]);const h=b=>{j.current=!0,f(b)},N=()=>{const b=i.trim(),S=g.trim();b.length>0&&S.length>0&&!r&&a(S,b)},P=i.trim().length>0&&!r;return e.jsx("div",{role:"presentation",onClick:()=>{r||c()},className:"fixed inset-0 z-50 flex items-center justify-center bg-black/60 px-6 backdrop-blur-sm",children:e.jsxs("div",{role:"dialog","aria-modal":"true","aria-label":"Create your first project",onClick:b=>b.stopPropagation(),className:"w-full max-w-md rounded-xl border border-[var(--border-default)] bg-[var(--bg-elevated,#141414)] p-6 text-[var(--text-primary)] shadow-2xl",children:[e.jsx("h2",{className:"text-lg font-semibold text-[var(--text-primary)]",children:"Create your first project"}),e.jsx("p",{className:"mt-1 text-sm text-[var(--text-muted)]",children:"Name your project and choose where it lives. CodeYam will scaffold it and walk you through the rest."}),e.jsxs("label",{className:"mt-5 block text-xs font-medium text-[var(--text-secondary)]",children:["Project name",e.jsx("input",{type:"text",autoFocus:!0,value:i,onChange:b=>x(b.target.value),onKeyDown:b=>{b.key==="Enter"&&N()},placeholder:"My First App","aria-label":"Project name",className:"mt-1 w-full rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-2 text-sm font-normal text-[var(--text-primary)] placeholder:text-[var(--text-dim)] focus:border-[var(--accent-active)] focus:outline-none"})]}),e.jsxs("label",{className:"mt-4 block text-xs font-medium text-[var(--text-secondary)]",children:["Location",e.jsx("input",{type:"text",value:g,onChange:b=>h(b.target.value),onKeyDown:b=>{b.key==="Enter"&&N()},placeholder:p||"/path/to/new/project","aria-label":"Project location",className:"mt-1 w-full rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-2 font-mono text-sm font-normal text-[var(--text-primary)] placeholder:text-[var(--text-dim)] focus:border-[var(--accent-active)] focus:outline-none"})]}),n&&e.jsx("div",{className:"mt-3",children:e.jsx(_,{text:n,className:"whitespace-pre-line text-xs text-[var(--accent-red)]"})}),e.jsxs("div",{className:"mt-6 flex items-center justify-end gap-2",children:[e.jsx("button",{type:"button",onClick:c,disabled:r,className:"rounded border border-[var(--border-default)] px-4 py-2 text-sm text-[var(--text-secondary)] transition hover:text-[var(--text-primary)] disabled:cursor-not-allowed disabled:opacity-50",children:"Cancel"}),e.jsx("button",{type:"button",onClick:N,disabled:!P,className:"rounded bg-[var(--accent-active)] px-4 py-2 text-sm font-semibold text-[var(--bg-deep)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:r?"Creating…":"Create Project"})]})]})})}const ve=Object.freeze(Object.defineProperty({__proto__:null,CreateProjectModal:R},Symbol.toStringTag,{value:"Module"}));function I({projects:t,onClone:r,cloningRepoUrl:n=null,cloneError:a=null,defaultOpen:c=!1}){const[i,x]=o.useState(c),l=i||n!==null||a!==null;return e.jsxs("div",{className:"mt-6",children:[e.jsxs("button",{type:"button",onClick:()=>x(f=>!f),"aria-expanded":l,className:"flex w-full items-center justify-between rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-3 text-left transition hover:border-[var(--accent-active)]",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--text-primary)]",children:"Open Source Projects"}),e.jsx("span",{className:"text-xs text-[var(--text-dim)]",children:l?"▾":"▸"})]}),l&&e.jsxs(e.Fragment,{children:[e.jsx("p",{className:"mt-2 px-1 text-xs text-[var(--text-muted)]",children:"Clone one of codeyam's showcase apps and open it in the editor — nothing to configure."}),e.jsx("ul",{className:"mt-2 flex flex-col gap-3",children:t.map(f=>e.jsx(U,{project:f,busy:n===f.repoUrl,onClone:r},f.slug))}),a&&e.jsx("div",{role:"alert",className:"mt-3 rounded-lg border border-[var(--accent-red)]/40 bg-[var(--bg-card)] px-4 py-3",children:e.jsxs("div",{className:"flex items-start justify-between gap-3",children:[e.jsx("div",{className:"min-w-0 whitespace-pre-line font-mono text-xs text-[var(--accent-red)]",children:a}),e.jsx(ce,{variant:"subtle",text:a,label:"Copy"})]})})]})]})}function U({project:t,busy:r,onClone:n}){const{name:a,description:c,repoUrl:i,thumbnail:x}=t;return e.jsxs("li",{className:"flex items-center justify-between gap-4 rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-3",children:[e.jsxs("div",{className:"flex min-w-0 items-center gap-3",children:[e.jsx(B,{src:x,name:a,size:"h-24 w-20"}),e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"truncate font-medium text-[var(--text-primary)]",children:a}),e.jsx("div",{className:"mt-1 text-xs text-[var(--text-muted)]",children:c})]})]}),e.jsx("button",{type:"button",disabled:r,onClick:()=>n(i),className:"shrink-0 rounded bg-[var(--accent-active)] px-3 py-1.5 text-sm font-medium text-[var(--bg-deep)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:r?"Cloning…":"Clone & Open"})]})}const je=Object.freeze(Object.defineProperty({__proto__:null,OpenSourceProjectRow:U,OpenSourceProjectsSection:I},Symbol.toStringTag,{value:"Module"})),ue=[{slug:"tabcommand",name:"TabCommand",description:"A Chrome extension dashboard to control your browser (React + Vite, Manifest V3).",repoUrl:"https://github.com/codeyam-ai/tabcommand",thumbnail:"/open-source/tabcommand.png",thumbnailSource:{repo:"tabcommand",scenario:"home-grouped",dimension:"desktop"}},{slug:"codeyam-counter",name:"CodeYam Counter",description:"A native SwiftUI iOS counter app with a shared AppCore SwiftPM library.",repoUrl:"https://github.com/codeyam-ai/codeyam-counter",thumbnail:"/open-source/codeyam-counter.png",thumbnailSource:{repo:"codeyam-counter",scenario:"counter-active-count",dimension:"iphone-16"}},{slug:"el-carot",name:"El Carot",description:"AI-powered tarot readings through a one-of-a-kind deck (Next.js + Prisma + Anthropic API).",repoUrl:"https://github.com/codeyam-ai/el-carot",thumbnail:"/open-source/el-carot.png",thumbnailSource:{repo:"el-carot",scenario:"landing-english",dimension:"mobile"}}];function z(t){const r=t.filter(a=>a.isCurrent),n=t.filter(a=>!a.isCurrent);return[...r,...n]}function H({projects:t,loading:r=!1,error:n=null,openError:a=null,busyPath:c=null,onOpen:i,onStop:x,onScan:l,scanning:f=!1,scanResult:j=null,scanError:y=null,onCreate:m,creating:p=!1,createError:w=null,suggestedNewProjectPath:g=null,bootstrapping:h=!1,onClone:N,cloningRepoUrl:P=null,cloneError:b=null}){const[S,E]=o.useState(!1),{version:k}=le(),L=!r&&!n&&t.length===0,T=!!m&&!(L&&m);return e.jsxs("div",{className:"flex min-h-screen flex-col items-center bg-[var(--bg-deep)] px-6 py-16 text-[var(--text-primary)]",children:[e.jsxs("div",{className:"w-full max-w-2xl",children:[e.jsx(D,{}),T&&m&&e.jsx(K,{onCreate:m,creating:p,createError:w,suggestedNewProjectPath:g}),l&&e.jsx(Y,{onScan:l,scanning:f,scanResult:j,scanError:y}),h&&e.jsx(V,{}),a&&e.jsx(Z,{message:a}),r?e.jsx(Q,{}):n?e.jsx(X,{message:n}):t.length===0?e.jsx(ee,{onScan:l,scanning:f,onCreateFirst:m?()=>E(!0):void 0}):e.jsx("ul",{className:"flex flex-col gap-3",children:z(t).map(O=>e.jsx(J,{project:O,busy:c===O.path,onOpen:i,onStop:x},O.path))}),N&&e.jsx(I,{projects:ue,onClone:N,cloningRepoUrl:P,cloneError:b,defaultOpen:t.length===0}),k&&e.jsxs("div",{"data-testid":"launcher-version",className:"mt-8 text-center text-xs text-[var(--text-dim)]",children:["codeyam-editor v",k]})]}),S&&m&&e.jsx(R,{suggestedNewProjectPath:g,creating:p,createError:w,onCreate:m,onClose:()=>E(!1)})]})}function J({project:t,busy:r,onOpen:n,onStop:a}){const{name:c,path:i,exists:x,running:l,controlPort:f,tests:j,thumbnailUrl:y,isCurrent:m,protected:p}=t;return e.jsxs("li",{className:`flex items-center justify-between gap-4 rounded-lg border bg-[var(--bg-card)] px-4 py-3 ${m?"border-[var(--accent-active)]":"border-[var(--border-default)]"} ${x?"":"opacity-60"}`,children:[e.jsxs("div",{className:"flex min-w-0 items-center gap-3",children:[e.jsx(B,{src:x&&!p?y:null,name:c}),e.jsxs("div",{className:"min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"truncate font-medium text-[var(--text-primary)]",children:c}),m&&e.jsx(q,{}),l&&e.jsx(G,{controlPort:f})]}),e.jsx("div",{className:"truncate text-xs text-[var(--text-dim)]",title:i,children:i}),p?e.jsx("div",{className:"mt-1 text-xs text-[var(--text-dim)]",children:"OS-protected location — not scanned in the background"}):x?e.jsx(xe,{tests:j}):e.jsx("div",{className:"mt-1 text-xs text-[var(--accent-orange)]",children:"Folder missing — moved or deleted"})]})]}),e.jsx(W,{path:i,exists:x,running:l,busy:r,isCurrent:m,onOpen:n,onStop:a})]})}function D(){return e.jsxs("header",{className:"mb-8",children:[e.jsx("h1",{className:"text-2xl font-semibold text-[var(--text-primary)]",children:"Your projects"}),e.jsx("p",{className:"mt-1 text-sm text-[var(--text-muted)]",children:"Pick a project to open in the editor, or stop one that's running."})]})}function K({onCreate:t,creating:r=!1,createError:n=null,suggestedNewProjectPath:a=null,defaultOpen:c=!1}){const[i,x]=o.useState(c),[l,f]=o.useState(""),[j,y]=o.useState(""),m=o.useRef(!1);o.useEffect(()=>{m.current||a&&l===""&&f(a)},[a,l]);const p=h=>{m.current=!0,f(h)};if(!(i||r||n!==null))return e.jsx("div",{className:"mb-3",children:e.jsx("button",{type:"button",onClick:()=>x(!0),className:"text-sm text-[var(--text-secondary)] underline-offset-2 transition hover:text-[var(--text-primary)] hover:underline",children:"+ New project…"})});const g=()=>{const h=l.trim();h.length>0&&!r&&t(h,j.trim())};return e.jsxs("div",{className:"mb-3 rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-4",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"text",value:l,onChange:h=>p(h.target.value),onKeyDown:h=>{h.key==="Enter"&&g()},placeholder:"/path/to/new/project","aria-label":"Folder for the new project",className:"min-w-0 flex-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-dim)] focus:border-[var(--accent-active)] focus:outline-none"}),e.jsx("button",{type:"button",disabled:r||l.trim().length===0,onClick:g,className:"shrink-0 rounded bg-[var(--accent-active)] px-3 py-1.5 text-sm font-medium text-[var(--bg-deep)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:r?"Creating…":"Create"})]}),e.jsx("input",{type:"text",value:j,onChange:h=>y(h.target.value),onKeyDown:h=>{h.key==="Enter"&&g()},placeholder:"Project name (optional)","aria-label":"Project name (optional)",className:"mt-2 w-full rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-dim)] focus:border-[var(--accent-active)] focus:outline-none"}),n&&e.jsx("div",{className:"mt-2",children:e.jsx(_,{text:n,className:"whitespace-pre-line text-xs text-[var(--accent-red)]"})})]})}function Y({onScan:t,scanning:r=!1,scanResult:n=null,scanError:a=null}){const[c,i]=o.useState(!1),[x,l]=o.useState(""),[f,j]=o.useState(!0);if(!(c||r||n!==null||a!==null))return e.jsx("div",{className:"mb-6",children:e.jsx("button",{type:"button",onClick:()=>i(!0),className:"text-sm text-[var(--text-secondary)] underline-offset-2 transition hover:text-[var(--text-primary)] hover:underline",children:"+ Add projects from a folder…"})});const m=()=>{const p=x.trim();p.length>0&&!r&&t(p,f)};return e.jsxs("div",{className:"mb-6 rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-4",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"text",value:x,onChange:p=>l(p.target.value),onKeyDown:p=>{p.key==="Enter"&&m()},placeholder:"/path/to/your/workspace","aria-label":"Folder to scan for projects",className:"min-w-0 flex-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-dim)] focus:border-[var(--accent-active)] focus:outline-none"}),e.jsx("button",{type:"button",disabled:r||x.trim().length===0,onClick:m,className:"shrink-0 rounded bg-[var(--accent-active)] px-3 py-1.5 text-sm font-medium text-[var(--bg-deep)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:r?"Scanning…":"Scan"})]}),e.jsxs("label",{className:"mt-2 flex items-center gap-2 text-xs text-[var(--text-muted)]",children:[e.jsx("input",{type:"checkbox",checked:f,onChange:p=>j(p.target.checked)}),"Remember this folder and re-scan it next time"]}),a&&e.jsx(_,{text:a,className:"text-xs text-[var(--accent-red)]",rowClassName:"mt-2"}),n&&!a&&e.jsxs("div",{className:"mt-2 text-xs text-[var(--accent-green)]",children:["Found ",n.found," project",n.found===1?"":"s",", ",n.added," new"]}),(n==null?void 0:n.note)&&!a&&e.jsx("div",{className:"mt-1 text-xs text-[var(--text-dim)]",children:n.note})]})}function V(){return e.jsx("div",{className:"mb-6 rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-3 text-sm text-[var(--text-muted)]",children:"Looking for your projects…"})}function W({path:t,exists:r,running:n,busy:a,isCurrent:c=!1,onOpen:i,onStop:x}){const l=a?"Opening…":c?"Open this project":n?"Reopen":"Open";return e.jsxs("div",{className:"flex shrink-0 items-center gap-2",children:[n&&r&&e.jsx("button",{type:"button",disabled:a,onClick:()=>x(t),className:"rounded border border-[var(--border-default)] px-3 py-1.5 text-sm text-[var(--text-secondary)] transition hover:border-[var(--accent-red)] hover:text-[var(--accent-red)] disabled:cursor-not-allowed disabled:opacity-50",children:a?"Stopping…":"Stop"}),e.jsx("button",{type:"button",disabled:!r||a,onClick:()=>i(t),className:"rounded bg-[var(--accent-active)] px-3 py-1.5 text-sm font-medium text-[var(--bg-deep)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:l})]})}function q(){return e.jsx("span",{className:"inline-flex items-center rounded-full bg-[var(--accent-active)]/15 px-2 py-0.5 text-xs font-medium text-[var(--accent-active)]",children:"Current"})}function B({src:t,name:r,size:n="h-12 w-16"}){const[a,c]=o.useState(!1);o.useEffect(()=>{c(!1)},[t]);const i=!!t&&!a;return e.jsx("div",{className:`flex ${n} shrink-0 items-center justify-center overflow-hidden rounded border border-[var(--border-subtle)] bg-[var(--bg-surface)]`,children:i?e.jsx("img",{src:t??void 0,alt:`${r} preview`,className:"max-h-full max-w-full object-contain",onError:()=>c(!0)}):e.jsx("div",{className:"flex h-full w-full items-center justify-center text-[10px] text-[var(--text-dim)]",children:"no preview"})})}function xe({tests:t}){return!t||t.total===0?e.jsx("div",{className:"mt-1 text-xs text-[var(--text-dim)]",children:"No tests yet"}):e.jsxs("div",{className:"mt-1 flex items-center gap-2 text-xs",children:[e.jsxs("span",{className:"text-[var(--text-muted)]",children:[t.total," tests"]}),t.passed>0&&e.jsxs("span",{className:"text-[var(--accent-green)]",children:[t.passed," passed"]}),t.failed>0&&e.jsxs("span",{className:"text-[var(--accent-red)]",children:[t.failed," failed"]})]})}function G({controlPort:t}){return e.jsxs("span",{className:"inline-flex items-center gap-1.5 rounded-full bg-[var(--accent-green)]/15 px-2 py-0.5 text-xs text-[var(--accent-green)]",children:[e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-[var(--accent-green)]"}),"running",t?` · :${t}`:""]})}function Q(){return e.jsx("div",{className:"rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-6 text-sm text-[var(--text-muted)]",children:"Loading projects…"})}function X({message:t}){return e.jsx("div",{className:"rounded-lg border border-[var(--accent-red)]/40 bg-[var(--bg-card)] px-4 py-6",children:e.jsx(_,{text:`Couldn't load projects: ${t}`,className:"whitespace-pre-line text-sm text-[var(--accent-red)]"})})}function Z({message:t}){return e.jsx("div",{className:"mb-3 rounded-lg border border-[var(--accent-red)]/40 bg-[var(--bg-card)] px-4 py-3",children:e.jsx(_,{text:t,className:"whitespace-pre-line font-mono text-xs text-[var(--accent-red)]"})})}function ee({onScan:t,scanning:r=!1,onCreateFirst:n}={}){return n?e.jsxs("div",{className:"flex flex-col items-center rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-6 py-16 text-center",children:[e.jsx("h2",{className:"text-xl font-semibold text-[var(--text-primary)]",children:"Create Your First Project"}),e.jsx("p",{className:"mt-2 max-w-sm text-sm text-[var(--text-muted)]",children:"CodeYam will walk you through creating your first project. You just need an idea to get started"}),e.jsx("button",{type:"button",onClick:n,className:"mt-6 rounded bg-[var(--accent-active)] px-5 py-2.5 text-sm font-semibold text-[var(--bg-deep)] transition hover:opacity-90",children:"Create Project"})]}):e.jsx("div",{className:"rounded-lg border border-dashed border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-10 text-center",children:e.jsx("p",{className:"text-sm text-[var(--text-muted)]",children:t?e.jsxs(e.Fragment,{children:["No projects yet."," ",r?"Scanning for projects…":"Add a folder above to scan for projects already on disk, or run"," ",!r&&e.jsx("code",{className:"rounded bg-[var(--bg-input)] px-1.5 py-0.5 text-xs text-[var(--text-secondary)]",children:"codeyam-editor init"}),!r&&" in a project folder."]}):e.jsxs(e.Fragment,{children:["No projects yet. Run"," ",e.jsx("code",{className:"rounded bg-[var(--bg-input)] px-1.5 py-0.5 text-xs text-[var(--text-secondary)]",children:"codeyam-editor init"})," ","in a project folder to get started."]})})})}const ye=Object.freeze(Object.defineProperty({__proto__:null,CurrentBadge:q,ProjectBootstrapBanner:V,ProjectCreateForm:K,ProjectLauncher:H,ProjectLauncherEmpty:ee,ProjectLauncherError:X,ProjectLauncherHeader:D,ProjectLauncherLoading:Q,ProjectOpenError:Z,ProjectRow:J,ProjectRowActions:W,ProjectScanForm:Y,RunningBadge:G,Thumbnail:B,sortCurrentFirst:z},Symbol.toStringTag,{value:"Module"})),me=3e3,$=305e3;async function A(t,r,n){const a=new AbortController,c=setTimeout(()=>a.abort(),n);try{return await fetch(t,{...r,signal:a.signal})}finally{clearTimeout(c)}}function M(t,r){return t instanceof DOMException&&t.name==="AbortError"?"Opening timed out — the project may not be responding.":t instanceof Error?t.message:r}function pe(){const[t,r]=o.useState(null),[n,a]=o.useState(null),[c,i]=o.useState(null),[x,l]=o.useState(null),[f,j]=o.useState(!1),[y,m]=o.useState(null),[p,w]=o.useState(null),[g,h]=o.useState(!1),[N,P]=o.useState(null),[b,S]=o.useState(!1),[E,k]=o.useState(null),[L,T]=o.useState(null),[O,te]=o.useState(null),C=o.useCallback(async()=>{try{const d=await fetch("/api/projects");if(!d.ok)throw new Error(`HTTP ${d.status}`);const s=await d.json();r(Array.isArray(s.projects)?s.projects:[]),te(typeof s.suggestedNewProjectPath=="string"?s.suggestedNewProjectPath:null),a(null)}catch(d){a(d instanceof Error?d.message:"Failed to load projects")}},[]),F=o.useCallback(async()=>{try{const d=await fetch("/api/projects/scan-status");if(!d.ok)return;const u=!!(await d.json()).running;S(v=>(v&&!u&&C(),u))}catch{}},[C]);o.useEffect(()=>{C(),F();const d=setInterval(()=>{document.visibilityState==="visible"&&(C(),F())},me),s=()=>{document.visibilityState==="visible"&&(C(),F())};return document.addEventListener("visibilitychange",s),()=>{clearInterval(d),document.removeEventListener("visibilitychange",s)}},[C,F]);const re=o.useCallback(async(d,s)=>{j(!0),w(null),m(null);try{const u=await fetch("/api/projects/scan",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({root:d,remember:s})}),v=await u.json().catch(()=>({}));if(!u.ok)throw new Error(typeof v.error=="string"?v.error:`HTTP ${u.status}`);r(Array.isArray(v.projects)?v.projects:[]),m({found:v.found??0,added:v.added??0,note:typeof v.note=="string"?v.note:void 0})}catch(u){w(u instanceof Error?u.message:"Scan failed")}finally{j(!1)}},[]),ae=o.useCallback(async(d,s)=>{h(!0),P(null);try{const u=await A("/api/projects/create",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s?{path:d,name:s}:{path:d})},$),v=await u.json().catch(()=>({}));if(!u.ok)throw new Error(typeof v.error=="string"?v.error:`HTTP ${u.status}`);if(typeof v.url=="string"){window.location.href=v.url;return}throw new Error("create did not return a url")}catch(u){P(M(u,"Failed to create project")),h(!1)}},[]),ne=o.useCallback(async d=>{k(d),T(null);try{const s=await A("/api/projects/clone",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({repoUrl:d})},$),u=await s.json().catch(()=>({}));if(!s.ok)throw new Error(typeof u.error=="string"?u.error:`HTTP ${s.status}`);if(typeof u.url=="string"){window.location.href=u.url;return}throw new Error("clone did not return a url")}catch(s){T(M(s,"Failed to clone project")),k(null)}},[]),oe=o.useCallback(async d=>{l(d),i(null);try{const s=await A("/api/projects/open",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:d})},$),u=await s.json().catch(()=>({}));if(!s.ok)throw new Error(typeof u.error=="string"?u.error:`HTTP ${s.status}`);const{url:v}=u;if(typeof v=="string"){window.location.href=v;return}throw new Error("open did not return a url")}catch(s){i(M(s,"Failed to open project")),l(null)}},[]),se=o.useCallback(async d=>{l(d);try{const s=await fetch("/api/projects/stop",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:d})});if(!s.ok)throw new Error(`HTTP ${s.status}`);await C()}catch(s){a(s instanceof Error?s.message:"Failed to stop project")}finally{l(null)}},[C]);return e.jsx(H,{projects:t??[],loading:t===null&&n===null,error:n,openError:c,busyPath:x,onOpen:oe,onStop:se,onScan:re,scanning:f,scanResult:y,scanError:p,onCreate:ae,creating:g,createError:N,suggestedNewProjectPath:O,bootstrapping:b,onClone:ne,cloningRepoUrl:E,cloneError:L})}const ge=Object.freeze(Object.defineProperty({__proto__:null,ProjectLauncherScreen:pe},Symbol.toStringTag,{value:"Module"}));export{R as C,U as O,pe as P,G as R,B as T,ge as _,I as a,Z as b,ee as c,X as d,Q as e,D as f,q as g,V as h,K as i,Y as j,W as k,J as l,H as m,ye as n,je as o,ve as p};

Sorry, the diff of this file is too big to display

import{j as c}from"./markdown-C9tbsVHX.js";import{b as o}from"./react-nrLBr15I.js";import{S as l}from"./ScenarioDataPanel-6qJE-0lA.js";function m({slug:t}){const[r,i]=o.useState(void 0);return o.useEffect(()=>{let n=!1;return fetch(`/api/scenarios/${encodeURIComponent(t)}`).then(e=>e.ok?e.json():null).then(e=>{if(n||!e)return;const a=typeof e=="object"&&e!==null&&"name"in e?String(e.name):void 0;i(a),a&&(document.title=`${a} · data`)}).catch(()=>{}),()=>{n=!0}},[t]),c.jsx(l,{slug:t,scenarioName:r,variant:"fullPage"})}const p=Object.freeze(Object.defineProperty({__proto__:null,ScenarioDataPanelFullPage:m},Symbol.toStringTag,{value:"Module"}));export{m as S,p as _};

Sorry, the diff of this file is too big to display

import{j as i}from"./markdown-C9tbsVHX.js";import{b as c}from"./react-nrLBr15I.js";async function b(t){try{if(typeof navigator<"u"&&navigator.clipboard&&typeof navigator.clipboard.writeText=="function")return await navigator.clipboard.writeText(t),!0}catch{}return h(t)}function h(t){var e;if(typeof document>"u"||!document.body)return!1;const n=document.activeElement,r=document.createElement("textarea");r.value=t,r.setAttribute("readonly",""),r.style.position="fixed",r.style.top="0",r.style.left="-9999px",r.style.opacity="0",r.style.pointerEvents="none",document.body.appendChild(r);let o=!1;try{r.focus(),r.select(),o=document.execCommand("copy")}catch{o=!1}finally{r.remove(),(e=n==null?void 0:n.focus)==null||e.call(n)}return o}const x=async t=>{if(!await b(t))throw new Error("clipboard write failed")};function m(t={}){const{writer:n=x,resetMs:r=2e3}=t,[o,e]=c.useState(!1),[a,d]=c.useState(!1),l=c.useRef(null);c.useEffect(()=>()=>{l.current!==null&&clearTimeout(l.current)},[]);const u=c.useCallback(async s=>{let f=!0;try{await n(s)}catch{f=!1}return e(f),d(!f),l.current!==null&&clearTimeout(l.current),l.current=setTimeout(()=>{e(!1),d(!1),l.current=null},r),f},[n,r]);return{copied:o,failed:a,copy:u}}const v={danger:"shrink-0 px-3 py-1.5 text-xs font-medium rounded-md bg-red-600 hover:bg-red-700 disabled:bg-red-300 disabled:cursor-not-allowed text-white transition-colors",subtle:"shrink-0 px-2 py-1 text-xs font-medium rounded-md border border-[var(--border-default)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors",icon:"shrink-0 inline-flex items-center justify-center h-5 w-5 rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors"};function y({text:t,label:n="Copy",copiedLabel:r="Copied!",failedLabel:o="Copy blocked",variant:e="danger",ariaLabel:a="Copy error message",writer:d,className:l}){const{copied:u,failed:s,copy:f}=m({writer:d}),p=e==="icon";return i.jsx("button",{type:"button",onClick:()=>{f(t)},disabled:t.length===0,title:p?s?o:n:void 0,"aria-label":p?a:void 0,className:l??v[e],children:p?g(u,s):s?o:u?r:n})}function g(t,n){return n?i.jsx("span",{"aria-hidden":"true",children:"!"}):t?i.jsx("span",{"aria-hidden":"true",children:"✓"}):i.jsxs("svg",{"aria-hidden":"true",viewBox:"0 0 16 16",className:"h-3.5 w-3.5",fill:"none",stroke:"currentColor",strokeWidth:"1.5",children:[i.jsx("rect",{x:"5.5",y:"5.5",width:"8",height:"8",rx:"1.5"}),i.jsx("path",{d:"M10.5 3.5a1.5 1.5 0 0 0-1.5-1.5H4a1.5 1.5 0 0 0-1.5 1.5v5A1.5 1.5 0 0 0 4 10"})]})}const k=Object.freeze(Object.defineProperty({__proto__:null,CopyButton:y},Symbol.toStringTag,{value:"Module"}));function C({text:t,copyText:n,className:r="text-xs text-[var(--text-danger)]",rowClassName:o="",testId:e,writer:a}){return t?i.jsxs("div",{role:"alert","data-testid":e,className:`flex items-start gap-1.5 min-w-0 ${o}`,children:[i.jsx("span",{className:`min-w-0 ${r}`,children:t}),i.jsx(y,{variant:"icon",text:n??t,writer:a})]}):null}const S=Object.freeze(Object.defineProperty({__proto__:null,CopyableErrorText:C},Symbol.toStringTag,{value:"Module"}));function j(t){return t==="posix"||t==="powershell"?t:null}function T(){const[t,n]=c.useState({projectDir:null,inContainer:null,reachableUrl:null,version:null,shellDialect:null,loaded:!1});return c.useEffect(()=>{let r=!1;return fetch("/api/codeyam-server-identity").then(o=>o.ok?o.json():null).then(o=>{if(r)return;const e=o&&typeof o=="object"?o:null,a=e&&typeof e.projectDir=="string"?e.projectDir:null,d=e&&typeof e.inContainer=="boolean"?e.inContainer:null,l=e&&typeof e.reachableUrl=="string"?e.reachableUrl:null,u=e&&typeof e.version=="string"?e.version:null,s=j(e==null?void 0:e.shellDialect);n({projectDir:a,inContainer:d,reachableUrl:l,version:u,shellDialect:s,loaded:!0})}).catch(()=>{r||n({projectDir:null,inContainer:null,reachableUrl:null,version:null,shellDialect:null,loaded:!0})}),()=>{r=!0}},[]),t}export{C,S as _,y as a,k as b,m as c,b as d,T as u};
+59
-12

@@ -102,6 +102,9 @@ 'use strict';

// ingress is still wiring; `verifying` is the residue: no failure signature
// matched and nothing durable proves the VM is dead. Both render a calm card and
// neither may use the word "failed". Consumed by holdConvergingBadge and by
// deriveStatus (server.js) to pick the matching calm status.
const CALM_ADD_CATEGORIES = new Set(['converging', 'verifying']);
// matched and nothing durable proves the VM is dead; `access-pending` is a VM that
// is up and rostered but whose IAP policy has no accessors YET, because its
// detached wiring tail — which applies the grant last — is still running. All
// three render a calm card and none may use the word "failed". Consumed by
// holdConvergingBadge and by deriveStatus (server.js) to pick the matching calm
// status.
const CALM_ADD_CATEGORIES = new Set(['converging', 'verifying', 'access-pending']);

@@ -161,2 +164,13 @@ // Is a non-zero add outcome CORROBORATED by a durable fact — i.e. do we have

// Lift the `Next valid action:` remedy out of a BLOCKED reason so a classified
// card's recovery line stays the SAME text the producing check already emitted,
// instead of a second copy of the same instructions that can silently drift from
// it. Returns '' when the text carries no remedy, in which case the caller's
// static copy stands on its own.
function nextValidActionRemedy(text) {
const lines = String(text || '').split('\n');
const i = lines.findIndex((l) => /^\s*Next valid action:/.test(l));
return i === -1 ? '' : lines.slice(i).join('\n').trim();
}
// Build a verdict, stamping `retryable` from the single-source-of-truth Set so it

@@ -184,2 +198,5 @@ // can never drift from the category.

// wiringDeadlineBlown did the add-time wire tail drop its failed marker?
// wiringInFlight is that tail still RUNNING? Only the IAP read-back arm
// reads it, to tell "no accessors yet" from "no accessors,
// ever" — the grant is the tail's last step.
//

@@ -194,3 +211,3 @@ // Returns { category, headline, recovery, retryable, files? }.

// operator would lose the safe one-click recovery.
function classifyAddFailure({ logTail, exitCode, instanceAlive, containerHealthy, wiringDeadlineBlown } = {}) {
function classifyAddFailure({ logTail, exitCode, instanceAlive, containerHealthy, wiringDeadlineBlown, wiringInFlight } = {}) {
const log = String(logTail || '');

@@ -277,3 +294,33 @@

// 8. converging — the add reached the early-roster marker (`CY_ROSTER`): the
// 8. The post-add IAP read-back verdicts. Unlike every signature above, this one
// is not produced by the provision transcript at all — it is the BLOCKED
// reason iapReadbackOutcome hands to annotateAddFailure, which is what
// stops a read-back failure from being the one add failure that reaches the
// card with no category at all (and so no differentiated headline, no
// recovery, no guarded Retry, and no chance for holdConvergingBadge to hold it
// calm). Tested AFTER all seven hard-failure signatures so a proven failure
// always wins if both texts are ever present, and BEFORE converging/verifying
// because it is strictly more specific than either.
if (/IAP policy authorizes no human/i.test(log)) {
const remedy = nextValidActionRemedy(log);
// 8a. access-pending — no accessors YET. The grant is the LAST step of the
// detached wire-add-tail, so while that tail is alive an empty policy is
// the expected mid-flight reading, not a verdict. Calm: this is the state
// that used to render a red "Add failed" on healthy VM-4/VM-7 minutes
// before their grant step had even run (2026-08-11).
if (wiringInFlight) {
return buildFailureVerdict('access-pending', 'Access not granted yet',
'The VM is up and rostered, but its IAP policy has no human accessors yet — the add\'s background wiring applies the grant as its last step and is still running. This resolves on its own once that tail finishes; no action is needed unless it persists past the wiring deadline.');
}
// 8b. iap-unauthorized — the wiring is done and the policy STILL authorizes
// nobody, so the public URL returns "Error code 51" for every human. This
// is the 2026-08-09 case the read-back exists to catch (one step granted
// the operator, the next revoked him) and it is a genuine hard failure.
// Not retryable: the VM exists and is fine — re-adding it would duplicate
// a live box when what is missing is one grant.
return buildFailureVerdict('iap-unauthorized', 'No one can reach this VM',
`The VM is up but its IAP policy authorizes no human, so the public URL returns "Error code 51" for everyone. Its background wiring is no longer running, so this will not resolve on its own — grant access to fix it. Do NOT destroy or re-add this VM.${remedy ? `\n\n${remedy}` : ''}`);
}
// 9. converging — the add reached the early-roster marker (`CY_ROSTER`): the
// container gate passed, the instance is real, and the VM is rostered, yet it

@@ -288,5 +335,5 @@ // exited non-zero WITHOUT matching any specific hard-failure signature above.

// the VM exists and its detached wiring owns finishing it, so a one-click
// retry could duplicate/orphan a live box. Tested LAST so a hard-failure
// signature (all printed pre-roster) always wins — `converging` can never
// shadow a proven failure.
// retry could duplicate/orphan a live box. Tested after every hard-failure
// signature (all printed pre-roster) so a proven failure always wins —
// `converging` can never shadow one.
if (transcriptRostered(log)) {

@@ -297,3 +344,3 @@ return buildFailureVerdict('converging', 'Add still converging',

// 9. The residue: no failure signature above matched. The polarity here is
// 10. The residue: no failure signature above matched. The polarity here is
// INVERTED from the original design. It used to read "no recognized success

@@ -308,3 +355,3 @@ // signature ⇒ failed", which made red the DEFAULT for "we don't know" — so a

//
// 9a. unknown — a non-zero exit CORROBORATED by a durable fact (see
// 10a. unknown — a non-zero exit CORROBORATED by a durable fact (see
// addFailureCorroborated). The box is provably dead or provably never

@@ -318,3 +365,3 @@ // came up, so the loud, terminal escalation is warranted and unchanged.

// 9b. verifying — the honest default. No failure signature, and nothing
// 10b. verifying — the honest default. No failure signature, and nothing
// durable proves this VM is dead: it may still be provisioning or

@@ -321,0 +368,0 @@ // wiring, and the ingress can keep propagating for minutes after the

@@ -340,2 +340,128 @@ 'use strict';

// May the PERIODIC stranded-reconcile pass drive a recover/roster probe for this
// VM, or is a provision already running that a second cloud:up would collide
// with? This is the guard the boot re-attach path has always had and the
// stranded path never did — the 2026-08-07 asymmetry that destroyed VM-4/VM-5.
//
// On that day an `add` job badge read `error` while its detached `cloud:up`
// child was alive and healthy (a source-build bootstrap runs ~40 minutes;
// `npm install` alone took 25m). `pollStrandedInstances` builds `activeAddNs`
// from BADGE state and deliberately excludes an `error` add so the bounded
// bootstrap-recovery budget can retry on a timer — so the VM read
// running-but-unrostered, crossed STRANDED_ADOPT_THRESHOLD in ~3 minutes, and
// the pass launched a SECOND cloud:up on top of the live one. The two raced on
// `docker compose up -d`, the loser hit `Conflict. The container name
// "/codeyam-editor-editor-1" is already in use`, and its abort handler deleted
// the instance. The badge is a derived, lossy signal that was wrong for 30
// minutes straight; LIVENESS is the ground truth, so this gate reads exactly
// what `reattachAdd` reads — the exit marker plus a `kill -0` of the recorded
// pid — and composes `reattachVerdict`/`reattachNextStep` rather than forking a
// parallel matrix.
//
// 'watch' no marker + a live child → the provision is genuinely in flight.
// Re-attach and watch (mirroring
// runStartupReap); NEVER provision.
// 'defer' no marker, no live child, and no pid was EVER recorded, inside the
// ambiguity window → we cannot tell whether a child is
// running. Do nothing this tick.
// 'probe' everything else → existing behavior: drive the gcloud
// probe (reconcileViaGcloud →
// reapVerdict).
//
// The 'defer' rung exists because `childAlive` is FALSE-NEGATIVE-PRONE on one
// specific path: when `strandedReconcileMeta` finds no sidecar it reconstructs
// cfg from launch history and persists `{ cfg }` with NO pid, so `isPidAlive`
// reads false for a VM whose child may well be alive — and, worse, that pid-less
// sidecar is what every later tick reads back, blinding the guard permanently.
// `pidRecorded` separates "we knew the pid and the process is gone" (real
// evidence → probe) from "we never had a pid" (absence of evidence → defer).
// Erring toward NOT provisioning is the asymmetric-cost choice: waiting one more
// 60s tick costs nothing, being wrong costs a destroyed VM.
//
// The window is what keeps 'defer' from becoming a permanent refusal that would
// break stranded adoption outright: once more than `ambiguityWindowMs` has
// elapsed since we first saw the pid-less sidecar, no plausible provision child
// is still running, so the probe proceeds and the self-heal path is preserved.
// An unknown/unstamped elapsed is treated as "just noticed" (defer), again the
// safe direction; a caller that supplies no window at all opts out entirely and
// keeps the old probe-always behavior.
//
// Pure, so the whole matrix is testable without a VM: server.js maps the verdict
// to its side effect.
function strandedRecoverGate({
markerPresent,
exitCode,
childAlive,
pidRecorded,
sinceMs,
ambiguityWindowMs,
} = {}) {
// A live child is a live child — reuse the existing classifier so 'running'
// keeps meaning exactly what the boot path means by it.
if (reattachNextStep(reattachVerdict({ markerPresent, exitCode, childAlive })) === 'watch') {
return 'watch';
}
// A marker is durable proof the child reached its EXIT trap: nothing is in
// flight, so the probe is safe. (Which probe outcome follows — roster,
// recover-bootstrap, surface-error — stays reapVerdict's call, not ours.)
if (markerPresent) return 'probe';
// We knew the pid and `kill -0` says it is gone: real evidence, so probe.
if (pidRecorded) return 'probe';
const window = Number(ambiguityWindowMs);
if (!Number.isFinite(window) || window <= 0) return 'probe'; // caller opted out
const elapsed = Number(sinceMs);
if (!Number.isFinite(elapsed)) return 'defer'; // unstamped — treat as just-noticed
return elapsed < window ? 'defer' : 'probe';
}
// Is an `add` genuinely IN FLIGHT for this VM right now?
//
// Extracted from pollVanishedInstances, where the inline expression was
// `addPending.has(n) || !!(state.jobs[n] && state.jobs[n].action === 'add')` —
// whose second clause is also true for a TERMINAL (`done` / `error`) add job.
// That permanently marks the VM busy, so `reconcileRosteredVm` can never return
// 'cleanup' and a rostered VM whose instance is gone can never be auto-reaped.
// Live evidence from the same 2026-08-07 incident: VM-5's instance was deleted
// at ~12:46 and its card logged `vanish-reconcile: VM-5 unreachable …` once a
// minute for 50 consecutive polls, only clearing after a manual `clear-badge`
// deleted the terminal job record.
//
// The on-disk sidecar clause is already correct and is preserved: a sidecar is
// removed on the add's success path, so its presence really does mean in-flight.
// The job clause now filters on job STATE the same way `activeAddNs` and
// `pollStartupProbes` do. Pure so the caller's computation is directly
// assertable — the bug was never in `reconcileRosteredVm`'s contract, it was in
// what this expression fed it.
const IN_FLIGHT_ADD_STATES = ['running', 'queued', 'recovering'];
function hasInFlightAdd({ sidecarPending, job } = {}) {
if (sidecarPending) return true;
if (!job || job.action !== 'add') return false;
return IN_FLIGHT_ADD_STATES.includes(job.state);
}
// WHICH of reconcileRosteredVm's two distinct 'leave' causes fired, as a tag the
// caller renders. Pure so the distinction is directly assertable.
//
// 'leave' is returned both when the GCP probe was INCONCLUSIVE (exists null /
// undefined — absence of evidence, never act) and when the instance is
// CONFIRMED gone but the VM is busy. The old single log line conflated them into
// a self-contradiction — it announced "instance-gone unconfirmed (exists=false)"
// when exists=false IS the confirmation — which cost hours of code-reading
// during the 2026-08-07 incident to work out which branch was actually firing.
//
// 'probe-inconclusive' exists is null/undefined — retry next tick
// 'live-session' instance confirmed gone but a session is live
// 'in-flight-add' instance confirmed gone but an add is in flight
// null not a 'leave' case at all
//
// Mirrors reconcileRosteredVm's own precedence (exists-uncertainty first, then
// hasSession, then hasPendingAdd) so the tag can never disagree with the verdict.
function vanishLeaveReason({ exists, hasSession, hasPendingAdd } = {}) {
if (exists === null || exists === undefined) return 'probe-inconclusive';
if (hasSession) return 'live-session';
if (hasPendingAdd) return 'in-flight-add';
return null;
}
// Anti-loop budget for the `recover-bootstrap` path. Pure: given the count of

@@ -541,2 +667,5 @@ // recoveries ALREADY persisted on the sidecar, return the next attempt number,

reattachNextStep,
strandedRecoverGate,
hasInFlightAdd,
vanishLeaveReason,
};

@@ -293,4 +293,12 @@ 'use strict';

*
* `detachedWiringCount` — when supplied — appends `tails=N`, which is what makes
* the RSS/cgroup gap ATTRIBUTABLE rather than merely visible. On 2026-08-11 the
* cgroup read 2154MB against a 96MB RSS and the whole diagnosis was "those ~2GB
* are this process's own transient add children, not a leak". `RSS=96MB
* cgroup=2154MB tails=4` reads as a batch draining on its own; `RSS=1300MB
* cgroup=1400MB tails=0` reads as a genuine leak. Omitted (the pre-existing
* callers, and any host with no tail accounting) prints exactly as before.
*
* Pure; no I/O. */
function formatMemoryText({ rssBytes, memoryCurrent, memoryHigh } = {}) {
function formatMemoryText({ rssBytes, memoryCurrent, memoryHigh, detachedWiringCount } = {}) {
const mb = (b) => (Number(b) / 1024 / 1024).toFixed(0);

@@ -312,2 +320,4 @@ const current = Number(memoryCurrent);

}
const tails = Number(detachedWiringCount);
if (Number.isFinite(tails) && tails >= 0) parts.push(`tails=${tails}`);
return parts.join(' ');

@@ -328,7 +338,22 @@ }

*
* Pure; no I/O — the caller passes state.jobs / state.dashboardUpdate. */
function hasJobInFlight({ jobs, dashboardUpdate } = {}) {
* `detachedWiringCount` is the THIRD source of busy, and the one this guard was
* blind to: an Add settles at container-healthy while its detached wire-add-tail
* keeps spawning gcloud children for another 5-10 min in the SAME cgroup. Judging
* "safe to restart" off `state.jobs` alone reported no job in flight while that
* work was live — on 2026-08-11 the escalation crossed the restart threshold at
* 86% of memory.high and the only thing that held it back was an unrelated job
* happening to still be `running`. Had it settled thirty seconds earlier the
* dashboard would have restarted itself mid-batch and orphaned four adds, which
* is precisely the failure class the detached-provision work exists to prevent.
* Omitting the key behaves exactly as before, so every current caller is
* unaffected.
*
* Pure; no I/O — the caller passes state.jobs / state.dashboardUpdate and the
* live-tail count. */
function hasJobInFlight({ jobs, dashboardUpdate, detachedWiringCount } = {}) {
const busy = (j) => !!j && (j.state === 'running' || j.state === 'recovering');
const anyJob = Object.values((jobs && typeof jobs === 'object') ? jobs : {}).some(busy);
return anyJob || !!(dashboardUpdate && dashboardUpdate.state === 'running');
const tails = Number(detachedWiringCount);
const anyTail = Number.isFinite(tails) && tails > 0;
return anyJob || anyTail || !!(dashboardUpdate && dashboardUpdate.state === 'running');
}

@@ -335,0 +360,0 @@

@@ -129,2 +129,138 @@ 'use strict';

// Consecutive failed reads before the panel stops trusting its mirror. At the
// default QUEUE_POLL_MS of 5000 this is ~15s of sustained failure — long enough
// that the ordinary 20s-timeout blip the next tick clears only ever reaches
// 'degraded', short enough that a real credential outage is flagged in seconds.
// Named rather than inlined so the verdict and the tooltip copy read the same
// number from one place.
const QUEUE_STALE_AFTER_FAILURES = 3;
/** Merge one commit-queue poll result over the previous memo.
*
* THE RULE: a failed read RETAINS the previous `entries` AND the previous
* `readAt`, and records `error`. Retaining the entries is deliberate — a stale
* order beats a blank panel for an operator mid-triage. Leaving `readAt`
* untouched is what makes the staleness legible: it is the ONLY signal that
* distinguishes a frozen view from a live one.
*
* Advancing `readAt` on a failure is precisely the bug this exists to prevent.
* On 2026-08-14 /etc/codeyam/git-credentials on the operator host went to 0
* bytes and every origin git operation began failing; the queue panel went on
* naming VM-5 as HEAD for 3.5 hours after VM-5 had released, while the real
* queue was VM-2 -> VM-8 -> VM-4. Nothing on the page distinguished it from
* live. This is the same lesson mergeDashboardRevision records from
* 2026-07-31, applied to the second poller.
*
* A previous `readAt` of null (no successful read has EVER happened) stays
* null and is not coerced to a timestamp — "never read" and "read a moment
* ago" must stay distinguishable, the same reason mergeDashboardRevision
* refuses to push a null `behind` through Number().
*
* Success is the absence of `error`. Pure; no I/O. */
function mergeQueuePoll({ prev, entries, readAt, error } = {}) {
const base = (prev && typeof prev === 'object') ? prev : {};
const str = (v) => ((typeof v === 'string' && v) ? v : null);
const prevEntries = Array.isArray(base.entries) ? base.entries : [];
const prevReadAt = str(base.readAt);
const prevFailures = (typeof base.consecutiveFailures === 'number'
&& Number.isFinite(base.consecutiveFailures) && base.consecutiveFailures >= 0)
? base.consecutiveFailures : 0;
const err = str(error);
if (err) {
return {
entries: prevEntries,
readAt: prevReadAt,
error: err,
consecutiveFailures: prevFailures + 1,
};
}
return {
entries: Array.isArray(entries) ? entries : [],
readAt: str(readAt) || prevReadAt,
error: null,
consecutiveFailures: 0,
};
}
/** What does the current queue memo mean for the operator?
*
* Levels:
* 'fresh' — the last read succeeded. Render exactly as before; every
* control stays live.
* 'degraded' — erroring, but under QUEUE_STALE_AFTER_FAILURES consecutive
* failures. A blip. Noted quietly, controls stay live — a
* single 20s-timeout hiccup that the next tick clears must not
* cry wolf or lock the operator out of their own queue.
* 'stale' — erroring at or past the threshold. The mirror is not to be
* trusted and writes computed against it are blocked.
* 'unknown' — erroring and NO read has ever succeeded (the boot window with
* broken credentials). Worth saying out loud: an empty-looking
* queue here means "we cannot see the queue", not "go".
*
* AGE IS DELIBERATELY NOT AN INPUT. A genuinely quiet queue is not a broken
* one, so escalation keys on consecutive read FAILURES only; age is carried by
* projectQueueFreshness purely to phrase the message ("last read 3h 12m ago").
* An hours-old `readAt` with no error is 'fresh' and must stay that way. Taking
* a clock here would imply otherwise, so the signature omits one — the same
* take-only-what-you-use shape as classifyDashboardStaleness.
*
* Pure; no I/O. */
function classifyQueueFreshness({ readAt, error, consecutiveFailures } = {}) {
if (!(typeof error === 'string' && error)) return 'fresh';
if (!(typeof readAt === 'string' && readAt)) return 'unknown';
const failures = (typeof consecutiveFailures === 'number' && Number.isFinite(consecutiveFailures))
? consecutiveFailures : 0;
return failures >= QUEUE_STALE_AFTER_FAILURES ? 'stale' : 'degraded';
}
/** The `/data` view of the queue memo, with the freshness VERDICT resolved
* server-side.
*
* The panel is then a pure renderer. Two consumers re-deriving "is this stale"
* from a raw timestamp would drift, and `public/index.html` is an inline
* browser script with no module boundary — any decision left in it could only
* ever be source-scan tested, which is how it ships bugs green. Same split, and
* same reason, as projectDashboardRevision.
*
* `now` is injected (epoch ms) rather than read from the clock so the
* projection stays pure and its tests deterministic. `ageSec` is null when no
* successful read has happened or no clock was supplied.
*
* Pure; no I/O. */
function projectQueueFreshness(memo = {}, now = null) {
const at = (typeof memo.readAt === 'string' && memo.readAt) ? Date.parse(memo.readAt) : NaN;
const clock = (typeof now === 'number' && Number.isFinite(now)) ? now : null;
const ageSec = (Number.isFinite(at) && clock !== null)
? Math.max(0, Math.floor((clock - at) / 1000))
: null;
return { ...memo, level: classifyQueueFreshness(memo), ageSec };
}
/** The operator-facing reason a queue WRITE must be refused, or null when writes
* are allowed.
*
* A write computed against a mirror known to be frozen is never the right
* action, and the silent-no-op outcome is worse than a refusal: a Release
* fires at a uuid that no longer holds the head, reports "released head
* bb65addf", changes nothing, and leaves the real head held. A reorder is
* worse still — computeReorderTargets validates the posted order against the
* very mirror that is wrong, so a doomed reorder looks perfectly valid.
*
* The verbatim git error is interpolated rather than summarised to "poll
* failed". On 2026-08-14 the entire recovery was legible from one line —
* `could not read Username for 'https://github.com'` — and truncating it would
* have cost the same 3.5 hours.
*
* Lives here rather than in server.js so the refusal COPY is directly
* assertable: server.js has top-level side effects on require, so a message
* built there could only ever be source-scan tested. Pure; no I/O. */
function queueWriteBlockReason(memo = {}) {
const level = classifyQueueFreshness(memo);
if (level !== 'stale' && level !== 'unknown') return null;
const why = (typeof memo.error === 'string' && memo.error) ? ` (${memo.error})` : '';
return level === 'unknown'
? `commit queue has never been read successfully${why} — cannot act on an unknown queue`
: `commit queue read is failing${why} — the displayed order cannot be trusted; fix the read before changing the queue`;
}
module.exports = {

@@ -135,2 +271,7 @@ decideAutoReleaseGoneHead,

reconcileQueueDraft,
mergeQueuePoll,
classifyQueueFreshness,
projectQueueFreshness,
queueWriteBlockReason,
QUEUE_STALE_AFTER_FAILURES,
};

@@ -218,2 +218,157 @@ 'use strict';

/** The BLOCKED/Next-valid-action refusal for an Add that would create an
* IAP-gated VM authorizing NOBODY — the front-door counterpart to
* `noDefaultOperatorDegradedReasons`, which can only warn AFTER the VM exists.
* Fires only in LB mode (`lbMode` — a fleet domain is configured, so the VM's
* public URL is IAP-gated) on a numbered fleet VM (`vmNumber != null`) when the
* intended principal set is empty (`intendedCount === 0`): no `--vm-access`
* collaborators AND no default operator from any of the three tiers. The check
* depends only on env + config, never on GCP state, so the caller can run it in
* the add's preflight and refuse before an instance exists — provisioning a
* machine nobody can open, then revoking the slot's prior tenant, is what
* happened to VMs 4/5/6 on 2026-08-09.
*
* Names all THREE resolution tiers because which one to populate depends on the
* host: an automation-SA session (the dashboard) can only be fixed by env or
* cloud.json, while a human gcloud session resolves tier 3 on its own and never
* reaches this refusal. Returns null when the Add is fine (so the call site is a
* bare `if (msg) throw`), else the two-line recovery pair. Pure — the resolve
* happens at the call site and only its COUNT is passed in. */
function unauthorizableAddRefusal({ lbMode, vmNumber, intendedCount } = {}) {
if (!lbMode || vmNumber == null || intendedCount !== 0) return null;
return (
`BLOCKED: this Add would create an IAP-gated VM (VM-${vmNumber}) that authorizes NOBODY.\n`
+ ` ${DEFAULT_OPERATOR_ENV} is unset, .codeyam/cloud.json has no \`vmAccessDefault\`,\n`
+ ' and the active gcloud identity is a service account (skipped by design).\n'
+ 'Next valid action: add `"vmAccessDefault": "user:you@example.com"` to\n'
+ ' .codeyam/cloud.json (durable, no env re-bake), or pass --vm-access.'
);
}
/** Is this IAM principal a service account — i.e. authorizes no HUMAN? Matches
* both the typed form gcloud returns on a policy read (`serviceAccount:x@…`) and
* a bare `*.gserviceaccount.com` address, so the read-back below cannot be
* satisfied by the automation SA that `iap-access.sh` unions into every grant.
* Read-path semantics, like parseProjectMembers: it inspects whatever IAM
* already holds rather than validating a write, so it never throws. */
function isServiceAccountPrincipal(principal) {
const p = String(principal == null ? '' : principal).trim().toLowerCase();
if (p === '') return false;
return p.startsWith('serviceaccount:') || /gserviceaccount\.com$/.test(p);
}
/** Does a finished job warrant the post-add IAP read-back at all? Only a SUCCESSFUL
* `add` in LB mode has an IAP-gated public URL whose reachability is worth
* proving — a failed job, another action, or legacy quick-tunnel mode each have
* nothing to read back, and flagging them would bury a real failure under a wrong
* one. Split out so the caller can skip the (gcloud) policy read entirely on the
* cheap gates, and so that decision is stated in exactly one place instead of
* being duplicated at the call site and inside the verdict below.
*
* `wiringInFlight` is the fourth gate and the one that fixes the 2026-08-11 race:
* the IAP grant is the LAST step of the detached `wire-add-tail` that the add path
* spawns, and that tail runs for another 5–10 minutes after the Add settles at
* container-healthy. A read-back fired while the tail is alive is therefore racing
* the very step it checks for — it is GUARANTEED to observe zero accessor bindings
* on a perfectly healthy VM, which is exactly how healthy VM-4 and VM-7 were failed
* with "authorizes no human". A live tail means the grant provably has not run yet,
* so there is nothing to read back. Defaults false, so an omitted fact behaves
* exactly as before this input existed. */
function shouldReadBackIapAccess({ ok, action, lbMode, wiringInFlight } = {}) {
return !!ok && action === 'add' && !!lbMode && !wiringInFlight;
}
/** Is `job` still the job a post-add read-back was started for? The policy read is
* async, so a newer job can replace it mid-flight; downgrading THAT job would
* report a stale verdict against work it never describes. */
function readbackAppliesToJob(job) {
return !!job && job.action === 'add' && job.state === 'done';
}
/** Post-add read-back verdict: after a successful LB-mode Add, does VM-N's LIVE
* IAP policy authorize at least one non-service-account principal? This is the
* only thing that PROVES the end state is reachable — every other signal in the
* add path reports intent (what we meant to grant), and on 2026-08-09 intent and
* outcome disagreed: the job reported `done` while the URL authorized nobody.
*
* `iapMembers` is the union of accessor members across surfaces
* (`aggregateIapMembers(...).all`); `null` means a surface's read ERRORED, the
* same absence-of-evidence posture aggregateIapMembers itself takes.
*
* Reported as a discriminated outcome, so a caller can tell the four cases apart
* instead of collapsing three of them onto `null`:
*
* 'skipped' the check does not apply (not an ok LB-mode add, or the add's
* detached wiring is still in flight). Nothing to do.
* 'undetermined' a surface's policy read ERRORED (`iapMembers` is not an array).
* This is absence of evidence, NOT a pass: the caller must
* re-arm and re-check rather than concluding. Collapsing it onto
* "authorized" is how VM-5 settled `done` on 2026-08-11 with its
* access never actually proven — a flaky gcloud call under load
* silently skipped the only check that proves reachability.
* 'authorized' at least one non-service-account principal holds an accessor
* binding. The end state is provably reachable by a human.
* 'unauthorized' the policy was read and authorizes no human. `reason` carries
* the BLOCKED / `Next valid action:` remedy.
*
* `reason` is non-null only for 'unauthorized'. Pure — the gcloud read is the
* caller's. */
function iapReadbackOutcome({ ok, action, lbMode, wiringInFlight, iapMembers, vmNumber } = {}) {
if (!shouldReadBackIapAccess({ ok, action, lbMode, wiringInFlight })) {
return { outcome: 'skipped', reason: null };
}
if (!Array.isArray(iapMembers)) return { outcome: 'undetermined', reason: null };
if (iapMembers.some((p) => !isServiceAccountPrincipal(p))) return { outcome: 'authorized', reason: null };
const held = iapMembers.length > 0 ? iapMembers.join(', ') : '(none)';
return {
outcome: 'unauthorized',
reason: (
`BLOCKED: VM-${vmNumber} is up but its IAP policy authorizes no human — the public URL\n`
+ ` returns "Error code 51" for everyone. Accessor bindings held: ${held}.\n`
+ `Next valid action: configure a default operator (${DEFAULT_OPERATOR_ENV} or\n`
+ ' .codeyam/cloud.json `vmAccessDefault`), then run\n'
+ ` VM_ACCESS=user:you@example.com bash scripts/gcp/iap-access.sh ${vmNumber} grant`
),
};
}
/** Timing policy for the DEFERRED post-add read-back: on this attempt, is there
* anything worth reading yet? Separate from shouldReadBackIapAccess, which asks
* whether the check applies AT ALL — this asks whether now is the moment.
*
* wiringInFlight the add's detached wiring tail is still running, and the IAP
* grant is its LAST step. Reading now can only observe the
* grant not having happened yet, so wait.
* wiringFailed the tail dropped its failed marker. A blown deadline is
* EVIDENCE, not a reason to keep waiting: read now and let a
* genuinely unauthorized VM escalate instead of waiting forever.
* budgetSpent the caller's total wait has elapsed. Read regardless — a
* crashed tail that never unlinked its marker must not defer the
* check indefinitely, and the classifier still renders the card
* calm while the wiring reads live.
*
* Returns 'defer' (re-arm and ask again later) or 'read' (do the policy read). */
function iapReadbackDeferral({ wiringInFlight, wiringFailed, budgetSpent } = {}) {
if (budgetSpent || wiringFailed) return 'read';
return wiringInFlight ? 'defer' : 'read';
}
/** What should the caller DO with an iapReadbackOutcome? Pure so the one rule that
* cost us a silently-skipped check is stated and tested in one place:
*
* An 'undetermined' read (a gcloud call that errored under load) must DEFER, never
* settle. Treating it as a pass is how VM-5 reached `done` on 2026-08-11 with its
* reachability never actually proven — the check that exists to catch an
* unreachable VM was skipped entirely by one flaky call. It settles only once the
* wait budget is spent, and even then it settles rather than failing: absence of
* evidence must not manufacture a failed Add either.
*
* Returns 'fail' (downgrade the job with the reason), 'defer' (re-arm and
* re-check), or 'settle' (leave the job alone). */
function iapReadbackNextAction({ outcome, budgetSpent } = {}) {
if (outcome === 'unauthorized') return 'fail';
if (outcome === 'undetermined') return budgetSpent ? 'settle' : 'defer';
return 'settle';
}
/** Union the default-operator principal(s) into an access list, deduped and

@@ -395,2 +550,9 @@ * order-preserving (explicit collaborators first, the operator appended if not

noDefaultOperatorDegradedReasons,
unauthorizableAddRefusal,
isServiceAccountPrincipal,
shouldReadBackIapAccess,
readbackAppliesToJob,
iapReadbackOutcome,
iapReadbackDeferral,
iapReadbackNextAction,
withDefaultOperator,

@@ -397,0 +559,0 @@ parseProjectMembers,

@@ -81,3 +81,11 @@ 'use strict';

* `down` so the matching host rule is removed); omit it to keep the numeric
* `vm-<N>` host. */
* `vm-<N>` host.
*
* `vmAccess` (an array or comma-separated string, `up` only) becomes VM_ACCESS —
* the Add's INTENDED principal set, which the script's `apply_default_iap_access`
* grants instead of reading the per-VM sidecar. Passing it is what stops the
* ingress path granting a reused slot's prior tenant that the very next step then
* revokes as stale (VMs 4/5/6, 2026-08-09). Omit it (or pass an empty list) to
* leave the script's sidecar-read fallback in place — the standalone/reconcile
* path, where no Add is deciding the intent. */
function vmIngressInvocation({

@@ -93,2 +101,3 @@ scriptPath,

slug,
vmAccess,
} = {}) {

@@ -110,2 +119,4 @@ if (!scriptPath) throw new Error('vmIngressInvocation requires scriptPath');

if (slug != null && String(slug).trim() !== '') env.VM_SLUG = String(slug).trim();
const access = (Array.isArray(vmAccess) ? vmAccess.join(',') : String(vmAccess == null ? '' : vmAccess)).trim();
if (action === 'up' && access !== '') env.VM_ACCESS = access;
return { command: 'bash', args: [scriptPath, String(n), action], env };

@@ -433,2 +444,9 @@ }

/** Basename prefix of the in-flight marker, shared by the path BUILDER below and
* the `inflightMarkerVmNumber` PARSER beneath it so the naming has exactly one
* owner. The two must agree exactly: the parser is how the dashboard counts live
* tails fleet-wide, and a prefix that drifted on one side only would silently
* count zero — which reads identically to a quiet fleet. */
const INFLIGHT_MARKER_BASENAME_PREFIX = 'cy-ingress-inflight-vm-';
/** The per-VM marker file the detached add-tail holds while its wiring is still

@@ -445,5 +463,30 @@ * running, so the dashboard's auto-rewire gather skips a VM whose FIRST wiring

}
return `${dir.replace(/\/+$/, '')}/cy-ingress-inflight-vm-${n}`;
return `${dir.replace(/\/+$/, '')}/${INFLIGHT_MARKER_BASENAME_PREFIX}${n}`;
}
/** Inverse of `ingressInflightMarkerPath`'s basename: given ONE directory entry
* name, the VM number it is an in-flight marker for, or null if it is not one.
*
* This is what lets the dashboard count live wiring tails FLEET-WIDE by scanning
* the marker directory, rather than per-VM against a roster — the population that
* matters is exactly the one a roster lags behind, since a batch of adds is when
* VMs appear and their tails outlive their jobs. That count feeds both the
* heavy-job concurrency budget and the memory guard's restart refusal.
*
* Deliberately strict, because the marker directory is `os.tmpdir()` and the
* add's own siblings live there too: `cy-ingress-failed-vm-3` (a different
* marker), `cy-wire-add-vm-5.log` (the tail's log), and any `<marker>.tmp`.
* Counting one of those as a live tail would charge a phantom slot against a cap
* of 4 and, worse, hold the memory self-restart off forever. Accepts exactly what
* the builder emits: a positive integer with no leading zeros and nothing after.
*
* Pure; no I/O — the caller does the readdir. */
function inflightMarkerVmNumber(fileName) {
if (typeof fileName !== 'string') return null;
if (!fileName.startsWith(INFLIGHT_MARKER_BASENAME_PREFIX)) return null;
const rest = fileName.slice(INFLIGHT_MARKER_BASENAME_PREFIX.length);
if (!/^[1-9][0-9]*$/.test(rest)) return null;
return Number(rest);
}
/** The per-VM marker file the detached add-tail drops when its converge loop

@@ -770,2 +813,3 @@ * exhausts the deadline WITHOUT reaching `wired`

ingressInflightMarkerPath,
inflightMarkerVmNumber,
ingressFailedMarkerPath,

@@ -772,0 +816,0 @@ isWiringInflight,

@@ -138,2 +138,11 @@ const { createIssue } = require("./scenario-issues");

// Third mirror of SUPPRESSION_PATTERN in ui/src/hooks/useViteHmrAutoReload.ts
// and VITE_WS_NOISE_PATTERN in crates/proxy-http/src/error_capture.js. All
// three surfaces silence the same harness-owned Vite HMR reconnect noise; keep
// in sync. The capture path is the one that was missing it, which made every
// preview-flow / preview-interact against a proxy-wrapped route fail its
// console check on noise the editor itself produces.
const VITE_WS_NOISE_PATTERN =
/vite.*failed to connect to websocket|WebSocket closed without opened/i;
function handleConsoleMessage(message) {

@@ -148,2 +157,10 @@ if (message.type() !== "error") return null;

// Vite's reconnect banner ("[vite] failed to connect to websocket." plus the
// `(browser) … <--[WebSocket (failing)]--> … (server)` diagram) contains none
// of the substrings the ignore list below matches, so it fell through and
// vetoed the capture. Matched narrowly — the `vite`-anchored alternative and
// the exact-phrase second alternative both leave a genuine app-authored error
// mentioning websockets surfacing as a real issue.
if (VITE_WS_NOISE_PATTERN.test(text)) return null;
// Ignore known dev-server WebSocket/HMR errors from Vite proxy, plus the

@@ -224,2 +241,3 @@ // crxjs/Vite dynamic-import reload race: dev loaders `import()` the app entry

INSECURE_HOST_ADVISORY_MESSAGE,
VITE_WS_NOISE_PATTERN,
};

@@ -27,2 +27,4 @@ function createIssue(kind, message, extra = {}) {

unmockedRoutes = [],
mockUsage = { used: [], unused: [] },
externalRequests = [],
}) {

@@ -41,2 +43,11 @@ return {

unmockedRoutes,
// Diagnostic-only, and deliberately NOT part of `ok`: an unused mock is not
// automatically an error (a scenario may legitimately declare a mock for a
// request the page makes only on interaction). Reporting it turns a silent
// inertness into a visible fact; failing on it would break working scenarios
// for a heuristic.
mockUsage,
// Requests grouped by origin, split mocked/unmocked and same/cross-origin.
// The line that ends a "why is this page blank" misdiagnosis.
externalRequests,
};

@@ -43,0 +54,0 @@ }

@@ -1,20 +0,44 @@

// Route matcher shared, semantically, with the injected live-preview shim
// (crates/proxy-http/src/fetch_patch.js::matchUrl / matchPath / matchQuerySpec).
// This is a VERBATIM port of that shim's matcher — the two interception paths
// MUST agree on which requests a mock route covers. When they drifted (the
// shim gained `:param` + query-spec parity but this capture-harness matcher was
// left doing exact string matching), a parameterized or query-spec'd mock
// rendered in the Live Preview but escaped Playwright's `page.route` during
// capture and rendered an empty list. `scenario-mocks.test.js` pins the two to
// identical verdicts on a shared fixture table so a future divergence is a
// test failure, not another lost build session.
// Route matcher for the capture harness — one of THREE implementations of a
// single semantic. The authority is the Rust engine
// (crates/mock-engine/src/route_parser.rs), where the contract is written down;
// this file and the injected live-preview shim
// (crates/proxy-http/src/fetch_patch.js) are ports of it.
//
// `:param` path segments match any single non-empty segment; a `?k=v` / `?k=*`
// query-spec suffix constrains the query as a subset match (`k=*` matches any
// value; an empty spec matches any query). Full-URL patterns (`http://…`) match
// by prefix on the raw URL. Mirrors crates/mock-engine's server matcher.
// This is NOT a verbatim copy of the shim, and describing it as one is what let
// the drift hide: the two JS matchers agreed with EACH OTHER while both
// disagreed with the server (they prefix-matched full-URL patterns and returned
// before applying any query spec, where the server anchored). All three are now
// pinned against one shared fixture table,
// crates/mock-engine/fixtures/route-match-fixtures.json, asserted with its
// expected verdicts by both `scenario-mocks.test.js` and the Rust tests — so a
// change to one implementation the others do not follow is a test failure, and
// two matchers agreeing on a wrong answer no longer passes.
//
// The semantic, in short (see route_parser.rs for the full contract): `:name`
// and `*` each match one non-empty path segment; a TRAILING `**` matches the
// remaining path including `/` and including an empty remainder; a `?k=v` /
// `?k=*` suffix constrains the query as a subset match. A full-URL pattern
// (`http://…`) with no wildcard matches by PREFIX — the compatibility rule
// every shipped external mock relies on — while a wildcarded one matches by the
// wildcard rules.
function matchUrl(pattern, url) {
// Full-URL patterns (external APIs) match by prefix on the raw URL.
if (pattern.startsWith("http://") || pattern.startsWith("https://")) {
return url.indexOf(pattern) === 0;
// Split the query spec off FIRST — for full-URL and path-only patterns
// alike. Returning early for full URLs (as this did) meant a spec like
// `?k=*` on an external mock was silently ignored.
const pqi = pattern.indexOf("?");
const patPath = pqi >= 0 ? pattern.slice(0, pqi) : pattern;
const patSpec = pqi >= 0 ? pattern.slice(pqi + 1) : "";
// Full-URL patterns (external APIs) match against the whole URL, with its
// own query stripped before the compare.
if (patPath.startsWith("http://") || patPath.startsWith("https://")) {
const [rawPath, rawQuery] = splitUrlQuery(url);
// Compatibility rule: a wildcard-free full-URL pattern prefix-matches,
// which is what every external mock relies on today. A wildcarded one
// matches by the wildcard rules, with no prefix fallback.
const urlMatched = hasWildcard(patPath)
? matchPath(patPath, rawPath)
: rawPath.indexOf(patPath) === 0;
if (!urlMatched) return false;
return matchQuerySpec(patSpec, rawQuery);
}

@@ -29,27 +53,56 @@

} catch {
const qi = url.indexOf("?");
if (qi >= 0) {
reqPath = url.slice(0, qi);
reqQuery = url.slice(qi + 1);
}
[reqPath, reqQuery] = splitUrlQuery(url);
}
const patParts = pattern.split("?");
if (!matchPath(patParts[0], reqPath)) return false;
return matchQuerySpec(patParts.length > 1 ? patParts[1] : "", reqQuery);
if (!matchPath(patPath, reqPath)) return false;
return matchQuerySpec(patSpec, reqQuery);
}
// Segment-wise path match with `:param` wildcards. A `:name` segment matches
// any single non-empty segment; every other segment must be equal, and the
// segment counts must match — no implicit prefix match, same as the server.
// Split a raw URL into [everything-before-the-query, query]. The `#fragment`
// is dropped from both halves, mirroring the server's strip_url_query.
function splitUrlQuery(url) {
const qi = url.indexOf("?");
const hi = url.indexOf("#");
const cut = qi < 0 ? hi : hi < 0 ? qi : Math.min(qi, hi);
const head = cut < 0 ? url : url.slice(0, cut);
if (qi < 0) return [head, ""];
const query = url.slice(qi + 1);
const fi = query.indexOf("#");
return [head, fi >= 0 ? query.slice(0, fi) : query];
}
// True when a segment is a valid `:name` param. A `:`-prefixed segment with
// an empty or punctuated name is matched literally, same as the server.
function isParamSegment(seg) {
return /^:[\p{L}\p{N}_]+$/u.test(seg);
}
// True when a segment is any wildcard: `:name`, `*`, or `**`.
function isWildcardSegment(seg) {
return seg === "*" || seg === "**" || isParamSegment(seg);
}
// True when the pattern contains any wildcard segment.
function hasWildcard(pattern) {
return pattern.split("/").some(isWildcardSegment);
}
// Segment-wise path match. `:name` and `*` each match any single non-empty
// segment; a TRAILING `**` matches the remaining path including `/` and
// including an empty remainder (`/api/**` covers `/api`); every other segment
// must be equal. No implicit prefix match, same as the server.
function matchPath(pattern, path) {
const pp = pattern.split("/");
const rp = path.split("/");
if (pp[pp.length - 1] === "**") {
const head = pp.length - 1;
if (rp.length < head) return false;
for (let i = 0; i < head; i++) {
if (!matchSegment(pp[i], rp[i])) return false;
}
return true;
}
if (pp.length !== rp.length) return false;
for (let i = 0; i < pp.length; i++) {
if (pp[i].charAt(0) === ":") {
if (rp[i] === "") return false;
continue;
}
if (pp[i] !== rp[i]) return false;
if (!matchSegment(pp[i], rp[i])) return false;
}

@@ -59,2 +112,9 @@ return true;

// One segment. `:name`, `*`, and a non-final `**` each match any single
// non-empty segment; anything else must be equal.
function matchSegment(pat, seg) {
if (isWildcardSegment(pat)) return seg !== "";
return pat === seg;
}
// Query-spec match. An empty spec matches any query (a path-only route still

@@ -87,3 +147,7 @@ // matches a request that carries query params). Otherwise every `k=v` pair in

function findHttpMock(httpMocks, request) {
// The matching mock together with the KEY that declared it, or null. The key is
// what the usage tally counts — reporting "which declared mock fired" needs the
// declaration's identity, not just its body, since two keys can carry
// structurally identical mocks.
function findHttpMockEntry(httpMocks, request) {
const method = request.method().toUpperCase();

@@ -96,3 +160,3 @@ const url = request.url();

if (parsed.method.toUpperCase() !== method) continue;
if (matchUrl(parsed.route, url)) return mock;
if (matchUrl(parsed.route, url)) return { key, mock };
}

@@ -102,2 +166,7 @@ return null;

function findHttpMock(httpMocks, request) {
const entry = findHttpMockEntry(httpMocks, request);
return entry ? entry.mock : null;
}
// The set of path/URL route patterns declared by the mock keys. A key is

@@ -129,5 +198,131 @@ // `"<METHOD> <route>"` (e.g. `"GET /api/plans"`); we strip the method so the

async function attachHttpMocks(page, httpMocks) {
if (!httpMocks || Object.keys(httpMocks).length === 0) return;
// The origin half of a URL, or the raw string when it will not parse. Grouping
// by origin is what turns twenty tile URLs into one readable line.
function originOf(url) {
try {
return new URL(url).origin;
} catch {
return url;
}
}
// Which declared mock keys actually served a response.
//
// This exists because a declared mock whose key never matches any request is
// silently inert: the capture still reports success, and the frame that lands
// is the healthy state under a name promising a degraded one. The declared keys
// were always known; nothing ever compared them against what fired.
function createMockUsageTally(httpMocks) {
const declared = Object.keys(httpMocks || {});
const usedKeys = new Set();
return {
recordFulfilled(key) {
usedKeys.add(key);
},
// Declared order is preserved in both lists so the reported set reads the
// same way the scenario's mock block does.
get used() {
return declared.filter((key) => usedKeys.has(key));
},
get unused() {
return declared.filter((key) => !usedKeys.has(key));
},
};
}
// Every request the page made, collapsed to one row per `(origin, mocked)` pair.
// A full URL list is noise (twenty tile URLs); origin + count + a representative
// URL is the line that ends a "why is this page blank" misdiagnosis.
function createRequestInventory(appOrigin) {
// Keyed by `origin|mocked` rather than origin alone: an origin serving both a
// mocked API call and unmocked sub-resources would otherwise collapse into one
// row whose `mocked` flag is a coin toss.
const groups = new Map();
function groupFor(url, mocked) {
const origin = originOf(url);
const groupKey = `${origin}|${mocked ? "mocked" : "unmocked"}`;
let row = groups.get(groupKey);
if (!row) {
row = {
origin,
count: 0,
sampleUrl: url,
mocked: !!mocked,
failed: false,
// `false` when the capture URL did not parse — the caller then has no
// origin to compare against, and treating everything as cross-origin
// over-reports rather than hiding a request.
sameOrigin: !!appOrigin && origin === appOrigin,
};
groups.set(groupKey, row);
}
return row;
}
return {
recordRequest(url, mocked) {
groupFor(url, mocked).count += 1;
},
recordFailure(url, mocked) {
groupFor(url, mocked).failed = true;
},
// Busiest origin first — the one worth explaining leads the report.
get groups() {
return [...groups.values()].sort(
(a, b) => b.count - a.count || a.origin.localeCompare(b.origin),
);
},
};
}
// Composes the two independent halves above into the single handle the capture
// harness threads through.
//
// Purely observational. Nothing here changes which requests are intercepted:
// `attachHttpMocks` scopes `page.route` to declared targets so unmocked requests
// load natively, and that scoping is load-bearing (a blanket catch-all route
// predicate breaks Vite ESM module loading and blanks the SPA).
function createMockObserver(httpMocks, appOrigin) {
const usage = createMockUsageTally(httpMocks);
const inventory = createRequestInventory(appOrigin);
return {
recordFulfilled: (key) => usage.recordFulfilled(key),
recordRequest: (url, mocked) => inventory.recordRequest(url, mocked),
recordFailure: (url, mocked) => inventory.recordFailure(url, mocked),
get used() {
return usage.used;
},
get unused() {
return usage.unused;
},
get externalRequests() {
return inventory.groups;
},
};
}
// Passive request inventory. `page.on` observes; it never routes, so attaching
// this cannot change what the page loads.
function attachRequestInventory(page, observer, httpMocks) {
const targets = mockedTargets(httpMocks || {});
page.on("request", (request) => {
const url = request.url();
observer.recordRequest(url, requestTargetsMock(targets, url));
});
page.on("requestfailed", (request) => {
const url = request.url();
observer.recordFailure(url, requestTargetsMock(targets, url));
});
}
// Returns the capture's mock observer (see `createMockObserver`). Always returns
// one, including for a scenario that declares no mocks at all — that is exactly
// the case where an unexplained third-party fetch is hardest to account for.
async function attachHttpMocks(page, httpMocks, options = {}) {
const observer = createMockObserver(httpMocks, options.appOrigin);
attachRequestInventory(page, observer, httpMocks);
if (!httpMocks || Object.keys(httpMocks).length === 0) return observer;
// Intercept ONLY requests whose path matches a declared mock — not every

@@ -143,7 +338,11 @@ // request. A blanket `page.route("**/*")` intercepts the dev server's ESM

async (route) => {
const mock = findHttpMock(httpMocks, route.request());
if (!mock) {
const entry = findHttpMockEntry(httpMocks, route.request());
if (!entry) {
await route.continue();
return;
}
const { key, mock } = entry;
// Counted at the fulfil branch, not at match time: a key is "used" only
// when it actually served a response.
observer.recordFulfilled(key);

@@ -185,2 +384,4 @@ const headers = { ...(mock.headers || {}) };

});
return observer;
}

@@ -233,4 +434,9 @@

findHttpMock,
findHttpMockEntry,
mockedTargets,
requestTargetsMock,
originOf,
createMockUsageTally,
createRequestInventory,
createMockObserver,
attachHttpMocks,

@@ -237,0 +443,0 @@ isDeclaredErrorMock,

@@ -149,11 +149,37 @@ const {

// Resolve the `localhost` origin that serves the iframe-harness route. The
// harness is mounted on the editor's control-api listener, whose port is
// recorded in `.codeyam/server-state.json` as `controlPort` (the same file the
// top-level loader already reads for `appPort`). Returns
// `http://localhost:<controlPort>` — a secure context the nested iframe
// inherits — or null when the state file is missing/unreadable, in which case
// the caller falls back to the legacy in-page `setContent` harness.
// `readStateFile` is injectable so the resolver is unit-testable without disk.
function resolveHarnessOrigin({ readStateFile = defaultReadServerState } = {}) {
// Resolve the loopback origin that serves the iframe-harness route. The harness
// is mounted on the editor's control-api listener, whose port is recorded in
// `.codeyam/server-state.json` as `controlPort` (the same file the top-level
// loader already reads for `appPort`). Returns `http://127.0.0.1:<controlPort>`
// — a secure context the nested iframe inherits, exactly as `localhost` is — or
// null when the state file is missing/unreadable, in which case the caller falls
// back to the legacy in-page `setContent` harness. `readStateFile` is injectable
// so the resolver is unit-testable without disk.
//
// The HOST is taken from `targetUrl` — the URL the iframe will load — and only
// the port comes from server-state. That mirroring is load-bearing, because the
// two capture modes address the editor by different loopback spellings:
// `/__codeyam_preview` captures are pinned to `127.0.0.1` (PROXY_CAPTURE_LOOPBACK
// in handlers.rs, matching the forwarder's own pinning) while direct app-port
// captures use `localhost`. `localhost` and `127.0.0.1` are DIFFERENT sites to
// the cookie jar, so whenever the harness host and the iframe host disagree the
// nested load is cross-site — and the `cy_session` cookie is `SameSite=Lax`,
// which rides top-level navigations only. It is therefore withheld from the
// iframe request (and from the `/api/*` calls the framed page makes), and the
// token-gated routes answer 401 on a non-loopback bind.
//
// Hardcoding EITHER spelling only moves the failure between the two modes:
// `localhost` 401s every `/__codeyam_preview` capture, `127.0.0.1` 401s the
// framed page's own `/api/scenarios` + `/api/render-environment` calls. Raising
// the cookie to `SameSite=None` fixes neither — Chromium rejects `None` without
// `Secure`, and these origins are plain http. Mirroring the target's host is what
// keeps the harness same-site in both modes, which is also the same-origin model
// the `/__codeyam_preview` subpath proxy exists to provide.
//
// `targetUrl` omitted or unparseable falls back to `127.0.0.1`, the spelling the
// proxy-route capture (the token-gated one) uses.
function resolveHarnessOrigin({
readStateFile = defaultReadServerState,
targetUrl = null,
} = {}) {
try {

@@ -163,3 +189,11 @@ const state = readStateFile();

if (typeof port === "number" && port > 0) {
return `http://localhost:${port}`;
let host = "127.0.0.1";
if (targetUrl) {
try {
host = new URL(targetUrl).hostname || host;
} catch (_) {
/* unparseable target — keep the proxy-route default */
}
}
return `http://${host}:${port}`;
}

@@ -305,12 +339,32 @@ } catch (_) {

// Inject a capture-only stylesheet that snaps entrance animations to their
// FINAL frame: remove animation/transition timing and force the common
// "hidden until revealed" symptoms (`opacity:0`, an entrance `transform`) back
// to their resting values. This is the belt to `scrollThroughDocument`'s
// suspenders — it covers pure-CSS keyframe entrances that are mid-flight or
// stuck at an `opacity:0` start state even after the observers fired. It
// targets the generic CSS symptom, not any framework's reveal class, so it
// works for any stack. The caller gates this OFF when a scenario declares an
// interactive state, so an intentionally animated/collapsed interactive frame
// is never clobbered. Idempotent (a single injected style id) and best-effort.
// Returns true when the style is present after the call.
// FINAL frame: remove animation/transition timing, then reveal the elements an
// entrance animation left INVISIBLE. This is the belt to
// `scrollThroughDocument`'s suspenders — it covers pure-CSS keyframe entrances
// that are mid-flight or stuck at an `opacity:0` start state even after the
// observers fired. It targets the generic CSS symptom, not any framework's
// reveal class, so it works for any stack. The caller gates this OFF when a
// scenario declares an interactive state, so an intentionally animated /
// collapsed interactive frame is never clobbered. Idempotent (a single injected
// style id) and best-effort. Returns true when the style is present after the
// call.
//
// The reveal is per-element and conditional ON THE ELEMENT BEING INVISIBLE,
// never a blanket `opacity: 1 !important` / `transform: none !important` over
// `*`. A blanket rule cannot tell an entrance animation's `opacity: 0` from
// DELIBERATE, resting state — a disabled control's dim, a muted row, a
// collapsed chevron's rotation — so it silently flattened every one of them out
// of every screenshot the capture pipeline produced. Two scenarios differing
// only in such a state then captured byte-identically and collided in the
// distinct-capture gate, and the collision was unfixable in the component: the
// state rendered correctly in a real browser (verified: computed opacity 0.4 vs
// 1) and was erased only at capture time. Anything already visible is now left
// exactly as the app rendered it.
//
// The reveal's TRANSFORM half additionally stops at the SVG boundary, because a
// CSS `transform` overrides the SVG `transform` presentation attribute: inside
// an `<svg>` it erases the static geometry that CONSTRUCTS the drawing instead
// of neutralizing an entrance animation, so a revealed SVG node lands on the
// origin. Invisible SVG nodes are therefore revealed in place — opacity forced,
// transform left alone. See the loop below for why the boundary is
// `ownerSVGElement` rather than `closest("svg")`.
async function forceFinalVisualState(target) {

@@ -328,2 +382,4 @@ return target.evaluate(() => {

style.id = STYLE_ID;
// Temporal properties only — these carry no resting state, so removing
// them cannot erase anything the app meant to show.
style.textContent =

@@ -333,4 +389,2 @@ "*, *::before, *::after {" +

"transition: none !important;" +
"opacity: 1 !important;" +
"transform: none !important;" +
"}";

@@ -340,2 +394,40 @@ const head = document.head || document.documentElement;

head.appendChild(style);
// With animations disabled above, an element held back by an entrance
// animation now computes to its pre-animation resting state — typically
// `opacity: 0`, often paired with a translate/scale that parks it offscreen.
// Reveal exactly those, and only those: a fully transparent element shows
// nothing either way, so forcing it can hide no real state, while an element
// at any visible opacity is left untouched.
//
// The transform half of the reveal STOPS at the SVG boundary. A CSS
// `transform` overrides the SVG `transform` presentation attribute, so
// inside an `<svg>` a forced `transform: none` does not neutralize an
// entrance animation — it erases the static rotate/translate/scale that
// CONSTRUCTS the drawing, revealing the node collapsed on the origin. The
// opacity half is still right there (a node at ~0 shows nothing either
// way), so an invisible SVG node is revealed IN PLACE. The boundary test is
// `ownerSVGElement` and NOT `closest("svg")`: HTML inside a
// `<foreignObject>` is a real HTMLElement with no `ownerSVGElement`, so it
// keeps the full reveal — `closest("svg")` would silently strand its
// genuine CSS entrance transform.
if (typeof document.querySelectorAll !== "function") return true;
const INVISIBLE_EPSILON = 0.01;
for (const el of document.querySelectorAll("*")) {
let computed = null;
try {
computed = getComputedStyle(el);
} catch (_) {
continue;
}
if (!computed) continue;
const opacity = parseFloat(computed.opacity);
if (!Number.isFinite(opacity) || opacity > INVISIBLE_EPSILON) continue;
if (!el.style || typeof el.style.setProperty !== "function") continue;
const inSvg =
el.ownerSVGElement != null ||
(typeof el.tagName === "string" && el.tagName.toLowerCase() === "svg");
el.style.setProperty("opacity", "1", "important");
if (!inSvg) el.style.setProperty("transform", "none", "important");
}
return true;

@@ -342,0 +434,0 @@ });

+5
-5
{
"name": "@codeyam-editor/codeyam-editor",
"version": "0.1.0-staging.g296f8a2",
"version": "0.1.0-staging.g3858572",
"description": "Language-agnostic managed execution sandbox for scenario-driven development",

@@ -12,6 +12,6 @@ "bin": {

"optionalDependencies": {
"@codeyam-editor/codeyam-editor-darwin-arm64": "0.1.0-staging.g296f8a2",
"@codeyam-editor/codeyam-editor-darwin-x64": "0.1.0-staging.g296f8a2",
"@codeyam-editor/codeyam-editor-linux-x64": "0.1.0-staging.g296f8a2",
"@codeyam-editor/codeyam-editor-win32-x64": "0.1.0-staging.g296f8a2"
"@codeyam-editor/codeyam-editor-darwin-arm64": "0.1.0-staging.g3858572",
"@codeyam-editor/codeyam-editor-darwin-x64": "0.1.0-staging.g3858572",
"@codeyam-editor/codeyam-editor-linux-x64": "0.1.0-staging.g3858572",
"@codeyam-editor/codeyam-editor-win32-x64": "0.1.0-staging.g3858572"
},

@@ -18,0 +18,0 @@ "keywords": [

@@ -8,6 +8,6 @@ <!DOCTYPE html>

<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<script type="module" crossorigin src="/assets/index-D_Bz8tMJ.js"></script>
<script type="module" crossorigin src="/assets/index-BPLKgwon.js"></script>
<link rel="modulepreload" crossorigin href="/assets/react-nrLBr15I.js">
<link rel="modulepreload" crossorigin href="/assets/markdown-C9tbsVHX.js">
<link rel="stylesheet" crossorigin href="/assets/index-BygCiJSb.css">
<link rel="stylesheet" crossorigin href="/assets/index-DKpgJLo3.css">
</head>

@@ -14,0 +14,0 @@ <body>

import{j as e}from"./markdown-C9tbsVHX.js";import{b as a}from"./react-nrLBr15I.js";import{u as O}from"./useEvents-S3Qk1Y_d.js";import{b as E}from"./index-D_Bz8tMJ.js";function P({connected:t}){return e.jsx("span",{className:`inline-block rounded-full px-2 py-0.5 text-xs font-medium text-[var(--text-primary)] ${t?"bg-[var(--accent-green)]":"bg-[var(--accent-red)]"}`,children:t?"connected":"reconnecting…"})}const re=Object.freeze(Object.defineProperty({__proto__:null,ConnectionBadge:P},Symbol.toStringTag,{value:"Module"}));function N(){const[t,r]=a.useState(null),[o,s]=a.useState(!0),{events:l}=O(),n=a.useCallback(async()=>{try{const c=await(await fetch("/api/inspector/status")).json();r(c)}catch{}finally{s(!1)}},[]);return a.useEffect(()=>{n()},[n]),a.useEffect(()=>{const i=l[l.length-1];i&&i.type==="scenario_switch"&&n()},[l,n]),{scenario:(t==null?void 0:t.scenario)??null,previewScenario:(t==null?void 0:t.previewScenario)??null,routes:(t==null?void 0:t.routes)??[],routeCount:(t==null?void 0:t.routeCount)??0,loading:o,refetch:n}}function v({title:t}){return e.jsx("h2",{style:{fontSize:14,fontWeight:600,color:"#f0f6fc",margin:"0 0 8px 0",borderBottom:"1px solid #30363d",paddingBottom:8},children:t})}function f({color:t,children:r}){return e.jsx("span",{style:{background:`${t}22`,color:t,border:`1px solid ${t}55`,borderRadius:4,padding:"2px 8px",fontSize:11,fontWeight:600,letterSpacing:"0.5px",whiteSpace:"nowrap"},children:r})}const $={GET:"#D7FF63",POST:"#238636",PUT:"#d29922",DELETE:"#da3633",PATCH:"#bc8cff"};function k({method:t}){const r=$[t.toUpperCase()]??"#8b949e";return e.jsx("span",{style:{color:r,fontWeight:600,fontSize:11,minWidth:48,display:"inline-block"},children:t.toUpperCase()})}function C({color:t,children:r}){return e.jsx("div",{style:{padding:"12px 16px",borderRadius:6,border:`1px solid ${t}44`,background:`${t}11`,color:t},children:r})}const u={padding:"8px 12px",textAlign:"center",color:"#8b949e",fontWeight:500,fontSize:11,textTransform:"uppercase",letterSpacing:"0.5px"},h={padding:"6px 12px"},S={background:"#161b22",border:"1px solid #30363d",borderRadius:6,color:"#c9d1d9",padding:"6px 12px",fontSize:13,fontFamily:"inherit"},R={...S,appearance:"none",paddingRight:24,backgroundImage:`url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238b949e' d='M6 8L1 3h10z'/%3E%3C/svg%3E")`,backgroundRepeat:"no-repeat",backgroundPosition:"right 8px center"},oe=Object.freeze(Object.defineProperty({__proto__:null,Badge:f,MethodBadge:k,SectionHeader:v,StatusBox:C,inputStyle:S,selectStyle:R,tdStyle:h,thStyle:u},Symbol.toStringTag,{value:"Module"}));function z({routes:t}){return e.jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:12},children:[e.jsx("thead",{children:e.jsxs("tr",{style:{borderBottom:"1px solid #30363d"},children:[e.jsx("th",{style:u,children:"Method"}),e.jsx("th",{style:{...u,textAlign:"left"},children:"Pattern"}),e.jsx("th",{style:u,children:"Status"})]})}),e.jsx("tbody",{children:t.map((r,o)=>e.jsxs("tr",{style:{borderBottom:"1px solid #21262d"},children:[e.jsx("td",{style:{...h,textAlign:"center"},children:e.jsx(k,{method:r.method})}),e.jsx("td",{style:h,children:r.pattern}),e.jsx("td",{style:{...h,textAlign:"center",color:"#8b949e"},children:r.status})]},o))})]})}const se=Object.freeze(Object.defineProperty({__proto__:null,InspectorRouteTable:z},Symbol.toStringTag,{value:"Module"}));function A(){const{scenario:t,previewScenario:r,routes:o,routeCount:s,loading:l}=N();return e.jsxs("section",{children:[e.jsx(v,{title:"Active Scenario"}),l?e.jsx(C,{color:"#8b949e",children:"Loading..."}):t?e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:12},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[e.jsx(f,{color:"#238636",children:"ACTIVE"}),e.jsx("span",{style:{color:"#f0f6fc",fontWeight:500},children:t.name}),e.jsxs("span",{style:{color:"#8b949e"},children:["(",t.slug,")"]})]}),e.jsxs("div",{style:{color:"#8b949e",fontSize:12},children:[s," mock route",s!==1?"s":""," loaded"]}),o.length>0&&e.jsx(z,{routes:o})]}):r?e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:12},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[e.jsx(f,{color:"#D7FF63",children:"PREVIEW"}),e.jsx("span",{style:{color:"#f0f6fc",fontWeight:500},children:r.name}),e.jsxs("span",{style:{color:"#8b949e"},children:["(",r.slug,")"]})]}),e.jsx("div",{style:{color:"#8b949e",fontSize:12},children:"Component preview — no HTTP mocks active"})]}):e.jsx(C,{color:"#d29922",children:"No scenario active — all requests pass through to upstream"})]})}const ne=Object.freeze(Object.defineProperty({__proto__:null,InspectorScenarioSection:A},Symbol.toStringTag,{value:"Module"}));function F({textUrlFilter:t,onTextUrlFilterChange:r,methodFilter:o,onMethodFilterChange:s,sourceFilter:l,onSourceFilterChange:n}){return e.jsxs("div",{style:{display:"flex",gap:8,marginBottom:12,alignItems:"center"},children:[e.jsx("input",{type:"text",placeholder:"Filter by URL...",value:t,onChange:i=>r(i.target.value),style:{...S,flex:1,minWidth:0}}),e.jsxs("select",{value:o,onChange:i=>s(i.target.value),style:R,children:[e.jsx("option",{value:"ALL",children:"All Methods"}),e.jsx("option",{value:"GET",children:"GET"}),e.jsx("option",{value:"POST",children:"POST"}),e.jsx("option",{value:"PUT",children:"PUT"}),e.jsx("option",{value:"DELETE",children:"DELETE"}),e.jsx("option",{value:"PATCH",children:"PATCH"})]}),e.jsxs("select",{value:l,onChange:i=>n(i.target.value),style:R,children:[e.jsx("option",{value:"ALL",children:"All Sources"}),e.jsx("option",{value:"MOCKED",children:"Mocked"}),e.jsx("option",{value:"PASSTHROUGH",children:"Passthrough"})]})]})}const ie=Object.freeze(Object.defineProperty({__proto__:null,InspectorTrafficFilterBar:F},Symbol.toStringTag,{value:"Module"})),G={color:"#8b949e",fontSize:11,fontWeight:600,textTransform:"uppercase",letterSpacing:"0.5px",marginBottom:4},M={background:"#161b22",border:"1px solid #30363d",borderRadius:4,padding:"8px 12px",margin:0,fontSize:11,color:"#c9d1d9",fontFamily:"monospace",maxHeight:200,overflow:"auto",whiteSpace:"pre-wrap",wordBreak:"break-all"};function _({label:t,children:r}){return e.jsxs("div",{style:{marginBottom:12},children:[e.jsx("div",{style:G,children:t}),r]})}function B({event:t}){let r=[];try{const n=new URL(t.url,"http://localhost");r=Array.from(n.searchParams.entries())}catch{}const o=t.requestHeaders&&Object.keys(t.requestHeaders).length>0,s=t.requestBody!=null,l=t.responseBody!=null;return e.jsxs("div",{style:{background:"#0d1117",padding:"12px 16px",borderTop:"1px solid #30363d"},children:[r.length>0&&e.jsx(_,{label:"Query Parameters",children:e.jsx("div",{style:{display:"grid",gridTemplateColumns:"auto 1fr",gap:"2px 12px",fontSize:12},children:r.map(([n,i],c)=>e.jsxs("div",{style:{display:"contents"},children:[e.jsx("span",{style:{color:"#D7FF63",fontFamily:"monospace"},children:n}),e.jsx("span",{style:{color:"#c9d1d9",fontFamily:"monospace"},children:i})]},c))})}),o&&e.jsx(_,{label:"Request Headers",children:e.jsx("pre",{style:M,children:Object.entries(t.requestHeaders).map(([n,i])=>`${n}: ${i}`).join(`
`)})}),s&&e.jsx(_,{label:"Request Body",children:e.jsx("pre",{style:M,children:typeof t.requestBody=="string"?t.requestBody:JSON.stringify(t.requestBody,null,2)})}),l&&e.jsx(_,{label:"Response Body",children:e.jsx("pre",{style:M,children:typeof t.responseBody=="string"?t.responseBody:JSON.stringify(t.responseBody,null,2)})}),r.length===0&&!o&&!s&&!l&&e.jsx("div",{style:{color:"#8b949e",fontSize:12},children:"No additional details available for this request"})]})}const le=Object.freeze(Object.defineProperty({__proto__:null,DetailSection:_,InspectorTrafficDetail:B},Symbol.toStringTag,{value:"Module"}));function L({event:t}){const[r,o]=a.useState(!1),s=new Date(t.timestampMs).toLocaleTimeString(),l=t.mocked;return e.jsxs(e.Fragment,{children:[e.jsxs("tr",{onClick:()=>o(!r),style:{borderBottom:"1px solid #21262d",background:l?"transparent":"rgba(210, 153, 34, 0.05)",cursor:"pointer"},children:[e.jsxs("td",{style:{...h,color:"#8b949e",textAlign:"center"},children:[e.jsx("span",{style:{display:"inline-block",width:12,marginRight:4,fontSize:10,color:"#8b949e",transition:"transform 0.15s",transform:r?"rotate(90deg)":"rotate(0deg)"},children:"▶"}),s]}),e.jsx("td",{style:{...h,textAlign:"center"},children:e.jsx(k,{method:t.method})}),e.jsx("td",{style:{...h,maxWidth:400,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:t.url}),e.jsx("td",{style:{...h,textAlign:"center",color:"#8b949e"},children:t.status??"---"}),e.jsxs("td",{style:{...h,textAlign:"center",color:"#8b949e"},children:[t.durationMs,"ms"]}),e.jsx("td",{style:{...h,textAlign:"center"},children:l?t.mockSource==="default"?e.jsx(f,{color:"#1f6feb",children:"MOCKED (default)"}):e.jsx(f,{color:"#238636",children:"MOCKED (scenario)"}):e.jsx(f,{color:"#d29922",children:"PASSTHROUGH"})})]}),r&&e.jsx("tr",{children:e.jsx("td",{colSpan:6,style:{padding:0},children:e.jsx(B,{event:t})})})]})}const ae=Object.freeze(Object.defineProperty({__proto__:null,InspectorTrafficRow:L},Symbol.toStringTag,{value:"Module"}));function D({events:t}){return e.jsx("div",{style:{maxHeight:300,overflowY:"auto",border:"1px solid #30363d",borderRadius:6},children:e.jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:12},children:[e.jsx("thead",{children:e.jsxs("tr",{style:{borderBottom:"1px solid #30363d",position:"sticky",top:0,background:"#0d1117"},children:[e.jsx("th",{style:u,children:"Time"}),e.jsx("th",{style:u,children:"Method"}),e.jsx("th",{style:{...u,textAlign:"left"},children:"URL"}),e.jsx("th",{style:u,children:"Status"}),e.jsx("th",{style:u,children:"Duration"}),e.jsx("th",{style:u,children:"Source"})]})}),e.jsx("tbody",{children:t.map(r=>e.jsx(L,{event:r},r.id))})]})})}const ce=Object.freeze(Object.defineProperty({__proto__:null,InspectorTrafficTable:D},Symbol.toStringTag,{value:"Module"}));function K(t){if(t===void 0)return"checked";if(t==="checked")return"excluded"}function I({chips:t,chipStates:r,onChipStatesChange:o}){if(t.length===0)return null;const s=n=>{const i=new Map(r),c=K(i.get(n));c===void 0?i.delete(n):i.set(n,c),o(i)},l=()=>{o(new Map)};return e.jsxs("div",{style:{display:"flex",flexWrap:"wrap",gap:6,marginBottom:12},children:[r.size>0&&e.jsx("button",{onClick:l,style:{background:"#da363322",color:"#da3633",border:"1px solid #da363355",borderRadius:4,padding:"2px 8px",fontSize:11,fontWeight:600,cursor:"pointer",fontFamily:"inherit"},children:"Clear all"}),t.map(({path:n,count:i})=>{const c=r.get(n),d=c==="checked",g=c==="excluded";let m="#30363d",b="#8b949e",T="#30363d",y="";return d?(m="#D7FF6322",b="#D7FF63",T="#D7FF6355",y="✓ "):g&&(m="#da363322",b="#da3633",T="#da363355",y="✗ "),e.jsxs("button",{onClick:()=>s(n),style:{background:m,color:b,border:`1px solid ${T}`,borderRadius:4,padding:"2px 8px",fontSize:11,fontFamily:"monospace",cursor:"pointer",whiteSpace:"nowrap"},children:[y,n,e.jsxs("span",{style:{marginLeft:4,opacity:.6,fontFamily:"inherit"},children:["(",i,")"]})]},n)})]})}const de=Object.freeze(Object.defineProperty({__proto__:null,InspectorTrafficUrlChips:I},Symbol.toStringTag,{value:"Module"}));function H(){const{events:t}=O(),[r,o]=a.useState(""),[s,l]=a.useState(()=>new Map),[n,i]=a.useState("ALL"),[c,d]=a.useState("ALL"),g=t.filter(p=>p.type==="http_request"),m=a.useMemo(()=>{const p=new Map;for(const x of g){const j=E(x.url);p.set(j,(p.get(j)||0)+1)}return Array.from(p.entries()).sort((x,j)=>j[1]-x[1]).slice(0,10).map(([x,j])=>({path:x,count:j}))},[g]),b=a.useMemo(()=>Array.from(s.values()).some(p=>p==="checked"),[s]),y=g.filter(p=>{if(r&&!p.url.toLowerCase().includes(r.toLowerCase()))return!1;const x=E(p.url);return!(s.get(x)==="excluded"||b&&s.get(x)!=="checked"||n!=="ALL"&&p.method.toUpperCase()!==n||c==="MOCKED"&&!p.mocked||c==="PASSTHROUGH"&&p.mocked)}).slice(-50).reverse();return e.jsxs("section",{children:[e.jsx(v,{title:"Live Traffic"}),e.jsx(F,{textUrlFilter:r,onTextUrlFilterChange:o,methodFilter:n,onMethodFilterChange:i,sourceFilter:c,onSourceFilterChange:p=>d(p)}),e.jsx(I,{chips:m,chipStates:s,onChipStatesChange:l}),y.length===0?e.jsx(C,{color:"#8b949e",children:g.length>0?"No matching traffic — try adjusting the filters":"No HTTP traffic yet — requests will appear here in real-time"}):e.jsx(D,{events:y})]})}const pe=Object.freeze(Object.defineProperty({__proto__:null,InspectorTrafficSection:H},Symbol.toStringTag,{value:"Module"}));function J(){const[t,r]=a.useState(null),[o,s]=a.useState(!1);return{explain:a.useCallback(async(n,i)=>{s(!0);try{const d=await(await fetch("/api/inspector/explain",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({method:n,url:i})})).json();r(d)}catch{}finally{s(!1)}},[]),result:t,loading:o}}function U({result:t}){return e.jsxs("div",{style:{border:"1px solid #30363d",borderRadius:6,padding:12},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,marginBottom:12},children:[e.jsxs("span",{style:{color:"#f0f6fc",fontWeight:500},children:[t.method," ",t.url]}),t.wouldMatch?e.jsx(f,{color:"#238636",children:"WOULD MATCH"}):e.jsx(f,{color:"#da3633",children:"NO MATCH"})]}),t.attempts.length===0?e.jsx("div",{style:{color:"#8b949e"},children:"No routes loaded to match against"}):e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:6},children:t.attempts.map((r,o)=>e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,padding:"4px 8px",borderRadius:4,background:r.failure?"transparent":"rgba(35, 134, 54, 0.1)"},children:[e.jsx("span",{style:{color:r.failure?"#da3633":"#238636",fontWeight:600,width:16},children:r.failure?"x":"v"}),e.jsx(k,{method:r.method}),e.jsx("span",{children:r.pattern}),r.failure&&e.jsx("span",{style:{color:"#8b949e",fontSize:11},children:r.failure.reason==="methodMismatch"?`method: expected ${r.failure.expected}, got ${r.failure.actual}`:`path "${r.failure.path}" does not match "${r.failure.pattern}"`})]},o))})]})}const ue=Object.freeze(Object.defineProperty({__proto__:null,InspectorExplainerResult:U},Symbol.toStringTag,{value:"Module"}));function W(){const{explain:t,result:r,loading:o}=J(),[s,l]=a.useState("GET"),[n,i]=a.useState(""),c=d=>{d.preventDefault(),n.trim()&&t(s,n.trim())};return e.jsxs("section",{children:[e.jsx(v,{title:"Match Explainer"}),e.jsxs("form",{onSubmit:c,style:{display:"flex",gap:8,marginBottom:12},children:[e.jsx("select",{value:s,onChange:d=>l(d.target.value),style:{...S,width:100},children:["GET","POST","PUT","DELETE","PATCH"].map(d=>e.jsx("option",{value:d,children:d},d))}),e.jsx("input",{type:"text",value:n,onChange:d=>i(d.target.value),placeholder:"/api/users",style:{...S,flex:1}}),e.jsx("button",{type:"submit",disabled:o||!n.trim(),style:{background:"#238636",color:"#fff",border:"none",borderRadius:6,padding:"6px 16px",cursor:"pointer",fontSize:12,fontWeight:500,opacity:o||!n.trim()?.5:1},children:o?"Checking...":"Explain"})]}),r&&e.jsx(U,{result:r})]})}const he=Object.freeze(Object.defineProperty({__proto__:null,InspectorExplainerSection:W},Symbol.toStringTag,{value:"Module"})),w={"/api/health":"Health check — confirms the proxy and mock engine are running","/api/events":"SSE stream of real-time events (traffic, errors) powering this Inspector","/api/config":"Returns current CodeYam configuration for the project","/api/editor-dev-server":"Polls/controls the dev server status (start, stop, restart)","/api/inspector/status":"Returns active scenario and loaded mock routes","/api/inspector/explain":"Tests which mock route would match a given method + URL","/api/scenarios":"Lists all defined scenarios with metadata","/api/tests":"Returns the AI-maintained test registry joined with per-test-evidence, grouped by file","/api/test-results":"Returns latest test execution results","/api/screenshots/*":"Serves captured scenario screenshots","/api/session-info":"Returns current editor session metadata","/api/data-structure":"Returns the app's data structure definition","/api/dependency-graph":"Returns the project's component dependency graph","/api/app-route-entities":"Lists app routes and their associated entities","/api/glossary":"Returns the project's component glossary","/api/step":"Returns current editor workflow step","/api/editor-commands":"Lists available editor commands"};function V(t){if(w[t])return w[t];for(const[r,o]of Object.entries(w))if(r.endsWith("/*")){const s=r.slice(0,-1);if(t.startsWith(s))return o}}function Y(t){const r=new Set([...t,...Object.keys(w)]),o=[];for(const s of Array.from(r).sort()){if(s.endsWith("/*"))continue;const l=V(s);o.push({path:s,description:l??"Application endpoint"})}return o}function q(){const{events:t}=O(),r=a.useMemo(()=>{const o=[];for(const s of t)s.type==="http_request"&&o.push(E(s.url));return Y(o)},[t]);return r.length===0?null:e.jsxs("section",{children:[e.jsx(v,{title:"URL Glossary"}),e.jsx("div",{style:{border:"1px solid #30363d",borderRadius:6,overflow:"hidden"},children:e.jsxs("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:12},children:[e.jsx("thead",{children:e.jsxs("tr",{style:{borderBottom:"1px solid #30363d",background:"#161b22"},children:[e.jsx("th",{style:{padding:"6px 12px",textAlign:"left",color:"#8b949e",fontWeight:500,fontSize:11,textTransform:"uppercase",letterSpacing:"0.5px",width:"30%"},children:"Path"}),e.jsx("th",{style:{padding:"6px 12px",textAlign:"left",color:"#8b949e",fontWeight:500,fontSize:11,textTransform:"uppercase",letterSpacing:"0.5px"},children:"Description"})]})}),e.jsx("tbody",{children:r.map(({path:o,description:s})=>e.jsxs("tr",{style:{borderBottom:"1px solid #21262d"},children:[e.jsx("td",{style:{padding:"4px 12px",fontFamily:"monospace",color:"#D7FF63",fontSize:11},children:o}),e.jsx("td",{style:{padding:"4px 12px",color:"#8b949e"},children:s})]},o))})]})})]})}const xe=Object.freeze(Object.defineProperty({__proto__:null,InspectorUrlGlossary:q},Symbol.toStringTag,{value:"Module"}));function Q(){const{connected:t}=O();return e.jsxs("div",{style:{background:"#0d1117",color:"#c9d1d9",minHeight:"100vh",fontFamily:"'SF Mono', 'Fira Code', 'Cascadia Code', Menlo, monospace",fontSize:13,padding:24,display:"flex",flexDirection:"column",gap:24},children:[e.jsxs("header",{style:{display:"flex",alignItems:"center",gap:12},children:[e.jsx("h1",{style:{fontSize:18,fontWeight:600,color:"#f0f6fc",margin:0},children:"Container Inspector"}),e.jsx("span",{style:{color:"#8b949e",fontSize:12},children:"Read-only view of proxy and mock engine state"}),e.jsx(P,{connected:t})]}),e.jsx(A,{}),e.jsx(H,{}),e.jsx(W,{}),e.jsx(q,{})]})}const fe=Object.freeze(Object.defineProperty({__proto__:null,ContainerInspector:Q},Symbol.toStringTag,{value:"Module"}));export{f as B,Q as C,_ as D,L as I,k as M,v as S,xe as _,C as a,q as b,I as c,D as d,F as e,B as f,U as g,W as h,H as i,z as j,A as k,P as l,de as m,ce as n,pe as o,ae as p,ie as q,le as r,ne as s,u as t,se as u,oe as v,he as w,ue as x,fe as y,re as z};

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

import{j as e}from"./markdown-C9tbsVHX.js";import{b as o}from"./react-nrLBr15I.js";import{u as se}from"./useServerIdentity-B7rI0u3O.js";function ce(t){return t.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")}function le(t){if(!t)return{base:null,sep:"/"};const r=t.lastIndexOf("/"),a=t.lastIndexOf("\\"),n=Math.max(r,a);if(n<0)return{base:null,sep:"/"};const c=n===a?"\\":"/";return{base:t.slice(0,n),sep:c}}function B({suggestedNewProjectPath:t,creating:r=!1,createError:a=null,onCreate:n,onClose:c}){const[i,x]=o.useState(""),[l,f]=o.useState(""),j=o.useRef(!1),{base:y,sep:m}=le(t),p=y?`${y}${m}`:"",w=`${p}${ce(i)}`,g=j.current?l:w;o.useEffect(()=>{const b=S=>{S.key==="Escape"&&!r&&c()};return window.addEventListener("keydown",b),()=>window.removeEventListener("keydown",b)},[r,c]);const h=b=>{j.current=!0,f(b)},N=()=>{const b=i.trim(),S=g.trim();b.length>0&&S.length>0&&!r&&n(S,b)},P=i.trim().length>0&&!r;return e.jsx("div",{role:"presentation",onClick:()=>{r||c()},className:"fixed inset-0 z-50 flex items-center justify-center bg-black/60 px-6 backdrop-blur-sm",children:e.jsxs("div",{role:"dialog","aria-modal":"true","aria-label":"Create your first project",onClick:b=>b.stopPropagation(),className:"w-full max-w-md rounded-xl border border-[var(--border-default)] bg-[var(--bg-elevated,#141414)] p-6 text-[var(--text-primary)] shadow-2xl",children:[e.jsx("h2",{className:"text-lg font-semibold text-[var(--text-primary)]",children:"Create your first project"}),e.jsx("p",{className:"mt-1 text-sm text-[var(--text-muted)]",children:"Name your project and choose where it lives. CodeYam will scaffold it and walk you through the rest."}),e.jsxs("label",{className:"mt-5 block text-xs font-medium text-[var(--text-secondary)]",children:["Project name",e.jsx("input",{type:"text",autoFocus:!0,value:i,onChange:b=>x(b.target.value),onKeyDown:b=>{b.key==="Enter"&&N()},placeholder:"My First App","aria-label":"Project name",className:"mt-1 w-full rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-2 text-sm font-normal text-[var(--text-primary)] placeholder:text-[var(--text-dim)] focus:border-[var(--accent-active)] focus:outline-none"})]}),e.jsxs("label",{className:"mt-4 block text-xs font-medium text-[var(--text-secondary)]",children:["Location",e.jsx("input",{type:"text",value:g,onChange:b=>h(b.target.value),onKeyDown:b=>{b.key==="Enter"&&N()},placeholder:p||"/path/to/new/project","aria-label":"Project location",className:"mt-1 w-full rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-2 font-mono text-sm font-normal text-[var(--text-primary)] placeholder:text-[var(--text-dim)] focus:border-[var(--accent-active)] focus:outline-none"})]}),a&&e.jsx("div",{className:"mt-3 whitespace-pre-line text-xs text-[var(--accent-red)]",children:a}),e.jsxs("div",{className:"mt-6 flex items-center justify-end gap-2",children:[e.jsx("button",{type:"button",onClick:c,disabled:r,className:"rounded border border-[var(--border-default)] px-4 py-2 text-sm text-[var(--text-secondary)] transition hover:text-[var(--text-primary)] disabled:cursor-not-allowed disabled:opacity-50",children:"Cancel"}),e.jsx("button",{type:"button",onClick:N,disabled:!P,className:"rounded bg-[var(--accent-active)] px-4 py-2 text-sm font-semibold text-[var(--bg-deep)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:r?"Creating…":"Create Project"})]})]})})}const fe=Object.freeze(Object.defineProperty({__proto__:null,CreateProjectModal:B},Symbol.toStringTag,{value:"Module"}));function R({projects:t,onClone:r,cloningRepoUrl:a=null,cloneError:n=null,defaultOpen:c=!1}){const[i,x]=o.useState(c),l=i||a!==null||n!==null;return e.jsxs("div",{className:"mt-6",children:[e.jsxs("button",{type:"button",onClick:()=>x(f=>!f),"aria-expanded":l,className:"flex w-full items-center justify-between rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-3 text-left transition hover:border-[var(--accent-active)]",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--text-primary)]",children:"Open Source Projects"}),e.jsx("span",{className:"text-xs text-[var(--text-dim)]",children:l?"▾":"▸"})]}),l&&e.jsxs(e.Fragment,{children:[e.jsx("p",{className:"mt-2 px-1 text-xs text-[var(--text-muted)]",children:"Clone one of codeyam's showcase apps and open it in the editor — nothing to configure."}),e.jsx("ul",{className:"mt-2 flex flex-col gap-3",children:t.map(f=>e.jsx(I,{project:f,busy:a===f.repoUrl,onClone:r},f.slug))}),n&&e.jsx("div",{className:"mt-3 whitespace-pre-line rounded-lg border border-[var(--accent-red)]/40 bg-[var(--bg-card)] px-4 py-3 font-mono text-xs text-[var(--accent-red)]",children:n})]})]})}function I({project:t,busy:r,onClone:a}){const{name:n,description:c,repoUrl:i,thumbnail:x}=t;return e.jsxs("li",{className:"flex items-center justify-between gap-4 rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-3",children:[e.jsxs("div",{className:"flex min-w-0 items-center gap-3",children:[e.jsx($,{src:x,name:n,size:"h-24 w-20"}),e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"truncate font-medium text-[var(--text-primary)]",children:n}),e.jsx("div",{className:"mt-1 text-xs text-[var(--text-muted)]",children:c})]})]}),e.jsx("button",{type:"button",disabled:r,onClick:()=>a(i),className:"shrink-0 rounded bg-[var(--accent-active)] px-3 py-1.5 text-sm font-medium text-[var(--bg-deep)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:r?"Cloning…":"Clone & Open"})]})}const he=Object.freeze(Object.defineProperty({__proto__:null,OpenSourceProjectRow:I,OpenSourceProjectsSection:R},Symbol.toStringTag,{value:"Module"})),ie=[{slug:"tabcommand",name:"TabCommand",description:"A Chrome extension dashboard to control your browser (React + Vite, Manifest V3).",repoUrl:"https://github.com/codeyam-ai/tabcommand",thumbnail:"/open-source/tabcommand.png",thumbnailSource:{repo:"tabcommand",scenario:"home-grouped",dimension:"desktop"}},{slug:"codeyam-counter",name:"CodeYam Counter",description:"A native SwiftUI iOS counter app with a shared AppCore SwiftPM library.",repoUrl:"https://github.com/codeyam-ai/codeyam-counter",thumbnail:"/open-source/codeyam-counter.png",thumbnailSource:{repo:"codeyam-counter",scenario:"counter-active-count",dimension:"iphone-16"}},{slug:"el-carot",name:"El Carot",description:"AI-powered tarot readings through a one-of-a-kind deck (Next.js + Prisma + Anthropic API).",repoUrl:"https://github.com/codeyam-ai/el-carot",thumbnail:"/open-source/el-carot.png",thumbnailSource:{repo:"el-carot",scenario:"landing-english",dimension:"mobile"}}];function U(t){const r=t.filter(n=>n.isCurrent),a=t.filter(n=>!n.isCurrent);return[...r,...a]}function z({projects:t,loading:r=!1,error:a=null,openError:n=null,busyPath:c=null,onOpen:i,onStop:x,onScan:l,scanning:f=!1,scanResult:j=null,scanError:y=null,onCreate:m,creating:p=!1,createError:w=null,suggestedNewProjectPath:g=null,bootstrapping:h=!1,onClone:N,cloningRepoUrl:P=null,cloneError:b=null}){const[S,_]=o.useState(!1),{version:k}=se(),F=!r&&!a&&t.length===0,E=!!m&&!(F&&m);return e.jsxs("div",{className:"flex min-h-screen flex-col items-center bg-[var(--bg-deep)] px-6 py-16 text-[var(--text-primary)]",children:[e.jsxs("div",{className:"w-full max-w-2xl",children:[e.jsx(J,{}),E&&m&&e.jsx(D,{onCreate:m,creating:p,createError:w,suggestedNewProjectPath:g}),l&&e.jsx(K,{onScan:l,scanning:f,scanResult:j,scanError:y}),h&&e.jsx(Y,{}),n&&e.jsx(X,{message:n}),r?e.jsx(G,{}):a?e.jsx(Q,{message:a}):t.length===0?e.jsx(Z,{onScan:l,scanning:f,onCreateFirst:m?()=>_(!0):void 0}):e.jsx("ul",{className:"flex flex-col gap-3",children:U(t).map(O=>e.jsx(H,{project:O,busy:c===O.path,onOpen:i,onStop:x},O.path))}),N&&e.jsx(R,{projects:ie,onClone:N,cloningRepoUrl:P,cloneError:b,defaultOpen:t.length===0}),k&&e.jsxs("div",{"data-testid":"launcher-version",className:"mt-8 text-center text-xs text-[var(--text-dim)]",children:["codeyam-editor v",k]})]}),S&&m&&e.jsx(B,{suggestedNewProjectPath:g,creating:p,createError:w,onCreate:m,onClose:()=>_(!1)})]})}function H({project:t,busy:r,onOpen:a,onStop:n}){const{name:c,path:i,exists:x,running:l,controlPort:f,tests:j,thumbnailUrl:y,isCurrent:m,protected:p}=t;return e.jsxs("li",{className:`flex items-center justify-between gap-4 rounded-lg border bg-[var(--bg-card)] px-4 py-3 ${m?"border-[var(--accent-active)]":"border-[var(--border-default)]"} ${x?"":"opacity-60"}`,children:[e.jsxs("div",{className:"flex min-w-0 items-center gap-3",children:[e.jsx($,{src:x&&!p?y:null,name:c}),e.jsxs("div",{className:"min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"truncate font-medium text-[var(--text-primary)]",children:c}),m&&e.jsx(W,{}),l&&e.jsx(q,{controlPort:f})]}),e.jsx("div",{className:"truncate text-xs text-[var(--text-dim)]",title:i,children:i}),p?e.jsx("div",{className:"mt-1 text-xs text-[var(--text-dim)]",children:"OS-protected location — not scanned in the background"}):x?e.jsx(de,{tests:j}):e.jsx("div",{className:"mt-1 text-xs text-[var(--accent-orange)]",children:"Folder missing — moved or deleted"})]})]}),e.jsx(V,{path:i,exists:x,running:l,busy:r,isCurrent:m,onOpen:a,onStop:n})]})}function J(){return e.jsxs("header",{className:"mb-8",children:[e.jsx("h1",{className:"text-2xl font-semibold text-[var(--text-primary)]",children:"Your projects"}),e.jsx("p",{className:"mt-1 text-sm text-[var(--text-muted)]",children:"Pick a project to open in the editor, or stop one that's running."})]})}function D({onCreate:t,creating:r=!1,createError:a=null,suggestedNewProjectPath:n=null,defaultOpen:c=!1}){const[i,x]=o.useState(c),[l,f]=o.useState(""),[j,y]=o.useState(""),m=o.useRef(!1);o.useEffect(()=>{m.current||n&&l===""&&f(n)},[n,l]);const p=h=>{m.current=!0,f(h)};if(!(i||r||a!==null))return e.jsx("div",{className:"mb-3",children:e.jsx("button",{type:"button",onClick:()=>x(!0),className:"text-sm text-[var(--text-secondary)] underline-offset-2 transition hover:text-[var(--text-primary)] hover:underline",children:"+ New project…"})});const g=()=>{const h=l.trim();h.length>0&&!r&&t(h,j.trim())};return e.jsxs("div",{className:"mb-3 rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-4",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"text",value:l,onChange:h=>p(h.target.value),onKeyDown:h=>{h.key==="Enter"&&g()},placeholder:"/path/to/new/project","aria-label":"Folder for the new project",className:"min-w-0 flex-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-dim)] focus:border-[var(--accent-active)] focus:outline-none"}),e.jsx("button",{type:"button",disabled:r||l.trim().length===0,onClick:g,className:"shrink-0 rounded bg-[var(--accent-active)] px-3 py-1.5 text-sm font-medium text-[var(--bg-deep)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:r?"Creating…":"Create"})]}),e.jsx("input",{type:"text",value:j,onChange:h=>y(h.target.value),onKeyDown:h=>{h.key==="Enter"&&g()},placeholder:"Project name (optional)","aria-label":"Project name (optional)",className:"mt-2 w-full rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-dim)] focus:border-[var(--accent-active)] focus:outline-none"}),a&&e.jsx("div",{className:"mt-2 whitespace-pre-line text-xs text-[var(--accent-red)]",children:a})]})}function K({onScan:t,scanning:r=!1,scanResult:a=null,scanError:n=null}){const[c,i]=o.useState(!1),[x,l]=o.useState(""),[f,j]=o.useState(!0);if(!(c||r||a!==null||n!==null))return e.jsx("div",{className:"mb-6",children:e.jsx("button",{type:"button",onClick:()=>i(!0),className:"text-sm text-[var(--text-secondary)] underline-offset-2 transition hover:text-[var(--text-primary)] hover:underline",children:"+ Add projects from a folder…"})});const m=()=>{const p=x.trim();p.length>0&&!r&&t(p,f)};return e.jsxs("div",{className:"mb-6 rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-4",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"text",value:x,onChange:p=>l(p.target.value),onKeyDown:p=>{p.key==="Enter"&&m()},placeholder:"/path/to/your/workspace","aria-label":"Folder to scan for projects",className:"min-w-0 flex-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-dim)] focus:border-[var(--accent-active)] focus:outline-none"}),e.jsx("button",{type:"button",disabled:r||x.trim().length===0,onClick:m,className:"shrink-0 rounded bg-[var(--accent-active)] px-3 py-1.5 text-sm font-medium text-[var(--bg-deep)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:r?"Scanning…":"Scan"})]}),e.jsxs("label",{className:"mt-2 flex items-center gap-2 text-xs text-[var(--text-muted)]",children:[e.jsx("input",{type:"checkbox",checked:f,onChange:p=>j(p.target.checked)}),"Remember this folder and re-scan it next time"]}),n&&e.jsx("div",{className:"mt-2 text-xs text-[var(--accent-red)]",children:n}),a&&!n&&e.jsxs("div",{className:"mt-2 text-xs text-[var(--accent-green)]",children:["Found ",a.found," project",a.found===1?"":"s",", ",a.added," new"]}),(a==null?void 0:a.note)&&!n&&e.jsx("div",{className:"mt-1 text-xs text-[var(--text-dim)]",children:a.note})]})}function Y(){return e.jsx("div",{className:"mb-6 rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-3 text-sm text-[var(--text-muted)]",children:"Looking for your projects…"})}function V({path:t,exists:r,running:a,busy:n,isCurrent:c=!1,onOpen:i,onStop:x}){const l=n?"Opening…":c?"Open this project":a?"Reopen":"Open";return e.jsxs("div",{className:"flex shrink-0 items-center gap-2",children:[a&&r&&e.jsx("button",{type:"button",disabled:n,onClick:()=>x(t),className:"rounded border border-[var(--border-default)] px-3 py-1.5 text-sm text-[var(--text-secondary)] transition hover:border-[var(--accent-red)] hover:text-[var(--accent-red)] disabled:cursor-not-allowed disabled:opacity-50",children:n?"Stopping…":"Stop"}),e.jsx("button",{type:"button",disabled:!r||n,onClick:()=>i(t),className:"rounded bg-[var(--accent-active)] px-3 py-1.5 text-sm font-medium text-[var(--bg-deep)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",children:l})]})}function W(){return e.jsx("span",{className:"inline-flex items-center rounded-full bg-[var(--accent-active)]/15 px-2 py-0.5 text-xs font-medium text-[var(--accent-active)]",children:"Current"})}function $({src:t,name:r,size:a="h-12 w-16"}){const[n,c]=o.useState(!1);o.useEffect(()=>{c(!1)},[t]);const i=!!t&&!n;return e.jsx("div",{className:`flex ${a} shrink-0 items-center justify-center overflow-hidden rounded border border-[var(--border-subtle)] bg-[var(--bg-surface)]`,children:i?e.jsx("img",{src:t??void 0,alt:`${r} preview`,className:"max-h-full max-w-full object-contain",onError:()=>c(!0)}):e.jsx("div",{className:"flex h-full w-full items-center justify-center text-[10px] text-[var(--text-dim)]",children:"no preview"})})}function de({tests:t}){return!t||t.total===0?e.jsx("div",{className:"mt-1 text-xs text-[var(--text-dim)]",children:"No tests yet"}):e.jsxs("div",{className:"mt-1 flex items-center gap-2 text-xs",children:[e.jsxs("span",{className:"text-[var(--text-muted)]",children:[t.total," tests"]}),t.passed>0&&e.jsxs("span",{className:"text-[var(--accent-green)]",children:[t.passed," passed"]}),t.failed>0&&e.jsxs("span",{className:"text-[var(--accent-red)]",children:[t.failed," failed"]})]})}function q({controlPort:t}){return e.jsxs("span",{className:"inline-flex items-center gap-1.5 rounded-full bg-[var(--accent-green)]/15 px-2 py-0.5 text-xs text-[var(--accent-green)]",children:[e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-[var(--accent-green)]"}),"running",t?` · :${t}`:""]})}function G(){return e.jsx("div",{className:"rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-6 text-sm text-[var(--text-muted)]",children:"Loading projects…"})}function Q({message:t}){return e.jsxs("div",{className:"whitespace-pre-line rounded-lg border border-[var(--accent-red)]/40 bg-[var(--bg-card)] px-4 py-6 text-sm text-[var(--accent-red)]",children:["Couldn't load projects: ",t]})}function X({message:t}){return e.jsx("div",{className:"mb-3 whitespace-pre-line rounded-lg border border-[var(--accent-red)]/40 bg-[var(--bg-card)] px-4 py-3 font-mono text-xs text-[var(--accent-red)]",children:t})}function Z({onScan:t,scanning:r=!1,onCreateFirst:a}={}){return a?e.jsxs("div",{className:"flex flex-col items-center rounded-lg border border-[var(--border-default)] bg-[var(--bg-card)] px-6 py-16 text-center",children:[e.jsx("h2",{className:"text-xl font-semibold text-[var(--text-primary)]",children:"Create Your First Project"}),e.jsx("p",{className:"mt-2 max-w-sm text-sm text-[var(--text-muted)]",children:"CodeYam will walk you through creating your first project. You just need an idea to get started"}),e.jsx("button",{type:"button",onClick:a,className:"mt-6 rounded bg-[var(--accent-active)] px-5 py-2.5 text-sm font-semibold text-[var(--bg-deep)] transition hover:opacity-90",children:"Create Project"})]}):e.jsx("div",{className:"rounded-lg border border-dashed border-[var(--border-default)] bg-[var(--bg-card)] px-4 py-10 text-center",children:e.jsx("p",{className:"text-sm text-[var(--text-muted)]",children:t?e.jsxs(e.Fragment,{children:["No projects yet."," ",r?"Scanning for projects…":"Add a folder above to scan for projects already on disk, or run"," ",!r&&e.jsx("code",{className:"rounded bg-[var(--bg-input)] px-1.5 py-0.5 text-xs text-[var(--text-secondary)]",children:"codeyam-editor init"}),!r&&" in a project folder."]}):e.jsxs(e.Fragment,{children:["No projects yet. Run"," ",e.jsx("code",{className:"rounded bg-[var(--bg-input)] px-1.5 py-0.5 text-xs text-[var(--text-secondary)]",children:"codeyam-editor init"})," ","in a project folder to get started."]})})})}const ve=Object.freeze(Object.defineProperty({__proto__:null,CurrentBadge:W,ProjectBootstrapBanner:Y,ProjectCreateForm:D,ProjectLauncher:z,ProjectLauncherEmpty:Z,ProjectLauncherError:Q,ProjectLauncherHeader:J,ProjectLauncherLoading:G,ProjectOpenError:X,ProjectRow:H,ProjectRowActions:V,ProjectScanForm:K,RunningBadge:q,Thumbnail:$,sortCurrentFirst:U},Symbol.toStringTag,{value:"Module"})),ue=3e3,L=305e3;async function A(t,r,a){const n=new AbortController,c=setTimeout(()=>n.abort(),a);try{return await fetch(t,{...r,signal:n.signal})}finally{clearTimeout(c)}}function M(t,r){return t instanceof DOMException&&t.name==="AbortError"?"Opening timed out — the project may not be responding.":t instanceof Error?t.message:r}function xe(){const[t,r]=o.useState(null),[a,n]=o.useState(null),[c,i]=o.useState(null),[x,l]=o.useState(null),[f,j]=o.useState(!1),[y,m]=o.useState(null),[p,w]=o.useState(null),[g,h]=o.useState(!1),[N,P]=o.useState(null),[b,S]=o.useState(!1),[_,k]=o.useState(null),[F,E]=o.useState(null),[O,ee]=o.useState(null),C=o.useCallback(async()=>{try{const d=await fetch("/api/projects");if(!d.ok)throw new Error(`HTTP ${d.status}`);const s=await d.json();r(Array.isArray(s.projects)?s.projects:[]),ee(typeof s.suggestedNewProjectPath=="string"?s.suggestedNewProjectPath:null),n(null)}catch(d){n(d instanceof Error?d.message:"Failed to load projects")}},[]),T=o.useCallback(async()=>{try{const d=await fetch("/api/projects/scan-status");if(!d.ok)return;const u=!!(await d.json()).running;S(v=>(v&&!u&&C(),u))}catch{}},[C]);o.useEffect(()=>{C(),T();const d=setInterval(()=>{document.visibilityState==="visible"&&(C(),T())},ue),s=()=>{document.visibilityState==="visible"&&(C(),T())};return document.addEventListener("visibilitychange",s),()=>{clearInterval(d),document.removeEventListener("visibilitychange",s)}},[C,T]);const te=o.useCallback(async(d,s)=>{j(!0),w(null),m(null);try{const u=await fetch("/api/projects/scan",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({root:d,remember:s})}),v=await u.json().catch(()=>({}));if(!u.ok)throw new Error(typeof v.error=="string"?v.error:`HTTP ${u.status}`);r(Array.isArray(v.projects)?v.projects:[]),m({found:v.found??0,added:v.added??0,note:typeof v.note=="string"?v.note:void 0})}catch(u){w(u instanceof Error?u.message:"Scan failed")}finally{j(!1)}},[]),re=o.useCallback(async(d,s)=>{h(!0),P(null);try{const u=await A("/api/projects/create",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s?{path:d,name:s}:{path:d})},L),v=await u.json().catch(()=>({}));if(!u.ok)throw new Error(typeof v.error=="string"?v.error:`HTTP ${u.status}`);if(typeof v.url=="string"){window.location.href=v.url;return}throw new Error("create did not return a url")}catch(u){P(M(u,"Failed to create project")),h(!1)}},[]),ae=o.useCallback(async d=>{k(d),E(null);try{const s=await A("/api/projects/clone",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({repoUrl:d})},L),u=await s.json().catch(()=>({}));if(!s.ok)throw new Error(typeof u.error=="string"?u.error:`HTTP ${s.status}`);if(typeof u.url=="string"){window.location.href=u.url;return}throw new Error("clone did not return a url")}catch(s){E(M(s,"Failed to clone project")),k(null)}},[]),ne=o.useCallback(async d=>{l(d),i(null);try{const s=await A("/api/projects/open",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:d})},L),u=await s.json().catch(()=>({}));if(!s.ok)throw new Error(typeof u.error=="string"?u.error:`HTTP ${s.status}`);const{url:v}=u;if(typeof v=="string"){window.location.href=v;return}throw new Error("open did not return a url")}catch(s){i(M(s,"Failed to open project")),l(null)}},[]),oe=o.useCallback(async d=>{l(d);try{const s=await fetch("/api/projects/stop",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:d})});if(!s.ok)throw new Error(`HTTP ${s.status}`);await C()}catch(s){n(s instanceof Error?s.message:"Failed to stop project")}finally{l(null)}},[C]);return e.jsx(z,{projects:t??[],loading:t===null&&a===null,error:a,openError:c,busyPath:x,onOpen:ne,onStop:oe,onScan:te,scanning:f,scanResult:y,scanError:p,onCreate:re,creating:g,createError:N,suggestedNewProjectPath:O,bootstrapping:b,onClone:ae,cloningRepoUrl:_,cloneError:F})}const je=Object.freeze(Object.defineProperty({__proto__:null,ProjectLauncherScreen:xe},Symbol.toStringTag,{value:"Module"}));export{B as C,I as O,xe as P,q as R,$ as T,je as _,R as a,X as b,Z as c,Q as d,G as e,J as f,W as g,Y as h,D as i,K as j,V as k,H as l,z as m,ve as n,he as o,fe as p};

Sorry, the diff of this file is too big to display

import{j as c}from"./markdown-C9tbsVHX.js";import{b as o}from"./react-nrLBr15I.js";import{S as l}from"./ScenarioDataPanel-wBhNIA9N.js";function m({slug:t}){const[r,i]=o.useState(void 0);return o.useEffect(()=>{let n=!1;return fetch(`/api/scenarios/${encodeURIComponent(t)}`).then(e=>e.ok?e.json():null).then(e=>{if(n||!e)return;const a=typeof e=="object"&&e!==null&&"name"in e?String(e.name):void 0;i(a),a&&(document.title=`${a} · data`)}).catch(()=>{}),()=>{n=!0}},[t]),c.jsx(l,{slug:t,scenarioName:r,variant:"fullPage"})}const p=Object.freeze(Object.defineProperty({__proto__:null,ScenarioDataPanelFullPage:m},Symbol.toStringTag,{value:"Module"}));export{m as S,p as _};

Sorry, the diff of this file is too big to display

import{b as i}from"./react-nrLBr15I.js";function a(t){return t==="posix"||t==="powershell"?t:null}function f(){const[t,r]=i.useState({projectDir:null,inContainer:null,version:null,shellDialect:null,loaded:!1});return i.useEffect(()=>{let l=!1;return fetch("/api/codeyam-server-identity").then(n=>n.ok?n.json():null).then(n=>{if(l)return;const e=n&&typeof n=="object"?n:null,o=e&&typeof e.projectDir=="string"?e.projectDir:null,s=e&&typeof e.inContainer=="boolean"?e.inContainer:null,c=e&&typeof e.version=="string"?e.version:null,u=a(e==null?void 0:e.shellDialect);r({projectDir:o,inContainer:s,version:c,shellDialect:u,loaded:!0})}).catch(()=>{l||r({projectDir:null,inContainer:null,version:null,shellDialect:null,loaded:!0})}),()=>{l=!0}},[]),t}export{f as u};

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display