@contentful/experience-design-system-cli 2.11.4-dev-build-d47ab65.0 → 2.12.1-dev-build-589b615.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 +259 -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 +814 -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/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 +243 -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
|
@@ -11,5 +11,5 @@ export function FinalizeDialog({ accepted, rejected, needsReview, onConfirm, onC
|
|
|
11
11
|
}
|
|
12
12
|
});
|
|
13
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 ? '
|
|
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 + ' unresolved component' + (needsReview === 1 ? '' : 's') + ' will not be pushed.' }), _jsx(Text, { color: "yellow", children: ' Only explicitly accepted components ship.' }), _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
15
|
}
|
|
@@ -55,7 +55,11 @@ export function JsonPanel({ label, value, scrollOffset, width, height, active })
|
|
|
55
55
|
const allLines = value.split('\n');
|
|
56
56
|
const visibleLines = allLines.slice(scrollOffset, scrollOffset + height);
|
|
57
57
|
const innerWidth = Math.max(1, width - 2); // subtract border
|
|
58
|
-
|
|
58
|
+
const totalLines = allLines.length;
|
|
59
|
+
const truncated = totalLines > height;
|
|
60
|
+
const visibleStart = totalLines === 0 ? 0 : scrollOffset + 1;
|
|
61
|
+
const visibleEnd = Math.min(totalLines, scrollOffset + height);
|
|
62
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height + 2, borderStyle: "single", borderColor: active ? 'white' : undefined, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, dimColor: !active, children: label }), truncated && (_jsxs(_Fragment, { children: [_jsx(Box, { flexGrow: 1 }), _jsx(Text, { dimColor: !active, children: `↕ ${visibleStart}-${visibleEnd}/${totalLines}` })] }))] }), visibleLines.map((line, i) => {
|
|
59
63
|
const truncated = truncateLine(line, innerWidth);
|
|
60
64
|
return _jsx(Box, { children: highlightJson(truncated) }, i);
|
|
61
65
|
})] }));
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type RationaleRow = {
|
|
3
|
+
name: string;
|
|
4
|
+
kind: 'prop' | 'slot';
|
|
5
|
+
rationale: string;
|
|
6
|
+
};
|
|
7
|
+
export type RationalePanelProps = {
|
|
8
|
+
componentName: string;
|
|
9
|
+
rows: RationaleRow[];
|
|
10
|
+
scrollOffset: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
active: boolean;
|
|
14
|
+
};
|
|
15
|
+
type RenderedLine = {
|
|
16
|
+
kind: 'name';
|
|
17
|
+
text: string;
|
|
18
|
+
isSlot: boolean;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'text';
|
|
21
|
+
text: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'blank';
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Flatten `rows` to a list of rendered lines so scrollOffset slicing matches
|
|
27
|
+
* what the operator sees. Layout per row:
|
|
28
|
+
* <name> ← bold
|
|
29
|
+
* <wrapped text> ← one or more lines
|
|
30
|
+
* <blank> ← spacer (omitted after the final row)
|
|
31
|
+
*/
|
|
32
|
+
export declare function renderRationaleLines(rows: RationaleRow[], innerWidth: number): RenderedLine[];
|
|
33
|
+
export declare function RationalePanel({ componentName, rows, scrollOffset, width, height, active, }: RationalePanelProps): React.ReactElement;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
/**
|
|
4
|
+
* Word-wrap a single rationale paragraph to fit within `innerWidth` columns.
|
|
5
|
+
* Returns an array of wrapped lines. Empty input → single empty line so the
|
|
6
|
+
* row still occupies vertical space.
|
|
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
|
+
// Word longer than width — hard-break.
|
|
20
|
+
if (w.length > width) {
|
|
21
|
+
let rest = w;
|
|
22
|
+
while (rest.length > width) {
|
|
23
|
+
lines.push(rest.slice(0, width));
|
|
24
|
+
rest = rest.slice(width);
|
|
25
|
+
}
|
|
26
|
+
current = rest;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
current = w;
|
|
30
|
+
}
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (current.length + 1 + w.length <= width) {
|
|
34
|
+
current += ' ' + w;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
lines.push(current);
|
|
38
|
+
if (w.length > width) {
|
|
39
|
+
let rest = w;
|
|
40
|
+
while (rest.length > width) {
|
|
41
|
+
lines.push(rest.slice(0, width));
|
|
42
|
+
rest = rest.slice(width);
|
|
43
|
+
}
|
|
44
|
+
current = rest;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
current = w;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (current.length > 0)
|
|
52
|
+
lines.push(current);
|
|
53
|
+
return lines.length > 0 ? lines : [''];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Flatten `rows` to a list of rendered lines so scrollOffset slicing matches
|
|
57
|
+
* what the operator sees. Layout per row:
|
|
58
|
+
* <name> ← bold
|
|
59
|
+
* <wrapped text> ← one or more lines
|
|
60
|
+
* <blank> ← spacer (omitted after the final row)
|
|
61
|
+
*/
|
|
62
|
+
export function renderRationaleLines(rows, innerWidth) {
|
|
63
|
+
const out = [];
|
|
64
|
+
rows.forEach((row, idx) => {
|
|
65
|
+
out.push({ kind: 'name', text: row.name, isSlot: row.kind === 'slot' });
|
|
66
|
+
for (const line of wrapText(row.rationale, innerWidth)) {
|
|
67
|
+
out.push({ kind: 'text', text: line });
|
|
68
|
+
}
|
|
69
|
+
if (idx < rows.length - 1) {
|
|
70
|
+
out.push({ kind: 'blank' });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
export function RationalePanel({ componentName, rows, scrollOffset, width, height, active, }) {
|
|
76
|
+
const innerWidth = Math.max(1, width - 2); // subtract border
|
|
77
|
+
const allLines = renderRationaleLines(rows, innerWidth);
|
|
78
|
+
const totalLines = allLines.length;
|
|
79
|
+
const visible = allLines.slice(scrollOffset, scrollOffset + height);
|
|
80
|
+
const overflowed = totalLines > height;
|
|
81
|
+
const visibleStart = totalLines === 0 ? 0 : scrollOffset + 1;
|
|
82
|
+
const visibleEnd = Math.min(totalLines, scrollOffset + height);
|
|
83
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height + 2, borderStyle: "single", borderColor: active ? 'white' : undefined, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, dimColor: !active, children: `RATIONALE — ${componentName}` }), overflowed && (_jsxs(_Fragment, { children: [_jsx(Box, { flexGrow: 1 }), _jsx(Text, { dimColor: !active, children: `↕ ${visibleStart}-${visibleEnd}/${totalLines}` })] }))] }), visible.map((line, i) => {
|
|
84
|
+
if (line.kind === 'blank') {
|
|
85
|
+
return (_jsx(Box, { children: _jsx(Text, { children: " " }) }, i));
|
|
86
|
+
}
|
|
87
|
+
if (line.kind === 'name') {
|
|
88
|
+
return (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", dimColor: !active, children: line.text }), line.isSlot && _jsx(Text, { dimColor: true, children: " (slot)" })] }, i));
|
|
89
|
+
}
|
|
90
|
+
return (_jsx(Box, { children: _jsx(Text, { dimColor: !active, children: line.text }) }, i));
|
|
91
|
+
})] }));
|
|
92
|
+
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ReviewComponentSummary, ReviewComponentStatus } from '../../types.js';
|
|
2
|
+
import type { PreviewAnnotation, ReviewComponentSummary, ReviewComponentStatus } from '../../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Pilot-2026-06-23 R2: render a single-character preview-diff badge directly
|
|
5
|
+
* before each component name in the sidebar so operators can scan the diff
|
|
6
|
+
* shape at a glance without opening each row. Returns null for unannotated
|
|
7
|
+
* rows so the row falls back to its existing single-space layout.
|
|
8
|
+
*/
|
|
9
|
+
export declare function previewBadge(annotation: PreviewAnnotation | undefined): {
|
|
10
|
+
char: string;
|
|
11
|
+
color: string;
|
|
12
|
+
bold?: boolean;
|
|
13
|
+
dim?: boolean;
|
|
14
|
+
} | null;
|
|
3
15
|
type SidebarProps = {
|
|
4
16
|
components: ReviewComponentSummary[];
|
|
5
17
|
selectedId: string | null;
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
/**
|
|
4
|
+
* Pilot-2026-06-23 R2: render a single-character preview-diff badge directly
|
|
5
|
+
* before each component name in the sidebar so operators can scan the diff
|
|
6
|
+
* shape at a glance without opening each row. Returns null for unannotated
|
|
7
|
+
* rows so the row falls back to its existing single-space layout.
|
|
8
|
+
*/
|
|
9
|
+
export function previewBadge(annotation) {
|
|
10
|
+
switch (annotation) {
|
|
11
|
+
case 'new':
|
|
12
|
+
return { char: '+', color: 'green' };
|
|
13
|
+
case 'changed':
|
|
14
|
+
return { char: '~', color: 'yellow' };
|
|
15
|
+
case 'removed':
|
|
16
|
+
return { char: '-', color: 'red', dim: true };
|
|
17
|
+
case 'breaking':
|
|
18
|
+
return { char: '!', color: 'red', bold: true };
|
|
19
|
+
default:
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
3
23
|
export function statusIcon(status, validationErrorCount,
|
|
4
24
|
// Warnings deliberately do NOT override the icon — the user's accept/reject
|
|
5
25
|
// decision must remain visible. Color is already yellow via statusColor for
|
|
@@ -70,15 +90,19 @@ export function Sidebar({ components, selectedId, focused, scrollOffset, visible
|
|
|
70
90
|
const showScrollUp = scrollOffset > 0;
|
|
71
91
|
const showScrollDown = scrollOffset + visibleCount < sorted.length;
|
|
72
92
|
const width = collapsed ? 3 : (widthProp ?? 18);
|
|
73
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: focused ? 'white' : undefined, children: [showScrollUp && !collapsed && _jsx(Text, { dimColor: true, children: "\u25B2" }), visible.map((component) => {
|
|
93
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, flexShrink: 0, borderStyle: "single", borderColor: focused ? 'white' : undefined, children: [showScrollUp && !collapsed && _jsx(Text, { dimColor: true, children: "\u25B2" }), visible.map((component) => {
|
|
74
94
|
const isSelected = component.id === selectedId;
|
|
75
95
|
const icon = statusIcon(component.status, component.validationErrorCount, component.validationWarningCount);
|
|
76
96
|
const color = statusColor(component.status, component.validationErrorCount, component.validationWarningCount);
|
|
77
|
-
const
|
|
97
|
+
const badge = previewBadge(component.previewAnnotation);
|
|
98
|
+
// Reserve one column for the preview badge regardless of whether this
|
|
99
|
+
// row has one — keeps name truncation stable across rows so the column
|
|
100
|
+
// width doesn't jitter as annotations flip in/out.
|
|
101
|
+
const maxNameLen = Math.max(1, width - 5);
|
|
78
102
|
const name = truncateName(component.name, maxNameLen);
|
|
79
103
|
if (collapsed) {
|
|
80
104
|
return (_jsx(Box, { children: _jsx(Text, { color: color, inverse: isSelected && focused, underline: isSelected && !focused, children: icon }) }, component.id));
|
|
81
105
|
}
|
|
82
|
-
return (
|
|
106
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: color, inverse: isSelected && focused, underline: isSelected && !focused, children: icon }), badge ? (_jsx(Text, { color: badge.color, bold: badge.bold, dimColor: badge.dim, children: badge.char })) : (_jsx(Text, { children: " " })), _jsx(Text, { color: color, inverse: isSelected && focused, underline: isSelected && !focused, wrap: "truncate", children: ' ' + name })] }, component.id));
|
|
83
107
|
}), showScrollDown && !collapsed && _jsx(Text, { dimColor: true, children: "\u25BC" })] }));
|
|
84
108
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helper for computing the next scroll offset when the operator presses
|
|
3
|
+
* a navigation key inside a scrollable TUI panel (JSON view, rationale view,
|
|
4
|
+
* etc.). Extracted as a shared module so multiple panels can reuse the same
|
|
5
|
+
* scroll bindings without duplicating logic.
|
|
6
|
+
*
|
|
7
|
+
* Returns the new offset, or `null` if the input doesn't match any scroll
|
|
8
|
+
* binding (caller should NOT update state in that case).
|
|
9
|
+
*
|
|
10
|
+
* Supported bindings:
|
|
11
|
+
* - j / downArrow → +1 line
|
|
12
|
+
* - k / upArrow → -1 line
|
|
13
|
+
* - Ctrl+d → +half-page (floor(panelHeight / 2))
|
|
14
|
+
* - Ctrl+u → -half-page
|
|
15
|
+
* - PageDown → +panelHeight
|
|
16
|
+
* - PageUp → -panelHeight
|
|
17
|
+
* - G → jump to maxOffset
|
|
18
|
+
*
|
|
19
|
+
* `g` (single-tap) and `gg` (double-tap → jump to 0) are handled separately
|
|
20
|
+
* by the caller via a pending-flag ref, since they need transient state.
|
|
21
|
+
*/
|
|
22
|
+
export type ScrollKeyState = {
|
|
23
|
+
upArrow?: boolean;
|
|
24
|
+
downArrow?: boolean;
|
|
25
|
+
pageUp?: boolean;
|
|
26
|
+
pageDown?: boolean;
|
|
27
|
+
ctrl?: boolean;
|
|
28
|
+
};
|
|
29
|
+
export declare function computeNextScrollOffset(current: number, input: string, key: ScrollKeyState, totalLines: number, panelHeight: number): number | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function computeNextScrollOffset(current, input, key, totalLines, panelHeight) {
|
|
2
|
+
const maxOffset = Math.max(0, totalLines - panelHeight);
|
|
3
|
+
const halfPage = Math.max(1, Math.floor(panelHeight / 2));
|
|
4
|
+
if (key.ctrl && input === 'd') {
|
|
5
|
+
return Math.min(maxOffset, current + halfPage);
|
|
6
|
+
}
|
|
7
|
+
if (key.ctrl && input === 'u') {
|
|
8
|
+
return Math.max(0, current - halfPage);
|
|
9
|
+
}
|
|
10
|
+
if (key.downArrow || input === 'j') {
|
|
11
|
+
return Math.min(maxOffset, current + 1);
|
|
12
|
+
}
|
|
13
|
+
if (key.upArrow || input === 'k') {
|
|
14
|
+
return Math.max(0, current - 1);
|
|
15
|
+
}
|
|
16
|
+
if (key.pageDown) {
|
|
17
|
+
return Math.min(maxOffset, current + panelHeight);
|
|
18
|
+
}
|
|
19
|
+
if (key.pageUp) {
|
|
20
|
+
return Math.max(0, current - panelHeight);
|
|
21
|
+
}
|
|
22
|
+
if (input === 'G') {
|
|
23
|
+
return maxOffset;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|