@scaleflex/template-builder 0.2.0 → 0.4.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.
@@ -8,7 +8,6 @@ metadata:
8
8
  category: integration
9
9
  tags:
10
10
  - scaleflex
11
- - filerobot
12
11
  - template-builder
13
12
  - design-templates
14
13
  - web-component
@@ -23,7 +22,7 @@ metadata:
23
22
  - User says "add the template builder", "embed the design template editor",
24
23
  "let users edit templates in our app"
25
24
  - User asks how to use `@scaleflex/template-builder` in their project
26
- - User wants a banner / creative editor backed by Filerobot rendering
25
+ - User wants a banner / creative editor backed by Scaleflex rendering
27
26
 
28
27
  ## Step 1 — Decide who stores the template
29
28
 
@@ -32,17 +31,17 @@ not already clear from the project.
32
31
 
33
32
  | | **DAM-backed** (default) | **Stateless** |
34
33
  |---|---|---|
35
- | Template lives in | Filerobot, as a `.fdt` file | The host's own database |
36
- | `template-id` is | a Filerobot file uuid | any string the host chooses |
34
+ | Template lives in | Scaleflex, as a `.fdt` file | The host's own database |
35
+ | `template-id` is | a DAM file uuid | any string the host chooses |
37
36
  | Save does | uploads a new version, returns `{ uuid }` | hands back `{ content }` for the host to store |
38
37
  | Needs per-user Scaleflex identities | yes | no — one service tenant is enough |
39
38
 
40
39
  Pick **stateless** when the host already has its own users, permissions and
41
40
  storage and does not want to map them onto Scaleflex tenants. Pick
42
- **DAM-backed** when templates are a Filerobot asset like any other.
41
+ **DAM-backed** when templates are a Scaleflex asset like any other.
43
42
 
44
43
  Statelessness applies to the **document only**. The editor still needs an
45
- authenticated Filerobot tenant for server-side text rendering, custom fonts,
44
+ authenticated Scaleflex tenant for server-side text rendering, custom fonts,
46
45
  asset browsing, and metadata variables — so a stateless embed still needs a
47
46
  credential (Step 7).
48
47
 
@@ -62,7 +61,7 @@ npm i @scaleflex/template-builder
62
61
  Or via CDN (self-registering, Lit bundled in — pin the major):
63
62
 
64
63
  ```html
65
- <script type="module" src="https://cdn.scaleflex.com/plugins/scaleflex/template-builder/0.1.0/template-builder.min.js"></script>
64
+ <script type="module" src="https://cdn.cloudimage.io/template-builder/0.4.1/template-builder.min.js"></script>
66
65
  ```
67
66
 
68
67
  **SSR caution:** the element extends `HTMLElement`, so importing `.`,
@@ -83,7 +82,7 @@ in, take the edited XML back out.**
83
82
  <sfx-template-builder
84
83
  stateless
85
84
  base-url="https://<deployment>"
86
- token="FILEROBOT_TOKEN"
85
+ token="PROJECT_TOKEN"
87
86
  sass-key="SASS_KEY"
88
87
  session-uuid="SESSION_UUID"
89
88
  style="display:block;height:800px"
@@ -180,13 +179,13 @@ refused until a layout exists.
180
179
 
181
180
  ### DAM-backed
182
181
 
183
- `template-id` is a Filerobot file uuid; omit it to open the new-template flow.
182
+ `template-id` is a DAM file uuid; omit it to open the new-template flow.
184
183
  The app loads and saves the file itself.
185
184
 
186
185
  ```html
187
186
  <sfx-template-builder
188
187
  base-url="https://<deployment>"
189
- token="FILEROBOT_TOKEN"
188
+ token="PROJECT_TOKEN"
190
189
  sass-key="SASS_KEY"
191
190
  session-uuid="SESSION_UUID"
192
191
  template-id="TEMPLATE_UUID"
@@ -198,8 +197,8 @@ The app loads and saves the file itself.
198
197
 
199
198
  ## Step 5 — Handle the save outcome (stateless only)
200
199
 
201
- The editor clears its unsaved-changes state as soon as it posts `save`, because
202
- a delivered postMessage says nothing about whether the host stored anything.
200
+ The editor clears its unsaved-changes state as soon as it emits `save`, because
201
+ receiving the event says nothing about whether the host stored anything.
203
202
  **Call `confirmSave(false)` when your write fails**, or a failed save silently
204
203
  looks successful and the user loses work:
205
204
 
@@ -245,7 +244,7 @@ it when the host has no Scaleflex identity to hand over per user:
245
244
  ```html
246
245
  <sfx-template-builder
247
246
  base-url="https://<deployment>"
248
- token="FILEROBOT_TOKEN"
247
+ token="PROJECT_TOKEN"
249
248
  sec-template="SEC_TEMPLATE_KEY"
250
249
  stateless
251
250
  ></sfx-template-builder>
@@ -281,13 +280,13 @@ template is what authorizes it when there is no Hub session. Full explanation:
281
280
 
282
281
  ## Step 8 — Register the embedding origin (required)
283
282
 
284
- The host page's origin must be in the deployment's `frame-ancestors` allowlist,
285
- via `NEXT_PUBLIC_TRUSTED_HUB_ORIGINS` on the `design-templates-app` deployment.
283
+ The host page's origin must be in the deployment's embedding allowlist, via
284
+ `NEXT_PUBLIC_TRUSTED_HUB_ORIGINS` on the `design-templates-app` deployment.
286
285
  This is baked in at build time, so it needs a redeploy.
287
286
 
288
- **If this is missed**, the browser refuses to render the iframe and the widget
289
- reports `error` with code `handshake-timeout`. That is the single most common
290
- first-integration failure — check it before anything else.
287
+ **If this is missed**, the browser refuses to load the editor on the host page
288
+ and the widget reports `error` with code `handshake-timeout`. That is the
289
+ single most common first-integration failure — check it before anything else.
291
290
 
292
291
  ## Step 9 — Theming (optional)
293
292
 
@@ -310,12 +309,12 @@ template document.
310
309
  | Attribute / property | Required | Description |
311
310
  |---|---|---|
312
311
  | `base-url` / `baseUrl` | yes | Origin of the design-templates-app deployment |
313
- | `token` | yes | Filerobot token (`ftoken`) |
312
+ | `token` | yes | Scaleflex token (`ftoken`) |
314
313
  | `sass-key` / `sassKey` | session auth | Project sass key |
315
314
  | `session-uuid` / `sessionUuid` | session auth | Hub session uuid |
316
315
  | `sec-template` / `secTemplate` | guest auth | Security-template key, instead of sass key + session uuid. Stateless only (Step 7) |
317
316
  | `company-uuid`, `project-uuid` | no | Company / project scoping (session auth only) |
318
- | `template-id` / `templateId` | no | DAM: Filerobot uuid. Stateless: opaque host id |
317
+ | `template-id` / `templateId` | no | DAM: the file uuid. Stateless: opaque host id |
319
318
  | `mode` | no | `inline` (default) or `modal` (starts closed — call `open()`) |
320
319
  | `stateless` | no | Host owns the document; requires `content`, or `new-template` |
321
320
  | `content` (property only) | stateless | Template as `.fdt` XML |
@@ -339,6 +338,13 @@ template document.
339
338
  `error` codes: `auth`, `invalid-content`, `invalid-config`,
340
339
  `handshake-timeout`, `invalid-base-url`, `unknown`.
341
340
 
341
+ Post-0.3.0 additions (check the CHANGELOG against your pinned bundle):
342
+ `custom-metadata-label` renames the "Custom metadata" value source in the
343
+ editor's UI (wording only); `dam-store` makes the element store each stateless
344
+ save in Filerobot and adds `stored: { uuid, url }` (or `storeError`) to the
345
+ `save` detail — `url` + `templateQuery` is a render URL, and `store-folder`
346
+ names where templates with no existing DAM file land.
347
+
342
348
  ## Public Methods
343
349
 
344
350
  `open(templateId?)`, `close()`, `load({ content, templateId?, name? })`,
@@ -387,7 +393,7 @@ ignored; and `layout` / `locale` / `force_format` are reserved slugs.
387
393
 
388
394
  | Symptom | Cause |
389
395
  |---|---|
390
- | `error` code `handshake-timeout`, blank frame | Host origin not in `frame-ancestors` (Step 8), or third-party cookies blocked |
396
+ | `error` code `handshake-timeout`, nothing rendered | Host origin not in the deployment's embedding allowlist (Step 8), or third-party cookies blocked |
391
397
  | `error` code `auth` | Bad/expired session credentials, a rejected security template, or cookies blocked |
392
398
  | `error` code `invalid-content` | The `content` handed over is not parseable `.fdt` XML |
393
399
  | `error` code `invalid-config` | `sec-template` without `stateless` (Step 7) |
package/CHANGELOG.md CHANGED
@@ -8,6 +8,180 @@ Protocol message *values* are wire format: an existing string is never changed,
8
8
  only new messages added, so an older widget keeps working against a newer app
9
9
  deployment and vice versa.
10
10
 
11
+ ## [Unreleased]
12
+
13
+ ## [0.4.1] - 2026-08-26
14
+
15
+ ### Changed
16
+
17
+ - Demo page defaults: `dam-store` is always on (the toggle is gone from the
18
+ panel — the feature stays a host choice via the attribute), authentication
19
+ defaults to the security template (guest) and the editor opens as a modal.
20
+ No library changes; the release exists to publish the demo and sandbox
21
+ pages on fresh CDN paths.
22
+
23
+ ## [0.4.0] - 2026-08-26
24
+
25
+ ### Added
26
+
27
+ - `stored-uuid` attribute / `storedUuid` prop (and a `storedUuid` field on
28
+ `load()`) — seeds the element's `dam-store` memory with the copy a document
29
+ already has, so re-saves after a page reload resolve to (and version) the
30
+ existing file instead of reporting a spurious `storeError` on unchanged
31
+ content or starting a fresh file. Per-document: `load()` without it clears
32
+ the seed, `createNew()` always does.
33
+ - `flushPendingSaves()` public method — emits any `dam-store` saves still
34
+ waiting on their upload, raw content with `storeError` in place of the
35
+ links. For framework wrappers that unsubscribe their listeners before the
36
+ element leaves the DOM; the React wrapper calls it from its cleanup, so a
37
+ host unmounting `<TemplateBuilder>` mid-upload no longer loses the save.
38
+
39
+ ### Changed
40
+
41
+ - Closing the editor no longer flushes a `dam-store` upload still in flight
42
+ into a `storeError`: the element outlives a close (inline keeps rendering,
43
+ modal drops only its overlay), so the save emits its real outcome when the
44
+ copy lands — possibly after the `close` event. Element removal still
45
+ flushes immediately.
46
+ - `dam-store` filenames: the element reuses its known copy's own filename so
47
+ versioning-in-place keeps matching; otherwise the name carries a
48
+ deterministic hash of the host's template id — the same document lands on
49
+ the same DAM file across page reloads (no fork per session, no stored-uuid
50
+ seed required), while two documents sharing a display name ("Untitled")
51
+ never collide. Only an id-less document falls back to a per-page unique
52
+ suffix.
53
+ - `dam-store` document memory is now epoch-based and validated at read time:
54
+ the document epoch advances only when a genuinely different document ships,
55
+ never on iframe changes — a modal close or theme swap mid-save no longer
56
+ forgets the stored copy, a host echo followed by an in-place remount no
57
+ longer orphans it, and a late-landing upload is recorded under its own
58
+ document's epoch so it can never masquerade as another document's copy.
59
+ - `dam-store` no longer looks up opaque (non-uuid-shaped) template ids
60
+ against the files API — one round trip saved per save, and the store no
61
+ longer depends on how the API answers a malformed id. The transient-failure
62
+ rule is role-aware: the required folder anchor still fails the copy, but a
63
+ transient failure of the optional freshness lookup no longer fails a save
64
+ the other anchor can carry.
65
+ - An unchanged-content conflict with no anchor in hand (first save after a
66
+ reload without the stored-uuid seed) now resolves itself by finding the
67
+ document's own earlier copy by its deterministic filename; a genuinely
68
+ foreign duplicate reports an actionable error naming `stored-uuid` as the
69
+ fix.
70
+ - Removing the element flushes pending saves one task later, skipped if the
71
+ element is reconnected — reparenting the mounted widget (e.g. a fullscreen
72
+ toggle) no longer reports a false `storeError` for an upload that lands
73
+ fine.
74
+ - Demo page: the Filerobot API helpers (key exchange, scope headers, file
75
+ lookup) are imported from `dam-store.ts` instead of being hand copies.
76
+
77
+ ### Fixed
78
+
79
+ - A host echoing the full save detail back into the props (content + name +
80
+ `templateQuery` — the documented controlled pattern) no longer triggers a
81
+ HOST_LOAD that reloads the editor and wipes undo history: the element now
82
+ also accepts the save's own identity, whose query nearly always differs
83
+ from the one the template was loaded with.
84
+ - A transient Filerobot failure (5xx, 429, auth, network) during the
85
+ folder lookup no longer reads as "file does not exist" — which silently
86
+ re-homed an existing template into the fallback folder as a brand-new
87
+ file, or failed an unchanged re-save with a spurious `storeError`. Only a
88
+ clean 404 means missing; everything else fails the copy explicitly, and
89
+ the post-upload record read-back stays best-effort.
90
+ - `dam-store` uploads now declare the template MIME type
91
+ (`text/xml+sfxtemplate`) the app's own uploads use, instead of plain
92
+ `text/xml`.
93
+ - A queued `dam-store` upload captures `store-folder` and the credentials at
94
+ save time — an in-place document swap while a save is still queued no
95
+ longer re-targets that save's upload to the new document's folder or
96
+ tenant.
97
+ - A `dam-store` upload finishing after the host swapped documents in place
98
+ (new `content`/`templateId` on the mounted element) no longer records the
99
+ old document's uuid into the new document's session memory — a follow-up
100
+ save can no longer version the wrong DAM file.
101
+ - React wrapper: event listeners are now attached once per element and read
102
+ the latest callbacks at event time, instead of being re-bound on every
103
+ handler identity change. The unmount-time flush is guarded, so a page
104
+ whose older CDN bundle registered the element first degrades silently
105
+ instead of throwing from the effect cleanup.
106
+ - Demo page: the save handler now detects a `dam-store` save from the event
107
+ detail itself (`stored`/`storeError` present) rather than the live
108
+ `damStore` property, which a "Load template" inside the upload window can
109
+ rewrite before the serialized save event arrives.
110
+ - Demo sample template: the `$headline$` placeholder now renders — the
111
+ variable holds its value in the `default=` attribute as
112
+ `type="text_placeholder"`, the shape the app's parser actually reads
113
+ (CDATA text on a `type="text"` variable is ignored, leaving the layer
114
+ blank).
115
+
116
+ - `dam-store` attribute / `damStore` prop (stateless only) — the element
117
+ stores each save in Filerobot itself, with the same multipart upload the
118
+ DAM-backed editor makes, before emitting `save`. The detail then carries
119
+ `stored: { uuid, url }` (the DAM copy's uuid and its CDN URL with a fresh
120
+ `?vh=`) next to the raw `content` — append the `templateQuery` to `url` and
121
+ it is a render. A failed copy emits `storeError` instead; the raw data
122
+ arrives either way, and the host decides via the save ack whether that
123
+ fails the save. `store-folder` / `storeFolder` names the folder for
124
+ templates whose id matches no existing DAM file (an existing file's own
125
+ folder always wins, so same-name saves version in place). The stored
126
+ `template_query` metadata has custom-metadata-bound values stripped —
127
+ record-agnostic, like the editor's own stores. Rapid saves are serialized
128
+ (events and acks keep post order); element removal mid-upload flushes the
129
+ pending save immediately — raw content with `storeError`, never a dropped
130
+ save (a mere close lets the upload finish and the save emit its real
131
+ outcome); and the element remembers the copy it last made, so unchanged
132
+ re-saves resolve to it instead of erroring even when the host's own
133
+ template id is opaque.
134
+ - Demo page: the store checkbox now drives `dam-store` (on by default) — the
135
+ page writes no upload code; it reads `stored` off the save event, logs the
136
+ links, and the Template id / .fdt URL fields follow the stored file so the
137
+ preview always renders what was just saved. A failed copy fails the save
138
+ (`confirmSave(false)`).
139
+ - Demo page: a **Show preview** button under the template-query field. It
140
+ renders the stored `.fdt` with the query applied and the host's
141
+ custom-metadata values substituted — the same image URL a host page would
142
+ embed. The file's CDN URL is filled in by a library pick, resolved from the
143
+ Template id via the API when the field is left empty (dam-store saves also
144
+ fill it), or pasted by hand. Renders the stored file, not unsaved local
145
+ edits.
146
+
147
+ - `custom-metadata-label` attribute / `customMetadataLabel` prop — renames the
148
+ **Custom metadata** value source in the editor's UI (source dropdowns and the
149
+ properties-panel section), e.g. to `External metadata`. Wording only: the
150
+ saved template carries the same `custom_ckey` binding either way, and an
151
+ empty value keeps the editor's default label. Requires an app deployment
152
+ that understands `customMetadataLabel` in `HOST_CONFIG`; an older app
153
+ ignores it.
154
+
155
+ ## [0.3.0] - 2026-08-18
156
+
157
+ ### Added
158
+
159
+ - `custom-metadata` attribute / `customMetadata` prop — a metadata model
160
+ (`[{ key, title?, group? }]`) offered in the editor as a third **Custom
161
+ metadata** value source, so authors bind a text variable to one of your field
162
+ names instead of to a bare slug. Names only: no values travel with the model
163
+ and the editor resolves nothing against it. The binding is saved on the
164
+ variable as `custom_ckey` while the variable stays `source="URL"`, so the
165
+ render pipeline is unchanged — you substitute the value with `$slug=value` as
166
+ you would for free text, and read `custom_ckey` back from the stored `.fdt` to
167
+ know which field each variable wants. Set no model and the source is not
168
+ offered. Its main use is `sec-template` / stateless embeds, where the Hub
169
+ project model — and with it the *File metadata* source — is unavailable.
170
+ Fields the editor cannot use are dropped rather than breaking the source: no
171
+ `key`, a duplicate `key`, or a `key` holding a character XML cannot represent
172
+ (a C0 control, an unpaired surrogate) — the last of which would otherwise make
173
+ the saved `.fdt` unparseable in its entirety.
174
+ - The demo page plays the host half of it: a switch, an editable JSON model to
175
+ start from, and an optional `value` per field that the page substitutes into
176
+ the render query for any variable bound to that field — on load and on save,
177
+ with each substitution logged. Values never reach the editor.
178
+ - `HOST_CONFIG` message and `CustomMetadataField` / `HostConfigData` protocol
179
+ types; `PROTOCOL_VERSION` is now 3. Additive: an app deployment that predates
180
+ it ignores the message, and a host that sends no model gets the previous
181
+ behaviour exactly. The widget delivers config on the editor's ready signal
182
+ (so it reaches DAM-backed embeds, which exchange no content messages) and
183
+ whenever the model changes.
184
+
11
185
  ## [0.2.0] - 2026-08-11
12
186
 
13
187
  ### Added
@@ -33,12 +207,12 @@ deployment and vice versa.
33
207
  ## [0.1.1] - 2026-08-10
34
208
 
35
209
  First published release. `<sfx-template-builder>` custom element, `./react`
36
- wrapper, `./define` registration, and the v2 postMessage protocol (DAM-backed
37
- and stateless modes). 0.1.0 was never published.
210
+ wrapper, `./define` registration, and the v2 host protocol (DAM-backed and
211
+ stateless modes). 0.1.0 was never published.
38
212
 
39
213
  ### Added
40
214
 
41
- - `sec-template` attribute — authenticate with a Filerobot security template
215
+ - `sec-template` attribute — authenticate with a Scaleflex security template
42
216
  instead of a Hub session. The app exchanges the key for an access key itself
43
217
  and renews it on expiry. Stateless mode only, and Hub-project features
44
218
  (metadata fields, regional variants, project branding) come back empty; see
@@ -58,7 +232,7 @@ and stateless modes). 0.1.0 was never published.
58
232
  parse, rather than leaving the host on a loading state indefinitely.
59
233
 
60
234
  - Buildable demo (`build:demo` → `demo-dist/`) and a `release` script wrapping
61
- the shared Filerobot CDN release pipeline. The demo fetches its template over
235
+ the shared Scaleflex CDN release pipeline. The demo fetches its template over
62
236
  HTTP from a bundled sample, so the "XML comes from your API, goes in, comes
63
237
  back out" round trip is visible end to end with no backend.
64
238
  - `LICENSE` (proprietary), shipped in the npm tarball.