feat(theming): in-UI theme editor (2.10.0)
Recolour the whole UI from Settings — 12 palette colour pickers with live preview, presets (Ember/Frost/Verdant/Amethyst), and reset to the default Blackflame. Overrides persist in app_settings (key 'theme') via a hex-validated /api/theme route and apply to :root on boot. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
21
lib/api/routes/theme.js
Normal file
21
lib/api/routes/theme.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Router } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { asyncWrap } from '../errors.js';
|
||||
import { requireOwner } from '../cap.js';
|
||||
import { validate } from '../validate.js';
|
||||
import * as settings from '../../db/repos/app_settings.js';
|
||||
export const router = Router();
|
||||
|
||||
// Theme = a small map of palette-var overrides, e.g. { accent: '#ff4f2e' }.
|
||||
// Keys are short slugs (mapped to --<key> on the client); values must be hex,
|
||||
// so a saved theme can never inject arbitrary CSS.
|
||||
const themeSchema = z.record(
|
||||
z.string().regex(/^[a-z0-9-]{1,24}$/),
|
||||
z.string().regex(/^#[0-9a-fA-F]{3,8}$/)
|
||||
);
|
||||
|
||||
router.get('/', asyncWrap(async (_req, res) => res.json(await settings.get('theme', {}))));
|
||||
|
||||
router.put('/', requireOwner, validate({ body: themeSchema }), asyncWrap(async (req, res) => {
|
||||
res.json(await settings.set('theme', req.body));
|
||||
}));
|
||||
Reference in New Issue
Block a user