feat(openspec): openbuilt-page-editor — visual manifest designer (chain spec #5)#4
feat(openspec): openbuilt-page-editor — visual manifest designer (chain spec #5)#4rubenvdlinde wants to merge 17 commits into
Conversation
….4, 3.1–3.2)
Schemas (openbuilt-application-register capability):
- Declare Application schema with manifest blob + slug + version + status
- Add x-openregister-lifecycle (draft → published → archived) on Application —
canonical ADR-031 example, no service class
- Declare BuiltAppRoute schema for slug → applicationUuid index
- Wire BuiltAppRoute upkeep via x-openregister-lifecycle on_transition
(declarative path per design.md Decision 2 / Q1)
- Declare HelloMessage seed schema
Runtime (openbuilt-runtime capability):
- Register GET /api/applications/{slug}/manifest route in appinfo/routes.php
- ApplicationsController::getManifest — thin-glue resolver (slug → BuiltAppRoute
→ Application → manifest blob unwrapped). #[NoAdminRequired] + #[NoCSRFRequired]
per design.md Decision 6
- BuilderHost.vue — nested CnAppRoot mount with key=slug, options.endpoint
redirecting useAppManifest's backend fetch to the per-slug endpoint
(workaround per design.md Decision 4 until chain spec #2 ships)
- ApplicationEditor.vue — textarea-based manifest editor with validateManifest
- src/router/index.js — /applications + /builder/:slug/:pathMatch(.*)? routes
- src/manifests/placeholder.json — bundled placeholder skeleton for useAppManifest
Seed (ADR-001):
- SeedHelloWorld repair step — idempotent seed of canonical hello-world
Application + manifest exercising index/detail/form pages + three sample
HelloMessage objects
- info.xml repair-steps — SeedHelloWorld runs after InitializeSettings
Deferred to follow-up apply: tasks 4.x (verification), 5.x (tests),
6.x (docs), 7.x (i18n).
Refs: openspec/changes/bootstrap-openbuilt/{proposal,specs,design,tasks}.md
…tasks 4.1, 4.2, 4.5, 7.1-7.3) PHP refactors per "fix all issues" rule (no @SuppressWarnings shortcuts): - ApplicationsController + SeedHelloWorld: constructor injection of OCA\OpenRegister\Service\ObjectService (eliminates Server::get static access). OR is a declared hard dep in info.xml, so injection is the right pattern per ADR-022 + ADR-003. - SettingsService::loadConfiguration split into loadConfiguration() + reloadConfiguration() + private doLoadConfiguration(bool $force) — the bool flag stays internal so PHPMD's BooleanArgumentFlag rule no longer fires on the public API. - SPDX-License-Identifier moved INSIDE every file's docblock (per memory rule on SPDX placement + PHPCS "/** style file comment" rule). Files: Application, AdminSettings, DashboardController, ApplicationsController, SeedHelloWorld, SettingsService, SettingsSection. - Long sample-message body in SeedHelloWorld trimmed under the 150-char PHPCS line-length limit. Dependency bumps: - @conduction/nextcloud-vue: ^0.1.0-beta.3 → ^1.0.0-beta.30. The 1.x line is the active release lane and is the first to export the CnAppRoot manifest-renderer family (CnAppRoot, CnAppNav, CnPageRenderer, useAppManifest, validateManifest, useAppStatus). 0.1.0-beta.3 did NOT export them — design.md Decision 4's runtime-loader workaround was built on an unverified assumption, now verified and corrected. - @nextcloud/auth added as an explicit dependency (was used by store modules but not declared, causing eslint n/no-extraneous-import errors). phpstan.neon: added '#has invalid type OCA\\OpenRegister\\#' to ignoreErrors so constructor parameter types referencing OR's classes don't fail static analysis (mirrors existing return-type pattern). i18n keys (tasks 7.1, 7.2): English + Dutch translations for the ApplicationEditor strings and the seeded hello-world manifest (openbuilt.helloworld.menu.*, openbuilt.helloworld.title.*, openbuilt.editor.help). Verification status (task 4.x): - ✓ 4.1 composer phpcs / phpmd / psalm / phpstan — all clean - ✓ 4.2 npm run lint — clean - ⊘ 4.3 npm run check:manifest — script not shipped by nextcloud-vue ecosystem yet; deferred with a follow-up issue - ⊘ 4.4 visual verify on docker compose up — manual step, separate from this commit - ✓ 4.5 ADR-031 service-class gate — confirmed no ApplicationLifecycleService / ApplicationStateMachine class under lib/Service/
…s 4.x, 5.1, 6.x, 7.x) Docs (Section 6): - docs/openbuilt-runtime.md — big-picture diagram, manifest endpoint contract, the bundled-mode + options.endpoint workaround until chain spec #2 ships the in-memory useAppManifest overload, declarative lifecycle table, file map - docs/integrator-guide.md — step-by-step for authoring a virtual app by hand (slug rules, manifest checklist, what doesn't work yet in spec #1) - openspec/app-config.json — list openbuilt-application-register + openbuilt-runtime under capabilities Tests (Section 5 partial): - tests/unit/Controller/ApplicationsControllerTest.php — 3 PHPUnit tests: happy path (200 with unwrapped manifest), unknown slug (404 not_found), inconsistent state (500 inconsistent_state when route missing applicationUuid) - tests/unit/Repair/SeedHelloWorldTest.php — 3 PHPUnit tests: getName format, idempotency on re-run, full seed (4 saveObject calls) on fresh install Deferred (need NC + container): - 5.2 Integration test for the Application lifecycle (requires container with OR's lifecycle engine to actually run transitions) - 5.3 Newman collection (requires running NC instance) - 5.4 Playwright e2e (requires NC + browser, mounts via docker-compose) - 4.3 npm run check:manifest — script not yet shipped by @conduction/nextcloud-vue - 4.4 Visual verify on docker compose up — manual step tasks.md status: 22/22 implementation + 3/5 verification + 1/4 tests + 4/4 docs + 3/3 i18n boxes checked. Remaining 5 deferred tasks documented inline.
…-verify
Two pre-existing template inheritances violated ADR-004 hard rules and the
corresponding hydra mechanical gates. Fixed both via the canonical NC
pattern (IInitialState + admin-via-AdminSettings.php-only).
hydra-gate-10 (DOM dataset → IInitialState):
- AdminSettings.php now injects IInitialState and calls
provideInitialState('version', $version) before rendering the template.
- templates/settings/admin.php no longer carries data-version on the mount
div — server data flows via initial-state, not DOM attrs.
- AdminRoot.vue replaces document.getElementById('openbuilt-settings')
.dataset.version with loadState('openbuilt', 'version', 'Unknown').
hydra-gate-11 (admin-router → no admin in vue-router):
- src/router/index.js no longer imports AdminRoot or registers a /settings
route. Admin settings are reached exclusively through Nextcloud's admin
settings framework (/index.php/settings/admin/openbuilt), which goes
through the admin auth gate. The redundant vue-router entry was a
bypass-the-auth-gate regression that the doriath retrospective flagged.
Verification:
- gate-10 grep: no matches
- gate-11 grep: no matches
- composer phpcs/phpmd/psalm/phpstan: all clean
- npm run lint: clean
Surfaced by: /opsx-verify bootstrap-openbuilt
…le + curl manifest endpoint Verified end-to-end: GET /api/applications/hello-world/manifest returns HTTP 200 with the seeded hello-world manifest. Smoke test mission complete. Bug 1 — SettingsService::doLoadConfiguration missing $data + $version args. OR's importFromApp signature is importFromApp(string $appId, array $data, string $version, bool $force=false). My call was passing only appId + force, so importFromApp threw "Argument #2 ($data) not passed". Fixed by reading lib/Settings/openbuilt_register.json from disk + parsing + passing data+version (per openregister-wiring-guide.md — the sweep agent missed this step). Bug 2 — ObjectService::getObjects doesn't exist. The real API is findAll($config) + find($id, register, schema). Refactored ApplicationsController and SeedHelloWorld to the real signatures. Bug 3 — searchObjects requires NUMERIC register/schema IDs in @self, not slugs. Slug-to-ID resolution is NOT applied at this layer. Injected RegisterMapper + SchemaMapper into ApplicationsController to resolve before searchObjects. Found via direct CLI test: @self with slug strings returns 0 results; @self with numeric IDs returns the matching object. Bug 4 — RegisterMapper/SchemaMapper::find with default _multitenancy=true filters out the lookup. Pass _multitenancy: false on the LOOKUP only (object-level multitenancy still enforced via searchObjects + the underlying RBAC). Bug 5 — x-openregister-lifecycle.on_transition.upsert_relation NOT supported by OR's current engine (design.md OQ-1 confirmed). The Application's declarative lifecycle declaration is preserved (still ADR-031-compliant when OR ships the hook), but SeedHelloWorld now explicitly creates the BuiltAppRoute as the ADR-031 §Exceptions(1) fallback. A BuiltAppRouteSyncListener subscribed to ObjectLifecycleTransitionedEvent should follow in a separate change. Plus ObjectEntity UUID extraction now reads $entity->jsonSerialize()['@self']['id'] (__call-based getUuid() is invisible to method_exists, so we read the array). Tests updated to the new controller signature + searchObjects mock. Known remaining gaps (smoke-test follow-ups, deferred): - Openbuilt Register row was NOT auto-created by importFromApp despite schemas being created. Manually created via REST during smoke test; needs an explicit createRegisterIfMissing step in InitializeSettings. - Webpack build fails with @nextcloud/axios exports-field errors when the local apps-extra/nextcloud-vue/ source-alias is active. Frontend not yet runtime- validated; backend is. - BuiltAppRouteSyncListener (the proper ADR-031 §Exceptions(1) PHP fallback for the missing OR lifecycle hook) — covered only for the seed Application; new user-created Applications won't get BuiltAppRoute upkeep yet. All quality tools green: phpcs, phpmd, psalm, phpstan, ESLint.
Quality Report — ConductionNL/openbuilt @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ❌ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 215/215 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-11 10:17 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/openbuilt @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ❌ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ❌ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 215/215 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-11 11:12 UTC
Download the full PDF report from the workflow artifacts.
Spec #5 of the 9-spec OpenBuilt chain — visual manifest/page designer that replaces the spec-1 textarea (now the Raw JSON fallback tab) with a graphical builder targeting the canonical 9-page-type enum (@conduction/nextcloud-vue v1.4.0+). Artifacts: - proposal.md (kind: code, depends_on: bootstrap-openbuilt) - specs/openbuilt-page-designer/spec.md (11 ADDED requirements covering menu tree, page list, 9 per-type sub-editors, live preview, save flow, raw-JSON fallback, debounced validator) - specs/openbuilt-runtime/spec.md (1 MODIFIED — REQ-OBR-005 reshaped from textarea-only to tabbed Design + Raw JSON shell) - design.md (5 decisions covering sub-editor decomposition, drag-drop reuse, live-preview chain-#2 dependency with graceful degradation, validator surface, custom-page registry deferral; ADR-031 declarative-vs-imperative call-out; 4 open questions) - tasks.md (39 tasks across foundations, shared field builders, per-page-type sub-editors, designer view + tabbed swap, i18n, tests, chain coordination) No backend changes — manifest CRUD continues via OR REST per ADR-022. Feature-detects chain spec #2's in-memory useAppManifest overload at runtime; falls back to save-and-reload preview when absent so this spec ships independently. Strict validation passes.
…tor shell) - PageDesigner.vue: three-pane layout dispatching to per-page-type sub-editors; side-panel validator errors; live-preview fallback affordance (TODO chain spec #2 for in-memory preview mount). - ApplicationEditor.vue: two-tab shell (Design default, Raw JSON fallback); both tabs share the in-flight Pinia store so unsaved edits survive a tab switch; Save is gated on dirty + no parse errors. - CustomPageEditor.vue: StubPageEditor passthrough (full editor v1.1). - Router: /applications/:slug, /applications/:slug/design (default), /applications/:slug/json routes wired to ApplicationEditor. - ESLint: disable import/named + n/no-unpublished-import (re-exports through the aliased nextcloud-vue/src trip the resolver; webpack handles at build time), broaden no-unused-vars to allow _-prefixed discarded destructure vars, fix one JSDoc tag-parse warning. Closes tasks 1.1-1.4, 2.1-2.5, 3.1-3.2, 4.1-4.3, 4.8, 5.1, 5.2, 5.3, 5.6. Tasks 4.4-4.7, 4.9 ship as StubPageEditor passthroughs (round-trip lossless); tests (7.x), i18n l10n bundles (6.x), and docs (8.x) deferred to v1.1.
…jectStore
Two ADR-004 / memory-rule violations cleared in one pass:
1. The hand-rolled `applicationEditor` Pinia store + the local
`useObjectStore` defineStore are gone. The local object store is now
a thin `createObjectStore('object')` wrapper around the shared
@conduction/nextcloud-vue implementation. The application-editor
store keeps its editor-local UI state (manifest draft, raw JSON,
dirty flag, validation mirror) but delegates fetchObject / saveObject
/ fetchCollection to the shared store.
2. IndexPageEditor + DetailPageEditor no longer call `axios.get` on OR
REST. They consume a new `useRegisterPicker` composable that wraps
the register / schema / properties endpoints behind
`@nextcloud/router` + request-token headers. The composable applies
the hybrid register model: when the current Application has a slug,
the per-app register `openbuilt-{slug}` is hoisted to the top of the
register list so the picker defaults to the right namespace.
PageDesigner now forwards the application slug + parent route down to
each sub-editor via the dispatched component binding.
The page-designer + ApplicationEditor + 11 sub-editors call
`t('openbuilt', '…')` against 122 unique English source strings; only 3
overlapped with the bootstrap locale (Save, Saving…, Register). The
remaining 119 are now present in both l10n/en.json (mapped to themselves
per the locked plain-English-keys decision) and l10n/nl.json (with
Dutch translations).
Sample additions:
"Application editor" → "Applicatie-editor"
"Raw JSON tab — integrator fallback…" → "Ruwe JSON-tab — integrator-fallback…"
"No pages yet. Click "Add page" to start." → "Nog geen pagina's. Klik op "Pagina toevoegen" om te beginnen."
"Live preview" → "Live-voorvertoning"
"Drag to reorder" → "Sleep om te herordenen"
Translation quality is best-effort by an AI translator; a Dutch speaker
should review the longer help-text strings before release (the page-type
"Structured … editor coming in v1.1" strings in particular).
48492a6 to
fa961ef
Compare
Quality Report — ConductionNL/openbuilt @
|
| 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-11 18:53 UTC
Download the full PDF report from the workflow artifacts.
…elopers Chain spec #4 of 9 (ADR-032). Adds the openspec change with proposal, specs (openbuilt-schema-designer new + openbuilt-runtime modified), design, and tasks. Frontend-only (kind: code). Depends on chain #3 (openregister-runtime-schema-api) for runtime schema CRUD. The designer authors declarative x-openregister-* JSON only — no imperative escape hatches (ADR-031). Phased delivery: v1 ships field editor + lifecycle + relation + widget editors; v1.1 adds aggregation / calculation / notification DSL editors once the shared DSL package is published by chain #3.
Adds vitest config (mirrors schema-editor / mydash CSS-noop + inline-deps), stub aliases for @conduction/nextcloud-vue, vuedraggable, @nextcloud/router, @nextcloud/auth, and a global t()/n() setup file. Composable specs: - useManifestValidator: 10 tests — debounce, validator mock, valid/invalid/edge cases, errorsByPrefix registration. - useLivePreview: 8 tests — arity-1 degraded path, arity-2 chain-spec-2 path, missing-export edge case.
Quality Report — ConductionNL/openbuilt @
|
| 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-11 19:22 UTC
Download the full PDF report from the workflow artifacts.
- MenuTreeEditor: 11 tests — add/remove, depth-2 cap, action/route mutex, monotonic order assignment, drag-reorder via stubbed vuedraggable. - PageListEditor: 15 tests — add-with-type-picker, DEFAULT_CONFIGS shape per page type, duplicate-id detection, route-pattern validation (incl. /:param shape), delete + select handoff, drag-reorder.
Quality Report — ConductionNL/openbuilt @
|
| 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-11 19:24 UTC
Download the full PDF report from the workflow artifacts.
- IndexPageEditor: 12 tests — register/schema picker, columns/actions forward via update:config, sidebar toggle, cardComponent. - FormPageEditor: 14 tests — submitHandler/submitEndpoint mutex, submitMethod + mode enums, submitLabel + successMessage, field add. - PageDesigner: 19 tests — three-pane mount, page-type dispatcher for all 9 types, unknown-type fallback to StubPageEditor (raw-JSON preserves edits), selectedIndex switching, validator side-panel, save-and-preview affordance, deep manifest watcher. Also: fix Vue 2 case-sensitivity bug — kebab-case @update:model-value listeners never matched the children's camelCase update:modelValue emits. Changed 4 sub-editors (Index, Detail, Dashboard, Form) to use @update:modelValue so the parent->child wiring actually fires.
Quality Report — ConductionNL/openbuilt @
|
| 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-11 19:28 UTC
Download the full PDF report from the workflow artifacts.
Adds the Playwright runner config and the openbuilt-page-editor e2e
suite (tasks 7.5 + tab-roundtrip variant of 7.6).
Config models the sibling mydash/procest setup but trims the
global-setup login dance — each spec drives the login form directly
once, since the openbuilt CI matrix only runs one spec file.
httpCredentials + the OCS-APIRequest header are pre-set so specs can
call OR REST and the openbuilt manifest endpoint via request.fetch()
without a second login.
Scenarios:
1. Open the seeded hello-world Application's Design tab, add an
index-type page bound to openbuilt/hello-message, save, then
assert the new route renders inside the inner CnAppRoot mount
under /builder/hello-world/... — covers REQ-OBPD-002 +
REQ-OBPD-003 + REQ-OBPD-009.
2. Switch to the Raw JSON tab, inject a marker page, switch back to
Design — confirms the shared Pinia store round-trips edits across
the tab boundary (MODIFIED REQ-OBR-005).
Playwright binaries are NOT installed automatically (npm install only
brings @playwright/test). The new test:e2e:install script invokes
'playwright install --with-deps chromium' as a one-time per-machine
setup, documented inline in playwright.config.ts.
npm run lint clean.
Adds tests/integration/openbuilt-page-editor.postman_collection.json
to cover the save-flow end-to-end against the running stack. Picked
up automatically by the CI Newman runner alongside the existing
openbuilt.postman_collection.json health check.
Requests:
1. GET OR REST .../objects/openbuilt/application filtered by
slug=hello-world — captures the seed UUID + the original
Application body in collection variables so subsequent steps can
mutate the manifest non-destructively.
2. PUT the mutated Application (one extra `index`-type page bound
to openbuilt/hello-message) — asserts 200 + the saved manifest
reflects the new page id.
3. GET /index.php/apps/openbuilt/api/applications/hello-world/manifest
— confirms the openbuilt manifest endpoint (consumed by the
runtime CnAppRoot mount) serves the mutated manifest.
4. PUT an invalid manifest missing the required `pages` array —
asserts 4xx, covering REQ-OBPD-009 + MODIFIED REQ-OBR-005's
"Invalid edit is blocked before save" clause from the API side.
5. Teardown — PUT the captured original Application body back so
the suite is idempotent across reruns.
Uses the {{base_url}} / {{admin_user}} / {{admin_password}} variables
documented in tests/integration/README.md.
npm run lint clean. openspec validate openbuilt-page-editor --strict
passes.
Quality Report — ConductionNL/openbuilt @
|
| 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-11 20:08 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/openbuilt @
|
| 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-11 20:10 UTC
Download the full PDF report from the workflow artifacts.
CI was red on lint-check + License/Security/Vue Quality npm jobs because `npm ci` refused the out-of-sync lockfile (missing playwright, pinia v3 transitive deps, etc. after the @conduction/nextcloud-vue 0.1.0-beta.3 → 1.0.0-beta.30 bump). Regenerated package-lock.json from scratch and confirmed `npm run lint`, `npm run stylelint`, `npm audit --audit-level=critical --omit=dev`, and the workflow's license-checker step all pass locally. Also addresses MWest2020's PR-#2 review: * Finding #3 (manifest visibility) — added an explicit visibility block to `ApplicationsController::getManifest`'s docblock noting manifests are publicly readable to authenticated users by design, and that future role-scoped manifests must extend BuiltAppRoute (e.g. a `restrictToGroup` property) rather than hardening this endpoint. Forwards the decision to the RBAC spec (PR #6). * Finding #4 (correlation ID) — the catch-all 500 path now generates a 16-hex correlationId, includes it in both the log context and the response envelope, and is covered by a new testGetManifestIncludesCorrelationIdOnInternalError unit test. * Finding #5 (tracking issue) — opened issue #10 to track the 3 remaining deferred items (tasks 4.3, 4.4, 5.2); the other 13 ticks from sections 5/6/7 were landed before merge. Also fixes 9 rule-empty-line-before stylelint violations in ApplicationEditor.vue surfaced once Vue Quality (stylelint) could run.
Quality Report — ConductionNL/openbuilt @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 427/427 | |||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ⏭️ |
Coverage: 0% (0/63 statements)
Quality workflow — 2026-05-11 21:59 UTC
Download the full PDF report from the workflow artifacts.
* Apply bootstrap-openbuilt — core implementation (tasks 1.1–1.5, 2.1–2.4, 3.1–3.2)
Schemas (openbuilt-application-register capability):
- Declare Application schema with manifest blob + slug + version + status
- Add x-openregister-lifecycle (draft → published → archived) on Application —
canonical ADR-031 example, no service class
- Declare BuiltAppRoute schema for slug → applicationUuid index
- Wire BuiltAppRoute upkeep via x-openregister-lifecycle on_transition
(declarative path per design.md Decision 2 / Q1)
- Declare HelloMessage seed schema
Runtime (openbuilt-runtime capability):
- Register GET /api/applications/{slug}/manifest route in appinfo/routes.php
- ApplicationsController::getManifest — thin-glue resolver (slug → BuiltAppRoute
→ Application → manifest blob unwrapped). #[NoAdminRequired] + #[NoCSRFRequired]
per design.md Decision 6
- BuilderHost.vue — nested CnAppRoot mount with key=slug, options.endpoint
redirecting useAppManifest's backend fetch to the per-slug endpoint
(workaround per design.md Decision 4 until chain spec #2 ships)
- ApplicationEditor.vue — textarea-based manifest editor with validateManifest
- src/router/index.js — /applications + /builder/:slug/:pathMatch(.*)? routes
- src/manifests/placeholder.json — bundled placeholder skeleton for useAppManifest
Seed (ADR-001):
- SeedHelloWorld repair step — idempotent seed of canonical hello-world
Application + manifest exercising index/detail/form pages + three sample
HelloMessage objects
- info.xml repair-steps — SeedHelloWorld runs after InitializeSettings
Deferred to follow-up apply: tasks 4.x (verification), 5.x (tests),
6.x (docs), 7.x (i18n).
Refs: openspec/changes/bootstrap-openbuilt/{proposal,specs,design,tasks}.md
* Quality pass — DI refactors, method split, SPDX placement, dep bump (tasks 4.1, 4.2, 4.5, 7.1-7.3)
PHP refactors per "fix all issues" rule (no @SuppressWarnings shortcuts):
- ApplicationsController + SeedHelloWorld: constructor injection of
OCA\OpenRegister\Service\ObjectService (eliminates Server::get static
access). OR is a declared hard dep in info.xml, so injection is the
right pattern per ADR-022 + ADR-003.
- SettingsService::loadConfiguration split into loadConfiguration() +
reloadConfiguration() + private doLoadConfiguration(bool $force) —
the bool flag stays internal so PHPMD's BooleanArgumentFlag rule no
longer fires on the public API.
- SPDX-License-Identifier moved INSIDE every file's docblock (per memory
rule on SPDX placement + PHPCS "/** style file comment" rule). Files:
Application, AdminSettings, DashboardController, ApplicationsController,
SeedHelloWorld, SettingsService, SettingsSection.
- Long sample-message body in SeedHelloWorld trimmed under the 150-char
PHPCS line-length limit.
Dependency bumps:
- @conduction/nextcloud-vue: ^0.1.0-beta.3 → ^1.0.0-beta.30. The 1.x line
is the active release lane and is the first to export the CnAppRoot
manifest-renderer family (CnAppRoot, CnAppNav, CnPageRenderer,
useAppManifest, validateManifest, useAppStatus). 0.1.0-beta.3 did NOT
export them — design.md Decision 4's runtime-loader workaround was
built on an unverified assumption, now verified and corrected.
- @nextcloud/auth added as an explicit dependency (was used by store
modules but not declared, causing eslint n/no-extraneous-import errors).
phpstan.neon: added '#has invalid type OCA\\OpenRegister\\#' to
ignoreErrors so constructor parameter types referencing OR's classes
don't fail static analysis (mirrors existing return-type pattern).
i18n keys (tasks 7.1, 7.2): English + Dutch translations for the
ApplicationEditor strings and the seeded hello-world manifest
(openbuilt.helloworld.menu.*, openbuilt.helloworld.title.*,
openbuilt.editor.help).
Verification status (task 4.x):
- ✓ 4.1 composer phpcs / phpmd / psalm / phpstan — all clean
- ✓ 4.2 npm run lint — clean
- ⊘ 4.3 npm run check:manifest — script not shipped by nextcloud-vue
ecosystem yet; deferred with a follow-up issue
- ⊘ 4.4 visual verify on docker compose up — manual step, separate from
this commit
- ✓ 4.5 ADR-031 service-class gate — confirmed no
ApplicationLifecycleService / ApplicationStateMachine class under
lib/Service/
* Apply bootstrap-openbuilt — docs + minimum tests + capabilities (tasks 4.x, 5.1, 6.x, 7.x)
Docs (Section 6):
- docs/openbuilt-runtime.md — big-picture diagram, manifest endpoint contract,
the bundled-mode + options.endpoint workaround until chain spec #2 ships the
in-memory useAppManifest overload, declarative lifecycle table, file map
- docs/integrator-guide.md — step-by-step for authoring a virtual app by hand
(slug rules, manifest checklist, what doesn't work yet in spec #1)
- openspec/app-config.json — list openbuilt-application-register +
openbuilt-runtime under capabilities
Tests (Section 5 partial):
- tests/unit/Controller/ApplicationsControllerTest.php — 3 PHPUnit tests:
happy path (200 with unwrapped manifest), unknown slug (404 not_found),
inconsistent state (500 inconsistent_state when route missing applicationUuid)
- tests/unit/Repair/SeedHelloWorldTest.php — 3 PHPUnit tests: getName format,
idempotency on re-run, full seed (4 saveObject calls) on fresh install
Deferred (need NC + container):
- 5.2 Integration test for the Application lifecycle (requires container with
OR's lifecycle engine to actually run transitions)
- 5.3 Newman collection (requires running NC instance)
- 5.4 Playwright e2e (requires NC + browser, mounts via docker-compose)
- 4.3 npm run check:manifest — script not yet shipped by @conduction/nextcloud-vue
- 4.4 Visual verify on docker compose up — manual step
tasks.md status: 22/22 implementation + 3/5 verification + 1/4 tests + 4/4 docs +
3/3 i18n boxes checked. Remaining 5 deferred tasks documented inline.
* Fix hydra-gate-10 + hydra-gate-11 security findings surfaced by /opsx-verify
Two pre-existing template inheritances violated ADR-004 hard rules and the
corresponding hydra mechanical gates. Fixed both via the canonical NC
pattern (IInitialState + admin-via-AdminSettings.php-only).
hydra-gate-10 (DOM dataset → IInitialState):
- AdminSettings.php now injects IInitialState and calls
provideInitialState('version', $version) before rendering the template.
- templates/settings/admin.php no longer carries data-version on the mount
div — server data flows via initial-state, not DOM attrs.
- AdminRoot.vue replaces document.getElementById('openbuilt-settings')
.dataset.version with loadState('openbuilt', 'version', 'Unknown').
hydra-gate-11 (admin-router → no admin in vue-router):
- src/router/index.js no longer imports AdminRoot or registers a /settings
route. Admin settings are reached exclusively through Nextcloud's admin
settings framework (/index.php/settings/admin/openbuilt), which goes
through the admin auth gate. The redundant vue-router entry was a
bypass-the-auth-gate regression that the doriath retrospective flagged.
Verification:
- gate-10 grep: no matches
- gate-11 grep: no matches
- composer phpcs/phpmd/psalm/phpstan: all clean
- npm run lint: clean
Surfaced by: /opsx-verify bootstrap-openbuilt
* Runtime smoke test — fix 5 real bugs surfaced by docker exec app:enable + curl manifest endpoint
Verified end-to-end: GET /api/applications/hello-world/manifest returns HTTP 200 with
the seeded hello-world manifest. Smoke test mission complete.
Bug 1 — SettingsService::doLoadConfiguration missing $data + $version args.
OR's importFromApp signature is importFromApp(string $appId, array $data,
string $version, bool $force=false). My call was passing only appId + force,
so importFromApp threw "Argument #2 ($data) not passed". Fixed by reading
lib/Settings/openbuilt_register.json from disk + parsing + passing data+version
(per openregister-wiring-guide.md — the sweep agent missed this step).
Bug 2 — ObjectService::getObjects doesn't exist. The real API is findAll($config)
+ find($id, register, schema). Refactored ApplicationsController and SeedHelloWorld
to the real signatures.
Bug 3 — searchObjects requires NUMERIC register/schema IDs in @self, not slugs.
Slug-to-ID resolution is NOT applied at this layer. Injected RegisterMapper +
SchemaMapper into ApplicationsController to resolve before searchObjects. Found
via direct CLI test: @self with slug strings returns 0 results; @self with
numeric IDs returns the matching object.
Bug 4 — RegisterMapper/SchemaMapper::find with default _multitenancy=true filters
out the lookup. Pass _multitenancy: false on the LOOKUP only (object-level
multitenancy still enforced via searchObjects + the underlying RBAC).
Bug 5 — x-openregister-lifecycle.on_transition.upsert_relation NOT supported by
OR's current engine (design.md OQ-1 confirmed). The Application's declarative
lifecycle declaration is preserved (still ADR-031-compliant when OR ships the
hook), but SeedHelloWorld now explicitly creates the BuiltAppRoute as the
ADR-031 §Exceptions(1) fallback. A BuiltAppRouteSyncListener subscribed to
ObjectLifecycleTransitionedEvent should follow in a separate change.
Plus ObjectEntity UUID extraction now reads $entity->jsonSerialize()['@self']['id']
(__call-based getUuid() is invisible to method_exists, so we read the array).
Tests updated to the new controller signature + searchObjects mock.
Known remaining gaps (smoke-test follow-ups, deferred):
- Openbuilt Register row was NOT auto-created by importFromApp despite schemas
being created. Manually created via REST during smoke test; needs an explicit
createRegisterIfMissing step in InitializeSettings.
- Webpack build fails with @nextcloud/axios exports-field errors when the local
apps-extra/nextcloud-vue/ source-alias is active. Frontend not yet runtime-
validated; backend is.
- BuiltAppRouteSyncListener (the proper ADR-031 §Exceptions(1) PHP fallback for
the missing OR lifecycle hook) — covered only for the seed Application; new
user-created Applications won't get BuiltAppRoute upkeep yet.
All quality tools green: phpcs, phpmd, psalm, phpstan, ESLint.
* tests: expand unit + Newman coverage for bootstrap-openbuilt
- ApplicationsControllerTest: rename happy-path to match spec naming;
add testGetManifestReturns500OnInconsistentState covering the
dangling-applicationUuid branch (Application deleted, route survives).
- SeedHelloWorldTest: tighten fresh-install test to stub jsonSerialize
so it asserts exactly 4 core saves; add new
testRunCreatesBuiltAppRouteWhenApplicationUuidIsExposed covering the
5-save path (Application + BuiltAppRoute + 3 messages) that locks the
design.md Decision 6 lifecycle-hook fallback.
- Postman collection: replace placeholder /status request with three
real assertions — GET hello-world manifest (200 + version/menu/pages),
GET unknown-slug manifest (404 + error=not_found), and GET the
OpenRegister-backed Application listing (>=1 result with
slug=hello-world after seed).
* Replace placeholder PHPUnit tests with real coverage (task 5.1)
- OpenBuiltTest: assert APP_ID + autoload resolves worktree's lib/
- ApplicationsControllerTest: 4 tests on getManifest (200/404/500-x2)
- SeedHelloWorldTest: 4 tests on idempotency + fresh/uuid-exposed paths
- bootstrap-unit.php: pin OCP/NCU PSR-4 + rebuild OpenBuilt classmap from
the worktree's lib/ so tests run in a git-worktree dev setup (the
symlinked vendor/ otherwise resolves classes against a sibling checkout)
- tests/stubs/openregister-stubs.php: fallback ObjectService/Mapper stubs
when OR sources aren't on the autoload path (CI strip-down)
All 13 unit tests pass: ./vendor/bin/phpunit -c phpunit-unit.xml
PHPCS still clean — tests/ are out of phpcs.xml scope.
* tests(e2e): bootstrap Playwright framework
- playwright.config.ts: chromium project, baseURL http://localhost:8080,
basic-auth httpCredentials, OCS-APIRequest header pinned for NC API
calls, headless by default, no webServer (Docker stack is the
documented dev path).
- tests/e2e/bootstrap-openbuilt.e2e.spec.ts: two specs covering the
bootstrap-openbuilt change — (1) renders the three seeded
hello-message titles on the index page, and (2) returns a valid
unwrapped manifest (version/menu/pages) from the public endpoint.
- package.json: add @playwright/test devDep and test:e2e /
test:e2e:install scripts. Browser install is the one-time setup
documented in the spec header.
- .gitignore: ignore playwright-report/, test-results/, .playwright/,
playwright/.cache/.
* tests: expand Newman CRUD + add BuilderHost journey e2e
Linter rewrites that landed after the initial commits:
- openbuilt.postman_collection.json: extend from 3 to 6 requests —
add a full CRUD round-trip on Application objects via OR
(POST/GET/PUT/DELETE on /openregister/api/objects/openbuilt/application)
in addition to the manifest endpoint coverage (200 + 404) and the
seed listing assertion.
- tests/e2e/builder-host.spec.ts: BuilderHost journey covering
REQ-OBR-002 (mount the manifest) and REQ-OBR-003 (forward to detail
pages declared in the manifest). Companion to the
bootstrap-openbuilt.e2e.spec.ts smoke test.
* psalm: suppress UndefinedClass for OR mappers (cross-app deps)
OCA\OpenRegister\Db\RegisterMapper and SchemaMapper are referenced by
ApplicationsController's constructor for the slug→id resolution; they
live in the openregister app and are only present at runtime. Adding
them to the existing OpenRegister suppress list (alongside ObjectService
and ConfigurationService) clears the 4 pre-existing UndefinedClass
errors surfaced during the test-coverage task.
* tests(e2e): add ApplicationEditor + manifest-endpoint specs
Two further e2e specs surfaced by the linter to round out coverage:
- tests/e2e/application-editor.spec.ts: exercises the in-app manifest
editor (data-testid hooks added to ApplicationEditor.vue).
- tests/e2e/manifest-endpoint.spec.ts: API-level coverage of the
/api/applications/{slug}/manifest endpoint (200/404 paths,
unwrapped envelope).
Companion changes:
- ApplicationEditor.vue: add data-testid='openbuilt-editor-textarea'
+ 'openbuilt-editor-save' to make the form addressable from Playwright.
- BuilderHost.vue: add data-testid='openbuilt-builder-host' so the
journey spec can scope its locators to the inner-app root.
* tasks: tick 5.3 (Newman) + 5.4 (Playwright) — coverage landed
* fix(ci+review): regenerate lockfile, address MWest review on PR #2
CI was red on lint-check + License/Security/Vue Quality npm jobs because
`npm ci` refused the out-of-sync lockfile (missing playwright, pinia
v3 transitive deps, etc. after the @conduction/nextcloud-vue
0.1.0-beta.3 → 1.0.0-beta.30 bump). Regenerated package-lock.json from
scratch and confirmed `npm run lint`, `npm run stylelint`,
`npm audit --audit-level=critical --omit=dev`, and the workflow's
license-checker step all pass locally.
Also addresses MWest2020's PR-#2 review:
* Finding #3 (manifest visibility) — added an explicit visibility block
to `ApplicationsController::getManifest`'s docblock noting manifests
are publicly readable to authenticated users by design, and that
future role-scoped manifests must extend BuiltAppRoute (e.g. a
`restrictToGroup` property) rather than hardening this endpoint.
Forwards the decision to the RBAC spec (PR #6).
* Finding #4 (correlation ID) — the catch-all 500 path now generates a
16-hex correlationId, includes it in both the log context and the
response envelope, and is covered by a new
testGetManifestIncludesCorrelationIdOnInternalError unit test.
* Finding #5 (tracking issue) — opened issue #10 to track the 3
remaining deferred items (tasks 4.3, 4.4, 5.2); the other 13 ticks
from sections 5/6/7 were landed before merge.
Also fixes 9 rule-empty-line-before stylelint violations in
ApplicationEditor.vue surfaced once Vue Quality (stylelint) could run.
* fix(tests): replace stdClass mocks with concrete entity mocks
PHPUnit on CI rejects the prior stdClass+addMethods doubles because OR's
RegisterMapper::find() and SchemaMapper::find() are typed `: Register`
and `: Schema`, and ObjectService::saveObject() is typed `: ObjectEntity`.
Locally those return-type checks pass against the anonymous stubs because
OR isn't on the classpath; CI mounts the real classes and raises
PHPUnit\Framework\MockObject\IncompatibleReturnValueException.
Switched both ApplicationsControllerTest and SeedHelloWorldTest to mock
the concrete entity classes (Register, Schema, ObjectEntity). Behaviour
is unchanged; only the mock construction is correct now.
* brand: OpenBuilt icon — white box symbol on cobalt point-up hexagon
Replace the scaffold's rounded-rectangle #0082C9 placeholder with the
Conduction app-icon convention (matches ConductionNL/scholiq):
- img/app-store.svg — point-up hexagon fill #4376FC (Conduction Cobalt),
white `package-variant-closed` box symbol centred via translate(136,136)
scale(10) over the 512×512 viewBox. This is the icon the README + the
Nextcloud app store render.
- img/app.svg — monochrome box symbol, fill currentColor (navbar/light).
- img/app-dark.svg — same symbol, fill #ffffff (dark theme).
Per feedback_brand-rules-strict: pointy-top point-up, solid colours only,
hex polygon background (no rounded rect, no Nextcloud blue). README logo
block already references img/app-store.svg so it now shows the hex.
…elopers Chain spec #4 of 9 (ADR-032). Adds the openspec change with proposal, specs (openbuilt-schema-designer new + openbuilt-runtime modified), design, and tasks. Frontend-only (kind: code). Depends on chain #3 (openregister-runtime-schema-api) for runtime schema CRUD. The designer authors declarative x-openregister-* JSON only — no imperative escape hatches (ADR-031). Phased delivery: v1 ships field editor + lifecycle + relation + widget editors; v1.1 adds aggregation / calculation / notification DSL editors once the shared DSL package is published by chain #3.
…-editor spec(openbuilt-schema-editor): visual schema designer (chain #4 of 9)
|
Not merged in the chain rebase: this PR ships a replacement |
) OpenBuilt now eats its own dog food: instead of a hand-rolled NcContent + MainMenu.vue + vue-router shell, it declares src/manifest.json and mounts @conduction/nextcloud-vue's CnAppRoot — the same renderer it drives for the virtual apps it builds (ADR-024). - src/manifest.json: menu (Dashboard / Virtual apps / Schemas / Exports / Documentation) + pages. OpenBuilt's pages are tooling UIs, not generic register CRUD, so each is type:"custom" and resolves a view via customComponents — Dashboard, the virtual-app manager (list + detail + Editor/History/Diff tabs), the schema designer (/builder/:slug/schemas + a paramless /schemas shortcut defaulting to the hello-world seed), the export-jobs list, and the BuilderHost virtual-app host. dependencies: ["openregister"] drives CnAppRoot's dependency-check phase (the old "OpenRegister is required" empty state moves to the #dependency-missing slot). - src/customComponents.js: the registry mapping the five custom pages to src/views/*.vue. - src/App.vue: thin CnAppRoot wrapper (manifest / customComponents / pageTypes / translate / permissions) — no more bespoke chrome. - src/main.js: builds the vue-router config from manifest.pages (routesFromManifest), registerIcons() / registerTranslations(), fire-and-forget loadTranslations, mounts App.vue. Deletes src/router/index.js and src/navigation/MainMenu.vue. - SchemaDesigner.appSlug now falls back to 'hello-world' for the paramless /schemas shortcut. - tests/vitest/manifest.spec.js: structural manifest checks (menu→page, custom-page→customComponent, no dead registry entries). Stub extended with the manifest-renderer family. 84 Vitest tests pass. Verified in the dev container: /apps/openbuilt/, /applications, /schemas, /exports all render through CnPageRenderer with the manifest-driven CnAppNav. Supersedes the competing editor shells in #4/#8 (their PageDesigner / templates-gallery become customComponents in a follow-up).
|
The Tier-4 shell conversion (#17 → PR #20, merged) is now on New files to bring over: Store layer: this PR's Wiring into the manifest shell:
Close this PR once that lands. |
Grafts the never-merged #4 (feature/spec-openbuilt-page-editor) content onto the current Tier-4 manifest shell, without the competing ApplicationEditor.vue replacement / store refactor that originally blocked it. New: - src/views/PageDesigner.vue — three-pane visual manifest page designer (page list + menu tree / per-page-type sub-editor / validator panel). - src/components/page-editor/** — page-type sub-editors (index, detail, dashboard, form, logs, settings, chat, files, custom, stub), PageListEditor, MenuTreeEditor, and the fields/* builders. - src/composables/{useLivePreview,useManifestValidator,useRegisterPicker}.js - src/store/modules/applicationEditor.js — editor-local manifest state slice (Pinia; CRUD delegated to the shared object store). Wiring: - customComponents.js registers PageDesignerView. - manifest.json adds the /builder/:slug/pages page (after SchemaDesigner, before the BuilderHost wildcard). No top-level menu entry — it is a per-virtual-app sub-page like the schema designer. - ApplicationEditor.vue's Editor/History/Diff tabs are untouched. Tooling: - vuedraggable added to dependencies (used by PageListEditor/MenuTreeEditor). - eslint.config.js: no-unused-vars varsIgnorePattern now also allows leading-underscore discarded-destructure vars. - vitest.config.js: vuedraggable added to inline deps + aliased to a stub; conduction-nextcloud-vue stub gains a useAppManifest export. - tests/vitest/stubs/{nextcloud-auth,nextcloud-router,vuedraggable}.js added. Tests: PageDesigner.spec.js, page-editor component specs, composable specs, the page-designer e2e spec, and the page-editor Postman collection. Quality gates: eslint, stylelint, webpack build, and vitest (173 tests) all green. PHPCS/PHPMD/Psalm/PHPStan and PHPUnit/Newman pre-existing debt on development is untouched (this changeset is frontend-only).
#23) Grafts the never-merged #4 (feature/spec-openbuilt-page-editor) content onto the current Tier-4 manifest shell, without the competing ApplicationEditor.vue replacement / store refactor that originally blocked it. New: - src/views/PageDesigner.vue — three-pane visual manifest page designer (page list + menu tree / per-page-type sub-editor / validator panel). - src/components/page-editor/** — page-type sub-editors (index, detail, dashboard, form, logs, settings, chat, files, custom, stub), PageListEditor, MenuTreeEditor, and the fields/* builders. - src/composables/{useLivePreview,useManifestValidator,useRegisterPicker}.js - src/store/modules/applicationEditor.js — editor-local manifest state slice (Pinia; CRUD delegated to the shared object store). Wiring: - customComponents.js registers PageDesignerView. - manifest.json adds the /builder/:slug/pages page (after SchemaDesigner, before the BuilderHost wildcard). No top-level menu entry — it is a per-virtual-app sub-page like the schema designer. - ApplicationEditor.vue's Editor/History/Diff tabs are untouched. Tooling: - vuedraggable added to dependencies (used by PageListEditor/MenuTreeEditor). - eslint.config.js: no-unused-vars varsIgnorePattern now also allows leading-underscore discarded-destructure vars. - vitest.config.js: vuedraggable added to inline deps + aliased to a stub; conduction-nextcloud-vue stub gains a useAppManifest export. - tests/vitest/stubs/{nextcloud-auth,nextcloud-router,vuedraggable}.js added. Tests: PageDesigner.spec.js, page-editor component specs, composable specs, the page-designer e2e spec, and the page-editor Postman collection. Quality gates: eslint, stylelint, webpack build, and vitest (173 tests) all green. PHPCS/PHPMD/Psalm/PHPStan and PHPUnit/Newman pre-existing debt on development is untouched (this changeset is frontend-only).
|
Superseded by #23, which re-applied openbuilt-page-editor (PageDesigner.vue + the page-editor builder components + composables + applicationEditor store) onto the merged Tier-4 shell, wired as the |
Summary
Spec #5 of the 9-spec OpenBuilt chain. Adds the visual manifest/page designer that replaces spec #1's JSON textarea with a graphical builder targeting the canonical 9-page-type enum (
@conduction/nextcloud-vue/src/schemas/app-manifest.schema.jsonv1.4.0+).OpenSpec artifacts only — no implementation. Implementation lands in a follow-on PR via
/opsx-apply.What this spec ships (at apply time)
src/views/PageDesigner.vue— three-pane visual editor (page list + menu tree | per-type sub-editor | live preview / errors).IndexPageEditor.vue,DetailPageEditor.vue,DashboardPageEditor.vue,LogsPageEditor.vue,SettingsPageEditor.vue,ChatPageEditor.vue,FilesPageEditor.vue,FormPageEditor.vue,CustomPageEditor.vue.$deffield builders (ColumnBuilder,ActionBuilder,WidgetBuilder,LayoutItemBuilder,FormFieldBuilder,SidebarSectionBuilder,SidebarTabBuilder).MenuTreeEditor.vue(drag-reorder, depth-2 cap) +PageListEditor.vue(uniqueness + route-pattern validation).useManifestValidator.js(debounced 300msvalidateManifest) +useLivePreview.js(feature-detects chain spec Apply bootstrap-openbuilt — core implementation (sections 1–3) #2's in-memoryuseAppManifestoverload, degrades to "save & reload" when absent).ApplicationEditor.vuereshaped to a tabbed shell — "Design" (default) + "Raw JSON" (the existing spec-1 textarea, preserved as integrator fallback).l10n/en.json+l10n/nl.json.Capabilities
openbuilt-page-designer(11 requirements)openbuilt-runtime(REQ-OBR-005 reshaped from textarea-only to tabbed Design + Raw JSON shell)Architectural commitments
/api/objects/openbuilt/application/{uuid}), reusing the spec-1 store action.design.md.Chain coordination
depends_on: [bootstrap-openbuilt]— requires the Application register + the OR-REST CRUD path from spec Bump the npm_and_yarn group across 1 directory with 8 updates #1.nextcloud-vue-in-memory-manifest) — live-preview pane feature-detects spec Apply bootstrap-openbuilt — core implementation (sections 1–3) #2's overload at runtime and degrades gracefully when absent, so this spec ships independently.Validation
openspec validate openbuilt-page-editor --strict→ Change 'openbuilt-page-editor' is validTest plan
openspec validate openbuilt-page-editor --strictpasseskind: code,depends_on: [bootstrap-openbuilt], full 9-spec chain