Skip to content

refactor fix: forbid zero with a length unit across tokens, lint and docs - #830

Merged
robsongajunior merged 17 commits into
mainfrom
fix/zero-length-no-unit
Jul 30, 2026
Merged

refactor fix: forbid zero with a length unit across tokens, lint and docs#830
robsongajunior merged 17 commits into
mainfrom
fix/zero-length-no-unit

Conversation

@isaquebock

@isaquebock isaquebock commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

A zero length is identical in every unit, so the unit is pure noise — and it made the same zero read three different ways across the codebase (0px, 0rem, 0em). This forbids zero with a length unit and gates it on every authoring surface, so no path escapes it.

  • Rule.claude/rules/styling.md gains the "A zero length carries no unit" section (length units only; 0%, 0s/0ms, 0deg, 0fr are untouched). Mirrored into the shipped webkit-tokens.md and documented in packages/webkit/docs/DOC_LINTS.md.
  • Token checkszero-with-unit added to the shared engine in token-checks.js, so it blocks write-time (hook) and in the check-authoring CI ratchet over component sources.
  • Stylelintlength-zero-no-unit enabled in .stylelintrc.json and in the shipped stylelint-config.js, set to plain true so the preset's ignore: ['custom-properties'] does not apply (a design system is authored almost entirely as custom properties). A lint-canaries/stylelint/length-zero-no-unit.css fixture keeps that from being silently relaxed.
  • Theme tokenstracking.normal emits 0 instead of 0em, with a build-time assertion in build:tokens. Token values are authored in JS and compiled, so build:tokens is the only gate that sees them.
  • Fixes — the two existing violations (accordion-content.vue, toaster.vue).

Notes

  • No new dependencies. No breaking change — bump is patch for theme and webkit.
  • The .claude/rules + skills edits ship with the code on purpose: packages/webkit/test/standards/invariant.test.mjs pairs each rule to its gates, so splitting the doc from the enforcement would fail CI on whichever side landed first.
  • packages/theme/dist/v4/globals.{css,scss} changes are the regenerated build output of the tracking.normal token.

The one carve-out: a zero inside a math function

Inside calc() / min() / max() / clamp() a bare 0 is a number, not a length, so CSS requires the unit and the whole expression is invalid without it. Stylelint's length-zero-no-unit skips math functions for exactly that reason — which means it accepts calc(100% - 0px).

So where the unit is unavoidable, it is rem (the one length unit this system scales in), and the two engines we own enforce it: a new zero-unit-in-calc token check and the same assertion in build:tokens flag any non-rem unit on a zero inside a math function, while zero-with-unit stops reporting the zeros that legitimately keep a unit there. A test pins stylelint's skip so the split of responsibility stays visible.

Comment thread packages/theme/src/scripts/build-tokens.mjs
@isaquebock
isaquebock changed the base branch from dev to main July 29, 2026 13:28
@isaquebock
isaquebock requested a review from a team July 29, 2026 13:28

@robsongajunior robsongajunior left a comment

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.

pr está poluído. nao sei onde termina o review

@robsongajunior robsongajunior added the WIP Work in Progress label Jul 29, 2026
@isaquebock
isaquebock force-pushed the fix/zero-length-no-unit branch from 3d93d4d to 4f29d7a Compare July 29, 2026 13:41
…n build:tokens

A zero length is identical in every unit, so the unit is noise: --tracking-normal
shipped as 0em. Tokens are authored in JS and compiled, so no linter reads them —
build:tokens now asserts the generated CSS carries no zero with a length unit and
fails with the offending custom property. Units that mean something at zero (0%,
0s, 0deg, 0fr) are out of scope.
…ylelint config

Adds a zero-with-unit check to the shared token-checks engine, so the write-time
hook and the check-authoring CI ratchet both block it, and length-zero-no-unit to
the shipped stylelint config so consumers inherit the rule. The config sets it
without stylelint's default ignore of custom properties: a design system is
authored almost entirely as custom properties, which is where the drift appeared.

Fixes the three call sites the new check surfaced (accordion-content, toaster),
which set a height of '0px' through inline style. Only length units are in scope,
matching stylelint — 0%, 0s, 0deg and 0fr are untouched.
Overrides the stylelint-config-standard-scss default, which ignores custom
properties, to plain true — the ignore is precisely why --tracking-normal: 0em
passed. The canary fixture uses a custom property on purpose, so reintroducing
the ignore breaks the lint-canary job.
…d DOC_LINTS

styling.md gains the rule plus the four surfaces that enforce it; webkit-tokens.md
carries the consumer-facing form; component-scaffold and token-map gain the
prescription and a checklist item, with token-map told to map a Figma 0px/0em to
0; DOC_LINTS documents the stylelint rule and why the custom-property ignore was
dropped.
A bare 0 inside calc()/min()/max()/clamp() is a number, not a length, so CSS
requires the unit there — stylelint's length-zero-no-unit skips math functions
for that reason and therefore accepts calc(100% - 0px). The two engines we own
now close that: a new zero-unit-in-calc token check and the build:tokens
assertion flag any non-rem unit on a zero inside a math function, while
zero-with-unit stops reporting the zeros that legitimately keep a unit there.

Documents the carve-out in styling.md, webkit-tokens.md, DOC_LINTS.md and the
two skills, pins the stylelint skip with a test, and drops the verbose
explanatory comments from both guards.
…op its canary

The fixture now carries only unit-less zeros, so it no longer violates
length-zero-no-unit and cannot act as a canary — its registry entry is removed
and styling.md points at the stylelint-config tests as what keeps the rule from
being relaxed.
@isaquebock
isaquebock force-pushed the fix/zero-length-no-unit branch from 4f29d7a to 90ba4a0 Compare July 29, 2026 13:44
@robsongajunior robsongajunior removed the WIP Work in Progress label Jul 29, 2026
robsongajunior and others added 2 commits July 29, 2026 18:12
- webkit-tokens.md: the Wrong example shows m-[0px] — m-0 is the correct
  form and read as a fourth violation
- token-checks + build:tokens: i flag on both zero regexes (CSS units
  are case-insensitive), with comments pinning the one-level nesting
  limit and the cross-package sync requirement
- validate-tokens hook: scope via the shared tokenChecksApply so test
  files are exempt like the CI ratchet (the browser serializes a
  collapsed height as '0px'; type tests use @ts-expect-error)
- token-checks.test.mjs: pins both regexes and the shared file filter
@robsongajunior
robsongajunior merged commit 54a3502 into main Jul 30, 2026
23 checks passed
@robsongajunior
robsongajunior deleted the fix/zero-length-no-unit branch July 30, 2026 13:02
@robsongajunior robsongajunior changed the title [NO-ISSUE] fix: forbid zero with a length unit across tokens, lint and docs refactor fix: forbid zero with a length unit across tokens, lint and docs Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants