@bygghammar/cms-platform 0.80.1 → 0.90.0-m7.10
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 +489 -0
- package/README.md +16 -2
- package/docs/EDIT-LOCKS.md +213 -0
- package/docs/POPUPS.md +102 -0
- package/migrations/20260902090000_dc072b25-8467-46ae-aef3-0ccdf40aa7eb.sql +274 -0
- package/package.json +31 -3
- package/scripts/create-cms-site.mjs +5 -4
- package/scripts/sync-migrations.mjs +27 -6
- package/src/backoffice/ContactDetailsView.tsx +19 -5
- package/src/backoffice/DomainsView.tsx +152 -25
- package/src/backoffice/EditLock.tsx +513 -0
- package/src/backoffice/EmailSettingsView.tsx +99 -0
- package/src/backoffice/FormEditorView.tsx +531 -512
- package/src/backoffice/FormSettingsView.tsx +16 -4
- package/src/backoffice/GlobalSectionBuilderView.tsx +42 -11
- package/src/backoffice/NavigationView.tsx +35 -11
- package/src/backoffice/PageBuilderView.tsx +51 -16
- package/src/backoffice/RichTextEditor.tsx +50 -14
- package/src/backoffice/SectionEditorView.tsx +18 -6
- package/src/backoffice/SectionFormView.tsx +184 -31
- package/src/backoffice/SiteSettingsView.tsx +14 -15
- package/src/backoffice/TrackingView.tsx +247 -208
- package/src/backoffice/forms/VersionHistory.tsx +4 -2
- package/src/backoffice/inspectors/blocks.tsx +22 -1
- package/src/backoffice/inspectors/fields.tsx +21 -0
- package/src/backoffice/routes/host.ts +58 -7
- package/src/backoffice/routes/index.tsx +117 -15
- package/src/backoffice/routes/pages.tsx +10 -6
- package/src/backoffice/seo/PageSeoPanelView.tsx +215 -197
- package/src/backoffice/shell/domain-guard.ts +27 -14
- package/src/backoffice/shell/index.ts +5 -0
- package/src/backoffice/shell/nav-model.ts +32 -23
- package/src/backoffice/shell/use-resolved-config.tsx +67 -0
- package/src/backoffice/tracking-drafts.ts +150 -0
- package/src/blocks/components/CtaBannerBlock.tsx +18 -12
- package/src/blocks/components/HeroFullBlock.tsx +25 -12
- package/src/blocks/components/MediaSplitBlock.tsx +91 -41
- package/src/blocks/components/PriceTableBlock.tsx +123 -89
- package/src/blocks/components/RichTextBlock.tsx +26 -4
- package/src/blocks/index.ts +8 -0
- package/src/blocks/optional-content.ts +82 -0
- package/src/blocks/registry.tsx +1 -0
- package/src/blocks/types.ts +6 -0
- package/src/cms/edit-lock.ts +491 -0
- package/src/config/modules.ts +208 -0
- package/src/config/site-config.ts +9 -31
- package/src/forms/FormRenderer.tsx +62 -3
- package/src/forms/submission-result.ts +35 -0
- package/src/forms/success-listeners.ts +46 -0
- package/src/host/index.tsx +27 -1
- package/src/modules/index.ts +28 -0
- package/src/modules/public-runtime.tsx +159 -0
- package/src/navigation/index.ts +29 -0
- package/src/popups/admin.tsx +123 -0
- package/src/popups/cms-bridges.ts +240 -0
- package/src/popups/destinations.tsx +147 -0
- package/src/popups/host.ts +91 -0
- package/src/popups/media-bridge.tsx +84 -0
- package/src/popups/register.ts +23 -0
- package/src/popups/runtime.tsx +128 -0
- package/src/runtime/server-entry.ts +8 -2
- package/src/server/content-sections-core.ts +22 -5
- package/src/server/domains-core.ts +124 -107
- package/src/server/edit-lock-adapter.ts +91 -0
- package/src/server/edit-lock-supabase-adapter.ts +135 -0
- package/src/server/edit-locks-core.ts +286 -0
- package/src/server/forms-core.ts +37 -2
- package/src/server/injections-core.ts +230 -49
- package/src/server/page-seo-save-core.ts +27 -0
- package/src/server/pages-core.ts +108 -11
- package/src/server/popup-optin-trigger.ts +111 -0
- package/src/server/popup-optin-webhook-core.ts +363 -0
- package/src/server/provider-secrets.ts +100 -4
- package/src/server/schemas.ts +74 -9
- package/src/server/webhook-url-guard.ts +199 -0
- package/src/site/SiteHeader.tsx +11 -5
- package/src/site/host.ts +172 -0
- package/src/site/locale-domains.ts +500 -0
- package/src/site/routes.ts +206 -38
- package/src/site/site-url.ts +80 -17
- package/src/tracking/injections.ts +52 -9
- package/src/types/css-modules.d.ts +6 -0
- package/src/ui/Reveal.tsx +74 -9
package/CHANGELOG.md
CHANGED
|
@@ -8,12 +8,458 @@ matches `package.json` exactly. See
|
|
|
8
8
|
Versions 0.1.0 through 0.78.0 predate this repository and were reconstructed
|
|
9
9
|
from the published npm tarballs.
|
|
10
10
|
|
|
11
|
+
## 0.90.0-m7.10
|
|
12
|
+
|
|
13
|
+
- Code & tracking drafts survive a refetch. A background reload replaced the
|
|
14
|
+
table under the administrator's hands: a brand new profile, a duplicate made
|
|
15
|
+
from an existing one, and edits typed but not yet saved all disappeared
|
|
16
|
+
without a word. What the server sends is merged into what is on screen
|
|
17
|
+
instead of replacing it.
|
|
18
|
+
- Tracking profiles apply in global, then language, then domain order, so a
|
|
19
|
+
domain profile refines the language one rather than replacing it. Hostname
|
|
20
|
+
matching is consistent throughout: an apex and its single `www.` are the same
|
|
21
|
+
site everywhere the decision is made.
|
|
22
|
+
- GTM, GA4, Meta Pixel, TikTok Pixel, verification meta tags and raw
|
|
23
|
+
head / body-start / body-end code all reach the server-rendered HTML at the
|
|
24
|
+
position each one requires, rather than depending on a client-side step to
|
|
25
|
+
fire.
|
|
26
|
+
- Raw custom code is injected byte for byte. `$&`, `` $` ``, `$'` and `$$` in a
|
|
27
|
+
snippet were read as replacement patterns and rewrote the markup around them;
|
|
28
|
+
a profile now reaches the page exactly as it was typed.
|
|
29
|
+
- Tracking is excluded from the configured backoffice path, preview routes, API
|
|
30
|
+
routes and server-function routes, so pixels no longer fire on administrative
|
|
31
|
+
traffic or on responses that are not pages.
|
|
32
|
+
- A tracking read that fails leaves the public site standing. The page renders
|
|
33
|
+
without injection, the failure is reported through an injectable logger, and
|
|
34
|
+
it is not cached — the next request retries instead of serving a fabricated
|
|
35
|
+
empty configuration for the rest of the cache window. A backend that answers
|
|
36
|
+
with an error rather than throwing counts as a failure; treating it as "no
|
|
37
|
+
profiles" made a broken deployment indistinguishable from an unconfigured one.
|
|
38
|
+
- The tracking cache is bounded rather than growing one entry per hostname
|
|
39
|
+
seen, and a successful create, update or delete drops it, so a change goes
|
|
40
|
+
live within the TTL instead of sitting behind whatever was cached first.
|
|
41
|
+
- A failed audit entry or a failed log line cannot undo a write that already
|
|
42
|
+
happened. Both are best-effort in both directions: the mutation stands and is
|
|
43
|
+
reported as the success it was, rather than coming back as an error whose
|
|
44
|
+
obvious retry inserts a second profile. That now holds even when the
|
|
45
|
+
host-supplied logger is itself what throws.
|
|
46
|
+
- A newly created profile whose id the server cannot establish is held until the
|
|
47
|
+
page is reloaded. The insert may well have landed, so pressing Save again
|
|
48
|
+
would create a duplicate firing every pixel twice; the screen says to reload,
|
|
49
|
+
and saving existing profiles is unaffected.
|
|
50
|
+
- Languages & domains has moved out of the main sidebar into System
|
|
51
|
+
administration, alongside the other installation-level screens.
|
|
52
|
+
- Code & tracking and Languages & domains require the `admin` role, matching the
|
|
53
|
+
authorization their server functions already enforce. Staff without it get a
|
|
54
|
+
plain no-access screen instead of a form that fails on Save.
|
|
55
|
+
- Storing a hostname and matching a request against one are now separate
|
|
56
|
+
questions. Canonical output carries the exact hostname the deployment
|
|
57
|
+
configured; matching still folds an apex and one `www.` together. A page is no
|
|
58
|
+
longer canonical on two of its own addresses.
|
|
59
|
+
- Canonical URLs, `og:url`, hreflang, `og:locale:alternate` and both sitemaps
|
|
60
|
+
are built from the configured canonical hostnames.
|
|
61
|
+
- Rows written before the domain validator existed are ignored for matching and
|
|
62
|
+
for every generated URL, instead of being normalized into a plausible-looking
|
|
63
|
+
address. `ftp://example.com` no longer reduces to `example.com` and wins a
|
|
64
|
+
request for it — which had decided the locale, the tracking profiles that fire
|
|
65
|
+
and the backoffice hostname alike.
|
|
66
|
+
- In explicit domain-mapping mode a supported language with no configured domain
|
|
67
|
+
is left out of hreflang and sitemap alternates, instead of borrowing another
|
|
68
|
+
locale's origin — or the origin of the current request — and advertising a URL
|
|
69
|
+
this deployment may not serve.
|
|
70
|
+
- Single-origin multilingual sites are supported by the sitemap helpers.
|
|
71
|
+
`sitemapEntries` and `sitemapIndexEntries` take the public origin and emit
|
|
72
|
+
absolute URLs only. Previously a locale sitemap could come out carrying
|
|
73
|
+
relative URLs, while the sitemap index had no domain mappings from which to
|
|
74
|
+
build entries at all.
|
|
75
|
+
- Public locale-domain projections omit `isAdminDomain`. Which hostname serves
|
|
76
|
+
the backoffice is not something an anonymous reader needs to be told.
|
|
77
|
+
- `SITE_URL` is validated before it can become the authoritative admin hostname,
|
|
78
|
+
and as configured rather than after normalizing — normalizing strips the
|
|
79
|
+
scheme, so a value nobody can serve validated cleanly and was allowed to
|
|
80
|
+
override the database and switch off the check that stops an unreachable
|
|
81
|
+
admin domain being saved.
|
|
82
|
+
- Admin-host resolution fails open. An unusable flagged row falls through to the
|
|
83
|
+
next source instead of stranding the backoffice, the screen names which source
|
|
84
|
+
the hostname came from, and the banner refreshes when a domain is saved.
|
|
85
|
+
- **Host integration required after upgrading.** Nothing below is done by this
|
|
86
|
+
package on the host's behalf:
|
|
87
|
+
- expose the shared Languages & domains screen at the host's canonical
|
|
88
|
+
`/admin/domains` route;
|
|
89
|
+
- keep the old `/domains` route as a compatibility redirect where applicable;
|
|
90
|
+
- register `getEffectiveAdminHost` among the backoffice host functions, so
|
|
91
|
+
the screen can say where the backoffice actually is;
|
|
92
|
+
- use `siteCanonicalHost()` when passing `SITE_URL` authority, not the raw
|
|
93
|
+
environment value;
|
|
94
|
+
- pass the public origin to `sitemapEntries` and `sitemapIndexEntries` on
|
|
95
|
+
single-origin sites;
|
|
96
|
+
- pass the configured `backofficePath` into `createServerEntry`, or tracking
|
|
97
|
+
will be injected into the backoffice;
|
|
98
|
+
- split public-safe locale-domain reads from authenticated admin reads; and
|
|
99
|
+
- ensure the tracking-create server function returns the newly created
|
|
100
|
+
profile id, which the screen now requires before it will treat a create as
|
|
101
|
+
complete.
|
|
102
|
+
- No database migration is included in this release, and no database data is
|
|
103
|
+
changed by it.
|
|
104
|
+
- Not included and not yet verified: Voyara's own integration is not part of
|
|
105
|
+
this package release, and neither staging nor production has been verified
|
|
106
|
+
with `0.90.0-m7.10`.
|
|
107
|
+
|
|
108
|
+
## 0.90.0-m7.9
|
|
109
|
+
|
|
110
|
+
- The email settings screen says where provider credentials actually go. An
|
|
111
|
+
administrator told only that "passwords are stored as server secrets" does the
|
|
112
|
+
one thing the sentence was meant to prevent and pastes the password into the
|
|
113
|
+
CMS field in front of them, so the screen names the configuration file, the
|
|
114
|
+
environment-variable names and the secret-name convention for that
|
|
115
|
+
installation instead. It stays value-free: no secret is read, returned or
|
|
116
|
+
rendered, and hosts supply their own deployment root through
|
|
117
|
+
`secretsLocation` — including `/`, which stays absolute.
|
|
118
|
+
- Optional block content whose localized value is empty is no longer rendered.
|
|
119
|
+
A heading or paragraph written in one locale and left blank in another used to
|
|
120
|
+
leave its empty markup behind in the locale that had nothing to say.
|
|
121
|
+
- Nested navigation items are editable. Only top-level entries could be changed,
|
|
122
|
+
so fixing a typo in a child link meant deleting it and building it again.
|
|
123
|
+
- Rich text holds up at length, and a block chooses how it appears. Each block
|
|
124
|
+
carries its own reveal mode; `Reveal` still defaults to `fade`, so every
|
|
125
|
+
already-published page looks exactly as it did.
|
|
126
|
+
- Cooperative editing locks stop two administrators overwriting each other. A
|
|
127
|
+
lock is taken when an editor opens a resource, renewed while it stays open and
|
|
128
|
+
released when it closes, and a second editor is told who holds it instead of
|
|
129
|
+
silently losing the earlier write. Pages, global sections, shared content,
|
|
130
|
+
navigation, contact details, forms, form defaults, form history and page SEO
|
|
131
|
+
are covered, and the refusal is enforced in the server cores — not only by
|
|
132
|
+
disabling controls in the interface.
|
|
133
|
+
- The lock's session token is private and the session match is exact.
|
|
134
|
+
`session_id` is never returned by any read: authenticated callers hold column
|
|
135
|
+
grants that omit it, the JSON shape drops it, and the administrator listing
|
|
136
|
+
returns a redacted row. A write against a live lock succeeds only for the
|
|
137
|
+
exact, non-empty session that holds it. Empty or malformed lock responses fail
|
|
138
|
+
closed rather than reading as "free", a stale asynchronous reply cannot revive
|
|
139
|
+
a lock that was already released, reopening an editor can no longer let its
|
|
140
|
+
own release overtake its next acquire, and a duplicated browser tab is given
|
|
141
|
+
its own session identity — Web Locks where the browser provides them, a
|
|
142
|
+
best-effort BroadcastChannel fallback where it does not.
|
|
143
|
+
- Lock storage is a host port, not a provider. `editLockActionCore` and
|
|
144
|
+
`assertEditable` reach the database only through `EditLockAdapter`
|
|
145
|
+
(`@bygghammar/cms-platform/server/edit-lock-adapter`) — five methods covering
|
|
146
|
+
acquire, status, release, list and the staff check — so an application on
|
|
147
|
+
ordinary PostgreSQL implements it with the SQL it already writes, with no
|
|
148
|
+
PostgREST client and no `supabase` object to imitate. Hosts that already build
|
|
149
|
+
`{ supabase: { rpc }, userId, claims }` for their other cores keep passing
|
|
150
|
+
exactly that: `createSupabaseEditLockAdapter` is applied for them
|
|
151
|
+
automatically and nothing on an existing host changes.
|
|
152
|
+
- Locks require a migration and host wiring, and change nothing until both are
|
|
153
|
+
done. `migrations/20260902090000_dc072b25-8467-46ae-aef3-0ccdf40aa7eb.sql`
|
|
154
|
+
creates the table and its functions; `docs/EDIT-LOCKS.md` is the host
|
|
155
|
+
contract: one authenticated server endpoint through `editLockActionCore`, one
|
|
156
|
+
`EditLockProvider` built with `createEditLockApi`, the non-empty
|
|
157
|
+
`editSessionId` forwarded to every protected write, `pageKey` passed to
|
|
158
|
+
`PageSeoPanelView`, and the `edit_locked:` message prefix preserved across the
|
|
159
|
+
host's own transport. Until then every screen behaves exactly as it did before
|
|
160
|
+
locks existed.
|
|
161
|
+
- The lock is a cooperative server preflight, not a transaction-atomic write
|
|
162
|
+
fence. The check and the mutation are separate statements, so a host needing
|
|
163
|
+
strict exclusion must put both in one transaction or RPC. Full-document exits
|
|
164
|
+
deliberately let the lock lapse by TTL — 120 seconds by default — rather than
|
|
165
|
+
race a reload against its own release.
|
|
166
|
+
- Verified on PostgreSQL 16 and 17: the migration applies in a single
|
|
167
|
+
transaction against a throwaway container carrying a host's real role,
|
|
168
|
+
authentication and user objects; the grants, the RLS policy and the
|
|
169
|
+
`SECURITY DEFINER` `search_path` come out as written; twenty simultaneous
|
|
170
|
+
acquires of one resource leave exactly one holder and no errors; and a
|
|
171
|
+
shadowing `pg_temp` object cannot reach inside a definer body.
|
|
172
|
+
- Not verified in this release: the migration has not been applied to any real
|
|
173
|
+
staging or production database, and the two-user real-browser lock behaviour
|
|
174
|
+
has not been exercised against a live host. Both remain required before
|
|
175
|
+
production use.
|
|
176
|
+
|
|
177
|
+
## 0.90.0-m7.8
|
|
178
|
+
|
|
179
|
+
- Requires `@bygghammar/lightweight-popup@>=0.2.0-m7.8`. `m7.6` and `m7.7` were
|
|
180
|
+
tagged and never published: two defects in the release workflow stopped them
|
|
181
|
+
after every gate had passed. The form-identity work they were meant to carry
|
|
182
|
+
is unchanged and ships here.
|
|
183
|
+
- The release workflow gets both corrections, proven in lightweight-popup:
|
|
184
|
+
the redundant post-checkout `git fetch` is gone (checkout already brings
|
|
185
|
+
every branch, and its credentials are removed on the way out), and
|
|
186
|
+
`npm pack`'s destination is created before it is packed into, with the same
|
|
187
|
+
directory carried to the tarball verification rather than spelled twice.
|
|
188
|
+
- `ops/verify-release-tag.mjs` checks `refs/remotes/origin/main` is present
|
|
189
|
+
before asking `merge-base` about it, so a checkout that fetched too little no
|
|
190
|
+
longer reports itself as a tag on an unmerged branch.
|
|
191
|
+
|
|
192
|
+
## 0.90.0-m7.6
|
|
193
|
+
|
|
194
|
+
- A form submitted from inside a popup now says so. `FormRenderer` and
|
|
195
|
+
`ManagedForm` take an opaque `context` that is merged into the submission,
|
|
196
|
+
and the popup module fills it with `{ popupKey }` — the form system keeps
|
|
197
|
+
knowing nothing about the features that use it. Without this the request was
|
|
198
|
+
indistinguishable from the same form filled on an ordinary page, so the
|
|
199
|
+
server stored an ordinary entry and every delivery configured on the popup
|
|
200
|
+
silently did nothing.
|
|
201
|
+
- `HostSubmitFormInput` gains `popupKey?: string`, the only popup-shaped value
|
|
202
|
+
the browser sends. Destination, endpoint, signing secret and which fields may
|
|
203
|
+
leave are all read on the server from the popup's stored configuration.
|
|
204
|
+
- Drafts and the builder preview carry no context, so a part-filled form and an
|
|
205
|
+
editor checking a layout cannot fire a campaign's webhook.
|
|
206
|
+
- Requires `@bygghammar/lightweight-popup@>=0.2.0-m7.6`, which is where the
|
|
207
|
+
identity comes from.
|
|
208
|
+
|
|
209
|
+
## 0.90.0-m7.5
|
|
210
|
+
|
|
211
|
+
- `BackofficeHost.pages.fns.listPages` is declared. It was what
|
|
212
|
+
`cmsPageDirectory` had always looked for, read through an index cast because
|
|
213
|
+
the contract did not name it — so a site could not register it without a type
|
|
214
|
+
error, and every site that had not silently got a picker with nothing in it.
|
|
215
|
+
The top-level lookup stays for anyone who registered it there anyway.
|
|
216
|
+
|
|
217
|
+
- Requires `@bygghammar/lightweight-popup@>=0.2.0-m7.5`. In `m7.4` an advanced
|
|
218
|
+
path rule could not be typed: the row was reclassified after the first
|
|
219
|
+
keystroke and disappeared. No change here beyond the floor — a site pairing
|
|
220
|
+
this with `m7.4` would get a picker whose rules field does not work.
|
|
221
|
+
|
|
222
|
+
## 0.90.0-m7.4
|
|
223
|
+
|
|
224
|
+
- `cmsPageDirectory` takes the site's routing engine and asks it for each
|
|
225
|
+
page's public path, instead of assuming the page key is the path. The home
|
|
226
|
+
page is why: its key is `home` and its address is `/`, so the popup page
|
|
227
|
+
picker would have offered an address that matches nothing.
|
|
228
|
+
- The Popups screen supplies that resolver from the `usePageUrl` hook the
|
|
229
|
+
backoffice already registers — no new host field, no second pages query. The
|
|
230
|
+
hook is called from its own component so it stays unconditional while the
|
|
231
|
+
page group remains optional.
|
|
232
|
+
- Requires `@bygghammar/lightweight-popup@>=0.2.0-m7.4`, which is where the
|
|
233
|
+
page picker itself lives.
|
|
234
|
+
|
|
235
|
+
## 0.90.0-m7.3
|
|
236
|
+
|
|
237
|
+
- Popup exclusions are derived from the configured `backofficePath`, so a site
|
|
238
|
+
that mounts its office at `/admin` or `/cms/backoffice` is covered without
|
|
239
|
+
naming it twice. Both the tree and its landing page are excluded: `/admin/**`
|
|
240
|
+
does not match `/admin`.
|
|
241
|
+
- `registerPopupPublicHost` accepts `excludedPaths` for a site's own private
|
|
242
|
+
routes — a checkout, a members' area. The CMS adds to that list and never
|
|
243
|
+
replaces it, and drops duplicates rather than repeating a path.
|
|
244
|
+
- Requires `@bygghammar/lightweight-popup@>=0.2.0-m7.3`, whose defaults no
|
|
245
|
+
longer name anyone's backoffice.
|
|
246
|
+
|
|
247
|
+
## 0.90.0-m7.2
|
|
248
|
+
|
|
249
|
+
- `backofficePaths` gains `popups` and `popupDestinations`, and both popup
|
|
250
|
+
mount points supply them to the package's navigation. The destinations screen
|
|
251
|
+
was reachable only by typing its address.
|
|
252
|
+
- Requires `@bygghammar/lightweight-popup@>=0.2.0-m7.2`, which is where the
|
|
253
|
+
navigation itself lives.
|
|
254
|
+
|
|
255
|
+
## 0.90.0-m7.1
|
|
256
|
+
|
|
257
|
+
First version published to npm rather than vendored as a tarball. Same code as
|
|
258
|
+
`0.80.2-popup.15`, renumbered for a real prerelease: the minor moves because the
|
|
259
|
+
popup integration gained form-field discovery, and the `m7` prerelease says this
|
|
260
|
+
is still waiting on staging acceptance. Published under the `m7` dist-tag, so
|
|
261
|
+
`latest` keeps pointing at 0.80.1.
|
|
262
|
+
|
|
263
|
+
- The optional `@bygghammar/lightweight-popup` peer range now names a
|
|
264
|
+
prerelease. `>=0.0.0` does not accept `0.2.0-m7.1` — a prerelease only
|
|
265
|
+
satisfies a range that mentions one at the same version — so the peer would
|
|
266
|
+
have read as unmet on every install.
|
|
267
|
+
|
|
268
|
+
## 0.80.2-popup.15
|
|
269
|
+
|
|
270
|
+
- `cmsFormDirectory` can describe a form's fields, so the popup builder offers
|
|
271
|
+
real fields to tick instead of asking an administrator to type column names.
|
|
272
|
+
Built from the listing the forms screen already reads — no second host
|
|
273
|
+
contract and no second query.
|
|
274
|
+
- Disabled fields, removed ones and the spam trap never reach the selector; a
|
|
275
|
+
field's own label is shown while its stable key is what gets stored.
|
|
276
|
+
- Vendors popup 0.1.0-dev.9.
|
|
277
|
+
|
|
278
|
+
## 0.80.2-popup.14
|
|
279
|
+
|
|
280
|
+
- A test delivery that failed before any request was made is no longer reported
|
|
281
|
+
as "The endpoint refused it". The bridge defaulted an absent category to
|
|
282
|
+
`http-error`; a missing signing secret arrives exactly that way, so the screen
|
|
283
|
+
sent administrators to debug a receiver that had never been contacted. An
|
|
284
|
+
unnamed failure is now `config-error`.
|
|
285
|
+
|
|
286
|
+
## 0.80.2-popup.13
|
|
287
|
+
|
|
288
|
+
- The destinations screen imports the popup admin stylesheet. It is a sibling
|
|
289
|
+
route rather than a child of the builder, so it loads on its own and nothing
|
|
290
|
+
else pulled the styling in — the first time anyone could reach it, twelve
|
|
291
|
+
`lwp-` elements rendered as plain text.
|
|
292
|
+
- A test now requires every module that mounts one of the package's admin
|
|
293
|
+
screens to import that stylesheet beside it.
|
|
294
|
+
|
|
295
|
+
## 0.80.2-popup.12
|
|
296
|
+
|
|
297
|
+
- Documents the popup bridges in `docs/POPUPS.md`: which port each CMS system
|
|
298
|
+
fills, why an unfilled port is not an integration, what crosses the media
|
|
299
|
+
boundary, and which webhook responsibility sits in which layer.
|
|
300
|
+
|
|
301
|
+
## 0.80.2-popup.11
|
|
302
|
+
|
|
303
|
+
- A destinations screen for popup opt-in webhooks: `popups/destinations` binds
|
|
304
|
+
the package's provider-neutral `DestinationsAdmin` to the host's own server
|
|
305
|
+
functions. A site that registers none gets an honest empty screen.
|
|
306
|
+
- The backoffice host contract gains the remaining optional webhook slots
|
|
307
|
+
(save, delete, send test, list deliveries).
|
|
308
|
+
- Vendors popup 0.1.0-dev.5.
|
|
309
|
+
|
|
310
|
+
## 0.80.2-popup.10
|
|
311
|
+
|
|
312
|
+
- The popup builder now gets the site's media library. `cmsMediaPicker()`
|
|
313
|
+
renders the same `MediaPickerDialog` every other backoffice screen uses, and
|
|
314
|
+
is passed into the popup admin adapters — the line whose absence left
|
|
315
|
+
administrators typing storage URLs by hand.
|
|
316
|
+
- Only a stable public address, a filename and any stored alt text cross into
|
|
317
|
+
the popup package. Uploading, listing and deleting stay with the library.
|
|
318
|
+
- Vendors popup 0.1.0-dev.4.
|
|
319
|
+
|
|
320
|
+
## 0.80.2-popup.9
|
|
321
|
+
|
|
322
|
+
- The backoffice host contract gains an optional , so
|
|
323
|
+
a site can offer opt-in destinations to the popup editor. A site without one
|
|
324
|
+
registers nothing and the picker does not appear.
|
|
325
|
+
|
|
326
|
+
## 0.80.2-popup.8
|
|
327
|
+
|
|
328
|
+
- Vendors popup 0.1.0-dev.3, which adds the opt-in destination picker this
|
|
329
|
+
version bridges to.
|
|
330
|
+
|
|
331
|
+
## 0.80.2-popup.7
|
|
332
|
+
|
|
333
|
+
- Server-only popup opt-in webhooks. `server/popup-optin-webhook-core` signs a
|
|
334
|
+
versioned payload with HMAC-SHA256 over a timestamped string, re-validates
|
|
335
|
+
the endpoint on every redirect hop, bounds the response it will read, and
|
|
336
|
+
categorises failures without ever echoing the receiver's own words.
|
|
337
|
+
- `server/webhook-url-guard` refuses anything that is not public https:
|
|
338
|
+
loopback, private, link-local, carrier-grade NAT, unique-local, multicast and
|
|
339
|
+
cloud-metadata addresses, including the IPv4 forms hidden inside IPv6, plus
|
|
340
|
+
credentials embedded in the URL.
|
|
341
|
+
- `server/popup-optin-trigger` is the single predicate deciding what counts as
|
|
342
|
+
a genuine opt-in. The honeypot's `{ok: true, skipped: true}` is not one, and
|
|
343
|
+
neither is a Yes click, a dismissal, a preview or a test-mode popup.
|
|
344
|
+
- CMS bridges for the popup editor's opt-in destination picker and media
|
|
345
|
+
picker. Only an identifier, a name and a warning cross the boundary — never
|
|
346
|
+
an endpoint or a secret.
|
|
347
|
+
- Vendors popup 0.1.0-dev.2 (image layouts, four presentation formats).
|
|
348
|
+
|
|
349
|
+
## 0.80.2-popup.6
|
|
350
|
+
|
|
351
|
+
- Vendors popup development build 0.0.0-dev.11: two fixes found by using the
|
|
352
|
+
redesigned backoffice in a browser — a false "unsaved changes" state
|
|
353
|
+
immediately after saving, and bottom-anchored presentations rendering at the
|
|
354
|
+
top of the builder's preview frame.
|
|
355
|
+
|
|
356
|
+
## 0.80.2-popup.5
|
|
357
|
+
|
|
358
|
+
- Vendors popup development build 0.0.0-dev.10: the design and usability
|
|
359
|
+
sprint over the popup backoffice and the public popup. No CMS-side code
|
|
360
|
+
changes were needed — the module host, the form and page bridges and the
|
|
361
|
+
lazy admin/runtime split all carry the new screen unchanged, which is the
|
|
362
|
+
layering working as intended.
|
|
363
|
+
|
|
364
|
+
## 0.80.2-popup.4
|
|
365
|
+
|
|
366
|
+
- The popup backoffice screen imports `@bygghammar/lightweight-popup/admin.css`
|
|
367
|
+
itself, from inside the lazily loaded admin module. The screen was rendering
|
|
368
|
+
unstyled because nothing imported the stylesheet; importing it here lets the
|
|
369
|
+
consumer's bundler attach it to the admin chunk, keeping every byte of it out
|
|
370
|
+
of public bundles.
|
|
371
|
+
- Vendored popup development build 0.0.0-dev.9: live step switching in the
|
|
372
|
+
builder, draft-until-valid-save creation, strict schema validation in the
|
|
373
|
+
reference storage adapter.
|
|
374
|
+
|
|
375
|
+
## 0.80.2-popup.3
|
|
376
|
+
|
|
377
|
+
Not yet published. Local development artifact for the Popups module only.
|
|
378
|
+
|
|
379
|
+
### Changed
|
|
380
|
+
|
|
381
|
+
- Rebuilt against the popup package's `0.0.0-dev.8`, which default-exports the
|
|
382
|
+
administration app so the module host's lazy loader resolves to it.
|
|
383
|
+
|
|
384
|
+
## 0.80.2-popup.2
|
|
385
|
+
|
|
386
|
+
Not yet published. Local development artifact for the Popups module only.
|
|
387
|
+
|
|
388
|
+
### Added
|
|
389
|
+
|
|
390
|
+
- The Popups backoffice now mounts the popup package's full administration
|
|
391
|
+
screen — list, builder, rules, appearance, statistics, import and export —
|
|
392
|
+
instead of a read-only summary.
|
|
393
|
+
- `./popups/cms-bridges`: form and page discovery adapted from the platform's
|
|
394
|
+
own host contract, so every site gets working pickers without writing them.
|
|
395
|
+
Only a key and a display name cross into the builder; the full form row
|
|
396
|
+
carries private settings a popup builder has no business seeing. A host with
|
|
397
|
+
no page listing gets no picker rather than a screen that fails to render.
|
|
398
|
+
|
|
399
|
+
## 0.80.2-popup.1
|
|
400
|
+
|
|
401
|
+
Not yet published. Local development artifact for the Popups module only.
|
|
402
|
+
|
|
403
|
+
### Added
|
|
404
|
+
|
|
405
|
+
- **CMS module table** (`config/modules`, exported as `./modules`). Modules were
|
|
406
|
+
described in four places that had to agree by hand — the flag type, the
|
|
407
|
+
defaults, the Modules screen labels and the sidebar definitions. All four now
|
|
408
|
+
derive from one table, so a new first-party module is one entry plus one key
|
|
409
|
+
in `SiteFeatureFlags`. The key space stays a closed typed union and the
|
|
410
|
+
settings parser still drops anything it does not recognise, on read and on
|
|
411
|
+
write. Every pre-existing module keeps its default, label, path and sidebar
|
|
412
|
+
position.
|
|
413
|
+
- **Lazy public module runtimes** (`CmsModuleRuntimes`, `registerModuleRuntime`)
|
|
414
|
+
and **lazy backoffice screens** (`CmsModuleAdmin`, `registerModuleAdmin`). A
|
|
415
|
+
module registers loader functions rather than components, so defining one
|
|
416
|
+
costs nothing: a site with the module off never evaluates its code and the
|
|
417
|
+
browser never requests its chunk. Loading happens in an effect, which is
|
|
418
|
+
itself the hydration gate — the server renders nothing, the first client pass
|
|
419
|
+
renders nothing. Public and admin loaders live in separate registries.
|
|
420
|
+
- **Generic successful-submission seam for forms** (`./forms/success`,
|
|
421
|
+
`./forms/submission-result`). `FormRenderer` reads the submit result instead
|
|
422
|
+
of assuming success, and announces only submissions the host actually stored:
|
|
423
|
+
a honeypot answers `{ ok: true, skipped: true }` after storing nothing, and
|
|
424
|
+
counting that is how a bot inflates every conversion figure a site reports.
|
|
425
|
+
The event is emitted *before* a thank-you redirect assigns
|
|
426
|
+
`window.location.href`, because that tears the page down. Subscribers are
|
|
427
|
+
isolated, and the form system names no module. `ManagedForm` and
|
|
428
|
+
`FormRenderer` also take an optional `onSuccess`.
|
|
429
|
+
- **Popups module** (`popups: false` by default), split across `./popups/host`,
|
|
430
|
+
`./popups/register`, `./popups/runtime` and `./popups/admin`. The public
|
|
431
|
+
bridge imports only the popup package's runtime entry, and the admin screen
|
|
432
|
+
stays out of the eagerly-imported backoffice barrel.
|
|
433
|
+
`@bygghammar/lightweight-popup` is an optional peer dependency.
|
|
434
|
+
|
|
435
|
+
### Changed
|
|
436
|
+
|
|
437
|
+
- `DEFAULT_FEATURE_FLAGS`, the Modules screen and the backoffice sidebar now
|
|
438
|
+
derive from the module table.
|
|
439
|
+
- The lazy module host uses its component-loading effect as the hydration gate;
|
|
440
|
+
an earlier separate `hydrated` flag could be deleted without a single test
|
|
441
|
+
noticing, which meant it was guarding nothing.
|
|
442
|
+
|
|
11
443
|
## 0.80.1
|
|
12
444
|
|
|
13
445
|
- Release pipeline verification: first version published from the canonical
|
|
14
446
|
`byggis79/cms-platform` repository via the tag-triggered GitHub Actions
|
|
15
447
|
release workflow. No source or behaviour changes.
|
|
16
448
|
|
|
449
|
+
### Changed
|
|
450
|
+
|
|
451
|
+
- Module public runtimes and backoffice screens are now **lazily loaded**.
|
|
452
|
+
A module registers loader functions (`() => import(...)`) instead of
|
|
453
|
+
components, through `@bygghammar/cms-platform/popups/register` for popups.
|
|
454
|
+
Defining a module therefore costs nothing: a site with the module switched off
|
|
455
|
+
never evaluates its code and the browser never requests its chunk. Loading
|
|
456
|
+
happens in an effect after hydration, so nothing runs during SSR and the first
|
|
457
|
+
client render matches the server's markup.
|
|
458
|
+
- The public and admin loaders live in separate registries, so a public page
|
|
459
|
+
never pulls the backoffice screen and vice versa. `CmsModuleAdmin` mounts a
|
|
460
|
+
module's screen, loading it on first visit.
|
|
461
|
+
|
|
462
|
+
|
|
17
463
|
## 0.80.0
|
|
18
464
|
|
|
19
465
|
- Search engines status card: the help text now explicitly tells admins which
|
|
@@ -32,6 +478,49 @@ from the published npm tarballs.
|
|
|
32
478
|
screens instead of its own inline card, so the status and help text stay
|
|
33
479
|
identical in both places.
|
|
34
480
|
|
|
481
|
+
### Fixed
|
|
482
|
+
|
|
483
|
+
- The Popups module now registers through an exported `registerPopupModule()`
|
|
484
|
+
instead of a module-scope side effect. A host whose `package.json`
|
|
485
|
+
`sideEffects` field is an allowlist tree-shakes a bare
|
|
486
|
+
`import "@bygghammar/cms-platform/popups/runtime"` away, taking the
|
|
487
|
+
registration and the entire popup runtime with it — a silent failure where the
|
|
488
|
+
build succeeds and no popup ever appears.
|
|
489
|
+
|
|
490
|
+
### Added
|
|
491
|
+
|
|
492
|
+
- **CMS module table** (`config/modules`, exported as `./modules`). Modules were
|
|
493
|
+
previously described in four places that had to agree by hand — the flag type,
|
|
494
|
+
the defaults, the Modules screen labels and the sidebar definitions. All four
|
|
495
|
+
now derive from one table, so a new first-party module is one entry plus one
|
|
496
|
+
key in `SiteFeatureFlags`. The key space stays a closed typed union, and the
|
|
497
|
+
settings parser still drops anything it does not recognise.
|
|
498
|
+
- **Public module runtimes** (`CmsModuleRuntimes`, `registerModuleRuntime`). A
|
|
499
|
+
site renders the host once in its root layout; enabled modules that registered
|
|
500
|
+
a component are mounted. Registration happens by import, so a module a site
|
|
501
|
+
never imports is never bundled, and a module whose flag is off never runs.
|
|
502
|
+
- **Generic successful-submission seam for forms** (`./forms/success`,
|
|
503
|
+
`./forms/submission-result`). `FormRenderer` now reads the submit result
|
|
504
|
+
instead of assuming success, and announces only submissions the host actually
|
|
505
|
+
stored: a honeypot answer of `{ ok: true, skipped: true }` stored nothing and
|
|
506
|
+
must never be counted. The event is emitted *before* a thank-you redirect
|
|
507
|
+
assigns `window.location.href`, because that tears the page down. Subscribers
|
|
508
|
+
are isolated — one that throws cannot break a form — and the form system names
|
|
509
|
+
no module, so it keeps working with none subscribed. `ManagedForm` and
|
|
510
|
+
`FormRenderer` also take an optional `onSuccess`.
|
|
511
|
+
- **Popups module** (`popups: false` by default), with its CMS integration split
|
|
512
|
+
across `./popups/host`, `./popups/runtime` (public) and `./popups/admin`. The
|
|
513
|
+
public bridge imports only the popup package's runtime entry, so a site with
|
|
514
|
+
popups on never pulls the builder, the statistics screen or the validator into
|
|
515
|
+
its public bundle. `@bygghammar/lightweight-popup` is an optional peer
|
|
516
|
+
dependency: sites without the module need not install it.
|
|
517
|
+
|
|
518
|
+
### Changed
|
|
519
|
+
|
|
520
|
+
- `DEFAULT_FEATURE_FLAGS`, the Modules screen and the backoffice sidebar are now
|
|
521
|
+
derived from the module table. Every existing module keeps its previous
|
|
522
|
+
default, label, path and sidebar position.
|
|
523
|
+
|
|
35
524
|
## 0.78.0
|
|
36
525
|
|
|
37
526
|
- Countdown block: the unit tiles now always stay on a single row on mobile.
|
package/README.md
CHANGED
|
@@ -25,6 +25,14 @@ apply the canonical schema with `bunx cms-platform-migrations`, import
|
|
|
25
25
|
`theme.css`, register host components / platform data / translations, wrap the
|
|
26
26
|
server cores and mount the shared backoffice views.
|
|
27
27
|
|
|
28
|
+
## Editing locks
|
|
29
|
+
|
|
30
|
+
`docs/EDIT-LOCKS.md` is what a host wires so two administrators cannot
|
|
31
|
+
overwrite each other: run the migration, expose one server endpoint through
|
|
32
|
+
`editLockActionCore`, mount `EditLockProvider` with `createEditLockApi`, and
|
|
33
|
+
forward `editSessionId` to the saves. Until that is done every screen behaves
|
|
34
|
+
exactly as it did before locks existed.
|
|
35
|
+
|
|
28
36
|
## Compatibility policy
|
|
29
37
|
|
|
30
38
|
- `CHANGELOG.md` records every release; read it before upgrading.
|
|
@@ -119,8 +127,14 @@ bun run check # typecheck + tests + package verification
|
|
|
119
127
|
|
|
120
128
|
The package ships TypeScript sources; there is no build output. Every change
|
|
121
129
|
goes through a pull request, and every release is triggered by pushing a
|
|
122
|
-
`vX.Y.Z` tag that matches `package.json
|
|
123
|
-
|
|
130
|
+
`vX.Y.Z` tag that matches `package.json`. That tag runs
|
|
131
|
+
`.github/workflows/publish.yml`, which publishes to npm using **Trusted
|
|
132
|
+
Publishing**: GitHub mints a short-lived OIDC token scoped to that single
|
|
133
|
+
workflow run, and npm exchanges it for publish rights through the package's
|
|
134
|
+
Trusted Publisher configuration. No long-lived npm credential takes part — no
|
|
135
|
+
`NPM_TOKEN`, no `NODE_AUTH_TOKEN`, no OTP. [RELEASING.md](./RELEASING.md) is
|
|
136
|
+
what the workflow checks and the npm settings it has to match. Never publish
|
|
137
|
+
manually.
|
|
124
138
|
|
|
125
139
|
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full workflow and changelog
|
|
126
140
|
conventions. Consumers install an explicit released version such as
|