feat(admin): Saddle Violet, a flat rail, and a Dashboard that leads with a sentence (#153) - #154
Merged
Merged
Conversation
Replaces the monochrome accent (#171717) with the brand palette, as consumer-side --pp-* overrides in Saddle's own stylesheet. Nothing in node_modules or the library is touched; the DS defines its tokens inside :where(), which has zero specificity, so a plain class selector wins without !important. Both .pp-app and the body class are targeted because portaled overlays render outside .pp-app and would otherwise lose every token. The rule the whole palette follows: every color has a bright value for FILLS and a darker same-hue value for TEXT. This is not a stylistic choice — three of the four given hexes cannot carry text at all (lime 1.98:1 on white, cyan 2.43, coral 3.67), and the DS derives --pp-tone-text from --pp-tone for status, so a lime --pp-success would have meant lime badge text at 1.9:1: materially worse than what ships today. The readable partners go back at the tone layer, the same seam the accent tone already uses. Lime, cyan and amber each move one step down their own ramp for the MARK tier too. A status dot or a 2px lane rail has to clear 3:1 on a white card to be seen, and at the -500 step they are at 1.98 and 2.43 — invisible. Coral already clears it and is used exactly as given. The Permissions lane rails take the readable step for the same reason: a 2px hairline at 1.9:1 is not there at all. Warning stays amber rather than folding into coral. Saddle says two different things with those: amber is "powerful, asks first" (the Remove lane, the shield chip, the write-tier pill) and red is "blocked or failed" (a denied call, a connection error). They need opposite reactions from the reader. Also here, because they are all color decisions in the same block: - Neutrals warmed onto #FBFBFA / #111111. The ramp is the only lever needed — no DS component reads --pp-n-* directly, all eleven feed ten semantic aliases — and the lightness steps are unchanged, so no hierarchy moves. Muted text goes 4.40 -> 4.50, clearing AA. - --pp-focus set explicitly. It is a separate literal that no accent file touches, so Saddle has been shipping a monochrome accent with a WordPress-blue focus ring this whole time. - Radii tightened to 3/4/6px and elevation flattened, to sit closer to wp-admin's own register while staying a hair softer. Pills untouched. - .pp-code--dark's ground pinned to near-black. It reads --pp-code-bg from --pp-action but hard-codes its border, muted and body colors, so a violet action token would have left 35 CodeBlock/Snippet instances violet-backed with muted text at 2.26:1. - The brand mark is recolored through the nav lockup, never in the SVG: editing the file would break the `black` sentinel that core's svg-painter needs to repaint the wp-admin menu icon. Four token references were already broken and would have stayed broken through any re-brand: --pp-fg-muted, --pp-fg and --pp-fg-subtle do not exist in @plugpress/ui 0.12.0, and --s-accent was referenced by the Cookbook recipe bar but defined nowhere in the repo. Refs #153
The UX half of the re-brand. Four rules, now recorded in DESIGN-ALIGNMENT.md, and every change below traces to one of them: one idea per screen; content over chrome; say nothing rather than say nothing; plain, task-first names. **The rail is one flat list.** The three labelled sections were three more things to read before you could read the thing you came for, and seven items is not a wall. This is a pure data change — AppNav takes a mixed array of items and groups, so only what the useMemo returns changed; the JSX did not. Two things fell out of it and are cleaned up here rather than left half-done. TopBar's "group · title" breadcrumb had nothing left to resolve, so the lookup goes and it shows the title. And the footer was 28 lines of raw <button>s re-implementing the DS's own nav-item markup by hand; the DS supports footer groups natively (`footer: true` puts a group in .pp-nav__bottom), which brings aria-current and the collapsed-rail tooltips with it instead of us maintaining them. Two labels now say what you do there: Guidance -> Instructions, Connections -> Apps. Routing keys are untouched, so every #hash link, the ALIASES map and Pro's routing keep working. **The Dashboard leads with a sentence.** It opened with four equal tiles of three different kinds — two counts, a setting and a health state — which reads as a metrics dashboard for a question that is not a metric. The answer to "what can my AI do right now" was already written, in levelFor(tier).one, and the tiles were paraphrasing it down to one word. So that sentence is the page now, the counts are one quiet line under it, and the Connection tile is gone: it said "—" on most installs while a real problem already had its own callout with an explanation and a fix, so it spent a quarter of the page saying nothing. When there are no apps the count is dropped too — the callout directly below already says it. The LEVELS strings in api.js were raw English, never wrapped in __() — including the sentence this design just promoted to the page headline. Fixed, which is most of the 10 new msgids. **The Cookbook is removed** — tab, screen, CSS, REST route, PHP class and its 6 tests. Verified not agent-facing first: Saddle_Cookbook is referenced in exactly three places (itself, its test, one admin REST callback) and has zero hits across includes/abilities, the system context, the context bundle, skills, playbooks and the MCP transport. Not to be confused with Saddle_Recipes, the section recipes behind saddle/list-section-recipes — different system, untouched. 637 tests (was 643; the 6 are the cookbook's), 0 lint errors. Refs #153
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #153
What
Saddle's admin gets its own look and a calmer shape: a violet palette, one flat sidebar, and a Dashboard that opens with a sentence instead of a grid of tiles. No behaviour changes — no ability, no permission, no REST route outside the Cookbook removal.
Why
Fahim asked for a brand palette, then for the UX around it: "less border radius… wordpress core design but modern", "sidebar, options, etc make more user friendly", "dashboard make overview cleaner page", "remove cookbook", "no group in sidebar… simple name", "make like apple company ux".
admin/DESIGN-ALIGNMENT.mdcarried a DECIDED (2026-07-04) that the monochrome identity was deliberate and not to be re-litigated. This reverses it, at Fahim's direction, so that record is replaced rather than contradicted — otherwise the next agent reads it and undoes this.The palette
Consumer-side
--pp-*overrides in Saddle's own stylesheet. Nothing innode_modulesor the library is touched; the DS defines its tokens inside:where()(zero specificity), so a plain class selector wins with no!important. Both.pp-appand the body class are targeted because portaled overlays render outside.pp-app.The rule everything follows: every color has a bright value for FILLS and a darker same-hue value for TEXT. That is not a style choice. Measured on white, violet is 5.84:1 — but lime is 1.98, cyan 2.43, coral 3.67. None of them can carry a label, and the DS derives
--pp-tone-textfrom--pp-tonefor status, so a lime--pp-successwould have meant lime badge text at 1.9:1 — materially worse than what ships today. The readable partners go back at the tone layer.Lime, cyan and amber each move one step down their own ramp for the mark tier too: a status dot or a 2px lane rail has to clear 3:1 on a white card to be seen, and at the
-500step they are invisible. Coral already clears it and is used exactly as given.Warning stays amber rather than folding into coral. Saddle says two different things with those — amber is "powerful, asks first" (the Remove lane, the shield chip), red is "blocked or failed" (a denied call, an error) — and they need opposite reactions.
Also: neutrals warmed onto
#FBFBFA/#111111via the ramp (no DS component reads--pp-n-*directly, so eleven values move the whole neutral system, with lightness steps unchanged); radii to 3/4/6px and elevation flattened;--pp-focusset explicitly, since it is a separate literal no accent file touches — Saddle has been shipping a monochrome accent with a WordPress-blue focus ring this whole time.One library quirk neutralized:
.pp-code--darkreads--pp-code-bgfrom--pp-actionbut hard-codes its border, muted and body colors. A violet action token would have left 35CodeBlock/Snippetinstances violet-backed with muted text at 2.26:1. Its ground is pinned back to near-black.The UX
One flat rail. The three labelled sections were three more things to read before you could read the thing you came for. Pure data change —
AppNavtakes a mixed array, so only theuseMemoreturn changed. Two things fell out and are cleaned up rather than left half-done:TopBar's group breadcrumb had nothing left to resolve, and the footer was 28 lines of raw<button>s re-implementing the DS's own nav-item markup — the DS supports footer groups natively, which bringsaria-currentand the collapsed-rail tooltips with it.Two labels now say what you do there: Guidance → Instructions, Connections → Apps. Routing keys are untouched.
The Dashboard leads with a sentence. It opened with four equal tiles of three different kinds — two counts, a setting, a health state — reading as a metrics dashboard for a question that isn't a metric. The answer was already written, in
levelFor(tier).one, and the tiles were paraphrasing it into one word. TheConnectiontile is gone: it said—on most installs while a real problem already had a callout with an explanation and a fix.The Cookbook is removed — verified not agent-facing first:
Saddle_Cookbookis referenced in exactly three places and has zero hits acrossincludes/abilities/, the system context, the context bundle, skills, playbooks and the MCP transport. (NotSaddle_Recipes, the section recipes behindsaddle/list-section-recipes— different system, untouched.)Breaking
saddle_cookbook_recipesis removed. A public filter with no consumer in this repo — checkedsaddle-pro, it does not hook it.#cookbookno longer resolves (falls back to Dashboard).LEVELSstrings inapi.jswere raw English, never wrapped in__()— including the sentence now used as the Dashboard headline. Fixed; translators see 10 new msgids.Companion PR
The License moves off the sidebar into Settings — that is Pro-only: plugpressco/saddle-pro#78. Free needs no change; both seams already existed.
Testing
composer test— 637 tests, 0 failures (was 643; the 6 are the Cookbook's)composer lint·npm run lint:js— 0 errorsnpm run build,.potregenerated (10 new msgids, 91 removed — all Cookbook copy and the retired group labels)<button class="pp-nav__item">with thetitletooltip andaria-currentexactly as the hand-rolled version did;#guidance→ Instructions and#connect→ Apps still route;#cookbookfalls back cleanly; code blocks still near-black, not violet; focus rings violetCI note:
mainis red onSaddle_Skills_Test::test_the_playbook_adapts_step_two_to_a_classic_theme(#145, CI-only, green locally). This branch inherits it.Screenshots
Before/after handed to Fahim in-session — this is a visual change and the diff doesn't show it.