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.
- package/CHANGELOG.md +4349 -0
- package/README.md +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +2 -2
- package/admin/js/lib/mail-compose.js +1 -0
- package/admin/js/lib/page-editor-reference.js +5 -5
- package/admin/js/lib/shortcode-tags.js +1 -1
- package/admin/js/templates/call-new.html +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +39 -0
- package/admin/js/views/call-new.js +1 -1
- package/admin/js/views/contacts.js +15 -15
- package/admin/js/views/form-submissions.js +2 -2
- package/docs/DommaFeatures.md +3005 -0
- package/docs/MultiTenant.md +176 -0
- package/docs/api-reference.md +1667 -0
- package/docs/cache.md +96 -0
- package/docs/configuration.md +1811 -0
- package/docs/editions-and-licences.md +190 -0
- package/docs/getting-started.md +201 -0
- package/docs/guides/form-triggers.md +260 -0
- package/docs/guides/mongodb-storage-adapter.md +282 -0
- package/docs/guides/multi-page-forms.md +121 -0
- package/docs/images/scribe-composer.png +0 -0
- package/docs/markdown-shortcodes.md +1738 -0
- package/docs/pentest-report-2026-04-03.md +181 -0
- package/docs/plans/2026-02-28-admin-topbar-login-forms.md +349 -0
- package/docs/plans/2026-02-28-domma-ui-polish.md +286 -0
- package/docs/plans/2026-02-28-reset-seed-fresh-commands.md +520 -0
- package/docs/plans/2026-03-02-cookie-consent-design.md +86 -0
- package/docs/plans/2026-03-02-cookie-consent-plugin.md +453 -0
- package/docs/plans/2026-03-02-tooltip-sweep-and-back-to-top-plugin-design.md +126 -0
- package/docs/plans/2026-03-02-tooltip-sweep-back-to-top.md +427 -0
- package/docs/plans/2026-03-05-cookie-consent-btt-core.md +115 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core-design.md +35 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core.md +352 -0
- package/docs/plans/2026-03-05-scribe-script-shortcode.md +373 -0
- package/docs/plans/2026-03-05-user-types-design.md +139 -0
- package/docs/plans/2026-03-05-user-types-implementation.md +1035 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts-design.md +78 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts.md +388 -0
- package/docs/plans/2026-03-09-pro-documentation-design.md +80 -0
- package/docs/plans/2026-03-09-pro-documentation.md +1017 -0
- package/docs/plans/2026-06-10-core-project-design.md +240 -0
- package/docs/plugin-development.md +827 -0
- package/docs/recruitment-recipe.md +501 -0
- package/docs/scaffolding.md +312 -0
- package/docs/screenshots/calculator-floating.png +0 -0
- package/docs/screenshots/calculator-standard.png +0 -0
- package/docs/screenshots/email-settings.png +0 -0
- package/docs/screenshots/messages-count.png +0 -0
- package/docs/structure/user-types.md +107 -0
- package/docs/superpowers/plans/2026-05-25-admin-sidebar-via-menus.md +1112 -0
- package/docs/superpowers/plans/2026-06-08-navbar-mobile-drawer-and-menu-enrichment.md +1161 -0
- package/docs/superpowers/plans/2026-06-25-domma-docs-project.md +695 -0
- package/docs/superpowers/plans/2026-06-26-surveys-HANDOFF.md +68 -0
- package/docs/superpowers/plans/2026-06-26-surveys-pro-plugin.md +1902 -0
- package/docs/superpowers/plans/2026-08-19-collection-context-sort-export.md +1577 -0
- package/docs/superpowers/plans/2026-10-09-content-api.md +1192 -0
- package/docs/superpowers/specs/2026-03-12-form-followup-actions-design.md +152 -0
- package/docs/superpowers/specs/2026-03-12-page-editor-form-insert-and-per-page-theme-design.md +115 -0
- package/docs/superpowers/specs/2026-03-17-nav-hidden-items-design.md +85 -0
- package/docs/superpowers/specs/2026-03-19-accordion-insert-design.md +175 -0
- package/docs/superpowers/specs/2026-03-25-static-block-shortcode-design.md +153 -0
- package/docs/superpowers/specs/2026-03-25-text-shortcode-design.md +168 -0
- package/docs/superpowers/specs/2026-04-05-card-builder-design.md +315 -0
- package/docs/superpowers/specs/2026-04-06-insert-builders-design.md +278 -0
- package/docs/superpowers/specs/2026-04-06-layouts-design.md +226 -0
- package/docs/superpowers/specs/2026-04-07-blog-plugin-design.md +300 -0
- package/docs/superpowers/specs/2026-04-07-calendar-plugin-design.md +538 -0
- package/docs/superpowers/specs/2026-04-17-components-area-design.md +368 -0
- package/docs/superpowers/specs/2026-05-02-effects-slideover-overhaul-design.md +193 -0
- package/docs/superpowers/specs/2026-05-06-dashboard-rebuild-design.md +257 -0
- package/docs/superpowers/specs/2026-05-24-menus-design.md +466 -0
- package/docs/superpowers/specs/2026-05-25-admin-sidebar-via-menus-design.md +358 -0
- package/docs/superpowers/specs/2026-05-25-projects-design.md +380 -0
- package/docs/superpowers/specs/2026-06-08-navbar-mobile-drawer-and-menu-enrichment-design.md +292 -0
- package/docs/superpowers/specs/2026-06-08-sidebar-tools-plugins-folders-design.md +59 -0
- package/docs/superpowers/specs/2026-06-25-domma-docs-project-design.md +192 -0
- package/docs/superpowers/specs/2026-06-26-domma-charts-upstream-proposal.md +44 -0
- package/docs/superpowers/specs/2026-06-26-manager-scheduler-upstream-proposal.md +46 -0
- package/docs/superpowers/specs/2026-06-26-surveys-pro-plugin-design.md +206 -0
- package/docs/superpowers/specs/2026-07-02-shortcode-context-menu-design.md +96 -0
- package/docs/superpowers/specs/2026-08-19-collection-context-sort-export-design.md +330 -0
- package/docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md +1150 -0
- package/docs/superpowers/specs/2026-10-09-content-api-design.md +325 -0
- package/docs/superpowers/specs/plans/2026-03-12-form-followup-actions.md +624 -0
- package/docs/superpowers/specs/plans/2026-03-12-page-editor-form-insert-and-per-page-theme.md +125 -0
- package/docs/superpowers/specs/plans/2026-03-17-nav-hidden-items.md +578 -0
- package/docs/superpowers/specs/plans/2026-03-19-accordion-insert.md +635 -0
- package/docs/superpowers/specs/plans/2026-03-25-text-shortcode.md +545 -0
- package/docs/superpowers/specs/plans/2026-04-05-card-builder.md +1970 -0
- package/docs/superpowers/specs/plans/2026-04-06-insert-builders.md +1049 -0
- package/docs/superpowers/specs/plans/2026-04-06-layouts.md +1015 -0
- package/docs/superpowers/specs/plans/2026-04-07-calendar-plugin.md +1526 -0
- package/docs/superpowers/specs/plans/2026-04-08-plugin-monetisation-split.md +242 -0
- package/docs/superpowers/specs/plans/2026-04-17-components-area.md +2038 -0
- package/docs/superpowers/specs/plans/2026-05-02-effects-slideover-overhaul.md +2335 -0
- package/docs/superpowers/specs/plans/2026-05-06-dashboard-rebuild.md +1999 -0
- package/docs/superpowers/specs/plans/2026-05-17-cache-system.md +1436 -0
- package/docs/superpowers/specs/plans/2026-05-24-menus.md +3096 -0
- package/docs/superpowers/specs/plans/2026-05-25-projects.md +2869 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step2-consolidation.md +505 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step3-catalogue-and-key.md +154 -0
- package/docs/superpowers/specs/plans/2026-09-22-tools-promotion.md +260 -0
- package/docs/theming.md +320 -0
- package/package.json +2 -2
- package/plugins/_lib/admin/mail/compose-window.js +3 -1
- package/plugins/_lib/admin/mail/identity.js +18 -3
- package/plugins/_lib/admin/mail/reader-view.js +32 -3
- package/plugins/blog/CLAUDE.md +4 -0
- package/plugins/blog/admin/lib/email.js +191 -0
- package/plugins/blog/admin/views/comments.js +3 -1
- package/plugins/blog/plugin.json +2 -2
- package/plugins/blog/plugin.public.js +1 -1
- package/plugins/blog/tests/email.test.js +69 -0
- package/plugins/booking/CLAUDE.md +4 -0
- package/plugins/booking/admin/lib/email.js +191 -0
- package/plugins/booking/admin/views/panels.js +7 -3
- package/plugins/booking/plugin.json +1 -1
- package/plugins/booking/tests/email.test.js +69 -0
- package/plugins/free-tier.lock.json +27 -19
- package/plugins/shopping-cart/CLAUDE.md +4 -0
- package/plugins/shopping-cart/admin/lib/email.js +191 -0
- package/plugins/shopping-cart/admin/views/orders.js +11 -3
- package/plugins/shopping-cart/plugin.json +2 -2
- package/plugins/shopping-cart/tests/email.test.js +69 -0
- package/plugins/support/CLAUDE.md +4 -0
- package/plugins/support/admin/lib/email.js +191 -0
- package/plugins/support/admin/views/desk.js +4 -1
- package/plugins/support/admin/views/inbox.js +6 -2
- package/plugins/support/plugin.json +2 -2
- package/plugins/support/tests/email.test.js +69 -0
- package/public/js/celebrations/core/canvas.js +1 -1
- package/public/js/celebrations/index.js +1 -1
- package/public/js/celebrations/licenses/ABeeZee-Regular-OFL.txt +93 -0
- package/public/js/celebrations/licenses/Allura-Regular-OFL.txt +93 -0
- package/public/js/celebrations/message-attrs.mjs +1 -0
- package/public/js/celebrations/themes/anniversary.js +1 -0
- package/public/js/celebrations/themes/autumn.js +1 -0
- package/public/js/celebrations/themes/birthday.js +1 -0
- package/public/js/celebrations/themes/butterfly-flight.js +1 -0
- package/public/js/celebrations/themes/calendar.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year-dates.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year.js +1 -0
- package/public/js/celebrations/themes/christmas.js +1 -1
- package/public/js/celebrations/themes/easter.js +1 -0
- package/public/js/celebrations/themes/garden-drawing.js +1 -0
- package/public/js/celebrations/themes/garden.js +1 -0
- package/public/js/celebrations/themes/graduation.js +1 -0
- package/public/js/celebrations/themes/greeting.js +1 -0
- package/public/js/celebrations/themes/make-it-rain.js +1 -0
- package/public/js/celebrations/themes/money-rain.js +1 -0
- package/public/js/celebrations/themes/new-year.js +1 -0
- package/public/js/celebrations/themes/occasion-drawing.js +1 -0
- package/public/js/celebrations/themes/occasions.js +1 -0
- package/public/js/celebrations/themes/rain-control.js +1 -0
- package/public/js/celebrations/themes/sparkler-font-data.js +1 -0
- package/public/js/celebrations/themes/sparkler-font.js +1 -0
- package/public/js/celebrations/themes/sparkler-writing.js +1 -0
- package/public/js/celebrations/themes/summer-garden.js +1 -0
- package/public/js/celebrations/themes/winter.js +1 -0
- package/public/js/celebrations/trait-data.mjs +1 -1
- package/public/js/effects.js +1 -1
- package/scripts/build.js +4 -0
- package/server/services/markdown.js +5 -2
- package/server/templates/page.html +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,4349 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.7.1] - 2026-10-15
|
|
4
|
+
|
|
5
|
+
- **Email… opens Email Pro, not your desktop mail program.** In Contacts, form submissions and call links (and in Booking, Shopping Cart, Blog and Support), an Email action now opens Email Pro's compose window, already addressed, when you have a mailbox there; otherwise your device's mail program as before. Groups and selections can be emailed (everyone in Bcc). A prefilled message sits above your signature. A list too long for a mail link is copied for you to paste into Bcc, and says what was left out. New `admin/js/lib/mail-compose.js` (`openEmail`) for any screen - never `location.href = 'mailto:'`; the hand-off is `#/plugins/email-pro?compose=1&to=…`, read once by the mail reader.
|
|
6
|
+
- **Free Tools:** Booking 1.7.1, Shopping Cart 1.7.1, Blog 1.10.1 and Support 1.2.1 - their email actions go through `openEmail`, and stored addresses are encoded into links.
|
|
7
|
+
|
|
8
|
+
## [1.7.0] - 2026-10-15
|
|
9
|
+
|
|
10
|
+
- **20 celebration scenes in every site.** Add New Year, Chinese New Year, Easter, Summer Garden, Winter, Autumn, Graduation, Birthday, Anniversary, standalone sparkler greetings, weather rain and Money Rain. Choose them in the Effects builder or right-click Celebrations menu, with controls for each scene's decorations.
|
|
11
|
+
- **Personal sparkler messages.** Set `message` to your own wording, including names, Latin accents and `{year}` substitution. Choose stroke or cursive lettering and gold or silver sparks, or the Best wishes preset with a year. Chinese New Year writes 新年快乐 by default, with a dragon, lanterns and fireworks.
|
|
12
|
+
- **Garden movement.** Bees visit flowers, Easter bunnies hop and butterflies use the existing illustrated meadow flight. Automatic dates handle moving Easter and Chinese New Year and give holidays priority over Autumn and Winter.
|
|
13
|
+
- **Make it rain means weather.** Actual water streaks and splashes, with the banknote effect separately named Money Rain. Both accept a duration and remove their canvas and listeners when finished.
|
|
14
|
+
- **Editor and documentation.** Update the User Manual, editor reference, shortcode guide and feature documentation. Existing self-closing celebrations reopen for editing without losing their personalised settings. Quotes and brackets survive message editing safely. Published packages include the docs, scene preview and bundled font licence notices.
|
|
15
|
+
- Built against Domma JS 0.52.4 and domma-celebrate 1.4.0.
|
|
16
|
+
|
|
17
|
+
## [1.6.60] - 2026-10-15
|
|
18
|
+
|
|
19
|
+
- **Dialogs answer only the keys meant for them** (domma-js 0.52.3). A confirm opened by pressing Enter - on a list row or a menu item - no longer answers itself with that same Enter, so a "Throw away…?" or "Delete…?" question waits for a real answer. Escape closes only the top-most dialog or panel, not the panel underneath too. Enter on a focused dialog button does what that button says, and a `danger: true` confirm starts on Cancel. See [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.52.3).
|
|
20
|
+
- **Unsaved drafts never pass between people on a shared browser.** When a different user signs in, every kept draft that does not belong to them or to the person who used the browser last is cleared before any screen can offer it - for every Tool and plugin (`admin/js/lib/draft-owner.js`). Signing out and back in as yourself keeps your work. A half-finished edit of someone's account in Users is kept for the admin who made it (`user-editor.draft.<editorId>.for-<editedId>`), never offered to that account's owner.
|
|
21
|
+
- **Mail: Enter and Space work again on buttons** in the compose window and other panels (Send, Attach files, More options…). The reader's single-key shortcuts now apply only to the reader itself (`keyBelongsElsewhere` in `plugins/_lib/admin/mail/keyboard.js`). Enter on a focused message opens it.
|
|
22
|
+
|
|
23
|
+
## [1.6.59] - 2026-10-15
|
|
24
|
+
|
|
25
|
+
- **Mail editor: Font and Size menus**, usable by mouse or keyboard, in compose, signatures, templates and the away message. 49 email-safe fonts in groups (sans-serif, serif, monospace, display and handwriting, web fonts, generic), each shown in its own face; every choice is written inline as a span carrying the full fallback stack, so mail looks right in Outlook and Gmail. Web fonts are marked as needing a fallback and are never loaded into the sent mail. Sizes 8-72px. Domma's rich editor has none of its own: `plugins/_lib/admin/mail/font-controls.js` adds them, the lists are `fonts.js` (pure).
|
|
26
|
+
|
|
27
|
+
## [1.6.58] - 2026-10-15
|
|
28
|
+
|
|
29
|
+
- **Mail reader: editions can add their own actions.** `createReaderView({extensions})` takes `paneActions` (buttons in the Folders pane head, beside New folder), `folderItems` and `mailboxItems` (menu items on those rows), each handed a `view` with the current account, folders, API and lifetime. Actions run a task later, so the Enter that chose one cannot answer a question it opens. Email Pro 0.8.2 uses it to put **Import mail…** beside New folder and on every folder's and mailbox's menu.
|
|
30
|
+
- Escape in the mail reader no longer clears the selection or search while a panel is open.
|
|
31
|
+
|
|
32
|
+
## [1.6.57] - 2026-10-15
|
|
33
|
+
|
|
34
|
+
The Tool Upgrade's second pass for the built-in Tools: menus on every list, counts on filter buttons, skeletons while lists first load, theme tokens only, and unsaved work kept as a per-user draft with one question - **"Throw away…?"**, where OK throws the changes away and Cancel or Escape keeps them. The question says when the record changed or was deleted since, leaving while it is open keeps the draft, and the Enter that opened a row no longer answers it.
|
|
35
|
+
|
|
36
|
+
- **Analytics and Calculator.** Pages in every Analytics list open on the site by click or Enter; their menu adds "Edit the page" for the site's own pages. Tabs show counts, the first load shows a skeleton, and the chart no longer holds up the figures.
|
|
37
|
+
- **Notes and Todo.** The Notes editor opens again on current Domma; the category filter shows counts; skeletons while loading.
|
|
38
|
+
- **Forms.** The form editor no longer lets you save a form that failed to load, which used to replace it with an empty one.
|
|
39
|
+
- **Contacts.** Sidebar count, skeleton, menus on groups (right-click or Shift+F10). Changes left on a deleted contact can be saved as a new one; a contact in Trash keeps its draft until it is restored; a contact saved and then edited again keeps its draft as an edit of that contact. Bank details are never kept in browser drafts (`protectToolEditor`'s new `persist` option). Leaving Notes with a dirty editor no longer leaves a dialog over the next screen (`viewLifetime().leaving`).
|
|
40
|
+
- **SEO and System › Search.** SEO's page, redirect, import and settings panels keep unsaved work as a draft; filter buttons show counts; the search-result preview follows the theme. An SEO Pro tab is disposed when you switch to any other tab, and one still loading can no longer land in the wrong tab. Search shows a skeleton, keeps typing done during a save, and keeps unsaved settings per signed-in user.
|
|
41
|
+
- **Mail (Mail Reader and Email Pro).** Menus on folders and mailboxes in both editions; the mailbox form keeps unsaved changes per user (never the password); skeletons; theme tokens only. The unread badge turns red when a mailbox cannot be checked and updates after saves and refreshes. User text in dialogs is escaped; the section context gains `guard()` and `onClose()`; dialogs close properly on Escape; no more "Reply was already sent" in the log when the mail server fails.
|
|
42
|
+
- **Free Tools, synced from the marketplace:** Blog 1.10.0, Booking 1.7.0, Security 1.2.0, Shopping Cart 1.7.0, Feedback 1.2.0 and Support 1.2.0 - the same upgrade, with drafts kept per person.
|
|
43
|
+
|
|
44
|
+
## [1.6.56] - 2026-10-15
|
|
45
|
+
|
|
46
|
+
- Vertical navigation keeps the current page's parent menus open after following a submenu link or reloading, including nested submenus. Opening the mobile drawer reveals the current branch. Menu highlighting and decorations now resolve the correct item at every nesting level, including menus with separators and spacers.
|
|
47
|
+
|
|
48
|
+
## [1.6.55] - 2026-10-15
|
|
49
|
+
|
|
50
|
+
- **Built against domma-js 0.52.2**, whose router no longer drops an address change made while a screen is changing - the root cause of the fix in 1.6.54. Route changes now run one at a time and catch up with the address; `admin/js/lib/router-catch-up.js` stays for a site still on an older Domma. See [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.52.2).
|
|
51
|
+
|
|
52
|
+
## [1.6.54] - 2026-10-15
|
|
53
|
+
|
|
54
|
+
- **The screen follows the address, however fast it changes.** Changing the address while a screen was still changing - a link, Back or Forward, or a typed address within about 300 ms of the last change - moved the address but left the old screen showing (`#/seo/sitemap` with Redirects on screen, or any two admin screens). Domma's router ignores an address change during its fade between screens. The admin now notes a change it dropped and, once the screen in hand is drawn, shows the one the address names (`admin/js/lib/router-catch-up.js`, installed before `R.init()`).
|
|
55
|
+
|
|
56
|
+
## [1.6.53] - 2026-10-14
|
|
57
|
+
|
|
58
|
+
- **Names with a double quote survive a rename.** The rename and copy boxes - a list tag in Pages and Blocks, a Contacts group, a dashboard, a view's copy - opened a name like `Q3 "draft"` cut short at the quote, and OK saved it that way. Domma's prompt sanitises its starting value into an HTML attribute and turns `"` back into a bare quote, so escaping did not help. `promptWith(message, options, value)` in `admin/js/lib/tool-kit.js` sets the value on the input instead; a test keeps `inputValue` out of the admin.
|
|
59
|
+
|
|
60
|
+
## [1.6.52] - 2026-10-14
|
|
61
|
+
|
|
62
|
+
- **Screens that read the keyboard themselves keep their keys.** An element marked `data-own-keys` counts as typing for the admin's hotkeys, as fields do. Ctrl+D and single-key shortcuts reach it, and Ctrl+K still opens the palette. The Sheets grid uses it: Ctrl+D (fill down) opened the Tool switcher instead.
|
|
63
|
+
- **The page-editor browser test no longer fails a release at random.** It waits for the editor to load and the save to land, rather than for fixed times. Each Chrome launch gets its own profile. A Chrome that hangs before rendering is retried once; a failed check is never retried. The cut-release run for 1.6.51 failed once on such a hang.
|
|
64
|
+
|
|
65
|
+
## [1.6.51] - 2026-10-14
|
|
66
|
+
|
|
67
|
+
- **Admin tags in the page editor.** Page Details has an **Admin tags** field under Tags, with the Pages list's tags as suggestions. A save sends admin tags only when you changed them in the editor, so a tag added from the Pages list while the page was open is kept. An empty list leaves no `adminTags` key in the file.
|
|
68
|
+
- **The page editor's Tags field shows all of a page's tags.** It showed only the first tag, as "undefined". The pillbox runs its change handler while it is being built, which failed and stopped the build. The editor also gave it plain-string suggestions, which Domma turns into blank pills.
|
|
69
|
+
|
|
70
|
+
## [1.6.50] - 2026-10-14
|
|
71
|
+
|
|
72
|
+
- **Tags for pages and blocks.** Cluttered lists can now be sorted with tags that only the admin sees. A page or block can have several. Right-click one in the Pages or Blocks list and choose **Add tag ▸** (an existing tag, or New tag…) or **Remove tag ▸**. The new cog in each list's banner picks which tag the list shows (All, No tag, or one tag, remembered in your browser) and adds, renames or deletes tags. Deleting a tag only takes it off its pages or blocks; nothing else changes. Tags are stored as `adminTags` in a page's frontmatter and in a block's `.meta.json`, and the tag names in `config/list-tags.json`. A page's public `tags` are untouched. Tagging is not an edit, so it adds no revision and does not change "updated". API: `/api/list-tags/:kind` (`server/services/listTags.js`).
|
|
73
|
+
|
|
74
|
+
## [1.6.49] - 2026-10-14
|
|
75
|
+
|
|
76
|
+
- **The admin wears the site's name.** The top bar said "Domma CMS" on every site, whatever it was called. It now shows the admin brand - Site Settings › Branding › **Admin name**, logo and icon, or the site title when the name is empty - and so do the sidebar header and the browser tab ("Pages - ACME Ltd"). The brand comes with `GET /api/auth/permissions` (`adminBrandFor()` in `server/services/adminBrand.js`), so every signed-in role sees it; the sidebar header used to read Site Settings and showed "Admin" to anyone without the settings permission. Tab titles are rebranded by one observer on `<title>` (`admin/js/lib/admin-brand.js`), so views and plugins that set "… - Domma CMS" keep working, and renaming the admin updates an open tab.
|
|
77
|
+
|
|
78
|
+
## [1.6.48] - 2026-10-14
|
|
79
|
+
|
|
80
|
+
- **Security: `scope="mine"` collections are sanitised.** A per-user collection is drawn in the browser from `POST /api/collections/render-scope`, after the page was cleaned, and that fragment was never cleaned itself - so an `on*` handler or `javascript:` link in a block, or in a field shown with `{{{raw}}}`, reached the page there. The fragment now goes through the same sanitiser as the page (`sanitizePageHtml` in `markdown.js`).
|
|
81
|
+
- **A Blocks chapter in the documentation.** Documentation > Usage > Blocks (`#/docs/usage/blocks`, `/domma-docs/usage/blocks`) covers what a block is, the three ways to place one (`[collection]`/`[view]` with `display="block"`, `[block]` and the short tag), placeholders and date formats, the doc comment, scoped CSS, the Blocks screen and editor, export/import, a worked team-page example and permissions. The Blocks list and the block editor have a **Blocks guide** button in the banner.
|
|
82
|
+
- **"Writing a Plugin" is now "Writing a Tool".** The tutorial says what a Tool is and adds a section on making one feel like the built-in Tools: sidebar folder, group and hotkey, a `countUrl` badge with its popover, banner actions, help popovers and the tool kit loaded at run time, `requires`/`uses`, shipping collections and a block (created once in `onEnable`, never overwritten), and sharing a `.dcmsplugin`. The Marketplace banner links to it. Existing admin sidebars rename the item once, only where it still says Writing a Plugin (`meta.writingToolName`).
|
|
83
|
+
|
|
84
|
+
## [1.6.47] - 2026-10-14
|
|
85
|
+
|
|
86
|
+
- **Built against domma-js 0.52.1** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.52.1).
|
|
87
|
+
- **Required dropdowns start empty.** A required select in a generated form (Surveys, Onboard Pro form steps and others) now opens on "-- Select --" instead of showing the first option as chosen while sending nothing, which made the form refuse a person who wanted that first option.
|
|
88
|
+
|
|
89
|
+
## [1.6.46] - 2026-10-14
|
|
90
|
+
|
|
91
|
+
- **The admin asks for a reload after an update.** An open admin tab keeps the screen code it loaded but fetches forms fresh, so after an update it could show new settings and save without them (the Menus editor's Staggered entrance did). Signed-in `/api` answers now carry `X-Domma-CMS-Version` (the version the server booted with), and when it differs from the one the tab started on, a bar asks to save and reload; Later hides it until the next version (`admin/js/lib/update-notice.js`).
|
|
92
|
+
|
|
93
|
+
## [1.6.45] - 2026-10-14
|
|
94
|
+
|
|
95
|
+
- Custom collection and View block cards now match heights in each row, including Regimen’s About team cards. Authored widths, wrapping, full-width blocks, filters and grouping are preserved.
|
|
96
|
+
- Refreshed the public stylesheet cache version for returning visitors.
|
|
97
|
+
|
|
98
|
+
## [1.6.44] - 2026-10-14
|
|
99
|
+
|
|
100
|
+
- **Menus can stagger their items in.** A Staggered entrance switch on a menu's Behaviour tab brings the items in one after another when the menu is drawn - fade, slide (up, down, left, right), zoom, pop, flip or blur, with the delay between items and each item's duration. It applies on the navbar, footer, `[menu]` and floating panels; dropdowns and footer columns stagger on their own as they open. Stored as `entrance: {enabled, animation, stagger, duration}` (`entranceAttrs` / `applyEntrance` in `menu-decor.mjs`, animated in `menu-highlight.css`). Visitors who asked for less motion see the items at once.
|
|
101
|
+
- **A menu's CSS classes are saved.** `createMenu` / `updateMenu` dropped the `class` field, so the Behaviour tab's CSS classes never reached the file.
|
|
102
|
+
|
|
103
|
+
## [1.6.43] - 2026-10-14
|
|
104
|
+
|
|
105
|
+
- Image carousels align and crop images from the top with a shared height. Choose the shortest scaled image or the midpoint of the shortest and tallest with `height-mode`, or set a fixed pixel `height`; the page editor exposes both controls. Collection and View carousels accept the same options.
|
|
106
|
+
- Cards stretch to match the tallest card in each grid or flex row, including cards inside columns and collection cards. Side-by-side card layouts retain their responsive stacking.
|
|
107
|
+
- Admin theme application always targets the admin body and leaves the public site's saved theme preference alone. Forest Dark and Lemon Light changes keep the saved admin colours.
|
|
108
|
+
- Updated public asset cache versions so returning visitors receive the layout fixes.
|
|
109
|
+
- **Built against domma-js 0.52.0** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.52.0).
|
|
110
|
+
|
|
111
|
+
## [1.6.42] - 2026-10-13
|
|
112
|
+
|
|
113
|
+
- **A page can read another site's public API.** `csp.connectHosts` in `config/server.json` adds hosts to the Content-Security-Policy's `connect-src` (`connectSources()` in `server/services/embedCsp.js`, beside `frameSources()`; bare hostnames, https only). The policy was fixed to the site and cdn.jsdelivr.net, so a `fetch` to anywhere else was blocked.
|
|
114
|
+
|
|
115
|
+
## [1.6.41] - 2026-10-13
|
|
116
|
+
|
|
117
|
+
- **A plugin's dependencies on its details.** The Marketplace's plugin slideover shows **Needs** (its `requires`, by display name, each On, Off or Not installed) and **Works better with** (its `uses`, with what each adds), with help popovers saying that switching the plugin on switches what it needs on too. Bound with domma-reactive (`data-each`, text by binding); the rows come from the pure `dependencyRows()` in `admin/js/lib/plugins-arrange.js`. The site Manager now also switches on what a plugin needs when it grants it or turns Pro on - reactive's Cards Pro and Shopping Cart Pro were licensed and enabled but never loaded because Shopping Cart was off.
|
|
118
|
+
|
|
119
|
+
## [1.6.40] - 2026-10-13
|
|
120
|
+
|
|
121
|
+
- **Built against domma-js 0.51.14** - the CSS fireworks animate again (they named keyframes that had been renamed).
|
|
122
|
+
|
|
123
|
+
## [1.6.39] - 2026-10-13
|
|
124
|
+
|
|
125
|
+
- **Mailboxes can sign in with Microsoft or Google (OAuth2 / XOAUTH2).** The shared mail core (`plugins/_lib/mail`) gains `oauth.js` (providers, token exchange and renewal, revoke) and `oauthRoutes.js` (Connect, the provider callback, Disconnect, Test), and the mailbox form a "Sign in with Microsoft / Google" choice with a setup guide (`_lib/admin/mail/oauth-panel.js`). The customer registers their own app; the client secret and tokens are sealed like passwords. The access token is renewed before expiry and once after a refused IMAP or SMTP login (the pool, and nodemailer's `provisionCallback`); Microsoft's rotated refresh tokens are saved; a renewal the provider refuses marks the mailbox "Needs reconnecting" and fails fast from then on, never a retry loop. Only Email Pro 0.6 registers the routes; Mail Reader still reads a signed-in mailbox. Password mailboxes are unchanged. A sign-in must be finished in the browser that pressed Connect (an HttpOnly cookie bound to the one-time ticket); a renewal can never undo a Disconnect or Reconnect (a generation counter); a queued send or snooze whose mailbox needs reconnecting fails at once with the reason; the mailbox store's writes are serialised. The request log now redacts `code` and `state` query values.
|
|
126
|
+
- **Built against domma-js 0.51.13** - dialog footers follow the theme (dark themes showed a white bar).
|
|
127
|
+
- **The unsubscribe page names onboarding emails too**, now that Onboard Pro invites and reminders carry the unsubscribe footer.
|
|
128
|
+
- **Bundled Tools:** Shopping Cart 1.6.3 (the refund dialog is a real form - part refunds, a reason, restock), Feedback 1.1.8 and Support 1.1.6 (a site forgets reports and requests dcm deleted, and clears their files).
|
|
129
|
+
|
|
130
|
+
## [1.6.38] - 2026-10-13
|
|
131
|
+
|
|
132
|
+
- **Built against domma-js 0.51.12** (domma-reactive 1.3.1) - names with `&`, `<` or quotes now read as typed in the tooltips, screen-reader labels and row menus of every reactive list in the admin, instead of showing `&`.
|
|
133
|
+
- **Bundled Tools upgraded.** Blog 1.9.7, Booking 1.6.4, Feedback 1.1.7, Support 1.1.5, Shopping Cart 1.6.2 and Mail Reader 1.2.8: cached badge counts and public reads; the Booking diary rebuilt; part refunds in the Shop; file attachments and settings cogs in Feedback and Support.
|
|
134
|
+
|
|
135
|
+
## [1.6.37] - 2026-10-13
|
|
136
|
+
|
|
137
|
+
- **Outreach unsubscribe needs a click.** `GET /campaign-unsubscribe` now shows a confirm button and changes nothing; only the `POST` (the button, or a mail client's RFC 8058 one-click `List-Unsubscribe=One-Click`) puts the address on the shared suppression list. Mail security scanners that prefetch every link used to unsubscribe people who never clicked - and, the list being shared, silence Surveys and Onboard for them too. The outreach library gains `unsubscribeHeaders()` and `sendBatch` takes `headersFor`, so Campaigns Pro, Surveys and Onboard Pro can send the `List-Unsubscribe` headers Gmail and Yahoo require of bulk senders.
|
|
138
|
+
- **Outreach library reads one row, not the collection.** `_lib/outreach` ledger lookups (`invitesFor`, `findByCode`) and the suppression check (`isSuppressed`, `add`, `remove`) pass a structured `filter` to `listEntries`, so a campaign claim, a tracking hit or a send no longer loads every invite or opt-out on the site (Mongo answers from an index; the file adapter matches in memory). Plugins on an older engine keep working - the in-memory match is unchanged.
|
|
139
|
+
- **Email reply and forward markers.** Successful sends mark the original message in the list. The markers survive saved drafts, delayed sends and mailbox refreshes; failed sends leave messages unmarked.
|
|
140
|
+
|
|
141
|
+
## [1.6.34] - 2026-10-12
|
|
142
|
+
|
|
143
|
+
- Added date/time, per-IP or per-session duration and view limits, IP allowlists, and once-per-session expiry controls to every shortcode. Expired content is excluded from public render caching; editor previews do not consume visitor limits.
|
|
144
|
+
- Centred images vertically within carousels by default.
|
|
145
|
+
|
|
146
|
+
All notable changes to this project will be documented in this file.
|
|
147
|
+
|
|
148
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
149
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## [1.6.36] - 2026-10-13
|
|
154
|
+
|
|
155
|
+
- **Security sweep (2026-10-13).** A sign-in is refused when an after-login check (a second factor) throws instead of being skipped - one malformed cookie used to sign a Security Pro user in without TOTP. The request log redacts tokens, keys and secrets from query strings. Mail account and SMTP test routes refuse private and loopback hosts (`allowPrivateMailHosts` to opt out) and are rate-limited; attachment downloads honour `maxMessageBytes`. The bundled Blog, Booking, Feedback, Security, Shopping Cart, Support and Mail Reader ship their sweep fixes (slug collisions, SSRF-safe fetches, rate limits, no email links from a Host header, crypto codes, no account enumeration) - see each Tool's guide.
|
|
156
|
+
|
|
157
|
+
- **Calls: TURN credentials only on admission.** The ICE servers (with the coturn time-limited credential, when TURN is configured) now arrive in a participant's `admitted` event, made as they are let in, instead of in the join reply that a guest still waiting - or about to be turned away - also received. Anyone holding a call link used to get a relay credential for the full TTL; the default TTL is now 4 hours (was 12). The shared call page reads them from the event.
|
|
158
|
+
- **Collections API keeps to its owners.** The generic `/api/collections/:slug/...` routes (and the public `/public` and `/v1` aliases) show an admin only their own notes, tasks and contacts, as the Tools do; contact groups are readable by all and changed by their creator or root. A collection a plugin created needs that plugin's own permission when it declares one, so holding `collections` alone no longer opens every Tool's data. Root (level 0) sees everything as before.
|
|
159
|
+
- **Analytics beacon hardened.** `POST /api/analytics/hit` now accepts only a site path (`/...`, no whitespace, 2 KB at most) and refuses prototype names as counter keys - a `constructor` url used to turn every total into a string, and an oversized url became a permanent key in lifetime.json. Journey days older than 90 days are pruned on write; nothing read them and the file was rewritten on every hit.
|
|
160
|
+
|
|
161
|
+
## [1.6.35] - 2026-10-13
|
|
162
|
+
|
|
163
|
+
- **Request log redaction.** Query-string credentials (`?token=`, `?key=`, `hostKey`, `inviteKey`, `t`, anything named *secret* or *password*) are replaced with `[redacted]` in the request log. The mail reader's body grant and e-card links no longer sit in server logs.
|
|
164
|
+
- **Mail: no connections to the server's own network.** Testing or saving a mailbox or outgoing server refuses a private, loopback or link-local host (the site's own configured SMTP host excepted) unless `allowPrivateMailHosts` is set; the test routes are limited to 10 a minute. Attachment downloads now honour `maxMessageBytes` like message bodies.
|
|
165
|
+
- **Pages list transitions.** The page context menu offers Transition → Published, Draft and Review. Review pages show their status in the list, remain hidden from visitors, and keep Review selected in the editor.
|
|
166
|
+
- **Carousel appearance controls.** Choose the background and arrow colours and place the arrows at the top, middle or bottom in the editor and shortcode context menu.
|
|
167
|
+
- **Scribe web fonts.** Choose from the Domma Theme web font list, with the selected font loaded in previews and on the site.
|
|
168
|
+
- **Admin theme picks apply instantly.** Content > Theme: choosing an admin theme shows it at once so you can try them on. Save makes it everyone's; discarding or leaving without saving puts the saved theme back. Custom (Theme Roller) themes preview with their full CSS via `GET /api/theme/admin?id=`.
|
|
169
|
+
- **Shopping Cart 1.6.0: platform commission.** When the platform sets a commission for a site (on the manager), each sale records its commission when it is paid, at the rate of that day; a refund returns it. The shop sees it in Sales, on the order and in Shop settings; customers never do.
|
|
170
|
+
- **Shopping Cart 1.5.0: Sales.** A Sales tab: takings for any range by day, week or month, where the money came from (Shop, downloads and keys, Takeaway, other add-ons), how it was paid, discounts, delivery, tax, refunds and net, and a CSV for the accountant.
|
|
171
|
+
- **Shopping Cart 1.4.0: customer accounts.** Optional, from Shop settings: customers create an account confirmed by email, sign in and reset their password on pages under the shop's address, keep their details and up to 8 addresses, see every order placed with their email, and delete their account. Checkout fills in a signed-in customer, and the receipt offers to create an account. Needs the site's email set up.
|
|
172
|
+
- **Shopping Cart 1.3.0: checkout for add-ons such as Takeaway.** An add-on can sell lines it prices itself (a dish with its chosen protein and extras, shown under the line on the receipt, the order and the packing slip), add a delivery charge, take collection or delivery details, refund an order, and word the receipt's payment note ("Pay when you collect"). A shop without add-ons works as before.
|
|
173
|
+
|
|
174
|
+
## [1.6.33] - 2026-10-12
|
|
175
|
+
|
|
176
|
+
- **Ctrl+/ toggles page comments.** Comment or uncomment selected text; with no selection, toggle the current paragraph. Cmd+/ works on Mac, and edits support native undo.
|
|
177
|
+
- **Page comments.** HTML comments and lines starting with `//` are hidden before shortcode rendering; code examples and URLs are preserved.
|
|
178
|
+
- **Page history names its author.** Saves, named versions and pre-restore snapshots record the signed-in user's name instead of falling back to System.
|
|
179
|
+
- **Scribe typography.** Font, size, weight, colour, italic, alignment, line height and letter spacing are supported by `[scribe]`, the composer, edit form and context menu, with matching previews.
|
|
180
|
+
- **Favicon generator in Site Settings.** A Favicon tab generates a downloadable ICO from a Media image, with 16, 32, 48 and 256 pixel sizes, and selects it for the next settings save.
|
|
181
|
+
- **Timeline event icons and statuses.** Preserve event icons and show Completed, Planned, In progress and Blocked badges after the timeline is built.
|
|
182
|
+
|
|
183
|
+
## [1.6.32] - 2026-10-12
|
|
184
|
+
|
|
185
|
+
- **Shopping Cart 1.2.0: room for add-ons in the Shop.** An add-on such as Shopping Cart Pro can now add its own section to the product form and to an order, keep its own details on a product, show a section on the shopper's receipt page, and move an order on. Product saves and sales no longer overwrite each other. Copying a product gives its options new ids. A Cart without add-ons works as before.
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
## [1.6.31] - 2026-10-12
|
|
189
|
+
|
|
190
|
+
- **Insert Image at the top of the editor Insert menu.** Open Media to select or upload an image and insert it at the cursor.
|
|
191
|
+
- **Page editor title follows the page.** Show the page name in the editor banner and browser tab, updating as its title is edited.
|
|
192
|
+
- **Clone pages from the editor.** The small Save icon is now Clone. Choose a title and URL to create a draft copy with the current edits; the main Save button and Ctrl+S remain available.
|
|
193
|
+
|
|
194
|
+
## [1.6.30] - 2026-10-12
|
|
195
|
+
|
|
196
|
+
- **Pick carousel and hero images from Media.** Slide images and the hero background image have a button that opens Media to choose or upload a picture, in the insert form, each carousel slide row and the right-click menu. Typing an address still works.
|
|
197
|
+
|
|
198
|
+
## [1.6.29] - 2026-10-12
|
|
199
|
+
|
|
200
|
+
- **Page editor image upload shortcut.** The Custom CSS button says CSS, and an image button immediately before Help opens Media to upload or select a picture and insert it at the saved cursor position. Insert > Image uses the same picker.
|
|
201
|
+
|
|
202
|
+
## [1.6.28] - 2026-10-12
|
|
203
|
+
|
|
204
|
+
- **Card icon size and alignment.** `[card]` takes `icon-size` (xs, sm, md, lg, xl, 2xl, or an exact size such as `48px` or `3rem`) and `icon-align` (left, center or right, lining up the icon and title together). Both work with inline and stacked icons and the icon-top / icon-inline layouts, and are in the shortcode panel and card builder. The builder's inline/stacked toggle now highlights the right button.
|
|
205
|
+
|
|
206
|
+
## [1.6.27] - 2026-10-12
|
|
207
|
+
|
|
208
|
+
- **Extra large keeps the full-width image fill.** Restore cover sizing as the default; Show whole image remains an explicit Background fit choice. Full width is visible beside Size in the hero form and still works independently of height and image fit.
|
|
209
|
+
|
|
210
|
+
## [1.6.26] - 2026-10-12
|
|
211
|
+
|
|
212
|
+
- **Extra large heroes show the whole image.** The new Extra large size uses at least 75% of the viewport height and contains the background image without cropping. Background fit offers Show whole image or Fill area (crop), independently of image position and content alignment.
|
|
213
|
+
|
|
214
|
+
## [1.6.25] - 2026-10-12
|
|
215
|
+
|
|
216
|
+
- **Spaced notifications label and count.** The admin top bar separates the bell from “Notifications” and puts the unread-count pill to the right of the label.
|
|
217
|
+
|
|
218
|
+
## [1.6.24] - 2026-10-12
|
|
219
|
+
|
|
220
|
+
- **Named notifications link.** The admin top bar shows “Notifications” beside the bell, with the unread badge kept on the icon.
|
|
221
|
+
|
|
222
|
+
## [1.6.23] - 2026-10-12
|
|
223
|
+
|
|
224
|
+
- **Branding in Site Settings.** Choose the public brand name, logo, icon, link and favicon, plus the admin sidebar name, logo and icon. Choose or upload images through Media; branding uses the screen's Save, drafts and change indicators.
|
|
225
|
+
- **Hero background positioning.** The editor form and right-click menu offer horizontal Left/Centre/Right and vertical Top/Centre/Bottom background positions, independently of content alignment.
|
|
226
|
+
- **Dismissible banners work without forms.** The close handler now runs on every page. Both `dismissible` and `dismissable` are accepted, and an explicit false leaves the close button off.
|
|
227
|
+
|
|
228
|
+
## [1.6.22] - 2026-10-12
|
|
229
|
+
|
|
230
|
+
- **New Tools start right.**
|
|
231
|
+
- The scaffold template's example route used the full `/api/plugins/<slug>/…` path. The CMS already mounts a Tool's routes under that prefix, so a route written that way answered at a doubled address. The template now shows a relative path and explains why.
|
|
232
|
+
- Tools scaffolded on an installed site now get the template's `CLAUDE.md` and `docs/guide.md`, which the build had left out of the package. The New plugin panel promised the `CLAUDE.md`. The plugin-authoring guide `plugins/CLAUDE.md` ships too.
|
|
233
|
+
- **Your own Tool says so.** A Tool you made yourself was labelled *Included with Domma CMS* on its banner. Only the plugins the engine ships say that now. Bought ones say *Commercial licence*, and your own say *Your own Tool*.
|
|
234
|
+
|
|
235
|
+
## [1.6.21] - 2026-10-12
|
|
236
|
+
|
|
237
|
+
- **Built against domma-js 0.51.11** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.51.11).
|
|
238
|
+
|
|
239
|
+
## [1.6.20] - 2026-10-12
|
|
240
|
+
|
|
241
|
+
- **Scribe keeps words together.** Animated text wraps between whole words, including character entrance effects. The composer preview also stops forcing breaks inside words.
|
|
242
|
+
|
|
243
|
+
- **Actions keep to their own rules everywhere.** Three fixes:
|
|
244
|
+
- A run that stops part-way (a step fails) is reported as a failure on the site. The page's button used to say *Action completed.* even when a step had failed. The visitor gets a plain message, and the step's error goes to the log.
|
|
245
|
+
- The admin's ⚡ menu offers only the actions you may run, and running one applies the action's **Who can run it**. Before, anyone who could open Actions could run every one.
|
|
246
|
+
- **Run an Action** in authored right-click menus works. It posted to a route that does not exist. It now runs on the entry you right-clicked, signed in, by the same rules as an Action button on a page.
|
|
247
|
+
|
|
248
|
+
## [1.6.19] - 2026-10-11
|
|
249
|
+
|
|
250
|
+
- **Built against domma-js 0.51.10** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.51.10).
|
|
251
|
+
|
|
252
|
+
## [1.6.18] - 2026-10-11
|
|
253
|
+
|
|
254
|
+
- **A footer laid out in columns.** A footer menu with groups (top-level items with sub-items) is now drawn as columns - your brand, a short blurb and the social links on the left, a column per group beside them - with copyright, the legal menu and the Reduce motion control in a bar beneath. Before, the groups ran together on one line, headings and all. Footers without groups keep the single row. **Site Settings > Footer** gains **Layout** (Automatic, Columns, One row) and **Blurb**.
|
|
255
|
+
|
|
256
|
+
## [1.6.17] - 2026-10-11
|
|
257
|
+
|
|
258
|
+
- **Complete Scribe shortcode controls.** Both editor forms expose characters, words or sentences, entrance effects, typing/deletion speeds, cursor style/character/blinking, counted or continuous loops, loop delay, hover pause, manual start, reduced motion and playback controls. Script render/redraw steps inherit the default effect and can override it. Callback integrations use bubbling Scribe DOM events. Fixes the composer preview and releases old preview players.
|
|
259
|
+
- **Scribe animation improvements.** Built against the updated Domma JS release with accurate pacing, reliable pause/resume, whole emoji, working bounce/glow entrances and readable reduced-motion text.
|
|
260
|
+
- **Built against domma-js 0.51.9** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.51.9).
|
|
261
|
+
|
|
262
|
+
## [1.6.16] - 2026-10-11
|
|
263
|
+
|
|
264
|
+
- **Two kinds of menu, named apart.** The menus built in the admin are now **Site menus** (the screen, its banner, the permission and the sidebar item - an existing sidebar's "Menus" item is renamed once, unless you named it yourself), and the list a layout builds from a page's own headings is **Page contents** throughout Layouts ("Sidebar" there is now "Page contents"). Docs and help say which is which.
|
|
265
|
+
|
|
266
|
+
## [1.6.15] - 2026-10-11
|
|
267
|
+
|
|
268
|
+
- **Page contents settings, and `[contents]`.** The list a layout with a sidebar builds from the page's headings has settings - on each layout in **Layouts** (Page contents settings), and per page with the `[contents ... /]` tag:
|
|
269
|
+
- *Layout*: side, width, stays in view or scrolls, floating panel (anchor, movable by visitors), or none on this page.
|
|
270
|
+
- *Behaviour*: collapsible to a button (remembered per visitor, applied before the first paint), start collapsed, autohide off its edge, scroll-spy on or off.
|
|
271
|
+
- *Content*: which headings (sections, H2, H3, or H2 and H3 nested), numbered (1, 2.1...), a title, headings to leave out.
|
|
272
|
+
- *Look*: the menus' hover and selected effects, an accent, font family, size and weight.
|
|
273
|
+
Visitors can right-click the list to collapse or show it, copy a link to the section being read, or go back to the top. The list is now the CMS's own markup (no Domma sidebar), so the menus' highlight styles apply to it. In the page editor it is under Insert → Layout, with one-click actions on right-click.
|
|
274
|
+
|
|
275
|
+
## [1.6.14] - 2026-10-11
|
|
276
|
+
|
|
277
|
+
- **Reduced motion: a site default, and a checkbox.** **Site Settings → Footer** gains *Reduced motion for new visitors* - follow their device (as before), off (full motion until they ask for less) or on - and *The control*: the switch, or a plain checkbox. A visitor's own choice always wins, and it is applied before the first paint as before.
|
|
278
|
+
|
|
279
|
+
## [1.6.13] - 2026-10-11
|
|
280
|
+
|
|
281
|
+
- **Docked menus: width, collapse, autohide, hide on scroll.** A vertical navbar rail takes a width (160-480px), can collapse to its icons with a toggle (labels become tooltips, sub-menus fly out beside it; each visitor's choice is remembered and applied before the first paint), can start collapsed, or can autohide off its edge and come back on hover, tap or focus. A horizontal bar can hide while scrolling down and return on the way up. Set them in **Menus → Behaviour**. Visitors can right-click the rail to collapse it, hide it when not in use, or go back to the site's setting.
|
|
282
|
+
- **`[navbar]` sets a page's own navbar.** Any Behaviour setting of the navbar menu - position, orientation, side, highlight, fonts, the floating panel, and the new docked options - for one page, e.g. `[navbar collapsed="true" /]`. `true` / `false` switch settings on or off; an override that would not validate is ignored. In the page editor under Insert → Layout, with one-click actions on right-click.
|
|
283
|
+
- Page editor: a tag can now offer one-click actions and a shortlist of settings in its right-click menu (the registry's `actions` and `quickAttrs`).
|
|
284
|
+
|
|
285
|
+
## [1.6.12] - 2026-10-10
|
|
286
|
+
|
|
287
|
+
- **Built against domma-js 0.51.8: factory modals work again.** Modals made with `E.createModal()` / `E.showModal()` - in a site's own scripts or plugins - showed their HTML content as text and drew no buttons, so a `showModal()` promise never settled from a button. They now show their markup and buttons. See [the Domma release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.51.8).
|
|
288
|
+
|
|
289
|
+
## [1.6.11] - 2026-10-10
|
|
290
|
+
|
|
291
|
+
- **The theme switcher can sit in a menu.** Menus gain a **Theme switcher** item (Menus editor: **Add theme switcher**, or right-click an item) that draws the site's theme dots among the links - in the navbar, a vertical rail, the footer or a `[menu]`. It shows the themes chosen under **Theme > Theme switcher** and works with the floating switcher turned off.
|
|
292
|
+
- **`[theme-switcher]` puts one on a page.** Inline where the tag stands, or pinned with `position` (`top-left`, `top-right`, `left-center`, `right-center`, `bottom-left`, `bottom-right`) and `offset-x` / `offset-y`; `themes` narrows the list. On its page it takes the floating switcher's place, so there are never two. It is in the page editor's Insert menu under Components, and right-clicking one edits it.
|
|
293
|
+
|
|
294
|
+
## [1.6.10] - 2026-10-10
|
|
295
|
+
|
|
296
|
+
- **A layout's sidebar can be on the right.** Layouts gain `sidebarSide` (`left`, the default, or `right`), set in **System > Layouts** under the Sidebar switch; a page can choose its own with `sidebarSide` in its front matter. The page contents then sit beside the article on the right, the usual place for a documentation page's contents. The small layout drawings in the list now show the sidebar on the side it is really on - they had always drawn it on the right.
|
|
297
|
+
|
|
298
|
+
## [1.6.9] - 2026-10-10
|
|
299
|
+
|
|
300
|
+
- **The With Sidebar layout works.** It had never shown its contents: Domma's sidebar has no heading auto-generation, so the list the layout asked for came out empty - and the sidebar was started in Domma's fixed, push-the-content mode inside a layout that already gave it a grid column, so it left the grid and the push margin was taken from the wrong column, squeezing the page to 10px wide. The sidebar now lists the page's sections (its H2s, or its H3s when it has none; headings inside heroes, cards, accordions, tabs and figures are left out), sits in its column, sticks below the navbar while the page scrolls, and marks the section being read. A page with nothing to list gets one column instead of an empty rail.
|
|
301
|
+
- Docs: the `step-numbered` card's `step` attribute (the number on its badge) is now listed.
|
|
302
|
+
|
|
303
|
+
## [1.6.8] - 2026-10-10
|
|
304
|
+
|
|
305
|
+
- **The sitemap leaves out pages of a switched-off project.** They 404 (the public renderer already refused them, and site search left them out), but `/sitemap.xml` still listed them, so search engines were sent to dead pages and a backup restore test - which checks every sitemap page - failed on them. The rule now lives once in `offlinePageUrls()` (`server/services/projects.js`), which search and the sitemap both use.
|
|
306
|
+
|
|
307
|
+
## [1.6.7] - 2026-10-10
|
|
308
|
+
|
|
309
|
+
- **Notes, Todo and Contacts have a Trash.** Deleting a note, task or contact - including completed tasks cleared together and contacts deleted in bulk - moves it to that Tool's Trash instead of removing it. From Trash an item can be restored, keeping its id and every field, or deleted permanently. Trashed items stay out of normal lists, badges, notifications and contact sharing (`server/services/tool-trash.js`; `GET /trash`, `POST <items>/:id/restore`, `DELETE /trash/:id` on each Tool's API). `contact:deleted` and `note:deleted` now fire when an item is purged, not when it is trashed, so its Calendar links survive a restore.
|
|
310
|
+
- **Unsaved changes in the Notes, Todo and Contacts editors are kept.** Closing an editor with changes offers to keep them as a browser draft, per user and item; opening the item again offers to restore or discard it. Todo also keeps an unfinished task in its quick-add bar. A list that fails to load shows Retry instead of looking empty.
|
|
311
|
+
- **Analytics says when its figures are stale.** The figures show when they last refreshed; a failed refresh keeps the previous ones, marks them stale and offers Retry, and the live visitor count says when it is unavailable or stale. A slow live count can no longer overwrite a newer one.
|
|
312
|
+
- **Non-English text in context menus and collection views.** Their base64 payloads were decoded as Latin-1, so accented letters and symbols such as £ came out garbled. They are decoded as UTF-8 now (`public/js/utf8-base64.mjs`).
|
|
313
|
+
- **Effects:** hover fireworks have a hit target and fire again; a non-numeric effect setting falls back to its default instead of `NaN`; with Reduce motion on, typing effects show their text instead of nothing, and the effects showcase explains why demos are static and offers to enable them. `firework` shortcode classes are escaped.
|
|
314
|
+
|
|
315
|
+
## [1.6.6] - 2026-10-10
|
|
316
|
+
|
|
317
|
+
- **The sidebar's first folder is Dashboards, not Overview.** New installs are seeded with it, and an existing sidebar is renamed once on its next boot - only while the folder still says Overview, so a name an admin gave it stays. Its key is still `overview`, so Tools placed in it and its open or closed state are unchanged.
|
|
318
|
+
|
|
319
|
+
## [1.6.5] - 2026-10-10
|
|
320
|
+
|
|
321
|
+
- **Frequent follows you, not the browser.** The sidebar's Frequent folder was kept in the browser's storage, so each browser or device had its own list and clearing site data lost it. It is now kept on the server per user (`content/sidebar/frequent/<user id>.json`, `GET`/`PUT /api/sidebar/frequent`) and copied into the browser to draw from. A list already in a browser is sent up the first time it signs in, if the server has none yet.
|
|
322
|
+
|
|
323
|
+
## [1.6.4] - 2026-10-10
|
|
324
|
+
|
|
325
|
+
- **Security: a user limited to some projects was not limited at all.** Project access (the user editor's **Project access**, `user.projects`) was never applied: the access token does not carry it and `authenticate()` did not read it, so every `canSeeArtefact()` check saw an empty scope - unrestricted - and such a user could list, open and change every project's collections, forms, blocks, components, views, actions, menus, API endpoints and tokens. `authenticate()` now reads the scope from the user record on each request, so a change to someone's access applies at once. The route tests had stubbed `authenticate()` with a user that already carried `projects`, which is how it went unnoticed; `tests/routes/project-access-scope.test.js` uses real tokens and a real user.
|
|
326
|
+
- **Pages follow project access too.** The page routes had no project check of their own: the list, its badge and tags leave out pages in projects outside the user's scope, and opening, saving, deleting, versioning or sharing such a page answers 403. A page can no longer be created in, renamed into or re-filed under a project outside the scope. A page's project is the usual one - its front matter, else its address (`canSeePage()` in `server/services/projects.js`). Core pages stay visible to everyone.
|
|
327
|
+
- **A project outside your access is not there.** Opening, editing, deleting, untagging or listing the contents of such a project answers 404, as Delete with contents already did; the projects list already left it out once the scope was read.
|
|
328
|
+
|
|
329
|
+
## [1.6.3] - 2026-10-10
|
|
330
|
+
|
|
331
|
+
- **Parallel page loads no longer compete with their assets.** Uploaded media is excluded from the request budget, and public pages have a separate budget from background API calls. Login and other explicit route limits stay enforced.
|
|
332
|
+
- **Analytics keeps concurrent page hits.** Page-hit recording and counter resets are serialised, preventing lost counts and temporary-file rename errors during concurrent requests.
|
|
333
|
+
- **Inline shortcode examples stay literal.** Code spans already rendered inside collection answers are protected from shortcode processing, fixing the FAQ answer that expanded its sitemap example into the whole site map.
|
|
334
|
+
|
|
335
|
+
## [1.6.2] - 2026-10-09
|
|
336
|
+
|
|
337
|
+
- **A hint after G.** Press G and pause, and a small chip at the foot of the screen shows what comes next: your Frequent numbers, then the letters ("G then 1 Pages · D Dashboards · E Email…", and ? for the rest). It goes as soon as you press the letter, or when G lapses - a quick G E never shows it.
|
|
338
|
+
|
|
339
|
+
## [1.6.1] - 2026-10-09
|
|
340
|
+
|
|
341
|
+
- **The palette leads with what you came for.** With nothing typed, Tools with a hotkey come first; a project's own copy of a screen (Projects › Core › Pages) now sits after the main one instead of before it, and its name no longer carries the count ("Pages", not "Pages (8)").
|
|
342
|
+
|
|
343
|
+
## [1.6.0] - 2026-10-09
|
|
344
|
+
|
|
345
|
+
- **Hotkeys.** Press **G**, let go, then a Tool's letter to open it from anywhere in the admin: G E Email, G D Dashboards, G P Pages, G , Site Settings and so on. **G then 1-9** opens the Tools in your Frequent folder, in order. **Ctrl+K or Ctrl+D** (Cmd on a Mac) opens a palette: type part of any Tool's name, press Enter. **?** lists every hotkey you can use. Hover over a Tool in the sidebar to see its key; it is beside Open in the right-click menu too. Tools declare their own letter (`hotkey` on their sidebar entry); the built-in ones come with theirs. Keys wait while you type, and only ever cover Tools you can open.
|
|
346
|
+
|
|
347
|
+
## [1.5.0] - 2026-10-09
|
|
348
|
+
|
|
349
|
+
- **Dashboards: one entry, as tabs.** With Dashboard Pro, the sidebar's Dashboard and My Dashboard become one entry, **Dashboards**. Its first tab, **Overview**, is the Dashboard everyone knows - always there, fixed - and your own boards follow it as tabs, each at its own address you can bookmark. **+** on any tab makes a board; right-click a board's tab to rename, duplicate, move or delete it. The banner reads "Dashboards › Sales". "Open the admin on this board" is now **Open on my boards** (the board you used last), with the Overview one click away. Old My Dashboard links land on your boards. Without Dashboard Pro nothing changes.
|
|
350
|
+
- **Email: Ctrl+Enter sends** (Cmd+Enter on a Mac) from anywhere in the compose window, the editor included - after an "Are you sure?" that says who it goes to, the subject, and when (now, after the undo delay, or at the time you scheduled). **Keep writing** goes back to the message. It is in the shortcut list (press ?) and the Send help.
|
|
351
|
+
|
|
352
|
+
## [1.4.0] - 2026-10-09
|
|
353
|
+
|
|
354
|
+
- **Frequent is easier to find.** The sidebar now shows the **Frequent** folder from the start, with a hint - "Right-click any Tool and choose Add to Frequent" - until you add one. Right-click the empty folder for **Hide Frequent until I add a Tool**; adding a Tool brings it back. The User Manual (Built-in Tools) now covers Frequent.
|
|
355
|
+
- **Buy and stop paid Tools yourself.** On a site we host, a paid Tool you do not have shows **Buy…** in Marketplace > Browse (**Renew…** once its licence has expired). It opens **Manage my site** in a new tab with the Tool ticked, so you see the new monthly price and confirm there; then press Install. **Uninstalling a paid Tool now tells your site's manager**: one you pay for on its own comes off your monthly bill, with the rest of the month credited.
|
|
356
|
+
|
|
357
|
+
## [1.3.0] - 2026-10-09
|
|
358
|
+
|
|
359
|
+
- **Dashboard Pro: several boards.** Keep as many as twelve boards - Sales, Content, Support - as tabs above the Dashboard: **+** makes one, and right-clicking a tab renames, duplicates, moves or deletes it. Each has its own widgets and layout, and the board you were on is the one that opens. Right-clicking anywhere on the board now offers **Add widget…**, a new board, fold or open everything, refresh and the settings; a widget's own menu starts with Add widget too.
|
|
360
|
+
- **Dashboard Pro: a settings cog.** The board's banner has a cog with your own settings - a **name** for your board (shown in its banner and the sidebar), a **greeting** with today's date, **3, 4 or 6 columns**, **compact cards**, how often widgets **update** (live, slower, or only when you press Refresh), and whether the admin **opens on your board**. Administrators also set the board's name for everyone and can **lock the layout**, so everyone else sees the site's default and can fold and refresh widgets but not change the board.
|
|
361
|
+
|
|
362
|
+
## [1.2.0] - 2026-10-09
|
|
363
|
+
|
|
364
|
+
- **Dashboard Pro: build your own Dashboard from widgets.** With the new Dashboard Pro Tool (paid), the Dashboard becomes a board you arrange yourself. **Add widget** lists everything the Tools you use can show - traffic, visitors now, what needs attention, recent activity, site health, top pages, and a widget for every Tool with a count in the sidebar, so unread email, bookings, support tickets and form submissions are there from the start. Drag cards by their grip, size them Small to Full width, fold them down to their title (one at a time or all at once - a folded card stops refreshing until it is opened), and remove them from their menu. Your layout is yours; **Reset** goes back to the site's default, and an administrator can make their layout that default. Where another Tool owns the Dashboard (the fleet manager's), the board is at **My Dashboard** in the sidebar. Without Dashboard Pro the Dashboard is exactly as it was.
|
|
365
|
+
- **Tools can offer Dashboard widgets** with `admin.widgets` in `plugin.json` - a kind (`count`, `stat`, `list` or `chart`) and a data route, no code. See "Dashboard widgets" in `docs/plugin-development.md`.
|
|
366
|
+
- **The admin sidebar has a right-click menu, and a Frequent folder.** Right-click any Tool for Open, Open in a new tab, Add to Frequent and Copy link; right-click a folder to open or close it, or all of them. **Frequent** is your own folder at the top of the sidebar - up to twelve Tools, in the order you choose (Move up / Move down), cleared from the folder's own menu. It is yours alone: kept in your browser, never in the shared sidebar menu, and it only ever shows Tools you are allowed to open. Count badges show there too.
|
|
367
|
+
|
|
368
|
+
## [1.1.0] - 2026-10-09
|
|
369
|
+
|
|
370
|
+
- **Your own CSS classes in more places.** The same class boxes menus and forms have, and the same rules for names:
|
|
371
|
+
- **Collections and Views:** `item-class` on `[collection]` and `[view]` puts your classes on every entry - each card, list item, accordion panel, timeline event, carousel slide, list-group item or block. (Table rows are drawn in the browser and do not take them.)
|
|
372
|
+
- **Footer:** Site Settings > Footer has CSS classes for the footer itself and for every social media icon.
|
|
373
|
+
- **Every page:** Site Settings > General > Body CSS classes puts your classes on the `<body>` of every public page - one hook for site-wide styling.
|
|
374
|
+
- **Context menus:** a menu's Appearance takes CSS classes for its panel, and each item takes its own in the item's panel (item classes need Domma 0.51.7).
|
|
375
|
+
- **Built against domma-js 0.51.7** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.51.7).
|
|
376
|
+
|
|
377
|
+
## [1.0.0] - 2026-10-09
|
|
378
|
+
|
|
379
|
+
**Domma CMS 1.0.** From here the version number is a promise: anything that breaks a site or a plugin - the plugin hooks and manifest, the config file shapes, the shortcodes, the REST and Content APIs - waits for a new major version. Features arrive in minor releases and fixes in patches, as they have been. Nothing changes on upgrade; 1.0.0 is 0.120.0 plus the change below.
|
|
380
|
+
|
|
381
|
+
- **Menus: Darkened hover is stronger.** The hover wash goes from 12% to 16% black, so it reads clearly on a light bar; the selected item stays at 22%, still the darker of the two.
|
|
382
|
+
|
|
383
|
+
## [0.120.0] - 2026-10-09
|
|
384
|
+
|
|
385
|
+
- **New sites start lighter.** `npx domma-cms <name>` now creates a site on the **Dreamy Light** theme with a **Corporate Light** admin and **Open Sans** as the font (the setup wizard's theme prompt defaults to Dreamy Light too), and its main menu is a light bar that darkens the item under the pointer and, more strongly, the page you are on. Existing sites are not touched - the updater never rewrites `site.json`, `theme.json` or a menu.
|
|
386
|
+
- **Menus: a Darkened hover effect.** `highlight.hover: "darken"` puts a flat dark wash behind the item under the pointer, on light and dark bars alike - the hover twin of the selected item's Darkened, and a little lighter so the current page still stands out. Offered in the menu's Behaviour tab and in each item's own Highlighting.
|
|
387
|
+
|
|
388
|
+
## [0.119.0] - 2026-10-09
|
|
389
|
+
|
|
390
|
+
- **Forms: your own CSS classes.** The form builder has a **CSS classes** box in Settings (added to the `<form>`), under Submit Button (the submit button, and Finish on a multi-step form) and on every field (its wrapper, `[data-field="name"]`), so a designer can style a form from Custom CSS. They apply on every render path - the `[form]` shortcode, `data-form` embeds and the builder's preview - and on each step of a multi-step form. Names follow the same rules as menu classes; a bad one is refused on save. The `[form class=…]` attribute still sets the placement's wrapper.
|
|
391
|
+
|
|
392
|
+
## [0.118.0] - 2026-10-09
|
|
393
|
+
|
|
394
|
+
- **Menus: your own CSS classes.** A menu item's editor has a **Styling > CSS classes** box, and a menu has one on its **Behaviour** tab, so a designer can style a menu or a single item from Custom CSS. An item's classes go on its `<li>` - in the navbar (dropdown children included), the footer and the `[menu]` shortcode - so they can lay the item out as well as style it; target the link with `.your-class > a`. A menu's classes go on its outer element: the navbar, the footer column's `<nav>`, or the `[menu]` wrapper (before the shortcode's own `class=`). Names are letters, digits, hyphens and underscores; anything else is refused on save. See **Designer classes** in `docs/configuration.md`.
|
|
395
|
+
|
|
396
|
+
## [0.117.0] - 2026-10-09
|
|
397
|
+
|
|
398
|
+
- **Content API: headless, read-only access to your pages.** `/api/content/v1` serves the page list, one page by its path (as HTML, Markdown or both), the menu at a location, the site settings public pages already show, and search, as JSON. It is off until you switch it on at **System > Content API**, which also sets who may call it - anyone, a project API token, or a role - what it serves and in which formats, and has a Try it console that fetches a page as an anonymous caller would get it, before the API is on. A page it serves is exactly a page the site would serve the same caller: both now make one decision (`resolvePublishedPage()` in `server/services/pagePublishing.js`), and a test holds them to it. Front matter `api: false` keeps a page off the API. Payloads are allowlists - no custom front matter, no mail settings - and each page names the Domma behaviour its HTML needs. Settings: `config/content-api.json`; reference: **API Reference > Content API** and `docs/api-reference.md`.
|
|
399
|
+
- **API tokens: content scopes.** A scope line of `@pages`, `@menus`, `@site` or `@search` limits a token to those Content API resources. A token reads only its own project's pages; a token scoped only to collections gets no content.
|
|
400
|
+
- **Search no longer finds unpublished pages other than drafts.** It hid only `status: draft`, so a scheduled or status-less page came back in results that led to a 404. Search, its sidebar count and the Content API now share one rule: published, in an enabled project, and visible to the visitor.
|
|
401
|
+
|
|
402
|
+
## [0.116.8] - 2026-10-08
|
|
403
|
+
|
|
404
|
+
- **Email: choose how many messages a page lists.** A picker under the message list (between Newer and Older) offers 25, 50, 100 or 200 per page, remembered in this browser. Changing it keeps the first message of the current page in view. It applies to folders, conversations and search; the routes already honoured `limit` up to `maxListLimit` (200), the reader just never sent it. Mail Reader gets it too.
|
|
405
|
+
|
|
406
|
+
## [0.116.7] - 2026-10-08
|
|
407
|
+
|
|
408
|
+
- **Email: double-click a folder to fold it.** Double-clicking a folder that has folders inside it opens or closes it, as the chevron does; a single click still just opens the folder.
|
|
409
|
+
- **Email: a default mailbox.** Settings > Mailboxes > Make default marks one mailbox as yours to start with: it is listed first, in the settings and in the folder pane, and the screen opens on it rather than on the last one looked at. One per person; Unset default clears it (`PUT /accounts/:id/default`, `setDefaultAccount()` in `_lib/mail/accounts.js`). Mail Reader gets both too.
|
|
410
|
+
|
|
411
|
+
## [0.116.6] - 2026-10-07
|
|
412
|
+
|
|
413
|
+
- **No more lost entries when two changes land at once.** A file collection's entries are kept in one `data.json` that every change reads, alters and writes back whole; two changes overlapping (two form submissions, two imports, a save during a migration) each started from the same file and the later write silently dropped the earlier one's entry. Changes to one collection now run one at a time (`server/services/adapters/fileLock.js`, also used by `userProfiles.js`, which writes its file directly), and every write goes to a temporary file that is renamed over the old one, so a crash mid-write can no longer leave a truncated file that reads as empty. Found when two Contacts Pro imports at once stored 149 of 150 contacts.
|
|
414
|
+
|
|
415
|
+
## [0.116.5] - 2026-10-07
|
|
416
|
+
|
|
417
|
+
- **Right-click the email itself.** In Email (and Mail Reader), right-clicking the open message - or the menu key / Shift+F10 inside it - opens a menu: Copy and Quote in reply for selected text, then the same actions as the message list (Reply, Reply all, Forward, read/unread, star, spam, snooze, Move to, Save to the site, Delete, and the actions other Tools add, such as Call or Book). Links and images in the message keep the browser's own menu. The message frame now runs exactly one script, ours, allowed by its hash in the body's CSP (`MENU_BRIDGE` in `_lib/mail/render.js`); it still has no same-origin access, and the mail's own scripts are stripped and refused.
|
|
418
|
+
- **Mail Reader 1.2.4** (free-tier copy): its guide and notes describe the right-click menu on the open message.
|
|
419
|
+
|
|
420
|
+
## [0.116.4] - 2026-10-07
|
|
421
|
+
|
|
422
|
+
- **Site search is a disc on the right by default.** A new site (or one with no `config/search.json`) gets the floating search button, a 52px circle 32px from the right edge and halfway down the page, instead of the navbar icon. Sites that chose their own placement keep it.
|
|
423
|
+
|
|
424
|
+
## [0.116.3] - 2026-10-07
|
|
425
|
+
|
|
426
|
+
- **The handbook catches up with 0.103-0.116.** Built-in Tools: the purpose groups (Productivity, Communication, Marketing, Publishing, Business, Maintenance) replace the Core group in the text, a Support section, Booking among the Marketplace Tools, and what SEO Pro adds now (Search Console with sending the sitemap to Google, verification, IndexNow, speed). Navigation: sidebar counts that show when nothing needs attention, and resizing or folding the sidebar. Shortcodes: `[celebrate character="…"]`, the optional Christmas and St Patrick's scenes, and the page editor's Celebrations submenu. Pages: deleting a project with its contents, and Remove sample content. Site Settings: where the browser-tab icon comes from.
|
|
427
|
+
|
|
428
|
+
## [0.116.2] - 2026-10-05
|
|
429
|
+
|
|
430
|
+
- **Mail "Test connection" errors in plain English** (Email Pro and the mail reader). A wrong port/TLS pairing used to show OpenSSL's `wrong version number` and a self-signed server Node's `--use-system-ca` hint; they now say what to change - untick Implicit TLS on 587/143, or put a trusted certificate covering the hostname on the mail server. Hostname mismatches, expired certificates, a refused SMTP login and unreachable servers read plainly too.
|
|
431
|
+
|
|
432
|
+
## [0.116.1] - 2026-10-04
|
|
433
|
+
|
|
434
|
+
- **Support 1.1.0** (bundled): right-click a support request (or use its ⋮ button, or Shift+F10) to manage it. On a site: mark as read, mark resolved, **archive** it or **remove** it from the list - Domma keeps the conversation, and a new reply from Domma brings it straight back; removing a request that never reached Domma cancels it. In the Domma inbox: resolve or reopen, mark urgent, **archive** (out of the badge until the customer writes again), copy the customer's email, open their site's admin, delete; and right-click a message to copy it or quote it in a reply. Both screens are fully reactive, the request panel included.
|
|
435
|
+
|
|
436
|
+
## [0.116.0] - 2026-10-04
|
|
437
|
+
|
|
438
|
+
- **Celebration characters on their own.** `[celebrate character="…"]` puts one character on a page at any time of year, independent of the theme, the date and the seasonal particles: `elf`, `peeing-elf`, `thieving-elf`, `mooning-elf`, `leprechaun`, `mooning-leprechaun`, `bagpiper`, `knight`, `dragon`, `welsh-dragon`, `cupid`, `robin` and `snowman`. `size` is `small` (96px), `medium` (160px, the default), `large`, `xlarge` or a pixel height; `animate="false"` shows a still pose, and reduced motion stops it too. See `docs/markdown-shortcodes.md`.
|
|
439
|
+
- **Two optional scenes:** `[christmas mooning-elf="true" /]` and `[st-patricks mooning-leprechaun="true" /]` add a walk-in, wiggle and walk-out. Both are off unless asked for.
|
|
440
|
+
|
|
441
|
+
## [0.115.5] - 2026-10-04
|
|
442
|
+
|
|
443
|
+
- **Footer menus look like footer menus.** Nothing styled them, so every site with a footer menu showed a bulleted list; they are one wrapped row of links now, in the theme's quieter text colour. A site's own `custom.css` still wins.
|
|
444
|
+
- **Dreamy heroes centre their tagline and buttons.** The 48-character measure on `gradient-dreamy-light` hero text had no centring, so in a full-width (or any wide) centred hero they sat off to the left.
|
|
445
|
+
|
|
446
|
+
## [0.115.4] - 2026-10-04
|
|
447
|
+
|
|
448
|
+
- **Support 1.0.2** (bundled): Domma's replies to a request are emailed to the address the site was **bought with** (its owner on the Domma server), not to whatever email the requester's profile on the site holds - a site's admin can set that to anyone. A site made by hand still uses the profile email. The Support inbox says which address a reply will go to, and why.
|
|
449
|
+
|
|
450
|
+
## [0.115.3] - 2026-10-04
|
|
451
|
+
|
|
452
|
+
- **Support 1.0.1** (bundled): the address is **support@domma-cms.com** - on the contact card and for new-request alerts - and a reply emailed to a customer comes from that mailbox with Reply-To it, so a customer who answers the email reaches Domma Support.
|
|
453
|
+
|
|
454
|
+
## [0.115.2] - 2026-10-04
|
|
455
|
+
|
|
456
|
+
- **Support is on from the start on every install**, not only on sites a Domma server runs: a new project (`npx domma-cms`) has it switched on, and an update that brings it in switches it on (as it does Security). A standalone site shows the contact card and an email link; nothing can be sent from it.
|
|
457
|
+
|
|
458
|
+
## [0.115.1] - 2026-10-04
|
|
459
|
+
|
|
460
|
+
- **The pricing card's button really is readable now.** 0.115.0 set its colour, but the content-link rules (which skip only classes containing "btn", "nav" or "footer") still coloured and underlined it as a link. The button carries `card-pricing-btn` too, so they leave it alone - white (the theme's text-on-primary) and not underlined, checked in a browser.
|
|
461
|
+
|
|
462
|
+
## [0.115.0] - 2026-10-04
|
|
463
|
+
|
|
464
|
+
- **Remove sample content in one step.** A project can now be deleted together with everything filed under it - its pages (with their versions and share links), collections and their entries, forms, blocks, views, components, actions, menus, API endpoints (offline at once) and API tokens. Links to its pages leave every other menu (however they are written), and menus and right-click menus stop being bound to it - one bound only to it, or only to its pages, goes. It shows exactly what will go, then asks for the project's slug to be typed back. In **Projects > (project) > Settings**, *Delete with contents*; a project marked as sample content (a starter's example pages) shows **Remove sample content** on its overview.
|
|
465
|
+
- **Refused with nothing touched** while a user or role is filed under the project (people are never deleted with content), while one of its menus fills a site slot, or while a built-in collection is filed under it.
|
|
466
|
+
- **If something fails part way**, the project is kept so what is left can still be found under it, and the failures are listed.
|
|
467
|
+
- It needs the right to delete projects, pages and collections, and access to that project.
|
|
468
|
+
- **Every site has a browser-tab icon.** The Settings favicon or the brand logo, or otherwise the first letter of the site's name - there was none, and every page load asked for `/favicon.ico` and got a 404.
|
|
469
|
+
- **The pricing card's button is readable and goes somewhere.** It took the page's link colour (primary text on a primary button); it now uses the theme's text-on-primary colour. `[card layout="pricing" href="…"]` sets where it goes - it always went to `#`.
|
|
470
|
+
|
|
471
|
+
## [0.114.1] - 2026-10-04
|
|
472
|
+
|
|
473
|
+
- **Embedded videos play.** The Content-Security-Policy had no `frame-src`, so browsers blocked every `[embed]` player - YouTube and Vimeo included. It now allows the site itself, YouTube's privacy-friendly player, Vimeo's player and the hosts in `config/embeds.json`.
|
|
474
|
+
- **Timelines with icons show their events.** `[event icon="…"]` (and `[collection display="timeline" icon-field="…"]`) emptied every item: the icon was written where the icon scanner turns the whole element into an icon. It is kept as `data-event-icon` now; the markers show the status icon.
|
|
475
|
+
- **Countdowns count down to their date.** `[countdown to="…"]` ran a one-minute timer whatever the date, `duration` was read as milliseconds rather than seconds, and `DD:HH:mm:ss` showed minutes and seconds. All three work as documented.
|
|
476
|
+
|
|
477
|
+
## [0.114.0] - 2026-10-04
|
|
478
|
+
|
|
479
|
+
- **Support** (bundled, free on every site): a customer can reach Domma from their own admin.
|
|
480
|
+
- **The contact card** (email, phone, hours) is always on screen, set once on the Domma server and passed to every site on its next check. A site the Domma server did not start shows the card and an email link.
|
|
481
|
+
- **Ask for help** opens a conversation with the Domma team: normal or urgent, replies in the admin and by email, a badge for replies not yet read, and *Mark resolved*. Anything written while the Domma server is restarting waits on the site and is sent when it answers.
|
|
482
|
+
- **On the Domma server** it is the inbox of every site's requests: urgent first, a red badge while one is waiting, reply (the customer is emailed), resolve, priority and internal notes.
|
|
483
|
+
|
|
484
|
+
## [0.113.0] - 2026-10-03
|
|
485
|
+
|
|
486
|
+
- **Signing in from a link to a plugin's screen works.**
|
|
487
|
+
- **Before:** a `#/login?next=` link (an expired session, a link in an email) to any plugin screen came back as "not found" after signing in, with no sidebar or top bar until a reload. The login screen went there before the plugins' routes were loaded, and the router's "not found" never runs the post-sign-in step.
|
|
488
|
+
- **Now:** an address that matches nothing while you are signed in loads the plugins and is tried again, once. A link to nothing, straight after signing in, lands on your home page with a note rather than a bare "not found". Plugin routes are no longer added twice when you sign in again.
|
|
489
|
+
- **Shop 1.1.0** (bundled): other plugins can sell their own products through the Shop's checkout. Cards Pro sells card-credit packs this way. Such products never appear in the shop itself.
|
|
490
|
+
|
|
491
|
+
## [0.112.1] - 2026-10-03
|
|
492
|
+
|
|
493
|
+
- **Email stays in step with its unread badge.** An open inbox checks for mirrored arrivals every 30 seconds without interrupting search, selection or older pages. Refresh checks the mail server, returns to the first page and bypasses the cached message list; conversations and search use the newly synchronised mirror.
|
|
494
|
+
|
|
495
|
+
## [0.112.0] - 2026-10-03
|
|
496
|
+
|
|
497
|
+
- **Every Tool's sidebar badge shows a count when nothing needs attention.** Before, a Tool with nothing pending showed a blank:
|
|
498
|
+
- **Todo** with nothing open shows how many tasks are done ("All done - 7 tasks completed").
|
|
499
|
+
- **Analytics** with nobody on the site shows today's page views.
|
|
500
|
+
- **Booking, Feedback and Shop** (bundled): the bookings in the next seven days, the reports sent or received, and the products.
|
|
501
|
+
|
|
502
|
+
These counts are inventories, so a closed folder's total still only adds up what needs attention.
|
|
503
|
+
- **Bundled plugins:** Booking's badge popover had arrived as a single block of text and now shows its rows. Feedback no longer answers with an empty label.
|
|
504
|
+
- `calculator` is now on the retired-plugins list, because the marketplace reserves the built-in Tool's name.
|
|
505
|
+
|
|
506
|
+
## [0.111.0] - 2026-10-03
|
|
507
|
+
|
|
508
|
+
- **Emails can carry pictures, a Reply-To and list headers.** `sendEmail()` now passes `replyTo`, `headers` and `attachments` on to the mail transport, and omits each one when it is empty. An attachment with a `cid` is an inline image that the HTML can show with `<img src="cid:…">`. The Cards plugin uses this for the picture of a card's front, for replies going to the card's sender, and for one-click unsubscribe. Existing callers are unchanged.
|
|
509
|
+
- **A sender's name can no longer change the sender's address.** The From header is now passed to the mail transport as a separate name and address, so the name is encoded as text. Before, a display name containing quotes, `<…>` or a comma could put a different address in From. This applies to every email the CMS sends, including form notifications.
|
|
510
|
+
|
|
511
|
+
## [0.110.0] - 2026-10-02
|
|
512
|
+
|
|
513
|
+
- **Calculator Tool.** Basic and Scientific layouts can open on their own page or in a floating panel that stays open during admin navigation. Choose a saved floating position from the settings cog or drag the title bar. Uses domma-reactive for shared state, with a coloured live display, calculation history, MC/MR/MS/M+/M− memory controls and selectable accents. The settings cog lives in the standard Tool card header. Includes keyboard input, degree/radian trigonometry, powers, factorials and safe expression parsing. Calculator participates in Tool switches and role permissions.
|
|
514
|
+
|
|
515
|
+
## [0.109.5] - 2026-10-01
|
|
516
|
+
|
|
517
|
+
- **Messages keeps its unread count visible.** Sidebar count responses may request `showZero`, preserving an explicit zero badge on initial rendering and live updates when all messages are read. Other Tools keep their existing badge behavior.
|
|
518
|
+
|
|
519
|
+
## [0.109.4] - 2026-10-01
|
|
520
|
+
|
|
521
|
+
- **A second licence-signing key, for licences only.** Each trusted key now says what it may sign: plugin code, licences, or both. The marketplace key keeps both. The new `svr-2-1` key belongs to the second manager server and is trusted for licences only, so a plugin signed with it is refused like any unknown key. Licences it issues load on sites running this release or later.
|
|
522
|
+
|
|
523
|
+
## [0.109.3] - 2026-10-01
|
|
524
|
+
|
|
525
|
+
- **Explicit rate limits retain their IP-based buckets.** Login, password-reset and other routes with their own limit keep the same shared IP budget even when requests carry valid access tokens. The larger per-user allowance applies to the default API limit.
|
|
526
|
+
|
|
527
|
+
## [0.109.2] - 2026-10-01
|
|
528
|
+
|
|
529
|
+
- **Email reading preferences.** Mail Settings has a Settings tab with a configurable delay before an open message is marked as read. Enter seconds; zero marks it read immediately. The default remains one second. Preferences are saved in the browser across mailboxes, and the read-only edition continues to leave messages unchanged.
|
|
530
|
+
- **Admin reloads no longer exhaust the API rate limit.** Browser assets do not consume the global request budget. Verified access tokens receive a separate allowance of 3,000 requests per minute per user; public traffic remains limited to 500 per minute per IP. Explicit login, password-reset and other route limits remain enforced.
|
|
531
|
+
|
|
532
|
+
## [0.109.1] - 2026-10-01
|
|
533
|
+
|
|
534
|
+
- **Curled ticker tape and the remaining seasonal character redraws.** Ticker paper tumbles with shaded folds, consistent timing and retina rendering in Domma JS 0.51.6. Bonfire Night decorations, leprechauns, bagpiper, knight, English and Welsh dragons, and Cupid use the illustrated artwork from domma-celebrate 1.3.0 in the CMS renderer.
|
|
535
|
+
- **Built against domma-js 0.51.6** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.51.6).
|
|
536
|
+
|
|
537
|
+
## [0.109.0] - 2026-10-01
|
|
538
|
+
|
|
539
|
+
- **The admin sidebar can be resized and collapsed.** Drag its right edge (mouse, touch or pen) - the handle now sits on the sidebar itself with a visible grip; it used to land on the far right of the window, where nobody could find it. Focus the handle for the keyboard: arrows step the width (Shift for bigger steps), Home/End go to the narrowest and widest, Enter collapses. Double-click resets to the natural width. The chevron in the sidebar header (or dragging the edge in past the minimum) collapses it to a rail of icons: hover or focus one for its name and what its count means, click a folder to open its contents in a flyout beside the rail. Width and collapsed state are remembered per browser. A floating sidebar's gutter now follows its width, so page content no longer slides under it after a resize.
|
|
540
|
+
|
|
541
|
+
## [0.108.0] - 2026-10-01
|
|
542
|
+
|
|
543
|
+
- **Blur your background in a call.** A **Blur** button on the join screen and in every call's control bar (instant calls, online bookings, online meetings, Messages calls - for everyone in the call). The person stays sharp and the room behind them is blurred, worked out inside the caller's own browser: no picture leaves it to do this, and nothing is fetched from anyone but the site. Loads on first use (a few seconds), is remembered by the browser, and turns itself off on a machine that cannot keep up. Built on MediaPipe's selfie segmenter (Apache 2.0), hosted by the site with its usage logging removed. The site's security policy now allows WebAssembly (`'wasm-unsafe-eval'`; JavaScript `eval` stays blocked).
|
|
544
|
+
|
|
545
|
+
## [0.107.1] - 2026-10-01
|
|
546
|
+
|
|
547
|
+
- **Celebration controls in the Page Editor right-click menu.** Insert any seasonal theme, butterflies, ticker tape or fireworks from a dedicated submenu. Existing seasonal shortcodes expose their theme’s own switches, including falling particles only. Off switches filter initial and newly spawned artwork on the published site.
|
|
548
|
+
|
|
549
|
+
- **Illustrated Halloween characters and Valentine’s butterflies.** Candlelit carved pumpkins, drifting cloth ghosts, articulated bats, a stitched scarecrow, a crooked haunted house and butterflies with shaped wings, veins and markings.
|
|
550
|
+
- **Email: hidden preview text stays hidden.** Marketing mail (LinkedIn job alerts, for one) hides its inbox preview line with `display:none`; the reader stripped that but kept its `width:0`, so the text showed one letter per line under a tall gap. The reader now keeps the styles that hide things (`display`, `visibility`, `height`, `max-height`, `line-height`, `overflow: hidden`, `opacity`, `vertical-align`), still none that can position or load anything.
|
|
551
|
+
- **Built against domma-js 0.51.5** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.51.5).
|
|
552
|
+
|
|
553
|
+
## [0.107.0] - 2026-09-30
|
|
554
|
+
|
|
555
|
+
- **Add people to a call that is under way.** The host clicks **Add people** in the call, searches their contacts and the team (or types a name and email), and sends each person their own Join link - by email, and for team members also as a notification (bell and desktop) and a direct message in Messages. **Copy link** makes a link to pass on. Everyone added knocks and the host lets them in; nobody is ever given the host's link or the guest's own, and someone rung into a call does not see what was said before they came. Contacts' addresses are shown masked and only a whole address finds one; Join emails are limited per person and to 30 an hour for the whole site; links use the site's address. Instant calls ("Call Sam now…") have it, and so have Booking and Calendar calls. For Tools: `mountCallRoutes({invite: {search, send}})` adds `GET :room/people` and `POST :room/invite` (the host only, 10 people a request, 20 a minute), `_lib/call/invite.js` (signed per-person invite passes, matching, limits), `server/services/callInvites.js` (finding people and sending) and a new hook, `registerDirectMessenger` / `sendDirectMessage`, which Messages provides.
|
|
556
|
+
- **Booking 1.6.0 / Calendar 1.14.0 / Messages 1.7.0.** Add people in an online booking's call (each person their own link for the session; when the person booked has no account on the site, their call offers Copy link only) and in an online meeting's call (each person their own link for that date only). In Messages, anyone in a conversation's call can ring in another team member they may message: they get a notification and a message with a link of their own that works for that call only, they are not added to the conversation and do not see the call's earlier chat.
|
|
557
|
+
- **The call screen, corporate.** Full-bleed under the site header with no page margins; a charcoal stage with one accent colour; video tiles fill the height for any number of people; a docked control bar with labelled buttons; the chat in quiet grey blocks with your own messages in the accent; the join screen a clean card.
|
|
558
|
+
|
|
559
|
+
## [0.106.0] - 2026-09-30
|
|
560
|
+
|
|
561
|
+
- **The call screen, redesigned.** The call fills the window on a dark stage: the other person large, your own camera in a small corner window, a top bar with a timer, and round controls at the bottom. The chat reads like WhatsApp - grouped bubbles, times, day dividers - and people who join late see what was already said.
|
|
562
|
+
- **A call's chat is saved against the call.** What people type in a call's chat is kept, and a call opened again - after everyone has left, or after a restart - shows the earlier messages. Instant calls ("Call Sam now…") keep it on the call itself (the last 500 lines): a contact's Calls list says how many messages a call had, and clicking that opens the chat, read-only, for the person who made the call or an admin (`GET /api/calls/:id/chat`). Tools get the same: `_lib/call/chatStore.js` (a small JSON store for the engine's `chat: {load, save}`), `save(room, item, {identity})` now tells the Tool who the line is from (server side only, never sent to a browser), `whoOf` is exported from rooms.js, and `plugins/_lib/admin/ui/transcript.js` shows a chat read-only in a slideover.
|
|
563
|
+
- **Booking 1.5.0.** An online booking's panel has "Call chat (N messages)": the chat from its call, read-only, for staff who can see bookings. A class's shared call has one chat.
|
|
564
|
+
- **Calendar 1.13.0 / Messages 1.6.0** (Pro). Calendar keeps each date's call chat and shows the latest in the event editor for its organiser and calendar admins. In Messages, what you type in a conversation's call is posted into the conversation as your own messages ("in the call"), once - read already by everyone who was in the call.
|
|
565
|
+
|
|
566
|
+
## [0.105.2] - 2026-09-30
|
|
567
|
+
|
|
568
|
+
- **The snowman joins the Christmas redraw.** Softly shaded snow, twig arms, coal features, a carved carrot, a striped knitted scarf and a holly-trimmed felt top hat, matching domma-celebrate 1.1.1.
|
|
569
|
+
- **Built against domma-js 0.51.4** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.51.4).
|
|
570
|
+
|
|
571
|
+
## [0.105.1] - 2026-09-30
|
|
572
|
+
|
|
573
|
+
- **Christmas decorations have been redrawn with domma-celebrate 1.1.0's illustrated style.** Layered fir trees with lights and presents, evergreen wreaths, feathered robins, dressed elves, a lacquered red sleigh and the vintage green Christmas express.
|
|
574
|
+
- **Two naughty elves, both off by default.** Enable `peeing-elf="true"` or `thieving-elf="true"` on `[christmas /]` or `[celebrate theme="christmas" /]`, or use their checkboxes in the effect builder. The thief takes a real present from a visible tree, then runs away carrying it. Snow only keeps both off, regardless of attribute order.
|
|
575
|
+
- **Built against domma-js 0.51.3** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.51.3).
|
|
576
|
+
|
|
577
|
+
## [0.105.0] - 2026-09-30
|
|
578
|
+
|
|
579
|
+
- **Call someone now, from any Pro Tool with calls.** "Call Sam now…" on a contact's menu and an email's menu starts a video call on your own site with that one person, straight away - not a booking or an event. A Call now screen (`#/calls/new`) picks who, emails them a Join the call link if you like, and opens the call in a new tab with you as host; they knock and you let them in. The link lasts 3 hours, and a contact shows your recent calls with them. It is part of the CMS now (it was Booking Pro's), switched on by any of Booking Pro 0.4, Contacts Pro 0.8 or Calendar 1.12 through the new `hooks.enableInstantCalls()`; a site without one of them has no instant calls, and every route answers 404. A new **Calls** permission decides who may call (Admin is given it once). You can call a contact shared with you (Contacts Pro). The call page is `/_call/<id>`; it carries none of the site's added scripts, and the link's key leaves the address bar once the page has it (on every Tool's call page). At most 10 calls a minute per person and 3 call emails an hour to any one address.
|
|
580
|
+
- **Booking 1.4.1.** Its call page sends a reloaded address back to the link.
|
|
581
|
+
- **Reloading a call page keeps you in the call** - the link leaves the address bar once the page has it (Booking, Calendar and Messages call pages too), and a reload of this tab returns to it.
|
|
582
|
+
|
|
583
|
+
## [0.104.2] - 2026-09-30
|
|
584
|
+
|
|
585
|
+
- **The page editor's right-click menu and effect builder offer Christmas's options** - Snow only, and each decoration (trees, wreaths, North Star, snowmen, sleigh, elf, train, fireworks, robin) Shown or Off - on `[celebrate]` and on `[christmas /]`, which it now recognises.
|
|
586
|
+
|
|
587
|
+
## [0.104.1] - 2026-09-30
|
|
588
|
+
|
|
589
|
+
- **`[christmas snow-only="true" /]`** - just the falling snow, every decoration off. Switching one decoration off by name now works whatever its case - `northstars="off"` never did.
|
|
590
|
+
|
|
591
|
+
## [0.104.0] - 2026-09-30
|
|
592
|
+
|
|
593
|
+
- **The call screen has icon buttons** - mic, camera, share screen, chat (with an unread count), record and leave; on a phone the bar shows icons only.
|
|
594
|
+
- **Booking 1.4.0.** Call now: an online booking's call can be opened at once, even when it is booked for later - the customer's existing link works straight away. Extensions can add buttons to Booking's banner (Booking Pro's Call now uses it).
|
|
595
|
+
|
|
596
|
+
## [0.103.0] - 2026-09-30
|
|
597
|
+
|
|
598
|
+
- **Online meetings: video calls on your own site.** A new shared call engine (`plugins/_lib/call`) holds meetings in the browser - camera, microphone, screen sharing, chat and a waiting room the host lets people in from - with nothing to install and no outside service. Up to 6 people a call. The host can record from their own browser (the file is saved on their computer; everyone is told). Booking Pro's online appointments and the Calendar's online meetings use it; a site gets calls through those Tools. An optional `config/calls.json` points calls at a relay (TURN) server for strict office and mobile networks.
|
|
599
|
+
- **Booking 1.3.1.** Serves the call for online bookings (made by Booking Pro), and keeps it working for bookings already made if Pro lapses. The booking page is rebuilt on domma-reactive. Book from an email: a sender already in your Contacts is booked as that contact.
|
|
600
|
+
- **Tools start in groups in the sidebar** by what they are for - Productivity, Communication, Marketing, Publishing, Business, Maintenance - built-in and marketplace Tools alike. Anything you have already arranged stays where you put it.
|
|
601
|
+
- **New free Tool: Booking.** People book appointments with you or your team, and rooms or equipment, from a booking page (`/booking`, or the `[booking /]` shortcode): opening hours, days off, buffers and notice; confirmation and reminder emails with a link to move or cancel; a Diary in the admin. Book straight from a contact or an email; bookings show in the Calendar and customers become contacts. Arrives switched off - turn it on in Tools.
|
|
602
|
+
- **Tools can add actions to an email's menu.** `registerRelatedItems('message', {id, label, actions})` puts entries such as Booking's "Book Sam..." in a message's menu in Email and Mail Reader; `{email}`, `{name}` and `{subject}` in an action's url become the sender's. Any related-items action can now carry `permission: {resource, action}`, and is shown only to roles that hold it.
|
|
603
|
+
|
|
604
|
+
## [0.102.1] - 2026-09-29
|
|
605
|
+
|
|
606
|
+
- **Placeholders in block templates are filled in one pass.** A raw value (`{{{field}}}` - a post body, pre-rendered HTML) is no longer read again for `{{placeholders}}`, so text that shows `{{date}}` or `{{title}}` - a code sample, a merge-field example - keeps it instead of getting the date or title. The same fix is in Blog 1.9.4 and Blog Pro 0.5.5, whose post layouts had the same two-pass fill.
|
|
607
|
+
|
|
608
|
+
## [0.102.0] - 2026-09-29
|
|
609
|
+
|
|
610
|
+
- **Cards and boxes can span columns in a grid.** `[card span="2"]` or `[box span="2"]` (2 to 6, or `full` for the whole row) takes that many columns of a `[grid]`, whether it sits straight in the grid or inside a `[col]`. The span never takes more columns than the grid has at that screen width, so on phones it is full width and a wide card never adds a phantom column. Set it in the Card Builder, the Edit Card and Edit Box slideovers, or the right-click menu (Span (in a grid)).
|
|
611
|
+
|
|
612
|
+
## [0.101.11] - 2026-09-29
|
|
613
|
+
|
|
614
|
+
- **Cards and boxes now transition their border colour on hover.** The border moves smoothly from its existing colour to the theme colour, without an outline or lift; tinted boxes use their own theme tone.
|
|
615
|
+
- **Built against domma-js 0.51.2** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.51.2).
|
|
616
|
+
|
|
617
|
+
## [0.101.10] - 2026-09-29
|
|
618
|
+
|
|
619
|
+
- **Cards and boxes highlight instead of lifting on hover.** The default hover uses a subtle outline in the theme's primary colour; tinted boxes use their own theme colour. `hover="off"` still disables the highlight.
|
|
620
|
+
- **Built against domma-js 0.51.1** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.51.1).
|
|
621
|
+
|
|
622
|
+
## [0.101.9] - 2026-09-29
|
|
623
|
+
|
|
624
|
+
- **Fixed: stray characters in place of middle dots on some browsers.** HTML pages, public docs pages, shared-link pages and error pages are now sent as `text/html; charset=utf-8`, and the middle dots in the admin Documentation, blog blocks and cart are written as `·`, so they no longer show as garbled characters when a browser guesses the wrong encoding.
|
|
625
|
+
- **Built-in array helpers in bindings.** Tool and plugin views can use `len`, `includes`, `count`, `where`, `sortBy` and more straight from `data-bind-*`, `data-if` and `data-each` expressions (domma-reactive 1.3.0), with no computed needed.
|
|
626
|
+
- **Built against domma-js 0.51.0** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.51.0).
|
|
627
|
+
|
|
628
|
+
## [0.101.8] - 2026-09-28
|
|
629
|
+
|
|
630
|
+
### Security
|
|
631
|
+
|
|
632
|
+
- **Database connection passwords no longer reach the browser.** Data > Collections > Database connections
|
|
633
|
+
showed each MongoDB URI with its password in plain text. `GET /api/collections/connections` now masks the
|
|
634
|
+
password in every URI, secret query parameters and secret-looking driver options. Saving puts the stored value
|
|
635
|
+
back for a mask left untouched, but only while the connection still points at the same scheme, user, hosts,
|
|
636
|
+
query and driver options (renaming it or changing the database is fine) - point it anywhere else and the password must be typed
|
|
637
|
+
again, so the stored one can never be sent to another server. A mask it cannot match is refused, never saved.
|
|
638
|
+
|
|
639
|
+
### Fixed
|
|
640
|
+
|
|
641
|
+
- Saving connections from the rows view no longer wipes driver `options` set in the raw JSON.
|
|
642
|
+
|
|
643
|
+
## [0.101.7] - 2026-09-28
|
|
644
|
+
|
|
645
|
+
- **Built against domma-js 0.50.3** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.50.3).
|
|
646
|
+
|
|
647
|
+
## [0.101.6] - 2026-09-28
|
|
648
|
+
|
|
649
|
+
- **Clearer warning for bindings inside an unkeyed list.** Domma Reactive 1.2.1 gives one warning per unkeyed `{{#each}}` naming every binding it dropped and pointing at `key=`, instead of a second message advising to move them out of the block.
|
|
650
|
+
- **Built against domma-js 0.50.2** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.50.2).
|
|
651
|
+
|
|
652
|
+
## [0.101.5] - 2026-09-28
|
|
653
|
+
|
|
654
|
+
- **Fixed: accordions open to their answers again.** Every `[accordion]` and collection accordion showed an empty panel (a Domma 0.48 change paired each header with the wrong panel); fixed in domma-js 0.50.1. Reactive bindings handed an observable instead of its value now warn and stay hidden rather than showing their content (domma-reactive 1.2.0).
|
|
655
|
+
- **Fixed: the component preview on sites installed or updated from npm.** `admin/preview/` was missing from the package, so the component editor's Live preview and the page editor's component inserter showed "Route GET:/admin/preview/component-preview.html not found". A test now checks every admin file loaded by URL ships in the package.
|
|
656
|
+
- **Built against domma-js 0.50.1** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.50.1).
|
|
657
|
+
|
|
658
|
+
## [0.101.4] - 2026-09-28
|
|
659
|
+
|
|
660
|
+
- **New: `hover="off"` on `[box]` and `[card]`.** Boxes and cards lift a little when the pointer is over them; `hover="off"` (also `false`, `none`, `no`, `0`) keeps one still - for a box that is not clickable. It is in the Box insert form, the editor reference and the shortcode docs. A bare `hover` still asks for the opt-in lift.
|
|
661
|
+
|
|
662
|
+
## [0.101.3] - 2026-09-28
|
|
663
|
+
|
|
664
|
+
- **Fixed: the block editor's preview shows your site's styles.** Blocks were drawn inside the admin, so the site's theme, fonts, site styles and Custom CSS never reached them and the preview looked unstyled. The preview now draws the block inside your site's own page and redraws it as you type. It can show one copy or three in a collection grid, at full, tablet or mobile width, and in another site theme to try. `{{{field}}}` placeholders now render as HTML (they showed stray braces), template comments are left out as on the site, and image and link fields get a picture and `#` as samples.
|
|
665
|
+
- **Security: the block editor's preview cleans a block's markup as the site does.** A saved block could carry script (an `onerror` handler, a `javascript:` link, a nested frame) that the public site strips but the editor's preview ran - with the signed-in admin's access - when someone opened the block. The preview now removes it before drawing, and talks to the preview by message only.
|
|
666
|
+
- **Block editor: smaller fixes.** Ctrl+S saves while the preview has focus; a sample you have not changed follows its placeholder when you switch between `{{field}}` and `{{{field}}}` (raw rows are labelled `{{{field}}}`, and Insert/Copy use that form); placeholders inside template comments no longer get sample rows; only fields whose names end in image, photo, logo and the like get a picture (not imageCaption or photoCredit); a remembered preview theme that no longer exists falls back to the site's; a preview page that cannot hold the block says so instead of "Loading" for ever.
|
|
667
|
+
|
|
668
|
+
## [0.101.2] - 2026-09-28
|
|
669
|
+
|
|
670
|
+
- **Users list: avatars.** Each user shows their profile picture, or their initials on a colour of their own, the same colour wherever they appear. Inactive users' avatars are faded.
|
|
671
|
+
## [0.101.1] - 2026-09-28
|
|
672
|
+
|
|
673
|
+
- **Built against domma-js 0.50.0** - avatars and avatar groups (`E.avatar`, `E.avatarGroup`) and accessibility/text utilities (`.sr-only`, `.skip-link`, `.line-clamp-*`, `.stretched-link`, `.divider-text`); see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.50.0).
|
|
674
|
+
|
|
675
|
+
## [0.101.0] - 2026-09-28
|
|
676
|
+
|
|
677
|
+
- **Changed: admin help popovers use Domma's popover.** The (?) help icons open the same text as before, but the panel now follows the page as you scroll instead of closing, flips to the other side when there is no room, and Esc inside a slideover closes only the help. Screen readers hear the help's title as the panel's name. Sites on a Domma core older than 0.49 keep the previous panel.
|
|
678
|
+
- **Added: loading placeholders in the admin.** While a list or the dashboard loads, grey shapes of what is coming - rows, a table, media tiles, the dashboard's figures, lists and chart - stand in for the old "Loading..." text or empty space. A quick load shows nothing extra, and an error still shows the screen's error message. On Dashboard, Pages, Collections and their entries, Forms and submissions, Media, Users, Menus, Marketplace and Notifications. Needs domma-js 0.48 or later; an older one keeps the "Loading..." text.
|
|
679
|
+
|
|
680
|
+
## [0.100.2] - 2026-09-27
|
|
681
|
+
|
|
682
|
+
- **Built against domma-js 0.49.0** - popovers (`E.popover`): rich panels anchored to a trigger, click or hover, keyboard and screen-reader friendly; see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.49.0).
|
|
683
|
+
|
|
684
|
+
## [0.100.1] - 2026-09-27
|
|
685
|
+
|
|
686
|
+
- **Built against domma-js 0.48.0** - skeleton loaders (`E.skeleton`, `.skeleton-*`), and an accessibility sweep: Modal, Tabs, Accordion, Toast and Table add their own ARIA roles and keyboard support, and 31 theme contrast failures are fixed; see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.48.0).
|
|
687
|
+
|
|
688
|
+
## [0.100.0] - 2026-09-27
|
|
689
|
+
|
|
690
|
+
- **Built against domma-js 0.47.0** - input extras (show / hide, clear, stepper, counter) and readable link colours on 25 themes; see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.47.0).
|
|
691
|
+
- **Added: helpers on form and collection fields.** A password field can have a show / hide button, text fields and textareas a clear button (x) and a character counter, and number fields - and + buttons that respect the field's min, max and step. Tick them on the field in the form editor or the collection editor's field panel. Needs domma-js 0.47.
|
|
692
|
+
- **Added: style the submit button, give it icons, and add a second button.** The form editor's Settings tab has a **Submit Button** card: the text and position, a style (Primary, Secondary, Success, Danger, Warning, Info, the Outline styles or Ghost), a size (Small, Normal, Large) and an icon before and/or after the text, picked with the icon picker. A **second button** can sit beside it: **Clear form** empties the answers and puts the form back as it started (asking first if the visitor has typed anything), or a **Cancel link** takes the visitor to a page. It has its own text, style and icons; choose which button comes first and whether the two sit side by side or stacked, and Position lines the pair up. Works on pages and in embeds, and the live preview shows it. On a multi-step form, Next and Finish take the style and size and Finish the icons; there is no second button there. When a trigger ends a form early, its Finish keeps the icons and style. A Cancel address must be a page on this site or an http(s), mailto or tel address - anything else is refused when you save. Forms you do not change look exactly as before.
|
|
693
|
+
- **Fixed: links were hard to read on 25 themes.** Where a theme's main colour is too pale to read as text - lemon-light, silver-light, sunset-light, wedding-light, royal and others - page links now use a darker (or, on dark themes, lighter) shade of it that reads at 5:1 or better. Themes whose colour already reads keep it unchanged. Needs domma-js 0.47.
|
|
694
|
+
- **Added: choose where a form's submit button sits.** The form editor's Settings tab has a **Submit Button Position**: Default (as before, straight after the last field), Left, Centre, Right or Full width. Left, Centre and Right give the button a row of its own at that side of the form, and Full width stretches it across. It works in grid, stacked and inline forms, on pages and in embeds, and the live preview shows it. It takes over from the grid-only Submit Button Span - a form set to a full-width span keeps its look and shows as Left. Multi-step forms keep Previous and Next / Finish in their footer as before. Forms nobody positions do not move.
|
|
695
|
+
- **Fixed: the form editor's preview put the submit button on the right when visitors saw it on the left.** The preview now draws the button row the way the public page does.
|
|
696
|
+
- **Boxes follow the theme, and can be coloured.** `[box color="success"]` (or `primary`, `secondary`, `info`, `warning`, `danger`) tints a box with that colour from the site's theme - pale on a light theme, deep on a dark one - and it changes with the theme switcher. `[box color="#0f766e"]` keeps your own colour on every theme and picks black or white text for it automatically. `variant="primary"` now works on a box too.
|
|
697
|
+
- **Fixed: dark, glass and glow cards and boxes were hard to read on some themes.** `variant="dark"` had a footer at 1.9:1 contrast, links in the theme's colour on the dark panel (1.8:1) and pale code; on light themes a dark card's header strip stayed light. Everything on the dark panel is now set for it. `glass` was a white film that greyed out dark themes and is now a see-through pane of the theme's own surface, and the footers of the clean, glass, accent, gradient and glow variants use the theme's secondary text colour (the glow footer was 1.7:1 on Lemon). Applies to `[card]` and `[box]` alike.
|
|
698
|
+
|
|
699
|
+
## [0.99.0] - 2026-09-27
|
|
700
|
+
|
|
701
|
+
- **Built against domma-js 0.46.1** - joined input groups (`E.inputGroup`, addons on selects and textareas) and a Forms icon set; see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.46.1).
|
|
702
|
+
- **Added: icons and text joined to form inputs.** A form field or a collection field can now have an icon or a short text joined to the start or end of its input, Bootstrap-style - a magnifier before a search box, £ before a price, kg after a weight. Set it under Before / After in the form editor's field card, or Joined to the input in the collection editor's field panel, with the icon picker. Works on text, number, email, phone, URL, date, time, dropdown and textarea fields.
|
|
703
|
+
- **Added: form trigger messages can show the visitor's answers.** Write `{{name}}` (any field's name) in a banner, toast, block reason or end-form message, and it shows what the visitor answered - "Hello {{name}}" updates as they type. Choices show their labels, a tickbox shows Yes, and passwords, files, images and anything a trigger has hidden are never repeated back. Answers always show as plain text. An **Insert field** list beside the message box picks the field for you, and saving warns about any token that will come out blank. Redirects and admin notifications don't fill tokens yet.
|
|
704
|
+
|
|
705
|
+
## [0.98.0] - 2026-09-27
|
|
706
|
+
|
|
707
|
+
- **Context Menus editor: drag items into order.** In a context menu's Items tab you can now drag a row to move it, with the other rows sliding aside; the arrow buttons, the row menu and Alt+Up/Down still work.
|
|
708
|
+
- **Collection editor: smoother field dragging.** Field rows drag the same way as everywhere else now - the others slide out of the way, touch works and Esc cancels.
|
|
709
|
+
- **Form editor: smoother field dragging.** Drag a field card by the six-dot grip in its header and the other cards slide out of the way; it works on touch screens, Esc cancels, and Alt+Up/Down moves the focused card (the Move up/down menu items now show the shortcut).
|
|
710
|
+
|
|
711
|
+
## [0.97.0] - 2026-09-27
|
|
712
|
+
|
|
713
|
+
- **Added: Arrange mode for the admin sidebar.** A six-dot button at the top of the sidebar (for users who may edit menus) lets you rearrange the sidebar where it is: drag items to move them, drop one on the middle of another to put it in that folder, hide or show items with the eye, or use Alt+arrow keys. Done saves it for everyone; Cancel or Esc puts it back. It changes the same menu as Content > Menus > Admin sidebar, which is still where labels, icons and who-sees-what are edited.
|
|
714
|
+
|
|
715
|
+
## [0.96.0] - 2026-09-27
|
|
716
|
+
|
|
717
|
+
- **Added: Tool groups in the admin sidebar.** A Tool can name a `group` (`registerSidebarItem({group})`, or `group` on a plugin.json `admin.sidebar` entry) and then sits in a sub-folder of its folder. The built-in Tools - Notes, Todo, Analytics, Contacts and SEO - now sit in **Tools > Core**; Marketplace plugin Tools stay directly in Tools unless they name a group. The group is made when the sidebar draws and only stored once you save something inside it; moving a Tool (or the whole group) in the Menus editor still wins, **Back to its default place** returns a Tool to its group, and a user who may use none of a group's Tools does not see an empty folder.
|
|
718
|
+
- **Fixed: two sidebar folders with the same name opened and closed together.** A folder's open/closed state (and its closed-folder total) was keyed by its name alone, so Tools > Core and a project called Core, or two "Components" folders, shared one. Nested folders are now keyed by their key or their place in the tree; top-level folders keep the state you left them in.
|
|
719
|
+
- **Menus editor: drag rows the smooth way.** Dragging a row by its grip now uses Domma's new `E.sortable`: the row follows the pointer, a line shows where it will land above or below a row and an outline shows it going inside one, and the rows glide to their new places when you let go. Esc cancels, it works on touch screens, and a drop that would put a row inside its own branch or inside a separator is refused before you let go. Dropping on the line just under a folder now lands the row there, at the top of that folder, rather than below the whole folder. The grip is a six-dot handle with room before the label.
|
|
720
|
+
- **A site repo no longer picks up the CMS's own bookkeeping.** The generated `.gitignore` now leaves out `content/.notification-sources.json` (when each notification source last ran) and `content/.migrations.json` (which one-off migrations have run) alongside the SEO audit, so they stop showing as changes after every start.
|
|
721
|
+
- **Built against domma-js 0.45.0** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.45.0).
|
|
722
|
+
|
|
723
|
+
## [0.95.0] - 2026-09-27
|
|
724
|
+
|
|
725
|
+
- **Changed: the seeded Admin role can use the built-in Tools.** Admin is given Contacts, Notes, Todo, Analytics and SEO once, the first time the site starts on this release. Untick one under System > Roles and it stays unticked - it is not put back on the next start. (Until now only the level-0 role had Notes, Todo, Contacts and Analytics, so on sites whose owners are Admins those Tools were out of reach.) SEO Pro uses the same SEO permission, so it follows.
|
|
726
|
+
- **Security: applying a scaffold recipe needs permission for everything the recipe creates.** It used to need only `collections.create`, although a recipe can also create roles, user accounts, menus, a project, Actions, API tokens and API endpoints. Apply now checks each (`users.create` for roles and users, `menus.create`, `projects.create`, `actions.create`, `api-tokens.create`, `api-endpoints.create`) before it writes anything, and refuses with 403 naming every missing one. A role the recipe creates must sit below your own level and grant only permissions you hold, and a user it creates must get a role below your level. The level-0 role passes.
|
|
727
|
+
- **Fixed: scaffold recipes mangled emails and passwords.** Every option was turned into a slug, so the onboarding recipe's HR email `hr@acme.com` became `hr-acme-com` and the password was changed too. Options now have a `type`: only `slug` options (the default) are slugified; `email` must look like an address, `password` is kept exactly as typed (at least 8 characters, never logged or echoed) and `text` is trimmed. The Apply template form shows email and password fields for them.
|
|
728
|
+
- **Fixed: a scaffold apply refused for a conflict left its new project behind.** The project is now created after the conflict check.
|
|
729
|
+
- **Fixed: Marketplace > Browse said only "Installation failed." for a plugin that needs a newer Domma CMS.** It now shows the reason, as Install from file does ("SEO Pro needs Domma CMS 0.90.0 or later; this site runs 0.89.2. Update the CMS first."), and the card says **Needs newer CMS** before you click, instead of Install or Update. `GET /api/plugins/marketplace` adds `cmsVersion` and a `needsCms` sentence per plugin.
|
|
730
|
+
|
|
731
|
+
## [0.94.0] - 2026-09-27
|
|
732
|
+
|
|
733
|
+
- **Changed: `npx domma-cms` stops on a Node older than 24 again.** In 0.93.0 it only warned below the `engines` minimum, because the Domma fleet still ran on Node 22; the fleet now runs Node 24, so the scaffolder and `update` refuse to run on older Node, naming the version they found.
|
|
734
|
+
- **Docs: `docs/DommaFeatures.md` brought up to 0.93.0** - the 0.92 and 0.93 changes in the feature text (dd/mm/yyyy dates and their helpers, the Contacts group rule, action roles, `scope="mine"` with transitions, id-keeping storage moves, Notes and Todo on MongoDB, the access fixes, `minCmsVersion`, Node 24), plugin guides and the new Documentation pages, Feedback and Waypoint Pro sections, Social Media Pro's LinkedIn posting, a catalogue table rebuilt from the Marketplace manifests, and a feature history mapping reference and file fields, row-level access, storage migration and the timeline, carousel and listgroup displays to the versions they arrived in.
|
|
735
|
+
- **Added: plugin guides in the Documentation folder.** A plugin's `docs/*.md` files show under **Documentation > Plugins** (`#/docs/plugins`, one page per guide, tabs for extra files) while the plugin is enabled, and only to users who can use it - the manifest's `docs.permission`, else its sidebar permission. Markdown is sanitised and shortcodes are shown as written. Every Marketplace plugin now ships a guide; `plugins/_template` has a starter. `GET /api/documentation/plugins[/:name[/:page]]`.
|
|
736
|
+
- **Docs: the admin Documentation folder brought up to 0.93.0.** New pages: Editions & Licences, Collections & Forms, Built-in Tools, and API reference for Forms, Scaffold, the External API and tokens, and API Builder (also `docs/editions-and-licences.md`). Every existing Usage, API, Tutorial and Components page was checked against the code and corrected (roles and the visibility ladder, Actions roles, Views access, the Navigation page that still described `navigation.json`, Site Settings fields, `[view]` needing no MongoDB, and more). Template braces and blank lines inside code blocks no longer vanish or break the public handbook.
|
|
737
|
+
|
|
738
|
+
## [0.93.0] - 2026-09-27
|
|
739
|
+
|
|
740
|
+
- **Changed: dates read dd/mm/yyyy everywhere a person sees one** (dd/mm/yyyy HH:mm where a time is shown). Admin lists that fell back to ISO (`2026-09-01`) after a fortnight, draft-restore notes ("27 Sep 14:30"), entry, version, media, user and project details, collection date fields and their filter chips, the SEO screen, related items, the analytics chart axis, sidebar badge popovers, notifications (licence, API token, crash and sample notices), the password-changed email, the share-link expiry note, and the shared mail reader, compose window, send-later text and the `{date}` / `{time}` template tokens (which followed the browser's locale, so could read 9/20/2026 or 2:30 PM). "Relative" wording ("3 days ago", "tomorrow at 09:00") is kept. Stored values, API payloads, date inputs, filenames, feeds and CSV exports are unchanged. One helper per side does the formatting: `admin/js/lib/dates.js`, `server/services/dates.js`, and for plugins `plugins/_lib/admin/ui/dates.js` (browser and server) - Marketplace plugins updated to use it need this release.
|
|
741
|
+
- **Fixed: Notes and Todo declared MongoDB storage but were always kept in files**, so on a site with MongoDB any notes or todos stored there were invisible. Notes, Todo and Contacts (the Tools that hold people's own data) now use MongoDB when their collection declares it (Notes and Todo do; Contacts declare files) and the site has a working MongoDB connection - MongoDB is a Pro feature, so a site without one keeps using files, as before. A site is never switched to an empty store: while the files hold any notes or todos the collection stays on files, and where both stores hold rows it stays on files and the server log names the collection with both row counts. A collection's **Storage** move (which keeps ids) now works for these four collections and is how such a site settles it; a restart no longer resets their storage choice. Roles, user profiles, projects, notifications and API tokens/endpoints stay on files and cannot be moved.
|
|
742
|
+
- **Changed: any role an action lists is enough to run it.** An action allowed to `admin` and `hr` used to admit only admins and above - the check took the most senior role listed and compared levels, so `hr` was ignored. Each listed role now lets its holders in, and everyone more senior, across all the roles a user holds - the rule page and view visibility use (`canReadView`). It decides `POST /api/actions/:slug/public` and which transition buttons a row offers. A role the site does not have still admits only the level-0 role; an action naming no roles is for admins (levels 0 and 1), as the Actions list has always said. The action editor's help says so.
|
|
743
|
+
- **New: `transitions` on a `[collection scope="mine"]`.** An interactive "my entries" block (`searchable`, `sortable`, `filterable` or `paginate`) now renders as the Collection Browser with the viewer's own rows, so each row gets its state-machine buttons - the recruitment recipe's candidate dashboard. It used to fall back to a static list and drop the buttons. The server keeps the viewer to their rows: paging, the transition list and running a transition or `cta` from that block all say `scope: "mine"`, and a row the viewer did not create is refused with 403 (as a row-level rule refuses it) before the state check, so an illegal move on their own row is still 409. Transition buttons also honour the action's row-level rule now, so an owner-only action is no longer offered on someone else's row.
|
|
744
|
+
- **Fixed: the interactive Collection Browser never started.** `collection-browser.js` built its browsers before the class it builds them from was defined, so every `searchable` / `sortable` / `filterable` / `paginate` block stayed on its no-JS fallback. Its `cta` and transition buttons were also never wired, because `site.js` is a module and its button wiring was not visible to the browser script.
|
|
745
|
+
- **Changed: only a contact group's creator, or someone allowed to manage every group, can rename or delete it.** Groups are shared by everyone who uses Contacts, so a rename or delete changes every user's contacts - and until now anyone with the Contacts edit or delete permission could do it. New groups record who created them; the new **Contacts > Manage groups** permission (`contacts.manageGroups`) lets its holders rename or delete any group. The level-0 role holds it, and `admin` is granted it once (removed by hand, it stays removed). Groups created before this release have no recorded creator, so only holders of the permission can rename or delete them. Anyone can still file their own contacts in any group. In **Manage groups** the Rename and Delete buttons are greyed out where the server would refuse, with a help popover saying why; `GET /api/contacts/groups/access` answers who may do what, and `GET /api/contacts/groups` is still a plain list of names.
|
|
746
|
+
- **Fixed: moving a collection between file and MongoDB storage gave every entry a new id and date**, so references, links and row ownership pointing at them broke, and an entry that no longer passed validation was dropped. The move now copies each entry as it is - same id, data and `meta` (`createdAt`, `updatedAt`, `createdBy`, `source`, ...) - in both directions. It refuses, changing nothing, when the target already holds an entry with one of the ids (answering 409 with the ids) or the source holds an id twice, and 503 when the MongoDB connection is not available. The old copy is set aside, never deleted: `data.json` becomes `data.json.bak` (timestamped if one exists), and a MongoDB collection is renamed `cms_<slug>__moved_<time>` so a later move back does not collide with it. System collections (roles, user profiles, projects, ...) can no longer be moved at all - they always use file storage.
|
|
747
|
+
- **Security: an action allowed only to a role the site does not have could be run by any signed-in user** through `POST /api/actions/:slug/public`, and its transition buttons were offered to everyone - the same unknown-role gap 0.92.1 closed for collections. Unknown role names now count for nothing, and an action naming no role that exists is for the level-0 role only.
|
|
748
|
+
- **Fixed: in the role editor, the built-in Tools' permissions (Contacts, Notes, Todo, Analytics, SEO) were listed after the plugins' ones**; "Tools" now has its place before Plugins.
|
|
749
|
+
- **New in the action editor: "Create an entry in another collection".** The `createInCollection` step has run on the server for a long time but could only be added by hand-editing an action; it is now in the step picker, with a check that its data is a JSON object, and in Documentation › Actions.
|
|
750
|
+
- **Fixed: importing entries skipped the reference check** that every other save makes, so an import could store a reference to an entry that does not exist. Such entries are now skipped and reported with the rest. The import help no longer says that fields the collection lacks are ignored (they are stored as given) and names Data Transfer as the Marketplace plugin it is.
|
|
751
|
+
- **Changed: the page editor no longer offers "Show in Navigation"**. Nothing on the site has read it since menus replaced the old navigation; the site's navigation is edited in Menus. A value already stored in a page is kept.
|
|
752
|
+
- **Fixed: block templates named after a real shortcode could shadow it.** The reserved-name list had `list-group` (the tag is `listgroup`) and `effect` (no such shortcode), and lacked `embed`, `component`, `listgroup` and the named celebrations (`[christmas /]` and the rest), so a block called `embed` would have been read as a block short tag.
|
|
753
|
+
- **Fixed: installing a plugin ran its `onEnable` straight away**, with nothing passed in, although the plugin is installed switched off; a plugin that read its argument failed at install, and every plugin's `onEnable` ran twice (install, then enable). It now runs once, when the plugin is enabled, with `{fastify, services, hooks}`.
|
|
754
|
+
- **New: a plugin's `minCmsVersion` is enforced.** Installing or updating a plugin that needs a newer Domma CMS is refused with both version numbers ("… needs Domma CMS 0.93.0 or later; this site runs 0.92.2"), and an update leaves the installed version in place. A site whose CMS version cannot be read is not locked out.
|
|
755
|
+
- **New for plugin authors: `options.hooks.registerComponent({name, source})`** adds a Domma component from a `.dmc` source, used on pages as `<dm-name>` or `[component]` and listed read-only in Components. It existed in core but was never handed to plugins.
|
|
756
|
+
- **Changelog correction:** the 0.68.0 entry announced `POST /api/plugins/entitlement` for renewals; that route was never shipped. The entry now says how a renewal is applied.
|
|
757
|
+
- **Fixed: `npx domma-cms` checked for Node 18**, although Domma CMS needs Node 24 (`engines` in package.json). The check now takes the minimum from `engines` and warns, naming the version it found; it does not stop, because fleet updates still run on older Node. Its usage comment names the real `--no-seed` flag, and the list of plugins "enabled by default" - all three long since core or gone - is removed; bundled plugins start switched off as before.
|
|
758
|
+
- **Fixed: running an action whose state-machine transition no longer applies answered 400**; it now answers 409, as documented, on both `POST /api/actions/:slug/execute` and `/public`, so a client can tell "this entry has moved on" from a bad request. The action editor's help no longer mentions a `[transitions]` shortcode, which never existed - transition buttons come from an interactive `[collection … transitions]`.
|
|
759
|
+
|
|
760
|
+
## [0.92.1] - 2026-09-27
|
|
761
|
+
|
|
762
|
+
- **Security: a collection's API access set to a role the site does not have let any signed-in user through.** The collection editor used to offer `subscriber`, `editor` and `manager`, which a default install has never had; an unknown role came out as the lowest level, which every signed-in user meets. It now admits only the level-0 role, the same rule page visibility uses, on `/api/v1`, the public collection route and API Builder endpoints alike. The editor lists the site's real roles ("User and above") and marks a stale one "manager (role not found)" with a note, instead of quietly swapping it.
|
|
763
|
+
- **Fixed: `[view]` showed a view's rows to anyone who could open the page**, whatever the view's own access said. The static display, the interactive one and `/api/views/:slug/public` now make one decision (`canReadView`): public views for everyone, otherwise the view's roles on the usual ladder, and row-level rules applied as the viewer. A page with a per-user view is rendered fresh for signed-in visitors rather than served from the role-shared cache, and an anonymous visitor to a row-level view gets no rows (it used to match every row with no owner).
|
|
764
|
+
- **Fixed: views with row-level `reference` mode did no row filtering at all**, on file and MongoDB storage. Rows are now checked per viewer, and totals and page counts include only the rows that viewer may see. A `field` rule with no field set shows nothing instead of everything.
|
|
765
|
+
- **Fixed: site search listed pages the visitor cannot open.** Role-gated pages came up for anonymous visitors, title, description and snippet included. Results now follow the searcher's roles (signed-in visitors are recognised by their session, as on pages) and leave out `noindex` pages - in the page's own SEO or set in SEO - and pages in a disabled project. System › Search's "Left out" count says which.
|
|
766
|
+
- **Fixed: collection export set to a role the site does not have was hidden from everyone**, super-admins included; it is now offered to the level-0 role only.
|
|
767
|
+
- **Changed: a new view with no roles chosen is for the admin tier by level**, not by the names `admin` and `super-admin`, so renaming a role no longer changes who can read it.
|
|
768
|
+
- **Docs brought in line with the code**: getting started (the scaffolder installs for you, the real setup steps, what `reset` and `fresh` do), the full shortcode list and processing order, `SECURITY.md` (sign-outs have survived restarts since 0.83.0), `TRUST_PROXY=loopback` rather than `true` (which lets visitors fake their address), the seeded roles in place of Manager/Editor/Subscriber, the plugin hook reference, and cache keys. `docs/DommaFeatures.md` is new: the full feature reference for the CMS and the Marketplace plugins.
|
|
769
|
+
|
|
770
|
+
## [0.92.0] - 2026-09-27
|
|
771
|
+
|
|
772
|
+
- **Feedback (new, free plugin): tell Domma what is wrong or missing.** Admins of a managed site send a bug, a request, a question or praise from the admin and see what became of it - its status and any reply - in the same place. On the manager the plugin is the inbox for every site's reports, able to file one as a Waypoint Pro issue that the report then follows. A report is queued before it is sent, so a manager restart loses nothing; the site's fleet token alone says which site sent it.
|
|
773
|
+
|
|
774
|
+
## [0.91.1] - 2026-09-27
|
|
775
|
+
|
|
776
|
+
- **Fixed: changes in SEO settings (who runs the site, blog posts as articles, robots rules) could take a while to show on pages**, which are cached; saving now refreshes them at once. The same for a blog post's SEO title and description set in SEO.
|
|
777
|
+
- **Fixed: four front-end files changed without their cache-buster moving** (`site.css`, `collection-context.js`, `menu-float.js`, `collection-browser.js`). Their `?v=` tokens are bumped, and the test that was meant to catch this now compares each file's content, not just its token, and covers every versioned asset the page links.
|
|
778
|
+
|
|
779
|
+
## [0.91.0] - 2026-09-27
|
|
780
|
+
|
|
781
|
+
- **New: `[box]`** - a card with no header, for wrapping content in a plain container. Without attributes it has no shadow and a 2px corner radius; `shadow` and `rounded` change either, and the other card attributes (padding, borderless, variant, footer, class, effects) work as on `[card]`. A card can go inside a box and a box inside a card. It is in the editor's Insert menu, the Editor Reference and Documentation › Shortcodes; `rounded="xs"` (2px) is also new on `[card]`.
|
|
782
|
+
|
|
783
|
+
## [0.90.5] - 2026-09-27
|
|
784
|
+
|
|
785
|
+
- **Fixed: SEO settings would not save "who runs the site" without a name.** The name is optional now - search engines are given the site's name when it is empty.
|
|
786
|
+
- **The Media Picker for pictures in SEO**: the logo in SEO settings and a page's share image are chosen from Media - or uploaded there and then - with a Choose… button, instead of typing a path (typing one still works).
|
|
787
|
+
- **Fixed: after saving a settings slideover, "Discard the changes you have not saved?" came up anyway.** Closing a slideover tidied it away through the same check that guards unsaved changes, which still compared against the values from before the save. Every Tool's settings had it (SEO, Security and others); tidying up no longer asks.
|
|
788
|
+
|
|
789
|
+
## [0.90.4] - 2026-09-27
|
|
790
|
+
|
|
791
|
+
- **Built against domma-js 0.44.9** - a fresh install of the CMS (a new site, or `npm install` in one) no longer pulls in domma-js's development web server, `live-server`, and the ~180 packages under it; see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.44.9).
|
|
792
|
+
|
|
793
|
+
## [0.90.3] - 2026-09-27
|
|
794
|
+
|
|
795
|
+
- **The page editor has settings** (the cog in its banner): choose the editor's font - Fira Code, JetBrains Mono, Source Code Pro, IBM Plex Mono and other monospace faces, or a plain sans-serif or serif - its size, line spacing, and whether long lines wrap. Changes show as you choose and are kept in your browser for every page; the line numbers stay level with the text whatever you pick.
|
|
796
|
+
|
|
797
|
+
## [0.90.2] - 2026-09-27
|
|
798
|
+
|
|
799
|
+
- **Fixed: the page editor did not fill the window, and its editor and preview would not scroll.** The Tool banner put the editor inside two more boxes that did not pass the window's height down, so the panes grew to the page's full length: on a long page there were no scrollbars, on a short one the editor stopped part way down. It fills the window again, and each pane scrolls on its own. Live Preview no longer cuts off the bottom of the page.
|
|
800
|
+
|
|
801
|
+
## [0.90.1] - 2026-09-27
|
|
802
|
+
|
|
803
|
+
- **Built against domma-js 0.44.8** - admin and site sidebars follow the active theme; see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.44.8).
|
|
804
|
+
|
|
805
|
+
## [0.90.0] - 2026-09-27
|
|
806
|
+
|
|
807
|
+
**SEO is built in.** What was the free SEO plugin in 0.89.0 is now part of Domma CMS - a built-in Tool like Analytics, in the sidebar and on by default (it can be switched off in System › Plugins like the other Tools). SEO Pro, in the Marketplace, still adds the not-found log, the link monitor and SEO history on top.
|
|
808
|
+
|
|
809
|
+
- **SEO in the sidebar on every site** (`#/seo`): the audit of every page, the search result and share card as they will look, redirects, the sitemap and robots.txt - as the SEO plugin had them. Its API is `/api/seo` (was `/api/plugins/seo`); a new `seo` permission (View / Manage) is given to Admins once.
|
|
810
|
+
- **A site that used the SEO plugin keeps everything.** Its redirects, page overrides and settings move across on the first start (to `content/seo/` and `config/seo.json`); the old plugin folder is never loaded again.
|
|
811
|
+
- **Redirects and overrides are part of the site's content** (`content/seo/redirects.json`, `overrides.json`), tracked with the rest; how often each redirect is used, and the last audit, are kept out of the repo.
|
|
812
|
+
- For plugin authors: SEO's registry (`globalThis[Symbol.for('domma.seo')]`) is unchanged, so SEO Pro and anything else built on it works as before; `RETIRED_PLUGINS` in services/plugins.js lists plugins that became part of the CMS, whose folders are skipped.
|
|
813
|
+
|
|
814
|
+
## [0.89.1] - 2026-09-27
|
|
815
|
+
|
|
816
|
+
- **Fixed: renaming a page failed with an error, and menu links kept pointing at the old address.** Changing a page's URL Path moved the page, then failed while updating the old navigation file the menus replaced - so the editor said the save failed and stayed on the old address. Menu links to the page (every menu, sub-items included) now follow it to the new address, as the field's help always said.
|
|
817
|
+
- **The page editor is built on Domma's reactive bindings.** Every field, the Unsaved and problem notes, Discard, the view modes, the CSS pane, Live Preview, History, share links and named versions read one set of state instead of being shown, hidden and filled in by hand. Nothing looks different; History's select-all box now shows when only some versions are ticked. The Editor Reference is data drawn by one template, so adding a shortcode's entry is adding an object.
|
|
818
|
+
- **Removed: the old Navigation screen's code.** `/navigation` has sent you to Menus since menus replaced it; the unused screen behind it is gone.
|
|
819
|
+
- **Built against domma-js 0.44.7** - domma-reactive 1.1.1; see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.44.7).
|
|
820
|
+
|
|
821
|
+
## [0.89.0] - 2026-09-27
|
|
822
|
+
|
|
823
|
+
**SEO.** A new free plugin, SEO, ships with the engine (switched off until you turn it on in System › Plugins): an audit of every page, the search result and share card as they will look, redirects, and control of the sitemap and robots.txt. SEO Pro, a paid add-on in the Marketplace, adds a not-found log, a link monitor and SEO history.
|
|
824
|
+
|
|
825
|
+
- **SEO 1.0.0 (new, free).** Checks every page in the sitemap - title, description, headings, image descriptions, links, a focus keyphrase, canonical link, share image, structured data - and titles or descriptions used on more than one page. Open a page to see its Google result and share card as they will look, and change its title, description, share image and focus keyphrase, or hide it from search engines. Redirects (301, 302, 307, 308, 410 gone; `/old/*` moves everything under it) are added for you when a page is renamed, and re-aimed so they never chain. The sitemap's sources can be switched off and addresses left out; robots.txt can take extra rules, or ask every search engine to stay away while a site is not ready; the home page can tell search engines who runs the site (organisation, local business or person), and blog posts are marked as articles.
|
|
826
|
+
- **Blog posts are in the sitemap.** Until now `/sitemap.xml` listed only pages; plugins can now add what they serve (hooks.registerSitemapSource). Blog 1.9.0 lists its posts; Blog Pro 0.5.0 every blog's.
|
|
827
|
+
- **Fixed: blog pages had no canonical link** (and no `og:url`) - the Blog never told the page its address.
|
|
828
|
+
- **Fixed: saving a page in the editor wiped the SEO fields it does not show** (share image, hidden from search engines). The editor sends only the SEO title and description; the rest of a page's `seo` is now kept. The Blog's post editor had the same fault, fixed in Blog 1.9.0 / Blog Pro 0.5.0.
|
|
829
|
+
- **Share images and structured-data images are full addresses** (`https://…/media/photo.jpg`, not `/media/photo.jpg`) - Facebook, LinkedIn and others ignore a relative one.
|
|
830
|
+
- **Fixed: a page saved without a visibility was left out of the sitemap**, although it is served to everyone.
|
|
831
|
+
- For plugin authors: the `seo:meta` transform (one model behind `<title>`, the description and every SEO tag, on pages and plugin pages - `renderBlogPage` passes `seoMeta.item`), `sitemap:entries` and `robots:lines` transforms, `hooks.sitemapChanged()`, `sitemap.listSitemapEntries()`, and the `content:pageRenamed` event. JSON-LD now escapes every `<`.
|
|
832
|
+
|
|
833
|
+
## [0.88.1] - 2026-09-27
|
|
834
|
+
|
|
835
|
+
- **Fixed: the first-run wizard's "Name your site" step failed on every new site.** Continue asked for the old navigation file, which the menus migration retired, so it stopped with an error. The site's name now goes on the navbar brand in the same save.
|
|
836
|
+
- **Fixed: the theme picked in the first-run wizard was never used.** It was saved where nothing reads it any more; it now goes to the site's theme settings (Content > Theme).
|
|
837
|
+
- **The sign-in screen is built on Domma's reactive bindings.** Setup, onboarding, sign in, the two-factor step, forgot and reset password are one set of state the markup reads, rather than panels shown and hidden by hand. Nothing looks different.
|
|
838
|
+
- **Fixed: what visitors sent through a form was committed to the site's repo.** A form storing its submissions in its own collection (`find-a-tutor`, `feedback`, anything but the built-in `enquiries`) had every entry - names, emails, phone numbers - tracked by the site's content-only `.gitignore`. The file now keeps out the entries of every collection a visitor can write to: a form's collection when the form is placed on a page or block, a collection a form created, and - the moment a submission is stored - whatever collection it went into. A content collection with a form of its own (every collection made in the admin gets one) stays tracked. Cart Pro's orders and discount codes, Calendar RSVPs and the job board's applications, candidates and signups joined the private list.
|
|
839
|
+
|
|
840
|
+
## [0.88.0] - 2026-09-27
|
|
841
|
+
|
|
842
|
+
**Password resets that work on every site, and Tools you can switch off.** Reset links have not opened from their email on any site until now; this release fixes that and rebuilds the whole reset flow. Contacts, Notes, Todo and Analytics can now be switched off, plugins say what they depend on, and a site's manager can lock a switch.
|
|
843
|
+
|
|
844
|
+
- **Fixed: a password reset link opened from its email showed "404 Page not found".** A page loaded on an admin address carrying a query (`#/reset-password?token=...`) matched no screen, so the link only ever worked in a tab that already had the admin open. It opens the reset screen now.
|
|
845
|
+
- **Fixed: password reset links did not open on sites behind a proxy.** The link carried the CMS's internal port (`https://example.com:4096/...`); it now uses the site's real address.
|
|
846
|
+
- **Fixed: a reset request could make the site email a genuine reset link to another website.** The link was built from whatever host the request claimed, so someone could ask for a reset of your account "from" their own domain and collect the token if you clicked. Links now go only to the Site URL, or to an address someone has signed in at.
|
|
847
|
+
- **Fixed: how long "Forgot your password?" took to answer showed whether an account existed.** It now answers at once, the same way for every address, and sends the email afterwards.
|
|
848
|
+
- **Fixed: without a mail server, "Forgot your password?" pretended to send a link.** The login screen now says the site cannot email one and to ask an administrator; the admins are notified when someone asks (new notification: Password reset not sent).
|
|
849
|
+
- **Fixed: signing in straight after the page loaded - a password manager that fills and submits - could leave you on the sign-in screen**, signed in but going nowhere.
|
|
850
|
+
- **Fixed: the reset email and login screen always said the link expires in 1 hour**, whatever `resetTokenExpiry` was set to; the account name is now escaped in the email; a reset link for an account made inactive since no longer works; malformed reset requests get a 400.
|
|
851
|
+
- **The reset screen checks the link as it opens.** An expired, used or withdrawn link says so straight away, with Send me a new link - not after you have typed a new password twice. A good one names the account and how long the link has left, checks the new password against the site's password rules as you type, and ends on a Password changed screen with your email ready to sign in. Password managers file the new password under the right account.
|
|
852
|
+
- **Back to where you were.** Opening an admin page while signed out - or having your session run out - takes you to sign in and then straight back to that page; a session that ran out says so.
|
|
853
|
+
- **Password boxes have show / hide and warn when Caps Lock is on**, on sign in, setup and reset. The email you typed at sign in comes with you to Forgot your password; the card shows the site's name.
|
|
854
|
+
- **Administrators can reset a password from Users.** Right-click a user (or ⋮, or Shift+F10): Send password reset email, Copy a reset link - for a site without email or a mailbox that is not receiving, shown once - and Withdraw the reset link. A Reset link open chip marks who has one, and the Users badge counts them. The user editor has the same under the password (Or let them choose it). As everywhere in Users: not your own account, only someone your role is senior to, and only an active account - greyed with the reason otherwise.
|
|
855
|
+
- **"Your password was changed" email** to the account holder after any change - a reset, My Profile or an administrator - saying how and when, with what to do if it was not them.
|
|
856
|
+
- **Reset emails are limited to 3 an hour per account**, whatever address they are asked for from, so nobody can fill someone's inbox (an administrator's resets are not counted).
|
|
857
|
+
- **Site URL in Settings › General** (`site.baseUrl`) - the site's own address, which reset links, canonical links and the sitemap are built on; checked as you type. A client sending it back through `PUT /api/settings` no longer gets a 400.
|
|
858
|
+
- New: `POST /api/auth/reset-password/check`, `POST`/`DELETE /api/users/:id/password-reset`, events `auth:resetRequested` and `auth:resetIssued` (the auth events are now documented in docs/plugin-development.md). A user update can no longer write reset tokens.
|
|
859
|
+
|
|
860
|
+
- **The built-in Tools can be switched off.** Contacts, Notes, Todo and Analytics are still on unless you say otherwise - they now have their own switches at the top of System › Plugins. A Tool that is off disappears from the sidebar, its screens and API stop answering, its notices and calendar entries go, and Analytics stops counting. Nothing is deleted: switch it back on and everything is where it was.
|
|
861
|
+
- **Plugins say what they depend on, and a switch tells you before it breaks anything.** A plugin that needs a Tool (Contacts Pro needs Contacts) goes off with it; one that only uses it (Calendar invites contact groups) stays on and you are told which feature it loses. Switching a plugin on brings back what it needs. You confirm first. A plugin whose requirement is off is not started, whatever else says so.
|
|
862
|
+
- **Your site's manager can lock a switch.** A Tool or plugin marked Locked can only be changed by whoever manages the site; everything else is yours to switch as before.
|
|
863
|
+
- **Mail Reader 1.2.1** says it uses Contacts (Add sender to contacts). Marketplace plugins updated alongside: Calendar 1.8.0, Surveys 1.4.0, Invoices 1.6.1 (its contact picker works again - it had called an address gone since 0.67.0), Contacts Pro 0.7.1, Shopping Cart Pro 0.1.1, Email 0.5.1.
|
|
864
|
+
|
|
865
|
+
## [0.87.0] - 2026-09-26
|
|
866
|
+
|
|
867
|
+
- **To-dos now notify their owner.** Each morning, anyone with overdue to-dos gets one notice listing them, and one for what is due today (gone at midnight) - never one per task. Completing, deleting or re-dating a to-do updates it straight away. Switch it off under Notifications › Sources (*To-dos overdue or due today*).
|
|
868
|
+
|
|
869
|
+
## [0.86.0] - 2026-09-26
|
|
870
|
+
|
|
871
|
+
- **Blog 1.8.0: who may do what on the blog is now a permission, checked on every route.** Until now any signed-in account could read every draft and commenters' email addresses, write drafts, and approve or delete comments. Posts, categories and comments each have their own permission (under Roles), given to admins; Blog Author writes and edits their own posts, Blog Editor runs everything. An editor held as an additional role now counts.
|
|
872
|
+
- **Mail Reader 1.2.0: an Email permission**, for admins by default. A mailbox here opens connections from your server, so it is no longer open to every signed-in account; give it to other roles under Roles.
|
|
873
|
+
- **Blog 1.7.0: a comment waiting for approval now shows in Notifications** (source *Comments awaiting moderation*, for Super Admins by default), naming the post and quoting the comment. It goes once the comment is approved, rejected or deleted.
|
|
874
|
+
- **Fixed: an account confined to a few admin screens could still use every plugin's API.** A role can confine its holders to some screens (Job Board's companies, agents and job seekers - people who may have signed themselves up on the public site). The admin kept them on those screens, but the server did not: a plugin route that only checked for a signed-in user answered them too, so a job seeker could, for one, publish or delete blog posts through the API. Now a confined account can call a plugin's API only if one of that plugin's screens is open to it. Staff and every unconfined role are unaffected, and so is the public side. Their sidebar also stops asking other Tools for their badge counts, which had shown a confined account figures such as outstanding invoices and their clients.
|
|
875
|
+
- **Notifications with no audience now go to admins and up, not every role.** A manager push, or anything raised before notifications had audiences, was shown to every role that can read notifications. From this release, roles a plugin opens the bell to (Job Board's companies, agents and job seekers, who may have signed themselves up on the public site) see only what is meant for them.
|
|
876
|
+
- **Sample notifications are only for whoever added them.** *Add samples* in the Notifications settings no longer shows the four samples to everyone.
|
|
877
|
+
|
|
878
|
+
## [0.85.0] - 2026-09-26
|
|
879
|
+
|
|
880
|
+
- **Built against domma-js 0.44.6** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.44.6).
|
|
881
|
+
- **Added Corporate theme suite:** `corporate-light` and `corporate-dark` are now available in the admin theme picker, theme settings, and setup wizard.
|
|
882
|
+
- **Shopping Cart, a new built-in plugin - switched off until you turn it on.** Products with options, stock and pictures; a shop at `/shop` (the address is a setting) with categories, search and sort; a basket that follows shoppers round the site; one-page checkout paid through Stripe, PayPal or offline; orders to fulfil, track and refund; `[shop-products]`, `[shop-product]` and `[buy-button]` for any page; sample products to try it with. Turn it on under System › Plugins. Shopping Cart Pro (discount codes, delivery zones, tax rules, order emails, insights) is in the marketplace.
|
|
883
|
+
- **Added Admin Slate finish:** `admin-slate-steel`, `admin-slate-indigo`, and `admin-slate-teal` complete the 3×3 Admin theme matrix with a balanced mid-tone chrome for long administrative sessions.
|
|
884
|
+
|
|
885
|
+
## [0.84.1] - 2026-09-26
|
|
886
|
+
|
|
887
|
+
- **Fixed: Security's health check warned about HTTPS on every site** when it ran without a public address to check (a site's own report). Headers and HTTPS are now judged only through the site's address.
|
|
888
|
+
- **Fixed: Security could block the server's own address.** Requests the server makes to itself through a site's public name (health checks, certificate checks) count as local now, and are never blocked.
|
|
889
|
+
|
|
890
|
+
## [0.84.0] - 2026-09-26
|
|
891
|
+
|
|
892
|
+
- **Fixed: Security said every site was out of date.** Its health check read the site's own version (from the site's `package.json`) as the CMS's; it now reads the CMS version the updater records.
|
|
893
|
+
- **Setting up two-factor at sign-in.** A sign-in check can now show a QR code and a key to type into an authenticator app on the *One more step* screen - so a role that requires two-factor sign-in (Security Pro) can have it set up right there. Only an image the server made itself is shown.
|
|
894
|
+
|
|
895
|
+
## [0.83.0] - 2026-09-26
|
|
896
|
+
|
|
897
|
+
- **Security, a new built-in Tool - switched on.** Under Tools › Security:
|
|
898
|
+
- **A health check** of what could let someone in, each with why it matters and how to fix it: unused admin accounts, the CMS version, the HTTPS certificate, security headers, private files being served, visitor addresses behind a proxy, email for resets, plugin licences. A score, and a sidebar count of what needs attention.
|
|
899
|
+
- **Sign-in lockout.** Repeated wrong passwords lock the account and the address for a while - longer each time - and while locked, even the right password is refused.
|
|
900
|
+
- **Password rules** for every new password: at least 10 characters, not one of the 10,000 most common, not containing your name or email.
|
|
901
|
+
- **A log of every sign-in**, with CSV export, and notifications when an account is locked or someone signs in after many wrong passwords.
|
|
902
|
+
- **Scanners turned away.** The requests they send every site - WordPress logins, `.env` and `.git` files, database dumps - get an empty *not found*, and an address that keeps asking is blocked for a while. Addresses can be blocked by hand.
|
|
903
|
+
- **Locked out yourself?** `node plugins/security/bin/unlock.js --all` in the site's folder.
|
|
904
|
+
An update switches Security on the first time it arrives; a site that switches it off keeps it off.
|
|
905
|
+
- **Fixed: behind a proxy that was not trusted, nobody was rate limited - the sign-in included.** Loopback was exempt from rate limiting, and a CMS behind Apache with no `TRUST_PROXY` saw every visitor as loopback. Only a genuinely local call (a loopback connection with no `X-Forwarded-For`) is exempt now.
|
|
906
|
+
- **`TRUST_PROXY=loopback`.** `TRUST_PROXY` also takes Fastify's own values: `loopback` (recommended behind Apache or the manager - the visitor is the address the local proxy saw, which the visitor cannot forge), a list of addresses or ranges, or a hop count. `1` keeps its old meaning (trust every hop). New sites made by the manager get `loopback`.
|
|
907
|
+
- **Signing out now lasts.** Each sign-in is a session, kept on disk (`content/sessions/`, never committed). Signing out, signing out everywhere, a new password or a password reset end sessions for good - until now a signed-out session came back after a restart. A new password ends your other sessions; an admin changing someone's password, or switching the account off, ends theirs. `GET /api/auth/sessions`, `DELETE /api/auth/sessions/:sid` and `POST /api/auth/sessions/revoke-others` for your own.
|
|
908
|
+
- **Sign-in and password checks for plugins.** `registerLoginCheck` (before the password - a lockout - or after it - two-factor, which can ask one more question on a new *One more step* sign-in screen) and `registerPasswordCheck` (wherever a password is set). Events: `auth:loginFailed` (with the reason), `auth:loginSucceeded`, `auth:loggedOut`, `auth:passwordChanged`, `auth:rateLimited`.
|
|
909
|
+
- **Audit events.** Every successful change a signed-in person makes through the API raises an `audit` event - who, what, when, from where, and the names of the fields sent (never their values). Nothing is stored by the CMS; Security Pro keeps the trail.
|
|
910
|
+
|
|
911
|
+
## [0.82.0] - 2026-09-26
|
|
912
|
+
|
|
913
|
+
- **A picture on My Profile.** Click the circle (or drop an image on it) to add your own avatar - any common photo up to 8 MB, iPhone HEIC included. It is cropped square around the subject, made small and saved at once, and shows on your profile and in the top bar; *Remove* brings your initials back. The uploaded file itself is never kept or served: it is re-encoded to a 256 px WebP. Deleting a user removes their picture.
|
|
914
|
+
- **Fixed: a doubled full stop in form errors.** A refused submission ended "…no more than 5.." - the route added a full stop after messages that already had one.
|
|
915
|
+
|
|
916
|
+
## [0.81.1] - 2026-09-26
|
|
917
|
+
|
|
918
|
+
- **Fixed: number limits were ignored.** A number field's min and max (and a text field's min and max length) were stored by the form builder but dropped by the `[form]` embed, which is how most pages show a form, and never checked by the server. A "lessons per week, 1-5" field took any number. The embed now applies them, and the server refuses a submission outside them, naming the limit ("Lessons must be no more than 5."). A number field also refuses something that is not a number.
|
|
919
|
+
- **Fixed: field tooltips did not show on `[form]` embeds.** A tooltip set in the builder appeared only on the older `data-form` embed.
|
|
920
|
+
|
|
921
|
+
## [0.81.0] - 2026-09-26
|
|
922
|
+
|
|
923
|
+
- **Forms can end early, and tell the admins.** Two new trigger actions:
|
|
924
|
+
- **End the form here.** When an answer means there is nothing more to ask (under age, a region you do not serve), everything after the field is masked, your message takes its place, and the submit button - or a wizard's *Next* - becomes *Finish*. Finishing saves the answers given so far and shows your closing message. The masked fields are neither required nor stored, even where the form marks them required, and the entry is tagged `meta.outcome = {type: 'ended', trigger, field}`. Change the answer and the rest of the form comes back.
|
|
925
|
+
- **Save the answers given so far** (on by default): untick it to keep nothing. No entry is stored and the form's email, webhook and action do not run.
|
|
926
|
+
- **Let the visitor finish** (on by default): untick it to stop the form dead. The rest is still masked, but the submit is disabled and the server refuses the post, so nothing is saved and nothing is raised.
|
|
927
|
+
- **Notify the admins.** Raised by the server when the form is submitted with the condition holding, so it cannot be skipped. It names the form, the question and the answer, and links to the submissions. It goes to every admin by default - Notifications' settings, source **Form triggers**.
|
|
928
|
+
- **Actions can notify too.** A new **Notify the admins** step (title, body, severity, link, all with template variables), source **Actions**.
|
|
929
|
+
- **The trigger editor is readable.** Every control has a label (a filled box used to have only a vanished placeholder). When / Then / Otherwise are distinct sections. Each action is a collapsible card whose head names it, summarises it in one line and says when it happens. Options that do not apply are hidden, and a narrow panel stacks instead of overflowing.
|
|
930
|
+
- **Fixed: a trigger could not test its own field.** The editor left the field a trigger belongs to out of its condition list, so a saved "when this answer is No" came back testing the next field, and the next save rewrote it. Worth checking any trigger saved since triggers arrived in 0.57.0. The field is now listed first, marked *(this field)*.
|
|
931
|
+
- **Fixed: a hidden required field refused the submission.** A field hidden by a trigger was excused by the form but still demanded by its submissions collection, which copies the form's required flags. The form's own decision now holds in both.
|
|
932
|
+
|
|
933
|
+
## [0.80.0] - 2026-09-26
|
|
934
|
+
|
|
935
|
+
- **The site tells its Super Admins what needs them.** Notifications used to come only from the manager. Now core and plugins raise their own, and they go to Super Admins unless the source is set otherwise:
|
|
936
|
+
- **System sources:**
|
|
937
|
+
- Critical: a plugin failed to start, a licensed plugin stopped (unlicensed or expired), a MongoDB connection failed (collections fall back to files), the site restarted after a crash.
|
|
938
|
+
- Warning: a licence in its grace period or ending within 14 days, an API token expiring, repeated failed sign-ins or the sign-in limit hit, email not delivered or not set up, a form's email, webhook or action failing, someone given Admin or Super Admin, low disk space, a start-up repair left to retry.
|
|
939
|
+
- Off by default: every form submission, users added or removed.
|
|
940
|
+
- **Where to control them:** Notifications' settings cog lists every source under **What this site tells you**. Super Admins see this section only; each source can be switched off or sent to all admins or to everyone, and timed checks have **Run now**.
|
|
941
|
+
- **Repeats and resolving:** a repeat updates the one notification rather than adding another, and is unread again only if it got worse. A notification ends by itself when its cause goes away (the plugin loads again, the licence is renewed).
|
|
942
|
+
- **Links:** a notification can link to an admin screen (**Go there**) as well as a web address.
|
|
943
|
+
- **For plugins:** `options.hooks` now carries `notify({title, body, severity, link, source, dedupeKey, audience, expiresAt})`, `resolveNotification(dedupeKey)` and `registerNotificationSource({id, label, description, severity, defaultOn, every, atBoot, check})`, stamped with the plugin's name. `plugin.public.js` (survey responses, RSVPs) and `onEnable`/`onDisable` get them too.
|
|
944
|
+
- Scheduled checks run `boot`, every `15m`, `hourly` or `daily` (08:00 site time).
|
|
945
|
+
- A source may send at most 30 new notifications an hour, and a failure while raising one never breaks the caller.
|
|
946
|
+
- A notification's `audience` (`{roles}` or `{userIds}`) decides who sees it. Manager pushes and older entries have none, so everyone who can read notifications still sees them.
|
|
947
|
+
- **More sidebar badges.**
|
|
948
|
+
- **Effects:** how many effects the editor offers.
|
|
949
|
+
- **My Profile:** your details not yet filled in.
|
|
950
|
+
- **Layouts and API Tokens:** a count while all is well, and the problem count, as before, when pages use a missing layout or tokens are expiring. A count route may now answer `inventory: true` to keep its number out of a closed folder's total.
|
|
951
|
+
- **Desktop notifications.** Notifications has a settings cog. Its **Desktop notifications** switch asks the browser for permission and then pops up each new notification on the desktop, even when the admin is in a background tab. Clicking one brings you back to Notifications. You choose which ones pop up: all, warnings and critical, or critical only. Critical ones stay until you close them, and **Send a test** checks the setup. The setting is per browser, like the permission it needs. Turning it on never brings up what was already there, and a notification pops up once in that browser, however many admin tabs are open. The admin's once-a-minute bell check does the delivery, so no new polling is added.
|
|
952
|
+
- **Sample notifications.** The same panel's **Add samples** adds four made-up notifications, one of each severity, shaped like what the site's own sources will send. Use them to see the screen, the bell, the sidebar badge and desktop notifications working on a quiet site. They stay unread and go by themselves after a week; adding them again replaces the earlier ones. This needs the Notifications delete permission (`POST /api/system/notifications/samples`).
|
|
953
|
+
- **The sign-in pop-ups can be turned off.** In the same panel, **Pop-ups when you sign in** controls the sticky toasts for unread warnings and critical notifications (on by default, as before).
|
|
954
|
+
|
|
955
|
+
## [0.79.1] - 2026-09-26
|
|
956
|
+
|
|
957
|
+
- **Right-click menus in the admin now highlight the item under the pointer.** The pages, layouts, forms, roles, settings, analytics and API-token views, and the shortcode editor menu, showed no hover at all in dark themes and a barely visible one in light themes. Built against domma-js 0.44.5 - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.44.5).
|
|
958
|
+
|
|
959
|
+
## [0.79.0] - 2026-09-25
|
|
960
|
+
|
|
961
|
+
- **Todo, Contacts and Notes meet the Calendar.** Two small registries let a plugin work with the built-in Tools without core ever importing it:
|
|
962
|
+
- **Calendar sources** (`registerCalendarSource` / `getCalendarSources`, handed to plugins in `options.hooks`): dated items a Tool shows on the Calendar plugin, asked for per range and per viewer - nothing is copied, so nothing falls out of step. Todo registers **To-dos**: your open to-dos, on their due day.
|
|
963
|
+
- **Related items** (`registerRelatedItems`, `GET /api/related`): what a plugin shows beside a contact or a note. The Calendar adds **Events** to the contact and note editors, and **Schedule with Sam...** / **Add to calendar...** to their menus. Core checks the record is the viewer's own before any plugin is asked; a failing or slow one is named and left out. Without such a plugin nothing appears.
|
|
964
|
+
- **Contacts and Notes say when something is deleted** (`contact:deleted`, `note:deleted` on the hooks bus), so a plugin that points at one - the Calendar's event links - can let go.
|
|
965
|
+
|
|
966
|
+
## [0.78.1] - 2026-09-25
|
|
967
|
+
|
|
968
|
+
- **Built against domma-js 0.44.4** - no change in behaviour; see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.44.4).
|
|
969
|
+
|
|
970
|
+
## [0.78.0] - 2026-09-25
|
|
971
|
+
|
|
972
|
+
- **The server stores number fields as numbers**, whoever writes them - the admin, the external API (`/api/v1`), form submissions, import and Actions. A `number` field sent as text ("42", as HTML forms and many integrations send it) is saved as 42, so it sorts, filters and totals properly. **API clients:** such a field now comes back as a number where it used to come back exactly as sent. Text that is not a number is still stored as sent rather than refused, so no request that worked before now fails. Plugins writing through a storage adapter directly are not covered.
|
|
973
|
+
|
|
974
|
+
## [0.77.2] - 2026-09-25
|
|
975
|
+
|
|
976
|
+
- **Number fields stored as text are converted, once.** On its first start, a site turns every number field that the entry form saved as text before 0.77.1 ("12") into a number (12), in every collection - MongoDB-backed ones included - so they sort, filter and total properly again. Only plain decimal text in `number` fields changes; empty values and anything that is not a number are left as they are, and entries keep their "last changed" dates. It runs once (recorded in `content/.migrations.json`); a MongoDB collection whose connection is down is retried on the next start.
|
|
977
|
+
|
|
978
|
+
## [0.77.1] - 2026-09-25
|
|
979
|
+
|
|
980
|
+
- **Fixed: a number field saved from a collection's entry form was stored as text** ("42", not 42), so it sorted, filtered and totalled as text in the API, `[collection]` displays and views. The form now saves numbers as numbers (0, negatives and decimals included); an empty box is still saved empty. Entries saved wrongly before are corrected the next time they are saved from the form.
|
|
981
|
+
|
|
982
|
+
## [0.77.0] - 2026-09-25
|
|
983
|
+
|
|
984
|
+
The editors get the Tool Upgrade - every page, menu, form, collection, view, action, API, block, component,
|
|
985
|
+
role, user, project and plugin-code editor, the entries and submissions screens, Menu locations and the core
|
|
986
|
+
Dashboard. Each is in the Tool banner ("Pages › About"), with one Save (banner, an unsaved bar, Ctrl+S), a
|
|
987
|
+
leave guard that also covers moving from one item to another, a draft per item offered back on your next
|
|
988
|
+
visit (never a password, token or secret), context menus on its inner lists, help on the non-obvious
|
|
989
|
+
controls and theme tokens only. The three largest editors (form, page, menu) were upgraded in place rather
|
|
990
|
+
than rewritten.
|
|
991
|
+
|
|
992
|
+
- **The Dashboard is a Tool** (sites without the site manager). Page views today, visitors now, pages and drafts, form submissions this week and site health at a glance; "Needs attention" gathers what the rest of the admin is flagging - the red and amber sidebar badges and any health check that is not OK - each a click from its screen. Recent activity, traffic, top pages, spikes and visits are rows you can click, right-click (or ⋮, or Shift+F10) to open on the site, copy or edit the page. Refresh and New page are in the Tool banner; the chart follows the theme. Fixed: the "last 30 days" chart only ever showed the last week, and every new entry in any collection - roles and API tokens included - was listed and counted as a form submission.
|
|
993
|
+
- **The page editor is a Tool.** One Save (banner, the bar, Ctrl+S) that checks the address and DConfig first and shows the tab a problem is on; an Unsaved marker that clears again if you undo your changes; Discard. Leaving with unsaved changes asks - going to another page included - and keeps a draft of that page, offered back on your next visit (it says so if the saved page changed meanwhile). The banner names the page, with Back, Editor reference and View on the site. Version history rows have a right-click / ⋮ / Shift+F10 menu (compare, restore, delete). Help on every non-obvious field.
|
|
994
|
+
- **The menu editor and Menu locations are Tools.** Banner Save and Ctrl+S, a leave guard with a per-menu draft (moving to another menu included), context menus on every menu item (edit, add below, move, indent, outdent, duplicate, hide, remove - locked admin-sidebar rows and Tools keep their rules) and on every location slot (edit the menu, clear, undo, copy the shortcode), a list of what would stop a save before it is sent, warnings for slots nested too deep or pointing at a missing menu, and help on the non-obvious controls.
|
|
995
|
+
- **The context menu editor is a Tool.** One Save (banner, the unsaved bar, Ctrl+S); leaving with unsaved changes - for another screen or another menu - asks and keeps a draft for that menu; the banner names the menu. Items are rows: click to edit one in a slideover (checked as you type, asks before discarding), right-click (or ⋮, or Shift+F10) to move, duplicate, add beside it or remove it; Alt+↑/↓ move. Checks mirror the server's, so problems show - on the tab that has them - before Save. Fixed: clearing a menu's Pages / Projects or setting its look back to theme defaults never stuck (an update merged, and empty scope and theme were not sent).
|
|
996
|
+
- **The collection editor is a Tool.** One form and one Save (banner, the unsaved bar, Ctrl+S) for settings, fields, API access, export and storage, with every problem marked on its tab; leaving with unsaved changes asks - moving to another collection too - and keeps a draft per collection. Fields are rows: click to edit in a slideover that checks the name as you type, drag or right-click (or ⋮, Shift+F10, Alt+↑/↓) to move, duplicate or remove them; removing or renaming a field that has entries says what happens to their values. Help on every setting. Fixed: saving a storage change and choosing to move the entries skipped the rest of the edit, and declining the move switched silently - it now asks.
|
|
997
|
+
- **A collection's entries are a Tool.** A table whose rows you can reach by keyboard - click to edit, right-click (or ⋮, or Shift+F10) to see all of an entry, duplicate it, copy its id, run one of the collection's actions, filter by the value you clicked, or delete it; tick rows to delete several; sort by any column. The entry form is a slideover that asks before throwing changes away and keeps a draft (never a password) when you leave. Add, Import (now from a file too), Export, Edit fields and Form are in the banner, which names the collection. Big collections load 500 at a time with "Load more" (it stopped at 500 before). Fixed: an entry's 0 was saved back as empty, and > / < filters treated every date in a year as equal. Sites without MongoDB no longer get a 503 for actions on this screen.
|
|
998
|
+
- **The form editor is a Tool.** "Forms › <title>" in the banner with Save (Ctrl+S), All forms and Submissions; one unsaved-changes bar with Discard. Leaving with unsaved changes asks - moving to another form's editor included - and keeps a draft of that form, offered back on your next visit to it (never the webhook URL). Every field row has a menu - ⋮, right-click on its header, or Shift+F10 - to edit it, open its logic and triggers, duplicate it (a unique name), move it up or down, copy its name or delete it; Enter opens it. Help on the non-obvious settings; theme tokens only.
|
|
999
|
+
- **Form submissions is a Tool.** One row per submission - named after the sender, the other answers under it - with Inbox / New this week / Spam, a search over every answer and a date range. Click one for everything that was sent; right-click it (or ⋮, or Shift+F10) to copy it, copy or email the sender, mark it as spam (kept out of the inbox, not deleted) or delete it. All forms, Export (CSV or JSON) and Clear all are in the Tool banner, which reads "Forms › <form> submissions". Fixed: export, clear all and delete used the form's own collection, not the one its collection action writes to - so for such a form export was empty and delete failed. New: POST /api/forms/:slug/submissions/:id/spam. Where several forms write to one collection, each form's submissions screen now lists, counts, exports, clears and deletes only its own entries (those it stamped `form:<slug>`), and every submissions route answers 404 for a form outside the user's projects.
|
|
1000
|
+
- **The view editor is a Tool.** One Save (banner, the unsaved bar, Ctrl+S), with what is wrong dotted on its tab; leaving - for another screen or another view - asks and keeps a draft for that view. Stages are cards with a summary line; right-click one (or ⋮, or Shift+F10) to move, duplicate or delete it, or to see the results up to it. A Results tab runs the pipeline being edited, saved or not (`POST /api/views/_preview`), and follows your edits. All ten stages the server allows are offered ($limit, $skip and $count were missing), roles come from the site's roles (the list was fixed at admin/manager/editor/subscriber), and the slug is read-only once the view exists (the server always ignored a change). The preview page runs on opening, pages through results, and copies a row as JSON.
|
|
1001
|
+
- **The action editor is a Tool.** One Save (banner, the unsaved bar, Ctrl+S); leaving with unsaved changes asks - another action's editor included - and keeps a draft for that action's next visit. Steps are numbered cards with a summary; right-click one (or ⋮, or Shift+F10) to move, duplicate or remove it. New: the state transition (field, from, to) - the service always had it, the editor did not; allowed roles come from System › Roles (they were a hard-coded four, one of which, "manager", did not exist) and a role that is gone is kept and flagged; unticking Bundled now saves. Without MongoDB it explains that actions need it instead of failing. Help on every control.
|
|
1002
|
+
- **The API Builder editor is a Tool.** One Save (banner, the unsaved bar, Ctrl+S), checked as you type against the server's own rules; leaving with unsaved changes asks - moving to another endpoint included - and keeps a draft for that endpoint. Filters, the response-field list and the test history have right-click / ⋮ / Shift+F10 menus (move, duplicate, use a path param, tick all, run again, copy as curl). Try it gains Save and send; the token field is a password field and is never kept. Help on every control. The missing "history" icon is fixed.
|
|
1003
|
+
- **The block editor is a Tool.** "Blocks › <name>" in the banner, with Save (and Ctrl+S), All blocks and Copy shortcode; one Unsaved bar with Discard; leaving with unsaved changes asks - switching to another block included - and keeps a draft for that block that the next visit offers back. The HTML and CSS panes are syntax-highlighted editors; right-click a sample value to insert its placeholder, copy it or reset it; help on every field. A new block can no longer take an existing block's name - saving it used to overwrite that block.
|
|
1004
|
+
- **The component editor is a Tool.** One Save (banner, the Unsaved bar, Ctrl+S) over the name, project, Bundled and the source; leaving with unsaved changes asks - opening another component included - and keeps a draft per component, offered back on your next visit. Preview props are rows with a right-click / ⋮ / Shift+F10 menu: reset, copy as an attribute or a whole tag, jump to it in <props>. Help on every part. "View source" on a plugin's component opens it read-only (it 404'd and bounced back to the list), with Copy to a new component.
|
|
1005
|
+
- **The role editor is a Tool.** One Save (banner, the unsaved bar, Ctrl+S); leaving with unsaved changes asks - moving to another role's editor too - and keeps a draft for that role. The permission matrix gains search, Granted / Not granted, one-click actions and Full, and right-click (or ⋮, or Shift+F10) on an area for view only / none, or on a group heading to grant or clear the whole group. "What it can do" reads the ticks back in words. Editing a role you hold warns before you make yourself less senior or remove what you need to come back and fix it; renaming a role people hold says how many.
|
|
1006
|
+
- **The user editor is a Tool.** One form and one Save (banner, Ctrl+S) for account, roles, projects and profile fields - it was two forms, and a new user's profile could not be saved at all. What you may do mirrors the server: roles you cannot give are greyed with the reason (judged, like the server, on everyone's most senior role, primary or additional), a user you may not edit opens read-only, and your own account cannot be switched off here. A taken email shows on the field. Leaving asks (moving to another user too) and keeps a draft per user - never the password. Fixed: a new user's project access, project ownership, active switch and profile were sent in the create request, which ignored them, and were lost; `POST /api/users` now takes them all, so the account is made whole in one request.
|
|
1007
|
+
- **A project's overview and settings are Tools.** The overview's tiles open their list (or start the first) on click, and have a right-click / ⋮ / Shift+F10 menu (open, new, new in its editor); a disabled project says so, with Enable. Settings is one form checked as you type with the same rules as Manage projects, one Save (banner, Ctrl+S), a leave guard and a per-project draft; the danger zone explains what untagging cannot move (pages under the root URL). The banner reads "Projects › <name>".
|
|
1008
|
+
- **Signing out asks first when an editor has unsaved changes** - and now while you are still signed in: Cancel leaves you on the editor with a working session (sign-out used to happen before the question, so a Save after Cancel failed), OK keeps the draft and signs out.
|
|
1009
|
+
- **The view editor's Results preview checks joined collections too**: a `$lookup` into a collection outside your projects is refused, like the source already was.
|
|
1010
|
+
- **The plugin code editor is a Tool** (Marketplace › <plugin>). Files grouped by folder with a filter and a ● on anything unsaved; right-click a file (or ⋮, or Shift+F10) to save, revert, copy its path, add a file beside it, duplicate, rename or delete it (plugin.json, plugin.js and config.js cannot be). Ctrl+S saves the open file, Ctrl+Shift+S all of them; a Server file / Admin file chip says whether saving needs a restart, and a bar offers the restart once one is pending. Leaving with unsaved files asks, and keeps them as a draft the next visit offers back - naming any file changed on the server meanwhile. New file, Save all, Restart server and Back are in the Tool banner; new, rename and duplicate paths are checked as you type.
|
|
1011
|
+
|
|
1012
|
+
## [0.76.0] - 2026-09-25
|
|
1013
|
+
|
|
1014
|
+
Every screen in System gets the Tool Upgrade - Notifications, Roles, Users, Site Settings, Effects, Layouts,
|
|
1015
|
+
the Marketplace, My Profile, API Tokens and Search - and so does the mail client shared by Email and Mail
|
|
1016
|
+
Reader. Settings screens get one Save (banner, Ctrl+S), a leave guard and a draft; lists get rows with
|
|
1017
|
+
right-click / ⋮ / Shift+F10 menus; badges say what needs doing.
|
|
1018
|
+
|
|
1019
|
+
### System
|
|
1020
|
+
|
|
1021
|
+
- **Notifications is a Tool.** One row per notification - severity, when, from where, a link - with New / severity filters and search; click one to read it all, right-click (or ⋮, or Shift+F10) to open its link, copy it, mark it unread, dismiss it or delete it for everyone. Dismiss all and Refresh are in the Tool banner; the sidebar badge keeps the unread count and turns red while anything unread is critical (GET /api/system/notifications/_count). Fixed: notifications pushed by the manager were stored without an id or data wrapper, and marking one read wrote it back the same way, so the list skipped them and none ever showed; they are now stored whole. Opening the screen marks them read in one request (POST /read-all) - the per-notification requests it made at once lost updates on file storage.
|
|
1022
|
+
- **Roles is a Tool.** One row per role, most senior first - its badge, name, permissions, level, how many users hold it, plugin and admin scope - with All / Built in / Yours / Plugins and search. Click to set its permissions; right-click (or ⋮, or Shift+F10) to duplicate it, copy its name or delete it (built-in and plugin roles cannot be - the old list offered Delete on admin and user). New role is a slideover that checks the name and level as you type and keeps a draft; one made from a project's list is filed under it. A sidebar badge counts roles and turns red while a user holds a role that no longer exists (GET /api/roles/_count, /_holders).
|
|
1023
|
+
- **Users is a Tool.** One row per user - email, when they last signed in, role and additional roles, project access, inactive - with All / Active / Inactive, a role filter, search and sort (name or last signed in). Click to edit; right-click (or ⋮, or Shift+F10) to copy their email, make the account active or inactive, or delete it - actions your role cannot take are greyed with the reason. New user is in the Tool banner; the sidebar badge keeps its count and its popover shows who is inactive, who never signed in and the latest sign-ins (GET /api/users/_count).
|
|
1024
|
+
- **Site Settings is a Tool.** One Save for the whole screen - general, SEO, footer, email, back to top,
|
|
1025
|
+
cookie consent, breadcrumbs and custom CSS (the last two had their own buttons) - in the banner, the
|
|
1026
|
+
unsaved bar and Ctrl+S; a dot on each tab with unsaved changes; leaving with unsaved changes asks, and
|
|
1027
|
+
keeps a draft the next visit offers back (never the SMTP password). "Needs attention" says what is
|
|
1028
|
+
quietly wrong - no SMTP host above all, where mail goes to a test inbox and still reports success - and a
|
|
1029
|
+
sidebar badge counts the same (`GET /api/settings/_count`). The cache tab lists cached pages as rows
|
|
1030
|
+
(open the page, copy its key) and can turn the cache on or off. Help on every non-obvious setting.
|
|
1031
|
+
- **Effects is a Tool.** Live now shows what the site uses; the reveal defaults are one form with a single Save (banner, Ctrl+S) - leaving with unsaved changes asks and keeps a draft. A new Effects tab lists every effect the page editor inserts, from the same catalogue: filter by category, search by name or setting, click for its settings and an example, right-click to copy its shortcode. The long-form reference is kept as the Guide tab. PUT /api/effects/settings now stores only the four settings it knows, checked (it wrote the request body to config/effects.json verbatim, and GET serves that to every public page).
|
|
1032
|
+
- **Layouts is a Tool.** One row per layout with a little drawing of its shape (navbar, width, sidebar, footer, background) and how many pages use it; Built in / Yours and search. Click to edit in a slideover with a live drawing, checked as you type; right-click (or ⋮, or Shift+F10) to see the pages that use it, duplicate it, copy its `layout:` line or delete it (the confirm says how many pages fall back to Default). Pages that name a layout which no longer exists - they quietly show as Default - are called out on the screen and in a sidebar badge (`GET /api/layouts/_count`, `GET /api/layouts/_usage`). The spacer options have Ctrl+S, a leave guard and a draft.
|
|
1033
|
+
- **The Marketplace is a Tool.** Installed plugins are rows - version, author, release date, On / Off / Built in / Not running / Replaced, licence state and "Update to x" - with On / Off / Needs attention and search; what needs doing comes first. Click a plugin for its details and settings; right-click it (or ⋮, or Shift+F10) to switch it on or off (it asks - the server restarts), configure, update, view its source, export or uninstall it. Configure works at last: the form comes from a plugin's settingsSchema or, failing that, its config.js settings (the old Settings button needed a schema no plugin had, so it never appeared), and saves only what changed. Browse is part of the same screen (Available / Installed / Updates, search). New plugin checks as you type and keeps a draft. A sidebar badge counts plugins, turns red when a licensed plugin that is on is not running, and lists updates (GET /api/plugins/_count). GET /api/plugins carries settingsSchema and effectiveSettings.
|
|
1034
|
+
- **My Profile is a Tool.** One form and one Save (banner, the unsaved-changes bar, Ctrl+S) for your name, email, password and profile fields - it used to be two forms, and saving one threw away changes to the other. Save sends only what changed. Leaving with unsaved changes asks and keeps them for your next visit - never a new password. A card shows your role, project access and account dates; help on each part. A required profile field no longer stops you changing your password.
|
|
1035
|
+
- **API Tokens is a Tool.** One row per token - its ending, project, scopes, expiry, who made it and when it was last used - with tokens that need attention first, All / Active / Expiring soon / Expired / Off, a project filter and search. Click a token to edit its name, scopes or expiry, or switch it off (it could not be edited before); right-click (or ⋮, or Shift+F10) to switch it on or off, copy its ending, show only its project or revoke it. New token is a slideover that checks as you type and keeps a draft - never the token, which is shown once and asks before being closed uncopied. A sidebar badge counts tokens that have expired or expire within two weeks, red once one has expired (`GET /api/api-tokens/_count`).
|
|
1036
|
+
- **Search is a Tool.** Live now shows what visitors get (on/off, trigger, shortcut, results, how many pages are searchable). One Save for the screen (banner, Ctrl+S); leaving with unsaved changes asks and keeps a draft; a number outside its range says what it will be saved as. The floating button is set on a preview at its real size and a map of the screen. New: Try it - the site's own search run from the admin with the settings being edited, results right-clickable to open or edit the page (GET /api/search/preview). Help on every control; a sidebar badge counts the searchable pages (GET /api/search/_count).
|
|
1037
|
+
|
|
1038
|
+
### Mail
|
|
1039
|
+
|
|
1040
|
+
- **Mail (Email and Mail Reader).** The screen now tears itself down when you leave it - its keyboard shortcuts
|
|
1041
|
+
stayed live on every other admin screen, and piled up per visit. Compose asks before losing text by any way
|
|
1042
|
+
out, and leaving the screen saves a changed message to Drafts. Context menus on messages and folders by
|
|
1043
|
+
right-click, ⋮ or Shift+F10 (Reply / Reply all / Forward added; the free reader gets one too), and their
|
|
1044
|
+
dividers draw as rules instead of blank rows. Compose, Refresh and Settings are in the Tool banner; every
|
|
1045
|
+
settings section explains itself; dialogs closed by their X no longer hang. Mail Reader gains an unread
|
|
1046
|
+
badge (INBOX, by STATUS, never marking anything read).
|
|
1047
|
+
|
|
1048
|
+
### Fixed (security and integrity)
|
|
1049
|
+
|
|
1050
|
+
- **An account could take another account's email.** Creating a user refused a taken address, but updating
|
|
1051
|
+
one (Users, and My Profile's own `PUT /api/auth/me`) did not check - two accounts could share a sign-in
|
|
1052
|
+
email. Both now answer 409.
|
|
1053
|
+
- **The built-in roles could be deleted.** `DELETE /api/collections/roles/entries/:id` refused only the root
|
|
1054
|
+
role, so `admin` and `user` could go (the old Roles screen offered it); and clearing the roles collection
|
|
1055
|
+
was allowed outright. Both are refused now.
|
|
1056
|
+
- **Plugin secrets reached the browser.** `GET /api/plugins` sent every plugin setting as stored - a DVLA key,
|
|
1057
|
+
an SMTP password, a connection URI. Secret-looking values (and `settingsSchema` fields marked `secret`) are
|
|
1058
|
+
now sent masked, and saving an untouched one keeps what is stored. `settingsSchema` is documented in
|
|
1059
|
+
docs/plugin-development.md.
|
|
1060
|
+
- **Screen-confined roles got two 403s on every page** (a job-board candidate, say): the sidebar read Site
|
|
1061
|
+
Settings for its title and the top bar polled notifications. Neither is asked without the permission now.
|
|
1062
|
+
|
|
1063
|
+
## [0.75.0] - 2026-09-25
|
|
1064
|
+
|
|
1065
|
+
Every screen in Content and Data gets the Tool Upgrade: Pages, Media, Menus, Context Menus and Theme;
|
|
1066
|
+
Collections, Forms, Views, Actions, API Builder, Blocks and Components. Each is on domma-reactive, in the
|
|
1067
|
+
Tool banner with its actions, with right-click / ⋮ / Shift+F10 menus, help popovers, a sidebar badge with a
|
|
1068
|
+
popover, and - where a screen has a form - a guard and a draft for unsaved work.
|
|
1069
|
+
|
|
1070
|
+
### Content
|
|
1071
|
+
|
|
1072
|
+
- **Pages is a Tool.** One row per page - address, layout, revisions, when it changed, tags - with Published/Draft, visibility, project, Bundled and plugin chips; All / Published / Drafts, search, sort (navigation order, recently changed, title, URL) and the Tree view, remembered per viewer. Click a page to edit it; right-click (or ⋮, or Shift+F10) to view it, copy its address, trim its revisions or delete it. Trimming revisions is a slideover (it was a browser prompt), and the Pages badge names the drafts in its label and popover (GET /api/pages/_count). GET /api/pages now carries each page's plugin and bundled flags, so the plugin chip and the "managed by a plugin" delete warning show at last.
|
|
1073
|
+
- **Media is a Tool.** A grid of tiles - thumbnail, name, size, age, type - with filters by kind, search and sort. Click a file for its details (preview, dimensions, its address and a Markdown snippet to copy, rename, edit image, delete); right-click it (or ⋮, or Shift+F10) for the same actions. Upload is in the Tool banner, and files dropped anywhere on the screen upload too - one at a time, each reporting its own error, and asking before replacing a file of the same name (the server overwrites silently). A sidebar badge shows the file count and space used (`GET /api/media/_count`). Deleting a file that is already gone answers 404 instead of a 500.
|
|
1074
|
+
- **Menus is a Tool.** One row per menu with the location(s) it fills, its binding (flagged when it can never
|
|
1075
|
+
match) and whether it is bundled; All / In a location / Not in a location, and search. Click to edit;
|
|
1076
|
+
right-click (or ⋮, or Shift+F10) to duplicate, copy its `[menu]` shortcode, open Locations or delete (still
|
|
1077
|
+
refused while a location uses it, and the menu says so). New menu and Locations are in the Tool banner; a
|
|
1078
|
+
sidebar badge counts menus and lists empty locations (`GET /api/menus/_count`).
|
|
1079
|
+
- **Context Menus is a Tool.** One row per menu - its target, item count, where it applies (site-wide, its binding rules, or never) and whether it is on - with All / On / Off and a search. Click a menu to edit it; right-click it (or ⋮, or Shift+F10) to switch it on or off, duplicate it, copy its slug or delete it. The Tool banner, with New context menu; help popovers; a sidebar badge that calls out menus switched off and ones whose binding matches nothing (`GET /api/context-menus/_count`).
|
|
1080
|
+
- **Theme is a Tool.** Live now shows what the site and admin are using (day/night included).
|
|
1081
|
+
One Save for the screen (banner, Ctrl+S): leaving with unsaved changes asks, and keeps them as a draft
|
|
1082
|
+
the next visit offers back. Element overrides are a list - click to edit in a slideover (token names
|
|
1083
|
+
checked against the stylesheet; the "what adopting this theme changes" panel kept), right-click to
|
|
1084
|
+
switch off, duplicate, move or remove; the generated CSS follows your edits. Help on every
|
|
1085
|
+
non-obvious control. The sidebar badge keeps the override count and turns red when a theme in use no
|
|
1086
|
+
longer exists (`GET /api/theme/count`).
|
|
1087
|
+
|
|
1088
|
+
### Data
|
|
1089
|
+
|
|
1090
|
+
- **Collections is a Tool.** One row per collection - its fields, project and entry count, with MongoDB / Built in / Bundled / plugin chips - with All / Yours / Built in / Plugins filters and a search box. Click one for its entries; right-click it (or ⋮, or Shift+F10) to edit it, open its form, copy its slug or delete it (built-in ones cannot be). New collection is a slideover that checks the slug as you type and keeps a draft; one made from a project's list is filed under that project. Database connections has a dirty marker, Ctrl+S, and keeps unsaved edits when you leave (in memory only - a URI can carry a password). A sidebar badge counts collections and entries (GET /api/collections/_count), and GET /api/collections marks the built-in ones (`builtIn`).
|
|
1091
|
+
- **Forms is a Tool.** One row per form - questions, wizard steps, conditional logic, when it last had a
|
|
1092
|
+
submission and how many - with All / New this week / No submissions and a search box. Click a form to
|
|
1093
|
+
edit it; right-click it (or ⋮, or Shift+F10) for its submissions, its shortcode, Duplicate or Delete.
|
|
1094
|
+
New form and Test email are slideovers in the Tool banner; Duplicate asks for the copy's title (a
|
|
1095
|
+
second "(Copy)" used to fail). The sidebar badge adds submissions this week to its label and popover
|
|
1096
|
+
(`GET /api/forms/_count`). The list now counts submissions where the submissions screen reads them -
|
|
1097
|
+
a form whose collection action writes elsewhere showed the wrong number.
|
|
1098
|
+
- **Views is a Tool.** One row per view - what it reads from (and whether that collection still exists), its stages, display mode, engine (JS pipeline or MongoDB aggregation), project and who can see it - with Public / Restricted / Missing source filters and search. Click to edit; right-click (or ⋮, or Shift+F10) to preview, copy its `[view]` shortcode, duplicate or delete. A view whose collection was deleted is listed first, in red, and turns the sidebar badge red (`GET /api/views/_count`). `GET /api/views?sources=1` adds each view's `sourceInfo`.
|
|
1099
|
+
- **Actions is a Tool.** One row per action - its collection, button, state transition, steps and roles - with a collection filter and search; click to edit, right-click (or ⋮, or Shift+F10) to duplicate, open the collection's entries, copy the slug or delete. Duplicate is a slideover that keeps the whole config. Without MongoDB the screen explains that actions are a Pro feature instead of raising an error. Sidebar badge: `GET /api/actions/_count` (count 0, `available: false`, in file mode).
|
|
1100
|
+
- **Blocks is a Tool.** One row per block with its description (from the block's own comment), size, when it changed and where it is used; All / In use / Unused and search. Right-click (or ⋮, Shift+F10) to copy its shortcode, see where it is used, duplicate, export or delete it; Import and New block are in the Tool banner. A sidebar badge counts blocks, with the unused ones in its popover (GET /api/blocks/_count). GET /api/blocks carries hasCss, description and example, and ?usage=1 where each is used.
|
|
1101
|
+
- **Components is a Tool.** One row per component - its tag, props, size and last change - with Yours / Built in / Plugins, search by name or prop, and sort by name or last change. Click to edit; right-click (or ⋮, or Shift+F10) to copy its tag, duplicate it (a new slideover that checks the name as you type and keeps a draft), export it or delete it. New component and Import are in the Tool banner, and a sidebar badge counts them (`GET /api/components/_count`).
|
|
1102
|
+
- **API Builder is a Tool.** One row per endpoint - its GET URL, collection, params and who may call it - with All / Enabled / Disabled / Public, a project filter and search. Click opens it in the builder; right-click (or ⋮, or Shift+F10) to copy its URL, open it, enable or disable it, or delete it. New endpoint is in the Tool banner, and a sidebar badge counts endpoints with the public ones called out (`GET /api/api-endpoints/_count`). A project's own API list (`#/projects/<slug>/apis`) keeps its filter and pre-fills that project on New endpoint.
|
|
1103
|
+
|
|
1104
|
+
### Sidebar
|
|
1105
|
+
|
|
1106
|
+
- Each of these Tools brings its own badge route, which keeps the number the sidebar always showed there and
|
|
1107
|
+
adds a popover. The sidebar no longer downloads those lists just to count them - which also ends the
|
|
1108
|
+
`/api/actions` 503 logged on every render of a site without MongoDB.
|
|
1109
|
+
- A closed folder's total adds up signals (open tasks, new messages), not inventories: registered items
|
|
1110
|
+
marked `inventory: true` (all of these, and Projects) are left out, so a closed Content folder does not
|
|
1111
|
+
show pages + files + menus as one meaningless number. The Forms and Blocks badges keep their costly parts
|
|
1112
|
+
(submission stats, where blocks are used) for a minute between the sidebar's polls.
|
|
1113
|
+
|
|
1114
|
+
## [0.74.0] - 2026-09-25
|
|
1115
|
+
|
|
1116
|
+
Manage projects gets the Tool Upgrade.
|
|
1117
|
+
|
|
1118
|
+
- **Manage projects is a Tool.** One row per project - its icon, root URL and what is filed under it
|
|
1119
|
+
("12 artefacts - 5 pages, 3 forms…") - with All / Enabled / Disabled and a search box. Click a project
|
|
1120
|
+
for its overview; right-click it (or ⋮, or Shift+F10) to edit it, open its settings page, enable or
|
|
1121
|
+
disable it, untag everything under it or delete it. It wears the Tool banner, with New project in it.
|
|
1122
|
+
- **New and Edit are a slideover** on the list instead of a page, checked as you type against the
|
|
1123
|
+
same rules as the server, asking before throwing typed changes away and keeping a draft if you leave.
|
|
1124
|
+
A new project's slug follows its name. `#/projects/new` and `#/projects/edit/<slug>` still work -
|
|
1125
|
+
they open it. An edit can now clear a root URL (the old form could not).
|
|
1126
|
+
- **A sidebar badge on Manage projects**: how many projects, with the disabled ones called out
|
|
1127
|
+
(`GET /api/projects/_count`). `GET /api/projects?counts=1` answers the list in one request instead of
|
|
1128
|
+
one per project.
|
|
1129
|
+
- Creating, deleting, enabling or disabling a project re-renders the sidebar in place (the new
|
|
1130
|
+
`dm:sidebar-rebuild` event) instead of reloading the admin.
|
|
1131
|
+
|
|
1132
|
+
## [0.73.0] - 2026-09-25
|
|
1133
|
+
|
|
1134
|
+
Custom themes are themes: a plugin's themes (the Theme Roller's) join the built-in ones everywhere a theme
|
|
1135
|
+
is chosen.
|
|
1136
|
+
|
|
1137
|
+
- **Custom themes are themes.** A plugin can offer themes with `hooks.registerThemes()` - a built-in
|
|
1138
|
+
base plus the tokens it changes - and they join the built-in ones everywhere a theme is chosen: the
|
|
1139
|
+
site, the admin, day and night, and element overrides (Content › Theme lists them under Custom
|
|
1140
|
+
themes). Applied as `dm-theme-<base> dm-custom-<id>` with one generated rule, on both render paths,
|
|
1141
|
+
in the day/night switch and in the admin (`GET /api/theme/admin`, any signed-in user). The Theme
|
|
1142
|
+
Roller 1.1 is the first provider. Before this, a Theme Roller theme could not actually be used: the
|
|
1143
|
+
page template never applied the custom class, and saving one as the site theme was refused.
|
|
1144
|
+
- config/theme.json remembers each custom theme's base (`customBases`), so a page keeps it if the
|
|
1145
|
+
theme's plugin goes away. The public theme switcher refuses custom themes (Domma cannot switch to
|
|
1146
|
+
them).
|
|
1147
|
+
|
|
1148
|
+
## [0.72.0] - 2026-09-25
|
|
1149
|
+
|
|
1150
|
+
Every admin screen is a Tool: placeable anywhere in the sidebar, some locked, and the admin's home is a
|
|
1151
|
+
setting.
|
|
1152
|
+
|
|
1153
|
+
- **Choose the admin's home screen.** System > Site Settings > Admin home picks the screen the admin
|
|
1154
|
+
opens on, and where Dashboard goes - any Tool or page in the sidebar, or the built-in dashboard.
|
|
1155
|
+
Someone without the permission for it, or a home whose plugin has since been disabled, gets the
|
|
1156
|
+
built-in dashboard; a confined role keeps its own home. `/api/auth/permissions` answers `adminHome`.
|
|
1157
|
+
|
|
1158
|
+
- **Some Tools cannot be hidden or lost from the sidebar.** Dashboard, Menus, Users and My Profile can
|
|
1159
|
+
be moved, but not hidden or removed - the Menus editor greys those buttons out, and the sidebar puts
|
|
1160
|
+
one back in its folder if a menu has lost it or hidden it, including inside a hidden folder. They
|
|
1161
|
+
are how an admin finds their way back from a sidebar edit gone wrong. Role gates (`visibility`,
|
|
1162
|
+
`permission`) still apply.
|
|
1163
|
+
|
|
1164
|
+
- **Admins can put any Tool anywhere in the sidebar.** Content > Menus > Admin sidebar now lists every
|
|
1165
|
+
enabled Tool where it appears, badged with where it comes from (a plugin's name, or Built in). Move,
|
|
1166
|
+
hide or relabel one and Save: only the Tools you placed are saved, as `{url, ref}` placeholders,
|
|
1167
|
+
so the rest keep following their default folder. A Tool's address and
|
|
1168
|
+
permission are fixed; its bin puts it back in its default place. A placeholder for a Tool that is
|
|
1169
|
+
not enabled is kept (shown as "Not enabled") and takes effect again when it is.
|
|
1170
|
+
|
|
1171
|
+
- **Every admin screen is a Tool with a default folder, and the sidebar places it.** A plugin's
|
|
1172
|
+
`admin.sidebar` entries (and `registerSidebarItem`) may name a `folder` - `tools` unless they say
|
|
1173
|
+
otherwise - and the renderer places each Tool by `url`: where the admin-sidebar menu puts it if it
|
|
1174
|
+
does, else in its default folder. Nothing looks different yet; this is the groundwork for moving
|
|
1175
|
+
Tools anywhere from the Menus editor.
|
|
1176
|
+
- **Sidebar folders have stable keys.** Existing installs get `key`s on the seeded folders and a
|
|
1177
|
+
persisted Tools folder (once - deleting it is respected), so a Tool still finds its folder after
|
|
1178
|
+
an admin renames it. The Menus editor now keeps `key` and `ref` when it saves, and an empty keyed
|
|
1179
|
+
folder is a valid menu item.
|
|
1180
|
+
- **Hiding a plugin's sidebar item works.** The documented way - a hidden item in the admin-sidebar
|
|
1181
|
+
menu - never did: `/api/sidebar/menu` dropped hidden items before the renderer could see which
|
|
1182
|
+
Tools they hid. Hide by `url` now; the old same-text form is honoured too - so a hidden, url-less
|
|
1183
|
+
item that shares a Tool's name now hides that Tool, as documented.
|
|
1184
|
+
- **A hand-added sidebar link to a Tool's url is now that Tool's place.** Before, the Tool also
|
|
1185
|
+
appeared in Tools; now it renders once, where the link is, with the Tool's badge, permission and
|
|
1186
|
+
sub-items. A Tool restricted with `visibility` stays out of Tools for the users it is hidden from.
|
|
1187
|
+
|
|
1188
|
+
- **A plugin can take over a built-in Tool.** Notes, Todo, Analytics and Contacts are core since
|
|
1189
|
+
0.67.0, so `supersedes` could not displace them. A loaded plugin that names one now takes it over:
|
|
1190
|
+
the core sidebar entry is hidden and `#/<tool>` goes to the plugin's first admin route. Nothing is
|
|
1191
|
+
stored - a lapsed licence brings the core Tool straight back. First user: Contacts Pro.
|
|
1192
|
+
- **The plugin banner follows the screen.** A view with no template was mounted over the previous
|
|
1193
|
+
route's content, so the banner kept the old plugin's name and gained a second settings cog. The
|
|
1194
|
+
banner also names the screen when a plugin has several sidebar entries (Site Manager > Sites).
|
|
1195
|
+
- **Every wait shows a throbber.** One loader appears whenever a same-origin fetch runs past 200ms,
|
|
1196
|
+
beside the banner name or in the top bar.
|
|
1197
|
+
- **domma-js 0.44.3** (with domma-reactive 1.0.2): a computed refreshed early by another computed
|
|
1198
|
+
mid-flush no longer swallows the change, so every binding reading it updates. Includes 0.44.2's
|
|
1199
|
+
context-menu submenus closing when a sibling is hovered.
|
|
1200
|
+
- **Tests:** the page-editor smoke test clears a leftover fixture user instead of failing every run
|
|
1201
|
+
after an interrupted one, and the sidebar grouping tests - which sat outside `tests/**` - now run.
|
|
1202
|
+
|
|
1203
|
+
## [0.71.0] - 2026-09-24
|
|
1204
|
+
|
|
1205
|
+
- **Roles can have a site of their own.** A signed-in visitor now sees the navbar, footer, overlay
|
|
1206
|
+
and `[menu]` items meant for their role (the cache is keyed by role set). Visibility gains an
|
|
1207
|
+
exact gate, `=role` ("X only"), beside "X and above"; the page editor lists the live roles. The
|
|
1208
|
+
admin sidebar no longer needs `menus.read`, so any role gets its own sidebar.
|
|
1209
|
+
- **Plugins bring their own roles and permissions.** `plugin.json` `permissions[]` and
|
|
1210
|
+
`roles/*.json` are registered on every boot, and `hooks.registerRole()` /
|
|
1211
|
+
`hooks.unregisterRole()` create and remove a plugin's roles from code - never the site's or
|
|
1212
|
+
another plugin's. Uninstall removes the roles a plugin still owns. Plugins receive
|
|
1213
|
+
`requirePermission` in `options.auth`.
|
|
1214
|
+
- **Role-driven admin scope.** A role can carry `admin: {home, allow}`, confining its holders to
|
|
1215
|
+
those admin routes (edited under Roles > Admin area); this replaces the job-board roles that were
|
|
1216
|
+
hardcoded into core. Saving a role no longer erases its `plugin` and `grantedBy`, and the topbar's
|
|
1217
|
+
bell, badge and Settings now hide for users who cannot use them.
|
|
1218
|
+
- **Blog 1.6.1-1.6.3.** `/blog/` redirects to the index instead of "Not found"; the base path in
|
|
1219
|
+
the Blog settings applies as soon as it is saved (pages, shortcodes, preview and the navbar link
|
|
1220
|
+
follow it); and posts Blog Pro files under its other blogs stay out of `/blog`, so a lapsed Pro
|
|
1221
|
+
licence falls back to the free Blog cleanly.
|
|
1222
|
+
|
|
1223
|
+
## [0.70.1] - 2026-09-23
|
|
1224
|
+
|
|
1225
|
+
- **The mail reader's right-click menus show the glyph cursor too.** The message list and folder
|
|
1226
|
+
menus, shared by Mail Reader and Email Pro, were the last CMS-side menus still on the plain
|
|
1227
|
+
`context-menu` keyword.
|
|
1228
|
+
|
|
1229
|
+
## [0.70.0] - 2026-09-23
|
|
1230
|
+
|
|
1231
|
+
- **Right-click menus show that they are there.** Every context menu in the CMS now uses Domma's
|
|
1232
|
+
glyph cursor on hover - an arrow with a small list beside it - instead of the CSS `context-menu`
|
|
1233
|
+
keyword, which Windows and most Linux themes draw as the plain arrow. That covers authored public
|
|
1234
|
+
menus, collection displays, the Context Menu editor's preview, and the Todo, Notes, Contacts and
|
|
1235
|
+
Analytics menus. Collection displays drop their own funnel cursor for it. The page editor's
|
|
1236
|
+
shortcode menu covers the whole text area, so it keeps the text I-beam.
|
|
1237
|
+
- **Blog 1.6.0.**
|
|
1238
|
+
- The post editor's Status card shows when the post was published (or goes live), last saved and
|
|
1239
|
+
created.
|
|
1240
|
+
- Categories and tags are typeable pick-lists with removable pills; tags suggest the ones already
|
|
1241
|
+
in use, take Enter, commas and pastes, and reuse an existing spelling whatever its case.
|
|
1242
|
+
- Two new post layouts: **Split** (picture and headline side by side) and **Essay** (a serif long
|
|
1243
|
+
read with a drop cap and pull quotes).
|
|
1244
|
+
- Enabling the blog adds a Blog link to the navbar; disabling it removes that link, never one the
|
|
1245
|
+
site made itself.
|
|
1246
|
+
- Ctrl+S in the post editor saved twice, so a new post could be created twice.
|
|
1247
|
+
- **Domma 0.44.1**: Pillbox pills are styled. The stylesheet had been targeting class names the
|
|
1248
|
+
component never used, so every pill arrived as bare text beside a grey button.
|
|
1249
|
+
|
|
1250
|
+
## [0.69.4] - 2026-09-23
|
|
1251
|
+
|
|
1252
|
+
- **A withdrawn plugin is never committed as a site's own code.** A site's repo keeps a plugin only
|
|
1253
|
+
when the site wrote it, judged by "no upstream ships this name" - and nothing ships a withdrawn
|
|
1254
|
+
plugin, so a leftover `shopping-cart` folder was committed as site code. Every slug the
|
|
1255
|
+
marketplace reserves (promoted, retired or withdrawn) now counts as upstream.
|
|
1256
|
+
- **The old `contacts` collection stays out of site repos**, like the core Contacts collections:
|
|
1257
|
+
names, emails and phone numbers are data, not content.
|
|
1258
|
+
|
|
1259
|
+
## [0.69.3] - 2026-09-23
|
|
1260
|
+
|
|
1261
|
+
- **Published to npm again.** No change to what a site runs - the code is 0.69.2's. Three signing
|
|
1262
|
+
tests needed a private key that only the release machine holds, so every publish since 0.67.0
|
|
1263
|
+
failed in CI and npm stayed at 0.66.0. They now skip, saying why, where the key is absent; the
|
|
1264
|
+
rest of the suite runs everywhere.
|
|
1265
|
+
|
|
1266
|
+
## [0.69.2] - 2026-09-23
|
|
1267
|
+
|
|
1268
|
+
- **A site's repo holds its content and never the engine.** A new site's `.gitignore` is now an
|
|
1269
|
+
allowlist: everything is ignored, then `content/`, `config/` and the site's own plugins are let
|
|
1270
|
+
back in. Pages, media, blocks, views, forms, components, collections, API endpoints, menus and
|
|
1271
|
+
theme are all committed; `server/`, `admin/`, `public/`, `scripts/`, `node_modules/` (domma-js and
|
|
1272
|
+
domma-reactive) and every distributed plugin never are. `npx domma-cms <name>` writes it - the old
|
|
1273
|
+
file ignored `content/media/`, so uploaded images never reached the repo.
|
|
1274
|
+
- **A plugin created in the admin is committed with the site.** Scaffolding one adds its own allow
|
|
1275
|
+
rule, since it is the site's code rather than the updater's.
|
|
1276
|
+
- **Plugins can declare where users' work lives inside them.** `contentPaths` in `plugin.json` lists
|
|
1277
|
+
folders (e.g. `["data"]`) that hold what users author; installing the plugin tracks just those and
|
|
1278
|
+
leaves its code out. Docs and Theme Roller are covered for installs that predate the field.
|
|
1279
|
+
- Secrets, visitor-submitted and per-user data (users, api-tokens, contacts, invoices, enquiries,
|
|
1280
|
+
notes, todos), version history, analytics and share-link tokens stay out.
|
|
1281
|
+
|
|
1282
|
+
## [0.69.1] - 2026-09-23
|
|
1283
|
+
|
|
1284
|
+
- **The Blog and Mail Reader ship exactly as the marketplace publishes them.** They are written in
|
|
1285
|
+
dcms-marketplace and copied here by its `make sync-free-tier`, which will not copy over a change
|
|
1286
|
+
made here or left uncommitted. The engine no longer minifies or re-stamps them, so a site updated
|
|
1287
|
+
by the engine runs the same bytes as one installed from the catalogue - and Marketplace Sync no
|
|
1288
|
+
longer reports the engine's Blog as "modified".
|
|
1289
|
+
- **An engine update never takes a plugin back a version.** A site whose Blog came newer from the
|
|
1290
|
+
catalogue keeps it; before, the updater replaced every bundled plugin with the engine's copy.
|
|
1291
|
+
|
|
1292
|
+
## [0.69.0] - 2026-09-23
|
|
1293
|
+
|
|
1294
|
+
- **Mail: the sidebar hears when you read or unread a message**, so an unread badge moves straight
|
|
1295
|
+
away, and the mail mirror can list the newest unread messages (`listUnread`) for a badge's popover.
|
|
1296
|
+
|
|
1297
|
+
- **A closed sidebar folder shows the total of its Tools' counts.** Collapse Tools and it still says
|
|
1298
|
+
3 - red if anything inside is overdue - and hovering lists what makes it up ("Todo: 3 open tasks",
|
|
1299
|
+
"Invoices: 1 outstanding, 1 overdue"). Only the Tools' own counts add up, not totals like Pages, and
|
|
1300
|
+
the badge exists only when there is something to count.
|
|
1301
|
+
|
|
1302
|
+
- **Contacts, rebuilt on the Tool upgrade path, with a menu on every contact.** Right-click a contact
|
|
1303
|
+
(or its ⋮) to edit, email or call it, copy its email or phone, star it, add it to or take it out of
|
|
1304
|
+
a group, duplicate, select or delete it. Editing opens in a resizable side panel with every field
|
|
1305
|
+
the old form had - company, VAT, bank details, addresses, extra emails and phones. One row of chips
|
|
1306
|
+
filters by starred or group and shows the counts; search covers names, emails, phones, company,
|
|
1307
|
+
notes and groups. The toolbar ⋮ holds export, import and group management; selecting several
|
|
1308
|
+
contacts offers add-to-group and delete. The cog sets sort, starred first, density and columns.
|
|
1309
|
+
|
|
1310
|
+
- **Fixed: editing a contact stored some values in the wrong shape.** Payment terms were saved as text
|
|
1311
|
+
("30") and a favourite sent as the text "false" counted as starred. Editing now shapes values the
|
|
1312
|
+
same way creating does, and leaves fields it was not sent alone.
|
|
1313
|
+
|
|
1314
|
+
- **Blog 1.4.4: the post editor saves and stays.** Save (or Ctrl+S) stores the post without changing
|
|
1315
|
+
its status - a draft stays a draft, a live post updates - and keeps you on the page; the header
|
|
1316
|
+
says "Unsaved changes" or when it was last saved. A Posts button goes back to the list, asking first
|
|
1317
|
+
if anything is unsaved, and closing the tab with unsaved work asks too. Publish and Schedule also
|
|
1318
|
+
stay on the page. "Save Draft", which quietly took a live post off the site, is now an explicit
|
|
1319
|
+
"Move to drafts".
|
|
1320
|
+
|
|
1321
|
+
- **One typeface and one type scale across the Tools.** Every button, input, select and text box in
|
|
1322
|
+
a Tool was drawn in the browser's Arial beside Roboto text; they now use the page font, and
|
|
1323
|
+
monospace is kept for slugs, Markdown and addresses. Font sizes in Notes, Todo, Analytics, Blog and
|
|
1324
|
+
Marketplace Sync are on Domma's scale (xs / sm / base / lg / xl) instead of a dozen one-off sizes.
|
|
1325
|
+
|
|
1326
|
+
- **Tools show their numbers in the sidebar.** Todo shows open tasks and turns red when any are
|
|
1327
|
+
overdue; Blog shows comments waiting for approval; Analytics the visitors on the site right now
|
|
1328
|
+
(with a pulsing dot); Notes how many notes you have. Hover a badge - or focus its item - for a
|
|
1329
|
+
popover with the detail behind it: Todo's overdue, due-today and in-progress figures and its most
|
|
1330
|
+
urgent tasks; Blog's pending comments (who, on which post), drafts and next scheduled post;
|
|
1331
|
+
Analytics' views and sessions today and which pages people are on now; Notes' categories and most
|
|
1332
|
+
recently edited notes. Badges move the moment you act - completing a task or approving a comment
|
|
1333
|
+
updates the count with no reload - and refresh every minute. A plugin gets the same with `countUrl`
|
|
1334
|
+
on its sidebar entry, answering `{count, label, tone, details}`, and a `dm:sidebar-count` event
|
|
1335
|
+
when its number changes.
|
|
1336
|
+
|
|
1337
|
+
- **Blog 1.4.1: the blog's own link colours win again.** The rule that keeps the theme's link styling
|
|
1338
|
+
off the blog's buttons and chips outranked its own exceptions, so on some themes the active
|
|
1339
|
+
display button read dark-on-dark and category chips lost their colour.
|
|
1340
|
+
|
|
1341
|
+
- **Blog 1.4.0: layouts built from blocks, and listings you can search and right-click.** Every post
|
|
1342
|
+
picks a layout in the editor - Classic, Feature (a full-width hero), Minimal or Sidebar (a contents
|
|
1343
|
+
list that follows the reader) - or follows the site default in Blog settings. Listings show as
|
|
1344
|
+
Cards, List, Magazine or Compact, and readers can switch between them. Both are ordinary blocks
|
|
1345
|
+
(`blog-post-*`, `blog-card-*`): restyle one in the Blocks tool and every post using it changes;
|
|
1346
|
+
copy one under a new name and it becomes a new choice. The listing now searches, filters by
|
|
1347
|
+
category pill, sorts and shows more in the page with domma-reactive, keeps its filters in the
|
|
1348
|
+
address, and every post has a right-click menu - open, copy link, More in this category, More
|
|
1349
|
+
tagged, More by this author. Posts gain reading progress, related posts, older/newer links,
|
|
1350
|
+
sharing, and a comment form that posts in place. The editor previews the unsaved post in any
|
|
1351
|
+
layout at desktop, tablet or phone width. Posts > ⋮ adds a set of sample posts, and removes them.
|
|
1352
|
+
|
|
1353
|
+
- **Fixed: the blog was a blank page in every browser.** Its pages were sent without returning the
|
|
1354
|
+
reply, and under response compression - which every browser asks for - the body was lost: a 200
|
|
1355
|
+
with nothing in it. Tools like curl, which do not ask for compression, still saw the page.
|
|
1356
|
+
|
|
1357
|
+
- **Fixed: a blog author could publish without an editor.** Saving a post took `status` from the
|
|
1358
|
+
request as-is, so `{"status": "published"}` skipped the publish check. Status now changes only
|
|
1359
|
+
through Publish and Schedule, which check who is asking; an author can still move a post to draft.
|
|
1360
|
+
|
|
1361
|
+
- **Fixed: exported plugins downloaded corrupt.** The `.dcmsplugin` file is already gzip, and the
|
|
1362
|
+
server's compression labelled it as such, so browsers unzipped it on arrival (saving a bare tar) or
|
|
1363
|
+
failed with "Decoding failed". Plugin exports, and Site Manager's licensed downloads, are now sent
|
|
1364
|
+
as they are.
|
|
1365
|
+
|
|
1366
|
+
- **Todo, rebuilt on the Tool upgrade path.** Add a task by typing and pressing Enter, with an
|
|
1367
|
+
optional priority and due date. Due dates read "Today", "Tomorrow" or "3 days ago", and an overdue
|
|
1368
|
+
task is marked. One filter menu (Open, All, each status, Overdue, with counts) and a search replace
|
|
1369
|
+
the row of filter buttons, and completed tasks always sink to the bottom. Every change to a task -
|
|
1370
|
+
complete, status, priority, due today/tomorrow/next week, edit, delete - is on its right-click menu,
|
|
1371
|
+
also reached from its ⋮ or by clicking its status or priority pill. Editing opens a side panel.
|
|
1372
|
+
The cog holds what the list opens on, its sort, the default priority, relative dates and delete
|
|
1373
|
+
confirmation.
|
|
1374
|
+
|
|
1375
|
+
- **Analytics, rebuilt on the Tool upgrade path, and it shows the journeys it was already recording.**
|
|
1376
|
+
Sessions and bounce rate join the figures. A Journeys tab lists entry pages, exit pages and the
|
|
1377
|
+
most common page-to-page paths, and a Spikes tab lists pages running well above their usual rate
|
|
1378
|
+
today - both were computed by the API and never displayed. "Active now" refreshes itself. A range
|
|
1379
|
+
menu replaces five buttons, pages carry a share bar and a menu (open on the site, copy address,
|
|
1380
|
+
filter), and export and reset move behind the toolbar's ⋮. The chart takes the theme's colours.
|
|
1381
|
+
The cog sets the opening range, rows shown, line or bar chart, live refresh and reset confirmation.
|
|
1382
|
+
|
|
1383
|
+
- **Fixed: a page address could run script in the Analytics screen.** The public hit counter accepts
|
|
1384
|
+
any URL string and the old table wrote it into the page unescaped, so anyone could plant markup that
|
|
1385
|
+
ran when an administrator opened Analytics. Addresses are now always text.
|
|
1386
|
+
|
|
1387
|
+
- **Blog 1.3.0, on the Tool upgrade path.** Settings open from the cog in a side panel instead of a
|
|
1388
|
+
separate page (the old address still works). Posts: status chips with counts filter the list,
|
|
1389
|
+
with search, a category filter and sort. Every row has a menu - edit, view on the site, copy link,
|
|
1390
|
+
publish or unpublish, duplicate as a draft, delete. Categories show how many posts use them, open
|
|
1391
|
+
in a side panel, and "Show its posts" jumps to Posts already filtered. Comments load once, with a
|
|
1392
|
+
count on every status. Rejected comments can finally be listed. Each row names its post, opens to
|
|
1393
|
+
the full text, and carries approve, pending, reject, spam, reply-by-email and delete. "Approve all
|
|
1394
|
+
pending" is one click. Help (?) icons explain each part.
|
|
1395
|
+
|
|
1396
|
+
- **Fixed: a blog with comments switched off still accepted them.** The public comment route never
|
|
1397
|
+
checked the setting, and it read moderation from the settings the plugin started with, so changing
|
|
1398
|
+
either did nothing until a restart. Both are now read live. Saving one blog setting also no longer
|
|
1399
|
+
resets the others to their defaults.
|
|
1400
|
+
|
|
1401
|
+
- `admin/js/lib/tool-kit.js` gathers what every upgraded Tool shares - slideover clean-up, the ⋮
|
|
1402
|
+
button that opens the row's menu, per-browser settings, and tear-down when the view is left.
|
|
1403
|
+
|
|
1404
|
+
- **Notes, rebuilt on the Tool upgrade path.** Notes show as cards or a dense list, found by a
|
|
1405
|
+
search that also matches categories and a category filter that fits in the toolbar. Everything you
|
|
1406
|
+
can do to a note - edit, duplicate, copy, filter by its category, delete - is on its right-click
|
|
1407
|
+
menu, or its ⋮ button. Notes open in a resizable side panel with categories as pills, a word count
|
|
1408
|
+
and Ctrl+Enter to save. The banner's cog opens settings: layout, sort, preview length, category
|
|
1409
|
+
chips, delete confirmation and default categories for new notes, remembered per browser. Help (?)
|
|
1410
|
+
icons explain each part in place; press / to search and N for a new note. The help popovers are a
|
|
1411
|
+
shared admin helper (`admin/js/lib/help-popover.js`) for the other Tools to adopt.
|
|
1412
|
+
|
|
1413
|
+
- **Every plugin's banner shows when its licence runs out, and holds its settings cog.** A paid
|
|
1414
|
+
plugin's banner gains an expiry chip after the licence: "Expires" for a rental, "Support to" for a
|
|
1415
|
+
perpetual licence, whose plugin keeps running when support lapses. It turns amber inside 30 days and
|
|
1416
|
+
red once a rental has lapsed. Settings sits as a cog to the left of the chips on every plugin -
|
|
1417
|
+
Email and Mail Reader move theirs there from the Folders pane, and Blog's Settings leaves the
|
|
1418
|
+
sidebar for it (the page is still at its old address).
|
|
1419
|
+
|
|
1420
|
+
- **A managed site can browse and install from its manager.** The Browse tab lists what the fleet
|
|
1421
|
+
sells, with the monthly price, whether this site is licensed, and Update when the catalogue is ahead
|
|
1422
|
+
of what is installed. A paid plugin arrives with an entitlement minted for that site, so it runs as
|
|
1423
|
+
soon as it is enabled. Sites that manage themselves are unaffected and keep installing from a file.
|
|
1424
|
+
|
|
1425
|
+
- **Fixed: the Browse tab could never load its own template**, so it had never mounted once - it asked
|
|
1426
|
+
Domma's HTTP helper for HTML and that helper always parses JSON.
|
|
1427
|
+
|
|
1428
|
+
## [0.68.0] - 2026-09-22
|
|
1429
|
+
|
|
1430
|
+
- **Install a plugin from a file, and update one.** The Marketplace screen has an **Install from
|
|
1431
|
+
File** button and a per-plugin export, so the envelope routes are reachable without curl. A second
|
|
1432
|
+
upload of a plugin already installed offers to **update** it rather than refusing: the new version
|
|
1433
|
+
is extracted and validated before the working one is disturbed, `data/` and the entitlement carry
|
|
1434
|
+
across, and the previous version goes back if anything fails. A downgrade is refused unless asked
|
|
1435
|
+
for, and version comparison is numeric - 1.10.0 is newer than 1.2.0. Installing an unsigned plugin
|
|
1436
|
+
still takes an explicit confirmation; so does replacing one that is running.
|
|
1437
|
+
|
|
1438
|
+
- **A plugin card stops claiming to be enabled when it is not running.** A lapsed or unlicensed
|
|
1439
|
+
entitlement reads "Not running" with the reason on the badge, and a plugin displaced by one that
|
|
1440
|
+
replaces it reads "Replaced by <name>". Both facts were already in the API and were being dropped
|
|
1441
|
+
by the card.
|
|
1442
|
+
|
|
1443
|
+
- **A licensed plugin must be entitled to run.** Plugins declaring `closedSource` are checked against
|
|
1444
|
+
a signed entitlement at load; everything else - including every plugin you wrote yourself - is never
|
|
1445
|
+
asked. A **rental** stops when its term and grace period end. A **perpetual licence never stops**:
|
|
1446
|
+
what lapses is support and updates, so a plugin bought outright keeps running indefinitely, offline.
|
|
1447
|
+
An entitlement lives beside its plugin, or at `config/entitlements/` for one covering the whole
|
|
1448
|
+
instance. *(Correction, 0.92.2: this entry announced a `POST /api/plugins/entitlement` route for
|
|
1449
|
+
renewals; it was never shipped. A renewal is the new entitlement file placed at
|
|
1450
|
+
`plugins/<slug>/.entitlement.json` or in `config/entitlements/` followed by a restart, or the
|
|
1451
|
+
plugin re-installed from a `.dcmsplugin` carrying it, as an update.)*
|
|
1452
|
+
|
|
1453
|
+
## [0.67.0] - 2026-09-22
|
|
1454
|
+
|
|
1455
|
+
- **The Plugins screen is now the Marketplace.** You install, enable and configure there; an enabled
|
|
1456
|
+
plugin appears under Tools. The dead "Bundled" badge is gone - nothing read the value it wrote.
|
|
1457
|
+
|
|
1458
|
+
- **A plugin travels as one file.** `GET /api/plugins/:name/export` produces a `.dcmsplugin`
|
|
1459
|
+
envelope, and `POST /api/plugins/install-upload` installs one - the same format a marketplace
|
|
1460
|
+
purchase arrives in, so both enter through the same installer with the same traversal guard and
|
|
1461
|
+
manifest validation. Runtime `data/` and internal notes never travel; `?includeData=1` opts the
|
|
1462
|
+
first back in. A **licensed plugin cannot be exported**, on the same flag that refuses its source.
|
|
1463
|
+
An envelope exported from a running CMS is unsigned, because the marketplace key is offline:
|
|
1464
|
+
installing one takes an explicit confirmation, and a site can refuse them outright with
|
|
1465
|
+
`requireSignedPlugins` in `config/server.json`. A bundle whose signature is present but broken is
|
|
1466
|
+
always refused - that is tampering, never "unsigned".
|
|
1467
|
+
|
|
1468
|
+
- **Contacts is a built-in Tool.** Enabled on every site already, so it is part of the CMS rather
|
|
1469
|
+
than something to install. Your contacts and groups are untouched - they live in
|
|
1470
|
+
`contacts-contacts` and `contacts-groups`, and the slugs are unchanged. It gains `contacts.*`
|
|
1471
|
+
permissions, and the sample contacts the plugin shipped are no longer seeded: a fresh install
|
|
1472
|
+
starts empty rather than with two fictional people in it.
|
|
1473
|
+
|
|
1474
|
+
- **Analytics is a built-in Tool.** It was already unswitchable-off, so the code now matches what the
|
|
1475
|
+
behaviour has been for a while. **Your traffic history is preserved**: the counters are copied from
|
|
1476
|
+
`plugins/analytics/data/` to `content/analytics/` on first start, verified byte for byte before the
|
|
1477
|
+
old directory is removed, and a failed copy abandons the whole retirement rather than proceeding.
|
|
1478
|
+
Do-Not-Track, per-session dedup and the `/admin` and `/api` exclusions are unchanged. `analytics-pro`
|
|
1479
|
+
is unaffected - it counts server-side and remains a separate paid plugin.
|
|
1480
|
+
|
|
1481
|
+
- **Notes and Todo are built-in Tools.** Both plugins are retired into the CMS and removed on first
|
|
1482
|
+
start. **Your notes and tasks are untouched** - they already lived in the `notes-notes` and
|
|
1483
|
+
`todo-items` collections rather than inside the plugin, and the slugs are unchanged. They gain
|
|
1484
|
+
proper permissions (`notes.*`, `todo.*`), so a role can be given or denied either, and their admin
|
|
1485
|
+
screens now refresh an expired session instead of failing the save.
|
|
1486
|
+
|
|
1487
|
+
- **The theme switcher is a theme setting, not a plugin.** Enable it at **Content → Theme**; the
|
|
1488
|
+
`theme-switcher` plugin is retired into the CMS and removed on first start, with its placement
|
|
1489
|
+
lifted into `config/theme.json`. It now offers **every** theme the stylesheet ships - the plugin
|
|
1490
|
+
hardcoded 26 dots against 32 real themes - and each dot takes its colours from that theme's own
|
|
1491
|
+
tokens, so a new theme appears without touching anything.
|
|
1492
|
+
|
|
1493
|
+
- **`[demo]` is a built-in shortcode.** The `demo-viewer` plugin is retired into the CMS and removed
|
|
1494
|
+
on first start. **It also fixes a bug that made it useless:** the sanitiser drops a relative
|
|
1495
|
+
`iframe` src whenever `allowedIframeHostnames` is set, so every `[demo]` rendered an iframe
|
|
1496
|
+
pointing at nothing. Relative sources are same-origin by definition and are now allowed; the host
|
|
1497
|
+
gate on `[embed]` is unchanged.
|
|
1498
|
+
|
|
1499
|
+
- **`invoice` and `surveys` are no longer bundled.** Both move to the plugin marketplace as paid
|
|
1500
|
+
plugins (`dcms-marketplace`). `surveys`' 11 test files moved with it and run there against a
|
|
1501
|
+
sibling CMS checkout. Existing installs are unaffected: the plugin keeps
|
|
1502
|
+
working where it is already installed, and the four managed sites running it hold licences issued
|
|
1503
|
+
before the move. Published npm tarballs stay published, so a version you already have is a version
|
|
1504
|
+
you keep - only newer versions are withheld. Git history is not rewritten; the code has been public
|
|
1505
|
+
in released tarballs and a rewrite would buy little.
|
|
1506
|
+
|
|
1507
|
+
Design: `docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md`.
|
|
1508
|
+
|
|
1509
|
+
## [0.66.0] - 2026-09-22
|
|
1510
|
+
|
|
1511
|
+
### Added
|
|
1512
|
+
- **The whole invoice list as one PDF.** CSV and Excel describe the invoices; this exports the
|
|
1513
|
+
invoices themselves, a page each, for whatever the filters are currently showing. The hazard
|
|
1514
|
+
was CSS rather than layout: every print template is a whole HTML document, and they fight when
|
|
1515
|
+
merged - `modern` hides `.top`, `classic-nologo` hides `.top .party .name`. A site picks one
|
|
1516
|
+
style, but the logo and no-logo variants are chosen per invoice, so a batch spanning an issuer
|
|
1517
|
+
with a logo and one without mixes two templates, and unscoped the no-logo rule would delete the
|
|
1518
|
+
trading name from the invoices that do have a logo. Each stylesheet is confined with `@scope`,
|
|
1519
|
+
and identical ones are emitted once and shared, so the ordinary single-template case collapses
|
|
1520
|
+
to one block.
|
|
1521
|
+
|
|
1522
|
+
The invoice ids do not travel in the download token: a hundred uuids is four kilobytes of JWT in
|
|
1523
|
+
a query string, so the token carries a one-shot claim ticket and the list stays on the server,
|
|
1524
|
+
deleted on redemption. Capped at 100, with the render timeout growing with the batch.
|
|
1525
|
+
|
|
1526
|
+
- **The floating search button can be sized and shaped.** `iconSize` (32-96px) sets its width and
|
|
1527
|
+
height, with the glyph keeping its share of the button rather than staying 20px inside it, and
|
|
1528
|
+
`iconRadius` takes it from a hard square to a circle. The two are coupled, because at half its
|
|
1529
|
+
width a square is already a circle: the radius ceiling is half the size, and the admin slider's
|
|
1530
|
+
maximum moves with it. Icon mode only - the navbar trigger has no surface to round and the
|
|
1531
|
+
labelled variant is a pill.
|
|
1532
|
+
|
|
1533
|
+
### Changed
|
|
1534
|
+
- **Every slideover resizes, and remembers.** Domma sizes a slideover from a fixed set of presets
|
|
1535
|
+
and `admin.css` then pins it, which suits a confirmation and not a working panel: the width
|
|
1536
|
+
someone wants for a table of issuers or a menu tree depends on their screen, and a panel that
|
|
1537
|
+
forgets it on every open is barely better than a fixed one. Done by patching `E.slideover` once
|
|
1538
|
+
at boot rather than at the forty-odd call sites that open one - a call-site sweep drifts, misses
|
|
1539
|
+
new panels, and cannot reach the ones plugins open, since a plugin view runs inside the same SPA
|
|
1540
|
+
and calls the same `E`.
|
|
1541
|
+
|
|
1542
|
+
- **A plugin view gets the screen.** The 1200px `.view-container` cap is a measure for reading -
|
|
1543
|
+
right for a settings form, wrong for a table of invoices with nine columns, and with no
|
|
1544
|
+
centring it left the panel pinned left with half a wide display empty. Lifted for anything
|
|
1545
|
+
carrying the plugin shell, so it reaches every plugin without the author doing anything.
|
|
1546
|
+
|
|
1547
|
+
- **The mail admin is built from Domma rather than from `createElement`.** The settings sections
|
|
1548
|
+
declared their own inputs, laid themselves out with `style.cssText` on every node, and set
|
|
1549
|
+
status colours inline - which works, and is what the house rules exist to prevent. An inline
|
|
1550
|
+
colour cannot read a `--dm-*` token that a theme override redefines, so those screens were the
|
|
1551
|
+
same shade in every theme; and five sections each inventing a form is five copies of the same
|
|
1552
|
+
bugs.
|
|
1553
|
+
|
|
1554
|
+
Sections now declare a Forma schema and `_lib/admin/mail/section-kit.js` renders it, handing
|
|
1555
|
+
back the model's tracked proxy - so a card's title following what is typed into it, or the Away
|
|
1556
|
+
tab's state line following its dates, is a subscription rather than a set of callbacks somebody
|
|
1557
|
+
has to remember to call. Cards, badges, list groups and switches are Domma's components, and
|
|
1558
|
+
every layout and colour decision is a Domma class or one in `mail.css`.
|
|
1559
|
+
|
|
1560
|
+
There is no inline styling left anywhere in the mail admin. The rule builder is the one screen
|
|
1561
|
+
still assembled directly, for the reason given in its module comment: its rows change shape as
|
|
1562
|
+
you build them, and a schema re-rendered on each keystroke throws away focus.
|
|
1563
|
+
|
|
1564
|
+
## [0.65.0] - 2026-09-20
|
|
1565
|
+
|
|
1566
|
+
### Added
|
|
1567
|
+
- **An away message**, with the refusals that make one safe. It never answers something that
|
|
1568
|
+
announced itself as automatic, a mailing list, a bounce, a no-reply address, its own mailbox, or
|
|
1569
|
+
anything it was merely Bcc'd on, and it tells one person once per period. Its own replies carry
|
|
1570
|
+
`Auto-Submitted: auto-replied` so the responder at the far end refuses them in turn - one half
|
|
1571
|
+
without the other is a mail loop. The replies are deliberately not filed in Sent, since one
|
|
1572
|
+
absence makes a great many; a log on the Away tab is the record instead.
|
|
1573
|
+
|
|
1574
|
+
It also carries a preview, because every interesting thing an auto-responder does is a refusal
|
|
1575
|
+
and a refusal is invisible: "it did not answer the mailing list" and "it is broken" look
|
|
1576
|
+
identical. The preview runs the real decision over real mail and prints the reason for each.
|
|
1577
|
+
|
|
1578
|
+
- **A sidebar badge for unread mail**, and the mechanism behind it: a plugin's `admin.sidebar`
|
|
1579
|
+
entry may now declare `countUrl`, and the admin sidebar fetches it. The core counts were a fixed
|
|
1580
|
+
list a plugin could not join.
|
|
1581
|
+
- **A mailbox can have more than one face.** The Identity section is now a list rather than a
|
|
1582
|
+
form: each entry has its own display name, optional alias address, Reply-To and signature, and
|
|
1583
|
+
the first is the default. Compose grows a **From** picker when there is more than one, and
|
|
1584
|
+
changing it swaps the signature already in the message - where the block can still be found.
|
|
1585
|
+
An alias sends as itself in the header *and* the SMTP envelope, because SPF and DMARC are
|
|
1586
|
+
checked against the envelope and an alias that fails alignment lands in spam.
|
|
1587
|
+
|
|
1588
|
+
A mailbox configured before this migrates to a one-entry list on read, so nothing needed
|
|
1589
|
+
changing and nothing was lost.
|
|
1590
|
+
|
|
1591
|
+
- **Templates.** Canned responses with a name, an optional subject and a rich-text body, inserted
|
|
1592
|
+
from the compose window. Placeholders (`{{to.first}}`, `{{subject}}`, `{{my.name}}` and five
|
|
1593
|
+
more) are filled in on insertion; anything unrecognised is left visible, so a typo is found
|
|
1594
|
+
while the window is still open rather than after "Dear ," has gone to a customer. A template is
|
|
1595
|
+
added *above* what is already written and never overwrites a subject the message has.
|
|
1596
|
+
|
|
1597
|
+
- **Filing rules.** Conditions on sender, recipient, subject, mailing list, attachment, priority
|
|
1598
|
+
or size; actions to move, copy, mark read, flag, treat as spam, move to Trash, or stop. They run
|
|
1599
|
+
as part of a sync pass, in order, over **new mail only** - never during a full resync, and never
|
|
1600
|
+
over anything older than the day the rules were written. Every rule has a **Test** that runs it
|
|
1601
|
+
against the messages already mirrored and lists exactly what it would have caught, moving
|
|
1602
|
+
nothing: a rule's failure mode is not an error message, it is mail quietly going somewhere
|
|
1603
|
+
nobody looks.
|
|
1604
|
+
|
|
1605
|
+
`matches` is a wildcard rather than a regular expression - a stored regex is a pattern the
|
|
1606
|
+
server evaluates on every message that arrives, which is a denial of service waiting for a
|
|
1607
|
+
nested quantifier.
|
|
1608
|
+
|
|
1609
|
+
- **Send later, undo send and snooze**, all on one durable queue. Send holds a message for a
|
|
1610
|
+
configurable few seconds (ten by default) with an Undo, or schedules it for a time you pick;
|
|
1611
|
+
snooze moves a message out of the inbox and brings it back, unread, when it is due. The hold is
|
|
1612
|
+
written to the store before you are told the message was accepted, so a deploy landing in the
|
|
1613
|
+
middle of it loses nothing, and a queued send is claimed with a conditional update so it cannot
|
|
1614
|
+
go twice. Cancelling hands the draft back whole and reopens it.
|
|
1615
|
+
|
|
1616
|
+
- **Conversations.** The message list can group into threads, reconstructed from `Message-ID`,
|
|
1617
|
+
`In-Reply-To` and `References` - never from the subject. Grouping happens in the database so a
|
|
1618
|
+
page is a page of conversations, with a repair pass over the page for clients that put only the
|
|
1619
|
+
immediate parent in `References`. Mirrors built by an earlier version are backfilled the first
|
|
1620
|
+
time a folder is listed this way.
|
|
1621
|
+
|
|
1622
|
+
- **One-click unsubscribe** where a list advertises it (RFC 8058), the link opened in a tab where
|
|
1623
|
+
it does not, and a prefilled message where the list only offers `mailto:`. An `http://`
|
|
1624
|
+
unsubscribe link is ignored: it carries a token identifying the recipient.
|
|
1625
|
+
|
|
1626
|
+
- **Mail into the CMS.** An attachment saves straight to the media library; a message becomes a
|
|
1627
|
+
draft page or a collection entry. The attachment allowlist is deliberately narrower than the
|
|
1628
|
+
CMS's own - no SVG and no HTML, because `/media/` is same-origin and this file came from a
|
|
1629
|
+
stranger - and every name is de-duplicated, since `saveMedia` overwrites silently. Collections
|
|
1630
|
+
whose required fields a message cannot fill are not offered, rather than failing on submit.
|
|
1631
|
+
|
|
1632
|
+
- **Read receipts**, requested per message and sending both conventional headers or neither.
|
|
1633
|
+
**Mailbox quota**, where the server implements it. **Folder export as mbox**, streamed, so a
|
|
1634
|
+
mailbox mirrored here is not a mailbox held hostage here.
|
|
1635
|
+
|
|
1636
|
+
### Security
|
|
1637
|
+
- **An attachment's filename could defeat the media allowlist.** `/media/` is served with a
|
|
1638
|
+
`Content-Type` derived from the file extension, and the declared MIME type and the filename are
|
|
1639
|
+
two independent fields - both chosen by whoever sent the message. An attachment declared
|
|
1640
|
+
`image/png` and named `logo.svg` therefore passed the type check and landed on disk as a script
|
|
1641
|
+
container, served from the site's own origin. The extension is now derived from the allowed
|
|
1642
|
+
type and never from the sender.
|
|
1643
|
+
- **One-click unsubscribe could be pointed at the internal network.** It is the only place the
|
|
1644
|
+
server fetches a URL a stranger chose. Redirects are no longer followed, and the host must
|
|
1645
|
+
resolve to a public address - checking the literal is not enough, since a hostname can resolve
|
|
1646
|
+
to 127.0.0.1.
|
|
1647
|
+
- **`POST /messages/:uid/to-page` could overwrite any page.** `createPage()` writes
|
|
1648
|
+
unconditionally; the check the CMS's own route performs was missing here, so a caller holding
|
|
1649
|
+
only `pages.create` could replace the home page with the text of an email.
|
|
1650
|
+
|
|
1651
|
+
### Fixed
|
|
1652
|
+
- **Snooze could lose a message on a server without UIDPLUS.** `moveMessage` returning no new UID
|
|
1653
|
+
means the server cannot report where the message went - not that it stayed put. The route said
|
|
1654
|
+
"the message has not been moved" after it had, leaving it parked with no queue row and no way
|
|
1655
|
+
back. The Message-ID is now read before the move and searched for on wake.
|
|
1656
|
+
- **A queue row claimed when the process restarted was stranded forever.** Nothing matched
|
|
1657
|
+
`sending` or `waking` again, so the message was neither sent nor reported as failed, appeared in
|
|
1658
|
+
no list, and had no button. A later tick now reclaims a stale claim, failed sends can be
|
|
1659
|
+
retried, and the snoozed list shows everything unfinished rather than only what is pending.
|
|
1660
|
+
- **A failed wake was terminal.** A snooze coming due while the mail server was briefly
|
|
1661
|
+
unreachable was abandoned, and then hidden by the same filter. It retries like a send.
|
|
1662
|
+
- **Rules could fire on a message that was already there.** In an IMAP UID range `*` is the
|
|
1663
|
+
highest EXISTING uid, so `101:*` on a folder topping out at 100 is read as `100:101`. Harmless
|
|
1664
|
+
for the mirror; not harmless for a rule re-filing mail somebody had deliberately left alone.
|
|
1665
|
+
- **The conversation view could exceed the 16MB document limit.** A folder of automated mail
|
|
1666
|
+
sharing one thread root built a single aggregation document larger than a document may be.
|
|
1667
|
+
- **A signature containing `$'` pasted the rest of the draft into the message** when the sender
|
|
1668
|
+
was changed mid-compose: `String.replace` expands `$&`, `` $` `` and `$'` in a string
|
|
1669
|
+
replacement.
|
|
1670
|
+
- **A retry after a post-acceptance SMTP failure sent a second message.** It cannot be made
|
|
1671
|
+
exactly-once - a dropped connection between the terminating dot and the `250` is
|
|
1672
|
+
indistinguishable from one that never arrived - so a queued send now carries a fixed Message-ID
|
|
1673
|
+
and the duplicate is one the receiver can detect.
|
|
1674
|
+
- **The Undo strip expired before the hold it was offering to cancel**, on any mailbox set to
|
|
1675
|
+
20 or 30 seconds.
|
|
1676
|
+
- **Filing a message as a collection entry did not fire `collection:entryCreated`**, so a plugin
|
|
1677
|
+
listening for new entries never learned about one that arrived from the mail reader.
|
|
1678
|
+
- **`fromLine()` mangled an address with a display name.** Stripping spaces and angle brackets
|
|
1679
|
+
turned `Sales <s@e.com>` into `Saless@e.com` - an mbox separator naming an address that does
|
|
1680
|
+
not exist. It goes through `bareAddress()` now.
|
|
1681
|
+
- **An absent undo-send setting read as "send immediately".** `Number(null)` is `0`, and `0` is a
|
|
1682
|
+
legal setting meaning exactly that, so a mailbox that had never been asked was treated as one
|
|
1683
|
+
that had opted out of the hold.
|
|
1684
|
+
|
|
1685
|
+
|
|
1686
|
+
## [0.64.3] - 2026-09-20
|
|
1687
|
+
|
|
1688
|
+
### Added
|
|
1689
|
+
- **A mailbox can say who it is and sign off.** Mail Settings gains an **Identity** section: a
|
|
1690
|
+
display name, an optional Reply-To, a rich-text signature, and control over which of new
|
|
1691
|
+
messages, replies and forwards get it - plus whether it sits above the quoted message or at the
|
|
1692
|
+
very bottom. The signature is added at the one point all four ways of starting a message meet,
|
|
1693
|
+
so a reopened draft never accumulates a second one.
|
|
1694
|
+
|
|
1695
|
+
The engine ships the shared half. `_lib/admin/mail/identity.js` is pure and used by both sides:
|
|
1696
|
+
the send path builds the From header with it, the compose window adds the signature with it. It
|
|
1697
|
+
lives under `admin/` because only `{plugin}/admin/**` and `{plugin}/public/**` are served, and a
|
|
1698
|
+
module the browser must import cannot sit beside the credential store.
|
|
1699
|
+
|
|
1700
|
+
### Fixed
|
|
1701
|
+
- **The sealed SMTP password was sent to the browser.** `GET /accounts` returned each mailbox
|
|
1702
|
+
through `toPublic()`, whose destructure removes the top-level `passwordSealed` but does not
|
|
1703
|
+
reach into the nested `smtp` object - so the outgoing password's ciphertext shipped to every
|
|
1704
|
+
admin that listed its mailboxes. Nothing read it, and it was sealed rather than plaintext, but
|
|
1705
|
+
it had no business leaving the server. The nested object is now rebuilt through `smtpToPublic()`.
|
|
1706
|
+
- **Discarding an untouched reply asked about losing changes nobody had made.** The compose
|
|
1707
|
+
window compared the editor's text against the draft's plain-text part, and those already
|
|
1708
|
+
differed on every HTML reply because the editor is seeded from the HTML. It now compares against
|
|
1709
|
+
what the window actually opened showing. The redundant "or the subject is non-empty" clause is
|
|
1710
|
+
gone too - it caught nothing the change test did not, and flagged every reply and forward, which
|
|
1711
|
+
open with `Re:` / `Fwd:` prefilled.
|
|
1712
|
+
|
|
1713
|
+
## [0.64.2] - 2026-09-20
|
|
1714
|
+
|
|
1715
|
+
### Fixed
|
|
1716
|
+
- **A block template's own documentation could break its render.** The bundled-plugins grid on the
|
|
1717
|
+
home page came out as one empty `<div>` with the block's stylesheet printed underneath it as
|
|
1718
|
+
prose. Two causes, both general.
|
|
1719
|
+
|
|
1720
|
+
Every block ships with a header comment documenting its parameters and showing an example call,
|
|
1721
|
+
and that comment is still live markup when the shortcode processor runs. `plugin-card`'s example
|
|
1722
|
+
reads `[collection slug="plugins" … block="plugin-card"]` with no `/]`, so the processor took it
|
|
1723
|
+
for an unclosed tag and swallowed the rest of the render - four cards became one empty wrapper.
|
|
1724
|
+
Comments are now stripped in `loadBlockTemplate`, the single point every render path loads
|
|
1725
|
+
through.
|
|
1726
|
+
|
|
1727
|
+
Separately, a blank line ends an HTML block as far as Markdown is concerned. A block used inside
|
|
1728
|
+
another shortcode's wrapper (`[reveal]`, a `[col]`) arrives mid-HTML-block, so everything after
|
|
1729
|
+
the first blank line in its stylesheet came back wrapped in `<p>`. `buildBlockStyleTag` emits the
|
|
1730
|
+
CSS without blank lines now.
|
|
1731
|
+
|
|
1732
|
+
- **No Code button on a plugin whose source the server refuses.** A licensed plugin
|
|
1733
|
+
(`closedSource: true`) is distributed to run on a site, not to be read there, and the source
|
|
1734
|
+
endpoints refuse it - but the Plugins screen offered a Code button on every card, so the only way
|
|
1735
|
+
to learn the rule was to press it. The card leaves the button off now and shows a "Licensed"
|
|
1736
|
+
badge instead. `GET /api/plugins` carries `closedSource` and `licence` so the screen can tell.
|
|
1737
|
+
The header also wraps rather than squeezing the plugin name into a 46px column when three badges
|
|
1738
|
+
are present.
|
|
1739
|
+
|
|
1740
|
+
### Changed
|
|
1741
|
+
- **Search is part of the CMS now, not a plugin.** `site-search` was already force-loaded as a core
|
|
1742
|
+
feature - it could not be switched off, and the Plugins screen offered a toggle whose result the
|
|
1743
|
+
loader ignored. It is now what it always was in practice: a built-in tool.
|
|
1744
|
+
|
|
1745
|
+
Everything moved rather than changed. Scoring and indexing are `server/services/search.js` (same
|
|
1746
|
+
`stripMarkdown`, same weights), the endpoint is `GET /api/search?q=`, the overlay is
|
|
1747
|
+
`public/js/search.js` + `public/css/search.css`, and the settings screen is **System > Search**
|
|
1748
|
+
instead of a plugin page. Settings live in `config/search.json` and now include `enabled`, so a
|
|
1749
|
+
site that does not want search emits no trigger, stylesheet or script at all.
|
|
1750
|
+
|
|
1751
|
+
One real improvement fell out of the move: the renderer embeds the settings in the page as
|
|
1752
|
+
`window.__CMS_SEARCH__` instead of the client fetching `/settings` before it can draw anything, so
|
|
1753
|
+
the trigger is there on the first paint. The settings object is whitelisted on save - it reaches
|
|
1754
|
+
the source of every public page, so "whatever was PUT last" was the wrong contract for it.
|
|
1755
|
+
|
|
1756
|
+
`window.DommaSiteSearch.open()` keeps its name, so authored context menus and any site script
|
|
1757
|
+
calling it are unaffected, as are existing `.site-search-*` CSS overrides.
|
|
1758
|
+
|
|
1759
|
+
Existing sites are migrated on the next start: settings are lifted out of `plugins.json`, the
|
|
1760
|
+
plugins.json entry is dropped, and `plugins/site-search/` is deleted. That last part is the one
|
|
1761
|
+
that matters - the updater never removes a plugin that has gone away upstream, and a leftover copy
|
|
1762
|
+
switched back on would run a second search UI on every public page.
|
|
1763
|
+
|
|
1764
|
+
|
|
1765
|
+
## [0.62.0] - 2026-09-20
|
|
1766
|
+
|
|
1767
|
+
### Changed
|
|
1768
|
+
- **An enabled plugin now appears under Tools, not Plugins.** The sidebar already synthesised two
|
|
1769
|
+
folders, but it split them by whether a plugin was *core* - so only `analytics` ever reached
|
|
1770
|
+
Tools and everything else you switched on sat under Plugins next to the Manage link, reading as
|
|
1771
|
+
an optional extra forever.
|
|
1772
|
+
|
|
1773
|
+
Only an enabled plugin registers a sidebar item at all, so anything arriving at the grouper is a
|
|
1774
|
+
feature the site actually has. All of it goes to **Tools**. **Plugins** keeps exactly one thing:
|
|
1775
|
+
the Manage Plugins link, which is where you go to turn things on and off. Turning something on
|
|
1776
|
+
now moves it from the thing you manage to the thing you use, which is what enabling it meant.
|
|
1777
|
+
|
|
1778
|
+
No change to what is installed or where a plugin lives on disk, and the `core` flag still travels
|
|
1779
|
+
on each item for anything else that wants it.
|
|
1780
|
+
|
|
1781
|
+
## [0.61.0] - 2026-09-19
|
|
1782
|
+
|
|
1783
|
+
### Added
|
|
1784
|
+
- **"Mark as spam" in the context menu vocabulary.** An authored menu item can now flag the entry
|
|
1785
|
+
it was right-clicked on, defaulting to a `spam` field and taking an optional field name. The id
|
|
1786
|
+
comes from the wrapper the collection renderer puts round every entry and the collection slug
|
|
1787
|
+
from the display's own `data-ctx`, so the action needs no configuration to work.
|
|
1788
|
+
|
|
1789
|
+
It posts to a new `POST /api/collections/:slug/entries/:id/spam` rather than the generic entry
|
|
1790
|
+
update, because `updateEntry` **replaces** `data` wholesale - the obvious client-side PUT of
|
|
1791
|
+
`{spam: true}` would wipe every other field on the record. The merge happens server-side, under
|
|
1792
|
+
the same `collections.update` permission as any other write, so a logged-out visitor gets a 401
|
|
1793
|
+
and is told so rather than failing silently. The flag field need not be declared in the schema;
|
|
1794
|
+
validation only checks required fields, so an undeclared key is carried through untouched.
|
|
1795
|
+
|
|
1796
|
+
Three files in step as the vocabulary demands - `ACTION_TYPES` for server validation, the handler
|
|
1797
|
+
in `public/js/context-menus.js`, and `ACTION_GROUPS` in the editor picker - plus tests that read
|
|
1798
|
+
the other two files rather than trusting they were remembered. Miss the handler and an editor can
|
|
1799
|
+
save an item that silently does nothing; miss the picker and the action cannot be chosen.
|
|
1800
|
+
|
|
1801
|
+
## [0.60.4] - 2026-09-19
|
|
1802
|
+
|
|
1803
|
+
### Changed
|
|
1804
|
+
- Plugin release dates on the admin Plugins screen render as **dd.mm.yyyy** rather than the raw
|
|
1805
|
+
manifest string. Formatted with Domma's `D()` like every other date in the admin, and anything
|
|
1806
|
+
that is not a real date is dropped rather than printed as "Invalid Date" - `plugins/_template`
|
|
1807
|
+
ships the literal placeholder `PLUGIN_DATE`, and a scaffolded plugin keeps it until its author
|
|
1808
|
+
edits it.
|
|
1809
|
+
|
|
1810
|
+
### Changed
|
|
1811
|
+
- **The mail read surface is now shared, not owned by the free plugin.** The routes, the
|
|
1812
|
+
three-pane view, the folder tree, the pane sizing, the template, the stylesheet and the
|
|
1813
|
+
configuration defaults all moved into `plugins/_lib/mail/` and `plugins/_lib/admin/mail/`. The
|
|
1814
|
+
Pro edition registers the same surface and adds to it, rather than forking roughly two thousand
|
|
1815
|
+
lines that would then need every IMAP quirk and sanitiser decision fixed twice. `mail-reader` is
|
|
1816
|
+
now 48 lines of plugin and 17 of view; nothing about how it behaves has changed.
|
|
1817
|
+
|
|
1818
|
+
UI preferences moved from per-plugin keys to a shared `domma-mail.*` prefix. The editions never
|
|
1819
|
+
run together, so per-plugin keys would only have meant that upgrading to Pro silently forgot
|
|
1820
|
+
your pane widths and which folders you had folded.
|
|
1821
|
+
|
|
1822
|
+
## [0.60.3] - 2026-09-19
|
|
1823
|
+
|
|
1824
|
+
### Fixed
|
|
1825
|
+
- **The navbar hover no longer inverts the label, so it cannot go invisible.** `elements.css`
|
|
1826
|
+
paints a hover as white text on a dark slate fill: two properties that must BOTH land, and if the
|
|
1827
|
+
fill does not, white text is left on a cream page and the item reads as empty. Three attempts to
|
|
1828
|
+
make the pair land together - killing the dropdown's background-only transition, then the
|
|
1829
|
+
top-level links' crossfade, then the cache-buster so the fixes actually shipped - each measured
|
|
1830
|
+
clean in Chromium and each was still reported broken, because the reporter is on **Firefox** and
|
|
1831
|
+
every check had been Chromium. Rather than keep chasing an engine difference I cannot reproduce
|
|
1832
|
+
locally, the inversion itself is gone: hover now keeps the label its normal dark colour, tints
|
|
1833
|
+
the background with `--dm-hover-bg`, and underlines it. A tint that fails to paint costs a hover
|
|
1834
|
+
cue instead of the word, and the text colour never approaches the page background, so this class
|
|
1835
|
+
of bug cannot recur on any engine.
|
|
1836
|
+
|
|
1837
|
+
## [0.60.2] - 2026-09-19
|
|
1838
|
+
|
|
1839
|
+
### Fixed
|
|
1840
|
+
- **The two hover fixes reached nobody, because their cache-buster was never bumped.**
|
|
1841
|
+
`page.html` links `site.css` with a hand-written `?v=` token, and it still read
|
|
1842
|
+
`20260905-menu-highlight`. A browser keys its cache on the full URL, so a returning visitor kept
|
|
1843
|
+
the September stylesheet and never fetched either fix - while a cold browser, which is what every
|
|
1844
|
+
check of mine used, saw them immediately and passed. Same for `collection-context.js`, still on
|
|
1845
|
+
`20260819-export`. The tokens are now `20260919-nav-hover` and `20260919-ctx-tokens`.
|
|
1846
|
+
|
|
1847
|
+
This is why the hover bug survived two releases, a nine-site rollout and repeated headless
|
|
1848
|
+
verification: the fix was correct on disk, correct on the server, and invisible to anyone who had
|
|
1849
|
+
visited before.
|
|
1850
|
+
|
|
1851
|
+
### Added
|
|
1852
|
+
- Test pinning each versioned asset's content to its `?v=` token. Shipping a changed file under an
|
|
1853
|
+
unchanged token is silent in every other way - the file is right, the server is right, the suite
|
|
1854
|
+
passes - so it now fails loudly and says which token to bump.
|
|
1855
|
+
|
|
1856
|
+
## [0.60.1] - 2026-09-19
|
|
1857
|
+
|
|
1858
|
+
### Fixed
|
|
1859
|
+
- **The same hover fade on the top-level nav links, which is the one people actually notice.**
|
|
1860
|
+
0.60.0 fixed `.navbar-dropdown-item`, where `elements.css` transitions only the background while
|
|
1861
|
+
the hover rule sets the colour instantly. The top-level `.navbar-link` has the opposite spelling,
|
|
1862
|
+
`transition: all 0.15s`, so both properties animate - but the crossover from dark-text-on-light
|
|
1863
|
+
to white-on-dark passes through a washed-out middle, measured at roughly 2:1 around 30ms. Since
|
|
1864
|
+
the whole navbar is hoverable, the label appears to fade out every time the cursor crosses it,
|
|
1865
|
+
which is what "I can't see the nav text I'm on" meant. Colour and background now land together;
|
|
1866
|
+
transform, shadow and the highlight effects keep their 150ms, because a later entry in a
|
|
1867
|
+
transition list overrides an earlier one for that property alone. Scoped to the exact condition
|
|
1868
|
+
of the legacy rule that paints the dark fill, and `make verify-menus` still passes 158/158.
|
|
1869
|
+
|
|
1870
|
+
### Added
|
|
1871
|
+
- **Plugins can supersede one another.** A plugin declaring `"supersedes": ["other"]` in its
|
|
1872
|
+
manifest pushes that other aside at load time: the superseded plugin registers no routes, no
|
|
1873
|
+
sidebar item and no collections, so a paid edition and the free one it replaces never appear
|
|
1874
|
+
side by side. The claim lives on the replacement, so a bundled plugin needs no knowledge of the
|
|
1875
|
+
paid one that may displace it.
|
|
1876
|
+
|
|
1877
|
+
It is resolved every boot and **never written to `config/plugins.json`**, which is the whole
|
|
1878
|
+
point: a licence is withdrawn by disabling a plugin and leaving its files, so persisting the
|
|
1879
|
+
supersession would cost a lapsed customer both the replacement and the thing it replaced,
|
|
1880
|
+
leaving them worse off than someone who never paid. Derived at load time, the superseded plugin
|
|
1881
|
+
simply returns on the next restart. Mutual claims, cycles, self-claims and claims on core
|
|
1882
|
+
plugins are all refused with a warning rather than applied silently.
|
|
1883
|
+
|
|
1884
|
+
- **The Mail Reader shows the real folder hierarchy.** IMAP returns flat paths plus a delimiter
|
|
1885
|
+
('.' on Dovecot, '/' elsewhere), so `Work.Contracts` arrived as one entry and was drawn as a
|
|
1886
|
+
top-level "Contracts" with nothing tying it to Work - and two folders sharing a leaf name were
|
|
1887
|
+
indistinguishable. The pane now rebuilds the tree, indents by depth, and synthesises any
|
|
1888
|
+
intermediate level the server did not list rather than orphaning its children.
|
|
1889
|
+
|
|
1890
|
+
- **Nested mail folders fold away, and the panes are adjustable.** Any folder with children gets
|
|
1891
|
+
a disclosure control, remembered per mailbox. The reader now fills the window rather than
|
|
1892
|
+
stopping at a fixed height that guessed at admin chrome, and the two dividers between the panes
|
|
1893
|
+
can be dragged (double-click resets them); widths are remembered per user.
|
|
1894
|
+
|
|
1895
|
+
- **Deleting a user removes their stored mailboxes.** `deleteUser()` now emits a `user:deleted`
|
|
1896
|
+
hook, and the mail core clears that user's saved mailboxes and closes their connections when it
|
|
1897
|
+
fires. A sweep at startup catches anyone deleted while the plugin happened to be disabled, so
|
|
1898
|
+
stored credentials cannot outlive the account that owned them.
|
|
1899
|
+
|
|
1900
|
+
### Changed
|
|
1901
|
+
- **The IMAP core moved to `plugins/_lib/mail/`.** Crypto, account store, connection pool and
|
|
1902
|
+
renderer are shared with the forthcoming Pro edition, so they belong to neither plugin. State
|
|
1903
|
+
moved with it, to `plugins/_lib/data/` - the plugin-root `data` directory is the only path
|
|
1904
|
+
`bin/update.js` preserves, and a nested `_lib/mail/data` would have been deleted on every
|
|
1905
|
+
engine update, taking every saved mailbox with it. Existing installs are migrated on first
|
|
1906
|
+
read: the old encryption key is adopted before any new one is generated, so stored passwords
|
|
1907
|
+
keep working and nobody is asked to retype them. The folder-pane logic and the pane sizing moved
|
|
1908
|
+
to `plugins/_lib/admin/mail/` for the same reason - the Pro edition draws the same three panes -
|
|
1909
|
+
and the store location is overridable via `DOMMA_MAIL_DATA_DIR` so tests never operate on a
|
|
1910
|
+
real one.
|
|
1911
|
+
|
|
1912
|
+
### Fixed
|
|
1913
|
+
- **A plugin's admin stylesheet and template were never cache-busted.** View entries have been
|
|
1914
|
+
auto-stamped with their mtime for a while, but the stylesheet declared in `admin.css[]` and the
|
|
1915
|
+
template fetched by `templateUrl` were not, so after an update a browser kept serving the old
|
|
1916
|
+
ones. The visible result is a plugin that looks half-broken - a template cached without the
|
|
1917
|
+
markup a new feature needs reads as "that feature doesn't work" rather than as a stale file, and
|
|
1918
|
+
only a hard refresh explains it. Both are stamped now; the template reuses the token the loader
|
|
1919
|
+
put on the view module, so it refreshes exactly when the plugin does.
|
|
1920
|
+
- **The Mail Reader did not use the full width.** The admin caps its content at `max-width: 1200px`,
|
|
1921
|
+
which suits forms and tables and leaves a dead strip down the side of a mail client on a wide
|
|
1922
|
+
screen. The view now lifts that cap while it is mounted and puts it back on the way out, so no
|
|
1923
|
+
other screen is affected.
|
|
1924
|
+
- **Only the open mailbox's header pins now.** Every mailbox row was `position: sticky; top: 0`, so
|
|
1925
|
+
with more than one mailbox they all stacked at the same place and silently overlapped, the last
|
|
1926
|
+
one painted winning. The folders scrolling past belong to the open mailbox, so its name is the
|
|
1927
|
+
one that has to stay visible; the others scroll normally.
|
|
1928
|
+
- **The Mail Reader did not fill the window.** It sized itself against `window.innerHeight`, but
|
|
1929
|
+
the admin puts a view inside `main.dashboard-main`, which scrolls on its own and sits inside a
|
|
1930
|
+
padded container. Measuring against the window therefore overshot by that padding and the
|
|
1931
|
+
container scrolled instead of the panes fitting. It now measures the nearest scrolling ancestor
|
|
1932
|
+
and subtracts the padding in between.
|
|
1933
|
+
- **"Load images" could never work.** The reader rendered a message with `srcdoc`, and a `srcdoc`
|
|
1934
|
+
document **inherits the embedding page's Content-Security-Policy** - the admin's, whose
|
|
1935
|
+
`img-src` is `'self' data: blob:`. So the button flipped the image back to its real URL, the
|
|
1936
|
+
server returned it correctly, and the browser then refused to fetch it, silently, without ever
|
|
1937
|
+
making a request. Nothing in the server was wrong and no amount of testing it would have shown
|
|
1938
|
+
the problem.
|
|
1939
|
+
|
|
1940
|
+
The body is now served from its own route and loaded with `src`, so it carries its own policy
|
|
1941
|
+
instead of inheriting one. That route cannot read an `Authorization` header - an `<iframe src>`
|
|
1942
|
+
sends none - so it is authorised by a short-lived signed token naming one user, one mailbox and
|
|
1943
|
+
one message, in the same shape as the CMS's existing preview links. The token covers the
|
|
1944
|
+
load-images decision too, so it cannot be escalated by editing a URL. Scripts, frames and
|
|
1945
|
+
everything else stay refused, and a message whose images have not been loaded is served a policy
|
|
1946
|
+
that permits no remote images at all.
|
|
1947
|
+
- **The Mail Reader's folder list did not scroll.** `.mail-folders-list` had `overflow-y: auto`
|
|
1948
|
+
but, as a flex child, kept the default `min-height: auto` - "at least as tall as my content" -
|
|
1949
|
+
so a mailbox with more folders than fitted pushed the pane taller instead of scrolling inside
|
|
1950
|
+
it. The scrolling children now set `min-height: 0`. The mailbox header is sticky, so scrolling a
|
|
1951
|
+
long list never leaves you looking at indented folders with nothing to say which mailbox they
|
|
1952
|
+
belong to, and the message-list heading names the full path and the mailbox - an "Archive" under
|
|
1953
|
+
Work and another under Personal were previously both just "Archive".
|
|
1954
|
+
- **`plugins/_lib/` shipped in no published package.** It matches none of the plugin copy
|
|
1955
|
+
patterns in `scripts/build.js` - no `plugin.js`, no `services/`, no `views/` - so the shared
|
|
1956
|
+
helper library was absent from every tarball. A plugin built against its documented API worked
|
|
1957
|
+
in development and broke on an installed copy. Nothing bundled imported it yet, which is why it
|
|
1958
|
+
went unnoticed.
|
|
1959
|
+
- **Plugin runtime state was copied into the build.** `plugins/**/*.json` swept each plugin's
|
|
1960
|
+
`data/` directory into `_publish/`, so the build artifact carried the analytics counters - and
|
|
1961
|
+
would have carried sealed mailbox passwords once the mail core landed. The npm `files` list
|
|
1962
|
+
excluded them from the published tarball, but that is one typo away from shipping someone's
|
|
1963
|
+
credentials, and the artifact should never have contained them.
|
|
1964
|
+
|
|
1965
|
+
## [0.60.0] - 2026-09-19
|
|
1966
|
+
|
|
1967
|
+
### Added
|
|
1968
|
+
- **Mail Reader - a read-only IMAP mailbox in the admin.** `plugins/mail-reader/` connects to a
|
|
1969
|
+
standard IMAP server and displays mail: folders, paged envelopes, message bodies, attachments.
|
|
1970
|
+
A user may hold several mailboxes and sees only their own. It ships **disabled by default**, so
|
|
1971
|
+
no site stores a credential until someone opts in.
|
|
1972
|
+
|
|
1973
|
+
Read-only is enforced at the protocol level rather than merely by leaving routes out. Mailboxes
|
|
1974
|
+
open with `EXAMINE` and fetches use `BODY.PEEK[]`, so reading a message never sets `\Seen` - a
|
|
1975
|
+
reader that silently marks mail as read on someone's phone is worse than no reader at all.
|
|
1976
|
+
There is no route in the plugin that writes to a mailbox.
|
|
1977
|
+
|
|
1978
|
+
Message bodies are hostile content by default and are treated as such: scripts, styles, frames,
|
|
1979
|
+
forms and event handlers are stripped, `javascript:` URLs are dropped, and the result renders in
|
|
1980
|
+
an iframe sandboxed with neither `allow-scripts` nor `allow-same-origin`, so a hole in the
|
|
1981
|
+
sanitiser has no origin to abuse. Remote images are blocked unless the reader asks for them per
|
|
1982
|
+
message, because loading one reports the open - and who opened it - back to the sender; that
|
|
1983
|
+
choice is deliberately never persisted.
|
|
1984
|
+
|
|
1985
|
+
Passwords are sealed with AES-256-GCM into a gitignored, owner-only file rather than a
|
|
1986
|
+
collection: a plugin-owned collection is still browsable from the admin Collections screen and
|
|
1987
|
+
reachable through `/api/v1`, which is not where a mailbox credential belongs. The key resolves
|
|
1988
|
+
`MAIL_SECRET` -> `INSTANCE_SECRET` -> a generated plugin-local key, because `INSTANCE_SECRET`
|
|
1989
|
+
only exists on sites that ran setup or registered with a manager, and deriving from `JWT_SECRET`
|
|
1990
|
+
would mean rotating it bricked every stored password at the same time as logging everyone out.
|
|
1991
|
+
|
|
1992
|
+
Access control is ownership: account lookups take the owner's id as well as the account's, so a
|
|
1993
|
+
route cannot hand one user another's mailbox, and an id that is not the caller's answers 404
|
|
1994
|
+
rather than 403.
|
|
1995
|
+
- Test covering theme-token references: every `--dm-*` used from `public/` must be defined in a
|
|
1996
|
+
shipped stylesheet or set at runtime by our own code. An undefined token does not warn and does
|
|
1997
|
+
not throw, it silently uses whatever literal was typed as its fallback, which is how a
|
|
1998
|
+
dark-theme colour reaches every light theme unnoticed. Reintroducing either fix above fails the
|
|
1999
|
+
suite.
|
|
2000
|
+
|
|
2001
|
+
### Fixed
|
|
2002
|
+
- **Every npm publish since 0.55.1 failed, so the registry sat four minor versions behind the
|
|
2003
|
+
fleet.** The workflow ran `npm test` before the step that vendors the Domma bundle, and
|
|
2004
|
+
`public/dist/` is gitignored - so on a fresh CI checkout the theme service had no
|
|
2005
|
+
`domma-themes.css` to read theme and token names out of, and the admin smoke test drove a page
|
|
2006
|
+
with no bundle. Ten tests failed on every run and the job never reached the npm step, which is
|
|
2007
|
+
why the failure looked like a publishing problem and was not one. The bundle is now vendored
|
|
2008
|
+
before the tests. `npm latest` was 0.55.1 while every site ran 0.59.3, so a routine
|
|
2009
|
+
`npx domma-cms@latest update` would have silently downgraded the whole fleet.
|
|
2010
|
+
- **The published package carried a real contact record.** `plugins/contacts/data/contacts.json`
|
|
2011
|
+
is tracked, and `files` excluded `plugins/analytics/data` and `plugins/mail-reader/data` but not
|
|
2012
|
+
this one, so every tarball since the contacts plugin landed shipped a live name and mobile
|
|
2013
|
+
number to a public registry. The file is now an empty seed, and `files` excludes the data
|
|
2014
|
+
directory of every bundled plugin rather than naming them one at a time as each leak is noticed.
|
|
2015
|
+
`_lib/dataStore` returns a default on a missing file and creates the directory before writing,
|
|
2016
|
+
so shipping no `data/` changes nothing at runtime.
|
|
2017
|
+
- **A plugin's admin stylesheet never reached the published package.** `scripts/build.js`
|
|
2018
|
+
minified `plugins/*/public/**/*.css` but had no pattern for `plugins/*/admin/css/**/*.css`, so
|
|
2019
|
+
an `admin.css` declared in a plugin's `plugin.json` was dropped from the tarball. It is
|
|
2020
|
+
invisible in development, where the file is served straight off disk, and only shows up as an
|
|
2021
|
+
unstyled admin screen on an installed copy - the same shape as the missing `.mjs` that shipped
|
|
2022
|
+
0.22.1. The `invoice` plugin has been shipping unstyled since it was added.
|
|
2023
|
+
- **A collection sorted by a number scrambled itself past nine entries.**
|
|
2024
|
+
`FileAdapter.list()` compared sort values with a plain
|
|
2025
|
+
`String(a).localeCompare(String(b))`, so an `order` field came back 1, 10, 11,
|
|
2026
|
+
2, 20, 3. Nine entries look perfect, which is why this survived so long: the
|
|
2027
|
+
tenth entry is what breaks it, and the breakage looks like someone mis-typed
|
|
2028
|
+
the order values rather than like a bug. `MongoAdapter` hands the sort to the
|
|
2029
|
+
database, which compares numbers numerically, so the two adapters had been
|
|
2030
|
+
disagreeing about identical data - as had the server and the browser, since
|
|
2031
|
+
the public right-click menu sorts through `collection-sort.mjs`, which has
|
|
2032
|
+
always had a proper numeric ladder. The comparison now uses numeric
|
|
2033
|
+
collation. Dates and text are unaffected: ISO dates still order
|
|
2034
|
+
chronologically and text still orders alphabetically, both covered by tests.
|
|
2035
|
+
- **A navbar dropdown flashed its labels invisible on every hover.** Domma's `elements.css`
|
|
2036
|
+
transitions only the background on a dropdown item (`transition: background 0.15s ease`), while
|
|
2037
|
+
its light-mode hover rule sets `color: #ffffff !important` with no transition. The label
|
|
2038
|
+
therefore turned white on the first frame and then waited 150ms for the dark background to fade
|
|
2039
|
+
in behind it: white text on a near-white panel, about **1.02:1**, on every hover. Running the
|
|
2040
|
+
cursor down a menu flashed each row blank in turn. Transitioning the colour as well does not
|
|
2041
|
+
help, because the crossover from dark-on-light to light-on-dark passes through roughly 1.2:1
|
|
2042
|
+
whichever way it is animated, so both now land together the way a native menu does.
|
|
2043
|
+
`#site-navbar` outranks the framework selector, so no `!important` is needed. Measured on a
|
|
2044
|
+
real page frame by frame; the first frame goes from 1.02:1 to 14.6:1.
|
|
2045
|
+
- **The public right-click menu had no visible hover at all on light themes.**
|
|
2046
|
+
`collection-context.js` styled three surfaces with `var(--dm-surface-2, ...)`, and
|
|
2047
|
+
`--dm-surface-2` is defined in **none of the 32 shipped themes**, so every site fell back to the
|
|
2048
|
+
hardcoded literal sitting beside it: `rgba(255,255,255,.06)` and friends, white washes written
|
|
2049
|
+
while looking at a dark theme. On a light theme that is white on white, so hovering a row, and
|
|
2050
|
+
the search box and the filter chips, were invisible. They now use `--dm-hover-bg` and
|
|
2051
|
+
`--dm-input-bg`, which exist, with neutral-gray fallbacks that degrade sanely in either mode.
|
|
2052
|
+
`--dm-surface-alt` in `site.css` was the same mistake pointing the other way - a dark wash,
|
|
2053
|
+
invisible on dark themes - and is gone too.
|
|
2054
|
+
|
|
2055
|
+
## [0.59.3] - 2026-09-19
|
|
2056
|
+
|
|
2057
|
+
### Fixed
|
|
2058
|
+
- **Public pages carry cache headers.** They went out with no `Cache-Control`, no `ETag` and no
|
|
2059
|
+
`Last-Modified` at all, so a browser applied heuristic caching and could keep showing an edited
|
|
2060
|
+
page's old content on an ordinary refresh while the server served the new one - which looks
|
|
2061
|
+
exactly like a CMS bug and was reported as one. Pages now send
|
|
2062
|
+
`Cache-Control: private, no-cache` plus an `ETag`: store it, but check before reusing it. A
|
|
2063
|
+
repeat visit with nothing changed costs a 304 with no body; an edit changes the validator and the
|
|
2064
|
+
visitor gets the new page. `private` because page visibility is role-dependent, so a shared proxy
|
|
2065
|
+
must never hand one visitor a page rendered for another's permissions. Draft previews keep their
|
|
2066
|
+
stricter `no-store`, and 404s keep their status - the helper deliberately sets no status code, so
|
|
2067
|
+
adding headers could not turn a not-found into a soft 200.
|
|
2068
|
+
|
|
2069
|
+
## [0.59.2] - 2026-09-19
|
|
2070
|
+
|
|
2071
|
+
### Fixed
|
|
2072
|
+
- **The Context Menu editor's preview showed the menu you edited before, not the one on screen.**
|
|
2073
|
+
`onUnmount` called `closeAll()`, which hides the open panel but leaves the instance registered
|
|
2074
|
+
against `#ctx-preview`. Opening a second menu's editor bound a second instance to the same
|
|
2075
|
+
element; Domma collects every instance matching a right-click and sorts by priority, both were 0,
|
|
2076
|
+
the sort is stable - so the FIRST registered won, and the old menu's items appeared under the new
|
|
2077
|
+
menu's heading. The instance is destroyed on unmount now, and any stray still bound to the
|
|
2078
|
+
preview element is cleared on mount, for the case where unmount never ran.
|
|
2079
|
+
|
|
2080
|
+
## [0.59.1] - 2026-09-19
|
|
2081
|
+
|
|
2082
|
+
### Fixed
|
|
2083
|
+
- **Editing a context menu did not reach the front end on a live site.** The invalidation call was
|
|
2084
|
+
wrong twice over: it named `invalidateTag`, which the cache module does not export, and the tag
|
|
2085
|
+
`pages`, which nothing is ever stored with - so even the corrected spelling would have matched
|
|
2086
|
+
nothing. A `try/catch` marked "cache is optional" swallowed the resulting TypeError. Because the
|
|
2087
|
+
response cache is **off in development and on in production**, the edit worked perfectly on the
|
|
2088
|
+
machine it was written on and the live site served the old menu for up to an hour. Pages are
|
|
2089
|
+
tagged `site`, so that is what is cleared now, and a failed invalidation is logged instead of
|
|
2090
|
+
hidden.
|
|
2091
|
+
|
|
2092
|
+
### Changed
|
|
2093
|
+
- **Any authenticated admin change now clears the response cache.** The targeted invalidations stay
|
|
2094
|
+
- they are what stops one collection edit clearing every unrelated page - but they are no longer
|
|
2095
|
+
the only thing standing between an edit and the live site. Runs after the reply is sent, and is
|
|
2096
|
+
scoped to authenticated mutations so a public form submission does not cold-start the cache on a
|
|
2097
|
+
busy site.
|
|
2098
|
+
- The theme service passed a dead `pages` tag alongside `site`; dropped.
|
|
2099
|
+
|
|
2100
|
+
## [0.59.0] - 2026-09-19
|
|
2101
|
+
|
|
2102
|
+
### Added
|
|
2103
|
+
- **The Theme editor now shows what adopting a theme actually changes.** Picking a theme for an
|
|
2104
|
+
override lists how many tokens it moves here, the first few with before/after swatches, and -
|
|
2105
|
+
the point of it - warns when the adopted theme shares the current theme's surface colour:
|
|
2106
|
+
|
|
2107
|
+
Changes 70 of 119 tokens here.
|
|
2108
|
+
Both themes use the same surface colour, so this region keeps the fill it has now.
|
|
2109
|
+
Set --dm-card-bg (a card) or --dm-surface (most other regions) below to change it.
|
|
2110
|
+
|
|
2111
|
+
Adopting one light theme onto a region inside another changes its accent and text and leaves the
|
|
2112
|
+
fill exactly as it was, because most light themes use a white surface. That reads as a broken
|
|
2113
|
+
feature when it is doing precisely what it says. The check deliberately ignores
|
|
2114
|
+
`--dm-background`: that is the page canvas, which a card never paints from, and counting it
|
|
2115
|
+
would silence the warning for the very case that prompted this.
|
|
2116
|
+
- `GET /api/theme/themes/:id` returns one theme's token values, fetched on demand rather than
|
|
2117
|
+
folded into `/catalog` - 32 themes by ~245 tokens is a payload nobody needs on a page load.
|
|
2118
|
+
|
|
2119
|
+
## [0.58.2] - 2026-09-19
|
|
2120
|
+
|
|
2121
|
+
### Added
|
|
2122
|
+
- **Page picker for URL fields** (`admin/js/lib/page-picker.js`). A browse button beside the URL
|
|
2123
|
+
box lists the site's own pages with search, and marks anything not published. Typing still wins:
|
|
2124
|
+
the picker fills the field in rather than replacing it, because plenty of legitimate targets are
|
|
2125
|
+
not pages at all - an external site, an anchor, a file in the media library. Wired into the
|
|
2126
|
+
Context Menu editor's `navigate` action; `makePageInput()` returns the same `{el, input}` shape
|
|
2127
|
+
`makeIconInput()` does, so any other URL field can take it.
|
|
2128
|
+
|
|
2129
|
+
### Fixed
|
|
2130
|
+
- The Breadcrumbs tab in Site Settings used `col-sm-*`, which Domma's grid does not define, so its
|
|
2131
|
+
offset fields had been stacking full-width instead of sitting side by side. Same dead-class
|
|
2132
|
+
problem as the Context Menu editor in 0.58.1; these were the last of them in the admin.
|
|
2133
|
+
|
|
2134
|
+
## [0.58.1] - 2026-09-19
|
|
2135
|
+
|
|
2136
|
+
### Fixed
|
|
2137
|
+
- **The Context Menu editor's preview left headings out.** A menu with two headings listed six
|
|
2138
|
+
entries in the Items tab and previewed four. The preview now uses the same mapping the public
|
|
2139
|
+
renderer does, so what you see is what the page gets - a preview that quietly omits things is
|
|
2140
|
+
worse than none, because you trust it.
|
|
2141
|
+
- **The editor's two-column layouts never worked.** Every column was marked `col-md-*`, which
|
|
2142
|
+
Domma's grid does not define - only `col-1` to `col-12` exist - so every "column" had been a
|
|
2143
|
+
full-width block and the page was one long stack. That was the real cause of the editor reading
|
|
2144
|
+
as a mess.
|
|
2145
|
+
|
|
2146
|
+
### Changed
|
|
2147
|
+
- The preview pane now sits to the **right of the tabs**, visible from whichever tab you are on,
|
|
2148
|
+
and sticks as a long Items list scrolls past it. One instance rather than one per tab, because
|
|
2149
|
+
four copies would be four elements sharing `#ctx-preview` and the menu binds by selector.
|
|
2150
|
+
Domma's columns go side by side from 576px, which is far too narrow for a two-pane editor, so
|
|
2151
|
+
this workspace stacks below 1100px instead.
|
|
2152
|
+
- The item **Icon** field is the admin's icon picker rather than a bare text box.
|
|
2153
|
+
|
|
2154
|
+
## [0.58.0] - 2026-09-19
|
|
2155
|
+
|
|
2156
|
+
### Added
|
|
2157
|
+
- **"Specials" in the Context Menu builder** - the things a browser's own right-click menu offers:
|
|
2158
|
+
Reload, Back, Forward, Copy, Cut, Paste, Select all, plus **Open site search**. They take no
|
|
2159
|
+
arguments and each decides whether it applies to the click that opened the menu: Back greys out
|
|
2160
|
+
with no history behind the page, Paste outside an editable field, Copy with nothing selected,
|
|
2161
|
+
Site search when that plugin is disabled. That is `disabled` rather than hidden, so the menu keeps
|
|
2162
|
+
a stable shape and reads the way a native one does. Forward is the one the browser cannot report
|
|
2163
|
+
at all - there is no `canGoForward` - so it keys off whether this page load came from a
|
|
2164
|
+
back/forward navigation, which lights it up exactly when you have gone back.
|
|
2165
|
+
- The action picker is **grouped** (This page / Clipboard and selection / Collection displays /
|
|
2166
|
+
This site). Flat, it had grown past the point where anything could be found in it, and the
|
|
2167
|
+
self-disabling actions now say so under the picker rather than in the docs.
|
|
2168
|
+
- `window.DommaSiteSearch.open()` - the one handle the Site Search plugin exports, so the rest of
|
|
2169
|
+
the site can open it without reaching for its trigger button's class.
|
|
2170
|
+
- First tests for the context-menu action vocabulary, which shipped in 0.56.0 with none.
|
|
2171
|
+
|
|
2172
|
+
## [0.57.1] - 2026-09-19
|
|
2173
|
+
|
|
2174
|
+
### Added
|
|
2175
|
+
- Count pill for **Theme** in the admin sidebar, showing how many element overrides are
|
|
2176
|
+
configured. Theme answers with a config object rather than a list, so it needs its own
|
|
2177
|
+
accessor rather than the shared array-length one the other counts use.
|
|
2178
|
+
|
|
2179
|
+
## [0.57.0] - 2026-09-19
|
|
2180
|
+
|
|
2181
|
+
### Added
|
|
2182
|
+
- **Theme has its own home: Content > Theme.** The front-end theme, admin theme, automatic
|
|
2183
|
+
day/night pair and typography moved out of Site Settings into `config/theme.json`, lifted
|
|
2184
|
+
there by a one-shot migration on first boot. The matching `site.json` keys are still
|
|
2185
|
+
rewritten on every save as a one-way mirror, so plugins, the Theme Roller and fleet tooling
|
|
2186
|
+
that read them keep working; nothing in the CMS reads them any more.
|
|
2187
|
+
- **Per-element theme overrides.** An override is a CSS selector plus either a theme to adopt
|
|
2188
|
+
wholesale or individual `--dm-*` values, compiled server-side into one `<style>` block that
|
|
2189
|
+
is injected ahead of `content/custom.css`. A Domma theme is only a class-scoped block of
|
|
2190
|
+
custom properties, so re-declaring those properties on a selector re-themes everything
|
|
2191
|
+
inside it - including components rendered later, which is what hand-written CSS always
|
|
2192
|
+
misses, and without the specificity fight that made the navbar hover rule unbeatable. The
|
|
2193
|
+
theme and token pickers are built from the shipped `domma-themes.css`, so an override cannot
|
|
2194
|
+
name a token that does not exist. Values that could close their own declaration, and
|
|
2195
|
+
selectors that would re-theme the whole document, are refused rather than escaped.
|
|
2196
|
+
- **Form Triggers.** A trigger hangs off a field and acts on the whole form when the answers
|
|
2197
|
+
meet a condition: show a banner or toast, hide, disable or require other fields, refuse the
|
|
2198
|
+
submit, jump to a wizard step, celebrate, redirect, or run a CMS action. State actions apply
|
|
2199
|
+
while the condition holds and are reverted when it stops; event actions fire once on the
|
|
2200
|
+
transition into a branch, and never on the first render. Everything a trigger decides about
|
|
2201
|
+
submission is re-enforced in the submit route from the same engine, so a blocked form stays
|
|
2202
|
+
blocked when the button is re-enabled in dev tools. Authored in each field's Triggers panel,
|
|
2203
|
+
beside Conditional Logic, whose condition engine it shares. See
|
|
2204
|
+
`docs/guides/form-triggers.md`.
|
|
2205
|
+
|
|
2206
|
+
### Changed
|
|
2207
|
+
- Em dashes swept back out of the source, docs, tests and the seed script in favour of plain
|
|
2208
|
+
hyphens. `scripts/seed.js` carried 68 of them, so a freshly seeded site's demo content was the
|
|
2209
|
+
biggest offender.
|
|
2210
|
+
|
|
2211
|
+
### Fixed
|
|
2212
|
+
- **The Context Menu editor never loaded the menu you opened.** Domma's router calls
|
|
2213
|
+
`onMount($container)` with one argument, so the editor's `params.slug` was always undefined
|
|
2214
|
+
and every "edit" opened a blank new-menu form - which also left the live preview with no
|
|
2215
|
+
items to register, so right-clicking the Try it panel did nothing at all. It now reads the
|
|
2216
|
+
slug from the hash, as the menu and form editors do.
|
|
2217
|
+
- **The inline `[form]` embed leaked the form's actions into the page.** The whole form record
|
|
2218
|
+
was base64-encoded into `data-form-inline`, including the notification email recipients and
|
|
2219
|
+
the webhook URL. `GET /forms/:slug/public` has always stripped those; this path never did.
|
|
2220
|
+
The browser never read them - the submit route resolves them server-side.
|
|
2221
|
+
- **One malformed record 500'd the notification bell on every admin page load.** The admin polls
|
|
2222
|
+
the unread count on every screen, and all four notification handlers read `entry.data.x`
|
|
2223
|
+
without checking there was an `entry.data` - so a single flat record in the collection (left
|
|
2224
|
+
behind by a form that once pointed its submissions at `notifications`) threw
|
|
2225
|
+
`Cannot read properties of undefined (reading 'expiresAt')` every time. Entries that are not
|
|
2226
|
+
notifications are now skipped, with a one-time warning naming the id, and the rest of the
|
|
2227
|
+
inbox is still served. Mark-read and dismiss answer 404 for such a record rather than
|
|
2228
|
+
crashing.
|
|
2229
|
+
- **The theming docs listed design tokens that do not exist** (`--dm-color-primary` and
|
|
2230
|
+
friends). A `var()` on an undefined token silently resolves to nothing, which is how a popup
|
|
2231
|
+
ends up with no background; the list now matches the shipped stylesheet.
|
|
2232
|
+
|
|
2233
|
+
## [0.56.1] - 2026-09-18
|
|
2234
|
+
|
|
2235
|
+
### Fixed
|
|
2236
|
+
- **Context Menus was invisible to the `admin` role.** 0.56.0 added the
|
|
2237
|
+
`context-menus` permission family to the registry but never added it to the base
|
|
2238
|
+
`admin` seed, so only `super-admin` could see the sidebar item - on every site in
|
|
2239
|
+
a fleet, the entry was present in `admin-sidebar.json` and then filtered out
|
|
2240
|
+
again by permission. `roles.js seed()` self-heals base roles at boot, so this
|
|
2241
|
+
lands on restart with no migration. Custom roles are still not back-filled and
|
|
2242
|
+
need the grant adding by hand, as with Menus, Projects and API Tokens.
|
|
2243
|
+
|
|
2244
|
+
## [0.56.0] - 2026-09-18
|
|
2245
|
+
|
|
2246
|
+
### Added
|
|
2247
|
+
- **Context Menus are authorable in the admin** (Content > Context Menus). A list, an editor,
|
|
2248
|
+
and the storage and API behind them. Until now a right-click menu could only be added by
|
|
2249
|
+
editing JavaScript. Storage mirrors menus - one file per menu under `config/context-menus/` -
|
|
2250
|
+
and `binding` is deliberately the same shape menus already use, so per-page and per-project
|
|
2251
|
+
scoping behaves identically.
|
|
2252
|
+
- **Authored menus reach the public site.** Resolved per page server-side, so a visitor receives
|
|
2253
|
+
only the menus that apply - a menu bound to `/shop/*` is not in the source of `/about`. Both
|
|
2254
|
+
render paths do it. `public/js/context-menus.js` translates the records into Domma
|
|
2255
|
+
registrations; which menu answers a right-click stays Domma's decision.
|
|
2256
|
+
- Item actions come from a fixed vocabulary (navigate, filter, search, sort, export, copy-link,
|
|
2257
|
+
print, open-modal, run-action), never stored JavaScript: `admin/` is replaced wholesale by the
|
|
2258
|
+
updater, and a stored script on a public page is a cross-site scripting hole with extra steps.
|
|
2259
|
+
Anything bespoke goes through `run-action`, naming an Actions artefact with its own permissions.
|
|
2260
|
+
- Permission family `context-menus.{read,create,update,delete}`. Custom roles are not back-filled.
|
|
2261
|
+
|
|
2262
|
+
### Changed
|
|
2263
|
+
- **Requires `domma-js` 0.43.0** for `E.contextMenu`.
|
|
2264
|
+
|
|
2265
|
+
|
|
2266
|
+
### Changed
|
|
2267
|
+
- **Both context menus register with Domma instead of listening for themselves.** The collection
|
|
2268
|
+
filter panel and the page editor's shortcode menu each owned a `contextmenu` listener - one on
|
|
2269
|
+
`document`, one on the textarea. That works while a page has exactly one context menu. Add a
|
|
2270
|
+
second and the winner is decided by which script registered first rather than by which menu is
|
|
2271
|
+
nearer the click, which is a bundling accident and differs between dev and production. Domma
|
|
2272
|
+
0.43.0 keeps one document listener and a registry of bound containers and resolves by walking
|
|
2273
|
+
outward from the click, so depth decides and load order cannot. Requires `domma-js` 0.43.0; both
|
|
2274
|
+
menus fall back to their original listener below that, so behaviour is unchanged until the bump.
|
|
2275
|
+
- **The collection menu now owns its display outright.** It registers with `exclusive: true`, so a
|
|
2276
|
+
right-click menu bound deeper - on `[data-entry-id]`, or on any selector that happens to match
|
|
2277
|
+
inside a display - is not offered the click. Filter, search, sort, group-by and export are the
|
|
2278
|
+
point of a collection display and are not replaced by accident. It is not a veto: when the menu
|
|
2279
|
+
declines because there is no usable model, it steps aside completely and the click reaches
|
|
2280
|
+
whatever encloses the display.
|
|
2281
|
+
- Neither panel is an item list, so both hand off through Domma's `render` callback and keep their
|
|
2282
|
+
own rendering and open/close handling unchanged. A rendered panel is opaque in both directions:
|
|
2283
|
+
no ancestor items merge in, and it contributes none upward.
|
|
2284
|
+
|
|
2285
|
+
## [0.55.1] - 2026-09-18
|
|
2286
|
+
|
|
2287
|
+
### Fixed
|
|
2288
|
+
- **A menu's selected-item effect was wiped by its own `hover: none`.** `highlight.active` did nothing
|
|
2289
|
+
on any menu that also set `highlight.hover` to "none" - which is most menus that configure
|
|
2290
|
+
highlighting at all. `hover: none` declares its rest and hover states together, on purpose, so the
|
|
2291
|
+
two are provably identical; that includes `background-color: transparent !important`, and since
|
|
2292
|
+
0.54.1 it carries a `#site-navbar`-scoped copy at specificity (1,4,0). The current-page effects had
|
|
2293
|
+
no such copy and sat at (0,4,0), so the rest-state rule won: `darken`, `background` and `pill` lost
|
|
2294
|
+
their background entirely, and `underline`/`overline` lost their line. Every current-page effect now
|
|
2295
|
+
carries the navbar-scoped twin. Per-item overrides already had theirs, which is why only the
|
|
2296
|
+
menu-wide effects were affected.
|
|
2297
|
+
|
|
2298
|
+
---
|
|
2299
|
+
|
|
2300
|
+
## [0.55.0] - 2026-09-18
|
|
2301
|
+
|
|
2302
|
+
### Added
|
|
2303
|
+
- **Per-item highlighting.** `highlight: {hover, active, accent}` can now sit on any individual menu
|
|
2304
|
+
item, not just on the menu. The menu's block is the default; an item's own replaces either half of
|
|
2305
|
+
it, so one entry can glow orange and become an orange pill while the rest of the bar keeps a plain
|
|
2306
|
+
colour shift. Edited under **Highlighting** in the Menus item editor: blank means "inherit the
|
|
2307
|
+
menu's", `None` means "switch this off for this item only" - different things, and the editor says
|
|
2308
|
+
so. Applies on every render path: the public navbar, footer columns, `[menu]`, overlay panels and
|
|
2309
|
+
the admin sidebar.
|
|
2310
|
+
- **Menu alignment.** `align: left | centre | right | spread` on a menu, as **Alignment** on the
|
|
2311
|
+
Menus Behaviour tab. Horizontal menus only.
|
|
2312
|
+
|
|
2313
|
+
### Fixed
|
|
2314
|
+
- **Navbar items were never actually centred.** Domma's stylesheet centres `.navbar-nav`, but inside
|
|
2315
|
+
the space left over once the brand and the actions block have taken theirs - so a wide brand shunts
|
|
2316
|
+
the items right by half its width, and a search icon pulls them back left by half of its own. They
|
|
2317
|
+
lined up on the bar's real centre only when those two happened to be the same width. The bar is now
|
|
2318
|
+
a three-track grid whose outer tracks are equal, so the items land on the centre line whatever
|
|
2319
|
+
flanks them. Measured at 1440px with a 161px brand: the list centre moved from 80px right of the
|
|
2320
|
+
bar centre to 0. This is the default; `align` overrides it.
|
|
2321
|
+
- **The "when selected" effect looked like it did nothing.** `public/css/site.css` still carried the
|
|
2322
|
+
pre-menus current-page marker - bold plus a 2px `::after` bar, with `!important` - and nothing in
|
|
2323
|
+
`menu-highlight.css` can reset a pseudo-element it does not know about. So the rule was additive
|
|
2324
|
+
rather than a default: picking "Filled pill" drew a pill *and* the old bar, picking "None" left both
|
|
2325
|
+
the bar and the bold. It now stands down for any menu-level or per-item effect, and still draws for
|
|
2326
|
+
menus that have chosen nothing.
|
|
2327
|
+
- **New sites ship with SMTP configured.** `bin/cli.js` wrote no `smtp` key at all, and an empty
|
|
2328
|
+
`smtp.host` is not an error - it diverts every message to a throwaway Ethereal test account that
|
|
2329
|
+
accepts the mail and reports success. Password resets and form notifications appeared to send, the
|
|
2330
|
+
Health panel's "Last email send" went green, and nothing was delivered. A scaffolded site now gets
|
|
2331
|
+
a complete block from `bin/lib/smtp-defaults.js`: `SMTP_HOST`/`PORT`/`SECURE`/`USER`/`PASS`/`FROM`
|
|
2332
|
+
from the environment, falling back to a local MTA on port 25, with the port defaulting to 587 when
|
|
2333
|
+
a real host is supplied. Credentials are read from the environment, never compiled in. The fleet
|
|
2334
|
+
manager already did this for the sites it provisions (`composeSmtp` in `siteScaffolder.js`); this
|
|
2335
|
+
is the equivalent for a site created straight from the CLI.
|
|
2336
|
+
- **A site with no SMTP host now says so.** Three surfaces, because the failure is invisible
|
|
2337
|
+
otherwise: a warning banner on Settings → Email (live as you edit the field), a new `SMTP host`
|
|
2338
|
+
check in Health at `warn` - deliberately separate from `Last email send`, which is green on an
|
|
2339
|
+
Ethereal send - and a `console.warn` naming the consequence instead of the previous friendly info
|
|
2340
|
+
line. The fallback itself is unchanged, so existing sites keep working.
|
|
2341
|
+
- `npm run setup` now renames `smtp.fromName` along with the site title, so a fresh install stops
|
|
2342
|
+
signing its email "My Site" - but only while the two still agree, leaving a deliberately chosen
|
|
2343
|
+
From name alone.
|
|
2344
|
+
|
|
2345
|
+
### Changed
|
|
2346
|
+
- The two effect dropdowns on the Menus Behaviour tab are built from `HIGHLIGHT_HOVER` /
|
|
2347
|
+
`HIGHLIGHT_ACTIVE` in `menu-decor.mjs` rather than from hand-written `<option>` markup, so they and
|
|
2348
|
+
the per-item ones cannot offer different effects. The menu-wide "Current page" field is relabelled
|
|
2349
|
+
**When selected**, matching the per-item control.
|
|
2350
|
+
|
|
2351
|
+
---
|
|
2352
|
+
|
|
2353
|
+
## [0.54.2] - 2026-09-06
|
|
2354
|
+
|
|
2355
|
+
### Fixed
|
|
2356
|
+
- **Preset colours degrade to a literal instead of silently changing.** `colourToCss` emitted a bare
|
|
2357
|
+
`var(--dm-success)`. A token that a theme does not define makes the custom property
|
|
2358
|
+
guaranteed-invalid, and what happens next depends on the call site: a menu `highlight.accent`
|
|
2359
|
+
quietly fell through to the theme primary, so the accent an editor picked was not the one drawn;
|
|
2360
|
+
a badge or pill background, which uses the value directly with no fallback, became transparent and
|
|
2361
|
+
took its white text with it. Presets now emit `var(--dm-success, #198754)` - the literal each token
|
|
2362
|
+
carries in Domma's core theme - the way Domma's own CSS does it. This covers every consumer:
|
|
2363
|
+
menu item colours, badges, pills, floating-panel accents and highlight accents.
|
|
2364
|
+
- The stylesheet's own `var(--dm-menu-highlight, var(--dm-primary))` chain ended in a bare token for
|
|
2365
|
+
the same reason and now ends in `#0d6efd`.
|
|
2366
|
+
|
|
2367
|
+
### Changed
|
|
2368
|
+
- `PRESET_COLOUR_VARS` / `colourToCss` are no longer duplicated between `server/services/menus.js`
|
|
2369
|
+
and `public/js/menu-decor.mjs`. The server imports and re-exports the browser module's copy, as it
|
|
2370
|
+
already does for the highlight helpers, so the two can no longer drift. `PRESET_COLOUR_FALLBACKS`
|
|
2371
|
+
joins them, and a test asserts every preset has both a token and a literal.
|
|
2372
|
+
|
|
2373
|
+
---
|
|
2374
|
+
|
|
2375
|
+
## [0.54.1] - 2026-09-06
|
|
2376
|
+
|
|
2377
|
+
### Fixed
|
|
2378
|
+
- **Menu hover effects did nothing on a transparent navbar.** Every `highlight.hover` value was
|
|
2379
|
+
inert on any navbar without a `navbar-light`/`navbar-dark` variant class while the site was in
|
|
2380
|
+
light mode: "None" did not switch hover off, "Colour change" changed no colour, "Background tint"
|
|
2381
|
+
showed the theme's legacy slate instead of the accent, and "Uppercase" and "Grow" were swamped by
|
|
2382
|
+
a background that never moved. Domma's `elements.css` carries
|
|
2383
|
+
|
|
2384
|
+
[data-mode="light"] .navbar:not(.navbar-light):not(.navbar-dark) .navbar-link:hover
|
|
2385
|
+
{ color: #fff !important; background-color: #1e293b !important }
|
|
2386
|
+
|
|
2387
|
+
at specificity (0,5,0). The effect rules were (0,3,0), and with `!important` on both sides
|
|
2388
|
+
specificity decides the winner - so the legacy rule owned the hover colour and background and the
|
|
2389
|
+
chosen effect was invisible. Each hover effect is now declared twice, the second scoped to
|
|
2390
|
+
`#site-navbar` at (1,3,0), which wins. `transform`, `text-transform`, `letter-spacing`,
|
|
2391
|
+
`animation` and `text-shadow` also gained the `!important` they needed for the same reason.
|
|
2392
|
+
A test now asserts the paired selectors exist for every effect in the vocabulary.
|
|
2393
|
+
- **The footer's motion switch is honoured.** The reduced-motion rules keyed only off the OS
|
|
2394
|
+
`prefers-reduced-motion` media query, but the site's own switch stores the visitor's choice and
|
|
2395
|
+
stamps `dm-reduced-motion` on `<html>` (it patches `matchMedia` for JS, which CSS never sees). A
|
|
2396
|
+
visitor who had switched motion off still got `grow` and `breathe`.
|
|
2397
|
+
|
|
2398
|
+
---
|
|
2399
|
+
|
|
2400
|
+
## [0.54.0] - 2026-09-05
|
|
2401
|
+
|
|
2402
|
+
### Added
|
|
2403
|
+
- **Menu item highlighting is configurable.** A menu can now say what its items do under the
|
|
2404
|
+
pointer and how the item for the current page is marked, through a new `highlight` block in
|
|
2405
|
+
`config/menus/<slug>.json` (Menus > Behaviour in the admin):
|
|
2406
|
+
`hover` - `none`, `colour`, `underline`, `background`, `uppercase`, `grow`, `breathe` or `glow`;
|
|
2407
|
+
`active` - `none`, `bold`, `underline`, `overline`, `background`, `darken` or `pill`;
|
|
2408
|
+
plus an optional `accent` (preset key or `#rrggbb`) the effects draw with. Previously the hover
|
|
2409
|
+
colour shift and the current-page underline were fixed by the framework theme with no way to
|
|
2410
|
+
change or switch them off. Both fields are optional and a menu without the block looks exactly
|
|
2411
|
+
as it did, so nothing moves on an existing site until an editor picks an effect.
|
|
2412
|
+
The effects apply on every path a menu is drawn on - the public navbar, both footer columns, the
|
|
2413
|
+
`[menu]` shortcode, overlay panels and the admin sidebar. `grow` and `breathe` are dropped for
|
|
2414
|
+
visitors whose browser asks for reduced motion.
|
|
2415
|
+
- **The current page is now marked in menus.** Menu items pointing at the page being viewed carry
|
|
2416
|
+
`aria-current="page"` (and `.active` / `.is-active`), on the navbar, the footer and the `[menu]`
|
|
2417
|
+
shortcode. Nothing marked them before - Domma's navbar only ever read an `active` flag the CMS
|
|
2418
|
+
never set - so the current page was announced to nobody and `highlight.active` would have had
|
|
2419
|
+
nothing to style. Matching is exact, ignoring query string, fragment and trailing slash, so an
|
|
2420
|
+
item pointing at `/` is only marked on the home page and a parent is not marked because you are
|
|
2421
|
+
somewhere below it.
|
|
2422
|
+
|
|
2423
|
+
### Changed
|
|
2424
|
+
- The blanket `.page-body a` link styling in `site.css` now steps aside for a `[menu]` whose menu
|
|
2425
|
+
declares a `highlight` - its rest-state underline would otherwise pre-empt a hover underline.
|
|
2426
|
+
Menus without a highlight block are untouched.
|
|
2427
|
+
- Supersedes the first cut of this feature (`style.hover` / `style.active`, never released). Those
|
|
2428
|
+
keys are still read and translated onto `highlight`, so a menu saved under the previous admin
|
|
2429
|
+
keeps rendering. Two defects went with it: nothing in the CMS ever set `.active` or
|
|
2430
|
+
`aria-current` on a public menu, so the whole selected-item half was inert outside the admin
|
|
2431
|
+
sidebar; and the navbar's generated `<style>` tag appended `:hover` / `.active` to only the last
|
|
2432
|
+
selector of a comma-joined list, so those rules applied to every navbar item unconditionally.
|
|
2433
|
+
- `aria-current` is allowed through the markdown sanitiser (previously only `aria-hidden` was), so
|
|
2434
|
+
the marking survives on the `[menu]` shortcode path.
|
|
2435
|
+
|
|
2436
|
+
---
|
|
2437
|
+
|
|
2438
|
+
## [0.53.0] - 2026-08-31
|
|
2439
|
+
|
|
2440
|
+
### Added
|
|
2441
|
+
- **The log level is configurable.** `config/server.json` gains an optional
|
|
2442
|
+
`logging.level` (`fatal`, `error`, `warn`, `info`, `debug`, `trace`, `silent`), also
|
|
2443
|
+
settable through the `LOG_LEVEL` environment variable. Previously it was hardcoded to
|
|
2444
|
+
`info` in development and `warn` otherwise, and `warn` hides every 4xx response and every
|
|
2445
|
+
`log.info` a plugin writes - so a rejected admin action left no trace at all and could not
|
|
2446
|
+
be diagnosed after the event. Defaults are unchanged, so nothing moves unless you set it.
|
|
2447
|
+
An unrecognised value warns and falls back rather than stopping the server booting.
|
|
2448
|
+
|
|
2449
|
+
---
|
|
2450
|
+
|
|
2451
|
+
## [0.52.0] - 2026-08-31
|
|
2452
|
+
|
|
2453
|
+
### Fixed
|
|
2454
|
+
- **Preview renders no longer record analytics page views.** The preview iframe reports its own
|
|
2455
|
+
location to the page-view beacon, and inside a `srcdoc` document that is the literal string
|
|
2456
|
+
`srcdoc` - so opening a preview invented a page called `srcdoc` and inflated the counts for it.
|
|
2457
|
+
Present since the Live Preview tab shipped in 0.51.0. Preview renders now set
|
|
2458
|
+
`window.__CMS_PREVIEW__` before any other injection and the bundled tracker returns immediately
|
|
2459
|
+
when it sees it. This covers all three preview paths - editor preview, draft preview and share
|
|
2460
|
+
links. Real page views are unaffected. **Plugin authors:** any client-side beacon or outbound
|
|
2461
|
+
tracker must check this flag; a preview is not a visit, and it renders at a URL that is not a
|
|
2462
|
+
real page.
|
|
2463
|
+
- **The editor's split-view preview now shows the page's real colours.** It rendered the markdown
|
|
2464
|
+
body as a fragment inside the admin document, where the site's background could never apply -
|
|
2465
|
+
the page background comes from `body { background-color: var(--dm-background) }`, and a fragment
|
|
2466
|
+
has no body to attach it to - while `site.css` cannot be loaded into the admin without restyling
|
|
2467
|
+
the admin itself. The pane is now an iframe carrying the real rendered page, so background,
|
|
2468
|
+
theme, layout preset colours and page-level `theme:` are exactly what the public site shows.
|
|
2469
|
+
- **Live components now work in the split-view preview.** `[countdown]`, `[carousel]`, tooltips,
|
|
2470
|
+
`[timeline]`, forms and effects never initialised there: they are wired by `site.js` against
|
|
2471
|
+
`.page-body`, which the old fragment had no equivalent of, and `site.js` never ran in the admin
|
|
2472
|
+
at all. Only icons and tabs were ever initialised. All of them now behave as they do on the
|
|
2473
|
+
live page.
|
|
2474
|
+
|
|
2475
|
+
### Added
|
|
2476
|
+
- **The editor panes can be resized.** Drag the divider between the writing and preview panes to
|
|
2477
|
+
give either one more room. The ratio persists with the other editor preferences. The handle is
|
|
2478
|
+
keyboard-operable (arrow keys, Shift for larger steps, Home/End for the extremes), double-click
|
|
2479
|
+
restores an even split, and the split is clamped so neither pane can be squeezed away. Below the
|
|
2480
|
+
mobile breakpoint the panes still stack, where a horizontal ratio has no meaning.
|
|
2481
|
+
|
|
2482
|
+
---
|
|
2483
|
+
|
|
2484
|
+
## [0.51.0] - 2026-08-31
|
|
2485
|
+
|
|
2486
|
+
### Added
|
|
2487
|
+
- **The editor's Live Preview shows the whole page, from unsaved changes.** It renders the current
|
|
2488
|
+
editor state through the server's real page pipeline - the same parse and render the public site
|
|
2489
|
+
runs on save - so navbar, footer, theme, menus, `dconfig` and custom CSS are exactly what
|
|
2490
|
+
publishing would produce. It previously pointed at the saved URL, so it showed the last saved
|
|
2491
|
+
version rather than the work in progress. Re-renders shortly after typing stops, and only while
|
|
2492
|
+
the tab is on screen. Width buttons check tablet (1024px) and mobile (390px) layout, and scroll
|
|
2493
|
+
position survives a re-render. The markdown split-view pane is unchanged and still renders the
|
|
2494
|
+
body alone. Backed by `POST /api/pages/preview/full`.
|
|
2495
|
+
- **Revocable share links for unpublished pages.** *Share link* in the Live Preview tab mints a URL
|
|
2496
|
+
that renders one unpublished page to someone with no account - a client, an external designer, a
|
|
2497
|
+
copywriter. The page is named inside the signed token, so a holder cannot repoint the link.
|
|
2498
|
+
Expires after 1 hour, 24 hours, 7 days or 30 days, and can be revoked at any moment: every token
|
|
2499
|
+
carries an id that must still be present and unrevoked in `content/preview-links.json`, because a
|
|
2500
|
+
bare signed token cannot be withdrawn once it is out. Links are dropped automatically when the
|
|
2501
|
+
page is deleted or renamed. Recipients render as anonymous, so menus and role-gated shortcodes
|
|
2502
|
+
show them the least rather than the most. Responses carry `no-store`, `noindex, nofollow` and
|
|
2503
|
+
`Referrer-Policy: no-referrer`, since the credential is in the URL and must not leak through
|
|
2504
|
+
`Referer`. Minting requires `pages.update`, not merely `pages.read` - handing an unauthenticated
|
|
2505
|
+
stranger a working URL is an act of publishing, however narrow.
|
|
2506
|
+
|
|
2507
|
+
---
|
|
2508
|
+
|
|
2509
|
+
## [0.50.0] - 2026-08-31
|
|
2510
|
+
|
|
2511
|
+
### Added
|
|
2512
|
+
- **Draft pages can be previewed on the public site.** A page with `status: draft` still returns
|
|
2513
|
+
404 to the public, but a signed-in user whose role holds `pages.read` is now served the real
|
|
2514
|
+
page - real layout, navbar, footer, theme, menus and custom CSS - at its real URL. Designers and
|
|
2515
|
+
developers can verify a page before it is released instead of guessing from the editor's
|
|
2516
|
+
body-only preview. A draft banner pinned to the bottom of the viewport names the viewer, links
|
|
2517
|
+
to the page in the admin editor, and offers a one-click Publish. Nothing leaks: an anonymous
|
|
2518
|
+
visitor, or a signed-in user without the permission, gets the same 404 as a page that does not
|
|
2519
|
+
exist. Draft renders bypass the response cache entirely (`Cache-Control: no-store, private`) so
|
|
2520
|
+
a preview can never be replayed to the public, carry `X-Robots-Tag: noindex, nofollow`, stay out
|
|
2521
|
+
of `sitemap.xml`, and emit no `content:pageViewed` hook, so previewing does not pollute
|
|
2522
|
+
analytics. A disabled project still 404s for everyone, previewers included.
|
|
2523
|
+
|
|
2524
|
+
### Fixed
|
|
2525
|
+
- **Role-gated pages were unreachable from a browser.** `visibility:` was only ever enforceable
|
|
2526
|
+
against a client that could set an `Authorization` header, because the token lived in `S` storage
|
|
2527
|
+
and no cookie was ever issued - so a signed-in person clicking a link to a page gated to their own
|
|
2528
|
+
role got a 403. Login now also sets a `dm_session` cookie (HttpOnly, SameSite=Lax, Secure over
|
|
2529
|
+
https, expiring with the access token), and the public renderer accepts either channel. The
|
|
2530
|
+
cookie is read **only** when rendering public pages; every API route stays Bearer-only, so it
|
|
2531
|
+
carries no write authority and the CSRF surface is unchanged. Logout clears it.
|
|
2532
|
+
|
|
2533
|
+
---
|
|
2534
|
+
|
|
2535
|
+
## [0.49.2] - 2026-08-29
|
|
2536
|
+
|
|
2537
|
+
### Fixed
|
|
2538
|
+
- **The floating panel's hamburger button was empty.** It asked the icon set for `bars`, which is
|
|
2539
|
+
not an icon - that name belongs to Domma's chart renderer. The scanner marked the span processed
|
|
2540
|
+
and drew nothing, so every collapsible panel since 0.49.0 had an invisible control: the button
|
|
2541
|
+
worked, but there was nothing to see or aim at. It is now drawn in CSS from one element and its
|
|
2542
|
+
two pseudo-elements, the same shape Domma uses for `.navbar-toggle-icon`, so it depends on no
|
|
2543
|
+
icon set at all. Both render paths lose their `data-icon` reference.
|
|
2544
|
+
|
|
2545
|
+
---
|
|
2546
|
+
|
|
2547
|
+
## [0.49.1] - 2026-08-29
|
|
2548
|
+
|
|
2549
|
+
### Fixed
|
|
2550
|
+
- **A floating navbar's Collapsible and Movable did nothing.** The panel chrome - drag grip, title
|
|
2551
|
+
and hamburger - was only ever drawn server-side by `buildFloatChrome()`, which runs for the
|
|
2552
|
+
`[menu]` shortcode and overlay panels. The public navbar is built in the browser by Domma from
|
|
2553
|
+
`window.__CMS_NAV__` and never passes through that code, so it arrived with the behaviour
|
|
2554
|
+
attributes and no controls at all: `wireCollapse`/`wireDrag` found no toggle and no handle and
|
|
2555
|
+
returned silently. `public/js/menu-float.js` now builds the bar itself when a floating panel
|
|
2556
|
+
declares the behaviour but has no chrome, so the navbar, the shortcode and an overlay all behave
|
|
2557
|
+
the same.
|
|
2558
|
+
- **"Start collapsed" was ignored on a floating navbar** for the same reason - the initial
|
|
2559
|
+
`is-collapsed` class is stamped by the server renderer. `applyFloat()` now carries the author's
|
|
2560
|
+
default across as `data-float-collapsed`, and the module normalises it to the class on init.
|
|
2561
|
+
- **Collapsing hid the wrong thing on a navbar.** The rule targeted the panel's direct `<ul>`,
|
|
2562
|
+
which a server-rendered menu has and the navbar does not - its body is `.navbar-container`. It
|
|
2563
|
+
now hides everything except the bar, and restates that at id specificity, since a vertical rail
|
|
2564
|
+
sets `#site-navbar.dm-nav-vertical .navbar-container { display: flex }` and outranked the
|
|
2565
|
+
class-only rule. A collapsed navbar was left full size behind the hamburger.
|
|
2566
|
+
- **The "transparent" menu variant was not transparent.** The Menus editor has offered it since
|
|
2567
|
+
the variant dropdown existed, but nothing ever implemented it - Domma ships `.navbar-dark` and
|
|
2568
|
+
`.navbar-light` only, so `navbar-transparent` matched no rule. It now clears the background,
|
|
2569
|
+
border and shadow, with selectors specific enough to beat the `var(--dm-surface)` that
|
|
2570
|
+
`.dm-menu-floating` paints behind a floating panel. The collapsed hamburger keeps a surface of
|
|
2571
|
+
its own, so a transparent panel does not mean an invisible button.
|
|
2572
|
+
|
|
2573
|
+
---
|
|
2574
|
+
|
|
2575
|
+
## [0.49.0] - 2026-08-29
|
|
2576
|
+
|
|
2577
|
+
### Added
|
|
2578
|
+
- **Floating menu panels can now be collapsed, moved and resized.** A menu with
|
|
2579
|
+
`position: floating` was a fixed decal on the page - wherever the author pinned it was where it
|
|
2580
|
+
stayed. Its `float` block takes four new flags: `collapsible` (a hamburger folds the panel down
|
|
2581
|
+
to a single round button), `collapsed` (start folded), `movable` (drag the panel by its bar) and
|
|
2582
|
+
`resizable` (drag its bottom-right corner), plus `title` for the bar's label. Any of the first
|
|
2583
|
+
two or `movable` makes the server draw a chrome bar - drag grip, title, hamburger - and
|
|
2584
|
+
`public/js/menu-float.js` wires the behaviour. All four are checkboxes on the Menus editor's
|
|
2585
|
+
Behaviour tab.
|
|
2586
|
+
- **The config is a starting point, not a state.** Where a visitor drags a panel, what size they
|
|
2587
|
+
leave it and whether they folded it are stored per visitor under `dm_menu_float_<slug>` and win
|
|
2588
|
+
over `float` on the next load; double-clicking the bar throws that away and restores the
|
|
2589
|
+
authored pinning. Nothing about one visitor's layout reaches the server or another visitor.
|
|
2590
|
+
- A panel keeps tracking its `anchor` until it is actually moved or resized - only then is its
|
|
2591
|
+
right/bottom/transform pinning converted to plain left/top, so an untouched panel still
|
|
2592
|
+
re-pins itself as the viewport changes. Once freed it is clamped to stay reachable on screen.
|
|
2593
|
+
- **Menus can now contain spacers.** `{"type": "spacer"}` joins `{"type": "separator"}` as a
|
|
2594
|
+
structural item: blank room with no line, no text and no URL. `size` is `xs|sm|md|lg|xl`
|
|
2595
|
+
(mapped to the theme's `--dm-space-*` tokens, default `md`) or `flex`, which claims all the
|
|
2596
|
+
leftover room and so pushes everything after it to the far end - the right of a navbar, the foot
|
|
2597
|
+
of a vertical rail or the admin sidebar. An **Add spacer** button and a per-row size picker sit
|
|
2598
|
+
beside the existing separator controls in the Menus editor.
|
|
2599
|
+
- Spacers work in every render path: the `[menu]` shortcode, floating and overlay panels, the
|
|
2600
|
+
public navbar, the footer and the admin sidebar. The axis follows the menu - height in a
|
|
2601
|
+
vertical list, width in a horizontal one - and they are `aria-hidden`, so they never read as
|
|
2602
|
+
empty items.
|
|
2603
|
+
|
|
2604
|
+
### Changed
|
|
2605
|
+
- Below 768px floating panels already return to the document flow, so moving and resizing are
|
|
2606
|
+
inert there; collapsing still works, which is the hamburger behaviour a phone wants anyway.
|
|
2607
|
+
- The markdown sanitiser now allows `aria-hidden` on any element. It was stripping the attribute
|
|
2608
|
+
from spacers and the floating panel's drag grip, leaving decorative markup announced as empty
|
|
2609
|
+
items. Presentational only - `on*` handlers and `javascript:` URLs are still stripped globally.
|
|
2610
|
+
|
|
2611
|
+
---
|
|
2612
|
+
|
|
2613
|
+
## [0.48.0] - 2026-08-29
|
|
2614
|
+
|
|
2615
|
+
### Added
|
|
2616
|
+
- **Site Settings > Footer can now show or hide the "Reduce motion" switch.** The footer's
|
|
2617
|
+
accessibility switch was unconditional - every site got it whether or not it suited the design.
|
|
2618
|
+
A new checkbox on the Footer tab writes `footer.showMotionToggle` to `site.json`; the renderer
|
|
2619
|
+
already spreads the whole `footer` block onto `window.__CMS_SITE__`, so `public/js/site.js` gates
|
|
2620
|
+
the control on it.
|
|
2621
|
+
- The key **defaults to shown**. `site.js` gates on `!== false`, so an absent key behaves exactly
|
|
2622
|
+
as before and no existing site loses its switch on update.
|
|
2623
|
+
- Hiding the switch removes the control, not the capability: a visitor who already flipped it keeps
|
|
2624
|
+
their stored `domma:reduced_motion` preference, and anyone whose browser sends
|
|
2625
|
+
`prefers-reduced-motion: reduce` still gets the calmer site - they just cannot override it from
|
|
2626
|
+
the footer. Documented in `docs/configuration.md` under a new Footer section.
|
|
2627
|
+
|
|
2628
|
+
---
|
|
2629
|
+
|
|
2630
|
+
## [0.47.2] - 2026-08-29
|
|
2631
|
+
|
|
2632
|
+
### Fixed
|
|
2633
|
+
- **Slideovers now scroll instead of clipping.** Domma sets `overflow: hidden` inline on the
|
|
2634
|
+
slideover panel, so whichever wrapper holds the content is the only place overflowing content
|
|
2635
|
+
can scroll. Content projected as a *sibling* of `.dm-slideover-body` - the light-DOM mount the
|
|
2636
|
+
Insert dialogs use so their inputs and buttons stay interactive - had padding but no overflow
|
|
2637
|
+
handling, so anything taller than the viewport was simply cut off with no way to reach it. The
|
|
2638
|
+
embed builder, the effects builder and the menu item editor were all affected. That wrapper is
|
|
2639
|
+
now a scroll container in its own right.
|
|
2640
|
+
- The empty `.dm-slideover-body` those dialogs leave behind carried `flex: 1` and so claimed half
|
|
2641
|
+
the panel height, squashing the real content into the other half. An empty body now takes no
|
|
2642
|
+
space at all.
|
|
2643
|
+
- Both scrolling wrappers get a thin, themed scrollbar (`--dm-border` thumb) rather than the
|
|
2644
|
+
browser default, matching the admin sidebar, and `overscroll-behavior: contain` so scrolling
|
|
2645
|
+
past the end of a slideover no longer scrolls the page behind it.
|
|
2646
|
+
- The public site's `[slideover]` body was hardened the same way: explicit `min-height: 0`,
|
|
2647
|
+
overflow on both axes so a wide table or code block scrolls rather than being clipped, and the
|
|
2648
|
+
same scrollbar treatment.
|
|
2649
|
+
|
|
2650
|
+
---
|
|
2651
|
+
|
|
2652
|
+
## [0.47.1] - 2026-08-28
|
|
2653
|
+
|
|
2654
|
+
### Fixed
|
|
2655
|
+
- **Shortcodes nested inside a container lost every attribute.** `[text size="lg" color="red"]`
|
|
2656
|
+
inside a `[tab]`, or inside a `[card]` inside a `[hero]`, rendered unstyled; `[button href="/x"]`
|
|
2657
|
+
in the same position came out with `href="true"`. Containers hand their nested body to marked,
|
|
2658
|
+
and marked HTML-escapes the quotes in any `[tag attr="x"]` still sitting in the text, so the
|
|
2659
|
+
tag's own processor received `attr="x"` and every value missed its lookup.
|
|
2660
|
+
- The top-level pipeline has always avoided this by running the inline tags (badge, text, button,
|
|
2661
|
+
link, cta) before the container tags. Nested bodies did not: `processHeroBlocks` ran
|
|
2662
|
+
`processCardBlocks` first and the other containers never ran the inline pass at all. They now
|
|
2663
|
+
share one `processInlineShortcodes()` helper that mirrors the top-level order, applied at every
|
|
2664
|
+
site that renders a nested body.
|
|
2665
|
+
- Seven contexts were affected and are now covered by tests: hero > card, tabs > tab,
|
|
2666
|
+
tabs > tab > card, accordion > item, carousel > slide, timeline > event and listgroup > item.
|
|
2667
|
+
Directly nested cases (a tag straight inside `[card]`, `[hero]`, `[banner]`, `[center]`,
|
|
2668
|
+
`[slideover]` or `[grid]`) were already correct and stay that way.
|
|
2669
|
+
|
|
2670
|
+
---
|
|
2671
|
+
|
|
2672
|
+
## [0.47.0] - 2026-08-28
|
|
2673
|
+
|
|
2674
|
+
### Changed
|
|
2675
|
+
- **One shortcode dialog for every entry point.** Inserting a link used to produce three
|
|
2676
|
+
different things depending on where you started: the toolbar's Insert menu emitted
|
|
2677
|
+
`[link href]text[/link]`, the right-click menu's `Insert…` dropped the registry template
|
|
2678
|
+
`[link href label /]`, Ctrl+K wrote a Markdown link or a `[button]`, and editing an
|
|
2679
|
+
existing tag happened in a 210px popup. The two Insert menus were separately maintained
|
|
2680
|
+
lists as well - a hardcoded array of 25 items in `markdown-toolbar.js` against a
|
|
2681
|
+
registry-generated list in the context menu - so they had already drifted apart by six
|
|
2682
|
+
shortcodes.
|
|
2683
|
+
- Both menus are now generated from the shortcode registry through `shortcode-insert.js`,
|
|
2684
|
+
and every insert opens the same generated slideover (`shortcode-form.js`): fields built
|
|
2685
|
+
from the registry's attribute list, repeatable rows for container children (tabs, grid
|
|
2686
|
+
columns, accordion items, carousel slides, list-group items), and a live preview of the
|
|
2687
|
+
exact snippet before it is inserted. `Edit <tag>…` on the right-click menu opens the same
|
|
2688
|
+
form for an existing tag; the inline quick-toggles stay where they were for one-click
|
|
2689
|
+
changes.
|
|
2690
|
+
- The 22 hand-written insert dialogs in `page-editor.js` are gone (about 1,500 lines),
|
|
2691
|
+
along with the toolbar's parallel `on*(cb)` registration surface. Four dialogs that do
|
|
2692
|
+
more than fill in attributes remain and are registered with the insert router instead:
|
|
2693
|
+
the media picker, the card builder, the timeline builder and the scribe composer, plus
|
|
2694
|
+
the lazily loaded component gallery, embed builder and colour picker.
|
|
2695
|
+
- Consequences worth knowing: the Insert menus now also list Row, Slideover, Centre,
|
|
2696
|
+
Countdown, Table and Menu, which only the right-click menu offered before; every form
|
|
2697
|
+
exposes the full attribute set from the registry rather than the subset its old dialog
|
|
2698
|
+
happened to implement; block-level snippets are given their own line when inserted
|
|
2699
|
+
mid-paragraph; and editing a tag writes back only the attributes actually changed, so
|
|
2700
|
+
opening a form and pressing Apply leaves the tag byte-for-byte as it was.
|
|
2701
|
+
- The Link form offers both spellings - a Markdown link (still the default, as Ctrl+K
|
|
2702
|
+
always produced) and the `[link]` shortcode - and falls back to the shortcode as soon as
|
|
2703
|
+
an attribute Markdown cannot carry is set. The preview shows which one will be inserted.
|
|
2704
|
+
Inserting a *button* is now the Button entry rather than a checkbox inside the link
|
|
2705
|
+
dialog.
|
|
2706
|
+
- The Style Text dialog's live styled preview survives the move: a registry entry can name
|
|
2707
|
+
a rendered preview (`preview: 'text'`), and the form shows the styled sample above the
|
|
2708
|
+
snippet. Its `point-size` and `style` attributes were added to the registry too, so
|
|
2709
|
+
nothing that dialog could write is lost.
|
|
2710
|
+
- The `[text]` attribute → CSS mapping now lives in `public/js/text-style.mjs` and is
|
|
2711
|
+
imported by both `server/services/markdown.js` and the editor preview, the same way
|
|
2712
|
+
`collection-export.mjs` is shared. The preview is no longer an approximation of the
|
|
2713
|
+
rendered output - it is computed by the code that renders it, and a test asserts the two
|
|
2714
|
+
agree. Two consequences of the shared mapping: `bold="false"` / `italic="false"` now mean
|
|
2715
|
+
off rather than on (the word-match they used to hit read them as on), and a double quote
|
|
2716
|
+
in a free-form value can no longer break out of the generated `style` attribute.
|
|
2717
|
+
- Inserting `[text]` starts unstyled, as its dialog always did - the registry template's
|
|
2718
|
+
style attributes are a syntax example, not a default, so they no longer prefill the form.
|
|
2719
|
+
- The toolbar-anchored icon and spacer pickers are replaced by the form's icon and number
|
|
2720
|
+
fields, with the layout's configured spacer height still supplying the default.
|
|
2721
|
+
|
|
2722
|
+
---
|
|
2723
|
+
|
|
2724
|
+
## [0.46.4] - 2026-08-22
|
|
2725
|
+
|
|
2726
|
+
### Fixed
|
|
2727
|
+
- **Hover-to-open nav dropdowns closed on the way to themselves.** With a menu's
|
|
2728
|
+
`appearOnHover` on, `site.js` reimplemented the behaviour with `mouseenter`/`mouseleave`
|
|
2729
|
+
toggling Domma's `.open` class directly. `.navbar-dropdown-menu` hangs 4px below its
|
|
2730
|
+
toggle, and that 4px belongs to neither box, so `mouseleave` fired the instant the
|
|
2731
|
+
pointer entered it and closed the menu with no grace period. Whether it survived came
|
|
2732
|
+
down to whether the pointer happened to jump the gap between two events - open on a
|
|
2733
|
+
quick flick, gone on a normal one. It also fought the navbar's own click toggle, so
|
|
2734
|
+
clicking a menu to keep it open was undone by the next mousemove.
|
|
2735
|
+
- Domma's navbar has had its own `appearOnHover` since 0.38.0 - delegated so it survives
|
|
2736
|
+
a re-render, reconciling the whole open chain so flyouts keep their parents open, and
|
|
2737
|
+
holding a grace period on leave. The option is now passed through and the local
|
|
2738
|
+
reimplementation deleted. Reproduced and verified in headless Chrome against a running
|
|
2739
|
+
site: slow crossing of the gap fails on the old code and passes on the new.
|
|
2740
|
+
|
|
2741
|
+
### Changed
|
|
2742
|
+
- **Pinned `domma-js` to 0.42.0**, and moved the vendored Domma Tools pair with it - both
|
|
2743
|
+
banners read v0.42.0. That release carries the matching fixes: `hoverCloseDelay` on the
|
|
2744
|
+
navbar (250ms), a click that *pins* a dropdown open rather than being reconciled away,
|
|
2745
|
+
a toggle that closes its siblings, and `Navbar.collapseAt` moved from 768 to **993** to
|
|
2746
|
+
match the breakpoint `elements.css` switches the bar to its desktop layout at. Sites
|
|
2747
|
+
that restyled the collapse breakpoint should now pass `collapseAt` explicitly.
|
|
2748
|
+
|
|
2749
|
+
---
|
|
2750
|
+
|
|
2751
|
+
## [0.46.3] - 2026-08-22
|
|
2752
|
+
|
|
2753
|
+
### Changed
|
|
2754
|
+
- **Pinned `domma-js` to 0.41.0**, and moved the vendored Domma Tools pair with it.
|
|
2755
|
+
npm does not ship `domma-tools.*`, so without that copy `/dist/domma/domma.min.js`
|
|
2756
|
+
would have gone to 0.41.0 while `admin/dist/domma/domma-tools.min.js` sat on 0.40.0,
|
|
2757
|
+
and the tools bundle is built against a specific core. Both banners read v0.41.0.
|
|
2758
|
+
|
|
2759
|
+
### Fixed
|
|
2760
|
+
- **The navbar brand was invisible on every dark theme.** `.navbar-dark` painted the
|
|
2761
|
+
bar from `--dm-background` and the brand from `--dm-text-inverse`, which resolve to
|
|
2762
|
+
the same slate in every dark theme - 1.00 contrast. `site.css` compounded it by
|
|
2763
|
+
re-applying the same token at ID specificity. Fixed at source in domma-js 0.41.0
|
|
2764
|
+
along with three more rules of the same shape (`.card-primary .card-body` at 1.19,
|
|
2765
|
+
`.navbar-link.active` at 1.72, and the `.navbar-dark .navbar-dropdown-*` overrides
|
|
2766
|
+
at 1.00), so the interim overrides carried here have been **removed** rather than
|
|
2767
|
+
left to drift from the fix they duplicated.
|
|
2768
|
+
- **`.navbar-actions` still needs its own rule** - the site-search icon is a CMS
|
|
2769
|
+
addition, not a Domma element, so the upstream token fix does not reach it. It had
|
|
2770
|
+
the same defect for the same reason.
|
|
2771
|
+
- **The seven generic hero gradients ignored the theme.** They were fixed pastels with
|
|
2772
|
+
fixed dark text, so `gradient-forest` painted a mint band across the top of a navy
|
|
2773
|
+
site - the loudest reason the seed pages read as unthemed. The hue now mixes into
|
|
2774
|
+
`--dm-background` with `--dm-text` over it, so the band follows the theme in both
|
|
2775
|
+
modes. Worst case across 15 themes and 7 hues: 9.65. `night` and the 26
|
|
2776
|
+
theme-specific classes are unchanged.
|
|
2777
|
+
- **`[card variant="primary"]` header** keeps the primary identity as an 18% tint plus
|
|
2778
|
+
the accent border rather than a solid fill, because no single token reads reliably on
|
|
2779
|
+
`--dm-primary` across the theme set.
|
|
2780
|
+
|
|
2781
|
+
All eight seed pages now scan clean at the 4.5 AA threshold with computed styles in a
|
|
2782
|
+
real browser. One pre-existing miss is left alone: inline `<code>` on /resources at
|
|
2783
|
+
3.94, which is a theme-palette issue rather than a token misuse.
|
|
2784
|
+
|
|
2785
|
+
---
|
|
2786
|
+
|
|
2787
|
+
## [0.46.2] - 2026-08-22
|
|
2788
|
+
|
|
2789
|
+
### Fixed
|
|
2790
|
+
- **Vertical navbar layout and rail rendering.** Fixed a bug where `Domma.elements.navbar()`
|
|
2791
|
+
overwrote `#site-navbar`'s class list, stripping `.dm-nav-vertical` on initialisation
|
|
2792
|
+
and causing vertical menus to render as standard horizontal navbars.
|
|
2793
|
+
- **Vertical navbar container styling.** Added column flex styling to `.navbar-container`
|
|
2794
|
+
inside `#site-navbar.dm-nav-vertical` so that brand identity and navigation items
|
|
2795
|
+
stack vertically inside the rail instead of laying out in a compressed horizontal row.
|
|
2796
|
+
- **Server-rendered navbar classes.** Stamped the initial vertical navbar class directly
|
|
2797
|
+
onto `<nav id="site-navbar">` in `page.html` and `renderer.js` to match the body gutter
|
|
2798
|
+
on first paint.
|
|
2799
|
+
- **Floating navbar configuration and body gutter handling.** Fixed an issue where `navMenu.float`
|
|
2800
|
+
was omitted from `__CMS_NAV__` in `renderer.js`, and prevented vertical floating navbars
|
|
2801
|
+
from erroneously applying a full-height body gutter (`dm-vnav`).
|
|
2802
|
+
- **Asset cache busting.** Bumped cache-busting version query parameters on `site.css` and
|
|
2803
|
+
`site.js` in `page.html` to ensure updated stylesheets and scripts load immediately.
|
|
2804
|
+
|
|
2805
|
+
---
|
|
2806
|
+
|
|
2807
|
+
## [0.46.1] - 2026-08-21
|
|
2808
|
+
|
|
2809
|
+
### Changed
|
|
2810
|
+
- **The Domma pin moves to 0.40.0.** Two ancestor binding-context names arrive
|
|
2811
|
+
with it - `$parents` and `$parentContext` - along with a warning for a nested
|
|
2812
|
+
`data-each` that previously failed quietly, and a binding write to a frozen
|
|
2813
|
+
target that now warns instead of throwing. Nothing in the CMS uses them yet;
|
|
2814
|
+
the pin moves so the admin is not served a core two minor versions behind the
|
|
2815
|
+
one it is documented against. The vendored Domma Tools pair
|
|
2816
|
+
(`admin/dist/domma/domma-tools.min.js` and its CSS) moves with it, because npm
|
|
2817
|
+
does not ship `domma-tools.*` and the tools bundle is built against a specific
|
|
2818
|
+
core - leaving it on 0.39.2 while the core went to 0.40.0 is exactly the
|
|
2819
|
+
mismatch that breaks the admin.
|
|
2820
|
+
|
|
2821
|
+
---
|
|
2822
|
+
|
|
2823
|
+
## [0.46.0] - 2026-08-21
|
|
2824
|
+
|
|
2825
|
+
### Fixed
|
|
2826
|
+
- **`[form]` on a page renders through `public/js/site.js`, and that renderer
|
|
2827
|
+
ignored the grid on every step.** The shortcode does not emit `data-form` -
|
|
2828
|
+
it emits `data-form-inline` with the form definition encoded into the
|
|
2829
|
+
attribute, hydrated by a separate implementation in `site.js` that called
|
|
2830
|
+
`F.wizard(el, {schema, onSubmit})` with no `layout` and no `columns`. It
|
|
2831
|
+
carried its own copy of step splitting, its own `columns || 1` and
|
|
2832
|
+
`layout || 'stacked'` defaults, decorated only the first step, and injected
|
|
2833
|
+
the spam honeypot into a step the visitor never submits from. It now uses
|
|
2834
|
+
the shared splitter and matches the other two renderers.
|
|
2835
|
+
|
|
2836
|
+
- **Multi-page forms ignored the form's grid setting.** `Domma.forms.wizard()`
|
|
2837
|
+
forwards its leftover options to the form renderer for each step, and the
|
|
2838
|
+
renderer only lays out a grid when it is told `layout: 'grid'`. Neither the
|
|
2839
|
+
public renderer nor the builder's preview passed `layout` or `columns`, so
|
|
2840
|
+
every page of every wizard rendered stacked whatever the author chose. The
|
|
2841
|
+
single-page path passed both, which is why only wizards were affected.
|
|
2842
|
+
|
|
2843
|
+
- **Everything applied after the first render was lost on page two.** The
|
|
2844
|
+
wizard rebuilds its container with `innerHTML` on each navigation, and the
|
|
2845
|
+
CMS decorated the rendered form exactly once. From page two onwards: date
|
|
2846
|
+
fields fell back to plain text inputs, icon and image fields lost their
|
|
2847
|
+
Browse buttons and previews, spacers vanished, and conditional logic was
|
|
2848
|
+
bound to elements that no longer existed. Each page is now decorated as it
|
|
2849
|
+
is rendered.
|
|
2850
|
+
|
|
2851
|
+
- **Conditional logic could not see answers from an earlier page.** The logic
|
|
2852
|
+
runtime reads values out of the DOM, and a wizard only has one page in the
|
|
2853
|
+
DOM at a time, so a rule on page two testing an answer from page one
|
|
2854
|
+
evaluated against nothing and silently decided the wrong way.
|
|
2855
|
+
`FormLogicRuntime` takes a `baseValues` option; the wizard seeds it with the
|
|
2856
|
+
answers collected so far.
|
|
2857
|
+
|
|
2858
|
+
- **Files chosen before the last page were dropped.** The submitter collected
|
|
2859
|
+
`<input type="file">` elements from the DOM, which by then held only the
|
|
2860
|
+
final page. A CV attached on page one never left the browser. File choices
|
|
2861
|
+
are now retained as the visitor advances.
|
|
2862
|
+
|
|
2863
|
+
- **Spam protection did nothing on multi-page forms.** The honeypot pair was
|
|
2864
|
+
injected into page one's form and destroyed on the first navigation, so the
|
|
2865
|
+
submission arrived with both values empty - which the server reads as "not a
|
|
2866
|
+
bot". Both are now present on the page that submits, carrying the *original*
|
|
2867
|
+
timestamp rather than a fresh one (re-stamping per page would measure only
|
|
2868
|
+
the last page's dwell time, and the server discards anything submitted
|
|
2869
|
+
within 2s of the stamp).
|
|
2870
|
+
|
|
2871
|
+
- **A form with its own field named `website` discarded every submission.**
|
|
2872
|
+
The honeypot decoy is an input named `website`, and the submitter read it
|
|
2873
|
+
back by name - picking up the visitor's real answer, sending it as `_hp`,
|
|
2874
|
+
and having the server treat the submission as a bot: success message shown,
|
|
2875
|
+
nothing stored. Silent data loss on single-page forms too, not only wizards.
|
|
2876
|
+
The decoy now steps aside to `_website` when the name is taken, and is read
|
|
2877
|
+
from inside its own wrapper rather than by name.
|
|
2878
|
+
|
|
2879
|
+
- **A page break at the end produced an empty final page** holding nothing but
|
|
2880
|
+
the button, and one at the start produced an empty first page. The builder's
|
|
2881
|
+
preview dropped the trailing one but the live form kept it, so the author
|
|
2882
|
+
and the visitor saw different forms.
|
|
2883
|
+
|
|
2884
|
+
- **A full-width submit button only worked in the preview.** The builder
|
|
2885
|
+
applied `col-span-full` to its own preview and the public renderer never
|
|
2886
|
+
did, so the setting looked right to the author and did nothing for the
|
|
2887
|
+
visitor.
|
|
2888
|
+
|
|
2889
|
+
### Changed
|
|
2890
|
+
- **Grid is now the default layout for forms.** Forms were created as
|
|
2891
|
+
`stacked`, so a wizard that correctly honoured its layout setting still had
|
|
2892
|
+
no grid to honour. Grid is applied wherever a layout is not explicitly set:
|
|
2893
|
+
new forms from both creation paths, the builder's Layout control, and the
|
|
2894
|
+
renderer reading a form saved before the change. An explicit Stacked or
|
|
2895
|
+
Inline choice is still respected.
|
|
2896
|
+
- **The Columns control only offers what the renderer can produce.** Domma
|
|
2897
|
+
emits `grid-cols-2` or `grid-cols-3` and nothing else, but the control
|
|
2898
|
+
accepted 1 to 6 - four of the six choices quietly rendered as two columns.
|
|
2899
|
+
It is now a 2/3 select, and stored values outside that range are clamped.
|
|
2900
|
+
- Wizard step splitting moved to `public/js/form-steps.mjs`, imported by all
|
|
2901
|
+
three renderers - the `[form]` embed hydrator, the `data-form` renderer and
|
|
2902
|
+
the builder preview. Each carried its own copy, and they had drifted apart.
|
|
2903
|
+
- The wizard's final button honours the form's Submit Text setting.
|
|
2904
|
+
- The five stored forms still marked `stacked` from the old default
|
|
2905
|
+
(`categories`, `comments`, `notes`, `posts`, `to-do`) were switched to grid.
|
|
2906
|
+
- `site.js` imported `forms.js` under a different cache-busting query string
|
|
2907
|
+
from the one in `page.html`, so a page fetched the module twice and ran its
|
|
2908
|
+
top-level scan twice. Both now use one URL.
|
|
2909
|
+
|
|
2910
|
+
### Added
|
|
2911
|
+
- `docs/guides/multi-page-forms.md` - building wizards with page breaks, and
|
|
2912
|
+
what carries across pages.
|
|
2913
|
+
## [0.45.1] - 2026-08-21
|
|
2914
|
+
|
|
2915
|
+
### Fixed
|
|
2916
|
+
- **Carousels with text slides did not appear at all.** The `[carousel]`
|
|
2917
|
+
shortcode's own default snippet - three `[slide]`s with titles and body text
|
|
2918
|
+
and no images - rendered as a gap between the paragraphs either side of it.
|
|
2919
|
+
The cause is in domma-js: `.carousel-slide-content` was unconditionally
|
|
2920
|
+
`position: absolute`, being the caption overlay for a slide's background
|
|
2921
|
+
image. On a slide with no `<img>` that caption was the only child, so
|
|
2922
|
+
nothing was left in flow, the slide computed to 0px tall, and the track and
|
|
2923
|
+
carousel collapsed with it. Arrows, indicators and slide transitions were
|
|
2924
|
+
all present and working the whole time, on an element with no height - which
|
|
2925
|
+
is why the markup and the JS both looked correct in isolation.
|
|
2926
|
+
|
|
2927
|
+
Fixed in **domma-js 0.39.2**: slide content sits in flow by default and
|
|
2928
|
+
inherits its colour, with the overlay stated as the enhancement for
|
|
2929
|
+
`.carousel-slide:has(> img)`, so a browser without `:has()` puts an image
|
|
2930
|
+
caption under the picture rather than hiding the slide. In-flow content is
|
|
2931
|
+
also padded clear of the arrows and indicator dots, which are positioned
|
|
2932
|
+
over the slide and otherwise sat on top of the words.
|
|
2933
|
+
|
|
2934
|
+
---
|
|
2935
|
+
|
|
2936
|
+
## [0.45.0] - 2026-08-21
|
|
2937
|
+
|
|
2938
|
+
### Fixed
|
|
2939
|
+
- **The page editor could not save.** 0.43.1's un-minifying refactor renamed
|
|
2940
|
+
the router global `R` to `Router` in `page-editor.js` (8 references).
|
|
2941
|
+
`Router` is not a global, so the editor threw `ReferenceError` partway
|
|
2942
|
+
through mounting, and every listener wired after that point - Save, Cancel,
|
|
2943
|
+
the unsaved-changes navigation guard - was never attached. The button did
|
|
2944
|
+
nothing at all. The rename checker (`tools/check-rename.mjs`) could not see
|
|
2945
|
+
it: the mapping IS one-to-one, it just points at a name that does not exist.
|
|
2946
|
+
- **An admin smoke test now runs the editor in a real browser.** Nothing in
|
|
2947
|
+
the suite loaded the admin - node:test has no DOM - so any browser-only
|
|
2948
|
+
error shipped silently, which is how the above got out. The new test boots
|
|
2949
|
+
the server, drives the page editor in headless Chrome, and asserts a save
|
|
2950
|
+
round-trips through the API, failing on any uncaught browser error. Verified
|
|
2951
|
+
against the bug: reintroducing `Router` makes it fail. It skips when no
|
|
2952
|
+
headless Chrome is present (set `DOMMA_CHROME` to point at one).
|
|
2953
|
+
- **A page created inside a project vanished, but kept its URL.** `createPage`
|
|
2954
|
+
built its frontmatter from a fixed whitelist, so `project`, `visibility`,
|
|
2955
|
+
`tags`, `category` and `theme` were all discarded on the FIRST save of a
|
|
2956
|
+
page. A page created from `#/projects/<slug>/pages` therefore landed in
|
|
2957
|
+
core: missing from that project's list and totals, while still owning the
|
|
2958
|
+
path - so re-creating it answered "already exists" for a page the admin
|
|
2959
|
+
could not see anywhere. Create now writes every key through, the same
|
|
2960
|
+
contract `updatePage` has always had.
|
|
2961
|
+
- **Project context survives "New Page".** The per-project list linked to the
|
|
2962
|
+
shared `#/pages/new` route, which dropped the slug. The link now carries
|
|
2963
|
+
`?project=<slug>` and the editor pre-selects it (`carryProjectContext`).
|
|
2964
|
+
The Project field's empty option is "Inherit from URL" and now genuinely
|
|
2965
|
+
inherits: it omits the key so the rootUrl resolver runs, instead of writing
|
|
2966
|
+
`project: null`, which pinned the page to core forever.
|
|
2967
|
+
- **A trailing slash on a project's `rootUrl` no longer switches inheritance
|
|
2968
|
+
off.** The prefix test built `rootUrl + '/'`, so `/shop/` produced `/shop//`
|
|
2969
|
+
and matched nothing: one stray keystroke in the project editor and every
|
|
2970
|
+
page under that project quietly fell through to core. Note that `/` itself
|
|
2971
|
+
stays reserved for core, so a whole-site project inherits nothing by URL and
|
|
2972
|
+
its pages carry an explicit `project:` tag - which is now automatic for
|
|
2973
|
+
pages created from inside a project.
|
|
2974
|
+
- **A create conflict names the project.** `POST /api/pages` returns
|
|
2975
|
+
"A page already exists at /x (project: core)" rather than a bare
|
|
2976
|
+
"already exists".
|
|
2977
|
+
- **Wizard forms lost their conditional logic on every step change.**
|
|
2978
|
+
`F.wizard` replaces the container's innerHTML per step, so the logic
|
|
2979
|
+
runtime's listeners pointed at dead nodes and its hidden classes were gone:
|
|
2980
|
+
stepping to page 2 and back brought every conditionally hidden field back
|
|
2981
|
+
into view. The runtime, date patching, media/icon pickers and the honeypot
|
|
2982
|
+
are all re-applied per step now, in both render paths (`site.js` inline
|
|
2983
|
+
embeds and `forms.js` `[data-form]` targets).
|
|
2984
|
+
- **Next now says what is missing.** An empty required field made the wizard's
|
|
2985
|
+
Next button silently do nothing. A capture-phase guard runs a
|
|
2986
|
+
conditional-aware check (`FormLogicEngine.validateFields`) and, on failure,
|
|
2987
|
+
flags the offending fields, scrolls to the first, and names them in a
|
|
2988
|
+
dialog. Fields whose requirement or visibility is conditional are no longer
|
|
2989
|
+
statically `required` in the blueprint - a field the logic had hidden used
|
|
2990
|
+
to block the form for good.
|
|
2991
|
+
- **Forms stopped collapsing on narrow screens.** Domma's `grid-cols-N` are
|
|
2992
|
+
fixed counts with no built-in collapse, so a 2 or 3 column form kept all its
|
|
2993
|
+
columns down to 320px. Below 700px a CMS form is one column and `col-span-N`
|
|
2994
|
+
collapses with it.
|
|
2995
|
+
- **Radio buttons no longer render as choosers.** Switching a chooser field to
|
|
2996
|
+
Radio buttons / Dropdown / Checkbox group left the chooser's visual config
|
|
2997
|
+
(`variant`, `density`, `columns`, `glow`) on the field, and both renderers
|
|
2998
|
+
promote "a select/radio with a variant" back to a chooser - so every option
|
|
2999
|
+
type looked identical. The builder drops chooser-only keys the moment the
|
|
3000
|
+
type stops being a chooser.
|
|
3001
|
+
|
|
3002
|
+
### Changed
|
|
3003
|
+
- **A project-filtered list now admits what it is hiding.** Entering a list
|
|
3004
|
+
from a project (`#/projects/<slug>/pages`) drops every artefact belonging to
|
|
3005
|
+
another project and said nothing about it: on a site with 14 pages, 10 of
|
|
3006
|
+
which are tagged, the table footer read "10 of 10" while four pages sat one
|
|
3007
|
+
route away. Anything resolving to core was therefore indistinguishable from
|
|
3008
|
+
something that had never been created - the exact reading a freshly created
|
|
3009
|
+
page invites, since it lands in core and still owns its slug. Filtered lists
|
|
3010
|
+
now carry a line above the table naming the count held back, with a link to
|
|
3011
|
+
the unfiltered view. Applied to pages, collections, forms, actions, views,
|
|
3012
|
+
blocks, components, users and API endpoints.
|
|
3013
|
+
- **Admin lists show 25 rows before paginating, not 10.** Every admin table
|
|
3014
|
+
took Domma's default `pageSize: 10`, so a site with 14 pages showed 10 of
|
|
3015
|
+
them and put the rest behind a pagination control. A newly created page
|
|
3016
|
+
sorts to the end (default `sortOrder: 99`), which means it landed on page 2
|
|
3017
|
+
the moment a site passed ten pages - indistinguishable from the page not
|
|
3018
|
+
existing. Applied to all 16 admin list tables; the page-size control is
|
|
3019
|
+
still there for larger sets.
|
|
3020
|
+
- **The admin API is never cached.** Everything under `/api` is served
|
|
3021
|
+
`Cache-Control: no-store`, except the two externally consumable surfaces
|
|
3022
|
+
(`/api/v1/*`, `/api/x/*`) which have their own caching contracts. A stale
|
|
3023
|
+
list is indistinguishable from a missing artefact.
|
|
3024
|
+
- **New forms start as a 2-column grid** instead of a single stacked column,
|
|
3025
|
+
and the column/span controls are visible on mount rather than after the
|
|
3026
|
+
layout select is touched.
|
|
3027
|
+
- **Themes are listed A-Z** in every admin picker, the login theme swatches
|
|
3028
|
+
and the theme-switcher plugin's dots. Admin-chrome palettes sort last as a
|
|
3029
|
+
block.
|
|
3030
|
+
- **Cancel sits clear of Save.** Admin view headers, every admin modal footer
|
|
3031
|
+
and public form button rows moved from a 0.5rem gap to 1rem, with an extra
|
|
3032
|
+
half-rem before the primary action.
|
|
3033
|
+
- **The Form Builder's conditional logic sections are laid out as blocks** -
|
|
3034
|
+
Visibility, Requirement, Validation and Cascade each get a bordered panel,
|
|
3035
|
+
and each "when ... then ..." rule sits on its own tinted strip instead of
|
|
3036
|
+
running together as one stack of selects.
|
|
3037
|
+
- **Em dashes are gone.** 5,800 of them across source, docs, admin copy and
|
|
3038
|
+
demo content, replaced with plain hyphens.
|
|
3039
|
+
|
|
3040
|
+
---
|
|
3041
|
+
|
|
3042
|
+
## [0.44.0] - 2026-08-20
|
|
3043
|
+
|
|
3044
|
+
No functional change over 0.43.1. This release exists to resolve a version
|
|
3045
|
+
number that means two different things.
|
|
3046
|
+
|
|
3047
|
+
`0.43.1` was published to npm from a second environment while the de-mangling
|
|
3048
|
+
work was still local, so the registry's `0.43.1` is the 0.43.0 feature set with
|
|
3049
|
+
the admin views still minified - different code under the same number as the
|
|
3050
|
+
`v0.43.1` tag here, and the registry will not accept a re-publish. `0.43.0` was
|
|
3051
|
+
never published at all; npm goes `0.42.0` → `0.43.1`.
|
|
3052
|
+
|
|
3053
|
+
Publishing as `0.44.0` gives the current tree a number that is free on both
|
|
3054
|
+
sides. The npm package therefore gains, relative to its `0.43.1`:
|
|
3055
|
+
|
|
3056
|
+
- the right-click sort, group, export, copy, print and share-view additions to
|
|
3057
|
+
the public collection context menu, and the `field: value` search syntax
|
|
3058
|
+
(released here as 0.43.0)
|
|
3059
|
+
- readable admin view sources and the setup wizard fix (released here as 0.43.1)
|
|
3060
|
+
|
|
3061
|
+
Nothing published under `0.43.1` is lost - that tarball's contents are an
|
|
3062
|
+
ancestor of this one.
|
|
3063
|
+
|
|
3064
|
+
### Fixed
|
|
3065
|
+
- **Releases no longer carry the build machine's local config.** `config/site.json`
|
|
3066
|
+
is a working file on a development install, so the copy staged into `_publish/`
|
|
3067
|
+
held whatever SMTP host and site URL were last set on the machine cutting the
|
|
3068
|
+
release - every published tarball up to and including 0.43.1 put a LAN address
|
|
3069
|
+
(`192.168.1.27`) on the public registry, and handed anyone installing without
|
|
3070
|
+
the scaffolder a mail host that does not exist for them. `scripts/build.js` now
|
|
3071
|
+
resets `smtp` and `url` to neutral defaults in the staged copy and drops the
|
|
3072
|
+
`.bak` migration leftovers. `config/connections.json` was already excluded by
|
|
3073
|
+
`.npmignore` and never shipped.
|
|
3074
|
+
|
|
3075
|
+
## [0.43.1] - 2026-08-20
|
|
3076
|
+
|
|
3077
|
+
### Fixed
|
|
3078
|
+
- **The setup wizard could not run at all.** `scripts/setup.js` declared
|
|
3079
|
+
`const existingSecret` twice at module scope - once for `JWT_SECRET`, again
|
|
3080
|
+
for `INSTANCE_SECRET` - which is a hard `SyntaxError`, so `npm run setup`
|
|
3081
|
+
failed before printing anything. Present since 2026-04-08 and shipped in
|
|
3082
|
+
every release since; only fresh installs were affected, which is why it went
|
|
3083
|
+
unnoticed.
|
|
3084
|
+
|
|
3085
|
+
### Changed
|
|
3086
|
+
- **Seven admin view files are readable source again.** An accidental in-place
|
|
3087
|
+
minify run in March 2026 overwrote them with their own build output and was
|
|
3088
|
+
committed; five months of work then landed on the damaged versions, so this
|
|
3089
|
+
is a rename-and-reformat pass rather than a revert. `view-editor.js` and
|
|
3090
|
+
`action-editor.js` were single lines of 18,824 and 13,248 bytes; `page-editor.js`
|
|
3091
|
+
carried a 23,541-character line. Identifiers were recovered from the last
|
|
3092
|
+
readable ancestor where one existed, and inferred from behaviour for
|
|
3093
|
+
`block-editor.js`, which has never existed in readable form.
|
|
3094
|
+
|
|
3095
|
+
Every file is proved to be a pure rename of what shipped: `tools/check-rename.mjs`
|
|
3096
|
+
compares token streams and passes only if the two are identical except for a
|
|
3097
|
+
one-to-one identifier mapping. Where the original minifier reused one letter
|
|
3098
|
+
for different kinds of value across scopes, the letter was left alone rather
|
|
3099
|
+
than given a name that would be false in some of its occurrences.
|
|
3100
|
+
|
|
3101
|
+
No behaviour changes. Nothing needed minified source here in the first place:
|
|
3102
|
+
`scripts/build.js` treats `admin/js` and `public/js` as inputs and writes
|
|
3103
|
+
minified copies to `_publish/`.
|
|
3104
|
+
|
|
3105
|
+
### Added
|
|
3106
|
+
- `tools/check-rename.mjs` - verifies that a change to a JavaScript file is a
|
|
3107
|
+
pure identifier rename, for use on any future refactor of untested code.
|
|
3108
|
+
- A `pre-commit` hook that refuses to commit minified output over source,
|
|
3109
|
+
keyed on average bytes per line (healthy source here runs 43-67; the damaged
|
|
3110
|
+
files ran 419-18,824). The existing `commit-msg` hook is now tracked rather
|
|
3111
|
+
than local to one machine, and `npm run setup` enables `core.hooksPath` so a
|
|
3112
|
+
fresh clone gets both.
|
|
3113
|
+
|
|
3114
|
+
---
|
|
3115
|
+
|
|
3116
|
+
## [0.43.0] - 2026-08-19
|
|
3117
|
+
|
|
3118
|
+
### Added
|
|
3119
|
+
- **The right-click menu on collection displays now sorts, groups, exports and
|
|
3120
|
+
copies, not just filters.** Rows are organised into drill-in panels rather
|
|
3121
|
+
than one growing list: filtering stays a single click, while `Sort…`,
|
|
3122
|
+
`Group by…`, `Copy…` and `Export…` open a sub-panel with a Back row. Sort
|
|
3123
|
+
works on every display type and labels its directions by detected value type
|
|
3124
|
+
(`A → Z`, `oldest → newest`, `low → high`). Grouping inserts headings between
|
|
3125
|
+
entries and is offered only where entries render as siblings - cards, list,
|
|
3126
|
+
listgroup, accordion and block; it is hidden on table, timeline and carousel,
|
|
3127
|
+
which re-render or sequence their entries and have nowhere to put a heading.
|
|
3128
|
+
- **Export and copy of what is on screen.** Export writes the filtered and
|
|
3129
|
+
sorted rows as CSV or JSON; Copy puts a single value, a row as JSON, or the
|
|
3130
|
+
whole visible set as a Markdown table on the clipboard. Both are limited to
|
|
3131
|
+
the fields the display actually rendered, so neither can emit a column the
|
|
3132
|
+
page did not show.
|
|
3133
|
+
- **Export permissions in the collection schema**, editable from the collection
|
|
3134
|
+
editor's new "API & Export" tab: `enabled`, `access` (`public` or a role) and
|
|
3135
|
+
`formats`. Absent config means signed-in users only. An existing
|
|
3136
|
+
`[collection exportable="true"]` is honoured as a public opt-in, so no page
|
|
3137
|
+
that already offered export loses it. The gate is a convenience affordance,
|
|
3138
|
+
not an enforcement boundary - entries already ship in the page's `data-ctx`
|
|
3139
|
+
attribute - and `docs/configuration.md` says so plainly.
|
|
3140
|
+
- **`field: value` search syntax** in the menu's search box, with `=`, `!=`,
|
|
3141
|
+
`>` and `<` as well as `:`. Field references match on name or label, case
|
|
3142
|
+
insensitively. Anything that does not name a known field falls back to a
|
|
3143
|
+
plain search, so `10:30` and `Re: proposal` keep working.
|
|
3144
|
+
- **Print, "Copy link to this view", and a unified "Clear all"** that resets
|
|
3145
|
+
filters, sort and grouping together and names what it will clear. Sort and
|
|
3146
|
+
grouping now travel in the URL hash alongside filters, so a shared link
|
|
3147
|
+
restores the whole view; links shared before this release still restore.
|
|
3148
|
+
|
|
3149
|
+
### Fixed
|
|
3150
|
+
- Object-valued collection fields exported as the literal string
|
|
3151
|
+
`[object Object]`. CSV export now shares one dialect with the browser,
|
|
3152
|
+
including a UTF-8 BOM so Excel reads accented characters correctly.
|
|
3153
|
+
- Values beginning with a tab or carriage return bypassed CSV formula-injection
|
|
3154
|
+
sanitising, because `Number()` trims whitespace and stripped the very
|
|
3155
|
+
character that flagged them. Only `+` and `-` may now take the numeric
|
|
3156
|
+
exemption.
|
|
3157
|
+
- `>` and `<` filters treated two dates in the same calendar year as equal:
|
|
3158
|
+
`parseFloat` read `2026-06-15` as the bare year and short-circuited before
|
|
3159
|
+
the date comparison. The comparison ladder now tries whole-string numbers,
|
|
3160
|
+
then dates, then prefix numbers, then strings.
|
|
3161
|
+
- The export `access` role was not enforced. Every role behaved identically
|
|
3162
|
+
because the check only asked whether anyone was signed in.
|
|
3163
|
+
- `CLAUDE.md` referenced `server/services/userTypes.js`, which does not exist;
|
|
3164
|
+
those functions live in `roles.js`.
|
|
3165
|
+
|
|
3166
|
+
---
|
|
3167
|
+
|
|
3168
|
+
## [0.42.0] - 2026-08-17
|
|
3169
|
+
|
|
3170
|
+
### Added
|
|
3171
|
+
- **Menus can be placed anywhere, not just the header, footer and admin
|
|
3172
|
+
sidebar.** A new core `overlay` slot: any menu bound to it renders on
|
|
3173
|
+
matching pages positioned by its own `float` config - no navbar or footer
|
|
3174
|
+
region, and no `[menu]` shortcode in the page body. Unlike the other slots it
|
|
3175
|
+
is not one-menu-wins, so a page can carry several overlays at once (a left
|
|
3176
|
+
rail and a corner widget, say); mapping a menu to `overlay` in
|
|
3177
|
+
`menu-locations.json` makes it site-wide instead. Overlays compose with
|
|
3178
|
+
everything already there - bindings decide *which pages*, `float` decides
|
|
3179
|
+
*where on the screen*, `orientation` decides *how it flows* - and an overlay
|
|
3180
|
+
left with no visible items after role filtering is dropped rather than
|
|
3181
|
+
leaving an empty box on the page. The slot appears automatically in the
|
|
3182
|
+
menu editor's Binding tab and in Menu locations.
|
|
3183
|
+
|
|
3184
|
+
### Changed
|
|
3185
|
+
- Menu → HTML now lives in one place (`server/services/menuRender.js`), shared
|
|
3186
|
+
by the `[menu]` shortcode and overlay rendering, so the same menu produces
|
|
3187
|
+
identical markup however it reaches the page.
|
|
3188
|
+
|
|
3189
|
+
## [0.41.1] - 2026-08-17
|
|
3190
|
+
|
|
3191
|
+
### Added
|
|
3192
|
+
- **Enabling or disabling a plugin restarts the server.** A plugin's routes,
|
|
3193
|
+
hooks and admin views only register at boot, so a toggle used to be inert
|
|
3194
|
+
until someone restarted by hand. The process now exits cleanly after the
|
|
3195
|
+
toggle is saved and the supervisor brings it back, with the admin UI waiting
|
|
3196
|
+
and reporting when it is live. Guarded two ways: only where a supervisor is
|
|
3197
|
+
detected (pm2's `pm_id`/`PM2_HOME`, or the fleet manager's IPC channel - a
|
|
3198
|
+
bare `node server/server.js` is told to restart manually instead of being
|
|
3199
|
+
killed), and only when the enabled flag actually changed, so a settings save
|
|
3200
|
+
never bounces the site. Rapid toggles debounce into one restart. Opt out with
|
|
3201
|
+
`restartOnPluginToggle: false` in `config/server.json`.
|
|
3202
|
+
- The `[menu]` shortcode's `orientation` and `float` attributes are now offered
|
|
3203
|
+
in the editor's shortcode menu.
|
|
3204
|
+
|
|
3205
|
+
### Fixed
|
|
3206
|
+
- **`[menu]` no longer disappears without explanation.** An unknown slug, or a
|
|
3207
|
+
tag with no slug or location at all, rendered as nothing - a blank space with
|
|
3208
|
+
no clue what went wrong. It now renders a visible note naming the problem, the
|
|
3209
|
+
way `[form]` and `[block]` already did. An unmapped `location=` slot stays
|
|
3210
|
+
silent (a legitimate config state) but logs a warning server-side.
|
|
3211
|
+
- **Unquoted shortcode attributes.** `[menu slug=main /]` parsed as two
|
|
3212
|
+
meaningless flags rather than a value, so the shortcode lost its argument and
|
|
3213
|
+
silently rendered nothing. Unquoted `key=value` pairs now work in every
|
|
3214
|
+
shortcode.
|
|
3215
|
+
|
|
3216
|
+
## [0.41.0] - 2026-08-17
|
|
3217
|
+
|
|
3218
|
+
### Added
|
|
3219
|
+
- **Menu bindings.** A menu can now claim a slot for part of the site instead
|
|
3220
|
+
of being mapped to it everywhere: `binding: {slot, projects, pages}` makes it
|
|
3221
|
+
take over (say) the navbar on the pages of one project, or on a list of URL
|
|
3222
|
+
patterns (`/pricing`, `/docs/*`, `/*`). The most specific match wins - exact
|
|
3223
|
+
page, then wildcard (longest first), then project, then the
|
|
3224
|
+
`menu-locations.json` mapping. Honoured by the public navbar, the footer
|
|
3225
|
+
columns and `[menu location="…" /]`. New **Binding** tab in the menu editor,
|
|
3226
|
+
with a page picker and a live plain-English summary of what the binding
|
|
3227
|
+
resolves to; the menus list gains a "Bound to" column.
|
|
3228
|
+
- **Vertical menus.** `orientation: horizontal | vertical` (plus
|
|
3229
|
+
`side: left | right`) on any menu. In the navbar slot a vertical menu docks
|
|
3230
|
+
as a fixed side rail from 993px up, with the page body given a matching
|
|
3231
|
+
gutter server-side so there is no layout shift; below that breakpoint the
|
|
3232
|
+
existing mobile drawer is unchanged. The `[menu]` shortcode gains an
|
|
3233
|
+
`orientation` attribute to override a menu for one placement. Menus that set
|
|
3234
|
+
no orientation render exactly as before.
|
|
3235
|
+
- **Drag-and-drop menu items.** The menu editor's tree reorders by dragging a
|
|
3236
|
+
row's grip: dropping on the top or bottom quarter of a row places the item
|
|
3237
|
+
above or below it as a sibling, and dropping in the middle nests it as that
|
|
3238
|
+
row's last child. Dropping a row into its own subtree is refused. The
|
|
3239
|
+
existing indent / outdent / up / down buttons are untouched.
|
|
3240
|
+
- **Floating menu surface options.** `float` now also takes `radius`,
|
|
3241
|
+
`shadow` (`none | sm | md | lg | xl`), `accent` (preset colour key or hex -
|
|
3242
|
+
tints the border and draws a stripe down the panel's leading edge) and
|
|
3243
|
+
`opacity` (20-100, translucent blurred surface with fully opaque text). All
|
|
3244
|
+
resolve to theme tokens, so a floating panel still tracks the active theme.
|
|
3245
|
+
- **Grouped font pickers.** Site settings and the navbar font picker group
|
|
3246
|
+
families into Sans-serif / Serif / Dyslexia-friendly / System, adding
|
|
3247
|
+
Lexend, Atkinson Hyperlegible and Andika as an accessibility group.
|
|
3248
|
+
|
|
3249
|
+
### Fixed
|
|
3250
|
+
- **Font families with digits or a non-sans fallback.** The renderer emitted
|
|
3251
|
+
`font-family: Source Sans 3, sans-serif` - an unquoted identifier cannot
|
|
3252
|
+
start with a digit, so browsers dropped the whole declaration and the font
|
|
3253
|
+
silently never applied. Family names are now quoted and sanitised, and
|
|
3254
|
+
serif/monospace families get the matching generic fallback instead of
|
|
3255
|
+
`sans-serif`.
|
|
3256
|
+
|
|
3257
|
+
## [0.37.0] - 2026-07-22
|
|
3258
|
+
|
|
3259
|
+
### Added
|
|
3260
|
+
- **Forms: `icon` and `image` field types.** The form builder offers two new
|
|
3261
|
+
field types that store plain strings (an icon name / a media URL) but are
|
|
3262
|
+
filled with pickers instead of typed by hand. Icon fields get a live
|
|
3263
|
+
preview chip and a Browse button opening a searchable overlay of the full
|
|
3264
|
+
Domma icon set; image fields get a thumbnail preview and a Browse button
|
|
3265
|
+
opening the site media library (authenticated with the visitor's JWT +
|
|
3266
|
+
one refresh retry - anonymous visitors keep manual URL entry with a
|
|
3267
|
+
sign-in hint). Pickers work in all render paths: inline embeds
|
|
3268
|
+
(`site.js`), the legacy `[data-form]` renderer and its no-Domma manual
|
|
3269
|
+
fallback (`forms.js`), and the form builder's live preview
|
|
3270
|
+
(`enhancePickerFields` is shared from `public/js/forms.js`). Submissions
|
|
3271
|
+
store the chosen string as-is, so collections, actions, email and webhook
|
|
3272
|
+
payloads see a normal text value.
|
|
3273
|
+
|
|
3274
|
+
## [0.36.19] - 2026-07-16
|
|
3275
|
+
|
|
3276
|
+
### Fixed
|
|
3277
|
+
- **Ticker-tape inserts as a self-closing tag.** The effects builder and the
|
|
3278
|
+
editor context menu inserted `[ticker-tape]Content[/ticker-tape]`; the effect
|
|
3279
|
+
is now marked `selfClosing` so both insert `[ticker-tape … /]` (the full-page
|
|
3280
|
+
overlay form), matching `[celebrate]`. The wrapping container-scoped form is
|
|
3281
|
+
still rendered by the server and can be hand-written.
|
|
3282
|
+
|
|
3283
|
+
## [0.36.18] - 2026-07-04
|
|
3284
|
+
|
|
3285
|
+
### Changed
|
|
3286
|
+
- **Plugin code editor, properly.** The plain textarea is now the shared
|
|
3287
|
+
simple-editor with a new zero-dependency syntax-highlight layer (JS, JSON,
|
|
3288
|
+
CSS, HTML, Markdown - output HTML-escaped by construction, unit-tested for
|
|
3289
|
+
XSS), line numbers, auto-indent on Enter, and Ln/Col in the status bar.
|
|
3290
|
+
The file tree groups by directory; unsaved edits survive switching files
|
|
3291
|
+
(per-file buffers with dirty dots), Save All / Ctrl+Shift+S flushes them,
|
|
3292
|
+
files can be renamed, and closing the tab with unsaved changes warns first.
|
|
3293
|
+
simple-editor's new `language` / `autoIndent` / `onCursor` options are
|
|
3294
|
+
opt-in - the Block and Component editors are unchanged.
|
|
3295
|
+
|
|
3296
|
+
## [0.36.17] - 2026-07-04
|
|
3297
|
+
|
|
3298
|
+
### Added
|
|
3299
|
+
- **In-admin plugin code editor.** Every plugin card gains a Code button opening
|
|
3300
|
+
`#/plugins/code/<name>` - file tree, monospace editor with Ctrl+S, new/delete
|
|
3301
|
+
file, and a Restart Server button so server-side edits apply without leaving
|
|
3302
|
+
the browser. Saves are flagged per file: `admin/` files need only a hard
|
|
3303
|
+
refresh, everything else needs the restart. All I/O is confined to the plugin
|
|
3304
|
+
directory (traversal/symlink escapes refused, text extensions only, JSON
|
|
3305
|
+
validated, required files undeletable) and gated by the new `plugins.develop`
|
|
3306
|
+
permission - separate from `plugins.update` because plugin source is code
|
|
3307
|
+
execution. Base admin roles receive it automatically; custom roles need an
|
|
3308
|
+
explicit grant.
|
|
3309
|
+
|
|
3310
|
+
## [0.36.16] - 2026-07-04
|
|
3311
|
+
|
|
3312
|
+
### Added
|
|
3313
|
+
- **Plugin scaffolder.** New Plugin button on the admin Plugins view opens a slideover
|
|
3314
|
+
(display name with auto-derived slug, description, author, icon) that instantiates
|
|
3315
|
+
`plugins/_template` via the new `POST /api/plugins/scaffold` endpoint (guarded by the
|
|
3316
|
+
existing `plugins.create` permission). The plugin is created pre-enabled - server
|
|
3317
|
+
routes, admin view, sidebar entry and CLAUDE.md all wired - and goes live on the next
|
|
3318
|
+
server restart. Scaffolding documented in `docs/plugin-development.md`.
|
|
3319
|
+
|
|
3320
|
+
## [0.36.15] - 2026-07-04
|
|
3321
|
+
|
|
3322
|
+
### Changed
|
|
3323
|
+
- Converted the last remaining modal - the page editor's "Save Named Version" dialog -
|
|
3324
|
+
to a slideover, completing the modal→slideover sweep.
|
|
3325
|
+
|
|
3326
|
+
## [0.36.14] - 2026-07-04
|
|
3327
|
+
|
|
3328
|
+
### Changed
|
|
3329
|
+
- **All admin modals are now slideovers.** Create Form, Send Test Email, New Collection,
|
|
3330
|
+
Add Role, New API Token / Token created, Entry add/edit/details/import, Submission
|
|
3331
|
+
Details, and the image editor (75% viewport at full height) all open as right-hand
|
|
3332
|
+
slideovers with identical behaviour - light-DOM content projection, focus handling,
|
|
3333
|
+
and button wiring preserved.
|
|
3334
|
+
|
|
3335
|
+
## [0.36.13] - 2026-07-04
|
|
3336
|
+
|
|
3337
|
+
### Fixed
|
|
3338
|
+
- **Form builder crashed for any form with option fields (regression in 0.36.6).** The
|
|
3339
|
+
cascade help-tooltips wired `E.tooltip` on a not-yet-attached node; Domma's tooltip
|
|
3340
|
+
wraps elements via `parentNode.insertBefore`, so it threw and killed the entire
|
|
3341
|
+
field-card render - the builder showed nothing for forms containing select/chooser
|
|
3342
|
+
fields while simple forms were unaffected. Tooltip wiring is now deferred a frame,
|
|
3343
|
+
guarded, and falls back to a native title.
|
|
3344
|
+
|
|
3345
|
+
## [0.36.12] - 2026-07-04
|
|
3346
|
+
|
|
3347
|
+
### Added
|
|
3348
|
+
- **Duplicate form.** The Forms list gained a copy action that clones a form's fields,
|
|
3349
|
+
settings, and email/webhook actions under a new slug - the backing collection is
|
|
3350
|
+
freshly provisioned for the copy so its submissions never land in the source form's
|
|
3351
|
+
collection.
|
|
3352
|
+
|
|
3353
|
+
### Fixed
|
|
3354
|
+
- **Page cache now self-invalidates on file changes.** The response-cache key includes
|
|
3355
|
+
the page file's mtime, so out-of-band edits (scripts, git pulls, deploys) serve fresh
|
|
3356
|
+
content without relying on service-layer hooks having fired. Previously a directly
|
|
3357
|
+
edited page could be served stale indefinitely per host/role variant.
|
|
3358
|
+
- **Row delete buttons are theme-correct.** All list/table delete buttons switched from
|
|
3359
|
+
solid `btn-danger` blocks (whose background varies per theme - the "white bin icon")
|
|
3360
|
+
to quiet ghost buttons whose trash icon carries the `--dm-danger` accent, visible on
|
|
3361
|
+
all 26 themes.
|
|
3362
|
+
|
|
3363
|
+
## [0.36.11] - 2026-07-04
|
|
3364
|
+
|
|
3365
|
+
### Fixed
|
|
3366
|
+
- **Table action icons no longer vanish after sort/search/pagination.** T.create
|
|
3367
|
+
re-renders its cells, wiping the injected icon SVGs and any per-button click
|
|
3368
|
+
listeners - most visibly the Forms list's delete button. A shared observer
|
|
3369
|
+
(`admin/js/lib/table-icons.js`) now re-scans icons and re-wraps tooltips after every
|
|
3370
|
+
table re-render across all 14 table views, per-button delete/export/run bindings are
|
|
3371
|
+
replaced with delegated handlers that survive re-renders, and all row-action icons
|
|
3372
|
+
are sized to the standard 14px (they rendered at a 24px default, overflowing their
|
|
3373
|
+
buttons). Collection entries' row-click detail modal now also survives sorting
|
|
3374
|
+
(resolved by entry id instead of row index).
|
|
3375
|
+
|
|
3376
|
+
## [0.36.10] - 2026-07-04
|
|
3377
|
+
|
|
3378
|
+
### Fixed
|
|
3379
|
+
- **One broken artefact can no longer blank a whole type in Projects.** The artefact
|
|
3380
|
+
resolver re-reads menus/forms/collections per item; a single unreadable one (e.g. a
|
|
3381
|
+
form whose internal slug didn't match its filename) threw and emptied that entire
|
|
3382
|
+
type across every project - "Forms disappeared from Core". Each item is now guarded
|
|
3383
|
+
individually.
|
|
3384
|
+
- **`listForms()` normalises the slug to the filename** (the canonical key used by
|
|
3385
|
+
read/delete/save and the `[form]` shortcode), so a stale internal slug can't desync
|
|
3386
|
+
the list from the files again.
|
|
3387
|
+
|
|
3388
|
+
## [0.36.9] - 2026-07-04
|
|
3389
|
+
|
|
3390
|
+
### Fixed
|
|
3391
|
+
- **Admin sidebar type scale normalised.** Section headings, links, and nested items all
|
|
3392
|
+
rendered at an identical 16px with no hierarchy (and larger than the brand header).
|
|
3393
|
+
Now: items 0.875rem at every depth, top-level sections as small uppercase muted labels,
|
|
3394
|
+
nested section headers item-weighted, and both badge styles unified. Menu-data style
|
|
3395
|
+
overrides still take precedence.
|
|
3396
|
+
|
|
3397
|
+
## [0.36.8] - 2026-07-04
|
|
3398
|
+
|
|
3399
|
+
### Fixed
|
|
3400
|
+
- **Conditional logic now triggers from chooser fields.** Domma choosers (chips/cards)
|
|
3401
|
+
check their underlying radio programmatically without dispatching change/input, so any
|
|
3402
|
+
visibility/requirement condition keyed on a chooser never re-evaluated - a "reveal on
|
|
3403
|
+
yes" question after a chip picker simply never appeared. The logic engine now also
|
|
3404
|
+
delegates clicks on chooser containers and re-evaluates the field after the selection
|
|
3405
|
+
lands.
|
|
3406
|
+
|
|
3407
|
+
---
|
|
3408
|
+
|
|
3409
|
+
## [0.36.7] - 2026-07-04
|
|
3410
|
+
|
|
3411
|
+
### Fixed
|
|
3412
|
+
- **Chooser styling on select/radio/checkbox-group fields now renders.** The form
|
|
3413
|
+
builder's chooser section configures variant/density/columns/accent on any option
|
|
3414
|
+
field, but all three renderers (public inline embed, public `[data-form]` embed,
|
|
3415
|
+
admin preview) only honoured those props on `chooser`-type fields - a chip-styled
|
|
3416
|
+
select silently collapsed to a plain dropdown. Option fields carrying a chooser
|
|
3417
|
+
variant are now promoted to chooser blueprints (identical value contract;
|
|
3418
|
+
checkbox-groups map to multi-select choosers).
|
|
3419
|
+
|
|
3420
|
+
---
|
|
3421
|
+
|
|
3422
|
+
## [0.36.6] - 2026-07-04
|
|
3423
|
+
|
|
3424
|
+
### Changed
|
|
3425
|
+
- **Form builder polish.** Field cards gained definition - card background, subtle shadow,
|
|
3426
|
+
softer radius, more separation - with roomier body padding, row gaps, and clearer
|
|
3427
|
+
section headings.
|
|
3428
|
+
- **Cascade Options explained in place.** Help tooltips on the Cascade heading, the
|
|
3429
|
+
Mapping JSON label, and Default options describe exactly how the cascade behaves:
|
|
3430
|
+
the source field's value picks the matching mapping entry as this field's options,
|
|
3431
|
+
falling back to the defaults when nothing matches.
|
|
3432
|
+
|
|
3433
|
+
---
|
|
3434
|
+
|
|
3435
|
+
## [0.36.5] - 2026-07-04
|
|
3436
|
+
|
|
3437
|
+
### Changed
|
|
3438
|
+
- Form builder layout rebalanced to a 60/40 builder/preview split - the preview pane now
|
|
3439
|
+
scales with the viewport instead of capping at a fixed 460px.
|
|
3440
|
+
|
|
3441
|
+
---
|
|
3442
|
+
|
|
3443
|
+
## [0.36.4] - 2026-07-04
|
|
3444
|
+
|
|
3445
|
+
### Added
|
|
3446
|
+
- **Form builder: drag-to-reorder fields.** Field, spacer, and page-break cards reorder by
|
|
3447
|
+
dragging (grip handle / card header), mirroring the collection editor's behaviour and
|
|
3448
|
+
drop-highlight. The per-card up/down arrow buttons are gone - the drag grip plus a
|
|
3449
|
+
tidier header (type pill, label, edit, remove) matches the collection editor exactly.
|
|
3450
|
+
Chooser options keep their small arrows (they don't drag).
|
|
3451
|
+
- **Form builder uses the full workspace width** - the fields + preview layout is no
|
|
3452
|
+
longer squeezed into the default 1200px column, and the preview pane gained room.
|
|
3453
|
+
|
|
3454
|
+
### Fixed
|
|
3455
|
+
- **Admin-wide consistency sweep.** Every remaining unicode text-glyph button across the
|
|
3456
|
+
admin now uses the standard Domma icon set at consistent sizes: card/timeline/effects
|
|
3457
|
+
builders (dialog closes, browse/star buttons, drag grips, row arrows/removes, Replay),
|
|
3458
|
+
markdown-toolbar caret, shortcode-modal icon-browse, the collection editor's drag grip,
|
|
3459
|
+
disclosure chevron and oversized remove icon, page-editor's fold-all toggle and
|
|
3460
|
+
ListGroup-builder remove. Editor-folding's per-line gutter chevrons stay as text for
|
|
3461
|
+
performance but are normalised to one glyph pair.
|
|
3462
|
+
- **Every data-fetching admin view now shows the Domma loader** while loading (22 views:
|
|
3463
|
+
actions, API builder/endpoints/tokens, blocks, components, effects, layouts, menus ×3,
|
|
3464
|
+
profile, notifications, projects ×4, users, views ×3, collection entries, view preview,
|
|
3465
|
+
login's setup check) - all with guaranteed teardown on error paths.
|
|
3466
|
+
|
|
3467
|
+
---
|
|
3468
|
+
|
|
3469
|
+
## [0.36.3] - 2026-07-04
|
|
3470
|
+
|
|
3471
|
+
### Added
|
|
3472
|
+
- **Context menu: data-driven dropdowns.** Attributes that bind to CMS data are now live
|
|
3473
|
+
pickers instead of type-the-slug text fields: `[form]` lists your forms, `[collection]`
|
|
3474
|
+
your collections (and its Sort/Title-field dropdowns read the selected collection's
|
|
3475
|
+
schema), `[view]`, `[block]`, `[menu]` (slugs + location slots) and `[cta]` actions
|
|
3476
|
+
likewise. Values that no longer exist are flagged "(missing)". The Insert menu drills
|
|
3477
|
+
into the same pickers, so Insert → Form inserts `[form name="…"]` with the chosen form
|
|
3478
|
+
baked in - no placeholder slug to fix up.
|
|
3479
|
+
- **Form builder: always-on live preview.** The Fields tab is now a two-column layout with
|
|
3480
|
+
a sticky preview pane on the right that re-renders (debounced) as fields and settings
|
|
3481
|
+
change; the Preview toolbar toggle is gone. Test mode still posts real submissions.
|
|
3482
|
+
|
|
3483
|
+
### Fixed
|
|
3484
|
+
- **Form builder icon standardisation.** All unicode text-glyph buttons (↑ ↓ ✕ ⋯ ⚡ → ▲ ▼ ▾)
|
|
3485
|
+
replaced with the site-standard `data-icon` set at a consistent 14px: `arrow-up/down`
|
|
3486
|
+
for reordering, quiet ghost `x` for per-row removes (matching the collection editor -
|
|
3487
|
+
no more bold red ✕ buttons), `more-horizontal` for edit toggles, `zap` for the logic
|
|
3488
|
+
badge, `chevron-*` for disclosure. Chooser option rows and the Add-option button now
|
|
3489
|
+
match the rest of the builder.
|
|
3490
|
+
- Forms list, form editor, and submissions views show the Domma loader during fetches.
|
|
3491
|
+
|
|
3492
|
+
---
|
|
3493
|
+
|
|
3494
|
+
## [0.36.2] - 2026-07-04
|
|
3495
|
+
|
|
3496
|
+
### Fixed
|
|
3497
|
+
- **Theme-compliance sweep: every popup, panel, and component now honours the active theme.**
|
|
3498
|
+
The admin and public UI referenced ~300 CSS variables that were never defined anywhere
|
|
3499
|
+
(`--card-bg`, `--border-color`, `--primary`, and invented `--dm-*` names such as
|
|
3500
|
+
`--dm-surface-subtle`, `--dm-bg`, `--dm-muted`, `--dm-accent`). Undefined variables silently
|
|
3501
|
+
fall back to hardcoded colours tuned for a single dark theme, which made popups blank or
|
|
3502
|
+
illegible on other themes - most visibly the form editor's Add ▾ menu and new-field card.
|
|
3503
|
+
All references are canonicalised to real Domma tokens (verified against
|
|
3504
|
+
`domma-themes.css`/`domma.css`: zero undefined names remain).
|
|
3505
|
+
- **Hardcoded admin-chrome colours tokenised (~230 sites).** Editor toolbar/dropdown hover
|
|
3506
|
+
states, API-doc method badges, card/timeline/effects-builder dialogs, the API Builder
|
|
3507
|
+
try-it JSON palette, form-editor pills and test-submit banners, project-detail artefact
|
|
3508
|
+
accents, sidebar badges, and role/stat badges now use semantic theme tokens
|
|
3509
|
+
(`--dm-hover-bg`, `--dm-primary-text`, `color-mix(...)` tints, etc.).
|
|
3510
|
+
- **Public `site.css`/`forms.css` tokenised (~100 sites).** Inline code/pre blocks, card
|
|
3511
|
+
variants (accent/glow/gradient body), block-template cards that were hardcoded
|
|
3512
|
+
white-on-light, marketing components, menu badges, and form success/error messages now
|
|
3513
|
+
follow the site theme. Deliberate design colours (gradient presets, always-dark code
|
|
3514
|
+
panels, image-overlay scrims, rating stars) are intentionally unchanged.
|
|
3515
|
+
- Menu badge text (`makeBadgeEl`) no longer hardcodes white on themed backgrounds.
|
|
3516
|
+
|
|
3517
|
+
---
|
|
3518
|
+
|
|
3519
|
+
## [0.36.1] - 2026-07-04
|
|
3520
|
+
|
|
3521
|
+
### Fixed
|
|
3522
|
+
- **Page editor: Ctrl+Z now undoes toolbar, Insert-menu, and context-menu edits.** Every
|
|
3523
|
+
programmatic insert wrote `textarea.value` directly, which clears the browser's native undo
|
|
3524
|
+
buffer - so nothing inserted (or even typed beforehand) could be undone. All content-edit
|
|
3525
|
+
paths now route through a new `replaceRange()` helper built on
|
|
3526
|
+
`document.execCommand('insertText')`, which participates in native undo/redo: formatting
|
|
3527
|
+
buttons, headings/lists, Tab indent, all Insert-menu items, the `[text]` and Insert Component
|
|
3528
|
+
slideovers, the Effects builder, the card builder, the CRUD tutorial snippets, and the
|
|
3529
|
+
right-click context menu's live attribute edits and Wrap-in. Falls back to the old direct
|
|
3530
|
+
splice where `execCommand` is unavailable.
|
|
3531
|
+
- **Context menu now mirrors the actual tag state.** Checkboxes read bare flags, `="true"`,
|
|
3532
|
+
and `="1"` alike and show the attribute's default when absent (e.g. Full-width is now
|
|
3533
|
+
checked when `fullwidth="true"` is present); writes emit the form every server-side check
|
|
3534
|
+
accepts and drop attributes that match their default. `[collection]` `search`/`sortable`
|
|
3535
|
+
are recognised as default-on, so unchecking writes `="false"` instead of silently
|
|
3536
|
+
re-enabling them. Selects highlight the effective value (explicit or default), inputs show
|
|
3537
|
+
defaults as placeholders, and injected-effect detection matches the server's
|
|
3538
|
+
presence check (`reveal="true"` counts).
|
|
3539
|
+
- Preview/autosave `input` listeners no longer fire twice per inserted snippet.
|
|
3540
|
+
|
|
3541
|
+
---
|
|
3542
|
+
|
|
3543
|
+
## [0.36.0] - 2026-07-03
|
|
3544
|
+
|
|
3545
|
+
### Added
|
|
3546
|
+
- **`[embed]` shortcode** - embed video and external pages from one tag. YouTube/Vimeo
|
|
3547
|
+
links render via privacy-friendly `youtube-nocookie.com` / `player.vimeo.com`;
|
|
3548
|
+
self-hosted `.mp4`/`.webm`/`.ogg` render as a native `<video>`; any other URL renders
|
|
3549
|
+
as a generic iframe. Attributes: `ratio` (16:9 default, plus 4:3/1:1/21:9/9:16/3:4),
|
|
3550
|
+
`title` (caption + accessibility), `width`, `align`, and `autoplay`/`loop`/`muted`.
|
|
3551
|
+
Generic iframes are gated by a host allow-list in `config/embeds.json`, enforced twice
|
|
3552
|
+
(at render time and again by the HTML sanitiser's `allowedIframeHostnames`). New
|
|
3553
|
+
**Insert → Embed** builder with a URL field and a self-hosted-video picker.
|
|
3554
|
+
- **Right-click context menu in the Page Editor** - right-click inside any shortcode to
|
|
3555
|
+
edit that tag's options live and inline (checkboxes toggle instantly, values edit in
|
|
3556
|
+
place, selects open sub-menus, and the shortcode rewrites as you go); right-click
|
|
3557
|
+
anywhere for **Insert…** and **Wrap in…**. Registry-driven across all 32 shortcodes
|
|
3558
|
+
(plus child tags) and all 17 effects. Effects reach full parity: edit standalone effect
|
|
3559
|
+
tags, edit or remove effects injected onto host tags, and **Add effect…** to inject into
|
|
3560
|
+
a compatible host or insert a standalone effect.
|
|
3561
|
+
|
|
3562
|
+
### Fixed
|
|
3563
|
+
- **Effects button** - the toolbar Effects panel now reopens on repeat presses; its
|
|
3564
|
+
re-open guard previously latched until a full page reload.
|
|
3565
|
+
- **Unsaved-changes navigation** - confirming "Leave this page?" now actually changes the
|
|
3566
|
+
page; the guard previously discarded the navigation, forcing a hard reload. (The router
|
|
3567
|
+
awaits the guard's returned confirm promise.)
|
|
3568
|
+
|
|
3569
|
+
---
|
|
3570
|
+
|
|
3571
|
+
## [0.35.0] - 2026-07-02
|
|
3572
|
+
|
|
3573
|
+
### Added
|
|
3574
|
+
- **Butterflies effect** - `[butterflies /]` (full-page overlay) or `[butterflies]…[/butterflies]`
|
|
3575
|
+
(container-scoped): procedurally drawn butterflies wander and rise with flapping wings.
|
|
3576
|
+
Thirteen palettes plus density, speed, wander, rise/flap speed, wingspan range, two-tone,
|
|
3577
|
+
and burst-mode controls. Injectable into `[hero]` (`butterflies butterflies-palette="pastel"`).
|
|
3578
|
+
- **Strobe lighting effect** - `[strobe /]` / wrapping form: light beams sweep from the corners
|
|
3579
|
+
and brighten where they cross. Six presets (club, concert, police, searchlight, scanner, mood)
|
|
3580
|
+
with motion, speed, arc, beam width, intensity, flicker + Hz, custom colours, and auto-stop
|
|
3581
|
+
overrides. Injectable into `[hero]`.
|
|
3582
|
+
- Both effects appear in the Page Editor **Insert → Effects** catalogue (Celebrations group)
|
|
3583
|
+
with full parameter forms and live preview. The strobe form carries a photosensitivity hint
|
|
3584
|
+
mirroring the framework's own >5 Hz flicker warning.
|
|
3585
|
+
|
|
3586
|
+
### Fixed
|
|
3587
|
+
- Docs: the effects shortcode table now lists `[ticker-tape]` (previously missing) alongside
|
|
3588
|
+
the new `[butterflies]` and `[strobe]` rows.
|
|
3589
|
+
|
|
3590
|
+
Both new effects respect `prefers-reduced-motion` and degrade silently on sites running an
|
|
3591
|
+
older `domma-js` bundle (feature-detected before initialisation).
|
|
3592
|
+
|
|
3593
|
+
---
|
|
3594
|
+
|
|
3595
|
+
## [0.34.1] - 2026-07-02
|
|
3596
|
+
|
|
3597
|
+
### Fixed
|
|
3598
|
+
- `listCollections()` no longer crashes when a hand-rolled schema lacks `title` -
|
|
3599
|
+
the sort falls back to the slug. (Previously a single title-less schema 500'd
|
|
3600
|
+
`/api/collections` for the entire site.)
|
|
3601
|
+
- Deleting a MongoDB-backed collection now **drops** the backing `cms_<slug>`
|
|
3602
|
+
Mongo collection (new `MongoAdapter.drop()`); previously only the documents
|
|
3603
|
+
were cleared, leaving the namespace and its unique index behind forever.
|
|
3604
|
+
|
|
3605
|
+
---
|
|
3606
|
+
|
|
3607
|
+
## [0.34.0] - 2026-07-02
|
|
3608
|
+
|
|
3609
|
+
### Added
|
|
3610
|
+
- **Views are now innate** - available on every install with no MongoDB requirement.
|
|
3611
|
+
File-backed collections run a new built-in JS pipeline evaluator (`server/services/viewPipeline.js`)
|
|
3612
|
+
covering all ten allow-listed aggregation stages; MongoDB-backed collections keep using native
|
|
3613
|
+
aggregation. The same view definition works identically on both storage adapters.
|
|
3614
|
+
- One-time automatic migration of legacy view configs from the MongoDB `cms__views` collection to
|
|
3615
|
+
`content/views/*.json` (read-only on the Mongo side).
|
|
3616
|
+
- Views section in the API reference (previously undocumented).
|
|
3617
|
+
|
|
3618
|
+
### Changed
|
|
3619
|
+
- View configs are now stored as JSON files in `content/views/` (always file-based, like collection
|
|
3620
|
+
schemas and forms), regardless of storage adapter.
|
|
3621
|
+
|
|
3622
|
+
### Removed
|
|
3623
|
+
- The Pro/MongoDB requirement for Views: the "pro mode" error, the admin MongoDB notice on the Views
|
|
3624
|
+
screen, and the *(Pro)* framing in the docs.
|
|
3625
|
+
|
|
3626
|
+
---
|
|
3627
|
+
|
|
3628
|
+
## [0.33.1] - 2026-07-01
|
|
3629
|
+
|
|
3630
|
+
### Fixed
|
|
3631
|
+
|
|
3632
|
+
- **Custom API endpoints now honour `limit`, `page`, `sort` and `order` query params.** These
|
|
3633
|
+
reserved keys were correctly kept out of ad-hoc field filtering but were never wired to the
|
|
3634
|
+
query, so `?limit=5` did nothing. In list mode `?limit=N` now shrinks the page - **capped at the
|
|
3635
|
+
endpoint's own configured limit**, so a caller can request fewer results but never more than the
|
|
3636
|
+
endpoint intends - `?page=N` walks the results, `?sort=field` sorts (restricted to fields the
|
|
3637
|
+
endpoint exposes) and `?order=asc|desc` sets direction. `search` remains reserved.
|
|
3638
|
+
|
|
3639
|
+
---
|
|
3640
|
+
|
|
3641
|
+
## [0.33.0] - 2026-07-01
|
|
3642
|
+
|
|
3643
|
+
### Added
|
|
3644
|
+
|
|
3645
|
+
- **Custom API endpoints now accept ad-hoc query filters.** A request's query string is applied
|
|
3646
|
+
as additional filters *on top of* an endpoint's declared filters (which always win on conflict).
|
|
3647
|
+
Ad-hoc filters are restricted to the fields the endpoint actually exposes - its read-field
|
|
3648
|
+
allowlist, or the collection's fields when none is set - so a caller can never probe a field
|
|
3649
|
+
hidden from the response, and `page`/`limit`/`sort`/`order`/`search` are reserved as envelope
|
|
3650
|
+
controls. Declared `{{query.x}}` clauses are unchanged.
|
|
3651
|
+
- **API Builder "Try it" console overhaul.** Dedicated, self-labelling inputs for each
|
|
3652
|
+
`{{query.x}}` an endpoint reads; the full request rendered as copy-paste cURL alongside the
|
|
3653
|
+
response status, headers, and body; a syntax-coloured, collapsible JSON viewer for the response
|
|
3654
|
+
(with a Tree⇄Raw toggle and copy); per-endpoint test history you can click to re-run (tokens are
|
|
3655
|
+
never persisted); and keyboard shortcuts - `Cmd/Ctrl+S` to save, `Cmd/Ctrl+Enter` to send.
|
|
3656
|
+
- **Tabbed, full-width endpoint editor.** The editor's left column is now split into **Definition**,
|
|
3657
|
+
**Query** and **Response fields** tabs (Enabled folded into Definition), and the view uses the full
|
|
3658
|
+
browser width in a three-column layout - definition · Try-It · results - so the response lands
|
|
3659
|
+
*beside* the request instead of below the fold. Collapses to two columns then one on narrower
|
|
3660
|
+
viewports.
|
|
3661
|
+
|
|
3662
|
+
### Fixed
|
|
3663
|
+
|
|
3664
|
+
- **Hardened Try-it path-parameter substitution** so a `:id` placeholder can no longer clobber a
|
|
3665
|
+
`:idx` in the same path, and a parameter value that contains `:name` is not mistaken for a
|
|
3666
|
+
placeholder.
|
|
3667
|
+
- **`Cmd/Ctrl+S` no longer wrongly reports "already exists".** The editor mounts into a reused
|
|
3668
|
+
container, so the save shortcut's `keydown` handler was never being torn down - handlers stacked
|
|
3669
|
+
up on `document`, and the one closing over a *new* (unsaved) endpoint kept running `create` after
|
|
3670
|
+
the endpoint had been saved, tripping the duplicate-path guard. Exactly one editor shortcut
|
|
3671
|
+
handler is now active at a time.
|
|
3672
|
+
|
|
3673
|
+
---
|
|
3674
|
+
|
|
3675
|
+
## [0.32.4] - 2026-07-01
|
|
3676
|
+
|
|
3677
|
+
### Fixed
|
|
3678
|
+
|
|
3679
|
+
- **Hover-triggered dropdown menus in the admin are now reliable.** Bumped `domma-js` to **0.29.3**,
|
|
3680
|
+
which fixes a dropdown that opened inconsistently on hover and would not stay open when the pointer
|
|
3681
|
+
moved from the trigger onto the menu - which in turn destabilised the click trigger. Two root causes
|
|
3682
|
+
in Domma: `close()` scheduled menu removal via a timer that read the live menu reference at fire time,
|
|
3683
|
+
so a rapid re-open could have its fresh menu torn down and leave the open-state flag desynced; and the
|
|
3684
|
+
menu (a `document.body` sibling) had an offset/margin dead-zone the pointer had to cross, which fired
|
|
3685
|
+
the close. Domma now detaches state synchronously with the removal timer owning its own node, and adds
|
|
3686
|
+
a transparent hover bridge so the trigger-to-menu path is one continuous hover surface. The committed
|
|
3687
|
+
admin tools bundle was synced to 0.29.3 to keep core and tools in lockstep.
|
|
3688
|
+
|
|
3689
|
+
---
|
|
3690
|
+
|
|
3691
|
+
## [0.32.3] - 2026-06-30
|
|
3692
|
+
|
|
3693
|
+
### Fixed
|
|
3694
|
+
|
|
3695
|
+
- **Action-button tooltips no longer vanish in admin tables.** Bumped `domma-js` to **0.29.2**,
|
|
3696
|
+
which fixes a tooltip double-wrap regression introduced by 0.29.1's table re-render fix. The
|
|
3697
|
+
`E.tooltip` factory physically wraps each element in a `<domma-tooltip>` but was not idempotent,
|
|
3698
|
+
so when the framework's per-render tooltip wiring and the admin views both wired the same action
|
|
3699
|
+
button, it got nested in two `<domma-tooltip>` wrappers and showed nothing. The wrapper is now
|
|
3700
|
+
idempotent (refreshes instead of nesting) and falls back to `data-tooltip`/`title` for content.
|
|
3701
|
+
The committed admin tools bundle was synced to 0.29.2 to keep core and tools in lockstep.
|
|
3702
|
+
|
|
3703
|
+
---
|
|
3704
|
+
|
|
3705
|
+
## [0.32.2] - 2026-06-30
|
|
3706
|
+
|
|
3707
|
+
### Fixed
|
|
3708
|
+
|
|
3709
|
+
- **Surveys: the non-responders reminder no longer drops recipients in tests, and the send path is
|
|
3710
|
+
now testable without real SMTP.** The `/surveys/:slug/send` route always built a live SMTP
|
|
3711
|
+
transport (or, with no SMTP configured, an Ethereal account over the network), so in a sandboxed
|
|
3712
|
+
test environment every send timed out and was marked `bounced` - which `ledger.outstanding()`
|
|
3713
|
+
excludes, making the `non-responders` reminder resolve to zero recipients. The plugin now accepts
|
|
3714
|
+
an injectable `email` transport (`createTransport` / `sendEmail`) that **defaults to the real
|
|
3715
|
+
service** - no production behaviour change - so tests can supply a deterministic, network-free
|
|
3716
|
+
stub. The surveys send suite drops from ~240s to ~0.2s and the full test suite goes green
|
|
3717
|
+
(404/404, ~8s).
|
|
3718
|
+
|
|
3719
|
+
---
|
|
3720
|
+
|
|
3721
|
+
## [0.32.1] - 2026-06-30
|
|
3722
|
+
|
|
3723
|
+
### Fixed
|
|
3724
|
+
|
|
3725
|
+
- **Data tables no longer lose action-button icons and tooltips after pagination, search or
|
|
3726
|
+
sort.** Bumped `domma-js` to **0.29.1**, which fixes `Table.render()` to re-scan icons and
|
|
3727
|
+
re-wire tooltips on every re-render. Previously only the *initial* render was processed, so
|
|
3728
|
+
paging, searching or sorting any admin or public-site table stripped the icons and tooltip
|
|
3729
|
+
popovers from rendered cells - the action buttons appeared to "fall apart". The committed admin
|
|
3730
|
+
tools bundle (`admin/dist/domma/domma-tools.*`) was synced to 0.29.1 to keep core and tools in
|
|
3731
|
+
lockstep.
|
|
3732
|
+
|
|
3733
|
+
---
|
|
3734
|
+
|
|
3735
|
+
## [0.32.0] - 2026-06-29
|
|
3736
|
+
|
|
3737
|
+
### Added
|
|
3738
|
+
|
|
3739
|
+
- **Surveys - a Pro plugin for targeted email surveys, built on the Forms system.** Create a survey
|
|
3740
|
+
campaign that reuses an existing form definition, target one or more contact groups, and send each
|
|
3741
|
+
recipient a personalised invitation carrying a unique link. Responses are recorded against the form's
|
|
3742
|
+
own collection, tagged to the originating survey, and surfaced in a results dashboard - response rate,
|
|
3743
|
+
per-question distributions, free-text answers - with CSV and JSON export.
|
|
3744
|
+
- **Audience.** Shared `surveys-contacts` and `surveys-groups` collections, with a one-way import from
|
|
3745
|
+
the Contacts plugin so existing contacts can be reused without duplication.
|
|
3746
|
+
- **Identified or anonymous per survey.** Anonymous mode de-links responses from the contact who
|
|
3747
|
+
submitted them.
|
|
3748
|
+
- **Hosted survey page** at `/survey/:slug`, with a Markdown intro and footer and a custom thank-you
|
|
3749
|
+
message; plus a `[survey slug="…"]` shortcode to embed a survey inside any page.
|
|
3750
|
+
- **Editor-triggered sending.** Reminders re-issue fresh tokens to non-responders only, and a generic
|
|
3751
|
+
due-work contract lets an external scheduler drive reminders without bespoke wiring.
|
|
3752
|
+
- MongoDB-backed (Pro) storage for campaign, audience, and invite data.
|
|
3753
|
+
- **Six new admin chrome themes** (Sharp and Smooth variants in Indigo, Steel, and Teal), selectable in
|
|
3754
|
+
Site Settings.
|
|
3755
|
+
|
|
3756
|
+
## [0.31.0] - 2026-06-25
|
|
3757
|
+
|
|
3758
|
+
### Changed
|
|
3759
|
+
|
|
3760
|
+
- **Domma Docs now render at request time from the engine's templates** instead of being baked into
|
|
3761
|
+
per-site Markdown by a one-shot porter. Enabling the `domma-docs` project publishes the handbook at
|
|
3762
|
+
`/domma-docs/…` exactly as before - but a domma-cms version bump now updates the published docs on
|
|
3763
|
+
every site automatically. There is no longer any per-site provisioning step to run, nothing that can
|
|
3764
|
+
go stale, and no baked pages to drift from the shipped templates. The project's enable/disable flag
|
|
3765
|
+
remains the public kill-switch.
|
|
3766
|
+
- Section landing pages (`/domma-docs` and the Usage / Tutorials / API indexes) are now first-class -
|
|
3767
|
+
the previous porter linked to them from its nav but never generated them.
|
|
3768
|
+
|
|
3769
|
+
### Removed
|
|
3770
|
+
|
|
3771
|
+
- `scripts/migrate-docs.js` (the one-shot porter) and the baked `content/pages/domma-docs/*` pages.
|
|
3772
|
+
Existing installs can delete their baked `content/pages/domma-docs/` and the managed Domma Docs block
|
|
3773
|
+
in `content/custom.css`; the request-time renderer supersedes both.
|
|
3774
|
+
|
|
3775
|
+
## [0.30.0] - 2026-06-25
|
|
3776
|
+
|
|
3777
|
+
### Added
|
|
3778
|
+
|
|
3779
|
+
- **Domma Docs - a built-in, toggleable documentation project.** Every site is now seeded with a
|
|
3780
|
+
default **Domma Docs** project (`domma-docs`, disabled by default, undeletable). Enable it and the
|
|
3781
|
+
full CMS documentation is published as public pages under `/domma-docs/…`; disable it and those
|
|
3782
|
+
pages leave the public site entirely (the project's enable/disable acts as a hard public
|
|
3783
|
+
kill-switch).
|
|
3784
|
+
- Pages are built from native Domma components - full-width `[hero]`, `[card]` grids,
|
|
3785
|
+
`[tabs]`/`[accordion]` sections, `[button]`s and list-groups - with a persistent section nav,
|
|
3786
|
+
breadcrumbs and prev/next paging between pages.
|
|
3787
|
+
- Generated from the admin documentation screens by `scripts/migrate-docs.js`; doc-specific
|
|
3788
|
+
styling lives in `content/custom.css`.
|
|
3789
|
+
|
|
3790
|
+
### Changed
|
|
3791
|
+
|
|
3792
|
+
- **Projects: `protected` now means "undeletable", not "un-disable-able".** Protected projects
|
|
3793
|
+
(core and Domma Docs) cannot be deleted or untagged at both the service and route layers, but -
|
|
3794
|
+
unlike before - non-core protected projects can still be enabled and disabled. Only the core
|
|
3795
|
+
project can never be disabled.
|
|
3796
|
+
- **Disabled projects now appear (greyed, with an "off" badge) in the admin Projects sidebar**
|
|
3797
|
+
instead of being hidden, so a disabled-by-default project stays discoverable.
|
|
3798
|
+
|
|
3799
|
+
### Fixed
|
|
3800
|
+
|
|
3801
|
+
- **The Manage Projects enable/disable toggle now shows for protected non-core projects.** It was
|
|
3802
|
+
suppressed for every protected project, which left Domma Docs impossible to switch on from the UI.
|
|
3803
|
+
|
|
3804
|
+
## [0.25.15] - 2026-06-15
|
|
3805
|
+
|
|
3806
|
+
### Fixed
|
|
3807
|
+
|
|
3808
|
+
- **Navbar `appearOnHover` did nothing - it was only half-wired across the stack.** Toggling a
|
|
3809
|
+
menu's "open dropdowns on hover" had no visible effect, and a hard-refresh couldn't help because
|
|
3810
|
+
three separate links were missing:
|
|
3811
|
+
- **Server:** `renderer.js` now injects `appearOnHover` into `window.__CMS_NAV__` for both the
|
|
3812
|
+
page and blog render paths (it was being dropped, so the flag never reached the client).
|
|
3813
|
+
- **Client:** `public/js/site.js` now acts on the flag. The Domma navbar opens dropdowns on
|
|
3814
|
+
click only and has no hover option, so when `appearOnHover` is set we toggle Domma's own
|
|
3815
|
+
`.open` class on `mouseenter`/`mouseleave`. Desktop only (≥993px) - the mobile drawer keeps
|
|
3816
|
+
tap-to-expand.
|
|
3817
|
+
- **Cache:** menu saves now invalidate the response cache. `menus.js` never imported the cache
|
|
3818
|
+
module, so on a production site (response cache on) editing *any* menu - content, behaviour,
|
|
3819
|
+
style, or slot mapping - left the `nav`/`site`-tagged public pages stale until TTL or restart.
|
|
3820
|
+
Create/update/delete and `setLocations` now call `cache.invalidateTags(['nav', 'site'])`.
|
|
3821
|
+
|
|
3822
|
+
Site-owned `public/css/site.css` is unchanged; existing sites need `server/` + `public/js/site.js`
|
|
3823
|
+
refreshed and the site process restarted.
|
|
3824
|
+
|
|
3825
|
+
---
|
|
3826
|
+
|
|
3827
|
+
## [0.25.14] - 2026-06-14
|
|
3828
|
+
|
|
3829
|
+
### Fixed
|
|
3830
|
+
|
|
3831
|
+
- **Admin Dashboard showed "No Traffic" despite analytics recording, and the traffic chart grew
|
|
3832
|
+
unbounded.** The analytics plugin decorates `fastify.analytics` inside its own encapsulated
|
|
3833
|
+
(prefixed) scope, so the core dashboard route could never read it - `daily`/`journeys` were always
|
|
3834
|
+
empty. The dashboard now reads the analytics counters (`plugins/analytics/data/{daily,journeys}.json`,
|
|
3835
|
+
with a pre-2.1 root fallback) straight off disk and computes traffic/top-pages/realtime/spikes/journeys
|
|
3836
|
+
with logic ported from the analytics plugin (kept dependency-free). The traffic chart canvas is now
|
|
3837
|
+
wrapped in a fixed-height container so Chart.js (`maintainAspectRatio:false`) can't grow it to full
|
|
3838
|
+
page height.
|
|
3839
|
+
|
|
3840
|
+
---
|
|
3841
|
+
|
|
3842
|
+
## [0.25.12] - 2026-06-13
|
|
3843
|
+
|
|
3844
|
+
### Fixed
|
|
3845
|
+
|
|
3846
|
+
- **Analytics counters are now stored under `plugins/analytics/data/`** (plugin v2.1.0). They
|
|
3847
|
+
previously lived in the plugin root, which meant a normal plugin push - the manager's
|
|
3848
|
+
`siteCmsUpdater`/`copyPlugin` and `npx domma-cms update` both replace plugin code wholesale and
|
|
3849
|
+
preserve only the `data/` carve-out - reset each site's live analytics to the distributed copy on
|
|
3850
|
+
every update. Moving the counters into `data/` makes both update paths preserve them
|
|
3851
|
+
automatically. A one-shot boot migration (`migrateToDataDir`) relocates any pre-2.1 root files on
|
|
3852
|
+
first start; `.gitignore`, the new-project template, the `update` self-heal and the `files`
|
|
3853
|
+
allowlist all point at `plugins/analytics/data/`.
|
|
3854
|
+
|
|
3855
|
+
---
|
|
3856
|
+
|
|
3857
|
+
## [0.25.11] - 2026-06-13
|
|
3858
|
+
|
|
3859
|
+
### Fixed
|
|
3860
|
+
|
|
3861
|
+
- **Analytics/dashboard data died on every update and was never tracked reliably.** The analytics
|
|
3862
|
+
plugin's runtime counters (`daily.json`, `lifetime.json`, `journeys.json`, legacy `stats.json`)
|
|
3863
|
+
were treated as source: new projects committed them, the npm package shipped them, and
|
|
3864
|
+
`domma-cms update` overwrote each site's live counters with the bundled copies - so accumulated
|
|
3865
|
+
analytics were repeatedly reset to a stale seed. This recurred because an earlier fix only
|
|
3866
|
+
gitignored the old single-file `stats.json`, and the later split into three files never updated
|
|
3867
|
+
the ignore list.
|
|
3868
|
+
- `bin/cli.js`: the new-project `.gitignore` template now ignores all four counter files.
|
|
3869
|
+
- `bin/update.js`: the `.gitignore` self-heal (previously `.domma-backups/` only) now also ensures
|
|
3870
|
+
the four files are ignored - existing installs are fixed automatically on their next update.
|
|
3871
|
+
`scaffold.reset` in the analytics manifest lists all four, so the updater now **preserves** live
|
|
3872
|
+
`journeys.json`/`stats.json` instead of wiping them.
|
|
3873
|
+
- `package.json` `files`: `!plugins/analytics/*.json` negations exclude the counters from the
|
|
3874
|
+
published package (also stops shipping real visitor paths/counts).
|
|
3875
|
+
|
|
3876
|
+
---
|
|
3877
|
+
|
|
3878
|
+
## [0.25.10] - 2026-06-13
|
|
3879
|
+
|
|
3880
|
+
### Fixed
|
|
3881
|
+
|
|
3882
|
+
- **Components (and API endpoints) never appeared in a project's sidebar submenu.** 0.25.8 added
|
|
3883
|
+
Components as a project artefact type to the project Overview tiles, the per-project routes, and
|
|
3884
|
+
the server-side enumeration - but the admin sidebar's per-project sub-tree (`buildProjectsSubtree`
|
|
3885
|
+
in `sidebar-renderer.js`) carries its **own** hardcoded artefact-type list, which still listed only
|
|
3886
|
+
9 types. Components and APIs were missing from it, so even a project with components tagged (e.g.
|
|
3887
|
+
4 tagged to a project) showed no "Components" row under it in the sidebar. Added both to that list;
|
|
3888
|
+
the submenu now matches the Overview tiles. (As with every type, a row only shows when the project
|
|
3889
|
+
has ≥1 of that artefact - the always-visible entry point is the Overview tile.)
|
|
3890
|
+
|
|
3891
|
+
---
|
|
3892
|
+
|
|
3893
|
+
## [0.25.9] - 2026-06-13
|
|
3894
|
+
|
|
3895
|
+
### Fixed
|
|
3896
|
+
|
|
3897
|
+
- **Navbar dropdown child items rendered at the wrong (smaller) font size.** Only the dropdown
|
|
3898
|
+
*toggle* (the top-level item that opens a submenu) was normalised to match the regular nav-link
|
|
3899
|
+
size at each breakpoint; the first-level child items (`.navbar-dropdown-item`) were left at
|
|
3900
|
+
Domma's base component size, so dropdown children visibly mismatched the rest of the menu. The
|
|
3901
|
+
per-breakpoint normalisation now covers the child items too (scoped to `#site-navbar`), on both
|
|
3902
|
+
the desktop dropdown and the mobile accordion drawer. Site-owned file - existing sites need
|
|
3903
|
+
`public/css/site.css` refreshed.
|
|
3904
|
+
|
|
3905
|
+
---
|
|
3906
|
+
|
|
3907
|
+
## [0.25.8] - 2026-06-13
|
|
3908
|
+
|
|
3909
|
+
### Added
|
|
3910
|
+
|
|
3911
|
+
- **Components are now a project artefact type.** Domma `.dmc` components can be filed under a
|
|
3912
|
+
project just like blocks: the component editor gains a **Project** field, the project Overview
|
|
3913
|
+
shows a **Components** count tile, and `#/projects/:slug/components` lists a project's components.
|
|
3914
|
+
Membership is stored in each component's `.meta.json` sidecar (`meta.project`), resolved the same
|
|
3915
|
+
way as every other artefact (untagged → core). The components API list/get endpoints now enforce
|
|
3916
|
+
project access-scope (`canSeeArtefact`), and the project delete-guard and "untag all" cover
|
|
3917
|
+
components too.
|
|
3918
|
+
|
|
3919
|
+
### Changed
|
|
3920
|
+
|
|
3921
|
+
- **List-view action buttons are now icon + tooltip** across Collections, Actions, Forms, Views,
|
|
3922
|
+
Roles, Users, Blocks, Components, and Collection Entries - replacing the text buttons for a more
|
|
3923
|
+
compact, consistent action column.
|
|
3924
|
+
|
|
3925
|
+
---
|
|
3926
|
+
|
|
3927
|
+
## [0.25.7] - 2026-06-13
|
|
3928
|
+
|
|
3929
|
+
### Fixed
|
|
3930
|
+
|
|
3931
|
+
- **Mobile navbar drawer was unclickable / dimmed (regression from 0.25.6)** - the slide-in
|
|
3932
|
+
drawer opened but appeared masked out and swallowed every click. The sticky navbar is
|
|
3933
|
+
`z-index:100`, which makes it its own stacking context; the drawer (`.navbar-collapse`)
|
|
3934
|
+
lives inside it while the backdrop is a `<body>` child at `z-index:1090`. Because the
|
|
3935
|
+
navbar's context sat *below* the backdrop, the entire navbar subtree - drawer included -
|
|
3936
|
+
painted under the backdrop regardless of the drawer's own z-index. While the drawer is
|
|
3937
|
+
open we now lift the navbar above the backdrop (`body.dm-drawer-open #site-navbar`), so
|
|
3938
|
+
the drawer sits on top and is fully interactive. Existing sites need `public/css/site.css`
|
|
3939
|
+
refreshed (carried automatically by a normal `update`).
|
|
3940
|
+
|
|
3941
|
+
---
|
|
3942
|
+
|
|
3943
|
+
## [0.25.6] - 2026-06-13
|
|
3944
|
+
|
|
3945
|
+
### Fixed
|
|
3946
|
+
|
|
3947
|
+
- **Mobile navbar submenus hung off the screen** - parent (dropdown) items in the mobile
|
|
3948
|
+
drawer rendered their submenus as absolutely-positioned floating cards, so a long list
|
|
3949
|
+
(e.g. a World Cup menu) overflowed past the edge of the narrow drawer and could only be
|
|
3950
|
+
reached by awkward scrolling. Submenus now expand **inline as an accordion**: the panel
|
|
3951
|
+
is full-width, children are indented, and the drawer scrolls as one. The parent caret is
|
|
3952
|
+
parked on the right edge of a full-width tap row so it clearly reads as expandable.
|
|
3953
|
+
The drawer breakpoint is also raised to `992px` to match Domma's desktop cutover (993px),
|
|
3954
|
+
closing the 769-992px gap where the hamburger appeared but the old default collapse (with
|
|
3955
|
+
the same overflow bug) leaked through. Existing sites need `public/css/site.css` refreshed
|
|
3956
|
+
(site-owned file).
|
|
3957
|
+
|
|
3958
|
+
---
|
|
3959
|
+
|
|
3960
|
+
## [0.25.5] - 2026-06-12
|
|
3961
|
+
|
|
3962
|
+
### Added
|
|
3963
|
+
|
|
3964
|
+
- **Per-row insertion in the menu editor** - every row (items and separators) gains "Add item
|
|
3965
|
+
below" and "Add separator below" buttons that insert directly beneath that row, at that row's
|
|
3966
|
+
level. Building a dropdown divider is now one click on the child you want it under - no more
|
|
3967
|
+
add-at-bottom, arrow-walk, indent.
|
|
3968
|
+
|
|
3969
|
+
### Fixed
|
|
3970
|
+
|
|
3971
|
+
- **Separators were invisible on dark themes** - the editor's separator line, the admin
|
|
3972
|
+
sidebar divider, and the navbar dropdown divider all relied on black (or surface-colour)
|
|
3973
|
+
fallbacks. The editor row now shows a labelled "SEPARATOR" line; all divider fallbacks are
|
|
3974
|
+
neutral gray. Existing sites need `public/css/site.css` refreshed for the navbar part
|
|
3975
|
+
(site-owned file).
|
|
3976
|
+
|
|
3977
|
+
---
|
|
3978
|
+
|
|
3979
|
+
## [0.25.4] - 2026-06-12
|
|
3980
|
+
|
|
3981
|
+
### Fixed
|
|
3982
|
+
|
|
3983
|
+
- **Menu editor separator rows wrapped across three lines** - 0.25.3 added the movement buttons
|
|
3984
|
+
but not the matching CSS: the separator row's grid was still sized for grip/line/remove only.
|
|
3985
|
+
The grid now accommodates the full control set, and rows sharpen on hover.
|
|
3986
|
+
|
|
3987
|
+
---
|
|
3988
|
+
|
|
3989
|
+
## [0.25.3] - 2026-06-12
|
|
3990
|
+
|
|
3991
|
+
### Fixed
|
|
3992
|
+
|
|
3993
|
+
- **Menu separators are now movable and actually render.** Three stacked issues:
|
|
3994
|
+
- The menu editor rendered separator rows with only a Remove button - and "Add separator"
|
|
3995
|
+
appends at the bottom, so separators were stuck there. Separator rows now carry the same
|
|
3996
|
+
up/down/indent/outdent controls as items (indenting *under* a separator is refused - they
|
|
3997
|
+
can't hold children).
|
|
3998
|
+
- The public navbar stripped separators entirely. Separators inside dropdown submenus now
|
|
3999
|
+
translate to the navbar component's native divider (`navbar-dropdown-divider`, styled by
|
|
4000
|
+
Domma's elements.css). Top-level separators are still stripped - a horizontal navbar has no
|
|
4001
|
+
divider representation.
|
|
4002
|
+
- The `[menu]` shortcode and footer renderers emitted separator markup with no CSS - the
|
|
4003
|
+
`.dm-menu-sep` / `.footer-sep` divider styles now ship in `public/css/site.css`.
|
|
4004
|
+
- Note for existing sites: `public/js/site.js` and `public/css/site.css` are site-owned and
|
|
4005
|
+
not touched by rolling updates - refresh them per site to pick up the navbar/divider parts.
|
|
4006
|
+
|
|
4007
|
+
---
|
|
4008
|
+
|
|
4009
|
+
## [0.25.2] - 2026-06-10
|
|
4010
|
+
|
|
4011
|
+
### Fixed
|
|
4012
|
+
|
|
4013
|
+
- **Inline code and fenced blocks inside `[reveal]`/effects shortcodes rendered as literal
|
|
4014
|
+
`undefined`** (or raw backticks). Two root causes in the markdown pipeline: code-region scrub
|
|
4015
|
+
placeholders were not namespaced per call, so a nested processor's restore consumed the outer
|
|
4016
|
+
call's placeholders and looked them up in the wrong store; and the effects handlers parsed the
|
|
4017
|
+
still-scrubbed body, so code regions missed markdown rendering. Placeholders now carry a
|
|
4018
|
+
per-call id, and effects handlers restore the body before their nested parse (the existing
|
|
4019
|
+
tabs-handler pattern). Regression-tested for nesting, fences, and placeholder leakage.
|
|
4020
|
+
|
|
4021
|
+
---
|
|
4022
|
+
|
|
4023
|
+
## [0.25.1] - 2026-06-10
|
|
4024
|
+
|
|
4025
|
+
### Changed
|
|
4026
|
+
|
|
4027
|
+
- **API endpoints are now collection-scoped to their project** - an endpoint may only expose
|
|
4028
|
+
collections from its own project or core. Previously a definition could query any collection on
|
|
4029
|
+
the site, which let a project-scoped user publish another project's data through their own URL
|
|
4030
|
+
namespace. The builder's collection picker now filters to the selected project (+ core, marked
|
|
4031
|
+
as such), and the server enforces the same rule (400 on save). Pre-existing cross-project
|
|
4032
|
+
endpoints keep serving but cannot be re-saved until corrected - the editor flags them.
|
|
4033
|
+
- **Scoped users must be able to see a collection to expose it** - endpoint create/update now
|
|
4034
|
+
403s when the chosen collection is outside the caller's project access scope.
|
|
4035
|
+
- **The base `admin` role now includes `api-tokens` and `api-endpoints`** - seeded on fresh
|
|
4036
|
+
installs, and the boot self-heal now covers all base roles (append-only against their seed
|
|
4037
|
+
definitions), so existing fleet admins get API Builder and API Tokens after updating without a
|
|
4038
|
+
manual grant. Custom roles are never touched; note a *seeded* permission removed from a base
|
|
4039
|
+
role is re-added at boot - use a custom role to run with less than the base set.
|
|
4040
|
+
|
|
4041
|
+
---
|
|
4042
|
+
|
|
4043
|
+
## [0.25.0] - 2026-06-10
|
|
4044
|
+
|
|
4045
|
+
### Added
|
|
4046
|
+
|
|
4047
|
+
- **API Builder - custom REST endpoints over collection data** (`/api/x/*`). Build named, curated
|
|
4048
|
+
endpoints like `GET /api/x/world-cup/fixtures-day/:date` without writing code: a definition binds
|
|
4049
|
+
a clean URL path to a fixed collection query and is served by a single catch-all route.
|
|
4050
|
+
- **Definitions are data** - entries in a new `api-endpoints` preset collection (file-based,
|
|
4051
|
+
undeletable), project-scoped: the project is the endpoint's URL namespace and access boundary.
|
|
4052
|
+
- **Path params + query refinements** - filter values may embed `{{params.name}}` (from a
|
|
4053
|
+
`:name` path segment; missing → 400) or `{{query.name}}` (optional - the clause is dropped
|
|
4054
|
+
when absent). Values only ever feed the filter engine; there is no way to execute code.
|
|
4055
|
+
- **`list` and `single` modes**, sort/order/limit, and a response field allowlist.
|
|
4056
|
+
- **Auth reuses the collections API machinery** - `public`, `token` (project-bound API token,
|
|
4057
|
+
scopes enforced), or a role name. Same strictness rules as `/api/v1`.
|
|
4058
|
+
- **Static paths beat `:param` paths**; duplicate path shapes per project are refused (409);
|
|
4059
|
+
system-managed collections (e.g. `api-tokens`) cannot be exposed.
|
|
4060
|
+
- **Cached** - public-auth responses are cached and invalidated automatically when the
|
|
4061
|
+
collection's entries or the definition change.
|
|
4062
|
+
- **Admin: Data → API Builder** - list view plus a builder editor with live URL preview,
|
|
4063
|
+
schema-driven filter rows and field checkboxes, and a **try-it console** that sends a raw,
|
|
4064
|
+
credential-free request to the saved definition and renders status, pretty JSON, and a
|
|
4065
|
+
copy-as-curl line.
|
|
4066
|
+
- **Projects integration** - endpoints are the tenth artefact type: APIs tile on the project
|
|
4067
|
+
overview, Quick-add, and `/projects/:slug/apis` filtered list. Untag-all skips endpoints
|
|
4068
|
+
(their project is their URL namespace); a project with endpoints refuses deletion.
|
|
4069
|
+
- **Scaffolder** - recipes may declare `apiEndpoints: [...]`; created at apply time under the
|
|
4070
|
+
recipe's project, idempotent on re-apply.
|
|
4071
|
+
- New permission family `api-endpoints.{read, create, update, delete}` - the root role
|
|
4072
|
+
self-heals at boot; grant to other roles via the Roles editor.
|
|
4073
|
+
|
|
4074
|
+
### Fixed
|
|
4075
|
+
|
|
4076
|
+
- **Revoked API tokens could be resurrected** by an in-flight throttled `lastUsedAt` write racing
|
|
4077
|
+
the revoke (FileAdapter read-modify-write). The token service now serialises its own writes and
|
|
4078
|
+
drains them before revoke/update - revoke is final.
|
|
4079
|
+
|
|
4080
|
+
---
|
|
4081
|
+
|
|
4082
|
+
## [0.24.0] - 2026-06-10
|
|
4083
|
+
|
|
4084
|
+
### Added
|
|
4085
|
+
|
|
4086
|
+
- **External collections API with project-scoped API tokens** - collections are now first-class
|
|
4087
|
+
externally consumable resources, secured by machine credentials instead of public-or-human-JWT.
|
|
4088
|
+
- **`/api/v1/:slug[/:id]`** - a stable, versioned alias of the public collection endpoints
|
|
4089
|
+
(`/api/collections/:slug/public[...]`); same handlers, same query parameters. The documented
|
|
4090
|
+
surface for third-party integrations.
|
|
4091
|
+
- **API tokens** - a new `api-tokens` preset collection (always file-based, undeletable). Tokens
|
|
4092
|
+
are `dcms_` + 64 hex; only a SHA-256 hash is stored and the plaintext is shown **once**, at
|
|
4093
|
+
creation. Each token is bound to one project and only works on collections resolving to that
|
|
4094
|
+
project; optional `scopes: [{collection, verbs}]` narrow it to specific collections/verbs.
|
|
4095
|
+
Disable, expiry (`expiresAt`), and `lastUsedAt` tracking included.
|
|
4096
|
+
- **`access: 'token'` mode** - a third per-verb access mode in `schema.api` alongside `'public'`
|
|
4097
|
+
and role names. Strict: a token is only accepted in token mode, never as a role substitute,
|
|
4098
|
+
and a JWT is never accepted in token mode. Entries created via token auth are attributed
|
|
4099
|
+
`meta.createdBy: 'token:<id>'`.
|
|
4100
|
+
- **Read field allowlist** - `schema.api.read.fields: []` restricts which `data` fields the
|
|
4101
|
+
public/external read endpoints return (admin endpoints unaffected).
|
|
4102
|
+
- **Admin UI** - new System → API Tokens page (create with copy-once reveal, enable/disable,
|
|
4103
|
+
revoke); the collection editor's API tab gains the Token access mode and a read-fields input.
|
|
4104
|
+
- **Token management API** - `/api/api-tokens` CRUD guarded by a new `api-tokens.{read, create,
|
|
4105
|
+
update, delete}` permission family. Existing roles are **not back-filled** (grant via the Roles
|
|
4106
|
+
editor) - but `roles.js seed()` now self-heals the level-0 root role with any missing registry
|
|
4107
|
+
resources at boot, so super-admins always see newly added permission families.
|
|
4108
|
+
- **Scaffolder** - recipes may declare `apiTokens: [{name, scopes?}]`; tokens are generated at
|
|
4109
|
+
apply time under the recipe's project and surfaced once in the apply summary. Re-applying never
|
|
4110
|
+
re-issues an existing token.
|
|
4111
|
+
- **`ensureSidebarItem()`** - sidebar migration gains an append-if-absent helper so updates can
|
|
4112
|
+
surface new admin pages on existing installs without clobbering admin edits (no-op when the
|
|
4113
|
+
item's URL exists anywhere in the persisted menu).
|
|
4114
|
+
|
|
4115
|
+
### Changed
|
|
4116
|
+
|
|
4117
|
+
- `admin/js/views/collection-editor.js` was de-minified (esbuild one-liner → readable source;
|
|
4118
|
+
identifiers remain mangled) so it can be reviewed and edited like the rest of `admin/js`.
|
|
4119
|
+
|
|
4120
|
+
### Fixed
|
|
4121
|
+
|
|
4122
|
+
- **Project Quick-add navigation did nothing for forms, views, users, roles, and actions** - the
|
|
4123
|
+
Domma router compares raw hash segments, so `#/forms/new?project=x` never matched `/forms/new`
|
|
4124
|
+
and rendered not-found. The admin's router patch now strips query strings before matching.
|
|
4125
|
+
- **Core project lists showed empty despite non-zero count tiles** - the client-side project
|
|
4126
|
+
filter only matched explicit tags and never applied core's ownership-by-exclusion. It now
|
|
4127
|
+
mirrors the server's `resolveArtefactProject()` fallback (untagged → core) in both
|
|
4128
|
+
`filterByProject()` and the roles view's inline filter.
|
|
4129
|
+
|
|
4130
|
+
---
|
|
4131
|
+
|
|
4132
|
+
## [0.23.0] - 2026-06-10
|
|
4133
|
+
|
|
4134
|
+
### Added
|
|
4135
|
+
|
|
4136
|
+
- **Built-in Core project** - every site now has a protected `core` project representing the core site itself. It
|
|
4137
|
+
owns **everything not assigned to another project** - by resolution, not by tagging: any artefact without a
|
|
4138
|
+
`meta.project` tag, and any page no other project's `rootUrl` claims, belongs to Core automatically. Nothing is
|
|
4139
|
+
ever stamped; "no tag" *is* Core membership, so artefacts created later (by plugins, the API, or hand-edited
|
|
4140
|
+
files) can never become strays.
|
|
4141
|
+
- Seeded at boot (create-if-absent) - **existing sites gain Core automatically on first start after updating**,
|
|
4142
|
+
with no migration and no content rewrites. A pre-existing user-created project with the slug `core` is adopted
|
|
4143
|
+
rather than overwritten.
|
|
4144
|
+
- **Not removable** - delete and untag-all are refused at both service and API level (403); the admin UI shows a
|
|
4145
|
+
*System* badge, hides the delete button, and locks the Root URL field. Name, description, icon and sort order
|
|
4146
|
+
remain editable.
|
|
4147
|
+
- **Never an access boundary** - Core artefacts are visible to all authenticated users regardless of their
|
|
4148
|
+
`projects: []` scope, so scoped users always keep the site's pages, menus and blocks. Core always appears in
|
|
4149
|
+
every user's project list, so the sidebar's Projects section now renders on every site.
|
|
4150
|
+
- Page frontmatter `project: null` (the old "untagged" opt-out) is deprecated and now resolves to `core`.
|
|
4151
|
+
Longest-prefix `rootUrl` matching is unchanged - scaffolded projects still win over Core for their own URLs.
|
|
4152
|
+
- **`npm test`** - the repo gains a canonical test script (`node --test --test-concurrency=1`); files run serially
|
|
4153
|
+
because test processes share the live `content/` and `config/` directories.
|
|
4154
|
+
|
|
4155
|
+
### Fixed
|
|
4156
|
+
|
|
4157
|
+
- **Form submissions lost when the backing collection was missing** - a form whose collection action pointed at a
|
|
4158
|
+
slug that was never provisioned was permanently broken: the admin submissions list 404'd and every public
|
|
4159
|
+
submission was rejected with a 500 - and lost. Forms now **self-heal**: the missing collection is provisioned
|
|
4160
|
+
from the form's own fields on first submission (admin-only API access, since submissions can contain PII), the
|
|
4161
|
+
submission is saved instead of dropped, and the submissions list shows empty rather than erroring. Explicitly
|
|
4162
|
+
disabled collection storage is still respected.
|
|
4163
|
+
|
|
4164
|
+
## [0.22.6] - 2026-06-08
|
|
4165
|
+
|
|
4166
|
+
### Changed
|
|
4167
|
+
|
|
4168
|
+
- **Analytics is now a core plugin** - always-on and un-disableable, so page-view recording can never be silently
|
|
4169
|
+
switched off. The analytics Top Pages filter now uses the Domma form input (was a foreign Bootstrap class).
|
|
4170
|
+
- **Admin sidebar auto-groups plugin nav** - core plugins under a **Tools** folder, optional plugins under a
|
|
4171
|
+
**Plugins** folder led by *Manage Plugins*. The standalone System → Plugins item is relocated at render time - no
|
|
4172
|
+
menu migration needed. Grouping logic extracted to `admin/js/lib/sidebar-grouping.js` with unit tests.
|
|
4173
|
+
- **Admin role seed grants the `plugins` permission** so child-site admins can see and manage their own plugins.
|
|
4174
|
+
|
|
4175
|
+
## [0.22.5] - 2026-06-08
|
|
4176
|
+
|
|
4177
|
+
### Fixed
|
|
4178
|
+
|
|
4179
|
+
- **Plugin sidebar items missing** - plugins that declare their navigation in `plugin.json` (`admin.sidebar`) were not
|
|
4180
|
+
appearing in the admin sidebar. The renderer reads plugin nav from `/api/sidebar/registered-items`, but manifest
|
|
4181
|
+
`admin.sidebar` entries were never registered there. They are now bridged into the registry at plugin load, so
|
|
4182
|
+
plugin sections (e.g. Site Manager) render again. Regression introduced when the sidebar moved to the registry
|
|
4183
|
+
mechanism in 0.22.4.
|
|
4184
|
+
|
|
4185
|
+
## [0.22.4] - 2026-06-08
|
|
4186
|
+
|
|
4187
|
+
### Added
|
|
4188
|
+
|
|
4189
|
+
- **Create artefacts straight from a project's Overview** - every artefact card now carries a **+**. Pages,
|
|
4190
|
+
collections, menus and blocks open a slide-over and are created already tagged to the project *without leaving the
|
|
4191
|
+
page* (the card's count ticks up immediately). Forms, actions, views, roles and users open their editor with the
|
|
4192
|
+
project pre-selected.
|
|
4193
|
+
- **Project Overview redesign** - a hero header with the project's identity, colour-coded artefact cards, and
|
|
4194
|
+
**Created / Creator / Updated** metadata badges. Projects now record who created them.
|
|
4195
|
+
|
|
4196
|
+
### Fixed
|
|
4197
|
+
|
|
4198
|
+
- **Projects → Pages listed nothing** - the pages list API dropped each page's resolved project, so the per-project
|
|
4199
|
+
filter matched no rows. The resolved project is now included in the listing.
|
|
4200
|
+
- **Collections and blocks created under a project weren't tagged** - their create routes silently discarded the
|
|
4201
|
+
`meta.project` tag, so the new artefact never appeared under the project. The tag is now persisted.
|
|
4202
|
+
- **Block "untag all" / project block counts** - the projects service referenced helpers that don't exist
|
|
4203
|
+
(`readBlock` / `writeBlock`), making block untagging a silent no-op. It now uses the real block read/save helpers.
|
|
4204
|
+
|
|
4205
|
+
## [0.22.3] - 2026-05-29
|
|
4206
|
+
|
|
4207
|
+
### Changed
|
|
4208
|
+
|
|
4209
|
+
- **Menu editor - Visibility is now a dropdown** - the per-item Visibility field was a free-text box (you had to know
|
|
4210
|
+
to type a role name). It is now a select populated from the site's roles: *Everyone / Public / `<role>` and above /
|
|
4211
|
+
Private*, preserving any existing custom value. Short help text was added under the Label, URL, Visibility,
|
|
4212
|
+
Permission, Badge-count and Render-as fields so the item editor is self-explanatory.
|
|
4213
|
+
|
|
4214
|
+
## [0.22.2] - 2026-05-29
|
|
4215
|
+
|
|
4216
|
+
### Fixed
|
|
4217
|
+
|
|
4218
|
+
- **Build dropped `.mjs` modules from the published package** - the minify globs in `scripts/build.js` only matched
|
|
4219
|
+
`*.js`, so browser ESM modules using the `.mjs` extension (e.g. `public/js/menu-decor.mjs`) were silently excluded
|
|
4220
|
+
from `_publish/`. This shipped 0.22.1 with the menu decoration code importing `/public/js/menu-decor.mjs` but
|
|
4221
|
+
without the file itself, 404'ing the import and breaking every page. The globs now include `*.mjs`.
|
|
4222
|
+
|
|
4223
|
+
### Added
|
|
4224
|
+
|
|
4225
|
+
- **Asset-integrity build gate** (`scripts/verify-assets.mjs`) - after building `_publish/`, every local ESM import
|
|
4226
|
+
under `admin/` and `public/` is statically resolved against the files that actually ship; any dangling import fails
|
|
4227
|
+
the build, so an incomplete release can no longer be packaged or published. Also runnable standalone:
|
|
4228
|
+
`node scripts/verify-assets.mjs [root]`.
|
|
4229
|
+
|
|
4230
|
+
## [0.6.21] - 2026-03-25
|
|
4231
|
+
|
|
4232
|
+
### Added
|
|
4233
|
+
|
|
4234
|
+
- **Page editor - CSS Editor toggle** - new `</>` toolbar button (left of the Split View icon) swaps the left pane
|
|
4235
|
+
between the Markdown editor and the Custom CSS editor. CSS changes save automatically alongside the page in a single
|
|
4236
|
+
operation with a combined "Page and CSS saved." toast.
|
|
4237
|
+
- **Page editor - inline preview honours Custom CSS** - the split-pane preview now injects the site's Custom CSS so
|
|
4238
|
+
the inline preview matches the live page appearance.
|
|
4239
|
+
- **Page editor - batch history delete** - the History tab now shows a checkbox per version row plus a Select All
|
|
4240
|
+
checkbox. Selecting one or more entries reveals a "Delete Selected" button which bulk-deletes via a single
|
|
4241
|
+
`POST /api/versions/bulk-delete/*` request.
|
|
4242
|
+
- **Page editor - IDE-style keybindings** - the Markdown and CSS editor textareas now support:
|
|
4243
|
+
`Tab` (indent 2 spaces), `Shift+Tab` (dedent 2 spaces), `Ctrl+X` (cut line / selection), `Ctrl+C` (copy line /
|
|
4244
|
+
selection), `Ctrl+Z` (undo), `Ctrl+Y` (redo). Each action pops a brief info toast.
|
|
4245
|
+
- **Page editor - Colour Picker** - new Insert → Colour Picker entry opens a centred overlay with a native colour
|
|
4246
|
+
picker, an editable hex input, a Copy button (copies hex to clipboard), and an Insert button (drops the hex code
|
|
4247
|
+
at the cursor). Click the backdrop to dismiss.
|
|
4248
|
+
- **`[collection]` / `[view]` / `[block]` shortcodes - `class` and `id` support** - all three shortcodes now accept
|
|
4249
|
+
`class` and `id` attributes. For `[collection]` and `[view]` the attributes are applied to an outer wrapper div;
|
|
4250
|
+
for `[block]` they are applied directly to the `dm-static-block` div.
|
|
4251
|
+
- **Page editor help - keybindings reference** - the Editor Reference slideover (Basics tab) now includes a full
|
|
4252
|
+
keyboard shortcuts table.
|
|
4253
|
+
- **Page editor help - CSS Editor section** - the Editor Reference slideover (Advanced tab) now documents the CSS
|
|
4254
|
+
Editor toggle, view mode buttons, and combined save behaviour.
|
|
4255
|
+
- **Page editor help - Colour Picker section** - the Editor Reference slideover (Advanced tab) documents the new
|
|
4256
|
+
Insert → Colour Picker workflow.
|
|
4257
|
+
- **Page editor help - copy code buttons** - every code snippet in the Editor Reference slideover now has a copy icon
|
|
4258
|
+
button; clicking copies the snippet to the clipboard and toasts "Copied!".
|
|
4259
|
+
- **Versions API - bulk delete endpoint** - `POST /api/versions/bulk-delete/*` accepts `{ filenames: [...] }` and
|
|
4260
|
+
deletes all listed versions in a single read/write of `_meta.json`.
|
|
4261
|
+
|
|
4262
|
+
### Changed
|
|
4263
|
+
|
|
4264
|
+
- **Page editor help - Data tab** - `[collection]` and `[view]` attribute tables updated to include `class` and `id`.
|
|
4265
|
+
|
|
4266
|
+
---
|
|
4267
|
+
|
|
4268
|
+
## [0.6.20] - 2026-03-25
|
|
4269
|
+
|
|
4270
|
+
### Added
|
|
4271
|
+
|
|
4272
|
+
- **`[block]` shortcode** - renders a single block template with static inline data, no Collection required.
|
|
4273
|
+
Usage: `[block template="contact-card" full_name="Jane Doe" email_address="jane@example.com" /]`. Any attribute
|
|
4274
|
+
beyond `template` is passed as data for `{{placeholder}}` substitution. Missing templates are silently removed;
|
|
4275
|
+
missing placeholder values render as empty strings; all values are HTML-escaped before substitution.
|
|
4276
|
+
- **Insert → Block in the page editor** - new guided modal in the markdown toolbar's Insert menu. Select a block
|
|
4277
|
+
template, fill in dynamically generated input fields (auto-detected from the template's `{{placeholders}}`), and
|
|
4278
|
+
see a live DOMPurify-sanitised preview before inserting. Builds the `[block ...]` shortcode and drops it at the
|
|
4279
|
+
cursor position.
|
|
4280
|
+
- **`[text]` shortcode** - inline text styling with granular control over size, weight, colour, font, transform,
|
|
4281
|
+
decoration, and letter-spacing. Full guided modal with live preview in the page editor Insert menu.
|
|
4282
|
+
|
|
4283
|
+
---
|
|
4284
|
+
|
|
4285
|
+
## [0.6.5] - 2026-03-17
|
|
4286
|
+
|
|
4287
|
+
### Added
|
|
4288
|
+
|
|
4289
|
+
- **`cols` attribute for `[collection display="block"]`** - block display mode now accepts a `cols` attribute (values
|
|
4290
|
+
2-6) to wrap entries in a Domma CSS Grid (`grid grid-cols-N gap-4`). Invalid or omitted values fall back to the
|
|
4291
|
+
existing stacked layout. Usage: `[collection slug="..." display="block" block="post-card" cols="3" /]`.
|
|
4292
|
+
- **`[header]` and `[footer]` sub-tags in `[card]`** - cards now support `[header]...[/header]` and
|
|
4293
|
+
`[footer]...[/footer]` sub-tags for rich Markdown content (bold, links, icons) in card headers and footers.
|
|
4294
|
+
- **Wedding and Christmas themes** - two new Domma themes (`wedding` and `christmas`) added to the theme selector in
|
|
4295
|
+
Site Settings.
|
|
4296
|
+
- **Hero gradient variants expanded** - `[hero]` shortcode `variant` attribute now supports 8 generic gradients (
|
|
4297
|
+
`gradient-purple`, `gradient-blue`, `gradient-green`, `gradient-sunset`, `gradient-ocean`, `gradient-rose`,
|
|
4298
|
+
`gradient-forest`, `gradient-night`) and 26 theme-specific variants (`gradient-{theme}-light/dark` for all 13 themes).
|
|
4299
|
+
Reference page at `/hero-gradients`.
|
|
4300
|
+
- **Hero scroll-reveal animation** - hero sections on public pages now slide up on entry with re-animation support on
|
|
4301
|
+
scroll.
|
|
4302
|
+
- **`npx domma-cms update` command** - new `bin/update.js` script for updating an existing project via npx; includes
|
|
4303
|
+
smart config merge (`bin/lib/config-merge.js`) that preserves user config values.
|
|
4304
|
+
- **Server port in setup script** - `npm run setup` now prompts for a custom port and writes it to `config/server.json`;
|
|
4305
|
+
CLI respects the configured port dynamically.
|
|
4306
|
+
- **CLAUDE.md for site developers** - AI assistant guide committed to the project root for use with Claude Code.
|
|
4307
|
+
- **Cookie and privacy policy pages** - `cookies.md` and `privacy.md` seed pages added (UK GDPR / PECR compliant);
|
|
4308
|
+
seeded via `npm run seed`.
|
|
4309
|
+
- **"Display as button" option in page editor** - checkbox + colour picker in the page editor to render the page CTA as
|
|
4310
|
+
a styled button.
|
|
4311
|
+
|
|
4312
|
+
### Changed
|
|
4313
|
+
|
|
4314
|
+
- **Collection schema format** - select field options now use `{ "value": "...", "label": "..." }` objects consistently
|
|
4315
|
+
across all bundled collection schemas; field type `"text"` normalised to `"string"`.
|
|
4316
|
+
- **`public/css/site.css` expanded** - additional utility classes and theme-aware CSS variables added for hero
|
|
4317
|
+
gradients, navbar dropdown icons, and scroll-reveal effects.
|
|
4318
|
+
- **`domma-js` upgraded** to `^0.20.2`.
|
|
4319
|
+
- **Shortcode documentation updated** - `docs/markdown-shortcodes.md` extended with `[header]`/`[footer]` card sub-tag
|
|
4320
|
+
examples, hero gradient reference table, and grid usage clarifications.
|
|
4321
|
+
|
|
4322
|
+
---
|
|
4323
|
+
|
|
4324
|
+
## [0.5.0] - 2026-03-13
|
|
4325
|
+
|
|
4326
|
+
### Added
|
|
4327
|
+
|
|
4328
|
+
- **Initial public release** of Domma CMS on npm.
|
|
4329
|
+
- **Admin SPA** - full single-page application built with the Domma framework; hash-based routing, role-aware sidebar, auto-refreshing JWT auth.
|
|
4330
|
+
- **File-based pages** - content stored as Markdown files with YAML frontmatter in `content/pages/`; filename maps directly to public URL.
|
|
4331
|
+
- **Server-side rendering** - public pages assembled server-side via Fastify and served as complete HTML; supports `{{headInject}}` and `{{bodyEndInject}}` plugin slots.
|
|
4332
|
+
- **Plugin system** - three-file structure (`plugin.json`, `plugin.js`, `config.js`); plugins can add backend routes, inject HTML into public pages, and register admin panel views and sidebar entries.
|
|
4333
|
+
- **Bundled plugins** - Form Builder (visual form creation, submissions, email/webhook actions), Analytics (flat-file page-view tracking), Back to Top button, Cookie Consent banner.
|
|
4334
|
+
- **Role-based permissions** - four built-in roles (`admin`, `manager`, `editor`, `subscriber`) stored as a preset Collection; roles and permissions fully manageable from the admin panel.
|
|
4335
|
+
- **Dynamic Collections** - schema-driven content collections with a file-based storage adapter by default; optional MongoDB adapter for Pro deployments.
|
|
4336
|
+
- **Markdown shortcodes** - server-side shortcode processing before Markdown parsing:
|
|
4337
|
+
- `[card]` - Domma card component with optional title.
|
|
4338
|
+
- `[grid]` / `[row]` / `[col]` - CSS Grid layout blocks.
|
|
4339
|
+
- `[slideover]` - slide-over panel triggered by a button.
|
|
4340
|
+
- `[dconfig]` - declarative page config (maps to `$.setup()`).
|
|
4341
|
+
- **Password reset flow** - time-limited reset tokens with email delivery via Nodemailer; configurable SMTP in `config/site.json`.
|
|
4342
|
+
- **Media management** - file upload (multipart), image processing via Sharp, media library in admin panel.
|
|
4343
|
+
- **Navigation editor** - drag-and-drop nav builder with dropdown support; persisted to `config/navigation.json`.
|
|
4344
|
+
- **Layouts and presets** - admin-managed layout presets stored in `config/presets.json`.
|
|
4345
|
+
- **Setup wizard** - interactive `npm run setup` script; creates admin account, sets site title and theme; safe to re-run.
|
|
4346
|
+
- **Utility scripts** - `seed` (sample content), `reset` (factory reset), `fresh` (reset + seed), `pro` (Pro upgrade helper).
|
|
4347
|
+
- **CLI scaffolder** - `npx domma-cms my-site` scaffolds a new project from the npm package.
|
|
4348
|
+
- **Configuration architecture** - all config in `config/*.json`; secrets-only `.env`; `getConfig()` / `saveConfig()` API for safe in-process reads and writes.
|
|
4349
|
+
- **Domma dist via npm** - `domma-js` resolved from `node_modules` at runtime; no copy step required.
|