domma-cms 1.6.60 → 1.7.1

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 (168) hide show
  1. package/CHANGELOG.md +4349 -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/lib/effect-defs.js +1 -1
  6. package/admin/js/lib/effects-builder.js +2 -2
  7. package/admin/js/lib/mail-compose.js +1 -0
  8. package/admin/js/lib/page-editor-reference.js +5 -5
  9. package/admin/js/lib/shortcode-tags.js +1 -1
  10. package/admin/js/templates/call-new.html +1 -1
  11. package/admin/js/templates/docs/usage-shortcodes.html +39 -0
  12. package/admin/js/views/call-new.js +1 -1
  13. package/admin/js/views/contacts.js +15 -15
  14. package/admin/js/views/form-submissions.js +2 -2
  15. package/docs/DommaFeatures.md +3005 -0
  16. package/docs/MultiTenant.md +176 -0
  17. package/docs/api-reference.md +1667 -0
  18. package/docs/cache.md +96 -0
  19. package/docs/configuration.md +1811 -0
  20. package/docs/editions-and-licences.md +190 -0
  21. package/docs/getting-started.md +201 -0
  22. package/docs/guides/form-triggers.md +260 -0
  23. package/docs/guides/mongodb-storage-adapter.md +282 -0
  24. package/docs/guides/multi-page-forms.md +121 -0
  25. package/docs/images/scribe-composer.png +0 -0
  26. package/docs/markdown-shortcodes.md +1738 -0
  27. package/docs/pentest-report-2026-04-03.md +181 -0
  28. package/docs/plans/2026-02-28-admin-topbar-login-forms.md +349 -0
  29. package/docs/plans/2026-02-28-domma-ui-polish.md +286 -0
  30. package/docs/plans/2026-02-28-reset-seed-fresh-commands.md +520 -0
  31. package/docs/plans/2026-03-02-cookie-consent-design.md +86 -0
  32. package/docs/plans/2026-03-02-cookie-consent-plugin.md +453 -0
  33. package/docs/plans/2026-03-02-tooltip-sweep-and-back-to-top-plugin-design.md +126 -0
  34. package/docs/plans/2026-03-02-tooltip-sweep-back-to-top.md +427 -0
  35. package/docs/plans/2026-03-05-cookie-consent-btt-core.md +115 -0
  36. package/docs/plans/2026-03-05-custom-css-migrate-to-core-design.md +35 -0
  37. package/docs/plans/2026-03-05-custom-css-migrate-to-core.md +352 -0
  38. package/docs/plans/2026-03-05-scribe-script-shortcode.md +373 -0
  39. package/docs/plans/2026-03-05-user-types-design.md +139 -0
  40. package/docs/plans/2026-03-05-user-types-implementation.md +1035 -0
  41. package/docs/plans/2026-03-09-page-editor-view-cta-inserts-design.md +78 -0
  42. package/docs/plans/2026-03-09-page-editor-view-cta-inserts.md +388 -0
  43. package/docs/plans/2026-03-09-pro-documentation-design.md +80 -0
  44. package/docs/plans/2026-03-09-pro-documentation.md +1017 -0
  45. package/docs/plans/2026-06-10-core-project-design.md +240 -0
  46. package/docs/plugin-development.md +827 -0
  47. package/docs/recruitment-recipe.md +501 -0
  48. package/docs/scaffolding.md +312 -0
  49. package/docs/screenshots/calculator-floating.png +0 -0
  50. package/docs/screenshots/calculator-standard.png +0 -0
  51. package/docs/screenshots/email-settings.png +0 -0
  52. package/docs/screenshots/messages-count.png +0 -0
  53. package/docs/structure/user-types.md +107 -0
  54. package/docs/superpowers/plans/2026-05-25-admin-sidebar-via-menus.md +1112 -0
  55. package/docs/superpowers/plans/2026-06-08-navbar-mobile-drawer-and-menu-enrichment.md +1161 -0
  56. package/docs/superpowers/plans/2026-06-25-domma-docs-project.md +695 -0
  57. package/docs/superpowers/plans/2026-06-26-surveys-HANDOFF.md +68 -0
  58. package/docs/superpowers/plans/2026-06-26-surveys-pro-plugin.md +1902 -0
  59. package/docs/superpowers/plans/2026-08-19-collection-context-sort-export.md +1577 -0
  60. package/docs/superpowers/plans/2026-10-09-content-api.md +1192 -0
  61. package/docs/superpowers/specs/2026-03-12-form-followup-actions-design.md +152 -0
  62. package/docs/superpowers/specs/2026-03-12-page-editor-form-insert-and-per-page-theme-design.md +115 -0
  63. package/docs/superpowers/specs/2026-03-17-nav-hidden-items-design.md +85 -0
  64. package/docs/superpowers/specs/2026-03-19-accordion-insert-design.md +175 -0
  65. package/docs/superpowers/specs/2026-03-25-static-block-shortcode-design.md +153 -0
  66. package/docs/superpowers/specs/2026-03-25-text-shortcode-design.md +168 -0
  67. package/docs/superpowers/specs/2026-04-05-card-builder-design.md +315 -0
  68. package/docs/superpowers/specs/2026-04-06-insert-builders-design.md +278 -0
  69. package/docs/superpowers/specs/2026-04-06-layouts-design.md +226 -0
  70. package/docs/superpowers/specs/2026-04-07-blog-plugin-design.md +300 -0
  71. package/docs/superpowers/specs/2026-04-07-calendar-plugin-design.md +538 -0
  72. package/docs/superpowers/specs/2026-04-17-components-area-design.md +368 -0
  73. package/docs/superpowers/specs/2026-05-02-effects-slideover-overhaul-design.md +193 -0
  74. package/docs/superpowers/specs/2026-05-06-dashboard-rebuild-design.md +257 -0
  75. package/docs/superpowers/specs/2026-05-24-menus-design.md +466 -0
  76. package/docs/superpowers/specs/2026-05-25-admin-sidebar-via-menus-design.md +358 -0
  77. package/docs/superpowers/specs/2026-05-25-projects-design.md +380 -0
  78. package/docs/superpowers/specs/2026-06-08-navbar-mobile-drawer-and-menu-enrichment-design.md +292 -0
  79. package/docs/superpowers/specs/2026-06-08-sidebar-tools-plugins-folders-design.md +59 -0
  80. package/docs/superpowers/specs/2026-06-25-domma-docs-project-design.md +192 -0
  81. package/docs/superpowers/specs/2026-06-26-domma-charts-upstream-proposal.md +44 -0
  82. package/docs/superpowers/specs/2026-06-26-manager-scheduler-upstream-proposal.md +46 -0
  83. package/docs/superpowers/specs/2026-06-26-surveys-pro-plugin-design.md +206 -0
  84. package/docs/superpowers/specs/2026-07-02-shortcode-context-menu-design.md +96 -0
  85. package/docs/superpowers/specs/2026-08-19-collection-context-sort-export-design.md +330 -0
  86. package/docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md +1150 -0
  87. package/docs/superpowers/specs/2026-10-09-content-api-design.md +325 -0
  88. package/docs/superpowers/specs/plans/2026-03-12-form-followup-actions.md +624 -0
  89. package/docs/superpowers/specs/plans/2026-03-12-page-editor-form-insert-and-per-page-theme.md +125 -0
  90. package/docs/superpowers/specs/plans/2026-03-17-nav-hidden-items.md +578 -0
  91. package/docs/superpowers/specs/plans/2026-03-19-accordion-insert.md +635 -0
  92. package/docs/superpowers/specs/plans/2026-03-25-text-shortcode.md +545 -0
  93. package/docs/superpowers/specs/plans/2026-04-05-card-builder.md +1970 -0
  94. package/docs/superpowers/specs/plans/2026-04-06-insert-builders.md +1049 -0
  95. package/docs/superpowers/specs/plans/2026-04-06-layouts.md +1015 -0
  96. package/docs/superpowers/specs/plans/2026-04-07-calendar-plugin.md +1526 -0
  97. package/docs/superpowers/specs/plans/2026-04-08-plugin-monetisation-split.md +242 -0
  98. package/docs/superpowers/specs/plans/2026-04-17-components-area.md +2038 -0
  99. package/docs/superpowers/specs/plans/2026-05-02-effects-slideover-overhaul.md +2335 -0
  100. package/docs/superpowers/specs/plans/2026-05-06-dashboard-rebuild.md +1999 -0
  101. package/docs/superpowers/specs/plans/2026-05-17-cache-system.md +1436 -0
  102. package/docs/superpowers/specs/plans/2026-05-24-menus.md +3096 -0
  103. package/docs/superpowers/specs/plans/2026-05-25-projects.md +2869 -0
  104. package/docs/superpowers/specs/plans/2026-09-22-marketplace-step2-consolidation.md +505 -0
  105. package/docs/superpowers/specs/plans/2026-09-22-marketplace-step3-catalogue-and-key.md +154 -0
  106. package/docs/superpowers/specs/plans/2026-09-22-tools-promotion.md +260 -0
  107. package/docs/theming.md +320 -0
  108. package/package.json +2 -2
  109. package/plugins/_lib/admin/mail/compose-window.js +3 -1
  110. package/plugins/_lib/admin/mail/identity.js +18 -3
  111. package/plugins/_lib/admin/mail/reader-view.js +32 -3
  112. package/plugins/blog/CLAUDE.md +4 -0
  113. package/plugins/blog/admin/lib/email.js +191 -0
  114. package/plugins/blog/admin/views/comments.js +3 -1
  115. package/plugins/blog/plugin.json +2 -2
  116. package/plugins/blog/plugin.public.js +1 -1
  117. package/plugins/blog/tests/email.test.js +69 -0
  118. package/plugins/booking/CLAUDE.md +4 -0
  119. package/plugins/booking/admin/lib/email.js +191 -0
  120. package/plugins/booking/admin/views/panels.js +7 -3
  121. package/plugins/booking/plugin.json +1 -1
  122. package/plugins/booking/tests/email.test.js +69 -0
  123. package/plugins/free-tier.lock.json +27 -19
  124. package/plugins/shopping-cart/CLAUDE.md +4 -0
  125. package/plugins/shopping-cart/admin/lib/email.js +191 -0
  126. package/plugins/shopping-cart/admin/views/orders.js +11 -3
  127. package/plugins/shopping-cart/plugin.json +2 -2
  128. package/plugins/shopping-cart/tests/email.test.js +69 -0
  129. package/plugins/support/CLAUDE.md +4 -0
  130. package/plugins/support/admin/lib/email.js +191 -0
  131. package/plugins/support/admin/views/desk.js +4 -1
  132. package/plugins/support/admin/views/inbox.js +6 -2
  133. package/plugins/support/plugin.json +2 -2
  134. package/plugins/support/tests/email.test.js +69 -0
  135. package/public/js/celebrations/core/canvas.js +1 -1
  136. package/public/js/celebrations/index.js +1 -1
  137. package/public/js/celebrations/licenses/ABeeZee-Regular-OFL.txt +93 -0
  138. package/public/js/celebrations/licenses/Allura-Regular-OFL.txt +93 -0
  139. package/public/js/celebrations/message-attrs.mjs +1 -0
  140. package/public/js/celebrations/themes/anniversary.js +1 -0
  141. package/public/js/celebrations/themes/autumn.js +1 -0
  142. package/public/js/celebrations/themes/birthday.js +1 -0
  143. package/public/js/celebrations/themes/butterfly-flight.js +1 -0
  144. package/public/js/celebrations/themes/calendar.js +1 -0
  145. package/public/js/celebrations/themes/chinese-new-year-dates.js +1 -0
  146. package/public/js/celebrations/themes/chinese-new-year.js +1 -0
  147. package/public/js/celebrations/themes/christmas.js +1 -1
  148. package/public/js/celebrations/themes/easter.js +1 -0
  149. package/public/js/celebrations/themes/garden-drawing.js +1 -0
  150. package/public/js/celebrations/themes/garden.js +1 -0
  151. package/public/js/celebrations/themes/graduation.js +1 -0
  152. package/public/js/celebrations/themes/greeting.js +1 -0
  153. package/public/js/celebrations/themes/make-it-rain.js +1 -0
  154. package/public/js/celebrations/themes/money-rain.js +1 -0
  155. package/public/js/celebrations/themes/new-year.js +1 -0
  156. package/public/js/celebrations/themes/occasion-drawing.js +1 -0
  157. package/public/js/celebrations/themes/occasions.js +1 -0
  158. package/public/js/celebrations/themes/rain-control.js +1 -0
  159. package/public/js/celebrations/themes/sparkler-font-data.js +1 -0
  160. package/public/js/celebrations/themes/sparkler-font.js +1 -0
  161. package/public/js/celebrations/themes/sparkler-writing.js +1 -0
  162. package/public/js/celebrations/themes/summer-garden.js +1 -0
  163. package/public/js/celebrations/themes/winter.js +1 -0
  164. package/public/js/celebrations/trait-data.mjs +1 -1
  165. package/public/js/effects.js +1 -1
  166. package/scripts/build.js +4 -0
  167. package/server/services/markdown.js +5 -2
  168. package/server/templates/page.html +1 -1
@@ -0,0 +1,176 @@
1
+ # Theoretical: Multi-Tenancy in Domma CMS
2
+
3
+ ## Context
4
+
5
+ Domma CMS is a file-based CMS (Fastify 5 ESM backend, Domma SPA admin, SSR public site). Every service currently assumes
6
+ a single tenant - config, content, users, collections, and plugins all resolve from fixed module-level directory
7
+ constants. The user asked whether multi-tenancy is **possible**, not to implement it.
8
+
9
+ ## Answer: Yes, But It's a Spectrum
10
+
11
+ There are three viable approaches, each with a different trade-off between isolation, complexity, and operational
12
+ overhead.
13
+
14
+ ---
15
+
16
+ ## Approach 1: Process-Per-Tenant (Simplest)
17
+
18
+ Run a separate Domma CMS instance per tenant, each with its own `content/`, `config/`, and port.
19
+
20
+ | Aspect | Detail |
21
+ |----------------------|----------------------------------------------------|
22
+ | **Isolation** | Complete - separate processes, separate file trees |
23
+ | **Code changes** | Zero |
24
+ | **Operational cost** | High - N processes, N ports, reverse proxy routing |
25
+ | **Tenant limit** | ~50-100 before operational pain |
26
+ | **Best for** | Agency hosting a handful of client sites |
27
+
28
+ A reverse proxy (nginx/Caddy) maps `tenant1.example.com` → `:4096`, `tenant2.example.com` → `:4097`, etc. Each instance
29
+ is a vanilla Domma CMS install.
30
+
31
+ ---
32
+
33
+ ## Approach 2: Single Process, Directory-Scoped Tenancy (Medium)
34
+
35
+ One Fastify instance, tenant resolved from hostname/subdomain, all services parameterised by tenant ID.
36
+
37
+ ### What Changes
38
+
39
+ **A. Tenant detection hook** (new - `server/middleware/tenant.js`)
40
+
41
+ - Fastify `onRequest` hook derives `request.tenant` from subdomain or `X-Tenant` header
42
+ - All downstream services receive tenant context
43
+
44
+ **B. Config** (`server/config.js`)
45
+
46
+ - `getConfig(name)` → `getConfig(name, tenantId)` resolving to `config/{tenantId}/{name}.json`
47
+ - Startup singleton `config` (server/auth/content) stays global (shared infrastructure config)
48
+ - Admin-editable config (site/navigation/plugins/presets) becomes per-tenant
49
+
50
+ **C. Content & Collections** (`server/services/content.js`, `collections.js`)
51
+
52
+ - Module-level constants (`PAGES_DIR`, `MEDIA_DIR`, `COLLECTIONS_DIR`) become functions: `getPagesDir(tenantId)` →
53
+ `content/{tenantId}/pages/`
54
+ - Every exported function gains a tenant parameter
55
+ - URL-to-file mapping scoped: `/about` → `content/{tenantId}/pages/about.md`
56
+
57
+ **D. Users & Auth** (`server/services/users.js`, `server/middleware/auth.js`)
58
+
59
+ - Users dir: `content/{tenantId}/users/`
60
+ - JWT tokens include `tenantId` claim - validated on every request
61
+ - Role caches keyed by `tenantId:roleName`
62
+
63
+ **E. Storage Adapters** (`server/services/adapterRegistry.js`)
64
+
65
+ - Cache key changes from `slug` to `{tenantId}:{slug}`
66
+ - MongoDB: per-tenant database or `{tenantId}_` collection prefix
67
+ - FileAdapter: tenant-scoped base directory
68
+
69
+ **F. Renderer** (`server/services/renderer.js`)
70
+
71
+ - Already reads config fresh per render - making `getConfig()` tenant-aware cascades naturally
72
+ - Template cache keyed by `tenantId`
73
+ - Custom CSS path: `content/{tenantId}/custom.css`
74
+
75
+ **G. Plugins** (`server/services/plugins.js`, `server/services/hooks.js`) - **HARDEST PART**
76
+
77
+ - Plugin code stays shared (global `plugins/` directory)
78
+ - Enabled/disabled state + settings become per-tenant (via scoped `config/{tenantId}/plugins.json`)
79
+ - Hook/shortcode/transform registries need namespacing - currently global `Map`s and a single `EventEmitter`
80
+ - Plugin-registered Fastify routes need tenant middleware guards
81
+
82
+ ### Effort Estimate by Area
83
+
84
+ | Area | Effort | Why |
85
+ |-----------------------|-------------|------------------------------------------------------|
86
+ | Tenant detection hook | Low | New file, ~30 lines |
87
+ | Config scoping | Medium | `getConfig`/`saveConfig` + directory structure |
88
+ | Content service | Medium | ~15 functions to parameterise |
89
+ | Collections | Medium | Same pattern as content |
90
+ | Users & auth | Medium-High | JWT claims, role cache refactor |
91
+ | Adapters | Low-Medium | Cache key change, already supports named connections |
92
+ | Renderer | Low | Already re-reads config per render |
93
+ | Plugins/hooks | **High** | Global registries, no teardown, route scoping |
94
+ | Admin SPA | Medium | API calls need tenant context (header or URL prefix) |
95
+ | **Total** | **Large** | ~2-3 weeks full-time for an experienced developer |
96
+
97
+ ### Directory Structure
98
+
99
+ ```
100
+ config/
101
+ server.json # shared infrastructure
102
+ auth.json # shared infrastructure
103
+ content.json # shared infrastructure
104
+ tenants.json # NEW - tenant registry (id, hostname, plan, etc.)
105
+ {tenantId}/
106
+ site.json
107
+ navigation.json
108
+ plugins.json
109
+ presets.json
110
+
111
+ content/
112
+ {tenantId}/
113
+ pages/
114
+ media/
115
+ users/
116
+ collections/
117
+ custom.css
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Approach 3: MongoDB-Native Multi-Tenancy (Pro Only)
123
+
124
+ For Pro users already on MongoDB - tenancy lives entirely in the database layer.
125
+
126
+ - Each tenant gets a separate MongoDB database (strongest isolation) or a `tenantId` field on every document (weaker but
127
+ simpler)
128
+ - `connectionManager.js` already supports named connections - extend to per-tenant connections
129
+ - File-based content (pages, media) still needs directory scoping OR moves entirely to MongoDB (GridFS for media,
130
+ documents for pages)
131
+ - Most natural extension of the existing adapter pattern
132
+
133
+ ---
134
+
135
+ ## The Five Hardest Problems
136
+
137
+ 1. **Plugin hook registries are global singletons.** The `EventEmitter`, `_shortcodes` Map, `_sanitizeExtensions`, and
138
+ `_transforms` Map in `hooks.js` have no tenant dimension. Options: (a) namespace keys as `{tenantId}:{hookName}`, (b)
139
+ per-tenant EventEmitter instances, (c) accept that plugins are global and only settings are per-tenant.
140
+
141
+ 2. **Module-level constants everywhere.** `PAGES_DIR`, `MEDIA_DIR`, `USERS_DIR`, `COLLECTIONS_DIR` are all `const` at
142
+ module scope. Every function using them would need refactoring to accept or derive tenant context. This is the bulk
143
+ of the mechanical work.
144
+
145
+ 3. **No request context threading.** There is no `AsyncLocalStorage` or similar mechanism to pass tenant context through
146
+ the call stack without explicitly threading it through every function parameter. Adding `AsyncLocalStorage` would be
147
+ the cleanest approach but touches every service boundary.
148
+
149
+ 4. **Admin SPA is a single build.** The admin panel (`admin/`) is served as static files and makes API calls without
150
+ tenant context. It would need to either: (a) send a tenant header with every API call, (b) be served from a
151
+ tenant-scoped URL, or (c) derive tenant from the page hostname.
152
+
153
+ 5. **Static asset isolation.** `public/css/site.css`, `public/js/site.js`, and theme assets are shared. Per-tenant
154
+ CSS/JS customisation would need a serving layer that overlays tenant-specific files on the shared base.
155
+
156
+ ---
157
+
158
+ ## Pragmatic Recommendation
159
+
160
+ If multi-tenancy is a real future goal:
161
+
162
+ 1. **Start with Approach 1** (process-per-tenant) for immediate needs - zero code changes
163
+ 2. **Introduce `AsyncLocalStorage`** for tenant context propagation as a foundation
164
+ 3. **Scope config first** (`getConfig` + `saveConfig`) - lowest risk, highest leverage since the renderer already
165
+ re-reads config per render
166
+ 4. **Scope content second** - parameterise path helpers in content.js
167
+ 5. **Leave plugins global** with per-tenant settings - full plugin isolation is disproportionately expensive
168
+ 6. **MongoDB-native tenancy** for Pro users as the long-term path - the adapter pattern already supports this direction
169
+
170
+ ## Verification (If Implemented)
171
+
172
+ 1. Two tenants configured with different `site.json` (title, theme) serve distinct homepages from the same process
173
+ 2. Tenant A cannot access Tenant B's content, users, or media (path traversal guards)
174
+ 3. JWT from Tenant A is rejected on Tenant B's API routes
175
+ 4. Admin panel for each tenant shows only that tenant's pages/collections
176
+ 5. Shared plugins work for both tenants with independent settings