pre-claude 0.0.1-beta.3 → 0.0.1-beta.4

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/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import { defineCommand as defineCommand3, runMain } from "citty";
5
5
 
6
6
  // package.json
7
7
  var name = "pre-claude";
8
- var version = "0.0.1-beta.3";
8
+ var version = "0.0.1-beta.4";
9
9
  var description = "\u{1F40D} TUI for building structured prompts for Claude";
10
10
 
11
11
  // src/cli/commands/init.ts
@@ -237,11 +237,12 @@ var safeParseConfig = (data) => {
237
237
  };
238
238
 
239
239
  // src/tui/App.tsx
240
- import { Text as Text16, useApp } from "ink";
240
+ import { Text as Text16, useApp as useApp2 } from "ink";
241
241
  import { useCallback as useCallback6, useState as useState8 } from "react";
242
242
 
243
243
  // src/tui/views/Preview/Preview.tsx
244
- import { Box as Box5, Text as Text4 } from "ink";
244
+ import { spawn } from "node:child_process";
245
+ import { Box as Box5, Text as Text4, useApp, useStdout as useStdout2 } from "ink";
245
246
  import { useCallback, useMemo, useState } from "react";
246
247
 
247
248
  // src/tui/features/document/services.ts
@@ -352,26 +353,6 @@ var HELP_COLOR = "blue";
352
353
 
353
354
  // src/tui/features/generation/services.ts
354
355
  import { query } from "@anthropic-ai/claude-agent-sdk";
355
- async function* generateDesignDocStream({
356
- scenario,
357
- formData,
358
- aiContext
359
- }) {
360
- const prompt = scenario.prompt({ formData, aiContext });
361
- for await (const msg of query({
362
- prompt,
363
- options: {
364
- includePartialMessages: true
365
- }
366
- })) {
367
- if (msg.type === "stream_event") {
368
- const event = msg.event;
369
- if (event.type === "content_block_delta" && event.delta?.type === "text_delta" && event.delta.text != null) {
370
- yield { type: "text_delta", text: event.delta.text };
371
- }
372
- }
373
- }
374
- }
375
356
  var generatePreview = async ({
376
357
  scenario,
377
358
  formData,
@@ -382,15 +363,26 @@ var generatePreview = async ({
382
363
  }) => {
383
364
  try {
384
365
  let content = "";
385
- for await (const chunk of generateDesignDocStream({
386
- scenario,
387
- formData,
388
- aiContext
366
+ let sessionId = null;
367
+ const prompt = scenario.prompt({ formData, aiContext });
368
+ for await (const msg of query({
369
+ prompt,
370
+ options: {
371
+ includePartialMessages: true
372
+ }
389
373
  })) {
390
- content += chunk.text;
391
- onChunk(chunk.text);
374
+ if (msg.type === "stream_event") {
375
+ const event = msg.event;
376
+ if (event.type === "content_block_delta" && event.delta?.type === "text_delta" && event.delta.text != null) {
377
+ content += event.delta.text;
378
+ onChunk(event.delta.text);
379
+ }
380
+ }
381
+ if ("session_id" in msg && msg.session_id && !sessionId) {
382
+ sessionId = msg.session_id;
383
+ }
392
384
  }
393
- onComplete(content);
385
+ onComplete({ content, sessionId });
394
386
  } catch (error) {
395
387
  onError(error instanceof Error ? error : new Error(String(error)));
396
388
  }
@@ -414,6 +406,7 @@ var useControl = (options = {}) => {
414
406
  onPrev,
415
407
  onRegenerate,
416
408
  onInfo,
409
+ onContinue,
417
410
  onChar,
418
411
  isActive = true,
419
412
  viKeysEnabled = true
@@ -485,6 +478,9 @@ var useControl = (options = {}) => {
485
478
  case "i":
486
479
  onInfo?.();
487
480
  return;
481
+ case "c":
482
+ onContinue?.();
483
+ return;
488
484
  }
489
485
  onChar?.(input);
490
486
  },
@@ -522,28 +518,19 @@ var ControlBar = ({ items }) => {
522
518
  // src/tui/components/Header/Header.tsx
523
519
  import { Box as Box2, Text as Text2 } from "ink";
524
520
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
525
- var ASCII_LOGO = ` _ _
526
- _ __ _ __ ___ ___| | __ _ _ _ __| | ___
527
- | '_ \\| '__/ _ \\_____/ __| |/ _\` | | | |/ _\` |/ _ \\
528
- | |_) | | | __/_____| (__| | (_| | |_| | (_| | __/
529
- | .__/|_| \\___| \\___|_|\\__,_|\\__,_|\\__,_|\\___|
530
- |_|`;
531
521
  var Header = ({
532
522
  description: description2 = "Structured Prompt Builder for Claude"
533
523
  }) => {
534
524
  return /* @__PURE__ */ jsxs(
535
525
  Box2,
536
526
  {
537
- flexDirection: "column",
538
527
  borderStyle: "double",
539
528
  borderColor: ACCENT_COLOR,
540
529
  paddingX: 1,
530
+ justifyContent: "space-between",
541
531
  children: [
542
- /* @__PURE__ */ jsx2(Text2, { color: ACCENT_COLOR, children: ASCII_LOGO }),
543
- /* @__PURE__ */ jsx2(Box2, { marginTop: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs(Text2, { color: ACCENT_COLOR, children: [
544
- "\u{1F40D} ",
545
- description2
546
- ] }) })
532
+ /* @__PURE__ */ jsx2(Text2, { color: ACCENT_COLOR, bold: true, children: "\u{1F40D} pre-claude" }),
533
+ /* @__PURE__ */ jsx2(Text2, { color: ACCENT_COLOR, children: description2 })
547
534
  ]
548
535
  }
549
536
  );
@@ -589,6 +576,16 @@ var StatusBar = ({ message, type = "info" }) => {
589
576
  ] }) : /* @__PURE__ */ jsx3(Text3, { color, children: message }) });
590
577
  };
591
578
 
579
+ // src/tui/hooks/useTerminalHeight.ts
580
+ import { useStdout } from "ink";
581
+ var useTerminalHeight = () => {
582
+ const { stdout } = useStdout();
583
+ const rows = stdout?.rows ?? 24;
584
+ const fixedLayoutHeight = 7;
585
+ const availableHeight = Math.max(10, rows - fixedLayoutHeight);
586
+ return { rows, availableHeight };
587
+ };
588
+
592
589
  // src/tui/layouts/CommonLayout/CommonLayout.tsx
593
590
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
594
591
  var CommonLayout = ({
@@ -596,9 +593,10 @@ var CommonLayout = ({
596
593
  status,
597
594
  controls = []
598
595
  }) => {
599
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", padding: 1, children: [
596
+ const { rows } = useTerminalHeight();
597
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", padding: 1, height: rows, children: [
600
598
  /* @__PURE__ */ jsx4(Header, {}),
601
- children,
599
+ /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", flexGrow: 1, children }),
602
600
  controls.length > 0 && /* @__PURE__ */ jsx4(ControlBar, { items: controls }),
603
601
  status?.message && /* @__PURE__ */ jsx4(StatusBar, { message: status.message, type: status.type })
604
602
  ] });
@@ -630,22 +628,30 @@ var Preview = ({
630
628
  const [error, setError] = useState(null);
631
629
  const [isSaving, setIsSaving] = useState(false);
632
630
  const [scrollOffset, setScrollOffset] = useState(0);
631
+ const [dataPreviewScrollOffset, setDataPreviewScrollOffset] = useState(0);
633
632
  const [savedFilename, setSavedFilename] = useState(null);
634
633
  const [showDataPreview, setShowDataPreview] = useState(false);
634
+ const [showContinueDialog, setShowContinueDialog] = useState(false);
635
+ const { stdout } = useStdout2();
636
+ const [sessionId, setSessionId] = useState(null);
637
+ const { exit } = useApp();
635
638
  const isEditing = editingFilename != null;
636
639
  const canSave = previewContent !== "" && !isGenerating;
637
640
  const lines = previewContent.split("\n");
638
- const maxVisible = 15;
641
+ const fixedHeight = 8;
642
+ const maxVisible = Math.max(10, (stdout?.rows ?? 24) - fixedHeight);
639
643
  const visibleLines = lines.slice(scrollOffset, scrollOffset + maxVisible);
644
+ const canContinue = sessionId != null && !isGenerating;
640
645
  const controls = useMemo(
641
646
  () => [
642
647
  { key: "\u2191\u2193/jk", action: "scroll" },
643
648
  { key: "r", action: "regenerate" },
644
649
  ...canSave ? [{ key: "s", action: "save" }] : [],
650
+ ...canContinue ? [{ key: "c", action: "continue in Claude" }] : [],
645
651
  { key: "i", action: "info" },
646
652
  { key: "q/Esc", action: "back" }
647
653
  ],
648
- [canSave]
654
+ [canSave, canContinue]
649
655
  );
650
656
  const status = useMemo(() => {
651
657
  if (error) {
@@ -666,6 +672,7 @@ var Preview = ({
666
672
  setIsGenerating(true);
667
673
  setError(null);
668
674
  setPreviewContent("");
675
+ setSessionId(null);
669
676
  let accumulatedContent = "";
670
677
  try {
671
678
  await generatePreview({
@@ -676,8 +683,9 @@ var Preview = ({
676
683
  accumulatedContent += chunk;
677
684
  setPreviewContent(accumulatedContent);
678
685
  },
679
- onComplete: (content) => {
680
- setPreviewContent(content);
686
+ onComplete: (result) => {
687
+ setPreviewContent(result.content);
688
+ setSessionId(result.sessionId);
681
689
  setIsGenerating(false);
682
690
  },
683
691
  onError: (err) => {
@@ -719,33 +727,128 @@ var Preview = ({
719
727
  editingFilename,
720
728
  isSaving
721
729
  ]);
730
+ const formDataLines = useMemo(
731
+ () => JSON.stringify(formValues, null, 2).split("\n"),
732
+ [formValues]
733
+ );
734
+ const aiContextLines = useMemo(
735
+ () => JSON.stringify(aiContext, null, 2).split("\n"),
736
+ [aiContext]
737
+ );
738
+ const maxDataLines = Math.max(formDataLines.length, aiContextLines.length);
739
+ const handleContinueInClaude = useCallback(
740
+ async (shouldSave) => {
741
+ if (!sessionId) return;
742
+ if (shouldSave && canSave) {
743
+ setIsSaving(true);
744
+ setError(null);
745
+ try {
746
+ await saveDocument({
747
+ config,
748
+ scenario,
749
+ formData: formValues,
750
+ aiContext,
751
+ content: previewContent,
752
+ existingFilename: editingFilename
753
+ });
754
+ } catch (err) {
755
+ setError(err instanceof Error ? err.message : "Failed to save");
756
+ setIsSaving(false);
757
+ setShowContinueDialog(false);
758
+ return;
759
+ }
760
+ setIsSaving(false);
761
+ }
762
+ exit();
763
+ setTimeout(() => {
764
+ const child = spawn("claude", ["--resume", sessionId], {
765
+ stdio: "inherit",
766
+ shell: true
767
+ });
768
+ child.on("close", (code) => {
769
+ process.exit(code ?? 0);
770
+ });
771
+ }, 100);
772
+ },
773
+ [
774
+ sessionId,
775
+ exit,
776
+ canSave,
777
+ config,
778
+ scenario,
779
+ formValues,
780
+ aiContext,
781
+ previewContent,
782
+ editingFilename
783
+ ]
784
+ );
722
785
  useControl({
723
786
  onEscape: () => {
724
- if (showDataPreview) {
787
+ if (showContinueDialog) {
788
+ setShowContinueDialog(false);
789
+ } else if (showDataPreview) {
725
790
  setShowDataPreview(false);
726
791
  } else {
727
792
  onBack();
728
793
  }
729
794
  },
730
- onQuit: onBack,
795
+ onQuit: showContinueDialog ? void 0 : onBack,
731
796
  onSave: () => {
732
- if (!isGenerating && !isSaving) {
797
+ if (!isGenerating && !isSaving && !showContinueDialog) {
733
798
  handleSave();
734
799
  }
735
800
  },
736
801
  onRegenerate: () => {
737
- if (!isGenerating) {
802
+ if (!isGenerating && !showContinueDialog) {
738
803
  handleGenerate();
739
804
  }
740
805
  },
741
806
  onUp: () => {
742
- setScrollOffset((prev) => Math.max(0, prev - 1));
807
+ if (showContinueDialog) return;
808
+ if (showDataPreview) {
809
+ setDataPreviewScrollOffset((prev) => Math.max(0, prev - 1));
810
+ } else {
811
+ setScrollOffset((prev) => Math.max(0, prev - 1));
812
+ }
743
813
  },
744
814
  onDown: () => {
745
- setScrollOffset((prev) => prev + 1);
815
+ if (showContinueDialog) return;
816
+ if (showDataPreview) {
817
+ const maxDataVisible = Math.max(5, maxVisible - 2);
818
+ setDataPreviewScrollOffset(
819
+ (prev) => Math.min(prev + 1, Math.max(0, maxDataLines - maxDataVisible))
820
+ );
821
+ } else {
822
+ setScrollOffset(
823
+ (prev) => Math.min(prev + 1, Math.max(0, lines.length - maxVisible))
824
+ );
825
+ }
826
+ setScrollOffset(
827
+ (prev) => Math.min(prev + 1, Math.max(0, lines.length - maxVisible))
828
+ );
746
829
  },
747
830
  onInfo: () => {
748
- setShowDataPreview((prev) => !prev);
831
+ if (showContinueDialog) return;
832
+ setShowDataPreview((prev) => {
833
+ if (!prev) {
834
+ setDataPreviewScrollOffset(0);
835
+ }
836
+ return !prev;
837
+ });
838
+ },
839
+ onContinue: () => {
840
+ if (canContinue && !showContinueDialog) {
841
+ setShowContinueDialog(true);
842
+ }
843
+ },
844
+ onChar: (char) => {
845
+ if (showContinueDialog) {
846
+ if (char === "y" || char === "Y") {
847
+ handleContinueInClaude(true);
848
+ } else if (char === "n" || char === "N") {
849
+ handleContinueInClaude(false);
850
+ }
851
+ }
749
852
  }
750
853
  });
751
854
  useMount(() => {
@@ -754,10 +857,19 @@ var Preview = ({
754
857
  }
755
858
  });
756
859
  if (showDataPreview) {
757
- const formDataJson = JSON.stringify(formValues, null, 2);
758
- const aiContextJson = JSON.stringify(aiContext, null, 2);
860
+ const maxDataVisible = Math.max(5, maxVisible - 2);
861
+ const visibleFormDataLines = formDataLines.slice(
862
+ dataPreviewScrollOffset,
863
+ dataPreviewScrollOffset + maxDataVisible
864
+ );
865
+ const visibleAiContextLines = aiContextLines.slice(
866
+ dataPreviewScrollOffset,
867
+ dataPreviewScrollOffset + maxDataVisible
868
+ );
869
+ const hasMoreDataAbove = dataPreviewScrollOffset > 0;
870
+ const hasMoreDataBelow = dataPreviewScrollOffset + maxDataVisible < maxDataLines;
759
871
  return /* @__PURE__ */ jsxs4(ScenarioLayout, { controls, status, children: [
760
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "row", gap: 1, children: [
872
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "row", gap: 1, flexGrow: 1, children: [
761
873
  /* @__PURE__ */ jsxs4(
762
874
  Box5,
763
875
  {
@@ -773,7 +885,11 @@ var Preview = ({
773
885
  "formData",
774
886
  " "
775
887
  ] }) }),
776
- formDataJson.split("\n").map((line, index) => /* @__PURE__ */ jsx6(Text4, { children: line || " " }, index))
888
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", overflowY: "hidden", children: [
889
+ hasMoreDataAbove && /* @__PURE__ */ jsx6(Text4, { dimColor: true, children: "\u2191 more" }),
890
+ visibleFormDataLines.map((line, index) => /* @__PURE__ */ jsx6(Text4, { children: line || " " }, dataPreviewScrollOffset + index)),
891
+ hasMoreDataBelow && /* @__PURE__ */ jsx6(Text4, { dimColor: true, children: "\u2193 more" })
892
+ ] })
777
893
  ]
778
894
  }
779
895
  ),
@@ -792,12 +908,19 @@ var Preview = ({
792
908
  "aiContext",
793
909
  " "
794
910
  ] }) }),
795
- aiContextJson.split("\n").map((line, index) => /* @__PURE__ */ jsx6(Text4, { children: line || " " }, index))
911
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", overflowY: "hidden", children: [
912
+ hasMoreDataAbove && /* @__PURE__ */ jsx6(Text4, { dimColor: true, children: "\u2191 more" }),
913
+ visibleAiContextLines.map((line, index) => /* @__PURE__ */ jsx6(Text4, { children: line || " " }, dataPreviewScrollOffset + index)),
914
+ hasMoreDataBelow && /* @__PURE__ */ jsx6(Text4, { dimColor: true, children: "\u2193 more" })
915
+ ] })
796
916
  ]
797
917
  }
798
918
  )
799
919
  ] }),
800
- /* @__PURE__ */ jsx6(Box5, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text4, { dimColor: true, children: "Press i or Esc to close" }) })
920
+ /* @__PURE__ */ jsx6(Box5, { paddingX: 1, children: /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
921
+ "Press i or Esc to close",
922
+ maxDataLines > maxDataVisible && ` | Lines ${dataPreviewScrollOffset + 1}-${Math.min(dataPreviewScrollOffset + maxDataVisible, maxDataLines)} of ${maxDataLines}`
923
+ ] }) })
801
924
  ] });
802
925
  }
803
926
  return /* @__PURE__ */ jsxs4(ScenarioLayout, { controls, status, children: [
@@ -828,7 +951,39 @@ var Preview = ({
828
951
  " of",
829
952
  " ",
830
953
  lines.length
831
- ] }) })
954
+ ] }) }),
955
+ showContinueDialog && /* @__PURE__ */ jsxs4(
956
+ Box5,
957
+ {
958
+ flexDirection: "column",
959
+ borderStyle: "round",
960
+ borderColor: "yellow",
961
+ paddingX: 1,
962
+ paddingY: 0,
963
+ marginTop: 1,
964
+ children: [
965
+ /* @__PURE__ */ jsx6(Box5, { marginTop: -1, marginLeft: 1, children: /* @__PURE__ */ jsxs4(Text4, { backgroundColor: "black", color: "yellow", bold: true, children: [
966
+ " ",
967
+ "Continue in Claude",
968
+ " "
969
+ ] }) }),
970
+ /* @__PURE__ */ jsx6(Text4, { children: "Save document before continuing?" }),
971
+ /* @__PURE__ */ jsx6(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs4(Text4, { children: [
972
+ /* @__PURE__ */ jsx6(Text4, { color: "green", bold: true, children: "[y]" }),
973
+ " ",
974
+ "Save and continue",
975
+ " ",
976
+ /* @__PURE__ */ jsx6(Text4, { color: "blue", bold: true, children: "[n]" }),
977
+ " ",
978
+ "Continue without saving",
979
+ " ",
980
+ /* @__PURE__ */ jsx6(Text4, { dimColor: true, bold: true, children: "[Esc]" }),
981
+ " ",
982
+ "Cancel"
983
+ ] }) })
984
+ ]
985
+ }
986
+ )
832
987
  ] });
833
988
  };
834
989
 
@@ -1760,6 +1915,19 @@ var FieldEditor = ({
1760
1915
  // src/tui/views/ScenarioForm/-internal/FieldSelector/FieldSelector.tsx
1761
1916
  import { Box as Box11, Text as Text12 } from "ink";
1762
1917
  import { useState as useState5 } from "react";
1918
+
1919
+ // src/tui/utils/scroll.ts
1920
+ var adjustScrollOffset = (newIndex, currentOffset, maxVisible) => {
1921
+ if (newIndex < currentOffset) {
1922
+ return newIndex;
1923
+ }
1924
+ if (newIndex >= currentOffset + maxVisible) {
1925
+ return newIndex - maxVisible + 1;
1926
+ }
1927
+ return currentOffset;
1928
+ };
1929
+
1930
+ // src/tui/views/ScenarioForm/-internal/FieldSelector/FieldSelector.tsx
1763
1931
  import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
1764
1932
  var FieldSelector = ({
1765
1933
  flatItems,
@@ -1767,6 +1935,7 @@ var FieldSelector = ({
1767
1935
  isFocused,
1768
1936
  isFirstStep,
1769
1937
  isLastStep,
1938
+ maxHeight,
1770
1939
  onFocusUp,
1771
1940
  onFocusToForm,
1772
1941
  onAddItem,
@@ -1777,18 +1946,34 @@ var FieldSelector = ({
1777
1946
  onBack
1778
1947
  }) => {
1779
1948
  const [selectedIndex, setSelectedIndex] = useState5(0);
1949
+ const [scrollOffset, setScrollOffset] = useState5(0);
1780
1950
  const validIndex = Math.min(selectedIndex, Math.max(0, flatItems.length - 1));
1781
1951
  const currentItem = flatItems[validIndex];
1952
+ const maxVisibleItems = Math.max(3, maxHeight - 3);
1953
+ const visibleItems = flatItems.slice(
1954
+ scrollOffset,
1955
+ scrollOffset + maxVisibleItems
1956
+ );
1957
+ const hasMoreAbove = scrollOffset > 0;
1958
+ const hasMoreBelow = scrollOffset + maxVisibleItems < flatItems.length;
1782
1959
  useControl({
1783
1960
  onUp: () => {
1784
1961
  if (selectedIndex === 0) {
1785
1962
  onFocusUp();
1786
1963
  return;
1787
1964
  }
1788
- setSelectedIndex((prev) => prev - 1);
1965
+ const newIndex = selectedIndex - 1;
1966
+ setSelectedIndex(newIndex);
1967
+ setScrollOffset(
1968
+ (prev) => adjustScrollOffset(newIndex, prev, maxVisibleItems)
1969
+ );
1789
1970
  },
1790
1971
  onDown: () => {
1791
- setSelectedIndex((prev) => prev < flatItems.length - 1 ? prev + 1 : 0);
1972
+ const newIndex = selectedIndex < flatItems.length - 1 ? selectedIndex + 1 : 0;
1973
+ setSelectedIndex(newIndex);
1974
+ setScrollOffset(
1975
+ (prev) => adjustScrollOffset(newIndex, prev, maxVisibleItems)
1976
+ );
1792
1977
  },
1793
1978
  onEnter: () => {
1794
1979
  if (!currentItem) return;
@@ -1840,58 +2025,63 @@ var FieldSelector = ({
1840
2025
  ]
1841
2026
  }
1842
2027
  ) }),
1843
- /* @__PURE__ */ jsx14(Box11, { flexDirection: "column", overflowY: "hidden", children: flatItems.map((item, index) => {
1844
- const isSelected = index === validIndex;
1845
- const valueDisplay = getValueDisplay(item, stepValues);
1846
- const typeIndicator = getFieldTypeIndicator(item);
1847
- const treePrefix = item.treePrefix || "";
1848
- if (item.type === "repeatable-add") {
1849
- return /* @__PURE__ */ jsxs9(Box11, { children: [
1850
- /* @__PURE__ */ jsx14(Text12, { color: "gray", children: treePrefix }),
1851
- /* @__PURE__ */ jsx14(
1852
- Text12,
1853
- {
1854
- color: isSelected && isFocused ? "black" : "green",
1855
- backgroundColor: isSelected && isFocused ? ACCENT_COLOR : void 0,
1856
- children: item.label
1857
- }
1858
- )
1859
- ] }, `add-${item.path}`);
1860
- }
1861
- if (item.type === "repeatable-header") {
2028
+ /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", overflowY: "hidden", children: [
2029
+ hasMoreAbove && /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "\u2191 more" }),
2030
+ visibleItems.map((item, visibleIndex) => {
2031
+ const actualIndex = scrollOffset + visibleIndex;
2032
+ const isSelected = actualIndex === validIndex;
2033
+ const valueDisplay = getValueDisplay(item, stepValues);
2034
+ const typeIndicator = getFieldTypeIndicator(item);
2035
+ const treePrefix = item.treePrefix || "";
2036
+ if (item.type === "repeatable-add") {
2037
+ return /* @__PURE__ */ jsxs9(Box11, { children: [
2038
+ /* @__PURE__ */ jsx14(Text12, { color: "gray", children: treePrefix }),
2039
+ /* @__PURE__ */ jsx14(
2040
+ Text12,
2041
+ {
2042
+ color: isSelected && isFocused ? "black" : "green",
2043
+ backgroundColor: isSelected && isFocused ? ACCENT_COLOR : void 0,
2044
+ children: item.label
2045
+ }
2046
+ )
2047
+ ] }, `add-${item.path}`);
2048
+ }
2049
+ if (item.type === "repeatable-header") {
2050
+ return /* @__PURE__ */ jsxs9(Box11, { children: [
2051
+ /* @__PURE__ */ jsx14(Text12, { color: "gray", children: treePrefix }),
2052
+ /* @__PURE__ */ jsx14(
2053
+ Text12,
2054
+ {
2055
+ color: isSelected && isFocused ? "black" : ACCENT_COLOR,
2056
+ backgroundColor: isSelected && isFocused ? ACCENT_COLOR : void 0,
2057
+ bold: true,
2058
+ children: item.label
2059
+ }
2060
+ )
2061
+ ] }, `header-${item.path}`);
2062
+ }
1862
2063
  return /* @__PURE__ */ jsxs9(Box11, { children: [
1863
2064
  /* @__PURE__ */ jsx14(Text12, { color: "gray", children: treePrefix }),
1864
- /* @__PURE__ */ jsx14(
2065
+ /* @__PURE__ */ jsxs9(
1865
2066
  Text12,
1866
2067
  {
1867
- color: isSelected && isFocused ? "black" : ACCENT_COLOR,
2068
+ color: isSelected && isFocused ? "black" : void 0,
1868
2069
  backgroundColor: isSelected && isFocused ? ACCENT_COLOR : void 0,
1869
- bold: true,
1870
- children: item.label
2070
+ children: [
2071
+ typeIndicator,
2072
+ " ",
2073
+ item.label
2074
+ ]
1871
2075
  }
1872
- )
1873
- ] }, `header-${item.path}`);
1874
- }
1875
- return /* @__PURE__ */ jsxs9(Box11, { children: [
1876
- /* @__PURE__ */ jsx14(Text12, { color: "gray", children: treePrefix }),
1877
- /* @__PURE__ */ jsxs9(
1878
- Text12,
1879
- {
1880
- color: isSelected && isFocused ? "black" : void 0,
1881
- backgroundColor: isSelected && isFocused ? ACCENT_COLOR : void 0,
1882
- children: [
1883
- typeIndicator,
1884
- " ",
1885
- item.label
1886
- ]
1887
- }
1888
- ),
1889
- valueDisplay && /* @__PURE__ */ jsxs9(Text12, { color: "gray", children: [
1890
- " : ",
1891
- valueDisplay
1892
- ] })
1893
- ] }, item.path);
1894
- }) })
2076
+ ),
2077
+ valueDisplay && /* @__PURE__ */ jsxs9(Text12, { color: "gray", children: [
2078
+ " : ",
2079
+ valueDisplay
2080
+ ] })
2081
+ ] }, item.path);
2082
+ }),
2083
+ hasMoreBelow && /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "\u2193 more" })
2084
+ ] })
1895
2085
  ]
1896
2086
  }
1897
2087
  );
@@ -1982,6 +2172,8 @@ var ScenarioForm = ({
1982
2172
  const [focusPanel, setFocusPanel] = useState6("steps");
1983
2173
  const [currentItem, setCurrentItem] = useState6();
1984
2174
  const [validationError, setValidationError] = useState6(null);
2175
+ const { availableHeight } = useTerminalHeight();
2176
+ const fieldSelectorHeight = Math.max(8, availableHeight - 3);
1985
2177
  const currentStep = scenario.steps[stepIndex];
1986
2178
  if (currentStep == null) {
1987
2179
  return /* @__PURE__ */ jsx16(Text14, { color: "red", children: "Error: Step not found" });
@@ -2216,7 +2408,7 @@ var ScenarioForm = ({
2216
2408
  onBack
2217
2409
  }
2218
2410
  ),
2219
- /* @__PURE__ */ jsxs11(Box13, { height: 16, children: [
2411
+ /* @__PURE__ */ jsxs11(Box13, { flexGrow: 1, children: [
2220
2412
  /* @__PURE__ */ jsx16(
2221
2413
  FieldSelector,
2222
2414
  {
@@ -2225,6 +2417,7 @@ var ScenarioForm = ({
2225
2417
  isFocused: focusPanel === "list",
2226
2418
  isFirstStep,
2227
2419
  isLastStep,
2420
+ maxHeight: fieldSelectorHeight,
2228
2421
  onFocusUp: () => setFocusPanel("steps"),
2229
2422
  onFocusToForm: handleFocusToForm,
2230
2423
  onAddItem: handleAddItem,
@@ -2253,7 +2446,7 @@ var ScenarioForm = ({
2253
2446
  // src/tui/views/SelectScenario/SelectScenario.tsx
2254
2447
  import { Box as Box14, Text as Text15 } from "ink";
2255
2448
  import { useCallback as useCallback5, useMemo as useMemo4, useState as useState7 } from "react";
2256
- import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
2449
+ import { Fragment, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
2257
2450
  var SelectScenario = ({
2258
2451
  scenarios,
2259
2452
  onSelectNew,
@@ -2265,6 +2458,10 @@ var SelectScenario = ({
2265
2458
  const [focusPanel, setFocusPanel] = useState7("scenarios");
2266
2459
  const [documents, setDocuments] = useState7([]);
2267
2460
  const [isLoading, setIsLoading] = useState7(false);
2461
+ const [scenarioScrollOffset, setScenarioScrollOffset] = useState7(0);
2462
+ const [actionScrollOffset, setActionScrollOffset] = useState7(0);
2463
+ const { availableHeight } = useTerminalHeight();
2464
+ const maxVisibleItems = Math.max(3, availableHeight - 3);
2268
2465
  const currentScenario = scenarios[scenarioIndex];
2269
2466
  const actionItems = useMemo4(
2270
2467
  () => [
@@ -2273,6 +2470,14 @@ var SelectScenario = ({
2273
2470
  ],
2274
2471
  [documents]
2275
2472
  );
2473
+ const visibleScenarios = scenarios.slice(
2474
+ scenarioScrollOffset,
2475
+ scenarioScrollOffset + maxVisibleItems
2476
+ );
2477
+ const visibleActionItems = actionItems.slice(
2478
+ actionScrollOffset,
2479
+ actionScrollOffset + maxVisibleItems
2480
+ );
2276
2481
  const controls = useMemo4(() => {
2277
2482
  if (focusPanel === "scenarios") {
2278
2483
  return [
@@ -2308,11 +2513,17 @@ var SelectScenario = ({
2308
2513
  onUp: () => {
2309
2514
  const newIndex = scenarioIndex > 0 ? scenarioIndex - 1 : scenarios.length - 1;
2310
2515
  setScenarioIndex(newIndex);
2516
+ setScenarioScrollOffset(
2517
+ (prev) => adjustScrollOffset(newIndex, prev, maxVisibleItems)
2518
+ );
2311
2519
  loadDocuments(newIndex);
2312
2520
  },
2313
2521
  onDown: () => {
2314
2522
  const newIndex = scenarioIndex < scenarios.length - 1 ? scenarioIndex + 1 : 0;
2315
2523
  setScenarioIndex(newIndex);
2524
+ setScenarioScrollOffset(
2525
+ (prev) => adjustScrollOffset(newIndex, prev, maxVisibleItems)
2526
+ );
2316
2527
  loadDocuments(newIndex);
2317
2528
  },
2318
2529
  onRight: () => {
@@ -2326,10 +2537,18 @@ var SelectScenario = ({
2326
2537
  });
2327
2538
  useControl({
2328
2539
  onUp: () => {
2329
- setActionIndex((prev) => prev > 0 ? prev - 1 : actionItems.length - 1);
2540
+ const newIndex = actionIndex > 0 ? actionIndex - 1 : actionItems.length - 1;
2541
+ setActionIndex(newIndex);
2542
+ setActionScrollOffset(
2543
+ (prev) => adjustScrollOffset(newIndex, prev, maxVisibleItems)
2544
+ );
2330
2545
  },
2331
2546
  onDown: () => {
2332
- setActionIndex((prev) => prev < actionItems.length - 1 ? prev + 1 : 0);
2547
+ const newIndex = actionIndex < actionItems.length - 1 ? actionIndex + 1 : 0;
2548
+ setActionIndex(newIndex);
2549
+ setActionScrollOffset(
2550
+ (prev) => adjustScrollOffset(newIndex, prev, maxVisibleItems)
2551
+ );
2333
2552
  },
2334
2553
  onEnter: () => {
2335
2554
  const item = actionItems[actionIndex];
@@ -2353,7 +2572,11 @@ var SelectScenario = ({
2353
2572
  useMount(() => {
2354
2573
  loadDocuments(0);
2355
2574
  });
2356
- return /* @__PURE__ */ jsx17(CommonLayout, { controls, children: /* @__PURE__ */ jsxs12(Box14, { children: [
2575
+ const hasMoreScenariosAbove = scenarioScrollOffset > 0;
2576
+ const hasMoreScenariosBelow = scenarioScrollOffset + maxVisibleItems < scenarios.length;
2577
+ const hasMoreActionsAbove = actionScrollOffset > 0;
2578
+ const hasMoreActionsBelow = actionScrollOffset + maxVisibleItems < actionItems.length;
2579
+ return /* @__PURE__ */ jsx17(CommonLayout, { controls, children: /* @__PURE__ */ jsxs12(Box14, { flexGrow: 1, children: [
2357
2580
  /* @__PURE__ */ jsxs12(
2358
2581
  Box14,
2359
2582
  {
@@ -2376,22 +2599,27 @@ var SelectScenario = ({
2376
2599
  ]
2377
2600
  }
2378
2601
  ) }),
2379
- /* @__PURE__ */ jsx17(Box14, { flexDirection: "column", children: scenarios.map((scenario, index) => {
2380
- const isSelected = index === scenarioIndex;
2381
- const isFocused = focusPanel === "scenarios";
2382
- return /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsxs12(
2383
- Text15,
2384
- {
2385
- color: isSelected && isFocused ? "black" : void 0,
2386
- backgroundColor: isSelected ? isFocused ? ACCENT_COLOR : "gray" : void 0,
2387
- children: [
2388
- " ",
2389
- scenario.name,
2390
- " "
2391
- ]
2392
- }
2393
- ) }, scenario.id);
2394
- }) })
2602
+ /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", overflowY: "hidden", children: [
2603
+ hasMoreScenariosAbove && /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "\u2191 more" }),
2604
+ visibleScenarios.map((scenario, visibleIndex) => {
2605
+ const actualIndex = scenarioScrollOffset + visibleIndex;
2606
+ const isSelected = actualIndex === scenarioIndex;
2607
+ const isFocused = focusPanel === "scenarios";
2608
+ return /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsxs12(
2609
+ Text15,
2610
+ {
2611
+ color: isSelected && isFocused ? "black" : void 0,
2612
+ backgroundColor: isSelected ? isFocused ? ACCENT_COLOR : "gray" : void 0,
2613
+ children: [
2614
+ " ",
2615
+ scenario.name,
2616
+ " "
2617
+ ]
2618
+ }
2619
+ ) }, scenario.id);
2620
+ }),
2621
+ hasMoreScenariosBelow && /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "\u2193 more" })
2622
+ ] })
2395
2623
  ]
2396
2624
  }
2397
2625
  ),
@@ -2418,23 +2646,28 @@ var SelectScenario = ({
2418
2646
  ]
2419
2647
  }
2420
2648
  ) }),
2421
- /* @__PURE__ */ jsx17(Box14, { flexDirection: "column", children: isLoading ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Loading..." }) : actionItems.map((item, index) => {
2422
- const isSelected = index === actionIndex;
2423
- const isFocused = focusPanel === "actions";
2424
- const label = item.type === "new" ? item.label : item.doc.filename;
2425
- return /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsxs12(
2426
- Text15,
2427
- {
2428
- color: isSelected && isFocused ? "black" : item.type === "new" ? "green" : void 0,
2429
- backgroundColor: isSelected && isFocused ? ACCENT_COLOR : void 0,
2430
- children: [
2431
- " ",
2432
- label,
2433
- " "
2434
- ]
2435
- }
2436
- ) }, item.type === "new" ? "new" : item.doc.filename);
2437
- }) })
2649
+ /* @__PURE__ */ jsx17(Box14, { flexDirection: "column", overflowY: "hidden", children: isLoading ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Loading..." }) : /* @__PURE__ */ jsxs12(Fragment, { children: [
2650
+ hasMoreActionsAbove && /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "\u2191 more" }),
2651
+ visibleActionItems.map((item, visibleIndex) => {
2652
+ const actualIndex = actionScrollOffset + visibleIndex;
2653
+ const isSelected = actualIndex === actionIndex;
2654
+ const isFocused = focusPanel === "actions";
2655
+ const label = item.type === "new" ? item.label : item.doc.filename;
2656
+ return /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsxs12(
2657
+ Text15,
2658
+ {
2659
+ color: isSelected && isFocused ? "black" : item.type === "new" ? "green" : void 0,
2660
+ backgroundColor: isSelected && isFocused ? ACCENT_COLOR : void 0,
2661
+ children: [
2662
+ " ",
2663
+ label,
2664
+ " "
2665
+ ]
2666
+ }
2667
+ ) }, item.type === "new" ? "new" : item.doc.filename);
2668
+ }),
2669
+ hasMoreActionsBelow && /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "\u2193 more" })
2670
+ ] }) })
2438
2671
  ]
2439
2672
  }
2440
2673
  )
@@ -2444,7 +2677,7 @@ var SelectScenario = ({
2444
2677
  // src/tui/App.tsx
2445
2678
  import { jsx as jsx18 } from "react/jsx-runtime";
2446
2679
  var App = ({ config, initialScenarioId }) => {
2447
- const { exit } = useApp();
2680
+ const { exit } = useApp2();
2448
2681
  const [appState, setAppState] = useState8(() => {
2449
2682
  if (initialScenarioId != null) {
2450
2683
  const scenario2 = config.scenarios.find((s) => s.id === initialScenarioId);