diff --git a/docs/web-console-docs/warehouse-native/modes.mdx b/docs/web-console-docs/warehouse-native/modes.mdx
index b29a0246..161e03ee 100644
--- a/docs/web-console-docs/warehouse-native/modes.mdx
+++ b/docs/web-console-docs/warehouse-native/modes.mdx
@@ -34,49 +34,41 @@ In Hybrid mode, you keep the ABsmartly-managed data source as your default and a
- Because that import lands the exposures inside your warehouse, **goals sourced from your warehouse can filter and enrich those exposures** the same way Fully mode does — strip out bots and internal traffic, or join in your own attributes table. **Goals that stay managed by ABsmartly don't get this**, since their exposures never leave ABsmartly's default data source.
```mermaid
-%%{init: {"theme": "base", "themeVariables": {"fontFamily": "Manrope, system-ui, sans-serif", "primaryColor": "#ffffff", "primaryBorderColor": "#CA3721", "primaryTextColor": "#28293d", "lineColor": "#9296a6", "tertiaryColor": "#ffffff", "fontSize": "15px"}}}%%
-flowchart TB
- SDK(["Your app - ABsmartly SDK"])
-
- subgraph ABC["ABsmartly (default)"]
- DEFAULT[("Exposures (no filtering)
and managed goals")]
- end
-
- subgraph EWH["Your warehouse (external)"]
- IMPORT[("Imported
exposures")]
- WGOALS[("Your goals")]
- ATTR[("Your attributes")]
- FILTER("Filter and enrich")
- QUERY("Query interface")
- end
-
- subgraph CLOUD["ABsmartly Cloud"]
- ENGINE("Assignment logic, stats and
metric governance")
- end
-
- SDK --> DEFAULT
- DEFAULT -.->|"exposures import"| IMPORT
- IMPORT --> FILTER
- ATTR --> FILTER
- FILTER --> QUERY
- WGOALS --> QUERY
- DEFAULT --> ENGINE
- ENGINE -.->|"scheduled query"| QUERY
- QUERY -.->|"aggregate results only"| ENGINE
-
- style ABC fill:#FBEDEA,stroke:#CA3721,stroke-width:1.5px,color:#28293d
- style EWH fill:#EEF1FF,stroke:#465BFF,stroke-width:1.5px,color:#28293d
- style CLOUD fill:#FBEDEA,stroke:#CA3721,stroke-width:1.5px,color:#28293d
- style DEFAULT fill:#ffffff,stroke:#CA3721,stroke-width:1.5px,color:#28293d
- style IMPORT fill:#ffffff,stroke:#465BFF,stroke-width:1.5px,color:#28293d
- style WGOALS fill:#ffffff,stroke:#465BFF,stroke-width:1.5px,color:#28293d
- style ATTR fill:#ffffff,stroke:#465BFF,stroke-width:1.5px,color:#28293d
- style FILTER fill:#465BFF,stroke:#465BFF,stroke-width:1.5px,color:#ffffff
- style QUERY fill:#ffffff,stroke:#465BFF,stroke-width:1.5px,color:#28293d
- style ENGINE fill:#ffffff,stroke:#CA3721,stroke-width:1.5px,color:#28293d
- style SDK fill:#ffffff,stroke:#9296a6,stroke-width:1.5px,color:#28293d
+%%{init: {"themeVariables": {"fontFamily": "Manrope, system-ui, sans-serif", "fontSize": "15px", "archEdgeColor": "#9296a6", "archEdgeArrowColor": "#9296a6", "archGroupBorderColor": "#CA3721"}, "architecture": {"iconSize": 100, "padding": 60, "nodeSeparation": 160, "idealEdgeLengthMultiplier": 2.2, "numIter": 6000, "seed": 2}}}%%
+architecture-beta
+ service sdk(mdi:cellphone-link)[Your app ABsmartly SDK]
+
+ group abc(mdi:server)[ABsmartly default]
+ service default(mdi:database)[Exposures with no filtering and managed goals] in abc
+
+ group ewh(mdi:server-network)[Your warehouse external]
+ service importsvc(mdi:database-import)[Imported exposures] in ewh
+ service wgoals(mdi:database)[Your goals] in ewh
+ service attr(mdi:database)[Your attributes] in ewh
+ service filtersvc(mdi:filter-outline)[Filter and enrich] in ewh
+ service query(mdi:magnify)[Query interface] in ewh
+
+ group cloud(mdi:cloud-outline)[ABsmartly Cloud]
+ service engine(mdi:cog-outline)[Assignment stats and governance] in cloud
+
+ align row sdk default importsvc wgoals
+ align row attr filtersvc query engine
+ align column importsvc filtersvc
+ align column wgoals query
+
+ sdk:R --> L:default
+ default:R --> L:importsvc
+ importsvc:B --> T:filtersvc
+ attr:R --> L:filtersvc
+ filtersvc:R --> L:query
+ wgoals:B --> T:query
+ default:B --> T:engine
+ query:R --> L:engine
+ engine:L --> R:query
```
+*Across the privacy boundary between your warehouse and ABsmartly Cloud, ABsmartly runs a scheduled query in and only aggregate results come back out — no user-level data crosses that boundary. The default source's exposures flow into your warehouse on a scheduled exposures import.*
+
Hybrid lets you bring metrics into your warehouse at your own pace: you keep ABsmartly's battle-tested assignment and exposure pipeline, and move as many (or as few) of your goals into your warehouse as you like — gaining filtering and enrichment for each one you move.
## Fully Warehouse Native
@@ -89,41 +81,36 @@ Because every exposure already lives in your warehouse, Fully extends Hybrid's w
- **Enrich exposures with your own attributes.** You can join in an external **attributes** table to add segmentation dimensions, then slice experiment results by those attributes.
```mermaid
-%%{init: {"theme": "base", "themeVariables": {"fontFamily": "Manrope, system-ui, sans-serif", "primaryColor": "#ffffff", "primaryBorderColor": "#CA3721", "primaryTextColor": "#28293d", "lineColor": "#9296a6", "tertiaryColor": "#ffffff", "fontSize": "15px"}}}%%
-flowchart TB
- SDK(["Your app - ABsmartly SDK"])
-
- subgraph WH["Your warehouse (default)"]
- EXP[("Exposures")]
- GOALS[("Goals")]
- ATTR[("Your attributes")]
- FILTER("Filter and enrich")
- QUERY("Query interface")
- end
-
- subgraph ABC["ABsmartly Cloud"]
- ENGINE("Assignment logic, stats and
metric governance
no user-level data stored")
- end
-
- SDK --> EXP
- EXP --> FILTER
- ATTR --> FILTER
- FILTER --> QUERY
- GOALS --> QUERY
- ENGINE -.->|"scheduled query"| QUERY
- QUERY -.->|"aggregate results only"| ENGINE
-
- style WH fill:#EEF1FF,stroke:#465BFF,stroke-width:1.5px,color:#28293d
- style ABC fill:#FBEDEA,stroke:#CA3721,stroke-width:1.5px,color:#28293d
- style EXP fill:#ffffff,stroke:#465BFF,stroke-width:1.5px,color:#28293d
- style GOALS fill:#ffffff,stroke:#465BFF,stroke-width:1.5px,color:#28293d
- style ATTR fill:#ffffff,stroke:#465BFF,stroke-width:1.5px,color:#28293d
- style FILTER fill:#465BFF,stroke:#465BFF,stroke-width:1.5px,color:#ffffff
- style QUERY fill:#ffffff,stroke:#465BFF,stroke-width:1.5px,color:#28293d
- style ENGINE fill:#ffffff,stroke:#CA3721,stroke-width:1.5px,color:#28293d
- style SDK fill:#ffffff,stroke:#9296a6,stroke-width:1.5px,color:#28293d
+%%{init: {"themeVariables": {"fontFamily": "Manrope, system-ui, sans-serif", "fontSize": "15px", "archEdgeColor": "#9296a6", "archEdgeArrowColor": "#9296a6", "archGroupBorderColor": "#CA3721"}, "architecture": {"iconSize": 100, "padding": 60, "nodeSeparation": 160, "idealEdgeLengthMultiplier": 2.2, "numIter": 6000, "seed": 2}}}%%
+architecture-beta
+ service sdk(mdi:cellphone-link)[Your app ABsmartly SDK]
+
+ group wh(mdi:cloud-outline)[Your warehouse default]
+ service exp(mdi:database)[Exposures] in wh
+ service goals(mdi:database)[Goals] in wh
+ service attr(mdi:database)[Your attributes] in wh
+ service filtersvc(mdi:filter-outline)[Filter and enrich] in wh
+ service query(mdi:magnify)[Query interface] in wh
+
+ group cloud(mdi:cloud-outline)[ABsmartly Cloud]
+ service engine(mdi:cog-outline)[Assignment stats and governance with no user level data stored] in cloud
+
+ align row sdk exp goals
+ align row attr filtersvc query engine
+ align column exp filtersvc
+ align column goals query
+
+ sdk:R --> L:exp
+ exp:B --> T:filtersvc
+ attr:R --> L:filtersvc
+ filtersvc:R --> L:query
+ goals:B --> T:query
+ query:R --> L:engine
+ engine:L --> R:query
```
+*Across the privacy boundary between your warehouse and ABsmartly Cloud, ABsmartly runs a scheduled query in and only aggregate results come back out — no user-level data crosses that boundary.*
+
:::info Why the exposure source matters
Exposure-side filtering (bots, scrapers, internal traffic) and attribute enrichment both operate on the exposure stream, and only work once that stream lives inside your warehouse. In **Hybrid**, that's true for goals you source from your warehouse — their imported exposures can be filtered and enriched. It's **not** true for goals that stay managed by ABsmartly, since their exposures never leave the default data source. In **Fully**, every exposure lives in your warehouse, so filtering and enrichment apply to every goal.
:::
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 76193c50..508ec90f 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -56,8 +56,11 @@ const config = {
locales: ["en"],
},
- // Client modules for anchor redirects
- clientModules: [require.resolve("./src/anchorRedirects.ts")],
+ // Client modules for anchor redirects and mermaid icon registration
+ clientModules: [
+ require.resolve("./src/anchorRedirects.ts"),
+ require.resolve("./src/scripts/mermaidIcons.js"),
+ ],
presets: [
[
diff --git a/package.json b/package.json
index ca79f20b..2dba94df 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"@docusaurus/plugin-ideal-image": "^3.10.2",
"@docusaurus/preset-classic": "^3.10.2",
"@docusaurus/theme-mermaid": "^3.10.2",
+ "@iconify-json/mdi": "^1.2.3",
"@mdx-js/react": "^3.1.1",
"clsx": "^2.1.0",
"docusaurus-plugin-openapi-docs": "^5.1.3",
diff --git a/src/scripts/mermaidIcons.js b/src/scripts/mermaidIcons.js
new file mode 100644
index 00000000..eae66272
--- /dev/null
+++ b/src/scripts/mermaidIcons.js
@@ -0,0 +1,7 @@
+import { icons } from "@iconify-json/mdi";
+
+if (typeof window !== "undefined") {
+ import("mermaid").then(({ default: mermaid }) => {
+ mermaid.registerIconPacks([{ name: icons.prefix, icons }]);
+ });
+}
diff --git a/yarn.lock b/yarn.lock
index bcdbbdea..8b419119 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2258,7 +2258,14 @@
resolved "https://registry.yarnpkg.com/@hookform/error-message/-/error-message-2.0.1.tgz#6a37419106e13664ad6a29c9dae699ae6cd276b8"
integrity sha512-U410sAr92xgxT1idlu9WWOVjndxLdgPUHEB8Schr27C9eh7/xUnITWpCMF93s+lGiG++D4JnbSnrb5A21AdSNg==
-"@iconify/types@^2.0.0":
+"@iconify-json/mdi@^1.2.3":
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/@iconify-json/mdi/-/mdi-1.2.3.tgz#5e4ee9d8c8aaad04381c8cc6c6edbe3327f031b5"
+ integrity sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg==
+ dependencies:
+ "@iconify/types" "*"
+
+"@iconify/types@*", "@iconify/types@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@iconify/types/-/types-2.0.0.tgz#ab0e9ea681d6c8a1214f30cd741fe3a20cc57f57"
integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==