diff --git a/.diagram-design b/.diagram-design
new file mode 100644
index 00000000..e155c7e6
--- /dev/null
+++ b/.diagram-design
@@ -0,0 +1 @@
+profile: absmartly
diff --git a/docs/web-console-docs/warehouse-native/modes.mdx b/docs/web-console-docs/warehouse-native/modes.mdx
index b29a0246..87e27ed8 100644
--- a/docs/web-console-docs/warehouse-native/modes.mdx
+++ b/docs/web-console-docs/warehouse-native/modes.mdx
@@ -33,49 +33,9 @@ In Hybrid mode, you keep the ABsmartly-managed data source as your default and a
- When a goal is sourced from an external warehouse, ABsmartly makes the default source's exposures available to that warehouse so the two can be evaluated together. You enable this by pointing ABsmartly at an object store through the **[Exposures import](./get-started#step-4-configure-exposures-import-hybrid-only)** section on each external data source.
- 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
-```
+
+
+*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.
@@ -88,41 +48,9 @@ Because every exposure already lives in your warehouse, Fully extends Hybrid's w
- **Filter exposures yourself.** You control the exposures table, so you can strip out bots, scrapers, internal traffic, or any other unwanted exposures before ABsmartly analyzes them — using your own logic in your own warehouse.
- **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
-```
+
+
+*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/static/img/warehouse-native/fully-architecture.svg b/static/img/warehouse-native/fully-architecture.svg
new file mode 100644
index 00000000..cf5ab6fb
--- /dev/null
+++ b/static/img/warehouse-native/fully-architecture.svg
@@ -0,0 +1,128 @@
+
+
diff --git a/static/img/warehouse-native/hybrid-architecture.svg b/static/img/warehouse-native/hybrid-architecture.svg
new file mode 100644
index 00000000..eb94655c
--- /dev/null
+++ b/static/img/warehouse-native/hybrid-architecture.svg
@@ -0,0 +1,155 @@
+
+
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==