@embeddables/cli 0.14.4 → 0.15.0-beta.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 (107) hide show
  1. package/.prompts/custom/build-funnel.md +3 -1
  2. package/.prompts/custom/carousel.md +159 -0
  3. package/.prompts/embeddables-cli.md +116 -42
  4. package/.prompts/short-rule-body.md +15 -0
  5. package/README.md +94 -0
  6. package/dist/auth/index.d.ts +27 -0
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js +44 -0
  9. package/dist/auth/index.js.map +1 -1
  10. package/dist/cli.js +69 -2
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/build-workbench.d.ts +5 -0
  13. package/dist/commands/build-workbench.d.ts.map +1 -0
  14. package/dist/commands/build-workbench.js +117 -0
  15. package/dist/commands/build-workbench.js.map +1 -0
  16. package/dist/commands/dangerously-publish.d.ts +53 -0
  17. package/dist/commands/dangerously-publish.d.ts.map +1 -0
  18. package/dist/commands/dangerously-publish.js +731 -0
  19. package/dist/commands/dangerously-publish.js.map +1 -0
  20. package/dist/commands/init.d.ts.map +1 -1
  21. package/dist/commands/init.js +3 -253
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/commands/login.d.ts +2 -0
  24. package/dist/commands/login.d.ts.map +1 -1
  25. package/dist/commands/login.js +41 -1
  26. package/dist/commands/login.js.map +1 -1
  27. package/dist/commands/provide-otp.d.ts +11 -0
  28. package/dist/commands/provide-otp.d.ts.map +1 -0
  29. package/dist/commands/provide-otp.js +102 -0
  30. package/dist/commands/provide-otp.js.map +1 -0
  31. package/dist/commands/update-project-files.d.ts +8 -0
  32. package/dist/commands/update-project-files.d.ts.map +1 -0
  33. package/dist/commands/update-project-files.js +245 -0
  34. package/dist/commands/update-project-files.js.map +1 -0
  35. package/dist/commands/upgrade.d.ts.map +1 -1
  36. package/dist/commands/upgrade.js +16 -0
  37. package/dist/commands/upgrade.js.map +1 -1
  38. package/dist/compiler/flatten.js +1 -0
  39. package/dist/compiler/parsePage.js +14 -9
  40. package/dist/compiler/parsePage.js.map +1 -1
  41. package/dist/compiler/reverse.d.ts.map +1 -1
  42. package/dist/compiler/reverse.js +18 -8
  43. package/dist/compiler/reverse.js.map +1 -1
  44. package/dist/components/primitives/CustomHTML.d.ts +2 -1
  45. package/dist/components/primitives/CustomHTML.d.ts.map +1 -1
  46. package/dist/components/primitives/CustomHTML.js +11 -2
  47. package/dist/components/primitives/CustomHTML.js.map +1 -1
  48. package/dist/components/primitives/OptionSelector.d.ts +1 -0
  49. package/dist/components/primitives/OptionSelector.d.ts.map +1 -1
  50. package/dist/components/primitives/OptionSelector.js.map +1 -1
  51. package/dist/constants.d.ts +1 -1
  52. package/dist/constants.d.ts.map +1 -1
  53. package/dist/constants.js +2 -3
  54. package/dist/constants.js.map +1 -1
  55. package/dist/helpers/TEMP helpers file.d.ts +1 -0
  56. package/dist/helpers/TEMP helpers file.d.ts.map +1 -0
  57. package/dist/helpers/TEMP helpers file.js +1 -0
  58. package/dist/helpers/json.d.ts.map +1 -1
  59. package/dist/helpers/json.js +132 -17
  60. package/dist/helpers/json.js.map +1 -1
  61. package/dist/prompts/branches.d.ts.map +1 -1
  62. package/dist/prompts/branches.js +2 -0
  63. package/dist/prompts/branches.js.map +1 -1
  64. package/dist/types-builder.d.ts +6 -2
  65. package/dist/types-builder.d.ts.map +1 -1
  66. package/dist/workbench/AutofillPanel.d.ts.map +1 -1
  67. package/dist/workbench/AutofillPanel.js +52 -14
  68. package/dist/workbench/AutofillPanel.js.map +1 -1
  69. package/dist/workbench/FeedbackPanel.d.ts +39 -0
  70. package/dist/workbench/FeedbackPanel.d.ts.map +1 -0
  71. package/dist/workbench/FeedbackPanel.js +279 -0
  72. package/dist/workbench/FeedbackPanel.js.map +1 -0
  73. package/dist/workbench/PageThumbnailStrip.d.ts +6 -0
  74. package/dist/workbench/PageThumbnailStrip.d.ts.map +1 -0
  75. package/dist/workbench/PageThumbnailStrip.js +124 -0
  76. package/dist/workbench/PageThumbnailStrip.js.map +1 -0
  77. package/dist/workbench/Toast.d.ts +18 -0
  78. package/dist/workbench/Toast.d.ts.map +1 -0
  79. package/dist/workbench/Toast.js +46 -0
  80. package/dist/workbench/Toast.js.map +1 -0
  81. package/dist/workbench/UserDataPanel.d.ts +2 -1
  82. package/dist/workbench/UserDataPanel.d.ts.map +1 -1
  83. package/dist/workbench/UserDataPanel.js +2 -1
  84. package/dist/workbench/UserDataPanel.js.map +1 -1
  85. package/dist/workbench/WorkbenchApp.d.ts.map +1 -1
  86. package/dist/workbench/WorkbenchApp.js +19 -3
  87. package/dist/workbench/WorkbenchApp.js.map +1 -1
  88. package/dist/workbench/cloudflare-worker/README.md +31 -0
  89. package/dist/workbench/cloudflare-worker/public/workbench.css +1614 -0
  90. package/dist/workbench/cloudflare-worker/public/workbench.js +77 -0
  91. package/dist/workbench/cloudflare-worker/worker.js +40 -0
  92. package/dist/workbench/cloudflare-worker/wrangler.toml +10 -0
  93. package/dist/workbench/supabase-browser.d.ts +11 -0
  94. package/dist/workbench/supabase-browser.d.ts.map +1 -0
  95. package/dist/workbench/supabase-browser.js +18 -0
  96. package/dist/workbench/supabase-browser.js.map +1 -0
  97. package/dist/workbench/types.d.ts +25 -0
  98. package/dist/workbench/types.d.ts.map +1 -0
  99. package/dist/workbench/types.js +2 -0
  100. package/dist/workbench/types.js.map +1 -0
  101. package/dist/workbench/useComponentSelection.d.ts +27 -0
  102. package/dist/workbench/useComponentSelection.d.ts.map +1 -0
  103. package/dist/workbench/useComponentSelection.js +203 -0
  104. package/dist/workbench/useComponentSelection.js.map +1 -0
  105. package/dist/workbench/workbench.css +1614 -0
  106. package/dist/workbench/workbench.js +77 -0
  107. package/package.json +1 -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, @AGENTS.md, or @.agent/rules/embeddables-cli.md depending on your editor).
5
+ Important: Always read the Embeddables CLI context before starting. The full guide is at @embeddables/AI-README.md. Your editor's rule file (e.g. @.cursor/rules/embeddables-cli.md, @.claude/CLAUDE.md, @AGENTS.md, or @.agent/rules/embeddables-cli.md) points to it with a summary of what it covers.
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.
@@ -17,6 +17,8 @@ Notes:
17
17
  a. `emojiIcon` with an emoji when there is an appropriate one (can be used in `imageUrl` in CustomButton, or `imageUrl` inside OptionSelector buttons)
18
18
  b. placeholder image: https://placehold.co/600x400?text=My+Text+To+Display if there is text in the image (can't be emojis), otherwise https://placehold.co/600x400 (but with the correct size specified in the URL in either case; can be used in `src` in MediaImage, `imageUrl` in CustomButton, or `imageUrl` inside OptionSelector buttons)
19
19
 
20
+ Before finishing: **Review** your changes per the Embeddables CLI rules—especially the "When to Review (Required Checks)" section. Run `embeddables build` to verify compilation, then run `embeddables save` and review the JSON diff it shows before confirming.
21
+
20
22
  At the end of your work, include a short summary covering:
21
23
 
22
24
  - Decisions made – Any implementation choices where requirements, design, or instructions were not fully explicit.
@@ -0,0 +1,159 @@
1
+ # Carousel / Swiper Implementation Guide
2
+
3
+ When building a carousel or slider in an Embeddable, use **Swiper v11.2.10** loaded at runtime via CDN. No build tooling is required.
4
+
5
+ ## Pattern Overview
6
+
7
+ 1. **Page TSX structure** — A `Container` with child elements (each child becomes a slide)
8
+ 2. **Initialization action** — An action file that loads Swiper from CDN and initializes it on `outputs_onloadflow`
9
+ 3. **CSS** — Styles for the carousel container and optional prev/next navigation buttons
10
+ 4. **Optional** — Prev/next `CustomButton` components for navigation
11
+
12
+ ## 1. Page TSX Structure
13
+
14
+ Structure your carousel as nested Containers:
15
+
16
+ ```tsx
17
+ <Container id="comp_xxx" key="carousel_container" tags={["carousel_container"]}>
18
+ {/* Inner container — this becomes the Swiper container (identified by key) */}
19
+ <Container id="comp_yyy" key="my_carousel" tags={["carousel_inner"]}>
20
+ {/* Each direct child becomes a slide */}
21
+ <Container id="comp_1" key="slide_1" tags={["slide"]}>...</Container>
22
+ <Container id="comp_2" key="slide_2" tags={["slide"]}>...</Container>
23
+ <Container id="comp_3" key="slide_3" tags={["slide"]}>...</Container>
24
+ </Container>
25
+ {/* Optional prev/next buttons */}
26
+ <CustomButton id="comp_prev" key="carousel_prev" tags={["carousel_arrow", "left_arrow"]} text="" action="no-action" />
27
+ <CustomButton id="comp_next" key="carousel_next" tags={["carousel_arrow", "right_arrow"]} text="" action="no-action" />
28
+ </Container>
29
+ ```
30
+
31
+ - The **inner** `Container` (e.g. `key="my_carousel"`) is the one the action targets — it must have a unique `key`.
32
+ - The action adds `swiper`, `swiper-wrapper`, and `swiper-slide` classes at runtime.
33
+ - Slides can be any component: `Container`, `MediaImage`, `CustomHTML`, etc.
34
+
35
+ ## 2. Action File
36
+
37
+ Create an action (e.g. `actions/swiper.js`) that:
38
+
39
+ 1. Loads Swiper CSS and JS from CDN
40
+ 2. Finds the container by `data-component-key-value` (the component `key`)
41
+ 3. Adds Swiper classes to the container and its children
42
+ 4. Initializes `new Swiper(container, options)` with optional navigation
43
+
44
+ **Minimal template:**
45
+
46
+ ```javascript
47
+ function output(userData) {
48
+ loadStylesheet("https://unpkg.com/swiper@11.2.10/swiper-bundle.min.css");
49
+ loadScript("https://unpkg.com/swiper@11.2.10/swiper-bundle.min.js", () => {
50
+ initCarousel({
51
+ containerKey: "my_carousel", // key of the inner Container
52
+ prevKey: "carousel_prev", // key of prev button (or null)
53
+ nextKey: "carousel_next", // key of next button (or null)
54
+ slidesPerView: 1,
55
+ spaceBetween: 10,
56
+ loop: true,
57
+ breakpoints: {
58
+ 1024: { slidesPerView: 3, spaceBetween: 24 },
59
+ },
60
+ });
61
+ });
62
+ }
63
+
64
+ function loadStylesheet(href) {
65
+ const link = document.createElement("link");
66
+ link.rel = "stylesheet";
67
+ link.href = href;
68
+ document.head.appendChild(link);
69
+ }
70
+
71
+ function loadScript(src, callback) {
72
+ const script = document.createElement("script");
73
+ script.src = src;
74
+ script.onload = callback;
75
+ document.head.appendChild(script);
76
+ }
77
+
78
+ function getElementByKey(key) {
79
+ return document.querySelector(`[data-component-key-value="${key}"]`);
80
+ }
81
+
82
+ function initCarousel(options) {
83
+ const { containerKey, prevKey, nextKey, ...swiperOptions } = options;
84
+ const container = getElementByKey(containerKey);
85
+ if (!container) return;
86
+
87
+ const wrapper = container.firstElementChild;
88
+ if (!wrapper) return;
89
+
90
+ wrapper.classList.add("swiper-wrapper");
91
+ Array.from(wrapper.children).forEach(el => el.classList.add("swiper-slide"));
92
+ container.classList.add("swiper");
93
+
94
+ const nextBtn = nextKey ? getElementByKey(nextKey) : null;
95
+ const prevBtn = prevKey ? getElementByKey(prevKey) : null;
96
+
97
+ new Swiper(container, {
98
+ ...swiperOptions,
99
+ navigation: nextBtn && prevBtn ? { nextEl: nextBtn, prevEl: prevBtn } : {},
100
+ });
101
+ }
102
+ ```
103
+
104
+ **Wire the action** to `outputs_onloadflow` in `config.json` so it runs when the embeddable loads.
105
+
106
+ ## 3. CSS
107
+
108
+ Add styles for the carousel layout and navigation buttons in `styles/index.css`:
109
+
110
+ ```css
111
+ .Flow-Component.ComponentTag-carousel_container {
112
+ display: flex;
113
+ align-items: center;
114
+ width: 100%;
115
+ position: relative;
116
+ justify-content: center;
117
+ height: fit-content;
118
+ }
119
+
120
+ .Flow-Component.ComponentTag-carousel_arrow {
121
+ width: 44px;
122
+ height: 44px;
123
+ z-index: 10;
124
+ position: absolute;
125
+ cursor: pointer;
126
+ background-size: contain;
127
+ background-position: center;
128
+ border-radius: 50%;
129
+ }
130
+
131
+ .Flow-Component.ComponentTag-left_arrow {
132
+ left: -72px;
133
+ /* background-image: url(...) for arrow icon */
134
+ }
135
+
136
+ .Flow-Component.ComponentTag-right_arrow {
137
+ right: -72px;
138
+ /* background-image: url(...) for arrow icon */
139
+ }
140
+ ```
141
+
142
+ Use breakpoints to adjust arrow position on mobile (e.g. overlay on the carousel).
143
+
144
+ ## 4. Swiper Options
145
+
146
+ Swiper options are passed to `new Swiper(container, options)`. Common options:
147
+
148
+ - `slidesPerView` — Number of visible slides
149
+ - `spaceBetween` — Gap between slides (px)
150
+ - `loop` — Infinite loop
151
+ - `autoplay` — Auto-advance (object with `delay`, etc.)
152
+ - `breakpoints` — Responsive overrides (see [Swiper API](https://swiperjs.com/swiper-api))
153
+
154
+ ## Key Points
155
+
156
+ - **Swiper v11.2.10** is loaded at runtime via CDN — no npm install or build step.
157
+ - Turn any `Container` with child elements into a carousel via an action that loads and initializes Swiper.
158
+ - The container is identified by its component `key` (exposed as `data-component-key-value` in the DOM).
159
+ - Do not add `swiper`, `swiper-wrapper`, or `swiper-slide` classes in TSX — the action adds them at runtime.
@@ -20,6 +20,23 @@ The CLI transforms an Embeddable JSON into:
20
20
  - **JS files**: Computed fields and actions
21
21
  - **config.json**: Reduced JSON containing structure/metadata without file content
22
22
 
23
+ ## When to Review (Required Checks)
24
+
25
+ Review is **required** in these situations. Do not skip these checks.
26
+
27
+ | When (trigger) | Where to review | What to verify |
28
+ | --- | --- | --- |
29
+ | **Before running `embeddables save`** | The JSON diff shown by the save command | Confirm the diff matches your intended changes. Do not save without reviewing—unexpected additions, removals, or edits may indicate a compile or config error. |
30
+ | **After modifying `config.json`** | `config.json` and any related React/JS files | Ensure `pages[]`, `computedFields`, `dataOutputs`, `breakpoints`, and other structure match the actual page files, computed-field files, and action files. IDs and keys must be consistent. |
31
+ | **After adding or removing pages** | `config.json` → `pages[]` and `pages/{pageKey}.page.tsx` | Each page in `config.json` must have a matching `.page.tsx` file, and vice versa. |
32
+ | **After adding or removing components** | Page React files and `config.json` | Component IDs and keys must be unique. Nested components need correct `parent_id`. |
33
+ | **After adding or removing actions or computed fields** | `config.json` → `dataOutputs` / `computedFields` and `actions/` / `computed-fields/` | Each entry in config must have a matching JS file; each JS file must have a config entry. |
34
+ | **After changing action triggers** | `config.json` (flow/page level) and component props (React) | `outputs_on*` fields must reference valid action IDs. Page-level triggers use arrays; flow-level can be string or array. |
35
+ | **After modifying styles** | `styles/index.css` and the JSON diff from `embeddables save` | Confirm no unexpected selectors were added or removed; check that breakpoint names match `config.json`. |
36
+ | **When debugging round-trip or compile issues** | `embeddables inspect --bypass-auth --id <embeddableId>` output | Run inspect to compare original vs rebuilt JSON and identify divergences. |
37
+
38
+ **Summary**: A review is required whenever you add, remove, or modify pages, components, styles, actions, computed fields, or config. In those cases, review the affected files and the JSON diff before saving.
39
+
23
40
  ## Embeddable JSON Structure (Flow)
24
41
 
25
42
  The root Embeddable object (called `Flow`) is defined in `src/types-builder.ts`. Key properties:
@@ -413,6 +430,22 @@ NOTE: OptionSelectors are either dropdowns (`dropdown` is `true`, can use other
413
430
  Note: the component itself IS the button. There is no `button` element inside it.
414
431
  Note: button icons and checkboxes are before text in the button, unless you set the flex-direction to be row-reverse or column-reverse.
415
432
 
433
+ **Making a CTA disabled until validation passes**: Use `needs_validation_passed` together with `validation_show_state`. Both props must be set — `needs_validation_passed` alone does not visually disable the button.
434
+
435
+ ```tsx
436
+ <CustomButton
437
+ needs_validation_passed={true}
438
+ validation_show_state="disable"
439
+ {/* ... other props ... */}
440
+ />
441
+ ```
442
+
443
+ - `needs_validation_passed={true}` — prevents the button action from firing until all validation on the page passes.
444
+ - `validation_show_state` — controls how the button appears when validation has not yet passed:
445
+ - `"disable"` — button appears disabled (grayed out, not clickable). **Use this for the standard "disabled until valid" CTA pattern.**
446
+ - `"hide"` — button is hidden entirely until validation passes.
447
+ - `"always_show"` — button always appears enabled regardless of validation state (default behaviour; omit both props if this is what you want).
448
+
416
449
  | ElementType | Element | Condition | Notes |
417
450
  | ------------------- | ---------------- | ------------------------- | ------------------------------------------------------------------------------------------------- |
418
451
  | `ButtonTextWrapper` | `<div>` | Always | |
@@ -488,6 +521,8 @@ NOTE: there is also a reset icon button with the class `reset` - hide this if yo
488
521
 
489
522
  ## Computed Fields Structure
490
523
 
524
+ > **Docs**: [Computed Fields](https://docs.embeddables.com/features/computed-fields) · [Custom Code](https://docs.embeddables.com/guides/custom-code)
525
+
491
526
  Computed fields are stored in `Flow.computedFields` and transform to JS files. The `ComputedField` type is defined in `src/types-builder.ts`.
492
527
 
493
528
  **Key Properties**:
@@ -500,28 +535,42 @@ Computed fields are stored in `Flow.computedFields` and transform to JS files. T
500
535
 
501
536
  **File Mapping**: `computed-fields/{key}.js` (e.g., `computed-fields/total_price.js`)
502
537
 
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:
538
+ **Code Structure**: The `code` property contains JavaScript that must include a function called `result`. The runtime passes three arguments `userData`, `helperFunctions`, and `triggerContext`:
504
539
 
505
540
  ```javascript
506
- function result(context) {
507
- // Access user data via global userData object
541
+ function result(userData, helperFunctions, triggerContext) {
508
542
  const price = userData.price || 0
509
543
  const quantity = userData.quantity || 0
510
544
 
511
- // context.triggerContext is available (see Actions section for shape)
512
- // context.trackCustomEvent(name, props) can track analytics events
545
+ // helperFunctions.trackCustomEvent(name, props) can track analytics events
546
+ // triggerContext contains metadata about what triggered this recalculation
513
547
 
514
548
  return price * quantity
515
549
  }
516
550
  ```
517
551
 
552
+ **Function arguments**:
553
+
554
+ | Argument | Description |
555
+ | --- | --- |
556
+ | `userData` | Object containing the User Data fields listed in **Inputs** (or all User Data when "Include full User Data" is enabled — see below). The computed field's own key is excluded to prevent circular references. |
557
+ | `helperFunctions` | Object with helper utilities. Currently provides `trackCustomEvent(eventName, eventProps)` for analytics (see "Custom Event Tracking" below). |
558
+ | `triggerContext` | Metadata about what triggered the recalculation (see "Trigger Context" below). For computed fields the shape differs slightly from actions — it describes which input changed rather than a UI event. |
559
+
560
+ **Scope of `userData` and when it re-runs**:
561
+
562
+ Computed fields can be configured (in the builder UI) to receive broader User Data, which affects both what's available inside `result(userData, …)` and when it re-runs:
563
+
564
+ - **Include and watch full User Data** — `userData` contains the entire User Data object (excluding the computed field's own key). The field re-runs whenever *any* User Data value changes. Useful when a field needs access to everything or should update frequently.
565
+ - **Include, but not watch full User Data** — `userData` contains the entire User Data object (excluding the computed field's own key), but the field only re-runs when the keys listed in **Inputs** or **Triggers** change. Useful when full access is needed but recalculation should be controlled.
566
+ - **Default (neither option)** — `userData` contains only the keys listed in **Inputs**. The field re-runs when those keys (or any **Triggers**) change.
567
+
518
568
  **Important**:
519
569
 
570
+ - **Always write at least `function result(userData)`, never `function result()`.** Omit trailing parameters you don't use: `result(userData)` if you only need user data, `result(userData, helperFunctions)` if you also need helpers, or `result(userData, helperFunctions, triggerContext)` if you need all three. You cannot skip a parameter to reach a later one (e.g. you must include `helperFunctions` to access `triggerContext`).
520
571
  - Computed fields can reference other computed fields via their keys in `inputs`
521
- - The function has access to a global `userData` object containing all user data
522
572
  - If `async` is true, the function can return a Promise
523
573
  - `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)
525
574
 
526
575
  ## Actions Structure
527
576
 
@@ -536,19 +585,18 @@ Actions are stored in `Flow.dataOutputs` and transform to JS files. The `Action`
536
585
 
537
586
  **File Mapping**: `actions/{name}.js` (e.g., `actions/submit_form.js`)
538
587
 
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:
588
+ **Code Structure**: For `output: "custom"`, the `code` property contains JavaScript that must include a function called `output`. The runtime passes three arguments `userData`, `helperFunctions`, and `triggerContext`:
540
589
 
541
590
  ```javascript
542
- function output(context) {
543
- // Access user data via global userData object
591
+ function output(userData, helperFunctions, triggerContext) {
544
592
  const email = userData.email
545
593
  const name = userData.name
546
594
 
547
- // context.triggerContext contains metadata about what fired this action (see below)
548
- const trigger = context.triggerContext
595
+ // triggerContext contains metadata about what fired this action (see below)
596
+ const { triggerType, pageKey } = triggerContext || {}
549
597
 
550
- // Track a custom analytics event (in-action usage)
551
- context.trackCustomEvent('form_submitted', { email })
598
+ // Track a custom analytics event
599
+ helperFunctions.trackCustomEvent('form_submitted', { email })
552
600
 
553
601
  // Perform action (API call, data transformation, etc.)
554
602
  return {
@@ -558,11 +606,18 @@ function output(context) {
558
606
  }
559
607
  ```
560
608
 
609
+ **Function arguments**:
610
+
611
+ | Argument | Description |
612
+ | --- | --- |
613
+ | `userData` | Object containing all User Data at the time the action fires. |
614
+ | `helperFunctions` | Object with helper utilities. Currently provides `trackCustomEvent(eventName, eventProps)` for analytics (see "Custom Event Tracking" below). |
615
+ | `triggerContext` | Metadata about what triggered the action — see "Trigger Context" below. |
616
+
561
617
  **Important**:
562
618
 
619
+ - **Always write at least `function output(userData)`, never `function output()`.** Omit trailing parameters you don't use: `output(userData)` if you only need user data, `output(userData, helperFunctions)` if you also need helpers, or `output(userData, helperFunctions, triggerContext)` if you need all three. You cannot skip a parameter to reach a later one (e.g. you must include `helperFunctions` to access `triggerContext`).
563
620
  - Actions are triggered by `outputs_on*` fields (see "Action Trigger Wiring" below) or programmatically
564
- - The function has access to a global `userData` object
565
- - The `context` parameter provides `triggerContext` and `trackCustomEvent` (see below)
566
621
  - For non-custom actions (airtable, hubspot, etc.), the action configuration is stored in config.json, not in the JS file
567
622
 
568
623
  ### Action Trigger Wiring (`outputs_on*` Fields)
@@ -585,12 +640,14 @@ These are top-level properties on the `Flow` object (stored in `config.json`):
585
640
 
586
641
  These are properties on `FlowPage` objects (stored in the page entry in `config.json`):
587
642
 
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 |
643
+ | Field | Type | Fires when |
644
+ | ------------------------- | ---------- | ---------------------------------------------------------- |
645
+ | `outputs_oncomplete` | `string[]` | The user completes this page (i.e. moves to the next page) |
646
+ | `outputs_onload` | `string[]` | The page is loaded / navigated to |
647
+ | `outputs_onopen_infobox` | `string[]` | An info box on this page is opened |
648
+ | `outputs_onclose_infobox` | `string[]` | An info box on this page is closed |
649
+
650
+ **Note**: Page-level trigger fields must be arrays of action IDs. The runtime does not accept a single string for these fields.
594
651
 
595
652
  #### Component-Level Triggers
596
653
 
@@ -677,7 +734,9 @@ window.Savvy.triggerAction(embeddableId, actionId)
677
734
 
678
735
  ### Trigger Context (`triggerContext`)
679
736
 
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:
737
+ `triggerContext` is the **third argument** passed to both `output(userData, helperFunctions, triggerContext)` and `result(userData, helperFunctions, triggerContext)`. It contains metadata about what triggered the action or computed field recalculation.
738
+
739
+ **Action `triggerContext` fields**:
681
740
 
682
741
  | Field | Type | Description |
683
742
  | -------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
@@ -690,8 +749,8 @@ When the engine fires an action, it passes a `context` object to the `output(con
690
749
  Example usage inside an action:
691
750
 
692
751
  ```javascript
693
- function output(context) {
694
- const { triggerType, componentKey, pageKey } = context.triggerContext || {}
752
+ function output(userData, helperFunctions, triggerContext) {
753
+ const { triggerType, componentKey, pageKey } = triggerContext || {}
695
754
 
696
755
  if (triggerType === 'oncomplete' && pageKey === 'checkout_page') {
697
756
  // Only run full submission on checkout page completion
@@ -703,7 +762,7 @@ function output(context) {
703
762
  }
704
763
  ```
705
764
 
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.
765
+ **Computed field `triggerContext`**: Computed fields also receive `triggerContext` as the third argument. The shape differs from actions — it describes which input key changed rather than a UI event. This is useful for computed fields that need to behave differently based on what triggered their recalculation.
707
766
 
708
767
  ### Custom Event Tracking
709
768
 
@@ -711,14 +770,14 @@ Embeddables support tracking custom analytics events. There are two contexts whe
711
770
 
712
771
  #### 1. Inside Action or Computed Field Code (Runtime Context)
713
772
 
714
- Use `context.trackCustomEvent(eventName, eventProps)` inside `output()` or `result()` functions:
773
+ Use `helperFunctions.trackCustomEvent(eventName, eventProps)` inside `output()` or `result()` functions:
715
774
 
716
775
  ```javascript
717
- function output(context) {
776
+ function output(userData, helperFunctions, triggerContext) {
718
777
  const plan = userData.selected_plan
719
778
 
720
779
  // Track a custom event from within an action
721
- context.trackCustomEvent('plan_selected', {
780
+ helperFunctions.trackCustomEvent('plan_selected', {
722
781
  plan: plan,
723
782
  price: userData.plan_price,
724
783
  })
@@ -744,10 +803,10 @@ window.Savvy.trackCustomEvent('flow_abc123', 'external_checkout_complete', {
744
803
 
745
804
  #### When to Use Which
746
805
 
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) |
806
+ | Context | API | Use when |
807
+ | ------------------------------ | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
808
+ | Inside `output()` / `result()` | `helperFunctions.trackCustomEvent(name, props)` | Tracking events as part of action/computed field logic (e.g. after a form submission, when a computed value changes) |
809
+ | 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
810
 
752
811
  ## Conditions Structure
753
812
 
@@ -885,14 +944,12 @@ The `_docs` property (string only) documents non-obvious aspects of the embeddab
885
944
  ### JS Files (Computed Fields)
886
945
 
887
946
  - Location: `computed-fields/{key}.js`
888
- - Export: Function named `result` that returns computed value
889
- - Access: Global `userData` object available; `context` parameter provides `triggerContext` and `trackCustomEvent()`
947
+ - Export: Function named `result` that returns computed value — signature is `result(userData)`, `result(userData, helperFunctions)`, or `result(userData, helperFunctions, triggerContext)` depending on which arguments are needed (include only the ones you use; trailing unused parameters can be omitted)
890
948
 
891
949
  ### JS Files (Actions)
892
950
 
893
951
  - Location: `actions/{name}.js`
894
- - Export: Function named `output` for custom actions
895
- - Access: Global `userData` object available; `context` parameter provides `triggerContext` and `trackCustomEvent()`
952
+ - Export: Function named `output` for custom actions — signature is `output(userData)`, `output(userData, helperFunctions)`, or `output(userData, helperFunctions, triggerContext)` depending on which arguments are needed (include only the ones you use; trailing unused parameters can be omitted)
896
953
 
897
954
  ## Key Principles for Modifications
898
955
 
@@ -931,8 +988,20 @@ The `_docs` property (string only) documents non-obvious aspects of the embeddab
931
988
 
932
989
  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.
933
990
 
991
+ 14. **Review before save**: When any change is made (pages, components, styles, config, actions, computed fields), follow "When to Review (Required Checks)" above. Always review the JSON diff before running `embeddables save`.
992
+
934
993
  ## Common Patterns
935
994
 
995
+ ### Carousel / Swiper
996
+
997
+ When you need to build a carousel or slider, **always consult the carousel implementation guide first** (injected by `embeddables init` into your AI rules directory).
998
+
999
+ Key points:
1000
+
1001
+ - Swiper v11.2.10 is loaded at runtime via CDN — no build tooling required.
1002
+ - Turn any `Container` with child elements into a carousel via an action file that loads and initializes Swiper.
1003
+ - The guide covers the full pattern: page TSX structure, the initialization action, CSS, and optional prev/next navigation buttons.
1004
+
936
1005
  ### Adding a New Page
937
1006
 
938
1007
  1. Create `pages/{pageKey}.page.tsx` with `'use client'` at the top and a default export.
@@ -959,14 +1028,14 @@ The `_docs` property (string only) documents non-obvious aspects of the embeddab
959
1028
  ### Adding a Computed Field
960
1029
 
961
1030
  1. Create JS file: `computed-fields/{key}.js`
962
- 2. Implement `result()` function
1031
+ 2. Implement `result(userData, helperFunctions, triggerContext)` function — access input values via `userData`, use `helperFunctions.trackCustomEvent()` for analytics if needed
963
1032
  3. Add metadata to config.json: `computedFields` array
964
1033
  4. Specify `inputs` array for dependencies
965
1034
 
966
1035
  ### Adding an Action
967
1036
 
968
1037
  1. Create JS file: `actions/{name}.js`
969
- 2. Implement `output(context)` function (for custom actions) — use `context.triggerContext` for trigger metadata and `context.trackCustomEvent()` for analytics
1038
+ 2. Implement `output(userData, helperFunctions, triggerContext)` function (for custom actions) — access data via `userData`, use `triggerContext` for trigger metadata and `helperFunctions.trackCustomEvent()` for analytics
970
1039
  3. Add metadata to config.json: `dataOutputs` array
971
1040
  4. Configure action type and mappings in config.json
972
1041
  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)
@@ -1050,9 +1119,14 @@ Add a `languages` prop to any component in your `.page.tsx` or `.location.tsx` f
1050
1119
  />
1051
1120
  ```
1052
1121
 
1053
- ### Adding Translations to OptionSelector Buttons
1122
+ ### Adding Translations to OptionSelector
1123
+
1124
+ For OptionSelector components, translations can be added both:
1125
+
1126
+ - on the component itself (e.g., `label`, `placeholder`), and
1127
+ - on each **button** object (e.g., `text`, `description`).
1054
1128
 
1055
- For OptionSelector components, translations are added on each **button** object (not on the component itself). Add a `languages` property inside each button definition. The `OptionSelectorButtonWithLanguages` type (not plain `OptionSelectorButton`) is required here since it includes the `languages` property:
1129
+ Add a `languages` property at the appropriate level for the attributes being translated. The `OptionSelectorButtonWithLanguages` type (not plain `OptionSelectorButton`) is required for button constants since it includes the `languages` property:
1056
1130
 
1057
1131
  ```tsx
1058
1132
  import { OptionSelector } from '@embeddables/cli/components'
@@ -1095,7 +1169,7 @@ Any text-based attribute on a component can be translated. Common translatable a
1095
1169
  | `RichTextMarkdown` | `text` |
1096
1170
  | `CustomButton` | `text`, `description` |
1097
1171
  | `InputBox` | `label`, `placeholder`, `empty_invalid_message` |
1098
- | `OptionSelector` | `label` (on the component); `text`, `description` (on each button) |
1172
+ | `OptionSelector` | `label`, `placeholder` (on the component); `text`, `description` (on each button) |
1099
1173
  | `MediaImage` | `caption`, `alt_text` |
1100
1174
  | `FileUpload` | `label` |
1101
1175
 
@@ -0,0 +1,15 @@
1
+ # Embeddables CLI
2
+
3
+ **Before editing embeddables, read [embeddables/AI-README.md](embeddables/AI-README.md) for the full guide.**
4
+
5
+ [embeddables/AI-README.md](embeddables/AI-README.md) is the single source of truth for working with Embeddables in this project. It covers:
6
+
7
+ - **Overview** — how the CLI transforms Embeddable JSON into local React/CSS/JS files
8
+ - **Embeddable Structure** — Flow, pages, components, styles, computed fields, actions
9
+ - **Component Types** — all types, allowed props, and the registry
10
+ - **File Layout** — pages/, global-components/, styles/, computed-fields/, actions/, config.json
11
+ - **CLI Commands** — pull, dev, build, save, inspect, init, branch
12
+ - **Compiler Pipeline** — forward (React → JSON) and reverse (JSON → React)
13
+ - **AI & Agent Rules** — command safety, types as source of truth, no publish commands
14
+
15
+ Re-inject this guide at any time by running `embeddables init` in your project root.
package/README.md CHANGED
@@ -88,6 +88,48 @@ The project ID is stored in `embeddables.json` and enables interactive embeddabl
88
88
 
89
89
  Authenticate with your Embeddables account.
90
90
 
91
+ **Interactive mode** (default):
92
+
93
+ ```bash
94
+ embeddables login
95
+ ```
96
+
97
+ Prompts for email, sends OTP, then prompts for the code.
98
+
99
+ **Non-interactive mode** (for CI, automated environments, AI agents):
100
+
101
+ ```bash
102
+ # Option 1: Token-based login (single step)
103
+ embeddables login --token <your-token>
104
+
105
+ # Option 2: OTP-based login (two steps)
106
+ embeddables login --email <your-email>
107
+ embeddables provide-otp --code <code-from-email>
108
+ ```
109
+
110
+ Options:
111
+
112
+ - `-t, --token <token>`: Use a token for non-interactive login (e.g. CI, cloud containers)
113
+ - `-e, --email <email>`: Send OTP to email address (non-interactive, step 1 of 2)
114
+
115
+ ### `embeddables provide-otp`
116
+
117
+ Complete non-interactive login by providing the OTP code received via email. This is step 2 of the non-interactive OTP login flow.
118
+
119
+ ```bash
120
+ # First, request OTP
121
+ embeddables login --email your@email.com
122
+
123
+ # Then, provide the code from your email
124
+ embeddables provide-otp --code 123456
125
+ ```
126
+
127
+ Options:
128
+
129
+ - `-c, --code <code>`: OTP code from email (required, 6-digit code)
130
+
131
+ The OTP code expires after 10 minutes. If expired, run `embeddables login --email` again to request a new code.
132
+
91
133
  ### `embeddables logout`
92
134
 
93
135
  Clear stored authentication.
@@ -154,6 +196,58 @@ Options:
154
196
  - `-s, --skip-build`: Skip the build step and use existing compiled JSON from `.generated/embeddable.json`
155
197
  - `--from-version <number>`: Base version number (auto-detected from local config/files if not provided)
156
198
 
199
+ ### `embeddables dangerously-publish`
200
+
201
+ Promote an **existing** saved version on the server to staging or production (same idea as the Builder: you pick a version, you don’t create a new one by default). **Use with caution** — this bypasses the normal review workflow. Requires login; production requires publisher/admin.
202
+
203
+ You must specify exactly one of `--staging` or `--prod`. If you omit both, the command exits with an error.
204
+
205
+ **Default (no `--save`)** — no new version is created:
206
+
207
+ - The version to promote is resolved in this order: `--publish-version`, then `_version` in `embeddables/<id>/config.json`, then the highest version inferred from `.generated/` snapshot filenames.
208
+ - The CLI calls the set-version-status API (main-line only; branch checkouts are rejected). The server still enforces rules (e.g. production only if that version is already on staging).
209
+
210
+ **With `--save`** — build, upload a **new** saved version, then publish **that** new version:
211
+
212
+ - Same build/upload flow as `embeddables save`; then promote to `--staging`, or to production with `--prod --via-staging` (**`--save --prod` without `--via-staging` is not allowed** — new versions must hit staging before production). The base version for the save is taken from `config.json` `_version` or `.generated/` snapshots (same as `embeddables save` without `--from-version`).
213
+ - Use `--skip-build` to reuse `.generated/embeddable.json`.
214
+ - Local snapshots after save are written as `embeddable-main@<version>.json` under `.generated/`.
215
+
216
+ Options:
217
+
218
+ - `-i, --id <id>`: Embeddable ID (will prompt from local embeddables if not provided)
219
+ - `-l, --label <label>`: Label for the new version (**only with `--save`**)
220
+ - `--skip-build`: Skip the build step (**only with `--save`**)
221
+ - `--publish-version <number>`: Promote this version number instead of `config.json` `_version` (no new version unless `--save`)
222
+ - `-p, --project-id <id>`: Project ID (will prompt if not configured)
223
+ - `--save`: Build (unless skipped), create a new saved version on the server, then publish it
224
+ - `--force`: Skip confirmation prompts on version conflicts (**when using `--save`**)
225
+ - `--staging`: Publish to staging
226
+ - `--prod`: Publish to production only (single API step; the version must **already** be on staging server-side)
227
+ - `--via-staging`: With `--prod`, push to staging first, then production (for when the version is not on staging yet)
228
+
229
+ Examples:
230
+
231
+ ```bash
232
+ # Promote the version in config.json (_version) to staging (no new save)
233
+ embeddables dangerously-publish --staging
234
+
235
+ # Promote a specific existing version to staging
236
+ embeddables dangerously-publish --staging --publish-version 42
237
+
238
+ # Promote to production (version must already be staging on the server)
239
+ embeddables dangerously-publish --prod
240
+
241
+ # Production: staging first, then prod (e.g. version still SAVED)
242
+ embeddables dangerously-publish --prod --via-staging
243
+
244
+ # Build, save a new version, then publish that version to staging
245
+ embeddables dangerously-publish --save --staging
246
+
247
+ # Save, then staging and production (new version is not on staging yet)
248
+ embeddables dangerously-publish --save --prod --via-staging
249
+ ```
250
+
157
251
  ### `embeddables dev`
158
252
 
159
253
  Start a dev server with hot reload. Proxies to the Engine; by default uses production (`https://engine.embeddables.com`). Use `--local` to point to a local engine.