Get started

Дизайн імпорту користувацької теми Tweakcn

Дизайн імпорту користувацької теми Tweakcn

Статус: схвалено в терміналі 2026-04-22

Підсумок

Додати рівно один браузерно-локальний слот користувацької теми Control UI, який можна імпортувати з посилання поширення tweakcn. Наявні вбудовані сімейства тем залишаються claw, knot і dash. Нове сімейство custom поводиться як звичайне сімейство тем OpenClaw і підтримує режими light, dark і system, коли імпортований payload tweakcn містить набори токенів і для світлої, і для темної теми.

Імпортована тема зберігається лише в поточному профілі браузера разом з рештою налаштувань Control UI. Вона не записується в конфігурацію gateway і не синхронізується між пристроями чи браузерами.

Проблема

Система тем Control UI зараз замкнена на трьох жорстко закодованих сімействах тем:

  • ui/src/ui/theme.ts
  • ui/src/ui/views/config.ts
  • ui/src/styles/base.css

Користувачі можуть перемикатися між вбудованими сімействами та варіантами режимів, але не можуть додати тему з tweakcn без редагування CSS репозиторію. Запитаний результат менший за загальну систему темізації: залишити три вбудовані теми й додати один керований користувачем імпортований слот, який можна замінити з посилання tweakcn.

Цілі

  • Залишити наявні вбудовані сімейства тем без змін.
  • Додати рівно один імпортований користувацький слот, а не бібліотеку тем.
  • Приймати посилання поширення tweakcn або прямий URL https://tweakcn.com/r/themes/{id}.
  • Зберігати імпортовану тему лише в локальному сховищі браузера.
  • Забезпечити роботу імпортованого слота з наявними елементами керування режимами light, dark і system.
  • Зберегти безпечну поведінку в разі помилок: невдалий імпорт ніколи не ламає активну тему UI.

Не цілі

  • Жодної багатотемної бібліотеки чи браузерно-локального списку імпортів.
  • Жодного збереження на боці gateway або синхронізації між пристроями.
  • Жодного довільного редактора CSS або редактора сирого JSON теми.
  • Жодного автоматичного завантаження віддалених ресурсів шрифтів із tweakcn.
  • Жодної спроби підтримувати payload-и tweakcn, які відкривають лише один режим.
  • Жодного загальнорепозиторного рефакторингу темізації поза швами, потрібними для Control UI.

Уже ухвалені користувацькі рішення

  • Залишити три вбудовані теми.
  • Додати один слот імпорту на основі tweakcn.
  • Зберігати імпортовану тему в браузері, а не в конфігурації gateway.
  • Підтримувати light, dark і system для імпортованого слота.
  • Перезапис користувацького слота наступним імпортом є очікуваною поведінкою.

Рекомендований підхід

Додати четвертий ідентифікатор сімейства тем, custom, до моделі тем Control UI. Сімейство custom стає доступним для вибору лише тоді, коли наявний валідний імпорт tweakcn. Імпортований payload нормалізується в специфічний для OpenClaw запис користувацької теми й зберігається в локальному сховищі браузера разом з рештою налаштувань UI.

Під час виконання OpenClaw рендерить керований тег <style>, який визначає розв’язані блоки CSS-змінних користувацької теми:

:root[data-theme="custom"] { ... }
:root[data-theme="custom-light"] { ... }

Це зберігає змінні користувацької теми в межах сімейства custom і запобігає витоку inline CSS-змінних у вбудовані сімейства.

Архітектура

Модель теми

Оновити ui/src/ui/theme.ts:

  • Розширити ThemeName, щоб включити custom.
  • Розширити ResolvedTheme, щоб включити custom і custom-light.
  • Розширити VALID_THEME_NAMES.
  • Оновити resolveTheme(), щоб custom віддзеркалював наявну поведінку сімейств:
    • custom + dark -> custom
    • custom + light -> custom-light
    • custom + system -> custom або custom-light залежно від налаштування ОС

Для custom не додаються застарілі псевдоніми.

Модель збереження

Розширити збереження UiSettings в ui/src/ui/storage.ts одним необов’язковим payload-ом користувацької теми:

  • customTheme?: ImportedCustomTheme

Рекомендована форма збереження:

type ImportedCustomTheme = {
  sourceUrl: string;
  themeId: string;
  label: string;
  importedAt: string;
  light: Record<string, string>;
  dark: Record<string, string>;
};

Примітки:

  • sourceUrl зберігає початкове введення користувача після нормалізації.
  • themeId — це ідентифікатор теми tweakcn, витягнутий з URL.
  • label — це поле name tweakcn, якщо воно наявне, інакше Custom.
  • light і dark — це вже нормалізовані мапи токенів OpenClaw, а не сирі payload-и tweakcn.
  • Імпортований payload живе поруч з іншими браузерно-локальними налаштуваннями й серіалізується в тому самому документі local-storage.
  • Якщо збережені дані користувацької теми відсутні або невалідні під час завантаження, ігнорувати payload і повертатися до theme: "claw", коли збережене сімейство було custom.

Застосування під час виконання

Додати вузький менеджер stylesheet користувацької теми в runtime Control UI, поруч із ui/src/ui/app-settings.ts і ui/src/ui/theme.ts.

Обов’язки:

  • Створювати або оновлювати один стабільний тег <style id="openclaw-custom-theme"> у document.head.
  • Виводити CSS лише тоді, коли існує валідний payload користувацької теми.
  • Очищати вміст style-тега, коли payload очищено.
  • Тримати CSS вбудованих сімейств у ui/src/styles/base.css; не вставляти імпортовані токени в stylesheet, що зберігається в репозиторії.

Цей менеджер запускається щоразу, коли налаштування завантажуються, зберігаються, імпортуються або очищаються.

Селектори світлого режиму

Реалізація має віддавати перевагу data-theme-mode="light" для світлих стилів між сімействами, а не спеціальній обробці custom-light. Якщо наявний селектор прив’язаний до data-theme="light" і має застосовуватися до кожного світлого сімейства, розширити його в межах цієї роботи.

UX імпорту

Оновити ui/src/ui/views/config.ts у розділі Appearance:

  • Додати картку теми Custom поруч із Claw, Knot і Dash.
  • Показувати картку як вимкнену, коли імпортованої користувацької теми немає.
  • Додати панель імпорту під сіткою тем із:
    • одним текстовим полем для посилання поширення tweakcn або URL /r/themes/{id}
    • однією кнопкою Import
    • одним шляхом Replace, коли користувацький payload уже існує
    • однією дією Clear, коли користувацький payload уже існує
  • Показувати мітку імпортованої теми та хост джерела, коли payload існує.
  • Якщо активна тема — custom, імпорт заміни застосовується негайно.
  • Якщо активна тема не custom, імпорт лише зберігає новий payload, доки користувач не вибере картку Custom.

Швидкий вибір теми в ui/src/ui/views/config-quick.ts також має показувати Custom лише тоді, коли payload існує.

Парсинг URL і віддалене завантаження

Браузерний шлях імпорту приймає:

  • https://tweakcn.com/themes/{id}
  • https://tweakcn.com/r/themes/{id}

Реалізація має нормалізувати обидві форми до:

  • https://tweakcn.com/r/themes/{id}

Після цього браузер напряму отримує нормалізований endpoint /r/themes/{id}.

Використати вузький валідатор schema для зовнішнього payload. Бажано використати zod, бо це недовірена зовнішня межа.

Обов’язкові віддалені поля:

  • top-level name як необов’язковий рядок
  • cssVars.theme як необов’язковий об’єкт
  • cssVars.light як об’єкт
  • cssVars.dark як об’єкт

Якщо cssVars.light або cssVars.dark відсутній, відхилити імпорт. Це навмисно: схвалена продуктова поведінка — повна підтримка режимів, а не best-effort синтез відсутньої сторони.

Мапінг токенів

Не дзеркалити змінні tweakcn сліпо. Нормалізувати обмежену підмножину в токени OpenClaw і виводити решту в helper.

Токени, що імпортуються напряму

З кожного блока режиму tweakcn:

  • background
  • foreground
  • card
  • card-foreground
  • popover
  • popover-foreground
  • primary
  • primary-foreground
  • secondary
  • secondary-foreground
  • muted
  • muted-foreground
  • accent
  • accent-foreground
  • destructive
  • destructive-foreground
  • border
  • input
  • ring
  • radius

Із спільного cssVars.theme, коли він наявний:

  • font-sans
  • font-mono

Якщо блок режиму перевизначає font-sans, font-mono або radius, перемагає локальне для режиму значення.

Токени, виведені для OpenClaw

Імпортер виводить змінні лише для OpenClaw з імпортованих базових кольорів:

  • --bg-accent
  • --bg-elevated
  • --bg-hover
  • --panel
  • --panel-strong
  • --panel-hover
  • --chrome
  • --chrome-strong
  • --text
  • --text-strong
  • --chat-text
  • --muted
  • --muted-strong
  • --accent-hover
  • --accent-muted
  • --accent-subtle
  • --accent-glow
  • --focus
  • --focus-ring
  • --focus-glow
  • --secondary
  • --secondary-foreground
  • --danger
  • --danger-muted
  • --danger-subtle

Правила виведення живуть у чистому helper, щоб їх можна було тестувати незалежно. Точні формули змішування кольорів є деталлю реалізації, але helper має задовольняти дві умови:

  • зберігати читабельний контраст, близький до задуму імпортованої теми
  • створювати стабільний output для того самого імпортованого payload

Токени, проігноровані у v1

Ці токени tweakcn навмисно ігноруються в першій версії:

  • chart-*
  • sidebar-*
  • font-serif
  • shadow-*
  • tracking-*
  • letter-spacing
  • spacing

Це утримує scope на токенах, які поточному Control UI фактично потрібні.

Шрифти

Рядки стеків шрифтів імпортуються, якщо наявні, але OpenClaw не завантажує віддалені ресурси шрифтів у v1. Якщо імпортований стек посилається на шрифти, недоступні в браузері, застосовується звичайна поведінка fallback.

Поведінка в разі помилки

Невдалі імпорти мають завершуватися закрито.

  • Невалідний формат URL: показати inline-помилку валідації, не виконувати fetch.
  • Непідтримуваний host або форма path: показати inline-помилку валідації, не виконувати fetch.
  • Мережева помилка, не-OK відповідь або malformed JSON: показати inline-помилку, залишити поточний збережений payload без змін.
  • Помилка schema або відсутні блоки light/dark: показати inline-помилку, залишити поточний збережений payload без змін.
  • Дія очищення:
    • видаляє збережений користувацький payload
    • видаляє вміст керованого style-тега користувацької теми
    • якщо custom активний, перемикає сімейство тем назад на claw
  • Невалідний збережений payload користувацької теми під час першого завантаження:
    • ігнорувати збережений payload
    • не виводити користувацький CSS
    • якщо збережене сімейство тем було custom, повернутися до claw

У жоден момент невдалий імпорт не має залишати активний документ із частково застосованими користувацькими CSS-змінними.

Файли, які очікувано зміняться під час реалізації

Основні файли:

  • ui/src/ui/theme.ts
  • ui/src/ui/storage.ts
  • ui/src/ui/app-settings.ts
  • ui/src/ui/views/config.ts
  • ui/src/ui/views/config-quick.ts
  • ui/src/styles/base.css

Ймовірні нові helper-и:

  • ui/src/ui/custom-theme.ts

Тести:

  • ui/src/ui/app-settings.test.ts
  • ui/src/ui/storage.node.test.ts
  • ui/src/ui/views/config.browser.test.ts
  • нові сфокусовані тести для парсингу URL і нормалізації payload

Тестування

Мінімальне покриття реалізації:

  • розпарсити URL посилання поширення в ідентифікатор теми tweakcn
  • нормалізувати /themes/{id} і /r/themes/{id} у fetch URL
  • відхиляти непідтримувані host-и та malformed id
  • валідувати форму payload tweakcn
  • мапити валідний payload tweakcn у нормалізовані мапи світлих і темних токенів OpenClaw
  • завантажувати й зберігати користувацький payload у браузерно-локальних налаштуваннях
  • розв’язувати custom для light, dark і system
  • вимикати вибір Custom, коли payload відсутній
  • застосовувати імпортовану тему негайно, коли custom уже активний
  • повертатися до claw, коли активну користувацьку тему очищено

Ціль ручної перевірки:

  • імпортувати відому тему tweakcn із Settings
  • перемикатися між light, dark і system
  • перемикатися між custom і вбудованими сімействами
  • перезавантажити сторінку й підтвердити, що імпортована користувацька тема зберігається локально

Нотатки щодо rollout

Ця функція навмисно мала. Якщо користувачі пізніше попросять кілька імпортованих тем, перейменування, експорт або синхронізацію між пристроями, розглядати це як подальший дизайн. Не будувати заздалегідь абстракцію бібліотеки тем у цій реалізації.