nigig-org/crates/apps/theming/CHANGES.md
2026-07-26 19:38:26 +03:00

6.2 KiB

Changes & Rationale

This document explains how the codebase was improved and why. Each item notes the original behavior, the change, and the reasoning.

1. Three-tier design-token architecture

Before: Tokens were split across streem_theme/colors.rs, streem_theme/shapes.rs, streem_theme/typography.rs (writing into mod.widgets.*), while the actual themes lived in streem_theme/mod.rs (mod.streem_theme.*). The token files were also not declared as Rust modules anywhere, so they were effectively dead code that never ran.

After: A clean 3-tier system:

  1. streem_design/{palette,shapes,typography}.rs — tier-1 primitives.
  2. streem_theme/mod.rs — tier-2 semantic themes that bind roles to primitives.
  3. Widgets — read only the active theme (s_themes) / language (s_i18n).

Why: This is the standard, scalable token model. Changing one palette entry re-brands everything; widgets never reference raw values.

Registration note: the tier-1 modules use script_eval! (runtime mutation of the mod object), not the script_mod! macro form. The script_mod! form returns a value to be applied to a component and does not eagerly install a fresh top-level namespace like mod.streem_design.*, which is exactly why the original colors.rs/shapes.rs/typography.rs (written with script_mod! but never wired up) produced "streem_design not found" errors. script_eval! installs the namespace immediately, matching the working streem_theme / streem_i18n pattern. Inside script_eval! there is no prelude use, so fonts are referenced by their fully-qualified path (mod.theme.font_bold) rather than the bare theme.font_bold alias.

2. App-owned namespace (mod.streem_design.*)

Before: Tokens were written into mod.widgets.*.

After: All app tokens live under mod.streem_design.*.

Why: mod.widgets.* is the framework's widget namespace; writing app tokens there is confusing and risks collisions. An app-owned namespace makes ownership explicit.

3. Themes reference palette primitives (single source of truth)

Before: streem_theme/mod.rs inlined hex literals (color_bg: #FFF7ED, color_primary: #D97706, …), duplicating values and divorcing themes from any shared palette.

After: Each theme role points at a palette token (color_primary: mod.streem_design.palette.amber_600).

Why: One palette edit now propagates to every theme that uses it; the palette becomes the single source of truth.

4. Fixed the unused color_text role

Before: Both themes defined color_text, but the UI never used it — body text relied on widget defaults (white), which is invisible on light surfaces.

After: Semantic roles (color_text, color_text_secondary, color_text_on_primary) are explicitly applied to the title, subtitle, status, and section labels.

Why: Per the Splash manual, default text color is white; light themes must set dark text explicitly or it disappears (white-on-white).

5. Live, data-driven status label

Before: The DSL hard-coded text: "Current: light | reapply: 0", a string that could drift out of sync with actual state.

After: The label starts neutral and is always written from Rust state via refresh_status (Current: {theme} / {lang} | reapply: {n}), called on startup, on every toggle, and on live-edit.

Why: A single source of truth prevents the displayed status from lying.

6. Generic theme cycling (no rigid 2-state enum)

Before: A ThemeState { Light, Dark } enum with toggled() hard-coded the two-theme assumption.

After: The host tracks theme_idx / lang_idx into ordered name lists (THEMES, LANGS) that mirror theme_names / lang_names in script. Toggling advances the index modulo the list length.

Why: Adding a third theme (or third language) now requires no host changes to the cycling logic — just extend the lists.

7. Runtime language switching

Before: i18n existed but was never wired to the UI, and it defaulted to Swahili (s_i18n = sw), which looked like a leftover.

After: A Language button cycles en/sw; the active language is selected alongside the theme in the single reapply reload path. Default is now English.

Why: Demonstrates that the same swap-and-reload mechanism localizes the UI, not just re-colors it.

8. Single reload path

Before: apply_theme and reload_ui_from_rust_state overlapped and both re-applied the theme; the flow was easy to get out of sync.

After: One reapply(cx) selects the active theme+language in the VM, reloads the script tree, refreshes the status label, and redraws. handle_* methods call it once when something changes.

Why: Less duplication, clearer control flow, no double application.

9. Hardened Event::LiveEdit

Before: On LiveEdit, the app called apply_theme (which re-evaluated the theme but could leave i18n/state inconsistent after a hot reload).

After: On LiveEdit, the app re-selects the active theme and language in the VM and refreshes the status, so a hot reload preserves the user's current selection instead of reverting to script defaults.

Why: Hot-reloading code shouldn't silently reset the user's theme/language.

10. Expanded, polished UI consuming the tokens

Before: A minimal toggle + two markup cards with magic-number padding and inline font_size values.

After: A header (title/subtitle), a control bar (themed buttons + live status), and two bordered surface cards — all driven by the spacing scale, typography styles, semantic colors, and new_batch: true on text-bearing backgrounds (per the Splash batching guidance, so text never hides behind its own background).

Why: Shows the design system actually being consumed end-to-end and follows the documented layout/batching best practices.

11. Better tests

Before: One test that checked the enum toggles back and forth.

After: Tests cover full theme/language cycling and verify the modulo guards never index out of range.

12. Build ergonomics

  • Cargo.toml documents the expected sibling-of-Makepad layout for the path dependency and provides a commented, branch-pinned git fallback.
  • Added a markup feature (default on) for the Markdown/Html widgets.
  • Added .gitignore for target/.