Files
cimtechniques-service-suite/docs/DESIGN-SYSTEM.md
2026-06-11 16:13:51 -04:00

115 lines
7.0 KiB
Markdown

# CIM Service Suite — Design System ("Instrument")
> **Scope: this is the binding visual standard for the whole suite.** Every current
> and future module follows it so the unified app looks like one product. The
> authoritative specification — every color token, type style, geometry value, and
> component rule — is **[`Instrument Design Spec.md`](Instrument%20Design%20Spec.md)**.
> This document is the working guide: where the implementation lives, how to adopt
> it in a module, and the rules that keep it coherent. If a new module needs
> something the system doesn't cover, **extend the theme/kit packages** — never
> introduce per-module colors, fonts, or one-off styling.
The mockups (`Instrument Suite (standalone).html`) are a visual reference ONLY —
never pixel-measure or scrape values from them; the spec's tables are the source of
truth. Rollout history: `docs/superpowers/specs/2026-06-10-instrument-design-system-rollout-design.md`
(six phases, all merged — per-phase records in `docs/BACKLOG.md` BL-DS1..BL-DS-P6).
## Implementation map
```
cim_suite/core/ui/theme/ The theme layer — the only place colors/fonts/QSS live
tokens.py Two theme dicts (light/dark) keyed by spec token names,
+ SEVERITY/STALENESS/CHART maps, Space/Radius/Metrics
fonts.py Bundled IBM Plex Sans + IBM Plex Mono (OFL), loaded at startup
type_styles.py The spec's named type styles as QFont factories
(letter-spacing/weight included — QSS can't express tracking)
stylesheet.py build_qss(...) — the entire QSS, generated from tokens
manager.py current() accessor + themeChanged signal + persistence
__init__.py apply_theme(app, theme=None) — fonts + QSS + Fusion base
cim_suite/core/ui/kit/ The component kit (spec §5) — built once, adopted per module
delegate.py InstrumentDelegate: cell kinds, §1.2 status tags, edge bars,
alarm-row tint, hover edit affordance, styled editor,
validation, write feedback (mark_pending/resolve), QUIET_ROLE
settings_list.py SettingsList (§5.5): groups, hints, RO chips, toggles, choices
summary_strip.py SummaryStrip (§5.4 footer) + the shared EDIT_HINT constant
units_header.py UnitsHeaderView: microcaps header + mono units line
tab_widget.py InstrumentTabWidget: §5.3 underline tabs
toggle_switch.py ToggleSwitch (§5.4 boolean columns)
connection_chip.py ConnectionChip (§5.2 toolbar pill)
sidebar.py Sidebar (§5.7 device list)
activity_log.py ActivityLogCard (§5.8)
icons.py currentColor-tinted SVG icons
cim_suite/core/ui/chrome/ Window chrome (spec §4): frameless TitleBar, AccentStrip,
StatusFooter, ChromeDialog + message_box confirms
```
`apply_theme(app, theme=None)` is called once by the shell; it loads the saved theme
choice when `theme` is omitted. Theme choice persists in the JSON config under
`ui.json`. **Theme switching:** `set_theme(app, name)` regenerates and reapplies the
QSS; anything that paints token colors in code must read `theme.current()` **at paint
time** (delegates do this) or repaint on `manager.signals.themeChanged`.
## Rules (enforced)
- **No hard-coded hex and no `setStyleSheet` outside `cim_suite/core/ui/theme/`** —
`tests/core/test_design_discipline.py` fails the build otherwise.
- **Fonts come from `type_styles`**, never ad-hoc `QFont(...)` — microcaps tracking
and the Plex families only exist there (Qt ignores `text-transform`/tracking in
QSS, so microcaps text is `.upper()` + the style's font, in code).
- **Status is never color alone** (§1.2): the kit's status tags pair the dot shape
with a text label; alarm rows pair the tint with the edge bar.
- **One primary action per toolbar** (§5.1): the connect/disconnect verb gets
`objectName="PrimaryAction"`; everything else is secondary/quiet.
- **Single-click inline editing** (§5.6) via `TableTab`/the kit delegate;
`Qt.ItemIsEditable` is the single source of editability. When repopulating a table
from the model, run under the tab's `_loading` guard so programmatic fills don't
fire phantom edits (write-storm protection — regression-tested).
- Geometry/spacing from `Space`/`Radius`/`Metrics` tokens, not magic numbers.
## Component hooks
| Intent | How |
|---|---|
| Highlighted toolbar action | tool button `objectName="PrimaryAction"` |
| Brand marks in the toolbar | `QLabel` `objectName="BrandTitle"` / `"BrandTitleAccent"` |
| Filled accent button | `button.setProperty("variant", "primary")` |
| Footer readouts | `chrome.StatusFooter` — declarative `add_dot`/`add_field` |
| Confirm/info dialogs | `chrome.message_box` submodule — `message_box.question/information/warning` (chrome-framed, breadcrumb titles) |
| Grid summary footer | `TableTab.enable_summary(kit.EDIT_HINT)` + `summary.set_summary([...])` |
## For new modules — adoption checklist
The worked examples are the three shipped modules; `cim_suite/modules/da07/ui/` is
the most recent full adoption and `cim_suite/modules/iomodbus/ui/` shows the sidebar +
activity log. Copy this checklist into the module's adoption plan:
- [ ] Toolbar per spec §5.2: `← Suite` (emit `suiteRequested`; the shell wires it),
brand labels + `kit.ConnectionChip` (the module feeds COM/SIM source labels),
rare/risky commands behind a confirm-guarded `▾` menu
(`menu.setToolTipsVisible(True)` — QMenu hides tooltips by default),
primary Connect/Disconnect far right
- [ ] Grid screens subclass `TableTab`; column kinds set on the delegate
(`NUMERIC`/`IDENTIFIER`/`STATUS`/`TOGGLE`), units rows via `set_column_units`,
header help via `set_column_help` (tooltips — never ⓘ markers)
- [ ] Status columns use §1.2 status tags (`STATUS_ROLE`), full-row alarm treatment
via `ALARM_ROW_ROLE` for alarm-class states only
- [ ] Summary strip with `kit.EDIT_HINT` on every editable grid
- [ ] Station/settings screens use `kit.SettingsList` (§5.5) with curated groups;
read-only governed by the wire/protocol, never invented; toggles/choices only
where the value's meaning is verified (VB6-fidelity rule)
- [ ] Write feedback (§5.6): `delegate.mark_pending` on edit; `resolve` on the
protocol's confirmation (echo or re-read)
- [ ] No `setStyleSheet`, no hex, no ad-hoc fonts in module code (the guard test
enforces it); custom paints read `theme.current()` at paint time
- [ ] Spreadsheet export wired per `docs/EXPORT.md`
- [ ] Status shown by color *and* text; exactly one primary action per surface
## Fonts & licensing
IBM Plex Sans and IBM Plex Mono are bundled under the SIL Open Font License
(`cim_suite/core/ui/theme/fonts/OFL-IBMPlex.txt`) and registered at startup via
`QFontDatabase`; if the assets are missing, the theme degrades to system fonts
rather than failing.