/**
* wishlist-picker.tsx — Full-screen Ink component for browsing and promoting
* wishlist items.
*
* Replaces the neo-blessed WishlistPicker class from tui-wishlist.ts.
*
* Layout:
* ┌───────────────────────────────────────────────────────┐
* │ wombo-combo Wishlist │ 5 items │
* ├──────────────────────────┬────────────────────────────┤
* │ • Fix login timeout... │ Description: Fix login... │
* │ • Add dark mode support │ │
* │ • Refactor DB layer │ Tags: auth, ux │
* │ │ Created: 2025-01-15 │
* ├──────────────────────────┴────────────────────────────┤
* │ E:errand P:quest G:genesis D:delete Esc:back Q:quit│
* └───────────────────────────────────────────────────────┘
*
* Keybinds:
* E — promote selected item to errand
* P — promote selected item to quest
* G — promote selected item to genesis
* D / Del — delete selected item
* S-Up/Down — move selected item up/down in order
* Up/Down — navigate items
* Esc — go back
* Q — quit
*/
import React from "react";
import { Box, Text, useInput } from "ink";
import type { WishlistItem } from "../lib/wishlist-store";
import { useWishlistStore } from "./use-wishlist-store";
import { formatDate, truncateText } from "./wishlist-helpers";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface WishlistPickerProps {
/** Project root directory. */
projectRoot: string;
/** Called when the user promotes an item to errand (E key). */
onPromoteErrand: (item: WishlistItem) => void;
/** Called when the user promotes an item to genesis (G key). */
onPromoteGenesis: (item: WishlistItem) => void;
/** Called when the user promotes an item to quest (P key). */
onPromoteQuest: (item: WishlistItem) => void;
/** Called when the user presses Esc to go back. */
onBack: () => void;
/** Called when the user presses Q to quit. */
onQuit: () => void;
}
// ---------------------------------------------------------------------------
// Sub-components
// ---------------------------------------------------------------------------
/**
* Header — displays app name, "Wishlist" label, and item count.
*/
function Header({ itemCount }: { itemCount: number }): React.ReactElement {
return (
wombo-combo
Wishlist
|
{itemCount}
item{itemCount !== 1 ? "s" : ""}
Promote items to errands, quests, or genesis
);
}
/**
* ItemList — left pane showing all wishlist items.
*/
function ItemList({
items,
selectedIndex,
}: {
items: WishlistItem[];
selectedIndex: number;
}): React.ReactElement {
if (items.length === 0) {
return (
No wishlist items
);
}
return (
{items.map((item, i) => {
const isSelected = i === selectedIndex;
const pos = String(i + 1).padStart(2, " ");
const text = truncateText(item.text, 38);
const date = formatDate(item.created_at);
const tagsBadge =
item.tags.length > 0 ? ` [${item.tags.join(", ")}]` : "";
return (
{isSelected ? (
{` ${pos}. \u2022 ${text}${tagsBadge} ${date} `}
) : (
{` ${pos}. `}
{"\u2022"}
{` ${text}`}
{tagsBadge}
{` ${date}`}
)}
);
})}
);
}
/**
* DetailPane — right pane showing details of the selected item.
*/
function DetailPane({
item,
index,
totalCount,
}: {
item: WishlistItem | null;
index: number;
totalCount: number;
}): React.ReactElement {
if (!item) {
return (
No wishlist items
Your wishlist is empty.
Add ideas from the CLI:
{'woco wishlist add "your idea"'}
Or from the Task Browser:
Press W to open the wishlist overlay,
then press A to add an item.
Press Esc to go back.
);
}
return (
{`Item ${index + 1}/${totalCount}`}
{/* Description */}
Description:
{item.text}
{/* Tags */}
{item.tags.length > 0 && (
<>
Tags:
{" "}
{item.tags.map((tag, i) => (
{i > 0 && {" "}}
{tag}
))}
>
)}
{/* Created */}
Created:
{" "}
{item.created_at.slice(0, 10)} {item.created_at.slice(11, 19)}
{/* ID */}
ID:
{" "}{item.id}
{/* Promote hints */}
Promote:
{" "}
E
{" \u2192 Create errand from this item"}
{" "}
P
{" \u2192 Create quest (goal pre-filled)"}
{" "}
G
{" \u2192 Use as genesis vision"}
);
}
/**
* StatusBar — bottom bar with keybind hints and selected item preview.
*/
function StatusBar({
selectedItem,
}: {
selectedItem: WishlistItem | null;
}): React.ReactElement {
return (
Keys:
E
errand
P
quest
G
genesis
D
delete
S-{"\u2191"}/{"\u2193"}
reorder
Esc
back
Q
quit
{selectedItem ? (
{truncateText(selectedItem.text, 60)}
) : (
{'Add items with: woco wishlist add "your idea"'}
)}
);
}
// ---------------------------------------------------------------------------
// Main Component
// ---------------------------------------------------------------------------
/**
* WishlistPicker — full-screen Ink component for browsing and promoting
* wishlist items.
*/
export function WishlistPicker({
projectRoot,
onPromoteErrand,
onPromoteGenesis,
onPromoteQuest,
onBack,
onQuit,
}: WishlistPickerProps): React.ReactElement {
const store = useWishlistStore({ projectRoot });
useInput((input, key) => {
// Navigation
if (key.downArrow && !key.shift) {
store.selectNext();
return;
}
if (key.upArrow && !key.shift) {
store.selectPrev();
return;
}
// Shift+Up/Down — reorder
if (key.upArrow && key.shift) {
store.moveSelectedUp();
return;
}
if (key.downArrow && key.shift) {
store.moveSelectedDown();
return;
}
// E — promote to errand
if (input === "e") {
if (store.selectedItem) {
onPromoteErrand(store.selectedItem);
}
return;
}
// G — promote to genesis
if (input === "g") {
if (store.selectedItem) {
onPromoteGenesis(store.selectedItem);
}
return;
}
// P — promote to quest
if (input === "p") {
if (store.selectedItem) {
onPromoteQuest(store.selectedItem);
}
return;
}
// D / Delete — delete selected item
if (input === "d" || key.delete) {
store.deleteSelected();
return;
}
// Q — quit
if (input === "q") {
onQuit();
return;
}
// Escape — go back
if (key.escape) {
onBack();
return;
}
});
return (
{/* Header */}
{/* Main content: list + detail */}
{/* Status bar */}
);
}