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,358 @@
1
+ # Admin Sidebar via Menus Design
2
+
3
+ **Date:** 2026-05-25
4
+ **Status:** Approved
5
+ **Scope:** Replace the hand-rolled `admin/js/config/sidebar-config.js` flat-array sidebar with a menu rendered from the Menus system at a new built-in slot `admin-sidebar`. Plugins extend via the existing menu-locations registry. The Menus admin can edit the sidebar visually. Native `<details>`/`<summary>` provides expand/collapse for nested folder behaviour.
6
+
7
+ ## Problem
8
+
9
+ The admin sidebar is currently generated by `admin/js/config/sidebar-config.js` - a hand-rolled flat array of `heading`, `divider`, and item objects passed to `Domma.elements.sidebar(...)`. This shape:
10
+
11
+ - Forces every new feature to edit the code to add an entry, instead of editing data.
12
+ - Has no native nesting - "headings" are visual groupings, not collapsible folders.
13
+ - Permission-gates each item in code (`if (canAny('pages'))`), with the rules buried in the file.
14
+ - Cannot be edited by an admin in the UI.
15
+ - Has bespoke handling for plugin-injected items via `pluginSidebarItems` in `app.js`.
16
+ - Special-cased the Projects section in the recent redesign - per-project nested entries are generated procedurally rather than living as menu data.
17
+
18
+ The Menus system shipped earlier supports unbounded depth, plugin-registered slots, per-item visibility/hidden flags, and admin-editable items. The admin sidebar is the natural next consumer.
19
+
20
+ ## Goals
21
+
22
+ - Render the admin sidebar from a menu stored at `config/menus/admin-sidebar.json` (mapped to a new core slot `admin-sidebar`).
23
+ - Each menu item gains an optional `permission` field - a resource key from `permissionRegistry`. The renderer hides items the user can't act on.
24
+ - Plugins register sidebar items via a new `hooks.registerSidebarItem({parent, item})` hook. Registered items are merged into the rendered tree without modifying the menu JSON.
25
+ - Auto-migrate the current `sidebar-config.js` output to `admin-sidebar.json` on first boot. Idempotent.
26
+ - Admin-editable: the existing Menus admin lists `admin-sidebar` like any other menu; the menu editor's item row gains a `permission` field.
27
+ - Nested folder behaviour via native `<details>`/`<summary>` - works in every browser, no upstream PR required.
28
+
29
+ ## Non-Goals (YAGNI)
30
+
31
+ - Drag-and-drop reordering directly in the sidebar (use the menu editor).
32
+ - Per-user sidebar customisation (everyone with the same permissions sees the same sidebar).
33
+ - Sidebar themes / colour overrides.
34
+ - Removing the legacy `Domma.elements.sidebar` rendering path (kept for non-CMS Domma sites that still use it).
35
+
36
+ ## Data Model
37
+
38
+ ### Menu shape - adds `permission`
39
+
40
+ Existing item shape (from the Menus design):
41
+
42
+ ```json
43
+ {
44
+ "text": "Pages",
45
+ "url": "#/pages",
46
+ "icon": "file-text",
47
+ "hidden": false,
48
+ "visibility": "public",
49
+ "items": []
50
+ }
51
+ ```
52
+
53
+ Adds one field:
54
+
55
+ | Field | Type | Description |
56
+ |---|---|---|
57
+ | `permission` | string | Resource key from `permissionRegistry` (e.g. `pages`, `menus`, `projects`). The renderer hides the item when the user doesn't have any action under that resource. Missing = always visible (subject to existing `visibility`). |
58
+
59
+ `visibility` (per-role/per-project) and `permission` (resource access) are different axes. Both apply at render time. Most admin items use `permission`; public-site menu items use `visibility`. Items can have both.
60
+
61
+ ### Built-in slot - `admin-sidebar`
62
+
63
+ Registered by core in `server/services/menus.js` alongside `navbar`, `footer-primary`, `footer-legal`:
64
+
65
+ ```js
66
+ registerLocation('admin-sidebar', {
67
+ label: 'Admin sidebar',
68
+ description: 'The left-side navigation in the admin panel',
69
+ source: 'core',
70
+ maxDepth: null // unbounded
71
+ });
72
+ ```
73
+
74
+ After migration, `config/menu-locations.json` includes:
75
+
76
+ ```json
77
+ {
78
+ "navbar": "main",
79
+ "footer-primary": "footer",
80
+ "admin-sidebar": "admin-sidebar"
81
+ }
82
+ ```
83
+
84
+ ### Migrated menu shape
85
+
86
+ `config/menus/admin-sidebar.json` after auto-migration mirrors the current sidebar structure, with sections (which are headings today) becoming top-level items that nest their links:
87
+
88
+ ```json
89
+ {
90
+ "slug": "admin-sidebar",
91
+ "name": "Admin sidebar",
92
+ "items": [
93
+ {
94
+ "text": "Overview",
95
+ "icon": "home",
96
+ "items": [
97
+ {"text": "Dashboard", "url": "#/", "icon": "home"}
98
+ ]
99
+ },
100
+ {
101
+ "text": "Projects",
102
+ "icon": "folder",
103
+ "permission": "projects",
104
+ "items": [
105
+ {"text": "Manage projects", "url": "#/projects", "icon": "folder", "permission": "projects"}
106
+ ]
107
+ },
108
+ {
109
+ "text": "Content",
110
+ "icon": "edit",
111
+ "items": [
112
+ {"text": "Pages", "url": "#/pages", "icon": "file-text", "permission": "pages"},
113
+ {"text": "Media", "url": "#/media", "icon": "image", "permission": "media"},
114
+ {"text": "Menus", "url": "#/menus", "icon": "menu", "permission": "menus"}
115
+ ]
116
+ },
117
+ {
118
+ "text": "Data",
119
+ "icon": "database",
120
+ "items": [
121
+ {"text": "Collections", "url": "#/collections", "icon": "database", "permission": "collections"},
122
+ {"text": "Forms", "url": "#/forms", "icon": "layout", "permission": "collections"},
123
+ {"text": "Views", "url": "#/views", "icon": "eye", "permission": "views"},
124
+ {"text": "Actions", "url": "#/actions", "icon": "zap", "permission": "actions"},
125
+ {"text": "Blocks", "url": "#/blocks", "icon": "box", "permission": "pages"},
126
+ {"text": "Components", "url": "#/components", "icon": "component", "permission": "components"}
127
+ ]
128
+ },
129
+ {
130
+ "text": "System",
131
+ "icon": "settings",
132
+ "items": [
133
+ {"text": "Notifications", "url": "#/system/notifications", "icon": "bell", "permission": "notifications"},
134
+ {"text": "Roles", "url": "#/roles", "icon": "shield", "permission": "plugins"},
135
+ {"text": "Users", "url": "#/users", "icon": "users", "permission": "users"},
136
+ {"text": "Site Settings", "url": "#/settings", "icon": "settings", "permission": "settings"},
137
+ {"text": "Effects", "url": "#/effects", "icon": "sparkles", "permission": "settings"},
138
+ {"text": "Layouts", "url": "#/layouts", "icon": "layout", "permission": "layouts"},
139
+ {"text": "Plugins", "url": "#/plugins", "icon": "package", "permission": "plugins"},
140
+ {"text": "My Profile", "url": "#/my-profile", "icon": "user"}
141
+ ]
142
+ },
143
+ {
144
+ "text": "Documentation",
145
+ "icon": "book",
146
+ "items": [
147
+ {"text": "Usage", "url": "#/documentation", "icon": "book"},
148
+ {"text": "Tutorials", "url": "#/tutorials", "icon": "document"},
149
+ {"text": "API Reference", "url": "#/api-reference", "icon": "code"}
150
+ ]
151
+ }
152
+ ]
153
+ }
154
+ ```
155
+
156
+ ### Projects - special-case rendering
157
+
158
+ Per-project entries are NOT stored in the menu JSON. The `Projects` top-level item is a placeholder (it has only the static `Manage projects` child by default). At render time, the sidebar renderer:
159
+
160
+ 1. Detects the item with `id === 'projects'` (or text `Projects`)
161
+ 2. Calls `/api/projects` to get the user's visible projects + per-project artefact counts
162
+ 3. Synthesises a child item per project with sub-items (Overview, populated type rows, Settings) - same shape as the current procedural code
163
+
164
+ This keeps per-project entries dynamic (they react to data changes without menu edits) while letting the admin still re-order or hide the `Projects` parent in the menu editor.
165
+
166
+ ## Server-side Services
167
+
168
+ ### `server/services/sidebar-migration.js` (new)
169
+
170
+ `runMigration({configDir})` - idempotent one-shot. Guarded by the existence of `config/menus/admin-sidebar.json`. Steps:
171
+
172
+ 1. If `config/menus/admin-sidebar.json` exists → return `{migrated: false, reason: 'already present'}`
173
+ 2. Write the seeded menu JSON (the shape above) to `config/menus/admin-sidebar.json`
174
+ 3. Patch `config/menu-locations.json` to add `"admin-sidebar": "admin-sidebar"`
175
+ 4. Log success
176
+
177
+ The seeded JSON is held in code (in the migration module). Future admin edits don't touch the migration - once the file exists, the migration is permanently skipped.
178
+
179
+ ### `server/services/menus.js`
180
+
181
+ - Register the `admin-sidebar` core slot at module load (alongside the existing three core slots).
182
+ - Update `validateMenu` to allow the `permission` field on items (no validation of the value - that's a registry lookup at render time, runtime-tolerant).
183
+
184
+ ### `server/services/hooks.js`
185
+
186
+ Add the `registerSidebarItem(opts)` export. Plugin-call shape:
187
+
188
+ ```js
189
+ hooks.registerSidebarItem({
190
+ parent: 'system', // existing item id to nest under; default = root level
191
+ item: {
192
+ text: 'My Plugin',
193
+ url: '#/plugins/my-plugin',
194
+ icon: 'package',
195
+ permission: 'plugins'
196
+ }
197
+ });
198
+ ```
199
+
200
+ Storage: module-level array `_registeredSidebarItems`. Exposed via `getRegisteredSidebarItems()` for the admin renderer to consume. Not persisted - plugins re-register on each server boot via their `setupPlugin()` hook.
201
+
202
+ ## Server-side Boot Wiring
203
+
204
+ In `server/server.js`, after config load and before route registration (i.e. alongside the existing menus migration call):
205
+
206
+ ```js
207
+ try {
208
+ const {runMigration: runSidebarMigration} = await import('./services/sidebar-migration.js');
209
+ await runSidebarMigration();
210
+ } catch (err) {
211
+ app.log.warn(`[admin-sidebar] Migration skipped: ${err.message}`);
212
+ }
213
+ ```
214
+
215
+ ## Admin Renderer
216
+
217
+ ### `admin/js/lib/sidebar-renderer.js` (new)
218
+
219
+ Drop-in replacement for the existing `Domma.elements.sidebar(...)` call. Signature:
220
+
221
+ ```js
222
+ export async function renderAdminSidebar({mount, user, permissions, counts}) {
223
+ // ...
224
+ }
225
+ ```
226
+
227
+ Behaviour:
228
+
229
+ 1. Fetch the resolved admin-sidebar menu via `/api/menus/admin-sidebar` (or the slug mapped at the `admin-sidebar` slot).
230
+ 2. Fetch `/api/projects` and per-project artefact counts (for the synthesised Projects sub-tree).
231
+ 3. Fetch dynamically-registered plugin items (from a new endpoint `/api/sidebar/registered-items` that returns the server-side `_registeredSidebarItems` snapshot).
232
+ 4. Merge plugin items into the tree under their declared `parent`.
233
+ 5. Walk the tree, filtering items by `permission` (using the supplied `permissions` array).
234
+ 6. Render as nested `<details>`/`<summary>` elements. Expansion state per-item persists to `S.set('sidebar.expanded.<itemId>', bool)`.
235
+ 7. Apply `data-icon` + run `Domma.icons.scan()` after render.
236
+ 8. Wire the active-item indicator via `M.subscribe('router:afterChange', ...)`.
237
+
238
+ The render is async (it fetches projects). The mount point shows a skeleton (or the last cached render) until the first response lands.
239
+
240
+ **Fallback when no menu is mapped to `admin-sidebar`:** the renderer falls back to a minimal hardcoded tree containing only `Dashboard` + `Menus` (the second so the admin can navigate to fix the broken state). A WARN-level log entry is emitted. This covers: first boot before migration runs, an admin who deletes the menu by accident, or the menu file being lost on disk.
241
+
242
+ ### `admin/js/app.js`
243
+
244
+ Find the existing `Domma.elements.sidebar('#admin-sidebar', {...})` call (around line 150). Replace with:
245
+
246
+ ```js
247
+ const {renderAdminSidebar} = await import('./lib/sidebar-renderer.js');
248
+ await renderAdminSidebar({
249
+ mount: '#admin-sidebar',
250
+ user,
251
+ permissions,
252
+ counts
253
+ });
254
+ ```
255
+
256
+ `buildSidebar(permissions)` simplifies - it no longer composes the items array; the renderer does that internally from menu data.
257
+
258
+ ### `admin/js/config/sidebar-config.js`
259
+
260
+ Deprecated. Keep the file with a single-line export that returns `[]` for one release (in case any external code imports it). Add a deprecation comment at the top. Delete in v2.
261
+
262
+ ### `admin/js/views/menu-editor.js`
263
+
264
+ The item-row template gains a `permission` field - a `<select>` populated from `/api/auth/permissions-registry` (already exposed). Default value: empty (no permission gate). The save handler includes `permission` on each item that has a value.
265
+
266
+ ### `admin/css/admin.css`
267
+
268
+ Append styles for the `<details>`-based sidebar:
269
+
270
+ ```css
271
+ #admin-sidebar details { /* no native marker; we provide our own */ }
272
+ #admin-sidebar details > summary {
273
+ list-style: none;
274
+ cursor: pointer;
275
+ display: flex;
276
+ align-items: center;
277
+ gap: 0.5rem;
278
+ padding: 0.4rem 0.75rem;
279
+ border-radius: 4px;
280
+ }
281
+ #admin-sidebar details > summary::-webkit-details-marker { display: none; }
282
+ #admin-sidebar details > summary::after {
283
+ content: '▸';
284
+ margin-left: auto;
285
+ transition: transform 0.15s;
286
+ }
287
+ #admin-sidebar details[open] > summary::after { transform: rotate(90deg); }
288
+ #admin-sidebar details > div { padding-left: 1rem; } /* indent rail for children */
289
+ #admin-sidebar a.active { background: var(--dm-accent-bg, rgba(0,0,0,0.06)); font-weight: 600; }
290
+ ```
291
+
292
+ ## Data Flow (on render)
293
+
294
+ ```
295
+ Browser load
296
+ ↓
297
+ admin/js/app.js buildSidebar() → renderAdminSidebar()
298
+ ↓
299
+ GET /api/menu-locations → slot map
300
+ GET /api/menus/admin-sidebar → full tree
301
+ GET /api/auth/permissions → user's permissions
302
+ GET /api/projects → user-visible projects (for Projects branch)
303
+ GET /api/projects/<slug>/artefacts (parallel per project) → per-section counts
304
+ GET /api/sidebar/registered-items → plugin-injected items
305
+ ↓
306
+ Merge plugin items + project sub-tree into menu data
307
+ Filter by permission
308
+ Render nested <details>/<summary>
309
+ Apply icon scan + active-state subscriber
310
+ ```
311
+
312
+ ## API Surface
313
+
314
+ One new endpoint:
315
+
316
+ | Method | Path | Permission | Description |
317
+ |---|---|---|---|
318
+ | `GET` | `/api/sidebar/registered-items` | `menus.read` | Plugin-registered sidebar items (read snapshot of `getRegisteredSidebarItems()`) |
319
+
320
+ No new permission family - reuses `menus.read` for the registry endpoint, and the existing `permissionRegistry` keys for item gating.
321
+
322
+ ## Caching
323
+
324
+ New cache tag:
325
+
326
+ | Tag | Invalidated when |
327
+ |---|---|
328
+ | `admin-sidebar` | The `admin-sidebar` menu changes, or plugin items registered/removed |
329
+
330
+ The sidebar renderer's fetches go through the existing per-role + per-user cache keying so scoped users see correctly-filtered sidebars.
331
+
332
+ ## Migration / Backwards Compat
333
+
334
+ - **`Domma.elements.sidebar` API is untouched** - non-CMS Domma sites that use the sidebar component directly are unaffected.
335
+ - **`sidebar-config.js` becomes a stub** for one release. Anyone importing it gets `[]` and a deprecation warning.
336
+ - **Existing user installs auto-migrate on first boot.** No admin action required. The migration is idempotent - re-runs are no-ops.
337
+ - **The Projects synth code** stays in `sidebar-renderer.js` rather than being injected into menu data, so a re-run of the sidebar migration doesn't try to re-import per-project entries.
338
+
339
+ ## Documentation Updates
340
+
341
+ - `docs/configuration.md` - add a section on the `admin-sidebar` slot + the `permission` item field.
342
+ - `docs/plugin-development.md` - document `hooks.registerSidebarItem()` alongside the existing hooks.
343
+ - `docs/api-reference.md` - add `/api/sidebar/registered-items`.
344
+ - `CLAUDE.md` - note that the admin sidebar is now menu-data-driven and editable from the Menus admin.
345
+
346
+ ## Out of Scope (Recorded for Later)
347
+
348
+ - Per-user sidebar customisation
349
+ - Drag-and-drop reorder directly in the sidebar
350
+ - Sidebar themes
351
+ - Removing the legacy `Domma.elements.sidebar` rendering path
352
+
353
+ ## Open Follow-Ups (Tracked)
354
+
355
+ - **Once `domma-js` flyout PR lands**, optionally swap the `<details>`-based render for the native flyout behaviour (cleaner mobile UX).
356
+ - **Sidebar visual editor** in the Menus admin - drag-drop reorder, live preview of the rendered sidebar.
357
+ - **Plugin items declared in plugin manifest** (`plugin.json` `sidebar: {...}`) as an alternative to the `hooks.registerSidebarItem()` runtime call - declarative for plugins that don't need dynamic items.
358
+ - **`roles` permission family** - the seeded menu gates the Roles item via `permission: 'plugins'` to preserve the current sidebar quirk. Once `roles.{read,create,update,delete}` exists as its own family, the seeded menu (and the migration) should switch to the proper key. Tracked separately because it touches the permission registry shape.