Release v0.51.242 — Release HJ (stage-q14): Graphite skin - #3500
Conversation
Add the Graphite appearance skin (#3440). - New neutral-gray "workbench" skin, alternative to the default gold/cream. All visual changes scoped to data-skin="graphite" (default appearance unchanged); both light + dark palettes on the existing CSS-variable token system, no new dependency. Registered in the skin picker + /theme help. De-olive-tinted light palette. (@t3chn0pr13st) UX-approved via Telegram (Nathan, dark+light+mobile screenshots). Co-authored-by: t3chn0pr13st <t3chn0pr13st@users.noreply.github.com> Test-robustness fix (the new graphite scoped selectors precede the canonical unscoped rules, breaking 3 naive first-occurrence CSS-contract tests): test_issue677, test_issue856_pinned_indicator_layout, test_workspace_panel_session_list now anchor on the canonical UNSCOPED rule (start-of-line regex) instead of the first `.selector` match — robust against this and future skins. Verified the 3 tests still pass on clean master CSS (invariant preserved, not weakened).
|
| Filename | Overview |
|---|---|
| static/style.css | Adds ~130 lines of graphite skin CSS; all rules correctly scoped under [data-skin="graphite"] and :root.dark[data-skin="graphite"]. font-weight:430 silently falls back to 400 on non-variable fonts. color-mix() is already used extensively in the codebase, so the new occurrence is consistent with established patterns. |
| api/config.py | Correctly adds "graphite" to both the _SETTINGS_DEFAULTS comment and _SETTINGS_SKIN_VALUES allowlist set. |
| static/boot.js | Adds Graphite to the _SKINS array in the correct position (after Mono), consistent with the config.py allowlist ordering. |
| static/index.html | Adds graphite:1 to the inline boot-script skins whitelist, enabling localStorage persistence and data-skin attribute application at page load. |
| static/i18n.js | Updates cmd_theme help strings in all 12 locales (confirmed by grep: exactly 12 cmd_theme occurrences, all updated in the diff). |
| tests/test_graphite_skin.py | New test file with 7 tests covering end-to-end registration, palette correctness, neutral accent enforcement, workbench chrome, code-block framing, and font stack presence. |
| tests/test_issue677.py | Fixed test to use a start-of-line regex anchor instead of a naive first-occurrence find(), correctly skipping the skin-scoped grouped selector that now precedes the canonical rule. |
| tests/test_issue856_pinned_indicator_layout.py | Fixed test to anchor on the canonical unscoped .session-time{ rule and tightened the slice to just the single rule's declaration block, also relaxing semicolon-exact assertions. |
| tests/test_workspace_panel_session_list.py | Same regex fix applied — anchors on the unscoped .session-time{ rule to prevent matching the skin-scoped variant that appears earlier in the file. |
| tests/test_issue2462_theme_i18n.py | Updates the expected skin-list fragment to include "graphite"; the 12 tested locales align exactly with the locales updated in i18n.js. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Page Load — index.html inline boot script] -->|reads localStorage hermes-skin| B{graphite in skins whitelist?}
B -->|yes| C[dataset.skin = 'graphite']
B -->|no / default| D[no data-skin attribute set]
C --> E[CSS: :root data-skin=graphite rules activate]
E --> F[Light palette tokens applied]
E --> G[dark class? → Dark palette tokens applied]
F & G --> H[Skin-scoped overrides: typography, shadows, sidebar, composer, code blocks]
I[Settings UI / /theme skin graphite] -->|boot.js _SKINS array| J[Skin picker shows Graphite swatch]
J -->|user selects| K[localStorage.setItem hermes-skin graphite]
K --> A
L[api/config.py _SETTINGS_SKIN_VALUES] -->|validates POST body| M{graphite allowed?}
M -->|yes — added in this PR| N[Setting persisted server-side]
Reviews (1): Last reviewed commit: "Release v0.51.242 — Release HJ (stage-q1..." | Re-trigger Greptile
| :root[data-skin="graphite"] .session-jump-btn{box-shadow:0 1px 2px rgba(0,0,0,0.08)!important;background:var(--surface)!important;} | ||
| :root.dark[data-skin="graphite"] .workspace-panel-edge-toggle, | ||
| :root.dark[data-skin="graphite"] .scroll-to-bottom-btn, | ||
| :root.dark[data-skin="graphite"] .session-jump-btn{box-shadow:0 1px 2px rgba(0,0,0,0.28)!important;} |
There was a problem hiding this comment.
font-weight:430 may silently fall back to 400 on non-variable fonts
430 is a valid numeric CSS weight, but it only has a visible effect on variable fonts. -apple-system (San Francisco) and ui-monospace are variable, so macOS/iOS users see the intended feel. On Windows, Segoe UI is not a variable font — the browser rounds to 400 (regular). The same applies to most Linux system fonts. The visual "tightened typography" goal of the skin will be inconsistently met depending on OS.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
## Release v0.51.242 — Release HJ (stage-q14) UX-approved via Telegram (Nathan — dark/light/mobile screenshots). ### Added | PR | Author | Feature | |----|--------|---------| | nesquena#3440 | @t3chn0pr13st | **Graphite appearance skin** — a quiet, neutral-gray "workbench" alternative to the default gold/cream. Selectable from Settings → Appearance and `/theme skin graphite`. Both light + dark palettes on the existing CSS-variable token system; tightened typography, shadows, active-sidebar spacing, code-block framing. | ### Why it's safe - **Fully scoped + additive**: every new CSS rule (and every `!important`) is under `[data-skin="graphite"]` — Codex verified zero bleed into the default appearance or other skins. `api/config.py` keeps the default skin as `default` and only *adds* `graphite` to the allowed set. No i18n keys dropped (only the `/theme` help string gains `graphite`). - Opt-in; a user has to select it. Default experience unchanged. ### Test-robustness fix (absorbed) The new graphite scoped selectors (e.g. `:root[data-skin="graphite"] .session-item.active .session-time{…}`) appear in `style.css` *before* the canonical unscoped rules, which broke 3 naive first-occurrence CSS-contract tests (`test_issue677` scroll-btn-overlay, `test_issue856_pinned_indicator_layout`, `test_workspace_panel_session_list`). Fixed those 3 to anchor on the canonical **unscoped** rule (start-of-line regex) instead of the first `.selector` match — robust against this and future skins. Verified they still pass on clean master CSS (invariant preserved, not weakened). ### Gate - Full pytest suite: **7517 passed, 9 skipped, 3 xpassed, 0 failed** - ESLint runtime gate: CLEAN · ruff: CLEAN · browser-smoke: CLEAN - Codex (regression): **SAFE TO SHIP** (verified all CSS scoped, default skin unchanged, no i18n key loss) - Vision-verified dark + light + mobile; UX-approved by Nathan via Telegram Co-authored-by: t3chn0pr13st <t3chn0pr13st@users.noreply.github.com>
Release v0.51.242 — Release HJ (stage-q14)
UX-approved via Telegram (Nathan — dark/light/mobile screenshots).
Added
/theme skin graphite. Both light + dark palettes on the existing CSS-variable token system; tightened typography, shadows, active-sidebar spacing, code-block framing.Why it's safe
!important) is under[data-skin="graphite"]— Codex verified zero bleed into the default appearance or other skins.api/config.pykeeps the default skin asdefaultand only addsgraphiteto the allowed set. No i18n keys dropped (only the/themehelp string gainsgraphite).Test-robustness fix (absorbed)
The new graphite scoped selectors (e.g.
:root[data-skin="graphite"] .session-item.active .session-time{…}) appear instyle.cssbefore the canonical unscoped rules, which broke 3 naive first-occurrence CSS-contract tests (test_issue677scroll-btn-overlay,test_issue856_pinned_indicator_layout,test_workspace_panel_session_list). Fixed those 3 to anchor on the canonical unscoped rule (start-of-line regex) instead of the first.selectormatch — robust against this and future skins. Verified they still pass on clean master CSS (invariant preserved, not weakened).Gate
Co-authored-by: t3chn0pr13st t3chn0pr13st@users.noreply.github.com