Senior DS review — implementation on main (17 Oct 2026)

Method: 6 auditors (Sonnet) over origin/main mirror, 3 adversarial verifiers (Opus) re-measured every finding. Refuted findings are listed but struck. Counts are the verifier-corrected ones.

A — Scoped overrides of DS primitives

Parsed every rule in 100+ CSS files under apps/web/src (selectors split on commas, comments stripped). 1132 selectors mention .acrm-*; 675 of them are scoped (ancestor + .acrm-*). Excluding files that own the DS primitives (ds.css, ds/button.css, dialog-window.css, chart.css, inputs.css, responsive.css base layer, etc.), 140 rules in about 50 files restyle or reposition a DS primitive from a page/feature scope: 69 layout-only (flex/width/margin/order) and 71 look-changing (height, padding, font, border, bg, radius, colour). Button/IconButton alone: 66 scoped rules in 29 files, 25 of them resize it. Beyond .acrm-*: 79 rules re-style ItemList (.il-*) from 10 files, 11 rules re-style SectionTopBar (.tbar), 8 re-style RecordRail (.rr-*). !important is rare (28 in the whole app, only 6 declarations on DS-class rules in 3 places); the topbar/tabs/pill/popover primitives are almost never overridden from page scopes (clean). Sanctioned and skipped: .pipe-btn and .toast-btn (registry: SectionTopBar identity and Toast are documented non-Button primitives), .nt-bub-url / .sgb-th-edit inline inputs (registry input notes, AD 28 Sep 2026), app-shell bell 32 px = --field-h (documented exception), .acrm-split and .acrm-btn .acrm-kbd inside ds/button.css (DS-owned), dark-theme overrides in ds.css/inputs.css (DS-owned), reduced-motion.css !important (ds-ok, AD 27 Sep 2026), ScrollArea/rich-editor-dark !important (documented inline-style fights).

idverdictimpactfixnfindingcanonical
A1partlyhighvisual10Buttons given non-canonical height/padding/font/radius inside page scopes<Button> default (--control-h) or compact (24px); full width via width:100% on a wrapper; for field-aligned buttons add a DS size/field prop instead of a scoped height
A2partlyhighvisual8Local 44px touch-height rules on Button/IconButton instead of the central tap-target ruleDelete the local rules and rely on the §19 ::after target in responsive.css; where a footer needs full-width CTAs use <Button touch> / width:100% only
A3partlyhighdecision25ItemList re-gridded and re-spaced from feature scopesItemList slots/props (tags, cols, meta, density/'lg' variant, embedded) extended in packages/ui; no .il-* restyling from page scopes
A4partlymediummechanical69Parent scopes lay out DS primitives (flex/width/margin/order) instead of a layout wrapperOne layout helper per pattern (a DS actions row / .acrm-actions with auto-margin end slot, or a className prop on the component styled by the wrapper) so scopes stop selecting .acrm-* descendants
A5partlyhighvisual14Chromeless 'inline-edit' look rebuilt on Input / Select / Textarea per scopeDS inline/bare variant on Input, Textarea and Select trigger (one shape: transparent, hover --hover-surface, focus ring), replacing the scoped copies
A6partlymediumdecision8Ghost buttons recoloured by scoped selectors and invented modifier classes<Button variant="ghost" tone="muted"> for the muted three (mechanical); decide an accent/positive tone for later-chip and unlocked; drop custom hover fills
A7partlymediumdecision9Dialog shell restyled per feature (radius, shadow, background, width, animation)Dialog props (size=l flexi, bare/page-embedded, full-screen state) in @acrm/ui so features pass a prop; AD must define the 'dialog as page / full-screen' state first
A8confirmedlowmechanical8Dead legacy .pdr-sec .pill* palette in record-drawer.css (duplicate of .acrm-pill tones).acrm-pill with the DS tone classes (lime/blue/purple/green/amber/grey); delete the .pdr-sec .pill rules
A9confirmedmediumdecision7Avatar sized by scope instead of Avatar sizes<Avatar size> ladder extended (xs 22, xl 96) plus an AvatarStack for overlapping faces
A10partlylowvisual3Pill restyled in a column header and note text recoloured by scopeCount badge = .acrm-count (neutral/accent); note text = DS Note with its tone, truncation via the DS ellipsis utility
A11partlylowmechanical4Un-prefixed legacy .icobtn defined globally in segment-screen.css and app-shell.css<IconButton> (.acrm-icobtn); keep the single registered 32px top-bar size as a DS prop, delete the global .icobtn copy
A12confirmedlowmechanical6!important on DS-class rules (popover in-sheet, formless body, phone-value width)Raise specificity or stop writing inline px from the component (Popover inSheet position via CSS vars; FormlessBody height via class); remove !important

Verifier notes:

  • A1 The evidence is real: record-drawer.css:329 sets 34px/13px/radius-md, vyvoj-schvaleni.css:105 sets control-h+sp-1 and :194 sets tap-min + 600, and stage-move.css:81 is also real. Four of the eight examples should not count. inputs.css:172 .acrm-fieldrow > .acrm-btn is the DS field-row component's own rule (the button lines up with the field), not a page scope. import.css:567 is a wrap fix (height:auto + min-height control-h). The tap-height on record-rail.css:1186 and the tl-sugact rule is registry-sanctioned (TaskList notes, acrm !4069: suggestion actions 44px on touch) and also duplicates A2. bulk-bar.css:89 only trims padding-inline on mobile. .pdr-foot belongs to DrawerForm, which is deprecated in the registry. The fix there is to retire the drawer, not to resize its buttons. The count of 25 cannot be reproduced. About 10 real page-scope resizes remain. Risk: Removing the vapr-act height breaks a 1:1 approved prototype (code comment). Removing the fieldrow height misaligns the button with the field.
  • A2 About half the examples are central DS rules, not local overrides. responsive.css:451/462/626/633 are the global dialog-footer mobile rules that carry the binding mobile-overlay rule (full-width CTA, 44px), the same contract as <Button touch> in button.css:82. The tl-sugact 44px rule (task-list.css:440, responsive.css:976) is written into the registry TaskList notes (acrm !4069). Scoped copies that really conflict with §19.3 remain: chat-visibility.css:247 (.chv-selbottom), message-reply.css:231 (.mrp-bar), vyvoj-schvaleni.css:194, stage-move.css:82, record-rail.css:1187 and note-dialog.css:280/287. Those should become <Button touch> or rely on ::after. Risk: Deleting the dialog-footer rules would remove the sanctioned full-width 44px mobile CTA in every dialog.
  • A3 The timeline part (27 rules, timeline.css:186/227) is sanctioned. The registry entry 'timeline' says every row is an ItemList row and that timeline.css adds the axis, groups and event cells and must load after item-list.css. The .eml-table .il-row grid rebuild in komunikace.css (8 top-level .il- rules, 49 .il mentions) is real and has no registry entry. So are the small project-tasks, task-detail and delegace tweaks. Re-measured unsanctioned set: about 25 rules, not 79. Risk: Moving the eml-table grid into ItemList props changes the mail row layout on phones (visual). The timeline rules must stay.
  • A4 The examples are real: auth.css:249 (flex:1), note-card.css:52 and 7 Button style marginLeft:auto (KpiSection.tsx:104, VehicleDetailPage.tsx:135/288, TeamDetailPage.tsx:328, BenefitSection.tsx:133 and others). Most of these are ordinary parent-owned layout (flex, gap, margin) and not overrides of a DS primitive's look, so the impact is overstated. The count of 69 was not reproducible in this pass. Fixing it mechanically only works once a shared actions-row helper exists in the DS. Risk: Low. An auto-margin helper must keep the order; otherwise the ghost action drops back to the left.
  • A5 The scoped copies are real (person-record.css:1213 .itm-num/.itm-note input.acrm-input with border 0, height auto and its own padding). However, ds/inline-edit.css is not a scoped copy. It is the shared inline variant (.inl-in/.inl-ta on <Input>/<Textarea>), decided by the owner on 26 Aug (P408/P412/P415, documented in the file header). So the canon partly exists already. The fix is to move the scoped copies onto .inl-in/.inl-ta and register it, which is less of a new decision. inline-edit.css should not count as a finding site. Risk: The .inl-* look has no focus ring by owner decision (P415). Unifying it with the 'focus ring' shape the auditor proposed would contradict that decision.
  • A6 The evidence holds: person-record.css:146 (.acrm-btn.ghost.pr-lists-add, colour fg-secondary), voice-message.css:113/116/119 and later-menu.css:7. Button has tone='muted' (Button.tsx:11), so pr-lists-add and .vmx are mechanical candidates. later-chip comes from an approved prototype (proto/pozdeji-jedna-ikona, 14 Oct, code comment). It is an approved visual and only lacks a DS tone. .pdr-foot .unlocked has no emitter in TSX ('unlocked' appears only as keyStatus), so it is probably dead CSS. Risk: Swapping to tone=muted can shift the hover colour (pr-lists-add hovers to ink-700). Check the computed styles first.
  • A7 Two examples are registry canon. .acrm-dialog.deal.pick and .gsp-body are listed classes of the canon dialog-morph and command-palette entries (the palette is full-screen on phones by design), so deal-dialog.css:44 and global-search.css:545 are sanctioned. compose-page.css:52, mail.css:965, required-actions.css:2 and the formless/avail body tweaks are real per-feature shell rewrites. Risk: Removing the gsp-body override breaks the approved full-screen palette on phones.
  • A10 vyvoj-board.css:53 really turns a pill into a count badge (height, min-width, 600 14px, dot hidden), and .acrm-count exists. ai-chat.css:364 recolours a note. contract.css:171 .sml-title runs on private --sml-* tokens of the contract document, which is a self-contained printable layout, so treating it as a DS deviation is weak. Risk: Changing the contract typography changes the printed contract output.
  • A11 The global .icobtn in segment-screen.css:53/64/67 and the .shd-row > .icobtn rule at :1693 duplicate an exception. Per the registry IconButton notes (wave 16, !3997), the segment .icobtn sites were migrated to framed, and the only remaining emitter is NotifBell.tsx:443 inside .as-util. Its look is already fully defined by app-shell.css:217, a registry-sanctioned exception. So the 4 segment-screen rules are dead or redundant and can be deleted. NotifBell and app-shell are not findings. Risk: The global rule also matches the bell. .as-util .icobtn declares the same properties, so deleting it is a no-op, but verify the bell after deleting.

Examples:

  • A1 apps/web/src/ds/record-drawer.css:329, apps/web/src/ds/vyvoj-schvaleni.css:105, apps/web/src/ds/vyvoj-schvaleni.css:194, apps/web/src/ds/stage-move.css:81, apps/web/src/ds/import.css:567, apps/web/src/ds/inputs.css:172, apps/web/src/components/bulk/bulk-bar.css:89, apps/web/src/ds/record-rail.css:1186
  • A2 apps/web/src/ds/responsive.css:451, apps/web/src/ds/responsive.css:462, apps/web/src/ds/responsive.css:626, apps/web/src/ds/responsive.css:633, apps/web/src/ds/task-list.css:440, apps/web/src/ds/responsive.css:976, apps/web/src/pages/komunikace/chat-visibility.css:247, apps/web/src/pages/komunikace/message-reply.css:231
  • A3 apps/web/src/pages/komunikace/komunikace.css:575, apps/web/src/pages/komunikace/komunikace.css:620, apps/web/src/pages/komunikace/komunikace.css:659, apps/web/src/ds/timeline.css:186, apps/web/src/ds/timeline.css:227, apps/web/src/ds/project-tasks.css:257, apps/web/src/ds/task-detail.css:300, apps/web/src/pages/komunikace/delegace/delegace.css:210
  • A4 apps/web/src/ds/auth.css:249, apps/web/src/ds/note-card.css:52, apps/web/src/ds/communication-rules.css:89, apps/web/src/ds/settings.css:490, apps/web/src/ds/builder-panels.css:18, apps/web/src/ds/segment-screen.css:1702, apps/web/src/pages/komunikace/delegace/delegace.css:147, apps/web/src/components/clen-tymu/KpiSection.tsx:104
  • A5 apps/web/src/ds/person-record.css:1213, apps/web/src/ds/inputs.css:380, apps/web/src/ds/share-dialog-formless.css:40, apps/web/src/pages/komunikace/message-page.css:303, apps/web/src/ds/project-milestones.css:111, apps/web/src/ds/task-detail.css:57, apps/web/src/ds/project-overview.css:12, apps/web/src/ds/inline-edit.css:16
  • A6 apps/web/src/ds/person-record.css:146, apps/web/src/ds/voice-message.css:113, apps/web/src/pages/komunikace/message-page.css:375, apps/web/src/ds/later-menu.css:7, apps/web/src/ds/record-drawer.css:339, apps/web/src/ds/voice-message.css:116, apps/web/src/ds/voice-message.css:119
  • A7 apps/web/src/pages/komunikace/compose-page.css:52, apps/web/src/components/mail/mail.css:965, apps/web/src/components/required-actions/required-actions.css:2, apps/web/src/ds/deal-dialog.css:44, apps/web/src/ds/global-search.css:545, apps/web/src/ds/deal-dialog.css:17, apps/web/src/ds/dialog-formless.css:19, apps/web/src/ds/delete-user-dialog.css:6
  • A8 apps/web/src/ds/record-drawer.css:354, apps/web/src/ds/record-drawer.css:360, apps/web/src/ds/record-drawer.css:376, apps/web/src/ds/record-drawer.css:381, apps/web/src/ds/record-drawer.css:391, apps/web/src/ds/record-rail.css:508
  • A9 apps/web/src/components/OrgLogo.css:86, apps/web/src/ds.css:852, apps/web/src/ds.css:859, apps/web/src/ds/inputs.css:595, apps/web/src/pages/komunikace/delegace/delegace.css:198, apps/web/src/ds/project-tasks.css:145
  • A10 apps/web/src/ds/vyvoj-board.css:53, apps/web/src/ds/vyvoj-board.css:60, apps/web/src/ds/ai-chat.css:364, apps/web/src/ds/ai-chat.css:630, apps/web/src/ds/contract.css:171
  • A11 apps/web/src/ds/segment-screen.css:53, apps/web/src/ds/segment-screen.css:64, apps/web/src/ds/segment-screen.css:1693, apps/web/src/ds/app-shell.css:217, apps/web/src/components/notifications/NotifBell.tsx:443
  • A12 apps/web/src/ds/responsive.css:763, apps/web/src/ds/responsive.css:601, apps/web/src/ds/dialog-formless.css:931

B — Re-implemented primitives

validate-ds-usage.mjs: 2068 files, 0 HARD, 1 WARN (DraftReplyCard.tsx:247 button-loading-label-swap) — enforced primitives (Dialog/Select/Checkbox/Switch/EmptyState/Avatar/Loader) are well adopted (Dialog 151 uses, EmptyState 316, Checkbox 69 vs 2 raw, Switch 44, IconButton 236, role="tab" hand-rolls only 3 sites, raw <select> 1). Remaining divergence is concentrated in app-level CSS-class primitives with no React component. Scan of non-test TSX under apps/web/src: ~376 raw <button> in 176 files (~293 without className on the same line; multi-line approximations), 39 icon-only raw buttons with aria-label in 30 files, ~225 legacy-token class hits (chip/pill/tag/badge/btn), 115 raw <input> (31 as .fl-text), 32 raw <textarea> (24 as .fl-title), 15 className="a" link-buttons in 12 files, ~38 files using .tk-chip, 40 files using .fl-in, 5 hand-rolled drawer/lightbox/scrim overlays.

idverdictimpactfixnfindingcanonical
B1partlyhighdecision41Property chips (.tk-chip) hand-rolled as raw <button> across ~38 files, not an @acrm/ui componentOne @acrm/ui Chip/MetaChip (button, value + empty state, popover anchor) built on the existing MetaChip.tsx; register as chip family in registry.json; reduce .tk-chip CSS to the component.
B2partlyhighdecision23Four+ parallel chip/pill-button families (tk-chip, fb-chip, tchip, acrm-optchip, eref-chip) with no shared primitive@acrm/ui OptionChips/OptionChip component for .acrm-optchip (registry: option-chips), and AD decision on merging tk-chip/fb-chip/tchip into one Chip with variants (value, filter, option).
B3partlymediumvisual5Pill/chip look re-implemented in local CSS instead of .acrm-pill<span className="acrm-pill"> (pill family, with tone via existing --pill-*-bg/ink tokens) or @acrm/ui Pill/CountBadge; actions-as-chips (mrp-chip/dlr-chip) to Button size compact or Chip from B2.
B4partlyhighvisual39Raw <button> used as icon-only or text buttons instead of IconButton/Button@acrm/ui IconButton (.acrm-icobtn, sm/framed) for glyph-only; Button (ghost/compact) for text actions; exceptions only for row-shell/click-through cases noted in registry.
B5partlymediumdecision13Unscoped className="a" text-link buttons with no DS primitiveOne DS text-link: Button variant="link"/ghost compact or a .acrm-link class (needs AD canon), replacing .a / .au-textlink / .dup-link / .mr-reclink.
B6refutedhighdecision90~~Formless-dialog fields hand-rolled with raw <input>/<textarea>/<button> (.fl-text, .fl-title, .fl-in)~~@acrm/ui Input / Textarea with a formless variant (or Field bare mode) and Select/RecordSelect triggers for pickers; or register .fl-* as an official formless-field family with a React component.
B7partlymediumdecision7Hand-built overlays/scrims and drawers outside Dialog/BottomSheet/RecordDrawer@acrm/ui Dialog (lightbox variant), BottomSheet/Popover (multivalue, notif panel), RecordDrawer for .pdr panels, shared Scrim for shell overlays.
B8partlylowvisual1Hand-rolled tablists next to canonical TabStrip@acrm/ui TabStrip (.acrm-tabs/.acrm-tab) with icon support for AsistentPlochaPage; AD to decide whether eml-tabs / npanel-tabs stay as sanctioned variants or fold into TabStrip.
B9partlylowmechanical3Raw native checkbox/radio/select instead of Checkbox / Radio / Select@acrm/ui Checkbox with indeterminate; new @acrm/ui Radio/RadioGroup (or Segmented/OptionChips); Select borderless/quiet variant to replace .phe-type native select.
B10partlymediumdecision7Duplicate popover/picker implementations for "pick a record/person"@acrm/ui RecordSelect / PickerMenu / CheckMenu as the single picker stack; EntityPicker/ProductPicker as thin configs; Popover/BottomSheet instead of local createPortal.
B11refutedlowdecision6~~Tag display/editing split across TagPills, TagsField, TagsEditor vs @acrm/ui tag-input and .rr-tags~~Extend RRField.tags in packages/ui with edit mode using TagInput; drop TagPills/TagsEditor; single .acrm-tag for all tag chips.
B12partlylowvisual37Local empty-state / loading blocks instead of EmptyState / Loader@acrm/ui EmptyState (sm) for block-level empties; .acrm-popover-empty only inside menus; Loader / ListLoader / Skeleton for loading (docs/DS-USAGE.md loader rule); Button loading for button busy state (DraftReplyCard.tsx:247 is the sole validator WARN).

Verifier notes:

  • B1 Usage is real: 41 tk-chip hits in 27 TSX files, and MetaChip is used in 10 files. The registry calls .tk-chip 'canonical' (task-dialog suggestions note, attachments note: 'aligned with .tk-chip'), so this is canon CSS without a React component, not a reimplemented primitive. The finding is valid as 'component missing, wiring duplicated per site'. The count is 41 hits in 27 files, not '38 files'. Risk: Wrapping every site in one component can drop per-site popover anchoring or aria details. Each site needs a test.
  • B2 Each family is registered canon. .fb-chip is the filter-chip entry, .acrm-optchip is option-chips (canon, react []), .tchip appears among the registry classes, and .tk-chip is called canonical. So this is not a reimplementation. It is a decision to merge separate canon chips. What does hold: option-chips has no React component and 23 raw call sites. Re-measured: fb-chip in 9 files (not 13), tchip in 24 files (not 27), and acrm-optchip 23 hits. Risk: Merging filter, option and value chips into one component changes heights and radii across the filter bars (visual).
  • B3 Most of the examples are sanctioned. .pdr-pill is 'kept aside' in the registry pill notes. .au-chip (auth.css:446) carries ds-ok: pill-adhoc per RULES §9. .mrp-chip carries a ds-ok note for an approved prototype from 15 Oct (it is an action, not a label). .dg-chip is an avatar + text marker, not a pill look. dlr-chip, resv-chip, chv-tag, ev-label and mc-status remain. Risk: The remaining chips are partly actions (dlr-chip) that would become Button compact, not pills.
  • B4 The scale and method do not hold. There are 415 raw <button in 202 files, but the 'no className on the opening line' metric is meaningless: className usually sits on the next line in multi-line JSX. Many raw buttons are canon CSS buttons (.tk-chip, .fb-chip, .tl-addrow, .phe-x, which is a registry rail class) or registry exceptions. The example AppShell.tsx:434 .as-icobtn is the sanctioned shell exception, and RecLink is a text link. validate-ds-usage reports 0 HARD and 1 WARN. A real subset of glyph-only buttons exists, but swapping them for IconButton changes the box size and hover, so 'mechanical' is false. Risk: A mass swap breaks row-shell and click-through buttons, and changes 22–32px glyph boxes to IconButton sizes.
  • B5 Re-measured: 14 className="a" sites (not 15 in 12 files). Rules exist only under .sb-h (section-block.css:108) and .pr-tabsrow (person-record.css:214). SectionBlock.tsx:169 and probably the Person/CompanyRecordPage sites are inside those scopes, so they are styled canonically. The OpravneniOsoba, OpravneniRole, MinutesTemplates, CoachTab, SummaryTab, CrmChangesBlock, OrderDetail, ReadinessTable and DraftReplyCard sites fall outside them. dup-link is in 3 files, not 6. A text-link canon is missing, so this is a decision. Risk: Unstyled .a buttons currently inherit browser button chrome. Any fix is a visible change.
  • B6 The form-less dialog is a canon registry family (status canon). Its listed classes explicitly include .fl-in, .fl-text, .fl-title, textarea.fl-title, .fl-row and .fl-note, with React consumers NewMeetingForm, EditMeetingForm, TaskDialog and others. The notes call shape B (.fl-*) 'the documented engine'. The auditor's own alternative ('register .fl-* as an official family') already exists. This is the sanctioned DS pattern, not a reimplementation.
  • B7 .phe-scrim is a registry-listed class of the rail multi-value mobile sheet (MR !3539), so MultiValuePopover.tsx:157 and ApprovalComposer.tsx:108 are sanctioned. The hand-written <aside className="pdr"> drawers (DochazkaPage.tsx:540, RozpoctyPage.tsx:347, DialerPage.tsx:1657, AssetSuggestionsDrawer.tsx:72) are real. Their target is not RecordDrawer, because the .pdr/DrawerForm family is deprecated in favour of form-less dialogs. AttachmentLightbox and the AppShell scrims remain. Risk: Moving the .pdr panels onto Dialog changes the side-panel interaction into a modal. That needs a prototype first.
  • B8 Only AsistentPlochaPage.tsx:300 (.pl-tabs) is unsanctioned. .eml-tabs and .npanel-tabs are listed classes of canon registry entries. TabStrip is used in 16 files, not 7. The count is 1. Risk: TabStrip has no icon slot equivalent to .pl-tab yet. That may need a package change.
  • B9 The radio part is refuted. The registry 'radio' entry is canon CSS (react []) whose listed classes are .acrm-radio-input and .acrm-radio-dot, so a raw <input type=radio className=acrm-radio-input> is the canonical markup. The checkbox part holds: SegmentScreen.tsx:1136/1295 use raw acrm-check-input, while @acrm/ui Checkbox has an indeterminate prop (Checkbox.tsx:20). The phe-type native select is flagged as a violation in the registry picker audit, so it holds too. Risk: The SegmentScreen select-all sits in .sgb-ck with its own box styling. The Checkbox component may render a second box (visual).
  • B10 The numbers check out (12 files with createPortal, 8 with <Popover>). The registry picker audit, however, classifies several of these as 'composition — fine' (e.g. TagsEditor = RailPickerMenu multi), and MultiValuePopover's portal is the sanctioned mobile sheet. The finding is a vague architecture claim with no per-site proof of reimplemented keyboard or search logic. Risk: Consolidating the picker stack is L effort and touches keyboard and a11y behaviour in many dialogs.
  • B11 TagPills is a registered React emitter of the canon pill entry (registry: react [TagPills, StatusChip, ContactStatusChip]). The registry picker audit lists TagsEditor as 'RailPickerMenu multi — composition fine'. The TagsField hatch is self-documented. What remains is scattered tag classes, which are low-value and unmeasured.
  • B12 Re-measured: 38 local empty/loading className hits, not about 52. One of them, npanel-empty, is a canon registry class. The '43 hard-coded Načítání…' figure does not reproduce: 0 literal 'Načítání…' strings in TSX, 78 'Načít' substrings in total, mostly i18n or comments. validate-ds-usage confirms only DraftReplyCard.tsx:247 (1 WARN). Swapping these for EmptyState changes size and illustration, so it is visual, not mechanical. Risk: EmptyState sm is larger than the in-popover/in-row empty lines (fb-empty, phe-ccempty) and would reflow popovers.

Examples:

  • B1 apps/web/src/ds/task-dialog.css:131, apps/web/src/components/dialog/MetaChip.tsx:55, apps/web/src/components/costs/CostDialog.tsx:712, apps/web/src/components/evidence/NewDocDialog.tsx:479, apps/web/src/pages/MajetekPage.tsx:457, apps/web/src/pages/VozidlaPage.tsx, apps/web/src/pages/TelefonyPage.tsx, apps/web/src/components/RecordLinksChip.tsx:199
  • B2 apps/web/src/components/callwrapup/WrapupBand.tsx:43, apps/web/src/components/orders/NewOrderForm.tsx:562, apps/web/src/components/FilterBar.tsx:116, apps/web/src/components/communication/SubjectCommunicationTab.tsx:239, apps/web/src/components/activity/CategoryFilterChip.tsx:67, apps/web/src/components/segment/SegmentScreen.tsx:1923, apps/web/src/components/segment/TagFilter.tsx, apps/web/src/pages/PostaPage.tsx:510
  • B3 apps/web/src/ds/record-drawer.css:97, apps/web/src/components/drawer/RecordDrawer.tsx:381, apps/web/src/pages/DialerPage.tsx:1703, apps/web/src/ds/auth.css:447, apps/web/src/pages/komunikace/message-reply.css:204, apps/web/src/ds/dialer.css:225, apps/web/src/pages/komunikace/delegace/delegace.css:7, apps/web/src/ds/reservation.css:211
  • B4 apps/web/src/components/AppShell.tsx:434, apps/web/src/components/meetings/RecLink.tsx:15, apps/web/src/components/meetings/SummaryTab.tsx:100, apps/web/src/components/multivalue/MvRow.tsx:46, apps/web/src/components/mail/MailLabelPill.tsx:35, apps/web/src/components/assistant/AssistantChat.tsx:885, apps/web/src/components/projects/AttachmentLightbox.tsx:74, apps/web/src/components/mail/DraftReplyCard.tsx:247
  • B5 apps/web/src/pages/settings/OpravneniOsoba.tsx:187, apps/web/src/pages/settings/OpravneniRole.tsx:256, apps/web/src/pages/settings/MinutesTemplatesSection.tsx:88, apps/web/src/components/sales-reports/CoachTab.tsx:166, apps/web/src/components/meetings/SummaryTab.tsx:86, apps/web/src/components/meetings/CrmChangesBlock.tsx:87, apps/web/src/ds/auth.css:231, apps/web/src/components/org/NewOrgDialog.tsx:542
  • B6 apps/web/src/components/costs/CostDialog.tsx:471, apps/web/src/components/calendar/EditMeetingForm.tsx:793, apps/web/src/components/calendar/MeetingKindSelect.tsx:40, apps/web/src/components/meetings/NewMeetingForm.tsx:672, apps/web/src/components/evidence/NewDocDialog.tsx:341, apps/web/src/components/assistant/pinned-views/PinDialogs.tsx:57, apps/web/src/components/mail/MailFolderDialog.tsx:204, apps/web/src/ds/dialog-formless.css:151
  • B7 apps/web/src/components/projects/AttachmentLightbox.tsx:57, apps/web/src/components/projects/ApprovalComposer.tsx:108, apps/web/src/components/multivalue/MultiValuePopover.tsx:157, apps/web/src/components/AppShell.tsx:1482, apps/web/src/pages/DochazkaPage.tsx:540, apps/web/src/pages/RozpoctyPage.tsx:347, apps/web/src/pages/DialerPage.tsx:1657, apps/web/src/components/notifications/NotifBell.tsx:462
  • B8 apps/web/src/pages/AsistentPlochaPage.tsx:300, apps/web/src/pages/PostaPage.tsx:602, apps/web/src/components/notifications/NotifBell.tsx:305, apps/web/src/ds/asistent-plocha.css
  • B9 apps/web/src/components/segment/SegmentScreen.tsx:1136, apps/web/src/components/segment/SegmentScreen.tsx:1295, apps/web/src/components/callwrapup/FollowupMail.tsx:60, apps/web/src/components/clen-tymu/DeleteUserDialog.tsx:374, apps/web/src/components/multivalue/ChannelsPopoverEditor.tsx:654
  • B10 apps/web/src/components/dialog/ChipSearchMenu.tsx:97, apps/web/src/components/RecordLinksChip.tsx:199, apps/web/src/components/EntityPicker.tsx, apps/web/src/components/multivalue/MultiValuePopover.tsx:161, apps/web/src/components/multivalue/EntityRowsPopoverEditor.tsx:153, apps/web/src/components/MentionInput.tsx:681, apps/web/src/components/mail/MailLabelMenu.tsx
  • B11 apps/web/src/components/TagPills.tsx:1, apps/web/src/components/TagsField.tsx:1, apps/web/src/components/TagsEditor.tsx:1, apps/web/src/components/assistant/AssistantChat.tsx:885, apps/web/src/pages/AiZamestnanciPage.tsx:92, apps/web/src/pages/komunikace/chat-visibility.css:158
  • B12 apps/web/src/components/mail/DraftReplyCard.tsx:247, apps/web/src/components/FilterBar.tsx, apps/web/src/components/record/ProcessList.tsx:285, apps/web/src/components/multivalue/ChannelsPopoverEditor.tsx, apps/web/src/components/pipeline/BoardCardSettings.tsx:152
  • A13 (missed, medium, mechanical, n=60) Inline style={{}} tweaks on DS-class elements. JSX elements carrying a DS class get one-off inline geometry: style={{ marginLeft:'auto' }} on .acrm-pill badges in AppShell nav, marginTop on .acrm-field-label, display:grid+gap on .acrm-radio-group, alignItems on .acrm-radio, sizes on .acrm-avatar/.acrm-card. Counted by scanning opening tags with className containing acrm-* and style={{: acrm-field-label 14, acrm-pill 11, acrm-muted 11, acrm-radio* 8, acrm-field 4, acrm-avatar 3, others 9. Plus Select style={{minWidth:180}} ×2 and Input style={{flex:1}}. → Layout via the wrapper's gap (stack/row helpers) and DS props; .acrm-radio-group layout (vertical with gap) in inputs.css; no inline style on DS classes. apps/web/src/components/AppShell.tsx:770, apps/web/src/components/AppShell.tsx:774, apps/web/src/components/assistant/AssistantSettings.tsx:67, apps/web/src/components/assistant/AssistantSettings.tsx:128, apps/web/src/pages/NastaveniPage.tsx:2695
  • B13 (missed, low, mechanical, n=7) Radio canon has no React component, so sites hand-assemble it (and one fakes it with buttons). Registry 'radio' is canon but CSS-only (react: []). Every site rebuilds label + input.acrm-radio-input + .acrm-radio-dot + is-on, and the group layout is patched inline (style display:grid/gap, alignItems:flex-start) in NastaveniPage and SouhlasPripojeniPage. ChatVisibilitySection renders a role=radio <button className="chv-opt"> copy that reuses .acrm-radio-dot. CompanyMergeTable, FollowupMail, DeleteUserDialog and AccessPresets each wire it again. → @acrm/ui RadioGroup/Radio (label + description slot, vertical gap) emitting the canon .acrm-radio* classes; chv-opt moved onto it. apps/web/src/pages/NastaveniPage.tsx:2695, apps/web/src/pages/SouhlasPripojeniPage.tsx:142, apps/web/src/pages/settings/AccessPresets.tsx:71, apps/web/src/pages/komunikace/ChatVisibilitySection.tsx:73, apps/web/src/components/callwrapup/FollowupMail.tsx:56

C - Variant sprawl inside the DS itself

Measured on /root/acrm-main (origin/main) with a JSX tag scanner over apps/web/src *.tsx (tests and stories excluded). Button 825 call sites: primary 253, default 286, ghost 205, danger 13, 10 dynamic. Local chip/pill/tag class families: 14 (tk-chip, fb-chip, dg-chip, cd-chip, mrp-chip, aichat-chip, later-chip, chv-tag, pdr-pill, nd-rec-chip, nd-magic-chip, rr-tag-more, aichat-prompt-tag, sml-esign-tag) next to the canonical .acrm-pill (83 className refs). Deprecated registry entries still live: drawer-form 1 call site, popover 12 importing files and 3 useAnchoredMenu sites, Dialog numeric width prop 9 sites. Tone vocabularies: 6 different sets. Props with 0-1 call sites: 9. Legacy .btn is gone (0 TSX uses), so there is no finding there. The validate-ds scripts were not run.

idverdictimpactfixnfindingcanonical
C1partlyhighdecision24Chip / pill / tag: 14 local label-chip families next to canonical .acrm-pillStatus or label becomes .acrm-pill [sm] <tone> (TagPills / StatusChip). A control that opens a menu becomes .tchip. A removable value becomes .acrm-tag / filter-chip. The AD must first decide whether tk-chip and fb-chip are one 'meta-chip' canon or two.
C2partlyhighdecision38.tk-chip meta-chip has no registry entry and is spread over 8 CSS filesOne MetaChip component (the existing components/dialog/MetaChip.tsx is the seed) with a single CSS home in dialog-formless.css. The AD should either list it in the registry or fold it into .tchip.
C3partlyhighdecision8Popover (deprecated, internal layer) is still composed directly in 12 files, and its frame is overridden in about 25 CSS filesWrap the remaining direct Popover users in MenuButton / PickerMenu / PickerPanel / MenuPanel. Move the per-screen frame overrides into modifier props or classes in one popover.css. The AD should decide whether the registry entry stays 'deprecated' or becomes 'internal'.
C4confirmedhighdecision7Six unrelated tone vocabularies across DS componentsOne Tone = neutral / accent / positive / warning / critical type in packages/ui, mapped onto the pill and --ic-* tokens. Meter, Progress, Segmented, DealCard and RecordRail dot adopt it, with the old values kept as aliases for one release.
C5partlymediumdecision4Props and variants with 0-1 call sites in apps/webRemove the dead values (Segmented tone, EmptyState ghost, Flag lg, MenuButton primary/default), or fold them into defaults. For 1-site values, the AD decides: promote to canon with a story, or delete.
C6confirmedmediumdecision6Size vocabulary is inconsistent: size='sm', compact boolean, inline/sm/lg and s/m/lA shared Size type, size='sm'/'md'/'lg', with compact kept as a deprecated alias, implemented through the --control-h / --control-h-sm tokens.
C7partlymediumvisual3Button primary/default used as a selected-state toggle<Segmented> for exclusive choice and option-chips for multi-select. A Button keeps primary for one CTA per view.
C8partlymediummechanical7Deprecated numeric Dialog width prop still used, and size 'l' sits outside the size mapsize='s'/'m'/'l'/'xl' with 1200 and 1020 as named sizes. The command palette gets its own 'palette' size or a dedicated component.
C9partlymediumvisual26Button CSS has two homes and 21 CSS files that restyle .acrm-btnOne button.css holding the base, variants and responsive rules. Screen-specific overrides become props (compact, iconOnly) or layout wrappers. The audit of the 21 overrides needs the AD to approve which are legitimate.
C10partlymediumvisual1DrawerForm (deprecated) has one remaining call site; the .pdr-* forks sit beside the canonical RecordDrawerMove AiZamestnanciPage to the form-less dialog (like 'Nový kontakt'). Then delete DrawerForm / DrawerField from packages/ui and the registry entry. Move or rename the remaining .pdr-* to .rd-* under record-drawer.
C11refutedmediumdecision5~~Avatar / UserAvatar / EntityMark / OrgLogo / PersonPhoto: overlapping identity-mark components~~One Avatar with kind='person'/'user'/'entity' (UserAvatar and EntityMark become thin aliases). CSS in a single avatar.css. Wrappers pass a typed Size.
C12refutedlowmechanical0~~Meter vs Progress vs StatTile bars: two CSS homes, a legacy class prefix, and four inconsistent bar implementations~~Rename the Meter classes to .acrm-meter. Make Progress the multi-step variant of Meter, or document why both exist. Replace the 12 raw fin-bar markups with <Meter>.
C13partlylowdecision9formless variant duplicated per input, with two parallel mask CSS familiesA single formless boolean (or a context set by Dialog formless) read by all masked inputs. One mask CSS family. Replace the native select with <Select>.

Verifier notes:

  • C1 Two of the listed 'local' families are sanctioned canon. .fb-chip is a registered class of the canon filter-chip entry ('Rendered by FilterBar.tsx'). .tk-chip is called 'kanonický .tk-chip' in RULES.md §11 and in registry notes. .pdr-pill is explicitly 'Kept aside' in the pill registry note. Once those are removed, the truly local shapes are dg-chip(6), cd-chip(3), mrp-chip(2), aichat-chip(3), later-chip(1), chv-tag(2), rr-tag-more(1), aichat-prompt-tag(3) and sml-esign-tag(3): about 24 TSX lines in 9 families, not 45 in 14. dg-chip does have a CSS home (1 file). Risk: If tk-chip/fb-chip are folded into .acrm-pill, the canonical morphing record chip (RULES §11) and the FilterBar chips change shape.
  • C2 The CSS spread is real: .tk-chip appears in 8 CSS files and 38 TSX lines. But RULES.md §11 names .tk-chip the canonical chip, and the registry notes (task quick-add, attachment 'aligned with .tk-chip') treat it as canon. The real gap is that no registry entry lists it under classes and it has no single CSS home. It is not an undocumented local variant. Risk: Consolidating its CSS from 8 files risks cascade-order changes (chip-morph, attachment alignment at 28px).
  • C3 Only 8 non-test files render <Popover> directly (FilterBar, AssignPicker, NotifBell, SegmentBuilder, ColorPicker, CheckMenu, BoardCardSettings, EntityPicker), not 12. The registry note says Popover is the overlay LAYER that menu components build on. FilterBar is the canon filter-chip's own implementation ('menu frame is the canonical Popover'). CheckMenu and ColorPicker carry ds-ok waivers. About 28 CSS files mention .acrm-popover; that count includes the frame's own home. Risk: Rewrapping FilterBar or EntityPicker can change focus, flip and portal behaviour inside modal dialogs (the global rule on overlays portalled into dialogs).
  • C5 These are confirmed at 0 call sites: Segmented tone, EmptyState variant, and MenuButton variant="primary". But MenuButton 'default' is the implicit default value, so it is not dead. Flag size 'lg' is consumed through PhoneFlag lg (PhoneLineDrawer:332), so it is not dead either. The rest of the list (Button tone=muted 8, formless 5+2+2, IconButton framed 35) are values that are actually used, not dead API. Risk: Removing Flag lg breaks PhoneFlag size="lg".
  • C7 Only 3 sites are genuine toggles: NastaveniPage:1808 (DAY_OPTIONS), SearchUsageSection:122 (WEEK_OPTIONS) and NastaveniPage:3150. NastaveniPage:3150 is an on/off toggle ('Zapnuto/Vypnuto'), so its canonical replacement is Switch, not Segmented. DelegationStrip:293, artefact:64, StageGateDialog:116 and PublicInvite:241 promote the recommended action to primary based on context. That is legitimate emphasis, not selection state. Risk: Converting emphasis-switching buttons to Segmented would remove the action affordance (reopen, accept).
  • C8 There are 7 <Dialog width=> sites: GlobalSearch:1745, PickPalette:110, EditMeetingForm:513, CompanyMergeTable:232, PlaudPickerDialog:187, SegmentBuilder:905 (452), TaskDialog:1785 (800). NewMeetingForm:423 has no width prop. Size 'l' is not 'outside the map' by mistake: it is the documented flex mode. Plaud's 560 equals the default and can simply be dropped. Adding named sizes at the same px is mechanical, but the palette width is viewport-dependent. Risk: The palette width depends on the phone viewport. A static size could break the 720px mobile palette.
  • C9 There are 28 CSS files that select .acrm-btn, so 26 outside ds.css and button.css. The count is understated, not overstated. The split base is documented in button.css's header as a size-ratchet workaround (ds.css is over 100 kB and may not grow), so the 'two homes' part is a known constraint rather than drift. The local overrides are real. Risk: Moving the base out of ds.css changes cascade order for every override. The responsive.css rules are mobile layout and must stay after it.
  • C10 There is 1 remaining <DrawerForm> site, at AiZamestnanciPage.tsx:239. However, .pdr/.pdr-top/.pdr-body/.pdr-foot are DrawerForm's own registered classes, and .pdr-pill is explicitly 'Kept aside' in the pill registry note. So the .pdr-* part of the finding is sanctioned or inherited, not a fork. Risk: Renaming .pdr-* while RecordDrawer and the Dialer still use them breaks those screens.
  • C11 The registry deliberately keeps three canon entries, with AD rationale. entity-mark 'Replaces seven parallel vocabularies'. user-avatar: 'The palette is deliberately not --ic-*', because colleagues must not look like siblings. avatar: screens render it 'through PersonPhoto / OrgLogo, never <Avatar photo> directly'. The wrappers are the sanctioned path. Merging them overrides explicit AD rulings. Risk: Folding user-avatar into avatar would apply the wrong palette to colleagues.
  • C12 The registry meter note (AD, 26 Sep 2026) explicitly sanctions Meter vs Progress as 'two legitimate elements, the same pair as HTML <meter> vs <progress>'. The claimed '12 raw fin-bar markups' do not exist: every fin-bar hit in TSX is a comment or a CSS import. WhyPanel already uses <Meter>. Renaming the class is not a mechanical cleanup of sprawl.
  • C13 The native <select> at ChannelsPopoverEditor:654 carries ds-ok with an AD sanction dated 2 Oct 2026 ('tichá buňka typu z DS karty Multi-value editor'). That part is refuted. The two mask CSS families (.acrm-mask* in masked/phone-input, .fl-mask* in masked-date-input) and the per-component variant prop are real, but both families are registered canon. Risk: Unifying mask CSS changes the ghost-template look in New contact/org dialogs. It needs visual baselines.

Examples:

  • C1 apps/web/src/pages/komunikace/delegace/DelegationChip.tsx:32, apps/web/src/pages/DialerPage.tsx:1703, apps/web/src/pages/komunikace/ChatPublishTag.tsx:22, apps/web/src/pages/CrmDashboardPage.tsx:396, apps/web/src/pages/komunikace/DraftPanel.tsx:418, apps/web/src/components/mail/LaterMenu.tsx:153, apps/web/src/components/assistant/entity-refs.tsx:190, apps/web/src/components/contract/ContractDocument.tsx:336
  • C2 apps/web/src/pages/MajetekPage.tsx:478, apps/web/src/pages/VozidlaPage.tsx:388, apps/web/src/pages/TelefonyPage.tsx:369, apps/web/src/components/costs/CostDialog.tsx:712, apps/web/src/components/org/NewOrgDialog.tsx:727, apps/web/src/components/person/NewContactDialog.tsx:761, apps/web/src/components/evidence/NewDocDialog.tsx:479, apps/web/src/components/dialog/MetaChip.tsx:48
  • C3 apps/web/src/components/FilterBar.tsx:1001, apps/web/src/components/CheckMenu.tsx:275, apps/web/src/components/color/ColorPicker.tsx:77, apps/web/src/components/notifications/NotifBell.tsx:457, apps/web/src/components/pipeline/BoardCardSettings.tsx:280, apps/web/src/components/mail/AssignPicker.tsx:41, apps/web/src/components/EntityPicker.tsx:216, apps/web/src/ds/inputs.css:1
  • C4 packages/ui/src/components/StatusBanner.tsx:11, packages/ui/src/components/Segmented.tsx:26, packages/ui/src/components/DealCard.tsx:39, packages/ui/src/components/Meter.tsx:51, packages/ui/src/components/Progress.tsx:34, packages/ui/src/components/RecordRail.tsx:138, packages/ui/src/components/Button.tsx:11
  • C5 packages/ui/src/components/Segmented.tsx:26, packages/ui/src/components/EmptyState.tsx:59, packages/ui/src/components/CountryFlag.tsx:191, packages/ui/src/components/MenuButton.tsx:139, packages/ui/src/components/DealCard.tsx:39, packages/ui/src/components/Progress.tsx:34, apps/web/src/pages/telefony/PhoneLineDrawer.tsx:332, apps/web/src/pages/komunikace/PozvankyList.tsx:447
  • C6 packages/ui/src/components/Button.tsx:34, packages/ui/src/components/IconButton.tsx:60, packages/ui/src/components/StatusBanner.tsx:11, packages/ui/src/components/Avatar.tsx:17, packages/ui/src/components/Dialog.tsx:127, packages/ui/src/components/CountryFlag.tsx:191
  • C7 apps/web/src/pages/NastaveniPage.tsx:1808, apps/web/src/pages/NastaveniPage.tsx:3150, apps/web/src/pages/settings/SearchUsageSection.tsx:122, apps/web/src/pages/komunikace/delegace/DelegationStrip.tsx:293, apps/web/src/components/assistant/artefact.tsx:64, apps/web/src/components/deal/StageGateDialog.tsx:116, apps/web/src/pages/PublicInvitePage.tsx:241
  • C8 apps/web/src/components/GlobalSearch.tsx:1745, apps/web/src/components/search/PickPalette.tsx:110, apps/web/src/components/calendar/EditMeetingForm.tsx:513, apps/web/src/components/meetings/NewMeetingForm.tsx:423, apps/web/src/components/company/CompanyMergeTable.tsx:232, apps/web/src/components/meetings/PlaudPickerDialog.tsx:187, packages/ui/src/components/Dialog.tsx:127
  • C9 apps/web/src/ds/responsive.css:1, apps/web/src/ds/person-record.css:1, apps/web/src/ds/vyvoj-schvaleni.css:1, apps/web/src/ds/record-drawer.css:1, apps/web/src/ds/auth.css:1, apps/web/src/ds/voice-message.css:1, apps/web/src/ds/button.css:1, apps/web/src/ds.css:1
  • C10 apps/web/src/pages/AiZamestnanciPage.tsx:239, packages/ui/src/index.ts:99, apps/web/src/ds/record-drawer.css:97, apps/web/src/pages/DialerPage.tsx:1703
  • C11 packages/ui/src/components/Avatar.tsx:17, packages/ui/src/components/UserAvatar.tsx:70, packages/ui/src/components/EntityMark.tsx:28, apps/web/src/components/OrgLogo.tsx:59, apps/web/src/components/person/PersonPhoto.tsx:59, apps/web/src/ds/entity-mark.css:1, apps/web/src/ds/meeting-record.css:1, apps/web/src/ds/project-tasks.css:1
  • C12 packages/ui/src/components/Meter.tsx:51, packages/ui/src/components/Progress.tsx:34, apps/web/src/ds/meter.css:1, apps/web/src/ds/progress.css:1, apps/web/src/pages/ProduktDetailPage.tsx:219, apps/web/src/components/StatTile.tsx:84
  • C13 packages/ui/src/components/AmountInput.tsx:69, packages/ui/src/components/PhoneInput.tsx:56, packages/ui/src/components/MaskedInput.tsx:36, apps/web/src/components/org/NewOrgDialog.tsx:323, apps/web/src/pages/TelefonyPage.tsx:278, apps/web/src/components/multivalue/ChannelsPopoverEditor.tsx:654

D — Tokens

validate-ds.mjs (apps/web/src + ds): 120 files, 0 HARD / 0 token / 0 colour / 0 frag / 0 unknown / 0 dialog, but ratcheted tiers carry 14 type, 269 size (px), 8 tone. Own grep, excluding ds-ok lines: ~515 px width/height decls (65 of them equal the icon scale 12-24, 37 equal control heights 24-37), 86 un-annotated z-index numbers (9 distinct scale values: 1-5, 58-60, 70-78, 80, 85, 90, 92, 101, 1000), 133 transition/animation durations as raw s/ms literals (0.12s x28, 0.14s x10, 120ms x4 ...), 156 color-mix() calls (43 with literal 16%, 24 with 72%), 24 identical focus rings 0 0 0 3px color-mix(accent 16%), 26 color:#fff (annotated in only 2), ~95 hex values in CSS (mostly commented brand/document kit), 199 numeric font-weight decls (600 x109, 500 x54, 400 x29, 700 x7), 42 outline-offset px literals, 18 font-size px literals, 26 padding/margin px literals (11 in mail.css). Inline style props: 1201 style={{}} sites in tsx; they are token-clean (gap/colour/font use var(--sp-*)/var(--fg-*)/var(--small), 0 inline hex, 0 inline zIndex), but ~157 contain raw numeric px (width/minWidth/maxWidth/margin/padding) and 11 string px, concentrated in PublicInvitePage.tsx, settings/Opravneni*.tsx, NewOrderForm.tsx. tokens.css has NO z-index, duration/easing, font-weight or focus-ring tokens.

idverdictimpactfixnfindingcanonical
D1confirmedhighdecision84No z-index scale: 86 raw layer numbers across ds CSSAdd semantic layer tokens (--z-sticky, --z-drawer, --z-dialog, --z-popover, --z-toast, --z-banner) to tokens.css and replace literals; registry/RULES to document the scale.
D2partlyhighdecision133No motion tokens: 133 raw durations/easings with drifting valuesIntroduce --dur-fast/--dur-base/--dur-slow and --ease-standard tokens, snap the nine near-duplicate values to 2-3 steps.
D3partlyhighmechanical102Raw width/height px that equal existing size tokens (icon, control, row)--ico-2xs..xl, --control-h, --control-h-sm, --field-h, --row-h, --row-h-nav, --avatar-*, --dot*, --pill-h*.
D4confirmedmediumdecision58Off-scale control sizes 26px and 34px repeated (no token)Snap to nearest control token (--control-h-sm/--control-h/--field-h) or define one named token if AD confirms a real 26/34 step.
D5partlyhighmechanical29Focus ring re-typed 24 times as raw box-shadow + color-mixAdd --focus-ring and --focus-ring-critical tokens (or a shared .acrm-focus-ring mixin class) and use them everywhere; keep outline-offset on a token.
D6confirmedmediummechanical26Literal #fff for text on coloured fills instead of --fg-on-accentcolor: var(--fg-on-accent) (or --ic-ink / tone-class ink for tinted tiles). Genuine exceptions (QR white, print ink) already carry ds-ok.
D7partlymediumdecision160No font-weight tokens; 199 numeric weights with 4 valuesDefine --fw-regular/--fw-medium/--fw-semibold/--fw-bold in tokens.css (or fold weight into font shorthand tokens) and migrate.
D8partlymediumdecision45Local per-component palettes redefine colours instead of using tokens/tone classesAvatar palette -> existing --gc-*/--ic-* identity tokens; gradients -> brand tokens; contract/doc kit keep local but reference neutrals via tokens or get ds-ok + registry note.
D9partlymediumvisual86Raw px font-size, padding/margin and outline-offset literalsfont: var(--small/--cell/--p/--section/--h2); padding/margin: var(--sp-*); outline-offset: var(--sp-0)/--sp-1 (or part of focus-ring token D5).
D10partlymediumvisual120Static inline style={{}} blocks with raw numeric sizesMove to DS layout classes / @acrm/ui props (Stack/Field width tokens, var(--sp-*)), keep inline only for dynamic values.
D11partlymediumdecision154Magic tint percentages in color-mix (156 calls, 16% and 72% dominate)Use existing --*-tint / --*-wash tokens; add --tint-hover/--tint-focus steps if a recurring percentage is a real semantic step.
D12partlylowvisual18Raw rgba()/shadow values outside the shadow tokensvar(--shadow-sm/md/pop) and var(--ov-scrim)/--overlay-shadow; sanctioned true-overlay values need ds-ok.
D13partlylowdecision117Local alias custom properties re-mapping tokens (--ev-*, --rail-bg, --gc/--gi)Keep indirection only for variant contracts; otherwise reference tokens directly. Review surface aliases (--rail-bg, --ph) for value-vs-meaning.

Verifier notes:

  • D2 Confirmed: 133 raw durations and no motion tokens. But some values are canon by rule. RULES specifies the dialog scrim fade at 120 ms and the rise+scale at 160 ms. RULES §11 makes the spring (packages/ui/src/lib/spring.ts) canonical for morphs and forbids custom curves. Tokens should encode those sanctioned values, not snap them freely. Risk: Snapping 0.12s/0.14s/0.15s to one step changes the feel. Reduced-motion.css relies on transitionend still firing.
  • D3 Spot checks hold (ds.css:145 16px, auth.css:103 32px, task-dialog.css:135 28px, notifications:159 30px, dialer:172 24px), and the tokens exist with those values. But equal value is not equal meaning. RULES §13 'size by meaning' means a 16px width that is not an icon must not become --ico-sm, and 28px is --row-h-nav (nav rows only). Each site needs semantic judgment, so this is not a blind mechanical swap. Risk: Binding unrelated elements to --row-h-nav or --ico-* couples them to future token changes.
  • D5 There are 29 0 0 0 Npx color-mix(...) ring declarations, and tokens.css only has --ring: var(--blue-500), with no composite ring token. But the 1px/2px variants (e.g. ds.css:2123 0 0 0 1px var(--accent)) are different rings. Merging them into one token changes pixels, so only the identical 3px/16% copies are mechanical. Risk: Folding the 1px/2px selection outlines into a 3px focus ring is a visual change.
  • D7 There are 160 numeric font-weight values in ds CSS (not 199) and 0 weight variables. RULES 'Váhy = 400 / 500 / 600, nic víc' sanctions those values, so numeric 400/500/600 complies with the rule. A missing token is a nice-to-have. The real defect is the 700 weights that RULES forbids (see missed D-new).
  • D8 The blocks exist (entity-mark.css:146 --uav-1:#5b1d99 etc., contract.css:22, home, document). But the user-avatar registry note says 'The palette is deliberately not --ic-*', so the proposed avatar palette → --gc-*/--ic-* replacement contradicts an AD ruling. Contract and document kits are plausibly sanctioned print kits. The home gradients remain unmarked. Risk: Remapping the avatar palette makes colleagues look like entity tones, which the AD rejected.
  • D9 Confirmed: 42 outline-offset px and 19 font-size px without ds-ok. But font-size tokens are font shorthands (--small/--p/--cell) that also set line-height and weight, and 11px or 15px have no equal step. Replacing them changes rendering, so the fix is visual, not mechanical. Risk: Using the font: shorthand resets line-height and weight on those elements.
  • D10 There are 1201 style={{}} sites. Inline numeric px for width/height/margin/padding/fontSize appears on about 120 lines (some are margin:0, which is harmless). The concentration is OpravneniOsoba 12, OpravneniFirma 11 (missing from the finding), PublicInvite 7, SadyOpravneniTab 5. Widths like 200/260/180 have no token, so the change needs new layout classes, not a mechanical move. Risk: Column widths in the permissions matrices shift.
  • D11 There are 154 color-mix calls; 16% (43) and 72% (24) dominate. Of the cited tint tokens, only --accent-tint exists. --critical-tint does not exist in tokens.css, so the proposed 'use existing tint tokens' replacement covers only the accent. Most 16% hits are the focus ring and duplicate D5.
  • D12 There are 18 rgba() literals without ds-ok in app CSS (not 30). The proposed canonical --ov-scrim / --overlay-shadow are not DS tokens. They are declared in :root inside the screen file segment-screen.css:1005. date-picker:618 rgba(0,0,0,.32) also differs from --ov-scrim-dim (.3), so swapping it is visual. Risk: In dark mode, --ov-scrim is .48 versus .32 now, so the date-picker sheet scrim gets darker.
  • D13 There are 117 --x: var(--token) alias lines in ds CSS (not 129), with calendar.css at 28 as stated. The finding itself concedes that most are legitimate variant indirection, so the actionable subset is small and unmeasured.

Examples:

  • D1 apps/web/src/ds.css:1554, apps/web/src/ds.css:1829, apps/web/src/ds/responsive.css:707, apps/web/src/ds/responsive.css:740, apps/web/src/ds/reorder.css:35, apps/web/src/ds/date-picker.css:118, apps/web/src/ds/impersonation-banner.css:15, apps/web/src/ds/task-attachments.css:166
  • D2 apps/web/src/ds.css:429, apps/web/src/ds.css:836, apps/web/src/ds/record-rail.css, apps/web/src/ds/inputs.css, apps/web/src/ds/segment-screen.css, apps/web/src/ds/app-shell.css, apps/web/src/pages/komunikace/komunikace.css
  • D3 apps/web/src/ds.css:145, apps/web/src/ds.css:326, apps/web/src/ds/auth.css:103, apps/web/src/ds/task-list.css:595, apps/web/src/ds/task-dialog.css:135, apps/web/src/ds/notifications.css:159, apps/web/src/ds/dialer.css:172, apps/web/src/ds/inputs.css
  • D4 apps/web/src/ds/record-rail.css, apps/web/src/ds/record-drawer.css, apps/web/src/ds/segment-screen.css, apps/web/src/ds/import.css, apps/web/src/ds/date-picker.css, apps/web/src/ds/person-record.css
  • D5 apps/web/src/ds.css:41, apps/web/src/ds.css:63, apps/web/src/ds.css:141, apps/web/src/ds/dialog-formless.css:510, apps/web/src/ds.css:2123
  • D6 apps/web/src/ds/segment-screen.css:42, apps/web/src/ds/calendar.css:353, apps/web/src/ds/auth.css:107, apps/web/src/ds/settings.css:872, apps/web/src/ds/dialer.css:25, apps/web/src/ds/pipeline.css:71, apps/web/src/ds/inputs.css:815, apps/web/src/ds/import.css:76
  • D7 apps/web/src/ds.css, apps/web/src/ds/person-record.css, apps/web/src/ds/settings.css, apps/web/src/ds/inputs.css
  • D8 apps/web/src/ds/entity-mark.css:146, apps/web/src/ds/home.css:210, apps/web/src/ds/contract.css:24, apps/web/src/doc/document.css:18, apps/web/src/ds/contract.css:60
  • D9 apps/web/src/ds.css:490, apps/web/src/ds/date-picker.css:665, apps/web/src/ds/meeting-record.css:628, apps/web/src/ds/auth.css:583, apps/web/src/ds/record-drawer.css:94, apps/web/src/components/mail/mail.css, apps/web/src/ds/timeline.css
  • D10 apps/web/src/pages/PublicInvitePage.tsx, apps/web/src/pages/settings/OpravneniOsoba.tsx, apps/web/src/pages/settings/SadyOpravneniTab.tsx, apps/web/src/components/orders/NewOrderForm.tsx, apps/web/src/pages/PickPage.tsx, apps/web/src/pages/SdileniNahledPage.tsx
  • D11 apps/web/src/ds.css:41, apps/web/src/ds/dialer.css:397, apps/web/src/ds/coach-report.css:621, apps/web/src/ds/meeting-analysis.css:308
  • D12 apps/web/src/ds/date-picker.css:618, apps/web/src/ds/app-shell.css:836, apps/web/src/ds/dialer.css:24, apps/web/src/ds/dialer.css:397, apps/web/src/ds/coach-report.css:621, apps/web/src/ds/segment-screen.css
  • D13 apps/web/src/ds/calendar.css:460, apps/web/src/ds/calendar.css:533, apps/web/src/ds/builder-panels.css:80, apps/web/src/ds/timeline.css:190, apps/web/src/ds/timeline.css:481, apps/web/src/ds/timeline.css:819
  • D14 (missed, high, mechanical, n=8) Global :root design tokens declared in screen CSS files instead of tokens.css. :root / [data-theme='dark'] token blocks live outside packages/ui tokens.css. segment-screen.css:1005 defines the app-wide --overlay-shadow, --ov-scrim and --ov-scrim-dim, which ds.css:1563 and responsive.css:108/741 consume. Similar global token blocks sit in skeleton.css:15, contract.css:22, impersonation-banner.css:6, entity-mark.css:144, item-list.css:32 and komunikace.css:474. The global scrim and shadow therefore depend on a screen stylesheet. The Foundations page (built from tokens.css) does not show them. → Move shared overlay tokens (--overlay-shadow, --ov-scrim, --ov-scrim-dim, skeleton) into packages/ui/src/tokens/tokens.css with light/dark values. Component-private palettes stay scoped to their component selector rather than :root.. apps/web/src/ds/segment-screen.css:1005, apps/web/src/ds/segment-screen.css:1012, apps/web/src/ds.css:1563, apps/web/src/ds/responsive.css:741, apps/web/src/ds/skeleton.css:15
  • D15 (missed, medium, visual, n=10) Hard RULES typography violations: font-weight 700 and font-size above 16px. RULES.md typography says 'Váhy = 400 / 500 / 600, nic víc ... váhy 700+ se nepoužívají' and 'Max 16px'. On main, 6 declarations use font-weight:700 without ds-ok: contract.css:258/367/436, coach-report.css:458, person-record.css:331, mail.css:1104. 4 font-size values sit at 17–22px: auth.css:583 22px, auth.css:603 18px, mail.css:1109 20px, mail.css:1113 17px. These break explicit rules rather than lacking a token. Contract print output may deserve a ds-ok decision. → Use font-weight 600 (or the --section font token) and type sizes up to 16px (--h1..--h3, --p). Add ds-ok with the AD's reason only for print or contract output.. apps/web/src/ds/person-record.css:331, apps/web/src/ds/coach-report.css:458, apps/web/src/components/mail/mail.css:1104, apps/web/src/components/mail/mail.css:1109, apps/web/src/ds/auth.css:583

E — RULES.md compliance sweep (§14/§24 explanatory text, §15 warnings/brown, More trigger, §22 status pill, §26 action visibility)

Scanned apps/web/src + packages/ui/src (excluding tests/stories). validate-ds.mjs: 0 HARD/token/color violations (ratchet tiers only: 14 type, 269 size, 8 tone). alert-triangle: 0 app usages (only registered glyph in ds-icons.tsx:28, guarded by contract test). Brown tokens (--warning-ink, --pill-amber-ink, --warning-subtle, --pill-amber-bg) used as text/surface in ~14 rule blocks in 10 CSS files. 3 horizontal-dots kebabs (icon="dots") vs 32 dots-vertical. 8 alert-circle/octagon error glyphs vs 119 alert-square-rounded. 10 literal-string Field hints + ~30 i18n *Hint Field/FormSection hints + ~45 private hint-class sites render permanent explanatory text; 83 InfoTip usages exist. 257 variant=primary Buttons (single-primary not grep-checkable; not flagged). Section 26: Novy obchod correctly placed in Obchody tab/rail (no violation).

idverdictimpactfixnfindingcanonical
E1partlyhighvisual40Permanently visible explanatory hints under fields (Field hint= literals and i18n *Hint keys)RULES 14.2/14.4/24: delete the sentence, or move to InfoTip via Field labelExtra / SectionBlock info (83 InfoTip usages exist), examples to placeholder. Keep visible only state/error/single action cue.
E2confirmedhighvisual2Brown --warning-ink used as glyph/text colour on warning rows15.1/15.5: alert-square-rounded in --critical, text --critical-ink (or --fg with orange --ic-amber dot as ds.css:2438 .c-due.warn).
E3partlymediumvisual2Amber-pill ink/surface tokens (brown) used for chipsNeutral/grey pill or accent tint; for status .acrm-pill.amber (orange dot only, ink default, ds.css:983).
E4confirmedmediumdecision9Brown tokens still defined; --warning-subtle surfaces patched per file in dark modeAD decides: retire/redefine --warning-ink, --pill-amber-ink, --warning-subtle, --pill-amber-bg in tokens.css (neutral or amber tint), then delete local dark overrides.
E5confirmedmediummechanical3'More' trigger uses horizontal dots glyph instead of dots-verticalicon="dots-vertical"; remove or mark dots / dots-circle-horizontal as non-trigger in ds-icons.tsx:239-241.
E6partlymediumvisual7Error/failed states use alert-circle instead of alert-square-roundedIcon alert-square-rounded with --critical glyph / --critical-ink text; spam octagon only if AD agrees.
E7partlymediumdecision2Warning glyphs coloured amber instead of --critical15.1: alert glyph --critical, text --critical-ink; amber only for data colours (15.3). AD must rule which of dialer/coach/report icons are warnings vs data.
E8partlymediumvisual41Hand-made hint classes duplicate Field hint and carry explanatory textVisible text only for state/error/single action cue (14.3); else InfoTip (14.2) or delete; where a hint stays use Field/acrm-field-hint.
E9partlymediumdecision5Status as dot + text in list columns instead of pillRULES second 22: .acrm-pill sm <tone> in ItemList slot tags before labels, no x; .il-dot only in dense rows without tags slot.
E10confirmedlowdecision31Permanent descriptive paragraphs in dialogs and onboarding14/24: one sentence max, rest in InfoTip or collapsed 'Zobrazit podklady'; AD confirms exempt onboarding/legal copy.
E11partlylowvisual2Local warning blocks with private styling instead of StatusBanner criticalStatusBanner tone critical (neutral/accent/positive/critical) or inline alert-square-rounded + --critical-ink row.

Verifier notes:

  • E1 All 8 example sites exist on main (1e2a6839a) and are real explanatory sentences under fields, e.g. AiFormFields.tsx:60 and PortfolioFields.tsx:114. The count is overstated. There are 47 hint= props in total; 10 are literals of 30+ chars and 20 use t('...Hint') keys, not ~30. Several of the 47 are errors or state text, which §14.3 allows. 50 is an upper bound, not a measured violation count. Risk: Some hints are the one allowed action cue (§14.3) or a warning required by §14.8. Moving them to InfoTip without reading each one could hide warnings about irreversible actions.
  • E3 The chat-visibility.css:292-293 (.chv-sys) and asistent-plocha.css:184-185 (.pl-unverified) sites are real: brown text in light mode, brown fill in dark mode, and no override. note-markdown.css:37 is refuted: [data-theme='dark'] .nmd-hl at :42 replaces the fill with background:none and an inset --ic-amber underline, and its text is --fg. So it is never brown. Risk: low
  • E6 There are 7 alert-circle sites, all real error/failed indicators: OtpPage:127, ChatComposer:138, mail-delivery:47, NoteSaveStatus:31, voice-message-parts:449/492 and ChatThread:105. use-message-danger-menu.tsx:107 alert-octagon is the icon for a 'report spam' menu action, not an error state, so §15.5 does not apply to it. Risk: mail-delivery and ChatThread are per-message status glyphs; check that the --critical colour is applied there too, not only the glyph.
  • E7 datamart.css:163 (.dm-warn alert-square-rounded in --ic-amber, StepCard.tsx:217) is a real §15.1 warning. coach-report.css:72 (.cr-hero-segs is-out) is the Koučink band, which §15.3 explicitly exempts as a data colour. sales-reports.css:991 is a recorded owner decision ('oranžově', ACRM-432). dialer.css:298 is a result-tone legend chip, arguably data. Only 1 to 2 sites are violations. Risk: Repainting the coach and report icons red would reverse the §15.3 exemption and ACRM-432.
  • E8 The class counts are roughly right: sgb-hint 15, fl-hint 12 (not 10), kar-hint 5, au-hint 3, plus 6 singletons, total 41. But 4 of the 5 cited examples are text §14.3 allows. DealStageFilter:36 is a loading/empty state, MergeDialog:132 is an error, CompanyKnowledgeSection:172 shows data counts, and NastaveniPage:542 shows field key and type (data; it also uses .fhint, not a private class). The real issue is duplicate hint classes, not explanatory text, and nobody counted the explanatory subset. Risk: Converting state and error lines to InfoTip would hide states and errors that §14.3 requires to stay visible.
  • E9 DokladyList:281, KodyList:317, PozvankyList:440 and SetkaniRow:30 draw .il-dot plus a word in a column and never use tags=. Under the second §22.3/22.4, status in a column should be a pill. MessagePage.tsx:1400 is a message detail, not a list row. These lists have no tags slot, so the §22.4 exemption ('dense row without tags slot') makes this ambiguous. It needs an AD ruling, not just a visual fix. Risk: Moving status into tags changes column widths and alignment across 4 list screens.
  • E11 Two of the four cited blocks are not divergent. DeleteUserParts.tsx:119 (.du-warns) already renders <StatusBanner tone="critical" icon="alert-square-rounded">. dg-dlg-warn (DelegateDialog:234) is a role=status skipped-items count, i.e. state text in --fg-muted, not a warning. kar-note.warning (critical-subtle box with alert-square-rounded) and dlrw-warn (the §14.8-style warning before an irreversible save) are the real private warning styles. Risk: low

Examples:

  • E1 apps/web/src/components/mail/compose/AiFormFields.tsx:60, apps/web/src/components/mail/compose/AiFormFields.tsx:85, apps/web/src/components/mail/compose/PortfolioFields.tsx:114, apps/web/src/components/mail/compose/PortfolioFields.tsx:165, apps/web/src/components/mail/compose/OrderFields.tsx:20, apps/web/src/pages/AiZamestnanciPage.tsx:291, apps/web/src/components/accounting/PostDocumentDialog.tsx:491, apps/web/src/components/callwrapup/WrapupBand.tsx:133
  • E2 apps/web/src/ds/stage-move.css:47, apps/web/src/ds/deal-card-rows.css:12, apps/web/src/components/deal/StageGateDialog.tsx:205
  • E3 apps/web/src/pages/komunikace/chat-visibility.css:292, apps/web/src/pages/komunikace/chat-visibility.css:293, apps/web/src/ds/asistent-plocha.css:184, apps/web/src/ds/asistent-plocha.css:185, apps/web/src/ds/note-markdown.css:37
  • E4 apps/web/src/ds/analytics.css:145, apps/web/src/ds/analytics.css:174, apps/web/src/ds/analytics.css:216, apps/web/src/ds/sales-reports.css:514, apps/web/src/ds/meeting-record.css:902, apps/web/src/pages/komunikace/message-page.css:290, apps/web/src/pages/komunikace/message-page.css:402, packages/ui/src/tokens/tokens.css:65
  • E5 apps/web/src/pages/komunikace/RailQuickActions.tsx:106, apps/web/src/components/assistant/pinned-views/ArtefactHeadActions.tsx:25, apps/web/src/components/assistant/pinned-views/PinnedViewTile.tsx:76
  • E6 apps/web/src/pages/onboarding/OtpPage.tsx:127, apps/web/src/pages/komunikace/ChatComposer.tsx:138, apps/web/src/components/mail/mail-delivery.tsx:47, apps/web/src/components/notes/NoteSaveStatus.tsx:31, packages/ui/src/components/voice-message-parts.tsx:449, packages/ui/src/components/voice-message-parts.tsx:492, packages/ui/src/components/ChatThread.tsx:105, apps/web/src/pages/komunikace/use-message-danger-menu.tsx:107
  • E7 apps/web/src/ds/datamart.css:163, apps/web/src/components/datamart/StepCard.tsx:217, apps/web/src/ds/dialer.css:298, apps/web/src/ds/coach-report.css:72, apps/web/src/ds/sales-reports.css:991
  • E8 apps/web/src/components/segment/DealStageFilter.tsx:36, apps/web/src/components/segment/InteractionFilter.tsx:81, apps/web/src/components/MergeDialog.tsx:132, apps/web/src/pages/settings/CompanyKnowledgeSection.tsx:172, apps/web/src/pages/NastaveniPage.tsx:542
  • E9 apps/web/src/pages/komunikace/DokladyList.tsx:281, apps/web/src/pages/komunikace/KodyList.tsx:317, apps/web/src/pages/komunikace/PozvankyList.tsx:440, apps/web/src/components/setkani/SetkaniRow.tsx:30, apps/web/src/pages/komunikace/MessagePage.tsx:1400
  • E10 apps/web/src/pages/SouhlasPripojeniPage.tsx, apps/web/src/pages/OdemknoutAsistentaPage.tsx, apps/web/src/components/projects/TaskDetailContent.tsx, apps/web/src/components/pipeline/BacklogFillDialog.tsx, apps/web/src/pages/onboarding/ProfilPage.tsx
  • E11 apps/web/src/pages/KarieraPage.tsx:160, apps/web/src/ds/kariera.css:77, apps/web/src/pages/komunikace/delegace/DelegateDialog.tsx:234, apps/web/src/pages/komunikace/delegace/delegace.css:126, apps/web/src/components/callwrapup/WrapupBand.tsx:408, apps/web/src/components/clen-tymu/DeleteUserParts.tsx:119

F — Registry ↔ code drift and dead DS code

Registry: 35 families / 107 subcomponents (canon 94, beta 6, candidate 5, deprecated 2); source package 60, app-css 41, app 6. Verified against /root/acrm-main: 8 stale registry file/css paths; 21 react names listed under source=package that @acrm/ui index.ts does not export (the 50+ other missing names are app-source and exist in apps/web); 44 registry class selectors with no definition in any ds CSS; 7 subs with zero stories, of which 5 (range-bar, weekly-bars, coach-hero, record-nav-arrows, country-flag) have Ladle stories that exist but are not mapped; all 455 mapped story ids resolve in the Ladle catalog meta (0 broken) but 57 of 508 catalog stories are unmapped; 2 subs lack usage.md and a11y.md (brand-mark, voice-trace). Unregistered: 2 package components (BottomSheet, CountryFlag file path stale) plus about 146 exported symbols not in any react[] (mostly prop types, ~12 real components/hooks: Icon, BottomSheet, MenuPanel, PickerMenuPanel, PortalRoot, InSheetProvider, DragGhost, DropSlot, GapList, UserFacesProvider, Flag/PhoneFlag); 62 of 115 ds CSS files (~403 kB of ~1.34 MB) are in no registry css[]. Dead CSS: 62 of 4035 distinct class names in ds.css + ds/*.css are not referenced as identifiers anywhere in non-test apps/web/src or packages/ui/src (23 of them are the calendar ci-* set, which is built dynamically in KalendarPage.tsx:1207, so real dead = about 39); 0 dead in 40+ files.

idverdictimpactfixnfindingcanonical
F1confirmedhighmechanical8Registry points at files that no longer exist on main (8 stale paths)Update registry files/css to existing paths; re-run a files-exist check in ds:validate / the registry build so the claim is enforced; move masked-date-input and country-flag to source=package.
F2partlymediummechanical43Registry classes[] that are not defined in any DS CSS (44 selectors)Remove renamed/removed selectors from registry classes[] or restore the class in CSS if the component still emits it (check each against the component TSX).
F3confirmedmediummechanical57Stories exist in Ladle but are not mapped in the registry (57 of 508 unmapped; 5 subs show as 'no story')Map existing story ids per section in registry; add registry entries (or fold into existing) for Slider and Removable chip; write one story for project-label and voice-trace.
F4confirmedhighdecision62Large share of app DS CSS is outside the registry (62 of 115 files, ~403 kB)Decide per file: DS layer (attach to the owning registry sub css[], e.g. button.css to button/split-button, chip-morph.css to note-dialog, reorder.css to a reorder/drag entry) vs. page CSS (declare as out of DS scope in registry $note or a 'pageCss' list so coverage is measurable).
F5partlymediummechanical11Registry react[] names for source=package that @acrm/ui does not export (21)Either export the real helpers from index.ts or move app-level components to source=app/app-css and drop the non-public names from react[].
F6partlymediummechanical7Public @acrm/ui components with no registry entryAdd registry subs (e.g. overlay/bottom-sheet, foundations icon, reorder) or attach to the owning sub react[] so Coverage and props generation cover them.
F7partlyhighdecision13Beta/candidate/deprecated entries and their blockersAD decision queue: promote beta items with approvedAt set (record-actions-menu, record-nav-arrows, bulk-action-bar) after fixing F1-F3; delete or fold deprecated DrawerForm and Popover consumers; drop or ship candidates.
F8partlylowmechanical2Missing usage.md / a11y.md for two candidatesWrite usage.md + a11y.md (fixed headings) before beta, or drop the candidates.
F9partlylowmechanical31Dead CSS classes in ds.css and ds/*.css (about 39 real, 62 flagged)Delete the unused rule blocks (also acrm-fieldrow, acrm-check-row, il-md/il-lg density variants if no consumer is planned); keep a size-ratchet win for ds.css (102 kB, over the 100 kB cap).
F10confirmedlowmechanical2Registry source/status mismatches after code moved into the packageSet source=package, files to packages/ui/src/components/*.tsx, remove app-only caveats from notes.

Verifier notes:

  • F2 Re-measured against every .css under apps/web/src and packages/ui/src: 43 registry selectors are undefined anywhere, so the count is close. The cited set is partly wrong. .aichat-head, .pp-layout, .acrm-bulkbar-count/-sep and .il-has-tags ARE defined in assistant.css, layout-switch.css, bulk-bar.css and komunikace.css, which the registry css[] lists (e.g. bulk-action-bar css = components/bulk/bulk-bar.css). The auditor only searched ds.css and ds/*.css. Some genuinely undefined names are hook classes that the TSX emits on purpose (.acrm-flag at CountryFlag.tsx:197, .vm-copy at voice-message-parts.tsx:567), so 'cannot style anything' is not a defect for those. Risk: Deleting hook classes from classes[] would hide selectors that tests and other hooks use. Recompute the set against registry css[] first.
  • F5 Only 11 names are missing from packages/ui/src/index.ts: DealDialog, DealSubjectStep, NewContactDialog, PageHeader, RecordCloseButton, FilePreview, FilePreviewModal, EntityRefPreview, GoogleConnectBanner, RelationshipStatusBanner and RepBanner. The other 10 are exported. useConfirm is at index.ts:100, spring* at :73-75, useAnchoredMenu at :71, useInlineEdit at :52, hashHue/initials/hueStyle at :184, entityMark/glyphNudge at :255. Risk: low
  • F6 Icon/ICONS (index:82), BottomSheet and InSheetProvider (:189-190), MenuPanel (:90), PickerMenuPanel (:88), PortalRoot (:194) and UserFacesProvider (:253) are exported and absent from every react[], so the core finding holds. The list is padded, though. DragGhost, DropSlot and GapList are type-only exports (index:56, :67), not components. Flag and PhoneFlag ARE in the country-flag react[]. Risk: low
  • F7 Statuses are right: 6 beta, 5 candidate, 2 deprecated. One stated blocker is false: bulk-action-bar's .acrm-bulkbar-count and -sep are defined in components/bulk/bulk-bar.css, which is its registered css. brand-mark does have stories mapped; it lacks only docs. This is an inventory and decision queue rather than a divergence finding. Risk: low
  • F8 Confirmed that avatar/brand-mark and meeting-analysis/voice-trace lack usage.md and a11y.md (105 of 107 subs have both). The claim that both have zero stories is wrong: brand-mark has stories mapped, and only voice-trace has none. Risk: none
  • F9 Several 'dead' classes are built dynamically and are live. il-md and il-lg are ItemList density modifiers ('il il-' + density, ItemList.tsx:208-209, default 'md'). The lh-f2/3/4 classes come from lh-f${n} (ListLoader.tsx:27). s2 comes from 'acrm-chart-key s' + (i+1) (Chart.tsx:95/113/123). as-drawer-left comes from 'as-drawer-' + side (AppShell.tsx:1492). The proposed canonical fix explicitly says to delete il-md/il-lg, which would break every ItemList. About 31 remain plausible (e.g. acrm-fieldrow, acrm-check-row, ac-pop/ac-opt, sgb-ac-pop, prof-pop, sm-unmet, ds-osoby/ds-firmy, idimg-*), each still needing a dynamic-build check. Risk: HIGH: deleting .il-md and .il-lg removes ItemList density styling app-wide. Deleting .s2 removes chart series colour, deleting .lh-f* breaks the ListLoader animation, and deleting .as-drawer-left breaks the left shell drawer.

Examples:

  • F1 /root/ds-export/extracted/site/registry.json (text-field/masked-date-input files), /root/ds-export/extracted/site/registry.json (select/check-menu css apps/web/src/ds/check-menu.css), /root/ds-export/extracted/site/registry.json (task-list css apps/web/src/ds/task-list-dnd.css), /root/ds-export/extracted/site/registry.json (avatar/country-flag files PhoneFlag.tsx), /root/ds-export/extracted/site/registry.json (select/entity-picker, select/product-picker AddRefMenu.tsx), packages/ui/src/index.ts:113 and :249
  • F2 site/registry.json ai-assistant/ai-assistant .aichat-head ... .aichat-typing (8), site/registry.json discussion/chat-thread .pp-chat, .pp-chat-day, .pp-msg-m, .pp-read, site/registry.json select/check-menu .cm-reorder .cm-dragging .cm-drop-line .cm-sheet, site/registry.json filter/filter-bar .fb-add .fb-reset .fb-save, site/registry.json task-list/task-list .tl-navrhy .tl-reorder .tl-drop-line, site/registry.json table/bulk-action-bar .acrm-bulkbar-count .acrm-bulkbar-sep, site/registry.json voice-message/* .vm-copy (3x), site/registry.json avatar/country-flag .acrm-flag
  • F3 site/registry.json meeting-analysis/range-bar stories {}, site/registry.json meeting-analysis/weekly-bars stories {}, site/registry.json meeting-analysis/coach-hero stories {}, site/registry.json section-top-bar/record-nav-arrows stories {}, site/registry.json avatar/country-flag stories {}, packages/ui/stories/slider.stories.tsx:13, packages/ui/stories/removable-chip.stories.tsx:15, site/registry.json status/project-label stories {}
  • F4 apps/web/src/ds/button.css, apps/web/src/ds/reorder.css, apps/web/src/ds/scrollbar.css, apps/web/src/ds/inline-edit.css, apps/web/src/ds/chip-morph.css, apps/web/src/ds/popover-desc.css, apps/web/src/ds/later-menu.css, apps/web/src/ds/reduced-motion.css
  • F5 site/registry.json dialog/multi-step-dialog react, site/registry.json status/status-banner react, site/registry.json attachment/attachment react, site/registry.json avatar/avatar react (hashHue, initials, hueStyle), site/registry.json dialog/spring react, packages/ui/src/index.ts
  • F6 packages/ui/src/components/BottomSheet.tsx, packages/ui/src/components/PickerPanel.tsx, packages/ui/src/index.ts (Icon, ICONS, MenuPanel, PickerMenuPanel, PortalRoot, InSheetProvider, DragGhost, DropSlot, GapList), packages/ui/src/components/CountryFlag.tsx
  • F7 site/registry.json app-shell/split-view, site/registry.json select/entity-picker, site/registry.json table/bulk-action-bar, site/registry.json discussion/chat-thread, site/registry.json meeting-analysis/voice-trace, site/registry.json dialog/drawer-form (deprecated, packages/ui/src/components/DrawerForm.tsx), site/registry.json popover/popover (deprecated, packages/ui/src/components/Popover.tsx)
  • F8 /root/ds-export/extracted/docs/avatar/brand-mark.usage.md (absent), /root/ds-export/extracted/docs/meeting-analysis/voice-trace.usage.md (absent)
  • F9 apps/web/src/ds.css:144 (.acrm-check), apps/web/src/ds.css:1192 (.lh-f2), apps/web/src/ds/item-list.css:273 (.il-md), apps/web/src/ds/item-list.css:345 (.il-lg), apps/web/src/ds/segment-screen.css (.ac-pop, .ac-opt), apps/web/src/ds/builder-panels.css (.sgb-ac-pop, .prof-pop), apps/web/src/ds/inputs.css (.acrm-fieldrow, .acrm-check-row), apps/web/src/ds/stage-move.css (.sm-unmet)
  • F10 site/registry.json text-field/masked-date-input, site/registry.json avatar/country-flag, packages/ui/src/components/MaskedDateInput.tsx, packages/ui/src/components/CountryFlag.tsx
  • F11 (missed, medium, mechanical, n=27) Colocated component CSS outside ds/ missing from registry css[] (27 files). 27 of 30 app CSS files outside apps/web/src/ds/ are in no registry css[]. Some style components the registry already lists: FilePreview.css (FilePreview is in attachment react[]), ScrollArea.css, OrgLogo.css, PersonPhoto.css, AskAssistantField.css and required-actions.css. Others are large feature layers, e.g. mail/mail.css at 52 kB, komunikace.css at 23 kB and message-page.css at 19 kB, and they also carry the brown --warning-subtle overrides from E4. The Code tab and coverage cannot see where these components are styled. → Attach the component CSS to its owning sub's css[] (FilePreview.css to attachment/attachment, ScrollArea.css and OrgLogo.css to their families). Declare mail, komunikace and doc CSS as page scope in the registry $note or a pageCss list.. apps/web/src/components/FilePreview.css, apps/web/src/components/ScrollArea.css, apps/web/src/components/OrgLogo.css, apps/web/src/components/person/PersonPhoto.css, apps/web/src/components/mail/mail.css
  • F12 (missed, high, mechanical, n=8) Dead-CSS detection must account for dynamically built modifier classes. A literal-identifier search marks live modifiers as dead because the class names are built at runtime. 'il-' + density, lh-f${n}, 'acrm-chart-key s' + (i+1) and 'as-drawer-' + side are all live. Any automated dead-class cleanup (F9) built on that search would delete the ItemList density, chart series, loader and drawer styles. → Before a dead-CSS MR, grep the class prefix plus string concatenation and template literals, or check the class in a rendered DOM snapshot. Record known dynamic prefixes (il-, lh-f, s<n>, as-drawer-, ci-) in the dead-CSS tool's allowlist.. apps/web/src/components/ItemList.tsx:208, packages/ui/src/components/ListLoader.tsx:27, packages/ui/src/components/Chart.tsx:95, apps/web/src/components/AppShell.tsx:1492, apps/web/src/ds/item-list.css:273

Unregistered app CSS (triage)

Added 17 Oct 2026 after the registry repair (F4/F11). 13 CSS files were attached to their owning entries (FilePreview, OrgLogo, PersonPhoto, AskAssistantField, button, reorder (x3), chip-morph, share-dialog-formless, deal-card-rows, detail-field). The remaining 79 files under apps/web/src are in no registry css[]; no entries were created. Best guess is from the TSX that imports the file and the classes it defines; "page" = feature or page layer that is probably out of DS scope (declare as page CSS, review F4 option 2). Note: the F7 claim about bulk-action-bar is moot in the registry, which already lists components/bulk/bulk-bar.css and its classes resolve.

filesizebest-guess family
components/ScrollArea.css2.7 kBglobal (no family)
components/communication/communication.css0.2 kBfeature layer (none)
components/integration/integration-card.css1.5 kBfeature layer (status/pill)
components/mail/mail.css50.6 kBfeature layer (status/pill)
components/mail/rich-editor-dark.css0.8 kBfeature layer (none)
components/mail/thread/thread.css11.7 kBfeature layer (dialog/formless-dialog)
components/pipeline/board-card-settings.css2.6 kBselect/check-menu
components/required-actions/required-actions.css1.6 kBstatus/status-banner
doc/document.css12.9 kBfeature layer (none)
doc/styl-nahled.css1.4 kBfeature layer (none)
ds/accounting-invoices.css6.5 kBpage / feature (out of DS scope?)
ds/accounting.css25.4 kBfeature layer (status/pill)
ds/analytics.css5.2 kBpage / feature (out of DS scope?)
ds/asistent-plocha.css6.4 kBai-assistant/ai-assistant
ds/auth.css16.3 kBpage / feature (out of DS scope?)
ds/builder-panels.css9.8 kBpage / feature (out of DS scope?)
ds/calc.css0.6 kBpage / feature (out of DS scope?)
ds/call-bar.css3.6 kBbutton/fab
ds/chaty-pripojeni.css1.5 kBpage (settings)
ds/communication-rules.css3.0 kBpage / feature (out of DS scope?)
ds/communication-style.css5.1 kBfeature layer (status/pill)
ds/contract.css18.5 kBpage / feature (out of DS scope?)
ds/crm-dashboard.css6.3 kBstat-tile/stat-tile (page)
ds/datamart.css3.4 kBpage / feature (out of DS scope?)
ds/delete-user-dialog.css5.5 kBdialog/dialog
ds/dialer.css19.6 kBfeature layer (status/pill)
ds/duplicate-panel.css2.5 kBstatus/status-banner
ds/eref-hc-strop.css1.8 kBdialog/dialog
ds/error-boundary.css0.8 kBloading/detail-state-screen
ds/evidence.css4.6 kBfeature layer (record-rail/rail)
ds/finance.css8.4 kBpage / feature (out of DS scope?)
ds/home.css5.1 kBpage / feature (out of DS scope?)
ds/impersonation-banner.css1.5 kBstatus/status-banner
ds/import.css14.1 kBpage / feature (out of DS scope?)
ds/inline-edit.css2.3 kBtext-field/input (inline edit)
ds/kariera.css2.5 kBpage / feature (out of DS scope?)
ds/later-menu.css0.7 kBbutton/menu-button
ds/mail-label-dialog.css6.3 kBdialog/formless-dialog
ds/meeting-record.css30.4 kBfeature layer (record-rail/rail)
ds/merge-table.css6.5 kBfeature layer (choice/checkbox)
ds/next-action.css8.1 kBsection-block/section-block
ds/note-markdown.css3.5 kBnote-card/note-card
ds/orders.css7.6 kBfeature layer (status/pill)
ds/persona-panel.css2.1 kBfeature layer (none)
ds/pinned-views.css1.1 kBai-assistant/ai-assistant
ds/pipeline.css7.2 kBpage / feature (out of DS scope?)
ds/popover-desc.css1.1 kBpopover/popover
ds/process-list.css10.4 kBitem-list/item-list
ds/product-print.css2.7 kBpage / feature (out of DS scope?)
ds/product-profile.css3.2 kBfeature layer (text-field/input-with-icon-kbd)
ds/project-milestones.css6.4 kBfeature layer (button/icon-button)
ds/project-rail.css7.8 kBfeature layer (record-rail/rail)
ds/project-tasks.css13.8 kBfeature layer (item-list/item-list)
ds/record-pick.css2.1 kBselect/record-select
ds/reduced-motion.css1.1 kBglobal (foundation)
ds/reports.css2.9 kBpage / feature (out of DS scope?)
ds/reservation.css9.9 kBfeature layer (button/icon-button)
ds/rooms.css5.5 kBpage / feature (out of DS scope?)
ds/sales-reports.css28.1 kBfeature layer (section-block/section-block)
ds/scrollbar.css4.7 kBglobal (foundation)
ds/signature-pad.css1.1 kBnone (feature)
ds/stage-move.css2.2 kBdialog/dialog
ds/task-attachments.css5.8 kBattachment/attachment
ds/vyvoj-board.css3.6 kBfeature layer (status/pill)
ds/vyvoj-schvaleni.css6.9 kBfeature layer (button/button)
ds/why-panel.css4.6 kBfeature layer (status/pill)
index.css2.6 kBglobal base
pages/ds-inputs.css1.3 kBpage / feature (out of DS scope?)
pages/komunikace/chat-conversation.css7.5 kBdiscussion/chat-thread
pages/komunikace/chat-visibility.css8.1 kBpage / feature (out of DS scope?)
pages/komunikace/compose-page.css4.4 kBpage / feature (out of DS scope?)
pages/komunikace/crumbs.css2.5 kBsection-top-bar/section-top-bar
pages/komunikace/delegace/delegace.css5.6 kBpage / feature (out of DS scope?)
pages/komunikace/komunikace-narrow.css2.3 kBpage / feature (out of DS scope?)
pages/komunikace/komunikace.css22.2 kBpage / feature (out of DS scope?)
pages/komunikace/message-ajda.css1.1 kBpage / feature (out of DS scope?)
pages/komunikace/message-page.css20.0 kBpage / feature (out of DS scope?)
pages/komunikace/message-rail.css3.6 kBpage / feature (out of DS scope?)
pages/komunikace/message-reply.css5.7 kBpage / feature (out of DS scope?)

Source: docs/REVIEW-ds-implementation-2026-10-17.md · GitLab