Skip to content

[docs] Use specific date to avoid daily Argos diffs#4337

Merged
LukasTy merged 8 commits into
mui:masterfrom
LukasTy:docs/stablize-calendar-demo-snapshots
Mar 18, 2026
Merged

[docs] Use specific date to avoid daily Argos diffs#4337
LukasTy merged 8 commits into
mui:masterfrom
LukasTy:docs/stablize-calendar-demo-snapshots

Conversation

@LukasTy
Copy link
Copy Markdown
Member

@LukasTy LukasTy commented Mar 15, 2026

This is to avoid the Argos diff everyday shifting the today in each demo and causing diffs.

It's a modified version of what is used in X Pickers e2e tests, because working with TZDate required a more elaborate overriding.

@LukasTy LukasTy self-assigned this Mar 15, 2026
@LukasTy LukasTy added docs Improvements or additions to the documentation. type: bug It doesn't behave as expected. labels Mar 15, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 15, 2026

commit: 6dd4408

@mui-bot
Copy link
Copy Markdown

mui-bot commented Mar 15, 2026

Bundle size report

Bundle Parsed size Gzip size
@base-ui/react 0B(0.00%) 0B(0.00%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

@netlify
Copy link
Copy Markdown

netlify Bot commented Mar 15, 2026

Deploy Preview for base-ui ready!

Name Link
🔨 Latest commit 6dd4408
🔍 Latest deploy log https://app.netlify.com/projects/base-ui/deploys/69ba9c27af1e3b0008664178
😎 Deploy Preview https://deploy-preview-4337--base-ui.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@LukasTy LukasTy marked this pull request as ready for review March 15, 2026 11:57
@LukasTy LukasTy requested a review from flaviendelangle March 15, 2026 11:57
@flaviendelangle
Copy link
Copy Markdown
Member

I think using today will cause issue with hydration on the doc, not just with Argos.
If we build the doc in March and load its Calendar page in April, won't the Calendar flicker from March to April?

@LukasTy
Copy link
Copy Markdown
Member Author

LukasTy commented Mar 16, 2026

I think using today will cause issue with hydration on the doc, not just with Argos. If we build the doc in March and load its Calendar page in April, won't the Calendar flicker from March to April?

Yes, there will still be hydration mismatches, especially for today (current day).
My first course of action was to go for a specific referenceDate on each demo outside of the current month to avoid the today sync issue altogether.
But I noticed competitors having alive today behavior, figured it's nicer to strive for that.

Do you prefer a safe approach, avoiding all hydration/flashing problems altogether?

@flaviendelangle
Copy link
Copy Markdown
Member

But I noticed competitors having alive today behavior, figured it's nicer to strive for that.

What behavior do they have exactly?

@LukasTy
Copy link
Copy Markdown
Member Author

LukasTy commented Mar 16, 2026

But I noticed competitors having alive today behavior, figured it's nicer to strive for that.

What behavior do they have exactly?

Spectrum shows the current month and hydrates the today (i.e., getting back to a stale tab).

@flaviendelangle
Copy link
Copy Markdown
Member

OK so same thing as us right now?

@LukasTy
Copy link
Copy Markdown
Member Author

LukasTy commented Mar 16, 2026

OK so same thing as us right now?

Yup. The basic idea is the same.
I think it's the nicest UX if we can make it work and not have problems on our end, WDYT?
However, I'm checking the today calculation; it looks like one over-optimization might have caused issues with not updating it in all relevant cases.

@flaviendelangle
Copy link
Copy Markdown
Member

If Spectrum is doing that way, I'm fine doing the same at least for now 👍

@michaldudak
Copy link
Copy Markdown
Member

michaldudak commented Mar 16, 2026

Code Review by Claude Code

Overview

The PR overrides Date in the Playwright page context so that visual regression screenshots always render with the same "today" date, preventing daily Argos diffs caused by date-sensitive components (e.g., Calendar). The approach is well-reasoned, and the comments explain clearly why simpler alternatives (class extends Date, page.clock) don't work with @date-fns/tz's TZDateMini.

Notes

  1. timezoneId: 'UTC' — Good addition, ensures timezone-dependent rendering is stable across CI environments.

  2. Reflect.construct approach — Correctly preserves new.target for subclass construction, which is the key requirement for TZDateMini compatibility. The Date() called-as-function branch also correctly matches native behavior (returns a string).

  3. Minor: addInitScript arg parameter — Playwright's addInitScript supports passing an arg parameter, which could replace the string interpolation:

    await page.addInitScript((fakeNowValue) => {
      const __OriginalDate = Date;
      const __DateNowOffset = fakeNowValue - __OriginalDate.now();
      // ...
    }, fakeNow);

    Not a blocker since fakeNow is always a number (no injection risk), but slightly cleaner.

  4. Minor: comment clarity"Calendar PR merge day" is a bit cryptic for future readers. Something like "Fixed date chosen to prevent Argos visual regression diffs from shifting daily" would make the why more obvious at a glance.

Looks good to merge with or without the nits.

@LukasTy
Copy link
Copy Markdown
Member Author

LukasTy commented Mar 16, 2026

The approach suggested by @Janpot works just as well.

  • It also affects the regressions dev server, so, trying out demos locally will have consistent results;
  • It allows providing the class override in JS, instead of plain text, which is way more maintainable.

@michaldudak
Copy link
Copy Markdown
Member

The docs-components-calendar-demos-validation-css-modules demo still looks date-dependent. The min/max dates use the actual current day.

@LukasTy
Copy link
Copy Markdown
Member Author

LukasTy commented Mar 17, 2026

The docs-components-calendar-demos-validation-css-modules demo still looks date-dependent. The min/max dates use the actual current day.

@michaldudak All the demos use the current day, but with this change it would be mocked to be the same for each regression test run (screenshots and Argos diff comparison).
Am I missing something? But I think it shouldn't change between PR runs.

Or are you suggesting changing the validation demo to use specific dates to have the demo more stable in the docs?

@michaldudak
Copy link
Copy Markdown
Member

The latest Argos screenshot for this demo looks like this:
image

Even though the "today" is fixed to be March 13th, it seems that min/max use March 16th (the date Argos ran this)

@LukasTy LukasTy requested a review from colmtuite as a code owner March 17, 2026 14:11
@LukasTy
Copy link
Copy Markdown
Member Author

LukasTy commented Mar 17, 2026

Even though the "today" is fixed to be March 13th, it seems that min/max use March 16th (the date Argos ran this)

Sorry, my bad, I missed this. 🙈
The today constant was being initialized outside of the component in that demo, hence, the override didn't take effect.
Moved today into the component code.

import { ChevronLeft, ChevronRight } from 'lucide-react';
import styles from './calendar.module.css';

const today = new Date();
Copy link
Copy Markdown
Member

@Janpot Janpot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surprises me that this doesn't work, we're importing the demos after mocking the date. Have you checked what's going on? Is this something that was broken in prod or dev or both?

edit: tried it and it seems to work for me 🤔

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In short - bundlers don't seem to guarantee module evaluation order.
P.S. It's reproducible in dev mode as well.
And just to clarify, change here is just for consistency; the main relevant change is in the demos, not experiments.

Here is the full Claude response:

This is a classic module evaluation order issue.

When Vite (or any bundler) processes the import.meta.glob(..., { eager: true }) calls in main.tsx, it eagerly imports all matching demo files. Each module's top-level code executes once, at import time, as part of the module's static evaluation.

Here's the sequence:

  1. import './fakeDateSetup' runs first — it patches globalThis.Date with FakeDate.
  2. The import.meta.glob calls pull in all demo .tsx files. Each module is evaluated (its top-level scope runs).
  3. Inside a demo, const today = new Date() at the module top level executes during step 2 — after the fake is installed — so it should get the faked date.

But that's the happy path. The real problem is that bundlers don't guarantee the evaluation order of eagerly-globbed modules relative to explicit imports in all scenarios. Specifically:

  • With Vite's dev server (ESM), import.meta.glob({ eager: true }) resolves modules that may have already been loaded by the browser as part of the module graph (e.g., via HMR or another import chain). If another entry point or a transitive dependency caused a demo module to load before fakeDateSetup ran, then new Date() at the top level of that module captured the real Date constructor.
  • In production builds, code-splitting and chunk ordering can similarly cause modules to evaluate before fakeDateSetup if they end up in a different chunk or are shared across entry points.

Why moving it inside the component fixes it:

// Module top-level — runs once at import/evaluation time
const today = new Date(); // Captures Date at module eval time (might be real Date)

function MyDemo() {
  // Runs at render time — always after all imports have settled
  const today = new Date(); // Always uses the patched FakeDate
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P.S. It's reproducible in dev mode as well.

How do you reproduce it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert the change in the demo file, run regressions app locally with pnpm test:regression:dev, navigate to http://localhost:5173/docs-components-calendar-demos-validation-css-modules/index.tsx and observe the min and max dates are using actual today instead of mocked.

Copy link
Copy Markdown
Member

@Janpot Janpot Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I did, but it's showing the mocked date for me. I even tried changing the mocked date, and it just follows. Both in prod and in dev.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just double-checked.
Merged with the latest master.
Retried the local build - same.
Tried pnpm test:regressions:build && pnpm test:regressions:server - same.

In both instances, it works exactly the same.
If I move the date initialization outside of the component scope - date faking is not applied.
Anyone else interested in trying?

In any case, CI also confirms this behavior, so I'm not sure if there is a point in keeping this PR open and Argos broken. 🤔

@LukasTy LukasTy merged commit e74faca into mui:master Mar 18, 2026
23 checks passed
@LukasTy LukasTy deleted the docs/stablize-calendar-demo-snapshots branch March 18, 2026 12:57
LukasTy added a commit to LukasTy/material-ui that referenced this pull request May 29, 2026
The previous inline `Date` override was hand-rolled and I incorrectly
described it as mirroring mui-x (mui-x uses sinon `useFakeTimers`; this
bundle does not). Replace the shim with the reviewed approach from
mui/base-ui#4337: `Reflect.construct` preserves the native
`Date.prototype` (timezone-aware date libraries enumerate its own
methods) and forwards `new.target`.

Also pin `timezoneId: 'UTC'` on the playwright page — without it the
frozen instant would render in the CI machine's local timezone and
still churn the baseline. This was missing before.

Kept as an inline classic <script> (rather than base-ui's module +
fixture-extraction in mui#4370): a classic script runs synchronously
before the deferred module bundle, so it beats the eager-glob import
hoisting that mui#4370 worked around — without refactoring the glob
loading that drives the entire VRT suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to the documentation. type: bug It doesn't behave as expected.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants