7.0 KiB
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. 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
setStyleSheetoutsidecim_suite/core/ui/theme/—tests/core/test_design_discipline.pyfails the build otherwise. - Fonts come from
type_styles, never ad-hocQFont(...)— microcaps tracking and the Plex families only exist there (Qt ignorestext-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.ItemIsEditableis the single source of editability. When repopulating a table from the model, run under the tab's_loadingguard so programmatic fills don't fire phantom edits (write-storm protection — regression-tested). - Geometry/spacing from
Space/Radius/Metricstokens, 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(emitsuiteRequested; 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 viaset_column_units, header help viaset_column_help(tooltips — never ⓘ markers) - Status columns use §1.2 status tags (
STATUS_ROLE), full-row alarm treatment viaALARM_ROW_ROLEfor alarm-class states only - Summary strip with
kit.EDIT_HINTon 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_pendingon edit;resolveon 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 readtheme.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.