Skip to content

feat(widgets): catalog 17 registry-driven types + close persistence loop#143

Open
rubenvdlinde wants to merge 3 commits into
developmentfrom
chore/widget-catalog-docs
Open

feat(widgets): catalog 17 registry-driven types + close persistence loop#143
rubenvdlinde wants to merge 3 commits into
developmentfrom
chore/widget-catalog-docs

Conversation

@rubenvdlinde
Copy link
Copy Markdown
Contributor

Summary

The 17 registry-driven custom widget types (label, text, image, link, nc-widget, header, divider, files, people, quicklinks, news, video, calendar, links, menu, container, tile) had a full Vue form + renderer pipeline but the per-type configuration had nowhere to land — the content column the specs assumed didn't exist, the controller/service layer didn't accept content, the frontend store packed {type, content} into the wrong field (causing a 500), and WidgetRenderer.vue had no branch that mounted the registry-driven Vue components. This PR closes that loop end-to-end and documents the catalog with 34 form/rendered screenshots.

Two commits, one PR:

  1. feat(widgets) — migration + entity + services + controller + frontend store + renderer dispatch
  2. docs(widgets) — docs/features/widgets.md expanded with gallery table + per-type sections

Test plan

  • occ upgrade runs the new migration cleanly
  • oc_mydash_widget_placements.content exists and is nullable
  • All 17 widget types add successfully and persist their content payload
  • All 17 widget types render their type-specific Vue component on the dashboard
  • Files widget reads from the new column and lists folder contents
  • Calendar widget surfaces seeded events
  • News widget polls the configured RSS feed

Known polish items (tracked, not blocking)

  • Wrapper still draws a Widget header above chrome-less types (label / divider / header). Data and rendering correct; visual noise only.
  • AddWidgetModal Add button can stay disabled on fresh type switch — validationTick only bumps on update:content, not on type changes.
  • /apps/mydash/{slug} deep-link 404 — byPath API resolves but the page controller 404s.

…ontent

The 17 registry-driven widget types (label, text, image, link, nc-widget,
header, divider, files, people, quicklinks, news, video, calendar, links,
menu, container, tile) all collected per-type configuration via
AddWidgetModal but the configuration had nowhere to land — `oc_mydash_widget_placements`
had no `content` column and no service/controller layer accepted a content
payload. The frontend store also passed the modal's `{type, content}`
payload through as `widgetId`, hitting a 500 on the server's `?string`
type-hint. Even with content saved, WidgetRenderer.vue had no branch that
mounted the registry's renderer components, so all 17 types fell through
to the legacy callback path and rendered nothing meaningful.

This change closes the loop end-to-end:

- Migration `Version001025Date20260508060000` adds a nullable TEXT
  `content` column to `mydash_widget_placements`.
- `WidgetPlacement` exposes `$content` with `getContentArray()` /
  `setContentArray()` helpers (mirrors the existing styleConfig pattern)
  and includes `content` in `jsonSerialize()`.
- `PlacementService::addWidget` and `WidgetService::addWidget` accept an
  optional `?array $content`. `PlacementUpdater::applyDisplayUpdates`
  handles the `content` key on update.
- `WidgetApiController::addWidget` reads `$content` from the request and
  forwards it to the service. The container-depth check it already had
  for `content` keeps working.
- `dashboard.js addWidgetToDashboard` unpacks the modal's `{type, content}`
  payload into the documented API shape (`widgetId` string, `content`
  object) instead of sending the whole object as `widgetId`.
- `WidgetRenderer.vue` gains a registry dispatch branch that mounts the
  type's Vue renderer with `placement.content` for any `widgetId` that
  resolves to a registry entry, before falling through to tile / API
  widget / legacy paths. `initWidget()` is skipped for registry-driven
  placements so we don't fire spurious `loadWidgetItems` fetches keyed on
  Nextcloud-Dashboard widget ids that don't exist.
- `FilesWidgetController::loadConfig` now reads from the new `content`
  column with a fallback to the legacy `style_config.content` slot, so
  pre-migration rows still resolve.

App version bumped to 1.0.5-unstable.0 so the migration runs.
…nshots

Expands docs/features/widgets.md from a one-paragraph stub into a full
catalog. Each of the 17 registry-driven types (label, text, image, link,
nc-widget, header, divider, files, people, quicklinks, news, video,
calendar, links, menu, container, tile) now has:

- A gallery-table entry linking to its section
- Renderer + form + spec links for navigation into the source
- A configuration table covering every persisted content field with its
  default and notes (constraints, allowed values, validation)
- The Add Widget configuration form screenshot
- The rendered widget screenshot, captured against a "Widget Catalog"
  test dashboard with seeded sample data (3 calendar events, 8 mydash
  test users, an RSS feed pointing at the Nextcloud blog, mp4 sample,
  internal NC logo, etc.)

The doc also documents the storage shape (`oc_mydash_widget_placements`
columns), the REST API surface, and two known polish items:

- The chrome-less types (label / divider / header) still draw a "Widget"
  wrapper title — visual only, data and rendering are correct.
- The Add Widget modal's Add button can stay disabled when the type is
  freshly switched until the user touches a sub-form input — the
  validationTick watcher only bumps on `update:content`, not on `type`
  changes.

Screenshots live at docs/screenshots/widgets/{type}-{form|rendered}.png
(34 PNGs total).
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

Quality Report — ConductionNL/mydash @ 6ed0417

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 100/100
npm ✅ 501/501
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-08 08:51 UTC

Download the full PDF report from the workflow artifacts.

…e on type switch

Two polish items surfaced while building the widget catalog (#143):

1. Chrome-less wrapper title above headings, dividers, and banners.
   `WidgetWrapper` always drew a "Widget"-titled header row, which is
   visual noise above types that own their entire surface — `label`,
   `divider`, `header`, and the registry-driven `tile`. Add an
   `isChromelessType` computed that suppresses the header row for those
   four types unless the user has set an explicit `customTitle`
   (treated as opt-in). Other types are unchanged.

2. Add button stays disabled when type is freshly switched.
   `AddWidgetModal.onTypeSwitch` bumps `validationTick` synchronously,
   but the sub-form is keyed on `state.type` so changing the type
   tears down the old `<component :is>` and remounts a new one;
   `$refs.activeSubForm` is briefly null between teardown and mount.
   The first tick fires while the ref is stale, the composable's
   `validate(subFormRef)` returns `__no-active-form__`, and `isValid`
   stays false until the user touches a field. Bumping the tick again
   on `$nextTick` (after Vue has rebound the ref) lets the computed
   re-run against the freshly-mounted sub-form's `validate()`.

Removes incidental `console.log` calls on `isTileWidget` /
`showHeader` while we're in there (debug breadcrumbs that landed in
production).
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

Quality Report — ConductionNL/mydash @ 5fbb017

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 100/100
npm ✅ 501/501
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-08 10:31 UTC

Download the full PDF report from the workflow artifacts.

Copy link
Copy Markdown

@WilcoLouwerse WilcoLouwerse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blockers

  • 🔴 Dynamic missing :key — Vue 2 may reuse stale instance across type changes
  • 🔴 Double $nextTick validationTick bump races against component unmount during rapid type switching

🟡 Concerns

  • 🟡 content={} sent for all registry widgets even when sub-form defaults are unchanged
  • 🟡 content update path in PUT has no server-side size or depth guard
  • 🟡 placement.content read directly on the placement prop — not reactive in Vue 2 for added properties
  • 🟡 Migration file missing SPDX-License-Identifier header line
  • 🟡 isChromelessType hardcodes 4 types; 'text' widget header is shown but has no title semantics
  • 🟡 Text widget contentMode='html' renders user-supplied HTML — XSS risk not addressed in docs

🟢 Minor

  • 🟢 $contentParam variable referenced but not shown defined in this diff hunk
  • 🟢 setContentArray called before setCreatedAt/setUpdatedAt — ordering inconsistency

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.

2 participants