Skip to content

Feature - Establish house-style color system and migrate collections to it - #752

Draft
markmead wants to merge 30 commits into
feature/generate-dark-variants-scriptfrom
feature/house-style-dark-mode-reset
Draft

Feature - Establish house-style color system and migrate collections to it#752
markmead wants to merge 30 commits into
feature/generate-dark-variants-scriptfrom
feature/house-style-dark-mode-reset

Conversation

@markmead

@markmead markmead commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Summary

Stacked on top of #751 — this PR's base is feature/generate-dark-variants-script, not main, so the diff below is only the incremental house-style work. Once #751 merges, GitHub will retarget this PR to main automatically.

  • Establishes an explicit house-style color system (documented in HOUSE_STYLE_PLAN.md) covering surfaces, text, borders, action/destructive/status colors, and focus rings — fixing real drift across the 311 light components (e.g. ~91 bg-indigo-600 vs 66 bg-blue-600 action buttons, 146 stray text-gray-400 body-copy instances failing WCAG AA)
  • Adds house-style-aware rules to the shared dark-mode engine (src/lib/dark-mode/config.js): solid-fill indigo/red buttons get an explicit darkShade override instead of the generic 600→300 shade map, which was producing pastel dark:bg-indigo-300 fills for what should be solid action buttons
  • Switches dark-mode support from a per-component flag to a single collection-level flag: content.config.ts's dark now defaults to false, and a collection only flips to true once every component in it has been migrated. The old per-component dark: true | { contributors } union is retired in favor of an optional darkContributors: string[] for the rare hand-authored-dark-only case
  • ComponentPost.astro / ComponentCard.astro / SearchWrapper.astro updated to match — renders a paired "(Dark)" card per component when the collection flag is set, replacing the "X/Y Dark Mode (Request)" badge with a plain "Dark Mode" label
  • Migrates 7 application collections through the full pipeline (standardize light-mode classes to house style, then regenerate dark variants via the generator script from Feature - Add dark variant generator script and collection-level dark mode opt-out #751, verify, flip the collection flag): badges, breadcrumbs, button-groups, checkboxes, details-list, dividers, dropdown
  • charts is deliberately skipped for now and flagged in HOUSE_STYLE_PLAN.md for a dedicated follow-up — its dark variants pair Tailwind classes with hand-picked hex colors inside inline Chart.js <script> blocks that the shared engine can't touch
  • Fixes scripts/generate-dark-variants.js to add the standalone dark:bg-gray-900 background (from Bugfix - Give dark example HTML files an explicit dark background #754) to every freshly-generated file's <body> itself, instead of needing it patched by hand after every regeneration — <body> never carries a bg-* class in light mode for the shade-map engine to invert, so this is a fixed convention applied as a small post-processing step, independent of the color-transform engine

Progress and per-collection gotchas are tracked in HOUSE_STYLE_PLAN.md as the effort continues — empty-states is next alphabetically (accordions was seemingly skipped by the original ordering and still needs a pass at some point too).

Test plan

  • pnpm lint
  • pnpm astro check
  • Manual dev-server spot check (light + dark) for each migrated collection — contrast, focus-ring visibility, button legibility against dark surfaces
  • Verified the generator script's new <body> background handling against both <body class="..."> and bare <body> fixtures
  • pnpm test (Playwright) once the full collection set is migrated

markmead and others added 10 commits August 21, 2026 08:55
…o a single collection flag

Dark mode support moves from a per-component opt-in (duplicated dark: true
across every entry in a collection) to a single collection-level flag in
content.config.ts, defaulting to false until a collection has been through
a full house-style audit. ComponentPost.astro now renders a dark card for
every component purely off that flag; the per-component field is retired
in favor of an optional darkContributors override for the rare hand-authored
dark variant (announcements, toasts, steps keep their WinnyChang credit).

Adds two targeted dark-mode rules (src/lib/dark-mode/config.js) so solid
action/destructive buttons map to indigo-500/red-500 in dark mode instead
of the generic shade map's washed-out indigo-300/red-300, and fixes the
browser tool's createDefaultConfig() to actually seed from those rules
instead of hardcoding an empty array.

badges is migrated as the pilot collection: its four generic variants used
arbitrary purple, now standardized to the indigo action color; its themed
variant already matched the intended success/warning/error recipe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…the house style

The Rules panel now auto-loads 3 house-style rules (from the previous
commit's createDefaultConfig fix) with no explanation of where they came
from. Adds a short line above the rule list so it's not a silent surprise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tracks status, the settled house-style color table, and the remaining
phases for the collection-by-collection dark mode migration, so the
effort can be picked back up without re-deriving the decisions already
made (house style values, which collections are exempt, why).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ark-mode-reset

# Conflicts:
#	src/layouts/ComponentPost.astro
…ants

Fixes the grouped/bordered variant's border-gray-300 to border-gray-200,
matching the convention used by other grouped interactive elements
(button-groups, pagination). Switches to the collection-level dark flag
and regenerates all five dark variants through the shared engine.
…riants

Fixes focus:ring-blue-500 (a stray shade found nowhere else in the codebase)
to focus:ring-gray-900 per the house style's non-action-control focus ring
rule. Switches to the collection-level dark flag and regenerates all five
dark variants through the shared engine.
…follow-up

charts pairs Tailwind classes with hand-picked Chart.js hex colors in
inline <script> blocks that the shared dark-mode engine can't touch, so
the normal delete-and-regenerate Phase 3 step would silently break dark
mode there. Skipping it in the alphabetical rollout order until it gets
its own pass.
No light-mode class drift found. Regenerating dark variants dropped the
control's own dark:bg-gray-900/dark:ring-offset-gray-900 because
@tailwindcss/forms bakes a white background and white ring-offset into its
own base CSS for [type=checkbox], never exposed as a light-mode class for
the engine to transform — restored both by hand to avoid a white checkbox
on a dark page. Documents the same gap for other forms-plugin collections
still to come.
…iants

No light-mode class drift found. Regeneration only tightened
dark:text-white to dark:text-gray-50, the engine's standard shade-map
inversion for text-gray-900 headings.
Fixes the divider line's bg-gray-300 (and gradient to-gray-300) to
bg-gray-200/to-gray-200, matching the house style table's border-gray-200
row which names dividers explicitly under the default (not emphasized)
border weight.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 30, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
hyperui 375a44a Commit Preview URL

Branch Preview URL
Sep 04 2026, 10:53 AM

claude added 6 commits August 30, 2026 10:22
…cript' into feature/house-style-dark-mode-reset
…cript' into feature/house-style-dark-mode-reset
CLAUDE.md now mentions the scoping flags in the main dark-variant-generation
note. HOUSE_STYLE_PLAN.md's Phase 3 instructions and rollout log were still
describing the old unscoped, discard-the-extras workaround now that the
generator itself is fixed.
…s-script)

That doc line is owned by feature/generate-dark-variants-script's own
CLAUDE.md addition, not this branch's — moving the flag documentation
there instead so each branch's docs match what it actually introduces.
…cript' into feature/house-style-dark-mode-reset
… divider

dark:bg-black had almost no contrast against the preview iframe's own
bg-gray-900 wrapper, making the card nearly disappear. Uses dark:bg-gray-800
for the card (visibly lighter than the page) and dark:bg-gray-900 for the
recessed "Home" segment inside it, preserving the same relative
relationship as light mode (bg-gray-100 inside bg-white) just inverted.
Documents the same gotcha in HOUSE_STYLE_PLAN.md for any future
bg-white-card component.
markmead and others added 11 commits August 31, 2026 16:01
Phase 2: both wrapper elements (split-button trigger, flyout menu panel)
used border-gray-300/divide-gray-300, neither of which is a form input —
changed to border-gray-200 to match the button-groups/card precedent.
Also fixed the "Delete" menu item's text-red-700 to text-red-600 per the
house style table's plain-destructive-text row.

Phase 3: regenerated dark variants, then applied two known gotchas by
hand — the bg-white card fix (dark:bg-black -> dark:bg-gray-800) and,
new this collection, a hover/card-shade collision it exposed: with the
card now dark:bg-gray-800, the engine's dark:hover:bg-gray-800 on items
inside it collides and the hover highlight disappears, so bumped it one
step lighter to dark:hover:bg-gray-700. Also hand-added the dark:bg-gray-900
body background (#754) that the generator script doesn't know to add to
freshly-generated files. Flips dropdown's collection-level dark flag and
documents both gotchas in HOUSE_STYLE_PLAN.md for the next collection.
scripts/generate-dark-variants.js previously left every freshly-generated
-dark.html's <body> without the dark:bg-gray-900 background from #754
(that fix predates the script, and <body> never carries a bg-* class in
light mode for the shade-map engine to invert in the first place), so it
had to be added by hand for every migrated collection.

Adds ensureBodyDarkBackground(), a small post-processing step run on the
generator's output that adds dark:bg-gray-900 to <body>'s existing class
attribute, or adds the attribute if <body> has none. Verified against
both cases with throwaway fixtures. Only affects the Node CLI path
(transformHtmlString) — the browser tool's DOM-aware path only ever
returns body.innerHTML for iframe embedding, so it was never exposed to
this gap.
…iants

Fixed a stray focus:border-indigo-500 (Action color) on the search input's
focus state - non-action controls use the gray-900 focus color. Regenerated
dark variants and restored the dark:bg-gray-900/dark:ring-offset-gray-900
pairing on the search input by hand, since @tailwindcss/forms bakes a white
background/ring-offset into plain text inputs (not just checkboxes/radios)
that the light file never states as a class, so the engine can't invert it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ariants

Light-mode classes already conformed to house style. Regenerated dark
variants and fixed a hover-state collision the shared SHADE_MAP produces
for any bg-gray-50/hover:bg-gray-100 pair: both 50 and 100 invert to the
same dark shade (800), so the "Browse files" chip lost its hover feedback
entirely in dark mode. Bumped the hover shade one step lighter by hand
(dark:hover:bg-gray-700) so it visibly lifts on hover instead of staying
flat, same principle as the earlier dropdown hover/card-shade fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixed the filter panels' border/divide color: -300 was borrowed from the
inputs/interactive-outlines role, but a floating flyout panel and a
self-contained accordion card both belong under the default -200 border,
matching the dropdown and details-list precedent. Left the standalone
<summary> toggle's own border and the checkboxes' plugin-default checked
color alone (established precedent for both).

Regenerated dark variants, then fixed the recurring bg-white-card gotcha
(dark:bg-gray-800 instead of the engine's dark:bg-black) and the
@tailwindcss/forms gotcha on both the checkbox and number inputs
(restored dark:bg-gray-900/dark:ring-offset-gray-900, plus dark:text-gray-50
on the number inputs) by hand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Light-mode classes already conformed to house style. Regenerated dark
variants and hit the @tailwindcss/forms gotcha on all four input
variants - restored dark:bg-gray-900/dark:text-gray-50/
dark:ring-offset-gray-900 by hand.

The floating-label variant's border-masking span surfaced a narrower
case of the bg-white-card gotcha: it's a small patch meant to blend into
the page behind it, not an elevated surface, so it needs dark:bg-gray-900
(matching the page) rather than dark:bg-gray-800 (standing out from it)
or the engine's generic dark:bg-black.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Light-mode classes already conformed to house style. Regenerating dark
variants fixed the pastel dark:bg-indigo-300 fill bug (the exact problem
Phase 1's solid-fill rule was built for) on the pulse/ping/bounce dots
and the progress-bar fill - they now correctly render dark:bg-indigo-500,
a solid visible fill instead of a washed-out one. Spinner stroke colors
are unaffected (that rule is bg-only) and read fine as-is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixed the "Done" button's bg-blue-600 -> bg-indigo-600 (Action color
drift) and added the Confirm input's missing focus ring in the "Input
with close" variant to match its sibling.

Settled the focus-ring scope question left open after empty-states:
modals already carries the a11y-sweep's focus:ring-indigo-600 recipe on
every interactive control, confirming it's a pre-existing accessibility
convention orthogonal to this color migration - preserve it where
present, fix only its colors, don't add or remove it wholesale.

Regenerated dark variants and fixed three new issues that only show up
with a real elevated card:
- backdrop:bg-black/50 was inverting to backdrop:bg-white/50 in dark
  mode (a dimming scrim shouldn't invert at all) - removed the bad
  override.
- ring-offset color needs to match the nearest enclosing surface, not
  always the page - buttons/input inside the now-gray-800 dialog card
  get dark:ring-offset-gray-800, not the page's gray-900.
- The "recessed sub-region inside a card" principle from breadcrumbs
  applies to buttons too - the Cancel button's bg-gray-100 mapped to the
  same shade as its own card, making it invisible at rest; bumped one
  step darker (dark:bg-gray-900) to preserve the light-mode relationship.
Also fixed the Close button's hover/card-shade collision the same way as
earlier collections.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The dark-mode engine's generic white<->black colorMap swap fires on any
utility, so text-white labels on a solid bg-indigo-600 Action fill were
rewritten to dark:text-black on regeneration, undoing Phase 1's
fill-shade override that exists specifically to keep the label legible.
Found after the fact on empty-states (3 buttons/badges) and modals (the
Done button across 4 variants) - removes the erroneous dark:text-black,
leaving plain text-white to carry through unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHz6MHjQz4Nbas6NyT9xSN
No light-mode class drift found. Regenerating dark variants hit the
text-white -> dark:text-black flip on the current-page indicator (fixed
per the empty-states/modals gotcha) plus a new variant of it: the
indicator's border-indigo-600 exists only to match its own bg-indigo-600
fill, but border and bg shade-map independently, producing a mismatched
dark:border-indigo-300 around the dark:bg-indigo-500 fill - fixed to
dark:border-indigo-500 to keep the seamless solid box. Also restored the
@tailwindcss/forms dark chrome on the number-input variant per the
inputs/filters precedent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHz6MHjQz4Nbas6NyT9xSN
…l review

Relying on each migration session to notice and hand-fix the text-white ->
dark:text-black flip had already failed twice (missed on both
empty-states's and modals's own migrations, only caught afterward). Adds
a rule to DEFAULT_CONFIG.rules (src/lib/dark-mode/config.js) so the
engine's white<->black colorMap swap never fires on the text utility -
text-white/text-black now pass through regeneration unchanged in both the
Node script and the browser tool, since both read the same config.
Verified by regenerating empty-states from scratch: output is
byte-for-byte identical to the hand-fixed file.

Also ran the existing dark-mode-generator Playwright suite before and
after this change - 6 pre-existing failures in that file (a stale rules-
count assertion and two rule-inspector cases) reproduce identically with
the change reverted, confirming they predate this fix and aren't a
regression from it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHz6MHjQz4Nbas6NyT9xSN
The previous commit added a rule making the engine's white<->black
colorMap swap skip the text utility entirely, verified only against the
11 known cases (all in application, all white text on a colored
Action/Destructive fill that stays colored in dark mode). That
verification was too narrow: neobrutalism pairs text-black with neutral
surfaces (bg-white, bg-blue-100, etc, 32+ occurrences) that themselves
invert via the existing bg-white -> dark:bg-black rule, so the text has
to invert with them or it goes invisible on its own card once that
collection is migrated. A global "never invert text white/black" rule
fixes one shape of this problem and silently breaks the other - the real
distinction is whether the paired surface itself inverts, which the
engine has no sibling-class awareness to determine per class. Reverting
to hand-fixing each occurrence at Phase 3 review, as before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHz6MHjQz4Nbas6NyT9xSN
…te properly

The previous revert left the text-white/dark:text-black flip as a manual
check again, since a blanket "never invert text white/black" rule was
unsafe (neobrutalism pairs text-black with neutral surfaces that must
invert with it). But the engine already had what it needed: element class
attributes are split into tokens before per-class transformation, that
token list just never got threaded any further. Adds requireClasses to
the Rule schema - an extra match gate alongside utilities/shade/colors
that only fires when the same element also carries one of the listed
light-mode classes - and threads a siblingClasses param through
applyRules/transformClass/transformClassAttribute so it has something to
check. Re-adds the fill-text rule scoped correctly this time:
text-white only stays put when the element also has bg-indigo-600 or
bg-red-600, so neobrutalism's text-black/text-white (paired with
bg-white/bg-black, never a Phase-1 fill color) is untouched and keeps
inverting normally through the plain colorMap swap.

Verified both directions before shipping: regenerated empty-states,
pagination, and one modals file, diffed byte-for-byte against the
hand-fixed committed versions (the only differences were other,
unrelated, already-documented manual gotchas, with text-color handling
identical); separately ran the transform against neobrutalism/badges
(both bg-white->text-black and bg-black->text-white pairings on the same
file) and tabs' hover state, confirming they still invert correctly.
Re-ran the dark-mode-generator Playwright suite - identical pass/fail
counts to the reverted baseline, no new regressions.

Also audited every already-migrated collection (badges through
pagination) for this bug shape before trusting the "done" list: every
text-white occurrence is the same already-fixed bg-indigo-600 pairing,
and text-black doesn't appear anywhere in application. Nothing needed
rework.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHz6MHjQz4Nbas6NyT9xSN
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