@jay-framework/aiditor 0.17.3 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +1 -5
  2. package/dist/actions/check-add-page-route.jay-action +12 -0
  3. package/dist/actions/check-add-page-route.jay-action.d.ts +10 -0
  4. package/dist/actions/check-page-add-page-brief.jay-action +9 -0
  5. package/dist/actions/check-page-add-page-brief.jay-action.d.ts +8 -0
  6. package/dist/actions/check-plugin-update.jay-action +14 -0
  7. package/dist/actions/check-plugin-update.jay-action.d.ts +13 -0
  8. package/dist/actions/ensure-project-plugin.jay-action +9 -0
  9. package/dist/actions/ensure-project-plugin.jay-action.d.ts +8 -0
  10. package/dist/actions/generate-add-page-brief-from-image.jay-action +20 -0
  11. package/dist/actions/generate-add-page-brief-from-image.jay-action.d.ts +20 -0
  12. package/dist/actions/get-plugin-setup-status.jay-action +9 -0
  13. package/dist/actions/get-plugin-setup-status.jay-action.d.ts +6 -0
  14. package/dist/actions/list-jay-plugins.jay-action +8 -0
  15. package/dist/actions/list-jay-plugins.jay-action.d.ts +5 -0
  16. package/dist/actions/list-plugin-contracts.jay-action +11 -0
  17. package/dist/actions/list-plugin-contracts.jay-action.d.ts +9 -0
  18. package/dist/actions/open-add-page-from-brief.jay-action +20 -0
  19. package/dist/actions/open-add-page-from-brief.jay-action.d.ts +18 -0
  20. package/dist/actions/reclassify-add-page-asset.jay-action +12 -0
  21. package/dist/actions/reclassify-add-page-asset.jay-action.d.ts +10 -0
  22. package/dist/actions/rerun-plugin-setup.jay-action +12 -0
  23. package/dist/actions/rerun-plugin-setup.jay-action.d.ts +10 -0
  24. package/dist/actions/save-add-page-draft.jay-action +10 -0
  25. package/dist/actions/save-add-page-draft.jay-action.d.ts +9 -0
  26. package/dist/actions/start-add-page-request.jay-action +12 -0
  27. package/dist/actions/start-add-page-request.jay-action.d.ts +10 -0
  28. package/dist/actions/submit-add-page.jay-action +21 -0
  29. package/dist/actions/submit-add-page.jay-action.d.ts +17 -0
  30. package/dist/actions/submit-task.jay-action +1 -0
  31. package/dist/actions/submit-task.jay-action.d.ts +1 -0
  32. package/dist/actions/sync-add-page-plugin-manifest.jay-action +11 -0
  33. package/dist/actions/sync-add-page-plugin-manifest.jay-action.d.ts +9 -0
  34. package/dist/actions/upload-add-page-asset.jay-action +13 -0
  35. package/dist/actions/upload-add-page-asset.jay-action.d.ts +13 -0
  36. package/dist/actions/write-plugin-source.jay-action +13 -0
  37. package/dist/actions/write-plugin-source.jay-action.d.ts +12 -0
  38. package/dist/add-page-agent/SKILL.md +79 -0
  39. package/dist/add-page-agent/system.md +47 -0
  40. package/dist/index.client.js +2696 -278
  41. package/dist/index.d.ts +797 -6
  42. package/dist/index.js +2682 -14
  43. package/dist/pages/aiditor/page.jay-html +603 -2
  44. package/dist/prompts/add-page-figma-brief-prompt.md +163 -0
  45. package/dist/prompts/add-page-figma-design-definitions-prompt.md +256 -0
  46. package/dist/prompts/add-page-figma-design-from-image-prompt.md +144 -0
  47. package/package.json +24 -7
  48. package/plugin.yaml +34 -0
@@ -10,6 +10,9 @@ const visualModeToForm = {
10
10
  video: "5"
11
11
  };
12
12
  const CHANNEL_MAX_FILE_PARTS = 300;
13
+ function isChatSubmitOptions(options) {
14
+ return options != null && "kind" in options && options.kind === "chat";
15
+ }
13
16
  function countAttachmentFiles(m) {
14
17
  if (!m) return 0;
15
18
  let n = 0;
@@ -26,14 +29,18 @@ function validateMultipartPartBudget(options) {
26
29
  }
27
30
  return null;
28
31
  }
29
- function submitAndStreamTask(notes, visual, callbacks) {
32
+ function submitAndStreamTask(notes, options, callbacks) {
30
33
  let aborted = false;
31
34
  const abort = () => {
32
35
  aborted = true;
33
36
  };
34
37
  const input = { notes };
35
- if (visual?.mode === "video") {
36
- const v = visual;
38
+ if (isChatSubmitOptions(options)) {
39
+ input.messageKind = "chat";
40
+ input.pageRoute = options.pageRoute;
41
+ input.renderedUrl = options.renderedUrl;
42
+ } else if (options?.mode === "video") {
43
+ const v = options;
37
44
  const attCount = countAttachmentFiles(v.attachmentsByPin);
38
45
  const includeVideo = Boolean(v.video);
39
46
  const err = validateMultipartPartBudget({
@@ -76,8 +83,8 @@ function submitAndStreamTask(notes, visual, callbacks) {
76
83
  }
77
84
  }
78
85
  input.extraFiles = extraFiles;
79
- } else if (visual?.dual) {
80
- const v = visual;
86
+ } else if (options?.dual) {
87
+ const v = options;
81
88
  input.visualTask = visualModeToForm[v.mode];
82
89
  input.pageRoute = v.pageRoute;
83
90
  input.renderedUrl = v.renderedUrl;
@@ -7975,7 +7982,7 @@ function freezeTransientStyles(iframeEl) {
7975
7982
  const seen = /* @__PURE__ */ new Set();
7976
7983
  for (const pseudo of TRANSIENT_PSEUDOCLASSES) {
7977
7984
  try {
7978
- for (const el of doc.querySelectorAll(pseudo)) seen.add(el);
7985
+ for (const el of Array.from(doc.querySelectorAll(pseudo))) seen.add(el);
7979
7986
  } catch {
7980
7987
  }
7981
7988
  }
@@ -8553,7 +8560,17 @@ function stopMediaStreamTracks(stream) {
8553
8560
  }
8554
8561
  }
8555
8562
  const AIDITOR_NOTES_VERSION = 1;
8563
+ const AIDITOR_NOTES_BINDING_VERSION = 1.1;
8564
+ function serializedAnnotationHasBindings(annotation) {
8565
+ return (annotation.pluginBindings?.length ?? 0) > 0 || !!annotation.pluginBinding;
8566
+ }
8556
8567
  function serializeAiditorNotes(payload) {
8568
+ if ("annotations" in payload && payload.annotations.some((a2) => serializedAnnotationHasBindings(a2))) {
8569
+ return JSON.stringify({
8570
+ ...payload,
8571
+ version: AIDITOR_NOTES_BINDING_VERSION
8572
+ });
8573
+ }
8557
8574
  return JSON.stringify(payload);
8558
8575
  }
8559
8576
  function resolvePreviewContextAtTime(timeSec, navLog, fallback) {
@@ -8680,178 +8697,183 @@ function joinHttpBaseUrlAndPath(httpBaseUrl, pathname) {
8680
8697
  const path = !pathname || pathname === "/" ? "/" : pathname.startsWith("/") ? pathname : `/${pathname}`;
8681
8698
  return new URL(path, baseUrl).href;
8682
8699
  }
8683
- setActionCallerOptions({ timeout: 12e4 });
8684
- const MAX_ATTACHMENTS_PER_PIN = 10;
8685
- const MAX_ATTACHMENT_BYTES_PER_PIN = 5 * 1024 * 1024;
8686
- function mbLabel(bytes) {
8687
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
8688
- }
8689
- function newLocalAttachmentId() {
8690
- return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `f_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
8691
- }
8692
- function newAnnotationId() {
8693
- return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `ann_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
8700
+ const checkAddPageRouteAction = createActionCaller("aiditor.checkAddPageRoute", "GET");
8701
+ const startAddPageRequestAction = createActionCaller("aiditor.startAddPageRequest", "GET");
8702
+ createActionCaller("aiditor.saveAddPageDraft", "GET");
8703
+ const uploadAddPageAssetAction = createActionCaller("aiditor.uploadAddPageAsset", "POST", { acceptsFiles: true });
8704
+ const reclassifyAddPageAssetAction = createActionCaller("aiditor.reclassifyAddPageAsset", "GET");
8705
+ const checkPageAddPageBriefAction = createActionCaller("aiditor.checkPageAddPageBrief", "GET");
8706
+ const openAddPageFromBriefAction = createActionCaller("aiditor.openAddPageFromBrief", "GET");
8707
+ const submitAddPageAction = createStreamCaller("aiditor.submitAddPage");
8708
+ const generateAddPageBriefFromImageAction = createActionCaller("aiditor.generateAddPageBriefFromImage", "POST", { acceptsFiles: true });
8709
+ async function checkAddPageRoute(pageRoute) {
8710
+ return checkAddPageRouteAction({ pageRoute });
8694
8711
  }
8695
- function renumberAnnotations(list) {
8696
- return list.map((a2, i) => ({ ...a2, id: String(i + 1) }));
8712
+ async function startAddPageRequest(pageRoute) {
8713
+ return startAddPageRequestAction({ pageRoute });
8697
8714
  }
8698
- function serializeGeometry(a2) {
8699
- if (a2.kind === "point")
8700
- return { kind: "point", x: a2.x, y: a2.y };
8701
- if (a2.kind === "area")
8702
- return { kind: "area", x: a2.x, y: a2.y, w: a2.w, h: a2.h };
8703
- return { kind: "arrow", x1: a2.x1, y1: a2.y1, x2: a2.x2, y2: a2.y2 };
8715
+ async function uploadAddPageAsset(input) {
8716
+ return uploadAddPageAssetAction(input);
8704
8717
  }
8705
- function toNotesPayload(annotations, breakpoint) {
8706
- return {
8707
- version: AIDITOR_NOTES_VERSION,
8708
- breakpoint,
8709
- annotations: annotations.map((a2) => ({
8710
- id: a2.id,
8711
- mode: a2.kind,
8712
- instruction: a2.instruction.trim(),
8713
- geometry: serializeGeometry(a2)
8714
- }))
8715
- };
8718
+ async function reclassifyAddPageAsset(input) {
8719
+ return reclassifyAddPageAssetAction(input);
8716
8720
  }
8717
- function toVideoNotesPayload(annotations, videoMeta, notesContext) {
8718
- const baseOrigin = typeof window !== "undefined" ? window.location.origin : JAY_DEV_URL_DEFAULT;
8719
- const fallback = fallbackPreviewContextFromBar(notesContext.fallbackRenderedUrl, notesContext.fallbackRoute, baseOrigin);
8720
- const navLog = notesContext.previewNavLog.length > 0 ? notesContext.previewNavLog : void 0;
8721
- const payload = {
8722
- version: 2,
8723
- taskKind: "video",
8724
- videoMeta,
8725
- previewNavLog: navLog,
8726
- breakpoint: notesContext.breakpoint,
8727
- annotations: annotations.map((a2) => {
8728
- const ctx = resolvePreviewContextAtTime(a2.timeSec, navLog, fallback);
8729
- return {
8730
- id: a2.id,
8731
- mode: a2.kind,
8732
- instruction: a2.instruction.trim(),
8733
- geometry: serializeGeometry(a2),
8734
- timeSec: a2.timeSec,
8735
- previewUrlAtTime: ctx.href,
8736
- pagePathAtTime: ctx.locationPath
8737
- };
8738
- })
8739
- };
8740
- return JSON.stringify(payload);
8721
+ async function generateAddPageBriefFromImage(input) {
8722
+ const extraFiles = {};
8723
+ input.images.forEach((file, i) => {
8724
+ extraFiles[`image_${i}`] = file;
8725
+ });
8726
+ return generateAddPageBriefFromImageAction({
8727
+ target: input.target,
8728
+ requestId: input.requestId,
8729
+ pageRoute: input.pageRoute,
8730
+ contextNotes: input.contextNotes,
8731
+ extraFiles
8732
+ });
8741
8733
  }
8742
- function toSerializedVideoForPins(a2) {
8743
- return {
8744
- id: a2.id,
8745
- mode: a2.kind,
8746
- instruction: a2.instruction,
8747
- geometry: serializeGeometry(a2),
8748
- timeSec: a2.timeSec
8734
+ function submitAddPageStream(input, callbacks) {
8735
+ let aborted = false;
8736
+ const abort = () => {
8737
+ aborted = true;
8749
8738
  };
8739
+ void (async () => {
8740
+ try {
8741
+ for await (const chunk of submitAddPageAction(input)) {
8742
+ if (aborted) break;
8743
+ callbacks.onChunk(chunk);
8744
+ if (chunk.type === "done" || chunk.type === "error") {
8745
+ callbacks.onEnd();
8746
+ return;
8747
+ }
8748
+ }
8749
+ if (!aborted) callbacks.onEnd();
8750
+ } catch (err) {
8751
+ if (!aborted) {
8752
+ callbacks.onError(err instanceof Error ? err : new Error(String(err)));
8753
+ }
8754
+ }
8755
+ })();
8756
+ return abort;
8750
8757
  }
8751
- function kindLabel(kind) {
8752
- if (kind === "point")
8753
- return "Point";
8754
- if (kind === "area")
8755
- return "Area";
8756
- return "Arrow";
8758
+ async function checkPageAddPageBrief(input) {
8759
+ return checkPageAddPageBriefAction(input);
8757
8760
  }
8758
- const VIDEO_ANNOTATION_TIME_TOLERANCE_SEC = 0.15;
8759
- function isVideoAnnotationAtPlayhead(playheadSec, annotationTimeSec) {
8760
- return Math.abs(playheadSec - annotationTimeSec) <= VIDEO_ANNOTATION_TIME_TOLERANCE_SEC;
8761
+ async function openAddPageFromBrief(input) {
8762
+ return openAddPageFromBriefAction(input);
8761
8763
  }
8762
- function bubbleAnchorNorm(a2) {
8763
- if (a2.kind === "point")
8764
- return { nx: a2.x, ny: a2.y };
8765
- if (a2.kind === "area") {
8766
- return { nx: Math.min(1, a2.x + a2.w), ny: Math.min(1, a2.y + a2.h) };
8764
+ async function ensureAddPageRequestId(pageRoute, currentRequestId) {
8765
+ if (currentRequestId) {
8766
+ return { requestId: currentRequestId };
8767
8767
  }
8768
- return { nx: a2.x2, ny: a2.y2 };
8768
+ const trimmed = pageRoute.trim();
8769
+ if (!trimmed) {
8770
+ return {
8771
+ requestId: "",
8772
+ error: "Enter a page route before attaching files or pasting images."
8773
+ };
8774
+ }
8775
+ const result = await startAddPageRequest(trimmed);
8776
+ if (result.routeExists) {
8777
+ return {
8778
+ requestId: "",
8779
+ error: result.routeExistsMessage ?? "This route already exists."
8780
+ };
8781
+ }
8782
+ if (!result.requestId) {
8783
+ return {
8784
+ requestId: "",
8785
+ error: result.routeExistsMessage ?? "Could not create page request. Check the route and try again."
8786
+ };
8787
+ }
8788
+ return { requestId: result.requestId };
8769
8789
  }
8770
- const POPOVER_FLIP_X_PCT = 56;
8771
- const POPOVER_FLIP_Y_PCT = 52;
8772
- function popoverFlipFromPct(leftPct, topPct) {
8773
- return {
8774
- flipX: leftPct > POPOVER_FLIP_X_PCT,
8775
- flipY: topPct > POPOVER_FLIP_Y_PCT
8776
- };
8790
+ function insertTextAtCursor(textarea, text, currentValue) {
8791
+ const start = textarea.selectionStart ?? currentValue.length;
8792
+ const end = textarea.selectionEnd ?? start;
8793
+ const before = currentValue.slice(0, start);
8794
+ const after = currentValue.slice(end);
8795
+ const prefix = before.length > 0 && !before.endsWith("\n") ? "\n" : "";
8796
+ const insertion = `${prefix}${text}
8797
+ `;
8798
+ const next = before + insertion + after;
8799
+ const newPos = before.length + insertion.length;
8800
+ queueMicrotask(() => {
8801
+ textarea.selectionStart = newPos;
8802
+ textarea.selectionEnd = newPos;
8803
+ textarea.focus();
8804
+ });
8805
+ return next;
8777
8806
  }
8778
- const JAY_DEV_URL_DEFAULT = typeof window !== "undefined" ? window.location.origin : "http://localhost:3000";
8779
- const paramsCache = /* @__PURE__ */ new Map();
8780
- function dedupePastedFiles(files) {
8807
+ function inferRouteKindFromRoute(route) {
8808
+ return /\[[^\]/]+\]/.test(route) ? "dynamic" : "static";
8809
+ }
8810
+ const BRIEF_FILL_MAX_IMAGES = 5;
8811
+ function dedupePastedFiles$1(files) {
8781
8812
  const seen = /* @__PURE__ */ new Set();
8782
8813
  const out = [];
8783
8814
  for (const f2 of files) {
8784
- const k = `${f2.name}\0${f2.size}\0${f2.lastModified}\0${f2.type}`;
8785
- if (seen.has(k))
8786
- continue;
8815
+ const k = `${f2.name}:${f2.size}:${f2.type}`;
8816
+ if (seen.has(k)) continue;
8787
8817
  seen.add(k);
8788
8818
  out.push(f2);
8789
8819
  }
8790
8820
  return out;
8791
8821
  }
8792
- function filesFromClipboardData(cd) {
8793
- if (!cd)
8794
- return [];
8822
+ function filesFromClipboardData$1(cd) {
8823
+ if (!cd) return [];
8795
8824
  const accum = [];
8796
8825
  for (let i = 0; i < cd.files.length; i++) {
8797
8826
  const f2 = cd.files.item(i);
8798
- if (f2)
8799
- accum.push(f2);
8827
+ if (f2) accum.push(f2);
8800
8828
  }
8801
8829
  if (cd.items) {
8802
8830
  for (let i = 0; i < cd.items.length; i++) {
8803
8831
  const it = cd.items[i];
8804
8832
  if (it?.kind === "file") {
8805
8833
  const f2 = it.getAsFile();
8806
- if (f2)
8807
- accum.push(f2);
8834
+ if (f2) accum.push(f2);
8808
8835
  }
8809
8836
  }
8810
8837
  }
8811
- return dedupePastedFiles(accum);
8812
- }
8813
- function clipboardItemsArray(cd) {
8814
- const items = cd?.items;
8815
- if (!items || items.length === 0)
8816
- return [];
8817
- return Array.from({ length: items.length }, (_, i) => items[i]).filter(Boolean);
8838
+ return dedupePastedFiles$1(accum).filter((f2) => f2.type.startsWith("image/"));
8818
8839
  }
8819
- function dataUrlToImageFileSync(dataUrl, index) {
8840
+ function dataUrlToImageFile(dataUrl, index) {
8820
8841
  const comma = dataUrl.indexOf(",");
8821
- if (comma < 0)
8822
- return null;
8842
+ if (comma < 0) return null;
8823
8843
  const header = dataUrl.slice(0, comma);
8824
8844
  const dataPart = dataUrl.slice(comma + 1);
8825
8845
  const mimeMatch = header.match(/^data:([^;,]+)/i);
8826
8846
  const mime = mimeMatch?.[1]?.trim() ?? "";
8827
- if (!mime.startsWith("image/"))
8828
- return null;
8847
+ if (!mime.startsWith("image/")) return null;
8829
8848
  const isBase64 = /;base64/i.test(header);
8830
8849
  let bytes;
8831
8850
  if (isBase64) {
8832
8851
  try {
8833
8852
  const bin = atob(dataPart.replace(/\s/g, ""));
8834
8853
  bytes = new Uint8Array(bin.length);
8835
- for (let i = 0; i < bin.length; i++)
8836
- bytes[i] = bin.charCodeAt(i);
8854
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
8837
8855
  } catch {
8838
8856
  return null;
8839
8857
  }
8840
8858
  } else {
8841
8859
  try {
8842
- bytes = new TextEncoder().encode(decodeURIComponent(dataPart.replace(/\+/g, "%20")));
8860
+ bytes = new TextEncoder().encode(
8861
+ decodeURIComponent(dataPart.replace(/\+/g, "%20"))
8862
+ );
8843
8863
  } catch {
8844
8864
  return null;
8845
8865
  }
8846
8866
  }
8847
8867
  const extPart = mime.split("/")[1] || "png";
8848
8868
  const ext = extPart.replace(/\+xml$/, "").split("+")[0] || "png";
8849
- const buf = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
8869
+ const buf = bytes.buffer.slice(
8870
+ bytes.byteOffset,
8871
+ bytes.byteOffset + bytes.byteLength
8872
+ );
8850
8873
  return new File([buf], `paste-${index}.${ext}`, { type: mime });
8851
8874
  }
8852
- function filesFromPastedHtmlDataUrls(html) {
8853
- if (!html)
8854
- return [];
8875
+ function filesFromPastedHtmlDataUrls$1(html) {
8876
+ if (!html) return [];
8855
8877
  let doc;
8856
8878
  try {
8857
8879
  doc = new DOMParser().parseFromString(html, "text/html");
@@ -8861,69 +8883,2108 @@ function filesFromPastedHtmlDataUrls(html) {
8861
8883
  const out = [];
8862
8884
  for (const img of Array.from(doc.querySelectorAll("img[src]"))) {
8863
8885
  const src = img.getAttribute("src");
8864
- if (!src?.startsWith("data:image"))
8865
- continue;
8866
- const f2 = dataUrlToImageFileSync(src, out.length);
8867
- if (f2)
8868
- out.push(f2);
8886
+ if (!src?.startsWith("data:image")) continue;
8887
+ const f2 = dataUrlToImageFile(src, out.length);
8888
+ if (f2) out.push(f2);
8869
8889
  }
8870
- return dedupePastedFiles(out);
8890
+ return dedupePastedFiles$1(out);
8871
8891
  }
8872
- function filesFromPlainDataImageUrl(text) {
8892
+ function filesFromPlainDataImageUrl$1(text) {
8873
8893
  const t = text.trim();
8874
- if (!t.startsWith("data:image/"))
8875
- return [];
8876
- const f2 = dataUrlToImageFileSync(t, 0);
8894
+ if (!t.startsWith("data:image/")) return [];
8895
+ const f2 = dataUrlToImageFile(t, 0);
8877
8896
  return f2 ? [f2] : [];
8878
8897
  }
8879
- async function imageFilesFromNavigatorClipboard() {
8880
- if (!navigator.clipboard?.read)
8881
- return [];
8882
- let clipItems;
8883
- try {
8884
- clipItems = await navigator.clipboard.read();
8885
- } catch {
8886
- return [];
8887
- }
8888
- const out = [];
8889
- let idx = 0;
8890
- for (const ci of clipItems) {
8891
- for (const type of ci.types) {
8892
- if (!type.startsWith("image/"))
8893
- continue;
8894
- try {
8895
- const blob = await ci.getType(type);
8896
- if (!(blob instanceof Blob) || blob.size === 0)
8897
- continue;
8898
- const ext = type.split("/")[1]?.split("+")[0] || "png";
8899
- out.push(new File([blob], `paste-${idx++}.${ext}`, { type }));
8900
- } catch {
8898
+ function imageFilesFromPasteEvent(event) {
8899
+ const cd = event.clipboardData;
8900
+ const direct = filesFromClipboardData$1(cd);
8901
+ if (direct.length > 0) return direct;
8902
+ const html = cd?.getData("text/html") ?? "";
8903
+ const plain = (cd?.getData("text/plain") ?? "").trim();
8904
+ const fromRich = dedupePastedFiles$1([
8905
+ ...filesFromPastedHtmlDataUrls$1(html),
8906
+ ...filesFromPlainDataImageUrl$1(plain)
8907
+ ]);
8908
+ if (fromRich.length > 0) return fromRich;
8909
+ if (cd?.items) {
8910
+ for (const item of Array.from(cd.items)) {
8911
+ if (item.type.startsWith("image/")) {
8912
+ const file = item.getAsFile();
8913
+ if (file) return [file];
8914
+ }
8915
+ }
8916
+ }
8917
+ return [];
8918
+ }
8919
+ function initAddPageBriefFillPanel(deps) {
8920
+ const [showPopover, setShowPopover] = createSignal(false);
8921
+ const [target, setTarget] = createSignal("content");
8922
+ const [contextNotes, setContextNotes] = createSignal("");
8923
+ const [images, setImages] = createSignal([]);
8924
+ const [thumbUrls, setThumbUrls] = createSignal([]);
8925
+ const [generating, setGenerating] = createSignal(false);
8926
+ const [popoverError, setPopoverError] = createSignal("");
8927
+ const [pendingReferenceFiles, setPendingReferenceFiles] = createSignal([]);
8928
+ const [suggestedRoute, setSuggestedRoute] = createSignal(null);
8929
+ const [suggestedRouteKind, setSuggestedRouteKind] = createSignal(null);
8930
+ const showSuggestedRouteBanner = createMemo(
8931
+ () => suggestedRoute() !== null && suggestedRoute().length > 0
8932
+ );
8933
+ const suggestedRouteLabel = createMemo(() => {
8934
+ const r = suggestedRoute();
8935
+ const k = suggestedRouteKind();
8936
+ if (!r) return "";
8937
+ return k ? `${r} (${k})` : r;
8938
+ });
8939
+ const briefFillDisabled = createMemo(() => deps.isBusy() || generating());
8940
+ const showBriefFillPopover = createMemo(() => showPopover());
8941
+ const canGenerate = createMemo(
8942
+ () => images().length > 0 && !generating() && !deps.isBusy()
8943
+ );
8944
+ const briefFillInputDisabled = createMemo(() => generating());
8945
+ const briefFillDropzoneClass = createMemo(
8946
+ () => briefFillInputDisabled() ? "add-page-brief-fill-dropzone add-page-brief-fill-dropzone-disabled" : "add-page-brief-fill-dropzone"
8947
+ );
8948
+ const generateDisabled = createMemo(() => !canGenerate());
8949
+ const briefFillStatusHint = createMemo(() => {
8950
+ if (generating()) {
8951
+ return target() === "content" ? "Generating content brief…" : "Generating design brief…";
8952
+ }
8953
+ return "";
8954
+ });
8955
+ const showBriefFillStatusHint = createMemo(
8956
+ () => briefFillStatusHint().length > 0
8957
+ );
8958
+ const imageThumbRows = createMemo(
8959
+ () => images().map((f2, i) => ({
8960
+ rowKey: `${f2.name}-${f2.size}-${i}`,
8961
+ name: f2.name,
8962
+ thumbUrl: thumbUrls()[i] ?? "",
8963
+ index: i,
8964
+ removeDisabled: generating()
8965
+ }))
8966
+ );
8967
+ const showBriefFillThumbnails = createMemo(() => imageThumbRows().length > 0);
8968
+ function revokeThumbUrls() {
8969
+ for (const url of thumbUrls()) {
8970
+ if (url) URL.revokeObjectURL(url);
8971
+ }
8972
+ }
8973
+ function resetPopoverState() {
8974
+ revokeThumbUrls();
8975
+ setImages([]);
8976
+ setThumbUrls([]);
8977
+ setContextNotes("");
8978
+ setPopoverError("");
8979
+ }
8980
+ function closePopover() {
8981
+ setShowPopover(false);
8982
+ resetPopoverState();
8983
+ }
8984
+ function openPopover(t) {
8985
+ resetPopoverState();
8986
+ setTarget(t);
8987
+ setShowPopover(true);
8988
+ }
8989
+ function addImageFiles(files) {
8990
+ if (generating()) return;
8991
+ const imageOnly = files.filter((f2) => f2.type.startsWith("image/"));
8992
+ if (imageOnly.length === 0) return;
8993
+ setPopoverError("");
8994
+ setImages((prev) => {
8995
+ const merged = [...prev];
8996
+ for (const f2 of imageOnly) {
8997
+ if (merged.length >= BRIEF_FILL_MAX_IMAGES) break;
8998
+ merged.push(f2);
8999
+ }
9000
+ if (prev.length + imageOnly.length > BRIEF_FILL_MAX_IMAGES) {
9001
+ setPopoverError(`Maximum ${BRIEF_FILL_MAX_IMAGES} images.`);
9002
+ }
9003
+ return merged;
9004
+ });
9005
+ setThumbUrls((prev) => {
9006
+ const next = [...prev];
9007
+ for (const f2 of imageOnly) {
9008
+ if (next.length >= BRIEF_FILL_MAX_IMAGES) break;
9009
+ next.push(URL.createObjectURL(f2));
8901
9010
  }
9011
+ return next;
9012
+ });
9013
+ }
9014
+ function removeImageAt(index) {
9015
+ if (generating()) return;
9016
+ setImages((prev) => prev.filter((_, i) => i !== index));
9017
+ setThumbUrls((prev) => {
9018
+ const url = prev[index];
9019
+ if (url) URL.revokeObjectURL(url);
9020
+ return prev.filter((_, i) => i !== index);
9021
+ });
9022
+ }
9023
+ function applyMarkdownToTarget(md, t) {
9024
+ const current = t === "content" ? deps.getContentMd() : deps.getDesignMd();
9025
+ const setter = t === "content" ? deps.setContentMd : deps.setDesignMd;
9026
+ if (!current.trim()) {
9027
+ setter(md);
9028
+ return;
9029
+ }
9030
+ const replace = window.confirm(
9031
+ t === "content" ? "Replace existing Content brief? Click Cancel to append below." : "Replace existing Design brief? Click Cancel to append below."
9032
+ );
9033
+ if (replace) {
9034
+ setter(md);
9035
+ } else {
9036
+ setter((prev) => `${prev.trim()}
9037
+
9038
+ ---
9039
+
9040
+ ${md}`);
9041
+ }
9042
+ }
9043
+ async function flushPendingReferenceFiles(requestId) {
9044
+ const pending = pendingReferenceFiles();
9045
+ if (pending.length === 0) return;
9046
+ setPendingReferenceFiles([]);
9047
+ const chips = [];
9048
+ for (const file of pending) {
9049
+ const result = await uploadAddPageAsset({
9050
+ requestId,
9051
+ role: "reference",
9052
+ file
9053
+ });
9054
+ chips.push({
9055
+ rowKey: result.id,
9056
+ id: result.id,
9057
+ filename: file.name,
9058
+ path: result.path,
9059
+ role: "reference",
9060
+ roleLabel: "Reference"
9061
+ });
8902
9062
  }
9063
+ if (chips.length > 0) deps.appendAttachments(chips);
8903
9064
  }
8904
- return dedupePastedFiles(out);
8905
- }
8906
- let aiditorBoundDocumentPaste = null;
8907
- function bindAiditorDocumentPasteCapture(handler) {
8908
- if (typeof document === "undefined")
8909
- return;
8910
- if (aiditorBoundDocumentPaste) {
8911
- document.removeEventListener("paste", aiditorBoundDocumentPaste, true);
9065
+ async function runGenerate() {
9066
+ if (!canGenerate()) return;
9067
+ setPopoverError("");
9068
+ setGenerating(true);
9069
+ deps.setComposerError("");
9070
+ const files = images();
9071
+ const t = target();
9072
+ let requestId = deps.getRequestId() ?? void 0;
9073
+ if (!requestId && deps.getRoute().trim()) {
9074
+ const minted = await deps.mintRequestIdIfNeeded();
9075
+ if (minted) requestId = minted;
9076
+ }
9077
+ try {
9078
+ const result = await generateAddPageBriefFromImage({
9079
+ target: t,
9080
+ requestId,
9081
+ pageRoute: deps.getRoute().trim() || void 0,
9082
+ contextNotes: contextNotes().trim() || void 0,
9083
+ images: files
9084
+ });
9085
+ if (!result.ok || !result.markdown) {
9086
+ setPopoverError(result.error ?? "Brief generation failed.");
9087
+ return;
9088
+ }
9089
+ applyMarkdownToTarget(result.markdown, t);
9090
+ if (t === "content" && result.suggestedRoute) {
9091
+ setSuggestedRoute(result.suggestedRoute);
9092
+ setSuggestedRouteKind(result.suggestedRouteKind ?? null);
9093
+ }
9094
+ if (result.referenceAttachments?.length) {
9095
+ deps.appendAttachments(
9096
+ result.referenceAttachments.map((a2) => ({
9097
+ rowKey: a2.id,
9098
+ id: a2.id,
9099
+ filename: a2.filename,
9100
+ path: a2.path,
9101
+ role: "reference",
9102
+ roleLabel: "Reference"
9103
+ }))
9104
+ );
9105
+ } else if (!requestId) {
9106
+ setPendingReferenceFiles((prev) => [...prev, ...files]);
9107
+ }
9108
+ closePopover();
9109
+ } catch (err) {
9110
+ setPopoverError(err instanceof Error ? err.message : String(err));
9111
+ } finally {
9112
+ setGenerating(false);
9113
+ }
9114
+ }
9115
+ function applySuggestedRoute() {
9116
+ const route = suggestedRoute();
9117
+ if (!route) return;
9118
+ deps.setRoute(route);
9119
+ deps.setRequestId(null);
9120
+ deps.scheduleRouteCheck(route);
9121
+ setSuggestedRoute(null);
9122
+ setSuggestedRouteKind(null);
9123
+ }
9124
+ function dismissSuggestedRoute() {
9125
+ setSuggestedRoute(null);
9126
+ setSuggestedRouteKind(null);
9127
+ }
9128
+ function onRequestIdMinted(requestId) {
9129
+ void flushPendingReferenceFiles(requestId);
9130
+ }
9131
+ function resetBriefFill() {
9132
+ closePopover();
9133
+ setSuggestedRoute(null);
9134
+ setSuggestedRouteKind(null);
9135
+ setPendingReferenceFiles([]);
9136
+ }
9137
+ function bindBriefFillRefs(refs) {
9138
+ refs.addPageFillContentFromImageBtn.onclick(() => openPopover("content"));
9139
+ refs.addPageFillDesignFromImageBtn.onclick(() => openPopover("design"));
9140
+ refs.addPageBriefFillCancelBtn.onclick(() => closePopover());
9141
+ refs.addPageBriefFillCloseBtn.onclick(() => closePopover());
9142
+ refs.addPageBriefFillGenerateBtn.onclick(() => {
9143
+ void runGenerate();
9144
+ });
9145
+ refs.addPageBriefFillContextNotes.oninput(
9146
+ ({ event }) => {
9147
+ if (generating()) return;
9148
+ setContextNotes(event.target.value);
9149
+ }
9150
+ );
9151
+ refs.addPageBriefFillFileInput.onchange(
9152
+ ({ event }) => {
9153
+ if (generating()) return;
9154
+ const input = event.target;
9155
+ const files = input.files ? Array.from(input.files) : [];
9156
+ addImageFiles(files);
9157
+ input.value = "";
9158
+ }
9159
+ );
9160
+ refs.addPageBriefFillDropzone.onclick(() => {
9161
+ if (generating()) return;
9162
+ refs.addPageBriefFillFileInput.exec$?.((el) => {
9163
+ el.click();
9164
+ });
9165
+ });
9166
+ refs.addPageBriefFillDropzone.ondragover(
9167
+ ({ event }) => {
9168
+ event.preventDefault();
9169
+ }
9170
+ );
9171
+ refs.addPageBriefFillDropzone.ondrop(
9172
+ ({ event }) => {
9173
+ event.preventDefault();
9174
+ if (generating()) return;
9175
+ const dt = event.dataTransfer;
9176
+ if (!dt?.files?.length) return;
9177
+ addImageFiles(Array.from(dt.files));
9178
+ }
9179
+ );
9180
+ refs.addPageBriefFillDropzone.addEventListener(
9181
+ "paste",
9182
+ (ev) => {
9183
+ if (generating()) return;
9184
+ const pasted = imageFilesFromPasteEvent(ev.event);
9185
+ if (pasted.length === 0) return;
9186
+ ev.event.preventDefault();
9187
+ addImageFiles(pasted);
9188
+ }
9189
+ );
9190
+ refs.addPageBriefFillThumbnails.onclick(
9191
+ ({ event }) => {
9192
+ if (generating()) return;
9193
+ const btn = event.target.closest(
9194
+ "[data-brief-fill-remove]"
9195
+ );
9196
+ if (!btn) return;
9197
+ const idx = Number.parseInt(btn.dataset.briefFillRemove ?? "", 10);
9198
+ if (Number.isNaN(idx)) return;
9199
+ removeImageAt(idx);
9200
+ }
9201
+ );
9202
+ refs.addPageSuggestedRouteApplyBtn.onclick(() => applySuggestedRoute());
9203
+ refs.addPageSuggestedRouteDismissBtn.onclick(() => dismissSuggestedRoute());
8912
9204
  }
8913
- aiditorBoundDocumentPaste = handler;
8914
- document.addEventListener("paste", handler, true);
9205
+ return {
9206
+ showBriefFillPopover,
9207
+ briefFillContextNotes: contextNotes,
9208
+ briefFillPopoverError: popoverError,
9209
+ showBriefFillPopoverError: createMemo(() => popoverError().length > 0),
9210
+ briefFillDisabled,
9211
+ briefFillInputDisabled,
9212
+ briefFillDropzoneClass,
9213
+ generateDisabled,
9214
+ briefFillStatusHint,
9215
+ showBriefFillStatusHint,
9216
+ imageThumbRows,
9217
+ showBriefFillThumbnails,
9218
+ showSuggestedRouteBanner,
9219
+ suggestedRouteLabel,
9220
+ bindBriefFillRefs,
9221
+ resetBriefFill,
9222
+ onRequestIdMinted,
9223
+ briefFillGenerating: generating
9224
+ };
8915
9225
  }
8916
- function aiditorConstructor(_props, refs) {
8917
- const [projectDir, setProjectDir] = createSignal("");
8918
- const [jayDevUrl, setJayDevUrl] = createSignal(JAY_DEV_URL_DEFAULT);
8919
- const [isBootstrapping, setIsBootstrapping] = createSignal(true);
8920
- const [showBootstrapError, setShowBootstrapError] = createSignal(false);
8921
- const [bootstrapErrorText, setBootstrapErrorText] = createSignal("");
8922
- const [activeTaskId, setActiveTaskId] = createSignal(null);
8923
- const [chunks, setChunks] = createSignal([]);
8924
- const [bottomPanelCollapsed, setBottomPanelCollapsed] = createSignal(true);
8925
- const [isRunning, setIsRunning] = createSignal(false);
8926
- const [showFilePreview, setShowFilePreview] = createSignal(false);
9226
+ const CATALOG = [
9227
+ {
9228
+ pluginName: "wix-server-client",
9229
+ packageName: "@jay-framework/wix-server-client",
9230
+ description: "Wix API client and authentication",
9231
+ kind: "service-only",
9232
+ requires: [],
9233
+ showInAddPagePicker: false,
9234
+ configFiles: ["config/.wix.yaml"]
9235
+ },
9236
+ {
9237
+ pluginName: "wix-cart",
9238
+ packageName: "@jay-framework/wix-cart",
9239
+ description: "Shopping cart headless components",
9240
+ kind: "headless",
9241
+ requires: ["wix-server-client"],
9242
+ showInAddPagePicker: true
9243
+ },
9244
+ {
9245
+ pluginName: "wix-stores",
9246
+ packageName: "@jay-framework/wix-stores",
9247
+ description: "Wix Stores product search, product page, categories",
9248
+ kind: "headless",
9249
+ requires: ["wix-server-client", "wix-cart"],
9250
+ showInAddPagePicker: true,
9251
+ configFiles: ["config/.wix-stores.yaml"]
9252
+ },
9253
+ {
9254
+ pluginName: "wix-stores-v1",
9255
+ packageName: "@jay-framework/wix-stores-v1",
9256
+ description: "Wix Stores v1 catalog API",
9257
+ kind: "headless",
9258
+ requires: ["wix-server-client", "wix-cart"],
9259
+ showInAddPagePicker: true
9260
+ },
9261
+ {
9262
+ pluginName: "wix-data",
9263
+ packageName: "@jay-framework/wix-data",
9264
+ description: "Wix Data collections CMS",
9265
+ kind: "headless",
9266
+ requires: ["wix-server-client"],
9267
+ showInAddPagePicker: true,
9268
+ configFiles: ["config/.wix-data.yaml"]
9269
+ },
9270
+ {
9271
+ pluginName: "wix-media",
9272
+ packageName: "@jay-framework/wix-media",
9273
+ description: "Wix media service (no page components)",
9274
+ kind: "service-only",
9275
+ requires: ["wix-server-client"],
9276
+ showInAddPagePicker: true
9277
+ },
9278
+ {
9279
+ pluginName: "ui-kit",
9280
+ packageName: "@jay-framework/ui-kit",
9281
+ description: "Jay UI kit headless components",
9282
+ kind: "headless",
9283
+ requires: [],
9284
+ showInAddPagePicker: true
9285
+ },
9286
+ {
9287
+ pluginName: "gemini-agent",
9288
+ packageName: "@jay-framework/gemini-agent",
9289
+ description: "Gemini AI agent plugin",
9290
+ kind: "headless",
9291
+ requires: [],
9292
+ showInAddPagePicker: true,
9293
+ configFiles: ["config/.gemini-agent.yaml"]
9294
+ },
9295
+ {
9296
+ pluginName: "webmcp",
9297
+ packageName: "@jay-framework/webmcp",
9298
+ description: "Web MCP tooling",
9299
+ kind: "tooling",
9300
+ requires: [],
9301
+ showInAddPagePicker: false
9302
+ },
9303
+ {
9304
+ pluginName: "aiditor",
9305
+ packageName: "@jay-framework/aiditor",
9306
+ description: "AIditor self plugin",
9307
+ kind: "tooling",
9308
+ requires: [],
9309
+ showInAddPagePicker: false
9310
+ }
9311
+ ];
9312
+ function getCatalogEntry(pluginName) {
9313
+ return CATALOG.find((e2) => e2.pluginName === pluginName);
9314
+ }
9315
+ const DEFAULT_COMPONENT_KEYS = {
9316
+ "product-page": "p",
9317
+ "product-search": "search",
9318
+ "cart-indicator": "cart",
9319
+ "cart-page": "cartPage",
9320
+ "category-list": "categories"
9321
+ };
9322
+ function getDefaultComponentKey(contractName) {
9323
+ if (contractName in DEFAULT_COMPONENT_KEYS) {
9324
+ return DEFAULT_COMPONENT_KEYS[contractName];
9325
+ }
9326
+ return contractName.charAt(0) || "c";
9327
+ }
9328
+ const listJayPluginsAction = createActionCaller("aiditor.listJayPlugins", "GET");
9329
+ createActionCaller("aiditor.listPluginContracts", "GET");
9330
+ const checkPluginUpdateAction = createActionCaller("aiditor.checkPluginUpdate", "GET");
9331
+ const ensureProjectPluginAction = createStreamCaller("aiditor.ensureProjectPlugin");
9332
+ createActionCaller("aiditor.getPluginSetupStatus", "GET");
9333
+ const rerunPluginSetupAction = createActionCaller("aiditor.rerunPluginSetup", "GET");
9334
+ const syncAddPagePluginManifestAction = createActionCaller("aiditor.syncAddPagePluginManifest", "GET");
9335
+ const writePluginSourceAction = createActionCaller("aiditor.writePluginSource", "GET");
9336
+ async function listJayPlugins(refreshSetupStatus = false) {
9337
+ return listJayPluginsAction({ refreshSetupStatus });
9338
+ }
9339
+ async function checkPluginUpdate(pluginName) {
9340
+ return checkPluginUpdateAction({ pluginName });
9341
+ }
9342
+ function ensureProjectPluginStream(input, callbacks) {
9343
+ let aborted = false;
9344
+ const abort = () => {
9345
+ aborted = true;
9346
+ };
9347
+ void (async () => {
9348
+ try {
9349
+ for await (const chunk of ensureProjectPluginAction(input)) {
9350
+ if (aborted) break;
9351
+ callbacks.onChunk(chunk);
9352
+ if (chunk.type === "done" || chunk.type === "error") {
9353
+ callbacks.onEnd();
9354
+ return;
9355
+ }
9356
+ }
9357
+ if (!aborted) callbacks.onEnd();
9358
+ } catch (err) {
9359
+ if (!aborted) {
9360
+ callbacks.onError(err instanceof Error ? err : new Error(String(err)));
9361
+ }
9362
+ }
9363
+ })();
9364
+ return abort;
9365
+ }
9366
+ async function syncAddPagePluginManifest(input) {
9367
+ return syncAddPagePluginManifestAction(input);
9368
+ }
9369
+ async function rerunPluginSetup(pluginName, force) {
9370
+ return rerunPluginSetupAction({ pluginName, force });
9371
+ }
9372
+ async function writePluginSource(input) {
9373
+ return writePluginSourceAction(input);
9374
+ }
9375
+ const ADD_PAGE_MANAGE_PLUGINS_VALUE = "__manage_plugins__";
9376
+ function upsertPagePluginContract(prev, pluginName, packageName, contractName, componentKey) {
9377
+ const next = [...prev];
9378
+ let plugin = next.find((p) => p.pluginName === pluginName);
9379
+ if (!plugin) {
9380
+ plugin = {
9381
+ pluginName,
9382
+ packageName,
9383
+ contracts: [],
9384
+ installStatus: "not_needed"
9385
+ };
9386
+ next.push(plugin);
9387
+ }
9388
+ const exists = plugin.contracts.some((c) => c.contractName === contractName);
9389
+ const contracts = exists ? plugin.contracts.map(
9390
+ (c) => c.contractName === contractName ? { ...c, componentKey } : c
9391
+ ) : [...plugin.contracts, { contractName, componentKey }];
9392
+ return next.map(
9393
+ (p) => p.pluginName === pluginName ? { ...p, packageName, contracts } : p
9394
+ );
9395
+ }
9396
+ function removePagePluginContract(prev, pluginName, contractName) {
9397
+ return prev.map(
9398
+ (p) => p.pluginName === pluginName ? {
9399
+ ...p,
9400
+ contracts: p.contracts.filter(
9401
+ (c) => c.contractName !== contractName
9402
+ )
9403
+ } : p
9404
+ ).filter((p) => p.contracts.length > 0);
9405
+ }
9406
+ function initAddPagePluginsPanel(deps) {
9407
+ const { projectPlugins, openProjectSettings } = deps;
9408
+ const [selectedPlugins, setSelectedPlugins] = createSignal([]);
9409
+ const [pickerPlugin, setPickerPlugin] = createSignal("");
9410
+ const [pickerValue, setPickerValue] = createSignal("");
9411
+ let syncTimer;
9412
+ const installedHeadlessPlugins = createMemo(
9413
+ () => projectPlugins.pluginsList().filter(
9414
+ (p) => p.installed && p.kind === "headless" && p.contracts.length > 0
9415
+ )
9416
+ );
9417
+ const addPagePluginPickerOptions = createMemo(
9418
+ () => {
9419
+ const options = installedHeadlessPlugins().map((p) => ({
9420
+ rowKey: p.pluginName,
9421
+ value: p.pluginName,
9422
+ label: p.pluginName
9423
+ }));
9424
+ options.push({
9425
+ rowKey: ADD_PAGE_MANAGE_PLUGINS_VALUE,
9426
+ value: ADD_PAGE_MANAGE_PLUGINS_VALUE,
9427
+ label: "Manage plugins…"
9428
+ });
9429
+ return options;
9430
+ }
9431
+ );
9432
+ const addPageContractPickerRows = createMemo(
9433
+ () => {
9434
+ const pluginName = pickerPlugin();
9435
+ if (!pluginName) return [];
9436
+ const entry = projectPlugins.pluginsList().find((p) => p.pluginName === pluginName);
9437
+ if (!entry?.installed || entry.kind !== "headless") return [];
9438
+ const sel = selectedPlugins().find((s) => s.pluginName === pluginName);
9439
+ return entry.contracts.map((c) => ({
9440
+ rowKey: `${pluginName}:${c.contractName}`,
9441
+ pluginName,
9442
+ packageName: entry.packageName,
9443
+ contractName: c.contractName,
9444
+ description: c.description ?? "",
9445
+ onPage: !!sel?.contracts.some((x) => x.contractName === c.contractName),
9446
+ componentKey: sel?.contracts.find((x) => x.contractName === c.contractName)?.componentKey ?? getDefaultComponentKey(c.contractName)
9447
+ }));
9448
+ }
9449
+ );
9450
+ const pagePluginChipRows = createMemo(() => {
9451
+ const chips = [];
9452
+ for (const plugin of selectedPlugins()) {
9453
+ for (const contract of plugin.contracts) {
9454
+ chips.push({
9455
+ rowKey: `${plugin.pluginName}:${contract.contractName}`,
9456
+ label: `${plugin.pluginName} · ${contract.contractName}`,
9457
+ pluginName: plugin.pluginName,
9458
+ packageName: plugin.packageName,
9459
+ contractName: contract.contractName,
9460
+ componentKey: contract.componentKey ?? getDefaultComponentKey(contract.contractName)
9461
+ });
9462
+ }
9463
+ }
9464
+ return chips;
9465
+ });
9466
+ const showContractPicker = createMemo(() => pickerPlugin().length > 0);
9467
+ const showPagePluginsZone = createMemo(() => true);
9468
+ const showPluginsPanel = createMemo(
9469
+ () => projectPlugins.pluginsLoaded() && projectPlugins.pluginsList().length > 0
9470
+ );
9471
+ const showPagePluginsEmptyHint = createMemo(
9472
+ () => showPluginsPanel() && pagePluginChipRows().length === 0
9473
+ );
9474
+ const pluginsBlockCreatePage = createMemo(() => {
9475
+ if (projectPlugins.installInProgress()) return true;
9476
+ return selectedPlugins().some(
9477
+ (p) => p.installStatus === "installing" || p.installStatus === "pending" || p.installStatus === "failed"
9478
+ );
9479
+ });
9480
+ function scheduleManifestSync() {
9481
+ const requestId = deps.getRequestId();
9482
+ if (!requestId) return;
9483
+ if (syncTimer) clearTimeout(syncTimer);
9484
+ syncTimer = setTimeout(() => {
9485
+ void syncAddPagePluginManifest({
9486
+ requestId,
9487
+ selectedPlugins: selectedPlugins()
9488
+ }).then((res) => {
9489
+ if (res.ok && res.contentMd) {
9490
+ deps.setContentMd(res.contentMd);
9491
+ }
9492
+ });
9493
+ }, 300);
9494
+ }
9495
+ function addContractToPage(pluginName, packageName, contractName, componentKey) {
9496
+ setSelectedPlugins(
9497
+ (prev) => upsertPagePluginContract(
9498
+ prev,
9499
+ pluginName,
9500
+ packageName,
9501
+ contractName,
9502
+ componentKey
9503
+ )
9504
+ );
9505
+ scheduleManifestSync();
9506
+ }
9507
+ function removeFromPage(pluginName, packageName, contractName) {
9508
+ setSelectedPlugins(
9509
+ (prev) => removePagePluginContract(prev, pluginName, contractName)
9510
+ );
9511
+ scheduleManifestSync();
9512
+ }
9513
+ function handlePickerChange(value) {
9514
+ setPickerValue("");
9515
+ if (value === ADD_PAGE_MANAGE_PLUGINS_VALUE) {
9516
+ openProjectSettings();
9517
+ setPickerPlugin("");
9518
+ return;
9519
+ }
9520
+ if (!value) {
9521
+ setPickerPlugin("");
9522
+ return;
9523
+ }
9524
+ setPickerPlugin(value);
9525
+ }
9526
+ function resetPluginsPanel() {
9527
+ setSelectedPlugins([]);
9528
+ setPickerPlugin("");
9529
+ setPickerValue("");
9530
+ if (syncTimer) clearTimeout(syncTimer);
9531
+ }
9532
+ function bindPluginsPanelRefs(refs) {
9533
+ refs.addPagePluginPicker?.onchange(
9534
+ ({ event }) => {
9535
+ const value = event.target.value;
9536
+ handlePickerChange(value);
9537
+ }
9538
+ );
9539
+ refs.addPagePluginsPanel.onclick(
9540
+ ({ event }) => {
9541
+ const target = event.target;
9542
+ const manageLink = target.closest("[data-add-page-manage-plugins]");
9543
+ if (manageLink) {
9544
+ openProjectSettings();
9545
+ return;
9546
+ }
9547
+ const addBtn = target.closest(
9548
+ "[data-add-page-add-contract]"
9549
+ );
9550
+ if (addBtn?.dataset.plugin && addBtn.dataset.contract && addBtn.dataset.package) {
9551
+ addContractToPage(
9552
+ addBtn.dataset.plugin,
9553
+ addBtn.dataset.package,
9554
+ addBtn.dataset.contract,
9555
+ addBtn.dataset.key ?? getDefaultComponentKey(addBtn.dataset.contract)
9556
+ );
9557
+ return;
9558
+ }
9559
+ const removeChip = target.closest(
9560
+ "[data-add-page-remove-chip]"
9561
+ );
9562
+ if (removeChip?.dataset.plugin && removeChip.dataset.contract && removeChip.dataset.package) {
9563
+ removeFromPage(
9564
+ removeChip.dataset.plugin,
9565
+ removeChip.dataset.package,
9566
+ removeChip.dataset.contract
9567
+ );
9568
+ }
9569
+ }
9570
+ );
9571
+ }
9572
+ return {
9573
+ selectedPlugins,
9574
+ setSelectedPlugins,
9575
+ scheduleManifestSync,
9576
+ syncManifestNow: async () => {
9577
+ const requestId = deps.getRequestId();
9578
+ if (!requestId) return;
9579
+ const res = await syncAddPagePluginManifest({
9580
+ requestId,
9581
+ selectedPlugins: selectedPlugins()
9582
+ });
9583
+ if (res.ok && res.contentMd) deps.setContentMd(res.contentMd);
9584
+ },
9585
+ bindPluginsPanelRefs,
9586
+ addPagePluginPickerOptions,
9587
+ addPagePluginPickerValue: pickerValue,
9588
+ addPagePickerPluginLabel: createMemo(() => {
9589
+ const name = pickerPlugin();
9590
+ return name ? `Components from ${name}` : "";
9591
+ }),
9592
+ addPageContractPickerRows,
9593
+ pagePluginChipRows,
9594
+ showContractPicker,
9595
+ showPagePluginsZone,
9596
+ showPluginsPanel,
9597
+ showPagePluginsEmptyHint,
9598
+ pluginsBlockCreatePage,
9599
+ pickerPlugin,
9600
+ setPickerPlugin,
9601
+ handlePickerChange,
9602
+ resetPluginsPanel
9603
+ };
9604
+ }
9605
+ function tryJayRefExec(refObj, fn) {
9606
+ if (typeof refObj?.exec$ !== "function") return false;
9607
+ try {
9608
+ refObj.exec$(fn);
9609
+ return true;
9610
+ } catch {
9611
+ return false;
9612
+ }
9613
+ }
9614
+ function initAddPageComposer(deps) {
9615
+ const [showAddPageComposer, setShowAddPageComposer] = createSignal(false);
9616
+ const [addPageRoute, setAddPageRoute] = createSignal("");
9617
+ const [addPageContentMd, setAddPageContentMd] = createSignal("");
9618
+ const [addPageDesignMd, setAddPageDesignMd] = createSignal("");
9619
+ const [addPageRequestId, setAddPageRequestId] = createSignal(
9620
+ null
9621
+ );
9622
+ const [addPageRequestRoute, setAddPageRequestRoute] = createSignal(null);
9623
+ const [addPageRouteError, setAddPageRouteError] = createSignal("");
9624
+ const [addPageComposerError, setAddPageComposerError] = createSignal("");
9625
+ const [addPageUploadHint, setAddPageUploadHint] = createSignal("");
9626
+ const [addPageAttachments, setAddPageAttachments] = createSignal([]);
9627
+ const [addPageShowDesignHint, setAddPageShowDesignHint] = createSignal(false);
9628
+ const [addPageIsDynamicRoute, setAddPageIsDynamicRoute] = createSignal(false);
9629
+ const [addPageSubmitting, setAddPageSubmitting] = createSignal(false);
9630
+ const pluginsPanel = initAddPagePluginsPanel({
9631
+ projectPlugins: deps.projectPlugins,
9632
+ getRequestId: () => addPageRequestId(),
9633
+ getContentMd: () => addPageContentMd(),
9634
+ setContentMd: setAddPageContentMd,
9635
+ openProjectSettings: deps.openProjectSettings
9636
+ });
9637
+ const briefFillPanel = initAddPageBriefFillPanel({
9638
+ isBusy: () => deps.isRunning() || addPageSubmitting(),
9639
+ getRoute: () => addPageRoute(),
9640
+ setRoute: setAddPageRoute,
9641
+ getRequestId: () => addPageRequestId(),
9642
+ setRequestId: setAddPageRequestId,
9643
+ getContentMd: () => addPageContentMd(),
9644
+ setContentMd: setAddPageContentMd,
9645
+ getDesignMd: () => addPageDesignMd(),
9646
+ setDesignMd: setAddPageDesignMd,
9647
+ appendAttachments: (chips) => {
9648
+ setAddPageAttachments((prev) => [...prev, ...chips]);
9649
+ updateDesignHint();
9650
+ },
9651
+ mintRequestIdIfNeeded,
9652
+ scheduleRouteCheck,
9653
+ setComposerError: setAddPageComposerError
9654
+ });
9655
+ let routeCheckTimer;
9656
+ let contentTextareaEl = null;
9657
+ let designTextareaEl = null;
9658
+ const showAddPageRouteError = createMemo(
9659
+ () => addPageRouteError().length > 0
9660
+ );
9661
+ const showAddPageComposerError = createMemo(
9662
+ () => addPageComposerError().length > 0
9663
+ );
9664
+ const showAddPageUploadHint = createMemo(
9665
+ () => addPageUploadHint().length > 0
9666
+ );
9667
+ const showAddPageAttachments = createMemo(
9668
+ () => addPageAttachments().length > 0
9669
+ );
9670
+ const addPageRunningHint = createMemo(
9671
+ () => deps.isRunning() ? "Agent is running…" : ""
9672
+ );
9673
+ const showAddPageRunningHint = createMemo(
9674
+ () => addPageRunningHint().length > 0
9675
+ );
9676
+ const addPageAttachmentDisplayRows = createMemo(
9677
+ () => addPageAttachments().map((a2) => ({
9678
+ ...a2,
9679
+ rowKey: a2.id
9680
+ }))
9681
+ );
9682
+ const addPageCanSubmit = createMemo(() => {
9683
+ if (deps.isRunning() || addPageSubmitting() || briefFillPanel.briefFillGenerating()) {
9684
+ return false;
9685
+ }
9686
+ if (pluginsPanel.pluginsBlockCreatePage()) return false;
9687
+ const route = addPageRoute().trim();
9688
+ if (!route || addPageRouteError()) return false;
9689
+ const hasMd = addPageContentMd().trim().length > 0 || addPageDesignMd().trim().length > 0;
9690
+ return hasMd;
9691
+ });
9692
+ const addPageCreateDisabled = createMemo(
9693
+ () => !addPageCanSubmit() || deps.isRunning() || briefFillPanel.briefFillGenerating()
9694
+ );
9695
+ function resetAddPageComposer() {
9696
+ setAddPageRoute("");
9697
+ setAddPageContentMd("");
9698
+ setAddPageDesignMd("");
9699
+ setAddPageRequestId(null);
9700
+ setAddPageRequestRoute(null);
9701
+ setAddPageRouteError("");
9702
+ setAddPageComposerError("");
9703
+ setAddPageUploadHint("");
9704
+ setAddPageAttachments([]);
9705
+ setAddPageShowDesignHint(false);
9706
+ setAddPageIsDynamicRoute(false);
9707
+ pluginsPanel.resetPluginsPanel();
9708
+ briefFillPanel.resetBriefFill();
9709
+ }
9710
+ function openAddPageComposer() {
9711
+ resetAddPageComposer();
9712
+ setShowAddPageComposer(true);
9713
+ void deps.projectPlugins.refreshPluginsList();
9714
+ }
9715
+ async function refreshCurrentPageBriefAvailability() {
9716
+ const filePath = deps.selectedPageFilePath().trim();
9717
+ if (!filePath) {
9718
+ deps.setCurrentPageHasBrief(false);
9719
+ return;
9720
+ }
9721
+ try {
9722
+ const result = await checkPageAddPageBrief({
9723
+ jayHtmlPath: filePath,
9724
+ pageRoute: deps.selectedPageUrl() ?? void 0
9725
+ });
9726
+ deps.setCurrentPageHasBrief(result.hasBrief);
9727
+ } catch {
9728
+ deps.setCurrentPageHasBrief(false);
9729
+ }
9730
+ }
9731
+ async function openAddPageComposerFromBrief() {
9732
+ const filePath = deps.selectedPageFilePath().trim();
9733
+ const sourceRoute = deps.selectedPageUrl();
9734
+ if (!filePath || !sourceRoute) {
9735
+ setAddPageComposerError(
9736
+ "Select a project page with saved Add Page briefs."
9737
+ );
9738
+ setShowAddPageComposer(true);
9739
+ return;
9740
+ }
9741
+ resetAddPageComposer();
9742
+ setShowAddPageComposer(true);
9743
+ setAddPageComposerError("");
9744
+ void deps.projectPlugins.refreshPluginsList();
9745
+ try {
9746
+ const result = await openAddPageFromBrief({
9747
+ jayHtmlPath: filePath,
9748
+ sourcePageRoute: sourceRoute
9749
+ });
9750
+ if (!result.ok || !result.requestId || !result.pageRoute) {
9751
+ setAddPageComposerError(result.error ?? "Could not load page brief.");
9752
+ return;
9753
+ }
9754
+ setAddPageRoute(result.pageRoute);
9755
+ setAddPageContentMd(result.contentMd ?? "");
9756
+ setAddPageDesignMd(result.designMd ?? "");
9757
+ setAddPageRequestId(result.requestId);
9758
+ setAddPageRequestRoute(result.pageRoute);
9759
+ setAddPageIsDynamicRoute(result.routeKind === "dynamic");
9760
+ setAddPageRouteError("");
9761
+ setAddPageAttachments(
9762
+ (result.attachments ?? []).map((a2) => ({
9763
+ rowKey: a2.id,
9764
+ id: a2.id,
9765
+ filename: a2.filename,
9766
+ path: a2.path,
9767
+ role: a2.role,
9768
+ roleLabel: a2.role === "reference" ? "Reference" : "Asset"
9769
+ }))
9770
+ );
9771
+ updateDesignHint();
9772
+ } catch (err) {
9773
+ setAddPageComposerError(err instanceof Error ? err.message : String(err));
9774
+ }
9775
+ }
9776
+ function closeAddPageComposer(force = false) {
9777
+ const hasText = addPageRoute().trim() || addPageContentMd().trim() || addPageDesignMd().trim() || addPageAttachments().length > 0;
9778
+ if (!force && hasText && !window.confirm("Discard Add Page draft?")) {
9779
+ return;
9780
+ }
9781
+ setShowAddPageComposer(false);
9782
+ resetAddPageComposer();
9783
+ }
9784
+ function scheduleRouteCheck(route) {
9785
+ if (routeCheckTimer) clearTimeout(routeCheckTimer);
9786
+ setAddPageIsDynamicRoute(inferRouteKindFromRoute(route) === "dynamic");
9787
+ if (!route.trim()) {
9788
+ setAddPageRouteError("");
9789
+ return;
9790
+ }
9791
+ routeCheckTimer = setTimeout(() => {
9792
+ void checkAddPageRoute(route).then((res) => {
9793
+ if (!res.valid) {
9794
+ setAddPageRouteError(res.error ?? "Invalid route.");
9795
+ setAddPageRequestId(null);
9796
+ setAddPageRequestRoute(null);
9797
+ return;
9798
+ }
9799
+ if (res.routeExists) {
9800
+ setAddPageRouteError("This route already exists.");
9801
+ setAddPageRequestId(null);
9802
+ setAddPageRequestRoute(null);
9803
+ return;
9804
+ }
9805
+ setAddPageRouteError("");
9806
+ setAddPageIsDynamicRoute(res.routeKind === "dynamic");
9807
+ if (addPageRequestId() && addPageRequestRoute() && addPageRequestRoute() !== res.normalizedRoute) {
9808
+ setAddPageRequestId(null);
9809
+ }
9810
+ if (!addPageRequestId()) {
9811
+ void ensureAddPageRequestId(res.normalizedRoute, null).then(
9812
+ (ensured) => {
9813
+ if (ensured.requestId) {
9814
+ setAddPageRequestId(ensured.requestId);
9815
+ setAddPageRequestRoute(res.normalizedRoute);
9816
+ briefFillPanel.onRequestIdMinted(ensured.requestId);
9817
+ }
9818
+ }
9819
+ );
9820
+ }
9821
+ }).catch((err) => {
9822
+ setAddPageRouteError(
9823
+ err instanceof Error ? err.message : String(err)
9824
+ );
9825
+ setAddPageRequestId(null);
9826
+ setAddPageRequestRoute(null);
9827
+ });
9828
+ }, 300);
9829
+ }
9830
+ function updateDesignHint() {
9831
+ const thinDesign = addPageDesignMd().trim().length < 40;
9832
+ const noRefs = addPageAttachments().length === 0;
9833
+ setAddPageShowDesignHint(thinDesign && noRefs);
9834
+ }
9835
+ async function mintRequestIdIfNeeded() {
9836
+ const route = addPageRoute().trim();
9837
+ if (!route) {
9838
+ setAddPageComposerError(
9839
+ "Enter a page route before attaching files or pasting images."
9840
+ );
9841
+ return null;
9842
+ }
9843
+ if (addPageRouteError()) {
9844
+ setAddPageComposerError(addPageRouteError());
9845
+ return null;
9846
+ }
9847
+ const ensured = await ensureAddPageRequestId(route, addPageRequestId());
9848
+ if (ensured.error) {
9849
+ setAddPageComposerError(ensured.error);
9850
+ return null;
9851
+ }
9852
+ setAddPageRequestId(ensured.requestId);
9853
+ setAddPageRequestRoute(route);
9854
+ briefFillPanel.onRequestIdMinted(ensured.requestId);
9855
+ return ensured.requestId;
9856
+ }
9857
+ async function handleAddPageUpload(file, role, markdownTarget = "content") {
9858
+ setAddPageComposerError("");
9859
+ setAddPageUploadHint(`Uploading ${file.name}…`);
9860
+ const requestId = await mintRequestIdIfNeeded();
9861
+ if (!requestId) {
9862
+ setAddPageUploadHint("");
9863
+ return;
9864
+ }
9865
+ try {
9866
+ const result = await uploadAddPageAsset({
9867
+ requestId,
9868
+ role,
9869
+ file
9870
+ });
9871
+ const chip = {
9872
+ rowKey: result.id,
9873
+ id: result.id,
9874
+ filename: file.name,
9875
+ path: result.path,
9876
+ role,
9877
+ roleLabel: role === "reference" ? "Reference" : "Asset"
9878
+ };
9879
+ setAddPageAttachments((prev) => [...prev, chip]);
9880
+ if (role === "asset" && result.markdownSnippet) {
9881
+ const ta = markdownTarget === "design" ? designTextareaEl : contentTextareaEl;
9882
+ const setMd = markdownTarget === "design" ? setAddPageDesignMd : setAddPageContentMd;
9883
+ if (ta) {
9884
+ setMd((md) => insertTextAtCursor(ta, result.markdownSnippet, md));
9885
+ }
9886
+ }
9887
+ updateDesignHint();
9888
+ } catch (err) {
9889
+ setAddPageComposerError(err instanceof Error ? err.message : String(err));
9890
+ } finally {
9891
+ setAddPageUploadHint("");
9892
+ }
9893
+ }
9894
+ function handleAddPageChunk(chunk) {
9895
+ if (chunk.type === "thinking" && chunk.text) {
9896
+ deps.appendAgentOutputChunk(chunk.text, "chunk chunk-thinking");
9897
+ } else if (chunk.type === "chunk" && chunk.text) {
9898
+ deps.appendAgentOutputChunk(chunk.text);
9899
+ } else if (chunk.type === "status" && chunk.message) {
9900
+ deps.appendAgentOutputChunk(chunk.message, "chunk chunk-status");
9901
+ } else if (chunk.type === "tool" && chunk.name) {
9902
+ const detail = chunk.text ? ` ${chunk.text}` : "";
9903
+ const fp = chunk.name === "Read" || chunk.name === "Write" ? chunk.text?.split("\n")[0]?.trim() ?? "" : "";
9904
+ deps.appendAgentOutputChunk(
9905
+ `> ${chunk.name}${detail}`,
9906
+ "chunk chunk-tool",
9907
+ fp,
9908
+ fp ? chunk.name : ""
9909
+ );
9910
+ } else if (chunk.type === "error" && chunk.message) {
9911
+ deps.appendAgentOutputChunk(chunk.message, "chunk chunk-error");
9912
+ if (chunk.text) {
9913
+ deps.appendAgentOutputChunk(chunk.text, "chunk chunk-error");
9914
+ }
9915
+ } else if (chunk.type === "result") {
9916
+ deps.appendAgentOutputChunk(
9917
+ chunk.message ?? "Page created.",
9918
+ "chunk chunk-result"
9919
+ );
9920
+ }
9921
+ }
9922
+ function findProjectPageForRoute(pageRoute) {
9923
+ const normalized = pageRoute.replace(/\/+$/, "") || "/";
9924
+ return deps.projectPages().find((p) => p.url === normalized) ?? deps.projectPages().find((p) => p.url === pageRoute);
9925
+ }
9926
+ async function openPreviewForRoute(pageRoute) {
9927
+ const normalized = pageRoute.replace(/\/+$/, "") || "/";
9928
+ await deps.loadProjectInfo();
9929
+ let page2 = findProjectPageForRoute(pageRoute);
9930
+ if (!page2) {
9931
+ await new Promise((r) => setTimeout(r, 400));
9932
+ await deps.loadProjectInfo();
9933
+ page2 = findProjectPageForRoute(pageRoute);
9934
+ }
9935
+ if (!page2) {
9936
+ page2 = {
9937
+ name: normalized,
9938
+ url: normalized,
9939
+ filePath: "",
9940
+ usedComponents: []
9941
+ };
9942
+ }
9943
+ deps.selectProjectPage(page2);
9944
+ if (inferRouteKindFromRoute(pageRoute) === "dynamic") {
9945
+ try {
9946
+ const allRows = [];
9947
+ for await (const batch of getPageParamsAction({ route: pageRoute })) {
9948
+ allRows.push(...batch);
9949
+ }
9950
+ const paths = pathnamesFromParamRows(page2.url, allRows);
9951
+ if (paths.length === 0) {
9952
+ deps.appendAgentOutputChunk(
9953
+ "Add a param value in the route picker to preview this dynamic page.",
9954
+ "chunk chunk-status"
9955
+ );
9956
+ }
9957
+ } catch {
9958
+ deps.appendAgentOutputChunk(
9959
+ "Add a param value in the route picker to preview this dynamic page.",
9960
+ "chunk chunk-status"
9961
+ );
9962
+ }
9963
+ }
9964
+ }
9965
+ let lastSubmitSnapshot = null;
9966
+ function submitAddPage(isRetry = false) {
9967
+ if (!addPageCanSubmit()) return;
9968
+ const route = addPageRoute().trim();
9969
+ void (async () => {
9970
+ setAddPageComposerError("");
9971
+ setAddPageSubmitting(true);
9972
+ const requestId = await mintRequestIdIfNeeded();
9973
+ if (!requestId) {
9974
+ setAddPageSubmitting(false);
9975
+ return;
9976
+ }
9977
+ lastSubmitSnapshot = {
9978
+ requestId,
9979
+ pageRoute: route,
9980
+ contentMd: addPageContentMd(),
9981
+ designMd: addPageDesignMd()
9982
+ };
9983
+ setShowAddPageComposer(false);
9984
+ deps.setChunks([]);
9985
+ deps.setIsRunning(true);
9986
+ deps.setBottomPanelCollapsed(false);
9987
+ deps.setAddPageResultBanner(null);
9988
+ await pluginsPanel.syncManifestNow();
9989
+ deps.getStopStream()?.();
9990
+ const abort = submitAddPageStream(
9991
+ {
9992
+ requestId,
9993
+ contentMd: addPageContentMd(),
9994
+ designMd: addPageDesignMd(),
9995
+ pageRoute: route,
9996
+ isRetry
9997
+ },
9998
+ {
9999
+ onChunk: (chunk) => {
10000
+ handleAddPageChunk(chunk);
10001
+ if (chunk.type === "result" && chunk.text) {
10002
+ deps.setAddPageResultBanner({
10003
+ kind: "success",
10004
+ message: "Page created — use annotate tools to refine details.",
10005
+ pageRoute: chunk.text,
10006
+ showRetry: false,
10007
+ showFixWithAiditor: false
10008
+ });
10009
+ void openPreviewForRoute(chunk.text);
10010
+ }
10011
+ if (chunk.type === "error" && "validateErrors" in chunk && Array.isArray(
10012
+ chunk.validateErrors
10013
+ ) && (chunk.validateErrors?.length ?? 0) > 0) {
10014
+ deps.setAddPageResultBanner({
10015
+ kind: "failed",
10016
+ message: "Validation failed.",
10017
+ pageRoute: route,
10018
+ showRetry: true,
10019
+ showFixWithAiditor: true
10020
+ });
10021
+ } else if (chunk.type === "error") {
10022
+ deps.setAddPageResultBanner({
10023
+ kind: "failed",
10024
+ message: chunk.message ?? "Add Page failed.",
10025
+ pageRoute: route,
10026
+ showRetry: true,
10027
+ showFixWithAiditor: true
10028
+ });
10029
+ }
10030
+ },
10031
+ onEnd: () => {
10032
+ deps.setIsRunning(false);
10033
+ setAddPageSubmitting(false);
10034
+ },
10035
+ onError: (err) => {
10036
+ deps.appendAgentOutputChunk(err.message, "chunk chunk-error");
10037
+ deps.setIsRunning(false);
10038
+ setAddPageSubmitting(false);
10039
+ deps.setAddPageResultBanner({
10040
+ kind: "failed",
10041
+ message: err.message,
10042
+ pageRoute: route,
10043
+ showRetry: true,
10044
+ showFixWithAiditor: false
10045
+ });
10046
+ }
10047
+ }
10048
+ );
10049
+ deps.setStopStream(abort);
10050
+ })();
10051
+ }
10052
+ function retryAddPage() {
10053
+ const snap = lastSubmitSnapshot;
10054
+ if (!snap) {
10055
+ openAddPageComposer();
10056
+ return;
10057
+ }
10058
+ setAddPageRoute(snap.pageRoute);
10059
+ setAddPageContentMd(snap.contentMd);
10060
+ setAddPageDesignMd(snap.designMd);
10061
+ setAddPageRequestId(snap.requestId);
10062
+ submitAddPage(true);
10063
+ }
10064
+ function bindAddPageRefs(refs) {
10065
+ refs.addPageOpenBtn.onclick(() => openAddPageComposer());
10066
+ refs.addPageFromBriefBtn.onclick(() => {
10067
+ void openAddPageComposerFromBrief();
10068
+ });
10069
+ refs.addPageCloseBtn.onclick(() => closeAddPageComposer());
10070
+ refs.addPageCancelBtn.onclick(() => closeAddPageComposer());
10071
+ refs.addPageOverlay.onclick(({ event }) => {
10072
+ if (event.target === event.currentTarget) {
10073
+ closeAddPageComposer();
10074
+ }
10075
+ });
10076
+ refs.addPageCreateBtn.onclick(() => submitAddPage(false));
10077
+ refs.addPageRouteInput.oninput(({ event }) => {
10078
+ const v = event.target.value;
10079
+ setAddPageRoute(v);
10080
+ scheduleRouteCheck(v);
10081
+ });
10082
+ refs.addPageContentMd.oninput(({ event }) => {
10083
+ contentTextareaEl = event.target;
10084
+ setAddPageContentMd(contentTextareaEl.value);
10085
+ updateDesignHint();
10086
+ });
10087
+ refs.addPageDesignMd.oninput(({ event }) => {
10088
+ designTextareaEl = event.target;
10089
+ setAddPageDesignMd(designTextareaEl.value);
10090
+ updateDesignHint();
10091
+ });
10092
+ function imageFilesFromDataTransfer(dt) {
10093
+ if (!dt?.files?.length) return [];
10094
+ return Array.from(dt.files).filter((f2) => f2.type.startsWith("image/"));
10095
+ }
10096
+ function bindAddPageMarkdownFileDrop(ref, markdownTarget) {
10097
+ ref.ondragover(({ event }) => {
10098
+ if (imageFilesFromDataTransfer(event.dataTransfer).length > 0) {
10099
+ event.preventDefault();
10100
+ }
10101
+ });
10102
+ ref.ondrop(({ event }) => {
10103
+ const files = imageFilesFromDataTransfer(event.dataTransfer);
10104
+ if (files.length === 0) return;
10105
+ event.preventDefault();
10106
+ for (const file of files) {
10107
+ void handleAddPageUpload(file, "asset", markdownTarget);
10108
+ }
10109
+ });
10110
+ ref.addEventListener("paste", (ev) => {
10111
+ const files = imageFilesFromPasteEvent(ev.event);
10112
+ if (files.length === 0) return;
10113
+ ev.event.preventDefault();
10114
+ for (const file of files) {
10115
+ void handleAddPageUpload(file, "asset", markdownTarget);
10116
+ }
10117
+ });
10118
+ }
10119
+ bindAddPageMarkdownFileDrop(refs.addPageContentMd, "content");
10120
+ bindAddPageMarkdownFileDrop(refs.addPageDesignMd, "design");
10121
+ refs.addPageReferenceFileInput.onchange(
10122
+ ({ event }) => {
10123
+ const input = event.target;
10124
+ const files = input.files ? Array.from(input.files) : [];
10125
+ for (const file of files) void handleAddPageUpload(file, "reference");
10126
+ input.value = "";
10127
+ }
10128
+ );
10129
+ refs.addPageReferenceDrop.ondragover(
10130
+ ({ event }) => {
10131
+ event.preventDefault();
10132
+ }
10133
+ );
10134
+ refs.addPageReferenceDrop.ondrop(
10135
+ ({ event }) => {
10136
+ event.preventDefault();
10137
+ const files = event.dataTransfer?.files;
10138
+ if (!files?.length) return;
10139
+ for (const file of Array.from(files)) {
10140
+ void handleAddPageUpload(file, "reference");
10141
+ }
10142
+ }
10143
+ );
10144
+ refs.addPageAttachReferenceBtn.onclick(() => {
10145
+ tryJayRefExec(refs.addPageReferenceFileInput, (el) => {
10146
+ el.click();
10147
+ });
10148
+ });
10149
+ pluginsPanel.bindPluginsPanelRefs(refs);
10150
+ briefFillPanel.bindBriefFillRefs(refs);
10151
+ refs.addPageAttachmentsList.onchange(
10152
+ ({ event }) => {
10153
+ const sel = event.target;
10154
+ const id = sel.dataset.attachmentId;
10155
+ const newRole = sel.value;
10156
+ if (!id) return;
10157
+ const chip = addPageAttachments().find((c) => c.id === id);
10158
+ if (!chip || chip.role === newRole) return;
10159
+ const requestId = addPageRequestId();
10160
+ if (!requestId) return;
10161
+ void reclassifyAddPageAsset({
10162
+ requestId,
10163
+ attachmentId: id,
10164
+ newRole
10165
+ }).then((res) => {
10166
+ setAddPageAttachments(
10167
+ (prev) => prev.map(
10168
+ (c) => c.id === id ? {
10169
+ ...c,
10170
+ role: newRole,
10171
+ roleLabel: newRole === "reference" ? "Reference" : "Asset",
10172
+ path: res.path
10173
+ } : c
10174
+ )
10175
+ );
10176
+ });
10177
+ }
10178
+ );
10179
+ refs.addPageAttachmentsList.onclick(
10180
+ ({ event }) => {
10181
+ const btn = event.target.closest(
10182
+ "[data-add-page-remove]"
10183
+ );
10184
+ if (!btn) return;
10185
+ const id = btn.dataset.addPageRemove;
10186
+ if (!id) return;
10187
+ setAddPageAttachments((prev) => prev.filter((c) => c.id !== id));
10188
+ updateDesignHint();
10189
+ }
10190
+ );
10191
+ }
10192
+ return {
10193
+ showAddPageComposer,
10194
+ addPageRoute,
10195
+ addPageContentMd,
10196
+ addPageDesignMd,
10197
+ addPageRouteError,
10198
+ addPageComposerError,
10199
+ addPageUploadHint,
10200
+ addPageAttachmentRows: addPageAttachmentDisplayRows,
10201
+ addPageShowDesignHint,
10202
+ showAddPagePluginsPanel: pluginsPanel.showPluginsPanel,
10203
+ showAddPagePagePluginsZone: pluginsPanel.showPagePluginsZone,
10204
+ addPagePluginPickerOptions: pluginsPanel.addPagePluginPickerOptions,
10205
+ addPagePluginPickerValue: pluginsPanel.addPagePluginPickerValue,
10206
+ showAddPageContractPicker: pluginsPanel.showContractPicker,
10207
+ addPagePickerPluginLabel: pluginsPanel.addPagePickerPluginLabel,
10208
+ addPageContractPickerRows: pluginsPanel.addPageContractPickerRows,
10209
+ addPagePagePluginChips: pluginsPanel.pagePluginChipRows,
10210
+ showAddPageHasPagePluginChips: createMemo(
10211
+ () => pluginsPanel.pagePluginChipRows().length > 0
10212
+ ),
10213
+ showAddPagePagePluginsEmptyHint: pluginsPanel.showPagePluginsEmptyHint,
10214
+ addPageIsDynamicRoute,
10215
+ addPageCreateDisabled,
10216
+ showAddPageRouteError,
10217
+ showAddPageComposerError,
10218
+ showAddPageUploadHint,
10219
+ showAddPageAttachments,
10220
+ addPageRunningHint,
10221
+ showAddPageRunningHint,
10222
+ showBriefFillPopover: briefFillPanel.showBriefFillPopover,
10223
+ briefFillContextNotes: briefFillPanel.briefFillContextNotes,
10224
+ briefFillPopoverError: briefFillPanel.briefFillPopoverError,
10225
+ showBriefFillPopoverError: briefFillPanel.showBriefFillPopoverError,
10226
+ briefFillDisabled: briefFillPanel.briefFillDisabled,
10227
+ briefFillInputDisabled: briefFillPanel.briefFillInputDisabled,
10228
+ briefFillDropzoneClass: briefFillPanel.briefFillDropzoneClass,
10229
+ addPageBriefFillGenerateDisabled: briefFillPanel.generateDisabled,
10230
+ briefFillStatusHint: briefFillPanel.briefFillStatusHint,
10231
+ showBriefFillStatusHint: briefFillPanel.showBriefFillStatusHint,
10232
+ addPageBriefFillThumbRows: briefFillPanel.imageThumbRows,
10233
+ showBriefFillThumbnails: briefFillPanel.showBriefFillThumbnails,
10234
+ showSuggestedRouteBanner: briefFillPanel.showSuggestedRouteBanner,
10235
+ suggestedRouteLabel: briefFillPanel.suggestedRouteLabel,
10236
+ openAddPageComposer,
10237
+ openAddPageComposerFromBrief,
10238
+ refreshCurrentPageBriefAvailability,
10239
+ closeAddPageComposer,
10240
+ submitAddPage,
10241
+ retryAddPage,
10242
+ openPreviewForRoute,
10243
+ bindAddPageRefs
10244
+ };
10245
+ }
10246
+ function annotationPluginBindings(annotation) {
10247
+ if (annotation.pluginBindings?.length) return [...annotation.pluginBindings];
10248
+ if (annotation.pluginBinding) return [annotation.pluginBinding];
10249
+ return [];
10250
+ }
10251
+ function buildBindingChipLabel(binding, annotationId) {
10252
+ const where = binding.scope === "page" ? "anywhere on page" : `marker #${annotationId}`;
10253
+ return `${binding.pluginName} · ${binding.contractName} · ${where}`;
10254
+ }
10255
+ function markerHeadlessPluginOptions(plugins) {
10256
+ const installed = plugins.filter(
10257
+ (p) => p.kind === "headless" && p.installed && p.showInAddPagePicker !== false && p.contracts.length > 0
10258
+ );
10259
+ const options = installed.map((p) => ({
10260
+ rowKey: p.pluginName,
10261
+ value: p.pluginName,
10262
+ label: p.pluginName
10263
+ }));
10264
+ options.push({
10265
+ rowKey: ADD_PAGE_MANAGE_PLUGINS_VALUE,
10266
+ value: ADD_PAGE_MANAGE_PLUGINS_VALUE,
10267
+ label: "Manage plugins…"
10268
+ });
10269
+ return options;
10270
+ }
10271
+ function markerContractPickerRows(plugins, pluginName, bindings) {
10272
+ const entry = plugins.find((p) => p.pluginName === pluginName);
10273
+ if (!entry?.installed) return [];
10274
+ return entry.contracts.map((c) => ({
10275
+ rowKey: `${pluginName}:${c.contractName}`,
10276
+ pluginName,
10277
+ packageName: entry.packageName,
10278
+ contractName: c.contractName,
10279
+ description: c.description ?? "",
10280
+ onPage: bindings.some(
10281
+ (b) => b.pluginName === pluginName && b.contractName === c.contractName
10282
+ ),
10283
+ componentKey: bindings.find(
10284
+ (b) => b.pluginName === pluginName && b.contractName === c.contractName
10285
+ )?.componentKey ?? getDefaultComponentKey(c.contractName)
10286
+ }));
10287
+ }
10288
+ function upsertAnnotationBinding(prev, binding) {
10289
+ const idx = prev.findIndex(
10290
+ (b) => b.pluginName === binding.pluginName && b.contractName === binding.contractName
10291
+ );
10292
+ if (idx === -1) return [...prev, binding];
10293
+ const next = [...prev];
10294
+ next[idx] = binding;
10295
+ return next;
10296
+ }
10297
+ function removeAnnotationBinding(prev, pluginName, contractName) {
10298
+ return prev.filter(
10299
+ (b) => !(b.pluginName === pluginName && b.contractName === contractName)
10300
+ );
10301
+ }
10302
+ function enrichAnnotationBindingUi(annotation, plugins) {
10303
+ const bindings = annotationPluginBindings(annotation);
10304
+ const pickerPlugin = annotation.bindingPickerPlugin ?? "";
10305
+ const open = !!annotation.bindingSectionOpen;
10306
+ return {
10307
+ bindingSectionOpen: open,
10308
+ showBindingControls: open,
10309
+ showMarkerHasBindingChips: bindings.length > 0,
10310
+ showMarkerBindingEmptyHint: bindings.length === 0,
10311
+ markerBindingChips: bindings.map((b) => ({
10312
+ rowKey: `${b.pluginName}:${b.contractName}`,
10313
+ label: buildBindingChipLabel(b, annotation.id),
10314
+ pluginName: b.pluginName,
10315
+ packageName: b.packageName,
10316
+ contractName: b.contractName,
10317
+ scope: b.scope
10318
+ })),
10319
+ markerPluginOptions: markerHeadlessPluginOptions(plugins),
10320
+ markerContractPickerRows: markerContractPickerRows(
10321
+ plugins,
10322
+ pickerPlugin,
10323
+ bindings
10324
+ ),
10325
+ showMarkerContractPicker: pickerPlugin.length > 0,
10326
+ markerPickerPluginLabel: pickerPlugin ? `Components from ${pickerPlugin}` : ""
10327
+ };
10328
+ }
10329
+ function bindingFromSelection(plugins, pluginName, contractName, scope) {
10330
+ const entry = plugins.find((p) => p.pluginName === pluginName);
10331
+ if (!entry?.installed || !contractName) return void 0;
10332
+ return {
10333
+ pluginName,
10334
+ packageName: entry.packageName,
10335
+ contractName,
10336
+ componentKey: getDefaultComponentKey(contractName),
10337
+ scope
10338
+ };
10339
+ }
10340
+ function buildPluginInstallAgentNotes(entry, installError, projectDirHint) {
10341
+ const requires = entry.requires.length > 0 ? entry.requires.join(", ") : "(none — install this plugin only)";
10342
+ const wixPortal = entry.packageName.startsWith("@jay-framework/wix-") ? `
10343
+ - If npm is unreachable, use portal from project root: \`portal:../../../wix/packages/${entry.pluginName}\` (adjust if your wix repo path differs).` : "";
10344
+ return [
10345
+ "Install a Jay Framework plugin for this project. Work in the project directory shown below.",
10346
+ "",
10347
+ `Project directory (Jay stack app root): ${projectDirHint}`,
10348
+ "",
10349
+ "## Plugin to install",
10350
+ `- pluginName: ${entry.pluginName}`,
10351
+ `- packageName: ${entry.packageName}`,
10352
+ `- requires (install these first if missing): ${requires}`,
10353
+ "",
10354
+ "## AIditor automatic install failed",
10355
+ installError,
10356
+ "",
10357
+ "## Required steps (in order)",
10358
+ "1. Determine monorepo layout: if the project is `examples/starter` inside `jay-aiditor`, run Yarn from the **workspace root** (parent with `packageManager: yarn@4.x` in package.json), NOT global Yarn 1.x.",
10359
+ "2. Install dependencies first, then the plugin:",
10360
+ " - From workspace root: `corepack yarn workspace aiditor-starter add <packageName>` (use latest published version, or an explicit `@0.16.x` if needed).",
10361
+ " - Or from project root with Yarn 4: `corepack yarn add <packageName>`.",
10362
+ " - Do NOT use bare `yarn add` if it reports Yarn 1.22 — use `corepack yarn` instead.",
10363
+ "3. Run `npx jay-stack-cli setup <pluginName>` in the project directory.",
10364
+ "4. Run `npx jay-stack-cli agent-kit --plugin <pluginName>` in the project directory.",
10365
+ "5. Confirm `package.json` lists the package and `agent-kit/plugins-index.yaml` includes the plugin contracts.",
10366
+ "",
10367
+ "## Local / monorepo alternative",
10368
+ wixPortal,
10369
+ "- Read `agent-kit/plugin/INSTRUCTIONS.md` for plugin setup conventions.",
10370
+ "",
10371
+ "When finished, summarize what you installed and any config files the user must edit (e.g. config/.wix.yaml)."
10372
+ ].filter((line) => line !== "").join("\n");
10373
+ }
10374
+ const LOCAL_SPEC_PREFIXES = ["portal:", "file:", "workspace:"];
10375
+ function isRegistryInstallSpec(installSpec) {
10376
+ if (!installSpec) return true;
10377
+ return !LOCAL_SPEC_PREFIXES.some((p) => installSpec.startsWith(p));
10378
+ }
10379
+ const VALID_INSTALL_PREFIXES = ["portal:", "file:", "workspace:"];
10380
+ function isValidInstallSpec(spec) {
10381
+ return VALID_INSTALL_PREFIXES.some((p) => spec.startsWith(p));
10382
+ }
10383
+ function initProjectPluginsManager(deps) {
10384
+ const [pluginsList, setPluginsList] = createSignal([]);
10385
+ const [pluginsLoaded, setPluginsLoaded] = createSignal(false);
10386
+ const [installingPlugin, setInstallingPlugin] = createSignal(
10387
+ null
10388
+ );
10389
+ const [installStepLabel, setInstallStepLabel] = createSignal("");
10390
+ const [pluginsPanelError, setPluginsPanelError] = createSignal("");
10391
+ const [failedInstallPlugin, setFailedInstallPlugin] = createSignal(null);
10392
+ const [wixConfigWarning, setWixConfigWarning] = createSignal(false);
10393
+ const [showReloadPreviewBanner, setShowReloadPreviewBanner] = createSignal(false);
10394
+ const [showAddLocalPluginForm, setShowAddLocalPluginForm] = createSignal(false);
10395
+ const [addLocalPluginName, setAddLocalPluginName] = createSignal("");
10396
+ const [addLocalPackageName, setAddLocalPackageName] = createSignal("");
10397
+ const [addLocalPath, setAddLocalPath] = createSignal("");
10398
+ const [pluginUpdateUi, setPluginUpdateUi] = createSignal({});
10399
+ let installAbort;
10400
+ function updateUiForPlugin(pluginName, patch) {
10401
+ setPluginUpdateUi((prev) => ({
10402
+ ...prev,
10403
+ [pluginName]: {
10404
+ checked: false,
10405
+ checking: false,
10406
+ updateAvailable: false,
10407
+ isLocalSource: false,
10408
+ ...prev[pluginName],
10409
+ ...patch
10410
+ }
10411
+ }));
10412
+ }
10413
+ const availableInstallRows = createMemo(() => {
10414
+ const installing = installingPlugin();
10415
+ return pluginsList().filter(
10416
+ (p) => p.showInAddPagePicker && !p.installed && p.kind !== "tooling"
10417
+ ).map((p) => ({
10418
+ rowKey: p.pluginName,
10419
+ pluginName: p.pluginName,
10420
+ packageName: p.packageName,
10421
+ description: p.description,
10422
+ buttonLabel: installing === p.pluginName ? "Installing…" : "Install",
10423
+ buttonDisabled: installing !== null
10424
+ }));
10425
+ });
10426
+ const installedPluginRows = createMemo(() => {
10427
+ const ui = pluginUpdateUi();
10428
+ return pluginsList().filter((p) => p.installed).map((p) => {
10429
+ const updateState = ui[p.pluginName];
10430
+ const isLocal = updateState?.isLocalSource || !isRegistryInstallSpec(p.installSpec);
10431
+ const showUpdateControl = !isLocal;
10432
+ let updateButtonLabel = "Check for update";
10433
+ let showCheckForUpdateBtn = showUpdateControl;
10434
+ let showRunUpdateBtn = false;
10435
+ let updateButtonDisabled = installingPlugin() !== null;
10436
+ if (updateState?.checking) {
10437
+ updateButtonLabel = "Checking…";
10438
+ updateButtonDisabled = true;
10439
+ showCheckForUpdateBtn = true;
10440
+ showRunUpdateBtn = false;
10441
+ } else if (updateState?.checked && updateState.updateAvailable) {
10442
+ updateButtonLabel = `Update to ${updateState.latestVersion ?? "latest"}`;
10443
+ showCheckForUpdateBtn = false;
10444
+ showRunUpdateBtn = true;
10445
+ } else if (updateState?.checked && !updateState.updateAvailable) {
10446
+ updateButtonLabel = "Up to date";
10447
+ updateButtonDisabled = true;
10448
+ showCheckForUpdateBtn = true;
10449
+ showRunUpdateBtn = false;
10450
+ }
10451
+ const versionLabel = updateState?.installedVersion ? `v${updateState.installedVersion}` : p.installedVersion ? `v${p.installedVersion}` : isLocal ? "local" : "";
10452
+ const setupBadge = p.setupStatus === "configured" ? "Configured" : p.setupStatus === "needs-config" ? "Needs configuration" : p.setupStatus === "error" ? "Setup failed" : "";
10453
+ const setupBadgeClass = p.setupStatus === "configured" ? "add-page-setup-ok" : p.setupStatus === "needs-config" ? "add-page-setup-warn" : "add-page-setup-error";
10454
+ const contractSummary = p.kind === "headless" && p.contracts.length > 0 ? p.contracts.map((c) => c.contractName).join(", ") : p.kind === "service-only" ? "No page components (service)" : "";
10455
+ return {
10456
+ rowKey: p.pluginName,
10457
+ pluginName: p.pluginName,
10458
+ packageName: p.packageName,
10459
+ description: p.description,
10460
+ versionLabel,
10461
+ setupBadge,
10462
+ setupBadgeClass,
10463
+ configFile: p.configFiles[0] ?? "",
10464
+ showConfig: p.configFiles.length > 0,
10465
+ contractSummary,
10466
+ showUpdateControl,
10467
+ showCheckForUpdateBtn,
10468
+ showRunUpdateBtn,
10469
+ updateButtonLabel,
10470
+ updateButtonDisabled
10471
+ };
10472
+ });
10473
+ });
10474
+ const showInstallZone = createMemo(() => availableInstallRows().length > 0);
10475
+ const showInstalledZone = createMemo(() => installedPluginRows().length > 0);
10476
+ const showPluginsPanel = createMemo(
10477
+ () => pluginsLoaded() && pluginsList().length > 0
10478
+ );
10479
+ const showPluginsPanelError = createMemo(
10480
+ () => pluginsPanelError().length > 0
10481
+ );
10482
+ const showFixInstallWithAgent = createMemo(
10483
+ () => showPluginsPanelError() && failedInstallPlugin() !== null && deps.fixInstallWithAgent !== void 0
10484
+ );
10485
+ const fixInstallPluginName = createMemo(() => failedInstallPlugin() ?? "");
10486
+ const showInstallProgress = createMemo(
10487
+ () => installingPlugin() !== null && installStepLabel().length > 0
10488
+ );
10489
+ const installInProgress = createMemo(() => installingPlugin() !== null);
10490
+ const showWixConfigWarning = createMemo(() => wixConfigWarning());
10491
+ const showReloadPreview = createMemo(() => showReloadPreviewBanner());
10492
+ async function refreshPluginsList(refreshSetup = false) {
10493
+ try {
10494
+ const result = await listJayPlugins(refreshSetup);
10495
+ setPluginsList(result.plugins);
10496
+ setPluginsLoaded(true);
10497
+ const wixClient = result.plugins.find(
10498
+ (p) => p.pluginName === "wix-server-client"
10499
+ );
10500
+ setWixConfigWarning(
10501
+ !!wixClient?.installed && wixClient.setupStatus === "needs-config"
10502
+ );
10503
+ } catch (err) {
10504
+ setPluginsPanelError(err instanceof Error ? err.message : String(err));
10505
+ }
10506
+ }
10507
+ async function runCheckUpdate(pluginName) {
10508
+ updateUiForPlugin(pluginName, { checking: true });
10509
+ setPluginsPanelError("");
10510
+ try {
10511
+ const result = await checkPluginUpdate(pluginName);
10512
+ updateUiForPlugin(pluginName, {
10513
+ checking: false,
10514
+ checked: true,
10515
+ updateAvailable: result.updateAvailable,
10516
+ installedVersion: result.installedVersion ?? void 0,
10517
+ latestVersion: result.latestVersion ?? void 0,
10518
+ isLocalSource: result.isLocalSource,
10519
+ message: result.message
10520
+ });
10521
+ if (result.message && !result.updateAvailable) {
10522
+ setPluginsPanelError("");
10523
+ }
10524
+ } catch (err) {
10525
+ updateUiForPlugin(pluginName, { checking: false, checked: false });
10526
+ setPluginsPanelError(err instanceof Error ? err.message : String(err));
10527
+ }
10528
+ }
10529
+ async function runInstall(pluginName, installSpec, options) {
10530
+ setInstallingPlugin(pluginName);
10531
+ setInstallStepLabel(
10532
+ options?.upgrade ? "Starting update…" : "Starting install…"
10533
+ );
10534
+ setPluginsPanelError("");
10535
+ setFailedInstallPlugin(null);
10536
+ installAbort?.();
10537
+ installAbort = ensureProjectPluginStream(
10538
+ { pluginName, installSpec, upgrade: options?.upgrade },
10539
+ {
10540
+ onChunk: (chunk) => {
10541
+ if (chunk.type === "progress" && chunk.message) {
10542
+ const step = chunk.step === "package" ? "Package" : chunk.step === "requires" ? "Dependencies" : chunk.step === "setup" ? "Setup" : chunk.step === "agent-kit" ? "Contracts" : "Installing";
10543
+ setInstallStepLabel(`${step}: ${chunk.message}`);
10544
+ }
10545
+ if (chunk.type === "error") {
10546
+ setFailedInstallPlugin(pluginName);
10547
+ setPluginsPanelError(
10548
+ chunk.installError ?? chunk.message ?? "Install failed."
10549
+ );
10550
+ setInstallingPlugin(null);
10551
+ setInstallStepLabel("");
10552
+ }
10553
+ if (chunk.type === "complete") {
10554
+ setInstallingPlugin(null);
10555
+ setInstallStepLabel("");
10556
+ updateUiForPlugin(pluginName, {
10557
+ checked: false,
10558
+ checking: false,
10559
+ updateAvailable: false
10560
+ });
10561
+ void refreshPluginsList(true).then(() => deps.loadProjectInfo());
10562
+ setShowReloadPreviewBanner(true);
10563
+ }
10564
+ },
10565
+ onEnd: () => {
10566
+ setInstallingPlugin(null);
10567
+ },
10568
+ onError: (err) => {
10569
+ setFailedInstallPlugin(pluginName);
10570
+ setPluginsPanelError(err.message);
10571
+ setInstallingPlugin(null);
10572
+ setInstallStepLabel("");
10573
+ }
10574
+ }
10575
+ );
10576
+ }
10577
+ function resetManager() {
10578
+ setPluginsList([]);
10579
+ setPluginsLoaded(false);
10580
+ setInstallingPlugin(null);
10581
+ setInstallStepLabel("");
10582
+ setPluginsPanelError("");
10583
+ setFailedInstallPlugin(null);
10584
+ setWixConfigWarning(false);
10585
+ setShowReloadPreviewBanner(false);
10586
+ setShowAddLocalPluginForm(false);
10587
+ setAddLocalPluginName("");
10588
+ setAddLocalPackageName("");
10589
+ setAddLocalPath("");
10590
+ setPluginUpdateUi({});
10591
+ installAbort?.();
10592
+ installAbort = void 0;
10593
+ }
10594
+ function bindProjectPluginsRefs(refs) {
10595
+ refs.projectPluginsPanel.onclick(
10596
+ ({ event }) => {
10597
+ const target = event.target;
10598
+ const installBtn = target.closest(
10599
+ "[data-project-install-plugin]"
10600
+ );
10601
+ if (installBtn) {
10602
+ const name = installBtn.dataset.plugin;
10603
+ if (name) void runInstall(name, installBtn.dataset.installSpec);
10604
+ return;
10605
+ }
10606
+ const checkUpdateBtn = target.closest(
10607
+ "[data-project-check-update]"
10608
+ );
10609
+ if (checkUpdateBtn?.dataset.plugin) {
10610
+ void runCheckUpdate(checkUpdateBtn.dataset.plugin);
10611
+ return;
10612
+ }
10613
+ const runUpdateBtn = target.closest(
10614
+ "[data-project-run-update]"
10615
+ );
10616
+ if (runUpdateBtn?.dataset.plugin) {
10617
+ void runInstall(runUpdateBtn.dataset.plugin, void 0, {
10618
+ upgrade: true
10619
+ });
10620
+ return;
10621
+ }
10622
+ const rerunBtn = target.closest(
10623
+ "[data-project-rerun-setup]"
10624
+ );
10625
+ if (rerunBtn?.dataset.plugin) {
10626
+ void rerunPluginSetup(rerunBtn.dataset.plugin).then(
10627
+ () => refreshPluginsList(true)
10628
+ );
10629
+ return;
10630
+ }
10631
+ const openConfigBtn = target.closest(
10632
+ "[data-project-open-config]"
10633
+ );
10634
+ if (openConfigBtn?.dataset.config) {
10635
+ void readFileAction({ filePath: openConfigBtn.dataset.config }).then(
10636
+ (res) => {
10637
+ if (res.type === "text") {
10638
+ window.alert(
10639
+ `${openConfigBtn.dataset.config}
10640
+
10641
+ (Edit in your IDE — secrets are not edited in AIditor.)
10642
+
10643
+ ${res.data.slice(0, 2e3)}`
10644
+ );
10645
+ }
10646
+ }
10647
+ );
10648
+ return;
10649
+ }
10650
+ const fixAgentBtn = target.closest(
10651
+ "[data-project-fix-install-agent]"
10652
+ );
10653
+ if (fixAgentBtn?.dataset.plugin) {
10654
+ const name = fixAgentBtn.dataset.plugin;
10655
+ const err = pluginsPanelError();
10656
+ if (name && err && deps.fixInstallWithAgent) {
10657
+ deps.fixInstallWithAgent(name, err);
10658
+ }
10659
+ return;
10660
+ }
10661
+ if (target.closest("[data-project-show-local-plugin]")) {
10662
+ setShowAddLocalPluginForm(true);
10663
+ return;
10664
+ }
10665
+ if (target.closest("[data-project-cancel-local-plugin]")) {
10666
+ setShowAddLocalPluginForm(false);
10667
+ return;
10668
+ }
10669
+ if (target.closest("[data-project-save-local-plugin]")) {
10670
+ void (async () => {
10671
+ const pluginName = addLocalPluginName().trim();
10672
+ const packageName = addLocalPackageName().trim();
10673
+ let installSpec = addLocalPath().trim();
10674
+ if (!pluginName || !packageName || !installSpec) {
10675
+ setPluginsPanelError("Fill plugin name, package, and path.");
10676
+ return;
10677
+ }
10678
+ if (!isValidInstallSpec(installSpec)) {
10679
+ installSpec = `portal:${installSpec.replace(/^\.\//, "")}`;
10680
+ }
10681
+ if (!isValidInstallSpec(installSpec)) {
10682
+ setPluginsPanelError(
10683
+ "Path must be portal:, file:, or workspace: (or a relative path for portal:)."
10684
+ );
10685
+ return;
10686
+ }
10687
+ try {
10688
+ await writePluginSource({
10689
+ pluginName,
10690
+ packageName,
10691
+ installSpec,
10692
+ label: "Local"
10693
+ });
10694
+ setShowAddLocalPluginForm(false);
10695
+ setAddLocalPluginName("");
10696
+ setAddLocalPackageName("");
10697
+ setAddLocalPath("");
10698
+ setPluginsPanelError("");
10699
+ await refreshPluginsList();
10700
+ } catch (err) {
10701
+ setPluginsPanelError(
10702
+ err instanceof Error ? err.message : String(err)
10703
+ );
10704
+ }
10705
+ })();
10706
+ }
10707
+ }
10708
+ );
10709
+ }
10710
+ return {
10711
+ pluginsList,
10712
+ pluginsLoaded,
10713
+ refreshPluginsList,
10714
+ resetManager,
10715
+ bindProjectPluginsRefs,
10716
+ availableInstallRows,
10717
+ installedPluginRows,
10718
+ showInstallZone,
10719
+ showInstalledZone,
10720
+ showPluginsPanel,
10721
+ showPluginsPanelError,
10722
+ showFixInstallWithAgent,
10723
+ fixInstallPluginName,
10724
+ pluginsPanelError,
10725
+ showInstallProgress,
10726
+ installStepLabel,
10727
+ showWixConfigWarning,
10728
+ showReloadPreview,
10729
+ installInProgress,
10730
+ showAddLocalPluginForm,
10731
+ addLocalPluginName,
10732
+ addLocalPackageName,
10733
+ addLocalPath,
10734
+ setAddLocalPluginName,
10735
+ setAddLocalPackageName,
10736
+ setAddLocalPath,
10737
+ runInstall
10738
+ };
10739
+ }
10740
+ setActionCallerOptions({ timeout: 12e4 });
10741
+ const MAX_ATTACHMENTS_PER_PIN = 10;
10742
+ const MAX_ATTACHMENT_BYTES_PER_PIN = 5 * 1024 * 1024;
10743
+ function mbLabel(bytes) {
10744
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
10745
+ }
10746
+ function newLocalAttachmentId() {
10747
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `f_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
10748
+ }
10749
+ function newAnnotationId() {
10750
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `ann_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
10751
+ }
10752
+ function renumberAnnotations(list) {
10753
+ return list.map((a2, i) => ({ ...a2, id: String(i + 1) }));
10754
+ }
10755
+ function serializeGeometry(a2) {
10756
+ if (a2.kind === "point")
10757
+ return { kind: "point", x: a2.x, y: a2.y };
10758
+ if (a2.kind === "area")
10759
+ return { kind: "area", x: a2.x, y: a2.y, w: a2.w, h: a2.h };
10760
+ return { kind: "arrow", x1: a2.x1, y1: a2.y1, x2: a2.x2, y2: a2.y2 };
10761
+ }
10762
+ function toNotesPayload(annotations, breakpoint) {
10763
+ return {
10764
+ version: AIDITOR_NOTES_VERSION,
10765
+ breakpoint,
10766
+ annotations: annotations.map((a2) => ({
10767
+ id: a2.id,
10768
+ mode: a2.kind,
10769
+ instruction: a2.instruction.trim(),
10770
+ geometry: serializeGeometry(a2),
10771
+ ...a2.pluginBindings?.length ? { pluginBindings: a2.pluginBindings } : {}
10772
+ }))
10773
+ };
10774
+ }
10775
+ function toVideoNotesPayload(annotations, videoMeta, notesContext) {
10776
+ const baseOrigin = typeof window !== "undefined" ? window.location.origin : JAY_DEV_URL_DEFAULT;
10777
+ const fallback = fallbackPreviewContextFromBar(notesContext.fallbackRenderedUrl, notesContext.fallbackRoute, baseOrigin);
10778
+ const navLog = notesContext.previewNavLog.length > 0 ? notesContext.previewNavLog : void 0;
10779
+ const payload = {
10780
+ version: 2,
10781
+ taskKind: "video",
10782
+ videoMeta,
10783
+ previewNavLog: navLog,
10784
+ breakpoint: notesContext.breakpoint,
10785
+ annotations: annotations.map((a2) => {
10786
+ const ctx = resolvePreviewContextAtTime(a2.timeSec, navLog, fallback);
10787
+ return {
10788
+ id: a2.id,
10789
+ mode: a2.kind,
10790
+ instruction: a2.instruction.trim(),
10791
+ geometry: serializeGeometry(a2),
10792
+ timeSec: a2.timeSec,
10793
+ previewUrlAtTime: ctx.href,
10794
+ pagePathAtTime: ctx.locationPath,
10795
+ ...a2.pluginBindings?.length ? { pluginBindings: a2.pluginBindings } : {}
10796
+ };
10797
+ })
10798
+ };
10799
+ return JSON.stringify(payload);
10800
+ }
10801
+ function toSerializedVideoForPins(a2) {
10802
+ return {
10803
+ id: a2.id,
10804
+ mode: a2.kind,
10805
+ instruction: a2.instruction,
10806
+ geometry: serializeGeometry(a2),
10807
+ timeSec: a2.timeSec
10808
+ };
10809
+ }
10810
+ function kindLabel(kind) {
10811
+ if (kind === "point")
10812
+ return "Point";
10813
+ if (kind === "area")
10814
+ return "Area";
10815
+ return "Arrow";
10816
+ }
10817
+ const VIDEO_ANNOTATION_TIME_TOLERANCE_SEC = 0.15;
10818
+ function isVideoAnnotationAtPlayhead(playheadSec, annotationTimeSec) {
10819
+ return Math.abs(playheadSec - annotationTimeSec) <= VIDEO_ANNOTATION_TIME_TOLERANCE_SEC;
10820
+ }
10821
+ function bubbleAnchorNorm(a2) {
10822
+ if (a2.kind === "point")
10823
+ return { nx: a2.x, ny: a2.y };
10824
+ if (a2.kind === "area") {
10825
+ return { nx: Math.min(1, a2.x + a2.w), ny: Math.min(1, a2.y + a2.h) };
10826
+ }
10827
+ return { nx: a2.x2, ny: a2.y2 };
10828
+ }
10829
+ const POPOVER_FLIP_X_PCT = 56;
10830
+ const POPOVER_FLIP_Y_PCT = 52;
10831
+ function popoverFlipFromPct(leftPct, topPct) {
10832
+ return {
10833
+ flipX: leftPct > POPOVER_FLIP_X_PCT,
10834
+ flipY: topPct > POPOVER_FLIP_Y_PCT
10835
+ };
10836
+ }
10837
+ const JAY_DEV_URL_DEFAULT = typeof window !== "undefined" ? window.location.origin : "http://localhost:3000";
10838
+ const paramsCache = /* @__PURE__ */ new Map();
10839
+ function dedupePastedFiles(files) {
10840
+ const seen = /* @__PURE__ */ new Set();
10841
+ const out = [];
10842
+ for (const f2 of files) {
10843
+ const k = `${f2.name}\0${f2.size}\0${f2.lastModified}\0${f2.type}`;
10844
+ if (seen.has(k))
10845
+ continue;
10846
+ seen.add(k);
10847
+ out.push(f2);
10848
+ }
10849
+ return out;
10850
+ }
10851
+ function filesFromClipboardData(cd) {
10852
+ if (!cd)
10853
+ return [];
10854
+ const accum = [];
10855
+ for (let i = 0; i < cd.files.length; i++) {
10856
+ const f2 = cd.files.item(i);
10857
+ if (f2)
10858
+ accum.push(f2);
10859
+ }
10860
+ if (cd.items) {
10861
+ for (let i = 0; i < cd.items.length; i++) {
10862
+ const it = cd.items[i];
10863
+ if (it?.kind === "file") {
10864
+ const f2 = it.getAsFile();
10865
+ if (f2)
10866
+ accum.push(f2);
10867
+ }
10868
+ }
10869
+ }
10870
+ return dedupePastedFiles(accum);
10871
+ }
10872
+ function clipboardItemsArray(cd) {
10873
+ const items = cd?.items;
10874
+ if (!items || items.length === 0)
10875
+ return [];
10876
+ return Array.from({ length: items.length }, (_, i) => items[i]).filter(Boolean);
10877
+ }
10878
+ function dataUrlToImageFileSync(dataUrl, index) {
10879
+ const comma = dataUrl.indexOf(",");
10880
+ if (comma < 0)
10881
+ return null;
10882
+ const header = dataUrl.slice(0, comma);
10883
+ const dataPart = dataUrl.slice(comma + 1);
10884
+ const mimeMatch = header.match(/^data:([^;,]+)/i);
10885
+ const mime = mimeMatch?.[1]?.trim() ?? "";
10886
+ if (!mime.startsWith("image/"))
10887
+ return null;
10888
+ const isBase64 = /;base64/i.test(header);
10889
+ let bytes;
10890
+ if (isBase64) {
10891
+ try {
10892
+ const bin = atob(dataPart.replace(/\s/g, ""));
10893
+ bytes = new Uint8Array(bin.length);
10894
+ for (let i = 0; i < bin.length; i++)
10895
+ bytes[i] = bin.charCodeAt(i);
10896
+ } catch {
10897
+ return null;
10898
+ }
10899
+ } else {
10900
+ try {
10901
+ bytes = new TextEncoder().encode(decodeURIComponent(dataPart.replace(/\+/g, "%20")));
10902
+ } catch {
10903
+ return null;
10904
+ }
10905
+ }
10906
+ const extPart = mime.split("/")[1] || "png";
10907
+ const ext = extPart.replace(/\+xml$/, "").split("+")[0] || "png";
10908
+ const buf = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
10909
+ return new File([buf], `paste-${index}.${ext}`, { type: mime });
10910
+ }
10911
+ function filesFromPastedHtmlDataUrls(html) {
10912
+ if (!html)
10913
+ return [];
10914
+ let doc;
10915
+ try {
10916
+ doc = new DOMParser().parseFromString(html, "text/html");
10917
+ } catch {
10918
+ return [];
10919
+ }
10920
+ const out = [];
10921
+ for (const img of Array.from(doc.querySelectorAll("img[src]"))) {
10922
+ const src = img.getAttribute("src");
10923
+ if (!src?.startsWith("data:image"))
10924
+ continue;
10925
+ const f2 = dataUrlToImageFileSync(src, out.length);
10926
+ if (f2)
10927
+ out.push(f2);
10928
+ }
10929
+ return dedupePastedFiles(out);
10930
+ }
10931
+ function filesFromPlainDataImageUrl(text) {
10932
+ const t = text.trim();
10933
+ if (!t.startsWith("data:image/"))
10934
+ return [];
10935
+ const f2 = dataUrlToImageFileSync(t, 0);
10936
+ return f2 ? [f2] : [];
10937
+ }
10938
+ async function imageFilesFromNavigatorClipboard() {
10939
+ if (!navigator.clipboard?.read)
10940
+ return [];
10941
+ let clipItems;
10942
+ try {
10943
+ clipItems = await navigator.clipboard.read();
10944
+ } catch {
10945
+ return [];
10946
+ }
10947
+ const out = [];
10948
+ let idx = 0;
10949
+ for (const ci of clipItems) {
10950
+ for (const type of ci.types) {
10951
+ if (!type.startsWith("image/"))
10952
+ continue;
10953
+ try {
10954
+ const blob = await ci.getType(type);
10955
+ if (!(blob instanceof Blob) || blob.size === 0)
10956
+ continue;
10957
+ const ext = type.split("/")[1]?.split("+")[0] || "png";
10958
+ out.push(new File([blob], `paste-${idx++}.${ext}`, { type }));
10959
+ } catch {
10960
+ }
10961
+ }
10962
+ }
10963
+ return dedupePastedFiles(out);
10964
+ }
10965
+ let aiditorBoundDocumentPaste = null;
10966
+ function bindAiditorDocumentPasteCapture(handler) {
10967
+ if (typeof document === "undefined")
10968
+ return;
10969
+ if (aiditorBoundDocumentPaste) {
10970
+ document.removeEventListener("paste", aiditorBoundDocumentPaste, true);
10971
+ }
10972
+ aiditorBoundDocumentPaste = handler;
10973
+ document.addEventListener("paste", handler, true);
10974
+ }
10975
+ function aiditorConstructor(_props, refs) {
10976
+ const [projectDir, setProjectDir] = createSignal("");
10977
+ const [jayDevUrl, setJayDevUrl] = createSignal(JAY_DEV_URL_DEFAULT);
10978
+ const [isBootstrapping, setIsBootstrapping] = createSignal(true);
10979
+ const [showBootstrapError, setShowBootstrapError] = createSignal(false);
10980
+ const [bootstrapErrorText, setBootstrapErrorText] = createSignal("");
10981
+ const [activeTaskId, setActiveTaskId] = createSignal(null);
10982
+ const [chunks, setChunks] = createSignal([]);
10983
+ const [bottomPanelCollapsed, setBottomPanelCollapsed] = createSignal(true);
10984
+ const [agentChatMessage, setAgentChatMessage] = createSignal("");
10985
+ const [addPageResultBanner, setAddPageResultBanner] = createSignal(null);
10986
+ const [isRunning, setIsRunning] = createSignal(false);
10987
+ const [showFilePreview, setShowFilePreview] = createSignal(false);
8927
10988
  const [filePreviewPath, setFilePreviewPath] = createSignal("");
8928
10989
  const [filePreviewContent, setFilePreviewContent] = createSignal("");
8929
10990
  const [filePreviewIsImage, setFilePreviewIsImage] = createSignal(false);
@@ -8935,6 +10996,7 @@ function aiditorConstructor(_props, refs) {
8935
10996
  const [httpBaseUrl, setHttpBaseUrl] = createSignal("");
8936
10997
  const [isPreviewMode, setIsPreviewMode] = createSignal(false);
8937
10998
  const [selectedPageUrl, setSelectedPageUrl] = createSignal(null);
10999
+ const [currentPageHasBrief, setCurrentPageHasBrief] = createSignal(false);
8938
11000
  const [previewSrc, setPreviewSrc] = createSignal("");
8939
11001
  const [previewUrlBar, setPreviewUrlBar] = createSignal("");
8940
11002
  const [previewLoading, setPreviewLoading] = createSignal(false);
@@ -8956,6 +11018,21 @@ function aiditorConstructor(_props, refs) {
8956
11018
  const [areaDraft, setAreaDraft] = createSignal(null);
8957
11019
  const [arrowPending, setArrowPending] = createSignal(null);
8958
11020
  const [visualSubmitError, setVisualSubmitError] = createSignal("");
11021
+ const [showProjectSettingsModal, setShowProjectSettingsModal] = createSignal(false);
11022
+ let runPluginInstallAgent;
11023
+ const projectPlugins = initProjectPluginsManager({
11024
+ loadProjectInfo: () => loadProjectInfo(),
11025
+ fixInstallWithAgent: (pluginName, installError) => {
11026
+ runPluginInstallAgent?.(pluginName, installError);
11027
+ }
11028
+ });
11029
+ function openProjectSettings() {
11030
+ setShowProjectSettingsModal(true);
11031
+ void projectPlugins.refreshPluginsList();
11032
+ }
11033
+ function closeProjectSettings() {
11034
+ setShowProjectSettingsModal(false);
11035
+ }
8959
11036
  const [showVideoReviewModal, setShowVideoReviewModal] = createSignal(false);
8960
11037
  const [videoReviewObjectUrl, setVideoReviewObjectUrl] = createSignal("");
8961
11038
  const [videoReviewPreparing, setVideoReviewPreparing] = createSignal(false);
@@ -9072,6 +11149,14 @@ function aiditorConstructor(_props, refs) {
9072
11149
  });
9073
11150
  const selectedRouteSelectValue = createMemo(() => selectedPageUrl() ?? "");
9074
11151
  const hasPages = createMemo(() => projectPages().length > 0);
11152
+ const selectedPageFilePath = createMemo(() => {
11153
+ const url = selectedPageUrl();
11154
+ if (!url)
11155
+ return "";
11156
+ const page2 = projectPages().find((p) => p.url === url) ?? projectPages().find((p) => p.url === url.replace(/\/+$/, "") || "/");
11157
+ return page2?.filePath ?? "";
11158
+ });
11159
+ const showAddPageFromBriefBtn = createMemo(() => currentPageHasBrief() && selectedPageFilePath().length > 0);
9075
11160
  const showPathSelect = createMemo(() => isPreviewMode() && !previewLoading() && !previewError() && previewPathOptions().length > 0);
9076
11161
  const showPreviewIframe = createMemo(() => isPreviewMode() && !previewLoading() && !!previewSrc() && !previewError());
9077
11162
  const showVisualToolbar = createMemo(() => isPreviewMode() && !previewLoading() && !previewError() && !!previewSrc());
@@ -9224,6 +11309,7 @@ function aiditorConstructor(_props, refs) {
9224
11309
  });
9225
11310
  const visualAnnotationRows = createMemo(() => {
9226
11311
  const runDisabled = visualRunDisabled();
11312
+ const plugins = projectPlugins.pluginsList();
9227
11313
  return visualAnnotations().map((a2) => {
9228
11314
  const { nx, ny } = bubbleAnchorNorm(a2);
9229
11315
  const leftPct = nx * 100;
@@ -9244,7 +11330,8 @@ function aiditorConstructor(_props, refs) {
9244
11330
  topPct,
9245
11331
  popoverFlipX: flipX,
9246
11332
  popoverFlipY: flipY,
9247
- attachmentChips
11333
+ attachmentChips,
11334
+ ...enrichAnnotationBindingUi(a2, plugins)
9248
11335
  };
9249
11336
  });
9250
11337
  });
@@ -9375,6 +11462,7 @@ function aiditorConstructor(_props, refs) {
9375
11462
  const t = videoReviewCurrentTimeSec();
9376
11463
  const list = fullList.filter((a2) => isVideoAnnotationAtPlayhead(t, a2.timeSec));
9377
11464
  const runDisabled = fullList.length > 0 && (isRunning() || fullList.some((a2) => a2.instruction.trim().length === 0));
11465
+ const plugins = projectPlugins.pluginsList();
9378
11466
  return list.map((a2) => {
9379
11467
  const { nx, ny } = bubbleAnchorNorm(a2);
9380
11468
  const leftPct = nx * 100;
@@ -9396,7 +11484,8 @@ function aiditorConstructor(_props, refs) {
9396
11484
  topPct,
9397
11485
  popoverFlipX: flipX,
9398
11486
  popoverFlipY: flipY,
9399
- attachmentChips
11487
+ attachmentChips,
11488
+ ...enrichAnnotationBindingUi(a2, plugins)
9400
11489
  };
9401
11490
  });
9402
11491
  });
@@ -9521,30 +11610,34 @@ function aiditorConstructor(_props, refs) {
9521
11610
  const snapshotAreaDraftTopPct = createMemo(() => (snapshotAreaDraftNorm()?.y ?? 0) * 100);
9522
11611
  const snapshotAreaDraftWidthPct = createMemo(() => (snapshotAreaDraftNorm()?.w ?? 0) * 100);
9523
11612
  const snapshotAreaDraftHeightPct = createMemo(() => (snapshotAreaDraftNorm()?.h ?? 0) * 100);
9524
- const snapshotAnnotationItems = createMemo(() => snapshotAnnotations().map((a2) => {
9525
- const { nx, ny } = bubbleAnchorNorm(a2);
9526
- const leftPct = nx * 100;
9527
- const topPct = ny * 100;
9528
- const { flipX, flipY } = popoverFlipFromPct(leftPct, topPct);
9529
- const attachmentChips = a2.attachments.map((at) => ({
9530
- key: at.id,
9531
- name: at.file.name,
9532
- thumbUrl: at.previewUrl && at.file.type.startsWith("image/") ? at.previewUrl : "",
9533
- annotationId: a2.id
9534
- }));
9535
- return {
9536
- id: a2.id,
9537
- indexLabel: a2.id,
9538
- kindLabel: `${a2.id} — ${kindLabel(a2.kind)}`,
9539
- instruction: a2.instruction,
9540
- kind: a2.kind,
9541
- leftPct,
9542
- topPct,
9543
- popoverFlipX: flipX,
9544
- popoverFlipY: flipY,
9545
- attachmentChips
9546
- };
9547
- }));
11613
+ const snapshotAnnotationItems = createMemo(() => {
11614
+ const plugins = projectPlugins.pluginsList();
11615
+ return snapshotAnnotations().map((a2) => {
11616
+ const { nx, ny } = bubbleAnchorNorm(a2);
11617
+ const leftPct = nx * 100;
11618
+ const topPct = ny * 100;
11619
+ const { flipX, flipY } = popoverFlipFromPct(leftPct, topPct);
11620
+ const attachmentChips = a2.attachments.map((at) => ({
11621
+ key: at.id,
11622
+ name: at.file.name,
11623
+ thumbUrl: at.previewUrl && at.file.type.startsWith("image/") ? at.previewUrl : "",
11624
+ annotationId: a2.id
11625
+ }));
11626
+ return {
11627
+ id: a2.id,
11628
+ indexLabel: a2.id,
11629
+ kindLabel: `${a2.id} — ${kindLabel(a2.kind)}`,
11630
+ instruction: a2.instruction,
11631
+ kind: a2.kind,
11632
+ leftPct,
11633
+ topPct,
11634
+ popoverFlipX: flipX,
11635
+ popoverFlipY: flipY,
11636
+ attachmentChips,
11637
+ ...enrichAnnotationBindingUi(a2, plugins)
11638
+ };
11639
+ });
11640
+ });
9548
11641
  const showSnapshotAnnotationsPanel = createMemo(() => snapshotAnnotations().length > 0);
9549
11642
  const showSnapshotSubmitError = createMemo(() => snapshotSubmitError().trim().length > 0);
9550
11643
  const showSnapshotPointMarkers = createMemo(() => snapshotPointDisplayItems().length > 0);
@@ -9558,6 +11651,10 @@ function aiditorConstructor(_props, refs) {
9558
11651
  const bottomPanelClass = createMemo(() => bottomPanelCollapsed() ? "bottom-panel bottom-panel-collapsed" : "bottom-panel");
9559
11652
  const bottomPanelToggleGlyph = createMemo(() => bottomPanelCollapsed() ? "▲" : "▼");
9560
11653
  const showBottomPanelRunning = createMemo(() => isRunning());
11654
+ const showAgentChatInput = createMemo(() => !bottomPanelCollapsed());
11655
+ const agentChatInputDisabled = createMemo(() => isRunning());
11656
+ const agentChatSendDisabled = createMemo(() => isRunning() || agentChatMessage().trim().length === 0);
11657
+ const agentChatPlaceholder = createMemo(() => isRunning() ? "Agent is working…" : "Type a message…");
9561
11658
  let stopStream = null;
9562
11659
  let previewParamsGeneration = 0;
9563
11660
  let attachTargetPinId = null;
@@ -9766,7 +11863,7 @@ function aiditorConstructor(_props, refs) {
9766
11863
  }
9767
11864
  function focusLastSnapshotAnnotationTextarea() {
9768
11865
  requestAnimationFrame(() => {
9769
- tryJayRefExec(refs.snapshotBackdrop, (backdrop) => {
11866
+ tryJayRefExec2(refs.snapshotBackdrop, (backdrop) => {
9770
11867
  const textareas = backdrop.querySelectorAll("textarea.visual-annotation-instruction");
9771
11868
  const last = textareas[textareas.length - 1];
9772
11869
  if (last)
@@ -9787,6 +11884,85 @@ function aiditorConstructor(_props, refs) {
9787
11884
  setVisualAnnotations((prev) => prev.map((a2) => a2.id === id ? { ...a2, instruction } : a2));
9788
11885
  setVisualSubmitError("");
9789
11886
  }
11887
+ createEffect(() => {
11888
+ if (visualAnnotations().length > 0 || snapshotAnnotations().length > 0 || videoSessionAnnotations().length > 0 || showProjectSettingsModal()) {
11889
+ void projectPlugins.refreshPluginsList();
11890
+ }
11891
+ });
11892
+ function updateVisualAnnotation(id, updater) {
11893
+ setVisualAnnotations((prev) => prev.map((a2) => a2.id === id ? updater(a2) : a2));
11894
+ }
11895
+ function updateSnapshotAnnotation(id, updater) {
11896
+ setSnapshotAnnotations((prev) => prev.map((a2) => a2.id === id ? updater(a2) : a2));
11897
+ }
11898
+ function updateVideoAnnotation(id, updater) {
11899
+ setVideoSessionAnnotations((prev) => prev.map((a2) => a2.id === id ? updater(a2) : a2));
11900
+ }
11901
+ function updateVideoAsVisual(id, updater) {
11902
+ updateVideoAnnotation(id, (a2) => updater(a2));
11903
+ }
11904
+ function toggleBindingSection(update, id) {
11905
+ update(id, (a2) => ({
11906
+ ...a2,
11907
+ bindingSectionOpen: !a2.bindingSectionOpen
11908
+ }));
11909
+ }
11910
+ function setBindingPickerPlugin(update, id, pluginName) {
11911
+ if (pluginName === ADD_PAGE_MANAGE_PLUGINS_VALUE || pluginName === "__install__") {
11912
+ openProjectSettings();
11913
+ return;
11914
+ }
11915
+ update(id, (a2) => ({
11916
+ ...a2,
11917
+ bindingSectionOpen: true,
11918
+ bindingPickerPlugin: pluginName
11919
+ }));
11920
+ }
11921
+ function addBindingToAnnotation(update, id, pluginName, _packageName, contractName, componentKey) {
11922
+ const plugins = projectPlugins.pluginsList();
11923
+ update(id, (a2) => {
11924
+ const binding = bindingFromSelection(plugins, pluginName, contractName, "marker");
11925
+ if (!binding)
11926
+ return a2;
11927
+ if (componentKey)
11928
+ binding.componentKey = componentKey;
11929
+ return {
11930
+ ...a2,
11931
+ bindingSectionOpen: true,
11932
+ pluginBindings: upsertAnnotationBinding(a2.pluginBindings ?? [], binding)
11933
+ };
11934
+ });
11935
+ }
11936
+ function removeBindingFromAnnotation(update, id, pluginName, contractName) {
11937
+ update(id, (a2) => ({
11938
+ ...a2,
11939
+ pluginBindings: removeAnnotationBinding(a2.pluginBindings ?? [], pluginName, contractName)
11940
+ }));
11941
+ }
11942
+ function handleBindingUiEvent(target, id, update) {
11943
+ if (target.classList.contains("annotation-binding-toggle")) {
11944
+ toggleBindingSection(update, id);
11945
+ return;
11946
+ }
11947
+ if (target.closest("[data-marker-manage-plugins]")) {
11948
+ openProjectSettings();
11949
+ return;
11950
+ }
11951
+ if (target.classList.contains("annotation-binding-plugin")) {
11952
+ setBindingPickerPlugin(update, id, target.value);
11953
+ return;
11954
+ }
11955
+ const addBtn = target.closest("[data-marker-add-contract]");
11956
+ if (addBtn?.dataset.plugin && addBtn.dataset.contract && addBtn.dataset.package) {
11957
+ addBindingToAnnotation(update, id, addBtn.dataset.plugin, addBtn.dataset.package, addBtn.dataset.contract, addBtn.dataset.key ?? "");
11958
+ return;
11959
+ }
11960
+ const removeChip = target.closest("[data-marker-remove-binding]");
11961
+ if (removeChip?.dataset.plugin && removeChip.dataset.contract) {
11962
+ removeBindingFromAnnotation(update, id, removeChip.dataset.plugin, removeChip.dataset.contract);
11963
+ return;
11964
+ }
11965
+ }
9790
11966
  function setRecordingDraftInstruction(instruction) {
9791
11967
  setRecordingDraftAnnotation((prev) => {
9792
11968
  if (!prev)
@@ -9835,7 +12011,7 @@ function aiditorConstructor(_props, refs) {
9835
12011
  }
9836
12012
  function focusLastAnnotationTextarea() {
9837
12013
  requestAnimationFrame(() => {
9838
- tryJayRefExec(refs.visualOverlay, (overlay) => {
12014
+ tryJayRefExec2(refs.visualOverlay, (overlay) => {
9839
12015
  const root = overlay.closest(".main-panel") ?? overlay.parentElement;
9840
12016
  if (!root)
9841
12017
  return;
@@ -9904,7 +12080,7 @@ function aiditorConstructor(_props, refs) {
9904
12080
  });
9905
12081
  }
9906
12082
  function touchAnnotationDropWire() {
9907
- tryJayRefExec(refs.previewCaptureRoot, (root) => {
12083
+ tryJayRefExec2(refs.previewCaptureRoot, (root) => {
9908
12084
  const el = root;
9909
12085
  if (el._aiditorAnnotEvents)
9910
12086
  return;
@@ -9950,8 +12126,8 @@ function aiditorConstructor(_props, refs) {
9950
12126
  }
9951
12127
  function loadProjectInfo() {
9952
12128
  if (!projectDir())
9953
- return;
9954
- getProjectInfoAction({ jayDevUrl: jayDevUrl() }).then((raw) => {
12129
+ return Promise.resolve();
12130
+ return getProjectInfoAction({ jayDevUrl: jayDevUrl() }).then((raw) => {
9955
12131
  const routes = raw.routes ?? [];
9956
12132
  const pages = routes.map((r) => ({
9957
12133
  name: r.path,
@@ -9967,7 +12143,9 @@ function aiditorConstructor(_props, refs) {
9967
12143
  const home = pages.find((p) => p.url === "/" || p.url === "");
9968
12144
  if (home)
9969
12145
  selectProjectPage(home);
9970
- }).catch((err) => console.error("[aiditor] getProjectInfo error:", err));
12146
+ }).catch((err) => {
12147
+ console.error("[aiditor] getProjectInfo error:", err);
12148
+ });
9971
12149
  }
9972
12150
  function loadFreezes(route) {
9973
12151
  listFreezesAction({ route }).then(({ freezes }) => {
@@ -10049,6 +12227,20 @@ function aiditorConstructor(_props, refs) {
10049
12227
  }
10050
12228
  })();
10051
12229
  }
12230
+ function appendAgentOutputChunk(text, cssClass = "chunk", filePath = "", toolName = "", showUserPrefix = false) {
12231
+ setChunks((prev) => [
12232
+ ...prev,
12233
+ { text, cssClass, filePath, toolName, showUserPrefix }
12234
+ ]);
12235
+ requestAnimationFrame(() => {
12236
+ tryJayRefExec2(refs.outputScroll, (el) => {
12237
+ el.scrollTop = el.scrollHeight;
12238
+ });
12239
+ });
12240
+ }
12241
+ const refreshPageBriefRef = {
12242
+ current: null
12243
+ };
10052
12244
  function selectProjectPage(page2) {
10053
12245
  ++previewParamsGeneration;
10054
12246
  clearVisualSession();
@@ -10064,6 +12256,7 @@ function aiditorConstructor(_props, refs) {
10064
12256
  setPreviewSrc("");
10065
12257
  setPreviewUrlBar("");
10066
12258
  setPreviewError("Connect to Jay dev (setup) to load previews.");
12259
+ void refreshPageBriefRef.current?.();
10067
12260
  return;
10068
12261
  }
10069
12262
  if (!page2.url.includes(":")) {
@@ -10073,11 +12266,44 @@ function aiditorConstructor(_props, refs) {
10073
12266
  setLogicalPreviewFull(full);
10074
12267
  queueMicrotask(() => touchAnnotationDropWire());
10075
12268
  loadFreezes(page2.url);
12269
+ void refreshPageBriefRef.current?.();
10076
12270
  return;
10077
12271
  }
10078
12272
  setPreviewSrc("");
10079
12273
  loadParamsForRoute(page2);
10080
- }
12274
+ void refreshPageBriefRef.current?.();
12275
+ }
12276
+ const addPage = initAddPageComposer({
12277
+ projectDir,
12278
+ isRunning,
12279
+ setIsRunning,
12280
+ setBottomPanelCollapsed,
12281
+ appendAgentOutputChunk,
12282
+ setChunks,
12283
+ loadProjectInfo,
12284
+ projectPlugins,
12285
+ openProjectSettings,
12286
+ projectPages,
12287
+ httpBaseUrl,
12288
+ setSelectedPageUrl,
12289
+ selectProjectPage,
12290
+ setAddPageResultBanner,
12291
+ getStopStream: () => stopStream ?? void 0,
12292
+ setStopStream: (fn) => {
12293
+ stopStream = fn ?? null;
12294
+ },
12295
+ selectedPageFilePath,
12296
+ selectedPageUrl,
12297
+ setCurrentPageHasBrief
12298
+ });
12299
+ refreshPageBriefRef.current = () => {
12300
+ void addPage.refreshCurrentPageBriefAvailability();
12301
+ };
12302
+ const showAddPageResultBanner = createMemo(() => addPageResultBanner() !== null);
12303
+ const addPageResultBannerClass = createMemo(() => addPageResultBanner()?.kind === "success" ? "add-page-result-success" : "add-page-result-failed");
12304
+ const addPageResultBannerMessage = createMemo(() => addPageResultBanner()?.message ?? "");
12305
+ const showAddPageRetry = createMemo(() => addPageResultBanner()?.showRetry === true);
12306
+ const showAddPageFixWithAiditor = createMemo(() => addPageResultBanner()?.showFixWithAiditor === true);
10081
12307
  runBootstrap();
10082
12308
  ensurePreviewProbePoll();
10083
12309
  function handleSnapshotShortcut() {
@@ -10114,6 +12340,41 @@ function aiditorConstructor(_props, refs) {
10114
12340
  });
10115
12341
  }
10116
12342
  refs.retryBootstrapBtn.onclick(() => runBootstrap());
12343
+ addPage.bindAddPageRefs(refs);
12344
+ refs.projectSettingsBtn.onclick(() => openProjectSettings());
12345
+ refs.projectSettingsCloseBtn.onclick(() => closeProjectSettings());
12346
+ refs.projectSettingsOverlay.onclick(({ event }) => {
12347
+ if (event.target === event.currentTarget)
12348
+ closeProjectSettings();
12349
+ });
12350
+ projectPlugins.bindProjectPluginsRefs(refs);
12351
+ refs.projectPluginsPanel.oninput(({ event }) => {
12352
+ const t = event.target;
12353
+ if (t.classList.contains("project-local-name")) {
12354
+ projectPlugins.setAddLocalPluginName(t.value);
12355
+ } else if (t.classList.contains("project-local-package")) {
12356
+ projectPlugins.setAddLocalPackageName(t.value);
12357
+ } else if (t.classList.contains("project-local-path")) {
12358
+ projectPlugins.setAddLocalPath(t.value);
12359
+ }
12360
+ });
12361
+ refs.addPageResultBannerSlot.onclick(({ event }) => {
12362
+ const target = event.target.closest("[data-add-page-retry], [data-add-page-fix]");
12363
+ if (!target)
12364
+ return;
12365
+ const banner = addPageResultBanner();
12366
+ if (!banner)
12367
+ return;
12368
+ if (target.hasAttribute("data-add-page-retry")) {
12369
+ setAddPageResultBanner(null);
12370
+ addPage.retryAddPage();
12371
+ return;
12372
+ }
12373
+ if (target.hasAttribute("data-add-page-fix")) {
12374
+ setAddPageResultBanner(null);
12375
+ void addPage.openPreviewForRoute(banner.pageRoute);
12376
+ }
12377
+ });
10117
12378
  refs.pageRouteSelect.onchange(({ event }) => {
10118
12379
  const v = event.target.value;
10119
12380
  if (!v)
@@ -10353,11 +12614,25 @@ function aiditorConstructor(_props, refs) {
10353
12614
  if (btn.classList.contains("visual-annotation-attach")) {
10354
12615
  event.preventDefault();
10355
12616
  attachTargetPinId = draft.id;
10356
- tryJayRefExec(refs.visualAttachFileInput, (el) => el.click());
12617
+ tryJayRefExec2(refs.visualAttachFileInput, (el) => el.click());
10357
12618
  }
10358
12619
  });
12620
+ refs.visualAnnotationRows.annotationRow.onchange(({ event }) => {
12621
+ const t = event.target;
12622
+ const card = t.closest("[data-annotation-id]");
12623
+ const id = card?.getAttribute("data-annotation-id");
12624
+ if (!id)
12625
+ return;
12626
+ handleBindingUiEvent(t, id, updateVisualAnnotation);
12627
+ });
10359
12628
  refs.visualAnnotationRows.annotationRow.onclick(({ event, viewState }) => {
10360
- const t = event.target.closest("button");
12629
+ const raw = event.target;
12630
+ if (raw.closest(".annotation-binding")) {
12631
+ event.preventDefault();
12632
+ handleBindingUiEvent(raw, viewState.id, updateVisualAnnotation);
12633
+ return;
12634
+ }
12635
+ const t = raw.closest("button");
10361
12636
  if (!t)
10362
12637
  return;
10363
12638
  if (t.classList.contains("visual-attachment-remove-file")) {
@@ -10371,7 +12646,7 @@ function aiditorConstructor(_props, refs) {
10371
12646
  if (t.classList.contains("visual-annotation-attach")) {
10372
12647
  event.preventDefault();
10373
12648
  attachTargetPinId = viewState.id;
10374
- tryJayRefExec(refs.visualAttachFileInput, (el) => el.click());
12649
+ tryJayRefExec2(refs.visualAttachFileInput, (el) => el.click());
10375
12650
  return;
10376
12651
  }
10377
12652
  if (t.classList.contains("visual-annotation-remove")) {
@@ -10408,7 +12683,7 @@ function aiditorConstructor(_props, refs) {
10408
12683
  setIsRunning(true);
10409
12684
  setVisualSubmitError("");
10410
12685
  let rootEl = null;
10411
- const gotRoot = tryJayRefExec(refs.previewCaptureRoot, (el) => {
12686
+ const gotRoot = tryJayRefExec2(refs.previewCaptureRoot, (el) => {
10412
12687
  rootEl = el;
10413
12688
  });
10414
12689
  if (!gotRoot || !rootEl) {
@@ -10491,13 +12766,13 @@ function aiditorConstructor(_props, refs) {
10491
12766
  if (!previewIsSameOriginForCapture(previewSrc()))
10492
12767
  return;
10493
12768
  let rootEl = null;
10494
- tryJayRefExec(refs.previewCaptureRoot, (el) => {
12769
+ tryJayRefExec2(refs.previewCaptureRoot, (el) => {
10495
12770
  rootEl = el;
10496
12771
  });
10497
12772
  if (!rootEl)
10498
12773
  return;
10499
12774
  let iframeEl = null;
10500
- tryJayRefExec(refs.previewIframe, (el) => {
12775
+ tryJayRefExec2(refs.previewIframe, (el) => {
10501
12776
  iframeEl = el;
10502
12777
  });
10503
12778
  try {
@@ -10585,7 +12860,7 @@ function aiditorConstructor(_props, refs) {
10585
12860
  }
10586
12861
  }
10587
12862
  function ensureIframeShortcutForwarder() {
10588
- tryJayRefExec(refs.previewIframe, (fr) => {
12863
+ tryJayRefExec2(refs.previewIframe, (fr) => {
10589
12864
  const iframe = fr;
10590
12865
  try {
10591
12866
  const doc = iframe.contentDocument;
@@ -10613,7 +12888,7 @@ function aiditorConstructor(_props, refs) {
10613
12888
  if (!src || !previewIsSameOriginForCapture(src))
10614
12889
  return;
10615
12890
  ensureIframeShortcutForwarder();
10616
- tryJayRefExec(refs.previewIframe, (fr) => {
12891
+ tryJayRefExec2(refs.previewIframe, (fr) => {
10617
12892
  const iframe = fr;
10618
12893
  try {
10619
12894
  const win = iframe.contentWindow;
@@ -10644,7 +12919,7 @@ function aiditorConstructor(_props, refs) {
10644
12919
  recordingPreviewNavLog = [];
10645
12920
  queueMicrotask(tickPreviewIframeProbe);
10646
12921
  }
10647
- function tryJayRefExec(refObj, fn) {
12922
+ function tryJayRefExec2(refObj, fn) {
10648
12923
  const r = refObj;
10649
12924
  if (typeof r?.exec$ !== "function")
10650
12925
  return false;
@@ -10673,13 +12948,13 @@ function aiditorConstructor(_props, refs) {
10673
12948
  normalizeRecordingPinsToVideoDurationIfNeeded(dur);
10674
12949
  setVideoDurationSec(dur);
10675
12950
  setVideoTimeLabel(`${formatClock(cur)} / ${formatClock(dur)}`);
10676
- tryJayRefExec(refs.videoTimeline, (inp) => {
12951
+ tryJayRefExec2(refs.videoTimeline, (inp) => {
10677
12952
  inp.value = String(Math.round(cur / dur * 1e3));
10678
12953
  });
10679
12954
  }
10680
12955
  }
10681
12956
  function pauseVideoForAnnotationCapture() {
10682
- tryJayRefExec(refs.videoReviewPlayer, (el) => {
12957
+ tryJayRefExec2(refs.videoReviewPlayer, (el) => {
10683
12958
  const v = el;
10684
12959
  if (!v.paused)
10685
12960
  v.pause();
@@ -10688,13 +12963,13 @@ function aiditorConstructor(_props, refs) {
10688
12963
  }
10689
12964
  function syncReviewTransportAfterAnnotation() {
10690
12965
  queueMicrotask(() => {
10691
- tryJayRefExec(refs.videoReviewPlayer, (el) => {
12966
+ tryJayRefExec2(refs.videoReviewPlayer, (el) => {
10692
12967
  refreshVideoReviewTransport(el);
10693
12968
  });
10694
12969
  });
10695
12970
  }
10696
12971
  function seekVideoReviewToTimeAndPause(timeSec) {
10697
- tryJayRefExec(refs.videoReviewPlayer, (el) => {
12972
+ tryJayRefExec2(refs.videoReviewPlayer, (el) => {
10698
12973
  const v = el;
10699
12974
  v.pause();
10700
12975
  let dur = effectiveVideoDuration(v);
@@ -10768,7 +13043,7 @@ function aiditorConstructor(_props, refs) {
10768
13043
  });
10769
13044
  }
10770
13045
  };
10771
- const wired = tryJayRefExec(refs.videoReviewPlayer, (el) => bindVideo(el)) || (() => {
13046
+ const wired = tryJayRefExec2(refs.videoReviewPlayer, (el) => bindVideo(el)) || (() => {
10772
13047
  const q = document.querySelector("[data-aiditor-video-review]");
10773
13048
  if (q?.isConnected) {
10774
13049
  bindVideo(q);
@@ -10777,7 +13052,7 @@ function aiditorConstructor(_props, refs) {
10777
13052
  return false;
10778
13053
  })();
10779
13054
  if (wired) {
10780
- tryJayRefExec(refs.videoReviewBackdrop, (el) => {
13055
+ tryJayRefExec2(refs.videoReviewBackdrop, (el) => {
10781
13056
  el.focus();
10782
13057
  });
10783
13058
  startWatchdog();
@@ -10921,7 +13196,7 @@ function aiditorConstructor(_props, refs) {
10921
13196
  }
10922
13197
  function currentVideoTimeForNewPin() {
10923
13198
  let t = 0;
10924
- tryJayRefExec(refs.videoReviewPlayer, (v) => {
13199
+ tryJayRefExec2(refs.videoReviewPlayer, (v) => {
10925
13200
  t = v.currentTime;
10926
13201
  });
10927
13202
  return t;
@@ -10955,10 +13230,10 @@ function aiditorConstructor(_props, refs) {
10955
13230
  }
10956
13231
  let captureRoot = null;
10957
13232
  let videoEl = null;
10958
- tryJayRefExec(refs.videoReviewCaptureRoot, (r) => {
13233
+ tryJayRefExec2(refs.videoReviewCaptureRoot, (r) => {
10959
13234
  captureRoot = r;
10960
13235
  });
10961
- tryJayRefExec(refs.videoReviewPlayer, (v) => {
13236
+ tryJayRefExec2(refs.videoReviewPlayer, (v) => {
10962
13237
  videoEl = v;
10963
13238
  });
10964
13239
  if (!captureRoot || !videoEl) {
@@ -11038,6 +13313,79 @@ function aiditorConstructor(_props, refs) {
11038
13313
  event.stopPropagation();
11039
13314
  setChunks([]);
11040
13315
  });
13316
+ refs.agentChatInput.oninput(({ event }) => {
13317
+ setAgentChatMessage(event.target.value);
13318
+ });
13319
+ refs.agentChatInput.onkeydown(({ event }) => {
13320
+ if (event.key !== "Enter" || event.shiftKey)
13321
+ return;
13322
+ event.preventDefault();
13323
+ sendChatMessage();
13324
+ });
13325
+ refs.agentChatSendBtn.onclick(() => {
13326
+ sendChatMessage();
13327
+ });
13328
+ function focusAgentChatInput() {
13329
+ requestAnimationFrame(() => {
13330
+ tryJayRefExec2(refs.agentChatInput, (el) => {
13331
+ el.focus();
13332
+ });
13333
+ });
13334
+ }
13335
+ function handleAgentStreamChunk(chunk) {
13336
+ if (chunk.type === "thinking" && chunk.text) {
13337
+ appendAgentOutputChunk(chunk.text, "chunk chunk-thinking");
13338
+ } else if (chunk.type === "chunk" && chunk.text) {
13339
+ appendAgentOutputChunk(chunk.text);
13340
+ } else if (chunk.type === "status" && chunk.message) {
13341
+ appendAgentOutputChunk(chunk.message, "chunk chunk-status");
13342
+ } else if (chunk.type === "tool" && chunk.name) {
13343
+ const detail = chunk.text ? ` ${chunk.text}` : "";
13344
+ const isFileOp = chunk.name === "Read" || chunk.name === "Write";
13345
+ const fp = isFileOp && chunk.text ? chunk.text.split("\n")[0].trim() : "";
13346
+ appendAgentOutputChunk(`> ${chunk.name}${detail}`, "chunk chunk-tool", fp, fp ? chunk.name : "");
13347
+ } else if (chunk.type === "error" && chunk.message) {
13348
+ appendAgentOutputChunk(chunk.message, "chunk chunk-error");
13349
+ } else if (chunk.type === "result") {
13350
+ const cost = chunk.cost ? ` ($${chunk.cost.toFixed(4)})` : "";
13351
+ appendAgentOutputChunk(`Done${cost}`, "chunk chunk-result");
13352
+ }
13353
+ }
13354
+ function finishAgentStreamRun() {
13355
+ setIsRunning(false);
13356
+ focusAgentChatInput();
13357
+ }
13358
+ function runAgentStream(notes2, options, streamOptions) {
13359
+ stopStream?.();
13360
+ if (streamOptions.clearOutput)
13361
+ setChunks([]);
13362
+ setIsRunning(true);
13363
+ setBottomPanelCollapsed(false);
13364
+ stopStream = submitAndStreamTask(notes2, options, {
13365
+ onChunk: handleAgentStreamChunk,
13366
+ onEnd: finishAgentStreamRun,
13367
+ onError: (err) => {
13368
+ appendAgentOutputChunk(err.message, "chunk chunk-error");
13369
+ finishAgentStreamRun();
13370
+ }
13371
+ });
13372
+ }
13373
+ function sendChatMessage() {
13374
+ if (isRunning())
13375
+ return;
13376
+ const text = agentChatMessage().trim();
13377
+ if (!text)
13378
+ return;
13379
+ const route = selectedPageUrl() ?? "/";
13380
+ const rendered = previewUrlBar().trim();
13381
+ appendAgentOutputChunk(text, "chunk chunk-user", "", "", true);
13382
+ setAgentChatMessage("");
13383
+ runAgentStream(text, {
13384
+ kind: "chat",
13385
+ pageRoute: route,
13386
+ renderedUrl: rendered
13387
+ }, { clearOutput: false });
13388
+ }
11041
13389
  refs.outputScroll.onclick(({ event }) => {
11042
13390
  const target = event.target;
11043
13391
  if (!target.classList.contains("file-link"))
@@ -11098,7 +13446,7 @@ function aiditorConstructor(_props, refs) {
11098
13446
  document.addEventListener("mouseup", onUp);
11099
13447
  });
11100
13448
  refs.freezeBtn.onclick(() => {
11101
- tryJayRefExec(refs.previewIframe, (fr) => {
13449
+ tryJayRefExec2(refs.previewIframe, (fr) => {
11102
13450
  const iframe = fr;
11103
13451
  iframe.contentWindow?.postMessage({ type: "jay:requestFreeze" }, "*");
11104
13452
  });
@@ -11118,7 +13466,7 @@ function aiditorConstructor(_props, refs) {
11118
13466
  setVisualSubmitError("");
11119
13467
  setVideoRecordingNavLog([]);
11120
13468
  let rootEl = null;
11121
- const gotRoot = tryJayRefExec(refs.previewCaptureRoot, (r) => {
13469
+ const gotRoot = tryJayRefExec2(refs.previewCaptureRoot, (r) => {
11122
13470
  rootEl = r;
11123
13471
  });
11124
13472
  if (!gotRoot || !rootEl) {
@@ -11317,6 +13665,14 @@ function aiditorConstructor(_props, refs) {
11317
13665
  }
11318
13666
  }
11319
13667
  });
13668
+ refs.videoAnnotationRows.videoAnnotationRow.onchange(({ event }) => {
13669
+ const t = event.target;
13670
+ const card = t.closest("[data-annotation-id]");
13671
+ const id = card?.getAttribute("data-annotation-id");
13672
+ if (!id)
13673
+ return;
13674
+ handleBindingUiEvent(t, id, updateVideoAsVisual);
13675
+ });
11320
13676
  refs.videoAnnotationRows.videoAnnotationRow.oninput(({ event }) => {
11321
13677
  const t = event.target;
11322
13678
  if (t.tagName !== "TEXTAREA" || !t.classList.contains("visual-annotation-instruction"))
@@ -11327,7 +13683,13 @@ function aiditorConstructor(_props, refs) {
11327
13683
  setVideoAnnotationInstruction(id, t.value);
11328
13684
  });
11329
13685
  refs.videoAnnotationRows.videoAnnotationRow.onclick(({ event, viewState }) => {
11330
- const t = event.target.closest("button");
13686
+ const raw = event.target;
13687
+ if (raw.closest(".annotation-binding")) {
13688
+ event.preventDefault();
13689
+ handleBindingUiEvent(raw, viewState.id, updateVideoAsVisual);
13690
+ return;
13691
+ }
13692
+ const t = raw.closest("button");
11331
13693
  if (!t)
11332
13694
  return;
11333
13695
  if (t.classList.contains("visual-attachment-remove-file")) {
@@ -11341,7 +13703,7 @@ function aiditorConstructor(_props, refs) {
11341
13703
  if (t.classList.contains("visual-video-annotation-attach")) {
11342
13704
  event.preventDefault();
11343
13705
  attachVideoTargetPinId = viewState.id;
11344
- tryJayRefExec(refs.videoReviewAttachFileInput, (el) => el.click());
13706
+ tryJayRefExec2(refs.videoReviewAttachFileInput, (el) => el.click());
11345
13707
  return;
11346
13708
  }
11347
13709
  if (t.classList.contains("visual-annotation-remove")) {
@@ -11362,7 +13724,7 @@ function aiditorConstructor(_props, refs) {
11362
13724
  });
11363
13725
  refs.videoTimeline.oninput(({ event }) => {
11364
13726
  const val = Number(event.target.value);
11365
- tryJayRefExec(refs.videoReviewPlayer, (el) => {
13727
+ tryJayRefExec2(refs.videoReviewPlayer, (el) => {
11366
13728
  const v = el;
11367
13729
  let dur = effectiveVideoDuration(v);
11368
13730
  if (dur <= 0)
@@ -11388,7 +13750,7 @@ function aiditorConstructor(_props, refs) {
11388
13750
  seekVideoReviewToTimeAndPause(t);
11389
13751
  });
11390
13752
  refs.videoPlayPauseBtn.onclick(() => {
11391
- tryJayRefExec(refs.videoReviewPlayer, (el) => {
13753
+ tryJayRefExec2(refs.videoReviewPlayer, (el) => {
11392
13754
  const v = el;
11393
13755
  if (v.paused)
11394
13756
  void v.play().catch(() => {
@@ -11413,7 +13775,7 @@ function aiditorConstructor(_props, refs) {
11413
13775
  refs.videoReviewBackdrop.onkeydown(({ event }) => {
11414
13776
  if (event.key === "Escape") {
11415
13777
  event.preventDefault();
11416
- tryJayRefExec(refs.videoReviewCloseBtn, (b) => b.click());
13778
+ tryJayRefExec2(refs.videoReviewCloseBtn, (b) => b.click());
11417
13779
  }
11418
13780
  });
11419
13781
  refs.snapshotToolPointBtn.onclick(() => onSelectSnapshotTool("point"));
@@ -11454,7 +13816,7 @@ function aiditorConstructor(_props, refs) {
11454
13816
  return;
11455
13817
  const el = event.currentTarget;
11456
13818
  let imgEl = null;
11457
- tryJayRefExec(refs.snapshotImg, (img) => {
13819
+ tryJayRefExec2(refs.snapshotImg, (img) => {
11458
13820
  imgEl = img;
11459
13821
  });
11460
13822
  if (!imgEl)
@@ -11524,7 +13886,7 @@ function aiditorConstructor(_props, refs) {
11524
13886
  return;
11525
13887
  const el = event.currentTarget;
11526
13888
  let imgEl = null;
11527
- tryJayRefExec(refs.snapshotImg, (img) => {
13889
+ tryJayRefExec2(refs.snapshotImg, (img) => {
11528
13890
  imgEl = img;
11529
13891
  });
11530
13892
  if (!imgEl)
@@ -11568,6 +13930,14 @@ function aiditorConstructor(_props, refs) {
11568
13930
  }
11569
13931
  }
11570
13932
  });
13933
+ refs.snapshotAnnotationItems.snapshotAnnotationRow.onchange(({ event }) => {
13934
+ const t = event.target;
13935
+ const card = t.closest("[data-annotation-id]");
13936
+ const id = card?.getAttribute("data-annotation-id");
13937
+ if (!id)
13938
+ return;
13939
+ handleBindingUiEvent(t, id, updateSnapshotAnnotation);
13940
+ });
11571
13941
  refs.snapshotAnnotationItems.snapshotAnnotationRow.oninput(({ event }) => {
11572
13942
  const t = event.target;
11573
13943
  if (t.tagName !== "TEXTAREA" || !t.classList.contains("visual-annotation-instruction"))
@@ -11593,7 +13963,16 @@ function aiditorConstructor(_props, refs) {
11593
13963
  }
11594
13964
  });
11595
13965
  refs.snapshotAnnotationItems.snapshotAnnotationRow.onclick(({ event }) => {
11596
- const btn = event.target.closest("button");
13966
+ const raw = event.target;
13967
+ if (raw.closest(".annotation-binding")) {
13968
+ event.preventDefault();
13969
+ const card = raw.closest("[data-annotation-id]");
13970
+ const id = card?.getAttribute("data-annotation-id");
13971
+ if (id)
13972
+ handleBindingUiEvent(raw, id, updateSnapshotAnnotation);
13973
+ return;
13974
+ }
13975
+ const btn = raw.closest("button");
11597
13976
  if (!btn)
11598
13977
  return;
11599
13978
  if (btn.classList.contains("snapshot-annotation-remove")) {
@@ -11610,7 +13989,7 @@ function aiditorConstructor(_props, refs) {
11610
13989
  if (!id)
11611
13990
  return;
11612
13991
  snapshotAttachTargetPinId = id;
11613
- tryJayRefExec(refs.snapshotAttachFileInput, (inp) => inp.click());
13992
+ tryJayRefExec2(refs.snapshotAttachFileInput, (inp) => inp.click());
11614
13993
  return;
11615
13994
  }
11616
13995
  if (btn.classList.contains("visual-attachment-remove-file")) {
@@ -11635,7 +14014,7 @@ function aiditorConstructor(_props, refs) {
11635
14014
  createEffect(() => {
11636
14015
  if (showSnapshotModal()) {
11637
14016
  requestAnimationFrame(() => {
11638
- tryJayRefExec(refs.snapshotBackdrop, (el) => el.focus());
14017
+ tryJayRefExec2(refs.snapshotBackdrop, (el) => el.focus());
11639
14018
  });
11640
14019
  }
11641
14020
  });
@@ -11731,47 +14110,16 @@ function aiditorConstructor(_props, refs) {
11731
14110
  });
11732
14111
  }
11733
14112
  bindAiditorDocumentPasteCapture(onAiditorDocumentPasteCapture);
14113
+ runPluginInstallAgent = (pluginName, installError) => {
14114
+ const entry = getCatalogEntry(pluginName);
14115
+ if (!entry)
14116
+ return;
14117
+ setShowProjectSettingsModal(false);
14118
+ const notes2 = buildPluginInstallAgentNotes(entry, installError, projectDir() || "Jay project root (where jay-stack dev runs)");
14119
+ startAgentStream(notes2, void 0);
14120
+ };
11734
14121
  function startAgentStream(notes2, visual) {
11735
- stopStream?.();
11736
- setChunks([]);
11737
- setIsRunning(true);
11738
- setBottomPanelCollapsed(false);
11739
- const addChunk = (text, cssClass = "chunk", filePath = "", toolName = "") => {
11740
- setChunks((prev) => [...prev, { text, cssClass, filePath, toolName }]);
11741
- requestAnimationFrame(() => {
11742
- tryJayRefExec(refs.outputScroll, (el) => {
11743
- el.scrollTop = el.scrollHeight;
11744
- });
11745
- });
11746
- };
11747
- stopStream = submitAndStreamTask(notes2, visual, {
11748
- onChunk: (chunk) => {
11749
- if (chunk.type === "thinking" && chunk.text) {
11750
- addChunk(chunk.text, "chunk chunk-thinking");
11751
- } else if (chunk.type === "chunk" && chunk.text) {
11752
- addChunk(chunk.text);
11753
- } else if (chunk.type === "status" && chunk.message) {
11754
- addChunk(chunk.message, "chunk chunk-status");
11755
- } else if (chunk.type === "tool" && chunk.name) {
11756
- const detail = chunk.text ? ` ${chunk.text}` : "";
11757
- const isFileOp = chunk.name === "Read" || chunk.name === "Write";
11758
- const fp = isFileOp && chunk.text ? chunk.text.split("\n")[0].trim() : "";
11759
- addChunk(`> ${chunk.name}${detail}`, "chunk chunk-tool", fp, fp ? chunk.name : "");
11760
- } else if (chunk.type === "error" && chunk.message) {
11761
- addChunk(chunk.message, "chunk chunk-error");
11762
- } else if (chunk.type === "result") {
11763
- const cost = chunk.cost ? ` ($${chunk.cost.toFixed(4)})` : "";
11764
- addChunk(`Done${cost}`, "chunk chunk-result");
11765
- }
11766
- },
11767
- onEnd: () => {
11768
- setIsRunning(false);
11769
- },
11770
- onError: (err) => {
11771
- addChunk(err.message, "chunk chunk-error");
11772
- setIsRunning(false);
11773
- }
11774
- });
14122
+ runAgentStream(notes2, visual, { clearOutput: true });
11775
14123
  }
11776
14124
  return {
11777
14125
  render: () => ({
@@ -11806,6 +14154,11 @@ function aiditorConstructor(_props, refs) {
11806
14154
  bottomPanelClass,
11807
14155
  bottomPanelToggleGlyph,
11808
14156
  showBottomPanelRunning,
14157
+ agentChatMessage,
14158
+ showAgentChatInput,
14159
+ agentChatInputDisabled,
14160
+ agentChatSendDisabled,
14161
+ agentChatPlaceholder,
11809
14162
  showFilePreview,
11810
14163
  filePreviewPath,
11811
14164
  filePreviewContent,
@@ -11912,7 +14265,72 @@ function aiditorConstructor(_props, refs) {
11912
14265
  snapshotAnnotationItems,
11913
14266
  showSnapshotAnnotationsPanel,
11914
14267
  showSnapshotSubmitError,
11915
- snapshotSubmitError
14268
+ snapshotSubmitError,
14269
+ showAddPageComposer: addPage.showAddPageComposer,
14270
+ addPageRoute: addPage.addPageRoute,
14271
+ addPageContentMd: addPage.addPageContentMd,
14272
+ addPageDesignMd: addPage.addPageDesignMd,
14273
+ addPageRouteError: addPage.addPageRouteError,
14274
+ showAddPageRouteError: addPage.showAddPageRouteError,
14275
+ addPageComposerError: addPage.addPageComposerError,
14276
+ showAddPageComposerError: addPage.showAddPageComposerError,
14277
+ addPageUploadHint: addPage.addPageUploadHint,
14278
+ showAddPageUploadHint: addPage.showAddPageUploadHint,
14279
+ addPageIsDynamicRoute: addPage.addPageIsDynamicRoute,
14280
+ addPageShowDesignHint: addPage.addPageShowDesignHint,
14281
+ addPageCreateDisabled: addPage.addPageCreateDisabled,
14282
+ addPageRunningHint: addPage.addPageRunningHint,
14283
+ showAddPageRunningHint: addPage.showAddPageRunningHint,
14284
+ addPageAttachmentRows: addPage.addPageAttachmentRows,
14285
+ showAddPageAttachments: addPage.showAddPageAttachments,
14286
+ showProjectSettingsModal,
14287
+ showProjectSettingsPluginsPanel: projectPlugins.showPluginsPanel,
14288
+ showProjectSettingsInstallZone: projectPlugins.showInstallZone,
14289
+ showProjectSettingsInstalledZone: projectPlugins.showInstalledZone,
14290
+ projectSettingsInstallRows: projectPlugins.availableInstallRows,
14291
+ projectSettingsInstalledRows: projectPlugins.installedPluginRows,
14292
+ showProjectSettingsPluginsError: projectPlugins.showPluginsPanelError,
14293
+ showFixInstallWithAgent: projectPlugins.showFixInstallWithAgent,
14294
+ fixInstallPluginName: projectPlugins.fixInstallPluginName,
14295
+ projectSettingsPluginsError: projectPlugins.pluginsPanelError,
14296
+ showProjectSettingsInstallProgress: projectPlugins.showInstallProgress,
14297
+ projectSettingsInstallProgress: projectPlugins.installStepLabel,
14298
+ showProjectSettingsWixConfigWarning: projectPlugins.showWixConfigWarning,
14299
+ showProjectSettingsReloadPreview: projectPlugins.showReloadPreview,
14300
+ showProjectLocalPluginForm: projectPlugins.showAddLocalPluginForm,
14301
+ projectLocalPluginName: projectPlugins.addLocalPluginName,
14302
+ projectLocalPackageName: projectPlugins.addLocalPackageName,
14303
+ projectLocalPath: projectPlugins.addLocalPath,
14304
+ showAddPagePluginsPanel: addPage.showAddPagePluginsPanel,
14305
+ showAddPagePagePluginsZone: addPage.showAddPagePagePluginsZone,
14306
+ addPagePluginPickerOptions: addPage.addPagePluginPickerOptions,
14307
+ addPagePluginPickerValue: addPage.addPagePluginPickerValue,
14308
+ showAddPageContractPicker: addPage.showAddPageContractPicker,
14309
+ addPagePickerPluginLabel: addPage.addPagePickerPluginLabel,
14310
+ addPageContractPickerRows: addPage.addPageContractPickerRows,
14311
+ addPagePagePluginChips: addPage.addPagePagePluginChips,
14312
+ showAddPageHasPagePluginChips: addPage.showAddPageHasPagePluginChips,
14313
+ showAddPagePagePluginsEmptyHint: addPage.showAddPagePagePluginsEmptyHint,
14314
+ showAddPageResultBanner,
14315
+ addPageResultBannerClass,
14316
+ addPageResultBannerMessage,
14317
+ showAddPageRetry,
14318
+ showAddPageFixWithAiditor,
14319
+ showAddPageFromBriefBtn,
14320
+ showBriefFillPopover: addPage.showBriefFillPopover,
14321
+ briefFillContextNotes: addPage.briefFillContextNotes,
14322
+ briefFillPopoverError: addPage.briefFillPopoverError,
14323
+ showBriefFillPopoverError: addPage.showBriefFillPopoverError,
14324
+ briefFillDisabled: addPage.briefFillDisabled,
14325
+ briefFillInputDisabled: addPage.briefFillInputDisabled,
14326
+ briefFillDropzoneClass: addPage.briefFillDropzoneClass,
14327
+ addPageBriefFillGenerateDisabled: addPage.addPageBriefFillGenerateDisabled,
14328
+ briefFillStatusHint: addPage.briefFillStatusHint,
14329
+ showBriefFillStatusHint: addPage.showBriefFillStatusHint,
14330
+ showBriefFillThumbnails: addPage.showBriefFillThumbnails,
14331
+ addPageBriefFillThumbRows: addPage.addPageBriefFillThumbRows,
14332
+ showSuggestedRouteBanner: addPage.showSuggestedRouteBanner,
14333
+ suggestedRouteLabel: addPage.suggestedRouteLabel
11916
14334
  })
11917
14335
  };
11918
14336
  }