Skip to content

feat: four switchable skins, and the token that makes them possible - #252

Merged
milind-soni merged 1 commit into
milind-soni:mainfrom
koeseo:feat/skins
Aug 20, 2026
Merged

feat: four switchable skins, and the token that makes them possible#252
milind-soni merged 1 commit into
milind-soni:mainfrom
koeseo:feat/skins

Conversation

@koeseo

@koeseo koeseo commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Midnight (the current palette, unchanged) plus three alternatives, switchable in Settings → General and remembered per machine. They fill a grid:

cool warm
light Lagoon — ceramic, teal Atelier — paper, daylight
dark Midnight — the original Foundry — night shift, brass

Why this is mostly a CSS change

Because of a property this codebase already has: 916 token classes against 8 hardcoded hex values and no Tailwind greys. A skin is therefore one block of custom properties — Lagoon cost 51 lines. That token discipline is what made this worth trying at all.

The selector is [data-skin=…] rather than :root[data-skin=…], so any element can open a skin context for its subtree. That is how the picker renders a live miniature of each skin instead of a painted mockup.

One token does real work beyond theming

--color-accent-ink — the ink carried by a filled accent or danger surface. Components hardcode text-white on those fills at ~20 sites, which holds only while every accent is dark enough to carry white.

That constraint is measurable, and the numbers are worth having on their own: white on the current accent is 3.65:1 and on danger 3.10:1, both short of the 4.5:1 that AA asks of the 12–14px labels they carry. (Measured in more detail in #249, which stands alone.)

Midnight sets the token to #ffffff, so its rendering is byte-identical. But Foundry can invert the relationship — brass fill, near-black lettering, 7.4:1 — which is what lets it read as lit metal rather than a dark paint chip. With hardcoded white, every accent would have to stay dark and the skin would lose its point.

Same story for the disabled state: disabled:opacity-40 works on a dark ground and inverts on a light one, where Atelier's accent at 40% over white lands on #d9bfa8 and white lettering measures 1.75:1. Disabled buttons get their own colours instead of transparency.

Contrast is measured, not asserted

scripts/check-skin-contrast.mjs parses the stylesheet, reads @theme and :root as the inherited base — so a token a skin does not set is still measured as it actually ships — and fails on an undefined token rather than skipping it. 27 pairs per skin, all three new ones clear. Midnight is advisory, since its job is to be the original, gaps included.

That inheritance detail is not theoretical: while rebasing this onto v0.1.24 the new --color-focus landed, no skin overrode it, and all three would have silently worn Midnight's blue — 2.27:1 on Lagoon. The check now catches that class of regression.

Fully understand if a four-way skin picker is not where you want this app to go — the --color-accent-ink and disabled-state parts stand on their own if you would rather take only those.

🤖 Generated with Claude Code

Midnight (the current palette, unchanged) plus three alternatives, switchable
in Settings → General and remembered per machine. They fill a grid: dark/cool
is Midnight, light/warm Atelier, dark/warm Foundry, light/cool Lagoon.

This is mostly a CSS change because of a property this codebase already has:
916 token classes against 8 hardcoded hex values and no Tailwind greys. A skin
is therefore one block of custom properties — Lagoon cost 51 lines. The
selector is `[data-skin=…]` rather than `:root[data-skin=…]`, so any element
can open a skin context for its subtree, which is how the picker renders a
live miniature of each skin instead of a painted mockup.

One token is doing real work beyond theming: --color-accent-ink, the ink
carried by a filled accent or danger surface. Components hardcode `text-white`
on those fills at ~20 sites, which holds only while every accent is dark
enough to carry white. That is the constraint behind a measurement worth
having on its own: white on the current accent is 3.65:1 and on danger
3.10:1, both short of the 4.5:1 that AA asks of the 12-14px labels they carry.
Midnight sets the token to #ffffff so its rendering is byte-identical, while
Foundry can invert the relationship — brass fill, near-black lettering, 7.4:1
— which is what lets it read as lit metal rather than as a dark paint chip.

Same story for the disabled state: `disabled:opacity-40` works on a dark
ground and inverts on a light one, where Atelier's accent at 40% over white
lands on #d9bfa8 and white lettering measures 1.75:1. Disabled buttons get
their own colours instead of transparency.

Contrast is measured, not asserted: check-skin-contrast.mjs parses the
stylesheet, reads @theme and :root as the inherited base — so a token a skin
does NOT set is still measured as it ships — and fails on an undefined token
rather than skipping it. 27 pairs per skin. Midnight is advisory, since its
job is to be the original, gaps included.
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@koeseo, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Limit details: You’ve used all 3 included reviews currently available.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8767c01b-c948-4c80-8235-62a6063ab178

📥 Commits

Reviewing files that changed from the base of the PR and between 70805c0 and 22b7634.

📒 Files selected for processing (7)
  • scripts/check-skin-contrast.mjs
  • src/components/SettingsModal.tsx
  • src/components/SkinPicker.tsx
  • src/lib/skins.test.ts
  • src/lib/skins.ts
  • src/main.tsx
  • src/styles.css

Comment @coderabbitai help to get the list of available commands.

@milind-soni milind-soni left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against current main, including the newly merged Ubuntu/Wayland work. The CSS-token architecture, pre-paint application, local persistence, live previews, and accessible selection semantics are sound. Verified all four skins visually, reload persistence, production build, contrast checks, typecheck, full test suite, and packaged-server smoke test. No blocking issues.

@milind-soni
milind-soni merged commit 9d8c4a0 into milind-soni:main Aug 20, 2026
6 checks passed
kargnas added a commit to kargnas/OpenMausBot that referenced this pull request Aug 20, 2026
main의 milind-soni#277(팀 임포트 additive-only), milind-soni#252(스킨), milind-soni#276(Ubuntu 릴리스)
병합 충돌 9개 파일을 해결했다.

- localComputerMcp capability는 채택, 정적 effortLevels 재주입은 제거
- fake-acp-cli dump를 main의 dumpState 구조로 통일하고 droid
  exec --help와 RPC calls 기록을 유지했다
- grok argv는 main 순서(서브커맨드 뒤 -m)로, duplicate PATCH 테스트는
  PR의 검증 계약(미확인 인스턴스 409)에 맞췄다

Tested: pnpm typecheck, pnpm vitest run (125 files, 1205 passed, 12 skipped)

Confidence: high
Scope-risk: moderate
Reversability: moderate
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants