brew-tui 0.9.2 → 1.1.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,14 +66,40 @@ 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
73
+ import { useRef } from "react";
73
74
  import { Box as Box3 } from "ink";
74
75
 
75
76
  // src/components/layout/header.tsx
76
- 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
+ }
77
103
 
78
104
  // src/stores/navigation-store.ts
79
105
  import { create } from "zustand";
@@ -296,7 +322,7 @@ var GRADIENTS = {
296
322
  };
297
323
 
298
324
  // src/components/common/blinking-text.tsx
299
- import { useEffect, useState } from "react";
325
+ import { useEffect as useEffect2, useState as useState2 } from "react";
300
326
  import { Text as Text2 } from "ink";
301
327
  import { jsx as jsx2 } from "react/jsx-runtime";
302
328
  function BlinkingText({
@@ -305,8 +331,8 @@ function BlinkingText({
305
331
  bold = true,
306
332
  children
307
333
  }) {
308
- const [bright, setBright] = useState(true);
309
- useEffect(() => {
334
+ const [bright, setBright] = useState2(true);
335
+ useEffect2(() => {
310
336
  const id = setInterval(() => setBright((b) => !b), intervalMs);
311
337
  return () => clearInterval(id);
312
338
  }, [intervalMs]);
@@ -388,9 +414,11 @@ function Header() {
388
414
  const menuMode = useNavigationStore((s) => s.menuMode);
389
415
  const menuCursor = useNavigationStore((s) => s.menuCursor);
390
416
  useLocaleStore((s) => s.locale);
391
- const { stdout } = useStdout();
392
- const cols = stdout?.columns ?? 80;
393
- const isNarrow = cols < 95;
417
+ const { columns, rows } = useTerminalSize();
418
+ const isNarrow = columns < 95;
419
+ const hideLogoByWidth = columns < 45;
420
+ const hideLogoByHeight = rows < 32;
421
+ const collapseMenu = rows < 22 && !menuMode;
394
422
  const cursorView = menuMode ? MENU_VIEWS[menuCursor] ?? null : null;
395
423
  const logoBlock = /* @__PURE__ */ jsx3(Box, { flexDirection: "column", flexShrink: 0, children: LOGO_BREW.map((brew, i) => /* @__PURE__ */ jsxs(Box, { children: [
396
424
  /* @__PURE__ */ jsx3(GradientText, { colors: GRADIENTS.gold, children: brew }),
@@ -411,12 +439,30 @@ function Header() {
411
439
  t("hint_menuOpen_suffix")
412
440
  ] }) })
413
441
  ] });
442
+ if (collapseMenu) {
443
+ const currentLabel = t(VIEW_LABEL_KEYS[currentView]);
444
+ const currentIsPro = isProView(currentView) || isTeamView(currentView);
445
+ return /* @__PURE__ */ jsxs(Box, { paddingX: SPACING.xs, children: [
446
+ /* @__PURE__ */ jsx3(Text3, { color: COLORS.success, bold: true, children: "\u25B6 " }),
447
+ /* @__PURE__ */ jsx3(Text3, { color: COLORS.success, bold: true, children: currentLabel }),
448
+ currentIsPro && /* @__PURE__ */ jsxs(Text3, { color: COLORS.brand, bold: true, children: [
449
+ " ",
450
+ t("pro_badge")
451
+ ] }),
452
+ /* @__PURE__ */ jsx3(Text3, { color: COLORS.textSecondary, children: " " }),
453
+ /* @__PURE__ */ jsx3(BlinkingText, { color: COLORS.brand, children: "M" }),
454
+ /* @__PURE__ */ jsx3(Text3, { color: COLORS.textSecondary, children: t("hint_menuOpen_suffix") })
455
+ ] });
456
+ }
414
457
  if (isNarrow) {
415
458
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: SPACING.xs, children: [
416
- logoBlock,
417
- /* @__PURE__ */ jsx3(Box, { marginTop: SPACING.xs, children: menuBlock })
459
+ !hideLogoByWidth && !hideLogoByHeight && logoBlock,
460
+ /* @__PURE__ */ jsx3(Box, { marginTop: hideLogoByWidth || hideLogoByHeight ? SPACING.none : SPACING.xs, children: menuBlock })
418
461
  ] });
419
462
  }
463
+ if (hideLogoByHeight) {
464
+ return /* @__PURE__ */ jsx3(Box, { flexDirection: "column", paddingX: SPACING.xs, children: menuBlock });
465
+ }
420
466
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", paddingX: SPACING.xs, alignItems: "center", children: [
421
467
  logoBlock,
422
468
  /* @__PURE__ */ jsx3(Box, { marginLeft: SPACING.sm, children: menuBlock })
@@ -459,8 +505,9 @@ function Footer() {
459
505
  const currentView = useNavigationStore((s) => s.currentView);
460
506
  const menuMode = useNavigationStore((s) => s.menuMode);
461
507
  const locale = useLocaleStore((s) => s.locale);
508
+ const { rows } = useTerminalSize();
462
509
  const defs = VIEW_HINT_DEFS[currentView] ?? [];
463
- const showChoose = hasNumberedActions(defs) && !menuMode;
510
+ const showChoose = hasNumberedActions(defs) && !menuMode && rows >= 26;
464
511
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
465
512
  showChoose && /* @__PURE__ */ jsx4(Box2, { paddingX: SPACING.xs, children: /* @__PURE__ */ jsx4(Text4, { color: COLORS.text, children: t("hint_chooseNumber") }) }),
466
513
  /* @__PURE__ */ jsxs2(Box2, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: COLORS.gold, paddingX: SPACING.xs, flexWrap: "wrap", children: [
@@ -505,13 +552,64 @@ function Footer() {
505
552
  ] });
506
553
  }
507
554
 
555
+ // src/hooks/use-container-size.ts
556
+ import { useEffect as useEffect3, useState as useState3 } from "react";
557
+ import { measureElement } from "ink";
558
+ function useContainerSize(ref) {
559
+ const terminal = useTerminalSize();
560
+ const [size, setSize] = useState3({ width: 0, height: 0 });
561
+ useEffect3(() => {
562
+ if (!ref.current) return;
563
+ const measured = measureElement(ref.current);
564
+ setSize(
565
+ (prev) => prev.width === measured.width && prev.height === measured.height ? prev : measured
566
+ );
567
+ }, [ref, terminal.columns, terminal.rows]);
568
+ return size;
569
+ }
570
+
571
+ // src/components/layout/content-size-context.tsx
572
+ import { createContext, useContext } from "react";
573
+ import { jsx as jsx5 } from "react/jsx-runtime";
574
+ var EMPTY_CONTENT_SIZE = { width: 0, height: 0 };
575
+ var ContentSizeContext = createContext(EMPTY_CONTENT_SIZE);
576
+ function ContentSizeProvider({
577
+ children,
578
+ value
579
+ }) {
580
+ return /* @__PURE__ */ jsx5(ContentSizeContext.Provider, { value, children });
581
+ }
582
+ function useContentSize() {
583
+ return useContext(ContentSizeContext);
584
+ }
585
+
508
586
  // src/components/layout/app-layout.tsx
509
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
587
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
510
588
  function AppLayout({ children }) {
511
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", width: "100%", children: [
512
- /* @__PURE__ */ jsx5(Header, {}),
513
- /* @__PURE__ */ jsx5(Box3, { flexDirection: "column", flexGrow: 1, paddingX: SPACING.sm, paddingY: SPACING.xs, children }),
514
- /* @__PURE__ */ jsx5(Footer, {})
589
+ const { rows } = useTerminalSize();
590
+ const contentRef = useRef(null);
591
+ const contentSize = useContainerSize(contentRef);
592
+ const innerContentSize = {
593
+ width: Math.max(0, contentSize.width - SPACING.sm * 2),
594
+ height: Math.max(0, contentSize.height - SPACING.xs * 2)
595
+ };
596
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", width: "100%", height: rows, overflow: "hidden", children: [
597
+ /* @__PURE__ */ jsx6(Box3, { flexShrink: 0, children: /* @__PURE__ */ jsx6(Header, {}) }),
598
+ /* @__PURE__ */ jsx6(
599
+ Box3,
600
+ {
601
+ ref: contentRef,
602
+ flexDirection: "column",
603
+ flexGrow: 1,
604
+ flexShrink: 1,
605
+ minHeight: 0,
606
+ overflow: "hidden",
607
+ paddingX: SPACING.sm,
608
+ paddingY: SPACING.xs,
609
+ children: /* @__PURE__ */ jsx6(ContentSizeProvider, { value: innerContentSize, children })
610
+ }
611
+ ),
612
+ /* @__PURE__ */ jsx6(Box3, { flexShrink: 0, children: /* @__PURE__ */ jsx6(Footer, {}) })
515
613
  ] });
516
614
  }
517
615
 
@@ -737,7 +835,7 @@ async function markOnboardingComplete() {
737
835
  }
738
836
 
739
837
  // src/views/welcome.tsx
740
- import { useEffect as useEffect2 } from "react";
838
+ import { useEffect as useEffect4 } from "react";
741
839
  import { Box as Box4, Text as Text5 } from "ink";
742
840
 
743
841
  // src/hooks/use-view-input.ts
@@ -749,9 +847,9 @@ function useViewInput(handler, opts) {
749
847
  }
750
848
 
751
849
  // src/views/welcome.tsx
752
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
850
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
753
851
  function WelcomeView({ onContinue }) {
754
- useEffect2(() => {
852
+ useEffect4(() => {
755
853
  return () => {
756
854
  };
757
855
  }, []);
@@ -760,65 +858,65 @@ function WelcomeView({ onContinue }) {
760
858
  void markOnboardingComplete().finally(onContinue);
761
859
  }
762
860
  });
763
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingY: SPACING.md, paddingX: SPACING.lg, children: [
764
- /* @__PURE__ */ jsx6(Box4, { children: /* @__PURE__ */ jsx6(GradientText, { colors: GRADIENTS.gold, bold: true, children: t("welcome_title") }) }),
765
- /* @__PURE__ */ jsx6(Box4, { marginTop: SPACING.sm, children: /* @__PURE__ */ jsx6(Text5, { color: COLORS.text, children: t("welcome_intro") }) }),
766
- /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: SPACING.sm, children: [
767
- /* @__PURE__ */ jsx6(Text5, { color: COLORS.muted, children: t("welcome_keysHeader") }),
768
- /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: SPACING.sm, marginTop: SPACING.xs, children: [
861
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingY: SPACING.xs, paddingX: SPACING.sm, flexShrink: 1, children: [
862
+ /* @__PURE__ */ jsx7(Box4, { children: /* @__PURE__ */ jsx7(GradientText, { colors: GRADIENTS.gold, bold: true, children: t("welcome_title") }) }),
863
+ /* @__PURE__ */ jsx7(Box4, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx7(Text5, { color: COLORS.text, wrap: "wrap", children: t("welcome_intro") }) }),
864
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: SPACING.xs, children: [
865
+ /* @__PURE__ */ jsx7(Text5, { color: COLORS.muted, children: t("welcome_keysHeader") }),
866
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: SPACING.sm, children: [
769
867
  /* @__PURE__ */ jsxs4(Text5, { children: [
770
- /* @__PURE__ */ jsx6(Text5, { color: COLORS.gold, bold: true, children: "m" }),
868
+ /* @__PURE__ */ jsx7(Text5, { color: COLORS.gold, bold: true, children: "m" }),
771
869
  " ",
772
870
  t("welcome_keyMenu")
773
871
  ] }),
774
872
  /* @__PURE__ */ jsxs4(Text5, { children: [
775
- /* @__PURE__ */ jsx6(Text5, { color: COLORS.gold, bold: true, children: "\u2191 \u2193" }),
873
+ /* @__PURE__ */ jsx7(Text5, { color: COLORS.gold, bold: true, children: "\u2191 \u2193" }),
776
874
  " ",
777
875
  t("welcome_keyMove")
778
876
  ] }),
779
877
  /* @__PURE__ */ jsxs4(Text5, { children: [
780
- /* @__PURE__ */ jsx6(Text5, { color: COLORS.gold, bold: true, children: "1-9" }),
878
+ /* @__PURE__ */ jsx7(Text5, { color: COLORS.gold, bold: true, children: "1-9" }),
781
879
  " ",
782
880
  t("welcome_keyAction")
783
881
  ] }),
784
882
  /* @__PURE__ */ jsxs4(Text5, { children: [
785
- /* @__PURE__ */ jsx6(Text5, { color: COLORS.gold, bold: true, children: "Enter" }),
883
+ /* @__PURE__ */ jsx7(Text5, { color: COLORS.gold, bold: true, children: "Enter" }),
786
884
  " ",
787
885
  t("welcome_keySelect")
788
886
  ] }),
789
887
  /* @__PURE__ */ jsxs4(Text5, { children: [
790
- /* @__PURE__ */ jsx6(Text5, { color: COLORS.gold, bold: true, children: "S" }),
888
+ /* @__PURE__ */ jsx7(Text5, { color: COLORS.gold, bold: true, children: "S" }),
791
889
  " ",
792
890
  t("welcome_keySearch")
793
891
  ] }),
794
892
  /* @__PURE__ */ jsxs4(Text5, { children: [
795
- /* @__PURE__ */ jsx6(Text5, { color: COLORS.gold, bold: true, children: "Esc" }),
893
+ /* @__PURE__ */ jsx7(Text5, { color: COLORS.gold, bold: true, children: "Esc" }),
796
894
  " ",
797
895
  t("welcome_keyBack")
798
896
  ] }),
799
897
  /* @__PURE__ */ jsxs4(Text5, { children: [
800
- /* @__PURE__ */ jsx6(Text5, { color: COLORS.gold, bold: true, children: "L" }),
898
+ /* @__PURE__ */ jsx7(Text5, { color: COLORS.gold, bold: true, children: "L" }),
801
899
  " ",
802
900
  t("welcome_keyLocale")
803
901
  ] }),
804
902
  /* @__PURE__ */ jsxs4(Text5, { children: [
805
- /* @__PURE__ */ jsx6(Text5, { color: COLORS.gold, bold: true, children: "q" }),
903
+ /* @__PURE__ */ jsx7(Text5, { color: COLORS.gold, bold: true, children: "q" }),
806
904
  " ",
807
905
  t("welcome_keyQuit")
808
906
  ] })
809
907
  ] })
810
908
  ] }),
811
- /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: SPACING.sm, children: [
812
- /* @__PURE__ */ jsx6(Text5, { color: COLORS.muted, children: t("welcome_proHeader") }),
813
- /* @__PURE__ */ jsx6(Box4, { paddingLeft: SPACING.sm, children: /* @__PURE__ */ jsx6(Text5, { color: COLORS.textSecondary, children: t("welcome_proIntro") }) })
909
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: SPACING.xs, children: [
910
+ /* @__PURE__ */ jsx7(Text5, { color: COLORS.muted, children: t("welcome_proHeader") }),
911
+ /* @__PURE__ */ jsx7(Box4, { paddingLeft: SPACING.sm, children: /* @__PURE__ */ jsx7(Text5, { color: COLORS.textSecondary, wrap: "wrap", children: t("welcome_proIntro") }) })
814
912
  ] }),
815
- /* @__PURE__ */ jsx6(Box4, { marginTop: SPACING.md, children: /* @__PURE__ */ jsx6(Text5, { color: COLORS.success, bold: true, children: t("welcome_continueHint") }) })
913
+ /* @__PURE__ */ jsx7(Box4, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx7(Text5, { color: COLORS.success, bold: true, children: t("welcome_continueHint") }) })
816
914
  ] });
817
915
  }
818
916
 
819
917
  // src/components/common/upgrade-prompt.tsx
820
918
  import { Box as Box5, Text as Text6 } from "ink";
821
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
919
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
822
920
  var FEATURE_KEYS = {
823
921
  profiles: { title: "upgrade_profiles", desc: "upgrade_profilesDesc" },
824
922
  "smart-cleanup": { title: "upgrade_cleanup", desc: "upgrade_cleanupDesc" },
@@ -838,15 +936,16 @@ function UpgradePrompt({ viewId }) {
838
936
  const pricingKey = team ? "upgrade_teamPricing" : "upgrade_pricing";
839
937
  const buyUrlKey = team ? "upgrade_buyUrlTeam" : "upgrade_buyUrl";
840
938
  const labelKey = team ? "upgrade_teamLabel" : "upgrade_proLabel";
841
- return /* @__PURE__ */ jsx7(Box5, { flexDirection: "column", alignItems: "center", paddingY: SPACING.sm, children: /* @__PURE__ */ jsxs5(
939
+ return /* @__PURE__ */ jsx8(Box5, { flexDirection: "column", alignItems: "center", paddingY: SPACING.xs, children: /* @__PURE__ */ jsxs5(
842
940
  Box5,
843
941
  {
844
942
  borderStyle: "double",
845
943
  borderColor: COLORS.brand,
846
- paddingX: SPACING.md,
847
- paddingY: SPACING.sm,
944
+ paddingX: SPACING.sm,
945
+ paddingY: SPACING.none,
848
946
  flexDirection: "column",
849
947
  alignItems: "center",
948
+ flexShrink: 1,
850
949
  width: "80%",
851
950
  children: [
852
951
  /* @__PURE__ */ jsxs5(Text6, { bold: true, color: COLORS.brand, children: [
@@ -854,25 +953,14 @@ function UpgradePrompt({ viewId }) {
854
953
  " ",
855
954
  t(headerKey, { title })
856
955
  ] }),
857
- /* @__PURE__ */ jsx7(Text6, { children: " " }),
858
- /* @__PURE__ */ jsx7(Text6, { color: COLORS.text, wrap: "wrap", children: t(keys.desc) }),
859
- /* @__PURE__ */ jsx7(Text6, { children: " " }),
860
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", alignItems: "center", children: [
861
- /* @__PURE__ */ jsx7(Text6, { color: COLORS.info, bold: true, children: t(pricingKey) }),
862
- /* @__PURE__ */ jsx7(Text6, { children: " " }),
863
- /* @__PURE__ */ jsx7(Text6, { color: COLORS.muted, children: t("upgrade_buyAt") }),
864
- /* @__PURE__ */ jsxs5(Text6, { color: COLORS.sky, bold: true, children: [
865
- " ",
866
- t(buyUrlKey)
867
- ] }),
868
- /* @__PURE__ */ jsx7(Text6, { children: " " }),
869
- /* @__PURE__ */ jsx7(Text6, { color: COLORS.muted, children: t("upgrade_activateWith") }),
870
- /* @__PURE__ */ jsxs5(Text6, { color: COLORS.success, bold: true, children: [
871
- " ",
872
- t("upgrade_activateCmd")
873
- ] }),
874
- /* @__PURE__ */ jsx7(Text6, { children: " " }),
875
- /* @__PURE__ */ jsx7(Text6, { color: COLORS.brand, children: t(labelKey) })
956
+ /* @__PURE__ */ jsx8(Text6, { color: COLORS.text, wrap: "wrap", children: t(keys.desc) }),
957
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", alignItems: "center", marginTop: SPACING.xs, children: [
958
+ /* @__PURE__ */ jsx8(Text6, { color: COLORS.info, bold: true, children: t(pricingKey) }),
959
+ /* @__PURE__ */ jsx8(Text6, { color: COLORS.muted, children: t("upgrade_buyAt") }),
960
+ /* @__PURE__ */ jsx8(Text6, { color: COLORS.sky, bold: true, children: t(buyUrlKey) }),
961
+ /* @__PURE__ */ jsx8(Text6, { color: COLORS.muted, children: t("upgrade_activateWith") }),
962
+ /* @__PURE__ */ jsx8(Text6, { color: COLORS.success, bold: true, children: t("upgrade_activateCmd") }),
963
+ /* @__PURE__ */ jsx8(Text6, { color: COLORS.brand, children: t(labelKey) })
876
964
  ] })
877
965
  ]
878
966
  }
@@ -880,8 +968,20 @@ function UpgradePrompt({ viewId }) {
880
968
  }
881
969
 
882
970
  // src/views/dashboard.tsx
883
- import { useEffect as useEffect3, useMemo as useMemo2 } from "react";
884
- import { Box as Box9, Text as Text12, useStdout as useStdout3 } from "ink";
971
+ import { useEffect as useEffect5, useMemo as useMemo2 } from "react";
972
+ import { Box as Box9, Text as Text12 } from "ink";
973
+
974
+ // src/hooks/use-visible-rows.ts
975
+ function useVisibleRows({
976
+ reservedRows,
977
+ fallbackReservedRows = reservedRows,
978
+ minRows = 3
979
+ }) {
980
+ const { height: contentHeight } = useContentSize();
981
+ const { rows: terminalRows } = useTerminalSize();
982
+ const availableRows = contentHeight > 0 ? contentHeight - reservedRows : terminalRows - fallbackReservedRows;
983
+ return Math.max(minRows, Math.floor(availableRows));
984
+ }
885
985
 
886
986
  // src/stores/brew-store.ts
887
987
  import { create as create4 } from "zustand";
@@ -1847,12 +1947,11 @@ var useComplianceStore = create8((set, get) => ({
1847
1947
  }));
1848
1948
 
1849
1949
  // src/components/common/stat-card.tsx
1850
- import { Box as Box6, Text as Text7, useStdout as useStdout2 } from "ink";
1851
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1950
+ import { Box as Box6, Text as Text7 } from "ink";
1951
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1852
1952
  function StatCard({ label, value, color = COLORS.white }) {
1853
- const { stdout } = useStdout2();
1854
- const cols = stdout?.columns ?? 80;
1855
- const minW = cols < 60 ? 12 : cols < 100 ? 14 : 16;
1953
+ const { columns } = useTerminalSize();
1954
+ const minW = columns < 60 ? 12 : columns < 100 ? 14 : 16;
1856
1955
  return /* @__PURE__ */ jsxs6(
1857
1956
  Box6,
1858
1957
  {
@@ -1862,10 +1961,11 @@ function StatCard({ label, value, color = COLORS.white }) {
1862
1961
  paddingY: SPACING.none,
1863
1962
  flexDirection: "column",
1864
1963
  alignItems: "center",
1964
+ flexShrink: 1,
1865
1965
  minWidth: minW,
1866
1966
  children: [
1867
- /* @__PURE__ */ jsx8(Text7, { bold: true, color, children: value }),
1868
- /* @__PURE__ */ jsx8(Text7, { color: COLORS.muted, children: label })
1967
+ /* @__PURE__ */ jsx9(Text7, { bold: true, color, children: value }),
1968
+ /* @__PURE__ */ jsx9(Text7, { color: COLORS.muted, children: label })
1869
1969
  ]
1870
1970
  }
1871
1971
  );
@@ -1874,10 +1974,10 @@ function StatCard({ label, value, color = COLORS.white }) {
1874
1974
  // src/components/common/loading.tsx
1875
1975
  import { Box as Box7, Text as Text8 } from "ink";
1876
1976
  import { Spinner } from "@inkjs/ui";
1877
- import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1977
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1878
1978
  function Loading({ message }) {
1879
1979
  useLocaleStore((s) => s.locale);
1880
- return /* @__PURE__ */ jsx9(Box7, { paddingY: SPACING.xs, children: /* @__PURE__ */ jsx9(Spinner, { label: message ?? t("loading_default") }) });
1980
+ return /* @__PURE__ */ jsx10(Box7, { paddingY: SPACING.xs, children: /* @__PURE__ */ jsx10(Spinner, { label: message ?? t("loading_default") }) });
1881
1981
  }
1882
1982
  function ErrorMessage({ message }) {
1883
1983
  useLocaleStore((s) => s.locale);
@@ -1887,7 +1987,7 @@ function ErrorMessage({ message }) {
1887
1987
  " ",
1888
1988
  t("error_prefix")
1889
1989
  ] }),
1890
- /* @__PURE__ */ jsx9(Text8, { color: COLORS.error, children: message })
1990
+ /* @__PURE__ */ jsx10(Text8, { color: COLORS.error, children: message })
1891
1991
  ] });
1892
1992
  }
1893
1993
 
@@ -1912,14 +2012,14 @@ function StatusBadge({ label, variant }) {
1912
2012
 
1913
2013
  // src/components/common/section-header.tsx
1914
2014
  import { Box as Box8, Text as Text10 } from "ink";
1915
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2015
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1916
2016
  function SectionHeader({ emoji, title, color = COLORS.gold, gradient, count }) {
1917
2017
  return /* @__PURE__ */ jsxs9(Box8, { gap: SPACING.xs, children: [
1918
2018
  /* @__PURE__ */ jsxs9(Text10, { children: [
1919
2019
  emoji,
1920
2020
  " "
1921
2021
  ] }),
1922
- gradient ? /* @__PURE__ */ jsx10(GradientText, { colors: gradient, bold: true, children: title }) : /* @__PURE__ */ jsx10(Text10, { bold: true, color, children: title }),
2022
+ gradient ? /* @__PURE__ */ jsx11(GradientText, { colors: gradient, bold: true, children: title }) : /* @__PURE__ */ jsx11(Text10, { bold: true, color, children: title }),
1923
2023
  count !== void 0 && /* @__PURE__ */ jsxs9(Text10, { color: COLORS.textSecondary, children: [
1924
2024
  "(",
1925
2025
  count,
@@ -1930,22 +2030,22 @@ function SectionHeader({ emoji, title, color = COLORS.gold, gradient, count }) {
1930
2030
 
1931
2031
  // src/components/common/version-arrow.tsx
1932
2032
  import { Text as Text11 } from "ink";
1933
- import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2033
+ import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1934
2034
  function VersionArrow({ current, latest }) {
1935
2035
  return /* @__PURE__ */ jsxs10(Fragment3, { children: [
1936
2036
  /* @__PURE__ */ jsxs10(Text11, { color: COLORS.muted, children: [
1937
2037
  t("version_installed"),
1938
2038
  " "
1939
2039
  ] }),
1940
- /* @__PURE__ */ jsx11(Text11, { color: COLORS.error, children: current }),
1941
- /* @__PURE__ */ jsx11(Text11, { color: COLORS.warning, children: " \u2500\u2500 " }),
1942
- /* @__PURE__ */ jsx11(Text11, { color: COLORS.gold, children: "\u25B6" }),
2040
+ /* @__PURE__ */ jsx12(Text11, { color: COLORS.error, children: current }),
2041
+ /* @__PURE__ */ jsx12(Text11, { color: COLORS.warning, children: " \u2500\u2500 " }),
2042
+ /* @__PURE__ */ jsx12(Text11, { color: COLORS.gold, children: "\u25B6" }),
1943
2043
  /* @__PURE__ */ jsxs10(Text11, { color: COLORS.muted, children: [
1944
2044
  " ",
1945
2045
  t("version_available"),
1946
2046
  " "
1947
2047
  ] }),
1948
- /* @__PURE__ */ jsx11(Text11, { color: COLORS.teal, children: latest })
2048
+ /* @__PURE__ */ jsx12(Text11, { color: COLORS.teal, children: latest })
1949
2049
  ] });
1950
2050
  }
1951
2051
 
@@ -1977,7 +2077,7 @@ function truncate(str, maxLen) {
1977
2077
  }
1978
2078
 
1979
2079
  // src/views/dashboard.tsx
1980
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2080
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1981
2081
  function ProStatusPanel() {
1982
2082
  const security = useSecurityStore((s) => s.summary);
1983
2083
  const drift = useBrewfileStore((s) => s.drift);
@@ -1990,28 +2090,28 @@ function ProStatusPanel() {
1990
2090
  const syncAgo = lastSync ? formatRelativeTime(new Date(lastSync).getTime() / 1e3) : null;
1991
2091
  const violationCount = complianceReport ? complianceReport.violations.length : null;
1992
2092
  return /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", borderStyle: "round", borderColor: COLORS.purple, paddingX: SPACING.sm, paddingY: SPACING.none, marginTop: SPACING.xs, children: [
1993
- /* @__PURE__ */ jsx12(Text12, { bold: true, color: COLORS.purple, children: t("dashboard_pro_status") }),
2093
+ /* @__PURE__ */ jsx13(Text12, { bold: true, color: COLORS.purple, children: t("dashboard_pro_status") }),
1994
2094
  /* @__PURE__ */ jsxs11(Box9, { gap: SPACING.xs, children: [
1995
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.muted, children: t("dashboard_security") }),
1996
- cveCount === null ? /* @__PURE__ */ jsx12(Text12, { color: COLORS.muted, children: "\u2014" }) : cveCount === 0 ? /* @__PURE__ */ jsx12(Text12, { color: COLORS.success, children: t("dashboard_no_cves") }) : /* @__PURE__ */ jsxs11(Text12, { color: COLORS.error, children: [
2095
+ /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: t("dashboard_security") }),
2096
+ cveCount === null ? /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: "\u2014" }) : cveCount === 0 ? /* @__PURE__ */ jsx13(Text12, { color: COLORS.success, children: t("dashboard_no_cves") }) : /* @__PURE__ */ jsxs11(Text12, { color: COLORS.error, children: [
1997
2097
  t("dashboard_cves", { count: String(cveCount) }),
1998
2098
  criticalCount && criticalCount > 0 ? ` (${criticalCount} critical)` : ""
1999
2099
  ] })
2000
2100
  ] }),
2001
2101
  /* @__PURE__ */ jsxs11(Box9, { gap: SPACING.xs, children: [
2002
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.muted, children: t("dashboard_brewfile") }),
2003
- driftScore === null ? /* @__PURE__ */ jsx12(Text12, { color: COLORS.muted, children: "\u2014" }) : /* @__PURE__ */ jsxs11(Text12, { color: driftScore >= 80 ? COLORS.success : COLORS.warning, children: [
2102
+ /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: t("dashboard_brewfile") }),
2103
+ driftScore === null ? /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: "\u2014" }) : /* @__PURE__ */ jsxs11(Text12, { color: driftScore >= 80 ? COLORS.success : COLORS.warning, children: [
2004
2104
  driftScore,
2005
2105
  "%"
2006
2106
  ] })
2007
2107
  ] }),
2008
2108
  /* @__PURE__ */ jsxs11(Box9, { gap: SPACING.xs, children: [
2009
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.muted, children: t("dashboard_sync") }),
2010
- syncAgo === null ? /* @__PURE__ */ jsx12(Text12, { color: COLORS.muted, children: t("dashboard_sync_never") }) : /* @__PURE__ */ jsx12(Text12, { color: COLORS.info, children: t("dashboard_sync_ago", { time: syncAgo }) })
2109
+ /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: t("dashboard_sync") }),
2110
+ syncAgo === null ? /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: t("dashboard_sync_never") }) : /* @__PURE__ */ jsx13(Text12, { color: COLORS.info, children: t("dashboard_sync_ago", { time: syncAgo }) })
2011
2111
  ] }),
2012
2112
  /* @__PURE__ */ jsxs11(Box9, { gap: SPACING.xs, children: [
2013
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.muted, children: t("dashboard_compliance") }),
2014
- violationCount === null ? /* @__PURE__ */ jsx12(Text12, { color: COLORS.muted, children: "\u2014" }) : violationCount === 0 ? /* @__PURE__ */ jsx12(Text12, { color: COLORS.success, children: t("dashboard_compliance_ok") }) : /* @__PURE__ */ jsx12(Text12, { color: COLORS.warning, children: t("dashboard_compliance_violations", { count: String(violationCount) }) })
2113
+ /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: t("dashboard_compliance") }),
2114
+ violationCount === null ? /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: "\u2014" }) : violationCount === 0 ? /* @__PURE__ */ jsx13(Text12, { color: COLORS.success, children: t("dashboard_compliance_ok") }) : /* @__PURE__ */ jsx13(Text12, { color: COLORS.warning, children: t("dashboard_compliance_violations", { count: String(violationCount) }) })
2015
2115
  ] })
2016
2116
  ] });
2017
2117
  }
@@ -2026,9 +2126,14 @@ function DashboardView() {
2026
2126
  const lastFetchedAt = useBrewStore((s) => s.lastFetchedAt);
2027
2127
  const fetchAll = useBrewStore((s) => s.fetchAll);
2028
2128
  const isPro = useLicenseStore((s) => s.isPro);
2029
- const { stdout } = useStdout3();
2030
- const columns = stdout?.columns ?? 80;
2031
- useEffect3(() => {
2129
+ const { columns } = useTerminalSize();
2130
+ const splitRows = useVisibleRows({
2131
+ reservedRows: 18,
2132
+ fallbackReservedRows: 22,
2133
+ minRows: 2
2134
+ });
2135
+ const halfRows = Math.max(1, Math.floor(splitRows / 2));
2136
+ useEffect5(() => {
2032
2137
  fetchAll();
2033
2138
  }, []);
2034
2139
  useViewInput((input) => {
@@ -2053,11 +2158,11 @@ function DashboardView() {
2053
2158
  const outdatedValue = loading.outdated ? "..." : errors.outdated ? t("dashboard_statError") : outdated.formulae.length + outdated.casks.length;
2054
2159
  const servicesValue = loading.services ? "..." : errors.services ? t("dashboard_statError") : `${runningServices}/${services.length}`;
2055
2160
  const lastUpdated = lastFetchedAt.installed ? formatRelativeTime(lastFetchedAt.installed / 1e3) : null;
2056
- if (loading.installed) return /* @__PURE__ */ jsx12(Loading, { message: t("loading_fetchingBrew") });
2161
+ if (loading.installed) return /* @__PURE__ */ jsx13(Loading, { message: t("loading_fetchingBrew") });
2057
2162
  if (errors.installed) {
2058
2163
  return /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", children: [
2059
- /* @__PURE__ */ jsx12(ErrorMessage, { message: errors.installed }),
2060
- /* @__PURE__ */ jsx12(Box9, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs11(Text12, { color: COLORS.textSecondary, children: [
2164
+ /* @__PURE__ */ jsx13(ErrorMessage, { message: errors.installed }),
2165
+ /* @__PURE__ */ jsx13(Box9, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs11(Text12, { color: COLORS.textSecondary, children: [
2061
2166
  "r:",
2062
2167
  t("hint_refresh")
2063
2168
  ] }) })
@@ -2065,11 +2170,11 @@ function DashboardView() {
2065
2170
  }
2066
2171
  const isNarrow = columns < 60;
2067
2172
  return /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", gap: SPACING.sm, children: [
2068
- /* @__PURE__ */ jsx12(SectionHeader, { emoji: "\u{1F4CA}", title: t("dashboard_overview"), gradient: GRADIENTS.gold }),
2173
+ /* @__PURE__ */ jsx13(SectionHeader, { emoji: "\u{1F4CA}", title: t("dashboard_overview"), gradient: GRADIENTS.gold }),
2069
2174
  /* @__PURE__ */ jsxs11(Box9, { gap: SPACING.xs, flexWrap: "wrap", flexDirection: isNarrow ? "column" : "row", children: [
2070
- /* @__PURE__ */ jsx12(StatCard, { label: t("dashboard_formulae"), value: formulae.length, color: COLORS.info }),
2071
- /* @__PURE__ */ jsx12(StatCard, { label: t("dashboard_casks"), value: casks.length, color: COLORS.purple }),
2072
- /* @__PURE__ */ jsx12(
2175
+ /* @__PURE__ */ jsx13(StatCard, { label: t("dashboard_formulae"), value: formulae.length, color: COLORS.info }),
2176
+ /* @__PURE__ */ jsx13(StatCard, { label: t("dashboard_casks"), value: casks.length, color: COLORS.purple }),
2177
+ /* @__PURE__ */ jsx13(
2073
2178
  StatCard,
2074
2179
  {
2075
2180
  label: t("dashboard_outdated"),
@@ -2077,7 +2182,7 @@ function DashboardView() {
2077
2182
  color: typeof outdatedValue === "number" && outdatedValue > 0 ? COLORS.warning : errors.outdated ? COLORS.error : COLORS.success
2078
2183
  }
2079
2184
  ),
2080
- /* @__PURE__ */ jsx12(
2185
+ /* @__PURE__ */ jsx13(
2081
2186
  StatCard,
2082
2187
  {
2083
2188
  label: t("dashboard_services"),
@@ -2086,9 +2191,9 @@ function DashboardView() {
2086
2191
  }
2087
2192
  )
2088
2193
  ] }),
2089
- lastUpdated && /* @__PURE__ */ jsx12(Text12, { color: COLORS.muted, children: t("dashboard_lastUpdated", { time: lastUpdated }) }),
2194
+ lastUpdated && /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: t("dashboard_lastUpdated", { time: lastUpdated }) }),
2090
2195
  partialErrors.length > 0 && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", borderStyle: "round", borderColor: COLORS.warning, paddingX: SPACING.sm, paddingY: SPACING.none, children: [
2091
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.warning, bold: true, children: t("dashboard_partialData") }),
2196
+ /* @__PURE__ */ jsx13(Text12, { color: COLORS.warning, bold: true, children: t("dashboard_partialData") }),
2092
2197
  partialErrors.map((item) => /* @__PURE__ */ jsxs11(Text12, { color: COLORS.muted, children: [
2093
2198
  item.label,
2094
2199
  ": ",
@@ -2096,56 +2201,59 @@ function DashboardView() {
2096
2201
  ] }, item.label))
2097
2202
  ] }),
2098
2203
  config && !errors.config && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", children: [
2099
- /* @__PURE__ */ jsx12(SectionHeader, { emoji: "\u2139\uFE0F", title: t("dashboard_systemInfo"), gradient: [COLORS.text, COLORS.muted] }),
2204
+ /* @__PURE__ */ jsx13(SectionHeader, { emoji: "\u2139\uFE0F", title: t("dashboard_systemInfo"), gradient: [COLORS.text, COLORS.muted] }),
2100
2205
  /* @__PURE__ */ jsxs11(Box9, { borderStyle: "round", borderColor: COLORS.blue, paddingX: SPACING.sm, paddingY: SPACING.none, flexDirection: "column", marginTop: SPACING.xs, children: [
2101
2206
  /* @__PURE__ */ jsxs11(Text12, { children: [
2102
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.muted, children: t("dashboard_homebrew") }),
2207
+ /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: t("dashboard_homebrew") }),
2103
2208
  " ",
2104
2209
  config.HOMEBREW_VERSION
2105
2210
  ] }),
2106
2211
  /* @__PURE__ */ jsxs11(Text12, { children: [
2107
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.muted, children: t("dashboard_prefix") }),
2212
+ /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: t("dashboard_prefix") }),
2108
2213
  " ",
2109
2214
  config.HOMEBREW_PREFIX
2110
2215
  ] }),
2111
2216
  /* @__PURE__ */ jsxs11(Text12, { children: [
2112
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.muted, children: t("dashboard_updated") }),
2217
+ /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: t("dashboard_updated") }),
2113
2218
  " ",
2114
2219
  config.coreUpdated
2115
2220
  ] })
2116
2221
  ] })
2117
2222
  ] }),
2118
2223
  !errors.outdated && outdated.formulae.length > 0 && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginTop: SPACING.xs, children: [
2119
- /* @__PURE__ */ jsx12(SectionHeader, { emoji: "\u{1F4E6}", title: t("dashboard_outdatedPackages"), gradient: GRADIENTS.fire }),
2224
+ /* @__PURE__ */ jsx13(SectionHeader, { emoji: "\u{1F4E6}", title: t("dashboard_outdatedPackages"), gradient: GRADIENTS.fire }),
2120
2225
  /* @__PURE__ */ jsxs11(Box9, { paddingLeft: SPACING.sm, flexDirection: "column", children: [
2121
- outdated.formulae.slice(0, 10).map((pkg) => /* @__PURE__ */ jsxs11(Box9, { gap: SPACING.xs, children: [
2122
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.text, children: pkg.name }),
2123
- /* @__PURE__ */ jsx12(VersionArrow, { current: pkg.installed_versions[0] ?? "", latest: pkg.current_version })
2226
+ outdated.formulae.slice(0, halfRows).map((pkg) => /* @__PURE__ */ jsxs11(Box9, { gap: SPACING.xs, children: [
2227
+ /* @__PURE__ */ jsx13(Text12, { color: COLORS.text, children: pkg.name }),
2228
+ /* @__PURE__ */ jsx13(VersionArrow, { current: pkg.installed_versions[0] ?? "", latest: pkg.current_version })
2124
2229
  ] }, pkg.name)),
2125
- outdated.formulae.length > 10 && /* @__PURE__ */ jsx12(Text12, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: outdated.formulae.length - 10 }) })
2230
+ outdated.formulae.length > halfRows && /* @__PURE__ */ jsx13(Text12, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: outdated.formulae.length - halfRows }) })
2126
2231
  ] })
2127
2232
  ] }),
2128
2233
  !errors.services && errorServices > 0 && /* @__PURE__ */ jsxs11(Box9, { flexDirection: "column", marginTop: SPACING.xs, children: [
2129
- /* @__PURE__ */ jsx12(SectionHeader, { emoji: "\u26A0\uFE0F", title: t("dashboard_serviceErrors"), color: COLORS.error }),
2130
- /* @__PURE__ */ jsx12(Box9, { paddingLeft: SPACING.sm, flexDirection: "column", children: errorServiceList.map((s) => /* @__PURE__ */ jsxs11(Box9, { gap: SPACING.xs, children: [
2131
- /* @__PURE__ */ jsx12(StatusBadge, { label: t("badge_error"), variant: "error" }),
2132
- /* @__PURE__ */ jsx12(Text12, { children: s.name }),
2133
- s.exit_code != null && /* @__PURE__ */ jsx12(Text12, { color: COLORS.muted, children: t("common_exit", { code: s.exit_code }) })
2134
- ] }, s.name)) })
2234
+ /* @__PURE__ */ jsx13(SectionHeader, { emoji: "\u26A0\uFE0F", title: t("dashboard_serviceErrors"), color: COLORS.error }),
2235
+ /* @__PURE__ */ jsxs11(Box9, { paddingLeft: SPACING.sm, flexDirection: "column", children: [
2236
+ errorServiceList.slice(0, halfRows).map((s) => /* @__PURE__ */ jsxs11(Box9, { gap: SPACING.xs, children: [
2237
+ /* @__PURE__ */ jsx13(StatusBadge, { label: t("badge_error"), variant: "error" }),
2238
+ /* @__PURE__ */ jsx13(Text12, { children: s.name }),
2239
+ s.exit_code != null && /* @__PURE__ */ jsx13(Text12, { color: COLORS.muted, children: t("common_exit", { code: s.exit_code }) })
2240
+ ] }, s.name)),
2241
+ errorServiceList.length > halfRows && /* @__PURE__ */ jsx13(Text12, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: errorServiceList.length - halfRows }) })
2242
+ ] })
2135
2243
  ] }),
2136
- isPro() && /* @__PURE__ */ jsx12(ProStatusPanel, {})
2244
+ isPro() && /* @__PURE__ */ jsx13(ProStatusPanel, {})
2137
2245
  ] });
2138
2246
  }
2139
2247
 
2140
2248
  // src/views/installed.tsx
2141
- import { useState as useState6, useMemo as useMemo3, useEffect as useEffect9, useRef as useRef2 } from "react";
2249
+ import { useState as useState6, useMemo as useMemo3, useEffect as useEffect9, useRef as useRef3 } from "react";
2142
2250
  import { Box as Box15, Text as Text18 } from "ink";
2143
2251
 
2144
2252
  // src/hooks/use-debounce.ts
2145
- import { useState as useState2, useEffect as useEffect4 } from "react";
2253
+ import { useState as useState4, useEffect as useEffect6 } from "react";
2146
2254
  function useDebounce(value, delayMs) {
2147
- const [debounced, setDebounced] = useState2(value);
2148
- useEffect4(() => {
2255
+ const [debounced, setDebounced] = useState4(value);
2256
+ useEffect6(() => {
2149
2257
  const timer = setTimeout(() => setDebounced(value), delayMs);
2150
2258
  return () => clearTimeout(timer);
2151
2259
  }, [value, delayMs]);
@@ -2153,7 +2261,7 @@ function useDebounce(value, delayMs) {
2153
2261
  }
2154
2262
 
2155
2263
  // src/hooks/use-brew-stream.ts
2156
- import { useState as useState3, useCallback, useRef, useEffect as useEffect5 } from "react";
2264
+ import { useState as useState5, useCallback, useRef as useRef2, useEffect as useEffect7 } from "react";
2157
2265
  var MAX_LINES = 100;
2158
2266
  async function logToHistory(args, success, error) {
2159
2267
  const detected = detectAction(args);
@@ -2166,13 +2274,13 @@ async function logToHistory(args, success, error) {
2166
2274
  }
2167
2275
  }
2168
2276
  function useBrewStream() {
2169
- const [lines, setLines] = useState3([]);
2170
- const [isRunning, setIsRunning] = useState3(false);
2171
- const [error, setError2] = useState3(null);
2172
- const cancelRef = useRef(false);
2173
- const generatorRef = useRef(null);
2174
- const mountedRef = useRef(true);
2175
- useEffect5(() => {
2277
+ const [lines, setLines] = useState5([]);
2278
+ const [isRunning, setIsRunning] = useState5(false);
2279
+ const [error, setError2] = useState5(null);
2280
+ const cancelRef = useRef2(false);
2281
+ const generatorRef = useRef2(null);
2282
+ const mountedRef = useRef2(true);
2283
+ useEffect7(() => {
2176
2284
  mountedRef.current = true;
2177
2285
  return () => {
2178
2286
  mountedRef.current = false;
@@ -2232,7 +2340,7 @@ function useBrewStream() {
2232
2340
  // src/components/common/search-input.tsx
2233
2341
  import { Box as Box10, Text as Text13 } from "ink";
2234
2342
  import { TextInput } from "@inkjs/ui";
2235
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2343
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
2236
2344
  function SearchInput({ defaultValue, onChange, placeholder, isActive = true }) {
2237
2345
  const resolvedPlaceholder = placeholder ?? t("searchInput_placeholder");
2238
2346
  return /* @__PURE__ */ jsxs12(Box10, { children: [
@@ -2240,25 +2348,25 @@ function SearchInput({ defaultValue, onChange, placeholder, isActive = true }) {
2240
2348
  "\u{1F50D}",
2241
2349
  " "
2242
2350
  ] }),
2243
- isActive ? /* @__PURE__ */ jsx13(
2351
+ isActive ? /* @__PURE__ */ jsx14(
2244
2352
  TextInput,
2245
2353
  {
2246
2354
  placeholder: resolvedPlaceholder,
2247
2355
  defaultValue,
2248
2356
  onChange
2249
2357
  }
2250
- ) : /* @__PURE__ */ jsx13(Text13, { color: COLORS.textSecondary, children: defaultValue || placeholder })
2358
+ ) : /* @__PURE__ */ jsx14(Text13, { color: COLORS.textSecondary, children: defaultValue || placeholder })
2251
2359
  ] });
2252
2360
  }
2253
2361
 
2254
2362
  // src/components/common/confirm-dialog.tsx
2255
- import { useEffect as useEffect6 } from "react";
2363
+ import { useEffect as useEffect8 } from "react";
2256
2364
  import { Box as Box11, Text as Text14, useInput as useInput3 } from "ink";
2257
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2365
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2258
2366
  function ConfirmDialog({ message, onConfirm, onCancel }) {
2259
2367
  const locale = useLocaleStore((s) => s.locale);
2260
2368
  const { openModal, closeModal } = useModalStore();
2261
- useEffect6(() => {
2369
+ useEffect8(() => {
2262
2370
  openModal();
2263
2371
  return () => {
2264
2372
  closeModal();
@@ -2270,12 +2378,12 @@ function ConfirmDialog({ message, onConfirm, onCancel }) {
2270
2378
  else if (input === "n" || input === "N") onCancel();
2271
2379
  else if (key.escape) onCancel();
2272
2380
  });
2273
- return /* @__PURE__ */ jsxs13(Box11, { borderStyle: "double", borderColor: COLORS.purple, paddingX: SPACING.sm, paddingY: SPACING.xs, flexDirection: "column", children: [
2274
- /* @__PURE__ */ jsx14(Text14, { bold: true, color: COLORS.text, children: message }),
2381
+ return /* @__PURE__ */ jsxs13(Box11, { borderStyle: "double", borderColor: COLORS.purple, paddingX: SPACING.sm, paddingY: SPACING.xs, flexDirection: "column", flexShrink: 1, children: [
2382
+ /* @__PURE__ */ jsx15(Text14, { bold: true, color: COLORS.text, wrap: "wrap", children: message }),
2275
2383
  /* @__PURE__ */ jsxs13(Box11, { marginTop: SPACING.xs, children: [
2276
- /* @__PURE__ */ jsx14(Text14, { color: COLORS.success, children: t("confirm_yes") }),
2277
- /* @__PURE__ */ jsx14(Text14, { children: " / " }),
2278
- /* @__PURE__ */ jsx14(Text14, { color: COLORS.error, children: t("confirm_no") })
2384
+ /* @__PURE__ */ jsx15(Text14, { color: COLORS.success, children: t("confirm_yes") }),
2385
+ /* @__PURE__ */ jsx15(Text14, { children: " / " }),
2386
+ /* @__PURE__ */ jsx15(Text14, { color: COLORS.error, children: t("confirm_no") })
2279
2387
  ] })
2280
2388
  ] });
2281
2389
  }
@@ -2283,13 +2391,13 @@ function ConfirmDialog({ message, onConfirm, onCancel }) {
2283
2391
  // src/components/common/progress-log.tsx
2284
2392
  import { Box as Box12, Text as Text15 } from "ink";
2285
2393
  import { Spinner as Spinner2 } from "@inkjs/ui";
2286
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
2394
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
2287
2395
  function ProgressLog({ lines, isRunning, title, maxVisible = 15 }) {
2288
2396
  const start = Math.max(0, lines.length - maxVisible);
2289
2397
  const visible = lines.slice(start);
2290
2398
  return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", borderStyle: "round", borderColor: COLORS.sky, paddingX: SPACING.xs, children: [
2291
2399
  title && /* @__PURE__ */ jsxs14(Box12, { marginBottom: SPACING.xs, children: [
2292
- isRunning && /* @__PURE__ */ jsx15(Spinner2, { label: "" }),
2400
+ isRunning && /* @__PURE__ */ jsx16(Spinner2, { label: "" }),
2293
2401
  /* @__PURE__ */ jsxs14(Text15, { bold: true, color: COLORS.sky, children: [
2294
2402
  " ",
2295
2403
  title
@@ -2299,15 +2407,15 @@ function ProgressLog({ lines, isRunning, title, maxVisible = 15 }) {
2299
2407
  // UI-006: keys are absolute indices, so a line that scrolls off-screen
2300
2408
  // does not change the key of remaining lines. Stable identity prevents
2301
2409
  // React from treating the whole list as new on every append.
2302
- /* @__PURE__ */ jsx15(Text15, { color: COLORS.muted, wrap: "wrap", children: line }, `log-${start + i}`)
2410
+ /* @__PURE__ */ jsx16(Text15, { color: COLORS.muted, wrap: "wrap", children: line }, `log-${start + i}`)
2303
2411
  )),
2304
- lines.length === 0 && !isRunning && /* @__PURE__ */ jsx15(Text15, { color: COLORS.textSecondary, italic: true, children: t("progress_noOutput") })
2412
+ lines.length === 0 && !isRunning && /* @__PURE__ */ jsx16(Text15, { color: COLORS.textSecondary, italic: true, children: t("progress_noOutput") })
2305
2413
  ] });
2306
2414
  }
2307
2415
 
2308
2416
  // src/components/common/result-banner.tsx
2309
2417
  import { Box as Box13, Text as Text16 } from "ink";
2310
- import { jsx as jsx16 } from "react/jsx-runtime";
2418
+ import { jsx as jsx17 } from "react/jsx-runtime";
2311
2419
  var STATUS_COLORS = {
2312
2420
  success: COLORS.success,
2313
2421
  error: COLORS.error,
@@ -2315,64 +2423,21 @@ var STATUS_COLORS = {
2315
2423
  info: COLORS.info
2316
2424
  };
2317
2425
  function ResultBanner({ status, message }) {
2318
- return /* @__PURE__ */ jsx16(Box13, { borderStyle: "round", borderColor: STATUS_COLORS[status], paddingX: SPACING.sm, paddingY: SPACING.none, children: /* @__PURE__ */ jsx16(Text16, { color: STATUS_COLORS[status], bold: true, children: message }) });
2426
+ return /* @__PURE__ */ jsx17(Box13, { borderStyle: "round", borderColor: STATUS_COLORS[status], paddingX: SPACING.sm, paddingY: SPACING.none, children: /* @__PURE__ */ jsx17(Text16, { color: STATUS_COLORS[status], bold: true, children: message }) });
2319
2427
  }
2320
2428
 
2321
2429
  // src/components/common/selectable-row.tsx
2322
2430
  import { Box as Box14, Text as Text17 } from "ink";
2323
- import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2431
+ import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
2324
2432
  function SelectableRow({ isCurrent, children, gap = 1 }) {
2325
2433
  return /* @__PURE__ */ jsxs15(Box14, { gap, children: [
2326
- /* @__PURE__ */ jsx17(Text17, { color: isCurrent ? COLORS.success : COLORS.muted, children: isCurrent ? "\u25B6" : " " }),
2434
+ /* @__PURE__ */ jsx18(Text17, { color: isCurrent ? COLORS.success : COLORS.muted, children: isCurrent ? "\u25B6" : " " }),
2327
2435
  children
2328
2436
  ] });
2329
2437
  }
2330
2438
 
2331
- // src/hooks/use-container-size.ts
2332
- import { useEffect as useEffect8, useState as useState5 } from "react";
2333
- import { measureElement } from "ink";
2334
-
2335
- // src/hooks/use-terminal-size.ts
2336
- import { useEffect as useEffect7, useState as useState4 } from "react";
2337
- import { useStdout as useStdout4 } from "ink";
2338
- function useTerminalSize() {
2339
- const { stdout } = useStdout4();
2340
- const [size, setSize] = useState4(() => ({
2341
- columns: stdout?.columns ?? 80,
2342
- rows: stdout?.rows ?? 24
2343
- }));
2344
- useEffect7(() => {
2345
- if (!stdout) return;
2346
- const onResize = () => {
2347
- setSize({
2348
- columns: stdout.columns ?? 80,
2349
- rows: stdout.rows ?? 24
2350
- });
2351
- };
2352
- stdout.on("resize", onResize);
2353
- return () => {
2354
- stdout.off("resize", onResize);
2355
- };
2356
- }, [stdout]);
2357
- return size;
2358
- }
2359
-
2360
- // src/hooks/use-container-size.ts
2361
- function useContainerSize(ref) {
2362
- const terminal = useTerminalSize();
2363
- const [size, setSize] = useState5({ width: 0, height: 0 });
2364
- useEffect8(() => {
2365
- if (!ref.current) return;
2366
- const measured = measureElement(ref.current);
2367
- setSize(
2368
- (prev) => prev.width === measured.width && prev.height === measured.height ? prev : measured
2369
- );
2370
- }, [ref, terminal.columns, terminal.rows]);
2371
- return size;
2372
- }
2373
-
2374
2439
  // src/views/installed.tsx
2375
- import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2440
+ import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
2376
2441
  function InstalledView() {
2377
2442
  const formulae = useBrewStore((s) => s.formulae);
2378
2443
  const casks = useBrewStore((s) => s.casks);
@@ -2382,12 +2447,12 @@ function InstalledView() {
2382
2447
  const navigate = useNavigationStore((s) => s.navigate);
2383
2448
  const selectPackage = useNavigationStore((s) => s.selectPackage);
2384
2449
  const { openModal, closeModal } = useModalStore();
2385
- const containerRef = useRef2(null);
2450
+ const containerRef = useRef3(null);
2386
2451
  const { width: containerWidth } = useContainerSize(containerRef);
2387
- const { rows: terminalRows } = useTerminalSize();
2388
2452
  const columns = containerWidth > 0 ? containerWidth : 80;
2389
- const nameWidth = Math.floor(columns * 0.35);
2390
- const versionWidth = Math.floor(columns * 0.15);
2453
+ const nameWidth = Math.max(12, Math.floor(columns * 0.35));
2454
+ const versionWidth = Math.max(8, Math.floor(columns * 0.15));
2455
+ const descWidth = Math.max(10, columns - nameWidth - versionWidth - 8);
2391
2456
  const [filter, setFilter] = useState6("");
2392
2457
  const [cursor, setCursor] = useState6(0);
2393
2458
  const [tab, setTab] = useState6("formulae");
@@ -2395,6 +2460,16 @@ function InstalledView() {
2395
2460
  const [confirmUninstall, setConfirmUninstall] = useState6(null);
2396
2461
  const debouncedFilter = useDebounce(filter, 200);
2397
2462
  const stream = useBrewStream();
2463
+ const listRows = useVisibleRows({
2464
+ reservedRows: isSearching ? 14 : 10,
2465
+ fallbackReservedRows: isSearching ? 18 : 14,
2466
+ minRows: 1
2467
+ });
2468
+ const streamRows = useVisibleRows({
2469
+ reservedRows: 5,
2470
+ fallbackReservedRows: 14,
2471
+ minRows: 1
2472
+ });
2398
2473
  useEffect9(() => {
2399
2474
  fetchInstalled();
2400
2475
  }, []);
@@ -2459,16 +2534,17 @@ function InstalledView() {
2459
2534
  setCursor(0);
2460
2535
  }
2461
2536
  }, { isActive: true });
2462
- if (loading.installed) return /* @__PURE__ */ jsx18(Loading, { message: t("loading_installed") });
2463
- if (errors.installed) return /* @__PURE__ */ jsx18(ErrorMessage, { message: errors.installed });
2537
+ if (loading.installed) return /* @__PURE__ */ jsx19(Loading, { message: t("loading_installed") });
2538
+ if (errors.installed) return /* @__PURE__ */ jsx19(ErrorMessage, { message: errors.installed });
2464
2539
  if (stream.isRunning || stream.lines.length > 0) {
2465
2540
  return /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", children: [
2466
- /* @__PURE__ */ jsx18(
2541
+ /* @__PURE__ */ jsx19(
2467
2542
  ProgressLog,
2468
2543
  {
2469
2544
  lines: stream.lines,
2470
2545
  isRunning: stream.isRunning,
2471
- title: t("pkgInfo_uninstalling", { name: "..." })
2546
+ title: t("pkgInfo_uninstalling", { name: "..." }),
2547
+ maxVisible: streamRows
2472
2548
  }
2473
2549
  ),
2474
2550
  stream.isRunning && /* @__PURE__ */ jsxs16(Text18, { color: COLORS.textSecondary, children: [
@@ -2476,7 +2552,7 @@ function InstalledView() {
2476
2552
  t("hint_cancel")
2477
2553
  ] }),
2478
2554
  !stream.isRunning && /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", marginTop: SPACING.xs, children: [
2479
- /* @__PURE__ */ jsx18(
2555
+ /* @__PURE__ */ jsx19(
2480
2556
  ResultBanner,
2481
2557
  {
2482
2558
  status: stream.error ? "error" : "success",
@@ -2490,12 +2566,12 @@ function InstalledView() {
2490
2566
  ] })
2491
2567
  ] });
2492
2568
  }
2493
- const MAX_VISIBLE_ROWS = Math.max(5, terminalRows - 8);
2569
+ const MAX_VISIBLE_ROWS = listRows;
2494
2570
  const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
2495
2571
  const visible = allItems.slice(start, start + MAX_VISIBLE_ROWS);
2496
2572
  return /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", ref: containerRef, children: [
2497
2573
  /* @__PURE__ */ jsxs16(Box15, { marginBottom: SPACING.xs, gap: SPACING.xs, children: [
2498
- /* @__PURE__ */ jsx18(
2574
+ /* @__PURE__ */ jsx19(
2499
2575
  Box15,
2500
2576
  {
2501
2577
  borderStyle: "round",
@@ -2508,7 +2584,7 @@ function InstalledView() {
2508
2584
  ] })
2509
2585
  }
2510
2586
  ),
2511
- /* @__PURE__ */ jsx18(
2587
+ /* @__PURE__ */ jsx19(
2512
2588
  Box15,
2513
2589
  {
2514
2590
  borderStyle: "round",
@@ -2522,7 +2598,7 @@ function InstalledView() {
2522
2598
  }
2523
2599
  )
2524
2600
  ] }),
2525
- confirmUninstall && /* @__PURE__ */ jsx18(
2601
+ confirmUninstall && /* @__PURE__ */ jsx19(
2526
2602
  ConfirmDialog,
2527
2603
  {
2528
2604
  message: t("installed_confirmUninstall", { name: confirmUninstall }),
@@ -2536,17 +2612,17 @@ function InstalledView() {
2536
2612
  onCancel: () => setConfirmUninstall(null)
2537
2613
  }
2538
2614
  ),
2539
- isSearching && /* @__PURE__ */ jsx18(Box15, { marginBottom: SPACING.xs, borderStyle: "round", borderColor: COLORS.gold, paddingX: SPACING.xs, children: /* @__PURE__ */ jsx18(SearchInput, { defaultValue: filter, onChange: setFilter, isActive: isSearching }) }),
2615
+ 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 }) }),
2540
2616
  /* @__PURE__ */ jsxs16(Box15, { gap: SPACING.xs, borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, borderColor: COLORS.border, children: [
2541
2617
  /* @__PURE__ */ jsxs16(Text18, { color: COLORS.text, bold: true, children: [
2542
2618
  " ",
2543
2619
  t("installed_col_package").padEnd(nameWidth)
2544
2620
  ] }),
2545
- /* @__PURE__ */ jsx18(Text18, { color: COLORS.text, bold: true, children: t("installed_col_version").padEnd(versionWidth) }),
2546
- /* @__PURE__ */ jsx18(Text18, { color: COLORS.text, bold: true, children: t("installed_col_status") })
2621
+ /* @__PURE__ */ jsx19(Text18, { color: COLORS.text, bold: true, children: t("installed_col_version").padEnd(versionWidth) }),
2622
+ /* @__PURE__ */ jsx19(Text18, { color: COLORS.text, bold: true, children: t("installed_col_status") })
2547
2623
  ] }),
2548
2624
  /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", children: [
2549
- visible.length === 0 && /* @__PURE__ */ jsx18(Box15, { paddingY: SPACING.xs, justifyContent: "center", children: /* @__PURE__ */ jsx18(Text18, { color: COLORS.textSecondary, italic: true, children: t("installed_noPackages") }) }),
2625
+ visible.length === 0 && /* @__PURE__ */ jsx19(Box15, { paddingY: SPACING.xs, justifyContent: "center", children: /* @__PURE__ */ jsx19(Text18, { color: COLORS.textSecondary, italic: true, children: t("installed_noPackages") }) }),
2550
2626
  start > 0 && /* @__PURE__ */ jsxs16(Text18, { color: COLORS.textSecondary, dimColor: true, children: [
2551
2627
  " ",
2552
2628
  t("scroll_moreAbove", { count: start })
@@ -2555,13 +2631,13 @@ function InstalledView() {
2555
2631
  const idx = start + i;
2556
2632
  const isCurrent = idx === cursor;
2557
2633
  return /* @__PURE__ */ jsxs16(SelectableRow, { isCurrent, children: [
2558
- /* @__PURE__ */ jsx18(Text18, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: truncate(item.name, nameWidth).padEnd(nameWidth) }),
2559
- /* @__PURE__ */ jsx18(Text18, { color: COLORS.teal, children: item.version.padEnd(versionWidth) }),
2560
- item.outdated && /* @__PURE__ */ jsx18(StatusBadge, { label: t("badge_outdated"), variant: "warning" }),
2561
- item.pinned && /* @__PURE__ */ jsx18(StatusBadge, { label: t("badge_pinned"), variant: "info" }),
2562
- item.kegOnly && /* @__PURE__ */ jsx18(StatusBadge, { label: t("badge_kegOnly"), variant: "muted" }),
2563
- item.installedAsDependency && /* @__PURE__ */ jsx18(StatusBadge, { label: t("badge_dep"), variant: "muted" }),
2564
- !item.outdated && !item.pinned && !item.kegOnly && !item.installedAsDependency && /* @__PURE__ */ jsx18(Text18, { color: COLORS.textSecondary, dimColor: true, children: truncate(item.desc, 30) })
2634
+ /* @__PURE__ */ jsx19(Text18, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: truncate(item.name, nameWidth).padEnd(nameWidth) }),
2635
+ /* @__PURE__ */ jsx19(Text18, { color: COLORS.teal, children: item.version.padEnd(versionWidth) }),
2636
+ item.outdated && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_outdated"), variant: "warning" }),
2637
+ item.pinned && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_pinned"), variant: "info" }),
2638
+ item.kegOnly && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_kegOnly"), variant: "muted" }),
2639
+ item.installedAsDependency && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_dep"), variant: "muted" }),
2640
+ !item.outdated && !item.pinned && !item.kegOnly && !item.installedAsDependency && /* @__PURE__ */ jsx19(Text18, { color: COLORS.textSecondary, dimColor: true, children: truncate(item.desc, descWidth) })
2565
2641
  ] }, item.name);
2566
2642
  }),
2567
2643
  start + MAX_VISIBLE_ROWS < allItems.length && /* @__PURE__ */ jsxs16(Text18, { color: COLORS.textSecondary, dimColor: true, children: [
@@ -2569,15 +2645,15 @@ function InstalledView() {
2569
2645
  t("scroll_moreBelow", { count: allItems.length - start - MAX_VISIBLE_ROWS })
2570
2646
  ] })
2571
2647
  ] }),
2572
- /* @__PURE__ */ jsx18(Box15, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx18(Text18, { color: COLORS.text, bold: true, children: allItems.length > 0 ? `${cursor + 1}/${allItems.length}` : "0/0" }) })
2648
+ /* @__PURE__ */ jsx19(Box15, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx19(Text18, { color: COLORS.text, bold: true, children: allItems.length > 0 ? `${cursor + 1}/${allItems.length}` : "0/0" }) })
2573
2649
  ] });
2574
2650
  }
2575
2651
 
2576
2652
  // src/views/search.tsx
2577
- import { useState as useState7, useCallback as useCallback2, useEffect as useEffect10, useRef as useRef3 } from "react";
2653
+ import { useState as useState7, useCallback as useCallback2, useEffect as useEffect10, useRef as useRef4 } from "react";
2578
2654
  import { Box as Box16, Text as Text19 } from "ink";
2579
2655
  import { TextInput as TextInput2 } from "@inkjs/ui";
2580
- import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
2656
+ import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
2581
2657
  function SearchView() {
2582
2658
  const [query, setQuery] = useState7("");
2583
2659
  const [results, setResults] = useState7(null);
@@ -2590,7 +2666,17 @@ function SearchView() {
2590
2666
  const navigate = useNavigationStore((s) => s.navigate);
2591
2667
  const selectPackage = useNavigationStore((s) => s.selectPackage);
2592
2668
  const fetchInstalled = useBrewStore((s) => s.fetchInstalled);
2593
- const hasRefreshed = useRef3(false);
2669
+ const hasRefreshed = useRef4(false);
2670
+ const resultRows = useVisibleRows({
2671
+ reservedRows: searchError ? 8 : 6,
2672
+ fallbackReservedRows: searchError ? 18 : 16,
2673
+ minRows: 1
2674
+ });
2675
+ const streamRows = useVisibleRows({
2676
+ reservedRows: 5,
2677
+ fallbackReservedRows: 14,
2678
+ minRows: 1
2679
+ });
2594
2680
  useEffect10(() => {
2595
2681
  if (results !== null) {
2596
2682
  openModal();
@@ -2625,10 +2711,18 @@ function SearchView() {
2625
2711
  void fetchInstalled();
2626
2712
  }
2627
2713
  }, [stream.isRunning, stream.error]);
2628
- const MAX_VISIBLE = 20;
2629
- const visibleFormulae = results ? results.formulae.slice(0, MAX_VISIBLE) : [];
2630
- const visibleCasks = results ? results.casks.slice(0, MAX_VISIBLE) : [];
2631
- const allVisible = [...visibleFormulae, ...visibleCasks];
2714
+ const allResults = results ? [
2715
+ ...results.formulae.map((name) => ({ name, type: "formula" })),
2716
+ ...results.casks.map((name) => ({ name, type: "cask" }))
2717
+ ] : [];
2718
+ const start = Math.min(
2719
+ Math.max(0, cursor - Math.floor(resultRows / 2)),
2720
+ Math.max(0, allResults.length - resultRows)
2721
+ );
2722
+ const visibleResults = allResults.slice(start, start + resultRows);
2723
+ useEffect10(() => {
2724
+ setCursor((current) => Math.min(current, Math.max(0, allResults.length - 1)));
2725
+ }, [allResults.length]);
2632
2726
  useViewInput((input, key) => {
2633
2727
  if (stream.isRunning) {
2634
2728
  if (key.escape) stream.cancel();
@@ -2645,17 +2739,18 @@ function SearchView() {
2645
2739
  void doSearch(query);
2646
2740
  return;
2647
2741
  }
2648
- if (key.return && allVisible[cursor]) {
2649
- selectPackage(allVisible[cursor]);
2742
+ if (key.return && allResults[cursor]) {
2743
+ const result = allResults[cursor];
2744
+ selectPackage(result.name, result.type);
2650
2745
  navigate("package-info");
2651
2746
  return;
2652
2747
  }
2653
- if ((input === "i" || input === "1") && allVisible[cursor]) {
2654
- setConfirmInstall(allVisible[cursor]);
2748
+ if ((input === "i" || input === "1") && allResults[cursor]) {
2749
+ setConfirmInstall(allResults[cursor].name);
2655
2750
  return;
2656
2751
  }
2657
2752
  if (input === "j" || key.downArrow) {
2658
- setCursor((c) => Math.min(c + 1, Math.max(0, allVisible.length - 1)));
2753
+ setCursor((c) => Math.min(c + 1, Math.max(0, allResults.length - 1)));
2659
2754
  } else if (input === "k" || key.upArrow) {
2660
2755
  setCursor((c) => Math.max(c - 1, 0));
2661
2756
  } else if (key.escape) {
@@ -2665,12 +2760,13 @@ function SearchView() {
2665
2760
  });
2666
2761
  if (stream.isRunning || stream.lines.length > 0) {
2667
2762
  return /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
2668
- /* @__PURE__ */ jsx19(
2763
+ /* @__PURE__ */ jsx20(
2669
2764
  ProgressLog,
2670
2765
  {
2671
2766
  lines: stream.lines,
2672
2767
  isRunning: stream.isRunning,
2673
- title: t("search_installing")
2768
+ title: t("search_installing"),
2769
+ maxVisible: streamRows
2674
2770
  }
2675
2771
  ),
2676
2772
  stream.isRunning && /* @__PURE__ */ jsxs17(Text19, { color: COLORS.textSecondary, children: [
@@ -2678,7 +2774,7 @@ function SearchView() {
2678
2774
  t("hint_cancel")
2679
2775
  ] }),
2680
2776
  !stream.isRunning && /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", marginTop: SPACING.xs, children: [
2681
- /* @__PURE__ */ jsx19(
2777
+ /* @__PURE__ */ jsx20(
2682
2778
  ResultBanner,
2683
2779
  {
2684
2780
  status: stream.error ? "error" : "success",
@@ -2698,7 +2794,7 @@ function SearchView() {
2698
2794
  "\u{1F50D}",
2699
2795
  " "
2700
2796
  ] }),
2701
- !results ? /* @__PURE__ */ jsx19(
2797
+ !results ? /* @__PURE__ */ jsx20(
2702
2798
  TextInput2,
2703
2799
  {
2704
2800
  placeholder: t("search_placeholder"),
@@ -2709,14 +2805,14 @@ function SearchView() {
2709
2805
  ) : /* @__PURE__ */ jsxs17(Text19, { children: [
2710
2806
  t("search_resultsFor"),
2711
2807
  ' "',
2712
- /* @__PURE__ */ jsx19(Text19, { bold: true, color: COLORS.text, children: query }),
2808
+ /* @__PURE__ */ jsx20(Text19, { bold: true, color: COLORS.text, children: query }),
2713
2809
  '" ',
2714
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.textSecondary, children: t("search_escToClear") })
2810
+ /* @__PURE__ */ jsx20(Text19, { color: COLORS.textSecondary, children: t("search_escToClear") })
2715
2811
  ] })
2716
2812
  ] }),
2717
- searching && /* @__PURE__ */ jsx19(Loading, { message: t("loading_searching") }),
2718
- searchError && /* @__PURE__ */ jsx19(Box16, { marginBottom: SPACING.xs, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.error, children: searchError }) }),
2719
- confirmInstall && /* @__PURE__ */ jsx19(
2813
+ searching && /* @__PURE__ */ jsx20(Loading, { message: t("loading_searching") }),
2814
+ searchError && /* @__PURE__ */ jsx20(Box16, { marginBottom: SPACING.xs, children: /* @__PURE__ */ jsx20(Text19, { color: COLORS.error, children: searchError }) }),
2815
+ confirmInstall && /* @__PURE__ */ jsx20(
2720
2816
  ConfirmDialog,
2721
2817
  {
2722
2818
  message: t("search_confirmInstall", { name: confirmInstall }),
@@ -2730,39 +2826,45 @@ function SearchView() {
2730
2826
  }
2731
2827
  ),
2732
2828
  results && !searching && !confirmInstall && /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
2733
- visibleFormulae.length > 0 && /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", marginBottom: SPACING.xs, children: [
2734
- /* @__PURE__ */ jsx19(Text19, { bold: true, color: COLORS.info, children: t("search_formulaeHeader", { count: results.formulae.length }) }),
2735
- visibleFormulae.map((name, i) => {
2736
- const isCurrent = i === cursor;
2737
- return /* @__PURE__ */ jsx19(SelectableRow, { isCurrent, children: /* @__PURE__ */ jsx19(Text19, { bold: isCurrent, inverse: isCurrent, children: name }) }, name);
2738
- }),
2739
- results.formulae.length > MAX_VISIBLE && /* @__PURE__ */ jsxs17(Text19, { color: COLORS.textSecondary, dimColor: true, children: [
2740
- " ",
2741
- t("scroll_moreBelow", { count: results.formulae.length - MAX_VISIBLE })
2742
- ] })
2829
+ allResults.length > 0 && /* @__PURE__ */ jsxs17(Text19, { color: COLORS.textSecondary, children: [
2830
+ t("search_formulaeHeader", { count: results.formulae.length }),
2831
+ " ",
2832
+ t("search_casksHeader", { count: results.casks.length })
2743
2833
  ] }),
2744
- visibleCasks.length > 0 && /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
2745
- /* @__PURE__ */ jsx19(Text19, { bold: true, color: COLORS.purple, children: t("search_casksHeader", { count: results.casks.length }) }),
2746
- visibleCasks.map((name, i) => {
2747
- const idx = visibleFormulae.length + i;
2834
+ allResults.length > 0 && /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
2835
+ start > 0 && /* @__PURE__ */ jsxs17(Text19, { color: COLORS.textSecondary, dimColor: true, children: [
2836
+ " ",
2837
+ t("scroll_moreAbove", { count: start })
2838
+ ] }),
2839
+ visibleResults.map((result, i) => {
2840
+ const idx = start + i;
2748
2841
  const isCurrent = idx === cursor;
2749
- return /* @__PURE__ */ jsx19(SelectableRow, { isCurrent, children: /* @__PURE__ */ jsx19(Text19, { bold: isCurrent, inverse: isCurrent, children: name }) }, name);
2842
+ return /* @__PURE__ */ jsxs17(SelectableRow, { isCurrent, children: [
2843
+ /* @__PURE__ */ jsx20(Text19, { bold: isCurrent, inverse: isCurrent, children: result.name }),
2844
+ /* @__PURE__ */ jsx20(
2845
+ StatusBadge,
2846
+ {
2847
+ label: result.type === "formula" ? "Formula" : "Cask",
2848
+ variant: result.type === "formula" ? "info" : "muted"
2849
+ }
2850
+ )
2851
+ ] }, `${result.type}:${result.name}`);
2750
2852
  }),
2751
- results.casks.length > MAX_VISIBLE && /* @__PURE__ */ jsxs17(Text19, { color: COLORS.textSecondary, dimColor: true, children: [
2853
+ start + resultRows < allResults.length && /* @__PURE__ */ jsxs17(Text19, { color: COLORS.textSecondary, dimColor: true, children: [
2752
2854
  " ",
2753
- t("scroll_moreBelow", { count: results.casks.length - MAX_VISIBLE })
2855
+ t("scroll_moreBelow", { count: allResults.length - start - resultRows })
2754
2856
  ] })
2755
2857
  ] }),
2756
- allVisible.length === 0 && /* @__PURE__ */ jsx19(Box16, { borderStyle: "round", borderColor: COLORS.textSecondary, paddingX: SPACING.sm, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.textSecondary, italic: true, children: t("search_noResults") }) }),
2757
- /* @__PURE__ */ jsx19(Box16, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.text, bold: true, children: allVisible.length > 0 ? `${cursor + 1}/${allVisible.length}` : "" }) })
2858
+ allResults.length === 0 && /* @__PURE__ */ jsx20(Box16, { borderStyle: "round", borderColor: COLORS.textSecondary, paddingX: SPACING.sm, children: /* @__PURE__ */ jsx20(Text19, { color: COLORS.textSecondary, italic: true, children: t("search_noResults") }) }),
2859
+ /* @__PURE__ */ jsx20(Box16, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx20(Text19, { color: COLORS.text, bold: true, children: allResults.length > 0 ? `${cursor + 1}/${allResults.length}` : "" }) })
2758
2860
  ] })
2759
2861
  ] });
2760
2862
  }
2761
2863
 
2762
2864
  // src/views/outdated.tsx
2763
- import { useEffect as useEffect11, useMemo as useMemo4, useRef as useRef4, useState as useState8 } from "react";
2865
+ import { useEffect as useEffect11, useMemo as useMemo4, useRef as useRef5, useState as useState8 } from "react";
2764
2866
  import { Box as Box17, Text as Text20 } from "ink";
2765
- import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
2867
+ import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
2766
2868
  function ImpactPanel({ impact }) {
2767
2869
  const riskColor = impact.risk === "high" ? COLORS.error : impact.risk === "medium" ? COLORS.warning : COLORS.success;
2768
2870
  const riskLabel = impact.risk === "high" ? t("impact_high") : impact.risk === "medium" ? t("impact_medium") : t("impact_low");
@@ -2779,9 +2881,9 @@ function ImpactPanel({ impact }) {
2779
2881
  t("impact_affects", { count: impact.reverseDeps.length })
2780
2882
  ] })
2781
2883
  ] }),
2782
- impact.riskReasons.length > 0 && /* @__PURE__ */ jsx20(Text20, { color: COLORS.textSecondary, children: impact.riskReasons.join(" \xB7 ") }),
2783
- impact.reverseDeps.length > 0 && impact.reverseDeps.length <= 5 && /* @__PURE__ */ jsx20(Text20, { color: COLORS.muted, dimColor: true, children: t("impact_usedBy", { packages: impact.reverseDeps.join(", ") }) }),
2784
- impact.risk === "high" && /* @__PURE__ */ jsx20(Text20, { color: COLORS.info, children: t("impact_brewfile_hint") })
2884
+ impact.riskReasons.length > 0 && /* @__PURE__ */ jsx21(Text20, { color: COLORS.textSecondary, children: impact.riskReasons.join(" \xB7 ") }),
2885
+ impact.reverseDeps.length > 0 && impact.reverseDeps.length <= 5 && /* @__PURE__ */ jsx21(Text20, { color: COLORS.muted, dimColor: true, children: t("impact_usedBy", { packages: impact.reverseDeps.join(", ") }) }),
2886
+ impact.risk === "high" && /* @__PURE__ */ jsx21(Text20, { color: COLORS.info, children: t("impact_brewfile_hint") })
2785
2887
  ] });
2786
2888
  }
2787
2889
  function OutdatedView() {
@@ -2789,10 +2891,20 @@ function OutdatedView() {
2789
2891
  const stream = useBrewStream();
2790
2892
  const [cursor, setCursor] = useState8(0);
2791
2893
  const [confirmAction, setConfirmAction] = useState8(null);
2792
- const hasRefreshed = useRef4(false);
2793
- const pendingUpgradeRef = useRef4(null);
2894
+ const hasRefreshed = useRef5(false);
2895
+ const pendingUpgradeRef = useRef5(null);
2794
2896
  const [impact, setImpact] = useState8(null);
2795
2897
  const [impactLoading, setImpactLoading] = useState8(false);
2898
+ const listRows = useVisibleRows({
2899
+ reservedRows: impact || impactLoading ? 11 : 7,
2900
+ fallbackReservedRows: impact || impactLoading ? 18 : 14,
2901
+ minRows: 1
2902
+ });
2903
+ const streamRows = useVisibleRows({
2904
+ reservedRows: 5,
2905
+ fallbackReservedRows: 14,
2906
+ minRows: 1
2907
+ });
2796
2908
  useEffect11(() => {
2797
2909
  fetchOutdated();
2798
2910
  }, []);
@@ -2880,20 +2992,20 @@ function OutdatedView() {
2880
2992
  void fetchOutdated();
2881
2993
  }
2882
2994
  });
2883
- const { rows: terminalRows } = useTerminalSize();
2884
- const MAX_VISIBLE_ROWS = Math.max(5, terminalRows - 8);
2995
+ const MAX_VISIBLE_ROWS = listRows;
2885
2996
  const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
2886
2997
  const visible = allOutdated.slice(start, start + MAX_VISIBLE_ROWS);
2887
- if (loading.outdated) return /* @__PURE__ */ jsx20(Loading, { message: t("loading_outdated") });
2888
- if (errors.outdated) return /* @__PURE__ */ jsx20(ErrorMessage, { message: errors.outdated });
2998
+ if (loading.outdated) return /* @__PURE__ */ jsx21(Loading, { message: t("loading_outdated") });
2999
+ if (errors.outdated) return /* @__PURE__ */ jsx21(ErrorMessage, { message: errors.outdated });
2889
3000
  if (stream.isRunning || stream.lines.length > 0) {
2890
3001
  return /* @__PURE__ */ jsxs18(Box17, { flexDirection: "column", children: [
2891
- /* @__PURE__ */ jsx20(
3002
+ /* @__PURE__ */ jsx21(
2892
3003
  ProgressLog,
2893
3004
  {
2894
3005
  lines: stream.lines,
2895
3006
  isRunning: stream.isRunning,
2896
- title: t("outdated_upgrading")
3007
+ title: t("outdated_upgrading"),
3008
+ maxVisible: streamRows
2897
3009
  }
2898
3010
  ),
2899
3011
  stream.isRunning && /* @__PURE__ */ jsxs18(Text20, { color: COLORS.textSecondary, children: [
@@ -2902,7 +3014,7 @@ function OutdatedView() {
2902
3014
  ] }),
2903
3015
  !stream.isRunning && /* @__PURE__ */ jsxs18(Box17, { flexDirection: "column", marginTop: SPACING.xs, children: [
2904
3016
  /* @__PURE__ */ jsxs18(Box17, { borderStyle: "round", borderColor: stream.error ? COLORS.error : COLORS.success, paddingX: SPACING.sm, paddingY: SPACING.none, children: [
2905
- /* @__PURE__ */ jsx20(Text20, { color: stream.error ? COLORS.error : COLORS.success, bold: true, children: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("outdated_upgradeComplete")}` }),
3017
+ /* @__PURE__ */ jsx21(Text20, { color: stream.error ? COLORS.error : COLORS.success, bold: true, children: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("outdated_upgradeComplete")}` }),
2906
3018
  /* @__PURE__ */ jsxs18(Text20, { color: COLORS.muted, children: [
2907
3019
  " ",
2908
3020
  t("outdated_pressRefresh")
@@ -2920,8 +3032,8 @@ function OutdatedView() {
2920
3032
  const upgradeAllMessage = confirmAction?.type === "all" ? `${t("outdated_confirmAll", { count: allOutdated.length })}
2921
3033
  ${t("outdated_upgradeAllList", { list: allOutdated.map((p) => p.name).join(", ") })}` : "";
2922
3034
  return /* @__PURE__ */ jsxs18(Box17, { flexDirection: "column", children: [
2923
- /* @__PURE__ */ jsx20(SectionHeader, { emoji: "\u{1F4E6}", title: t("outdated_title", { count: allOutdated.length }), gradient: GRADIENTS.fire }),
2924
- confirmAction && /* @__PURE__ */ jsx20(Box17, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx20(
3035
+ /* @__PURE__ */ jsx21(SectionHeader, { emoji: "\u{1F4E6}", title: t("outdated_title", { count: allOutdated.length }), gradient: GRADIENTS.fire }),
3036
+ confirmAction && /* @__PURE__ */ jsx21(Box17, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx21(
2925
3037
  ConfirmDialog,
2926
3038
  {
2927
3039
  message: confirmAction.type === "all" ? upgradeAllMessage : t("outdated_confirmSingle", { name: confirmAction.type === "single" ? confirmAction.name : "" }),
@@ -2939,7 +3051,7 @@ ${t("outdated_upgradeAllList", { list: allOutdated.map((p) => p.name).join(", ")
2939
3051
  onCancel: () => setConfirmAction(null)
2940
3052
  }
2941
3053
  ) }),
2942
- allOutdated.length === 0 && !confirmAction && /* @__PURE__ */ jsx20(Box17, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx20(ResultBanner, { status: "success", message: `\u2714 ${t("outdated_upToDate")}` }) }),
3054
+ allOutdated.length === 0 && !confirmAction && /* @__PURE__ */ jsx21(Box17, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx21(ResultBanner, { status: "success", message: `\u2714 ${t("outdated_upToDate")}` }) }),
2943
3055
  allOutdated.length > 0 && !confirmAction && /* @__PURE__ */ jsxs18(Box17, { flexDirection: "column", marginTop: SPACING.xs, children: [
2944
3056
  start > 0 && /* @__PURE__ */ jsxs18(Text20, { color: COLORS.textSecondary, dimColor: true, children: [
2945
3057
  " ",
@@ -2949,31 +3061,31 @@ ${t("outdated_upgradeAllList", { list: allOutdated.map((p) => p.name).join(", ")
2949
3061
  const idx = start + i;
2950
3062
  const isCurrent = idx === cursor;
2951
3063
  return /* @__PURE__ */ jsxs18(SelectableRow, { isCurrent, children: [
2952
- /* @__PURE__ */ jsx20(Text20, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: pkg.name }),
2953
- /* @__PURE__ */ jsx20(VersionArrow, { current: pkg.installed_versions[0] ?? "", latest: pkg.current_version }),
2954
- pkg.pinned && /* @__PURE__ */ jsx20(StatusBadge, { label: t("outdated_pinned"), variant: "info" })
3064
+ /* @__PURE__ */ jsx21(Text20, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: pkg.name }),
3065
+ /* @__PURE__ */ jsx21(VersionArrow, { current: pkg.installed_versions[0] ?? "", latest: pkg.current_version }),
3066
+ pkg.pinned && /* @__PURE__ */ jsx21(StatusBadge, { label: t("outdated_pinned"), variant: "info" })
2955
3067
  ] }, pkg.name);
2956
3068
  }),
2957
3069
  start + MAX_VISIBLE_ROWS < allOutdated.length && /* @__PURE__ */ jsxs18(Text20, { color: COLORS.textSecondary, dimColor: true, children: [
2958
3070
  " ",
2959
3071
  t("scroll_moreBelow", { count: allOutdated.length - start - MAX_VISIBLE_ROWS })
2960
3072
  ] }),
2961
- /* @__PURE__ */ jsx20(Box17, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs18(Text20, { color: COLORS.text, bold: true, children: [
3073
+ /* @__PURE__ */ jsx21(Box17, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs18(Text20, { color: COLORS.text, bold: true, children: [
2962
3074
  cursor + 1,
2963
3075
  "/",
2964
3076
  allOutdated.length
2965
3077
  ] }) }),
2966
- impact && !stream.isRunning && !confirmAction && /* @__PURE__ */ jsx20(ImpactPanel, { impact }),
2967
- impactLoading && !stream.isRunning && !confirmAction && /* @__PURE__ */ jsx20(Box17, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx20(Text20, { color: COLORS.textSecondary, children: t("impact_analyzing") }) }),
2968
- /* @__PURE__ */ jsx20(Box17, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx20(Text20, { color: COLORS.textSecondary, children: t("impact_hint") }) })
3078
+ impact && !stream.isRunning && !confirmAction && /* @__PURE__ */ jsx21(ImpactPanel, { impact }),
3079
+ impactLoading && !stream.isRunning && !confirmAction && /* @__PURE__ */ jsx21(Box17, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx21(Text20, { color: COLORS.textSecondary, children: t("impact_analyzing") }) }),
3080
+ /* @__PURE__ */ jsx21(Box17, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx21(Text20, { color: COLORS.textSecondary, children: t("impact_hint") }) })
2969
3081
  ] })
2970
3082
  ] });
2971
3083
  }
2972
3084
 
2973
3085
  // src/views/package-info.tsx
2974
- import { useEffect as useEffect12, useRef as useRef5, useState as useState9 } from "react";
3086
+ import { useEffect as useEffect12, useRef as useRef6, useState as useState9 } from "react";
2975
3087
  import { Box as Box18, Text as Text21 } from "ink";
2976
- import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
3088
+ import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
2977
3089
  var ACTION_PROGRESS_KEYS = {
2978
3090
  install: "pkgInfo_installing",
2979
3091
  uninstall: "pkgInfo_uninstalling",
@@ -2991,9 +3103,9 @@ function PackageInfoView() {
2991
3103
  const [loading, setLoading2] = useState9(true);
2992
3104
  const [error, setError2] = useState9(null);
2993
3105
  const [confirmAction, setConfirmAction] = useState9(null);
2994
- const activeActionRef = useRef5("install");
2995
- const mountedRef = useRef5(true);
2996
- const hasRefreshed = useRef5(false);
3106
+ const activeActionRef = useRef6("install");
3107
+ const mountedRef = useRef6(true);
3108
+ const hasRefreshed = useRef6(false);
2997
3109
  const stream = useBrewStream();
2998
3110
  useEffect12(() => {
2999
3111
  mountedRef.current = true;
@@ -3069,20 +3181,20 @@ function PackageInfoView() {
3069
3181
  }
3070
3182
  });
3071
3183
  if (!packageName) {
3072
- return /* @__PURE__ */ jsx21(Text21, { color: COLORS.textSecondary, italic: true, children: t("pkgInfo_noPackage") });
3184
+ return /* @__PURE__ */ jsx22(Text21, { color: COLORS.textSecondary, italic: true, children: t("pkgInfo_noPackage") });
3073
3185
  }
3074
- if (loading) return /* @__PURE__ */ jsx21(Loading, { message: t("loading_package", { name: packageName }) });
3075
- if (error) return /* @__PURE__ */ jsx21(ErrorMessage, { message: error });
3076
- if (!formula) return /* @__PURE__ */ jsx21(ErrorMessage, { message: t("pkgInfo_notFound") });
3186
+ if (loading) return /* @__PURE__ */ jsx22(Loading, { message: t("loading_package", { name: packageName }) });
3187
+ if (error) return /* @__PURE__ */ jsx22(ErrorMessage, { message: error });
3188
+ if (!formula) return /* @__PURE__ */ jsx22(ErrorMessage, { message: t("pkgInfo_notFound") });
3077
3189
  if (stream.isRunning || stream.lines.length > 0) {
3078
3190
  return /* @__PURE__ */ jsxs19(Box18, { flexDirection: "column", children: [
3079
- /* @__PURE__ */ jsx21(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t(ACTION_PROGRESS_KEYS[activeActionRef.current] ?? ACTION_PROGRESS_KEYS["install"], { name: formula.name }) }),
3191
+ /* @__PURE__ */ jsx22(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t(ACTION_PROGRESS_KEYS[activeActionRef.current] ?? ACTION_PROGRESS_KEYS["install"], { name: formula.name }) }),
3080
3192
  stream.isRunning && /* @__PURE__ */ jsxs19(Text21, { color: COLORS.textSecondary, children: [
3081
3193
  "esc:",
3082
3194
  t("hint_cancel")
3083
3195
  ] }),
3084
3196
  !stream.isRunning && /* @__PURE__ */ jsxs19(Fragment4, { children: [
3085
- /* @__PURE__ */ jsx21(Text21, { color: stream.error ? COLORS.error : COLORS.success, bold: true, children: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("pkgInfo_done")}` }),
3197
+ /* @__PURE__ */ jsx22(Text21, { color: stream.error ? COLORS.error : COLORS.success, bold: true, children: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("pkgInfo_done")}` }),
3086
3198
  /* @__PURE__ */ jsxs19(Text21, { color: COLORS.textSecondary, children: [
3087
3199
  "esc:",
3088
3200
  t("hint_back")
@@ -3093,7 +3205,7 @@ function PackageInfoView() {
3093
3205
  const installed = formula.installed[0];
3094
3206
  const isInstalled = formula.installed.length > 0;
3095
3207
  return /* @__PURE__ */ jsxs19(Box18, { flexDirection: "column", children: [
3096
- confirmAction && /* @__PURE__ */ jsx21(
3208
+ confirmAction && /* @__PURE__ */ jsx22(
3097
3209
  ConfirmDialog,
3098
3210
  {
3099
3211
  message: t(ACTION_CONFIRM_KEYS[confirmAction], { name: formula.name }),
@@ -3110,42 +3222,42 @@ function PackageInfoView() {
3110
3222
  }
3111
3223
  ),
3112
3224
  /* @__PURE__ */ jsxs19(Box18, { gap: SPACING.sm, marginBottom: SPACING.xs, children: [
3113
- /* @__PURE__ */ jsx21(GradientText, { colors: GRADIENTS.gold, bold: true, children: formula.name }),
3114
- /* @__PURE__ */ jsx21(Text21, { color: COLORS.teal, children: installed?.version ?? formula.versions.stable }),
3115
- isInstalled && /* @__PURE__ */ jsx21(StatusBadge, { label: t("badge_installed"), variant: "success" }),
3116
- formula.outdated && /* @__PURE__ */ jsx21(StatusBadge, { label: t("badge_outdated"), variant: "warning" }),
3117
- formula.pinned && /* @__PURE__ */ jsx21(StatusBadge, { label: t("badge_pinned"), variant: "info" }),
3118
- formula.keg_only && /* @__PURE__ */ jsx21(StatusBadge, { label: t("badge_kegOnly"), variant: "muted" }),
3119
- formula.deprecated && /* @__PURE__ */ jsx21(StatusBadge, { label: t("badge_deprecated"), variant: "error" })
3225
+ /* @__PURE__ */ jsx22(GradientText, { colors: GRADIENTS.gold, bold: true, children: formula.name }),
3226
+ /* @__PURE__ */ jsx22(Text21, { color: COLORS.teal, children: installed?.version ?? formula.versions.stable }),
3227
+ isInstalled && /* @__PURE__ */ jsx22(StatusBadge, { label: t("badge_installed"), variant: "success" }),
3228
+ formula.outdated && /* @__PURE__ */ jsx22(StatusBadge, { label: t("badge_outdated"), variant: "warning" }),
3229
+ formula.pinned && /* @__PURE__ */ jsx22(StatusBadge, { label: t("badge_pinned"), variant: "info" }),
3230
+ formula.keg_only && /* @__PURE__ */ jsx22(StatusBadge, { label: t("badge_kegOnly"), variant: "muted" }),
3231
+ formula.deprecated && /* @__PURE__ */ jsx22(StatusBadge, { label: t("badge_deprecated"), variant: "error" })
3120
3232
  ] }),
3121
3233
  /* @__PURE__ */ jsxs19(Box18, { flexDirection: "column", gap: SPACING.xs, children: [
3122
- /* @__PURE__ */ jsx21(Text21, { children: formula.desc }),
3234
+ /* @__PURE__ */ jsx22(Text21, { children: formula.desc }),
3123
3235
  /* @__PURE__ */ jsxs19(Box18, { flexDirection: "column", children: [
3124
- /* @__PURE__ */ jsx21(SectionHeader, { emoji: "\u{1F4CB}", title: t("pkgInfo_details"), gradient: [COLORS.text, COLORS.muted] }),
3236
+ /* @__PURE__ */ jsx22(SectionHeader, { emoji: "\u{1F4CB}", title: t("pkgInfo_details"), gradient: [COLORS.text, COLORS.muted] }),
3125
3237
  /* @__PURE__ */ jsxs19(Box18, { borderStyle: "round", borderColor: COLORS.border, paddingX: SPACING.sm, flexDirection: "column", children: [
3126
3238
  /* @__PURE__ */ jsxs19(Text21, { children: [
3127
- /* @__PURE__ */ jsx21(Text21, { color: COLORS.muted, children: t("pkgInfo_homepage") }),
3239
+ /* @__PURE__ */ jsx22(Text21, { color: COLORS.muted, children: t("pkgInfo_homepage") }),
3128
3240
  " ",
3129
3241
  formula.homepage
3130
3242
  ] }),
3131
3243
  /* @__PURE__ */ jsxs19(Text21, { children: [
3132
- /* @__PURE__ */ jsx21(Text21, { color: COLORS.muted, children: t("pkgInfo_license") }),
3244
+ /* @__PURE__ */ jsx22(Text21, { color: COLORS.muted, children: t("pkgInfo_license") }),
3133
3245
  " ",
3134
3246
  formula.license
3135
3247
  ] }),
3136
3248
  /* @__PURE__ */ jsxs19(Text21, { children: [
3137
- /* @__PURE__ */ jsx21(Text21, { color: COLORS.muted, children: t("pkgInfo_tap") }),
3249
+ /* @__PURE__ */ jsx22(Text21, { color: COLORS.muted, children: t("pkgInfo_tap") }),
3138
3250
  " ",
3139
3251
  formula.tap
3140
3252
  ] }),
3141
3253
  /* @__PURE__ */ jsxs19(Text21, { children: [
3142
- /* @__PURE__ */ jsx21(Text21, { color: COLORS.muted, children: t("pkgInfo_stable") }),
3254
+ /* @__PURE__ */ jsx22(Text21, { color: COLORS.muted, children: t("pkgInfo_stable") }),
3143
3255
  " ",
3144
3256
  formula.versions.stable
3145
3257
  ] }),
3146
3258
  installed && /* @__PURE__ */ jsxs19(Fragment4, { children: [
3147
3259
  /* @__PURE__ */ jsxs19(Text21, { children: [
3148
- /* @__PURE__ */ jsx21(Text21, { color: COLORS.muted, children: t("pkgInfo_installed") }),
3260
+ /* @__PURE__ */ jsx22(Text21, { color: COLORS.muted, children: t("pkgInfo_installed") }),
3149
3261
  " ",
3150
3262
  installed.version,
3151
3263
  " (",
@@ -3153,12 +3265,12 @@ function PackageInfoView() {
3153
3265
  ")"
3154
3266
  ] }),
3155
3267
  /* @__PURE__ */ jsxs19(Text21, { children: [
3156
- /* @__PURE__ */ jsx21(Text21, { color: COLORS.muted, children: t("pkgInfo_bottle") }),
3268
+ /* @__PURE__ */ jsx22(Text21, { color: COLORS.muted, children: t("pkgInfo_bottle") }),
3157
3269
  " ",
3158
3270
  installed.poured_from_bottle ? t("common_yes") : t("common_no")
3159
3271
  ] }),
3160
3272
  /* @__PURE__ */ jsxs19(Text21, { children: [
3161
- /* @__PURE__ */ jsx21(Text21, { color: COLORS.muted, children: t("pkgInfo_onRequest") }),
3273
+ /* @__PURE__ */ jsx22(Text21, { color: COLORS.muted, children: t("pkgInfo_onRequest") }),
3162
3274
  " ",
3163
3275
  installed.installed_on_request ? t("common_yes") : t("pkgInfo_noDependency")
3164
3276
  ] })
@@ -3166,15 +3278,18 @@ function PackageInfoView() {
3166
3278
  ] })
3167
3279
  ] }),
3168
3280
  formula.dependencies.length > 0 && /* @__PURE__ */ jsxs19(Box18, { flexDirection: "column", children: [
3169
- /* @__PURE__ */ jsx21(SectionHeader, { emoji: "\u{1F517}", title: t("pkgInfo_dependencies", { count: formula.dependencies.length }), gradient: GRADIENTS.ocean }),
3170
- /* @__PURE__ */ jsx21(Box18, { paddingLeft: SPACING.sm, flexWrap: "wrap", columnGap: 2, children: formula.dependencies.map((dep) => /* @__PURE__ */ jsx21(Text21, { color: COLORS.muted, children: dep }, dep)) })
3281
+ /* @__PURE__ */ jsx22(SectionHeader, { emoji: "\u{1F517}", title: t("pkgInfo_dependencies", { count: formula.dependencies.length }), gradient: GRADIENTS.ocean }),
3282
+ /* @__PURE__ */ jsxs19(Box18, { paddingLeft: SPACING.sm, flexWrap: "wrap", columnGap: 2, children: [
3283
+ formula.dependencies.slice(0, 30).map((dep) => /* @__PURE__ */ jsx22(Text21, { color: COLORS.muted, children: dep }, dep)),
3284
+ formula.dependencies.length > 30 && /* @__PURE__ */ jsx22(Text21, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: formula.dependencies.length - 30 }) })
3285
+ ] })
3171
3286
  ] }),
3172
3287
  formula.caveats && /* @__PURE__ */ jsxs19(Box18, { flexDirection: "column", children: [
3173
- /* @__PURE__ */ jsx21(SectionHeader, { emoji: "\u26A0\uFE0F", title: t("pkgInfo_caveats"), color: COLORS.warning }),
3174
- /* @__PURE__ */ jsx21(Box18, { borderStyle: "round", borderColor: COLORS.warning, paddingX: SPACING.sm, children: /* @__PURE__ */ jsx21(Text21, { color: COLORS.warning, children: formula.caveats }) })
3288
+ /* @__PURE__ */ jsx22(SectionHeader, { emoji: "\u26A0\uFE0F", title: t("pkgInfo_caveats"), color: COLORS.warning }),
3289
+ /* @__PURE__ */ jsx22(Box18, { borderStyle: "round", borderColor: COLORS.warning, paddingX: SPACING.sm, children: /* @__PURE__ */ jsx22(Text21, { color: COLORS.warning, wrap: "wrap", children: formula.caveats }) })
3175
3290
  ] })
3176
3291
  ] }),
3177
- /* @__PURE__ */ jsx21(Box18, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs19(Text21, { color: COLORS.textSecondary, children: [
3292
+ /* @__PURE__ */ jsx22(Box18, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs19(Text21, { color: COLORS.textSecondary, children: [
3178
3293
  isInstalled ? `u:${t("hint_uninstall")}` : `i:${t("hint_install")}`,
3179
3294
  isInstalled && formula.outdated ? ` U:${t("hint_upgrade")}` : "",
3180
3295
  ` esc:${t("hint_back")}`
@@ -3183,9 +3298,9 @@ function PackageInfoView() {
3183
3298
  }
3184
3299
 
3185
3300
  // src/views/services.tsx
3186
- import { useEffect as useEffect13, useState as useState10 } from "react";
3187
- import { Box as Box19, Text as Text22, useStdout as useStdout5 } from "ink";
3188
- import { jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
3301
+ import { useEffect as useEffect13, useRef as useRef7, useState as useState10 } from "react";
3302
+ import { Box as Box19, Text as Text22 } from "ink";
3303
+ import { jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
3189
3304
  var STATUS_VARIANTS = {
3190
3305
  started: "success",
3191
3306
  stopped: "muted",
@@ -3204,11 +3319,16 @@ function ServicesView() {
3204
3319
  const [actionInProgress, setActionInProgress] = useState10(false);
3205
3320
  const [confirmAction, setConfirmAction] = useState10(null);
3206
3321
  const [lastError, setLastError] = useState10(null);
3207
- const { stdout } = useStdout5();
3208
- const cols = stdout?.columns ?? 80;
3209
- const svcNameWidth = Math.floor(cols * 0.35);
3210
- const svcStatusWidth = Math.floor(cols * 0.15);
3211
- const MAX_VISIBLE_ROWS = Math.max(5, (stdout?.rows ?? 24) - 10);
3322
+ const containerRef = useRef7(null);
3323
+ const { width: containerWidth } = useContainerSize(containerRef);
3324
+ const cols = containerWidth > 0 ? containerWidth : 80;
3325
+ const svcNameWidth = Math.max(12, Math.floor(cols * 0.35));
3326
+ const svcStatusWidth = Math.max(8, Math.floor(cols * 0.15));
3327
+ const MAX_VISIBLE_ROWS = useVisibleRows({
3328
+ reservedRows: lastError || actionInProgress ? 8 : 6,
3329
+ fallbackReservedRows: lastError || actionInProgress ? 16 : 14,
3330
+ minRows: 1
3331
+ });
3212
3332
  useEffect13(() => {
3213
3333
  fetchServices();
3214
3334
  }, []);
@@ -3241,19 +3361,19 @@ function ServicesView() {
3241
3361
  else if (input === "x" || input === "2") setConfirmAction({ type: "stop", name: svc.name });
3242
3362
  else if (input === "R" || input === "3") setConfirmAction({ type: "restart", name: svc.name });
3243
3363
  });
3244
- if (loading.services) return /* @__PURE__ */ jsx22(Loading, { message: t("loading_services") });
3245
- if (errors.services) return /* @__PURE__ */ jsx22(ErrorMessage, { message: errors.services });
3364
+ if (loading.services) return /* @__PURE__ */ jsx23(Loading, { message: t("loading_services") });
3365
+ if (errors.services) return /* @__PURE__ */ jsx23(ErrorMessage, { message: errors.services });
3246
3366
  if (services.length === 0) {
3247
3367
  return /* @__PURE__ */ jsxs20(Box19, { flexDirection: "column", children: [
3248
- /* @__PURE__ */ jsx22(SectionHeader, { emoji: "\u2699\uFE0F", title: t("services_title"), gradient: GRADIENTS.ocean }),
3249
- /* @__PURE__ */ jsx22(Text22, { color: COLORS.textSecondary, italic: true, children: t("services_noServices") })
3368
+ /* @__PURE__ */ jsx23(SectionHeader, { emoji: "\u2699\uFE0F", title: t("services_title"), gradient: GRADIENTS.ocean }),
3369
+ /* @__PURE__ */ jsx23(Text22, { color: COLORS.textSecondary, italic: true, children: t("services_noServices") })
3250
3370
  ] });
3251
3371
  }
3252
3372
  const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
3253
3373
  const visible = services.slice(start, start + MAX_VISIBLE_ROWS);
3254
- return /* @__PURE__ */ jsxs20(Box19, { flexDirection: "column", children: [
3255
- /* @__PURE__ */ jsx22(SectionHeader, { emoji: "\u2699\uFE0F", title: t("services_titleCount", { count: services.length }), gradient: GRADIENTS.ocean }),
3256
- confirmAction && /* @__PURE__ */ jsx22(Box19, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx22(
3374
+ return /* @__PURE__ */ jsxs20(Box19, { flexDirection: "column", ref: containerRef, children: [
3375
+ /* @__PURE__ */ jsx23(SectionHeader, { emoji: "\u2699\uFE0F", title: t("services_titleCount", { count: services.length }), gradient: GRADIENTS.ocean }),
3376
+ confirmAction && /* @__PURE__ */ jsx23(Box19, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx23(
3257
3377
  ConfirmDialog,
3258
3378
  {
3259
3379
  message: confirmAction.type === "stop" ? t("services_confirmStop", { name: confirmAction.name }) : t("services_confirmRestart", { name: confirmAction.name }),
@@ -3278,8 +3398,8 @@ function ServicesView() {
3278
3398
  " ",
3279
3399
  t("services_name").padEnd(svcNameWidth)
3280
3400
  ] }),
3281
- /* @__PURE__ */ jsx22(Text22, { bold: true, color: COLORS.text, children: t("services_status").padEnd(svcStatusWidth) }),
3282
- /* @__PURE__ */ jsx22(Text22, { bold: true, color: COLORS.text, children: t("services_user") })
3401
+ /* @__PURE__ */ jsx23(Text22, { bold: true, color: COLORS.text, children: t("services_status").padEnd(svcStatusWidth) }),
3402
+ /* @__PURE__ */ jsx23(Text22, { bold: true, color: COLORS.text, children: t("services_user") })
3283
3403
  ] }),
3284
3404
  start > 0 && /* @__PURE__ */ jsxs20(Text22, { color: COLORS.textSecondary, dimColor: true, children: [
3285
3405
  " ",
@@ -3289,10 +3409,10 @@ function ServicesView() {
3289
3409
  const idx = start + i;
3290
3410
  const isCurrent = idx === cursor;
3291
3411
  return /* @__PURE__ */ jsxs20(SelectableRow, { isCurrent, children: [
3292
- /* @__PURE__ */ jsx22(Text22, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: svc.name.padEnd(svcNameWidth - 2) }),
3293
- /* @__PURE__ */ jsx22(StatusBadge, { label: svc.status, variant: STATUS_VARIANTS[svc.status] }),
3294
- /* @__PURE__ */ jsx22(Text22, { color: COLORS.muted, children: svc.user ?? "-" }),
3295
- svc.exit_code != null && svc.exit_code !== 0 && /* @__PURE__ */ jsx22(Text22, { color: COLORS.error, children: t("common_exit", { code: svc.exit_code }) })
3412
+ /* @__PURE__ */ jsx23(Text22, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: truncate(svc.name, svcNameWidth - 2).padEnd(svcNameWidth - 2) }),
3413
+ /* @__PURE__ */ jsx23(StatusBadge, { label: svc.status, variant: STATUS_VARIANTS[svc.status] }),
3414
+ /* @__PURE__ */ jsx23(Text22, { color: COLORS.muted, children: svc.user ?? "-" }),
3415
+ svc.exit_code != null && svc.exit_code !== 0 && /* @__PURE__ */ jsx23(Text22, { color: COLORS.error, children: t("common_exit", { code: svc.exit_code }) })
3296
3416
  ] }, svc.name);
3297
3417
  }),
3298
3418
  start + MAX_VISIBLE_ROWS < services.length && /* @__PURE__ */ jsxs20(Text22, { color: COLORS.textSecondary, dimColor: true, children: [
@@ -3300,9 +3420,9 @@ function ServicesView() {
3300
3420
  t("scroll_moreBelow", { count: services.length - start - MAX_VISIBLE_ROWS })
3301
3421
  ] })
3302
3422
  ] }),
3303
- actionInProgress && /* @__PURE__ */ jsx22(Text22, { color: COLORS.sky, children: t("services_processing") }),
3304
- lastError && /* @__PURE__ */ jsx22(Box19, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx22(Text22, { color: COLORS.error, children: lastError }) }),
3305
- /* @__PURE__ */ jsx22(Box19, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs20(Text22, { color: COLORS.text, bold: true, children: [
3423
+ actionInProgress && /* @__PURE__ */ jsx23(Text22, { color: COLORS.sky, children: t("services_processing") }),
3424
+ lastError && /* @__PURE__ */ jsx23(Box19, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx23(Text22, { color: COLORS.error, children: lastError }) }),
3425
+ /* @__PURE__ */ jsx23(Box19, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs20(Text22, { color: COLORS.text, bold: true, children: [
3306
3426
  cursor + 1,
3307
3427
  "/",
3308
3428
  services.length
@@ -3311,12 +3431,18 @@ function ServicesView() {
3311
3431
  }
3312
3432
 
3313
3433
  // src/views/doctor.tsx
3314
- import { useEffect as useEffect14, useRef as useRef6 } from "react";
3434
+ import { useEffect as useEffect14, useRef as useRef8, useState as useState11 } from "react";
3315
3435
  import { Box as Box20, Text as Text23 } from "ink";
3316
- import { jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
3436
+ import { jsx as jsx24, jsxs as jsxs21 } from "react/jsx-runtime";
3317
3437
  function DoctorView() {
3318
3438
  const { doctorWarnings, doctorClean, loading, errors, fetchDoctor } = useBrewStore();
3319
- const mountedRef = useRef6(true);
3439
+ const [cursor, setCursor] = useState11(0);
3440
+ const visibleWarnings = useVisibleRows({
3441
+ reservedRows: 6,
3442
+ fallbackReservedRows: 14,
3443
+ minRows: 1
3444
+ });
3445
+ const mountedRef = useRef8(true);
3320
3446
  useEffect14(() => {
3321
3447
  mountedRef.current = true;
3322
3448
  return () => {
@@ -3326,27 +3452,42 @@ function DoctorView() {
3326
3452
  useEffect14(() => {
3327
3453
  fetchDoctor();
3328
3454
  }, []);
3329
- useViewInput((input) => {
3330
- if (input === "r" || input === "1") void fetchDoctor();
3455
+ useViewInput((input, key) => {
3456
+ if (input === "r" || input === "1") {
3457
+ void fetchDoctor();
3458
+ return;
3459
+ }
3460
+ if (input === "j" || key.downArrow) setCursor((c) => Math.min(c + 1, Math.max(0, doctorWarnings.length - 1)));
3461
+ else if (input === "k" || key.upArrow) setCursor((c) => Math.max(c - 1, 0));
3331
3462
  });
3332
- if (loading.doctor) return /* @__PURE__ */ jsx23(Loading, { message: t("loading_doctor") });
3333
- if (errors.doctor) return /* @__PURE__ */ jsx23(ErrorMessage, { message: errors.doctor });
3463
+ if (loading.doctor) return /* @__PURE__ */ jsx24(Loading, { message: t("loading_doctor") });
3464
+ if (errors.doctor) return /* @__PURE__ */ jsx24(ErrorMessage, { message: errors.doctor });
3465
+ const start = Math.max(0, cursor - Math.floor(visibleWarnings / 2));
3466
+ const visible = doctorWarnings.slice(start, start + visibleWarnings);
3334
3467
  return /* @__PURE__ */ jsxs21(Box20, { flexDirection: "column", children: [
3335
- /* @__PURE__ */ jsx23(SectionHeader, { emoji: "\u{1FA7A}", title: t("doctor_title"), gradient: GRADIENTS.emerald }),
3468
+ /* @__PURE__ */ jsx24(SectionHeader, { emoji: "\u{1FA7A}", title: t("doctor_title"), gradient: GRADIENTS.emerald }),
3336
3469
  /* @__PURE__ */ jsxs21(Box20, { flexDirection: "column", marginTop: SPACING.xs, children: [
3337
- doctorClean && /* @__PURE__ */ jsx23(ResultBanner, { status: "success", message: `\u2714 ${t("doctor_clean")}` }),
3338
- doctorClean === false && doctorWarnings.length === 0 && /* @__PURE__ */ jsx23(Text23, { color: COLORS.warning, children: t("doctor_warningsNotCaptured") }),
3339
- doctorWarnings.map((warning, i) => (
3340
- // FE-004: Improved React key
3341
- /* @__PURE__ */ jsx23(Box20, { flexDirection: "column", marginBottom: SPACING.xs, borderStyle: "single", borderColor: COLORS.warning, paddingX: SPACING.xs, children: warning.split("\n").map((line, j) => /* @__PURE__ */ jsx23(Text23, { color: j === 0 ? COLORS.warning : COLORS.muted, children: line }, `warning-${i}-${j}-${line.slice(0, 20)}`)) }, `warning-${i}-${warning.slice(0, 20)}`)
3342
- ))
3470
+ doctorClean && /* @__PURE__ */ jsx24(ResultBanner, { status: "success", message: `\u2714 ${t("doctor_clean")}` }),
3471
+ doctorClean === false && doctorWarnings.length === 0 && /* @__PURE__ */ jsx24(Text23, { color: COLORS.warning, children: t("doctor_warningsNotCaptured") }),
3472
+ start > 0 && /* @__PURE__ */ jsxs21(Text23, { color: COLORS.textSecondary, dimColor: true, children: [
3473
+ " ",
3474
+ t("scroll_moreAbove", { count: start })
3475
+ ] }),
3476
+ visible.map((warning, i) => {
3477
+ const idx = start + i;
3478
+ 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)}`);
3479
+ }),
3480
+ start + visibleWarnings < doctorWarnings.length && /* @__PURE__ */ jsxs21(Text23, { color: COLORS.textSecondary, dimColor: true, children: [
3481
+ " ",
3482
+ t("scroll_moreBelow", { count: doctorWarnings.length - start - visibleWarnings })
3483
+ ] })
3343
3484
  ] }),
3344
- /* @__PURE__ */ jsx23(Box20, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx23(Text23, { color: COLORS.text, bold: true, children: doctorWarnings.length > 0 ? tp("plural_warnings", doctorWarnings.length) : "" }) })
3485
+ /* @__PURE__ */ jsx24(Box20, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx24(Text23, { color: COLORS.text, bold: true, children: doctorWarnings.length > 0 ? tp("plural_warnings", doctorWarnings.length) : "" }) })
3345
3486
  ] });
3346
3487
  }
3347
3488
 
3348
3489
  // src/views/profiles.tsx
3349
- import { useEffect as useEffect15, useRef as useRef7, useState as useState11 } from "react";
3490
+ import { useEffect as useEffect15, useRef as useRef9, useState as useState12 } from "react";
3350
3491
  import { Box as Box25 } from "ink";
3351
3492
 
3352
3493
  // src/stores/profile-store.ts
@@ -3568,12 +3709,12 @@ var useProfileStore = create9((set) => ({
3568
3709
 
3569
3710
  // src/views/profiles/profile-list-mode.tsx
3570
3711
  import { Box as Box21, Text as Text24 } from "ink";
3571
- import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
3712
+ import { jsx as jsx25, jsxs as jsxs22 } from "react/jsx-runtime";
3572
3713
  function ProfileListMode({ profileNames, cursor, confirmDelete, loadError, onConfirmDelete, onCancelDelete }) {
3573
3714
  return /* @__PURE__ */ jsxs22(Box21, { flexDirection: "column", children: [
3574
- /* @__PURE__ */ jsx24(SectionHeader, { emoji: "\u{1F4C1}", title: t("profiles_title", { count: profileNames.length }), gradient: GRADIENTS.gold }),
3575
- loadError && /* @__PURE__ */ jsx24(Box21, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx24(Text24, { color: COLORS.error, children: loadError }) }),
3576
- confirmDelete && profileNames[cursor] && /* @__PURE__ */ jsx24(Box21, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx24(
3715
+ /* @__PURE__ */ jsx25(SectionHeader, { emoji: "\u{1F4C1}", title: t("profiles_title", { count: profileNames.length }), gradient: GRADIENTS.gold }),
3716
+ loadError && /* @__PURE__ */ jsx25(Box21, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx25(Text24, { color: COLORS.error, children: loadError }) }),
3717
+ confirmDelete && profileNames[cursor] && /* @__PURE__ */ jsx25(Box21, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx25(
3577
3718
  ConfirmDialog,
3578
3719
  {
3579
3720
  message: t("profiles_confirmDelete", { name: profileNames[cursor] }),
@@ -3581,12 +3722,12 @@ function ProfileListMode({ profileNames, cursor, confirmDelete, loadError, onCon
3581
3722
  onCancel: onCancelDelete
3582
3723
  }
3583
3724
  ) }),
3584
- profileNames.length === 0 && !confirmDelete && /* @__PURE__ */ jsx24(Box21, { marginTop: SPACING.xs, borderStyle: "round", borderColor: COLORS.textSecondary, paddingX: SPACING.sm, paddingY: SPACING.none, children: /* @__PURE__ */ jsxs22(Box21, { flexDirection: "column", children: [
3585
- /* @__PURE__ */ jsx24(Text24, { color: COLORS.textSecondary, italic: true, children: t("profiles_noProfiles") }),
3725
+ profileNames.length === 0 && !confirmDelete && /* @__PURE__ */ jsx25(Box21, { marginTop: SPACING.xs, borderStyle: "round", borderColor: COLORS.textSecondary, paddingX: SPACING.sm, paddingY: SPACING.none, children: /* @__PURE__ */ jsxs22(Box21, { flexDirection: "column", children: [
3726
+ /* @__PURE__ */ jsx25(Text24, { color: COLORS.textSecondary, italic: true, children: t("profiles_noProfiles") }),
3586
3727
  /* @__PURE__ */ jsxs22(Text24, { color: COLORS.muted, children: [
3587
3728
  t("profiles_press"),
3588
3729
  " ",
3589
- /* @__PURE__ */ jsx24(Text24, { color: COLORS.gold, bold: true, children: "n" }),
3730
+ /* @__PURE__ */ jsx25(Text24, { color: COLORS.gold, bold: true, children: "n" }),
3590
3731
  " ",
3591
3732
  t("profiles_exportHint")
3592
3733
  ] })
@@ -3594,9 +3735,9 @@ function ProfileListMode({ profileNames, cursor, confirmDelete, loadError, onCon
3594
3735
  profileNames.length > 0 && !confirmDelete && /* @__PURE__ */ jsxs22(Box21, { flexDirection: "column", marginTop: SPACING.xs, children: [
3595
3736
  profileNames.map((name, i) => {
3596
3737
  const isCurrent = i === cursor;
3597
- return /* @__PURE__ */ jsx24(SelectableRow, { isCurrent, children: /* @__PURE__ */ jsx24(Text24, { bold: isCurrent, inverse: isCurrent, children: name }) }, name);
3738
+ return /* @__PURE__ */ jsx25(SelectableRow, { isCurrent, children: /* @__PURE__ */ jsx25(Text24, { bold: isCurrent, inverse: isCurrent, children: name }) }, name);
3598
3739
  }),
3599
- /* @__PURE__ */ jsx24(Box21, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs22(Text24, { color: COLORS.text, bold: true, children: [
3740
+ /* @__PURE__ */ jsx25(Box21, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs22(Text24, { color: COLORS.text, bold: true, children: [
3600
3741
  cursor + 1,
3601
3742
  "/",
3602
3743
  profileNames.length
@@ -3607,22 +3748,37 @@ function ProfileListMode({ profileNames, cursor, confirmDelete, loadError, onCon
3607
3748
 
3608
3749
  // src/views/profiles/profile-detail-mode.tsx
3609
3750
  import { Box as Box22, Text as Text25 } from "ink";
3610
- import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
3751
+ import { jsx as jsx26, jsxs as jsxs23 } from "react/jsx-runtime";
3611
3752
  function ProfileDetailMode({ profile }) {
3753
+ const totalRows = useVisibleRows({
3754
+ reservedRows: 7,
3755
+ fallbackReservedRows: 14,
3756
+ minRows: 2
3757
+ });
3758
+ const total = profile.formulae.length + profile.casks.length;
3759
+ const formulaeBudget = total === 0 ? 0 : Math.min(profile.formulae.length, Math.max(1, Math.round(profile.formulae.length / total * totalRows)));
3760
+ const casksBudget = Math.max(0, totalRows - formulaeBudget);
3761
+ const visibleFormulae = profile.formulae.slice(0, formulaeBudget);
3762
+ const visibleCasks = profile.casks.slice(0, casksBudget);
3763
+ const formulaeHidden = profile.formulae.length - visibleFormulae.length;
3764
+ const casksHidden = profile.casks.length - visibleCasks.length;
3612
3765
  return /* @__PURE__ */ jsxs23(Box22, { flexDirection: "column", children: [
3613
- /* @__PURE__ */ jsx25(Text25, { bold: true, color: COLORS.gold, children: profile.name }),
3614
- /* @__PURE__ */ jsx25(Text25, { color: COLORS.muted, children: profile.description }),
3615
- /* @__PURE__ */ jsx25(Text25, { color: COLORS.muted, children: t("profiles_created", { date: formatDate(profile.createdAt) }) }),
3766
+ /* @__PURE__ */ jsx26(Text25, { bold: true, color: COLORS.gold, children: profile.name }),
3767
+ /* @__PURE__ */ jsx26(Text25, { color: COLORS.muted, wrap: "wrap", children: profile.description }),
3768
+ /* @__PURE__ */ jsx26(Text25, { color: COLORS.muted, children: t("profiles_created", { date: formatDate(profile.createdAt) }) }),
3616
3769
  /* @__PURE__ */ jsxs23(Box22, { marginTop: SPACING.xs, flexDirection: "column", children: [
3617
- /* @__PURE__ */ jsx25(Text25, { bold: true, children: t("profiles_formulaeCount", { count: profile.formulae.length }) }),
3770
+ /* @__PURE__ */ jsx26(Text25, { bold: true, children: t("profiles_formulaeCount", { count: profile.formulae.length }) }),
3618
3771
  /* @__PURE__ */ jsxs23(Box22, { paddingLeft: SPACING.sm, flexDirection: "column", children: [
3619
- profile.formulae.slice(0, 30).map((f) => /* @__PURE__ */ jsx25(Text25, { color: COLORS.muted, children: f }, f)),
3620
- profile.formulae.length > 30 && /* @__PURE__ */ jsx25(Text25, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: profile.formulae.length - 30 }) })
3772
+ visibleFormulae.map((f) => /* @__PURE__ */ jsx26(Text25, { color: COLORS.muted, children: f }, f)),
3773
+ formulaeHidden > 0 && /* @__PURE__ */ jsx26(Text25, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: formulaeHidden }) })
3621
3774
  ] }),
3622
- /* @__PURE__ */ jsx25(Text25, { bold: true, children: t("profiles_casksCount", { count: profile.casks.length }) }),
3623
- /* @__PURE__ */ jsx25(Box22, { paddingLeft: SPACING.sm, flexDirection: "column", children: profile.casks.map((c) => /* @__PURE__ */ jsx25(Text25, { color: COLORS.muted, children: c }, c)) })
3775
+ /* @__PURE__ */ jsx26(Text25, { bold: true, children: t("profiles_casksCount", { count: profile.casks.length }) }),
3776
+ /* @__PURE__ */ jsxs23(Box22, { paddingLeft: SPACING.sm, flexDirection: "column", children: [
3777
+ visibleCasks.map((c) => /* @__PURE__ */ jsx26(Text25, { color: COLORS.muted, children: c }, c)),
3778
+ casksHidden > 0 && /* @__PURE__ */ jsx26(Text25, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: casksHidden }) })
3779
+ ] })
3624
3780
  ] }),
3625
- /* @__PURE__ */ jsx25(Box22, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs23(Text25, { color: COLORS.textSecondary, children: [
3781
+ /* @__PURE__ */ jsx26(Box22, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs23(Text25, { color: COLORS.textSecondary, children: [
3626
3782
  "esc:",
3627
3783
  t("hint_back"),
3628
3784
  " e:",
@@ -3636,11 +3792,11 @@ function ProfileDetailMode({ profile }) {
3636
3792
  // src/views/profiles/profile-create-flow.tsx
3637
3793
  import { Box as Box23, Text as Text26 } from "ink";
3638
3794
  import { TextInput as TextInput3 } from "@inkjs/ui";
3639
- import { jsx as jsx26, jsxs as jsxs24 } from "react/jsx-runtime";
3795
+ import { jsx as jsx27, jsxs as jsxs24 } from "react/jsx-runtime";
3640
3796
  function ProfileCreateName({ onSubmit }) {
3641
3797
  return /* @__PURE__ */ jsxs24(Box23, { flexDirection: "column", children: [
3642
- /* @__PURE__ */ jsx26(Text26, { bold: true, children: t("profiles_createName") }),
3643
- /* @__PURE__ */ jsx26(
3798
+ /* @__PURE__ */ jsx27(Text26, { bold: true, children: t("profiles_createName") }),
3799
+ /* @__PURE__ */ jsx27(
3644
3800
  TextInput3,
3645
3801
  {
3646
3802
  placeholder: t("profiles_namePlaceholder"),
@@ -3651,12 +3807,12 @@ function ProfileCreateName({ onSubmit }) {
3651
3807
  }
3652
3808
  function ProfileCreateDesc({ name, loadError, onSubmit }) {
3653
3809
  return /* @__PURE__ */ jsxs24(Box23, { flexDirection: "column", children: [
3654
- /* @__PURE__ */ jsx26(Text26, { bold: true, children: t("profiles_createDesc", { name }) }),
3810
+ /* @__PURE__ */ jsx27(Text26, { bold: true, children: t("profiles_createDesc", { name }) }),
3655
3811
  loadError && /* @__PURE__ */ jsxs24(Text26, { color: COLORS.error, children: [
3656
3812
  t("error_prefix"),
3657
3813
  loadError
3658
3814
  ] }),
3659
- /* @__PURE__ */ jsx26(
3815
+ /* @__PURE__ */ jsx27(
3660
3816
  TextInput3,
3661
3817
  {
3662
3818
  placeholder: t("profiles_descPlaceholder"),
@@ -3669,11 +3825,11 @@ function ProfileCreateDesc({ name, loadError, onSubmit }) {
3669
3825
  // src/views/profiles/profile-edit-flow.tsx
3670
3826
  import { Box as Box24, Text as Text27 } from "ink";
3671
3827
  import { TextInput as TextInput4 } from "@inkjs/ui";
3672
- import { jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
3828
+ import { jsx as jsx28, jsxs as jsxs25 } from "react/jsx-runtime";
3673
3829
  function ProfileEditName({ defaultName, onSubmit }) {
3674
3830
  return /* @__PURE__ */ jsxs25(Box24, { flexDirection: "column", children: [
3675
- /* @__PURE__ */ jsx27(Text27, { bold: true, children: t("profiles_editName") }),
3676
- /* @__PURE__ */ jsx27(
3831
+ /* @__PURE__ */ jsx28(Text27, { bold: true, children: t("profiles_editName") }),
3832
+ /* @__PURE__ */ jsx28(
3677
3833
  TextInput4,
3678
3834
  {
3679
3835
  defaultValue: defaultName,
@@ -3684,12 +3840,12 @@ function ProfileEditName({ defaultName, onSubmit }) {
3684
3840
  }
3685
3841
  function ProfileEditDesc({ name, defaultDesc, loadError, onSubmit }) {
3686
3842
  return /* @__PURE__ */ jsxs25(Box24, { flexDirection: "column", children: [
3687
- /* @__PURE__ */ jsx27(Text27, { bold: true, children: t("profiles_editDesc", { name }) }),
3843
+ /* @__PURE__ */ jsx28(Text27, { bold: true, children: t("profiles_editDesc", { name }) }),
3688
3844
  loadError && /* @__PURE__ */ jsxs25(Text27, { color: COLORS.error, children: [
3689
3845
  t("error_prefix"),
3690
3846
  loadError
3691
3847
  ] }),
3692
- /* @__PURE__ */ jsx27(
3848
+ /* @__PURE__ */ jsx28(
3693
3849
  TextInput4,
3694
3850
  {
3695
3851
  defaultValue: defaultDesc,
@@ -3700,22 +3856,22 @@ function ProfileEditDesc({ name, defaultDesc, loadError, onSubmit }) {
3700
3856
  }
3701
3857
 
3702
3858
  // src/views/profiles.tsx
3703
- import { jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
3859
+ import { jsx as jsx29, jsxs as jsxs26 } from "react/jsx-runtime";
3704
3860
  function ProfilesView() {
3705
3861
  const { profileNames, selectedProfile, loading, loadError, fetchProfiles, loadProfile: loadProfile2, exportCurrent, deleteProfile: deleteProfile2, updateProfile: updateProfile2 } = useProfileStore();
3706
- const [cursor, setCursor] = useState11(0);
3707
- const [mode, setMode] = useState11("list");
3708
- const [newName, setNewName] = useState11("");
3709
- const [confirmDelete, setConfirmDelete] = useState11(false);
3710
- const [editName, setEditName] = useState11("");
3711
- const [editDesc, setEditDesc] = useState11("");
3712
- const [importLines, setImportLines] = useState11([]);
3713
- const [importRunning, setImportRunning] = useState11(false);
3714
- const [importHadError, setImportHadError] = useState11(false);
3715
- const [importProfile2, setImportProfile] = useState11(null);
3862
+ const [cursor, setCursor] = useState12(0);
3863
+ const [mode, setMode] = useState12("list");
3864
+ const [newName, setNewName] = useState12("");
3865
+ const [confirmDelete, setConfirmDelete] = useState12(false);
3866
+ const [editName, setEditName] = useState12("");
3867
+ const [editDesc, setEditDesc] = useState12("");
3868
+ const [importLines, setImportLines] = useState12([]);
3869
+ const [importRunning, setImportRunning] = useState12(false);
3870
+ const [importHadError, setImportHadError] = useState12(false);
3871
+ const [importProfile2, setImportProfile] = useState12(null);
3716
3872
  const { openModal, closeModal } = useModalStore();
3717
- const importGenRef = useRef7(null);
3718
- const mountedRef = useRef7(true);
3873
+ const importGenRef = useRef9(null);
3874
+ const mountedRef = useRef9(true);
3719
3875
  useEffect15(() => {
3720
3876
  fetchProfiles();
3721
3877
  }, []);
@@ -3809,9 +3965,9 @@ function ProfilesView() {
3809
3965
  }
3810
3966
  }
3811
3967
  };
3812
- if (loading) return /* @__PURE__ */ jsx28(Loading, { message: t("loading_profiles") });
3968
+ if (loading) return /* @__PURE__ */ jsx29(Loading, { message: t("loading_profiles") });
3813
3969
  if (mode === "confirm-import" && importProfile2) {
3814
- return /* @__PURE__ */ jsx28(Box25, { flexDirection: "column", children: /* @__PURE__ */ jsx28(
3970
+ return /* @__PURE__ */ jsx29(Box25, { flexDirection: "column", children: /* @__PURE__ */ jsx29(
3815
3971
  ConfirmDialog,
3816
3972
  {
3817
3973
  message: t("profiles_importSummary", {
@@ -3832,18 +3988,18 @@ function ProfilesView() {
3832
3988
  }
3833
3989
  if (mode === "importing") {
3834
3990
  return /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", children: [
3835
- /* @__PURE__ */ jsx28(ProgressLog, { lines: importLines, isRunning: importRunning, title: t("profiles_importTitle") }),
3836
- !importRunning && /* @__PURE__ */ jsx28(Box25, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx28(ResultBanner, { status: importHadError ? "error" : "success", message: importHadError ? t("profiles_importPartial") : `\u2714 ${t("profiles_importComplete")}` }) })
3991
+ /* @__PURE__ */ jsx29(ProgressLog, { lines: importLines, isRunning: importRunning, title: t("profiles_importTitle") }),
3992
+ !importRunning && /* @__PURE__ */ jsx29(Box25, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx29(ResultBanner, { status: importHadError ? "error" : "success", message: importHadError ? t("profiles_importPartial") : `\u2714 ${t("profiles_importComplete")}` }) })
3837
3993
  ] });
3838
3994
  }
3839
3995
  if (mode === "create-name") {
3840
- return /* @__PURE__ */ jsx28(ProfileCreateName, { onSubmit: (val) => {
3996
+ return /* @__PURE__ */ jsx29(ProfileCreateName, { onSubmit: (val) => {
3841
3997
  setNewName(val);
3842
3998
  setMode("create-desc");
3843
3999
  } });
3844
4000
  }
3845
4001
  if (mode === "create-desc") {
3846
- return /* @__PURE__ */ jsx28(
4002
+ return /* @__PURE__ */ jsx29(
3847
4003
  ProfileCreateDesc,
3848
4004
  {
3849
4005
  name: newName,
@@ -3860,13 +4016,13 @@ function ProfilesView() {
3860
4016
  );
3861
4017
  }
3862
4018
  if (mode === "edit-name") {
3863
- return /* @__PURE__ */ jsx28(ProfileEditName, { defaultName: editName, onSubmit: (val) => {
4019
+ return /* @__PURE__ */ jsx29(ProfileEditName, { defaultName: editName, onSubmit: (val) => {
3864
4020
  setEditName(val);
3865
4021
  setMode("edit-desc");
3866
4022
  } });
3867
4023
  }
3868
4024
  if (mode === "edit-desc") {
3869
- return /* @__PURE__ */ jsx28(
4025
+ return /* @__PURE__ */ jsx29(
3870
4026
  ProfileEditDesc,
3871
4027
  {
3872
4028
  name: editName,
@@ -3884,9 +4040,9 @@ function ProfilesView() {
3884
4040
  );
3885
4041
  }
3886
4042
  if (mode === "detail" && selectedProfile) {
3887
- return /* @__PURE__ */ jsx28(ProfileDetailMode, { profile: selectedProfile });
4043
+ return /* @__PURE__ */ jsx29(ProfileDetailMode, { profile: selectedProfile });
3888
4044
  }
3889
- return /* @__PURE__ */ jsx28(
4045
+ return /* @__PURE__ */ jsx29(
3890
4046
  ProfileListMode,
3891
4047
  {
3892
4048
  profileNames,
@@ -3903,7 +4059,7 @@ function ProfilesView() {
3903
4059
  }
3904
4060
 
3905
4061
  // src/views/smart-cleanup.tsx
3906
- import { useEffect as useEffect16, useRef as useRef8, useState as useState12 } from "react";
4062
+ import { useEffect as useEffect16, useRef as useRef10, useState as useState13 } from "react";
3907
4063
  import { Box as Box26, Text as Text28 } from "ink";
3908
4064
 
3909
4065
  // src/stores/cleanup-store.ts
@@ -4024,15 +4180,20 @@ var useCleanupStore = create10((set, get) => ({
4024
4180
  }));
4025
4181
 
4026
4182
  // src/views/smart-cleanup.tsx
4027
- import { jsx as jsx29, jsxs as jsxs27 } from "react/jsx-runtime";
4183
+ import { jsx as jsx30, jsxs as jsxs27 } from "react/jsx-runtime";
4028
4184
  function SmartCleanupView() {
4029
4185
  const { summary, selected, loading, error, analyze, toggleSelect, selectAll } = useCleanupStore();
4030
- const [cursor, setCursor] = useState12(0);
4031
- const [confirmClean, setConfirmClean] = useState12(false);
4032
- const [confirmForce, setConfirmForce] = useState12(false);
4033
- const [failedNames, setFailedNames] = useState12([]);
4186
+ const [cursor, setCursor] = useState13(0);
4187
+ const [confirmClean, setConfirmClean] = useState13(false);
4188
+ const [confirmForce, setConfirmForce] = useState13(false);
4189
+ const [failedNames, setFailedNames] = useState13([]);
4034
4190
  const stream = useBrewStream();
4035
- const hasRefreshed = useRef8(false);
4191
+ const hasRefreshed = useRef10(false);
4192
+ const listRows = useVisibleRows({
4193
+ reservedRows: confirmClean || confirmForce ? 12 : 9,
4194
+ fallbackReservedRows: confirmClean || confirmForce ? 18 : 14,
4195
+ minRows: 1
4196
+ });
4036
4197
  useEffect16(() => {
4037
4198
  analyze();
4038
4199
  }, []);
@@ -4075,18 +4236,18 @@ function SmartCleanupView() {
4075
4236
  if (input === "j" || key.downArrow) setCursor((c) => Math.min(c + 1, Math.max(0, candidates.length - 1)));
4076
4237
  else if (input === "k" || key.upArrow) setCursor((c) => Math.max(c - 1, 0));
4077
4238
  });
4078
- if (loading) return /* @__PURE__ */ jsx29(Loading, { message: t("loading_cleanup") });
4079
- if (error) return /* @__PURE__ */ jsx29(ErrorMessage, { message: error });
4239
+ if (loading) return /* @__PURE__ */ jsx30(Loading, { message: t("loading_cleanup") });
4240
+ if (error) return /* @__PURE__ */ jsx30(ErrorMessage, { message: error });
4080
4241
  if (stream.isRunning || stream.lines.length > 0) {
4081
4242
  return /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", children: [
4082
- /* @__PURE__ */ jsx29(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t("cleanup_cleaning") }),
4243
+ /* @__PURE__ */ jsx30(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t("cleanup_cleaning") }),
4083
4244
  stream.isRunning && /* @__PURE__ */ jsxs27(Text28, { color: COLORS.muted, children: [
4084
4245
  "esc:",
4085
4246
  t("hint_cancel")
4086
4247
  ] }),
4087
- !stream.isRunning && !stream.error && /* @__PURE__ */ jsx29(ResultBanner, { status: "success", message: `\u2714 ${t("cleanup_complete")}` }),
4248
+ !stream.isRunning && !stream.error && /* @__PURE__ */ jsx30(ResultBanner, { status: "success", message: `\u2714 ${t("cleanup_complete")}` }),
4088
4249
  !stream.isRunning && stream.error && /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", gap: SPACING.xs, children: [
4089
- /* @__PURE__ */ jsx29(ResultBanner, { status: "error", message: `\u2718 ${t("cleanup_depError")}` }),
4250
+ /* @__PURE__ */ jsx30(ResultBanner, { status: "error", message: `\u2718 ${t("cleanup_depError")}` }),
4090
4251
  isDependencyError && failedNames.length > 0 && /* @__PURE__ */ jsxs27(Text28, { color: COLORS.warning, children: [
4091
4252
  "F:",
4092
4253
  t("hint_force"),
@@ -4094,7 +4255,7 @@ function SmartCleanupView() {
4094
4255
  t("hint_refresh")
4095
4256
  ] })
4096
4257
  ] }),
4097
- confirmForce && /* @__PURE__ */ jsx29(Box26, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx29(
4258
+ confirmForce && /* @__PURE__ */ jsx30(Box26, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx30(
4098
4259
  ConfirmDialog,
4099
4260
  {
4100
4261
  message: t("cleanup_confirmForce", { count: failedNames.length }),
@@ -4110,15 +4271,15 @@ function SmartCleanupView() {
4110
4271
  ] });
4111
4272
  }
4112
4273
  return /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", children: [
4113
- /* @__PURE__ */ jsx29(SectionHeader, { emoji: "\u{1F9F9}", title: t("cleanup_title"), gradient: GRADIENTS.emerald }),
4274
+ /* @__PURE__ */ jsx30(SectionHeader, { emoji: "\u{1F9F9}", title: t("cleanup_title"), gradient: GRADIENTS.emerald }),
4114
4275
  summary && /* @__PURE__ */ jsxs27(Box26, { gap: SPACING.xs, marginY: SPACING.xs, children: [
4115
- /* @__PURE__ */ jsx29(StatCard, { label: t("cleanup_orphans"), value: candidates.length, color: candidates.length > 0 ? COLORS.warning : COLORS.success }),
4116
- /* @__PURE__ */ jsx29(StatCard, { label: t("cleanup_reclaimable"), value: summary.totalReclaimableFormatted, color: COLORS.sky }),
4117
- /* @__PURE__ */ jsx29(StatCard, { label: t("cleanup_selected"), value: selected.size, color: selected.size > 0 ? COLORS.success : COLORS.muted })
4276
+ /* @__PURE__ */ jsx30(StatCard, { label: t("cleanup_orphans"), value: candidates.length, color: candidates.length > 0 ? COLORS.warning : COLORS.success }),
4277
+ /* @__PURE__ */ jsx30(StatCard, { label: t("cleanup_reclaimable"), value: summary.totalReclaimableFormatted, color: COLORS.sky }),
4278
+ /* @__PURE__ */ jsx30(StatCard, { label: t("cleanup_selected"), value: selected.size, color: selected.size > 0 ? COLORS.success : COLORS.muted })
4118
4279
  ] }),
4119
4280
  confirmClean && /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", marginY: SPACING.xs, gap: SPACING.xs, children: [
4120
- /* @__PURE__ */ jsx29(Box26, { borderStyle: "round", borderColor: COLORS.warning, paddingX: SPACING.sm, paddingY: SPACING.none, children: /* @__PURE__ */ jsx29(Text28, { color: COLORS.warning, children: t("cleanup_warning_system_tools") }) }),
4121
- /* @__PURE__ */ jsx29(
4281
+ /* @__PURE__ */ jsx30(Box26, { borderStyle: "round", borderColor: COLORS.warning, paddingX: SPACING.sm, paddingY: SPACING.none, children: /* @__PURE__ */ jsx30(Text28, { color: COLORS.warning, children: t("cleanup_warning_system_tools") }) }),
4282
+ /* @__PURE__ */ jsx30(
4122
4283
  ConfirmDialog,
4123
4284
  {
4124
4285
  message: t("cleanup_confirmUninstall", { count: selected.size }),
@@ -4133,34 +4294,47 @@ function SmartCleanupView() {
4133
4294
  }
4134
4295
  )
4135
4296
  ] }),
4136
- candidates.length === 0 && !confirmClean && /* @__PURE__ */ jsx29(ResultBanner, { status: "success", message: `\u2714 ${t("cleanup_systemClean")}` }),
4137
- candidates.length > 0 && !confirmClean && /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", children: [
4138
- candidates.map((c, i) => {
4139
- const isCurrent = i === cursor;
4140
- const isSelected = selected.has(c.name);
4141
- return /* @__PURE__ */ jsxs27(SelectableRow, { isCurrent, children: [
4142
- /* @__PURE__ */ jsx29(Text28, { color: isSelected ? COLORS.success : COLORS.muted, children: isSelected ? "\u2611" : "\u2610" }),
4143
- /* @__PURE__ */ jsx29(Text28, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: c.name }),
4144
- /* @__PURE__ */ jsx29(Text28, { color: COLORS.warning, children: c.diskUsageFormatted }),
4145
- /* @__PURE__ */ jsxs27(Text28, { color: COLORS.textSecondary, children: [
4146
- "[",
4147
- c.reason,
4148
- "]"
4149
- ] })
4150
- ] }, c.name);
4151
- }),
4152
- /* @__PURE__ */ jsx29(Box26, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs27(Text28, { color: COLORS.text, bold: true, children: [
4153
- cursor + 1,
4154
- "/",
4155
- candidates.length
4156
- ] }) })
4157
- ] })
4297
+ candidates.length === 0 && !confirmClean && /* @__PURE__ */ jsx30(ResultBanner, { status: "success", message: `\u2714 ${t("cleanup_systemClean")}` }),
4298
+ candidates.length > 0 && !confirmClean && (() => {
4299
+ const start = Math.max(0, cursor - Math.floor(listRows / 2));
4300
+ const visible = candidates.slice(start, start + listRows);
4301
+ return /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", children: [
4302
+ start > 0 && /* @__PURE__ */ jsxs27(Text28, { color: COLORS.textSecondary, dimColor: true, children: [
4303
+ " ",
4304
+ t("scroll_moreAbove", { count: start })
4305
+ ] }),
4306
+ visible.map((c, i) => {
4307
+ const idx = start + i;
4308
+ const isCurrent = idx === cursor;
4309
+ const isSelected = selected.has(c.name);
4310
+ return /* @__PURE__ */ jsxs27(SelectableRow, { isCurrent, children: [
4311
+ /* @__PURE__ */ jsx30(Text28, { color: isSelected ? COLORS.success : COLORS.muted, children: isSelected ? "\u2611" : "\u2610" }),
4312
+ /* @__PURE__ */ jsx30(Text28, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: c.name }),
4313
+ /* @__PURE__ */ jsx30(Text28, { color: COLORS.warning, children: c.diskUsageFormatted }),
4314
+ /* @__PURE__ */ jsxs27(Text28, { color: COLORS.textSecondary, children: [
4315
+ "[",
4316
+ c.reason,
4317
+ "]"
4318
+ ] })
4319
+ ] }, c.name);
4320
+ }),
4321
+ start + listRows < candidates.length && /* @__PURE__ */ jsxs27(Text28, { color: COLORS.textSecondary, dimColor: true, children: [
4322
+ " ",
4323
+ t("scroll_moreBelow", { count: candidates.length - start - listRows })
4324
+ ] }),
4325
+ /* @__PURE__ */ jsx30(Box26, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs27(Text28, { color: COLORS.text, bold: true, children: [
4326
+ cursor + 1,
4327
+ "/",
4328
+ candidates.length
4329
+ ] }) })
4330
+ ] });
4331
+ })()
4158
4332
  ] });
4159
4333
  }
4160
4334
 
4161
4335
  // src/views/history.tsx
4162
- import { useEffect as useEffect17, useState as useState13, useMemo as useMemo5 } from "react";
4163
- import { Box as Box27, Text as Text29, useStdout as useStdout6 } from "ink";
4336
+ import { useEffect as useEffect17, useState as useState14, useMemo as useMemo5 } from "react";
4337
+ import { Box as Box27, Text as Text29 } from "ink";
4164
4338
 
4165
4339
  // src/stores/history-store.ts
4166
4340
  import { create as create11 } from "zustand";
@@ -4196,7 +4370,7 @@ var useHistoryStore = create11((set) => ({
4196
4370
  }));
4197
4371
 
4198
4372
  // src/views/history.tsx
4199
- import { jsx as jsx30, jsxs as jsxs28 } from "react/jsx-runtime";
4373
+ import { jsx as jsx31, jsxs as jsxs28 } from "react/jsx-runtime";
4200
4374
  var ACTION_ICONS = {
4201
4375
  install: { icon: "+", color: COLORS.success },
4202
4376
  uninstall: { icon: "-", color: COLORS.error },
@@ -4212,17 +4386,25 @@ var ACTION_LABEL_KEYS = {
4212
4386
  var FILTERS = ["all", "install", "uninstall", "upgrade", "upgrade-all"];
4213
4387
  function HistoryView() {
4214
4388
  const { entries, loading, error, fetchHistory, clearHistory: clearHistory2 } = useHistoryStore();
4215
- const [cursor, setCursor] = useState13(0);
4216
- const [filter, setFilter] = useState13("all");
4217
- const [searchQuery, setSearchQuery] = useState13("");
4218
- const [isSearching, setIsSearching] = useState13(false);
4219
- const [confirmClear, setConfirmClear] = useState13(false);
4220
- const [confirmReplay, setConfirmReplay] = useState13(null);
4389
+ const [cursor, setCursor] = useState14(0);
4390
+ const [filter, setFilter] = useState14("all");
4391
+ const [searchQuery, setSearchQuery] = useState14("");
4392
+ const [isSearching, setIsSearching] = useState14(false);
4393
+ const [confirmClear, setConfirmClear] = useState14(false);
4394
+ const [confirmReplay, setConfirmReplay] = useState14(null);
4221
4395
  const stream = useBrewStream();
4222
4396
  const debouncedQuery = useDebounce(searchQuery, 200);
4223
4397
  const { openModal, closeModal } = useModalStore();
4224
- const { stdout } = useStdout6();
4225
- const MAX_VISIBLE_ROWS = Math.max(5, (stdout?.rows ?? 24) - 8);
4398
+ const MAX_VISIBLE_ROWS = useVisibleRows({
4399
+ reservedRows: isSearching || stream.lines.length > 0 || stream.isRunning ? 8 : 5,
4400
+ fallbackReservedRows: isSearching || stream.lines.length > 0 || stream.isRunning ? 16 : 13,
4401
+ minRows: 1
4402
+ });
4403
+ const streamRows = useVisibleRows({
4404
+ reservedRows: 7,
4405
+ fallbackReservedRows: 16,
4406
+ minRows: 1
4407
+ });
4226
4408
  useEffect17(() => {
4227
4409
  fetchHistory();
4228
4410
  }, []);
@@ -4276,17 +4458,17 @@ function HistoryView() {
4276
4458
  if (input === "j" || key.downArrow) setCursor((c) => Math.min(c + 1, Math.max(0, filtered.length - 1)));
4277
4459
  else if (input === "k" || key.upArrow) setCursor((c) => Math.max(c - 1, 0));
4278
4460
  });
4279
- if (loading) return /* @__PURE__ */ jsx30(Loading, { message: t("loading_history") });
4280
- if (error) return /* @__PURE__ */ jsx30(ErrorMessage, { message: error });
4461
+ if (loading) return /* @__PURE__ */ jsx31(Loading, { message: t("loading_history") });
4462
+ if (error) return /* @__PURE__ */ jsx31(ErrorMessage, { message: error });
4281
4463
  const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
4282
4464
  const visible = filtered.slice(start, start + MAX_VISIBLE_ROWS);
4283
4465
  return /* @__PURE__ */ jsxs28(Box27, { flexDirection: "column", children: [
4284
4466
  /* @__PURE__ */ jsxs28(Box27, { gap: SPACING.sm, marginBottom: SPACING.xs, children: [
4285
- /* @__PURE__ */ jsx30(SectionHeader, { emoji: "\u{1F4DC}", title: t("history_title", { count: filtered.length }), gradient: GRADIENTS.gold }),
4286
- /* @__PURE__ */ jsx30(Text29, { color: filter === "all" ? COLORS.text : COLORS.gold, children: t("history_filterLabel", { filter }) })
4467
+ /* @__PURE__ */ jsx31(SectionHeader, { emoji: "\u{1F4DC}", title: t("history_title", { count: filtered.length }), gradient: GRADIENTS.gold }),
4468
+ /* @__PURE__ */ jsx31(Text29, { color: filter === "all" ? COLORS.text : COLORS.gold, children: t("history_filterLabel", { filter }) })
4287
4469
  ] }),
4288
- isSearching && /* @__PURE__ */ jsx30(Box27, { marginBottom: SPACING.xs, children: /* @__PURE__ */ jsx30(SearchInput, { defaultValue: searchQuery, onChange: setSearchQuery, placeholder: t("history_searchPlaceholder"), isActive: true }) }),
4289
- confirmClear && /* @__PURE__ */ jsx30(
4470
+ isSearching && /* @__PURE__ */ jsx31(Box27, { marginBottom: SPACING.xs, children: /* @__PURE__ */ jsx31(SearchInput, { defaultValue: searchQuery, onChange: setSearchQuery, placeholder: t("history_searchPlaceholder"), isActive: true }) }),
4471
+ confirmClear && /* @__PURE__ */ jsx31(
4290
4472
  ConfirmDialog,
4291
4473
  {
4292
4474
  message: t("history_confirmClear", { count: entries.length }),
@@ -4297,7 +4479,7 @@ function HistoryView() {
4297
4479
  onCancel: () => setConfirmClear(false)
4298
4480
  }
4299
4481
  ),
4300
- confirmReplay && /* @__PURE__ */ jsx30(
4482
+ confirmReplay && /* @__PURE__ */ jsx31(
4301
4483
  ConfirmDialog,
4302
4484
  {
4303
4485
  message: confirmReplay.action === "upgrade-all" ? t("history_replayAll") + "\n" + t("upgrade_all_warning") : t("history_confirmReplay", { action: t(ACTION_LABEL_KEYS[confirmReplay.action]), name: confirmReplay.packageName ?? "" }),
@@ -4325,8 +4507,16 @@ function HistoryView() {
4325
4507
  onCancel: () => setConfirmReplay(null)
4326
4508
  }
4327
4509
  ),
4328
- (stream.isRunning || stream.lines.length > 0) && /* @__PURE__ */ jsx30(Box27, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx30(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t("hint_replay") }) }),
4329
- filtered.length === 0 && !confirmClear && /* @__PURE__ */ jsx30(Text29, { color: COLORS.textSecondary, italic: true, children: filter !== "all" ? t("history_noEntriesFor", { filter }) : t("history_noEntries") }),
4510
+ (stream.isRunning || stream.lines.length > 0) && /* @__PURE__ */ jsx31(Box27, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx31(
4511
+ ProgressLog,
4512
+ {
4513
+ lines: stream.lines,
4514
+ isRunning: stream.isRunning,
4515
+ title: t("hint_replay"),
4516
+ maxVisible: streamRows
4517
+ }
4518
+ ) }),
4519
+ filtered.length === 0 && !confirmClear && /* @__PURE__ */ jsx31(Text29, { color: COLORS.textSecondary, italic: true, children: filter !== "all" ? t("history_noEntriesFor", { filter }) : t("history_noEntries") }),
4330
4520
  filtered.length > 0 && !confirmClear && /* @__PURE__ */ jsxs28(Box27, { flexDirection: "column", children: [
4331
4521
  start > 0 && /* @__PURE__ */ jsxs28(Text29, { color: COLORS.textSecondary, dimColor: true, children: [
4332
4522
  " ",
@@ -4338,18 +4528,18 @@ function HistoryView() {
4338
4528
  const { icon, color } = ACTION_ICONS[entry.action];
4339
4529
  const ts = new Date(entry.timestamp).getTime() / 1e3;
4340
4530
  return /* @__PURE__ */ jsxs28(SelectableRow, { isCurrent, children: [
4341
- /* @__PURE__ */ jsx30(Text29, { color, bold: true, children: icon }),
4342
- /* @__PURE__ */ jsx30(Text29, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: t(ACTION_LABEL_KEYS[entry.action]).padEnd(12) }),
4343
- /* @__PURE__ */ jsx30(Text29, { color: COLORS.text, children: entry.packageName ?? t("history_all") }),
4344
- entry.success ? /* @__PURE__ */ jsx30(StatusBadge, { label: t("badge_ok"), variant: "success" }) : /* @__PURE__ */ jsx30(StatusBadge, { label: t("badge_fail"), variant: "error" }),
4345
- /* @__PURE__ */ jsx30(Text29, { color: COLORS.muted, children: formatRelativeTime(ts) })
4531
+ /* @__PURE__ */ jsx31(Text29, { color, bold: true, children: icon }),
4532
+ /* @__PURE__ */ jsx31(Text29, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: t(ACTION_LABEL_KEYS[entry.action]).padEnd(12) }),
4533
+ /* @__PURE__ */ jsx31(Text29, { color: COLORS.text, children: entry.packageName ?? t("history_all") }),
4534
+ entry.success ? /* @__PURE__ */ jsx31(StatusBadge, { label: t("badge_ok"), variant: "success" }) : /* @__PURE__ */ jsx31(StatusBadge, { label: t("badge_fail"), variant: "error" }),
4535
+ /* @__PURE__ */ jsx31(Text29, { color: COLORS.muted, children: formatRelativeTime(ts) })
4346
4536
  ] }, entry.id);
4347
4537
  }),
4348
4538
  start + MAX_VISIBLE_ROWS < filtered.length && /* @__PURE__ */ jsxs28(Text29, { color: COLORS.textSecondary, dimColor: true, children: [
4349
4539
  " ",
4350
4540
  t("scroll_moreBelow", { count: filtered.length - start - MAX_VISIBLE_ROWS })
4351
4541
  ] }),
4352
- /* @__PURE__ */ jsx30(Box27, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs28(Text29, { color: COLORS.text, bold: true, children: [
4542
+ /* @__PURE__ */ jsx31(Box27, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs28(Text29, { color: COLORS.text, bold: true, children: [
4353
4543
  cursor + 1,
4354
4544
  "/",
4355
4545
  filtered.length
@@ -4359,9 +4549,9 @@ function HistoryView() {
4359
4549
  }
4360
4550
 
4361
4551
  // src/views/security-audit.tsx
4362
- import { useEffect as useEffect18, useState as useState14 } from "react";
4552
+ import { useEffect as useEffect18, useState as useState15 } from "react";
4363
4553
  import { Box as Box28, Text as Text30 } from "ink";
4364
- import { jsx as jsx31, jsxs as jsxs29 } from "react/jsx-runtime";
4554
+ import { jsx as jsx32, jsxs as jsxs29 } from "react/jsx-runtime";
4365
4555
  var SEVERITY_COLORS = {
4366
4556
  CRITICAL: COLORS.error,
4367
4557
  HIGH: COLORS.error,
@@ -4382,9 +4572,9 @@ function isNetworkError(msg) {
4382
4572
  function SecurityAuditView() {
4383
4573
  const { summary, loading, error, scan, cachedAt } = useSecurityStore();
4384
4574
  const navigate = useNavigationStore((s) => s.navigate);
4385
- const [cursor, setCursor] = useState14(0);
4386
- const [expandedPkg, setExpandedPkg] = useState14(null);
4387
- const [confirmUpgrade, setConfirmUpgrade] = useState14(null);
4575
+ const [cursor, setCursor] = useState15(0);
4576
+ const [expandedPkg, setExpandedPkg] = useState15(null);
4577
+ const [confirmUpgrade, setConfirmUpgrade] = useState15(null);
4388
4578
  const stream = useBrewStream();
4389
4579
  useEffect18(() => {
4390
4580
  scan();
@@ -4410,17 +4600,17 @@ function SecurityAuditView() {
4410
4600
  setExpandedPkg(expandedPkg === results[cursor].packageName ? null : results[cursor].packageName);
4411
4601
  }
4412
4602
  });
4413
- if (loading) return /* @__PURE__ */ jsx31(Loading, { message: t("loading_security") });
4603
+ if (loading) return /* @__PURE__ */ jsx32(Loading, { message: t("loading_security") });
4414
4604
  if (error) {
4415
4605
  const displayError = isNetworkError(error) ? t("security_networkError") : error;
4416
- return /* @__PURE__ */ jsx31(ErrorMessage, { message: displayError });
4606
+ return /* @__PURE__ */ jsx32(ErrorMessage, { message: displayError });
4417
4607
  }
4418
4608
  const cacheAge = cachedAt ? formatRelativeTime(cachedAt / 1e3) : null;
4419
4609
  return /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", children: [
4420
- /* @__PURE__ */ jsx31(SectionHeader, { emoji: "\u{1F6E1}\uFE0F", title: t("security_title"), gradient: GRADIENTS.ocean }),
4610
+ /* @__PURE__ */ jsx32(SectionHeader, { emoji: "\u{1F6E1}\uFE0F", title: t("security_title"), gradient: GRADIENTS.ocean }),
4421
4611
  summary && /* @__PURE__ */ jsxs29(Box28, { gap: SPACING.xs, marginY: SPACING.xs, children: [
4422
- /* @__PURE__ */ jsx31(StatCard, { label: t("security_scanned"), value: summary.totalPackages, color: COLORS.info }),
4423
- /* @__PURE__ */ jsx31(
4612
+ /* @__PURE__ */ jsx32(StatCard, { label: t("security_scanned"), value: summary.totalPackages, color: COLORS.info }),
4613
+ /* @__PURE__ */ jsx32(
4424
4614
  StatCard,
4425
4615
  {
4426
4616
  label: t("security_vulnerable"),
@@ -4428,14 +4618,14 @@ function SecurityAuditView() {
4428
4618
  color: summary.vulnerablePackages > 0 ? COLORS.error : COLORS.success
4429
4619
  }
4430
4620
  ),
4431
- summary.criticalCount > 0 && /* @__PURE__ */ jsx31(StatCard, { label: t("security_critical"), value: summary.criticalCount, color: COLORS.error }),
4432
- summary.highCount > 0 && /* @__PURE__ */ jsx31(StatCard, { label: t("security_high"), value: summary.highCount, color: COLORS.error }),
4433
- summary.mediumCount > 0 && /* @__PURE__ */ jsx31(StatCard, { label: t("security_medium"), value: summary.mediumCount, color: COLORS.warning })
4621
+ summary.criticalCount > 0 && /* @__PURE__ */ jsx32(StatCard, { label: t("security_critical"), value: summary.criticalCount, color: COLORS.error }),
4622
+ summary.highCount > 0 && /* @__PURE__ */ jsx32(StatCard, { label: t("security_high"), value: summary.highCount, color: COLORS.error }),
4623
+ summary.mediumCount > 0 && /* @__PURE__ */ jsx32(StatCard, { label: t("security_medium"), value: summary.mediumCount, color: COLORS.warning })
4434
4624
  ] }),
4435
- cacheAge && /* @__PURE__ */ jsx31(Text30, { color: COLORS.muted, children: t("security_cachedResults", { time: cacheAge }) }),
4436
- summary && /* @__PURE__ */ jsx31(Box28, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx31(Text30, { color: COLORS.textSecondary, italic: true, children: t("security_coverage_warning") }) }),
4437
- results.length === 0 && summary && /* @__PURE__ */ jsx31(Box28, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx31(ResultBanner, { status: "success", message: `\u2714 ${t("security_noVulns")}` }) }),
4438
- confirmUpgrade && /* @__PURE__ */ jsx31(Box28, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx31(
4625
+ cacheAge && /* @__PURE__ */ jsx32(Text30, { color: COLORS.muted, children: t("security_cachedResults", { time: cacheAge }) }),
4626
+ summary && /* @__PURE__ */ jsx32(Box28, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx32(Text30, { color: COLORS.textSecondary, italic: true, children: t("security_coverage_warning") }) }),
4627
+ results.length === 0 && summary && /* @__PURE__ */ jsx32(Box28, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx32(ResultBanner, { status: "success", message: `\u2714 ${t("security_noVulns")}` }) }),
4628
+ confirmUpgrade && /* @__PURE__ */ jsx32(Box28, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx32(
4439
4629
  ConfirmDialog,
4440
4630
  {
4441
4631
  message: t("security_confirmUpgrade", { name: confirmUpgrade }),
@@ -4448,50 +4638,50 @@ function SecurityAuditView() {
4448
4638
  onCancel: () => setConfirmUpgrade(null)
4449
4639
  }
4450
4640
  ) }),
4451
- (stream.isRunning || stream.lines.length > 0) && /* @__PURE__ */ jsx31(Box28, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx31(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t("hint_upgrade") }) }),
4641
+ (stream.isRunning || stream.lines.length > 0) && /* @__PURE__ */ jsx32(Box28, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx32(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t("hint_upgrade") }) }),
4452
4642
  results.length > 0 && /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", marginTop: SPACING.xs, children: [
4453
4643
  results.map((pkg, i) => {
4454
4644
  const isCurrent = i === cursor;
4455
4645
  const isExpanded = expandedPkg === pkg.packageName;
4456
4646
  return /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", children: [
4457
4647
  /* @__PURE__ */ jsxs29(SelectableRow, { isCurrent, children: [
4458
- /* @__PURE__ */ jsx31(StatusBadge, { label: pkg.maxSeverity, variant: SEVERITY_BADGE[pkg.maxSeverity] }),
4459
- /* @__PURE__ */ jsx31(Text30, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: pkg.packageName }),
4460
- /* @__PURE__ */ jsx31(Text30, { color: COLORS.muted, children: pkg.installedVersion }),
4461
- /* @__PURE__ */ jsx31(Text30, { color: COLORS.muted, children: tp("plural_vulns", pkg.vulnerabilities.length) }),
4648
+ /* @__PURE__ */ jsx32(StatusBadge, { label: pkg.maxSeverity, variant: SEVERITY_BADGE[pkg.maxSeverity] }),
4649
+ /* @__PURE__ */ jsx32(Text30, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: pkg.packageName }),
4650
+ /* @__PURE__ */ jsx32(Text30, { color: COLORS.muted, children: pkg.installedVersion }),
4651
+ /* @__PURE__ */ jsx32(Text30, { color: COLORS.muted, children: tp("plural_vulns", pkg.vulnerabilities.length) }),
4462
4652
  pkg.vulnerabilities.some((v) => v.fixedVersion) && /* @__PURE__ */ jsxs29(Text30, { color: COLORS.textSecondary, children: [
4463
4653
  "[R:",
4464
4654
  t("hint_rollback"),
4465
4655
  "]"
4466
4656
  ] }),
4467
- /* @__PURE__ */ jsx31(Text30, { color: COLORS.muted, children: isExpanded ? "\u25BC" : "\u25B6" })
4657
+ /* @__PURE__ */ jsx32(Text30, { color: COLORS.muted, children: isExpanded ? "\u25BC" : "\u25B6" })
4468
4658
  ] }),
4469
- isExpanded && /* @__PURE__ */ jsx31(Box28, { flexDirection: "column", paddingLeft: SPACING.lg, marginBottom: SPACING.xs, children: pkg.vulnerabilities.map((vuln) => /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", marginBottom: SPACING.xs, children: [
4659
+ isExpanded && /* @__PURE__ */ jsx32(Box28, { flexDirection: "column", paddingLeft: SPACING.lg, marginBottom: SPACING.xs, children: pkg.vulnerabilities.map((vuln) => /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", marginBottom: SPACING.xs, children: [
4470
4660
  /* @__PURE__ */ jsxs29(Box28, { gap: SPACING.xs, children: [
4471
- /* @__PURE__ */ jsx31(Text30, { color: SEVERITY_COLORS[vuln.severity], bold: true, children: vuln.id }),
4661
+ /* @__PURE__ */ jsx32(Text30, { color: SEVERITY_COLORS[vuln.severity], bold: true, children: vuln.id }),
4472
4662
  /* @__PURE__ */ jsxs29(Text30, { color: COLORS.muted, children: [
4473
4663
  "[",
4474
4664
  vuln.severity,
4475
4665
  "]"
4476
4666
  ] })
4477
4667
  ] }),
4478
- /* @__PURE__ */ jsx31(Text30, { color: COLORS.muted, wrap: "wrap", children: vuln.summary }),
4479
- vuln.fixedVersion && /* @__PURE__ */ jsx31(Text30, { color: COLORS.success, children: t("security_fixedIn", { version: vuln.fixedVersion }) })
4668
+ /* @__PURE__ */ jsx32(Text30, { color: COLORS.muted, wrap: "wrap", children: vuln.summary }),
4669
+ vuln.fixedVersion && /* @__PURE__ */ jsx32(Text30, { color: COLORS.success, children: t("security_fixedIn", { version: vuln.fixedVersion }) })
4480
4670
  ] }, vuln.id)) })
4481
4671
  ] }, pkg.packageName);
4482
4672
  }),
4483
- /* @__PURE__ */ jsx31(Box28, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs29(Text30, { color: COLORS.text, bold: true, children: [
4673
+ /* @__PURE__ */ jsx32(Box28, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs29(Text30, { color: COLORS.text, bold: true, children: [
4484
4674
  cursor + 1,
4485
4675
  "/",
4486
4676
  results.length
4487
4677
  ] }) }),
4488
- /* @__PURE__ */ jsx31(Box28, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx31(Text30, { color: COLORS.textSecondary, children: t("security_rollback_hint") }) })
4678
+ /* @__PURE__ */ jsx32(Box28, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx32(Text30, { color: COLORS.textSecondary, children: t("security_rollback_hint") }) })
4489
4679
  ] })
4490
4680
  ] });
4491
4681
  }
4492
4682
 
4493
4683
  // src/views/account.tsx
4494
- import { useState as useState15 } from "react";
4684
+ import { useState as useState16 } from "react";
4495
4685
  import { Box as Box29, Text as Text31 } from "ink";
4496
4686
  import { TextInput as TextInput5 } from "@inkjs/ui";
4497
4687
 
@@ -4570,16 +4760,16 @@ async function redeemPromoCode(code) {
4570
4760
  }
4571
4761
 
4572
4762
  // src/views/account.tsx
4573
- import { Fragment as Fragment5, jsx as jsx32, jsxs as jsxs30 } from "react/jsx-runtime";
4763
+ import { Fragment as Fragment5, jsx as jsx33, jsxs as jsxs30 } from "react/jsx-runtime";
4574
4764
  function AccountView() {
4575
4765
  const { status, license, deactivate: deactivate2, revalidate: revalidate2, degradation } = useLicenseStore();
4576
- const [confirmDeactivate, setConfirmDeactivate] = useState15(false);
4577
- const [deactivating, setDeactivating] = useState15(false);
4578
- const [deactivateError, setDeactivateError] = useState15(null);
4579
- const [promoMode, setPromoMode] = useState15(false);
4580
- const [promoLoading, setPromoLoading] = useState15(false);
4581
- const [promoResult, setPromoResult] = useState15(null);
4582
- const [revalidating, setRevalidating] = useState15(false);
4766
+ const [confirmDeactivate, setConfirmDeactivate] = useState16(false);
4767
+ const [deactivating, setDeactivating] = useState16(false);
4768
+ const [deactivateError, setDeactivateError] = useState16(null);
4769
+ const [promoMode, setPromoMode] = useState16(false);
4770
+ const [promoLoading, setPromoLoading] = useState16(false);
4771
+ const [promoResult, setPromoResult] = useState16(null);
4772
+ const [revalidating, setRevalidating] = useState16(false);
4583
4773
  useViewInput((input, key) => {
4584
4774
  if (confirmDeactivate || deactivating || promoMode || revalidating) {
4585
4775
  if (key.escape && promoMode) {
@@ -4605,11 +4795,11 @@ function AccountView() {
4605
4795
  return key.slice(0, 4) + "-****-****-" + key.slice(-4);
4606
4796
  };
4607
4797
  if (status === "validating") {
4608
- return /* @__PURE__ */ jsx32(Loading, { message: t("account_loading") });
4798
+ return /* @__PURE__ */ jsx33(Loading, { message: t("account_loading") });
4609
4799
  }
4610
4800
  return /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", children: [
4611
- /* @__PURE__ */ jsx32(SectionHeader, { emoji: "\u{1F464}", title: t("account_title"), gradient: GRADIENTS.gold }),
4612
- confirmDeactivate && /* @__PURE__ */ jsx32(Box29, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx32(
4801
+ /* @__PURE__ */ jsx33(SectionHeader, { emoji: "\u{1F464}", title: t("account_title"), gradient: GRADIENTS.gold }),
4802
+ confirmDeactivate && /* @__PURE__ */ jsx33(Box29, { marginY: SPACING.xs, children: /* @__PURE__ */ jsx33(
4613
4803
  ConfirmDialog,
4614
4804
  {
4615
4805
  message: t("account_confirmDeactivate"),
@@ -4630,70 +4820,68 @@ function AccountView() {
4630
4820
  ) }),
4631
4821
  /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", marginTop: SPACING.xs, paddingLeft: SPACING.sm, children: [
4632
4822
  /* @__PURE__ */ jsxs30(Box29, { gap: SPACING.xs, children: [
4633
- /* @__PURE__ */ jsx32(Text31, { color: COLORS.muted, children: t("account_statusLabel") }),
4634
- status === "pro" && /* @__PURE__ */ jsx32(Text31, { color: COLORS.success, bold: true, children: t("account_pro") }),
4635
- status === "free" && /* @__PURE__ */ jsx32(Text31, { color: COLORS.muted, children: t("account_free") }),
4636
- status === "expired" && /* @__PURE__ */ jsx32(Text31, { color: COLORS.error, children: t("account_expired") })
4823
+ /* @__PURE__ */ jsx33(Text31, { color: COLORS.muted, children: t("account_statusLabel") }),
4824
+ status === "pro" && /* @__PURE__ */ jsx33(Text31, { color: COLORS.success, bold: true, children: t("account_pro") }),
4825
+ status === "free" && /* @__PURE__ */ jsx33(Text31, { color: COLORS.muted, children: t("account_free") }),
4826
+ status === "expired" && /* @__PURE__ */ jsx33(Text31, { color: COLORS.error, children: t("account_expired") })
4637
4827
  ] }),
4638
- (degradation === "warning" || degradation === "limited") && license && /* @__PURE__ */ jsx32(Box29, { marginTop: SPACING.xs, borderStyle: "round", borderColor: COLORS.warning, paddingX: SPACING.sm, paddingY: SPACING.none, children: /* @__PURE__ */ jsx32(Text31, { color: COLORS.warning, children: t("license_offlineWarning", {
4828
+ (degradation === "warning" || degradation === "limited") && license && /* @__PURE__ */ jsx33(Box29, { marginTop: SPACING.xs, borderStyle: "round", borderColor: COLORS.warning, paddingX: SPACING.sm, paddingY: SPACING.none, children: /* @__PURE__ */ jsx33(Text31, { color: COLORS.warning, children: t("license_offlineWarning", {
4639
4829
  days: Math.floor((Date.now() - new Date(license.lastValidatedAt).getTime()) / (24 * 60 * 60 * 1e3))
4640
4830
  }) }) }),
4641
4831
  license && /* @__PURE__ */ jsxs30(Fragment5, { children: [
4642
4832
  /* @__PURE__ */ jsxs30(Box29, { gap: SPACING.xs, children: [
4643
- /* @__PURE__ */ jsx32(Text31, { color: COLORS.muted, children: t("account_emailLabel") }),
4644
- /* @__PURE__ */ jsx32(Text31, { children: license.customerEmail })
4833
+ /* @__PURE__ */ jsx33(Text31, { color: COLORS.muted, children: t("account_emailLabel") }),
4834
+ /* @__PURE__ */ jsx33(Text31, { children: license.customerEmail })
4645
4835
  ] }),
4646
4836
  /* @__PURE__ */ jsxs30(Box29, { gap: SPACING.xs, children: [
4647
- /* @__PURE__ */ jsx32(Text31, { color: COLORS.muted, children: t("account_nameLabel") }),
4648
- /* @__PURE__ */ jsx32(Text31, { children: license.customerName })
4837
+ /* @__PURE__ */ jsx33(Text31, { color: COLORS.muted, children: t("account_nameLabel") }),
4838
+ /* @__PURE__ */ jsx33(Text31, { children: license.customerName })
4649
4839
  ] }),
4650
4840
  /* @__PURE__ */ jsxs30(Box29, { gap: SPACING.xs, children: [
4651
- /* @__PURE__ */ jsx32(Text31, { color: COLORS.muted, children: t("account_planLabel") }),
4652
- /* @__PURE__ */ jsx32(Text31, { color: COLORS.success, bold: true, children: "Pro" })
4841
+ /* @__PURE__ */ jsx33(Text31, { color: COLORS.muted, children: t("account_planLabel") }),
4842
+ /* @__PURE__ */ jsx33(Text31, { color: COLORS.success, bold: true, children: "Pro" })
4653
4843
  ] }),
4654
4844
  /* @__PURE__ */ jsxs30(Box29, { gap: SPACING.xs, children: [
4655
- /* @__PURE__ */ jsx32(Text31, { color: COLORS.muted, children: t("account_keyLabel") }),
4656
- /* @__PURE__ */ jsx32(Text31, { children: maskKey(license.key) })
4845
+ /* @__PURE__ */ jsx33(Text31, { color: COLORS.muted, children: t("account_keyLabel") }),
4846
+ /* @__PURE__ */ jsx33(Text31, { children: maskKey(license.key) })
4657
4847
  ] }),
4658
4848
  license.expiresAt && /* @__PURE__ */ jsxs30(Box29, { gap: SPACING.xs, children: [
4659
- /* @__PURE__ */ jsx32(Text31, { color: COLORS.muted, children: t("account_expiresLabel") }),
4660
- /* @__PURE__ */ jsx32(Text31, { children: formatDate(license.expiresAt) })
4849
+ /* @__PURE__ */ jsx33(Text31, { color: COLORS.muted, children: t("account_expiresLabel") }),
4850
+ /* @__PURE__ */ jsx33(Text31, { children: formatDate(license.expiresAt) })
4661
4851
  ] }),
4662
4852
  /* @__PURE__ */ jsxs30(Box29, { gap: SPACING.xs, children: [
4663
- /* @__PURE__ */ jsx32(Text31, { color: COLORS.muted, children: t("account_activatedLabel") }),
4664
- /* @__PURE__ */ jsx32(Text31, { children: formatDate(license.activatedAt) })
4853
+ /* @__PURE__ */ jsx33(Text31, { color: COLORS.muted, children: t("account_activatedLabel") }),
4854
+ /* @__PURE__ */ jsx33(Text31, { children: formatDate(license.activatedAt) })
4665
4855
  ] })
4666
4856
  ] }),
4667
- status === "free" && /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", marginTop: SPACING.sm, borderStyle: "round", borderColor: COLORS.brand, paddingX: SPACING.sm, paddingY: SPACING.xs, children: [
4857
+ status === "free" && /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", marginTop: SPACING.xs, borderStyle: "round", borderColor: COLORS.brand, paddingX: SPACING.sm, paddingY: SPACING.none, flexShrink: 1, children: [
4668
4858
  /* @__PURE__ */ jsxs30(Text31, { bold: true, color: COLORS.brand, children: [
4669
4859
  "\u2B50",
4670
4860
  " ",
4671
4861
  t("account_upgradeTitle")
4672
4862
  ] }),
4673
- /* @__PURE__ */ jsx32(Text31, { children: " " }),
4674
- /* @__PURE__ */ jsx32(Text31, { children: t("account_unlockDesc") }),
4675
- /* @__PURE__ */ jsx32(Text31, { color: COLORS.info, bold: true, children: t("account_pricing") }),
4676
- /* @__PURE__ */ jsx32(Text31, { children: " " }),
4863
+ /* @__PURE__ */ jsx33(Text31, { wrap: "wrap", children: t("account_unlockDesc") }),
4864
+ /* @__PURE__ */ jsx33(Text31, { color: COLORS.info, bold: true, children: t("account_pricing") }),
4677
4865
  /* @__PURE__ */ jsxs30(Text31, { color: COLORS.muted, children: [
4678
4866
  t("upgrade_buyAt"),
4679
4867
  " ",
4680
- /* @__PURE__ */ jsx32(Text31, { color: COLORS.sky, bold: true, children: t("upgrade_buyUrl") })
4868
+ /* @__PURE__ */ jsx33(Text31, { color: COLORS.sky, bold: true, children: t("upgrade_buyUrl") })
4681
4869
  ] }),
4682
4870
  /* @__PURE__ */ jsxs30(Text31, { color: COLORS.muted, children: [
4683
4871
  t("account_runActivate"),
4684
4872
  " ",
4685
- /* @__PURE__ */ jsx32(Text31, { color: COLORS.success, bold: true, children: t("account_activateCmd") })
4873
+ /* @__PURE__ */ jsx33(Text31, { color: COLORS.success, bold: true, children: t("account_activateCmd") })
4686
4874
  ] })
4687
4875
  ] }),
4688
- status === "expired" && /* @__PURE__ */ jsx32(Box29, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx32(Box29, { borderStyle: "round", borderColor: COLORS.error, paddingX: SPACING.sm, paddingY: SPACING.none, children: /* @__PURE__ */ jsx32(Text31, { color: COLORS.error, children: t("account_licenseExpired") }) }) }),
4689
- deactivating && /* @__PURE__ */ jsx32(Text31, { color: COLORS.sky, children: t("account_deactivating") }),
4690
- deactivateError && /* @__PURE__ */ jsx32(Text31, { color: COLORS.error, children: deactivateError })
4876
+ status === "expired" && /* @__PURE__ */ jsx33(Box29, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx33(Box29, { borderStyle: "round", borderColor: COLORS.error, paddingX: SPACING.sm, paddingY: SPACING.none, children: /* @__PURE__ */ jsx33(Text31, { color: COLORS.error, children: t("account_licenseExpired") }) }) }),
4877
+ deactivating && /* @__PURE__ */ jsx33(Text31, { color: COLORS.sky, children: t("account_deactivating") }),
4878
+ deactivateError && /* @__PURE__ */ jsx33(Text31, { color: COLORS.error, children: deactivateError })
4691
4879
  ] }),
4692
- /* @__PURE__ */ jsx32(Box29, { flexDirection: "column", marginTop: SPACING.xs, paddingLeft: SPACING.sm, children: promoMode ? /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", gap: SPACING.xs, children: [
4693
- /* @__PURE__ */ jsx32(Text31, { bold: true, color: COLORS.gold, children: t("account_promoTitle") }),
4694
- promoLoading ? /* @__PURE__ */ jsx32(Text31, { color: COLORS.sky, children: t("account_promoValidating") }) : /* @__PURE__ */ jsxs30(Box29, { gap: SPACING.xs, children: [
4695
- /* @__PURE__ */ jsx32(Text31, { color: COLORS.muted, children: t("account_promoLabel") }),
4696
- /* @__PURE__ */ jsx32(
4880
+ /* @__PURE__ */ jsx33(Box29, { flexDirection: "column", marginTop: SPACING.xs, paddingLeft: SPACING.sm, children: promoMode ? /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", gap: SPACING.xs, children: [
4881
+ /* @__PURE__ */ jsx33(Text31, { bold: true, color: COLORS.gold, children: t("account_promoTitle") }),
4882
+ promoLoading ? /* @__PURE__ */ jsx33(Text31, { color: COLORS.sky, children: t("account_promoValidating") }) : /* @__PURE__ */ jsxs30(Box29, { gap: SPACING.xs, children: [
4883
+ /* @__PURE__ */ jsx33(Text31, { color: COLORS.muted, children: t("account_promoLabel") }),
4884
+ /* @__PURE__ */ jsx33(
4697
4885
  TextInput5,
4698
4886
  {
4699
4887
  defaultValue: "",
@@ -4717,21 +4905,21 @@ function AccountView() {
4717
4905
  }
4718
4906
  )
4719
4907
  ] }),
4720
- promoResult && /* @__PURE__ */ jsx32(ResultBanner, { status: promoResult.success ? "success" : "error", message: promoResult.message }),
4721
- /* @__PURE__ */ jsx32(Text31, { color: COLORS.textSecondary, dimColor: true, children: t("account_promoEsc") })
4722
- ] }) : /* @__PURE__ */ jsx32(Text31, { color: COLORS.textSecondary, children: t("account_promoHint") }) }),
4723
- /* @__PURE__ */ jsx32(Box29, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs30(Text31, { color: COLORS.textSecondary, children: [
4908
+ promoResult && /* @__PURE__ */ jsx33(ResultBanner, { status: promoResult.success ? "success" : "error", message: promoResult.message }),
4909
+ /* @__PURE__ */ jsx33(Text31, { color: COLORS.textSecondary, dimColor: true, children: t("account_promoEsc") })
4910
+ ] }) : /* @__PURE__ */ jsx33(Text31, { color: COLORS.textSecondary, children: t("account_promoHint") }) }),
4911
+ /* @__PURE__ */ jsx33(Box29, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs30(Text31, { color: COLORS.textSecondary, children: [
4724
4912
  status === "pro" || status === "team" ? `d ${t("hint_deactivate")} ` : "",
4725
4913
  status === "pro" || status === "team" || status === "expired" ? `v ${t("hint_revalidate")} ` : "",
4726
4914
  revalidating ? t("account_revalidating") : "",
4727
4915
  " ",
4728
- t("app_version", { version: "0.9.2" })
4916
+ t("app_version", { version: "1.1.0" })
4729
4917
  ] }) })
4730
4918
  ] });
4731
4919
  }
4732
4920
 
4733
4921
  // src/views/rollback.tsx
4734
- import { useCallback as useCallback3, useEffect as useEffect19, useRef as useRef9, useState as useState16 } from "react";
4922
+ import { useCallback as useCallback3, useEffect as useEffect19, useRef as useRef11, useState as useState17 } from "react";
4735
4923
  import { Box as Box30, Text as Text32 } from "ink";
4736
4924
 
4737
4925
  // src/stores/rollback-store.ts
@@ -4942,7 +5130,7 @@ var useRollbackStore = create12((set) => ({
4942
5130
  }));
4943
5131
 
4944
5132
  // src/views/rollback.tsx
4945
- import { jsx as jsx33, jsxs as jsxs31 } from "react/jsx-runtime";
5133
+ import { jsx as jsx34, jsxs as jsxs31 } from "react/jsx-runtime";
4946
5134
  function strategyLabel(action) {
4947
5135
  switch (action.strategy) {
4948
5136
  case "versioned-formula":
@@ -4970,21 +5158,29 @@ function actionPrefix(action) {
4970
5158
  }
4971
5159
  function PlanView({ plan }) {
4972
5160
  const executableCount = plan.actions.filter((a) => a.strategy !== "unavailable" && a.action !== "remove").length;
5161
+ const reservedForWarnings = Math.min(plan.warnings.length, 3) * 2;
5162
+ const actionsBudget = useVisibleRows({
5163
+ reservedRows: 6 + reservedForWarnings,
5164
+ fallbackReservedRows: 14 + reservedForWarnings,
5165
+ minRows: 2
5166
+ });
5167
+ const visibleActions = plan.actions.slice(0, actionsBudget);
5168
+ const hiddenActions = plan.actions.length - visibleActions.length;
4973
5169
  return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: SPACING.xs, children: [
4974
5170
  /* @__PURE__ */ jsxs31(Box30, { marginBottom: SPACING.xs, children: [
4975
5171
  /* @__PURE__ */ jsxs31(Text32, { color: COLORS.text, bold: true, children: [
4976
5172
  plan.snapshotLabel,
4977
5173
  " "
4978
5174
  ] }),
4979
- /* @__PURE__ */ jsx33(Text32, { color: COLORS.textSecondary, children: plan.snapshotDate })
5175
+ /* @__PURE__ */ jsx34(Text32, { color: COLORS.textSecondary, children: plan.snapshotDate })
4980
5176
  ] }),
4981
- plan.actions.length === 0 && /* @__PURE__ */ jsx33(ResultBanner, { status: "success", message: t("rollback_diff_empty") }),
4982
- plan.actions.map((a) => /* @__PURE__ */ jsxs31(Box30, { children: [
5177
+ plan.actions.length === 0 && /* @__PURE__ */ jsx34(ResultBanner, { status: "success", message: t("rollback_diff_empty") }),
5178
+ visibleActions.map((a) => /* @__PURE__ */ jsxs31(Box30, { children: [
4983
5179
  /* @__PURE__ */ jsxs31(Text32, { color: actionColor(a), children: [
4984
5180
  actionPrefix(a),
4985
5181
  " "
4986
5182
  ] }),
4987
- /* @__PURE__ */ jsx33(Text32, { color: actionColor(a), bold: true, children: a.packageName }),
5183
+ /* @__PURE__ */ jsx34(Text32, { color: actionColor(a), bold: true, children: a.packageName }),
4988
5184
  a.fromVersion !== "" && a.toVersion !== "" && /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, children: [
4989
5185
  " ",
4990
5186
  a.fromVersion,
@@ -4995,18 +5191,26 @@ function PlanView({ plan }) {
4995
5191
  " install ",
4996
5192
  a.toVersion
4997
5193
  ] }),
4998
- a.fromVersion !== "" && a.toVersion === "" && /* @__PURE__ */ jsx33(Text32, { color: COLORS.textSecondary, children: " remove" }),
5194
+ a.fromVersion !== "" && a.toVersion === "" && /* @__PURE__ */ jsx34(Text32, { color: COLORS.textSecondary, children: " remove" }),
4999
5195
  /* @__PURE__ */ jsxs31(Text32, { color: COLORS.muted, dimColor: true, children: [
5000
5196
  " [",
5001
5197
  strategyLabel(a),
5002
5198
  "]"
5003
5199
  ] })
5004
5200
  ] }, a.packageName + a.action)),
5005
- plan.warnings.map((w) => /* @__PURE__ */ jsx33(Box30, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs31(Text32, { color: COLORS.warning, children: [
5201
+ hiddenActions > 0 && /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, dimColor: true, children: [
5202
+ " ",
5203
+ t("scroll_moreBelow", { count: hiddenActions })
5204
+ ] }),
5205
+ plan.warnings.slice(0, 3).map((w) => /* @__PURE__ */ jsx34(Box30, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs31(Text32, { color: COLORS.warning, wrap: "wrap", children: [
5006
5206
  "\u26A0 ",
5007
5207
  w
5008
5208
  ] }) }, w)),
5009
- /* @__PURE__ */ jsx33(Box30, { marginTop: SPACING.xs, children: plan.canExecute ? /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, children: [
5209
+ plan.warnings.length > 3 && /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, dimColor: true, children: [
5210
+ " ",
5211
+ t("common_andMore", { count: plan.warnings.length - 3 })
5212
+ ] }),
5213
+ /* @__PURE__ */ jsx34(Box30, { marginTop: SPACING.xs, children: plan.canExecute ? /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, children: [
5010
5214
  "enter:",
5011
5215
  t("rollback_confirm", { count: String(executableCount) }),
5012
5216
  " esc:",
@@ -5021,13 +5225,18 @@ function PlanView({ plan }) {
5021
5225
  function RollbackView() {
5022
5226
  const isPro = useLicenseStore((s) => s.isPro);
5023
5227
  const { snapshots, loading, error, plan, planLoading, planError, fetchSnapshots, selectSnapshot, clearPlan } = useRollbackStore();
5024
- const [cursor, setCursor] = useState16(0);
5025
- const [phase, setPhase] = useState16("list");
5026
- const [streamLines, setStreamLines] = useState16([]);
5027
- const [streamRunning, setStreamRunning] = useState16(false);
5028
- const [streamError, setStreamError] = useState16(null);
5029
- const generatorRef = useRef9(null);
5030
- const mountedRef = useRef9(true);
5228
+ const [cursor, setCursor] = useState17(0);
5229
+ const [phase, setPhase] = useState17("list");
5230
+ const [streamLines, setStreamLines] = useState17([]);
5231
+ const [streamRunning, setStreamRunning] = useState17(false);
5232
+ const [streamError, setStreamError] = useState17(null);
5233
+ const generatorRef = useRef11(null);
5234
+ const mountedRef = useRef11(true);
5235
+ const snapshotRows = useVisibleRows({
5236
+ reservedRows: 6,
5237
+ fallbackReservedRows: 14,
5238
+ minRows: 1
5239
+ });
5031
5240
  useEffect19(() => {
5032
5241
  mountedRef.current = true;
5033
5242
  return () => {
@@ -5095,21 +5304,21 @@ function RollbackView() {
5095
5304
  void fetchSnapshots(isPro());
5096
5305
  }
5097
5306
  });
5098
- if (loading) return /* @__PURE__ */ jsx33(Loading, { message: t("rollback_select_snapshot") });
5099
- if (error) return /* @__PURE__ */ jsx33(ErrorMessage, { message: error });
5307
+ if (loading) return /* @__PURE__ */ jsx34(Loading, { message: t("rollback_select_snapshot") });
5308
+ if (error) return /* @__PURE__ */ jsx34(ErrorMessage, { message: error });
5100
5309
  if (phase === "executing") {
5101
- return /* @__PURE__ */ jsx33(Box30, { flexDirection: "column", children: /* @__PURE__ */ jsx33(ProgressLog, { lines: streamLines, isRunning: streamRunning, title: t("rollback_executing") }) });
5310
+ return /* @__PURE__ */ jsx34(Box30, { flexDirection: "column", children: /* @__PURE__ */ jsx34(ProgressLog, { lines: streamLines, isRunning: streamRunning, title: t("rollback_executing") }) });
5102
5311
  }
5103
5312
  if (phase === "result") {
5104
5313
  return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: SPACING.xs, children: [
5105
- /* @__PURE__ */ jsx33(
5314
+ /* @__PURE__ */ jsx34(
5106
5315
  ResultBanner,
5107
5316
  {
5108
5317
  status: streamError ? "error" : "success",
5109
5318
  message: streamError ? t("rollback_error", { error: streamError }) : t("rollback_success")
5110
5319
  }
5111
5320
  ),
5112
- /* @__PURE__ */ jsx33(Box30, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, children: [
5321
+ /* @__PURE__ */ jsx34(Box30, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, children: [
5113
5322
  "r:",
5114
5323
  t("hint_refresh"),
5115
5324
  " esc:",
@@ -5118,30 +5327,47 @@ function RollbackView() {
5118
5327
  ] });
5119
5328
  }
5120
5329
  return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
5121
- /* @__PURE__ */ jsx33(SectionHeader, { emoji: "\u23EA", title: t("rollback_title"), gradient: GRADIENTS.gold }),
5122
- snapshots.length === 0 && /* @__PURE__ */ jsx33(Box30, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx33(ResultBanner, { status: "info", message: t("rollback_no_snapshots") }) }),
5123
- phase === "list" && snapshots.length > 0 && /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: SPACING.xs, children: [
5124
- /* @__PURE__ */ jsx33(Text32, { color: COLORS.textSecondary, dimColor: true, children: t("rollback_select_snapshot") }),
5125
- /* @__PURE__ */ jsx33(Box30, { flexDirection: "column", marginTop: SPACING.xs, children: snapshots.map((s, i) => /* @__PURE__ */ jsxs31(SelectableRow, { isCurrent: i === cursor, children: [
5126
- /* @__PURE__ */ jsx33(Text32, { bold: i === cursor, color: i === cursor ? COLORS.text : COLORS.muted, children: s.label ?? t("rollback_snapshot_auto") }),
5127
- /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, children: [
5128
- " \u2014 ",
5129
- new Date(s.capturedAt).toLocaleString()
5130
- ] }),
5131
- /* @__PURE__ */ jsxs31(Text32, { color: COLORS.muted, dimColor: true, children: [
5132
- " ",
5133
- "(",
5134
- tp("packages", s.formulae.length + s.casks.length),
5135
- ")"
5330
+ /* @__PURE__ */ jsx34(SectionHeader, { emoji: "\u23EA", title: t("rollback_title"), gradient: GRADIENTS.gold }),
5331
+ snapshots.length === 0 && /* @__PURE__ */ jsx34(Box30, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx34(ResultBanner, { status: "info", message: t("rollback_no_snapshots") }) }),
5332
+ phase === "list" && snapshots.length > 0 && (() => {
5333
+ const start = Math.max(0, cursor - Math.floor(snapshotRows / 2));
5334
+ const visible = snapshots.slice(start, start + snapshotRows);
5335
+ return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: SPACING.xs, children: [
5336
+ /* @__PURE__ */ jsx34(Text32, { color: COLORS.textSecondary, dimColor: true, children: t("rollback_select_snapshot") }),
5337
+ /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: SPACING.xs, children: [
5338
+ start > 0 && /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, dimColor: true, children: [
5339
+ " ",
5340
+ t("scroll_moreAbove", { count: start })
5341
+ ] }),
5342
+ visible.map((s, vi) => {
5343
+ const i = start + vi;
5344
+ return /* @__PURE__ */ jsxs31(SelectableRow, { isCurrent: i === cursor, children: [
5345
+ /* @__PURE__ */ jsx34(Text32, { bold: i === cursor, color: i === cursor ? COLORS.text : COLORS.muted, children: s.label ?? t("rollback_snapshot_auto") }),
5346
+ /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, children: [
5347
+ " \u2014 ",
5348
+ new Date(s.capturedAt).toLocaleString()
5349
+ ] }),
5350
+ /* @__PURE__ */ jsxs31(Text32, { color: COLORS.muted, dimColor: true, children: [
5351
+ " ",
5352
+ "(",
5353
+ tp("packages", s.formulae.length + s.casks.length),
5354
+ ")"
5355
+ ] })
5356
+ ] }, s.capturedAt);
5357
+ }),
5358
+ start + snapshotRows < snapshots.length && /* @__PURE__ */ jsxs31(Text32, { color: COLORS.textSecondary, dimColor: true, children: [
5359
+ " ",
5360
+ t("scroll_moreBelow", { count: snapshots.length - start - snapshotRows })
5361
+ ] })
5136
5362
  ] })
5137
- ] }, s.capturedAt)) })
5138
- ] }),
5363
+ ] });
5364
+ })(),
5139
5365
  phase === "plan" && /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
5140
- planLoading && /* @__PURE__ */ jsx33(Loading, { message: t("rollback_capturing") }),
5141
- planError && /* @__PURE__ */ jsx33(ErrorMessage, { message: planError }),
5142
- plan && !planLoading && /* @__PURE__ */ jsx33(PlanView, { plan })
5366
+ planLoading && /* @__PURE__ */ jsx34(Loading, { message: t("rollback_capturing") }),
5367
+ planError && /* @__PURE__ */ jsx34(ErrorMessage, { message: planError }),
5368
+ plan && !planLoading && /* @__PURE__ */ jsx34(PlanView, { plan })
5143
5369
  ] }),
5144
- phase === "confirm" && plan && /* @__PURE__ */ jsx33(Box30, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx33(
5370
+ phase === "confirm" && plan && /* @__PURE__ */ jsx34(Box30, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx34(
5145
5371
  ConfirmDialog,
5146
5372
  {
5147
5373
  message: t("rollback_confirm", {
@@ -5155,10 +5381,10 @@ function RollbackView() {
5155
5381
  }
5156
5382
 
5157
5383
  // src/views/brewfile.tsx
5158
- import { useCallback as useCallback4, useEffect as useEffect20, useRef as useRef10, useState as useState17 } from "react";
5384
+ import { useCallback as useCallback4, useEffect as useEffect20, useRef as useRef12, useState as useState18 } from "react";
5159
5385
  import { Box as Box31, Text as Text33 } from "ink";
5160
5386
  import { TextInput as TextInput6 } from "@inkjs/ui";
5161
- import { jsx as jsx34, jsxs as jsxs32 } from "react/jsx-runtime";
5387
+ import { jsx as jsx35, jsxs as jsxs32 } from "react/jsx-runtime";
5162
5388
  function DriftScore({ score }) {
5163
5389
  const color = score >= 80 ? COLORS.success : score >= 50 ? COLORS.warning : COLORS.error;
5164
5390
  const bars = Math.round(score / 10);
@@ -5174,40 +5400,40 @@ function DriftScore({ score }) {
5174
5400
  score,
5175
5401
  "% "
5176
5402
  ] }),
5177
- /* @__PURE__ */ jsx34(Text33, { color: COLORS.textSecondary, children: t("brewfile_compliant") })
5403
+ /* @__PURE__ */ jsx35(Text33, { color: COLORS.textSecondary, children: t("brewfile_compliant") })
5178
5404
  ] });
5179
5405
  }
5180
5406
  function DriftSummary({ drift }) {
5181
5407
  return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", marginTop: SPACING.xs, children: [
5182
5408
  drift.missingPackages.length > 0 && /* @__PURE__ */ jsxs32(Box31, { children: [
5183
- /* @__PURE__ */ jsx34(Text33, { color: COLORS.error, children: "\u25CF " }),
5184
- /* @__PURE__ */ jsx34(Text33, { color: COLORS.error, children: t("brewfile_drift_missing", { count: drift.missingPackages.length }) }),
5409
+ /* @__PURE__ */ jsx35(Text33, { color: COLORS.error, children: "\u25CF " }),
5410
+ /* @__PURE__ */ jsx35(Text33, { color: COLORS.error, children: t("brewfile_drift_missing", { count: drift.missingPackages.length }) }),
5185
5411
  /* @__PURE__ */ jsxs32(Text33, { color: COLORS.textSecondary, children: [
5186
5412
  ": " + drift.missingPackages.slice(0, 3).join(", "),
5187
5413
  drift.missingPackages.length > 3 ? "..." : ""
5188
5414
  ] })
5189
5415
  ] }),
5190
5416
  drift.extraPackages.length > 0 && /* @__PURE__ */ jsxs32(Box31, { children: [
5191
- /* @__PURE__ */ jsx34(Text33, { color: COLORS.warning, children: "\u25CF " }),
5192
- /* @__PURE__ */ jsx34(Text33, { color: COLORS.warning, children: t("brewfile_drift_extra", { count: drift.extraPackages.length }) })
5417
+ /* @__PURE__ */ jsx35(Text33, { color: COLORS.warning, children: "\u25CF " }),
5418
+ /* @__PURE__ */ jsx35(Text33, { color: COLORS.warning, children: t("brewfile_drift_extra", { count: drift.extraPackages.length }) })
5193
5419
  ] }),
5194
5420
  drift.wrongVersions.length > 0 && /* @__PURE__ */ jsxs32(Box31, { children: [
5195
- /* @__PURE__ */ jsx34(Text33, { color: COLORS.info, children: "\u25CF " }),
5196
- /* @__PURE__ */ jsx34(Text33, { color: COLORS.info, children: t("brewfile_drift_wrong", { count: drift.wrongVersions.length }) })
5421
+ /* @__PURE__ */ jsx35(Text33, { color: COLORS.info, children: "\u25CF " }),
5422
+ /* @__PURE__ */ jsx35(Text33, { color: COLORS.info, children: t("brewfile_drift_wrong", { count: drift.wrongVersions.length }) })
5197
5423
  ] }),
5198
- drift.missingPackages.length === 0 && drift.extraPackages.length === 0 && drift.wrongVersions.length === 0 && /* @__PURE__ */ jsx34(ResultBanner, { status: "success", message: t("brewfile_in_sync") })
5424
+ drift.missingPackages.length === 0 && drift.extraPackages.length === 0 && drift.wrongVersions.length === 0 && /* @__PURE__ */ jsx35(ResultBanner, { status: "success", message: t("brewfile_in_sync") })
5199
5425
  ] });
5200
5426
  }
5201
5427
  function BrewfileView() {
5202
5428
  const isPro = useLicenseStore((s) => s.isPro);
5203
5429
  const { schema, drift, loading, driftLoading, error, load, createFromCurrent } = useBrewfileStore();
5204
- const [phase, setPhase] = useState17("overview");
5205
- const [streamLines, setStreamLines] = useState17([]);
5206
- const [streamRunning, setStreamRunning] = useState17(false);
5207
- const [streamError, setStreamError] = useState17(null);
5208
- const [resultMessage, setResultMessage] = useState17("");
5209
- const generatorRef = useRef10(null);
5210
- const mountedRef = useRef10(true);
5430
+ const [phase, setPhase] = useState18("overview");
5431
+ const [streamLines, setStreamLines] = useState18([]);
5432
+ const [streamRunning, setStreamRunning] = useState18(false);
5433
+ const [streamError, setStreamError] = useState18(null);
5434
+ const [resultMessage, setResultMessage] = useState18("");
5435
+ const generatorRef = useRef12(null);
5436
+ const mountedRef = useRef12(true);
5211
5437
  useEffect20(() => {
5212
5438
  mountedRef.current = true;
5213
5439
  void load();
@@ -5275,10 +5501,10 @@ function BrewfileView() {
5275
5501
  if (key.escape) {
5276
5502
  }
5277
5503
  });
5278
- if (loading) return /* @__PURE__ */ jsx34(Loading, { message: t("loading_default") });
5279
- if (error) return /* @__PURE__ */ jsx34(ErrorMessage, { message: error });
5504
+ if (loading) return /* @__PURE__ */ jsx35(Loading, { message: t("loading_default") });
5505
+ if (error) return /* @__PURE__ */ jsx35(ErrorMessage, { message: error });
5280
5506
  if (phase === "confirming-reconcile" && drift) {
5281
- return /* @__PURE__ */ jsx34(
5507
+ return /* @__PURE__ */ jsx35(
5282
5508
  ConfirmDialog,
5283
5509
  {
5284
5510
  message: t("confirm_brewfile_reconcile", {
@@ -5296,13 +5522,13 @@ function BrewfileView() {
5296
5522
  }
5297
5523
  if (phase === "creating") {
5298
5524
  return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", marginTop: SPACING.xs, children: [
5299
- /* @__PURE__ */ jsx34(SectionHeader, { emoji: "\u{1F4E6}", title: t("brewfile_title"), gradient: GRADIENTS.ocean }),
5525
+ /* @__PURE__ */ jsx35(SectionHeader, { emoji: "\u{1F4E6}", title: t("brewfile_title"), gradient: GRADIENTS.ocean }),
5300
5526
  /* @__PURE__ */ jsxs32(Box31, { marginTop: SPACING.xs, children: [
5301
5527
  /* @__PURE__ */ jsxs32(Text33, { color: COLORS.textSecondary, children: [
5302
5528
  t("brewfile_create_name"),
5303
5529
  " "
5304
5530
  ] }),
5305
- /* @__PURE__ */ jsx34(
5531
+ /* @__PURE__ */ jsx35(
5306
5532
  TextInput6,
5307
5533
  {
5308
5534
  defaultValue: "My Environment",
@@ -5318,7 +5544,7 @@ function BrewfileView() {
5318
5544
  ] });
5319
5545
  }
5320
5546
  if (phase === "reconciling") {
5321
- return /* @__PURE__ */ jsx34(Box31, { flexDirection: "column", children: /* @__PURE__ */ jsx34(
5547
+ return /* @__PURE__ */ jsx35(Box31, { flexDirection: "column", children: /* @__PURE__ */ jsx35(
5322
5548
  ProgressLog,
5323
5549
  {
5324
5550
  lines: streamLines,
@@ -5329,14 +5555,14 @@ function BrewfileView() {
5329
5555
  }
5330
5556
  if (phase === "result") {
5331
5557
  return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", marginTop: SPACING.xs, children: [
5332
- /* @__PURE__ */ jsx34(
5558
+ /* @__PURE__ */ jsx35(
5333
5559
  ResultBanner,
5334
5560
  {
5335
5561
  status: streamError ? "error" : "success",
5336
5562
  message: resultMessage
5337
5563
  }
5338
5564
  ),
5339
- /* @__PURE__ */ jsx34(Box31, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs32(Text33, { color: COLORS.textSecondary, children: [
5565
+ /* @__PURE__ */ jsx35(Box31, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs32(Text33, { color: COLORS.textSecondary, children: [
5340
5566
  "r:",
5341
5567
  t("hint_refresh"),
5342
5568
  " esc:",
@@ -5345,17 +5571,17 @@ function BrewfileView() {
5345
5571
  ] });
5346
5572
  }
5347
5573
  return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
5348
- /* @__PURE__ */ jsx34(SectionHeader, { emoji: "\u{1F4E6}", title: t("brewfile_title"), gradient: GRADIENTS.ocean }),
5574
+ /* @__PURE__ */ jsx35(SectionHeader, { emoji: "\u{1F4E6}", title: t("brewfile_title"), gradient: GRADIENTS.ocean }),
5349
5575
  schema === null ? /* @__PURE__ */ jsxs32(Box31, { marginTop: SPACING.xs, flexDirection: "column", children: [
5350
- /* @__PURE__ */ jsx34(ResultBanner, { status: "info", message: t("brewfile_no_brewfile") }),
5351
- /* @__PURE__ */ jsx34(Box31, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs32(Text33, { color: COLORS.textSecondary, children: [
5576
+ /* @__PURE__ */ jsx35(ResultBanner, { status: "info", message: t("brewfile_no_brewfile") }),
5577
+ /* @__PURE__ */ jsx35(Box31, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs32(Text33, { color: COLORS.textSecondary, children: [
5352
5578
  "n:",
5353
5579
  t("hint_new")
5354
5580
  ] }) })
5355
5581
  ] }) : /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", marginTop: SPACING.xs, children: [
5356
5582
  /* @__PURE__ */ jsxs32(Box31, { gap: SPACING.sm, children: [
5357
- /* @__PURE__ */ jsx34(Text33, { color: COLORS.text, bold: true, children: schema.meta.name }),
5358
- schema.meta.description && /* @__PURE__ */ jsx34(Text33, { color: COLORS.textSecondary, children: schema.meta.description }),
5583
+ /* @__PURE__ */ jsx35(Text33, { color: COLORS.text, bold: true, children: schema.meta.name }),
5584
+ schema.meta.description && /* @__PURE__ */ jsx35(Text33, { color: COLORS.textSecondary, children: schema.meta.description }),
5359
5585
  schema.strictMode && /* @__PURE__ */ jsxs32(Text33, { color: COLORS.warning, children: [
5360
5586
  "[",
5361
5587
  t("brewfile_strict_mode"),
@@ -5363,15 +5589,15 @@ function BrewfileView() {
5363
5589
  ] })
5364
5590
  ] }),
5365
5591
  /* @__PURE__ */ jsxs32(Box31, { gap: SPACING.md, marginTop: SPACING.xs, children: [
5366
- /* @__PURE__ */ jsx34(Text33, { color: COLORS.sky, children: t("brewfile_formulae_count", { count: schema.formulae.length }) }),
5367
- /* @__PURE__ */ jsx34(Text33, { color: COLORS.teal, children: t("brewfile_casks_count", { count: schema.casks.length }) })
5592
+ /* @__PURE__ */ jsx35(Text33, { color: COLORS.sky, children: t("brewfile_formulae_count", { count: schema.formulae.length }) }),
5593
+ /* @__PURE__ */ jsx35(Text33, { color: COLORS.teal, children: t("brewfile_casks_count", { count: schema.casks.length }) })
5368
5594
  ] }),
5369
- driftLoading && /* @__PURE__ */ jsx34(Box31, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx34(Text33, { color: COLORS.muted, children: t("brewfile_computing_drift") }) }),
5595
+ driftLoading && /* @__PURE__ */ jsx35(Box31, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx35(Text33, { color: COLORS.muted, children: t("brewfile_computing_drift") }) }),
5370
5596
  drift && !driftLoading && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", marginTop: SPACING.xs, children: [
5371
- /* @__PURE__ */ jsx34(DriftScore, { score: drift.score }),
5372
- /* @__PURE__ */ jsx34(DriftSummary, { drift })
5597
+ /* @__PURE__ */ jsx35(DriftScore, { score: drift.score }),
5598
+ /* @__PURE__ */ jsx35(DriftSummary, { drift })
5373
5599
  ] }),
5374
- /* @__PURE__ */ jsx34(Box31, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs32(Text33, { color: COLORS.textSecondary, children: [
5600
+ /* @__PURE__ */ jsx35(Box31, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs32(Text33, { color: COLORS.textSecondary, children: [
5375
5601
  "r:",
5376
5602
  t("hint_refresh"),
5377
5603
  drift && (drift.missingPackages.length > 0 || drift.wrongVersions.length > 0) ? ` c:${t("hint_reconcile")}` : "",
@@ -5384,9 +5610,9 @@ function BrewfileView() {
5384
5610
  }
5385
5611
 
5386
5612
  // src/views/sync.tsx
5387
- import { useCallback as useCallback5, useEffect as useEffect21, useState as useState18 } from "react";
5613
+ import { useCallback as useCallback5, useEffect as useEffect21, useState as useState19 } from "react";
5388
5614
  import { Box as Box32, Text as Text34 } from "ink";
5389
- import { Fragment as Fragment6, jsx as jsx35, jsxs as jsxs33 } from "react/jsx-runtime";
5615
+ import { Fragment as Fragment6, jsx as jsx36, jsxs as jsxs33 } from "react/jsx-runtime";
5390
5616
  function OverviewSection({
5391
5617
  config,
5392
5618
  lastResult,
@@ -5398,17 +5624,17 @@ function OverviewSection({
5398
5624
  const showComplianceHint = !hasConflicts && !!lastResult?.success;
5399
5625
  return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", marginTop: SPACING.xs, children: [
5400
5626
  config ? /* @__PURE__ */ jsxs33(Fragment6, { children: [
5401
- /* @__PURE__ */ jsx35(Box32, { marginBottom: SPACING.xs, children: /* @__PURE__ */ jsx35(Text34, { color: COLORS.textSecondary, children: t("sync_machine", { name: config.machineName }) }) }),
5402
- config.lastSync && /* @__PURE__ */ jsx35(Box32, { marginBottom: SPACING.xs, children: /* @__PURE__ */ jsx35(Text34, { color: COLORS.textSecondary, children: t("sync_last_sync", { date: new Date(config.lastSync).toLocaleString() }) }) }),
5403
- hasConflicts ? /* @__PURE__ */ jsx35(
5627
+ /* @__PURE__ */ jsx36(Box32, { marginBottom: SPACING.xs, children: /* @__PURE__ */ jsx36(Text34, { color: COLORS.textSecondary, children: t("sync_machine", { name: config.machineName }) }) }),
5628
+ config.lastSync && /* @__PURE__ */ jsx36(Box32, { marginBottom: SPACING.xs, children: /* @__PURE__ */ jsx36(Text34, { color: COLORS.textSecondary, children: t("sync_last_sync", { date: new Date(config.lastSync).toLocaleString() }) }) }),
5629
+ hasConflicts ? /* @__PURE__ */ jsx36(
5404
5630
  ResultBanner,
5405
5631
  {
5406
5632
  status: "error",
5407
5633
  message: t("sync_status_conflict", { count: String(conflicts.length) })
5408
5634
  }
5409
- ) : lastResult?.success ? /* @__PURE__ */ jsx35(ResultBanner, { status: "success", message: t("sync_status_ok") }) : null
5410
- ] }) : /* @__PURE__ */ jsx35(Box32, { marginBottom: SPACING.xs, children: /* @__PURE__ */ jsx35(Text34, { color: COLORS.textSecondary, children: t("sync_disabled") }) }),
5411
- /* @__PURE__ */ jsx35(Box32, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs33(Text34, { color: COLORS.textSecondary, children: [
5635
+ ) : lastResult?.success ? /* @__PURE__ */ jsx36(ResultBanner, { status: "success", message: t("sync_status_ok") }) : null
5636
+ ] }) : /* @__PURE__ */ jsx36(Box32, { marginBottom: SPACING.xs, children: /* @__PURE__ */ jsx36(Text34, { color: COLORS.textSecondary, children: t("sync_disabled") }) }),
5637
+ /* @__PURE__ */ jsx36(Box32, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs33(Text34, { color: COLORS.textSecondary, children: [
5412
5638
  "s",
5413
5639
  /* @__PURE__ */ jsxs33(Text34, { color: COLORS.gold, children: [
5414
5640
  ":",
@@ -5435,13 +5661,27 @@ function ConflictsList({
5435
5661
  entries,
5436
5662
  cursor
5437
5663
  }) {
5664
+ const availableRows = useVisibleRows({
5665
+ reservedRows: 8,
5666
+ fallbackReservedRows: 14,
5667
+ minRows: 4
5668
+ });
5669
+ const perEntryRows = 4;
5670
+ const maxEntries = Math.max(1, Math.floor(availableRows / perEntryRows));
5671
+ const start = Math.max(0, cursor - Math.floor(maxEntries / 2));
5672
+ const visible = entries.slice(start, start + maxEntries);
5438
5673
  return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", marginTop: SPACING.xs, children: [
5439
- entries.map((entry, i) => {
5674
+ start > 0 && /* @__PURE__ */ jsxs33(Text34, { color: COLORS.textSecondary, dimColor: true, children: [
5675
+ " ",
5676
+ t("scroll_moreAbove", { count: start })
5677
+ ] }),
5678
+ visible.map((entry, vi) => {
5679
+ const i = start + vi;
5440
5680
  const { conflict, resolution } = entry;
5441
5681
  const isActive = i === cursor;
5442
5682
  return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", marginBottom: SPACING.xs, children: [
5443
5683
  /* @__PURE__ */ jsxs33(SelectableRow, { isCurrent: isActive, children: [
5444
- /* @__PURE__ */ jsx35(Text34, { bold: true, color: isActive ? COLORS.text : COLORS.textSecondary, children: t("sync_conflict_title", { package: conflict.packageName }) }),
5684
+ /* @__PURE__ */ jsx36(Text34, { bold: true, color: isActive ? COLORS.text : COLORS.textSecondary, children: t("sync_conflict_title", { package: conflict.packageName }) }),
5445
5685
  /* @__PURE__ */ jsxs33(Text34, { color: COLORS.muted, children: [
5446
5686
  " (",
5447
5687
  conflict.packageType,
@@ -5474,7 +5714,11 @@ function ConflictsList({
5474
5714
  ] })
5475
5715
  ] }, `${conflict.packageName}-${conflict.remoteMachine}`);
5476
5716
  }),
5477
- /* @__PURE__ */ jsx35(Box32, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs33(Text34, { color: COLORS.textSecondary, children: [
5717
+ start + maxEntries < entries.length && /* @__PURE__ */ jsxs33(Text34, { color: COLORS.textSecondary, dimColor: true, children: [
5718
+ " ",
5719
+ t("scroll_moreBelow", { count: entries.length - start - maxEntries })
5720
+ ] }),
5721
+ /* @__PURE__ */ jsx36(Box32, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs33(Text34, { color: COLORS.textSecondary, children: [
5478
5722
  "j/k:",
5479
5723
  t("hint_navigate"),
5480
5724
  " l:",
@@ -5492,10 +5736,10 @@ function SyncView() {
5492
5736
  const isPro = useLicenseStore((s) => s.isPro);
5493
5737
  const navigate = useNavigationStore((s) => s.navigate);
5494
5738
  const { config, lastResult, conflicts, loading, error, initialize, syncNow, resolveConflicts } = useSyncStore();
5495
- const [phase, setPhase] = useState18("overview");
5496
- const [syncError, setSyncError] = useState18(null);
5497
- const [conflictEntries, setConflictEntries] = useState18([]);
5498
- const [cursor, setCursor] = useState18(0);
5739
+ const [phase, setPhase] = useState19("overview");
5740
+ const [syncError, setSyncError] = useState19(null);
5741
+ const [conflictEntries, setConflictEntries] = useState19([]);
5742
+ const [cursor, setCursor] = useState19(0);
5499
5743
  useEffect21(() => {
5500
5744
  void initialize(isPro());
5501
5745
  }, []);
@@ -5591,7 +5835,7 @@ function SyncView() {
5591
5835
  }
5592
5836
  });
5593
5837
  if (phase === "confirming-sync") {
5594
- return /* @__PURE__ */ jsx35(
5838
+ return /* @__PURE__ */ jsx36(
5595
5839
  ConfirmDialog,
5596
5840
  {
5597
5841
  message: t("confirm_sync_now"),
@@ -5605,7 +5849,7 @@ function SyncView() {
5605
5849
  );
5606
5850
  }
5607
5851
  if (phase === "confirming-apply") {
5608
- return /* @__PURE__ */ jsx35(
5852
+ return /* @__PURE__ */ jsx36(
5609
5853
  ConfirmDialog,
5610
5854
  {
5611
5855
  message: t("confirm_sync_apply", { count: String(conflictEntries.length) }),
@@ -5619,20 +5863,20 @@ function SyncView() {
5619
5863
  );
5620
5864
  }
5621
5865
  if (phase === "syncing" || loading) {
5622
- return /* @__PURE__ */ jsx35(Loading, { message: t("sync_syncing") });
5866
+ return /* @__PURE__ */ jsx36(Loading, { message: t("sync_syncing") });
5623
5867
  }
5624
5868
  if (phase === "result") {
5625
5869
  const isError = !!(syncError ?? error);
5626
5870
  return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", marginTop: SPACING.xs, children: [
5627
- /* @__PURE__ */ jsx35(SectionHeader, { emoji: "\u{1F504}", title: t("sync_title"), gradient: GRADIENTS.gold }),
5628
- /* @__PURE__ */ jsx35(Box32, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx35(
5871
+ /* @__PURE__ */ jsx36(SectionHeader, { emoji: "\u{1F504}", title: t("sync_title"), gradient: GRADIENTS.gold }),
5872
+ /* @__PURE__ */ jsx36(Box32, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx36(
5629
5873
  ResultBanner,
5630
5874
  {
5631
5875
  status: isError ? "error" : "success",
5632
5876
  message: isError ? t("sync_error", { error: syncError ?? error ?? "" }) : t("sync_success")
5633
5877
  }
5634
5878
  ) }),
5635
- /* @__PURE__ */ jsx35(Box32, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs33(Text34, { color: COLORS.textSecondary, children: [
5879
+ /* @__PURE__ */ jsx36(Box32, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs33(Text34, { color: COLORS.textSecondary, children: [
5636
5880
  "r:",
5637
5881
  t("hint_refresh"),
5638
5882
  " esc:",
@@ -5641,9 +5885,9 @@ function SyncView() {
5641
5885
  ] });
5642
5886
  }
5643
5887
  return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
5644
- /* @__PURE__ */ jsx35(SectionHeader, { emoji: "\u{1F504}", title: t("sync_title"), gradient: GRADIENTS.gold }),
5645
- error && phase === "overview" && /* @__PURE__ */ jsx35(Box32, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx35(ResultBanner, { status: "error", message: t("sync_error", { error }) }) }),
5646
- phase === "overview" && /* @__PURE__ */ jsx35(
5888
+ /* @__PURE__ */ jsx36(SectionHeader, { emoji: "\u{1F504}", title: t("sync_title"), gradient: GRADIENTS.gold }),
5889
+ error && phase === "overview" && /* @__PURE__ */ jsx36(Box32, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx36(ResultBanner, { status: "error", message: t("sync_error", { error }) }) }),
5890
+ phase === "overview" && /* @__PURE__ */ jsx36(
5647
5891
  OverviewSection,
5648
5892
  {
5649
5893
  config,
@@ -5657,14 +5901,14 @@ function SyncView() {
5657
5901
  }
5658
5902
  ),
5659
5903
  phase === "conflicts" && /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
5660
- /* @__PURE__ */ jsx35(SectionHeader, { emoji: "\u26A0", title: t("sync_status_conflict", { count: String(conflictEntries.length) }), gradient: GRADIENTS.gold }),
5661
- /* @__PURE__ */ jsx35(ConflictsList, { entries: conflictEntries, cursor })
5904
+ /* @__PURE__ */ jsx36(SectionHeader, { emoji: "\u26A0", title: t("sync_status_conflict", { count: String(conflictEntries.length) }), gradient: GRADIENTS.gold }),
5905
+ /* @__PURE__ */ jsx36(ConflictsList, { entries: conflictEntries, cursor })
5662
5906
  ] })
5663
5907
  ] });
5664
5908
  }
5665
5909
 
5666
5910
  // src/views/compliance.tsx
5667
- import { useCallback as useCallback6, useEffect as useEffect22, useRef as useRef11, useState as useState19 } from "react";
5911
+ import { useCallback as useCallback6, useEffect as useEffect22, useRef as useRef13, useState as useState20 } from "react";
5668
5912
  import { Box as Box33, Text as Text35 } from "ink";
5669
5913
  import { TextInput as TextInput7 } from "@inkjs/ui";
5670
5914
 
@@ -5710,7 +5954,7 @@ async function* remediateViolations(violations, isPro) {
5710
5954
 
5711
5955
  // src/views/compliance.tsx
5712
5956
  import { join as join4 } from "path";
5713
- import { jsx as jsx36, jsxs as jsxs34 } from "react/jsx-runtime";
5957
+ import { jsx as jsx37, jsxs as jsxs34 } from "react/jsx-runtime";
5714
5958
  function ComplianceScore({ report }) {
5715
5959
  const color = report.score >= 80 ? COLORS.success : report.score >= 50 ? COLORS.warning : COLORS.error;
5716
5960
  const bars = Math.round(report.score / 10);
@@ -5745,38 +5989,58 @@ function ViolationItem({ violation }) {
5745
5989
  prefix,
5746
5990
  " "
5747
5991
  ] }),
5748
- /* @__PURE__ */ jsx36(Text35, { color, children: violation.detail })
5992
+ /* @__PURE__ */ jsx37(Text35, { color, wrap: "wrap", children: violation.detail })
5749
5993
  ] });
5750
5994
  }
5751
5995
  function ViolationList({ violations }) {
5752
5996
  const errors = violations.filter((v) => v.severity === "error");
5753
5997
  const warnings = violations.filter((v) => v.severity === "warning");
5998
+ const totalRows = useVisibleRows({
5999
+ reservedRows: 11,
6000
+ fallbackReservedRows: 18,
6001
+ minRows: 2
6002
+ });
6003
+ const total = errors.length + warnings.length;
6004
+ const errorBudget = total === 0 ? 0 : Math.min(errors.length, Math.max(1, Math.round(errors.length / total * totalRows)));
6005
+ const warningBudget = Math.max(0, totalRows - errorBudget);
6006
+ const visibleErrors = errors.slice(0, errorBudget);
6007
+ const visibleWarnings = warnings.slice(0, warningBudget);
6008
+ const errorsHidden = errors.length - visibleErrors.length;
6009
+ const warningsHidden = warnings.length - visibleWarnings.length;
5754
6010
  return /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", marginTop: SPACING.xs, children: [
5755
6011
  errors.length > 0 && /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", marginBottom: SPACING.xs, children: [
5756
6012
  /* @__PURE__ */ jsxs34(Text35, { color: COLORS.error, bold: true, children: [
5757
6013
  t("compliance_violations", { count: String(errors.length) }),
5758
6014
  " (errors)"
5759
6015
  ] }),
5760
- errors.map((v) => /* @__PURE__ */ jsx36(ViolationItem, { violation: v }, `${v.type}-${v.packageName}`))
6016
+ visibleErrors.map((v) => /* @__PURE__ */ jsx37(ViolationItem, { violation: v }, `${v.type}-${v.packageName}`)),
6017
+ errorsHidden > 0 && /* @__PURE__ */ jsxs34(Text35, { color: COLORS.textSecondary, dimColor: true, children: [
6018
+ " ",
6019
+ t("scroll_moreBelow", { count: errorsHidden })
6020
+ ] })
5761
6021
  ] }),
5762
6022
  warnings.length > 0 && /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", children: [
5763
6023
  /* @__PURE__ */ jsxs34(Text35, { color: COLORS.warning, bold: true, children: [
5764
6024
  t("compliance_violations", { count: String(warnings.length) }),
5765
6025
  " (warnings)"
5766
6026
  ] }),
5767
- warnings.map((v) => /* @__PURE__ */ jsx36(ViolationItem, { violation: v }, `${v.type}-${v.packageName}`))
6027
+ visibleWarnings.map((v) => /* @__PURE__ */ jsx37(ViolationItem, { violation: v }, `${v.type}-${v.packageName}`)),
6028
+ warningsHidden > 0 && /* @__PURE__ */ jsxs34(Text35, { color: COLORS.textSecondary, dimColor: true, children: [
6029
+ " ",
6030
+ t("scroll_moreBelow", { count: warningsHidden })
6031
+ ] })
5768
6032
  ] })
5769
6033
  ] });
5770
6034
  }
5771
6035
  function ComplianceView() {
5772
6036
  const isPro = useLicenseStore((s) => s.isPro);
5773
6037
  const { policy, report, loading, error, importPolicy, runCheck } = useComplianceStore();
5774
- const [phase, setPhase] = useState19("overview");
5775
- const [resultMessage, setResultMessage] = useState19(null);
5776
- const [streamLines, setStreamLines] = useState19([]);
5777
- const [streamRunning, setStreamRunning] = useState19(false);
5778
- const generatorRef = useRef11(null);
5779
- const mountedRef = useRef11(true);
6038
+ const [phase, setPhase] = useState20("overview");
6039
+ const [resultMessage, setResultMessage] = useState20(null);
6040
+ const [streamLines, setStreamLines] = useState20([]);
6041
+ const [streamRunning, setStreamRunning] = useState20(false);
6042
+ const generatorRef = useRef13(null);
6043
+ const mountedRef = useRef13(true);
5780
6044
  useEffect22(() => {
5781
6045
  mountedRef.current = true;
5782
6046
  return () => {
@@ -5889,7 +6153,7 @@ function ComplianceView() {
5889
6153
  const actionable = report.violations.filter(
5890
6154
  (v) => v.type === "missing" || v.type === "wrong-version"
5891
6155
  );
5892
- return /* @__PURE__ */ jsx36(
6156
+ return /* @__PURE__ */ jsx37(
5893
6157
  ConfirmDialog,
5894
6158
  {
5895
6159
  message: t("confirm_compliance_remediate", { count: String(actionable.length) }),
@@ -5905,23 +6169,23 @@ function ComplianceView() {
5905
6169
  if (phase === "remediating" || loading && phase !== "importing") {
5906
6170
  if (phase === "remediating") {
5907
6171
  return /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", children: [
5908
- /* @__PURE__ */ jsx36(SectionHeader, { emoji: "\u{1F50D}", title: t("compliance_title"), gradient: GRADIENTS.gold }),
5909
- /* @__PURE__ */ jsx36(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx36(ProgressLog, { lines: streamLines, isRunning: streamRunning, title: t("compliance_remediating") }) })
6172
+ /* @__PURE__ */ jsx37(SectionHeader, { emoji: "\u{1F50D}", title: t("compliance_title"), gradient: GRADIENTS.gold }),
6173
+ /* @__PURE__ */ jsx37(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx37(ProgressLog, { lines: streamLines, isRunning: streamRunning, title: t("compliance_remediating") }) })
5910
6174
  ] });
5911
6175
  }
5912
- return /* @__PURE__ */ jsx36(Loading, { message: t("compliance_title") });
6176
+ return /* @__PURE__ */ jsx37(Loading, { message: t("compliance_title") });
5913
6177
  }
5914
6178
  if (phase === "result" && resultMessage) {
5915
6179
  return /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", marginTop: SPACING.xs, children: [
5916
- /* @__PURE__ */ jsx36(SectionHeader, { emoji: "\u{1F50D}", title: t("compliance_title"), gradient: GRADIENTS.gold }),
5917
- /* @__PURE__ */ jsx36(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx36(
6180
+ /* @__PURE__ */ jsx37(SectionHeader, { emoji: "\u{1F50D}", title: t("compliance_title"), gradient: GRADIENTS.gold }),
6181
+ /* @__PURE__ */ jsx37(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx37(
5918
6182
  ResultBanner,
5919
6183
  {
5920
6184
  status: resultMessage.ok ? "success" : "error",
5921
6185
  message: resultMessage.text
5922
6186
  }
5923
6187
  ) }),
5924
- /* @__PURE__ */ jsx36(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs34(Text35, { color: COLORS.textSecondary, children: [
6188
+ /* @__PURE__ */ jsx37(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs34(Text35, { color: COLORS.textSecondary, children: [
5925
6189
  "r:",
5926
6190
  t("hint_refresh"),
5927
6191
  " esc:",
@@ -5930,11 +6194,11 @@ function ComplianceView() {
5930
6194
  ] });
5931
6195
  }
5932
6196
  return /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", children: [
5933
- /* @__PURE__ */ jsx36(SectionHeader, { emoji: "\u{1F50D}", title: t("compliance_title"), gradient: GRADIENTS.gold }),
5934
- error && /* @__PURE__ */ jsx36(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx36(ResultBanner, { status: "error", message: t("compliance_import_error", { error }) }) }),
6197
+ /* @__PURE__ */ jsx37(SectionHeader, { emoji: "\u{1F50D}", title: t("compliance_title"), gradient: GRADIENTS.gold }),
6198
+ error && /* @__PURE__ */ jsx37(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx37(ResultBanner, { status: "error", message: t("compliance_import_error", { error }) }) }),
5935
6199
  phase === "importing" && /* @__PURE__ */ jsxs34(Box33, { marginTop: SPACING.xs, flexDirection: "column", children: [
5936
- /* @__PURE__ */ jsx36(Text35, { color: COLORS.textSecondary, children: t("compliance_import_prompt") }),
5937
- /* @__PURE__ */ jsx36(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx36(
6200
+ /* @__PURE__ */ jsx37(Text35, { color: COLORS.textSecondary, children: t("compliance_import_prompt") }),
6201
+ /* @__PURE__ */ jsx37(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx37(
5938
6202
  TextInput7,
5939
6203
  {
5940
6204
  defaultValue: "",
@@ -5943,20 +6207,20 @@ function ComplianceView() {
5943
6207
  }
5944
6208
  }
5945
6209
  ) }),
5946
- /* @__PURE__ */ jsx36(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs34(Text35, { color: COLORS.muted, dimColor: true, children: [
6210
+ /* @__PURE__ */ jsx37(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsxs34(Text35, { color: COLORS.muted, dimColor: true, children: [
5947
6211
  "esc:",
5948
6212
  t("hint_back")
5949
6213
  ] }) })
5950
6214
  ] }),
5951
6215
  phase === "overview" && /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", marginTop: SPACING.xs, children: [
5952
- !policy ? /* @__PURE__ */ jsx36(Box33, { flexDirection: "column", children: /* @__PURE__ */ jsx36(Text35, { color: COLORS.textSecondary, children: t("compliance_no_policy") }) }) : /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", children: [
5953
- /* @__PURE__ */ jsx36(Text35, { color: COLORS.textSecondary, bold: true, children: t("compliance_policy_by", { maintainer: policy.meta.maintainer }) }),
6216
+ !policy ? /* @__PURE__ */ jsx37(Box33, { flexDirection: "column", children: /* @__PURE__ */ jsx37(Text35, { color: COLORS.textSecondary, children: t("compliance_no_policy") }) }) : /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", children: [
6217
+ /* @__PURE__ */ jsx37(Text35, { color: COLORS.textSecondary, bold: true, children: t("compliance_policy_by", { maintainer: policy.meta.maintainer }) }),
5954
6218
  report ? /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", marginTop: SPACING.xs, children: [
5955
- /* @__PURE__ */ jsx36(ComplianceScore, { report }),
5956
- report.compliant ? /* @__PURE__ */ jsx36(ResultBanner, { status: "success", message: t("compliance_ok") }) : /* @__PURE__ */ jsx36(ViolationList, { violations: report.violations })
5957
- ] }) : /* @__PURE__ */ jsx36(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx36(Text35, { color: COLORS.muted, dimColor: true, children: t("compliance_press_r_hint") }) })
6219
+ /* @__PURE__ */ jsx37(ComplianceScore, { report }),
6220
+ report.compliant ? /* @__PURE__ */ jsx37(ResultBanner, { status: "success", message: t("compliance_ok") }) : /* @__PURE__ */ jsx37(ViolationList, { violations: report.violations })
6221
+ ] }) : /* @__PURE__ */ jsx37(Box33, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx37(Text35, { color: COLORS.muted, dimColor: true, children: t("compliance_press_r_hint") }) })
5958
6222
  ] }),
5959
- /* @__PURE__ */ jsx36(Box33, { marginTop: SPACING.sm, flexWrap: "wrap", children: /* @__PURE__ */ jsxs34(Text35, { color: COLORS.textSecondary, children: [
6223
+ /* @__PURE__ */ jsx37(Box33, { marginTop: SPACING.sm, flexWrap: "wrap", children: /* @__PURE__ */ jsxs34(Text35, { color: COLORS.textSecondary, children: [
5960
6224
  "i:",
5961
6225
  t("hint_import"),
5962
6226
  policy && /* @__PURE__ */ jsxs34(Text35, { children: [
@@ -5979,7 +6243,7 @@ function ComplianceView() {
5979
6243
  }
5980
6244
 
5981
6245
  // src/app.tsx
5982
- import { Fragment as Fragment7, jsx as jsx37, jsxs as jsxs35 } from "react/jsx-runtime";
6246
+ import { Fragment as Fragment7, jsx as jsx38, jsxs as jsxs35 } from "react/jsx-runtime";
5983
6247
  function LicenseInitializer() {
5984
6248
  const initLicense = useLicenseStore((s) => s.initialize);
5985
6249
  useEffect23(() => {
@@ -5991,65 +6255,65 @@ function ViewRouter({ currentView }) {
5991
6255
  const isPro = useLicenseStore((s) => s.isPro);
5992
6256
  const isTeam = useLicenseStore((s) => s.isTeam);
5993
6257
  if (isProView(currentView) && !isPro()) {
5994
- return /* @__PURE__ */ jsx37(UpgradePrompt, { viewId: currentView });
6258
+ return /* @__PURE__ */ jsx38(UpgradePrompt, { viewId: currentView });
5995
6259
  }
5996
6260
  if (isTeamView(currentView) && !isTeam()) {
5997
- return /* @__PURE__ */ jsx37(UpgradePrompt, { viewId: currentView });
6261
+ return /* @__PURE__ */ jsx38(UpgradePrompt, { viewId: currentView });
5998
6262
  }
5999
6263
  switch (currentView) {
6000
6264
  case "dashboard":
6001
- return /* @__PURE__ */ jsx37(DashboardView, {});
6265
+ return /* @__PURE__ */ jsx38(DashboardView, {});
6002
6266
  case "installed":
6003
- return /* @__PURE__ */ jsx37(InstalledView, {});
6267
+ return /* @__PURE__ */ jsx38(InstalledView, {});
6004
6268
  case "search":
6005
- return /* @__PURE__ */ jsx37(SearchView, {});
6269
+ return /* @__PURE__ */ jsx38(SearchView, {});
6006
6270
  case "outdated":
6007
- return /* @__PURE__ */ jsx37(OutdatedView, {});
6271
+ return /* @__PURE__ */ jsx38(OutdatedView, {});
6008
6272
  case "package-info":
6009
- return /* @__PURE__ */ jsx37(PackageInfoView, {});
6273
+ return /* @__PURE__ */ jsx38(PackageInfoView, {});
6010
6274
  case "services":
6011
- return /* @__PURE__ */ jsx37(ServicesView, {});
6275
+ return /* @__PURE__ */ jsx38(ServicesView, {});
6012
6276
  case "doctor":
6013
- return /* @__PURE__ */ jsx37(DoctorView, {});
6277
+ return /* @__PURE__ */ jsx38(DoctorView, {});
6014
6278
  case "profiles":
6015
- return /* @__PURE__ */ jsx37(ProfilesView, {});
6279
+ return /* @__PURE__ */ jsx38(ProfilesView, {});
6016
6280
  case "smart-cleanup":
6017
- return /* @__PURE__ */ jsx37(SmartCleanupView, {});
6281
+ return /* @__PURE__ */ jsx38(SmartCleanupView, {});
6018
6282
  case "history":
6019
- return /* @__PURE__ */ jsx37(HistoryView, {});
6283
+ return /* @__PURE__ */ jsx38(HistoryView, {});
6020
6284
  case "rollback":
6021
- return /* @__PURE__ */ jsx37(RollbackView, {});
6285
+ return /* @__PURE__ */ jsx38(RollbackView, {});
6022
6286
  case "brewfile":
6023
- return /* @__PURE__ */ jsx37(BrewfileView, {});
6287
+ return /* @__PURE__ */ jsx38(BrewfileView, {});
6024
6288
  case "sync":
6025
- return /* @__PURE__ */ jsx37(SyncView, {});
6289
+ return /* @__PURE__ */ jsx38(SyncView, {});
6026
6290
  case "security-audit":
6027
- return /* @__PURE__ */ jsx37(SecurityAuditView, {});
6291
+ return /* @__PURE__ */ jsx38(SecurityAuditView, {});
6028
6292
  case "compliance":
6029
- return /* @__PURE__ */ jsx37(ComplianceView, {});
6293
+ return /* @__PURE__ */ jsx38(ComplianceView, {});
6030
6294
  case "account":
6031
- return /* @__PURE__ */ jsx37(AccountView, {});
6295
+ return /* @__PURE__ */ jsx38(AccountView, {});
6032
6296
  }
6033
6297
  }
6034
6298
  function App() {
6035
6299
  const { exit } = useApp();
6036
6300
  const currentView = useNavigationStore((s) => s.currentView);
6037
6301
  const isTestEnv = typeof process !== "undefined" && false;
6038
- const [showWelcome, setShowWelcome] = useState20(isTestEnv ? false : null);
6302
+ const [showWelcome, setShowWelcome] = useState21(isTestEnv ? false : null);
6039
6303
  useEffect23(() => {
6040
6304
  if (isTestEnv) return;
6041
6305
  void hasCompletedOnboarding().then((done) => setShowWelcome(!done));
6042
6306
  }, []);
6043
6307
  useGlobalKeyboard({ onQuit: exit });
6044
6308
  if (showWelcome === null) {
6045
- return /* @__PURE__ */ jsx37(AppLayout, { children: /* @__PURE__ */ jsx37(Fragment7, {}) });
6309
+ return /* @__PURE__ */ jsx38(AppLayout, { children: /* @__PURE__ */ jsx38(Fragment7, {}) });
6046
6310
  }
6047
6311
  if (showWelcome) {
6048
- return /* @__PURE__ */ jsx37(AppLayout, { children: /* @__PURE__ */ jsx37(WelcomeView, { onContinue: () => setShowWelcome(false) }) });
6312
+ return /* @__PURE__ */ jsx38(AppLayout, { children: /* @__PURE__ */ jsx38(WelcomeView, { onContinue: () => setShowWelcome(false) }) });
6049
6313
  }
6050
6314
  return /* @__PURE__ */ jsxs35(AppLayout, { children: [
6051
- /* @__PURE__ */ jsx37(LicenseInitializer, {}),
6052
- /* @__PURE__ */ jsx37(ViewRouter, { currentView })
6315
+ /* @__PURE__ */ jsx38(LicenseInitializer, {}),
6316
+ /* @__PURE__ */ jsx38(ViewRouter, { currentView })
6053
6317
  ] });
6054
6318
  }
6055
6319
 
@@ -6133,7 +6397,7 @@ async function reportError(err, context = {}) {
6133
6397
  const config = await resolveConfig();
6134
6398
  if (!config.enabled || !config.endpoint) return;
6135
6399
  const machineId = await getMachineId();
6136
- const version = true ? "0.9.2" : "unknown";
6400
+ const version = true ? "1.1.0" : "unknown";
6137
6401
  await postReport(buildReport("error", err, context, machineId, version), config);
6138
6402
  }
6139
6403
  async function installCrashReporter() {
@@ -6142,7 +6406,7 @@ async function installCrashReporter() {
6142
6406
  if (!config.enabled || !config.endpoint) return;
6143
6407
  _installed = true;
6144
6408
  const machineId = await getMachineId();
6145
- const version = true ? "0.9.2" : "unknown";
6409
+ const version = true ? "1.1.0" : "unknown";
6146
6410
  process.on("uncaughtException", (err) => {
6147
6411
  void postReport(buildReport("fatal", err, { kind: "uncaughtException" }, machineId, version), config);
6148
6412
  });
@@ -6153,11 +6417,11 @@ async function installCrashReporter() {
6153
6417
  }
6154
6418
 
6155
6419
  // src/index.tsx
6156
- import { jsx as jsx38 } from "react/jsx-runtime";
6420
+ import { jsx as jsx39 } from "react/jsx-runtime";
6157
6421
  var [, , command, arg] = process.argv;
6158
6422
  async function runCli() {
6159
6423
  if (command === "--version" || command === "-v" || command === "version") {
6160
- process.stdout.write("0.9.2\n");
6424
+ process.stdout.write("1.1.0\n");
6161
6425
  return;
6162
6426
  }
6163
6427
  await ensureDataDirs();
@@ -6333,14 +6597,14 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
6333
6597
  await ensureBrewBarRunning();
6334
6598
  process.env.BREW_TUI_TUI_MODE = "1";
6335
6599
  process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
6336
- render(/* @__PURE__ */ jsx38(App, {}));
6600
+ render(/* @__PURE__ */ jsx39(App, {}));
6337
6601
  }
6338
6602
  async function ensureBrewBarRunning() {
6339
6603
  if (process.platform !== "darwin") return;
6340
6604
  await useLicenseStore.getState().initialize();
6341
6605
  if (!useLicenseStore.getState().isPro()) return;
6342
6606
  const { isBrewBarInstalled, installBrewBar, launchBrewBar } = await import("./brewbar-installer-GWJ76J6G.js");
6343
- const { checkBrewBarVersion } = await import("./version-check-LHQYDFDA.js");
6607
+ const { checkBrewBarVersion } = await import("./version-check-LVSQFXZU.js");
6344
6608
  try {
6345
6609
  if (!await isBrewBarInstalled()) {
6346
6610
  console.log(t("cli_brewbarInstalling"));