@contentful/experience-design-system-cli 2.6.2-dev-build-864e323.0 → 2.7.1-dev-build-02830eb.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 (45) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/analyze/command.js +1 -1
  3. package/dist/src/analyze/extract/scoring.d.ts +3 -2
  4. package/dist/src/analyze/extract/scoring.js +39 -22
  5. package/dist/src/analyze/select/tui/App.js +54 -474
  6. package/dist/src/analyze/select/tui/components/ComponentDetail.d.ts +3 -5
  7. package/dist/src/analyze/select/tui/components/ComponentDetail.js +9 -10
  8. package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +1 -1
  9. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +2 -10
  10. package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +1 -1
  11. package/dist/src/analyze/select/tui/components/HelpOverlay.js +2 -7
  12. package/dist/src/analyze/select/tui/components/JsonEditor.d.ts +7 -5
  13. package/dist/src/analyze/select/tui/components/JsonEditor.js +15 -146
  14. package/dist/src/analyze/select/tui/components/QuitDialog.d.ts +1 -1
  15. package/dist/src/analyze/select/tui/components/QuitDialog.js +2 -10
  16. package/dist/src/analyze/select/tui/components/Sidebar.d.ts +0 -2
  17. package/dist/src/analyze/select/tui/components/StatusBar.d.ts +0 -2
  18. package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +5 -0
  19. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +7 -4
  20. package/dist/src/analyze/select/tui/hooks/useKeymap.d.ts +0 -23
  21. package/dist/src/analyze/select/tui/hooks/useKeymap.js +1 -67
  22. package/dist/src/analyze/select/tui/hooks/useRawMode.d.ts +6 -0
  23. package/dist/src/analyze/select/tui/hooks/useRawMode.js +16 -0
  24. package/dist/src/analyze/select/tui/hooks/useSideEffects.d.ts +17 -0
  25. package/dist/src/analyze/select/tui/hooks/useSideEffects.js +226 -0
  26. package/dist/src/analyze/select/tui/hooks/useUndo.js +15 -21
  27. package/dist/src/analyze/select/tui/inputToAction.d.ts +13 -0
  28. package/dist/src/analyze/select/tui/inputToAction.js +68 -0
  29. package/dist/src/analyze/select/tui/state.d.ts +136 -0
  30. package/dist/src/analyze/select/tui/state.js +385 -0
  31. package/dist/src/analyze/select/tui/utils.d.ts +5 -0
  32. package/dist/src/analyze/select/tui/utils.js +11 -0
  33. package/dist/src/analyze/select/types.d.ts +1 -1
  34. package/dist/src/analyze/select/types.js +1 -1
  35. package/dist/src/analyze/tui/AnalyzeView.d.ts +1 -1
  36. package/dist/src/analyze/tui/AnalyzeView.js +5 -3
  37. package/dist/src/generate/agent-runner.js +7 -2
  38. package/dist/src/import/tui/WizardApp.js +2 -0
  39. package/dist/src/import/tui/steps/GenerateReviewStep.js +5 -12
  40. package/dist/src/print/validate/tui/ValidateView.js +2 -0
  41. package/dist/src/session/db.js +4 -4
  42. package/dist/src/types.d.ts +3 -1
  43. package/dist/src/types.js +4 -1
  44. package/package.json +2 -2
  45. package/skills/select-components.md +8 -3
@@ -0,0 +1,385 @@
1
+ import { stripScoringFields } from '../../../types.js';
2
+ import { computeScrollOffset } from './utils.js';
3
+ function makeEditorState(value) {
4
+ return {
5
+ cursor: { lines: value.split('\n'), cursorRow: 0, cursorCol: 0 },
6
+ undoStack: [],
7
+ scrollRow: 0,
8
+ validationError: null,
9
+ };
10
+ }
11
+ function editorWithCursor(e, next) {
12
+ return {
13
+ ...e,
14
+ cursor: next,
15
+ undoStack: e.undoStack.length >= 50 ? [...e.undoStack.slice(1), e.cursor] : [...e.undoStack, e.cursor],
16
+ };
17
+ }
18
+ function syncEditorScroll(e, visibleHeight) {
19
+ const { cursorRow } = e.cursor;
20
+ let { scrollRow } = e;
21
+ if (cursorRow < scrollRow)
22
+ scrollRow = cursorRow;
23
+ if (cursorRow >= scrollRow + visibleHeight)
24
+ scrollRow = cursorRow - visibleHeight + 1;
25
+ return scrollRow === e.scrollRow ? e : { ...e, scrollRow };
26
+ }
27
+ // Pure: apply one keypress to EditorState. Returns null if key has no effect.
28
+ export function applyEditorKey(e, input, key, visibleHeight) {
29
+ const { lines, cursorRow, cursorCol } = e.cursor;
30
+ // Ctrl+Z undo
31
+ if (key.ctrl && input === 'z') {
32
+ if (e.undoStack.length === 0)
33
+ return null;
34
+ const prev = e.undoStack[e.undoStack.length - 1];
35
+ return syncEditorScroll({ ...e, cursor: prev, undoStack: e.undoStack.slice(0, -1) }, visibleHeight);
36
+ }
37
+ let newLines = [...lines];
38
+ let newRow = cursorRow;
39
+ let newCol = cursorCol;
40
+ if (key.return) {
41
+ const before = newLines[cursorRow].slice(0, cursorCol);
42
+ const after = newLines[cursorRow].slice(cursorCol);
43
+ newLines = [...newLines.slice(0, cursorRow), before, after, ...newLines.slice(cursorRow + 1)];
44
+ newRow = cursorRow + 1;
45
+ newCol = 0;
46
+ }
47
+ else if (key.backspace) {
48
+ if (cursorCol > 0) {
49
+ newLines[cursorRow] = newLines[cursorRow].slice(0, cursorCol - 1) + newLines[cursorRow].slice(cursorCol);
50
+ newCol = cursorCol - 1;
51
+ }
52
+ else if (cursorRow > 0) {
53
+ const prevLen = newLines[cursorRow - 1].length;
54
+ newLines[cursorRow - 1] = newLines[cursorRow - 1] + newLines[cursorRow];
55
+ newLines = [...newLines.slice(0, cursorRow), ...newLines.slice(cursorRow + 1)];
56
+ newRow = cursorRow - 1;
57
+ newCol = prevLen;
58
+ }
59
+ else {
60
+ return null;
61
+ }
62
+ }
63
+ else if (key.delete) {
64
+ if (cursorCol < newLines[cursorRow].length) {
65
+ newLines[cursorRow] = newLines[cursorRow].slice(0, cursorCol) + newLines[cursorRow].slice(cursorCol + 1);
66
+ }
67
+ else if (cursorRow < newLines.length - 1) {
68
+ newLines[cursorRow] = newLines[cursorRow] + newLines[cursorRow + 1];
69
+ newLines = [...newLines.slice(0, cursorRow + 1), ...newLines.slice(cursorRow + 2)];
70
+ }
71
+ else {
72
+ return null;
73
+ }
74
+ }
75
+ else if (key.leftArrow) {
76
+ if (cursorCol > 0) {
77
+ newCol = cursorCol - 1;
78
+ }
79
+ else if (cursorRow > 0) {
80
+ newRow = cursorRow - 1;
81
+ newCol = newLines[cursorRow - 1].length;
82
+ }
83
+ else
84
+ return null;
85
+ }
86
+ else if (key.rightArrow) {
87
+ if (cursorCol < newLines[cursorRow].length) {
88
+ newCol = cursorCol + 1;
89
+ }
90
+ else if (cursorRow < newLines.length - 1) {
91
+ newRow = cursorRow + 1;
92
+ newCol = 0;
93
+ }
94
+ else
95
+ return null;
96
+ }
97
+ else if (key.upArrow) {
98
+ if (cursorRow === 0)
99
+ return null;
100
+ newRow = cursorRow - 1;
101
+ newCol = Math.min(cursorCol, newLines[newRow].length);
102
+ }
103
+ else if (key.downArrow) {
104
+ if (cursorRow >= newLines.length - 1)
105
+ return null;
106
+ newRow = cursorRow + 1;
107
+ newCol = Math.min(cursorCol, newLines[newRow].length);
108
+ }
109
+ else if (input === '\x1b[H' || input === '\x1b[1~') {
110
+ newCol = 0; // Home
111
+ }
112
+ else if (input === '\x1b[F' || input === '\x1b[4~') {
113
+ newCol = newLines[cursorRow].length; // End
114
+ }
115
+ else if (input && input.length === 1 && !key.ctrl && !key.meta) {
116
+ newLines[cursorRow] = newLines[cursorRow].slice(0, cursorCol) + input + newLines[cursorRow].slice(cursorCol);
117
+ newCol = cursorCol + 1;
118
+ }
119
+ else {
120
+ return null;
121
+ }
122
+ return syncEditorScroll(editorWithCursor(e, { lines: newLines, cursorRow: newRow, cursorCol: newCol }), visibleHeight);
123
+ }
124
+ export const initialState = {
125
+ mode: { type: 'browsing', sidebarFocused: true, sourceVisible: false },
126
+ session: null,
127
+ paths: null,
128
+ selectedId: null,
129
+ sortedIds: [],
130
+ sessionSummary: [],
131
+ sidebarScrollOffset: 0,
132
+ jsonScrollOffset: 0,
133
+ draftsByComponentId: {},
134
+ editor: null,
135
+ sourceCodeById: {},
136
+ previewAnnotations: (() => {
137
+ const raw = process.env['EDS_PREVIEW_ANNOTATIONS'];
138
+ if (!raw)
139
+ return {};
140
+ try {
141
+ return JSON.parse(raw);
142
+ }
143
+ catch {
144
+ return {};
145
+ }
146
+ })(),
147
+ previewResponse: null,
148
+ previewLoading: false,
149
+ previewError: null,
150
+ saveError: null,
151
+ };
152
+ // ── Derived data ──────────────────────────────────────────────────────────────
153
+ function computeSortedIds(components) {
154
+ return [...components]
155
+ .map((c) => ({
156
+ id: c.id,
157
+ needsReview: c.originalProposal.needsReview ?? false,
158
+ status: c.status,
159
+ conf: c.originalProposal.extractionConfidence ?? 6,
160
+ }))
161
+ .sort((a, b) => {
162
+ const aF = a.needsReview && a.status === 'needs-review' ? 0 : 1;
163
+ const bF = b.needsReview && b.status === 'needs-review' ? 0 : 1;
164
+ if (aF !== bF)
165
+ return aF - bF;
166
+ return a.conf - b.conf;
167
+ })
168
+ .map((c) => c.id);
169
+ }
170
+ // Builds a stable summary array for Sidebar — same reference if nothing changed.
171
+ // Keeping this in state means Sidebar never re-renders on scroll/selection alone.
172
+ function computeSessionSummary(components, annotations, sortedIds) {
173
+ return sortedIds
174
+ .map((id) => components.find((c) => c.id === id))
175
+ .filter((c) => !!c)
176
+ .map((c) => ({
177
+ id: c.id,
178
+ name: c.name,
179
+ status: c.status,
180
+ previewAnnotation: annotations[c.name],
181
+ extractionConfidence: c.originalProposal.extractionConfidence ?? null,
182
+ needsReview: c.originalProposal.needsReview ?? false,
183
+ }));
184
+ }
185
+ // Rebuild both sortedIds and sessionSummary from updated components + annotations.
186
+ function withUpdatedSession(state, components, annotations) {
187
+ const ann = annotations ?? state.previewAnnotations;
188
+ const sortedIds = computeSortedIds(components);
189
+ const sessionSummary = computeSessionSummary(components, ann, sortedIds);
190
+ return {
191
+ ...state,
192
+ session: { ...state.session, components },
193
+ previewAnnotations: ann,
194
+ sortedIds,
195
+ sessionSummary,
196
+ };
197
+ }
198
+ function updateStatus(state, newStatus) {
199
+ if (!state.session || !state.selectedId)
200
+ return state;
201
+ const components = state.session.components.map((c) => (c.id === state.selectedId ? { ...c, status: newStatus } : c));
202
+ return withUpdatedSession(state, components);
203
+ }
204
+ // ── Reducer ───────────────────────────────────────────────────────────────────
205
+ export function reducer(state, action) {
206
+ switch (action.type) {
207
+ case 'SESSION_LOADED': {
208
+ const sortedIds = computeSortedIds(action.session.components);
209
+ const sessionSummary = computeSessionSummary(action.session.components, state.previewAnnotations, sortedIds);
210
+ return {
211
+ ...state,
212
+ session: action.session,
213
+ paths: action.paths,
214
+ sortedIds,
215
+ sessionSummary,
216
+ selectedId: sortedIds[0] ?? null,
217
+ };
218
+ }
219
+ case 'SELECT':
220
+ return { ...state, selectedId: action.id, jsonScrollOffset: 0 };
221
+ case 'SIDEBAR_UP': {
222
+ if (!state.session || !state.selectedId)
223
+ return state;
224
+ const idx = state.sortedIds.indexOf(state.selectedId);
225
+ if (idx <= 0)
226
+ return state;
227
+ const ni = idx - 1;
228
+ return {
229
+ ...state,
230
+ selectedId: state.sortedIds[ni],
231
+ jsonScrollOffset: 0,
232
+ sidebarScrollOffset: computeScrollOffset(ni, state.sidebarScrollOffset, action.visibleCount),
233
+ };
234
+ }
235
+ case 'SIDEBAR_DOWN': {
236
+ if (!state.session || !state.selectedId)
237
+ return state;
238
+ const idx = state.sortedIds.indexOf(state.selectedId);
239
+ if (idx >= state.sortedIds.length - 1)
240
+ return state;
241
+ const ni = idx + 1;
242
+ return {
243
+ ...state,
244
+ selectedId: state.sortedIds[ni],
245
+ jsonScrollOffset: 0,
246
+ sidebarScrollOffset: computeScrollOffset(ni, state.sidebarScrollOffset, action.visibleCount),
247
+ };
248
+ }
249
+ case 'ACCEPT':
250
+ return updateStatus(state, 'accepted');
251
+ case 'REJECT':
252
+ return updateStatus(state, 'rejected');
253
+ case 'APPROVE_ALL': {
254
+ if (!state.session)
255
+ return state;
256
+ const components = state.session.components.map((c) => c.status === 'needs-review' ? { ...c, status: 'accepted' } : c);
257
+ return withUpdatedSession(state, components);
258
+ }
259
+ case 'ENTER_EDIT': {
260
+ if (state.mode.type !== 'browsing' || !state.selectedId || !state.session)
261
+ return state;
262
+ const component = state.session.components.find((c) => c.id === state.selectedId);
263
+ if (!component)
264
+ return state;
265
+ const existing = state.draftsByComponentId[state.selectedId];
266
+ const draft = existing ?? JSON.stringify(stripScoringFields(component.editedProposal), null, 2);
267
+ const editor = makeEditorState(draft);
268
+ return {
269
+ ...state,
270
+ mode: { type: 'editing', componentId: state.selectedId },
271
+ draftsByComponentId: { ...state.draftsByComponentId, [state.selectedId]: draft },
272
+ editor,
273
+ };
274
+ }
275
+ case 'EDITOR_KEY': {
276
+ if (!state.editor || state.mode.type !== 'editing')
277
+ return state;
278
+ const next = applyEditorKey(state.editor, action.input, action.key, action.visibleHeight);
279
+ if (!next)
280
+ return state;
281
+ const newValue = next.cursor.lines.join('\n');
282
+ return {
283
+ ...state,
284
+ editor: next,
285
+ draftsByComponentId: { ...state.draftsByComponentId, [state.mode.componentId]: newValue },
286
+ };
287
+ }
288
+ case 'EDITOR_VALIDATE': {
289
+ // inputToAction calls this on Ctrl+S — reducer does the parse check
290
+ if (!state.editor || state.mode.type !== 'editing')
291
+ return state;
292
+ const text = state.editor.cursor.lines.join('\n');
293
+ try {
294
+ JSON.parse(text);
295
+ // Valid — switch mode back to browsing; side effect will detect the transition and persist
296
+ return {
297
+ ...state,
298
+ editor: { ...state.editor, validationError: null },
299
+ mode: { type: 'browsing', sidebarFocused: true, sourceVisible: false },
300
+ // keep draft in draftsByComponentId — side effect will persist it and clear it
301
+ };
302
+ }
303
+ catch (e) {
304
+ return {
305
+ ...state,
306
+ editor: { ...state.editor, validationError: e instanceof Error ? e.message : String(e) },
307
+ };
308
+ }
309
+ }
310
+ case 'DRAFT_DISCARD': {
311
+ if (state.mode.type !== 'editing')
312
+ return state;
313
+ const { componentId } = state.mode;
314
+ const { [componentId]: _removed, ...remaining } = state.draftsByComponentId;
315
+ return {
316
+ ...state,
317
+ mode: { type: 'browsing', sidebarFocused: true, sourceVisible: false },
318
+ draftsByComponentId: remaining,
319
+ editor: null,
320
+ };
321
+ }
322
+ case 'DRAFT_PERSIST_DONE': {
323
+ const { componentId, updatedComponents } = action;
324
+ const { [componentId]: _removed, ...remaining } = state.draftsByComponentId;
325
+ const base = withUpdatedSession(state, updatedComponents);
326
+ return { ...base, draftsByComponentId: remaining };
327
+ }
328
+ case 'TOGGLE_FOCUS': {
329
+ if (state.mode.type !== 'browsing')
330
+ return state;
331
+ return { ...state, mode: { ...state.mode, sidebarFocused: !state.mode.sidebarFocused } };
332
+ }
333
+ case 'SCROLL_UP':
334
+ return { ...state, jsonScrollOffset: Math.max(0, state.jsonScrollOffset - 1) };
335
+ case 'SCROLL_DOWN':
336
+ return { ...state, jsonScrollOffset: state.jsonScrollOffset + 1 };
337
+ case 'TOGGLE_SOURCE': {
338
+ if (state.mode.type !== 'browsing')
339
+ return state;
340
+ if (action.terminalWidth < 120)
341
+ return { ...state, saveError: 'Terminal too narrow for source panel (need 120+ cols)' };
342
+ return { ...state, mode: { ...state.mode, sourceVisible: !state.mode.sourceVisible } };
343
+ }
344
+ case 'OPEN_DIALOG':
345
+ return { ...state, mode: { type: 'dialog', which: action.which } };
346
+ case 'CLOSE_DIALOG':
347
+ return { ...state, mode: { type: 'browsing', sidebarFocused: true, sourceVisible: false } };
348
+ case 'FINALIZE_CONFIRM': {
349
+ if (!state.session)
350
+ return state;
351
+ const accepted = state.session.components.filter((c) => c.status === 'accepted').length;
352
+ const rejected = state.session.components.filter((c) => c.status === 'rejected').length;
353
+ const excluded = state.session.components.filter((c) => c.status === 'needs-review').length;
354
+ return { ...state, mode: { type: 'finalized', accepted, rejected, excluded } };
355
+ }
356
+ case 'QUIT_CONFIRM':
357
+ return state; // side effect handles exit
358
+ case 'SOURCE_LOADED':
359
+ return { ...state, sourceCodeById: { ...state.sourceCodeById, [action.componentId]: action.code } };
360
+ case 'PREVIEW_START':
361
+ return { ...state, previewLoading: true };
362
+ case 'PREVIEW_SUCCESS': {
363
+ const components = state.session?.components ?? [];
364
+ const sortedIds = computeSortedIds(components);
365
+ const sessionSummary = computeSessionSummary(components, action.annotations, sortedIds);
366
+ return {
367
+ ...state,
368
+ previewResponse: action.response,
369
+ previewAnnotations: action.annotations,
370
+ previewLoading: false,
371
+ previewError: null,
372
+ sortedIds,
373
+ sessionSummary,
374
+ };
375
+ }
376
+ case 'PREVIEW_ERROR':
377
+ return { ...state, previewLoading: false, previewError: action.message };
378
+ case 'SAVE_ERROR':
379
+ return { ...state, saveError: action.message };
380
+ case 'CLEAR_ERRORS':
381
+ return { ...state, saveError: null, previewError: null };
382
+ default:
383
+ return state;
384
+ }
385
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Compute the new sidebar scroll offset after moving selection to `idx`.
3
+ * Keeps the selected item within the visible window.
4
+ */
5
+ export declare function computeScrollOffset(idx: number, prev: number, visibleCount: number): number;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Compute the new sidebar scroll offset after moving selection to `idx`.
3
+ * Keeps the selected item within the visible window.
4
+ */
5
+ export function computeScrollOffset(idx, prev, visibleCount) {
6
+ if (idx < prev)
7
+ return idx;
8
+ if (idx >= prev + visibleCount)
9
+ return idx - visibleCount + 1;
10
+ return prev;
11
+ }
@@ -22,7 +22,7 @@ export type ReviewComponentSummary = {
22
22
  name: string;
23
23
  status: ReviewComponentStatus;
24
24
  previewAnnotation?: PreviewAnnotation;
25
- extractionConfidence: number;
25
+ extractionConfidence: number | null;
26
26
  needsReview: boolean;
27
27
  };
28
28
  export type ReviewSessionSnapshot = {
@@ -4,7 +4,7 @@ export function createReviewSessionSummary(session) {
4
4
  id: component.id,
5
5
  name: component.name,
6
6
  status: component.status,
7
- extractionConfidence: component.originalProposal.extractionConfidence ?? 100,
7
+ extractionConfidence: component.originalProposal.extractionConfidence ?? null,
8
8
  needsReview: component.originalProposal.needsReview ?? false,
9
9
  })),
10
10
  };
@@ -9,7 +9,7 @@ export type AnalyzeViewResult = {
9
9
  propCount: number;
10
10
  slotCount: number;
11
11
  warnings: string[];
12
- extractionConfidence: number;
12
+ extractionConfidence: number | null;
13
13
  needsReview: boolean;
14
14
  }>;
15
15
  totalWarnings: number;
@@ -3,12 +3,14 @@ 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';
6
7
  function truncateName(name, maxLen = 30) {
7
8
  if (name.length <= maxLen)
8
9
  return name;
9
10
  return name.slice(0, maxLen - 1) + '…';
10
11
  }
11
12
  export function AnalyzeView({ result, onExit }) {
13
+ useRawMode();
12
14
  const [scrollOffset, setScrollOffset] = useState(0);
13
15
  const { stdout } = useStdout();
14
16
  // Header lines: TopBar(1) + summary(3) + blank(1) + dividers+header(3) + blank(1) + footer(1) + footer-bar(1) = ~12
@@ -50,9 +52,9 @@ export function AnalyzeView({ result, onExit }) {
50
52
  result.components.length +
51
53
  ')'
52
54
  : '') }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { children: " " }), showScrollUp && _jsx(Text, { dimColor: true, children: " \u25B2 scroll up" }), visible.map((component) => {
53
- const conf = component.extractionConfidence ?? 100;
54
- const confColor = component.needsReview ? 'red' : conf >= 80 ? 'white' : conf >= 50 ? 'yellow' : 'red';
55
- const confLabel = (component.needsReview ? '⚑ ' : '') + String(conf);
55
+ const conf = component.extractionConfidence;
56
+ const confColor = conf === null ? 'gray' : component.needsReview ? 'red' : conf >= 4 ? 'white' : conf >= 3 ? 'yellow' : 'red';
57
+ const confLabel = conf === null ? '—' : (component.needsReview ? '⚑ ' : '') + String(conf);
56
58
  return (_jsxs(Box, { children: [component.warnings.length > 0 && _jsx(Text, { color: "yellow", children: "\u26A0 " }), component.warnings.length === 0 && _jsx(Text, { children: " " }), _jsx(Text, { children: truncateName(component.name).padEnd(20) }), _jsx(Text, { dimColor: true, children: component.framework.padEnd(10) }), _jsx(Text, { children: (component.propCount + ' props').padEnd(10) }), _jsx(Text, { children: (component.slotCount + ' ' + (component.slotCount === 1 ? 'slot' : 'slots')).padEnd(8) }), _jsx(Text, { color: confColor, children: confLabel }), component.warnings.length > 0 && (_jsx(Text, { color: "yellow", children: ' ⚠ ' + component.warnings.length + ' warning' + (component.warnings.length === 1 ? '' : 's') }))] }, component.name));
57
59
  }), showScrollDown && _jsx(Text, { dimColor: true, children: " \u25BC scroll down" }), result.totalWarnings > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { bold: true, color: "yellow", children: 'Warnings (' + result.totalWarnings + ')' }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { children: " " }), result.components
58
60
  .filter((c) => c.warnings.length > 0)
@@ -30,8 +30,13 @@ export function parseSelectToolCallLines(stdout) {
30
30
  };
31
31
  if (typeof rec.reason === 'string')
32
32
  call.reason = rec.reason;
33
- if (typeof rec.confidence === 'number' && rec.confidence >= 0 && rec.confidence <= 100) {
34
- call.confidence = rec.confidence;
33
+ if (typeof rec.confidence === 'number' && rec.confidence >= 1 && rec.confidence <= 5) {
34
+ if (call.tool === 'select_component') {
35
+ call.confidence = rec.confidence;
36
+ }
37
+ else {
38
+ call.confidence = rec.confidence;
39
+ }
35
40
  }
36
41
  calls.push(call);
37
42
  }
@@ -8,6 +8,7 @@ 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';
11
12
  import { WelcomeStep } from './steps/WelcomeStep.js';
12
13
  import { PathValidationStep } from './steps/PathValidationStep.js';
13
14
  import { RunningStep } from './steps/RunningStep.js';
@@ -44,6 +45,7 @@ function logStep(entry) {
44
45
  export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', initialHost, initialAgent, initialProjectPath, host, } = {}) {
45
46
  const defaultConfiguredHost = toConfiguredHost(host || process.env['EDS_HOST']) ?? DEFAULT_CONFIGURED_HOST;
46
47
  const resolveWizardHost = (hostValue) => hostValue || defaultConfiguredHost;
48
+ useRawMode();
47
49
  const { stdout } = useStdout();
48
50
  const terminalWidth = stdout?.columns ?? 80;
49
51
  const logInit = useRef(false);
@@ -8,6 +8,7 @@ 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';
11
12
  import { openPipelineDb, loadCDFComponents, storeCDFComponents } from '../../../session/db.js';
12
13
  const VISIBLE_COUNT = 20;
13
14
  const PANEL_HEIGHT = 22;
@@ -161,7 +162,7 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, }) {
161
162
  const newIdx = Math.min(components.length - 1, selectedIdx + 1);
162
163
  setSelectedIdx(newIdx);
163
164
  setJsonScrollOffset(0);
164
- setSidebarScrollOffset((prev) => (newIdx >= prev + VISIBLE_COUNT ? newIdx - VISIBLE_COUNT + 1 : prev));
165
+ setSidebarScrollOffset((prev) => computeScrollOffset(newIdx, prev, VISIBLE_COUNT));
165
166
  }
166
167
  }
167
168
  else {
@@ -185,7 +186,7 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, }) {
185
186
  id: c.key,
186
187
  name: c.key,
187
188
  status: c.status,
188
- extractionConfidence: 100,
189
+ extractionConfidence: null,
189
190
  needsReview: false,
190
191
  }));
191
192
  const longestName = components.reduce((m, c) => Math.max(m, c.key.length), 0);
@@ -196,15 +197,7 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, }) {
196
197
  const needsReview = components.filter((c) => c.status === 'needs-review').length;
197
198
  const propCount = selected ? Object.keys(selected.entry.$properties).length : 0;
198
199
  const slotCount = selected?.entry.$slots ? Object.keys(selected.entry.$slots).length : 0;
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
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
206
201
  ? ' [Ctrl+S] save [Esc] discard'
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) }))] }));
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 })] }));
210
203
  }
@@ -3,7 +3,9 @@ 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';
6
7
  export function ValidateView({ results, onExit }) {
8
+ useRawMode();
7
9
  const [scrollOffset, setScrollOffset] = useState(0);
8
10
  const allValid = results.every((r) => r.valid);
9
11
  useImmediateInput((input, key) => {
@@ -43,7 +43,7 @@ CREATE TABLE IF NOT EXISTS raw_components (
43
43
  status TEXT NOT NULL DEFAULT 'extracted',
44
44
  cdf_schema TEXT,
45
45
  description TEXT,
46
- extraction_confidence INTEGER NOT NULL DEFAULT 100,
46
+ extraction_confidence INTEGER,
47
47
  review_reasons TEXT NOT NULL DEFAULT '[]',
48
48
  needs_review INTEGER NOT NULL DEFAULT 0,
49
49
  PRIMARY KEY (session_id, component_id)
@@ -171,7 +171,7 @@ function applyDbMigrations(db) {
171
171
  const rawCompCols = db.prepare('PRAGMA table_info(raw_components)').all();
172
172
  const rawCompColNames = new Set(rawCompCols.map((c) => c.name));
173
173
  if (!rawCompColNames.has('extraction_confidence')) {
174
- db.exec('ALTER TABLE raw_components ADD COLUMN extraction_confidence INTEGER NOT NULL DEFAULT 100');
174
+ db.exec('ALTER TABLE raw_components ADD COLUMN extraction_confidence INTEGER');
175
175
  }
176
176
  if (!rawCompColNames.has('review_reasons')) {
177
177
  db.exec("ALTER TABLE raw_components ADD COLUMN review_reasons TEXT NOT NULL DEFAULT '[]'");
@@ -402,7 +402,7 @@ export function storeRawComponents(db, sessionId, components, options) {
402
402
  db.prepare('DELETE FROM raw_components WHERE session_id = ?').run(sessionId);
403
403
  for (const comp of components) {
404
404
  const componentId = deriveComponentId(comp.name, comp.source);
405
- insertComp.run(sessionId, componentId, comp.name, comp.source, comp.framework, now, comp.extractionConfidence ?? 100, JSON.stringify(comp.reviewReasons ?? []), comp.needsReview ? 1 : 0);
405
+ insertComp.run(sessionId, componentId, comp.name, comp.source, comp.framework, now, comp.extractionConfidence ?? null, JSON.stringify(comp.reviewReasons ?? []), comp.needsReview ? 1 : 0);
406
406
  for (let i = 0; i < comp.props.length; i++) {
407
407
  const prop = comp.props[i];
408
408
  insertProp.run(sessionId, componentId, prop.name, prop.type, prop.required ? 1 : 0, prop.category ?? null, prop.defaultValue ?? null, prop.description ?? null, prop.tokenReference ?? null, i);
@@ -513,7 +513,7 @@ export function loadRawComponents(db, sessionId, allowedNames) {
513
513
  name: c.name,
514
514
  source: c.source,
515
515
  framework: c.framework,
516
- extractionConfidence: c.extraction_confidence ?? 100,
516
+ extractionConfidence: c.extraction_confidence ?? null,
517
517
  reviewReasons: (() => {
518
518
  try {
519
519
  return JSON.parse(c.review_reasons ?? '[]');
@@ -34,7 +34,7 @@ export interface RawComponentDefinition {
34
34
  * non-authorable context-provider components.
35
35
  */
36
36
  usesCreateContext?: boolean;
37
- extractionConfidence?: number;
37
+ extractionConfidence?: number | null;
38
38
  reviewReasons?: string[];
39
39
  needsReview?: boolean;
40
40
  }
@@ -55,3 +55,5 @@ export interface TokenExtractor {
55
55
  name: string;
56
56
  extract(projectRoot: string): Promise<RawTokenDefinition[]>;
57
57
  }
58
+ /** Strip internal scoring fields before serialising a RawComponentDefinition for display or editing. */
59
+ export declare function stripScoringFields({ extractionConfidence: _c, reviewReasons: _r, needsReview: _n, ...rest }: RawComponentDefinition): Omit<RawComponentDefinition, 'extractionConfidence' | 'reviewReasons' | 'needsReview'>;
package/dist/src/types.js CHANGED
@@ -1 +1,4 @@
1
- export {};
1
+ /** Strip internal scoring fields before serialising a RawComponentDefinition for display or editing. */
2
+ export function stripScoringFields({ extractionConfidence: _c, reviewReasons: _r, needsReview: _n, ...rest }) {
3
+ return rest;
4
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.6.2-dev-build-864e323.0",
3
+ "version": "2.7.1-dev-build-02830eb.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.6.2-dev-build-864e323.0"
39
+ "@contentful/experience-design-system-types": "2.7.1-dev-build-02830eb.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@tsconfig/node24": "^24.0.3",