Release: merge development into beta - #519
Open
github-actions[bot] wants to merge 31 commits into
Open
Conversation
The 1.0.24-beta.20260831132856 release bumped the version on beta. Without this, development stays behind beta and the next development -> beta promotion conflicts on the version file. Version files resolve to development's side, which is the higher line, so this never moves a version backwards.
…0.24-beta.20260831132856 chore(release): sync beta back into development
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
August 31, 2026 14:04
Contributor
Author
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 526/526 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 14:41 UTC
Download the full PDF report from the workflow artifacts.
Dependabot cannot update these packages. Its npm updater fails with: npm error code EOVERRIDE npm error Override for <pkg> conflicts with direct dependency The package is listed BOTH as a direct dependency and as a literal overrides entry pinned to the same range. Dependabot bumps the direct dependency and does not move the override, npm rejects the mismatched pair, and the update is abandoned -- so these packages can never be updated, security advisories included. npm's self-reference is the idiom for exactly this: $<name> tells the override to follow whatever the direct dependency resolves to, instead of a range that has to be kept in lockstep by hand. This repo already uses it (see the vue entry under @vue/test-utils). Transitive consumers still get pinned to one version -- the direct dependency's -- which is what the override was there to do. What changes is that the pin tracks the dependency instead of duplicating it. Verified in a clean container: bumping the direct dependency with the literal override reproduces the EOVERRIDE above; with the self-reference the same bump resolves. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Contributor
Author
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 526/526 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 16:37 UTC
Download the full PDF report from the workflow artifacts.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Contributor
Author
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 526/526 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 20:43 UTC
Download the full PDF report from the workflow artifacts.
`npm run lint` was `eslint src`, so two whole trees were never linted. Across
the fleet that hid roughly 3,900 errors, none of which any CI leg had shown.
Most of it was the config, not the code, and the same two defects were in
every app.
**scripts/ had no config block at all.** These are CommonJS Node CLI checkers,
and flat config defaults every `.js` to ESM with browser-ish globals, so eslint
read the CommonJS wrapper itself as undefined identifiers: `require`,
`process`, `__dirname`, `__filename`, `module`. A `scripts/**` block now
declares the environment. Declaring beats suppressing here: `no-undef` is the
rule that catches a genuinely misspelled identifier, and dozens of fake
findings would bury a real one. A second block covers `scripts/**/*.mjs`, which
is ESM and needs Node's globals without the CommonJS wrapper.
**The tests block applied a non-TypeScript-aware rule to TypeScript.** It named
`tests/**/*.ts` while setting the CORE `no-unused-vars`, which v9 deliberately
turns off for `.ts` in favour of the `@typescript-eslint` version. The core
rule reads the parameter names inside a function TYPE as bindings, so
t?: (app: string, key: string) => string
reports `app` and `key` as unused variables, and every unused `catch (e)` in a
`.ts` spec reports twice. The block is split now: `.js`/`.mjs` on the core
rule, `.ts`/`.tsx` on the TypeScript one, same patterns on both.
Also: stale `eslint-disable` comments naming plugins eslint 10 no longer
registers, which are themselves errors ("Definition for rule ... was not
found"), and a rule that must not parse shell scripts.
The genuinely real findings were the useful part: dead locals, unused imports,
dead helper functions, unused `catch` bindings, extensionless relative imports,
and a handful of `== null` comparisons spelled out so they still match null AND
undefined.
Verified per app: `npm run lint` 0 errors over src + tests + scripts,
`prettier --check` clean, and the unit suite still green.
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Contributor
Author
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 526/526 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 10:53 UTC
Download the full PDF report from the workflow artifacts.
… inside (#526) its own card.⚠️ THE CAUSE IS AN INSET, NOT A COMPONENT. A card widget renders `flush` and then has its padding put BACK by the card-fit rule (`padding: 8px 14px`), so the KPI sits inside the wrapper while the WRAPPER draws the border, radius and background the user reads as "the card". The library's `--clickable:hover` rule then drew a 2px border and a drop shadow on the KPI itself, 8-14px in from the edge being hovered. Measured live on dossiq, while genuinely hovered: the tile carried a 2px rgb(0,103,158) border and its own shadow, inset 9px from the wrapper's top and 15px from its left. A non-card-fit tile sits at 1px, so its border lands on the wrapper's own edge and reads as one card. That inset is the whole difference, which is why it looked app-specific and was not. nextcloud-vue#932 moves the affordance to the wrapper. No layout change: nothing moves, one card outlines. Verified in the browser with a real pointer hover: before inner 2px rgb(0,103,158) + shadow wrapper grey, no shadow after inner transparent, no shadow wrapper rgb(0,103,158) + shadow Every app with clickable stat, gauge or delta tiles had this. This bump is one of the fleet sweep that clears it. Dependency change only: package.json and the nextcloud-vue entry in package-lock.json. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Contributor
Author
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 526/526 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-02 19:06 UTC
Download the full PDF report from the workflow artifacts.
Schema property descriptions render as help text under form fields, so whatever is written there is product copy. Two things in it were not meant for a reader. Requirement ids: 8 strings carried a trailing "(REQ-DASH-011)" or similar. That is our traceability, and it means nothing to the person filling in the field. Em-dashes: 11 strings used one. Voice rule 8 bans them outright, so each is rewritten with a comma, a colon or a full stop rather than swapped for a different dash. One description also lost a paragraph explaining that a union type is rejected by the schema importer, and why that once prevented the schema being created. True, useful, and addressed to whoever maintains the register rather than to the person filling in the form. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…text docs(schemas): keep spec ids and em-dashes out of text a user reads
* feat(nav): give launchpad the Documentation and roadmap chrome ADR-114 Decision 1: seven items belong at the bottom-left of every Conduction app. CnAppNav draws Personal settings and Admin settings itself; the other five are the app's own. launchpad shipped one of those five. Adds Documentation (footer, order 90, the app's own docs site) and Features & roadmap (footer, order 100) with its type:"roadmap" page, which ADR-018 has required of every app since it was written. Both icons had to be registered in src/icons.js. An unregistered name renders NO glyph, not a fallback, so shipping the entries alone would have put two blank rows in the footer. gate-60 caught it. * fix(l10n): carry the roadmap label in the English catalogue too The nl catalogue had it and en did not, so the two disagreed about a string this app now ships. * fix(l10n): translate the new chrome labels into every required locale tests/l10n/check-l10n-parity.js requires all 36 European locales to carry every English source key, in BOTH the .json catalogues and the .js ones. A new user-facing label is therefore 36 translations, not one. Measured against origin/development before and after: 0 missing keys on the base, so these are the strings this branch introduced and nothing else. launchpad has no scripts/build-l10n-js.js, so the .js catalogues are maintained by hand and each got the key in the file's own style. --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Contributor
Author
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 526/526 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-09-03 10:24 UTC
Download the full PDF report from the workflow artifacts.
Contributor
Author
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 526/526 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-03 10:56 UTC
Download the full PDF report from the workflow artifacts.
Every push to development fired Code Quality TWICE on the same commit: once for the push event, once for the pull_request event of the permanently-open "Release: merge development into beta", whose head_ref IS development. The concurrency block is not the bug and must not be changed: it suffixes the group by event name deliberately so the push run is NOT cancelled by the PR run, because the push run is the only carrier of Coverage Baseline Check, SBOM and Features Extract. The consequence is that both lanes run to completion, so the duplicate has to be dropped at the job. The PR run is the one to drop: its head sha IS development's, which the push run already decided, and it runs strictly fewer jobs. Not done by removing `beta` from pull_request.branches, though that would also stop it: the release/v* and sync/main-to-beta PRs target beta too and genuinely need their run. Only the head_ref:development PR is redundant. A development -> main promotion PR is skipped by the same clause, for the same reason and just as correctly. Proven on openregister#3370 before rolling out: run 33757014196 (pull_request, sha 4f3ef5e9) completed as `skipped` with ZERO jobs in 72s, while the push run for the same sha ran the full suite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ADR-114 puts Documentation, Reports and Features & roadmap in the footer in that order. LaunchPad had two of the three, with room at 95. One card, deliberately. LaunchPad's register holds a single schema — dashboard — so a second report would either repeat this one or invent a reading the data cannot support. The `widgets` array holds widget descriptors rather than references, so grouping by it would bucket objects and render noise; it is left out rather than shipped as a chart that looks like an answer. The e2e asserts the count, so a schema added later without a report is something a test notices. The report is a declarative type:dashboard page over the app's own register. No bespoke Vue component and no per-app controller. Every filter is scalar equality, the only kind OpenRegister's aggregation endpoint can evaluate. Ten strings across all 36 locales. Parity passes on development and still passes here.
feat(chrome): give launchpad a Reports page with one honest report
Three defects, all found by actually RUNNING the specs rather than
collecting them. Until now they had only ever been listed, which proves a
spec parses and nothing else.
1. The cn-nav-* testids are on the <li> WRAPPER. The clickable element and
the href both live on the <a class="app-navigation-entry-link"> inside
it, so clicking the li resolved the locator and then never became
actionable — a 30s timeout that reads like the navigation is broken —
and reading href off the li gave null.
2. On a FRESH instance the setup wizard modal opens over the app and
intercepts pointer events. Every nav click failed while every URL
navigation passed, which is exactly the shape that hides this: only the
click-through tests break, and only on a clean install.
3. A bare getByText('Open') matched the SVG <title>Opens in a new tab</title>
on an external-link icon — attached, hidden, unrelated. Single-word
probes are now scoped to the page body.
Verified against a throwaway Nextcloud (not the shared :8080, and not the
neighbouring sessions' containers): planninq 6/6 and keepiq 5/5 green.
fix(e2e): make the chrome specs pass in a real browser
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) from 2.2.9 to 2.2.12. - [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-version: 2.2.12 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 [conduction/hydra-gates](https://github.com/ConductionNL/.github) from 1.10.0 to 1.11.0. - [Commits](ConductionNL/.github@v1.10.0...v1.11.0) --- updated-dependencies: - dependency-name: conduction/hydra-gates dependency-version: 1.11.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>
…#532) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.68.0 to 8.69.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.69.0/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" dependency-version: 8.69.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>
ADR-114 puts Documentation, Store, Reports and Features & roadmap in the footer in that order. This app had three of the four; gate-107 goes from 4 of 5 to 5 of 5. The page writes no backend. Openregister hosts the store plane, so adopting it is a type:"store" page plus a top-level `store` block and nothing else. With no registry configured the page renders the app's own items and makes no network call. There is deliberately no `installable` allowlist, following decidiq's precedent: a configuration set exists to introduce schemas the instance does not have yet, so listing schemas this app already owns would refuse exactly the sets worth installing. An EMPTY `installable` would mean install NOTHING rather than install anything, so omitting the key and declaring an empty list are different things. StoreOutline is registered in src/icons.js. An unregistered name renders no glyph at all, with no fallback and no console error. nextcloud-vue moves ^2.31.1 -> ^2.36.1, the version that ships CnStorePage; without it the store page renders nothing. The published tarball was unpacked to confirm the component is in it rather than trusting the version number. The chrome spec's footer test goes from three items to four, and a new test opens the Store surface and asserts it renders on a plain instance with no registry configured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(chrome): give launchpad a Store
…are Run button (#543) The demo-data step was a Run button under a paragraph. The paragraph said the data was safe to load and safe to delete. Neither said what was about to land in the operator's register, and there was no way to say no. ## Declining was unsayable, and that reopened the wizard for ever This app implements a `skip-demo-data` action. No manifest step could reach it: the only step was the run-action that INSTALLS. So an operator who did not want example data had no way to record that, `demo-data` stayed `done: false`, and CnAppRoot reopens the wizard while any optional step is outstanding. On every fresh browser profile, over every page, until they imported data they did not want. Measured across the fleet on 2026-09-05: fifteen apps implement that action and not one declares a step that can post it. ## What the step asks now Two cards, read from the server: "None, I will set this up myself" and the dataset this app ships, with its object count. Picking one is an answer, and `none` closes both steps without importing anything. The list comes from `GET /api/setup/status` as `datasets` — the step declares `optionsSource` and carries no options of its own, so nothing in the manifest can disagree with what will actually be imported. The count is read from the descriptor file, so the card promises the number that lands. The card's description carries NO number, deliberately: the wizard translates a description by literal lookup, so an interpolated count would leave a Dutch operator reading English. The count travels as `objectCount` and the card renders it as a stat with a label the library translates. ## Compatibility `install-demo-data` still works and still means "the dataset this app ships", so a runbook or script that posts it keeps working. `skip-demo-data` now writes both keys rather than only the decision flag. ## Verification 21 PHPUnit tests pass, 9 of them new. PHPCS clean, ESLint clean, prettier clean. All 75 applicable hydra gates green. Requires @conduction/nextcloud-vue with CnChoiceCards and hydra-gates >= v1.15.0 for manifest schema 2.33.0. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…545) The demo-data step is a choice followed by a load step now, and the CI seed settles the optional steps by posting `skip-demo-data` — which records `demo_dataset = none`. The load action then correctly imports nothing and answers "No example data was loaded", so `installing the demo data reports HOW MUCH landed` asserted against a decision it never made. It went red on every app whose seed reaches that line. It picks the shipped dataset first now, and it reads the id from `/api/setup/status` rather than writing a literal: the choice step reads its options from exactly that list, so a hardcoded id can pass while the list an operator sees is empty. The `api()` helper gained a body, which it needed to post the choice at all. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
LaunchPad does not render CnAppNav, and that is deliberate: it does not root on CnAppRoot/NcContent, and App.vue says so where it writes its own `.workspace-shell` — org navigation rail, slide-in sidebar, branded DashboardFooter, its own skip link. Five tests asserted `[data-testid="cn-nav"]` and `.cn-app-nav__footer-list` anyway, from "feat(chrome): give launchpad a Store" (2026-09-04) onward. The E2E leg has been red on every push since: a beforeEach waiting 30 s for a nav that cannot appear, reported as five broken features. The manifest got the Store entry; the shell never got the nav. The chrome IS declared — Documentation, Store, Reports and Features & roadmap, in the manifest's footer section — and its destinations do work. So the tests check that instead: the four entries in order, each with an icon, Documentation as an external href, and each of the other three opening the page this app hosts. Read from the manifest rather than restated, so a renamed entry fails rather than going stale. Reports, the dashboards report, Store and Flows are reached by route, which is how they are reachable here. A new first test asserts the absence of CnAppNav. It is the premise the others rest on, and it is the tripwire: if this app ever adopts CnAppRoot, that fails where the reason is written down instead of leaving four tests passing against chrome that moved. The settings-foldout test drops its personal-settings assertion, because that entry is a CnAppNav widget with no equivalent in this shell. What survives is the part about LaunchPad rather than about the component: the admin settings section and the Flows page open. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ure (#549) LaunchPad declares nine pages in its manifest and serves ONE. /store, /reports and /flows each redirect to /dashboard, and /reports/dashboards renders the workspace grid. That is not a bug in these tests. It is the app: LaunchPad has no vue-router at all — createRouter appears nowhere in src/ — because navigation is Pinia state that never touches the URL, and it is the only app in the fleet that does not root on CnAppRoot. main.js has called this Tier 1 since manifest adoption landed and named the change that would fix it. That change was never filed, so it is filed here: openspec/changes/launchpad-manifest-tier-3/. The four route-dependent assertions are marked test.fail() against it. Not skipped: a skipped test proves nothing and stops being read, while Playwright FAILS the run when a test.fail() test passes — so landing routing turns these red until the markers come off, in the change that earns it. What stays a live assertion is the half that is true today and worth guarding: the manifest declares four footer destinations in ADR-114's order, each with an icon, Documentation as an external href, and each of the other three naming a page the manifest hosts. The admin settings section splits out of the old foldout test and keeps passing, because it is a Nextcloud settings route rather than one of this app's own. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(routing): serve the pages the manifest declares LaunchPad declared nine pages and served ONE. /store, /reports and /flows each redirected to /dashboard, and /reports/dashboards rendered the workspace grid — because there was no vue-router at all. `createRouter` appeared nowhere in src/: navigation was Pinia state that never touched the URL, and this was the only app in the fleet that did not root on CnAppRoot. main.js has named this change since Tier 1 adoption landed. Its own file header already described the intent — "App.vue watches it and passes the live value down to CnAppRoot" — and the implementation stopped short. WHAT CHANGED App.vue roots on CnAppRoot. The route table is derived from manifest.pages, so a declared page is a routed page by construction. `/` and `/dashboards/:id` render WorkspaceApp; the rest render their declared page types. The org navigation rail stays exactly where it is. It is NOT an app menu — it renders an org-wide tree from GET /api/admin/org-navigation, group-filtered, with its own position setting — so it coexists with CnAppNav rather than folding into it, and it renders only when an admin has configured a tree. The bespoke skip link is gone. It existed BECAUSE this app did not root on NcContent; CnAppRoot renders it, and two bypass links are worse than one. #launchpad-main-content keeps tabindex="-1" for the quick-search Esc contract, which needs it independently. A dashboard has an address now. /dashboards/:id was declared all along and routed nowhere; WorkspaceApp watches the param and switches. An unknown id is left alone deliberately — the store already resolves a sensible active dashboard, and overriding would make a bad link empty the page instead of falling back. TWO PAGES WERE NOT PAGES admin-settings named AdminSettingsPage, which has never existed, and admin-templates-index named TemplatesPage, which is a TAB inside the Nextcloud admin section. Routing them would have rendered half an admin surface beside the real one. Both resolve to AdminSettingsRedirect, which sends the operator to /settings/admin/launchpad and renders a real anchor as well as navigating. TESTS The four test.fail() markers come off — this is the change that earns them. The tripwire is INVERTED rather than deleted: it asserted the absence of CnAppNav, and now asserts the shared chrome renders with the workspace still inside it. A new test asserts a dashboard has an address, asking the app which dashboards exist rather than seeding an id. Verified: check:manifest, format, check:schema-l10n and lint pass; webpack compiles (exit 0, the 3 warnings are the pre-existing bundle-size ones); vitest WorkspaceApp 14 passed; playwright --list collects 9. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(chrome): give CnAppNav a slot, and answer the two gates Three findings, all real. 🔴 REQ-SHELL-001 SUPPRESSED THE NAVIGATION SLOT. PageController passed `id-app-navigation => null` so Nextcloud allocated no left panel — the app rendered its own slide-in sidebar and nothing else. CnAppRoot renders NcContent, and NcContent allocates that panel for CnAppNav, so leaving the suppression in place would have left the shared chrome with nowhere to render: an empty rail, not an error. Every other app in the fleet passes no slot ids at all. Found by reading the spec this change contradicts, not by CI. gate-16 wanted @SPEC on three changed methods. The change had no spec to point at, so one is written — `specs/manifest-routing/spec.md`, six requirements including REQ-ROUTE-004, which records that it supersedes REQ-SHELL-001's chrome-slot clause. Every anchor was checked to resolve; a tag that merely exists is not evidence. gate-26 wanted a visual baseline for two new page components. AdminSettingsRedirect gets a real test: both admin routes are opened and must either reach /settings/admin/launchpad or render the notice, since the redirect deliberately does both. WorkspaceApp gets `@visual exclude` with the reason — it is not a new screen, it is this app's oldest and only one, and a baseline captured now would be a baseline of the screen that already shipped. Verified: all 78 applicable hydra gates pass locally against origin/development; check:manifest, format and lint green; phpcs clean on PageController; playwright --list collects 10. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(e2e): settle the support note once, for the whole run
The tier-3 push run came back 62 failed / 85 passed. Every one of the 62
is a click that never became actionable, across a dozen unrelated spec
files, and the call log names the same thing each time:
<div data-testid-modal="cn-support-dialog" class="dialog__modal modal-mask">
subtree intercepts pointer events
CnAppRoot mounts CnSupportDialog on first open by default. This app
rooted on CnAppRoot in launchpad-manifest-tier-3, and inherited it. The
chrome spec was among the survivors only because it happens to dismiss
[data-testid="cn-modal"] in its own beforeEach — which is why the tier-3
PR went green on every check and the regression only appeared on the
push run, where the E2E leg actually runs.
Dismissal persists per USER through the app's preferences endpoint, so
recording it once in globalSetup settles it for every spec — the same
shape as dismissFirstRunWizard beside it, and the same reasoning the
walkthrough sentinel already documents two paragraphs down: the marker is
per user, so without it the suite is order-dependent and whichever spec
runs first wears the dialog.
The localStorage key goes with it, because the composable falls back to
it when the server read fails and a flaky preferences call would
otherwise put the mask back.
⚠️ NOT `:support-dialog="false"` in the app. That would take a real
feature away from real users to make the tests quiet.
Verified: format, lint and check:manifest pass. The standalone `tsc`
TS5097 on this file is pre-existing — identical on origin/development
without this change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(e2e): the report and store pages are not the workspace
Two of the 62 were not the modal mask. They are my own assertions, and
they were stale the moment routing landed.
`the dashboards report renders real numbers` and `Store opens the hosted
store surface` both asserted `.workspace-shell` on their page. That
passed before `launchpad-manifest-tier-3` for the wrong reason: every URL
fell back to the dashboard, so the workspace WAS on every page. It routes
now, and those pages are the report and the store — the workspace being
absent is the change working.
They assert the shared chrome instead, which is what those pages do have.
The rest of that spec is the direct evidence tier 3 works: `each declared
chrome destination opens`, `Reports lists the one report this app can
honestly offer`, `the Flows page opens` and `the admin manifest pages
redirect` all PASSED on the same run — every route that used to fall
through to /dashboard.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t behind it (#555) #551 adopted the shared shell, and the dashboard switcher has been unreachable ever since. 58 e2e tests across eleven specs fail as `locator.click` timing out, and all 240 interception lines in that run name the same element: `[data-testid="cn-nav"] .app-navigation`. The panel slides out, it is visible, and every click on it lands on the navigation instead. This is a stacking context, not a z-index. The sidebar is `position: fixed`, which escapes the scroll flow but not the stacking context. Until #551 this view WAS the page, so there was no context to escape and nothing else claimed the left 280px. Now it renders inside `#app-content`, a sibling of `.app-navigation`, and NcAppNavigation carries `z-index: 1400` on itself. A sibling with a z-index paints above the whole of a sibling subtree whose own z-index is auto, so the sidebar's 1500 could never win: it was competing inside a box that had already lost. Raising the number again would have changed nothing, which is worth saying because that is the obvious first move. The sidebar and its backdrop are therefore teleported to `body`, making them siblings of the navigation rather than descendants of the content, which is the only place their z-index means what it says. The backdrop goes from 999 to 1450 while it is there. At 999 the navigation painted over it even once teleported, so a click meant to dismiss the sidebar navigated instead. 1450 puts it above the navigation and below the sidebar it sits behind. Verified locally: 682 unit tests pass, webpack build clean, eslint and prettier clean. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ve (#557) * fix(shell): the dashboard switcher opens above the app navigation, not behind it #551 adopted the shared shell, and the dashboard switcher has been unreachable ever since. 58 e2e tests across eleven specs fail as `locator.click` timing out, and all 240 interception lines in that run name the same element: `[data-testid="cn-nav"] .app-navigation`. The panel slides out, it is visible, and every click on it lands on the navigation instead. This is a stacking context, not a z-index. The sidebar is `position: fixed`, which escapes the scroll flow but not the stacking context. Until #551 this view WAS the page, so there was no context to escape and nothing else claimed the left 280px. Now it renders inside `#app-content`, a sibling of `.app-navigation`, and NcAppNavigation carries `z-index: 1400` on itself. A sibling with a z-index paints above the whole of a sibling subtree whose own z-index is auto, so the sidebar's 1500 could never win: it was competing inside a box that had already lost. Raising the number again would have changed nothing, which is worth saying because that is the obvious first move. The sidebar and its backdrop are therefore teleported to `body`, making them siblings of the navigation rather than descendants of the content, which is the only place their z-index means what it says. The backdrop goes from 999 to 1450 while it is there. At 999 the navigation painted over it even once teleported, so a click meant to dismiss the sidebar navigated instead. 1450 puts it above the navigation and below the sidebar it sits behind. Verified locally: 682 unit tests pass, webpack build clean, eslint and prettier clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(shell): the floating controls move with the backdrop they sit above #555 teleported the sidebar and its backdrop to `body` and raised the backdrop to 1450. It should have moved the floating controls at the same time, and the e2e said so within one run. Those controls are `position: fixed` with `z-index: 1000`, and that number was not arbitrary: it sat just above the backdrop's original 999 so that "click the hamburger again to close the sidebar" works — the sidebar is open, the backdrop is up, and the toggle has to stay reachable through it. Teleporting one half of that pairing broke it. The backdrop became a body-level sibling while the controls stayed inside `#app-content`, whose own z-index is auto, so the backdrop painted over them whatever number they carried. `runtime-shell-canEdit` names it exactly: the backdrop intercepting pointer events over a button the same log calls visible, enabled and stable. So both halves move, and the controls go to 1460 — above the backdrop, below the sidebar they never overlap. The comment says which number it is above and why the Teleport is what makes it mean anything, because the next person to see 1460 will otherwise try raising it. Verified locally: 682 unit tests pass, webpack build clean, eslint and prettier clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…'s own (#559) * fix(shell): compose the runtime manifest instead of replacing the app's own Reproduced on a clean instance rather than reasoned about, and the finding is worse than the 13 failing tests suggested: with tier 3, launchpad rendered NOTHING on a fresh install. `#workspace-vue` held 7 bytes, an empty comment, against 43,564 for the same instance on the commit before tier 3. `GET /api/manifest` is not this app's manifest. ManifestController says so in its own docblock: it assembles a document from the user's OpenRegister dashboard objects, one page and one menu entry per dashboard, and "when the user has no dashboards the manifest returns empty pages/menu". App.vue read `runtimeManifest?.value ?? this.manifest`. An empty object is truthy, so a user with no dashboards got a manifest with no pages and no menu, and tier 3 builds both the router and the nav from the manifest. A user WITH dashboards was no better off, only less obviously: the runtime document still replaced the nine declared pages, the ADR-114 footer and the walkthrough, which is what the chrome and routing specs have been failing on. So the two are composed, which is what each is for. The bundled manifest owns the declared surfaces; the runtime one contributes the dashboards only the server knows about, and the live `runtime` block. THE SETUP WIZARD CAME WITH CnAppRoot, and nothing accounted for it. CnAppRoot opens CnSetupWizard while ANY step is outstanding, optional or not (nextcloud-vue#806) — and `completed` is not `settled`: the status endpoint answered `"completed": true` while still reporting `demo-data` and `load-demo-data` as `done: false`. `cn-wizard-dialog` is an aria-modal that intercepts pointer events, so every click landed on it. Measured locally: with the wizard up, dashboard-sharing failed 4 of 4; with the choice recorded, runtime-shell-canEdit and add-widget-modal ran 10 passed, 0 failed. globalSetup now records the choice once, because the marker is per user and settling it per spec makes the suite order-dependent. Three manifest references could never resolve. The `dashboards` menu entry and two walkthrough steps pointed at `dashboard-detail`, whose path is `/dashboards/:id`, so vue-router threw `Missing required param "id"` on every render. They were inert until tier 3 gave the manifest a real router. A third step named the `Flows` PAGE where a menu-entry id belongs. Six labels were raw i18n keys. `launchpad.menu.dashboards` and five others render as the key itself in all 36 locales but Dutch, which is the only one carrying them. They are plain English now, like every other entry in this menu already was. Dutch loses a translation it only ever had for these six; that is the same position `Flows`, `Store` and `Reports` are already in, and worth a follow-up for all of them together rather than a special case for these. 682 unit tests pass. The development push run is the E2E proof, since that leg does not run on a pull request. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(nav): the admin entry is not called Settings, because the foldout already is gate-63 caught the label I chose. ADR-079 reserves `settings`, `appsettings` and `app settings` for a settings-foldout entry, because NcAppNavigation's foldout is ITSELF labelled "Settings" — an entry by that name renders as Settings > Settings, which is the duplication ADR-079 D1 removed a whole menu to stop. Replacing the raw `launchpad.menu.adminSettings` key with "Settings" walked straight into it. "Admin settings" says where the entry goes — it redirects to /settings/admin/launchpad — and is not reserved.⚠️ FOLLOW-UP, NOT SILENTLY FIXED HERE: this entry now carries the same label as the one nc-vue's shared chrome already renders, and both lead to the same admin page. ADR-079 D1's precedent says an in-app duplicate of the Nextcloud admin settings page should go, not be renamed. Removing a menu surface is a product decision rather than a gate fix, so it is named here instead of taken. Verified locally: gate-63 reports `checked 2 manifest(s): 0 failure(s)`, 682 unit tests pass, webpack build clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(l10n): the new admin label has a Dutch key, so it does not render English gate-102 caught what my last commit deferred, and it was right to: a manifest string with no `l10n/nl.json` key renders its English source to a Dutch user, and nothing else reports it. `check:l10n-js` compares nl.json to nl.js, and a string absent from BOTH is in sync.⚠️ CORRECTION TO THAT COMMIT MESSAGE. It said Dutch loses a translation it only ever had for these six labels. That was wrong, and the gate is what measured it: five of the six English strings I introduced — Dashboards, Templates, Settings, Dashboard — were ALREADY keys in nl.json, so replacing the dotted i18n keys cost Dutch nothing there. Only "Admin settings" was new, because only that label had no prior English form to have been catalogued under. So there is no follow-up owed for the other five, and the one that was genuinely missing is added rather than noted: "Beheerinstellingen", matching the file's own "Instellingen > Beheer" and "beheerder". Verified locally: gate-102 `checked 51 manifest string(s)` with no findings, gate-63 `0 failure(s)`, prettier clean, 682 unit tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
All eleven of launchpad's remaining e2e failures, across two specs, were one defect wearing a misleading name — and a second, smaller one behind it. NcSelect defaults `appendToBody` to true, so vue-select moves the open menu to `<body>` and positions it with inline styles. Its z-index reads `var(--vs-dropdown-z-index)`, which nc-vue declares on `body` as 9999. NcModal does not use a fixed z-index at all: it assigns one at RUNTIME, counting up as modals open — measured 10005 for the first and 10010 for the second on one page. Every one of those is above 9999, so the modal always wins and the menu is drawn underneath it. The failure never names the select. `elementFromPoint()` at the option's own centre returns whatever modal content happens to sit over it, so Playwright reported `<p class="dashboard-config__hint"> intercepts pointer events` and a reader goes looking at the hint. That is why this survived three rounds of fixes: the interceptor named in the log is innocent.⚠️ NOT THE SAME BUG as `.dashboard-config__field--select` in DashboardConfigModal.vue, and that rule cannot help. It raises the FIELD so a menu rendered INSIDE the modal clears the fields below it. This menu is not inside the modal at all.⚠️ TWO OBVIOUS FIXES BOTH LOSE, MEASURED RATHER THAN ASSUMED. A plain `.vs__dropdown-menu { z-index }` is overridden by nc-vue's own same-specificity rule in `_plugin-vue_export-helper-*.chunk.css`, which loads after this file: the rule shipped in the bundle and the computed value stayed 9999. Redeclaring the variable on `:root` loses to nc-vue's declaration on `body`, which is more specific for everything inside it. Setting the variable on the MENU beats an inherited value whatever the source order, so this needs no `!important`. THE SUPPORT NOTE WAS SETTLED FOR ONE USER OF THE TWO. `global-setup.ts` records it through the logged-in admin's own preference and the marker is per user, so the suite settled it for exactly one of the people it logs in as. With the z-index fixed, "recipient sees the shared dashboard in their switcher" was the last failure standing, and the call log named `cn-support-dialog` intercepting a toggle it also called visible, enabled and stable. seed.sh now settles it for every seeded account, with occ, because occ is the only thing there that can write another user's preference — the app's own endpoint writes the caller's. That mirrors the firstrunwizard block already above it, which documents this same shape for the same test. Verified against a real instance, not reasoned about: conditional-visibility- editor 13 passed of 13, dashboard-sharing 4 passed of 4. Both were 0 of their CI-failing subsets before. The z-index collision belongs upstream in nextcloud-vue, where the modal counter and the dropdown constant should be related rather than coincidental. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d alone (#562) Correcting my own description in #561. It said nc-vue declares `--vs-dropdown-z-index` on `body` as 9999. That is true of nc-vue's chunk stylesheet, and it is ALSO true of this app's own `css/launchpad.css`, which re-declares it there deliberately under ConductionNL/nextcloud-vue#864 because nc-vue 9.10 moved the declaration onto the select root where an appended menu cannot inherit it. Leaving that out made the new rule look like it replaced the old one. It does not. They fix different halves and removing either brings a different failure back: css/launchpad.css restores INHERITANCE, so the menu receives a value. src/styles/workspace.css raises the VALUE above NcModal, which assigns itself a runtime z-index counting up as modals open — measured 10005 for the first and 10010 for the second. Every one beats 9999, so the #864 workaround alone still leaves the menu under any modal. That matters because #864 is open and ends with "delete both once upstream fixes the scoping". Whoever closes it will read this file, and now finds a line saying the 10100 rule must stay until nc-vue relates the two numbers rather than choosing them independently. Both comments now point at each other. Comment-only. No rule changes. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.