feat: Geist-based theme system + design standards + routed demo#350
Merged
Conversation
…cumentation Implements a module-level RouterCore singleton (createRouter / useRouter) with navigation guards, SPA/MPA View Transitions support, and SSR-safe browser API guards. Binds to r-router / r-link components via _bind/_unbind so they pick up programmatic navigation without an event bus. Ships VitePress docs (EN + CN), updates ranui/utils READMEs and CLAUDE.md with the full router API reference. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
60 tests covering createRouter/useRouter singleton, push/replace/history navigation, hash mode, base path stripping, beforeEach guards (allow/cancel/ redirect/order/unsubscribe), afterEach, onRouteChange, component bind/unbind, destroy cleanup, _buildLocation query parsing, MPA style injection, onPageSwap/onPageReveal, SPA transition graceful degradation, and SSR window guards. Also limits vitest to maxForks: 4 to prevent JavaScript heap OOM when 60+ jsdom workers run in parallel. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…enerateStaticPages Implements build-time route pre-rendering so static HTML has the correct r-route visible (no hidden attr) and all others hidden. Key changes: - HTMLElementMock: reflect hidden property to/from attributes so _update() works in SSR (previously hidden = true only set a JS property, not serialized) - ssr-registry.defineSSR: stamp _ssrTag on each component prototype so HTMLElementMock.serialize() uses the correct custom element tag name when components appear as children in a larger tree (previously serialized as <div>) - utils/router/index.ts: add _ssgPath context (setSSGPath/clearSSGPath/getSSGPath) plus RouterCore.matchRoute() and RouterCore.getStaticPaths() for SSG enumeration - components/route/index.ts: add _preSerialize() hook — called by HTMLElementMock before serialization; resolves hidden state from the active SSG path context - utils/ssg.ts: new module exporting renderStaticPage() and generateStaticPages() - package.json: add NODE_OPTIONS=--max-old-space-size=4096 to test scripts to prevent jsdom OOM on full parallel test run (replaces removed Vitest 4 poolOptions.forks config which was deprecated) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Deleted transitions.ts, windows-98.less, windows-98.ts, windows-xp.less, windows-xp.ts, wired-assets.less, wired-overlay.ts, wired.less, wired.ts, color.less, and compat.less files. - Cleaned up theme pack structure by removing unused styles and scripts to streamline the codebase.
- Removed all theme packs and streamlined the theme management to only support light, dark, and system themes. - Simplified theme utility functions by eliminating theme pack logic and related storage. - Introduced a new token architecture based on the Geist design system, enhancing color and semantic token definitions. - Updated demo page to showcase the new token-driven design, including color scales, radius, elevation, and component examples. - Fixed dark mode bugs by ensuring all colors are token-based for automatic theme adaptation.
- Add utils/i18n core (I18nCore / createI18n / useI18n): t() with locale
fallback + {param} interpolation, setLocale/onChange, addMessages,
localStorage persistence, navigator detection; SSR-safe. Mirrors the
router core/singleton design and is exported from the ranui barrel.
- Split demo locales into demo/locales/{en,zh}.json (resolveJsonModule);
demo/i18n.ts now consumes the core as a thin data-i18n DOM binding.
- Replace the demo language toggle button with an r-select picker, and
add GitHub/Issues nav icons (assets/icons/github.svg, issue.svg) that
inherit currentColor for theme adaptivity.
- Add test/unit/utils.i18n.test.ts (16 cases); update changelogs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove docs/SPECIAL_THEME_PACKS_PLAN.md (theme packs were deleted).
- Rewrite docs/THEME_STYLE_SYSTEM_DESIGN.md to describe the current
Geist-based token system: base scales → semantic tokens → component
tokens, the single-source dark mixin, runtime API, files, and tests.
- Regenerate docs/style-tokens-{public,parts}.md from current components.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Dotted-grid backdrop, hero accent glow, eyebrow badge, gradient heading, and primary/ghost CTA buttons. - Numbered section eyebrows in Geist Mono (01, 02 …). - Hover micro-interactions on cards and color swatches. - New hero i18n keys (badge, CTAs) in en/zh locales. All chrome stays token-driven and theme-adaptive. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dogfood ranui's own router to split the demo into chapters: - Routes (history mode): Overview, Design (with Geist design.md / design.dark.md references), Components, Guide (install / theming / i18n / SSR with code blocks). - demo/public/_redirects (/* /index.html 200) for Cloudflare Pages SPA fallback so deep links and refresh work in history mode. - routechange highlights the active nav link; the canvas radar re-renders when the Components route becomes visible. - r-link box model injected via `sheet`; per-route section numbering via CSS counters; expanded en/zh locales for nav/design/guide. - Fix radar sample data (scoreRate is 0–100, not 0–1). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Fix the generic hero: left-aligned, solid high-contrast heading (drop gradient text that lowered contrast) and remove the decorative glow; tighten the spacing rhythm. - Add interaction-state semantic tokens per Geist's 100–1000 model: --ran-color-bg-hover/active and --ran-color-border-hover/active. - Rebuild the Design route into a methodology page modeled on the Vercel/Geist spec: color state ladder (each step → a fixed state), spacing scale + rhythm, typography roles, motion durations, copy do/don't, and accessibility. The page follows its own rules (do/don't use ✓/✕ icons + text, not color alone). - State legend + spacing scale generated in index.ts; expanded en/zh locales for the new content. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add docs/DESIGN.md — an executable design spec (color state ladder, spacing rhythm, typography roles, radius/elevation, motion, copy, accessibility, component application, pre-ship checklist), modeled on the Vercel/Geist design methodology. - Fix hero CTA vertical centering: the inner <a> used height:100% against an auto-height host (collapsed to auto) plus inherited line-height; now a fixed 42px host height + line-height:1 + box-sizing:border-box. - Fix mobile navigation: route links were hidden < 820px; they now drop to a full-width row (brand tagline hidden) so chapters stay reachable. Found via a Vercel-Product-Design-skill review pass (verified rendered output in light/dark and at compact/wide viewports). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add aria-labels to the GitHub/Issues links (they render icon-only on mobile, so they had no accessible name) and label the primary <nav>. - Honor prefers-reduced-motion: disable smooth scroll, transitions, and hover transforms — as DESIGN.md §5 requires. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CLAUDE.md is auto-loaded when working in this repo, so the standards now apply to all future AI work here: - Add a top-level "Design Standards" section pointing to docs/DESIGN.md as authoritative, with the non-negotiables (color state ladder, dark-safe fallbacks, spacing/type/motion tokens, copy, a11y, verify rendered output). - Update the stale theme section (theme packs were removed; no more setThemePack/RanThemePackName) and document the i18n utility. - Refresh the token list (Geist scales, space/shadow/radius, trimmed skin layer) and the project layout. - Add 8 pitfalls distilled from this session: dark-safe color fallbacks, button vertical centering, icon-only aria-label, prefers-reduced-motion, no color-only state, don't hide mobile nav, r-link box model via `sheet`, and the Cloudflare _redirects deploy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add docs/DESIGN.md to the `files` whitelist so consumers get the design spec. CLAUDE.md stays unpublished (repo-internal working instructions). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Large, breaking changes (theme packs + setThemePack API removed, Geist token system) warrant a minor bump under 0.x semver; switch the prerelease channel from alpha to beta. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Major bump to 1.0 (still beta): the Geist token system and component realignment mark the first stable design baseline; theme packs and the setThemePack API are removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Geist-literal menu/modal shadows were too faint to read, so floating layers (dropdown, select, modal) looked flat, and message was on the flat card tier entirely. - Recalibrate light --ran-shadow-menu / --ran-shadow-modal so overlays clearly lift off the page; keep --ran-shadow-elevated (cards) subtle. - message: box-shadow now uses --ran-shadow-menu (was the card tier). - Document the principle "elevation = role" (raised / overlay / modal, each must be perceptible) in DESIGN.md §4 and CLAUDE.md. Verified rendered: select dropdown (light) and modal (dark) now read as elevated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complete the "elevation = role" standard: alongside the DESIGN.md §4 table and the Design Standards bullet, add the operational gotcha as a pitfall row (overlays use --ran-shadow-menu, not the card tier; every tier must be perceptible). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BREAKING CHANGE: Node-only APIs (Server/WSS/Router/runCommand/…) are no
longer re-exported from the default `ranuts` entry — import them from
`ranuts/node`. Keeps the default bundle browser-safe (no node: builtins).
- Remove the broken `./react` subpath export (no source, never built).
- Expose previously-unreachable subsystems as ESM subpaths: `./visual`,
`./vnode`, `./wicket`, `./arithmetic`, `./sort`, `./optimize`.
Add barrels for `vnode` and `sort` (was empty `export {}`).
`cache` is intentionally NOT exposed (demo server with import-time
side effects).
- Add smoke tests for the newly-public surface (optimize, visual math,
vnode h/patch); 139 → 160 tests.
- Lock the new export contract in package-exports.test.ts.
- Add @vitest/coverage-v8 + `test:coverage` script.
- Bump version 0.1.0-alpha-23 → 1.0.0-beta-1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The error/warning state was signalled by color alone. Now it also shows: - an automatic status icon (CSS-mask SVG tinted by the status token), and - an optional `message` attribute rendering helper/validation text below the field, colored by status. Demo: the error email field shows the icon + "Enter a valid email address" (en/zh). Input contract tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Agents (and humans) had no per-component attribute/event reference, so using a component meant reading source. Add a generator that extracts each custom element's API and emit docs/COMPONENTS.md. - bin/generate-component-api.ts → docs/COMPONENTS.md (29 elements): attributes (observedAttributes), properties (get/set), events (CustomEvent), slots, and ::part() names — all from source, so it never drifts. `npm run doc:api`. - Publish docs/COMPONENTS.md + style-tokens-public.md with the package. - CLAUDE.md points to it as the element API source of truth. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`get checked()` returned "true"/"false" strings, so `if (el.checked)` was always truthy (even unchecked). It now returns a boolean, matching the native checkbox; the setter still accepts boolean or string. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same wart as checkbox.checked: these returned strings, so `if (el.prop)` was always truthy. Now return real booleans (setters still accept boolean|string; boolean attrs reflect as `disabled=""`): - r-input.disabled, r-input.required - r-checkbox.disabled (and fix the onChange disabled guard accordingly) Tests updated; input + checkbox suites pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…gines - r-input: `change` now fires on native change (blur/commit), not on every keystroke; `input` still fires per keystroke. Matches the native <input> and stops e.g. writing localStorage on each keypress. - r-select: listbox items now get `role="option"` (had aria-selected only) so screen readers announce them; long selected text ellipsizes (the selection item is now width-bounded). - r-button: `type` is a real observed attribute + property (was only a CSS hook), so it's discoverable in docs and settable via JS. - engines.node lowered >=24.0.0 → >=20.19.0 (fewer engine warnings). Regenerated docs/COMPONENTS.md. tsc clean; input/select/button/checkbox suites pass; role="option" verified in-browser. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- r-input field box now defaults to content height (auto + min-height 32px) instead of 100%. With the new `message` below it, `height:100%` re-resolved against the taller host and the box ballooned. Now all fields are a consistent height and the message stacks below. - Demo password field used both `label` and `icon`, which the component treats as mutually exclusive (the absolute floating label collided with the icon → "lPassword"). Switched it to icon + placeholder so the lock icon renders cleanly. Verified: field heights are [32, 32, 54(=32+message)]; password shows a clean lock icon. input suite passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The generated component API listed only names, so agents knew an event
existed but not its `detail` shape, nor a property's type. Now extracted
from source:
- Properties show types: `checked: boolean`, `value: string`, …
- Events show detail keys: `change → detail { value, label }` (select),
`change → detail { checked }` (checkbox), `input/change → { value }`
(input).
Regenerated docs/COMPONENTS.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each attribute in COMPONENTS.md now shows the type of its matching property (e.g. `checked: boolean`, `value: string`); attributes without a typed accessor stay bare, which flags them as markup-only. Makes the attribute↔property correspondence explicit for agents. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- The API generator now resolves same-file `enum`/`type` aliases, so r-loading `name` shows the real icon-name union instead of leaking the internal `ICON_NAME_AMP` type name. - Annotated r-message getters/setters (`type`/`content`: string | null, `sheet`: string) so they show types in the docs. Regenerated docs/COMPONENTS.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract each accessor's `@description` JSDoc (getter preferred) and show it next to the typed property in COMPONENTS.md, e.g. `value: string — input 的值`. Properties are rendered as a list when any has a description, inline otherwise. Descriptions are the source's (Chinese) JSDoc; events have no JSDoc so they stay name + detail. Regenerated docs/COMPONENTS.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The visual engine shipped a PixiJS-style API but only Canvas2D worked end-to-end. This wires up the GPU backends, unifies the pipeline, and fixes three latent bugs that made GPU rendering produce nothing. Backends: - Add async Application.create() factory that awaits renderer.init(), so WebGPU's async device setup actually runs (was never called). - Rewrite WebGLRenderer to extend BatchRenderer, sharing the triangulate→pack pipeline with WebGPU (vertex layout already matched); enable OES_element_index_uint for the shared uint32 index buffer. - Delete the dead, disconnected WebGL BatchPool + Container.renderWebGL stubs that never drew anything. Latent bugs fixed: - GraphicsGeometry.dirty was never set true on drawShape, so buildVerticesAndTriangulate always early-returned → GPU geometry was always empty even when wired correctly. - ObservablePoint.set()/setters invoked the change callback with no args, so Transform.onScaleChange/onSkewChange received undefined → the whole transform matrix became NaN. Any non-default scale/skew silently broke rendering. - Scenes never rebuilt after the first frame (needBuildArr only ever flipped to false). Replace with a per-scene structureVersion bumped by addChild/removeChild and Graphics draw/clear; the renderer rebuilds the batch arrays when the version changes, else just repacks vertices. clear() now fully resets geometry buffers so no stale data lingers. Hygiene: - Remove per-node per-frame console.log from hot render paths; gate the renderer banner behind options.debug (also unblocks testability). - needBuildArr/projection state moved off static onto the instance. Tests + verification: - Add visual-batch (data-level pipeline, transform-no-NaN, add/remove/clear rebuild) and visual-application (create wiring, getRenderer selection) suites. - visual-demo.html: a three-backend switcher with live add/remove, used to verify Canvas2D/WebGL/WebGPU all render on real hardware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… files - Updated style tokens documentation to enhance formatting and readability. - Refactored exports in index.ts for cleaner structure. - Simplified route definitions in router.ssg.test.ts. - Consolidated import statements in utils.router.test.ts for brevity. - Streamlined router index.ts regex pattern creation for clarity. - Optimized SSG utility functions for better readability. - Enhanced WebGLRenderer class methods for concise code. - Cleaned up vnode index.ts import statements for consistency. - Improved visual-math test cases for better readability. - Reformatted visual-demo.html CSS for improved structure and clarity.
…case The routed rebuild only carried a subset of components; these five were dropped by omission, not intent. Add a "More components" section to the Components route showcasing r-icon, r-colorpicker, r-popover (trigger + r-content), r-math (katex), and r-player, with en/zh strings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ark-safe r-select rendered an <r-icon name="arrow-down"> caret but never registered the icon, so the caret was missing (and logged "icon not registered") unless the consuming app happened to register arrow-down itself. The caret color was also hardcoded to #d9d9d9, which does not adapt to dark mode. - select now imports arrow-down.svg and registers it at module load, making the component self-contained for any consumer. - caret color uses var(--ran-color-text-secondary) so it flips with the theme. Demo: register the demo's icons via a side-effect module imported first, before any component module loads, so route content rendered on mount finds its icons in the registry instead of warning "icon not registered". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The demo was rebuilt into a routed app, so component showcases moved from the flat root page to the /components route and lost their #component-* anchors. The visual regression specs still targeted the old root-page structure, so every section locator failed with "element not found". - demo: add stable id="component-*" hooks to each showcase block on the /components route, and give the tabs semantic r-key values (overview/api/ theming/disabled) the spec already expects. - specs: navigate to /components and wait for domcontentloaded instead of networkidle (the demo player streams HLS continuously, so networkidle never settles and the navigation would time out). - prettier: reformat demo/index.html (Format check was failing on it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The panel was visually broken: r-popover portals the panel out of the
component's shadow root into document.body, so all shadow-scoped CSS stopped
applying — the saturation square collapsed to 0px, sliders floated unanchored,
and the inputs were non-functional decoration.
Redesign:
- Panel styles now live in panel.less and are injected as a <style> inside the
panel subtree, so they travel with the portaled content. Selectors are
uniquely namespaced and every color is a theme token, so dark mode just works
(tokens inherit from :root in the light DOM).
- Replace the fragile r-progress sliders with custom hue/alpha sliders using
percent-based, layout-independent thumb/dot positioning.
- Wire the value input + HEX/RGB format select to actually display and edit the
color, and emit a `change` event ({ value, hex, rgb, rgba, alpha }).
- Polished, Geist-aligned spacing, radii, preview swatch, and checkerboard
(token-driven); trigger swatch gets a hover state.
Verified in light and dark; contract tests extended (alpha parsing, currentValue,
change event) and visual e2e specs pass.
Co-Authored-By: Claude Opus 4.8 <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.
Summary
Redesigns ranui's theming around the Geist design system (light/dark only), aligns every component to it, rebuilds the demo as a routed showcase, and codifies the result as standards so future work (human or AI) stays consistent.
Theme system (Geist)
dark-overrides/transitions, the wired SVG pipeline, theroughjsdependency, and thesetThemePack/getThemePackAPIs. Only the base light/dark theme remains.--ran-gray/gray-alpha/blue/red/amber/green-100..1000,--ran-background-100/200) → semantic tokens (--ran-color-*, incl.-hover/-activestate tokens) → component tokens.theme/dark.lessredefines only the base scale via one mixin; semantic tokens flip automatically.--ran-space-*scale, shadows (elevated/menu/modal), focus ring, Geist Sans/Mono.Components
Utilities
utils/i18n:createI18n/useI18n/I18nCore) — mirrors the router core/singleton design; SSR-safe.Demo
r-router/r-route/r-link(Overview · Design · Components · Guide), history mode + a Cloudflare Pages_redirectsSPA fallback.r-select; light/dark toggle; everything token-driven.aria-labels, labelled nav,prefers-reduced-motion).Standards (so AI follows them automatically)
docs/DESIGN.md— an AI-facing, executable design spec (color states, spacing, type roles, motion, copy, accessibility, component application, pre-ship checklist).CLAUDE.md— new top-level "Design Standards" section pointing at DESIGN.md, refreshed theme/i18n references, and pitfalls distilled from this work.Also included (prior unpushed foundation the demo builds on)
r-routerJS API + View Transitions, RouterCore unit tests, and true SSG support forr-route.Verification
tscclean; theme + i18n unit tests pass; LESS compiles; demo builds.🤖 Generated with Claude Code