domma-cms 1.6.59 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/CHANGELOG.md +4344 -0
  2. package/README.md +1 -1
  3. package/admin/dist/domma/domma-tools.css +3 -3
  4. package/admin/dist/domma/domma-tools.min.js +3 -3
  5. package/admin/js/api.js +1 -1
  6. package/admin/js/app.js +2 -2
  7. package/admin/js/lib/draft-owner.js +1 -0
  8. package/admin/js/lib/effect-defs.js +1 -1
  9. package/admin/js/lib/effects-builder.js +2 -2
  10. package/admin/js/lib/page-editor-reference.js +5 -5
  11. package/admin/js/lib/shortcode-tags.js +1 -1
  12. package/admin/js/templates/docs/usage-shortcodes.html +39 -0
  13. package/admin/js/views/user-editor.js +1 -1
  14. package/docs/DommaFeatures.md +3005 -0
  15. package/docs/MultiTenant.md +176 -0
  16. package/docs/api-reference.md +1667 -0
  17. package/docs/cache.md +96 -0
  18. package/docs/configuration.md +1811 -0
  19. package/docs/editions-and-licences.md +190 -0
  20. package/docs/getting-started.md +201 -0
  21. package/docs/guides/form-triggers.md +260 -0
  22. package/docs/guides/mongodb-storage-adapter.md +282 -0
  23. package/docs/guides/multi-page-forms.md +121 -0
  24. package/docs/images/scribe-composer.png +0 -0
  25. package/docs/markdown-shortcodes.md +1738 -0
  26. package/docs/pentest-report-2026-04-03.md +181 -0
  27. package/docs/plans/2026-02-28-admin-topbar-login-forms.md +349 -0
  28. package/docs/plans/2026-02-28-domma-ui-polish.md +286 -0
  29. package/docs/plans/2026-02-28-reset-seed-fresh-commands.md +520 -0
  30. package/docs/plans/2026-03-02-cookie-consent-design.md +86 -0
  31. package/docs/plans/2026-03-02-cookie-consent-plugin.md +453 -0
  32. package/docs/plans/2026-03-02-tooltip-sweep-and-back-to-top-plugin-design.md +126 -0
  33. package/docs/plans/2026-03-02-tooltip-sweep-back-to-top.md +427 -0
  34. package/docs/plans/2026-03-05-cookie-consent-btt-core.md +115 -0
  35. package/docs/plans/2026-03-05-custom-css-migrate-to-core-design.md +35 -0
  36. package/docs/plans/2026-03-05-custom-css-migrate-to-core.md +352 -0
  37. package/docs/plans/2026-03-05-scribe-script-shortcode.md +373 -0
  38. package/docs/plans/2026-03-05-user-types-design.md +139 -0
  39. package/docs/plans/2026-03-05-user-types-implementation.md +1035 -0
  40. package/docs/plans/2026-03-09-page-editor-view-cta-inserts-design.md +78 -0
  41. package/docs/plans/2026-03-09-page-editor-view-cta-inserts.md +388 -0
  42. package/docs/plans/2026-03-09-pro-documentation-design.md +80 -0
  43. package/docs/plans/2026-03-09-pro-documentation.md +1017 -0
  44. package/docs/plans/2026-06-10-core-project-design.md +240 -0
  45. package/docs/plugin-development.md +827 -0
  46. package/docs/recruitment-recipe.md +501 -0
  47. package/docs/scaffolding.md +312 -0
  48. package/docs/screenshots/calculator-floating.png +0 -0
  49. package/docs/screenshots/calculator-standard.png +0 -0
  50. package/docs/screenshots/email-settings.png +0 -0
  51. package/docs/screenshots/messages-count.png +0 -0
  52. package/docs/structure/user-types.md +107 -0
  53. package/docs/superpowers/plans/2026-05-25-admin-sidebar-via-menus.md +1112 -0
  54. package/docs/superpowers/plans/2026-06-08-navbar-mobile-drawer-and-menu-enrichment.md +1161 -0
  55. package/docs/superpowers/plans/2026-06-25-domma-docs-project.md +695 -0
  56. package/docs/superpowers/plans/2026-06-26-surveys-HANDOFF.md +68 -0
  57. package/docs/superpowers/plans/2026-06-26-surveys-pro-plugin.md +1902 -0
  58. package/docs/superpowers/plans/2026-08-19-collection-context-sort-export.md +1577 -0
  59. package/docs/superpowers/plans/2026-10-09-content-api.md +1192 -0
  60. package/docs/superpowers/specs/2026-03-12-form-followup-actions-design.md +152 -0
  61. package/docs/superpowers/specs/2026-03-12-page-editor-form-insert-and-per-page-theme-design.md +115 -0
  62. package/docs/superpowers/specs/2026-03-17-nav-hidden-items-design.md +85 -0
  63. package/docs/superpowers/specs/2026-03-19-accordion-insert-design.md +175 -0
  64. package/docs/superpowers/specs/2026-03-25-static-block-shortcode-design.md +153 -0
  65. package/docs/superpowers/specs/2026-03-25-text-shortcode-design.md +168 -0
  66. package/docs/superpowers/specs/2026-04-05-card-builder-design.md +315 -0
  67. package/docs/superpowers/specs/2026-04-06-insert-builders-design.md +278 -0
  68. package/docs/superpowers/specs/2026-04-06-layouts-design.md +226 -0
  69. package/docs/superpowers/specs/2026-04-07-blog-plugin-design.md +300 -0
  70. package/docs/superpowers/specs/2026-04-07-calendar-plugin-design.md +538 -0
  71. package/docs/superpowers/specs/2026-04-17-components-area-design.md +368 -0
  72. package/docs/superpowers/specs/2026-05-02-effects-slideover-overhaul-design.md +193 -0
  73. package/docs/superpowers/specs/2026-05-06-dashboard-rebuild-design.md +257 -0
  74. package/docs/superpowers/specs/2026-05-24-menus-design.md +466 -0
  75. package/docs/superpowers/specs/2026-05-25-admin-sidebar-via-menus-design.md +358 -0
  76. package/docs/superpowers/specs/2026-05-25-projects-design.md +380 -0
  77. package/docs/superpowers/specs/2026-06-08-navbar-mobile-drawer-and-menu-enrichment-design.md +292 -0
  78. package/docs/superpowers/specs/2026-06-08-sidebar-tools-plugins-folders-design.md +59 -0
  79. package/docs/superpowers/specs/2026-06-25-domma-docs-project-design.md +192 -0
  80. package/docs/superpowers/specs/2026-06-26-domma-charts-upstream-proposal.md +44 -0
  81. package/docs/superpowers/specs/2026-06-26-manager-scheduler-upstream-proposal.md +46 -0
  82. package/docs/superpowers/specs/2026-06-26-surveys-pro-plugin-design.md +206 -0
  83. package/docs/superpowers/specs/2026-07-02-shortcode-context-menu-design.md +96 -0
  84. package/docs/superpowers/specs/2026-08-19-collection-context-sort-export-design.md +330 -0
  85. package/docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md +1150 -0
  86. package/docs/superpowers/specs/2026-10-09-content-api-design.md +325 -0
  87. package/docs/superpowers/specs/plans/2026-03-12-form-followup-actions.md +624 -0
  88. package/docs/superpowers/specs/plans/2026-03-12-page-editor-form-insert-and-per-page-theme.md +125 -0
  89. package/docs/superpowers/specs/plans/2026-03-17-nav-hidden-items.md +578 -0
  90. package/docs/superpowers/specs/plans/2026-03-19-accordion-insert.md +635 -0
  91. package/docs/superpowers/specs/plans/2026-03-25-text-shortcode.md +545 -0
  92. package/docs/superpowers/specs/plans/2026-04-05-card-builder.md +1970 -0
  93. package/docs/superpowers/specs/plans/2026-04-06-insert-builders.md +1049 -0
  94. package/docs/superpowers/specs/plans/2026-04-06-layouts.md +1015 -0
  95. package/docs/superpowers/specs/plans/2026-04-07-calendar-plugin.md +1526 -0
  96. package/docs/superpowers/specs/plans/2026-04-08-plugin-monetisation-split.md +242 -0
  97. package/docs/superpowers/specs/plans/2026-04-17-components-area.md +2038 -0
  98. package/docs/superpowers/specs/plans/2026-05-02-effects-slideover-overhaul.md +2335 -0
  99. package/docs/superpowers/specs/plans/2026-05-06-dashboard-rebuild.md +1999 -0
  100. package/docs/superpowers/specs/plans/2026-05-17-cache-system.md +1436 -0
  101. package/docs/superpowers/specs/plans/2026-05-24-menus.md +3096 -0
  102. package/docs/superpowers/specs/plans/2026-05-25-projects.md +2869 -0
  103. package/docs/superpowers/specs/plans/2026-09-22-marketplace-step2-consolidation.md +505 -0
  104. package/docs/superpowers/specs/plans/2026-09-22-marketplace-step3-catalogue-and-key.md +154 -0
  105. package/docs/superpowers/specs/plans/2026-09-22-tools-promotion.md +260 -0
  106. package/docs/theming.md +320 -0
  107. package/package.json +2 -2
  108. package/plugins/_lib/admin/mail/keyboard.js +49 -0
  109. package/plugins/_lib/admin/mail/reader-view.js +4 -2
  110. package/public/js/celebrations/core/canvas.js +1 -1
  111. package/public/js/celebrations/index.js +1 -1
  112. package/public/js/celebrations/licenses/ABeeZee-Regular-OFL.txt +93 -0
  113. package/public/js/celebrations/licenses/Allura-Regular-OFL.txt +93 -0
  114. package/public/js/celebrations/message-attrs.mjs +1 -0
  115. package/public/js/celebrations/themes/anniversary.js +1 -0
  116. package/public/js/celebrations/themes/autumn.js +1 -0
  117. package/public/js/celebrations/themes/birthday.js +1 -0
  118. package/public/js/celebrations/themes/butterfly-flight.js +1 -0
  119. package/public/js/celebrations/themes/calendar.js +1 -0
  120. package/public/js/celebrations/themes/chinese-new-year-dates.js +1 -0
  121. package/public/js/celebrations/themes/chinese-new-year.js +1 -0
  122. package/public/js/celebrations/themes/christmas.js +1 -1
  123. package/public/js/celebrations/themes/easter.js +1 -0
  124. package/public/js/celebrations/themes/garden-drawing.js +1 -0
  125. package/public/js/celebrations/themes/garden.js +1 -0
  126. package/public/js/celebrations/themes/graduation.js +1 -0
  127. package/public/js/celebrations/themes/greeting.js +1 -0
  128. package/public/js/celebrations/themes/make-it-rain.js +1 -0
  129. package/public/js/celebrations/themes/money-rain.js +1 -0
  130. package/public/js/celebrations/themes/new-year.js +1 -0
  131. package/public/js/celebrations/themes/occasion-drawing.js +1 -0
  132. package/public/js/celebrations/themes/occasions.js +1 -0
  133. package/public/js/celebrations/themes/rain-control.js +1 -0
  134. package/public/js/celebrations/themes/sparkler-font-data.js +1 -0
  135. package/public/js/celebrations/themes/sparkler-font.js +1 -0
  136. package/public/js/celebrations/themes/sparkler-writing.js +1 -0
  137. package/public/js/celebrations/themes/summer-garden.js +1 -0
  138. package/public/js/celebrations/themes/winter.js +1 -0
  139. package/public/js/celebrations/trait-data.mjs +1 -1
  140. package/public/js/effects.js +1 -1
  141. package/scripts/build.js +4 -0
  142. package/server/services/markdown.js +5 -2
  143. package/server/templates/page.html +1 -1
@@ -0,0 +1,257 @@
1
+ # Dashboard Rebuild Design
2
+
3
+ **Date:** 2026-05-06
4
+ **Status:** Approved
5
+ **Scope:** Replace the current admin dashboard with a site-aware command centre that surfaces traffic, journeys, spikes, content state, and combined site/server health.
6
+
7
+ ## Problem
8
+
9
+ The existing admin dashboard (`admin/js/views/dashboard.js`) shows three count cards (Total / Published / Drafts) and a Recent Pages table. It tells the operator nothing about how the site is performing - no hits, no journeys, no spike detection, no health signal. For a single-site CMS this is the natural landing page; it should be the place the operator opens first to know whether the site is OK and where attention is needed.
10
+
11
+ ## Goals
12
+
13
+ - One-glance answer to: *Is the site healthy? What's getting traffic? Where are spikes? What's stale?*
14
+ - Single network call on mount; auto-refresh only the volatile tiles (KPIs + spike feed).
15
+ - Each widget self-contained so we can add/remove without touching the orchestrator.
16
+ - Privacy-preserving - no IP, no UA, no PII; honour DNT; sessionStorage-only session IDs.
17
+
18
+ ## Non-Goals (YAGNI)
19
+
20
+ - Geographic / IP-derived data
21
+ - Browser / device breakdown (no UA parsing)
22
+ - Multi-day cohort retention or returning-visitor tracking
23
+ - Per-user custom widget layouts
24
+ - A pluggable widget framework (defer until a 2nd plugin actually wants to contribute widgets)
25
+
26
+ ## Layout
27
+
28
+ 12-column grid, four rows:
29
+
30
+ | Row | Content | Cols |
31
+ |-----|---------------------------------------------------------------------------------------|------|
32
+ | 1 | KPI strip: Hits today (Δ vs yesterday) · Live sessions · Submissions/week · Health | 4×3 |
33
+ | 2 | Traffic chart (30d trend + today hourly overlay, spike points annotated) · Spike feed | 8 / 4 |
34
+ | 3 | Top pages (sparkline + Δ%) · User journeys (entry / exit / top 2-step paths) | 6 / 6 |
35
+ | 4 | Site & server health detail · Activity feed (edits, publishes, submissions) | 6 / 6 |
36
+
37
+ ## Data Model - Analytics Plugin Extensions
38
+
39
+ ### Beacon
40
+
41
+ `public/inject-body.html` currently posts `{url, dnt}`. Extend to:
42
+
43
+ ```json
44
+ { "url": "/about", "dnt": false, "ref": "https://example.com/", "sid": "<uuid>" }
45
+ ```
46
+
47
+ - `sid` - generated client-side via `crypto.randomUUID()` on first hit, stored in `sessionStorage` under `dm_sid`. Clears on tab close. No cross-session tracking.
48
+ - `ref` - `document.referrer || ''`. Stripped to origin + path (no query, no fragment) before send.
49
+
50
+ ### Storage
51
+
52
+ Two new files in `plugins/analytics/`:
53
+
54
+ ```
55
+ journeys.json { "YYYY-MM-DD": [ { sid, t, url, ref } ] }
56
+ spikes-cache.json { "lastBuiltAt": iso, "items": [ { url, ratio, hits, baseline } ] }
57
+ ```
58
+
59
+ - `journeys.json` is daily-capped (configurable, default **5,000 events/day**). When the cap is hit for the day, further events are dropped silently - keeps disk bounded.
60
+ - `spikes-cache.json` is rebuilt on demand by `/spikes` and cached for 60s; avoids re-scanning daily files on every dashboard refresh.
61
+
62
+ Existing `lifetime.json` and `daily.json` are unchanged - extension is additive.
63
+
64
+ ### New Endpoints (mounted at `/api/plugins/analytics`)
65
+
66
+ | Method | Path | Auth | Purpose |
67
+ |--------|-------------|-------|----------------------------------------------------------------------------------|
68
+ | GET | `/journeys` | Admin | `?range=7d` - top entry pages, top exit pages, top 2-step paths, bounce rate |
69
+ | GET | `/realtime` | Admin | Distinct `sid` count active in last 5 min |
70
+ | GET | `/spikes` | Admin | Current-hour hits per URL vs trailing 7-day same-hour baseline; flag where ratio > 2σ |
71
+
72
+ #### Journeys derivation
73
+
74
+ For the requested range, group `journeys.json` events by `sid`, sort by `t`. Then:
75
+ - **Entry page** = first URL in each session
76
+ - **Exit page** = last URL in each session
77
+ - **2-step path** = each consecutive `(url[i], url[i+1])` pair
78
+ - **Bounce rate** = sessions of length 1 ÷ total sessions
79
+
80
+ Aggregate; return top 10 of each category plus the bounce-rate scalar.
81
+
82
+ #### Spike detection
83
+
84
+ For each URL with hits in the current hour:
85
+ - Baseline = mean hits at this hour-of-day across the previous 7 days
86
+ - StdDev = sample stddev across same window
87
+ - If `currentHits > baseline + 2*stddev` AND `currentHits >= 5` (noise floor), flag
88
+ - Return up to 5 flagged URLs sorted by `currentHits / baseline` ratio descending
89
+
90
+ ### Privacy
91
+
92
+ - `sid` lives only in `sessionStorage` - closes with the tab; no cookie banner triggered.
93
+ - No IP logging, no UA, no fingerprinting.
94
+ - DNT continues to short-circuit at the beacon.
95
+ - Reset button (existing) wipes `journeys.json` along with the others.
96
+
97
+ ## Data Model - Core Dashboard Endpoint
98
+
99
+ ### `GET /api/dashboard/summary`
100
+
101
+ **Auth:** authenticate + requireAdmin
102
+
103
+ **Response:**
104
+
105
+ ```json
106
+ {
107
+ "traffic": {
108
+ "today": 1234,
109
+ "yesterday": 980,
110
+ "deltaPct": 25.9,
111
+ "weekToDate": 8200,
112
+ "previousWeek": 7600,
113
+ "hourlyToday": [{ "hour": 0, "hits": 12 }, ...]
114
+ },
115
+ "topPages": [
116
+ { "url": "/about", "hits": 432, "deltaPct": 12.4, "spark": [10, 14, ...] }
117
+ ],
118
+ "journeys": {
119
+ "entry": [{ "url": "/", "count": 800 }, ...],
120
+ "exit": [{ "url": "/contact", "count": 120 }, ...],
121
+ "paths": [{ "from": "/", "to": "/pricing", "count": 90 }, ...],
122
+ "bounceRate": 0.42
123
+ },
124
+ "spikes": [{ "url": "/pricing", "ratio": 4.12, "hits": 312, "baseline": 76 }],
125
+ "realtime": { "activeSessions": 7 },
126
+ "health": {
127
+ "status": "warn",
128
+ "checks": [
129
+ { "id": "uptime", "level": "ok", "label": "Uptime", "value": "3d 4h" },
130
+ { "id": "disk-content", "level": "ok", "label": "Content disk", "value": "142 MB" },
131
+ { "id": "memory", "level": "ok", "label": "Heap", "value": "84 / 512 MB" },
132
+ { "id": "stale-pages", "level": "warn", "label": "Stale pages (>90d)", "value": 4, "detail": ["/about", "/legal", ...] },
133
+ { "id": "missing-meta", "level": "warn", "label": "Pages missing description", "value": 2 },
134
+ { "id": "last-form", "level": "ok", "label": "Last form submission", "value": "2h ago" },
135
+ { "id": "smtp", "level": "ok", "label": "Last email send", "value": "2026-05-06 09:12" },
136
+ { "id": "plugin-load", "level": "ok", "label": "Plugin failures", "value": 0 }
137
+ ]
138
+ },
139
+ "activity": [
140
+ { "type": "edit", "at": "2026-05-06T09:14:00Z", "actor": "darryl", "target": "/about" },
141
+ { "type": "publish", "at": "...", "actor": "...", "target": "..." },
142
+ { "type": "form", "at": "...", "form": "contact" }
143
+ ]
144
+ }
145
+ ```
146
+
147
+ ### Aggregation sources
148
+
149
+ | Section | Source |
150
+ |--------------|-------------------------------------------------------------------|
151
+ | traffic | analytics plugin `daily.json` (today, yesterday, weeks) |
152
+ | topPages | analytics plugin `daily.json` (last 7d, sparkline = per-day) |
153
+ | journeys | analytics plugin `journeys.json` via internal helper |
154
+ | spikes | analytics plugin `spikes-cache.json` |
155
+ | realtime | analytics plugin `journeys.json` (sids in last 5 min) |
156
+ | health | new `server/services/health.js` (see below) |
157
+ | activity | versions service + form submissions (no auth audit log exists yet) |
158
+
159
+ ### `server/services/health.js`
160
+
161
+ Computes all health checks. Each check returns `{ id, level: 'ok'\|'warn'\|'fail', label, value, detail? }`. Top-level `status` = worst level across checks (`fail` > `warn` > `ok`).
162
+
163
+ Checks implemented:
164
+
165
+ - **uptime** - `process.uptime()`, formatted
166
+ - **disk-content** - recursive size of `content/` directory
167
+ - **memory** - `process.memoryUsage().heapUsed / heapTotal`
168
+ - **stale-pages** - pages whose `updatedAt` is >90 days ago; counts + lists up to 10
169
+ - **missing-meta** - pages without `description` in frontmatter
170
+ - **last-form** - newest submission across `content/forms/*/submissions/`; warn if >7 days, fail if no submissions ever AND any forms exist
171
+ - **smtp** - read last result from `server/services/email.js` (add a small `lastSendResult` export)
172
+ - **plugin-load** - count of plugins that failed manifest validation at startup (already logged; expose via `plugins.js`)
173
+
174
+ Cross-cutting: results cached for 30s in-memory inside the service to avoid recomputing on every poll.
175
+
176
+ ### Cross-plugin coupling
177
+
178
+ The dashboard endpoint imports the analytics plugin's helpers directly. To avoid making `server/` depend on `plugins/analytics/` (which violates the upstream-replaced boundary), the analytics plugin **registers an internal aggregator on the Fastify instance**:
179
+
180
+ ```js
181
+ fastify.decorate('analytics', {
182
+ getTraffic, getTopPages, getJourneys, getSpikes, getRealtime
183
+ });
184
+ ```
185
+
186
+ `/api/dashboard/summary` reads `request.server.analytics?.getTraffic(...)` and tolerates the analytics plugin being absent (returns `null` for those sections; the dashboard renders empty-state placeholders).
187
+
188
+ ## Frontend Architecture
189
+
190
+ ### File structure
191
+
192
+ ```
193
+ admin/js/views/dashboard.js Orchestrator: fetch summary, mount widgets, schedule refresh
194
+ admin/js/templates/dashboard.html Grid skeleton with named slot divs
195
+ admin/js/views/dashboard/widgets/
196
+ kpi-strip.js / kpi-strip.html Row 1
197
+ traffic-chart.js / traffic-chart.html Row 2 left
198
+ spike-feed.js / spike-feed.html Row 2 right
199
+ top-pages.js / top-pages.html Row 3 left
200
+ journeys.js / journeys.html Row 3 right
201
+ health-detail.js / health-detail.html Row 4 left
202
+ activity-feed.js / activity-feed.html Row 4 right
203
+ admin/css/dashboard.css Grid + tile styling
204
+ ```
205
+
206
+ ### Widget contract
207
+
208
+ Each widget exports:
209
+
210
+ ```js
211
+ export default {
212
+ selector: '#dash-kpi-strip',
213
+ templateUrl: '/admin/js/templates/dashboard/kpi-strip.html',
214
+ render(data) { /* receives the relevant slice of /summary; returns nothing, mutates DOM via Domma */ }
215
+ };
216
+ ```
217
+
218
+ The orchestrator loads the template into the slot once, then calls `render(slice)` on initial mount and each refresh. Widgets do not fetch their own data - the orchestrator hands them what they need.
219
+
220
+ ### Refresh
221
+
222
+ Single `setInterval(30_000)` in the orchestrator hits `/api/dashboard/summary?lite=1` (which returns only `traffic`, `realtime`, `spikes`, `health.status`). Re-renders KPI strip, spike feed, and the health pill. Other widgets are static after mount.
223
+
224
+ ### Charts
225
+
226
+ Reuse the existing analytics-plugin Chart.js cache pattern (CDN with one-time load + module-level promise so we don't double-load). Sparklines in the top-pages widget use inline SVG (lightweight; Chart.js for one mini chart is overkill).
227
+
228
+ ### Empty states
229
+
230
+ Every widget must render a zero-state when its data slice is empty (no hits yet, no journeys yet, no spikes detected). Show "No data yet" with a hint, not an empty box.
231
+
232
+ ## Error Handling
233
+
234
+ - `/api/dashboard/summary` returns 200 with partial data even if a sub-aggregator throws. Each section is wrapped in `try/catch`; on failure, that key is `null` and a `warnings: ["traffic: <message>"]` array is populated.
235
+ - Frontend orchestrator surfaces warnings as a single dismissable banner above the grid, not per-widget toasts.
236
+ - Widget render functions must tolerate `null` slices (render the empty state).
237
+
238
+ ## Testing
239
+
240
+ - `server/services/health.js` - unit tests for each check with mocked filesystem / process state.
241
+ - Analytics plugin journey aggregation - unit tests with seeded `journeys.json` fixtures (entry/exit/paths/bounce).
242
+ - Spike detector - unit tests across edge cases (no baseline, all zeros, single high hour).
243
+ - `/api/dashboard/summary` - integration test asserting graceful degradation when analytics plugin is disabled (sections are `null`, status is 200).
244
+ - Frontend widget rendering - manual browser verification per the user's standing rule (no "verified" claims without a real browser load).
245
+
246
+ ## Implementation Order
247
+
248
+ 1. **Analytics extensions** - beacon update, `journeys.json` storage, `/journeys` `/realtime` `/spikes` endpoints, internal `fastify.analytics` decorator. Ship and verify with the existing analytics admin view (still works).
249
+ 2. **Health service** - `server/services/health.js` with all checks + caching.
250
+ 3. **Dashboard summary endpoint** - `server/routes/api/dashboard.js` aggregating all sources.
251
+ 4. **Dashboard frontend** - new template + orchestrator + 7 widgets + CSS. Last because it depends on the API surface above.
252
+
253
+ Each step is independently verifiable.
254
+
255
+ ## Open Questions
256
+
257
+ None - all scope decisions resolved during brainstorm.