Skip to content

feat(connections): an Integrations page over integriq's connection registry - #641

Merged
rubenvdlinde merged 9 commits into
developmentfrom
feat/adopt-connection-registry
Sep 15, 2026
Merged

rubenvdlinde merged 9 commits into
developmentfrom
feat/adopt-connection-registry

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

LaunchPad now has an Integrations page under the settings gear. It lists LaunchPad's outside connections from integriq's connection registry (hydra#667, amended in hydra#673, hydra#674 and hydra#676).

Connections

Key Declared as What sets its status
dashboard-registry requiredConfig: ["registry_url"], links to /settings/admin/launchpad?tab=sharing#section-dashboard-registry Rule 5 after a save. A store search reports configured, unconfigured, limited, error, or unavailable without OpenRegister.
weather reportedOnly A weather widget with a location reports. No provider URL reads unconfigured, a bad scheme or an unreadable answer reads error.
news-feeds reportedOnly, one family row (D12) A feed fetch reports, with the host only.
ics-calendars reportedOnly, one family row (D12) A calendar feed fetch reports, with the host only.
live-tiles reportedOnly, one family row (D12) A tile fetch reports. An empty fail-closed allow-list reads unconfigured.
health-ping reportedOnly, one family row (D12) A ping reports, from a page load or the refresh job. An empty allow-list reads unconfigured.

Weather. A placement without a location reads Nextcloud's weather status app for the viewer and never calls the provider URL. So an empty weather_provider_url is a working default for those widgets. It reads Not configured only after a widget with a location found no URL. A weather status reading is about one user and sends nothing.

Why StoreService reports. It is the one caller of OpenRegister's GenericStoreService, which calls the remote dashboard registry at registry_url with registry_token. It is also the one writer of those keys, so the refresh after a save goes there too. Application.php builds it by hand, so the reporter is passed in there.

Throttle. The report memory is one app-config value per connection. The same status reports again after an hour, a different status after five minutes. A registry save clears the registry's memory, so the next search reports at once. Every report runs after the call it observes, catches everything, and never changes the widget's answer. Without integriq nothing is read, stored, sent or logged.

What changed

  • lib/Settings/connections.json, valid against integriq's connections.schema.json from development (vendored in tests/Fixtures/Integriq/).
  • The registry form on the Sharing tab has id="section-dashboard-registry". The link adds ?tab=sharing because BeheerTabs renders only the active tab.
  • src/manifest.d/connection-registry.json: index page over integriq/app_connection, requiresApp integriq, admin only, showAdd: false. The menu entry carries query: {app: launchpad} and visibleIf.appInstalled: integriq.
  • Add integration opens /apps/integriq/connections?app=launchpad&link=1 through a handler in src/customComponents.js.
  • Local connectionStatus and connectionSettingsLabel formatters, with limited ("Limited", "Beperkt"). The strings are in English and Dutch.
  • ConnectionReporter and ConnectionObservations. Events are named by string constant behind class_exists. A save sends the refresh first, and the report follows.
  • Reports from StoreService, WeatherService, NewsWidgetService, CalendarWidgetService, LiveTileService and HealthPingService.
  • Event stubs in tests/Stubs/Integriq/, loaded by tests/bootstrap.php and declared in psalm.xml.
  • tests/e2e/integrations-page.spec.ts, and CI installs integriq through additional-apps.

Contract misfits

  • An allow-list is a JSON list. D4 counts "", false, 0 and null as empty, not []. A fail-closed list that allows nothing would read Configured, so it cannot be requiredConfig. A report after a refusal says it instead.
  • No settings screen. Five of six rows are set with occ only. They carry no settingsUrl, and their messages name the key.
  • A source chosen per widget. The weather source depends on whether a placement has a location. reportedOnly plus reports is the closest fit.
  • A family row shows one address. The last fetch stands for every widget. One dead feed among working ones can move the row between Configured and Error, at most every five minutes.
  • A key set with occ sends no refresh. Integriq's hourly resolver pass covers it.

Verified

  • openspec validate adopt-connection-registry --strict: valid.
  • The declaration validates against integriq's schema on development (the vendored copy matches it byte for byte after formatting). The gate-116 checker, run by hand against that schema, reports 0 findings.
  • Unit tests for the reporter, the mapper, and all six callers running with the real reporter behind them. One test runs 50 health pings in an hour and gets one report.
  • I broke each new guard once and watched its test go red on its assertion: class check, the report skip without integriq, both throttle windows, the refresh key match, memory clearing on refresh, the listener catch on the refresh path, memory written only after a send, the unknown key, host only, the non-empty allow-list, per-address statuses, and the report calls in the store, weather, health ping and news services. That run turned up three weak tests, now fixed. One mutant survives by design: on the report path, the catch in send() sits inside a second catch-all.
  • Diff check (diff-check.sh --base origin/development) at c934e99: exit 0, GREEN. 0 NEW findings from gates, php -l, phpcs, phpstan, phpunit, eslint and stylelint.
  • COMPOSER_PROCESS_TIMEOUT=0 composer check:strict at c934e99: exit 0, "ALL CHECKS PASSED". PHPUnit 1,756 tests, 4,748 assertions, 12 skipped. Psalm and PHPStan: no errors.
  • npm run lint: exit 0.
  • Vitest connectionRegistry.spec.js: 12 passed.

Not verified

  • The e2e spec is written but has not run. It needs integriq with hydra#674 on the instance (tasks.md 5.1).
  • No deploy and no browser run. The page, the menu entry and the handler are checked only by vitest.
  • The report paths ran against doubles of the HTTP client, the dispatcher and app config, not against a live integriq listener.

Inherited findings

  • phpcs inside check:strict: 681 warnings for missing @spec tags on classes this change does not touch. No errors.
  • Gate 23 (OpenRegister abstractions, warning only): RoleFeaturePermissionService and PermissionService are app-local permission services.
  • Gate 53 (manifest cross-reference, warning only): register integriq is not in LaunchPad's register JSON. That is expected for a page over another app's register.
  • Gate 19 reported "not applicable" because it only reads openspec/specs/, so the new change spec's @e2e tags were not checked by the gate.
  • Gate 116 is not in the vendored conduction/hydra-gates, so I ran its checker by hand (see Verified).

🤖 Generated with Claude Code

@rubenvdlinde
rubenvdlinde merged commit 7b7190d into development Sep 15, 2026
31 of 32 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/launchpad @ d1e9a84

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
format
check-schema-l10n
composer ✅ 104/104
npm ✅ 534/534
app:check-code ⏭️
info.xml
REUSE
lockfile sync
PHPUnit
Newman
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it on any branch from the Actions tab, or locally with npx playwright test.
Hydra gates

Quality workflow — 2026-09-15 04:46 UTC

Download the full PDF report from the workflow artifacts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant