@embeddables/cli 0.11.0 → 0.12.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.
@@ -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
 
@@ -498,13 +500,17 @@ Computed fields are stored in `Flow.computedFields` and transform to JS files. T
498
500
 
499
501
  **File Mapping**: `computed-fields/{key}.js` (e.g., `computed-fields/total_price.js`)
500
502
 
501
- **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:
502
504
 
503
505
  ```javascript
504
- function result() {
506
+ function result(context) {
505
507
  // Access user data via global userData object
506
508
  const price = userData.price || 0
507
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
+
508
514
  return price * quantity
509
515
  }
510
516
  ```
@@ -515,6 +521,7 @@ function result() {
515
521
  - The function has access to a global `userData` object containing all user data
516
522
  - If `async` is true, the function can return a Promise
517
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)
518
525
 
519
526
  ## Actions Structure
520
527
 
@@ -529,14 +536,20 @@ Actions are stored in `Flow.dataOutputs` and transform to JS files. The `Action`
529
536
 
530
537
  **File Mapping**: `actions/{name}.js` (e.g., `actions/submit_form.js`)
531
538
 
532
- **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:
533
540
 
534
541
  ```javascript
535
- function output() {
542
+ function output(context) {
536
543
  // Access user data via global userData object
537
544
  const email = userData.email
538
545
  const name = userData.name
539
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
+
540
553
  // Perform action (API call, data transformation, etc.)
541
554
  return {
542
555
  success: true,
@@ -547,10 +560,195 @@ function output() {
547
560
 
548
561
  **Important**:
549
562
 
550
- - 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
551
564
  - The function has access to a global `userData` object
565
+ - The `context` parameter provides `triggerContext` and `trackCustomEvent` (see below)
552
566
  - For non-custom actions (airtable, hubspot, etc.), the action configuration is stored in config.json, not in the JS file
553
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
+
554
752
  ## Conditions Structure
555
753
 
556
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`.
@@ -601,6 +799,7 @@ The `config.json` file contains the reduced Embeddable JSON with:
601
799
  - Style selectors but NOT style values (styles are in CSS files)
602
800
  - Computed field metadata (id, key, inputs, etc.) but NOT code (code is in JS files)
603
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
604
803
  - All other Flow properties (experiments, transitions, settings, etc.)
605
804
 
606
805
  **Example config.json structure**:
@@ -643,6 +842,22 @@ The `config.json` file contains the reduced Embeddable JSON with:
643
842
  }
644
843
  ```
645
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
+
646
861
  ## File Organization Rules
647
862
 
648
863
  ### React Files (Pages)
@@ -671,19 +886,21 @@ The `config.json` file contains the reduced Embeddable JSON with:
671
886
 
672
887
  - Location: `computed-fields/{key}.js`
673
888
  - Export: Function named `result` that returns computed value
674
- - Access: Global `userData` object available
889
+ - Access: Global `userData` object available; `context` parameter provides `triggerContext` and `trackCustomEvent()`
675
890
 
676
891
  ### JS Files (Actions)
677
892
 
678
893
  - Location: `actions/{name}.js`
679
894
  - Export: Function named `output` for custom actions
680
- - Access: Global `userData` object available
895
+ - Access: Global `userData` object available; `context` parameter provides `triggerContext` and `trackCustomEvent()`
681
896
 
682
897
  ## Key Principles for Modifications
683
898
 
684
- 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.
685
902
 
686
- 2. **Tags**: Used for CSS styling. Apply tags consistently:
903
+ 3. **Tags**: Used for CSS styling. Apply tags consistently:
687
904
  - Primary tag (required): Describes component type (e.g., `standard_button`, `price_card`)
688
905
  - Secondary tag (optional): Variant of primary (e.g., `primary_button`, `outline_button`)
689
906
  - Utility tag (optional): Only for margins that can't be applied via primary/secondary (e.g., `m_2`)
@@ -691,28 +908,28 @@ The `config.json` file contains the reduced Embeddable JSON with:
691
908
  - Standard tags to recognize:
692
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`
693
910
 
694
- 3. **Component Types**:
911
+ 4. **Component Types**:
695
912
  - Use `PlainText` by default
696
913
  - Use `RichTextMarkdown` only for mixed formatting (bold, italic, lists, links, colors)
697
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.
698
915
 
699
- 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.
700
917
 
701
- 5. **Global Components**: Must use the filenames to replace the `_location` property.
918
+ 6. **Global Components**: Must use the filenames to replace the `_location` property.
702
919
 
703
- 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.
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.
704
921
 
705
- 7. **CSS Selectors**: Must follow exact structure with proper spacing (trailing space on breakpoints, leading space on sub-elements).
922
+ 8. **CSS Selectors**: Must follow exact structure with proper spacing (trailing space on breakpoints, leading space on sub-elements).
706
923
 
707
- 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.
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.
708
925
 
709
- 9. **User Data Keys**: Input component keys become User Data keys. Choose descriptive names (e.g., `email` not `email_input`).
926
+ 10. **User Data Keys**: Input component keys become User Data keys. Choose descriptive names (e.g., `email` not `email_input`).
710
927
 
711
- 10. **Computed Field Dependencies**: `inputs` array defines which User Data keys trigger recalculation. Can reference other computed fields.
928
+ 11. **Computed Field Dependencies**: `inputs` array defines which User Data keys trigger recalculation. Can reference other computed fields.
712
929
 
713
- 11. **Action Triggers**: Actions are triggered by events (button clicks, page completion, etc.). Configuration stored in config.json, code in JS files.
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.
714
931
 
715
- 12. **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.
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.
716
933
 
717
934
  ## Common Patterns
718
935
 
@@ -725,11 +942,12 @@ The `config.json` file contains the reduced Embeddable JSON with:
725
942
 
726
943
  ### Adding a Component to a Page
727
944
 
728
- 1. Update the page's React file (`pages/{pageKey}.page.tsx`)
729
- 2. Add component JSX with appropriate props
730
- 3. If new tags are used, ensure CSS exists for those tags
731
- 4. 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`
732
- 5. 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)
733
951
 
734
952
  ### Styling a Component
735
953
 
@@ -748,9 +966,10 @@ The `config.json` file contains the reduced Embeddable JSON with:
748
966
  ### Adding an Action
749
967
 
750
968
  1. Create JS file: `actions/{name}.js`
751
- 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
752
970
  3. Add metadata to config.json: `dataOutputs` array
753
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)
754
973
 
755
974
  ### Modifying Global Components
756
975