feat(openspec): 41 IntraVox-derived spec proposals (foundation → glue)#96
feat(openspec): 41 IntraVox-derived spec proposals (foundation → glue)#96rubenvdlinde wants to merge 5 commits into
Conversation
…glue)
41 OpenSpec change proposals derived from a clean-room behavioural analysis
of the IntraVox Nextcloud app, reframed under the unified-dashboard model
(IntraVox features land as widgets/extensions on existing MyDash dashboards
rather than as a parallel "page" concept).
Each change folder contains: proposal.md + tasks.md + specs/<cap>/spec.md.
All 41 pass `openspec validate --strict`.
Foundation (3): groupfolder-storage-backend, dashboard-tree,
dashboard-metadata-fields.
Lifecycle (5): dashboard-templates, dashboard-locking, dashboard-versioning,
dashboard-draft-published, dashboard-language-content.
Engagement (4): dashboard-comments, dashboard-reactions, dashboard-rss-feeds,
dashboard-public-share.
Widget extensions (4): text-widget-markdown, text-widget-tables,
image-widget-media-picker, link-button-widget-list-mode.
New widgets (10): calendar-widget, people-widget, news-widget, files-widget,
video-widget, divider-widget, links-widget, header-widget, menu-widget,
quicklinks-widget.
Org UX (5): footer-customization, nc-unified-search-integration,
navigation-editor-org, demo-data-showcases, admin-roles.
Maintenance (5): dashboard-export-import, confluence-html-import,
dashboard-view-analytics, dashboard-bulk-operations, orphaned-data-cleanup.
Glue (5): setup-wizard, cli-commands, background-job-feed-refresh,
activity-feed-integration, dashboard-cascade-events.
No vendor names ("intravox", "intra", "voxcloud") appear in any spec —
capabilities are described in our own neutral terms.
123 files, ~12K lines of spec across the 41 changes.
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 498/498 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-01 15:55 UTC
Download the full PDF report from the workflow artifacts.
Each of the 9 specs flagged HIGH in the triage now has a sibling
design.md that records the IntraVox source-derived decisions, evidence,
and remaining open follow-ups. Six of those nine specs needed material
rewrites where the original assumptions turned out to be wrong.
Specs rewritten (6):
* dashboard-locking — no clientId column, no expiresAt column
(uses updated_at + LOCK_TIMEOUT_MINUTES = 15), same-user acquire
is re-entrant, heartbeat is PUT not POST, admin-override is
force-release not force-acquire, audit via PSR logger not Activity.
* people-widget — field names corrected (jobTitle→role,
department→organisation), birthdate is ISO string not int,
pagination is offset+hasMore not cursor, scope-filtering downgraded
from MUST to SHOULD with v1 follow-up note (source has none),
showFields map replaces cardFields array, columns config added.
* confluence-html-import — index.html is NOT the hierarchy source
(breadcrumbs inside each page are), body selector is a 6-element
XPath waterfall, macros render as rich widgets (panel/code/expand)
not plain placeholders, and link rewriting / image upload / dry-run
/ async / CLI are now explicitly annotated as MyDash additions
rather than implied as ports.
* navigation-editor-org — storage is per-language JSON files on the
Nextcloud filesystem (5 MB cap), not a 64 KB app-config blob. v1
ships nl + en as independent trees with ?lang= query param on
GET/PUT (default nl). groupVisibility annotated as MyDash addition
(the source uses GroupFolder ACL).
* demo-data-showcases — bundled as ZIP archives (showcases/{id}/{id}.zip
containing export.json + per-locale page directories + _media/),
not flat JSON. Real showcase IDs (de-bron, de-linden, gemeente-duin,
horizon-labs, van-der-berg) replace fictional placeholders. NL-only
in v1. Widget schema is row-based (column/order ints + inline fields),
not position{x,y,w,h}+config. --force flag added to install CLI.
* dashboard-cascade-events — REQ-CSC-007 rewritten from
failure-recording table to log-and-continue (the source has no
failure table; orphan-cleanup picks up stragglers). NOTE annotations
added throughout marking which listeners are reference-derived
(only the comments listener) vs MyDash additions (the other 9 +
TreeListener + GroupDeletedListener).
Specs that mostly held up (3, design.md only):
* groupfolder-storage-backend — chose hard-fail over dual-read because
the source app's DB index has zero content column; dual-read is
architecturally impossible.
* dashboard-language-content — separate-row design correct, but the
three-tier locale matching collapses to two-step because Nextcloud
stores nl_NL (underscore) and the source always truncates to two
characters before lookup.
* calendar-widget — server-side RRULE expansion confirmed,
cross-source dedup intentionally avoided (source hides NC ICS
subscriptions from the picker), SSRF guard via DNS check rather
than hostname allow-list.
All 41 changes still pass openspec validate --strict.
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 498/498 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-01 17:19 UTC
Download the full PDF report from the workflow artifacts.
Each of the 10 specs flagged MEDIUM in the triage now has a sibling design.md that records the source-derived decisions, evidence, and recommended spec deltas. Eight of the ten resolved a concrete behavioural question against the source app; two are deliberate divergences where MyDash improves on the reference. Concrete behaviour calls made: * dashboard-public-share — Throttle is IP-global across all share actions (matches source), with two action buckets: mydash_share_access (60/60s) and mydash_share_password (10/60s). Returns HTTP 429 on trip. * dashboard-bulk-operations — Hard delete (matches source) but cascade=false is the default. Parent-with-children + cascade=false returns HTTP 409. Aligns with dashboard-tree's existing opt-in cascade convention. Soft-delete deferred to a future capability. * background-job-feed-refresh — SimpleXMLElement (PHP built-in) with LIBXML_NOCDATA | LIBXML_NONET, not SimplePie. Wrapped behind a FeedParserInterface so SimplePie remains a drop-in upgrade if customers report parsing edge cases. Cache TTL 60min (vs source's 15min). 10 MB response cap. Deliberate divergences from the source (kept on MyDash track): * dashboard-templates — Templates remain type='admin_template' rows in oc_mydash_dashboards (existing admin-templates capability). Source uses filesystem folders under _templates/ per language; rejected because (1) admin-templates is already shipped and switching breaks existing REQ-TMPL-001..011, (2) DB enum is a single indexed query vs filesystem path-prefix scan, (3) MyDash has two storage backends (db / groupfolder) and filesystem-only doesn't fit cross-backend. * dashboard-view-analytics — Daily-rotating salt for unique-viewer dedup (rotated by SaltRotationJob at UTC midnight, no historical retention), not the source's static config.secret hash. Privacy improvement: makes cross-day re-identification computationally infeasible if the analytics table leaks. Annotation passes (MyDash design beyond source): * admin-roles — Source has no role concept (only GroupFolder ACL bitmask). MyDash's Admin/Editor/Viewer roles are net-new, stored in oc_mydash_role_assignments table. Effective-role resolution: NC admin always wins; otherwise direct user assignment overrides group assignment; max-privilege wins among multiple group hits. * setup-wizard — Source ships CLI-only (occ intravox:setup); the 7-step frontend wizard is entirely a MyDash addition. Each step embeds the corresponding sibling capability's admin UI rather than duplicating settings surfaces. * activity-feed-integration — 13-event catalogue: 5 source-derived (dashboard_created/updated/deleted/commented/reacted) + 8 MyDash additions emerging from sibling capabilities (publication, sharing, public-share, versioning, locking, roles). page_viewed intentionally NOT in the Activity stream — view tracking is analytics-only via dashboard-view-analytics. 15-min debounce on high-volume types prevents spam at default-group scale. * footer-customization — Source has global per-language footer only; per-dashboard override (dashboardFooterMode: 'inherit'|'hidden'| 'custom') is a MyDash addition. Multi-language fallback chain: viewer NC locale > dashboard primary language > first key. Source-confirmed (no spec change beyond NOTE annotations): * dashboard-draft-published — Migration adds publicationStatus column with DEFAULT 'published' (matches source's lazy-backfill pattern). New dashboards default to 'draft' via application code, not column default. PublicationSettingsService name is reserved in the source for an unrelated MetaVox concern; MyDash should pick a different class name (suggested: DashboardPublicationService). Specs already aligned with source (no design.md needed): * dashboard-versioning — Uses OCP\IVersionManager exactly as spec assumes. * dashboard-rss-feeds — One-token-per-user enforced via generateToken() pre-delete, matches spec. 10 design.md files added (~1230 lines total). All 41 changes still pass openspec validate --strict.
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 498/498 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-01 17:36 UTC
Download the full PDF report from the workflow artifacts.
Each of the 10 MEDIUM design.md files from commit fbc36d5 listed a "Spec changes implied" section. This commit applies those deltas to the corresponding spec.md files. All 10 specs still pass openspec validate --strict. Material rewrites (4 specs — scenarios changed, not just NOTEs): * dashboard-public-share — Throttle is IP-global across all share actions, not per-share-per-IP. Two action buckets: mydash_share_access (60/60s) and mydash_share_password (10/60s). Trip returns HTTP 429. REQ-PSHR-009 + REQ-PSHR-005 scenarios rewritten; REQ-PSHR-010 service-account note added. * dashboard-bulk-operations — Hard delete (no soft-delete flag), cascade=false default. Parent-with-children + cascade=false → HTTP 409 with {error: "has_children", childCount: N}. Two new scenarios for cascade behaviour; 4 existing scenarios updated to drop "soft-delete" wording. * admin-roles — REQ-ROLE-005 algorithm rewritten as ordered 4-step (NC admin override → direct user assignment used as-is → max of matching group assignments → null). REQ-ROLE-009 reconciled with -005 (direct assignment is sole source when present, not a rank comparison). 8 NOTE annotations across 4 requirements. * background-job-feed-refresh — Parser pinned as simplexml_load_string + LIBXML_NOCDATA | LIBXML_NONET (PHP built-in, not SimplePie). FeedParserInterface kept as drop-in upgrade path. IClientService 10s/30s timeouts pinned. 10MB response cap pinned. NOTE-only annotations (6 specs — bodies unchanged, NOTEs added to mark deliberate divergence or pin concrete config): * dashboard-templates — Top-of-spec divergence block added documenting the deliberate keep-DB-enum decision vs source's filesystem-folder model. 3 additional NOTEs on REQ-TMPL-012/013/015 pinning composite index, deep-copy semantics, and custom-icon-upload shape reuse. * dashboard-draft-published — Migration column DEFAULT changed from 'draft' to 'published' (lazy-backfill at re-index time). New dashboards default to 'draft' via DashboardService::createDashboard() application code, not column default. * activity-feed-integration — 13-event catalogue confirmed (5 source + 8 MyDash). dashboard_viewed explicitly excluded with rationale. Four-scope audience table pinned as normative (REQ-ACT-004). 900-second debounce TTL made normative in REQ-ACT-007 + REQ-ACT-008 bodies. * footer-customization — REQ-FTR-006 marked MyDash-original (per-dashboard override is beyond the source's global-only model). REQ-FTR-007 fallback chain pinned as normative three-step (viewer locale → dashboard primary language → first key); locale-fallback scenario split into per-step sub-scenarios. * dashboard-view-analytics — REQ-ANLT-002/003 pin daily-rotating salt + SaltRotationJob (UTC midnight, no history). TTL is seconds-until-next-UTC-midnight, not fixed 86400s. REQ-ANLT-009 retention clamp 30-3650 days documented. * setup-wizard — 4 NOTEs added (REQ-WIZ-001/002/008/010) marking this as net-new MyDash, the per-step embed pattern, the intentional auto-launch SHOULD/MAY scope, and the cli-commands YAML schema cross-reference. All 41 changes still pass openspec validate --strict.
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 498/498 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-01 21:15 UTC
Download the full PDF report from the workflow artifacts.
…overage) The conduction OpenSpec schema marks design.md as a required artifact for every change. After the HIGH (9) and MEDIUM (10) deep-dives, 22 specs still lacked a design.md — the LOW-triaged ones that the audit judged decisive enough to implement against as-is. This commit completes coverage: every one of the 41 changes now has proposal + tasks + spec + design. The 22 design.md files are intentionally tighter than the HIGH/MEDIUM ones (50-100 lines each, ~1.9K lines total). They document the implementation choices the spec implies but doesn't pin (which library, which OCP interface, which file layout), acknowledge any deliberate divergences from the source app, and capture open follow-ups that don't block implementation. Files added (one per slug, all under openspec/changes/<slug>/design.md): Dashboard core (6): * dashboard-tree, dashboard-metadata-fields, dashboard-versioning, dashboard-comments, dashboard-reactions, dashboard-rss-feeds Widget extensions (4): * text-widget-markdown, text-widget-tables, image-widget-media-picker, link-button-widget-list-mode Content widgets (3): * news-widget, files-widget, video-widget Simple widgets (5): * divider-widget, links-widget, header-widget, menu-widget, quicklinks-widget System / glue (4): * nc-unified-search-integration, dashboard-export-import, orphaned-data-cleanup, cli-commands Each follows the conduction-schema design.md instruction: Context, Goals/Non-Goals, Decisions (with Decision/Alternatives/Rationale per decision), Risks/Trade-offs, Open follow-ups. All 41 changes now pass openspec validate --strict and have full artifact coverage per the conduction schema (proposal + spec + design + tasks). Apply-ready.
Quality Report — ConductionNL/mydash @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 498/498 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-01 21:38 UTC
Download the full PDF report from the workflow artifacts.
|
Superseded — all 25 spec names in this PR already exist in dev (either as active proposals in Audit:
|
Summary
41 OpenSpec change proposals derived from a clean-room behavioural analysis of the IntraVox Nextcloud app, reframed under the unified-dashboard model — IntraVox features land as widgets/extensions on existing MyDash dashboards rather than as a parallel "page" concept.
Each change folder contains:
proposal.md+tasks.md+specs/<cap>/spec.md. All 41 passopenspec validate --strict.This PR is specs-only — no implementation. Implementation will follow in dependency-aware waves once the proposals are reviewed.
Capability inventory
Foundation (3) — everything else depends on these:
groupfolder-storage-backend— abstract dashboard CONTENT storage so it CAN live as JSON in a managed GroupFolderdashboard-tree— parent/child hierarchy on dashboards, slugs, breadcrumbs (delta todashboards, REQ-DASH-011..018)dashboard-metadata-fields— admin-defined custom fields per dashboardLifecycle (5):
dashboard-templates— gallery + save-as-template (delta toadmin-templates, REQ-TMPL-012..015)dashboard-locking— concurrent-edit guard with admin overridedashboard-versioning— version history + restore (delegates to NC Files versioning when on GroupFolder)dashboard-draft-published— draft / published / scheduled workflow (delta todashboards, REQ-DASH-019..025)dashboard-language-content— per-language content variants (delta todashboards, REQ-DASH-026..032)Engagement (4):
dashboard-comments— threaded one-level-deep via NCICommentsManagerdashboard-reactions— emoji reactions with admin allow-listdashboard-rss-feeds— per-user RSS token; ACL-respecting public feeddashboard-public-share— anonymous render with optional passwordWidget extensions (4) — each is a NEW capability since the parent widget isn't yet promoted to
openspec/specs/:text-widget-markdown,text-widget-tables,image-widget-media-picker,link-button-widget-list-modeNew widgets (10):
calendar-widget,people-widget,news-widget,files-widget,video-widget,divider-widget,links-widget,header-widget,menu-widget,quicklinks-widgetOrg UX (5):
footer-customization,nc-unified-search-integration,navigation-editor-org,demo-data-showcases,admin-rolesMaintenance (5):
dashboard-export-import,confluence-html-import,dashboard-view-analytics,dashboard-bulk-operations,orphaned-data-cleanupGlue (5):
setup-wizard,cli-commands,background-job-feed-refresh,activity-feed-integration,dashboard-cascade-eventsCoordination
dashboardscapability are pre-allocated to avoid clash:dashboard-treeREQ-DASH-011..018,dashboard-draft-published019..025,dashboard-language-content026..032.news-widgetreads from the cache populated bybackground-job-feed-refresh;dashboard-cascade-eventsdefinesDashboardDeletedEventand the listener pattern that every dependent-data capability hooks into.Decisions baked in
mydash.content_storage = groupfolder) — DB-backed remains the default. Seegroupfolder-storage-backend.Hygiene
Test plan
cd mydash && openspec validate <slug> --strictpasses for each of the 41 slugs (already verified at author time).mydash/docs/intravox-analysis.md, gitignored).