@embeddables/cli 0.10.0 → 0.12.0
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/.prompts/custom/build-funnel.md +1 -1
- package/.prompts/embeddables-cli.md +308 -26
- package/dist/assets-manifest.d.ts +31 -0
- package/dist/assets-manifest.d.ts.map +1 -0
- package/dist/assets-manifest.js +59 -0
- package/dist/assets-manifest.js.map +1 -0
- package/dist/cli.js +283 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/assets-sync.d.ts +6 -0
- package/dist/commands/assets-sync.d.ts.map +1 -0
- package/dist/commands/assets-sync.js +35 -0
- package/dist/commands/assets-sync.js.map +1 -0
- package/dist/commands/assets-upload.d.ts +13 -0
- package/dist/commands/assets-upload.d.ts.map +1 -0
- package/dist/commands/assets-upload.js +475 -0
- package/dist/commands/assets-upload.js.map +1 -0
- package/dist/commands/experiments-connect.js +22 -19
- package/dist/commands/experiments-connect.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +37 -91
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +3 -0
- package/dist/commands/pull.js.map +1 -1
- package/dist/compiler/reverse.d.ts.map +1 -1
- package/dist/compiler/reverse.js +19 -6
- package/dist/compiler/reverse.js.map +1 -1
- package/dist/constants/defaultTagStylesV4.d.ts +6 -0
- package/dist/constants/defaultTagStylesV4.d.ts.map +1 -0
- package/dist/constants/defaultTagStylesV4.js +659 -0
- package/dist/constants/defaultTagStylesV4.js.map +1 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -1
- package/dist/group-context.d.ts +2 -0
- package/dist/group-context.d.ts.map +1 -0
- package/dist/group-context.js +29 -0
- package/dist/group-context.js.map +1 -0
- package/dist/helpers/typeStubs.d.ts +25 -0
- package/dist/helpers/typeStubs.d.ts.map +1 -0
- package/dist/helpers/typeStubs.js +153 -0
- package/dist/helpers/typeStubs.js.map +1 -0
- package/dist/prompts/experiments.d.ts +3 -1
- package/dist/prompts/experiments.d.ts.map +1 -1
- package/dist/prompts/experiments.js +12 -5
- package/dist/prompts/experiments.js.map +1 -1
- package/dist/workbench/FieldEditorPanel.d.ts +2 -1
- package/dist/workbench/FieldEditorPanel.d.ts.map +1 -1
- package/dist/workbench/FieldEditorPanel.js +21 -10
- package/dist/workbench/FieldEditorPanel.js.map +1 -1
- package/dist/workbench/UserDataPanel.d.ts.map +1 -1
- package/dist/workbench/UserDataPanel.js +103 -3
- package/dist/workbench/UserDataPanel.js.map +1 -1
- package/dist/{commands/build-workbench.d.ts → workbench/build.d.ts} +1 -1
- package/dist/workbench/build.d.ts.map +1 -0
- package/dist/{commands/build-workbench.js → workbench/build.js} +1 -1
- package/dist/workbench/build.js.map +1 -0
- package/package.json +1 -1
- package/dist/commands/build-workbench.d.ts.map +0 -1
- package/dist/commands/build-workbench.js.map +0 -1
|
@@ -2,7 +2,7 @@ We need to build the pages from the attached images. Please create a plan and hi
|
|
|
2
2
|
|
|
3
3
|
Embeddable ID: <EMBEDDABLE_ID>
|
|
4
4
|
|
|
5
|
-
Important: Always read the Embeddables CLI context before starting (e.g. @.cursor/rules/embeddables-cli.md, @.claude/embeddables-cli.md,
|
|
5
|
+
Important: Always read the Embeddables CLI context before starting (e.g. @.cursor/rules/embeddables-cli.md, @.claude/embeddables-cli.md, @AGENTS.md, or @.agent/rules/embeddables-cli.md depending on your editor).
|
|
6
6
|
|
|
7
7
|
[KEEP / REMOVE]
|
|
8
8
|
Use the designs from the images but use the content from the attached document instead, and build out the pages based on that content. Therefore, when using the designs from the images, for each page just find the most relevant design for the page's content and design it based on that. However, don't be confined to the provided designs - just find the closest design and modify it to fit the content required.
|
|
@@ -7,7 +7,9 @@ alwaysApply: true
|
|
|
7
7
|
|
|
8
8
|
This document provides comprehensive context for Cursor AI when working with Embeddables in the CLI codebase. The CLI transforms Embeddable JSON into a local file structure for development, then compiles it back to JSON for saving.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## Types as Source of Truth (MANDATORY)
|
|
11
|
+
|
|
12
|
+
**Always consult `.types/` before adding, modifying, or using component props, Flow properties, or any API.** The types are the authoritative source of truth.
|
|
11
13
|
|
|
12
14
|
## Overview
|
|
13
15
|
|
|
@@ -219,7 +221,47 @@ Note that these are desktop-first, not mobile-first (i.e. that the default style
|
|
|
219
221
|
- Tablet: `[max-width~='720px'] ` (note trailing space)
|
|
220
222
|
- Mobile: `[max-width~='520px'] ` (note trailing space)
|
|
221
223
|
|
|
222
|
-
|
|
224
|
+
**Breakpoint config in `config.json` (high priority; must follow exactly):**
|
|
225
|
+
|
|
226
|
+
- If you need to use any breakpoints other than `720px` and `520px`, you must add them in a top-level `breakpoints` property in `config.json` (typing is `Breakpoint[]`).
|
|
227
|
+
- `breakpoints` must be a **top-level** property in `config.json`.
|
|
228
|
+
- `breakpoints` must be placed **before** the `defaults` property.
|
|
229
|
+
- Each breakpoint must contain:
|
|
230
|
+
- `max_width` (number)
|
|
231
|
+
- `name` (string)
|
|
232
|
+
- Do not rename properties or change this structure.
|
|
233
|
+
- Keep `breakpoints` ordered from largest to smallest `max_width`.
|
|
234
|
+
- Always preserve valid JSON formatting.
|
|
235
|
+
|
|
236
|
+
Required structure:
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"breakpoints": [
|
|
241
|
+
{
|
|
242
|
+
"max_width": 1280,
|
|
243
|
+
"name": "Desktop medium"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"max_width": 1024,
|
|
247
|
+
"name": "Desktop small"
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"max_width": 900,
|
|
251
|
+
"name": "Tablet big"
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"max_width": 380,
|
|
255
|
+
"name": "Mobile small"
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"max_width": 320,
|
|
259
|
+
"name": "Mobile mini"
|
|
260
|
+
}
|
|
261
|
+
],
|
|
262
|
+
"defaults": {}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
223
265
|
|
|
224
266
|
#### 3. Scope
|
|
225
267
|
|
|
@@ -282,7 +324,10 @@ Note: If you need to use any breakpoints other than 720px and 520px, you must ad
|
|
|
282
324
|
// Button text on hover (sub-element: component in selector must be the one that owns it—CustomButton, not a container)
|
|
283
325
|
.Flow-Component.ComponentType-CustomButton.ComponentTag-primary_button:hover .ElementType-ButtonText
|
|
284
326
|
|
|
285
|
-
// Mobile breakpoint
|
|
327
|
+
// Mobile breakpoint (built-in)
|
|
328
|
+
.Flow-EntireFlow[max-width~='520px'] .Flow-Component.ComponentTag-landing_container
|
|
329
|
+
|
|
330
|
+
// Custom breakpoint (only after adding it to top-level config.json "breakpoints")
|
|
286
331
|
.Flow-EntireFlow[max-width~='1024px'] .Flow-Component.ComponentTag-landing_container
|
|
287
332
|
|
|
288
333
|
// Page-scoped component (PageKey is on the component, not the page)
|
|
@@ -323,6 +368,18 @@ For placeholder images, the recommended service is placehold.co:
|
|
|
323
368
|
|
|
324
369
|
Please be consistent with your choice out of those options within a particular component or section.
|
|
325
370
|
|
|
371
|
+
### Project Asset Manifest (`assets.json`)
|
|
372
|
+
|
|
373
|
+
If an `assets.json` file exists in the project root, treat it as the source of truth for uploaded assets and their uplaoded URLs and prefer it before creating new placeholder URLs.
|
|
374
|
+
|
|
375
|
+
- `assets.json` is generated by `embeddables assets sync` and refreshed by `embeddables assets upload`.
|
|
376
|
+
- It contains DB-backed metadata (for the project/group) plus optional `relativePath` entries for files uploaded from local folders.
|
|
377
|
+
- For embeddable-specific assets, prefer entries where `flow_id` matches the current embeddable.
|
|
378
|
+
- For project-level shared assets (from `/assets`), prefer entries where `flow_id` is `null`.
|
|
379
|
+
- If an asset is referenced by local file path, use `relativePath` to resolve the matching uploaded URL from `assets.json`.
|
|
380
|
+
|
|
381
|
+
When you need an existing image/icon URL for `MediaImage.src` or button `imageUrl`, check `assets.json` first and reuse those URLs whenever possible.
|
|
382
|
+
|
|
326
383
|
## Component Sub-Element Types Reference
|
|
327
384
|
|
|
328
385
|
### OptionSelector
|
|
@@ -443,13 +500,17 @@ Computed fields are stored in `Flow.computedFields` and transform to JS files. T
|
|
|
443
500
|
|
|
444
501
|
**File Mapping**: `computed-fields/{key}.js` (e.g., `computed-fields/total_price.js`)
|
|
445
502
|
|
|
446
|
-
**Code Structure**: The `code` property contains JavaScript that must include a function called `result`
|
|
503
|
+
**Code Structure**: The `code` property contains JavaScript that must include a function called `result`. The runtime passes a `context` object as the first argument:
|
|
447
504
|
|
|
448
505
|
```javascript
|
|
449
|
-
function result() {
|
|
506
|
+
function result(context) {
|
|
450
507
|
// Access user data via global userData object
|
|
451
508
|
const price = userData.price || 0
|
|
452
509
|
const quantity = userData.quantity || 0
|
|
510
|
+
|
|
511
|
+
// context.triggerContext is available (see Actions section for shape)
|
|
512
|
+
// context.trackCustomEvent(name, props) can track analytics events
|
|
513
|
+
|
|
453
514
|
return price * quantity
|
|
454
515
|
}
|
|
455
516
|
```
|
|
@@ -460,6 +521,7 @@ function result() {
|
|
|
460
521
|
- The function has access to a global `userData` object containing all user data
|
|
461
522
|
- If `async` is true, the function can return a Promise
|
|
462
523
|
- `input_triggers` specify additional keys that trigger recalculation
|
|
524
|
+
- The `context` parameter provides `triggerContext` and `trackCustomEvent()` — same as actions (see "Trigger Context" and "Custom Event Tracking" under Actions)
|
|
463
525
|
|
|
464
526
|
## Actions Structure
|
|
465
527
|
|
|
@@ -474,14 +536,20 @@ Actions are stored in `Flow.dataOutputs` and transform to JS files. The `Action`
|
|
|
474
536
|
|
|
475
537
|
**File Mapping**: `actions/{name}.js` (e.g., `actions/submit_form.js`)
|
|
476
538
|
|
|
477
|
-
**Code Structure**: For `output: "custom"`, the `code` property contains JavaScript that must include a function called `output
|
|
539
|
+
**Code Structure**: For `output: "custom"`, the `code` property contains JavaScript that must include a function called `output`. The runtime passes a `context` object as the first argument:
|
|
478
540
|
|
|
479
541
|
```javascript
|
|
480
|
-
function output() {
|
|
542
|
+
function output(context) {
|
|
481
543
|
// Access user data via global userData object
|
|
482
544
|
const email = userData.email
|
|
483
545
|
const name = userData.name
|
|
484
546
|
|
|
547
|
+
// context.triggerContext contains metadata about what fired this action (see below)
|
|
548
|
+
const trigger = context.triggerContext
|
|
549
|
+
|
|
550
|
+
// Track a custom analytics event (in-action usage)
|
|
551
|
+
context.trackCustomEvent('form_submitted', { email })
|
|
552
|
+
|
|
485
553
|
// Perform action (API call, data transformation, etc.)
|
|
486
554
|
return {
|
|
487
555
|
success: true,
|
|
@@ -492,10 +560,195 @@ function output() {
|
|
|
492
560
|
|
|
493
561
|
**Important**:
|
|
494
562
|
|
|
495
|
-
- Actions are triggered by
|
|
563
|
+
- Actions are triggered by `outputs_on*` fields (see "Action Trigger Wiring" below) or programmatically
|
|
496
564
|
- The function has access to a global `userData` object
|
|
565
|
+
- The `context` parameter provides `triggerContext` and `trackCustomEvent` (see below)
|
|
497
566
|
- For non-custom actions (airtable, hubspot, etc.), the action configuration is stored in config.json, not in the JS file
|
|
498
567
|
|
|
568
|
+
### Action Trigger Wiring (`outputs_on*` Fields)
|
|
569
|
+
|
|
570
|
+
Actions are wired to events via `outputs_on*` properties. Each property holds an **action ID** (string) or an **array of action IDs** (`string[]`) referencing entries in `dataOutputs`. When the event fires, the engine runs the referenced actions.
|
|
571
|
+
|
|
572
|
+
#### Flow-Level Triggers
|
|
573
|
+
|
|
574
|
+
These are top-level properties on the `Flow` object (stored in `config.json`):
|
|
575
|
+
|
|
576
|
+
| Field | Type | Fires when |
|
|
577
|
+
| ---------------------- | -------------------- | ---------------------------------------------- |
|
|
578
|
+
| `outputs_onloadflow` | `string \| string[]` | The embeddable script loads and initializes |
|
|
579
|
+
| `outputs_onviewflow` | `string \| string[]` | The embeddable becomes visible in the viewport |
|
|
580
|
+
| `outputs_onremoved` | `string[]` | The embeddable is removed from the DOM |
|
|
581
|
+
| `outputs_onopenpopup` | `string \| string[]` | A popup embeddable is opened |
|
|
582
|
+
| `outputs_onclosepopup` | `string \| string[]` | A popup embeddable is closed |
|
|
583
|
+
|
|
584
|
+
#### Page-Level Triggers
|
|
585
|
+
|
|
586
|
+
These are properties on `FlowPage` objects (stored in the page entry in `config.json`):
|
|
587
|
+
|
|
588
|
+
| Field | Type | Fires when |
|
|
589
|
+
| ------------------------- | -------------------- | ---------------------------------------------------------- |
|
|
590
|
+
| `outputs_oncomplete` | `string \| string[]` | The user completes this page (i.e. moves to the next page) |
|
|
591
|
+
| `outputs_onload` | `string \| string[]` | The page is loaded / navigated to |
|
|
592
|
+
| `outputs_onopen_infobox` | `string[]` | An info box on this page is opened |
|
|
593
|
+
| `outputs_onclose_infobox` | `string[]` | An info box on this page is closed |
|
|
594
|
+
|
|
595
|
+
#### Component-Level Triggers
|
|
596
|
+
|
|
597
|
+
These are properties on component objects (written as props in React/TSX files):
|
|
598
|
+
|
|
599
|
+
**Base (all components)**:
|
|
600
|
+
|
|
601
|
+
| Field | Type | Fires when |
|
|
602
|
+
| ---------------------------- | ---------- | ------------------------------------ |
|
|
603
|
+
| `outputs_onmounted` | `string[]` | The component mounts in the DOM |
|
|
604
|
+
| `outputs_onunmounted` | `string[]` | The component unmounts from the DOM |
|
|
605
|
+
| `outputs_onrepeatablerender` | `string[]` | A repeater re-renders this component |
|
|
606
|
+
|
|
607
|
+
**CustomButton**:
|
|
608
|
+
|
|
609
|
+
| Field | Type | Fires when |
|
|
610
|
+
| ----------------- | -------------------- | --------------------- |
|
|
611
|
+
| `outputs_onclick` | `string \| string[]` | The button is clicked |
|
|
612
|
+
|
|
613
|
+
**InputBox**:
|
|
614
|
+
|
|
615
|
+
| Field | Type | Fires when |
|
|
616
|
+
| ------------------ | ---------- | ----------------------- |
|
|
617
|
+
| `outputs_onchange` | `string[]` | The input value changes |
|
|
618
|
+
|
|
619
|
+
**OptionSelector**:
|
|
620
|
+
|
|
621
|
+
| Field | Type | Fires when |
|
|
622
|
+
| ----------------------------------- | ---------- | ------------------------------ |
|
|
623
|
+
| `outputs_onchange` | `string[]` | The selected option(s) change |
|
|
624
|
+
| `outputs_onbuttonsrepeatablerender` | `string[]` | The button repeater re-renders |
|
|
625
|
+
|
|
626
|
+
**FileUpload**:
|
|
627
|
+
|
|
628
|
+
| Field | Type | Fires when |
|
|
629
|
+
| ------------------ | ---------- | ----------------------------- |
|
|
630
|
+
| `outputs_onchange` | `string[]` | Files are uploaded or removed |
|
|
631
|
+
|
|
632
|
+
**Payment Components** (StripeCheckout2, PaypalCheckout):
|
|
633
|
+
|
|
634
|
+
| Field | Type | Fires when |
|
|
635
|
+
| ------------------------------ | -------- | ---------------------------------- |
|
|
636
|
+
| `on_payment_complete_outputs` | `string` | Payment succeeds |
|
|
637
|
+
| `on_payment_failed_outputs` | `string` | Payment fails (Stripe only) |
|
|
638
|
+
| `on_payment_attempted_outputs` | `string` | Payment is attempted (Stripe only) |
|
|
639
|
+
|
|
640
|
+
#### Example: Wiring an Action to a Button Click
|
|
641
|
+
|
|
642
|
+
```tsx
|
|
643
|
+
<CustomButton
|
|
644
|
+
id="comp_0000000001"
|
|
645
|
+
key="submit_button"
|
|
646
|
+
text="Submit"
|
|
647
|
+
action="no-action"
|
|
648
|
+
outputs_onclick={['action_1234567890']}
|
|
649
|
+
/>
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Where `action_1234567890` is the `id` of an action in `dataOutputs`. The button's built-in `action` prop (`"next-page"`, `"prev-page"`, etc.) handles navigation; `outputs_onclick` fires separate data actions.
|
|
653
|
+
|
|
654
|
+
#### Example: Wiring an Action to Page Completion
|
|
655
|
+
|
|
656
|
+
In `config.json`:
|
|
657
|
+
|
|
658
|
+
```json
|
|
659
|
+
{
|
|
660
|
+
"pages": [
|
|
661
|
+
{
|
|
662
|
+
"id": "page_0000000001",
|
|
663
|
+
"key": "contact_page",
|
|
664
|
+
"outputs_oncomplete": ["action_1234567890"]
|
|
665
|
+
}
|
|
666
|
+
]
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### Programmatic Action Triggering
|
|
671
|
+
|
|
672
|
+
You can trigger an Action from code by doing this:
|
|
673
|
+
|
|
674
|
+
```javascript
|
|
675
|
+
window.Savvy.triggerAction(embeddableId, actionId)
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### Trigger Context (`triggerContext`)
|
|
679
|
+
|
|
680
|
+
When the engine fires an action, it passes a `context` object to the `output(context)` function. `context.triggerContext` contains metadata about what triggered the action:
|
|
681
|
+
|
|
682
|
+
| Field | Type | Description |
|
|
683
|
+
| -------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
684
|
+
| `triggerType` | `string` | The event type that fired the action (e.g. `"onclick"`, `"onchange"`, `"oncomplete"`, `"onloadflow"`, `"onviewflow"`, `"manual"`) |
|
|
685
|
+
| `componentId` | `string \| undefined` | The component ID that fired the trigger (for component-level triggers) |
|
|
686
|
+
| `componentKey` | `string \| undefined` | The component key that fired the trigger |
|
|
687
|
+
| `pageId` | `string \| undefined` | The page ID associated with the trigger |
|
|
688
|
+
| `pageKey` | `string \| undefined` | The page key associated with the trigger |
|
|
689
|
+
|
|
690
|
+
Example usage inside an action:
|
|
691
|
+
|
|
692
|
+
```javascript
|
|
693
|
+
function output(context) {
|
|
694
|
+
const { triggerType, componentKey, pageKey } = context.triggerContext || {}
|
|
695
|
+
|
|
696
|
+
if (triggerType === 'oncomplete' && pageKey === 'checkout_page') {
|
|
697
|
+
// Only run full submission on checkout page completion
|
|
698
|
+
return submitOrder(userData)
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// For other triggers, just track progress
|
|
702
|
+
return { tracked: true }
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
Computed fields also receive `context` in the `result(context)` function with the same `triggerContext` shape, which is useful for computed fields that need to behave differently based on what triggered their recalculation.
|
|
707
|
+
|
|
708
|
+
### Custom Event Tracking
|
|
709
|
+
|
|
710
|
+
Embeddables support tracking custom analytics events. There are two contexts where you can track events:
|
|
711
|
+
|
|
712
|
+
#### 1. Inside Action or Computed Field Code (Runtime Context)
|
|
713
|
+
|
|
714
|
+
Use `context.trackCustomEvent(eventName, eventProps)` inside `output()` or `result()` functions:
|
|
715
|
+
|
|
716
|
+
```javascript
|
|
717
|
+
function output(context) {
|
|
718
|
+
const plan = userData.selected_plan
|
|
719
|
+
|
|
720
|
+
// Track a custom event from within an action
|
|
721
|
+
context.trackCustomEvent('plan_selected', {
|
|
722
|
+
plan: plan,
|
|
723
|
+
price: userData.plan_price,
|
|
724
|
+
})
|
|
725
|
+
|
|
726
|
+
return { success: true }
|
|
727
|
+
}
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
#### 2. From the Host Page (Window API)
|
|
731
|
+
|
|
732
|
+
Use `window.Savvy.trackCustomEvent(flowId, eventName, eventProps)` from the host page embedding the embeddable:
|
|
733
|
+
|
|
734
|
+
```javascript
|
|
735
|
+
window.Savvy.trackCustomEvent('flow_abc123', 'external_checkout_complete', {
|
|
736
|
+
orderId: '12345',
|
|
737
|
+
total: 99.99,
|
|
738
|
+
})
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
- `flowId` — the embeddable's `id`
|
|
742
|
+
- `eventName` — a custom event name string
|
|
743
|
+
- `eventProps` — an object of key-value pairs to attach to the event
|
|
744
|
+
|
|
745
|
+
#### When to Use Which
|
|
746
|
+
|
|
747
|
+
| Context | API | Use when |
|
|
748
|
+
| ------------------------------ | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
749
|
+
| Inside `output()` / `result()` | `context.trackCustomEvent(name, props)` | Tracking events as part of action/computed field logic (e.g. after a form submission, when a computed value changes) |
|
|
750
|
+
| Host page JS | `window.Savvy.trackCustomEvent(flowId, name, props)` | Tracking events from outside the embeddable (e.g. after an external checkout, on a parent SPA route change, from a third-party integration callback) |
|
|
751
|
+
|
|
499
752
|
## Conditions Structure
|
|
500
753
|
|
|
501
754
|
Conditions control visibility of components and pages based on User Data. The `Condition`, `ConditionOperator`, `ConditionValue`, and `ConditionalTag` types are defined in `src/types-builder.ts`.
|
|
@@ -541,10 +794,12 @@ Example: for experiment key `hero_test` with variants `control` and `variant_b`,
|
|
|
541
794
|
The `config.json` file contains the reduced Embeddable JSON with:
|
|
542
795
|
|
|
543
796
|
- All structural properties (id, title, builder_version, etc.)
|
|
797
|
+
- Optional top-level `breakpoints` array when using custom breakpoints (must appear before `defaults`; see Breakpoint section)
|
|
544
798
|
- Page metadata (id, key, properties) but NOT component content (components are in React files)
|
|
545
799
|
- Style selectors but NOT style values (styles are in CSS files)
|
|
546
800
|
- Computed field metadata (id, key, inputs, etc.) but NOT code (code is in JS files)
|
|
547
801
|
- Action metadata (id, name, output, mappings, etc.) but NOT code (code is in JS files)
|
|
802
|
+
- `_docs` (string) — preserved at top-level, in pages, computedFields, and dataOutputs
|
|
548
803
|
- All other Flow properties (experiments, transitions, settings, etc.)
|
|
549
804
|
|
|
550
805
|
**Example config.json structure**:
|
|
@@ -587,6 +842,22 @@ The `config.json` file contains the reduced Embeddable JSON with:
|
|
|
587
842
|
}
|
|
588
843
|
```
|
|
589
844
|
|
|
845
|
+
## \_docs Property
|
|
846
|
+
|
|
847
|
+
The `_docs` property (string only) documents non-obvious aspects of the embeddable. It can appear at:
|
|
848
|
+
|
|
849
|
+
- **Top-level** — embeddable-wide notes (e.g. flow purpose, integration details)
|
|
850
|
+
- **Pages** — page-specific notes (e.g. why a page exists, routing logic)
|
|
851
|
+
- **Computed fields** — notes about formulas or dependencies
|
|
852
|
+
- **Actions (dataOutputs)** — notes about what an action does or when it runs
|
|
853
|
+
|
|
854
|
+
**AI behavior**:
|
|
855
|
+
|
|
856
|
+
- **Read** all `_docs` when working on an embeddable, page, computed field, or action to understand context that may not be obvious from the code.
|
|
857
|
+
- **Add** `_docs` when building or discovering something that should be documented because it is non-obvious (e.g. a computed field with non-trivial logic, an action with special side effects, a page with conditional routing).
|
|
858
|
+
|
|
859
|
+
**Storage**: When reverse-compiled, `_docs` is preserved in `config.json` at the corresponding level (top-level, `pages[]`, `computedFields[]`, `dataOutputs[]`).
|
|
860
|
+
|
|
590
861
|
## File Organization Rules
|
|
591
862
|
|
|
592
863
|
### React Files (Pages)
|
|
@@ -609,48 +880,56 @@ The `config.json` file contains the reduced Embeddable JSON with:
|
|
|
609
880
|
- Location: `styles/index.css` — a single CSS file for the flow (do not create multiple CSS files)
|
|
610
881
|
- Format: Standard CSS with selector as class name
|
|
611
882
|
- Content: Style properties from `Flow.styles[selector]`
|
|
883
|
+
- Project root reference: `default-styles.css` contains baseline styles for standard component tags (scaffolded by `embeddables init`)
|
|
612
884
|
|
|
613
885
|
### JS Files (Computed Fields)
|
|
614
886
|
|
|
615
887
|
- Location: `computed-fields/{key}.js`
|
|
616
888
|
- Export: Function named `result` that returns computed value
|
|
617
|
-
- Access: Global `userData` object available
|
|
889
|
+
- Access: Global `userData` object available; `context` parameter provides `triggerContext` and `trackCustomEvent()`
|
|
618
890
|
|
|
619
891
|
### JS Files (Actions)
|
|
620
892
|
|
|
621
893
|
- Location: `actions/{name}.js`
|
|
622
894
|
- Export: Function named `output` for custom actions
|
|
623
|
-
- Access: Global `userData` object available
|
|
895
|
+
- Access: Global `userData` object available; `context` parameter provides `triggerContext` and `trackCustomEvent()`
|
|
624
896
|
|
|
625
897
|
## Key Principles for Modifications
|
|
626
898
|
|
|
627
|
-
1. **
|
|
899
|
+
1. **Types first**: Before adding or changing any component props, Flow properties, validation, or behavior, read the relevant interfaces in `.types/types.d.ts`. Use the types as the source of truth—do not guess or infer from examples.
|
|
900
|
+
|
|
901
|
+
2. **Component Keys**: Must be unique and descriptive. Keys become User Data keys for input components.
|
|
628
902
|
|
|
629
|
-
|
|
903
|
+
3. **Tags**: Used for CSS styling. Apply tags consistently:
|
|
630
904
|
- Primary tag (required): Describes component type (e.g., `standard_button`, `price_card`)
|
|
631
905
|
- Secondary tag (optional): Variant of primary (e.g., `primary_button`, `outline_button`)
|
|
632
906
|
- Utility tag (optional): Only for margins that can't be applied via primary/secondary (e.g., `m_2`)
|
|
907
|
+
- When using **standard tags**, ensure required default selectors exist in `default-styles.css` and copy missing selectors into the embeddable's `styles/index.css` when needed
|
|
908
|
+
- Standard tags to recognize:
|
|
909
|
+
`standard_plain_text`, `standard_rich_text`, `standard_container`, `standard_image`, `standard_button`, `standard_media_embed`, `standard_slider`, `standard_input`, `standard_checkbox`, `standard_option_buttons`, `standard_dropdown`, `standard_switch`, `standard_file_upload`, `standard_stripe_checkout`, `standard_calendly`, `standard_hubspot`, `standard_progress_bar`, `standard_progress_circle`
|
|
633
910
|
|
|
634
|
-
|
|
911
|
+
4. **Component Types**:
|
|
635
912
|
- Use `PlainText` by default
|
|
636
913
|
- Use `RichTextMarkdown` only for mixed formatting (bold, italic, lists, links, colors)
|
|
637
914
|
- Use `CustomHTML` for presentational content only (overlays, inline SVGs, decorative HTML). **Caution**: CustomHTML is for presentational content only. For interactive elements like buttons or selectable options, always use `CustomButton` or `OptionSelector` — CustomHTML elements have no access to flow actions or user data outputs.
|
|
638
915
|
|
|
639
|
-
|
|
916
|
+
5. **Nested Components**: Containers can have `components[]` array with child components. Render as nested JSX.
|
|
917
|
+
|
|
918
|
+
6. **Global Components**: Must use the filenames to replace the `_location` property.
|
|
640
919
|
|
|
641
|
-
|
|
920
|
+
7. **Conditions**: Stored in config.json, applied at runtime. Multiple values in one condition = OR, multiple conditions = AND. For experiment-gated conditions, when setting the control variant's conditions always include both the control variant value and `_no_value` so both control and unassigned users see the intended default.
|
|
642
921
|
|
|
643
|
-
|
|
922
|
+
8. **CSS Selectors**: Must follow exact structure with proper spacing (trailing space on breakpoints, leading space on sub-elements).
|
|
644
923
|
|
|
645
|
-
|
|
924
|
+
9. **No `!important` in CSS**: Do not use `!important` in embeddable styles. If a style does not apply, fix the selector per the CSS Selector System (principle 8); do not use `!important` as a fix.
|
|
646
925
|
|
|
647
|
-
|
|
926
|
+
10. **User Data Keys**: Input component keys become User Data keys. Choose descriptive names (e.g., `email` not `email_input`).
|
|
648
927
|
|
|
649
|
-
|
|
928
|
+
11. **Computed Field Dependencies**: `inputs` array defines which User Data keys trigger recalculation. Can reference other computed fields.
|
|
650
929
|
|
|
651
|
-
|
|
930
|
+
12. **Action Triggers**: Actions fire from `outputs_on*` fields that store action IDs. Flow-level triggers (e.g. `outputs_onloadflow`) go in `config.json` top-level; page-level triggers (e.g. `outputs_oncomplete`) go in the page entry in `config.json`; component-level triggers (e.g. `outputs_onclick`, `outputs_onchange`) go as props in React files. Actions can be invoked via `window.Savvy.triggerAction()`. See "Action Trigger Wiring" and "Programmatic Action Triggering" sections for details.
|
|
652
931
|
|
|
653
|
-
|
|
932
|
+
13. **Asset URL Source of Truth**: If `assets.json` exists in the project root, use it as the first source for image/file URLs. Prefer matching by `flow_id` (or `flow_id: null` for project-level shared assets) and use `relativePath` when mapping local files to uploaded URLs. Always use the (uploaded) url property, not local relative paths, as the image src when building in React.
|
|
654
933
|
|
|
655
934
|
## Common Patterns
|
|
656
935
|
|
|
@@ -663,10 +942,12 @@ The `config.json` file contains the reduced Embeddable JSON with:
|
|
|
663
942
|
|
|
664
943
|
### Adding a Component to a Page
|
|
665
944
|
|
|
666
|
-
1.
|
|
667
|
-
2.
|
|
668
|
-
3.
|
|
669
|
-
4.
|
|
945
|
+
1. Check `.types/types.d.ts` for the component interface (e.g. `InputBox`, `CustomButton`) and `Base` for shared props—use only props that exist in the types.
|
|
946
|
+
2. Update the page's React file (`pages/{pageKey}.page.tsx`)
|
|
947
|
+
3. Add component JSX with appropriate props
|
|
948
|
+
4. If new tags are used, ensure CSS exists for those tags
|
|
949
|
+
5. If any tag is a `standard_*` tag, check `default-styles.css` in project root and add missing style selectors for that tag to `styles/index.css`
|
|
950
|
+
6. Update config.json if component metadata is needed (rarely)
|
|
670
951
|
|
|
671
952
|
### Styling a Component
|
|
672
953
|
|
|
@@ -685,9 +966,10 @@ The `config.json` file contains the reduced Embeddable JSON with:
|
|
|
685
966
|
### Adding an Action
|
|
686
967
|
|
|
687
968
|
1. Create JS file: `actions/{name}.js`
|
|
688
|
-
2. Implement `output()` function (for custom actions)
|
|
969
|
+
2. Implement `output(context)` function (for custom actions) — use `context.triggerContext` for trigger metadata and `context.trackCustomEvent()` for analytics
|
|
689
970
|
3. Add metadata to config.json: `dataOutputs` array
|
|
690
971
|
4. Configure action type and mappings in config.json
|
|
972
|
+
5. Wire the action to a trigger: add the action's `id` to the appropriate `outputs_on*` field on the flow (config.json top-level), page (config.json pages entry), or component (React prop)
|
|
691
973
|
|
|
692
974
|
### Modifying Global Components
|
|
693
975
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
export interface AssetMetadataRow {
|
|
3
|
+
id: string;
|
|
4
|
+
group_id: string;
|
|
5
|
+
flow_id: string | null;
|
|
6
|
+
name: string;
|
|
7
|
+
url: string;
|
|
8
|
+
archived: boolean;
|
|
9
|
+
created_at: string;
|
|
10
|
+
updated_at: string;
|
|
11
|
+
}
|
|
12
|
+
export interface AssetManifestEntry extends AssetMetadataRow {
|
|
13
|
+
relativePath?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface AssetsManifestFile {
|
|
16
|
+
generatedAt: string;
|
|
17
|
+
groupId: string;
|
|
18
|
+
assets: AssetManifestEntry[];
|
|
19
|
+
}
|
|
20
|
+
export declare function readAssetsManifest(filePath: string): AssetsManifestFile | null;
|
|
21
|
+
export declare function writeAssetsManifest(filePath: string, manifest: AssetsManifestFile): void;
|
|
22
|
+
export declare function syncAssetsManifest(params: {
|
|
23
|
+
supabase: SupabaseClient;
|
|
24
|
+
groupId: string;
|
|
25
|
+
outFilePath?: string;
|
|
26
|
+
relativePathByUrl?: Map<string, string>;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
outFilePath: string;
|
|
29
|
+
count: number;
|
|
30
|
+
}>;
|
|
31
|
+
//# sourceMappingURL=assets-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets-manifest.d.ts","sourceRoot":"","sources":["../src/assets-manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,kBAAmB,SAAQ,gBAAgB;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,kBAAkB,EAAE,CAAA;CAC7B;AAMD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAa9E;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAExF;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,QAAQ,EAAE,cAAc,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxC,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAuClD"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
function normalizeRelativePath(relativePath) {
|
|
4
|
+
return relativePath.replace(/\\/g, '/').replace(/^\.?\//, '');
|
|
5
|
+
}
|
|
6
|
+
export function readAssetsManifest(filePath) {
|
|
7
|
+
if (!fs.existsSync(filePath))
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
11
|
+
if (!parsed || !Array.isArray(parsed.assets))
|
|
12
|
+
return null;
|
|
13
|
+
return {
|
|
14
|
+
generatedAt: typeof parsed.generatedAt === 'string' ? parsed.generatedAt : '',
|
|
15
|
+
groupId: typeof parsed.groupId === 'string' ? parsed.groupId : '',
|
|
16
|
+
assets: parsed.assets,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function writeAssetsManifest(filePath, manifest) {
|
|
24
|
+
fs.writeFileSync(filePath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
25
|
+
}
|
|
26
|
+
export async function syncAssetsManifest(params) {
|
|
27
|
+
const { supabase, groupId } = params;
|
|
28
|
+
const outFilePath = path.resolve(params.outFilePath ?? 'assets.json');
|
|
29
|
+
const { data, error } = await supabase
|
|
30
|
+
.from('assets')
|
|
31
|
+
.select('id, group_id, flow_id, name, url, archived, created_at, updated_at')
|
|
32
|
+
.eq('group_id', groupId)
|
|
33
|
+
.order('created_at', { ascending: false });
|
|
34
|
+
if (error)
|
|
35
|
+
throw new Error(`Could not fetch assets metadata: ${error.message}`);
|
|
36
|
+
const rows = (data ?? []);
|
|
37
|
+
const existing = readAssetsManifest(outFilePath);
|
|
38
|
+
const existingRelativeByUrl = new Map();
|
|
39
|
+
for (const asset of existing?.assets ?? []) {
|
|
40
|
+
if (asset.url && asset.relativePath) {
|
|
41
|
+
existingRelativeByUrl.set(asset.url, normalizeRelativePath(asset.relativePath));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const incomingRelativeByUrl = new Map();
|
|
45
|
+
for (const [url, relativePath] of params.relativePathByUrl ?? new Map()) {
|
|
46
|
+
incomingRelativeByUrl.set(url, normalizeRelativePath(relativePath));
|
|
47
|
+
}
|
|
48
|
+
const assets = rows.map((row) => {
|
|
49
|
+
const relativePath = incomingRelativeByUrl.get(row.url) ?? existingRelativeByUrl.get(row.url);
|
|
50
|
+
return relativePath ? { ...row, relativePath } : row;
|
|
51
|
+
});
|
|
52
|
+
writeAssetsManifest(outFilePath, {
|
|
53
|
+
generatedAt: new Date().toISOString(),
|
|
54
|
+
groupId,
|
|
55
|
+
assets,
|
|
56
|
+
});
|
|
57
|
+
return { outFilePath, count: assets.length };
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=assets-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets-manifest.js","sourceRoot":"","sources":["../src/assets-manifest.ts"],"names":[],"mappings":"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"}
|