Skip to content

Fluent-next: extend the component tier to every component that owns v… - #34977

Merged
EugeniyKiyashko merged 5 commits into
DevExpress:feature/26_2_new_fluent_theme_with_design_tokensfrom
EugeniyKiyashko:fluent-next/component-tier
Aug 31, 2026
Merged

Fluent-next: extend the component tier to every component that owns v…#34977
EugeniyKiyashko merged 5 commits into
DevExpress:feature/26_2_new_fluent_theme_with_design_tokensfrom
EugeniyKiyashko:fluent-next/component-tier

Conversation

@EugeniyKiyashko

Copy link
Copy Markdown
Contributor

No description provided.

…ariables

Wave F published --dx-* for 50 of 84 components; the other 34 (scheduler, chat,
fileManager, diagram, pivotGrid, cardView, list, gantt, form, popup, calendar,
treeView, tabs, ...) were checked by neither the strict grammar nor the tier, so
customization through the tier did not work for them at all.

migrated 50 -> 78, tier 798 -> 2049 names in 76 folders. The six components left
out own no variables (autocomplete, dropDownBox, multiView, rangeSlider,
responsiveBox, speechToText). cardView stopped being an exemption: its 111 BEM
names moved onto the grammar and exemptFolders is now empty; the shared
$cardview-* parameters of base keep their spelling, so the legacy themes are
untouched.

Three defects the gates caught: -var(--dx-...)/2 from a mixin that forwards its
parameters into unguarded math, a cross-scope duplicate on
.dx-radio-value-container, and math.div(var(--dx-...), 2) printing a division
without calc(). The wiring codmod grew a WIRING_UNSAFE list so a later run cannot
silently undo those reverts.

Per-selector resolve diff: 0 lost, 0 changed values in both modes (+1802 tier
declarations). generic/material/fluent byte-identical. jest 81/81, stylelint 0,
naming:check 0, calc budget 349 -> 348.
@EugeniyKiyashko EugeniyKiyashko self-assigned this Aug 28, 2026
@EugeniyKiyashko
EugeniyKiyashko requested a review from a team as a code owner August 28, 2026 14:26
…nder outside their root

CI on the previous commit was red: 180 screenshots in 10 jobs. All of one class —
the tier resolves only under the root it is declared on, so a rule that paints an
element JS renders elsewhere lost the whole declaration. The resolve diff cannot
see this (values are unchanged) and neither can the cross-scope gate (no
duplicate); only the cascade shows it.

Four roots were missing:
  - pagination: dx-pager and dx-pagination are MUTUALLY EXCLUSIVE
    (pagination/content.tsx, getClasses) — a grid's pager runs in
    grid-compatibility mode and never carries dx-pagination, which emptied the
    tier in every grid and moved every screenshot with a pager;
  - pivotGrid: the field chooser is a widget of its own, shown in a popup;
  - htmlEditor: the AI dialog is a popup carrying .dx-aidialog;
  - chat: the message context menu is overlay content;
  - cardView: dragging a header item puts a COPY in .dx-sortable-dragging, and
    `.dx-sortable-dragging > .dx-cardview-header-item` paints that copy.

cardView's wiring now carries the tier: 81 of the 112 mirrors passed base a frozen
SCSS value, so the published names did nothing. base prints all of them (one
guarded if(type-of == number) — the var takes its calc branch, which is the
documented var-safe form), and the header-panel compensation now recalculates when
the padding is overridden, as the calc policy asks.

Resolve diff against the pre-wave parent, both bundles built by the same target:
0 lost, 0 changed, +2155 tier declarations. One new non-tier pair — the minifier
stops merging base's and the theme's rule for the alpha-channel label now that the
theme's value is a var(); the theme's rule still wins and the computed margin is
the same. generic/material/fluent byte-identical.

Runtime reachability audit extended from 35 widgets to 52 (all wave-H components,
their satellites and the drag portals): 9248 checks / 0 holes. It also stopped
judging elements that do not render — a closed overlay keeps its content inline and
paints nothing, which was 3 false positives — and its synthetic AI-chat portal now
carries .dx-chat, the class the real widget has (grid_core/ai_chat renders a Chat).

jest 81/81, stylelint 0, naming:check 0.
…in screenshots

The wave's defects were all one shape — a rule paints an element the component's
root does not reach — and nothing but the screenshots caught them. Now three gates
do, and the roots the second CI run exposed are fixed.

Roots added (each verified on the built bundle): cardView column chooser,
chat confirmation popup, htmlEditor add-image dialog, filterBuilder field and
operation overlays, list context menu, pivotGrid DnD clone container
(.dx-pivotgrid-fields-container — the dragged field lost its background, border and
shadow), fileManager dialogs and context menu, diagram properties/toolbox popups.

Gates:
  - reachability.mjs: a tier read whose selector no root covers must be a REVIEWED
    scope. 488 scopes recorded in nested-scopes.json; a new one fails the run with
    the two ways out (add a root, or prove nesting in the runtime audit). This is
    what .dx-pager would have hit: pagination's rules target a class the component
    only carries inside a grid.
  - fluent-next-naming: every publishing component must appear in the audit gallery
    — the vacuous pass that let wave F stay at 35 widgets while 28 more shipped.
  - the audit page now counts roots that match NO element: 30 of them, wave F's
    included, were never exercised. 16 remain and are printed on every run.

The gallery reached 12200 checks / 0 holes (was 9248). Two of its own defects are
fixed: gantt and diagram were loaded after dx.all (E1041, both widgets silently
absent), and the synthetic dialog markup lacked .dx-form / .dx-fileuploader /
.dx-treeview, which the real widgets do render — 8 phantom holes.

Resolve diff against the pre-wave parent: 0 lost, 0 changed, +3110 tier
declarations; generic/material/fluent byte-identical. jest 82/82, stylelint 0,
naming:check 0.

apps/demos: looksSameComparisonOptions commented out on request, so the next run
compares without the tolerance that has been hiding small drifts. It must be
restored before merge — it loosens comparison for every theme, not just this one.
@EugeniyKiyashko
EugeniyKiyashko force-pushed the fluent-next/component-tier branch from 6d37d8f to 15c7a66 Compare August 30, 2026 18:16
…ns, mask a known flake

Two more elements the tier could not reach, both read off the DOM the CI
screenshots produced:

  - cardView's drop indicator is a dxSortable placeholder built in the VIEWPORT
    with the widget's own class on it, so `.dx-cardview-header-item-sort-indicator`
    painted an element no root contained. Legacy paints it with literals and before
    the tier it read `--dxds-*` from `:root`; the tier names resolved to nothing,
    `border-width` collapsed to 0 and the 4px indicator vanished from six
    screenshots. Verified live after the fix: 4x32, 2px border, 8px radius.
  - the scheduler tooltip renders inside `.dx-scheduler-overlay-panel`, which was
    not a root: the appointment marker, the title weight and the paddings were all
    dropped declarations.

Both classes are now exercised by the audit gallery (12215 checks / 0 holes).

Etalons, 13 files. The scheduler ones lost a 1px line under the left header cell —
that is PR DevExpress#34753 (Scheduler: Multilevel grouping) restructuring the header borders
with `border-top-color: transparent`, a deliberate change that never got its
screenshots re-shot; the same single line, in the same place, in fluent-next,
fluent and material alike. The two TreeView drag-and-drop demos drifted before this
branch: their etalons still carry #d8d8d8 for a hairline the token package made
#e1e1e1, plus a 1px shift that antialiasingTolerance used to swallow. Every
replacement was accepted pixel by pixel against the file it replaces.

dropdownbox_multiple_selection_gridbox is not a theme difference at all: the grid's
select checkbox is caught focused, and fluent-next paints that state darker
(--dx-check-box-icon-checked-bg-focused, #003c70, against #0f6cbd at rest). It is a
flake — two frameworks of five — and fluent already carries a mask over exactly
that square, so fluent-next gets the same mask instead of a re-shot etalon.

Resolve diff against the pre-wave parent: 0 lost, 0 changed, +3470 tier
declarations; generic/material/fluent byte-identical. jest 82/82, stylelint 0,
naming:check 0.
…t the default

`looksSameComparisonOptions` (tolerance 20, antialiasingTolerance 20) was applied to
every screenshot of every demo in every theme, and it was spread AFTER the options a
test passed, so a demo could not even opt out of it.

It hid real regressions for months. Turning it off for one run surfaced: a 1px line
under the scheduler's header cell that PR DevExpress#34753 removed deliberately and nobody
re-shot; a hairline the token package changed from #d8d8d8 to #e1e1e1; a whole
TreeView shifted by a pixel; a checkbox captured in its focused state.

Now the default carries `textDiffTreshold` only, per-demo options win over it, and
the four demos whose pixels really do wobble ask for the looser comparison
themselves — three through their own visualtestrc.json, one at the call site,
each with the measurement that justifies it. The schema grew a `comment` field so
an exception can carry its reason.

Everything else is compared at the comparator's own defaults, which is the point:
a drift in a demo that nobody marked as noisy now fails instead of passing quietly.
@EugeniyKiyashko
EugeniyKiyashko requested a review from a team as a code owner August 30, 2026 22:11
@EugeniyKiyashko
EugeniyKiyashko merged commit ecfc4de into DevExpress:feature/26_2_new_fluent_theme_with_design_tokens Aug 31, 2026
237 of 238 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants