Skip to content

fix(manifest): validate against the declared schema; drop the dead Settings widget - #59

Merged
rubenvdlinde merged 1 commit into
developmentfrom
fix/manifest-validator-schema-selection
Aug 7, 2026
Merged

fix(manifest): validate against the declared schema; drop the dead Settings widget#59
rubenvdlinde merged 1 commit into
developmentfrom
fix/manifest-validator-schema-selection

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What this is

check:manifest reported 21 errors on a manifest that renders correctly and passes its own declared schema with zero errors. I looked at the rendered pages before changing anything, because the proposed fix (migrating content into props) would have broken working pages.

The widgets render — content / icon / integrationId are live

The premise was that CnDetailPage reads only w.id and ...(def?.props || {}), so the content blobs were inert. That props spread is getIntegrationProps() — the integration-only path, two lines below resolveRegistryWidget(def.integrationId, this.surface). The general path is different:

  • widgetContentFor(item) returns def.content and feeds it to every catalog widget
  • findWidget(item).icon is passed to <CnIcon :name> in the #title-icon slot
  • resolveRegistryWidget(def.integrationId, surface) resolves the Files integration on DocumentDetail

All four detail pages were checked in the browser. Every widget renders its content. The decisive one: PortalAccountDetail's session list shows "No sessions issued yet", and

emptyText() { return this.content.emptyText || t('nextcloud-vue', 'No items') }

That string exists exactly once in the whole repo — in content.emptyText. If content were inert the widget would say "No items".

So the manifest widgets are left untouched. icon is an MDI component name ("ShieldCheckOutline"), which is neither iconClass (a CSS class) nor iconUrl — the proposed rename would have silently blanked every widget icon.

The real cause: the validator picked the wrong schema

tests/validate-manifest.js hardcoded the v1 schema file. portaliq's manifest declares v2, so every widget was checked against v1's $defs.widgetDefadditionalProperties: false, and no content / icon / integrationId.

The validator now selects the schema variant from the manifest's own $schema, which is what openconnector and scholiq already do.

Positive control — pointing $schema at v1 makes it select v1 and fail, so the pass is meaningful:

$schema variant selected result
app-manifest-v2.schema.json (actual) v2 (2.19.0) PASS, 0 errors
app-manifest.schema.json (injected) v1 (1.8.0) exit 1, 21 errors

The structural-lint fallback's page-type allowlist was also stale (missing form, map, roadmap, search, wiki). It is synced to the schema enum — and roadmap was verified to actually render, not just to validate.

A real user-facing bug, found by looking

The Settings page carried a page-level widgets[] entry with widgetKey: "version-info". Nothing is registered in src/registry.js (registry.spec reports 0 custom widget entries), so it rendered a visible error box to users:

BeforeApplication Name: App Template, and an orange "Widget unavailable / version-info" box under Save.
AfterApplication Name: Portaliq, no error box, Version Information card intact, 0 console errors.

It duplicated config.sections[0].widgets[0], which already renders the Version Information card — so the entry is removed rather than registered (registering it would render the card twice). The "App Template" scaffolding placeholder is corrected to "Portaliq".

Proven by an A/B on one instance: the bundle was patched in-flight in the browser, so the same deployed bytes produced both states. Clearing the cache brings the error box back, confirming the shared dev instance was never modified.

Checks

  • check:specs (json-strict + manifest-v2 + register + registry) — PASS
  • check:manifest — PASS, and demonstrated to still fail on a bad manifest
  • No waiver, no .skip, no continue-on-error; no widget data deleted

🤖 Generated with Claude Code

…ttings widget

check:manifest reported 21 errors on a manifest that renders correctly and
passes its own declared schema with zero errors.

tests/validate-manifest.js hardcoded the v1 schema file. portaliq's manifest
declares v2 (`$schema` -> app-manifest-v2.schema.json), so every widget was
checked against v1's `$defs.widgetDef`, which sets additionalProperties:false
and predates the `content` / `icon` / `integrationId` keys that CnDetailPage
actually renders:

  - widgetContentFor(item) returns def.content and feeds it to every catalog
    widget (CnObjectListWidget's emptyText, the data widget's columns, the
    stats-block entries)
  - findWidget(item).icon is passed to <CnIcon :name> in the #title-icon slot
  - resolveRegistryWidget(def.integrationId, surface) resolves the Files
    integration on DocumentDetail

Verified in the browser on all four detail pages before changing anything:
every widget renders its content. PortalAccountDetail's session list shows
"No sessions issued yet" — that string exists only in content.emptyText; the
component's fallback is "No items". Renaming content -> props would have
broken working pages, so the manifest widgets are left untouched.

The validator now picks the schema variant from the manifest's own $schema,
matching what openconnector and scholiq already do. Positive control: pointing
$schema at v1 makes it select v1 and exit 1 with the original 21 errors.

Separately, page "Settings" carried a page-level widgets[] entry with
widgetKey "version-info". No widget is registered in src/registry.js
(registry.spec reports 0 custom widget entries), so it rendered a visible
"Widget unavailable / version-info" error box below the Save button. It
duplicated config.sections[0].widgets[0], which already renders the Version
Information card, so the entry is removed rather than registered. Its
"App Template" scaffolding placeholder, shown to users as
"Application Name: App Template", is corrected to "Portaliq".
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/portaliq @ 12a9b21

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-specs
check-manifest
composer ✅ 100/100
npm ✅ 871/871
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-07 12:58 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

One red gate, and it is pre-existing

quality / Hydra Gates fails on gate-63 settings-surface (ADR-079 D1), and quality / Quality Report is the aggregate that inherits it. All 27 other checks pass, including the gate this PR exists to fix:

  • quality / Frontend Check (check:manifest)FAIL on developmentPASS here

Gate-63 is not caused by this change

The gate is diff-triggered: it only runs when a manifest changed. This PR changes a manifest, so it ran — and found a violation that predates the PR. Running the gate's own script (hydra-gates/scripts/lib/check_store_and_settings_surface.py) against both trees gives byte-identical output:

$ python3 check_store_and_settings_surface.py <development> --gate settings
FAIL src/manifest.json: page 'Settings' is a type:settings page claiming the platform meaning of 'Settings'. ...
WARN src/manifest.json: app ships an in-app type:settings page AND a lib/Settings/*Admin.php section ...
checked 1 manifest(s): 1 failure(s), 1 warning(s).   RC=1

$ python3 check_store_and_settings_surface.py <this branch> --gate settings
FAIL src/manifest.json: page 'Settings' is a type:settings page claiming the platform meaning of 'Settings'. ...
WARN src/manifest.json: app ships an in-app type:settings page AND a lib/Settings/*Admin.php section ...
checked 1 manifest(s): 1 failure(s), 1 warning(s).   RC=1

The violation is the page's id/title ("Settings"), which this PR does not touch — it only removes a dead widget entry from that page's body and corrects a placeholder string.

Follow-up needed (separate PR)

This is the failure mode the gate's own source comments describe for pipelinq: a single pre-existing violation blocks every manifest-touching PR in the repo, permanently. portaliq is now in that state. Resolving it means renaming or removing the in-app type: "settings" page per ADR-079 D1 — a user-visible navigation change and a product decision, so it is deliberately out of scope here rather than bundled into a validator fix.

@rubenvdlinde
rubenvdlinde merged commit 79bbf68 into development Aug 7, 2026
30 of 32 checks passed
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