Files
deepseek-harness/packages/client/ui-theme/README.md
T
ihsiang 6d6f8f044c feat(ui): adaptive content width and font-size control
Add conversation adaptive content width and a Settings font-size control,
with theme presenter, font-size row, snapshots, tests, and agent notes.
2026-08-25 20:42:03 +08:00

5.8 KiB
Raw Blame History

@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) and the conversation content font size (fontSize, integer px 1217, default 14), 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], --dsh-content-font-size on body, and inline alias tokens). A loopback browser provides the service immediately with the defaults, then loads the ui-theme namespace in the background and writes each accepted 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. The Client keeps Host settings persistence disabled on non-loopback pages, so their selections remain process-local even though Connection authentication applies to every API method. 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 <body> tag. Each index response embeds the registered Host settings for ui-theme (preference and font size), or the schema defaults when no settings provider is present; the browser resolves system from the OS scheme, then sets color-scheme, body[data-ds-dark-theme], and --dsh-content-font-size 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.

The plugin registers two rows into the settings General section: the Appearance preference cubes and the font-size stepper (1217 px, centered value with hover-revealed up/down arrows, a px unit label after the pill). The font size drives --dsh-content-font-size; gradient-shadow-text.css derives --dsh-content-font-delta from it and shifts the markdown heading/base ladder by the same px increment, while table, small, and code variants stay fixed. Consumers outside the token ladder (the user bubble, the composer draft) read the same pair directly.

src/styles/ holds five sheets imported in order by ui-theme's dynamic client entry: base.css, design-platform.css, scrollbar.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.

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 — values absent from cssdesign (for example the design's #4176E6 tab blue) are deliberately not appended; the nearest semantic token wins. Design-owner-approved additions are the exception and enter as a static step plus a semantic alias in the same change (--dsw-static-blue-900 / --dsw-alias-label-primary-bluish).