Skip to content
This repository was archived by the owner on Aug 4, 2026. It is now read-only.

feat(vue3): migrate deskdesk to Vue 3 + nc-vue 2.1.0-vue3.17 (and re-enable this repo's CI) - #76

Merged
rubenvdlinde merged 1 commit into
developmentfrom
feat/vue3-migration
Aug 3, 2026
Merged

feat(vue3): migrate deskdesk to Vue 3 + nc-vue 2.1.0-vue3.17 (and re-enable this repo's CI)#76
rubenvdlinde merged 1 commit into
developmentfrom
feat/vue3-migration

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What

Migrates deskdesk from Vue 2 to Vue 3 and pins @conduction/nextcloud-vue to 2.1.0-vue3.17 (exact — a caret does float an already-resolved prerelease when the dist-tag moves).

deskdesk was one of four apps missed by the original fleet Vue 3 working set. It sat on vue@^2.7.14 with @conduction/nextcloud-vue@^1.0.0-beta.101 — the oldest nc-vue pin in the org.

⚠️ CI on this repo was switched off entirely — and that is now fixed

deskdesk issue #75 reported that code-quality.yml "produces no runs at all". The root cause was not the workflow:

$ gh api repos/ConductionNL/deskdesk/actions/permissions
{"enabled": false}

GitHub Actions was disabled at the repository level. deskdesk was the only one of 8 ConductionNL repos I checked with enabled: false (nldesign, GDPRdesk, hermiq, docudesk, doriath, decidesk, openregister are all true). The last Code Quality run was 2026-05-26 (run 26435406003, 26 jobs), and every push and PR since produced nothing — a gate's failure and its complete absence look identical from the PR page.

I re-enabled it (PUT …/actions/permissions {"enabled": true, "allowed_actions": "all"}), so this PR should be the first deskdesk PR in over two months to get real checks. Please read them rather than assuming green.

Note the correction to issue #75 while you are there: the issue says there is "no Code Quality run on development". There is — run 26435406003 from 2026-05-26, and it did include a Security (composer) job, which passed. The gate set exists and works; it was simply switched off.

Dependencies

from to
@conduction/nextcloud-vue ^1.0.0-beta.101 2.1.0-vue3.17 exact
vue ^2.7.14 ^3.5.13
@nextcloud/vue ^8.39.0 ^9.9.0
@nextcloud/dialogs ^3.2.0 ^7.4.1
vue-router ^3.6.5 ^4.6.4
vue-loader ^15 ^17.4.2
vue-template-compiler ^2.7.16 dropped
@nextcloud/webpack-vue-config ^6.0.1 ^7.0.2
terser-webpack-plugin, @vue/compiler-sfc, gridstack@^12 added

vue-apexcharts (the Vue-2-only wrapper, zero usages in src/) is removed and apexcharts goes ^3.54.1 → ^4.7.0. overrides."vue3-apexcharts" is pinned to 1.8.0 because 1.9.0+ is proprietary and forbids sublicensing in our EUPL-1.2 apps. No licence override was added — core apexcharts is MIT and reports an identical Custom: symptom.

Code

Bootstrapnew Vue().$mount()createApp().mount(); Vue.mixin/Vue.useapp.mixin/app.use; PiniaVuePluginapp.use(pinia); Vue.observable()reactive().

Routernew Router({ mode: 'history' })createRouter({ history: createWebHistory(...) }), and path: '*'path: '/:pathMatch(.*)*'. v4 removed the bare wildcard and does not error: the route simply never matches, the shell renders and <main> stays empty.

:open.syncv-model:open on CnObjectSidebar. Checked against the vue3.17 source rather than assumed — it really does declare an open prop and emit update:open. This is the case where eslint --fix is dangerous: its .sync autofix preserves the prop name, so it would have written v-model:value on a component with no value prop — lint green, binding dead. All .sync conversions here were done by hand.

Mount target renamed #content#deskdesk-app in templates/index.php. Vue 2's $mount() replaced the matched element, so mounting on the template's <div id="content"> quietly replaced Nextcloud's own #content wrapper from layout.user.php and the duplicate id never showed. Vue 3's mount() renders inside the match, and with two #content elements it is undefined which one wins.

Admin settings no longer mounts inside the loadTranslations callback. On installs where /custom_apps/<app>/l10n/<locale>.json 404s the callback never fires — a blank admin panel with no error.

Build config

  • Aliases now point at absolute FILES, not package directories. @nextcloud/vue@9, @nextcloud/dialogs@7 and vue-router@4 ship an exports map with no main and no module; webpack applies an exports map to package requests and never to an already-absolutised path, so the old Vue-2-era directory aliases resolved to nothing.
  • New src/setPublicPath.js, the first import of both entry points, setting __webpack_public_path__ via generateFilePath and __webpack_nonce__. The hardcoded /apps/<app>/js/ is wrong under custom_apps, and the wrong path does not 404 — Nextcloud answers 200 text/html, so it surfaces as a MIME refusal and ChunkLoadError. Vue 2 never exposed this because it emitted no async chunks; the Vue 3 dependency set emits many.
  • sideEffects: true rule for the nc-vue dist. The library's sideEffects allowlist does not glob-match its compiled *.vue.js wrappers, so webpack tree-shakes the script.render = render glue and every Cn component renders as a bare comment node with no warning.
  • appName / appVersion DefinePlugin entries re-added — they were silently dropped by replacing webpackConfig.plugins wholesale, which makes every @nextcloud/vue mount log "the library was used without setting the appName".
  • USE_LOCAL_LIB is now opt-IN. It was opt-OUT against ../nextcloud-vue/src, which is the Vue 2 beta line — so any build run from the shared apps-extra checkout silently compiled Vue 2 sources into this app.

Lint

eslint.config.js now spreads conductionVue3Fixes last. The @nextcloud v8 base is Vue-2 era and activates zero vue/no-deprecated-* rules, so Vue-2 idioms survive a green lint. Verified by severity, not rule name (--print-config lists rules set to off too): 21 of 21 now resolve to error/warn.

Also fixed pre-existing indentation errors in DetailPageWrapper.vue's watch handler and declared @spec as a known JSDoc tag, as hermiq does.

Verification

Recipe: rm -rf package-lock.json node_modulesnpm install (npm 11.13.0) → npm install --package-lock-only (npm 10.9.8) → npm ci (npm 10.9.8). All exit 0. npm ci passes without --min-release-age=0, so CI's install is unaffected by the .npmrc cooldown.

Verified from the lockfile:

@conduction/nextcloud-vue 2.1.0-vue3.17
vue / @nextcloud/vue / vue-router 3.5.40 / 9.9.0 / 4.6.4
vue3-apexcharts 1.8.0 (below the proprietary line)
bootstrap-vue, vue-template-compiler absent
check before after
npm run lint 16 errors (all indent, DetailPageWrapper.vue) 0 errors, 2 pre-existing jsdoc/require-param warnings
npm run stylelint exit 0 exit 0
npm run build n/a (Vue 2) exit 0, asset-size warnings only

A quote-aware multi-line tag scan over all 5 .vue files (a line grep undercounts these by 60–100%, and a naive multi-line matcher truncates at the > inside an attribute value) finds zero residual .sync, zero <NcButton type=, zero plain :value= — positive control: 33 tags matched, so the scan can match.

Not done

  • No browser smoke check on a live instance. The host is at 96% disk with the Docker default address pool exhausted, and hermiq — the repo with the live wizard bug — took priority for the one instance available. npm run build is green and the residual scan is clean, but this app has not been loaded in a browser on this branch. It should be before release.
  • No e2e run (test:e2e exists; playwright.config.ts is committed but no suite was executed).
  • The committed symlink openspec/schemas/conduction → ../../../.claude/openspec/schemas/conduction is dangling and escapes the repo. It is untouched here, but it makes docker cp of this tree refuse outright, so it will block deployment tooling. Separate issue.

deskdesk was one of four apps missed by the original fleet Vue 3 working set. It
sat on vue@^2.7.14 with @conduction/nextcloud-vue@^1.0.0-beta.101 — the oldest
nc-vue pin in the org.

Dependencies
  @conduction/nextcloud-vue  ^1.0.0-beta.101 -> 2.1.0-vue3.17 (EXACT, no caret:
      a caret DOES float an already-resolved prerelease when the dist-tag moves)
  vue                        ^2.7.14 -> ^3.5.13
  @nextcloud/vue             ^8.39.0 -> ^9.9.0
  @nextcloud/dialogs         ^3.2.0  -> ^7.4.1
  @nextcloud/router          ^3.1.0 (already v3)
  vue-router                 ^3.6.5  -> ^4.6.4
  vue-loader                 ^15     -> ^17.4.2, vue-template-compiler DROPPED
  @nextcloud/webpack-vue-config ^6.0.1 -> ^7.0.2, + terser-webpack-plugin
  @vue/compiler-sfc added; gridstack ^12 added (nc-vue peer, CSS imported)
  vue-apexcharts (Vue-2-only wrapper, ZERO usages in src/) removed;
  apexcharts ^3.54.1 -> ^4.7.0, and overrides."vue3-apexcharts" pinned to 1.8.0
  because 1.9.0+ is proprietary and forbids sublicensing in our EUPL-1.2 apps.

Bootstrap (src/main.js, src/settings.js, src/router/index.js)
  new Vue({...}).$mount()      -> createApp({...}).mount()
  Vue.mixin / Vue.use          -> app.mixin / app.use
  PiniaVuePlugin               -> app.use(pinia)
  new Router({ mode:'history'})-> createRouter({ history: createWebHistory() })
  path: '*'                    -> path: '/:pathMatch(.*)*'   (v4 REMOVED the bare
      wildcard; it does not error, the route just never matches and <main> is
      empty)
  Vue.observable()             -> reactive()
  :open.sync                   -> v-model:open (CnObjectSidebar really does
      declare an `open` prop and emit `update:open` in vue3.17 — checked, not
      assumed; the .sync autofix would have written v-model:value)

Mount target renamed #content -> #deskdesk-app in templates/index.php. Vue 2's
$mount() REPLACED the matched element, so mounting on the template's <div
id="content"> silently replaced Nextcloud's own #content wrapper from
layout.user.php. Vue 3's mount() renders INSIDE the match, and with two #content
ids it is undefined which one wins.

The admin settings entry no longer mounts inside the loadTranslations callback.
On installs where /custom_apps/<app>/l10n/<locale>.json 404s the callback never
fires, which is a blank admin panel with no error.

Build config
  Aliases now point at ABSOLUTE FILES, not package directories. @nextcloud/vue@9,
  @nextcloud/dialogs@7 and vue-router@4 ship an `exports` map with no `main` and
  no `module`, and webpack applies an exports map to package requests only —
  never to an already-absolutised path — so the old directory aliases resolved to
  nothing.
  New src/setPublicPath.js, first import of both entry points, sets
  __webpack_public_path__ via generateFilePath and __webpack_nonce__. The
  hardcoded /apps/<app>/js/ is wrong under custom_apps, and the wrong path does
  NOT 404 — Nextcloud answers 200 text/html, so it surfaces as a MIME refusal and
  ChunkLoadError. Vue 2 never exposed this because it emitted no async chunks.
  sideEffects:true rule for the nc-vue dist, or webpack tree-shakes the
  `script.render = render` wrapper and every Cn component renders as a comment
  node with no warning.
  appName / appVersion DefinePlugin entries re-added — they were being dropped by
  replacing webpackConfig.plugins wholesale.
  USE_LOCAL_LIB is now opt-IN. It was opt-OUT against ../nextcloud-vue/src, which
  is the Vue 2 beta line, so any build from the shared checkout silently compiled
  Vue 2 sources into the app.

Lint
  eslint.config.js now spreads conductionVue3Fixes LAST. The @nextcloud v8 base
  is Vue-2 era and activates ZERO vue/no-deprecated-* rules; verified by
  SEVERITY, not by rule name — 21 of 21 now resolve to error/warn.
  Fixed pre-existing indentation errors in DetailPageWrapper.vue's watch handler
  and declared @SPEC as a known JSDoc tag, as hermiq does.

Verification (npm 11 generate -> npm 10 normalise -> npm 10 ci, all exit 0)
  lockfile @conduction/nextcloud-vue = 2.1.0-vue3.17
  lockfile vue = 3.5.40, @nextcloud/vue = 9.9.0, vue-router = 4.6.4
  lockfile vue3-apexcharts = 1.8.0; bootstrap-vue ABSENT; vue-template-compiler
      ABSENT
  npm run lint      -> 0 errors (was 16), 2 pre-existing jsdoc warnings
  npm run stylelint -> exit 0
  npm run build     -> exit 0, 2 asset-size warnings only
  quote-aware multi-line tag scan over all 5 .vue files: ZERO residual .sync,
  ZERO <NcButton type=, ZERO plain :value= (positive control: 33 tags matched)
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/deskdesk @ 9a92883

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 100/100
npm ✅ 651/651
PHPUnit
Newman
Playwright ⏭️

Coverage: 0% (0/3 statements)


Quality workflow — 2026-08-03 11:35 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

CI is alive again, and this is the first real check run on this repo since 2026-05-26

Re-enabling GitHub Actions produced 26 jobs on this PR. Job conclusions read individually, not taken from the PR's overall colour:

Vue Quality (eslint) / (stylelint) pass
Security (npm) / Security (composer) pass
License (npm) / License (composer) pass
PHP Quality — lint, phpcs, phpmd, phpstan, psalm, phpmetrics pass
PHPUnit — PHP 8.3/8.4 × NC stable31/32/33 (6 legs) pass
Frontend Tests (unit) pass
Integration Tests (Newman) pass
lint-check pass

21 pass, 2 fail.

The two red checks are pre-existing

Features Check fails with docs/features.json is out of date, and Quality Report only aggregates it. This is not a regression from this branch: the last Code Quality run before Actions was switched off — run 26435406003, development, 2026-05-26 — also has Features Check => failure, on content predating this work by two months. Regenerating that file rewrites user-facing feature copy, which is a docs decision rather than part of a Vue 3 migration, so it is deliberately out of scope here.

Please also see the correction to issue #75 in the PR description

The issue's diagnosis ("the workflow produces no runs") was a symptom. The cause was actions/permissions → {"enabled": false} at the repository level, and the issue's claim that there is no Code Quality run on development is not accurate — run 26435406003 exists and did include a passing Security (composer) job.

@rubenvdlinde
rubenvdlinde merged commit 2c9fae9 into development Aug 3, 2026
28 of 30 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant