From 564fcdeffc6f4ba891f91cee1b3a7ca3309974c3 Mon Sep 17 00:00:00 2001 From: Andrey Kuznetsov Date: Mon, 15 Jun 2026 17:33:13 +0200 Subject: [PATCH 1/9] docs(sprint): seed jewel-brand migration sprint doc --- docs/plans/jewel-brand-sprint.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 docs/plans/jewel-brand-sprint.md diff --git a/docs/plans/jewel-brand-sprint.md b/docs/plans/jewel-brand-sprint.md new file mode 100644 index 000000000..d8516676b --- /dev/null +++ b/docs/plans/jewel-brand-sprint.md @@ -0,0 +1,31 @@ +# Migrate right-ui (CLI) + right-dashboard (Telegram) to Observatory/jewel brand — Sprint + +Integration: claude/strange-borg-9c9f27 · Base: master +Engine: mimo +Issue: https://github.com/onsails/right-agent/issues/130 (follow-up to #129) +Legend: todo · brainstorming · planned · executing · review · blocked · done + +## Stages +1. [todo] CLI — right-ui (Rust): replace orange accents → jewel semantic hexes; keep NO_COLOR/TERM=dumb fallbacks. +2. [todo] Dashboard — right-dashboard (Vue): introduce jewel palette tokens; accent→teal, identity→ruby, warm→gold; replace ad-hoc Telegram-blue/scattered hexes. + +## Brand reference (authoritative: docs/brand-guidelines.html, v2 jewel) +- base plum `#121016`, panel `#201a26`, lines `#2d2533` / `#3e3146` +- ruby `#c75f88` = identity (word "right" + claw mark) +- teal `#3bb0c4` = action / structure / links +- gold `#cda14b` = warmth / secondary highlight +- text `#f1ece9`, muted `#b6a8b0`, dim `#6f6169` +- semantic: ok `#6bbf59`, warn `#e6c06a`, err `#e2556a`, info `#3bb0c4` + +## Notes +- Recolor only — not a structural change. Dashboard keeps `right_ui::*`/`AsyncState`/`CollapsibleSection` primitives and existing component structure. +- right-ui current orange: `ORANGE = #E8632A` const in `crates/right-ui/src/atoms.rs`; `BRAND_ORANGE` in `prompts.rs`; status glyphs in atoms/line/recap/splash/theme. +- Dashboard is NOT literally orange today — it's Telegram-native blue (`#2481cc`/`#17212b`) + scattered hardcoded hexes with no jewel tokens. Migration introduces palette CSS custom properties. + +## Decisions log +- 2-stage decomposition (CLI then dashboard); CLI first fixes canonical jewel hexes the dashboard reuses. +- Integration branch = current worktree branch `claude/strange-borg-9c9f27` (no new branch, per user rule). +- Engine: mimo, not pinned → resolve model per stage via mimo-resolve. + +## Open questions +- (none yet) From 4de5d654ee6345fbeb7762763dad40e8eb0c0f38 Mon Sep 17 00:00:00 2001 From: Andrey Kuznetsov Date: Mon, 15 Jun 2026 17:37:35 +0200 Subject: [PATCH 2/9] docs(sprint): stage 01 CLI spec + plan --- docs/plans/01-cli-plan.md | 210 +++++++++++++++++++++++++++++++ docs/plans/01-cli-spec.md | 75 +++++++++++ docs/plans/jewel-brand-sprint.md | 2 +- 3 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 docs/plans/01-cli-plan.md create mode 100644 docs/plans/01-cli-spec.md diff --git a/docs/plans/01-cli-plan.md b/docs/plans/01-cli-plan.md new file mode 100644 index 000000000..4b73658ad --- /dev/null +++ b/docs/plans/01-cli-plan.md @@ -0,0 +1,210 @@ +# Stage 01 — right-ui (CLI) jewel recolor — Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Recolor `right-ui` from coal/fire orange (`#E8632A`) to the +Observatory/jewel palette — ruby identity, teal action, corrected semantic +glyphs, and a brand wordmark in the splash — without any structural change. + +**Architecture:** All ANSI color originates in `crates/right-ui/src/atoms.rs` +(palette consts + `Rail` + `Glyph`) and `prompts.rs` (cursor). `splash.rs` gains +theme-aware wordmark coloring that reuses the atoms palette consts. Mono/Ascii +tiers stay byte-identical (no ANSI). Spec: `docs/plans/01-cli-spec.md`. + +**Tech Stack:** Rust (edition 2024), `owo-colors` (`truecolor`), `inquire` +(`Color::Rgb`), `cargo nextest`. + +**Jewel RGB reference (decimal, for `truecolor` escapes `\x1b[38;2;R;G;Bm`):** +- ruby `#c75f88` = (199, 95, 136) → escape `\x1b[38;2;199;95;136m` +- teal `#3bb0c4` = (59, 176, 196) → escape `\x1b[38;2;59;176;196m` +- muted `#b6a8b0` = (182, 168, 176) +- ok `#6bbf59` = (107, 191, 89) (unchanged) +- warn `#e6c06a` = (230, 192, 106) +- err `#e2556a` = (226, 85, 106) +- info `#3bb0c4` = (59, 176, 196) (== teal) + +**Commit policy:** Do NOT `git commit` per task. The sprint stage-runner commits +and lands the whole stage after review + verify (mechanics §7). Leave the working +tree dirty; just keep each task green. + +--- + +### Task 1: atoms.rs — jewel palette constants + +**Files:** +- Modify: `crates/right-ui/src/atoms.rs` +- Test: `crates/right-ui/src/atoms_tests.rs` + +**Step 1 — Write failing regression tests.** Append to `atoms_tests.rs`: + +```rust +// --- jewel palette regression (truecolor escapes) --- + +#[test] +fn rail_is_ruby() { + // ruby #c75f88 = (199, 95, 136) + let s = Rail::prefix(Theme::Color); + assert!(s.contains("\x1b[38;2;199;95;136m"), "rail not ruby: {s:?}"); +} + +#[test] +fn mark_is_ruby() { + let s = Rail::mark(Theme::Color); + assert!(s.contains("\x1b[38;2;199;95;136m"), "mark not ruby: {s:?}"); +} + +#[test] +fn glyph_semantic_hexes() { + assert!(Glyph::Ok.render(Theme::Color).contains("\x1b[38;2;107;191;89m")); + assert!(Glyph::Warn.render(Theme::Color).contains("\x1b[38;2;230;192;106m")); + assert!(Glyph::Err.render(Theme::Color).contains("\x1b[38;2;226;85;106m")); + assert!(Glyph::Info.render(Theme::Color).contains("\x1b[38;2;59;176;196m")); +} +``` + +**Step 2 — Run, verify red.** +Run: `devenv shell -- cargo nextest run -p right-ui rail_is_ruby mark_is_ruby glyph_semantic_hexes` +Expected: FAIL (still orange `232;99;42`, warn `217;168;42`, etc.). + +**Step 3 — Edit the consts and rename.** In `atoms.rs`: +- Replace `pub(crate) const ORANGE: (u8, u8, u8) = (0xE8, 0x63, 0x2A);` + with `pub(crate) const RUBY: (u8, u8, u8) = (0xC7, 0x5F, 0x88);` +- Add `pub(crate) const MUTED: (u8, u8, u8) = (0xB6, 0xA8, 0xB0);` + and `pub(crate) const TEAL: (u8, u8, u8) = (0x3B, 0xB0, 0xC4);` + (TEAL/MUTED are used by prompts.rs / splash.rs in later tasks.) +- `const OK` stays `(0x6B, 0xBF, 0x59)`. +- `const WARN: (u8,u8,u8) = (0xE6, 0xC0, 0x6A);` +- `const ERR: (u8,u8,u8) = (0xE2, 0x55, 0x6A);` +- `const INFO: (u8,u8,u8) = (0x3B, 0xB0, 0xC4);` +- Replace the three `ORANGE.0, ORANGE.1, ORANGE.2` uses in `Rail` with + `RUBY.0, RUBY.1, RUBY.2`. +- Update the module doc comment (line ~4) "orange rail" → "ruby rail". + +> Note: `TEAL`/`MUTED` may trip `dead_code` until Tasks 2–3 use them. If you do +> Task 1 in isolation and the crate fails to compile on the unused const, add the +> consts in the task that first consumes them instead, or complete Tasks 1–3 as +> one edit before the first full build. Either ordering is fine; keep them in +> `atoms.rs` as the single palette source. + +**Step 4 — Run, verify green.** +Run: `devenv shell -- cargo nextest run -p right-ui` +Expected: PASS (existing atoms tests + new regression tests). + +--- + +### Task 2: prompts.rs — highlighted cursor → teal + +**Files:** +- Modify: `crates/right-ui/src/prompts.rs` (impl + its inline `#[cfg(test)] mod tests`) + +**Step 1 — Update the test first.** In the `tests` module: +- `color_theme_only_colors_highlighted_cursor`: the assertion + `assert_eq!(cfg.highlighted_option_prefix.style.fg, Some(BRAND_ORANGE));` + becomes `Some(CURSOR_TEAL)` (new const name from Step 2). + +**Step 2 — Edit impl.** +- Change `use crate::atoms::ORANGE;` → `use crate::atoms::TEAL;` +- Replace the `BRAND_ORANGE` const with: + ```rust + const CURSOR_TEAL: Color = Color::Rgb { r: TEAL.0, g: TEAL.1, b: TEAL.2 }; + ``` +- Update both references (`with_fg(BRAND_ORANGE)` and the test) to `CURSOR_TEAL`. +- Update the module doc comment (lines ~6–9): "brand orange" → "brand teal"; + drop the now-stale parenthetical about orange if it no longer reads true. + +**Step 3 — Run, verify green.** +Run: `devenv shell -- cargo nextest run -p right-ui prompts` +Expected: PASS. + +--- + +### Task 3: splash.rs — brand wordmark (`right` ruby + `agent` muted) + +**Files:** +- Modify: `crates/right-ui/src/splash.rs` +- Test: `crates/right-ui/src/splash_tests.rs` + +**Step 1 — Update/replace tests.** The current Color-theme test asserts the +contiguous substring `"right agent v0.10.2"`, which is no longer contiguous once +ANSI is interleaved. Replace `splash_color_has_ansi_no_unicode_loss` with: + +```rust +#[test] +fn splash_color_wordmark_is_ruby_and_muted() { + let s = splash(Theme::Color, "0.10.2", "tagline"); + assert!(s.contains(ESC), "color splash should emit ANSI"); + // "right" in ruby, "agent" in muted; version stays plain. + assert!(s.contains("\x1b[38;2;199;95;136m"), "wordmark 'right' not ruby: {s:?}"); + assert!(s.contains("\x1b[38;2;182;168;176m"), "wordmark 'agent' not muted: {s:?}"); + assert!(s.contains("v0.10.2"), "version text missing: {s:?}"); +} +``` +Leave `splash_mono_three_lines`, `splash_ascii`, `splash_mono_no_ansi`, +`splash_ascii_no_unicode_atoms` unchanged — Mono/Ascii output must stay +byte-identical (`"▐✓ right agent v0.10.2"` / `"|* right agent v0.10.2"`). + +**Step 2 — Run, verify red.** +Run: `devenv shell -- cargo nextest run -p right-ui splash` +Expected: FAIL on the new ruby/muted assertions. + +**Step 3 — Implement wordmark coloring.** In `splash.rs`: +- Add `use owo_colors::OwoColorize;` and `use crate::atoms::{MUTED, RUBY};`. +- Add a private helper: + ```rust + /// Brand wordmark: `right` (ruby) + `agent` (muted) in Color; plain otherwise. + fn wordmark(theme: Theme) -> String { + match theme { + Theme::Color => format!( + "{} {}", + "right".truecolor(RUBY.0, RUBY.1, RUBY.2), + "agent".truecolor(MUTED.0, MUTED.1, MUTED.2), + ), + Theme::Mono | Theme::Ascii => "right agent".to_string(), + } + } + ``` +- In `splash`, replace the line-1 construction + `out.push_str("right agent v"); out.push_str(version);` + with: + ```rust + out.push_str(&wordmark(theme)); + out.push_str(" v"); + out.push_str(version); + ``` + (Mark `▐✓ ` and the trailing space before the wordmark are unchanged.) + +**Step 4 — Run, verify green.** +Run: `devenv shell -- cargo nextest run -p right-ui splash` +Expected: PASS. Confirm Mono line is still exactly `"▐✓ right agent v0.10.2"`. + +--- + +### Task 4: cleanup + grep gate + final stage verification + +**Step 1 — Grep gate.** From repo root: +Run: `rg -n -i "0xE8, 0x63, 0x2A|#E8632A|ORANGE|orange" crates/right-ui/src` +Expected: NO matches (all renamed; doc comments updated). If any remain, fix the +wording/value; "orange" must not appear in `right-ui` source. + +**Step 2 — Build clean.** +Run: `devenv shell -- cargo build -p right-ui` +Expected: no warnings about unused `TEAL`/`MUTED`/`RUBY` (all consumed). + +**Step 3 — Full crate test.** +Run: `devenv shell -- cargo nextest run -p right-ui` +Expected: ALL green. + +**Step 4 — Clippy (crate-scoped).** +Run: `devenv shell -- cargo clippy -p right-ui -- -D warnings` +Expected: clean. + +--- + +## Stage completion gate (handled by stage-runner, not per-task) +- `cargo nextest run -p right-ui` green, `cargo clippy -p right-ui` clean, + grep gate empty. +- Code review (`/code-review high --fix`) clean or all findings resolved. +- Then commit + merge `$BR` into integration via `--no-ff` + remove worktree. +- The mandatory full-workspace test (`cargo nextest run --workspace` + + `cargo test --doc --workspace`) is run once at the END of the whole sprint + (after Stage 02), not per stage. diff --git a/docs/plans/01-cli-spec.md b/docs/plans/01-cli-spec.md new file mode 100644 index 000000000..06b029762 --- /dev/null +++ b/docs/plans/01-cli-spec.md @@ -0,0 +1,75 @@ +# Stage 01 — right-ui (CLI) → Observatory/jewel — Spec + +## Goal +Recolor the `right-ui` crate from the old "coal & fire" orange brand to the +Observatory/jewel palette, matching `docs/brand-guidelines.html` (v2). Recolor +only — no structural/API changes. `NO_COLOR` (Mono) and `TERM=dumb`/non-TTY +(Ascii) tiers must keep producing identical plain output (no ANSI). + +## Authoritative palette (from brand guide) +- ruby `#c75f88` = identity (mark + the word `right`) +- teal `#3bb0c4` = action / structure / links / info +- muted `#b6a8b0` = secondary text (the word `agent`) +- semantic: ok `#6bbf59`, warn `#e6c06a`, err `#e2556a`, info `#3bb0c4` +- gold `#cda14b` = warmth/secondary — **not used in the CLI** (YAGNI; decided). + +## Color surface (the only callsites — verified by grep) +All ANSI color lives in two files; `splash.rs` adds wordmark coloring. +1. `crates/right-ui/src/atoms.rs` + - `ORANGE` const `#E8632A` → replace with `RUBY` `#c75f88`. Used by + `Rail::{prefix,mark,blank}` for the rail `▐` and claw mark `▐✓`. + Brand guide: "the mark is always rendered in ruby." + - Semantic glyph consts corrected to brand hexes: + - `OK` `#6BBF59` → unchanged (already brand `#6bbf59`). + - `WARN` `#D9A82A` → `#E6C06A`. + - `ERR` `#E03C3C` → `#E2556A`. + - `INFO` `#4A90E2` (blue) → `#3BB0C4` (teal). +2. `crates/right-ui/src/prompts.rs` + - `BRAND_ORANGE` (currently aliases `ORANGE`) → new `TEAL` `#3bb0c4` + for the highlighted-option cursor `>` (action/focal). Rename the const to + `CURSOR_TEAL` (or keep a clearly-named teal const); stop importing `ORANGE`. +3. `crates/right-ui/src/splash.rs` + - Line 1 currently pushes `"right agent v"` as **plain text**. + Apply the brand wordmark in `Theme::Color` only: + - `right` → ruby `#c75f88` + - ` agent` → muted `#b6a8b0` + - ` v` → default (uncolored). + - The leading `▐✓ ` (claw mark) already comes from `Rail::mark` (now ruby). + - `Theme::Mono` and `Theme::Ascii`: wordmark stays plain (no ANSI) — output + byte-identical to today. + +## Naming / constants +- Introduce shared jewel constants once (in `atoms.rs`) and reuse: `RUBY`, + `TEAL`, `MUTED` as `(u8,u8,u8)` tuples, matching the existing const style. +- `prompts.rs` builds its `comfy`/`inquire` `Color::Rgb` from the shared tuple + rather than redefining the literal, to keep one source of truth. +- Update doc comments that say "orange" (atoms.rs header, prompts.rs header) to + "ruby"/"teal" as appropriate. + +## Out of scope +- No new themes, no `--no-color` flag work, no changes to detection logic in + `theme.rs`, no changes to `line.rs`/`recap.rs`/`header.rs`/`writer.rs` + (they delegate to atoms and need no edits). +- Dashboard (Stage 02). + +## Verification criteria +- `cargo build -p right-ui` clean. +- `cargo nextest run -p right-ui` green after test updates. +- Tests to update (all in `crates/right-ui/src/*_tests.rs`): + - `atoms_tests.rs`: any assertion embedding the orange truecolor escape + (`\x1b[38;2;232;99;42m`) or referencing `ORANGE`; update to ruby + `\x1b[38;2;199;95;136m`; update warn/err/info escape assertions to new hexes. + - `prompts_tests.rs` (in `prompts.rs` `#[cfg(test)]`): `BRAND_ORANGE` + assertion → new teal const. + - `splash_tests.rs`: add/adjust a `Theme::Color` assertion proving the + wordmark carries ruby+muted ANSI; assert Mono/Ascii lines are byte-identical + to today (`"▐✓ right agent v0.10.2"` / `"|* right agent v0.10.2"`). +- Grep gate: no `0xE8, 0x63, 0x2A` / `#E8632A` / `ORANGE` / "orange" remains in + `crates/right-ui/src` (except possibly a CHANGELOG-style comment — none expected). +- Brand-conformance rule (AGENTS.md): all user-facing output still flows through + `right_ui::*`; this stage only changes hex values, not the routing. + +## TDD note +For the wordmark (new behavior), write the failing `splash.rs` Color-theme test +first (assert ruby+muted ANSI present), confirm red, then implement. The const +swaps in atoms/prompts are mechanical — update tests and impl together. diff --git a/docs/plans/jewel-brand-sprint.md b/docs/plans/jewel-brand-sprint.md index d8516676b..434708318 100644 --- a/docs/plans/jewel-brand-sprint.md +++ b/docs/plans/jewel-brand-sprint.md @@ -6,7 +6,7 @@ Issue: https://github.com/onsails/right-agent/issues/130 (follow-up to #129) Legend: todo · brainstorming · planned · executing · review · blocked · done ## Stages -1. [todo] CLI — right-ui (Rust): replace orange accents → jewel semantic hexes; keep NO_COLOR/TERM=dumb fallbacks. +1. [planned] CLI — right-ui (Rust): replace orange accents → jewel semantic hexes; keep NO_COLOR/TERM=dumb fallbacks. spec:01-cli-spec.md plan:01-cli-plan.md 2. [todo] Dashboard — right-dashboard (Vue): introduce jewel palette tokens; accent→teal, identity→ruby, warm→gold; replace ad-hoc Telegram-blue/scattered hexes. ## Brand reference (authoritative: docs/brand-guidelines.html, v2 jewel) From 59ac254c6eac00dfb01f3b37154e611efad4828d Mon Sep 17 00:00:00 2001 From: Andrey Kuznetsov Date: Mon, 15 Jun 2026 17:58:59 +0200 Subject: [PATCH 3/9] feat(jewel-brand): stage 01 right-ui CLI jewel recolor --- crates/right-ui/src/atoms.rs | 18 +++++++------ crates/right-ui/src/atoms_tests.rs | 39 +++++++++++++++++++++++++++++ crates/right-ui/src/prompts.rs | 20 +++++++-------- crates/right-ui/src/splash.rs | 19 ++++++++++++-- crates/right-ui/src/splash_tests.rs | 13 ++++++++-- 5 files changed, 87 insertions(+), 22 deletions(-) diff --git a/crates/right-ui/src/atoms.rs b/crates/right-ui/src/atoms.rs index 7ebd2f1a2..6c070b65e 100644 --- a/crates/right-ui/src/atoms.rs +++ b/crates/right-ui/src/atoms.rs @@ -1,7 +1,7 @@ //! Brand atoms — rail (`▐`), mark (`▐✓`), and semantic glyphs (`✓ ! ✗ …`). //! //! Color values come from the brand guide. Three render tiers: -//! * `Color`: orange rail + colored Unicode glyphs via owo-colors truecolor +//! * `Color`: ruby rail + colored Unicode glyphs via owo-colors truecolor //! * `Mono`: same glyphs without ANSI //! * `Ascii`: `|` rail + bracketed text (`[ok]/[warn]/[err]/[…]`) @@ -9,11 +9,13 @@ use owo_colors::OwoColorize; use crate::theme::Theme; -pub(crate) const ORANGE: (u8, u8, u8) = (0xE8, 0x63, 0x2A); +pub(crate) const RUBY: (u8, u8, u8) = (0xC7, 0x5F, 0x88); +pub(crate) const MUTED: (u8, u8, u8) = (0xB6, 0xA8, 0xB0); +pub(crate) const TEAL: (u8, u8, u8) = (0x3B, 0xB0, 0xC4); const OK: (u8, u8, u8) = (0x6B, 0xBF, 0x59); -const WARN: (u8, u8, u8) = (0xD9, 0xA8, 0x2A); -const ERR: (u8, u8, u8) = (0xE0, 0x3C, 0x3C); -const INFO: (u8, u8, u8) = (0x4A, 0x90, 0xE2); +const WARN: (u8, u8, u8) = (0xE6, 0xC0, 0x6A); +const ERR: (u8, u8, u8) = (0xE2, 0x55, 0x6A); +const INFO: (u8, u8, u8) = (0x3B, 0xB0, 0xC4); pub struct Rail; @@ -21,7 +23,7 @@ impl Rail { /// `"▐ "` (Color/Mono) or `"| "` (Ascii). Always 4 visible cells. pub fn prefix(theme: Theme) -> String { match theme { - Theme::Color => format!("{} ", "▐".truecolor(ORANGE.0, ORANGE.1, ORANGE.2)), + Theme::Color => format!("{} ", "▐".truecolor(RUBY.0, RUBY.1, RUBY.2)), Theme::Mono => "▐ ".to_string(), Theme::Ascii => "| ".to_string(), } @@ -30,7 +32,7 @@ impl Rail { /// `"▐✓"` (Color/Mono) or `"|*"` (Ascii). 2 visible cells. pub fn mark(theme: Theme) -> String { match theme { - Theme::Color => format!("{}", "▐✓".truecolor(ORANGE.0, ORANGE.1, ORANGE.2)), + Theme::Color => format!("{}", "▐✓".truecolor(RUBY.0, RUBY.1, RUBY.2)), Theme::Mono => "▐✓".to_string(), Theme::Ascii => "|*".to_string(), } @@ -39,7 +41,7 @@ impl Rail { /// `"▐"` (Color/Mono) or `"|"` (Ascii). For blank rail rows. pub fn blank(theme: Theme) -> String { match theme { - Theme::Color => format!("{}", "▐".truecolor(ORANGE.0, ORANGE.1, ORANGE.2)), + Theme::Color => format!("{}", "▐".truecolor(RUBY.0, RUBY.1, RUBY.2)), Theme::Mono => "▐".to_string(), Theme::Ascii => "|".to_string(), } diff --git a/crates/right-ui/src/atoms_tests.rs b/crates/right-ui/src/atoms_tests.rs index 0e4c65bc3..87773e6ee 100644 --- a/crates/right-ui/src/atoms_tests.rs +++ b/crates/right-ui/src/atoms_tests.rs @@ -118,3 +118,42 @@ fn no_ansi_in_mono_or_ascii() { } } } + +// --- jewel palette regression (truecolor escapes) --- + +#[test] +fn rail_is_ruby() { + // ruby #c75f88 = (199, 95, 136) + let s = Rail::prefix(Theme::Color); + assert!(s.contains("\x1b[38;2;199;95;136m"), "rail not ruby: {s:?}"); +} + +#[test] +fn mark_is_ruby() { + let s = Rail::mark(Theme::Color); + assert!(s.contains("\x1b[38;2;199;95;136m"), "mark not ruby: {s:?}"); +} + +#[test] +fn glyph_semantic_hexes() { + assert!( + Glyph::Ok + .render(Theme::Color) + .contains("\x1b[38;2;107;191;89m") + ); + assert!( + Glyph::Warn + .render(Theme::Color) + .contains("\x1b[38;2;230;192;106m") + ); + assert!( + Glyph::Err + .render(Theme::Color) + .contains("\x1b[38;2;226;85;106m") + ); + assert!( + Glyph::Info + .render(Theme::Color) + .contains("\x1b[38;2;59;176;196m") + ); +} diff --git a/crates/right-ui/src/prompts.rs b/crates/right-ui/src/prompts.rs index dd8b42ac2..70bc6f137 100644 --- a/crates/right-ui/src/prompts.rs +++ b/crates/right-ui/src/prompts.rs @@ -4,30 +4,30 @@ //! options LightCyan — both clash with the rail-and-glyph palette. The brand //! reads "interactive prompts stay plain" (spec Decision #1) literally: no //! color injected into the prompt chrome — except the `>` highlighted-option -//! cursor, which uses brand orange so the active selection is the focal point. +//! cursor, which uses brand teal so the active selection is the focal point. //! (Color::DarkGrey was tried for the rest and rendered as pastel blue on the //! macOS Terminal default palette, defeating the purpose.) use inquire::ui::{Color, RenderConfig, Styled}; use crate::Theme; -use crate::atoms::ORANGE; +use crate::atoms::TEAL; -const BRAND_ORANGE: Color = Color::Rgb { - r: ORANGE.0, - g: ORANGE.1, - b: ORANGE.2, +const CURSOR_TEAL: Color = Color::Rgb { + r: TEAL.0, + g: TEAL.1, + b: TEAL.2, }; /// Returns the brand `RenderConfig` for the given theme. /// -/// `Color`: `empty()` chrome plus the orange `>` highlighted-option cursor. +/// `Color`: `empty()` chrome plus the teal `>` highlighted-option cursor. /// `Mono` / `Ascii`: `RenderConfig::empty()` — no styling at all. pub fn render_config(theme: Theme) -> RenderConfig<'static> { match theme { Theme::Mono | Theme::Ascii => RenderConfig::empty(), Theme::Color => RenderConfig::empty() - .with_highlighted_option_prefix(Styled::new(">").with_fg(BRAND_ORANGE)), + .with_highlighted_option_prefix(Styled::new(">").with_fg(CURSOR_TEAL)), } } @@ -63,8 +63,8 @@ mod tests { assert!(cfg.answered_prompt_prefix.style.fg.is_none()); assert!(cfg.help_message.fg.is_none()); assert!(cfg.canceled_prompt_indicator.style.fg.is_none()); - // Only the highlighted cursor gets brand orange. + // Only the highlighted cursor gets brand teal. assert_eq!(cfg.highlighted_option_prefix.content, ">"); - assert_eq!(cfg.highlighted_option_prefix.style.fg, Some(BRAND_ORANGE)); + assert_eq!(cfg.highlighted_option_prefix.style.fg, Some(CURSOR_TEAL)); } } diff --git a/crates/right-ui/src/splash.rs b/crates/right-ui/src/splash.rs index 33603520c..f2da306a6 100644 --- a/crates/right-ui/src/splash.rs +++ b/crates/right-ui/src/splash.rs @@ -1,8 +1,22 @@ //! Full splash header — `▐✓ right agent vX.Y.Z` + tagline + blank rail. -use crate::atoms::Rail; +use owo_colors::OwoColorize; + +use crate::atoms::{MUTED, RUBY, Rail}; use crate::theme::Theme; +/// Brand wordmark: `right` (ruby) + `agent` (muted) in Color; plain otherwise. +fn wordmark(theme: Theme) -> String { + match theme { + Theme::Color => format!( + "{} {}", + "right".truecolor(RUBY.0, RUBY.1, RUBY.2), + "agent".truecolor(MUTED.0, MUTED.1, MUTED.2), + ), + Theme::Mono | Theme::Ascii => "right agent".to_string(), + } +} + /// Three-line splash: `▐✓ right agent v` / `▐ ` / `▐`. /// No trailing newline after the third line. Reserved for `right init`. pub fn splash(theme: Theme, version: &str, tagline: &str) -> String { @@ -10,7 +24,8 @@ pub fn splash(theme: Theme, version: &str, tagline: &str) -> String { // Line 1: ▐✓ right agent v0.10.2 out.push_str(&Rail::mark(theme)); out.push(' '); - out.push_str("right agent v"); + out.push_str(&wordmark(theme)); + out.push_str(" v"); out.push_str(version); out.push('\n'); // Line 2: ▐ diff --git a/crates/right-ui/src/splash_tests.rs b/crates/right-ui/src/splash_tests.rs index 559c13e0c..cce873bdb 100644 --- a/crates/right-ui/src/splash_tests.rs +++ b/crates/right-ui/src/splash_tests.rs @@ -22,10 +22,19 @@ fn splash_ascii() { } #[test] -fn splash_color_has_ansi_no_unicode_loss() { +fn splash_color_wordmark_is_ruby_and_muted() { let s = splash(Theme::Color, "0.10.2", "tagline"); assert!(s.contains(ESC), "color splash should emit ANSI"); - assert!(s.contains("right agent v0.10.2")); + // "right" in ruby, "agent" in muted; version stays plain. + assert!( + s.contains("\x1b[38;2;199;95;136m"), + "wordmark 'right' not ruby: {s:?}" + ); + assert!( + s.contains("\x1b[38;2;182;168;176m"), + "wordmark 'agent' not muted: {s:?}" + ); + assert!(s.contains("v0.10.2"), "version text missing: {s:?}"); } #[test] From 1228bfc58ed0616abb16fc8c8bbedadf3767d987 Mon Sep 17 00:00:00 2001 From: Andrey Kuznetsov Date: Mon, 15 Jun 2026 18:00:21 +0200 Subject: [PATCH 4/9] docs(sprint): stage 01 CLI done @809d6e3c --- docs/plans/jewel-brand-sprint.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/plans/jewel-brand-sprint.md b/docs/plans/jewel-brand-sprint.md index 434708318..3605019f8 100644 --- a/docs/plans/jewel-brand-sprint.md +++ b/docs/plans/jewel-brand-sprint.md @@ -6,7 +6,7 @@ Issue: https://github.com/onsails/right-agent/issues/130 (follow-up to #129) Legend: todo · brainstorming · planned · executing · review · blocked · done ## Stages -1. [planned] CLI — right-ui (Rust): replace orange accents → jewel semantic hexes; keep NO_COLOR/TERM=dumb fallbacks. spec:01-cli-spec.md plan:01-cli-plan.md +1. [done] CLI — right-ui (Rust): orange→jewel; rail/mark→ruby, cursor→teal, glyphs→semantic, splash wordmark right=ruby/agent=muted. spec:01-cli-spec.md plan:01-cli-plan.md (merged @809d6e3c · review clean · 55/55 right-ui tests) 2. [todo] Dashboard — right-dashboard (Vue): introduce jewel palette tokens; accent→teal, identity→ruby, warm→gold; replace ad-hoc Telegram-blue/scattered hexes. ## Brand reference (authoritative: docs/brand-guidelines.html, v2 jewel) @@ -25,7 +25,8 @@ Legend: todo · brainstorming · planned · executing · review · blocked · do ## Decisions log - 2-stage decomposition (CLI then dashboard); CLI first fixes canonical jewel hexes the dashboard reuses. - Integration branch = current worktree branch `claude/strange-borg-9c9f27` (no new branch, per user rule). -- Engine: mimo, not pinned → resolve model per stage via mimo-resolve. +- Engine: mimo, not pinned → resolve model per stage via mimo-resolve. Per user: always most-capable model + highest effort → openai/gpt-5.4 variant max (review max). +- Stage 01: mimo's first session stalled empty; resume (same handle) completed. A pre-commit hook reformats (rustfmt) — re-stage + retry the commit if it rewrites files. ## Open questions - (none yet) From 56dd48f08cd0fe5da76f82376e73478d9edf7a25 Mon Sep 17 00:00:00 2001 From: Andrey Kuznetsov Date: Mon, 15 Jun 2026 18:22:24 +0200 Subject: [PATCH 5/9] docs(sprint): stage 02 dashboard spec + plan (fixed jewel-dark) --- docs/plans/02-dashboard-plan.md | 249 +++++++++++++++++++++++++++++++ docs/plans/02-dashboard-spec.md | 124 +++++++++++++++ docs/plans/jewel-brand-sprint.md | 2 +- 3 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 docs/plans/02-dashboard-plan.md create mode 100644 docs/plans/02-dashboard-spec.md diff --git a/docs/plans/02-dashboard-plan.md b/docs/plans/02-dashboard-plan.md new file mode 100644 index 000000000..cf87a2bad --- /dev/null +++ b/docs/plans/02-dashboard-plan.md @@ -0,0 +1,249 @@ +# Stage 02 — right-dashboard jewel recolor — Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Recolor the `right-dashboard` Vue frontend to a fixed jewel-dark theme +(brand-forward), with `--jewel-*` CSS tokens as the single source of truth, the +Telegram theme vars overridden to jewel (defeating Telegram's inline injection), +recolored semantics, ruby identity, and a legible ECharts dark theme. No +structural change. Spec: `docs/plans/02-dashboard-spec.md`. + +**Architecture:** Jewel tokens live in `App.vue :root`. `telegram.ts` gains +`applyJewelTheme()` that re-points `--tg-theme-*` at `var(--jewel-*)` after +Telegram init. Hardcoded semantic hexes are replaced in place; charts merge a +shared jewel option fragment. Tokens are the source of truth; components keep +using `var(--tg-theme-*)` which now resolves to jewel. + +**Tech Stack:** Vue 3 SFC, TypeScript, Vite, Vitest (SSR `renderToString`), +vue-echarts/ECharts, **pnpm**. + +**Run commands from the frontend dir.** Use: +`devenv shell -- bash -c 'cd crates/right-dashboard/frontend && pnpm