@contentful/experience-design-system-cli 2.14.2 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +64 -2
  2. package/dist/package.json +3 -2
  3. package/dist/src/analyze/command.d.ts +11 -0
  4. package/dist/src/analyze/command.js +298 -3
  5. package/dist/src/analyze/composite-closure.d.ts +25 -0
  6. package/dist/src/analyze/composite-closure.js +0 -0
  7. package/dist/src/analyze/composition/agent-parser/author-prompt.d.ts +4 -0
  8. package/dist/src/analyze/composition/agent-parser/author-prompt.js +22 -0
  9. package/dist/src/analyze/composition/agent-parser/extract-parser.d.ts +8 -0
  10. package/dist/src/analyze/composition/agent-parser/extract-parser.js +44 -0
  11. package/dist/src/analyze/composition/agent-parser/load-prompt.d.ts +7 -0
  12. package/dist/src/analyze/composition/agent-parser/load-prompt.js +31 -0
  13. package/dist/src/analyze/composition/agent-parser/resolve-via-parser.d.ts +46 -0
  14. package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +88 -0
  15. package/dist/src/analyze/composition/agent-parser/sandbox.d.ts +45 -0
  16. package/dist/src/analyze/composition/agent-parser/sandbox.js +187 -0
  17. package/dist/src/analyze/composition/apply-mapping.d.ts +22 -0
  18. package/dist/src/analyze/composition/apply-mapping.js +67 -0
  19. package/dist/src/analyze/composition/candidate-critic-agent.d.ts +7 -0
  20. package/dist/src/analyze/composition/candidate-critic-agent.js +39 -0
  21. package/dist/src/analyze/composition/candidate-critic.d.ts +32 -0
  22. package/dist/src/analyze/composition/candidate-critic.js +63 -0
  23. package/dist/src/analyze/composition/candidate-files.d.ts +34 -0
  24. package/dist/src/analyze/composition/candidate-files.js +131 -0
  25. package/dist/src/analyze/composition/composition-cache-key.d.ts +19 -0
  26. package/dist/src/analyze/composition/composition-cache-key.js +19 -0
  27. package/dist/src/analyze/composition/interchange-schema.d.ts +37 -0
  28. package/dist/src/analyze/composition/interchange-schema.js +61 -0
  29. package/dist/src/analyze/composition/merge-edges.d.ts +19 -0
  30. package/dist/src/analyze/composition/merge-edges.js +50 -0
  31. package/dist/src/analyze/composition/parse-map-edges.d.ts +16 -0
  32. package/dist/src/analyze/composition/parse-map-edges.js +55 -0
  33. package/dist/src/analyze/composition/resolve-mapping-cli.d.ts +23 -0
  34. package/dist/src/analyze/composition/resolve-mapping-cli.js +29 -0
  35. package/dist/src/analyze/composition/resolve-mapping.d.ts +59 -0
  36. package/dist/src/analyze/composition/resolve-mapping.js +94 -0
  37. package/dist/src/analyze/cycle-detection.d.ts +27 -0
  38. package/dist/src/analyze/cycle-detection.js +234 -0
  39. package/dist/src/analyze/cycle-view.d.ts +7 -0
  40. package/dist/src/analyze/cycle-view.js +28 -0
  41. package/dist/src/analyze/fuzzy-search.d.ts +3 -0
  42. package/dist/src/analyze/fuzzy-search.js +55 -0
  43. package/dist/src/analyze/issue-inheritance.d.ts +12 -0
  44. package/dist/src/analyze/issue-inheritance.js +108 -0
  45. package/dist/src/analyze/lineage.d.ts +29 -0
  46. package/dist/src/analyze/lineage.js +0 -0
  47. package/dist/src/analyze/scope-gate-cascade.d.ts +11 -0
  48. package/dist/src/analyze/scope-gate-cascade.js +170 -0
  49. package/dist/src/analyze/search-neighborhood.d.ts +3 -0
  50. package/dist/src/analyze/search-neighborhood.js +56 -0
  51. package/dist/src/analyze/select/tui/App.js +5 -16
  52. package/dist/src/analyze/select/tui/components/ComponentRationalePanel.d.ts +0 -4
  53. package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +3 -15
  54. package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +29 -38
  55. package/dist/src/analyze/select/tui/components/FieldEditor.js +309 -185
  56. package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +13 -1
  57. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +23 -2
  58. package/dist/src/analyze/select/tui/components/GotoBanner.d.ts +31 -0
  59. package/dist/src/analyze/select/tui/components/GotoBanner.js +30 -0
  60. package/dist/src/analyze/select/tui/components/GroupedSidebar.d.ts +91 -0
  61. package/dist/src/analyze/select/tui/components/GroupedSidebar.js +497 -0
  62. package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +13 -1
  63. package/dist/src/analyze/select/tui/components/HelpOverlay.js +9 -3
  64. package/dist/src/analyze/select/tui/components/JsonEditor.js +3 -3
  65. package/dist/src/analyze/select/tui/components/LineagePanel.d.ts +14 -0
  66. package/dist/src/analyze/select/tui/components/LineagePanel.js +22 -0
  67. package/dist/src/analyze/select/tui/components/RationalePanel.d.ts +0 -7
  68. package/dist/src/analyze/select/tui/components/RationalePanel.js +3 -15
  69. package/dist/src/analyze/select/tui/components/Sidebar.d.ts +0 -6
  70. package/dist/src/analyze/select/tui/components/Sidebar.js +11 -27
  71. package/dist/src/analyze/select/tui/components/StatusBar.js +2 -1
  72. package/dist/src/analyze/select/tui/components/removed-components-text.d.ts +7 -0
  73. package/dist/src/analyze/select/tui/components/removed-components-text.js +11 -0
  74. package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +1 -5
  75. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +8 -10
  76. package/dist/src/analyze/select/tui/theme.d.ts +15 -0
  77. package/dist/src/analyze/select/tui/theme.js +28 -0
  78. package/dist/src/analyze/selection-cascade.d.ts +3 -0
  79. package/dist/src/analyze/selection-cascade.js +19 -0
  80. package/dist/src/analyze/slot-graph.d.ts +11 -0
  81. package/dist/src/analyze/slot-graph.js +17 -0
  82. package/dist/src/apply/api-client.d.ts +2 -1
  83. package/dist/src/apply/api-client.js +40 -6
  84. package/dist/src/apply/command.d.ts +17 -1
  85. package/dist/src/apply/command.js +69 -11
  86. package/dist/src/apply/error-parser.d.ts +16 -0
  87. package/dist/src/apply/error-parser.js +117 -0
  88. package/dist/src/apply/tui/ServerPreviewView.js +3 -1
  89. package/dist/src/credentials-store.d.ts +6 -0
  90. package/dist/src/credentials-store.js +5 -1
  91. package/dist/src/generate/agent-runner.d.ts +6 -0
  92. package/dist/src/generate/agent-runner.js +24 -8
  93. package/dist/src/import/command.js +69 -31
  94. package/dist/src/import/cycle-auto-reject.d.ts +4 -0
  95. package/dist/src/import/cycle-auto-reject.js +14 -0
  96. package/dist/src/import/orchestrator.d.ts +20 -7
  97. package/dist/src/import/orchestrator.js +139 -35
  98. package/dist/src/import/strip-allowed-components.d.ts +18 -0
  99. package/dist/src/import/strip-allowed-components.js +22 -0
  100. package/dist/src/import/tui/CustomPromptBanner.d.ts +0 -5
  101. package/dist/src/import/tui/CustomPromptBanner.js +2 -1
  102. package/dist/src/import/tui/WizardApp.d.ts +10 -74
  103. package/dist/src/import/tui/WizardApp.js +234 -246
  104. package/dist/src/import/tui/ai-flag.d.ts +5 -0
  105. package/dist/src/import/tui/ai-flag.js +6 -0
  106. package/dist/src/import/tui/autocomplete.d.ts +5 -0
  107. package/dist/src/import/tui/autocomplete.js +32 -0
  108. package/dist/src/import/tui/components/AutoFilterBanner.d.ts +10 -0
  109. package/dist/src/import/tui/components/AutoFilterBanner.js +19 -0
  110. package/dist/src/import/tui/components/CounterStrip.d.ts +12 -0
  111. package/dist/src/import/tui/components/CounterStrip.js +12 -0
  112. package/dist/src/import/tui/components/LegendEntry.d.ts +2 -0
  113. package/dist/src/import/tui/components/LegendEntry.js +6 -0
  114. package/dist/src/import/tui/cycle-panel-scroll.d.ts +8 -0
  115. package/dist/src/import/tui/cycle-panel-scroll.js +33 -0
  116. package/dist/src/import/tui/final-review-host.d.ts +3 -7
  117. package/dist/src/import/tui/final-review-host.js +9 -4
  118. package/dist/src/import/tui/group-collapse.d.ts +2 -0
  119. package/dist/src/import/tui/group-collapse.js +11 -0
  120. package/dist/src/import/tui/history.d.ts +22 -0
  121. package/dist/src/import/tui/history.js +53 -0
  122. package/dist/src/import/tui/hooks/useLineage.d.ts +32 -0
  123. package/dist/src/import/tui/hooks/useLineage.js +50 -0
  124. package/dist/src/import/tui/hooks/useOverlayPanel.d.ts +13 -0
  125. package/dist/src/import/tui/hooks/useOverlayPanel.js +27 -0
  126. package/dist/src/import/tui/lineage-layout.d.ts +75 -0
  127. package/dist/src/import/tui/lineage-layout.js +82 -0
  128. package/dist/src/import/tui/runLivePreview.d.ts +11 -0
  129. package/dist/src/import/tui/runLivePreview.js +7 -1
  130. package/dist/src/import/tui/scope-gate-columns.d.ts +31 -0
  131. package/dist/src/import/tui/scope-gate-columns.js +94 -0
  132. package/dist/src/import/tui/scope-gate-host.d.ts +3 -1
  133. package/dist/src/import/tui/scope-gate-host.js +11 -3
  134. package/dist/src/import/tui/sidebar-width.d.ts +1 -0
  135. package/dist/src/import/tui/sidebar-width.js +3 -0
  136. package/dist/src/import/tui/step-filters.d.ts +19 -0
  137. package/dist/src/import/tui/step-filters.js +42 -0
  138. package/dist/src/import/tui/steps/AtomicGenerateReviewStep.d.ts +42 -0
  139. package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +726 -0
  140. package/dist/src/import/tui/steps/AtomicScopeGateStep.d.ts +3 -0
  141. package/dist/src/import/tui/steps/AtomicScopeGateStep.js +211 -0
  142. package/dist/src/import/tui/steps/CredentialsStep.js +5 -18
  143. package/dist/src/import/tui/steps/DoneStep.d.ts +0 -2
  144. package/dist/src/import/tui/steps/DoneStep.js +3 -2
  145. package/dist/src/import/tui/steps/ErrorStep.js +2 -1
  146. package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +19 -25
  147. package/dist/src/import/tui/steps/GenerateReviewStep.js +1258 -229
  148. package/dist/src/import/tui/steps/PathValidationStep.js +3 -2
  149. package/dist/src/import/tui/steps/PreviewStep.js +2 -1
  150. package/dist/src/import/tui/steps/PushDecisionGateStep.d.ts +0 -7
  151. package/dist/src/import/tui/steps/PushDecisionGateStep.js +3 -6
  152. package/dist/src/import/tui/steps/PushingStep.js +2 -1
  153. package/dist/src/import/tui/steps/RunningStep.d.ts +4 -1
  154. package/dist/src/import/tui/steps/RunningStep.js +3 -2
  155. package/dist/src/import/tui/steps/ScopeGateStep.d.ts +24 -0
  156. package/dist/src/import/tui/steps/ScopeGateStep.js +850 -193
  157. package/dist/src/import/tui/steps/TokenInputStep.js +2 -1
  158. package/dist/src/import/tui/steps/WelcomeStep.js +2 -1
  159. package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +6 -0
  160. package/dist/src/import/tui/steps/WizardPreviewStep.js +95 -56
  161. package/dist/src/import/tui/steps/auto-reject-decision.d.ts +7 -0
  162. package/dist/src/import/tui/steps/auto-reject-decision.js +9 -0
  163. package/dist/src/import/tui/steps/breaking-change-format.d.ts +2 -0
  164. package/dist/src/import/tui/steps/breaking-change-format.js +24 -0
  165. package/dist/src/import/tui/steps/enumerate-cycle-breaks.d.ts +14 -0
  166. package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
  167. package/dist/src/import/tui/steps/preview-diff.js +48 -5
  168. package/dist/src/import/tui/useFinalizePreview.d.ts +34 -0
  169. package/dist/src/import/tui/useFinalizePreview.js +0 -0
  170. package/dist/src/import/tui/useLivePreview.d.ts +3 -0
  171. package/dist/src/import/tui/useLivePreview.js +1 -0
  172. package/dist/src/import/tui/wizard-state-transitions.d.ts +5 -59
  173. package/dist/src/import/tui/wizard-state-transitions.js +5 -60
  174. package/dist/src/lib/composition-mode.d.ts +28 -0
  175. package/dist/src/lib/composition-mode.js +37 -0
  176. package/dist/src/lib/prompt-overrides.d.ts +38 -0
  177. package/dist/src/lib/prompt-overrides.js +69 -0
  178. package/dist/src/print/command.js +19 -1
  179. package/dist/src/runs/modify-launcher.d.ts +3 -0
  180. package/dist/src/runs/modify-launcher.js +2 -0
  181. package/dist/src/runs/replay-helpers.js +1 -0
  182. package/dist/src/runs/run-picker.js +2 -18
  183. package/dist/src/runs/store.d.ts +4 -0
  184. package/dist/src/session/db.d.ts +23 -40
  185. package/dist/src/session/db.js +110 -138
  186. package/dist/src/setup/composition-mode-prompt.d.ts +14 -0
  187. package/dist/src/setup/composition-mode-prompt.js +37 -0
  188. package/package.json +5 -4
  189. package/prompts/composition-dir-critic.md +7 -0
  190. package/prompts/composition-edges.md +8 -0
  191. package/prompts/composition-parser-repair-empty.md +1 -0
  192. package/prompts/composition-parser-repair-error.md +2 -0
  193. package/prompts/composition-parser.md +42 -0
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from 'react';
3
+ import { PALETTE } from '../../../analyze/select/tui/theme.js';
3
4
  import { Box, Text } from 'ink';
4
5
  import { statSync } from 'node:fs';
5
6
  import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
@@ -83,5 +84,5 @@ export function TokenInputStep({ onConfirm, onSkip, onQuit }) {
83
84
  }
84
85
  });
85
86
  const displayValue = inputValue + (cursorVisible ? '█' : ' ');
86
- return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, children: "Design tokens" }), _jsx(Text, { dimColor: true, children: "Point me to your raw token file (e.g. ~/design-tokens/tokens.json). You can use ~, relative, or absolute paths. Claude will map it to DTCG format." }), _jsxs(Box, { flexDirection: "column", marginTop: 1, gap: 0, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "cyan", children: "?" }), _jsx(Text, { children: "Token path (file or directory):" }), _jsx(Text, { children: displayValue })] }), resolvedPath && resolvedPath !== inputValue.trim() && !error && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["\u2192 ", resolvedPath] }) })), error && (_jsxs(Box, { marginTop: 0, flexDirection: "column", children: [_jsxs(Text, { color: "red", children: ["\u2717 ", error] }), resolvedPath && _jsxs(Text, { dimColor: true, children: [" Resolved to: ", resolvedPath] })] }))] }), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[Enter] Submit / Skip if empty" }), _jsx(Text, { dimColor: true, children: "[Tab] Exit typing" }), _jsx(Text, { dimColor: true, children: "[Esc] Clear & exit" }), _jsx(Text, { dimColor: true, children: "[s] Skip" }), _jsx(Text, { dimColor: true, children: "[q] Quit" })] })] }));
87
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, children: "Design tokens" }), _jsx(Text, { dimColor: true, children: "Point me to your raw token file (e.g. ~/design-tokens/tokens.json). You can use ~, relative, or absolute paths. Claude will map it to DTCG format." }), _jsxs(Box, { flexDirection: "column", marginTop: 1, gap: 0, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.info, children: "?" }), _jsx(Text, { children: "Token path (file or directory):" }), _jsx(Text, { children: displayValue })] }), resolvedPath && resolvedPath !== inputValue.trim() && !error && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["\u2192 ", resolvedPath] }) })), error && (_jsxs(Box, { marginTop: 0, flexDirection: "column", children: [_jsxs(Text, { color: PALETTE.error, children: ["\u2717 ", error] }), resolvedPath && _jsxs(Text, { dimColor: true, children: [" Resolved to: ", resolvedPath] })] }))] }), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[Enter] Submit / Skip if empty" }), _jsx(Text, { dimColor: true, children: "[Tab] Exit typing" }), _jsx(Text, { dimColor: true, children: "[Esc] Clear & exit" }), _jsx(Text, { dimColor: true, children: "[s] Skip" }), _jsx(Text, { dimColor: true, children: "[q] Quit" })] })] }));
87
88
  }
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from 'react';
3
+ import { PALETTE } from '../../../analyze/select/tui/theme.js';
3
4
  import { Box, Text } from 'ink';
4
5
  import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
5
6
  export function WelcomeStep({ onContinue, onQuit }) {
@@ -29,5 +30,5 @@ export function WelcomeStep({ onContinue, onQuit }) {
29
30
  }
30
31
  });
31
32
  const displayValue = inputValue + (cursorVisible ? '█' : ' ');
32
- return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: "green", children: "\uD83D\uDC4B Hey! Let's import your design system into Contentful." }), _jsx(Text, { dimColor: true, children: "I'll walk you through 5 steps to get your components into Contentful ExO." }), _jsxs(Box, { flexDirection: "column", marginTop: 1, gap: 0, children: [_jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, children: "Step 1" }), _jsx(Text, { dimColor: true, children: "Extract components from your codebase" })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, children: "Step 2" }), _jsx(Text, { dimColor: true, children: "Review what was extracted" })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, children: "Step 3" }), _jsx(Text, { dimColor: true, children: "Generate CDF definitions with Claude" })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, children: "Step 4" }), _jsx(Text, { dimColor: true, children: "Review generated definitions" })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, children: "Step 5" }), _jsx(Text, { dimColor: true, children: "Push to Contentful" })] }), _jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" })] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, gap: 0, children: [_jsx(Text, { children: "Where is your component library?" }), _jsxs(Box, { gap: 1, marginTop: 0, children: [_jsx(Text, { color: "cyan", children: "?" }), _jsx(Text, { children: "Project path:" }), _jsx(Text, { children: displayValue })] })] }), _jsxs(Box, { marginTop: 1, gap: 3, children: [_jsx(Text, { dimColor: true, children: "[Enter] Continue" }), _jsx(Text, { dimColor: true, children: "[q] Quit" })] })] }));
33
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: PALETTE.success, children: "\uD83D\uDC4B Hey! Let's import your design system into Contentful." }), _jsx(Text, { dimColor: true, children: "I'll walk you through 5 steps to get your components into Contentful ExO." }), _jsxs(Box, { flexDirection: "column", marginTop: 1, gap: 0, children: [_jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, children: "Step 1" }), _jsx(Text, { dimColor: true, children: "Extract components from your codebase" })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, children: "Step 2" }), _jsx(Text, { dimColor: true, children: "Review what was extracted" })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, children: "Step 3" }), _jsx(Text, { dimColor: true, children: "Generate CDF definitions with Claude" })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, children: "Step 4" }), _jsx(Text, { dimColor: true, children: "Review generated definitions" })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, children: "Step 5" }), _jsx(Text, { dimColor: true, children: "Push to Contentful" })] }), _jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" })] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, gap: 0, children: [_jsx(Text, { children: "Where is your component library?" }), _jsxs(Box, { gap: 1, marginTop: 0, children: [_jsx(Text, { color: PALETTE.info, children: "?" }), _jsx(Text, { children: "Project path:" }), _jsx(Text, { children: displayValue })] })] }), _jsxs(Box, { marginTop: 1, gap: 3, children: [_jsx(Text, { dimColor: true, children: "[Enter] Continue" }), _jsx(Text, { dimColor: true, children: "[q] Quit" })] })] }));
33
34
  }
@@ -1,5 +1,11 @@
1
1
  import React from 'react';
2
2
  import type { ServerPreviewResponse } from '@contentful/experience-design-system-types';
3
+ export interface PreviewDiffLine {
4
+ key: string;
5
+ color: string;
6
+ text: string;
7
+ }
8
+ export declare function buildPreviewDiffLines(preview: ServerPreviewResponse): PreviewDiffLine[];
3
9
  type WizardPreviewStepProps = {
4
10
  preview: ServerPreviewResponse;
5
11
  spaceId: string;
@@ -1,9 +1,94 @@
1
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useMemo } from 'react';
3
+ import { PALETTE } from '../../../analyze/select/tui/theme.js';
3
4
  import { Box, Text, useStdout } from 'ink';
4
5
  import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
5
6
  import { hasBreakingChangesWithImpact } from '../../../apply/manifest.js';
6
7
  import { computeComponentDiffLines } from './preview-diff.js';
8
+ export function buildPreviewDiffLines(preview) {
9
+ const lines = [];
10
+ const { components, tokens } = preview;
11
+ for (const item of components.new) {
12
+ const raw = item;
13
+ const name = raw.key ?? raw.$name ?? 'unknown';
14
+ lines.push({ key: `comp-new-${name}`, color: PALETTE.success, text: ` + ${name}` });
15
+ const slots = (raw.$slots ?? {});
16
+ for (const slotName of Object.keys(slots).sort()) {
17
+ lines.push({
18
+ key: `comp-new-${name}-slot-${slotName}`,
19
+ color: PALETTE.success,
20
+ text: ` slot: ${slotName}`,
21
+ });
22
+ const allowed = slots[slotName]?.['$allowedComponents'];
23
+ if (Array.isArray(allowed) && allowed.length > 0) {
24
+ const names = allowed.filter((n) => typeof n === 'string');
25
+ lines.push({
26
+ key: `comp-new-${name}-slot-${slotName}-allow`,
27
+ color: PALETTE.success,
28
+ text: ` allowedComponents: [${names.join(', ')}]`,
29
+ });
30
+ }
31
+ }
32
+ }
33
+ for (const item of components.removed) {
34
+ lines.push({ key: `comp-rm-${item.name}`, color: PALETTE.error, text: ` - ${item.name}` });
35
+ }
36
+ for (const item of components.changed) {
37
+ lines.push({
38
+ key: `comp-h-${item.current.name}`,
39
+ color: PALETTE.warning,
40
+ text: ` ~ ${item.current.name}${item.hasPendingDraftChanges ? ' ⚡ has pending draft changes' : ''}`,
41
+ });
42
+ if (item.changeClassification?.classification === 'breaking') {
43
+ const reasons = item.changeClassification.breakingChanges
44
+ .map((bc) => `${'slotId' in bc ? bc.slotId : bc.propertyId}: ${bc.reason}`)
45
+ .join(', ');
46
+ lines.push({ key: `comp-b-${item.current.name}`, color: PALETTE.error, text: ` ⚠ BREAKING: ${reasons}` });
47
+ }
48
+ const diffLines = computeComponentDiffLines(item.current, item.proposed, item.changeClassification);
49
+ for (const d of diffLines) {
50
+ lines.push({ key: `comp-d-${item.current.name}-${d.key}`, color: d.color, text: ` ${d.text}` });
51
+ }
52
+ const proposedSlots = item.proposed['$slots'] ?? {};
53
+ for (const slotName of Object.keys(proposedSlots).sort()) {
54
+ const allowed = proposedSlots[slotName]?.['$allowedComponents'];
55
+ if (Array.isArray(allowed) && allowed.length > 0) {
56
+ const names = allowed.filter((n) => typeof n === 'string');
57
+ const key = `comp-d-${item.current.name}-slot-${slotName}-allow-list`;
58
+ if (!lines.some((l) => l.key === key)) {
59
+ lines.push({
60
+ key,
61
+ color: 'gray',
62
+ text: ` slot ${slotName} allowedComponents: [${names.join(', ')}]`,
63
+ });
64
+ }
65
+ }
66
+ }
67
+ }
68
+ for (const item of tokens.new) {
69
+ const raw = item;
70
+ const name = raw.name ?? raw.path ?? 'unknown';
71
+ lines.push({ key: `tok-new-${name}`, color: PALETTE.success, text: ` + ${name}` });
72
+ }
73
+ for (const item of tokens.removed) {
74
+ lines.push({ key: `tok-rm-${item.name}`, color: PALETTE.error, text: ` - ${item.name}` });
75
+ }
76
+ for (const item of tokens.changed) {
77
+ const tokenName = item.current.name;
78
+ lines.push({
79
+ key: `tok-h-${tokenName}`,
80
+ color: PALETTE.warning,
81
+ text: ` ~ ${tokenName}${item.hasPendingDraftChanges ? ' ⚡ has pending draft changes' : ''}`,
82
+ });
83
+ if (item.changeClassification?.classification === 'breaking') {
84
+ const reasons = item.changeClassification.breakingChanges
85
+ .map((bc) => `${'slotId' in bc ? bc.slotId : bc.propertyId}: ${bc.reason}`)
86
+ .join(', ');
87
+ lines.push({ key: `tok-b-${tokenName}`, color: PALETTE.error, text: ` ⚠ BREAKING: ${reasons}` });
88
+ }
89
+ }
90
+ return lines;
91
+ }
7
92
  export function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber, totalSteps, onConfirm, onEdit, onSaveFiles, onQuit, }) {
8
93
  const breakingWithImpact = hasBreakingChangesWithImpact(preview);
9
94
  const [diffExpanded, setDiffExpanded] = useState(false);
@@ -14,56 +99,10 @@ export function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber,
14
99
  const allDiffLines = useMemo(() => {
15
100
  if (!diffExpanded)
16
101
  return [];
17
- const lines = [];
18
- const { components, tokens } = preview;
19
- for (const item of components.new) {
20
- const name = item.key ??
21
- item.$name ??
22
- 'unknown';
23
- lines.push({ key: `comp-new-${name}`, element: _jsxs(Text, { color: "green", children: [" + ", name] }) });
24
- }
25
- for (const item of components.removed) {
26
- lines.push({ key: `comp-rm-${item.name}`, element: _jsxs(Text, { color: "red", children: [" - ", item.name] }) });
27
- }
28
- for (const item of components.changed) {
29
- lines.push({
30
- key: `comp-h-${item.current.name}`,
31
- element: (_jsxs(Text, { color: "yellow", children: [' ', "~ ", item.current.name, item.hasPendingDraftChanges ? _jsx(Text, { color: "yellow", children: " \u26A1 has pending draft changes" }) : null] })),
32
- });
33
- if (item.changeClassification?.classification === 'breaking') {
34
- const reasons = item.changeClassification.breakingChanges
35
- .map((bc) => `${bc.propertyId}: ${bc.reason}`)
36
- .join(', ');
37
- lines.push({ key: `comp-b-${item.current.name}`, element: _jsxs(Text, { color: "red", children: [" \u26A0 BREAKING: ", reasons] }) });
38
- }
39
- const diffLines = computeComponentDiffLines(item.current, item.proposed, item.changeClassification);
40
- for (const d of diffLines) {
41
- lines.push({ key: `comp-d-${item.current.name}-${d.key}`, element: _jsxs(Text, { color: d.color, children: [" ", d.text] }) });
42
- }
43
- }
44
- for (const item of tokens.new) {
45
- const name = item.name ??
46
- item.path ??
47
- 'unknown';
48
- lines.push({ key: `tok-new-${name}`, element: _jsxs(Text, { color: "green", children: [" + ", name] }) });
49
- }
50
- for (const item of tokens.removed) {
51
- lines.push({ key: `tok-rm-${item.name}`, element: _jsxs(Text, { color: "red", children: [" - ", item.name] }) });
52
- }
53
- for (const item of tokens.changed) {
54
- const tokenName = item.current.name;
55
- lines.push({
56
- key: `tok-h-${tokenName}`,
57
- element: (_jsxs(Text, { color: "yellow", children: [' ', "~ ", tokenName, item.hasPendingDraftChanges ? _jsx(Text, { color: "yellow", children: " \u26A1 has pending draft changes" }) : null] })),
58
- });
59
- if (item.changeClassification?.classification === 'breaking') {
60
- const reasons = item.changeClassification.breakingChanges
61
- .map((bc) => `${bc.propertyId}: ${bc.reason}`)
62
- .join(', ');
63
- lines.push({ key: `tok-b-${tokenName}`, element: _jsxs(Text, { color: "red", children: [" \u26A0 BREAKING: ", reasons] }) });
64
- }
65
- }
66
- return lines;
102
+ return buildPreviewDiffLines(preview).map((line) => ({
103
+ key: line.key,
104
+ element: (_jsx(Text, { color: line.color === 'gray' ? undefined : line.color, dimColor: line.color === 'gray', children: line.text })),
105
+ }));
67
106
  }, [diffExpanded, preview]);
68
107
  const maxScroll = Math.max(0, allDiffLines.length - viewportHeight);
69
108
  useImmediateInput((input, key) => {
@@ -111,11 +150,11 @@ export function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber,
111
150
  const hasComponents = components.new.length + components.changed.length + components.removed.length > 0;
112
151
  const hasTokens = tokens.new.length + tokens.changed.length + tokens.removed.length > 0;
113
152
  const hasAnything = hasComponents || hasTokens;
114
- return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsxs(Box, { gap: 1, children: [_jsxs(Text, { bold: true, children: ["Step ", stepNumber, " of ", totalSteps] }), _jsx(Text, { bold: true, children: "\u2014" }), _jsx(Text, { bold: true, children: "Push to Contentful" })] }), _jsx(Text, { dimColor: true, children: '─'.repeat(40) })] }), hasAnything ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: "Here's what will happen in your space:" }), hasComponents && (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Box, { gap: 1, marginTop: 1, children: _jsx(Text, { bold: true, dimColor: true, children: "ComponentTypes" }) }), components.new.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "green", children: " \uFF0B" }), _jsxs(Text, { children: [components.new.length, " will be created"] })] }), components.new.map((item, i) => {
153
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsxs(Box, { gap: 1, children: [_jsxs(Text, { bold: true, children: ["Step ", stepNumber, " of ", totalSteps] }), _jsx(Text, { bold: true, children: "\u2014" }), _jsx(Text, { bold: true, children: "Push to Contentful" })] }), _jsx(Text, { dimColor: true, children: '─'.repeat(40) })] }), hasAnything ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: "Here's what will happen in your space:" }), hasComponents && (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Box, { gap: 1, marginTop: 1, children: _jsx(Text, { bold: true, dimColor: true, children: "ComponentTypes" }) }), components.new.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.success, children: " \uFF0B" }), _jsxs(Text, { children: [components.new.length, " will be created"] })] }), components.new.map((item, i) => {
115
154
  const name = item.key ?? item.$name ?? 'unknown';
116
- return (_jsxs(Text, { color: "green", children: [' ', "+ ", name] }, `new-${i}`));
117
- })] })), components.changed.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "yellow", children: " \uFF5E" }), _jsxs(Text, { children: [components.changed.length, " will be updated"] })] }), components.changed.map((item, i) => {
155
+ return (_jsxs(Text, { color: PALETTE.success, children: [' ', "+ ", name] }, `new-${i}`));
156
+ })] })), components.changed.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.warning, children: " \uFF5E" }), _jsxs(Text, { children: [components.changed.length, " will be updated"] })] }), components.changed.map((item, i) => {
118
157
  const isBreaking = item.changeClassification?.classification === 'breaking';
119
- return (_jsxs(Text, { color: isBreaking ? 'red' : 'yellow', children: [' ', isBreaking ? '⚠' : '~', " ", item.current.name] }, `chg-${i}`));
120
- })] })), components.removed.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "red", children: " \u2717" }), _jsxs(Text, { children: [components.removed.length, " will be removed"] })] }), components.removed.map((item, i) => (_jsxs(Text, { color: "red", children: [' ', "\u2717 ", item.name] }, `rm-${i}`)))] })), components.unchanged.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: " \u00B7" }), _jsxs(Text, { dimColor: true, children: [components.unchanged.length, " unchanged"] })] }))] })), hasTokens && (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Box, { gap: 1, marginTop: 1, children: _jsx(Text, { bold: true, dimColor: true, children: "Design Tokens" }) }), tokens.new.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "green", children: " \uFF0B" }), _jsxs(Text, { children: [tokens.new.length, " will be created"] })] })), tokens.changed.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "yellow", children: " \uFF5E" }), _jsxs(Text, { children: [tokens.changed.length, " will be updated"] })] })), tokens.removed.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "red", children: " \u2717" }), _jsxs(Text, { children: [tokens.removed.length, " will be removed"] })] })), tokens.unchanged.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: " \u00B7" }), _jsxs(Text, { dimColor: true, children: [tokens.unchanged.length, " unchanged"] })] }))] })), diffExpanded && allDiffLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsxs(Text, { dimColor: true, children: [' ', "Diff (", allDiffLines.length, " lines) \u2014 line ", scrollOffset + 1, "\u2013", Math.min(scrollOffset + viewportHeight, allDiffLines.length), " of ", allDiffLines.length] }), _jsx(Box, { flexDirection: "column", children: allDiffLines.slice(scrollOffset, scrollOffset + viewportHeight).map((line) => (_jsx(Box, { children: line.element }, line.key))) }), maxScroll > 0 && _jsx(Text, { dimColor: true, children: " \u2195 j/k to scroll, f/b to page" })] }))] })) : (_jsx(Text, { dimColor: true, children: "Nothing to push \u2014 everything is already up to date." })), _jsxs(Box, { gap: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Space:" }), _jsx(Text, { children: spaceId }), _jsx(Text, { dimColor: true, children: "/" }), _jsx(Text, { dimColor: true, children: "Environment:" }), _jsx(Text, { children: environmentId })] }), breakingWithImpact && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", bold: true, children: "\u26A0 Breaking changes will affect downstream entities. Press Enter to acknowledge and apply." }) })), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[Enter] Push to Contentful" }), _jsxs(Text, { dimColor: true, children: ["[d] ", diffExpanded ? 'Hide' : 'Show', " diff"] }), diffExpanded && _jsx(Text, { dimColor: true, children: "[j/k] Scroll [f/b] Page" }), onEdit && _jsx(Text, { dimColor: true, children: "[e] Edit definitions" }), onSaveFiles && _jsx(Text, { dimColor: true, children: "[s] Save files instead" }), _jsx(Text, { dimColor: true, children: "[q] Cancel" })] })] }));
158
+ return (_jsxs(Text, { color: isBreaking ? PALETTE.error : PALETTE.warning, children: [' ', isBreaking ? '⚠' : '~', " ", item.current.name] }, `chg-${i}`));
159
+ })] })), components.removed.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.error, children: " \u2717" }), _jsxs(Text, { children: [components.removed.length, " will be removed"] })] }), components.removed.map((item, i) => (_jsxs(Text, { color: PALETTE.error, children: [' ', "\u2717 ", item.name] }, `rm-${i}`)))] })), components.unchanged.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: " \u00B7" }), _jsxs(Text, { dimColor: true, children: [components.unchanged.length, " unchanged"] })] }))] })), hasTokens && (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Box, { gap: 1, marginTop: 1, children: _jsx(Text, { bold: true, dimColor: true, children: "Design Tokens" }) }), tokens.new.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.success, children: " \uFF0B" }), _jsxs(Text, { children: [tokens.new.length, " will be created"] })] })), tokens.changed.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.warning, children: " \uFF5E" }), _jsxs(Text, { children: [tokens.changed.length, " will be updated"] })] })), tokens.removed.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.error, children: " \u2717" }), _jsxs(Text, { children: [tokens.removed.length, " will be removed"] })] })), tokens.unchanged.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: " \u00B7" }), _jsxs(Text, { dimColor: true, children: [tokens.unchanged.length, " unchanged"] })] }))] })), diffExpanded && allDiffLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsxs(Text, { dimColor: true, children: [' ', "Diff (", allDiffLines.length, " lines) \u2014 line ", scrollOffset + 1, "\u2013", Math.min(scrollOffset + viewportHeight, allDiffLines.length), " of ", allDiffLines.length] }), _jsx(Box, { flexDirection: "column", children: allDiffLines.slice(scrollOffset, scrollOffset + viewportHeight).map((line) => (_jsx(Box, { children: line.element }, line.key))) }), maxScroll > 0 && _jsx(Text, { dimColor: true, children: " \u2195 j/k to scroll, f/b to page" })] }))] })) : (_jsx(Text, { dimColor: true, children: "Nothing to push \u2014 everything is already up to date." })), _jsxs(Box, { gap: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Space:" }), _jsx(Text, { children: spaceId }), _jsx(Text, { dimColor: true, children: "/" }), _jsx(Text, { dimColor: true, children: "Environment:" }), _jsx(Text, { children: environmentId })] }), breakingWithImpact && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: PALETTE.error, bold: true, children: "\u26A0 Breaking changes will affect downstream entities. Press Enter to acknowledge and apply." }) })), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[Enter] Push to Contentful" }), _jsxs(Text, { dimColor: true, children: ["[d] ", diffExpanded ? 'Hide' : 'Show', " diff"] }), diffExpanded && _jsx(Text, { dimColor: true, children: "[j/k] Scroll [f/b] Page" }), onEdit && _jsx(Text, { dimColor: true, children: "[e] Edit definitions" }), onSaveFiles && _jsx(Text, { dimColor: true, children: "[s] Save files instead" }), _jsx(Text, { dimColor: true, children: "[q] Cancel" })] })] }));
121
160
  }
@@ -0,0 +1,7 @@
1
+ export interface AutoRejectDecisionInput {
2
+ loading: boolean;
3
+ autoRejectFired: boolean;
4
+ hasCycle: boolean;
5
+ }
6
+ export type AutoRejectDecision = 'fire' | 'skip';
7
+ export declare function computeAutoRejectDecision(input: AutoRejectDecisionInput): AutoRejectDecision;
@@ -0,0 +1,9 @@
1
+ export function computeAutoRejectDecision(input) {
2
+ if (input.loading)
3
+ return 'skip';
4
+ if (input.autoRejectFired)
5
+ return 'skip';
6
+ if (!input.hasCycle)
7
+ return 'skip';
8
+ return 'fire';
9
+ }
@@ -0,0 +1,2 @@
1
+ import type { BreakingChange, ComponentTypeSummary } from '@contentful/experience-design-system-types';
2
+ export declare function formatBreakingChange(bc: BreakingChange, current?: Pick<ComponentTypeSummary, 'fullProperties'>): string;
@@ -0,0 +1,24 @@
1
+ const PROPERTY_REASON_COPY = {
2
+ removed: 'removed',
3
+ added_required_no_default: 'now required, no default',
4
+ type_changed: 'type changed',
5
+ validation_narrowed: 'allowed values narrowed',
6
+ };
7
+ const SLOT_REASON_COPY = {
8
+ slot_removed: 'slot removed',
9
+ slot_allowed_components_narrowed: 'allowed components narrowed',
10
+ };
11
+ export function formatBreakingChange(bc, current) {
12
+ if ('slotId' in bc) {
13
+ const copy = SLOT_REASON_COPY[bc.reason] ?? bc.reason;
14
+ return `${bc.slotId} — ${copy}`;
15
+ }
16
+ const copy = PROPERTY_REASON_COPY[bc.reason] ?? bc.reason;
17
+ const meta = current?.fullProperties?.[bc.propertyId];
18
+ if (meta) {
19
+ const tags = [meta.category, meta.type].filter((t) => typeof t === 'string' && t.length > 0);
20
+ const suffix = tags.length > 0 ? ` (${tags.join(', ')})` : '';
21
+ return `${bc.propertyId}${suffix} — ${copy}`;
22
+ }
23
+ return `${bc.propertyId} — ${copy}`;
24
+ }
@@ -0,0 +1,14 @@
1
+ import type { CDFComponentEntry } from '@contentful/experience-design-system-types';
2
+ import type { SlotCycle, SlotEdge } from '../../../analyze/cycle-detection.js';
3
+ export type BreakEdge = SlotEdge;
4
+ /** Row shape accepted by {@link enumerateCycleBreaks}. */
5
+ export interface BreakEnumComponent {
6
+ key: string;
7
+ entry: CDFComponentEntry;
8
+ }
9
+ export declare function enumerateCycleBreaks(cycle: Pick<SlotCycle, 'edges'>, components: BreakEnumComponent[]): BreakEdge[];
10
+ export declare function shouldBreakOverlayGoFullScreen({ rows, edgeCount, reservedChrome, }: {
11
+ rows: number;
12
+ edgeCount: number;
13
+ reservedChrome?: number;
14
+ }): boolean;
@@ -7,14 +7,12 @@ export function computeComponentDiffLines(current, proposed, changeClassificatio
7
7
  const proposedProperties = (proposed['$properties'] ?? {});
8
8
  const proposedProps = new Set(Object.keys(proposedProperties));
9
9
  const oldProps = current.fullProperties;
10
- // Added props
11
10
  for (const name of [...proposedProps].sort()) {
12
11
  if (!currentProps.has(name)) {
13
12
  const def = proposedProperties[name];
14
13
  lines.push({ key: `${name}-add`, color: 'green', text: `+ ${name}: ${formatPropDef(def)}` });
15
14
  }
16
15
  }
17
- // Removed props
18
16
  for (const name of [...currentProps].sort()) {
19
17
  if (!proposedProps.has(name)) {
20
18
  if (oldProps?.[name]) {
@@ -26,7 +24,6 @@ export function computeComponentDiffLines(current, proposed, changeClassificatio
26
24
  }
27
25
  }
28
26
  }
29
- // Modified props — compare old vs new definitions
30
27
  const handledProps = new Set();
31
28
  for (const name of [...currentProps].sort()) {
32
29
  if (!proposedProps.has(name))
@@ -42,9 +39,10 @@ export function computeComponentDiffLines(current, proposed, changeClassificatio
42
39
  lines.push(d);
43
40
  }
44
41
  }
45
- // If no old definitions available, fall back to breaking change reasons
46
42
  if (!oldProps && changeClassification?.breakingChanges) {
47
43
  for (const bc of changeClassification.breakingChanges) {
44
+ if (!('propertyId' in bc))
45
+ continue;
48
46
  if (!currentProps.has(bc.propertyId) || !proposedProps.has(bc.propertyId))
49
47
  continue;
50
48
  if (handledProps.has(bc.propertyId))
@@ -55,10 +53,10 @@ export function computeComponentDiffLines(current, proposed, changeClassificatio
55
53
  lines.push({ key: `${bc.propertyId}-new`, color: 'green', text: `+ ${bc.propertyId}: ${formatPropDef(def)}` });
56
54
  }
57
55
  }
58
- // Slot diffs
59
56
  const currentSlots = new Set(current.slots);
60
57
  const proposedSlots = (proposed['$slots'] ?? {});
61
58
  const proposedSlotNames = new Set(Object.keys(proposedSlots));
59
+ const currentSlotAllowed = current.currentSlotAllowed ?? {};
62
60
  for (const name of [...proposedSlotNames].sort()) {
63
61
  if (!currentSlots.has(name)) {
64
62
  lines.push({ key: `slot-${name}-add`, color: 'green', text: `+ slot: ${name}` });
@@ -69,8 +67,53 @@ export function computeComponentDiffLines(current, proposed, changeClassificatio
69
67
  lines.push({ key: `slot-${name}-rm`, color: 'red', text: `- slot: ${name}` });
70
68
  }
71
69
  }
70
+ for (const name of [...proposedSlotNames].sort()) {
71
+ const nextAllowed = normalizeAllowedComponents(proposedSlots[name]?.['$allowedComponents']);
72
+ const prevAllowed = normalizeAllowedComponents(currentSlotAllowed[name]);
73
+ const prevExists = currentSlots.has(name);
74
+ if (!prevExists) {
75
+ if (nextAllowed.length > 0) {
76
+ lines.push({
77
+ key: `slot-${name}-allow-new`,
78
+ color: 'green',
79
+ text: `+ slot ${name} allowedComponents: [${nextAllowed.join(', ')}]`,
80
+ });
81
+ }
82
+ continue;
83
+ }
84
+ if (arraysEqual(prevAllowed, nextAllowed))
85
+ continue;
86
+ lines.push({
87
+ key: `slot-${name}-allow-old`,
88
+ color: 'red',
89
+ text: `- slot ${name} allowedComponents: [${prevAllowed.join(', ')}]`,
90
+ });
91
+ lines.push({
92
+ key: `slot-${name}-allow-new`,
93
+ color: 'green',
94
+ text: `+ slot ${name} allowedComponents: [${nextAllowed.join(', ')}]`,
95
+ });
96
+ }
72
97
  return lines;
73
98
  }
99
+ function normalizeAllowedComponents(value) {
100
+ if (!Array.isArray(value))
101
+ return [];
102
+ const out = [];
103
+ for (const item of value) {
104
+ if (typeof item === 'string' && item.length > 0)
105
+ out.push(item);
106
+ }
107
+ return out.sort();
108
+ }
109
+ function arraysEqual(a, b) {
110
+ if (a.length !== b.length)
111
+ return false;
112
+ for (let i = 0; i < a.length; i++)
113
+ if (a[i] !== b[i])
114
+ return false;
115
+ return true;
116
+ }
74
117
  function normalizeType(type) {
75
118
  switch (type.toLowerCase()) {
76
119
  case 'boolean':
@@ -0,0 +1,34 @@
1
+ import type { ComponentTypeSummary } from '@contentful/experience-design-system-types';
2
+ export type FinalizePreviewStatus = 'idle' | 'running' | 'done' | 'error';
3
+ export type UseFinalizePreviewOptions = {
4
+ /** True while the Finalize dialog is open. Toggling true (re)fires the preview. */
5
+ open: boolean;
6
+ extractSessionId: string | null;
7
+ tokensPath: string;
8
+ spaceId: string;
9
+ environmentId: string;
10
+ cmaToken: string;
11
+ host: string;
12
+ /** Component keys the operator has accepted; drives the scoped preview. */
13
+ acceptedKeys: ReadonlySet<string>;
14
+ };
15
+ export type UseFinalizePreviewReturn = {
16
+ status: FinalizePreviewStatus;
17
+ removed: ComponentTypeSummary[];
18
+ scrollOffset: number;
19
+ /** Adjust the removed-list scroll (clamped to the window). */
20
+ scrollBy: (delta: number) => void;
21
+ };
22
+ /**
23
+ * Shared Finalize-dialog deletion preview, used by both the composite and
24
+ * atomic generate-review steps so the "what will this push delete" experience
25
+ * is identical.
26
+ *
27
+ * When the dialog opens it fires a preview scoped to the ACCEPTED set (so the
28
+ * result reflects exactly what the push does, independent of the session's
29
+ * not-yet-reclassified generated rows), tracks loading/error, and owns the
30
+ * scroll offset for the (windowed) removed list. Credentials-missing (e.g.
31
+ * `--no-push`) short-circuits to `done` with an empty list — the dialog then
32
+ * simply omits the deletion section.
33
+ */
34
+ export declare function useFinalizePreview(opts: UseFinalizePreviewOptions): UseFinalizePreviewReturn;
@@ -9,6 +9,9 @@ export type UseLivePreviewOptions = {
9
9
  host: string;
10
10
  onResult: (response: ServerPreviewResponse | null) => void;
11
11
  debounceMs?: number;
12
+ /** Preview an empty-but-present manifest (delete-all diff) when nothing is
13
+ * accepted, so the final-review UI can show what a push would delete. */
14
+ deleteAllComponents?: boolean;
12
15
  };
13
16
  export type LivePreviewStatus = 'idle' | 'running';
14
17
  export type UseLivePreviewReturn = {
@@ -81,6 +81,7 @@ export function useLivePreview(opts) {
81
81
  cmaToken: current.cmaToken,
82
82
  host: current.host,
83
83
  generation,
84
+ deleteAllComponents: current.deleteAllComponents === true,
84
85
  });
85
86
  // Discard stale responses (generation tag).
86
87
  if (result.generation !== latestRef.current)
@@ -1,45 +1,9 @@
1
- /**
2
- * Pure state-machine helpers for the wizard. Extracted from WizardApp.tsx so the
3
- * transitions can be unit-tested without rendering ink.
4
- *
5
- * Bug fix (2026-06): credentials moved BEFORE generate so:
6
- * 1. Bad creds (401/403) are caught upfront — before the operator pays the cost
7
- * of extract + LLM classification (potentially many minutes).
8
- * 2. Live preview in final-review actually fires, because the validated creds
9
- * are present in WizardApp state when GenerateReviewStep mounts.
10
- *
11
- * New order:
12
- * welcome → token-input → path-validation → extracting → scope-gate
13
- * → credentials (validates inline) → generating
14
- * → generating → final-review → push-decision-gate → previewing
15
- * → preview-gate → pushing → done
16
- *
17
- * `--no-push` short-circuits the credentials/preview/push branch — the operator
18
- * just wants extract + classify + final-review without ever touching Contentful.
19
- */
20
1
  export type WizardStepAfterScope = 'credentials' | 'generating' | 'push-decision-gate' | 'print-gate';
21
2
  export type WizardStepAfterCredentials = 'generating' | 'push-decision-gate';
22
- /**
23
- * Transition target after the scope-gate confirms.
24
- *
25
- * - acceptedCount > 0:
26
- * - !noPush → `credentials` (gather + validate creds upfront, before LLM cost)
27
- * - noPush → `generating` (skip creds entirely; live preview no-ops gracefully)
28
- * - acceptedCount === 0 (everything rejected):
29
- * - !noPush → `credentials` (still need creds to push tokens / removals)
30
- * - noPush → `print-gate` (nothing to do; let the operator save files and exit)
31
- */
32
3
  export declare function nextStepAfterScopeGate(opts: {
33
4
  acceptedCount: number;
34
5
  noPush: boolean;
35
6
  }): WizardStepAfterScope;
36
- /**
37
- * Transition target after credentials are validated (or skipped via the
38
- * inline validation pings the API). Drives the post-credentials branch:
39
- * - acceptedCount > 0 → `generating` (run the generator now that creds are good)
40
- * - acceptedCount === 0 → `push-decision-gate` (no components to generate;
41
- * skip straight to push-decision and preview)
42
- */
43
7
  export declare function nextStepAfterCredentialsValidated(opts: {
44
8
  acceptedCount: number;
45
9
  }): WizardStepAfterCredentials;
@@ -48,38 +12,20 @@ export declare function shouldSkipFinalReviewAfterCredentials(state: {
48
12
  finalReviewPassed: boolean;
49
13
  }): boolean;
50
14
  export declare function resolveNoCacheForGenerate(opts: {
51
- isFreshSession: boolean;
52
15
  cliNoCache: boolean;
53
16
  }): boolean;
54
- /**
55
- * Skip-credentials spec — Task 2. When the operator pressed `s` on the
56
- * credentials screen, the wizard advanced without validating creds. That
57
- * makes `previewImport` impossible (no working token) and undesirable (the
58
- * whole point is to inspect locally without touching the server). Callers
59
- * check this before issuing the API call and route to
60
- * `buildSkippedPreviewTransition()` instead.
61
- */
62
17
  export declare function shouldBypassPreview(state: {
63
18
  credentialsSkipped: boolean;
64
19
  }): boolean;
65
- /**
66
- * State patch applied when `shouldBypassPreview` is true: skip the preview
67
- * step entirely, surface no server preview to downstream consumers, and
68
- * hand control to the push-decision-gate (which will render with push
69
- * disabled — see Task 3).
70
- */
71
20
  export declare function buildSkippedPreviewTransition(): {
72
21
  step: 'push-decision-gate';
73
22
  serverPreview: null;
74
23
  };
75
- /**
76
- * Skip-credentials spec — Task 4 (defensive guard). The push-decision-gate
77
- * disables "Save AND push" and "Push only" when credentialsSkipped is true,
78
- * so `runPush` should never be reached. If a state-machine regression ever
79
- * routed an operator past that guard, `runPush` checks this helper and
80
- * refuses to issue the API call — instead it routes back to the print-files
81
- * local-save path via `buildSkippedPushTransition`.
82
- */
24
+ export type CycleGateAction = 'block' | 'auto-reject' | 'proceed';
25
+ export declare function resolveCycleGateAction(opts: {
26
+ hasCycles: boolean;
27
+ autoRejectCycles: boolean;
28
+ }): CycleGateAction;
83
29
  export declare function shouldRefusePush(state: {
84
30
  credentialsSkipped: boolean;
85
31
  }): boolean;