diff --git a/DESIGN.md b/DESIGN.md
index 95978ac569..528876d1bf 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -13,7 +13,7 @@ colors:
brand-cyan: "#78bce8"
ombre-start: "#1535B2"
ombre-end: "#6AA6DC"
- # the sheet — crates/tui/src/palette/tokens.rs, exported to web/app/tokens.css (generated, never hand-edit)
+ # the sheet — crates/palette/src/tokens.rs, exported to web/app/tokens.css (generated, never hand-edit)
paper: "#f6f2e8" # WHALE_TEXT_BODY, Whale Ivory — the page above the waterline
paper-deep: "#e8eef8" # LIGHT_ELEVATED — the shallows; cards and code-adjacent plates
paper-card: "#fffdf8" # LIGHT_PANEL — a raised sheet on the paper
@@ -135,8 +135,11 @@ Hard rules, not taste notes.
The strata are geometry drawn from tokens — never a hex of their own.
2. **No gradients as decoration elsewhere.** The ombre `#1535B2 → #6AA6DC`
lives in the mark, the wordmark, and the water. The ocean column's field is
- the TUI's own chrome → bg descent; no spotlight glows, gradient text, or
- gradient rules on paper.
+ the site's own chrome → bg descent — the navy water column is the TUI's
+ selectable `underwater` theme, not the ground the terminal opens on. That
+ ground is Shoreline, the warm charcoal and single blue shared with the GPUI
+ client (`docs/design/TUI_REDESIGN.md`). No spotlight glows, gradient text,
+ or gradient rules on paper.
3. **One shadow.** The terminal plate at the waterline casts one soft, offset,
blue shadow. Nothing else on the site has a drop shadow.
4. **No generic SaaS scaffolding.** No icon-card grids, logo walls,
@@ -157,7 +160,7 @@ Hard rules, not taste notes.
## Colors
-One palette, owned by `crates/tui/src/palette/tokens.rs` and exported to
+One palette, owned by `crates/palette/src/tokens.rs` and exported to
`web/app/tokens.css` by `scripts/export-design-tokens.py` — both the
`WHALE_*` dark tokens (`--whale-*`) and the Blue Stage light preset's
`LIGHT_*` tokens (`--light-*`). `web/app/globals.css` maps them to the site's
diff --git a/PRODUCT.md b/PRODUCT.md
index 689de6c5b9..36f74456d0 100644
--- a/PRODUCT.md
+++ b/PRODUCT.md
@@ -78,11 +78,14 @@ no hosted runtime to sell.
- Voice: quiet, dense, factual. Terminal vocabulary, no marketing superlatives,
no fabricated transcripts or reasoning traces.
- "It doesn't need to look special — it needs to look like Codewhale."
-- Assets: the founder's whale mark traced to `brand/mark.svg` (with
- `brand/mark-navy.svg`, `brand/mark-gradient.svg`) and the founder's rounded
- monoline wordmark traced from `brand/wordmark0901.png` (`brand/wordmark.svg`
- navy #142352, `brand/wordmark-inverted.svg` white; regenerated with the site
- icons by `scripts/brand/trace-brand.py`). Web copies live in
+- Assets: the canonical vector family lives in the CWC repo at
+ `codewhale-apps/packages/brand/svg/` (mark, mark-gradient, mark-mono,
+ mark-reversed, wordmark, wordmark-inverted); `brand/` and
+ `web/public/brand/` carry byte-identical copies — sync from there, never
+ re-trace. The founder's brand sheet `brand/codewhalemarkfinal.png` remains
+ the source `scripts/brand/braille-mark.py` derives TUI launch art from. The
+ earlier local trace and `scripts/brand/trace-brand.py` were retired
+ 2026-09-15 in favor of the canonical family. Web copies live in
`web/public/brand/`.
- Palette, type, shell direction, and the anti-slop rules are recorded in
`DESIGN.md`; the colour tokens are owned by `crates/tui/src/palette/tokens.rs`
diff --git a/brand/mark-gradient.svg b/brand/mark-gradient.svg
index 412eb6c121..412e4c14d9 100644
--- a/brand/mark-gradient.svg
+++ b/brand/mark-gradient.svg
@@ -1 +1,9 @@
-
+
diff --git a/brand/mark-mono.svg b/brand/mark-mono.svg
new file mode 100644
index 0000000000..f67e8d277b
--- /dev/null
+++ b/brand/mark-mono.svg
@@ -0,0 +1,3 @@
+
diff --git a/brand/mark-navy.svg b/brand/mark-navy.svg
deleted file mode 100644
index 4272f677ae..0000000000
--- a/brand/mark-navy.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/brand/mark-reversed.svg b/brand/mark-reversed.svg
new file mode 100644
index 0000000000..90ceae43ce
--- /dev/null
+++ b/brand/mark-reversed.svg
@@ -0,0 +1,3 @@
+
diff --git a/brand/mark.svg b/brand/mark.svg
index 75b6a4d86d..71d3b534c6 100644
--- a/brand/mark.svg
+++ b/brand/mark.svg
@@ -1 +1,3 @@
-
+
diff --git a/brand/wordmark-inverted.svg b/brand/wordmark-inverted.svg
index e537014177..b0bb87e19f 100644
--- a/brand/wordmark-inverted.svg
+++ b/brand/wordmark-inverted.svg
@@ -1 +1,3 @@
-
+
diff --git a/brand/wordmark.svg b/brand/wordmark.svg
index 81133ab7e2..03d61e8957 100644
--- a/brand/wordmark.svg
+++ b/brand/wordmark.svg
@@ -1 +1,3 @@
-
+
diff --git a/brand/wordmark0901.png b/brand/wordmark0901.png
deleted file mode 100644
index 23f403178f..0000000000
Binary files a/brand/wordmark0901.png and /dev/null differ
diff --git a/brand/wordmarkinverted.png b/brand/wordmarkinverted.png
deleted file mode 100644
index e66c3fb32a..0000000000
Binary files a/brand/wordmarkinverted.png and /dev/null differ
diff --git a/crates/config/src/settings_schema.rs b/crates/config/src/settings_schema.rs
index f854d00de5..d5ce80123b 100644
--- a/crates/config/src/settings_schema.rs
+++ b/crates/config/src/settings_schema.rs
@@ -404,7 +404,7 @@ pub const SETTINGS_SCHEMA: &[SettingDef] = &[
def(
"theme",
SettingKind::String,
- "underwater",
+ "shoreline",
ui(
TAB_APPEARANCE,
"display",
diff --git a/crates/palette/src/adapt.rs b/crates/palette/src/adapt.rs
index 8207a275c5..fda68220c2 100644
--- a/crates/palette/src/adapt.rs
+++ b/crates/palette/src/adapt.rs
@@ -199,13 +199,18 @@ const fn theme_diff_deleted_bg(ui: &UiTheme) -> Color {
/// Returns `true` if the preset participates in the cell-level remap. The
/// default Whale and System themes pass through unchanged so this whole
-/// stage compiles down to a single load+compare on the hot path.
+/// stage compiles down to a single load+compare on the hot path. Shoreline is
+/// listed because it is a full re-ink — warm charcoal instead of the navy the
+/// direct terminal constants were tuned for — so every one of those call
+/// sites has to land on the preset's slots.
#[inline]
#[must_use]
pub const fn theme_remap_active(theme: ThemeId) -> bool {
matches!(
theme,
ThemeId::Terminal
+ | ThemeId::Shoreline
+ | ThemeId::ShorelineLight
| ThemeId::CatppuccinMocha
| ThemeId::TokyoNight
| ThemeId::Dracula
diff --git a/crates/palette/src/themes.rs b/crates/palette/src/themes.rs
index 6dfaa0ecf3..6e6bc3c104 100644
--- a/crates/palette/src/themes.rs
+++ b/crates/palette/src/themes.rs
@@ -336,6 +336,443 @@ pub const LIGHT_UI_THEME: UiTheme = UiTheme {
}
.with_terminal_native_shell();
+/// Shoreline — the default shell from 0.9.14, and the terminal's half of the
+/// product-client palette.
+///
+/// The GPUI desktop window and this terminal now cite the same slots, so the
+/// two clients read as one product: a warm charcoal ground, one raised plate
+/// for cards and the composer, one restrained blue for action and selection,
+/// and the whale's ivory ink. It does **not** call
+/// [`UiTheme::with_terminal_native_shell`] — the field is painted, the way the
+/// desktop window paints it — and `adapt::theme_remap_active` remaps every
+/// direct terminal constant onto these slots, so nothing navy-tuned leaks
+/// through.
+///
+/// The ocean is not gone, it is a choice: `underwater` keeps the painted
+/// water column, the ambient life and the ombre.
+///
+/// Every pair `contrast::theme_contrast_violations` audits clears its floor.
+pub const SHORELINE_UI_THEME: UiTheme = UiTheme {
+ name: "shoreline",
+ mode: PaletteMode::Dark,
+ surface_bg: Color::Rgb(
+ SHORELINE_SURFACE_RGB.0,
+ SHORELINE_SURFACE_RGB.1,
+ SHORELINE_SURFACE_RGB.2,
+ ),
+ panel_bg: Color::Rgb(
+ SHORELINE_PANEL_RGB.0,
+ SHORELINE_PANEL_RGB.1,
+ SHORELINE_PANEL_RGB.2,
+ ),
+ elevated_bg: Color::Rgb(
+ SHORELINE_ELEVATED_RGB.0,
+ SHORELINE_ELEVATED_RGB.1,
+ SHORELINE_ELEVATED_RGB.2,
+ ),
+ composer_bg: Color::Rgb(
+ SHORELINE_COMPOSER_RGB.0,
+ SHORELINE_COMPOSER_RGB.1,
+ SHORELINE_COMPOSER_RGB.2,
+ ),
+ selection_bg: Color::Rgb(
+ SHORELINE_SELECTION_RGB.0,
+ SHORELINE_SELECTION_RGB.1,
+ SHORELINE_SELECTION_RGB.2,
+ ),
+ header_bg: Color::Rgb(
+ SHORELINE_CHROME_RGB.0,
+ SHORELINE_CHROME_RGB.1,
+ SHORELINE_CHROME_RGB.2,
+ ),
+ footer_bg: Color::Rgb(
+ SHORELINE_CHROME_RGB.0,
+ SHORELINE_CHROME_RGB.1,
+ SHORELINE_CHROME_RGB.2,
+ ),
+ text_dim: Color::Rgb(
+ SHORELINE_TEXT_DIM_RGB.0,
+ SHORELINE_TEXT_DIM_RGB.1,
+ SHORELINE_TEXT_DIM_RGB.2,
+ ),
+ text_hint: Color::Rgb(
+ SHORELINE_TEXT_HINT_RGB.0,
+ SHORELINE_TEXT_HINT_RGB.1,
+ SHORELINE_TEXT_HINT_RGB.2,
+ ),
+ text_muted: Color::Rgb(
+ SHORELINE_TEXT_MUTED_RGB.0,
+ SHORELINE_TEXT_MUTED_RGB.1,
+ SHORELINE_TEXT_MUTED_RGB.2,
+ ),
+ text_body: Color::Rgb(
+ SHORELINE_TEXT_BODY_RGB.0,
+ SHORELINE_TEXT_BODY_RGB.1,
+ SHORELINE_TEXT_BODY_RGB.2,
+ ),
+ text_soft: Color::Rgb(
+ SHORELINE_TEXT_SOFT_RGB.0,
+ SHORELINE_TEXT_SOFT_RGB.1,
+ SHORELINE_TEXT_SOFT_RGB.2,
+ ),
+ border: Color::Rgb(
+ SHORELINE_BORDER_RGB.0,
+ SHORELINE_BORDER_RGB.1,
+ SHORELINE_BORDER_RGB.2,
+ ),
+ accent_primary: Color::Rgb(
+ SHORELINE_ACTION_RGB.0,
+ SHORELINE_ACTION_RGB.1,
+ SHORELINE_ACTION_RGB.2,
+ ),
+ accent_secondary: Color::Rgb(
+ SHORELINE_LIVE_RGB.0,
+ SHORELINE_LIVE_RGB.1,
+ SHORELINE_LIVE_RGB.2,
+ ),
+ accent_action: Color::Rgb(
+ SHORELINE_HUMAN_RGB.0,
+ SHORELINE_HUMAN_RGB.1,
+ SHORELINE_HUMAN_RGB.2,
+ ),
+ error_fg: Color::Rgb(
+ SHORELINE_ERROR_RGB.0,
+ SHORELINE_ERROR_RGB.1,
+ SHORELINE_ERROR_RGB.2,
+ ),
+ error_hover: Color::Rgb(
+ SHORELINE_ERROR_HOVER_RGB.0,
+ SHORELINE_ERROR_HOVER_RGB.1,
+ SHORELINE_ERROR_HOVER_RGB.2,
+ ),
+ error_surface: Color::Rgb(
+ SHORELINE_ERROR_SURFACE_RGB.0,
+ SHORELINE_ERROR_SURFACE_RGB.1,
+ SHORELINE_ERROR_SURFACE_RGB.2,
+ ),
+ error_border: Color::Rgb(
+ SHORELINE_ERROR_RGB.0,
+ SHORELINE_ERROR_RGB.1,
+ SHORELINE_ERROR_RGB.2,
+ ),
+ error_text: Color::Rgb(
+ SHORELINE_ERROR_TEXT_RGB.0,
+ SHORELINE_ERROR_TEXT_RGB.1,
+ SHORELINE_ERROR_TEXT_RGB.2,
+ ),
+ warning: Color::Rgb(
+ SHORELINE_WARNING_RGB.0,
+ SHORELINE_WARNING_RGB.1,
+ SHORELINE_WARNING_RGB.2,
+ ),
+ success: Color::Rgb(
+ SHORELINE_SUCCESS_RGB.0,
+ SHORELINE_SUCCESS_RGB.1,
+ SHORELINE_SUCCESS_RGB.2,
+ ),
+ info: Color::Rgb(
+ SHORELINE_ACTION_RGB.0,
+ SHORELINE_ACTION_RGB.1,
+ SHORELINE_ACTION_RGB.2,
+ ),
+ mode_agent: Color::Rgb(
+ SHORELINE_MODE_AGENT_RGB.0,
+ SHORELINE_MODE_AGENT_RGB.1,
+ SHORELINE_MODE_AGENT_RGB.2,
+ ),
+ mode_yolo: Color::Rgb(
+ SHORELINE_MODE_YOLO_RGB.0,
+ SHORELINE_MODE_YOLO_RGB.1,
+ SHORELINE_MODE_YOLO_RGB.2,
+ ),
+ mode_plan: Color::Rgb(
+ SHORELINE_MODE_PLAN_RGB.0,
+ SHORELINE_MODE_PLAN_RGB.1,
+ SHORELINE_MODE_PLAN_RGB.2,
+ ),
+ mode_operate: Color::Rgb(
+ SHORELINE_MODE_OPERATE_RGB.0,
+ SHORELINE_MODE_OPERATE_RGB.1,
+ SHORELINE_MODE_OPERATE_RGB.2,
+ ),
+ permission_ask: Color::Rgb(
+ SHORELINE_TEXT_MUTED_RGB.0,
+ SHORELINE_TEXT_MUTED_RGB.1,
+ SHORELINE_TEXT_MUTED_RGB.2,
+ ),
+ permission_auto_review: Color::Rgb(
+ SHORELINE_HUMAN_RGB.0,
+ SHORELINE_HUMAN_RGB.1,
+ SHORELINE_HUMAN_RGB.2,
+ ),
+ permission_full_access: Color::Rgb(
+ SHORELINE_WARNING_RGB.0,
+ SHORELINE_WARNING_RGB.1,
+ SHORELINE_WARNING_RGB.2,
+ ),
+ status_ready: Color::Rgb(
+ SHORELINE_SUCCESS_RGB.0,
+ SHORELINE_SUCCESS_RGB.1,
+ SHORELINE_SUCCESS_RGB.2,
+ ),
+ status_working: Color::Rgb(
+ SHORELINE_STATUS_WORKING_RGB.0,
+ SHORELINE_STATUS_WORKING_RGB.1,
+ SHORELINE_STATUS_WORKING_RGB.2,
+ ),
+ status_warning: Color::Rgb(
+ SHORELINE_WARNING_RGB.0,
+ SHORELINE_WARNING_RGB.1,
+ SHORELINE_WARNING_RGB.2,
+ ),
+ diff_added_fg: Color::Rgb(
+ SHORELINE_DIFF_ADDED_FG_RGB.0,
+ SHORELINE_DIFF_ADDED_FG_RGB.1,
+ SHORELINE_DIFF_ADDED_FG_RGB.2,
+ ),
+ diff_deleted_fg: Color::Rgb(
+ SHORELINE_DIFF_DELETED_FG_RGB.0,
+ SHORELINE_DIFF_DELETED_FG_RGB.1,
+ SHORELINE_DIFF_DELETED_FG_RGB.2,
+ ),
+ diff_added_bg: Color::Rgb(
+ SHORELINE_DIFF_ADDED_BG_RGB.0,
+ SHORELINE_DIFF_ADDED_BG_RGB.1,
+ SHORELINE_DIFF_ADDED_BG_RGB.2,
+ ),
+ diff_deleted_bg: Color::Rgb(
+ SHORELINE_DIFF_DELETED_BG_RGB.0,
+ SHORELINE_DIFF_DELETED_BG_RGB.1,
+ SHORELINE_DIFF_DELETED_BG_RGB.2,
+ ),
+ tool_running: Color::Rgb(
+ SHORELINE_LIVE_RGB.0,
+ SHORELINE_LIVE_RGB.1,
+ SHORELINE_LIVE_RGB.2,
+ ),
+ tool_success: Color::Rgb(
+ SHORELINE_SUCCESS_RGB.0,
+ SHORELINE_SUCCESS_RGB.1,
+ SHORELINE_SUCCESS_RGB.2,
+ ),
+ tool_failed: Color::Rgb(
+ SHORELINE_ERROR_RGB.0,
+ SHORELINE_ERROR_RGB.1,
+ SHORELINE_ERROR_RGB.2,
+ ),
+};
+
+/// Shoreline Light — Shoreline on warm paper, for light terminals.
+pub const SHORELINE_LIGHT_UI_THEME: UiTheme = UiTheme {
+ name: "shoreline-light",
+ mode: PaletteMode::Light,
+ surface_bg: Color::Rgb(
+ SHORELINE_LIGHT_SURFACE_RGB.0,
+ SHORELINE_LIGHT_SURFACE_RGB.1,
+ SHORELINE_LIGHT_SURFACE_RGB.2,
+ ),
+ panel_bg: Color::Rgb(
+ SHORELINE_LIGHT_PANEL_RGB.0,
+ SHORELINE_LIGHT_PANEL_RGB.1,
+ SHORELINE_LIGHT_PANEL_RGB.2,
+ ),
+ elevated_bg: Color::Rgb(
+ SHORELINE_LIGHT_ELEVATED_RGB.0,
+ SHORELINE_LIGHT_ELEVATED_RGB.1,
+ SHORELINE_LIGHT_ELEVATED_RGB.2,
+ ),
+ composer_bg: Color::Rgb(
+ SHORELINE_LIGHT_COMPOSER_RGB.0,
+ SHORELINE_LIGHT_COMPOSER_RGB.1,
+ SHORELINE_LIGHT_COMPOSER_RGB.2,
+ ),
+ selection_bg: Color::Rgb(
+ SHORELINE_LIGHT_SELECTION_RGB.0,
+ SHORELINE_LIGHT_SELECTION_RGB.1,
+ SHORELINE_LIGHT_SELECTION_RGB.2,
+ ),
+ header_bg: Color::Rgb(
+ SHORELINE_LIGHT_CHROME_RGB.0,
+ SHORELINE_LIGHT_CHROME_RGB.1,
+ SHORELINE_LIGHT_CHROME_RGB.2,
+ ),
+ footer_bg: Color::Rgb(
+ SHORELINE_LIGHT_CHROME_RGB.0,
+ SHORELINE_LIGHT_CHROME_RGB.1,
+ SHORELINE_LIGHT_CHROME_RGB.2,
+ ),
+ text_dim: Color::Rgb(
+ SHORELINE_LIGHT_TEXT_DIM_RGB.0,
+ SHORELINE_LIGHT_TEXT_DIM_RGB.1,
+ SHORELINE_LIGHT_TEXT_DIM_RGB.2,
+ ),
+ text_hint: Color::Rgb(
+ SHORELINE_LIGHT_TEXT_HINT_RGB.0,
+ SHORELINE_LIGHT_TEXT_HINT_RGB.1,
+ SHORELINE_LIGHT_TEXT_HINT_RGB.2,
+ ),
+ text_muted: Color::Rgb(
+ SHORELINE_LIGHT_TEXT_MUTED_RGB.0,
+ SHORELINE_LIGHT_TEXT_MUTED_RGB.1,
+ SHORELINE_LIGHT_TEXT_MUTED_RGB.2,
+ ),
+ text_body: Color::Rgb(
+ SHORELINE_LIGHT_TEXT_BODY_RGB.0,
+ SHORELINE_LIGHT_TEXT_BODY_RGB.1,
+ SHORELINE_LIGHT_TEXT_BODY_RGB.2,
+ ),
+ text_soft: Color::Rgb(
+ SHORELINE_LIGHT_TEXT_SOFT_RGB.0,
+ SHORELINE_LIGHT_TEXT_SOFT_RGB.1,
+ SHORELINE_LIGHT_TEXT_SOFT_RGB.2,
+ ),
+ border: Color::Rgb(
+ SHORELINE_LIGHT_BORDER_RGB.0,
+ SHORELINE_LIGHT_BORDER_RGB.1,
+ SHORELINE_LIGHT_BORDER_RGB.2,
+ ),
+ accent_primary: Color::Rgb(
+ SHORELINE_LIGHT_ACTION_RGB.0,
+ SHORELINE_LIGHT_ACTION_RGB.1,
+ SHORELINE_LIGHT_ACTION_RGB.2,
+ ),
+ accent_secondary: Color::Rgb(
+ SHORELINE_LIGHT_LIVE_RGB.0,
+ SHORELINE_LIGHT_LIVE_RGB.1,
+ SHORELINE_LIGHT_LIVE_RGB.2,
+ ),
+ accent_action: Color::Rgb(
+ SHORELINE_LIGHT_HUMAN_RGB.0,
+ SHORELINE_LIGHT_HUMAN_RGB.1,
+ SHORELINE_LIGHT_HUMAN_RGB.2,
+ ),
+ error_fg: Color::Rgb(
+ SHORELINE_LIGHT_ERROR_RGB.0,
+ SHORELINE_LIGHT_ERROR_RGB.1,
+ SHORELINE_LIGHT_ERROR_RGB.2,
+ ),
+ error_hover: Color::Rgb(
+ SHORELINE_LIGHT_ERROR_RGB.0,
+ SHORELINE_LIGHT_ERROR_RGB.1,
+ SHORELINE_LIGHT_ERROR_RGB.2,
+ ),
+ error_surface: Color::Rgb(
+ SHORELINE_LIGHT_ERROR_SURFACE_RGB.0,
+ SHORELINE_LIGHT_ERROR_SURFACE_RGB.1,
+ SHORELINE_LIGHT_ERROR_SURFACE_RGB.2,
+ ),
+ error_border: Color::Rgb(
+ SHORELINE_LIGHT_ERROR_RGB.0,
+ SHORELINE_LIGHT_ERROR_RGB.1,
+ SHORELINE_LIGHT_ERROR_RGB.2,
+ ),
+ error_text: Color::Rgb(
+ SHORELINE_LIGHT_ERROR_TEXT_RGB.0,
+ SHORELINE_LIGHT_ERROR_TEXT_RGB.1,
+ SHORELINE_LIGHT_ERROR_TEXT_RGB.2,
+ ),
+ warning: Color::Rgb(
+ SHORELINE_LIGHT_WARNING_RGB.0,
+ SHORELINE_LIGHT_WARNING_RGB.1,
+ SHORELINE_LIGHT_WARNING_RGB.2,
+ ),
+ success: Color::Rgb(
+ SHORELINE_LIGHT_SUCCESS_RGB.0,
+ SHORELINE_LIGHT_SUCCESS_RGB.1,
+ SHORELINE_LIGHT_SUCCESS_RGB.2,
+ ),
+ info: Color::Rgb(
+ SHORELINE_LIGHT_ACTION_RGB.0,
+ SHORELINE_LIGHT_ACTION_RGB.1,
+ SHORELINE_LIGHT_ACTION_RGB.2,
+ ),
+ mode_agent: Color::Rgb(
+ SHORELINE_LIGHT_MODE_AGENT_RGB.0,
+ SHORELINE_LIGHT_MODE_AGENT_RGB.1,
+ SHORELINE_LIGHT_MODE_AGENT_RGB.2,
+ ),
+ mode_yolo: Color::Rgb(
+ SHORELINE_LIGHT_MODE_YOLO_RGB.0,
+ SHORELINE_LIGHT_MODE_YOLO_RGB.1,
+ SHORELINE_LIGHT_MODE_YOLO_RGB.2,
+ ),
+ mode_plan: Color::Rgb(
+ SHORELINE_LIGHT_MODE_PLAN_RGB.0,
+ SHORELINE_LIGHT_MODE_PLAN_RGB.1,
+ SHORELINE_LIGHT_MODE_PLAN_RGB.2,
+ ),
+ mode_operate: Color::Rgb(
+ SHORELINE_LIGHT_MODE_OPERATE_RGB.0,
+ SHORELINE_LIGHT_MODE_OPERATE_RGB.1,
+ SHORELINE_LIGHT_MODE_OPERATE_RGB.2,
+ ),
+ permission_ask: Color::Rgb(
+ SHORELINE_LIGHT_TEXT_MUTED_RGB.0,
+ SHORELINE_LIGHT_TEXT_MUTED_RGB.1,
+ SHORELINE_LIGHT_TEXT_MUTED_RGB.2,
+ ),
+ permission_auto_review: Color::Rgb(
+ SHORELINE_LIGHT_HUMAN_RGB.0,
+ SHORELINE_LIGHT_HUMAN_RGB.1,
+ SHORELINE_LIGHT_HUMAN_RGB.2,
+ ),
+ permission_full_access: Color::Rgb(
+ SHORELINE_LIGHT_WARNING_RGB.0,
+ SHORELINE_LIGHT_WARNING_RGB.1,
+ SHORELINE_LIGHT_WARNING_RGB.2,
+ ),
+ status_ready: Color::Rgb(
+ SHORELINE_LIGHT_SUCCESS_RGB.0,
+ SHORELINE_LIGHT_SUCCESS_RGB.1,
+ SHORELINE_LIGHT_SUCCESS_RGB.2,
+ ),
+ status_working: Color::Rgb(
+ SHORELINE_LIGHT_LIVE_RGB.0,
+ SHORELINE_LIGHT_LIVE_RGB.1,
+ SHORELINE_LIGHT_LIVE_RGB.2,
+ ),
+ status_warning: Color::Rgb(
+ SHORELINE_LIGHT_WARNING_RGB.0,
+ SHORELINE_LIGHT_WARNING_RGB.1,
+ SHORELINE_LIGHT_WARNING_RGB.2,
+ ),
+ diff_added_fg: Color::Rgb(
+ SHORELINE_LIGHT_DIFF_ADDED_FG_RGB.0,
+ SHORELINE_LIGHT_DIFF_ADDED_FG_RGB.1,
+ SHORELINE_LIGHT_DIFF_ADDED_FG_RGB.2,
+ ),
+ diff_deleted_fg: Color::Rgb(
+ SHORELINE_LIGHT_DIFF_DELETED_FG_RGB.0,
+ SHORELINE_LIGHT_DIFF_DELETED_FG_RGB.1,
+ SHORELINE_LIGHT_DIFF_DELETED_FG_RGB.2,
+ ),
+ diff_added_bg: Color::Rgb(
+ SHORELINE_LIGHT_DIFF_ADDED_BG_RGB.0,
+ SHORELINE_LIGHT_DIFF_ADDED_BG_RGB.1,
+ SHORELINE_LIGHT_DIFF_ADDED_BG_RGB.2,
+ ),
+ diff_deleted_bg: Color::Rgb(
+ SHORELINE_LIGHT_DIFF_DELETED_BG_RGB.0,
+ SHORELINE_LIGHT_DIFF_DELETED_BG_RGB.1,
+ SHORELINE_LIGHT_DIFF_DELETED_BG_RGB.2,
+ ),
+ tool_running: Color::Rgb(
+ SHORELINE_LIGHT_LIVE_RGB.0,
+ SHORELINE_LIGHT_LIVE_RGB.1,
+ SHORELINE_LIGHT_LIVE_RGB.2,
+ ),
+ tool_success: Color::Rgb(
+ SHORELINE_LIGHT_SUCCESS_RGB.0,
+ SHORELINE_LIGHT_SUCCESS_RGB.1,
+ SHORELINE_LIGHT_SUCCESS_RGB.2,
+ ),
+ tool_failed: Color::Rgb(
+ SHORELINE_LIGHT_ERROR_RGB.0,
+ SHORELINE_LIGHT_ERROR_RGB.1,
+ SHORELINE_LIGHT_ERROR_RGB.2,
+ ),
+};
+
pub const SOLARIZED_LIGHT_UI_THEME: UiTheme = UiTheme {
name: "solarized-light",
mode: PaletteMode::SolarizedLight,
@@ -888,6 +1325,8 @@ pub const UWU_UI_THEME: UiTheme = UiTheme {
pub enum ThemeId {
System,
Terminal,
+ Shoreline,
+ ShorelineLight,
Underwater,
UnderwaterRetro,
Whale,
@@ -914,6 +1353,8 @@ impl ThemeId {
"terminal" => Some(Self::Terminal),
"underwater" | "deepsea" => Some(Self::Underwater),
"underwater-retro" | "retro" => Some(Self::UnderwaterRetro),
+ "shoreline" => Some(Self::Shoreline),
+ "shoreline-light" => Some(Self::ShorelineLight),
"dark" => Some(Self::Whale),
"light" => Some(Self::WhaleLight),
"grayscale" => Some(Self::Grayscale),
@@ -936,6 +1377,8 @@ impl ThemeId {
match self {
Self::System => "system",
Self::Terminal => "terminal",
+ Self::Shoreline => "shoreline",
+ Self::ShorelineLight => "shoreline-light",
Self::Underwater => "underwater",
Self::UnderwaterRetro => "underwater-retro",
Self::Whale => "dark",
@@ -958,6 +1401,8 @@ impl ThemeId {
match self {
Self::System => "System",
Self::Terminal => "Terminal",
+ Self::Shoreline => "Shoreline",
+ Self::ShorelineLight => "Shoreline Light",
Self::Underwater => "Underwater",
Self::UnderwaterRetro => "Underwater Retro",
Self::Whale => "Blue Stage",
@@ -980,6 +1425,8 @@ impl ThemeId {
match self {
Self::System => "Follow terminal background (COLORFGBG / macOS appearance)",
Self::Terminal => "Inherit terminal colors fully (transparent surfaces, ANSI accents)",
+ Self::Shoreline => "Warm charcoal, one restrained blue — the desktop client's palette",
+ Self::ShorelineLight => "Shoreline on warm paper — the desktop client's light mode",
Self::Underwater => "The painted ocean field: ombre water, ambient life, the whale",
Self::UnderwaterRetro => "Flat phosphor-teal ocean: the legacy deepsea look, no ombre",
Self::Whale => "Stage black, action blue, and one Signal Gold human beacon",
@@ -1007,6 +1454,8 @@ impl ThemeId {
match self {
Self::System => UiTheme::detect(),
Self::Terminal => TERMINAL_UI_THEME,
+ Self::Shoreline => SHORELINE_UI_THEME,
+ Self::ShorelineLight => SHORELINE_LIGHT_UI_THEME,
Self::Underwater => UNDERWATER_UI_THEME,
Self::UnderwaterRetro => UNDERWATER_RETRO_UI_THEME,
Self::Whale => UI_THEME,
@@ -1028,6 +1477,8 @@ impl ThemeId {
pub const SELECTABLE_THEMES: &[ThemeId] = &[
ThemeId::System,
ThemeId::Terminal,
+ ThemeId::Shoreline,
+ ThemeId::ShorelineLight,
ThemeId::Underwater,
ThemeId::UnderwaterRetro,
ThemeId::Whale,
@@ -1090,6 +1541,8 @@ pub fn normalize_theme_name(value: &str) -> Option<&'static str> {
"terminal" | "term" | "transparent" | "follow-terminal" | "inherit" => Some("terminal"),
"underwater" | "deepsea" | "deep-sea" | "ocean" | "ombre" => Some("underwater"),
"underwater-retro" | "retro" | "uw-retro" => Some("underwater-retro"),
+ "shoreline" | "warm" | "charcoal" => Some("shoreline"),
+ "shoreline-light" | "paper" => Some("shoreline-light"),
"dark" | "whale" | "whale-dark" => Some("dark"),
"light" | "whale-light" => Some("light"),
"grayscale" | "greyscale" | "gray" | "grey" | "mono" | "monochrome" | "black-white"
@@ -1165,6 +1618,8 @@ mod tests {
[
"system",
"terminal",
+ "shoreline",
+ "shoreline-light",
"underwater",
"underwater-retro",
"dark",
diff --git a/crates/palette/src/tokens.rs b/crates/palette/src/tokens.rs
index e65112c355..d4c04dc782 100644
--- a/crates/palette/src/tokens.rs
+++ b/crates/palette/src/tokens.rs
@@ -222,6 +222,84 @@ pub const MATRIX_TEXT_SOFT_RGB: (u8, u8, u8) = (221, 255, 221); // #DDFFDD
pub const MATRIX_TEXT_DIM_RGB: (u8, u8, u8) = (0, 108, 0); // #006C00, lifted for 3:1
pub const MATRIX_BORDER_RGB: (u8, u8, u8) = (0, 204, 0); // #00CC00
+// Shoreline — the product-client palette, shared with the GPUI desktop.
+//
+// Warm charcoal ground and warm paper sheet, one restrained blue, and the
+// whale's ivory ink on both sides. This is the default shell from 0.9.14:
+// the navy "Underwater" water column remains a selectable theme, it is no
+// longer the ground the product opens on. Values are the GPUI client's
+// `Theme` slots (`codehwhale-gpui/src/workspace/mod.rs`), so the terminal
+// and the desktop window are one product rather than two visual systems.
+//
+// Every pair audited by `contrast::theme_contrast_violations` clears its
+// floor: body roles clear 4.5:1 on all four surfaces, hint/dim and the
+// status roles clear 3:1.
+pub const SHORELINE_SURFACE_RGB: (u8, u8, u8) = (33, 31, 35); // #211F23 warm charcoal field
+pub const SHORELINE_PANEL_RGB: (u8, u8, u8) = (43, 40, 46); // #2B282E raised plate
+pub const SHORELINE_ELEVATED_RGB: (u8, u8, u8) = (53, 49, 58); // #35313A
+pub const SHORELINE_COMPOSER_RGB: (u8, u8, u8) = (43, 40, 46); // #2B282E
+pub const SHORELINE_CHROME_RGB: (u8, u8, u8) = (26, 24, 28); // #1A181C recessed chrome
+pub const SHORELINE_SELECTION_RGB: (u8, u8, u8) = (53, 73, 103); // #354967
+pub const SHORELINE_TEXT_BODY_RGB: (u8, u8, u8) = (242, 236, 229); // #F2ECE5
+pub const SHORELINE_TEXT_SOFT_RGB: (u8, u8, u8) = (217, 210, 220); // #D9D2DC
+pub const SHORELINE_TEXT_MUTED_RGB: (u8, u8, u8) = (176, 167, 178); // #B0A7B2
+pub const SHORELINE_TEXT_HINT_RGB: (u8, u8, u8) = (154, 145, 159); // #9A919F
+pub const SHORELINE_TEXT_DIM_RGB: (u8, u8, u8) = (126, 117, 131); // #7E7583
+pub const SHORELINE_BORDER_RGB: (u8, u8, u8) = (73, 66, 77); // #49424D
+pub const SHORELINE_ACTION_RGB: (u8, u8, u8) = (144, 185, 255); // #90B9FF the one blue
+pub const SHORELINE_LIVE_RGB: (u8, u8, u8) = (127, 214, 198); // #7FD6C6 the live lane
+pub const SHORELINE_HUMAN_RGB: (u8, u8, u8) = (246, 196, 83); // #F6C453 Signal Gold, the human lane
+pub const SHORELINE_ERROR_RGB: (u8, u8, u8) = (255, 143, 168); // #FF8FA8
+pub const SHORELINE_ERROR_HOVER_RGB: (u8, u8, u8) = (255, 166, 187); // #FFA6BB
+pub const SHORELINE_ERROR_SURFACE_RGB: (u8, u8, u8) = (58, 32, 41); // #3A2029
+pub const SHORELINE_ERROR_TEXT_RGB: (u8, u8, u8) = (255, 220, 230); // #FFDCE6
+pub const SHORELINE_WARNING_RGB: (u8, u8, u8) = (240, 168, 104); // #F0A868
+pub const SHORELINE_SUCCESS_RGB: (u8, u8, u8) = (163, 217, 119); // #A3D977
+pub const SHORELINE_STATUS_WORKING_RGB: (u8, u8, u8) = SHORELINE_LIVE_RGB;
+// Mode badges keep the shipped mode ramp: these four are proof-tested distinct
+// from one another and from every semantic lane, and a mode badge is identity,
+// not decoration. `mode_agent` must differ from `accent_primary` or
+// `adapt::theme_semantic_foreground_role` resolves the action lane to a mode
+// role (`ModeAgent`) before it ever reaches `Action`.
+pub const SHORELINE_MODE_AGENT_RGB: (u8, u8, u8) = (126, 180, 232); // #7EB4E8
+pub const SHORELINE_MODE_YOLO_RGB: (u8, u8, u8) = (255, 112, 160); // #FF70A0
+pub const SHORELINE_MODE_PLAN_RGB: (u8, u8, u8) = (185, 220, 236); // #B9DCEC
+pub const SHORELINE_MODE_OPERATE_RGB: (u8, u8, u8) = (173, 136, 255); // #AD88FF
+pub const SHORELINE_DIFF_ADDED_FG_RGB: (u8, u8, u8) = (127, 214, 160); // #7FD6A0
+pub const SHORELINE_DIFF_ADDED_BG_RGB: (u8, u8, u8) = (30, 44, 37); // #1E2C25
+pub const SHORELINE_DIFF_DELETED_FG_RGB: (u8, u8, u8) = (255, 154, 171); // #FF9AAB
+pub const SHORELINE_DIFF_DELETED_BG_RGB: (u8, u8, u8) = (51, 32, 42); // #33202A
+
+// Shoreline Light — the same system on warm paper.
+pub const SHORELINE_LIGHT_SURFACE_RGB: (u8, u8, u8) = (245, 240, 233); // #F5F0E9
+pub const SHORELINE_LIGHT_PANEL_RGB: (u8, u8, u8) = (236, 229, 224); // #ECE5E0
+pub const SHORELINE_LIGHT_ELEVATED_RGB: (u8, u8, u8) = (255, 252, 247); // #FFFCF7
+pub const SHORELINE_LIGHT_COMPOSER_RGB: (u8, u8, u8) = (251, 247, 241); // #FBF7F1
+pub const SHORELINE_LIGHT_CHROME_RGB: (u8, u8, u8) = (237, 231, 224); // #EDE7E0
+pub const SHORELINE_LIGHT_SELECTION_RGB: (u8, u8, u8) = (200, 217, 249); // #C8D9F9
+pub const SHORELINE_LIGHT_TEXT_BODY_RGB: (u8, u8, u8) = (48, 40, 50); // #302832
+pub const SHORELINE_LIGHT_TEXT_SOFT_RGB: (u8, u8, u8) = (74, 65, 76); // #4A414C
+pub const SHORELINE_LIGHT_TEXT_MUTED_RGB: (u8, u8, u8) = (107, 96, 110); // #6B606E
+pub const SHORELINE_LIGHT_TEXT_HINT_RGB: (u8, u8, u8) = (117, 112, 128); // #757080
+pub const SHORELINE_LIGHT_TEXT_DIM_RGB: (u8, u8, u8) = (138, 130, 144); // #8A8290
+pub const SHORELINE_LIGHT_BORDER_RGB: (u8, u8, u8) = (215, 206, 213); // #D7CED5
+pub const SHORELINE_LIGHT_ACTION_RGB: (u8, u8, u8) = (36, 91, 199); // #245BC7
+pub const SHORELINE_LIGHT_LIVE_RGB: (u8, u8, u8) = (31, 122, 107); // #1F7A6B
+pub const SHORELINE_LIGHT_HUMAN_RGB: (u8, u8, u8) = (122, 85, 0); // #7A5500 Signal Gold at AA on paper
+pub const SHORELINE_LIGHT_ERROR_RGB: (u8, u8, u8) = (180, 35, 90); // #B4235A
+pub const SHORELINE_LIGHT_ERROR_SURFACE_RGB: (u8, u8, u8) = (251, 228, 236); // #FBE4EC
+pub const SHORELINE_LIGHT_ERROR_TEXT_RGB: (u8, u8, u8) = (122, 18, 53); // #7A1235
+pub const SHORELINE_LIGHT_WARNING_RGB: (u8, u8, u8) = (143, 85, 20); // #8F5514
+pub const SHORELINE_LIGHT_SUCCESS_RGB: (u8, u8, u8) = (47, 107, 58); // #2F6B3A
+pub const SHORELINE_LIGHT_MODE_AGENT_RGB: (u8, u8, u8) = (22, 54, 178); // #1636B2 distinct from LIGHT action
+pub const SHORELINE_LIGHT_MODE_YOLO_RGB: (u8, u8, u8) = (181, 35, 90); // #B5235A
+pub const SHORELINE_LIGHT_MODE_PLAN_RGB: (u8, u8, u8) = (52, 92, 128); // #345C80
+pub const SHORELINE_LIGHT_MODE_OPERATE_RGB: (u8, u8, u8) = (112, 71, 184); // #7047B8
+pub const SHORELINE_LIGHT_DIFF_ADDED_FG_RGB: (u8, u8, u8) = (31, 107, 69); // #1F6B45
+pub const SHORELINE_LIGHT_DIFF_ADDED_BG_RGB: (u8, u8, u8) = (226, 242, 230); // #E2F2E6
+pub const SHORELINE_LIGHT_DIFF_DELETED_FG_RGB: (u8, u8, u8) = (168, 40, 80); // #A82850
+pub const SHORELINE_LIGHT_DIFF_DELETED_BG_RGB: (u8, u8, u8) = (251, 230, 236); // #FBE6EC
+
// Semantic colors
pub const BORDER_COLOR_RGB: (u8, u8, u8) = WHALE_BORDER_RGB;
diff --git a/crates/tui/src/runtime_web/codewhale-192.png b/crates/tui/src/runtime_web/codewhale-192.png
index bfb67a6ff0..7513605aee 100644
Binary files a/crates/tui/src/runtime_web/codewhale-192.png and b/crates/tui/src/runtime_web/codewhale-192.png differ
diff --git a/crates/tui/src/settings.rs b/crates/tui/src/settings.rs
index fb167e29fa..a4f94d8e72 100644
--- a/crates/tui/src/settings.rs
+++ b/crates/tui/src/settings.rs
@@ -345,13 +345,14 @@ pub struct Settings {
/// ca, de, fr, id, hi, ru, uk.
/// Every shipped pack holds full `en.json` parity; nothing falls back.
pub locale: String,
- /// Named UI theme. `"underwater"` is the fresh-install default and paints
- /// the ocean field. `"terminal"` fully inherits the host terminal's
- /// foreground/background. `"system"`, `"dark"`, `"light"`,
- /// `"grayscale"`, and the community presets: `"catppuccin-mocha"`,
- /// `"tokyo-night"`, `"dracula"`, `"gruvbox-dark"`. The
- /// `background_color` setting still overrides the surface color on top
- /// of the resolved theme.
+ /// Named UI theme. `"shoreline"` is the fresh-install default: the warm
+ /// charcoal and restrained blue the desktop client paints too.
+ /// `"underwater"` paints the ocean field. `"terminal"` fully inherits the
+ /// host terminal's foreground/background. `"system"`, `"dark"`,
+ /// `"light"`, `"grayscale"`, and the community presets:
+ /// `"catppuccin-mocha"`, `"tokyo-night"`, `"dracula"`,
+ /// `"gruvbox-dark"`. The `background_color` setting still overrides the
+ /// surface color on top of the resolved theme.
pub theme: String,
/// Optional main TUI background color as a 6-digit hex RGB value.
pub background_color: Option,
@@ -571,7 +572,7 @@ impl Default for Settings {
show_tool_details: false,
inline_diffs: "full".to_string(),
locale: "auto".to_string(),
- theme: "underwater".to_string(),
+ theme: "shoreline".to_string(),
background_color: None,
composer_density: "comfortable".to_string(),
composer_border: true,
@@ -2886,9 +2887,9 @@ fn normalize_synchronized_output(value: &str) -> &str {
fn normalize_settings_theme(value: &str) -> String {
// A malformed persisted selector must not turn into a painted application
- // background. Falling back to the underwater default keeps a single
- // compiled first-party theme until the user picks an explicit palette.
- normalize_theme_setting(value).unwrap_or_else(|_| "underwater".to_string())
+ // background. Falling back to the compiled default keeps a single
+ // first-party theme until the user picks an explicit palette.
+ normalize_theme_setting(value).unwrap_or_else(|_| "shoreline".to_string())
}
/// Returns `true` when the active terminal is Ptyxis (the new default
@@ -4032,22 +4033,22 @@ mod tests {
}
#[test]
- fn default_settings_resolve_to_the_underwater_theme() {
- // Slice C: the fresh-install default is the underwater theme, end to
- // end from `Settings::default()` through theme resolution.
+ fn default_settings_resolve_to_the_shoreline_theme() {
+ // The fresh-install default is the Shoreline theme, end to end from
+ // `Settings::default()` through theme resolution.
let settings = Settings::default();
- assert_eq!(settings.theme, "underwater");
+ assert_eq!(settings.theme, "shoreline");
let (name, id, theme) = codewhale_palette::resolve_theme_setting(&settings.theme, None)
.expect("default resolves");
- assert_eq!(id, codewhale_palette::ThemeId::Underwater);
- assert_eq!(name, "underwater");
- assert_eq!(theme.name, "underwater");
+ assert_eq!(id, codewhale_palette::ThemeId::Shoreline);
+ assert_eq!(name, "shoreline");
+ assert_eq!(theme.name, "shoreline");
}
#[test]
fn theme_normalizes_supported_values_and_rejects_unknowns() {
let mut settings = Settings::default();
- assert_eq!(settings.theme, "underwater");
+ assert_eq!(settings.theme, "shoreline");
settings.set("theme", "grayscale").expect("set grayscale");
assert_eq!(settings.theme, "grayscale");
@@ -5482,7 +5483,7 @@ mod tests {
let loaded = Settings::load().expect("load settings");
assert_eq!(
- loaded.theme, "underwater",
+ loaded.theme, "shoreline",
"explicit CODEWHALE_HOME must not inherit ambient legacy settings"
);
assert_eq!(
diff --git a/crates/tui/src/tui/goldens/config_panel_120x32.txt b/crates/tui/src/tui/goldens/config_panel_120x32.txt
index 761b64a452..1893d4eb8a 100644
--- a/crates/tui/src/tui/goldens/config_panel_120x32.txt
+++ b/crates/tui/src/tui/goldens/config_panel_120x32.txt
@@ -15,16 +15,16 @@
│ Help Expand Groups Off [ ] │kind choice
│ Contextual tips On [x] │available not observed this session
│ Pin Last Prompt On [x] │
- │ Tool detail level Off [ ] │system | terminal | underwater |
- │ Inline file changes Full diff ‹ › │underwater-retro | dark | light |
- │ Output pacing auto ‹ › │grayscale | catppuccin-mocha |
- │ Cost currency usd ‹ › │tokyo-night | dracula | gruvbox-dark |
- │ Transcript spacing comfortable ‹ › │claude | matrix | solarized-light | uwu
- │ Tool cards compact ‹ › │Enter or click again: Enter opens
+ │ Tool detail level Off [ ] │system | terminal | shoreline |
+ │ Inline file changes Full diff ‹ › │shoreline-light | underwater |
+ │ Output pacing auto ‹ › │underwater-retro | dark | light |
+ │ Cost currency usd ‹ › │grayscale | catppuccin-mocha |
+ │ Transcript spacing comfortable ‹ › │tokyo-night | dracula | gruvbox-dark |
+ │ Tool cards compact ‹ › │claude | matrix | solarized-light | uwu
+ │ │Enter or click again: Enter opens
│ │choices
│ │
- │ │
- system | terminal | underwater | underwater-retro | dark | light | grayscale | catppuccin-mocha | tokyo-night |…
+ system | terminal | shoreline | shoreline-light | underwater | underwater-retro | dark | light | grayscale |…
Preview: ▶▶ ask · agent
type=filter, Up/Down=select, Enter=edit, Esc=close
diff --git a/crates/tui/src/tui/goldens/config_panel_80x24.txt b/crates/tui/src/tui/goldens/config_panel_80x24.txt
index b28af81691..c4ce3e0ad5 100644
--- a/crates/tui/src/tui/goldens/config_panel_80x24.txt
+++ b/crates/tui/src/tui/goldens/config_panel_80x24.txt
@@ -14,7 +14,7 @@
Reasoning background highlight On [x] SAVED │
Help Expand Groups Off [ ] SAVED │
Contextual tips On [x] SAVED │
- system | terminal | underwater | underwater-retro | dark | light |…
+ system | terminal | shoreline | shoreline-light | underwater |…
Enter or click again: Enter opens choices · Theme: current terminal · saved
terminal · applies on save
Preview: ▶▶ ask · agent
diff --git a/crates/tui/src/tui/goldens/edit_theme_120x32.txt b/crates/tui/src/tui/goldens/edit_theme_120x32.txt
index 043e5798c9..1d8eb23629 100644
--- a/crates/tui/src/tui/goldens/edit_theme_120x32.txt
+++ b/crates/tui/src/tui/goldens/edit_theme_120x32.txt
@@ -4,26 +4,26 @@
Edit Theme [theme]
Scope: SAVED
- Current: underwater
+ Current: shoreline
Choose:
1. system
2. terminal
- ▸ 3. underwater
- 4. underwater-retro
- 5. dark
- 6. light
- 7. grayscale
- 8. catppuccin-mocha
- 9. tokyo-night
- 10. dracula
- 11. gruvbox-dark
- 12. claude
- 13. matrix
- 14. solarized-light
- 15. uwu
-
-
+ ▸ 3. shoreline
+ 4. shoreline-light
+ 5. underwater
+ 6. underwater-retro
+ 7. dark
+ 8. light
+ 9. grayscale
+ 10. catppuccin-mocha
+ 11. tokyo-night
+ 12. dracula
+ 13. gruvbox-dark
+ 14. claude
+ 15. matrix
+ 16. solarized-light
+ 17. uwu
[ Apply ] [ Cancel ]
↑/↓ or click choose · Enter/Apply · Esc/Cancel · 1-9 jump
diff --git a/crates/tui/src/tui/goldens/edit_theme_80x24.txt b/crates/tui/src/tui/goldens/edit_theme_80x24.txt
index b1bdf46b88..e13d606cb6 100644
--- a/crates/tui/src/tui/goldens/edit_theme_80x24.txt
+++ b/crates/tui/src/tui/goldens/edit_theme_80x24.txt
@@ -4,19 +4,19 @@
Edit Theme [theme]
Scope: SAVED
- Current: underwater
+ Current: shoreline
Choose:
1. system
2. terminal
- ▸ 3. underwater
- 4. underwater-retro
- 5. dark
- 6. light
- 7. grayscale
- 8. catppuccin-mocha
- 9. tokyo-night
- 10. dracula
+ ▸ 3. shoreline
+ 4. shoreline-light
+ 5. underwater
+ 6. underwater-retro
+ 7. dark
+ 8. light
+ 9. grayscale
+ 10. catppuccin-mocha
[ Apply ] [ Cancel ]
↑/↓ or click choose · Enter/Apply · Esc/Cancel · 1-9 jump
diff --git a/crates/tui/src/tui/goldens/settings_100x30.txt b/crates/tui/src/tui/goldens/settings_100x30.txt
index daac23af3c..c38e7a5a7c 100644
--- a/crates/tui/src/tui/goldens/settings_100x30.txt
+++ b/crates/tui/src/tui/goldens/settings_100x30.txt
@@ -1,10 +1,12 @@
▸ Appearance System PREVIEW · Blue Stage
Models & providers Terminal ▎ restyle the work stage
- Work Underwater ├── whale-1 · footer band
- Tools & MCP Underwater Retro └── whale-2 · goldens
- Trust [ ✓ Blue Stage ] ● working whale-1 editing · 14:41:02 ×
- Motion Blue Stage Light ✓ done whale-2 surfaced ✓ · 14:39:02 ×
- Advanced Grayscale done: stage restyled
+ Work Shoreline ├── whale-1 · footer band
+ Tools & MCP Shoreline Light └── whale-2 · goldens
+ Trust Underwater ● working whale-1 editing · 14:41:02 ×
+ Motion Underwater Retro ✓ done whale-2 surfaced ✓ · 14:39:02 ×
+ Advanced [ ✓ Blue Stage ] done: stage restyled
+ Blue Stage Light
+ Grayscale
Catppuccin Mocha
Tokyo Night
Dracula
@@ -16,8 +18,6 @@
MOTION (OPTIONAL)
○ low motion
◉ ambient life
-
-
● working ○ ready ✓ done ! cauti
FLEET LEDGER
WHALE │ASSIGNMENT │STATE
diff --git a/crates/tui/src/tui/goldens/settings_120x32.txt b/crates/tui/src/tui/goldens/settings_120x32.txt
index 3b6bb3036f..e2d0390cc3 100644
--- a/crates/tui/src/tui/goldens/settings_120x32.txt
+++ b/crates/tui/src/tui/goldens/settings_120x32.txt
@@ -1,10 +1,12 @@
▸ Appearance System PREVIEW · Blue Stage
Models & providers Terminal ▎ restyle the work stage
- Work Underwater ├── whale-1 · footer band
- Tools & MCP Underwater Retro └── whale-2 · goldens
- Trust [ ✓ Blue Stage ] ● working whale-1 editing · 14:41:02 ×12
- Motion Blue Stage Light ✓ done whale-2 surfaced ✓ · 14:39:02 ×34
- Advanced Grayscale done: stage restyled
+ Work Shoreline ├── whale-1 · footer band
+ Tools & MCP Shoreline Light └── whale-2 · goldens
+ Trust Underwater ● working whale-1 editing · 14:41:02 ×12
+ Motion Underwater Retro ✓ done whale-2 surfaced ✓ · 14:39:02 ×34
+ Advanced [ ✓ Blue Stage ] done: stage restyled
+ Blue Stage Light
+ Grayscale
Catppuccin Mocha
Tokyo Night
Dracula
@@ -18,8 +20,6 @@
◉ ambient life
-
-
● working ○ ready ✓ done ! caution ✗ f
FLEET LEDGER
WHALE │ASSIGNMENT │STATE
diff --git a/crates/tui/src/tui/goldens/settings_160x40.txt b/crates/tui/src/tui/goldens/settings_160x40.txt
index 6341b484df..61e78a6cbc 100644
--- a/crates/tui/src/tui/goldens/settings_160x40.txt
+++ b/crates/tui/src/tui/goldens/settings_160x40.txt
@@ -1,10 +1,12 @@
▸ Appearance System PREVIEW · Blue Stage
Models & providers Terminal ▎ restyle the work stage
- Work Underwater ├── whale-1 · footer band
- Tools & MCP Underwater Retro └── whale-2 · goldens
- Trust [ ✓ Blue Stage ] ● working whale-1 editing · 14:41:02 ×12
- Motion Blue Stage Light ✓ done whale-2 surfaced ✓ · 14:39:02 ×34
- Advanced Grayscale done: stage restyled
+ Work Shoreline ├── whale-1 · footer band
+ Tools & MCP Shoreline Light └── whale-2 · goldens
+ Trust Underwater ● working whale-1 editing · 14:41:02 ×12
+ Motion Underwater Retro ✓ done whale-2 surfaced ✓ · 14:39:02 ×34
+ Advanced [ ✓ Blue Stage ] done: stage restyled
+ Blue Stage Light
+ Grayscale
Catppuccin Mocha
Tokyo Night
Dracula
@@ -26,8 +28,6 @@
-
-
● working ○ ready ✓ done ! caution ✗ failed
FLEET LEDGER
WHALE │ASSIGNMENT │STATE
diff --git a/crates/tui/src/tui/goldens/settings_80x24.txt b/crates/tui/src/tui/goldens/settings_80x24.txt
index 5bd549168f..b815ce44cb 100644
--- a/crates/tui/src/tui/goldens/settings_80x24.txt
+++ b/crates/tui/src/tui/goldens/settings_80x24.txt
@@ -1,6 +1,8 @@
Appearance Models & providers Work Tools & MCP Trust Motion ›
System
Terminal
+ Shoreline
+ Shoreline Light
Underwater
Underwater Retro
[ ✓ Blue Stage ]
diff --git a/crates/tui/src/tui/goldens/theme_picker_120x32.txt b/crates/tui/src/tui/goldens/theme_picker_120x32.txt
index 17ad567471..6ac9c53a45 100644
--- a/crates/tui/src/tui/goldens/theme_picker_120x32.txt
+++ b/crates/tui/src/tui/goldens/theme_picker_120x32.txt
@@ -4,21 +4,21 @@
1. System Follow terminal background (COLORFGBG / macOS appearance)
2. Terminal Inherit terminal colors fully (transparent surfaces, ANSI accents)
- ▸ 3. Underwater The painted ocean field: ombre water, ambient life, the whale
- 4. Underwater Retro Flat phosphor-teal ocean: the legacy deepsea look, no ombre
- 5. Blue Stage Stage black, action blue, and one Signal Gold human beacon
- 6. Blue Stage Light Paper, cobalt action, and one Signal Gold human beacon
- 7. Grayscale Color-minimal high contrast
- 8. Catppuccin Mocha Soft pastels on warm dark
- 9. Tokyo Night Deep blue/violet night palette
- 10. Dracula Classic high-contrast purple
- 11. Gruvbox Dark Vintage warm earth tones
- 12. Claude Warm navy & coral
- 13. Matrix The Matrix films inspired theme
- 14. Solarized Light Solarized light — Light, calming palette on warm ivory — easy on the eyes
- 15. Uwu Soft kawaii night — sakura, mint, and peach
-
-
+ 3. Shoreline Warm charcoal, one restrained blue — the desktop client's palette
+ 4. Shoreline Light Shoreline on warm paper — the desktop client's light mode
+ ▸ 5. Underwater The painted ocean field: ombre water, ambient life, the whale
+ 6. Underwater Retro Flat phosphor-teal ocean: the legacy deepsea look, no ombre
+ 7. Blue Stage Stage black, action blue, and one Signal Gold human beacon
+ 8. Blue Stage Light Paper, cobalt action, and one Signal Gold human beacon
+ 9. Grayscale Color-minimal high contrast
+ 10. Catppuccin Mocha Soft pastels on warm dark
+ 11. Tokyo Night Deep blue/violet night palette
+ 12. Dracula Classic high-contrast purple
+ 13. Gruvbox Dark Vintage warm earth tones
+ 14. Claude Warm navy & coral
+ 15. Matrix The Matrix films inspired theme
+ 16. Solarized Light Solarized light — Light, calming palette on warm ivory — easy on the eyes
+ 17. Uwu Soft kawaii night — sakura, mint, and peach
diff --git a/crates/tui/src/tui/goldens/theme_picker_80x24.txt b/crates/tui/src/tui/goldens/theme_picker_80x24.txt
index ddbf11dd0a..584fc1ec1a 100644
--- a/crates/tui/src/tui/goldens/theme_picker_80x24.txt
+++ b/crates/tui/src/tui/goldens/theme_picker_80x24.txt
@@ -1,23 +1,23 @@
theme · live preview ────────────────────────────────────────────────────────
-
- 1. System Follow terminal background…
- 2. Terminal Inherit terminal colors fully…
- ▸ 3. Underwater The painted ocean field: ombre…
- 4. Underwater Retro Flat phosphor-teal ocean: the…
- 5. Blue Stage Stage black, action blue, and one…
- 6. Blue Stage Light Paper, cobalt action, and one…
- 7. Grayscale Color-minimal high contrast
- 8. Catppuccin Mocha Soft pastels on warm dark
- 9. Tokyo Night Deep blue/violet night palette
- 10. Dracula Classic high-contrast purple
- 11. Gruvbox Dark Vintage warm earth tones
- 12. Claude Warm navy & coral
- 13. Matrix The Matrix films inspired theme
- 14. Solarized Light Solarized light — Light, calming…
- 15. Uwu Soft kawaii night — sakura, mint,…
-
+ █
+ 1. System Follow terminal background… █
+ 2. Terminal Inherit terminal colors fully… █
+ 3. Shoreline Warm charcoal, one restrained… █
+ 4. Shoreline Light Shoreline on warm paper — the… █
+ ▸ 5. Underwater The painted ocean field: ombre… █
+ 6. Underwater Retro Flat phosphor-teal ocean: the… █
+ 7. Blue Stage Stage black, action blue, and one… █
+ 8. Blue Stage Light Paper, cobalt action, and one… █
+ 9. Grayscale Color-minimal high contrast █
+ 10. Catppuccin Mocha Soft pastels on warm dark █
+ 11. Tokyo Night Deep blue/violet night palette █
+ 12. Dracula Classic high-contrast purple █
+ 13. Gruvbox Dark Vintage warm earth tones █
+ 14. Claude Warm navy & coral █
+ 15. Matrix The Matrix films inspired theme █
+ 16. Solarized Light Solarized light — Light, calming… │
↑/↓ preview Enter save Esc revert
──────────────────────────────────────────────────────────────────────────────
diff --git a/crates/tui/src/tui/theme_picker.rs b/crates/tui/src/tui/theme_picker.rs
index 5a25fa6840..e0eee8db0e 100644
--- a/crates/tui/src/tui/theme_picker.rs
+++ b/crates/tui/src/tui/theme_picker.rs
@@ -226,9 +226,9 @@ fn theme_options_with_custom(
.help("Pick a theme with live preview")
.values(SettingValues::new(
Cow::Owned(current.clone()),
- // A reset returns to the underwater default, not a
+ // A reset returns to the Shoreline default, not a
// detected palette that can repaint it.
- Cow::Borrowed("underwater"),
+ Cow::Borrowed("shoreline"),
Cow::Borrowed(name),
))
.availability(SettingAvailability::Available)
@@ -255,7 +255,7 @@ fn theme_options_with_custom(
.help("Pick a user-authored theme overlay")
.values(SettingValues::new(
Cow::Owned(current.clone()),
- Cow::Borrowed("underwater"),
+ Cow::Borrowed("shoreline"),
Cow::Owned(custom.selector.clone()),
))
.availability(SettingAvailability::Available)
@@ -612,11 +612,11 @@ mod tests {
#[test]
fn enter_commits_with_persist_true() {
let mut v = ThemePickerView::new("system".to_string());
- v.handle_key(key(KeyCode::Char('8'))); // -> CatppuccinMocha
+ v.handle_key(key(KeyCode::Char('9'))); // -> Grayscale
let action = v.handle_key(key(KeyCode::Enter));
match action {
ViewAction::EmitAndClose(ViewEvent::ThemeSelectionUpdated { theme, persist }) => {
- assert_eq!(theme, ThemeId::CatppuccinMocha.name());
+ assert_eq!(theme, ThemeId::Grayscale.name());
assert!(persist);
}
other => panic!("expected commit, got {other:?}"),
@@ -706,11 +706,11 @@ mod tests {
}
#[test]
- fn digit_jumps_to_underwater_and_previews() {
+ fn digit_jumps_to_shoreline_and_previews() {
let mut v = ThemePickerView::new("system".to_string());
let action = v.handle_key(key(KeyCode::Char('3')));
- // Underwater follows System and Terminal.
- assert_eq!(selected_values(&action), Some(("underwater", false)));
+ // Shoreline follows System and Terminal.
+ assert_eq!(selected_values(&action), Some(("shoreline", false)));
}
#[test]
diff --git a/crates/tui/src/tui/theme_picker/tideline_tests.rs b/crates/tui/src/tui/theme_picker/tideline_tests.rs
index 3357e996f3..e747925b05 100644
--- a/crates/tui/src/tui/theme_picker/tideline_tests.rs
+++ b/crates/tui/src/tui/theme_picker/tideline_tests.rs
@@ -7,8 +7,8 @@ use crate::tui::golden_harness::render_golden_text;
use codewhale_palette::SELECTABLE_THEMES;
#[test]
-fn theme_rows_are_the_fourteen_selectable_themes() {
- assert_eq!(tideline_theme_rows().len(), 15);
+fn theme_rows_are_the_seventeen_selectable_themes() {
+ assert_eq!(tideline_theme_rows().len(), 17);
assert_eq!(tideline_theme_rows().as_slice(), SELECTABLE_THEMES);
}
diff --git a/crates/tui/src/tui/views/mod.rs b/crates/tui/src/tui/views/mod.rs
index 6a8880ed99..3740d17b3e 100644
--- a/crates/tui/src/tui/views/mod.rs
+++ b/crates/tui/src/tui/views/mod.rs
@@ -10404,7 +10404,7 @@ context_window = 262144
"Enter must open the theme editor"
);
- // ↓ highlights underwater: preview (persist:false), editor stays open.
+ // ↓ highlights shoreline: preview (persist:false), editor stays open.
match key(&mut view, KeyCode::Down) {
ViewAction::Emit(ViewEvent::ConfigUpdated {
key,
@@ -10412,7 +10412,7 @@ context_window = 262144
persist,
}) => {
assert_eq!(key, "theme");
- assert_eq!(value, "underwater");
+ assert_eq!(value, "shoreline");
assert!(!persist, "highlighting must not persist");
}
other => panic!("highlight must preview, got {other:?}"),
@@ -10454,7 +10454,7 @@ context_window = 262144
persist,
}) => {
assert_eq!(key, "theme");
- assert_eq!(value, "underwater");
+ assert_eq!(value, "shoreline");
assert!(persist, "Apply must persist");
}
other => panic!("enter must persist the highlight, got {other:?}"),
@@ -10529,14 +10529,14 @@ context_window = 262144
modifiers: KeyModifiers::NONE,
})
};
- // Choice index 2 is underwater (system, terminal, underwater, …).
+ // Choice index 2 is shoreline (system, terminal, shoreline, …).
let (rect, _) = view
.last_choice_hitboxes
.borrow()
.iter()
.copied()
.find(|(_, idx)| *idx == 2)
- .expect("rendered underwater hitbox");
+ .expect("rendered shoreline hitbox");
match hover(&mut view, rect.x, rect.y) {
ViewAction::Emit(ViewEvent::ConfigUpdated {
key,
@@ -10544,7 +10544,7 @@ context_window = 262144
persist,
}) => {
assert_eq!(key, "theme");
- assert_eq!(value, "underwater");
+ assert_eq!(value, "shoreline");
assert!(!persist, "hover preview must not persist");
}
other => panic!("hover must preview, got {other:?}"),
diff --git a/crates/tui/src/tui/views/tideline_tests.rs b/crates/tui/src/tui/views/tideline_tests.rs
index 6cecf07724..003ac63a7a 100644
--- a/crates/tui/src/tui/views/tideline_tests.rs
+++ b/crates/tui/src/tui/views/tideline_tests.rs
@@ -29,7 +29,9 @@ fn draw_stage(width: u16, height: u16) -> String {
ascii_safe: false,
locale: Locale::En,
};
- let theme_list = TidelineThemeList::new(&UI_THEME, 4).motion(false, true);
+ // Index 6 is Blue Stage (Blue Stage follows Shoreline, Shoreline
+ // Light, Underwater and Underwater Retro), matching `candidate_name`.
+ let theme_list = TidelineThemeList::new(&UI_THEME, 6).motion(false, true);
let preview = TidelineSettingsPreview {
active_theme: &UI_THEME,
candidate: &UI_THEME,
@@ -126,23 +128,24 @@ fn settings_strip_windows_to_the_selected_category_with_painted_hitboxes() {
}
#[test]
-fn theme_list_shows_fourteen_themes_boxed_selection_and_motion_toggles() {
- let list = TidelineThemeList::new(&UI_THEME, 4).motion(false, true);
+fn theme_list_shows_seventeen_themes_boxed_selection_and_motion_toggles() {
+ let list = TidelineThemeList::new(&UI_THEME, 6).motion(false, true);
let text = render_golden_text(30, 24, |buf| {
render_tideline_theme_list(Rect::new(0, 0, 30, 24), buf, &list);
});
- assert_eq!(SELECTABLE_THEMES.len(), 15, "4 mode rows + 11 presets");
+ assert_eq!(SELECTABLE_THEMES.len(), 17, "4 mode rows + 13 presets");
for name in [
"System",
"Terminal",
+ "Shoreline",
"Blue Stage",
"Catppuccin Mocha",
"Uwu",
] {
assert!(text.contains(name), "missing {name}: {text}");
}
- // Index 4 is Blue Stage; Underwater + Underwater Retro sit between
- // Terminal and Blue Stage.
+ // Index 6 is Blue Stage; Shoreline, Shoreline Light, Underwater and
+ // Underwater Retro sit between Terminal and Blue Stage.
assert!(text.contains("Underwater"), "{text}");
assert!(text.contains("Underwater Retro"), "{text}");
assert!(
@@ -237,7 +240,7 @@ fn settings_rail_and_theme_list_hitboxes_match_painted_rows() {
let mut buf = Buffer::empty(Rect::new(0, 0, w, h));
render_tideline_theme_list(form, &mut buf, &list);
let boxes = tideline_theme_list_hitboxes(form, &list);
- assert_eq!(boxes.len(), 17, "15 theme rows + 2 motion toggles");
+ assert_eq!(boxes.len(), 19, "17 theme rows + 2 motion toggles");
for rect in &boxes {
let cells: String = (rect.x..rect.x + rect.width)
.map(|x| buf[(x, rect.y)].symbol().to_string())
diff --git a/docs/design/TUI_REDESIGN.md b/docs/design/TUI_REDESIGN.md
new file mode 100644
index 0000000000..829b07b25b
--- /dev/null
+++ b/docs/design/TUI_REDESIGN.md
@@ -0,0 +1,164 @@
+# TUI redesign — Shoreline
+
+Branch `tui-redesign`, 2026-09-15. The Codewhale TUI re-inks to the product
+client's palette and moves toward the conventions the terminal-agent market
+converged on. The whalemark is unchanged and is the constant across every
+surface.
+
+Two asks produced this. First: a more professional shell, closer to what the
+market ships. Second: closer to the GPUI client, which is deliberately simple.
+Both are the same move — `codehwhale-gpui` already *is* the product client, and
+it already solved "calm, warm, one accent". The terminal should read as the same
+product, not as a second design system.
+
+## What changed
+
+### 1. One palette, two clients (landed)
+
+`SHORELINE_UI_THEME` and `SHORELINE_LIGHT_UI_THEME` in
+`crates/palette/src/themes.rs` cite the same slots
+`codehwhale-gpui/src/workspace/mod.rs:147-164` installs into GPUI's `Theme`.
+The terminal's default was `underwater` — a saturated navy gradient with
+decorative ambient life. It is a named theme now, not the ground the product
+opens on.
+
+| Role | Value | Note |
+| --- | --- | --- |
+| field (`surface_bg`) | `#211F23` | warm charcoal |
+| plate (`panel_bg`, `composer_bg`) | `#2B282E` | raised |
+| chrome (`header_bg`, `footer_bg`) | `#1A181C` | recessed |
+| border | `#49424D` | |
+| body | `#F2ECE5` | the whale's ivory survives |
+| soft / muted / hint / dim | `#D9D2DC` / `#B0A7B2` / `#9A919F` / `#7E7583` | 4.5:1 floors |
+| action, selection | `#90B9FF` / `#354967` | the one blue |
+| live | `#7FD6C6` | |
+| human | `#F6C453` | Signal Gold, unchanged |
+| warning / danger / success | `#F0A868` / `#FF8FA8` / `#A3D977` | |
+| mode ramp | `#7EB4E8` / `#B9DCEC` / `#AD88FF` / `#FF70A0` | the shipped ramp |
+
+Light is the same system on warm paper (`#F5F0E9` field, `#245BC7` action).
+
+Three mechanisms make it hold:
+
+- **`adapt::theme_remap_active`** lists both new presets, so every direct
+ `palette::TEXT_*` / `WHALE_*` call site lands on a Shoreline slot instead of
+ a navy-tuned value. Without this the re-ink would be half-applied — the
+ frame would repaint while every widget kept its cold grey.
+- **The mode ramp is separate from `accent_primary`.** `mode_agent` must not
+ equal the action blue: `adapt::theme_semantic_foreground_role` resolves mode
+ slots *before* the action lane, so an equal value reads the action lane as
+ `ModeAgent` and the ANSI-16 role matrix collapses. Found by
+ `every_selectable_theme_keeps_mode_badges_distinct`; fixed in the theme, not
+ the test.
+- **Every audited pair clears its floor.** `contrast::theme_contrast_violations`
+ runs over `SELECTABLE_THEMES`, so the new presets are held to 4.5:1 on the
+ four surfaces and 3:1 for hint/dim/status/diff.
+
+`scripts/export-design-tokens.py` reads only `(WHALE|LIGHT)_*_RGB`, so the new
+`SHORELINE_*` constants deliberately leave `web/app/tokens.css` untouched —
+`--check` still passes. When the web surfaces are re-inked, that pattern is the
+one line to widen.
+
+### 2. What the market actually does (surveyed, not assumed)
+
+Seven of nine reference checkouts under `refs/` are terminal TUIs (codex,
+opencode, kimi-code, grokbuild, oh-my-pi, piagent, prime-agent); `dsh` and
+`openhands` are not TUIs at all. The convergent rules:
+
+1. **No persistent top bar.** 7/7. Branding is launch-only — codex's session
+ card and grokbuild's cwd bar are the first transcript cell, they scroll
+ away.
+2. **Exactly one status row, never two, never above the composer.** All five
+ that have a status line.
+3. **That row carries model + cwd + git branch + context % + cost.**
+4. **Full-width composer.** 7/7. No reading-column cap — this is where the
+ terminal convention and the desktop window legitimately differ, and the
+ terminal wins here.
+5. **Assistant turn = bare markdown on the field.** User turn = a filled
+ background or one coloured glyph, never bold text alone.
+6. **Tool calls are collapsible cards or one-line headers**, not bare lines.
+7. **One accent over a near-black field.** `#141414` recurs as the base field
+ in three separate projects.
+
+Against that list the TUI is already close on 1, 4, 5 and 6 — `work_surface::height`
+returns 0 unless a panel is explicitly open, the composer is full width, and
+tool receipts are typed cells. The live deviations are 2 and 3, and the launch
+screen's proportions.
+
+### 3. Deliberately not done
+
+- **The composer stays full width.** The GPUI client centres a 690px column;
+ no terminal agent does. Narrowing it here would be the desktop's layout
+ applied where the medium disagrees.
+- **The ocean is not deleted.** `underwater` keeps its water column, ombre and
+ ambient life. It is selectable, and `/theme` lists it. Nothing is lost.
+
+## Remaining work
+
+### The one-status-row merge (highest value, not started)
+
+Two rows sit under the composer:
+
+- slot 6, the posture bar — `frame.rs:1820-1835` →
+ `phase_strip::tideline_footer_from_app` (`phase_strip.rs:1270`) →
+ `render_tideline_footer` (`phase_strip.rs:953`).
+- slot 7, the metrics line — `frame.rs:1841-1844` → `render_info_row`
+ (`frame.rs:387`), fed by `info_segments` (`frame.rs:61`).
+
+They are separate systems with separate shed ladders, separate hitbox
+registries (`register_footer_count_targets`, `register_info_interaction_targets`)
+and separate golden families, so this is a merge, not a deletion.
+
+The shape that fits the existing design: **the footer becomes the one row**,
+gaining a route segment and a cost segment. `TidelineFooterFacts` already
+carries `context_percent` (it uses it for the ≥80% cap warning), and
+`phase_strip::route_identity_fields` — which the info line already calls — is
+the shared route formatter, so both segments are one call each. `metrics_line`
+then defaults to `ChromeRowPreset::Hidden` and the transcript gets the line
+back.
+
+**The blocker to solve first, not after:** the metrics line owns two pointer
+targets — `/model` on the route segment and the context inspector on the meter.
+Hiding the row without re-homing them removes two interactions users have
+today. The footer's count rects are the precedent to copy: paint the segment,
+return its `Rect`, register the target. Do that in the same commit that
+hides the row, or the slice ships a regression.
+
+### The launch screen
+
+`underwater::launch_empty_state` (`underwater.rs:1948`) rides the hero mark
+(14×6, `MarkSize::Large`) beside three text lines and then a session card. Two
+things read as splash rather than product: the mark is unindented at the top
+left, and its six rows are taller than the text column beside it, so `New
+session` floats in the mark's dead space. The GPUI launch is a centred compact
+lockup — mark, wordmark, one heading, one line of subtitle. Worth doing; the
+golden churn is contained to the launch surfaces.
+
+### Adjacent, out of scope here
+
+- Two `main` test failures pre-date this branch and are unrelated:
+ `runtime_api::tests::set_config_rejects_unknown_key_with_bad_request`
+ (the code says `unknown setting`, the test expects `unknown config key` —
+ both present at `4a85cb7877`) and
+ `tui::views::tests::every_settings_row_reaches_a_store`
+ (`auto_compact_threshold_percent` is a schema row `Settings::set` never
+ accepted; also present at `4a85cb7877`).
+- `arrow_navigation_wraps_at_picker_edges` and
+ `theme_picker_uses_shared_settings_controller` are order-dependent under a
+ bare `cargo test`: they read the real user theme directory, so a
+ `custom:` row changes the last picker row. Run tests through
+ `scripts/dev-test.sh`, which supplies the hermetic HOME.
+- `crates/tui/src/tui/work_surface/tideline.rs:207-263` (`tideline_rail_groups`,
+ the RUNS/WHALES/FLEET/WORK/CONTEXT groups) is `#[allow(dead_code)]`
+ translation scaffolding. The live rail paints `RailPanel` dock tabs. It
+ should be deleted, not reimplemented.
+
+## Verification
+
+Goldens record cell symbols only — `golden_harness.rs:27-49` dumps
+`cell.symbol()`, so **a palette change is invisible to every golden buffer**.
+The `ink plane` the harness documents at `:84-94` would close that hole and is
+documented but unimplemented. Until it exists, palette regressions are caught
+by the contrast audit and by a real terminal capture, not by a golden.
+
+What was run, with counts, is recorded in the three commits on this branch.
diff --git a/extensions/vscode/media/codewhale.svg b/extensions/vscode/media/codewhale.svg
index a082e2bc5b..f67e8d277b 100644
--- a/extensions/vscode/media/codewhale.svg
+++ b/extensions/vscode/media/codewhale.svg
@@ -1,11 +1,3 @@
-
diff --git a/scripts/brand/trace-brand.py b/scripts/brand/trace-brand.py
deleted file mode 100644
index 054f3d5c92..0000000000
--- a/scripts/brand/trace-brand.py
+++ /dev/null
@@ -1,233 +0,0 @@
-#!/usr/bin/env python3
-"""Re-derive every brand surface from the founder's artwork in brand/.
-
- python3 scripts/brand/trace-brand.py # rewrite every output
- python3 scripts/brand/trace-brand.py --check # exit 1 if any output drifted
-
-Founder sources (committed, never regenerated):
-
- brand/codewhalemarkfinal.png 1254x1254 sheet; hero mark top centre
- brand/wordmark0901.png "codewhale" wordmark, navy on white, 2172x724
- brand/wordmarkinverted.png the same wordmark, white on navy, 2508x627
-
-`brand/mark.svg` is the kept trace of the sheet's hero mark (viewBox 0 0 512
-512, one evenodd path, fill currentColor). It is the source of truth for the
-mark: this script does not re-trace it, it derives every other mark surface
-from it. The wordmark is traced from `wordmark0901.png` every run:
-
- magick brand/wordmark0901.png -colorspace Gray -threshold 60% -trim +repage wm.pbm
- potrace wm.pbm -s --flat -t 20 -O 0.4 -a 1.2 -o wm-raw.svg
-
-potrace's `translate(0,H) scale(0.1,-0.1)` group is folded into the
-coordinates so the result is one compact path in a tight pixel viewBox
-(1874x264 for the 0901 render; the wordmark is ~7.1:1).
-
-`wordmarkinverted.png` traces just as cleanly (negate first, threshold 40%),
-but its render is set slightly tighter (2172x310 after trim, ~7.0:1), so the
-0901 render is the one path used for both colourways: geometry that differs
-between the light and dark wordmark is a second wordmark.
-
-Outputs:
-
- brand/wordmark.svg #142352 on transparent
- brand/wordmark-inverted.svg #FFFFFF on transparent, same path
- brand/mark-navy.svg mark.svg path, #142352
- brand/mark-gradient.svg mark.svg path, cobalt->action gradient
- web/public/brand/{mark,mark-gradient,wordmark,wordmark-inverted}.svg
- web/app/icon.svg white mark on the #142352 rounded tile
- web/app/favicon.ico 16/32/48 of the tile
- web/app/apple-icon.png 180, opaque tile
- web/public/icon-192.png, icon-512.png
-
-`web/components/whale.tsx` inlines the mark path; the script checks it still
-matches `brand/mark.svg` rather than rewriting a React component.
-
-Needs ImageMagick 7 (`magick`) and potrace on PATH. No network.
-"""
-
-from __future__ import annotations
-
-import re
-import shutil
-import subprocess
-import sys
-import tempfile
-from pathlib import Path
-
-ROOT = Path(__file__).resolve().parents[2]
-BRAND = ROOT / "brand"
-WEB = ROOT / "web"
-
-NAVY = "#142352"
-WHITE = "#FFFFFF"
-# Gradient stops are the TUI's WHALE_COBALT -> WHALE_ACTION.
-GRADIENT = ("#1535B2", "#6AA6DC")
-
-# The mark in the 512 tile: the sheet's app icon sets the whale at ~2/3 of
-# the tile width, so scale the 512 mark by 0.72 and centre its content box
-# (x 16.9..494.3, y 46.7..471.1) on 256,256.
-TILE_SCALE = 0.72
-TILE_TRANSLATE = (72, 70)
-TILE_RADIUS = 102.4 # 20% of 512, the sheet's corner
-
-THRESHOLD = "60%"
-POTRACE = ["potrace", "-s", "--flat", "-t", "20", "-O", "0.4", "-a", "1.2"]
-
-
-def run(*args: str) -> None:
- subprocess.run(args, check=True)
-
-
-def number(value: float) -> str:
- text = f"{value:.1f}"
- return text[:-2] if text.endswith(".0") else text
-
-
-def fold_potrace(svg: str) -> tuple[str, float, float]:
- """Return (path d, width, height) with potrace's group transform applied."""
- width = float(re.search(r'width="([\d.]+)pt"', svg).group(1))
- height = float(re.search(r'height="([\d.]+)pt"', svg).group(1))
- tx, ty, sx, sy = (
- float(v)
- for v in re.search(
- r"translate\(([-\d.]+),([-\d.]+)\) scale\(([-\d.]+),([-\d.]+)\)", svg
- ).groups()
- )
- d = " ".join(re.findall(r' tuple[str, float, float]:
- pbm = tmp / "wm.pbm"
- raw = tmp / "wm-raw.svg"
- run(
- "magick", str(png), "-colorspace", "Gray", "-threshold", THRESHOLD,
- "-trim", "+repage", str(pbm),
- )
- run(*POTRACE, str(pbm), "-o", str(raw))
- return fold_potrace(raw.read_text())
-
-
-def wordmark_svg(d: str, width: float, height: float, fill: str) -> str:
- return (
- f'\n'
- )
-
-
-def mark_path() -> str:
- return re.search(r' d="([^"]+)"', (BRAND / "mark.svg").read_text()).group(1)
-
-
-def mark_svg(fill: str, defs: str = "") -> str:
- return (
- '\n'
- )
-
-
-def icon_svg() -> str:
- tx, ty = TILE_TRANSLATE
- return (
- '\n'
- )
-
-
-def rasters(icon: Path) -> None:
- run(
- "magick", "-background", "none", str(icon),
- "-define", "icon:auto-resize=48,32,16", str(WEB / "app/favicon.ico"),
- )
- run(
- "magick", "-background", NAVY, str(icon), "-resize", "180x180",
- "-alpha", "off", "-depth", "8", str(WEB / "app/apple-icon.png"),
- )
- for size in (192, 512):
- run(
- "magick", "-background", "none", str(icon), "-resize", f"{size}x{size}",
- "-depth", "8", str(WEB / f"public/icon-{size}.png"),
- )
-
-
-def main() -> int:
- check = "--check" in sys.argv[1:]
- gradient = (
- ''
- f''
- f''
- )
- with tempfile.TemporaryDirectory() as tmpdir:
- tmp = Path(tmpdir)
- d, width, height = trace_wordmark(BRAND / "wordmark0901.png", tmp)
- texts = {
- BRAND / "wordmark.svg": wordmark_svg(d, width, height, NAVY),
- BRAND / "wordmark-inverted.svg": wordmark_svg(d, width, height, WHITE),
- BRAND / "mark-navy.svg": mark_svg(NAVY),
- BRAND / "mark-gradient.svg": mark_svg("url(#mark-gradient)", gradient),
- WEB / "app/icon.svg": icon_svg(),
- }
- texts[WEB / "public/brand/mark.svg"] = (BRAND / "mark.svg").read_text()
- for name in ("mark-gradient", "wordmark", "wordmark-inverted"):
- texts[WEB / "public/brand" / f"{name}.svg"] = texts[BRAND / f"{name}.svg"]
-
- whale = (WEB / "components/whale.tsx").read_text()
- inline = re.search(r'WHALE_MARK =\s*"([^"]+)"', whale).group(1)
- drift = [] if inline == mark_path() else ["web/components/whale.tsx"]
-
- if check:
- drift += [
- str(path.relative_to(ROOT))
- for path, text in texts.items()
- if not path.exists() or path.read_text() != text
- ]
- for name in drift:
- print(f"drift: {name}", file=sys.stderr)
- return 1 if drift else 0
-
- for path, text in texts.items():
- path.write_text(text)
- print(f"wrote {path.relative_to(ROOT)}")
- rasters(WEB / "app/icon.svg")
- print("wrote web/app/favicon.ico apple-icon.png public/icon-192.png icon-512.png")
- for name in drift:
- print(f"drift: {name} no longer matches brand/mark.svg", file=sys.stderr)
- return 1 if drift else 0
-
-
-if __name__ == "__main__":
- if not shutil.which("magick") or not shutil.which("potrace"):
- sys.exit("needs `magick` (ImageMagick 7) and `potrace` on PATH")
- sys.exit(main())
diff --git a/web/app/opengraph-image.tsx b/web/app/opengraph-image.tsx
index d99791d8d4..2df331ca77 100644
--- a/web/app/opengraph-image.tsx
+++ b/web/app/opengraph-image.tsx
@@ -14,7 +14,7 @@ export const contentType = "image/png";
// regeneration on the site fail with a 500 and serve its build snapshot forever.
async function brandSvgs(): Promise<[string, string]> {
const [mark, wordmark] = await Promise.all([
- readFile(join(process.cwd(), "public/brand/mark.svg")),
+ readFile(join(process.cwd(), "public/brand/mark-reversed.svg")),
readFile(join(process.cwd(), "public/brand/wordmark-inverted.svg")),
]);
return [mark.toString().replace("currentColor", "#ffffff"), wordmark.toString()];
@@ -43,8 +43,8 @@ export default async function OpengraphImage() {
}}
>
- {/* The traced wordmark is 1874x264 (~7.1:1). */}
-
+ {/* The family wordmark is 1024x160 (6.4:1). */}
+
{IDENTITY_PHRASE}
diff --git a/web/lib/public-auth-routes.ts b/web/lib/public-auth-routes.ts
index 97d45e7548..dbe4f9b2d6 100644
--- a/web/lib/public-auth-routes.ts
+++ b/web/lib/public-auth-routes.ts
@@ -14,8 +14,9 @@ import { pathLocale } from "./i18n/path";
/**
* App-icon raster (1254×1254) used on public account-entry pages: the canonical
* mark in the current brand treatment — blue gradient glyph on a white rounded
- * tile, generated from repo-root-relative `web/public/brand/mark.svg`. The vector is
- * the master — never hand-edit this PNG, regenerate it. Pinned by hash so a
+ * tile. The master vectors are the canonical family in
+ * `codewhale-apps/packages/brand/svg/` (byte copies in `web/public/brand/`) —
+ * never hand-edit this PNG, regenerate it from the family. Pinned by hash so a
* redrawn or stale substitute fails the test rather than shipping.
*/
export const CANONICAL_MARK_SRC = "/brand/codewhale-mark.png";
diff --git a/web/public/brand/mark-gradient.svg b/web/public/brand/mark-gradient.svg
index e7458e956e..412e4c14d9 100644
--- a/web/public/brand/mark-gradient.svg
+++ b/web/public/brand/mark-gradient.svg
@@ -1 +1,9 @@
-
+
diff --git a/web/public/brand/mark-mono.svg b/web/public/brand/mark-mono.svg
new file mode 100644
index 0000000000..f67e8d277b
--- /dev/null
+++ b/web/public/brand/mark-mono.svg
@@ -0,0 +1,3 @@
+
diff --git a/web/public/brand/mark-reversed.svg b/web/public/brand/mark-reversed.svg
new file mode 100644
index 0000000000..90ceae43ce
--- /dev/null
+++ b/web/public/brand/mark-reversed.svg
@@ -0,0 +1,3 @@
+
diff --git a/web/public/brand/mark.svg b/web/public/brand/mark.svg
index 75b6a4d86d..71d3b534c6 100644
--- a/web/public/brand/mark.svg
+++ b/web/public/brand/mark.svg
@@ -1 +1,3 @@
-
+
diff --git a/web/public/brand/wordmark-inverted.svg b/web/public/brand/wordmark-inverted.svg
index e537014177..b0bb87e19f 100644
--- a/web/public/brand/wordmark-inverted.svg
+++ b/web/public/brand/wordmark-inverted.svg
@@ -1 +1,3 @@
-
+
diff --git a/web/public/brand/wordmark.svg b/web/public/brand/wordmark.svg
index 81133ab7e2..03d61e8957 100644
--- a/web/public/brand/wordmark.svg
+++ b/web/public/brand/wordmark.svg
@@ -1 +1,3 @@
-
+