@contentful/experience-design-system-cli 2.12.0 → 2.12.1-dev-build-bae15c7.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/README.md +214 -357
- package/dist/package.json +1 -1
- package/dist/src/analyze/extract/astro.js +9 -0
- package/dist/src/analyze/extract/react.js +23 -0
- package/dist/src/analyze/extract/stencil.js +3 -0
- package/dist/src/analyze/extract/vue.js +6 -0
- package/dist/src/analyze/extract/web-components.js +10 -0
- package/dist/src/analyze/pre-classify.d.ts +1 -1
- package/dist/src/analyze/pre-classify.js +3 -3
- package/dist/src/analyze/select/persistence.d.ts +13 -0
- package/dist/src/analyze/select/persistence.js +38 -0
- package/dist/src/analyze/select/preview-annotations.d.ts +26 -0
- package/dist/src/analyze/select/preview-annotations.js +62 -0
- package/dist/src/analyze/select/tui/App.js +5 -2
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.d.ts +30 -0
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +137 -0
- package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +58 -1
- package/dist/src/analyze/select/tui/components/FieldEditor.js +637 -161
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +1 -1
- package/dist/src/analyze/select/tui/components/JsonPanel.js +5 -1
- package/dist/src/analyze/select/tui/components/RationalePanel.d.ts +34 -0
- package/dist/src/analyze/select/tui/components/RationalePanel.js +92 -0
- package/dist/src/analyze/select/tui/components/Sidebar.d.ts +13 -1
- package/dist/src/analyze/select/tui/components/Sidebar.js +27 -3
- package/dist/src/analyze/select/tui/hooks/scroll-offset.d.ts +29 -0
- package/dist/src/analyze/select/tui/hooks/scroll-offset.js +26 -0
- package/dist/src/analyze/select-agent/command.d.ts +2 -0
- package/dist/src/analyze/select-agent/command.js +260 -45
- package/dist/src/analyze/select-agent/show-rationale.d.ts +35 -0
- package/dist/src/analyze/select-agent/show-rationale.js +92 -0
- package/dist/src/apply/command.js +7 -3
- package/dist/src/apply/tui/ServerApplyView.d.ts +3 -1
- package/dist/src/apply/tui/ServerApplyView.js +3 -2
- package/dist/src/credentials-store.d.ts +5 -0
- package/dist/src/credentials-store.js +7 -1
- package/dist/src/generate/agent-runner.d.ts +14 -0
- package/dist/src/generate/agent-runner.js +16 -0
- package/dist/src/generate/command.d.ts +6 -0
- package/dist/src/generate/command.js +45 -10
- package/dist/src/generate/progress.d.ts +9 -0
- package/dist/src/generate/progress.js +11 -0
- package/dist/src/generate/prompt-builder.d.ts +8 -0
- package/dist/src/generate/prompt-builder.js +22 -11
- package/dist/src/import/agent-model-resolve.d.ts +23 -0
- package/dist/src/import/agent-model-resolve.js +35 -0
- package/dist/src/import/auto-filter-resolve.d.ts +12 -0
- package/dist/src/import/auto-filter-resolve.js +16 -0
- package/dist/src/import/command.js +248 -8
- package/dist/src/import/orchestrator.d.ts +2 -0
- package/dist/src/import/orchestrator.js +11 -3
- package/dist/src/import/print-prompt.d.ts +35 -0
- package/dist/src/import/print-prompt.js +31 -0
- package/dist/src/import/tui/CustomPromptBanner.d.ts +11 -0
- package/dist/src/import/tui/CustomPromptBanner.js +7 -0
- package/dist/src/import/tui/WizardApp.d.ts +112 -8
- package/dist/src/import/tui/WizardApp.js +820 -333
- package/dist/src/import/tui/auto-filter-error.d.ts +2 -0
- package/dist/src/import/tui/auto-filter-error.js +29 -0
- package/dist/src/import/tui/final-review-host.d.ts +15 -0
- package/dist/src/import/tui/final-review-host.js +20 -0
- package/dist/src/import/tui/merge-ai-decisions.d.ts +5 -0
- package/dist/src/import/tui/merge-ai-decisions.js +22 -0
- package/dist/src/import/tui/push-decision-gate-helpers.d.ts +8 -0
- package/dist/src/import/tui/push-decision-gate-helpers.js +8 -0
- package/dist/src/import/tui/push-progress.d.ts +23 -0
- package/dist/src/import/tui/push-progress.js +14 -0
- package/dist/src/import/tui/run-print-files-helpers.d.ts +26 -0
- package/dist/src/import/tui/run-print-files-helpers.js +8 -0
- package/dist/src/import/tui/run-teaser.d.ts +5 -0
- package/dist/src/import/tui/run-teaser.js +9 -0
- package/dist/src/import/tui/runLivePreview.d.ts +35 -0
- package/dist/src/import/tui/runLivePreview.js +73 -0
- package/dist/src/import/tui/runScopeGate.d.ts +22 -0
- package/dist/src/import/tui/runScopeGate.js +28 -0
- package/dist/src/import/tui/scope-gate-host.d.ts +21 -0
- package/dist/src/import/tui/scope-gate-host.js +20 -0
- package/dist/src/import/tui/spawn-generate.d.ts +33 -0
- package/dist/src/import/tui/spawn-generate.js +53 -0
- package/dist/src/import/tui/steps/CredentialsStep.d.ts +25 -1
- package/dist/src/import/tui/steps/CredentialsStep.js +31 -3
- package/dist/src/import/tui/steps/DoneStep.d.ts +6 -1
- package/dist/src/import/tui/steps/DoneStep.js +10 -4
- package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +29 -2
- package/dist/src/import/tui/steps/GenerateReviewStep.js +412 -48
- package/dist/src/import/tui/steps/PushDecisionGateStep.d.ts +19 -0
- package/dist/src/import/tui/steps/PushDecisionGateStep.js +76 -0
- package/dist/src/import/tui/steps/PushingStep.d.ts +10 -0
- package/dist/src/import/tui/steps/PushingStep.js +40 -0
- package/dist/src/import/tui/steps/ScopeGateStep.d.ts +23 -0
- package/dist/src/import/tui/steps/ScopeGateStep.js +250 -0
- package/dist/src/import/tui/steps/TokenInputStep.js +20 -3
- package/dist/src/import/tui/useLivePreview.d.ts +35 -0
- package/dist/src/import/tui/useLivePreview.js +120 -0
- package/dist/src/import/tui/wizard-generate-progress.d.ts +21 -0
- package/dist/src/import/tui/wizard-generate-progress.js +25 -0
- package/dist/src/import/tui/wizard-save-flow.d.ts +37 -0
- package/dist/src/import/tui/wizard-save-flow.js +20 -0
- package/dist/src/import/tui/wizard-state-transitions.d.ts +80 -0
- package/dist/src/import/tui/wizard-state-transitions.js +79 -0
- package/dist/src/lib/contentful-urls.d.ts +34 -0
- package/dist/src/lib/contentful-urls.js +41 -0
- package/dist/src/program.js +2 -0
- package/dist/src/runs/export-helpers.d.ts +30 -0
- package/dist/src/runs/export-helpers.js +59 -0
- package/dist/src/runs/fingerprint.d.ts +47 -0
- package/dist/src/runs/fingerprint.js +75 -0
- package/dist/src/runs/ls-command.d.ts +12 -0
- package/dist/src/runs/ls-command.js +137 -0
- package/dist/src/runs/modify-launcher.d.ts +24 -0
- package/dist/src/runs/modify-launcher.js +36 -0
- package/dist/src/runs/path-prompt.d.ts +8 -0
- package/dist/src/runs/path-prompt.js +72 -0
- package/dist/src/runs/push-creds-prompt.d.ts +25 -0
- package/dist/src/runs/push-creds-prompt.js +39 -0
- package/dist/src/runs/push-helpers.d.ts +29 -0
- package/dist/src/runs/push-helpers.js +69 -0
- package/dist/src/runs/replay-helpers.d.ts +67 -0
- package/dist/src/runs/replay-helpers.js +148 -0
- package/dist/src/runs/resolve-run-target.d.ts +12 -0
- package/dist/src/runs/resolve-run-target.js +45 -0
- package/dist/src/runs/run-picker-mount.d.ts +46 -0
- package/dist/src/runs/run-picker-mount.js +72 -0
- package/dist/src/runs/run-picker.d.ts +17 -0
- package/dist/src/runs/run-picker.js +145 -0
- package/dist/src/runs/save-conflict.d.ts +8 -0
- package/dist/src/runs/save-conflict.js +56 -0
- package/dist/src/runs/save-path-resolver.d.ts +55 -0
- package/dist/src/runs/save-path-resolver.js +71 -0
- package/dist/src/runs/staleness.d.ts +38 -0
- package/dist/src/runs/staleness.js +140 -0
- package/dist/src/runs/store.d.ts +78 -0
- package/dist/src/runs/store.js +136 -0
- package/dist/src/session/cache-keys.d.ts +15 -0
- package/dist/src/session/cache-keys.js +38 -0
- package/dist/src/session/db.d.ts +89 -3
- package/dist/src/session/db.js +447 -104
- package/dist/src/setup/auto-filter-prompt.d.ts +13 -0
- package/dist/src/setup/auto-filter-prompt.js +24 -0
- package/dist/src/setup/command.d.ts +7 -0
- package/dist/src/setup/command.js +58 -2
- package/dist/src/types.d.ts +6 -0
- package/package.json +2 -2
- package/skills/generate-components.md +54 -6
- package/skills/select-components.md +13 -4
package/dist/package.json
CHANGED
|
@@ -108,6 +108,8 @@ function extractPropsFromFrontmatter(frontmatter) {
|
|
|
108
108
|
type: typeText,
|
|
109
109
|
required,
|
|
110
110
|
...(allowedValues && { allowedValues }),
|
|
111
|
+
sourceStartLine: member.getStartLineNumber(),
|
|
112
|
+
sourceEndLine: member.getEndLineNumber(),
|
|
111
113
|
});
|
|
112
114
|
}
|
|
113
115
|
}
|
|
@@ -127,6 +129,12 @@ function extractPropsFromFrontmatter(frontmatter) {
|
|
|
127
129
|
type: typeText,
|
|
128
130
|
required,
|
|
129
131
|
...(allowedValues && { allowedValues }),
|
|
132
|
+
...(typeof decl.getStartLineNumber === 'function'
|
|
133
|
+
? {
|
|
134
|
+
sourceStartLine: decl.getStartLineNumber(),
|
|
135
|
+
sourceEndLine: decl.getEndLineNumber(),
|
|
136
|
+
}
|
|
137
|
+
: {}),
|
|
130
138
|
});
|
|
131
139
|
}
|
|
132
140
|
}
|
|
@@ -236,6 +244,7 @@ function extractFromAstroFile(filePath, source) {
|
|
|
236
244
|
return {
|
|
237
245
|
name,
|
|
238
246
|
source: filePath,
|
|
247
|
+
sourcePath: filePath,
|
|
239
248
|
framework: 'astro',
|
|
240
249
|
props: propsWithDefaults,
|
|
241
250
|
slots,
|
|
@@ -485,6 +485,12 @@ function extractPropsFromTypeSymbols(type, slotNames, suppressNeverChildrenSlot
|
|
|
485
485
|
type: typeText,
|
|
486
486
|
required,
|
|
487
487
|
...(allowedValues && { allowedValues }),
|
|
488
|
+
...(typeof declaration.getStartLineNumber === 'function'
|
|
489
|
+
? {
|
|
490
|
+
sourceStartLine: declaration.getStartLineNumber(),
|
|
491
|
+
sourceEndLine: declaration.getEndLineNumber(),
|
|
492
|
+
}
|
|
493
|
+
: {}),
|
|
488
494
|
});
|
|
489
495
|
}
|
|
490
496
|
return {
|
|
@@ -539,6 +545,12 @@ function extractPropsFromInterfaceDeclaration(declaration, slotNames, seen, excl
|
|
|
539
545
|
type: typeText,
|
|
540
546
|
required,
|
|
541
547
|
...(allowedValues && { allowedValues }),
|
|
548
|
+
...(typeof decl.getStartLineNumber === 'function'
|
|
549
|
+
? {
|
|
550
|
+
sourceStartLine: decl.getStartLineNumber(),
|
|
551
|
+
sourceEndLine: decl.getEndLineNumber(),
|
|
552
|
+
}
|
|
553
|
+
: {}),
|
|
542
554
|
});
|
|
543
555
|
return acc;
|
|
544
556
|
}, { props: [], hasChildren: false })
|
|
@@ -921,6 +933,13 @@ function extractDestructuredBindingFallbackProps(func, param, existingPropNames,
|
|
|
921
933
|
name: propName,
|
|
922
934
|
type: propertyType === 'unknown' ? 'any' : propertyType,
|
|
923
935
|
required: property ? !property.isOptional() : false,
|
|
936
|
+
...(declaration &&
|
|
937
|
+
typeof declaration.getStartLineNumber === 'function'
|
|
938
|
+
? {
|
|
939
|
+
sourceStartLine: declaration.getStartLineNumber(),
|
|
940
|
+
sourceEndLine: declaration.getEndLineNumber(),
|
|
941
|
+
}
|
|
942
|
+
: {}),
|
|
924
943
|
});
|
|
925
944
|
}
|
|
926
945
|
}
|
|
@@ -1674,6 +1693,7 @@ function extractFromSourceFile(sourceFile, isNext) {
|
|
|
1674
1693
|
components.push({
|
|
1675
1694
|
name,
|
|
1676
1695
|
source: sourceFile.getFilePath(),
|
|
1696
|
+
sourcePath: sourceFile.getFilePath(),
|
|
1677
1697
|
framework: isNext ? 'next' : 'react',
|
|
1678
1698
|
props: [],
|
|
1679
1699
|
slots: [],
|
|
@@ -1759,6 +1779,7 @@ function extractFromSourceFile(sourceFile, isNext) {
|
|
|
1759
1779
|
components.push({
|
|
1760
1780
|
name,
|
|
1761
1781
|
source: sourceFile.getFilePath(),
|
|
1782
|
+
sourcePath: sourceFile.getFilePath(),
|
|
1762
1783
|
framework: isNext ? 'next' : 'react',
|
|
1763
1784
|
props: propsAfterSlotExpansion,
|
|
1764
1785
|
slots: finalSlots,
|
|
@@ -1823,6 +1844,8 @@ function extractPropTypes(sourceFile, componentName) {
|
|
|
1823
1844
|
type,
|
|
1824
1845
|
required,
|
|
1825
1846
|
...(allowedValues && { allowedValues }),
|
|
1847
|
+
sourceStartLine: property.getStartLineNumber(),
|
|
1848
|
+
sourceEndLine: property.getEndLineNumber(),
|
|
1826
1849
|
});
|
|
1827
1850
|
}
|
|
1828
1851
|
return props.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -99,6 +99,8 @@ function extractProps(classDecl) {
|
|
|
99
99
|
...(defaultValue !== undefined && { defaultValue }),
|
|
100
100
|
...(description && { description }),
|
|
101
101
|
...(allowedValues && { allowedValues }),
|
|
102
|
+
sourceStartLine: property.getStartLineNumber(),
|
|
103
|
+
sourceEndLine: property.getEndLineNumber(),
|
|
102
104
|
});
|
|
103
105
|
}
|
|
104
106
|
return props.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -231,6 +233,7 @@ function extractFromSourceFile(sourceFile, warnings) {
|
|
|
231
233
|
components.push({
|
|
232
234
|
name,
|
|
233
235
|
source: sourceFile.getFilePath(),
|
|
236
|
+
sourcePath: sourceFile.getFilePath(),
|
|
234
237
|
framework: 'stencil',
|
|
235
238
|
props,
|
|
236
239
|
slots,
|
|
@@ -78,6 +78,7 @@ async function extractFromVueSFC(filePath, source) {
|
|
|
78
78
|
component: {
|
|
79
79
|
name,
|
|
80
80
|
source: filePath,
|
|
81
|
+
sourcePath: filePath,
|
|
81
82
|
framework: 'vue',
|
|
82
83
|
props,
|
|
83
84
|
slots,
|
|
@@ -104,6 +105,7 @@ async function extractOptionsComponent(filePath, scriptContent, fileWarnings, na
|
|
|
104
105
|
component: {
|
|
105
106
|
name,
|
|
106
107
|
source: filePath,
|
|
108
|
+
sourcePath: filePath,
|
|
107
109
|
framework: 'vue',
|
|
108
110
|
props,
|
|
109
111
|
slots,
|
|
@@ -552,6 +554,8 @@ function parseObjectProps(obj) {
|
|
|
552
554
|
name,
|
|
553
555
|
type: VUE_TYPE_MAP[initText] ?? 'any',
|
|
554
556
|
required: false,
|
|
557
|
+
sourceStartLine: prop.getStartLineNumber(),
|
|
558
|
+
sourceEndLine: prop.getEndLineNumber(),
|
|
555
559
|
});
|
|
556
560
|
continue;
|
|
557
561
|
}
|
|
@@ -585,6 +589,8 @@ function parseObjectProps(obj) {
|
|
|
585
589
|
type,
|
|
586
590
|
required,
|
|
587
591
|
...(defaultValue !== undefined && { defaultValue }),
|
|
592
|
+
sourceStartLine: prop.getStartLineNumber(),
|
|
593
|
+
sourceEndLine: prop.getEndLineNumber(),
|
|
588
594
|
});
|
|
589
595
|
}
|
|
590
596
|
return result;
|
|
@@ -364,6 +364,8 @@ function extractClassProperties(classDecl, applyRuntimeFieldDenylist = false) {
|
|
|
364
364
|
type,
|
|
365
365
|
required: false,
|
|
366
366
|
...(defaultValue !== undefined && { defaultValue }),
|
|
367
|
+
sourceStartLine: property.getStartLineNumber(),
|
|
368
|
+
sourceEndLine: property.getEndLineNumber(),
|
|
367
369
|
});
|
|
368
370
|
}
|
|
369
371
|
return props;
|
|
@@ -417,10 +419,17 @@ function extractAccessorProperties(classDecl, applyRuntimeFieldDenylist = false)
|
|
|
417
419
|
if (!hasPropertyDecorator && !(getter && setter))
|
|
418
420
|
continue;
|
|
419
421
|
const type = getter?.getReturnTypeNode()?.getText() ?? setter?.getParameters()[0]?.getTypeNode()?.getText() ?? 'any';
|
|
422
|
+
const accessorNode = getter ?? setter;
|
|
420
423
|
props.push({
|
|
421
424
|
name,
|
|
422
425
|
type,
|
|
423
426
|
required: false,
|
|
427
|
+
...(accessorNode
|
|
428
|
+
? {
|
|
429
|
+
sourceStartLine: accessorNode.getStartLineNumber(),
|
|
430
|
+
sourceEndLine: accessorNode.getEndLineNumber(),
|
|
431
|
+
}
|
|
432
|
+
: {}),
|
|
424
433
|
});
|
|
425
434
|
}
|
|
426
435
|
return props;
|
|
@@ -824,6 +833,7 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
824
833
|
components.push({
|
|
825
834
|
name,
|
|
826
835
|
source: sourceFile.getFilePath(),
|
|
836
|
+
sourcePath: sourceFile.getFilePath(),
|
|
827
837
|
framework: 'web-component',
|
|
828
838
|
props,
|
|
829
839
|
slots,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RawPropDefinition, RawComponentDefinition } from '../types.js';
|
|
2
2
|
export interface PreClassification {
|
|
3
3
|
category: 'content' | 'design' | 'state' | 'exclude';
|
|
4
|
-
cdfTypeHint?: 'string' | 'enum' | 'richtext' | 'media';
|
|
4
|
+
cdfTypeHint?: 'string' | 'enum' | 'richtext' | 'media' | 'boolean';
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Deterministic pre-classification rule engine.
|
|
@@ -142,14 +142,14 @@ export function preClassifyProp(prop) {
|
|
|
142
142
|
if (isBooleanType(type)) {
|
|
143
143
|
const stateNames = ['disabled', 'loading', 'expanded', 'isOpen', 'selected', 'checked', 'active', 'preview'];
|
|
144
144
|
if (stateNames.includes(name)) {
|
|
145
|
-
return { category: 'state', cdfTypeHint: '
|
|
145
|
+
return { category: 'state', cdfTypeHint: 'boolean' };
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
// Rule 9: Boolean + visual toggle name
|
|
149
149
|
if (isBooleanType(type)) {
|
|
150
150
|
const visualToggle = /^(hide|show|enable|disable|vertical|horizontal|reverse|bold|italic|imageOn|with)/i;
|
|
151
151
|
if (visualToggle.test(name)) {
|
|
152
|
-
return { category: 'design', cdfTypeHint: '
|
|
152
|
+
return { category: 'design', cdfTypeHint: 'boolean' };
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
// Rule 11: State identifiers
|
|
@@ -178,7 +178,7 @@ export function preClassifyProp(prop) {
|
|
|
178
178
|
}
|
|
179
179
|
// Rule 15: Remaining booleans
|
|
180
180
|
if (isBooleanType(type)) {
|
|
181
|
-
return { category: 'design', cdfTypeHint: '
|
|
181
|
+
return { category: 'design', cdfTypeHint: 'boolean' };
|
|
182
182
|
}
|
|
183
183
|
// Rule 16: Remaining numbers
|
|
184
184
|
if (isNumberType(type)) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
1
2
|
import type { ReviewSessionPaths, ReviewSessionSnapshot } from './types.js';
|
|
2
3
|
export declare function getRefineArtifactsRoot(): string;
|
|
3
4
|
export declare function getRefineSessionPaths(sessionId: string, artifactsRoot: string): Promise<ReviewSessionPaths>;
|
|
@@ -7,3 +8,15 @@ export declare function appendReviewEvent(eventsPath: string, event: {
|
|
|
7
8
|
payload: Record<string, unknown>;
|
|
8
9
|
}): Promise<void>;
|
|
9
10
|
export declare function ensureRefineSession(sessionId: string, artifactsRoot: string, initialSnapshot: ReviewSessionSnapshot): Promise<ReviewSessionSnapshot>;
|
|
11
|
+
/**
|
|
12
|
+
* Persist scope-gate decisions to `current-review-state.json` so downstream
|
|
13
|
+
* consumers (notably `loadAcceptedNames` in `generate components`) can filter
|
|
14
|
+
* out rejected components. The wizard's scope-gate doesn't drive the rich
|
|
15
|
+
* `analyze select` TUI, so fields that file consumes (resolvedSourcePath,
|
|
16
|
+
* sourceCode) are populated with safe placeholders. Only `name` and `status`
|
|
17
|
+
* matter for `loadAcceptedNames`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function writeScopeDecisionsSnapshot(db: DatabaseSync, sessionId: string, decisions: {
|
|
20
|
+
accepted: string[];
|
|
21
|
+
rejected: string[];
|
|
22
|
+
}): Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { access, appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
|
+
import { loadRawComponents } from '../../session/db.js';
|
|
4
5
|
export function getRefineArtifactsRoot() {
|
|
5
6
|
if (process.env.EDS_REVIEW_ARTIFACTS_DIR) {
|
|
6
7
|
return resolve(process.env.EDS_REVIEW_ARTIFACTS_DIR);
|
|
@@ -40,3 +41,40 @@ export async function ensureRefineSession(sessionId, artifactsRoot, initialSnaps
|
|
|
40
41
|
return initialSnapshot;
|
|
41
42
|
}
|
|
42
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Persist scope-gate decisions to `current-review-state.json` so downstream
|
|
46
|
+
* consumers (notably `loadAcceptedNames` in `generate components`) can filter
|
|
47
|
+
* out rejected components. The wizard's scope-gate doesn't drive the rich
|
|
48
|
+
* `analyze select` TUI, so fields that file consumes (resolvedSourcePath,
|
|
49
|
+
* sourceCode) are populated with safe placeholders. Only `name` and `status`
|
|
50
|
+
* matter for `loadAcceptedNames`.
|
|
51
|
+
*/
|
|
52
|
+
export async function writeScopeDecisionsSnapshot(db, sessionId, decisions) {
|
|
53
|
+
const acceptedSet = new Set(decisions.accepted);
|
|
54
|
+
const rawComponents = loadRawComponents(db, sessionId);
|
|
55
|
+
const records = rawComponents.map((c) => {
|
|
56
|
+
const status = acceptedSet.has(c.name) ? 'accepted' : 'rejected';
|
|
57
|
+
const proposal = {
|
|
58
|
+
name: c.name,
|
|
59
|
+
source: c.source,
|
|
60
|
+
framework: c.framework,
|
|
61
|
+
props: c.props,
|
|
62
|
+
slots: c.slots,
|
|
63
|
+
...(c.extractionConfidence !== undefined ? { extractionConfidence: c.extractionConfidence } : {}),
|
|
64
|
+
...(c.reviewReasons !== undefined ? { reviewReasons: c.reviewReasons } : {}),
|
|
65
|
+
...(c.needsReview !== undefined ? { needsReview: c.needsReview } : {}),
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
id: c.component_id,
|
|
69
|
+
name: c.name,
|
|
70
|
+
resolvedSourcePath: '',
|
|
71
|
+
sourceCode: null,
|
|
72
|
+
originalProposal: proposal,
|
|
73
|
+
editedProposal: proposal,
|
|
74
|
+
status,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
const paths = await getRefineSessionPaths(sessionId, getRefineArtifactsRoot());
|
|
78
|
+
await mkdir(paths.sessionDir, { recursive: true });
|
|
79
|
+
await saveReviewState(paths.statePath, { components: records });
|
|
80
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ServerPreviewResponse } from '@contentful/experience-design-system-types';
|
|
2
|
+
import type { PreviewAnnotation } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Pure mapper from a `ServerPreviewResponse` to a per-component-name
|
|
5
|
+
* `PreviewAnnotation` map. Consumed by the wizard's final-review sidebar
|
|
6
|
+
* (and any other surface that wants to render diff badges next to a
|
|
7
|
+
* component row).
|
|
8
|
+
*
|
|
9
|
+
* The `new` bucket of `ServerPreviewResponse.components` is `CDFComponentEntry[]`
|
|
10
|
+
* — those entries do NOT carry a `name` field on the wire (the name is the
|
|
11
|
+
* KEY in the parent manifest). So instead of trying to read names off entries,
|
|
12
|
+
* we derive the "new" set as:
|
|
13
|
+
*
|
|
14
|
+
* new = localNames \ (unchangedNames ∪ changedNames ∪ removedNames)
|
|
15
|
+
*
|
|
16
|
+
* Callers must pass the local manifest's component keys as `localNames` so
|
|
17
|
+
* we can perform the set difference. Removed components from the server that
|
|
18
|
+
* aren't in the local manifest are still annotated as `'removed'` (so the
|
|
19
|
+
* detail panel can list them).
|
|
20
|
+
*
|
|
21
|
+
* Precedence: `breaking` > `changed` > `removed` > `new`.
|
|
22
|
+
*
|
|
23
|
+
* Unchanged entries are omitted from the map — callers should treat
|
|
24
|
+
* `map.get(name) === undefined` as "no annotation".
|
|
25
|
+
*/
|
|
26
|
+
export declare function applyPreviewAnnotations(preview: ServerPreviewResponse, localNames: readonly string[]): Map<string, PreviewAnnotation>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure mapper from a `ServerPreviewResponse` to a per-component-name
|
|
3
|
+
* `PreviewAnnotation` map. Consumed by the wizard's final-review sidebar
|
|
4
|
+
* (and any other surface that wants to render diff badges next to a
|
|
5
|
+
* component row).
|
|
6
|
+
*
|
|
7
|
+
* The `new` bucket of `ServerPreviewResponse.components` is `CDFComponentEntry[]`
|
|
8
|
+
* — those entries do NOT carry a `name` field on the wire (the name is the
|
|
9
|
+
* KEY in the parent manifest). So instead of trying to read names off entries,
|
|
10
|
+
* we derive the "new" set as:
|
|
11
|
+
*
|
|
12
|
+
* new = localNames \ (unchangedNames ∪ changedNames ∪ removedNames)
|
|
13
|
+
*
|
|
14
|
+
* Callers must pass the local manifest's component keys as `localNames` so
|
|
15
|
+
* we can perform the set difference. Removed components from the server that
|
|
16
|
+
* aren't in the local manifest are still annotated as `'removed'` (so the
|
|
17
|
+
* detail panel can list them).
|
|
18
|
+
*
|
|
19
|
+
* Precedence: `breaking` > `changed` > `removed` > `new`.
|
|
20
|
+
*
|
|
21
|
+
* Unchanged entries are omitted from the map — callers should treat
|
|
22
|
+
* `map.get(name) === undefined` as "no annotation".
|
|
23
|
+
*/
|
|
24
|
+
export function applyPreviewAnnotations(preview, localNames) {
|
|
25
|
+
const out = new Map();
|
|
26
|
+
const unchanged = new Set(preview.components.unchanged);
|
|
27
|
+
// Annotate changed first; collect their names so we can exclude from "new".
|
|
28
|
+
const changedNames = new Set();
|
|
29
|
+
for (const item of preview.components.changed) {
|
|
30
|
+
const name = item.current?.name;
|
|
31
|
+
if (typeof name !== 'string')
|
|
32
|
+
continue;
|
|
33
|
+
changedNames.add(name);
|
|
34
|
+
if (item.changeClassification?.classification === 'breaking') {
|
|
35
|
+
out.set(name, 'breaking');
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
out.set(name, 'changed');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Annotate removed.
|
|
42
|
+
const removedNames = new Set();
|
|
43
|
+
for (const entity of preview.components.removed) {
|
|
44
|
+
if (entity.name) {
|
|
45
|
+
removedNames.add(entity.name);
|
|
46
|
+
out.set(entity.name, 'removed');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Derive `new` = localNames \ (unchanged ∪ changed ∪ removed).
|
|
50
|
+
for (const name of localNames) {
|
|
51
|
+
if (unchanged.has(name))
|
|
52
|
+
continue;
|
|
53
|
+
if (changedNames.has(name))
|
|
54
|
+
continue;
|
|
55
|
+
if (removedNames.has(name))
|
|
56
|
+
continue;
|
|
57
|
+
if (out.has(name))
|
|
58
|
+
continue;
|
|
59
|
+
out.set(name, 'new');
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
@@ -363,8 +363,11 @@ export function App({ sessionId, artifactsRoot, reviewRoot }) {
|
|
|
363
363
|
const collapsed = terminalWidth < 80;
|
|
364
364
|
const visibleCount = 20;
|
|
365
365
|
const longestName = session.components.reduce((max, c) => Math.max(max, c.name.length), 0);
|
|
366
|
-
// icon + space + name + 2 border chars; min 14, max 22
|
|
367
|
-
|
|
366
|
+
// icon + badge + space + name + 2 border chars; min 14, max 22.
|
|
367
|
+
// The badge column is reserved (pilot R2) even when no annotation is set —
|
|
368
|
+
// see Sidebar.previewBadge — so the column width is stable as live-preview
|
|
369
|
+
// annotations flip in/out.
|
|
370
|
+
const sidebarWidth = collapsed ? 3 : Math.min(Math.max(longestName + 5, 14), 22);
|
|
368
371
|
const handleDraftSave = async (draft) => {
|
|
369
372
|
if (!selectedId || !session || !paths)
|
|
370
373
|
return;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ComponentRationale } from '../../../../session/db.js';
|
|
3
|
+
export type ComponentRationalePanelProps = {
|
|
4
|
+
data: ComponentRationale;
|
|
5
|
+
scrollOffset: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
active: boolean;
|
|
9
|
+
};
|
|
10
|
+
type RenderedLine = {
|
|
11
|
+
kind: 'heading';
|
|
12
|
+
text: string;
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'text';
|
|
15
|
+
text: string;
|
|
16
|
+
dim?: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'list-name';
|
|
19
|
+
text: string;
|
|
20
|
+
sublabel?: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'blank';
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Flatten the component rationale into a list of rendered lines so scrollOffset
|
|
26
|
+
* slicing matches what the operator sees.
|
|
27
|
+
*/
|
|
28
|
+
export declare function renderComponentRationaleLines(data: ComponentRationale, innerWidth: number): RenderedLine[];
|
|
29
|
+
export declare function ComponentRationalePanel({ data, scrollOffset, width, height, active, }: ComponentRationalePanelProps): React.ReactElement;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
const PLACEHOLDER = '(no rationale captured)';
|
|
4
|
+
/**
|
|
5
|
+
* Word-wrap a paragraph to fit `innerWidth` columns. Mirrors the helper in
|
|
6
|
+
* RationalePanel.tsx so wrapping is identical across both panels.
|
|
7
|
+
*/
|
|
8
|
+
function wrapText(text, innerWidth) {
|
|
9
|
+
if (!text)
|
|
10
|
+
return [''];
|
|
11
|
+
const width = Math.max(1, innerWidth);
|
|
12
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
13
|
+
if (words.length === 0)
|
|
14
|
+
return [''];
|
|
15
|
+
const lines = [];
|
|
16
|
+
let current = '';
|
|
17
|
+
for (const w of words) {
|
|
18
|
+
if (current.length === 0) {
|
|
19
|
+
if (w.length > width) {
|
|
20
|
+
let rest = w;
|
|
21
|
+
while (rest.length > width) {
|
|
22
|
+
lines.push(rest.slice(0, width));
|
|
23
|
+
rest = rest.slice(width);
|
|
24
|
+
}
|
|
25
|
+
current = rest;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
current = w;
|
|
29
|
+
}
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (current.length + 1 + w.length <= width) {
|
|
33
|
+
current += ' ' + w;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
lines.push(current);
|
|
37
|
+
if (w.length > width) {
|
|
38
|
+
let rest = w;
|
|
39
|
+
while (rest.length > width) {
|
|
40
|
+
lines.push(rest.slice(0, width));
|
|
41
|
+
rest = rest.slice(width);
|
|
42
|
+
}
|
|
43
|
+
current = rest;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
current = w;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (current.length > 0)
|
|
51
|
+
lines.push(current);
|
|
52
|
+
return lines.length > 0 ? lines : [''];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Flatten the component rationale into a list of rendered lines so scrollOffset
|
|
56
|
+
* slicing matches what the operator sees.
|
|
57
|
+
*/
|
|
58
|
+
export function renderComponentRationaleLines(data, innerWidth) {
|
|
59
|
+
const out = [];
|
|
60
|
+
const pushSection = (heading, body) => {
|
|
61
|
+
out.push({ kind: 'heading', text: heading });
|
|
62
|
+
const text = body && body.trim().length > 0 ? body : PLACEHOLDER;
|
|
63
|
+
for (const ln of wrapText(text, Math.max(1, innerWidth - 2))) {
|
|
64
|
+
out.push({ kind: 'text', text: ' ' + ln, dim: !body });
|
|
65
|
+
}
|
|
66
|
+
out.push({ kind: 'blank' });
|
|
67
|
+
};
|
|
68
|
+
// "Description" section shows the human-facing component description.
|
|
69
|
+
// The `descriptionRationale` (WHY the component was classified this way)
|
|
70
|
+
// is appended as a small "why" line so the operator can see the LLM's
|
|
71
|
+
// reasoning without leaving the panel.
|
|
72
|
+
out.push({ kind: 'heading', text: 'Description' });
|
|
73
|
+
const descBody = data.description && data.description.trim().length > 0 ? data.description : PLACEHOLDER;
|
|
74
|
+
for (const ln of wrapText(descBody, Math.max(1, innerWidth - 2))) {
|
|
75
|
+
out.push({ kind: 'text', text: ' ' + ln, dim: !data.description });
|
|
76
|
+
}
|
|
77
|
+
if (data.descriptionRationale && data.descriptionRationale.trim().length > 0) {
|
|
78
|
+
for (const ln of wrapText(`why: ${data.descriptionRationale}`, Math.max(1, innerWidth - 2))) {
|
|
79
|
+
out.push({ kind: 'text', text: ' ' + ln, dim: true });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
out.push({ kind: 'blank' });
|
|
83
|
+
pushSection('Why these props', data.propsRationale);
|
|
84
|
+
pushSection('Why these slots', data.slotsRationale);
|
|
85
|
+
out.push({ kind: 'heading', text: 'Props' });
|
|
86
|
+
if (data.props.length === 0) {
|
|
87
|
+
out.push({ kind: 'text', text: ' ' + PLACEHOLDER, dim: true });
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
for (const p of data.props) {
|
|
91
|
+
const sub = p.category ? `(${p.category})` : undefined;
|
|
92
|
+
out.push({ kind: 'list-name', text: p.name, sublabel: sub });
|
|
93
|
+
const text = p.rationale && p.rationale.trim().length > 0 ? p.rationale : PLACEHOLDER;
|
|
94
|
+
for (const ln of wrapText(text, Math.max(1, innerWidth - 4))) {
|
|
95
|
+
out.push({ kind: 'text', text: ' ' + ln, dim: !p.rationale });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
out.push({ kind: 'blank' });
|
|
100
|
+
out.push({ kind: 'heading', text: 'Slots' });
|
|
101
|
+
if (data.slots.length === 0) {
|
|
102
|
+
out.push({ kind: 'text', text: ' ' + PLACEHOLDER, dim: true });
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
for (const s of data.slots) {
|
|
106
|
+
out.push({ kind: 'list-name', text: s.name });
|
|
107
|
+
const text = s.rationale && s.rationale.trim().length > 0 ? s.rationale : PLACEHOLDER;
|
|
108
|
+
for (const ln of wrapText(text, Math.max(1, innerWidth - 4))) {
|
|
109
|
+
out.push({ kind: 'text', text: ' ' + ln, dim: !s.rationale });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
export function ComponentRationalePanel({ data, scrollOffset, width, height, active, }) {
|
|
116
|
+
const innerWidth = Math.max(1, width - 2);
|
|
117
|
+
const all = renderComponentRationaleLines(data, innerWidth);
|
|
118
|
+
const totalLines = all.length;
|
|
119
|
+
// Reserve one line at the bottom for the legend.
|
|
120
|
+
const contentHeight = Math.max(1, height - 1);
|
|
121
|
+
const visible = all.slice(scrollOffset, scrollOffset + contentHeight);
|
|
122
|
+
const overflowed = totalLines > contentHeight;
|
|
123
|
+
const visibleStart = totalLines === 0 ? 0 : scrollOffset + 1;
|
|
124
|
+
const visibleEnd = Math.min(totalLines, scrollOffset + contentHeight);
|
|
125
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height + 2, borderStyle: "single", borderColor: active ? 'white' : undefined, children: [_jsx(Box, { children: _jsx(Text, { bold: true, dimColor: !active, children: `Component rationale: ${data.name}` }) }), visible.map((line, i) => {
|
|
126
|
+
if (line.kind === 'blank') {
|
|
127
|
+
return (_jsx(Box, { children: _jsx(Text, { children: " " }) }, i));
|
|
128
|
+
}
|
|
129
|
+
if (line.kind === 'heading') {
|
|
130
|
+
return (_jsx(Box, { children: _jsx(Text, { bold: true, color: "cyan", dimColor: !active, children: line.text }) }, i));
|
|
131
|
+
}
|
|
132
|
+
if (line.kind === 'list-name') {
|
|
133
|
+
return (_jsxs(Box, { children: [_jsx(Text, { children: ' - ' }), _jsx(Text, { bold: true, dimColor: !active, children: line.text }), line.sublabel ? _jsx(Text, { dimColor: true, children: ' ' + line.sublabel }) : null] }, i));
|
|
134
|
+
}
|
|
135
|
+
return (_jsx(Box, { children: _jsx(Text, { dimColor: !active || line.dim, children: line.text }) }, i));
|
|
136
|
+
}), _jsx(Box, { children: overflowed ? (_jsx(Text, { dimColor: true, children: `${visibleStart}-${visibleEnd}/${totalLines} [j/k] scroll [I/Esc] close` })) : (_jsx(Text, { dimColor: true, children: '[I/Esc] close' })) })] }));
|
|
137
|
+
}
|
|
@@ -1,11 +1,68 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
/** Per-prop / per-component metadata captured by the extractor + generate phase.
|
|
3
|
+
* Optional; when omitted, the FieldEditor renders without rationale/source
|
|
4
|
+
* affordances (backwards-compatible with mounts that pre-date Feature 1). */
|
|
5
|
+
export type PropMetadata = {
|
|
6
|
+
rationale?: string | null;
|
|
7
|
+
sourceStartLine?: number | null;
|
|
8
|
+
sourceEndLine?: number | null;
|
|
9
|
+
};
|
|
10
|
+
export type FieldEditorMetadata = {
|
|
11
|
+
/** Absolute path to the component's source file. Null/undefined = unknown. */
|
|
12
|
+
sourcePath?: string | null;
|
|
13
|
+
/** Full source text of the component (used by the source-view panel). */
|
|
14
|
+
componentSource?: string | null;
|
|
15
|
+
/** Per-prop metadata keyed by prop name. */
|
|
16
|
+
props?: Record<string, PropMetadata>;
|
|
17
|
+
};
|
|
2
18
|
type FieldEditorProps = {
|
|
3
19
|
value: string;
|
|
4
20
|
width: number;
|
|
5
21
|
height: number;
|
|
22
|
+
/**
|
|
23
|
+
* When false, the editor is mounted but does not consume keystrokes.
|
|
24
|
+
* Used by callers (e.g. GenerateReviewStep) that toggle focus between
|
|
25
|
+
* a sidebar and this editor — the editor stays visible while the sidebar
|
|
26
|
+
* has the keyboard.
|
|
27
|
+
*/
|
|
28
|
+
active?: boolean;
|
|
6
29
|
onChange: (value: string) => void;
|
|
7
30
|
onSave: () => void;
|
|
8
31
|
onDiscard: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Called when the user requests to exit the panel from row-level (Esc).
|
|
34
|
+
* Distinct from onDiscard, which drops pending edits without changing focus.
|
|
35
|
+
* Callers that embed FieldEditor inside a sidebar+panel layout wire this to
|
|
36
|
+
* return focus to the sidebar (e.g. setSidebarFocused(true)). When omitted,
|
|
37
|
+
* row-level Esc falls back to onDiscard for backward compatibility.
|
|
38
|
+
*/
|
|
39
|
+
onExit?: () => void;
|
|
40
|
+
/** Feature 1: source code + LLM rationale metadata. Optional. */
|
|
41
|
+
metadata?: FieldEditorMetadata;
|
|
42
|
+
/**
|
|
43
|
+
* When provided, the parent owns the prop-rationale panel: pressing `i`
|
|
44
|
+
* inside FieldEditor calls this callback (subject to text-entry gating)
|
|
45
|
+
* instead of toggling internal state. The panel is rendered by the parent
|
|
46
|
+
* in place of FieldEditor — FieldEditor no longer renders its own panel.
|
|
47
|
+
*/
|
|
48
|
+
onTogglePropRationale?: () => void;
|
|
49
|
+
/**
|
|
50
|
+
* When provided, the parent owns the component-rationale panel: pressing
|
|
51
|
+
* `I` (uppercase) calls this callback (subject to text-entry gating).
|
|
52
|
+
*/
|
|
53
|
+
onToggleComponentRationale?: () => void;
|
|
54
|
+
/**
|
|
55
|
+
* When provided, the parent owns the source-view panel: pressing `s`
|
|
56
|
+
* calls this callback (subject to text-entry gating). FieldEditor will not
|
|
57
|
+
* render its own source panel in that case.
|
|
58
|
+
*/
|
|
59
|
+
onToggleSourceExternal?: () => void;
|
|
60
|
+
/**
|
|
61
|
+
* Reports text-entry-active state changes to the parent so the parent can
|
|
62
|
+
* gate top-level keybinds (i/I/s) against literal keystrokes inside
|
|
63
|
+
* description editors, string default editors, and value-list text entry.
|
|
64
|
+
*/
|
|
65
|
+
onTextEntryActiveChange?: (active: boolean) => void;
|
|
9
66
|
};
|
|
10
|
-
export declare function FieldEditor({ value, width, height, onChange, onSave, onDiscard, }: FieldEditorProps): React.ReactElement;
|
|
67
|
+
export declare function FieldEditor({ value, width, height, active, onChange, onSave, onDiscard, onExit, metadata, onTogglePropRationale, onToggleComponentRationale, onToggleSourceExternal, onTextEntryActiveChange, }: FieldEditorProps): React.ReactElement;
|
|
11
68
|
export {};
|