Release: merge development into beta - #571
Open
github-actions[bot] wants to merge 34 commits into
Open
Conversation
…260906124710 chore(sync): carry beta back into development
hydra-gates v1.15.0 -> v1.16.0 nc-vue - -> 2.37.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>
rubenvdlinde
requested review from
WilcoLouwerse,
bbrands02 and
rjzondervan
as code owners
September 7, 2026 14:45
…n the test bootstrap (#576) 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. With PHPUNIT_USE_NC_BOOTSTRAP=1 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-stubs.xml: hard memory_limit of 2G (the unit suite peaks at 123 MB without coverage) - tests/bootstrap.php: the PHPUNIT_USE_NC_BOOTSTRAP opt-in now also requires launchpad_nc_root_is_installed(), which reads config/config.php in a closure and demands installed => true; a bare source tree says so on STDERR and falls back to the OCP stubs; if base.php still throws, the run stops 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>
rubenvdlinde
requested review from
Rem-Dam,
SudoThijn,
remko48 and
rubenvdlinde
as code owners
September 8, 2026 08:20
… suite (#578) Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Supersedes #533, which fails Frontend Build with Module not found: Can't resolve 'pinia' in node_modules/@conduction/nextcloud-vue/dist/esm/composables That reads as a broken shared library and is nothing of the kind. webpack names the ISSUER of an unresolvable import, not the config line that made it unresolvable, and the config line is ours. pinia 2.1.7 declared `main: index.js` and `module: dist/pinia.mjs`, so aliasing the package DIRECTORY resolved. 4.0.3 declares NEITHER — only "exports": { ".": "./dist/pinia.js" } A directory alias bypasses `exports`, finds no main or index, and resolves to nothing. This file already documents the same failure for @nextcloud/vue and @nextcloud/dialogs a few lines below, and prescribes the fix: point at the concrete entry rather than the directory. pinia 4 has joined that category, so it gets the same treatment. The `$` stays, so any deep import keeps going through the exports map. Same shape as the `@nextcloud/axios` alias that broke on 2.6.0 across planninq and zaakafhandelapp: a config encoding an assumption about someone else's package layout, correct until the day it is not. Verified locally rather than left to CI: `npm install` clean on pinia 4.0.3, then `npm run build` — webpack compiled, exit 0, warnings only (a pre-existing runtimeChunk recommendation). Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
* fix(livetile): point the connector probe at the class integriq actually ships
The connector source mode has been a silent no-op. Three separate names in
LiveTileService named things that do not exist:
- the app id `openconnector`, retired in the fleet rename;
- the FQCN `OCA\\OpenConnector\\Service\\DashboardDataSourceService`, which
is a namespace segment short and a capital S out from the real class and
has never existed under EITHER name (checked with `git log -S` over
openconnector's full 3944-commit history);
- the method `resolveDashboardValue`, likewise never present.
Every one of those failures is guarded. `isEnabledForUser()` on a name the
instance never registered returns false, `ContainerInterface::has()` on an
absent class returns false, and `method_exists()` on a missing method returns
false. So the tile rendered an 'unavailable' state and nothing was logged.
Read off integriq `development`, the real surface is
`OCA\\Integriq\\Service\\Datasource\\DashboardDatasourceService::resolve($sourceId,
$valueExpr, $params, $ttl)`, returning {value, fetchedAt, stale}. The value
key is what fetchFromConnector already reads, so the shape needed no change.
Resolution now runs through a FleetAppId helper ported from dossiq. A hard
swap to the new literal would re-break every instance still on a pre-rename
release, which is the same bug pointing the other way, so both the id and the
namespace resolve against a candidate list, newest first.
The old test faked a `resolveDashboardValue()` that nothing implements, so it
passed against a contract no app on the other side offers. It now mirrors the
real signature, and two new tests pin the FQCN and the old-id fallback.
* fix(frontend): name live fleet apps in the cross-app fixtures and docs
Renames retired fleet ids where the target on the other side was read and
confirmed to exist:
- nldesign -> thematiq in the TileEditor icon-URL fixtures. thematiq ships
img/icons/Star.svg, so /apps/thematiq/img/icons/Star.svg resolves.
- procest -> dossiq in the graphqlClient and deep-link fixtures. dossiq
serves /apps/dossiq/{path} through the AppHost SPA catch-all, so the
deep-link builder's output is a real URL.
Both call sites take the app id as an argument, so these are sample values in
tests of generic helpers, not production lookups. No behaviour changes.
Two production lookups in spendAnalytics.js are deliberately NOT repointed,
because the target does not exist under the new name either, and a rename
would have read as a fix while leaving both dead:
- /apps/openconnector/api/sources/{source}/call. integriq publishes no such
route, `git log -S` finds it was never added under the old name, and the
app has no LLM or inference endpoint at all.
- SPEND_SOURCES.PROCEST, which builds /apps/<app>/graphql. No fleet app
publishes that route under either name; OpenRegister serves GraphQL at
/apps/openregister/api/graphql, so the endpoint is what has to change.
Each carries a comment recording what was read and what the real target is.
* chore(quality): allowlist twelve pre-existing spec-annotation offenders
`composer lint:spec-annotations` has been failing on `development`, which took
`composer check:strict` red for everyone. All twelve post-date the 2026-05-03
baseline and match shapes already allowlisted wholesale: mapper query methods,
entity serialisers, a migration schema hook and an IRepairStep label getter.
check:strict now exits 0.
* test(support): cover FleetAppId's fallback paths
The coverage guard on #582 was right: FleetAppId landed with most of its
statements unexercised. Only the paths LiveTileService happens to walk were
covered, which is the wrong half — the branches that decide whether a
cross-app binding survives a half-migrated instance were all untested.
Those branches matter more than average because every one of them fails
SILENTLY. isInstalled() on an unregistered name returns false rather than
raising, class_exists() on a moved namespace answers false, and get() throws
into a caller that is already catching. Nothing announces itself.
28 tests, covering both halves of the rename:
- id: new id preferred, OLD id resolved when only it is present, both
present resolves newest-first, neither returns null, an unmapped app
falls back to its own name, and one throwing candidate does not abort
the search for the next.
- namespace: candidates newest-first, the irregular OCA\OpenBuilt pinned
against anyone deriving it from the id, container fallback to the old
FQCN, and a throwing container reading as absent rather than fatal.
- appPath: uses the REGISTERED id, no doubled separator, null when absent.
Seven mutations were run and each reddened only its own assertion: reversing
the candidate order, dropping either per-candidate catch, dropping the old
namespace, skipping id resolution in isEnabledForUser, removing hasService's
catch, and dropping appPath's ltrim.
The fixture declares a class under OCA\OpenBuilt, a namespace no launchpad
code binds to, so it cannot collide with a real class or reach another test.
---------
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
…585) `fetchSpendNarrative()` calls `/apps/openconnector/api/sources/{source}/call` and its spec asserts the path. gate-115 reports both as cross-app lookups naming a retired app id. The comment beside the call already explained why it is not repointed; this adds the machine-readable half so the gate stops re-raising an answered question, and puts the same marker on the assertion that pins the path. Re-verified 2026-09-10 against integriq `development`. The route exists under NEITHER name. SourcesController publishes only test, logs, tripCircuitBreaker and resetCircuitBreaker, and `git log -S "sources/{source}/call"` and `git log -S "sources#call"` over integriq's full 3,960-commit history, which spans the whole openconnector era, both return nothing. The app has no inference endpoint at all. So REQ-SAW-006 waits on an integriq surface that has never been built. Repointing the segment would turn one 404 into another while hiding that gap, on a diff that reads as a fix. Recorded with `@stale-fleet-app-id exclude`, the shared exclusion convention gate-115 reads (.github#738). Comments only. No behaviour changes, and no test assertion changes: the spec still asserts the path the caller actually uses. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
… them (#587) * fix(auth): admin menu entries rendered for every account CnAppNav treats an absent permissions prop as permission to render everything, and launchpad passed none, so two entries declared admin only were visible to anyone. Seventeen of nineteen fleet apps carry a filter reading a value that is always empty, so their menu filtering is dead code today and safe only because they declare nothing. Work in progress: preserved after a rate limit ended the session that was writing it. The e2e proof was still running when it stopped. * test(e2e): key the admin-entry absence on the menu id, not the route The absence assertions read `data-cn-route`, which renders `item.route` verbatim. This fix changes `item.route`: both entries declared the URL path where CnAppNav wants a route name. So against a bundle built without the fix, `expect(routes).not.toContain('admin-templates-index')` passed, because the unfixed manifest never emits that string. The entries were on screen throughout, and the same run recorded them for the admin as "/admin/templates" and "/admin/settings". The entry id is untouched by the fix, so the absence now keys on `data-testid="cn-nav-entry-<id>"`. `data-cn-route` is kept for the one question that really is about the route: whether an admin's entry resolves to a working link. Also raises the budget for the three tests that log in cold. A first login for a freshly provisioned account took 59s on the shared box, so the 60s config timeout expired during login and all three reported a timeout rather than an assertion. A guard test that times out is red whether the guard works or not. Adds tests/e2e/support/bundleOverride.ts, which serves the compiled bundle from a local directory so the same spec can be run against a build with the fix and one without it. Inert unless LAUNCHPAD_BUNDLE_DIR is set. * test(e2e): assert where a blocked non-admin lands, not one exact URL The redirect tests pinned the landing URL to `${APP_BASE}/?$`. The guard sends the browser to '/', and LaunchPad resolves '/' onward to its active dashboard, so a WORKING redirect arrives at /apps/launchpad/dashboard and the pattern called it a failure. Both redirect tests were red against a bundle that had already redirected correctly. Asserted instead: the browser is still inside this app, and no longer on an admin path. That still fails when the guard is absent, where the route is served, AdminSettingsRedirect bounces to /settings/admin/launchpad, and the first assertion does not match. * test(e2e): check the URL before the shell, so the failure names itself Against an unguarded bundle the first thing to fail was the wait for .workspace-shell, so both redirect tests reported the shell as not visible. True, and not the point: the shell is missing because the browser left LaunchPad for /settings/admin/launchpad, which is to say the non-admin reached the admin surface. "Shell not visible" equally describes an app that failed to boot, so the run could not tell a security failure from a broken build. The wait is now a settle whose rejection is swallowed, the URL is asserted first, and the shell is asserted properly afterwards so that a page rendering nothing still fails. * test(e2e): establish the identity before the action, not after it whoami was read after navigating to the gated route. Against an unguarded bundle that lands on /settings/admin/launchpad, which Nextcloud refuses to a non-admin with an error page carrying no OC bundle, so OC.getCurrentUser() returned null and the test died on "expected e2e-perm-..., received null": red for the right underlying reason, reported as something else entirely. The session is now confirmed to be the throwaway non-admin on the app's own page, before the navigation under test. Also raises the app-boot waits in openApp from 30s to 60s. A cold boot on a loaded instance went past 30s and turned a run that had already passed on the same bundle red at .workspace-shell. * chore(lint): sort the imports the fix added * chore(format): run prettier over the files this branch touched --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
#589) The replacement assertion anchored the path with ^, which passes locally and fails in CI. Nextcloud there serves index.php routing, so a WORKING redirect arrives at /index.php/apps/launchpad/dashboard and the pattern called it a failure. That is the same mistake as the ${APP_BASE}/?$ pattern it replaced: an assertion that only passes for one incidental spelling of success. Now unanchored and built from APP_BASE. It stays false for the unguarded outcome, which lands on /settings/admin/launchpad under either routing style. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…#591) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.69.0 to 8.70.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.70.0/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" dependency-version: 8.70.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.18.0. - [Release notes](https://github.com/ConductionNL/.github/releases) - [Commits](ConductionNL/.github@v1.16.0...v1.18.0) --- updated-dependencies: - dependency-name: conduction/hydra-gates dependency-version: 1.18.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 [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) from 2.2.12 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>
Bumps [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) from 4.1.11 to 5.0.0. - [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.0/packages/ui) --- updated-dependencies: - dependency-name: "@vitest/ui" dependency-version: 5.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [eslint](https://github.com/eslint/eslint) from 10.9.1 to 10.10.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](eslint/eslint@v10.9.1...v10.10.0) --- updated-dependencies: - dependency-name: eslint dependency-version: 10.10.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 [dompurify](https://github.com/cure53/DOMPurify) from 3.4.14 to 3.4.15. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](cure53/DOMPurify@3.4.14...3.4.15) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.4.15 dependency-type: direct:production 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 [@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 [@conduction/nextcloud-vue](https://github.com/ConductionNL/nextcloud-vue) from 2.37.0 to 2.41.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.37.0...v2.41.1) --- updated-dependencies: - dependency-name: "@conduction/nextcloud-vue" dependency-version: 2.41.1 dependency-type: direct:production 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 [marked](https://github.com/markedjs/marked) from 18.0.11 to 18.0.12. - [Release notes](https://github.com/markedjs/marked/releases) - [Commits](markedjs/marked@v18.0.11...v18.0.12) --- updated-dependencies: - dependency-name: marked dependency-version: 18.0.12 dependency-type: direct:production 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>
Two problems, both mine, both from admin-merging a dependabot batch without
resolving it first. `npm ci` on `development` exits 1, so every frontend job
would fail.
1. nc-vue 2.41.1 pins `peerOptional marked@^12.0.0` and launchpad carries
marked 18, so the graph cannot resolve:
Found: marked@18.0.12
Could not resolve: peerOptional marked@^12.0.0 from @conduction/nextcloud-vue@2.41.1
nc-vue widened that range to `>=12 <19` in 2.43.0, so taking 2.44.1 fixes it
rather than pinning marked back. dossiq already runs 2.44.1 with marked 18.
2. `@vitest/ui` went to ^5.0.0 alone. It peer-requires EXACTLY `vitest 5.0.0`
while vitest and @vitest/coverage-v8 stay ^4.1.11. That is a held major and I
merged it by mistake in a loop that only skipped conflicting PRs; restored to
^4.1.11 so the family is one version again.
Verified locally, by exit code, on a CLEAN tree:
rm -rf node_modules && npm ci -> 0
npm run build -> 0
npm run test -> 0 (64 files, 708 tests)
resolved: nc-vue 2.44.1 marked 18.0.12 vitest 4.1.11 @vitest/ui 4.1.11
The lock is updated in place rather than regenerated: deleting it makes npm fail
to resolve the `"pinia": "$pinia"` override.
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…silently wrote nothing (#605) * feat(showcases): a case handler dashboard, and the settings PUT that silently wrote nothing Two things, both found by building the dashboard against a live instance. THE SHOWCASE. `case-handler` is the sixth bundled demo showcase and the first ROLE one. The five that came before are Dutch fictional organisations answering "what does an intranet built on this look like"; this one answers a different question, what one person's working day looks like on one page: their cases, their tasks, today's agenda and their unread mail. It is English because the widgets it places are the fleet's own (a dossiq case list, the Tasks app, a calendar, unread mail) and those carry English labels, so a Dutch shell around English content would read as a half-translation. Authored as a personal dashboard and re-shaped for shipping: a showcase must not carry its author, so it goes out as `group_shared` with a null userId, `view_only`, and no calendar selected. Calendar ids are per-instance, and pointing a stranger's widget at calendar "1" is worse than asking them to choose. REQ-DEMO-001 is widened to admit role showcases, and CORRECTED while I was in it. It described an `export.json` plus an `nl/` locale tree, and neither has ever been in any bundled ZIP: the archives are `manifest.json` + `dashboards/`, which is what ExportService writes and ImportService reads. Anyone implementing against that paragraph would have built something no bundled archive matches. THE SILENT NO-OP. GET /api/admin/settings answers `allowUserDashboards`; PUT accepted only `allowUserDash`. Five settings had a read name and a write name and they did not match. An unrecognised key binds to null, null means "not supplied", so a caller round-tripping GET into PUT wrote NOTHING and was answered `{"status": "ok"}`. Measured: setting `allowUserDashboards: true` reported success and left the flag off, which reads as "this setting will not stick" rather than as a rejected key. Both spellings are accepted now; the short one wins, because AdminSettings.vue sends it. Tests. Two guards over the REAL archives, because every other test in DemoShowcasesServiceTest writes its own fixture ZIP into a temp dir, so the files actually shipped in data/demo-showcases were never opened by any test: an id in BUNDLED_IDS with a missing or malformed archive passed the whole suite and failed only on a user's instance. Four more for the alias. All six mutation checked: deleting the archive reddens both showcase guards by name, and removing the alias lines reddens the read-side test with `updateSettings(null, null, ...)`, which is the bug exactly. phpmd's ExcessiveClassLength deserves its own line: AdminController was 1024 of an allowed 1050 and this change took it to 1058. The comment is shorter than I first wrote it for that reason, not for taste. * chore(info.xml): take the em-dashes out of the App Store description gate-96 (manifest-copy-style) flags both descriptions against voice.md section 8, which bans em-dashes outright. It is advisory because 20 of 21 fleet apps carry the same debt, and the gate's own note says to fix it anyway: this is the first prose a stranger reads about the app. Twenty-six of them, in both languages. The bullets were `**Label** — text`, which is a label followed by its explanation, so they take a colon. The two lead sentences joined two independent clauses, so those become two sentences. The six em-dashes left are in XML COMMENTS. The gate does not read them and the writing rules do not cover them: comments explain, they do not sell. Also `**Key Features**` to `**Key features**`, same section, sentence case in headings. * fix(showcases): one showcase without a preview no longer 500s the whole gallery CI's Newman lane caught this and no unit test could: GET /api/admin/demo-showcases answered 500 on this branch. describeShowcase() resolves each card's thumbnail through IURLGenerator::imagePath(), which THROWS for an image that does not exist, and it runs once per showcase inside the listing. I added case-handler without img/showcases/case-handler.png, so the listing died on the sixth card and took the other five down with it. The unit suite was structurally blind to it: its setUp() stands in for imagePath() with a callback that always returns a string, so the throw never happens under test. Two fixes, because they answer different questions: - case-handler.png is added, and it is a real thumbnail of the dashboard rather than a copy of the 1x1 placeholders the other five ship, since it was cheap to make one from the live instance. - describeShowcase() catches the RuntimeException, logs it, and returns the row with a null thumbnailUrl. A missing preview is a cosmetic gap in one card, not a reason to hide every other showcase. Two guards, one per fix, each mutation checked: deleting the PNG reddens testEveryBundledIdShipsAPreviewImage by name; narrowing the catch to LogicException reddens testAMissingPreviewDoesNotFailTheListing with the RuntimeException. phpcs, phpmd, psalm and phpstan exit 0; test:all OK, 1646 tests. * chore(deps): take nextcloud-vue 2.46.0 so the case-handler's New case form is the scoped two-column one The showcase's My cases widget declares content.formSize, formColumns, formIncludeFields and formFieldOverrides. CnObjectListWidget reads all four only from 2.46.0 (nextcloud-vue#1068). On the 2.44.1 development carries, the New case button opens the WHOLE case schema: about thirty fields in one column at default width, which is what the live instance showed while this dashboard was being built. Five lines of lock. build exit 0, vitest 64 files / 708 tests, lint exit 0, prettier --check exit 0. --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
* docs(store): investigate the store plane and propose dashboard sharing * feat(store): browse a dashboard registry and install a template * test(store): cover discovery, install and the wiring both defects hid in * style(store): satisfy phpcs — no inline ternaries or inline docblocks * fix(l10n): retitle the store page string and translate it for Dutch * fix(manifest): move the store note into the page note so the schema accepts it * feat(store): let an administrator connect a dashboard registry from the Sharing tab * test(store): cover the store page, the registry form and the admin gate in e2e * fix(lint): declare the registry spec's field stub as a function * test(store): let an unrouted search fail on its status, not on parsing the 404 page * docs(store): record the admin form and the e2e coverage in the change --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
…e e2e that caught it (#606) * feat(showcases): a case handler dashboard, and the settings PUT that silently wrote nothing Two things, both found by building the dashboard against a live instance. THE SHOWCASE. `case-handler` is the sixth bundled demo showcase and the first ROLE one. The five that came before are Dutch fictional organisations answering "what does an intranet built on this look like"; this one answers a different question, what one person's working day looks like on one page: their cases, their tasks, today's agenda and their unread mail. It is English because the widgets it places are the fleet's own (a dossiq case list, the Tasks app, a calendar, unread mail) and those carry English labels, so a Dutch shell around English content would read as a half-translation. Authored as a personal dashboard and re-shaped for shipping: a showcase must not carry its author, so it goes out as `group_shared` with a null userId, `view_only`, and no calendar selected. Calendar ids are per-instance, and pointing a stranger's widget at calendar "1" is worse than asking them to choose. REQ-DEMO-001 is widened to admit role showcases, and CORRECTED while I was in it. It described an `export.json` plus an `nl/` locale tree, and neither has ever been in any bundled ZIP: the archives are `manifest.json` + `dashboards/`, which is what ExportService writes and ImportService reads. Anyone implementing against that paragraph would have built something no bundled archive matches. THE SILENT NO-OP. GET /api/admin/settings answers `allowUserDashboards`; PUT accepted only `allowUserDash`. Five settings had a read name and a write name and they did not match. An unrecognised key binds to null, null means "not supplied", so a caller round-tripping GET into PUT wrote NOTHING and was answered `{"status": "ok"}`. Measured: setting `allowUserDashboards: true` reported success and left the flag off, which reads as "this setting will not stick" rather than as a rejected key. Both spellings are accepted now; the short one wins, because AdminSettings.vue sends it. Tests. Two guards over the REAL archives, because every other test in DemoShowcasesServiceTest writes its own fixture ZIP into a temp dir, so the files actually shipped in data/demo-showcases were never opened by any test: an id in BUNDLED_IDS with a missing or malformed archive passed the whole suite and failed only on a user's instance. Four more for the alias. All six mutation checked: deleting the archive reddens both showcase guards by name, and removing the alias lines reddens the read-side test with `updateSettings(null, null, ...)`, which is the bug exactly. phpmd's ExcessiveClassLength deserves its own line: AdminController was 1024 of an allowed 1050 and this change took it to 1058. The comment is shorter than I first wrote it for that reason, not for taste. * chore(info.xml): take the em-dashes out of the App Store description gate-96 (manifest-copy-style) flags both descriptions against voice.md section 8, which bans em-dashes outright. It is advisory because 20 of 21 fleet apps carry the same debt, and the gate's own note says to fix it anyway: this is the first prose a stranger reads about the app. Twenty-six of them, in both languages. The bullets were `**Label** — text`, which is a label followed by its explanation, so they take a colon. The two lead sentences joined two independent clauses, so those become two sentences. The six em-dashes left are in XML COMMENTS. The gate does not read them and the writing rules do not cover them: comments explain, they do not sell. Also `**Key Features**` to `**Key features**`, same section, sentence case in headings. * test(e2e): pin the admin-settings key round-trip and the case-handler showcase * test(e2e): make both specs survive a slow shell load and report every key * test(e2e): align the case-handler spec with #605 and cite its two new scenarios * fix(showcases): one showcase without a preview no longer 500s the whole gallery CI's Newman lane caught this and no unit test could: GET /api/admin/demo-showcases answered 500 on this branch. describeShowcase() resolves each card's thumbnail through IURLGenerator::imagePath(), which THROWS for an image that does not exist, and it runs once per showcase inside the listing. I added case-handler without img/showcases/case-handler.png, so the listing died on the sixth card and took the other five down with it. The unit suite was structurally blind to it: its setUp() stands in for imagePath() with a callback that always returns a string, so the throw never happens under test. Two fixes, because they answer different questions: - case-handler.png is added, and it is a real thumbnail of the dashboard rather than a copy of the 1x1 placeholders the other five ship, since it was cheap to make one from the live instance. - describeShowcase() catches the RuntimeException, logs it, and returns the row with a null thumbnailUrl. A missing preview is a cosmetic gap in one card, not a reason to hide every other showcase. Two guards, one per fix, each mutation checked: deleting the PNG reddens testEveryBundledIdShipsAPreviewImage by name; narrowing the catch to LogicException reddens testAMissingPreviewDoesNotFailTheListing with the RuntimeException. phpcs, phpmd, psalm and phpstan exit 0; test:all OK, 1646 tests. * test(e2e): install the case-handler showcase once so every leg reports its own verdict * style(e2e): prettier reflow of the case-handler spec * chore(deps): take nextcloud-vue 2.46.0 so the case-handler's New case form is the scoped two-column one The showcase's My cases widget declares content.formSize, formColumns, formIncludeFields and formFieldOverrides. CnObjectListWidget reads all four only from 2.46.0 (nextcloud-vue#1068). On the 2.44.1 development carries, the New case button opens the WHOLE case schema: about thirty fields in one column at default width, which is what the live instance showed while this dashboard was being built. Five lines of lock. build exit 0, vitest 64 files / 708 tests, lint exit 0, prettier --check exit 0. * test(e2e): fix the two test bugs the first CI run against the fix exposed The settings key tests leaked real values into each other once the fix made the writes land: allowMultipleDashboards=false put the admin at a quota of one, which renamed the Add-Dashboard control and failed the browser leg's precondition. Restore the baseline in afterEach and set the flags the browser leg depends on explicitly. The showcase lookup read /api/dashboards, which lists only the caller's own dashboards and so never contains a group_shared showcase. Read /api/dashboards/visible instead. Also hand the admin's active dashboard back after the render test, and correct the van-der-berg note: its mail widget lands, only calendar is skipped. * fix(showcases): install the widgets LaunchPad renders itself, configured The case-handler showcase installed as an empty dashboard and reported success. partitionWidgets() kept only tiles and ids in Nextcloud's own dashboard registry, and object-list, nc-widget and calendar are LaunchPad types, so all four were skipped. The e2e in #606 caught it in CI. LaunchPad's types now live in lib/widget-types.json. The installer keeps a widget whose type is on it, and the widgetRegistry completeness test reads the same file, so the two cannot drift. A bare Nextcloud widget id whose app is not installed is still skipped. A missing or malformed list throws rather than quietly skipping every LaunchPad widget again. Behind that sat a second defect: buildPlacement() never copied `content`, so a kept widget would have landed unconfigured (no register for the case list, no target for the nc-widget proxies). It is copied now. The new unit test runs the install on the real bundled archive; the old ones only opened the zip. * docs(openspec): say what the settings PUT and the showcase installer now do admin-settings REQ-ASET-002 described the defect as the contract: its note said the PUT accepts only the short names. It now accepts both spellings, the short one wins, and two scenarios pin the GET-to-PUT round trip and the tie-break; the round-trip e2e cites them. demo-data-showcases REQ-DEMO-005 is rewritten for the new rule (tiles, LaunchPad's own types from lib/widget-types.json, registered Nextcloud widgets; content kept). The whole-spec "pure backend, no UI surface" exclusion was false, since showcases render in the admin gallery, so it is gone: scenarios the case-handler e2e covers are cited, and every other one carries its own reason, naming the PHPUnit or Newman test that pins it or saying plainly that the behaviour is not implemented or not yet tested. --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Dashboard import never landed a dashboard on a real database: the importer left created_at and updated_at unset, both NOT NULL on the dashboards and placements tables, so every insert failed and was reported as 'skipped'. Store installs (#607) hand their payload to the same importer, so none of those ever landed either. The unit tests mocked the database layer and saw neither this nor the second defect: an import that did land dropped every widget's content. One placement builder is now shared by import and the showcase installer (they had drifted apart). It carries content, title, icon, style, grid position and every tile field, and leaves behind fields that point at the exporting instance. Import sets the timestamps. Spec corrections: REQ-ASET-001 lists the 12 keys GET actually returns; REQ-DEMO-008 states who may edit an installed showcase (admins and Editors; everyone else read-only, with 'Add dashboard' forking an editable copy). Tests: export/import round trip, a timestamp guard on the rows handed to the database, a StoreService test through the real importer, the twelve-key set, three showcase permission tests, and an e2e that exports a configured widget, imports it through the admin page's own upload, deletes the source and checks the copy renders its configuration. Mutation-checked, including in CI. CI: 168 passed, 0 failed, 3 preexisting skips.
…ng spec seed its own dashboard (#611) Three preexisting defects, found while covering the store plane. Deleting a dashboard left its user and group shares behind: the delete cascade (REQ-CSC-002) had no entry for oc_launchpad_dashboard_shares. Nothing leaked, because occ launchpad:cleanup removes the orphans, but the rows outlived the dashboard until a sweep. dashboard-sharing.spec.ts failed 4 of 4 when run alone on a fresh instance: it assumed the admin already had a personal dashboard. It now seeds its own through the repo's fixture and removes it afterwards, so it no longer depends on CI's file order. Four specs also read BASE_URL directly instead of going through support/baseUrl.ts, so a local run with only the documented PLAYWRIGHT_BASE_URL failed them with 'Invalid URL'; they now resolve it the documented way. l10n: #559 added 'Admin settings' to en.json and nl.json only, and to no .js file at all, which is what the browser reads. 35 of 36 locales were missing it for six days. 28 take Nextcloud's own translation of the same label, nl keeps LaunchPad's wording, and 7 locales with no Nextcloud translation carry the English source, which is what their users already saw. The parity check now runs on every push as a warning, not a gate. Verified: check:strict exit 0, lint and format exit 0, the spec alone on a fresh instance 4/4 (and 4/4 red with the seeding removed), CI 169 passed with 3 preexisting skips.
…ee import defects the tests found (#616) The export-import spec carried one blanket e2e exclusion over ~40 scenarios. Each of its 48 scenarios now has its own verdict: 18 cited by new e2e (import validation and collisions, site export, schema versioning, partial import), 1 by #612's round trip, and 29 reason-bearing exclusions (10 not implemented, checked against every commit and the original task list; 12 with no browser surface; 5 that cannot be arranged on a shared instance; 2 other). Every exclusion names the unit test that pins the behaviour, or says there is none. Defects found and fixed, each with a test: - a corrupt dashboard file was reported with a blank ID and a misleading message; it now names the file; - a dashboard skipped for a missing field was reported under a freshly generated ID instead of the archive's own; - the admin page replaced the server's refusal reason with 'Import failed. Please try again.'; it now shows the reason, and an archive from a newer schema version tells the admin to upgrade, as the spec requires. Mutation-checked 11 ways in an isolated instance (with PHP opcache revalidation off, after two mutations first read green because the container cached the unbroken code). CI: 181 passed, 0 failed, 3 preexisting skips; all 12 new e2e ran. check:strict exit 0 (1696 tests), lint and format exit 0, scoped gates exit 0 (54/54 applicable).
Adds openspec/changes/e2e-coverage-programme: 2,834 scenarios across 78 specs, 5% cited by a Playwright test, 46 specs behind a spec-level blanket @e2e exclude. Five tiers (safety, data integrity, core surfaces, widgets, admin and operations), one PR per spec, a definition of done, refusal-test rules, and exact counts per spec measured 2026-09-11. No requirements change. Tier 0 is in progress.
…wo silent write failures (#617) #612 shipped an importer that never set created_at or updated_at, so every insert failed and was reported as 'skipped'. The unit tests mocked the database and saw none of it. The lane that would have caught it did not run: CI's PHPUnit used stub mode, so #611's database test SKIPPED there. One line in phpunit.xml (PHPUNIT_USE_NC_BOOTSTRAP=1) makes CI boot the Nextcloud it already installs. Head run: 1705 tests, 0 skipped, on all six PHP 8.3/8.4 x NC 32/33/34 legs. A plain clone still falls back to stubs. Four write paths now have real-database tests: dashboard create, dashboard update, add widget, share a dashboard, plus the share delete cascade. Each was mutation-checked by leaving a required column unset: the database test reddens (SQLSTATE 23502) while the mocked tests stay green, 24 of 25, 14 of 14, 7 of 7, 21 of 21, 12 of 12 respectively. Checking all 33 insert sites against the 73 required columns found two more live instances of the same bug: - Confluence import never saved a dashboard. The placeholder widget carried no timestamps, so every page rolled back and the command still exited 0 saying 'Imported 0 dashboards, skipped 1'. The 26 Confluence tests only covered parsing. - A role permission or layout default saved without a name hit the database constraint. It is now refused as a missing field before the insert. CI on the merged head: 38 pass, 6 skipping, 0 failing; e2e 169 passed.
* feat(e2e): refuse the shared instance unless the run names it The box runs one Nextcloud on localhost:8080 that bind-mounts everybody's checkout and holds data colleagues are working on. This suite seeds and deletes objects, so a run aimed there edits someone else's environment. tests/e2e/shared-instance.ts refuses loopback port 80 or 8080 unless the run set LAUNCHPAD_E2E_ALLOW_SHARED_INSTANCE or E2E_ALLOW_SHARED_INSTANCE to that same origin. CI is exempt, where 8080 is the job's own throwaway instance. The guard sits on the resolved value in tests/e2e/support/baseUrl.ts, the one module all three playwright configs import, so there is a single place a target enters the suite. A rig on its own port is unaffected. * style(e2e): format the shared-instance guard the way the format check wants The format leg runs prettier --check over the tree and it read all four files this branch touches as dirty, eslint green or not. They are two separate checks. The guard and its test are re-copied from the corrected fleet template, which is prettier-clean at source; the two edited files were clean before this branch and are clean again. No file this branch does not touch was reformatted. Ten unit cases still pass, with and without CI set. * fix(e2e): move the guard's unit test out of the playwright testDir The E2E job on this branch failed with 'Vitest cannot be imported in a CommonJS module using require()', and the reason is worse than the message. Playwright's default testMatch is **/*.@(spec|test).?(c|m)[jt]s?(x), so tests/e2e/shared-instance.test.ts was COLLECTED BY PLAYWRIGHT, threw on its vitest import, and took the whole run down. Measured on this checkout with the file in place: 'Total: 0 tests in 0 files'. With it moved: 184 tests in 39 files. A tally of zero reads like an empty suite, not like one poisoned file. The test now lives in tests/vitest/, which is where the rest of the fleet puts it, and vitest's include points there instead of at tests/e2e/**/*.test.ts. Verified on the CI pins: 10 of 10 passing both with CI=true and without, the full unit suite 67 files and 727 tests all passing, playwright collecting 184 tests in 39 files, prettier and eslint clean on every file this commit touches.
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.