cli: TUI component wishlist — rich table, streaming, parallel tasks, tree, stub upgrades #14

Closed
opened 2026-02-22 19:27:18 +00:00 by Virgil · 1 comment
Member

Overview

pkg/cli has a solid foundation (Table, Spinner, ProgressBar, Viewport, TextInput, InteractiveList, Layout, TUI escape hatch). This issue tracks the next wave of components that consuming packages (LEM, go-ai, core dev) need.

Prioritised by real-world usage — every item here comes from hitting a gap during actual development.


1. Rich Table (lipgloss borders + cell styling)

Current: Table in styles.go does aligned columns with ANSI header styling. No borders, no box drawing, no per-row/per-cell colouring.

Want: Optional bordered table using lipgloss table rendering. Per-cell style functions (e.g. red for "unavailable", green for "healthy"). Responsive column widths (truncate to terminal width).

Consumers: core dev health, core dev work --status, ml_backends, ml_status, any listing command.

API sketch:

t := cli.NewTable("REPO", "STATUS", "BRANCH").
    WithBorders(cli.BorderRounded).
    WithCellStyle(1, func(val string) *cli.AnsiStyle {
        if val == "clean" { return cli.SuccessStyle }
        return cli.WarningStyle
    })
t.AddRow("core-php", "clean", "main")
t.Render()

2. Streaming Text Renderer

Current: Nothing — fmt.Print token-by-token causes flickering and can't be composed with other components.

Want: Component that accepts a chan string or io.Reader and renders growing text in-place, with optional word-wrap and scroll-to-bottom. Think chat UI in terminal.

Consumers: lem gen distill (streaming inference), go-ai MCP tools, any LLM interaction.

API sketch:

stream := cli.NewStream(cli.WithWordWrap(80))
go func() {
    for token := range tokens {
        stream.Write(token)
    }
    stream.Done()
}()
stream.Wait()

3. Parallel Task Tracker

Current: Spinner handles one task. Progress overwrites one line. No way to show N concurrent operations.

Want: Multi-line display showing N tasks with individual spinners/status. Tasks can complete independently. Final summary when all done.

Consumers: core dev pull --all (18 repos), core dev commit --all, any batch operation.

API sketch:

tracker := cli.NewTaskTracker()
for _, repo := range repos {
    t := tracker.Add(repo.Name)
    go func(t *cli.TrackedTask) {
        t.Update("pulling...")
        // ...
        t.Done("up to date")
    }(t)
}
tracker.Wait()

4. Tree Renderer

Current: Nothing — dependency graphs are printed as flat text.

Want: Indented tree with box-drawing characters (├── └── │). Coloured labels. Collapsible optional.

Consumers: core dev impact, module dependency display, directory listings.

API sketch:

tree := cli.NewTree("core-php")
tree.Add("core-tenant").Add("core-bio")
tree.Add("core-admin")
tree.Add("core-api")
cli.Println("%s", tree)
// core-php
// ├── core-tenant
// │   └── core-bio
// ├── core-admin
// └── core-api

5. Upgrade Stubs to Real Implementations

stubs.go has three components with comments saying "will use charmbracelet/X later":

  • Formcharmbracelet/huh (interactive multi-field forms)
  • FilePickercharmbracelet/filepicker (browsable file tree)
  • Tabs → bubbletea model (keyboard-switchable tab panels)

The stub APIs are already designed — just need real implementations behind them. The public API should stay the same.


Priority

  1. Rich Table — broadest impact, every command benefits
  2. Parallel Task Tracker — multi-repo ops are the core use case
  3. Tree — dependency visualisation
  4. Streaming Text — ML/AI commands
  5. Stub upgrades — nice-to-have, current fallbacks work

Notes

  • All components MUST be importable via forge.lthn.ai/core/go/pkg/cli only — no direct charmbracelet/lipgloss imports in consuming packages
  • Follow existing pattern: simple API with functional options, graceful fallback when terminal doesn't support features
  • Tests should follow _Good/_Bad/_Ugly convention
## Overview pkg/cli has a solid foundation (Table, Spinner, ProgressBar, Viewport, TextInput, InteractiveList, Layout, TUI escape hatch). This issue tracks the next wave of components that consuming packages (LEM, go-ai, core dev) need. Prioritised by real-world usage — every item here comes from hitting a gap during actual development. --- ## 1. Rich Table (lipgloss borders + cell styling) **Current**: `Table` in `styles.go` does aligned columns with ANSI header styling. No borders, no box drawing, no per-row/per-cell colouring. **Want**: Optional bordered table using lipgloss table rendering. Per-cell style functions (e.g. red for "unavailable", green for "healthy"). Responsive column widths (truncate to terminal width). **Consumers**: `core dev health`, `core dev work --status`, `ml_backends`, `ml_status`, any listing command. **API sketch**: ```go t := cli.NewTable("REPO", "STATUS", "BRANCH"). WithBorders(cli.BorderRounded). WithCellStyle(1, func(val string) *cli.AnsiStyle { if val == "clean" { return cli.SuccessStyle } return cli.WarningStyle }) t.AddRow("core-php", "clean", "main") t.Render() ``` --- ## 2. Streaming Text Renderer **Current**: Nothing — `fmt.Print` token-by-token causes flickering and can't be composed with other components. **Want**: Component that accepts a `chan string` or `io.Reader` and renders growing text in-place, with optional word-wrap and scroll-to-bottom. Think chat UI in terminal. **Consumers**: `lem gen distill` (streaming inference), `go-ai` MCP tools, any LLM interaction. **API sketch**: ```go stream := cli.NewStream(cli.WithWordWrap(80)) go func() { for token := range tokens { stream.Write(token) } stream.Done() }() stream.Wait() ``` --- ## 3. Parallel Task Tracker **Current**: `Spinner` handles one task. `Progress` overwrites one line. No way to show N concurrent operations. **Want**: Multi-line display showing N tasks with individual spinners/status. Tasks can complete independently. Final summary when all done. **Consumers**: `core dev pull --all` (18 repos), `core dev commit --all`, any batch operation. **API sketch**: ```go tracker := cli.NewTaskTracker() for _, repo := range repos { t := tracker.Add(repo.Name) go func(t *cli.TrackedTask) { t.Update("pulling...") // ... t.Done("up to date") }(t) } tracker.Wait() ``` --- ## 4. Tree Renderer **Current**: Nothing — dependency graphs are printed as flat text. **Want**: Indented tree with box-drawing characters (├── └── │). Coloured labels. Collapsible optional. **Consumers**: `core dev impact`, module dependency display, directory listings. **API sketch**: ```go tree := cli.NewTree("core-php") tree.Add("core-tenant").Add("core-bio") tree.Add("core-admin") tree.Add("core-api") cli.Println("%s", tree) // core-php // ├── core-tenant // │ └── core-bio // ├── core-admin // └── core-api ``` --- ## 5. Upgrade Stubs to Real Implementations `stubs.go` has three components with comments saying "will use charmbracelet/X later": - **Form** → `charmbracelet/huh` (interactive multi-field forms) - **FilePicker** → `charmbracelet/filepicker` (browsable file tree) - **Tabs** → bubbletea model (keyboard-switchable tab panels) The stub APIs are already designed — just need real implementations behind them. The public API should stay the same. --- ## Priority 1. Rich Table — broadest impact, every command benefits 2. Parallel Task Tracker — multi-repo ops are the core use case 3. Tree — dependency visualisation 4. Streaming Text — ML/AI commands 5. Stub upgrades — nice-to-have, current fallbacks work ## Notes - All components MUST be importable via `forge.lthn.ai/core/go/pkg/cli` only — no direct charmbracelet/lipgloss imports in consuming packages - Follow existing pattern: simple API with functional options, graceful fallback when terminal doesn't support features - Tests should follow `_Good/_Bad/_Ugly` convention
Author
Member

Completed by Charon in core/cli (e360115). CLI package now lives at forge.lthn.ai/core/cli/pkg/cli, not core/go/pkg/cli.

Completed by Charon in `core/cli` (e360115). CLI package now lives at `forge.lthn.ai/core/cli/pkg/cli`, not `core/go/pkg/cli`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: core/go#14
No description provided.