Release: merge development into beta - #642
Open
github-actions[bot] wants to merge 78 commits into
Open
github-actions[bot] wants to merge 78 commits into
github-actions[bot] wants to merge 78 commits into
Conversation
Bumps [@gouvfr/dsfr](https://github.com/GouvernementFR/dsfr) from 1.15.2 to 1.15.3. - [Release notes](https://github.com/GouvernementFR/dsfr/releases) - [Changelog](https://github.com/GouvernementFR/dsfr/blob/main/CHANGELOG.md) - [Commits](GouvernementFR/dsfr@v1.15.2...v1.15.3) --- updated-dependencies: - dependency-name: "@gouvfr/dsfr" dependency-version: 1.15.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
…260922154440 chore(sync): carry beta back into development
Bumps [twig/twig](https://github.com/twigphp/Twig) from 3.28.0 to 3.29.0. - [Release notes](https://github.com/twigphp/Twig/releases) - [Changelog](https://github.com/twigphp/Twig/blob/3.x/CHANGELOG) - [Commits](twigphp/Twig@v3.28.0...v3.29.0) --- updated-dependencies: - dependency-name: twig/twig dependency-version: 3.29.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [nextcloud/ocp](https://github.com/nextcloud-deps/ocp) from 34.0.4 to 35.0.0. - [Commits](nextcloud-deps/ocp@v34.0.4...v35.0.0) --- updated-dependencies: - dependency-name: nextcloud/ocp dependency-version: 35.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.1/packages/vitest) --- updated-dependencies: - dependency-name: vitest dependency-version: 5.0.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.1/packages/coverage-v8) --- updated-dependencies: - dependency-name: "@vitest/coverage-v8" dependency-version: 5.0.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [@conduction/nextcloud-vue](https://github.com/ConductionNL/nextcloud-vue) from 2.52.0 to 2.55.1. - [Release notes](https://github.com/ConductionNL/nextcloud-vue/releases) - [Changelog](https://github.com/ConductionNL/nextcloud-vue/blob/main/CHANGELOG.md) - [Commits](ConductionNL/nextcloud-vue@v2.52.0...v2.55.1) --- updated-dependencies: - dependency-name: "@conduction/nextcloud-vue" dependency-version: 2.55.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [jsdom](https://github.com/jsdom/jsdom) from 30.0.1 to 30.1.0. - [Release notes](https://github.com/jsdom/jsdom/releases) - [Commits](jsdom/jsdom@v30.0.1...v30.1.0) --- updated-dependencies: - dependency-name: jsdom dependency-version: 30.1.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
The token editor could only write Nextcloud's globals, and Nextcloud has one global per concern, not per component. `--color-primary-element` alone paints the primary button, the selected navigation entry, the sidebar's active tab, a focused input, the checked checkbox, the progress bar, the dialog's confirm button and the counter bubble. So the component playground — built precisely so an admin could style one component while looking at it — could not reach a single component: every chip moved seven other things with it. `scripts/mapping/component-tokens.json` makes the mapping data: per component the selectors it occupies, and per token the global it replaces, its label, its type and whether the brand primary used to drive it. 123 component tokens across 33 components, following the precedent `scripts/mapping/nlds-to-nextcloud.json` set, so the editable registry and the stylesheet that applies it read one source and cannot drift. `generate-component-scopes.mjs` emits two files from it. `css/component-scopes.css` re-scopes rather than repaints: inside each component's subtree it redeclares the Nextcloud variable that component consumes, pointing it at the component token with the captured `--thematiq-global-*` behind it, and Nextcloud's own stylesheets keep doing the painting. The alternative — an `!important` element rule per component, the house style in theme.css — was rejected: twenty new rule blocks written against Nextcloud internals, each able to drift on a release, to achieve what redirecting one variable achieves. `css/primary-lock.css` is the other half, emitted for the toggle that gives the primary its reach back. Both are byte-compared against the committed copies by `npm run test:component-scopes`, so prettier must leave them alone or it would fail that check on a file nobody edited.
`defaults.css` already declared 104 `--nldesign-component-*` tokens and
theme.css already painted buttons, links, headings and form fields from some of
them, but the two halves never fully met: several rules still read a brand token
directly, so a component token could be set and nothing would move.
Every such rule now reads the component token first with the brand token behind
it, which is what makes a per-component value visible without changing anything
for an instance that has set none. The split by component is decided here rather
than in the generated scope layer: buttons, inputs, selects, textareas and the
modal container get their own rule blocks instead of one shared selector list,
because a shared list cannot give the login button a colour the main button does
not get.
Headings gain three tokens the six per-level tokens now derive from —
`--nldesign-component-heading-{font-family,font-weight,color}` — so the
playground's single `Headings` chip moves h1 through h6 at once, while a token
set that wants one level to differ still overrides that level's own token and
wins, because its declaration replaces the derivation.
`TokenRegistry::getTokens()` reads the mapping and returns the brand globals plus the 123 component tokens, each carrying `group` — `brand`, or the component id — and `primary`, which marks a token the brand primary used to drive. The brand globals stay editable from the four-tab list: that list IS the brand-level control, and moving one is MEANT to move every component that has not opted out. `primary_drives_components` is the opt-out, off by default, which changes nothing visually — with no per-component value stored the component tokens already resolve to the brand primary. While it is on, CssInjectionService emits `primary-lock.css` last of all, because it and `custom-overrides.css` both write `--nldesign-component-*` at `:root` with `!important` and the later one wins; turning the setting off drops the layer and the stored values take effect again, since nothing is ever deleted. The scope layer is emitted after the design-system layers, whose `:root` declarations it captures, and before the admin's own overrides, which are allowed to move the brand values those captures resolve to. The setting travels in the config bundle so an OTAP promotion carries it, and it is validated as a boolean on import rather than coerced.
The token editor renders the registry, so the component layer arrives in it on its own; what it did not have was a way to read 123 more rows. Rows now group by component under their own headings, and the four brand tabs keep listing the globals exactly as before. A row the `primary_drives_components` setting has taken ownership of renders disabled and dimmed rather than hidden: the stored value is still there and comes back the moment the setting is switched off, and a row that vanished would read as a value that was thrown away. `cursor: not-allowed` sits on the row rather than the input, because a disabled input fires no pointer events and the cursor would never change over the control itself. The row carries the reason as a title, so the lock explains itself where it is met. The toggle itself is a checkbox beside the existing theming options, posting to its own route. Three new strings, l10n catalogues rebuilt.
All 34 chips named a Nextcloud global, so the control a chip rendered did something other than what its title said: moving `Primary button` moved seven other components with it, and moving the login button was impossible without moving the main button too. Each chip now names the component token for the component it draws. The stage marks the component it is drawing with `data-thematiq-component`, so the theme can reach a specimen the way it reaches the real thing. A specimen carries the component's real class names, which is enough for every component whose rules are class-scoped; it is not enough for `#header` and the three under `#body-login`, because a specimen cannot carry an id that belongs to the page it is drawn on. The login button is the visible case — its specimen is the same `.button-vue--primary` markup as the primary button's, so without the hook it was painted by the primary button's token. The header specimens read their component token ahead of the set's own header colour. Reading `--nldesign-color-header-background` alone was not enough once the header had a component token: a token set DECLARES that colour, so the fallback behind it never fired and the specimen ignored anything set on the Header bar chip. `playgroundInventory.spec.js` exempts the brand globals from its coverage rule and adds the inverse check — that no chip names a global — so the defect this fixes cannot come back silently.
A component token is worth nothing unless something paints from it, and existence was never the problem: the login button shipped with a token that stored a colour, rendered a control and moved nothing, because a thematiq rule painted the property with `!important` while reading the primary button's token instead. The guard accepts the two ways a token can actually reach its component — the scope layer redirects the Nextcloud variable and a Nextcloud rule paints from it, or a thematiq rule paints the property and reads a token that chains to the component's own, directly, through defaults.css, or through an `aliases` entry in the mapping — and fails the case above, where the rule wins and nothing says so.
Proposal, design and tasks for the layer, plus the `component-tokens` spec delta covering the component layer and the `primary_drives_components` toggle.
`table-layout: fixed` with declared percentages bounded the column, not the content: a nowrap timestamp or a token set name wider than its 15% drew over the cell beside it, so Timestamp overlapped User and From overlapped To on every row. The `overflow-x` that was supposed to catch this sat inside a `max-width: 480px` query, so the settings page never reached it and the table escaped its container instead. The table now sizes to its content with a 72em floor and the container scrolls at every width — the container, not the <table>, because `display: block` on a table drops its implicit table role in Chrome and Firefox and unassociates every `th[scope=col]` from its cells, on the panel that exists to be shown to an accessibility auditor. The panel is dressed to match: a bordered, rounded scroller, cell padding on both sides, a tinted header row, banding for rows wider than the viewport, tabular-nums timestamps, and From and To pinned to a common width so the two sides of a change line up.
Raises max-version to 35. The playground already draws the Nextcloud 35 header, so the declaration was the part that had not caught up.
…ources DarkPaletteService output for eight sets no longer matched what is committed: zwolle across 71 tokens, cunningham on its background and table header, and a badge pair plus rotterdam's negative-inverse colour elsewhere. The light sources are unchanged; these are the derived files catching up.
`Frontend Check (format)` runs `prettier --check` over the whole repo, and the component-token work left three files it would reformat: the heading-token block in defaults.css, a wrapped condition in admin.js and the new inventory assertions. No behaviour changes.
psalm read `TokenRegistry::$componentTokens` as a shape without `global` while getComponentTokens() assigned one with it — InvalidPropertyAssignmentValue at the assignment. The property and getComponentTokens() now declare it, because the component layer always sets it; getTokens() declares it optional, because it merges in the brand layer, which carries no global of its own.
Adding the toggle pushed Admin::getForm() to 109 lines against phpmd's threshold of 100, and named a local `$primaryDrivesComponents`, which is over its twenty-character limit. Every toggle on this panel is stored the same way — '1' or '0' under the app id — and getForm() read five of them as five identical five-line blocks. `isFlagOn()` gives that shape one name: the method drops to 89 lines, the default stays visible at the call site, which is the part that differs between them, and the local becomes `$drivesComponents`. The controller's parameter keeps its name and takes a documented `@SuppressWarnings(PHPMD.LongVariable)` instead: Nextcloud binds that parameter from the JSON body key the admin panel posts, so shortening it to satisfy a length rule would rename the API field.
The layer was injected as a step of its own between the design-system layers and the admin's overrides. That put it outside `designSystemLayers()`, which has two consequences the unit suite caught: stock Nextcloud (design system `none`, which returns before any layer is added) started loading a Thematiq stylesheet, and `getStylesheetManifest()` never listed the layer — so the client that applies a token set without a reload could neither add nor remove it. It is now the last entry of `designSystemLayers()`, which is the same position in the emitted order — after the layers whose `:root` declarations it captures, before the overrides that may move the values those captures resolve to — while `none` stays stock and the manifest carries it. The three order assertions gain `component-scopes` where they already listed every other layer.
Hydra gate-65 failed on the drift the bump opened: info.xml advertises NC 32-35 to the App Store while the matrix ran 32, 33 and 34, so the newest major was claimed and exercised by nothing. The gate offers deleting the pin so the shared workflow derives the range, but ClaimAccuracyTest requires the pin to be explicit — inheriting the default is how the floor and the matrix drifted apart in #241/#242 — so the leg is added instead. stable35 goes first because Playwright reads `[0]` and selector-liveness.spec.ts only expires its SINCE deferrals once the survey reaches MAX_SUPPORTED_NC, which it reads from info.xml. Leaving stable34 in front of a declared 35 would put the survey one major behind the claim. Two ISimpleFolder fakes then have to load on that leg: `getOrCreateFolder()` is @SInCE 35.0.0, and without it the suite fatals at class-load time rather than failing a test. FontServiceTest's fake is filesystem-backed, so it creates the directory; ThemingAuditServiceTest's is flat — the audit log lives in one folder — so it refuses the way its newFolder() already does. Verified against a real Nextcloud 35: 840 tests load and pass.
The comment added with the stable35 leg said the constant is read from info.xml. It is not: it is hand-maintained in tests/e2e/spec-coverage/selector-liveness.spec.ts and still reads 34. Raising it expires that file's SINCE deferrals, so it is a decision of its own rather than something the version bump carries along.
`ISimpleFolder::getOrCreateFolder()` is @SInCE 35.0.0. Both fakes in this suite implement that interface and neither declared the method, so on a Nextcloud 35 server PHP refuses the class itself: Class ...FontServiceFakeSimpleFolder contains 1 abstract method and must therefore be declared abstract or implement the remaining method That is a fatal at class-load time, not a test failure. The suite stops before a single test runs, which is why an NC 35 leg cannot even produce a coverage baseline for the ratchet to compare against — the run has no clover report to show, and the ratchet refuses to report a number it did not measure. FontServiceTest's fake is backed by a real temp directory, so get-or-create is what it says: mkdir when the path is absent, then hand back a folder for it. ThemingAuditServiceTest's fake is flat — the audit log lives in one folder and the service never nests — so it refuses the way its own newFolder() already does, rather than pretending to a shape it does not have. Nothing else in the suite is NC 35-incompatible: against a real Nextcloud 35, all 840 tests load and run.
test: let the ISimpleFolder fakes load on Nextcloud 35
CustomSniffs.Functions.NamedParameters requires named arguments on calls to this app's own code, and the helper the phpmd fix introduced was called positionally five times, so phpcs failed on a file the phpmd fix had just made pass. Its second parameter is renamed $default -> $fallback so the named form does not sit on a PHP keyword. The IConfig::getAppValue() call inside the helper stays positional: that is Nextcloud's code, which the sniff does not cover, and `app:` is not even its parameter name.
…at is checked (wip)
docs(parity): capability matrix for thematiq against five theming competitors
…ess a vendor source is cited 82 rated cells carried evidence starting with 'not checked'. 78 go to unknown with the existing reason; 4 keep their rating and cite the vendor source their text already quoted or linked. Thematiq's own column untouched.
…hecked-2026-09-26 fix(parity): rate thematiq's not-checked competitor cells unknown unless a vendor source is cited
…rces objects, twelve mined rows (wip)
…s object, eight mined rows (wip)
…the token editor (wip)
…from source (wip)
…ect, eight mined roadmap and release-note rows (wip)
…opendesk, tokens-studio and liferay-dxp (wip)
…t, seven mined rows, theme switch rated in three columns (wip)
docs(parity): thematiq competitor columns re-read from source and public docs, sources objects, 36 demand-signal rows
… first use) (#671) thematiq uses nextcloud-vue only as a build-time source for the NL icon packs, so no bundle here carries Dexie before or after. This keeps the lock in step with the fleet; the icon build output is byte-identical.
…isions for all 64 (#673) * docs(openspec): environment marker, restore an earlier version, scheduled theme switch (gov-environment-marker, app-rollback-history, app-scheduled-switch) * docs(openspec): theme gallery, simple brand form (cat-marketplace, aut-colour-only-ui) * docs(openspec): theme as code, occ set-theme, token reference, document house style, assistant approved mark (gov-config-as-code, aut-git-sync, app-branding-package, int-cli-set-theme, cat-living-token-docs, sur-generated-documents, sur-assistant-logo) * docs(parity): gap decisions for the OpenSpec pass, 12 rows specified, 18 decided no, owners filled * docs(openspec): delegated group house style, per-app brand (gov-lock-per-space, sur-per-app-brand)
…ows, decisions complete (#687) * docs(openspec): authoring-multi-brand-token-source (aut-multi-brand-themes) * docs(openspec): authoring-dtcg-export (aut-dtcg-colour-objects) * docs(openspec): authoring-token-value-types (aut-alpha-hex, aut-light-dark-values, aut-motion-tokens) * docs(openspec): authoring-token-lifecycle (aut-add-token, gov-token-deprecation) * docs(openspec): authoring-own-markup-preview (aut-component-preview) * docs(parity): specify the five authoring changes' 8 rows, decisions file complete (65), motion evidence corrected
…le path out of errors (#701) A save with an unknown token name or a value the writer strips answered 200 with written set to the input size, and the audit entry listed tokens that never reached the file. It now answers 400 naming those tokens and writes nothing. A write failure answers a generic message instead of the exception text, which carried the absolute file path. Fixes #694
…and keep alpha (#702) A DTCG colour given as components was scaled straight to 0-255, so an srgb-linear colour came out too dark (a linear 0.5 grey became #808080 instead of #bcbcbc), and its alpha was never read, so a translucent colour imported opaque. Linear components now go through the sRGB transfer function, and an alpha below 1 is kept as the fourth pair of an 8-digit hex, also on the hex fallback. Opaque colours stay 6-digit. Fixes #695
…lpha in dark mode (#703) ContrastService read only #rgb and #rrggbb and dropped the alpha of rgba(), so an 8-digit hex was 'not a colour' and faint text was measured as opaque. It now reads #rgba, #rrggbbaa and the rgba() alpha, and the contrast audit, the shipped-set audit, the compliance report and the dark palette's brand check measure a translucent background over the page background and a translucent foreground over that. DarkPaletteService derived every dark value as an opaque hex and skipped 8-digit hex tokens. The dark channels still come from the opaque colour, and a light alpha below 1 is kept as #rrggbbaa. The generator version is now 3 and every css/tokens/dark file is regenerated with it. Fixes #696
…tor sets (#704) The editor writes --animation-quick and --animation-slow, while theme.css timed its buttons, links and inputs with --nldesign-animation-quick, which the editor never sets. overrides.css only maps the nldesign name onto the Nextcloud name, so the admin's speed never reached those transitions and the page ran two speeds. theme.css now reads --animation-quick, which overrides.css still feeds from the set's --nldesign-animation-quick when no override is saved. Fixes #697
… dark user sees one colour (#705) Every override sat in one :root block with !important. Nextcloud declares a chosen dark theme's colours on body, which a :root value never reaches, so a user who picked Dark theme kept core's colour while a user on System default with a dark OS saw the override. custom-overrides.css now also carries the two dark scopes of the generated dark stylesheets for every brand-layer colour override, with the value DarkPaletteService derives (the light value when it is not a colour literal). Component tokens are left out: both dark users already agree on them, and a body copy would outrank the primary-lock layer. The override import and the config bundle import read the :root block alone, so the dark copies in an exported file do not overwrite the light values. Fixes #698
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.