From c72f99a8fd46923ec2fedcdb1f22c4e0d06efcfa Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 24 May 2026 20:34:20 +0200 Subject: [PATCH 1/3] retrofit: draft deep-link-registration spec + annotate DeepLinkRegistrationListener Drafts 2 REQs (REQ-OBDL-001..002) in a new deep-link-registration capability spec covering the DeepLinkRegistrationListener wiring + handle() method (Bucket 2b from the 2026-05-24 coverage scan). Adds 2 @spec tags to the listener's file docblock. REQ-OBDL-001: Event listener wired at app registration - codifies the IRegistrationContext::registerEventListener wiring and the short-circuit on non-matching events. Includes the no-hard-dependency invariant: if OR isn't installed, the listener never fires. REQ-OBDL-002: Per-schema deep-link entries registered against the event - codifies the additive $event->register(appId, registerSlug, schemaSlug, urlTemplate) shape and the {uuid} placeholder. Notes block flags the placeholder schema slug 'example' (scaffold leftover) as TODO - real OpenBuilt schemas (application, application-version, built-app-route) have no deep-link entries yet. Future PRs will extend REQ-OBDL-002 with one register() call per OpenBuilt-owned schema. Refs #137 --- lib/Listener/DeepLinkRegistrationListener.php | 3 + .../design.md | 45 ++++++++ .../proposal.md | 33 ++++++ .../specs/deep-link-registration/spec.md | 100 ++++++++++++++++++ .../tasks.md | 4 + 5 files changed, 185 insertions(+) create mode 100644 openspec/changes/retrofit-2026-05-24-deep-link-registration/design.md create mode 100644 openspec/changes/retrofit-2026-05-24-deep-link-registration/proposal.md create mode 100644 openspec/changes/retrofit-2026-05-24-deep-link-registration/specs/deep-link-registration/spec.md create mode 100644 openspec/changes/retrofit-2026-05-24-deep-link-registration/tasks.md diff --git a/lib/Listener/DeepLinkRegistrationListener.php b/lib/Listener/DeepLinkRegistrationListener.php index 9d8f384c..b5fe4a64 100644 --- a/lib/Listener/DeepLinkRegistrationListener.php +++ b/lib/Listener/DeepLinkRegistrationListener.php @@ -15,6 +15,9 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-deep-link-registration/tasks.md#task-1 + * @spec openspec/changes/retrofit-2026-05-24-deep-link-registration/tasks.md#task-2 */ declare(strict_types=1); diff --git a/openspec/changes/retrofit-2026-05-24-deep-link-registration/design.md b/openspec/changes/retrofit-2026-05-24-deep-link-registration/design.md new file mode 100644 index 00000000..8f58d649 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-24-deep-link-registration/design.md @@ -0,0 +1,45 @@ +# Design — Retrofit deep-link-registration + +> Retrofit change. Tasks describe retroactive annotation, not new implementation +> work. The code already exists at HEAD. + +## Context + +`lib/Listener/DeepLinkRegistrationListener.php` (and its companion +wiring in `lib/AppInfo/Application.php::register`) implement +OpenBuilt's hook into OpenRegister's unified-search deep-link surface. +The 2026-05-24 coverage scan dropped these into Bucket 2b +(`deep-link-registration`) because no existing openbuilt capability +spec names this listener or the event it consumes. + +## Decisions + +- **New capability, not extend.** The closest existing spec is + `openbuilt-runtime`, but that's a runtime/serving capability — the + listener is a search-integration capability, not a runtime mount. + ADR-019 is the closest org-wide concept, but it does not specify + per-app listener wiring. So this is a brand-new app-local capability. +- **Two REQs, not one.** Wiring (REQ-OBDL-001) vs payload + (REQ-OBDL-002). Splitting them lets future PRs add more + `$event->register(...)` calls under REQ-OBDL-002 without touching + REQ-OBDL-001's wiring contract. +- **Capture the stub honestly.** The current handler body registers a + single placeholder entry for `schemaSlug: 'example'` — clearly + template-stub leftover (matches the comment "Update the register + slug, schema slug, and URL template to match your app's actual + schemas"). REQ-OBDL-002 stays minimal ("at least one entry, with + `{uuid}` placeholder") and the Note block names the gap as TODO + rather than silently spec'ing the placeholder as production + behaviour. +- **No hard OR dependency.** The wiring is "if OR is installed and + dispatches the event, OpenBuilt responds". REQ-OBDL-001's scenario + pins this as an observable invariant — the app must not crash when + OR is absent. + +## Out of scope + +- Replacing the placeholder `example` schema with real OpenBuilt + schemas (`application`, `application-version`, `built-app-route`) — + separate PR, will extend REQ-OBDL-002 with additional scenarios. +- ADR-019 integration-registry surface — orthogonal. +- Unified-search result rendering / icon — OR concern. diff --git a/openspec/changes/retrofit-2026-05-24-deep-link-registration/proposal.md b/openspec/changes/retrofit-2026-05-24-deep-link-registration/proposal.md new file mode 100644 index 00000000..9e235985 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-24-deep-link-registration/proposal.md @@ -0,0 +1,33 @@ +# Retrofit — deep-link-registration (new capability) + +Describes the observed behaviour of +`DeepLinkRegistrationListener::handle` as 2 REQs in a brand-new +capability spec. Code already exists — this change retroactively +specifies it. + +## Affected code units + +- lib/AppInfo/Application.php::register (event-listener wiring only) +- lib/Listener/DeepLinkRegistrationListener.php::handle + +## Approach + +- New capability (`--cluster`) rather than `--extend`: the existing + openbuilt specs (runtime, RBAC, exporter, versioning, etc.) all + describe authoring or runtime serving of virtual apps. None covers + the listener that hooks OpenRegister's unified-search deep-link + registration event. ADR-019 (integration registry) is the closest + org-wide concept, but it does not specify per-app event-listener + wiring as a capability. +- Two REQs: one for the event-listener wiring (REQ-OBDL-001) and one + for the registration payload that the handler emits (REQ-OBDL-002). + Splitting the wiring from the payload lets future schema additions + (more `$event->register(...)` calls) extend REQ-OBDL-002 without + re-litigating the wiring posture. +- The current `handle` body registers a single deep link for a + placeholder schema slug `example` (template-stub left over from + scaffold) — Notes block flags this as observed-but-incomplete TODO, + not silently spec'd as production behaviour. + +Source: `openspec/coverage-report.md` generated 2026-05-24. See +[retrofit playbook](../../../../hydra/.github/docs/claude/retrofit.md). diff --git a/openspec/changes/retrofit-2026-05-24-deep-link-registration/specs/deep-link-registration/spec.md b/openspec/changes/retrofit-2026-05-24-deep-link-registration/specs/deep-link-registration/spec.md new file mode 100644 index 00000000..831e7aa1 --- /dev/null +++ b/openspec/changes/retrofit-2026-05-24-deep-link-registration/specs/deep-link-registration/spec.md @@ -0,0 +1,100 @@ +--- +retrofit: true +--- + +# deep-link-registration Specification + +## Purpose + +OpenBuilt opts in to Nextcloud's unified-search deep-link integration +by listening for OpenRegister's `DeepLinkRegistrationEvent` and +registering per-schema URL templates. When OR resolves a search hit on +a registered schema, the unified-search result row links straight to +the matching OpenBuilt detail view — no double-click through OR. + +This capability is event-driven: the contract is "if OR is installed +and dispatches the event, OpenBuilt provides its deep-link table". If +OR is absent the event never fires and OpenBuilt silently no-ops, so +the integration is optional and adds zero hard dependency. + +## Requirements + +### Requirement: Event listener wired at app registration + +The app SHALL register a listener for +`OCA\OpenRegister\Event\DeepLinkRegistrationEvent` during +`Application::register` via +`IRegistrationContext::registerEventListener`. The listener class +SHALL be `OCA\OpenBuilt\Listener\DeepLinkRegistrationListener` and +SHALL implement `OCP\EventDispatcher\IEventListener`. The +listener SHALL be idempotent at the Nextcloud DI level — re-running +`register()` SHALL NOT result in duplicate registrations. The wiring +SHALL NOT introduce a hard dependency on OpenRegister: if OR is not +installed, no event fires and the listener is never invoked. + +**ID:** REQ-OBDL-001 + +#### Scenario: Listener is registered exactly once + +- **WHEN** Nextcloud bootstraps the OpenBuilt app and calls + `Application::register` +- **THEN** `IRegistrationContext::registerEventListener` is invoked + with `DeepLinkRegistrationEvent::class` and + `DeepLinkRegistrationListener::class` + +#### Scenario: OpenRegister absent → no-op + +- **WHEN** OpenRegister is not installed and OpenBuilt boots +- **THEN** the wiring registers without raising +- **AND** no deep-link entries are emitted because the event never + fires + +#### Scenario: Handler short-circuits on non-matching events + +- **WHEN** the listener is invoked with an event that is not an + instance of `DeepLinkRegistrationEvent` +- **THEN** `handle()` returns immediately without calling + `$event->register(...)` and without raising + +### Requirement: Per-schema deep-link entries registered against the event + +When `handle()` receives a `DeepLinkRegistrationEvent`, it SHALL +register one or more deep-link entries by calling +`$event->register(appId, registerSlug, schemaSlug, urlTemplate)`. +Each entry SHALL declare `appId: 'openbuilt'` as the host Nextcloud +app id. The `urlTemplate` SHALL be a relative path of shape +`/apps/openbuilt/#/{schemaPath}/{uuid}` with `{uuid}` as the canonical +placeholder OR substitutes with the matching object's UUID. The +registration SHALL be additive — calling `$event->register(...)` once +per schema; the listener SHALL NOT mutate or remove previously +registered entries on the event. + +**ID:** REQ-OBDL-002 + +#### Scenario: At least one deep-link entry is registered + +- **WHEN** OR dispatches `DeepLinkRegistrationEvent` and the listener + handles it +- **THEN** `$event->register(...)` is called at least once with + `appId: 'openbuilt'` + +#### Scenario: URL template carries the {uuid} placeholder + +- **WHEN** the listener registers a deep-link entry +- **THEN** the `urlTemplate` string contains the literal placeholder + `{uuid}` that OR substitutes per result row + +#### Note + +The handler currently registers only a single placeholder entry for +schema slug `example` (left over from the +`nextcloud-app-template` scaffold — see `lib/Resources/template/lib/ +Listener/DeepLinkRegistrationListener.php`). This is observed-but- +incomplete: the real OpenBuilt schemas (`application`, +`application-version`, `built-app-route`, etc.) have no deep-link +entries yet, so unified-search hits on those rows still land on OR's +generic detail page. TODO (filed separately): replace the placeholder +with one `$event->register(...)` call per OpenBuilt-owned schema. This +retrofit spec captures the wiring contract; the catalogue is +intentionally left to a follow-up so future PRs can extend +REQ-OBDL-002 without re-litigating REQ-OBDL-001. diff --git a/openspec/changes/retrofit-2026-05-24-deep-link-registration/tasks.md b/openspec/changes/retrofit-2026-05-24-deep-link-registration/tasks.md new file mode 100644 index 00000000..2ce0008b --- /dev/null +++ b/openspec/changes/retrofit-2026-05-24-deep-link-registration/tasks.md @@ -0,0 +1,4 @@ +# Tasks + +- [x] task-1: deep-link-registration#REQ-OBDL-001 — Event listener wired at app registration (retroactive annotation) +- [x] task-2: deep-link-registration#REQ-OBDL-002 — Per-schema deep-link entries registered against the event (retroactive annotation) From 160dfcfe1106dcb1199561eadcf1619a7efc02d9 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 24 May 2026 20:34:34 +0200 Subject: [PATCH 2/3] retrofit: archive deep-link-registration change Creates openspec/specs/deep-link-registration/spec.md (new capability) from the change's spec delta and archives the change. No code logic changes. Refs #137 --- .../design.md | 0 .../proposal.md | 0 .../specs/deep-link-registration/spec.md | 0 .../tasks.md | 0 openspec/specs/deep-link-registration/spec.md | 100 ++++++++++++++++++ 5 files changed, 100 insertions(+) rename openspec/changes/{ => archive}/retrofit-2026-05-24-deep-link-registration/design.md (100%) rename openspec/changes/{ => archive}/retrofit-2026-05-24-deep-link-registration/proposal.md (100%) rename openspec/changes/{ => archive}/retrofit-2026-05-24-deep-link-registration/specs/deep-link-registration/spec.md (100%) rename openspec/changes/{ => archive}/retrofit-2026-05-24-deep-link-registration/tasks.md (100%) create mode 100644 openspec/specs/deep-link-registration/spec.md diff --git a/openspec/changes/retrofit-2026-05-24-deep-link-registration/design.md b/openspec/changes/archive/retrofit-2026-05-24-deep-link-registration/design.md similarity index 100% rename from openspec/changes/retrofit-2026-05-24-deep-link-registration/design.md rename to openspec/changes/archive/retrofit-2026-05-24-deep-link-registration/design.md diff --git a/openspec/changes/retrofit-2026-05-24-deep-link-registration/proposal.md b/openspec/changes/archive/retrofit-2026-05-24-deep-link-registration/proposal.md similarity index 100% rename from openspec/changes/retrofit-2026-05-24-deep-link-registration/proposal.md rename to openspec/changes/archive/retrofit-2026-05-24-deep-link-registration/proposal.md diff --git a/openspec/changes/retrofit-2026-05-24-deep-link-registration/specs/deep-link-registration/spec.md b/openspec/changes/archive/retrofit-2026-05-24-deep-link-registration/specs/deep-link-registration/spec.md similarity index 100% rename from openspec/changes/retrofit-2026-05-24-deep-link-registration/specs/deep-link-registration/spec.md rename to openspec/changes/archive/retrofit-2026-05-24-deep-link-registration/specs/deep-link-registration/spec.md diff --git a/openspec/changes/retrofit-2026-05-24-deep-link-registration/tasks.md b/openspec/changes/archive/retrofit-2026-05-24-deep-link-registration/tasks.md similarity index 100% rename from openspec/changes/retrofit-2026-05-24-deep-link-registration/tasks.md rename to openspec/changes/archive/retrofit-2026-05-24-deep-link-registration/tasks.md diff --git a/openspec/specs/deep-link-registration/spec.md b/openspec/specs/deep-link-registration/spec.md new file mode 100644 index 00000000..831e7aa1 --- /dev/null +++ b/openspec/specs/deep-link-registration/spec.md @@ -0,0 +1,100 @@ +--- +retrofit: true +--- + +# deep-link-registration Specification + +## Purpose + +OpenBuilt opts in to Nextcloud's unified-search deep-link integration +by listening for OpenRegister's `DeepLinkRegistrationEvent` and +registering per-schema URL templates. When OR resolves a search hit on +a registered schema, the unified-search result row links straight to +the matching OpenBuilt detail view — no double-click through OR. + +This capability is event-driven: the contract is "if OR is installed +and dispatches the event, OpenBuilt provides its deep-link table". If +OR is absent the event never fires and OpenBuilt silently no-ops, so +the integration is optional and adds zero hard dependency. + +## Requirements + +### Requirement: Event listener wired at app registration + +The app SHALL register a listener for +`OCA\OpenRegister\Event\DeepLinkRegistrationEvent` during +`Application::register` via +`IRegistrationContext::registerEventListener`. The listener class +SHALL be `OCA\OpenBuilt\Listener\DeepLinkRegistrationListener` and +SHALL implement `OCP\EventDispatcher\IEventListener`. The +listener SHALL be idempotent at the Nextcloud DI level — re-running +`register()` SHALL NOT result in duplicate registrations. The wiring +SHALL NOT introduce a hard dependency on OpenRegister: if OR is not +installed, no event fires and the listener is never invoked. + +**ID:** REQ-OBDL-001 + +#### Scenario: Listener is registered exactly once + +- **WHEN** Nextcloud bootstraps the OpenBuilt app and calls + `Application::register` +- **THEN** `IRegistrationContext::registerEventListener` is invoked + with `DeepLinkRegistrationEvent::class` and + `DeepLinkRegistrationListener::class` + +#### Scenario: OpenRegister absent → no-op + +- **WHEN** OpenRegister is not installed and OpenBuilt boots +- **THEN** the wiring registers without raising +- **AND** no deep-link entries are emitted because the event never + fires + +#### Scenario: Handler short-circuits on non-matching events + +- **WHEN** the listener is invoked with an event that is not an + instance of `DeepLinkRegistrationEvent` +- **THEN** `handle()` returns immediately without calling + `$event->register(...)` and without raising + +### Requirement: Per-schema deep-link entries registered against the event + +When `handle()` receives a `DeepLinkRegistrationEvent`, it SHALL +register one or more deep-link entries by calling +`$event->register(appId, registerSlug, schemaSlug, urlTemplate)`. +Each entry SHALL declare `appId: 'openbuilt'` as the host Nextcloud +app id. The `urlTemplate` SHALL be a relative path of shape +`/apps/openbuilt/#/{schemaPath}/{uuid}` with `{uuid}` as the canonical +placeholder OR substitutes with the matching object's UUID. The +registration SHALL be additive — calling `$event->register(...)` once +per schema; the listener SHALL NOT mutate or remove previously +registered entries on the event. + +**ID:** REQ-OBDL-002 + +#### Scenario: At least one deep-link entry is registered + +- **WHEN** OR dispatches `DeepLinkRegistrationEvent` and the listener + handles it +- **THEN** `$event->register(...)` is called at least once with + `appId: 'openbuilt'` + +#### Scenario: URL template carries the {uuid} placeholder + +- **WHEN** the listener registers a deep-link entry +- **THEN** the `urlTemplate` string contains the literal placeholder + `{uuid}` that OR substitutes per result row + +#### Note + +The handler currently registers only a single placeholder entry for +schema slug `example` (left over from the +`nextcloud-app-template` scaffold — see `lib/Resources/template/lib/ +Listener/DeepLinkRegistrationListener.php`). This is observed-but- +incomplete: the real OpenBuilt schemas (`application`, +`application-version`, `built-app-route`, etc.) have no deep-link +entries yet, so unified-search hits on those rows still land on OR's +generic detail page. TODO (filed separately): replace the placeholder +with one `$event->register(...)` call per OpenBuilt-owned schema. This +retrofit spec captures the wiring contract; the catalogue is +intentionally left to a follow-up so future PRs can extend +REQ-OBDL-002 without re-litigating REQ-OBDL-001. From 4b6a904ad6c1c5572c7df9ce75cbcfa2b30f196c Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 24 May 2026 20:34:44 +0200 Subject: [PATCH 3/3] chore: record reverse-spec annotation SHA in .git-blame-ignore-revs Refs #137 --- .git-blame-ignore-revs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 5f01cca8..ad738ed3 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,2 +1,5 @@ # Retrofit annotation commit (opsx-annotate, 2026-05-24) 770ffed41a22f3a45e00e98a8484ca72330eefc9 + +# Retrofit reverse-spec annotations (opsx-reverse-spec, 2026-05-24, deep-link-registration) +c72f99a