@contentful/experience-design-system-cli 2.7.1-dev-build-02830eb.0 → 2.7.2-dev-build-bc65c2b.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 (34) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/analyze/select/tui/App.js +441 -54
  3. package/dist/src/analyze/select/tui/components/ComponentDetail.d.ts +4 -3
  4. package/dist/src/analyze/select/tui/components/ComponentDetail.js +2 -2
  5. package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +1 -1
  6. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +10 -2
  7. package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +1 -1
  8. package/dist/src/analyze/select/tui/components/HelpOverlay.js +7 -2
  9. package/dist/src/analyze/select/tui/components/JsonEditor.d.ts +4 -7
  10. package/dist/src/analyze/select/tui/components/JsonEditor.js +125 -17
  11. package/dist/src/analyze/select/tui/components/QuitDialog.d.ts +1 -1
  12. package/dist/src/analyze/select/tui/components/QuitDialog.js +10 -2
  13. package/dist/src/analyze/select/tui/components/Sidebar.d.ts +2 -0
  14. package/dist/src/analyze/select/tui/components/StatusBar.d.ts +2 -0
  15. package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +0 -5
  16. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +4 -7
  17. package/dist/src/analyze/select/tui/hooks/useKeymap.d.ts +23 -0
  18. package/dist/src/analyze/select/tui/hooks/useKeymap.js +67 -1
  19. package/dist/src/analyze/select/tui/hooks/useUndo.js +21 -15
  20. package/dist/src/analyze/tui/AnalyzeView.js +0 -2
  21. package/dist/src/import/tui/WizardApp.js +0 -2
  22. package/dist/src/import/tui/steps/GenerateReviewStep.js +11 -4
  23. package/dist/src/print/validate/tui/ValidateView.js +0 -2
  24. package/package.json +2 -2
  25. package/dist/src/analyze/select/tui/hooks/useRawMode.d.ts +0 -6
  26. package/dist/src/analyze/select/tui/hooks/useRawMode.js +0 -16
  27. package/dist/src/analyze/select/tui/hooks/useSideEffects.d.ts +0 -17
  28. package/dist/src/analyze/select/tui/hooks/useSideEffects.js +0 -226
  29. package/dist/src/analyze/select/tui/inputToAction.d.ts +0 -13
  30. package/dist/src/analyze/select/tui/inputToAction.js +0 -68
  31. package/dist/src/analyze/select/tui/state.d.ts +0 -136
  32. package/dist/src/analyze/select/tui/state.js +0 -385
  33. package/dist/src/analyze/select/tui/utils.d.ts +0 -5
  34. package/dist/src/analyze/select/tui/utils.js +0 -11
@@ -1,6 +1,11 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
- // Pure render input handled by inputToAction in App.
4
- export function HelpOverlay({ mode, onClose: _onClose }) {
3
+ import { useImmediateInput } from '../hooks/useImmediateInput.js';
4
+ export function HelpOverlay({ mode, onClose }) {
5
+ useImmediateInput((input, key) => {
6
+ if (input === '?' || key.escape) {
7
+ onClose();
8
+ }
9
+ });
5
10
  return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", padding: 1, width: 46, children: [_jsx(Text, { bold: true, children: '─'.repeat(18) + ' Help ' + '─'.repeat(18) }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Navigation" }), _jsx(Text, { children: ' ↑ / k / PgUp Scroll up' }), _jsx(Text, { children: ' ↓ / j / PgDn Scroll down' }), _jsx(Text, { children: ' g / Home Jump to top' }), _jsx(Text, { children: ' G / End Jump to bottom' }), mode === 'review' && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "[Review mode only]" }), _jsx(Text, { children: ' Tab Toggle sidebar/detail' }), _jsx(Text, { children: ' a Accept component' }), _jsx(Text, { children: ' r Reject component' }), _jsx(Text, { children: ' e Edit proposal' }), _jsx(Text, { children: ' s Toggle source code' }), _jsx(Text, { children: ' A Approve all' }), _jsx(Text, { children: ' F Open finalize dialog' }), _jsx(Text, { children: ' Ctrl+S Save draft' }), _jsx(Text, { children: ' Ctrl+Z Undo' }), _jsx(Text, { children: ' Esc Exit edit / close' })] })), _jsx(Text, { children: " " }), _jsx(Text, { children: ' ? Close help' }), _jsx(Text, { children: ' q Quit' })] }));
6
11
  }
@@ -1,13 +1,10 @@
1
1
  import React from 'react';
2
- import type { EditorState } from '../state.js';
3
2
  type JsonEditorProps = {
4
- editorState: EditorState;
3
+ value: string;
5
4
  width: number;
6
5
  height: number;
6
+ onSave: (value: string) => void;
7
+ onDiscard: () => void;
7
8
  };
8
- /**
9
- * Pure render component — no input handling, no hooks.
10
- * All state lives in the reducer; all keys are handled by inputToAction.
11
- */
12
- export declare function JsonEditor({ editorState, width, height }: JsonEditorProps): React.ReactElement;
9
+ export declare function JsonEditor({ value, width, height, onSave, onDiscard }: JsonEditorProps): React.ReactElement;
13
10
  export {};
@@ -1,23 +1,131 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
2
3
  import { Box, Text } from 'ink';
3
- /**
4
- * Pure render component — no input handling, no hooks.
5
- * All state lives in the reducer; all keys are handled by inputToAction.
6
- */
7
- export function JsonEditor({ editorState, width, height }) {
8
- const { cursor, scrollRow, validationError } = editorState;
9
- const { lines, cursorRow, cursorCol } = cursor;
4
+ import { useUndo } from '../hooks/useUndo.js';
5
+ import { useImmediateInput } from '../hooks/useImmediateInput.js';
6
+ export function JsonEditor({ value, width, height, onSave, onDiscard }) {
7
+ const undo = useUndo({
8
+ lines: value.split('\n'),
9
+ cursorRow: 0,
10
+ cursorCol: 0,
11
+ });
12
+ const [scrollRow, setScrollRow] = useState(0);
13
+ const [validationError, setValidationError] = useState(null);
14
+ const { lines, cursorRow, cursorCol } = undo.current;
15
+ const scrollCol = 0;
16
+ // Sync scroll with cursor — pure derivation, no setState needed when within bounds
17
+ let effectiveScrollRow = scrollRow;
18
+ if (cursorRow < scrollRow)
19
+ effectiveScrollRow = cursorRow;
20
+ if (cursorRow >= scrollRow + height)
21
+ effectiveScrollRow = cursorRow - height + 1;
22
+ if (effectiveScrollRow !== scrollRow)
23
+ setScrollRow(effectiveScrollRow);
24
+ useImmediateInput((input, key) => {
25
+ const currentLines = undo.current.lines;
26
+ const currentRow = undo.current.cursorRow;
27
+ const currentCol = undo.current.cursorCol;
28
+ if (key.ctrl && input === 's') {
29
+ const text = currentLines.join('\n');
30
+ try {
31
+ JSON.parse(text);
32
+ setValidationError(null);
33
+ onSave(text);
34
+ }
35
+ catch (e) {
36
+ setValidationError(e instanceof Error ? e.message : String(e));
37
+ }
38
+ return;
39
+ }
40
+ if (key.escape) {
41
+ onDiscard();
42
+ return;
43
+ }
44
+ if (key.ctrl && input === 'z') {
45
+ undo.undo();
46
+ return;
47
+ }
48
+ let newLines = [...currentLines];
49
+ let newRow = currentRow;
50
+ let newCol = currentCol;
51
+ if (key.return) {
52
+ const before = newLines[currentRow].slice(0, currentCol);
53
+ const after = newLines[currentRow].slice(currentCol);
54
+ newLines = [...newLines.slice(0, currentRow), before, after, ...newLines.slice(currentRow + 1)];
55
+ newRow = currentRow + 1;
56
+ newCol = 0;
57
+ }
58
+ else if (key.backspace) {
59
+ if (currentCol > 0) {
60
+ newLines[currentRow] = newLines[currentRow].slice(0, currentCol - 1) + newLines[currentRow].slice(currentCol);
61
+ newCol = currentCol - 1;
62
+ }
63
+ else if (currentRow > 0) {
64
+ const prevLen = newLines[currentRow - 1].length;
65
+ newLines[currentRow - 1] = newLines[currentRow - 1] + newLines[currentRow];
66
+ newLines = [...newLines.slice(0, currentRow), ...newLines.slice(currentRow + 1)];
67
+ newRow = currentRow - 1;
68
+ newCol = prevLen;
69
+ }
70
+ }
71
+ else if (key.delete) {
72
+ if (currentCol < newLines[currentRow].length) {
73
+ newLines[currentRow] = newLines[currentRow].slice(0, currentCol) + newLines[currentRow].slice(currentCol + 1);
74
+ }
75
+ else if (currentRow < newLines.length - 1) {
76
+ newLines[currentRow] = newLines[currentRow] + newLines[currentRow + 1];
77
+ newLines = [...newLines.slice(0, currentRow + 1), ...newLines.slice(currentRow + 2)];
78
+ }
79
+ }
80
+ else if (key.leftArrow) {
81
+ if (currentCol > 0)
82
+ newCol = currentCol - 1;
83
+ else if (currentRow > 0) {
84
+ newRow = currentRow - 1;
85
+ newCol = newLines[currentRow - 1].length;
86
+ }
87
+ }
88
+ else if (key.rightArrow) {
89
+ if (currentCol < newLines[currentRow].length)
90
+ newCol = currentCol + 1;
91
+ else if (currentRow < newLines.length - 1) {
92
+ newRow = currentRow + 1;
93
+ newCol = 0;
94
+ }
95
+ }
96
+ else if (key.upArrow) {
97
+ if (currentRow > 0) {
98
+ newRow = currentRow - 1;
99
+ newCol = Math.min(currentCol, newLines[currentRow - 1].length);
100
+ }
101
+ }
102
+ else if (key.downArrow) {
103
+ if (currentRow < newLines.length - 1) {
104
+ newRow = currentRow + 1;
105
+ newCol = Math.min(currentCol, newLines[currentRow + 1].length);
106
+ }
107
+ }
108
+ else if (input && input.length === 1 && !key.ctrl && !key.meta) {
109
+ newLines[currentRow] =
110
+ newLines[currentRow].slice(0, currentCol) + input + newLines[currentRow].slice(currentCol);
111
+ newCol = currentCol + 1;
112
+ }
113
+ else {
114
+ return;
115
+ }
116
+ undo.push({ lines: newLines, cursorRow: newRow, cursorCol: newCol });
117
+ });
10
118
  const innerWidth = Math.max(1, width - 2);
11
- const visibleLines = lines.slice(scrollRow, scrollRow + height);
12
- return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: "white", children: [_jsx(Text, { bold: true, children: 'EDIT [Ctrl+S save · Ctrl+Z undo · Esc discard]' }), visibleLines.map((line, displayRow) => {
13
- const actualRow = displayRow + scrollRow;
14
- const displayLine = line.slice(0, innerWidth);
119
+ const visibleLines = lines.slice(effectiveScrollRow, effectiveScrollRow + height);
120
+ return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: "white", children: [_jsx(Text, { bold: true, children: 'EDIT [EDITING — Ctrl+S save · Esc discard]' }), visibleLines.map((line, displayRow) => {
121
+ const actualRow = displayRow + effectiveScrollRow;
122
+ const displayLine = line.slice(scrollCol, scrollCol + innerWidth);
15
123
  if (actualRow === cursorRow) {
16
- const before = displayLine.slice(0, cursorCol);
17
- const cursorChar = displayLine[cursorCol] ?? ' ';
18
- const after = displayLine.slice(cursorCol + 1);
19
- return (_jsxs(Box, { children: [_jsx(Text, { children: before }), _jsx(Text, { inverse: true, children: cursorChar }), _jsx(Text, { children: after })] }, actualRow));
124
+ const beforeCursor = displayLine.slice(0, cursorCol - scrollCol);
125
+ const cursorChar = displayLine[cursorCol - scrollCol] ?? ' ';
126
+ const afterCursor = displayLine.slice(cursorCol - scrollCol + 1);
127
+ return (_jsxs(Box, { children: [_jsx(Text, { children: beforeCursor }), _jsx(Text, { inverse: true, children: cursorChar }), _jsx(Text, { children: afterCursor })] }, displayRow));
20
128
  }
21
- return _jsx(Text, { children: displayLine }, actualRow);
22
- }), validationError && _jsx(Text, { color: "red", children: '✗ ' + validationError })] }));
129
+ return _jsx(Text, { children: displayLine }, displayRow);
130
+ }), validationError && _jsx(Text, { color: "red", children: '✗ Invalid JSON: ' + validationError })] }));
23
131
  }
@@ -4,5 +4,5 @@ type QuitDialogProps = {
4
4
  onConfirm: () => void;
5
5
  onCancel: () => void;
6
6
  };
7
- export declare function QuitDialog({ hasUnsavedDrafts, onConfirm: _onConfirm, onCancel: _onCancel, }: QuitDialogProps): React.ReactElement;
7
+ export declare function QuitDialog({ hasUnsavedDrafts, onConfirm, onCancel }: QuitDialogProps): React.ReactElement;
8
8
  export {};
@@ -1,6 +1,14 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
- // Pure render input handled by inputToAction in App.
4
- export function QuitDialog({ hasUnsavedDrafts, onConfirm: _onConfirm, onCancel: _onCancel, }) {
3
+ import { useImmediateInput } from '../hooks/useImmediateInput.js';
4
+ export function QuitDialog({ hasUnsavedDrafts, onConfirm, onCancel }) {
5
+ useImmediateInput((input, key) => {
6
+ if (input === 'y' || key.return) {
7
+ onConfirm();
8
+ }
9
+ else if (input === 'n' || key.escape) {
10
+ onCancel();
11
+ }
12
+ });
5
13
  return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", padding: 1, width: 50, children: [_jsx(Text, { bold: true, children: '─'.repeat(19) + ' Quit ' + '─'.repeat(19) }), _jsx(Text, { children: " " }), hasUnsavedDrafts ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: "You have unsaved draft edits." }), _jsx(Text, { children: 'Session state is preserved — you can resume' }), _jsx(Text, { children: 'by running the same review command again.' })] })) : (_jsx(Text, { children: "Session is saved. Quit without finalizing?" })), _jsx(Text, { children: " " }), _jsx(Text, { children: ' [y / Enter] Quit [n / Esc] Stay' })] }));
6
14
  }
@@ -6,6 +6,8 @@ type SidebarProps = {
6
6
  focused: boolean;
7
7
  scrollOffset: number;
8
8
  visibleCount: number;
9
+ onSelect: (id: string) => void;
10
+ onScrollChange: (offset: number) => void;
9
11
  collapsed?: boolean;
10
12
  width?: number;
11
13
  };
@@ -4,6 +4,8 @@ type StatusBarProps = {
4
4
  rejected: number;
5
5
  reviewed: number;
6
6
  needsReview: number;
7
+ onApproveAll: () => void;
8
+ onFinalize: () => void;
7
9
  };
8
10
  export declare function StatusBar({ accepted, rejected, reviewed, needsReview }: StatusBarProps): React.ReactElement;
9
11
  export {};
@@ -19,11 +19,6 @@ type InputHandler = (input: string, key: Key) => void;
19
19
  * Like Ink's useInput, but uses useLayoutEffect so the listener is registered
20
20
  * synchronously after render. This allows stdin.write() calls in tests to work
21
21
  * immediately after render() without awaiting effects.
22
- *
23
- * Raw mode is NOT toggled here — it is managed once at the App level via
24
- * useRawMode. Toggling setRawMode per-hook caused brief raw-mode drops during
25
- * re-renders (e.g. when FieldEditor mounted alongside useKeymap), blanking the
26
- * terminal.
27
22
  */
28
23
  export declare function useImmediateInput(handler: InputHandler): void;
29
24
  export {};
@@ -47,17 +47,13 @@ function parseInput(data) {
47
47
  * Like Ink's useInput, but uses useLayoutEffect so the listener is registered
48
48
  * synchronously after render. This allows stdin.write() calls in tests to work
49
49
  * immediately after render() without awaiting effects.
50
- *
51
- * Raw mode is NOT toggled here — it is managed once at the App level via
52
- * useRawMode. Toggling setRawMode per-hook caused brief raw-mode drops during
53
- * re-renders (e.g. when FieldEditor mounted alongside useKeymap), blanking the
54
- * terminal.
55
50
  */
56
51
  export function useImmediateInput(handler) {
57
- const { stdin } = useStdin();
52
+ const { stdin, setRawMode } = useStdin();
58
53
  const handlerRef = useRef(handler);
59
54
  handlerRef.current = handler;
60
55
  useLayoutEffect(() => {
56
+ setRawMode(true);
61
57
  const handleData = (data) => {
62
58
  const str = Buffer.isBuffer(data) ? data.toString('utf8') : data;
63
59
  const { input, key } = parseInput(str);
@@ -66,6 +62,7 @@ export function useImmediateInput(handler) {
66
62
  stdin.on('data', handleData);
67
63
  return () => {
68
64
  stdin.off('data', handleData);
65
+ setRawMode(false);
69
66
  };
70
- }, []); // Register once on mount. Handler is always fresh via handlerRef — no re-registration needed.
67
+ }, [stdin, setRawMode]);
71
68
  }
@@ -1 +1,24 @@
1
+ type KeymapContext = {
2
+ sidebarFocused: boolean;
3
+ editMode: boolean;
4
+ dialogOpen: boolean;
5
+ disabled: boolean;
6
+ };
7
+ type KeymapHandlers = {
8
+ onSidebarUp: () => void;
9
+ onSidebarDown: () => void;
10
+ onSidebarSelect: () => void;
11
+ onAccept: () => void;
12
+ onReject: () => void;
13
+ onEnterEditMode: () => void;
14
+ onToggleSource: () => void;
15
+ onScrollUp: () => void;
16
+ onScrollDown: () => void;
17
+ onToggleFocus: () => void;
18
+ onApproveAll: () => void;
19
+ onFinalize: () => void;
20
+ onQuit: () => void;
21
+ onToggleHelp: () => void;
22
+ };
23
+ export declare function useKeymap(context: KeymapContext, handlers: KeymapHandlers): void;
1
24
  export {};
@@ -1 +1,67 @@
1
- export {};
1
+ import { useImmediateInput } from './useImmediateInput.js';
2
+ export function useKeymap(context, handlers) {
3
+ useImmediateInput((input, key) => {
4
+ if (context.disabled)
5
+ return;
6
+ if (context.dialogOpen)
7
+ return;
8
+ if (context.editMode)
9
+ return;
10
+ if (input === 'q') {
11
+ handlers.onQuit();
12
+ return;
13
+ }
14
+ if (input === '?') {
15
+ handlers.onToggleHelp();
16
+ return;
17
+ }
18
+ if (key.tab) {
19
+ handlers.onToggleFocus();
20
+ return;
21
+ }
22
+ if (input === 'A') {
23
+ handlers.onApproveAll();
24
+ return;
25
+ }
26
+ if (input === 'F') {
27
+ handlers.onFinalize();
28
+ return;
29
+ }
30
+ // a/r/e/s work regardless of sidebar focus
31
+ if (input === 'a') {
32
+ handlers.onAccept();
33
+ return;
34
+ }
35
+ if (input === 'r') {
36
+ handlers.onReject();
37
+ return;
38
+ }
39
+ if (input === 'e') {
40
+ handlers.onEnterEditMode();
41
+ return;
42
+ }
43
+ if (input === 's') {
44
+ handlers.onToggleSource();
45
+ return;
46
+ }
47
+ if (context.sidebarFocused) {
48
+ if (key.upArrow || input === 'k') {
49
+ handlers.onSidebarUp();
50
+ }
51
+ else if (key.downArrow || input === 'j') {
52
+ handlers.onSidebarDown();
53
+ }
54
+ else if (key.return) {
55
+ handlers.onSidebarSelect();
56
+ }
57
+ }
58
+ else {
59
+ if (key.upArrow || input === 'k') {
60
+ handlers.onScrollUp();
61
+ }
62
+ else if (key.downArrow || input === 'j') {
63
+ handlers.onScrollDown();
64
+ }
65
+ }
66
+ });
67
+ }
@@ -1,20 +1,26 @@
1
1
  import { useState } from 'react';
2
- // Single-state implementation — push and undo are atomic single setState calls,
3
- // eliminating the double-render that caused flash in the JsonEditor.
4
2
  export function useUndo(initial, maxSize = 50) {
5
- const [state, setState] = useState({ stack: [], current: initial });
3
+ const [stack, setStack] = useState([]);
4
+ const [current, setCurrent] = useState(initial);
6
5
  return {
7
- current: state.current,
8
- push: (next) => setState(({ stack, current }) => {
9
- const newStack = [...stack, current];
10
- return { stack: newStack.length > maxSize ? newStack.slice(1) : newStack, current: next };
11
- }),
12
- undo: () => setState(({ stack, current }) => {
13
- if (stack.length === 0)
14
- return { stack, current };
15
- return { stack: stack.slice(0, -1), current: stack[stack.length - 1] };
16
- }),
17
- canUndo: state.stack.length > 0,
18
- clear: () => setState(({ current }) => ({ stack: [], current })),
6
+ current,
7
+ push: (next) => {
8
+ setStack((prev) => {
9
+ const newStack = [...prev, current];
10
+ return newStack.length > maxSize ? newStack.slice(1) : newStack;
11
+ });
12
+ setCurrent(next);
13
+ },
14
+ undo: () => {
15
+ setStack((prev) => {
16
+ if (prev.length === 0)
17
+ return prev;
18
+ const newStack = prev.slice(0, -1);
19
+ setCurrent(prev[prev.length - 1]);
20
+ return newStack;
21
+ });
22
+ },
23
+ canUndo: stack.length > 0,
24
+ clear: () => setStack([]),
19
25
  };
20
26
  }
@@ -3,14 +3,12 @@ import { useState } from 'react';
3
3
  import { Box, Text, useStdout } from 'ink';
4
4
  import { TopBar } from '../select/tui/components/TopBar.js';
5
5
  import { useImmediateInput } from '../select/tui/hooks/useImmediateInput.js';
6
- import { useRawMode } from '../select/tui/hooks/useRawMode.js';
7
6
  function truncateName(name, maxLen = 30) {
8
7
  if (name.length <= maxLen)
9
8
  return name;
10
9
  return name.slice(0, maxLen - 1) + '…';
11
10
  }
12
11
  export function AnalyzeView({ result, onExit }) {
13
- useRawMode();
14
12
  const [scrollOffset, setScrollOffset] = useState(0);
15
13
  const { stdout } = useStdout();
16
14
  // Header lines: TopBar(1) + summary(3) + blank(1) + dividers+header(3) + blank(1) + footer(1) + footer-bar(1) = ~12
@@ -8,7 +8,6 @@ import { homedir, tmpdir } from 'node:os';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { execFile, spawn } from 'node:child_process';
10
10
  import { TopBar } from '../../analyze/select/tui/components/TopBar.js';
11
- import { useRawMode } from '../../analyze/select/tui/hooks/useRawMode.js';
12
11
  import { WelcomeStep } from './steps/WelcomeStep.js';
13
12
  import { PathValidationStep } from './steps/PathValidationStep.js';
14
13
  import { RunningStep } from './steps/RunningStep.js';
@@ -45,7 +44,6 @@ function logStep(entry) {
45
44
  export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', initialHost, initialAgent, initialProjectPath, host, } = {}) {
46
45
  const defaultConfiguredHost = toConfiguredHost(host || process.env['EDS_HOST']) ?? DEFAULT_CONFIGURED_HOST;
47
46
  const resolveWizardHost = (hostValue) => hostValue || defaultConfiguredHost;
48
- useRawMode();
49
47
  const { stdout } = useStdout();
50
48
  const terminalWidth = stdout?.columns ?? 80;
51
49
  const logInit = useRef(false);
@@ -8,7 +8,6 @@ import { StatusBar } from '../../../analyze/select/tui/components/StatusBar.js';
8
8
  import { FinalizeDialog } from '../../../analyze/select/tui/components/FinalizeDialog.js';
9
9
  import { QuitDialog } from '../../../analyze/select/tui/components/QuitDialog.js';
10
10
  import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
11
- import { computeScrollOffset } from '../../../analyze/select/tui/utils.js';
12
11
  import { openPipelineDb, loadCDFComponents, storeCDFComponents } from '../../../session/db.js';
13
12
  const VISIBLE_COUNT = 20;
14
13
  const PANEL_HEIGHT = 22;
@@ -162,7 +161,7 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, }) {
162
161
  const newIdx = Math.min(components.length - 1, selectedIdx + 1);
163
162
  setSelectedIdx(newIdx);
164
163
  setJsonScrollOffset(0);
165
- setSidebarScrollOffset((prev) => computeScrollOffset(newIdx, prev, VISIBLE_COUNT));
164
+ setSidebarScrollOffset((prev) => (newIdx >= prev + VISIBLE_COUNT ? newIdx - VISIBLE_COUNT + 1 : prev));
166
165
  }
167
166
  }
168
167
  else {
@@ -197,7 +196,15 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, }) {
197
196
  const needsReview = components.filter((c) => c.status === 'needs-review').length;
198
197
  const propCount = selected ? Object.keys(selected.entry.$properties).length : 0;
199
198
  const slotCount = selected?.entry.$slots ? Object.keys(selected.entry.$slots).length : 0;
200
- return (_jsxs(Box, { flexDirection: "column", children: [showFinalize && (_jsx(FinalizeDialog, { accepted: accepted, rejected: rejected, needsReview: needsReview, onConfirm: handleFinalizeConfirm, onCancel: () => setShowFinalize(false) })), showQuit && _jsx(QuitDialog, { hasUnsavedDrafts: false, onConfirm: onQuit, onCancel: () => setShowQuit(false) }), !dialogOpen && (_jsxs(Box, { children: [_jsx(Sidebar, { components: sidebarItems, selectedId: selected?.key ?? null, focused: sidebarFocused, scrollOffset: sidebarScrollOffset, visibleCount: VISIBLE_COUNT, width: sidebarWidth }), _jsx(Box, { flexGrow: 1, paddingLeft: 1, flexDirection: "column", children: selected ? (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: selected.key }), _jsx(Box, { flexGrow: 1 }), _jsxs(Text, { dimColor: true, children: [propCount, " prop", propCount !== 1 ? 's' : '', slotCount > 0 ? ` · ${slotCount} slot${slotCount !== 1 ? 's' : ''}` : '', ' ', sidebarFocused ? '[Tab] focus panel' : '[Tab] focus list'] })] }), editMode ? (_jsx(FieldEditor, { value: draftValue || selectedJson, width: panelWidth, height: PANEL_HEIGHT, onChange: setDraftValue, onSave: handleEditSave, onDiscard: handleEditDiscard })) : (_jsx(JsonPanel, { label: "GENERATED DEFINITION", value: selectedJson, scrollOffset: jsonScrollOffset, width: panelWidth, height: PANEL_HEIGHT, active: !sidebarFocused })), saveError && _jsx(Text, { color: "red", children: '✗ ' + saveError }), _jsx(Text, { dimColor: true, children: editMode
199
+ return (_jsxs(Box, { flexDirection: "column", children: [showFinalize && (_jsx(FinalizeDialog, { accepted: accepted, rejected: rejected, needsReview: needsReview, onConfirm: handleFinalizeConfirm, onCancel: () => setShowFinalize(false) })), showQuit && _jsx(QuitDialog, { hasUnsavedDrafts: false, onConfirm: onQuit, onCancel: () => setShowQuit(false) }), !dialogOpen && (_jsxs(Box, { children: [_jsx(Sidebar, { components: sidebarItems, selectedId: selected?.key ?? null, focused: sidebarFocused, scrollOffset: sidebarScrollOffset, visibleCount: VISIBLE_COUNT, onSelect: (id) => {
200
+ const idx = components.findIndex((c) => c.key === id);
201
+ if (idx >= 0) {
202
+ setSelectedIdx(idx);
203
+ setJsonScrollOffset(0);
204
+ }
205
+ }, onScrollChange: setSidebarScrollOffset, width: sidebarWidth }), _jsx(Box, { flexGrow: 1, paddingLeft: 1, flexDirection: "column", children: selected ? (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: selected.key }), _jsx(Box, { flexGrow: 1 }), _jsxs(Text, { dimColor: true, children: [propCount, " prop", propCount !== 1 ? 's' : '', slotCount > 0 ? ` · ${slotCount} slot${slotCount !== 1 ? 's' : ''}` : '', ' ', sidebarFocused ? '[Tab] focus panel' : '[Tab] focus list'] })] }), editMode ? (_jsx(FieldEditor, { value: draftValue || selectedJson, width: panelWidth, height: PANEL_HEIGHT, onChange: setDraftValue, onSave: handleEditSave, onDiscard: handleEditDiscard })) : (_jsx(JsonPanel, { label: "GENERATED DEFINITION", value: selectedJson, scrollOffset: jsonScrollOffset, width: panelWidth, height: PANEL_HEIGHT, active: !sidebarFocused })), saveError && _jsx(Text, { color: "red", children: '✗ ' + saveError }), _jsx(Text, { dimColor: true, children: editMode
201
206
  ? ' [Ctrl+S] save [Esc] discard'
202
- : ' [a] accept [r] reject [e] edit [A] accept all [F] finalize [Tab] toggle focus [q] quit' })] })) : (_jsx(Text, { dimColor: true, children: "No component selected" })) })] })), !dialogOpen && _jsx(StatusBar, { accepted: accepted, rejected: rejected, reviewed: 0, needsReview: needsReview })] }));
207
+ : ' [a] accept [r] reject [e] edit [A] accept all [F] finalize [Tab] toggle focus [q] quit' })] })) : (_jsx(Text, { dimColor: true, children: "No component selected" })) })] })), !dialogOpen && (_jsx(StatusBar, { accepted: accepted, rejected: rejected, reviewed: 0, needsReview: needsReview, onApproveAll: () => {
208
+ setComponents((prev) => prev.map((c) => (c.status === 'needs-review' ? { ...c, status: 'accepted' } : c)));
209
+ }, onFinalize: () => setShowFinalize(true) }))] }));
203
210
  }
@@ -3,9 +3,7 @@ import { useState } from 'react';
3
3
  import { Box, Text } from 'ink';
4
4
  import { TopBar } from '../../../analyze/select/tui/components/TopBar.js';
5
5
  import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
6
- import { useRawMode } from '../../../analyze/select/tui/hooks/useRawMode.js';
7
6
  export function ValidateView({ results, onExit }) {
8
- useRawMode();
9
7
  const [scrollOffset, setScrollOffset] = useState(0);
10
8
  const allValid = results.every((r) => r.valid);
11
9
  useImmediateInput((input, key) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.7.1-dev-build-02830eb.0",
3
+ "version": "2.7.2-dev-build-bc65c2b.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,7 +36,7 @@
36
36
  "react-dom": "^18.3.1",
37
37
  "ts-morph": "^27.0.2",
38
38
  "typescript": "^5.9.3",
39
- "@contentful/experience-design-system-types": "2.7.1-dev-build-02830eb.0"
39
+ "@contentful/experience-design-system-types": "2.7.2-dev-build-bc65c2b.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@tsconfig/node24": "^24.0.3",
@@ -1,6 +0,0 @@
1
- /**
2
- * Enables raw terminal mode for the lifetime of the component.
3
- * Call once at the root of each top-level TUI component.
4
- * useImmediateInput does NOT manage raw mode — this hook does.
5
- */
6
- export declare function useRawMode(): void;
@@ -1,16 +0,0 @@
1
- import { useLayoutEffect } from 'react';
2
- import { useStdin } from 'ink';
3
- /**
4
- * Enables raw terminal mode for the lifetime of the component.
5
- * Call once at the root of each top-level TUI component.
6
- * useImmediateInput does NOT manage raw mode — this hook does.
7
- */
8
- export function useRawMode() {
9
- const { setRawMode } = useStdin();
10
- useLayoutEffect(() => {
11
- setRawMode(true);
12
- return () => {
13
- setRawMode(false);
14
- };
15
- }, [setRawMode]);
16
- }
@@ -1,17 +0,0 @@
1
- import type { AppState, AppAction } from '../state.js';
2
- import type { ReviewSessionSnapshot } from '../../types.js';
3
- type Services = {
4
- sessionId: string;
5
- saveState: (session: ReviewSessionSnapshot) => Promise<void>;
6
- appendEvent: (event: {
7
- type: string;
8
- payload: Record<string, unknown>;
9
- }) => Promise<void>;
10
- };
11
- type Dispatch = (action: AppAction) => void;
12
- /**
13
- * All async side effects in one place.
14
- * Detects transitions by comparing state to prevState — no pending* counter signals needed.
15
- */
16
- export declare function useSideEffects(state: AppState, dispatch: Dispatch, services: Services): void;
17
- export {};