Skip to content

Releases: dembrandt/dembrandt

v0.39.2

Choose a tag to compare

@thevangelist thevangelist released this 11 Oct 14:05

Radii named without the word radius match again.

Fixed

  • 0.39.1 matched radii only against tokens whose name says radius, rounded or corner, so a file naming them shape.md or border.sm lost every radius match. Unnamed dimensions now stand in for a kind that has no tokens of its own, never for one that has (#274)

Upgrading

Output contract unchanged.

v0.39.1

Choose a tag to compare

@thevangelist thevangelist released this 11 Oct 13:55

Two findings from the first pipeline run of the token check.

Fixed

  • --tokens matched font sizes against radius tokens when the file had no font-size tokens. Font sizes now match only font-size tokens and radii only radius tokens; the report counts the tokens of each kind and says when there are none (#273)
  • --login with a piped stdin fell through to the sign-in page and saved nothing. It now exits 2 before the browser opens and names the reuse path: sign in once in a terminal with --save-storage-state, then --cookie-file (#273)

Upgrading

Output contract unchanged. tokenConformance.categories.* gains tokens and tokenConformance.tokens splits dimensions into radius, fontSize and dimension; additive.

v0.39.0

Choose a tag to compare

@thevangelist thevangelist released this 11 Oct 13:03

The run shows its evidence, and it reaches the pages behind a sign-in.

Evidence

  • colors.semanticEvidence.primary: how the primary was decided, elected with rule, reason, declaring tokens and runners-up, or refused with the candidates that lost. Terminal, HTML report and DESIGN.md say which (#261)
  • colors.occurrences: every colour per paint (fill, text, border) with summed fill area and the declaring custom property, summed across a crawl (#263, #267)
  • meta.context: the browser context the main pass was measured under; drift warns when two snapshots differ in a dial. --header repeats (#262)
  • A crawl tags every merged value with pageCount and scope and carries coverage, a 0-100 agreement score with the page-local outliers (#264)
  • colors.cssVariables is complete, with scope; palette.tokens names every merged colour (#260)
  • Drift compares frameworks and iconSystem as a tech category (#259)

Reach

  • --cookie-file, --basic-auth, and --login with --save-storage-state: sign in once in a visible browser, reuse the session (cookies, localStorage, sessionStorage) in later runs and CI; meta.sessionWarnings reports a lapsed or rejected session (#265)
  • --no-headed-fallback keeps a failed navigation from opening a visible browser (#269)

Against a token file

  • --tokens checks every rendered colour, radius and font size against a W3C Design Tokens file, --max-off-token gates on the count, --tokens-report writes a wiki-ready Markdown page, --annotations github|azure prints CI log commands for off-token values and drift (#270)

Also

  • The MCP server is published to the MCP Registry with each release (#268)
  • Formatter fixes: off-grid spacing findings, letter spacing in DTCG and DESIGN.md, heading token names, HTML role labels and contrast grading by text size (#267)
  • Playwright 1.63.0 and the lint toolchain (#266)

Upgrading

Output contract 1.18.0 to 1.19.0, additive. Measured against a same-day 0.38.0 control over 175 corpus sites: 158 identical, three primaries moved, two of them bot walls and one a site that moves between control days too. --compare and release:churn on dembrandt.com and stripe.com against 0.38.0: stable 0. A colour seen under three times reads role: supporting where it read accent. Primary accuracy on the 141-site labelled corpus: 106 correct on 0.39.0 against 105 on a same-day 0.38.0 control.

v0.38.0

Choose a tag to compare

@thevangelist thevangelist released this 03 Oct 20:07

Detection now names what builds the page, with versions.

Added

  • frameworks names what builds the UI: JS and meta frameworks, CSS frameworks, component libraries, web components, CSS-in-JS and site builders. Entries carry category, a version where the page states one, and coverage, the share of the page under that framework's mount points (#256)
  • iconSystem entries carry version, and more icon sets are named
  • The terminal and the HTML report show the versions

Changed

  • Every run installs an inert React devtools hook before the page loads and reads the script and stylesheet responses the page fetched, capped at 4 MB a file and 40 MB a page. Versions come from what the page states: a global, a licence banner, a constant that survives minification, the renderer's hook
  • A match on weak evidence alone is reported with medium confidence, and evidence quotes the marker that matched
  • The generic SVG Icons entry is reported only when no icon set was named
  • Across crawled pages the more confident reading of a framework is kept, with any version another page stated

Fixed

  • Consent dismissal could click a link to another origin and carry the run to a different site. It no longer clicks such a link
  • A frame that never answers no longer hangs consent dismissal

Upgrading
Output contract 1.17.0 to 1.18.0, additive. Churn on the reference site against 0.37.0: stable 0, and the drift engine reads neither field. A consumer that compares frameworks or iconSystem names across snapshots will see them differ once: more is named, and SVG Icons drops out where a set was named.

v0.37.0

Choose a tag to compare

@thevangelist thevangelist released this 29 Sep 17:34

What the page declares now reaches the output: token names, font families, a monochrome primary.

Fixed

  • customFonts names the declared families instead of fetched file names; selfHostedFonts keeps the file names and sources.fontDisplay is emitted for the first time (#240)
  • A site whose every button is black or white elected no primary; the shared fill is the primary when nothing chromatic was elected and two or more buttons share it (#245)
  • A crawl's merged palette could drop the homepage primary in favour of a more counted shade; the elected primary stays canonical for its cluster (#238)
  • Headless UI was reported on any Tailwind site with three or more accessible toggles; detection matches only the library's own markers (#236)

Added

  • Palette entries carry tokens, the custom property names that declare that colour. Read from html and body, gated on the value being a colour, color-mix() resolves (#237)

Changed

  • README export flags ordered by how people reach for them (#241)

Upgrading
Output contract 1.16.0 to 1.17.0, additive. Churn on the reference site against 0.36.0: stable 0. semantic.primary moves on sites that theme through unprefixed tokens and on monochrome sites that previously reported none. Re-approve with --compare --approve once, or regenerate the baseline.

v0.36.0

Choose a tag to compare

@thevangelist thevangelist released this 22 Sep 21:46
29d54ba

What the tool reports matches what is on the page.

Colours are read only off elements that paint them. color and border-color both
resolve to currentColor when unset, so every wrapper reported a value it never drew,
and on an unstyled anchor that value is the browser's own default link blue. Palettes
come out smaller and carry fewer invisible greys.

A baseline that parsed but was not an extraction read as an empty one, so every token
counted as added and the run reported total drift and exited 1, the code reserved for a
real design change. A truncated download or a typo onto another JSON file now exits 2
and names what is missing.

Six MCP tools an agent could not reach before: export_tailwind, export_shadcn,
get_motion, validate_dtcg, check_contrast and check_robots. darkMode moves
into the shared parameter set, and a surface-parity test fails when a CLI flag has
neither a tool, a parameter, nor a written reason for being CLI-only. The 21 tool
registrations are typed rather than cast.

meta.crawl.pages carries the landed URL of every page merged into a crawl, in order.

Upgrading

Output contract 1.15.0 to 1.16.0. Palettes shrink on most sites and semantic.primary
moves on some, because a colour the page never painted can no longer win. Re-approve
once with --compare <baseline> --approve, or regenerate the baseline.

Churn measured against 0.35.0, twice, on two reference sites: dembrandt.com stable 0,
stripe.com stable 5 against a threshold of 10.

v0.35.0

Choose a tag to compare

@thevangelist thevangelist released this 20 Sep 18:10
7fdc5d6

Three scoring checks that were quietly returning a pass.

Fixed

  • The drift gate could not fail on a changed brand colour. The semantic map shared the palette's denominator, so a moved primary was divided by every stable palette entry: a real stripe.com baseline with semantic.primary turned magenta reported delta 49.8, scored stable 0 and exited 0. Palette and semantic are scored apart now and the worse one stands, so a moved role reaches the threshold while the palette keeps its tolerance for run-to-run variance (DEM-376)
  • Off-scale spacing never fired. The check compared spacing.scaleType to base-8/base-4 while the extractor has written 8px/4px since schema 1.14.0, so the base was always 0 and every site reported zero off-grid values. Both spellings read through one helper now, shared with the Tailwind emitter, which carried a third copy of the same regex (DEM-379)
  • A colour swatch beside the hex that names it entered the palette as brand colour. The sample filter only recognised a strip of four or more equal siblings under one parent, while documentation far more often lists one swatch per row next to its value. Any page documenting a palette scored its own samples as brand evidence (DEM-253)

Upgrading

Output contract 1.14.0 to 1.15.0. These move values for a site that did not change, so they ship together and a consumer re-approves one baseline rather than three.

Baseline churn measured against 0.34.2 on two reference sites, twice each: dembrandt.com stable 0, stripe.com stable 4 against a threshold of 10. No gate flips on an unchanged site.

A site whose brand colour genuinely moved will now fail a gate that passed before. That is the fix. Re-approve once with --compare <baseline> --approve, or regenerate the baseline.

v0.34.2

Choose a tag to compare

@thevangelist thevangelist released this 19 Sep 13:23
7630642

A colour's alpha can be an alias, the way its components already could.

Fixed

  • The DTCG validator rejected a property-level $ref in a colour's alpha, although §7.3.1 allows one and the same colour's components already accepted it. A token set that aliases one opacity across a palette failed validation. The reference resolves now, and the resolved value is type-checked and range-checked like a literal

v0.34.1

Choose a tag to compare

@thevangelist thevangelist released this 19 Sep 13:05
27677c9

The shadcn radius was the smallest one on the page, not the one the page uses.

Fixed

  • --shadcn wrote --radius: 1px for a site whose buttons and inputs are 4px. The radius list arrives sorted by length and the emitter took the first entry, so a 31-use hairline outranked the 141-use value. It takes the highest count now
  • The --radius-sm and --radius-md rungs were emitted as a bare calc(), so a small base produced calc(1px - 4px): a negative radius the browser drops. Both are clamped with max(0px, ...)
  • The release workflow's downstream sync read npm before the publish had propagated and failed on ETARGET, leaving the dembrandt-next bump PR unopened. It waits for the version to resolve first

v0.34.0

Choose a tag to compare

@thevangelist thevangelist released this 19 Sep 12:36
356303a

Three heuristics stopped deciding a whole page from a single observation.

Fixed

  • spacing.scaleType called a page an 8px system if any one value divided by 8, so almost every site claimed a grid it did not have. The verdict is now weighted by how much of the observed spacing lands on the step, and a site reads custom below 60%. Two large public sites with 20% and 3% on the 8 grid both move
  • spacing.commonValues[].rem was computed against a hardcoded 16, so the rem column was wrong on any site that sets html { font-size }. It reads the document's root size now
  • Swatch strips, colour labels and code samples were scored as painted brand colour, so any page documenting a palette poisoned its own extraction. A run of four or more equal-size siblings with distinct opaque fills, an element whose whole text is its own colour value, and anything inside code/pre/samp/kbd are out. A row of cards shares one fill and is untouched
  • The neutral-primary rescue only woke below 0.12 chroma, so brands whose real colour sat in the palette kept a grey primary. The gate is 0.20; the bar a replacement must clear is unchanged at 0.25, so a deliberately neutral identity is protected exactly as before

Added

  • --shadcn writes a shadcn/ui theme: the :root block and the @theme inline mapping Tailwind v4 needs. A slot is written only when the page supplied it, and the rest are named in the file header and left to shadcn's own defaults, so a theme is never padded with plausible values that read as measured. Measured over 206 extractions: background, foreground, primary, border and radius land on almost every site; popover and destructive on none. --dark-mode produces the .dark block

Changed

  • Schema 1.14.0. Values move for an unchanged site: spacing.scaleType, spacing.commonValues[].rem, colors.palette and colors.semantic.primary. No field is added or removed, and a consumer re-approves one baseline rather than four. The neutral-primary change was measured over the 29 corpus sites that can change at all: two improved, none regressed. The drift gate scores this release stable 0 on dembrandt.com and stable 4 on stripe.com, because it does not read scaleType: the movement is real and the gate is blind to it