Skip to content

feat(dev-validation): add reusable dev-mode validation helpers and docs - #6508

Open
rubencarvalho wants to merge 22 commits into
mainfrom
ruben/feat-dev-warning-validation-foundation
Open

feat(dev-validation): add reusable dev-mode validation helpers and docs#6508
rubencarvalho wants to merge 22 commits into
mainfrom
ruben/feat-dev-warning-validation-foundation

Conversation

@rubencarvalho

@rubencarvalho rubencarvalho commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Description

Adds the shared foundation for dev-mode validation warnings in 2nd-gen components: reusable helpers, one gating/dispatch path, a dedup fix, and docs. Foundation only — no component behavior changes. Applying the helpers to components is a follow-up branch.

New helpers in @spectrum-web-components/core/utils, replacing the hand-rolled includes() + window.__swc.warn() checks each component wrote on its own:

  • validateEnum, validateRequiredSlot, validateAllowedChildren
  • warnIf — a general predicate that also covers required and conditionally-required properties and mutually-exclusive combinations

All warn in dev and no-op in production (via the process.env.NODE_ENV gate). They surface an invalid value with a console warning; they do not change it.

One dispatch path: every helper routes through a single private emitWarning, so there is one source of truth for how a warning is gated and emitted.

Dedup fix: the window.__swc.warn key now includes the message (localName:type:level…:message), so two different warnings on the same component no longer suppress each other. Known limitation: identical messages across multiple instances of one component still collapse to one (escape hatch: window.__swc.verbose = true).

isDebug() helper: gates expensive call-site checks (e.g. DOM traversals) so they do not run in production.

Docs and process: a Debug and validation style-guide page (all validation categories, a lifecycle-hook quick-reference, and a "Known limitations" section); a consumer-facing "Dev mode warnings" Storybook guide; and validation checklist items added to the migration workflow and the code-conformance rule.

Out of scope (tracked as follow-ups)

  • Applying the helpers across components (the next branch; includes retrofitting Badge / Tabs / ProgressCircle / IllustratedMessage's ad hoc warn() calls).
  • A real dev/prod dual-build split so warnings are guaranteed stripped from production bundles. This package currently ships the process.env.NODE_ENV gate unreplaced.

Motivation and context

Dev-warning validation exists but is inconsistently applied (~4 components, each with hand-rolled checks), and several requested categories (conditionally-required properties, mutually-exclusive combinations, required slots, allowed children) had no shared helper at all. This PR builds the reusable, documented foundation so the next phase — applying validation to every component — is mechanical and consistent instead of another one-off per component.

Related issue(s)

  • N/A (internal tooling/consistency initiative, no linked issue)

Screenshots (if appropriate)

N/A — no visual changes. New Storybook guide page: "Guides/Dev mode warnings".

Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • New helpers behave correctly

    1. Open 2nd-gen/packages/core/utils/test/dev-validation.test.ts in Storybook (or run yarn vitest --run --project storybook ../core/utils/test/dev-validation.test.ts from 2nd-gen/packages/swc)
    2. Confirm all 8 validation steps pass (enum / warnIf / required-slot / allowed-children, fires and does-not-fire cases)
    3. Expect all green
  • Dedup fix doesn't break existing warning tests

    1. Run yarn vitest --run --project storybook components/badge components/tabs components/progress-circle components/illustrated-message from 2nd-gen/packages/swc
    2. Expect all 119 tests to still pass unchanged
  • New Storybook guide renders

    1. Run Storybook locally (yarn storybook from 2nd-gen/packages/swc)
    2. Navigate to Guides → Dev mode warnings
    3. Expect the page to render with no console errors

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

Accessibility testing checklist

  • Keyboard: No focusable parts introduced by this change (utility functions and documentation only); confirm no regressions in the new Storybook guide page's own navigation (standard Storybook Docs page keyboard behavior, unchanged).
  • Screen reader: No new DOM/ARIA surface introduced; confirm the new "Dev mode warnings" Storybook Docs page has correct heading structure (headings read in order: h1 "Dev mode warnings", h2 "What triggers a warning" / "Only in development" / "Configuring which warnings you see") when navigated with a screen reader's heading navigation (e.g. VoiceOver VO+Cmd+H, NVDA H).

Lays the foundation for consistent dev-mode validation warnings across
2nd-gen components: shared helpers (validateEnum, warnIf,
validateRequiredSlot, validateAllowedChildren) in
@spectrum-web-components/core/utils replace the hand-rolled
includes()+warn() checks each component previously wrote independently.

Also fixes window.__swc.warn's dedup key (adds the message to
localName:type:level) so two distinct warnings on the same component no
longer silently suppress each other.

Documents the full set of validation categories (enum, required,
conditionally required, mutually exclusive/no-effect, required slots,
allowed children, component-specific quirks via warnIf) in the style
guide and the washing-machine migration workflow, and adds a consumer-
facing Storybook guide explaining how the warnings work.
@changeset-bot

changeset-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 397d9ec

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

📚 Branch Preview Links

🔍 Gen1 Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-6508

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

@coveralls

coveralls commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 30485771433

Coverage remained the same at 96.241%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 39268
Covered Lines: 37993
Line Coverage: 96.75%
Relevant Branches: 6465
Covered Branches: 6021
Branch Coverage: 93.13%
Branches in Coverage %: Yes
Coverage Strength: 460.86 hits per line

💛 - Coveralls

The rest of the repo's docs use "2nd-gen" as an established convention
(120+ files), so pre-existing prose in touched files is left as-is;
this only fixes the one line of net-new prose this PR introduced.
Turns the "no guaranteed production stripping" known limitation into
actionable guidance: concrete recipes for webpack/Next.js, Vite,
esbuild, and Rollup to confirm process.env.NODE_ENV is actually
replaced and the resulting dead branch removed, plus the no-bundler
ReferenceError case. Addresses the concern that components could ship
dev-only validation code and message strings to production bundles by
default.
rubencarvalho and others added 5 commits July 15, 2026 13:45
…DEBUG

Each helper in dev-validation.ts now returns immediately when
process.env.NODE_ENV === 'production', ahead of the existing
window.__swc?.DEBUG check. This makes the internal engine logic
(dedup lookup, message formatting, the window.__swc.warn call)
provably dead code to a bundler that replaces process.env.NODE_ENV
and minifies, since it's now gated on a build-time-foldable literal
rather than solely a runtime read on a global that no bundler can
prove false across module boundaries.

Caught during testing: an earlier version of this gate matched
spectrum-element.ts's exact `=== 'development'` check, which broke
every dev-validation test, because this repo's own Vitest environment
sets NODE_ENV to 'test', not 'development'. Using `=== 'production'`
(opt out of exactly production, rather than opt in to exactly
development) is both correct and safer, since it only disables
validation in a build that explicitly declares itself production,
leaving development/test/anything-else unaffected.

This does not remove call sites or their argument evaluation (still a
small, bounded per-call cost), and this package's own build still does
not perform the NODE_ENV substitution before publishing. Both are
documented as remaining gaps in the "Known limitations" section, which
also now correctly describes what stripping does and does not achieve
(the previous wording overstated it). A full dual dev/prod build
(React's require()-branch pattern) is noted as the more complete,
much larger follow-up.
…te cost

State the call-site overhead as a fact, not a fact plus a reassurance
that it's fine.
… into ruben/feat-dev-warning-validation-foundation
@miwha-adobe
miwha-adobe marked this pull request as ready for review July 24, 2026 18:47
@miwha-adobe
miwha-adobe requested a review from a team as a code owner July 24, 2026 18:47
* The version of the core base package.
*/
export const coreVersion = '0.3.0';
export const coreVersion = '2.0.0-beta.2';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am not sure where this came from. Is this the right call to update this version to 2.0.0-beta.2? Our latest release was Since 2.0.0-beta.1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we actually should make a ticket to remove this and disable the script thats updating it. its no longer coupled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed here: #6510

* is deduplicated. Exported so the dedup key can be unit tested without relying
* on the `NODE_ENV`-gated `window.__swc` setup below.
*/
export function warningId(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Extracted warningId so the dedup-key fix could be unit tested


## Configuring which warnings you see

Each distinct warning message fires once per element type per page load, not once per problem occurrence; this keeps a single repeated mistake from flooding your console. Two different problems on the same component (even ones that share a warning type and severity) each get their own warning; the one case that's still collapsed is **multiple instances of the same component with the identical problem**: ten badges all set to the same invalid `variant` produce one console warning, not ten, from whichever badge updates first.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Trade off: any mid-iteration or a re-introduced mistake goes silent after the first time. Is it worth the trade off?

@caseyisonit caseyisonit Jul 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i would argue no its not worth the trade off. a warning should fire always and if its making the console noisy, thats the point, fix this code. it also can help us debug rendering issues if something is firing too much or not enough. all mistakes should be documented and communicated.

the one case that's still collapsed is multiple instances of the same component with the identical problem: ten badges all set to the same invalid variant produce one console warning, not ten, from whichever badge updates first.

The message in our warnings includes where this error occurs. i would be irrated if i was only warned once, fix it in the spot the message told me and then get the same message again and have to do that 10 more times to clear all warnings. I would rather have a list of all issues, fix it and then rerun my server to check all warnings are clear.

we shouldnt be making the dev experience easier to work with warnings, dev mode is when everything should be firing and getting fixed before prod. by hiding any message we are doing a disservice in my opinion.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Following up on my earlier comment: I misread the doc, so please disregard my "goes silent after the first time" worry. I verified the actual behavior which is:

  1. Errors surface on every update as you develop, not just once on initial render.

  2. A page reload also resets the dedup, so nothing goes quiet while you iterate. This was my main concern about things being missed or going silent.

  3. Four components with the same invalid value produce 1 warning, not 4 (deduped by message). I am alright with this pattern personally, though I can see the argument in wanting to intentionally make it noisy so the developer fixes the issues. As a thought, perhaps there is something we could do to say "You have one or more instances of error xyz.....".

My biggest concern was mid-iteration or reintroduced issues being un-captured. But I did some manual testing and that doesn't seem to be the case.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@caseyisonit @miwha-adobe I feel you both are right about a different problem.
Only warn once means you have 10 broken badges, we point you at one, you fix it, reload and we point you at the next one. Ten times. @caseyisonit your complaint is right and its fair here.

Now warning every time means a table with 500 rows bound to one bad valye gives you 500 console messages each holding onto a live DOM element. The devtools will lock up here for just one bug.

I would recommend we warn once per broken element but group them into a single console line. One message with all ten elements listed inside it and a count. This way we fix one root cause and we already knew there were ten.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is what I am proposing (example just for viewing. Not commiting the storybook instance)

Screenshot 2026-07-29 at 12 35 49 PM

return `${localName}:${type}:${level}:${message}` as BrandedSWCWarningID;
}

if (process.env.NODE_ENV === 'development') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

While "development" seems like standard practice, if a consumer stores env as 'dev', 'prod', 'staging', or NODE_ENV unset, this will fail and falls into a gap: no warnings appear, and the code isn't stripped either (it runs and no-ops).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i would be down to extend this to cover potential gaps, great call out about staging too. prod and production should turn everything off.

@Rajdeepc Rajdeepc Jul 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@miwha-adobe @caseyisonit Only the exact word production can actually be deleted from our bundle. That's the specific string every build tool will look for.
prod can be made to behave correctly with no warnings but the code will still ship. This is something we cannot fix from inside the package, we need to amplify this saying in the consumer docs so nobody assumed they are equivalent.

@miwha-adobe miwha-adobe Jul 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@Rajdeepc curious for your take.

We enable warnings on NODE_ENV === 'development' but strip on === 'production'. Anything in between (staging, test, unset, non-standard dev) gets no warnings and no stripping.

Do consumers realistically use values like dev, and do we want warnings in staging?

@Rajdeepc Rajdeepc Jul 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does anyone really use dev? test should be hit automatically by vitest and Jest, I am not sure about unset thought.
Also do we want warnings in staging, we cant answer this from inside the package. Its a deploy target not a build type. What we can fix is that there is currently no switch. window.__swc.warn only exists inside the development branch, so DEBUG = true does nothing anywhere else. This is a bug regardless of which strings we enumerate.
I would recommend we flip the default to be on unless production.

@miwha-adobe miwha-adobe Jul 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I agree with that assessment, and good catch about the bug! That is something I did not consider.

miwha-adobe and others added 9 commits July 24, 2026 14:46
… into ruben/feat-dev-warning-validation-foundation
… into ruben/feat-dev-warning-validation-foundation
Exposes the same NODE_ENV/DEBUG predicate emitWarning uses so call sites can
guard warnings whose condition or message is expensive to compute (e.g. a DOM
traversal) and skip that work when validation is off. Cheap conditions should
still call warnIf/validateEnum directly, which gate internally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@miwha-adobe miwha-adobe added the Status:Ready for review PR ready for review or re-review. label Jul 30, 2026
@miwha-adobe miwha-adobe added 2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. run_vrt Triggers the Chromatic VRT run for 2nd-gen labels Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. run_vrt Triggers the Chromatic VRT run for 2nd-gen Status:Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants