feat(widgets): catalog 17 registry-driven types + close persistence loop#143
Open
rubenvdlinde wants to merge 3 commits into
Open
feat(widgets): catalog 17 registry-driven types + close persistence loop#143rubenvdlinde wants to merge 3 commits into
rubenvdlinde wants to merge 3 commits into
Conversation
…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).
Contributor
Quality Report — ConductionNL/mydash @
|
| 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).
Contributor
Quality Report — ConductionNL/mydash @
|
| 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.
WilcoLouwerse
requested changes
May 15, 2026
WilcoLouwerse
left a comment
There was a problem hiding this comment.
🔴 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
contentcolumn 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), andWidgetRenderer.vuehad 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:
Test plan
Known polish items (tracked, not blocking)