Integrator guide, authoring a virtual app by hand
This guide creates a virtual app by writing its JSON directly, instead of using the visual designers. You write the manifest and the runtime renders it.
You do not have to work this way. Buildiq ships visual designers for pages, schemas and walkthroughs, and most apps are built entirely through them. Start at create a virtual app if that is what you want.
Author by hand when you need something the designers do not expose yet, when you are diffing or reviewing a manifest, or when you are generating apps from a script.
What you author
A virtual app is one record in Buildiq's Application OR schema. The shape is:
{
"slug": "permit-tracker", // kebab-case, 2–48 chars
"name": "Permit Tracker",
"description": "Track building permits through review stages.",
"version": "0.1.0",
"status": "draft", // draft → published → archived
"manifest": {
"version": "1.0.0",
"dependencies": ["openregister"],
"menu": [ ... ],
"pages": [ ... ]
}
}
The manifest object validates against @conduction/nextcloud-vue/src/schemas/app-manifest.schema.json. The closed type enum for pages is index | detail | dashboard | logs | settings | chat | files | form | custom.
Creating a virtual app with the wizard
For most operators the visual wizard at Virtual apps → New application is the quicker path. It walks you through three steps in one round-trip:
- Identity — pick a slug + human-readable name + description.
- Versions — accept the default chain (
development → staging → production) or adjust it. Each version maps to its own per-version register (buildiq-{slug}-{versionSlug}) so production data is physically isolated from staging and development. The wizard's chain editor enforces ADR-002's linear-chain rule (no fan-out, no cycles, exactly one terminalproductiontier). - Permissions — pick the owners / editors / viewers. The caller is
pre-filled into
owners. Groupgroup:*becomes the "all signed-in users" wildcard per REQ-OBRBAC-004.
On submit the wizard atomically creates the Application record, the N
ApplicationVersion rows, and N per-version registers — and seeds each
register with the default schema set (hello-message by default) under the
namespaced slug {appSlug}-{versionSlug}-{originalSchemaSlug}. The manifest's
config.register and config.schema pointers are rewritten to match
(buildiq-{slug}-{tier} and {appSlug}-{tier}-hello-message respectively)
so the insights service and the runtime each address the right per-tier slice.
Empty-state landing — fresh installs no longer auto-seed a hello-world
Application (the legacy SeedHelloWorld repair step was retired by
buildiq-versioning-model). New deploys land the admin on an empty Virtual
apps index with a CTA pointing at the wizard. Pre-existing installs are not
affected; the migration step MigrateToVersionedModel only fires when
pre-spec-C Application rows are present and is idempotent on re-runs.
For further reading on what each step writes through to OR, see
openbuild-runtime.md and the wizard chain spec
openbuild-app-creation-wizard,
archived on 2026-05-17.
Editing session: undo/redo (page designer & schema designer)
Both visual designers — the page designer (/builder/{slug}/pages) and the
schema designer (/builder/{slug}/schemas/{schemaId}) — offer editor-level
undo/redo over your in-flight (unsaved) edits, on top of the toolbar's
Save action:
- Reach it three ways: the toolbar's Undo/Redo buttons (disabled at the
ends of the stack),
Ctrl+Z(undo) andCtrl+Shift+ZorCtrl+Y(redo), or theCmdequivalents on macOS. - Native text-field undo wins while typing. Pressing
Ctrl+Zinside an<input>,<textarea>,<select>, or any contenteditable field triggers the browser's own text-editing undo, not the designer's draft-level undo — so correcting a typo never accidentally reverts an earlier structural edit. - History survives navigating between pages/sub-editors. Selecting a different page in the page designer (or scrolling between sections in the schema designer) is navigation, not an edit, so your undo stack stays intact across it.
- Save is a session boundary, not an undo boundary. Once you save, Undo
and Redo both go back to disabled — the saved state becomes the new
baseline. Editor-level undo only ever covers the current, unsaved
editing session; to restore an earlier saved state, use Version
History (
VersionHistory.vue/ the rollback action), which is unaffected by this feature. - Switching app or version also starts a new session. Opening a
different app, or switching
?_version=to another version, resets the undo/redo stack to that version's current manifest — you can never accidentally undo into a different version's draft. - A raw-JSON paste (the "Manifest" tab's textarea) counts as one edit. If you paste a whole new manifest there and later edit it further in the page designer, one undo restores the complete pre-paste manifest — it does not unwind field-by-field.
- Schema designer parity: "Discard staged edits" is itself undoable — if
you discard by mistake,
Ctrl+Z(or the Undo button) brings your discarded edits straight back, in one step.
This is in-memory only (depth 100 per session) and issues no network request — it never competes with Save.
Step-by-step (manual / integrator path)
- Pick a slug. Must be kebab-case, 2–48 chars, unique within your organisation. The synthetic appId in CnAppRoot becomes
buildiq-${slug}. - Design your schemas in OpenRegister directly (the Buildiq schema editor lands in chain spec
buildiq-schema-editor). At minimum: one schema per primary entity your app shows. - Author the manifest as JSON. The canonical example is the seeded
hello-worldApplication — open it in Buildiq's manifest editor (top-bar Buildiq entry → Virtual apps → hello-world) and read its manifest. - Save as
draftwhile iterating. The textarea editor validates each save against the canonical schema; you see the failing JSON path on save error. - Transition to
publishedwhen ready (via OR's lifecycle endpoint or the editor's Publish action — landing in chain specbuildiq-versioning). On publish, Buildiq's lifecycle creates the correspondingBuiltAppRouteso/builder/{slug}becomes reachable.
Manifest checklist
Per ADR-024:
version(semver) — your app's content versiondependencies— list of NC app IDs that must be installed (almost always["openregister"])menu[]— at least one entry; supports one level ofchildren[]pages[]— at least one entry; every page'sidMUST be unique and match a vue-router route namelabel/titlestrings are i18n KEYS, not literals. The consuming app'st()resolves them. Use kebab.dot.notation:myapp.permits.title.list.
Per ADR-007:
- Every translation key MUST exist in
l10n/en.jsonANDl10n/nl.jsonof the Buildiq repo (until per-virtual-app translations land in chain specbuildiq-page-editor).
Reading the seed manifest
The seeded hello-world Application is the canonical reference. Its manifest exercises:
- index page → drives
CnIndexPagewithregister: buildiq,schema: hello-message, three columns - detail page → drives
CnDetailPagekeyed on:id - form page → drives
CnFormPagewithmode: createandsubmitEndpointgoing to OR's REST
See lib/Command/SeedHelloWorldFixture.php for the full JSON.
When you hit a limit
The closed type enum can't be extended from a manifest — adding a new page type requires a library-level openspec change in @conduction/nextcloud-vue. If you need something the four built-in types can't express:
- Confirm the requirement isn't satisfied by
form(the most flexible built-in). - Open an issue on
ConductionNL/nextcloud-vuedescribing the new page type's shape. - As an interim, mount a custom Vue component via
type: "custom"+component: "MyCustomPage"and register the component in Buildiq'scustomComponentsmap. (Note: spec #1 only ships the built-in types — thecustomComponentsregistry surface lands when a real consumer needs it.)
What does NOT work yet (spec #1 limitations)
- No visual editor — JSON textarea only. Visual editor: chain spec
buildiq-page-editor. - No schema designer — schemas must be authored in
lib/Settings/openbuild_register.jsonand imported via the repair step. Runtime schema authoring: chain specopenregister-runtime-schema-api. - No draft preview — only
publishedapps appear at/builder/{slug}. Draft preview: chain specbuildiq-versioning. - No per-app permissions — auth-only visibility for v1; everyone in your organisation sees every virtual app. Per-app RBAC: chain spec
buildiq-rbac. - No export to a real Nextcloud app — virtual-only. Export pipeline: chain spec
buildiq-export-to-real-app.
If any of these limitations block your project, talk to Conduction — chain spec prioritisation can shift.