brew-tui 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -66,7 +66,7 @@ import { rm as rm2 } from "fs/promises";
66
66
  import { render } from "ink";
67
67
 
68
68
  // src/app.tsx
69
- import { useEffect as useEffect23, useState as useState20 } from "react";
69
+ import { useEffect as useEffect23, useState as useState21 } from "react";
70
70
  import { useApp } from "ink";
71
71
 
72
72
  // src/components/layout/app-layout.tsx
@@ -74,7 +74,32 @@ import { useRef } from "react";
74
74
  import { Box as Box3 } from "ink";
75
75
 
76
76
  // src/components/layout/header.tsx
77
- import { Box, Text as Text3, useStdout } from "ink";
77
+ import { Box, Text as Text3 } from "ink";
78
+
79
+ // src/hooks/use-terminal-size.ts
80
+ import { useEffect, useState } from "react";
81
+ import { useStdout } from "ink";
82
+ function useTerminalSize() {
83
+ const { stdout } = useStdout();
84
+ const [size, setSize] = useState(() => ({
85
+ columns: stdout?.columns ?? 80,
86
+ rows: stdout?.rows ?? 24
87
+ }));
88
+ useEffect(() => {
89
+ if (!stdout) return;
90
+ const onResize = () => {
91
+ setSize({
92
+ columns: stdout.columns ?? 80,
93
+ rows: stdout.rows ?? 24
94
+ });
95
+ };
96
+ stdout.on("resize", onResize);
97
+ return () => {
98
+ stdout.off("resize", onResize);
99
+ };
100
+ }, [stdout]);
101
+ return size;
102
+ }
78
103
 
79
104
  // src/stores/navigation-store.ts
80
105
  import { create } from "zustand";
@@ -297,7 +322,7 @@ var GRADIENTS = {
297
322
  };
298
323
 
299
324
  // src/components/common/blinking-text.tsx
300
- import { useEffect, useState } from "react";
325
+ import { useEffect as useEffect2, useState as useState2 } from "react";
301
326
  import { Text as Text2 } from "ink";
302
327
  import { jsx as jsx2 } from "react/jsx-runtime";
303
328
  function BlinkingText({
@@ -306,8 +331,8 @@ function BlinkingText({
306
331
  bold = true,
307
332
  children
308
333
  }) {
309
- const [bright, setBright] = useState(true);
310
- useEffect(() => {
334
+ const [bright, setBright] = useState2(true);
335
+ useEffect2(() => {
311
336
  const id = setInterval(() => setBright((b) => !b), intervalMs);
312
337
  return () => clearInterval(id);
313
338
  }, [intervalMs]);
@@ -324,6 +349,20 @@ var SPACING = {
324
349
  xl: 6,
325
350
  xxl: 8
326
351
  };
352
+ var BREAKPOINTS = {
353
+ /** Below this we collapse to single-column rows (only the name fits). */
354
+ narrow: 50,
355
+ /** Below this we drop the description column but keep version. */
356
+ mid: 80,
357
+ /** At or above this we render every column comfortably. */
358
+ wide: 120
359
+ };
360
+ function getLayoutMode(columns) {
361
+ if (columns < BREAKPOINTS.narrow) return "single";
362
+ if (columns < BREAKPOINTS.mid) return "compact";
363
+ if (columns < BREAKPOINTS.wide) return "comfortable";
364
+ return "wide";
365
+ }
327
366
 
328
367
  // src/components/layout/header.tsx
329
368
  import { jsx as jsx3, jsxs } from "react/jsx-runtime";
@@ -389,9 +428,11 @@ function Header() {
389
428
  const menuMode = useNavigationStore((s) => s.menuMode);
390
429
  const menuCursor = useNavigationStore((s) => s.menuCursor);
391
430
  useLocaleStore((s) => s.locale);
392
- const { stdout } = useStdout();
393
- const cols = stdout?.columns ?? 80;
394
- const isNarrow = cols < 95;
431
+ const { columns, rows } = useTerminalSize();
432
+ const isNarrow = columns < 95;
433
+ const hideLogoByWidth = columns < 45;
434
+ const hideLogoByHeight = rows < 32;
435
+ const collapseMenu = rows < 22 && !menuMode;
395
436
  const cursorView = menuMode ? MENU_VIEWS[menuCursor] ?? null : null;
396
437
  const logoBlock = /* @__PURE__ */ jsx3(Box, { flexDirection: "column", flexShrink: 0, children: LOGO_BREW.map((brew, i) => /* @__PURE__ */ jsxs(Box, { children: [
397
438
  /* @__PURE__ */ jsx3(GradientText, { colors: GRADIENTS.gold, children: brew }),
@@ -412,12 +453,30 @@ function Header() {
412
453
  t("hint_menuOpen_suffix")
413
454
  ] }) })
414
455
  ] });
456
+ if (collapseMenu) {
457
+ const currentLabel = t(VIEW_LABEL_KEYS[currentView]);
458
+ const currentIsPro = isProView(currentView) || isTeamView(currentView);
459
+ return /* @__PURE__ */ jsxs(Box, { paddingX: SPACING.xs, children: [
460
+ /* @__PURE__ */ jsx3(Text3, { color: COLORS.success, bold: true, children: "\u25B6 " }),
461
+ /* @__PURE__ */ jsx3(Text3, { color: COLORS.success, bold: true, children: currentLabel }),
462
+ currentIsPro && /* @__PURE__ */ jsxs(Text3, { color: COLORS.brand, bold: true, children: [
463
+ " ",
464
+ t("pro_badge")
465
+ ] }),
466
+ /* @__PURE__ */ jsx3(Text3, { color: COLORS.textSecondary, children: " " }),
467
+ /* @__PURE__ */ jsx3(BlinkingText, { color: COLORS.brand, children: "M" }),
468
+ /* @__PURE__ */ jsx3(Text3, { color: COLORS.textSecondary, children: t("hint_menuOpen_suffix") })
469
+ ] });
470
+ }
415
471
  if (isNarrow) {
416
472
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: SPACING.xs, children: [
417
- logoBlock,
418
- /* @__PURE__ */ jsx3(Box, { marginTop: SPACING.xs, children: menuBlock })
473
+ !hideLogoByWidth && !hideLogoByHeight && logoBlock,
474
+ /* @__PURE__ */ jsx3(Box, { marginTop: hideLogoByWidth || hideLogoByHeight ? SPACING.none : SPACING.xs, children: menuBlock })
419
475
  ] });
420
476
  }
477
+ if (hideLogoByHeight) {
478
+ return /* @__PURE__ */ jsx3(Box, { flexDirection: "column", paddingX: SPACING.xs, children: menuBlock });
479
+ }
421
480
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", paddingX: SPACING.xs, alignItems: "center", children: [
422
481
  logoBlock,
423
482
  /* @__PURE__ */ jsx3(Box, { marginLeft: SPACING.sm, children: menuBlock })
@@ -460,8 +519,9 @@ function Footer() {
460
519
  const currentView = useNavigationStore((s) => s.currentView);
461
520
  const menuMode = useNavigationStore((s) => s.menuMode);
462
521
  const locale = useLocaleStore((s) => s.locale);
522
+ const { rows } = useTerminalSize();
463
523
  const defs = VIEW_HINT_DEFS[currentView] ?? [];
464
- const showChoose = hasNumberedActions(defs) && !menuMode;
524
+ const showChoose = hasNumberedActions(defs) && !menuMode && rows >= 26;
465
525
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
466
526
  showChoose && /* @__PURE__ */ jsx4(Box2, { paddingX: SPACING.xs, children: /* @__PURE__ */ jsx4(Text4, { color: COLORS.text, children: t("hint_chooseNumber") }) }),
467
527
  /* @__PURE__ */ jsxs2(Box2, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: COLORS.gold, paddingX: SPACING.xs, flexWrap: "wrap", children: [
@@ -506,31 +566,6 @@ function Footer() {
506
566
  ] });
507
567
  }
508
568
 
509
- // src/hooks/use-terminal-size.ts
510
- import { useEffect as useEffect2, useState as useState2 } from "react";
511
- import { useStdout as useStdout2 } from "ink";
512
- function useTerminalSize() {
513
- const { stdout } = useStdout2();
514
- const [size, setSize] = useState2(() => ({
515
- columns: stdout?.columns ?? 80,
516
- rows: stdout?.rows ?? 24
517
- }));
518
- useEffect2(() => {
519
- if (!stdout) return;
520
- const onResize = () => {
521
- setSize({
522
- columns: stdout.columns ?? 80,
523
- rows: stdout.rows ?? 24
524
- });
525
- };
526
- stdout.on("resize", onResize);
527
- return () => {
528
- stdout.off("resize", onResize);
529
- };
530
- }, [stdout]);
531
- return size;
532
- }
533
-
534
569
  // src/hooks/use-container-size.ts
535
570
  import { useEffect as useEffect3, useState as useState3 } from "react";
536
571
  import { measureElement } from "ink";
@@ -837,12 +872,12 @@ function WelcomeView({ onContinue }) {
837
872
  void markOnboardingComplete().finally(onContinue);
838
873
  }
839
874
  });
840
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingY: SPACING.md, paddingX: SPACING.lg, children: [
875
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingY: SPACING.xs, paddingX: SPACING.sm, flexShrink: 1, children: [
841
876
  /* @__PURE__ */ jsx7(Box4, { children: /* @__PURE__ */ jsx7(GradientText, { colors: GRADIENTS.gold, bold: true, children: t("welcome_title") }) }),
842
- /* @__PURE__ */ jsx7(Box4, { marginTop: SPACING.sm, children: /* @__PURE__ */ jsx7(Text5, { color: COLORS.text, children: t("welcome_intro") }) }),
843
- /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: SPACING.sm, children: [
877
+ /* @__PURE__ */ jsx7(Box4, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx7(Text5, { color: COLORS.text, wrap: "wrap", children: t("welcome_intro") }) }),
878
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: SPACING.xs, children: [
844
879
  /* @__PURE__ */ jsx7(Text5, { color: COLORS.muted, children: t("welcome_keysHeader") }),
845
- /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: SPACING.sm, marginTop: SPACING.xs, children: [
880
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: SPACING.sm, children: [
846
881
  /* @__PURE__ */ jsxs4(Text5, { children: [
847
882
  /* @__PURE__ */ jsx7(Text5, { color: COLORS.gold, bold: true, children: "m" }),
848
883
  " ",
@@ -885,11 +920,11 @@ function WelcomeView({ onContinue }) {
885
920
  ] })
886
921
  ] })
887
922
  ] }),
888
- /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: SPACING.sm, children: [
923
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: SPACING.xs, children: [
889
924
  /* @__PURE__ */ jsx7(Text5, { color: COLORS.muted, children: t("welcome_proHeader") }),
890
- /* @__PURE__ */ jsx7(Box4, { paddingLeft: SPACING.sm, children: /* @__PURE__ */ jsx7(Text5, { color: COLORS.textSecondary, children: t("welcome_proIntro") }) })
925
+ /* @__PURE__ */ jsx7(Box4, { paddingLeft: SPACING.sm, children: /* @__PURE__ */ jsx7(Text5, { color: COLORS.textSecondary, wrap: "wrap", children: t("welcome_proIntro") }) })
891
926
  ] }),
892
- /* @__PURE__ */ jsx7(Box4, { marginTop: SPACING.md, children: /* @__PURE__ */ jsx7(Text5, { color: COLORS.success, bold: true, children: t("welcome_continueHint") }) })
927
+ /* @__PURE__ */ jsx7(Box4, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx7(Text5, { color: COLORS.success, bold: true, children: t("welcome_continueHint") }) })
893
928
  ] });
894
929
  }
895
930
 
@@ -915,15 +950,16 @@ function UpgradePrompt({ viewId }) {
915
950
  const pricingKey = team ? "upgrade_teamPricing" : "upgrade_pricing";
916
951
  const buyUrlKey = team ? "upgrade_buyUrlTeam" : "upgrade_buyUrl";
917
952
  const labelKey = team ? "upgrade_teamLabel" : "upgrade_proLabel";
918
- return /* @__PURE__ */ jsx8(Box5, { flexDirection: "column", alignItems: "center", paddingY: SPACING.sm, children: /* @__PURE__ */ jsxs5(
953
+ return /* @__PURE__ */ jsx8(Box5, { flexDirection: "column", alignItems: "center", paddingY: SPACING.xs, children: /* @__PURE__ */ jsxs5(
919
954
  Box5,
920
955
  {
921
956
  borderStyle: "double",
922
957
  borderColor: COLORS.brand,
923
- paddingX: SPACING.md,
924
- paddingY: SPACING.sm,
958
+ paddingX: SPACING.sm,
959
+ paddingY: SPACING.none,
925
960
  flexDirection: "column",
926
961
  alignItems: "center",
962
+ flexShrink: 1,
927
963
  width: "80%",
928
964
  children: [
929
965
  /* @__PURE__ */ jsxs5(Text6, { bold: true, color: COLORS.brand, children: [
@@ -931,24 +967,13 @@ function UpgradePrompt({ viewId }) {
931
967
  " ",
932
968
  t(headerKey, { title })
933
969
  ] }),
934
- /* @__PURE__ */ jsx8(Text6, { children: " " }),
935
970
  /* @__PURE__ */ jsx8(Text6, { color: COLORS.text, wrap: "wrap", children: t(keys.desc) }),
936
- /* @__PURE__ */ jsx8(Text6, { children: " " }),
937
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", alignItems: "center", children: [
971
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", alignItems: "center", marginTop: SPACING.xs, children: [
938
972
  /* @__PURE__ */ jsx8(Text6, { color: COLORS.info, bold: true, children: t(pricingKey) }),
939
- /* @__PURE__ */ jsx8(Text6, { children: " " }),
940
973
  /* @__PURE__ */ jsx8(Text6, { color: COLORS.muted, children: t("upgrade_buyAt") }),
941
- /* @__PURE__ */ jsxs5(Text6, { color: COLORS.sky, bold: true, children: [
942
- " ",
943
- t(buyUrlKey)
944
- ] }),
945
- /* @__PURE__ */ jsx8(Text6, { children: " " }),
974
+ /* @__PURE__ */ jsx8(Text6, { color: COLORS.sky, bold: true, children: t(buyUrlKey) }),
946
975
  /* @__PURE__ */ jsx8(Text6, { color: COLORS.muted, children: t("upgrade_activateWith") }),
947
- /* @__PURE__ */ jsxs5(Text6, { color: COLORS.success, bold: true, children: [
948
- " ",
949
- t("upgrade_activateCmd")
950
- ] }),
951
- /* @__PURE__ */ jsx8(Text6, { children: " " }),
976
+ /* @__PURE__ */ jsx8(Text6, { color: COLORS.success, bold: true, children: t("upgrade_activateCmd") }),
952
977
  /* @__PURE__ */ jsx8(Text6, { color: COLORS.brand, children: t(labelKey) })
953
978
  ] })
954
979
  ]
@@ -958,7 +983,19 @@ function UpgradePrompt({ viewId }) {
958
983
 
959
984
  // src/views/dashboard.tsx
960
985
  import { useEffect as useEffect5, useMemo as useMemo2 } from "react";
961
- import { Box as Box9, Text as Text12, useStdout as useStdout4 } from "ink";
986
+ import { Box as Box9, Text as Text12 } from "ink";
987
+
988
+ // src/hooks/use-visible-rows.ts
989
+ function useVisibleRows({
990
+ reservedRows,
991
+ fallbackReservedRows = reservedRows,
992
+ minRows = 3
993
+ }) {
994
+ const { height: contentHeight } = useContentSize();
995
+ const { rows: terminalRows } = useTerminalSize();
996
+ const availableRows = contentHeight > 0 ? contentHeight - reservedRows : terminalRows - fallbackReservedRows;
997
+ return Math.max(minRows, Math.floor(availableRows));
998
+ }
962
999
 
963
1000
  // src/stores/brew-store.ts
964
1001
  import { create as create4 } from "zustand";
@@ -1924,12 +1961,11 @@ var useComplianceStore = create8((set, get) => ({
1924
1961
  }));
1925
1962
 
1926
1963
  // src/components/common/stat-card.tsx
1927
- import { Box as Box6, Text as Text7, useStdout as useStdout3 } from "ink";
1964
+ import { Box as Box6, Text as Text7 } from "ink";
1928
1965
  import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1929
1966
  function StatCard({ label, value, color = COLORS.white }) {
1930
- const { stdout } = useStdout3();
1931
- const cols = stdout?.columns ?? 80;
1932
- const minW = cols < 60 ? 12 : cols < 100 ? 14 : 16;
1967
+ const { columns } = useTerminalSize();
1968
+ const minW = columns < 60 ? 12 : columns < 100 ? 14 : 16;
1933
1969
  return /* @__PURE__ */ jsxs6(
1934
1970
  Box6,
1935
1971
  {
@@ -1939,6 +1975,7 @@ function StatCard({ label, value, color = COLORS.white }) {
1939
1975
  paddingY: SPACING.none,
1940
1976
  flexDirection: "column",
1941
1977
  alignItems: "center",
1978
+ flexShrink: 1,
1942
1979
  minWidth: minW,
1943
1980
  children: [
1944
1981
  /* @__PURE__ */ jsx9(Text7, { bold: true, color, children: value }),
@@ -2048,10 +2085,6 @@ function formatDate(value) {
2048
2085
  const locale = getLocale();
2049
2086
  return date.toLocaleDateString(locale === "es" ? "es-ES" : "en-US");
2050
2087
  }
2051
- function truncate(str, maxLen) {
2052
- if (str.length <= maxLen) return str;
2053
- return str.slice(0, maxLen - 1) + "\u2026";
2054
- }
2055
2088
 
2056
2089
  // src/views/dashboard.tsx
2057
2090
  import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
@@ -2103,8 +2136,13 @@ function DashboardView() {
2103
2136
  const lastFetchedAt = useBrewStore((s) => s.lastFetchedAt);
2104
2137
  const fetchAll = useBrewStore((s) => s.fetchAll);
2105
2138
  const isPro = useLicenseStore((s) => s.isPro);
2106
- const { stdout } = useStdout4();
2107
- const columns = stdout?.columns ?? 80;
2139
+ const { columns } = useTerminalSize();
2140
+ const splitRows = useVisibleRows({
2141
+ reservedRows: 18,
2142
+ fallbackReservedRows: 22,
2143
+ minRows: 2
2144
+ });
2145
+ const halfRows = Math.max(1, Math.floor(splitRows / 2));
2108
2146
  useEffect5(() => {
2109
2147
  fetchAll();
2110
2148
  }, []);
@@ -2195,20 +2233,23 @@ function DashboardView() {
2195
2233
  !errors.outdated && outdated.formulae.length > 0 && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginTop: SPACING.xs, children: [
2196
2234
  /* @__PURE__ */ jsx13(SectionHeader, { emoji: "\u{1F4E6}", title: t("dashboard_outdatedPackages"), gradient: GRADIENTS.fire }),
2197
2235
  /* @__PURE__ */ jsxs11(Box9, { paddingLeft: SPACING.sm, flexDirection: "column", children: [
2198
- outdated.formulae.slice(0, 10).map((pkg) => /* @__PURE__ */ jsxs11(Box9, { gap: SPACING.xs, children: [
2236
+ outdated.formulae.slice(0, halfRows).map((pkg) => /* @__PURE__ */ jsxs11(Box9, { gap: SPACING.xs, children: [
2199
2237
  /* @__PURE__ */ jsx13(Text12, { color: COLORS.text, children: pkg.name }),
2200
2238
  /* @__PURE__ */ jsx13(VersionArrow, { current: pkg.installed_versions[0] ?? "", latest: pkg.current_version })
2201
2239
  ] }, pkg.name)),
2202
- outdated.formulae.length > 10 && /* @__PURE__ */ jsx13(Text12, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: outdated.formulae.length - 10 }) })
2240
+ outdated.formulae.length > halfRows && /* @__PURE__ */ jsx13(Text12, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: outdated.formulae.length - halfRows }) })
2203
2241
  ] })
2204
2242
  ] }),
2205
2243
  !errors.services && errorServices > 0 && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginTop: SPACING.xs, children: [
2206
2244
  /* @__PURE__ */ jsx13(SectionHeader, { emoji: "\u26A0\uFE0F", title: t("dashboard_serviceErrors"), color: COLORS.error }),
2207
- /* @__PURE__ */ jsx13(Box9, { paddingLeft: SPACING.sm, flexDirection: "column", children: errorServiceList.map((s) => /* @__PURE__ */ jsxs11(Box9, { gap: SPACING.xs, children: [
2208
- /* @__PURE__ */ jsx13(StatusBadge, { label: t("badge_error"), variant: "error" }),
2209
- /* @__PURE__ */ jsx13(Text12, { children: s.name }),
2210
- s.exit_code != null && /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: t("common_exit", { code: s.exit_code }) })
2211
- ] }, s.name)) })
2245
+ /* @__PURE__ */ jsxs11(Box9, { paddingLeft: SPACING.sm, flexDirection: "column", children: [
2246
+ errorServiceList.slice(0, halfRows).map((s) => /* @__PURE__ */ jsxs11(Box9, { gap: SPACING.xs, children: [
2247
+ /* @__PURE__ */ jsx13(StatusBadge, { label: t("badge_error"), variant: "error" }),
2248
+ /* @__PURE__ */ jsx13(Text12, { children: s.name }),
2249
+ s.exit_code != null && /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: t("common_exit", { code: s.exit_code }) })
2250
+ ] }, s.name)),
2251
+ errorServiceList.length > halfRows && /* @__PURE__ */ jsx13(Text12, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: errorServiceList.length - halfRows }) })
2252
+ ] })
2212
2253
  ] }),
2213
2254
  isPro() && /* @__PURE__ */ jsx13(ProStatusPanel, {})
2214
2255
  ] });
@@ -2347,8 +2388,8 @@ function ConfirmDialog({ message, onConfirm, onCancel }) {
2347
2388
  else if (input === "n" || input === "N") onCancel();
2348
2389
  else if (key.escape) onCancel();
2349
2390
  });
2350
- return /* @__PURE__ */ jsxs13(Box11, { borderStyle: "double", borderColor: COLORS.purple, paddingX: SPACING.sm, paddingY: SPACING.xs, flexDirection: "column", children: [
2351
- /* @__PURE__ */ jsx15(Text14, { bold: true, color: COLORS.text, children: message }),
2391
+ return /* @__PURE__ */ jsxs13(Box11, { borderStyle: "double", borderColor: COLORS.purple, paddingX: SPACING.sm, paddingY: SPACING.xs, flexDirection: "column", flexShrink: 1, children: [
2392
+ /* @__PURE__ */ jsx15(Text14, { bold: true, color: COLORS.text, wrap: "wrap", children: message }),
2352
2393
  /* @__PURE__ */ jsxs13(Box11, { marginTop: SPACING.xs, children: [
2353
2394
  /* @__PURE__ */ jsx15(Text14, { color: COLORS.success, children: t("confirm_yes") }),
2354
2395
  /* @__PURE__ */ jsx15(Text14, { children: " / " }),
@@ -2405,18 +2446,6 @@ function SelectableRow({ isCurrent, children, gap = 1 }) {
2405
2446
  ] });
2406
2447
  }
2407
2448
 
2408
- // src/hooks/use-visible-rows.ts
2409
- function useVisibleRows({
2410
- reservedRows,
2411
- fallbackReservedRows = reservedRows,
2412
- minRows = 3
2413
- }) {
2414
- const { height: contentHeight } = useContentSize();
2415
- const { rows: terminalRows } = useTerminalSize();
2416
- const availableRows = contentHeight > 0 ? contentHeight - reservedRows : terminalRows - fallbackReservedRows;
2417
- return Math.max(minRows, Math.floor(availableRows));
2418
- }
2419
-
2420
2449
  // src/views/installed.tsx
2421
2450
  import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
2422
2451
  function InstalledView() {
@@ -2431,8 +2460,13 @@ function InstalledView() {
2431
2460
  const containerRef = useRef3(null);
2432
2461
  const { width: containerWidth } = useContainerSize(containerRef);
2433
2462
  const columns = containerWidth > 0 ? containerWidth : 80;
2434
- const nameWidth = Math.floor(columns * 0.35);
2435
- const versionWidth = Math.floor(columns * 0.15);
2463
+ const mode = getLayoutMode(columns);
2464
+ const nameWidth = mode === "single" ? Math.max(
2465
+ 8,
2466
+ columns - 2
2467
+ /* cursor + gap */
2468
+ ) : Math.max(12, Math.floor(columns * 0.35));
2469
+ const versionWidth = Math.max(8, Math.floor(columns * 0.15));
2436
2470
  const [filter, setFilter] = useState6("");
2437
2471
  const [cursor, setCursor] = useState6(0);
2438
2472
  const [tab, setTab] = useState6("formulae");
@@ -2594,12 +2628,10 @@ function InstalledView() {
2594
2628
  ),
2595
2629
  isSearching && /* @__PURE__ */ jsx19(Box15, { marginBottom: SPACING.xs, borderStyle: "round", borderColor: COLORS.gold, paddingX: SPACING.xs, children: /* @__PURE__ */ jsx19(SearchInput, { defaultValue: filter, onChange: setFilter, isActive: isSearching }) }),
2596
2630
  /* @__PURE__ */ jsxs16(Box15, { gap: SPACING.xs, borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, borderColor: COLORS.border, children: [
2597
- /* @__PURE__ */ jsxs16(Text18, { color: COLORS.text, bold: true, children: [
2598
- " ",
2599
- t("installed_col_package").padEnd(nameWidth)
2600
- ] }),
2601
- /* @__PURE__ */ jsx19(Text18, { color: COLORS.text, bold: true, children: t("installed_col_version").padEnd(versionWidth) }),
2602
- /* @__PURE__ */ jsx19(Text18, { color: COLORS.text, bold: true, children: t("installed_col_status") })
2631
+ /* @__PURE__ */ jsx19(Text18, { color: COLORS.text, children: " " }),
2632
+ /* @__PURE__ */ jsx19(Box15, { width: nameWidth, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx19(Text18, { color: COLORS.text, bold: true, wrap: "truncate", children: t("installed_col_package") }) }),
2633
+ mode !== "single" && /* @__PURE__ */ jsx19(Box15, { width: versionWidth, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx19(Text18, { color: COLORS.text, bold: true, wrap: "truncate", children: t("installed_col_version") }) }),
2634
+ mode !== "single" && mode !== "compact" && /* @__PURE__ */ jsx19(Box15, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx19(Text18, { color: COLORS.text, bold: true, wrap: "truncate", children: t("installed_col_status") }) })
2603
2635
  ] }),
2604
2636
  /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", children: [
2605
2637
  visible.length === 0 && /* @__PURE__ */ jsx19(Box15, { paddingY: SPACING.xs, justifyContent: "center", children: /* @__PURE__ */ jsx19(Text18, { color: COLORS.textSecondary, italic: true, children: t("installed_noPackages") }) }),
@@ -2610,14 +2642,25 @@ function InstalledView() {
2610
2642
  visible.map((item, i) => {
2611
2643
  const idx = start + i;
2612
2644
  const isCurrent = idx === cursor;
2645
+ const hasBadge = item.outdated || item.pinned || item.kegOnly || item.installedAsDependency;
2613
2646
  return /* @__PURE__ */ jsxs16(SelectableRow, { isCurrent, children: [
2614
- /* @__PURE__ */ jsx19(Text18, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: truncate(item.name, nameWidth).padEnd(nameWidth) }),
2615
- /* @__PURE__ */ jsx19(Text18, { color: COLORS.teal, children: item.version.padEnd(versionWidth) }),
2616
- item.outdated && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_outdated"), variant: "warning" }),
2617
- item.pinned && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_pinned"), variant: "info" }),
2618
- item.kegOnly && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_kegOnly"), variant: "muted" }),
2619
- item.installedAsDependency && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_dep"), variant: "muted" }),
2620
- !item.outdated && !item.pinned && !item.kegOnly && !item.installedAsDependency && /* @__PURE__ */ jsx19(Text18, { color: COLORS.textSecondary, dimColor: true, children: truncate(item.desc, 30) })
2647
+ /* @__PURE__ */ jsx19(Box15, { width: nameWidth, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx19(
2648
+ Text18,
2649
+ {
2650
+ bold: isCurrent,
2651
+ inverse: isCurrent,
2652
+ color: isCurrent ? COLORS.text : COLORS.muted,
2653
+ wrap: "truncate-middle",
2654
+ children: item.name
2655
+ }
2656
+ ) }),
2657
+ mode !== "single" && /* @__PURE__ */ jsx19(Box15, { width: versionWidth, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx19(Text18, { color: COLORS.teal, wrap: "truncate", children: item.version }) }),
2658
+ mode !== "single" && mode !== "compact" && /* @__PURE__ */ jsx19(Box15, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: hasBadge ? /* @__PURE__ */ jsxs16(Box15, { gap: SPACING.xs, children: [
2659
+ item.outdated && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_outdated"), variant: "warning" }),
2660
+ item.pinned && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_pinned"), variant: "info" }),
2661
+ item.kegOnly && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_kegOnly"), variant: "muted" }),
2662
+ item.installedAsDependency && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_dep"), variant: "muted" })
2663
+ ] }) : /* @__PURE__ */ jsx19(Text18, { color: COLORS.textSecondary, dimColor: true, wrap: "truncate", children: item.desc }) })
2621
2664
  ] }, item.name);
2622
2665
  }),
2623
2666
  start + MAX_VISIBLE_ROWS < allItems.length && /* @__PURE__ */ jsxs16(Text18, { color: COLORS.textSecondary, dimColor: true, children: [
@@ -2820,7 +2863,7 @@ function SearchView() {
2820
2863
  const idx = start + i;
2821
2864
  const isCurrent = idx === cursor;
2822
2865
  return /* @__PURE__ */ jsxs17(SelectableRow, { isCurrent, children: [
2823
- /* @__PURE__ */ jsx20(Text19, { bold: isCurrent, inverse: isCurrent, children: result.name }),
2866
+ /* @__PURE__ */ jsx20(Box16, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx20(Text19, { bold: isCurrent, inverse: isCurrent, wrap: "truncate-middle", children: result.name }) }),
2824
2867
  /* @__PURE__ */ jsx20(
2825
2868
  StatusBadge,
2826
2869
  {
@@ -3041,7 +3084,16 @@ ${t("outdated_upgradeAllList", { list: allOutdated.map((p) => p.name).join(", ")
3041
3084
  const idx = start + i;
3042
3085
  const isCurrent = idx === cursor;
3043
3086
  return /* @__PURE__ */ jsxs18(SelectableRow, { isCurrent, children: [
3044
- /* @__PURE__ */ jsx21(Text20, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: pkg.name }),
3087
+ /* @__PURE__ */ jsx21(Box17, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx21(
3088
+ Text20,
3089
+ {
3090
+ bold: isCurrent,
3091
+ inverse: isCurrent,
3092
+ color: isCurrent ? COLORS.text : COLORS.muted,
3093
+ wrap: "truncate-middle",
3094
+ children: pkg.name
3095
+ }
3096
+ ) }),
3045
3097
  /* @__PURE__ */ jsx21(VersionArrow, { current: pkg.installed_versions[0] ?? "", latest: pkg.current_version }),
3046
3098
  pkg.pinned && /* @__PURE__ */ jsx21(StatusBadge, { label: t("outdated_pinned"), variant: "info" })
3047
3099
  ] }, pkg.name);
@@ -3259,11 +3311,14 @@ function PackageInfoView() {
3259
3311
  ] }),
3260
3312
  formula.dependencies.length > 0 && /* @__PURE__ */ jsxs19(Box18, { flexDirection: "column", children: [
3261
3313
  /* @__PURE__ */ jsx22(SectionHeader, { emoji: "\u{1F517}", title: t("pkgInfo_dependencies", { count: formula.dependencies.length }), gradient: GRADIENTS.ocean }),
3262
- /* @__PURE__ */ jsx22(Box18, { paddingLeft: SPACING.sm, flexWrap: "wrap", columnGap: 2, children: formula.dependencies.map((dep) => /* @__PURE__ */ jsx22(Text21, { color: COLORS.muted, children: dep }, dep)) })
3314
+ /* @__PURE__ */ jsxs19(Box18, { paddingLeft: SPACING.sm, flexWrap: "wrap", columnGap: 2, children: [
3315
+ formula.dependencies.slice(0, 30).map((dep) => /* @__PURE__ */ jsx22(Text21, { color: COLORS.muted, children: dep }, dep)),
3316
+ formula.dependencies.length > 30 && /* @__PURE__ */ jsx22(Text21, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: formula.dependencies.length - 30 }) })
3317
+ ] })
3263
3318
  ] }),
3264
3319
  formula.caveats && /* @__PURE__ */ jsxs19(Box18, { flexDirection: "column", children: [
3265
3320
  /* @__PURE__ */ jsx22(SectionHeader, { emoji: "\u26A0\uFE0F", title: t("pkgInfo_caveats"), color: COLORS.warning }),
3266
- /* @__PURE__ */ jsx22(Box18, { borderStyle: "round", borderColor: COLORS.warning, paddingX: SPACING.sm, children: /* @__PURE__ */ jsx22(Text21, { color: COLORS.warning, children: formula.caveats }) })
3321
+ /* @__PURE__ */ jsx22(Box18, { borderStyle: "round", borderColor: COLORS.warning, paddingX: SPACING.sm, children: /* @__PURE__ */ jsx22(Text21, { color: COLORS.warning, wrap: "wrap", children: formula.caveats }) })
3267
3322
  ] })
3268
3323
  ] }),
3269
3324
  /* @__PURE__ */ jsx22(Box18, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs19(Text21, { color: COLORS.textSecondary, children: [
@@ -3275,8 +3330,8 @@ function PackageInfoView() {
3275
3330
  }
3276
3331
 
3277
3332
  // src/views/services.tsx
3278
- import { useEffect as useEffect13, useState as useState10 } from "react";
3279
- import { Box as Box19, Text as Text22, useStdout as useStdout5 } from "ink";
3333
+ import { useEffect as useEffect13, useRef as useRef7, useState as useState10 } from "react";
3334
+ import { Box as Box19, Text as Text22 } from "ink";
3280
3335
  import { jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
3281
3336
  var STATUS_VARIANTS = {
3282
3337
  started: "success",
@@ -3296,10 +3351,16 @@ function ServicesView() {
3296
3351
  const [actionInProgress, setActionInProgress] = useState10(false);
3297
3352
  const [confirmAction, setConfirmAction] = useState10(null);
3298
3353
  const [lastError, setLastError] = useState10(null);
3299
- const { stdout } = useStdout5();
3300
- const cols = stdout?.columns ?? 80;
3301
- const svcNameWidth = Math.floor(cols * 0.35);
3302
- const svcStatusWidth = Math.floor(cols * 0.15);
3354
+ const containerRef = useRef7(null);
3355
+ const { width: containerWidth } = useContainerSize(containerRef);
3356
+ const cols = containerWidth > 0 ? containerWidth : 80;
3357
+ const mode = getLayoutMode(cols);
3358
+ const svcNameWidth = mode === "single" ? Math.max(
3359
+ 8,
3360
+ cols - 2
3361
+ /* cursor + gap */
3362
+ ) : Math.max(12, Math.floor(cols * 0.35));
3363
+ const svcStatusWidth = Math.max(8, Math.floor(cols * 0.15));
3303
3364
  const MAX_VISIBLE_ROWS = useVisibleRows({
3304
3365
  reservedRows: lastError || actionInProgress ? 8 : 6,
3305
3366
  fallbackReservedRows: lastError || actionInProgress ? 16 : 14,
@@ -3347,7 +3408,7 @@ function ServicesView() {
3347
3408
  }
3348
3409
  const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
3349
3410
  const visible = services.slice(start, start + MAX_VISIBLE_ROWS);
3350
- return /* @__PURE__ */ jsxs20(Box19, { flexDirection: "column", children: [
3411
+ return /* @__PURE__ */ jsxs20(Box19, { flexDirection: "column", ref: containerRef, children: [
3351
3412
  /* @__PURE__ */ jsx23(SectionHeader, { emoji: "\u2699\uFE0F", title: t("services_titleCount", { count: services.length }), gradient: GRADIENTS.ocean }),
3352
3413
  confirmAction && /* @__PURE__ */ jsx23(Box19, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx23(
3353
3414
  ConfirmDialog,
@@ -3370,12 +3431,10 @@ function ServicesView() {
3370
3431
  ) }),
3371
3432
  /* @__PURE__ */ jsxs20(Box19, { flexDirection: "column", marginTop: SPACING.xs, children: [
3372
3433
  /* @__PURE__ */ jsxs20(Box19, { gap: SPACING.xs, borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, borderColor: COLORS.border, paddingBottom: SPACING.none, children: [
3373
- /* @__PURE__ */ jsxs20(Text22, { bold: true, color: COLORS.text, children: [
3374
- " ",
3375
- t("services_name").padEnd(svcNameWidth)
3376
- ] }),
3377
- /* @__PURE__ */ jsx23(Text22, { bold: true, color: COLORS.text, children: t("services_status").padEnd(svcStatusWidth) }),
3378
- /* @__PURE__ */ jsx23(Text22, { bold: true, color: COLORS.text, children: t("services_user") })
3434
+ /* @__PURE__ */ jsx23(Text22, { bold: true, color: COLORS.text, children: " " }),
3435
+ /* @__PURE__ */ jsx23(Box19, { width: svcNameWidth, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx23(Text22, { bold: true, color: COLORS.text, wrap: "truncate", children: t("services_name") }) }),
3436
+ mode !== "single" && /* @__PURE__ */ jsx23(Box19, { width: svcStatusWidth, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx23(Text22, { bold: true, color: COLORS.text, wrap: "truncate", children: t("services_status") }) }),
3437
+ mode !== "single" && mode !== "compact" && /* @__PURE__ */ jsx23(Box19, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx23(Text22, { bold: true, color: COLORS.text, wrap: "truncate", children: t("services_user") }) })
3379
3438
  ] }),
3380
3439
  start > 0 && /* @__PURE__ */ jsxs20(Text22, { color: COLORS.textSecondary, dimColor: true, children: [
3381
3440
  " ",
@@ -3385,10 +3444,21 @@ function ServicesView() {
3385
3444
  const idx = start + i;
3386
3445
  const isCurrent = idx === cursor;
3387
3446
  return /* @__PURE__ */ jsxs20(SelectableRow, { isCurrent, children: [
3388
- /* @__PURE__ */ jsx23(Text22, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: svc.name.padEnd(svcNameWidth - 2) }),
3389
- /* @__PURE__ */ jsx23(StatusBadge, { label: svc.status, variant: STATUS_VARIANTS[svc.status] }),
3390
- /* @__PURE__ */ jsx23(Text22, { color: COLORS.muted, children: svc.user ?? "-" }),
3391
- svc.exit_code != null && svc.exit_code !== 0 && /* @__PURE__ */ jsx23(Text22, { color: COLORS.error, children: t("common_exit", { code: svc.exit_code }) })
3447
+ /* @__PURE__ */ jsx23(Box19, { width: svcNameWidth, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx23(
3448
+ Text22,
3449
+ {
3450
+ bold: isCurrent,
3451
+ inverse: isCurrent,
3452
+ color: isCurrent ? COLORS.text : COLORS.muted,
3453
+ wrap: "truncate-middle",
3454
+ children: svc.name
3455
+ }
3456
+ ) }),
3457
+ mode !== "single" && /* @__PURE__ */ jsx23(Box19, { width: svcStatusWidth, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx23(StatusBadge, { label: svc.status, variant: STATUS_VARIANTS[svc.status] }) }),
3458
+ mode !== "single" && mode !== "compact" && /* @__PURE__ */ jsxs20(Box19, { flexGrow: 1, flexShrink: 1, minWidth: 0, gap: SPACING.xs, children: [
3459
+ /* @__PURE__ */ jsx23(Text22, { color: COLORS.muted, wrap: "truncate", children: svc.user ?? "-" }),
3460
+ svc.exit_code != null && svc.exit_code !== 0 && /* @__PURE__ */ jsx23(Text22, { color: COLORS.error, children: t("common_exit", { code: svc.exit_code }) })
3461
+ ] })
3392
3462
  ] }, svc.name);
3393
3463
  }),
3394
3464
  start + MAX_VISIBLE_ROWS < services.length && /* @__PURE__ */ jsxs20(Text22, { color: COLORS.textSecondary, dimColor: true, children: [
@@ -3407,12 +3477,18 @@ function ServicesView() {
3407
3477
  }
3408
3478
 
3409
3479
  // src/views/doctor.tsx
3410
- import { useEffect as useEffect14, useRef as useRef7 } from "react";
3480
+ import { useEffect as useEffect14, useRef as useRef8, useState as useState11 } from "react";
3411
3481
  import { Box as Box20, Text as Text23 } from "ink";
3412
3482
  import { jsx as jsx24, jsxs as jsxs21 } from "react/jsx-runtime";
3413
3483
  function DoctorView() {
3414
3484
  const { doctorWarnings, doctorClean, loading, errors, fetchDoctor } = useBrewStore();
3415
- const mountedRef = useRef7(true);
3485
+ const [cursor, setCursor] = useState11(0);
3486
+ const visibleWarnings = useVisibleRows({
3487
+ reservedRows: 6,
3488
+ fallbackReservedRows: 14,
3489
+ minRows: 1
3490
+ });
3491
+ const mountedRef = useRef8(true);
3416
3492
  useEffect14(() => {
3417
3493
  mountedRef.current = true;
3418
3494
  return () => {
@@ -3422,27 +3498,42 @@ function DoctorView() {
3422
3498
  useEffect14(() => {
3423
3499
  fetchDoctor();
3424
3500
  }, []);
3425
- useViewInput((input) => {
3426
- if (input === "r" || input === "1") void fetchDoctor();
3501
+ useViewInput((input, key) => {
3502
+ if (input === "r" || input === "1") {
3503
+ void fetchDoctor();
3504
+ return;
3505
+ }
3506
+ if (input === "j" || key.downArrow) setCursor((c) => Math.min(c + 1, Math.max(0, doctorWarnings.length - 1)));
3507
+ else if (input === "k" || key.upArrow) setCursor((c) => Math.max(c - 1, 0));
3427
3508
  });
3428
3509
  if (loading.doctor) return /* @__PURE__ */ jsx24(Loading, { message: t("loading_doctor") });
3429
3510
  if (errors.doctor) return /* @__PURE__ */ jsx24(ErrorMessage, { message: errors.doctor });
3511
+ const start = Math.max(0, cursor - Math.floor(visibleWarnings / 2));
3512
+ const visible = doctorWarnings.slice(start, start + visibleWarnings);
3430
3513
  return /* @__PURE__ */ jsxs21(Box20, { flexDirection: "column", children: [
3431
3514
  /* @__PURE__ */ jsx24(SectionHeader, { emoji: "\u{1FA7A}", title: t("doctor_title"), gradient: GRADIENTS.emerald }),
3432
3515
  /* @__PURE__ */ jsxs21(Box20, { flexDirection: "column", marginTop: SPACING.xs, children: [
3433
3516
  doctorClean && /* @__PURE__ */ jsx24(ResultBanner, { status: "success", message: `\u2714 ${t("doctor_clean")}` }),
3434
3517
  doctorClean === false && doctorWarnings.length === 0 && /* @__PURE__ */ jsx24(Text23, { color: COLORS.warning, children: t("doctor_warningsNotCaptured") }),
3435
- doctorWarnings.map((warning, i) => (
3436
- // FE-004: Improved React key
3437
- /* @__PURE__ */ jsx24(Box20, { flexDirection: "column", marginBottom: SPACING.xs, borderStyle: "single", borderColor: COLORS.warning, paddingX: SPACING.xs, children: warning.split("\n").map((line, j) => /* @__PURE__ */ jsx24(Text23, { color: j === 0 ? COLORS.warning : COLORS.muted, children: line }, `warning-${i}-${j}-${line.slice(0, 20)}`)) }, `warning-${i}-${warning.slice(0, 20)}`)
3438
- ))
3518
+ start > 0 && /* @__PURE__ */ jsxs21(Text23, { color: COLORS.textSecondary, dimColor: true, children: [
3519
+ " ",
3520
+ t("scroll_moreAbove", { count: start })
3521
+ ] }),
3522
+ visible.map((warning, i) => {
3523
+ const idx = start + i;
3524
+ return /* @__PURE__ */ jsx24(Box20, { flexDirection: "column", marginBottom: SPACING.xs, borderStyle: "single", borderColor: idx === cursor ? COLORS.gold : COLORS.warning, paddingX: SPACING.xs, children: warning.split("\n").map((line, j) => /* @__PURE__ */ jsx24(Text23, { color: j === 0 ? COLORS.warning : COLORS.muted, wrap: "wrap", children: line }, `warning-${idx}-${j}-${line.slice(0, 20)}`)) }, `warning-${idx}-${warning.slice(0, 20)}`);
3525
+ }),
3526
+ start + visibleWarnings < doctorWarnings.length && /* @__PURE__ */ jsxs21(Text23, { color: COLORS.textSecondary, dimColor: true, children: [
3527
+ " ",
3528
+ t("scroll_moreBelow", { count: doctorWarnings.length - start - visibleWarnings })
3529
+ ] })
3439
3530
  ] }),
3440
3531
  /* @__PURE__ */ jsx24(Box20, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx24(Text23, { color: COLORS.text, bold: true, children: doctorWarnings.length > 0 ? tp("plural_warnings", doctorWarnings.length) : "" }) })
3441
3532
  ] });
3442
3533
  }
3443
3534
 
3444
3535
  // src/views/profiles.tsx
3445
- import { useEffect as useEffect15, useRef as useRef8, useState as useState11 } from "react";
3536
+ import { useEffect as useEffect15, useRef as useRef9, useState as useState12 } from "react";
3446
3537
  import { Box as Box25 } from "ink";
3447
3538
 
3448
3539
  // src/stores/profile-store.ts
@@ -3705,18 +3796,33 @@ function ProfileListMode({ profileNames, cursor, confirmDelete, loadError, onCon
3705
3796
  import { Box as Box22, Text as Text25 } from "ink";
3706
3797
  import { jsx as jsx26, jsxs as jsxs23 } from "react/jsx-runtime";
3707
3798
  function ProfileDetailMode({ profile }) {
3799
+ const totalRows = useVisibleRows({
3800
+ reservedRows: 7,
3801
+ fallbackReservedRows: 14,
3802
+ minRows: 2
3803
+ });
3804
+ const total = profile.formulae.length + profile.casks.length;
3805
+ const formulaeBudget = total === 0 ? 0 : Math.min(profile.formulae.length, Math.max(1, Math.round(profile.formulae.length / total * totalRows)));
3806
+ const casksBudget = Math.max(0, totalRows - formulaeBudget);
3807
+ const visibleFormulae = profile.formulae.slice(0, formulaeBudget);
3808
+ const visibleCasks = profile.casks.slice(0, casksBudget);
3809
+ const formulaeHidden = profile.formulae.length - visibleFormulae.length;
3810
+ const casksHidden = profile.casks.length - visibleCasks.length;
3708
3811
  return /* @__PURE__ */ jsxs23(Box22, { flexDirection: "column", children: [
3709
3812
  /* @__PURE__ */ jsx26(Text25, { bold: true, color: COLORS.gold, children: profile.name }),
3710
- /* @__PURE__ */ jsx26(Text25, { color: COLORS.muted, children: profile.description }),
3813
+ /* @__PURE__ */ jsx26(Text25, { color: COLORS.muted, wrap: "wrap", children: profile.description }),
3711
3814
  /* @__PURE__ */ jsx26(Text25, { color: COLORS.muted, children: t("profiles_created", { date: formatDate(profile.createdAt) }) }),
3712
3815
  /* @__PURE__ */ jsxs23(Box22, { marginTop: SPACING.xs, flexDirection: "column", children: [
3713
3816
  /* @__PURE__ */ jsx26(Text25, { bold: true, children: t("profiles_formulaeCount", { count: profile.formulae.length }) }),
3714
3817
  /* @__PURE__ */ jsxs23(Box22, { paddingLeft: SPACING.sm, flexDirection: "column", children: [
3715
- profile.formulae.slice(0, 30).map((f) => /* @__PURE__ */ jsx26(Text25, { color: COLORS.muted, children: f }, f)),
3716
- profile.formulae.length > 30 && /* @__PURE__ */ jsx26(Text25, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: profile.formulae.length - 30 }) })
3818
+ visibleFormulae.map((f) => /* @__PURE__ */ jsx26(Text25, { color: COLORS.muted, children: f }, f)),
3819
+ formulaeHidden > 0 && /* @__PURE__ */ jsx26(Text25, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: formulaeHidden }) })
3717
3820
  ] }),
3718
3821
  /* @__PURE__ */ jsx26(Text25, { bold: true, children: t("profiles_casksCount", { count: profile.casks.length }) }),
3719
- /* @__PURE__ */ jsx26(Box22, { paddingLeft: SPACING.sm, flexDirection: "column", children: profile.casks.map((c) => /* @__PURE__ */ jsx26(Text25, { color: COLORS.muted, children: c }, c)) })
3822
+ /* @__PURE__ */ jsxs23(Box22, { paddingLeft: SPACING.sm, flexDirection: "column", children: [
3823
+ visibleCasks.map((c) => /* @__PURE__ */ jsx26(Text25, { color: COLORS.muted, children: c }, c)),
3824
+ casksHidden > 0 && /* @__PURE__ */ jsx26(Text25, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: casksHidden }) })
3825
+ ] })
3720
3826
  ] }),
3721
3827
  /* @__PURE__ */ jsx26(Box22, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs23(Text25, { color: COLORS.textSecondary, children: [
3722
3828
  "esc:",
@@ -3799,19 +3905,19 @@ function ProfileEditDesc({ name, defaultDesc, loadError, onSubmit }) {
3799
3905
  import { jsx as jsx29, jsxs as jsxs26 } from "react/jsx-runtime";
3800
3906
  function ProfilesView() {
3801
3907
  const { profileNames, selectedProfile, loading, loadError, fetchProfiles, loadProfile: loadProfile2, exportCurrent, deleteProfile: deleteProfile2, updateProfile: updateProfile2 } = useProfileStore();
3802
- const [cursor, setCursor] = useState11(0);
3803
- const [mode, setMode] = useState11("list");
3804
- const [newName, setNewName] = useState11("");
3805
- const [confirmDelete, setConfirmDelete] = useState11(false);
3806
- const [editName, setEditName] = useState11("");
3807
- const [editDesc, setEditDesc] = useState11("");
3808
- const [importLines, setImportLines] = useState11([]);
3809
- const [importRunning, setImportRunning] = useState11(false);
3810
- const [importHadError, setImportHadError] = useState11(false);
3811
- const [importProfile2, setImportProfile] = useState11(null);
3908
+ const [cursor, setCursor] = useState12(0);
3909
+ const [mode, setMode] = useState12("list");
3910
+ const [newName, setNewName] = useState12("");
3911
+ const [confirmDelete, setConfirmDelete] = useState12(false);
3912
+ const [editName, setEditName] = useState12("");
3913
+ const [editDesc, setEditDesc] = useState12("");
3914
+ const [importLines, setImportLines] = useState12([]);
3915
+ const [importRunning, setImportRunning] = useState12(false);
3916
+ const [importHadError, setImportHadError] = useState12(false);
3917
+ const [importProfile2, setImportProfile] = useState12(null);
3812
3918
  const { openModal, closeModal } = useModalStore();
3813
- const importGenRef = useRef8(null);
3814
- const mountedRef = useRef8(true);
3919
+ const importGenRef = useRef9(null);
3920
+ const mountedRef = useRef9(true);
3815
3921
  useEffect15(() => {
3816
3922
  fetchProfiles();
3817
3923
  }, []);
@@ -3999,7 +4105,7 @@ function ProfilesView() {
3999
4105
  }
4000
4106
 
4001
4107
  // src/views/smart-cleanup.tsx
4002
- import { useEffect as useEffect16, useRef as useRef9, useState as useState12 } from "react";
4108
+ import { useEffect as useEffect16, useRef as useRef10, useState as useState13 } from "react";
4003
4109
  import { Box as Box26, Text as Text28 } from "ink";
4004
4110
 
4005
4111
  // src/stores/cleanup-store.ts
@@ -4123,12 +4229,17 @@ var useCleanupStore = create10((set, get) => ({
4123
4229
  import { jsx as jsx30, jsxs as jsxs27 } from "react/jsx-runtime";
4124
4230
  function SmartCleanupView() {
4125
4231
  const { summary, selected, loading, error, analyze, toggleSelect, selectAll } = useCleanupStore();
4126
- const [cursor, setCursor] = useState12(0);
4127
- const [confirmClean, setConfirmClean] = useState12(false);
4128
- const [confirmForce, setConfirmForce] = useState12(false);
4129
- const [failedNames, setFailedNames] = useState12([]);
4232
+ const [cursor, setCursor] = useState13(0);
4233
+ const [confirmClean, setConfirmClean] = useState13(false);
4234
+ const [confirmForce, setConfirmForce] = useState13(false);
4235
+ const [failedNames, setFailedNames] = useState13([]);
4130
4236
  const stream = useBrewStream();
4131
- const hasRefreshed = useRef9(false);
4237
+ const hasRefreshed = useRef10(false);
4238
+ const listRows = useVisibleRows({
4239
+ reservedRows: confirmClean || confirmForce ? 12 : 9,
4240
+ fallbackReservedRows: confirmClean || confirmForce ? 18 : 14,
4241
+ minRows: 1
4242
+ });
4132
4243
  useEffect16(() => {
4133
4244
  analyze();
4134
4245
  }, []);
@@ -4230,32 +4341,45 @@ function SmartCleanupView() {
4230
4341
  )
4231
4342
  ] }),
4232
4343
  candidates.length === 0 && !confirmClean && /* @__PURE__ */ jsx30(ResultBanner, { status: "success", message: `\u2714 ${t("cleanup_systemClean")}` }),
4233
- candidates.length > 0 && !confirmClean && /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", children: [
4234
- candidates.map((c, i) => {
4235
- const isCurrent = i === cursor;
4236
- const isSelected = selected.has(c.name);
4237
- return /* @__PURE__ */ jsxs27(SelectableRow, { isCurrent, children: [
4238
- /* @__PURE__ */ jsx30(Text28, { color: isSelected ? COLORS.success : COLORS.muted, children: isSelected ? "\u2611" : "\u2610" }),
4239
- /* @__PURE__ */ jsx30(Text28, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: c.name }),
4240
- /* @__PURE__ */ jsx30(Text28, { color: COLORS.warning, children: c.diskUsageFormatted }),
4241
- /* @__PURE__ */ jsxs27(Text28, { color: COLORS.textSecondary, children: [
4242
- "[",
4243
- c.reason,
4244
- "]"
4245
- ] })
4246
- ] }, c.name);
4247
- }),
4248
- /* @__PURE__ */ jsx30(Box26, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs27(Text28, { color: COLORS.text, bold: true, children: [
4249
- cursor + 1,
4250
- "/",
4251
- candidates.length
4252
- ] }) })
4253
- ] })
4344
+ candidates.length > 0 && !confirmClean && (() => {
4345
+ const start = Math.max(0, cursor - Math.floor(listRows / 2));
4346
+ const visible = candidates.slice(start, start + listRows);
4347
+ return /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", children: [
4348
+ start > 0 && /* @__PURE__ */ jsxs27(Text28, { color: COLORS.textSecondary, dimColor: true, children: [
4349
+ " ",
4350
+ t("scroll_moreAbove", { count: start })
4351
+ ] }),
4352
+ visible.map((c, i) => {
4353
+ const idx = start + i;
4354
+ const isCurrent = idx === cursor;
4355
+ const isSelected = selected.has(c.name);
4356
+ return /* @__PURE__ */ jsxs27(SelectableRow, { isCurrent, children: [
4357
+ /* @__PURE__ */ jsx30(Text28, { color: isSelected ? COLORS.success : COLORS.muted, children: isSelected ? "\u2611" : "\u2610" }),
4358
+ /* @__PURE__ */ jsx30(Text28, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: c.name }),
4359
+ /* @__PURE__ */ jsx30(Text28, { color: COLORS.warning, children: c.diskUsageFormatted }),
4360
+ /* @__PURE__ */ jsxs27(Text28, { color: COLORS.textSecondary, children: [
4361
+ "[",
4362
+ c.reason,
4363
+ "]"
4364
+ ] })
4365
+ ] }, c.name);
4366
+ }),
4367
+ start + listRows < candidates.length && /* @__PURE__ */ jsxs27(Text28, { color: COLORS.textSecondary, dimColor: true, children: [
4368
+ " ",
4369
+ t("scroll_moreBelow", { count: candidates.length - start - listRows })
4370
+ ] }),
4371
+ /* @__PURE__ */ jsx30(Box26, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs27(Text28, { color: COLORS.text, bold: true, children: [
4372
+ cursor + 1,
4373
+ "/",
4374
+ candidates.length
4375
+ ] }) })
4376
+ ] });
4377
+ })()
4254
4378
  ] });
4255
4379
  }
4256
4380
 
4257
4381
  // src/views/history.tsx
4258
- import { useEffect as useEffect17, useState as useState13, useMemo as useMemo5 } from "react";
4382
+ import { useEffect as useEffect17, useState as useState14, useMemo as useMemo5 } from "react";
4259
4383
  import { Box as Box27, Text as Text29 } from "ink";
4260
4384
 
4261
4385
  // src/stores/history-store.ts
@@ -4308,12 +4432,12 @@ var ACTION_LABEL_KEYS = {
4308
4432
  var FILTERS = ["all", "install", "uninstall", "upgrade", "upgrade-all"];
4309
4433
  function HistoryView() {
4310
4434
  const { entries, loading, error, fetchHistory, clearHistory: clearHistory2 } = useHistoryStore();
4311
- const [cursor, setCursor] = useState13(0);
4312
- const [filter, setFilter] = useState13("all");
4313
- const [searchQuery, setSearchQuery] = useState13("");
4314
- const [isSearching, setIsSearching] = useState13(false);
4315
- const [confirmClear, setConfirmClear] = useState13(false);
4316
- const [confirmReplay, setConfirmReplay] = useState13(null);
4435
+ const [cursor, setCursor] = useState14(0);
4436
+ const [filter, setFilter] = useState14("all");
4437
+ const [searchQuery, setSearchQuery] = useState14("");
4438
+ const [isSearching, setIsSearching] = useState14(false);
4439
+ const [confirmClear, setConfirmClear] = useState14(false);
4440
+ const [confirmReplay, setConfirmReplay] = useState14(null);
4317
4441
  const stream = useBrewStream();
4318
4442
  const debouncedQuery = useDebounce(searchQuery, 200);
4319
4443
  const { openModal, closeModal } = useModalStore();
@@ -4451,8 +4575,8 @@ function HistoryView() {
4451
4575
  const ts = new Date(entry.timestamp).getTime() / 1e3;
4452
4576
  return /* @__PURE__ */ jsxs28(SelectableRow, { isCurrent, children: [
4453
4577
  /* @__PURE__ */ jsx31(Text29, { color, bold: true, children: icon }),
4454
- /* @__PURE__ */ jsx31(Text29, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: t(ACTION_LABEL_KEYS[entry.action]).padEnd(12) }),
4455
- /* @__PURE__ */ jsx31(Text29, { color: COLORS.text, children: entry.packageName ?? t("history_all") }),
4578
+ /* @__PURE__ */ jsx31(Box27, { width: 14, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx31(Text29, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, wrap: "truncate", children: t(ACTION_LABEL_KEYS[entry.action]) }) }),
4579
+ /* @__PURE__ */ jsx31(Box27, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx31(Text29, { color: COLORS.text, wrap: "truncate-middle", children: entry.packageName ?? t("history_all") }) }),
4456
4580
  entry.success ? /* @__PURE__ */ jsx31(StatusBadge, { label: t("badge_ok"), variant: "success" }) : /* @__PURE__ */ jsx31(StatusBadge, { label: t("badge_fail"), variant: "error" }),
4457
4581
  /* @__PURE__ */ jsx31(Text29, { color: COLORS.muted, children: formatRelativeTime(ts) })
4458
4582
  ] }, entry.id);
@@ -4471,7 +4595,7 @@ function HistoryView() {
4471
4595
  }
4472
4596
 
4473
4597
  // src/views/security-audit.tsx
4474
- import { useEffect as useEffect18, useState as useState14 } from "react";
4598
+ import { useEffect as useEffect18, useState as useState15 } from "react";
4475
4599
  import { Box as Box28, Text as Text30 } from "ink";
4476
4600
  import { jsx as jsx32, jsxs as jsxs29 } from "react/jsx-runtime";
4477
4601
  var SEVERITY_COLORS = {
@@ -4494,9 +4618,9 @@ function isNetworkError(msg) {
4494
4618
  function SecurityAuditView() {
4495
4619
  const { summary, loading, error, scan, cachedAt } = useSecurityStore();
4496
4620
  const navigate = useNavigationStore((s) => s.navigate);
4497
- const [cursor, setCursor] = useState14(0);
4498
- const [expandedPkg, setExpandedPkg] = useState14(null);
4499
- const [confirmUpgrade, setConfirmUpgrade] = useState14(null);
4621
+ const [cursor, setCursor] = useState15(0);
4622
+ const [expandedPkg, setExpandedPkg] = useState15(null);
4623
+ const [confirmUpgrade, setConfirmUpgrade] = useState15(null);
4500
4624
  const stream = useBrewStream();
4501
4625
  useEffect18(() => {
4502
4626
  scan();
@@ -4603,7 +4727,7 @@ function SecurityAuditView() {
4603
4727
  }
4604
4728
 
4605
4729
  // src/views/account.tsx
4606
- import { useState as useState15 } from "react";
4730
+ import { useState as useState16 } from "react";
4607
4731
  import { Box as Box29, Text as Text31 } from "ink";
4608
4732
  import { TextInput as TextInput5 } from "@inkjs/ui";
4609
4733
 
@@ -4685,13 +4809,13 @@ async function redeemPromoCode(code) {
4685
4809
  import { Fragment as Fragment5, jsx as jsx33, jsxs as jsxs30 } from "react/jsx-runtime";
4686
4810
  function AccountView() {
4687
4811
  const { status, license, deactivate: deactivate2, revalidate: revalidate2, degradation } = useLicenseStore();
4688
- const [confirmDeactivate, setConfirmDeactivate] = useState15(false);
4689
- const [deactivating, setDeactivating] = useState15(false);
4690
- const [deactivateError, setDeactivateError] = useState15(null);
4691
- const [promoMode, setPromoMode] = useState15(false);
4692
- const [promoLoading, setPromoLoading] = useState15(false);
4693
- const [promoResult, setPromoResult] = useState15(null);
4694
- const [revalidating, setRevalidating] = useState15(false);
4812
+ const [confirmDeactivate, setConfirmDeactivate] = useState16(false);
4813
+ const [deactivating, setDeactivating] = useState16(false);
4814
+ const [deactivateError, setDeactivateError] = useState16(null);
4815
+ const [promoMode, setPromoMode] = useState16(false);
4816
+ const [promoLoading, setPromoLoading] = useState16(false);
4817
+ const [promoResult, setPromoResult] = useState16(null);
4818
+ const [revalidating, setRevalidating] = useState16(false);
4695
4819
  useViewInput((input, key) => {
4696
4820
  if (confirmDeactivate || deactivating || promoMode || revalidating) {
4697
4821
  if (key.escape && promoMode) {
@@ -4776,16 +4900,14 @@ function AccountView() {
4776
4900
  /* @__PURE__ */ jsx33(Text31, { children: formatDate(license.activatedAt) })
4777
4901
  ] })
4778
4902
  ] }),
4779
- status === "free" && /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", marginTop: SPACING.sm, borderStyle: "round", borderColor: COLORS.brand, paddingX: SPACING.sm, paddingY: SPACING.xs, children: [
4903
+ status === "free" && /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", marginTop: SPACING.xs, borderStyle: "round", borderColor: COLORS.brand, paddingX: SPACING.sm, paddingY: SPACING.none, flexShrink: 1, children: [
4780
4904
  /* @__PURE__ */ jsxs30(Text31, { bold: true, color: COLORS.brand, children: [
4781
4905
  "\u2B50",
4782
4906
  " ",
4783
4907
  t("account_upgradeTitle")
4784
4908
  ] }),
4785
- /* @__PURE__ */ jsx33(Text31, { children: " " }),
4786
- /* @__PURE__ */ jsx33(Text31, { children: t("account_unlockDesc") }),
4909
+ /* @__PURE__ */ jsx33(Text31, { wrap: "wrap", children: t("account_unlockDesc") }),
4787
4910
  /* @__PURE__ */ jsx33(Text31, { color: COLORS.info, bold: true, children: t("account_pricing") }),
4788
- /* @__PURE__ */ jsx33(Text31, { children: " " }),
4789
4911
  /* @__PURE__ */ jsxs30(Text31, { color: COLORS.muted, children: [
4790
4912
  t("upgrade_buyAt"),
4791
4913
  " ",
@@ -4837,13 +4959,13 @@ function AccountView() {
4837
4959
  status === "pro" || status === "team" || status === "expired" ? `v ${t("hint_revalidate")} ` : "",
4838
4960
  revalidating ? t("account_revalidating") : "",
4839
4961
  " ",
4840
- t("app_version", { version: "1.0.0" })
4962
+ t("app_version", { version: "1.2.0" })
4841
4963
  ] }) })
4842
4964
  ] });
4843
4965
  }
4844
4966
 
4845
4967
  // src/views/rollback.tsx
4846
- import { useCallback as useCallback3, useEffect as useEffect19, useRef as useRef10, useState as useState16 } from "react";
4968
+ import { useCallback as useCallback3, useEffect as useEffect19, useRef as useRef11, useState as useState17 } from "react";
4847
4969
  import { Box as Box30, Text as Text32 } from "ink";
4848
4970
 
4849
4971
  // src/stores/rollback-store.ts
@@ -5082,6 +5204,14 @@ function actionPrefix(action) {
5082
5204
  }
5083
5205
  function PlanView({ plan }) {
5084
5206
  const executableCount = plan.actions.filter((a) => a.strategy !== "unavailable" && a.action !== "remove").length;
5207
+ const reservedForWarnings = Math.min(plan.warnings.length, 3) * 2;
5208
+ const actionsBudget = useVisibleRows({
5209
+ reservedRows: 6 + reservedForWarnings,
5210
+ fallbackReservedRows: 14 + reservedForWarnings,
5211
+ minRows: 2
5212
+ });
5213
+ const visibleActions = plan.actions.slice(0, actionsBudget);
5214
+ const hiddenActions = plan.actions.length - visibleActions.length;
5085
5215
  return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: SPACING.xs, children: [
5086
5216
  /* @__PURE__ */ jsxs31(Box30, { marginBottom: SPACING.xs, children: [
5087
5217
  /* @__PURE__ */ jsxs31(Text32, { color: COLORS.text, bold: true, children: [
@@ -5091,7 +5221,7 @@ function PlanView({ plan }) {
5091
5221
  /* @__PURE__ */ jsx34(Text32, { color: COLORS.textSecondary, children: plan.snapshotDate })
5092
5222
  ] }),
5093
5223
  plan.actions.length === 0 && /* @__PURE__ */ jsx34(ResultBanner, { status: "success", message: t("rollback_diff_empty") }),
5094
- plan.actions.map((a) => /* @__PURE__ */ jsxs31(Box30, { children: [
5224
+ visibleActions.map((a) => /* @__PURE__ */ jsxs31(Box30, { children: [
5095
5225
  /* @__PURE__ */ jsxs31(Text32, { color: actionColor(a), children: [
5096
5226
  actionPrefix(a),
5097
5227
  " "
@@ -5114,10 +5244,18 @@ function PlanView({ plan }) {
5114
5244
  "]"
5115
5245
  ] })
5116
5246
  ] }, a.packageName + a.action)),
5117
- plan.warnings.map((w) => /* @__PURE__ */ jsx34(Box30, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs31(Text32, { color: COLORS.warning, children: [
5247
+ hiddenActions > 0 && /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, dimColor: true, children: [
5248
+ " ",
5249
+ t("scroll_moreBelow", { count: hiddenActions })
5250
+ ] }),
5251
+ plan.warnings.slice(0, 3).map((w) => /* @__PURE__ */ jsx34(Box30, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs31(Text32, { color: COLORS.warning, wrap: "wrap", children: [
5118
5252
  "\u26A0 ",
5119
5253
  w
5120
5254
  ] }) }, w)),
5255
+ plan.warnings.length > 3 && /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, dimColor: true, children: [
5256
+ " ",
5257
+ t("common_andMore", { count: plan.warnings.length - 3 })
5258
+ ] }),
5121
5259
  /* @__PURE__ */ jsx34(Box30, { marginTop: SPACING.xs, children: plan.canExecute ? /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, children: [
5122
5260
  "enter:",
5123
5261
  t("rollback_confirm", { count: String(executableCount) }),
@@ -5133,13 +5271,18 @@ function PlanView({ plan }) {
5133
5271
  function RollbackView() {
5134
5272
  const isPro = useLicenseStore((s) => s.isPro);
5135
5273
  const { snapshots, loading, error, plan, planLoading, planError, fetchSnapshots, selectSnapshot, clearPlan } = useRollbackStore();
5136
- const [cursor, setCursor] = useState16(0);
5137
- const [phase, setPhase] = useState16("list");
5138
- const [streamLines, setStreamLines] = useState16([]);
5139
- const [streamRunning, setStreamRunning] = useState16(false);
5140
- const [streamError, setStreamError] = useState16(null);
5141
- const generatorRef = useRef10(null);
5142
- const mountedRef = useRef10(true);
5274
+ const [cursor, setCursor] = useState17(0);
5275
+ const [phase, setPhase] = useState17("list");
5276
+ const [streamLines, setStreamLines] = useState17([]);
5277
+ const [streamRunning, setStreamRunning] = useState17(false);
5278
+ const [streamError, setStreamError] = useState17(null);
5279
+ const generatorRef = useRef11(null);
5280
+ const mountedRef = useRef11(true);
5281
+ const snapshotRows = useVisibleRows({
5282
+ reservedRows: 6,
5283
+ fallbackReservedRows: 14,
5284
+ minRows: 1
5285
+ });
5143
5286
  useEffect19(() => {
5144
5287
  mountedRef.current = true;
5145
5288
  return () => {
@@ -5232,22 +5375,39 @@ function RollbackView() {
5232
5375
  return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
5233
5376
  /* @__PURE__ */ jsx34(SectionHeader, { emoji: "\u23EA", title: t("rollback_title"), gradient: GRADIENTS.gold }),
5234
5377
  snapshots.length === 0 && /* @__PURE__ */ jsx34(Box30, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx34(ResultBanner, { status: "info", message: t("rollback_no_snapshots") }) }),
5235
- phase === "list" && snapshots.length > 0 && /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: SPACING.xs, children: [
5236
- /* @__PURE__ */ jsx34(Text32, { color: COLORS.textSecondary, dimColor: true, children: t("rollback_select_snapshot") }),
5237
- /* @__PURE__ */ jsx34(Box30, { flexDirection: "column", marginTop: SPACING.xs, children: snapshots.map((s, i) => /* @__PURE__ */ jsxs31(SelectableRow, { isCurrent: i === cursor, children: [
5238
- /* @__PURE__ */ jsx34(Text32, { bold: i === cursor, color: i === cursor ? COLORS.text : COLORS.muted, children: s.label ?? t("rollback_snapshot_auto") }),
5239
- /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, children: [
5240
- " \u2014 ",
5241
- new Date(s.capturedAt).toLocaleString()
5242
- ] }),
5243
- /* @__PURE__ */ jsxs31(Text32, { color: COLORS.muted, dimColor: true, children: [
5244
- " ",
5245
- "(",
5246
- tp("packages", s.formulae.length + s.casks.length),
5247
- ")"
5378
+ phase === "list" && snapshots.length > 0 && (() => {
5379
+ const start = Math.max(0, cursor - Math.floor(snapshotRows / 2));
5380
+ const visible = snapshots.slice(start, start + snapshotRows);
5381
+ return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: SPACING.xs, children: [
5382
+ /* @__PURE__ */ jsx34(Text32, { color: COLORS.textSecondary, dimColor: true, children: t("rollback_select_snapshot") }),
5383
+ /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: SPACING.xs, children: [
5384
+ start > 0 && /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, dimColor: true, children: [
5385
+ " ",
5386
+ t("scroll_moreAbove", { count: start })
5387
+ ] }),
5388
+ visible.map((s, vi) => {
5389
+ const i = start + vi;
5390
+ return /* @__PURE__ */ jsxs31(SelectableRow, { isCurrent: i === cursor, children: [
5391
+ /* @__PURE__ */ jsx34(Text32, { bold: i === cursor, color: i === cursor ? COLORS.text : COLORS.muted, children: s.label ?? t("rollback_snapshot_auto") }),
5392
+ /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, children: [
5393
+ " \u2014 ",
5394
+ new Date(s.capturedAt).toLocaleString()
5395
+ ] }),
5396
+ /* @__PURE__ */ jsxs31(Text32, { color: COLORS.muted, dimColor: true, children: [
5397
+ " ",
5398
+ "(",
5399
+ tp("packages", s.formulae.length + s.casks.length),
5400
+ ")"
5401
+ ] })
5402
+ ] }, s.capturedAt);
5403
+ }),
5404
+ start + snapshotRows < snapshots.length && /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, dimColor: true, children: [
5405
+ " ",
5406
+ t("scroll_moreBelow", { count: snapshots.length - start - snapshotRows })
5407
+ ] })
5248
5408
  ] })
5249
- ] }, s.capturedAt)) })
5250
- ] }),
5409
+ ] });
5410
+ })(),
5251
5411
  phase === "plan" && /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
5252
5412
  planLoading && /* @__PURE__ */ jsx34(Loading, { message: t("rollback_capturing") }),
5253
5413
  planError && /* @__PURE__ */ jsx34(ErrorMessage, { message: planError }),
@@ -5267,7 +5427,7 @@ function RollbackView() {
5267
5427
  }
5268
5428
 
5269
5429
  // src/views/brewfile.tsx
5270
- import { useCallback as useCallback4, useEffect as useEffect20, useRef as useRef11, useState as useState17 } from "react";
5430
+ import { useCallback as useCallback4, useEffect as useEffect20, useRef as useRef12, useState as useState18 } from "react";
5271
5431
  import { Box as Box31, Text as Text33 } from "ink";
5272
5432
  import { TextInput as TextInput6 } from "@inkjs/ui";
5273
5433
  import { jsx as jsx35, jsxs as jsxs32 } from "react/jsx-runtime";
@@ -5313,13 +5473,13 @@ function DriftSummary({ drift }) {
5313
5473
  function BrewfileView() {
5314
5474
  const isPro = useLicenseStore((s) => s.isPro);
5315
5475
  const { schema, drift, loading, driftLoading, error, load, createFromCurrent } = useBrewfileStore();
5316
- const [phase, setPhase] = useState17("overview");
5317
- const [streamLines, setStreamLines] = useState17([]);
5318
- const [streamRunning, setStreamRunning] = useState17(false);
5319
- const [streamError, setStreamError] = useState17(null);
5320
- const [resultMessage, setResultMessage] = useState17("");
5321
- const generatorRef = useRef11(null);
5322
- const mountedRef = useRef11(true);
5476
+ const [phase, setPhase] = useState18("overview");
5477
+ const [streamLines, setStreamLines] = useState18([]);
5478
+ const [streamRunning, setStreamRunning] = useState18(false);
5479
+ const [streamError, setStreamError] = useState18(null);
5480
+ const [resultMessage, setResultMessage] = useState18("");
5481
+ const generatorRef = useRef12(null);
5482
+ const mountedRef = useRef12(true);
5323
5483
  useEffect20(() => {
5324
5484
  mountedRef.current = true;
5325
5485
  void load();
@@ -5496,7 +5656,7 @@ function BrewfileView() {
5496
5656
  }
5497
5657
 
5498
5658
  // src/views/sync.tsx
5499
- import { useCallback as useCallback5, useEffect as useEffect21, useState as useState18 } from "react";
5659
+ import { useCallback as useCallback5, useEffect as useEffect21, useState as useState19 } from "react";
5500
5660
  import { Box as Box32, Text as Text34 } from "ink";
5501
5661
  import { Fragment as Fragment6, jsx as jsx36, jsxs as jsxs33 } from "react/jsx-runtime";
5502
5662
  function OverviewSection({
@@ -5547,8 +5707,22 @@ function ConflictsList({
5547
5707
  entries,
5548
5708
  cursor
5549
5709
  }) {
5710
+ const availableRows = useVisibleRows({
5711
+ reservedRows: 8,
5712
+ fallbackReservedRows: 14,
5713
+ minRows: 4
5714
+ });
5715
+ const perEntryRows = 4;
5716
+ const maxEntries = Math.max(1, Math.floor(availableRows / perEntryRows));
5717
+ const start = Math.max(0, cursor - Math.floor(maxEntries / 2));
5718
+ const visible = entries.slice(start, start + maxEntries);
5550
5719
  return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", marginTop: SPACING.xs, children: [
5551
- entries.map((entry, i) => {
5720
+ start > 0 && /* @__PURE__ */ jsxs33(Text34, { color: COLORS.textSecondary, dimColor: true, children: [
5721
+ " ",
5722
+ t("scroll_moreAbove", { count: start })
5723
+ ] }),
5724
+ visible.map((entry, vi) => {
5725
+ const i = start + vi;
5552
5726
  const { conflict, resolution } = entry;
5553
5727
  const isActive = i === cursor;
5554
5728
  return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", marginBottom: SPACING.xs, children: [
@@ -5586,6 +5760,10 @@ function ConflictsList({
5586
5760
  ] })
5587
5761
  ] }, `${conflict.packageName}-${conflict.remoteMachine}`);
5588
5762
  }),
5763
+ start + maxEntries < entries.length && /* @__PURE__ */ jsxs33(Text34, { color: COLORS.textSecondary, dimColor: true, children: [
5764
+ " ",
5765
+ t("scroll_moreBelow", { count: entries.length - start - maxEntries })
5766
+ ] }),
5589
5767
  /* @__PURE__ */ jsx36(Box32, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs33(Text34, { color: COLORS.textSecondary, children: [
5590
5768
  "j/k:",
5591
5769
  t("hint_navigate"),
@@ -5604,10 +5782,10 @@ function SyncView() {
5604
5782
  const isPro = useLicenseStore((s) => s.isPro);
5605
5783
  const navigate = useNavigationStore((s) => s.navigate);
5606
5784
  const { config, lastResult, conflicts, loading, error, initialize, syncNow, resolveConflicts } = useSyncStore();
5607
- const [phase, setPhase] = useState18("overview");
5608
- const [syncError, setSyncError] = useState18(null);
5609
- const [conflictEntries, setConflictEntries] = useState18([]);
5610
- const [cursor, setCursor] = useState18(0);
5785
+ const [phase, setPhase] = useState19("overview");
5786
+ const [syncError, setSyncError] = useState19(null);
5787
+ const [conflictEntries, setConflictEntries] = useState19([]);
5788
+ const [cursor, setCursor] = useState19(0);
5611
5789
  useEffect21(() => {
5612
5790
  void initialize(isPro());
5613
5791
  }, []);
@@ -5776,7 +5954,7 @@ function SyncView() {
5776
5954
  }
5777
5955
 
5778
5956
  // src/views/compliance.tsx
5779
- import { useCallback as useCallback6, useEffect as useEffect22, useRef as useRef12, useState as useState19 } from "react";
5957
+ import { useCallback as useCallback6, useEffect as useEffect22, useRef as useRef13, useState as useState20 } from "react";
5780
5958
  import { Box as Box33, Text as Text35 } from "ink";
5781
5959
  import { TextInput as TextInput7 } from "@inkjs/ui";
5782
5960
 
@@ -5857,38 +6035,58 @@ function ViolationItem({ violation }) {
5857
6035
  prefix,
5858
6036
  " "
5859
6037
  ] }),
5860
- /* @__PURE__ */ jsx37(Text35, { color, children: violation.detail })
6038
+ /* @__PURE__ */ jsx37(Text35, { color, wrap: "wrap", children: violation.detail })
5861
6039
  ] });
5862
6040
  }
5863
6041
  function ViolationList({ violations }) {
5864
6042
  const errors = violations.filter((v) => v.severity === "error");
5865
6043
  const warnings = violations.filter((v) => v.severity === "warning");
6044
+ const totalRows = useVisibleRows({
6045
+ reservedRows: 11,
6046
+ fallbackReservedRows: 18,
6047
+ minRows: 2
6048
+ });
6049
+ const total = errors.length + warnings.length;
6050
+ const errorBudget = total === 0 ? 0 : Math.min(errors.length, Math.max(1, Math.round(errors.length / total * totalRows)));
6051
+ const warningBudget = Math.max(0, totalRows - errorBudget);
6052
+ const visibleErrors = errors.slice(0, errorBudget);
6053
+ const visibleWarnings = warnings.slice(0, warningBudget);
6054
+ const errorsHidden = errors.length - visibleErrors.length;
6055
+ const warningsHidden = warnings.length - visibleWarnings.length;
5866
6056
  return /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", marginTop: SPACING.xs, children: [
5867
6057
  errors.length > 0 && /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", marginBottom: SPACING.xs, children: [
5868
6058
  /* @__PURE__ */ jsxs34(Text35, { color: COLORS.error, bold: true, children: [
5869
6059
  t("compliance_violations", { count: String(errors.length) }),
5870
6060
  " (errors)"
5871
6061
  ] }),
5872
- errors.map((v) => /* @__PURE__ */ jsx37(ViolationItem, { violation: v }, `${v.type}-${v.packageName}`))
6062
+ visibleErrors.map((v) => /* @__PURE__ */ jsx37(ViolationItem, { violation: v }, `${v.type}-${v.packageName}`)),
6063
+ errorsHidden > 0 && /* @__PURE__ */ jsxs34(Text35, { color: COLORS.textSecondary, dimColor: true, children: [
6064
+ " ",
6065
+ t("scroll_moreBelow", { count: errorsHidden })
6066
+ ] })
5873
6067
  ] }),
5874
6068
  warnings.length > 0 && /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", children: [
5875
6069
  /* @__PURE__ */ jsxs34(Text35, { color: COLORS.warning, bold: true, children: [
5876
6070
  t("compliance_violations", { count: String(warnings.length) }),
5877
6071
  " (warnings)"
5878
6072
  ] }),
5879
- warnings.map((v) => /* @__PURE__ */ jsx37(ViolationItem, { violation: v }, `${v.type}-${v.packageName}`))
6073
+ visibleWarnings.map((v) => /* @__PURE__ */ jsx37(ViolationItem, { violation: v }, `${v.type}-${v.packageName}`)),
6074
+ warningsHidden > 0 && /* @__PURE__ */ jsxs34(Text35, { color: COLORS.textSecondary, dimColor: true, children: [
6075
+ " ",
6076
+ t("scroll_moreBelow", { count: warningsHidden })
6077
+ ] })
5880
6078
  ] })
5881
6079
  ] });
5882
6080
  }
5883
6081
  function ComplianceView() {
5884
6082
  const isPro = useLicenseStore((s) => s.isPro);
5885
6083
  const { policy, report, loading, error, importPolicy, runCheck } = useComplianceStore();
5886
- const [phase, setPhase] = useState19("overview");
5887
- const [resultMessage, setResultMessage] = useState19(null);
5888
- const [streamLines, setStreamLines] = useState19([]);
5889
- const [streamRunning, setStreamRunning] = useState19(false);
5890
- const generatorRef = useRef12(null);
5891
- const mountedRef = useRef12(true);
6084
+ const [phase, setPhase] = useState20("overview");
6085
+ const [resultMessage, setResultMessage] = useState20(null);
6086
+ const [streamLines, setStreamLines] = useState20([]);
6087
+ const [streamRunning, setStreamRunning] = useState20(false);
6088
+ const generatorRef = useRef13(null);
6089
+ const mountedRef = useRef13(true);
5892
6090
  useEffect22(() => {
5893
6091
  mountedRef.current = true;
5894
6092
  return () => {
@@ -6147,7 +6345,7 @@ function App() {
6147
6345
  const { exit } = useApp();
6148
6346
  const currentView = useNavigationStore((s) => s.currentView);
6149
6347
  const isTestEnv = typeof process !== "undefined" && false;
6150
- const [showWelcome, setShowWelcome] = useState20(isTestEnv ? false : null);
6348
+ const [showWelcome, setShowWelcome] = useState21(isTestEnv ? false : null);
6151
6349
  useEffect23(() => {
6152
6350
  if (isTestEnv) return;
6153
6351
  void hasCompletedOnboarding().then((done) => setShowWelcome(!done));
@@ -6245,7 +6443,7 @@ async function reportError(err, context = {}) {
6245
6443
  const config = await resolveConfig();
6246
6444
  if (!config.enabled || !config.endpoint) return;
6247
6445
  const machineId = await getMachineId();
6248
- const version = true ? "1.0.0" : "unknown";
6446
+ const version = true ? "1.2.0" : "unknown";
6249
6447
  await postReport(buildReport("error", err, context, machineId, version), config);
6250
6448
  }
6251
6449
  async function installCrashReporter() {
@@ -6254,7 +6452,7 @@ async function installCrashReporter() {
6254
6452
  if (!config.enabled || !config.endpoint) return;
6255
6453
  _installed = true;
6256
6454
  const machineId = await getMachineId();
6257
- const version = true ? "1.0.0" : "unknown";
6455
+ const version = true ? "1.2.0" : "unknown";
6258
6456
  process.on("uncaughtException", (err) => {
6259
6457
  void postReport(buildReport("fatal", err, { kind: "uncaughtException" }, machineId, version), config);
6260
6458
  });
@@ -6269,7 +6467,7 @@ import { jsx as jsx39 } from "react/jsx-runtime";
6269
6467
  var [, , command, arg] = process.argv;
6270
6468
  async function runCli() {
6271
6469
  if (command === "--version" || command === "-v" || command === "version") {
6272
- process.stdout.write("1.0.0\n");
6470
+ process.stdout.write("1.2.0\n");
6273
6471
  return;
6274
6472
  }
6275
6473
  await ensureDataDirs();
@@ -6452,7 +6650,7 @@ async function ensureBrewBarRunning() {
6452
6650
  await useLicenseStore.getState().initialize();
6453
6651
  if (!useLicenseStore.getState().isPro()) return;
6454
6652
  const { isBrewBarInstalled, installBrewBar, launchBrewBar } = await import("./brewbar-installer-GWJ76J6G.js");
6455
- const { checkBrewBarVersion } = await import("./version-check-J6PTTQ7M.js");
6653
+ const { checkBrewBarVersion } = await import("./version-check-MJZDQG73.js");
6456
6654
  try {
6457
6655
  if (!await isBrewBarInstalled()) {
6458
6656
  console.log(t("cli_brewbarInstalling"));