@contentful/experience-design-system-cli 2.14.2 → 2.15.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 +64 -2
- package/dist/package.json +3 -2
- package/dist/src/analyze/command.d.ts +11 -0
- package/dist/src/analyze/command.js +298 -3
- package/dist/src/analyze/composite-closure.d.ts +25 -0
- package/dist/src/analyze/composite-closure.js +0 -0
- package/dist/src/analyze/composition/agent-parser/author-prompt.d.ts +4 -0
- package/dist/src/analyze/composition/agent-parser/author-prompt.js +22 -0
- package/dist/src/analyze/composition/agent-parser/extract-parser.d.ts +8 -0
- package/dist/src/analyze/composition/agent-parser/extract-parser.js +44 -0
- package/dist/src/analyze/composition/agent-parser/load-prompt.d.ts +7 -0
- package/dist/src/analyze/composition/agent-parser/load-prompt.js +31 -0
- package/dist/src/analyze/composition/agent-parser/resolve-via-parser.d.ts +46 -0
- package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +88 -0
- package/dist/src/analyze/composition/agent-parser/sandbox.d.ts +45 -0
- package/dist/src/analyze/composition/agent-parser/sandbox.js +187 -0
- package/dist/src/analyze/composition/apply-mapping.d.ts +22 -0
- package/dist/src/analyze/composition/apply-mapping.js +67 -0
- package/dist/src/analyze/composition/candidate-critic-agent.d.ts +7 -0
- package/dist/src/analyze/composition/candidate-critic-agent.js +39 -0
- package/dist/src/analyze/composition/candidate-critic.d.ts +32 -0
- package/dist/src/analyze/composition/candidate-critic.js +63 -0
- package/dist/src/analyze/composition/candidate-files.d.ts +34 -0
- package/dist/src/analyze/composition/candidate-files.js +131 -0
- package/dist/src/analyze/composition/composition-cache-key.d.ts +19 -0
- package/dist/src/analyze/composition/composition-cache-key.js +19 -0
- package/dist/src/analyze/composition/interchange-schema.d.ts +37 -0
- package/dist/src/analyze/composition/interchange-schema.js +61 -0
- package/dist/src/analyze/composition/merge-edges.d.ts +19 -0
- package/dist/src/analyze/composition/merge-edges.js +50 -0
- package/dist/src/analyze/composition/parse-map-edges.d.ts +16 -0
- package/dist/src/analyze/composition/parse-map-edges.js +55 -0
- package/dist/src/analyze/composition/resolve-mapping-cli.d.ts +23 -0
- package/dist/src/analyze/composition/resolve-mapping-cli.js +29 -0
- package/dist/src/analyze/composition/resolve-mapping.d.ts +59 -0
- package/dist/src/analyze/composition/resolve-mapping.js +94 -0
- package/dist/src/analyze/cycle-detection.d.ts +27 -0
- package/dist/src/analyze/cycle-detection.js +234 -0
- package/dist/src/analyze/cycle-view.d.ts +7 -0
- package/dist/src/analyze/cycle-view.js +28 -0
- package/dist/src/analyze/fuzzy-search.d.ts +3 -0
- package/dist/src/analyze/fuzzy-search.js +55 -0
- package/dist/src/analyze/issue-inheritance.d.ts +12 -0
- package/dist/src/analyze/issue-inheritance.js +108 -0
- package/dist/src/analyze/lineage.d.ts +29 -0
- package/dist/src/analyze/lineage.js +0 -0
- package/dist/src/analyze/scope-gate-cascade.d.ts +11 -0
- package/dist/src/analyze/scope-gate-cascade.js +170 -0
- package/dist/src/analyze/search-neighborhood.d.ts +3 -0
- package/dist/src/analyze/search-neighborhood.js +56 -0
- package/dist/src/analyze/select/tui/App.js +5 -16
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.d.ts +0 -4
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +3 -15
- package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +29 -38
- package/dist/src/analyze/select/tui/components/FieldEditor.js +309 -185
- package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +13 -1
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +23 -2
- package/dist/src/analyze/select/tui/components/GotoBanner.d.ts +31 -0
- package/dist/src/analyze/select/tui/components/GotoBanner.js +30 -0
- package/dist/src/analyze/select/tui/components/GroupedSidebar.d.ts +91 -0
- package/dist/src/analyze/select/tui/components/GroupedSidebar.js +497 -0
- package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +13 -1
- package/dist/src/analyze/select/tui/components/HelpOverlay.js +9 -3
- package/dist/src/analyze/select/tui/components/JsonEditor.js +3 -3
- package/dist/src/analyze/select/tui/components/LineagePanel.d.ts +14 -0
- package/dist/src/analyze/select/tui/components/LineagePanel.js +22 -0
- package/dist/src/analyze/select/tui/components/RationalePanel.d.ts +0 -7
- package/dist/src/analyze/select/tui/components/RationalePanel.js +3 -15
- package/dist/src/analyze/select/tui/components/Sidebar.d.ts +0 -6
- package/dist/src/analyze/select/tui/components/Sidebar.js +11 -27
- package/dist/src/analyze/select/tui/components/StatusBar.js +2 -1
- package/dist/src/analyze/select/tui/components/removed-components-text.d.ts +7 -0
- package/dist/src/analyze/select/tui/components/removed-components-text.js +11 -0
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +1 -5
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +8 -10
- package/dist/src/analyze/select/tui/theme.d.ts +15 -0
- package/dist/src/analyze/select/tui/theme.js +28 -0
- package/dist/src/analyze/selection-cascade.d.ts +3 -0
- package/dist/src/analyze/selection-cascade.js +19 -0
- package/dist/src/analyze/slot-graph.d.ts +11 -0
- package/dist/src/analyze/slot-graph.js +17 -0
- package/dist/src/apply/api-client.d.ts +2 -1
- package/dist/src/apply/api-client.js +40 -6
- package/dist/src/apply/command.d.ts +17 -1
- package/dist/src/apply/command.js +69 -11
- package/dist/src/apply/error-parser.d.ts +16 -0
- package/dist/src/apply/error-parser.js +117 -0
- package/dist/src/apply/tui/ServerPreviewView.js +3 -1
- package/dist/src/credentials-store.d.ts +6 -0
- package/dist/src/credentials-store.js +5 -1
- package/dist/src/generate/agent-runner.d.ts +6 -0
- package/dist/src/generate/agent-runner.js +24 -8
- package/dist/src/import/command.js +69 -31
- package/dist/src/import/cycle-auto-reject.d.ts +4 -0
- package/dist/src/import/cycle-auto-reject.js +14 -0
- package/dist/src/import/orchestrator.d.ts +20 -7
- package/dist/src/import/orchestrator.js +139 -35
- package/dist/src/import/strip-allowed-components.d.ts +18 -0
- package/dist/src/import/strip-allowed-components.js +22 -0
- package/dist/src/import/tui/CustomPromptBanner.d.ts +0 -5
- package/dist/src/import/tui/CustomPromptBanner.js +2 -1
- package/dist/src/import/tui/WizardApp.d.ts +10 -74
- package/dist/src/import/tui/WizardApp.js +234 -246
- package/dist/src/import/tui/ai-flag.d.ts +5 -0
- package/dist/src/import/tui/ai-flag.js +6 -0
- package/dist/src/import/tui/autocomplete.d.ts +5 -0
- package/dist/src/import/tui/autocomplete.js +32 -0
- package/dist/src/import/tui/components/AutoFilterBanner.d.ts +10 -0
- package/dist/src/import/tui/components/AutoFilterBanner.js +19 -0
- package/dist/src/import/tui/components/CounterStrip.d.ts +12 -0
- package/dist/src/import/tui/components/CounterStrip.js +12 -0
- package/dist/src/import/tui/components/LegendEntry.d.ts +2 -0
- package/dist/src/import/tui/components/LegendEntry.js +6 -0
- package/dist/src/import/tui/cycle-panel-scroll.d.ts +8 -0
- package/dist/src/import/tui/cycle-panel-scroll.js +33 -0
- package/dist/src/import/tui/final-review-host.d.ts +3 -7
- package/dist/src/import/tui/final-review-host.js +9 -4
- package/dist/src/import/tui/group-collapse.d.ts +2 -0
- package/dist/src/import/tui/group-collapse.js +11 -0
- package/dist/src/import/tui/history.d.ts +22 -0
- package/dist/src/import/tui/history.js +53 -0
- package/dist/src/import/tui/hooks/useLineage.d.ts +32 -0
- package/dist/src/import/tui/hooks/useLineage.js +50 -0
- package/dist/src/import/tui/hooks/useOverlayPanel.d.ts +13 -0
- package/dist/src/import/tui/hooks/useOverlayPanel.js +27 -0
- package/dist/src/import/tui/lineage-layout.d.ts +75 -0
- package/dist/src/import/tui/lineage-layout.js +82 -0
- package/dist/src/import/tui/runLivePreview.d.ts +11 -0
- package/dist/src/import/tui/runLivePreview.js +7 -1
- package/dist/src/import/tui/scope-gate-columns.d.ts +31 -0
- package/dist/src/import/tui/scope-gate-columns.js +94 -0
- package/dist/src/import/tui/scope-gate-host.d.ts +3 -1
- package/dist/src/import/tui/scope-gate-host.js +11 -3
- package/dist/src/import/tui/sidebar-width.d.ts +1 -0
- package/dist/src/import/tui/sidebar-width.js +3 -0
- package/dist/src/import/tui/step-filters.d.ts +19 -0
- package/dist/src/import/tui/step-filters.js +42 -0
- package/dist/src/import/tui/steps/AtomicGenerateReviewStep.d.ts +42 -0
- package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +726 -0
- package/dist/src/import/tui/steps/AtomicScopeGateStep.d.ts +3 -0
- package/dist/src/import/tui/steps/AtomicScopeGateStep.js +211 -0
- package/dist/src/import/tui/steps/CredentialsStep.js +5 -18
- package/dist/src/import/tui/steps/DoneStep.d.ts +0 -2
- package/dist/src/import/tui/steps/DoneStep.js +3 -2
- package/dist/src/import/tui/steps/ErrorStep.js +2 -1
- package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +19 -25
- package/dist/src/import/tui/steps/GenerateReviewStep.js +1258 -229
- package/dist/src/import/tui/steps/PathValidationStep.js +3 -2
- package/dist/src/import/tui/steps/PreviewStep.js +2 -1
- package/dist/src/import/tui/steps/PushDecisionGateStep.d.ts +0 -7
- package/dist/src/import/tui/steps/PushDecisionGateStep.js +3 -6
- package/dist/src/import/tui/steps/PushingStep.js +2 -1
- package/dist/src/import/tui/steps/RunningStep.d.ts +4 -1
- package/dist/src/import/tui/steps/RunningStep.js +3 -2
- package/dist/src/import/tui/steps/ScopeGateStep.d.ts +24 -0
- package/dist/src/import/tui/steps/ScopeGateStep.js +850 -193
- package/dist/src/import/tui/steps/TokenInputStep.js +2 -1
- package/dist/src/import/tui/steps/WelcomeStep.js +2 -1
- package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +6 -0
- package/dist/src/import/tui/steps/WizardPreviewStep.js +95 -56
- package/dist/src/import/tui/steps/auto-reject-decision.d.ts +7 -0
- package/dist/src/import/tui/steps/auto-reject-decision.js +9 -0
- package/dist/src/import/tui/steps/breaking-change-format.d.ts +2 -0
- package/dist/src/import/tui/steps/breaking-change-format.js +24 -0
- package/dist/src/import/tui/steps/enumerate-cycle-breaks.d.ts +14 -0
- package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
- package/dist/src/import/tui/steps/preview-diff.js +48 -5
- package/dist/src/import/tui/useFinalizePreview.d.ts +34 -0
- package/dist/src/import/tui/useFinalizePreview.js +0 -0
- package/dist/src/import/tui/useLivePreview.d.ts +3 -0
- package/dist/src/import/tui/useLivePreview.js +1 -0
- package/dist/src/import/tui/wizard-state-transitions.d.ts +5 -59
- package/dist/src/import/tui/wizard-state-transitions.js +5 -60
- package/dist/src/lib/composition-mode.d.ts +28 -0
- package/dist/src/lib/composition-mode.js +37 -0
- package/dist/src/lib/prompt-overrides.d.ts +38 -0
- package/dist/src/lib/prompt-overrides.js +69 -0
- package/dist/src/print/command.js +19 -1
- package/dist/src/runs/modify-launcher.d.ts +3 -0
- package/dist/src/runs/modify-launcher.js +2 -0
- package/dist/src/runs/replay-helpers.js +1 -0
- package/dist/src/runs/run-picker.js +2 -18
- package/dist/src/runs/store.d.ts +4 -0
- package/dist/src/session/db.d.ts +23 -40
- package/dist/src/session/db.js +110 -138
- package/dist/src/setup/composition-mode-prompt.d.ts +14 -0
- package/dist/src/setup/composition-mode-prompt.js +37 -0
- package/package.json +5 -4
- package/prompts/composition-dir-critic.md +7 -0
- package/prompts/composition-edges.md +8 -0
- package/prompts/composition-parser-repair-empty.md +1 -0
- package/prompts/composition-parser-repair-error.md +2 -0
- package/prompts/composition-parser.md +42 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
function longestCommonPrefix(names) {
|
|
2
|
+
if (names.length === 0)
|
|
3
|
+
return '';
|
|
4
|
+
let prefixLen = names[0].length;
|
|
5
|
+
for (let i = 1; i < names.length; i++) {
|
|
6
|
+
const other = names[i];
|
|
7
|
+
let j = 0;
|
|
8
|
+
while (j < prefixLen && j < other.length && names[0][j].toLowerCase() === other[j].toLowerCase()) {
|
|
9
|
+
j++;
|
|
10
|
+
}
|
|
11
|
+
prefixLen = j;
|
|
12
|
+
if (prefixLen === 0)
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
return names[0].slice(0, prefixLen);
|
|
16
|
+
}
|
|
17
|
+
export function computeAutocomplete(query, names) {
|
|
18
|
+
const q = query.toLowerCase();
|
|
19
|
+
const candidates = names.filter((n) => n.toLowerCase().startsWith(q));
|
|
20
|
+
if (candidates.length === 0) {
|
|
21
|
+
return { completion: query, candidates: [] };
|
|
22
|
+
}
|
|
23
|
+
if (candidates.length === 1) {
|
|
24
|
+
return { completion: candidates[0], candidates: [] };
|
|
25
|
+
}
|
|
26
|
+
const lcp = longestCommonPrefix(candidates);
|
|
27
|
+
// Guard: never return a completion shorter than the current query (e.g. if
|
|
28
|
+
// casing differences shrank the LCP below the typed length, keep the query).
|
|
29
|
+
const completion = lcp.length >= query.length ? lcp : query;
|
|
30
|
+
const sorted = [...candidates].sort();
|
|
31
|
+
return { completion, candidates: sorted };
|
|
32
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type AutoFilterStatus = 'idle' | 'running' | 'complete' | 'cancelled' | 'failed';
|
|
3
|
+
export declare function AutoFilterBanner(props: {
|
|
4
|
+
status?: AutoFilterStatus;
|
|
5
|
+
progress?: {
|
|
6
|
+
done: number;
|
|
7
|
+
total: number;
|
|
8
|
+
} | null;
|
|
9
|
+
error?: string | null;
|
|
10
|
+
}): React.ReactElement | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { PALETTE } from '../../../analyze/select/tui/theme.js';
|
|
4
|
+
export function AutoFilterBanner(props) {
|
|
5
|
+
const { status, progress, error } = props;
|
|
6
|
+
const showRunningHeader = status === 'running' && progress !== null && progress !== undefined && progress.total > 0;
|
|
7
|
+
const showCancelledBanner = status === 'cancelled';
|
|
8
|
+
const showFailedBanner = status === 'failed';
|
|
9
|
+
if (showRunningHeader) {
|
|
10
|
+
return (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsxs(Text, { color: PALETTE.info, children: ["[AI filtering (", progress.done, "/", progress.total, ")\u2026] ", _jsx(Text, { dimColor: true, children: "[q] cancels" })] }) }));
|
|
11
|
+
}
|
|
12
|
+
if (showCancelledBanner) {
|
|
13
|
+
return (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: PALETTE.warning, children: ["AI auto-filter cancelled", progress ? ` at ${progress.done}/${progress.total}` : '', ". Review remaining manually."] }) }));
|
|
14
|
+
}
|
|
15
|
+
if (showFailedBanner) {
|
|
16
|
+
return (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: PALETTE.warning, children: ["AI auto-filter failed: ", error ?? 'unknown error', ". Continuing without AI suggestions."] }) }));
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type CounterStripCounters = {
|
|
3
|
+
accepted: number;
|
|
4
|
+
rejected: number;
|
|
5
|
+
undecided: number;
|
|
6
|
+
groups: number;
|
|
7
|
+
total: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function CounterStrip(props: {
|
|
10
|
+
counters: CounterStripCounters;
|
|
11
|
+
totalWidth: number;
|
|
12
|
+
}): React.ReactElement;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
export function CounterStrip(props) {
|
|
4
|
+
const { counters, totalWidth } = props;
|
|
5
|
+
const condensed = totalWidth < 60;
|
|
6
|
+
const labelAcc = condensed ? 'Acc' : 'Accepted';
|
|
7
|
+
const labelGrp = condensed ? 'Grp' : 'Groups';
|
|
8
|
+
const labelRej = condensed ? 'Rej' : 'Rejected';
|
|
9
|
+
const labelUnd = condensed ? 'Und' : 'Undecided';
|
|
10
|
+
const sep = condensed ? ' | ' : ' ';
|
|
11
|
+
return (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsxs(Text, { dimColor: true, children: [labelAcc, " "] }), _jsx(Text, { bold: true, children: counters.accepted }), _jsx(Text, { dimColor: true, children: `/${counters.total}` }), _jsx(Text, { dimColor: true, children: sep }), _jsxs(Text, { dimColor: true, children: [labelGrp, " "] }), _jsx(Text, { bold: true, children: counters.groups }), _jsx(Text, { dimColor: true, children: sep }), _jsxs(Text, { dimColor: true, children: [labelRej, " "] }), _jsx(Text, { bold: true, children: counters.rejected }), _jsx(Text, { dimColor: true, children: sep }), _jsxs(Text, { dimColor: true, children: [labelUnd, " "] }), _jsx(Text, { bold: true, children: counters.undecided })] }) }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
import { PALETTE } from '../../../analyze/select/tui/theme.js';
|
|
4
|
+
export function legendEntry(keyBracket, label, active = false) {
|
|
5
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: active ? PALETTE.warning : PALETTE.info, inverse: active, children: keyBracket }), _jsx(Text, { color: active ? PALETTE.warning : undefined, inverse: active, dimColor: !active, children: ' ' + label })] }, keyBracket + label));
|
|
6
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const CYCLE_PANEL_PREAMBLE_LINES = 4;
|
|
2
|
+
export interface CyclePanelLineCountable {
|
|
3
|
+
suggestedBreak?: unknown;
|
|
4
|
+
}
|
|
5
|
+
export declare function cycleLineCount(cycle: CyclePanelLineCountable): number;
|
|
6
|
+
export declare function cycleLineOffsets(cycles: readonly CyclePanelLineCountable[], preamble?: number): number[];
|
|
7
|
+
export declare function totalCyclePanelLines(cycles: readonly CyclePanelLineCountable[], preamble?: number): number;
|
|
8
|
+
export declare function followCycleScroll(currentScroll: number, cursorIdx: number, cycles: readonly CyclePanelLineCountable[], panelHeight: number, preamble?: number): number;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const CYCLE_PANEL_PREAMBLE_LINES = 4;
|
|
2
|
+
export function cycleLineCount(cycle) {
|
|
3
|
+
return 3 + (cycle.suggestedBreak ? 1 : 0);
|
|
4
|
+
}
|
|
5
|
+
export function cycleLineOffsets(cycles, preamble = CYCLE_PANEL_PREAMBLE_LINES) {
|
|
6
|
+
const offsets = [];
|
|
7
|
+
let acc = preamble;
|
|
8
|
+
for (const cycle of cycles) {
|
|
9
|
+
offsets.push(acc);
|
|
10
|
+
acc += cycleLineCount(cycle);
|
|
11
|
+
}
|
|
12
|
+
return offsets;
|
|
13
|
+
}
|
|
14
|
+
export function totalCyclePanelLines(cycles, preamble = CYCLE_PANEL_PREAMBLE_LINES) {
|
|
15
|
+
let acc = preamble;
|
|
16
|
+
for (const cycle of cycles)
|
|
17
|
+
acc += cycleLineCount(cycle);
|
|
18
|
+
return acc;
|
|
19
|
+
}
|
|
20
|
+
export function followCycleScroll(currentScroll, cursorIdx, cycles, panelHeight, preamble = CYCLE_PANEL_PREAMBLE_LINES) {
|
|
21
|
+
if (cursorIdx < 0 || cursorIdx >= cycles.length)
|
|
22
|
+
return currentScroll;
|
|
23
|
+
const offsets = cycleLineOffsets(cycles, preamble);
|
|
24
|
+
const blockStart = offsets[cursorIdx];
|
|
25
|
+
const lastContentLine = blockStart + cycleLineCount(cycles[cursorIdx]) - 2;
|
|
26
|
+
let next = currentScroll;
|
|
27
|
+
if (blockStart < currentScroll)
|
|
28
|
+
next = blockStart;
|
|
29
|
+
else if (lastContentLine >= currentScroll + panelHeight)
|
|
30
|
+
next = lastContentLine - panelHeight + 1;
|
|
31
|
+
const maxOffset = Math.max(0, totalCyclePanelLines(cycles, preamble) - panelHeight);
|
|
32
|
+
return Math.max(0, Math.min(next, maxOffset));
|
|
33
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import type { CompositionMode } from '../../lib/composition-mode.js';
|
|
2
3
|
export type FinalReviewHostProps = {
|
|
3
4
|
extractSessionId: string | null;
|
|
4
5
|
generatedCount: number;
|
|
5
6
|
autoAccept: boolean;
|
|
7
|
+
compositionMode?: CompositionMode;
|
|
6
8
|
onFinalize: (accepted: number, rejected: number, unresolved: number) => void;
|
|
7
9
|
onQuit: () => void;
|
|
8
10
|
livePreview?: boolean;
|
|
@@ -11,12 +13,6 @@ export type FinalReviewHostProps = {
|
|
|
11
13
|
cmaToken?: string;
|
|
12
14
|
host?: string;
|
|
13
15
|
tokensPath?: string;
|
|
14
|
-
/**
|
|
15
|
-
* INTEG-4411 refined: message displayed as an inline banner when the wizard
|
|
16
|
-
* routes back here after the preview API returned an empty diff (pure
|
|
17
|
-
* no-op push). Cleared on the next `a` / `A` keystroke inside
|
|
18
|
-
* GenerateReviewStep.
|
|
19
|
-
*/
|
|
20
16
|
initialFinalizeError?: string | null;
|
|
21
17
|
};
|
|
22
|
-
export declare function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, }: FinalReviewHostProps): React.ReactElement;
|
|
18
|
+
export declare function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, compositionMode, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, }: FinalReviewHostProps): React.ReactElement;
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { PALETTE } from '../../analyze/select/tui/theme.js';
|
|
3
4
|
import React from 'react';
|
|
4
5
|
import { GenerateReviewStep } from './steps/GenerateReviewStep.js';
|
|
5
|
-
|
|
6
|
+
import { AtomicGenerateReviewStep } from './steps/AtomicGenerateReviewStep.js';
|
|
7
|
+
export function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, compositionMode = 'atomic', onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, }) {
|
|
6
8
|
if (!extractSessionId) {
|
|
7
|
-
return (_jsx(Box, { paddingX: 2, paddingY: 1, children: _jsx(Text, { color:
|
|
9
|
+
return (_jsx(Box, { paddingX: 2, paddingY: 1, children: _jsx(Text, { color: PALETTE.error, children: "Error: no session ID \u2014 cannot load generated definitions." }) }));
|
|
8
10
|
}
|
|
9
11
|
if (autoAccept) {
|
|
10
12
|
return _jsx(FinalReviewAutoAccept, { generatedCount: generatedCount, onFinalize: onFinalize });
|
|
11
13
|
}
|
|
12
|
-
|
|
14
|
+
// Atomic mode (spec T9): render the pre-composite flat review step. It never
|
|
15
|
+
// passes projectSlotGraph to FieldEditor and never walks closures/cycles, so
|
|
16
|
+
// slot-composition editing and every hierarchy affordance stay absent.
|
|
17
|
+
const StepComponent = compositionMode === 'atomic' ? AtomicGenerateReviewStep : GenerateReviewStep;
|
|
18
|
+
return (_jsx(StepComponent, { extractSessionId: extractSessionId, onFinalize: onFinalize, onQuit: onQuit, livePreview: livePreview, spaceId: spaceId, environmentId: environmentId, cmaToken: cmaToken, host: host, tokensPath: tokensPath, initialFinalizeError: initialFinalizeError }));
|
|
13
19
|
}
|
|
14
20
|
function FinalReviewAutoAccept({ generatedCount, onFinalize, }) {
|
|
15
21
|
React.useEffect(() => {
|
|
16
22
|
onFinalize(generatedCount, 0, 0);
|
|
17
|
-
// fire once on mount; deps intentionally empty so a re-render with new generatedCount doesn't double-finalize
|
|
18
23
|
}, []);
|
|
19
24
|
return (_jsx(Box, { paddingX: 2, paddingY: 1, children: _jsxs(Text, { dimColor: true, children: ["Auto-accepting ", generatedCount, " generated components..."] }) }));
|
|
20
25
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function resolveGroupRoot(key, closures, cycleSet) {
|
|
2
|
+
if (cycleSet.has(key))
|
|
3
|
+
return key;
|
|
4
|
+
if (closures.has(key))
|
|
5
|
+
return key;
|
|
6
|
+
for (const [root, closure] of closures.entries()) {
|
|
7
|
+
if (closure.nodes.some((n) => n.name === key))
|
|
8
|
+
return root;
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CDFComponentEntry } from '@contentful/experience-design-system-types';
|
|
2
|
+
import type { ReviewComponentStatus } from '../../analyze/select/types.js';
|
|
3
|
+
export interface HistorySnapshot {
|
|
4
|
+
components: CdfReviewEntrySnapshot[];
|
|
5
|
+
autoRejected: string[];
|
|
6
|
+
undoSnapshot: Map<string, ReviewComponentStatus> | null;
|
|
7
|
+
}
|
|
8
|
+
export interface CdfReviewEntrySnapshot {
|
|
9
|
+
key: string;
|
|
10
|
+
entry: CDFComponentEntry;
|
|
11
|
+
status: ReviewComponentStatus;
|
|
12
|
+
}
|
|
13
|
+
export interface HistoryStack {
|
|
14
|
+
push(snapshot: HistorySnapshot, label: string): void;
|
|
15
|
+
undo(): HistorySnapshot | null;
|
|
16
|
+
redo(): HistorySnapshot | null;
|
|
17
|
+
canUndo(): boolean;
|
|
18
|
+
canRedo(): boolean;
|
|
19
|
+
reset(seed: HistorySnapshot): void;
|
|
20
|
+
size(): number;
|
|
21
|
+
}
|
|
22
|
+
export declare function createHistoryStack(seed: HistorySnapshot, maxDepth?: number): HistoryStack;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const DEFAULT_MAX_DEPTH = 50;
|
|
2
|
+
function cloneSnapshot(snap) {
|
|
3
|
+
return {
|
|
4
|
+
components: snap.components.map((c) => ({
|
|
5
|
+
key: c.key,
|
|
6
|
+
entry: JSON.parse(JSON.stringify(c.entry)),
|
|
7
|
+
status: c.status,
|
|
8
|
+
})),
|
|
9
|
+
autoRejected: [...snap.autoRejected],
|
|
10
|
+
undoSnapshot: snap.undoSnapshot === null ? null : new Map(Array.from(snap.undoSnapshot.entries())),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function createHistoryStack(seed, maxDepth = DEFAULT_MAX_DEPTH) {
|
|
14
|
+
const cap = Math.max(1, maxDepth);
|
|
15
|
+
let stack = [cloneSnapshot(seed)];
|
|
16
|
+
let cursor = 0;
|
|
17
|
+
function push(snapshot, _label) {
|
|
18
|
+
if (cursor < stack.length - 1) {
|
|
19
|
+
stack = stack.slice(0, cursor + 1);
|
|
20
|
+
}
|
|
21
|
+
stack.push(cloneSnapshot(snapshot));
|
|
22
|
+
while (stack.length > cap) {
|
|
23
|
+
stack.shift();
|
|
24
|
+
}
|
|
25
|
+
cursor = stack.length - 1;
|
|
26
|
+
}
|
|
27
|
+
function undo() {
|
|
28
|
+
if (cursor <= 0)
|
|
29
|
+
return null;
|
|
30
|
+
cursor -= 1;
|
|
31
|
+
return cloneSnapshot(stack[cursor]);
|
|
32
|
+
}
|
|
33
|
+
function redo() {
|
|
34
|
+
if (cursor >= stack.length - 1)
|
|
35
|
+
return null;
|
|
36
|
+
cursor += 1;
|
|
37
|
+
return cloneSnapshot(stack[cursor]);
|
|
38
|
+
}
|
|
39
|
+
function canUndo() {
|
|
40
|
+
return cursor > 0;
|
|
41
|
+
}
|
|
42
|
+
function canRedo() {
|
|
43
|
+
return cursor < stack.length - 1;
|
|
44
|
+
}
|
|
45
|
+
function reset(nextSeed) {
|
|
46
|
+
stack = [cloneSnapshot(nextSeed)];
|
|
47
|
+
cursor = 0;
|
|
48
|
+
}
|
|
49
|
+
function size() {
|
|
50
|
+
return stack.length;
|
|
51
|
+
}
|
|
52
|
+
return { push, undo, redo, canUndo, canRedo, reset, size };
|
|
53
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ComponentGraphNode } from '../../../analyze/composite-closure.js';
|
|
2
|
+
export type LineageEntry = {
|
|
3
|
+
kind: 'section';
|
|
4
|
+
label: string;
|
|
5
|
+
} | {
|
|
6
|
+
kind: 'ancestor';
|
|
7
|
+
label: string;
|
|
8
|
+
jumpTarget: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'descendant';
|
|
11
|
+
label: string;
|
|
12
|
+
jumpTarget: string;
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'empty';
|
|
15
|
+
label: string;
|
|
16
|
+
};
|
|
17
|
+
export interface LineageJumpable {
|
|
18
|
+
/** Original index into the `entries` array — callers highlight the source row. */
|
|
19
|
+
i: number;
|
|
20
|
+
entry: LineageEntry;
|
|
21
|
+
}
|
|
22
|
+
export interface UseLineageResult {
|
|
23
|
+
entries: LineageEntry[];
|
|
24
|
+
/**
|
|
25
|
+
* Jumpable entries — those the operator can Tab/Enter to. Excludes
|
|
26
|
+
* `section` / `empty` rows. Order matches `entries` minus the non-jumpable
|
|
27
|
+
* rows; each item carries the original `entries` index so callers can
|
|
28
|
+
* highlight the source row.
|
|
29
|
+
*/
|
|
30
|
+
jumpables: LineageJumpable[];
|
|
31
|
+
}
|
|
32
|
+
export declare function useLineage(focusedKey: string | null, graph: ComponentGraphNode[]): UseLineageResult;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { computeAllClosures } from '../../../analyze/composite-closure.js';
|
|
3
|
+
import { buildAncestorTree, renderAncestorTree } from '../../../analyze/lineage.js';
|
|
4
|
+
export function useLineage(focusedKey, graph) {
|
|
5
|
+
const graphKey = useMemo(() => JSON.stringify(graph), [graph]);
|
|
6
|
+
const stableGraph = useMemo(() => graph, [graphKey]);
|
|
7
|
+
const closures = useMemo(() => computeAllClosures(stableGraph), [stableGraph]);
|
|
8
|
+
const entries = useMemo(() => {
|
|
9
|
+
if (!focusedKey)
|
|
10
|
+
return [];
|
|
11
|
+
const name = focusedKey;
|
|
12
|
+
const tree = buildAncestorTree(name, stableGraph);
|
|
13
|
+
const closure = closures.get(name);
|
|
14
|
+
const out = [];
|
|
15
|
+
out.push({ kind: 'section', label: 'Ancestors:' });
|
|
16
|
+
if (tree.parents.length === 0) {
|
|
17
|
+
out.push({ kind: 'empty', label: ' (no ancestors)' });
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
const lines = renderAncestorTree(tree);
|
|
21
|
+
for (const line of lines) {
|
|
22
|
+
out.push({
|
|
23
|
+
kind: 'ancestor',
|
|
24
|
+
label: ' ' + line.text,
|
|
25
|
+
jumpTarget: line.jumpTarget ?? name,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
out.push({ kind: 'section', label: 'Descendants:' });
|
|
30
|
+
if (!closure || closure.nodes.length <= 1) {
|
|
31
|
+
out.push({ kind: 'empty', label: ' (none)' });
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
for (const node of closure.nodes) {
|
|
35
|
+
if (node.name === name)
|
|
36
|
+
continue;
|
|
37
|
+
out.push({
|
|
38
|
+
kind: 'descendant',
|
|
39
|
+
label: ' ' + ' '.repeat(Math.max(0, node.depth - 1)) + node.name,
|
|
40
|
+
jumpTarget: node.name,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}, [focusedKey, stableGraph, closures]);
|
|
46
|
+
const jumpables = useMemo(() => entries
|
|
47
|
+
.map((entry, i) => ({ entry, i }))
|
|
48
|
+
.filter(({ entry }) => entry.kind === 'ancestor' || entry.kind === 'descendant'), [entries]);
|
|
49
|
+
return { entries, jumpables };
|
|
50
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface UseOverlayPanelOptions {
|
|
2
|
+
toggleKey: string;
|
|
3
|
+
onClose?: () => void;
|
|
4
|
+
}
|
|
5
|
+
export interface UseOverlayPanelResult {
|
|
6
|
+
isOpen: boolean;
|
|
7
|
+
open: () => void;
|
|
8
|
+
close: () => void;
|
|
9
|
+
handleInput: (input: string, key: {
|
|
10
|
+
escape?: boolean;
|
|
11
|
+
}) => boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function useOverlayPanel(opts: UseOverlayPanelOptions): UseOverlayPanelResult;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
export function useOverlayPanel(opts) {
|
|
3
|
+
const { toggleKey, onClose } = opts;
|
|
4
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
5
|
+
const open = useCallback(() => {
|
|
6
|
+
setIsOpen(true);
|
|
7
|
+
}, []);
|
|
8
|
+
const close = useCallback(() => {
|
|
9
|
+
setIsOpen((prev) => {
|
|
10
|
+
if (prev && onClose)
|
|
11
|
+
onClose();
|
|
12
|
+
return false;
|
|
13
|
+
});
|
|
14
|
+
}, [onClose]);
|
|
15
|
+
const handleInput = useCallback((input, key) => {
|
|
16
|
+
if (!isOpen)
|
|
17
|
+
return false;
|
|
18
|
+
if (input === toggleKey || key.escape === true) {
|
|
19
|
+
if (onClose)
|
|
20
|
+
onClose();
|
|
21
|
+
setIsOpen(false);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}, [isOpen, toggleKey, onClose]);
|
|
26
|
+
return { isOpen, open, close, handleInput };
|
|
27
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Sidebar height (rows) — unchanged whether or not the panel is open. */
|
|
2
|
+
export declare const VISIBLE_COUNT = 20;
|
|
3
|
+
/** Panel window ceiling (matches LineagePanel's DEFAULT_MAX_ROWS from L2b). */
|
|
4
|
+
export declare const MAX_PANEL_ROWS = 15;
|
|
5
|
+
/** Floors — both surfaces stay usable even on a small terminal. */
|
|
6
|
+
export declare const SIDEBAR_MIN = 4;
|
|
7
|
+
export declare const MIN_PANEL_ROWS = 4;
|
|
8
|
+
/**
|
|
9
|
+
* Fallback when `stdout.rows` is unavailable (piped output, tests). No TTY
|
|
10
|
+
* means Ink is not interactively repainting, so there is no flicker risk — we
|
|
11
|
+
* assume enough height for the FULL sidebar (BASE_CHROME_OVERHEAD + the full
|
|
12
|
+
* VISIBLE_COUNT) so the non-interactive render is complete and unclipped.
|
|
13
|
+
*/
|
|
14
|
+
export declare const FALLBACK_ROWS = 40;
|
|
15
|
+
/**
|
|
16
|
+
* Vertical space consumed by everything ABOVE/BELOW the columns row. The panel
|
|
17
|
+
* shares the columns row, so the fit constraint while open is simply
|
|
18
|
+
* `FIXED_OVERHEAD + panelBoxHeight <= rows`.
|
|
19
|
+
*/
|
|
20
|
+
export declare const FIXED_OVERHEAD: number;
|
|
21
|
+
export declare const PANEL_BOX_CHROME = 6;
|
|
22
|
+
/**
|
|
23
|
+
* L2e — fixed vertical chrome around the sidebar in the BASE (no-panel) case,
|
|
24
|
+
* counting EVERYTHING that is not a variable sidebar entry row: the wizard
|
|
25
|
+
* header bar, the "✓ Extraction complete" + "Found N…" intro, the counter
|
|
26
|
+
* strip (with its blank separator), the cycle banner, the nothing-selected /
|
|
27
|
+
* AI-exclusion hint, the GroupedSidebar box borders + scroll indicator, the
|
|
28
|
+
* focused-detail block, and the wrapping legend region. Measured via the PTY
|
|
29
|
+
* harness against the react-ux-matrix fixture (cycles present = a tall case):
|
|
30
|
+
* the base frame was 39 lines with a 20-row sidebar after the legend row-gap
|
|
31
|
+
* compression, i.e. ≈19 lines of chrome. We use 20 for a one-line safety
|
|
32
|
+
* margin so the total frame stays strictly within `stdout.rows`.
|
|
33
|
+
*/
|
|
34
|
+
export declare const BASE_CHROME_OVERHEAD = 20;
|
|
35
|
+
export interface LineageLayoutInput {
|
|
36
|
+
/** Terminal height in rows (`stdout.rows`, or the fallback). */
|
|
37
|
+
rows: number;
|
|
38
|
+
/** Whether the lineage panel is currently open. */
|
|
39
|
+
panelOpen: boolean;
|
|
40
|
+
/** Number of lineage entries — caps the panel window so it never over-allocates. */
|
|
41
|
+
entryCount?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface LineageLayout {
|
|
44
|
+
/** Rows the sidebar should render (`visibleCount`). Constant across open/closed. */
|
|
45
|
+
sidebarVisible: number;
|
|
46
|
+
/** Rows the LineagePanel should window to (`maxRows`). */
|
|
47
|
+
panelMaxRows: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The sidebar height is constant (the panel replaces it in the same slot, so
|
|
51
|
+
* there is nothing to shrink). When the panel is open its entry window is sized
|
|
52
|
+
* to fit the sidebar's footprint AND the terminal — never taller than the
|
|
53
|
+
* sidebar was, so opening lineage cannot grow the frame past `rows`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function computeLineageLayout({ rows, panelOpen, entryCount }: LineageLayoutInput): LineageLayout;
|
|
56
|
+
export interface SidebarBudget {
|
|
57
|
+
/** Rows the GroupedSidebar should render (`visibleCount`). */
|
|
58
|
+
sidebarVisibleCount: number;
|
|
59
|
+
/** Rows the LineagePanel should window to (`maxRows`). */
|
|
60
|
+
panelMaxRows: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* L2e — autoscale the BASE frame to the terminal height. Even with the lineage
|
|
64
|
+
* panel closed, a fixed 20-row sidebar plus the always-on chrome can exceed a
|
|
65
|
+
* small terminal's rows, so plain Ink (no alt-screen) full-repaints (`\x1b[2J`)
|
|
66
|
+
* on every cursor move = flicker. This sizes the sidebar's visible-row budget
|
|
67
|
+
* from `stdout.rows` minus `BASE_CHROME_OVERHEAD` so the whole frame fits at
|
|
68
|
+
* 24/30/40 rows, and unifies the panel-open sizing with `computeLineageLayout`
|
|
69
|
+
* so BOTH cases stay within the terminal.
|
|
70
|
+
*
|
|
71
|
+
* Floor: `BASE_CHROME_OVERHEAD + SIDEBAR_MIN`. Below it `SIDEBAR_MIN`
|
|
72
|
+
* (usability) intentionally wins over the fit — a terminal that small can't
|
|
73
|
+
* host the chrome regardless.
|
|
74
|
+
*/
|
|
75
|
+
export declare function computeSidebarBudget({ rows, panelOpen, entryCount }: LineageLayoutInput): SidebarBudget;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** Sidebar height (rows) — unchanged whether or not the panel is open. */
|
|
2
|
+
export const VISIBLE_COUNT = 20;
|
|
3
|
+
/** Panel window ceiling (matches LineagePanel's DEFAULT_MAX_ROWS from L2b). */
|
|
4
|
+
export const MAX_PANEL_ROWS = 15;
|
|
5
|
+
/** Floors — both surfaces stay usable even on a small terminal. */
|
|
6
|
+
export const SIDEBAR_MIN = 4;
|
|
7
|
+
export const MIN_PANEL_ROWS = 4;
|
|
8
|
+
/**
|
|
9
|
+
* Fallback when `stdout.rows` is unavailable (piped output, tests). No TTY
|
|
10
|
+
* means Ink is not interactively repainting, so there is no flicker risk — we
|
|
11
|
+
* assume enough height for the FULL sidebar (BASE_CHROME_OVERHEAD + the full
|
|
12
|
+
* VISIBLE_COUNT) so the non-interactive render is complete and unclipped.
|
|
13
|
+
*/
|
|
14
|
+
export const FALLBACK_ROWS = 40;
|
|
15
|
+
const HEADER_ROWS = 2;
|
|
16
|
+
const COUNTER_STRIP_ROWS = 2;
|
|
17
|
+
const FOCUSED_DETAIL_ROWS = 2;
|
|
18
|
+
const LEGEND_ROWS = 3;
|
|
19
|
+
/**
|
|
20
|
+
* Vertical space consumed by everything ABOVE/BELOW the columns row. The panel
|
|
21
|
+
* shares the columns row, so the fit constraint while open is simply
|
|
22
|
+
* `FIXED_OVERHEAD + panelBoxHeight <= rows`.
|
|
23
|
+
*/
|
|
24
|
+
export const FIXED_OVERHEAD = HEADER_ROWS + COUNTER_STRIP_ROWS + FOCUSED_DETAIL_ROWS + LEGEND_ROWS;
|
|
25
|
+
// Panel box rows that are NOT entry rows: top border + header + footer hint +
|
|
26
|
+
// bottom border (+ up to two scroll indicators on large lineages).
|
|
27
|
+
export const PANEL_BOX_CHROME = 6;
|
|
28
|
+
/**
|
|
29
|
+
* L2e — fixed vertical chrome around the sidebar in the BASE (no-panel) case,
|
|
30
|
+
* counting EVERYTHING that is not a variable sidebar entry row: the wizard
|
|
31
|
+
* header bar, the "✓ Extraction complete" + "Found N…" intro, the counter
|
|
32
|
+
* strip (with its blank separator), the cycle banner, the nothing-selected /
|
|
33
|
+
* AI-exclusion hint, the GroupedSidebar box borders + scroll indicator, the
|
|
34
|
+
* focused-detail block, and the wrapping legend region. Measured via the PTY
|
|
35
|
+
* harness against the react-ux-matrix fixture (cycles present = a tall case):
|
|
36
|
+
* the base frame was 39 lines with a 20-row sidebar after the legend row-gap
|
|
37
|
+
* compression, i.e. ≈19 lines of chrome. We use 20 for a one-line safety
|
|
38
|
+
* margin so the total frame stays strictly within `stdout.rows`.
|
|
39
|
+
*/
|
|
40
|
+
export const BASE_CHROME_OVERHEAD = 20;
|
|
41
|
+
function clamp(value, min, max) {
|
|
42
|
+
return Math.max(min, Math.min(max, value));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The sidebar height is constant (the panel replaces it in the same slot, so
|
|
46
|
+
* there is nothing to shrink). When the panel is open its entry window is sized
|
|
47
|
+
* to fit the sidebar's footprint AND the terminal — never taller than the
|
|
48
|
+
* sidebar was, so opening lineage cannot grow the frame past `rows`.
|
|
49
|
+
*/
|
|
50
|
+
export function computeLineageLayout({ rows, panelOpen, entryCount }) {
|
|
51
|
+
const sidebarVisible = VISIBLE_COUNT;
|
|
52
|
+
if (!panelOpen) {
|
|
53
|
+
return { sidebarVisible, panelMaxRows: MAX_PANEL_ROWS };
|
|
54
|
+
}
|
|
55
|
+
// Rows available for panel ENTRIES given the terminal and the panel's box
|
|
56
|
+
// chrome; also never taller than the sidebar footprint it replaces.
|
|
57
|
+
const terminalFit = rows - FIXED_OVERHEAD - PANEL_BOX_CHROME;
|
|
58
|
+
let panelBase = Math.min(VISIBLE_COUNT, terminalFit);
|
|
59
|
+
if (entryCount !== undefined && entryCount > 0) {
|
|
60
|
+
panelBase = Math.min(panelBase, entryCount);
|
|
61
|
+
}
|
|
62
|
+
const panelMaxRows = clamp(panelBase, MIN_PANEL_ROWS, MAX_PANEL_ROWS);
|
|
63
|
+
return { sidebarVisible, panelMaxRows };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* L2e — autoscale the BASE frame to the terminal height. Even with the lineage
|
|
67
|
+
* panel closed, a fixed 20-row sidebar plus the always-on chrome can exceed a
|
|
68
|
+
* small terminal's rows, so plain Ink (no alt-screen) full-repaints (`\x1b[2J`)
|
|
69
|
+
* on every cursor move = flicker. This sizes the sidebar's visible-row budget
|
|
70
|
+
* from `stdout.rows` minus `BASE_CHROME_OVERHEAD` so the whole frame fits at
|
|
71
|
+
* 24/30/40 rows, and unifies the panel-open sizing with `computeLineageLayout`
|
|
72
|
+
* so BOTH cases stay within the terminal.
|
|
73
|
+
*
|
|
74
|
+
* Floor: `BASE_CHROME_OVERHEAD + SIDEBAR_MIN`. Below it `SIDEBAR_MIN`
|
|
75
|
+
* (usability) intentionally wins over the fit — a terminal that small can't
|
|
76
|
+
* host the chrome regardless.
|
|
77
|
+
*/
|
|
78
|
+
export function computeSidebarBudget({ rows, panelOpen, entryCount }) {
|
|
79
|
+
const sidebarVisibleCount = clamp(rows - BASE_CHROME_OVERHEAD, SIDEBAR_MIN, VISIBLE_COUNT);
|
|
80
|
+
const { panelMaxRows } = computeLineageLayout({ rows, panelOpen, entryCount });
|
|
81
|
+
return { sidebarVisibleCount, panelMaxRows };
|
|
82
|
+
}
|
|
@@ -15,6 +15,17 @@ export type RunLivePreviewOptions = {
|
|
|
15
15
|
host: string;
|
|
16
16
|
generation: number;
|
|
17
17
|
timeoutMs?: number;
|
|
18
|
+
/** When true and no components are accepted, preview an empty-but-present
|
|
19
|
+
* components manifest so the server returns the full delete-all diff
|
|
20
|
+
* (every existing component as `removed`) instead of rejecting an empty
|
|
21
|
+
* manifest. Lets the final-review UI show what a push would delete. */
|
|
22
|
+
deleteAllComponents?: boolean;
|
|
23
|
+
/** Restrict the preview to these component keys (the operator's accepted
|
|
24
|
+
* set). Undefined = all generated components in the session. An empty set
|
|
25
|
+
* narrows to zero components — pair with `deleteAllComponents` to preview a
|
|
26
|
+
* full delete. Lets the Finalize dialog show exactly what the accepted push
|
|
27
|
+
* would delete, independent of the session's on-disk generated rows. */
|
|
28
|
+
acceptedKeys?: ReadonlySet<string>;
|
|
18
29
|
};
|
|
19
30
|
/**
|
|
20
31
|
* Pure async helper used by `useLivePreview` to re-fire `previewImport` after a
|
|
@@ -42,11 +42,17 @@ export async function runLivePreview(opts) {
|
|
|
42
42
|
db.close();
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
// Narrow to the accepted set when provided, so the preview reflects exactly
|
|
46
|
+
// what a push of those components would create/update/remove — the session's
|
|
47
|
+
// generated rows still include not-yet-reclassified rejects at this point.
|
|
48
|
+
if (opts.acceptedKeys) {
|
|
49
|
+
components = components.filter((c) => opts.acceptedKeys.has(c.key));
|
|
50
|
+
}
|
|
45
51
|
let tokens = [];
|
|
46
52
|
if (tokensPath) {
|
|
47
53
|
tokens = await readTokensFromPath('tokens', tokensPath);
|
|
48
54
|
}
|
|
49
|
-
const manifest = buildManifest(components, tokens);
|
|
55
|
+
const manifest = buildManifest(components, tokens, { deleteAllComponents: opts.deleteAllComponents === true });
|
|
50
56
|
const client = new ImportApiClient({ host, cmaToken, spaceId, environmentId });
|
|
51
57
|
const startedAt = Date.now();
|
|
52
58
|
let timeoutHandle;
|