diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 96010b7..d89eeaa 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,6 +1,8 @@ # Retrofit annotation commit (opsx-annotate, 2026-05-24) 770ffed41a22f3a45e00e98a8484ca72330eefc9 +# Retrofit reverse-spec annotations (opsx-reverse-spec, 2026-05-24, deep-link-registration) +c72f99a # Retrofit reverse-spec annotations (opsx-reverse-spec, 2026-05-24, app-icon-management UUID) 01a2862 # Retrofit reverse-spec annotations (opsx-reverse-spec, 2026-05-24, openbuilt-runtime MCP) diff --git a/lib/Listener/DeepLinkRegistrationListener.php b/lib/Listener/DeepLinkRegistrationListener.php index 9d8f384..b5fe4a6 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/archive/retrofit-2026-05-24-deep-link-registration/design.md b/openspec/changes/archive/retrofit-2026-05-24-deep-link-registration/design.md new file mode 100644 index 0000000..8f58d64 --- /dev/null +++ b/openspec/changes/archive/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/archive/retrofit-2026-05-24-deep-link-registration/proposal.md b/openspec/changes/archive/retrofit-2026-05-24-deep-link-registration/proposal.md new file mode 100644 index 0000000..9e23598 --- /dev/null +++ b/openspec/changes/archive/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/archive/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 new file mode 100644 index 0000000..831e7aa --- /dev/null +++ b/openspec/changes/archive/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/archive/retrofit-2026-05-24-deep-link-registration/tasks.md b/openspec/changes/archive/retrofit-2026-05-24-deep-link-registration/tasks.md new file mode 100644 index 0000000..2ce0008 --- /dev/null +++ b/openspec/changes/archive/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) diff --git a/openspec/specs/deep-link-registration/spec.md b/openspec/specs/deep-link-registration/spec.md new file mode 100644 index 0000000..831e7aa --- /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.