feat(apps): copy an app, a page or a registration form - #1026
Merged
Merged
Conversation
POST /api/applications/{slug}/copy captures the app's companion schemas
and current manifest in memory, de-namespaced exactly as Save as template
does (AppTemplateCapture, the PHP twin of templateCapture.js), and installs
them through the template seam. Same gates as cloning a template, plus
owner or editor of the source; no records are copied.
A dialog asks a name and a slug, suggests 'Copy of <name>', and opens the copy once the server made it. Offered to an owner or editor who is an administrator, the people the server lets copy.
Each page row gets a copy button that inserts a deep copy below it, with a unique id and route (-copy, then -copy-2) and the title 'Copy of ...'. The copy is not added to the menu.
The copy keeps the fields, steps, rules, sections and presets for the same type, is named 'Copy of ...', is a draft and never the default, and opens in the editor. The body is checked against the real registrationForm schema with Ajv in the test and with Opis in the lane.
…te built Strings in English and Dutch, a docs section on copying, an API-level e2e for Copy app. The spec folds into openspec/specs/copy-app-page-and-form with one design correction: Copy app is on the detail page, not on the app cards, because a card is a single link.
…y has run T03's e2e has not run against a live stack, so the change goes back under openspec/changes, @SPEC tags point at the change's spec, and the app-duplicate row stays building. The e2e file is prettier-formatted.
…and-page # Conflicts: # l10n/en.js # l10n/en.json # l10n/nl.js # l10n/nl.json
Contributor
Quality Report — ConductionNL/buildiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| check-gitignore | ✅ | ||||
| check-nc-floor | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 752/752 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development. |
||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-02 05:15 UTC
Download the full PDF report from the workflow artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Change
apps-copy-app-and-page(not archived: T03's Playwright copy has not run against a live stack yet). A maker can copy a whole app, one page, or one registration form, and the copy is independent of its source from the moment it exists.What is built:
lib/Service/AppTemplateCapture.phpports the de-namespace step ofsrc/services/templateCapture.jsto PHP, so a template array can be built from an app's current version on the server. Its PHPUnit cases are the ones oftemplateCapture.spec.js, so both sides agree.POST /api/applications/{slug}/copyinApplicationsController. A signed-in administrator who owns or edits the source app; rate limited likefrom-template; a taken slug is the install seam's 409. It installs throughinstallFromTemplateArray(), so the copy gets its own register, the source's schemas and its current manifest, and no records.ApplicationDetailActions.vue, with a name and slug dialog insrc/modals/CopyAppDialog.vue; the copy opens once it is made. Design correction: not on the app cards. A card is one link, and a button inside a link is announced wrongly by screen readers and swallows the card's click.src/services/pageCopy.js): a unique id and route, "Copy of" the title, inserted below the source.src/services/formCopy.js): a draft for the same type value, never the default. The body is validated against the realregistrationFormschema fragment (Ajv in Vitest; Opis in the lane: valid, and a wrong-typed control invalid).docs/tutorials/admin/02-template-catalogue.md.Still open, and why the change is not archived: T03's Playwright test
tests/e2e/copy-app-and-page.spec.ts(copies the seeded Hello World app at API level, then a second copy on the same slug must be refused) has not run against a live stack. The parity rowapp-duplicatestaysbuildinguntil it has;form-copyisbuilt(its requirements are covered by Vitest and their e2e exclusions carry reasons).Tests, each red before its code (logs kept in the lane):
AppTemplateCaptureTest(testDeNamespacesSchemasAndEveryManifestReference, testCaptureThenCloneIsACleanRename, testAnUnprefixedSchemaIsCapturedUnchanged, testACollisionNamesBothSchemas, testNoRowsAndNoMutation).CopyApplicationTest(testAnEditorCopiesTheApp, testAViewerCannotCopyTheApp, testANonAdminOwnerIsRefused, testATakenSlugIsRefused, testCopyIsRateLimitedLikeFromTemplate).tests/modals/CopyAppDialog.spec.js,tests/services/pageCopy.spec.js,tests/services/formCopy.spec.js.Live check: as an admin,
POST /index.php/apps/buildiq/api/applications/hello-world/copywith{"name": "Copy", "slug": "copy-1"}answers 201;GET .../applications/copy-1/manifestlists the same page ids as hello-world; the same POST again answers 409. In the UI: an app's detail page, Actions, Copy app.Inherited, not fixed here: the vendored hydra-gates in
composer check:strictfail gate-101 on the five buildiq schemas that carry reasoned no-demo skips since #853; CI's required gate run (hydra-gates@main) honours the reasons.🤖 Generated with Claude Code