EHS-Studio Claude Sonnet 5.5 commited on
Commit
0434220
·
1 Parent(s): 92cf18f

v0.8.2: one cascade across all stylesheets, fluid on the longhands

Browse files

Found while rebuilding the website on its own engine.

- "Later wins" did not hold between stylesheets. Every sheet numbers its
rules from 0 and the engine sorted by (layer, order) comparing those raw
numbers across sheets, so a later sheet lost to an earlier one whenever
the earlier one had more rules above the rule in question. Reproduced with
a 30 rule sheet followed by a 1 rule sheet: the earlier colour won.
rebuild() now renumbers every rule into one global sequence in load order
(assignGlobalOrder, idempotent via order0). Layers still outrank order.
- Consequence: a tokens { } block in a later sheet now overrides a token an
earlier sheet set inside @media or @signal .
- fluid works on padding-top/right/bottom/left/inline/block, margin-*,
letter-spacing and border-radius, not only the four-sided shorthands.
- scripts/test-v082.mjs (27 assertions) runs the engine's own sort on rules
from real compiled sheets; the first check in each group reproduces the
old bug.
- docs: no em dashes in the markdown docs; GUIDE, README, PARITY, llms.txt
and the package USAGE updated; extension synced; zip, tarball and the
0.8.2 vsix rebuilt.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

GUIDE.md CHANGED
@@ -6,7 +6,7 @@ example in this guide is compiled by `scripts/test-guide-examples.mjs` on every
6
  release, so the examples cannot drift from the compiler. If an example here
7
  stops compiling, that is a release blocker, not a docs bug.
8
 
9
- Status up front: DSSL is a real, tested style engine at v0.8.1, and it is
10
  honest alpha. It needs JavaScript, it writes inline styles, and it has a
11
  first-paint story you should read before shipping anything serious (see
12
  "Honest limits"). Within those limits, it is genuinely nicer to write than
@@ -109,7 +109,7 @@ Token overrides can be scoped under `@media` or `@signal` for themes.
109
  .hero { center: both }
110
  ```
111
 
112
- Each place word owns an axis (v0.8.0 — the example above used to silently
113
  lose the `between` because every word wrote both axes):
114
 
115
  - `between`, `around`, `evenly` are main-axis words (`justify-content`)
@@ -123,7 +123,8 @@ overwriting, and a word written twice warns too.
123
  ### Sizing that moves: fluid
124
 
125
  `font-size: fluid 16 24` (and any length-ish property: width, height, gap,
126
- padding, margin and friends) makes a size that grows smoothly with the window.
 
127
  It is 16px on a 360px wide screen, 24px on a 1200px wide one, and in between it
128
  slides along a straight line. Past either end it stays put.
129
 
@@ -142,6 +143,8 @@ What you get at a few window widths for `fluid 16 24`:
142
 
143
  A few things worth knowing:
144
 
 
 
145
  - Sizes can be px (bare numbers are px), rem or em. Both sizes need the same unit.
146
  - Percentages are refused with an error, because a percentage cannot follow the
147
  window width.
@@ -151,7 +154,7 @@ A few things worth knowing:
151
  right slope but forgot where the line starts, so sizes ran ahead of the
152
  window and hit their maximum around 840px instead of 1200px.
153
 
154
- ### raw { } — the escape hatch
155
 
156
  DSSL has no sugar for `@font-face`, `@keyframes`, `@supports`, `@import` or
157
  `@property` yet. `raw { }` (alias `@css { }`) drops verbatim CSS into the
@@ -177,12 +180,18 @@ styles.dssl:3 warning: raw { } is verbatim CSS, so this line needs a ";" at the
177
  ```
178
 
179
  Unknown at-rules outside `raw { }` are skipped whole, with one clean error
180
- and a pointer here — they never leak their inner rules (that was a real
181
  0.7.2 bug: `@supports (display:grid) { .a { … } }` applied `.a` everywhere
182
  after erroring).
183
 
184
  ### The cascade is (layer, order)
185
 
 
 
 
 
 
 
186
  No specificity, no `!important`. Later wins inside a layer. Layers sort
187
  `tokens < base < components < utilities < pin`. Nested blocks do what the
188
  indentation says they do (fixed in v0.7.2, it used to be the engine's worst
@@ -283,7 +292,7 @@ for liveness. `unfreeze()` re-arms everything.
283
 
284
  Three ways to get it:
285
 
286
- 1. Download `dssl-vscode/dssl-vscode-0.8.1.vsix` from the repo files, then
287
  Extensions panel, `...`, Install from VSIX. Works in VS Code and VSCodium.
288
  2. Build it: `cd dssl-vscode && npx @vscode/vsce package --no-dependencies`.
289
  3. Registry listing: staged for Open VSX (the registry VSCodium searches).
@@ -316,6 +325,7 @@ The same output is available in the browser as `DSSL.toCSS(source)`.
316
 
317
  ```sh
318
  node cli/dssl.mjs check public/styles/*.dssl # compile everything, exit code for CI
 
319
  node scripts/test-v081.mjs # 0.8.1 regression suite (37 assertions)
320
  node scripts/test-v073.mjs # 0.8.0 regression suite (65 assertions)
321
  node scripts/test-guide-examples.mjs # every guide example compiles
@@ -340,7 +350,7 @@ Read this before choosing DSSL for a real site.
340
  - **JavaScript is required** for the live engine. No JS, no styles at runtime.
341
  v0.8.0 adds the exit lane: `dssl build styles.dssl -o styles.css` compiles
342
  the static subset to a real stylesheet you can `<link>` instead of the
343
- engine — tokens as custom properties, states as `:hover`, layers as
344
  `@layer`, motions as `@keyframes`, `raw { }` verbatim. What genuinely needs
345
  JS (`@signal`, `animate:`, deform, overlap) stays out and is counted in the
346
  report, so you always know what a no-JS page gives up. If a page must work
@@ -353,7 +363,7 @@ Read this before choosing DSSL for a real site.
353
  and is on the PARITY roadmap as the next big item.
354
  - **CSP.** Pseudo-elements compile to one real stylesheet the engine injects;
355
  strict `style-src 'self'` blocks inline style attributes and that sheet.
356
- DSSL wants `unsafe-inline` for styles today — or use the `dssl build`
357
  output, which is a normal external stylesheet and CSP-clean.
358
  - **Interop is invasive.** DSSL normal declarations beat external CSS, and
359
  `pin` beats external `!important`. Mixing with an existing CSS design
 
6
  release, so the examples cannot drift from the compiler. If an example here
7
  stops compiling, that is a release blocker, not a docs bug.
8
 
9
+ Status up front: DSSL is a real, tested style engine at v0.8.2, and it is
10
  honest alpha. It needs JavaScript, it writes inline styles, and it has a
11
  first-paint story you should read before shipping anything serious (see
12
  "Honest limits"). Within those limits, it is genuinely nicer to write than
 
109
  .hero { center: both }
110
  ```
111
 
112
+ Each place word owns an axis (v0.8.0, the example above used to silently
113
  lose the `between` because every word wrote both axes):
114
 
115
  - `between`, `around`, `evenly` are main-axis words (`justify-content`)
 
123
  ### Sizing that moves: fluid
124
 
125
  `font-size: fluid 16 24` (and any length-ish property: width, height, gap,
126
+ padding, margin, their `-top` / `-bottom` / `-inline` / `-block` longhands,
127
+ letter-spacing and border-radius) makes a size that grows smoothly with the window.
128
  It is 16px on a 360px wide screen, 24px on a 1200px wide one, and in between it
129
  slides along a straight line. Past either end it stays put.
130
 
 
143
 
144
  A few things worth knowing:
145
 
146
+ - `padding: fluid 8 16` sets all four sides. To make just one side fluid, use the
147
+ longhand: `padding-top: fluid 36 80`. (0.8.1 had no way to say that.)
148
  - Sizes can be px (bare numbers are px), rem or em. Both sizes need the same unit.
149
  - Percentages are refused with an error, because a percentage cannot follow the
150
  window width.
 
154
  right slope but forgot where the line starts, so sizes ran ahead of the
155
  window and hit their maximum around 840px instead of 1200px.
156
 
157
+ ### raw { }: the escape hatch
158
 
159
  DSSL has no sugar for `@font-face`, `@keyframes`, `@supports`, `@import` or
160
  `@property` yet. `raw { }` (alias `@css { }`) drops verbatim CSS into the
 
180
  ```
181
 
182
  Unknown at-rules outside `raw { }` are skipped whole, with one clean error
183
+ and a pointer here, they never leak their inner rules (that was a real
184
  0.7.2 bug: `@supports (display:grid) { .a { … } }` applied `.a` everywhere
185
  after erroring).
186
 
187
  ### The cascade is (layer, order)
188
 
189
+ (This holds across stylesheets too: a sheet loaded later beats one loaded before it.
190
+ Versions up to 0.8.1 got that wrong. Each sheet counted its own rules from zero and
191
+ the engine compared those numbers across sheets, so a later sheet could lose just
192
+ because the earlier one was longer. 0.8.2 gives every rule one position in load
193
+ order.)
194
+
195
  No specificity, no `!important`. Later wins inside a layer. Layers sort
196
  `tokens < base < components < utilities < pin`. Nested blocks do what the
197
  indentation says they do (fixed in v0.7.2, it used to be the engine's worst
 
292
 
293
  Three ways to get it:
294
 
295
+ 1. Download `dssl-vscode/dssl-vscode-0.8.2.vsix` from the repo files, then
296
  Extensions panel, `...`, Install from VSIX. Works in VS Code and VSCodium.
297
  2. Build it: `cd dssl-vscode && npx @vscode/vsce package --no-dependencies`.
298
  3. Registry listing: staged for Open VSX (the registry VSCodium searches).
 
325
 
326
  ```sh
327
  node cli/dssl.mjs check public/styles/*.dssl # compile everything, exit code for CI
328
+ node scripts/test-v082.mjs # 0.8.2 regression suite (27 assertions)
329
  node scripts/test-v081.mjs # 0.8.1 regression suite (37 assertions)
330
  node scripts/test-v073.mjs # 0.8.0 regression suite (65 assertions)
331
  node scripts/test-guide-examples.mjs # every guide example compiles
 
350
  - **JavaScript is required** for the live engine. No JS, no styles at runtime.
351
  v0.8.0 adds the exit lane: `dssl build styles.dssl -o styles.css` compiles
352
  the static subset to a real stylesheet you can `<link>` instead of the
353
+ engine, tokens as custom properties, states as `:hover`, layers as
354
  `@layer`, motions as `@keyframes`, `raw { }` verbatim. What genuinely needs
355
  JS (`@signal`, `animate:`, deform, overlap) stays out and is counted in the
356
  report, so you always know what a no-JS page gives up. If a page must work
 
363
  and is on the PARITY roadmap as the next big item.
364
  - **CSP.** Pseudo-elements compile to one real stylesheet the engine injects;
365
  strict `style-src 'self'` blocks inline style attributes and that sheet.
366
+ DSSL wants `unsafe-inline` for styles today, or use the `dssl build`
367
  output, which is a normal external stylesheet and CSP-clean.
368
  - **Interop is invasive.** DSSL normal declarations beat external CSS, and
369
  `pin` beats external `!important`. Mixing with an existing CSS design
PARITY.md CHANGED
@@ -1,4 +1,4 @@
1
- # PARITY.md — DSSL vs CSS, honestly
2
 
3
  The point of this table is to stop two bad arguments: "DSSL is missing everything" and
4
  "DSSL replaces all of CSS". Both are false. Here is the ledger. Status: shipped / partial
@@ -40,7 +40,7 @@ keeps working with the engine removed. No other styling replacement can say that
40
  no other replacement styles with the platform's own primitives.
41
 
42
  And since v0.8.0 there is a second exit: `dssl build styles.dssl -o styles.css`
43
- compiles the static subset to a real stylesheet before you ever ship — no runtime,
44
  no inline styles, CSP-clean, works without JavaScript. Tokens become custom properties,
45
  states become pseudo-classes, layers become `@layer`, motions become `@keyframes`,
46
  `pin` becomes `!important`, `raw { }` passes through verbatim, `seen` applies its final
@@ -57,6 +57,17 @@ so the boundary is explicit, not folklore.
57
  - loud errors with did-you-mean, and a warnings channel for cascade-order footguns
58
  - `freeze()` / `unfreeze()`: a live engine that can be parked at zero cost
59
 
 
 
 
 
 
 
 
 
 
 
 
60
  ## v0.8.1 follow-up corrections
61
 
62
  A re-test of 0.8.0 found two bugs, and more showed up while writing the tests:
@@ -81,7 +92,7 @@ A re-test of 0.8.0 found two bugs, and more showed up while writing the tests:
81
  The second external review (repro snippets, headless browser) found five bugs and
82
  asked for the static build first. All fixed or shipped:
83
 
84
- - `row: gap 8 between center` — the guide's own example — kept its `between`
85
  (every place word used to write both axes; the later word overwrote
86
  `justify-content` silently). Place words now own an axis: between/around/evenly
87
  are main-axis, stretch/baseline cross-axis, center/start/end fill free axes;
 
1
+ # PARITY.md: DSSL vs CSS, honestly
2
 
3
  The point of this table is to stop two bad arguments: "DSSL is missing everything" and
4
  "DSSL replaces all of CSS". Both are false. Here is the ledger. Status: shipped / partial
 
40
  no other replacement styles with the platform's own primitives.
41
 
42
  And since v0.8.0 there is a second exit: `dssl build styles.dssl -o styles.css`
43
+ compiles the static subset to a real stylesheet before you ever ship, no runtime,
44
  no inline styles, CSP-clean, works without JavaScript. Tokens become custom properties,
45
  states become pseudo-classes, layers become `@layer`, motions become `@keyframes`,
46
  `pin` becomes `!important`, `raw { }` passes through verbatim, `seen` applies its final
 
57
  - loud errors with did-you-mean, and a warnings channel for cascade-order footguns
58
  - `freeze()` / `unfreeze()`: a live engine that can be parked at zero cost
59
 
60
+ ## v0.8.2: what rebuilding the site found
61
+
62
+ - Cross-stylesheet cascade. "Later wins" held inside one sheet and not between
63
+ sheets: every sheet numbered its rules from zero and the engine compared those
64
+ numbers across sheets, so a later sheet lost to an earlier one whenever the
65
+ earlier one had more rules above the rule in question. Every rule now has one
66
+ position in load order. A consequence: a later `tokens { }` block now overrides
67
+ a token an earlier sheet set inside `@media` or `@signal`.
68
+ - `fluid` works on the longhands (`padding-top`, `margin-block`, `letter-spacing`,
69
+ `border-radius` and the rest), not only on the four-sided shorthands.
70
+
71
  ## v0.8.1 follow-up corrections
72
 
73
  A re-test of 0.8.0 found two bugs, and more showed up while writing the tests:
 
92
  The second external review (repro snippets, headless browser) found five bugs and
93
  asked for the static build first. All fixed or shipped:
94
 
95
+ - `row: gap 8 between center`, the guide's own example, kept its `between`
96
  (every place word used to write both axes; the later word overwrote
97
  `justify-content` silently). Place words now own an axis: between/around/evenly
98
  are main-axis, stretch/baseline cross-axis, center/start/end fill free axes;
README.md CHANGED
@@ -6,7 +6,7 @@ license: mit
6
 
7
  DSSL is a style language for the web. For the pages it fits, it replaces writing CSS entirely. It does not replace the CSS platform: the engine ends in inline styles that the browser parses, and it needs JavaScript. Where it wins is the authoring experience: tokens, words-as-layout, states and motion in one grammar, loud errors, zero build. PARITY.md keeps the honest feature ledger.
8
 
9
- Not a preprocessor that spits out stylesheets. Not a utility class graveyard. You write `.dssl` files, the page loads a small engine, and the engine walks your DOM in real time and applies every rule it matches straight onto the elements it finds. Nothing compiles at runtime — and since v0.8.0 there is also an exit lane: `dssl build` compiles the static subset of the same source to a real `.css` file for pages that must work without JavaScript or sit behind a strict CSP. You choose per page: live engine, built CSS, or both.
10
 
11
  Elements that show up later get styled the moment they appear. Change a rule and the page updates on the next pass. The language is the runtime.
12
 
@@ -68,7 +68,7 @@ Both drop a `dssl-engine/` folder next to you: the engine, a starter token set,
68
 
69
  Prefer to click: [dssl-engine.zip](https://dssl-sable.vercel.app/dssl-engine.zip) or [dssl-engine.tar.gz](https://dssl-sable.vercel.app/dssl-engine.tar.gz). Same package, straight from the browser.
70
 
71
- Writing your own .dssl? The official editor extension (`ehs-studio.dssl-vscode`) puts the real compiler in your editor: the squiggles are the same errors the browser console prints, completions come from your own token registry, and hovers explain the semantic vocabulary. One package for VS Code and VSCodium (same extension API): download `dssl-vscode/dssl-vscode-0.8.1.vsix` from the repo files, then Extensions panel -> ... -> Install from VSIX. An Open VSX listing (so it appears in VSCodium's Extensions tab, searchable) is staged and publishes with one Open VSX token; see `dssl-vscode/PUBLISHING.md`.
72
 
73
  AI agents and machine reviewers: this repo ships `llms.txt` (dense, structured facts), `AGENTS.md` (how to work on the codebase) and `PARITY.md` (the honest feature matrix against CSS). The site serves the same `llms.txt` at https://dssl-sable.vercel.app/llms.txt
74
 
@@ -346,7 +346,7 @@ v0.7 added warnings: things that compile, run, and are still probably not what y
346
  .nav-links {
347
  display: none
348
  column: gap 2 // warning: "column:" expands to display: flex and
349
- // overrides the earlier "display: none" — menu stays open
350
  }
351
  ```
352
 
@@ -415,17 +415,17 @@ DSSL.load(['/styles/app.dssl']).then(() => {
415
 
416
  ```js
417
  const rep = DSSL.explain(document.querySelector('.doc-card'))
418
- // rep.matched — [{ id, sel, layer, file, line, states, media, signals, decls }]
419
- // rep.wins — [{ prop, value, by: { sel, file, line } }]
420
- // rep.pseudo — the ::before / ::after rules touching this element
421
  ```
422
 
423
  The matcher also pre-filters every rule through a cheap class/tag/id gate extracted from the selector's rightmost compound, so a recompute skips obvious non-matches instead of asking the browser rule by rule. The gate only ever says no; anything it does not fully understand falls back to native matching.
424
 
425
- ## What works today (v0.8.1)
426
 
427
  - the full selector surface, including `:nth-child`, `:not`, `:is`, `:where`, `:has`
428
- - flexbox and grid with `row:` / `column:` / `grid:` / `stack:` / `center:` sugar — place words now own an axis (`between` is main-axis, `stretch` is cross-axis) and conflicts warn instead of silently overwriting
429
  - behaviors and `use`
430
  - design tokens, numeric keys included, scoped per file and per subtree
431
  - states, engine-native `seen`, `stagger` choreography
@@ -435,7 +435,8 @@ The matcher also pre-filters every rule through a cheap class/tag/id gate extrac
435
  - the overlap policy and `pointer-events` pass-through for veils
436
  - pseudo-elements in any spelling (shadow CSS), themed scrollbars included
437
  - `raw { }` / `@css { }` for verbatim CSS (`@font-face`, `@keyframes`, `@supports`); unknown at-rules skip their whole block with one clean error
438
- - `fluid 16 24` sizing sugar on any length property
 
439
  - the CLI: `dssl check` (CI, --json, --strict) and `dssl build` (real CSS from the static subset, also with --strict)
440
  - compiler warnings for cascade-order footguns and duplicate axis words
441
  - `DSSL.freeze()` / `DSSL.unfreeze()` / `DSSL.explain()` / `DSSL.toCSS()`
@@ -457,7 +458,7 @@ The matcher also pre-filters every rule through a cheap class/tag/id gate extrac
457
 
458
  ## Status
459
 
460
- v0.8.1, experimental, moving fast. The language grows whenever the site needs something it cannot express yet, so the site doubles as the test suite, and the docs examples are rendered and asserted in a real browser, so the guide cannot lie. Honest ledger: great for creative sites, prototypes, docs, playgrounds and internal tools; when a page must work without JavaScript, build the static subset with `dssl build` or pick another tool for that page. Freeze makes big static pages viable; live interaction is what the engine is for.
461
 
462
  ## License
463
 
 
6
 
7
  DSSL is a style language for the web. For the pages it fits, it replaces writing CSS entirely. It does not replace the CSS platform: the engine ends in inline styles that the browser parses, and it needs JavaScript. Where it wins is the authoring experience: tokens, words-as-layout, states and motion in one grammar, loud errors, zero build. PARITY.md keeps the honest feature ledger.
8
 
9
+ Not a preprocessor that spits out stylesheets. Not a utility class graveyard. You write `.dssl` files, the page loads a small engine, and the engine walks your DOM in real time and applies every rule it matches straight onto the elements it finds. Nothing compiles at runtime, and since v0.8.0 there is also an exit lane: `dssl build` compiles the static subset of the same source to a real `.css` file for pages that must work without JavaScript or sit behind a strict CSP. You choose per page: live engine, built CSS, or both.
10
 
11
  Elements that show up later get styled the moment they appear. Change a rule and the page updates on the next pass. The language is the runtime.
12
 
 
68
 
69
  Prefer to click: [dssl-engine.zip](https://dssl-sable.vercel.app/dssl-engine.zip) or [dssl-engine.tar.gz](https://dssl-sable.vercel.app/dssl-engine.tar.gz). Same package, straight from the browser.
70
 
71
+ Writing your own .dssl? The official editor extension (`ehs-studio.dssl-vscode`) puts the real compiler in your editor: the squiggles are the same errors the browser console prints, completions come from your own token registry, and hovers explain the semantic vocabulary. One package for VS Code and VSCodium (same extension API): download `dssl-vscode/dssl-vscode-0.8.2.vsix` from the repo files, then Extensions panel -> ... -> Install from VSIX. An Open VSX listing (so it appears in VSCodium's Extensions tab, searchable) is staged and publishes with one Open VSX token; see `dssl-vscode/PUBLISHING.md`.
72
 
73
  AI agents and machine reviewers: this repo ships `llms.txt` (dense, structured facts), `AGENTS.md` (how to work on the codebase) and `PARITY.md` (the honest feature matrix against CSS). The site serves the same `llms.txt` at https://dssl-sable.vercel.app/llms.txt
74
 
 
346
  .nav-links {
347
  display: none
348
  column: gap 2 // warning: "column:" expands to display: flex and
349
+ // overrides the earlier "display: none", menu stays open
350
  }
351
  ```
352
 
 
415
 
416
  ```js
417
  const rep = DSSL.explain(document.querySelector('.doc-card'))
418
+ // rep.matched: [{ id, sel, layer, file, line, states, media, signals, decls }]
419
+ // rep.wins : [{ prop, value, by: { sel, file, line } }]
420
+ // rep.pseudo : the ::before / ::after rules touching this element
421
  ```
422
 
423
  The matcher also pre-filters every rule through a cheap class/tag/id gate extracted from the selector's rightmost compound, so a recompute skips obvious non-matches instead of asking the browser rule by rule. The gate only ever says no; anything it does not fully understand falls back to native matching.
424
 
425
+ ## What works today (v0.8.2)
426
 
427
  - the full selector surface, including `:nth-child`, `:not`, `:is`, `:where`, `:has`
428
+ - flexbox and grid with `row:` / `column:` / `grid:` / `stack:` / `center:` sugar, place words now own an axis (`between` is main-axis, `stretch` is cross-axis) and conflicts warn instead of silently overwriting
429
  - behaviors and `use`
430
  - design tokens, numeric keys included, scoped per file and per subtree
431
  - states, engine-native `seen`, `stagger` choreography
 
435
  - the overlap policy and `pointer-events` pass-through for veils
436
  - pseudo-elements in any spelling (shadow CSS), themed scrollbars included
437
  - `raw { }` / `@css { }` for verbatim CSS (`@font-face`, `@keyframes`, `@supports`); unknown at-rules skip their whole block with one clean error
438
+ - `fluid 16 24` sizing sugar on any length property, including `padding-top`, `margin-block` and the other longhands
439
+ - one cascade across every stylesheet: a sheet loaded later beats an earlier one (fixed in 0.8.2)
440
  - the CLI: `dssl check` (CI, --json, --strict) and `dssl build` (real CSS from the static subset, also with --strict)
441
  - compiler warnings for cascade-order footguns and duplicate axis words
442
  - `DSSL.freeze()` / `DSSL.unfreeze()` / `DSSL.explain()` / `DSSL.toCSS()`
 
458
 
459
  ## Status
460
 
461
+ v0.8.2, experimental, moving fast. The language grows whenever the site needs something it cannot express yet, so the site doubles as the test suite, and the docs examples are rendered and asserted in a real browser, so the guide cannot lie. Honest ledger: great for creative sites, prototypes, docs, playgrounds and internal tools; when a page must work without JavaScript, build the static subset with `dssl build` or pick another tool for that page. Freeze makes big static pages viable; live interaction is what the engine is for.
462
 
463
  ## License
464
 
dssl-vscode/{dssl-vscode-0.8.1.vsix → dssl-vscode-0.8.2.vsix} RENAMED
Binary files a/dssl-vscode/dssl-vscode-0.8.1.vsix and b/dssl-vscode/dssl-vscode-0.8.2.vsix differ
 
dssl-vscode/lib/engine/resolver.js CHANGED
@@ -946,7 +946,12 @@ const SEMANTIC = {
946
  * (360px and 1200px by default) — the formula everyone writes by hand and
947
  * gets wrong. */
948
  const FLUID_PROPS = new Set(('font-size,width,height,inline-size,block-size,min-width,max-width,min-height,max-height,' +
949
- 'gap,row-gap,column-gap,padding,margin,inset,top,right,bottom,left').split(','));
 
 
 
 
 
950
 
951
  function buildFluidParts(parts, ctx, prop = 'font-size') {
952
  const bad = (s, why) => {
 
946
  * (360px and 1200px by default) — the formula everyone writes by hand and
947
  * gets wrong. */
948
  const FLUID_PROPS = new Set(('font-size,width,height,inline-size,block-size,min-width,max-width,min-height,max-height,' +
949
+ 'gap,row-gap,column-gap,padding,margin,inset,top,right,bottom,left,' +
950
+ // 0.8.2: the longhands. The site's own hero wanted a fluid padding-top and
951
+ // there was no way to say it, because `padding: fluid ...` is all four sides.
952
+ 'padding-top,padding-right,padding-bottom,padding-left,padding-inline,padding-block,' +
953
+ 'margin-top,margin-right,margin-bottom,margin-left,margin-inline,margin-block,' +
954
+ 'letter-spacing,border-radius').split(','));
955
 
956
  function buildFluidParts(parts, ctx, prop = 'font-size') {
957
  const bad = (s, why) => {
dssl-vscode/package.json CHANGED
@@ -2,7 +2,7 @@
2
  "name": "dssl-vscode",
3
  "displayName": "DSSL Language Support",
4
  "description": "The DSSL style language in your editor: syntax highlighting, real-compiler diagnostics (the same errors the browser console shows), completions from the live token registry, hover docs. Works in VS Code and VSCodium.",
5
- "version": "0.8.1",
6
  "publisher": "ehs-studio",
7
  "icon": "icon.png",
8
  "license": "MIT",
 
2
  "name": "dssl-vscode",
3
  "displayName": "DSSL Language Support",
4
  "description": "The DSSL style language in your editor: syntax highlighting, real-compiler diagnostics (the same errors the browser console shows), completions from the live token registry, hover docs. Works in VS Code and VSCodium.",
5
+ "version": "0.8.2",
6
  "publisher": "ehs-studio",
7
  "icon": "icon.png",
8
  "license": "MIT",
engine-package/USAGE.md CHANGED
@@ -1,4 +1,4 @@
1
- # DSSL engine — quickstart
2
 
3
  DSSL is a style language that runs in the browser. You write `.dssl` files, add two tags to your page, and the engine does the rest. No compiler, no build step, no CSS output. The engine walks your DOM live and writes styles straight onto the elements it matches, including elements that show up later.
4
 
 
1
+ # DSSL engine: quickstart
2
 
3
  DSSL is a style language that runs in the browser. You write `.dssl` files, add two tags to your page, and the engine does the rest. No compiler, no build step, no CSS output. The engine walks your DOM live and writes styles straight onto the elements it matches, including elements that show up later.
4
 
llms.txt CHANGED
@@ -1,18 +1,18 @@
1
  # DSSL
2
 
3
- > DSSL is a style language for the web. For JS-guaranteed pages it replaces writing CSS; it does not replace the CSS platform. You write `.dssl` files, the page loads the engine (about 58 kB gzipped, about 190 kB as plain source), and the engine walks the DOM in real time applying every matched rule as inline styles. Nothing compiles to CSS at runtime — but v0.8.0 adds the exit lane: `dssl build` compiles the static subset to a real .css file for no-JS, strict-CSP or size-critical pages. Elements added later are styled the moment they appear; signals, states and motion share one grammar.
4
 
5
  - Site (docs, playground, changelog, bench): https://dssl-sable.vercel.app
6
  - Repo + full docs in the README: https://huggingface.co/EHS-Studio/dssl
7
  - Install: `curl -fsSL https://dssl-sable.vercel.app/install.sh | sh` (or the zip: https://dssl-sable.vercel.app/dssl-engine.zip)
8
- - Version: 0.8.1 (engine), MIT license
9
  - Feature matrix vs CSS: see PARITY.md in the repo
10
  - For agents working ON the codebase: see AGENTS.md in the repo
11
 
12
  ## The five things reviewers ask about
13
 
14
  1. No build step. Styles are source files linked with `<link rel="stylesheet/dssl">` or embedded in the page as `<style type="text/dssl">` / `<script type="text/dssl">` blocks (auto-booted in document order). The engine restyles the live DOM, including nodes added later.
15
- 2. Honest trade: it is a JS runtime. If JavaScript is unavailable the page renders unstyled — unless you build the static subset: `dssl build styles.dssl -o styles.css` emits real CSS (tokens → custom properties, states → :hover, layers → @layer, motions → @keyframes, `raw { }` verbatim, `seen` applied as its no-JS final state) and the report counts what stays runtime-only (`@signal` rules, `animate:`). For content pages that must not flash, use the build output or choose another tool.
16
  3. Exit cost is zero. Abandoning DSSL leaves you a page of valid inline styles and one small shadow stylesheet for pseudo-elements. There is nothing to un-install from your markup.
17
  4. Speed has a handbrake. `DSSL.freeze()` detaches every observer and listener after first paint and leaves the resolved look at zero engine cost; `unfreeze()` re-arms. A gate prefilter skips obvious non-matches before native matching. The Bench page on the site measures 100 / 1,000 / 10,000 nodes on your machine: CSS `<style>` vs DSSL live vs DSSL frozen.
18
  5. The cascade is (layer, source order). No specificity, no !important. Nested state blocks sort after the parent rule's own declarations (v0.7.2 fix: they used to lose), and nested `@media`/`@signal`/`@layer` keep their condition. `pin { }` is the only must-win block. The compiler refuses to guess: unknown tokens, properties and values fail loudly with did-you-mean; `--*` custom properties pass through unvalidated on purpose.
@@ -31,7 +31,7 @@
31
  - Overlap policy: nothing below 100% opacity overlaps anything unless `overlap: allow`; `deny` is unconditional; pairs with `pointer-events: none` so veils stop eating clicks and text selection.
32
  - Behaviors: named rule groups applied with `use focus-ring`.
33
  - Verbatim escape hatch (v0.8.0): `raw { }` / `@css { }` drops real CSS (@font-face, @keyframes, @supports, @import, @property) into the page stylesheet untouched; unknown at-rules skip their whole block with one clean error. Inside raw { } semicolons are required (it is real CSS); a missing one is a warning with a line number.
34
- - Fluid sugar (v0.8.0): `font-size: fluid 16 24` (and any length-ish property) compiles to the linear clamp() between 360px and 1200px viewports (exactly the first size at 360px and the second at 1200px; px, rem or em, no percentages; shrinking sizes work); `fluid: 16, 24` still works. 0.8.1 fixed the math: 0.8.0 left out the intercept, so sizes were too large on small screens.
35
  - v0.8.0 review-driven fixes, all regression-tested incl. headless-Chromium computed-style tests of the docs: row/column axis roles (the documented `between center` example laid out wrong before); @font-face no longer crashes the compiler (unknown at-rules skip balanced); @supports inner rules no longer leak unconditionally; `url(...)` is atomic (unquoted form no longer false-errors); `!important` is a loud error that emits nothing; the animation shorthand warns when its motion is undefined instead of suggesting "span"; CSS-order transition values get the exact DSSL conversion in the error hint.
36
  - v0.7.2 review-driven fixes, all regression-tested: gate prefilter runs before native matches(); hover state events scope candidate recomputation to the touched subtree plus ancestors (was page-wide); checkbox/details states listen to `change`/`toggle`; raw hex alpha (`#ff0000/50` -> color-mix); bare numbers on time properties are ms; `::part()` supported, `::slotted()` rejected with an explanation; SVG properties (`fill`, `stroke*`) and `text-wrap`/`content-visibility` in the vocabulary.
37
  - Warnings channel (v0.7): things that compile but are probably wrong, e.g. a layout shorthand expanding to `display` and clobbering an earlier explicit `display:` in the same rule.
@@ -62,4 +62,4 @@ Wait for the `dssl-ready` event on `window` before measuring layout: the engine
62
 
63
  ## Editor support
64
 
65
- The official extension (VS Code and VSCodium, same .vsix) bundles the real parser and resolver: diagnostics identical to the browser console, completions from the workspace token registry, hover docs for the semantic vocabulary. Install `dssl-vscode/dssl-vscode-0.8.1.vsix` from the repo files. Source lives in `dssl-vscode/` in the repo.
 
1
  # DSSL
2
 
3
+ > DSSL is a style language for the web. For JS-guaranteed pages it replaces writing CSS; it does not replace the CSS platform. You write `.dssl` files, the page loads the engine (about 58 kB gzipped, about 190 kB as plain source), and the engine walks the DOM in real time applying every matched rule as inline styles. Nothing compiles to CSS at runtime, but v0.8.0 adds the exit lane: `dssl build` compiles the static subset to a real .css file for no-JS, strict-CSP or size-critical pages. Elements added later are styled the moment they appear; signals, states and motion share one grammar.
4
 
5
  - Site (docs, playground, changelog, bench): https://dssl-sable.vercel.app
6
  - Repo + full docs in the README: https://huggingface.co/EHS-Studio/dssl
7
  - Install: `curl -fsSL https://dssl-sable.vercel.app/install.sh | sh` (or the zip: https://dssl-sable.vercel.app/dssl-engine.zip)
8
+ - Version: 0.8.2 (engine), MIT license
9
  - Feature matrix vs CSS: see PARITY.md in the repo
10
  - For agents working ON the codebase: see AGENTS.md in the repo
11
 
12
  ## The five things reviewers ask about
13
 
14
  1. No build step. Styles are source files linked with `<link rel="stylesheet/dssl">` or embedded in the page as `<style type="text/dssl">` / `<script type="text/dssl">` blocks (auto-booted in document order). The engine restyles the live DOM, including nodes added later.
15
+ 2. Honest trade: it is a JS runtime. If JavaScript is unavailable the page renders unstyled, unless you build the static subset: `dssl build styles.dssl -o styles.css` emits real CSS (tokens → custom properties, states → :hover, layers → @layer, motions → @keyframes, `raw { }` verbatim, `seen` applied as its no-JS final state) and the report counts what stays runtime-only (`@signal` rules, `animate:`). For content pages that must not flash, use the build output or choose another tool.
16
  3. Exit cost is zero. Abandoning DSSL leaves you a page of valid inline styles and one small shadow stylesheet for pseudo-elements. There is nothing to un-install from your markup.
17
  4. Speed has a handbrake. `DSSL.freeze()` detaches every observer and listener after first paint and leaves the resolved look at zero engine cost; `unfreeze()` re-arms. A gate prefilter skips obvious non-matches before native matching. The Bench page on the site measures 100 / 1,000 / 10,000 nodes on your machine: CSS `<style>` vs DSSL live vs DSSL frozen.
18
  5. The cascade is (layer, source order). No specificity, no !important. Nested state blocks sort after the parent rule's own declarations (v0.7.2 fix: they used to lose), and nested `@media`/`@signal`/`@layer` keep their condition. `pin { }` is the only must-win block. The compiler refuses to guess: unknown tokens, properties and values fail loudly with did-you-mean; `--*` custom properties pass through unvalidated on purpose.
 
31
  - Overlap policy: nothing below 100% opacity overlaps anything unless `overlap: allow`; `deny` is unconditional; pairs with `pointer-events: none` so veils stop eating clicks and text selection.
32
  - Behaviors: named rule groups applied with `use focus-ring`.
33
  - Verbatim escape hatch (v0.8.0): `raw { }` / `@css { }` drops real CSS (@font-face, @keyframes, @supports, @import, @property) into the page stylesheet untouched; unknown at-rules skip their whole block with one clean error. Inside raw { } semicolons are required (it is real CSS); a missing one is a warning with a line number.
34
+ - Fluid sugar (v0.8.0): `font-size: fluid 16 24` (and any length-ish property) compiles to the linear clamp() between 360px and 1200px viewports (exactly the first size at 360px and the second at 1200px; px, rem or em, no percentages; shrinking sizes work); `fluid: 16, 24` still works; it also works on the longhands (`padding-top`, `margin-block`, `letter-spacing`, `border-radius`) since 0.8.2. 0.8.2 also made the cascade global: a stylesheet loaded later always beats an earlier one, and a later `tokens { }` block overrides an earlier conditional token. 0.8.1 fixed the math: 0.8.0 left out the intercept, so sizes were too large on small screens.
35
  - v0.8.0 review-driven fixes, all regression-tested incl. headless-Chromium computed-style tests of the docs: row/column axis roles (the documented `between center` example laid out wrong before); @font-face no longer crashes the compiler (unknown at-rules skip balanced); @supports inner rules no longer leak unconditionally; `url(...)` is atomic (unquoted form no longer false-errors); `!important` is a loud error that emits nothing; the animation shorthand warns when its motion is undefined instead of suggesting "span"; CSS-order transition values get the exact DSSL conversion in the error hint.
36
  - v0.7.2 review-driven fixes, all regression-tested: gate prefilter runs before native matches(); hover state events scope candidate recomputation to the touched subtree plus ancestors (was page-wide); checkbox/details states listen to `change`/`toggle`; raw hex alpha (`#ff0000/50` -> color-mix); bare numbers on time properties are ms; `::part()` supported, `::slotted()` rejected with an explanation; SVG properties (`fill`, `stroke*`) and `text-wrap`/`content-visibility` in the vocabulary.
37
  - Warnings channel (v0.7): things that compile but are probably wrong, e.g. a layout shorthand expanding to `display` and clobbering an earlier explicit `display:` in the same rule.
 
62
 
63
  ## Editor support
64
 
65
+ The official extension (VS Code and VSCodium, same .vsix) bundles the real parser and resolver: diagnostics identical to the browser console, completions from the workspace token registry, hover docs for the semantic vocabulary. Install `dssl-vscode/dssl-vscode-0.8.2.vsix` from the repo files. Source lives in `dssl-vscode/` in the repo.
public/dssl-engine.tar.gz CHANGED
Binary files a/public/dssl-engine.tar.gz and b/public/dssl-engine.tar.gz differ
 
public/dssl-engine.zip CHANGED
Binary files a/public/dssl-engine.zip and b/public/dssl-engine.zip differ
 
public/dssl/engine.js CHANGED
@@ -23,6 +23,36 @@ import { composeDeform, arcLayout } from './utils.js';
23
 
24
  const LAYER_RANK = { tokens: 0, base: 1, components: 2, utilities: 3, pin: 4 };
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  // v0.3.4 — alignment implies a formatting context.
27
  // place-items / place-content / align-* / justify-* only arrange children
28
  // inside flex or grid containers. On a plain <span> or <button> they used to
@@ -175,6 +205,7 @@ export class DsslEngine {
175
  const shadowRules = [];
176
  const raws = [];
177
  const bundles = [...this.bundles.values()].sort((a, b) => a.seq - b.seq);
 
178
  for (const b of bundles) {
179
  for (const r of b.rules) {
180
  rules.push(r); // scoped engines map :root rules onto the scope element
 
23
 
24
  const LAYER_RANK = { tokens: 0, base: 1, components: 2, utilities: 3, pin: 4 };
25
 
26
+ /* 0.8.2: one cascade position per rule, across every stylesheet on the page.
27
+ *
28
+ * A rule's own `order` is just its place inside the sheet it was written in:
29
+ * every sheet counts from 0. The cascade sorts by (layer, order), so when two
30
+ * sheets were combined their numbers were compared as if they were one count,
31
+ * and a later sheet's rule could lose to an earlier sheet's rule purely
32
+ * because the earlier sheet had more rules above it. "Later wins" held inside
33
+ * a sheet and not between them. That is also why a tokens block in a later
34
+ * sheet could not override a conditional token from an earlier one.
35
+ *
36
+ * This puts the bundles in load order and shifts each one's numbers past the
37
+ * end of the one before. The sheet's own numbers are kept in `order0`, so
38
+ * calling it again after a rebuild gives the same result instead of drifting.
39
+ */
40
+ export function assignGlobalOrder(bundlesInLoadOrder) {
41
+ let base = 0;
42
+ for (const b of bundlesInLoadOrder) {
43
+ let max = -1;
44
+ for (const list of [b.rules, b.shadowRules, b.raws]) {
45
+ if (!list) continue;
46
+ for (const r of list) {
47
+ if (r.order0 === undefined) r.order0 = r.order;
48
+ r.order = base + r.order0;
49
+ if (r.order0 > max) max = r.order0;
50
+ }
51
+ }
52
+ base += max + 1;
53
+ }
54
+ }
55
+
56
  // v0.3.4 — alignment implies a formatting context.
57
  // place-items / place-content / align-* / justify-* only arrange children
58
  // inside flex or grid containers. On a plain <span> or <button> they used to
 
205
  const shadowRules = [];
206
  const raws = [];
207
  const bundles = [...this.bundles.values()].sort((a, b) => a.seq - b.seq);
208
+ assignGlobalOrder(bundles);
209
  for (const b of bundles) {
210
  for (const r of b.rules) {
211
  rules.push(r); // scoped engines map :root rules onto the scope element
public/dssl/index.js CHANGED
@@ -19,7 +19,7 @@ import { resolve, DEFAULT_TOKENS } from './resolver.js';
19
  import { toCSS as docToCSS } from './static.js';
20
  import { DsslEngine } from './engine.js';
21
 
22
- export const VERSION = '0.8.1';
23
 
24
  let counter = 0;
25
  const engines = new Map();
 
19
  import { toCSS as docToCSS } from './static.js';
20
  import { DsslEngine } from './engine.js';
21
 
22
+ export const VERSION = '0.8.2';
23
 
24
  let counter = 0;
25
  const engines = new Map();
public/dssl/resolver.js CHANGED
@@ -946,7 +946,12 @@ const SEMANTIC = {
946
  * (360px and 1200px by default) — the formula everyone writes by hand and
947
  * gets wrong. */
948
  const FLUID_PROPS = new Set(('font-size,width,height,inline-size,block-size,min-width,max-width,min-height,max-height,' +
949
- 'gap,row-gap,column-gap,padding,margin,inset,top,right,bottom,left').split(','));
 
 
 
 
 
950
 
951
  function buildFluidParts(parts, ctx, prop = 'font-size') {
952
  const bad = (s, why) => {
 
946
  * (360px and 1200px by default) — the formula everyone writes by hand and
947
  * gets wrong. */
948
  const FLUID_PROPS = new Set(('font-size,width,height,inline-size,block-size,min-width,max-width,min-height,max-height,' +
949
+ 'gap,row-gap,column-gap,padding,margin,inset,top,right,bottom,left,' +
950
+ // 0.8.2: the longhands. The site's own hero wanted a fluid padding-top and
951
+ // there was no way to say it, because `padding: fluid ...` is all four sides.
952
+ 'padding-top,padding-right,padding-bottom,padding-left,padding-inline,padding-block,' +
953
+ 'margin-top,margin-right,margin-bottom,margin-left,margin-inline,margin-block,' +
954
+ 'letter-spacing,border-radius').split(','));
955
 
956
  function buildFluidParts(parts, ctx, prop = 'font-size') {
957
  const bad = (s, why) => {
public/llms.txt CHANGED
@@ -1,18 +1,18 @@
1
  # DSSL
2
 
3
- > DSSL is a style language for the web. For JS-guaranteed pages it replaces writing CSS; it does not replace the CSS platform. You write `.dssl` files, the page loads the engine (about 58 kB gzipped, about 190 kB as plain source), and the engine walks the DOM in real time applying every matched rule as inline styles. Nothing compiles to CSS at runtime — but v0.8.0 adds the exit lane: `dssl build` compiles the static subset to a real .css file for no-JS, strict-CSP or size-critical pages. Elements added later are styled the moment they appear; signals, states and motion share one grammar.
4
 
5
  - Site (docs, playground, changelog, bench): https://dssl-sable.vercel.app
6
  - Repo + full docs in the README: https://huggingface.co/EHS-Studio/dssl
7
  - Install: `curl -fsSL https://dssl-sable.vercel.app/install.sh | sh` (or the zip: https://dssl-sable.vercel.app/dssl-engine.zip)
8
- - Version: 0.8.1 (engine), MIT license
9
  - Feature matrix vs CSS: see PARITY.md in the repo
10
  - For agents working ON the codebase: see AGENTS.md in the repo
11
 
12
  ## The five things reviewers ask about
13
 
14
  1. No build step. Styles are source files linked with `<link rel="stylesheet/dssl">` or embedded in the page as `<style type="text/dssl">` / `<script type="text/dssl">` blocks (auto-booted in document order). The engine restyles the live DOM, including nodes added later.
15
- 2. Honest trade: it is a JS runtime. If JavaScript is unavailable the page renders unstyled — unless you build the static subset: `dssl build styles.dssl -o styles.css` emits real CSS (tokens → custom properties, states → :hover, layers → @layer, motions → @keyframes, `raw { }` verbatim, `seen` applied as its no-JS final state) and the report counts what stays runtime-only (`@signal` rules, `animate:`). For content pages that must not flash, use the build output or choose another tool.
16
  3. Exit cost is zero. Abandoning DSSL leaves you a page of valid inline styles and one small shadow stylesheet for pseudo-elements. There is nothing to un-install from your markup.
17
  4. Speed has a handbrake. `DSSL.freeze()` detaches every observer and listener after first paint and leaves the resolved look at zero engine cost; `unfreeze()` re-arms. A gate prefilter skips obvious non-matches before native matching. The Bench page on the site measures 100 / 1,000 / 10,000 nodes on your machine: CSS `<style>` vs DSSL live vs DSSL frozen.
18
  5. The cascade is (layer, source order). No specificity, no !important. Nested state blocks sort after the parent rule's own declarations (v0.7.2 fix: they used to lose), and nested `@media`/`@signal`/`@layer` keep their condition. `pin { }` is the only must-win block. The compiler refuses to guess: unknown tokens, properties and values fail loudly with did-you-mean; `--*` custom properties pass through unvalidated on purpose.
@@ -31,7 +31,7 @@
31
  - Overlap policy: nothing below 100% opacity overlaps anything unless `overlap: allow`; `deny` is unconditional; pairs with `pointer-events: none` so veils stop eating clicks and text selection.
32
  - Behaviors: named rule groups applied with `use focus-ring`.
33
  - Verbatim escape hatch (v0.8.0): `raw { }` / `@css { }` drops real CSS (@font-face, @keyframes, @supports, @import, @property) into the page stylesheet untouched; unknown at-rules skip their whole block with one clean error. Inside raw { } semicolons are required (it is real CSS); a missing one is a warning with a line number.
34
- - Fluid sugar (v0.8.0): `font-size: fluid 16 24` (and any length-ish property) compiles to the linear clamp() between 360px and 1200px viewports (exactly the first size at 360px and the second at 1200px; px, rem or em, no percentages; shrinking sizes work); `fluid: 16, 24` still works. 0.8.1 fixed the math: 0.8.0 left out the intercept, so sizes were too large on small screens.
35
  - v0.8.0 review-driven fixes, all regression-tested incl. headless-Chromium computed-style tests of the docs: row/column axis roles (the documented `between center` example laid out wrong before); @font-face no longer crashes the compiler (unknown at-rules skip balanced); @supports inner rules no longer leak unconditionally; `url(...)` is atomic (unquoted form no longer false-errors); `!important` is a loud error that emits nothing; the animation shorthand warns when its motion is undefined instead of suggesting "span"; CSS-order transition values get the exact DSSL conversion in the error hint.
36
  - v0.7.2 review-driven fixes, all regression-tested: gate prefilter runs before native matches(); hover state events scope candidate recomputation to the touched subtree plus ancestors (was page-wide); checkbox/details states listen to `change`/`toggle`; raw hex alpha (`#ff0000/50` -> color-mix); bare numbers on time properties are ms; `::part()` supported, `::slotted()` rejected with an explanation; SVG properties (`fill`, `stroke*`) and `text-wrap`/`content-visibility` in the vocabulary.
37
  - Warnings channel (v0.7): things that compile but are probably wrong, e.g. a layout shorthand expanding to `display` and clobbering an earlier explicit `display:` in the same rule.
@@ -62,4 +62,4 @@ Wait for the `dssl-ready` event on `window` before measuring layout: the engine
62
 
63
  ## Editor support
64
 
65
- The official extension (VS Code and VSCodium, same .vsix) bundles the real parser and resolver: diagnostics identical to the browser console, completions from the workspace token registry, hover docs for the semantic vocabulary. Install `dssl-vscode/dssl-vscode-0.8.1.vsix` from the repo files. Source lives in `dssl-vscode/` in the repo.
 
1
  # DSSL
2
 
3
+ > DSSL is a style language for the web. For JS-guaranteed pages it replaces writing CSS; it does not replace the CSS platform. You write `.dssl` files, the page loads the engine (about 58 kB gzipped, about 190 kB as plain source), and the engine walks the DOM in real time applying every matched rule as inline styles. Nothing compiles to CSS at runtime, but v0.8.0 adds the exit lane: `dssl build` compiles the static subset to a real .css file for no-JS, strict-CSP or size-critical pages. Elements added later are styled the moment they appear; signals, states and motion share one grammar.
4
 
5
  - Site (docs, playground, changelog, bench): https://dssl-sable.vercel.app
6
  - Repo + full docs in the README: https://huggingface.co/EHS-Studio/dssl
7
  - Install: `curl -fsSL https://dssl-sable.vercel.app/install.sh | sh` (or the zip: https://dssl-sable.vercel.app/dssl-engine.zip)
8
+ - Version: 0.8.2 (engine), MIT license
9
  - Feature matrix vs CSS: see PARITY.md in the repo
10
  - For agents working ON the codebase: see AGENTS.md in the repo
11
 
12
  ## The five things reviewers ask about
13
 
14
  1. No build step. Styles are source files linked with `<link rel="stylesheet/dssl">` or embedded in the page as `<style type="text/dssl">` / `<script type="text/dssl">` blocks (auto-booted in document order). The engine restyles the live DOM, including nodes added later.
15
+ 2. Honest trade: it is a JS runtime. If JavaScript is unavailable the page renders unstyled, unless you build the static subset: `dssl build styles.dssl -o styles.css` emits real CSS (tokens → custom properties, states → :hover, layers → @layer, motions → @keyframes, `raw { }` verbatim, `seen` applied as its no-JS final state) and the report counts what stays runtime-only (`@signal` rules, `animate:`). For content pages that must not flash, use the build output or choose another tool.
16
  3. Exit cost is zero. Abandoning DSSL leaves you a page of valid inline styles and one small shadow stylesheet for pseudo-elements. There is nothing to un-install from your markup.
17
  4. Speed has a handbrake. `DSSL.freeze()` detaches every observer and listener after first paint and leaves the resolved look at zero engine cost; `unfreeze()` re-arms. A gate prefilter skips obvious non-matches before native matching. The Bench page on the site measures 100 / 1,000 / 10,000 nodes on your machine: CSS `<style>` vs DSSL live vs DSSL frozen.
18
  5. The cascade is (layer, source order). No specificity, no !important. Nested state blocks sort after the parent rule's own declarations (v0.7.2 fix: they used to lose), and nested `@media`/`@signal`/`@layer` keep their condition. `pin { }` is the only must-win block. The compiler refuses to guess: unknown tokens, properties and values fail loudly with did-you-mean; `--*` custom properties pass through unvalidated on purpose.
 
31
  - Overlap policy: nothing below 100% opacity overlaps anything unless `overlap: allow`; `deny` is unconditional; pairs with `pointer-events: none` so veils stop eating clicks and text selection.
32
  - Behaviors: named rule groups applied with `use focus-ring`.
33
  - Verbatim escape hatch (v0.8.0): `raw { }` / `@css { }` drops real CSS (@font-face, @keyframes, @supports, @import, @property) into the page stylesheet untouched; unknown at-rules skip their whole block with one clean error. Inside raw { } semicolons are required (it is real CSS); a missing one is a warning with a line number.
34
+ - Fluid sugar (v0.8.0): `font-size: fluid 16 24` (and any length-ish property) compiles to the linear clamp() between 360px and 1200px viewports (exactly the first size at 360px and the second at 1200px; px, rem or em, no percentages; shrinking sizes work); `fluid: 16, 24` still works; it also works on the longhands (`padding-top`, `margin-block`, `letter-spacing`, `border-radius`) since 0.8.2. 0.8.2 also made the cascade global: a stylesheet loaded later always beats an earlier one, and a later `tokens { }` block overrides an earlier conditional token. 0.8.1 fixed the math: 0.8.0 left out the intercept, so sizes were too large on small screens.
35
  - v0.8.0 review-driven fixes, all regression-tested incl. headless-Chromium computed-style tests of the docs: row/column axis roles (the documented `between center` example laid out wrong before); @font-face no longer crashes the compiler (unknown at-rules skip balanced); @supports inner rules no longer leak unconditionally; `url(...)` is atomic (unquoted form no longer false-errors); `!important` is a loud error that emits nothing; the animation shorthand warns when its motion is undefined instead of suggesting "span"; CSS-order transition values get the exact DSSL conversion in the error hint.
36
  - v0.7.2 review-driven fixes, all regression-tested: gate prefilter runs before native matches(); hover state events scope candidate recomputation to the touched subtree plus ancestors (was page-wide); checkbox/details states listen to `change`/`toggle`; raw hex alpha (`#ff0000/50` -> color-mix); bare numbers on time properties are ms; `::part()` supported, `::slotted()` rejected with an explanation; SVG properties (`fill`, `stroke*`) and `text-wrap`/`content-visibility` in the vocabulary.
37
  - Warnings channel (v0.7): things that compile but are probably wrong, e.g. a layout shorthand expanding to `display` and clobbering an earlier explicit `display:` in the same rule.
 
62
 
63
  ## Editor support
64
 
65
+ The official extension (VS Code and VSCodium, same .vsix) bundles the real parser and resolver: diagnostics identical to the browser console, completions from the workspace token registry, hover docs for the semantic vocabulary. Install `dssl-vscode/dssl-vscode-0.8.2.vsix` from the repo files. Source lives in `dssl-vscode/` in the repo.
scripts/test-v082.mjs ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* test-v082.mjs: one assertion per change in 0.8.2.
2
+ * Found while rebuilding the site on its own engine:
3
+ * 1. "later wins" did not hold BETWEEN stylesheets. Each sheet numbers its
4
+ * rules from 0 and the cascade compared those numbers across sheets, so a
5
+ * later sheet lost to an earlier one that simply had more rules above the
6
+ * rule in question. Fixed by giving every rule one global position.
7
+ * 2. a later tokens { } block could not override a token an earlier sheet
8
+ * set inside @media or @signal (same cause).
9
+ * 3. fluid did not work on padding-top, margin-block and the other longhands.
10
+ * The cascade tests use the engine's own sort (layer rank, then order) on rules
11
+ * from real compiled sheets, so they check the thing that was wrong. */
12
+ import { assignGlobalOrder } from '../public/dssl/engine.js';
13
+ import { compile } from '../public/dssl/index.js';
14
+
15
+ let pass = 0, fail = 0;
16
+ const ok = (name, cond, extra) => {
17
+ if (cond) { pass++; console.log(` ok ${name}`); }
18
+ else { fail++; console.log(`FAIL ${name}${extra ? '; ' + extra : ''}`); }
19
+ };
20
+
21
+ const LAYER_RANK = { tokens: 0, base: 1, components: 2, utilities: 3, pin: 4 };
22
+ const cascade = (rules) => [...rules].sort((a, b) => (LAYER_RANK[a.layer] ?? 2) - (LAYER_RANK[b.layer] ?? 2) || a.order - b.order);
23
+ const bundleOf = (src) => { const { doc } = compile(src, 't.dssl'); return { rules: doc.rules, shadowRules: doc.shadowRules, raws: doc.raws || [] }; };
24
+ const winner = (rules, sel) => cascade(rules.filter((r) => r.sel === sel)).pop();
25
+ const val = (rule, prop) => rule.decls.filter((d) => d.prop === prop).pop()?.value;
26
+
27
+ /* ---- 1: later sheets win ---- */
28
+ {
29
+ let filler = '';
30
+ for (let i = 0; i < 30; i++) filler += `.zz${i} { color: green }\n`;
31
+ const first = bundleOf(filler + '.x { color: red }');
32
+ const second = bundleOf('.x { color: blue }');
33
+ const before = winner([...first.rules, ...second.rules], '.x');
34
+ ok('1a. the bug: without a global order the EARLIER sheet wins', val(before, 'color') === 'red', val(before, 'color'));
35
+
36
+ const a = bundleOf(filler + '.x { color: red }');
37
+ const b = bundleOf('.x { color: blue }');
38
+ assignGlobalOrder([a, b]);
39
+ const after = winner([...a.rules, ...b.rules], '.x');
40
+ ok('1b. with a global order the LATER sheet wins', val(after, 'color') === 'blue', val(after, 'color'));
41
+
42
+ const lastOfFirst = Math.max(...a.rules.map((r) => r.order));
43
+ const firstOfSecond = Math.min(...b.rules.map((r) => r.order));
44
+ ok('1c. every rule of a later sheet sorts after every rule of an earlier one', firstOfSecond > lastOfFirst, `${firstOfSecond} vs ${lastOfFirst}`);
45
+
46
+ const snapshot = [...a.rules, ...b.rules].map((r) => r.order).join(',');
47
+ assignGlobalOrder([a, b]);
48
+ assignGlobalOrder([a, b]);
49
+ ok('1d. it is idempotent: rebuilding does not make the numbers drift', [...a.rules, ...b.rules].map((r) => r.order).join(',') === snapshot);
50
+
51
+ // replacing a sheet in place: new rules get numbered from the same slot
52
+ const b2 = bundleOf('.x { color: purple }\n.y { color: teal }');
53
+ assignGlobalOrder([a, b2]);
54
+ ok('1e. a replaced sheet still sorts after the sheet before it', val(winner([...a.rules, ...b2.rules], '.x'), 'color') === 'purple');
55
+
56
+ // three sheets in a row
57
+ const c = bundleOf('.x { color: orange }');
58
+ assignGlobalOrder([a, b2, c]);
59
+ ok('1f. with three sheets the last one wins', val(winner([...a.rules, ...b2.rules, ...c.rules], '.x'), 'color') === 'orange');
60
+
61
+ // within one sheet nothing changed: the later line still wins
62
+ const one = bundleOf('.x { color: red }\n.x { color: green }');
63
+ assignGlobalOrder([one]);
64
+ ok('1g. inside one sheet the later rule still wins', val(winner(one.rules, '.x'), 'color') === 'green');
65
+
66
+ // layers still outrank order: a utilities rule in an EARLY sheet beats a components rule in a late one
67
+ const early = bundleOf('@layer utilities { .x { margin-top: 0 } }');
68
+ const late = bundleOf('.x { margin-top: 30 }');
69
+ assignGlobalOrder([early, late]);
70
+ ok('1h. layers still outrank load order', val(winner([...early.rules, ...late.rules], '.x'), 'margin-top') === '0px');
71
+
72
+ // shadow (pseudo-element) rules and raw blocks are renumbered with their sheet
73
+ const sh1 = bundleOf('.a::before { content: "a" }');
74
+ const sh2 = bundleOf('.a::before { content: "b" }');
75
+ assignGlobalOrder([sh1, sh2]);
76
+ ok('1i. pseudo-element rules follow the same global order', sh2.shadowRules[0].order > sh1.shadowRules[0].order);
77
+ }
78
+
79
+ /* ---- 2: a later tokens block beats an earlier conditional token ---- */
80
+ {
81
+ const palettes = bundleOf(`tokens { color { accent: red } }
82
+ @media (prefers-color-scheme: dark) { tokens { color { accent: pink } } }`);
83
+ const later = bundleOf('tokens { color { accent: blue } }');
84
+ const tokenRules = (b) => b.rules.filter((r) => r.layer === 'tokens' && r.decls.some((d) => d.prop === '--dssl-color-accent'));
85
+ const before = cascade([...tokenRules(palettes), ...tokenRules(later)]).pop();
86
+ ok('2a. the bug: an earlier conditional token out-ranked a later tokens block', before.decls.some((d) => d.value === 'pink'));
87
+
88
+ const p = bundleOf(`tokens { color { accent: red } }
89
+ @media (prefers-color-scheme: dark) { tokens { color { accent: pink } } }`);
90
+ const l = bundleOf('tokens { color { accent: blue } }');
91
+ assignGlobalOrder([p, l]);
92
+ const after = cascade([...tokenRules(p), ...tokenRules(l)]).pop();
93
+ ok('2b. now the later tokens block wins, even in dark mode', after.decls.some((d) => d.value === 'blue'));
94
+ }
95
+
96
+ /* ---- 3: fluid on the longhands ---- */
97
+ {
98
+ const evalClamp = (css, vw) => {
99
+ const m = /^clamp\(\s*(-?[\d.]+)px,\s*calc\(\s*(-?[\d.]+)px\s*\+\s*(-?[\d.]+)vw\s*\),\s*(-?[\d.]+)px\s*\)$/.exec(css);
100
+ if (!m) return NaN;
101
+ return Math.min(+m[4], Math.max(+m[1], +m[2] + (+m[3] * vw) / 100));
102
+ };
103
+ const near = (x, y) => Math.abs(x - y) < 0.01;
104
+ for (const prop of ['padding-top', 'padding-bottom', 'padding-left', 'padding-right', 'padding-inline', 'padding-block', 'margin-top', 'margin-bottom', 'margin-left', 'margin-right', 'margin-inline', 'margin-block', 'letter-spacing', 'border-radius']) {
105
+ const { doc, ok: good } = compile(`.a { ${prop}: fluid 8 24 }`);
106
+ const v = doc.rules[0]?.decls.find((d) => d.prop === prop)?.value;
107
+ ok(`3. ${prop}: fluid 8 24 is 8px at 360 and 24px at 1200`, good && near(evalClamp(v, 360), 8) && near(evalClamp(v, 1200), 24), v);
108
+ }
109
+ const still = compile('.a { padding: fluid 8 16 }');
110
+ ok('3b. the shorthand still works', still.ok && /^clamp\(/.test(still.doc.rules[0].decls[0].value));
111
+ const nope = compile('.a { opacity: fluid 0 1 }');
112
+ ok('3c. fluid on a property that cannot take it is still an error, not silence', !nope.ok);
113
+ }
114
+
115
+ console.log(`\n${pass} passed, ${fail} failed`);
116
+ process.exit(fail ? 1 : 0);
src/app/api/route.ts DELETED
@@ -1,5 +0,0 @@
1
- import { NextResponse } from "next/server";
2
-
3
- export async function GET() {
4
- return NextResponse.json({ message: "Hello, world!" });
5
- }