diff --git a/packages/prompts.chat/src/cli/components/PromptList.tsx b/packages/prompts.chat/src/cli/components/PromptList.tsx
index dfea156e599..82cc768dc61 100644
--- a/packages/prompts.chat/src/cli/components/PromptList.tsx
+++ b/packages/prompts.chat/src/cli/components/PromptList.tsx
@@ -19,6 +19,14 @@ interface PromptListProps {
type ViewMode = 'list' | 'categories';
+/**
+ * Renders the interactive CLI prompt browser with searchable prompt and category views,
+ * keyboard navigation, and pagination support.
+ *
+ * @param props - Component props including selection handlers, search state, pagination,
+ * and category filters used to drive prompt list interactions.
+ * @returns The PromptList Ink UI tree for list and category browsing modes.
+ */
export function PromptList({
onSelect,
onQuit,
@@ -52,6 +60,9 @@ export function PromptList({
const headerLines = 3;
const footerLines = 2;
const listHeight = Math.max(terminalHeight - headerLines - footerLines, 5);
+ // Category view: header + "All Categories" + footer + margins = 4 reserved
+ const categoryReservedLines = 4 + (isSearchingCategories ? 1 : 0);
+ const categoryListHeight = Math.max(terminalHeight - categoryReservedLines, 5);
const perPage = listHeight;
useEffect(() => {
@@ -214,6 +225,15 @@ export function PromptList({
? categories.filter(c => c.name.toLowerCase().includes(categorySearchQuery.toLowerCase()))
: categories;
+ // Category viewport scrolling - calculate which items to show
+ const isCategorySearchActive = Boolean(categorySearchQuery);
+ const categoryTotalItems = categorySearchQuery ? filteredCategories.length : filteredCategories.length + 1;
+ const categoryScrollOffset = Math.max(0, Math.min(categoryIndex - categoryListHeight + 2, Math.max(0, categoryTotalItems - categoryListHeight)));
+ const sliceStart = isCategorySearchActive ? categoryScrollOffset : Math.max(0, categoryScrollOffset - 1);
+ const sliceEnd = isCategorySearchActive
+ ? categoryScrollOffset + categoryListHeight
+ : (categoryScrollOffset === 0 ? categoryListHeight - 1 : categoryScrollOffset - 1 + categoryListHeight);
+
const maxTitleLength = terminalWidth - 30;
if (error) {
@@ -288,7 +308,7 @@ export function PromptList({
) : (
<>
- {!categorySearchQuery && (
+ {!categorySearchQuery && categoryScrollOffset === 0 && (
{categoryIndex === 0 ? '❯ ' : ' '}
@@ -298,8 +318,11 @@ export function PromptList({
)}
- {filteredCategories.slice(0, listHeight - 1).map((cat, index) => {
- const adjustedIndex = categorySearchQuery ? index : index + 1;
+ {filteredCategories.slice(sliceStart, sliceEnd).map((cat, index) => {
+ const actualIndex = (isCategorySearchActive ? sliceStart : categoryScrollOffset) + index;
+ // When All Categories is visible (scrollOffset=0), categories are at positions 1+ so add 1
+ // When scrolled out, positions match actualIndex directly
+ const adjustedIndex = isCategorySearchActive || categoryScrollOffset > 0 ? actualIndex : actualIndex + 1;
return (