# Lumi UI and theme system Lumi UI is the project-native design layer used by every page rendered through the shared EJS layout. It keeps route behavior and page-specific JavaScript separate from visual tokens and reusable components. ## Files - `src/web/public/lumi-tokens.css`: semantic colors, spacing, radii, shadows, typography, compatibility aliases, and reduced-motion behavior. - `src/web/public/lumi-layout.css`: application shell, sidebar, content containers, responsive grids, stacks, clusters, and mobile navigation. - `src/web/public/lumi-components.css`: buttons, forms, cards, tables, lists, badges, alerts, tabs, modals, empty/loading/error states, stateful action buttons, and tooltips. - `src/web/public/lumi-state-button.js` and `src/web/views/partials/state-button.ejs`: reusable multi-state button behavior for submit/loading/success actions. - `src/web/public/lumi-interactions.js`: progressive interaction layer for server-sent events, no-auto-refresh notices, dirty settings save bars, expandable settings containers, refresh prompts, and soft navigation. - `src/services/web-events.js`: small role-aware Server-Sent Events bus exposed at `GET /api/events` for authenticated users. - `src/web/public/styles.css`: legacy and feature-specific styles that still use the shared tokens. New general-purpose styling belongs in the Lumi UI files. - `src/web/views/partials/page-header.ejs`: standard page title and description. - `src/web/views/partials/theme-vars.ejs`: safe active-theme variables for shell and standalone pages. - `src/services/themes.js`: built-in themes, custom theme CRUD, validation, migration, fallback handling, and active-theme selection. Use `lumi-stack`, `lumi-cluster`, `lumi-split`, `lumi-grid`, `page-header`, `button-group`, `card`, `panel`, `table-wrap`, `empty-state`, `loading-state`, and `status-indicator` before adding one-off layout rules. Preserve existing IDs, field names, data attributes, and JavaScript hooks when restyling a page. ## Interaction Rules Pages should not self-refresh for state or progress changes. Core connection recovery now displays a notice instead of calling `window.location.reload()`. Server-originated events use `GET /api/events` with explicit event names such as `server:status`, `server:warning`, `ai:model_status`, and `data:new_available`. Admin-only events must be published with `{ role: "admin" }`. List/data updates should announce that new data exists and show a refresh prompt. The shared refresh prompt uses a 3-second cooldown before another refresh can be requested. It does not replace list contents automatically. Forms that represent page settings should add `data-lumi-settings-form`. Action-only forms must not use that attribute. The shared dirty-state layer tracks original values, marks changed fields with theme-aware unsaved styling, shows a top Save changes bar, warns before accidental navigation, and clears markers only after successful saves. Expandable settings rows use `data-lumi-expandable-settings` on a `
` container. Preview text can be wired with `data-placeholder-preview="#field-id"`; known placeholders such as `{gifter_username}`, `{item_name}`, `{creator_username}`, and `{amount_display}` render with plausible sample values without changing the saved template. Soft navigation progressively enhances same-origin links by replacing `main.content`, updating history, and fading content in place. If a fetch fails, JavaScript is unavailable, or unsaved settings are present, navigation falls back to normal browser behavior. ## Themes Lumi ships with six read-only themes: Lumi Default, Lumi Dark, Lumi Light, High Contrast, Midnight, and Soft Aurora. Admins select them from **Admin > Theming**. Built-in themes cannot be renamed, edited, or deleted. Open a theme's **More actions** section and duplicate it to create an editable custom theme. Custom themes can be previewed in light and dark mode, saved, applied globally, renamed, duplicated, or deleted. Deleting the active custom theme falls back to Lumi Default. The compact editor exposes common colors first. Advanced controls cover background glows, raised surfaces, links, buttons, inputs, focus rings, radius, shadow strength, and spacing scale. Typography controls use constrained font presets plus bounded base-size, heading-scale, and control-density ranges. The server accepts only six-digit hex colors, supported font presets, bounded metric values, and readable text/button/input contrast. Draft values are isolated to preview roots. The compact preview and pop-out preview update colors, role colors, metrics, spacing, and typography before save, but the editor shell and live site keep the active saved theme until the admin saves/applies the custom theme. The compact preview is hidden on narrow phone layouts; use the Preview action to open the synchronized pop-out instead. The compact preview includes representative headings, pills, cards, buttons, state buttons, inputs, toggles, alerts/statuses, badges, tables, modal samples, dirty-state markers, and spacing samples. The pop-out uses a faithful Lumi page shell with safe sample data so admins can test draft themes against dashboard, settings, logs, AI, and component-like layouts without exposing real admin data. Missing or invalid stored values are replaced from the custom theme's built-in base. Existing installations with modified legacy `theme_light_*`, `theme_dark_*`, or `theme_role_*` settings are migrated once into a custom **Migrated Theme** and selected automatically. The legacy `/admin/theming` POST route remains supported and writes into an editable custom theme. Run `npm run verify:webui` to compile every EJS view and exercise built-in theme validation plus custom duplicate, apply, edit validation, typography validation, stateful theme actions, localhost login rendering, rename, and delete. ## Localhost Login Development builds opened from `localhost`, `127.0.0.1`, or `::1` show a **Localhost Login** option. It defaults to username `admin` and password `admin` unless those settings have already been changed. The option is not inserted into the login list, cannot be used, and does not satisfy setup requirements for non-localhost requests. Admins can change the localhost username and password from **Admin > Settings** when the settings page itself is accessed through localhost. Leaving the password field blank keeps the existing password. ## Lumi AI Settings And Feedback Lumi AI's main Selected model dropdown lists only installed/downloaded models. If the configured model is missing, the settings page shows a warning and saving requires selecting an installed model. Main context, gate context, and output token budgets use shared presets from Tiny (256) through Extra extended (32768). Unsupported freeform values are rejected server-side. AI feedback supports `feedback_kind` values `strict_correction` and `instruction_based`; instruction-based feedback is the default because most reviews are guidance for future replies rather than exact replacement answers. Feedback tags include `wrong_tool_usage` for cases where the model called the wrong tool or failed to call an expected tool. Review, edit, and implementation views show both the kind and tag so admins can tell direct answer corrections from broader tool-calling or instruction guidance. Model/runtime downloads and the combined Start/Restart runtime control use the Lumi state button behavior. Enhanced browsers start downloads and runtime actions through fetch, update button state/progress in place, and avoid hard page refreshes; the underlying POST routes remain available for non-JavaScript fallbacks. ## Homepage Content Admins configure homepage external link buttons from Admin > Settings with the Homepage content builder. It writes the existing `homepage_link_buttons` JSON setting behind the scenes. Each entry may include `enabled`, `label`, `description`, `url`, `icon_url`, `permission` (`public`, `user`, `mod`, `admin`), and `sort_order`. Links open in a new tab with `rel="noopener noreferrer"` and are filtered server-side by permission. Admins configure priority-based hero entries with the same builder; it writes the existing `homepage_hero_entries` JSON setting behind the scenes. The homepage renders the first enabled, available entry the current user can access. Hero entries support type, priority/order, permission, source/embed/image URLs, video IDs, availability mode, autoplay mode metadata, and duration fields. Slow external availability checks are intentionally avoided; entries fail closed if required local configuration is missing. ## Admin Dashboard And Logs The admin dashboard polls `GET /api/admin/dashboard-metrics` for process uptime, memory, plugin counts, content counts, and recent log severity totals. The dashboard renders lightweight SVG graphs using Lumi tokens and does not add a frontend framework dependency. The logs page keeps server-side range/severity/limit filters and adds a labeled responsive filter bar with search, reset, refresh, and download actions. Search filters the loaded entries client-side; changing range, severity, or limit reloads the same `/admin/logs` route with query parameters. ## Visual references - [Home, desktop](screenshots/lumi-home-desktop.png) - [Home, 360px mobile](screenshots/lumi-home-mobile.png) - [Custom theme editor, desktop](screenshots/lumi-theme-editor-desktop.png) - [Custom theme editor, 360px mobile](screenshots/lumi-theme-editor-mobile.png) The broad sidebar and content structure remains in place. Theme controls moved from one long raw color form into the Theme Studio library and grouped custom editor; no navigation destination or non-theme control was relocated.