| 20 |
20 |
|
|
| 21 |
21 |
|
1. **No `Color32::from_rgb(...)` outside `theme.rs`.** Test by `grep -rE 'Color32::(from_rgb|WHITE|BLACK|GRAY|RED|GREEN|BLUE|YELLOW|DARK_GRAY)' src/ui/ | grep -v theme.rs`, must be empty.
|
| 22 |
22 |
|
2. **No raw `add_space(N.0)` outside `widgets.rs` and `theme.rs`**: use named spacing tokens (§Spacing).
|
| 23 |
|
- |
3. **No inline `selectable_label(active, RichText::new(label).strong().color(accent_blue()))`**: use `widgets::selectable_row` or one of its specializations.
|
|
23 |
+ |
3. **No inline `selectable_label(active, RichText::new(label).strong().color(action()))`**: use `widgets::selectable_row` or one of its specializations.
|
| 24 |
24 |
|
4. **No inline `Window::new(...).collapsible(false).resizable(false).anchor(CENTER_CENTER, [0,0])`**: use `widgets::modal_window` or `widgets::confirm_modal`.
|
| 25 |
25 |
|
5. **No emoji or checkmark glyphs in user-facing strings** (CLAUDE.md brand rule). Use words. Test by `grep -rE '\\u\{1F[0-9A-Fa-f]{3}\}|\\u\{2[0-9A-Fa-f]{3}\}'` against UI files: flagged glyphs require a documented exception comment.
|
| 26 |
26 |
|
|
| 30 |
30 |
|
|
| 31 |
31 |
|
### Colour tokens
|
| 32 |
32 |
|
|
| 33 |
|
- |
All UI colour reads must go through these accessors. The 15 base slots come from `ThemeColors`; the derived slots are computed at runtime.
|
|
33 |
+ |
All UI colour reads must go through these accessors. Every name is an intent, not
|
|
34 |
+ |
an appearance: `action` says what the colour is for, `accent_blue` said what it
|
|
35 |
+ |
looked like on one theme and lied on the next. The 14 base slots come from
|
|
36 |
+ |
`ThemeColors`, parsed from the theme TOML. Derived slots are blended once per
|
|
37 |
+ |
theme install (`DerivedColors`) rather than per read.
|
| 34 |
38 |
|
|
| 35 |
|
- |
| Token | Kind | Use |
|
| 36 |
|
- |
|--------------------|----------|-----------------------------------------------------------------------|
|
| 37 |
|
- |
| `bg_primary` | base | Deepest layer (table body, scroll background). |
|
| 38 |
|
- |
| `bg_secondary` | base | Panels, sidebars, window fill. |
|
| 39 |
|
- |
| `bg_tertiary` | base | Hover background, highlighted regions. |
|
| 40 |
|
- |
| `bg_surface` | base | Cards, popovers, tag chips. |
|
| 41 |
|
- |
| `fg_primary` | base | Primary text. |
|
| 42 |
|
- |
| `fg_secondary` | base | Secondary text, labels. |
|
| 43 |
|
- |
| `fg_muted` | base | Placeholders, disabled items, empty-state hints. |
|
| 44 |
|
- |
| `accent_blue` | base | Selection/active state. *The* affordance colour. |
|
| 45 |
|
- |
| `accent_red` | base | Errors, danger buttons, destructive confirms. |
|
| 46 |
|
- |
| `accent_green` | base | Success, ready state. |
|
| 47 |
|
- |
| `accent_yellow` | base | Warnings (not yet used as such, reserved). |
|
| 48 |
|
- |
| `accent_purple` | base | Reserved (classification palette). |
|
| 49 |
|
- |
| `accent_cyan` | base | Reserved (classification palette). |
|
| 50 |
|
- |
| `border_default` | base | Separator, panel boundary. |
|
| 51 |
|
- |
| `bg_row_even` | derived | Striped row alternation (even). |
|
| 52 |
|
- |
| `bg_row_odd` | derived | Striped row alternation (odd) = `bg_primary`. |
|
| 53 |
|
- |
| `bg_hover` | derived | = `bg_tertiary`. Shared row/button hover. |
|
| 54 |
|
- |
| `bg_selected` | derived | Selection fill (= `lerp(bg_primary, accent_blue, 0.3)`). |
|
| 55 |
|
- |
| `classification_color(c)` | domain | Sample-class palette. Stable across themes for muscle memory. |
|
| 56 |
|
- |
| `piano_white_key()`, `piano_black_key()` | domain | Instrument panel only. |
|
|
39 |
+ |
| Token | Kind | Use |
|
|
40 |
+ |
|-----------------------|----------|--------------------------------------------------------------------|
|
|
41 |
+ |
| `surface_page()` | base | Deepest layer (table body, scroll background). |
|
|
42 |
+ |
| `surface_overlay()` | base | Panels, sidebars, window fill. |
|
|
43 |
+ |
| `surface_sunken()` | base | Hover background, highlighted and recessed regions. |
|
|
44 |
+ |
| `surface_raised()` | base | Cards, popovers, tag chips. |
|
|
45 |
+ |
| `content()` | base | Primary text. |
|
|
46 |
+ |
| `content_secondary()` | base | Secondary text, labels. |
|
|
47 |
+ |
| `content_muted()` | base | Placeholders, disabled items, empty-state hints. |
|
|
48 |
+ |
| `action()` | base | Selection/active state. *The* affordance colour. |
|
|
49 |
+ |
| `danger()` | base | Errors, danger buttons, destructive confirms. |
|
|
50 |
+ |
| `success()` | base | Success, ready state. |
|
|
51 |
+ |
| `warning()` | base | Warnings. |
|
|
52 |
+ |
| `category_five()` | base | Classification palette only. |
|
|
53 |
+ |
| `category_six()` | base | Classification palette only. |
|
|
54 |
+ |
| `border()` | base | Separator, panel boundary. |
|
|
55 |
+ |
| `row_even()` | derived | Striped row alternation (even) = `lerp(page, overlay, 0.3)`. |
|
|
56 |
+ |
| `row_odd()` | alias | Striped row alternation (odd) = `surface_page`. |
|
|
57 |
+ |
| `hover_surface()` | alias | = `surface_sunken`. Shared row/button hover. |
|
|
58 |
+ |
| `selection()` | derived | Selection fill = `lerp(page, action, 0.3)`. |
|
|
59 |
+ |
| `trim_mute_overlay()` | derived | Edit panel's trimmed-region wash; dims toward the theme's own page colour. |
|
|
60 |
+ |
| `classification_color(c)` | domain | Sample-class palette. Stable across themes for muscle memory. |
|
|
61 |
+ |
| `piano_white_key()`, `piano_black_key()` | domain | Instrument panel only. |
|
| 57 |
62 |
|
|
| 58 |
63 |
|
### Theme selection
|
| 59 |
64 |
|
|
| 100 |
105 |
|
| `rounding` | 4.0 (TOML) | All widget corner radii. |
|
| 101 |
106 |
|
| `border_thin` | 0.5 px | Separators, inactive widget border. |
|
| 102 |
107 |
|
| `border_default` | 1.0 px | Hovered/active widget border, window stroke. |
|
| 103 |
|
- |
| `focus_ring` | 1.5 px `accent_blue` | New token: focus outline on text fields. |
|
|
108 |
+ |
| `focus_ring` | 1.5 px `action` | New token: focus outline on text fields. |
|
| 104 |
109 |
|
|
| 105 |
110 |
|
---
|
| 106 |
111 |
|
|
| 110 |
115 |
|
|
| 111 |
116 |
|
### Rows and selection
|
| 112 |
117 |
|
|
| 113 |
|
- |
- **`selectable_row(ui, active, label) -> Response`**: single source of truth for every "list item that highlights when active in `accent_blue`." Replaces sidebar VFS rows, collection rows, breadcrumb segments, tag tree leaves, sort headers. Internally: builds RichText with `.strong()` + `accent_blue()` when active; `text_secondary()` otherwise; delegates to `ui.selectable_label(active, …)`.
|
|
118 |
+ |
- **`selectable_row(ui, active, label) -> Response`**: single source of truth for every "list item that highlights when active in `action`." Replaces sidebar VFS rows, collection rows, breadcrumb segments, tag tree leaves, sort headers. Internally: builds RichText with `.strong()` + `action()` when active; `content_secondary()` otherwise; delegates to `ui.selectable_label(active, …)`.
|
| 114 |
119 |
|
- **`tree_node(ui, label, active, has_active_descendant, children)`**: for the sidebar tag tree; collapsing header variant of `selectable_row`.
|
| 115 |
120 |
|
|
| 116 |
121 |
|
### Headers and section structure
|
| 117 |
122 |
|
|
| 118 |
|
- |
- **`section_header(ui, "Vaults")`**: `.strong()`, `text_secondary()`, followed by `ui.separator()` and `space::bound()`.
|
|
123 |
+ |
- **`section_header(ui, "Vaults")`**: `.strong()`, `content_secondary()`, followed by `ui.separator()` and `space::bound()`.
|
| 119 |
124 |
|
- **`subsection_label(ui, "Save as Collection")`**: same colour, no separator. For sub-blocks inside an already-headed section.
|
| 120 |
125 |
|
- **`filter_section(ui, label, active, |ui| { … })`**: `CollapsingHeader` with `"* "` marker when active and `default_open(active)`. Replaces the 5-way duplication in `filter_panel.rs`.
|
| 121 |
126 |
|
|
| 123 |
128 |
|
|
| 124 |
129 |
|
- **`primary_button(ui, label) -> Response`**: bold weight, default fill. Singular per modal/dialog.
|
| 125 |
130 |
|
- **`secondary_button(ui, label) -> Response`**: current default button. Cancel and peer actions use this.
|
| 126 |
|
- |
- **`danger_button(ui, label) -> Response`**: `accent_red` text or fill (TBD during impl). For Delete / Purge / Discard.
|
| 127 |
|
- |
- **`toolbar_toggle(ui, glyph_or_word, active, tooltip) -> bool`**: used six times in `toolbar.rs:138–204`. Active state colours via `accent_blue` / `text_muted`. Replace the emoji glyphs with words during the rollout (see brand rule).
|
|
131 |
+ |
- **`danger_button(ui, label) -> Response`**: `danger` text or fill (TBD during impl). For Delete / Purge / Discard.
|
|
132 |
+ |
- **`toolbar_toggle(ui, glyph_or_word, active, tooltip) -> bool`**: used six times in `toolbar.rs:138–204`. Active state colours via `action` / `content_muted`. Replace the emoji glyphs with words during the rollout (see brand rule).
|
| 128 |
133 |
|
- **`icon_button(ui, glyph_or_word, tooltip) -> Response`**: small, non-toggle. (`x`, `+`, settings gear once renamed.)
|
| 129 |
134 |
|
|
| 130 |
135 |
|
### Pills, chips, badges
|
| 151 |
156 |
|
|
| 152 |
157 |
|
### Banners and notifications
|
| 153 |
158 |
|
|
| 154 |
|
- |
- **`info_banner(ui, body)`**: frame with `bg_tertiary()`, `corner_radius(4)`, `space::group()` inset; used by the existing VFS first-run banner and any future inline tips.
|
|
159 |
+ |
- **`info_banner(ui, body)`**: frame with `surface_sunken()`, `corner_radius(4)`, `space::group()` inset; used by the existing VFS first-run banner and any future inline tips.
|
| 155 |
160 |
|
- **`toast(ctx, severity, body)`**: *new* primitive. Timed transient notification surfaced from a state-owned queue. Replaces the current "set `state.status = '...'`" pattern for errors and ephemeral confirmations (rename success, copy-to-clipboard). The existing footer status label is retained for *persistent* state ("Sync: 3 pending") but should no longer be the channel for transient error feedback.
|
| 156 |
161 |
|
- **`loading_spinner(ui)` / `busy_indicator(ui, label)`**: *new* primitive for in-flight operations that aren't full-screen (sidebar refresh, sync running). Import has its own progress screen and is out of scope here.
|
| 157 |
162 |
|
|