Files
TimeTracker/docs/features
Dries Peeters a72b5476cc feat: add per-user custom theme system
Adds a per-user theme picker under Settings → Custom theme. Users can
pick one of eight built-in themes (default, ocean, forest, sunset,
lavender, rose, slate, high-contrast) and optionally override accent
colour, sidebar style, text size and corner radius. Preferences are
persisted on the users table and applied on every page load through a
single <style id="tt-theme-vars"> block injected into base.html.

The picker (app/templates/components/theme_picker.html) is self-
contained vanilla JS: clicks debounce a POST /api/user/theme that
returns the regenerated CSS block, so changes preview instantly with
no page reload, and only persist when Save is clicked.

The default theme generates an empty CSS block, so existing users see
zero visual change until they opt in. ThemeService reads every user
attribute through getattr() and falls back to defaults on exception,
so an unmigrated database never breaks rendering. All values that end
up in the generated CSS are validated against the strict #RRGGBB regex
or explicit allow-lists before being embedded.

- Alembic revision 156_add_user_theme_columns adds five nullable
  columns to the users table (defensively — each column is only added
  if missing): theme_name, theme_accent_color, theme_sidebar_style,
  theme_font_size, theme_border_radius.
- ThemeService (app/services/theme_service.py): BUILT_IN_THEMES dict,
  ACCENT_PRESETS palette, get_theme_css_vars / get_all_themes /
  validate_accent_color / save_user_theme.
- New API: GET / POST /api/user/theme (both @login_required).
- New context processor inject_theme exposes theme_css on every
  request; base.html renders it right after the existing CSS link
  tags.
- Documented in docs/features/CUSTOM_THEMES.md; CHANGELOG and README
  updated to reflect the feature.
2026-05-15 10:42:48 +02:00
..