@contentful/experience-design-system-cli 2.2.1

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 (165) hide show
  1. package/README.md +532 -0
  2. package/bin/cli.js +58 -0
  3. package/dist/package.json +56 -0
  4. package/dist/src/analyze/command.d.ts +3 -0
  5. package/dist/src/analyze/command.js +175 -0
  6. package/dist/src/analyze/extract/astro.d.ts +5 -0
  7. package/dist/src/analyze/extract/astro.js +280 -0
  8. package/dist/src/analyze/extract/pipeline.d.ts +6 -0
  9. package/dist/src/analyze/extract/pipeline.js +298 -0
  10. package/dist/src/analyze/extract/react.d.ts +2 -0
  11. package/dist/src/analyze/extract/react.js +1949 -0
  12. package/dist/src/analyze/extract/slot-detection.d.ts +35 -0
  13. package/dist/src/analyze/extract/slot-detection.js +101 -0
  14. package/dist/src/analyze/extract/stencil.d.ts +2 -0
  15. package/dist/src/analyze/extract/stencil.js +293 -0
  16. package/dist/src/analyze/extract/tsx-shared.d.ts +8 -0
  17. package/dist/src/analyze/extract/tsx-shared.js +263 -0
  18. package/dist/src/analyze/extract/vue-tsx.d.ts +2 -0
  19. package/dist/src/analyze/extract/vue-tsx.js +498 -0
  20. package/dist/src/analyze/extract/vue.d.ts +5 -0
  21. package/dist/src/analyze/extract/vue.js +647 -0
  22. package/dist/src/analyze/extract/web-components.d.ts +2 -0
  23. package/dist/src/analyze/extract/web-components.js +866 -0
  24. package/dist/src/analyze/pre-classify.d.ts +17 -0
  25. package/dist/src/analyze/pre-classify.js +144 -0
  26. package/dist/src/analyze/select/command.d.ts +2 -0
  27. package/dist/src/analyze/select/command.js +256 -0
  28. package/dist/src/analyze/select/index.d.ts +6 -0
  29. package/dist/src/analyze/select/index.js +5 -0
  30. package/dist/src/analyze/select/parser.d.ts +6 -0
  31. package/dist/src/analyze/select/parser.js +53 -0
  32. package/dist/src/analyze/select/persistence.d.ts +9 -0
  33. package/dist/src/analyze/select/persistence.js +42 -0
  34. package/dist/src/analyze/select/stdout.d.ts +7 -0
  35. package/dist/src/analyze/select/stdout.js +3 -0
  36. package/dist/src/analyze/select/tui/App.d.ts +8 -0
  37. package/dist/src/analyze/select/tui/App.js +491 -0
  38. package/dist/src/analyze/select/tui/components/ComponentDetail.d.ts +20 -0
  39. package/dist/src/analyze/select/tui/components/ComponentDetail.js +43 -0
  40. package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +11 -0
  41. package/dist/src/analyze/select/tui/components/FieldEditor.js +531 -0
  42. package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +10 -0
  43. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +15 -0
  44. package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +7 -0
  45. package/dist/src/analyze/select/tui/components/HelpOverlay.js +11 -0
  46. package/dist/src/analyze/select/tui/components/JsonEditor.d.ts +11 -0
  47. package/dist/src/analyze/select/tui/components/JsonEditor.js +154 -0
  48. package/dist/src/analyze/select/tui/components/JsonPanel.d.ts +11 -0
  49. package/dist/src/analyze/select/tui/components/JsonPanel.js +62 -0
  50. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.d.ts +8 -0
  51. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +29 -0
  52. package/dist/src/analyze/select/tui/components/QuitDialog.d.ts +8 -0
  53. package/dist/src/analyze/select/tui/components/QuitDialog.js +14 -0
  54. package/dist/src/analyze/select/tui/components/Sidebar.d.ts +15 -0
  55. package/dist/src/analyze/select/tui/components/Sidebar.js +48 -0
  56. package/dist/src/analyze/select/tui/components/SourcePanel.d.ts +11 -0
  57. package/dist/src/analyze/select/tui/components/SourcePanel.js +52 -0
  58. package/dist/src/analyze/select/tui/components/StatusBar.d.ts +11 -0
  59. package/dist/src/analyze/select/tui/components/StatusBar.js +6 -0
  60. package/dist/src/analyze/select/tui/components/TopBar.d.ts +10 -0
  61. package/dist/src/analyze/select/tui/components/TopBar.js +5 -0
  62. package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +24 -0
  63. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +68 -0
  64. package/dist/src/analyze/select/tui/hooks/useKeymap.d.ts +24 -0
  65. package/dist/src/analyze/select/tui/hooks/useKeymap.js +67 -0
  66. package/dist/src/analyze/select/tui/hooks/useSession.d.ts +19 -0
  67. package/dist/src/analyze/select/tui/hooks/useSession.js +52 -0
  68. package/dist/src/analyze/select/tui/hooks/useUndo.d.ts +8 -0
  69. package/dist/src/analyze/select/tui/hooks/useUndo.js +26 -0
  70. package/dist/src/analyze/select/types.d.ts +46 -0
  71. package/dist/src/analyze/select/types.js +20 -0
  72. package/dist/src/analyze/select-agent/command.d.ts +2 -0
  73. package/dist/src/analyze/select-agent/command.js +208 -0
  74. package/dist/src/analyze/tui/AnalyzeView.d.ts +24 -0
  75. package/dist/src/analyze/tui/AnalyzeView.js +38 -0
  76. package/dist/src/apply/api-client.d.ts +35 -0
  77. package/dist/src/apply/api-client.js +143 -0
  78. package/dist/src/apply/command.d.ts +6 -0
  79. package/dist/src/apply/command.js +787 -0
  80. package/dist/src/apply/manifest.d.ts +1 -0
  81. package/dist/src/apply/manifest.js +1 -0
  82. package/dist/src/apply/tui/SelectView.d.ts +18 -0
  83. package/dist/src/apply/tui/SelectView.js +34 -0
  84. package/dist/src/apply/tui/ServerApplyView.d.ts +32 -0
  85. package/dist/src/apply/tui/ServerApplyView.js +42 -0
  86. package/dist/src/apply/tui/ServerPreviewView.d.ts +9 -0
  87. package/dist/src/apply/tui/ServerPreviewView.js +21 -0
  88. package/dist/src/credentials-store.d.ts +8 -0
  89. package/dist/src/credentials-store.js +30 -0
  90. package/dist/src/generate/agent-runner.d.ts +86 -0
  91. package/dist/src/generate/agent-runner.js +314 -0
  92. package/dist/src/generate/command.d.ts +2 -0
  93. package/dist/src/generate/command.js +545 -0
  94. package/dist/src/generate/edit/command.d.ts +2 -0
  95. package/dist/src/generate/edit/command.js +126 -0
  96. package/dist/src/generate/prompt-builder.d.ts +18 -0
  97. package/dist/src/generate/prompt-builder.js +202 -0
  98. package/dist/src/generate/tui/GenerateView.d.ts +12 -0
  99. package/dist/src/generate/tui/GenerateView.js +10 -0
  100. package/dist/src/import/command.d.ts +2 -0
  101. package/dist/src/import/command.js +96 -0
  102. package/dist/src/import/orchestrator.d.ts +37 -0
  103. package/dist/src/import/orchestrator.js +374 -0
  104. package/dist/src/import/path-utils.d.ts +15 -0
  105. package/dist/src/import/path-utils.js +30 -0
  106. package/dist/src/import/tui/WizardApp.d.ts +10 -0
  107. package/dist/src/import/tui/WizardApp.js +906 -0
  108. package/dist/src/import/tui/steps/CredentialsStep.d.ts +15 -0
  109. package/dist/src/import/tui/steps/CredentialsStep.js +79 -0
  110. package/dist/src/import/tui/steps/DoneStep.d.ts +20 -0
  111. package/dist/src/import/tui/steps/DoneStep.js +17 -0
  112. package/dist/src/import/tui/steps/ErrorStep.d.ts +8 -0
  113. package/dist/src/import/tui/steps/ErrorStep.js +11 -0
  114. package/dist/src/import/tui/steps/GateStep.d.ts +14 -0
  115. package/dist/src/import/tui/steps/GateStep.js +20 -0
  116. package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +8 -0
  117. package/dist/src/import/tui/steps/GenerateReviewStep.js +208 -0
  118. package/dist/src/import/tui/steps/PathValidationStep.d.ts +10 -0
  119. package/dist/src/import/tui/steps/PathValidationStep.js +151 -0
  120. package/dist/src/import/tui/steps/PreviewStep.d.ts +21 -0
  121. package/dist/src/import/tui/steps/PreviewStep.js +36 -0
  122. package/dist/src/import/tui/steps/RunningStep.d.ts +10 -0
  123. package/dist/src/import/tui/steps/RunningStep.js +20 -0
  124. package/dist/src/import/tui/steps/TokenInputStep.d.ts +8 -0
  125. package/dist/src/import/tui/steps/TokenInputStep.js +70 -0
  126. package/dist/src/import/tui/steps/WelcomeStep.d.ts +7 -0
  127. package/dist/src/import/tui/steps/WelcomeStep.js +33 -0
  128. package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +15 -0
  129. package/dist/src/import/tui/steps/WizardPreviewStep.js +121 -0
  130. package/dist/src/import/tui/steps/preview-diff.d.ts +10 -0
  131. package/dist/src/import/tui/steps/preview-diff.js +132 -0
  132. package/dist/src/index.d.ts +1 -0
  133. package/dist/src/index.js +2 -0
  134. package/dist/src/output/format.d.ts +23 -0
  135. package/dist/src/output/format.js +110 -0
  136. package/dist/src/print/command.d.ts +2 -0
  137. package/dist/src/print/command.js +199 -0
  138. package/dist/src/print/validate/tui/ValidateView.d.ts +15 -0
  139. package/dist/src/print/validate/tui/ValidateView.js +37 -0
  140. package/dist/src/print/validate/validators/cdf-validator.d.ts +2 -0
  141. package/dist/src/print/validate/validators/cdf-validator.js +104 -0
  142. package/dist/src/print/validate/validators/dtcg-validator.d.ts +2 -0
  143. package/dist/src/print/validate/validators/dtcg-validator.js +110 -0
  144. package/dist/src/print/validate/validators/format-errors.d.ts +12 -0
  145. package/dist/src/print/validate/validators/format-errors.js +18 -0
  146. package/dist/src/program.d.ts +2 -0
  147. package/dist/src/program.js +25 -0
  148. package/dist/src/session/command.d.ts +2 -0
  149. package/dist/src/session/command.js +261 -0
  150. package/dist/src/session/db.d.ts +111 -0
  151. package/dist/src/session/db.js +1114 -0
  152. package/dist/src/session/migration.d.ts +4 -0
  153. package/dist/src/session/migration.js +117 -0
  154. package/dist/src/session/session-id.d.ts +1 -0
  155. package/dist/src/session/session-id.js +212 -0
  156. package/dist/src/session/stats.d.ts +27 -0
  157. package/dist/src/session/stats.js +89 -0
  158. package/dist/src/setup/command.d.ts +2 -0
  159. package/dist/src/setup/command.js +765 -0
  160. package/dist/src/types.d.ts +48 -0
  161. package/dist/src/types.js +1 -0
  162. package/package.json +55 -0
  163. package/skills/generate-components.md +361 -0
  164. package/skills/generate-tokens.md +194 -0
  165. package/skills/select-components.md +180 -0
@@ -0,0 +1,531 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { CDF_PROPERTY_TYPES, CDF_PROPERTY_CATEGORIES, DESIGN_TOKEN_TYPES, } from '@contentful/experience-design-system-types';
5
+ import { useImmediateInput } from '../hooks/useImmediateInput.js';
6
+ // Section indices for top-level navigation
7
+ // 0 = component $description row
8
+ // 1 = $properties header (then props[0..n-1])
9
+ // 2 = $slots header (then slots[0..m-1])
10
+ // ── Parsing helpers ───────────────────────────────────────────────────────────
11
+ function parseToState(json) {
12
+ let parsed;
13
+ try {
14
+ parsed = JSON.parse(json);
15
+ }
16
+ catch (e) {
17
+ return { state: { componentDescription: '', props: [], slots: [] }, error: String(e) };
18
+ }
19
+ if (typeof parsed !== 'object' || parsed === null) {
20
+ return {
21
+ state: { componentDescription: '', props: [], slots: [] },
22
+ error: 'Expected a JSON object',
23
+ };
24
+ }
25
+ const entry = parsed;
26
+ // Handle both bare CDFComponentEntry and wrapped { [name]: entry } forms
27
+ let component;
28
+ const keys = Object.keys(entry);
29
+ if (entry.$type === 'component' || entry.$properties !== undefined) {
30
+ component = entry;
31
+ }
32
+ else if (keys.length === 1 && typeof entry[keys[0]] === 'object') {
33
+ component = entry[keys[0]];
34
+ }
35
+ else {
36
+ component = entry;
37
+ }
38
+ const componentDescription = typeof component.$description === 'string' ? component.$description : '';
39
+ const rawProps = (component.$properties ?? {});
40
+ const props = Object.entries(rawProps).map(([name, raw]) => {
41
+ const p = (raw ?? {});
42
+ return {
43
+ name,
44
+ type: CDF_PROPERTY_TYPES.includes(p.$type) ? p.$type : 'string',
45
+ category: CDF_PROPERTY_CATEGORIES.includes(p.$category)
46
+ ? p.$category
47
+ : 'content',
48
+ required: p.$required === true,
49
+ description: typeof p.$description === 'string' ? p.$description : '',
50
+ values: Array.isArray(p.$values) ? p.$values.filter((v) => typeof v === 'string') : [],
51
+ tokenKind: typeof p['$token.kind'] === 'string' ? p['$token.kind'] : '',
52
+ };
53
+ });
54
+ const rawSlots = (component.$slots ?? {});
55
+ const slots = Object.entries(rawSlots).map(([name, raw]) => {
56
+ const s = (raw ?? {});
57
+ return {
58
+ name,
59
+ description: typeof s.$description === 'string' ? s.$description : '',
60
+ required: s.$required === true,
61
+ };
62
+ });
63
+ return { state: { componentDescription, props, slots }, error: null };
64
+ }
65
+ function serializeState(state, originalJson) {
66
+ // Preserve the wrapper key if the original was wrapped
67
+ let wrapperKey = null;
68
+ try {
69
+ const orig = JSON.parse(originalJson);
70
+ const keys = Object.keys(orig);
71
+ if (keys.length === 1 && orig[keys[0]] !== null && typeof orig[keys[0]] === 'object') {
72
+ const inner = orig[keys[0]];
73
+ if (inner.$type === 'component' || inner.$properties !== undefined) {
74
+ wrapperKey = keys[0];
75
+ }
76
+ }
77
+ }
78
+ catch {
79
+ // ignore
80
+ }
81
+ const $properties = {};
82
+ for (const p of state.props) {
83
+ const def = {
84
+ $type: p.type,
85
+ $category: p.category,
86
+ };
87
+ if (p.required)
88
+ def.$required = true;
89
+ if (p.description)
90
+ def.$description = p.description;
91
+ if (p.type === 'enum' && p.values.length > 0)
92
+ def.$values = p.values;
93
+ if (p.type === 'token' && p.tokenKind)
94
+ def['$token.kind'] = p.tokenKind;
95
+ $properties[p.name] = def;
96
+ }
97
+ const entry = {
98
+ $type: 'component',
99
+ $properties,
100
+ };
101
+ if (state.componentDescription)
102
+ entry.$description = state.componentDescription;
103
+ if (state.slots.length > 0) {
104
+ entry.$slots = {};
105
+ for (const s of state.slots) {
106
+ const slotDef = {};
107
+ if (s.description)
108
+ slotDef.$description = s.description;
109
+ if (s.required)
110
+ slotDef.$required = true;
111
+ entry.$slots[s.name] = slotDef;
112
+ }
113
+ }
114
+ if (wrapperKey) {
115
+ return JSON.stringify({ [wrapperKey]: entry }, null, 2);
116
+ }
117
+ return JSON.stringify(entry, null, 2);
118
+ }
119
+ // ── Sub-components ────────────────────────────────────────────────────────────
120
+ function Picker({ value, active }) {
121
+ return (_jsxs(Box, { children: [active && _jsx(Text, { color: "cyan", children: '‹' }), _jsx(Text, { color: active ? 'cyan' : 'white', bold: active, children: value }), active && _jsx(Text, { color: "cyan", children: '›' })] }));
122
+ }
123
+ function Toggle({ value, active }) {
124
+ return (_jsx(Box, { children: _jsx(Text, { color: active ? 'cyan' : value ? 'green' : undefined, children: value ? '[✓]' : '[ ]' }) }));
125
+ }
126
+ function PropRow({ prop, selected, activeField, textCursor, valueCursor, cursorVisible, width, }) {
127
+ const cursor = cursorVisible ? '█' : ' ';
128
+ const bg = selected ? 'blue' : undefined;
129
+ // Name column — fixed 14 chars
130
+ const nameDisplay = prop.name.length > 14 ? prop.name.slice(0, 13) + '…' : prop.name.padEnd(14);
131
+ return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsxs(Box, { gap: 1, children: [_jsxs(Text, { color: selected ? 'white' : 'cyan', bold: selected, backgroundColor: bg, children: [' ', nameDisplay, ' '] }), _jsx(Text, { dimColor: !selected, children: "type:" }), activeField === 'type' ? (_jsx(Picker, { value: prop.type, active: true })) : (_jsx(Text, { color: selected ? 'yellow' : 'white', children: prop.type })), _jsx(Text, { dimColor: !selected, children: "cat:" }), activeField === 'category' ? (_jsx(Picker, { value: prop.category, active: true })) : (_jsx(Text, { color: selected ? 'magenta' : 'white', children: prop.category })), _jsx(Text, { dimColor: !selected, children: "req:" }), activeField === 'required' ? (_jsx(Toggle, { value: prop.required, active: true })) : (_jsx(Toggle, { value: prop.required, active: false })), prop.type === 'token' && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: !selected, children: "kind:" }), activeField === 'tokenKind' ? (_jsx(Picker, { value: prop.tokenKind || DESIGN_TOKEN_TYPES[0], active: true })) : (_jsx(Text, { color: selected ? 'green' : 'white', children: prop.tokenKind || '—' }))] }))] }), selected && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: "desc:" }), activeField === 'description' ? (_jsxs(Box, { children: [_jsx(Text, { children: prop.description.slice(0, textCursor) }), _jsx(Text, { inverse: cursorVisible, children: prop.description[textCursor] ?? cursor }), _jsx(Text, { children: prop.description.slice(textCursor + 1) })] })) : (_jsx(Text, { color: "green", children: prop.description || '—' }))] })), selected && prop.type === 'enum' && (_jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "values:" }), prop.values.length === 0 && _jsx(Text, { dimColor: true, children: " (none)" }), prop.values.map((v, i) => (_jsx(Box, { gap: 1, paddingLeft: 2, children: _jsx(Text, { color: activeField === 'values' && valueCursor === i ? 'cyan' : 'white', children: activeField === 'values' && valueCursor === i ? `▶ ${v}` : ` ${v}` }) }, i))), activeField === 'values' && (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, children: "[+] add [x] remove [[] up []] down [Esc] done" }) }))] }))] }));
132
+ }
133
+ function SlotRow({ slot, selected, activeField, textCursor, cursorVisible, width, }) {
134
+ const cursor = cursorVisible ? '█' : ' ';
135
+ const bg = selected ? 'blue' : undefined;
136
+ const nameDisplay = slot.name.length > 14 ? slot.name.slice(0, 13) + '…' : slot.name.padEnd(14);
137
+ return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsxs(Box, { gap: 1, children: [_jsxs(Text, { color: selected ? 'white' : 'cyan', bold: selected, backgroundColor: bg, children: [' ', nameDisplay, ' '] }), _jsx(Text, { dimColor: !selected, children: "req:" }), activeField === 'required' ? (_jsx(Toggle, { value: slot.required, active: true })) : (_jsx(Toggle, { value: slot.required, active: false }))] }), selected && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: "desc:" }), activeField === 'description' ? (_jsxs(Box, { children: [_jsx(Text, { children: slot.description.slice(0, textCursor) }), _jsx(Text, { inverse: cursorVisible, children: slot.description[textCursor] ?? cursor }), _jsx(Text, { children: slot.description.slice(textCursor + 1) })] })) : (_jsx(Text, { color: "green", children: slot.description || '—' }))] }))] }));
138
+ }
139
+ const PROP_FIELDS_BASE = ['type', 'category', 'required', 'description'];
140
+ function propFields(prop) {
141
+ const fields = [...PROP_FIELDS_BASE];
142
+ if (prop.type === 'token')
143
+ fields.splice(3, 0, 'tokenKind');
144
+ if (prop.type === 'enum')
145
+ fields.push('values');
146
+ return fields;
147
+ }
148
+ const SLOT_FIELDS = ['required', 'description'];
149
+ // ── Main component ────────────────────────────────────────────────────────────
150
+ export function FieldEditor({ value, width, height, onChange, onSave, onDiscard, }) {
151
+ const { state: initialState, error: parseError } = parseToState(value);
152
+ const [editorState, setEditorState] = useState(initialState);
153
+ const [parseErr] = useState(parseError);
154
+ // Navigation state
155
+ const [focusLevel, setFocusLevel] = useState('prop');
156
+ const [propIdx, setPropIdx] = useState(0);
157
+ const [slotIdx, setSlotIdx] = useState(0);
158
+ // Whether we're navigating props (true) or slots (false) at the top level
159
+ const [inSlots, setInSlots] = useState(false);
160
+ // Active field within a prop/slot
161
+ const [activeField, setActiveField] = useState(null);
162
+ // Text cursor position for description fields
163
+ const [textCursor, setTextCursor] = useState(0);
164
+ // Value list cursor for $values editing
165
+ const [valueCursor, setValueCursor] = useState(0);
166
+ // New value being typed in $values add mode
167
+ const [addingValue, setAddingValue] = useState(false);
168
+ const [newValueText, setNewValueText] = useState('');
169
+ const [validationError, setValidationError] = useState(null);
170
+ const [cursorVisible] = useState(true);
171
+ const props = editorState.props;
172
+ const slots = editorState.slots;
173
+ const currentProp = props[propIdx] ?? null;
174
+ const currentSlot = slots[slotIdx] ?? null;
175
+ // Emit serialized JSON whenever state changes
176
+ const commit = (next) => {
177
+ setEditorState(next);
178
+ onChange(serializeState(next, value));
179
+ };
180
+ useImmediateInput((input, key) => {
181
+ // ── Save / Discard ───────────────────────────────────────────────────────
182
+ if (key.ctrl && input === 's') {
183
+ setValidationError(null);
184
+ onSave();
185
+ return;
186
+ }
187
+ if (key.escape) {
188
+ if (focusLevel === 'field') {
189
+ // Exit field editing back to prop/slot row level
190
+ setFocusLevel(inSlots ? 'slot' : 'prop');
191
+ setActiveField(null);
192
+ setAddingValue(false);
193
+ setNewValueText('');
194
+ return;
195
+ }
196
+ if (focusLevel === 'value') {
197
+ setFocusLevel('field');
198
+ setAddingValue(false);
199
+ setNewValueText('');
200
+ return;
201
+ }
202
+ onDiscard();
203
+ return;
204
+ }
205
+ // ── Prop-level navigation (not inside a field) ───────────────────────────
206
+ if (focusLevel === 'prop') {
207
+ if (key.upArrow || input === 'k') {
208
+ if (propIdx > 0) {
209
+ setPropIdx((i) => i - 1);
210
+ setActiveField(null);
211
+ }
212
+ else if (slots.length > 0) {
213
+ setInSlots(true);
214
+ setFocusLevel('slot');
215
+ setActiveField(null);
216
+ }
217
+ return;
218
+ }
219
+ if (key.downArrow || input === 'j') {
220
+ if (propIdx < props.length - 1) {
221
+ setPropIdx((i) => i + 1);
222
+ setActiveField(null);
223
+ }
224
+ else if (slots.length > 0) {
225
+ setInSlots(true);
226
+ setFocusLevel('slot');
227
+ setActiveField(null);
228
+ }
229
+ return;
230
+ }
231
+ if (key.return && currentProp) {
232
+ // Enter field editing on the first field of this prop
233
+ setFocusLevel('field');
234
+ setActiveField(propFields(currentProp)[0] ?? null);
235
+ setTextCursor(currentProp.description.length);
236
+ return;
237
+ }
238
+ return;
239
+ }
240
+ // ── Slot-level navigation ────────────────────────────────────────────────
241
+ if (focusLevel === 'slot') {
242
+ if (key.upArrow || input === 'k') {
243
+ if (slotIdx > 0) {
244
+ setSlotIdx((i) => i - 1);
245
+ setActiveField(null);
246
+ }
247
+ else if (props.length > 0) {
248
+ setInSlots(false);
249
+ setFocusLevel('prop');
250
+ setPropIdx(props.length - 1);
251
+ setActiveField(null);
252
+ }
253
+ return;
254
+ }
255
+ if (key.downArrow || input === 'j') {
256
+ if (slotIdx < slots.length - 1) {
257
+ setSlotIdx((i) => i + 1);
258
+ setActiveField(null);
259
+ }
260
+ return;
261
+ }
262
+ if (key.return && currentSlot) {
263
+ setFocusLevel('field');
264
+ setActiveField(SLOT_FIELDS[0] ?? null);
265
+ setTextCursor(currentSlot.description.length);
266
+ return;
267
+ }
268
+ return;
269
+ }
270
+ // ── Field-level navigation (inside a prop/slot, selecting which field) ───
271
+ if (focusLevel === 'field') {
272
+ const fields = inSlots ? SLOT_FIELDS : currentProp ? propFields(currentProp) : [];
273
+ const currentFieldIdx = fields.indexOf(activeField);
274
+ if (key.upArrow || input === 'k') {
275
+ if (currentFieldIdx > 0) {
276
+ const next = fields[currentFieldIdx - 1];
277
+ setActiveField(next);
278
+ if (next === 'description') {
279
+ const desc = inSlots ? (currentSlot?.description ?? '') : (currentProp?.description ?? '');
280
+ setTextCursor(desc.length);
281
+ }
282
+ }
283
+ else {
284
+ // Back to row navigation
285
+ setFocusLevel(inSlots ? 'slot' : 'prop');
286
+ setActiveField(null);
287
+ }
288
+ return;
289
+ }
290
+ if (key.downArrow || input === 'j') {
291
+ if (currentFieldIdx < fields.length - 1) {
292
+ const next = fields[currentFieldIdx + 1];
293
+ setActiveField(next);
294
+ if (next === 'description') {
295
+ const desc = inSlots ? (currentSlot?.description ?? '') : (currentProp?.description ?? '');
296
+ setTextCursor(desc.length);
297
+ }
298
+ }
299
+ return;
300
+ }
301
+ // ── Picker fields (left/right cycle) ──────────────────────────────────
302
+ if (activeField === 'type' && (key.leftArrow || key.rightArrow) && currentProp) {
303
+ const options = CDF_PROPERTY_TYPES;
304
+ const idx = options.indexOf(currentProp.type);
305
+ const next = key.leftArrow
306
+ ? options[(idx - 1 + options.length) % options.length]
307
+ : options[(idx + 1) % options.length];
308
+ const updated = { ...currentProp, type: next };
309
+ // Clear token/enum specific fields when switching away
310
+ if (next !== 'token')
311
+ updated.tokenKind = '';
312
+ if (next !== 'enum')
313
+ updated.values = [];
314
+ const nextProps = props.map((p, i) => (i === propIdx ? updated : p));
315
+ commit({ ...editorState, props: nextProps });
316
+ return;
317
+ }
318
+ if (activeField === 'category' && (key.leftArrow || key.rightArrow) && currentProp) {
319
+ const options = CDF_PROPERTY_CATEGORIES;
320
+ const idx = options.indexOf(currentProp.category);
321
+ const next = key.leftArrow
322
+ ? options[(idx - 1 + options.length) % options.length]
323
+ : options[(idx + 1) % options.length];
324
+ const nextProps = props.map((p, i) => (i === propIdx ? { ...p, category: next } : p));
325
+ commit({ ...editorState, props: nextProps });
326
+ return;
327
+ }
328
+ if (activeField === 'tokenKind' && (key.leftArrow || key.rightArrow) && currentProp) {
329
+ const options = DESIGN_TOKEN_TYPES;
330
+ const cur = currentProp.tokenKind || options[0];
331
+ const idx = options.indexOf(cur);
332
+ const next = key.leftArrow
333
+ ? options[(idx - 1 + options.length) % options.length]
334
+ : options[(idx + 1) % options.length];
335
+ const nextProps = props.map((p, i) => (i === propIdx ? { ...p, tokenKind: next } : p));
336
+ commit({ ...editorState, props: nextProps });
337
+ return;
338
+ }
339
+ // ── Toggle fields (space/enter) ────────────────────────────────────────
340
+ if (activeField === 'required' && (key.return || input === ' ')) {
341
+ if (inSlots && currentSlot) {
342
+ const nextSlots = slots.map((s, i) => (i === slotIdx ? { ...s, required: !s.required } : s));
343
+ commit({ ...editorState, slots: nextSlots });
344
+ }
345
+ else if (currentProp) {
346
+ const nextProps = props.map((p, i) => (i === propIdx ? { ...p, required: !p.required } : p));
347
+ commit({ ...editorState, props: nextProps });
348
+ }
349
+ return;
350
+ }
351
+ // ── Description text input ─────────────────────────────────────────────
352
+ if (activeField === 'description') {
353
+ const getDesc = () => (inSlots ? (currentSlot?.description ?? '') : (currentProp?.description ?? ''));
354
+ const setDesc = (next) => {
355
+ if (inSlots && currentSlot) {
356
+ const nextSlots = slots.map((s, i) => (i === slotIdx ? { ...s, description: next } : s));
357
+ commit({ ...editorState, slots: nextSlots });
358
+ }
359
+ else if (currentProp) {
360
+ const nextProps = props.map((p, i) => (i === propIdx ? { ...p, description: next } : p));
361
+ commit({ ...editorState, props: nextProps });
362
+ }
363
+ };
364
+ const desc = getDesc();
365
+ if (key.leftArrow) {
366
+ setTextCursor((c) => Math.max(0, c - 1));
367
+ return;
368
+ }
369
+ if (key.rightArrow) {
370
+ setTextCursor((c) => Math.min(desc.length, c + 1));
371
+ return;
372
+ }
373
+ if (input === '\x1b[H' || input === '\x1b[1~') {
374
+ setTextCursor(0);
375
+ return;
376
+ }
377
+ if (input === '\x1b[F' || input === '\x1b[4~') {
378
+ setTextCursor(desc.length);
379
+ return;
380
+ }
381
+ if (key.backspace) {
382
+ if (textCursor > 0) {
383
+ setDesc(desc.slice(0, textCursor - 1) + desc.slice(textCursor));
384
+ setTextCursor((c) => c - 1);
385
+ }
386
+ return;
387
+ }
388
+ if (key.delete) {
389
+ if (textCursor < desc.length)
390
+ setDesc(desc.slice(0, textCursor) + desc.slice(textCursor + 1));
391
+ return;
392
+ }
393
+ if (input && input.length === 1 && !key.ctrl && !key.meta && !key.return) {
394
+ setDesc(desc.slice(0, textCursor) + input + desc.slice(textCursor));
395
+ setTextCursor((c) => c + 1);
396
+ return;
397
+ }
398
+ return;
399
+ }
400
+ // ── Enter $values sub-list ─────────────────────────────────────────────
401
+ if (activeField === 'values' && key.return && currentProp) {
402
+ setFocusLevel('value');
403
+ setValueCursor(0);
404
+ return;
405
+ }
406
+ return;
407
+ }
408
+ // ── Value-list editing ────────────────────────────────────────────────────
409
+ if (focusLevel === 'value' && currentProp) {
410
+ const vals = currentProp.values;
411
+ if (addingValue) {
412
+ // Typing a new value
413
+ if (key.return) {
414
+ if (newValueText.trim()) {
415
+ const nextVals = [...vals, newValueText.trim()];
416
+ const nextProps = props.map((p, i) => (i === propIdx ? { ...p, values: nextVals } : p));
417
+ commit({ ...editorState, props: nextProps });
418
+ setValueCursor(nextVals.length - 1);
419
+ }
420
+ setAddingValue(false);
421
+ setNewValueText('');
422
+ return;
423
+ }
424
+ if (key.escape) {
425
+ setAddingValue(false);
426
+ setNewValueText('');
427
+ return;
428
+ }
429
+ if (key.backspace) {
430
+ setNewValueText((t) => t.slice(0, -1));
431
+ return;
432
+ }
433
+ if (input && input.length === 1 && !key.ctrl && !key.meta) {
434
+ setNewValueText((t) => t + input);
435
+ return;
436
+ }
437
+ return;
438
+ }
439
+ if (key.upArrow || input === 'k') {
440
+ setValueCursor((c) => Math.max(0, c - 1));
441
+ return;
442
+ }
443
+ if (key.downArrow || input === 'j') {
444
+ setValueCursor((c) => Math.max(0, Math.min(vals.length - 1, c + 1)));
445
+ return;
446
+ }
447
+ if (key.escape) {
448
+ setFocusLevel('field');
449
+ return;
450
+ }
451
+ if (input === '+') {
452
+ setAddingValue(true);
453
+ setNewValueText('');
454
+ return;
455
+ }
456
+ if (input === 'x' && vals.length > 0) {
457
+ const nextVals = vals.filter((_, i) => i !== valueCursor);
458
+ const nextProps = props.map((p, i) => (i === propIdx ? { ...p, values: nextVals } : p));
459
+ commit({ ...editorState, props: nextProps });
460
+ setValueCursor((c) => Math.max(0, Math.min(c, nextVals.length - 1)));
461
+ return;
462
+ }
463
+ // '[' moves item up, ']' moves item down
464
+ if (input === '[' && valueCursor > 0) {
465
+ const nextVals = [...vals];
466
+ [nextVals[valueCursor - 1], nextVals[valueCursor]] = [nextVals[valueCursor], nextVals[valueCursor - 1]];
467
+ const nextProps = props.map((p, i) => (i === propIdx ? { ...p, values: nextVals } : p));
468
+ commit({ ...editorState, props: nextProps });
469
+ setValueCursor((c) => c - 1);
470
+ return;
471
+ }
472
+ if (input === ']' && valueCursor < vals.length - 1) {
473
+ const nextVals = [...vals];
474
+ [nextVals[valueCursor], nextVals[valueCursor + 1]] = [nextVals[valueCursor + 1], nextVals[valueCursor]];
475
+ const nextProps = props.map((p, i) => (i === propIdx ? { ...p, values: nextVals } : p));
476
+ commit({ ...editorState, props: nextProps });
477
+ setValueCursor((c) => c + 1);
478
+ return;
479
+ }
480
+ return;
481
+ }
482
+ });
483
+ // ── Render ────────────────────────────────────────────────────────────────
484
+ const innerWidth = Math.max(1, width - 2);
485
+ if (parseErr) {
486
+ return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: "red", children: [_jsx(Text, { bold: true, color: "red", children: "FIELD EDITOR \u2014 parse error" }), _jsx(Text, { color: "red", children: parseErr }), _jsx(Text, { dimColor: true, children: "Cannot display structured editor. Fix the JSON first." })] }));
487
+ }
488
+ if (props.length === 0 && slots.length === 0) {
489
+ return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: "yellow", children: [_jsx(Text, { bold: true, children: "FIELD EDITOR \u2014 no fields" }), _jsx(Text, { dimColor: true, children: "This component has no properties or slots to edit." }), _jsx(Text, { dimColor: true, children: "Ctrl+S to save \u00B7 Esc to discard" })] }));
490
+ }
491
+ const modeLabel = focusLevel === 'field' && activeField === 'description'
492
+ ? '← → move cursor Esc exit field'
493
+ : focusLevel === 'field' && (activeField === 'type' || activeField === 'category' || activeField === 'tokenKind')
494
+ ? '← → cycle values ↑↓ next field Esc exit'
495
+ : focusLevel === 'field' && activeField === 'required'
496
+ ? 'Space/Enter toggle ↑↓ next field Esc exit'
497
+ : focusLevel === 'field' && activeField === 'values'
498
+ ? 'Enter edit list Esc exit field'
499
+ : focusLevel === 'value'
500
+ ? '+ add x remove [ up ] down Esc done'
501
+ : '↑↓ navigate Enter edit fields Ctrl+S save Esc discard';
502
+ const rows = [];
503
+ if (props.length > 0) {
504
+ rows.push({ kind: 'header', label: `── $properties (${props.length}) ` });
505
+ props.forEach((_, i) => rows.push({ kind: 'prop', idx: i }));
506
+ }
507
+ if (slots.length > 0) {
508
+ rows.push({ kind: 'header', label: `── $slots (${slots.length}) ` });
509
+ slots.forEach((_, i) => rows.push({ kind: 'slot', idx: i }));
510
+ }
511
+ // Scroll to keep selected row visible
512
+ const selectedRowIdx = rows.findIndex((r) => (r.kind === 'prop' && !inSlots && r.idx === propIdx && focusLevel !== 'section') ||
513
+ (r.kind === 'slot' && inSlots && r.idx === slotIdx));
514
+ const visibleRows = Math.max(1, height - 3); // title + hint bar + border
515
+ const scrollStart = selectedRowIdx < 0 ? 0 : Math.max(0, Math.min(selectedRowIdx, rows.length - visibleRows));
516
+ const visibleRowSlice = rows.slice(scrollStart, scrollStart + visibleRows);
517
+ return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: "cyan", children: [_jsx(Text, { bold: true, color: "cyan", children: 'FIELDS [Ctrl+S save · Esc discard]' }), _jsxs(Box, { flexDirection: "column", width: innerWidth, children: [visibleRowSlice.map((row, i) => {
518
+ if (row.kind === 'header') {
519
+ return (_jsx(Text, { dimColor: true, children: row.label }, i));
520
+ }
521
+ if (row.kind === 'prop') {
522
+ const p = props[row.idx];
523
+ const isSelected = !inSlots && row.idx === propIdx;
524
+ return (_jsx(PropRow, { prop: p, selected: isSelected, activeField: isSelected && focusLevel === 'field' ? activeField : null, textCursor: textCursor, valueCursor: valueCursor, cursorVisible: cursorVisible, width: innerWidth }, row.idx));
525
+ }
526
+ // slot row
527
+ const s = slots[row.idx];
528
+ const isSelected = inSlots && row.idx === slotIdx;
529
+ return (_jsx(SlotRow, { slot: s, selected: isSelected, activeField: isSelected && focusLevel === 'field' ? activeField : null, textCursor: textCursor, cursorVisible: cursorVisible, width: innerWidth }, `slot-${row.idx}`));
530
+ }), focusLevel === 'value' && addingValue && (_jsxs(Box, { paddingLeft: 4, gap: 1, children: [_jsx(Text, { color: "cyan", children: "+ " }), _jsx(Text, { children: newValueText }), _jsx(Text, { inverse: cursorVisible, children: " " }), _jsx(Text, { dimColor: true, children: "(Enter confirm \u00B7 Esc cancel)" })] }))] }), validationError && _jsx(Text, { color: "red", children: '✗ ' + validationError }), _jsx(Text, { dimColor: true, children: modeLabel })] }));
531
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ type FinalizeDialogProps = {
3
+ accepted: number;
4
+ rejected: number;
5
+ needsReview: number;
6
+ onConfirm: () => void;
7
+ onCancel: () => void;
8
+ };
9
+ export declare function FinalizeDialog({ accepted, rejected, needsReview, onConfirm, onCancel, }: FinalizeDialogProps): React.ReactElement;
10
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useImmediateInput } from '../hooks/useImmediateInput.js';
4
+ export function FinalizeDialog({ accepted, rejected, needsReview, 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
+ });
13
+ const allResolved = needsReview === 0;
14
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", padding: 1, width: 58, children: [_jsx(Text, { bold: true, children: '─'.repeat(17) + ' Finalize ' + '─'.repeat(17) }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsxs(Text, { color: "green", children: [accepted, " accepted"] }), _jsx(Text, { children: " \u00B7 " }), _jsxs(Text, { color: "red", children: [rejected, " rejected"] }), _jsx(Text, { children: " \u00B7 " }), _jsxs(Text, { dimColor: true, children: [needsReview, " unresolved"] })] }), _jsx(Text, { children: " " }), !allResolved && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "yellow", children: '⚠ ' + needsReview + ' component' + (needsReview === 1 ? ' is' : 's are') + ' unresolved and will be' }), _jsx(Text, { color: "yellow", children: ' excluded from the output.' }), _jsx(Text, { children: " " })] })), _jsx(Text, { children: allResolved ? 'Save decisions and exit? All components resolved.' : 'Save decisions and exit?' }), _jsx(Text, { children: " " }), _jsx(Text, { children: ' [y / Enter] Confirm [n / Esc] Cancel' })] }));
15
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ type HelpOverlayProps = {
3
+ mode: 'analyze' | 'validate' | 'review';
4
+ onClose: () => void;
5
+ };
6
+ export declare function HelpOverlay({ mode, onClose }: HelpOverlayProps): React.ReactElement;
7
+ export {};
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
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
+ });
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' })] }));
11
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ type JsonEditorProps = {
3
+ value: string;
4
+ width: number;
5
+ height: number;
6
+ onChange: (value: string) => void;
7
+ onSave: () => void;
8
+ onDiscard: () => void;
9
+ };
10
+ export declare function JsonEditor({ value, width, height, onChange, onSave, onDiscard }: JsonEditorProps): React.ReactElement;
11
+ export {};