@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.
Files changed (61) hide show
  1. package/.prompts/custom/build-funnel.md +1 -1
  2. package/.prompts/embeddables-cli.md +308 -26
  3. package/dist/assets-manifest.d.ts +31 -0
  4. package/dist/assets-manifest.d.ts.map +1 -0
  5. package/dist/assets-manifest.js +59 -0
  6. package/dist/assets-manifest.js.map +1 -0
  7. package/dist/cli.js +283 -1
  8. package/dist/cli.js.map +1 -1
  9. package/dist/commands/assets-sync.d.ts +6 -0
  10. package/dist/commands/assets-sync.d.ts.map +1 -0
  11. package/dist/commands/assets-sync.js +35 -0
  12. package/dist/commands/assets-sync.js.map +1 -0
  13. package/dist/commands/assets-upload.d.ts +13 -0
  14. package/dist/commands/assets-upload.d.ts.map +1 -0
  15. package/dist/commands/assets-upload.js +475 -0
  16. package/dist/commands/assets-upload.js.map +1 -0
  17. package/dist/commands/experiments-connect.js +22 -19
  18. package/dist/commands/experiments-connect.js.map +1 -1
  19. package/dist/commands/init.d.ts.map +1 -1
  20. package/dist/commands/init.js +37 -91
  21. package/dist/commands/init.js.map +1 -1
  22. package/dist/commands/pull.d.ts.map +1 -1
  23. package/dist/commands/pull.js +3 -0
  24. package/dist/commands/pull.js.map +1 -1
  25. package/dist/compiler/reverse.d.ts.map +1 -1
  26. package/dist/compiler/reverse.js +19 -6
  27. package/dist/compiler/reverse.js.map +1 -1
  28. package/dist/constants/defaultTagStylesV4.d.ts +6 -0
  29. package/dist/constants/defaultTagStylesV4.d.ts.map +1 -0
  30. package/dist/constants/defaultTagStylesV4.js +659 -0
  31. package/dist/constants/defaultTagStylesV4.js.map +1 -0
  32. package/dist/constants.d.ts +2 -0
  33. package/dist/constants.d.ts.map +1 -1
  34. package/dist/constants.js +2 -0
  35. package/dist/constants.js.map +1 -1
  36. package/dist/group-context.d.ts +2 -0
  37. package/dist/group-context.d.ts.map +1 -0
  38. package/dist/group-context.js +29 -0
  39. package/dist/group-context.js.map +1 -0
  40. package/dist/helpers/typeStubs.d.ts +25 -0
  41. package/dist/helpers/typeStubs.d.ts.map +1 -0
  42. package/dist/helpers/typeStubs.js +153 -0
  43. package/dist/helpers/typeStubs.js.map +1 -0
  44. package/dist/prompts/experiments.d.ts +3 -1
  45. package/dist/prompts/experiments.d.ts.map +1 -1
  46. package/dist/prompts/experiments.js +12 -5
  47. package/dist/prompts/experiments.js.map +1 -1
  48. package/dist/workbench/FieldEditorPanel.d.ts +2 -1
  49. package/dist/workbench/FieldEditorPanel.d.ts.map +1 -1
  50. package/dist/workbench/FieldEditorPanel.js +21 -10
  51. package/dist/workbench/FieldEditorPanel.js.map +1 -1
  52. package/dist/workbench/UserDataPanel.d.ts.map +1 -1
  53. package/dist/workbench/UserDataPanel.js +103 -3
  54. package/dist/workbench/UserDataPanel.js.map +1 -1
  55. package/dist/{commands/build-workbench.d.ts → workbench/build.d.ts} +1 -1
  56. package/dist/workbench/build.d.ts.map +1 -0
  57. package/dist/{commands/build-workbench.js → workbench/build.js} +1 -1
  58. package/dist/workbench/build.js.map +1 -0
  59. package/package.json +1 -1
  60. package/dist/commands/build-workbench.d.ts.map +0 -1
  61. 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, or @AGENTS.md depending on your editor).
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
- **Note**: All TypeScript types are defined in `.types/types-builder.d.ts`. Refer to that file for complete type definitions. This document focuses on contextual information about how the system works.
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
- Note: If you need to use any breakpoints other than 720px and 520px, you must add them in a top-level `breakpoints` (typing is Breakpoint[]) property in config.json.
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` that returns the computed value:
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 various events (button clicks, page completion, etc.)
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. **Component Keys**: Must be unique and descriptive. Keys become User Data keys for input components.
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
- 2. **Tags**: Used for CSS styling. Apply tags consistently:
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
- 3. **Component Types**:
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
- 4. **Nested Components**: Containers can have `components[]` array with child components. Render as nested JSX.
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
- 5. **Global Components**: Must use the filenames to replace the `_location` property.
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
- 6. **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.
922
+ 8. **CSS Selectors**: Must follow exact structure with proper spacing (trailing space on breakpoints, leading space on sub-elements).
644
923
 
645
- 7. **CSS Selectors**: Must follow exact structure with proper spacing (trailing space on breakpoints, leading space on sub-elements).
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
- 8. **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 7); do not use `!important` as a fix.
926
+ 10. **User Data Keys**: Input component keys become User Data keys. Choose descriptive names (e.g., `email` not `email_input`).
648
927
 
649
- 9. **User Data Keys**: Input component keys become User Data keys. Choose descriptive names (e.g., `email` not `email_input`).
928
+ 11. **Computed Field Dependencies**: `inputs` array defines which User Data keys trigger recalculation. Can reference other computed fields.
650
929
 
651
- 10. **Computed Field Dependencies**: `inputs` array defines which User Data keys trigger recalculation. Can reference other computed fields.
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
- 11. **Action Triggers**: Actions are triggered by events (button clicks, page completion, etc.). Configuration stored in config.json, code in JS files.
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. Update the page's React file (`pages/{pageKey}.page.tsx`)
667
- 2. Add component JSX with appropriate props
668
- 3. If new tags are used, ensure CSS exists for those tags
669
- 4. Update config.json if component metadata is needed (rarely)
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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"}