fix(spa): stop using the primary scale as a light-tint background fill - #1091
Merged
Merged
Conversation
The `primary` scale is generated from #0033a0 by lightness offset alone and keeps full chroma at every step, so `primary-50` is not the pale wash its name implies — it resolves to rgb(118, 179, 255), a saturated mid-blue. Used as a chip, badge, icon tile or selected-row fill it reads as a blue blob behind small text, and it fails WCAG AA. The `state-*` scales ARE real tints (`state-success-50` = rgb(240, 253, 244)), which is why the pattern looked safe by analogy and wasn't. Measured against the app's own resolved tokens: text-primary-accessible on bg-primary-100 4.13:1 FAIL ...with hover:bg-primary-200 3.52:1 FAIL text-gray-500 sub-label on bg-primary-50 2.23:1 FAIL text-primary-accessible-dark on the dark half 2.63:1 FAIL Replaced 47 opaque `bg-primary-50/100/200` fills and `hover:` affordances across 25 files with the neutral-surface pattern already established by the skill-command chip: `bg-gray-100` + `text-primary-accessible` in light, `bg-gray-700` + `text-primary-50` in dark (9.63:1 / 4.74:1). Sub-labels that the new fills brought closer together were stepped with them — `text-gray-500` on `bg-gray-100` is 4.39:1, so those move to `text-gray-600`/`dark:text-gray-300`. `hover:brightness-90` goes too; it only ever existed to tame the blue. Left alone deliberately: fractional washes (`bg-primary-50/40` composites to rgb(200, 225, 255), a genuine pale tint) on large transient drag-and-drop surfaces, and every solid `bg-primary-500`+ fill with white text. Guardrails so this does not come back: branding/README.md recommended `bg-primary-50` as the decorative-tint utility — that row is what propagated the pattern, and it now documents the neutral one. The tailwind-ui skill's app-conventions reference gains the same rule with the measured numbers. Verified in both themes with computed styles: all 17 shipped pairings pass. 2868 SPA tests pass; development build is clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
raoakkineni
pushed a commit
to AuraWorx/auraxai-agentcore-public-stack
that referenced
this pull request
Sep 22, 2026
… list The chip cloud had outgrown itself at 30-plus tools: a ragged wrap with no scanning order, descriptions trapped in hover-only tooltips (dead on touch), and selection state scattered across nine rows. The structural tell was the per-tool scoping panel — a wrap layout has nowhere to nest it, so it rendered after the *entire* cloud rather than under the chip that opened it. Tools is now a disclosure over a searchable, grouped list: - Rows carry name + description + a trailing switch, matching the sub-tool rows the same section already used, so one decision has one idiom instead of two. - Search filters label/description/category client-side. Everything is already loaded, so it is instant; same idiom as customize/tools. - Grouped by `meta.category`, which the bindable catalog already sent and the chips discarded — no backend change. Both levels sorted alphabetically; display order is free, the determinism contract is on `toolConfig`. - Sub-tool selection nests under the row that owns it, deliberately quieter than the section accordion (indent + hairline, not a second card) since it is the third level of disclosure on the page. The collapse default is conditional, not always-closed. Create mode and an empty selection stay OPEN — a section reading "none selected" hides the list at exactly the moment the author needs it, and a first-time author has no other way to learn what the platform can do. A saved agent with tools collapses to `Tools · 5 / Class Search, Policy Search, … +2 more`, which answers "what can this agent do?" better than the open list does. State is derived per load, never persisted; a template prefill forces it open so the author sees the toolset they were handed. Accessibility: one tab stop per row (the row *is* the `role="switch"`), with the caret a separate control in a slot reserved on every row so the name column stays a straight edge. Contrast measured from the running app in both themes — selected 16.13/6.87 light and 10.30/7.00 dark, unselected 17.75/4.84 and 14.67/5.64. The selected fill is `bg-gray-100`/`dark:bg-gray-700` per PR Boise-State-Development#1091, NOT a primary tint: this app's `primary-*` scale keeps full chroma, so `bg-primary-50` is a saturated mid-blue that fails at 2.23:1. Costs nothing against the model — this is authoring UI only; the bindings it writes are unchanged, so the prompt prefix is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
The finding
The
primaryscale infrontend/ai.client/src/styles/generated/brand-theme.cssis generated from Boise State blue by lightness offset alone, keeping full chroma at every step:So
primary-50is not a pale wash like a Tailwind-50normally is — it is a saturated mid-blue. Used as a chip/badge/selected-row fill it reads as a blue blob behind small text, and the text on it fails WCAG AA. Thestate-*scales are real tints (state-success-50=rgb(240, 253, 244)), which is exactly why the pattern looked safe by analogy and isn't.What was measured
Ratios computed from the app's own resolved token values (read back via
getComputedStyleon the running SPA, not from a palette table):text-primary-accessibleon the fill:hovertext-gray-500)text-primary-accessible-darkon the dark tintAll 17 shipped pairings pass in both themes; the full table is in the verification section below.
The fix
47 opaque
bg-primary-50/100/200fills andhover:affordances across 25 files, replaced with the neutral-surface pattern PR #1090 established for the skill-command chip:bg-gray-100+text-primary-accessiblebg-gray-700+text-primary-50Covered: citation badge + number pills, share/artifact-share owner chips, artifact "Latest" pill, KB engine badge, selected rows in the role dialogs, selected cards in the report/export/share/artifact-share/fine-tuning flows, dialog header icon tiles, the agents empty-state and migration step tiles, the top-users avatar, the cost-report segmented control, two admin form panels, and 11 ghost-button
hover:states (hover:brightness-90goes with them — it only ever existed to tame the blue).Two knock-on fixes the new fills exposed, since a neutral fill sits closer to its text than a blue one did:
text-gray-500onbg-gray-100is 4.39 — those sub-labels move totext-gray-600/dark:text-gray-300.dark:text-primary-accessible-darkis only 2.63 ondark:bg-gray-700, so every ghost button whose dark hover surface changed gaineddark:hover:text-primary-50.Deliberately left alone
bg-primary-50/40composites torgb(200, 225, 255), a genuine pale tint that passes comfortably. These are large transient drag-and-drop surfaces (create-training-job,create-inference-job,skill-form,knowledge-baseopt-in panel) where the blue is the signal and no small text sits on it. Happy to convert them too if you'd rather the blue go entirely.chat-input.component.html(drag-over state) — same category, and PR feat(composer): slash commands to invoke enabled skills #1090 is actively editing that file.bg-primary-500+ fill with white text — toggles, progress bars, checkboxes, status dots. Those were never the problem.Guardrails
src/branding/README.mdwas recommending the broken pattern outright —| Decorative tint (badge or panel background) | bg-primary-50, dark:bg-primary-900/30 |. That row is what propagated it. It now documents the neutral pattern, plus why the scale is not a tint ramp and whyaccessible-darkis not safe on a tinted fill. The same rule, with the measured numbers, is added to.claude/skills/tailwind-ui/references/app-conventions.md.Verification
npx ng test --no-watch— 2868 passed (241 files)npx ng build --configuration development— cleancolorScheme, body background confirmed atoklch(0.985 0.002 247.839)), with contrast read from computed styles rather than eyeballed:Branched from
developat3c2ea5d1; PR #1090's two skill-command sites are untouched.🤖 Generated with Claude Code