You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adopt the theme capabilities the nldesign app already owns — token-set generation, adoption and sharing — instead of the single static
stylesheet link Portaliq uses today. Portaliq stopped shipping design tokens
when css/themes/ was deleted and the VNG set moved to nldesign
(ConductionNL/thematiq#352); this change completes that move by consuming the
app's interfaces rather than only its files.
1.1 Read the catalogue rather than probing the filesystem for css/tokens/<theme>.css. Resolved server-side from the theme app's token-sets.json — the same file CatalogController serves — because the renderer runs for ANONYMOUS visitors and the endpoint is deliberately not public (see 1.2).
1.2 Establish that the catalogue endpoint is safe for an ANONYMOUS caller, not merely a non-admin one. It is not, by design.CatalogController::tokenSets() is #[NoAdminRequired] and deliberately not #[PublicPage]: its docblock states that exposing admin-uploaded custom sets to anonymous traffic "would be a new information-disclosure surface with no consumer need". The route was left alone; the public renderer reads the catalogue from disk, and the authenticated admin UI remains the endpoint's consumer.
1.3 PortalThemeResolver resolves portal.theme against the catalogue and returns null when the id is unknown. A file present on disk but absent from the catalogue — a generated variant, a leftover — is no longer adoptable.
1.4 Surface the resolvable set to the admin UI so portal.theme becomes a chosen id rather than free text.
1.5 Test: an unknown id renders UNSTYLED and does not fall back. Covered per branch: uncatalogued file, catalogued-but-missing file, and an unreadable catalogue (fails closed).
2.1 Link css/tokens/dark/<id>.cssWithdrawn on measurement, and the withdrawal is the deliverable. Implemented, rendered and measured twice: the artefact as generated changed 0 of 1,152,000 pixels; after nldesign was fixed (see 2.5) it changed 53% and left 10 of 11 text nodes below 4.5:1. This site has no token-driven surface layer. Backed out, with the numbers recorded in templates/site.php and pinned by a test so re-adding the line is deliberate.
2.2 Respect the instance's dark-mode toggle the same way CssInjectionService does; a portal must not invent a second switch. Blocked on 2.6.
2.3 Consume FontService for the theme's declared fonts instead of portaliq/css/nlds/nlds-fonts.css, which currently re-declares faces by hand because the vendored CSS carried root-relative urls.
2.4 Test: with a generated dark variant present, a prefers-color-scheme: dark visitor gets it. Blocked on 2.6.
2.5 Fix the generator in nldesign — three defects found by chasing the 0-pixel result, each of which produced output that reads as a successful run (fix(dark-mode): resolve var() aliases before deriving, and honour the generator version thematiq#353): var() aliases were never darkened (an alias declared on :root resolves there, and the dark block scopes to body, a descendant — so only 13 of 600 --utrecht-* tokens survived); text was classified as surface outside the --nldesign-* naming convention; and GENERATOR_VERSION was stamped into every header and read by nothing, so an algorithm fix regenerated 0 of 41 sets.
2.6 Give the site a token-driven surface layer — bands, cards and the page itself. Painting body from --utrecht-document-* is verified harmless (0 pixels changed in light mode) and insufficient alone: the inner bands stayed white. This is the real prerequisite for dark mode, and it is a change to the site's own CSS, not to the theme app.
3.1 Call ContrastController for the adopted theme and record the verdict against the portal.
3.2 Refuse — or loudly warn on — a theme whose own tokens fail AA for the surfaces a portal actually paints (bands, cards, footer).
3.3 Add the portal's own rendered surfaces to the check, walking to the first ancestor that PAINTS a background. Comparing against the nearest NAMED band produced a false failure in this codebase once already, and the "fix" for it made a working form invisible.
3.4 Test: a deliberately low-contrast token set is rejected/flagged; a compliant one passes.
4.1 Consume NlDesignThemeShareableConfigType so a theme shared through OpenRegister can be adopted by a portal.
4.2 Route every shared set through CustomTokenSetValidator before it can be linked. Shared configuration is input from another instance and must not be able to inject CSS.
4.3 Decide and document what happens when a shared theme is withdrawn while a portal is using it — the portal must not silently lose its styling.
4.4 Test: a shared set with a hostile declaration is refused, and the refusal is visible.
5.1 Record in nldesign that portals are a consumer of the catalogue, the dark variants and the shareable config type — the docs currently describe the Nextcloud UI only.
5.2 Update ADR-086 §6 ("Portaliq ships NO theming mechanism of its own") to state what it now consumes instead.
Synced from openspec/changes/nldesign-theme-integration by OpenSpec workflow App: portaliq
Artifacts
Summary
Adopt the theme capabilities the
nldesignapp already owns — token-setgeneration, adoption and sharing — instead of the single static
stylesheet link Portaliq uses today. Portaliq stopped shipping design tokens
when
css/themes/was deleted and the VNG set moved tonldesign(ConductionNL/thematiq#352); this change completes that move by consuming the
app's interfaces rather than only its files.
Specs
Tasks
css/tokens/<theme>.css. Resolved server-side from the theme app'stoken-sets.json— the same fileCatalogControllerserves — because the renderer runs for ANONYMOUS visitors and the endpoint is deliberately not public (see 1.2).CatalogController::tokenSets()is#[NoAdminRequired]and deliberately not#[PublicPage]: its docblock states that exposing admin-uploaded custom sets to anonymous traffic "would be a new information-disclosure surface with no consumer need". The route was left alone; the public renderer reads the catalogue from disk, and the authenticated admin UI remains the endpoint's consumer.PortalThemeResolverresolvesportal.themeagainst the catalogue and returns null when the id is unknown. A file present on disk but absent from the catalogue — a generated variant, a leftover — is no longer adoptable.portal.themebecomes a chosen id rather than free text.LinkWithdrawn on measurement, and the withdrawal is the deliverable. Implemented, rendered and measured twice: the artefact as generated changed 0 of 1,152,000 pixels; aftercss/tokens/dark/<id>.cssnldesignwas fixed (see 2.5) it changed 53% and left 10 of 11 text nodes below 4.5:1. This site has no token-driven surface layer. Backed out, with the numbers recorded intemplates/site.phpand pinned by a test so re-adding the line is deliberate.CssInjectionServicedoes; a portal must not invent a second switch. Blocked on 2.6.FontServicefor the theme's declared fonts instead ofportaliq/css/nlds/nlds-fonts.css, which currently re-declares faces by hand because the vendored CSS carried root-relative urls.prefers-color-scheme: darkvisitor gets it. Blocked on 2.6.nldesign— three defects found by chasing the 0-pixel result, each of which produced output that reads as a successful run (fix(dark-mode): resolve var() aliases before deriving, and honour the generator version thematiq#353):var()aliases were never darkened (an alias declared on:rootresolves there, and the dark block scopes tobody, a descendant — so only 13 of 600--utrecht-*tokens survived); text was classified as surface outside the--nldesign-*naming convention; andGENERATOR_VERSIONwas stamped into every header and read by nothing, so an algorithm fix regenerated 0 of 41 sets.bodyfrom--utrecht-document-*is verified harmless (0 pixels changed in light mode) and insufficient alone: the inner bands stayed white. This is the real prerequisite for dark mode, and it is a change to the site's own CSS, not to the theme app.ContrastControllerfor the adopted theme and record the verdict against the portal.NlDesignThemeShareableConfigTypeso a theme shared through OpenRegister can be adopted by a portal.CustomTokenSetValidatorbefore it can be linked. Shared configuration is input from another instance and must not be able to inject CSS.nldesignthat portals are a consumer of the catalogue, the dark variants and the shareable config type — the docs currently describe the Nextcloud UI only.Synced from
openspec/changes/nldesign-theme-integrationby OpenSpec workflowApp:
portaliq