.cursor/skills/ink-tui/references/PRIMITIVES.md
Custom layout primitives for the PostHog Setup Wizard TUI. These replace raw Ink/@inkjs/ui usage with opinionated, styled components that enforce visual consistency.
Import: All primitives are barrel-exported from src/ui/tui/primitives/index.ts.
import { ScreenContainer, TabContainer, PickerMenu, ... } from '../primitives/index.js';
Styles: Shared constants live in src/ui/tui/styles.ts — read that file for current Colors, Icons, HAlign, VAlign values.
Each primitive's props interface is defined in its source file. Read the file for the current API.
src/ui/tui/primitives/ScreenContainer.tsx
Top-level app shell. Renders TitleBar, routes between screens via store.currentScreen (router-driven), and plays a horizontal wipe transition on screen changes. Wraps each screen in ScreenErrorBoundary.
src/ui/tui/primitives/TabContainer.tsx
Self-contained tabbed interface with status bar. Manages its own active tab state. Arrow keys switch tabs.
Layout (top to bottom):
flexGrow)Tabs array can be built conditionally — see RunScreen.tsx for an example of a tab that only appears when data is available.
src/ui/tui/primitives/PickerMenu.tsx
Single and multi select. Fully custom renderers — does NOT use @inkjs/ui Select/MultiSelect.
▸ triangle cursor on focused item, enter selectsmode="multi"): ◻/◼ toggles with space, enter submitssrc/ui/tui/primitives/ConfirmationInput.tsx
Continue/cancel prompt with two bordered button boxes. Left/right arrows switch focus, enter activates focused, escape always cancels.
src/ui/tui/primitives/DissolveTransition.tsx
Horizontal wipe with split-flap/digital rain texture. Used internally by ScreenContainer. When transitionKey changes, a band of shade characters sweeps across covering old content, then reveals new content.
src/ui/tui/primitives/ProgressList.tsx
Task checklist with status icons and progress counter. Shows a LoadingBox placeholder when items array is empty.
◼ green = completed, ▶ cyan = in-progress, ◻ gray = pendingactiveForm text when in-progress (replaces label)src/ui/tui/primitives/EventPlanViewer.tsx
Pure render component for planned analytics events. Takes an events array prop and renders each event name (bold) with description (dim). Used in RunScreen's conditional "Event plan" tab.
src/ui/tui/primitives/SplitView.tsx
Two-pane horizontal layout (50/50 split).
src/ui/tui/primitives/CardLayout.tsx
Aligns a single child within available space using flexbox alignment (HAlign, VAlign).
src/ui/tui/primitives/LogViewer.tsx
Real-time log file tail. Watches a file with fs.watch and displays the latest lines that fit in the available terminal height.
src/ui/tui/primitives/LoadingBox.tsx
Spinner with message. Uses @inkjs/ui Spinner.
src/ui/tui/primitives/Divider.tsx
Responsive horizontal rule. Uses measureElement to measure its parent's width, then fills with a repeating character (─ by default). Props: dimColor (default true), char (default '─').
Ink provides measureElement(ref) to get the pixel-equivalent { width, height } of a rendered element. Use it with a useRef + useEffect to build components that adapt to their container size:
import { Box, Text, measureElement } from 'ink';
import { useRef, useState, useEffect } from 'react';
const ref = useRef(null);
const [width, setWidth] = useState(0);
useEffect(() => {
if (ref.current) {
const { width: measured } = measureElement(ref.current);
setWidth(measured);
}
}, []);
<Box ref={ref} width="100%">
<Text>{'─'.repeat(width)}</Text>
</Box>
See Divider.tsx for a working example. For terminal resize reactivity, combine with the useStdoutDimensions hook from src/ui/tui/hooks/useStdoutDimensions.ts.
borderStyle="single" (not "round") for cross-terminal compatibilityColors.accent for highlights, active states, prompt headersdimColor on unfocused/inactive itemsColors.muted for status text, inactive tabs, borders<Text> (Ink requirement)Colors.accent) can bleed in some terminals. If a component's text unexpectedly inherits color, set an explicit color on its <Text> elements.