@contentful/experience-design-system-cli 2.37.1-dev-build-f0533497.0 → 2.37.1-dev-build-a029dc0a.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
@@ -3947,8 +3947,30 @@ function applyScopeDecisions(db, sessionId, decisions) {
3947
3947
  const placeholders = accepted.map(() => "?").join(",");
3948
3948
  db.prepare(`UPDATE raw_components SET status = 'generated' WHERE session_id = ? AND name IN (${placeholders})`).run(sessionId, ...accepted);
3949
3949
  }
3950
+ removeSlotAllowedComponents(db, sessionId, decisions.excludedSlotEdges ?? [], rejected);
3950
3951
  db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, sessionId);
3951
3952
  }
3953
+ function removeSlotAllowedComponents(db, sessionId, edges, rejectedNames) {
3954
+ if (edges.length === 0 && rejectedNames.length === 0)
3955
+ return;
3956
+ const componentId = db.prepare("SELECT component_id FROM raw_components WHERE session_id = ? AND name = ?");
3957
+ const removeEdge = db.prepare("DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ? AND allowed_component = ?");
3958
+ const removeEverywhere = db.prepare("DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND allowed_component = ?");
3959
+ db.exec("BEGIN");
3960
+ try {
3961
+ for (const edge of edges) {
3962
+ const row = componentId.get(sessionId, edge.parent);
3963
+ if (row)
3964
+ removeEdge.run(sessionId, row.component_id, edge.child);
3965
+ }
3966
+ for (const name of rejectedNames)
3967
+ removeEverywhere.run(sessionId, name);
3968
+ db.exec("COMMIT");
3969
+ } catch (e) {
3970
+ db.exec("ROLLBACK");
3971
+ throw e;
3972
+ }
3973
+ }
3952
3974
  function seedCDFFromPreviewResponse(db, sessionId, removedItems) {
3953
3975
  if (removedItems.length === 0)
3954
3976
  return 0;
@@ -16898,7 +16920,7 @@ function buildVisibleRows(props) {
16898
16920
  if (visited.has(target)) continue;
16899
16921
  visited.add(target);
16900
16922
  const isCycleMember = cycleParticipants.has(target);
16901
- out.push({ name: target, depth: cur.depth + 1, isCycleMember });
16923
+ out.push({ name: target, depth: cur.depth + 1, isCycleMember, parent: cur.name });
16902
16924
  queue.push({ name: target, depth: cur.depth + 1 });
16903
16925
  }
16904
16926
  }
@@ -16945,7 +16967,8 @@ function buildVisibleRows(props) {
16945
16967
  sharedSuffix,
16946
16968
  cycleChild: child.isCycleMember || void 0,
16947
16969
  itemIdx: childRec?.idx ?? -1,
16948
- rootName: key
16970
+ rootName: key,
16971
+ parentName: child.parent
16949
16972
  });
16950
16973
  });
16951
16974
  }
@@ -17028,13 +17051,19 @@ function buildVisibleRows(props) {
17028
17051
  injectedChildren.push({
17029
17052
  name: target,
17030
17053
  depth: parentDepth + 1,
17031
- isCycleChild: true
17054
+ isCycleChild: true,
17055
+ parent: parentName
17032
17056
  });
17033
17057
  }
17034
17058
  };
17035
17059
  emitCycleChildrenOf(root, 0);
17036
17060
  for (const c2 of closureChildren) {
17037
- injectedChildren.push({ name: c2.name, depth: c2.depth, isCycleChild: false });
17061
+ injectedChildren.push({
17062
+ name: c2.name,
17063
+ depth: c2.depth,
17064
+ isCycleChild: false,
17065
+ parent: c2.path[c2.path.length - 2] ?? root
17066
+ });
17038
17067
  emitCycleChildrenOf(c2.name, c2.depth);
17039
17068
  }
17040
17069
  const isLastAtDepth = (idx) => {
@@ -17068,7 +17097,8 @@ function buildVisibleRows(props) {
17068
17097
  sharedSuffix,
17069
17098
  cycleChild: child.isCycleChild || void 0,
17070
17099
  itemIdx: childRec?.idx ?? -1,
17071
- rootName: root
17100
+ rootName: root,
17101
+ parentName: child.parent
17072
17102
  });
17073
17103
  });
17074
17104
  }
@@ -17221,7 +17251,7 @@ function GroupedSidebar(props) {
17221
17251
  const rs = itemName ? renderStatusByKey?.get(itemName) : void 0;
17222
17252
  const badge = itemName ? previewBadge(previewAnnotationByKey?.get(itemName)) : null;
17223
17253
  const showInheritance = rs !== void 0 && row.kind !== "cycle" && row.kind !== "empty" && row.kind !== "group-root";
17224
- 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");
17254
+ 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");
17225
17255
  const selState = supportsSelectionGlyph ? selectionStateByKey.get(itemName) ?? "undecided" : void 0;
17226
17256
  const isCycleRow = row.kind === "cycle";
17227
17257
  const selStyle = selectionGlyphStyleFor(selState, isCycleRow);
@@ -17251,7 +17281,7 @@ function GroupedSidebar(props) {
17251
17281
  color: isSelected ? PALETTE2.info : labelStyle.dim ? PALETTE2.muted : labelStyle.color,
17252
17282
  bold: isSelected || labelStyle.bold,
17253
17283
  inverse: false,
17254
- underline: isSelected && !focused,
17284
+ underline: isSelected && focused,
17255
17285
  wrap: wrapLabels ? "wrap" : "truncate",
17256
17286
  children: [
17257
17287
  " ",
@@ -17910,6 +17940,7 @@ function resolveKey(mode, key, ctx) {
17910
17940
  function resolveMain(key, ctx) {
17911
17941
  const { input } = key;
17912
17942
  const inMain = ctx.focusedColumn === "main";
17943
+ if (key.ctrl && input === "z") return { type: "undo" };
17913
17944
  if (input === "q" || key.escape) {
17914
17945
  if (key.escape && ctx.jumpFilterActive) return { type: "clear-jump-filter" };
17915
17946
  if (key.escape && ctx.searchActive) return { type: "clear-search" };
@@ -18168,21 +18199,42 @@ function rejectNeedsConfirmation(graph, name, state) {
18168
18199
  const ancestors = toReject.filter((n) => n !== name);
18169
18200
  return ancestors.length + toDeselect.length >= 2;
18170
18201
  }
18171
- function partitionDecisions(components, state) {
18202
+ function removeSlotChild(components, edge) {
18203
+ return components.map(
18204
+ (component) => component.name === edge.parent ? {
18205
+ ...component,
18206
+ slots: component.slots?.map((slot) => ({
18207
+ ...slot,
18208
+ allowedComponents: slot.allowedComponents.filter((name) => name !== edge.child)
18209
+ }))
18210
+ } : component
18211
+ );
18212
+ }
18213
+ function withoutSlotChildren(components, removedEdges) {
18214
+ return removedEdges.reduce(removeSlotChild, components);
18215
+ }
18216
+ function partitionDecisions(components, state, removedEdges) {
18172
18217
  const accepted = [];
18173
18218
  const rejected = [];
18174
18219
  for (const c2 of components) {
18175
18220
  if (state.get(c2.name) === "accepted") accepted.push(c2.name);
18176
18221
  else rejected.push(c2.name);
18177
18222
  }
18178
- return { accepted, rejected };
18223
+ const rejectedNames = new Set(rejected);
18224
+ const excludedSlotEdges = removedEdges.filter((e) => !rejectedNames.has(e.parent) && !rejectedNames.has(e.child));
18225
+ return { accepted, rejected, excludedSlotEdges };
18179
18226
  }
18180
18227
 
18181
18228
  // packages/experience-design-system-cli/src/tui/import/steps/07-scope-gate/use-scope-gate.ts
18182
- function useScopeGate({ components, onConfirm, onQuit }) {
18229
+ function useScopeGate({ components: loadedComponents, onConfirm, onQuit }) {
18183
18230
  const { columns: totalWidth, rows: terminalRows } = useTerminalSize();
18184
18231
  const columnPlan = useMemo2(() => computeColumnWidths(totalWidth), [totalWidth]);
18185
18232
  const [decisions, setDecisions] = useState12(/* @__PURE__ */ new Map());
18233
+ const [removedEdges, setRemovedEdges] = useState12([]);
18234
+ const components = useMemo2(
18235
+ () => withoutSlotChildren(loadedComponents, removedEdges),
18236
+ [loadedComponents, removedEdges]
18237
+ );
18186
18238
  const [nav, setNav] = useState12({ cursor: 0, scrollOffset: 0 });
18187
18239
  const [focusedColumn, setFocusedColumn] = useState12("main");
18188
18240
  const [addedComponentsCursor, setAddedComponentsCursor] = useState12(0);
@@ -18354,6 +18406,14 @@ function useScopeGate({ components, onConfirm, onQuit }) {
18354
18406
  return void 0;
18355
18407
  }
18356
18408
  };
18409
+ const focusedNestedEdge = () => {
18410
+ const row = visibleRows[safeCursor];
18411
+ if (row?.kind !== "group-child" || !row.parentName || row.itemIdx < 0) return void 0;
18412
+ const child = groupedItems[row.itemIdx]?.key;
18413
+ return child ? { parent: row.parentName, child } : void 0;
18414
+ };
18415
+ const removeFromParentSlot = (edge) => setRemovedEdges((edges) => [...edges, edge]);
18416
+ const undoSlotRemoval = () => setRemovedEdges((edges) => edges.slice(0, -1));
18357
18417
  const jumpCursorTo = (name) => {
18358
18418
  let idx = -1;
18359
18419
  for (let i = 0; i < visibleRows.length; i++) {
@@ -18453,7 +18513,7 @@ function useScopeGate({ components, onConfirm, onQuit }) {
18453
18513
  onQuit();
18454
18514
  return;
18455
18515
  case "confirm":
18456
- onConfirm(partitionDecisions(components, decisions));
18516
+ onConfirm(partitionDecisions(components, decisions, removedEdges));
18457
18517
  return;
18458
18518
  case "close-help":
18459
18519
  setShowHelp(false);
@@ -18506,6 +18566,11 @@ function useScopeGate({ components, onConfirm, onQuit }) {
18506
18566
  return;
18507
18567
  }
18508
18568
  case "reject": {
18569
+ const edge = focusedColumn === "main" ? focusedNestedEdge() : void 0;
18570
+ if (edge) {
18571
+ removeFromParentSlot(edge);
18572
+ return;
18573
+ }
18509
18574
  const key = focusedColumn === "added-components" ? addedComponents[safeAddedComponentsCursor]?.name : focusedRowKey();
18510
18575
  if (key) requestReject(key);
18511
18576
  return;
@@ -18513,6 +18578,9 @@ function useScopeGate({ components, onConfirm, onQuit }) {
18513
18578
  case "toggle-all":
18514
18579
  decide({ type: "toggle-all" });
18515
18580
  return;
18581
+ case "undo":
18582
+ undoSlotRemoval();
18583
+ return;
18516
18584
  case "accept-unflagged":
18517
18585
  decide({ type: "accept-unflagged" });
18518
18586
  return;
@@ -21326,8 +21394,8 @@ function FieldEditor({
21326
21394
  return /* @__PURE__ */ jsxs22(Box21, { flexDirection: "column", width, borderStyle: "single", borderColor: PALETTE3.warning, children: [
21327
21395
  /* @__PURE__ */ jsx27(Text23, { bold: true, color: PALETTE3.warning, children: "FIELD EDITOR \u2014 no fields" }),
21328
21396
  /* @__PURE__ */ jsx27(Text23, { color: PALETTE3.warning, children: "\u26A0 No properties classified for this component. The LLM didn't find anything to classify." }),
21329
- /* @__PURE__ */ jsx27(Text23, { color: PALETTE3.muted, children: "You can add fields manually below or reject this component." }),
21330
- /* @__PURE__ */ jsx27(Text23, { color: PALETTE3.muted, children: "Enter to save \xB7 Esc to discard" })
21397
+ /* @__PURE__ */ jsx27(Text23, { color: PALETTE3.muted, children: "There is nothing to edit here." }),
21398
+ /* @__PURE__ */ jsx27(Text23, { color: PALETTE3.muted, children: active ? "[Esc] go back to the list" : "In the list: [r] reject this component \xB7 [a] accept it" })
21331
21399
  ] });
21332
21400
  }
21333
21401
  const hasEmptyProperties = props.length === 0 && slots.length === 0;
@@ -21542,7 +21610,7 @@ function FieldEditor({
21542
21610
  children: [
21543
21611
  hasEmptyProperties && /* @__PURE__ */ jsx27(Text23, { color: PALETTE3.warning, children: "\u26A0 No properties classified for this component. The LLM didn't find anything to classify. Reject this component or add fields manually." }),
21544
21612
  header,
21545
- /* @__PURE__ */ jsx27(Box21, { flexDirection: "column", width: innerWidth, flexShrink: 0, children: focusLevel === "field" && !inComponentDesc && currentProp ? /* @__PURE__ */ jsx27(
21613
+ /* @__PURE__ */ jsx27(Box21, { flexDirection: "column", width: innerWidth, flexShrink: 0, children: focusLevel === "field" && !inComponentDesc && !inSlots && currentProp ? /* @__PURE__ */ jsx27(
21546
21614
  PropRow,
21547
21615
  {
21548
21616
  prop: currentProp,
@@ -22451,6 +22519,7 @@ function finalizeReviewSession(extractSessionId, components) {
22451
22519
  db.exec("ROLLBACK");
22452
22520
  throw error;
22453
22521
  }
22522
+ removeSlotAllowedComponents(db, extractSessionId, [], toReject);
22454
22523
  } finally {
22455
22524
  db.close();
22456
22525
  }
@@ -24182,33 +24251,50 @@ function GenerateReviewStepView({
24182
24251
  const cycle = cycleRows[cyclesCursor];
24183
24252
  return cycle ? enumerateCycleBreaks(cycle, components) : [];
24184
24253
  }, [cycleRows, cyclesCursor, components]);
24185
- const handleBreakEdge = (edge) => {
24186
- const target = components.find((component) => component.key === edge.fromComponent);
24187
- const slot = target?.entry.$slots?.[edge.slotName];
24188
- if (!target || !slot || !Array.isArray(slot.$allowedComponents)) return;
24189
- const nextEntry = {
24190
- ...target.entry,
24191
- $slots: {
24192
- ...target.entry.$slots,
24193
- [edge.slotName]: {
24194
- ...slot,
24195
- $allowedComponents: slot.$allowedComponents.filter((value) => value !== edge.toComponent)
24196
- }
24197
- }
24198
- };
24254
+ const saveSlotChange = (parentKey, nextEntry, historyLabel) => {
24199
24255
  const next = components.map(
24200
- (component) => component.key === target.key ? { ...component, entry: nextEntry } : component
24256
+ (component) => component.key === parentKey ? { ...component, entry: nextEntry } : component
24201
24257
  );
24202
24258
  setComponents(next);
24203
24259
  const db = openPipelineDb();
24204
24260
  try {
24205
- storeCDFComponents(db, extractSessionId, [{ key: target.key, entry: nextEntry }]);
24261
+ storeCDFComponents(db, extractSessionId, [{ key: parentKey, entry: nextEntry }]);
24206
24262
  } finally {
24207
24263
  db.close();
24208
24264
  }
24209
24265
  recomputeCycles(next);
24210
24266
  livePreviewHook.trigger();
24211
- pushHistorySnapshot(next, "break-cycle-edge");
24267
+ pushHistorySnapshot(next, historyLabel);
24268
+ };
24269
+ const handleBreakEdge = (edge) => {
24270
+ const target = components.find((component) => component.key === edge.fromComponent);
24271
+ const slot = target?.entry.$slots?.[edge.slotName];
24272
+ if (!target || !slot || !Array.isArray(slot.$allowedComponents)) return;
24273
+ saveSlotChange(
24274
+ target.key,
24275
+ {
24276
+ ...target.entry,
24277
+ $slots: {
24278
+ ...target.entry.$slots,
24279
+ [edge.slotName]: {
24280
+ ...slot,
24281
+ $allowedComponents: slot.$allowedComponents.filter((value) => value !== edge.toComponent)
24282
+ }
24283
+ }
24284
+ },
24285
+ "break-cycle-edge"
24286
+ );
24287
+ };
24288
+ const handleRemoveSlotChild = (parentKey, childKey) => {
24289
+ const parent = components.find((component) => component.key === parentKey);
24290
+ if (!parent?.entry.$slots) return;
24291
+ const slots = Object.fromEntries(
24292
+ Object.entries(parent.entry.$slots).map(([slotName, slot]) => [
24293
+ slotName,
24294
+ Array.isArray(slot.$allowedComponents) ? { ...slot, $allowedComponents: slot.$allowedComponents.filter((value) => value !== childKey) } : slot
24295
+ ])
24296
+ );
24297
+ saveSlotChange(parentKey, { ...parent.entry, $slots: slots }, "remove-slot-child");
24212
24298
  };
24213
24299
  const handleRejectComponent = (key) => {
24214
24300
  const rejectCascade2 = computeRejectCascade(key, componentGraph);
@@ -24538,6 +24624,11 @@ function GenerateReviewStepView({
24538
24624
  if (input === "r") {
24539
24625
  const current = components[selectedIdx];
24540
24626
  if (!current) return;
24627
+ const row = visibleRowsMemo[cursorRowIdx];
24628
+ if (row?.kind === "group-child" && row.parentName) {
24629
+ handleRemoveSlotChild(row.parentName, current.key);
24630
+ return;
24631
+ }
24541
24632
  handleRejectComponent(current.key);
24542
24633
  return;
24543
24634
  }
package/dist/index.js CHANGED
@@ -4014,8 +4014,30 @@ function applyScopeDecisions(db, sessionId, decisions) {
4014
4014
  const placeholders = accepted.map(() => "?").join(",");
4015
4015
  db.prepare(`UPDATE raw_components SET status = 'generated' WHERE session_id = ? AND name IN (${placeholders})`).run(sessionId, ...accepted);
4016
4016
  }
4017
+ removeSlotAllowedComponents(db, sessionId, decisions.excludedSlotEdges ?? [], rejected);
4017
4018
  db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, sessionId);
4018
4019
  }
4020
+ function removeSlotAllowedComponents(db, sessionId, edges, rejectedNames) {
4021
+ if (edges.length === 0 && rejectedNames.length === 0)
4022
+ return;
4023
+ const componentId = db.prepare("SELECT component_id FROM raw_components WHERE session_id = ? AND name = ?");
4024
+ const removeEdge = db.prepare("DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND component_id = ? AND allowed_component = ?");
4025
+ const removeEverywhere = db.prepare("DELETE FROM raw_slot_allowed_components WHERE session_id = ? AND allowed_component = ?");
4026
+ db.exec("BEGIN");
4027
+ try {
4028
+ for (const edge of edges) {
4029
+ const row = componentId.get(sessionId, edge.parent);
4030
+ if (row)
4031
+ removeEdge.run(sessionId, row.component_id, edge.child);
4032
+ }
4033
+ for (const name of rejectedNames)
4034
+ removeEverywhere.run(sessionId, name);
4035
+ db.exec("COMMIT");
4036
+ } catch (e) {
4037
+ db.exec("ROLLBACK");
4038
+ throw e;
4039
+ }
4040
+ }
4019
4041
  function seedCDFFromPreviewResponse(db, sessionId, removedItems) {
4020
4042
  if (removedItems.length === 0)
4021
4043
  return 0;
@@ -20291,7 +20313,7 @@ function buildVisibleRows(props) {
20291
20313
  if (visited.has(target)) continue;
20292
20314
  visited.add(target);
20293
20315
  const isCycleMember = cycleParticipants.has(target);
20294
- out.push({ name: target, depth: cur.depth + 1, isCycleMember });
20316
+ out.push({ name: target, depth: cur.depth + 1, isCycleMember, parent: cur.name });
20295
20317
  queue.push({ name: target, depth: cur.depth + 1 });
20296
20318
  }
20297
20319
  }
@@ -20338,7 +20360,8 @@ function buildVisibleRows(props) {
20338
20360
  sharedSuffix,
20339
20361
  cycleChild: child.isCycleMember || void 0,
20340
20362
  itemIdx: childRec?.idx ?? -1,
20341
- rootName: key
20363
+ rootName: key,
20364
+ parentName: child.parent
20342
20365
  });
20343
20366
  });
20344
20367
  }
@@ -20421,13 +20444,19 @@ function buildVisibleRows(props) {
20421
20444
  injectedChildren.push({
20422
20445
  name: target,
20423
20446
  depth: parentDepth + 1,
20424
- isCycleChild: true
20447
+ isCycleChild: true,
20448
+ parent: parentName
20425
20449
  });
20426
20450
  }
20427
20451
  };
20428
20452
  emitCycleChildrenOf(root, 0);
20429
20453
  for (const c2 of closureChildren) {
20430
- injectedChildren.push({ name: c2.name, depth: c2.depth, isCycleChild: false });
20454
+ injectedChildren.push({
20455
+ name: c2.name,
20456
+ depth: c2.depth,
20457
+ isCycleChild: false,
20458
+ parent: c2.path[c2.path.length - 2] ?? root
20459
+ });
20431
20460
  emitCycleChildrenOf(c2.name, c2.depth);
20432
20461
  }
20433
20462
  const isLastAtDepth = (idx) => {
@@ -20461,7 +20490,8 @@ function buildVisibleRows(props) {
20461
20490
  sharedSuffix,
20462
20491
  cycleChild: child.isCycleChild || void 0,
20463
20492
  itemIdx: childRec?.idx ?? -1,
20464
- rootName: root
20493
+ rootName: root,
20494
+ parentName: child.parent
20465
20495
  });
20466
20496
  });
20467
20497
  }
@@ -20614,7 +20644,7 @@ function GroupedSidebar(props) {
20614
20644
  const rs = itemName ? renderStatusByKey?.get(itemName) : void 0;
20615
20645
  const badge = itemName ? previewBadge(previewAnnotationByKey?.get(itemName)) : null;
20616
20646
  const showInheritance = rs !== void 0 && row.kind !== "cycle" && row.kind !== "empty" && row.kind !== "group-root";
20617
- 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");
20647
+ 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");
20618
20648
  const selState = supportsSelectionGlyph ? selectionStateByKey.get(itemName) ?? "undecided" : void 0;
20619
20649
  const isCycleRow = row.kind === "cycle";
20620
20650
  const selStyle = selectionGlyphStyleFor(selState, isCycleRow);
@@ -20644,7 +20674,7 @@ function GroupedSidebar(props) {
20644
20674
  color: isSelected ? PALETTE2.info : labelStyle.dim ? PALETTE2.muted : labelStyle.color,
20645
20675
  bold: isSelected || labelStyle.bold,
20646
20676
  inverse: false,
20647
- underline: isSelected && !focused,
20677
+ underline: isSelected && focused,
20648
20678
  wrap: wrapLabels ? "wrap" : "truncate",
20649
20679
  children: [
20650
20680
  " ",
@@ -21395,6 +21425,7 @@ function resolveKey(mode, key, ctx) {
21395
21425
  function resolveMain(key, ctx) {
21396
21426
  const { input } = key;
21397
21427
  const inMain = ctx.focusedColumn === "main";
21428
+ if (key.ctrl && input === "z") return { type: "undo" };
21398
21429
  if (input === "q" || key.escape) {
21399
21430
  if (key.escape && ctx.jumpFilterActive) return { type: "clear-jump-filter" };
21400
21431
  if (key.escape && ctx.searchActive) return { type: "clear-search" };
@@ -21693,14 +21724,30 @@ function rejectNeedsConfirmation(graph, name, state) {
21693
21724
  const ancestors = toReject.filter((n) => n !== name);
21694
21725
  return ancestors.length + toDeselect.length >= 2;
21695
21726
  }
21696
- function partitionDecisions(components, state) {
21727
+ function removeSlotChild(components, edge) {
21728
+ return components.map(
21729
+ (component) => component.name === edge.parent ? {
21730
+ ...component,
21731
+ slots: component.slots?.map((slot) => ({
21732
+ ...slot,
21733
+ allowedComponents: slot.allowedComponents.filter((name) => name !== edge.child)
21734
+ }))
21735
+ } : component
21736
+ );
21737
+ }
21738
+ function withoutSlotChildren(components, removedEdges) {
21739
+ return removedEdges.reduce(removeSlotChild, components);
21740
+ }
21741
+ function partitionDecisions(components, state, removedEdges) {
21697
21742
  const accepted = [];
21698
21743
  const rejected = [];
21699
21744
  for (const c2 of components) {
21700
21745
  if (state.get(c2.name) === "accepted") accepted.push(c2.name);
21701
21746
  else rejected.push(c2.name);
21702
21747
  }
21703
- return { accepted, rejected };
21748
+ const rejectedNames = new Set(rejected);
21749
+ const excludedSlotEdges = removedEdges.filter((e) => !rejectedNames.has(e.parent) && !rejectedNames.has(e.child));
21750
+ return { accepted, rejected, excludedSlotEdges };
21704
21751
  }
21705
21752
  var init_state = __esm({
21706
21753
  "packages/experience-design-system-cli/src/tui/import/steps/07-scope-gate/state.ts"() {
@@ -21712,10 +21759,15 @@ var init_state = __esm({
21712
21759
 
21713
21760
  // packages/experience-design-system-cli/src/tui/import/steps/07-scope-gate/use-scope-gate.ts
21714
21761
  import { useEffect as useEffect7, useMemo as useMemo2, useRef as useRef4, useState as useState12 } from "react";
21715
- function useScopeGate({ components, onConfirm, onQuit }) {
21762
+ function useScopeGate({ components: loadedComponents, onConfirm, onQuit }) {
21716
21763
  const { columns: totalWidth, rows: terminalRows } = useTerminalSize();
21717
21764
  const columnPlan = useMemo2(() => computeColumnWidths(totalWidth), [totalWidth]);
21718
21765
  const [decisions, setDecisions] = useState12(/* @__PURE__ */ new Map());
21766
+ const [removedEdges, setRemovedEdges] = useState12([]);
21767
+ const components = useMemo2(
21768
+ () => withoutSlotChildren(loadedComponents, removedEdges),
21769
+ [loadedComponents, removedEdges]
21770
+ );
21719
21771
  const [nav, setNav] = useState12({ cursor: 0, scrollOffset: 0 });
21720
21772
  const [focusedColumn, setFocusedColumn] = useState12("main");
21721
21773
  const [addedComponentsCursor, setAddedComponentsCursor] = useState12(0);
@@ -21887,6 +21939,14 @@ function useScopeGate({ components, onConfirm, onQuit }) {
21887
21939
  return void 0;
21888
21940
  }
21889
21941
  };
21942
+ const focusedNestedEdge = () => {
21943
+ const row = visibleRows[safeCursor];
21944
+ if (row?.kind !== "group-child" || !row.parentName || row.itemIdx < 0) return void 0;
21945
+ const child = groupedItems[row.itemIdx]?.key;
21946
+ return child ? { parent: row.parentName, child } : void 0;
21947
+ };
21948
+ const removeFromParentSlot = (edge) => setRemovedEdges((edges) => [...edges, edge]);
21949
+ const undoSlotRemoval = () => setRemovedEdges((edges) => edges.slice(0, -1));
21890
21950
  const jumpCursorTo = (name) => {
21891
21951
  let idx = -1;
21892
21952
  for (let i = 0; i < visibleRows.length; i++) {
@@ -21986,7 +22046,7 @@ function useScopeGate({ components, onConfirm, onQuit }) {
21986
22046
  onQuit();
21987
22047
  return;
21988
22048
  case "confirm":
21989
- onConfirm(partitionDecisions(components, decisions));
22049
+ onConfirm(partitionDecisions(components, decisions, removedEdges));
21990
22050
  return;
21991
22051
  case "close-help":
21992
22052
  setShowHelp(false);
@@ -22039,6 +22099,11 @@ function useScopeGate({ components, onConfirm, onQuit }) {
22039
22099
  return;
22040
22100
  }
22041
22101
  case "reject": {
22102
+ const edge = focusedColumn === "main" ? focusedNestedEdge() : void 0;
22103
+ if (edge) {
22104
+ removeFromParentSlot(edge);
22105
+ return;
22106
+ }
22042
22107
  const key = focusedColumn === "added-components" ? addedComponents[safeAddedComponentsCursor]?.name : focusedRowKey();
22043
22108
  if (key) requestReject(key);
22044
22109
  return;
@@ -22046,6 +22111,9 @@ function useScopeGate({ components, onConfirm, onQuit }) {
22046
22111
  case "toggle-all":
22047
22112
  decide({ type: "toggle-all" });
22048
22113
  return;
22114
+ case "undo":
22115
+ undoSlotRemoval();
22116
+ return;
22049
22117
  case "accept-unflagged":
22050
22118
  decide({ type: "accept-unflagged" });
22051
22119
  return;
@@ -25328,8 +25396,8 @@ function FieldEditor({
25328
25396
  return /* @__PURE__ */ jsxs22(Box21, { flexDirection: "column", width, borderStyle: "single", borderColor: PALETTE3.warning, children: [
25329
25397
  /* @__PURE__ */ jsx27(Text23, { bold: true, color: PALETTE3.warning, children: "FIELD EDITOR \u2014 no fields" }),
25330
25398
  /* @__PURE__ */ jsx27(Text23, { color: PALETTE3.warning, children: "\u26A0 No properties classified for this component. The LLM didn't find anything to classify." }),
25331
- /* @__PURE__ */ jsx27(Text23, { color: PALETTE3.muted, children: "You can add fields manually below or reject this component." }),
25332
- /* @__PURE__ */ jsx27(Text23, { color: PALETTE3.muted, children: "Enter to save \xB7 Esc to discard" })
25399
+ /* @__PURE__ */ jsx27(Text23, { color: PALETTE3.muted, children: "There is nothing to edit here." }),
25400
+ /* @__PURE__ */ jsx27(Text23, { color: PALETTE3.muted, children: active ? "[Esc] go back to the list" : "In the list: [r] reject this component \xB7 [a] accept it" })
25333
25401
  ] });
25334
25402
  }
25335
25403
  const hasEmptyProperties = props.length === 0 && slots.length === 0;
@@ -25544,7 +25612,7 @@ function FieldEditor({
25544
25612
  children: [
25545
25613
  hasEmptyProperties && /* @__PURE__ */ jsx27(Text23, { color: PALETTE3.warning, children: "\u26A0 No properties classified for this component. The LLM didn't find anything to classify. Reject this component or add fields manually." }),
25546
25614
  header,
25547
- /* @__PURE__ */ jsx27(Box21, { flexDirection: "column", width: innerWidth, flexShrink: 0, children: focusLevel === "field" && !inComponentDesc && currentProp ? /* @__PURE__ */ jsx27(
25615
+ /* @__PURE__ */ jsx27(Box21, { flexDirection: "column", width: innerWidth, flexShrink: 0, children: focusLevel === "field" && !inComponentDesc && !inSlots && currentProp ? /* @__PURE__ */ jsx27(
25548
25616
  PropRow,
25549
25617
  {
25550
25618
  prop: currentProp,
@@ -26548,6 +26616,7 @@ function finalizeReviewSession(extractSessionId, components) {
26548
26616
  db.exec("ROLLBACK");
26549
26617
  throw error;
26550
26618
  }
26619
+ removeSlotAllowedComponents(db, extractSessionId, [], toReject);
26551
26620
  } finally {
26552
26621
  db.close();
26553
26622
  }
@@ -28326,33 +28395,50 @@ function GenerateReviewStepView({
28326
28395
  const cycle = cycleRows[cyclesCursor];
28327
28396
  return cycle ? enumerateCycleBreaks(cycle, components) : [];
28328
28397
  }, [cycleRows, cyclesCursor, components]);
28329
- const handleBreakEdge = (edge) => {
28330
- const target = components.find((component) => component.key === edge.fromComponent);
28331
- const slot = target?.entry.$slots?.[edge.slotName];
28332
- if (!target || !slot || !Array.isArray(slot.$allowedComponents)) return;
28333
- const nextEntry = {
28334
- ...target.entry,
28335
- $slots: {
28336
- ...target.entry.$slots,
28337
- [edge.slotName]: {
28338
- ...slot,
28339
- $allowedComponents: slot.$allowedComponents.filter((value) => value !== edge.toComponent)
28340
- }
28341
- }
28342
- };
28398
+ const saveSlotChange = (parentKey, nextEntry, historyLabel) => {
28343
28399
  const next = components.map(
28344
- (component) => component.key === target.key ? { ...component, entry: nextEntry } : component
28400
+ (component) => component.key === parentKey ? { ...component, entry: nextEntry } : component
28345
28401
  );
28346
28402
  setComponents(next);
28347
28403
  const db = openPipelineDb();
28348
28404
  try {
28349
- storeCDFComponents(db, extractSessionId, [{ key: target.key, entry: nextEntry }]);
28405
+ storeCDFComponents(db, extractSessionId, [{ key: parentKey, entry: nextEntry }]);
28350
28406
  } finally {
28351
28407
  db.close();
28352
28408
  }
28353
28409
  recomputeCycles(next);
28354
28410
  livePreviewHook.trigger();
28355
- pushHistorySnapshot(next, "break-cycle-edge");
28411
+ pushHistorySnapshot(next, historyLabel);
28412
+ };
28413
+ const handleBreakEdge = (edge) => {
28414
+ const target = components.find((component) => component.key === edge.fromComponent);
28415
+ const slot = target?.entry.$slots?.[edge.slotName];
28416
+ if (!target || !slot || !Array.isArray(slot.$allowedComponents)) return;
28417
+ saveSlotChange(
28418
+ target.key,
28419
+ {
28420
+ ...target.entry,
28421
+ $slots: {
28422
+ ...target.entry.$slots,
28423
+ [edge.slotName]: {
28424
+ ...slot,
28425
+ $allowedComponents: slot.$allowedComponents.filter((value) => value !== edge.toComponent)
28426
+ }
28427
+ }
28428
+ },
28429
+ "break-cycle-edge"
28430
+ );
28431
+ };
28432
+ const handleRemoveSlotChild = (parentKey, childKey) => {
28433
+ const parent = components.find((component) => component.key === parentKey);
28434
+ if (!parent?.entry.$slots) return;
28435
+ const slots = Object.fromEntries(
28436
+ Object.entries(parent.entry.$slots).map(([slotName, slot]) => [
28437
+ slotName,
28438
+ Array.isArray(slot.$allowedComponents) ? { ...slot, $allowedComponents: slot.$allowedComponents.filter((value) => value !== childKey) } : slot
28439
+ ])
28440
+ );
28441
+ saveSlotChange(parentKey, { ...parent.entry, $slots: slots }, "remove-slot-child");
28356
28442
  };
28357
28443
  const handleRejectComponent = (key) => {
28358
28444
  const rejectCascade2 = computeRejectCascade(key, componentGraph);
@@ -28682,6 +28768,11 @@ function GenerateReviewStepView({
28682
28768
  if (input === "r") {
28683
28769
  const current = components[selectedIdx];
28684
28770
  if (!current) return;
28771
+ const row = visibleRowsMemo[cursorRowIdx];
28772
+ if (row?.kind === "group-child" && row.parentName) {
28773
+ handleRemoveSlotChild(row.parentName, current.key);
28774
+ return;
28775
+ }
28685
28776
  handleRejectComponent(current.key);
28686
28777
  return;
28687
28778
  }
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-f0533497.0",
3
+ "version": "2.37.1-dev-build-a029dc0a.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;
@@ -45,6 +45,8 @@ export interface VisibleRow {
45
45
  cycleChild?: boolean;
46
46
  itemIdx: number;
47
47
  rootName?: string;
48
+ /** The parent whose slot the nested row sits in. */
49
+ parentName?: string;
48
50
  }
49
51
  export declare function buildVisibleRows(props: {
50
52
  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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.37.1-dev-build-f0533497.0",
3
+ "version": "2.37.1-dev-build-a029dc0a.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
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-f0533497.0",
3
+ "version": "2.37.1-dev-build-a029dc0a.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,9 +45,9 @@
45
45
  "ts-morph": "^27.0.2",
46
46
  "typescript": "^5.9.3",
47
47
  "zod": "^4.6.5",
48
- "@contentful/experience-design-system-types": "2.37.1-dev-build-f0533497.0",
49
- "@contentful/experience-design-system-backend-pipeline": "2.37.1-dev-build-f0533497.0",
50
- "@contentful/experience-design-system-generation": "2.37.1-dev-build-f0533497.0"
48
+ "@contentful/experience-design-system-types": "2.37.1-dev-build-a029dc0a.0",
49
+ "@contentful/experience-design-system-backend-pipeline": "2.37.1-dev-build-a029dc0a.0",
50
+ "@contentful/experience-design-system-generation": "2.37.1-dev-build-a029dc0a.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@tsconfig/node24": "^24.0.4",