@visulima/vis 1.0.0-alpha.10 → 1.0.0-alpha.11

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 (120) hide show
  1. package/CHANGELOG.md +95 -42
  2. package/LICENSE.md +213 -0
  3. package/README.md +8 -4
  4. package/dist/bin.js +9 -1
  5. package/dist/config/index.d.ts +1818 -0
  6. package/dist/config/index.js +2 -0
  7. package/dist/generate/index.d.ts +1 -1
  8. package/dist/generate/index.js +3 -1
  9. package/dist/packem_chunks/applyDefaults.js +336 -0
  10. package/dist/packem_chunks/bin.js +9554 -64
  11. package/dist/packem_chunks/doctor-probe.js +112 -0
  12. package/dist/packem_chunks/fix.js +229 -48
  13. package/dist/packem_chunks/handler.js +99 -1
  14. package/dist/packem_chunks/handler10.js +53 -1
  15. package/dist/packem_chunks/handler11.js +32 -1
  16. package/dist/packem_chunks/handler12.js +100 -2
  17. package/dist/packem_chunks/handler13.js +25 -1
  18. package/dist/packem_chunks/handler14.js +916 -5
  19. package/dist/packem_chunks/handler15.js +206 -1
  20. package/dist/packem_chunks/handler16.js +122 -18
  21. package/dist/packem_chunks/handler17.js +13 -1
  22. package/dist/packem_chunks/handler18.js +106 -1
  23. package/dist/packem_chunks/handler19.js +19 -1
  24. package/dist/packem_chunks/handler2.js +75 -1
  25. package/dist/packem_chunks/handler20.js +29 -1
  26. package/dist/packem_chunks/handler21.js +222 -1
  27. package/dist/packem_chunks/handler22.js +237 -5
  28. package/dist/packem_chunks/handler23.js +101 -1
  29. package/dist/packem_chunks/handler24.js +110 -1
  30. package/dist/packem_chunks/handler25.js +402 -5
  31. package/dist/packem_chunks/handler26.js +13 -1
  32. package/dist/packem_chunks/handler27.js +63 -3
  33. package/dist/packem_chunks/handler28.js +34 -1
  34. package/dist/packem_chunks/handler29.js +458 -7
  35. package/dist/packem_chunks/handler3.js +95 -2
  36. package/dist/packem_chunks/handler30.js +168 -21
  37. package/dist/packem_chunks/handler31.js +530 -3
  38. package/dist/packem_chunks/handler32.js +214 -2
  39. package/dist/packem_chunks/handler33.js +119 -24
  40. package/dist/packem_chunks/handler34.js +630 -2
  41. package/dist/packem_chunks/handler35.js +283 -19
  42. package/dist/packem_chunks/handler36.js +521 -407
  43. package/dist/packem_chunks/handler37.js +762 -22
  44. package/dist/packem_chunks/handler38.js +989 -22
  45. package/dist/packem_chunks/handler39.js +574 -22
  46. package/dist/packem_chunks/handler4.js +90 -4
  47. package/dist/packem_chunks/handler40.js +1685 -3
  48. package/dist/packem_chunks/handler41.js +1088 -10
  49. package/dist/packem_chunks/handler42.js +785 -141
  50. package/dist/packem_chunks/handler43.js +2658 -42
  51. package/dist/packem_chunks/handler44.js +3886 -3
  52. package/dist/packem_chunks/handler45.js +2568 -21
  53. package/dist/packem_chunks/handler46.js +3769 -0
  54. package/dist/packem_chunks/handler47.js +1491 -0
  55. package/dist/packem_chunks/handler5.js +174 -2
  56. package/dist/packem_chunks/handler6.js +95 -13
  57. package/dist/packem_chunks/handler7.js +115 -8
  58. package/dist/packem_chunks/handler8.js +12 -1
  59. package/dist/packem_chunks/handler9.js +29 -1
  60. package/dist/packem_chunks/heal-accept.js +522 -0
  61. package/dist/packem_chunks/heal.js +673 -0
  62. package/dist/packem_chunks/index.js +873 -7
  63. package/dist/packem_chunks/loader.js +23 -1
  64. package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +1316 -0
  65. package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +5 -0
  66. package/dist/packem_shared/ai-analysis-CHeB1joD.js +367 -0
  67. package/dist/packem_shared/ai-cache-Be_jexe4.js +142 -0
  68. package/dist/packem_shared/ai-fix-B9iQVcD2.js +379 -0
  69. package/dist/packem_shared/cache-directory-2qvs4goY.js +98 -0
  70. package/dist/packem_shared/catalog-BJTtyi-O.js +1371 -0
  71. package/dist/packem_shared/dependency-scan-A0KSklpG.js +188 -0
  72. package/dist/packem_shared/docker-2iZzc280.js +181 -0
  73. package/dist/packem_shared/failure-log-Cz3Z4SKL.js +100 -0
  74. package/dist/packem_shared/flakiness-goTxXuCX.js +180 -0
  75. package/dist/packem_shared/otel-DCvqCTz_.js +158 -0
  76. package/dist/packem_shared/otelPlugin-DFaLDvJf.js +3 -0
  77. package/dist/packem_shared/registry-CbqXI0rc.js +272 -0
  78. package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +130 -0
  79. package/dist/packem_shared/runtime-check-Cobi3p6l.js +127 -0
  80. package/dist/packem_shared/selectors-SM69TfqC.js +194 -0
  81. package/dist/packem_shared/symbols-Ta7g2nU-.js +14 -0
  82. package/dist/packem_shared/toolchain-BdZd9eBi.js +975 -0
  83. package/dist/packem_shared/typosquats-C-bCh3PX.js +1210 -0
  84. package/dist/packem_shared/use-measured-height-CNP0vT4M.js +20 -0
  85. package/dist/packem_shared/utils-CthVdBPS.js +40 -0
  86. package/dist/packem_shared/xxh3-Ck8mXNg1.js +239 -0
  87. package/index.js +727 -555
  88. package/package.json +35 -17
  89. package/schemas/project.schema.json +8 -10
  90. package/schemas/vis-config.schema.json +132 -8
  91. package/skills/vis/SKILL.md +96 -0
  92. package/templates/buildkite-ci/.buildkite/pipeline.yml.tera +85 -0
  93. package/templates/buildkite-ci/template.yml +20 -0
  94. package/dist/errors/index.d.ts +0 -26
  95. package/dist/errors/index.js +0 -1
  96. package/dist/packem_chunks/config.js +0 -2
  97. package/dist/packem_shared/VisConfigCycleError-CAYNC7d-.js +0 -1
  98. package/dist/packem_shared/VisConfigError-B5LP1zRf.js +0 -1
  99. package/dist/packem_shared/VisConfigLoadError-CeqBSd2Z.js +0 -2
  100. package/dist/packem_shared/VisConfigNotFoundError-DZ9KC527.js +0 -5
  101. package/dist/packem_shared/VisUpdateApp-D-L4_-Iu.js +0 -1
  102. package/dist/packem_shared/_commonjsHelpers-D6W6KoPK.js +0 -1
  103. package/dist/packem_shared/ai-analysis-CGuy7dfE.js +0 -67
  104. package/dist/packem_shared/ai-cache-Bynt6Y9x.js +0 -1
  105. package/dist/packem_shared/cache-directory-D72ZEag2.js +0 -1
  106. package/dist/packem_shared/catalog-BVPerCwG.js +0 -12
  107. package/dist/packem_shared/dependency-scan-Du0tBu64.js +0 -2
  108. package/dist/packem_shared/docker-BcfqH4Av.js +0 -2
  109. package/dist/packem_shared/failure-log-DqYen0LC.js +0 -2
  110. package/dist/packem_shared/flakiness-DSIHZGBT.js +0 -1
  111. package/dist/packem_shared/run-summary-utils-C24Aaf9E.js +0 -1
  112. package/dist/packem_shared/runtime-check-CGHal8SO.js +0 -1
  113. package/dist/packem_shared/selectors-CfH9ZY08.js +0 -3
  114. package/dist/packem_shared/symbols-CQmER5MT.js +0 -1
  115. package/dist/packem_shared/target-merge-DNa-6eWu.js +0 -1
  116. package/dist/packem_shared/toolchain-DQfTQY8E.js +0 -5
  117. package/dist/packem_shared/typosquats-DOR8izpX.js +0 -1
  118. package/dist/packem_shared/use-measured-height-DjYgUOKk.js +0 -1
  119. package/dist/packem_shared/utils-DrNg0XTR.js +0 -1
  120. package/dist/packem_shared/xxh3-DrAUNq4n.js +0 -1
@@ -0,0 +1,1316 @@
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
+ import { useWindowSize, Box, Spinner, Text, ScrollView, Tabs, Tab, ScrollBar, useApp, useInput, Dialog } from '@visulima/tui';
3
+ import { useSyncExternalStore, useState, useRef, useMemo, useCallback, useEffect } from 'react';
4
+ import { j as scoreColor, _ as QuitDialog } from '../packem_chunks/bin.js';
5
+ import { u as useMeasuredHeight } from './use-measured-height-CNP0vT4M.js';
6
+
7
+ function CheckProgressApp({ current, total }) {
8
+ const { columns: termColumns } = useWindowSize();
9
+ const cols = termColumns || 80;
10
+ const percent = total > 0 ? Math.min(1, current / total) : 0;
11
+ const pctText = `${String(Math.round(percent * 100)).padStart(3)}%`;
12
+ const counter = `${String(current)}/${String(total)}`;
13
+ const barWidth = Math.max(10, cols - 2 - pctText.length - 1);
14
+ const filled = Math.round(barWidth * percent);
15
+ const empty = barWidth - filled;
16
+ return jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
17
+ jsxs(Box, { children: [
18
+ jsx(Spinner, { type: "dots" }),
19
+ jsx(Text, { children: " Checking catalog dependencies " }),
20
+ jsx(Text, { dimColor: true, children: counter })
21
+ ] }),
22
+ jsxs(Box, { children: [
23
+ jsx(Text, { color: "cyan", children: "━".repeat(filled) }),
24
+ jsx(Text, { dimColor: true, children: "─".repeat(empty) }),
25
+ jsxs(Text, { dimColor: true, children: [
26
+ " ",
27
+ pctText
28
+ ] })
29
+ ] })
30
+ ] });
31
+ }
32
+
33
+ const groupByCatalog = (entries) => {
34
+ const map = /* @__PURE__ */ new Map();
35
+ for (const entry of entries) {
36
+ const group = map.get(entry.catalogName);
37
+ if (group) {
38
+ group.push(entry);
39
+ } else {
40
+ map.set(entry.catalogName, [entry]);
41
+ }
42
+ }
43
+ return map;
44
+ };
45
+ const filterEntries = (entries, filterType, filterText) => {
46
+ let filtered = entries;
47
+ if (filterType !== "all") {
48
+ filtered = filterType === "security" ? filtered.filter((e) => e.vulnerabilities && e.vulnerabilities.length > 0 || e.socketReport && e.socketReport.alerts.length > 0) : filtered.filter((e) => e.updateType === filterType);
49
+ }
50
+ if (filterText) {
51
+ const lower = filterText.toLowerCase();
52
+ filtered = filtered.filter((e) => e.packageName.toLowerCase().includes(lower));
53
+ }
54
+ return filtered;
55
+ };
56
+ class UpdateStore {
57
+ #state;
58
+ #listeners = /* @__PURE__ */ new Set();
59
+ #allEntries;
60
+ #recommendationMap = null;
61
+ constructor(entries, aiResult = null) {
62
+ this.#allEntries = entries;
63
+ if (aiResult) {
64
+ this.#recommendationMap = new Map(aiResult.recommendations.map((r) => [r.package, r]));
65
+ }
66
+ this.#state = {
67
+ aiResult,
68
+ allChecked: true,
69
+ applyProgress: null,
70
+ checkedEntries: new Set(entries.map((e) => e.packageName)),
71
+ entries,
72
+ error: null,
73
+ filterActive: false,
74
+ filterText: "",
75
+ filterType: "all",
76
+ focusedPanel: "list",
77
+ groupedByCatalog: groupByCatalog(entries),
78
+ phase: "browsing",
79
+ selectedIndex: 0
80
+ };
81
+ }
82
+ // ── React integration ───────────────────────────────────────────
83
+ getSnapshot = () => this.#state;
84
+ subscribe = (listener) => {
85
+ this.#listeners.add(listener);
86
+ return () => {
87
+ this.#listeners.delete(listener);
88
+ };
89
+ };
90
+ // ── Derived data ────────────────────────────────────────────────
91
+ /** Get the currently filtered + visible entries. */
92
+ getFilteredEntries() {
93
+ return filterEntries(this.#allEntries, this.#state.filterType, this.#state.filterText);
94
+ }
95
+ /** Get AI recommendation for a specific package. */
96
+ getRecommendation(packageName) {
97
+ return this.#recommendationMap?.get(packageName);
98
+ }
99
+ /** Get the list of checked entries (for apply). */
100
+ getCheckedEntries() {
101
+ return this.#allEntries.filter((e) => this.#state.checkedEntries.has(e.packageName));
102
+ }
103
+ // ── Navigation ──────────────────────────────────────────────────
104
+ setSelectedIndex(index) {
105
+ const filtered = this.getFilteredEntries();
106
+ const clamped = Math.max(0, Math.min(index, filtered.length - 1));
107
+ if (clamped !== this.#state.selectedIndex) {
108
+ this.#emit({ ...this.#state, selectedIndex: clamped });
109
+ }
110
+ }
111
+ setFocusedPanel(panel) {
112
+ if (panel !== this.#state.focusedPanel) {
113
+ this.#emit({ ...this.#state, focusedPanel: panel });
114
+ }
115
+ }
116
+ // ── Filtering ───────────────────────────────────────────────────
117
+ setFilterType(type) {
118
+ if (type !== this.#state.filterType) {
119
+ const newEntries = filterEntries(this.#allEntries, type, this.#state.filterText);
120
+ this.#emit({
121
+ ...this.#state,
122
+ entries: newEntries,
123
+ filterType: type,
124
+ groupedByCatalog: groupByCatalog(newEntries),
125
+ selectedIndex: 0
126
+ });
127
+ }
128
+ }
129
+ setFilter(text) {
130
+ const newEntries = filterEntries(this.#allEntries, this.#state.filterType, text);
131
+ this.#emit({
132
+ ...this.#state,
133
+ entries: newEntries,
134
+ filterText: text,
135
+ groupedByCatalog: groupByCatalog(newEntries),
136
+ selectedIndex: 0
137
+ });
138
+ }
139
+ setFilterActive(active) {
140
+ if (active !== this.#state.filterActive) {
141
+ if (active) {
142
+ this.#emit({ ...this.#state, filterActive: true });
143
+ } else {
144
+ const newEntries = filterEntries(this.#allEntries, this.#state.filterType, "");
145
+ this.#emit({
146
+ ...this.#state,
147
+ entries: newEntries,
148
+ filterActive: false,
149
+ filterText: "",
150
+ groupedByCatalog: groupByCatalog(newEntries),
151
+ selectedIndex: 0
152
+ });
153
+ }
154
+ }
155
+ }
156
+ // ── Selection ───────────────────────────────────────────────────
157
+ toggleCheck(packageName) {
158
+ const checked = new Set(this.#state.checkedEntries);
159
+ if (checked.has(packageName)) {
160
+ checked.delete(packageName);
161
+ } else {
162
+ checked.add(packageName);
163
+ }
164
+ this.#emit({
165
+ ...this.#state,
166
+ allChecked: checked.size === this.#allEntries.length,
167
+ checkedEntries: checked
168
+ });
169
+ }
170
+ checkAll() {
171
+ this.#emit({
172
+ ...this.#state,
173
+ allChecked: true,
174
+ checkedEntries: new Set(this.#allEntries.map((e) => e.packageName))
175
+ });
176
+ }
177
+ uncheckAll() {
178
+ this.#emit({
179
+ ...this.#state,
180
+ allChecked: false,
181
+ checkedEntries: /* @__PURE__ */ new Set()
182
+ });
183
+ }
184
+ toggleAll() {
185
+ if (this.#state.allChecked) {
186
+ this.uncheckAll();
187
+ } else {
188
+ this.checkAll();
189
+ }
190
+ }
191
+ // ── Apply lifecycle ─────────────────────────────────────────────
192
+ startApply() {
193
+ const checked = this.getCheckedEntries();
194
+ this.#emit({
195
+ ...this.#state,
196
+ applyProgress: { current: 0, total: checked.length },
197
+ phase: "applying"
198
+ });
199
+ }
200
+ updateApplyProgress(current) {
201
+ if (this.#state.applyProgress) {
202
+ this.#emit({
203
+ ...this.#state,
204
+ applyProgress: { ...this.#state.applyProgress, current }
205
+ });
206
+ }
207
+ }
208
+ markDone() {
209
+ this.#emit({ ...this.#state, phase: "done" });
210
+ }
211
+ setError(error) {
212
+ this.#emit({ ...this.#state, error, phase: "error" });
213
+ }
214
+ // ── Internal ────────────────────────────────────────────────────
215
+ #emit(newState) {
216
+ this.#state = newState;
217
+ for (const listener of this.#listeners) {
218
+ try {
219
+ listener();
220
+ } catch {
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ const UPDATE_TYPE_COLORS$1 = {
227
+ major: "red",
228
+ minor: "yellow",
229
+ patch: "green"
230
+ };
231
+ const SEVERITY_COLORS = {
232
+ CRITICAL: "red",
233
+ HIGH: "red",
234
+ LOW: "gray",
235
+ MODERATE: "yellow",
236
+ UNKNOWN: "gray"
237
+ };
238
+ const SOCKET_SEVERITY_COLORS = {
239
+ critical: "red",
240
+ high: "red",
241
+ low: "gray",
242
+ medium: "yellow"
243
+ };
244
+ const RISK_COLORS = {
245
+ critical: "red",
246
+ high: "red",
247
+ low: "green",
248
+ medium: "yellow"
249
+ };
250
+ const ACTION_COLORS = {
251
+ defer: "gray",
252
+ review: "yellow",
253
+ skip: "red",
254
+ update: "green"
255
+ };
256
+ const PackageDetailPanel = ({ changelogUrl, entry, focused, recommendation, scrollRef }) => {
257
+ const borderColor = focused ? "white" : "gray";
258
+ if (!entry) {
259
+ return jsx(Box, { alignItems: "center", borderColor: "gray", borderStyle: "single", flexDirection: "column", flexGrow: 1, justifyContent: "center", children: jsx(Text, { dimColor: true, children: "No package selected" }) });
260
+ }
261
+ const typeColor = UPDATE_TYPE_COLORS$1[entry.updateType] ?? "white";
262
+ const hasVulnerabilities = entry.vulnerabilities && entry.vulnerabilities.length > 0;
263
+ const socketScore = entry.socketReport?.score.overall ?? 0;
264
+ const socketScoreColor = entry.socketReport ? scoreColor(socketScore) : "gray";
265
+ return jsxs(Box, { borderColor, borderStyle: "single", flexDirection: "column", flexGrow: 1, children: [
266
+ jsx(Box, { flexShrink: 0, paddingTop: 1, paddingX: 2, children: jsx(Text, { bold: true, color: "white", children: entry.packageName }) }),
267
+ jsxs(ScrollView, { flexGrow: 1, flexShrink: 1, paddingX: 2, ref: scrollRef, scrollbar: true, scrollbarColor: "gray", scrollbarStyle: "block", children: [
268
+ jsx(Text, {}),
269
+ jsxs(Box, { children: [
270
+ jsx(Box, { width: 12, children: jsx(Text, { dimColor: true, children: "Current:" }) }),
271
+ jsx(Text, { children: entry.currentRange })
272
+ ] }),
273
+ jsxs(Box, { children: [
274
+ jsx(Box, { width: 12, children: jsx(Text, { dimColor: true, children: "Target:" }) }),
275
+ jsx(Text, { children: entry.newRange }),
276
+ jsxs(Text, { bold: true, color: typeColor, children: [
277
+ " ",
278
+ "(",
279
+ entry.updateType,
280
+ ")"
281
+ ] })
282
+ ] }),
283
+ jsxs(Box, { children: [
284
+ jsx(Box, { width: 12, children: jsx(Text, { dimColor: true, children: "Version:" }) }),
285
+ jsx(Text, { children: entry.targetVersion })
286
+ ] }),
287
+ jsxs(Box, { children: [
288
+ jsx(Box, { width: 12, children: jsx(Text, { dimColor: true, children: "Catalog:" }) }),
289
+ jsx(Text, { children: entry.catalogName })
290
+ ] }),
291
+ entry.acceptedRisk && jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
292
+ jsx(Text, { color: "gray", children: "── " }),
293
+ jsx(Text, { bold: true, color: "gray", children: "ACKNOWLEDGED RISK" }),
294
+ jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [
295
+ jsxs(Box, { children: [
296
+ jsx(Text, { dimColor: true, children: "Reason: " }),
297
+ jsx(Text, { children: entry.acceptedRisk.reason })
298
+ ] }),
299
+ jsxs(Box, { children: [
300
+ jsx(Text, { dimColor: true, children: "Accepted: " }),
301
+ jsx(Text, { children: entry.acceptedRisk.acceptedAt.slice(0, 10) })
302
+ ] })
303
+ ] })
304
+ ] }),
305
+ hasVulnerabilities && jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
306
+ jsx(Text, { dimColor: true, children: "── " }),
307
+ jsx(Text, { bold: true, color: "red", children: "SECURITY" }),
308
+ jsx(Text, {}),
309
+ entry.vulnerabilities.map((vuln) => jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
310
+ jsxs(Box, { gap: 1, children: [
311
+ jsxs(Text, { bold: true, color: SEVERITY_COLORS[vuln.severity] ?? "gray", children: [
312
+ "⚠",
313
+ " ",
314
+ vuln.severity
315
+ ] }),
316
+ jsx(Text, { bold: true, children: vuln.id })
317
+ ] }),
318
+ jsx(Box, { paddingLeft: 2, children: jsx(Text, { children: vuln.summary }) }),
319
+ jsxs(Box, { gap: 2, paddingLeft: 2, children: [
320
+ vuln.cvssScore !== void 0 && jsxs(Text, { dimColor: true, children: [
321
+ "CVSS:",
322
+ String(vuln.cvssScore)
323
+ ] }),
324
+ vuln.fixedVersions.length > 0 && jsxs(Text, { dimColor: true, children: [
325
+ "Fixed in:",
326
+ vuln.fixedVersions.join(", ")
327
+ ] })
328
+ ] })
329
+ ] }, vuln.id))
330
+ ] }),
331
+ entry.socketReport && jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
332
+ jsx(Text, { dimColor: true, children: "── " }),
333
+ jsx(Text, { bold: true, color: "cyan", children: "SOCKET.DEV" }),
334
+ jsx(Text, {}),
335
+ jsxs(Box, { gap: 2, children: [
336
+ jsxs(Box, { children: [
337
+ jsx(Text, { dimColor: true, children: "Overall: " }),
338
+ jsxs(Text, { bold: true, color: socketScoreColor, children: [
339
+ String(Math.round(socketScore * 100)),
340
+ "%"
341
+ ] })
342
+ ] }),
343
+ jsxs(Box, { children: [
344
+ jsx(Text, { dimColor: true, children: "Supply Chain: " }),
345
+ jsxs(Text, { children: [
346
+ String(Math.round(entry.socketReport.score.supplyChain * 100)),
347
+ "%"
348
+ ] })
349
+ ] }),
350
+ jsxs(Box, { children: [
351
+ jsx(Text, { dimColor: true, children: "Quality: " }),
352
+ jsxs(Text, { children: [
353
+ String(Math.round(entry.socketReport.score.quality * 100)),
354
+ "%"
355
+ ] })
356
+ ] })
357
+ ] }),
358
+ jsxs(Box, { gap: 2, children: [
359
+ jsxs(Box, { children: [
360
+ jsx(Text, { dimColor: true, children: "Maintenance: " }),
361
+ jsxs(Text, { children: [
362
+ String(Math.round(entry.socketReport.score.maintenance * 100)),
363
+ "%"
364
+ ] })
365
+ ] }),
366
+ jsxs(Box, { children: [
367
+ jsx(Text, { dimColor: true, children: "Vulnerability: " }),
368
+ jsxs(Text, { children: [
369
+ String(Math.round(entry.socketReport.score.vulnerability * 100)),
370
+ "%"
371
+ ] })
372
+ ] }),
373
+ jsxs(Box, { children: [
374
+ jsx(Text, { dimColor: true, children: "License: " }),
375
+ jsxs(Text, { children: [
376
+ entry.socketReport.license || "unknown",
377
+ " (",
378
+ String(Math.round(entry.socketReport.score.license * 100)),
379
+ "%)"
380
+ ] })
381
+ ] })
382
+ ] }),
383
+ entry.socketReport.alerts.length > 0 && jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
384
+ jsxs(Text, { bold: true, color: "yellow", children: [
385
+ "⚠",
386
+ " ",
387
+ String(entry.socketReport.alerts.length),
388
+ " alert",
389
+ entry.socketReport.alerts.length === 1 ? "" : "s",
390
+ ":"
391
+ ] }),
392
+ entry.socketReport.alerts.map((alert) => jsxs(Box, { gap: 1, paddingLeft: 2, children: [
393
+ jsxs(Text, { bold: true, color: SOCKET_SEVERITY_COLORS[alert.severity] ?? "gray", children: [
394
+ "[",
395
+ alert.severity.toUpperCase(),
396
+ "]"
397
+ ] }),
398
+ jsx(Text, { children: alert.type }),
399
+ jsxs(Text, { dimColor: true, children: [
400
+ "(",
401
+ alert.category,
402
+ ")"
403
+ ] })
404
+ ] }, alert.key))
405
+ ] })
406
+ ] }),
407
+ recommendation && jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
408
+ jsx(Text, { dimColor: true, children: "── " }),
409
+ jsx(Text, { bold: true, color: "white", children: "AI ANALYSIS" }),
410
+ jsx(Text, {}),
411
+ jsxs(Box, { gap: 2, children: [
412
+ jsxs(Box, { children: [
413
+ jsx(Text, { dimColor: true, children: "Action: " }),
414
+ jsx(Text, { bold: true, color: ACTION_COLORS[recommendation.action] ?? "white", children: recommendation.action })
415
+ ] }),
416
+ jsxs(Box, { children: [
417
+ jsx(Text, { dimColor: true, children: "Risk: " }),
418
+ jsx(Text, { bold: true, color: RISK_COLORS[recommendation.riskLevel] ?? "white", children: recommendation.riskLevel })
419
+ ] }),
420
+ jsxs(Box, { children: [
421
+ jsx(Text, { dimColor: true, children: "Effort: " }),
422
+ jsx(Text, { bold: true, children: recommendation.effort })
423
+ ] })
424
+ ] }),
425
+ recommendation.reason && jsx(Box, { marginTop: 1, paddingLeft: 2, children: jsx(Text, { children: recommendation.reason }) }),
426
+ recommendation.breakingChanges.length > 0 && jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, children: [
427
+ jsx(Text, { bold: true, color: "yellow", children: "Breaking changes:" }),
428
+ recommendation.breakingChanges.map((change, i) => jsxs(Text, { children: [
429
+ " ",
430
+ "•",
431
+ " ",
432
+ change
433
+ ] }, String(i)))
434
+ ] })
435
+ ] }),
436
+ changelogUrl && jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
437
+ jsx(Text, { dimColor: true, children: "── " }),
438
+ jsx(Text, { bold: true, color: "white", children: "CHANGELOG" }),
439
+ jsx(Box, { marginTop: 1, paddingLeft: 2, children: jsx(Text, { color: "cyan", underline: true, children: changelogUrl }) })
440
+ ] }),
441
+ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
442
+ jsx(Text, { dimColor: true, children: "── " }),
443
+ jsx(Text, { bold: true, color: "white", children: "LINKS" }),
444
+ jsx(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, children: jsxs(Text, { color: "cyan", underline: true, children: [
445
+ "https://npmx.dev/",
446
+ entry.packageName
447
+ ] }) })
448
+ ] }),
449
+ !recommendation && jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
450
+ jsx(Text, { dimColor: true, children: "── " }),
451
+ jsx(Text, { bold: true, color: "white", children: "GUIDANCE" }),
452
+ jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, children: [
453
+ entry.updateType === "major" && jsxs(Fragment, { children: [
454
+ jsxs(Text, { color: "red", children: [
455
+ "⚠",
456
+ " Major update — likely contains breaking changes."
457
+ ] }),
458
+ jsx(Text, { dimColor: true, children: " Review the changelog before updating." }),
459
+ jsx(Text, { dimColor: true, children: " Use --changelog to fetch release URLs." })
460
+ ] }),
461
+ entry.updateType === "minor" && jsxs(Fragment, { children: [
462
+ jsxs(Text, { color: "yellow", children: [
463
+ "ℹ",
464
+ " Minor update — new features, backward compatible."
465
+ ] }),
466
+ jsx(Text, { dimColor: true, children: " Generally safe to update." })
467
+ ] }),
468
+ entry.updateType === "patch" && jsxs(Fragment, { children: [
469
+ jsxs(Text, { color: "green", children: [
470
+ "✓",
471
+ " Patch update — bug fixes only."
472
+ ] }),
473
+ jsx(Text, { dimColor: true, children: " Safe to update." })
474
+ ] }),
475
+ !recommendation && jsx(Text, { dimColor: true, children: " Use --ai to get AI-powered analysis." })
476
+ ] })
477
+ ] })
478
+ ] })
479
+ ] });
480
+ };
481
+
482
+ const UPDATE_TYPE_COLORS = {
483
+ major: "red",
484
+ minor: "yellow",
485
+ patch: "green"
486
+ };
487
+ const FILTER_TABS = [
488
+ { id: "all", label: "All" },
489
+ { id: "major", label: "Major" },
490
+ { id: "minor", label: "Minor" },
491
+ { id: "patch", label: "Patch" },
492
+ { id: "security", label: "Security" }
493
+ ];
494
+ const PackageRow = ({ checked, entry, isSelected }) => {
495
+ const typeColor = UPDATE_TYPE_COLORS[entry.updateType] ?? "white";
496
+ const hasSecurity = entry.vulnerabilities && entry.vulnerabilities.length > 0;
497
+ const hasSocketAlerts = entry.socketReport && entry.socketReport.alerts.length > 0;
498
+ const isAcknowledged = Boolean(entry.acceptedRisk);
499
+ const checkbox = checked ? "☑" : "☐";
500
+ const scoreText = entry.socketReport ? `${String(Math.round(entry.socketReport.score.overall * 100))}%` : "";
501
+ const scoreColorName = entry.socketReport ? scoreColor(entry.socketReport.score.overall) : "gray";
502
+ return jsxs(Box, { flexShrink: 0, height: 1, children: [
503
+ jsx(Text, { children: isSelected ? ">" : " " }),
504
+ jsxs(Text, { color: checked ? "white" : "gray", children: [
505
+ " ",
506
+ checkbox,
507
+ " "
508
+ ] }),
509
+ hasSecurity || hasSocketAlerts ? jsx(Text, { color: isAcknowledged ? "gray" : "red", children: isAcknowledged ? "✓ " : "⚠ " }) : jsx(Text, { children: " " }),
510
+ jsx(Box, { flexGrow: 1, children: jsxs(Text, { bold: isSelected, inverse: isSelected, wrap: "truncate", children: [
511
+ entry.packageName,
512
+ isAcknowledged ? " [ack]" : ""
513
+ ] }) }),
514
+ scoreText && jsxs(Text, { color: scoreColorName, children: [
515
+ " ",
516
+ scoreText
517
+ ] }),
518
+ jsxs(Text, { dimColor: true, children: [
519
+ " ",
520
+ entry.currentRange
521
+ ] }),
522
+ jsxs(Text, { dimColor: true, children: [
523
+ " ",
524
+ "→",
525
+ " "
526
+ ] }),
527
+ jsxs(Text, { children: [
528
+ entry.newRange,
529
+ " "
530
+ ] }),
531
+ jsx(Text, { bold: true, color: typeColor, children: entry.updateType })
532
+ ] });
533
+ };
534
+ const CatalogHeader = ({ count, name }) => jsxs(Box, { flexShrink: 0, height: 1, marginTop: 1, children: [
535
+ jsxs(Text, { dimColor: true, children: [
536
+ "▼",
537
+ " "
538
+ ] }),
539
+ jsx(Text, { bold: true, color: "white", children: name.toUpperCase() }),
540
+ jsxs(Text, { dimColor: true, children: [
541
+ " (",
542
+ count,
543
+ ")"
544
+ ] })
545
+ ] });
546
+ const PackageListPanel = ({
547
+ checkedEntries,
548
+ entries,
549
+ filterActive,
550
+ filteredOutCount,
551
+ filterText,
552
+ filterType,
553
+ focused,
554
+ groupedByCatalog,
555
+ isDryRun,
556
+ onViewportHeightChange,
557
+ scrollOffset,
558
+ selectedIndex,
559
+ totalCatalogEntries,
560
+ totalChecked,
561
+ totalEntries,
562
+ viewportHeight
563
+ }) => {
564
+ const borderColor = focused ? "white" : "gray";
565
+ const { measuredHeight: measuredViewportHeight, ref: contentRowRef } = useMeasuredHeight(viewportHeight, onViewportHeightChange);
566
+ let majors = 0;
567
+ let minors = 0;
568
+ let patches = 0;
569
+ let secCount = 0;
570
+ for (const e of entries) {
571
+ if (e.updateType === "major") {
572
+ majors++;
573
+ } else if (e.updateType === "minor") {
574
+ minors++;
575
+ } else {
576
+ patches++;
577
+ }
578
+ if (e.vulnerabilities && e.vulnerabilities.length > 0 || e.socketReport && e.socketReport.alerts.length > 0) {
579
+ secCount++;
580
+ }
581
+ }
582
+ const summaryParts = [];
583
+ if (majors > 0) {
584
+ summaryParts.push(`${majors} major`);
585
+ }
586
+ if (minors > 0) {
587
+ summaryParts.push(`${minors} minor`);
588
+ }
589
+ if (patches > 0) {
590
+ summaryParts.push(`${patches} patch`);
591
+ }
592
+ if (secCount > 0) {
593
+ summaryParts.push(`${secCount} vulnerable`);
594
+ }
595
+ const summaryText = summaryParts.length > 0 ? ` (${summaryParts.join(", ")})` : "";
596
+ let checkedCount = 0;
597
+ for (const e of entries) {
598
+ if (checkedEntries.has(e.packageName)) {
599
+ checkedCount++;
600
+ }
601
+ }
602
+ const rows = [];
603
+ let flatIndex = 0;
604
+ for (const [catalogName, catalogEntries] of groupedByCatalog) {
605
+ rows.push(jsx(CatalogHeader, { count: catalogEntries.length, name: catalogName }, `hdr-${catalogName}`));
606
+ for (const entry of catalogEntries) {
607
+ const currentFlatIndex = flatIndex;
608
+ rows.push(
609
+ jsx(
610
+ PackageRow,
611
+ {
612
+ checked: checkedEntries.has(entry.packageName),
613
+ entry,
614
+ isSelected: currentFlatIndex === selectedIndex
615
+ },
616
+ entry.packageName
617
+ )
618
+ );
619
+ flatIndex++;
620
+ }
621
+ }
622
+ let contentHeight = 0;
623
+ for (const [, catalogEntries] of groupedByCatalog) {
624
+ contentHeight += 2 + catalogEntries.length;
625
+ }
626
+ const showScrollbar = contentHeight > measuredViewportHeight && measuredViewportHeight > 0;
627
+ return jsxs(Box, { borderColor, borderStyle: "single", flexDirection: "column", flexGrow: 1, children: [
628
+ jsxs(Box, { flexShrink: 0, gap: 1, paddingX: 1, children: [
629
+ jsx(Text, { bold: true, inverse: true, children: " VIS " }),
630
+ jsxs(Text, { wrap: "truncate", children: [
631
+ totalEntries,
632
+ totalChecked > 0 ? `/${totalChecked}` : "",
633
+ " outdated",
634
+ summaryText,
635
+ totalCatalogEntries > totalChecked ? ` · ${totalCatalogEntries - totalChecked} dupes` : ""
636
+ ] }),
637
+ !isDryRun && checkedCount > 0 && jsxs(Text, { dimColor: true, children: [
638
+ " —",
639
+ checkedCount,
640
+ " selected"
641
+ ] })
642
+ ] }),
643
+ jsx(Box, { flexShrink: 0, paddingX: 1, paddingY: 1, children: jsx(
644
+ Tabs,
645
+ {
646
+ isFocused: focused,
647
+ keyMap: { next: [], previous: [], useNumbers: false, useTab: false },
648
+ onChange: () => {
649
+ },
650
+ showIndex: false,
651
+ value: filterType,
652
+ children: FILTER_TABS.map(({ id, label }) => jsx(Tab, { name: id, children: label }, id))
653
+ }
654
+ ) }),
655
+ filterActive && jsxs(Box, { flexShrink: 0, paddingX: 1, children: [
656
+ jsx(Text, { bold: true, color: "white", children: "/ " }),
657
+ jsx(Text, { children: filterText }),
658
+ jsx(Text, { inverse: true, children: " " })
659
+ ] }),
660
+ filteredOutCount > 0 && jsx(Box, { flexShrink: 0, paddingX: 1, children: jsxs(Text, { color: "yellow", children: [
661
+ "⚠",
662
+ " ",
663
+ filteredOutCount,
664
+ " package",
665
+ filteredOutCount === 1 ? "" : "s",
666
+ " filtered out by target constraint — press",
667
+ " ",
668
+ jsx(Text, { bold: true, color: "white", children: "f" }),
669
+ " ",
670
+ "to view"
671
+ ] }) }),
672
+ jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", ref: contentRowRef, children: [
673
+ jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", paddingLeft: 1, children: jsx(Box, { flexDirection: "column", marginTop: -scrollOffset, children: rows }) }),
674
+ showScrollbar && jsx(Box, { flexShrink: 0, marginLeft: 1, marginRight: 1, children: jsx(
675
+ ScrollBar,
676
+ {
677
+ contentHeight,
678
+ placement: "inset",
679
+ scrollOffset,
680
+ style: "block",
681
+ viewportHeight: measuredViewportHeight
682
+ }
683
+ ) })
684
+ ] }, `list-${filterType}-${filterText}`)
685
+ ] });
686
+ };
687
+
688
+ const MIN_HORIZONTAL_WIDTH = 100;
689
+ const MIN_VIEWPORT_WIDTH = 40;
690
+ const MIN_VIEWPORT_HEIGHT = 10;
691
+ const EMPTY_ENTRIES = [];
692
+ const VisUpdateApp = ({
693
+ autoExitSeconds = 0,
694
+ changelogUrls,
695
+ checkedCount = 0,
696
+ filteredOutEntries = EMPTY_ENTRIES,
697
+ isDryRun,
698
+ store,
699
+ totalCatalogEntries = 0
700
+ }) => {
701
+ const { exit } = useApp();
702
+ const { columns, rows } = useWindowSize();
703
+ const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
704
+ const [helpVisible, setHelpVisible] = useState(false);
705
+ const [filteredOutVisible, setFilteredOutVisible] = useState(false);
706
+ const helpScrollRef = useRef(null);
707
+ const filteredOutScrollRef = useRef(null);
708
+ const detailScrollRef = useRef(null);
709
+ const confirmScrollRef = useRef(null);
710
+ const [listScrollOffset, setListScrollOffset] = useState(0);
711
+ const [confirmVisible, setConfirmVisible] = useState(false);
712
+ const [quitDialogVisible, setQuitDialogVisible] = useState(false);
713
+ const filteredEntries = useMemo(() => store.getFilteredEntries(), [state.entries, state.filterType, state.filterText]);
714
+ const selectedEntry = filteredEntries[state.selectedIndex] ?? null;
715
+ const selectedRecommendation = selectedEntry ? store.getRecommendation(selectedEntry.packageName) : void 0;
716
+ const selectedChangelog = selectedEntry && changelogUrls ? changelogUrls.get(selectedEntry.packageName) : void 0;
717
+ const getRowForIndex = useCallback(
718
+ (index) => {
719
+ let row = 0;
720
+ let count = 0;
721
+ for (const [, catalogEntries] of state.groupedByCatalog) {
722
+ row += 2;
723
+ for (let i = 0; i < catalogEntries.length; i++) {
724
+ if (count === index) {
725
+ return row;
726
+ }
727
+ row += 1;
728
+ count++;
729
+ }
730
+ }
731
+ return row;
732
+ },
733
+ [state.groupedByCatalog]
734
+ );
735
+ const estimatedViewportHeight = Math.max(1, rows - 8 - (state.filterActive ? 1 : 0));
736
+ const [measuredViewportHeight, setMeasuredViewportHeight] = useState(estimatedViewportHeight);
737
+ const listViewportHeight = measuredViewportHeight > 0 ? measuredViewportHeight : estimatedViewportHeight;
738
+ const scrollToIndex = useCallback(
739
+ (index) => {
740
+ const targetRow = getRowForIndex(index);
741
+ setListScrollOffset((current) => {
742
+ if (targetRow > current + listViewportHeight - 2) {
743
+ return Math.max(0, targetRow - listViewportHeight + 2);
744
+ }
745
+ if (targetRow < current + 1) {
746
+ return Math.max(0, targetRow - 1);
747
+ }
748
+ return current;
749
+ });
750
+ },
751
+ [getRowForIndex, listViewportHeight]
752
+ );
753
+ useEffect(() => {
754
+ detailScrollRef.current?.scrollToTop();
755
+ }, [selectedEntry?.packageName]);
756
+ useInput(
757
+ (input, key) => {
758
+ if (input === "c" && key.ctrl) {
759
+ exit();
760
+ return;
761
+ }
762
+ if (quitDialogVisible) {
763
+ return;
764
+ }
765
+ if (filteredOutVisible) {
766
+ if (key.escape || input === "f" || input === "q") {
767
+ setFilteredOutVisible(false);
768
+ } else if (key.downArrow || input === "j") {
769
+ filteredOutScrollRef.current?.scrollBy(1);
770
+ } else if (key.upArrow || input === "k") {
771
+ filteredOutScrollRef.current?.scrollBy(-1);
772
+ }
773
+ return;
774
+ }
775
+ if (confirmVisible) {
776
+ if (input === "u" || key.return) {
777
+ setConfirmVisible(false);
778
+ store.startApply();
779
+ exit(store.getCheckedEntries());
780
+ } else if (key.escape || input === "q") {
781
+ setConfirmVisible(false);
782
+ } else if (key.downArrow || input === "j") {
783
+ confirmScrollRef.current?.scrollBy(1);
784
+ } else if (key.upArrow || input === "k") {
785
+ confirmScrollRef.current?.scrollBy(-1);
786
+ } else if (key.pageDown) {
787
+ confirmScrollRef.current?.scrollBy(5);
788
+ } else if (key.pageUp) {
789
+ confirmScrollRef.current?.scrollBy(-5);
790
+ }
791
+ return;
792
+ }
793
+ if (helpVisible) {
794
+ if (key.escape || input === "?") {
795
+ setHelpVisible(false);
796
+ } else if (input === "q") {
797
+ setHelpVisible(false);
798
+ setQuitDialogVisible(true);
799
+ } else if (key.downArrow || input === "j") {
800
+ helpScrollRef.current?.scrollBy(1);
801
+ } else if (key.upArrow || input === "k") {
802
+ helpScrollRef.current?.scrollBy(-1);
803
+ }
804
+ return;
805
+ }
806
+ if (input === "?") {
807
+ setHelpVisible(true);
808
+ return;
809
+ }
810
+ if (input === "q") {
811
+ setQuitDialogVisible(true);
812
+ return;
813
+ }
814
+ if (key.tab) {
815
+ store.setFocusedPanel(state.focusedPanel === "list" ? "detail" : "list");
816
+ return;
817
+ }
818
+ if (state.focusedPanel === "list" && (key.leftArrow || key.rightArrow)) {
819
+ const tabs = ["all", "major", "minor", "patch", "security"];
820
+ const currentIndex = tabs.indexOf(state.filterType);
821
+ const nextIndex = key.rightArrow ? (currentIndex + 1) % tabs.length : (currentIndex - 1 + tabs.length) % tabs.length;
822
+ setListScrollOffset(0);
823
+ detailScrollRef.current?.scrollToTop();
824
+ store.setFilterType(tabs[nextIndex]);
825
+ return;
826
+ }
827
+ if (input === "f" && filteredOutEntries.length > 0) {
828
+ setFilteredOutVisible((previous) => !previous);
829
+ return;
830
+ }
831
+ if (state.filterActive) {
832
+ if (key.escape) {
833
+ store.setFilterActive(false);
834
+ return;
835
+ }
836
+ if (key.return) {
837
+ store.setFilterActive(false);
838
+ return;
839
+ }
840
+ if (key.backspace) {
841
+ setListScrollOffset(0);
842
+ store.setFilter(state.filterText.slice(0, -1));
843
+ return;
844
+ }
845
+ if (input && !key.ctrl && !key.meta) {
846
+ setListScrollOffset(0);
847
+ store.setFilter(state.filterText + input);
848
+ return;
849
+ }
850
+ return;
851
+ }
852
+ if (state.focusedPanel === "list") {
853
+ if (key.downArrow || input === "j") {
854
+ const next = Math.min(state.selectedIndex + 1, filteredEntries.length - 1);
855
+ store.setSelectedIndex(next);
856
+ scrollToIndex(next);
857
+ return;
858
+ }
859
+ if (key.upArrow || input === "k") {
860
+ const next = Math.max(state.selectedIndex - 1, 0);
861
+ store.setSelectedIndex(next);
862
+ scrollToIndex(next);
863
+ return;
864
+ }
865
+ if (key.pageDown) {
866
+ const next = Math.min(state.selectedIndex + 10, filteredEntries.length - 1);
867
+ store.setSelectedIndex(next);
868
+ scrollToIndex(next);
869
+ return;
870
+ }
871
+ if (key.pageUp) {
872
+ const next = Math.max(state.selectedIndex - 10, 0);
873
+ store.setSelectedIndex(next);
874
+ scrollToIndex(next);
875
+ return;
876
+ }
877
+ if (key.home) {
878
+ store.setSelectedIndex(0);
879
+ setListScrollOffset(0);
880
+ return;
881
+ }
882
+ if (key.end) {
883
+ const last = filteredEntries.length - 1;
884
+ store.setSelectedIndex(last);
885
+ scrollToIndex(last);
886
+ return;
887
+ }
888
+ if (input === " " || key.return) {
889
+ if (selectedEntry) {
890
+ store.toggleCheck(selectedEntry.packageName);
891
+ }
892
+ return;
893
+ }
894
+ if (input === "a") {
895
+ store.toggleAll();
896
+ return;
897
+ }
898
+ if (input === "/") {
899
+ store.setFilterActive(true);
900
+ return;
901
+ }
902
+ if (input === "u" && !isDryRun && state.checkedEntries.size > 0) {
903
+ setConfirmVisible(true);
904
+ return;
905
+ }
906
+ if (key.rightArrow) {
907
+ store.setFocusedPanel("detail");
908
+ return;
909
+ }
910
+ return;
911
+ }
912
+ if (state.focusedPanel === "detail") {
913
+ if (key.escape || key.leftArrow) {
914
+ store.setFocusedPanel("list");
915
+ return;
916
+ }
917
+ if (key.downArrow || input === "j") {
918
+ detailScrollRef.current?.scrollBy(1);
919
+ return;
920
+ }
921
+ if (key.upArrow || input === "k") {
922
+ detailScrollRef.current?.scrollBy(-1);
923
+ return;
924
+ }
925
+ if (key.pageDown) {
926
+ detailScrollRef.current?.scrollBy(10);
927
+ return;
928
+ }
929
+ if (key.pageUp) {
930
+ detailScrollRef.current?.scrollBy(-10);
931
+ return;
932
+ }
933
+ if (key.home) {
934
+ detailScrollRef.current?.scrollToTop();
935
+ return;
936
+ }
937
+ if (key.end) {
938
+ detailScrollRef.current?.scrollToBottom();
939
+ }
940
+ }
941
+ },
942
+ { isActive: true }
943
+ );
944
+ if (columns < MIN_VIEWPORT_WIDTH || rows < MIN_VIEWPORT_HEIGHT) {
945
+ return jsx(Box, { alignItems: "center", height: rows, justifyContent: "center", width: columns, children: jsxs(Text, { color: "yellow", children: [
946
+ "Terminal too small (",
947
+ columns,
948
+ "x",
949
+ rows,
950
+ ")"
951
+ ] }) });
952
+ }
953
+ const isHorizontal = columns >= MIN_HORIZONTAL_WIDTH;
954
+ const footerItems = [
955
+ jsxs(Box, { gap: 1, children: [
956
+ jsx(Text, { bold: true, color: "white", children: "q" }),
957
+ jsx(Text, { dimColor: true, children: "QUIT" })
958
+ ] }, "q"),
959
+ jsxs(Box, { gap: 1, children: [
960
+ jsx(Text, { bold: true, color: "white", children: "?" }),
961
+ jsx(Text, { dimColor: true, children: "HELP" })
962
+ ] }, "?"),
963
+ jsxs(Box, { gap: 1, children: [
964
+ jsx(Text, { bold: true, color: "white", children: "↑↓" }),
965
+ jsx(Text, { dimColor: true, children: "NAV" })
966
+ ] }, "nav"),
967
+ jsxs(Box, { gap: 1, children: [
968
+ jsx(Text, { bold: true, color: "white", children: "Space" }),
969
+ jsx(Text, { dimColor: true, children: "CHECK" })
970
+ ] }, "sp"),
971
+ jsxs(Box, { gap: 1, children: [
972
+ jsx(Text, { bold: true, color: "white", children: "a" }),
973
+ jsx(Text, { dimColor: true, children: "ALL" })
974
+ ] }, "a")
975
+ ];
976
+ if (!isDryRun && state.checkedEntries.size > 0) {
977
+ footerItems.push(
978
+ jsxs(Box, { gap: 1, children: [
979
+ jsx(Text, { bold: true, color: "green", children: "u" }),
980
+ jsx(Text, { dimColor: true, children: "APPLY" })
981
+ ] }, "u")
982
+ );
983
+ }
984
+ if (filteredOutEntries.length > 0) {
985
+ footerItems.push(
986
+ jsxs(Box, { gap: 1, children: [
987
+ jsx(Text, { bold: true, color: "yellow", children: "f" }),
988
+ jsxs(Text, { dimColor: true, children: [
989
+ "FILTERED (",
990
+ filteredOutEntries.length,
991
+ ")"
992
+ ] })
993
+ ] }, "fo")
994
+ );
995
+ }
996
+ footerItems.push(
997
+ jsxs(Box, { gap: 1, children: [
998
+ jsx(Text, { bold: true, color: "white", children: "←→" }),
999
+ jsx(Text, { dimColor: true, children: "FILTER" })
1000
+ ] }, "lr"),
1001
+ jsxs(Box, { gap: 1, children: [
1002
+ jsx(Text, { bold: true, color: "white", children: "/" }),
1003
+ jsx(Text, { dimColor: true, children: "SEARCH" })
1004
+ ] }, "f"),
1005
+ jsxs(Box, { gap: 1, children: [
1006
+ jsx(Text, { bold: true, color: "white", children: "Tab" }),
1007
+ jsx(Text, { dimColor: true, children: "PANEL" })
1008
+ ] }, "t")
1009
+ );
1010
+ const footer = jsx(Box, { borderBottom: false, borderColor: "gray", borderLeft: false, borderRight: false, borderStyle: "single", flexShrink: 0, children: jsx(Box, { flexWrap: "wrap", gap: 2, paddingX: 1, children: footerItems }) });
1011
+ const helpPopup = jsxs(
1012
+ Dialog,
1013
+ {
1014
+ footer: jsxs(Text, { dimColor: true, children: [
1015
+ jsx(Text, { bold: true, color: "white", children: "↑↓" }),
1016
+ " ",
1017
+ "scroll",
1018
+ " ",
1019
+ jsx(Text, { bold: true, color: "white", children: "?" }),
1020
+ "/",
1021
+ jsx(Text, { bold: true, color: "white", children: "Esc" }),
1022
+ " ",
1023
+ "close"
1024
+ ] }),
1025
+ scrollRef: helpScrollRef,
1026
+ title: "KEYBOARD SHORTCUTS",
1027
+ visible: helpVisible,
1028
+ width: 52,
1029
+ children: [
1030
+ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
1031
+ jsxs(Box, { marginBottom: 1, children: [
1032
+ jsx(Text, { dimColor: true, children: "── " }),
1033
+ jsx(Text, { bold: true, color: "white", children: "NAVIGATION" })
1034
+ ] }),
1035
+ jsxs(Box, { children: [
1036
+ jsx(Box, { width: 24, children: jsxs(Text, { children: [
1037
+ jsxs(Text, { bold: true, color: "white", children: [
1038
+ " ",
1039
+ "↑",
1040
+ "/k"
1041
+ ] }),
1042
+ jsx(Text, { dimColor: true, children: " Move up" })
1043
+ ] }) }),
1044
+ jsxs(Text, { children: [
1045
+ jsxs(Text, { bold: true, color: "white", children: [
1046
+ " ",
1047
+ "↓",
1048
+ "/j"
1049
+ ] }),
1050
+ jsx(Text, { dimColor: true, children: " Move down" })
1051
+ ] })
1052
+ ] }),
1053
+ jsxs(Text, { children: [
1054
+ jsxs(Text, { bold: true, color: "white", children: [
1055
+ " ",
1056
+ "Tab"
1057
+ ] }),
1058
+ jsx(Text, { dimColor: true, children: " Switch panel" })
1059
+ ] }),
1060
+ jsxs(Text, { children: [
1061
+ jsxs(Text, { bold: true, color: "white", children: [
1062
+ " ",
1063
+ "→",
1064
+ "/",
1065
+ "←"
1066
+ ] }),
1067
+ jsx(Text, { dimColor: true, children: " Focus detail/list" })
1068
+ ] })
1069
+ ] }),
1070
+ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
1071
+ jsxs(Box, { marginBottom: 1, children: [
1072
+ jsx(Text, { dimColor: true, children: "── " }),
1073
+ jsx(Text, { bold: true, color: "white", children: "SELECTION" })
1074
+ ] }),
1075
+ jsxs(Text, { children: [
1076
+ jsxs(Text, { bold: true, color: "white", children: [
1077
+ " ",
1078
+ "Space"
1079
+ ] }),
1080
+ jsx(Text, { dimColor: true, children: " Toggle check on package" })
1081
+ ] }),
1082
+ jsxs(Text, { children: [
1083
+ jsxs(Text, { bold: true, color: "white", children: [
1084
+ " ",
1085
+ "a"
1086
+ ] }),
1087
+ jsx(Text, { dimColor: true, children: " Toggle check all" })
1088
+ ] })
1089
+ ] }),
1090
+ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
1091
+ jsxs(Box, { marginBottom: 1, children: [
1092
+ jsx(Text, { dimColor: true, children: "── " }),
1093
+ jsx(Text, { bold: true, color: "white", children: "FILTERS" })
1094
+ ] }),
1095
+ jsxs(Text, { children: [
1096
+ jsxs(Text, { bold: true, color: "white", children: [
1097
+ " ",
1098
+ "←→"
1099
+ ] }),
1100
+ jsx(Text, { dimColor: true, children: " Switch filter tab" })
1101
+ ] }),
1102
+ jsxs(Text, { children: [
1103
+ jsxs(Text, { bold: true, color: "white", children: [
1104
+ " ",
1105
+ "/"
1106
+ ] }),
1107
+ jsx(Text, { dimColor: true, children: " Text filter" })
1108
+ ] }),
1109
+ filteredOutEntries.length > 0 && jsxs(Text, { children: [
1110
+ jsxs(Text, { bold: true, color: "white", children: [
1111
+ " ",
1112
+ "f"
1113
+ ] }),
1114
+ jsx(Text, { dimColor: true, children: " View filtered-out packages" })
1115
+ ] })
1116
+ ] }),
1117
+ jsxs(Box, { flexDirection: "column", children: [
1118
+ jsxs(Box, { marginBottom: 1, children: [
1119
+ jsx(Text, { dimColor: true, children: "── " }),
1120
+ jsx(Text, { bold: true, color: "white", children: "ACTIONS" })
1121
+ ] }),
1122
+ !isDryRun && jsxs(Text, { children: [
1123
+ jsxs(Text, { bold: true, color: "white", children: [
1124
+ " ",
1125
+ "u"
1126
+ ] }),
1127
+ jsx(Text, { dimColor: true, children: " Apply selected updates" })
1128
+ ] }),
1129
+ jsxs(Text, { children: [
1130
+ jsxs(Text, { bold: true, color: "white", children: [
1131
+ " ",
1132
+ "q"
1133
+ ] }),
1134
+ jsx(Text, { dimColor: true, children: " Quit" })
1135
+ ] }),
1136
+ jsxs(Text, { children: [
1137
+ jsxs(Text, { bold: true, color: "white", children: [
1138
+ " ",
1139
+ "?"
1140
+ ] }),
1141
+ jsx(Text, { dimColor: true, children: " Toggle help" })
1142
+ ] })
1143
+ ] })
1144
+ ]
1145
+ }
1146
+ );
1147
+ const checkedList = store.getCheckedEntries();
1148
+ const majorCount = checkedList.filter((e) => e.updateType === "major").length;
1149
+ const confirmFooter = jsxs(Box, { alignItems: "center", flexDirection: "column", children: [
1150
+ majorCount > 0 && jsx(Box, { marginBottom: 1, marginTop: 1, children: jsxs(Text, { color: "yellow", children: [
1151
+ "⚠",
1152
+ " ",
1153
+ majorCount,
1154
+ " major update",
1155
+ majorCount === 1 ? "" : "s",
1156
+ " — review breaking changes"
1157
+ ] }) }),
1158
+ jsxs(Text, { dimColor: true, children: [
1159
+ "Press",
1160
+ " ",
1161
+ jsx(Text, { bold: true, color: "white", children: "u" }),
1162
+ " ",
1163
+ "or",
1164
+ " ",
1165
+ jsx(Text, { bold: true, color: "white", children: "Enter" }),
1166
+ " ",
1167
+ "to confirm,",
1168
+ " ",
1169
+ jsx(Text, { bold: true, color: "white", children: "Esc" }),
1170
+ " ",
1171
+ "to cancel"
1172
+ ] })
1173
+ ] });
1174
+ const confirmDialog = jsx(
1175
+ Dialog,
1176
+ {
1177
+ footer: confirmFooter,
1178
+ scrollRef: confirmScrollRef,
1179
+ title: `Apply ${checkedList.length} update${checkedList.length === 1 ? "" : "s"}?`,
1180
+ visible: confirmVisible,
1181
+ width: 70,
1182
+ children: checkedList.map((e) => jsxs(Box, { gap: 1, children: [
1183
+ jsxs(Text, { children: [
1184
+ " ",
1185
+ e.packageName
1186
+ ] }),
1187
+ jsxs(Text, { dimColor: true, children: [
1188
+ e.currentRange,
1189
+ " ",
1190
+ "→",
1191
+ " ",
1192
+ e.newRange
1193
+ ] }),
1194
+ jsx(Text, { bold: true, color: e.updateType === "major" ? "red" : e.updateType === "minor" ? "yellow" : "green", children: e.updateType })
1195
+ ] }, e.packageName))
1196
+ }
1197
+ );
1198
+ const filteredOutDialog = filteredOutEntries.length > 0 ? jsx(
1199
+ Dialog,
1200
+ {
1201
+ footer: jsxs(Text, { dimColor: true, children: [
1202
+ jsx(Text, { bold: true, color: "white", children: "↑↓" }),
1203
+ " ",
1204
+ "scroll",
1205
+ " ",
1206
+ jsx(Text, { bold: true, color: "white", children: "f" }),
1207
+ "/",
1208
+ jsx(Text, { bold: true, color: "white", children: "Esc" }),
1209
+ " ",
1210
+ "close"
1211
+ ] }),
1212
+ scrollRef: filteredOutScrollRef,
1213
+ title: `${filteredOutEntries.length} PACKAGE${filteredOutEntries.length === 1 ? "" : "S"} FILTERED BY TARGET`,
1214
+ visible: filteredOutVisible,
1215
+ width: 70,
1216
+ children: jsxs(Box, { flexDirection: "column", children: [
1217
+ jsx(Box, { marginBottom: 1, children: jsxs(Text, { dimColor: true, children: [
1218
+ "These packages have newer versions available but are excluded by the current target constraint. Use",
1219
+ " ",
1220
+ jsx(Text, { bold: true, color: "white", children: "--target latest" }),
1221
+ " ",
1222
+ "to include them."
1223
+ ] }) }),
1224
+ filteredOutEntries.map((e) => jsxs(Box, { gap: 1, children: [
1225
+ jsxs(Text, { children: [
1226
+ " ",
1227
+ e.packageName
1228
+ ] }),
1229
+ jsxs(Text, { dimColor: true, children: [
1230
+ e.currentRange,
1231
+ " ",
1232
+ "→",
1233
+ " ",
1234
+ e.newRange
1235
+ ] }),
1236
+ jsx(Text, { bold: true, color: e.updateType === "major" ? "red" : e.updateType === "minor" ? "yellow" : "green", children: e.updateType })
1237
+ ] }, e.packageName))
1238
+ ] })
1239
+ }
1240
+ ) : null;
1241
+ const listPanel = jsx(
1242
+ PackageListPanel,
1243
+ {
1244
+ checkedEntries: state.checkedEntries,
1245
+ entries: filteredEntries,
1246
+ filterActive: state.filterActive,
1247
+ filteredOutCount: filteredOutEntries.length,
1248
+ filterText: state.filterText,
1249
+ filterType: state.filterType,
1250
+ focused: state.focusedPanel === "list",
1251
+ groupedByCatalog: state.groupedByCatalog,
1252
+ isDryRun,
1253
+ onViewportHeightChange: setMeasuredViewportHeight,
1254
+ scrollOffset: listScrollOffset,
1255
+ selectedIndex: state.selectedIndex,
1256
+ totalCatalogEntries,
1257
+ totalChecked: checkedCount,
1258
+ totalEntries: filteredEntries.length,
1259
+ viewportHeight: listViewportHeight
1260
+ }
1261
+ );
1262
+ const detailPanel = jsx(
1263
+ PackageDetailPanel,
1264
+ {
1265
+ changelogUrl: selectedChangelog,
1266
+ entry: selectedEntry,
1267
+ focused: state.focusedPanel === "detail",
1268
+ recommendation: selectedRecommendation,
1269
+ scrollRef: detailScrollRef
1270
+ }
1271
+ );
1272
+ if (isHorizontal) {
1273
+ const detailWidth = Math.floor(columns * 0.35);
1274
+ return jsxs(Box, { flexDirection: "column", height: rows, width: columns, children: [
1275
+ jsxs(Box, { flexDirection: "row", flexGrow: 1, children: [
1276
+ jsx(Box, { flexGrow: 1, children: listPanel }),
1277
+ jsx(Box, { width: detailWidth, children: detailPanel })
1278
+ ] }),
1279
+ footer,
1280
+ confirmDialog,
1281
+ filteredOutDialog,
1282
+ jsx(
1283
+ QuitDialog,
1284
+ {
1285
+ autoExitSeconds: autoExitSeconds || 3,
1286
+ onCancel: () => {
1287
+ setQuitDialogVisible(false);
1288
+ },
1289
+ visible: quitDialogVisible
1290
+ }
1291
+ ),
1292
+ helpPopup
1293
+ ] });
1294
+ }
1295
+ const listHeight = Math.floor(rows * 0.55);
1296
+ return jsxs(Box, { flexDirection: "column", height: rows, width: columns, children: [
1297
+ jsx(Box, { height: listHeight, children: listPanel }),
1298
+ jsx(Box, { flexGrow: 1, children: detailPanel }),
1299
+ footer,
1300
+ confirmDialog,
1301
+ filteredOutDialog,
1302
+ jsx(
1303
+ QuitDialog,
1304
+ {
1305
+ autoExitSeconds: autoExitSeconds || 3,
1306
+ onCancel: () => {
1307
+ setQuitDialogVisible(false);
1308
+ },
1309
+ visible: quitDialogVisible
1310
+ }
1311
+ ),
1312
+ helpPopup
1313
+ ] });
1314
+ };
1315
+
1316
+ export { CheckProgressApp as C, UpdateStore as U, VisUpdateApp as V };