@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,497 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { computeAllClosures, findSharedDeps, } from '../../../composite-closure.js';
|
|
4
|
+
import { previewBadge } from './Sidebar.js';
|
|
5
|
+
import { PALETTE } from '../theme.js';
|
|
6
|
+
const GLYPH_EXPAND_COLLAPSED = '▸';
|
|
7
|
+
const GLYPH_EXPAND_EXPANDED = '▾';
|
|
8
|
+
const GLYPH_TREE_MID = '├─';
|
|
9
|
+
const GLYPH_TREE_LAST = '└─';
|
|
10
|
+
const GLYPH_WARN = '⚠';
|
|
11
|
+
const GLYPH_ERROR = '✗';
|
|
12
|
+
function isEmpty(entry) {
|
|
13
|
+
return Object.keys(entry.$properties ?? {}).length === 0 && Object.keys(entry.$slots ?? {}).length === 0;
|
|
14
|
+
}
|
|
15
|
+
function aggregateGlyphFor(closure, statusByName) {
|
|
16
|
+
let worst = 'ok';
|
|
17
|
+
for (const node of closure.nodes) {
|
|
18
|
+
const s = statusByName.get(node.name);
|
|
19
|
+
if (!s)
|
|
20
|
+
continue;
|
|
21
|
+
if (s === 'error') {
|
|
22
|
+
worst = 'error';
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
if (s === 'warning')
|
|
26
|
+
worst = 'warning';
|
|
27
|
+
}
|
|
28
|
+
if (worst === 'error')
|
|
29
|
+
return GLYPH_ERROR;
|
|
30
|
+
if (worst === 'warning')
|
|
31
|
+
return GLYPH_WARN;
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
export function buildVisibleRows(props) {
|
|
35
|
+
const { items, cycleParticipants, alwaysExpanded, showFlatTier, viewMode, graph, filterVisibleKeys } = props;
|
|
36
|
+
if (viewMode === 'flat') {
|
|
37
|
+
const rows = [];
|
|
38
|
+
if (items.length === 0)
|
|
39
|
+
return rows;
|
|
40
|
+
const itemByKey = new Map(items.map((it, idx) => [it.key, { it, idx }]));
|
|
41
|
+
const cycleKeys = [];
|
|
42
|
+
const otherKeys = [];
|
|
43
|
+
for (const it of items) {
|
|
44
|
+
if (cycleParticipants.has(it.key))
|
|
45
|
+
cycleKeys.push(it.key);
|
|
46
|
+
else
|
|
47
|
+
otherKeys.push(it.key);
|
|
48
|
+
}
|
|
49
|
+
cycleKeys.sort();
|
|
50
|
+
otherKeys.sort();
|
|
51
|
+
const otherKeySet = new Set(otherKeys);
|
|
52
|
+
const flatGraph = graph.filter((n) => otherKeySet.has(n.name));
|
|
53
|
+
const closures = computeAllClosures(flatGraph);
|
|
54
|
+
const depCountByKey = new Map();
|
|
55
|
+
for (const [name, closure] of closures) {
|
|
56
|
+
if (closure.nodes.length > 1)
|
|
57
|
+
depCountByKey.set(name, closure.nodes.length - 1);
|
|
58
|
+
}
|
|
59
|
+
for (const key of cycleKeys) {
|
|
60
|
+
const rec = itemByKey.get(key);
|
|
61
|
+
if (!rec)
|
|
62
|
+
continue;
|
|
63
|
+
rows.push({
|
|
64
|
+
kind: 'cycle',
|
|
65
|
+
key: `cycle:${key}`,
|
|
66
|
+
label: `${GLYPH_WARN} ${key} (cycle)`,
|
|
67
|
+
indent: 0,
|
|
68
|
+
itemIdx: rec.idx,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
for (const key of otherKeys) {
|
|
72
|
+
const rec = itemByKey.get(key);
|
|
73
|
+
if (!rec)
|
|
74
|
+
continue;
|
|
75
|
+
const dep = depCountByKey.get(key);
|
|
76
|
+
const suffix = dep !== undefined ? ` (${dep} dep${dep === 1 ? '' : 's'})` : '';
|
|
77
|
+
rows.push({
|
|
78
|
+
kind: 'flat',
|
|
79
|
+
key: `flat:${key}`,
|
|
80
|
+
label: `${key}${suffix}`,
|
|
81
|
+
indent: 0,
|
|
82
|
+
itemIdx: rec.idx,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return rows;
|
|
86
|
+
}
|
|
87
|
+
const rows = [];
|
|
88
|
+
if (items.length === 0)
|
|
89
|
+
return rows;
|
|
90
|
+
const itemByKey = new Map(items.map((it, idx) => [it.key, { it, idx }]));
|
|
91
|
+
const statusByName = new Map(items.map((it) => [it.key, it.status]));
|
|
92
|
+
const cycleKeys = [];
|
|
93
|
+
const emptyKeys = [];
|
|
94
|
+
const otherKeys = [];
|
|
95
|
+
for (const it of items) {
|
|
96
|
+
if (cycleParticipants.has(it.key))
|
|
97
|
+
cycleKeys.push(it.key);
|
|
98
|
+
else if (isEmpty(it.entry))
|
|
99
|
+
emptyKeys.push(it.key);
|
|
100
|
+
else
|
|
101
|
+
otherKeys.push(it.key);
|
|
102
|
+
}
|
|
103
|
+
cycleKeys.sort();
|
|
104
|
+
emptyKeys.sort();
|
|
105
|
+
const seenCycleTierChildOccurrence = new Set();
|
|
106
|
+
const computeCycleMemberSubtree = (root) => {
|
|
107
|
+
const out = [];
|
|
108
|
+
const visited = new Set([root]);
|
|
109
|
+
const queue = [{ name: root, depth: 0 }];
|
|
110
|
+
while (queue.length > 0) {
|
|
111
|
+
const cur = queue.shift();
|
|
112
|
+
const parentItem = itemByKey.get(cur.name)?.it;
|
|
113
|
+
if (!parentItem)
|
|
114
|
+
continue;
|
|
115
|
+
const slotTargets = [];
|
|
116
|
+
const seenTarget = new Set();
|
|
117
|
+
for (const slot of Object.values(parentItem.entry.$slots ?? {})) {
|
|
118
|
+
const allowed = Array.isArray(slot?.$allowedComponents)
|
|
119
|
+
? slot.$allowedComponents.filter((v) => typeof v === 'string')
|
|
120
|
+
: [];
|
|
121
|
+
for (const target of allowed) {
|
|
122
|
+
if (!itemByKey.has(target))
|
|
123
|
+
continue;
|
|
124
|
+
if (seenTarget.has(target))
|
|
125
|
+
continue;
|
|
126
|
+
seenTarget.add(target);
|
|
127
|
+
slotTargets.push(target);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
slotTargets.sort();
|
|
131
|
+
for (const target of slotTargets) {
|
|
132
|
+
if (visited.has(target))
|
|
133
|
+
continue;
|
|
134
|
+
visited.add(target);
|
|
135
|
+
const isCycleMember = cycleParticipants.has(target);
|
|
136
|
+
out.push({ name: target, depth: cur.depth + 1, isCycleMember });
|
|
137
|
+
queue.push({ name: target, depth: cur.depth + 1 });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
};
|
|
142
|
+
for (const key of cycleKeys) {
|
|
143
|
+
const rec = itemByKey.get(key);
|
|
144
|
+
if (!rec)
|
|
145
|
+
continue;
|
|
146
|
+
const expanded = alwaysExpanded ? true : props.expandedGroups.has(key);
|
|
147
|
+
const glyphExpand = expanded ? GLYPH_EXPAND_EXPANDED : GLYPH_EXPAND_COLLAPSED;
|
|
148
|
+
rows.push({
|
|
149
|
+
kind: 'cycle',
|
|
150
|
+
key: `cycle:${key}`,
|
|
151
|
+
label: `${glyphExpand} ${GLYPH_WARN} ${key} (cycle)`,
|
|
152
|
+
indent: 0,
|
|
153
|
+
itemIdx: rec.idx,
|
|
154
|
+
rootName: key,
|
|
155
|
+
});
|
|
156
|
+
if (!expanded)
|
|
157
|
+
continue;
|
|
158
|
+
const subtree = computeCycleMemberSubtree(key);
|
|
159
|
+
subtree.forEach((child, i) => {
|
|
160
|
+
const isLast = i === subtree.length - 1;
|
|
161
|
+
const glyph = isLast ? GLYPH_TREE_LAST : GLYPH_TREE_MID;
|
|
162
|
+
let sharedSuffix = false;
|
|
163
|
+
if (seenCycleTierChildOccurrence.has(child.name))
|
|
164
|
+
sharedSuffix = true;
|
|
165
|
+
else
|
|
166
|
+
seenCycleTierChildOccurrence.add(child.name);
|
|
167
|
+
const childRec = itemByKey.get(child.name);
|
|
168
|
+
const indent = ' '.repeat(Math.max(0, child.depth - 1));
|
|
169
|
+
const labelName = child.isCycleMember ? `${GLYPH_WARN} ${child.name} (cycle)` : child.name;
|
|
170
|
+
rows.push({
|
|
171
|
+
kind: 'group-child',
|
|
172
|
+
key: `cycle-child:${key}:${child.name}`,
|
|
173
|
+
label: `${indent}${glyph} ${labelName}${sharedSuffix ? ' (shared)' : ''}`,
|
|
174
|
+
indent: child.depth,
|
|
175
|
+
sharedSuffix,
|
|
176
|
+
cycleChild: child.isCycleMember || undefined,
|
|
177
|
+
itemIdx: childRec?.idx ?? -1,
|
|
178
|
+
rootName: key,
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
for (const key of emptyKeys) {
|
|
183
|
+
const rec = itemByKey.get(key);
|
|
184
|
+
if (!rec)
|
|
185
|
+
continue;
|
|
186
|
+
rows.push({
|
|
187
|
+
kind: 'empty',
|
|
188
|
+
key: `empty:${key}`,
|
|
189
|
+
label: `${key} (empty)`,
|
|
190
|
+
indent: 0,
|
|
191
|
+
itemIdx: rec.idx,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
const otherKeySet = new Set(otherKeys);
|
|
195
|
+
const subgraph = graph.filter((n) => otherKeySet.has(n.name));
|
|
196
|
+
const closures = computeAllClosures(subgraph);
|
|
197
|
+
const sharedDeps = findSharedDeps(closures);
|
|
198
|
+
const hasCycleDepDirect = (name) => {
|
|
199
|
+
const it = itemByKey.get(name)?.it;
|
|
200
|
+
if (!it)
|
|
201
|
+
return false;
|
|
202
|
+
for (const slot of Object.values(it.entry.$slots ?? {})) {
|
|
203
|
+
const allowed = Array.isArray(slot?.$allowedComponents)
|
|
204
|
+
? slot.$allowedComponents.filter((v) => typeof v === 'string')
|
|
205
|
+
: [];
|
|
206
|
+
for (const target of allowed) {
|
|
207
|
+
if (cycleParticipants.has(target))
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return false;
|
|
212
|
+
};
|
|
213
|
+
const rootNames = [...closures.keys()].sort();
|
|
214
|
+
const standaloneRoots = [];
|
|
215
|
+
const groupRoots = [];
|
|
216
|
+
for (const root of rootNames) {
|
|
217
|
+
const c = closures.get(root);
|
|
218
|
+
const promotesForCycle = hasCycleDepDirect(root);
|
|
219
|
+
if (c.nodes.length <= 1 && !promotesForCycle)
|
|
220
|
+
standaloneRoots.push(root);
|
|
221
|
+
else
|
|
222
|
+
groupRoots.push(root);
|
|
223
|
+
}
|
|
224
|
+
const seenSharedOccurrence = new Set();
|
|
225
|
+
const seenCycleChildOccurrence = new Set();
|
|
226
|
+
for (const root of groupRoots) {
|
|
227
|
+
const closure = closures.get(root);
|
|
228
|
+
let injectedCycleCount = 0;
|
|
229
|
+
const seenInject = new Set();
|
|
230
|
+
const countInjections = (parentName) => {
|
|
231
|
+
const parentItem = itemByKey.get(parentName)?.it;
|
|
232
|
+
if (!parentItem)
|
|
233
|
+
return;
|
|
234
|
+
for (const slot of Object.values(parentItem.entry.$slots ?? {})) {
|
|
235
|
+
const allowed = Array.isArray(slot?.$allowedComponents)
|
|
236
|
+
? slot.$allowedComponents.filter((v) => typeof v === 'string')
|
|
237
|
+
: [];
|
|
238
|
+
for (const target of allowed) {
|
|
239
|
+
if (!cycleParticipants.has(target))
|
|
240
|
+
continue;
|
|
241
|
+
const key = `${parentName}→${target}`;
|
|
242
|
+
if (seenInject.has(key))
|
|
243
|
+
continue;
|
|
244
|
+
seenInject.add(key);
|
|
245
|
+
injectedCycleCount += 1;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
countInjections(root);
|
|
250
|
+
for (const n of closure.nodes)
|
|
251
|
+
if (n.name !== root)
|
|
252
|
+
countInjections(n.name);
|
|
253
|
+
const depCount = closure.nodes.length - 1 + injectedCycleCount;
|
|
254
|
+
const expanded = alwaysExpanded ? true : props.expandedGroups.has(root);
|
|
255
|
+
const glyphExpand = expanded ? GLYPH_EXPAND_EXPANDED : GLYPH_EXPAND_COLLAPSED;
|
|
256
|
+
const aggregate = expanded ? null : aggregateGlyphFor(closure, statusByName);
|
|
257
|
+
const rec = itemByKey.get(root);
|
|
258
|
+
rows.push({
|
|
259
|
+
kind: 'group-root',
|
|
260
|
+
key: `root:${root}`,
|
|
261
|
+
label: `${glyphExpand} ${root} (${depCount} dep${depCount === 1 ? '' : 's'})${aggregate ? ' ' + aggregate : ''}`,
|
|
262
|
+
indent: 0,
|
|
263
|
+
aggregateGlyph: aggregate,
|
|
264
|
+
itemIdx: rec.idx,
|
|
265
|
+
rootName: root,
|
|
266
|
+
});
|
|
267
|
+
if (!expanded)
|
|
268
|
+
continue;
|
|
269
|
+
const closureChildren = closure.nodes
|
|
270
|
+
.filter((n) => n.name !== root)
|
|
271
|
+
.slice()
|
|
272
|
+
.sort((a, b) => a.depth - b.depth || a.name.localeCompare(b.name));
|
|
273
|
+
const injectedChildren = [];
|
|
274
|
+
const emitCycleChildrenOf = (parentName, parentDepth) => {
|
|
275
|
+
const parentItem = itemByKey.get(parentName)?.it;
|
|
276
|
+
if (!parentItem)
|
|
277
|
+
return;
|
|
278
|
+
const seen = new Set();
|
|
279
|
+
for (const slot of Object.values(parentItem.entry.$slots ?? {})) {
|
|
280
|
+
const allowed = Array.isArray(slot?.$allowedComponents)
|
|
281
|
+
? slot.$allowedComponents.filter((v) => typeof v === 'string')
|
|
282
|
+
: [];
|
|
283
|
+
for (const target of allowed) {
|
|
284
|
+
if (!cycleParticipants.has(target))
|
|
285
|
+
continue;
|
|
286
|
+
if (seen.has(target))
|
|
287
|
+
continue;
|
|
288
|
+
seen.add(target);
|
|
289
|
+
injectedChildren.push({
|
|
290
|
+
name: target,
|
|
291
|
+
depth: parentDepth + 1,
|
|
292
|
+
isCycleChild: true,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
emitCycleChildrenOf(root, 0);
|
|
298
|
+
for (const c of closureChildren) {
|
|
299
|
+
injectedChildren.push({ name: c.name, depth: c.depth, isCycleChild: false });
|
|
300
|
+
emitCycleChildrenOf(c.name, c.depth);
|
|
301
|
+
}
|
|
302
|
+
injectedChildren.forEach((child, i) => {
|
|
303
|
+
const isLast = i === injectedChildren.length - 1;
|
|
304
|
+
const glyph = isLast ? GLYPH_TREE_LAST : GLYPH_TREE_MID;
|
|
305
|
+
let sharedSuffix = false;
|
|
306
|
+
if (child.isCycleChild) {
|
|
307
|
+
if (seenCycleChildOccurrence.has(child.name))
|
|
308
|
+
sharedSuffix = true;
|
|
309
|
+
else
|
|
310
|
+
seenCycleChildOccurrence.add(child.name);
|
|
311
|
+
}
|
|
312
|
+
else if (sharedDeps.has(child.name)) {
|
|
313
|
+
if (seenSharedOccurrence.has(child.name))
|
|
314
|
+
sharedSuffix = true;
|
|
315
|
+
else
|
|
316
|
+
seenSharedOccurrence.add(child.name);
|
|
317
|
+
}
|
|
318
|
+
const childRec = itemByKey.get(child.name);
|
|
319
|
+
const indent = ' '.repeat(Math.max(0, child.depth - 1));
|
|
320
|
+
const labelName = child.isCycleChild ? `${GLYPH_WARN} ${child.name} (cycle)` : child.name;
|
|
321
|
+
rows.push({
|
|
322
|
+
kind: 'group-child',
|
|
323
|
+
key: `child:${root}:${child.name}`,
|
|
324
|
+
label: `${indent}${glyph} ${labelName}${sharedSuffix ? ' (shared)' : ''}`,
|
|
325
|
+
indent: child.depth,
|
|
326
|
+
sharedSuffix,
|
|
327
|
+
cycleChild: child.isCycleChild || undefined,
|
|
328
|
+
itemIdx: childRec?.idx ?? -1,
|
|
329
|
+
rootName: root,
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
for (const name of standaloneRoots) {
|
|
334
|
+
const rec = itemByKey.get(name);
|
|
335
|
+
if (!rec)
|
|
336
|
+
continue;
|
|
337
|
+
rows.push({
|
|
338
|
+
kind: 'standalone',
|
|
339
|
+
key: `stand:${name}`,
|
|
340
|
+
label: name,
|
|
341
|
+
indent: 0,
|
|
342
|
+
itemIdx: rec.idx,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (showFlatTier) {
|
|
346
|
+
const flatNames = otherKeys.slice().sort();
|
|
347
|
+
if (flatNames.length > 0) {
|
|
348
|
+
rows.push({
|
|
349
|
+
kind: 'flat-header',
|
|
350
|
+
key: 'flat-header',
|
|
351
|
+
label: '── All components ──',
|
|
352
|
+
indent: 0,
|
|
353
|
+
itemIdx: -1,
|
|
354
|
+
});
|
|
355
|
+
for (const name of flatNames) {
|
|
356
|
+
const rec = itemByKey.get(name);
|
|
357
|
+
if (!rec)
|
|
358
|
+
continue;
|
|
359
|
+
rows.push({
|
|
360
|
+
kind: 'flat',
|
|
361
|
+
key: `flat:${name}`,
|
|
362
|
+
label: name,
|
|
363
|
+
indent: 0,
|
|
364
|
+
itemIdx: rec.idx,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (filterVisibleKeys !== undefined) {
|
|
370
|
+
return rows.filter((row) => {
|
|
371
|
+
if (row.itemIdx < 0)
|
|
372
|
+
return true;
|
|
373
|
+
const name = items[row.itemIdx]?.key;
|
|
374
|
+
if (name === undefined)
|
|
375
|
+
return true;
|
|
376
|
+
return filterVisibleKeys.has(name);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
return rows;
|
|
380
|
+
}
|
|
381
|
+
export function visibleItemOrder(props) {
|
|
382
|
+
return buildVisibleRows(props)
|
|
383
|
+
.filter((row) => row.itemIdx >= 0)
|
|
384
|
+
.map((row) => row.itemIdx);
|
|
385
|
+
}
|
|
386
|
+
function rowColor(row, aggregate) {
|
|
387
|
+
if (row.kind === 'cycle')
|
|
388
|
+
return PALETTE.warning;
|
|
389
|
+
if (row.kind === 'group-child' && row.cycleChild)
|
|
390
|
+
return PALETTE.warning;
|
|
391
|
+
if (row.kind === 'empty')
|
|
392
|
+
return PALETTE.warning;
|
|
393
|
+
if (row.kind === 'group-root' && aggregate === GLYPH_ERROR)
|
|
394
|
+
return PALETTE.error;
|
|
395
|
+
if (row.kind === 'group-root' && aggregate === GLYPH_WARN)
|
|
396
|
+
return PALETTE.warning;
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
export function labelStyleFor(input) {
|
|
400
|
+
const { row, isCursor, wouldDim } = input;
|
|
401
|
+
if (isCursor) {
|
|
402
|
+
return { color: PALETTE.info, bold: true, dim: false };
|
|
403
|
+
}
|
|
404
|
+
return {
|
|
405
|
+
color: rowColor(row, row.aggregateGlyph),
|
|
406
|
+
bold: false,
|
|
407
|
+
dim: wouldDim,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
export function selectionGlyphStyleFor(selState, isCycleRow) {
|
|
411
|
+
if (selState === 'accepted') {
|
|
412
|
+
return { glyph: '[✓]', color: PALETTE.success, dim: false, bold: isCycleRow };
|
|
413
|
+
}
|
|
414
|
+
if (selState === 'rejected') {
|
|
415
|
+
return { glyph: '[✗]', color: PALETTE.error, dim: false, bold: isCycleRow };
|
|
416
|
+
}
|
|
417
|
+
if (selState === 'undecided') {
|
|
418
|
+
return { glyph: '[ ]', color: PALETTE.muted, dim: !isCycleRow, bold: false };
|
|
419
|
+
}
|
|
420
|
+
return { glyph: null, color: undefined, dim: false, bold: false };
|
|
421
|
+
}
|
|
422
|
+
export function inheritanceGlyphStyleFor(input) {
|
|
423
|
+
const { status, isOwn, isCursor } = input;
|
|
424
|
+
if (status === 'error') {
|
|
425
|
+
return { glyph: GLYPH_ERROR, color: PALETTE.error, dim: !isCursor && !isOwn, bold: isCursor };
|
|
426
|
+
}
|
|
427
|
+
if (status === 'warning') {
|
|
428
|
+
return { glyph: GLYPH_WARN, color: PALETTE.warning, dim: !isCursor && !isOwn, bold: isCursor };
|
|
429
|
+
}
|
|
430
|
+
return { glyph: null, color: undefined, dim: false, bold: false };
|
|
431
|
+
}
|
|
432
|
+
export function GroupedSidebar(props) {
|
|
433
|
+
const { items, cycleParticipants = new Set(), expandedGroups, focused, width, selectedIdx, selectedRowIdx, renderStatusByKey, previewAnnotationByKey, scrollOffset, visibleCount, alwaysExpanded, showFlatTier, viewMode, selectionStateByKey, aiFlaggedByKey, dimPredicate, visibleRows: providedRows, graph, filterVisibleKeys, } = props;
|
|
434
|
+
const allRows = providedRows ??
|
|
435
|
+
buildVisibleRows({
|
|
436
|
+
items,
|
|
437
|
+
cycleParticipants,
|
|
438
|
+
expandedGroups,
|
|
439
|
+
alwaysExpanded,
|
|
440
|
+
showFlatTier,
|
|
441
|
+
viewMode,
|
|
442
|
+
graph,
|
|
443
|
+
filterVisibleKeys,
|
|
444
|
+
});
|
|
445
|
+
const windowed = scrollOffset !== undefined && visibleCount !== undefined;
|
|
446
|
+
const start = windowed ? Math.max(0, scrollOffset ?? 0) : 0;
|
|
447
|
+
const end = windowed ? start + (visibleCount ?? allRows.length) : allRows.length;
|
|
448
|
+
const rows = windowed ? allRows.slice(start, end) : allRows;
|
|
449
|
+
const showScrollUp = windowed && start > 0;
|
|
450
|
+
const showScrollDown = windowed && end < allRows.length;
|
|
451
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, flexShrink: 0, borderStyle: "single", borderColor: focused ? 'white' : undefined, children: [showScrollUp && _jsx(Text, { dimColor: true, children: "\u25B2" }), rows.map((row, i) => {
|
|
452
|
+
const absoluteRowIdx = start + i;
|
|
453
|
+
const isSelected = selectedRowIdx !== undefined
|
|
454
|
+
? absoluteRowIdx === selectedRowIdx && row.itemIdx >= 0
|
|
455
|
+
: row.itemIdx >= 0 && row.itemIdx === selectedIdx;
|
|
456
|
+
const itemName = row.itemIdx >= 0 ? items[row.itemIdx]?.key : undefined;
|
|
457
|
+
const rs = itemName ? renderStatusByKey?.get(itemName) : undefined;
|
|
458
|
+
const badge = itemName ? previewBadge(previewAnnotationByKey?.get(itemName)) : null;
|
|
459
|
+
const showInheritance = rs !== undefined && row.kind !== 'cycle' && row.kind !== 'empty' && row.kind !== 'group-root';
|
|
460
|
+
const supportsSelectionGlyph = selectionStateByKey !== undefined &&
|
|
461
|
+
itemName !== undefined &&
|
|
462
|
+
(row.kind === 'standalone' ||
|
|
463
|
+
row.kind === 'group-root' ||
|
|
464
|
+
row.kind === 'group-child' ||
|
|
465
|
+
row.kind === 'flat' ||
|
|
466
|
+
row.kind === 'cycle');
|
|
467
|
+
const selState = supportsSelectionGlyph ? (selectionStateByKey.get(itemName) ?? 'undecided') : undefined;
|
|
468
|
+
const isCycleRow = row.kind === 'cycle';
|
|
469
|
+
const selStyle = selectionGlyphStyleFor(selState, isCycleRow);
|
|
470
|
+
const selGlyph = selStyle.glyph;
|
|
471
|
+
const selColor = selStyle.color;
|
|
472
|
+
const selDim = selStyle.dim;
|
|
473
|
+
const selBold = selStyle.bold;
|
|
474
|
+
const aiFlagged = aiFlaggedByKey !== undefined &&
|
|
475
|
+
itemName !== undefined &&
|
|
476
|
+
row.kind !== 'flat-header' &&
|
|
477
|
+
aiFlaggedByKey.get(itemName) === true;
|
|
478
|
+
const canDim = dimPredicate !== undefined &&
|
|
479
|
+
itemName !== undefined &&
|
|
480
|
+
row.kind !== 'cycle' &&
|
|
481
|
+
row.kind !== 'group-root' &&
|
|
482
|
+
row.kind !== 'flat-header' &&
|
|
483
|
+
dimPredicate(itemName);
|
|
484
|
+
const isSynthetic = row.kind === 'flat-header';
|
|
485
|
+
const isCursor = isSelected && focused;
|
|
486
|
+
const wouldDim = row.kind === 'flat-header' || row.sharedSuffix === true || canDim;
|
|
487
|
+
const labelStyle = labelStyleFor({ row, isCursor, wouldDim });
|
|
488
|
+
const inheritanceStyle = inheritanceGlyphStyleFor({
|
|
489
|
+
status: showInheritance ? rs?.status : undefined,
|
|
490
|
+
isOwn: rs?.isOwn ?? false,
|
|
491
|
+
isCursor,
|
|
492
|
+
});
|
|
493
|
+
return (_jsxs(Box, { children: [isCursor ? (_jsx(Text, { color: PALETTE.info, bold: true, children: '▶' })) : (_jsx(Text, { children: " " })), badge ? (_jsx(Text, { color: badge.color, bold: badge.bold, dimColor: badge.dim, children: badge.char })) : (_jsx(Text, { children: " " })), selectionStateByKey !== undefined &&
|
|
494
|
+
(selGlyph && !isSynthetic ? (_jsx(Text, { color: selColor, dimColor: selDim, bold: selBold, children: ' ' + selGlyph })) : (_jsx(Text, { children: ' ' }))), aiFlaggedByKey !== undefined &&
|
|
495
|
+
(aiFlagged ? (_jsx(Text, { color: PALETTE.warning, bold: true, children: ' [×]' })) : (_jsx(Text, { children: ' ' }))), _jsxs(Text, { color: labelStyle.color, bold: labelStyle.bold, inverse: isSelected && focused, underline: isSelected && !focused, dimColor: labelStyle.dim, wrap: "truncate", children: [' ', row.label] }), inheritanceStyle.glyph && (_jsx(Text, { color: inheritanceStyle.color, bold: inheritanceStyle.bold, dimColor: inheritanceStyle.dim, children: ' ' + inheritanceStyle.glyph }))] }, row.key));
|
|
496
|
+
}), showScrollDown && _jsx(Text, { dimColor: true, children: "\u25BC" })] }));
|
|
497
|
+
}
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
export type HelpSection = {
|
|
3
|
+
title: string;
|
|
4
|
+
entries: {
|
|
5
|
+
keys: string;
|
|
6
|
+
label: string;
|
|
7
|
+
}[];
|
|
8
|
+
};
|
|
2
9
|
type HelpOverlayProps = {
|
|
3
10
|
mode: 'analyze' | 'validate' | 'review';
|
|
11
|
+
sections?: undefined;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
} | {
|
|
14
|
+
mode?: undefined;
|
|
15
|
+
sections: HelpSection[];
|
|
4
16
|
onClose: () => void;
|
|
5
17
|
};
|
|
6
|
-
export declare function HelpOverlay(
|
|
18
|
+
export declare function HelpOverlay(props: HelpOverlayProps): React.ReactElement;
|
|
7
19
|
export {};
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
2
3
|
import { Box, Text } from 'ink';
|
|
3
4
|
import { useImmediateInput } from '../hooks/useImmediateInput.js';
|
|
4
|
-
export function HelpOverlay(
|
|
5
|
+
export function HelpOverlay(props) {
|
|
6
|
+
const { onClose } = props;
|
|
5
7
|
useImmediateInput((input, key) => {
|
|
6
8
|
if (input === '?' || key.escape) {
|
|
7
9
|
onClose();
|
|
8
10
|
}
|
|
9
11
|
});
|
|
10
|
-
|
|
12
|
+
if (props.sections) {
|
|
13
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", padding: 1, width: 46, children: [_jsx(Text, { bold: true, children: '─'.repeat(18) + ' Help ' + '─'.repeat(18) }), props.sections.map((section) => (_jsxs(React.Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: section.title }), section.entries.map((entry) => (_jsx(Text, { children: entry.keys ? ' ' + entry.keys.padEnd(16) + ' ' + entry.label : ' ' + entry.label }, entry.keys + entry.label)))] }, section.title)))] }));
|
|
14
|
+
}
|
|
15
|
+
const mode = props.mode;
|
|
16
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", padding: 1, width: 46, children: [_jsx(Text, { bold: true, children: '─'.repeat(18) + ' Help ' + '─'.repeat(18) }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Navigation" }), _jsx(Text, { children: ' ↑ / k / PgUp Scroll up' }), _jsx(Text, { children: ' ↓ / j / PgDn Scroll down' }), _jsx(Text, { children: ' g / Home Jump to top' }), _jsx(Text, { children: ' G / End Jump to bottom' }), mode === 'review' && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "[Review mode only]" }), _jsx(Text, { children: ' Tab Toggle sidebar/detail' }), _jsx(Text, { children: ' a Accept component' }), _jsx(Text, { children: ' r Reject component' }), _jsx(Text, { children: ' e Edit proposal' }), _jsx(Text, { children: ' s Toggle source code' }), _jsx(Text, { children: ' A Approve all' }), _jsx(Text, { children: ' F Open finalize dialog' }), _jsx(Text, { children: ' Ctrl+S Save draft' }), _jsx(Text, { children: ' Ctrl+Z Undo' }), _jsx(Text, { children: ' Ctrl+Y Redo' }), _jsx(Text, { children: ' Esc Exit edit / close' })] })), _jsx(Text, { children: " " }), _jsx(Text, { children: ' ? Close help' }), _jsx(Text, { children: ' q Quit' })] }));
|
|
11
17
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
+
import { PALETTE } from '../theme.js';
|
|
3
4
|
import { Box, Text } from 'ink';
|
|
4
5
|
import { useUndo } from '../hooks/useUndo.js';
|
|
5
6
|
import { useImmediateInput } from '../hooks/useImmediateInput.js';
|
|
@@ -13,7 +14,6 @@ export function JsonEditor({ value, width, height, onSave, onDiscard }) {
|
|
|
13
14
|
const [validationError, setValidationError] = useState(null);
|
|
14
15
|
const { lines, cursorRow, cursorCol } = undo.current;
|
|
15
16
|
const scrollCol = 0;
|
|
16
|
-
// Sync scroll with cursor — pure derivation, no setState needed when within bounds
|
|
17
17
|
let effectiveScrollRow = scrollRow;
|
|
18
18
|
if (cursorRow < scrollRow)
|
|
19
19
|
effectiveScrollRow = cursorRow;
|
|
@@ -117,7 +117,7 @@ export function JsonEditor({ value, width, height, onSave, onDiscard }) {
|
|
|
117
117
|
});
|
|
118
118
|
const innerWidth = Math.max(1, width - 2);
|
|
119
119
|
const visibleLines = lines.slice(effectiveScrollRow, effectiveScrollRow + height);
|
|
120
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor:
|
|
120
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: PALETTE.inverse, children: [_jsx(Text, { bold: true, children: 'EDIT [EDITING — Ctrl+S save · Esc discard]' }), visibleLines.map((line, displayRow) => {
|
|
121
121
|
const actualRow = displayRow + effectiveScrollRow;
|
|
122
122
|
const displayLine = line.slice(scrollCol, scrollCol + innerWidth);
|
|
123
123
|
if (actualRow === cursorRow) {
|
|
@@ -127,5 +127,5 @@ export function JsonEditor({ value, width, height, onSave, onDiscard }) {
|
|
|
127
127
|
return (_jsxs(Box, { children: [_jsx(Text, { children: beforeCursor }), _jsx(Text, { inverse: true, children: cursorChar }), _jsx(Text, { children: afterCursor })] }, displayRow));
|
|
128
128
|
}
|
|
129
129
|
return _jsx(Text, { children: displayLine }, displayRow);
|
|
130
|
-
}), validationError && _jsx(Text, { color:
|
|
130
|
+
}), validationError && _jsx(Text, { color: PALETTE.error, children: '✗ Invalid JSON: ' + validationError })] }));
|
|
131
131
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { LineageEntry, LineageJumpable } from '../../../../import/tui/hooks/useLineage.js';
|
|
3
|
+
export interface LineagePanelProps {
|
|
4
|
+
focusedComponentKey: string;
|
|
5
|
+
entries: LineageEntry[];
|
|
6
|
+
/** Index into the jumpables list — NOT into entries. */
|
|
7
|
+
cursor: number;
|
|
8
|
+
jumpables: LineageJumpable[];
|
|
9
|
+
/** Max entry rows rendered at once. Larger lineages window around the cursor. */
|
|
10
|
+
maxRows?: number;
|
|
11
|
+
/** Constrain the panel box to a fixed column width (e.g. the sidebar slot). */
|
|
12
|
+
width?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function LineagePanel({ focusedComponentKey, entries, cursor, jumpables, maxRows, width, }: LineagePanelProps): React.ReactElement;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PALETTE } from '../theme.js';
|
|
3
|
+
import { Text } from 'ink';
|
|
4
|
+
import { GotoBanner } from './GotoBanner.js';
|
|
5
|
+
const LINEAGE_FOOTER_HINT = '[↑/↓] move · [Enter] jump · [l/Esc] close';
|
|
6
|
+
export function LineagePanel({ focusedComponentKey, entries, cursor, jumpables, maxRows, width, }) {
|
|
7
|
+
const rows = entries.map((e) => ({
|
|
8
|
+
label: e.label,
|
|
9
|
+
jumpTarget: e.kind === 'ancestor' || e.kind === 'descendant' ? e.jumpTarget : '',
|
|
10
|
+
kind: e.kind,
|
|
11
|
+
}));
|
|
12
|
+
const cursorRowIndex = jumpables[cursor]?.i ?? 0;
|
|
13
|
+
return (_jsx(GotoBanner, { title: `Lineage: ${focusedComponentKey}`, rows: rows, cursor: cursor, cursorRowIndex: cursorRowIndex, maxRows: maxRows, width: width, footerHint: LINEAGE_FOOTER_HINT, renderRow: (row, i, isCursor) => {
|
|
14
|
+
if (row.kind === 'section') {
|
|
15
|
+
return (_jsxs(Text, { bold: true, children: [' ', row.label] }, i));
|
|
16
|
+
}
|
|
17
|
+
if (row.kind === 'empty') {
|
|
18
|
+
return (_jsxs(Text, { children: [_jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: ' ' + row.label })] }, i));
|
|
19
|
+
}
|
|
20
|
+
return (_jsxs(Text, { children: [isCursor ? (_jsx(Text, { color: PALETTE.info, bold: true, children: '▶' })) : (_jsx(Text, { children: " " })), _jsx(Text, { inverse: isCursor, children: ' ' + row.label })] }, i));
|
|
21
|
+
} }));
|
|
22
|
+
}
|
|
@@ -22,13 +22,6 @@ type RenderedLine = {
|
|
|
22
22
|
} | {
|
|
23
23
|
kind: 'blank';
|
|
24
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
25
|
export declare function renderRationaleLines(rows: RationaleRow[], innerWidth: number): RenderedLine[];
|
|
33
26
|
export declare function RationalePanel({ componentName, rows, scrollOffset, width, height, active, }: RationalePanelProps): React.ReactElement;
|
|
34
27
|
export {};
|