Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
a05919d
Apply bootstrap-openbuilt — core implementation (sections 1–3) (#2)
rubenvdlinde May 12, 2026
2515e7b
spec: openbuilt-versioning — draft/publish/rollback + manifest snapshots
rubenvdlinde May 11, 2026
a8ebd45
Apply openbuilt-versioning — backend (schema, listener, diff endpoint…
rubenvdlinde May 11, 2026
66ea58e
Apply openbuilt-versioning — frontend SFCs + review fixes
rubenvdlinde May 11, 2026
3e244d8
test(openbuilt-versioning): PHPUnit listener + diff controller + seed…
rubenvdlinde May 11, 2026
8c5af5b
test(openbuilt-versioning): integration publish→rollback + Vitest set…
rubenvdlinde May 11, 2026
217128b
test(openbuilt-versioning): Playwright version-rollback + Newman publ…
rubenvdlinde May 11, 2026
e5a4ff8
fix(ci): regenerate lockfile + stylelint auto-fixes
rubenvdlinde May 11, 2026
d59c43d
fix(quality): register ObjectTransitionedEvent in psalm UndefinedClas…
rubenvdlinde May 12, 2026
83f7862
Merge pull request #5 from ConductionNL/feature/spec-openbuilt-versio…
rubenvdlinde May 12, 2026
a113bd5
spec(openbuilt-rbac): add per-virtual-app RBAC change artifacts
rubenvdlinde May 11, 2026
ad1078d
Apply openbuilt-rbac change — controller enforcement, frontend gating…
rubenvdlinde May 11, 2026
efd59eb
fix(rbac): close list-endpoint IDOR + audit admin bypass to OR trail
rubenvdlinde May 11, 2026
c17ac34
fix(rbac): document <permission> navigation gate gap, link upstream N…
rubenvdlinde May 11, 2026
d847290
refactor(i18n): flip openbuilt.rbac.* dotted keys to plain English
rubenvdlinde May 11, 2026
3f259fd
fix(quality): clean up PHPCS errors + phpmd thresholds + psalm OR stubs
rubenvdlinde May 11, 2026
fd6e996
test(rbac): add Newman collection covering REQ-OBRBAC-002/003/005
rubenvdlinde May 11, 2026
fdfae30
test(rbac): add Playwright e2e — non-member blackout (REQ-OBRBAC-002/…
rubenvdlinde May 11, 2026
5715c1c
test(rbac): add Vitest — useRole (19) + PermissionsModal (8) = 27 tests
rubenvdlinde May 11, 2026
be76c74
fix(ci): regenerate lockfile + stylelint auto-fixes
rubenvdlinde May 11, 2026
3921843
fix(quality): raise ExcessiveClassComplexity threshold for the integr…
rubenvdlinde May 12, 2026
713c581
Merge pull request #6 from ConductionNL/feature/spec-openbuilt-rbac
rubenvdlinde May 12, 2026
e64bfb4
spec(openbuilt-schema-editor): visual schema designer for citizen dev…
rubenvdlinde May 11, 2026
4bc8e9f
Apply schema-editor source — store-based, hybrid register model
rubenvdlinde May 11, 2026
dabd032
test(schema-editor): set up Vitest + add SchemaListPanel + FieldEdito…
rubenvdlinde May 11, 2026
804eae6
test(schema-editor): add LifecycleEditor + SchemaDesigner unit specs
rubenvdlinde May 11, 2026
a07b4cb
test(schema-editor): add Newman + Playwright e2e + lint pass
rubenvdlinde May 11, 2026
58e6c1b
fix(ci): regenerate lockfile + stylelint auto-fixes
rubenvdlinde May 11, 2026
ebc8ffd
Merge pull request #3 from ConductionNL/feature/spec-openbuilt-schema…
rubenvdlinde May 12, 2026
54e93c2
spec(openbuilt-export-to-real-app): Phase-2 export pipeline
rubenvdlinde May 11, 2026
3267bca
feat(openbuilt-export-to-real-app): salvage Phase-2 export pipeline (…
rubenvdlinde May 11, 2026
e540d3a
fix(export): pascalCase camelCase boundaries + drop invalid ISimpleFo…
rubenvdlinde May 11, 2026
b1e26ee
fix(export): drive ExportJob lifecycle via OR TransitionEngine (CRITI…
rubenvdlinde May 11, 2026
27807c7
fix(export): close IDOR + add route-auth on ExportsController (CRITICAL)
rubenvdlinde May 11, 2026
b288afb
fix(export): make exported app a Tier-4 manifest consumer (ADR-024)
rubenvdlinde May 11, 2026
f77ad5e
fix(export): clean up PHPCS/PHPMD — named params, complexity split, e…
rubenvdlinde May 11, 2026
0369f21
fix(tests): make bootstrap work outside the docker container
rubenvdlinde May 11, 2026
0abbd2d
fix(export): drop unused container dep + green PHPStan
rubenvdlinde May 11, 2026
2943aab
test(exporter): cover PAT-handling contract for ExportJob + GitHub se…
rubenvdlinde May 11, 2026
9b74035
test(exporter): cover RunExportJob lifecycle + PAT-clear contract
rubenvdlinde May 11, 2026
ea34cc0
test(exporter): cover ExportsController submit/download HTTP surface
rubenvdlinde May 11, 2026
531f489
test(exporter): add Newman collection for export pipeline
rubenvdlinde May 11, 2026
7ad9d80
test(exporter): add Playwright e2e for ZIP export download
rubenvdlinde May 11, 2026
f026306
fix(ci): regenerate lockfile + stylelint and lint auto-fixes
rubenvdlinde May 11, 2026
9d2250a
Merge pull request #7 from ConductionNL/feature/spec-openbuilt-export…
rubenvdlinde May 12, 2026
5e9ffa4
build: fix frontend build (axios pin + SCSS + nc-vue beta.31) (#15)
rubenvdlinde May 12, 2026
c279bd1
fix: mount the app independently of translation loading (#16)
rubenvdlinde May 12, 2026
7aa2a1e
feat(listener): upsert the BuiltAppRoute on Application publish (#18)
rubenvdlinde May 12, 2026
6d7ebdb
feat: convert OpenBuilt's own shell to a manifest-driven Tier-4 app (…
rubenvdlinde May 12, 2026
3a071f2
chore(openspec): archive the 5 merged chain changes (#21)
rubenvdlinde May 12, 2026
20238ae
feat: re-apply openbuilt-templates-marketplace onto the Tier-4 shell …
rubenvdlinde May 12, 2026
c97f964
feat: re-apply openbuilt-page-editor onto the Tier-4 shell (closes #4…
rubenvdlinde May 12, 2026
b5df1e1
chore(openspec): archive openbuilt-templates-marketplace + openbuilt-…
rubenvdlinde May 12, 2026
853f70d
feat: adopt the Features & Roadmap menu (#25)
rubenvdlinde May 12, 2026
35f4046
feat: wire PageDesigner's manifest load/save (closes #26) (#27)
rubenvdlinde May 12, 2026
6b3c2ef
fix(ci): mock real OpenRegister classes in PHPUnit, sqlite for integr…
rubenvdlinde May 12, 2026
6591806
feat(mcp-tools): OpenBuiltToolProvider — AI companion MCP tools (MVP …
rubenvdlinde May 12, 2026
9b30df5
fix: white fill on app.svg so the navigation header icon renders corr…
rubenvdlinde May 12, 2026
88b0f5c
ci: install OpenRegister `development` (runtime-schema-API + lifecycl…
rubenvdlinde May 12, 2026
5b9de69
feat(dashboard): proper type:"dashboard" manifest page with OR-backed…
rubenvdlinde May 12, 2026
7f3fe95
test(seed): assert the hello-world manifest blob is structurally vali…
rubenvdlinde May 12, 2026
9b9168a
feat(page-editor): v1.1 — structured Logs/Settings/Chat/Files/Custom …
rubenvdlinde May 12, 2026
057b5de
docs(journeydoc): bootstrap capture-driven user documentation
rubenvdlinde May 12, 2026
6584ff5
feat(virtual-apps): CnIndexPage (custom cards, card-grid default) + C…
rubenvdlinde May 12, 2026
4da1dc9
test(virtual-apps): ApplicationCard spec + lint fixes; drop NcAppCont…
rubenvdlinde May 12, 2026
1e3578b
chore: remove orphaned src/views/settings/UserSettings.vue
rubenvdlinde May 12, 2026
7b8f34c
Merge branch 'feature/virtual-apps-index-detail-v2' into development
rubenvdlinde May 12, 2026
9d1d46d
Merge branch 'feat/manifest-nc-vue-beta38' (remove orphaned UserSetti…
rubenvdlinde May 12, 2026
f2da4ff
Merge branch 'feature/journeydoc-init' into development
rubenvdlinde May 12, 2026
c9b1716
chore(deps): bump @conduction/nextcloud-vue to ^1.0.0-beta.40
rubenvdlinde May 12, 2026
0a69645
Merge pull request #41 from ConductionNL/chore/bump-nc-vue-beta40
rubenvdlinde May 12, 2026
6a593d0
chore(docs): bump @conduction/docusaurus-preset to 1.5.1 in lockfile …
rubenvdlinde May 12, 2026
f858cf6
fix(docs): MDX-safe TODO placeholders in journeydoc tutorial skeleton…
rubenvdlinde May 12, 2026
81b2444
chore(docs): bump @conduction/docusaurus-preset to 2.6.1 (#45)
rubenvdlinde May 12, 2026
7e0dbbe
ci(newman): seed the OpenBuilt registers + hello-world app before the…
rubenvdlinde May 13, 2026
d8d32d2
Merge origin/development
rubenvdlinde May 13, 2026
717fc59
ci: keep PHPUnit (green); disable Newman until SeedHelloWorld's OR ru…
rubenvdlinde May 13, 2026
ca3c3e1
docs: fill journeydoc tutorials (content + capture spec + screenshots…
rubenvdlinde May 13, 2026
c8bf155
fix: detail-page routing + Newman collections (#33) (#48)
rubenvdlinde May 13, 2026
71e6d50
fix(rbac): namespace user-UID vs group-GID principals in permissions …
rubenvdlinde May 13, 2026
86af75c
fix(scaffold): ConfigurationService::importFromApp 4-arg signature in…
rubenvdlinde May 13, 2026
048566d
ci(docs): revert deploy source to documentation branch (docs CI separ…
rubenvdlinde May 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
27 changes: 25 additions & 2 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,29 @@ jobs:
enable-frontend: true
enable-eslint: true
enable-phpunit: true
enable-newman: true
additional-apps: '[{"repo":"ConductionNL/openregister","app":"openregister","ref":"main"}]'
# Newman stays disabled while the openbuilt#33 fixes are in flight.
# The collection bugs in this PR are fixed (uuid `@self.id` extraction,
# `export-job` slug correction, hello-world `@self.slug` fallback). But
# an upstream OR-side infrastructure blocker still prevents the seed
# from running in CI:
#
# 1. `OpenBuilt: SeedHelloWorld failed: Call to undefined function
# React\Async\await()` — OR's runtime-schema-API on `development`
# pulls `react/async` but the composer dependency isn't surfacing
# in the CI install, so the function isn't autoloaded.
# 2. SQLite (`database: sqlite`) trips
# `[PermissionHandler] no such function: REGEXP` from OR's
# MagicMapper. The query uses MySQL's REGEXP operator which
# SQLite doesn't ship.
#
# File those as their own issues; once both are unblocked, flip this
# back to `true` and re-evaluate.
enable-newman: false
database: sqlite
newman-seed-command: 'php occ app:disable openbuilt && php occ app:enable openbuilt'
# OpenRegister `development` carries the runtime-schema-API (auto-create
# Register on application-type imports — OR PR #1464) and the lifecycle
# engine (ObjectTransitionedEvent) that OpenBuilt's repair steps + the
# Newman publish step need; `main` predates both. See openbuilt#29.
additional-apps: '[{"repo":"ConductionNL/openregister","app":"openregister","ref":"development"}]'
enable-sbom: true
2 changes: 1 addition & 1 deletion .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ jobs:
deploy:
uses: ConductionNL/.github/.github/workflows/documentation.yml@main
with:
cname: openbuilt.app
cname: openbuilt.conduction.nl
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ phpqa_output.log
**/update*Settings*
**/rebase*
**/setup*
!tests/vitest/setup.js
!tests/e2e/global-setup.ts

# Temporary test files that shouldn't be committed
simple-solr-test.php
Expand Down Expand Up @@ -88,3 +90,9 @@ openspec/test-site-results/**/*.jpg
openspec/test-site-results/**/*.jpeg
openspec/test-site-results/**/*.gif
openspec/test-site-results/**/*.webp

# Playwright artefacts (e2e tests live in tests/e2e/, the artefacts do not).
/playwright-report/
/test-results/
/.playwright/
/playwright/.cache/
34 changes: 33 additions & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Vrij en open source onder de EUPL-1.2-licentie.

**Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl.
]]></description>
<version>0.1.0</version>
<version>0.2.0</version>
<licence>agpl</licence>
<author mail="info@conduction.nl" homepage="https://www.conduction.nl/">Conduction</author>
<namespace>OpenBuilt</namespace>
Expand All @@ -44,6 +44,28 @@ Vrij en open source onder de EUPL-1.2-licentie.
<php min-version="8.1"/>
</dependencies>

<!--
openbuilt.use navigation-entry gate (REQ-OBRBAC-006).

SPEC INTENT: REQ-OBRBAC-006 calls for a <permission>openbuilt.use</permission>
child on the <navigation> element so admins can restrict the top-bar
entry to selected groups. We tried shipping that element and verified on
2026-05-11 (running "occ app:enable openbuilt" with the force flag, on
Nextcloud 32) that the upstream apps/info.xsd schema rejects the
<permission> child ("appinfo file cannot be read"). The schema does not
yet expose this sub-element; an upstream issue is filed at:
https://github.com/nextcloud/server/issues/60310

FALLBACK: per design.md Decision 4 we rely on Nextcloud's standard
app-level group restriction ("occ app:enable openbuilt" with the groups
flag, e.g. "groups team-alpha"), which configures group visibility
outside info.xml. Per-Application RBAC enforced server-side by
ApplicationsController::getManifest + ::listMine (REQ-OBRBAC-002 /
REQ-OBR-006) remains the load-bearing security boundary; this gate is
coarse top-bar visibility only.

See docs/openbuilt-rbac.md for the operator guide.
-->
<navigations>
<navigation>
<id>openbuilt</id>
Expand All @@ -61,9 +83,19 @@ Vrij en open source onder de EUPL-1.2-licentie.
<repair-steps>
<install>
<step>OCA\OpenBuilt\Repair\InitializeSettings</step>
<step>OCA\OpenBuilt\Repair\SeedHelloWorld</step>
<step>OCA\OpenBuilt\Repair\PopulateApplicationPermissions</step>
<step>OCA\OpenBuilt\Repair\SeedApplicationTemplates</step>
</install>
<post-migration>
<step>OCA\OpenBuilt\Repair\InitializeSettings</step>
<step>OCA\OpenBuilt\Repair\SeedHelloWorld</step>
<step>OCA\OpenBuilt\Repair\PopulateApplicationPermissions</step>
<step>OCA\OpenBuilt\Repair\SeedApplicationTemplates</step>
</post-migration>
</repair-steps>

<background-jobs>
<job>OCA\OpenBuilt\BackgroundJob\CleanupExpiredExports</job>
</background-jobs>
</info>
33 changes: 33 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,39 @@
// Health check endpoint.
['name' => 'health#index', 'url' => '/api/health', 'verb' => 'GET'],

// RBAC-filtered Application list (openbuilt-rbac REQ-OBRBAC-002 / REQ-OBR-007).
// OR's schema-level read rule is a coarse group ACL — not a row-level filter on the
// Application's `permissions` block — so the editor list MUST go through this
// endpoint, NOT directly through `/apps/openregister/api/objects/openbuilt/application`,
// which would leak every Application + permissions to every authed user (IDOR).
// Listed BEFORE the {slug} route so the wildcard does not shadow it (Symfony router
// is order-sensitive when prefix overlaps).
['name' => 'applications#listMine', 'url' => '/api/applications', 'verb' => 'GET'],

// Clone-from-template action (openbuilt-templates-marketplace REQ-OBTC-004 / REQ-OBTC-005).
// POST so it does not collide with the GET {slug} routes; #[NoAdminRequired] on the
// controller method. Creates a per-app `openbuilt-{newSlug}` register, deep-copies the
// template's companion schemas into it, rewrites manifest schema refs, and persists a new
// Application in the shared `openbuilt` register tagged with the caller's UID.
['name' => 'applications#createFromTemplate', 'url' => '/api/applications/from-template/{templateSlug}', 'verb' => 'POST'],

// Manifest endpoint — returns the stored manifest JSON blob for a given virtual-app slug.
// Per ADR-016 routes.php is the only registration path; #[NoAdminRequired] is set on the
// controller method so auth-required-but-non-admin users can hit it (per design.md Decision 6).
// Slug matches the kebab-case pattern declared in openbuilt_register.json on the Application
// and BuiltAppRoute schemas (^[a-z0-9][a-z0-9-]*[a-z0-9]$, min 2 max 48 chars).
['name' => 'applications#getManifest', 'url' => '/api/applications/{slug}/manifest', 'verb' => 'GET', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']],

// Versioning — diff endpoint (chain spec #6 openbuilt-versioning, REQ-OBV-005). Returns
// two ApplicationVersion manifest blobs in one round-trip so the client diff component
// does not double-fetch. `from`/`to` are ApplicationVersion UUIDs OR the literal `draft`.
// Specific route MUST precede the SPA catch-all (memory rule: Symfony specific-first).
['name' => 'applications#diffVersions', 'url' => '/api/applications/{slug}/versions/diff', 'verb' => 'GET', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']],

// Export pipeline (Phase-2 graduation).
['name' => 'exports#submit', 'url' => '/api/applications/{slug}/exports', 'verb' => 'POST', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']],
['name' => 'exports#download', 'url' => '/api/exports/{uuid}/download', 'verb' => 'GET'],

// SPA catch-all — same controller as the index route; must use a distinct route name
// (duplicate names replace the earlier route in Symfony, which breaks GET /).
['name' => 'dashboard#catchAll', 'url' => '/{path}', 'verb' => 'GET', 'requirements' => ['path' => '.+'], 'defaults' => ['path' => '']],
Expand Down
8 changes: 8 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
"autoload": {
"psr-4": {
"OCA\\OpenBuilt\\": "lib/"
},
"exclude-from-classmap": [
"lib/Resources/template/"
]
},
"autoload-dev": {
"psr-4": {
"OCA\\OpenRegister\\": "tests/Stubs/"
}
},
"require": {
Expand Down
142 changes: 142 additions & 0 deletions docs/docusaurus.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// @ts-check

/**
* OpenBuilt documentation site.
*
* Built on @conduction/docusaurus-preset for brand defaults (tokens,
* theme swizzles for Navbar / Footer, i18n scaffolding, KvK / BTW
* copyright). Site-specific overrides — locale (en only), sidebar
* path, mermaid theme, custom prism themes, openbuilt-only navbar
* items — are passed through createConfig() opts.
*/

const { createConfig, baseFooterLinks } = require('@conduction/docusaurus-preset');

/* createConfig replaces themes wholesale when `themes:` is passed, so
we re-include the brand theme plugin alongside @docusaurus/theme-mermaid.
Without the brand theme entry the Navbar/Footer swizzles and
brand.css auto-load would silently drop. */
const BRAND_THEME = require.resolve('@conduction/docusaurus-preset/theme');

const config = createConfig({
title: 'OpenBuilt',
tagline: 'Citizen-developer app builder for Nextcloud — compose apps from registers, connectors, workflows, and documents without code',
url: 'https://openbuilt.conduction.nl',
baseUrl: '/',

organizationName: 'ConductionNL',
projectName: 'openbuilt',

/* English-only for now (ADR-030). Dutch is omitted because shipping
`i18n.locales: ['en', 'nl']` without translated markdown triggers
SSR rendering errors on tutorial pages when the docs source moves
faster than the locale's `current.json`. Re-enable by adding 'nl'
back once the Dutch translation pass has been completed and the
metadata audited for stale references. The brand preset's default
i18n block is replaced wholesale here. */
i18n: {
defaultLocale: 'en',
locales: ['en'],
localeConfigs: {
en: { label: 'English' },
},
},

/* The openbuilt docs source lives at the repo root of `docs/` rather
than under a `docs/` subfolder, so we override the preset's default
`presets:` block to point `docs.path` at './' and disable the blog
plugin. customCss carries openbuilt-specific CSS only — brand
tokens and the theme swizzles are auto-loaded by the brand theme
entry in `themes:` below. */
presets: [
[
'classic',
{
docs: {
path: './',
/* docs.path: './' makes plugin-content-docs scan every file
in docs/, which collides with plugin-content-pages's own
scan of docs/src/pages/. Exclude src/ (pages live there)
plus the standard node_modules bucket. */
exclude: ['**/node_modules/**', 'src/**'],
sidebarPath: require.resolve('./sidebars.js'),
editUrl: 'https://github.com/ConductionNL/openbuilt/tree/main/docs/',
},
blog: false,
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
},
],
],

themes: [BRAND_THEME, '@docusaurus/theme-mermaid'],

/* Brand navbar provides locale dropdown + GitHub by default; we
replace items[] with openbuilt's own (Documentation sidebar link,
openbuilt GitHub link). Object.assign in createConfig is shallow,
so items: replaces wholesale — re-include the locale dropdown and
add the openbuilt GitHub repo link explicitly. */
navbar: {
items: [
{
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
position: 'left',
label: 'Documentation',
},
{
href: 'https://github.com/ConductionNL/openbuilt',
label: 'GitHub',
position: 'right',
},
{ type: 'localeDropdown', position: 'right' },
],
},

/* Per-property footer override (preset 1.2.0+): we pass `links` only,
so the brand `style: 'dark'` and the brand KvK/BTW/IBAN/address
copyright string both inherit unchanged. */
footer: {
links: [
...baseFooterLinks().filter((column) => column.title === 'Conduction'),
],
},

/* Drop the canal-footer's interactive mini-games on this product-page
footer (preset 1.3.0+). The static skyline + canal decoration are
kept; the interactive layer goes away. */
minigames: false,

/* themeConfig is shallow-merged into the preset's defaults
(colorMode + navbar + footer). prism + mermaid land alongside. */
themeConfig: {
prism: {
theme: require('prism-react-renderer/themes/github'),
darkTheme: require('prism-react-renderer/themes/dracula'),
},
mermaid: {
theme: { light: 'default', dark: 'dark' },
},
},
});

/* createConfig doesn't pass-through arbitrary top-level fields; assign
markdown + onBrokenAnchors directly so they make it into the final
Docusaurus config. trailingSlash is left at the preset's default
(true) so /docs/intro/ resolves cleanly under GH Pages. */
config.onBrokenAnchors = 'warn';
config.markdown = {
mermaid: true,
/* Tutorial pages reference screenshots populated by
`tests/e2e/docs-screenshots.spec.ts`. The Playwright capture run
is separate from the docs build, so the build needs to succeed
even when a fresh checkout doesn't have every PNG yet. Warn
instead of failing — the absence is visible at preview time and
the capture spec brings everything back on demand. */
hooks: {
onBrokenMarkdownImages: 'warn',
},
};

module.exports = config;
75 changes: 75 additions & 0 deletions docs/integrator-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Integrator guide — authoring a virtual app by hand

This guide walks you through creating a new virtual app in OpenBuilt without using the visual editor (which lives in chain spec [`openbuilt-page-editor`](../openspec/changes/) — not yet shipped). At this stage, OpenBuilt is integrator-only: you write JSON and the runtime renders it.

## What you author

A virtual app is one record in OpenBuilt's `Application` OR schema. The shape is:

```jsonc
{
"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`](https://github.com/ConductionNL/nextcloud-vue/blob/main/src/schemas/app-manifest.schema.json). The closed `type` enum for pages is `index | detail | dashboard | logs | settings | chat | files | form | custom`.

## Step-by-step

1. **Pick a slug.** Must be kebab-case, 2–48 chars, unique within your organisation. The synthetic appId in CnAppRoot becomes `openbuilt-${slug}`.
2. **Design your schemas** in OpenRegister directly (the OpenBuilt schema editor lands in chain spec `openbuilt-schema-editor`). At minimum: one schema per primary entity your app shows.
3. **Author the manifest** as JSON. The canonical example is the seeded `hello-world` Application — open it in OpenBuilt's manifest editor (top-bar OpenBuilt entry → Virtual apps → hello-world) and read its manifest.
4. **Save as `draft`** while iterating. The textarea editor validates each save against the canonical schema; you see the failing JSON path on save error.
5. **Transition to `published`** when ready (via OR's lifecycle endpoint or the editor's Publish action — landing in chain spec `openbuilt-versioning`). On publish, OpenBuilt's lifecycle creates the corresponding `BuiltAppRoute` so `/builder/{slug}` becomes reachable.

## Manifest checklist

Per [ADR-024](https://github.com/ConductionNL/hydra/blob/main/openspec/architecture/adr-024-app-manifest.md):

- `version` (semver) — your app's content version
- `dependencies` — list of NC app IDs that must be installed (almost always `["openregister"]`)
- `menu[]` — at least one entry; supports one level of `children[]`
- `pages[]` — at least one entry; every page's `id` MUST be unique and match a vue-router route name
- `label` / `title` strings are i18n KEYS, not literals. The consuming app's `t()` resolves them. Use kebab.dot.notation: `myapp.permits.title.list`.

Per [ADR-007](https://github.com/ConductionNL/hydra/blob/main/openspec/architecture/adr-007-i18n.md):

- Every translation key MUST exist in `l10n/en.json` AND `l10n/nl.json` of the **OpenBuilt** repo (until per-virtual-app translations land in chain spec `openbuilt-page-editor`).

## Reading the seed manifest

The seeded `hello-world` Application is the canonical reference. Its manifest exercises:

- **index** page → drives `CnIndexPage` with `register: openbuilt`, `schema: hello-message`, three columns
- **detail** page → drives `CnDetailPage` keyed on `:id`
- **form** page → drives `CnFormPage` with `mode: create` and `submitEndpoint` going to OR's REST

See [`lib/Repair/SeedHelloWorld.php`](../lib/Repair/SeedHelloWorld.php) `buildHelloWorldManifest()` 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:

1. Confirm the requirement isn't satisfied by `form` (the most flexible built-in).
2. Open an issue on `ConductionNL/nextcloud-vue` describing the new page type's shape.
3. As an interim, mount a custom Vue component via `type: "custom"` + `component: "MyCustomPage"` and register the component in OpenBuilt's `customComponents` map. (Note: spec #1 only ships the built-in types — the `customComponents` registry 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 `openbuilt-page-editor`.
- **No schema designer** — schemas must be authored in `lib/Settings/openbuilt_register.json` and imported via the repair step. Runtime schema authoring: chain spec `openregister-runtime-schema-api`.
- **No draft preview** — only `published` apps appear at `/builder/{slug}`. Draft preview: chain spec `openbuilt-versioning`.
- **No per-app permissions** — auth-only visibility for v1; everyone in your organisation sees every virtual app. Per-app RBAC: chain spec `openbuilt-rbac`.
- **No export to a real Nextcloud app** — virtual-only. Export pipeline: chain spec `openbuilt-export-to-real-app`.

If any of these limitations block your project, talk to Conduction — chain spec prioritisation can shift.
Loading
Loading