@codeyam-editor/codeyam-editor 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/npm/cli.js +5 -1
- package/npm/cloud.js +1884 -210
- package/npm/codesign-plan.js +68 -0
- package/npm/empty-project-dir.js +92 -7
- package/npm/fleet-add-failure.js +252 -0
- package/npm/fleet-asset-version.js +37 -0
- package/npm/fleet-auth-health.js +143 -5
- package/npm/fleet-build-data.js +60 -5
- package/npm/fleet-cleanup.js +114 -9
- package/npm/fleet-destroy-safety.js +230 -13
- package/npm/fleet-early-roster.js +87 -0
- package/npm/fleet-effect-poll.js +110 -0
- package/npm/fleet-env.js +86 -0
- package/npm/fleet-ghcr-secret.js +129 -0
- package/npm/fleet-git-maintenance.js +142 -0
- package/npm/fleet-host-op-limiter.js +68 -0
- package/npm/fleet-iap-token.js +165 -0
- package/npm/fleet-live-status.js +40 -8
- package/npm/fleet-memory.js +245 -9
- package/npm/fleet-plans.js +98 -4
- package/npm/fleet-pool.js +75 -16
- package/npm/fleet-prewarm.js +87 -11
- package/npm/fleet-project-drift.js +58 -0
- package/npm/fleet-public-url.js +234 -0
- package/npm/fleet-queue.js +58 -1
- package/npm/fleet-reachability-debounce.js +69 -0
- package/npm/fleet-reachability.js +80 -0
- package/npm/fleet-reset.js +32 -0
- package/npm/fleet-running-plan.js +85 -0
- package/npm/fleet-session-token-cache.js +65 -0
- package/npm/fleet-setup-steps.js +19 -12
- package/npm/fleet-status-refs.js +96 -0
- package/npm/fleet-strand.js +39 -8
- package/npm/fleet-stuck-status.js +13 -7
- package/npm/fleet-vm-access.js +170 -0
- package/npm/fleet-vm-probe-target.js +282 -0
- package/npm/fleet-vm-readiness.js +136 -3
- package/npm/google-ingress.js +15 -0
- package/npm/ingress-invoke.js +478 -0
- package/npm/launch-config.js +182 -6
- package/npm/scenario-check.js +282 -10
- package/npm/scenario-handlers.js +85 -27
- package/npm/scenario-interactivity.js +145 -6
- package/npm/scenario-issues.js +13 -1
- package/npm/scenario-mocks.js +129 -26
- package/npm/scenario-playwright.js +286 -12
- package/npm/script-drift.js +72 -1
- package/npm/utils.js +37 -3
- package/package.json +5 -5
- package/ui/dist/assets/{ContainerInspector-D7VZkkaj.js → ContainerInspector-DgM0pTAx.js} +1 -1
- package/ui/dist/assets/EditorShell-B7Q06uJ_.js +252 -0
- package/ui/dist/assets/EditorShell-DP5UY20G.css +1 -0
- package/ui/dist/assets/{Markdown-BoLHHFXH.js → Markdown-Chgdf3PW.js} +1 -1
- package/ui/dist/assets/ProjectLauncherScreen-BRrEr8Ng.js +1 -0
- package/ui/dist/assets/ScenarioDataPanel-BeQfa28g.js +9 -0
- package/ui/dist/assets/{ScenarioDataPanel-DYP7pi_n.css → ScenarioDataPanel-C1LukYI6.css} +1 -1
- package/ui/dist/assets/{ScenarioDataPanelFullPage-CBLTGC4-.js → ScenarioDataPanelFullPage-BLqRCxQX.js} +1 -1
- package/ui/dist/assets/ScenarioRenderer-CIAxfHPh.js +21 -0
- package/ui/dist/assets/{StepsSimulator-BFldWyoX.js → StepsSimulator-CymGgfbu.js} +1 -1
- package/ui/dist/assets/index-BygCiJSb.css +1 -0
- package/ui/dist/assets/{index-QsOiOG9x.js → index-DCHqRrwO.js} +4 -4
- package/ui/dist/assets/{markdown-v0F0UEt3.js → markdown-C9tbsVHX.js} +1 -1
- package/ui/dist/assets/react-nrLBr15I.js +28 -0
- package/ui/dist/assets/{useEvents-DHkLJZR4.js → useEvents-S3Qk1Y_d.js} +1 -1
- package/ui/dist/assets/useServerIdentity-B7rI0u3O.js +1 -0
- package/ui/dist/index.html +4 -4
- package/ui/dist/open-source/codeyam-counter.png +0 -0
- package/ui/dist/open-source/el-carot.png +0 -0
- package/ui/dist/open-source/tabcommand.png +0 -0
- package/ui/dist/assets/EditorShell-DHhIaotd.css +0 -1
- package/ui/dist/assets/EditorShell-r-796N_W.js +0 -191
- package/ui/dist/assets/ProjectLauncherScreen-Ck-XWkQN.js +0 -1
- package/ui/dist/assets/ScenarioDataPanel-B9iDSUpA.js +0 -5
- package/ui/dist/assets/ScenarioRenderer-BhLfT-nX.js +0 -21
- package/ui/dist/assets/index-CIXZkwfB.css +0 -1
- package/ui/dist/assets/react-CSS0HapR.js +0 -28
|
@@ -336,6 +336,110 @@ async function forceFinalVisualState(target) {
|
|
|
336
336
|
});
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
// Center the isolated-component wrapper in the viewport before the shot.
|
|
340
|
+
//
|
|
341
|
+
// The capture pipeline always shoots the full viewport (`page.screenshot({
|
|
342
|
+
// fullPage: false })`) and never an element clip, so an isolation page that
|
|
343
|
+
// does not center its own component strands it in the top-left corner of every
|
|
344
|
+
// frame. Next.js does this in its isolation layout and the query-param stacks
|
|
345
|
+
// do it in `.codeyam/harness/isolate.tsx`, but the server-rendered scaffolds
|
|
346
|
+
// historically did not — and neither do pages hand-authored before that was
|
|
347
|
+
// fixed. This pass closes the gap at capture time, for any stack.
|
|
348
|
+
//
|
|
349
|
+
// It MEASURES rather than assumes: a wrapper that is already centered is left
|
|
350
|
+
// alone, so pages that already do the right thing are byte-identical. The
|
|
351
|
+
// `#codeyam-capture` marker (the cross-stack isolation convention; expo emits
|
|
352
|
+
// it via `nativeID`) is the gate, so an ordinary route capture is never
|
|
353
|
+
// touched. Centering is applied to `<body>` and to each ancestor strictly
|
|
354
|
+
// between body and the wrapper — never to the wrapper itself, which frequently
|
|
355
|
+
// carries the component's own padding/background and must keep its own box.
|
|
356
|
+
// Each axis independently falls back to `flex-start` when the wrapper overflows
|
|
357
|
+
// the viewport, so an oversized component loses its bottom/right edge rather
|
|
358
|
+
// than being clipped symmetrically out of its top/left one.
|
|
359
|
+
//
|
|
360
|
+
// Idempotent (a single injected style id), best-effort, and returns a small
|
|
361
|
+
// result object describing what it did.
|
|
362
|
+
async function centerCaptureWrapper(target) {
|
|
363
|
+
return target.evaluate(() => {
|
|
364
|
+
const STYLE_ID = "__codeyam_center_capture";
|
|
365
|
+
const ANCESTOR_CLASS = "__codeyam-center-ancestor";
|
|
366
|
+
// Subpixel layout and fractional scrollbar widths make exact gap equality
|
|
367
|
+
// unreliable; a couple of px of slop keeps already-centered pages on the
|
|
368
|
+
// no-op branch.
|
|
369
|
+
const TOLERANCE_PX = 2;
|
|
370
|
+
|
|
371
|
+
if (typeof document.getElementById !== "function") {
|
|
372
|
+
return { applied: false, reason: "no-dom" };
|
|
373
|
+
}
|
|
374
|
+
if (document.getElementById(STYLE_ID)) {
|
|
375
|
+
return { applied: false, reason: "already-applied" };
|
|
376
|
+
}
|
|
377
|
+
const wrapper = document.getElementById("codeyam-capture");
|
|
378
|
+
if (!wrapper || typeof wrapper.getBoundingClientRect !== "function") {
|
|
379
|
+
return { applied: false, reason: "no-capture-marker" };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const rect = wrapper.getBoundingClientRect();
|
|
383
|
+
const viewportWidth = window.innerWidth;
|
|
384
|
+
const viewportHeight = window.innerHeight;
|
|
385
|
+
|
|
386
|
+
const leftGap = rect.left;
|
|
387
|
+
const rightGap = viewportWidth - rect.right;
|
|
388
|
+
const topGap = rect.top;
|
|
389
|
+
const bottomGap = viewportHeight - rect.bottom;
|
|
390
|
+
|
|
391
|
+
const fitsHorizontally = rect.width <= viewportWidth;
|
|
392
|
+
const fitsVertically = rect.height <= viewportHeight;
|
|
393
|
+
const centeredHorizontally =
|
|
394
|
+
fitsHorizontally && Math.abs(leftGap - rightGap) <= TOLERANCE_PX;
|
|
395
|
+
const centeredVertically =
|
|
396
|
+
fitsVertically && Math.abs(topGap - bottomGap) <= TOLERANCE_PX;
|
|
397
|
+
|
|
398
|
+
if (centeredHorizontally && centeredVertically) {
|
|
399
|
+
return { applied: false, reason: "already-centered" };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const horizontal = fitsHorizontally ? "center" : "flex-start";
|
|
403
|
+
const vertical = fitsVertically ? "center" : "flex-start";
|
|
404
|
+
|
|
405
|
+
// Stamp the ancestor chain so the injected rule targets exactly the
|
|
406
|
+
// elements between <body> and the wrapper and cannot leak elsewhere.
|
|
407
|
+
let node = wrapper.parentElement;
|
|
408
|
+
while (node && node !== document.body) {
|
|
409
|
+
if (node.classList && typeof node.classList.add === "function") {
|
|
410
|
+
node.classList.add(ANCESTOR_CLASS);
|
|
411
|
+
}
|
|
412
|
+
node = node.parentElement;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (typeof document.createElement !== "function") {
|
|
416
|
+
return { applied: false, reason: "no-dom" };
|
|
417
|
+
}
|
|
418
|
+
const style = document.createElement("style");
|
|
419
|
+
style.id = STYLE_ID;
|
|
420
|
+
style.textContent =
|
|
421
|
+
"body, ." +
|
|
422
|
+
ANCESTOR_CLASS +
|
|
423
|
+
" {" +
|
|
424
|
+
"margin: 0;" +
|
|
425
|
+
"min-height: 100vh;" +
|
|
426
|
+
"display: flex;" +
|
|
427
|
+
"align-items: " +
|
|
428
|
+
vertical +
|
|
429
|
+
";" +
|
|
430
|
+
"justify-content: " +
|
|
431
|
+
horizontal +
|
|
432
|
+
";" +
|
|
433
|
+
"}";
|
|
434
|
+
const head = document.head || document.documentElement;
|
|
435
|
+
if (!head || typeof head.appendChild !== "function") {
|
|
436
|
+
return { applied: false, reason: "no-dom" };
|
|
437
|
+
}
|
|
438
|
+
head.appendChild(style);
|
|
439
|
+
return { applied: true, horizontal, vertical };
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
339
443
|
async function collectImageStates(target) {
|
|
340
444
|
return target.evaluate(() =>
|
|
341
445
|
Array.from(document.images || []).map((img) => ({
|
|
@@ -575,9 +679,31 @@ async function loadScenarioInIframe(
|
|
|
575
679
|
// and the nested scenario iframe inherits it (the Sveltia-class fix). The
|
|
576
680
|
// inner iframe `src` is still `url`, so the document-response probe above is
|
|
577
681
|
// unchanged.
|
|
578
|
-
|
|
682
|
+
const harnessUrl = buildHarnessUrl(resolvedHarnessOrigin, url, background);
|
|
683
|
+
const harnessResponse = await page.goto(harnessUrl, {
|
|
579
684
|
waitUntil: "domcontentloaded",
|
|
580
685
|
});
|
|
686
|
+
// Fail loudly on a fail-closed harness-auth rejection BEFORE the blind 30s
|
|
687
|
+
// `#scenario-frame` wait below. On a non-loopback (0.0.0.0 / cloud) bind the
|
|
688
|
+
// harness control-API route is session-token-gated; a generated
|
|
689
|
+
// `.codeyam/capture.js` (or an editor binary) that predates the `cy_session`
|
|
690
|
+
// cookie injection navigates without a token -> the server refuses the
|
|
691
|
+
// request with 401 -> the harness document never renders -> every component
|
|
692
|
+
// scenario times out on `#scenario-frame` with a generic Playwright error
|
|
693
|
+
// that hides the real cause. Surfacing the 401 here turns that blind timeout
|
|
694
|
+
// into a one-line root cause on the very first failing capture, naming both
|
|
695
|
+
// fixes. (Route/page scenarios navigate top-level to the un-gated app proxy,
|
|
696
|
+
// so they never hit this path.)
|
|
697
|
+
if (harnessResponse && harnessResponse.status() === 401) {
|
|
698
|
+
throw new Error(
|
|
699
|
+
`Capture harness route ${harnessUrl} returned 401 (fail-closed ` +
|
|
700
|
+
`session-token auth on a non-loopback bind). The capture script did not ` +
|
|
701
|
+
`send the \`cy_session\` token — most often a generated ` +
|
|
702
|
+
`\`.codeyam/capture.js\` (or an editor binary) that predates session-token ` +
|
|
703
|
+
`injection. Fixes: refresh the editor binary on this VM, or set ` +
|
|
704
|
+
`\`CODEYAM_INSECURE_BIND=1\` and restart the editor.`,
|
|
705
|
+
);
|
|
706
|
+
}
|
|
581
707
|
} else {
|
|
582
708
|
// Degraded fallback: no resolvable harness origin (server-state missing), so
|
|
583
709
|
// use the legacy in-page harness. The top-level document is then
|
|
@@ -677,6 +803,14 @@ async function loadScenarioTopLevel(
|
|
|
677
803
|
}
|
|
678
804
|
}
|
|
679
805
|
|
|
806
|
+
// The CSS selector for "clickable / interactive" elements — buttons, links,
|
|
807
|
+
// role=button, form controls, <summary>, and anything with an onclick. Shared
|
|
808
|
+
// by the candidate-label collector (below) and the text-target resolver
|
|
809
|
+
// (`resolveTextTarget`), which prefers an interactive element over a plain
|
|
810
|
+
// text node when a click/press label matches both.
|
|
811
|
+
const INTERACTIVE_SELECTOR =
|
|
812
|
+
"button, a[href], [role=button], input, select, textarea, summary, [onclick]";
|
|
813
|
+
|
|
680
814
|
// Collect up to 20 distinct visible labels of interactive elements on the
|
|
681
815
|
// page — buttons, links, role=button, form controls, <summary>, and anything
|
|
682
816
|
// with an onclick. Used to build an ACTIONABLE error when an interaction's
|
|
@@ -685,9 +819,7 @@ async function loadScenarioTopLevel(
|
|
|
685
819
|
// mean one of these?" hint. Pure read (no clicks); falls back to value /
|
|
686
820
|
// aria-label / placeholder when an element has no text.
|
|
687
821
|
async function collectInteractiveLabels(frame) {
|
|
688
|
-
return frame.evaluate(() => {
|
|
689
|
-
const selector =
|
|
690
|
-
"button, a[href], [role=button], input, select, textarea, summary, [onclick]";
|
|
822
|
+
return frame.evaluate((selector) => {
|
|
691
823
|
const nodes = Array.from(document.querySelectorAll(selector));
|
|
692
824
|
const labels = nodes
|
|
693
825
|
.map((node) => {
|
|
@@ -701,7 +833,39 @@ async function collectInteractiveLabels(frame) {
|
|
|
701
833
|
})
|
|
702
834
|
.filter((label) => label.length > 0);
|
|
703
835
|
return Array.from(new Set(labels)).slice(0, 20);
|
|
704
|
-
});
|
|
836
|
+
}, INTERACTIVE_SELECTOR);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Describe the elements a substring text match resolved, so an ambiguity
|
|
840
|
+
// warning can name the competing controls (e.g. the preset button "Bet" vs the
|
|
841
|
+
// disclosure button "…or bet on"). Reads each matched element's inner text via
|
|
842
|
+
// Playwright's `allInnerTexts`, caps the list at 5, and trims each to a single
|
|
843
|
+
// short line. Pure read; degrades to a count-only description if the locator
|
|
844
|
+
// API or the page can't produce texts.
|
|
845
|
+
async function describeMatchCandidates(baseLocator, matchCount) {
|
|
846
|
+
const CAP = 5;
|
|
847
|
+
const MAX_LEN = 60;
|
|
848
|
+
let texts = [];
|
|
849
|
+
try {
|
|
850
|
+
if (typeof baseLocator.allInnerTexts === "function") {
|
|
851
|
+
texts = await baseLocator.allInnerTexts();
|
|
852
|
+
}
|
|
853
|
+
} catch (_) {
|
|
854
|
+
texts = [];
|
|
855
|
+
}
|
|
856
|
+
const described = texts
|
|
857
|
+
.map((t) => String(t).replace(/\s+/g, " ").trim())
|
|
858
|
+
.filter((t) => t.length > 0)
|
|
859
|
+
.slice(0, CAP)
|
|
860
|
+
.map((t) => (t.length > MAX_LEN ? `${t.slice(0, MAX_LEN)}…` : t))
|
|
861
|
+
.map((t) => `"${t}"`);
|
|
862
|
+
if (described.length === 0) {
|
|
863
|
+
return [`${matchCount} elements (text unavailable)`];
|
|
864
|
+
}
|
|
865
|
+
if (matchCount > described.length) {
|
|
866
|
+
described.push(`…and ${matchCount - described.length} more`);
|
|
867
|
+
}
|
|
868
|
+
return described;
|
|
705
869
|
}
|
|
706
870
|
|
|
707
871
|
// Drive a single user-style interaction against the settled frame before the
|
|
@@ -714,16 +878,103 @@ async function collectInteractiveLabels(frame) {
|
|
|
714
878
|
// `Enter`). On a no-match target this THROWS with the list of candidate
|
|
715
879
|
// interactive labels — the capture script's outer catch turns that into a
|
|
716
880
|
// failed capture with an actionable message, never a silent blank screenshot.
|
|
717
|
-
|
|
881
|
+
//
|
|
882
|
+
// Resolve a visible-text target to the element most likely intended, instead
|
|
883
|
+
// of blindly taking the first substring hit. A bare
|
|
884
|
+
// `getByText(text, { exact: false })` is a case-insensitive SUBSTRING match, so
|
|
885
|
+
// a label can land on a plain text node or a placeholder rather than the chip /
|
|
886
|
+
// button the agent meant (observed: a `"Japan"` click hit placeholder text, not
|
|
887
|
+
// the chip; a `"Bet"` click hit a disclosure button described "or bet on"). We
|
|
888
|
+
// try increasingly-loose locators in priority order and take the first that
|
|
889
|
+
// matches anything:
|
|
890
|
+
// 1. exact + interactive (for click/press)
|
|
891
|
+
// 2. exact
|
|
892
|
+
// 3. substring + interactive (for click/press)
|
|
893
|
+
// 4. substring
|
|
894
|
+
// so an exact, clickable element wins over a substring plain-text node. Returns
|
|
895
|
+
// the winning tier's locator plus its match count; when nothing matches at all,
|
|
896
|
+
// returns the substring locator (count 0) so the caller's zero-match error path
|
|
897
|
+
// still fires with its candidate-label hint.
|
|
898
|
+
async function resolveTextTarget(frame, text, action) {
|
|
899
|
+
const preferInteractive = action === "click" || action === "press";
|
|
900
|
+
const exactBase = frame.getByText(text, { exact: true });
|
|
901
|
+
const substrBase = frame.getByText(text, { exact: false });
|
|
902
|
+
|
|
903
|
+
const tiers = [];
|
|
904
|
+
// `.and()` (Playwright ≥1.34) intersects two locators to the elements
|
|
905
|
+
// matching both — here, the text element that is ALSO interactive. Guard on
|
|
906
|
+
// its presence so a locator without `.and` degrades to text-only tiers.
|
|
907
|
+
if (preferInteractive && typeof exactBase.and === "function") {
|
|
908
|
+
const interactive = frame.locator(INTERACTIVE_SELECTOR);
|
|
909
|
+
tiers.push(exactBase.and(interactive));
|
|
910
|
+
tiers.push(exactBase);
|
|
911
|
+
tiers.push(substrBase.and(interactive));
|
|
912
|
+
tiers.push(substrBase);
|
|
913
|
+
} else {
|
|
914
|
+
tiers.push(exactBase);
|
|
915
|
+
tiers.push(substrBase);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
for (const loc of tiers) {
|
|
919
|
+
const count = await loc.count();
|
|
920
|
+
if (count > 0) {
|
|
921
|
+
return { baseLocator: loc, matchCount: count };
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return { baseLocator: substrBase, matchCount: 0 };
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// Pick the single element to act on from a resolved locator. When more than one
|
|
928
|
+
// candidate remains within the winning tier, prefer a VISIBLE element over a
|
|
929
|
+
// hidden one rather than acting on raw `.first()` (a hidden duplicate — an
|
|
930
|
+
// off-screen menu clone, an aria-hidden mirror — is almost never the intended
|
|
931
|
+
// target). Degrades to `.first()` when the locator API lacks a visible filter
|
|
932
|
+
// or no candidate is visible.
|
|
933
|
+
async function pickBestCandidate(baseLocator, matchCount) {
|
|
934
|
+
if (matchCount <= 1) {
|
|
935
|
+
return baseLocator.first();
|
|
936
|
+
}
|
|
937
|
+
try {
|
|
938
|
+
if (typeof baseLocator.filter === "function") {
|
|
939
|
+
const visible = baseLocator.filter({ visible: true });
|
|
940
|
+
if ((await visible.count()) > 0) {
|
|
941
|
+
return visible.first();
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
} catch (_) {
|
|
945
|
+
// Locator API without a `{ visible: true }` filter — fall through.
|
|
946
|
+
}
|
|
947
|
+
return baseLocator.first();
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Text matching prefers an EXACT, INTERACTIVE element over a looser substring /
|
|
951
|
+
// plain-text hit (see `resolveTextTarget`), so the first attempt lands on the
|
|
952
|
+
// chip or button the agent meant. When a genuine ambiguity survives that
|
|
953
|
+
// resolution (>1 candidate in the winning tier), we still act — on the best
|
|
954
|
+
// visible candidate — but push an actionable warning naming every candidate
|
|
955
|
+
// into the optional `warnings` array so the agent can switch to an exact
|
|
956
|
+
// selector; a zero-match throws an error that spells out the substring caveat
|
|
957
|
+
// and the exact-selector / URL-query-param alternatives.
|
|
958
|
+
async function performInteraction(
|
|
959
|
+
frame,
|
|
960
|
+
interaction,
|
|
961
|
+
{ timeoutMs = 5000, warnings } = {},
|
|
962
|
+
) {
|
|
718
963
|
const { action, selector, text, value } = interaction || {};
|
|
719
964
|
|
|
720
|
-
let
|
|
965
|
+
let baseLocator;
|
|
721
966
|
let targetDesc;
|
|
967
|
+
let matchedByText = false;
|
|
968
|
+
let matchCount;
|
|
722
969
|
if (typeof text === "string" && text.length > 0) {
|
|
723
|
-
|
|
970
|
+
const resolved = await resolveTextTarget(frame, text, action);
|
|
971
|
+
baseLocator = resolved.baseLocator;
|
|
972
|
+
matchCount = resolved.matchCount;
|
|
724
973
|
targetDesc = `text "${text}"`;
|
|
974
|
+
matchedByText = true;
|
|
725
975
|
} else if (typeof selector === "string" && selector.length > 0) {
|
|
726
|
-
|
|
976
|
+
baseLocator = frame.locator(selector);
|
|
977
|
+
matchCount = await baseLocator.count();
|
|
727
978
|
targetDesc = `selector "${selector}"`;
|
|
728
979
|
} else {
|
|
729
980
|
throw new Error(
|
|
@@ -731,14 +982,34 @@ async function performInteraction(frame, interaction, { timeoutMs = 5000 } = {})
|
|
|
731
982
|
);
|
|
732
983
|
}
|
|
733
984
|
|
|
734
|
-
const matchCount = await locator.count();
|
|
735
985
|
if (matchCount === 0) {
|
|
736
986
|
const candidates = await collectInteractiveLabels(frame);
|
|
737
987
|
const candidateList =
|
|
738
988
|
candidates.length > 0 ? candidates.join(", ") : "(none found on page)";
|
|
739
989
|
throw new Error(
|
|
740
990
|
`preview-interact: no element matched ${targetDesc}. ` +
|
|
741
|
-
`
|
|
991
|
+
`Text is matched as a case-insensitive SUBSTRING, so a misspelled or ` +
|
|
992
|
+
`over-specific label matches nothing — prefer an exact/role/testid CSS ` +
|
|
993
|
+
`selector (e.g. {"selector":"[data-testid=\\"save\\"]"}) for a precise ` +
|
|
994
|
+
`target. Many filter/status/sort states are also reachable directly via a ` +
|
|
995
|
+
`URL query param (e.g. add "?status=active" to the path) with no ` +
|
|
996
|
+
`interaction at all. Candidate interactive labels: ${candidateList}`,
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const locator = await pickBestCandidate(baseLocator, matchCount);
|
|
1001
|
+
|
|
1002
|
+
// Ambiguity warning: a text target that still resolves >1 element within the
|
|
1003
|
+
// winning tier acts on the best visible candidate, which may not be the one
|
|
1004
|
+
// intended. Name the competing elements instead of silently proceeding. Only
|
|
1005
|
+
// fires for text matches — an explicit selector that matches many is the
|
|
1006
|
+
// caller's deliberate choice.
|
|
1007
|
+
if (matchedByText && matchCount > 1 && Array.isArray(warnings)) {
|
|
1008
|
+
warnings.push(
|
|
1009
|
+
`preview-interact: ${targetDesc} matched ${matchCount} elements — acting on the best visible candidate. ` +
|
|
1010
|
+
`Text resolution prefers an exact, interactive match, but several remain, so this may not be the element you meant. ` +
|
|
1011
|
+
`Candidates: ${(await describeMatchCandidates(baseLocator, matchCount)).join(" | ")}. ` +
|
|
1012
|
+
`Use an exact/role/testid selector to disambiguate.`,
|
|
742
1013
|
);
|
|
743
1014
|
}
|
|
744
1015
|
|
|
@@ -829,11 +1100,12 @@ async function performInteractionSequence(
|
|
|
829
1100
|
settleMs = 5000,
|
|
830
1101
|
loadingMarkers,
|
|
831
1102
|
settle = waitForStablePage,
|
|
1103
|
+
warnings,
|
|
832
1104
|
} = {},
|
|
833
1105
|
) {
|
|
834
1106
|
for (let i = 0; i < interactions.length; i += 1) {
|
|
835
1107
|
try {
|
|
836
|
-
await performInteraction(frame, interactions[i], { timeoutMs });
|
|
1108
|
+
await performInteraction(frame, interactions[i], { timeoutMs, warnings });
|
|
837
1109
|
} catch (err) {
|
|
838
1110
|
// Prefix the failing step's index so a miss in a multi-step sequence is
|
|
839
1111
|
// locatable, matching the model-side `interactions[i]` validator.
|
|
@@ -856,6 +1128,7 @@ module.exports = {
|
|
|
856
1128
|
scrollThroughDocument,
|
|
857
1129
|
collectVisibleTextLength,
|
|
858
1130
|
forceFinalVisualState,
|
|
1131
|
+
centerCaptureWrapper,
|
|
859
1132
|
collectImageStates,
|
|
860
1133
|
waitForImagesSettled,
|
|
861
1134
|
waitForAnimationsSettled,
|
|
@@ -865,6 +1138,7 @@ module.exports = {
|
|
|
865
1138
|
loadScenarioInIframe,
|
|
866
1139
|
loadScenarioTopLevel,
|
|
867
1140
|
collectInteractiveLabels,
|
|
1141
|
+
describeMatchCandidates,
|
|
868
1142
|
performInteraction,
|
|
869
1143
|
waitForPredicate,
|
|
870
1144
|
performInteractionSequence,
|
package/npm/script-drift.js
CHANGED
|
@@ -76,6 +76,77 @@ function classifyScriptDrift({ files }) {
|
|
|
76
76
|
return { verdict: 'clean', staleFiles: [] };
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Decide, for one streamed provisioning script, whether the VM should receive the
|
|
81
|
+
* canonical remote-tip bytes or the local on-disk copy. The remote tip is the
|
|
82
|
+
* reviewed, canonical source: a checkout that is merely *behind* origin (a normal,
|
|
83
|
+
* expected consequence of an active fleet landing commits to these scripts) then
|
|
84
|
+
* streams exactly the bytes the drift guard expects — never stale on-disk bytes,
|
|
85
|
+
* never a false STALE abort. The local copy wins only when the operator authored
|
|
86
|
+
* content the remote tip does not have — an uncommitted (dirty) edit or a committed
|
|
87
|
+
* local-ahead change (developer mode / diverged) — or when the remote facts are
|
|
88
|
+
* unavailable (offline fallback, so an offline operator can still provision).
|
|
89
|
+
*
|
|
90
|
+
* @param {Object} file
|
|
91
|
+
* @param {string|null} file.localHash
|
|
92
|
+
* @param {string|null} file.remoteHash null/undefined ⇒ remote unreachable
|
|
93
|
+
* @param {string|null} file.baseHash
|
|
94
|
+
* @param {boolean} file.dirty
|
|
95
|
+
* @returns {'remote' | 'local'}
|
|
96
|
+
*/
|
|
97
|
+
function streamedScriptSource({ localHash, remoteHash, baseHash, dirty }) {
|
|
98
|
+
// Remote unreachable / ref missing → the only bytes we have are on disk.
|
|
99
|
+
if (remoteHash === null || remoteHash === undefined) {
|
|
100
|
+
return 'local';
|
|
101
|
+
}
|
|
102
|
+
// Uncommitted local edit → the operator is provisioning unreviewed bytes on
|
|
103
|
+
// purpose (developer mode, or an explicit CODEYAM_SKIP_SCRIPT_DRIFT_CHECK bypass).
|
|
104
|
+
if (dirty) {
|
|
105
|
+
return 'local';
|
|
106
|
+
}
|
|
107
|
+
// Local content the remote tip does not have (committed local-ahead, or a genuine
|
|
108
|
+
// divergence): local differs from the remote tip AND from the merge base → honor
|
|
109
|
+
// the local commit rather than silently overwriting it with the remote tip.
|
|
110
|
+
if (localHash !== remoteHash && localHash !== baseHash) {
|
|
111
|
+
return 'local';
|
|
112
|
+
}
|
|
113
|
+
// Clean, or merely behind the remote tip → stream the canonical remote-tip bytes.
|
|
114
|
+
return 'remote';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Decide what the streamed-script drift preflight should do, from the classifier
|
|
119
|
+
* verdict plus which streamed files carry uncommitted edits. A checkout that is
|
|
120
|
+
* merely *behind* origin (verdict 'stale') is benign — the add streams the
|
|
121
|
+
* canonical remote-tip bytes (see streamedScriptSource), so it never blocks. The
|
|
122
|
+
* guard fires ('block') only for local content the remote tip does not have: an
|
|
123
|
+
* uncommitted (dirty) edit, or a genuine divergence. A committed local-ahead
|
|
124
|
+
* change is developer mode — warn, don't block.
|
|
125
|
+
*
|
|
126
|
+
* @param {Object} o
|
|
127
|
+
* @param {string} o.verdict classifyScriptDrift verdict
|
|
128
|
+
* @param {string[]} [o.staleFiles] files the classifier flagged (the diverged set)
|
|
129
|
+
* @param {string[]} [o.dirtyFiles] streamed files with uncommitted local edits
|
|
130
|
+
* @returns {{ block: boolean, reason: 'clean'|'behind'|'local-ahead'|'dirty'|'diverged', blockedFiles: string[] }}
|
|
131
|
+
*/
|
|
132
|
+
function scriptDriftGuardAction({ verdict, staleFiles = [], dirtyFiles = [] }) {
|
|
133
|
+
if (verdict === 'diverged') {
|
|
134
|
+
return { block: true, reason: 'diverged', blockedFiles: staleFiles };
|
|
135
|
+
}
|
|
136
|
+
if (dirtyFiles.length > 0) {
|
|
137
|
+
return { block: true, reason: 'dirty', blockedFiles: dirtyFiles };
|
|
138
|
+
}
|
|
139
|
+
if (verdict === 'stale') {
|
|
140
|
+
return { block: false, reason: 'behind', blockedFiles: [] };
|
|
141
|
+
}
|
|
142
|
+
if (verdict === 'local-ahead') {
|
|
143
|
+
return { block: false, reason: 'local-ahead', blockedFiles: [] };
|
|
144
|
+
}
|
|
145
|
+
return { block: false, reason: 'clean', blockedFiles: [] };
|
|
146
|
+
}
|
|
147
|
+
|
|
79
148
|
module.exports = {
|
|
80
|
-
classifyScriptDrift
|
|
149
|
+
classifyScriptDrift,
|
|
150
|
+
streamedScriptSource,
|
|
151
|
+
scriptDriftGuardAction
|
|
81
152
|
};
|
package/npm/utils.js
CHANGED
|
@@ -103,6 +103,34 @@ function findBinary() {
|
|
|
103
103
|
return null;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Whether this install is a dev checkout (the source tree) rather than an
|
|
108
|
+
* npm-installed package. Signalled by a `Cargo.toml` at `rootDir` — present in
|
|
109
|
+
* the git checkout, absent in the npm install dir. The single source of truth
|
|
110
|
+
* for the checkout-vs-installed distinction; both `ensureBinary` (build-from-
|
|
111
|
+
* source vs error) and `serverSpawnEnv` (distribution-channel gating) read it.
|
|
112
|
+
* Accepts an explicit dir so it is unit-testable against a temp tree.
|
|
113
|
+
*/
|
|
114
|
+
function isDevCheckout(dir = rootDir) {
|
|
115
|
+
return fs.existsSync(path.join(dir, "Cargo.toml"));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Env for a launched editor-server, tagging the distribution channel so the
|
|
120
|
+
* server's update-availability check knows an npm install can be upgraded via
|
|
121
|
+
* `npm install -g …@latest`. Pure: returns a NEW object, never mutates the
|
|
122
|
+
* input.
|
|
123
|
+
*
|
|
124
|
+
* Installed (non-dev-checkout) → set `CODEYAM_DISTRIBUTION=npm`, so the server
|
|
125
|
+
* polls the npm registry and surfaces the upgrade banner. Dev checkout → leave
|
|
126
|
+
* the env untouched (any pre-existing value from the parent env is preserved),
|
|
127
|
+
* so a `codeyam-editor` run from source is never nagged to "upgrade".
|
|
128
|
+
*/
|
|
129
|
+
function serverSpawnEnv(baseEnv, { devCheckout } = {}) {
|
|
130
|
+
if (devCheckout) return { ...baseEnv };
|
|
131
|
+
return { ...baseEnv, CODEYAM_DISTRIBUTION: "npm" };
|
|
132
|
+
}
|
|
133
|
+
|
|
106
134
|
/**
|
|
107
135
|
* Return the binary path, building from source if we're in a dev checkout.
|
|
108
136
|
*
|
|
@@ -120,9 +148,9 @@ function ensureBinary() {
|
|
|
120
148
|
const existing = findBinary();
|
|
121
149
|
if (existing) return existing;
|
|
122
150
|
|
|
123
|
-
const
|
|
151
|
+
const devCheckout = isDevCheckout();
|
|
124
152
|
|
|
125
|
-
if (!
|
|
153
|
+
if (!devCheckout) {
|
|
126
154
|
const info = platformPackageInfo();
|
|
127
155
|
if (!info) {
|
|
128
156
|
console.error(
|
|
@@ -1051,7 +1079,11 @@ async function ensureServer(port = 14199) {
|
|
|
1051
1079
|
const child = spawn(
|
|
1052
1080
|
binary,
|
|
1053
1081
|
["start", "--no-open", "--port", String(port)],
|
|
1054
|
-
buildServerSpawnOptions({
|
|
1082
|
+
buildServerSpawnOptions({
|
|
1083
|
+
cwd: process.cwd(),
|
|
1084
|
+
env: serverSpawnEnv(process.env, { devCheckout: isDevCheckout() }),
|
|
1085
|
+
logFd,
|
|
1086
|
+
}),
|
|
1055
1087
|
);
|
|
1056
1088
|
if (logFd !== null) {
|
|
1057
1089
|
try {
|
|
@@ -1192,6 +1224,8 @@ module.exports = {
|
|
|
1192
1224
|
findBrokerBinary,
|
|
1193
1225
|
verifyBrokerBinaryPresent,
|
|
1194
1226
|
ensureBinary,
|
|
1227
|
+
isDevCheckout,
|
|
1228
|
+
serverSpawnEnv,
|
|
1195
1229
|
staleBinaryBanner,
|
|
1196
1230
|
enforceFreshBinary,
|
|
1197
1231
|
uiDistDir,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codeyam-editor/codeyam-editor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Language-agnostic managed execution sandbox for scenario-driven development",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codeyam-editor": "./npm/cli.js"
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"playwright": "^1.58.2"
|
|
10
10
|
},
|
|
11
11
|
"optionalDependencies": {
|
|
12
|
-
"@codeyam-editor/codeyam-editor-darwin-arm64": "0.1.
|
|
13
|
-
"@codeyam-editor/codeyam-editor-darwin-x64": "0.1.
|
|
14
|
-
"@codeyam-editor/codeyam-editor-linux-x64": "0.1.
|
|
15
|
-
"@codeyam-editor/codeyam-editor-win32-x64": "0.1.
|
|
12
|
+
"@codeyam-editor/codeyam-editor-darwin-arm64": "0.1.6",
|
|
13
|
+
"@codeyam-editor/codeyam-editor-darwin-x64": "0.1.6",
|
|
14
|
+
"@codeyam-editor/codeyam-editor-linux-x64": "0.1.6",
|
|
15
|
+
"@codeyam-editor/codeyam-editor-win32-x64": "0.1.6"
|
|
16
16
|
},
|
|
17
17
|
"keywords": [
|
|
18
18
|
"codeyam",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{j as e}from"./markdown-
|
|
1
|
+
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(`
|
|
2
2
|
`)})}),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};
|