Skip to content

feat(openspec): openbuilt-page-editor — visual manifest designer (chain spec #5)#4

Closed
rubenvdlinde wants to merge 17 commits into
developmentfrom
feature/spec-openbuilt-page-editor
Closed

feat(openspec): openbuilt-page-editor — visual manifest designer (chain spec #5)#4
rubenvdlinde wants to merge 17 commits into
developmentfrom
feature/spec-openbuilt-page-editor

Conversation

@rubenvdlinde
Copy link
Copy Markdown
Contributor

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.json v1.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).
  • One Vue sub-editor per canonical page type: IndexPageEditor.vue, DetailPageEditor.vue, DashboardPageEditor.vue, LogsPageEditor.vue, SettingsPageEditor.vue, ChatPageEditor.vue, FilesPageEditor.vue, FormPageEditor.vue, CustomPageEditor.vue.
  • Shared $def field builders (ColumnBuilder, ActionBuilder, WidgetBuilder, LayoutItemBuilder, FormFieldBuilder, SidebarSectionBuilder, SidebarTabBuilder).
  • MenuTreeEditor.vue (drag-reorder, depth-2 cap) + PageListEditor.vue (uniqueness + route-pattern validation).
  • useManifestValidator.js (debounced 300ms validateManifest) + useLivePreview.js (feature-detects chain spec Apply bootstrap-openbuilt — core implementation (sections 1–3) #2's in-memory useAppManifest overload, degrades to "save & reload" when absent).
  • ApplicationEditor.vue reshaped to a tabbed shell — "Design" (default) + "Raw JSON" (the existing spec-1 textarea, preserved as integrator fallback).
  • i18n strings in l10n/en.json + l10n/nl.json.

Capabilities

  • NEW openbuilt-page-designer (11 requirements)
  • MODIFIED openbuilt-runtime (REQ-OBR-005 reshaped from textarea-only to tabbed Design + Raw JSON shell)

Architectural commitments

  • ADR-022 — no per-app REST wrappers; save path PUTs via OR REST (/api/objects/openbuilt/application/{uuid}), reusing the spec-1 store action.
  • ADR-024 — editor output validates against the canonical manifest schema; closed 9-type enum is exposed exactly.
  • ADR-031 — no service classes; the manifest itself is the declarative artefact. Explicit call-out in design.md.
  • No backend changes — strictly frontend.

Chain coordination

Validation

  • openspec validate openbuilt-page-editor --strictChange 'openbuilt-page-editor' is valid

Test plan

  • openspec validate openbuilt-page-editor --strict passes
  • Proposal frontmatter declares kind: code, depends_on: [bootstrap-openbuilt], full 9-spec chain
  • Specs cover all 9 canonical page types
  • Design.md addresses sub-editor decomposition, drag-drop reuse, live-preview chain-Apply bootstrap-openbuilt — core implementation (sections 1–3) #2 dependency, validator strategy, custom-page deferral
  • Tasks.md sequenced by dependency (foundations → builders → sub-editors → view → i18n → tests)
  • No backend / route / schema / migration tasks

….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.
@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuilt @ 9621ab1

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.

@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuilt @ a1f3813

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).
@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuilt @ cde3e77

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.

rubenvdlinde added a commit that referenced this pull request May 11, 2026
…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.
@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuilt @ 0266036

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.
@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuilt @ 393b5fa

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.
@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuilt @ 6f5a422

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.
@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuilt @ c80de70

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.

@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuilt @ 66a223d

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.

rubenvdlinde added a commit that referenced this pull request May 11, 2026
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.
@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuilt @ dc1c633

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.

rubenvdlinde added a commit that referenced this pull request May 12, 2026
* 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.
rubenvdlinde added a commit that referenced this pull request May 12, 2026
…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.
rubenvdlinde added a commit that referenced this pull request May 12, 2026
…-editor

spec(openbuilt-schema-editor): visual schema designer (chain #4 of 9)
@rubenvdlinde
Copy link
Copy Markdown
Contributor Author

Not merged in the chain rebase: this PR ships a replacement ApplicationEditor.vue shell (and a store refactor) that conflicts structurally with the merged list-view + Editor/History/Diff-tabs + RBAC-gated shell now on development. The conflict is being resolved properly via the Tier-4 shell conversion tracked in #17 — the self-contained pieces of this PR (PageDesigner/sub-editors for #4; templates gallery + cloneFromTemplate for #8) will land there as customComponents / type: custom pages. Keeping this open as the source reference until #17's PR supersedes it.

rubenvdlinde added a commit that referenced this pull request May 12, 2026
)

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).
@rubenvdlinde
Copy link
Copy Markdown
Contributor Author

The Tier-4 shell conversion (#17 → PR #20, merged) is now on development, so the structural blocker is gone. Remaining work to land this PR's content (best done by re-applying the spec onto current development via opsx-apply openbuilt-page-editor):

New files to bring over: src/views/PageDesigner.vue, src/components/page-editor/StubPageEditor.vue + src/components/page-editor/fields/*.vue (7 builders), src/composables/{useLivePreview,useManifestValidator,useRegisterPicker}.js, the matching tests.

Store layer: this PR's src/store/modules/applicationEditor.js + object.js/store.js changes conflict with development's store (schema-editor + the merged shell). Either rebase the applicationEditor store onto development's store, or — if PageDesigner can be made to use the existing object store — drop it.

Wiring into the manifest shell:

  • src/customComponents.js — register PageDesignerView.
  • src/manifest.json — add a page for it: either a top-level { id: "PageDesigner", route: "/builder/:slug/pages", type: "custom", component: "PageDesignerView" }, or wire it as a "Design" sidebar tab inside the VirtualApps (ApplicationEditor) detail. The old src/router/index.js edits in this PR are obsolete.
  • Drop this PR's competing ApplicationEditor.vue replacement (the merged shell already has the list + Editor/History/Diff tabs + RBAC + export).

Close this PR once that lands.

rubenvdlinde added a commit that referenced this pull request May 12, 2026
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).
rubenvdlinde added a commit that referenced this pull request May 12, 2026
#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).
@rubenvdlinde
Copy link
Copy Markdown
Contributor Author

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 /builder/:slug/pages manifest page. Merged to development. The remaining load/save plumbing is tracked in #26.

@rubenvdlinde rubenvdlinde deleted the feature/spec-openbuilt-page-editor branch May 12, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant