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.6
to
0.1.7
+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 };
'use strict';
// The fleet dashboard's two LB-path HTTP probe transports, extracted from
// scripts/operator/fleet-dashboard/server.js so their SOCKET LIFETIME is
// testable.
//
// The undici contract, which is the whole reason this module exists: a `fetch`
// response body is a stream that holds its connection open until it is read to
// completion or explicitly cancelled. IGNORING `res.body` is NOT the same as
// releasing it — the socket stays checked out of the agent's pool, and once the
// peer sends FIN it parks in CLOSE-WAIT forever. Node's `http` module is the
// opposite (an unread response pauses the socket, hence the `res.resume(); //
// drain so the socket can close` in server.js's localGetJson), and that
// asymmetry is what got missed when the fetch path was added.
//
// On 2026-08-01 that leaked 10,749 CLOSE-WAIT sockets to the fleet LB
// (8.228.227.92:443) holding 128MB of unread IAP-login HTML in kernel receive
// queues — ~460 sockets/hour, a ~1-day fuse — driving the dashboard cgroup into
// swap-thrash until the node event loop stalled in `D` state and dashboard-bes
// went UNHEALTHY while systemd still reported the unit active.
//
// So: EVERY return path here consumes or cancels the body. `fetch` is injected
// rather than closed over, so a test can drive every branch with a body that
// records whether it was cancelled.
const fpt = require('./fleet-vm-probe-target');
/** Release the connection behind a response we are not going to read.
*
* Swallows its own failure deliberately. `cancel()` rejects when the body was
* already consumed or already errored — in both cases the connection is
* released anyway, so there is nothing to recover from and nothing to report.
* Letting it throw would be actively harmful: these calls sit inside the
* transports' `try`, so a rejection would be caught by the transport's own
* catch and misclassify a perfectly good HTTP response as `unreachable`.
*/
async function disposeBody(res) {
try {
await res.body?.cancel();
} catch {
// Already consumed, already errored, or no body at all — connection is
// released either way.
}
}
/** GET JSON from a resolved LB target.
*
* Uses redirect:'manual' so IAP's 302→accounts.google.com surfaces as a
* classifiable response instead of the fetch silently following it and handing
* us a login page that fails JSON parsing — which would read as "VM is down"
* when the real fault is our token. That 302 carries a full HTML login page,
* which is exactly why the non-ok return below must dispose of the body.
*
* `deps` injects the transport's collaborators for tests: `fetchImpl` (default
* global fetch) and `warn` (default console.warn).
*/
async function lbGetJson(target, timeout, deps = {}) {
const { fetchImpl = globalThis.fetch, warn = console.warn } = deps;
const ctl = new AbortController();
const timer = setTimeout(() => ctl.abort(), timeout);
try {
const res = await fetchImpl(target.url, {
headers: target.headers,
redirect: 'manual',
signal: ctl.signal,
});
const outcome = fpt.classifyProbeResponse({
status: res.status,
location: res.headers.get('location'),
});
if (outcome !== 'ok') {
// 401 and 403 share one outcome but have opposite fixes (OAuth client vs
// IAM grant). Name which one we hit, so the next misconfig is diagnosable
// from the log instead of a live curl against the backend.
const why = fpt.describeAuthMisconfig(res.status);
if (why) warn(`probe ${target.url}: HTTP ${res.status} — ${why}`);
await disposeBody(res);
return { outcome, json: null, status: res.status };
}
// `status` rides on every return so a caller can NAME the HTTP status in a
// fault warning. Without it a reachable-but-unusable body was indiscernible
// from a dead VM in the log — see probeBodyFault.
//
// Both branches below are body-safe without an explicit cancel: `res.text()`
// reads the stream to completion, so the connection is already released by
// the time JSON.parse runs or throws.
try { return { outcome: 'ok', json: JSON.parse(await res.text()), status: res.status }; }
catch { return { outcome: 'unreachable', json: null, error: 'bad-json', status: res.status }; }
} catch (err) {
// No response to dispose of — the request never produced one.
return {
outcome: 'unreachable',
json: null,
error: err && err.name === 'AbortError' ? 'timeout' : 'error',
};
} finally {
clearTimeout(timer);
}
}
/** Reachability-only transport for the PUBLIC preview ingress over the IAP-LB
* seam.
*
* Unlike lbGetJson it does not PARSE a body — the preview root returns HTML, so
* routing it through lbGetJson would fail-close on the non-JSON parse. It must
* still DISPOSE of that body on every path; "does not parse" was historically
* written as "does not touch", and that is precisely what leaked.
*
* Classifies purely on the HTTP response:
* any non-auth HTTP response (200, or even 404/502 — the LB routed to a
* backend that answered) → true (ingress wired);
* auth-misconfigured (302→Google login / 401 / 403) → null (UNKNOWN, never
* false): the preview backend is a SEPARATE IAP backend
* service whose audience/accessor may differ from the
* editor backend the operator token is minted for, so an
* auth challenge here proves nothing about the URL wiring;
* transport error / timeout → false (nothing answered).
*
* Uses redirect:'manual' so an IAP login 302 surfaces as classifiable instead
* of fetch silently following it to a login page.
*/
async function lbProbeReachable(target, timeout, deps = {}) {
const { fetchImpl = globalThis.fetch, warn = console.warn } = deps;
const ctl = new AbortController();
const timer = setTimeout(() => ctl.abort(), timeout);
try {
const res = await fetchImpl(target.url, {
headers: target.headers,
redirect: 'manual',
signal: ctl.signal,
});
const outcome = fpt.classifyProbeResponse({
status: res.status,
location: res.headers.get('location'),
});
if (outcome === 'auth-misconfigured') {
const why = fpt.describeAuthMisconfig(res.status);
if (why) warn(`preview ingress ${target.url}: HTTP ${res.status} — ${why}`);
await disposeBody(res);
return null;
}
await disposeBody(res);
return true;
} catch {
// No response to dispose of — the request never produced one.
return false;
} finally {
clearTimeout(timer);
}
}
module.exports = {
disposeBody,
lbGetJson,
lbProbeReachable,
};
'use strict';
// Pure helpers for the fleet dashboard's DEPLOYED-REVISION staleness signal
// (scripts/operator/fleet-dashboard/server.js requires this; unit tests in
// npm/fleet-revision.test.js).
//
// The dashboard header has always shown the branch name, which says nothing
// about WHICH COMMIT of that branch is serving. On 2026-07-31 the operator
// host's /opt/codeyam-editor sat 104 commits behind origin/editor-improvements-66,
// so the deployed dashboard predated its own memory fix and was
// indistinguishable from one at tip — the third recurrence of a landed fix
// surviving because the served checkout was behind (the June VM-5 slug bug was
// the same shape).
//
// server.js owns the I/O — the bounded `git fetch` / `rev-parse` / `rev-list`
// children and the slow-cycle memo. The two rules that actually matter are here
// and are pure: how a failed check merges over the last good one, and what
// verdict a given count means. Both were previously inline in an async IIFE
// that no test could reach.
/** Merge one revision-check result over the previous memo.
*
* THE RULE: a failed check RETAINS the previous `behind` and records `error` —
* it never reports zero. This is what makes the staleness chip sticky. A
* broken `git fetch` that reset the count to 0 would present a stale dashboard
* as current, which is precisely the failure this whole signal exists to catch;
* degrading to "last known count, plus a note that the check is failing" is
* the honest answer.
*
* A `behind` that is absent, negative, or unparseable counts as no reading and
* falls back to the previous value the same way. `sha` and `branch` likewise
* retain their last known values rather than blanking the header on a hiccup.
*
* A previous `behind` of null (never successfully counted) stays null — it is
* deliberately NOT coerced through Number(), because `Number(null) === 0` is
* the exact coercion that made "no reading" and "zero" indistinguishable in
* readCgroupMemory.
*
* Pure; no I/O. */
function mergeDashboardRevision({ prev, sha, branch, behind, checkedAt, error } = {}) {
const base = (prev && typeof prev === 'object') ? prev : {};
const str = (next, fallback) => ((typeof next === 'string' && next) ? next : (typeof fallback === 'string' && fallback ? fallback : null));
// Strictly a number: a string or null is NOT coerced, so a caller cannot
// accidentally turn "no reading" into 0 the way Number(null) would.
const count = (v) => (typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : null);
const counted = count(behind);
return {
sha: str(sha, base.sha),
branch: str(branch, base.branch),
behind: counted === null ? count(base.behind) : counted,
checkedAt: str(checkedAt, base.checkedAt),
error: str(error, null),
};
}
/** What does the current revision memo mean for the operator?
*
* Split out from the header chip so the verdict is a readable table rather
* than branches buried in string concatenation — the same reason
* operatorAuthRecovery was split out of operatorAuthBannerHtml, and the same
* reason resolveCgroupMemoryDir was split out of readCgroupMemory: logic that
* can only be reached through a browser inline script or a real cgroup
* filesystem is logic that ships its bugs green.
*
* Levels:
* 'stale' — behind > 0. Landed fixes are not live; the chip warns.
* 'current' — behind === 0. Clears the chip, and clears it even when the
* latest check errored: the count reaching zero is the ONLY
* thing that clears the warning, and once it has, a later
* failed fetch has no stale count to keep showing.
* 'unknown' — no count has ever succeeded AND the check is failing. Worth
* saying out loud rather than rendering the reassuring nothing
* that an at-tip dashboard renders.
* 'unchecked' — no count yet and nothing has failed (the boot window before
* the first slow-cycle refresh). Renders nothing.
*
* Pure; no I/O. */
function classifyDashboardStaleness({ behind, error } = {}) {
const counted = (typeof behind === 'number' && Number.isFinite(behind) && behind >= 0) ? behind : null;
if (counted === null) return error ? 'unknown' : 'unchecked';
return counted > 0 ? 'stale' : 'current';
}
/** The `/data` view of the revision memo, with the staleness VERDICT resolved
* server-side.
*
* The chip is then a pure renderer: `public/index.html` is an inline browser
* script with no module boundary, so any decision left in it could only ever
* be source-scan tested — assertions on text rather than on behavior.
*
* Lives here rather than in `server.js` for the same reason
* `classifyDashboardStaleness` does: `server.js` has top-level side effects on
* require (createServer/listen/setInterval), so nothing there can be executed
* by a test and a projection defined inside it ships its bugs green.
*
* Pure; no I/O. */
function projectDashboardRevision(memo = {}) {
return { ...memo, level: classifyDashboardStaleness(memo) };
}
module.exports = {
mergeDashboardRevision,
classifyDashboardStaleness,
projectDashboardRevision,
};
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-VuyqBbGJ.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

import{j as e}from"./markdown-C9tbsVHX.js";import{b as o}from"./react-nrLBr15I.js";import{C as _,a as ce,c as le}from"./useServerIdentity-BdXy8TQv.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-DjprZq2L.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 l}from"./markdown-C9tbsVHX.js";import{b as s}from"./react-nrLBr15I.js";async function x(e){try{if(typeof navigator<"u"&&navigator.clipboard&&typeof navigator.clipboard.writeText=="function")return await navigator.clipboard.writeText(e),!0}catch{}return m(e)}function m(e){var t;if(typeof document>"u"||!document.body)return!1;const n=document.activeElement,r=document.createElement("textarea");r.value=e,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(),(t=n==null?void 0:n.focus)==null||t.call(n)}return o}const b=async e=>{if(!await x(e))throw new Error("clipboard write failed")};function h(e={}){const{writer:n=b,resetMs:r=2e3}=e,[o,t]=s.useState(!1),[a,c]=s.useState(!1),i=s.useRef(null);s.useEffect(()=>()=>{i.current!==null&&clearTimeout(i.current)},[]);const d=s.useCallback(async f=>{let u=!0;try{await n(f)}catch{u=!1}return t(u),c(!u),i.current!==null&&clearTimeout(i.current),i.current=setTimeout(()=>{t(!1),c(!1),i.current=null},r),u},[n,r]);return{copied:o,failed:a,copy:d}}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:e,label:n="Copy",copiedLabel:r="Copied!",failedLabel:o="Copy blocked",variant:t="danger",ariaLabel:a="Copy error message",writer:c,className:i}){const{copied:d,failed:f,copy:u}=h({writer:c}),p=t==="icon";return l.jsx("button",{type:"button",onClick:()=>{u(e)},disabled:e.length===0,title:p?f?o:n:void 0,"aria-label":p?a:void 0,className:i??v[t],children:p?C(d,f):f?o:d?r:n})}function C(e,n){return n?l.jsx("span",{"aria-hidden":"true",children:"!"}):e?l.jsx("span",{"aria-hidden":"true",children:"✓"}):l.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:[l.jsx("rect",{x:"5.5",y:"5.5",width:"8",height:"8",rx:"1.5"}),l.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 g({text:e,copyText:n,className:r="text-xs text-[var(--text-danger)]",rowClassName:o="",testId:t,writer:a}){return e?l.jsxs("div",{role:"alert","data-testid":t,className:`flex items-start gap-1.5 min-w-0 ${o}`,children:[l.jsx("span",{className:`min-w-0 ${r}`,children:e}),l.jsx(y,{variant:"icon",text:n??e,writer:a})]}):null}const S=Object.freeze(Object.defineProperty({__proto__:null,CopyableErrorText:g},Symbol.toStringTag,{value:"Module"}));function j(e){return e==="posix"||e==="powershell"?e:null}function T(){const[e,n]=s.useState({projectDir:null,inContainer:null,version:null,shellDialect:null,loaded:!1});return s.useEffect(()=>{let r=!1;return fetch("/api/codeyam-server-identity").then(o=>o.ok?o.json():null).then(o=>{if(r)return;const t=o&&typeof o=="object"?o:null,a=t&&typeof t.projectDir=="string"?t.projectDir:null,c=t&&typeof t.inContainer=="boolean"?t.inContainer:null,i=t&&typeof t.version=="string"?t.version:null,d=j(t==null?void 0:t.shellDialect);n({projectDir:a,inContainer:c,version:i,shellDialect:d,loaded:!0})}).catch(()=>{r||n({projectDir:null,inContainer:null,version:null,shellDialect:null,loaded:!0})}),()=>{r=!0}},[]),e}export{g as C,S as _,y as a,k as b,T as c,x as d,h as u};
+123
-14

@@ -99,2 +99,63 @@ 'use strict';

// The categories that describe an add whose outcome is NOT yet proven bad — the
// calm, in-flight side of the classifier. `converging` is a rostered box whose
// 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']);
// Is a non-zero add outcome CORROBORATED by a durable fact — i.e. do we have
// positive proof of death, rather than merely an absence of proof of life?
//
// This is the conjunction that gates the red `unknown` verdict. Before it, the
// classifier's fallthrough treated "no recognized success signature" as failure,
// so "we don't know" rendered as a terminal red "Add failed" telling the operator
// to destroy a VM whose container was Up and whose editor answered /api/health
// 200 (VM-4, 2026-07-31). Red now requires BOTH halves:
//
// 1. A non-zero exit code. A *missing* code is not proof of death — today it
// usually means the wrapper was signal-killed while its provision ran on,
// which is exactly the case that was misreading as failure. `null`,
// `undefined`, a non-integer, and 0 all fail this half.
// 2. At least one durable corroborating fact:
// - `instanceAlive === false` GCP says the instance is gone.
// - `containerHealthy === false` the in-VM editor was probed and is not
// answering — the box exists but never came up.
// - `wiringDeadlineBlown` the wire-add-tail gave up (the failed
// marker is the tail's own honest deadline signal).
//
// Every fact is TRI-STATE on purpose: `undefined`/`null` means "not probed" and
// corroborates nothing. Only an explicit `false` counts, so an unprobed VM can
// never be escalated to red by absence of evidence.
function addFailureCorroborated({ exitCode, instanceAlive, containerHealthy, wiringDeadlineBlown } = {}) {
const code = exitCode == null ? NaN : Number(exitCode);
if (!Number.isInteger(code) || code === 0) return false;
return instanceAlive === false || containerHealthy === false || !!wiringDeadlineBlown;
}
// Resolve the two independent health signals into the single tri-state
// `containerHealthy` fact addFailureCorroborated consumes. Pure so the precedence
// — which decides whether a VM can be escalated to red — is unit-tested; the
// server (addFailureFacts) supplies the live signals.
//
// reachable the editor answered over the LB.
// inVmHealthy the STRAND_PROBE /api/health read from INSIDE the container.
//
// LB reachability wins when present: it is the stronger witness (it proves the
// whole URL→ingress→backend path, not just the process). Otherwise the in-VM
// witness answers — this is the still-wiring case, where the LB necessarily
// cannot see a VM whose ingress is what we are waiting on, yet the editor itself
// is demonstrably up.
//
// Returns `null` when NEITHER signal was gathered. That is the load-bearing case:
// an unprobed VM must corroborate nothing, so it can never be escalated to red by
// absence of evidence. `false` is reserved for a VM that was genuinely probed and
// did not answer.
function containerHealthWitness({ reachable, inVmHealthy } = {}) {
if (reachable) return true;
if (inVmHealthy != null) return !!inVmHealthy;
return null;
}
// Build a verdict, stamping `retryable` from the single-source-of-truth Set so it

@@ -112,4 +173,13 @@ // can never drift from the category.

// log was unreadable — must never throw)
// exitCode the provision's exit code (number; used for signal-kill detection)
// exitCode the provision's exit code (number; used for signal-kill detection
// and as the first half of the proof-of-death conjunction)
//
// The remaining inputs are the durable facts the server gathers (the module
// stays pure — it never probes). All are tri-state; omit any that was not
// probed and it corroborates nothing. See addFailureCorroborated.
//
// instanceAlive does GCP still list the instance?
// containerHealthy did the in-VM editor answer /api/health?
// wiringDeadlineBlown did the add-time wire tail drop its failed marker?
//
// Returns { category, headline, recovery, retryable, files? }.

@@ -123,3 +193,3 @@ //

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

@@ -224,5 +294,30 @@

// 9. unknown — fallback. Never invite an unproven retry.
return buildFailureVerdict('unknown', 'Add failed',
'The add failed for an unclassified reason — inspect the transcript below, then reconcile or destroy this VM before re-adding.');
// 9. The residue: no failure signature above matched. The polarity here is
// INVERTED from the original design. It used to read "no recognized success
// signature ⇒ failed", which made red the DEFAULT for "we don't know" — so a
// VM whose container was Up and whose editor answered /api/health 200 got a
// terminal red "Add failed" card advising the operator to destroy it (VM-4,
// 2026-07-31; the wrapper had been killed mid-flight, so its transcript was
// truncated before CY_ROSTER and it never reached the `converging` arm above).
//
// Red now requires positive proof of death, and everything else is in-flight:
//
// 9a. unknown — a non-zero exit CORROBORATED by a durable fact (see
// addFailureCorroborated). The box is provably dead or provably never
// came up, so the loud, terminal escalation is warranted and unchanged.
// Still never invites an unproven retry.
if (addFailureCorroborated({ exitCode, instanceAlive, containerHealthy, wiringDeadlineBlown })) {
return buildFailureVerdict('unknown', 'Add failed',
'The add failed for an unclassified reason and the VM is confirmed dead or never came up — inspect the transcript below, then reconcile or destroy this VM before re-adding.');
}
// 9b. verifying — the honest default. No failure signature, and nothing
// durable proves this VM is dead: it may still be provisioning or
// wiring, and the ingress can keep propagating for minutes after the
// wrapper is gone. Calm copy, transcript still one click away. NOT
// retryable — nothing is proven about the box either way, so a one-click
// retry could still duplicate a live GCP instance (the ADD_FAILURE_RETRYABLE
// grounds are unchanged by this inversion).
return buildFailureVerdict('verifying', 'Add outcome unconfirmed',
'No failure signature was found in the transcript and nothing proves this VM is dead, so the add may still be converging — the ingress keeps propagating for minutes after the provision wrapper exits. Inspect the transcript below; reconcile this VM only if it is still unresolved past the wiring deadline.');
}

@@ -245,13 +340,27 @@

// Pure badge-hold predicate for deriveStatus (server.js): hold the calm in-flight
// `add-converging` card — instead of the red add-failed — ONLY when the add was
// classified `converging` AND its detached first-wiring is still in flight. Once
// the wire-add-tail fails/deadlines (`addTailFailed`) or is gone (`addTailInflight`
// false), or the category is anything else, return false so the caller falls
// through to the red add-failed status — the operator-chosen escalation. Kept pure
// (like canRetryAdd / canClearBadge) so the badge decision is unit-tested without a
// card — instead of the red add-failed — while the add is still converging.
//
// The hold used to require `addTailInflight`, i.e. that the detached wire-add-tail
// PROCESS was still alive. That premise was wrong: the tail's exit says nothing
// about LB convergence, because the EXTERNAL_MANAGED backend services keep
// propagating for minutes afterwards. VM-4's backends were still being created at
// 18:17-18:20Z, long after its job had been declared failed at 18:15Z — the tail
// had exited, so the calm hold released and the card went red on a healthy VM.
//
// The hold is now driven by the question that actually matters: is this add still
// converging, and has its wiring deadline genuinely blown? `wiringDeadlineBlown`
// (the failed marker the tail drops when it gives up) stays the release signal —
// tail liveness is no longer a requirement for calm, so the calm state survives
// the multi-minute propagation window instead of ending at process exit.
//
// Both calm categories qualify (see CALM_ADD_CATEGORIES): a rostered box that is
// still wiring, and the unproven residue. Any proven-failure category returns
// false and falls through to the red add-failed status — the operator-chosen
// escalation, which this predicate narrows but never softens. Kept pure (like
// canRetryAdd / canClearBadge) so the badge decision is unit-tested without a
// live dashboard.
function holdConvergingBadge({ category, addTailInflight, addTailFailed } = {}) {
return category === 'converging' && !!addTailInflight && !addTailFailed;
function holdConvergingBadge({ category, wiringDeadlineBlown } = {}) {
return CALM_ADD_CATEGORIES.has(category) && !wiringDeadlineBlown;
}
module.exports = { classifyAddFailure, extractDriftFiles, lastNonEmptyLines, canRetryAdd, transcriptRostered, holdConvergingBadge, ADD_FAILURE_RETRYABLE };
module.exports = { classifyAddFailure, extractDriftFiles, lastNonEmptyLines, canRetryAdd, transcriptRostered, holdConvergingBadge, addFailureCorroborated, containerHealthWitness, CALM_ADD_CATEGORIES, ADD_FAILURE_RETRYABLE };

@@ -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,
};
+28
-12

@@ -18,17 +18,33 @@ 'use strict';

//
// job the carried job record ({ state, action, ... }) or null
// reachable the VM's editor answered this poll (true = healthy / responding)
// drifted a project-dir-drift signal is present (reachable-but-unwired /
// half-provisioned add — genuinely needs-recovery, owned by the
// companion reconcile-inflight-jobs plan; never falsely cleared here)
// offBranch the VM's live client branch differs from its target branch
// degraded the job completed but a wiring sub-step is flagged degraded
// job the carried job record ({ state, action, ... }) or null
// reachable the VM's editor answered this poll over the LB (true = healthy)
// inVmHealthy the in-VM editor answered /api/health from INSIDE the container
// (the STRAND_PROBE `HEALTH=1` witness gathered over docker-exec)
// drifted a project-dir-drift signal is present (reachable-but-unwired /
// half-provisioned add — genuinely needs-recovery, owned by the
// companion reconcile-inflight-jobs plan; never falsely cleared here)
// offBranch the VM's live client branch differs from its target branch
// degraded the job completed but a wiring sub-step is flagged degraded
//
// Returns true only when the job is a terminal error AND the VM is demonstrably
// fine (reachable, not drifted, on-branch, not degraded). An unreachable /
// drifted / off-branch VM keeps its error surfaced — a real problem must stay
// visible.
function shouldReconcileFailedBadge({ job, reachable, drifted, offBranch, degraded } = {}) {
// fine (healthy, not drifted, on-branch, not degraded). A drifted / off-branch /
// unwitnessed VM keeps its error surfaced — a real problem must stay visible.
//
// HEALTH IS WITNESSED TWO WAYS, and either suffices. `reachable` alone made the
// self-heal unable to fire in exactly the case that needs it most: on the GCE
// operator host `reachable` is derived from the LB probe, which needs the very
// ingress the add is still wiring. That is a circular dependency — the badge
// needs reachable, reachable needs ingress, and ingress is what we are waiting
// for — so a still-wiring VM sat at unreachableStreak 9 with a red add-error it
// could not clear by construction (VM-4, 2026-07-31). The dashboard already held
// the disconfirming evidence: gatherLauncherStrand runs STRAND_PROBE over
// docker-exec for exactly these unreachable-with-streak VMs and reads HEALTH=1
// from the in-VM /api/health.
//
// This widens ONE input; it does not relax the verdict. Every other guard is
// untouched, and the "absence of evidence never auto-acts" posture holds — an
// unprobed VM has neither witness and stays unreconciled.
function shouldReconcileFailedBadge({ job, reachable, inVmHealthy, drifted, offBranch, degraded } = {}) {
if (!job || job.state !== 'error') return false;
if (!reachable) return false;
if (!reachable && !inVmHealthy) return false;
if (drifted) return false;

@@ -35,0 +51,0 @@ if (offBranch) return false;

@@ -359,2 +359,45 @@ 'use strict';

/** Resolve the directory holding THIS process's memory.current / memory.high,
* or null when no cgroup-derived reading is available.
*
* The cgroup-v2 hierarchy ROOT exposes no memory.current and no memory.high —
* only a leaf cgroup does. Reading `/sys/fs/cgroup/memory.current` directly is
* therefore ENOENT on every systemd host, and that is exactly what made the
* 2026-07-30 memory remedy inert: both reads failed, every cgroup number
* degraded to null, decideMemoryRestart answered 'no-cgroup-limit' on every
* tick forever, and the drain silently fell back to the RSS threshold the
* incident had already proven cannot fire before the event loop stalls.
*
* Takes the CONTENTS of /proc/self/cgroup rather than reading it, so the path
* derivation — the part that was wrong — is unit-testable on a macOS dev box
* with no cgroup filesystem at all. That untestability is why a root-vs-leaf
* path error shipped green.
*
* Returns null for every shape that has no leaf memory file to read:
* • the unified line `0::/` — the process IS in the root cgroup, which
* genuinely has no memory files; this must NOT look like a valid path;
* • cgroup v1 (`N:controller:/path` lines, no `0::` entry);
* • empty, unreadable, or malformed input (a non-Linux host has no
* /proc/self/cgroup at all, and the caller passes the null through).
*
* Pure; no I/O. */
function resolveCgroupMemoryDir({ procSelfCgroup, root } = {}) {
if (typeof procSelfCgroup !== 'string') return null;
const base = (typeof root === 'string' && root)
? root.replace(/\/+$/, '')
: '/sys/fs/cgroup';
for (const line of procSelfCgroup.split('\n')) {
const trimmed = line.trim();
// cgroup v2 unified hierarchy is the single `0::<path>` entry. v1 lines
// (`N:controller:/path`) never match, which is the intended null path.
if (!trimmed.startsWith('0::')) continue;
const rel = trimmed.slice(3);
if (!rel.startsWith('/')) return null;
const clean = rel.replace(/\/+$/, '');
if (!clean) return null;
return base + clean;
}
return null;
}
/** Keep only the restart timestamps still inside the rolling window.

@@ -390,2 +433,3 @@ *

shouldAnnounceMemoryLevel,
resolveCgroupMemoryDir,
pruneRestartWindow,

@@ -392,0 +436,0 @@ buildStateSnapshot,

'use strict';
// Resolve the running-plan fields a fleet card shows — feature, step, mode,
// selectedPlan, holderSessionId — from the two independent per-VM probes plus
// the last-known-good state.
// selectedPlan, holderSessionId, startedAt, featureStartedAt — from the two
// independent per-VM probes plus the last-known-good state.
//
// The two timestamps ride here because they share the restart problem below and
// nothing else resolves them: `startedAt` / `featureStartedAt` drive the card's
// "on step" / "on feature" tenure badges AND the server-side slow/stuck/needs-you
// verdict (fleet-stuck-status.js deriveAttention keys on `startedAt`). A null
// degrades that verdict to level:'ok', needsYou:false — so a VM whose editor
// restarted was silently exempt from the very detection it most needed.
//
// Why this exists: the card used to hang these fields off `/api/session-info`

@@ -38,6 +45,7 @@ // alone (spread as `...info` in server.js). But a VM whose editor server

// @param {?Object} params.prev The previous `state.vms[n]` (carries the
// last-known feature/step/mode/selectedPlan/holderSessionId). null/undefined
// on the first poll.
// last-known feature/step/mode/selectedPlan/holderSessionId/startedAt/
// featureStartedAt). null/undefined on the first poll.
// @returns {{feature: ?string, step: ?number, mode: ?string,
// selectedPlan: ?string, holderSessionId: ?string}}
// selectedPlan: ?string, holderSessionId: ?string, startedAt: ?string,
// featureStartedAt: ?string}}
function resolveRunningPlan({ step, info, prev } = {}) {

@@ -60,2 +68,6 @@ const s = step || {};

holderSessionId: p.holderSessionId || null,
// Carried verbatim like the rest: a single timed-out cycle must not blink
// the tenure badges off and reset the stuck timer back to zero.
startedAt: p.startedAt || null,
featureStartedAt: p.featureStartedAt || null,
};

@@ -72,2 +84,11 @@ }

// The module's precedence rule, in ONE place: /api/step (session-independent)
// > /api/session-info > same-plan carry. Four fields resolve exactly this way,
// so hoisting it keeps a future precedence change from being applied to some
// of them and not others. The two fields that deviate stay longhand below, and
// their being longhand is the signal that they deviate: `step` needs an
// explicit null check (0 is a real step number, and `||` would swallow it),
// and holderSessionId deliberately skips session-info, which never carries it.
const pick = (key) => s[key] || i[key] || carry[key] || null;
const stepNum =

@@ -77,5 +98,5 @@ s.step != null ? s.step : i.step != null ? i.step : carry.step != null ? carry.step : null;

return {
feature: s.feature || i.feature || carry.feature || null,
feature: pick("feature"),
step: stepNum,
mode: s.mode || i.mode || carry.mode || null,
mode: pick("mode"),
selectedPlan: freshSelectedPlan || carry.selectedPlan || null,

@@ -86,2 +107,11 @@ // session-info never carries holderSessionId — it comes from /api/step

holderSessionId: s.holderSessionId || carry.holderSessionId || null,
// Workflow tenure timers. /api/step wins because EditorStepState serializes
// `started_at` (non-optional, camelCase) straight out of editor-step.json —
// session-independent, so it survives the editor restart that makes
// session-info report hasSession:false and omit them entirely. The same-plan
// `carry` applies unchanged: a completed plan's startedAt must never be
// pinned onto the next plan, or the badges would show a tenure the new plan
// has not accrued.
startedAt: pick("startedAt"),
featureStartedAt: pick("featureStartedAt"),
};

@@ -88,0 +118,0 @@ }

@@ -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}`;
}

@@ -166,0 +200,0 @@ } catch (_) {

{
"name": "@codeyam-editor/codeyam-editor",
"version": "0.1.6",
"version": "0.1.7",
"description": "Language-agnostic managed execution sandbox for scenario-driven development",

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

"optionalDependencies": {
"@codeyam-editor/codeyam-editor-darwin-arm64": "0.1.6",
"@codeyam-editor/codeyam-editor-darwin-x64": "0.1.6",
"@codeyam-editor/codeyam-editor-linux-x64": "0.1.6",
"@codeyam-editor/codeyam-editor-win32-x64": "0.1.6"
"@codeyam-editor/codeyam-editor-darwin-arm64": "0.1.7",
"@codeyam-editor/codeyam-editor-darwin-x64": "0.1.7",
"@codeyam-editor/codeyam-editor-linux-x64": "0.1.7",
"@codeyam-editor/codeyam-editor-win32-x64": "0.1.7"
},

@@ -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-DCHqRrwO.js"></script>
<script type="module" crossorigin src="/assets/index-VuyqBbGJ.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-CjxWhkSy.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-DCHqRrwO.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

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-BeQfa28g.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