Commit b4c7e06
authored
feat(style): cascading theme system and demo (#110)
* feat(style): add cascading style system with CSS-like selectors
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.
* feat(layout): integrate style context into Flex_layout and Grid_layout
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.
* feat(widgets): add semantic themed styling functions
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.
* refactor(widgets): migrate simple widgets to themed styling
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.
* refactor(widgets): migrate medium widgets to themed styling
- 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.
* refactor(widgets): migrate complex widgets to themed styling
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.
* feat(demo): add style system demo
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.
* fix(demo): make style system demo respond to theme changes
- 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
* chore(demo): surface theme parse status in style demo
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.
* fix(style): allow partial JSON style objects
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.
* fix(style): accept multiple JSON formats for colors
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.
* fix(style): make Style.t JSON parsing tolerant
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.
* fix(style): allow partial widget style JSON objects
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.
* fix(style): accept string JSON values for border styles
Allow Border.style to parse from simple string values like Rounded in
JSON theme files, fixing Border.style parse errors in demo themes.
* fix(widgets): fill contextual background across container width
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.
* chore(demo): distinguish selection from background
Adjust demo theme selection colors so selection stands out from contextual
background fills and focus styles.
* feat(style): add optional contrast validation for themes
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.
* feat(box): support borderless boxes for container backgrounds
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.
* fix(demo): restore flex-child selector context
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.
* docs(changelog): add 0.4.0 style system notes
Document the new cascading style system, theme JSON support, widget theming
changes, and the Box_widget None_ border style breaking change.
* feat(demo): add editable theme.json for style system
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.
* fix(demo): fall back to embedded theme with error notice
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.
* chore(release): bump version to 0.4.0
* feat(gallery): add style system demo
* fix(test): adapt ascii_style test for themed border rendering
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.1 parent c9ec473 commit b4c7e06
File tree
62 files changed
+3189
-259
lines changed- example
- demos/style_system
- themes
- gallery
- themes
- src
- miaou_core
- miaou_internals
- miaou_style
- miaou_widgets_display
- miaou_widgets_input
- miaou_widgets_layout
- test
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
62 files changed
+3189
-259
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
212 | 212 | | |
213 | 213 | | |
214 | 214 | | |
215 | | - | |
| 215 | + | |
216 | 216 | | |
217 | 217 | | |
218 | 218 | | |
219 | | - | |
| 219 | + | |
| 220 | + | |
220 | 221 | | |
221 | 222 | | |
222 | 223 | | |
| |||
228 | 229 | | |
229 | 230 | | |
230 | 231 | | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
231 | 340 | | |
232 | 341 | | |
233 | 342 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
8 | 28 | | |
9 | 29 | | |
10 | 30 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
15 | | - | |
16 | | - | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
28 | 29 | | |
29 | 30 | | |
30 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
0 commit comments