|
Download PARITY.md from EHS-Studio/dssl: direct link, hf CLI and curl.
- Browser
- Download file 21.5 kB
-
https://huggingface.co/EHS-Studio/dssl/resolve/main/PARITY.md
- Command line
-
hf download hf://EHS-Studio/dssl/PARITY.md
-
curl -L -o PARITY.md https://huggingface.co/EHS-Studio/dssl/resolve/main/PARITY.md
21.5 kB
| # PARITY.md: DSSL vs CSS, honestly | |
| The point of this table is to stop two bad arguments: "DSSL is missing everything" and | |
| "DSSL replaces all of CSS". Both are false. Here is the ledger. Status: shipped / partial | |
| / planned / by-design-no. | |
| | CSS feature | In DSSL | Status | Notes | | |
| |---|---|---|---| | |
| | Type / class / id / attribute selectors | native (rides browser matching) | shipped | | | |
| | Combinators (`>`, `+`, `~`, descendant) | native | shipped | | | |
| | Nesting | native, `&` substitution | shipped | predates CSS nesting; no build step needed | | |
| | `:nth-child()`, `:nth-of-type()`, `:not()`, `:is()`, `:where()`, `:has()` | native | shipped | the browser does the matching; v0.7 docs say so loudly | | |
| | Pseudo-classes (`:hover`, `:focus`...) | engine-native states as nested blocks | shipped | real events, enter + leave, descendants re-evaluated; `seen` (first viewport entry) has no CSS equivalent | | |
| | Pseudo-elements | one compiled shadow stylesheet | shipped | the ONE place DSSL emits real CSS; inline styles cannot reach them | | |
| | `@media` queries | `@media (max-width: 860)`, sugar: `dark`, `light`, `reduced-motion`, bare widths, `<=` ranges | shipped | | | |
| | `@container` queries | `@container (min-width: 480)` | shipped | nearest ancestor (or self) with container-type | | |
| | `@layer` cascade layers | `@layer base\|components\|utilities` + `pin {}` | shipped | cascade is (layer, source order); no specificity, no !important | | |
| | `@supports` | `raw { }` passes it through verbatim | partial | no sugar; the engine feature-detects itself, and `dssl import` moves it into `raw { }` | | |
| | `@font-face` | `raw { }` passes it through verbatim (semicolons required inside) | partial | `font.*` tokens can name any loaded family; a missing `;` in `raw` is a warning with a line number | | |
| | `@keyframes` | `motion name { from {} to {} }` via WAAPI | shipped | `animate: name 600 ease-out infinite alternate` | | |
| | View transitions | not implemented | planned | | | |
| | Scroll-driven animations | not implemented | planned | pairs naturally with `seen` + `stagger` | | |
| | Custom properties (`--*`) | pass through unvalidated, plus dotted token re-declaration per subtree | shipped | `--color.accent: color.err` re-themes a subtree via inheritance | | |
| | Design tokens | first-class: `tokens { }`, numeric keys, alpha suffix `/85`, scoped per file and subtree | shipped | no CSS-native equivalent at this ergonomics level | | |
| | Flexbox / Grid | raw properties pass through + `row:` `column:` `grid:` `stack:` sugar | shipped | shorthands write `display`; the compiler warns when that clobbers an explicit `display:` | | |
| | `place-items` / alignment | `center: both` | shipped | the engine promotes a formatting context when missing, so alignment cannot silently no-op | | |
| | Transitions | per-property: `transition: 200 snap transform` | shipped | honors `reduced-motion` | | |
| | Background patterns and noise | `texture:` (40 presets including ten wood species, 11 generators, your own) | shipped | there is no CSS shorthand for "grain on top of my background"; this one keeps the author's background and removes cleanly. Runtime-only | | |
| | Glow, particles, fire | `aura-glow`, `aura-particle`, `glow-particle`, `aura-glow-particle`, `aura:` | shipped | CSS has `box-shadow` and `filter: drop-shadow`, and nothing for particles. No presets by design. Runtime-only | | |
| | Styling `<input type="range">` | `slider:` writes the `::-webkit-slider-*` and `::-moz-range-*` rules together | shipped | Chrome tested; the Firefox rules are generated but not run yet. Histogram sliders via `graph` | | |
| | Accessibility hints in the compiler | contrast, removed focus outline, small targets (warnings) | shipped | CSS says nothing; these never fail a build unless you pass `--strict` | | |
| | Moving existing CSS over | `dssl import styles.css` | shipped | converts the mechanical differences and reports the rest; the output always compiles | | |
| | print styles (`@media print`) | not implemented | planned | | | |
| | `!important` | by design no | by-design-no | `pin { }` is the must-win block; specificity wars are the thing DSSL deletes | | |
| | Cascade | (layer, source order) | shipped | later wins; identical selectors resolve by order, deterministically | | |
| | Static extraction / view-source styling | `dssl build` emits real CSS (v0.8.0); prerender snapshot for live pages planned | partially shipped | freeze() already makes big static pages zero-cost after first paint | | |
| | DevTools story | `DSSL.explain(el)` returns the cascade as data | shipped | matched rules with file/line, winning declaration per property, pseudo rules; browser-extension panel planned | | |
| | Work without JavaScript | `dssl build` emits the static subset as real CSS (v0.8.0); `@signal`/`animate:` stay runtime-only and counted | shipped for the static subset; prerender snapshot still planned for live pages | the honest trade has an exit now | | |
| ## The exit story | |
| Abandoning DSSL leaves you: a DOM of valid inline styles, one small `<style>` element | |
| (pseudo-element rules), and token values sitting in `--dssl-*` custom properties. The page | |
| keeps working with the engine removed. No other styling replacement can say that, because | |
| no other replacement styles with the platform's own primitives. | |
| And since v0.8.0 there is a second exit: `dssl build styles.dssl -o styles.css` | |
| compiles the static subset to a real stylesheet before you ever ship, no runtime, | |
| no inline styles, CSP-clean, works without JavaScript. Tokens become custom properties, | |
| states become pseudo-classes, layers become `@layer`, motions become `@keyframes`, | |
| `pin` becomes `!important`, `raw { }` passes through verbatim, `seen` applies its final | |
| state. `@signal` rules and `animate:` stay runtime-only and are counted in the report, | |
| so the boundary is explicit, not folklore. | |
| ## What DSSL adds that CSS does not have | |
| - live tokens and signals: change one value or fire one bridge call, the whole DOM re-resolves | |
| - `seen` reveal-on-scroll and `stagger` choreography with zero JavaScript | |
| - the deform family composing into the transform channel | |
| - gradients as sentences with compile-time stop validation | |
| - the overlap policy: translucent boxes refuse to cover things unless you say so | |
| - loud errors with did-you-mean, and a warnings channel for cascade-order footguns | |
| - `freeze()` / `unfreeze()`: a live engine that can be parked at zero cost | |
| ## v0.16.2 | |
| `DSSL.lint`'s `css-loses` skips the output of `dssl build` (recognised by its `@layer tokens, base, components, utilities, pin` statement), because its reset is built to sit under the engine. The docs page of this site went from 4,314 false findings (0.16.0), to 111 (0.16.1), to none from its own CSS. | |
| ## v0.16.1 | |
| `DSSL.lint`'s `css-loses` ignores a CSS rule whose value is the same as the inline value DSSL wrote (found on this site, which ships compiled CSS and runs the engine: 4,314 false findings on the docs page). | |
| ## v0.16.0: round three | |
| | Item from the game builder | Status | | |
| |---|---| | |
| | Normal CSS loses to DSSL's inline styles (needed `!important`) | done: `yield:` hands properties back; `DSSL.lint` finds the losers (`css-loses`). Still true: without `yield`, an inline style beats a stylesheet. | | |
| | `env()` and `font-variant-numeric: tabular-nums` refused | done: the known words and properties come from the MDN CSS data | | |
| | `radius { pill: 999 card: 22 }` read as one value, error at line 0 | done: two tokens; errors point at the token's line | | |
| | Aura glows the box, not the shape | done: follows clip-path, border-radius, rotate/scale/transform, or `around ...`; `alpha` for images and canvases | | |
| | More aura types | done: inner glow, rays, ripples, 9 particle shapes, palette, spin, wind, orbit | | |
| | More textures | done: 33 presets, 9 generators | | |
| Not verified: Firefox and Safari. `around alpha` needs an `<img>`, `<canvas>` or `<video>` (a `div` with a `background-image` is not readable). The shape follows the element's own rotate, scale and transform, not a rotation on a parent. | |
| ## v0.15.0: the second game review | |
| | Review item | Status | | |
| |---|---| | |
| | Aura redraws 1.5 times a second under reduced motion | done: follows what moves, rests when nothing does | | |
| | Aura emitters linger when hidden | done: invisible emitters (opacity 0, display none, visibility hidden, on it or an ancestor) stop | | |
| | One canvas on `<body>`, never behind a hill | done as `z N` per layer; interleaves with the root stacking context only | | |
| | Silent file-order losses | done: `dssl check`, console and `DSSL.lint` name rules a later rule always beats | | |
| | Styling is asynchronous | done: `DSSL.flush()`, `await DSSL.settled()`, `DSSL.computed()` | | |
| | Signals typed | done | | |
| | `DSSL.lint` noisy | done: invisible text skipped, outline and tight shadow count | | |
| | `DSSL.signal` costs 50 ms | done: only the elements its rules select are restyled | | |
| | Several `animate:` per element | done | | |
| | Scoped signals | done (`DSSL.signal(name, value, el)`) | | |
| | `outline-shape` | done (square corners, uses `filter`) | | |
| | `DSSL.play` on pseudo-elements | done | | |
| | CLI `--engine` | done | | |
| | Test helpers (final value, pause, step) | done | | |
| | `rotate: 5`, nested state in `::after`, `font:` trap, gradient docs | done | | |
| Not verified: Firefox and Safari. Scoped signals on `::before`/`::after` use a marker attribute the engine keeps on the element. | |
| ## v0.14.0: made for games | |
| From a review by an agent that built a game on DSSL. Twelve problems and ten wishes; each has an assertion in `scripts/test-v0140.mjs`. | |
| | Review item | Status | | |
| |---|---| | |
| | Reduced motion silently kills every `animate:` | done: console says so once; `essential`, `DSSL.motion('full')`, `data-dssl-motion` | | |
| | `gradient:` and `background-image:` overwrite each other | done: warning; layers with `+` | | |
| | `animation-delay` etc. compile and do nothing | done: warning that names the replacement; `delay`/`jitter`/`iterations` words | | |
| | Motions scoped to one file | done: shared registry, same as tokens | | |
| | `paint-order: stroke fill`, `conic-gradient(from 0deg ...)` rejected | done | | |
| | `shadow: pop`, `radius: pill` for custom tokens | done | | |
| | Console noise "folded border-color" | done: removed | | |
| | Lint misses cross-rule contrast, false align warning | done: `DSSL.lint(root)` computes it on the live page; the align warning moved to `dssl build`'s report where it applies | | |
| | Docs drift | done: docs checked by a test against the real API and file list | | |
| | `0% { }` keyframes | done | | |
| | Engine overwrites inline styles | done: author styles win | | |
| | Static subtree, play(), scoped aura, `check index.html`, richer `@signal`, gradient sugar, motions on pseudo-elements, `text-stroke`, per-element variation, runtime lint | done | | |
| Not verified: Firefox and Safari (all checks ran in Chrome). The engine is still not at CSS parity for startup (see v0.13.0 below). | |
| ## v0.13.0: the compiled page is the page | |
| | Promise | Status | Evidence | | |
| |---|---|---| | |
| | With JavaScript off, a compiled page renders its static styles correctly on first paint | done | test-static-parity.mjs: 7 pages, 5,000+ elements, identical to the engine (demo elements for engine-only features excluded); the site itself ships this way | | |
| | Startup within 10% of equivalent CSS | done for the compiled page | bench-compiled.mjs: +0% (default build), -4% (`--inline-tokens`) | | |
| | Churn at parity | done with `--inline-tokens` (0%); default build +7% churn, +20% on an inherited-value restyle | same benchmark | | |
| | The same for the runtime engine | **no**: load 17x, churn 4x | the engine applies inline styles; a hybrid mode that skips what the CSS already did is not built | | |
| | SSR | done | `public/dssl/server.js` (no DOM), real React `renderToStaticMarkup` tested; the site links compiled CSS in its server-rendered head | | |
| | Strict CSP | done | no `<style>` made by script, zero violations under `style-src 'self'` with the engine, texture, slider, tilt, aura and inspector; textures need `img-src data:` | | |
| | Devtools show .dssl source lines | done for built CSS | `--sourcemap`; Chrome's own DevTools shows `card.dssl:4` (checked in a real DevTools window). Runtime inline styles cannot carry a source: use `DSSL.inspect()` | | |
| Bugs the comparison found, now fixed: compiled selectors had CSS specificity (now `:where()`), a later `border-style` or | |
| `border-width` was dropped by the engine, a margin or padding shorthand did not reset an earlier longhand, the reset was unlayered. | |
| ## v0.12.0: the rest of the wood list | |
| Fonts, richer gradients, an inspector, hot reload and mirrored tiling, from the same critic's list (rows marked "done in 0.12.0" below). | |
| ## v0.11.0 (wood): rings, species, planks, baked textures | |
| Written after another AI built a wood site in DSSL and listed what hurt. What came of it: | |
| | Their note | What DSSL does now | | |
| |---|---| | |
| | `texture: wood` is one generic grain | `oak`, `walnut`, `cherry`, `maple`, `pine`, `mahogany`, `ebony`, `birch`, plus `oak-planks` and `walnut-planks` | | |
| | Stretched noise with a contour layer is not wood | the `rings` generator draws growth rings as curves, with `figure`, `warp`, `pores` and `knots` | | |
| | No way to draw planks | `planks <px>`: boards with seams and a slightly different board each | | |
| | Seamless tiling, stable seed | integer harmonics and a seeded generator: same seed, same board everywhere; tested for seams | | |
| | `counter-reset` and `counter()` rejected | they work | | |
| | Contrast false alarms on translucent backgrounds | judged on the light page, white and black; warns only if it fails on all | | |
| | Flash of unstyled content | `dssl-ready` class on `<html>`, and `dssl build` for the static part (the wood page does this) | | |
| | Textures missing from `dssl build` | baked into the CSS when colours are known; the report names the ones that could not be | | |
| | `@font-face` in `raw {}` | done in 0.12.0: a `fonts { }` block (Google families, variable ranges, your own files) | | |
| | Gradients only cover simple cases | done in 0.12.0: repeating, colour spaces, midpoints, hard bands, `mesh`, layers | | |
| | Dev-tools inspector for which rule produced a style | done in 0.12.0 as an in-page panel, `DSSL.inspect()`. A real browser DevTools panel would need an extension and is still not done. | | |
| | Hot reload | done in 0.12.0: `DSSL.watch()` and `dssl dev` | | |
| | The wood tile repeats every 576px | `texture: ... symmetrical` mirrors the tile, so it never shows a seam | | |
| ## v0.10.0: glass, tilt, and a silent colour bug | |
| | Need | DSSL | Status | Notes | | |
| |---|---|---|---| | |
| | Frosted glass | `glass: 18 saturate 1.6` | shipped | plain CSS, survives `dssl build`; CSS needs `backdrop-filter` plus a matching background | | |
| | Pointer-following 3D tilt | `tilt: 10 lift 1.04 glare` | shipped | engine only; flat under reduced motion, ignores touch; not combinable with bend or fold | | |
| | Alpha on a named colour | `white/12` | fixed | used to compile to `white/12px` and be dropped by the browser with no error | | |
| ## v0.9.0: texture, aura, sliders, and what testing them found | |
| New language: `texture`, the aura family, `slider`, lint warnings, `dssl import`, and an | |
| interactive designer in the editor extension. Writing real-browser tests for them found bugs | |
| that were fixed in the same release: | |
| - `aura-glow: none` also wiped the particles slot. Each `none` now clears only its own slot. | |
| - With reduced motion the loop kept redrawing a still glow 60 times a second. It now redraws on | |
| scroll, resize and a slow poll only (and still follows the page when you scroll). | |
| - A glow was re-blurred every frame (11 ms for 40 glows). Each glow is now painted once into a | |
| cached bitmap and re-lit per frame (0.6 ms). | |
| - `freeze()` stopped aura but not the texture shimmer. It stops both. | |
| - A slider clause with a leftover word (`thumb 16 #fff shape bar`) was silently ignored. It is an error. | |
| - A typo in an aura (`speedd 4`) was swallowed as a colour and never got its did-you-mean. | |
| - `dssl build` did not count engine-native declarations inside a rule that kept other declarations, | |
| so it said nothing about the aura or texture it dropped. It now lists them. | |
| - The downloadable package and the editor extension each copied a hardcoded list of engine files, | |
| so a new module would have crashed them. They copy the whole folder now, and a test guards it. | |
| - `importCss` threw on a stray `@`. It never throws now. | |
| The contrast lint also found four real contrast failures in this site's own stylesheets. | |
| ## v0.8.2: what rebuilding the site found | |
| - Cross-stylesheet cascade. "Later wins" held inside one sheet and not between | |
| sheets: every sheet numbered its rules from zero and the engine compared those | |
| numbers across sheets, so a later sheet lost to an earlier one whenever the | |
| earlier one had more rules above the rule in question. Every rule now has one | |
| position in load order. A consequence: a later `tokens { }` block now overrides | |
| a token an earlier sheet set inside `@media` or `@signal`. | |
| - `fluid` works on the longhands (`padding-top`, `margin-block`, `letter-spacing`, | |
| `border-radius` and the rest), not only on the four-sided shorthands. | |
| ## v0.8.1 follow-up corrections | |
| A re-test of 0.8.0 found two bugs, and more showed up while writing the tests: | |
| - `fluid` left out the intercept of its line, so `fluid 16 24` was 19.6px on a | |
| 375px phone instead of 16.1px and reached 24px at about 840px instead of 1200px. | |
| Fixed, and the tests now evaluate the emitted `clamp()` at real window widths. | |
| The same fix repaired shrinking sizes (`fluid 24 16`, which produced a backwards | |
| `clamp()`) and rem sizes (the slope was 16 times too small). Percentages and | |
| mixed units are now refused with a message instead of producing nonsense. | |
| - A multi-line `raw { }` started with the text `undefined`, and anything written | |
| on the same line as `raw {` was dropped. The guide's own example also had no | |
| semicolons, which is not valid CSS. All three fixed, and a missing semicolon | |
| inside `raw { }` is now a warning with the right line number. | |
| - `dssl build` now takes `--strict`, like `dssl check`. | |
| - The `dssl` command in the downloadable package crashed on start (module not found), | |
| because it looked for the engine in `public/dssl/` and the package keeps it in | |
| `dssl/`. It now finds it in either place, and a test runs it from the package layout. | |
| ## v0.8.0 review-driven corrections | |
| The second external review (repro snippets, headless browser) found five bugs and | |
| asked for the static build first. All fixed or shipped: | |
| - `row: gap 8 between center`, the guide's own example, kept its `between` | |
| (every place word used to write both axes; the later word overwrote | |
| `justify-content` silently). Place words now own an axis: between/around/evenly | |
| are main-axis, stretch/baseline cross-axis, center/start/end fill free axes; | |
| conflicts and duplicates warn loudly. | |
| - `@font-face { ... }` crashed the compiler with an uncaught throw. Unknown | |
| at-rules now skip their entire balanced block with one clean error. | |
| - `@supports (...) { .a { ... } }` used to error AND still apply `.a` | |
| unconditionally. The whole block is skipped now; nothing leaks. | |
| - Unquoted `url(a.png)` false-errored ("unknown value a.png"); `url()` is | |
| atomic now, and the missing background words (`no-repeat`, `repeat-x/y`, | |
| `space`, `round`, `scroll`, `local`) joined the vocabulary. | |
| - `color: red !important` used to error AND emit `red !important` (which the | |
| browser then silently dropped). `!important` is now one loud error, nothing | |
| is emitted, and the hint names `pin { }` as the actual answer. | |
| - `animation: spin ...` suggested "did you mean span"; it now passes motion | |
| names through, warns when the motion is undefined, and bare durations are ms. | |
| - `transition: color .2s ease` (CSS order) is an error whose hint shows the | |
| exact DSSL conversion (`transition: 200 ease color`). | |
| And the wishlist items that shipped: `raw { }` / `@css { }` verbatim passthrough, | |
| `dssl build` + `dssl check` (the CLI, with `--json` and CI exit codes), | |
| `DSSL.toCSS()`, headless-Chromium computed-style tests for every guide example | |
| (`scripts/test-guide-browser.mjs`), and `font-size: fluid 16 24` clamp sugar. | |
| ## v0.7.2 review-driven corrections | |
| An external agent review reproduced a batch of real bugs; all of them are fixed | |
| and regression-tested (scripts/test-v072.mjs). Rows affected: | |
| - Nested state blocks (hover/focus/... inside a rule) now sort AFTER the parent | |
| rule's own declarations, so nested overrides win the cascade. Nested @media, | |
| @signal and @layer blocks also keep their condition and layer (they used to | |
| be dropped, making nested dark mode silently dead). | |
| - Declarations and a nested block can share one line (`.card { bg: red; hover { } }`) | |
| without the declaration being swallowed. | |
| - Raw hex alpha is real: `#ff0000/50` compiles to color-mix (was invalid /50px). | |
| - Time properties take bare numbers as ms (`transition-delay: 200` -> 200ms). | |
| - `grid: 3 gap 12` works; `rows N` joined the shorthand. | |
| - `seen { ... }` rejects motion-arrow syntax loudly and points at the real forms. | |
| - SVG and modern properties added: fill, stroke (and friends), text-wrap, | |
| content-visibility. | |
| - `::part()` rides the shadow pipeline; `::slotted()` is rejected with an | |
| explanation (it only exists inside shadow trees). | |
| - The gate prefilter now actually runs before native matches(). | |
| - State events scope candidate recomputation to the hovered subtree and its | |
| ancestors instead of the whole page (a 1k-card hover went from ~4,100 | |
| recomputes per event to the touched chain). | |
| - checkbox/details states hear `change` and `toggle` events, so keyboard | |
| toggles and programmatic dispatch restyle immediately. | |