Skip to content

Comments

feat(style): cascading theme system and demo#110

Merged
mathiasbourgoin merged 25 commits intomainfrom
feat/style-system
Feb 18, 2026
Merged

feat(style): cascading theme system and demo#110
mathiasbourgoin merged 25 commits intomainfrom
feat/style-system

Conversation

@mathiasbourgoin
Copy link
Collaborator

Summary

  • add miaou_style library with cascading semantic styles, CSS-like selectors, and effect-based context
  • migrate widgets/layouts to themed rendering, including markdown styling and container background fill
  • add style-system demo with editable theme.json and validation warnings
  • improve theme JSON parsing (tolerant styles, flexible color/border formats) and add theme validation
  • add borderless Box_widget mode and bump version to 0.4.0

Testing

  • dune build
  • dune runtest

Introduce a new miaou_style library providing:
- Style.t: core style type with fg/bg colors, text attributes, adaptive colors
- Border: border style variants (Single, Double, Rounded, Ascii, Heavy)
- Selector: CSS-like selector parser with pseudo-classes (:focus, :selected,
  :nth-child(even/odd/n)) and combinators (> for child, space for descendant)
- Theme.t: semantic tokens (primary, error, text, etc.) + CSS-like rules
- Theme_loader: file discovery, JSON parsing, theme merging
- Style_context: effect-based implicit theme access

Theme file discovery order (later overrides earlier):
1. Built-in default theme
2. ~/.config/miaou/theme.json (user global)
3. .miaou/theme.json (project local)
4. $MIAOU_THEME env var

Also includes example theme files (dark.json, light.json, high-contrast.json)
and unit tests for all modules.
Update layout widgets to set up style context for child widgets when rendering:
- Pass widget_name, index, and count to Style_context.with_child_context
- This enables CSS-like selectors such as :nth-child(even) to work for
  alternating row styles in flex/grid layouts

Child widgets can now access their position via Style_context.current_context()
to apply position-based styling rules from the theme.
Add themed_* functions to Widgets module that use Style_context to access
the current theme:

- themed_primary, themed_secondary, themed_accent
- themed_error, themed_warning, themed_success, themed_info
- themed_text, themed_muted, themed_emphasis
- themed_border, themed_selection
- themed_background, themed_background_alt
- themed_contextual (for CSS-like selector-based styling)
- current_widget_style() for accessing full style context

Update AGENTS.md with comprehensive styling guidelines:
- Document two-layer approach (semantic + contextual)
- List all available themed functions with use cases
- Explain when raw fg/bg is acceptable (gradients, charts)
- Show how to set up style context for child widgets

Widget authors should use these functions instead of hardcoded fg/bg colors
to ensure consistent, themeable rendering across all widgets.
Update widgets to use semantic themed functions instead of hardcoded colors:

- button_widget: Use themed_selection for focus state, themed_muted for disabled
- card_widget: Use themed_emphasis for title (when no accent override)
  - Mark accent field as deprecated, encourage semantic styling
- line_chart_widget: Use themed_emphasis for chart titles
- sparkline_widget: Use themed_emphasis for focus state
- bar_chart_widget: Use themed_emphasis for chart titles

Chart widgets continue to accept colors from their data model (series colors,
threshold colors) as this is appropriate for data visualization where colors
convey data meaning rather than UI semantics.
- select_widget: use themed_selection for highlighted items
- textarea_widget: use themed_border for box lines and themed_muted for
  placeholder/indicator text
- toast_widget: map severities to themed_info/success/warning/error
- progress_widget: use themed_muted for percentage and themed_secondary
  for labels (retain gradient fill)

Spinner and progress gradients remain raw-color by design for animated/visual
feedback; these are accepted uses of raw colors per styling guidelines.
Update complex widgets and markdown rendering to use semantic themed styles:

- pager_widget: replace hardcoded ANSI colors in help modal, status line,
  search prompt, and cursor highlight with themed_* functions
- table_widget: use themed borders, emphasis, selection, accent, and background
  styles across both terminal and SDL renderers
- file_browser_widget: apply themed selection, accent, muted, success, warning,
  and error styles for path bar, entries, and status messages
- box_widget: default border styling now uses themed_border; title uses
  themed_emphasis while preserving legacy per-side color overrides
- modal_utils: markdown_to_ansi now uses themed semantic styles for code,
  headings, links, quotes, and list markers
- tui_page.mli: document styling requirements for PAGE_SIG view functions

Raw color codes remain only where gradients or data-driven chart colors are
intended, aligning with the new styling guidelines.
Add a new demo showing the cascading style system with runtime theme
switching and contextual styling via flex-child selectors. The demo
includes three embedded themes (dark, light, high-contrast) and a simple
flex layout of tiles to highlight semantic tokens and contextual rules.
- Ensure embedded themes provide all required fields so JSON parsing succeeds
- Preserve flex-child index/count when adding focus context so nth-child rules apply
Show whether embedded themes parsed successfully so it is obvious when the
style context falls back to the default theme. This helps diagnose why visual
changes might not appear when switching themes.
Make Style.t fields optional in JSON parsing so theme files can specify
only the attributes they care about. This fixes demo theme parsing errors
and aligns with the intended cascading behavior.
Allow color values in theme JSON to be provided as:
- {Fixed: 75}
- [Fixed, 75]
- 75 (defaults to Fixed)
- {Adaptive: {light: 15, dark: 231}}
- [Adaptive, {light: 15, dark: 231}]

This preserves backward compatibility and fixes theme parsing failures in the
demo and bundled theme files.
Replace derived Style.t JSON parsing with a custom decoder that tolerates
missing fields and ignores nulls. This prevents theme parsing failures for
partial style objects and supports the intended cascading behavior.

Also expose to_yojson/of_yojson aliases for compatibility with existing
Theme JSON serialization.
Provide a tolerant widget_style JSON parser so theme rules can omit
optional fields like border_style/border_fg/border_bg without failing.
This resolves Border.style parse errors when loading demo themes.
Allow Border.style to parse from simple string values like Rounded in
JSON theme files, fixing Border.style parse errors in demo themes.
Apply contextual background to padded lines in Box_widget and render_frame,
reapplying background across ANSI resets so inline styling doesn't punch
holes. This makes container backgrounds fill the full content width.
Adjust demo theme selection colors so selection stands out from contextual
background fills and focus styles.
Introduce Theme.validate to detect low-contrast fg/bg pairs in semantic
styles and CSS-like rule styles. The style-system demo now surfaces the
first warning (if any) to help authors avoid unreadable combinations.
Add a None_ border style to Box_widget so containers can render without
borders. The style-system demo now uses borderless tiles with padded
content, matching the OpenCode-style layout with contrasted backgrounds.
Ensure the style-system demo preserves the flex-child widget name when
adding focus/selection context so nth-child rules apply and tiles alternate
backgrounds.
Document the new cascading style system, theme JSON support, widget theming
changes, and the Box_widget None_ border style breaking change.
Ship example/demos/style_system/theme.json as an OpenCode-like flex theme
and load it as the dark theme when present, so users can iterate on CSS-like
rules without recompiling.
When example/demos/style_system/theme.json fails to load, the demo now
falls back to the embedded theme JSON and surfaces a warning so users
know the file-based theme was ignored.
@mathiasbourgoin mathiasbourgoin changed the base branch from feat/new-widgets to main February 18, 2026 15:14
Box_widget now wraps border chars in ANSI codes via themed_border,
so first.[0] is no longer '+'. Check for '+' using Str.search_forward
to match the pattern used by other tests in the file.
@mathiasbourgoin mathiasbourgoin merged commit b4c7e06 into main Feb 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant