Conversation
…24911 chore(sync): carry main back into beta
…260906124917 chore(sync): carry beta back into development
The staged lockfile still carried seven unresolved merge-conflict markers from the @conduction/nextcloud-vue 2.25.1 -> 2.36.3 merge. Regenerating it resolves those and lifts fast-uri 3.1.5 -> 3.1.7, clearing GHSA-5jgf-p345-68v8, GHSA-f65p-4m7j-42xc, GHSA-fph4-wmhf-6fwf and GHSA-jqff-g426-hqxp (host confusion and SSRF, high severity). The three remaining moderate dompurify advisories arrive through @toast-ui/editor <- @conduction/nextcloud-vue and have no in-range fix. That chain is dev-only, consumed solely by scripts/build-icons.js, and never reaches the bundle.
hydra-gates v1.10.0 -> v1.16.0 nc-vue 2.27.2 -> 2.39.0 Lock-only: both packages are already declared with caret ranges that permit these versions, so nothing about what this app ACCEPTS changes - only what it currently resolves to. Opened by the weekly fleet shared-dependency bump, because a lock nobody re-resolves is a pin nobody chose. Merging is gated by this repository's own suite, deliberately: taking hydra-gates v1.8.1 added patchObject() to a published interface, which is a load-time fatal for any concrete double that implements it without the method. CI is the only thing that can tell a safe bump from that. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…n the test bootstrap (#565) The CLI php.ini sets memory_limit=-1, so a runaway test had nothing to stop it: on 2026-09-08 one openregister test recursed inside the DI container and took 19 GB of RAM. The test bootstrap also loaded the workspace's lib/base.php from a source tree that was never installed, which declares OC and builds a half-built OC::$server before throwing, and that state cannot be undone. - phpunit.xml and phpunit-unit.xml: hard memory_limit of 2G (the loadable part of the unit suite peaks at 69 MB without coverage) - tests/bootstrap.php: only boot a root whose config/config.php declares installed => true, otherwise say so on STDERR and run in pure-unit mode; if base.php still throws, stop with exit 1 instead of continuing half-booted - composer.json: psalm gets --memory-limit=2G and each phpmd call runs under php -d memory_limit=2G Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
The fleet sniff NoServiceLocator flags every \OCP\Server::get() outside lib/AppInfo, lib/AppHost, lib/Migration, lib/Repair and lib/Resources, because outside a booted Nextcloud that call autowires services from scratch and can recurse through a constructor cycle until memory runs out. Thematiq has exactly one such site, and it cannot be injected. The server constructs NLDesignEMailTemplate itself, from Mailer::makeTemplate(), with a fixed argument list, so the class has no constructor of its own to take a container. The lookup stays where it is and now says why, so the sniff counts it as resolved rather than as an unexamined lookup. No behaviour changes. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
… suite (#569) Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Make "this token set looks like its brand" a mechanical statement. A new TokenSetVocabularyAuditService checks every shipped set against three rules: it must declare the 26 required --nldesign-* semantic tokens the design system reads, it must not declare --nldesign-* names no stylesheet consumes, and its --nldesign-color-primary must agree with the manifest's theming.primary_color. Sets whose design system reads no --nldesign-* vocabulary (none, summer-breeze) are reported as not auditable. Measured baseline: 48 shipped sets, 5 complete, 2 not auditable, 41 incomplete. The 41 are recorded in a shrink-only allow-list fixture; TokenSetVocabularyTest fails both on an unlisted incomplete set and on a listed set that has started passing, so the list can only shrink. No token set file changes in this commit. - scripts/audit-token-sets.mjs mirrors the three rules in dependency-free Node (npm run audit:token-sets, --verbose, --json, --check) with a drift guard against the PHP required-token list. - TokenSetService takes the audit service as a constructor dependency and appends its verdict to the existing warnings channel as kind 'incomplete'; the six tests constructing the service directly are updated. - The admin page gains an "Incomplete set" badge next to the design-system badge, a matching banner in the apply dialog, and the same badge in the custom-set list. Strings added to en and nl. - openspec/changes/token-set-vocabulary-audit records the proposal, design, tasks and the token-sets spec delta.
Three overrides in Thematiq's own CSS fought Nextcloud's layout, so every themed page differed from stock in geometry, not only in tokens. - element-overrides.css set `position: relative !important` on #header, which put Nextcloud's out-of-flow header back into normal flow. #content is `position: fixed` with `top: auto`, so its offset resolved against a static position that now started below the 50px flowed header, and its own `margin-top: var(--header-height)` stacked on top: every themed page sat about 56px too low with the page background showing as a band between header and container (contentTop 106.5px themed vs 50px stock). The rule is removed; `overflow: visible` stays so the ribbon may hang below. - element-overrides.css gave #app-navigation `margin-right: 30px !important` and rounded the navigation and content panels individually. #content is a flex container with no background that already clips both panels into one rounded rectangle, so the margin showed --color-background-plain as a blue strip between the panels and the per-panel radius cut notches at the seam. Margin and per-panel radius are removed; the container radius stays themed once through --body-container-radius in overrides.css. - admin.css: the apply and theming-sync dialogs scrolled sideways when a token table was wider than 700px, so the sticky action bar drifted away from the rows and cells appeared beside and below the buttons. The dialog now hides x-overflow, the tables scroll inside their own container, and the action bar bleeds into the dialog padding so its background covers the full strip beneath the buttons.
css/custom-css.css is admin-authored runtime data written on demand by CustomCssService::write(), never app source. CssInjectionService only emits the stylesheet when the freeform-CSS feature is enabled and the file has content, so its absence is the correct default state, and a committed copy would restyle every instance that enables the feature. Ignore it next to custom-overrides.css.
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.62.1 to 1.63.0. - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](microsoft/playwright@v1.62.1...v1.63.0) --- updated-dependencies: - dependency-name: "@playwright/test" dependency-version: 1.63.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [stylelint](https://github.com/stylelint/stylelint) from 17.14.1 to 17.15.0. - [Release notes](https://github.com/stylelint/stylelint/releases) - [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md) - [Commits](stylelint/stylelint@17.14.1...17.15.0) --- updated-dependencies: - dependency-name: stylelint dependency-version: 17.15.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
) Bumps [conduction/hydra-gates](https://github.com/ConductionNL/.github) from 1.16.0 to 1.16.1. - [Release notes](https://github.com/ConductionNL/.github/releases) - [Commits](ConductionNL/.github@v1.16.0...v1.16.1) --- updated-dependencies: - dependency-name: conduction/hydra-gates dependency-version: 1.16.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) from 2.2.10 to 2.2.13. - [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-version: 2.2.13 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#579) `@vitest/coverage-v8` and `@vitest/ui` both declare `peer vitest` as an exact version, not a caret, so all three can only ever move as a set. Dependabot opened them as #573, #575 and #577, and each fails to resolve while the other two sit at 4. `vite` is declared explicitly because vitest 5 resolves it from the project where 4 did not; 8.2.2 was already in the lock transitively, so nothing new is pulled in. npm run test:unit -> 9 files, 119 tests passed, exit 0 npm run test:coverage -> same, 48.93% statements, exit 0 Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
The account glyphs on the right of the header were painted with the body text colour and dimmed to 0.8 opacity, which on a saturated header left Rotterdam's composited glyph at 1.05:1. They now take --nldesign-color-header-text at full strength, in both the .header-end (NC 32) and .header-right (NC 30) layouts, and the invert/brightness filter that flattened the whole header-end subtree - avatar included - is gone. App-menu icons follow the set through --nldesign-header-icon-filter, since images cannot be coloured. The avatar and its user-status badge keep their own shape and colours: excluded from the brand-radius rule, from the forced white on the initials plate, and from the fill/stop-color overrides that rendered the online badge as a black disc. `img` is no longer in the "remove all rounded corners" list, so avatars stop being rounded squares everywhere. The opaque white plate behind the account glyphs came from a rule written for the dropdown panel that addressed the header ITEM instead. It now addresses .header-menu__wrapper > .header-menu__content, so the header colour runs edge to edge. Sets that ship no img/logos/<id>.svg had a 56px hole where a stock installation shows the Nextcloud logo, and CSS alone cannot recover it: an !important declaration whose var() chain ends unresolved still wins and computes to unset. CssInjectionService::injectLogoUrl() now emits the fallback chain where the webroot is known - an admin-uploaded logo as it is, otherwise core's logo.svg masked to the set's own header text colour. The settings sidebar is returned to Nextcloud's own selection styling, instead of a column of underlined brand-coloured links with a tinted, 4px-displaced active row. On Nextcloud 32 the La Suite search pill is withdrawn to an icon-only trigger: inline-size: auto grew the button but not the .header-menu around it, so the following controls were drawn on top of the label. The NC 34 pill is untouched, and the block records what has to be verified before the pill comes back. Rotterdam and Zwolle are regenerated from their published token packages and drop out of the vocabulary allowlist. The admin preview's app shell now mirrors Nextcloud's real geometry - inset rounded container, pill navigation entries with icons, a real sidebar panel - and reads the header tokens rather than painting the bar with the primary colour.
The apply and theming-sync dialogs were unusable for the thing they exist for. Every table column was nowrap, so the font stack alone was wider than the 700px dialog and pushed the New column behind a horizontal scrollbar; the colour swatches next to each hex value had lost the rules that gave them a box, so they rendered zero pixels wide; and the action bar used sticky positioning, which pins to the scrollport rather than the dialog, so rows kept drawing below the buttons. The dialogs that carry a table are now flex columns with the table as the only scrolling child. On the themed pages, the rule that makes the dashboard panel transparent was unscoped, so it matched every Vue app's main element including the Files panel. With #content transparent above it, the page background showed as a line down the navigation seam, a wedge in the rounded corner and a frame along two edges. It is now scoped with the app class #content already carries. The active app was marked twice: core's own ::before pill, painted for a transparent header and therefore white-on-white on a light one, plus a full-width bar these bundles invented. Core's pill now takes a colour it is legible against and the bar is gone. --body-container-radius was mapped to the brand's CONTROL radius, which squared off the whole app shell for a near-square brand; it is left to Nextcloud, whose top-corner rounding still comes from the themed --border-radius-large. The Cunningham set stops applying its flat 4px to the Nextcloud chrome and stops painting every table header, and its theming.background_color matches the background its own stylesheet paints. OpenWOO is a local test theme, not part of the stack, so it no longer appears in any source comment or changelog entry; the shipped sets that illustrate the same roles are named instead. The local presentation mock is gitignored: usable in a working tree, never committed.
…termediate, not working correctly yet)
Intermediate commit of the theme converter (MAKEOVER-PLAN.md stage 4,
openspec/changes/nlds-theme-converter). It is committed to keep the work
reviewable and to stop it living only in one working tree; the feature is
NOT finished and does not yet behave correctly end to end from the admin
panel. Do not release from this state.
What is in and verified:
- scripts/mapping/nlds-to-nextcloud.json: the ordered mapping table, the
never-applied policy, the reason codes, and a logo block. Both runtimes
load this one file; its SHA-256 goes into every converted file.
- TokenSetConverterService (PHP) and js/lib/tokenConverter.js (dual-mode
Node/browser) plus scripts/convert-nlds-theme.mjs. The PHP and JS output
is byte-identical, provenance hash included, over six inputs.
- The converter runs ahead of CustomTokenSetValidator in upload(), which
now also accepts a pasted `content` body and detects the input by content.
- A theme's inline data: logo is decoded out of its token and written as
img/logos/{set-id}.{ext}; --nldesign-logo-url and theming.logo point at
the file and the theme's other logo slots become var(--nldesign-logo-url),
instead of ~40 KB of base64 in a file served on the login page.
- ThemingService::applyImages() now persists the mime type
ImageManager::updateImage() returns. Without that app value core kept
serving its own logo while the sync reported success, which is why no
synced logo had ever appeared.
- CssParserService no longer truncates values at the `;` inside
url("data:image/svg+xml;base64,…"); that one unclosed quote used to swallow
the rest of the :root block.
- The admin dropdown offers only `nextcloud` plus admin-imported sets
(TokenSetService::SELECTABLE_SHIPPED_SETS) until the 39 incomplete
shipped sets are regenerated; discovery is untouched.
Not done, and why this is not a working feature yet:
- No parity tests (vitest + PHPUnit); parity was checked by hand only.
- No paste textarea and no report rendering in the admin panel.
- DTCG (input B) is refused in the JS runtime; the CLI does not regenerate
the dark variant on --write.
- The 39 incomplete sets are not regenerated; the allow-list is still 39.
- PHPUnit could not be run on the authoring machine (no vendor/).
The converter extracts a theme's inline logo for every uploaded or converted set (TokenSetConverterService::extractLogo() and its JS mirror, driven by the mapping table's logo block), so a script hard-coded to one package, one version, one token path and one target file only suggested that logos were an OpenWOO special case. Nothing called it.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Selecting a token set, uploading one, deleting one, flipping a variant toggle or starting a preview all ended in a toast asking the admin to reload the page, and the theming sync reloaded on a hard-coded timer. Every judgement about how a theme looks therefore cost a page load, which is the wrong price for the thing this app exists to do. The set-dependent part of the cascade is now one ordered list owned by CssInjectionService: the render emits it and the new manifest endpoint serves the same list to the admin page, so the two can never disagree about which files make up a set. js/lib/layerSwap.js inserts the new run, waits for every sheet to fire load, and only then removes the old one, so the chrome never flashes unstyled. Elements are found by pathname, because a link emitted through Util::addStyle() cannot carry an attribute of ours. The core theming sync was unreachable from the apply dialog's confirm and, once reachable, opened a second modal on every switch. It is now a checked section of the apply dialog applied on the same confirm, and that dialog stays open until the new stylesheets are in effect and the sync has finished, so its disappearance is the signal that the theme is really on the page rather than that the request was sent. Selecting the stock set resets core theming through ThemingDefaults::undo() instead of matching a manifest entry. Stock is the absence of a synced theme, not a set of values to copy: matching offered a stale colour and, having no logo of its own, would have kept the previous brand's wordmark. ThemingService now persists the mime type ImageManager::updateImage() returns and marks the background as a plain colour. Without the first, a sync reported success while every page kept serving Nextcloud's own logo. css/tokens/nextcloud.css carried pre-Nextcloud-28 values for both the primary colour and the border radii, so the set that exists to be stock was not, and the apply dialog pinned those values into custom-overrides.css where !important kept them through every reset. They are read from the running instance's own defaults.
Merging development brought vite 8 into package.json while this branch's lockfile still resolved vite 6, so `npm ci` refused before any tool ran: npm error `npm ci` can only install packages when your package.json and package-lock.json are in sync npm error Invalid: lock file's vite@6.4.3 does not satisfy vite@8.2.2 Every npm job in the quality workflow installs first, so one unresolvable lockfile failed nine checks at once — build, unit tests, all three frontend checks, both npm license and security legs, eslint and stylelint — none of which had anything to do with their own subject matter. legacy-peer-deps goes with it. It was added because `@gouvfr/dsfr-nexus` declared peers on two packages that were never published; that package is no longer in the tree, so the flag bought nothing and would only have hidden the next peer conflict. Verified on npm 11.19: resolution and a clean `npm ci` both exit 0 without it, and vitest, prettier and stylelint pass on the resulting tree.
`npm run format` is a blocking check and nine files failed it: the three stylesheets and three scripts this branch edited, and the three specs it added. Formatting only — `prettier --write` output, no logic touched, with the unit suite still at 140 passing afterwards.
phpcs reported four blocking errors, all in new code. Two `@return` array shapes ran to 347 and 155 characters against a 150 limit; they are now multi-line shapes, which phpstan and psalm parse identically. Three inline ternaries — one building the converter's logo asset, two normalising an empty upload field to null — are now statements. `emitInlineStyle()` gained an `$id` parameter without a `@param` line for it. psalm's two errors are `BackgroundService::DEFAULT_COLOR` and `DEFAULT_BACKGROUND_COLOR`. OCA\Theming is a first-party Nextcloud app, present at runtime but not a composer dependency, so it is absent during analysis — the same reason this file already carries a suppression for ThemingDefaults, and the method now says so.
Three ignores went stale and phpstan failed on the drift rather than on new findings: the pattern for `ThemingDefaults::set()` was recorded as occurring once and now occurs three times, and the reset path added `undo()` plus two `BackgroundService` constants — all four the same "OCA\Theming is not a composer dependency" shape the baseline already tracked. Regenerated with `phpstan --generate-baseline`, the mechanism phpstan.neon names for per-app debt: 24 entries, no errors outside it. Two of those entries are NOT of that shape and are real debt in the converter — `runRules()` declares `&$manifest` nullable while never assigning null, and its `&$report` out-type carries `reason: mixed` where the signature promises `string|null`. Both are recorded, not fixed; `@param-out` tags are the documented remedy when that code is finished.
…atures Seven PHPUnit errors, all the same defect: a class gained a constructor argument and its test was not moved with it, so the suite aborted before asserting anything. CustomTokenSetController dropped DesignTokensMapper and took TokenSetConverterService at the end; the audit suite still passed the mapper in sixth position, where IL10N now sits, and PHP rejected the call on type. Admin took IRequest as a ninth argument and the initial-state suite still passed eight.
… 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
…ts own
There was one overrides file, loaded whatever the set. A value an admin pinned
while on some other theme stayed on the page after switching back to
"Nextcloud", so the stock set was not stock, and every theme saved off it wore
the other themes' pinned values too.
A set on the none design system — the stock nextcloud set, and every theme saved
off it — now keeps its edits in css/custom-overrides-{set}.css, and only that
file is loaded while it is the set on the page. The shared custom-overrides.css
stays for every set that wears a design system. The overrides endpoints take an
optional tokenSet so the editor reads and writes the file of the set it is
editing, which need not be the instance's active one.
Resetting to stock is now one request: it empties the stock file and the
previous set's, selects the stock set, and undoes what the sync pushed into
Nextcloud theming, so stock is really stock again. The per-set files are
runtime state and are ignored like custom-overrides.css.
Nextcloud 32 mixes its note-card fills from bare triplets (rgba(var(--color-success-rgb), 0.1)), so an -rgb token has to hold "r, g, b", not a colour. normaliseColorForPicker() now turns a triplet into #RRGGBB so the row gets a swatch, and hexToRgbTriplet() writes a picked colour back as the triplet, so nobody has to work one out by hand. The registry accepts `rgb` as a token type alongside color and text.
It is the base every other theme sits on and the one a theme saved from the editor starts from, which "(default)" did not say.
Many component tokens redirected a Nextcloud variable their component never
reads, so the row saved, previewed and moved nothing. Each mapping entry now
names the variable the component actually consumes — the secondary button's
--color-primary-element-light pair, the tertiary's --color-main-text and its
pressed --color-primary-element-light, the note-card glyph's --color-*-text,
the invalid field's --color-border-error, NcSelect's own --vs-* variables — or,
where Nextcloud reads no variable at all, paints the property itself.
A paint is the generator's second mechanism: the rule names the property and
paints it from the token, falling back to what Nextcloud paints there, so an
untouched instance renders as stock. It covers the header background, the
table header fill and weight, the note-card and toast text, the login card
edge (a template, "1px solid {token}", because the guest box draws no border),
the avatar's online dot, a focused input's border, the input and textarea
placeholders, the unchecked checkbox and the tertiary hover. The dialog aliases
its confirm button the way the login button aliases its own, and a heading
inside the empty state or a settings section keeps its component's colour
instead of the heading reset.
The capture of the globals also runs on the token editor's preview, where an
unsaved edit is declared, so the specimens show the edit and not the saved
value. The error and success buttons get the same background, hover and label
tokens; the select and textarea gain the tokens they lacked; the note cards'
error and success border tokens, which nothing reads, are removed. The error
button's label token is read before error-contrast.css falls back to white, and
nldesign's blanket text rule no longer paints over the secondary button's label.
Nextcloud's Theming panel holds the primary and background colours, whether
the background image was removed, and the uploaded background, logo,
navigation-bar logo and favicon. None of that lives in a token set, so a theme
saved from the editor came back without it: applying the theme later
re-coloured the components and left whatever branding was on before.
Saving overrides, or saving the editor's changes as a new theme, now captures
it: the colours, which of the three background states was on (an image of its
own, the image removed, or Nextcloud's default), and a copy of every uploaded
image under img/logos/ and img/backgrounds/ as {set}-captured-{slot}. Images are
copied rather than referenced because core takes an image only as a file and
the slot it came from is overwritten by the next theme that brings one. The
block is kept per set in one app value, shipped and custom sets alike, and laid
over the set's own theming; the stock set is never captured, because its
branding is Nextcloud's own settings.
The theming sync learns the navigation-bar logo, the favicon and the background
state, so applying the theme restores all of it, and a colour applied on its own
no longer drops a captured background image. Resetting to stock undoes the two
new image slots as well. Deleting a custom set forgets its branding and its
copied images, which are runtime state and ignored.
… is set Saving no longer loses what was saved. The overrides endpoint replaces the whole file, but the editor sent only the tokens that differed from their resolved value, and after a reload a saved value reads back as resolved, so every save deleted the earlier ones and those components fell back to the primary colour. The editor now sends the saved values plus the edits; after a save the unsaved-marker dots clear, and the reset button returns to the saved value rather than the theme's own. Both saves also ask the server to keep Nextcloud's branding with the theme, and the apply dialog restores the navigation-bar logo, favicon and background state along with the rest. Nextcloud's base tokens (--color-main-text and the other globals) are locked until the admin ticks "Also edit Nextcloud's base tokens" and confirms a warning that they reach far beyond one component; reset buttons on locked rows are disabled too. The "ask before saving" controls now read the same way round as the dialog's "do not ask again": ticked means stop asking. The playground's specimens answer the pointer: hover rules exist next to every frozen .is-hover state, the links are real links, the login card's button is scoped as the login button, and the text input and textarea show their placeholders, focus and invalid states. A component's shared tokens (the note cards' and toasts' corner) lead its panel, with a rule between the variants. The full view of each tab shows that tab: the app mock's buttons, hover included, are painted from their own tokens, the dialog carries every button, the note cards and a badge on Buttons & Status and the text styles on Typography, the content area gets breadcrumbs, a table, form controls and an avatar status dot, and the login preview uses the login card, field, button, logo and slogan tokens. The e2e specs unlock the base tokens before editing --color-primary and clear a field, not reset it, to drop an override.
The coverage ratchet on the changed PHP files dropped by 1.39%: the per-set overrides file, the branding capture on both saves, the captured theming overlay and the new background and image paths of the theming sync had no tests. Each now has one.
… below the phpmd thresholds
capture(), all() and forget() carry the @SPEC reference to the theming metadata requirement they implement, as the rest of the service layer does.
The app mock's table now has text headers, so each th says which cells it heads with scope="col".
… mini table headers and capitalise an inline comment
feat(editor): make every component token reach Nextcloud and keep what a theme saves
Release: merge development into beta
Contributor
Quality Report — ConductionNL/thematiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 2/2 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-09-29 08:13 UTC
Download the full PDF report from the workflow artifacts.
Contributor
Quality Report — ConductionNL/thematiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 107/107 | |||
| npm | ✅ | ✅ 2/2 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-29 08:18 UTC
Download the full PDF report from the workflow artifacts.
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.
No description provided.