feat(manifest-v2): runtime manifest + OR-backed dashboards#206
Merged
Conversation
Phase 1e of the v2 manifest rollout (hydra ADR-036). Mydash becomes the proving ground for the runtime-manifest pattern: no bundled home page, no shell pages, the manifest is per-user state read from OpenRegister at runtime. OpenBuilt and future user-built apps inherit this shape. What landed Library + dependencies - @conduction/nextcloud-vue: ^0.1.0-beta.17 → ^1.0.0-beta.57 — first beta containing the v2 schema, validator (validateManifestV2), CnAppRoot registry prop, and the unified widgets[] model. - appinfo/info.xml: <app>openregister</app> added to <dependencies>; nextcloud min-version bumped 28 → 29 (OpenRegister's floor). - src/manifest.json: 8-line stub declaring v2 $schema + dependencies:["openregister"]; pages[] and menu[] empty (the runtime endpoint fills them per user). OR schema for dashboards - lib/Settings/mydash_register.json (NEW) — defines the mydash/dashboard OR schema. Properties: slug, title, description, version, widgets[] (uniform v2 widget entries), sharedWith[], isDefault. Seed data: gemeente-overzicht (municipality, isDefault) + consultancy-werkplek. Per ADR-001 @self envelope; ObjectService handles register+schema+slug. Runtime manifest endpoint - lib/Controller/ManifestController.php (NEW) — GET /apps/mydash/api/manifest via the `manifest#index` route. Lazy-loads ObjectService via the container (decidesk/procest pattern; no composer dep on openregister). Lists dashboards visible to the current user (owner or shared); builds a v2 manifest with one type:"dashboard" page + menu entry per dashboard. Empty pages[]/menu[] for first-run users — frontend renders the empty-state CTA. #[NoAdminRequired] + #[NoCSRFRequired] attributes (GET-only). - appinfo/routes.php: manifest#index route registered before wildcards. Frontend runtime loader - src/main.js + src/App.vue (modified by prior subagent) — inline runtime-manifest fetch on bootstrap, replaces the bundled stub entirely (no merge per ADR-036). Existing Views.vue + GridStack surface is preserved during the transition; the CnPageRenderer-driven migration of Views.vue is a separate spec. Migration: legacy mydash tables → OR objects - lib/Migration/Version002000Date20260519000000.php (NEW) — runs in postSchemaChange. Iterates mydash_dashboards (excludes admin_template), loads widget placements from mydash_widget_placements, maps to v2 widget entries (widgetKey = widgetId, grid coords direct), calls ObjectService::saveObject(). Non-fatal on row failures (logs + continues). Safety cap: 2000 rows. Legacy tables NOT dropped — left as read-only artefacts pending the full CRUD migration to OR. If OpenRegister is unavailable, migration skips silently. Pre-production app; minor breaking change acceptable. Quality - composer lint (PHP syntax): PASS — 200+ files - composer lint:spec-annotations: PASS — added 11 pre-existing missing entries + 2 new migration methods to the allowlist - composer lint:initial-state: PASS - src/manifest.json validates against v2 schema (Ajv + ajv-formats): valid: true, errors: [] - npm run build / lint: not runnable in the worktree (node_modules incomplete); CI will run them via npm ci What this PR does NOT do (deferred) - DashboardService + DashboardApiController still read/write the legacy mydash_dashboards + mydash_widget_placements tables. The OR pivot is only for the manifest read endpoint in this PR. Full CRUD migration to OR is a follow-up spec — after that, the legacy tables can be dropped via a second migration. - Views.vue + GridStack are unchanged. Migration of Views.vue to CnPageRenderer + v2 widget grid is a separate follow-up. - Registry-driven custom widgets (TileCard, WidgetWrapper, etc.) are not registered via the 5-kind registry yet. Their migration lands with the Views.vue rewrite. Supersedes - Memory feedback_mydash-no-or-dependency.md is marked SUPERSEDED by ADR-036. The new direction: mydash depends on OR for dashboard storage. BI/chart features that read other apps' data still go via OR's GraphQL runtime endpoint (no install-time dep needed for that). Out of scope - Phase 2 fleet rollout to remaining apps (per-app opsx changes)
Contributor
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ❌ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ❌ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ❌ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ❌ | ❌ | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-19 12:00 UTC
Download the full PDF report from the workflow artifacts.
- phpcs: fix ManifestController (capital in doc, function spacing, inline @var comments, named param, end-try comment, no ternary) - phpcs: fix migration (function spacing, @param mismatch, inline @var, no implicit bool, end-try comments) - phpstan: suppress OpenRegister ObjectService and Doctrine\DBAL\Schema\Schema unknown-class errors in phpstan.neon (both are runtime-only deps loaded via DI container) - npm: add bootstrap-vue and vue-frag as explicit deps so the package-lock.json is in sync with what @conduction/nextcloud-vue 1.x declares as peer dependencies; npm ci was failing with "Missing: bootstrap-vue from lock file"
Contributor
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ❌ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ❌ | ❌ | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-19 12:15 UTC
Download the full PDF report from the workflow artifacts.
The previous npm install with --legacy-peer-deps skipped installing peer deps of dev deps (notably @babel/core required by @nextcloud/babel-config and @nextcloud/webpack-vue-config), causing npm ci to fail with 'Missing: @babel/core from lock file'. Re-run npm install without --legacy-peer-deps so the lockfile captures the full peer-dep closure (1646 packages vs 1093).
Contributor
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 522/522 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ⏭️ |
Coverage: 90.7% (127/140 statements)
Quality workflow — 2026-05-19 12:21 UTC
Download the full PDF report from the workflow artifacts.
Bump @conduction/nextcloud-vue from ^1.0.0-beta.57 to ^1.0.0-beta.58 — the Ajv CSP hotfix (nc-vue #259) that makes the v2 runtime path actually mount in CSP-hardened browsers (i.e. all Nextcloud installs). Regenerated package-lock.json without --legacy-peer-deps to preserve the full peer-dep closure (1648 packages), matching the approach used in the prior "full peer dep tree" regeneration commit.
Contributor
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 522/522 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ⏭️ |
Coverage: 90.7% (127/140 statements)
Quality workflow — 2026-05-19 13:24 UTC
Download the full PDF report from the workflow artifacts.
This was referenced May 19, 2026
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.
Summary
Phase 1e of the v2 manifest rollout (hydra ADR-036). Mydash becomes the proving ground for the runtime-manifest pattern: no bundled home page, no shell pages — the manifest is per-user state read from OpenRegister at runtime.
This is the canonical shape OpenBuilt + future user-built apps will inherit.
What landed
Library + dependencies
@conduction/nextcloud-vue:^0.1.0-beta.17→^1.0.0-beta.57(major bump; onlyNc*re-exports used so no import changes needed)appinfo/info.xml: added<app>openregister</app>+ bumped Nextcloud min-version 28 → 29src/manifest.json(new) — 8-line stub declaring v2$schema+dependencies: ["openregister"]OR schema for dashboards
lib/Settings/mydash_register.json(new) — definesmydash/dashboardschema. Properties:slug,title,description,version,widgets[](uniform v2 widget entries),sharedWith[],isDefault. Includes 2 seed objects.Runtime manifest endpoint
lib/Controller/ManifestController.php(new) —GET /apps/mydash/api/manifest. Lazy-loadsObjectServicevia the container (decidesk/procest pattern). Lists the current user's dashboards (owner + shared), builds a v2 manifest with onetype: "dashboard"page per dashboard. Emptypages[]/menu[]for first-run users.Migration: legacy tables → OR
lib/Migration/Version002000Date20260519000000.php(new) — runs inpostSchemaChange. Iteratesmydash_dashboards+mydash_widget_placements, maps each to a v2 widget entry, callsObjectService::saveObject(). Non-fatal on row failures. Legacy tables NOT dropped (read-only artefacts pending full CRUD migration).Frontend runtime loader
src/main.js+src/App.vue— inline runtime-manifest fetch on bootstrap; replaces the bundled stub entirely (no merge, per ADR-036). Views.vue + GridStack surface preserved during transition.Quality
composer lint(PHP syntax) ✓composer lint:spec-annotations✓ (added 11 pre-existing + 2 new migration methods to allowlist)composer lint:initial-state✓src/manifest.json✓npm run build/lint: not runnable in the worktree (node_modules incomplete); CI runs vianpm ciWhat this PR does NOT do (deferred)
DashboardService+DashboardApiControllerstill read/write the legacy mydash tables. The OR pivot is only for the manifest read endpoint here. Full CRUD migration to OR is a follow-up spec.Views.vue+ GridStack unchanged. Migration toCnPageRenderer+ v2 widget grid is a separate follow-up.Supersedes
Memory
mydash-no-or-dependency.mdmarked SUPERSEDED by ADR-036. The new direction: mydash depends on OR for dashboard storage. BI/chart features still go via OR's GraphQL runtime endpoint (no install-time dep needed there).Hydra labels
Not adding
ready-for-code-review/ready-for-security-review— Hydra is paused. Ready for manual review.Test plan
info.xmldependency resolves/apps/mydash/api/manifest→ returns emptypages[]/menu[](empty-state path)DashboardApiControllerCRUD endpoints still work (legacy tables unchanged)