@contentful/experience-design-system-cli 2.37.1-dev-build-7314a760.0 → 2.37.1-dev-build-daf73756.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.js CHANGED
@@ -4647,8 +4647,30 @@ function applyScopeDecisions(db, sessionId, decisions) {
4647
4647
  const placeholders = accepted.map(() => "?").join(",");
4648
4648
  db.prepare(`UPDATE raw_components SET status = 'generated' WHERE session_id = ? AND name IN (${placeholders})`).run(sessionId, ...accepted);
4649
4649
  }
4650
+ removeSlotAllowedComponents(db, sessionId, decisions.excludedSlotEdges ?? [], rejected);
4650
4651
  db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, sessionId);
4651
4652
  }
4653
+ function removeSlotAllowedComponents(db, sessionId, edges, rejectedNames) {
4654
+ if (edges.length === 0 && rejectedNames.length === 0)
4655
+ return;
4656
+ const componentId = db.prepare("SELECT component_id FROM raw_components WHERE session_id = ? AND name = ?");
4657
+ const removeEdge = db.prepare("DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ? AND allowed_component = ?");
4658
+ const removeEverywhere = db.prepare("DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND allowed_component = ?");
4659
+ db.exec("BEGIN");
4660
+ try {
4661
+ for (const edge of edges) {
4662
+ const row = componentId.get(sessionId, edge.parent);
4663
+ if (row)
4664
+ removeEdge.run(sessionId, row.component_id, edge.child);
4665
+ }
4666
+ for (const name of rejectedNames)
4667
+ removeEverywhere.run(sessionId, name);
4668
+ db.exec("COMMIT");
4669
+ } catch (e) {
4670
+ db.exec("ROLLBACK");
4671
+ throw e;
4672
+ }
4673
+ }
4652
4674
  function findLatestSessionForCommand(db, command) {
4653
4675
  return getLatestCompletedSessionForCommand(db, command);
4654
4676
  }
@@ -18910,7 +18932,7 @@ function buildVisibleRows(props) {
18910
18932
  if (visited.has(target)) continue;
18911
18933
  visited.add(target);
18912
18934
  const isCycleMember = cycleParticipants.has(target);
18913
- out.push({ name: target, depth: cur.depth + 1, isCycleMember });
18935
+ out.push({ name: target, depth: cur.depth + 1, isCycleMember, parent: cur.name });
18914
18936
  queue.push({ name: target, depth: cur.depth + 1 });
18915
18937
  }
18916
18938
  }
@@ -18957,7 +18979,8 @@ function buildVisibleRows(props) {
18957
18979
  sharedSuffix,
18958
18980
  cycleChild: child.isCycleMember || void 0,
18959
18981
  itemIdx: childRec?.idx ?? -1,
18960
- rootName: key
18982
+ rootName: key,
18983
+ parentName: child.parent
18961
18984
  });
18962
18985
  });
18963
18986
  }
@@ -19040,13 +19063,19 @@ function buildVisibleRows(props) {
19040
19063
  injectedChildren.push({
19041
19064
  name: target,
19042
19065
  depth: parentDepth + 1,
19043
- isCycleChild: true
19066
+ isCycleChild: true,
19067
+ parent: parentName
19044
19068
  });
19045
19069
  }
19046
19070
  };
19047
19071
  emitCycleChildrenOf(root, 0);
19048
19072
  for (const c2 of closureChildren) {
19049
- injectedChildren.push({ name: c2.name, depth: c2.depth, isCycleChild: false });
19073
+ injectedChildren.push({
19074
+ name: c2.name,
19075
+ depth: c2.depth,
19076
+ isCycleChild: false,
19077
+ parent: c2.path[c2.path.length - 2] ?? root
19078
+ });
19050
19079
  emitCycleChildrenOf(c2.name, c2.depth);
19051
19080
  }
19052
19081
  const isLastAtDepth = (idx) => {
@@ -19080,7 +19109,8 @@ function buildVisibleRows(props) {
19080
19109
  sharedSuffix,
19081
19110
  cycleChild: child.isCycleChild || void 0,
19082
19111
  itemIdx: childRec?.idx ?? -1,
19083
- rootName: root
19112
+ rootName: root,
19113
+ parentName: child.parent
19084
19114
  });
19085
19115
  });
19086
19116
  }
@@ -19238,7 +19268,7 @@ function GroupedSidebar(props) {
19238
19268
  const rs = itemName ? renderStatusByKey?.get(itemName) : void 0;
19239
19269
  const badge = itemName ? previewBadge(previewAnnotationByKey?.get(itemName)) : null;
19240
19270
  const showInheritance = rs !== void 0 && row.kind !== "cycle" && row.kind !== "empty" && row.kind !== "group-root";
19241
- const supportsSelectionGlyph = selectionStateByKey !== void 0 && itemName !== void 0 && (row.kind === "standalone" || row.kind === "group-root" || row.kind === "group-child" || row.kind === "flat" || row.kind === "cycle");
19271
+ const supportsSelectionGlyph = selectionStateByKey !== void 0 && itemName !== void 0 && (row.kind === "standalone" || row.kind === "group-root" || row.kind === "group-child" || row.kind === "flat" || row.kind === "empty" || row.kind === "cycle");
19242
19272
  const selState = supportsSelectionGlyph ? selectionStateByKey.get(itemName) ?? "undecided" : void 0;
19243
19273
  const isCycleRow = row.kind === "cycle";
19244
19274
  const selStyle = selectionGlyphStyleFor(selState, isCycleRow);
@@ -19268,7 +19298,7 @@ function GroupedSidebar(props) {
19268
19298
  color: isSelected ? PALETTE2.info : labelStyle.dim ? PALETTE2.muted : labelStyle.color,
19269
19299
  bold: isSelected || labelStyle.bold,
19270
19300
  inverse: false,
19271
- underline: isSelected && !focused,
19301
+ underline: isSelected && focused,
19272
19302
  wrap: wrapLabels ? "wrap" : "truncate",
19273
19303
  children: [
19274
19304
  " ",
@@ -19927,6 +19957,7 @@ function resolveKey(mode, key, ctx) {
19927
19957
  function resolveMain(key, ctx) {
19928
19958
  const { input } = key;
19929
19959
  const inMain = ctx.focusedColumn === "main";
19960
+ if (key.ctrl && input === "z") return { type: "undo" };
19930
19961
  if (input === "q" || key.escape) {
19931
19962
  if (key.escape && ctx.jumpFilterActive) return { type: "clear-jump-filter" };
19932
19963
  if (key.escape && ctx.searchActive) return { type: "clear-search" };
@@ -20196,21 +20227,42 @@ function rejectNeedsConfirmation(graph, name, state2) {
20196
20227
  const ancestors = toReject.filter((n) => n !== name);
20197
20228
  return ancestors.length + toDeselect.length >= 2;
20198
20229
  }
20199
- function partitionDecisions(components, state2) {
20230
+ function removeSlotChild(components, edge) {
20231
+ return components.map(
20232
+ (component) => component.name === edge.parent ? {
20233
+ ...component,
20234
+ slots: component.slots?.map((slot) => ({
20235
+ ...slot,
20236
+ allowedComponents: slot.allowedComponents.filter((name) => name !== edge.child)
20237
+ }))
20238
+ } : component
20239
+ );
20240
+ }
20241
+ function withoutSlotChildren(components, removedEdges) {
20242
+ return removedEdges.reduce(removeSlotChild, components);
20243
+ }
20244
+ function partitionDecisions(components, state2, removedEdges) {
20200
20245
  const accepted = [];
20201
20246
  const rejected = [];
20202
20247
  for (const c2 of components) {
20203
20248
  if (state2.get(c2.name) === "accepted") accepted.push(c2.name);
20204
20249
  else rejected.push(c2.name);
20205
20250
  }
20206
- return { accepted, rejected };
20251
+ const rejectedNames = new Set(rejected);
20252
+ const excludedSlotEdges = removedEdges.filter((e) => !rejectedNames.has(e.parent) && !rejectedNames.has(e.child));
20253
+ return { accepted, rejected, excludedSlotEdges };
20207
20254
  }
20208
20255
 
20209
20256
  // packages/experience-design-system-cli/src/tui/import/steps/07-scope-gate/use-scope-gate.ts
20210
- function useScopeGate({ components, onConfirm, onQuit }) {
20257
+ function useScopeGate({ components: loadedComponents, onConfirm, onQuit }) {
20211
20258
  const { columns: totalWidth, rows: terminalRows } = useTerminalSize();
20212
20259
  const columnPlan = useMemo2(() => computeColumnWidths(totalWidth), [totalWidth]);
20213
20260
  const [decisions, setDecisions] = useState12(/* @__PURE__ */ new Map());
20261
+ const [removedEdges, setRemovedEdges] = useState12([]);
20262
+ const components = useMemo2(
20263
+ () => withoutSlotChildren(loadedComponents, removedEdges),
20264
+ [loadedComponents, removedEdges]
20265
+ );
20214
20266
  const [nav, setNav] = useState12({ cursor: 0, scrollOffset: 0 });
20215
20267
  const [focusedColumn, setFocusedColumn] = useState12("main");
20216
20268
  const [addedComponentsCursor, setAddedComponentsCursor] = useState12(0);
@@ -20383,6 +20435,14 @@ function useScopeGate({ components, onConfirm, onQuit }) {
20383
20435
  return void 0;
20384
20436
  }
20385
20437
  };
20438
+ const focusedNestedEdge = () => {
20439
+ const row = visibleRows[safeCursor];
20440
+ if (row?.kind !== "group-child" || !row.parentName || row.itemIdx < 0) return void 0;
20441
+ const child = groupedItems[row.itemIdx]?.key;
20442
+ return child ? { parent: row.parentName, child } : void 0;
20443
+ };
20444
+ const removeFromParentSlot = (edge) => setRemovedEdges((edges) => [...edges, edge]);
20445
+ const undoSlotRemoval = () => setRemovedEdges((edges) => edges.slice(0, -1));
20386
20446
  const jumpCursorTo = (name) => {
20387
20447
  let idx = -1;
20388
20448
  for (let i = 0; i < visibleRows.length; i++) {
@@ -20482,7 +20542,7 @@ function useScopeGate({ components, onConfirm, onQuit }) {
20482
20542
  onQuit();
20483
20543
  return;
20484
20544
  case "confirm":
20485
- onConfirm(partitionDecisions(components, decisions));
20545
+ onConfirm(partitionDecisions(components, decisions, removedEdges));
20486
20546
  return;
20487
20547
  case "close-help":
20488
20548
  setShowHelp(false);
@@ -20535,6 +20595,11 @@ function useScopeGate({ components, onConfirm, onQuit }) {
20535
20595
  return;
20536
20596
  }
20537
20597
  case "reject": {
20598
+ const edge = focusedColumn === "main" ? focusedNestedEdge() : void 0;
20599
+ if (edge) {
20600
+ removeFromParentSlot(edge);
20601
+ return;
20602
+ }
20538
20603
  const key = focusedColumn === "added-components" ? addedComponents[safeAddedComponentsCursor]?.name : focusedRowKey();
20539
20604
  if (key) requestReject(key);
20540
20605
  return;
@@ -20542,6 +20607,9 @@ function useScopeGate({ components, onConfirm, onQuit }) {
20542
20607
  case "toggle-all":
20543
20608
  decide({ type: "toggle-all" });
20544
20609
  return;
20610
+ case "undo":
20611
+ undoSlotRemoval();
20612
+ return;
20545
20613
  case "accept-unflagged":
20546
20614
  decide({ type: "accept-unflagged" });
20547
20615
  return;
@@ -23174,8 +23242,8 @@ function FieldEditor({
23174
23242
  return /* @__PURE__ */ jsxs22(Box21, { flexDirection: "column", width, borderStyle: "single", borderColor: PALETTE.warning, children: [
23175
23243
  /* @__PURE__ */ jsx27(Text23, { bold: true, color: PALETTE.warning, children: "FIELD EDITOR \u2014 no fields" }),
23176
23244
  /* @__PURE__ */ jsx27(Text23, { color: PALETTE.warning, children: "\u26A0 No properties classified for this component. The LLM didn't find anything to classify." }),
23177
- /* @__PURE__ */ jsx27(Text23, { color: PALETTE.muted, children: "You can add fields manually below or reject this component." }),
23178
- /* @__PURE__ */ jsx27(Text23, { color: PALETTE.muted, children: "Enter to save \xB7 Esc to discard" })
23245
+ /* @__PURE__ */ jsx27(Text23, { color: PALETTE.muted, children: "There is nothing to edit here." }),
23246
+ /* @__PURE__ */ jsx27(Text23, { color: PALETTE.muted, children: active ? "[Esc] go back to the list" : "In the list: [r] reject this component \xB7 [a] accept it" })
23179
23247
  ] });
23180
23248
  }
23181
23249
  const hasEmptyProperties = props.length === 0 && slots.length === 0;
@@ -23390,7 +23458,7 @@ function FieldEditor({
23390
23458
  children: [
23391
23459
  hasEmptyProperties && /* @__PURE__ */ jsx27(Text23, { color: PALETTE.warning, children: "\u26A0 No properties classified for this component. The LLM didn't find anything to classify. Reject this component or add fields manually." }),
23392
23460
  header,
23393
- /* @__PURE__ */ jsx27(Box21, { flexDirection: "column", width: innerWidth, flexShrink: 0, children: focusLevel === "field" && !inComponentDesc && currentProp ? /* @__PURE__ */ jsx27(
23461
+ /* @__PURE__ */ jsx27(Box21, { flexDirection: "column", width: innerWidth, flexShrink: 0, children: focusLevel === "field" && !inComponentDesc && !inSlots && currentProp ? /* @__PURE__ */ jsx27(
23394
23462
  PropRow,
23395
23463
  {
23396
23464
  prop: currentProp,
@@ -24307,6 +24375,7 @@ function finalizeReviewSession(extractSessionId, components) {
24307
24375
  db.exec("ROLLBACK");
24308
24376
  throw error;
24309
24377
  }
24378
+ removeSlotAllowedComponents(db, extractSessionId, [], toReject);
24310
24379
  } finally {
24311
24380
  db.close();
24312
24381
  }
@@ -26038,33 +26107,50 @@ function GenerateReviewStepView({
26038
26107
  const cycle = cycleRows[cyclesCursor];
26039
26108
  return cycle ? enumerateCycleBreaks(cycle, components) : [];
26040
26109
  }, [cycleRows, cyclesCursor, components]);
26041
- const handleBreakEdge = (edge) => {
26042
- const target = components.find((component) => component.key === edge.fromComponent);
26043
- const slot = target?.entry.$slots?.[edge.slotName];
26044
- if (!target || !slot || !Array.isArray(slot.$allowedComponents)) return;
26045
- const nextEntry = {
26046
- ...target.entry,
26047
- $slots: {
26048
- ...target.entry.$slots,
26049
- [edge.slotName]: {
26050
- ...slot,
26051
- $allowedComponents: slot.$allowedComponents.filter((value) => value !== edge.toComponent)
26052
- }
26053
- }
26054
- };
26110
+ const saveSlotChange = (parentKey, nextEntry, historyLabel) => {
26055
26111
  const next = components.map(
26056
- (component) => component.key === target.key ? { ...component, entry: nextEntry } : component
26112
+ (component) => component.key === parentKey ? { ...component, entry: nextEntry } : component
26057
26113
  );
26058
26114
  setComponents(next);
26059
26115
  const db = openPipelineDb();
26060
26116
  try {
26061
- storeCDFComponents(db, extractSessionId, [{ key: target.key, entry: nextEntry }]);
26117
+ storeCDFComponents(db, extractSessionId, [{ key: parentKey, entry: nextEntry }]);
26062
26118
  } finally {
26063
26119
  db.close();
26064
26120
  }
26065
26121
  recomputeCycles(next);
26066
26122
  livePreviewHook.trigger();
26067
- pushHistorySnapshot(next, "break-cycle-edge");
26123
+ pushHistorySnapshot(next, historyLabel);
26124
+ };
26125
+ const handleBreakEdge = (edge) => {
26126
+ const target = components.find((component) => component.key === edge.fromComponent);
26127
+ const slot = target?.entry.$slots?.[edge.slotName];
26128
+ if (!target || !slot || !Array.isArray(slot.$allowedComponents)) return;
26129
+ saveSlotChange(
26130
+ target.key,
26131
+ {
26132
+ ...target.entry,
26133
+ $slots: {
26134
+ ...target.entry.$slots,
26135
+ [edge.slotName]: {
26136
+ ...slot,
26137
+ $allowedComponents: slot.$allowedComponents.filter((value) => value !== edge.toComponent)
26138
+ }
26139
+ }
26140
+ },
26141
+ "break-cycle-edge"
26142
+ );
26143
+ };
26144
+ const handleRemoveSlotChild = (parentKey, childKey) => {
26145
+ const parent = components.find((component) => component.key === parentKey);
26146
+ if (!parent?.entry.$slots) return;
26147
+ const slots = Object.fromEntries(
26148
+ Object.entries(parent.entry.$slots).map(([slotName, slot]) => [
26149
+ slotName,
26150
+ Array.isArray(slot.$allowedComponents) ? { ...slot, $allowedComponents: slot.$allowedComponents.filter((value) => value !== childKey) } : slot
26151
+ ])
26152
+ );
26153
+ saveSlotChange(parentKey, { ...parent.entry, $slots: slots }, "remove-slot-child");
26068
26154
  };
26069
26155
  const handleRejectComponent = (key) => {
26070
26156
  const rejectCascade2 = computeRejectCascade(key, componentGraph);
@@ -26394,6 +26480,11 @@ function GenerateReviewStepView({
26394
26480
  if (input === "r") {
26395
26481
  const current = components[selectedIdx];
26396
26482
  if (!current) return;
26483
+ const row = visibleRowsMemo[cursorRowIdx];
26484
+ if (row?.kind === "group-child" && row.parentName) {
26485
+ handleRemoveSlotChild(row.parentName, current.key);
26486
+ return;
26487
+ }
26397
26488
  handleRejectComponent(current.key);
26398
26489
  return;
26399
26490
  }
package/dist/index.js CHANGED
@@ -3765,8 +3765,30 @@ function applyScopeDecisions(db, sessionId, decisions) {
3765
3765
  const placeholders = accepted.map(() => "?").join(",");
3766
3766
  db.prepare(`UPDATE raw_components SET status = 'generated' WHERE session_id = ? AND name IN (${placeholders})`).run(sessionId, ...accepted);
3767
3767
  }
3768
+ removeSlotAllowedComponents(db, sessionId, decisions.excludedSlotEdges ?? [], rejected);
3768
3769
  db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, sessionId);
3769
3770
  }
3771
+ function removeSlotAllowedComponents(db, sessionId, edges, rejectedNames) {
3772
+ if (edges.length === 0 && rejectedNames.length === 0)
3773
+ return;
3774
+ const componentId = db.prepare("SELECT component_id FROM raw_components WHERE session_id = ? AND name = ?");
3775
+ const removeEdge = db.prepare("DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ? AND allowed_component = ?");
3776
+ const removeEverywhere = db.prepare("DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND allowed_component = ?");
3777
+ db.exec("BEGIN");
3778
+ try {
3779
+ for (const edge of edges) {
3780
+ const row = componentId.get(sessionId, edge.parent);
3781
+ if (row)
3782
+ removeEdge.run(sessionId, row.component_id, edge.child);
3783
+ }
3784
+ for (const name of rejectedNames)
3785
+ removeEverywhere.run(sessionId, name);
3786
+ db.exec("COMMIT");
3787
+ } catch (e) {
3788
+ db.exec("ROLLBACK");
3789
+ throw e;
3790
+ }
3791
+ }
3770
3792
  function findLatestSessionForCommand(db, command) {
3771
3793
  return getLatestCompletedSessionForCommand(db, command);
3772
3794
  }
@@ -22290,7 +22312,7 @@ function buildVisibleRows(props) {
22290
22312
  if (visited.has(target)) continue;
22291
22313
  visited.add(target);
22292
22314
  const isCycleMember = cycleParticipants.has(target);
22293
- out.push({ name: target, depth: cur.depth + 1, isCycleMember });
22315
+ out.push({ name: target, depth: cur.depth + 1, isCycleMember, parent: cur.name });
22294
22316
  queue.push({ name: target, depth: cur.depth + 1 });
22295
22317
  }
22296
22318
  }
@@ -22337,7 +22359,8 @@ function buildVisibleRows(props) {
22337
22359
  sharedSuffix,
22338
22360
  cycleChild: child.isCycleMember || void 0,
22339
22361
  itemIdx: childRec?.idx ?? -1,
22340
- rootName: key
22362
+ rootName: key,
22363
+ parentName: child.parent
22341
22364
  });
22342
22365
  });
22343
22366
  }
@@ -22420,13 +22443,19 @@ function buildVisibleRows(props) {
22420
22443
  injectedChildren.push({
22421
22444
  name: target,
22422
22445
  depth: parentDepth + 1,
22423
- isCycleChild: true
22446
+ isCycleChild: true,
22447
+ parent: parentName
22424
22448
  });
22425
22449
  }
22426
22450
  };
22427
22451
  emitCycleChildrenOf(root, 0);
22428
22452
  for (const c2 of closureChildren) {
22429
- injectedChildren.push({ name: c2.name, depth: c2.depth, isCycleChild: false });
22453
+ injectedChildren.push({
22454
+ name: c2.name,
22455
+ depth: c2.depth,
22456
+ isCycleChild: false,
22457
+ parent: c2.path[c2.path.length - 2] ?? root
22458
+ });
22430
22459
  emitCycleChildrenOf(c2.name, c2.depth);
22431
22460
  }
22432
22461
  const isLastAtDepth = (idx) => {
@@ -22460,7 +22489,8 @@ function buildVisibleRows(props) {
22460
22489
  sharedSuffix,
22461
22490
  cycleChild: child.isCycleChild || void 0,
22462
22491
  itemIdx: childRec?.idx ?? -1,
22463
- rootName: root
22492
+ rootName: root,
22493
+ parentName: child.parent
22464
22494
  });
22465
22495
  });
22466
22496
  }
@@ -22618,7 +22648,7 @@ function GroupedSidebar(props) {
22618
22648
  const rs = itemName ? renderStatusByKey?.get(itemName) : void 0;
22619
22649
  const badge = itemName ? previewBadge(previewAnnotationByKey?.get(itemName)) : null;
22620
22650
  const showInheritance = rs !== void 0 && row.kind !== "cycle" && row.kind !== "empty" && row.kind !== "group-root";
22621
- const supportsSelectionGlyph = selectionStateByKey !== void 0 && itemName !== void 0 && (row.kind === "standalone" || row.kind === "group-root" || row.kind === "group-child" || row.kind === "flat" || row.kind === "cycle");
22651
+ const supportsSelectionGlyph = selectionStateByKey !== void 0 && itemName !== void 0 && (row.kind === "standalone" || row.kind === "group-root" || row.kind === "group-child" || row.kind === "flat" || row.kind === "empty" || row.kind === "cycle");
22622
22652
  const selState = supportsSelectionGlyph ? selectionStateByKey.get(itemName) ?? "undecided" : void 0;
22623
22653
  const isCycleRow = row.kind === "cycle";
22624
22654
  const selStyle = selectionGlyphStyleFor(selState, isCycleRow);
@@ -22648,7 +22678,7 @@ function GroupedSidebar(props) {
22648
22678
  color: isSelected ? PALETTE2.info : labelStyle.dim ? PALETTE2.muted : labelStyle.color,
22649
22679
  bold: isSelected || labelStyle.bold,
22650
22680
  inverse: false,
22651
- underline: isSelected && !focused,
22681
+ underline: isSelected && focused,
22652
22682
  wrap: wrapLabels ? "wrap" : "truncate",
22653
22683
  children: [
22654
22684
  " ",
@@ -23399,6 +23429,7 @@ function resolveKey(mode, key, ctx) {
23399
23429
  function resolveMain(key, ctx) {
23400
23430
  const { input } = key;
23401
23431
  const inMain = ctx.focusedColumn === "main";
23432
+ if (key.ctrl && input === "z") return { type: "undo" };
23402
23433
  if (input === "q" || key.escape) {
23403
23434
  if (key.escape && ctx.jumpFilterActive) return { type: "clear-jump-filter" };
23404
23435
  if (key.escape && ctx.searchActive) return { type: "clear-search" };
@@ -23709,14 +23740,30 @@ function rejectNeedsConfirmation(graph, name, state2) {
23709
23740
  const ancestors = toReject.filter((n) => n !== name);
23710
23741
  return ancestors.length + toDeselect.length >= 2;
23711
23742
  }
23712
- function partitionDecisions(components, state2) {
23743
+ function removeSlotChild(components, edge) {
23744
+ return components.map(
23745
+ (component) => component.name === edge.parent ? {
23746
+ ...component,
23747
+ slots: component.slots?.map((slot) => ({
23748
+ ...slot,
23749
+ allowedComponents: slot.allowedComponents.filter((name) => name !== edge.child)
23750
+ }))
23751
+ } : component
23752
+ );
23753
+ }
23754
+ function withoutSlotChildren(components, removedEdges) {
23755
+ return removedEdges.reduce(removeSlotChild, components);
23756
+ }
23757
+ function partitionDecisions(components, state2, removedEdges) {
23713
23758
  const accepted = [];
23714
23759
  const rejected = [];
23715
23760
  for (const c2 of components) {
23716
23761
  if (state2.get(c2.name) === "accepted") accepted.push(c2.name);
23717
23762
  else rejected.push(c2.name);
23718
23763
  }
23719
- return { accepted, rejected };
23764
+ const rejectedNames = new Set(rejected);
23765
+ const excludedSlotEdges = removedEdges.filter((e) => !rejectedNames.has(e.parent) && !rejectedNames.has(e.child));
23766
+ return { accepted, rejected, excludedSlotEdges };
23720
23767
  }
23721
23768
  var init_state = __esm({
23722
23769
  "packages/experience-design-system-cli/src/tui/import/steps/07-scope-gate/state.ts"() {
@@ -23728,10 +23775,15 @@ var init_state = __esm({
23728
23775
 
23729
23776
  // packages/experience-design-system-cli/src/tui/import/steps/07-scope-gate/use-scope-gate.ts
23730
23777
  import { useEffect as useEffect7, useMemo as useMemo2, useRef as useRef4, useState as useState12 } from "react";
23731
- function useScopeGate({ components, onConfirm, onQuit }) {
23778
+ function useScopeGate({ components: loadedComponents, onConfirm, onQuit }) {
23732
23779
  const { columns: totalWidth, rows: terminalRows } = useTerminalSize();
23733
23780
  const columnPlan = useMemo2(() => computeColumnWidths(totalWidth), [totalWidth]);
23734
23781
  const [decisions, setDecisions] = useState12(/* @__PURE__ */ new Map());
23782
+ const [removedEdges, setRemovedEdges] = useState12([]);
23783
+ const components = useMemo2(
23784
+ () => withoutSlotChildren(loadedComponents, removedEdges),
23785
+ [loadedComponents, removedEdges]
23786
+ );
23735
23787
  const [nav, setNav] = useState12({ cursor: 0, scrollOffset: 0 });
23736
23788
  const [focusedColumn, setFocusedColumn] = useState12("main");
23737
23789
  const [addedComponentsCursor, setAddedComponentsCursor] = useState12(0);
@@ -23904,6 +23956,14 @@ function useScopeGate({ components, onConfirm, onQuit }) {
23904
23956
  return void 0;
23905
23957
  }
23906
23958
  };
23959
+ const focusedNestedEdge = () => {
23960
+ const row = visibleRows[safeCursor];
23961
+ if (row?.kind !== "group-child" || !row.parentName || row.itemIdx < 0) return void 0;
23962
+ const child = groupedItems[row.itemIdx]?.key;
23963
+ return child ? { parent: row.parentName, child } : void 0;
23964
+ };
23965
+ const removeFromParentSlot = (edge) => setRemovedEdges((edges) => [...edges, edge]);
23966
+ const undoSlotRemoval = () => setRemovedEdges((edges) => edges.slice(0, -1));
23907
23967
  const jumpCursorTo = (name) => {
23908
23968
  let idx = -1;
23909
23969
  for (let i = 0; i < visibleRows.length; i++) {
@@ -24003,7 +24063,7 @@ function useScopeGate({ components, onConfirm, onQuit }) {
24003
24063
  onQuit();
24004
24064
  return;
24005
24065
  case "confirm":
24006
- onConfirm(partitionDecisions(components, decisions));
24066
+ onConfirm(partitionDecisions(components, decisions, removedEdges));
24007
24067
  return;
24008
24068
  case "close-help":
24009
24069
  setShowHelp(false);
@@ -24056,6 +24116,11 @@ function useScopeGate({ components, onConfirm, onQuit }) {
24056
24116
  return;
24057
24117
  }
24058
24118
  case "reject": {
24119
+ const edge = focusedColumn === "main" ? focusedNestedEdge() : void 0;
24120
+ if (edge) {
24121
+ removeFromParentSlot(edge);
24122
+ return;
24123
+ }
24059
24124
  const key = focusedColumn === "added-components" ? addedComponents[safeAddedComponentsCursor]?.name : focusedRowKey();
24060
24125
  if (key) requestReject(key);
24061
24126
  return;
@@ -24063,6 +24128,9 @@ function useScopeGate({ components, onConfirm, onQuit }) {
24063
24128
  case "toggle-all":
24064
24129
  decide({ type: "toggle-all" });
24065
24130
  return;
24131
+ case "undo":
24132
+ undoSlotRemoval();
24133
+ return;
24066
24134
  case "accept-unflagged":
24067
24135
  decide({ type: "accept-unflagged" });
24068
24136
  return;
@@ -26961,8 +27029,8 @@ function FieldEditor({
26961
27029
  return /* @__PURE__ */ jsxs22(Box21, { flexDirection: "column", width, borderStyle: "single", borderColor: PALETTE.warning, children: [
26962
27030
  /* @__PURE__ */ jsx27(Text23, { bold: true, color: PALETTE.warning, children: "FIELD EDITOR \u2014 no fields" }),
26963
27031
  /* @__PURE__ */ jsx27(Text23, { color: PALETTE.warning, children: "\u26A0 No properties classified for this component. The LLM didn't find anything to classify." }),
26964
- /* @__PURE__ */ jsx27(Text23, { color: PALETTE.muted, children: "You can add fields manually below or reject this component." }),
26965
- /* @__PURE__ */ jsx27(Text23, { color: PALETTE.muted, children: "Enter to save \xB7 Esc to discard" })
27032
+ /* @__PURE__ */ jsx27(Text23, { color: PALETTE.muted, children: "There is nothing to edit here." }),
27033
+ /* @__PURE__ */ jsx27(Text23, { color: PALETTE.muted, children: active ? "[Esc] go back to the list" : "In the list: [r] reject this component \xB7 [a] accept it" })
26966
27034
  ] });
26967
27035
  }
26968
27036
  const hasEmptyProperties = props.length === 0 && slots.length === 0;
@@ -27177,7 +27245,7 @@ function FieldEditor({
27177
27245
  children: [
27178
27246
  hasEmptyProperties && /* @__PURE__ */ jsx27(Text23, { color: PALETTE.warning, children: "\u26A0 No properties classified for this component. The LLM didn't find anything to classify. Reject this component or add fields manually." }),
27179
27247
  header,
27180
- /* @__PURE__ */ jsx27(Box21, { flexDirection: "column", width: innerWidth, flexShrink: 0, children: focusLevel === "field" && !inComponentDesc && currentProp ? /* @__PURE__ */ jsx27(
27248
+ /* @__PURE__ */ jsx27(Box21, { flexDirection: "column", width: innerWidth, flexShrink: 0, children: focusLevel === "field" && !inComponentDesc && !inSlots && currentProp ? /* @__PURE__ */ jsx27(
27181
27249
  PropRow,
27182
27250
  {
27183
27251
  prop: currentProp,
@@ -28189,6 +28257,7 @@ function finalizeReviewSession(extractSessionId, components) {
28189
28257
  db.exec("ROLLBACK");
28190
28258
  throw error;
28191
28259
  }
28260
+ removeSlotAllowedComponents(db, extractSessionId, [], toReject);
28192
28261
  } finally {
28193
28262
  db.close();
28194
28263
  }
@@ -29967,33 +30036,50 @@ function GenerateReviewStepView({
29967
30036
  const cycle = cycleRows[cyclesCursor];
29968
30037
  return cycle ? enumerateCycleBreaks(cycle, components) : [];
29969
30038
  }, [cycleRows, cyclesCursor, components]);
29970
- const handleBreakEdge = (edge) => {
29971
- const target = components.find((component) => component.key === edge.fromComponent);
29972
- const slot = target?.entry.$slots?.[edge.slotName];
29973
- if (!target || !slot || !Array.isArray(slot.$allowedComponents)) return;
29974
- const nextEntry = {
29975
- ...target.entry,
29976
- $slots: {
29977
- ...target.entry.$slots,
29978
- [edge.slotName]: {
29979
- ...slot,
29980
- $allowedComponents: slot.$allowedComponents.filter((value) => value !== edge.toComponent)
29981
- }
29982
- }
29983
- };
30039
+ const saveSlotChange = (parentKey, nextEntry, historyLabel) => {
29984
30040
  const next = components.map(
29985
- (component) => component.key === target.key ? { ...component, entry: nextEntry } : component
30041
+ (component) => component.key === parentKey ? { ...component, entry: nextEntry } : component
29986
30042
  );
29987
30043
  setComponents(next);
29988
30044
  const db = openPipelineDb();
29989
30045
  try {
29990
- storeCDFComponents(db, extractSessionId, [{ key: target.key, entry: nextEntry }]);
30046
+ storeCDFComponents(db, extractSessionId, [{ key: parentKey, entry: nextEntry }]);
29991
30047
  } finally {
29992
30048
  db.close();
29993
30049
  }
29994
30050
  recomputeCycles(next);
29995
30051
  livePreviewHook.trigger();
29996
- pushHistorySnapshot(next, "break-cycle-edge");
30052
+ pushHistorySnapshot(next, historyLabel);
30053
+ };
30054
+ const handleBreakEdge = (edge) => {
30055
+ const target = components.find((component) => component.key === edge.fromComponent);
30056
+ const slot = target?.entry.$slots?.[edge.slotName];
30057
+ if (!target || !slot || !Array.isArray(slot.$allowedComponents)) return;
30058
+ saveSlotChange(
30059
+ target.key,
30060
+ {
30061
+ ...target.entry,
30062
+ $slots: {
30063
+ ...target.entry.$slots,
30064
+ [edge.slotName]: {
30065
+ ...slot,
30066
+ $allowedComponents: slot.$allowedComponents.filter((value) => value !== edge.toComponent)
30067
+ }
30068
+ }
30069
+ },
30070
+ "break-cycle-edge"
30071
+ );
30072
+ };
30073
+ const handleRemoveSlotChild = (parentKey, childKey) => {
30074
+ const parent = components.find((component) => component.key === parentKey);
30075
+ if (!parent?.entry.$slots) return;
30076
+ const slots = Object.fromEntries(
30077
+ Object.entries(parent.entry.$slots).map(([slotName, slot]) => [
30078
+ slotName,
30079
+ Array.isArray(slot.$allowedComponents) ? { ...slot, $allowedComponents: slot.$allowedComponents.filter((value) => value !== childKey) } : slot
30080
+ ])
30081
+ );
30082
+ saveSlotChange(parentKey, { ...parent.entry, $slots: slots }, "remove-slot-child");
29997
30083
  };
29998
30084
  const handleRejectComponent = (key) => {
29999
30085
  const rejectCascade2 = computeRejectCascade(key, componentGraph);
@@ -30323,6 +30409,11 @@ function GenerateReviewStepView({
30323
30409
  if (input === "r") {
30324
30410
  const current = components[selectedIdx];
30325
30411
  if (!current) return;
30412
+ const row = visibleRowsMemo[cursorRowIdx];
30413
+ if (row?.kind === "group-child" && row.parentName) {
30414
+ handleRemoveSlotChild(row.parentName, current.key);
30415
+ return;
30416
+ }
30326
30417
  handleRejectComponent(current.key);
30327
30418
  return;
30328
30419
  }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.37.1-dev-build-7314a760.0",
3
+ "version": "2.37.1-dev-build-daf73756.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -80,5 +80,7 @@ export type GateAction = {
80
80
  type: 'toggle-jump-filter';
81
81
  } | {
82
82
  type: 'toggle-view';
83
+ } | {
84
+ type: 'undo';
83
85
  };
84
86
  export declare function resolveKey(mode: GateMode, key: KeyPress, ctx: GateContext): GateAction;
@@ -46,6 +46,8 @@ export interface VisibleRow {
46
46
  cycleChild?: boolean;
47
47
  itemIdx: number;
48
48
  rootName?: string;
49
+ /** The parent whose slot the nested row sits in. */
50
+ parentName?: string;
49
51
  }
50
52
  export declare function buildVisibleRows(props: {
51
53
  items: GroupedSidebarItem[];
@@ -1,5 +1,5 @@
1
1
  import { type ScopeGraph } from './graph/index.js';
2
- import type { Decision, ScopeComponent, ScopeDecisions } from './types.js';
2
+ import type { Decision, ParentChildEdge, ScopeComponent, ScopeDecisions } from './types.js';
3
3
  export type DecisionState = Map<string, Decision>;
4
4
  export type DecisionAction = {
5
5
  type: 'accept';
@@ -15,5 +15,6 @@ export type DecisionAction = {
15
15
  export declare function reduceDecisions(state: DecisionState, action: DecisionAction, components: ScopeComponent[], graph: ScopeGraph): DecisionState;
16
16
  /** Whether rejecting `name` touches 2 or more other components, so the screen has to ask first. */
17
17
  export declare function rejectNeedsConfirmation(graph: ScopeGraph, name: string, state: DecisionState): boolean;
18
+ export declare function withoutSlotChildren(components: ScopeComponent[], removedEdges: ParentChildEdge[]): ScopeComponent[];
18
19
  /** Everything not accepted counts as rejected, as legacy's `partition` did. */
19
- export declare function partitionDecisions(components: ScopeComponent[], state: DecisionState): ScopeDecisions;
20
+ export declare function partitionDecisions(components: ScopeComponent[], state: DecisionState, removedEdges: ParentChildEdge[]): ScopeDecisions;
@@ -10,10 +10,17 @@ export type ScopeComponent = Omit<ScopeComponentRow, 'aiDecision' | 'aiReason' |
10
10
  slots?: ScopeComponentRow['slots'];
11
11
  };
12
12
  export type Decision = 'accepted' | 'rejected' | 'undecided';
13
+ /** One slot edge: `parent` allows `child` in a slot. */
14
+ export interface ParentChildEdge {
15
+ parent: string;
16
+ child: string;
17
+ }
13
18
  /** What the user confirmed. Becomes `ImportHandoff.scope`; the legacy wizard saves it and starts generation. */
14
19
  export interface ScopeDecisions {
15
20
  accepted: string[];
16
21
  rejected: string[];
22
+ /** Slot edges removed at the gate. The child keeps its own decision. Missing means none were removed. */
23
+ excludedSlotEdges?: ParentChildEdge[];
17
24
  }
18
25
  export type ScopeGateLoad = {
19
26
  ok: true;
@@ -10,7 +10,7 @@ export interface PendingRejectCascade {
10
10
  ancestors: string[];
11
11
  descendants: string[];
12
12
  }
13
- export declare function useScopeGate({ components, onConfirm, onQuit }: UseScopeGateOptions): {
13
+ export declare function useScopeGate({ components: loadedComponents, onConfirm, onQuit }: UseScopeGateOptions): {
14
14
  totalWidth: number;
15
15
  columnPlan: {
16
16
  layout: "single" | "two-column";
@@ -1,11 +1,9 @@
1
1
  import type { GenerationHandoff } from '@contentful/experience-design-system-types/handoff';
2
+ import type { ScopeDecisions } from '../07-scope-gate/types.js';
2
3
  export interface GenerationInput {
3
4
  /** The extraction session the scope was decided on. */
4
5
  sessionId: string;
5
- decisions: {
6
- accepted: string[];
7
- rejected: string[];
8
- };
6
+ decisions: ScopeDecisions;
9
7
  /**
10
8
  * The cache check that ran while the scope gate was open, which restores cached definitions into the pipeline
11
9
  * database. The stage waits for it before saving the decisions, since both write to that database.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.37.1-dev-build-7314a760.0",
3
+ "version": "2.37.1-dev-build-daf73756.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,8 +45,8 @@
45
45
  "ts-morph": "^27.0.2",
46
46
  "typescript": "^5.9.3",
47
47
  "zod": "^4.6.5",
48
- "@contentful/experience-design-system-backend-pipeline": "2.37.1-dev-build-7314a760.0",
49
- "@contentful/experience-design-system-types": "2.37.1-dev-build-7314a760.0"
48
+ "@contentful/experience-design-system-backend-pipeline": "2.37.1-dev-build-daf73756.0",
49
+ "@contentful/experience-design-system-types": "2.37.1-dev-build-daf73756.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@tsconfig/node24": "^24.0.4",