@contentful/experience-design-system-cli 2.26.1-dev-build-a852362.0 → 2.26.1-dev-build-f03f014.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.
Files changed (204) hide show
  1. package/dist/package.json +14 -10
  2. package/dist/src/index.js +34937 -9
  3. package/dist/src/lib/cli-path.d.ts +4 -0
  4. package/dist/src/runs/export-helpers.d.ts +1 -12
  5. package/dist/src/runs/push-helpers.d.ts +1 -6
  6. package/package.json +12 -7
  7. package/skills/generate-components.md +427 -0
  8. package/skills/generate-tokens.md +194 -0
  9. package/skills/select-components.md +224 -0
  10. package/dist/src/analytics/apply.js +0 -30
  11. package/dist/src/analytics/client.js +0 -81
  12. package/dist/src/analytics/constants.js +0 -4
  13. package/dist/src/analytics/env.js +0 -14
  14. package/dist/src/analytics/exit.js +0 -24
  15. package/dist/src/analytics/index.js +0 -9
  16. package/dist/src/analytics/normalize.js +0 -18
  17. package/dist/src/analytics/os.js +0 -13
  18. package/dist/src/analytics/session.js +0 -15
  19. package/dist/src/analytics/tracker.js +0 -126
  20. package/dist/src/analytics/types.js +0 -1
  21. package/dist/src/analyze/build-analyze-view-rows.js +0 -63
  22. package/dist/src/analyze/command.js +0 -599
  23. package/dist/src/analyze/composite-closure.js +0 -0
  24. package/dist/src/analyze/composition/agent-parser/author-prompt.js +0 -22
  25. package/dist/src/analyze/composition/agent-parser/extract-parser.js +0 -44
  26. package/dist/src/analyze/composition/agent-parser/load-prompt.js +0 -31
  27. package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +0 -88
  28. package/dist/src/analyze/composition/agent-parser/sandbox.js +0 -187
  29. package/dist/src/analyze/composition/apply-mapping.js +0 -67
  30. package/dist/src/analyze/composition/candidate-critic-agent.js +0 -39
  31. package/dist/src/analyze/composition/candidate-critic.js +0 -63
  32. package/dist/src/analyze/composition/candidate-files.js +0 -131
  33. package/dist/src/analyze/composition/composition-cache-key.js +0 -19
  34. package/dist/src/analyze/composition/interchange-schema.js +0 -61
  35. package/dist/src/analyze/composition/manifest-doc-evidence.js +0 -136
  36. package/dist/src/analyze/composition/merge-edges.js +0 -70
  37. package/dist/src/analyze/composition/parse-map-edges.js +0 -55
  38. package/dist/src/analyze/composition/resolve-mapping-cli.js +0 -29
  39. package/dist/src/analyze/composition/resolve-mapping.js +0 -111
  40. package/dist/src/analyze/cycle-detection.js +0 -234
  41. package/dist/src/analyze/cycle-view.js +0 -28
  42. package/dist/src/analyze/fuzzy-search.js +0 -55
  43. package/dist/src/analyze/issue-inheritance.js +0 -108
  44. package/dist/src/analyze/lineage.js +0 -0
  45. package/dist/src/analyze/scope-gate-cascade.js +0 -170
  46. package/dist/src/analyze/search-neighborhood.js +0 -56
  47. package/dist/src/analyze/select/command.js +0 -484
  48. package/dist/src/analyze/select/index.js +0 -5
  49. package/dist/src/analyze/select/parser.js +0 -53
  50. package/dist/src/analyze/select/persistence.js +0 -80
  51. package/dist/src/analyze/select/preview-annotations.js +0 -62
  52. package/dist/src/analyze/select/stdout.js +0 -3
  53. package/dist/src/analyze/select/tui/App.js +0 -483
  54. package/dist/src/analyze/select/tui/components/ComponentDetail.js +0 -48
  55. package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +0 -125
  56. package/dist/src/analyze/select/tui/components/FieldEditor.js +0 -1131
  57. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +0 -36
  58. package/dist/src/analyze/select/tui/components/GotoBanner.js +0 -30
  59. package/dist/src/analyze/select/tui/components/GroupedSidebar.js +0 -517
  60. package/dist/src/analyze/select/tui/components/HelpOverlay.js +0 -17
  61. package/dist/src/analyze/select/tui/components/JsonEditor.js +0 -131
  62. package/dist/src/analyze/select/tui/components/JsonPanel.js +0 -66
  63. package/dist/src/analyze/select/tui/components/LineagePanel.js +0 -22
  64. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +0 -29
  65. package/dist/src/analyze/select/tui/components/QuitDialog.js +0 -14
  66. package/dist/src/analyze/select/tui/components/RationalePanel.js +0 -80
  67. package/dist/src/analyze/select/tui/components/Sidebar.js +0 -92
  68. package/dist/src/analyze/select/tui/components/SourcePanel.js +0 -52
  69. package/dist/src/analyze/select/tui/components/StatusBar.js +0 -7
  70. package/dist/src/analyze/select/tui/components/TopBar.js +0 -10
  71. package/dist/src/analyze/select/tui/components/removed-components-text.js +0 -11
  72. package/dist/src/analyze/select/tui/hooks/scroll-offset.js +0 -26
  73. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +0 -66
  74. package/dist/src/analyze/select/tui/hooks/useKeymap.js +0 -67
  75. package/dist/src/analyze/select/tui/hooks/useSession.js +0 -52
  76. package/dist/src/analyze/select/tui/hooks/useUndo.js +0 -26
  77. package/dist/src/analyze/select/tui/theme.js +0 -28
  78. package/dist/src/analyze/select/types.js +0 -39
  79. package/dist/src/analyze/select-agent/command.js +0 -558
  80. package/dist/src/analyze/select-agent/context-builder.js +0 -197
  81. package/dist/src/analyze/select-agent/show-rationale.js +0 -92
  82. package/dist/src/analyze/selection-cascade.js +0 -19
  83. package/dist/src/analyze/slot-graph.js +0 -17
  84. package/dist/src/analyze/tui/AnalyzeView.js +0 -64
  85. package/dist/src/apply/api-client.js +0 -440
  86. package/dist/src/apply/command.js +0 -845
  87. package/dist/src/apply/manifest.js +0 -1
  88. package/dist/src/apply/preview-utils.js +0 -26
  89. package/dist/src/apply/tui/SelectView.js +0 -34
  90. package/dist/src/apply/tui/ServerApplyView.js +0 -49
  91. package/dist/src/apply/tui/ServerPreviewView.js +0 -23
  92. package/dist/src/credentials-store.js +0 -74
  93. package/dist/src/generate/command.js +0 -597
  94. package/dist/src/generate/edit/command.js +0 -127
  95. package/dist/src/generate/tui/GenerateView.js +0 -10
  96. package/dist/src/host-utils.js +0 -26
  97. package/dist/src/import/agent-model-resolve.js +0 -35
  98. package/dist/src/import/auto-filter-resolve.js +0 -16
  99. package/dist/src/import/command.js +0 -380
  100. package/dist/src/import/cycle-auto-reject.js +0 -14
  101. package/dist/src/import/orchestrator.js +0 -603
  102. package/dist/src/import/path-utils.js +0 -30
  103. package/dist/src/import/picker-dispatch.js +0 -44
  104. package/dist/src/import/print-prompt.js +0 -31
  105. package/dist/src/import/strip-allowed-components.js +0 -22
  106. package/dist/src/import/tui/CustomPromptBanner.js +0 -8
  107. package/dist/src/import/tui/WizardApp.js +0 -1670
  108. package/dist/src/import/tui/ai-flag.js +0 -6
  109. package/dist/src/import/tui/auto-filter-error.js +0 -29
  110. package/dist/src/import/tui/autocomplete.js +0 -32
  111. package/dist/src/import/tui/components/AutoFilterBanner.js +0 -19
  112. package/dist/src/import/tui/components/CounterStrip.js +0 -12
  113. package/dist/src/import/tui/components/LegendEntry.js +0 -6
  114. package/dist/src/import/tui/cycle-panel-scroll.js +0 -33
  115. package/dist/src/import/tui/final-review-host.js +0 -25
  116. package/dist/src/import/tui/group-collapse.js +0 -11
  117. package/dist/src/import/tui/history.js +0 -53
  118. package/dist/src/import/tui/hooks/useLineage.js +0 -50
  119. package/dist/src/import/tui/hooks/useOverlayPanel.js +0 -27
  120. package/dist/src/import/tui/lineage-layout.js +0 -82
  121. package/dist/src/import/tui/merge-ai-decisions.js +0 -22
  122. package/dist/src/import/tui/push-decision-gate-helpers.js +0 -8
  123. package/dist/src/import/tui/push-progress.js +0 -1
  124. package/dist/src/import/tui/run-print-files-helpers.js +0 -8
  125. package/dist/src/import/tui/run-teaser.js +0 -9
  126. package/dist/src/import/tui/runLivePreview.js +0 -82
  127. package/dist/src/import/tui/runScopeGate.js +0 -28
  128. package/dist/src/import/tui/scope-gate-columns.js +0 -94
  129. package/dist/src/import/tui/scope-gate-host.js +0 -28
  130. package/dist/src/import/tui/sidebar-width.js +0 -3
  131. package/dist/src/import/tui/spawn-generate.js +0 -53
  132. package/dist/src/import/tui/step-filters.js +0 -42
  133. package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +0 -726
  134. package/dist/src/import/tui/steps/AtomicScopeGateStep.js +0 -210
  135. package/dist/src/import/tui/steps/CredentialsStep.js +0 -108
  136. package/dist/src/import/tui/steps/DoneStep.js +0 -24
  137. package/dist/src/import/tui/steps/ErrorStep.js +0 -16
  138. package/dist/src/import/tui/steps/GateStep.js +0 -22
  139. package/dist/src/import/tui/steps/GenerateReviewStep.js +0 -1618
  140. package/dist/src/import/tui/steps/PathValidationStep.js +0 -152
  141. package/dist/src/import/tui/steps/PreviewStep.js +0 -37
  142. package/dist/src/import/tui/steps/PreviewValidationErrorStep.js +0 -23
  143. package/dist/src/import/tui/steps/PushDecisionGateStep.js +0 -73
  144. package/dist/src/import/tui/steps/PushingStep.js +0 -24
  145. package/dist/src/import/tui/steps/RunningStep.js +0 -21
  146. package/dist/src/import/tui/steps/ScopeGateStep.js +0 -907
  147. package/dist/src/import/tui/steps/TokenInputStep.js +0 -88
  148. package/dist/src/import/tui/steps/WelcomeStep.js +0 -34
  149. package/dist/src/import/tui/steps/WizardPreviewStep.js +0 -169
  150. package/dist/src/import/tui/steps/auto-reject-decision.js +0 -9
  151. package/dist/src/import/tui/steps/breaking-change-format.js +0 -24
  152. package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
  153. package/dist/src/import/tui/steps/preview-diff.js +0 -175
  154. package/dist/src/import/tui/useFinalizePreview.js +0 -0
  155. package/dist/src/import/tui/useLivePreview.js +0 -122
  156. package/dist/src/import/tui/wizard-422-helpers.js +0 -60
  157. package/dist/src/import/tui/wizard-generate-progress.js +0 -25
  158. package/dist/src/import/tui/wizard-save-flow.js +0 -20
  159. package/dist/src/import/tui/wizard-state-transitions.js +0 -32
  160. package/dist/src/lib/agent-model-options.js +0 -15
  161. package/dist/src/lib/command-options.js +0 -30
  162. package/dist/src/lib/composition-mode.js +0 -39
  163. package/dist/src/lib/contentful-urls.js +0 -27
  164. package/dist/src/lib/debug-logger.js +0 -229
  165. package/dist/src/lib/debug-preamble.js +0 -33
  166. package/dist/src/lib/error-parser.js +0 -269
  167. package/dist/src/lib/prompt-overrides.js +0 -69
  168. package/dist/src/lib/terminal-capabilities.js +0 -51
  169. package/dist/src/lib/user-agent.js +0 -29
  170. package/dist/src/output/format.js +0 -110
  171. package/dist/src/print/command.js +0 -221
  172. package/dist/src/print/validate/tui/ValidateView.js +0 -37
  173. package/dist/src/print/validate/validators/cdf-validator.js +0 -104
  174. package/dist/src/print/validate/validators/dtcg-validator.js +0 -110
  175. package/dist/src/print/validate/validators/format-errors.js +0 -18
  176. package/dist/src/program.js +0 -105
  177. package/dist/src/runs/export-helpers.js +0 -59
  178. package/dist/src/runs/fingerprint.js +0 -75
  179. package/dist/src/runs/ls-command.js +0 -137
  180. package/dist/src/runs/modify-launcher.js +0 -42
  181. package/dist/src/runs/path-prompt.js +0 -72
  182. package/dist/src/runs/push-creds-prompt.js +0 -39
  183. package/dist/src/runs/push-helpers.js +0 -72
  184. package/dist/src/runs/push-launcher.js +0 -77
  185. package/dist/src/runs/replay-helpers.js +0 -164
  186. package/dist/src/runs/resolve-run-target.js +0 -45
  187. package/dist/src/runs/run-picker-mount.js +0 -72
  188. package/dist/src/runs/run-picker.js +0 -129
  189. package/dist/src/runs/save-conflict.js +0 -56
  190. package/dist/src/runs/save-path-resolver.js +0 -75
  191. package/dist/src/runs/staleness.js +0 -140
  192. package/dist/src/runs/store.js +0 -136
  193. package/dist/src/session/cache-keys.js +0 -44
  194. package/dist/src/session/command.js +0 -261
  195. package/dist/src/session/db.js +0 -1538
  196. package/dist/src/session/migration.js +0 -117
  197. package/dist/src/session/session-id.js +0 -212
  198. package/dist/src/session/stats.js +0 -89
  199. package/dist/src/setup/analytics-prompt.js +0 -24
  200. package/dist/src/setup/auto-filter-prompt.js +0 -24
  201. package/dist/src/setup/command.js +0 -966
  202. package/dist/src/setup/composition-mode-prompt.js +0 -37
  203. package/dist/src/setup/debug-mode-prompt.js +0 -22
  204. package/dist/src/types.js +0 -1
@@ -1,1131 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React, { useState } from 'react';
3
- import { PALETTE } from '../theme.js';
4
- import { Box, Text } from 'ink';
5
- import { CDF_PROPERTY_TYPES, CDF_PROPERTY_CATEGORIES, DESIGN_TOKEN_TYPES, } from '@contentful/experience-design-system-types';
6
- import { useImmediateInput } from '../hooks/useImmediateInput.js';
7
- import { computeNextScrollOffset } from '../hooks/scroll-offset.js';
8
- import { findSlotCycles } from '../../../cycle-detection.js';
9
- function parseToState(json) {
10
- let parsed;
11
- try {
12
- parsed = JSON.parse(json);
13
- }
14
- catch (e) {
15
- return { state: { componentDescription: '', props: [], slots: [] }, error: String(e) };
16
- }
17
- if (typeof parsed !== 'object' || parsed === null) {
18
- return {
19
- state: { componentDescription: '', props: [], slots: [] },
20
- error: 'Expected a JSON object',
21
- };
22
- }
23
- const entry = parsed;
24
- let component;
25
- const keys = Object.keys(entry);
26
- if (entry.$type === 'component' || entry.$properties !== undefined) {
27
- component = entry;
28
- }
29
- else if (keys.length === 1 && typeof entry[keys[0]] === 'object') {
30
- component = entry[keys[0]];
31
- }
32
- else {
33
- component = entry;
34
- }
35
- const componentDescription = typeof component.$description === 'string' ? component.$description : '';
36
- const rawProps = (component.$properties ?? {});
37
- const props = Object.entries(rawProps).map(([name, raw]) => {
38
- const p = (raw ?? {});
39
- const type = CDF_PROPERTY_TYPES.includes(p.$type) ? p.$type : 'string';
40
- let defaultValue = null;
41
- if (p.$default !== undefined && p.$default !== null) {
42
- if (type === 'boolean') {
43
- defaultValue = typeof p.$default === 'boolean' ? p.$default : null;
44
- }
45
- else {
46
- defaultValue = typeof p.$default === 'string' ? p.$default : String(p.$default);
47
- }
48
- }
49
- return {
50
- name,
51
- type,
52
- category: CDF_PROPERTY_CATEGORIES.includes(p.$category)
53
- ? p.$category
54
- : 'content',
55
- required: p.$required === true,
56
- description: typeof p.$description === 'string' ? p.$description : '',
57
- values: Array.isArray(p.$values) ? p.$values.filter((v) => typeof v === 'string') : [],
58
- tokenKind: typeof p['$token.kind'] === 'string' ? p['$token.kind'] : '',
59
- default: defaultValue,
60
- };
61
- });
62
- const rawSlots = (component.$slots ?? {});
63
- const slots = Object.entries(rawSlots).map(([name, raw]) => {
64
- const s = (raw ?? {});
65
- return {
66
- name,
67
- description: typeof s.$description === 'string' ? s.$description : '',
68
- required: s.$required === true,
69
- allowedComponents: Array.isArray(s.$allowedComponents)
70
- ? s.$allowedComponents.filter((v) => typeof v === 'string')
71
- : [],
72
- };
73
- });
74
- return { state: { componentDescription, props, slots }, error: null };
75
- }
76
- function serializeState(state, originalJson) {
77
- let wrapperKey = null;
78
- try {
79
- const orig = JSON.parse(originalJson);
80
- const keys = Object.keys(orig);
81
- if (keys.length === 1 && orig[keys[0]] !== null && typeof orig[keys[0]] === 'object') {
82
- const inner = orig[keys[0]];
83
- if (inner.$type === 'component' || inner.$properties !== undefined) {
84
- wrapperKey = keys[0];
85
- }
86
- }
87
- }
88
- catch { }
89
- const $properties = {};
90
- for (const p of state.props) {
91
- const def = {
92
- $type: p.type,
93
- $category: p.category,
94
- };
95
- if (p.required)
96
- def.$required = true;
97
- if (p.description)
98
- def.$description = p.description;
99
- if (p.type === 'enum' && p.values.length > 0)
100
- def.$values = p.values;
101
- if (p.type === 'token' && p.tokenKind)
102
- def['$token.kind'] = p.tokenKind;
103
- if (p.default !== null) {
104
- if (p.type === 'boolean' && typeof p.default === 'boolean') {
105
- def.$default = p.default;
106
- }
107
- else if ((p.type === 'string' || p.type === 'token' || p.type === 'enum') &&
108
- typeof p.default === 'string' &&
109
- p.default !== '') {
110
- def.$default = p.default;
111
- }
112
- }
113
- $properties[p.name] = def;
114
- }
115
- const entry = {
116
- $type: 'component',
117
- $properties,
118
- };
119
- if (state.componentDescription)
120
- entry.$description = state.componentDescription;
121
- if (state.slots.length > 0) {
122
- entry.$slots = {};
123
- for (const s of state.slots) {
124
- const slotDef = {};
125
- if (s.description)
126
- slotDef.$description = s.description;
127
- if (s.required)
128
- slotDef.$required = true;
129
- if (s.allowedComponents.length > 0)
130
- slotDef.$allowedComponents = s.allowedComponents;
131
- entry.$slots[s.name] = slotDef;
132
- }
133
- }
134
- if (wrapperKey) {
135
- return JSON.stringify({ [wrapperKey]: entry }, null, 2);
136
- }
137
- return JSON.stringify(entry, null, 2);
138
- }
139
- function Picker({ value, active }) {
140
- return (_jsxs(Box, { children: [active && _jsx(Text, { color: PALETTE.info, children: '‹' }), _jsx(Text, { color: active ? PALETTE.info : PALETTE.inverse, bold: active, children: value }), active && _jsx(Text, { color: PALETTE.info, children: '›' })] }));
141
- }
142
- function Toggle({ value, active }) {
143
- return (_jsx(Box, { children: _jsx(Text, { color: active ? PALETTE.info : value ? PALETTE.success : undefined, children: value ? '[✓]' : '[ ]' }) }));
144
- }
145
- function DefaultSubRow({ prop, active, textCursor, cursorVisible, }) {
146
- const cursor = cursorVisible ? '█' : ' ';
147
- if (prop.type === 'richtext' || prop.type === 'media' || prop.type === 'link') {
148
- return (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: "default:" }), _jsx(Text, { dimColor: true, children: "(not applicable)" })] }));
149
- }
150
- if (prop.type === 'boolean') {
151
- const display = prop.default === true ? 'true' : prop.default === false ? 'false' : '(unset)';
152
- return (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: "default:" }), active ? _jsx(Picker, { value: display, active: true }) : _jsx(Text, { color: PALETTE.inverse, children: display })] }));
153
- }
154
- if (prop.type === 'enum') {
155
- if (prop.values.length === 0) {
156
- return (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: "default:" }), _jsx(Text, { dimColor: true, children: "(no values defined)" })] }));
157
- }
158
- const display = typeof prop.default === 'string' && prop.default !== '' ? prop.default : '(unset)';
159
- return (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: "default:" }), active ? _jsx(Picker, { value: display, active: true }) : _jsx(Text, { color: PALETTE.inverse, children: display })] }));
160
- }
161
- const value = typeof prop.default === 'string' ? prop.default : '';
162
- if (active) {
163
- return (_jsxs(Box, { paddingLeft: 2, flexDirection: "row", children: [_jsx(Text, { dimColor: true, children: "default:" }), _jsxs(Box, { flexGrow: 1, borderStyle: "round", borderColor: PALETTE.info, paddingX: 1, children: [_jsx(Text, { children: value.slice(0, textCursor) }), _jsx(Text, { inverse: cursorVisible, children: value[textCursor] ?? cursor }), _jsx(Text, { children: value.slice(textCursor + 1) })] })] }));
164
- }
165
- return (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: "default:" }), _jsx(Text, { color: value ? PALETTE.inverse : undefined, dimColor: !value, children: value || '(none)' })] }));
166
- }
167
- function PropRow({ prop, selected, activeField, textCursor, valueCursor, cursorVisible, editingValue, valueText, width, rationale, rowKey, }) {
168
- const cursor = cursorVisible ? '█' : ' ';
169
- const bg = selected ? 'blue' : undefined;
170
- const descActive = activeField === 'description';
171
- const nameDisplay = prop.name.length > 14 ? prop.name.slice(0, 13) + '…' : prop.name.padEnd(14);
172
- return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsxs(Box, { gap: 1, children: [_jsxs(Text, { color: selected ? PALETTE.inverse : PALETTE.info, bold: selected, backgroundColor: bg, children: [' ', nameDisplay, ' '] }), _jsx(Text, { dimColor: !selected, children: "type:" }), activeField === 'type' ? (_jsx(Picker, { value: prop.type, active: true })) : (_jsx(Text, { color: selected ? PALETTE.warning : PALETTE.inverse, children: prop.type })), _jsx(Text, { dimColor: !selected, children: "cat:" }), activeField === 'category' ? (_jsx(Picker, { value: prop.category, active: true })) : (_jsx(Text, { color: selected ? PALETTE.info : PALETTE.inverse, children: prop.category })), _jsx(Text, { dimColor: !selected, children: "req:" }), activeField === 'required' ? (_jsx(Toggle, { value: prop.required, active: true })) : (_jsx(Toggle, { value: prop.required, active: false })), prop.type === 'token' && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: !selected, children: "kind:" }), activeField === 'tokenKind' ? (_jsx(Picker, { value: prop.tokenKind || DESIGN_TOKEN_TYPES[0], active: true })) : (_jsx(Text, { color: selected ? PALETTE.success : PALETTE.inverse, children: prop.tokenKind || '—' }))] }))] }), selected && (_jsx(DefaultSubRow, { prop: prop, active: activeField === 'default', textCursor: textCursor, cursorVisible: cursorVisible })), selected && descActive && (_jsxs(Box, { paddingLeft: 2, flexDirection: "row", children: [_jsx(Text, { dimColor: true, children: "desc:" }), _jsxs(Box, { flexGrow: 1, borderStyle: "round", borderColor: PALETTE.info, paddingX: 1, children: [_jsx(Text, { children: prop.description.slice(0, textCursor) }), _jsx(Text, { inverse: cursorVisible, children: prop.description[textCursor] ?? cursor }), _jsx(Text, { children: prop.description.slice(textCursor + 1) })] })] })), selected && !descActive && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: "desc:" }), _jsx(Text, { color: PALETTE.success, children: prop.description || '—' })] })), selected && rationale && rationale.trim().length > 0 && (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, children: (() => {
173
- const max = Math.max(8, width - 8);
174
- const text = `~ ${rationale}`;
175
- return text.length > max ? text.slice(0, max - 1) + '…' : text;
176
- })() }) }, rowKey ? `rationale-${rowKey}` : undefined)), selected && prop.type === 'enum' && (_jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "values:" }), activeField === 'values' && (_jsx(Text, { dimColor: true, children: ' [a]dd [e]dit [r]emove [↑↓] navigate [K/J] reorder' }))] }), prop.values.length === 0 && !editingValue && _jsx(Text, { dimColor: true, children: " (none \u2014 press [a] to add)" }), prop.values.map((v, i) => {
177
- const isActiveCursor = activeField === 'values' && valueCursor === i;
178
- const isBeingEdited = editingValue?.mode === 'edit' && editingValue.index === i;
179
- if (isBeingEdited) {
180
- return (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: PALETTE.info, children: '✎ ' }), _jsx(Text, { children: valueText }), _jsx(Text, { inverse: cursorVisible, children: " " })] }, i));
181
- }
182
- return (_jsx(Box, { gap: 1, paddingLeft: 2, children: _jsx(Text, { color: isActiveCursor ? PALETTE.info : PALETTE.inverse, children: isActiveCursor ? `▶ ${v}` : ` ${v}` }) }, i));
183
- }), editingValue?.mode === 'add' && (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: PALETTE.info, children: '+ ' }), _jsx(Text, { children: valueText }), _jsx(Text, { inverse: cursorVisible, children: " " })] }))] }))] }));
184
- }
185
- function SlotRow({ slot, selected, activeField, textCursor, valueCursor, cursorVisible, editingValue, valueText, width, pickerCandidates, pickerCursor, }) {
186
- const cursor = cursorVisible ? '█' : ' ';
187
- const bg = selected ? 'blue' : undefined;
188
- const nameDisplay = slot.name.length > 14 ? slot.name.slice(0, 13) + '…' : slot.name.padEnd(14);
189
- return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsxs(Box, { gap: 1, children: [_jsxs(Text, { color: selected ? PALETTE.inverse : PALETTE.info, bold: selected, backgroundColor: bg, children: [' ', nameDisplay, ' '] }), _jsx(Text, { dimColor: !selected, children: "req:" }), activeField === 'required' ? (_jsx(Toggle, { value: slot.required, active: true })) : (_jsx(Toggle, { value: slot.required, active: false }))] }), !selected && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: "allowed:" }), slot.allowedComponents.length === 0 ? (_jsx(Text, { dimColor: true, children: "(any)" })) : (_jsx(Text, { color: PALETTE.info, children: slot.allowedComponents.join(', ') }))] })), selected && (_jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "allowed:" }), activeField === 'allowedComponents' && (_jsx(Text, { dimColor: true, children: slot.allowedComponents.length > 0
190
- ? ' [a]dd [e]dit [r]emove [←→] cycle [↑↓] navigate [K/J] reorder'
191
- : ' [a]dd [e]dit [r]emove [↑↓] navigate [K/J] reorder' }))] }), slot.allowedComponents.length === 0 && !editingValue && (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, children: activeField === 'allowedComponents' ? '(any — press [a] to add)' : '(any)' }) })), slot.allowedComponents.map((v, i) => {
192
- const isActiveCursor = activeField === 'allowedComponents' && valueCursor === i;
193
- const isBeingEdited = editingValue?.mode === 'edit' && editingValue.index === i;
194
- if (isBeingEdited) {
195
- return (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: PALETTE.info, children: '✎ ' }), _jsx(Text, { children: valueText }), _jsx(Text, { inverse: cursorVisible, children: " " })] }, i));
196
- }
197
- return (_jsx(Box, { gap: 1, paddingLeft: 2, children: _jsx(Text, { color: isActiveCursor ? PALETTE.info : PALETTE.inverse, children: isActiveCursor ? `▶ ${v}` : ` ${v}` }) }, i));
198
- }), editingValue?.mode === 'add' && activeField === 'allowedComponents' && (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: PALETTE.info, children: '+ ' }), _jsx(Text, { children: valueText }), _jsx(Text, { inverse: cursorVisible, children: " " })] })), editingValue?.mode === 'add' && activeField === 'allowedComponents' && pickerCandidates !== null && (_jsx(Box, { paddingLeft: 2, flexDirection: "column", children: pickerCandidates.length === 0 ? (_jsx(Text, { dimColor: true, children: '(no valid components to add — all remaining candidates would create cycles)' })) : ((() => {
199
- const filtered = valueText.length === 0
200
- ? pickerCandidates
201
- : pickerCandidates.filter((n) => n.toLowerCase().includes(valueText.toLowerCase()));
202
- if (filtered.length === 0) {
203
- return _jsx(Text, { dimColor: true, children: '(no candidates match — Enter to add as free text)' });
204
- }
205
- const cursor = pickerCursor % filtered.length;
206
- const MAX_VISIBLE = 5;
207
- const start = Math.max(0, Math.min(filtered.length - MAX_VISIBLE, cursor - 2));
208
- const slice = filtered.slice(start, start + MAX_VISIBLE);
209
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: ' candidates (↑↓ cycle, Enter to add):' }), slice.map((name, i) => {
210
- const absIdx = start + i;
211
- const isCursor = absIdx === cursor;
212
- return (_jsx(Text, { color: isCursor ? PALETTE.info : undefined, dimColor: !isCursor, children: isCursor ? ` ▶ ${name}` : ` ${name}` }, name));
213
- })] }));
214
- })()) }))] })), selected && activeField === 'description' && (_jsxs(Box, { paddingLeft: 2, flexDirection: "row", children: [_jsx(Text, { dimColor: true, children: "desc:" }), _jsxs(Box, { flexGrow: 1, borderStyle: "round", borderColor: PALETTE.info, paddingX: 1, children: [_jsx(Text, { children: slot.description.slice(0, textCursor) }), _jsx(Text, { inverse: cursorVisible, children: slot.description[textCursor] ?? cursor }), _jsx(Text, { children: slot.description.slice(textCursor + 1) })] })] })), selected && activeField !== 'description' && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: "desc:" }), _jsx(Text, { color: PALETTE.success, children: slot.description || '—' })] }))] }));
215
- }
216
- function propFields(prop) {
217
- const fields = ['type', 'category', 'required'];
218
- if (prop.type === 'token')
219
- fields.push('tokenKind');
220
- if (prop.type === 'enum')
221
- fields.push('values');
222
- if (prop.type !== 'richtext' && prop.type !== 'media' && prop.type !== 'link') {
223
- fields.push('default');
224
- }
225
- fields.push('description');
226
- return fields;
227
- }
228
- const SLOT_FIELDS = ['required', 'allowedComponents', 'description'];
229
- export function simulateGraphWithCandidate(projectSlotGraph, selfName, currentSlots, targetSlotName, candidate) {
230
- const selfEntry = {
231
- name: selfName,
232
- slots: currentSlots.map((s) => ({
233
- name: s.name,
234
- allowedComponents: s.name === targetSlotName ? [...s.allowedComponents, candidate] : [...s.allowedComponents],
235
- })),
236
- };
237
- const withoutSelf = projectSlotGraph.filter((c) => c.name !== selfName);
238
- return [...withoutSelf, selfEntry];
239
- }
240
- export function introducesNewCycle(before, next) {
241
- const key = (edges) => {
242
- if (edges.length === 0)
243
- return '';
244
- const encoded = edges.map((e) => `${e.fromComponent}|${e.slotName}|${e.toComponent}`);
245
- let minIdx = 0;
246
- for (let i = 1; i < encoded.length; i += 1)
247
- if (encoded[i] < encoded[minIdx])
248
- minIdx = i;
249
- return [...encoded.slice(minIdx), ...encoded.slice(0, minIdx)].join(';');
250
- };
251
- const beforeSet = new Set(before.map((c) => key(c.edges)));
252
- for (const cycle of next)
253
- if (!beforeSet.has(key(cycle.edges)))
254
- return true;
255
- return false;
256
- }
257
- export function computeAllowedComponentCandidates(projectSlotGraph, selfName, currentSlots, targetSlotName) {
258
- const targetSlot = currentSlots.find((s) => s.name === targetSlotName);
259
- const alreadyAdded = new Set(targetSlot?.allowedComponents ?? []);
260
- const universe = new Set();
261
- for (const c of projectSlotGraph)
262
- universe.add(c.name);
263
- universe.delete(selfName);
264
- for (const added of alreadyAdded)
265
- universe.delete(added);
266
- const baseline = findSlotCycles(simulateGraphWithCandidate(projectSlotGraph, selfName, currentSlots, '', ''));
267
- const safe = [];
268
- for (const candidate of universe) {
269
- const nextCycles = findSlotCycles(simulateGraphWithCandidate(projectSlotGraph, selfName, currentSlots, targetSlotName, candidate));
270
- if (!introducesNewCycle(baseline, nextCycles))
271
- safe.push(candidate);
272
- }
273
- safe.sort((a, b) => a.localeCompare(b));
274
- return safe;
275
- }
276
- export function simulateGraphWithReplacement(projectSlotGraph, selfName, currentSlots, targetSlotName, replaceIndex, candidate) {
277
- const selfEntry = {
278
- name: selfName,
279
- slots: currentSlots.map((s) => {
280
- if (s.name !== targetSlotName)
281
- return { name: s.name, allowedComponents: [...s.allowedComponents] };
282
- const nextVals = s.allowedComponents.map((v, i) => (i === replaceIndex ? candidate : v));
283
- return { name: s.name, allowedComponents: nextVals };
284
- }),
285
- };
286
- const withoutSelf = projectSlotGraph.filter((c) => c.name !== selfName);
287
- return [...withoutSelf, selfEntry];
288
- }
289
- export function computeAllowedComponentReplacementCandidates(projectSlotGraph, selfName, currentSlots, targetSlotName, replaceIndex) {
290
- const targetSlot = currentSlots.find((s) => s.name === targetSlotName);
291
- const existing = targetSlot?.allowedComponents ?? [];
292
- const excludeOtherEntries = new Set();
293
- existing.forEach((v, i) => {
294
- if (i !== replaceIndex)
295
- excludeOtherEntries.add(v);
296
- });
297
- const universe = new Set();
298
- for (const c of projectSlotGraph)
299
- universe.add(c.name);
300
- universe.delete(selfName);
301
- for (const other of excludeOtherEntries)
302
- universe.delete(other);
303
- const baseline = findSlotCycles(simulateGraphWithCandidate(projectSlotGraph, selfName, currentSlots, '', ''));
304
- const safe = [];
305
- for (const candidate of universe) {
306
- const nextCycles = findSlotCycles(simulateGraphWithReplacement(projectSlotGraph, selfName, currentSlots, targetSlotName, replaceIndex, candidate));
307
- if (!introducesNewCycle(baseline, nextCycles))
308
- safe.push(candidate);
309
- }
310
- safe.sort((a, b) => a.localeCompare(b));
311
- return safe;
312
- }
313
- export function FieldEditor({ value, width, height, active = true, onChange, onSave, onDiscard, onExit, metadata, onTogglePropRationale, propRationaleKey = 'i', onToggleComponentRationale, componentRationaleKey = 'I', onToggleSourceExternal, onTextEntryActiveChange, projectSlotGraph, currentComponentName, onDirtyChange, discardTrigger, initialFocusTarget, }) {
314
- const { state: initialState, error: parseError } = parseToState(value);
315
- const [editorState, setEditorState] = useState(initialState);
316
- const [parseErr] = useState(parseError);
317
- const initialFocus = (() => {
318
- if (initialFocusTarget) {
319
- if (initialFocusTarget.kind === 'prop') {
320
- const idx = initialState.props.findIndex((p) => p.name === initialFocusTarget.name);
321
- if (idx >= 0) {
322
- return {
323
- focusLevel: 'prop',
324
- inSlots: false,
325
- propIdx: idx,
326
- slotIdx: 0,
327
- activeField: null,
328
- textCursor: 0,
329
- };
330
- }
331
- }
332
- else {
333
- const idx = initialState.slots.findIndex((s) => s.name === initialFocusTarget.name);
334
- if (idx >= 0) {
335
- return {
336
- focusLevel: 'slot',
337
- inSlots: true,
338
- propIdx: 0,
339
- slotIdx: idx,
340
- activeField: null,
341
- textCursor: 0,
342
- };
343
- }
344
- }
345
- }
346
- if (initialState.props.length > 0) {
347
- return {
348
- focusLevel: 'prop',
349
- inSlots: false,
350
- propIdx: 0,
351
- slotIdx: 0,
352
- activeField: null,
353
- textCursor: 0,
354
- };
355
- }
356
- if (initialState.slots.length > 0) {
357
- return {
358
- focusLevel: 'slot',
359
- inSlots: true,
360
- propIdx: 0,
361
- slotIdx: 0,
362
- activeField: null,
363
- textCursor: 0,
364
- };
365
- }
366
- return {
367
- focusLevel: 'prop',
368
- inSlots: false,
369
- propIdx: 0,
370
- slotIdx: 0,
371
- activeField: null,
372
- textCursor: 0,
373
- };
374
- })();
375
- const [focusLevel, setFocusLevel] = useState(initialFocus.focusLevel);
376
- const [propIdx, setPropIdx] = useState(initialFocus.propIdx);
377
- const [slotIdx, setSlotIdx] = useState(initialFocus.slotIdx);
378
- const [inSlots, setInSlots] = useState(initialFocus.inSlots);
379
- const [inComponentDesc, setInComponentDesc] = useState(false);
380
- const [activeField, setActiveField] = useState(initialFocus.activeField);
381
- const [textCursor, setTextCursor] = useState(initialFocus.textCursor);
382
- const [valueCursor, setValueCursor] = useState(0);
383
- const [editingValue, setEditingValue] = useState(null);
384
- const [valueText, setValueText] = useState('');
385
- const [pickerCursor, setPickerCursor] = useState(0);
386
- const [validationError, setValidationError] = useState(null);
387
- const [cursorVisible] = useState(true);
388
- const [sourceOpen, setSourceOpen] = useState(false);
389
- const [rationaleOpen, setRationaleOpen] = useState(false);
390
- const [rationaleScrollOffset, setRationaleScrollOffset] = useState(0);
391
- const [showHelp, setShowHelp] = useState(false);
392
- const props = editorState.props;
393
- const slots = editorState.slots;
394
- const textEntryActive = (focusLevel === 'field' && activeField === 'description') ||
395
- (focusLevel === 'field' &&
396
- activeField === 'default' &&
397
- (editorState.props[propIdx]?.type === 'string' || editorState.props[propIdx]?.type === 'token')) ||
398
- editingValue != null;
399
- React.useEffect(() => {
400
- onTextEntryActiveChange?.(textEntryActive);
401
- }, [textEntryActive, onTextEntryActiveChange]);
402
- const currentProp = props[propIdx] ?? null;
403
- const currentSlot = slots[slotIdx] ?? null;
404
- const commit = (next) => {
405
- setEditorState(next);
406
- onChange(serializeState(next, value));
407
- };
408
- const canonicalize = React.useCallback((json) => {
409
- try {
410
- return JSON.stringify(JSON.parse(json));
411
- }
412
- catch {
413
- return `__unparseable__:${json}`;
414
- }
415
- }, []);
416
- const initialStateRef = React.useRef(initialState);
417
- const [baselineCanonical, setBaselineCanonical] = useState(() => canonicalize(serializeState(initialState, value)));
418
- const currentCanonical = canonicalize(serializeState(editorState, value));
419
- const isDirty = currentCanonical !== baselineCanonical;
420
- React.useEffect(() => {
421
- onDirtyChange?.(isDirty);
422
- }, [isDirty, onDirtyChange]);
423
- const lastDiscardTriggerRef = React.useRef(discardTrigger);
424
- React.useEffect(() => {
425
- if (discardTrigger === undefined)
426
- return;
427
- if (discardTrigger === lastDiscardTriggerRef.current)
428
- return;
429
- lastDiscardTriggerRef.current = discardTrigger;
430
- setEditorState(initialStateRef.current);
431
- onChange(serializeState(initialStateRef.current, value));
432
- }, [discardTrigger, onChange, value]);
433
- useImmediateInput((input, key) => {
434
- if (!active)
435
- return;
436
- if (showHelp) {
437
- if (input === '?' || key.escape) {
438
- setShowHelp(false);
439
- }
440
- return;
441
- }
442
- const inDescriptionTextEntryForHelp = focusLevel === 'field' && activeField === 'description';
443
- const inStringDefaultTextEntryForHelp = focusLevel === 'field' &&
444
- activeField === 'default' &&
445
- currentProp != null &&
446
- (currentProp.type === 'string' || currentProp.type === 'token');
447
- const inValueListTextEntry = editingValue != null;
448
- if (input === '?' &&
449
- !key.ctrl &&
450
- !key.meta &&
451
- !inDescriptionTextEntryForHelp &&
452
- !inStringDefaultTextEntryForHelp &&
453
- !inValueListTextEntry) {
454
- setShowHelp(true);
455
- return;
456
- }
457
- const isPropValuesEntry = editingValue && currentProp && activeField === 'values' && !inSlots;
458
- const isSlotAllowedEntry = editingValue && currentSlot && activeField === 'allowedComponents' && inSlots;
459
- const slotAddCandidates = isSlotAllowedEntry && editingValue?.mode === 'add' && projectSlotGraph && currentSlot && currentComponentName
460
- ? computeAllowedComponentCandidates(projectSlotGraph, currentComponentName, slots, currentSlot.name)
461
- : null;
462
- const filteredCandidates = slotAddCandidates
463
- ? valueText.length === 0
464
- ? slotAddCandidates
465
- : slotAddCandidates.filter((n) => n.toLowerCase().includes(valueText.toLowerCase()))
466
- : null;
467
- if (isPropValuesEntry || isSlotAllowedEntry) {
468
- const vals = isPropValuesEntry ? currentProp.values : currentSlot.allowedComponents;
469
- const pickerActive = isSlotAllowedEntry && editingValue?.mode === 'add' && slotAddCandidates !== null;
470
- if (pickerActive && (key.upArrow || key.downArrow)) {
471
- const len = filteredCandidates.length;
472
- if (len > 0) {
473
- setPickerCursor((c) => (key.upArrow ? (c - 1 + len) % len : (c + 1) % len));
474
- }
475
- return;
476
- }
477
- if (key.return) {
478
- let chosen = null;
479
- if (pickerActive && filteredCandidates && filteredCandidates.length > 0) {
480
- if (valueText.trim() === '') {
481
- chosen = filteredCandidates[pickerCursor % filteredCandidates.length] ?? null;
482
- }
483
- else {
484
- const exact = filteredCandidates.find((n) => n === valueText.trim());
485
- if (exact)
486
- chosen = exact;
487
- }
488
- }
489
- const trimmed = chosen ?? valueText.trim();
490
- if (trimmed) {
491
- if (isSlotAllowedEntry &&
492
- editingValue?.mode === 'add' &&
493
- projectSlotGraph &&
494
- currentSlot &&
495
- currentComponentName &&
496
- chosen === null) {
497
- if (trimmed === currentComponentName) {
498
- setValidationError(`"${trimmed}" cannot be added to its own slot (self-loop).`);
499
- return;
500
- }
501
- const baseline = findSlotCycles(simulateGraphWithCandidate(projectSlotGraph, currentComponentName, slots, '', ''));
502
- const next = findSlotCycles(simulateGraphWithCandidate(projectSlotGraph, currentComponentName, slots, currentSlot.name, trimmed));
503
- if (introducesNewCycle(baseline, next)) {
504
- setValidationError(`Adding "${trimmed}" to slot "${currentSlot.name}" would create a slot-dependency cycle. Choose a different component.`);
505
- return;
506
- }
507
- }
508
- let nextVals;
509
- let cursorAfter;
510
- if (editingValue.mode === 'add') {
511
- nextVals = [...vals, trimmed];
512
- cursorAfter = nextVals.length - 1;
513
- }
514
- else {
515
- const idx = editingValue.index ?? 0;
516
- nextVals = vals.map((v, i) => (i === idx ? trimmed : v));
517
- cursorAfter = idx;
518
- }
519
- if (isPropValuesEntry) {
520
- const nextProps = props.map((p, i) => (i === propIdx ? { ...p, values: nextVals } : p));
521
- commit({ ...editorState, props: nextProps });
522
- }
523
- else {
524
- const nextSlots = slots.map((s, i) => (i === slotIdx ? { ...s, allowedComponents: nextVals } : s));
525
- commit({ ...editorState, slots: nextSlots });
526
- }
527
- setValueCursor(cursorAfter);
528
- setValidationError(null);
529
- }
530
- setEditingValue(null);
531
- setValueText('');
532
- setPickerCursor(0);
533
- return;
534
- }
535
- if (key.escape) {
536
- setEditingValue(null);
537
- setValueText('');
538
- setPickerCursor(0);
539
- setValidationError(null);
540
- return;
541
- }
542
- if (key.backspace) {
543
- setValueText((t) => t.slice(0, -1));
544
- setPickerCursor(0);
545
- return;
546
- }
547
- if (input && input.length === 1 && !key.ctrl && !key.meta) {
548
- setValueText((t) => t + input);
549
- setPickerCursor(0);
550
- return;
551
- }
552
- return;
553
- }
554
- if (key.ctrl && input === 's') {
555
- setValidationError(null);
556
- const canonicalNow = canonicalize(serializeState(editorState, value));
557
- setBaselineCanonical(canonicalNow);
558
- initialStateRef.current = editorState;
559
- onSave();
560
- return;
561
- }
562
- const inDescriptionTextEntry = focusLevel === 'field' && activeField === 'description';
563
- const inComponentDescTextEntry = focusLevel === 'field' && inComponentDesc && activeField === 'description';
564
- if (input === 's' && !key.ctrl && !key.meta && !inDescriptionTextEntry && !inComponentDescTextEntry) {
565
- if (onToggleSourceExternal) {
566
- onToggleSourceExternal();
567
- return;
568
- }
569
- setRationaleOpen(false);
570
- setRationaleScrollOffset(() => 0);
571
- setSourceOpen((o) => !o);
572
- return;
573
- }
574
- if (rationaleOpen && !onTogglePropRationale) {
575
- const PANEL_HEIGHT = 12;
576
- const next = computeNextScrollOffset(rationaleScrollOffset, input, key, 9999, PANEL_HEIGHT);
577
- if (next !== null) {
578
- setRationaleScrollOffset(() => next);
579
- return;
580
- }
581
- if (input === 'i' || key.escape) {
582
- setRationaleOpen(false);
583
- setRationaleScrollOffset(() => 0);
584
- return;
585
- }
586
- return;
587
- }
588
- const inStringDefaultTextEntry = focusLevel === 'field' &&
589
- activeField === 'default' &&
590
- currentProp != null &&
591
- (currentProp.type === 'string' || currentProp.type === 'token');
592
- const rationaleKeyAllowed = !key.ctrl &&
593
- !key.meta &&
594
- !inDescriptionTextEntry &&
595
- !inComponentDescTextEntry &&
596
- !inStringDefaultTextEntry &&
597
- !inValueListTextEntry;
598
- if (input === propRationaleKey && rationaleKeyAllowed && onTogglePropRationale) {
599
- onTogglePropRationale();
600
- return;
601
- }
602
- if (input === 'i' && rationaleKeyAllowed && !onTogglePropRationale) {
603
- setSourceOpen(false);
604
- setRationaleScrollOffset(() => 0);
605
- setRationaleOpen((o) => !o);
606
- return;
607
- }
608
- if (input === componentRationaleKey && rationaleKeyAllowed && onToggleComponentRationale) {
609
- onToggleComponentRationale();
610
- return;
611
- }
612
- if (key.escape && sourceOpen) {
613
- setSourceOpen(false);
614
- return;
615
- }
616
- if (key.escape) {
617
- if (focusLevel === 'field') {
618
- if (inComponentDesc)
619
- setFocusLevel('componentDescription');
620
- else
621
- setFocusLevel(inSlots ? 'slot' : 'prop');
622
- setActiveField(null);
623
- return;
624
- }
625
- if (onExit) {
626
- onExit();
627
- }
628
- else {
629
- onDiscard();
630
- }
631
- return;
632
- }
633
- if (focusLevel === 'prop') {
634
- if (key.upArrow || input === 'k') {
635
- if (propIdx > 0) {
636
- setPropIdx(propIdx - 1);
637
- }
638
- else {
639
- setFocusLevel('componentDescription');
640
- setInSlots(false);
641
- setInComponentDesc(true);
642
- }
643
- return;
644
- }
645
- if (key.downArrow || input === 'j') {
646
- if (propIdx < props.length - 1) {
647
- setPropIdx(propIdx + 1);
648
- }
649
- else if (slots.length > 0) {
650
- setInSlots(true);
651
- setSlotIdx(0);
652
- setFocusLevel('slot');
653
- }
654
- return;
655
- }
656
- if (key.return && currentProp) {
657
- setFocusLevel('field');
658
- setActiveField(propFields(currentProp)[0] ?? null);
659
- setTextCursor(currentProp.description.length);
660
- return;
661
- }
662
- return;
663
- }
664
- if (focusLevel === 'componentDescription') {
665
- if (key.upArrow || input === 'k') {
666
- return;
667
- }
668
- if (key.downArrow || input === 'j') {
669
- if (props.length > 0) {
670
- setFocusLevel('prop');
671
- setPropIdx(0);
672
- setInSlots(false);
673
- setInComponentDesc(false);
674
- }
675
- else if (slots.length > 0) {
676
- setFocusLevel('slot');
677
- setSlotIdx(0);
678
- setInSlots(true);
679
- setInComponentDesc(false);
680
- }
681
- return;
682
- }
683
- if (key.return) {
684
- setFocusLevel('field');
685
- setActiveField('description');
686
- setTextCursor(editorState.componentDescription.length);
687
- return;
688
- }
689
- return;
690
- }
691
- if (focusLevel === 'slot') {
692
- if (key.upArrow || input === 'k') {
693
- if (slotIdx > 0) {
694
- setSlotIdx(slotIdx - 1);
695
- }
696
- else if (props.length > 0) {
697
- setInSlots(false);
698
- setPropIdx(props.length - 1);
699
- setFocusLevel('prop');
700
- }
701
- return;
702
- }
703
- if (key.downArrow || input === 'j') {
704
- if (slotIdx < slots.length - 1) {
705
- setSlotIdx(slotIdx + 1);
706
- }
707
- return;
708
- }
709
- if (key.return && currentSlot) {
710
- setFocusLevel('field');
711
- setActiveField(SLOT_FIELDS[0] ?? null);
712
- setTextCursor(currentSlot.description.length);
713
- return;
714
- }
715
- return;
716
- }
717
- if (focusLevel === 'field') {
718
- const fields = inSlots ? SLOT_FIELDS : currentProp ? propFields(currentProp) : [];
719
- const currentFieldIdx = fields.indexOf(activeField);
720
- const isDescriptionTextEntry = activeField === 'description';
721
- const isDefaultTextEntry = activeField === 'default' &&
722
- currentProp != null &&
723
- (currentProp.type === 'string' || currentProp.type === 'token');
724
- const isValuesNav = activeField === 'values' || activeField === 'allowedComponents';
725
- const arrowUp = key.upArrow;
726
- const arrowDown = key.downArrow;
727
- if (isValuesNav) {
728
- const vals = activeField === 'values' && currentProp
729
- ? currentProp.values
730
- : activeField === 'allowedComponents' && currentSlot
731
- ? currentSlot.allowedComponents
732
- : null;
733
- if (vals !== null) {
734
- // Move the value cursor within the list. At a boundary, fall through to
735
- // field navigation so the field itself is escapable (e.g. reach the
736
- // slot `description` field past `allowedComponents`).
737
- if (arrowUp || input === 'k') {
738
- if (valueCursor > 0) {
739
- setValueCursor((c) => Math.max(0, c - 1));
740
- return;
741
- }
742
- }
743
- else if (arrowDown || input === 'j') {
744
- if (valueCursor < vals.length - 1) {
745
- setValueCursor((c) => Math.min(vals.length - 1, c + 1));
746
- return;
747
- }
748
- }
749
- }
750
- }
751
- {
752
- const navUp = arrowUp || (!isDescriptionTextEntry && !isDefaultTextEntry && input === 'k');
753
- const navDown = arrowDown || (!isDescriptionTextEntry && !isDefaultTextEntry && input === 'j');
754
- if ((navUp || navDown) && fields.length > 0) {
755
- const lastIdx = fields.length - 1;
756
- const targetIdx = navDown
757
- ? currentFieldIdx >= lastIdx
758
- ? 0
759
- : currentFieldIdx + 1
760
- : currentFieldIdx <= 0
761
- ? lastIdx
762
- : currentFieldIdx - 1;
763
- const next = fields[targetIdx];
764
- setActiveField(next);
765
- if (next === 'description') {
766
- const desc = inSlots ? (currentSlot?.description ?? '') : (currentProp?.description ?? '');
767
- setTextCursor(desc.length);
768
- }
769
- else if (next === 'default' && currentProp) {
770
- const cur = typeof currentProp.default === 'string' ? currentProp.default : '';
771
- setTextCursor(cur.length);
772
- }
773
- else if (next === 'values' || next === 'allowedComponents') {
774
- const nextVals = next === 'values' && currentProp
775
- ? currentProp.values
776
- : next === 'allowedComponents' && currentSlot
777
- ? currentSlot.allowedComponents
778
- : [];
779
- // Land at the entry edge matching travel direction so continued
780
- // nav moves through the list before escaping the field again.
781
- setValueCursor(navDown ? 0 : Math.max(0, nextVals.length - 1));
782
- }
783
- return;
784
- }
785
- }
786
- if (activeField === 'type' && (key.leftArrow || key.rightArrow) && currentProp) {
787
- const options = CDF_PROPERTY_TYPES;
788
- const idx = options.indexOf(currentProp.type);
789
- const next = key.leftArrow
790
- ? options[(idx - 1 + options.length) % options.length]
791
- : options[(idx + 1) % options.length];
792
- const updated = { ...currentProp, type: next };
793
- if (next !== 'token')
794
- updated.tokenKind = '';
795
- if (next !== 'enum')
796
- updated.values = [];
797
- const nextProps = props.map((p, i) => (i === propIdx ? updated : p));
798
- commit({ ...editorState, props: nextProps });
799
- return;
800
- }
801
- if (activeField === 'category' && (key.leftArrow || key.rightArrow) && currentProp) {
802
- const options = CDF_PROPERTY_CATEGORIES;
803
- const idx = options.indexOf(currentProp.category);
804
- const next = key.leftArrow
805
- ? options[(idx - 1 + options.length) % options.length]
806
- : options[(idx + 1) % options.length];
807
- const nextProps = props.map((p, i) => (i === propIdx ? { ...p, category: next } : p));
808
- commit({ ...editorState, props: nextProps });
809
- return;
810
- }
811
- if (activeField === 'tokenKind' && (key.leftArrow || key.rightArrow) && currentProp) {
812
- const options = DESIGN_TOKEN_TYPES;
813
- const cur = currentProp.tokenKind || options[0];
814
- const idx = options.indexOf(cur);
815
- const next = key.leftArrow
816
- ? options[(idx - 1 + options.length) % options.length]
817
- : options[(idx + 1) % options.length];
818
- const nextProps = props.map((p, i) => (i === propIdx ? { ...p, tokenKind: next } : p));
819
- commit({ ...editorState, props: nextProps });
820
- return;
821
- }
822
- if (activeField === 'required' && (key.return || input === ' ')) {
823
- if (inSlots && currentSlot) {
824
- const nextSlots = slots.map((s, i) => (i === slotIdx ? { ...s, required: !s.required } : s));
825
- commit({ ...editorState, slots: nextSlots });
826
- }
827
- else if (currentProp) {
828
- const nextProps = props.map((p, i) => (i === propIdx ? { ...p, required: !p.required } : p));
829
- commit({ ...editorState, props: nextProps });
830
- }
831
- return;
832
- }
833
- if (activeField === 'default' && currentProp) {
834
- const setProp = (next) => {
835
- const nextProps = props.map((p, i) => (i === propIdx ? next : p));
836
- commit({ ...editorState, props: nextProps });
837
- };
838
- if (currentProp.type === 'boolean' && (key.leftArrow || key.rightArrow)) {
839
- const cycle = [null, true, false];
840
- const curIdx = cycle.findIndex((v) => v === currentProp.default);
841
- const idx = curIdx < 0 ? 0 : curIdx;
842
- const nextIdx = key.rightArrow ? (idx + 1) % cycle.length : (idx - 1 + cycle.length) % cycle.length;
843
- setProp({ ...currentProp, default: cycle[nextIdx] });
844
- return;
845
- }
846
- if (currentProp.type === 'enum' && (key.leftArrow || key.rightArrow)) {
847
- const opts = [null, ...currentProp.values];
848
- const cur = typeof currentProp.default === 'string' ? currentProp.default : null;
849
- const curIdx = opts.findIndex((v) => v === cur);
850
- const idx = curIdx < 0 ? 0 : curIdx;
851
- const nextIdx = key.rightArrow ? (idx + 1) % opts.length : (idx - 1 + opts.length) % opts.length;
852
- setProp({ ...currentProp, default: opts[nextIdx] });
853
- return;
854
- }
855
- if (currentProp.type === 'string' || currentProp.type === 'token') {
856
- const cur = typeof currentProp.default === 'string' ? currentProp.default : '';
857
- const setVal = (next) => setProp({ ...currentProp, default: next === '' ? null : next });
858
- if (key.leftArrow) {
859
- setTextCursor((c) => Math.max(0, c - 1));
860
- return;
861
- }
862
- if (key.rightArrow) {
863
- setTextCursor((c) => Math.min(cur.length, c + 1));
864
- return;
865
- }
866
- if (key.backspace) {
867
- if (textCursor > 0) {
868
- setVal(cur.slice(0, textCursor - 1) + cur.slice(textCursor));
869
- setTextCursor((c) => c - 1);
870
- }
871
- return;
872
- }
873
- if (key.delete) {
874
- if (textCursor < cur.length)
875
- setVal(cur.slice(0, textCursor) + cur.slice(textCursor + 1));
876
- return;
877
- }
878
- if (input && input.length === 1 && !key.ctrl && !key.meta && !key.return) {
879
- setVal(cur.slice(0, textCursor) + input + cur.slice(textCursor));
880
- setTextCursor((c) => c + 1);
881
- return;
882
- }
883
- return;
884
- }
885
- return;
886
- }
887
- if (activeField === 'description') {
888
- const getDesc = () => inComponentDesc
889
- ? editorState.componentDescription
890
- : inSlots
891
- ? (currentSlot?.description ?? '')
892
- : (currentProp?.description ?? '');
893
- const setDesc = (next) => {
894
- if (inComponentDesc) {
895
- commit({ ...editorState, componentDescription: next });
896
- }
897
- else if (inSlots && currentSlot) {
898
- const nextSlots = slots.map((s, i) => (i === slotIdx ? { ...s, description: next } : s));
899
- commit({ ...editorState, slots: nextSlots });
900
- }
901
- else if (currentProp) {
902
- const nextProps = props.map((p, i) => (i === propIdx ? { ...p, description: next } : p));
903
- commit({ ...editorState, props: nextProps });
904
- }
905
- };
906
- const desc = getDesc();
907
- if (key.leftArrow) {
908
- setTextCursor((c) => Math.max(0, c - 1));
909
- return;
910
- }
911
- if (key.rightArrow) {
912
- setTextCursor((c) => Math.min(desc.length, c + 1));
913
- return;
914
- }
915
- if (input === '\x1b[H' || input === '\x1b[1~') {
916
- setTextCursor(0);
917
- return;
918
- }
919
- if (input === '\x1b[F' || input === '\x1b[4~') {
920
- setTextCursor(desc.length);
921
- return;
922
- }
923
- if (key.backspace) {
924
- if (textCursor > 0) {
925
- setDesc(desc.slice(0, textCursor - 1) + desc.slice(textCursor));
926
- setTextCursor((c) => c - 1);
927
- }
928
- return;
929
- }
930
- if (key.delete) {
931
- if (textCursor < desc.length)
932
- setDesc(desc.slice(0, textCursor) + desc.slice(textCursor + 1));
933
- return;
934
- }
935
- if (input && input.length === 1 && !key.ctrl && !key.meta && !key.return) {
936
- setDesc(desc.slice(0, textCursor) + input + desc.slice(textCursor));
937
- setTextCursor((c) => c + 1);
938
- return;
939
- }
940
- return;
941
- }
942
- if (activeField === 'allowedComponents' && currentSlot) {
943
- const vals = currentSlot.allowedComponents;
944
- const setSlotVals = (next) => {
945
- const nextSlots = slots.map((s, i) => (i === slotIdx ? { ...s, allowedComponents: next } : s));
946
- commit({ ...editorState, slots: nextSlots });
947
- };
948
- if ((key.leftArrow || key.rightArrow || input === 'h' || input === 'l') &&
949
- vals.length > 0 &&
950
- projectSlotGraph &&
951
- currentComponentName) {
952
- const candidates = computeAllowedComponentReplacementCandidates(projectSlotGraph, currentComponentName, slots, currentSlot.name, valueCursor);
953
- if (candidates.length === 0) {
954
- setValidationError('no other valid components for this position');
955
- return;
956
- }
957
- const current = vals[valueCursor] ?? '';
958
- const curIdx = candidates.indexOf(current);
959
- const forward = key.rightArrow || input === 'l';
960
- const baseIdx = curIdx < 0 ? (forward ? -1 : 0) : curIdx;
961
- const nextIdx = forward
962
- ? (baseIdx + 1) % candidates.length
963
- : (baseIdx - 1 + candidates.length) % candidates.length;
964
- const next = candidates[nextIdx];
965
- if (next === undefined || next === current)
966
- return;
967
- const nextVals = vals.map((v, i) => (i === valueCursor ? next : v));
968
- setSlotVals(nextVals);
969
- setValidationError(null);
970
- return;
971
- }
972
- if (input === 'a') {
973
- setEditingValue({ mode: 'add' });
974
- setValueText('');
975
- return;
976
- }
977
- if (input === 'e' && vals.length > 0) {
978
- setEditingValue({ mode: 'edit', index: valueCursor });
979
- setValueText(vals[valueCursor] ?? '');
980
- return;
981
- }
982
- if (input === 'r' && vals.length > 0) {
983
- const nextVals = vals.filter((_, i) => i !== valueCursor);
984
- setSlotVals(nextVals);
985
- setValueCursor((c) => Math.max(0, Math.min(c, nextVals.length - 1)));
986
- return;
987
- }
988
- if (input === 'K' && valueCursor > 0) {
989
- const nextVals = [...vals];
990
- [nextVals[valueCursor - 1], nextVals[valueCursor]] = [nextVals[valueCursor], nextVals[valueCursor - 1]];
991
- setSlotVals(nextVals);
992
- setValueCursor((c) => c - 1);
993
- return;
994
- }
995
- if (input === 'J' && valueCursor < vals.length - 1) {
996
- const nextVals = [...vals];
997
- [nextVals[valueCursor], nextVals[valueCursor + 1]] = [nextVals[valueCursor + 1], nextVals[valueCursor]];
998
- setSlotVals(nextVals);
999
- setValueCursor((c) => c + 1);
1000
- return;
1001
- }
1002
- return;
1003
- }
1004
- if (activeField === 'values' && currentProp) {
1005
- const vals = currentProp.values;
1006
- if (input === 'a') {
1007
- setEditingValue({ mode: 'add' });
1008
- setValueText('');
1009
- return;
1010
- }
1011
- if (input === 'e' && vals.length > 0) {
1012
- setEditingValue({ mode: 'edit', index: valueCursor });
1013
- setValueText(vals[valueCursor] ?? '');
1014
- return;
1015
- }
1016
- if (input === 'r' && vals.length > 0) {
1017
- const nextVals = vals.filter((_, i) => i !== valueCursor);
1018
- const nextProps = props.map((p, i) => (i === propIdx ? { ...p, values: nextVals } : p));
1019
- commit({ ...editorState, props: nextProps });
1020
- setValueCursor((c) => Math.max(0, Math.min(c, nextVals.length - 1)));
1021
- return;
1022
- }
1023
- if (input === 'K' && valueCursor > 0) {
1024
- const nextVals = [...vals];
1025
- [nextVals[valueCursor - 1], nextVals[valueCursor]] = [nextVals[valueCursor], nextVals[valueCursor - 1]];
1026
- const nextProps = props.map((p, i) => (i === propIdx ? { ...p, values: nextVals } : p));
1027
- commit({ ...editorState, props: nextProps });
1028
- setValueCursor((c) => c - 1);
1029
- return;
1030
- }
1031
- if (input === 'J' && valueCursor < vals.length - 1) {
1032
- const nextVals = [...vals];
1033
- [nextVals[valueCursor], nextVals[valueCursor + 1]] = [nextVals[valueCursor + 1], nextVals[valueCursor]];
1034
- const nextProps = props.map((p, i) => (i === propIdx ? { ...p, values: nextVals } : p));
1035
- commit({ ...editorState, props: nextProps });
1036
- setValueCursor((c) => c + 1);
1037
- return;
1038
- }
1039
- return;
1040
- }
1041
- return;
1042
- }
1043
- });
1044
- const innerWidth = Math.max(1, width - 2);
1045
- if (parseErr) {
1046
- return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: PALETTE.error, children: [_jsx(Text, { bold: true, color: PALETTE.error, children: "FIELD EDITOR \u2014 parse error" }), _jsx(Text, { color: PALETTE.error, children: parseErr }), _jsx(Text, { dimColor: true, children: "Cannot display structured editor. Fix the JSON first." })] }));
1047
- }
1048
- if (props.length === 0 && slots.length === 0) {
1049
- return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: PALETTE.warning, children: [_jsx(Text, { bold: true, color: PALETTE.warning, children: "FIELD EDITOR \u2014 no fields" }), _jsx(Text, { color: PALETTE.warning, children: "⚠ No properties classified for this component. The LLM didn't find anything to classify." }), _jsx(Text, { dimColor: true, children: "You can add fields manually below or reject this component." }), _jsx(Text, { dimColor: true, children: "Ctrl+S to save \u00B7 Esc to discard" })] }));
1050
- }
1051
- const hasEmptyProperties = props.length === 0 && slots.length === 0;
1052
- const modeLabel = (() => {
1053
- if (editingValue) {
1054
- return editingValue.mode === 'add' ? 'Enter to add · Esc to cancel' : 'Enter to save edit · Esc to cancel';
1055
- }
1056
- if (focusLevel === 'field' && activeField === 'description') {
1057
- return 'Type to edit ←→ cursor ↑↓ cycle field Esc row Ctrl+S save';
1058
- }
1059
- if (focusLevel === 'field' &&
1060
- (activeField === 'type' || activeField === 'category' || activeField === 'tokenKind')) {
1061
- return '←→ cycle value ↑↓/jk cycle field Esc row';
1062
- }
1063
- if (focusLevel === 'field' && activeField === 'required') {
1064
- return 'Space/Enter toggle ↑↓/jk cycle field Esc row';
1065
- }
1066
- if (focusLevel === 'field' && (activeField === 'values' || activeField === 'allowedComponents')) {
1067
- return '[a]dd [e]dit [r]emove ↑↓/jk navigate [K/J] reorder Esc row';
1068
- }
1069
- if (rationaleOpen) {
1070
- return 'jk/Ctrl+u/d scroll i/Esc close rationale panel';
1071
- }
1072
- return `↑↓/jk navigate rows Enter edit fields s source ${propRationaleKey} prop rationale ${componentRationaleKey} component rationale ? help Ctrl+S save Esc exit panel`;
1073
- })();
1074
- const rows = [];
1075
- rows.push({ kind: 'component-description' });
1076
- if (props.length > 0) {
1077
- rows.push({ kind: 'header', label: `── PROPERTIES (${props.length}) `, section: 'properties' });
1078
- props.forEach((_, i) => rows.push({ kind: 'prop', idx: i }));
1079
- }
1080
- if (slots.length > 0) {
1081
- rows.push({ kind: 'header', label: `── SLOTS (${slots.length}) `, section: 'slots' });
1082
- slots.forEach((_, i) => rows.push({ kind: 'slot', idx: i }));
1083
- }
1084
- const selectedRowIdx = rows.findIndex((r) => (r.kind === 'prop' && !inSlots && !inComponentDesc && r.idx === propIdx) ||
1085
- (r.kind === 'slot' && inSlots && r.idx === slotIdx) ||
1086
- (r.kind === 'component-description' && inComponentDesc));
1087
- const visibleRows = Math.max(1, height - 3);
1088
- const scrollStart = selectedRowIdx < 0 ? 0 : Math.max(0, Math.min(selectedRowIdx, rows.length - visibleRows));
1089
- const visibleRowSlice = rows.slice(scrollStart, scrollStart + visibleRows);
1090
- return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: hasEmptyProperties ? PALETTE.warning : PALETTE.info, children: [_jsx(Text, { bold: true, color: hasEmptyProperties ? PALETTE.warning : PALETTE.info, children: 'FIELDS [Ctrl+S save · Esc discard]' }), hasEmptyProperties && (_jsx(Text, { color: PALETTE.warning, children: "⚠ No properties classified for this component. The LLM didn't find anything to classify. Reject this component or add fields manually." })), _jsx(Box, { flexDirection: "column", width: innerWidth, children: visibleRowSlice.map((row, i) => {
1091
- if (row.kind === 'header') {
1092
- return (_jsx(Text, { bold: true, color: row.section === 'slots' ? PALETTE.info : PALETTE.success, children: row.label }, `header-${i}`));
1093
- }
1094
- if (row.kind === 'component-description') {
1095
- const isSelected = inComponentDesc;
1096
- const isEditing = isSelected && focusLevel === 'field' && activeField === 'description';
1097
- const desc = editorState.componentDescription;
1098
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { gap: 1, children: _jsx(Text, { color: isSelected ? PALETTE.inverse : PALETTE.info, bold: isSelected, backgroundColor: isSelected ? 'blue' : undefined, children: ' component-$description ' }) }), isEditing ? (_jsx(Box, { paddingLeft: 2, flexDirection: "row", children: _jsxs(Box, { flexGrow: 1, borderStyle: "round", borderColor: PALETTE.info, paddingX: 1, children: [_jsx(Text, { children: desc.slice(0, textCursor) }), _jsx(Text, { inverse: cursorVisible, children: desc[textCursor] ?? (cursorVisible ? '█' : ' ') }), _jsx(Text, { children: desc.slice(textCursor + 1) })] }) })) : (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: desc ? PALETTE.success : undefined, dimColor: !desc, children: desc || '(none — Return to edit)' }) }))] }, `component-description-${i}`));
1099
- }
1100
- if (row.kind === 'prop') {
1101
- const p = props[row.idx];
1102
- const isSelected = !inSlots && !inComponentDesc && row.idx === propIdx;
1103
- const propMeta = metadata?.props?.[p.name];
1104
- return (_jsx(PropRow, { prop: p, selected: isSelected, activeField: isSelected && focusLevel === 'field' ? activeField : null, textCursor: textCursor, valueCursor: valueCursor, cursorVisible: cursorVisible, editingValue: isSelected ? editingValue : null, valueText: isSelected ? valueText : '', width: innerWidth, rationale: propMeta?.rationale ?? null, rowKey: String(row.idx) }, `prop-${row.idx}`));
1105
- }
1106
- const s = slots[row.idx];
1107
- const isSelected = inSlots && row.idx === slotIdx;
1108
- const slotPickerCandidates = isSelected &&
1109
- editingValue?.mode === 'add' &&
1110
- activeField === 'allowedComponents' &&
1111
- projectSlotGraph &&
1112
- currentComponentName
1113
- ? computeAllowedComponentCandidates(projectSlotGraph, currentComponentName, slots, s.name)
1114
- : null;
1115
- return (_jsx(SlotRow, { slot: s, selected: isSelected, activeField: isSelected && focusLevel === 'field' ? activeField : null, textCursor: textCursor, valueCursor: valueCursor, cursorVisible: cursorVisible, editingValue: isSelected ? editingValue : null, valueText: isSelected ? valueText : '', width: innerWidth, pickerCandidates: slotPickerCandidates, pickerCursor: pickerCursor }, `slot-${row.idx}`));
1116
- }) }), sourceOpen &&
1117
- !onToggleSourceExternal &&
1118
- (() => {
1119
- const propMeta = !inSlots && !inComponentDesc && currentProp ? metadata?.props?.[currentProp.name] : undefined;
1120
- const start = propMeta?.sourceStartLine ?? null;
1121
- const end = propMeta?.sourceEndLine ?? null;
1122
- const path = metadata?.sourcePath ?? null;
1123
- const src = metadata?.componentSource ?? null;
1124
- const headerPath = path ?? '<unknown source path>';
1125
- if (!start || !end || !src) {
1126
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { dimColor: true, bold: true, children: `source: ${headerPath}` }), _jsx(Text, { dimColor: true, children: "(no source location captured for this prop)" }), _jsx(Text, { dimColor: true, children: "[s] close" })] }));
1127
- }
1128
- const lines = src.split('\n').slice(Math.max(0, start - 1), end);
1129
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { dimColor: true, bold: true, children: `${headerPath}: lines ${start}–${end}` }), lines.map((ln, i) => (_jsx(Text, { dimColor: true, children: ln }, `source-line-${i}`))), _jsx(Text, { dimColor: true, children: "[s] close \u00B7 [Esc] close" })] }));
1130
- })(), showHelp && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: PALETTE.info, paddingX: 1, children: [_jsx(Text, { bold: true, color: PALETTE.info, children: "Keybindings" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Row navigation" }), _jsx(Text, { children: ' ↑/↓ or j/k move between rows' }), _jsx(Text, { children: ' Enter edit fields on the current row' }), _jsx(Text, { children: ' Esc exit the panel' }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Field editing" }), _jsx(Text, { children: ' ↑/↓ or j/k cycle through fields (j/k literal in text inputs)' }), _jsx(Text, { children: ' ←/→ cycle picker values · move cursor in text inputs' }), _jsx(Text, { children: ' Space/Enter toggle required' }), _jsx(Text, { children: ' Type edit description / string default' }), _jsx(Text, { children: ' Ctrl+S save changes' }), _jsx(Text, { children: ' Esc exit field-edit back to the row' }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Values / allowedComponents" }), _jsx(Text, { children: ' a / e / r add · edit · remove' }), _jsx(Text, { children: ' ↑↓ or j/k navigate the list' }), _jsx(Text, { children: ' K / J reorder up · down' }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Panels" }), _jsx(Text, { children: ' s toggle source-view for the current prop' }), _jsx(Text, { children: ' ' + propRationaleKey.padEnd(16) + ' toggle prop rationale panel' }), _jsx(Text, { children: ' ' + componentRationaleKey.padEnd(16) + ' toggle component rationale panel' }), _jsx(Text, { children: ' ? toggle this overlay' }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "press ? or Esc to close" })] })), validationError && _jsx(Text, { color: PALETTE.error, children: '✗ ' + validationError }), _jsx(Text, { dimColor: true, children: modeLabel })] }));
1131
- }