Haifai-AI/baby-whale--packages-client-ui-theme ↗★ 1

@deepseek-ai/dsh-client-ui-theme

Theme plugin: Host bootstrap for the pre-plugin palette; DOM-free ThemeRuntime for light/dark/system state; --dsw-* token styles and Appearance settings row 适合需要系统级主题响应、自定义色彩标记和外观设置的用户。

Package
@deepseek-ai/dsh-client-ui-theme
Compatibility
Unverified
Harness peer range
workspace:^
Cordis peer range
workspace:^
Version
0.2.0-preview.1
License
MIT
Last updated
Sep 17, 2026

Other repositories with this package name

Install

This plugin has no verified bundle, or compatibility checks failed. Read the repository notes first. Read the full README ↗

@deepseek-ai/dsh-client-ui-theme

English | 中文

Theme plugin: ThemeRuntime over the --dsw-* token base stylesheets (static scale + alias semantic layers). The service owns the live theme preference (light/dark/system), resolves system through prefers-color-scheme, and publishes immutable ThemeSnapshots on the theme/change event; it never touches the DOM — ui-layout's presenter applies the resolved snapshot (html { color-scheme }, body[data-ds-dark-theme], and inline alias tokens). A loopback browser provides the service immediately with system, then loads ui-theme.preference in the background and writes each built-in selection through the Host settings API, whose local provider stores it in $DSH_HOME/settings.yaml by default; pushed settings changes and reconnects refetch it, rapid selections are serialized in gesture order with namespace revisions, and a rejected latest write reloads the durable value. A remote browser cannot access the privileged settings API, so its selection remains process-local. Third-party registered theme ids remain an in-process extension and do not cross the built-in settings schema; removing one never overwrites the last durable built-in preference. The Host-backed preferences decision owns the persistence boundary.

When the host composition includes an HTTP server, the host half injects a synchronous bootstrap immediately after the opening `` tag. Each index response embeds the registered Host setting for ui-theme.preference, or system when no settings provider is present; the browser resolves system from the OS scheme, then sets color-scheme and body[data-ds-dark-theme] before the shell loading page renders. Compositions without an HTTP server remain unaffected, and ThemeRuntime and ui-layout remain authoritative for client state and subsequent DOM updates after the plugin tree activates.

src/styles/ holds six sheets imported in order by ui-theme's dynamic client entry: base.css, design-platform.css, scrollbar.css, surfaces.css, gradient-shadow-text.css, and shiki.css. The client bundle compiles and injects them as plugin-owned global styles, so unload and HMR remove them with ui-theme instead of leaving theme CSS in the static web shell. scrollbar.css is the sole consumer of the --dsw-alias-scrollbar-* tokens and must follow design-platform.css, which declares them.

design-platform.css is layered, and the layers are the contract. At the bottom sits the raw palette (--dsw-static-*, exported from the upstream Figma variable set); above it a closed neutral ramp (--dsw-gray-00 … --dsw-gray-95, declared per palette) and the accent/state inks (--dsw-accent*, --dsw-state-*); at the top the semantic aliases feature CSS consumes. Feature stylesheets read aliases only — a --dsw-static-* or --dsw-gray-* reference in a feature sheet is a surface that has not migrated yet. The light and dark blocks are independent palettes rather than inversions: dark places chrome above the canvas and re-picks its label steps for a dark ground.

base.css declares the fixed scales the alias layer and feature CSS share: the font stacks, the six-step corner scale (--dsw-radius-xs … -full), and the motion tokens (--ds-ease-out, --ds-ease-in-out, and the three durations). There is deliberately no overshoot or elastic curve. gradient-shadow-text.css owns the elevation ladder (--dsw-shadow-lv1/2/3, each a hairline plus a contact and an ambient shadow, with a dark override for all three), the two material fills, and the typography roles.

Two rules bind how the accent is spent, and both are enforced by review rather than by a gate. The accent appears at most once per column at rest — a sidebar shows one selected row, a settings panel one selected nav cell and one primary button, never both. And a selected item takes the accent as its fill with inverted ink, setting color: var(--dsw-alias-label-primary-foreground) so descendants drawing in currentColor invert with it.

surfaces.css themes the surfaces the browser draws rather than a component: ::selection, caret-color, accent-color for the UA-painted form controls, and the default keyboard-focus ring for :focus-visible. All of them resolve from the alias layer, so it too follows design-platform.css — a custom property substitutes against the cascade position of the rule that reads it, not sheet order alone. The focus ring's three values (--dsh-focus-ring-width, --dsh-focus-ring-offset, --dsh-focus-ring-color) are declared on body beside the ring rule and are the shared spelling for feature CSS that draws its own ring.

Scrollbar rebinding contract: scrollbar.css binds --dsh-scrollbar-thumb and --dsh-scrollbar-thumb-hover on body to the l1 (base-surface) tokens, and both rendering paths read that pair. An elevated surface (menu, popover, dialog) sets --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2) and --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2) on its own container; one rebind retints whichever path the engine took. The pair's other legal target is transparent, which draws no thumb at all — ui-sidebar rebinds its column that way while the pointer is elsewhere. A rebind to the l1 pair is not a rebind; it restates the base-surface default. --dsh-scrollbar-width mirrors the WebKit bar's layout width for surfaces that align themselves beside a space-consuming bar — ui-conversation reads it for the overlay composer seat's right offset — and the scrollbar-styles spec pairs it with the mirrored rule and the consumer.

The two paths are mutually exclusive by construction. scrollbar-width/scrollbar-color sit inside @supports not selector(::-webkit-scrollbar) because a non-auto value of either makes Chromium and Safari discard every ::-webkit-scrollbar* rule for that element, ::-webkit-scrollbar-thumb:hover included — declaring both unconditionally leaves --dsh-scrollbar-thumb-hover with no rendering anywhere. Firefox therefore takes the standard properties and WebKit-based engines take the pseudo-elements, so the hover token only ever renders through the pseudo-element path. Reasoning and the measured computed values: the scrollbar Agent Note.

Model Experience

None, as the theme service manages a browser preference; nothing here reaches a model request.

KV Cache effect

None; this package neither assembles nor sends a provider request.

Known Limitations and Deferred Work

  • Third-party themes are an extension point, not a product — registering one means overriding same-named alias variables; no validation exists that an override set is complete.
  • The token sheets are the sole color authority — a colour a feature sheet needs is added here as a ramp step plus a semantic alias in the same change, never written as a literal at the call site. The retina asset the sheets were originally exported from is no longer the source of truth; the ramp is.
  • The light palette cannot support a third label step above AA — on the sidebar's own fill a neutral must sit at or under ~112 to clear 4.5:1, so --dsw-alias-label-caption and -tertiary resolve to the same value in light and separate in dark. -quaternary, -dimmed, and --dsw-alias-separator-primary are decoration and must never be the only carrier of a meaning. Components that relied on caption and tertiary differing separate them by size or weight instead.
  • backdrop-filter is a layout decision as well as a visual one — it establishes a containing block for position: fixed descendants, and ui-primitives' Menu portal mode positions itself from an anchor's viewport rect. The blur therefore stays on leaf layers that host no portaled menu (the dialog scrim, the toast); a column or a card that contains a picker must not take one.
  • --dsw-static-* has no feature-CSS consumers left, but the palette remains declared — ansi.ts reads the raw blue steps for terminal colour, and the Office previews read the file-kind and document-surface aliases derived from it. Deleting the raw layer would turn any surviving reference into a silently invalid value rather than an error.