Skip to content

feat(token-sets): denhaag declares its bridge tokens from its own vendored source - #1089

Merged
rubenvdlinde merged 3 commits into
developmentfrom
feat/denhaag-declares-its-bridge-tokens
Oct 5, 2026
Merged

rubenvdlinde merged 3 commits into
developmentfrom
feat/denhaag-declares-its-bridge-tokens

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What this does

The coverage audit read denhaag at 0 of the 87 --utrecht-* names
css/systems/nldesign/utrecht-bridge.css reads. It is now at 81 of 87.

It is the one bridge-zero set that could be completed without choosing a
municipality's house style. scripts/sources/denhaag/ already holds the pinned
@gemeente-denhaag token CSS, vendored for scripts/generate-denhaag-bridge.mjs
and annotated EUPL-1.2 in REUSE.toml. So every value here is read off that
source. The seven other bridge-zero sets have no such source and stay undone on
purpose.

Bridge-zero never meant the set rendered wrong. Of the bridge's 84 declarations,
42 fall back to an --nldesign-* token the set declares and 38 to a non-colour
literal. This completes a set from a real source. It repairs no defect.

Where the 81 come from

  • 52 are Den Haag's own, declared in design-tokens-common@4.0.0.css and
    design-tokens-components@5.1.0.css: the button, badge, link, heading,
    paragraph, table, textbox, select and form-label families.
  • 29 come from the shared role layer, with the colour ramp re-pointed at Den
    Haag's palette: the heading colours and weights, --utrecht-heading-6-*,
    --utrecht-paragraph-color, the list colours and the rest of the button
    family.
  • 6 stay undeclared because neither the source nor the role layer states
    them: --utrecht-form-input-border-color, -border-radius, -font-size,
    --utrecht-separator-border-color, -border-width and
    --utrecht-table-header-cell-background-color. They keep the bridge fallback.

What is left out for want of a source

  • The pink ramp (4 steps), extra-limegreen and extra-purple. Den Haag
    publishes no magenta, lime or purple family. The generator reports them as 6
    unsubstituted literals, 12 occurrences, rather than hiding them. Two of the
    three places they reach are repointed by name instead: the focus ring takes
    Den Haag's own ocher-5 and the status badge takes its grey-1.
  • Den Haag's house font. Its token CSS names TheSans, a LucasFonts retail face.
    No release of it may be redistributed in an app package, so token-sets.json
    records the licence position the way duo does for RijksoverheidSans, and the
    set renders the self-hosted Fira Sans behind it until an administrator uploads
    the licensed one. The coverage table's font cell moves from self-hosted to
    declared, which still passes.
  • --nldesign-border-radius-pill. Den Haag ships one radius token, 3px, so
    every radius role is 3px. The pill value is the role layer's own lozenge
    geometry and the brand file says so.

Nothing is interpolated

The grey ramp is the one place the source does not map one for one: the role
layer wants eleven greys and Den Haag publishes six neutrals. The brand file
states the rule it follows, takes the declared neutral whose own hsl lightness
is nearest the slot's, and never computes a step in between. Five Den Haag greys
land on ten slots, two slots each, with warmgrey-1 on gray-50. gray-400 is the
one close call and the brand file names it: 82% against the slot's 66%, with
grey-3 at 48% one point further. It is read once, for
--utrecht-feedback-neutral-fill-border-color.

The palette is converted from hsl() to 8-bit sRGB hex, with the whole
conversion table in the brand file. This is not cosmetic:
ContrastService::parseColor returns null for hsl(), so leaving the source
notation would have turned all 15 measured Den Haag contrast pairs into
unevaluated ones while the verdict still read pass.

What a reader will see change

--nldesign-color-primary moves from #1a7a3e, which no Den Haag token states,
to green-3 #238541, which is
--utrecht-button-primary-action-background-color. The header moves from green
to white, because --denhaag-page-header-background-color is white and Den Haag
paints its own side navigation grey-4 on white.

Contrast, before and after

before after
textRatio (--nldesign-color-primary-text on --nldesign-color-primary) 5.39 4.66
uiRatio (--nldesign-color-primary on --nldesign-color-background) 5.39 4.66
verdict pass pass
Den Haag pairs 15 pass, 0 fail, 0 unevaluated 15 pass, 0 fail, 0 unevaluated

Both ratios fall because Den Haag's own green is lighter than the invented one.
Both stay above their thresholds, 4.5 and 3.0.

One measurement that changed a decision

--nldesign-color-primary-light-hover was green-2 first. Den Haag states no
hover wash, and green-2 is the only other green it publishes. Deriving the dark
variant with it pushed --nldesign-color-primary to near-black #111111,
because the repair loop could not reach 4.5:1 for primary sitting on the
lightened green-2 wash. Den Haag's one use of green-2 is a border, on
--denhaag-floating-contact-switch-border-color, so a wash made from it was a
colour Den Haag does not paint anyway. It is green-1 again, the hover state
matches the rest state, and the dark primary is a green #55d27b.

Regenerated, and nothing else moved

php scripts/generate-dark-variants.php --force rewrote all 58 dark variants
and only css/tokens/dark/denhaag.css differs from origin/development.
Verified twice, once per generator run.

One test changed, and why

tests/vitest/denhaagBridge.spec.js used denhaag as its example of a set with
only the semantic layer. That is no longer true of it, so the case card title
came back as Den Haag's blue-5 rather than the set's text colour. The subject
moves to tilburg, which the test now asserts declares no --denhaag-*
property at all, and a second test covers the mixed case: denhaag keeps its
own title and subtitle colours and still takes the card border through the
bridge, which is what linking the bridge first is for.

Licensing

REUSE.toml names both copyright holders on the generated set, its dark variant
and the two brand files. Their values are half Den Haag's and half ours, and
both halves are EUPL-1.2, so the licence identifier was already right and only
the copyright line was not.

Inherited, reported not fixed

css/public-bridge.css and docs/features/public-portals-as-consumers.md
claimed no set declares a case card property. cunningham already declared one
before this change, so the claim was wrong by one on development; it is now
two and the line is corrected, because this change had to touch it anyway.
css/tokens/rotterdam.css has the same composite-copyright shape as the Den
Haag set and no REUSE entry of its own. That predates this change and is noted
in REUSE.toml rather than fixed here.

Verified

Run once in ~/memcap-work/portal-design/dh/thematiq, each with its own
TMPDIR so no analyser cache is shared with another lane.

check exit
bash scripts/token-set-gate.sh (331 PHPUnit, 5691 assertions; 299 vitest) 0
npm run audit:token-sets:check 0
npm run test:token-set-coverage 0
COMPOSER_PROCESS_TIMEOUT=0 composer check:strict 0
npm run lint 0
npm run format 0
npm run stylelint 0
npm run test:denhaag-bridge 0
npm run test:l10n 0
npm run check:manifest 0

Two things that green does not cover, stated rather than implied.
composer check:strict reports test:all SKIPPED, because this clone sits
outside a Nextcloud server tree and the suite type-hints OCP interfaces. The
token-set subset it would have covered ran anyway, as
phpunit.token-sets.xml through the gate, including
DenhaagContrastPairsTest, TokenSetContrastAuditTest,
TokenSetFontAuditTest, TokenSetVocabularyTest and ClaimAccuracyTest. No
PHP source changed in this branch. CI's PHPUnit job is the authority for the
rest.

…source

The coverage audit read denhaag at 0 of the 87 --utrecht-* names the bridge
reads. It is the one bridge-zero set whose upstream is already vendored here:
scripts/sources/denhaag/ holds the pinned @Gemeente-DenHaag token CSS under
EUPL-1.2, so the values can be sourced instead of chosen.

scripts/brands/denhaag.json plus the captured
scripts/brands/denhaag.components.json regenerate css/tokens/denhaag.css
through scripts/generate-brand-set.mjs. The set now declares 81 of the 87
bridge names: 52 are Den Haag's own, 29 come from the shared role layer with
the ramp re-pointed at Den Haag colours.

Nothing is interpolated. The pink ramp, extra-limegreen and extra-purple have
no Den Haag family and are left out, which the generator reports as 6
unsubstituted literals. Six bridge names stay undeclared because neither the
source nor the role layer states them.

The palette is converted from hsl() to 8-bit sRGB hex, with the whole table in
the brand file. ContrastService::parseColor returns null for hsl(), so leaving
it would turn every measured Den Haag pair into an unmeasurable one.

primary moves from the invented #1a7a3e to Den Haag's own green-3 #238541, and
the header moves from green to white, which is what
--denhaag-page-header-background-color states.

token-sets.json records the font licence position: Den Haag names TheSans, a
LucasFonts retail face, so the set declares it and renders the self-hosted
Fira Sans until an administrator uploads the licensed one.

REUSE.toml names both copyright holders on the generated set and the brand
files, because their values are half Den Haag's and half ours.
The dark variant, the contrast report, the token reference page, the coverage
table and the two coverage claims all follow from css/tokens/denhaag.css, and
their staleness checks compare byte for byte.

`generate-dark-variants.php --force` rewrote all 58 variants and only
css/tokens/dark/denhaag.css differs from development. Verified after both
generator runs.

Contrast moves and the verdict holds: textRatio and uiRatio both 5.39 -> 4.66,
thresholds 4.5 and 3.0, verdict pass. The 15 measured Den Haag pairs stay 15
pass, 0 fail, 0 unevaluated, which is what converting the palette to hex buys.

--nldesign-color-primary-light-hover is green-1 rather than green-2. Den Haag
states no hover wash and uses green-2 only as a border colour. Deriving the
dark variant with it pushed --nldesign-color-primary to near-black #111111,
because the repair loop could not reach 4.5:1 for primary on the lightened
green-2 wash. With green-1 the dark primary is #55d27b.

tests/vitest/denhaagBridge.spec.js used denhaag as its example of a
semantic-only set, which it no longer is. The subject moves to tilburg, which
the test now asserts declares no --denhaag-* property, and a second test covers
the mixed case: denhaag keeps its own case card title and subtitle and still
takes the card border through the bridge.

The bridge header and the portals doc claimed no set declares a case card
property. cunningham already declared one before this change, so the count is
two.
…nance

The generator repeats the provenance string inside section A's own comment, so
a parenthesised licence note read as a bracket inside a bracket. Same facts,
commas instead. Only the two generated files and the brand file move; the
contrast report and the coverage table do not.
@rubenvdlinde
rubenvdlinde merged commit 416ef61 into development Oct 5, 2026
34 checks passed
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/thematiq @ 36d2ed7

Check PHP Vue Security License Tests
lint ✅
phpcs ✅
phpmd ✅
psalm ✅
phpstan ✅
phpmetrics ✅
eslint ✅
stylelint ✅
build ✅
check-manifest ✅
test-l10n ✅
format ✅
test-fonts ✅
test-token-set-coverage ✅
composer ✅ ✅ 107/107
npm ✅ ✅ 2/2
app:check-code ⏭️
info.xml ✅
REUSE ✅
lockfile sync ✅
PHPUnit ✅
Newman ✅
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ✅

Quality workflow — 2026-10-05 09:38 UTC

Download the full PDF report from the workflow artifacts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant