domma-cms 1.6.60 → 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 (137) 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/lib/effect-defs.js +1 -1
  6. package/admin/js/lib/effects-builder.js +2 -2
  7. package/admin/js/lib/page-editor-reference.js +5 -5
  8. package/admin/js/lib/shortcode-tags.js +1 -1
  9. package/admin/js/templates/docs/usage-shortcodes.html +39 -0
  10. package/docs/DommaFeatures.md +3005 -0
  11. package/docs/MultiTenant.md +176 -0
  12. package/docs/api-reference.md +1667 -0
  13. package/docs/cache.md +96 -0
  14. package/docs/configuration.md +1811 -0
  15. package/docs/editions-and-licences.md +190 -0
  16. package/docs/getting-started.md +201 -0
  17. package/docs/guides/form-triggers.md +260 -0
  18. package/docs/guides/mongodb-storage-adapter.md +282 -0
  19. package/docs/guides/multi-page-forms.md +121 -0
  20. package/docs/images/scribe-composer.png +0 -0
  21. package/docs/markdown-shortcodes.md +1738 -0
  22. package/docs/pentest-report-2026-04-03.md +181 -0
  23. package/docs/plans/2026-02-28-admin-topbar-login-forms.md +349 -0
  24. package/docs/plans/2026-02-28-domma-ui-polish.md +286 -0
  25. package/docs/plans/2026-02-28-reset-seed-fresh-commands.md +520 -0
  26. package/docs/plans/2026-03-02-cookie-consent-design.md +86 -0
  27. package/docs/plans/2026-03-02-cookie-consent-plugin.md +453 -0
  28. package/docs/plans/2026-03-02-tooltip-sweep-and-back-to-top-plugin-design.md +126 -0
  29. package/docs/plans/2026-03-02-tooltip-sweep-back-to-top.md +427 -0
  30. package/docs/plans/2026-03-05-cookie-consent-btt-core.md +115 -0
  31. package/docs/plans/2026-03-05-custom-css-migrate-to-core-design.md +35 -0
  32. package/docs/plans/2026-03-05-custom-css-migrate-to-core.md +352 -0
  33. package/docs/plans/2026-03-05-scribe-script-shortcode.md +373 -0
  34. package/docs/plans/2026-03-05-user-types-design.md +139 -0
  35. package/docs/plans/2026-03-05-user-types-implementation.md +1035 -0
  36. package/docs/plans/2026-03-09-page-editor-view-cta-inserts-design.md +78 -0
  37. package/docs/plans/2026-03-09-page-editor-view-cta-inserts.md +388 -0
  38. package/docs/plans/2026-03-09-pro-documentation-design.md +80 -0
  39. package/docs/plans/2026-03-09-pro-documentation.md +1017 -0
  40. package/docs/plans/2026-06-10-core-project-design.md +240 -0
  41. package/docs/plugin-development.md +827 -0
  42. package/docs/recruitment-recipe.md +501 -0
  43. package/docs/scaffolding.md +312 -0
  44. package/docs/screenshots/calculator-floating.png +0 -0
  45. package/docs/screenshots/calculator-standard.png +0 -0
  46. package/docs/screenshots/email-settings.png +0 -0
  47. package/docs/screenshots/messages-count.png +0 -0
  48. package/docs/structure/user-types.md +107 -0
  49. package/docs/superpowers/plans/2026-05-25-admin-sidebar-via-menus.md +1112 -0
  50. package/docs/superpowers/plans/2026-06-08-navbar-mobile-drawer-and-menu-enrichment.md +1161 -0
  51. package/docs/superpowers/plans/2026-06-25-domma-docs-project.md +695 -0
  52. package/docs/superpowers/plans/2026-06-26-surveys-HANDOFF.md +68 -0
  53. package/docs/superpowers/plans/2026-06-26-surveys-pro-plugin.md +1902 -0
  54. package/docs/superpowers/plans/2026-08-19-collection-context-sort-export.md +1577 -0
  55. package/docs/superpowers/plans/2026-10-09-content-api.md +1192 -0
  56. package/docs/superpowers/specs/2026-03-12-form-followup-actions-design.md +152 -0
  57. package/docs/superpowers/specs/2026-03-12-page-editor-form-insert-and-per-page-theme-design.md +115 -0
  58. package/docs/superpowers/specs/2026-03-17-nav-hidden-items-design.md +85 -0
  59. package/docs/superpowers/specs/2026-03-19-accordion-insert-design.md +175 -0
  60. package/docs/superpowers/specs/2026-03-25-static-block-shortcode-design.md +153 -0
  61. package/docs/superpowers/specs/2026-03-25-text-shortcode-design.md +168 -0
  62. package/docs/superpowers/specs/2026-04-05-card-builder-design.md +315 -0
  63. package/docs/superpowers/specs/2026-04-06-insert-builders-design.md +278 -0
  64. package/docs/superpowers/specs/2026-04-06-layouts-design.md +226 -0
  65. package/docs/superpowers/specs/2026-04-07-blog-plugin-design.md +300 -0
  66. package/docs/superpowers/specs/2026-04-07-calendar-plugin-design.md +538 -0
  67. package/docs/superpowers/specs/2026-04-17-components-area-design.md +368 -0
  68. package/docs/superpowers/specs/2026-05-02-effects-slideover-overhaul-design.md +193 -0
  69. package/docs/superpowers/specs/2026-05-06-dashboard-rebuild-design.md +257 -0
  70. package/docs/superpowers/specs/2026-05-24-menus-design.md +466 -0
  71. package/docs/superpowers/specs/2026-05-25-admin-sidebar-via-menus-design.md +358 -0
  72. package/docs/superpowers/specs/2026-05-25-projects-design.md +380 -0
  73. package/docs/superpowers/specs/2026-06-08-navbar-mobile-drawer-and-menu-enrichment-design.md +292 -0
  74. package/docs/superpowers/specs/2026-06-08-sidebar-tools-plugins-folders-design.md +59 -0
  75. package/docs/superpowers/specs/2026-06-25-domma-docs-project-design.md +192 -0
  76. package/docs/superpowers/specs/2026-06-26-domma-charts-upstream-proposal.md +44 -0
  77. package/docs/superpowers/specs/2026-06-26-manager-scheduler-upstream-proposal.md +46 -0
  78. package/docs/superpowers/specs/2026-06-26-surveys-pro-plugin-design.md +206 -0
  79. package/docs/superpowers/specs/2026-07-02-shortcode-context-menu-design.md +96 -0
  80. package/docs/superpowers/specs/2026-08-19-collection-context-sort-export-design.md +330 -0
  81. package/docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md +1150 -0
  82. package/docs/superpowers/specs/2026-10-09-content-api-design.md +325 -0
  83. package/docs/superpowers/specs/plans/2026-03-12-form-followup-actions.md +624 -0
  84. package/docs/superpowers/specs/plans/2026-03-12-page-editor-form-insert-and-per-page-theme.md +125 -0
  85. package/docs/superpowers/specs/plans/2026-03-17-nav-hidden-items.md +578 -0
  86. package/docs/superpowers/specs/plans/2026-03-19-accordion-insert.md +635 -0
  87. package/docs/superpowers/specs/plans/2026-03-25-text-shortcode.md +545 -0
  88. package/docs/superpowers/specs/plans/2026-04-05-card-builder.md +1970 -0
  89. package/docs/superpowers/specs/plans/2026-04-06-insert-builders.md +1049 -0
  90. package/docs/superpowers/specs/plans/2026-04-06-layouts.md +1015 -0
  91. package/docs/superpowers/specs/plans/2026-04-07-calendar-plugin.md +1526 -0
  92. package/docs/superpowers/specs/plans/2026-04-08-plugin-monetisation-split.md +242 -0
  93. package/docs/superpowers/specs/plans/2026-04-17-components-area.md +2038 -0
  94. package/docs/superpowers/specs/plans/2026-05-02-effects-slideover-overhaul.md +2335 -0
  95. package/docs/superpowers/specs/plans/2026-05-06-dashboard-rebuild.md +1999 -0
  96. package/docs/superpowers/specs/plans/2026-05-17-cache-system.md +1436 -0
  97. package/docs/superpowers/specs/plans/2026-05-24-menus.md +3096 -0
  98. package/docs/superpowers/specs/plans/2026-05-25-projects.md +2869 -0
  99. package/docs/superpowers/specs/plans/2026-09-22-marketplace-step2-consolidation.md +505 -0
  100. package/docs/superpowers/specs/plans/2026-09-22-marketplace-step3-catalogue-and-key.md +154 -0
  101. package/docs/superpowers/specs/plans/2026-09-22-tools-promotion.md +260 -0
  102. package/docs/theming.md +320 -0
  103. package/package.json +2 -2
  104. package/public/js/celebrations/core/canvas.js +1 -1
  105. package/public/js/celebrations/index.js +1 -1
  106. package/public/js/celebrations/licenses/ABeeZee-Regular-OFL.txt +93 -0
  107. package/public/js/celebrations/licenses/Allura-Regular-OFL.txt +93 -0
  108. package/public/js/celebrations/message-attrs.mjs +1 -0
  109. package/public/js/celebrations/themes/anniversary.js +1 -0
  110. package/public/js/celebrations/themes/autumn.js +1 -0
  111. package/public/js/celebrations/themes/birthday.js +1 -0
  112. package/public/js/celebrations/themes/butterfly-flight.js +1 -0
  113. package/public/js/celebrations/themes/calendar.js +1 -0
  114. package/public/js/celebrations/themes/chinese-new-year-dates.js +1 -0
  115. package/public/js/celebrations/themes/chinese-new-year.js +1 -0
  116. package/public/js/celebrations/themes/christmas.js +1 -1
  117. package/public/js/celebrations/themes/easter.js +1 -0
  118. package/public/js/celebrations/themes/garden-drawing.js +1 -0
  119. package/public/js/celebrations/themes/garden.js +1 -0
  120. package/public/js/celebrations/themes/graduation.js +1 -0
  121. package/public/js/celebrations/themes/greeting.js +1 -0
  122. package/public/js/celebrations/themes/make-it-rain.js +1 -0
  123. package/public/js/celebrations/themes/money-rain.js +1 -0
  124. package/public/js/celebrations/themes/new-year.js +1 -0
  125. package/public/js/celebrations/themes/occasion-drawing.js +1 -0
  126. package/public/js/celebrations/themes/occasions.js +1 -0
  127. package/public/js/celebrations/themes/rain-control.js +1 -0
  128. package/public/js/celebrations/themes/sparkler-font-data.js +1 -0
  129. package/public/js/celebrations/themes/sparkler-font.js +1 -0
  130. package/public/js/celebrations/themes/sparkler-writing.js +1 -0
  131. package/public/js/celebrations/themes/summer-garden.js +1 -0
  132. package/public/js/celebrations/themes/winter.js +1 -0
  133. package/public/js/celebrations/trait-data.mjs +1 -1
  134. package/public/js/effects.js +1 -1
  135. package/scripts/build.js +4 -0
  136. package/server/services/markdown.js +5 -2
  137. package/server/templates/page.html +1 -1
@@ -0,0 +1,373 @@
1
+ # Scribe Script Shortcode Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Expose Domma's Scribe `actions` array in CMS Markdown via nested `[render]`, `[wait]`, and `[undo]` shortcodes
6
+ inside `[scribe]`, so content editors can write multi-step typewriter scripts without touching JavaScript.
7
+
8
+ **Architecture:** The `[scribe]` shortcode handler in `plugin.js` gains a dedicated parser that scans the body for inner
9
+ action shortcodes and serialises them to JSON stored in a `data-fx-actions` attribute. `inject-body.html` reads that
10
+ attribute on the client side and passes the parsed array directly to `E.scribe()`. Backward compat is preserved: a
11
+ plain-text body continues to produce a single `{render: text}` action as before.
12
+
13
+ **Tech Stack:** Fastify plugin hooks, `parseShortcodeAttrs` / `escapeAttr` from markdown.js, `E.scribe()` from Domma JS,
14
+ vanilla JS in inject-body.html.
15
+
16
+ ---
17
+
18
+ ### Task 1: Export `escapeAttr` from markdown.js and add to plugin context
19
+
20
+ **Files:**
21
+
22
+ - Modify: `server/services/markdown.js`
23
+
24
+ **Step 1: Change `escapeAttr` declaration to `export function`**
25
+
26
+ In `server/services/markdown.js` (around line 154), change:
27
+
28
+ ```
29
+ function escapeAttr(str) {
30
+ ```
31
+
32
+ to:
33
+
34
+ ```
35
+ export function escapeAttr(str) {
36
+ ```
37
+
38
+ **Step 2: Add `escapeAttr` to the plugin shortcode context**
39
+
40
+ In `processPluginShortcodes` (around line 68), change:
41
+
42
+ ```
43
+ const context = {parseShortcodeAttrs, scrubCodeRegions, marked, processCardBlocks, processGridBlocks};
44
+ ```
45
+
46
+ to:
47
+
48
+ ```
49
+ const context = {parseShortcodeAttrs, scrubCodeRegions, marked, processCardBlocks, processGridBlocks, escapeAttr};
50
+ ```
51
+
52
+ **Step 3: Verify export**
53
+
54
+ Run: `node -e "import('./server/services/markdown.js').then(m => console.log('ok:', typeof m.escapeAttr))"`
55
+ Expected: `ok: function`
56
+
57
+ **Step 4: Commit**
58
+
59
+ ```
60
+ git add server/services/markdown.js
61
+ git commit -m "feat: export escapeAttr from markdown.js and expose in plugin shortcode context"
62
+ ```
63
+
64
+ ---
65
+
66
+ ### Task 2: Dedicated `[scribe]` shortcode handler with action parsing
67
+
68
+ **Files:**
69
+
70
+ - Modify: `plugins/domma-effects/plugin.js`
71
+
72
+ **Step 1: Remove `'scribe'` from the generic loop**
73
+
74
+ Change line ~66 from:
75
+
76
+ ```
77
+ for (const name of ['reveal', 'breathe', 'pulse', 'shake', 'scribe', 'scramble', 'ripple', 'twinkle']) {
78
+ ```
79
+
80
+ to:
81
+
82
+ ```
83
+ for (const name of ['reveal', 'breathe', 'pulse', 'shake', 'scramble', 'ripple', 'twinkle']) {
84
+ ```
85
+
86
+ **Step 2: Insert dedicated scribe handler before the generic loop**
87
+
88
+ ```js
89
+ registerShortcode('scribe', (attrStr, body, ctx) => {
90
+ if (body === null) return '';
91
+ const attrs = ctx.parseShortcodeAttrs(attrStr);
92
+ const dataAttrs = Object.entries(attrs).map(([k, v]) => ` data-fx-${k}="${v}"`).join('');
93
+
94
+ // Script mode: body contains at least one [render], [wait], or [undo] action shortcode
95
+ const hasActions = /\[\s*(render|wait|undo)\b/.test(body);
96
+
97
+ if (!hasActions) {
98
+ // Legacy path - single render from body text (backward compat)
99
+ const innerHtml = ctx.marked.parse(ctx.processCardBlocks(ctx.processGridBlocks(body.trim())));
100
+ return `<div class="dm-fx-scribe"${dataAttrs}>${innerHtml}</div>\n`;
101
+ }
102
+
103
+ // Script path - parse inner action shortcodes in document order
104
+ const actions = [];
105
+ const actionRe = /\[render([^\]]*)\]([\s\S]*?)\[\/render\]|\[wait\]([\s\S]*?)\[\/wait\]|\[undo([^\]]*?)\/\]/gi;
106
+ let match;
107
+ while ((match = actionRe.exec(body)) !== null) {
108
+ if (match[0].startsWith('[render')) {
109
+ const rAttrs = ctx.parseShortcodeAttrs(match[1] || '');
110
+ const action = {render: match[2].trim()};
111
+ if (rAttrs.effect) action.effect = rAttrs.effect;
112
+ actions.push(action);
113
+ } else if (match[0].startsWith('[wait')) {
114
+ const val = match[3].trim();
115
+ actions.push({wait: isNaN(val) ? val : parseInt(val, 10)});
116
+ } else {
117
+ const uAttrs = ctx.parseShortcodeAttrs(match[4] || '');
118
+ if (uAttrs.all === 'true') {
119
+ actions.push({undoRender: 'all'});
120
+ } else if (uAttrs.count) {
121
+ actions.push({undoRender: parseInt(uAttrs.count, 10)});
122
+ } else {
123
+ actions.push({undoRender: true});
124
+ }
125
+ }
126
+ }
127
+
128
+ if (!actions.length) return '';
129
+
130
+ const actionsJson = ctx.escapeAttr(JSON.stringify(actions));
131
+ return `<span class="dm-fx-scribe"${dataAttrs} data-fx-actions="${actionsJson}"></span>\n`;
132
+ }, {priority: 5});
133
+ ```
134
+
135
+ **Step 3: Smoke-test script mode**
136
+
137
+ Run:
138
+
139
+ ```
140
+ node -e "import('./server/services/markdown.js').then(({parseMarkdown}) => { const {html} = parseMarkdown('---\ntitle: T\n---\n[scribe loop=\"true\"]\n[render effect=\"fade\"]Hello[/render]\n[wait]1s[/wait]\n[undo /]\n[render]World[/render]\n[/scribe]'); console.log('span:', html.includes('dm-fx-scribe')); console.log('actions:', html.includes('data-fx-actions')); console.log('loop:', html.includes('data-fx-loop')); })"
141
+ ```
142
+
143
+ Expected: `span: true`, `actions: true`, `loop: true`
144
+
145
+ **Step 4: Smoke-test legacy mode is unchanged**
146
+
147
+ Run:
148
+
149
+ ```
150
+ node -e "import('./server/services/markdown.js').then(({parseMarkdown}) => { const {html} = parseMarkdown('---\ntitle: T\n---\n[scribe speed=\"50\"]Hello World[/scribe]'); console.log('div:', html.includes('<div class=\"dm-fx-scribe\"')); console.log('text:', html.includes('Hello World')); })"
151
+ ```
152
+
153
+ Expected: `div: true`, `text: true`
154
+
155
+ **Step 5: Commit**
156
+
157
+ ```
158
+ git add plugins/domma-effects/plugin.js
159
+ git commit -m "feat: dedicated [scribe] handler with [render]/[wait]/[undo] action script support"
160
+ ```
161
+
162
+ ---
163
+
164
+ ### Task 3: Update `inject-body.html` to read `data-fx-actions` and wire new options
165
+
166
+ **Files:**
167
+
168
+ - Modify: `plugins/domma-effects/public/inject-body.html`
169
+
170
+ **Step 1: Replace the `.dm-fx-scribe` handler block**
171
+
172
+ Find the existing block (lines ~218-229):
173
+
174
+ ```js
175
+ body.querySelectorAll('.dm-fx-scribe').forEach(function (el) {
176
+ var opts = {};
177
+ if (attr(el, 'speed')) opts.speed = numAttr(el, 'speed', 50);
178
+ if (attr(el, 'cursor')) opts.cursor = boolAttr(el, 'cursor', true);
179
+ if (attr(el, 'mode')) opts.mode = attr(el, 'mode');
180
+ if (attr(el, 'loop')) opts.loop = boolAttr(el, 'loop', false);
181
+ var text = el.textContent.trim();
182
+ if (!text) return;
183
+ el.textContent = '';
184
+ opts.actions = [{render: text}];
185
+ E.scribe(el, opts);
186
+ });
187
+ ```
188
+
189
+ Replace with:
190
+
191
+ ```js
192
+ body.querySelectorAll('.dm-fx-scribe').forEach(function (el) {
193
+ var opts = {};
194
+ if (attr(el, 'speed')) opts.speed = numAttr(el, 'speed', 50);
195
+ if (attr(el, 'delete-speed')) opts.deleteSpeed = numAttr(el, 'delete-speed', 30);
196
+ if (attr(el, 'cursor')) opts.cursor = boolAttr(el, 'cursor', true);
197
+ if (attr(el, 'cursor-char')) opts.cursorChar = attr(el, 'cursor-char');
198
+ if (attr(el, 'cursor-type')) opts.cursorType = attr(el, 'cursor-type');
199
+ if (attr(el, 'mode')) opts.mode = attr(el, 'mode');
200
+ if (attr(el, 'loop')) opts.loop = boolAttr(el, 'loop', false);
201
+ if (attr(el, 'loop-delay')) opts.loopDelay = numAttr(el, 'loop-delay', 1000);
202
+ if (attr(el, 'pause-on-hover')) opts.pauseOnHover = boolAttr(el, 'pause-on-hover', false);
203
+
204
+ var actionsAttr = attr(el, 'actions');
205
+ if (actionsAttr) {
206
+ try { opts.actions = JSON.parse(actionsAttr); } catch (e) { return; }
207
+ el.textContent = '';
208
+ } else {
209
+ var text = el.textContent.trim();
210
+ if (!text) return;
211
+ el.textContent = '';
212
+ opts.actions = [{render: text}];
213
+ }
214
+ E.scribe(el, opts);
215
+ });
216
+ ```
217
+
218
+ **Step 2: Commit**
219
+
220
+ ```
221
+ git add plugins/domma-effects/public/inject-body.html
222
+ git commit -m "feat: read data-fx-actions in scribe client init; expose deleteSpeed, cursorChar, cursorType, loopDelay, pauseOnHover"
223
+ ```
224
+
225
+ ---
226
+
227
+ ### Task 4: Update Domma Effects admin docs
228
+
229
+ **Files:**
230
+
231
+ - Modify: `plugins/domma-effects/admin/templates/domma-effects.html`
232
+
233
+ **Step 1: Replace the Scribe section (lines ~178-183)**
234
+
235
+ Find:
236
+
237
+ ```html
238
+ <h3 class="mb-2">Typewriter (Scribe)</h3>
239
+ <p class="text-muted mb-2">Types text character by character. Perfect for hero taglines or code
240
+ demonstrations.</p>
241
+ <pre class="code-block mb-3">[scribe speed="50" cursor="true" loop="false"]
242
+ Your text appears letter by letter.
243
+ [/scribe]</pre>
244
+ ```
245
+
246
+ Replace with:
247
+
248
+ ```html
249
+ <h3 class="mb-2">Typewriter (Scribe)</h3>
250
+ <p class="text-muted mb-2">Types text character by character. Supports a multi-step <strong>script</strong> of render, wait, and undo actions for looping typewriter sequences.</p>
251
+
252
+ <p class="text-muted mb-1" style="font-size:.85rem;"><strong>Simple (single phrase):</strong></p>
253
+ <pre class="code-block mb-3">[scribe speed="50" cursor="true" loop="false"]
254
+ Your text appears letter by letter.
255
+ [/scribe]</pre>
256
+
257
+ <p class="text-muted mb-1" style="font-size:.85rem;"><strong>Script (multi-step sequence):</strong></p>
258
+ <pre class="code-block mb-2">[scribe loop="true" loop-delay="1000" delete-speed="30"]
259
+ [render effect="fade"]Hello, World![/render]
260
+ [wait]1.5s[/wait]
261
+ [undo /]
262
+ [render]Something else entirely.[/render]
263
+ [wait]2s[/wait]
264
+ [undo all="true" /]
265
+ [/scribe]</pre>
266
+
267
+ <table class="table mb-2" style="font-size:.85rem;">
268
+ <thead><tr><th>Action</th><th>Description</th></tr></thead>
269
+ <tbody>
270
+ <tr><td><code>[render effect="fade"]text[/render]</code></td><td>Type text. effect: none, fade, bounce, glow</td></tr>
271
+ <tr><td><code>[wait]1.5s[/wait]</code></td><td>Pause. Accepts ms, s, or bare number (ms)</td></tr>
272
+ <tr><td><code>[undo /]</code></td><td>Delete the last render</td></tr>
273
+ <tr><td><code>[undo all="true" /]</code></td><td>Delete everything typed so far</td></tr>
274
+ <tr><td><code>[undo count="5" /]</code></td><td>Delete exactly N characters</td></tr>
275
+ </tbody>
276
+ </table>
277
+
278
+ <table class="table mb-3" style="font-size:.85rem;">
279
+ <thead><tr><th>Attribute</th><th>Default</th><th>Description</th></tr></thead>
280
+ <tbody>
281
+ <tr><td><code>speed</code></td><td>50</td><td>Typing speed (ms/char)</td></tr>
282
+ <tr><td><code>delete-speed</code></td><td>30</td><td>Deletion speed (ms/char)</td></tr>
283
+ <tr><td><code>cursor</code></td><td>true</td><td>Show cursor</td></tr>
284
+ <tr><td><code>cursor-type</code></td><td>caret</td><td>caret, block, underline</td></tr>
285
+ <tr><td><code>cursor-char</code></td><td>|</td><td>Custom cursor character</td></tr>
286
+ <tr><td><code>mode</code></td><td>typewriter</td><td>typewriter, word, sentence</td></tr>
287
+ <tr><td><code>loop</code></td><td>false</td><td>true to loop, or a repeat count</td></tr>
288
+ <tr><td><code>loop-delay</code></td><td>1000</td><td>Pause between loops (ms)</td></tr>
289
+ <tr><td><code>pause-on-hover</code></td><td>false</td><td>Pause typing while hovered</td></tr>
290
+ </tbody>
291
+ </table>
292
+ ```
293
+
294
+ **Step 2: Commit**
295
+
296
+ ```
297
+ git add plugins/domma-effects/admin/templates/domma-effects.html
298
+ git commit -m "docs: add Scribe script syntax and full attribute table to effects admin"
299
+ ```
300
+
301
+ ---
302
+
303
+ ### Task 5: Update page editor shortcode help
304
+
305
+ **Files:**
306
+
307
+ - Modify: `admin/js/views/page-editor.js`
308
+
309
+ **Step 1: Update the scribe row description (around line 284)**
310
+
311
+ Change:
312
+
313
+ ```js
314
+ ['[scribe speed="50"]...[/scribe]', 'Typewriter character-by-character'],
315
+ ```
316
+
317
+ to:
318
+
319
+ ```js
320
+ ['[scribe speed="50"]...[/scribe]', 'Typewriter - plain text or multi-step script'],
321
+ ```
322
+
323
+ **Step 2: Add Scribe script snippet after `section5.appendChild(effectsTable)` (around line 310)**
324
+
325
+ ```js
326
+ section5.appendChild(makeSnippet(
327
+ 'Scribe - multi-step script',
328
+ '[scribe loop="true" loop-delay="1000"]\n[render effect="fade"]Hello, World![/render]\n[wait]1.5s[/wait]\n[undo /]\n[render]Something else.[/render]\n[wait]2s[/wait]\n[undo all="true" /]\n[/scribe]',
329
+ 'Actions: [render effect="fade|bounce|glow"]text[/render] · [wait]1.5s[/wait] · [undo /] · [undo all="true" /] · [undo count="5" /]'
330
+ ));
331
+ ```
332
+
333
+ **Step 3: Commit**
334
+
335
+ ```
336
+ git add admin/js/views/page-editor.js
337
+ git commit -m "docs: add Scribe multi-step script snippet to page editor help"
338
+ ```
339
+
340
+ ---
341
+
342
+ ## Final Verification
343
+
344
+ ```
345
+ node -e "
346
+ import('./server/services/markdown.js').then(({parseMarkdown}) => {
347
+ const src = \`---
348
+ title: T
349
+ ---
350
+ [scribe loop=\"true\" delete-speed=\"25\" cursor-type=\"block\"]
351
+ [render effect=\"bounce\"]Step one[/render]
352
+ [wait]500ms[/wait]
353
+ [undo /]
354
+ [render]Step two[/render]
355
+ [wait]1s[/wait]
356
+ [undo count=\"4\" /]
357
+ [render]Done.[/render]
358
+ [/scribe]\`;
359
+ const {html} = parseMarkdown(src);
360
+ const m = html.match(/data-fx-actions=\\\"([^\\\"]*)\\\"/);
361
+ if (!m) { console.error('FAIL: no data-fx-actions'); process.exit(1); }
362
+ const decoded = m[1].replace(/&quot;/g,'\"').replace(/&amp;/g,'&');
363
+ const actions = JSON.parse(decoded);
364
+ console.log('actions.length === 6:', actions.length === 6);
365
+ console.log('render effect bounce:', actions[0].effect === 'bounce');
366
+ console.log('wait 500ms:', actions[1].wait === '500ms');
367
+ console.log('undoRender true:', actions[2].undoRender === true);
368
+ console.log('undoRender count 4:', actions[5].undoRender === 4);
369
+ console.log('loop attr:', html.includes('data-fx-loop=\"true\"'));
370
+ console.log('PASS');
371
+ }).catch(e => console.error('ERROR:', e.message));
372
+ "
373
+ ```
@@ -0,0 +1,139 @@
1
+ # User Types - Design Document
2
+
3
+ **Date:** 2026-03-05
4
+ **Status:** Approved
5
+ **Approach:** Option A - Preset Collection (full dynamic roles)
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ The Users admin view gains a second tab - **User Types** - backed by a preset Collection. Roles become runtime-dynamic: stored in `content/collections/user-types/data.json`, read at startup, cached in memory, and invalidated on CRUD. `config/auth.json` is stripped back to JWT/bcrypt config only.
12
+
13
+ ---
14
+
15
+ ## Data Model
16
+
17
+ ### Preset Collection: `user-types`
18
+
19
+ **Location:** `content/collections/user-types/`
20
+
21
+ ```
22
+ schema.json - field definitions (locked - preset: true)
23
+ data.json - role entries (fully editable via admin)
24
+ ```
25
+
26
+ **Schema fields:**
27
+
28
+ | Field | Type | Notes |
29
+ |---|---|---|
30
+ | `name` | text | Slug-style key (`editor`). Unique. |
31
+ | `label` | text | Display name (`Editor`) |
32
+ | `level` | number | Access hierarchy - lower = more privileged |
33
+ | `permissions` | multi-select | Which resources this role can access |
34
+ | `badgeClass` | select | Badge CSS class (`badge-info` etc.) |
35
+
36
+ **`config/auth.json` after migration:**
37
+
38
+ ```json
39
+ {
40
+ "accessTokenExpiry": "15m",
41
+ "refreshTokenExpiry": "7d",
42
+ "bcryptRounds": 10
43
+ }
44
+ ```
45
+
46
+ Roles and permissions blocks are removed entirely from `auth.json`.
47
+
48
+ ---
49
+
50
+ ## Auth Middleware
51
+
52
+ ### Startup
53
+
54
+ `server.js` calls `loadUserTypes()` before registering routes. This reads `data.json` and builds two in-memory maps:
55
+
56
+ ```js
57
+ roleMap: { admin: { label, level }, editor: { label, level }, ... }
58
+ permissionsMap: { pages: ['admin','manager','editor'], ... } // inverted from role-centric entries
59
+ ```
60
+
61
+ ### Cache Strategy
62
+
63
+ - Loaded once at startup
64
+ - Invalidated and reloaded on User Type create/update/delete (event-driven, no TTL)
65
+ - File is small - synchronous-equivalent performance
66
+
67
+ ### Bootstrap Protection
68
+
69
+ - `admin` role (level 0) cannot be deleted
70
+ - Delete route returns 403 if `entry.data.level === 0`
71
+
72
+ ---
73
+
74
+ ## Seeding
75
+
76
+ `seedPresetCollections()` runs at startup before routes register:
77
+
78
+ 1. Check if `content/collections/user-types/data.json` exists
79
+ 2. If **not** → create directory, write `schema.json` + seed `data.json`
80
+ 3. If **yes** → skip (user data preserved)
81
+
82
+ **Seed roles:**
83
+
84
+ ```json
85
+ [
86
+ { "data": { "name": "admin", "label": "Admin", "level": 0, "permissions": ["pages","settings","navigation","layouts","media","users","plugins","collections"], "badgeClass": "badge-danger" } },
87
+ { "data": { "name": "manager", "label": "Manager", "level": 1, "permissions": ["pages","settings","navigation","layouts","media","users","collections"], "badgeClass": "badge-warning" } },
88
+ { "data": { "name": "editor", "label": "Editor", "level": 2, "permissions": ["pages","media"], "badgeClass": "badge-info" } },
89
+ { "data": { "name": "subscriber", "label": "Subscriber", "level": 3, "permissions": [], "badgeClass": "badge-secondary" } }
90
+ ]
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Admin UI
96
+
97
+ **`admin/js/views/users.js`** - wrapped in `E.tabs()`:
98
+
99
+ - **Tab 1: Users** - existing `T.create()` table, unchanged
100
+ - **Tab 2: User Types** - own `T.create()` table, reads `/api/collections/user-types/entries`
101
+
102
+ User Types table features:
103
+ - Add button → modal form (name, label, level, permissions checkboxes, badge colour)
104
+ - Edit → same modal pre-populated
105
+ - Delete → `E.confirm()` guard, disabled for admin (level 0)
106
+ - Badge colour rendered live in table rows
107
+
108
+ **`admin/js/templates/users.html`** structure:
109
+
110
+ ```html
111
+ <div class="view-header">...</div>
112
+ <div class="tabs">
113
+ <div class="tab-list">
114
+ <button class="tab-item active">Users</button>
115
+ <button class="tab-item">User Types</button>
116
+ </div>
117
+ <div class="tab-content">
118
+ <div class="tab-panel active" id="panel-users">...</div>
119
+ <div class="tab-panel" id="panel-user-types">...</div>
120
+ </div>
121
+ </div>
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Files Changed
127
+
128
+ | File | Action |
129
+ |---|---|
130
+ | `server/server.js` | Call `seedPresetCollections()` + `loadUserTypes()` at startup |
131
+ | `server/services/userTypes.js` | New - seed, load, cache, invalidate, invert permissions |
132
+ | `server/routes/api/users.js` | Middleware reads from `userTypes` cache instead of `auth.json` |
133
+ | `server/routes/api/collections.js` | CRUD routes call `userTypes.invalidate()` for `user-types` slug |
134
+ | `config/auth.json` | Remove `roles` + `permissions` blocks |
135
+ | `content/collections/user-types/schema.json` | Seeded at startup |
136
+ | `content/collections/user-types/data.json` | Seeded at startup |
137
+ | `admin/js/views/users.js` | Add tabs + User Types table + modal |
138
+ | `admin/js/templates/users.html` | Add tabs structure |
139
+ | `docs/structure/user-types.md` | New - structure documentation |