@jay-framework/aiditor 0.18.4 → 0.19.3

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