UI: user customisation — font family (Atkinson Hyperlegible), font size, custom CSS #8

Closed
opened 2026-09-10 03:43:34 +00:00 by agent-pi · 2 comments
Collaborator

Problem

The UI's typography is fixed: one system font stack, one base size. I'd like to read in Atkinson Hyperlegible at a size I choose — and, more generally, to restyle the reader to my taste without forking the stylesheet.

Goal

User-controlled UI customisation:

  1. Choose the UI font family (e.g. Atkinson Hyperlegible) and base font size from the settings page.
  2. Optionally supply custom CSS (or CSS fragments) that yunjin applies on top of the built-in stylesheet, for anything beyond font/size.
## Problem The UI's typography is fixed: one system font stack, one base size. I'd like to read in Atkinson Hyperlegible at a size I choose — and, more generally, to restyle the reader to my taste without forking the stylesheet. ## Goal User-controlled UI customisation: 1. Choose the UI font family (e.g. Atkinson Hyperlegible) and base font size from the settings page. 2. Optionally supply custom CSS (or CSS fragments) that yunjin applies on top of the built-in stylesheet, for anything beyond font/size.
Author
Collaborator

Findings

  • Typography is already variable-driven: :root defines --font-family (system stack) and a --font-size-* scale, so a custom font and base size are two variable overrides.
  • One hardcoded font-size: 16px (body) sits outside the variable scheme — needs sweeping into it as part of this change.
  • The settings page currently manages only the tag ignore list (tags DB tables); there is no general key/value settings store — one will need to be added (DB table + alembic migration).
  • base.html loads exactly one stylesheet, so injecting a user-CSS block after it (inline <style> or a /settings/custom.css route serving the stored CSS) wins the cascade by default.
  • Atkinson Hyperlegible is published by the Braille Institute; its license is believed to be SIL OFL (verify before bundling) — if OFL, the woff2 files can be shipped in static/ so the font works without any external CDN (important: the reader may run offline/LAN-only). The custom-CSS route (@font-face pointing at user-hosted files) remains as an escape hatch either way.

Plan of attack

  1. New settings key/value table + alembic migration; db/settings.py accessors.
  2. "Display" section on the settings page: font-family text field (default: current system stack), base font size (px or rem), free-form custom-CSS textarea.
  3. base.html: after style.css, emit the overrides — --font-family / --font-size-base variables set from settings, then the user's custom CSS verbatim.
  4. If the OFL check passes: bundle Atkinson Hyperlegible woff2 in static/fonts/ with @font-face in the built-in stylesheet, so selecting it needs no network.
  5. Server-side tests: settings CRUD, template renders overrides when set, sanitisation decision documented (custom CSS is trusted-user-only — single-user app, no sanitisation beyond storage as plain text).
  6. Keep the palette variables overridable the same way — this naturally composes with the dark-mode work (#6).
## Findings - Typography is already variable-driven: `:root` defines `--font-family` (system stack) and a `--font-size-*` scale, so a custom font and base size are two variable overrides. - One hardcoded `font-size: 16px` (body) sits outside the variable scheme — needs sweeping into it as part of this change. - The settings page currently manages only the tag ignore list (tags DB tables); there is **no general key/value settings store** — one will need to be added (DB table + alembic migration). - `base.html` loads exactly one stylesheet, so injecting a user-CSS block after it (inline `<style>` or a `/settings/custom.css` route serving the stored CSS) wins the cascade by default. - Atkinson Hyperlegible is published by the Braille Institute; its license is believed to be SIL OFL (verify before bundling) — if OFL, the woff2 files can be shipped in `static/` so the font works without any external CDN (important: the reader may run offline/LAN-only). The custom-CSS route (`@font-face` pointing at user-hosted files) remains as an escape hatch either way. ## Plan of attack 1. New `settings` key/value table + alembic migration; `db/settings.py` accessors. 2. "Display" section on the settings page: font-family text field (default: current system stack), base font size (px or rem), free-form custom-CSS textarea. 3. `base.html`: after `style.css`, emit the overrides — `--font-family` / `--font-size-base` variables set from settings, then the user's custom CSS verbatim. 4. If the OFL check passes: bundle Atkinson Hyperlegible woff2 in `static/fonts/` with `@font-face` in the built-in stylesheet, so selecting it needs no network. 5. Server-side tests: settings CRUD, template renders overrides when set, sanitisation decision documented (custom CSS is trusted-user-only — single-user app, no sanitisation beyond storage as plain text). 6. Keep the palette variables overridable the same way — this naturally composes with the dark-mode work (#6).
Author
Collaborator

Resolved by PR #27 (merged): per-user settings store (alembic migration + db/settings.py), user-settable theme with system/light/dark override, Atkinson Hyperlegible bundled as woff2 with its OFL license, validated base font size, and custom CSS with style-breakout guard. Also fixed during review: web app now self-migrates on startup and seeds the default user (both found as real-deployment crashes), plus a generated 0600 secret key file.

Resolved by PR #27 (merged): per-user settings store (alembic migration + db/settings.py), user-settable theme with system/light/dark override, Atkinson Hyperlegible bundled as woff2 with its OFL license, validated base font size, and custom CSS with style-breakout guard. Also fixed during review: web app now self-migrates on startup and seeds the default user (both found as real-deployment crashes), plus a generated 0600 secret key file.
Sign in to join this conversation.
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
marvin8/yunjin#8
No description provided.