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,352 @@
1
+ # Custom CSS - Migrate to Core Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Move the Custom CSS plugin into core, storing CSS at `content/custom.css`, serving it as an inline `<style>` block, and editing it via a new tab in Site Settings.
6
+
7
+ **Architecture:** The CSS file lives at `content/custom.css`. The renderer reads it at request time and injects it as an inline `<style>` block in `headInjectLate`. Two new routes on the settings router handle admin read/write. The admin UI is a new "Custom CSS" tab in settings.html with its own Save button (separate from the main Save, since it writes a file not JSON config). The plugin directory and plugins.json entry are then removed.
8
+
9
+ **Tech Stack:** Fastify 5 ESM backend, Domma SPA admin, `fs/promises` for file I/O, `content/custom.css` as storage.
10
+
11
+ **Reference files:** `server/routes/api/settings.js` (lines 1-59), `server/services/renderer.js` (lines 29-86), `admin/js/views/settings.js` (lines 1-117), `admin/js/templates/settings.html` (lines 1-287), `plugins/custom-css/plugin.js`.
12
+
13
+ ---
14
+
15
+ ### Task 1: Create content/custom.css
16
+
17
+ **Files:**
18
+ - Create: `content/custom.css`
19
+
20
+ **Step 1: Create the empty file**
21
+
22
+ ```bash
23
+ touch content/custom.css
24
+ ```
25
+
26
+ **Step 2: Verify it exists**
27
+
28
+ Run: `ls -la content/custom.css`
29
+
30
+ Expected: file listed with 0 bytes.
31
+
32
+ **Step 3: Commit**
33
+
34
+ ```bash
35
+ git add content/custom.css
36
+ git commit -m "feat: add content/custom.css for core custom CSS storage"
37
+ ```
38
+
39
+ ---
40
+
41
+ ### Task 2: Add custom-css routes to settings.js
42
+
43
+ **Files:**
44
+ - Modify: `server/routes/api/settings.js`
45
+
46
+ **Step 1: Add fs import at the top**
47
+
48
+ After the existing imports (after line 10), add:
49
+
50
+ ```js
51
+ import fs from 'fs/promises';
52
+ import path from 'path';
53
+ import {fileURLToPath} from 'url';
54
+
55
+ const __dirname_settings = path.dirname(fileURLToPath(import.meta.url));
56
+ const CUSTOM_CSS_FILE = path.resolve(__dirname_settings, '../../../content/custom.css');
57
+ const CUSTOM_CSS_MAX = 100 * 1024; // 100 KB
58
+ ```
59
+
60
+ **Step 2: Add the two new routes inside `settingsRoutes`, after the test-email handler**
61
+
62
+ ```js
63
+ // GET /api/settings/custom-css - return current CSS as JSON
64
+ fastify.get('/settings/custom-css', guard, async () => {
65
+ try {
66
+ const css = await fs.readFile(CUSTOM_CSS_FILE, 'utf8');
67
+ return { css };
68
+ } catch {
69
+ return { css: '' };
70
+ }
71
+ });
72
+
73
+ // PUT /api/settings/custom-css - save CSS to content/custom.css
74
+ fastify.put('/settings/custom-css', guard, async (request, reply) => {
75
+ const { css } = request.body || {};
76
+ if (typeof css !== 'string') {
77
+ return reply.status(400).send({ error: 'css must be a string.' });
78
+ }
79
+ if (Buffer.byteLength(css, 'utf8') > CUSTOM_CSS_MAX) {
80
+ return reply.status(400).send({ error: 'CSS exceeds the 100 KB limit.' });
81
+ }
82
+ await fs.writeFile(CUSTOM_CSS_FILE, css, 'utf8');
83
+ return { ok: true };
84
+ });
85
+ ```
86
+
87
+ **Step 3: Verify the file imports without errors**
88
+
89
+ Run: `node --input-type=module --eval "import('/home/darryl/src/js/domma-cms/server/routes/api/settings.js').then(()=>console.log('OK')).catch(e=>{console.error(e.message);process.exit(1);})" 2>&1`
90
+
91
+ Expected: `OK`
92
+
93
+ **Step 4: Commit**
94
+
95
+ ```bash
96
+ git add server/routes/api/settings.js
97
+ git commit -m "feat: add GET/PUT /api/settings/custom-css routes"
98
+ ```
99
+
100
+ ---
101
+
102
+ ### Task 3: Update renderer.js to inject inline custom CSS
103
+
104
+ **Files:**
105
+ - Modify: `server/services/renderer.js`
106
+
107
+ **Step 1: Add fs import and CSS_FILE constant**
108
+
109
+ After the existing imports (after line 10 `import {applyTransforms} from './hooks.js';`), add:
110
+
111
+ ```js
112
+ import fs from 'fs/promises';
113
+
114
+ const CUSTOM_CSS_PATH = new URL('../../../content/custom.css', import.meta.url).pathname;
115
+
116
+ async function getCustomCss() {
117
+ try {
118
+ return await fs.readFile(CUSTOM_CSS_PATH, 'utf8');
119
+ } catch {
120
+ return '';
121
+ }
122
+ }
123
+ ```
124
+
125
+ **Step 2: Add `getCustomCss()` to the Promise.all in renderPage**
126
+
127
+ Current line 30:
128
+ ```js
129
+ const [template, site, navigation, presets, injection] = await Promise.all([
130
+ getTemplate(),
131
+ Promise.resolve(getConfig('site')),
132
+ Promise.resolve(getConfig('navigation')),
133
+ Promise.resolve(getConfig('presets')),
134
+ getInjectionSnippets()
135
+ ]);
136
+ ```
137
+
138
+ Replace with:
139
+ ```js
140
+ const [template, site, navigation, presets, injection, customCss] = await Promise.all([
141
+ getTemplate(),
142
+ Promise.resolve(getConfig('site')),
143
+ Promise.resolve(getConfig('navigation')),
144
+ Promise.resolve(getConfig('presets')),
145
+ getInjectionSnippets(),
146
+ getCustomCss()
147
+ ]);
148
+ ```
149
+
150
+ **Step 3: Build the customCssTag variable and extend headInjectLate**
151
+
152
+ After the `fontStyleTag` lines (after line 56), add:
153
+
154
+ ```js
155
+ const customCssTag = customCss.trim()
156
+ ? `<style>${customCss.replace(/<\/style>/gi, '<\\/style>')}</style>`
157
+ : '';
158
+ ```
159
+
160
+ Then in the `vars` object, change `headInjectLate`:
161
+
162
+ Current:
163
+ ```js
164
+ headInjectLate: injection.headLate,
165
+ ```
166
+
167
+ Replace with:
168
+ ```js
169
+ headInjectLate: [injection.headLate, customCssTag].filter(Boolean).join('\n'),
170
+ ```
171
+
172
+ **Step 4: Verify renderer imports without errors**
173
+
174
+ Run: `node --input-type=module --eval "import('/home/darryl/src/js/domma-cms/server/services/renderer.js').then(()=>console.log('OK')).catch(e=>{console.error(e.message);process.exit(1);})" 2>&1`
175
+
176
+ Expected: `OK`
177
+
178
+ **Step 5: Verify inline style tag appears when CSS is non-empty**
179
+
180
+ Write a test string to the file, curl the page, then clear it:
181
+
182
+ ```bash
183
+ echo "body { outline: 2px solid red; }" > content/custom.css
184
+ curl -s http://localhost:4096/ | grep -o '<style>body.*</style>'
185
+ echo "" > content/custom.css
186
+ ```
187
+
188
+ Expected: `<style>body { outline: 2px solid red; }</style>` (or similar match).
189
+
190
+ **Step 6: Commit**
191
+
192
+ ```bash
193
+ git add server/services/renderer.js
194
+ git commit -m "feat: renderer injects content/custom.css as inline style block"
195
+ ```
196
+
197
+ ---
198
+
199
+ ### Task 4: Add Custom CSS tab to settings.html
200
+
201
+ **Files:**
202
+ - Modify: `admin/js/templates/settings.html`
203
+
204
+ **Step 1: Add tab button after the Cookie Consent button**
205
+
206
+ Current last tab button:
207
+ ```html
208
+ <button class="tab-item">Cookie Consent</button>
209
+ ```
210
+
211
+ Add after it:
212
+ ```html
213
+ <button class="tab-item">Custom CSS</button>
214
+ ```
215
+
216
+ **Step 2: Add the tab panel before the closing `</div></div>`**
217
+
218
+ Add before the final ` </div>\n</div>`:
219
+
220
+ ```html
221
+ <!-- Custom CSS -->
222
+ <div class="tab-panel">
223
+ <div class="row mb-3">
224
+ <div class="col">
225
+ <p class="text-muted" style="font-size:.875rem;margin-bottom:1rem;">
226
+ CSS entered here is injected as an inline <code>&lt;style&gt;</code> block into the
227
+ <code>&lt;head&gt;</code> of every public page. Changes take effect immediately on save.
228
+ </p>
229
+ <textarea id="field-custom-css" class="form-input" style="font-family:monospace;min-height:420px;resize:vertical;" spellcheck="false" placeholder="/* Add your custom CSS here */"></textarea>
230
+ </div>
231
+ </div>
232
+ <div class="row">
233
+ <div class="col">
234
+ <button id="save-css-btn" class="btn btn-primary"><span data-icon="save"></span> Save CSS</button>
235
+ </div>
236
+ </div>
237
+ </div>
238
+ ```
239
+
240
+ **Step 3: Commit**
241
+
242
+ ```bash
243
+ git add admin/js/templates/settings.html
244
+ git commit -m "feat: add Custom CSS tab to Site Settings template"
245
+ ```
246
+
247
+ ---
248
+
249
+ ### Task 5: Update settings.js to load/save custom CSS
250
+
251
+ **Files:**
252
+ - Modify: `admin/js/views/settings.js`
253
+
254
+ **Step 1: Load CSS in onMount, after the Cookie Consent fields**
255
+
256
+ After the last `$container.find('#field-cc-version')...` line, add:
257
+
258
+ ```js
259
+ // Custom CSS
260
+ try {
261
+ const { css } = await apiRequest('/settings/custom-css');
262
+ $container.find('#field-custom-css').val(css || '');
263
+ } catch {
264
+ // non-fatal - textarea stays empty
265
+ }
266
+ ```
267
+
268
+ **Step 2: Add the Save CSS button handler, after the existing send-test-email handler**
269
+
270
+ After the closing of the `#send-test-email-btn` handler (after line ~115), add:
271
+
272
+ ```js
273
+ // Save Custom CSS
274
+ $container.find('#save-css-btn').on('click', async () => {
275
+ const css = $container.find('#field-custom-css').val();
276
+ const $btn = $container.find('#save-css-btn');
277
+ $btn.prop('disabled', true);
278
+ try {
279
+ await apiRequest('/settings/custom-css', {
280
+ method: 'PUT',
281
+ body: JSON.stringify({ css })
282
+ });
283
+ E.toast('Custom CSS saved.', { type: 'success' });
284
+ } catch (err) {
285
+ E.toast(err.message || 'Failed to save CSS.', { type: 'error' });
286
+ } finally {
287
+ $btn.prop('disabled', false);
288
+ }
289
+ });
290
+ ```
291
+
292
+ **Step 3: Verify JS syntax**
293
+
294
+ Run: `node --check admin/js/views/settings.js && echo OK`
295
+
296
+ Expected: `OK`
297
+
298
+ **Step 4: Commit**
299
+
300
+ ```bash
301
+ git add admin/js/views/settings.js
302
+ git commit -m "feat: settings view loads and saves custom CSS tab"
303
+ ```
304
+
305
+ ---
306
+
307
+ ### Task 6: Migrate existing CSS data and remove plugin
308
+
309
+ **Files:**
310
+ - Delete: `plugins/custom-css/` (entire directory)
311
+ - Modify: `config/plugins.json`
312
+
313
+ **Step 1: Copy any existing CSS data to core location**
314
+
315
+ ```bash
316
+ cp plugins/custom-css/data/custom.css content/custom.css 2>/dev/null || true
317
+ ```
318
+
319
+ **Step 2: Remove plugin directory**
320
+
321
+ ```bash
322
+ rm -rf plugins/custom-css
323
+ ```
324
+
325
+ **Step 3: Remove entry from config/plugins.json**
326
+
327
+ Remove the `"custom-css"` key. The resulting file should contain only: `example-analytics`, `form-builder`, `domma-effects`.
328
+
329
+ **Step 4: Verify JSON is valid**
330
+
331
+ Run: `node -e "JSON.parse(require('fs').readFileSync('config/plugins.json','utf8')); console.log('OK')"`
332
+
333
+ Expected: `OK`
334
+
335
+ **Step 5: Commit**
336
+
337
+ ```bash
338
+ git add -A
339
+ git commit -m "feat: remove custom-css plugin directory, migrate to core"
340
+ ```
341
+
342
+ ---
343
+
344
+ ## Verification
345
+
346
+ 1. **Inline style in page source**: Write some CSS to `content/custom.css`, then
347
+ `curl -s http://localhost:4096/ | grep '<style>'` - confirm the style block is present.
348
+ 2. **Empty file = no style tag**: Clear the file, re-curl, confirm no `<style>` tag appears.
349
+ 3. **Admin tab visible**: Open Site Settings in the admin - 7 tabs should be visible including "Custom CSS".
350
+ 4. **Save round-trip**: Enter CSS in the tab, click Save CSS, refresh the page - CSS should persist and appear in the public page source.
351
+ 5. **Plugin list**: Admin → Plugins - `custom-css` no longer appears.
352
+ 6. **Other plugins unaffected**: `domma-effects`, `form-builder`, `example-analytics` still load normally.