@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.
- package/dist/package.json +1 -1
- package/dist/src/analyze/command.js +1 -1
- package/dist/src/analyze/extract/scoring.d.ts +3 -2
- package/dist/src/analyze/extract/scoring.js +39 -22
- package/dist/src/analyze/select/tui/App.js +54 -474
- package/dist/src/analyze/select/tui/components/ComponentDetail.d.ts +3 -5
- package/dist/src/analyze/select/tui/components/ComponentDetail.js +9 -10
- package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +1 -1
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +2 -10
- package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +1 -1
- package/dist/src/analyze/select/tui/components/HelpOverlay.js +2 -7
- package/dist/src/analyze/select/tui/components/JsonEditor.d.ts +7 -5
- package/dist/src/analyze/select/tui/components/JsonEditor.js +15 -146
- package/dist/src/analyze/select/tui/components/QuitDialog.d.ts +1 -1
- package/dist/src/analyze/select/tui/components/QuitDialog.js +2 -10
- package/dist/src/analyze/select/tui/components/Sidebar.d.ts +0 -2
- package/dist/src/analyze/select/tui/components/StatusBar.d.ts +0 -2
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +5 -0
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +7 -4
- package/dist/src/analyze/select/tui/hooks/useKeymap.d.ts +0 -23
- package/dist/src/analyze/select/tui/hooks/useKeymap.js +1 -67
- package/dist/src/analyze/select/tui/hooks/useRawMode.d.ts +6 -0
- package/dist/src/analyze/select/tui/hooks/useRawMode.js +16 -0
- package/dist/src/analyze/select/tui/hooks/useSideEffects.d.ts +17 -0
- package/dist/src/analyze/select/tui/hooks/useSideEffects.js +226 -0
- package/dist/src/analyze/select/tui/hooks/useUndo.js +15 -21
- package/dist/src/analyze/select/tui/inputToAction.d.ts +13 -0
- package/dist/src/analyze/select/tui/inputToAction.js +68 -0
- package/dist/src/analyze/select/tui/state.d.ts +136 -0
- package/dist/src/analyze/select/tui/state.js +385 -0
- package/dist/src/analyze/select/tui/utils.d.ts +5 -0
- package/dist/src/analyze/select/tui/utils.js +11 -0
- package/dist/src/analyze/select/types.d.ts +1 -1
- package/dist/src/analyze/select/types.js +1 -1
- package/dist/src/analyze/tui/AnalyzeView.d.ts +1 -1
- package/dist/src/analyze/tui/AnalyzeView.js +5 -3
- package/dist/src/generate/agent-runner.js +7 -2
- package/dist/src/import/tui/WizardApp.js +2 -0
- package/dist/src/import/tui/steps/GenerateReviewStep.js +5 -12
- package/dist/src/print/validate/tui/ValidateView.js +2 -0
- package/dist/src/session/db.js +4 -4
- package/dist/src/types.d.ts +3 -1
- package/dist/src/types.js +4 -1
- package/package.json +2 -2
- 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,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 ??
|
|
7
|
+
extractionConfidence: component.originalProposal.extractionConfidence ?? null,
|
|
8
8
|
needsReview: component.originalProposal.needsReview ?? false,
|
|
9
9
|
})),
|
|
10
10
|
};
|
|
@@ -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
|
|
54
|
-
const confColor = component.needsReview ? 'red' : conf >=
|
|
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 >=
|
|
34
|
-
call.
|
|
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
|
|
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:
|
|
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,
|
|
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 &&
|
|
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) => {
|
package/dist/src/session/db.js
CHANGED
|
@@ -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
|
|
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
|
|
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 ??
|
|
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 ??
|
|
516
|
+
extractionConfidence: c.extraction_confidence ?? null,
|
|
517
517
|
reviewReasons: (() => {
|
|
518
518
|
try {
|
|
519
519
|
return JSON.parse(c.review_reasons ?? '[]');
|
package/dist/src/types.d.ts
CHANGED
|
@@ -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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
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",
|