Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/changes/herdr-theme-system/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
- [x] 1.2 Add the theme module with named palettes, attribution, and resolution
- [x] 1.3 Add the `theme` config keys and extend the config reference
- [x] 1.4 Route `Sheet` tones through the resolved palette
- [ ] 1.5 Derive the TUI palette from the resolved palette
- [x] 1.5 Derive the TUI palette from the resolved palette
- [x] 1.6 Add best-effort appearance detection with the configured fallback

## 2. Verification

- [x] 2.1 Test resolution precedence: `--no-color`, `NO_COLOR`, non-terminal, theme
- [x] 2.2 Test named selection, unknown-name warning, and token overrides
- [ ] 2.3 Pin golden TUI snapshots to one named theme
- [x] 2.3 Pin golden TUI snapshots to one named theme
- [x] 2.4 Run formatting, `cargo clippy --all-targets --all-features -- -D warnings`, and the tests
29 changes: 29 additions & 0 deletions docs/changes/tui-first-run/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# TUI First Run Design

## Approach

One palette struct in `src/tui/styles.rs` derives every TUI color from `theme::current()`. The
five theme tones carry the meaning (accent for focus and keys, good and bad for diffs and states,
alert for the cursor, violet for headings). Surfaces and text tones follow a `light` flag on the
theme, so a light palette flips panel and text without a second setting. This beat adding surface
tokens to every theme because the tones already exist and the flag is one bit per palette.

## Structure

- `ThemeTones` gains `light`. The two light palettes set it.
- `styles::Palette::from_theme` maps tones to the TUI roles; accessor functions replace the old
constants, so call sites change one token each.
- `App` detects provider states once at load through the shared detection registry and reads the
bound target once. The status bar renders both beside the deck name.
- `App::is_first_run` is true after a scan that found no deck and no modules. `render` then
draws the first-run panel in place of the three columns and a matching footer hint.
- The help overlay title carries the version and the close keys; group labels and keys use the
theme.

## Risks

- Detection at load adds filesystem reads before the first frame. They are bounded evidence
checks and run once.
- A light palette on a terminal that ignores truecolor falls back to the ANSI tones; surfaces
stay readable because text tones are basic colors.
- Golden snapshots pin the default dark theme; a theme change in tests must install one first.
30 changes: 30 additions & 0 deletions docs/changes/tui-first-run/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
adr: "docs/decisions/CLI-0036 TUI Status and First Run.md"
status: proposed
---

# TUI First Run

## Why

`rune tui` opens on a blank list when no deck is configured, paints one hard-coded dark palette
whatever `theme.name` says, and its status bar names nothing the user chose: no deck, no target,
no provider state. Herdr's TUI starts every session with the same three facts in view and leads a
new user to setup instead of an empty screen. Governing decision: CLI-0036.

## What Changes

- The TUI palette derives from the resolved theme. Light themes get light surfaces and dark text.
- The status bar shows the deck name, the bound target, and one glyph per enabled provider.
- A first-run panel replaces the empty list when the scan finds no deck and no modules, and names
the commands that lead out of it.
- The help overlay names its close keys and the rune version, in theme colors.

## Capabilities

- tui (new)

## Impact

- `src/tui/styles.rs`, `src/tui/app.rs`, `src/cli/theme.rs`, and the TUI tests
- Closes tasks 1.5 and 2.3 of the herdr-theme-system change
40 changes: 40 additions & 0 deletions docs/changes/tui-first-run/specs/tui/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## ADDED Requirements

### Requirement: Theme-derived TUI palette

The TUI SHALL derive every color from the resolved theme, and a light theme SHALL use light
surfaces with dark text.

#### Scenario: Light theme selected

- **WHEN** the user config selects a light theme
- **THEN** the TUI panels, status bar, and text use the light palette

### Requirement: Status bar context

The TUI status bar SHALL show the deck name, the bound target when one exists, and one glyph per
enabled provider that reflects its deployment state.

#### Scenario: Provider needs repair

- **WHEN** a provider's deployment state is `needs repair`
- **THEN** the status bar shows that provider with the repair glyph in the bad tone

### Requirement: First-run panel

When the scan finds no deck and no modules, the TUI SHALL replace the list with a panel that names
the root and the commands that configure a deck, and the footer SHALL point at `rune setup`.

#### Scenario: TUI opens on an unconfigured root

- **WHEN** `rune tui` starts in a directory with no deck and no modules
- **THEN** the panel names `rune setup`, `rune config set deck`, and `rune tui --source`

### Requirement: Help overlay close keys

The help overlay SHALL name its close keys and the rune version in its title.

#### Scenario: User opens help

- **WHEN** the user presses `?`
- **THEN** the overlay title names `?` and `Esc` as the close keys
14 changes: 14 additions & 0 deletions docs/changes/tui-first-run/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## 1. Implementation

- [x] 1.1 Record the decision in CLI-0036
- [x] 1.2 Add the `light` flag to the theme tones and derive the TUI palette from the theme
- [x] 1.3 Route every TUI color through the palette accessors
- [x] 1.4 Show deck, target, and provider states in the status bar
- [x] 1.5 Render the first-run panel and footer hint when no deck and no modules exist
- [x] 1.6 Name the close keys and version in the help overlay

## 2. Verification

- [x] 2.1 Test the light and dark palette derivation and the tone mapping
- [x] 2.2 Test the first-run panel, the status bar context, and the help title
- [x] 2.3 Run formatting, `cargo clippy --all-targets --all-features -- -D warnings`, and the tests
71 changes: 71 additions & 0 deletions docs/decisions/CLI-0036 TUI Status and First Run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: "TUI Status and First Run"
description: "The TUI paints from the resolved theme, shows deck, target, and provider states, and routes an unconfigured root into setup"
type: adr
category: cli
tags:
- cli
- ux
- tui
status: proposed
created: 2026-09-02
updated: 2026-09-02
author: "@N4M3Z"
project: rune-cli
related:
- "CLI-0007 Interactive Mode and TUI"
- "CLI-0028 Setup Plan and Apply"
- "CLI-0031 Terminal Theme System"
responsible: ["@N4M3Z"]
accountable: ["@N4M3Z"]
consulted: ["claude-fable-5"]
informed: []
upstream: []
---

# TUI Status and First Run

## Context and Problem Statement

CLI-0031 promised one palette for `Sheet` output and the TUI, but `src/tui/styles.rs` kept its
hard-coded dark constants and the app painted named terminal colors beside them. A user who
selects a light theme gets themed CLI output and an unchanged dark TUI. The status bar reports
scan counts and nothing the user configured. On a root without a deck the list reads `no rows`.
[Herdr][HERDR] opens every session with the workspace facts in view and leads a new user to
setup. Rune needs the same three things without a second configuration surface.

## Decision Drivers

- One palette source, as CLI-0031 requires
- The status bar shows what the user chose: deck, target, providers
- An unconfigured root leads into `rune setup`, never a blank list
- No new configuration keys

## Considered Options

1. **Surface tokens per theme** — add background and text tones to every palette. Six palettes
to maintain, and custom overrides grow.
2. **A light flag per theme with derived surfaces** — the five tones keep the meaning, one bit
picks the surface set, and the TUI derives the rest.
3. **Terminal default backgrounds** — paint no backgrounds and inherit the terminal. Selection
and diff highlights lose contrast on unknown backgrounds.

## Decision Outcome

Option 2. `ThemeTones` gains `light`; `styles::Palette::from_theme` derives every TUI color from
the tones and that flag, and accessor functions replace the constants. The app detects provider
states once at load through the shared registry and reads the bound target once; the status bar
shows the deck name, the target, and one glyph per enabled provider. After a scan that finds no
deck and no modules, the TUI draws a first-run panel that names the root and the commands that
configure a deck, and the footer points at `rune setup`. The help overlay names its close keys
and the version.

## Consequences

- [+] A theme change restyles the TUI without a second setting
- [+] The first frame answers which deck, target, and providers are active
- [+] A new user reaches setup from the TUI instead of an empty list
- [-] Provider detection adds bounded filesystem reads before the first frame
- [-] Light surfaces are fixed values; a custom light palette cannot tune them yet

[HERDR]: https://github.com/herdrdev/herdr
9 changes: 9 additions & 0 deletions src/cli/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ pub struct ThemeTones {
pub alert: Tone,
pub bad: Tone,
pub violet: Tone,
/// The palette expects a light terminal background. The TUI derives
/// its surfaces and text tones from this flag.
pub light: bool,
}

pub const DEFAULT_DARK: &str = "rune-dark";
Expand Down Expand Up @@ -55,6 +58,7 @@ const THEMES: &[(&str, ThemeTones)] = &[
rgb: (187, 154, 247),
ansi: 35,
},
light: false,
},
),
(
Expand All @@ -80,6 +84,7 @@ const THEMES: &[(&str, ThemeTones)] = &[
rgb: (110, 66, 180),
ansi: 35,
},
light: true,
},
),
(
Expand All @@ -105,6 +110,7 @@ const THEMES: &[(&str, ThemeTones)] = &[
rgb: (203, 166, 247),
ansi: 35,
},
light: false,
},
),
(
Expand All @@ -130,6 +136,7 @@ const THEMES: &[(&str, ThemeTones)] = &[
rgb: (136, 57, 239),
ansi: 35,
},
light: true,
},
),
(
Expand All @@ -155,6 +162,7 @@ const THEMES: &[(&str, ThemeTones)] = &[
rgb: (187, 154, 247),
ansi: 35,
},
light: false,
},
),
(
Expand All @@ -180,6 +188,7 @@ const THEMES: &[(&str, ThemeTones)] = &[
rgb: (180, 142, 173),
ansi: 35,
},
light: false,
},
),
];
Expand Down
Loading
Loading