Screenshotting the pages under dark mode and compact density (rather than
trusting that design tokens were enough) turned up two mistakes repeated across
every page this feature set added:
- `.card` carries only the surface, border and radius — padding comes from the
separate `.card-pad`. Fifteen cards were rendering with their content flush
against the edges.
- `.field` *is* the input box, a 40px-tall flex row. Wrapping a label plus a
control in it produced a joined addon rather than a label above its field.
`.field-block` is the label-above layout, and thirty-seven wrappers now use it.
Both were invisible to type-checking and to the tests, which is exactly why the
visual pass was worth running. Numbers in the new UI now go through
formatNumber so they render as Persian digits, and the utilization page's
header no longer repeats the sentence that appears under its filters verbatim.
The QA driver gained a `--ui` flag: theme and density live in
localStorage['clinicpro-ui'], so without seeding them dark mode and compact
density cannot be screenshotted at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Task 09 shipped a powerful API that a non-technical clinic owner could not
safely use. This closes that gap: activation now requires having seen what the
rule actually does.
- PolicySimulator runs a policy against real past appointments and writes
nothing: evaluation works on facts (never entities), the whole run sits in a
transaction rolled back and cleared in `finally`, and a test counts rows in
five sensitive tables before and after
- activate() now demands a simulation of the *same version* — a report for
version 1 does not unlock version 2
- PolicyTemplateRegistry: six ready-made rules, so the common case never
touches a raw condition
- Severity from the affected ratio; 0% is a warning too, since a rule that
changes nothing usually has a condition that never matches
- An empty clinic still succeeds with a warning, otherwise a new clinic could
never activate anything
Admin: PoliciesPage, PolicyFormPage, PolicySimulationPage, and a
PolicyConditionBuilder built entirely from GET /policy-schema — a test proves a
field that exists only in the schema shows up with no frontend change, and that
operators are filtered per field type.
The schema response now carries per-field metadata (label, type, meaningful
operators) so the form has one source of truth instead of two.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>