PR #612 added the three Catppuccin flavors but grouped all of them under ThemeCategory::Dark. Latte is the LIGHT flavor (its palette already builds light via bg_is_light); move it to the Light category arm so the theme picker groups it correctly. Frappé/Macchiato stay Dark.
cc
Adds the remaining three Catppuccin flavor palettes to complete the set:
- Latte (light)
- Frappé (dark)
- Macchiato (dark)
All colors transcribed from the official Catppuccin palette.
Themes follow the existing StdSpec builder pattern and are fully
integrated into the theme registry and UI.
Add crates/qbz-theme/src/custom.rs: the extended port of the Tauri "edit
theme colors" feature. A CustomThemeBase of 12 hand-picked semantic tokens
(surfaces, text, accent, status hues, border, favorite + is_dark polarity),
stored as opaque #rrggbb hex strings, derives the full ThemeColors contract.
Unlike Tauri (8 raw vars, no derivation, over the auto-theme only), editing a
base token now cascades: theme_from_base derives the accent triplet
(+0.10/-0.10 lightness, contrast-picked accent_text), the danger/warning/
success families (0.1/0.3 tints, polarity hover), muted/disabled text tiers,
border_strong, the translucent edges and the polarity alpha ramp.
Reuses the existing math instead of duplicating it: generator::{tint,
pick_btn_text_for_accent_set, ensure_text_contrast_target} are now pub(crate);
PaletteColor::shift_lightness and alpha_ramp are shared as-is. base_from_theme
is the inverse seed ("Start from current theme") and round-trips exactly for
any theme this module authored. Adds Rgba::to_hex. 9 unit tests (roundtrip,
polarity, determinism, accent-text legibility, malformed-hex fallback, JSON).
cc
Review follow-ups: extract_palette decoded the full image before the
100x100 downsample with no limits — a decompression-bomb PNG expands
to tens of GB, and this box hard-freezes on OOM; decode is now bounded
at 12k x 12k / 512 MB (any real wallpaper fits). The generating
indicator is now set through upgrade_in_event_loop so the select_image
path (which runs on a tokio thread, where a plain upgrade() returns
None) shows it too. Startup generation stays synchronous by design:
with the decode bounded it costs milliseconds and the first paint
comes up in the generated palette instead of flashing the default.
cc
Port the legacy Tauri src-tauri/src/auto_theme/{palette,system,generator}
logic into a new frontend-agnostic qbz-theme::auto module. The palette
extraction (k-means), WCAG contrast math, HSL shifts and DE detection are
carried over 1:1; only the final assembly changes: the output is now the
registry ThemeColors contract instead of a CSS custom-property map.
The generator mirrors registry::StdSpec::build so a generated theme
composites identically to a static one — same polarity-driven alpha ramp,
success/focus_ring/favorite/border_muted derivations, and status tints.
Per-status text tokens (--btn-danger-text/--btn-warning-text) are dropped
since ThemeColors has no field for them.
Adds `image` (0.25, minimal codec features) to qbz-theme and unit tests for
the mapping (polarity, derived families, determinism, scheme path).
cc
P4 foundation for the redundant-encoding accessibility pass. Adds a single
flag that High-Contrast-only affordances (1px control borders, slider-thumb
borders) read so they never leak into the polished normal themes, mirroring
the existing is-system wiring.
- qbz-theme: `pub fn is_high_contrast(id)` at the crate root next to
`is_light` (true for HighContrast + HighContrastLight), with a unit test
`high_contrast_flag_only_for_hc_themes` asserting exactly the two HC themes.
- qbz-slint theme.slint: `in-out property <bool> is-high-contrast: false;`
beside is-system; re-exported via semantic-colors.slint so all ~145
components get `Theme.is-high-contrast` with no per-file import change.
Embedded the canonical HC border gate and the non-layout focus-ring
snippet as a comment for the cluster agents to copy verbatim.
- qbz-slint theme.rs: apply_theme sets it via
`qbz_theme::is_high_contrast(id)`.
cc
Materialize the final verified Part B palettes for wcag-light, wcag-dark,
high-contrast (dark), high-contrast-light, and colorblind. Each is a fully
populated ThemeColors row with solid (opaque) status surfaces/borders (not
alpha tints) so accessible contrast is guaranteed independent of backdrop.
Includes the success + focus-ring families per token; high-contrast-light
applies the adversarial warning correction (#735c00 -> #5e4b00, AA-only ->
AAA on white). colorblind uses the exact Okabe-Ito values with body-size
success routed to success-hover on the lightest tier (C.6).
Mark accessibility themes as implemented and add WCAG/APCA + CVD unit tests:
- wcag-* assert AAA (>=7:1) text/accent/status + APCA body |Lc|>=75
- high-contrast/-light assert >= the wcag-dark/-light text-bg ratio and
>=3:1 non-text for border-strong/focus-ring/border-subtle
- colorblind asserts AA on all tiers + danger/warning/accent stay distinct
under a Machado deuteranopia simulation (delta-E separation)
- all-rows test asserts every registered theme is fully populated (no zero
color), exactly 5 accessibility themes, 33 total ThemeId rows
cc
P2 of the theme migration. Materialize every non-accessibility theme
from src/app.css (per 01-tauri-themes-inventory.md): the core Light row
plus the 23 branded/community themes (warm, nord, dracula,
catppuccin-mocha, breeze-dark, adwaita-dark, alucard, aurora, ikari,
ayanami, iscariot, stratego, rumi, zoey, mira, rose-pine-dawn,
breeze-light, adwaita-light, duotone-snow, snow-storm, frost, langley,
kurosaki).
- No CSS cascade on the Slint side: every row is fully materialized.
Omitted tokens (light's accent trio; breeze-dark/adwaita-dark's
danger/warning families) are resolved against :root Dark at
transcription time.
- Alpha polarity is luminance-derived, not the Tauri type flag: frost
(#2e3440) and langley (#2c0a0a) are registered light but are dark
canvases -> white alpha base; alucard (#fffbeb) is genuinely light ->
black base. surface_hover/border_muted bases flip with polarity too.
- Status families (danger/warning) are baked as straight-alpha tints of
the solid hue (0.1/0.3/0.2; dracula 0.15/0.4/0.25; light hover 0.15),
matching Tauri's rgba() derivation.
- Derived (no Tauri parity) per-theme: success (project green #3fae6a on
dark, #1f8a4c on light), focus_ring (= accent), favorite (= danger).
These are placeholders to be polished in P4.
- is_implemented() now reports all standard themes ready; the 5
accessibility themes remain gated for P3.
Adds a full-population smoke test over every registered theme plus
per-theme hero-value, polarity and non-standard-tint assertions.
cargo test -p qbz-theme: 33 passed.
cc
Introduce the `qbz-theme` crate (ADR-006): pure Rust, no Slint/Tauri/heavy
deps, hand-rolled color + contrast math. It holds the scalable theming data
model the Slint migration consumes.
- `Rgba` + WCAG relative-luminance/contrast-ratio + an APCA Lc approximation
(secondary a11y gate for P3 tests).
- `ThemeColors`: the complete token contract (surfaces incl. bg-hover, text,
accent, danger/warning/success families, border-subtle/muted/strong,
focus-ring, favorite, card-shadow, 24-tier alpha superset with polarity
baked in).
- `ThemeId` enum (all 33 themes) with stable slugs, display names (proper-noun
data, not i18n keys), Core/Dark/Light/Accessibility categories, and a
luminance-derived light/dark flag. `default_theme_id()` = OLED.
- Registry materializes the 4 P1 themes (Dark/OLED/Tokyo Night/System-fallback)
from src/app.css, preserving the legacy translucent alias values so the
existing themes stay pixel-identical; derives the new token families.
- 22 unit tests (registry fully populated, default is OLED, legacy alpha
aliases unchanged, slug roundtrip).
cc