@contentful/experience-design-system-cli 2.26.1-dev-build-a852362.0 → 2.26.1-dev-build-f03f014.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 (204) hide show
  1. package/dist/package.json +14 -10
  2. package/dist/src/index.js +34937 -9
  3. package/dist/src/lib/cli-path.d.ts +4 -0
  4. package/dist/src/runs/export-helpers.d.ts +1 -12
  5. package/dist/src/runs/push-helpers.d.ts +1 -6
  6. package/package.json +12 -7
  7. package/skills/generate-components.md +427 -0
  8. package/skills/generate-tokens.md +194 -0
  9. package/skills/select-components.md +224 -0
  10. package/dist/src/analytics/apply.js +0 -30
  11. package/dist/src/analytics/client.js +0 -81
  12. package/dist/src/analytics/constants.js +0 -4
  13. package/dist/src/analytics/env.js +0 -14
  14. package/dist/src/analytics/exit.js +0 -24
  15. package/dist/src/analytics/index.js +0 -9
  16. package/dist/src/analytics/normalize.js +0 -18
  17. package/dist/src/analytics/os.js +0 -13
  18. package/dist/src/analytics/session.js +0 -15
  19. package/dist/src/analytics/tracker.js +0 -126
  20. package/dist/src/analytics/types.js +0 -1
  21. package/dist/src/analyze/build-analyze-view-rows.js +0 -63
  22. package/dist/src/analyze/command.js +0 -599
  23. package/dist/src/analyze/composite-closure.js +0 -0
  24. package/dist/src/analyze/composition/agent-parser/author-prompt.js +0 -22
  25. package/dist/src/analyze/composition/agent-parser/extract-parser.js +0 -44
  26. package/dist/src/analyze/composition/agent-parser/load-prompt.js +0 -31
  27. package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +0 -88
  28. package/dist/src/analyze/composition/agent-parser/sandbox.js +0 -187
  29. package/dist/src/analyze/composition/apply-mapping.js +0 -67
  30. package/dist/src/analyze/composition/candidate-critic-agent.js +0 -39
  31. package/dist/src/analyze/composition/candidate-critic.js +0 -63
  32. package/dist/src/analyze/composition/candidate-files.js +0 -131
  33. package/dist/src/analyze/composition/composition-cache-key.js +0 -19
  34. package/dist/src/analyze/composition/interchange-schema.js +0 -61
  35. package/dist/src/analyze/composition/manifest-doc-evidence.js +0 -136
  36. package/dist/src/analyze/composition/merge-edges.js +0 -70
  37. package/dist/src/analyze/composition/parse-map-edges.js +0 -55
  38. package/dist/src/analyze/composition/resolve-mapping-cli.js +0 -29
  39. package/dist/src/analyze/composition/resolve-mapping.js +0 -111
  40. package/dist/src/analyze/cycle-detection.js +0 -234
  41. package/dist/src/analyze/cycle-view.js +0 -28
  42. package/dist/src/analyze/fuzzy-search.js +0 -55
  43. package/dist/src/analyze/issue-inheritance.js +0 -108
  44. package/dist/src/analyze/lineage.js +0 -0
  45. package/dist/src/analyze/scope-gate-cascade.js +0 -170
  46. package/dist/src/analyze/search-neighborhood.js +0 -56
  47. package/dist/src/analyze/select/command.js +0 -484
  48. package/dist/src/analyze/select/index.js +0 -5
  49. package/dist/src/analyze/select/parser.js +0 -53
  50. package/dist/src/analyze/select/persistence.js +0 -80
  51. package/dist/src/analyze/select/preview-annotations.js +0 -62
  52. package/dist/src/analyze/select/stdout.js +0 -3
  53. package/dist/src/analyze/select/tui/App.js +0 -483
  54. package/dist/src/analyze/select/tui/components/ComponentDetail.js +0 -48
  55. package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +0 -125
  56. package/dist/src/analyze/select/tui/components/FieldEditor.js +0 -1131
  57. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +0 -36
  58. package/dist/src/analyze/select/tui/components/GotoBanner.js +0 -30
  59. package/dist/src/analyze/select/tui/components/GroupedSidebar.js +0 -517
  60. package/dist/src/analyze/select/tui/components/HelpOverlay.js +0 -17
  61. package/dist/src/analyze/select/tui/components/JsonEditor.js +0 -131
  62. package/dist/src/analyze/select/tui/components/JsonPanel.js +0 -66
  63. package/dist/src/analyze/select/tui/components/LineagePanel.js +0 -22
  64. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +0 -29
  65. package/dist/src/analyze/select/tui/components/QuitDialog.js +0 -14
  66. package/dist/src/analyze/select/tui/components/RationalePanel.js +0 -80
  67. package/dist/src/analyze/select/tui/components/Sidebar.js +0 -92
  68. package/dist/src/analyze/select/tui/components/SourcePanel.js +0 -52
  69. package/dist/src/analyze/select/tui/components/StatusBar.js +0 -7
  70. package/dist/src/analyze/select/tui/components/TopBar.js +0 -10
  71. package/dist/src/analyze/select/tui/components/removed-components-text.js +0 -11
  72. package/dist/src/analyze/select/tui/hooks/scroll-offset.js +0 -26
  73. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +0 -66
  74. package/dist/src/analyze/select/tui/hooks/useKeymap.js +0 -67
  75. package/dist/src/analyze/select/tui/hooks/useSession.js +0 -52
  76. package/dist/src/analyze/select/tui/hooks/useUndo.js +0 -26
  77. package/dist/src/analyze/select/tui/theme.js +0 -28
  78. package/dist/src/analyze/select/types.js +0 -39
  79. package/dist/src/analyze/select-agent/command.js +0 -558
  80. package/dist/src/analyze/select-agent/context-builder.js +0 -197
  81. package/dist/src/analyze/select-agent/show-rationale.js +0 -92
  82. package/dist/src/analyze/selection-cascade.js +0 -19
  83. package/dist/src/analyze/slot-graph.js +0 -17
  84. package/dist/src/analyze/tui/AnalyzeView.js +0 -64
  85. package/dist/src/apply/api-client.js +0 -440
  86. package/dist/src/apply/command.js +0 -845
  87. package/dist/src/apply/manifest.js +0 -1
  88. package/dist/src/apply/preview-utils.js +0 -26
  89. package/dist/src/apply/tui/SelectView.js +0 -34
  90. package/dist/src/apply/tui/ServerApplyView.js +0 -49
  91. package/dist/src/apply/tui/ServerPreviewView.js +0 -23
  92. package/dist/src/credentials-store.js +0 -74
  93. package/dist/src/generate/command.js +0 -597
  94. package/dist/src/generate/edit/command.js +0 -127
  95. package/dist/src/generate/tui/GenerateView.js +0 -10
  96. package/dist/src/host-utils.js +0 -26
  97. package/dist/src/import/agent-model-resolve.js +0 -35
  98. package/dist/src/import/auto-filter-resolve.js +0 -16
  99. package/dist/src/import/command.js +0 -380
  100. package/dist/src/import/cycle-auto-reject.js +0 -14
  101. package/dist/src/import/orchestrator.js +0 -603
  102. package/dist/src/import/path-utils.js +0 -30
  103. package/dist/src/import/picker-dispatch.js +0 -44
  104. package/dist/src/import/print-prompt.js +0 -31
  105. package/dist/src/import/strip-allowed-components.js +0 -22
  106. package/dist/src/import/tui/CustomPromptBanner.js +0 -8
  107. package/dist/src/import/tui/WizardApp.js +0 -1670
  108. package/dist/src/import/tui/ai-flag.js +0 -6
  109. package/dist/src/import/tui/auto-filter-error.js +0 -29
  110. package/dist/src/import/tui/autocomplete.js +0 -32
  111. package/dist/src/import/tui/components/AutoFilterBanner.js +0 -19
  112. package/dist/src/import/tui/components/CounterStrip.js +0 -12
  113. package/dist/src/import/tui/components/LegendEntry.js +0 -6
  114. package/dist/src/import/tui/cycle-panel-scroll.js +0 -33
  115. package/dist/src/import/tui/final-review-host.js +0 -25
  116. package/dist/src/import/tui/group-collapse.js +0 -11
  117. package/dist/src/import/tui/history.js +0 -53
  118. package/dist/src/import/tui/hooks/useLineage.js +0 -50
  119. package/dist/src/import/tui/hooks/useOverlayPanel.js +0 -27
  120. package/dist/src/import/tui/lineage-layout.js +0 -82
  121. package/dist/src/import/tui/merge-ai-decisions.js +0 -22
  122. package/dist/src/import/tui/push-decision-gate-helpers.js +0 -8
  123. package/dist/src/import/tui/push-progress.js +0 -1
  124. package/dist/src/import/tui/run-print-files-helpers.js +0 -8
  125. package/dist/src/import/tui/run-teaser.js +0 -9
  126. package/dist/src/import/tui/runLivePreview.js +0 -82
  127. package/dist/src/import/tui/runScopeGate.js +0 -28
  128. package/dist/src/import/tui/scope-gate-columns.js +0 -94
  129. package/dist/src/import/tui/scope-gate-host.js +0 -28
  130. package/dist/src/import/tui/sidebar-width.js +0 -3
  131. package/dist/src/import/tui/spawn-generate.js +0 -53
  132. package/dist/src/import/tui/step-filters.js +0 -42
  133. package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +0 -726
  134. package/dist/src/import/tui/steps/AtomicScopeGateStep.js +0 -210
  135. package/dist/src/import/tui/steps/CredentialsStep.js +0 -108
  136. package/dist/src/import/tui/steps/DoneStep.js +0 -24
  137. package/dist/src/import/tui/steps/ErrorStep.js +0 -16
  138. package/dist/src/import/tui/steps/GateStep.js +0 -22
  139. package/dist/src/import/tui/steps/GenerateReviewStep.js +0 -1618
  140. package/dist/src/import/tui/steps/PathValidationStep.js +0 -152
  141. package/dist/src/import/tui/steps/PreviewStep.js +0 -37
  142. package/dist/src/import/tui/steps/PreviewValidationErrorStep.js +0 -23
  143. package/dist/src/import/tui/steps/PushDecisionGateStep.js +0 -73
  144. package/dist/src/import/tui/steps/PushingStep.js +0 -24
  145. package/dist/src/import/tui/steps/RunningStep.js +0 -21
  146. package/dist/src/import/tui/steps/ScopeGateStep.js +0 -907
  147. package/dist/src/import/tui/steps/TokenInputStep.js +0 -88
  148. package/dist/src/import/tui/steps/WelcomeStep.js +0 -34
  149. package/dist/src/import/tui/steps/WizardPreviewStep.js +0 -169
  150. package/dist/src/import/tui/steps/auto-reject-decision.js +0 -9
  151. package/dist/src/import/tui/steps/breaking-change-format.js +0 -24
  152. package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
  153. package/dist/src/import/tui/steps/preview-diff.js +0 -175
  154. package/dist/src/import/tui/useFinalizePreview.js +0 -0
  155. package/dist/src/import/tui/useLivePreview.js +0 -122
  156. package/dist/src/import/tui/wizard-422-helpers.js +0 -60
  157. package/dist/src/import/tui/wizard-generate-progress.js +0 -25
  158. package/dist/src/import/tui/wizard-save-flow.js +0 -20
  159. package/dist/src/import/tui/wizard-state-transitions.js +0 -32
  160. package/dist/src/lib/agent-model-options.js +0 -15
  161. package/dist/src/lib/command-options.js +0 -30
  162. package/dist/src/lib/composition-mode.js +0 -39
  163. package/dist/src/lib/contentful-urls.js +0 -27
  164. package/dist/src/lib/debug-logger.js +0 -229
  165. package/dist/src/lib/debug-preamble.js +0 -33
  166. package/dist/src/lib/error-parser.js +0 -269
  167. package/dist/src/lib/prompt-overrides.js +0 -69
  168. package/dist/src/lib/terminal-capabilities.js +0 -51
  169. package/dist/src/lib/user-agent.js +0 -29
  170. package/dist/src/output/format.js +0 -110
  171. package/dist/src/print/command.js +0 -221
  172. package/dist/src/print/validate/tui/ValidateView.js +0 -37
  173. package/dist/src/print/validate/validators/cdf-validator.js +0 -104
  174. package/dist/src/print/validate/validators/dtcg-validator.js +0 -110
  175. package/dist/src/print/validate/validators/format-errors.js +0 -18
  176. package/dist/src/program.js +0 -105
  177. package/dist/src/runs/export-helpers.js +0 -59
  178. package/dist/src/runs/fingerprint.js +0 -75
  179. package/dist/src/runs/ls-command.js +0 -137
  180. package/dist/src/runs/modify-launcher.js +0 -42
  181. package/dist/src/runs/path-prompt.js +0 -72
  182. package/dist/src/runs/push-creds-prompt.js +0 -39
  183. package/dist/src/runs/push-helpers.js +0 -72
  184. package/dist/src/runs/push-launcher.js +0 -77
  185. package/dist/src/runs/replay-helpers.js +0 -164
  186. package/dist/src/runs/resolve-run-target.js +0 -45
  187. package/dist/src/runs/run-picker-mount.js +0 -72
  188. package/dist/src/runs/run-picker.js +0 -129
  189. package/dist/src/runs/save-conflict.js +0 -56
  190. package/dist/src/runs/save-path-resolver.js +0 -75
  191. package/dist/src/runs/staleness.js +0 -140
  192. package/dist/src/runs/store.js +0 -136
  193. package/dist/src/session/cache-keys.js +0 -44
  194. package/dist/src/session/command.js +0 -261
  195. package/dist/src/session/db.js +0 -1538
  196. package/dist/src/session/migration.js +0 -117
  197. package/dist/src/session/session-id.js +0 -212
  198. package/dist/src/session/stats.js +0 -89
  199. package/dist/src/setup/analytics-prompt.js +0 -24
  200. package/dist/src/setup/auto-filter-prompt.js +0 -24
  201. package/dist/src/setup/command.js +0 -966
  202. package/dist/src/setup/composition-mode-prompt.js +0 -37
  203. package/dist/src/setup/debug-mode-prompt.js +0 -22
  204. package/dist/src/types.js +0 -1
@@ -1,88 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect } from 'react';
3
- import { PALETTE } from '../../../analyze/select/tui/theme.js';
4
- import { Box, Text } from 'ink';
5
- import { statSync } from 'node:fs';
6
- import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
7
- import { normalizePath } from '../../path-utils.js';
8
- export function TokenInputStep({ onConfirm, onSkip, onQuit }) {
9
- const [inputValue, setInputValue] = useState('');
10
- const [cursorVisible, setCursorVisible] = useState(true);
11
- const [error, setError] = useState(null);
12
- const [resolvedPath, setResolvedPath] = useState(null);
13
- const [typingMode, setTypingMode] = useState(false);
14
- useEffect(() => {
15
- const interval = setInterval(() => setCursorVisible((v) => !v), 500);
16
- return () => clearInterval(interval);
17
- }, []);
18
- useImmediateInput((input, key) => {
19
- if (key.return) {
20
- const trimmed = inputValue.trim();
21
- if (!trimmed) {
22
- onSkip();
23
- return;
24
- }
25
- const normalized = normalizePath(trimmed);
26
- let stat;
27
- try {
28
- stat = statSync(normalized);
29
- }
30
- catch {
31
- setError(`Path not found: ${normalized}`);
32
- setResolvedPath(normalized);
33
- return;
34
- }
35
- if (stat.isDirectory()) {
36
- setError(`That's a directory — provide a path to a token file (e.g. tokens.json), not a folder.`);
37
- setResolvedPath(normalized);
38
- return;
39
- }
40
- if (!stat.isFile()) {
41
- setError(`Not a regular file: ${normalized}`);
42
- setResolvedPath(normalized);
43
- return;
44
- }
45
- setError(null);
46
- setResolvedPath(normalized);
47
- onConfirm(normalized);
48
- return;
49
- }
50
- if (key.tab) {
51
- setTypingMode(false);
52
- return;
53
- }
54
- if (key.escape) {
55
- if (typingMode) {
56
- setTypingMode(false);
57
- setInputValue('');
58
- setError(null);
59
- setResolvedPath(null);
60
- return;
61
- }
62
- onQuit();
63
- return;
64
- }
65
- if (!typingMode && input === 's') {
66
- onSkip();
67
- return;
68
- }
69
- if (!typingMode && input === 'q') {
70
- onQuit();
71
- return;
72
- }
73
- if (key.backspace || key.delete) {
74
- setInputValue((v) => v.slice(0, -1));
75
- setError(null);
76
- setResolvedPath(null);
77
- return;
78
- }
79
- if (input && !key.ctrl && !key.meta) {
80
- setTypingMode(true);
81
- setInputValue((v) => v + input);
82
- setError(null);
83
- setResolvedPath(null);
84
- }
85
- });
86
- const displayValue = inputValue + (cursorVisible ? '█' : ' ');
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" })] })] }));
88
- }
@@ -1,34 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect } from 'react';
3
- import { PALETTE } from '../../../analyze/select/tui/theme.js';
4
- import { Box, Text } from 'ink';
5
- import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
6
- export function WelcomeStep({ onContinue, onQuit }) {
7
- const [inputValue, setInputValue] = useState('');
8
- const [cursorVisible, setCursorVisible] = useState(true);
9
- useEffect(() => {
10
- const interval = setInterval(() => setCursorVisible((v) => !v), 500);
11
- return () => clearInterval(interval);
12
- }, []);
13
- useImmediateInput((input, key) => {
14
- if (key.return) {
15
- const trimmed = inputValue.trim();
16
- if (trimmed)
17
- onContinue(trimmed);
18
- return;
19
- }
20
- if (key.escape || input === 'q') {
21
- onQuit();
22
- return;
23
- }
24
- if (key.backspace || key.delete) {
25
- setInputValue((v) => v.slice(0, -1));
26
- return;
27
- }
28
- if (input && !key.ctrl && !key.meta) {
29
- setInputValue((v) => v + input);
30
- }
31
- });
32
- const displayValue = inputValue + (cursorVisible ? '█' : ' ');
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" })] })] }));
34
- }
@@ -1,169 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState, useMemo } from 'react';
3
- import { PALETTE } from '../../../analyze/select/tui/theme.js';
4
- import { Box, Text, useStdout } from 'ink';
5
- import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
6
- import { hasBreakingChangesWithImpact } from '../../../apply/manifest.js';
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
- }
92
- export function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber, totalSteps, allowDeletions: fetchedAllowDeletions = false, onConfirm, onEdit, onSaveFiles, onQuit, }) {
93
- const breakingWithImpact = hasBreakingChangesWithImpact(preview);
94
- const [diffExpanded, setDiffExpanded] = useState(false);
95
- const [scrollOffset, setScrollOffset] = useState(0);
96
- // Local state exists only to let the user opt OUT of a deletion the fetch
97
- // already surfaced — it can never turn true when the fetch used false,
98
- // because there's nothing in `preview` to reveal in that case.
99
- const [allowDeletions, setAllowDeletions] = useState(fetchedAllowDeletions);
100
- const { stdout } = useStdout();
101
- const terminalRows = stdout?.rows ?? 40;
102
- const viewportHeight = Math.max(terminalRows - 14, 10);
103
- const allDiffLines = useMemo(() => {
104
- if (!diffExpanded)
105
- return [];
106
- return buildPreviewDiffLines(preview).map((line) => ({
107
- key: line.key,
108
- element: (_jsx(Text, { color: line.color === 'gray' ? undefined : line.color, dimColor: line.color === 'gray', children: line.text })),
109
- }));
110
- }, [diffExpanded, preview]);
111
- const maxScroll = Math.max(0, allDiffLines.length - viewportHeight);
112
- const removedCount = preview.components.removed.length + preview.tokens.removed.length;
113
- useImmediateInput((input, key) => {
114
- if (key.return) {
115
- onConfirm(breakingWithImpact, allowDeletions);
116
- return;
117
- }
118
- if ((input === 'x' || input === 'X') && fetchedAllowDeletions && removedCount > 0) {
119
- setAllowDeletions((prev) => !prev);
120
- return;
121
- }
122
- if (input === 'd' || input === 'D') {
123
- setDiffExpanded((prev) => !prev);
124
- setScrollOffset(0);
125
- return;
126
- }
127
- if (diffExpanded) {
128
- if (input === 'j' || key.downArrow) {
129
- setScrollOffset((prev) => Math.min(prev + 1, maxScroll));
130
- return;
131
- }
132
- if (input === 'k' || key.upArrow) {
133
- setScrollOffset((prev) => Math.max(prev - 1, 0));
134
- return;
135
- }
136
- if (input === 'f') {
137
- setScrollOffset((prev) => Math.min(prev + viewportHeight, maxScroll));
138
- return;
139
- }
140
- if (input === 'b') {
141
- setScrollOffset((prev) => Math.max(prev - viewportHeight, 0));
142
- return;
143
- }
144
- }
145
- if ((input === 'e' || input === 'E') && onEdit) {
146
- onEdit();
147
- return;
148
- }
149
- if (input === 's' && onSaveFiles) {
150
- onSaveFiles();
151
- return;
152
- }
153
- if (input === 'q' || key.escape) {
154
- onQuit();
155
- return;
156
- }
157
- });
158
- const { components, tokens } = preview;
159
- const hasComponents = components.new.length + components.changed.length + components.removed.length > 0;
160
- const hasTokens = tokens.new.length + tokens.changed.length + tokens.removed.length > 0;
161
- const hasAnything = hasComponents || hasTokens;
162
- 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) => {
163
- const name = item.key ?? item.$name ?? 'unknown';
164
- return (_jsxs(Text, { color: PALETTE.success, children: [' ', "+ ", name] }, `new-${i}`));
165
- })] })), 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) => {
166
- const isBreaking = item.changeClassification?.classification === 'breaking';
167
- return (_jsxs(Text, { color: isBreaking ? PALETTE.error : PALETTE.warning, children: [' ', isBreaking ? '⚠' : '~', " ", item.current.name] }, `chg-${i}`));
168
- })] })), components.removed.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: allowDeletions ? PALETTE.error : PALETTE.warning, children: allowDeletions ? ' ✗' : ' ⊘' }), _jsxs(Text, { children: [components.removed.length, " will be ", allowDeletions ? 'deleted' : 'skipped'] })] }), components.removed.map((item, i) => (_jsxs(Text, { color: allowDeletions ? PALETTE.error : PALETTE.warning, dimColor: !allowDeletions, children: [' ', allowDeletions ? '✗' : '⊘', " ", 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: allowDeletions ? PALETTE.error : PALETTE.warning, children: allowDeletions ? ' ✗' : ' ⊘' }), _jsxs(Text, { children: [tokens.removed.length, " will be ", allowDeletions ? 'deleted' : 'skipped'] })] })), 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." }) })), allowDeletions && removedCount > 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: PALETTE.error, bold: true, children: ["\u26A0 ", removedCount, " missing ", removedCount === 1 ? 'entity' : 'entities', " will be permanently deleted. Press Enter to confirm."] }) })), _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" }), fetchedAllowDeletions && removedCount > 0 && (_jsxs(Text, { dimColor: true, children: ["[x] ", allowDeletions ? '[✓]' : '[ ]', " Allow deletions"] })), _jsx(Text, { dimColor: true, children: "[q] Cancel" })] })] }));
169
- }
@@ -1,9 +0,0 @@
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
- }
@@ -1,24 +0,0 @@
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
- }
@@ -1,175 +0,0 @@
1
- export function computeComponentDiffLines(current, proposed, changeClassification) {
2
- const lines = [];
3
- // Use fullProperties keys as authoritative server state (includes state props, not just content+design)
4
- const currentProps = current.fullProperties
5
- ? new Set(Object.keys(current.fullProperties))
6
- : new Set([...current.contentProperties, ...current.designProperties]);
7
- const proposedProperties = (proposed['$properties'] ?? {});
8
- const proposedProps = new Set(Object.keys(proposedProperties));
9
- const oldProps = current.fullProperties;
10
- for (const name of [...proposedProps].sort()) {
11
- if (!currentProps.has(name)) {
12
- const def = proposedProperties[name];
13
- lines.push({ key: `${name}-add`, color: 'green', text: `+ ${name}: ${formatPropDef(def)}` });
14
- }
15
- }
16
- for (const name of [...currentProps].sort()) {
17
- if (!proposedProps.has(name)) {
18
- if (oldProps?.[name]) {
19
- lines.push({ key: `${name}-rm`, color: 'red', text: `- ${name}: ${formatOldProp(oldProps[name])}` });
20
- }
21
- else {
22
- const cat = current.contentProperties.includes(name) ? 'content' : 'design';
23
- lines.push({ key: `${name}-rm`, color: 'red', text: `- ${name} (${cat})` });
24
- }
25
- }
26
- }
27
- const handledProps = new Set();
28
- for (const name of [...currentProps].sort()) {
29
- if (!proposedProps.has(name))
30
- continue;
31
- const newDef = proposedProperties[name];
32
- const oldDef = oldProps?.[name];
33
- if (!oldDef)
34
- continue;
35
- const diffs = diffProperty(name, oldDef, newDef);
36
- if (diffs.length > 0) {
37
- handledProps.add(name);
38
- for (const d of diffs)
39
- lines.push(d);
40
- }
41
- }
42
- if (!oldProps && changeClassification?.breakingChanges) {
43
- for (const bc of changeClassification.breakingChanges) {
44
- if (!('propertyId' in bc))
45
- continue;
46
- if (!currentProps.has(bc.propertyId) || !proposedProps.has(bc.propertyId))
47
- continue;
48
- if (handledProps.has(bc.propertyId))
49
- continue;
50
- handledProps.add(bc.propertyId);
51
- const def = proposedProperties[bc.propertyId];
52
- lines.push({ key: `${bc.propertyId}-old`, color: 'red', text: `- ${bc.propertyId}: ${bc.reason}` });
53
- lines.push({ key: `${bc.propertyId}-new`, color: 'green', text: `+ ${bc.propertyId}: ${formatPropDef(def)}` });
54
- }
55
- }
56
- const currentSlots = new Set(current.slots);
57
- const proposedSlots = (proposed['$slots'] ?? {});
58
- const proposedSlotNames = new Set(Object.keys(proposedSlots));
59
- const currentSlotAllowed = current.currentSlotAllowed ?? {};
60
- for (const name of [...proposedSlotNames].sort()) {
61
- if (!currentSlots.has(name)) {
62
- lines.push({ key: `slot-${name}-add`, color: 'green', text: `+ slot: ${name}` });
63
- }
64
- }
65
- for (const name of [...currentSlots].sort()) {
66
- if (!proposedSlotNames.has(name)) {
67
- lines.push({ key: `slot-${name}-rm`, color: 'red', text: `- slot: ${name}` });
68
- }
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
- }
97
- return lines;
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
- }
117
- function normalizeType(type) {
118
- switch (type.toLowerCase()) {
119
- case 'boolean':
120
- return 'boolean';
121
- case 'number':
122
- return 'number';
123
- case 'symbol':
124
- case 'enum':
125
- return 'symbol';
126
- case 'string':
127
- case 'text':
128
- case 'richtext':
129
- case 'media':
130
- return 'string';
131
- default:
132
- return type.toLowerCase();
133
- }
134
- }
135
- export function diffProperty(name, old, newDef) {
136
- const diffs = [];
137
- const newType = newDef['$type'] ?? '';
138
- const newCategory = newDef['$category'] ?? '';
139
- const newRequired = newDef['$required'] === true;
140
- const newDefault = newDef['$default'];
141
- const typeChanged = old.type !== '' && newType !== '' && normalizeType(old.type) !== normalizeType(newType);
142
- const categoryChanged = old.category !== newCategory;
143
- const requiredChanged = old.required !== newRequired;
144
- const defaultChanged = JSON.stringify(old.default) !== JSON.stringify(newDefault);
145
- if (!typeChanged && !categoryChanged && !requiredChanged && !defaultChanged)
146
- return [];
147
- diffs.push({ key: `${name}-old`, color: 'red', text: `- ${name}: ${formatOldProp(old)}` });
148
- diffs.push({ key: `${name}-new`, color: 'green', text: `+ ${name}: ${formatPropDef(newDef)}` });
149
- return diffs;
150
- }
151
- export function formatOldProp(prop) {
152
- const parts = [];
153
- if (prop.type)
154
- parts.push(prop.type);
155
- if (prop.category)
156
- parts.push(prop.category);
157
- parts.push(prop.required ? 'required' : 'optional');
158
- if (prop.default !== undefined)
159
- parts.push(`default=${JSON.stringify(prop.default)}`);
160
- return parts.join(', ');
161
- }
162
- export function formatPropDef(def) {
163
- const parts = [];
164
- if (def['$type'])
165
- parts.push(def['$type']);
166
- if (def['$category'])
167
- parts.push(def['$category']);
168
- if (def['$required'] === true)
169
- parts.push('required');
170
- else
171
- parts.push('optional');
172
- if (def['$default'] !== undefined)
173
- parts.push(`default=${JSON.stringify(def['$default'])}`);
174
- return parts.join(', ');
175
- }
@@ -1,122 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState } from 'react';
2
- import { ApiError } from '../../apply/api-client.js';
3
- import { runLivePreview } from './runLivePreview.js';
4
- const DEFAULT_DEBOUNCE_MS = 500;
5
- /**
6
- * Hook that owns the post-save live-preview side effect for the wizard's
7
- * final-review step. Centralizes:
8
- *
9
- * - 500ms debounce so rapid Ctrl+S spam collapses to a single API call.
10
- * - Generation tag on every fire so a stale in-flight call's response is
11
- * discarded if a newer fire has already kicked off (in lieu of an
12
- * AbortController for v1).
13
- * - Disable-for-session on 401/403: subsequent triggers no-op until the
14
- * wizard exits. One-line stderr warning. No modal, no re-prompt — the
15
- * operator is mid-edit and we must not interrupt the flow.
16
- * - Status state for the sidebar spinner (`'idle' | 'running'`).
17
- *
18
- * Cred-missing / disabled / not-enabled cases short-circuit BEFORE the timer
19
- * is set so we don't accumulate dead timers.
20
- */
21
- export function useLivePreview(opts) {
22
- const debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
23
- const [status, setStatus] = useState('idle');
24
- const [disabled, setDisabled] = useState(false);
25
- const timerRef = useRef(null);
26
- const generationRef = useRef(0);
27
- const latestRef = useRef(0);
28
- const inflightRef = useRef(0);
29
- // Mirror props in a ref so the trigger callback can stay stable.
30
- const optsRef = useRef(opts);
31
- useEffect(() => {
32
- optsRef.current = opts;
33
- });
34
- // Mirror disabled into a ref so the timer callback sees the latest value
35
- // without redefining `trigger` (which would defeat its memoization).
36
- const disabledRef = useRef(disabled);
37
- useEffect(() => {
38
- disabledRef.current = disabled;
39
- }, [disabled]);
40
- // Clear any pending timer on unmount.
41
- useEffect(() => {
42
- return () => {
43
- if (timerRef.current) {
44
- clearTimeout(timerRef.current);
45
- timerRef.current = null;
46
- }
47
- };
48
- }, []);
49
- const trigger = useCallback(() => {
50
- const current = optsRef.current;
51
- if (!current.enabled)
52
- return;
53
- if (disabledRef.current)
54
- return;
55
- if (!current.spaceId || !current.environmentId || !current.cmaToken)
56
- return;
57
- if (timerRef.current) {
58
- clearTimeout(timerRef.current);
59
- timerRef.current = null;
60
- }
61
- timerRef.current = setTimeout(() => {
62
- timerRef.current = null;
63
- void fire();
64
- }, debounceMs);
65
- }, [debounceMs]);
66
- const fire = async () => {
67
- const current = optsRef.current;
68
- if (disabledRef.current)
69
- return;
70
- generationRef.current += 1;
71
- const generation = generationRef.current;
72
- latestRef.current = generation;
73
- inflightRef.current += 1;
74
- setStatus('running');
75
- try {
76
- const result = await runLivePreview({
77
- sessionId: current.sessionId,
78
- tokensPath: current.tokensPath,
79
- spaceId: current.spaceId,
80
- environmentId: current.environmentId,
81
- cmaToken: current.cmaToken,
82
- host: current.host,
83
- generation,
84
- deleteAllComponents: current.deleteAllComponents === true,
85
- allowDeletions: current.allowDeletions === true,
86
- });
87
- // Discard stale responses (generation tag).
88
- if (result.generation !== latestRef.current)
89
- return;
90
- current.onResult(result.response);
91
- }
92
- catch (err) {
93
- if (err instanceof ApiError && (err.status === 401 || err.status === 403)) {
94
- setDisabled(true);
95
- disabledRef.current = true;
96
- try {
97
- process.stderr.write(`live-preview: ${err.status}, disabling for this session\n`);
98
- }
99
- catch {
100
- // best-effort
101
- }
102
- }
103
- else {
104
- const msg = err instanceof Error ? err.message : String(err);
105
- try {
106
- process.stderr.write(`live-preview: ${msg}\n`);
107
- }
108
- catch {
109
- // best-effort
110
- }
111
- }
112
- }
113
- finally {
114
- inflightRef.current -= 1;
115
- if (inflightRef.current <= 0) {
116
- inflightRef.current = 0;
117
- setStatus('idle');
118
- }
119
- }
120
- };
121
- return { trigger, status, disabled };
122
- }