Files
TanTan fa9c73363d
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
SnowLuma v2.4.2 source snapshot
2026-11-13 09:02:00 +08:00

112 lines
6.1 KiB
Markdown

# Host appearance overlays
Set `SNOWLUMA_WEBUI_TARGET=maibot` on the **SnowLuma process** to use MaiBot's
millennium appearance. The same release bundle supports standalone and embedded
deployments. An unset or empty value keeps the existing SnowLuma experience;
unknown values fail WebUI startup with an explicit diagnostic.
This first adapter implements **millennium only**, in light and dark modes.
Other MaiBot styles are not implemented. There is no new appearance selector in
SnowLuma. The host owns theme changes; existing theme controls are unavailable
while managed, and the settings page explains which preferences are preserved.
## Runtime and ownership
1. Core reads the environment at startup. [Static document delivery](../core/src/webui/static-frontend.ts)
injects a validated, public JSON bootstrap into `/`, `/index.html` and SPA
fallback documents. Documents use `Cache-Control: no-store`. No secrets or
arbitrary environment variables reach the browser.
2. [Frontend bootstrap](src/appearance/runtime.ts) loads only the selected
target's assets before mounting React. Standalone deployments load no target
stylesheet, font or host listener. Vite development serves the same bootstrap
when its process has the environment variable; production builds remain neutral.
3. [ThemeContext](src/contexts/ThemeContext.tsx) keeps `savedAppearance` separate
from effective `appearance`. Only saved preferences enter localStorage or
`config/ui.json`. Target updates never invoke the persistence API.
4. The target owns colors, fonts, radius, wallpaper, CSS variable overrides,
custom CSS and custom cursor presentation. These saved values are preserved
but suspended. UI scale, density, motion/accessibility, navigation layout and
data preferences remain available. Safe-mode restrictions still apply when
returning to native appearance.
5. A single layout effect applies effective appearance. Changes do not reload
the document, change React keys or restart sessions. All target rules are
scoped to `html[data-webui-target]`, including portals under `body`.
## Host protocol, version 1
The contract lives in [common/webui-target.ts](../common/src/webui-target.ts).
The deployment environment is an opt-in, **not a live transport**. Existing
launchers need to implement the sender below; merely setting the environment
variable cannot convey another process's current theme.
A same-document embedding bridge (for example Electron `executeJavaScript`)
publishes the latest snapshot before dispatching the event:
```js
const appearance = {
version: 1,
target: 'maibot',
style: 'millennium',
mode: 'dark', // 'light' | 'dark' | 'system'
};
window.__SNOWLUMA_HOST_APPEARANCE__ = appearance;
window.dispatchEvent(new CustomEvent('snowluma:host-appearance', { detail: appearance }));
```
The snapshot handles messages sent before React or the lazy asset loader is
ready. Publish on each guest `dom-ready` and whenever the host's resolved
brightness changes. A host that already follows MaiBot should forward its own
resolved `light`/`dark` state; this avoids competing sources of truth. Validate
the guest's expected origin before injecting, serialize values with
`JSON.stringify`, and do not enable Node integration in the guest.
Send `{ version: 1, target: null }` using the same mechanism to release the
override for the current document. The original appearance returns immediately.
The opted-in document keeps its host subscription so the host can reapply the
target later. Listeners are released on provider unmount. This does not change
the startup environment or enable a target in a standalone deployment.
The current adapter starts with millennium and system brightness before a host
snapshot arrives. The last valid snapshot remains active if the host stops
sending. Snapshots are not persisted or shared across origins/tabs. A reload
requires the host to publish again. Unsupported versions, targets, styles or
modes are rejected with `[SnowLuma appearance]` console diagnostics; the previous
valid snapshot remains. The host cannot supply CSS, URLs or executable content
through this protocol. No unauthenticated HTTP write endpoint is introduced.
## Adding a target
- Add its identifier, styles and default style to `WEBUI_TARGETS` in the shared
contract. Keep validation at this boundary; do not accept arbitrary strings.
- Add one lazy loader to the exhaustive [frontend registry](src/appearance/registry.ts).
Implement a pure saved-to-effective resolver and scoped stylesheet under
`src/appearance/targets`. Keep any fonts local with their redistribution license.
- Use semantic color variables, shared component classes and neutral
`data-ui-surface` markers. Do not add target-name checks to business pages.
- Never persist resolved overrides, mutate saved preferences, introduce a
second DOM theme writer, or remount the application to switch presentation.
- Test default asset/listener isolation, initial document routes, malformed
messages, rapid updates, cleanup and exact restoration of saved preferences.
Verify forms, focus, scroll, portals and mobile layout with an isolated backend.
Electron integration needs a separate real-host check.
## Sources and checks
The adapter follows the main MaiBot repository's
[millennium stylesheet](https://github.com/Mai-with-u/MaiBot/blob/5c7637e6913c84d1c3d5e742fa9e95a7e70d3501/dashboard/src/styles/millennium.css),
not the archived standalone WebUI. Colors/materials also match the
[OneKey shell](https://github.com/Mai-with-u/MaiBotOneKey/blob/e8073b0bce35c8c4da973b06156602e14c42fee5/src/renderer/src/styles/millennium.css).
The bundled Jersey 20 font is from that MaiBot revision, copyright The Soft Type
Project Authors, under the [SIL Open Font License](src/appearance/targets/assets/Jersey20-OFL.txt).
Its license is emitted alongside the font in production assets.
Focused checks (from the repository root):
```sh
pnpm --filter @snowluma/common exec vitest run tests/webui-target.test.ts
pnpm --filter @snowluma/core exec vitest run tests/webui-static-frontend.test.ts
pnpm --filter webui exec vitest run tests/target-appearance.test.ts
```
Run the normal workspace checks from `AGENTS.md` before handing off changes.