@aduptive/strapi-blockscene 2.0.0-alpha.4 → 2.0.0-alpha.6

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/README.md CHANGED
@@ -33,7 +33,8 @@ module.exports = {
33
33
  'blocks.hero': {
34
34
  label: 'Hero banner',
35
35
  description: 'Introduce a page with a prominent title and image.',
36
- category: 'Editorial',
36
+ typology: 'hero', // optional; guessed from the name otherwise
37
+ tags: ['Editorial'],
37
38
  keywords: 'heading introduction',
38
39
  image: '/block-previews/hero.png',
39
40
  },
@@ -44,19 +45,75 @@ module.exports = {
44
45
  ```
45
46
 
46
47
  Rebuild/restart the admin. Open a content type with a Dynamic Zone. The
47
- editor panel provides one gallery button per editable, non-full zone plus
48
- "Open all blocks" / "Close all blocks" for zones that already have blocks.
48
+ editor panel provides one gallery button per editable, non-full zone. Zones
49
+ that already have blocks get "Open all blocks" / "Close all blocks" icon
50
+ buttons right beside the native zone label ("blocks (3)").
49
51
 
50
52
  ## Gallery
51
53
 
52
- The modal has one header row: block count (`34 blocks`, or `3 of 34` while
53
- filtering), search, a category filter (only when the zone spans more than one
54
- category, with counts), a "Fields" switch that shows each block's attribute
55
- list on its card, and a columns slider (1 to 5, remembered per browser). Blocks
56
- are grouped by category, system-like categories (`admin`) last; search and the
57
- category filter combine, and the empty state offers a reset. Categories come
58
- from `config.components[uid].category`, else the component's `category` in its
59
- schema. The modal is 80vw wide.
54
+ The modal is a block browser (80vw wide):
55
+
56
+ - **Sidebar** (collapsible to icons; remembered per browser): All, Recently
57
+ used and Starred with counts, then the typologies present in the zone.
58
+ - **Top bar**: count (`34 blocks`, or `3 of 34` while filtering), search,
59
+ filter menus Tags, Media and Content (checkboxes; several values in one menu
60
+ match any of them, different menus combine), a "Fields" switch that shows
61
+ each block's attribute list on its card, and a columns slider (1 to 5,
62
+ remembered per browser). Active values show as removable chips with "Clear
63
+ all"; the empty state offers a reset.
64
+ - **Cards**, grouped by typology: facet badges (IMAGE, VIDEO, GALLERY, RICH
65
+ TEXT, LIST, DYNAMIC, FORM), a star and a hover "+" that inserts at once.
66
+ - A click **magnifies** the block: the card grows into a large panel over the
67
+ grid (sidebar and top bar stay usable, the grid dims underneath) and shrinks
68
+ back into its card on close (close button, Esc or a click on the dimmed
69
+ grid). The animation is transform/opacity only, about 250 ms, and is skipped
70
+ under `prefers-reduced-motion: reduce`. The panel shows a large live preview
71
+ with Fit / Mobile / Tablet / Desktop widths (scaled to fit, as in the page
72
+ preview) and a compact strip: label, uid, description, badges, fields (on
73
+ demand), star and Insert. Double click, "+" or Enter on a focused card
74
+ inserts without it.
75
+
76
+ The magnified preview shows the card's thumbnail at once and fades the live
77
+ page in over it once it has loaded. Live source, first match wins:
78
+
79
+ 1. **Block preview URL** (`editor.blockPreviewUrl`, Settings, both versions):
80
+ a plain page per block in a plain iframe, no bridge. Placeholders, URL
81
+ encoded: `{uid}`, `{name}` (after the dot), `{category}` (before the dot),
82
+ `{variant}` (`default`) and `{locale}` (the entry's locale, empty when the
83
+ type is not localized). Example:
84
+ `http://localhost:3026/{locale}/block-preview/{name}/{variant}`. Such a
85
+ fixtures route is usually dev-only: leave the option empty on production.
86
+ 2. **Page preview route** (Strapi 5: the preview route base URL, else the
87
+ native Preview origin + `/block-preview/page`): the same bridge as the
88
+ whole-page preview, sent one block made from the schema defaults, mode
89
+ `preview`. Read-only: every message except `ready` is ignored.
90
+ 3. Otherwise the image only.
91
+
92
+ If the live page neither loads nor says `ready` within 8 s, the image stays
93
+ with a short note. The iframe uses the page preview's sandbox
94
+ (`allow-scripts allow-same-origin`) and `referrerpolicy="no-referrer"`.
95
+
96
+ Stars and recently used blocks are stored per admin user on the server
97
+ (`GET`/`PUT /blockscene/me/prefs`, `{ starred, recent }`, at most 200 and 20
98
+ existing component uids).
99
+
100
+ ### Taxonomy
101
+
102
+ Facets are read from each component schema and the components it nests (one
103
+ level): media fields (`image`, `video` by allowed types, `gallery` when
104
+ multiple), `richtext` (rich text, blocks or a CKEditor custom field), `list`
105
+ (repeatable component), `dynamic` (a relation, or a uid with query, archive or
106
+ related) and `form` (uid or display name with form or contact).
107
+
108
+ The typology is one of hero, text, media, listing, cards, cta, form and
109
+ layout, guessed from the component name and display name (hero, banner,
110
+ cover; text, rich, quote, title; image, media, video, gallery, carousel; list,
111
+ query, archive, related, posts, projects; card; cta, button, link; form,
112
+ contact; wrapper, column, grid, divider, spacer, section; else text). Override
113
+ it, and add up to 10 tags (1 to 24 characters), in Settings, Blockscene, or in
114
+ the plugin config: `components[uid].typology` and `components[uid].tags`. A
115
+ Settings value wins over the config. The former `category` config key still
116
+ works: it becomes the block's tag when no tags are set.
60
117
 
61
118
  The native "Add a component to <zone>" button of an editable zone opens this
62
119
  gallery as well (the click is intercepted in the capture phase; Strapi's
@@ -74,7 +131,59 @@ opening or closing the row. The sources are the same as the gallery cards,
74
131
  minus the wireframe: a block with no image shows nothing, so nothing new is
75
132
  invented for blocks that were never captured. The thumbnail is inserted into
76
133
  Strapi's own header through the DOM (no Content Manager patch) and is
77
- restored when Strapi re-renders the list.
134
+ restored when Strapi re-renders the list. Turn it off with the editor option
135
+ `showRowThumbnails: false`.
136
+
137
+ ## Row actions
138
+
139
+ Each block header gets a few icons right before Strapi's own delete button
140
+ (inserted through the DOM, like the thumbnails; read-only rows get none):
141
+
142
+ - **Hide on the site** (eye). The block stays in the document and in the
143
+ admin, dimmed with a "Hidden" badge (also in the page preview, which receives
144
+ `hidden: true` for it). What the content API does is the editor option
145
+ `hiddenBlocks`: `strip` (default) removes hidden rows from Dynamic Zones in
146
+ content-API reads (REST and GraphQL, including zones of populated relations
147
+ and components; admin and Content Manager reads always return every row),
148
+ `flag` sends them with the attribute so the frontend decides, `off` removes
149
+ the eye and strips nothing. On a configured group OPEN the eye hides the
150
+ whole group.
151
+ - **Duplicate**. A deep copy right below: ids removed from the row and its
152
+ nested components, new row key, media kept, relations kept (Strapi 5 reads
153
+ the source row's relations from the server and adds its unsaved changes).
154
+ A group OPEN duplicates with its whole range. Option `duplicate`.
155
+ - **Copy / Paste** across pages. The copy icon copies one block; the select
156
+ icon in the zone label shows a checkbox per row, then "Copy selected". The
157
+ blocks are stored in this browser's localStorage (`blockscene:clipboard:v1`,
158
+ with the source content type and locale). "Paste" appears in the zone label
159
+ (under the gallery button for an empty zone) and in the page preview seams.
160
+ Paste is all or nothing: every block must be allowed in the target zone, fit
161
+ its maximum, and groups must be whole; otherwise a notice explains why and the
162
+ form is not changed. Media and relations are kept by id/documentId, so paste
163
+ within the same install. Option `clipboard`.
164
+ - **Confirm delete**. The native delete asks "Delete block …?" first;
165
+ confirming runs Strapi's own removal. On a group marker the dialog says only
166
+ the marker is removed. Option `confirmDelete`.
167
+
168
+ Everything goes through the form: nothing is saved until Save, and undo/redo
169
+ (Strapi 5) covers every action. Strapi 4 supports the same actions; relations
170
+ are copied as the form holds them there (the loaded relations of an opened
171
+ block).
172
+
173
+ **The hidden attribute is a database column.** At register time the plugin adds
174
+ a boolean attribute (like i18n adds `locale`) to every component used in a
175
+ Dynamic Zone: `{ type: 'boolean', default: false, configurable: false }`, so
176
+ Strapi's schema sync creates a `bs_hidden` column in each of those component
177
+ tables on the next start (existing rows read as not hidden). It is never shown
178
+ as an input: the plugin removes it from the components' edit layouts through
179
+ the Content Manager's layout hook (it may be listed in "Configure the view" of
180
+ a component; leave it out of the layout). It is not marked `visible: false`,
181
+ because before Strapi 5.45 the edit view crashes on a value whose attribute the
182
+ admin does not know. Rename it with the plugin config
183
+ `hiddenAttribute: 'myName'`, or set `hiddenAttribute: false` to add nothing (no
184
+ eye, nothing stripped). A component that already has an attribute of that name
185
+ with another type is skipped with a warning. Removing the plugin (or setting
186
+ `false`) leaves the column in place with its data; drop it yourself if you want.
78
187
 
79
188
  ## Thumbnail priority
80
189
 
@@ -109,9 +218,59 @@ needed for the gallery to pick them up. Reading requires the
109
218
  - Wireframe palette (background, surfaces, text, accent) with a live preview
110
219
  and "Restore default colors". Colors must be `#RRGGBB`.
111
220
  - Component list with the effective source, choose/replace image from the
112
- Media Library, "Use automatic image" and the wireframe template.
221
+ Media Library, "Use automatic image", the wireframe template, the gallery
222
+ typology ("Automatic" shows the guess) and tags (comma separated).
113
223
  - Editor preferences: enhancements on/off, visibility of each collective
114
224
  button, initial accordion state and the initial block mode.
225
+ - Version history (Strapi 5): module switch, covered content types, retention
226
+ (see "Version history").
227
+ - Content types: every project type with a Dynamic Zone, each with on/off
228
+ (off leaves its edit view fully native) and, on Strapi 5, the mode its edit
229
+ views open in ("Default" follows the global preview mode) and its visual
230
+ editor sidebar (see below).
231
+ - "Restore project defaults" (or "Reset to defaults" when the project has no
232
+ code defaults), after a confirmation, deletes what was saved here
233
+ (`DELETE /blockscene/settings`, same permission as saving).
234
+
235
+ ### Project defaults from code
236
+
237
+ A project can ship its baseline in the plugin config, with the same shape as
238
+ the stored settings (every key optional):
239
+
240
+ ```js
241
+ // config/plugins.js
242
+ module.exports = {
243
+ blockscene: { config: { settings: require('./blockscene.json') } },
244
+ }
245
+ // config/blockscene.json
246
+ {
247
+ "palette": { "accent": "#AA3300" },
248
+ "editor": { "previewMode": "split", "initialState": "open" },
249
+ "components": { "blocks.faq": { "template": "faq", "typology": "text" } },
250
+ "contentTypes": {
251
+ "api::page.page": {
252
+ "sidebarPosition": "right",
253
+ "sidebar": [{ "label": "SEO", "icon": "seo", "open": "drawer", "fields": ["seo"] }]
254
+ }
255
+ }
256
+ }
257
+ ```
258
+
259
+ The effective settings are the built-in defaults, then the code `settings`,
260
+ then what was saved on the page:
261
+
262
+ - `palette` and `editor`: a saved key wins over the code key by key.
263
+ - `components` and `contentTypes`: a saved entry replaces the code entry for
264
+ that uid as a whole (a saved `api::page.page` entry replaces its code
265
+ sidebar too); other uids keep their code values.
266
+
267
+ Saving stores only what differs from the code defaults, so later changes to the
268
+ code reach every key nobody overrode. A document saved before the project had
269
+ code defaults holds every palette and editor key, and those keep winning until
270
+ "Restore project defaults". The code settings are checked at boot with the same
271
+ rules as `PUT`; if they are invalid a warning names the problem and they are
272
+ ignored (boot continues). `GET /blockscene/settings` returns them as
273
+ `projectDefaults` (null when absent or invalid).
115
274
 
116
275
  ### Editor preferences
117
276
 
@@ -120,14 +279,36 @@ needed for the gallery to pick them up. Reading requires the
120
279
  | Editor enhancements enabled | on/off | on |
121
280
  | Show "Open all blocks" | yes/no | yes |
122
281
  | Show "Close all blocks" | yes/no | yes |
282
+ | Show block thumbnails in the rows (`showRowThumbnails`) | yes/no | yes |
123
283
  | Initial accordion state | all closed / all open / remember | all closed |
284
+ | Load rich-text editors on demand (`lazyEditors`, Strapi 5) | yes/no | yes |
285
+ | Custom fields loaded on demand (`lazyFields`, Strapi 5) | up to 20 uids `plugin::x.y` / `global::x` | `["plugin::ckeditor5.CKEditor"]` |
124
286
  | Preview route base URL (Strapi 5) | full URL or empty | empty: native Preview origin |
287
+ | Block preview URL | http(s) URL with placeholders, up to 500 characters, or empty | empty: page preview route, else image |
125
288
  | Initial preview mode (Strapi 5) | form / side by side / preview | form |
289
+ | Confirm before deleting a block (`confirmDelete`) | yes/no | yes |
290
+ | Duplicate on each block (`duplicate`) | yes/no | yes |
291
+ | Copy and Paste (`clipboard`) | yes/no | yes |
292
+ | Blocks hidden on the site (`hiddenBlocks`) | `strip` / `flag` / `off` | `strip` |
293
+ | Friendly field labels (`friendlyLabels`, see [Field labels](#field-labels)) | yes/no | yes |
294
+
295
+ Every edit view opens in its content type's mode, else the initial preview
296
+ mode. Editors can switch while they edit; the switch is not remembered.
126
297
 
127
298
  The initial state is applied once per document/locale after the blocks
128
299
  render; edits, reorders and newly inserted blocks are not re-applied. Hiding a
129
300
  button only hides it; native accordion headers keep working.
130
301
 
302
+ Lazy editors: a custom field listed in `lazyFields` first shows a read-only
303
+ preview of its HTML (same label, hint and error; scripts, frames, styles, event
304
+ handlers and `javascript:` URLs removed) and mounts the real editor on click,
305
+ Enter/Space or keyboard focus, then puts the caret in it. Once mounted it stays
306
+ for that view; closing and reopening the block shows the preview again.
307
+ Disabled fields only show the preview. The wrapper is installed on every
308
+ registered custom field at boot and passes the others straight through; until
309
+ the catalog loads, and when the plugin or the content type is off, every field
310
+ is native.
311
+
131
312
  "Remember" stores only the last explicit open all / close all click, as
132
313
  `open`/`closed` in the browser's localStorage, keyed by admin URL, user id,
133
314
  content type and zone. Another user in the same browser does not inherit it.
@@ -147,11 +328,62 @@ your frontend serves; the page renders them with its own components and styles
147
328
  and can ask the admin to open a block, edit a field or pick media. Nothing is
148
329
  saved or published by the preview.
149
330
 
331
+ Side by side, hovering a zone row in the form (open or closed) outlines its
332
+ block in the page, and hovering a block in the page outlines its form row in the
333
+ theme's primary color; hovering never scrolls. When that row is outside the
334
+ visible form, an arrow at the top or bottom edge of the form scrolls to it. The
335
+ divider between form and page drags to resize, takes the arrow keys, Home and
336
+ End, and a double click resets it to half.
337
+
338
+ ### Visual editor sidebar
339
+
340
+ Per content type, a bar of buttons on the left, right or bottom edge of the
341
+ visual editor. Each button (label, optional icon) opens some of the document's
342
+ own fields in a modal or a drawer: the native form itself, with every other
343
+ field hidden, so components, repeatable components, media, relations and custom
344
+ fields all keep working. Done, Esc or the backdrop put the form back.
345
+
346
+ ```js
347
+ // Settings, Blockscene, Content types (or PUT /blockscene/settings, or the code `settings`):
348
+ contentTypes: {
349
+ 'api::page.page': {
350
+ sidebarPosition: 'left', // left | right | bottom
351
+ sidebar: [
352
+ { label: 'Title', icon: 'text', open: 'modal', fields: ['title'] },
353
+ { label: 'SEO', icon: 'seo', open: 'drawer', fields: ['pageSeo'] },
354
+ ],
355
+ },
356
+ }
357
+ ```
358
+
359
+ Icons: text, tag, seo, settings, image, link, palette, list, globe, info (or
360
+ none). Fields are the type's own top-level attributes; an item naming a field
361
+ removed from the schema is dropped on read. Up to 12 items.
362
+
363
+ The Settings page edits it under each enabled content type: position, then per
364
+ item the label, icon, drawer or modal, the fields (checkboxes), move up/down and
365
+ remove. An item without a label or without fields is flagged and blocks Save.
366
+ Removing every item removes the override.
367
+
368
+ The pane toolbar sets the page width: Fit (the pane), Mobile (390 px), Tablet
369
+ (834 px) or Desktop (1440 px). A device renders at its own width and scales down
370
+ when the pane is narrower, so the desktop layout fits side by side; switching
371
+ never reloads the page. The choice is remembered per browser.
372
+
150
373
  Minimal configuration:
151
374
 
152
375
  1. Serve a page that implements the bridge. `examples/page-preview/index.html`
153
376
  is a framework-free reference: it answers `ready`/`ping`, renders
154
377
  `update-page`, and sends `select`, `focus`, `edit`, `media`, `media-remove`.
378
+ Hover sync is optional: the page sends `{ type: 'hover', key }` (a block
379
+ key, or `null` off blocks) when it changes, and marks the block of the
380
+ admin's `{ type: 'hover', key }` without scrolling. A page that ignores it
381
+ keeps working.
382
+ Row actions: a block hidden on the site arrives with `hidden: true` (the
383
+ example dims it), and `update-page` carries `clipboard` (the number of copied
384
+ blocks, 0 when none or not editable); while it is positive the page may show
385
+ a Paste button in its seams that sends `{ type: 'paste', after }` (a block
386
+ key, or `null` for the start). Both are optional.
155
387
  Keep the origin check (`admin` query parameter or your own constant).
156
388
  2. Allow the admin origin to embed it (`Content-Security-Policy: frame-ancestors`)
157
389
  and allow the page origin in the Strapi admin CSP (`frame-src`).
@@ -171,14 +403,34 @@ update permission on it. Pending dialogs are dropped when the document or
171
403
  locale changes.
172
404
 
173
405
  Editing from the page: plain-text areas the page explicitly maps
174
- (`data-block-field` + `contenteditable="plaintext-only"`) send `edit`; any other
175
- editable field opens an admin modal with the field's own native input (custom
176
- fields such as CKEditor keep their options; string/text use the admin input).
177
- Apply updates the form and the preview; Cancel discards only the modal. Media
406
+ (`data-block-field` + `contenteditable="plaintext-only"`) send `edit`. In the
407
+ visual editor, a click on any other field or on the block opens the whole block
408
+ over the page: its own native form (every field type, CKEditor, media, nested
409
+ and repeatable components), with the clicked field focused and outlined. The
410
+ page updates while you type; Done, Esc or the backdrop put the block back. Side
411
+ by side focuses the field in the form on the left instead. Media
178
412
  fields open the native Media Library. Everything is validated against the schema
179
413
  and the zone's edit permission; a published page never receives unsaved values
180
414
  by itself. Strapi 4 does not have this panel yet (see `docs/BACKLOG.md`).
181
415
 
416
+ ### Undo and redo
417
+
418
+ The Blockscene panel and the pane toolbar have Undo and Redo buttons (Strapi 5).
419
+ They step through the whole edit view: blocks inserted, removed or moved from the
420
+ gallery, the page, groups or the native actions, and any field edit. Changes
421
+ within 400 ms of each other are one step, so a burst of typing undoes at once;
422
+ up to 100 steps. Shortcuts: Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z and Ctrl+Y, only while
423
+ the focus is outside a text field, select or rich text editor (those keep their
424
+ own undo).
425
+
426
+ Limits: the history lives in the open edit view and starts over when the
427
+ document, the locale or its loaded values change, so a Save clears it. Shortcuts
428
+ pressed inside the preview page are not seen (it is another origin); use the
429
+ buttons there. A custom field that reads its value only once on mount keeps
430
+ showing the old text after an undo until the view reloads; the CKEditor 5
431
+ custom field (@_sh/strapi-plugin-ckeditor) follows the restored value (checked
432
+ on Strapi 5.31).
433
+
182
434
  ## Layout groups (optional)
183
435
 
184
436
  Some sites keep "wrappers" in the flat Dynamic Zone: an OPEN component, the
@@ -239,6 +491,137 @@ With a valid map:
239
491
  Configured groups are stored exactly as before: the flat list of components in
240
492
  the zone. Removing the config only removes the group tools and the guard.
241
493
 
494
+ ## Field labels
495
+
496
+ The Content Manager shows each field under its "Configure the view" label, which
497
+ defaults to the raw attribute name (`mobileColumnsCount`). Blockscene rewrites
498
+ the edit view labels, hints and placeholders through the Content Manager's own
499
+ `Admin/CM/pages/EditView/mutate-edit-view-layout` hook (Strapi 5 and 4, the
500
+ same hook the i18n plugin uses), so nothing in the DOM is patched and the
501
+ saved view configuration is never changed. Per field, the first that applies:
502
+
503
+ 1. **Project texts** from the plugin config `fields` (below);
504
+ 2. **a label set in "Configure the view"** (anything other than the attribute name);
505
+ 3. **the humanized name** when "Friendly field labels" is on: `mobileColumnsCount`
506
+ reads "Mobile columns count", `page_seo` "Page seo", acronym runs stay
507
+ (`pageSEO` "Page SEO", `ctaURL` "Cta URL").
508
+
509
+ Description (the hint under the field) and placeholder change only when the
510
+ project texts have them. Texts follow the admin user's interface language
511
+ (`strapi-admin-language`, else English), resolved as: exact locale, then its
512
+ language (`pt-BR` → `pt`), then any variant of that language, then `en`, then
513
+ the first value.
514
+
515
+ ```js
516
+ // config/plugins.js
517
+ blockscene: { config: { fields: {
518
+ 'api::page.page': {
519
+ pageSeo: { label: { en: 'Search engine settings', 'pt-BR': 'Configurações de SEO' },
520
+ description: { en: 'Title and description shown on Google', 'pt-BR': 'Título e descrição no Google' } },
521
+ },
522
+ 'shared.seo': { metaTitle: { placeholder: 'Up to 60 characters' } },
523
+ } } },
524
+ ```
525
+
526
+ Keys are content type or component uids, then attribute names; each of
527
+ `label` (up to 80 characters), `description` (300) and `placeholder` (120) is a
528
+ string or `{ "<locale>": string }`. The map is validated at boot: an unknown
529
+ key, an empty or too long text, or a malformed locale code logs a warning
530
+ naming it and the whole `fields` config is ignored (labels fall back to the two
531
+ other steps). Entries for a uid or attribute the schema no longer has (a
532
+ renamed or removed field) are skipped with one warning listing them; the rest
533
+ keep working. It is code only, versioned with the
534
+ project; the Settings page has the on/off switch but does not edit texts.
535
+
536
+ To start the file, generate every field of a project with its humanized English
537
+ label from a clone of this repository, then keep what you want to change:
538
+
539
+ ```bash
540
+ node scripts/fields-skeleton.mjs ../my-project/backend > ../my-project/backend/config/blockscene-fields.json
541
+ # config/plugins.js: fields: require('./blockscene-fields.json')
542
+ ```
543
+
544
+ Limits: the hook runs synchronously and reads the last catalog the admin
545
+ loaded. The first edit or list view of a session starts that request and shows
546
+ native labels; the next layout computed (another document, list → edit, a
547
+ locale switch) has them. Strapi 5 gives the hook no content type uid, so the
548
+ layout is matched by display name and field names; two content types that
549
+ share both are left untouched (component fields always work: they are keyed by
550
+ uid). The switch "Editor enhancements enabled", `BLOCKSCENE_DISABLED` and a
551
+ content type turned off all leave the native labels; any error leaves the
552
+ layout untouched. Changing the interface language applies from the next edit
553
+ view.
554
+
555
+ ## Version history (Strapi 5)
556
+
557
+ Off by default. Turn it on in Settings, Blockscene, "Version history" (or from
558
+ code, `settings.history`, see below). Strapi Community has no content history
559
+ (it is a Growth feature); this module records one event per content change and
560
+ keeps a snapshot of the content, so an editor can see what changed and load an
561
+ earlier version back into the form.
562
+
563
+ **What it captures.** Every create, save, publish, unpublish, discard draft and
564
+ delete of the covered content types (all `api::` types by default, or a list)
565
+ made through Strapi's document service: the admin (single and bulk actions),
566
+ the REST and GraphQL APIs, and any plugin or custom code that uses
567
+ `strapi.documents`. One event per affected locale, with who (admin user, API
568
+ token, Users & Permissions user, or `system`), when, the action and a summary
569
+ (blocks added, removed and changed per Dynamic Zone, top-level fields changed)
570
+ against the previous snapshot of the same locale. The snapshot is the stored
571
+ entry with components and Dynamic Zones in full, relations as documentIds and
572
+ media as file ids (a 34-block page is a few KB; the capture adds a few
573
+ milliseconds to a save). A snapshot identical to the previous one is not stored
574
+ again (the event is, with the same hash).
575
+
576
+ **What it does not see.** Writes that bypass the document service: raw
577
+ `strapi.db.query`, knex or SQL, imports that write the database directly. This
578
+ is content activity for editors, not a security audit log.
579
+
580
+ **Failure policy.** A failed capture never blocks a save or a publish: the
581
+ change goes through, the error is logged and the event is marked "Not
582
+ recorded". A failed capture fails a delete: the delete runs in one transaction
583
+ with its events, so nothing is deleted without its last version (a bulk delete
584
+ is rolled back whole). `BLOCKSCENE_DISABLED=true` or the module switch off stop
585
+ all capture. The plugin never records its own content types.
586
+
587
+ **In the edit view.** The Blockscene side panel gets a History section (saved
588
+ documents of covered types, also on types without a Dynamic Zone): the
589
+ versions of the document in the current locale, newest first. Selecting one
590
+ shows a block-level diff against the form (relations are not compared) and
591
+ "Load this version", which puts the version into the form the way undo does:
592
+ nothing is written until the editor saves, so validation, permissions and
593
+ lifecycles apply as for any edit, and undo reverts the load. Components and
594
+ blocks come back as new rows; relations become the connect/disconnect change
595
+ from what the document has now; fields the editor may not update, fields the
596
+ schema no longer has, blocks whose component is no longer allowed, and media or
597
+ related entries that no longer exist are left out and listed.
598
+
599
+ **Permission.** `Blockscene: Read version history` (`history.read`), plus read
600
+ access to the content type in the Content Manager. Snapshots hold removed and
601
+ unpublished text, so the section is hidden without it. Admin API:
602
+ `GET /blockscene/history/:uid/:documentId?locale=` (the latest 100 events, no
603
+ snapshots) and `GET /blockscene/history-events/:id` (one event, its snapshot and
604
+ the media and relation targets that still exist).
605
+
606
+ **Retention.** Snapshots are kept `retentionDays` (90) and at most
607
+ `maxSnapshots` (100) per document, newest first; events without a snapshot are
608
+ kept `eventDays` (365, at least `retentionDays`). A cron job added by the plugin
609
+ runs nightly at 03:00 server time, in batches of 500; it only empties snapshots
610
+ and deletes events, never media files, and running it on several instances is
611
+ harmless.
612
+
613
+ ```js
614
+ // config/plugins.js
615
+ blockscene: { config: { settings: { history: { enabled: true, contentTypes: ['api::page.page', 'api::post.post'],
616
+ retentionDays: 90, maxSnapshots: 100, eventDays: 365 } } } }
617
+ ```
618
+
619
+ Events live in the `blockscene_events` table (content type
620
+ `plugin::blockscene.event`, hidden from the Content Manager and the Content-Type
621
+ Builder). Removing the plugin, or installing a Blockscene version older than
622
+ the one that added history, lets Strapi drop that table and every recorded
623
+ version on the next start: back up `blockscene_events` first if you need it.
624
+
242
625
  ## Languages
243
626
 
244
627
  All plugin chrome (gallery, settings, page preview, editor dialogs, diagnostics)
@@ -318,14 +701,17 @@ installs without overrides, not a promise for every minor in between. Strapi
318
701
  - Authenticated admin catalog endpoint exposes only supported metadata, resolved
319
702
  override URLs, palette and editor preferences; never the whole config.
320
703
  - Top-level Dynamic Zones only. Conditional fields are omitted conservatively.
321
- - "Open all" / "Close all" drive the native accordions: opening is batched into
322
- one render; closing yields a frame per block so the admin stays responsive.
323
- On a page with about 34 blocks whose fields include CKEditor, closing still
324
- takes a few seconds with pauses up to about 1.7 s: that is the editors being
325
- torn down by the host, not the plugin's loop.
704
+ - "Open all" / "Close all" drive the native accordions without touching the
705
+ whole list at once: open all opens the rows in and near the visible area, a
706
+ few per frame, and the others as they approach while scrolling (until Close
707
+ all or leaving the document); close all closes the visible rows at once and
708
+ the rest in idle time. With lazy editors, on a 34-block page with CKEditor
709
+ fields (Strapi 5.31) the longest pause went from about 2.9 s to 0.14 s on
710
+ open all and from 1.6 s to 0.14 s on close all.
326
711
  - No screenshot server, no hourly job, no in-editor rendered preview.
327
712
  - Removing the plugin leaves native content intact; remove its config entry and
328
- rebuild. No plugin data migration is needed.
713
+ rebuild. No plugin data migration is needed. The "hide on the site" column
714
+ (see Row actions) stays in the component tables until you drop it.
329
715
 
330
716
  Configuration edits require a server restart. Backend URL/prefix deployments
331
717
  should set `previewBaseUrl` explicitly. HTTP(S) image hosts must also be allowed
@@ -336,7 +722,7 @@ uploads share an origin.
336
722
  ## Development
337
723
 
338
724
  - `admin/`: shared gallery, settings page, wireframes and version-specific adapters.
339
- - `server/`: settings store, validation, permissions, the catalog endpoint and the layout-group publish guard (`groups.js` is shared with the admin).
725
+ - `server/`: settings store, validation, permissions, the catalog endpoint and the layout-group publish guard (`groups.js` is shared with the admin); `history.js` and `diff.js` for version history (`diff.js` is shared with the admin).
340
726
  - `admin/translations/`: one flat catalogue per admin locale (English is the source of truth).
341
727
  - `scripts/capture-previews.mjs`: local thumbnail capture.
342
728
  - `packages/strapi4`, `packages/strapi5`: independently installable distributions.