# Metrics Range TUI Output Implementation Plan

This ExecPlan is a living document. Keep `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` up to date as work proceeds.

Reference: `PLANS.md` at repository root. This plan must be maintained according to `PLANS.md`.

## Execution Protocol (Mandatory)

Execute subtasks strictly in sequence. Do not run multiple subtasks concurrently.

For each subtask, create a fresh subagent with non-inherited context. Provide only this plan, the target subtask, and minimum required repository context.

After each subtask finishes, the parent agent must review the patch, integrate it, run the subtask validation commands, commit that subtask, and then update `Progress` before starting the next subtask.

If a subtask fails validation, fix that subtask before moving forward. Never carry known failing behavior into a later subtask.

operator prompt template:

`Implement docs/2026-04-04-metrics-range-tui-output-plan.md using fresh non-inherited subagents per subtask, sequentially, with parent review/integration, one commit per subtask, and Progress updates after each subtask commit.`

## Purpose / Big Picture

After this change, `clinic metrics range` can render an interactive terminal UI for range-vector results, making large PromQL result sets explorable without external tooling. Users can select a returned time series from a list and inspect a chart plus summary stats in one command run.

The existing machine-readable path stays available through `--output json` (or `-o json`), and scripting behavior remains safe because non-interactive runs auto-fallback to JSON unless the user explicitly forces TUI.

This plan is intentionally scoped to `metrics range` only. `metrics query` and `metrics series` remain JSON output in this milestone.

## Progress

- [x] T1. Add `metrics range` output-mode contract (`--output`, `-o`) with deterministic validation and non-TTY fallback behavior.
- [x] T2. Add a dedicated metrics-range TUI module for Prometheus response decoding and series view-model construction.
- [x] T3. Implement Bubble Tea + Bubbles + ntcharts interactive renderer (left series list, right chart/stats, keyboard controls, empty-result screen).
- [x] T4. Integrate TUI renderer into command path and add/adjust tests for command routing, TTY behavior, and TUI data/model logic.
- [x] T5. Update help text and docs (`README`, `docs/COMMAND_SURFACE.md`) and run verification ladder through `make verify`.

## Surprises & Discoveries

- Observation: Running `go test ./internal/cmd -run Help -count=1 -v` before `make test`/`make verify` caused many later package runs to report `(cached)`.
  Evidence: `make test` and `make verify` still exited 0 and included full package traversal output plus `agent legibility checks passed`.

## Decision Log

- Decision: Add output selection only to `metrics range` in V1.
  Rationale: Range responses (`resultType=matrix`) are the right fit for interactive chart browsing; this limits scope and risk.
  Date/Author: 2026-04-04 / User + Codex

- Decision: Default output behavior is context-sensitive: TUI on interactive terminals, JSON on non-interactive terminals.
  Rationale: Preserve script/pipe compatibility while enabling richer local UX.
  Date/Author: 2026-04-04 / User + Codex

- Decision: Keep explicit machine mode via `--output json` and `-o json`.
  Rationale: User explicitly requested old JSON path remain available.
  Date/Author: 2026-04-04 / User + Codex

- Decision: Explicit `--output tui` in non-TTY is a hard error.
  Rationale: Avoid silent behavior changes when user explicitly requests TUI.
  Date/Author: 2026-04-04 / User + Codex

- Decision: V1 is static one-shot rendering (no polling/live refresh).
  Rationale: Smaller blast radius and clearer behavior contract.
  Date/Author: 2026-04-04 / User + Codex

- Decision: V1 layout is left series list + right chart/stats panel.
  Rationale: Best balance of discoverability and scanability for multi-series responses.
  Date/Author: 2026-04-04 / User + Codex

- Decision: Numeric conversion in `metricstui` skips invalid samples and tracks per-series invalid sample counts.
  Rationale: Preserve as much valid chart data as possible while keeping conversion behavior explicit and testable.
  Date/Author: 2026-04-04 / Codex

## Outcomes & Retrospective

- Implemented `metrics range` output routing with deterministic contracts:
  - `--output json`/`-o json` forces JSON output,
  - `--output tui`/`-o tui` forces TUI and errors on non-TTY,
  - omitted output defaults to TUI on TTY and JSON otherwise.
- Added `internal/cmd/metricstui` package with:
  - Prometheus `query_range` decoding (`resultType=matrix` validation),
  - deterministic series label sorting,
  - invalid-sample skip-and-count behavior,
  - Bubble Tea/Bubbles/ntcharts renderer (series list + chart + stats + empty state + quit/navigation keys).
- Wired command path to invoke `metricstui.RunRangeTUI` through injectable hooks for deterministic command tests.
- Updated help text and user/operator docs to match new behavior (`internal/cmd/help.go`, `README.md`, `docs/COMMAND_SURFACE.md`).
- Verification evidence:
  - `go test ./internal/cmd -run Metrics -count=1 -v` passed.
  - `go test ./internal/cmd/metricstui -count=1 -v` passed.
  - `go test ./internal/cmd -run Help -count=1 -v` passed.
  - `make test` passed.
  - `make verify` passed (including `scripts/check-agent-legibility.sh`).

## Context and Orientation

Current behavior in `internal/cmd/metrics.go` always writes formatted JSON for `metrics query`, `metrics range`, and `metrics series`. `metrics range` already builds Prometheus `query_range` requests and retries `504 Gateway Timeout` responses.

Relevant current files:

- `internal/cmd/metrics.go`: flag parsing, request shaping, retry loop, JSON output path.
- `internal/cmd/metrics_test.go`: command validation and request/response contract tests.
- `internal/cmd/help.go` and `internal/cmd/help_test.go`: user-facing help strings and assertions.
- `internal/cmd/json_format.go`: JSON formatting behavior for terminal/non-terminal outputs.
- `README.md` and `docs/COMMAND_SURFACE.md`: user-facing and maintainer-facing command contracts.

Non-obvious terms:

- TTY: interactive terminal device. In this codebase, writer interactivity is inferred from file descriptor checks.
- Range vector / matrix: Prometheus `query_range` result shape where each series has label map plus `[timestamp, value]` sample pairs.
- One-shot TUI: single response rendered interactively; no background re-querying.

## Plan of Work

### Task T1: Add Output-Mode Contract to `metrics range`

Files:

- Modify `internal/cmd/metrics.go`.
- Modify `internal/cmd/metrics_test.go`.

Required changes:

- Add `--output` and `-o` flags to `metrics range` only.
- Allow output values: `tui`, `json`.
- Behavior contract:
  - explicit `--output json` or `-o json`: write existing formatted JSON path.
  - explicit `--output tui` or `-o tui`: run TUI path; if non-TTY, return a clear error.
  - output omitted: default to TUI on TTY and JSON on non-TTY.
- Keep request parameter behavior unchanged (`query/start/end/step/timeout`).
- Keep existing retry semantics unchanged.

Acceptance criteria:

- Existing request path and query parameter tests still pass.
- New tests validate output-mode parsing and non-TTY behavior.

### Task T2: Add Metrics Range TUI Data Module

Files:

- Create `internal/cmd/metricstui/response.go`.
- Create `internal/cmd/metricstui/types.go`.
- Create `internal/cmd/metricstui/response_test.go`.

Required changes:

- Introduce a focused package responsible for decoding Prometheus `query_range` JSON and mapping it into chart-ready series structures.
- Decode and validate minimum required response structure:
  - `status == success` (or tolerate and surface API error field when present),
  - `data.resultType == matrix`,
  - `data.result[]` with `metric` labels and `values` sample tuples.
- Convert value strings to `float64` with explicit handling for parse failures (skip invalid sample and count it, or fail fast; choose one and lock it in test expectations).
- Produce deterministic series labels for list display (stable sort by computed display name).

Acceptance criteria:

- Unit tests cover: multi-series decode, empty result, malformed JSON, non-matrix result type, and numeric conversion behavior.

### Task T3: Implement TUI Renderer

Files:

- Create `internal/cmd/metricstui/model.go`.
- Create `internal/cmd/metricstui/view.go`.
- Create `internal/cmd/metricstui/run.go`.
- Create `internal/cmd/metricstui/model_test.go`.

Required changes:

- Build a Bubble Tea model with:
  - left panel: Bubbles list for series selection,
  - right panel: ntcharts time-series chart for selected series,
  - right panel summary stats: latest, min, max, average, sample count, and displayed time range.
- Keyboard behavior:
  - navigation: `up/down` and `j/k`,
  - exit: `q`, `esc`, `ctrl+c`.
- Empty-result behavior: render friendly “No series returned for this query range” screen with quit hint.
- Ensure renderer works with terminal resize events and does not panic on narrow widths.

Acceptance criteria:

- TUI model tests validate selection updates, quit behavior, and empty-state rendering contract.

### Task T4: Wire TUI Into Command Path and Broaden Tests

Files:

- Modify `internal/cmd/metrics.go`.
- Modify `internal/cmd/metrics_test.go`.
- Add targeted integration tests in `internal/cmd/metrics_test.go` or a new `internal/cmd/metrics_tui_integration_test.go`.

Required changes:

- In `runMetricsRange`, route response bytes to either JSON output path or TUI runner based on output mode and TTY detection policy.
- Keep `metrics query` and `metrics series` unchanged.
- Add tests for:
  - default output on non-TTY writer (JSON),
  - explicit JSON output,
  - explicit TUI in non-TTY returns expected error,
  - output validation error on unsupported format.
- For TTY-dependent paths, introduce a small injectable terminal capability function in `cmd` package so tests can deterministically control behavior.

Acceptance criteria:

- `go test ./internal/cmd -run Metrics -v` passes including new output-mode tests.

### Task T5: Help/Docs + Verification

Files:

- Modify `internal/cmd/help.go`.
- Modify `internal/cmd/help_test.go`.
- Modify `README.md`.
- Modify `docs/COMMAND_SURFACE.md`.

Required changes:

- Update `metrics range` help text to include `--output`/`-o`, supported values, and default behavior by terminal type.
- Add README examples for:
  - default TUI usage,
  - forcing JSON (`--output json`),
  - explicit TUI behavior note.
- Update command-surface notes to capture new output-mode contract.
- Run verification ladder and record evidence in this plan.

Acceptance criteria:

- Help tests pass.
- Documentation aligns with implemented behavior.
- `make verify` passes.

## Concrete Steps

All commands run from repository root: `/Users/jujiajia/code/tidbcloud/clinic-cli`.

1. Implement T1 and run targeted metrics tests.

    go test ./internal/cmd -run Metrics -v

Expected: existing request-path tests remain green and new output-mode tests pass.

2. Implement T2 and run module-focused tests.

    go test ./internal/cmd/metricstui -v

Expected: response decoder/mapping tests pass for success, empty, and malformed scenarios.

3. Implement T3 and run module tests again.

    go test ./internal/cmd/metricstui -v

Expected: model behavior and key-handling tests pass.

4. Implement T4 and rerun command tests.

    go test ./internal/cmd -run Metrics -v

Expected: deterministic output-mode routing is fully covered.

5. Implement T5 and run help + full verification ladder.

    go test ./internal/cmd -run Help -v
    make test
    make verify

Expected: all commands exit 0.

## Validation and Acceptance

Behavioral checks after tests pass:

1. Interactive terminal default TUI:

    ./bin/clinic metrics range --cluster-id <id> --query 'up' --start 1712123000 --end 1712123600

Expected: TUI launches with series list and chart view.

2. Force JSON output:

    ./bin/clinic metrics range --cluster-id <id> --query 'up' --start 1712123000 --end 1712123600 --output json

Expected: formatted JSON is printed; no TUI launched.

3. Non-interactive fallback to JSON:

    ./bin/clinic metrics range --cluster-id <id> --query 'up' --start 1712123000 --end 1712123600 | head -n 5

Expected: JSON output appears and command exits 0.

4. Explicit TUI in non-interactive context:

    ./bin/clinic metrics range --cluster-id <id> --query 'up' --start 1712123000 --end 1712123600 --output tui | cat

Expected: clear non-TTY error and non-zero exit.

5. Empty result rendering:

    ./bin/clinic metrics range --cluster-id <id> --query 'nonexistent_metric' --start 1712123000 --end 1712123600

Expected: TUI opens and shows friendly empty-state message with quit hint.

## Idempotence and Recovery

- Re-running tests and `make verify` is safe.
- Non-mutating checks can be repeated without side effects.
- If dependency additions introduce version conflicts, revert only `go.mod`/`go.sum` changes for that subtask and reapply with pinned versions.
- If a subtask fails, stop at that boundary, fix forward in the same subtask, and update `Progress` before advancing.

## Artifacts and Notes

- Keep commit boundaries aligned to T1-T5.
- After each subtask commit, update `Progress` with timestamp and one-line evidence.
- Capture any terminal-specific quirks (for example, width handling or color capability interactions) under `Surprises & Discoveries` with minimal command evidence.

## Interfaces and Dependencies

Add module dependencies in `go.mod` (exact versions decided during implementation with `go get` and committed in lockstep):

- `github.com/charmbracelet/bubbletea`
- `github.com/charmbracelet/bubbles`
- `github.com/NimbleMarkets/ntcharts`

Define TUI package entrypoint in `internal/cmd/metricstui/run.go`:

    func RunRangeTUI(stdout io.Writer, rawResponse []byte) error

Behavioral contract:

- Parses `rawResponse` into deterministic in-memory series model.
- Launches Bubble Tea program only when caller has already validated interactive terminal requirements.
- Returns non-nil error for malformed/unusable data or renderer startup failures.

Define decoded range model in `internal/cmd/metricstui/types.go` (names may vary, semantics fixed):

    type Series struct {
        Name    string
        Labels  map[string]string
        Points  []Point
    }

    type Point struct {
        UnixSeconds float64
        Value       float64
    }

Command-side routing contract in `internal/cmd/metrics.go`:

- `metrics range` chooses output mode before final rendering.
- Output-mode decision is pure/deterministic based on flag value + terminal capability.
- Request execution and retry behavior remain unchanged regardless of output mode.

Plan update note (2026-04-04): Initial plan created from approved Bubble Tea + Bubbles + ntcharts design decisions and repository ExecPlan requirements.
