@scaleflex/template-builder 0.1.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.
@@ -0,0 +1,356 @@
1
+ ---
2
+ name: integrate-template-builder
3
+ description: Integrate @scaleflex/template-builder into any project — install, choose
4
+ DAM-backed or stateless storage, wire the save round trip, theme, and register the
5
+ embedding origin. Works with vanilla JS, React, Vue, Angular, Svelte.
6
+ user_invocable: true
7
+ metadata:
8
+ category: integration
9
+ tags:
10
+ - scaleflex
11
+ - filerobot
12
+ - template-builder
13
+ - design-templates
14
+ - web-component
15
+ status: ready
16
+ version: 1
17
+ ---
18
+
19
+ # Scaleflex Template Builder Integration Skill
20
+
21
+ ## When to Use
22
+
23
+ - User says "add the template builder", "embed the design template editor",
24
+ "let users edit templates in our app"
25
+ - User asks how to use `@scaleflex/template-builder` in their project
26
+ - User wants a banner / creative editor backed by Filerobot rendering
27
+
28
+ ## Step 1 — Decide who stores the template
29
+
30
+ This is the first question, and it changes everything downstream. Ask if it is
31
+ not already clear from the project.
32
+
33
+ | | **DAM-backed** (default) | **Stateless** |
34
+ |---|---|---|
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 |
37
+ | Save does | uploads a new version, returns `{ uuid }` | hands back `{ content }` for the host to store |
38
+ | Needs per-user Scaleflex identities | yes | no — one service tenant is enough |
39
+
40
+ Pick **stateless** when the host already has its own users, permissions and
41
+ storage and does not want to map them onto Scaleflex tenants. Pick
42
+ **DAM-backed** when templates are a Filerobot asset like any other.
43
+
44
+ Statelessness applies to the **document only**. The editor still needs an
45
+ authenticated Filerobot tenant for server-side text rendering, custom fonts,
46
+ asset browsing, and metadata variables.
47
+
48
+ ## Step 2 — Detect the target framework
49
+
50
+ Read the project's `package.json`:
51
+
52
+ - **React** (18+): use `@scaleflex/template-builder/react`
53
+ - **Vue / Angular / Svelte / vanilla JS**: use `@scaleflex/template-builder/define`
54
+
55
+ ## Step 3 — Install
56
+
57
+ ```bash
58
+ npm i @scaleflex/template-builder
59
+ ```
60
+
61
+ Or via CDN (self-registering, Lit bundled in — pin the major):
62
+
63
+ ```html
64
+ <script type="module" src="https://cdn.scaleflex.com/plugins/scaleflex/template-builder/0.1.0/template-builder.min.js"></script>
65
+ ```
66
+
67
+ **SSR caution:** the element extends `HTMLElement`, so importing `.`,
68
+ `./define` or `./react` from a server-rendered module throws. In Next.js use
69
+ `dynamic(() => import(...), { ssr: false })`; in Nuxt use `<client-only>`.
70
+
71
+ ## Step 4 — Add the builder
72
+
73
+ Size the element — in `inline` mode it fills the box it is given, and a
74
+ zero-height parent renders nothing.
75
+
76
+ ### Stateless (host owns the template)
77
+
78
+ The flow is always these three steps: **fetch the XML from your API, pass it
79
+ in, take the edited XML back out.**
80
+
81
+ ```html
82
+ <sfx-template-builder
83
+ stateless
84
+ base-url="https://<deployment>"
85
+ token="FILEROBOT_TOKEN"
86
+ sass-key="SASS_KEY"
87
+ session-uuid="SESSION_UUID"
88
+ style="display:block;height:800px"
89
+ ></sfx-template-builder>
90
+
91
+ <script type="module">
92
+ import '@scaleflex/template-builder/define'
93
+
94
+ const builder = document.querySelector('sfx-template-builder')
95
+ const id = 'your-own-id-42'
96
+
97
+ // 1 — get the template XML from your API
98
+ const { content, name } = await fetch(`/api/templates/${id}`).then((r) => r.json())
99
+
100
+ // 2 — pass it in. `content` is a property, never an attribute: templates
101
+ // routinely exceed what fits in markup or a URL.
102
+ builder.load({ templateId: id, name, content })
103
+
104
+ // 3 — take the edited template back out and store it
105
+ builder.addEventListener('save', async (e) => {
106
+ const { templateId, content, name, templateQuery } = e.detail
107
+ const ok = await fetch(`/api/templates/${templateId}`, {
108
+ method: 'PUT',
109
+ headers: { 'content-type': 'application/json' },
110
+ body: JSON.stringify({ content, name, templateQuery }),
111
+ }).then((r) => r.ok)
112
+
113
+ // Reporting the outcome is not optional in practice — see Step 5.
114
+ builder.confirmSave(ok)
115
+ })
116
+ </script>
117
+ ```
118
+
119
+ ### Stateless, React
120
+
121
+ `content` is a prop, and the outcome of `onSave` is acked automatically:
122
+
123
+ ```tsx
124
+ import { TemplateBuilder } from '@scaleflex/template-builder/react'
125
+
126
+ <TemplateBuilder
127
+ stateless
128
+ baseUrl="https://<deployment>"
129
+ token={token}
130
+ sassKey={sassKey}
131
+ sessionUuid={sessionUuid}
132
+ templateId={id}
133
+ content={xml}
134
+ templateName={name}
135
+ style={{ height: 800 }}
136
+ onSave={async (data) => {
137
+ const res = await fetch(`/api/templates/${data.templateId}`, {
138
+ method: 'PUT',
139
+ headers: { 'content-type': 'application/json' },
140
+ body: JSON.stringify(data),
141
+ })
142
+ return res.ok // false → the editor restores its unsaved-changes flag
143
+ }}
144
+ />
145
+ ```
146
+
147
+ ### DAM-backed
148
+
149
+ `template-id` is a Filerobot file uuid; omit it to open the new-template flow.
150
+ The app loads and saves the file itself.
151
+
152
+ ```html
153
+ <sfx-template-builder
154
+ base-url="https://<deployment>"
155
+ token="FILEROBOT_TOKEN"
156
+ sass-key="SASS_KEY"
157
+ session-uuid="SESSION_UUID"
158
+ template-id="TEMPLATE_UUID"
159
+ style="display:block;height:800px"
160
+ ></sfx-template-builder>
161
+ ```
162
+
163
+ `save` then carries `{ uuid, name }` — the uuid of the version just written.
164
+
165
+ ## Step 5 — Handle the save outcome (stateless only)
166
+
167
+ The editor clears its unsaved-changes state as soon as it posts `save`, because
168
+ a delivered postMessage says nothing about whether the host stored anything.
169
+ **Call `confirmSave(false)` when your write fails**, or a failed save silently
170
+ looks successful and the user loses work:
171
+
172
+ ```js
173
+ try {
174
+ await yourApi.save(...)
175
+ builder.confirmSave(true)
176
+ } catch {
177
+ builder.confirmSave(false, 'Could not save — please try again.')
178
+ }
179
+ ```
180
+
181
+ The React wrapper does this from what `onSave` returns or throws.
182
+
183
+ ## Step 6 — Guard against losing edits
184
+
185
+ Assigning a new `content` / `templateId` / `templateName` / `templateQuery`
186
+ reloads the editor and
187
+ **discards unsaved edits without prompting** — the host is authoritative. Watch
188
+ `dirtychange` (or read `isDirty`) before swapping:
189
+
190
+ ```js
191
+ builder.addEventListener('dirtychange', (e) => {
192
+ hasUnsavedEdits = e.detail.isDirty
193
+ })
194
+ ```
195
+
196
+ Re-assigning an identical template is a no-op, so an unrelated re-render cannot
197
+ destroy work by accident.
198
+
199
+ ## Step 7 — Authentication
200
+
201
+ Two credentials work. Both are minted **server-side** and injected into the
202
+ page; neither belongs in a public bundle.
203
+
204
+ **Hub session** — `token` + `sass-key` + `session-uuid`. Full features, both
205
+ storage modes. Issue short-lived per-user sessions; never put a long-lived
206
+ master credential in client-side code.
207
+
208
+ **Security template** — `token` + `sec-template`, no Hub account involved. Use
209
+ it when the host has no Scaleflex identity to hand over per user:
210
+
211
+ ```html
212
+ <sfx-template-builder
213
+ base-url="https://<deployment>"
214
+ token="FILEROBOT_TOKEN"
215
+ sec-template="SEC_TEMPLATE_KEY"
216
+ stateless
217
+ ></sfx-template-builder>
218
+ ```
219
+
220
+ The app exchanges the key for a short-lived access key and renews it on expiry.
221
+ What it costs:
222
+
223
+ - **`stateless` is required.** Without it the widget reports `error` with code
224
+ `invalid-config` and never mounts. The DAM-backed editor keeps needing a
225
+ session.
226
+ - **Hub-project features come back empty** — metadata fields, regional variants,
227
+ dynamic fields, project branding. Theme with `brand-color` / `theme` instead.
228
+ - **Scope the template to what the editor needs**: `LIST` on browsable folders,
229
+ plus `LIST` + `UPLOAD` on `/.studio/fonts*` for custom fonts. Prefer short
230
+ TTLs — expiry is handled, over-permission is not.
231
+
232
+ In stateless mode a single service tenant is usually right either way: one
233
+ machine credential, with all per-user permission logic staying in the host app.
234
+
235
+ ## Step 8 — Register the embedding origin (required)
236
+
237
+ The host page's origin must be in the deployment's `frame-ancestors` allowlist,
238
+ via `NEXT_PUBLIC_TRUSTED_HUB_ORIGINS` on the `design-templates-app` deployment.
239
+ This is baked in at build time, so it needs a redeploy.
240
+
241
+ **If this is missed**, the browser refuses to render the iframe and the widget
242
+ reports `error` with code `handshake-timeout`. That is the single most common
243
+ first-integration failure — check it before anything else.
244
+
245
+ ## Step 9 — Theming (optional)
246
+
247
+ ```html
248
+ <sfx-template-builder brand-color="#FF6600" theme="dark" ...>
249
+ ```
250
+
251
+ - `brand-color` must be `#rgb` / `#rrggbb`; anything else is ignored and the
252
+ Scaleflex accent is kept.
253
+ - Label colour on top of the brand is chosen automatically by contrast.
254
+ - Pick a colour readable on white: one accent token serves both filled surfaces
255
+ and link text, so a very pale brand gives good buttons and weak links.
256
+ - `theme` is `light` / `dark` / `auto`.
257
+
258
+ Themes the **editor chrome only** — colours inside the design live in the
259
+ template document.
260
+
261
+ ## Attributes Reference
262
+
263
+ | Attribute / property | Required | Description |
264
+ |---|---|---|
265
+ | `base-url` / `baseUrl` | yes | Origin of the design-templates-app deployment |
266
+ | `token` | yes | Filerobot token (`ftoken`) |
267
+ | `sass-key` / `sassKey` | session auth | Project sass key |
268
+ | `session-uuid` / `sessionUuid` | session auth | Hub session uuid |
269
+ | `sec-template` / `secTemplate` | guest auth | Security-template key, instead of sass key + session uuid. Stateless only (Step 7) |
270
+ | `company-uuid`, `project-uuid` | no | Company / project scoping (session auth only) |
271
+ | `template-id` / `templateId` | no | DAM: Filerobot uuid. Stateless: opaque host id |
272
+ | `mode` | no | `inline` (default) or `modal` (starts closed — call `open()`) |
273
+ | `stateless` | no | Host owns the document; requires `content` |
274
+ | `content` (property only) | stateless | Template as `.fdt` XML |
275
+ | `template-name` / `templateName` | no | Stateless: header title |
276
+ | `template-query` / `templateQuery` | no | Stateless: the render to open on — the `templateQuery` from the last save. Empty uses the XML's `default=` values. |
277
+ | `brand-color`, `theme` | no | See Step 9 |
278
+ | `ready-timeout` / `readyTimeout` | no | Ms before `handshake-timeout` (default 20000) |
279
+
280
+ ## Events Reference
281
+
282
+ | Event | `detail` |
283
+ |---|---|
284
+ | `ready` | — (editor mounted, auth valid) |
285
+ | `open` | — |
286
+ | `save` | `{ uuid, name }` (DAM) or `{ templateId, content, name, templateQuery }` (stateless) |
287
+ | `dirtychange` | `{ isDirty }` (stateless) |
288
+ | `close` | — (user left the editor, or it unmounted) |
289
+ | `error` | `{ code, message? }` |
290
+
291
+ `error` codes: `auth`, `invalid-content`, `invalid-config`,
292
+ `handshake-timeout`, `invalid-base-url`, `unknown`.
293
+
294
+ ## Public Methods
295
+
296
+ `open(templateId?)`, `close()`, `load({ content, templateId?, name? })`,
297
+ `confirmSave(ok, message?)`. Read-only: `status`, `isDirty`.
298
+
299
+ In React these are reached through a forwarded ref — **required for
300
+ `mode="modal"`**, which renders nothing until `open()` is called:
301
+
302
+ ```tsx
303
+ const builder = useRef<SfxTemplateBuilder>(null)
304
+ <TemplateBuilder ref={builder} mode="modal" ... />
305
+ <button onClick={() => builder.current?.open()}>Edit</button>
306
+ ```
307
+
308
+ ## About `templateQuery`
309
+
310
+ `save` returns a `templateQuery` next to the content — the query string that
311
+ renders the template at its defaults (layout, variable values, locale):
312
+
313
+ ```
314
+ https://<tenant>.filerobot.com/<path>/<template>.fdt?<templateQuery>&force_format=png
315
+ ```
316
+
317
+ Persist it alongside `content`, then pass it back on the next load:
318
+
319
+ ```js
320
+ builder.load({ templateId, name, content, templateQuery })
321
+ ```
322
+
323
+ Omitting it opens the template on the `default=` values in the XML, which is a
324
+ different render whenever the query overrode any of them. It is applied as
325
+ display state — layout and variable values — and does not mark the document
326
+ dirty. Entries naming a variable or layout the document no longer defines are
327
+ ignored, so a stale query still opens.
328
+
329
+ Inbound limits, where the preview can differ from the CDN render of the same
330
+ query: `$locale` is not applied (seeded from Hub project info, which guest
331
+ embeds lack); an explicitly empty value (`$headline=`) falls back to the XML
332
+ default instead of clearing — omit the key rather than sending it blank;
333
+ metadata-sourced variables keep the query's value under a guest session but are
334
+ re-resolved from the linked asset under a Hub session; `$layout_color` is
335
+ ignored; and `layout` / `locale` / `force_format` are reserved slugs.
336
+
337
+ ## Troubleshooting
338
+
339
+ | Symptom | Cause |
340
+ |---|---|
341
+ | `error` code `handshake-timeout`, blank frame | Host origin not in `frame-ancestors` (Step 8), or third-party cookies blocked |
342
+ | `error` code `auth` | Bad/expired session credentials, a rejected security template, or cookies blocked |
343
+ | `error` code `invalid-content` | The `content` handed over is not parseable `.fdt` XML |
344
+ | `error` code `invalid-config` | `sec-template` without `stateless` (Step 7) |
345
+ | Metadata / regional / branding panels empty | Expected under `sec-template` — those come from the Hub project (Step 7) |
346
+ | Nothing renders, no events | `inline` mode inside a zero-height parent — size the element |
347
+ | Modal never appears | `open()` never called; in React that needs a ref |
348
+ | Saves look successful but nothing is stored | `confirmSave(false)` not wired (Step 5) |
349
+ | Images in the template fail to render | Their host is not in the deployment's `RENDER_ALLOWED_HOSTS_EXTRA` |
350
+
351
+ ## Browser Support
352
+
353
+ Chrome/Edge 114+, Firefox 131+, Safari 18.4+. The floors come from partitioned
354
+ cookies (CHIPS), which cross-site embedding depends on — not from web
355
+ components. Older browsers that block third-party cookies fail with `auth` /
356
+ `handshake-timeout`.
package/CHANGELOG.md ADDED
@@ -0,0 +1,66 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@scaleflex/template-builder` are documented here. The
4
+ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
5
+ the package follows [semantic versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ Protocol message *values* are wire format: an existing string is never changed,
8
+ only new messages added, so an older widget keeps working against a newer app
9
+ deployment and vice versa.
10
+
11
+ ## [0.1.1] - 2026-08-10
12
+
13
+ First published release. `<sfx-template-builder>` custom element, `./react`
14
+ wrapper, `./define` registration, and the v2 postMessage protocol (DAM-backed
15
+ and stateless modes). 0.1.0 was never published.
16
+
17
+ ### Added
18
+
19
+ - `sec-template` attribute — authenticate with a Filerobot security template
20
+ instead of a Hub session. The app exchanges the key for an access key itself
21
+ and renews it on expiry. Stateless mode only, and Hub-project features
22
+ (metadata fields, regional variants, project branding) come back empty; see
23
+ *Authentication* in the README. In React the credential props are a
24
+ discriminated union, so mixing the two modes is a type error.
25
+ - `invalid-config` error code — reported when attributes contradict each other,
26
+ currently `sec-template` without `stateless`.
27
+ - `AUTH_MODES` / `AuthMode` protocol exports, and `EMBED_PARAMS.SEC_TEMPLATE`.
28
+ - `brand-color` and `theme` attributes — restyle the editor chrome from a single
29
+ accent colour and pick the colour scheme. Themes the editor UI only; template
30
+ colours live in the document.
31
+ - `confirmSave(ok, message?)` and the `HOST_SAVED` protocol message. Stateless
32
+ hosts can report a failed write, and the editor restores its unsaved-changes
33
+ flag instead of showing the template as saved. The React wrapper reports it
34
+ automatically from what `onSave` returns or throws.
35
+ - `invalid-content` error code — the app now reports a template it could not
36
+ parse, rather than leaving the host on a loading state indefinitely.
37
+
38
+ - Buildable demo (`build:demo` → `demo-dist/`) and a `release` script wrapping
39
+ the shared Filerobot CDN release pipeline. The demo fetches its template over
40
+ HTTP from a bundled sample, so the "XML comes from your API, goes in, comes
41
+ back out" round trip is visible end to end with no backend.
42
+ - `LICENSE` (proprietary), shipped in the npm tarball.
43
+ - `.claude/skills/integrate-template-builder` — an integration skill shipped in
44
+ the package, matching `@scaleflex/asset-picker`.
45
+ - End-to-end coverage of the embed boundary (`e2e/embed-widget.spec.ts` in the
46
+ app repo): a cross-origin host page loads the built CDN bundle, fetches
47
+ template XML from its own API, and gets the edited document back on save.
48
+
49
+ ### Changed
50
+
51
+ - CDN bundle moved from `dist/cdn/template-builder.js` to
52
+ `dist-cdn/template-builder.min.js`, matching `@scaleflex/asset-picker`. It no
53
+ longer ships inside the npm tarball, where it duplicated the library and Lit.
54
+ - `build` now produces only the npm artifact; use `build:cdn` / `build:all`.
55
+ - `license` corrected from `MIT` to `SEE LICENSE IN LICENSE`.
56
+
57
+ ### Fixed
58
+
59
+ - The React wrapper kept its ref private, so `open()`, `close()`, `load()` and
60
+ `confirmSave()` were unreachable — which made `mode="modal"` unusable from
61
+ React, since nothing renders until `open()` is called. It now forwards a ref
62
+ to the element.
63
+ - An unparseable `base-url` emitted `error` on every render pass instead of once.
64
+ - The editor's close button navigated an embedded editor to the Scaleflex
65
+ dashboard inside the host page. It now posts `builder:close` and leaves the
66
+ decision to the embedder.
package/LICENSE ADDED
@@ -0,0 +1,50 @@
1
+ PROPRIETARY SOFTWARE LICENSE
2
+
3
+ Copyright (c) 2025 Scaleflex SAS. All Rights Reserved.
4
+
5
+ NOTICE: This software and associated documentation files (the "Software") are
6
+ the exclusive property of Scaleflex SAS. The Software is protected by copyright
7
+ laws, international treaties, and other intellectual property laws.
8
+
9
+ RESTRICTIONS
10
+
11
+ You may NOT, without prior written permission from Scaleflex SAS:
12
+
13
+ 1. Copy, reproduce, or duplicate the Software, in whole or in part.
14
+ 2. Modify, adapt, translate, reverse engineer, decompile, or disassemble the
15
+ Software, or create derivative works based on the Software.
16
+ 3. Distribute, sublicense, lease, rent, loan, sell, or otherwise transfer the
17
+ Software or any rights therein to any third party.
18
+ 4. Remove, alter, or obscure any proprietary notices, labels, or marks on the
19
+ Software.
20
+ 5. Use the Software for any purpose other than as expressly authorised under
21
+ a separate written licence agreement with Scaleflex SAS.
22
+
23
+ GRANT OF LICENCE
24
+
25
+ Use of this Software is permitted only under a separate written licence
26
+ agreement between the licensee and Scaleflex SAS. Installing or downloading
27
+ this Software does not, by itself, grant any licence or right to use the
28
+ Software.
29
+
30
+ NO WARRANTY
31
+
32
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34
+ FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL
35
+ SCALEFLEX SAS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN
36
+ AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN
37
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38
+
39
+ ENFORCEMENT
40
+
41
+ Unauthorised use, reproduction, or distribution of this Software, or any
42
+ portion thereof, may result in severe civil and criminal penalties and will be
43
+ prosecuted to the maximum extent permitted by applicable law.
44
+
45
+ CONTACT
46
+
47
+ For licensing enquiries:
48
+ Scaleflex SAS
49
+ Email: sales@scaleflex.com
50
+ Web: https://www.scaleflex.com