Skip to content

[0.2.1] Define and implement the Loupe UI design system #194

Description

@mberrys

[0.0.1B] Define and implement the Loop UI design system

Objective

Create a consistent visual and interaction system for the new shell and future production workflows.

Why this is not cosmetic

One thing here is correctness, not taste: severity mapping. loupe-preflight emits three severities — error, warning, info (profile.schema.json:69) — plus check statuses that are neither pass nor fail (not_inspected, budget-exceeded, not_applicable). If each surface invents its own colours, an incomplete check will eventually render as a clean pass somewhere. #133 refuses to certify in that state; the UI must not contradict it.

So the design system's load-bearing deliverable is a canonical state mapping, and everything else is consistency work.

Step-by-step

1. Tokens

Pdf4QtLibGui/loupe/loupetokens.h plus a documented table in docs/LOUPE_DESIGN_SYSTEM.md. Tokens are semantic, never raw colour names at call sites:

namespace loupe::tokens
{
    // Spacing — 4px base grid.
    inline constexpr int SpaceXs = 4, SpaceS = 8, SpaceM = 12, SpaceL = 16, SpaceXl = 24;

    // Semantic roles resolve per theme; call sites never hardcode a colour.
    enum class ColorRole
    {
        SurfaceBase, SurfacePanel, SurfaceOverlay,
        TextPrimary, TextSecondary, TextDisabled,
        SeverityError, SeverityWarning, SeverityInfo,
        StateIncomplete,      // NOT a severity — see below
        StateNotChecked,
        FocusRing, DestructiveAction
    };

    QColor color(ColorRole role, LoupeTheme theme);
}

2. The canonical state mapping

One table, one implementation, consumed by finding cards, the report dock, canvas overlays, the Inspector (#127), and the status bar:

State Source Colour role Icon Never
Error severity: "error" SeverityError filled circle
Warning severity: "warning" SeverityWarning filled triangle
Info severity: "info" SeverityInfo filled square
Incomplete PreflightCheckStatus.status != ok StateIncomplete hatched never green, never a checkmark
Not checked no run for this revision StateNotChecked outline never green
Passed run complete, no findings success checkmark
Waived active decision (#126) warning + badge badge overlay never the passed treatment
/// Single source of truth for finding/check presentation.
/// Every surface calls this; none derives its own colour.
LoupeStateVisual resolveStateVisual(const PreflightFinding* finding,
                                    const PreflightCheckStatus* status,
                                    const PreflightDecision* decision);

The two rules worth enforcing in code review: incomplete never renders as success, and waived never renders as passed. Both are the visual form of guarantees #133 and #126 make in data.

Shape and hatching carry the distinction alongside colour, so the mapping survives colour-blindness and greyscale printing — required by the #25 baseline anyway.

3. Components

Reusable implementations, each with a fixture exercising every state:

Component States
Finding card error / warning / info / incomplete / waived / selected / focused
Inspector row label+value / empty / truncated / copyable
Object overlay selection / finding highlight / guide / measurement
Progress indeterminate / determinate / cancellable / cancelling
Empty state not checked / no findings / no document / restriction excluded all
Error state recoverable / unrecoverable / config error (#128)
Destructive confirm requires explicit confirm; never a default button

The "restriction excluded all" empty state matters: with #125, an operator can scope a profile to nothing and get zero findings. That must read as nothing was inspected, not as clean.

4. Theme and high-DPI

  • Light and dark defined together; every role has both values, contrast-checked.
  • Icons as SVG so they scale — the fork already requires Qt ≥ 6.11.1 for Qt SVG security fixes (AGENTS.md), so SVG is a settled dependency.
  • Verify at 100% / 150% / 200% scaling.

5. Adoption

New Loupe surfaces (#193, #195, #196, #127) use the system. Inherited PDF4QT dialogs are adapted, hidden, or explicitly deferred — tracked in the #191 manifest rather than left ambiguous. Do not restyle inherited dialogs that ADR-005 may stop shipping; that is wasted work.

Acceptance criteria

Test strategy

  • State-mapping test: table-driven over every (severity, check status, decision) combination; assert no incomplete or waived combination resolves to the success visual. This is the highest-value test here.
  • Contrast: automated WCAG contrast check over every token pair in both themes.
  • Colour-independence: render the state set greyscale; assert every state remains distinguishable.
  • Visual regression: a fixture page rendering every component in every state, captured at 100%/150%/200% in both themes, diffed against committed baselines — reuse the golden-image approach from UnitTests/tst_overprintrendertest.cpp.
  • Keyboard: focus traversal reaches every interactive element with a visible focus ring.

Dependencies

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions