Architecture
LarpingApp is a schema-driven Nextcloud app. It stores no data in its
own database tables: every domain object (character, player, ability,
skill, item, condition, effect, event, setting) lives in
OpenRegister (ADR-001),
and the UI is built exclusively from @conduction/nextcloud-vue
components (ADR-012). This document describes the three cross-cutting
adoption patterns that follow from those decisions.
1. Manifest as the declarative source of truth
LarpingApp's navigation and routes are NOT hand-wired. They are derived from a single declarative manifest:
src/manifest.json— the bundled base manifest (manifest-v2). It declares themenu, and apagesarray with oneindexand onedetailpage per entity type, plus adashboardpage and aroadmappage.dependencies: ["openregister"]makes the ADR-001 dependency on OpenRegister explicit.src/manifest.d/*.json— additive fragments (ADR-037). Concurrent builds drop their own fragment file here instead of editing the sharedmanifest.json, so disjoint changes never conflict.mergeManifestFragments()insrc/main.jsdeep-merges every fragment'spages[]andmenu[]onto the base at build time.src/main.jsbuilds the vue-router routes directly from the merged manifest (routesFromManifest()) and renders each page throughCnPageRenderer. Adding a new entity is a manifest edit, not a code change across router + nav + view files.src/registry.jsis the ADR-036 kind-tagged component registry (replacing the deprecatedcustomComponentsprop). Slot overrides, action components (e.g. the dashboard/PDF actions), and section components are resolved from it byCnPageRenderer.
Validation: npm run check:manifest (node tests/validate-manifest.js)
runs as part of the check:specs composite gate.
2. Register / schema resolution (RegisterResolverService)
lib/Service/RegisterObjectFetcher.php is the single seam between
LarpingApp and OpenRegister's ObjectService. For each object type it
must resolve a (register, schema) pair from the
{type}_register / {type}_schema app-config keys.
Resolution is delegated to OpenRegister's RegisterResolverService
when it is available, consolidating the per-call
IAppConfig::getValueString pattern (ADR-022 — consume OR
abstractions, never reinvent them). The real resolver API is
resolveRegisterId($appId, $configKey, $default, $orgUuid) and
resolveSchemaId(...).
Because that service ships with newer OpenRegister releases, the consumption is backward-compatible:
getRegisterResolver()resolves the service lazily from the DI container, guarded byclass_exists(...). It returnsnullwhen the class is absent (older OR, or during an upgrade window) and never fails open on a missing dependency.resolveRegisterAndSchema()uses the resolver when present and falls back to the legacyIAppConfig::getValueStringpath otherwise, preserving identical "not configured" error semantics. The fallback logs a one-shot deprecation note viaLoggerInterface.
This keeps a single deployable artifact working against both old and
new OpenRegister, and removes the duplicated resolution logic from
getMapper().
SettingsService is intentionally NOT migrated to the resolver: its
only OR-pair access is a bulk round-trip of the full settings key set
(including non-pair *_source and top-level register keys) on behalf
of the settings UI. The resolver resolves one pair at a time and throws
on missing config — the wrong shape for a settings dump. The runtime
consumer of those keys (RegisterObjectFetcher) is the correct place
for the resolver, and it is migrated.
3. i18n and multi-tenancy (planned)
Two further OR/nc-vue adoptions are specified but deferred until their cross-app dependencies ship:
- i18n (ADR-025). Passing
?_lang={locale}on reads, stampingX-Translation-Target-Languageon non-default-language writes, and rendering a(translated from {lang})badge fromsourceLanguagemetadata. These depend on OpenRegister's object API honouring those signals (unmergedi18n-api-language-negotiation/i18n-source-of-truth). LarpingApp fetches objects through the sharedcreateObjectStorefrom@conduction/nextcloud-vue(src/store/modules/object.js), so the correct home for these signals is the shared store, not a per-app HTTP client. - Multi-tenancy. Adopting nc-vue's
useTenantContext()to clear caches and refetch on tenant switch, and stampingX-OpenRegister-Organisationon writes. Gated on a versioned nc-vue release that exports the composable. Until then the app behaves as single-tenant; nothing imports the composable, so its absence cannot crash the app.
See openspec/changes/larpingapp-adopt-or-abstractions/ for the full
requirements, scenarios, and deferral rationale.