@jay-framework/aiditor 0.18.4 → 0.19.1

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.
@@ -8275,6 +8275,18 @@ async function seekVideoToTime(video, timeSec, options) {
8275
8275
  let dur = effectiveVideoDuration(video);
8276
8276
  if (!Number.isFinite(dur) || dur <= 0) dur = Number.POSITIVE_INFINITY;
8277
8277
  const t = Math.max(0, Math.min(timeSec, dur));
8278
+ const skipSeek = Math.abs(video.currentTime - t) < 1e-4;
8279
+ console.info("[aiditor:video-capture] seek start", {
8280
+ t,
8281
+ currentTime: video.currentTime,
8282
+ skipSeek,
8283
+ paused: video.paused
8284
+ });
8285
+ if (skipSeek) {
8286
+ await flushLayout();
8287
+ console.info("[aiditor:video-capture] seek done", { t, via: "skipSeek" });
8288
+ return;
8289
+ }
8278
8290
  await new Promise((resolve, reject) => {
8279
8291
  let settled = false;
8280
8292
  const timer = window.setTimeout(() => {
@@ -8287,25 +8299,22 @@ async function seekVideoToTime(video, timeSec, options) {
8287
8299
  )
8288
8300
  );
8289
8301
  }, timeoutMs);
8290
- const done = () => {
8302
+ const onSeeked = () => {
8291
8303
  if (settled) return;
8292
8304
  settled = true;
8293
8305
  window.clearTimeout(timer);
8294
8306
  video.removeEventListener("seeked", onSeeked);
8295
- const rvfc = video.requestVideoFrameCallback;
8296
- if (typeof rvfc === "function") {
8297
- rvfc.call(video, () => resolve());
8298
- } else {
8299
- requestAnimationFrame(() => resolve());
8300
- }
8307
+ console.info("[aiditor:video-capture] seeked", {
8308
+ t,
8309
+ currentTime: video.currentTime
8310
+ });
8311
+ void flushLayout().then(() => {
8312
+ console.info("[aiditor:video-capture] seek done", { t, via: "seeked" });
8313
+ resolve();
8314
+ });
8301
8315
  };
8302
- const onSeeked = () => done();
8303
8316
  video.addEventListener("seeked", onSeeked);
8304
- if (Math.abs(video.currentTime - t) < 1e-4) {
8305
- queueMicrotask(() => onSeeked());
8306
- } else {
8307
- video.currentTime = t;
8308
- }
8317
+ video.currentTime = t;
8309
8318
  });
8310
8319
  }
8311
8320
  async function capturePreviewForVideoFrame(rootEl, videoEl, mode = "full") {
@@ -8495,12 +8504,22 @@ function blobToImage(blob) {
8495
8504
  });
8496
8505
  }
8497
8506
  async function captureVideoInstantDual(rootEl, videoEl) {
8507
+ console.info("[aiditor:video-capture] capture start", { mode: "clean" });
8498
8508
  const clean = await capturePreviewForVideoFrame(rootEl, videoEl, "clean");
8509
+ console.info("[aiditor:video-capture] capture done", {
8510
+ mode: "clean",
8511
+ bytes: clean.size
8512
+ });
8513
+ console.info("[aiditor:video-capture] capture start", { mode: "markersOnly" });
8499
8514
  const markersOnly = await capturePreviewForVideoFrame(
8500
8515
  rootEl,
8501
8516
  videoEl,
8502
8517
  "markersOnly"
8503
8518
  );
8519
+ console.info("[aiditor:video-capture] capture done", {
8520
+ mode: "markersOnly",
8521
+ bytes: markersOnly.size
8522
+ });
8504
8523
  return { clean, markersOnly };
8505
8524
  }
8506
8525
  function getRestrictionTargetCtor() {
@@ -8787,19 +8806,54 @@ async function ensureAddPageRequestId(pageRoute, currentRequestId) {
8787
8806
  }
8788
8807
  return { requestId: result.requestId };
8789
8808
  }
8790
- function insertTextAtCursor(textarea, text, currentValue) {
8791
- const start = textarea.selectionStart ?? currentValue.length;
8792
- const end = textarea.selectionEnd ?? start;
8809
+ function formatRouteParamToken(paramName) {
8810
+ return `[${paramName}]`;
8811
+ }
8812
+ function insertRouteParamInField(input, currentRoute, paramName) {
8813
+ const token = formatRouteParamToken(paramName);
8814
+ const route = currentRoute.trim() || "/";
8815
+ if (route.includes(token)) return route;
8816
+ const insertWithSlash = (before) => {
8817
+ if (before.length === 0) return token;
8818
+ const last = before[before.length - 1];
8819
+ if (last === "/" || last === "]") return token;
8820
+ return `/${token}`;
8821
+ };
8822
+ if (input && document.activeElement === input) {
8823
+ const start = input.selectionStart ?? route.length;
8824
+ const end = input.selectionEnd ?? start;
8825
+ const before = route.slice(0, start);
8826
+ const after = route.slice(end);
8827
+ const insert2 = insertWithSlash(before);
8828
+ const next = before + insert2 + after;
8829
+ queueMicrotask(() => {
8830
+ const pos = before.length + insert2.length;
8831
+ input.setSelectionRange(pos, pos);
8832
+ input.focus();
8833
+ });
8834
+ return next;
8835
+ }
8836
+ const insert = insertWithSlash(route);
8837
+ return route.endsWith("/") ? `${route}${token}` : `${route}${insert}`;
8838
+ }
8839
+ function insertTextAtRange(currentValue, text, selectionStart, selectionEnd = selectionStart) {
8840
+ const start = Math.max(0, Math.min(selectionStart, currentValue.length));
8841
+ const end = Math.max(start, Math.min(selectionEnd, currentValue.length));
8793
8842
  const before = currentValue.slice(0, start);
8794
8843
  const after = currentValue.slice(end);
8795
8844
  const prefix = before.length > 0 && !before.endsWith("\n") ? "\n" : "";
8796
8845
  const insertion = `${prefix}${text}
8797
8846
  `;
8798
8847
  const next = before + insertion + after;
8799
- const newPos = before.length + insertion.length;
8848
+ return { next, cursor: before.length + insertion.length };
8849
+ }
8850
+ function insertTextAtCursor(textarea, text, currentValue, selection) {
8851
+ const start = textarea.selectionStart ?? currentValue.length;
8852
+ const end = textarea.selectionEnd ?? start;
8853
+ const { next, cursor } = insertTextAtRange(currentValue, text, start, end);
8800
8854
  queueMicrotask(() => {
8801
- textarea.selectionStart = newPos;
8802
- textarea.selectionEnd = newPos;
8855
+ textarea.selectionStart = cursor;
8856
+ textarea.selectionEnd = cursor;
8803
8857
  textarea.focus();
8804
8858
  });
8805
8859
  return next;
@@ -9223,383 +9277,459 @@ ${md}`);
9223
9277
  briefFillGenerating: generating
9224
9278
  };
9225
9279
  }
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
9280
+ function detectRequiredParamCollisions(resolved) {
9281
+ const byContract = /* @__PURE__ */ new Map();
9282
+ for (const r of resolved) {
9283
+ byContract.set(r.contractName, r);
9284
+ }
9285
+ const contracts = [...byContract.values()];
9286
+ if (contracts.length < 2) return [];
9287
+ const warnings = [];
9288
+ for (let i = 0; i < contracts.length; i++) {
9289
+ for (let j = i + 1; j < contracts.length; j++) {
9290
+ const a2 = contracts[i];
9291
+ const b = contracts[j];
9292
+ if (a2.contractName === b.contractName) continue;
9293
+ const overlap = a2.requiredParams.filter(
9294
+ (p) => b.requiredParams.includes(p)
9295
+ );
9296
+ for (const param of overlap) {
9297
+ warnings.push({
9298
+ overlappingParam: param,
9299
+ contractNames: [a2.contractName, b.contractName],
9300
+ message: `${a2.contractName} and ${b.contractName} both require [${param}] for different data. One URL segment cannot serve both. Use separate pages, distinct param names in the route, or instance mode for one component.`
9301
+ });
9302
+ }
9303
+ }
9310
9304
  }
9311
- ];
9312
- function getCatalogEntry(pluginName) {
9313
- return CATALOG.find((e2) => e2.pluginName === pluginName);
9305
+ return warnings;
9314
9306
  }
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];
9307
+ function collectRouteSegmentNames(resolved) {
9308
+ const names = /* @__PURE__ */ new Set();
9309
+ for (const r of resolved) {
9310
+ for (const p of r.allParams) names.add(p);
9325
9311
  }
9326
- return contractName.charAt(0) || "c";
9312
+ return [...names].sort((a2, b) => a2.localeCompare(b));
9327
9313
  }
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 });
9314
+ function routeHasDynamicSegment(route, paramName) {
9315
+ return route.includes(`[${paramName}]`);
9338
9316
  }
9339
- async function checkPluginUpdate(pluginName) {
9340
- return checkPluginUpdateAction({ pluginName });
9317
+ function routeHasAnyDynamicSegment(route) {
9318
+ return /\[[^\]]+\]/.test(route);
9341
9319
  }
9342
- function ensureProjectPluginStream(input, callbacks) {
9343
- let aborted = false;
9344
- const abort = () => {
9345
- aborted = true;
9320
+ function suggestMissingRouteSegments(route, segmentNames) {
9321
+ const trimmed = route.replace(/\/+$/, "") || "/";
9322
+ const missing = segmentNames.filter(
9323
+ (name) => !routeHasDynamicSegment(trimmed, name)
9324
+ );
9325
+ if (missing.length === 0) return null;
9326
+ const suffix = missing.map((n) => `[${n}]`).join("/");
9327
+ if (trimmed === "/") return `/${suffix}`;
9328
+ return `${trimmed}/${suffix}`;
9329
+ }
9330
+ function stripNavRowCore(row) {
9331
+ const { item: _item, ...viewRow } = row;
9332
+ return viewRow;
9333
+ }
9334
+ function stripNavRowForView(row) {
9335
+ return stripNavRowCore(row);
9336
+ }
9337
+ function stripMarkerAddMenuNavRowForPage(row) {
9338
+ return stripNavRowCore(row);
9339
+ }
9340
+ function stripMarkerPickerNavRowForVisual(row) {
9341
+ return stripNavRowCore(row);
9342
+ }
9343
+ function stripMarkerPickerNavRowForVideo(row) {
9344
+ return stripNavRowCore(row);
9345
+ }
9346
+ function stripMarkerPickerNavRowForSnapshot(row) {
9347
+ return stripNavRowCore(row);
9348
+ }
9349
+ function buildAddMenuNavRows(categories, panelOpen, selectedCategory, selectedSubCategory) {
9350
+ if (!panelOpen) return [];
9351
+ const cat = selectedCategory;
9352
+ if (!cat) {
9353
+ return categories.map((c) => ({
9354
+ rowKey: `cat:${c.category}`,
9355
+ label: c.category,
9356
+ value: c.category,
9357
+ kind: 0
9358
+ /* category */
9359
+ }));
9360
+ }
9361
+ const group = categories.find((c) => c.category === cat);
9362
+ if (!group) return [];
9363
+ const sub = selectedSubCategory;
9364
+ if (sub === null && group.subCategories.length > 1) {
9365
+ return group.subCategories.map((s) => ({
9366
+ rowKey: `sub:${cat}:${s.subCategory ?? ""}`,
9367
+ label: s.subCategory ?? "General",
9368
+ value: s.subCategory ?? "",
9369
+ kind: 1
9370
+ /* subCategory */
9371
+ }));
9372
+ }
9373
+ const subKey = sub ?? group.subCategories[0]?.subCategory ?? null;
9374
+ const items = group.subCategories.find((s) => s.subCategory === subKey)?.items ?? group.subCategories[0]?.items ?? [];
9375
+ return items.map((item) => ({
9376
+ rowKey: `item:${item.id}`,
9377
+ label: item.title,
9378
+ value: item.id,
9379
+ kind: 2,
9380
+ item
9381
+ }));
9382
+ }
9383
+ const listAddMenuItemsAction = createActionCaller("aiditor.listAddMenuItems", "GET");
9384
+ const resolveAddMenuContractParamsAction = createActionCaller("aiditor.resolveAddMenuContractParams", "GET");
9385
+ const syncAddMenuAttachmentsAction = createActionCaller("aiditor.syncAddMenuAttachments", "GET");
9386
+ async function listAddMenuItems() {
9387
+ return listAddMenuItemsAction({});
9388
+ }
9389
+ async function resolveContractParamsForItem(_projectDir, item) {
9390
+ const result = await resolveAddMenuContractParamsAction({
9391
+ itemId: item.id,
9392
+ pluginName: item.pluginName
9393
+ });
9394
+ if (!result.contractName || !result.pluginName) return null;
9395
+ return {
9396
+ contractName: result.contractName,
9397
+ pluginName: result.pluginName,
9398
+ requiredParams: result.requiredParams ?? [],
9399
+ allParams: result.allParams ?? []
9346
9400
  };
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
9401
  }
9366
- async function syncAddPagePluginManifest(input) {
9367
- return syncAddPagePluginManifestAction(input);
9402
+ async function syncAddMenuAttachments(input) {
9403
+ return syncAddMenuAttachmentsAction(input);
9368
9404
  }
9369
- async function rerunPluginSetup(pluginName, force) {
9370
- return rerunPluginSetupAction({ pluginName, force });
9405
+ function attachmentScopeKey(a2) {
9406
+ return a2.annotationId ?? "";
9371
9407
  }
9372
- async function writePluginSource(input) {
9373
- return writePluginSourceAction(input);
9408
+ function dedupeAddMenuAttachment(prev, next) {
9409
+ const scope = attachmentScopeKey(next);
9410
+ const exists = prev.some(
9411
+ (a2) => a2.itemId === next.itemId && attachmentScopeKey(a2) === scope
9412
+ );
9413
+ if (exists) return prev;
9414
+ return [...prev, next];
9374
9415
  }
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
9416
+ function removeAddMenuAttachment(prev, itemId, annotationId) {
9417
+ const scope = annotationId ?? "";
9418
+ return prev.filter(
9419
+ (a2) => !(a2.itemId === itemId && attachmentScopeKey(a2) === scope)
9394
9420
  );
9395
9421
  }
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("");
9422
+ function initAddMenuPanel(deps) {
9423
+ const [attachments, setAttachments] = createSignal([]);
9424
+ const [catalogItems, setCatalogItems] = createSignal([]);
9425
+ const [catalogCategories, setCatalogCategories] = createSignal([]);
9426
+ const [catalogWarnings, setCatalogWarnings] = createSignal([]);
9427
+ const [panelOpen, setPanelOpen] = createSignal(false);
9428
+ const [selectedCategory, setSelectedCategory] = createSignal("");
9429
+ const [selectedSubCategory, setSelectedSubCategory] = createSignal(null);
9430
+ const [routeMigration, setRouteMigration] = createSignal(null);
9431
+ const [paramCollisionWarning, setParamCollisionWarning] = createSignal("");
9432
+ const [routeParamHint, setRouteParamHint] = createSignal("");
9433
+ const [pendingRouteParamItem, setPendingRouteParamItem] = createSignal(null);
9434
+ const [showRouteParamConfirm, setShowRouteParamConfirm] = createSignal(false);
9435
+ const [catalogLoading, setCatalogLoading] = createSignal(false);
9436
+ const [catalogLoaded, setCatalogLoaded] = createSignal(false);
9411
9437
  let syncTimer;
9412
- const installedHeadlessPlugins = createMemo(
9413
- () => projectPlugins.pluginsList().filter(
9414
- (p) => p.installed && p.kind === "headless" && p.contracts.length > 0
9415
- )
9438
+ const itemById = createMemo(() => {
9439
+ const map = /* @__PURE__ */ new Map();
9440
+ for (const item of catalogItems()) map.set(item.id, item);
9441
+ return map;
9442
+ });
9443
+ const chipRows = createMemo(
9444
+ () => attachments().map((a2) => {
9445
+ const item = itemById().get(a2.itemId);
9446
+ return {
9447
+ rowKey: `${a2.itemId}:${a2.annotationId ?? "request"}`,
9448
+ itemId: a2.itemId,
9449
+ title: item?.title ?? a2.itemId,
9450
+ subtitle: item?.pluginName ?? item?.id.split(":")[0] ?? "",
9451
+ annotationId: a2.annotationId
9452
+ };
9453
+ })
9416
9454
  );
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
- }
9455
+ const showAddMenuPanel = createMemo(() => true);
9456
+ const showAddMenuEmptyHint = createMemo(
9457
+ () => catalogItems().length === 0 && !catalogLoading()
9431
9458
  );
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
- }
9459
+ const showAddMenuChips = createMemo(() => chipRows().length > 0);
9460
+ const menuNavRows = createMemo(
9461
+ () => buildAddMenuNavRows(
9462
+ catalogCategories(),
9463
+ panelOpen(),
9464
+ selectedCategory(),
9465
+ selectedSubCategory()
9466
+ )
9449
9467
  );
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
9468
+ const menuNavRowsForView = createMemo(
9469
+ () => menuNavRows().map(stripNavRowForView)
9470
9470
  );
9471
- const showPagePluginsEmptyHint = createMemo(
9472
- () => showPluginsPanel() && pagePluginChipRows().length === 0
9471
+ const [routeSegmentNames, setRouteSegmentNames] = createSignal([]);
9472
+ const routeParamRows = createMemo(
9473
+ () => routeSegmentNames().map((name) => ({
9474
+ rowKey: name,
9475
+ name
9476
+ }))
9473
9477
  );
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
+ async function refreshCatalog() {
9479
+ setCatalogLoading(true);
9480
+ try {
9481
+ const result = await listAddMenuItems();
9482
+ setCatalogItems(result.items);
9483
+ setCatalogCategories(result.categories);
9484
+ setCatalogWarnings(result.warnings);
9485
+ if (result.warnings.length > 0) {
9486
+ console.warn("[aiditor] Add Menu catalog warnings:", result.warnings);
9487
+ }
9488
+ } catch (err) {
9489
+ console.error("[aiditor] listAddMenuItems failed:", err);
9490
+ setCatalogItems([]);
9491
+ setCatalogCategories([]);
9492
+ setCatalogWarnings([]);
9493
+ } finally {
9494
+ setCatalogLoaded(true);
9495
+ setCatalogLoading(false);
9496
+ }
9497
+ }
9498
+ async function ensureCatalogLoaded() {
9499
+ if (catalogLoaded() || catalogLoading()) return;
9500
+ await refreshCatalog();
9501
+ }
9502
+ async function updateRouteHelpers() {
9503
+ const resolved = [];
9504
+ for (const a2 of attachments()) {
9505
+ const item = itemById().get(a2.itemId);
9506
+ if (!item) continue;
9507
+ const params = await resolveContractParamsForItem(
9508
+ deps.projectDir(),
9509
+ item
9510
+ );
9511
+ if (params) resolved.push(params);
9512
+ }
9513
+ const collisions = detectRequiredParamCollisions(resolved);
9514
+ setParamCollisionWarning(collisions[0]?.message ?? "");
9515
+ const segments = collectRouteSegmentNames(resolved);
9516
+ if (segments.length === 0) {
9517
+ setRouteParamHint("");
9518
+ return segments;
9519
+ }
9520
+ const route = deps.getPageRoute();
9521
+ const suggested = suggestMissingRouteSegments(route, segments);
9522
+ setRouteParamHint(
9523
+ suggested && suggested !== route ? `Suggested route segments: ${suggested}` : ""
9478
9524
  );
9479
- });
9525
+ setRouteSegmentNames(segments);
9526
+ return segments;
9527
+ }
9480
9528
  function scheduleManifestSync() {
9481
9529
  const requestId = deps.getRequestId();
9482
9530
  if (!requestId) return;
9483
9531
  if (syncTimer) clearTimeout(syncTimer);
9484
9532
  syncTimer = setTimeout(() => {
9485
- void syncAddPagePluginManifest({
9533
+ void syncAddMenuAttachments({
9486
9534
  requestId,
9487
- selectedPlugins: selectedPlugins()
9488
- }).then((res) => {
9489
- if (res.ok && res.contentMd) {
9490
- deps.setContentMd(res.contentMd);
9491
- }
9535
+ addMenuAttachments: attachments(),
9536
+ routeMigration: routeMigration() ?? void 0
9492
9537
  });
9493
9538
  }, 300);
9494
9539
  }
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)
9540
+ async function commitAttachment(item, annotationId) {
9541
+ deps.preserveEditorContent();
9542
+ setAttachments(
9543
+ (prev) => dedupeAddMenuAttachment(prev, { itemId: item.id, annotationId })
9510
9544
  );
9545
+ await updateRouteHelpers();
9511
9546
  scheduleManifestSync();
9512
- }
9513
- function handlePickerChange(value) {
9514
- setPickerValue("");
9515
- if (value === ADD_PAGE_MANAGE_PLUGINS_VALUE) {
9516
- openProjectSettings();
9517
- setPickerPlugin("");
9547
+ setPanelOpen(false);
9548
+ setSelectedCategory("");
9549
+ setSelectedSubCategory(null);
9550
+ }
9551
+ async function tryAddItem(item, annotationId) {
9552
+ const params = await resolveContractParamsForItem(deps.projectDir(), item);
9553
+ const hasRouteParams = !!params && params.allParams.length > 0;
9554
+ if (deps.changeRequestMode?.() && hasRouteParams && deps.getCurrentRoute && !routeHasAnyDynamicSegment(deps.getCurrentRoute() ?? "")) {
9555
+ setPendingRouteParamItem(item);
9556
+ setShowRouteParamConfirm(true);
9518
9557
  return;
9519
9558
  }
9520
- if (!value) {
9521
- setPickerPlugin("");
9522
- return;
9523
- }
9524
- setPickerPlugin(value);
9559
+ await commitAttachment(item, annotationId);
9560
+ }
9561
+ async function confirmRouteParamAttach(annotationId) {
9562
+ const item = pendingRouteParamItem();
9563
+ if (!item) return;
9564
+ const current = deps.getCurrentRoute?.() ?? "/";
9565
+ const params = await resolveContractParamsForItem(deps.projectDir(), item);
9566
+ const segments = params ? collectRouteSegmentNames([params]) : [];
9567
+ const planned = suggestMissingRouteSegments(current, segments) ?? `${current}/[slug]`;
9568
+ deps.setPlannedRoute?.(planned);
9569
+ setRouteMigration({
9570
+ currentRoute: current,
9571
+ plannedRoute: planned,
9572
+ reason: "add-menu-route-param-component",
9573
+ triggeringItemIds: [item.id]
9574
+ });
9575
+ setShowRouteParamConfirm(false);
9576
+ setPendingRouteParamItem(null);
9577
+ await commitAttachment(item, annotationId);
9578
+ }
9579
+ function cancelRouteParamAttach() {
9580
+ setShowRouteParamConfirm(false);
9581
+ setPendingRouteParamItem(null);
9582
+ }
9583
+ function removeChip(itemId, annotationId) {
9584
+ deps.preserveEditorContent();
9585
+ setAttachments(
9586
+ (prev) => removeAddMenuAttachment(prev, itemId, annotationId)
9587
+ );
9588
+ void updateRouteHelpers();
9589
+ scheduleManifestSync();
9525
9590
  }
9526
- function resetPluginsPanel() {
9527
- setSelectedPlugins([]);
9528
- setPickerPlugin("");
9529
- setPickerValue("");
9591
+ function openPanel() {
9592
+ void ensureCatalogLoaded();
9593
+ setPanelOpen(true);
9594
+ setSelectedCategory("");
9595
+ setSelectedSubCategory(null);
9596
+ }
9597
+ function closePanel() {
9598
+ setPanelOpen(false);
9599
+ }
9600
+ function resetAddMenuPanel() {
9601
+ setAttachments([]);
9602
+ setRouteMigration(null);
9603
+ setParamCollisionWarning("");
9604
+ setRouteParamHint("");
9605
+ setPanelOpen(false);
9530
9606
  if (syncTimer) clearTimeout(syncTimer);
9531
9607
  }
9532
- function bindPluginsPanelRefs(refs) {
9533
- refs.addPagePluginPicker?.onchange(
9534
- ({ event }) => {
9535
- const value = event.target.value;
9536
- handlePickerChange(value);
9608
+ function bindRouteParamButtons(el) {
9609
+ el?.onclick(({ event }) => {
9610
+ const paramBtn = event.target.closest(
9611
+ "[data-add-menu-param]"
9612
+ );
9613
+ if (paramBtn?.dataset.param) {
9614
+ deps.insertRouteParam(paramBtn.dataset.param);
9537
9615
  }
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
- }
9616
+ });
9617
+ }
9618
+ function bindAddMenuPanelRefs(refs) {
9619
+ bindRouteParamButtons(refs.addPageRouteParamsPanel);
9620
+ refs.addMenuPanel?.onclick(({ event }) => {
9621
+ const target = event.target;
9622
+ const openBtn = target.closest("[data-add-menu-open]");
9623
+ if (openBtn) {
9624
+ openPanel();
9625
+ return;
9569
9626
  }
9570
- );
9627
+ const closeBtn = target.closest("[data-add-menu-close]");
9628
+ if (closeBtn) {
9629
+ closePanel();
9630
+ return;
9631
+ }
9632
+ const manage = target.closest("[data-add-menu-manage-plugins]");
9633
+ if (manage) {
9634
+ deps.openProjectSettings();
9635
+ return;
9636
+ }
9637
+ const catBtn = target.closest(
9638
+ "[data-add-menu-category]"
9639
+ );
9640
+ if (catBtn?.dataset.category) {
9641
+ setSelectedCategory(catBtn.dataset.category);
9642
+ setSelectedSubCategory(null);
9643
+ return;
9644
+ }
9645
+ const subBtn = target.closest(
9646
+ "[data-add-menu-subcategory]"
9647
+ );
9648
+ if (subBtn?.dataset.subcategory !== void 0) {
9649
+ setSelectedSubCategory(subBtn.dataset.subcategory || null);
9650
+ return;
9651
+ }
9652
+ const pickBtn = target.closest(
9653
+ "[data-add-menu-pick]"
9654
+ );
9655
+ if (pickBtn?.dataset.itemId) {
9656
+ const item = itemById().get(pickBtn.dataset.itemId);
9657
+ if (item) void tryAddItem(item);
9658
+ return;
9659
+ }
9660
+ const removeBtn = target.closest(
9661
+ "[data-add-menu-remove-chip]"
9662
+ );
9663
+ if (removeBtn?.dataset.itemId) {
9664
+ removeChip(removeBtn.dataset.itemId, removeBtn.dataset.annotationId);
9665
+ return;
9666
+ }
9667
+ const paramBtn = target.closest(
9668
+ "[data-add-menu-param]"
9669
+ );
9670
+ if (paramBtn?.dataset.param) {
9671
+ deps.insertRouteParam(paramBtn.dataset.param);
9672
+ return;
9673
+ }
9674
+ const atBtn = target.closest(
9675
+ "[data-add-menu-at-title]"
9676
+ );
9677
+ if (atBtn?.dataset.title) {
9678
+ deps.insertAtCursor(`@${atBtn.dataset.title}`);
9679
+ return;
9680
+ }
9681
+ const confirmBtn = target.closest("[data-add-menu-route-confirm]");
9682
+ if (confirmBtn) {
9683
+ void confirmRouteParamAttach();
9684
+ return;
9685
+ }
9686
+ const cancelBtn = target.closest("[data-add-menu-route-cancel]");
9687
+ if (cancelBtn) cancelRouteParamAttach();
9688
+ });
9571
9689
  }
9572
9690
  return {
9573
- selectedPlugins,
9574
- setSelectedPlugins,
9691
+ attachments,
9692
+ setAttachments,
9693
+ catalogItems,
9694
+ refreshCatalog,
9695
+ ensureCatalogLoaded,
9696
+ panelOpen,
9697
+ openPanel,
9698
+ closePanel,
9699
+ chipRows,
9700
+ menuNavRows: menuNavRowsForView,
9701
+ selectedCategory,
9702
+ showAddMenuPanel,
9703
+ showAddMenuEmptyHint,
9704
+ showAddMenuChips,
9705
+ showAddMenuPopover: panelOpen,
9706
+ catalogLoading,
9707
+ catalogWarnings,
9708
+ paramCollisionWarning,
9709
+ routeParamHint,
9710
+ routeSegmentButtons: routeSegmentNames,
9711
+ routeParamRows,
9712
+ showRouteParamConfirm,
9713
+ pendingRouteParamTitle: createMemo(
9714
+ () => pendingRouteParamItem()?.title ?? ""
9715
+ ),
9575
9716
  scheduleManifestSync,
9576
9717
  syncManifestNow: async () => {
9577
9718
  const requestId = deps.getRequestId();
9578
9719
  if (!requestId) return;
9579
- const res = await syncAddPagePluginManifest({
9720
+ await syncAddMenuAttachments({
9580
9721
  requestId,
9581
- selectedPlugins: selectedPlugins()
9722
+ addMenuAttachments: attachments(),
9723
+ routeMigration: routeMigration() ?? void 0
9582
9724
  });
9583
- if (res.ok && res.contentMd) deps.setContentMd(res.contentMd);
9584
9725
  },
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
9726
+ tryAddItem,
9727
+ removeChip,
9728
+ bindAddMenuPanelRefs,
9729
+ resetAddMenuPanel,
9730
+ updateRouteHelpers,
9731
+ routeMigration,
9732
+ setRouteMigration
9603
9733
  };
9604
9734
  }
9605
9735
  function tryJayRefExec(refObj, fn) {
@@ -9627,11 +9757,44 @@ function initAddPageComposer(deps) {
9627
9757
  const [addPageShowDesignHint, setAddPageShowDesignHint] = createSignal(false);
9628
9758
  const [addPageIsDynamicRoute, setAddPageIsDynamicRoute] = createSignal(false);
9629
9759
  const [addPageSubmitting, setAddPageSubmitting] = createSignal(false);
9630
- const pluginsPanel = initAddPagePluginsPanel({
9760
+ const addMenuPanel = initAddMenuPanel({
9631
9761
  projectPlugins: deps.projectPlugins,
9762
+ projectDir: deps.projectDir,
9632
9763
  getRequestId: () => addPageRequestId(),
9633
- getContentMd: () => addPageContentMd(),
9634
- setContentMd: setAddPageContentMd,
9764
+ preserveEditorContent: () => {
9765
+ const el = contentTextareaEl;
9766
+ if (el) setAddPageContentMd(el.value);
9767
+ },
9768
+ getPageRoute: () => addPageRoute(),
9769
+ setPageRoute: setAddPageRoute,
9770
+ insertAtCursor: (text) => {
9771
+ const current = contentTextareaEl?.value ?? addPageContentMd();
9772
+ const { next, cursor } = insertTextAtRange(
9773
+ current,
9774
+ text,
9775
+ contentTextareaEl?.selectionStart ?? contentSelectionStart,
9776
+ contentTextareaEl?.selectionEnd ?? contentSelectionEnd
9777
+ );
9778
+ setAddPageContentMd(next);
9779
+ contentSelectionStart = contentSelectionEnd = cursor;
9780
+ const el = contentTextareaEl;
9781
+ if (el) {
9782
+ queueMicrotask(() => {
9783
+ el.selectionStart = cursor;
9784
+ el.selectionEnd = cursor;
9785
+ el.focus();
9786
+ });
9787
+ }
9788
+ },
9789
+ insertRouteParam: (paramName) => {
9790
+ const next = insertRouteParamInField(
9791
+ routeInputEl,
9792
+ addPageRoute(),
9793
+ paramName
9794
+ );
9795
+ setAddPageRoute(next);
9796
+ scheduleRouteCheck(next);
9797
+ },
9635
9798
  openProjectSettings: deps.openProjectSettings
9636
9799
  });
9637
9800
  const briefFillPanel = initAddPageBriefFillPanel({
@@ -9653,8 +9816,20 @@ function initAddPageComposer(deps) {
9653
9816
  setComposerError: setAddPageComposerError
9654
9817
  });
9655
9818
  let routeCheckTimer;
9819
+ let routeInputEl = null;
9656
9820
  let contentTextareaEl = null;
9821
+ let contentSelectionStart = 0;
9822
+ let contentSelectionEnd = 0;
9657
9823
  let designTextareaEl = null;
9824
+ let designSelectionStart = 0;
9825
+ function trackTextareaSelection(el) {
9826
+ contentSelectionStart = el.selectionStart ?? 0;
9827
+ contentSelectionEnd = el.selectionEnd ?? contentSelectionStart;
9828
+ }
9829
+ function trackDesignTextareaSelection(el) {
9830
+ designSelectionStart = el.selectionStart ?? 0;
9831
+ el.selectionEnd ?? designSelectionStart;
9832
+ }
9658
9833
  const showAddPageRouteError = createMemo(
9659
9834
  () => addPageRouteError().length > 0
9660
9835
  );
@@ -9683,7 +9858,7 @@ function initAddPageComposer(deps) {
9683
9858
  if (deps.isRunning() || addPageSubmitting() || briefFillPanel.briefFillGenerating()) {
9684
9859
  return false;
9685
9860
  }
9686
- if (pluginsPanel.pluginsBlockCreatePage()) return false;
9861
+ if (deps.projectPlugins.installInProgress()) return false;
9687
9862
  const route = addPageRoute().trim();
9688
9863
  if (!route || addPageRouteError()) return false;
9689
9864
  const hasMd = addPageContentMd().trim().length > 0 || addPageDesignMd().trim().length > 0;
@@ -9704,13 +9879,14 @@ function initAddPageComposer(deps) {
9704
9879
  setAddPageAttachments([]);
9705
9880
  setAddPageShowDesignHint(false);
9706
9881
  setAddPageIsDynamicRoute(false);
9707
- pluginsPanel.resetPluginsPanel();
9882
+ addMenuPanel.resetAddMenuPanel();
9708
9883
  briefFillPanel.resetBriefFill();
9709
9884
  }
9710
9885
  function openAddPageComposer() {
9711
9886
  resetAddPageComposer();
9712
9887
  setShowAddPageComposer(true);
9713
9888
  void deps.projectPlugins.refreshPluginsList();
9889
+ void addMenuPanel.ensureCatalogLoaded();
9714
9890
  }
9715
9891
  async function refreshCurrentPageBriefAvailability() {
9716
9892
  const filePath = deps.selectedPageFilePath().trim();
@@ -9985,7 +10161,7 @@ function initAddPageComposer(deps) {
9985
10161
  deps.setIsRunning(true);
9986
10162
  deps.setBottomPanelCollapsed(false);
9987
10163
  deps.setAddPageResultBanner(null);
9988
- await pluginsPanel.syncManifestNow();
10164
+ await addMenuPanel.syncManifestNow();
9989
10165
  deps.getStopStream()?.();
9990
10166
  const abort = submitAddPageStream(
9991
10167
  {
@@ -10075,17 +10251,37 @@ function initAddPageComposer(deps) {
10075
10251
  });
10076
10252
  refs.addPageCreateBtn.onclick(() => submitAddPage(false));
10077
10253
  refs.addPageRouteInput.oninput(({ event }) => {
10078
- const v = event.target.value;
10254
+ routeInputEl = event.target;
10255
+ const v = routeInputEl.value;
10079
10256
  setAddPageRoute(v);
10080
10257
  scheduleRouteCheck(v);
10081
10258
  });
10259
+ refs.addPageRouteInput.onfocus(({ event }) => {
10260
+ routeInputEl = event.target;
10261
+ });
10262
+ refs.addPageContentMd.onfocus(({ event }) => {
10263
+ contentTextareaEl = event.target;
10264
+ trackTextareaSelection(contentTextareaEl);
10265
+ });
10082
10266
  refs.addPageContentMd.oninput(({ event }) => {
10083
10267
  contentTextareaEl = event.target;
10268
+ trackTextareaSelection(contentTextareaEl);
10084
10269
  setAddPageContentMd(contentTextareaEl.value);
10085
10270
  updateDesignHint();
10086
10271
  });
10272
+ refs.addPageContentMd.addEventListener(
10273
+ "select",
10274
+ (ev) => {
10275
+ trackTextareaSelection(ev.event.target);
10276
+ }
10277
+ );
10278
+ refs.addPageDesignMd.onfocus(({ event }) => {
10279
+ designTextareaEl = event.target;
10280
+ trackDesignTextareaSelection(designTextareaEl);
10281
+ });
10087
10282
  refs.addPageDesignMd.oninput(({ event }) => {
10088
10283
  designTextareaEl = event.target;
10284
+ trackDesignTextareaSelection(designTextareaEl);
10089
10285
  setAddPageDesignMd(designTextareaEl.value);
10090
10286
  updateDesignHint();
10091
10287
  });
@@ -10146,7 +10342,7 @@ function initAddPageComposer(deps) {
10146
10342
  el.click();
10147
10343
  });
10148
10344
  });
10149
- pluginsPanel.bindPluginsPanelRefs(refs);
10345
+ addMenuPanel.bindAddMenuPanelRefs(refs);
10150
10346
  briefFillPanel.bindBriefFillRefs(refs);
10151
10347
  refs.addPageAttachmentsList.onchange(
10152
10348
  ({ event }) => {
@@ -10199,18 +10395,21 @@ function initAddPageComposer(deps) {
10199
10395
  addPageUploadHint,
10200
10396
  addPageAttachmentRows: addPageAttachmentDisplayRows,
10201
10397
  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
10398
+ showAddMenuPanel: addMenuPanel.showAddMenuPanel,
10399
+ showAddMenuChips: addMenuPanel.showAddMenuChips,
10400
+ addMenuChipRows: addMenuPanel.chipRows,
10401
+ showAddMenuEmptyHint: addMenuPanel.showAddMenuEmptyHint,
10402
+ showAddMenuPopover: addMenuPanel.showAddMenuPopover,
10403
+ addMenuNavRows: addMenuPanel.menuNavRows,
10404
+ addMenuSelectedCategory: addMenuPanel.selectedCategory,
10405
+ addMenuParamCollisionWarning: addMenuPanel.paramCollisionWarning,
10406
+ addMenuRouteParamHint: addMenuPanel.routeParamHint,
10407
+ addMenuRouteParamRows: addMenuPanel.routeParamRows,
10408
+ showAddMenuRouteParams: createMemo(
10409
+ () => addMenuPanel.routeParamRows().length > 0
10212
10410
  ),
10213
- showAddPagePagePluginsEmptyHint: pluginsPanel.showPagePluginsEmptyHint,
10411
+ showAddMenuRouteParamConfirm: addMenuPanel.showRouteParamConfirm,
10412
+ addMenuPendingRouteParamTitle: addMenuPanel.pendingRouteParamTitle,
10214
10413
  addPageIsDynamicRoute,
10215
10414
  addPageCreateDisabled,
10216
10415
  showAddPageRouteError,
@@ -10240,102 +10439,558 @@ function initAddPageComposer(deps) {
10240
10439
  submitAddPage,
10241
10440
  retryAddPage,
10242
10441
  openPreviewForRoute,
10243
- bindAddPageRefs
10442
+ bindAddPageRefs,
10443
+ refreshAddMenuCatalog: () => addMenuPanel.refreshCatalog()
10244
10444
  };
10245
10445
  }
10246
- function annotationPluginBindings(annotation) {
10247
- if (annotation.pluginBindings?.length) return [...annotation.pluginBindings];
10248
- if (annotation.pluginBinding) return [annotation.pluginBinding];
10249
- return [];
10446
+ function markerAddMenuAttachments(annotation) {
10447
+ return (annotation.addMenuAttachments ?? []).filter(
10448
+ (a2) => a2.annotationId === annotation.id || a2.annotationId === void 0
10449
+ );
10250
10450
  }
10251
- function buildBindingChipLabel(binding, annotationId) {
10252
- const where = binding.scope === "page" ? "anywhere on page" : `marker #${annotationId}`;
10253
- return `${binding.pluginName} · ${binding.contractName} · ${where}`;
10451
+ function enrichAnnotationAddMenuUi(annotation, itemById, pickerOpenForAnnotation, selectedCategory) {
10452
+ const attachments = markerAddMenuAttachments(annotation);
10453
+ const open = !!annotation.addMenuSectionOpen;
10454
+ const pickerOpen = pickerOpenForAnnotation && open;
10455
+ return {
10456
+ addMenuSectionOpen: open,
10457
+ showAddMenuControls: open,
10458
+ showMarkerAddMenuChips: attachments.length > 0,
10459
+ showMarkerAddMenuEmptyHint: attachments.length === 0,
10460
+ markerAddMenuChips: attachments.map((a2) => {
10461
+ const item = itemById.get(a2.itemId);
10462
+ return {
10463
+ rowKey: `${a2.itemId}:${annotation.id}`,
10464
+ itemId: a2.itemId,
10465
+ title: item?.title ?? a2.itemId,
10466
+ subtitle: item?.pluginName ?? item?.id.split(":")[0] ?? ""
10467
+ };
10468
+ }),
10469
+ addMenuSelectedCategory: selectedCategory,
10470
+ showAddMenuItemPicker: pickerOpen,
10471
+ markerPickerNavRows: [],
10472
+ showMarkerPickerLoading: false,
10473
+ showMarkerPickerCatalogEmpty: false,
10474
+ crAddMenuParamCollisionWarning: "",
10475
+ crAddMenuRouteParamHint: "",
10476
+ showCrAddMenuRouteParams: false,
10477
+ crAddMenuRouteParamRows: [],
10478
+ showCrRouteParamConfirm: false,
10479
+ crPendingRouteParamTitle: "",
10480
+ showCrPlannedRoutePanel: false,
10481
+ crPlannedRoute: ""
10482
+ };
10483
+ }
10484
+ function addMenuItemToAnnotation(prev, annotationId, itemId) {
10485
+ return dedupeAddMenuAttachment(prev ?? [], {
10486
+ itemId,
10487
+ annotationId
10488
+ });
10254
10489
  }
10255
- function markerHeadlessPluginOptions(plugins) {
10256
- const installed = plugins.filter(
10257
- (p) => p.kind === "headless" && p.installed && p.showInAddPagePicker !== false && p.contracts.length > 0
10490
+ function removeMenuItemFromAnnotation(prev, annotationId, itemId) {
10491
+ return removeAddMenuAttachment(prev ?? [], itemId, annotationId);
10492
+ }
10493
+ function initVisualAddMenuController(deps) {
10494
+ const [catalogItems, setCatalogItems] = createSignal([]);
10495
+ const [catalogCategories, setCatalogCategories] = createSignal([]);
10496
+ const [catalogLoading, setCatalogLoading] = createSignal(false);
10497
+ const [catalogLoaded, setCatalogLoaded] = createSignal(false);
10498
+ let catalogRefreshPromise = null;
10499
+ const [pickerAnnotationId, setPickerAnnotationId] = createSignal(void 0);
10500
+ const [pickerCategory, setPickerCategory] = createSignal("");
10501
+ const [pickerSubCategory, setPickerSubCategory] = createSignal(
10502
+ null
10503
+ );
10504
+ const [requestAttachments, setRequestAttachments] = createSignal([]);
10505
+ const [routeMigration, setRouteMigration] = createSignal(null);
10506
+ const [plannedRoute, setPlannedRoute] = createSignal("");
10507
+ const [paramCollisionWarning, setParamCollisionWarning] = createSignal("");
10508
+ const [routeParamHint, setRouteParamHint] = createSignal("");
10509
+ const [routeSegmentNames, setRouteSegmentNames] = createSignal([]);
10510
+ const [pendingRouteParamItem, setPendingRouteParamItem] = createSignal(null);
10511
+ const [pendingRouteParamAnnotationId, setPendingRouteParamAnnotationId] = createSignal(null);
10512
+ const [showRouteParamConfirm, setShowRouteParamConfirm] = createSignal(false);
10513
+ const itemById = createMemo(() => {
10514
+ const map = /* @__PURE__ */ new Map();
10515
+ for (const item of catalogItems()) map.set(item.id, item);
10516
+ return map;
10517
+ });
10518
+ const pickerPanelOpen = createMemo(
10519
+ () => pickerAnnotationId() !== void 0
10520
+ );
10521
+ const pickerNavRows = createMemo(
10522
+ () => buildAddMenuNavRows(
10523
+ catalogCategories(),
10524
+ pickerPanelOpen(),
10525
+ pickerCategory(),
10526
+ pickerSubCategory()
10527
+ )
10528
+ );
10529
+ const markerPickerNavRowsForView = createMemo(
10530
+ () => pickerNavRows().map(stripMarkerAddMenuNavRowForPage)
10531
+ );
10532
+ const markerPickerNavRowsForVideo = markerPickerNavRowsForView;
10533
+ const markerPickerNavRowsForSnapshot = markerPickerNavRowsForView;
10534
+ const showMarkerPickerCatalogEmpty = createMemo(() => {
10535
+ const id = pickerAnnotationId();
10536
+ if (id === void 0 || id === null) return false;
10537
+ return !catalogLoading() && catalogItems().length === 0;
10538
+ });
10539
+ const showMarkerPickerLoading = createMemo(() => {
10540
+ const id = pickerAnnotationId();
10541
+ if (id === void 0 || id === null) return false;
10542
+ return catalogLoading();
10543
+ });
10544
+ const markerPickerSnapshot = createMemo(() => ({
10545
+ pickerId: pickerAnnotationId(),
10546
+ category: pickerCategory(),
10547
+ subCategory: pickerSubCategory(),
10548
+ navVisual: markerPickerNavRowsForView(),
10549
+ navVideo: markerPickerNavRowsForVideo(),
10550
+ navSnapshot: markerPickerNavRowsForSnapshot(),
10551
+ loading: showMarkerPickerLoading(),
10552
+ empty: showMarkerPickerCatalogEmpty()
10553
+ }));
10554
+ createMemo(
10555
+ () => requestAttachments().map((a2) => {
10556
+ const item = itemById().get(a2.itemId);
10557
+ return {
10558
+ rowKey: `req:${a2.itemId}`,
10559
+ itemId: a2.itemId,
10560
+ title: item?.title ?? a2.itemId,
10561
+ subtitle: item?.pluginName ?? item?.id.split(":")[0] ?? ""
10562
+ };
10563
+ })
10564
+ );
10565
+ const routeParamRows = createMemo(
10566
+ () => routeSegmentNames().map((name) => ({ rowKey: name, name }))
10567
+ );
10568
+ async function refreshCatalog() {
10569
+ if (catalogRefreshPromise) return catalogRefreshPromise;
10570
+ catalogRefreshPromise = (async () => {
10571
+ setCatalogLoading(true);
10572
+ try {
10573
+ const result = await listAddMenuItems();
10574
+ setCatalogItems(result.items);
10575
+ setCatalogCategories(result.categories);
10576
+ if (result.warnings.length > 0) {
10577
+ console.warn("[aiditor] Add Menu catalog warnings:", result.warnings);
10578
+ }
10579
+ } catch (err) {
10580
+ console.error("[aiditor] listAddMenuItems failed:", err);
10581
+ setCatalogItems([]);
10582
+ setCatalogCategories([]);
10583
+ } finally {
10584
+ setCatalogLoaded(true);
10585
+ setCatalogLoading(false);
10586
+ catalogRefreshPromise = null;
10587
+ }
10588
+ })();
10589
+ return catalogRefreshPromise;
10590
+ }
10591
+ async function ensureCatalogLoaded() {
10592
+ if (catalogLoading()) return;
10593
+ if (catalogLoaded() && catalogCategories().length > 0) return;
10594
+ await refreshCatalog();
10595
+ }
10596
+ async function updateRouteHelpers(attachments) {
10597
+ const resolved = [];
10598
+ for (const a2 of attachments) {
10599
+ const item = itemById().get(a2.itemId);
10600
+ if (!item) continue;
10601
+ const params = await resolveContractParamsForItem(
10602
+ deps.projectDir(),
10603
+ item
10604
+ );
10605
+ if (params) resolved.push(params);
10606
+ }
10607
+ const collisions = detectRequiredParamCollisions(resolved);
10608
+ setParamCollisionWarning(collisions[0]?.message ?? "");
10609
+ const segments = collectRouteSegmentNames(resolved);
10610
+ setRouteSegmentNames(segments);
10611
+ const route = plannedRoute() || deps.getCurrentRoute();
10612
+ const suggested = suggestMissingRouteSegments(route, segments);
10613
+ setRouteParamHint(
10614
+ suggested && suggested !== route ? `Suggested route segments: ${suggested}` : ""
10615
+ );
10616
+ }
10617
+ async function openPicker(annotationId) {
10618
+ setPickerAnnotationId(annotationId);
10619
+ setPickerCategory("");
10620
+ setPickerSubCategory(null);
10621
+ await ensureCatalogLoaded();
10622
+ if (catalogCategories().length === 0 && !catalogLoading()) {
10623
+ await refreshCatalog();
10624
+ }
10625
+ }
10626
+ function closePicker() {
10627
+ setPickerAnnotationId(void 0);
10628
+ setPickerCategory("");
10629
+ setPickerSubCategory(null);
10630
+ }
10631
+ async function commitItem(item, updateAnnotation, annotationId) {
10632
+ if (annotationId) {
10633
+ updateAnnotation(annotationId, (a2) => ({
10634
+ ...a2,
10635
+ addMenuSectionOpen: true,
10636
+ pickerOpen: false,
10637
+ addMenuAttachments: addMenuItemToAnnotation(
10638
+ a2.addMenuAttachments,
10639
+ annotationId,
10640
+ item.id
10641
+ )
10642
+ }));
10643
+ } else {
10644
+ setRequestAttachments((prev) => {
10645
+ const exists = prev.some(
10646
+ (a2) => a2.itemId === item.id && !a2.annotationId
10647
+ );
10648
+ if (exists) return prev;
10649
+ return [...prev, { itemId: item.id }];
10650
+ });
10651
+ closePicker();
10652
+ }
10653
+ const all = [
10654
+ ...requestAttachments(),
10655
+ ...annotationId ? [{ itemId: item.id, annotationId }] : [{ itemId: item.id }]
10656
+ ];
10657
+ await updateRouteHelpers(all);
10658
+ }
10659
+ async function tryPickItem(item, updateAnnotation, annotationId) {
10660
+ const params = await resolveContractParamsForItem(deps.projectDir(), item);
10661
+ const hasRouteParams = !!params && params.allParams.length > 0;
10662
+ const current = deps.getCurrentRoute();
10663
+ if (hasRouteParams && current && !routeHasAnyDynamicSegment(current)) {
10664
+ setPendingRouteParamItem(item);
10665
+ setPendingRouteParamAnnotationId(annotationId);
10666
+ setShowRouteParamConfirm(true);
10667
+ return;
10668
+ }
10669
+ await commitItem(item, updateAnnotation, annotationId);
10670
+ }
10671
+ async function confirmRouteParamPick(updateAnnotation) {
10672
+ const item = pendingRouteParamItem();
10673
+ if (!item) return;
10674
+ const current = deps.getCurrentRoute() || "/";
10675
+ const params = await resolveContractParamsForItem(deps.projectDir(), item);
10676
+ const segments = params ? collectRouteSegmentNames([params]) : [];
10677
+ const planned = suggestMissingRouteSegments(current, segments) ?? `${current}/[slug]`;
10678
+ setPlannedRoute(planned);
10679
+ setRouteMigration({
10680
+ currentRoute: current,
10681
+ plannedRoute: planned,
10682
+ reason: "add-menu-route-param-component",
10683
+ triggeringItemIds: [item.id]
10684
+ });
10685
+ setShowRouteParamConfirm(false);
10686
+ setPendingRouteParamItem(null);
10687
+ const aid = pendingRouteParamAnnotationId();
10688
+ setPendingRouteParamAnnotationId(null);
10689
+ await commitItem(item, updateAnnotation, aid);
10690
+ }
10691
+ function cancelRouteParamPick() {
10692
+ setShowRouteParamConfirm(false);
10693
+ setPendingRouteParamItem(null);
10694
+ setPendingRouteParamAnnotationId(null);
10695
+ }
10696
+ function enrichUi(annotation, _stripMarkerRow) {
10697
+ const pickerOpen = !!annotation.pickerOpen;
10698
+ const cat = annotation.selectedCategory ?? "";
10699
+ const sub = annotation.selectedSubCategory ?? null;
10700
+ const base = enrichAnnotationAddMenuUi(
10701
+ annotation,
10702
+ itemById(),
10703
+ pickerOpen,
10704
+ cat
10705
+ );
10706
+ const navRows = buildAddMenuNavRows(
10707
+ catalogCategories(),
10708
+ pickerOpen,
10709
+ cat,
10710
+ sub
10711
+ ).map(_stripMarkerRow);
10712
+ const hasAttachments = !!annotation.addMenuAttachments && annotation.addMenuAttachments.length > 0;
10713
+ const isPendingRoute = pendingRouteParamAnnotationId() === annotation.id;
10714
+ const showRouteUi = isPendingRoute || hasAttachments && routeMigration() !== null;
10715
+ const showParamsUi = isPendingRoute || hasAttachments && routeParamRows().length > 0;
10716
+ const showConfirm = isPendingRoute && showRouteParamConfirm();
10717
+ return {
10718
+ ...base,
10719
+ showAddMenuItemPicker: pickerOpen,
10720
+ markerPickerNavRows: navRows,
10721
+ showMarkerPickerLoading: pickerOpen && catalogLoading(),
10722
+ showMarkerPickerCatalogEmpty: pickerOpen && !catalogLoading() && catalogItems().length === 0,
10723
+ crAddMenuParamCollisionWarning: showParamsUi ? paramCollisionWarning() : "",
10724
+ crAddMenuRouteParamHint: showParamsUi ? routeParamHint() : "",
10725
+ showCrAddMenuRouteParams: showParamsUi && routeParamRows().length > 0,
10726
+ crAddMenuRouteParamRows: showParamsUi ? routeParamRows() : [],
10727
+ showCrRouteParamConfirm: showConfirm,
10728
+ crPendingRouteParamTitle: isPendingRoute && pendingRouteParamItem() ? pendingRouteParamItem().title : "",
10729
+ showCrPlannedRoutePanel: showRouteUi,
10730
+ crPlannedRoute: showRouteUi ? plannedRoute() : ""
10731
+ };
10732
+ }
10733
+ function handleMarkerAddMenuClick(target, annotationId, updateAnnotation) {
10734
+ if (target.classList.contains("annotation-add-menu-toggle")) {
10735
+ updateAnnotation(annotationId, (a2) => ({
10736
+ ...a2,
10737
+ addMenuSectionOpen: !a2.addMenuSectionOpen
10738
+ }));
10739
+ return true;
10740
+ }
10741
+ if (target.closest("[data-marker-add-menu-open]")) {
10742
+ updateAnnotation(annotationId, (a2) => ({
10743
+ ...a2,
10744
+ addMenuSectionOpen: true,
10745
+ pickerOpen: true,
10746
+ selectedCategory: "",
10747
+ selectedSubCategory: null
10748
+ }));
10749
+ void ensureCatalogLoaded();
10750
+ return true;
10751
+ }
10752
+ if (target.closest("[data-marker-add-menu-close]")) {
10753
+ updateAnnotation(annotationId, (a2) => ({
10754
+ ...a2,
10755
+ pickerOpen: false
10756
+ }));
10757
+ return true;
10758
+ }
10759
+ if (target.closest("[data-marker-add-menu-manage]")) {
10760
+ deps.openProjectSettings();
10761
+ return true;
10762
+ }
10763
+ const catBtn = target.closest(
10764
+ "[data-marker-add-menu-category]"
10765
+ );
10766
+ if (catBtn?.dataset.category) {
10767
+ updateAnnotation(annotationId, (a2) => ({
10768
+ ...a2,
10769
+ selectedCategory: catBtn.dataset.category,
10770
+ selectedSubCategory: null
10771
+ }));
10772
+ return true;
10773
+ }
10774
+ const subBtn = target.closest(
10775
+ "[data-marker-add-menu-subcategory]"
10776
+ );
10777
+ if (subBtn?.dataset.subcategory !== void 0) {
10778
+ updateAnnotation(annotationId, (a2) => ({
10779
+ ...a2,
10780
+ selectedSubCategory: subBtn.dataset.subcategory || null
10781
+ }));
10782
+ return true;
10783
+ }
10784
+ const pickBtn = target.closest(
10785
+ "[data-marker-add-menu-pick]"
10786
+ );
10787
+ if (pickBtn?.dataset.itemId) {
10788
+ const item = itemById().get(pickBtn.dataset.itemId);
10789
+ if (item) void tryPickItem(item, updateAnnotation, annotationId);
10790
+ return true;
10791
+ }
10792
+ const removeBtn = target.closest(
10793
+ "[data-marker-add-menu-remove]"
10794
+ );
10795
+ if (removeBtn?.dataset.itemId) {
10796
+ updateAnnotation(annotationId, (a2) => ({
10797
+ ...a2,
10798
+ addMenuAttachments: removeMenuItemFromAnnotation(
10799
+ a2.addMenuAttachments,
10800
+ annotationId,
10801
+ removeBtn.dataset.itemId
10802
+ )
10803
+ }));
10804
+ return true;
10805
+ }
10806
+ return false;
10807
+ }
10808
+ function handleRequestAddMenuClick(target, updateAnnotation) {
10809
+ if (target.closest("[data-cr-add-menu-open]")) {
10810
+ void openPicker(null);
10811
+ return;
10812
+ }
10813
+ if (target.closest("[data-cr-add-menu-close]")) {
10814
+ closePicker();
10815
+ return;
10816
+ }
10817
+ if (target.closest("[data-cr-add-menu-manage]")) {
10818
+ deps.openProjectSettings();
10819
+ return;
10820
+ }
10821
+ const catBtn = target.closest(
10822
+ "[data-cr-add-menu-category]"
10823
+ );
10824
+ if (catBtn?.dataset.category) {
10825
+ setPickerCategory(catBtn.dataset.category);
10826
+ setPickerSubCategory(null);
10827
+ return;
10828
+ }
10829
+ const subBtn = target.closest(
10830
+ "[data-cr-add-menu-subcategory]"
10831
+ );
10832
+ if (subBtn?.dataset.subcategory !== void 0) {
10833
+ setPickerSubCategory(subBtn.dataset.subcategory || null);
10834
+ return;
10835
+ }
10836
+ const pickBtn = target.closest(
10837
+ "[data-cr-add-menu-pick]"
10838
+ );
10839
+ if (pickBtn?.dataset.itemId) {
10840
+ const item = itemById().get(pickBtn.dataset.itemId);
10841
+ if (item) void tryPickItem(item, updateAnnotation, null);
10842
+ return;
10843
+ }
10844
+ const removeBtn = target.closest(
10845
+ "[data-cr-add-menu-remove]"
10846
+ );
10847
+ if (removeBtn?.dataset.itemId) {
10848
+ setRequestAttachments(
10849
+ (prev) => prev.filter((a2) => a2.itemId !== removeBtn.dataset.itemId)
10850
+ );
10851
+ return;
10852
+ }
10853
+ if (target.closest("[data-cr-route-param-confirm]")) {
10854
+ void confirmRouteParamPick(updateAnnotation);
10855
+ return;
10856
+ }
10857
+ if (target.closest("[data-cr-route-param-cancel]")) {
10858
+ setShowRouteParamConfirm(false);
10859
+ setPendingRouteParamItem(null);
10860
+ }
10861
+ }
10862
+ const showRequestAddMenuPicker = createMemo(
10863
+ () => pickerAnnotationId() === null
10258
10864
  );
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
10865
+ createMemo(
10866
+ () => showRequestAddMenuPicker() && catalogLoading()
10291
10867
  );
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)
10868
+ createMemo(
10869
+ () => showRequestAddMenuPicker() && !catalogLoading() && catalogItems().length === 0
10300
10870
  );
10301
- }
10302
- function enrichAnnotationBindingUi(annotation, plugins) {
10303
- const bindings = annotationPluginBindings(annotation);
10304
- const pickerPlugin = annotation.bindingPickerPlugin ?? "";
10305
- const open = !!annotation.bindingSectionOpen;
10306
10871
  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
10872
+ refreshCatalog,
10873
+ enrichUi,
10874
+ markerPickerSnapshot,
10875
+ handleMarkerAddMenuClick,
10876
+ handleRequestAddMenuClick,
10877
+ requestAttachments,
10878
+ routeMigration,
10879
+ plannedRoute,
10880
+ setPlannedRoute,
10881
+ catalogLoading,
10882
+ markerPickerNavRowsForView,
10883
+ markerPickerNavRowsForVideo,
10884
+ markerPickerNavRowsForSnapshot,
10885
+ markerPickerSelectedCategory: pickerCategory,
10886
+ showMarkerPickerLoading,
10887
+ showMarkerPickerCatalogEmpty,
10888
+ paramCollisionWarning,
10889
+ routeParamHint,
10890
+ routeParamRows,
10891
+ showRouteParams: createMemo(() => routeParamRows().length > 0),
10892
+ showRouteParamConfirm,
10893
+ pendingRouteParamTitle: createMemo(
10894
+ () => pendingRouteParamItem()?.title ?? ""
10324
10895
  ),
10325
- showMarkerContractPicker: pickerPlugin.length > 0,
10326
- markerPickerPluginLabel: pickerPlugin ? `Components from ${pickerPlugin}` : ""
10896
+ confirmRouteParamPick,
10897
+ cancelRouteParamPick,
10898
+ reset: () => {
10899
+ setRequestAttachments([]);
10900
+ setRouteMigration(null);
10901
+ setPlannedRoute("");
10902
+ closePicker();
10903
+ }
10327
10904
  };
10328
10905
  }
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
- };
10906
+ const CATALOG = [
10907
+ {
10908
+ pluginName: "wix-server-client",
10909
+ packageName: "@jay-framework/wix-server-client",
10910
+ description: "Wix API client and authentication",
10911
+ kind: "service-only",
10912
+ requires: [],
10913
+ showInAddPagePicker: false,
10914
+ configFiles: ["config/.wix.yaml"]
10915
+ },
10916
+ {
10917
+ pluginName: "wix-cart",
10918
+ packageName: "@jay-framework/wix-cart",
10919
+ description: "Shopping cart headless components",
10920
+ kind: "headless",
10921
+ requires: ["wix-server-client"],
10922
+ showInAddPagePicker: true
10923
+ },
10924
+ {
10925
+ pluginName: "wix-stores",
10926
+ packageName: "@jay-framework/wix-stores",
10927
+ description: "Wix Stores product search, product page, categories",
10928
+ kind: "headless",
10929
+ requires: ["wix-server-client", "wix-cart"],
10930
+ showInAddPagePicker: true,
10931
+ configFiles: ["config/.wix-stores.yaml"]
10932
+ },
10933
+ {
10934
+ pluginName: "wix-stores-v1",
10935
+ packageName: "@jay-framework/wix-stores-v1",
10936
+ description: "Wix Stores v1 catalog API",
10937
+ kind: "headless",
10938
+ requires: ["wix-server-client", "wix-cart"],
10939
+ showInAddPagePicker: true
10940
+ },
10941
+ {
10942
+ pluginName: "wix-data",
10943
+ packageName: "@jay-framework/wix-data",
10944
+ description: "Wix Data collections CMS",
10945
+ kind: "headless",
10946
+ requires: ["wix-server-client"],
10947
+ showInAddPagePicker: true,
10948
+ configFiles: ["config/.wix-data.yaml"]
10949
+ },
10950
+ {
10951
+ pluginName: "wix-media",
10952
+ packageName: "@jay-framework/wix-media",
10953
+ description: "Wix media service (no page components)",
10954
+ kind: "service-only",
10955
+ requires: ["wix-server-client"],
10956
+ showInAddPagePicker: true
10957
+ },
10958
+ {
10959
+ pluginName: "ui-kit",
10960
+ packageName: "@jay-framework/ui-kit",
10961
+ description: "Jay UI kit headless components",
10962
+ kind: "headless",
10963
+ requires: [],
10964
+ showInAddPagePicker: true
10965
+ },
10966
+ {
10967
+ pluginName: "gemini-agent",
10968
+ packageName: "@jay-framework/gemini-agent",
10969
+ description: "Gemini AI agent plugin",
10970
+ kind: "headless",
10971
+ requires: [],
10972
+ showInAddPagePicker: true,
10973
+ configFiles: ["config/.gemini-agent.yaml"]
10974
+ },
10975
+ {
10976
+ pluginName: "webmcp",
10977
+ packageName: "@jay-framework/webmcp",
10978
+ description: "Web MCP tooling",
10979
+ kind: "tooling",
10980
+ requires: [],
10981
+ showInAddPagePicker: false
10982
+ },
10983
+ {
10984
+ pluginName: "aiditor",
10985
+ packageName: "@jay-framework/aiditor",
10986
+ description: "AIditor self plugin",
10987
+ kind: "tooling",
10988
+ requires: [],
10989
+ showInAddPagePicker: false
10990
+ }
10991
+ ];
10992
+ function getCatalogEntry(pluginName) {
10993
+ return CATALOG.find((e2) => e2.pluginName === pluginName);
10339
10994
  }
10340
10995
  function buildPluginInstallAgentNotes(entry, installError, projectDirHint) {
10341
10996
  const requires = entry.requires.length > 0 ? entry.requires.join(", ") : "(none — install this plugin only)";
@@ -10367,6 +11022,7 @@ function buildPluginInstallAgentNotes(entry, installError, projectDirHint) {
10367
11022
  "## Local / monorepo alternative",
10368
11023
  wixPortal,
10369
11024
  "- Read `agent-kit/plugin/INSTRUCTIONS.md` for plugin setup conventions.",
11025
+ "- To contribute Add Menu catalog items, read `agent-kit/plugin/aiditor-add-menu.md`.",
10370
11026
  "",
10371
11027
  "When finished, summarize what you installed and any config files the user must edit (e.g. config/.wix.yaml)."
10372
11028
  ].filter((line) => line !== "").join("\n");
@@ -10380,6 +11036,50 @@ const VALID_INSTALL_PREFIXES = ["portal:", "file:", "workspace:"];
10380
11036
  function isValidInstallSpec(spec) {
10381
11037
  return VALID_INSTALL_PREFIXES.some((p) => spec.startsWith(p));
10382
11038
  }
11039
+ const listJayPluginsAction = createActionCaller("aiditor.listJayPlugins", "GET");
11040
+ createActionCaller("aiditor.listPluginContracts", "GET");
11041
+ const checkPluginUpdateAction = createActionCaller("aiditor.checkPluginUpdate", "GET");
11042
+ const ensureProjectPluginAction = createStreamCaller("aiditor.ensureProjectPlugin");
11043
+ createActionCaller("aiditor.getPluginSetupStatus", "GET");
11044
+ const rerunPluginSetupAction = createActionCaller("aiditor.rerunPluginSetup", "GET");
11045
+ createActionCaller("aiditor.syncAddPagePluginManifest", "GET");
11046
+ const writePluginSourceAction = createActionCaller("aiditor.writePluginSource", "GET");
11047
+ async function listJayPlugins(refreshSetupStatus = false) {
11048
+ return listJayPluginsAction({ refreshSetupStatus });
11049
+ }
11050
+ async function checkPluginUpdate(pluginName) {
11051
+ return checkPluginUpdateAction({ pluginName });
11052
+ }
11053
+ function ensureProjectPluginStream(input, callbacks) {
11054
+ let aborted = false;
11055
+ const abort = () => {
11056
+ aborted = true;
11057
+ };
11058
+ void (async () => {
11059
+ try {
11060
+ for await (const chunk of ensureProjectPluginAction(input)) {
11061
+ if (aborted) break;
11062
+ callbacks.onChunk(chunk);
11063
+ if (chunk.type === "done" || chunk.type === "error") {
11064
+ callbacks.onEnd();
11065
+ return;
11066
+ }
11067
+ }
11068
+ if (!aborted) callbacks.onEnd();
11069
+ } catch (err) {
11070
+ if (!aborted) {
11071
+ callbacks.onError(err instanceof Error ? err : new Error(String(err)));
11072
+ }
11073
+ }
11074
+ })();
11075
+ return abort;
11076
+ }
11077
+ async function rerunPluginSetup(pluginName, force) {
11078
+ return rerunPluginSetupAction({ pluginName, force });
11079
+ }
11080
+ async function writePluginSource(input) {
11081
+ return writePluginSourceAction(input);
11082
+ }
10383
11083
  function initProjectPluginsManager(deps) {
10384
11084
  const [pluginsList, setPluginsList] = createSignal([]);
10385
11085
  const [pluginsLoaded, setPluginsLoaded] = createSignal(false);
@@ -10559,6 +11259,7 @@ function initProjectPluginsManager(deps) {
10559
11259
  updateAvailable: false
10560
11260
  });
10561
11261
  void refreshPluginsList(true).then(() => deps.loadProjectInfo());
11262
+ deps.onInstallComplete?.(pluginName);
10562
11263
  setShowReloadPreviewBanner(true);
10563
11264
  }
10564
11265
  },
@@ -10759,20 +11460,31 @@ function serializeGeometry(a2) {
10759
11460
  return { kind: "area", x: a2.x, y: a2.y, w: a2.w, h: a2.h };
10760
11461
  return { kind: "arrow", x1: a2.x1, y1: a2.y1, x2: a2.x2, y2: a2.y2 };
10761
11462
  }
10762
- function toNotesPayload(annotations, breakpoint) {
11463
+ function serializeAnnotationNotesFields(a2) {
11464
+ if (a2.addMenuAttachments?.length) {
11465
+ return { addMenuAttachments: a2.addMenuAttachments };
11466
+ }
11467
+ if (a2.pluginBindings?.length) {
11468
+ return { pluginBindings: a2.pluginBindings };
11469
+ }
11470
+ return {};
11471
+ }
11472
+ function toNotesPayload(annotations, breakpoint, requestLevel) {
10763
11473
  return {
10764
11474
  version: AIDITOR_NOTES_VERSION,
10765
11475
  breakpoint,
11476
+ ...requestLevel?.addMenuAttachments?.length ? { addMenuAttachments: requestLevel.addMenuAttachments } : {},
11477
+ ...requestLevel?.routeMigration ? { routeMigration: requestLevel.routeMigration } : {},
10766
11478
  annotations: annotations.map((a2) => ({
10767
11479
  id: a2.id,
10768
11480
  mode: a2.kind,
10769
11481
  instruction: a2.instruction.trim(),
10770
11482
  geometry: serializeGeometry(a2),
10771
- ...a2.pluginBindings?.length ? { pluginBindings: a2.pluginBindings } : {}
11483
+ ...serializeAnnotationNotesFields(a2)
10772
11484
  }))
10773
11485
  };
10774
11486
  }
10775
- function toVideoNotesPayload(annotations, videoMeta, notesContext) {
11487
+ function toVideoNotesPayload(annotations, videoMeta, notesContext, requestLevel) {
10776
11488
  const baseOrigin = typeof window !== "undefined" ? window.location.origin : JAY_DEV_URL_DEFAULT;
10777
11489
  const fallback = fallbackPreviewContextFromBar(notesContext.fallbackRenderedUrl, notesContext.fallbackRoute, baseOrigin);
10778
11490
  const navLog = notesContext.previewNavLog.length > 0 ? notesContext.previewNavLog : void 0;
@@ -10782,6 +11494,8 @@ function toVideoNotesPayload(annotations, videoMeta, notesContext) {
10782
11494
  videoMeta,
10783
11495
  previewNavLog: navLog,
10784
11496
  breakpoint: notesContext.breakpoint,
11497
+ ...requestLevel?.addMenuAttachments?.length ? { addMenuAttachments: requestLevel.addMenuAttachments } : {},
11498
+ ...requestLevel?.routeMigration ? { routeMigration: requestLevel.routeMigration } : {},
10785
11499
  annotations: annotations.map((a2) => {
10786
11500
  const ctx = resolvePreviewContextAtTime(a2.timeSec, navLog, fallback);
10787
11501
  return {
@@ -10792,7 +11506,7 @@ function toVideoNotesPayload(annotations, videoMeta, notesContext) {
10792
11506
  timeSec: a2.timeSec,
10793
11507
  previewUrlAtTime: ctx.href,
10794
11508
  pagePathAtTime: ctx.locationPath,
10795
- ...a2.pluginBindings?.length ? { pluginBindings: a2.pluginBindings } : {}
11509
+ ...serializeAnnotationNotesFields(a2)
10796
11510
  };
10797
11511
  })
10798
11512
  };
@@ -11020,12 +11734,22 @@ function aiditorConstructor(_props, refs) {
11020
11734
  const [visualSubmitError, setVisualSubmitError] = createSignal("");
11021
11735
  const [showProjectSettingsModal, setShowProjectSettingsModal] = createSignal(false);
11022
11736
  let runPluginInstallAgent;
11737
+ let refreshAddMenuCatalog;
11023
11738
  const projectPlugins = initProjectPluginsManager({
11024
11739
  loadProjectInfo: () => loadProjectInfo(),
11025
11740
  fixInstallWithAgent: (pluginName, installError) => {
11026
11741
  runPluginInstallAgent?.(pluginName, installError);
11742
+ },
11743
+ onInstallComplete: () => {
11744
+ void refreshAddMenuCatalog?.();
11027
11745
  }
11028
11746
  });
11747
+ const visualAddMenu = initVisualAddMenuController({
11748
+ projectDir: () => projectDir(),
11749
+ projectPlugins,
11750
+ openProjectSettings,
11751
+ getCurrentRoute: () => selectedPageUrl() || "/"
11752
+ });
11029
11753
  function openProjectSettings() {
11030
11754
  setShowProjectSettingsModal(true);
11031
11755
  void projectPlugins.refreshPluginsList();
@@ -11307,9 +12031,28 @@ function aiditorConstructor(_props, refs) {
11307
12031
  return true;
11308
12032
  return list.some((a2) => a2.instruction.trim().length === 0);
11309
12033
  });
12034
+ createMemo(() => {
12035
+ return {
12036
+ show: false,
12037
+ annotationId: "",
12038
+ leftPct: 0,
12039
+ topPct: 0,
12040
+ navRows: [],
12041
+ selectedCategory: "",
12042
+ loading: false,
12043
+ empty: false
12044
+ };
12045
+ });
12046
+ const showMarkerAddMenuPopover = createMemo(() => false);
12047
+ const markerAddMenuPopoverLeftPct = createMemo(() => 0);
12048
+ const markerAddMenuPopoverTopPct = createMemo(() => 0);
12049
+ const markerAddMenuPickerAnnotationId = createMemo(() => "");
12050
+ const markerAddMenuNavRows = createMemo(() => []);
12051
+ const markerAddMenuSelectedCategory = createMemo(() => "");
12052
+ const showMarkerAddMenuPickerLoading = createMemo(() => false);
12053
+ const showMarkerAddMenuPickerCatalogEmpty = createMemo(() => false);
11310
12054
  const visualAnnotationRows = createMemo(() => {
11311
12055
  const runDisabled = visualRunDisabled();
11312
- const plugins = projectPlugins.pluginsList();
11313
12056
  return visualAnnotations().map((a2) => {
11314
12057
  const { nx, ny } = bubbleAnchorNorm(a2);
11315
12058
  const leftPct = nx * 100;
@@ -11331,10 +12074,11 @@ function aiditorConstructor(_props, refs) {
11331
12074
  popoverFlipX: flipX,
11332
12075
  popoverFlipY: flipY,
11333
12076
  attachmentChips,
11334
- ...enrichAnnotationBindingUi(a2, plugins)
12077
+ ...visualAddMenu.enrichUi(a2, stripMarkerPickerNavRowForVisual)
11335
12078
  };
11336
12079
  });
11337
12080
  });
12081
+ const showCrPlannedRoutePanel = createMemo(() => visualAddMenu.routeMigration() !== null);
11338
12082
  const recordingDraftPopoverRow = createMemo(() => {
11339
12083
  const a2 = recordingDraftAnnotation();
11340
12084
  if (!a2)
@@ -11462,7 +12206,6 @@ function aiditorConstructor(_props, refs) {
11462
12206
  const t = videoReviewCurrentTimeSec();
11463
12207
  const list = fullList.filter((a2) => isVideoAnnotationAtPlayhead(t, a2.timeSec));
11464
12208
  const runDisabled = fullList.length > 0 && (isRunning() || fullList.some((a2) => a2.instruction.trim().length === 0));
11465
- const plugins = projectPlugins.pluginsList();
11466
12209
  return list.map((a2) => {
11467
12210
  const { nx, ny } = bubbleAnchorNorm(a2);
11468
12211
  const leftPct = nx * 100;
@@ -11485,7 +12228,7 @@ function aiditorConstructor(_props, refs) {
11485
12228
  popoverFlipX: flipX,
11486
12229
  popoverFlipY: flipY,
11487
12230
  attachmentChips,
11488
- ...enrichAnnotationBindingUi(a2, plugins)
12231
+ ...visualAddMenu.enrichUi(a2, stripMarkerPickerNavRowForVideo)
11489
12232
  };
11490
12233
  });
11491
12234
  });
@@ -11611,7 +12354,6 @@ function aiditorConstructor(_props, refs) {
11611
12354
  const snapshotAreaDraftWidthPct = createMemo(() => (snapshotAreaDraftNorm()?.w ?? 0) * 100);
11612
12355
  const snapshotAreaDraftHeightPct = createMemo(() => (snapshotAreaDraftNorm()?.h ?? 0) * 100);
11613
12356
  const snapshotAnnotationItems = createMemo(() => {
11614
- const plugins = projectPlugins.pluginsList();
11615
12357
  return snapshotAnnotations().map((a2) => {
11616
12358
  const { nx, ny } = bubbleAnchorNorm(a2);
11617
12359
  const leftPct = nx * 100;
@@ -11634,7 +12376,7 @@ function aiditorConstructor(_props, refs) {
11634
12376
  popoverFlipX: flipX,
11635
12377
  popoverFlipY: flipY,
11636
12378
  attachmentChips,
11637
- ...enrichAnnotationBindingUi(a2, plugins)
12379
+ ...visualAddMenu.enrichUi(a2, stripMarkerPickerNavRowForSnapshot)
11638
12380
  };
11639
12381
  });
11640
12382
  });
@@ -11714,6 +12456,7 @@ function aiditorConstructor(_props, refs) {
11714
12456
  setAreaDraft(null);
11715
12457
  setArrowPending(null);
11716
12458
  setVisualSubmitError("");
12459
+ visualAddMenu.reset();
11717
12460
  cancelAreaDragListeners();
11718
12461
  setVisualTool("none");
11719
12462
  }
@@ -11901,68 +12644,27 @@ function aiditorConstructor(_props, refs) {
11901
12644
  function updateVideoAsVisual(id, updater) {
11902
12645
  updateVideoAnnotation(id, (a2) => updater(a2));
11903
12646
  }
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);
12647
+ function updateMarkerAddMenuAnnotation(id, updater) {
12648
+ if (visualAnnotations().some((a2) => a2.id === id)) {
12649
+ updateVisualAnnotation(id, (a2) => ({ ...a2, ...updater(a2) }));
11953
12650
  return;
11954
12651
  }
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 ?? "");
12652
+ if (videoSessionAnnotations().some((a2) => a2.id === id)) {
12653
+ updateVideoAsVisual(id, (a2) => ({ ...a2, ...updater(a2) }));
11958
12654
  return;
11959
12655
  }
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;
12656
+ if (snapshotAnnotations().some((a2) => a2.id === id)) {
12657
+ updateSnapshotAnnotation(id, (a2) => ({ ...a2, ...updater(a2) }));
11964
12658
  }
11965
12659
  }
12660
+ function changeRequestAddMenuPayload() {
12661
+ const attachments = visualAddMenu.requestAttachments();
12662
+ const migration = visualAddMenu.routeMigration();
12663
+ return {
12664
+ ...attachments.length ? { addMenuAttachments: attachments } : {},
12665
+ ...migration ? { routeMigration: migration } : {}
12666
+ };
12667
+ }
11966
12668
  function setRecordingDraftInstruction(instruction) {
11967
12669
  setRecordingDraftAnnotation((prev) => {
11968
12670
  if (!prev)
@@ -12117,6 +12819,7 @@ function aiditorConstructor(_props, refs) {
12117
12819
  setIsBootstrapping(false);
12118
12820
  paramsCache.clear();
12119
12821
  loadProjectInfo();
12822
+ void refreshAddMenuCatalog?.();
12120
12823
  }).catch((err) => {
12121
12824
  console.error("[aiditor] bootstrap", err);
12122
12825
  setIsBootstrapping(false);
@@ -12296,6 +12999,10 @@ function aiditorConstructor(_props, refs) {
12296
12999
  selectedPageUrl,
12297
13000
  setCurrentPageHasBrief
12298
13001
  });
13002
+ refreshAddMenuCatalog = () => {
13003
+ void addPage.refreshAddMenuCatalog();
13004
+ void visualAddMenu.refreshCatalog();
13005
+ };
12299
13006
  refreshPageBriefRef.current = () => {
12300
13007
  void addPage.refreshCurrentPageBriefAvailability();
12301
13008
  };
@@ -12545,6 +13252,10 @@ function aiditorConstructor(_props, refs) {
12545
13252
  });
12546
13253
  refs.visualAnnotationRows.annotationRow.oninput(({ event }) => {
12547
13254
  const t = event.target;
13255
+ if (t.classList.contains("cr-planned-route-input")) {
13256
+ visualAddMenu.setPlannedRoute(t.value);
13257
+ return;
13258
+ }
12548
13259
  if (t.tagName !== "TEXTAREA" || !t.classList.contains("visual-annotation-instruction"))
12549
13260
  return;
12550
13261
  const id = t.dataset.annotationId;
@@ -12617,19 +13328,31 @@ function aiditorConstructor(_props, refs) {
12617
13328
  tryJayRefExec2(refs.visualAttachFileInput, (el) => el.click());
12618
13329
  }
12619
13330
  });
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
- });
12628
13331
  refs.visualAnnotationRows.annotationRow.onclick(({ event, viewState }) => {
12629
13332
  const raw = event.target;
12630
- if (raw.closest(".annotation-binding")) {
13333
+ if (raw.closest(".annotation-add-menu")) {
12631
13334
  event.preventDefault();
12632
- handleBindingUiEvent(raw, viewState.id, updateVisualAnnotation);
13335
+ const handled = visualAddMenu.handleMarkerAddMenuClick(raw, viewState.id, updateMarkerAddMenuAnnotation);
13336
+ if (!handled) {
13337
+ const paramBtn = raw.closest("[data-cr-route-param]");
13338
+ if (paramBtn?.dataset.param) {
13339
+ const name = paramBtn.dataset.param;
13340
+ visualAddMenu.setPlannedRoute((route) => {
13341
+ const base = route || selectedPageUrl() || "/";
13342
+ if (base.includes(`[${name}]`))
13343
+ return base;
13344
+ return base.endsWith("/") ? `${base}[${name}]` : `${base}/[${name}]`;
13345
+ });
13346
+ return;
13347
+ }
13348
+ if (raw.closest("[data-cr-route-param-confirm]")) {
13349
+ void visualAddMenu.confirmRouteParamPick(updateMarkerAddMenuAnnotation);
13350
+ return;
13351
+ }
13352
+ if (raw.closest("[data-cr-route-param-cancel]")) {
13353
+ visualAddMenu.cancelRouteParamPick();
13354
+ }
13355
+ }
12633
13356
  return;
12634
13357
  }
12635
13358
  const t = raw.closest("button");
@@ -12678,7 +13401,7 @@ function aiditorConstructor(_props, refs) {
12678
13401
  return;
12679
13402
  }
12680
13403
  const list = visualAnnotations();
12681
- const notesJson = serializeAiditorNotes(toNotesPayload(list, previewBreakpoint()));
13404
+ const notesJson = serializeAiditorNotes(toNotesPayload(list, previewBreakpoint(), changeRequestAddMenuPayload()));
12682
13405
  try {
12683
13406
  setIsRunning(true);
12684
13407
  setVisualSubmitError("");
@@ -12722,7 +13445,7 @@ function aiditorConstructor(_props, refs) {
12722
13445
  setSnapshotSubmitError("Screenshot data is missing.");
12723
13446
  return;
12724
13447
  }
12725
- const notesJson = serializeAiditorNotes(toNotesPayload(list, previewBreakpoint()));
13448
+ const notesJson = serializeAiditorNotes(toNotesPayload(list, previewBreakpoint(), changeRequestAddMenuPayload()));
12726
13449
  try {
12727
13450
  setIsRunning(true);
12728
13451
  setSnapshotSubmitError("");
@@ -13262,7 +13985,7 @@ function aiditorConstructor(_props, refs) {
13262
13985
  fallbackRenderedUrl: rendered,
13263
13986
  fallbackRoute: route,
13264
13987
  breakpoint: previewBreakpoint()
13265
- });
13988
+ }, changeRequestAddMenuPayload());
13266
13989
  try {
13267
13990
  setIsRunning(true);
13268
13991
  setVideoSubmitError("");
@@ -13270,12 +13993,26 @@ function aiditorConstructor(_props, refs) {
13270
13993
  const frameDuals = [];
13271
13994
  const nFrames = distinctTimes.length;
13272
13995
  let frameIdx = 0;
13996
+ videoEl.pause();
13273
13997
  for (const t of distinctTimes) {
13274
13998
  frameIdx += 1;
13275
13999
  setVideoSubmitProgress(`Capturing annotated frames (${frameIdx}/${nFrames})…`);
14000
+ console.info("[aiditor:video-capture] frame start", {
14001
+ frameIdx,
14002
+ nFrames,
14003
+ timeSec: t,
14004
+ currentTime: videoEl.currentTime,
14005
+ paused: videoEl.paused
14006
+ });
13276
14007
  await seekVideoToTime(videoEl, t);
14008
+ setVideoReviewCurrentTimeSec(t);
14009
+ await flushLayout();
13277
14010
  const dual = await captureVideoInstantDual(captureRoot, videoEl);
13278
14011
  frameDuals.push({ timeSec: t, ...dual });
14012
+ console.info("[aiditor:video-capture] frame done", {
14013
+ frameIdx,
14014
+ timeSec: t
14015
+ });
13279
14016
  }
13280
14017
  const attachmentsByPin = /* @__PURE__ */ new Map();
13281
14018
  for (const a2 of list) {
@@ -13353,6 +14090,7 @@ function aiditorConstructor(_props, refs) {
13353
14090
  }
13354
14091
  function finishAgentStreamRun() {
13355
14092
  setIsRunning(false);
14093
+ void refreshAddMenuCatalog?.();
13356
14094
  focusAgentChatInput();
13357
14095
  }
13358
14096
  function runAgentStream(notes2, options, streamOptions) {
@@ -13665,16 +14403,12 @@ function aiditorConstructor(_props, refs) {
13665
14403
  }
13666
14404
  }
13667
14405
  });
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
- });
13676
14406
  refs.videoAnnotationRows.videoAnnotationRow.oninput(({ event }) => {
13677
14407
  const t = event.target;
14408
+ if (t.classList.contains("cr-planned-route-input")) {
14409
+ visualAddMenu.setPlannedRoute(t.value);
14410
+ return;
14411
+ }
13678
14412
  if (t.tagName !== "TEXTAREA" || !t.classList.contains("visual-annotation-instruction"))
13679
14413
  return;
13680
14414
  const id = t.dataset.annotationId;
@@ -13684,9 +14418,29 @@ function aiditorConstructor(_props, refs) {
13684
14418
  });
13685
14419
  refs.videoAnnotationRows.videoAnnotationRow.onclick(({ event, viewState }) => {
13686
14420
  const raw = event.target;
13687
- if (raw.closest(".annotation-binding")) {
14421
+ if (raw.closest(".annotation-add-menu")) {
13688
14422
  event.preventDefault();
13689
- handleBindingUiEvent(raw, viewState.id, updateVideoAsVisual);
14423
+ const handled = visualAddMenu.handleMarkerAddMenuClick(raw, viewState.id, updateMarkerAddMenuAnnotation);
14424
+ if (!handled) {
14425
+ const paramBtn = raw.closest("[data-cr-route-param]");
14426
+ if (paramBtn?.dataset.param) {
14427
+ const name = paramBtn.dataset.param;
14428
+ visualAddMenu.setPlannedRoute((route) => {
14429
+ const base = route || selectedPageUrl() || "/";
14430
+ if (base.includes(`[${name}]`))
14431
+ return base;
14432
+ return base.endsWith("/") ? `${base}[${name}]` : `${base}/[${name}]`;
14433
+ });
14434
+ return;
14435
+ }
14436
+ if (raw.closest("[data-cr-route-param-confirm]")) {
14437
+ void visualAddMenu.confirmRouteParamPick(updateMarkerAddMenuAnnotation);
14438
+ return;
14439
+ }
14440
+ if (raw.closest("[data-cr-route-param-cancel]")) {
14441
+ visualAddMenu.cancelRouteParamPick();
14442
+ }
14443
+ }
13690
14444
  return;
13691
14445
  }
13692
14446
  const t = raw.closest("button");
@@ -13930,16 +14684,12 @@ function aiditorConstructor(_props, refs) {
13930
14684
  }
13931
14685
  }
13932
14686
  });
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
- });
13941
14687
  refs.snapshotAnnotationItems.snapshotAnnotationRow.oninput(({ event }) => {
13942
14688
  const t = event.target;
14689
+ if (t.classList.contains("cr-planned-route-input")) {
14690
+ visualAddMenu.setPlannedRoute(t.value);
14691
+ return;
14692
+ }
13943
14693
  if (t.tagName !== "TEXTAREA" || !t.classList.contains("visual-annotation-instruction"))
13944
14694
  return;
13945
14695
  const id = t.dataset.annotationId;
@@ -13964,12 +14714,33 @@ function aiditorConstructor(_props, refs) {
13964
14714
  });
13965
14715
  refs.snapshotAnnotationItems.snapshotAnnotationRow.onclick(({ event }) => {
13966
14716
  const raw = event.target;
13967
- if (raw.closest(".annotation-binding")) {
14717
+ if (raw.closest(".annotation-add-menu")) {
13968
14718
  event.preventDefault();
13969
14719
  const card = raw.closest("[data-annotation-id]");
13970
14720
  const id = card?.getAttribute("data-annotation-id");
13971
- if (id)
13972
- handleBindingUiEvent(raw, id, updateSnapshotAnnotation);
14721
+ if (id) {
14722
+ const handled = visualAddMenu.handleMarkerAddMenuClick(raw, id, updateMarkerAddMenuAnnotation);
14723
+ if (!handled) {
14724
+ const paramBtn = raw.closest("[data-cr-route-param]");
14725
+ if (paramBtn?.dataset.param) {
14726
+ const name = paramBtn.dataset.param;
14727
+ visualAddMenu.setPlannedRoute((route) => {
14728
+ const base = route || selectedPageUrl() || "/";
14729
+ if (base.includes(`[${name}]`))
14730
+ return base;
14731
+ return base.endsWith("/") ? `${base}[${name}]` : `${base}/[${name}]`;
14732
+ });
14733
+ return;
14734
+ }
14735
+ if (raw.closest("[data-cr-route-param-confirm]")) {
14736
+ void visualAddMenu.confirmRouteParamPick(updateMarkerAddMenuAnnotation);
14737
+ return;
14738
+ }
14739
+ if (raw.closest("[data-cr-route-param-cancel]")) {
14740
+ visualAddMenu.cancelRouteParamPick();
14741
+ }
14742
+ }
14743
+ }
13973
14744
  return;
13974
14745
  }
13975
14746
  const btn = raw.closest("button");
@@ -14189,6 +14960,14 @@ function aiditorConstructor(_props, refs) {
14189
14960
  recordingDraftUi,
14190
14961
  recordingDraftAttachmentChips,
14191
14962
  showRecordingDraftPopover,
14963
+ showMarkerAddMenuPopover,
14964
+ markerAddMenuPopoverLeftPct,
14965
+ markerAddMenuPopoverTopPct,
14966
+ markerAddMenuPickerAnnotationId,
14967
+ markerAddMenuNavRows,
14968
+ markerAddMenuSelectedCategory,
14969
+ showMarkerAddMenuPickerLoading,
14970
+ showMarkerAddMenuPickerCatalogEmpty,
14192
14971
  showVisualAnnotationsPanel,
14193
14972
  showVisualSubmitError,
14194
14973
  visualSubmitError,
@@ -14301,16 +15080,19 @@ function aiditorConstructor(_props, refs) {
14301
15080
  projectLocalPluginName: projectPlugins.addLocalPluginName,
14302
15081
  projectLocalPackageName: projectPlugins.addLocalPackageName,
14303
15082
  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,
15083
+ showAddMenuPanel: addPage.showAddMenuPanel,
15084
+ showAddMenuChips: addPage.showAddMenuChips,
15085
+ addMenuChipRows: addPage.addMenuChipRows,
15086
+ showAddMenuEmptyHint: addPage.showAddMenuEmptyHint,
15087
+ showAddMenuPopover: addPage.showAddMenuPopover,
15088
+ addMenuNavRows: addPage.addMenuNavRows,
15089
+ addMenuSelectedCategory: addPage.addMenuSelectedCategory,
15090
+ addMenuParamCollisionWarning: addPage.addMenuParamCollisionWarning,
15091
+ addMenuRouteParamHint: addPage.addMenuRouteParamHint,
15092
+ showAddMenuRouteParams: addPage.showAddMenuRouteParams,
15093
+ addMenuRouteParamRows: addPage.addMenuRouteParamRows,
15094
+ showAddMenuRouteParamConfirm: addPage.showAddMenuRouteParamConfirm,
15095
+ addMenuPendingRouteParamTitle: addPage.addMenuPendingRouteParamTitle,
14314
15096
  showAddPageResultBanner,
14315
15097
  addPageResultBannerClass,
14316
15098
  addPageResultBannerMessage,
@@ -14330,7 +15112,15 @@ function aiditorConstructor(_props, refs) {
14330
15112
  showBriefFillThumbnails: addPage.showBriefFillThumbnails,
14331
15113
  addPageBriefFillThumbRows: addPage.addPageBriefFillThumbRows,
14332
15114
  showSuggestedRouteBanner: addPage.showSuggestedRouteBanner,
14333
- suggestedRouteLabel: addPage.suggestedRouteLabel
15115
+ suggestedRouteLabel: addPage.suggestedRouteLabel,
15116
+ crAddMenuParamCollisionWarning: visualAddMenu.paramCollisionWarning,
15117
+ crAddMenuRouteParamHint: visualAddMenu.routeParamHint,
15118
+ showCrAddMenuRouteParams: visualAddMenu.showRouteParams,
15119
+ crAddMenuRouteParamRows: visualAddMenu.routeParamRows,
15120
+ showCrRouteParamConfirm: visualAddMenu.showRouteParamConfirm,
15121
+ crPendingRouteParamTitle: visualAddMenu.pendingRouteParamTitle,
15122
+ showCrPlannedRoutePanel,
15123
+ crPlannedRoute: visualAddMenu.plannedRoute
14334
15124
  })
14335
15125
  };
14336
15126
  }