feat(ui): put the web app on the documentation site's palette - #194
Merged
Conversation
The app ran on Mantine's stock theme: teal primary, Inter named but never loaded, and no dark mode at all. The documentation site, meanwhile, has a palette with reasoning behind it — Ayu Dark surfaces, a violet accent, IBM Plex split by role — and a comment claiming the app already shared it. It did not. Nothing crossed between them: not a hex, not a typeface, not a variable name. The teal was the substantive problem. `healthColor()` returned "teal" for a healthy service while `primaryColor: "teal"` painted every button and link, so one hue meant both "healthy" and "clickable" on the same screen. The site picked violet precisely to avoid that, violet being the one hue in the palette that carries no status. ui/tokens.ts now holds the palette — the Ayu ramp, the violet accent, the three health hues and a categorical series palette taken from the brand mark — and ui/theme.ts binds it to Mantine under semantic names: brand, ok, warn, bad, info. A component asks for the accent or for a health state and gets whatever the palette holds, so re-hueing the product is a change to one file. The values mirror site/src/styles/fanout.css; the two are still separate sources kept in step by hand. Dark mode ships with it: defaultColorScheme="auto", a header toggle, and the scheme resolved by an inline script in the document head so the first paint lands on the right ground. The typeface is shipped rather than named — eight weights in the host, four inlined into each single-file embedded view, which needed assetsInlineLimit raised or the build would have emitted loose .woff2 files it never copies. Three latent bugs surfaced, all of them invisible while the app was light-only: the sign-in card hardcoded a white background, dashboard metric tiles used bg="gray.0" and rendered white-on-white, and the MCP app bridge passed theme: "light" as a literal, which would have left every embedded view light inside a dark host. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two browser stages copied `ui/theme.ts` by name, so `ui/tokens.ts` never reached the container and both `bun run build` invocations failed on an unresolved import. Globbing the directory means the next shared module does not have to remember this file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vishr
added a commit
that referenced
this pull request
Aug 24, 2026
* feat(ui): put the web app on the documentation site's palette The app ran on Mantine's stock theme: teal primary, Inter named but never loaded, and no dark mode at all. The documentation site, meanwhile, has a palette with reasoning behind it — Ayu Dark surfaces, a violet accent, IBM Plex split by role — and a comment claiming the app already shared it. It did not. Nothing crossed between them: not a hex, not a typeface, not a variable name. The teal was the substantive problem. `healthColor()` returned "teal" for a healthy service while `primaryColor: "teal"` painted every button and link, so one hue meant both "healthy" and "clickable" on the same screen. The site picked violet precisely to avoid that, violet being the one hue in the palette that carries no status. ui/tokens.ts now holds the palette — the Ayu ramp, the violet accent, the three health hues and a categorical series palette taken from the brand mark — and ui/theme.ts binds it to Mantine under semantic names: brand, ok, warn, bad, info. A component asks for the accent or for a health state and gets whatever the palette holds, so re-hueing the product is a change to one file. The values mirror site/src/styles/fanout.css; the two are still separate sources kept in step by hand. Dark mode ships with it: defaultColorScheme="auto", a header toggle, and the scheme resolved by an inline script in the document head so the first paint lands on the right ground. The typeface is shipped rather than named — eight weights in the host, four inlined into each single-file embedded view, which needed assetsInlineLimit raised or the build would have emitted loose .woff2 files it never copies. Three latent bugs surfaced, all of them invisible while the app was light-only: the sign-in card hardcoded a white background, dashboard metric tiles used bg="gray.0" and rendered white-on-white, and the MCP app bridge passed theme: "light" as a literal, which would have left every embedded view light inside a dark host. * fix(docker): copy every shared module into the browser build stages The two browser stages copied `ui/theme.ts` by name, so `ui/tokens.ts` never reached the container and both `bun run build` invocations failed on an unresolved import. Globbing the directory means the next shared module does not have to remember this file. ---------
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 web app ran on Mantine's stock theme — teal primary,
Internamed in the font stack but never loaded, and no dark mode at all. The documentation site has a palette with reasoning behind it (Ayu Dark surfaces, a violet accent, IBM Plex split by role) and a comment claiming the app already shared it. It did not: not a hex, not a typeface, not a variable name crossed between them.The collision
healthColor()returned"teal"for a healthy service whileprimaryColor: "teal"painted every button, link and badge. One hue meant both "healthy" and "clickable" on the same screen. The site picked violet precisely to avoid this — violet is the one hue in the palette that carries no status meaning.What this does
ui/tokens.ts— new. The Ayu ramp, the violet accent, the three health hues (#7fd962/#ffb454/#f26d78, the site's own), a fourthinfohue, chart chrome, and a categorical series palette drawn from the brand mark's ribbons. Values mirrorsite/src/styles/fanout.css; the two remain separate sources kept in step by hand.ui/theme.ts— binds those to Mantine under semantic names:brand,ok,warn,bad,info. A component asks for the accent or a health state and gets whatever the palette holds, so re-hueing is a one-file change.primaryShade: {light: 7, dark: 5}picks the accent each scheme already uses on the site;autoContrasthandles the two-shade fill; Mantine'sdarkramp is replaced by Ayu, which lands the app on the documentation's exact ground.defaultColorScheme="auto", a header toggle, and the scheme resolved by an inline script inindex.html's head so the first paint lands on the right ground. Pairedtheme-colormetas replace the stray#f6f5f1.assetsInlineLimithad to be raised or the build would have emitted loose.woff2files it never copies intointernal/mcp/apps.chartTheme/statusHex/seriesColor) instead of six hardcoded green-greys and a Mantine hue set that included the health colors.Bugs this surfaced
All three were invisible while the app was light-only:
rgba(255, 255, 255, 0.9), so the auth page would have stayed white under a dark app — with no toggle on that screen to escape.bg="gray.0", the same color in both schemes; the four health tiles rendered white-on-white.mcp-app-frame.tsxpassedtheme: "light"as a literal to the app bridge, which would have left every embedded MCP view light inside a dark host.Verification
just checkpasses end to end. Exercised against a real binary with 3,746 seeded spans in both schemes, toggling each direction, plus the signed-out auth page in dark.Two things reviewers should know: the embedded MCP views are type-checked but not visually exercised (chat needs an AI provider key), and running
just checkon Go 1.27 failsTestAppendJSONString_MatchesEncodingJSONand regeneratesTHIRD_PARTY_NOTICESwith one extra component —go.modpins 1.26.5 and that is what CI and this branch used.🤖 Generated with Claude Code