8.6 KiB
pikl-menu: Development Plan
Implementation order, phase definitions, and the running list of ideas. The DESIGN.md is the source of truth for what pikl-menu is. This doc is the plan for how and when we build it.
Principles
- Get a working vertical slice before going wide on features
- pikl-core stays rendering-agnostic. No GUI or TUI deps in the library.
- Every feature gets tests in pikl-core before frontend work
- Ship something usable early, iterate from real usage
- Don't optimize until there's a reason to
Phase 1: Core Loop (TUI)
The minimum thing that works end-to-end.
Deliverables:
- pikl-core: Item store (accepts JSON lines on stdin, plain text fallback)
- pikl-core: Basic fuzzy filter on label
- pikl-core: Single selection, Enter to confirm, Escape to cancel
- pikl-core: Event bus with on-select and on-cancel hooks (shell commands)
- pikl-tui: ratatui list renderer
- pikl (CLI): Reads stdin, opens TUI, emits selected item as JSON on stdout
- Exit codes: 0 = selected, 1 = cancelled, 2 = error
- CI: Strict clippy, fmt, tests on Linux + macOS
Done when: ls | pikl works and prints the selected
item.
Phase 1.5: Action-fd (Headless Mode)
Scriptable, non-interactive mode for integration tests and
automation. Small enough to slot in before phase 2. It's
about 100 lines of new code plus the binary orchestration
for show-ui.
Deliverables:
--action-fd <N>: read action script from file descriptor N- Action protocol: plain text, one action per line (filter, move-up/down, confirm, cancel, etc.)
show-ui/show-tui/show-guiactions for headless-to-interactive handoff- Upfront validation pass: reject unknown actions, malformed args, actions after show-ui
--stdin-timeout <seconds>: default 30s in action-fd mode, 0 in interactive mode- Default viewport height of 50 in headless mode
- Binary integration tests: spawn pikl with piped stdin + action-fd, assert stdout
Done when: echo -e "hello\nworld" | pikl --action-fd 3
with confirm on fd 3 prints "hello" to stdout.
Phase 2: Navigation & Filtering
Make it feel like home for a vim user. The filter system is the real star here: strategy prefixes, pipeline chaining, incremental caching.
Implementation order:
- Mode system in core (Insert/Normal, Ctrl+N/Ctrl+E to switch)
- Normal mode vim nav (j/k/gg/G/Ctrl+D/U/Ctrl+F/B)
/in normal mode enters insert mode with filter focused- Filter strategy engine (prefix parsing:
',!,!',/pattern/,!/pattern/) fancy-regexintegration for the regex strategy- Filter pipeline (
|chaining between stages, incremental caching)
Deliverables:
- Insert mode / normal mode (Ctrl+N to normal, Ctrl+E to insert)
- Escape always cancels and exits, in any mode
- Normal mode: j/k, gg, G, Ctrl+D/U, Ctrl+F/B
/in normal mode enters filter (insert) mode--start-mode normalflag- Filter strategies via inline prefixes: fuzzy (default),
exact (
'term), inverse (!term,!'term), regex (/pattern/,!/pattern/) - fancy-regex integration (unlimited capture groups, lookaround)
- Filter pipeline with
|: each stage narrows the previous stage's output - Incremental filter caching: each stage caches item indices, only recomputes from the edited stage forward
- Arrow keys to navigate within the filter text and edit earlier pipeline segments
\|escapes a literal pipe in the query- Case rules: fuzzy = smart case, exact =
case-insensitive, regex = case-sensitive (use
(?i)for insensitive)
Deferred to later:
- H/M/L (viewport-relative jumps): nice-to-have, not essential
- Filter syntax highlighting in the input field: deferred to theming work
Done when: You can navigate and filter a large list
with vim muscle memory.
'log | !temp | /[0-9]+/ works as a pipeline.
Phase 3: Structured I/O & Hooks
The structured data pipeline.
Deliverables:
- JSON line input parsing (label, sublabel, meta, icon, group)
- JSON output with action context
- Full hook lifecycle: on-open, on-close, on-hover, on-select, on-cancel, on-filter
- Hook debouncing
- Bidirectional hooks (hook stdout modifies menu state)
--formattemplate strings for display- Field filters (
meta.res:3840) - Filter scoping (
--filter-fields)
Done when: The wallpaper picker use case works entirely through hooks and structured I/O.
Phase 4: Multi-Select & Registers
Power selection features.
Deliverables:
- Space to toggle select, V for visual line mode
- Multi-select output (multiple JSON lines)
- Named registers (
"athrough"z) - Marks (
m{a-z},'{a-z}) --multiflag to enable multi-select mode
Done when: You can select multiple items, store them in registers, and get them all in the output.
Phase 5: Table Mode & CSV
Columnar data display.
Deliverables:
--columnsflag for table layout- Auto-alignment
- Column sorting keybinds
--input-format csvand--input-format tsv- Column-specific filtering
Done when: ps aux | pikl --input-format tsv --columns 1,10,2
renders a clean table.
Phase 6: Sessions & IPC
Persistence and external control.
Deliverables:
--session namefor state persistence- Session state: filter, scroll position, selections, marks, registers
- Session history log file
- Unix socket IPC while running
- IPC commands: push/remove/update items, set filter, read selection, close
- Protocol: newline-delimited JSON
Done when: You can close and reopen a session and find your state intact. External scripts can push items into a running pikl instance.
Phase 7: Streaming & Watched Sources
Live, dynamic menus.
Deliverables:
- Async/streaming stdin (items arrive over time, list updates progressively)
- Streaming output (on-hover events emitted to stdout)
--watch pathfor file/directory watching--watch-extensionsfilter- notify crate integration (inotify on Linux, FSEvents on macOS)
Done when: find / -name '*.log' | pikl populates
progressively. A watched directory updates the list live.
Phase 8: GUI Frontend (Wayland + X11)
The graphical overlay.
Deliverables:
- pikl-gui crate with iced
- Wayland: layer-shell overlay via iced_layershell
- X11: override-redirect window or EWMH hints
- Auto-detection of Wayland vs X11 vs fallback to TUI
--mode gui/--mode tuioverride- Theming (TOML-based, a few built-in themes)
- Image/icon rendering in item list
- Preview pane (text + images)
Done when: pikl looks and feels like a native Wayland overlay with keyboard-first interaction.
Phase 9: Drill-Down & Groups
Hierarchical navigation.
Deliverables:
on-selecthook returning{"action": "replace", "items": [...]}for drill-down- Backspace /
hin normal mode to go back - Navigation history stack
- Item groups with headers
- Tab to cycle groups
zato collapse/expand groups
Done when: A file browser built on pikl-menu can navigate directories without spawning new processes.
Open Design Notes
- Viewport cursor preservation on filter change. When the filter narrows and the highlighted item is still in the result set, keep it highlighted (like fzf/rofi). When it's gone, fall back to the top. Needs the filter to report whether a specific original index survived the query, or the viewport to do a lookup after each filter pass.
Future Ideas (Unscheduled)
These are things we've talked about or thought of. No commitment, no order.
- Lua scripting frontend (mlua + LuaJIT): stateful/ conditional automation, natural follow-on to action-fd and IPC. Lua runtime is just another frontend pushing Actions and subscribing to MenuEvents. Deliberately deferred until after IPC (phase 6) so the event/action API is battle-tested before exposing it to a scripting language. See "Scripting Ladder" in DESIGN.md.
- WASM plugin system for custom filter strategies
pcre2feature flag for JIT-compiled regex- Frecency sorting (track selection frequency, boost common picks)
- Manifest file format for reusable pikl configurations
- Sixel / kitty graphics protocol support in TUI mode
- Custom action keybinds (Ctrl+1 through Ctrl+9) with distinct exit codes
- Accessibility / screen reader support
- Sections as a first-class concept separate from groups
- Network input sources (HTTP, WebSocket)
- Shell completion generation (bash, zsh, fish)
- Man page generation
- Homebrew formula + AUR PKGBUILD
- App launcher use case: global hotkey opens pikl as GUI
overlay, fuzzy-filters PATH binaries, launches selection
(optionally into a tmux session). Needs GUI frontend
(phase 8) and frecency sorting.
See
docs/use-cases/app-launcher.md.