| 1 |
|
- |
# GoingsOn Design System: Charter
|
|
1 |
+ |
# GoingsOn Design System
|
| 2 |
2 |
|
|
| 3 |
|
- |
> **Out of date since 2026-08-22.** goingson swapped its document that day: the
|
| 4 |
|
- |
> Tauri window opens on `quasi://localhost/tasks`, `index.html` and every
|
| 5 |
|
- |
> hand-written file under `src-tauri/frontend/js/` are deleted, and the screens
|
| 6 |
|
- |
> are described in Rust under `src-tauri/src/quasi/`. What is still true here is
|
| 7 |
|
- |
> the part about `styles.css`, which is still served and still styles the
|
| 8 |
|
- |
> document; what is not is every reference to a script, a `<script>` tag, or a
|
| 9 |
|
- |
> class the JavaScript wrote. Rewriting it is goingson `3c5dae72`.
|
|
3 |
+ |
Where every rule that styles this app comes from, and which of them are the app's
|
|
4 |
+ |
own. For the visual language itself (the palette, the type scale, the logo) see
|
|
5 |
+ |
`styleguide.md`.
|
| 10 |
6 |
|
|
| 11 |
|
- |
This is the **canonical primitive list** for the GoingsOn frontend. Every JS module that renders markup MUST use the primitive named here. If a render need does not match a primitive, the fix is to extend the primitive, not to fork it locally.
|
|
7 |
+ |
## The stack
|
| 12 |
8 |
|
|
| 13 |
|
- |
For visual specs (colors, sizes, shadows, hover behavior) see `styleguide.md`. This file is the inventory and the rules.
|
|
9 |
+ |
Tauri 2 opens its window on `quasi://localhost/tasks`. There is no `index.html`
|
|
10 |
+ |
and no hand-written frontend: the screens are described in Rust under
|
|
11 |
+ |
`src-tauri/src/quasi/`, and `quasi-webview` renders each description to HTML.
|
|
12 |
+ |
`src/quasi/` names zero CSS classes, so every class the document carries comes
|
|
13 |
+ |
from the renderer or from makeover.
|
| 14 |
14 |
|
|
| 15 |
|
- |
**Stack:** Tauri 2 webview, vanilla HTML / CSS / JS. CSS in `src-tauri/frontend/css/styles.css`. JS modules under `src-tauri/frontend/js/` in IIFE `GoingsOn.*` namespace. Runtime themes in `src-tauri/frontend/themes/helix/`.
|
|
15 |
+ |
Five small scripts survive under `src-tauri/frontend/js/`, each doing something a
|
|
16 |
+ |
description cannot say: `host.js`, `quasi-clock.js`, `quasi-download.js`,
|
|
17 |
+ |
`quasi-fill.js`, `quasi-selection.js`. htmx is vendored under `frontend/vendor/`.
|
| 16 |
18 |
|
|
| 17 |
|
- |
---
|
|
19 |
+ |
## Four stylesheets, three of them generated
|
| 18 |
20 |
|
|
| 19 |
|
- |
## Token layer: `styles.css :root`
|
|
21 |
+ |
Served in this order by `src/quasi/mod.rs`, listed in `assets.rs`:
|
| 20 |
22 |
|
|
| 21 |
|
- |
The only place hex literals are allowed (besides `themes/`). Every JS render path consumes these via CSS classes; never via `var(--…)` in a JS string and never via fallback hex.
|
| 22 |
|
- |
|
| 23 |
|
- |
| Axis | Tokens | Notes |
|
|
23 |
+ |
| File | Owner | What it carries |
|
| 24 |
24 |
|
|---|---|---|
|
| 25 |
|
- |
| Surface color | `--bg-primary`, `--bg-secondary`, `--bg-tertiary`, `--bg-card`, `--bg-hover` | Themeable |
|
| 26 |
|
- |
| Text color | `--text-primary`, `--text-secondary`, `--text-muted`, `--text-on-accent` | Themeable |
|
| 27 |
|
- |
| Accent color | `--accent-yellow`, `--accent-green`, `--accent-blue`, `--accent-purple`, `--accent-red`, `--accent-cyan` | Themeable |
|
| 28 |
|
- |
| Accent alias | `--accent-color`, `--accent-primary` | Themeable |
|
| 29 |
|
- |
| Border | `--border-width` (2px), `--border-width-sm`, `--border-color`, `--border-light` | `--border-color` themeable; widths invariant |
|
| 30 |
|
- |
| Shadow | `--shadow-offset-xs/sm/md/lg/xl`, `--shadow-brutal-xs/md/lg/xl` | Theme-invariant (Neobrute signature) |
|
| 31 |
|
- |
| Radius | `--radius-xs/sm/md/lg/xl/full` | Invariant |
|
| 32 |
|
- |
| Spacing | `--gap-bound/peer/group/section/pane/page` over `--step-hair` ... `--step-colossal` | Invariant, and not in this file: `css/geometry.css`, generated by `src-tauri/build.rs` from the `makeover-geometry` crate |
|
| 33 |
|
- |
| Type size | `--font-size-xxs` ... `--font-size-4xl` | Invariant |
|
| 34 |
|
- |
| Line height | `--line-height-tight/normal/relaxed` | Invariant |
|
| 35 |
|
- |
| Font family | `--font-sans`, `--font-serif`, `--font-mono`, `--font-display` | Invariant |
|
| 36 |
|
- |
| Layout width | `--width-container`, `--width-modal`, `--width-sidebar` | Invariant |
|
| 37 |
|
- |
| Motion | `--transition-fast/normal/slow` | Invariant |
|
| 38 |
|
- |
| Cross-layer | `--timeline-slot-h` | Read by `js/day-planning-*` |
|
| 39 |
|
- |
|
| 40 |
|
- |
**Rule:** every CSS property `js/themes.js` maps must exist in `:root`. Every property in `:root` that uses color must appear in `js/themes.js`'s mapping or carry a `/* theme-invariant */` comment.
|
| 41 |
|
- |
|
| 42 |
|
- |
---
|
| 43 |
|
- |
|
| 44 |
|
- |
## Component primitives: canonical class is the contract
|
| 45 |
|
- |
|
| 46 |
|
- |
Each primitive below lists its **canonical class** (use this, only this). Modifier classes follow `--modifier` or `state-*` patterns. If you find yourself wanting a new modifier, add it here first.
|
| 47 |
|
- |
|
| 48 |
|
- |
### Button: `.button`
|
| 49 |
|
- |
Variants: `.button--primary`, `.button--secondary`, `.button--danger`, `.button--icon`, `.button--text`, `.button--link`.
|
| 50 |
|
- |
Sizes: `.button--sm` (default size is medium).
|
| 51 |
|
- |
State: `.button--loading`.
|
| 52 |
|
- |
**Never** style a `<button>` without `.button`. **Never** inline a hex color on a button.
|
| 53 |
|
- |
Fill and edge come from the generated `.button` in `css/layout.css`; do not restate them here.
|
| 54 |
|
- |
|
| 55 |
|
- |
### Card: `.card`
|
| 56 |
|
- |
Sub-parts: `.card-header`, `.card-title`, `.card-description`, `.card-meta`, `.card-badge`.
|
| 57 |
|
- |
Variant: `.card--list-item` (compact, for dense lists).
|
| 58 |
|
- |
Container: `.cards-grid`.
|
| 59 |
|
- |
Used by: projects-render, contacts-render, dashboard tiles.
|
| 60 |
|
- |
**A card is a control.** It answers a click, and the generated rule gives it hover, press, focus and disabled to say so. If the thing does not answer a click, it is a panel.
|
| 61 |
|
- |
|
| 62 |
|
- |
### Panel: `.panel`
|
| 63 |
|
- |
The card's box without the control: same border, radius and padding, no cursor and no states. Variants: `.panel--shell` (no padding, flex column, for list wrappers), `.panel--muted` (set back by colour, no edge).
|
| 64 |
|
- |
Wear `.raised` alongside it for fill and bevel: `<div class="raised panel review-card">`. `.panel--muted` takes no `.raised` — it is set back rather than raised.
|
| 65 |
|
- |
Replaced `.card--static | --shell | --muted` on 2026-08-06, which were cards with the control half cancelled. Never cancel a state a generated rule emits; take a class that does not emit it.
|
| 66 |
|
- |
|
| 67 |
|
- |
### Form field: `.form-group`
|
| 68 |
|
- |
Sub-parts: `.form-label`, `.field`, `.form-actions`, `.form-row`.
|
| 69 |
|
- |
The kind rides on the element, not on a modifier: `input.field`, `select.field`, `textarea.field`. Modifiers: `.field--ghost`, `.field--compact`. Invalid state is `[aria-invalid="true"]`, not a class, so the visual and the accessible state read one fact.
|
| 70 |
|
- |
Canonical render helper (**to be added**): `GoingsOn.ui.renderFormField({ kind, label, value, error, help })`. Until it exists, hand-rolled `.form-group` blocks are tolerated; once it lands they are not.
|
| 71 |
|
- |
|
| 72 |
|
- |
### Badge: `.badge`
|
| 73 |
|
- |
There is no `.tag`. It was a second name for the same thing and collapsed into `.badge`.
|
| 74 |
|
- |
Color variant: `[data-color="green|yellow|red|cyan|purple|blue|muted"]`.
|
| 75 |
|
- |
Status variant: `[data-tone="info|success|warning|danger"]`, from the generated stylesheet. Which tone a status takes is `GoingsOn.utils.statusTone(status)`; an unmapped status gets no attribute and stays neutral. The hand-coloured `.status-*` and `.badge-shared` classes are gone (2026-08-06).
|
| 76 |
|
- |
Sizes and intent: `.badge--xs`, `.badge--filled`.
|
| 77 |
|
- |
A plain badge is flat: no fill, no edge, text at `--content-muted`, all from the generated `.badge`. An edge on a label says it can be pressed, and no badge in GO is interactive. `[data-tone]` tones the text and leaves the box alone. `[data-color]` is the other axis and does make a pill: it names GO's own per-tag palette, where the colour is the content rather than a state.
|
| 78 |
|
- |
|
| 79 |
|
- |
### Modal: `.modal-overlay` (single global)
|
| 80 |
|
- |
Open via `GoingsOn.ui.openModal(title, html, opts)`. Sub-parts: `.modal-container` (+ `.modal-large`), `.modal-header`, `.modal-title`, `.modal-content`, `.modal-close`. Visibility via `.hidden` / `.closing`. **There is only one modal overlay in the DOM** (`#modal-overlay` in `index.html`); never create another.
|
| 81 |
|
- |
|
| 82 |
|
- |
### Toast: `.toast`
|
| 83 |
|
- |
Variants: `.toast-info`, `.toast-success`, `.toast-error`, `.toast-undo`.
|
| 84 |
|
- |
Undo sub-parts: `.undo-message`, `.undo-countdown`, and a plain `.button.button--sm.button--primary` for the action.
|
| 85 |
|
- |
Show via `GoingsOn.ui.showToast(msg, type, opts)` or `GoingsOn.ui.showUndoToast(...)`.
|
| 86 |
|
- |
**Rule (to be enforced):** positioning, shadow, and color belong on these classes in CSS. `showToast` MUST NOT inject `style.cssText`. Today's helper violates this; fix in remediation.
|
| 87 |
|
- |
|
| 88 |
|
- |
### Confirm dialog
|
| 89 |
|
- |
Render via `GoingsOn.ui.showConfirmDialog(title, message, opts)` or `GoingsOn.ui.confirmDelete(name, action)`. Uses the global modal. **Never** call `window.confirm()` (one offender remains in `contacts.js`, fix in remediation).
|
| 90 |
|
- |
|
| 91 |
|
- |
### Empty state: `.empty-state`
|
| 92 |
|
- |
Canonical: `<div class="empty-state"><div class="empty-state-icon">…</div><p class="empty-state-text">…</p><button class="button button--primary">…</button></div>`.
|
| 93 |
|
- |
Render via `GoingsOn.ui.renderEmptyState(message, buttonLabel?, onClick?)`.
|
| 94 |
|
- |
The non-canonical classes `.empty-dashboard-list`, `.kanban-empty`, `.virtual-scroller-empty` are **deprecated**; consolidate to `.empty-state` with size modifiers in remediation.
|
| 95 |
|
- |
|
| 96 |
|
- |
### Skeleton / loading
|
| 97 |
|
- |
Classes: `.skeleton-shimmer`, `.skeleton-row`, `.skeleton-lines`, `.skeleton-line.long | .medium | .short`, `.spinner`, `.loading`.
|
| 98 |
|
- |
Canonical helper (**to be added**): `GoingsOn.ui.renderSkeleton(kind, rows)`. No view uses skeletons today; once the helper exists, list views should switch on by default for the first paint after `invoke()`.
|
| 99 |
|
- |
|
| 100 |
|
- |
### Context menu: `.context-menu`
|
| 101 |
|
- |
State: `.visible`. Items: `.context-menu-item` (+ `--danger`), `.context-menu-separator`, `.context-menu-header`.
|
| 102 |
|
- |
Open via `GoingsOn.ui.showContextMenu(x, y, items)`.
|
| 103 |
|
- |
|
| 104 |
|
- |
### Tab / pill nav: `.tab-navigation` / `.pill-nav`
|
| 105 |
|
- |
Chosen state: `.tab.chosen` (scoped to `.tab-navigation`) / `.pill.active`. The two names differ because only `.tab` takes the generated selector rules. Used in shell (`index.html`) only. Feature modules should not introduce new tab styles.
|
| 106 |
|
- |
|
| 107 |
|
- |
### Filter bar: `.filter-bar`
|
| 108 |
|
- |
Children: `.filter-select`, `.filter-checkbox`. Used in tasks and emails filter rows.
|
| 109 |
|
- |
|
| 110 |
|
- |
### Progress bar: `.progress` (trough) + `.progress-fill`
|
| 111 |
|
- |
Sizes: `.progress--slim`, `.progress--mini`, `.progress--focus`. Tone on the fill: `[data-tone="info|success|warning|danger"]`.
|
| 112 |
|
- |
Used in tasks (subtask completion), milestones, and reviews.
|
| 113 |
|
- |
|
| 114 |
|
- |
### Row primitives
|
| 115 |
|
- |
|
| 116 |
|
- |
| Kind | Canonical class | Render helper (today) |
|
| 117 |
|
- |
|---|---|---|
|
| 118 |
|
- |
| Task row | `.row.task-row` (in `.task-table`) | `renderTaskRow(t, index)`, `tasks-render.js` |
|
| 119 |
|
- |
| Event row | `.row.event-row-virtual.event-upcoming` or `.event-recurring` | `renderEventRow(e, index, isPast, isRecurring)`, `events.js` |
|
| 120 |
|
- |
| Project card | `.card` (in `.cards-grid`) | `renderProjectCard(p)`, `projects.js` |
|
| 121 |
|
- |
| Contact card | `.card.contact-card` | inline in `contacts-render.js` |
|
| 122 |
|
- |
| Email row | `.row.email-item` (in `.email-list`) | `renderEmailItem(thread)`, `emails-render.js` |
|
| 123 |
|
- |
|
| 124 |
|
- |
### Table columns: `col-<name>` and the column description
|
| 125 |
|
- |
|
| 126 |
|
- |
Three tables have columns described in `src-tauri/build.rs`: the task table and the
|
| 127 |
|
- |
two event tables (upcoming and recurring, which are two tables and not one). A
|
| 128 |
|
- |
column says what it is worth (`Essential`, `Secondary`, `Optional`), never where it
|
| 129 |
|
- |
sits. `build.rs` turns each description into two generated files:
|
| 130 |
|
- |
|
| 131 |
|
- |
- `css/tables.css`: the `grid-template-columns` track list per breakpoint, plus a
|
| 132 |
|
- |
`display: none` on each dropped column *by its own class*. Both halves come out of
|
| 133 |
|
- |
one call, so the tracks and the hiding cannot disagree.
|
| 134 |
|
- |
- `tables.columns.json`: the same column names, for the tests.
|
| 135 |
|
- |
|
| 136 |
|
- |
Every cell wears `col-<name>`, which is what makes a column addressable without
|
| 137 |
|
- |
counting. Add a column by adding it to the description, then giving the header in
|
| 138 |
|
- |
`index.html` and the row builder a cell with its `col-` class. The suite in
|
| 139 |
|
- |
`js/tests/run.js` fails if either one disagrees with the description in content or
|
| 140 |
|
- |
in order.
|
| 141 |
|
- |
|
| 142 |
|
- |
Never hide a column with `nth-child`, and never write a track list by hand. Both
|
| 143 |
|
- |
were how the mobile task rule came to declare four tracks for three surviving cells,
|
| 144 |
|
- |
and how the upcoming events row rendered six cells into a five-track grid with every
|
| 145 |
|
- |
cell one column left of its label.
|
| 146 |
|
- |
|
| 147 |
|
- |
The rows themselves stay hand-built in JS. makeover-webview can emit cell containers
|
| 148 |
|
- |
(`list::cells_html`) and deliberately has no webview consumer: the virtual scroller
|
| 149 |
|
- |
calls its row builder synchronously while scrolling, so reaching Rust from there
|
| 150 |
|
- |
would put an IPC round trip in a 60Hz loop. GO takes the stylesheet half and the
|
| 151 |
|
- |
`col-` vocabulary, and nothing else.
|
| 152 |
|
- |
|
| 153 |
|
- |
Per-cell classes (`.task-project`, `.event-cell-time`) still carry padding, alignment
|
| 154 |
|
- |
and colour. They are not the column; `col-<name>` is.
|
| 155 |
|
- |
|
| 156 |
|
- |
### Hover-revealed row actions: `.row` + `.row-actions`
|
| 157 |
|
- |
Both classes are generated (`layout.css`, makeover-layout `RowPart`). `.row-actions`
|
| 158 |
|
- |
rests at `opacity: 0` with `pointer-events: none`; `.row:hover` and `.row:focus-within`
|
| 159 |
|
- |
bring both back, so the control stays in the DOM, stays focusable, and stays in the
|
| 160 |
|
- |
accessibility tree. Put `.row` on the row and `.row-actions` on each control that
|
| 161 |
|
- |
hides: `.kebab-btn`, `.task-row-action`, `.month-goal-delete-btn`. Those three classes
|
| 162 |
|
- |
carry the look only, never the hiding.
|
| 163 |
|
- |
|
| 164 |
|
- |
A control that is always visible does not wear `.row-actions` — the project card's
|
| 165 |
|
- |
kebab is one, which is why its card is not a `.row`. Where there is no hover
|
| 166 |
|
- |
`.row-actions` is shown unconditionally; that is a capability question, so the
|
| 167 |
|
- |
override lives under `(hover: none), (pointer: coarse)` and not under a width, and it
|
| 168 |
|
- |
must restore `pointer-events` as well as `opacity`.
|
| 169 |
|
- |
|
| 170 |
|
- |
### Row text parts: `.row-primary` / `.row-secondary` / `.row-meta`
|
| 171 |
|
- |
Generated too, and they are only the three content colours (`--content`,
|
| 172 |
|
- |
`--content-secondary`, `--content-muted`). Unlike `.row-actions` they are not scoped
|
| 173 |
|
- |
to a `.row` ancestor, so a list row that carries no hidden actions can still wear
|
| 174 |
|
- |
them: the email search result does.
|
| 175 |
|
- |
|
| 176 |
|
- |
Put one on any row text whose colour is the hierarchy and nothing more, and delete
|
| 177 |
|
- |
the declaration it replaces. Worn today by the task row's project, due and
|
| 178 |
|
- |
recurrence cells and its no-subtasks dash; the email item's from, subject, date and
|
| 179 |
|
- |
preview; the event row's date number, time and location.
|
| 180 |
|
- |
|
| 181 |
|
- |
A part is not worn where the colour means something other than rank. The overdue
|
| 182 |
|
- |
task description and due date, the recurring event's pattern label and every badge
|
| 183 |
|
- |
keep their own rule, and each of those out-specifies the part class it sits next to.
|
| 184 |
|
- |
A row element with no colour declaration of its own does not gain a part class
|
| 185 |
|
- |
either: it already inherits `--content`, so the class would add a declaration
|
| 186 |
|
- |
without replacing one.
|
| 187 |
|
- |
|
| 188 |
|
- |
**Canonical helper (to be added):** `GoingsOn.ui.renderRow(kind, model, opts)`. Each `renderXxx` above becomes a thin adapter that maps the model to the shared "icon · primary · secondary · meta · actions" slot layout. Surface audits in Phase 1+ assume this exists.
|
| 189 |
|
- |
|
| 190 |
|
- |
### Task row state classes (composed onto `.task-row`)
|
| 191 |
|
- |
`.task-overdue`, `.task-completed`, `.task-started`, `.task-snoozed`, `.task-timer-active`, `.priority-high | -medium | -low`, plus badges `.task-badge.has-items`, `.task-time-badge.over-estimate`.
|
| 192 |
|
- |
|
| 193 |
|
- |
### Bulk selection
|
| 194 |
|
- |
Bar: `.bulk-actions-bar`. Controls: `.bulk-checkbox`, `.bulk-select-all`, `.bulk-count`. Row state: `.selected`.
|
| 195 |
|
- |
|
| 196 |
|
- |
### Kanban: `.kanban-board`
|
| 197 |
|
- |
Children: `.kanban-column`, `.kanban-card`, `.kanban-card-empty`. Used only by tasks-kanban view.
|
| 198 |
|
- |
|
| 199 |
|
- |
### Day-plan timeline: `.timeline-slot`
|
| 200 |
|
- |
Blocks: `.time-block`, `.block-focus`, `.block-personal`. Slot height read from `--timeline-slot-h`.
|
| 201 |
|
- |
|
| 202 |
|
- |
### Weekly review grid: `.weekly-grid`
|
| 203 |
|
- |
Cells: `.weekly-cell`, `.weekly-day-header`.
|
| 204 |
|
- |
|
| 205 |
|
- |
### Subtasks: `.subtask-item`
|
| 206 |
|
- |
Variant: `.subtask-item-linked` (left-border indicator for linked task). Children: `.subtask-checkbox`, `.subtask-text-done`.
|
| 207 |
|
- |
|
| 208 |
|
- |
### Shell: `.app-header` / `.app-body` / `.main-content` / `.page-header` / `.page-title`
|
| 209 |
|
- |
Feature modules do not redefine shell classes.
|
| 210 |
|
- |
|
| 211 |
|
- |
---
|
| 212 |
|
- |
|
| 213 |
|
- |
## Theme contract: `js/themes.js`
|
| 214 |
|
- |
|
| 215 |
|
- |
Every theme is a TOML file under `themes/helix/` with a `[palette]` block (Helix-style names) and UI-key references. At runtime, `js/themes.js` maps 13 dotted TOML paths to CSS custom properties; selection persists to `localStorage` (`goingson-theme`).
|
| 216 |
|
- |
|
| 217 |
|
- |
**Rules:**
|
| 218 |
|
- |
1. A theme overrides **color tokens only**. Spacing, radius, shadow offsets, type are theme-invariant.
|
| 219 |
|
- |
2. Every color token in `:root` either has a mapping in `js/themes.js` or is annotated `/* theme-invariant */`. Adding a new color token requires updating the mapping in the same PR.
|
| 220 |
|
- |
3. JS rendering paths never read theme values directly. They use CSS classes that consume `var(--…)`. No JS string should contain `var(--accent-…, #fallback)` because the fallback bypasses the theme.
|
| 221 |
|
- |
|
| 222 |
|
- |
---
|
| 223 |
|
- |
|
| 224 |
|
- |
## Inline-style rules
|
| 225 |
|
- |
|
| 226 |
|
- |
1. `style="display:none"` in HTML is allowed only on the modal overlay and similar shell-level slots; feature views use `.hidden`.
|
| 227 |
|
- |
2. No `style.cssText` in JS that contains a color, shadow, or border value. Layout-only inline styles (`flex`, `gap`, `min-width`) are tolerated during remediation; the goal is zero.
|
| 228 |
|
- |
3. No hex literal in any file outside `styles.css` and `themes/*.toml`.
|
| 229 |
|
- |
4. No `var(--token, #fallback)`. The fallback defeats theming.
|
| 230 |
|
- |
|
| 231 |
|
- |
---
|
| 232 |
|
- |
|
| 233 |
|
- |
## Cross-cutting rules
|
| 234 |
|
- |
|
| 235 |
|
- |
These apply across every surface. Violations caught by reviewer checklist or `scripts/lint-frontend.sh`. Derived from the internal Phase 7 UX audit roll-up.
|
| 236 |
|
- |
|
| 237 |
|
- |
### State communication
|
| 238 |
|
- |
Every visual state (active, selected, running, error, success) must pair color with a second non-color signal: shape, position, weight, icon, or text. Color alone is not sufficient. (Phase 7 Pattern 1, 6 surfaces affected.)
|
| 239 |
|
- |
|
| 240 |
|
- |
### Filter & view state in the URL
|
| 241 |
|
- |
Every filter, sort, and view-mode setting that changes what the user sees must be mirrored to `location.search` on change and restored on init. Filter state must not live only in the DOM or in module-level JS. A shared `js/query-state.js` helper covers all surfaces. (Pattern 2, 5 surfaces affected.)
|
| 242 |
|
- |
|
| 243 |
|
- |
### Bulk operations always undoable
|
| 244 |
|
- |
Every bulk operation (any action touching more than one record at once) must wrap its API call in `GoingsOn.ui.showUndoToast` with a captured pre-state and an inverse operation. Use the shared `bulkActionWithUndo(action, inverse, ids, prevState)` helper. (Pattern 3, 3 surfaces affected.)
|
| 245 |
|
- |
|
| 246 |
|
- |
### Native dialogs forbidden
|
| 247 |
|
- |
`window.confirm`, `window.prompt`, and `window.alert` are banned. Use `GoingsOn.ui.showConfirmDialog`, `GoingsOn.ui.showPromptDialog`, and `GoingsOn.ui.showToast`. Native dialogs are disabled on iOS WKWebView and unstyled on all platforms. Lint rule `no-native-dialogs` enforces this.
|
| 248 |
|
- |
|
| 249 |
|
- |
### A described member stays in flow
|
| 250 |
|
- |
A group described by makeover-layout keeps every member in flow. A member never positions itself out of the row it shares: out of flow it contributes no width, so nothing can collide with it and nothing prevents the collision. Layering is the closed layer set (modals, the scrim, a drawer), not a member's own `position`. When a row runs out of room the answer is the group's fallback, not `position: absolute`. Lint rule `described-members-in-flow` enforces this for `.run`, `.page-header`, `.subview-head` and `.pill-nav`, and carries an allow-list for anything that has a reason.
|
| 251 |
|
- |
|
| 252 |
|
- |
### Mobile is responsive CSS by default
|
| 253 |
|
- |
JS branches on `GoingsOn.touch.isTouchDevice` (or media-query equivalents) require explicit justification documented here. Default is shared component + CSS layout reflow. (Pattern 5, Phase 6 architectural finding.)
|
| 254 |
|
- |
|
| 255 |
|
- |
### Multi-step flows show progress
|
| 256 |
|
- |
Any flow with more than two sequential modal steps shows a "Step N of M" indicator in the modal header. Applies to OAuth, encryption setup, plugin import wizards.
|
| 257 |
|
- |
|
| 258 |
|
- |
### Action bars cap at 5 visible
|
| 259 |
|
- |
A horizontal action bar has at most 5 visible actions; the rest live in an overflow `Actions â–¾` menu. Primary actions get `.button--primary`; destructive actions go in the overflow.
|
| 260 |
|
- |
|
| 261 |
|
- |
### Justified touch branches
|
| 262 |
|
- |
Modules with `isTouchDevice` branches must include a top-of-file comment naming what the branch does and why CSS-only isn't sufficient.
|
| 263 |
|
- |
|
| 264 |
|
- |
---
|
| 265 |
|
- |
|
| 266 |
|
- |
## Success criteria for remediation (input to the pre-Phase-1 plan)
|
| 267 |
|
- |
|
| 268 |
|
- |
Phase 1 surface audits start when **all** of the following are true:
|
| 269 |
|
- |
|
| 270 |
|
- |
- `GoingsOn.ui.renderRow(kind, model, opts)` exists, and `tasks-render.js`, `projects-render.js`, `contacts-render.js`, `events.js`, `emails.js` all call it (adapters allowed, parallel markup not).
|
| 271 |
|
- |
- `GoingsOn.ui.renderFormField({ … })` exists, and every form field in `form-modal.js`, `settings.js`, `email-accounts.js`, `settings-sync.js` is built through it. Error variant works.
|
| 272 |
|
- |
- `showToast` injects no `style.cssText`. All toast positioning, color, and shadow live on `.toast` + variant classes in `styles.css`.
|
| 273 |
|
- |
- Grep `\bstyle="` across `src-tauri/frontend/` returns no color, shadow, border, or font value; only visibility / layout micro-tweaks (and ideally none of those).
|
| 274 |
|
- |
- Grep `#[0-9a-fA-F]{3,8}` across `src-tauri/frontend/js/` and `src-tauri/frontend/*.html` returns zero matches.
|
| 275 |
|
- |
- Empty states: deprecate `.empty-dashboard-list`, `.kanban-empty`, `.virtual-scroller-empty`; consolidate to `.empty-state` with `--compact` / `--dashboard` modifiers, or keep them with explicit "use X when Y" rules documented in this charter.
|
| 276 |
|
- |
- Every color custom property in `styles.css :root` is either mapped in `js/themes.js` or carries a `/* theme-invariant */` comment.
|
| 277 |
|
- |
- `window.confirm()` calls: zero. All confirms route through `GoingsOn.ui.showConfirmDialog`.
|
| 278 |
|
- |
|
| 279 |
|
- |
When all criteria hold, Phase 1 (Shell & navigation) may begin.
|
|
25 |
+ |
| `css/typography.css` | generated from `makeover` | `@font-face` for the house faces and Reglo; `--font-mono`, `--font-sans`, `--font-display` |
|
|
26 |
+ |
| `css/geometry.css` | generated from `makeover-geometry` | the `--step-*` scale and the `--gap-*` relationships; touch density; the shell width boundaries |
|
|
27 |
+ |
| `css/layout.css` | generated from `makeover-webview` | depth, fill, edge, focus ring, disabled colour; `.raised`, `.well`, and the vocabulary classes |
|
|
28 |
+ |
| `css/styles.css` | this app, by hand | the reset, the intent token block, and the box model and type this app puts on a vocabulary class |
|
|
29 |
+ |
|
|
30 |
+ |
Do not edit the first three. They are written by `src-tauri/build.rs` on every
|
|
31 |
+ |
build, and a change made there is gone at the next one.
|
|
32 |
+ |
|
|
33 |
+ |
## Layers
|
|
34 |
+ |
|
|
35 |
+ |
`styles.css` declares three, and the renderer declares `makeover` ahead of them:
|
|
36 |
+ |
|
|
37 |
+ |
makeover the three generated stylesheets
|
|
38 |
+ |
base the reset and the token block
|
|
39 |
+ |
components what a component owns at rest
|
|
40 |
+ |
responsive every width and capability block
|
|
41 |
+ |
|
|
42 |
+ |
Layers resolve before specificity, so a base rule in `styles.css` beats a more
|
|
43 |
+ |
specific rule in `@layer makeover`. That is the point, and it is also the one
|
|
44 |
+ |
trap: an app setting a property at rest must restate that property's disabled
|
|
45 |
+ |
variant, or the generated `:disabled` never lands. Sections 9 and 20 of
|
|
46 |
+ |
`styles.css` work both cases.
|
|
47 |
+ |
|
|
48 |
+ |
## What may live in `styles.css`
|
|
49 |
+ |
|
|
50 |
+ |
Three kinds of rule, and the file's own header states them at length:
|
|
51 |
+ |
|
|
52 |
+ |
- the reset and the token block, which are the app's ground
|
|
53 |
+ |
- box model and type on a vocabulary class, where the design system supplies
|
|
54 |
+ |
depth and colour and declines to supply geometry
|
|
55 |
+ |
- placement for the described shell's chrome, which quasi states as structure
|
|
56 |
+ |
and refuses to state as position, because a terminal has neither
|
|
57 |
+ |
|
|
58 |
+ |
Depth, fill, edge, focus ring and disabled colour are not here. They come from
|
|
59 |
+ |
`layout.css`, and a rule that restates one is a bug.
|
|
60 |
+ |
|
|
61 |
+ |
Seventeen classes are styled today: `.badge`, `.button`, `.card`, `.chrome-nav`,
|
|
62 |
+ |
`.chrome-panel`, `.chrome-place`, `.field`, `.form-checkbox-label`,
|
|
63 |
+ |
`.form-error`, `.form-group`, `.form-hint`, `.form-label`, `.progress`,
|
|
64 |
+ |
`.progress-fill`, `.tab`, `.table-heading`, `.toast`. Every one of them is the
|
|
65 |
+ |
renderer's or makeover's.
|
|
66 |
+ |
|
|
67 |
+ |
## Adding something
|
|
68 |
+ |
|
|
69 |
+ |
Ask, in order:
|
|
70 |
+ |
|
|
71 |
+ |
1. Does the design system already answer this? If it does, take the answer.
|
|
72 |
+ |
2. Does the answer belong in the description rather than in CSS? Usually it
|
|
73 |
+ |
does.
|
|
74 |
+ |
3. Only then, a rule in `styles.css`.
|
|
75 |
+ |
|
|
76 |
+ |
A new class is not an option. Nothing in this app can emit one. If a screen needs
|
|
77 |
+ |
something the vocabulary cannot say, the gap is filed against quasi and the
|
|
78 |
+ |
vocabulary grows through the cascade: `quasi-router` describes it,
|
|
79 |
+ |
`makeover-layout` names it, `makeover-webview` emits it.
|
|
80 |
+ |
|
|
81 |
+ |
## The build is the check
|
|
82 |
+ |
|
|
83 |
+ |
`src-tauri/build.rs` runs three guards, and they are why this document does not
|
|
84 |
+ |
need a rule list:
|
|
85 |
+ |
|
|
86 |
+ |
- `check_stylesheet_reaches_markup` asks `quasi-webview` whether anything can
|
|
87 |
+ |
emit each selector in `styles.css`, prints the live and dead counts, and seals
|
|
88 |
+ |
the dead count so it cannot grow. The emittable set is closed, so a selector
|
|
89 |
+ |
outside it is dead rather than unaccounted for.
|
|
90 |
+ |
- `check_vocabulary` catches a rule in `styles.css` that restates what
|
|
91 |
+ |
`geometry.css` or `layout.css` already says.
|
|
92 |
+ |
- the intent check refuses a rule that reads a `--token` the fallback block does
|
|
93 |
+ |
not declare, since the declaration would be invalid until a theme lands.
|
|
94 |
+ |
|
|
95 |
+ |
`check_vocabulary_use` is not among them. It went with the swap and nothing
|
|
96 |
+ |
replaces it: goingson `43a682b0`.
|
|
97 |
+ |
|
|
98 |
+ |
`scripts/lint-frontend.sh` holds the three rules that police a stylesheet rather
|
|
99 |
+ |
than a script: `no-var-fallback-hex`, `no-deprecated-empty-states`,
|
|
100 |
+ |
`described-members-in-flow`. Its header records the seven rules the swap
|
|
101 |
+ |
retired and why each one stopped having anything to read.
|
|
102 |
+ |
|
|
103 |
+ |
## Two standing rules
|
|
104 |
+ |
|
|
105 |
+ |
**No `var(--token, #fallback)`.** The fallback defeats the theme. Lint rule
|
|
106 |
+ |
`no-var-fallback-hex`.
|
|
107 |
+ |
|
|
108 |
+ |
**A described member stays in flow.** A group described by `makeover-layout`
|
|
109 |
+ |
keeps every member in flow; a member never positions itself out of the row it
|
|
110 |
+ |
shares. Out of flow it contributes no width, so nothing can collide with it and
|
|
111 |
+ |
nothing prevents the collision. Layering is the closed layer set (modals, the
|
|
112 |
+ |
scrim, a drawer), never a member's own `position`. Lint rule
|
|
113 |
+ |
`described-members-in-flow`, with an allow-list that requires a reason. Wiki:
|
|
114 |
+ |
`layout-room-and-fallback`.
|