This repository was archived by the owner on May 29, 2026. It is now read-only.
Add 25 OpenSpec proposals for multi-tenant dashboard platform#42
Merged
Conversation
Contributor
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 342/342 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-04-30 10:59 UTC
Download the full PDF report from the workflow artifacts.
Contributor
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 342/342 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-04-30 11:16 UTC
Download the full PDF report from the workflow artifacts.
This was referenced Apr 30, 2026
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
…RES-001..005
Adds the resource-uploads capability: a small mini file API for
binary branding assets (icons, widget images) that MyDash widgets
need to host directly, separate from the user's Files folder.
- POST /api/resources accepts {base64: 'data:image/<type>;base64,...'}
- Admin-only via IGroupManager::isAdmin (HTTP 403 otherwise)
- Allowed types: jpeg, jpg, png, gif, svg, webp (case-insensitive)
- 5 MB hard cap on decoded bytes, enforced BEFORE invoking the
image library so oversize blobs cannot be loaded
- Raster MIME cross-check via getimagesizefromstring (SVG validation
is delegated to the sibling svg-sanitisation capability)
- Storage via IAppData::getFolder('resources') with high-entropy
resource_<uniqid>.<ext> filenames (folder auto-created on first use)
- Standardised response envelope: success {status, url, name, size};
error {status, error, message} with stable error enum
- Typed exceptions with stable error codes; raw exception messages
never leak to clients
Resource serving (GET) and SVG sanitisation are intentionally out
of scope here — they are delivered by sibling changes resource-serving
and svg-sanitisation in their own PRs.
Spec proposal: PR #42 (feature/replica-spec-proposals)
Requirements covered: REQ-RES-001, 002, 003, 004, 005
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
….005 Centralises every Nextcloud IInitialState::provideInitialState call behind a typed PHP builder (InitialStateBuilder + Page enum + MissingInitialStateException) and mirrors it on the JS side with a typed loadInitialState(page) reader that fills defaults and warns on schema-version mismatch. - lib/Service/InitialStateBuilder.php: typed setters per page key, REQUIRED_KEYS map keyed by Page::WORKSPACE / Page::ADMIN, apply() validates and pushes every key plus _schemaVersion (INITIAL_STATE_SCHEMA_VERSION = 1). - lib/Exception/MissingInitialStateException.php: thrown by apply() with page + missing-key context. - lib/Controller/PageController.php and lib/Settings/MyDashAdmin.php: refactored to construct the builder and call apply() — the only sanctioned call sites. - src/utils/loadInitialState.js: per-page key/default tables mirroring the spec Data Model exactly; emits a console.warn on _schemaVersion mismatch. - src/main.js and src/admin.js: load via the reader and provide() each key by identical name (no renaming at the boundary, no ref/reactive wrapping). - tests/Unit/Service/InitialStateBuilderTest.php: per-page apply pushes every key + schema version, missing required key throws, schema-version always pushed (5 tests / 28 assertions). - tests/Unit/Service/InitialStateContractLintTest.php: PHP grep that fails when any file under lib/ outside InitialStateBuilder.php calls provideInitialState() directly. - tests/bootstrap.php: register vendor/nextcloud/ocp PSR-4 autoload as fallback when running outside the Nextcloud container so OCP-interface mocks resolve. PR #42 owns the spec text on origin/feature/replica-spec-proposals. The matching JS-side Vitest scan (loadState\(['"]mydash) is deferred pending PR #43 (chore/add-vitest-setup); a TODO comment in InitialStateContractLintTest references the follow-up.
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
…SET-012..014
Adds the foundational global admin setting `group_order` (JSON `string[]`
in `oc_mydash_admin_settings`) plus two admin-only HTTP endpoints needed by
the upcoming `group-routing` change (REQ-TMPL-012):
- AdminSetting::KEY_GROUP_ORDER constant.
- AdminSettingsService::getGroupOrder() — defensive read; missing row,
null, empty string, corrupt JSON, or non-array decode all resolve to []
without throwing (REQ-ASET-012 corrupt-JSON scenario).
- AdminSettingsService::setGroupOrder(array) — validates non-empty
strings, deduplicates first-wins preserving order, persists as JSON.
- AdminController::listGroups() — GET /api/admin/groups, returns
{active, inactive, allKnown}; inactive sorted by displayName, stale IDs
preserved in active so the admin can remove them (REQ-ASET-013).
- AdminController::updateGroupOrder() — POST /api/admin/groups, replaces
wholesale, 400 on validation failure (REQ-ASET-014).
- Both endpoints admin-gated via IGroupManager::isAdmin (403 on
non-admin, 401 on unauthenticated). The inactive list reveals every
group on the system, so GET is admin-only too.
Routes registered in appinfo/routes.php. Unit tests cover corrupt-JSON
fallback, dedup order preservation, replace-wholesale semantics,
admin-guard 403/401, and the disjoint+exhaustive listGroups contract
including stale-ID surfacing.
Pre-existing quality issues fixed in passing:
- 4 NamedParametersSniff false-positives on Entity __call setters
(AdminSetting, Dashboard, ConditionalRule, WidgetPlacement) — guarded
with phpcs:ignore + a comment explaining why named args break Entity
__call.
- DashboardService MissingImport — added `use WidgetPlacement` and
removed the FQCN at the call site.
Spec: #42 (feature/replica-spec-proposals).
Frontend drag-drop UI is out of scope for this PR; ships as a follow-up.
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
…RES-001..005
Adds the resource-uploads capability: a small mini file API for
binary branding assets (icons, widget images) that MyDash widgets
need to host directly, separate from the user's Files folder.
- POST /api/resources accepts {base64: 'data:image/<type>;base64,...'}
- Admin-only via IGroupManager::isAdmin (HTTP 403 otherwise)
- Allowed types: jpeg, jpg, png, gif, svg, webp (case-insensitive)
- 5 MB hard cap on decoded bytes, enforced BEFORE invoking the
image library so oversize blobs cannot be loaded
- Raster MIME cross-check via getimagesizefromstring (SVG validation
is delegated to the sibling svg-sanitisation capability)
- Storage via IAppData::getFolder('resources') with high-entropy
resource_<uniqid>.<ext> filenames (folder auto-created on first use)
- Standardised response envelope: success {status, url, name, size};
error {status, error, message} with stable error enum
- Typed exceptions with stable error codes; raw exception messages
never leak to clients
Resource serving (GET) and SVG sanitisation are intentionally out
of scope here — they are delivered by sibling changes resource-serving
and svg-sanitisation in their own PRs.
Spec proposal: PR #42 (feature/replica-spec-proposals)
Requirements covered: REQ-RES-001, 002, 003, 004, 005
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
….005 Centralises every Nextcloud IInitialState::provideInitialState call behind a typed PHP builder (InitialStateBuilder + Page enum + MissingInitialStateException) and mirrors it on the JS side with a typed loadInitialState(page) reader that fills defaults and warns on schema-version mismatch. - lib/Service/InitialStateBuilder.php: typed setters per page key, REQUIRED_KEYS map keyed by Page::WORKSPACE / Page::ADMIN, apply() validates and pushes every key plus _schemaVersion (INITIAL_STATE_SCHEMA_VERSION = 1). - lib/Exception/MissingInitialStateException.php: thrown by apply() with page + missing-key context. - lib/Controller/PageController.php and lib/Settings/MyDashAdmin.php: refactored to construct the builder and call apply() — the only sanctioned call sites. - src/utils/loadInitialState.js: per-page key/default tables mirroring the spec Data Model exactly; emits a console.warn on _schemaVersion mismatch. - src/main.js and src/admin.js: load via the reader and provide() each key by identical name (no renaming at the boundary, no ref/reactive wrapping). - tests/Unit/Service/InitialStateBuilderTest.php: per-page apply pushes every key + schema version, missing required key throws, schema-version always pushed (5 tests / 28 assertions). - tests/Unit/Service/InitialStateContractLintTest.php: PHP grep that fails when any file under lib/ outside InitialStateBuilder.php calls provideInitialState() directly. - tests/bootstrap.php: register vendor/nextcloud/ocp PSR-4 autoload as fallback when running outside the Nextcloud container so OCP-interface mocks resolve. PR #42 owns the spec text on origin/feature/replica-spec-proposals. The matching JS-side Vitest scan (loadState\(['"]mydash) is deferred pending PR #43 (chore/add-vitest-setup); a TODO comment in InitialStateContractLintTest references the follow-up.
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
…SET-012..014
Adds the foundational global admin setting `group_order` (JSON `string[]`
in `oc_mydash_admin_settings`) plus two admin-only HTTP endpoints needed by
the upcoming `group-routing` change (REQ-TMPL-012):
- AdminSetting::KEY_GROUP_ORDER constant.
- AdminSettingsService::getGroupOrder() — defensive read; missing row,
null, empty string, corrupt JSON, or non-array decode all resolve to []
without throwing (REQ-ASET-012 corrupt-JSON scenario).
- AdminSettingsService::setGroupOrder(array) — validates non-empty
strings, deduplicates first-wins preserving order, persists as JSON.
- AdminController::listGroups() — GET /api/admin/groups, returns
{active, inactive, allKnown}; inactive sorted by displayName, stale IDs
preserved in active so the admin can remove them (REQ-ASET-013).
- AdminController::updateGroupOrder() — POST /api/admin/groups, replaces
wholesale, 400 on validation failure (REQ-ASET-014).
- Both endpoints admin-gated via IGroupManager::isAdmin (403 on
non-admin, 401 on unauthenticated). The inactive list reveals every
group on the system, so GET is admin-only too.
Routes registered in appinfo/routes.php. Unit tests cover corrupt-JSON
fallback, dedup order preservation, replace-wholesale semantics,
admin-guard 403/401, and the disjoint+exhaustive listGroups contract
including stale-ID surfacing.
Pre-existing quality issues fixed in passing:
- 4 NamedParametersSniff false-positives on Entity __call setters
(AdminSetting, Dashboard, ConditionalRule, WidgetPlacement) — guarded
with phpcs:ignore + a comment explaining why named args break Entity
__call.
- DashboardService MissingImport — added `use WidgetPlacement` and
removed the FQCN at the call site.
Spec: #42 (feature/replica-spec-proposals).
Frontend drag-drop UI is out of scope for this PR; ships as a follow-up.
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
….005 (#45) Centralises every Nextcloud IInitialState::provideInitialState call behind a typed PHP builder (InitialStateBuilder + Page enum + MissingInitialStateException) and mirrors it on the JS side with a typed loadInitialState(page) reader that fills defaults and warns on schema-version mismatch. - lib/Service/InitialStateBuilder.php: typed setters per page key, REQUIRED_KEYS map keyed by Page::WORKSPACE / Page::ADMIN, apply() validates and pushes every key plus _schemaVersion (INITIAL_STATE_SCHEMA_VERSION = 1). - lib/Exception/MissingInitialStateException.php: thrown by apply() with page + missing-key context. - lib/Controller/PageController.php and lib/Settings/MyDashAdmin.php: refactored to construct the builder and call apply() — the only sanctioned call sites. - src/utils/loadInitialState.js: per-page key/default tables mirroring the spec Data Model exactly; emits a console.warn on _schemaVersion mismatch. - src/main.js and src/admin.js: load via the reader and provide() each key by identical name (no renaming at the boundary, no ref/reactive wrapping). - tests/Unit/Service/InitialStateBuilderTest.php: per-page apply pushes every key + schema version, missing required key throws, schema-version always pushed (5 tests / 28 assertions). - tests/Unit/Service/InitialStateContractLintTest.php: PHP grep that fails when any file under lib/ outside InitialStateBuilder.php calls provideInitialState() directly. - tests/bootstrap.php: register vendor/nextcloud/ocp PSR-4 autoload as fallback when running outside the Nextcloud container so OCP-interface mocks resolve. PR #42 owns the spec text on origin/feature/replica-spec-proposals. The matching JS-side Vitest scan (loadState\(['"]mydash) is deferred pending PR #43 (chore/add-vitest-setup); a TODO comment in InitialStateContractLintTest references the follow-up.
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
…SET-012..014 (#44) Adds the foundational global admin setting `group_order` (JSON `string[]` in `oc_mydash_admin_settings`) plus two admin-only HTTP endpoints needed by the upcoming `group-routing` change (REQ-TMPL-012): - AdminSetting::KEY_GROUP_ORDER constant. - AdminSettingsService::getGroupOrder() — defensive read; missing row, null, empty string, corrupt JSON, or non-array decode all resolve to [] without throwing (REQ-ASET-012 corrupt-JSON scenario). - AdminSettingsService::setGroupOrder(array) — validates non-empty strings, deduplicates first-wins preserving order, persists as JSON. - AdminController::listGroups() — GET /api/admin/groups, returns {active, inactive, allKnown}; inactive sorted by displayName, stale IDs preserved in active so the admin can remove them (REQ-ASET-013). - AdminController::updateGroupOrder() — POST /api/admin/groups, replaces wholesale, 400 on validation failure (REQ-ASET-014). - Both endpoints admin-gated via IGroupManager::isAdmin (403 on non-admin, 401 on unauthenticated). The inactive list reveals every group on the system, so GET is admin-only too. Routes registered in appinfo/routes.php. Unit tests cover corrupt-JSON fallback, dedup order preservation, replace-wholesale semantics, admin-guard 403/401, and the disjoint+exhaustive listGroups contract including stale-ID surfacing. Pre-existing quality issues fixed in passing: - 4 NamedParametersSniff false-positives on Entity __call setters (AdminSetting, Dashboard, ConditionalRule, WidgetPlacement) — guarded with phpcs:ignore + a comment explaining why named args break Entity __call. - DashboardService MissingImport — added `use WidgetPlacement` and removed the FQCN at the call site. Spec: #42 (feature/replica-spec-proposals). Frontend drag-drop UI is out of scope for this PR; ships as a follow-up.
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
…RES-001..005
Adds the resource-uploads capability: a small mini file API for
binary branding assets (icons, widget images) that MyDash widgets
need to host directly, separate from the user's Files folder.
- POST /api/resources accepts {base64: 'data:image/<type>;base64,...'}
- Admin-only via IGroupManager::isAdmin (HTTP 403 otherwise)
- Allowed types: jpeg, jpg, png, gif, svg, webp (case-insensitive)
- 5 MB hard cap on decoded bytes, enforced BEFORE invoking the
image library so oversize blobs cannot be loaded
- Raster MIME cross-check via getimagesizefromstring (SVG validation
is delegated to the sibling svg-sanitisation capability)
- Storage via IAppData::getFolder('resources') with high-entropy
resource_<uniqid>.<ext> filenames (folder auto-created on first use)
- Standardised response envelope: success {status, url, name, size};
error {status, error, message} with stable error enum
- Typed exceptions with stable error codes; raw exception messages
never leak to clients
Resource serving (GET) and SVG sanitisation are intentionally out
of scope here — they are delivered by sibling changes resource-serving
and svg-sanitisation in their own PRs.
Spec proposal: PR #42 (feature/replica-spec-proposals)
Requirements covered: REQ-RES-001, 002, 003, 004, 005
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
…RES-001..005 (#46) Adds the resource-uploads capability: a small mini file API for binary branding assets (icons, widget images) that MyDash widgets need to host directly, separate from the user's Files folder. - POST /api/resources accepts {base64: 'data:image/<type>;base64,...'} - Admin-only via IGroupManager::isAdmin (HTTP 403 otherwise) - Allowed types: jpeg, jpg, png, gif, svg, webp (case-insensitive) - 5 MB hard cap on decoded bytes, enforced BEFORE invoking the image library so oversize blobs cannot be loaded - Raster MIME cross-check via getimagesizefromstring (SVG validation is delegated to the sibling svg-sanitisation capability) - Storage via IAppData::getFolder('resources') with high-entropy resource_<uniqid>.<ext> filenames (folder auto-created on first use) - Standardised response envelope: success {status, url, name, size}; error {status, error, message} with stable error enum - Typed exceptions with stable error codes; raw exception messages never leak to clients Resource serving (GET) and SVG sanitisation are intentionally out of scope here — they are delivered by sibling changes resource-serving and svg-sanitisation in their own PRs. Spec proposal: PR #42 (feature/replica-spec-proposals) Requirements covered: REQ-RES-001, 002, 003, 004, 005
This was referenced Apr 30, 2026
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
Add a transactional single-default-per-group invariant for
`group_shared` dashboards by reusing the existing `isDefault SMALLINT`
column (no schema migration required).
Changes
- `DashboardMapper::clearGroupDefaults()` and `setGroupDefaultUuid()`:
the two halves of the transactional flip.
- `DashboardService::setGroupDefault()`: admin-only, wraps both writes
in a single `IDBConnection::beginTransaction()` / `commit()` /
`rollBack()` block. Order is SET-target first so the 0-row count
triggers a 404 with the previous default preserved.
- `DashboardService::createGroupShared()`: explicitly forces
`isDefault = 0` on insert (REQ-DASH-016 — defense-in-depth against
payload smuggling).
- `DashboardService::updateGroupShared()`: drops `isDefault` from the
patch before applying (REQ-DASH-017 — PUT can never flip the flag).
- `DashboardApiController::setGroupDefault()` mapped to
`POST /api/dashboards/group/{groupId}/default`. Admin guard is in
the body (matches existing `updateGroup`/`deleteGroup` pattern).
Tests
- 7 service tests cover the transactional invariant (commit on
happy path, rollBack on cross-group 404, rollBack on mid-flip
exception), the non-admin guard, and the create/update field
hardening.
- 6 controller tests cover the route surface — non-admin 403,
anonymous 401, missing-uuid 400, happy path 200, cross-group 404,
and reflection-level checks that `createGroup`/`updateGroup`
signatures do not expose `isDefault` as a parameter.
- `tests/Stubs/DoctrineStubs.php` defines minimal Doctrine DBAL
placeholder classes so PHPUnit's automatic mock generator can
introspect `IDBConnection` outside the Nextcloud container; no-op
inside the container.
Spec source: PR #42 (`feature/replica-spec-proposals`,
`openspec/changes/default-dashboard-flag/`). Builds on PR #47
(`multi-scope-dashboards`, already in `development`).
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
Add a transactional single-default-per-group invariant for
`group_shared` dashboards by reusing the existing `isDefault SMALLINT`
column (no schema migration required).
Changes
- `DashboardMapper::clearGroupDefaults()` and `setGroupDefaultUuid()`:
the two halves of the transactional flip.
- `DashboardService::setGroupDefault()`: admin-only, wraps both writes
in a single `IDBConnection::beginTransaction()` / `commit()` /
`rollBack()` block. Order is SET-target first so the 0-row count
triggers a 404 with the previous default preserved.
- `DashboardService::createGroupShared()`: explicitly forces
`isDefault = 0` on insert (REQ-DASH-016 — defense-in-depth against
payload smuggling).
- `DashboardService::updateGroupShared()`: drops `isDefault` from the
patch before applying (REQ-DASH-017 — PUT can never flip the flag).
- `DashboardApiController::setGroupDefault()` mapped to
`POST /api/dashboards/group/{groupId}/default`. Admin guard is in
the body (matches existing `updateGroup`/`deleteGroup` pattern).
Tests
- 7 service tests cover the transactional invariant (commit on
happy path, rollBack on cross-group 404, rollBack on mid-flip
exception), the non-admin guard, and the create/update field
hardening.
- 6 controller tests cover the route surface — non-admin 403,
anonymous 401, missing-uuid 400, happy path 200, cross-group 404,
and reflection-level checks that `createGroup`/`updateGroup`
signatures do not expose `isDefault` as a parameter.
- `tests/Stubs/DoctrineStubs.php` defines minimal Doctrine DBAL
placeholder classes so PHPUnit's automatic mock generator can
introspect `IDBConnection` outside the Nextcloud container; no-op
inside the container.
Spec source: PR #42 (`feature/replica-spec-proposals`,
`openspec/changes/default-dashboard-flag/`). Builds on PR #47
(`multi-scope-dashboards`, already in `development`).
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
#52) Add a transactional single-default-per-group invariant for `group_shared` dashboards by reusing the existing `isDefault SMALLINT` column (no schema migration required). Changes - `DashboardMapper::clearGroupDefaults()` and `setGroupDefaultUuid()`: the two halves of the transactional flip. - `DashboardService::setGroupDefault()`: admin-only, wraps both writes in a single `IDBConnection::beginTransaction()` / `commit()` / `rollBack()` block. Order is SET-target first so the 0-row count triggers a 404 with the previous default preserved. - `DashboardService::createGroupShared()`: explicitly forces `isDefault = 0` on insert (REQ-DASH-016 — defense-in-depth against payload smuggling). - `DashboardService::updateGroupShared()`: drops `isDefault` from the patch before applying (REQ-DASH-017 — PUT can never flip the flag). - `DashboardApiController::setGroupDefault()` mapped to `POST /api/dashboards/group/{groupId}/default`. Admin guard is in the body (matches existing `updateGroup`/`deleteGroup` pattern). Tests - 7 service tests cover the transactional invariant (commit on happy path, rollBack on cross-group 404, rollBack on mid-flip exception), the non-admin guard, and the create/update field hardening. - 6 controller tests cover the route surface — non-admin 403, anonymous 401, missing-uuid 400, happy path 200, cross-group 404, and reflection-level checks that `createGroup`/`updateGroup` signatures do not expose `isDefault` as a parameter. - `tests/Stubs/DoctrineStubs.php` defines minimal Doctrine DBAL placeholder classes so PHPUnit's automatic mock generator can introspect `IDBConnection` outside the Nextcloud container; no-op inside the container. Spec source: PR #42 (`feature/replica-spec-proposals`, `openspec/changes/default-dashboard-flag/`). Builds on PR #47 (`multi-scope-dashboards`, already in `development`).
5fda513 to
9e204d5
Compare
This was referenced Apr 30, 2026
rubenvdlinde
added a commit
that referenced
this pull request
Apr 30, 2026
* feat(widgets): label widget per REQ-LBL-001..006 Adds a new `label` widget type for short, single-line plain-text headings inside dashboard cells. Distinct from `text-display` (which sanitises HTML and is multi-line): the renderer NEVER uses `v-html` — text is always output via Vue interpolation, eliminating the XSS surface entirely (REQ-LBL-001). - LabelWidget.vue renderer with theme-aware default styling (16px / bold / centred / var(--color-main-text) / transparent bg) and `overflow-wrap: break-word` so long single words wrap (REQ-LBL-002, REQ-LBL-003). Empty/whitespace text shows the localised `Label` fallback (REQ-LBL-004). Wrapper fills cell with 12px padding via flexbox (REQ-LBL-006). - LabelForm.vue sub-form with six controls (text, fontSize, color, backgroundColor, fontWeight select, textAlign select) and a `validate()` method requiring non-empty trimmed text (REQ-LBL-005). - widgetRegistry.js gets a `label` entry with the spec-mandated defaults so the type is selectable in the Add Widget modal (REQ-LBL-007). - Vitest coverage for HTML-as-literal-text, defaults application, long-word wrap, empty placeholder, form validation and registry exposure. - l10n keys `Label`, `Label text is required`, `Font Weight`, `Normal`, `Bold` added to en.json and nl.json. Mirrors PR #50 (text-display-widget) shape but plain-text only. References spec proposals on PR #42. * chore: update SBOM --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…form Adds 25 OpenSpec change proposals modelling a complete dashboard sharing and runtime experience for MyDash. Specs are organised across 4 dependency layers (foundation → extensions → UI surfaces → runtime shell): Dashboards capability (4): multi-scope-dashboards, default-dashboard-flag, active-dashboard-resolution, fork-current-as-personal — adds group_shared type, default flag, 7-step resolution chain, fork-from-current action. Admin (3): group-priority-order, group-routing, allow-personal-dashboards-flag — admin-controlled group ordering, primary-group resolver, runtime gating. Grid layout (2): responsive-grid-breakpoints, widget-collision-placement — 4 viewport breakpoints, moveScale reflow, deterministic add-widget placement. Widgets (5 + 2 modal/menu): text-display-widget, label-widget, image-widget, link-button-widget, nc-dashboard-widget-proxy, widget-add-edit-modal, widget-context-menu — five widget types plus shared add/edit modal and right-click menu. Resources (3): resource-uploads, resource-serving, svg-sanitisation — admin-only base64 upload pipeline, immutable-cached serve route, DOM-based SVG whitelist. Icons (2): dashboard-icons, custom-icon-upload-pattern — curated MDI registry plus URL discriminator for uploaded icons. UI (2): dashboard-switcher-sidebar, runtime-shell — slide-in 3-section sidebar, page-level shell with canEdit gate. Infra (1): initial-state-contract — typed PHP→JS bootstrap contract. Sharing follow-ups (1): dashboard-sharing-followups — extends the existing dashboard-sharing capability with notifications, bulk management, and deletion cascade with admin-retention guard. Includes the baseline dashboard-sharing capability spec under openspec/specs/. All 25 changes pass openspec validate --strict. Implementation (opsx-apply) is the next step for each.
fd1d7c7 to
1ec571d
Compare
Contributor
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 103/103 | |||
| npm | ✅ | ✅ 342/342 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-04-30 18:36 UTC
Download the full PDF report from the workflow artifacts.
rubenvdlinde
added a commit
that referenced
this pull request
May 3, 2026
….005 (#45) Centralises every Nextcloud IInitialState::provideInitialState call behind a typed PHP builder (InitialStateBuilder + Page enum + MissingInitialStateException) and mirrors it on the JS side with a typed loadInitialState(page) reader that fills defaults and warns on schema-version mismatch. - lib/Service/InitialStateBuilder.php: typed setters per page key, REQUIRED_KEYS map keyed by Page::WORKSPACE / Page::ADMIN, apply() validates and pushes every key plus _schemaVersion (INITIAL_STATE_SCHEMA_VERSION = 1). - lib/Exception/MissingInitialStateException.php: thrown by apply() with page + missing-key context. - lib/Controller/PageController.php and lib/Settings/MyDashAdmin.php: refactored to construct the builder and call apply() — the only sanctioned call sites. - src/utils/loadInitialState.js: per-page key/default tables mirroring the spec Data Model exactly; emits a console.warn on _schemaVersion mismatch. - src/main.js and src/admin.js: load via the reader and provide() each key by identical name (no renaming at the boundary, no ref/reactive wrapping). - tests/Unit/Service/InitialStateBuilderTest.php: per-page apply pushes every key + schema version, missing required key throws, schema-version always pushed (5 tests / 28 assertions). - tests/Unit/Service/InitialStateContractLintTest.php: PHP grep that fails when any file under lib/ outside InitialStateBuilder.php calls provideInitialState() directly. - tests/bootstrap.php: register vendor/nextcloud/ocp PSR-4 autoload as fallback when running outside the Nextcloud container so OCP-interface mocks resolve. PR #42 owns the spec text on origin/feature/replica-spec-proposals. The matching JS-side Vitest scan (loadState\(['"]mydash) is deferred pending PR #43 (chore/add-vitest-setup); a TODO comment in InitialStateContractLintTest references the follow-up.
rubenvdlinde
added a commit
that referenced
this pull request
May 3, 2026
…SET-012..014 (#44) Adds the foundational global admin setting `group_order` (JSON `string[]` in `oc_mydash_admin_settings`) plus two admin-only HTTP endpoints needed by the upcoming `group-routing` change (REQ-TMPL-012): - AdminSetting::KEY_GROUP_ORDER constant. - AdminSettingsService::getGroupOrder() — defensive read; missing row, null, empty string, corrupt JSON, or non-array decode all resolve to [] without throwing (REQ-ASET-012 corrupt-JSON scenario). - AdminSettingsService::setGroupOrder(array) — validates non-empty strings, deduplicates first-wins preserving order, persists as JSON. - AdminController::listGroups() — GET /api/admin/groups, returns {active, inactive, allKnown}; inactive sorted by displayName, stale IDs preserved in active so the admin can remove them (REQ-ASET-013). - AdminController::updateGroupOrder() — POST /api/admin/groups, replaces wholesale, 400 on validation failure (REQ-ASET-014). - Both endpoints admin-gated via IGroupManager::isAdmin (403 on non-admin, 401 on unauthenticated). The inactive list reveals every group on the system, so GET is admin-only too. Routes registered in appinfo/routes.php. Unit tests cover corrupt-JSON fallback, dedup order preservation, replace-wholesale semantics, admin-guard 403/401, and the disjoint+exhaustive listGroups contract including stale-ID surfacing. Pre-existing quality issues fixed in passing: - 4 NamedParametersSniff false-positives on Entity __call setters (AdminSetting, Dashboard, ConditionalRule, WidgetPlacement) — guarded with phpcs:ignore + a comment explaining why named args break Entity __call. - DashboardService MissingImport — added `use WidgetPlacement` and removed the FQCN at the call site. Spec: #42 (feature/replica-spec-proposals). Frontend drag-drop UI is out of scope for this PR; ships as a follow-up.
rubenvdlinde
added a commit
that referenced
this pull request
May 3, 2026
…RES-001..005 (#46) Adds the resource-uploads capability: a small mini file API for binary branding assets (icons, widget images) that MyDash widgets need to host directly, separate from the user's Files folder. - POST /api/resources accepts {base64: 'data:image/<type>;base64,...'} - Admin-only via IGroupManager::isAdmin (HTTP 403 otherwise) - Allowed types: jpeg, jpg, png, gif, svg, webp (case-insensitive) - 5 MB hard cap on decoded bytes, enforced BEFORE invoking the image library so oversize blobs cannot be loaded - Raster MIME cross-check via getimagesizefromstring (SVG validation is delegated to the sibling svg-sanitisation capability) - Storage via IAppData::getFolder('resources') with high-entropy resource_<uniqid>.<ext> filenames (folder auto-created on first use) - Standardised response envelope: success {status, url, name, size}; error {status, error, message} with stable error enum - Typed exceptions with stable error codes; raw exception messages never leak to clients Resource serving (GET) and SVG sanitisation are intentionally out of scope here — they are delivered by sibling changes resource-serving and svg-sanitisation in their own PRs. Spec proposal: PR #42 (feature/replica-spec-proposals) Requirements covered: REQ-RES-001, 002, 003, 004, 005
rubenvdlinde
added a commit
that referenced
this pull request
May 3, 2026
#52) Add a transactional single-default-per-group invariant for `group_shared` dashboards by reusing the existing `isDefault SMALLINT` column (no schema migration required). Changes - `DashboardMapper::clearGroupDefaults()` and `setGroupDefaultUuid()`: the two halves of the transactional flip. - `DashboardService::setGroupDefault()`: admin-only, wraps both writes in a single `IDBConnection::beginTransaction()` / `commit()` / `rollBack()` block. Order is SET-target first so the 0-row count triggers a 404 with the previous default preserved. - `DashboardService::createGroupShared()`: explicitly forces `isDefault = 0` on insert (REQ-DASH-016 — defense-in-depth against payload smuggling). - `DashboardService::updateGroupShared()`: drops `isDefault` from the patch before applying (REQ-DASH-017 — PUT can never flip the flag). - `DashboardApiController::setGroupDefault()` mapped to `POST /api/dashboards/group/{groupId}/default`. Admin guard is in the body (matches existing `updateGroup`/`deleteGroup` pattern). Tests - 7 service tests cover the transactional invariant (commit on happy path, rollBack on cross-group 404, rollBack on mid-flip exception), the non-admin guard, and the create/update field hardening. - 6 controller tests cover the route surface — non-admin 403, anonymous 401, missing-uuid 400, happy path 200, cross-group 404, and reflection-level checks that `createGroup`/`updateGroup` signatures do not expose `isDefault` as a parameter. - `tests/Stubs/DoctrineStubs.php` defines minimal Doctrine DBAL placeholder classes so PHPUnit's automatic mock generator can introspect `IDBConnection` outside the Nextcloud container; no-op inside the container. Spec source: PR #42 (`feature/replica-spec-proposals`, `openspec/changes/default-dashboard-flag/`). Builds on PR #47 (`multi-scope-dashboards`, already in `development`).
rubenvdlinde
added a commit
that referenced
this pull request
May 3, 2026
* feat(widgets): label widget per REQ-LBL-001..006 Adds a new `label` widget type for short, single-line plain-text headings inside dashboard cells. Distinct from `text-display` (which sanitises HTML and is multi-line): the renderer NEVER uses `v-html` — text is always output via Vue interpolation, eliminating the XSS surface entirely (REQ-LBL-001). - LabelWidget.vue renderer with theme-aware default styling (16px / bold / centred / var(--color-main-text) / transparent bg) and `overflow-wrap: break-word` so long single words wrap (REQ-LBL-002, REQ-LBL-003). Empty/whitespace text shows the localised `Label` fallback (REQ-LBL-004). Wrapper fills cell with 12px padding via flexbox (REQ-LBL-006). - LabelForm.vue sub-form with six controls (text, fontSize, color, backgroundColor, fontWeight select, textAlign select) and a `validate()` method requiring non-empty trimmed text (REQ-LBL-005). - widgetRegistry.js gets a `label` entry with the spec-mandated defaults so the type is selectable in the Add Widget modal (REQ-LBL-007). - Vitest coverage for HTML-as-literal-text, defaults application, long-word wrap, empty placeholder, form validation and registry exposure. - l10n keys `Label`, `Label text is required`, `Font Weight`, `Normal`, `Bold` added to en.json and nl.json. Mirrors PR #50 (text-display-widget) shape but plain-text only. References spec proposals on PR #42. * chore: update SBOM --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
rubenvdlinde
added a commit
that referenced
this pull request
May 3, 2026
* feat(openspec): add 25 spec proposals for multi-tenant dashboard platform Adds 25 OpenSpec change proposals modelling a complete dashboard sharing and runtime experience for MyDash. Specs are organised across 4 dependency layers (foundation → extensions → UI surfaces → runtime shell): Dashboards capability (4): multi-scope-dashboards, default-dashboard-flag, active-dashboard-resolution, fork-current-as-personal — adds group_shared type, default flag, 7-step resolution chain, fork-from-current action. Admin (3): group-priority-order, group-routing, allow-personal-dashboards-flag — admin-controlled group ordering, primary-group resolver, runtime gating. Grid layout (2): responsive-grid-breakpoints, widget-collision-placement — 4 viewport breakpoints, moveScale reflow, deterministic add-widget placement. Widgets (5 + 2 modal/menu): text-display-widget, label-widget, image-widget, link-button-widget, nc-dashboard-widget-proxy, widget-add-edit-modal, widget-context-menu — five widget types plus shared add/edit modal and right-click menu. Resources (3): resource-uploads, resource-serving, svg-sanitisation — admin-only base64 upload pipeline, immutable-cached serve route, DOM-based SVG whitelist. Icons (2): dashboard-icons, custom-icon-upload-pattern — curated MDI registry plus URL discriminator for uploaded icons. UI (2): dashboard-switcher-sidebar, runtime-shell — slide-in 3-section sidebar, page-level shell with canEdit gate. Infra (1): initial-state-contract — typed PHP→JS bootstrap contract. Sharing follow-ups (1): dashboard-sharing-followups — extends the existing dashboard-sharing capability with notifications, bulk management, and deletion cascade with admin-retention guard. Includes the baseline dashboard-sharing capability spec under openspec/specs/. All 25 changes pass openspec validate --strict. Implementation (opsx-apply) is the next step for each. * feat(dashboard-icons): implement icon registry per REQ-ICON-001..004
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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
Adds 25 OpenSpec change proposals at the proposal + refinement + validation stage modelling a complete dashboard sharing and runtime experience for MyDash. Specs are organised in 4 dependency layers; all 25 pass `openspec validate --strict`.
Includes the baseline `dashboard-sharing` capability spec under `openspec/specs/dashboard-sharing/` (referenced by `dashboard-sharing-followups`).
Capabilities touched
Status
Recommended apply order (dependency layers)
Note on extra commits
This branch was cut from `chore/phpstan-phpunit-screenshots` so the PR diff also shows those 2 unrelated commits — they're separate WIP and will land via their own PR.
Test plan