| tokens |
|
|---|
loop-task is a command-first terminal application built on Ink 7 + React 19. The design rejects the "web app in a terminal" metaphor in favor of CLI-native patterns: a persistent command input at the bottom, keyboard-only navigation, and inline information density.
Inspired by lazygit, Claude Code, and gh, the UI prioritizes:
- Always-focused input at the bottom where all actions begin
- Tab-based context switching (Loops / Tasks / Projects) with distinct accent colors
- Two-panel layout (list left, detail right) with Tab cycling between them
- Floating elements (toasts, dropdowns, modals) that overlay content without pushing layout
- Minimal chrome - one status line header, one hint line footer, no decorative borders
The palette is intentionally limited to 4 primary colors + 4 semantic + grays. No decorative colors exist outside this set.
- Brand (amber
#fbbf24) is the generic accent for UI elements that don't belong to a specific entity: app name, input bar, modal borders, command palette highlights. - Loop (blue
#38bdf8), Task (purple#a78bfa), Project (green#34d399) are entity-specific. When the active tab changes, the panel borders, filter labels, and selected-row indicators all shift to the active entity's color viatabAccentColor(). - Semantic colors (success/warning/danger/idle) are reserved for status communication - never decorative.
- Grays form a 6-tone background scale and 4-tone text scale. Selected rows use
bg.activewithtext.inversefor maximum contrast.
The board view is a vertical stack:
- Header (3 rows): App name + tagline, daemon status + loop counts + tab bar, separator line
- Content area (flexGrow, full terminal width): Wide terminals (
>=110columns) use a LeftPanel (60%) + RightPanel (40%) row. Compact terminals (70-109columns) stack full-width panels. Minimal terminals (<70columns) show the full-width navigator only. DebugPanel follows the active layout direction. - Command input (6 rows): Bordered box with accent bar, input line with inline placeholder, and hint bar
Panels receive focus via the isFocused prop (not Ink's native useFocus()). When focused:
- Panel border shifts to the active tab's accent color
- Arrow keys navigate within that panel's list
- Tab/Shift+Tab toggles between left and right panels (never into header or input)
When any modal is open, isFocused is false for both panels - blocking all arrow key input from reaching the lists behind the modal.
The bottom input is the heart of the interaction model. It has three modes, each with a distinct accent bar color:
- Command mode (amber
│): Fuzzy autocomplete viaink-comboboxheadless hooks. Dropdown floats above the input withposition="absolute". Placeholder: "Start typing an action or say help..." - Confirm mode (red
│): Destructive commands transition here. Shows the confirmation prompt, offers yes/cancel options. No text input - only navigation. - Search mode (green
│): Typing "search" + Enter transitions here. Filters the active list in real-time. Placeholder: "Write something to filter about..."
The input ignores all Ctrl key combos (if (key.ctrl) return) - these are handled by the global useInput in App.tsx for shortcuts like Ctrl+N (new), Ctrl+E (edit), Ctrl+D (delete), Ctrl+P (commands browser), Ctrl+Enter (panel action), Ctrl+Arrow (tab switching).
Only single printable ASCII characters (input.length === 1 && input >= " " && input <= "~") are inserted as text - this prevents multi-char sequences like \r\n (VS Code Ctrl+Enter) from injecting newlines.
- Create:
WizardForm- one field per screen with breadcrumb progress. Ctrl+S skips optional fields. Esc goes back one step (or cancels from step 1). UsesFocusableInputfor text steps and arrow-key select for suggestion steps. - Edit:
PatchEditForm- read-only labeled table of all current values. The command input suggestschange <field>commands. Selecting one activates that row's inline input. Pending changes shown with a●indicator and count in the header.savecommits,canceldiscards.
- Toasts:
position="absolute" bottom={0} right={0}- float over content in the bottom-right corner. Round border, elevated background, auto-dismiss after 3.5s. Max 4 visible. - Command dropdown:
position="absolute" bottom={3}- floats above the input row without pushing layout. Selected row usesbg.activehighlight. - Modals:
position="absolute"centered overlays withborderStyle="round". Escape to close. CommandsBrowserModal is centered with search + grouped command list. - DebugPanel: Optional 22% right column (toggled via "debug" command). Shows last 12 keypresses with char codes and modifier flags. Warning-yellow border.
The hint bar at the bottom of the command input shows contextual shortcuts:
- Left side:
······· esc cancel(dots as visual separator) - Right side:
tab panels ctrl+p commands(command mode),enter confirm(confirm mode),enter apply(search mode)
Key hints use bold for the key label and muted for the action description.
Loop status is communicated via color:
- Running loops show a green
ink-spinner(dots animation) inline in the list row - Status text in the list uses semantic colors (running=green, waiting=muted, paused=yellow, idle=orange, stopped=red)
- Run history sparkline uses blue bars with success/danger streak indicators
- Run exit codes use checkmark (
✓green) or cross (✗red) icons
- No mouse - Ink has no mouse API. All interaction is keyboard.
- No CSS animations - Terminal rendering. The only motion is
ink-spinner. - Terminal-width dependent - Responsive breakpoints: wide (
>=110columns) uses side-by-side panels; compact (70-109) stacks full-width navigator and inspector panels; minimal (<70) shows only the full-width navigator. - Color compatibility - Uses 24-bit ANSI true color. Falls back gracefully on terminals without true color support via Ink's color handling.