diff --git a/.env.example b/.env.example index 9b9e5af9..bcc0c4d6 100644 --- a/.env.example +++ b/.env.example @@ -7,5 +7,10 @@ NEXT_PUBLIC_RB2B_KEY= NEXT_PUBLIC_PYLON_APP_ID= PYLON_IDENTITY_SECRET= -# Search mode: 'fumadocs' (default, uses Fumadocs built-in search) or 'rag' (uses RAG endpoint at mcp.superwall.com) -SEARCH_MODE=fumadocs +# Search mode: 'fumadocs' (default, uses Fumadocs built-in search), 'rag' (uses RAG endpoint at mcp.superwall.com) or 'mixedbread' (vector search) +SEARCH_MODE=mixedbread + +MIXEDBREAD_API_KEY= +MIXEDBREAD_STORE_ID= +MIXEDBREAD_TOPK=8 #defaults to 8 +MIXEDBREAD_RERANK=8 #if empty, rerank is disabled \ No newline at end of file diff --git a/.github/workflows/docs-deploy-main.yml b/.github/workflows/docs-deploy-main.yml index 6c90f3ae..7b8ccacc 100644 --- a/.github/workflows/docs-deploy-main.yml +++ b/.github/workflows/docs-deploy-main.yml @@ -38,6 +38,10 @@ jobs: DOCS_NEXT_PUBLIC_RB2B_KEY: ${{ secrets.DOCS_NEXT_PUBLIC_RB2B_KEY }} NEXT_PUBLIC_PYLON_APP_ID: ${{ secrets.NEXT_PUBLIC_PYLON_APP_ID }} PYLON_IDENTITY_SECRET: ${{ secrets.PYLON_IDENTITY_SECRET }} + MIXEDBREAD_API_KEY: ${{ secrets.MIXEDBREAD_API_KEY }} + MIXEDBREAD_STORE_ID: ${{ secrets.MIXEDBREAD_STORE_ID }} + MIXEDBREAD_TOPK: ${{ secrets.MIXEDBREAD_TOPK }} + MIXEDBREAD_RERANK: ${{ secrets.MIXEDBREAD_RERANK }} steps: - uses: actions/checkout@v4 with: @@ -66,6 +70,10 @@ jobs: SEARCH_MODE=${SEARCH_MODE} NEXT_PUBLIC_PYLON_APP_ID=${NEXT_PUBLIC_PYLON_APP_ID} PYLON_IDENTITY_SECRET=${PYLON_IDENTITY_SECRET} + MIXEDBREAD_API_KEY=${MIXEDBREAD_API_KEY} + MIXEDBREAD_STORE_ID=${MIXEDBREAD_STORE_ID} + MIXEDBREAD_TOPK=${MIXEDBREAD_TOPK} + MIXEDBREAD_RERANK=${MIXEDBREAD_RERANK} EOF - name: Generate Wrangler config for CI @@ -103,7 +111,7 @@ jobs: run: | set -e bun run build - bunx opennextjs-cloudflare build --config wrangler.jsonc -- --skipNextBuild + ./node_modules/.bin/opennextjs-cloudflare build --config wrangler.jsonc -- --skipNextBuild - name: Create GitHub Deployment id: create_deployment @@ -117,7 +125,7 @@ jobs: id: deploy run: | set -e - OUTPUT=$(bunx opennextjs-cloudflare deploy --config wrangler.jsonc) + OUTPUT=$(./node_modules/.bin/opennextjs-cloudflare deploy --config wrangler.jsonc) echo "$OUTPUT" # Extract deployment URL if available DEPLOYMENT_URL=$(echo "$OUTPUT" | grep -oE 'https://[^ ]+' | head -1) diff --git a/.github/workflows/docs-deploy-pr.yml b/.github/workflows/docs-deploy-pr.yml index f119b3e8..c95acb22 100644 --- a/.github/workflows/docs-deploy-pr.yml +++ b/.github/workflows/docs-deploy-pr.yml @@ -337,6 +337,10 @@ jobs: DOCS_NEXT_PUBLIC_RB2B_KEY: ${{ secrets.DOCS_NEXT_PUBLIC_RB2B_KEY }} NEXT_PUBLIC_PYLON_APP_ID: ${{ secrets.NEXT_PUBLIC_PYLON_APP_ID }} PYLON_IDENTITY_SECRET: ${{ secrets.PYLON_IDENTITY_SECRET }} + MIXEDBREAD_API_KEY: ${{ secrets.MIXEDBREAD_API_KEY }} + MIXEDBREAD_STORE_ID: ${{ secrets.MIXEDBREAD_STORE_ID }} + MIXEDBREAD_TOPK: ${{ secrets.MIXEDBREAD_TOPK }} + MIXEDBREAD_RERANK: ${{ secrets.MIXEDBREAD_RERANK }} steps: - uses: actions/checkout@v4 @@ -384,6 +388,10 @@ jobs: SEARCH_MODE=${SEARCH_MODE} NEXT_PUBLIC_PYLON_APP_ID=${NEXT_PUBLIC_PYLON_APP_ID} PYLON_IDENTITY_SECRET=${PYLON_IDENTITY_SECRET} + MIXEDBREAD_API_KEY=${MIXEDBREAD_API_KEY} + MIXEDBREAD_STORE_ID=${MIXEDBREAD_STORE_ID} + MIXEDBREAD_TOPK=${MIXEDBREAD_TOPK} + MIXEDBREAD_RERANK=${MIXEDBREAD_RERANK} EOF - name: Generate Wrangler config for CI @@ -424,7 +432,7 @@ jobs: run: | set -e bun run build - bunx opennextjs-cloudflare build --config wrangler.jsonc -- --skipNextBuild + ./node_modules/.bin/opennextjs-cloudflare build --config wrangler.jsonc -- --skipNextBuild OUTPUT=$(npx wrangler@4.34.0 --config wrangler.jsonc versions upload --name ${CF_STAGING_WORKER_NAME} --preview-alias pr-$PR) echo "$OUTPUT" # Extract deployment URL diff --git a/.gitignore b/.gitignore index 171579b4..0c1143d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .next .source +next-env.d.ts # thesse will be automatically filled with the resources from the docs public/content diff --git a/bun.lock b/bun.lock index 74da8be6..8e96cd9c 100644 --- a/bun.lock +++ b/bun.lock @@ -7,16 +7,18 @@ "dependencies": { "@ai-sdk/react": "^2.0.109", "@aws-sdk/client-s3": "^3.948.0", + "@mixedbread/sdk": "^0.46.0", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.11", "ai": "^5.0.108", "class-variance-authority": "^0.7.1", "cli-progress": "^3.12.0", "clsx": "^2.1.0", - "fumadocs-core": "^16.0.5", - "fumadocs-mdx": "^13.0.2", - "fumadocs-ui": "^16.0.5", + "fumadocs-core": "16.0.5", + "fumadocs-mdx": "13.0.2", + "fumadocs-ui": "16.0.5", "lucide-react": "^0.503.0", + "mermaid": "^10.9.1", "nanoid": "^5.1.6", "next": "16.0.7", "react": "^19.1.0", @@ -204,6 +206,8 @@ "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.2", "", {}, "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg=="], + "@braintree/sanitize-url": ["@braintree/sanitize-url@6.0.4", "", {}, "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A=="], + "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="], "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.3.3", "", { "peerDependencies": { "unenv": "2.0.0-rc.17", "workerd": "^1.20250508.0" }, "optionalPeers": ["workerd"] }, "sha512-/M3MEcj3V2WHIRSW1eAQBPRJ6JnGQHc6JKMAPLkDb7pLs3m6X9ES/+K3ceGqxI6TKeF32AWAi7ls0AYzVxCP0A=="], @@ -362,6 +366,8 @@ "@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="], + "@mixedbread/sdk": ["@mixedbread/sdk@0.46.0", "", { "bin": { "mixedbread-sdk": "bin/cli" } }, "sha512-0WhmubKhwUdPWfygyOnW9L+nda/MhHtnrxeXnMp5k4cDYZeMrfnX00iWyQbNS+CwXi9EH6qG541xYF04O6QArA=="], + "@next/env": ["@next/env@16.0.7", "", {}, "sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw=="], "@next/mdx": ["@next/mdx@16.0.7", "", { "dependencies": { "source-map": "^0.7.0" }, "peerDependencies": { "@mdx-js/loader": ">=0.15.0", "@mdx-js/react": ">=0.15.0" }, "optionalPeers": ["@mdx-js/loader", "@mdx-js/react"] }, "sha512-ysX8mH24XuTwXStJLbecHO97I4EdUT9vHQymXLypLb3956cYXfVb/36nukH0C4Q2iA7RZE04yNpHs84Br77nNg=="], @@ -644,6 +650,12 @@ "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], + "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], + + "@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="], + + "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], @@ -674,6 +686,8 @@ "@types/supports-color": ["@types/supports-color@8.1.3", "", {}, "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg=="], + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], @@ -814,18 +828,92 @@ "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], + "cross-spawn": ["cross-spawn@6.0.6", "", { "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } }, "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw=="], "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "cytoscape": ["cytoscape@3.33.1", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="], + + "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], + + "d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="], + + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="], + + "d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="], + + "d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="], + + "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], + + "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], + + "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], + + "d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="], + + "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="], + + "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], + + "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="], + + "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="], + + "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="], + + "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="], + + "d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], + + "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], + + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], + + "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], + + "dagre-d3-es": ["dagre-d3-es@7.0.13", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q=="], + "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], + "dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="], + "debug": ["debug@4.3.6", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg=="], "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], @@ -836,6 +924,8 @@ "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], @@ -848,6 +938,10 @@ "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="], + + "dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="], + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -862,6 +956,8 @@ "electron-to-chromium": ["electron-to-chromium@1.5.234", "", {}, "sha512-RXfEp2x+VRYn8jbKfQlRImzoJU01kyDvVPBmG39eU2iuRVhuS6vQNocB8J0/8GrIMLnPzgz4eW6WiRnJkTuNWg=="], + "elkjs": ["elkjs@0.9.3", "", {}, "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], @@ -974,7 +1070,7 @@ "fumadocs-core": ["fumadocs-core@16.0.5", "", { "dependencies": { "@formatjs/intl-localematcher": "^0.6.2", "@orama/orama": "^3.1.16", "@shikijs/rehype": "^3.14.0", "@shikijs/transformers": "^3.14.0", "github-slugger": "^2.0.0", "hast-util-to-estree": "^3.1.3", "hast-util-to-jsx-runtime": "^2.3.6", "image-size": "^2.0.2", "negotiator": "^1.0.0", "npm-to-yarn": "^3.0.1", "path-to-regexp": "^8.3.0", "remark": "^15.0.1", "remark-gfm": "^4.0.1", "remark-rehype": "^11.1.2", "scroll-into-view-if-needed": "^3.1.0", "shiki": "^3.14.0", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "@mixedbread/sdk": "^0.19.0", "@orama/core": "1.x.x", "@tanstack/react-router": "1.x.x", "@types/react": "*", "algoliasearch": "5.x.x", "lucide-react": "*", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router": "7.x.x", "waku": "^0.26.0" }, "optionalPeers": ["@mixedbread/sdk", "@orama/core", "@tanstack/react-router", "@types/react", "algoliasearch", "lucide-react", "next", "react", "react-dom", "react-router", "waku"] }, "sha512-xeEDzjagdj1a9ryg5iportmvrJ9yDgQee2KBVjp3rLAUdquN4Z31Gh/pmuByZDVcwu4rl2Ax3qt+kbpY2e6djQ=="], - "fumadocs-mdx": ["fumadocs-mdx@13.0.8", "", { "dependencies": { "@mdx-js/mdx": "^3.1.1", "@standard-schema/spec": "^1.0.0", "chokidar": "^4.0.3", "esbuild": "^0.25.12", "estree-util-value-to-estree": "^3.5.0", "js-yaml": "^4.1.0", "lru-cache": "^11.2.2", "mdast-util-to-markdown": "^2.1.2", "picocolors": "^1.1.1", "picomatch": "^4.0.3", "remark-mdx": "^3.1.1", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "zod": "^4.1.12" }, "peerDependencies": { "@fumadocs/mdx-remote": "^1.4.0", "fumadocs-core": "^15.0.0 || ^16.0.0", "next": "^15.3.0 || ^16.0.0", "react": "*", "vite": "6.x.x || 7.x.x" }, "optionalPeers": ["@fumadocs/mdx-remote", "next", "react", "vite"], "bin": { "fumadocs-mdx": "dist/bin.js" } }, "sha512-UbUwH0iGvYbytnxhmfd7tWJKFK8L0mrbTAmrQYnpg6Wi/h8afNMJmbHBOzVcaEWJKeFipZ1CGDAsNA2fztwXNg=="], + "fumadocs-mdx": ["fumadocs-mdx@13.0.2", "", { "dependencies": { "@mdx-js/mdx": "^3.1.1", "@standard-schema/spec": "^1.0.0", "chokidar": "^4.0.3", "esbuild": "^0.25.11", "estree-util-value-to-estree": "^3.4.1", "js-yaml": "^4.1.0", "lru-cache": "^11.2.2", "mdast-util-to-markdown": "^2.1.2", "picocolors": "^1.1.1", "picomatch": "^4.0.3", "remark-mdx": "^3.1.1", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.15", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "zod": "^4.1.12" }, "peerDependencies": { "@fumadocs/mdx-remote": "^1.4.0", "fumadocs-core": "^15.0.0 || ^16.0.0", "next": "^15.3.0 || ^16.0.0", "react": "*", "vite": "6.x.x || 7.x.x" }, "optionalPeers": ["@fumadocs/mdx-remote", "next", "react", "vite"], "bin": { "fumadocs-mdx": "dist/bin.js" } }, "sha512-PLlpdDJze/Yy7phM6v9vyIcxDSGrfkUTkvhTiGHkLBft5bA171fAs+BzhiqwNCfR+4MfXMppfb7ffZV3sg6frA=="], "fumadocs-ui": ["fumadocs-ui@16.0.5", "", { "dependencies": { "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-direction": "^1.1.1", "@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-presence": "^1.1.5", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.13", "class-variance-authority": "^0.7.1", "fumadocs-core": "16.0.5", "lodash.merge": "^4.6.2", "next-themes": "^0.4.6", "postcss-selector-parser": "^7.1.0", "react-medium-image-zoom": "^5.4.0", "scroll-into-view-if-needed": "^3.1.0", "tailwind-merge": "^3.3.1" }, "peerDependencies": { "@types/react": "*", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "tailwindcss": "^4.0.0" }, "optionalPeers": ["@types/react", "next", "tailwindcss"] }, "sha512-WSvDnHMslEFrSDH7qQpL4j+vb5dQUjVfUResVSpQS2GbQhpLIiNbUuSaF3iofDNm+/EWKU8iqYVikZfO1i7ybw=="], @@ -1068,6 +1164,8 @@ "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], @@ -1164,12 +1262,18 @@ "jsonify": ["jsonify@0.0.1", "", {}, "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg=="], + "katex": ["katex@0.16.27", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw=="], + + "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], "klaw-sync": ["klaw-sync@6.0.0", "", { "dependencies": { "graceful-fs": "^4.1.11" } }, "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ=="], "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], + "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], @@ -1194,6 +1298,8 @@ "load-json-file": ["load-json-file@4.0.0", "", { "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", "pify": "^3.0.0", "strip-bom": "^3.0.0" } }, "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw=="], + "lodash-es": ["lodash-es@4.17.22", "", {}, "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], @@ -1214,7 +1320,7 @@ "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], - "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + "mdast-util-from-markdown": ["mdast-util-from-markdown@1.3.1", "", { "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", "decode-named-character-reference": "^1.0.0", "mdast-util-to-string": "^3.1.0", "micromark": "^3.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-decode-string": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "unist-util-stringify-position": "^3.0.0", "uvu": "^0.5.0" } }, "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww=="], "mdast-util-frontmatter": ["mdast-util-frontmatter@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "escape-string-regexp": "^5.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0" } }, "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA=="], @@ -1256,11 +1362,13 @@ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + "mermaid": ["mermaid@10.9.5", "", { "dependencies": { "@braintree/sanitize-url": "^6.0.1", "@types/d3-scale": "^4.0.3", "@types/d3-scale-chromatic": "^3.0.0", "cytoscape": "^3.28.1", "cytoscape-cose-bilkent": "^4.1.0", "d3": "^7.4.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.13", "dayjs": "^1.11.7", "dompurify": "^3.2.4", "elkjs": "^0.9.0", "katex": "^0.16.9", "khroma": "^2.0.0", "lodash-es": "^4.17.21", "mdast-util-from-markdown": "^1.3.0", "non-layered-tidy-tree-layout": "^2.0.2", "stylis": "^4.1.3", "ts-dedent": "^2.2.0", "uuid": "^9.0.0", "web-worker": "^1.2.0" } }, "sha512-eRlKEjzak4z1rcXeCd1OAlyawhrptClQDo8OuI8n6bSVqJ9oMfd5Lrf3Q+TdJHewi/9AIOc3UmEo8Fz+kNzzuQ=="], + "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="], - "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + "micromark": ["micromark@3.2.0", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "micromark-core-commonmark": "^1.0.1", "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-chunked": "^1.0.0", "micromark-util-combine-extensions": "^1.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-encode": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-resolve-all": "^1.0.0", "micromark-util-sanitize-uri": "^1.0.0", "micromark-util-subtokenize": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.1", "uvu": "^0.5.0" } }, "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA=="], - "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + "micromark-core-commonmark": ["micromark-core-commonmark@1.1.0", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-factory-destination": "^1.0.0", "micromark-factory-label": "^1.0.0", "micromark-factory-space": "^1.0.0", "micromark-factory-title": "^1.0.0", "micromark-factory-whitespace": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-chunked": "^1.0.0", "micromark-util-classify-character": "^1.0.0", "micromark-util-html-tag-name": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-resolve-all": "^1.0.0", "micromark-util-subtokenize": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.1", "uvu": "^0.5.0" } }, "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw=="], "micromark-extension-directive": ["micromark-extension-directive@4.0.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "parse-entities": "^4.0.0" } }, "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg=="], @@ -1290,45 +1398,45 @@ "micromark-extension-mdxjs-esm": ["micromark-extension-mdxjs-esm@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A=="], - "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + "micromark-factory-destination": ["micromark-factory-destination@1.1.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg=="], - "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + "micromark-factory-label": ["micromark-factory-label@1.1.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "uvu": "^0.5.0" } }, "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w=="], "micromark-factory-mdx-expression": ["micromark-factory-mdx-expression@2.0.3", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ=="], "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], - "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + "micromark-factory-title": ["micromark-factory-title@1.1.0", "", { "dependencies": { "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ=="], "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], - "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + "micromark-util-chunked": ["micromark-util-chunked@1.1.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ=="], "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], - "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@1.1.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw=="], "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], - "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + "micromark-util-encode": ["micromark-util-encode@1.1.0", "", {}, "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw=="], "micromark-util-events-to-acorn": ["micromark-util-events-to-acorn@2.0.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg=="], - "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@1.2.0", "", {}, "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q=="], - "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@1.1.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q=="], - "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + "micromark-util-resolve-all": ["micromark-util-resolve-all@1.1.0", "", { "dependencies": { "micromark-util-types": "^1.0.0" } }, "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA=="], "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], - "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + "micromark-util-subtokenize": ["micromark-util-subtokenize@1.1.0", "", { "dependencies": { "micromark-util-chunked": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "uvu": "^0.5.0" } }, "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A=="], - "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + "micromark-util-symbol": ["micromark-util-symbol@1.1.0", "", {}, "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag=="], "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], @@ -1376,6 +1484,8 @@ "node-releases": ["node-releases@2.0.23", "", {}, "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg=="], + "non-layered-tidy-tree-layout": ["non-layered-tidy-tree-layout@2.0.2", "", {}, "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="], + "normalize-package-data": ["normalize-package-data@2.5.0", "", { "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA=="], "normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="], @@ -1530,10 +1640,16 @@ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], + + "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], + "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], @@ -1644,6 +1760,8 @@ "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], + "stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="], + "supports-color": ["supports-color@9.4.0", "", {}, "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw=="], "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], @@ -1678,6 +1796,8 @@ "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="], + "ts-tqdm": ["ts-tqdm@0.8.6", "", {}, "sha512-3X3M1PZcHtgQbnwizL+xU8CAgbYbeLHrrDwL9xxcZZrV5J+e7loJm1XrXozHjSkl44J0Zg0SgA8rXbh83kCkcQ=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -1742,6 +1862,8 @@ "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + "uvu": ["uvu@0.5.6", "", { "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", "sade": "^1.7.3" }, "bin": { "uvu": "bin.js" } }, "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA=="], + "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], @@ -1758,6 +1880,8 @@ "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], + "web-worker": ["web-worker@1.5.0", "", {}, "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw=="], + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], @@ -2248,6 +2372,12 @@ "cross-spawn/which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="], + "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], + + "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], + + "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], + "execa/cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "express/cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="], @@ -2272,14 +2402,140 @@ "humanize-ms/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "load-json-file/pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="], + "mdast-util-directive/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "mdast-util-from-markdown/@types/mdast": ["@types/mdast@3.0.15", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ=="], + + "mdast-util-from-markdown/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "mdast-util-from-markdown/mdast-util-to-string": ["mdast-util-to-string@3.2.0", "", { "dependencies": { "@types/mdast": "^3.0.0" } }, "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg=="], + + "mdast-util-from-markdown/micromark-util-decode-string": ["micromark-util-decode-string@1.1.0", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-symbol": "^1.0.0" } }, "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ=="], + + "mdast-util-from-markdown/micromark-util-types": ["micromark-util-types@1.1.0", "", {}, "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="], + + "mdast-util-from-markdown/unist-util-stringify-position": ["unist-util-stringify-position@3.0.3", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg=="], + "mdast-util-frontmatter/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "mdast-util-frontmatter/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-gfm/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-gfm-footnote/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-gfm-table/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-mdx/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + "micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "micromark/micromark-factory-space": ["micromark-factory-space@1.1.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ=="], + + "micromark/micromark-util-character": ["micromark-util-character@1.2.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="], + + "micromark/micromark-util-combine-extensions": ["micromark-util-combine-extensions@1.1.0", "", { "dependencies": { "micromark-util-chunked": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA=="], + + "micromark/micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@1.2.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-encode": "^1.0.0", "micromark-util-symbol": "^1.0.0" } }, "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A=="], + + "micromark/micromark-util-types": ["micromark-util-types@1.1.0", "", {}, "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="], + + "micromark-core-commonmark/micromark-factory-space": ["micromark-factory-space@1.1.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ=="], + + "micromark-core-commonmark/micromark-factory-whitespace": ["micromark-factory-whitespace@1.1.0", "", { "dependencies": { "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ=="], + + "micromark-core-commonmark/micromark-util-character": ["micromark-util-character@1.2.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="], + + "micromark-core-commonmark/micromark-util-classify-character": ["micromark-util-classify-character@1.1.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw=="], + + "micromark-core-commonmark/micromark-util-types": ["micromark-util-types@1.1.0", "", {}, "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="], + + "micromark-extension-directive/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-extension-frontmatter/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-extension-gfm-autolink-literal/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-extension-gfm-footnote/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-gfm-footnote/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-extension-gfm-footnote/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-extension-gfm-strikethrough/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-extension-gfm-strikethrough/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-extension-gfm-strikethrough/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-extension-gfm-table/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-extension-gfm-task-list-item/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-extension-mdx-expression/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-extension-mdx-jsx/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-extension-mdxjs-esm/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-mdxjs-esm/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-factory-destination/micromark-util-character": ["micromark-util-character@1.2.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="], + + "micromark-factory-destination/micromark-util-types": ["micromark-util-types@1.1.0", "", {}, "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="], + + "micromark-factory-label/micromark-util-character": ["micromark-util-character@1.2.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="], + + "micromark-factory-label/micromark-util-types": ["micromark-util-types@1.1.0", "", {}, "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="], + + "micromark-factory-mdx-expression/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-factory-title/micromark-factory-space": ["micromark-factory-space@1.1.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ=="], + + "micromark-factory-title/micromark-util-character": ["micromark-util-character@1.2.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="], + + "micromark-factory-title/micromark-util-types": ["micromark-util-types@1.1.0", "", {}, "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="], + + "micromark-factory-whitespace/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-character/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-classify-character/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-combine-extensions/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-decode-string/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-events-to-acorn/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-resolve-all/micromark-util-types": ["micromark-util-types@1.1.0", "", {}, "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="], + + "micromark-util-sanitize-uri/micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-sanitize-uri/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-subtokenize/micromark-util-types": ["micromark-util-types@1.1.0", "", {}, "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="], + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], @@ -2308,6 +2564,8 @@ "raw-body/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], + "remark-parse/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + "router/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], @@ -2872,6 +3130,10 @@ "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], + + "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], + "execa/cross-spawn/path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "execa/cross-spawn/shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -2940,6 +3202,128 @@ "fumadocs-mdx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "mdast-util-directive/mdast-util-from-markdown/micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "mdast-util-directive/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "mdast-util-directive/mdast-util-from-markdown/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "mdast-util-directive/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "mdast-util-from-markdown/micromark-util-decode-string/micromark-util-character": ["micromark-util-character@1.2.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "mdast-util-gfm-footnote/micromark-util-normalize-identifier/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-extension-gfm-footnote/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-extension-gfm-footnote/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-extension-gfm-footnote/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-extension-gfm-footnote/micromark-core-commonmark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-extension-gfm-footnote/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-extension-gfm-footnote/micromark-core-commonmark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-extension-gfm-footnote/micromark-core-commonmark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-extension-mdxjs-esm/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-extension-mdxjs-esm/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-extension-mdxjs-esm/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-extension-mdxjs-esm/micromark-core-commonmark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-extension-mdxjs-esm/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-extension-mdxjs-esm/micromark-core-commonmark/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-extension-mdxjs-esm/micromark-core-commonmark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-extension-mdxjs-esm/micromark-core-commonmark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-combine-extensions/micromark-util-chunked/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + "micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "miniflare/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], @@ -2992,6 +3376,14 @@ "patch-package/cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "remark-parse/mdast-util-from-markdown/micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "remark-parse/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "remark-parse/mdast-util-from-markdown/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "remark-parse/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + "router/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="], @@ -3280,6 +3672,138 @@ "foreground-child/cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "mdast-util-directive/mdast-util-from-markdown/micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "mdast-util-directive/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "mdast-util-directive/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "mdast-util-directive/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "mdast-util-directive/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "mdast-util-directive/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + "patch-package/chalk/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "patch-package/chalk/supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], @@ -3288,6 +3812,18 @@ "patch-package/cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "remark-parse/mdast-util-from-markdown/micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "remark-parse/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "remark-parse/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "remark-parse/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "remark-parse/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "remark-parse/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + "vfile-reporter/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "wrap-ansi-cjs/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], @@ -3332,8 +3868,128 @@ "@opennextjs/aws/@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.6.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-XE7CtKfyxYiNZ5vz7OvyTf1osrdbJfmUy+rbh+NLQmZumMGvY0mT0Cq1qKSfhrvLtRYzMsOBuRpi10dyI0EBPg=="], + "mdast-util-directive/mdast-util-from-markdown/micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mdast-util-directive/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "mdast-util-directive/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "mdast-util-directive/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "mdast-util-directive/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "mdast-util-frontmatter/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "mdast-util-gfm-footnote/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "mdast-util-gfm-strikethrough/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "mdast-util-gfm-table/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "mdast-util-gfm-task-list-item/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "mdast-util-gfm/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "mdast-util-mdx-expression/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "mdast-util-mdx-jsx/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "mdast-util-mdx/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "mdast-util-mdxjs-esm/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + "patch-package/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "remark-parse/mdast-util-from-markdown/micromark/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "remark-parse/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "remark-parse/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "remark-parse/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "remark-parse/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + "@opennextjs/aws/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.907.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.907.0", "@aws-sdk/middleware-host-header": "3.901.0", "@aws-sdk/middleware-logger": "3.901.0", "@aws-sdk/middleware-recursion-detection": "3.901.0", "@aws-sdk/middleware-user-agent": "3.907.0", "@aws-sdk/region-config-resolver": "3.901.0", "@aws-sdk/types": "3.901.0", "@aws-sdk/util-endpoints": "3.901.0", "@aws-sdk/util-user-agent-browser": "3.907.0", "@aws-sdk/util-user-agent-node": "3.907.0", "@smithy/config-resolver": "^4.3.0", "@smithy/core": "^3.14.0", "@smithy/fetch-http-handler": "^5.3.0", "@smithy/hash-node": "^4.2.0", "@smithy/invalid-dependency": "^4.2.0", "@smithy/middleware-content-length": "^4.2.0", "@smithy/middleware-endpoint": "^4.3.0", "@smithy/middleware-retry": "^4.4.0", "@smithy/middleware-serde": "^4.2.0", "@smithy/middleware-stack": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/node-http-handler": "^4.3.0", "@smithy/protocol-http": "^5.3.0", "@smithy/smithy-client": "^4.7.0", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "@smithy/util-base64": "^4.2.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.0", "@smithy/util-defaults-mode-browser": "^4.2.0", "@smithy/util-defaults-mode-node": "^4.2.0", "@smithy/util-endpoints": "^3.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-retry": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-LycXsdC5sMIc+Az5z1Mo2eYShr2kLo2gUgx7Rja3udG0GdqgdR/NNJ6ArmDCeKk2O5RFS5EgEg89bT55ecl5Uw=="], } } diff --git a/content/docs/android/changelog.mdx b/content/docs/android/changelog.mdx index bb4f8dc0..cb89e096 100644 --- a/content/docs/android/changelog.mdx +++ b/content/docs/android/changelog.mdx @@ -3,6 +3,43 @@ title: "Changelog" description: "Release notes for the Superwall Android SDK" --- +## 2.7.0 + +### Enhancements +- Enables paywall post-purchase action execution instead of dismissing +- Enables triggering custom callback requests from paywall +- Adds a new method to PaywallPresentationHandler called onCustomCallback that allows user to handle custom callback requests +- Adds retrieving of paywall state inside paywall info +- Adds support for new one time purchases with purchase options and offers +- Update Superscript to version 1.0.13, find more in the [Superscript changelog](https://github.com/superwall/superscript/releases/tag/1.0.13) + +### Deprecations +- Deprecated `paywallWebviewLoad_timeout` - this event was causing confusion due to its naming, leading to it being deprecated + +### Fixes +- Fixes late initialization authorization issue for Stripe checkouts +- Improves how Shimmer duration is measured +- Fixes wrong redemption type being displayed due to integration attributes + +## 2.6.8 + +### Enhancements +- Adds microphone permission + +### Fixes +- Fixes error when redeeming external purchases + +## 2.6.7 + +### Enhancements +- Adds permission granting and callbacks to/from paywalls +- Adds `PaywallPreloadStart` and `PaywallPreloadComplete` events + +### Fixes +- Fix handling of deep links when paywall is detached +- Enables permission granting from paywall and callbacks +- Fix crash when handling drawer style paywalls with 100% height + ## 2.6.6 ## Enhancements diff --git a/content/docs/android/guides/advanced/custom-callbacks.mdx b/content/docs/android/guides/advanced/custom-callbacks.mdx new file mode 100644 index 00000000..8648d21d --- /dev/null +++ b/content/docs/android/guides/advanced/custom-callbacks.mdx @@ -0,0 +1,88 @@ +--- +title: "Custom callbacks" +description: "Handle custom callback requests from paywalls to run app-side logic and return results." +--- + + +Available from Android SDK 2.7.0. + + +## Overview + +Custom callbacks let a paywall request arbitrary actions from your app and receive results that determine which branch (`onSuccess` / `onFailure`) executes inside the paywall. Common use cases include validating user input, fetching data, or running business logic that lives outside the paywall. + +## How it works + +1. In the paywall editor, attach a **Custom callback** action to an element (button, form submit, etc.) and give it a name (e.g. `validate_email`). +2. When the user triggers that element the SDK calls your `onCustomCallback` handler with a `CustomCallback` object. +3. Your handler runs whatever logic is needed and returns a `CustomCallbackResult` — either `.success()` or `.failure()` — with optional data. +4. The paywall receives the result and executes the matching `onSuccess` or `onFailure` branch. + +## Setting up the handler + +Register the handler on a `PaywallPresentationHandler` before calling `register`: + +```kotlin +val handler = PaywallPresentationHandler() + +handler.onCustomCallback { callback -> + when (callback.name) { + "validate_email" -> { + val email = callback.variables?.get("email") as? String + if (isValidEmail(email)) { + CustomCallbackResult.success(mapOf("validated" to true)) + } else { + CustomCallbackResult.failure(mapOf("error" to "Invalid email")) + } + } + else -> CustomCallbackResult.failure() + } +} + +Superwall.instance.register(placement = "campaign_trigger", handler = handler) { + // Feature launched +} +``` + +## CustomCallback + +The `CustomCallback` data class is passed to your handler: + +?", + description: "Optional key-value pairs sent from the paywall. Values are type-preserved (String, Number, Boolean).", + }, + }} +/> + +## CustomCallbackResult + +Return one of the following from your handler to signal the outcome: + +```kotlin +// Success — the paywall's onSuccess branch runs +CustomCallbackResult.success(data = mapOf("key" to "value")) + +// Failure — the paywall's onFailure branch runs +CustomCallbackResult.failure(data = mapOf("error" to "Something went wrong")) +``` + +Both `success()` and `failure()` accept an optional `data` map whose values are sent back to the paywall and accessible as `callbacks..data.`. + +## Callback behavior + +When configuring the custom callback action in the paywall editor you can choose between two behaviors: + +- **Blocking** — the paywall waits for your handler to return before continuing the tap-action chain. Use this when the next step depends on the result (e.g. form validation). +- **Non-blocking** — the paywall continues immediately. The `onSuccess` / `onFailure` handlers still fire when the result arrives, but subsequent actions in the chain do not wait. + +## Accessing returned data in the paywall + +Inside the paywall you can reference the returned data using the pattern `callbacks..data.`. For example, if the callback named `validate_email` returns `mapOf("validated" to true)`, the paywall can access `callbacks.validate_email.data.validated`. diff --git a/content/docs/android/guides/advanced/meta.json b/content/docs/android/guides/advanced/meta.json index 3b4222e3..405668ed 100644 --- a/content/docs/android/guides/advanced/meta.json +++ b/content/docs/android/guides/advanced/meta.json @@ -6,6 +6,8 @@ "using-the-presentation-handler", "viewing-purchased-products", "custom-paywall-actions", + "custom-callbacks", + "request-permissions-from-paywalls", "observer-mode", "direct-purchasing", "game-controller-support", diff --git a/content/docs/android/guides/advanced/request-permissions-from-paywalls.mdx b/content/docs/android/guides/advanced/request-permissions-from-paywalls.mdx new file mode 100644 index 00000000..7d8ba815 --- /dev/null +++ b/content/docs/android/guides/advanced/request-permissions-from-paywalls.mdx @@ -0,0 +1,75 @@ +--- +title: "Request permissions from paywalls" +description: "Trigger Android runtime permission dialogs directly from a Superwall paywall action." +--- + +## Overview + +Use the **Request permission** action in the paywall editor when you want to gate features behind Android permissions without bouncing users back to native screens. When the user taps the element, the SDK: + +- Presents the corresponding Android system dialog. +- Emits analytics events (`permission_requested`, `permission_granted`, `permission_denied`). +- Sends the result back to the paywall so you can branch the UI (for example, swap a checklist item for a success state). + +## Add the action in the editor + +1. Open your paywall, select the button (or any element) that should prompt the permission, and set its action to **Request permission**. +2. Choose the permission you want to request. You can wire multiple buttons if you need to prime several permissions in a single flow. +3. Republish the paywall. No extra SDK configuration is required beyond having the proper `AndroidManifest.xml` entries. + +## Declare the permissions in `AndroidManifest.xml` + +| Editor option | `permission_type` sent from the paywall | Required manifest entries | Notes | +|---------------|-----------------------------------------|---------------------------|-------| +| Notifications | `notification` | `` (API 33+) | Devices below Android 13 do not require a runtime permission; the SDK reports `granted` immediately. | +| Location (Foreground) | `location` | `` | Also covers coarse location because FINE implies COARSE. | +| Location (Background) | `background_location` | Foreground entry above **and** `` (API 29+) | The SDK first ensures foreground access, then escalates to background. | +| Photos / Images | `read_images` | `` (API 33+) or `READ_EXTERNAL_STORAGE` for older OS versions | Automatically picks the right permission at runtime. | +| Videos | `read_video` | `` (API 33+) or `READ_EXTERNAL_STORAGE` pre-33 | | +| Contacts | `contacts` | `` | | +| Camera | `camera` | `` | | +| Microphone | `microphone` | `` | Added in 2.6.8. | + +If a manifest entry is missing—or the permission is unsupported on the current OS level—the SDK responds with an `unsupported` status so you can show fallback copy. + +## Analytics and delegate callbacks + +Forward the new events through `SuperwallDelegate.handleSuperwallEvent` to keep your analytics platform and feature flags in sync: + +```kotlin +override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) { + when (val event = eventInfo.event) { + is SuperwallEvent.PermissionRequested -> { + analytics.track("permission_requested", mapOf( + "permission" to event.permissionName, + "paywall_id" to event.paywallIdentifier + )) + } + is SuperwallEvent.PermissionGranted -> { + FeatureFlags.unlock(event.permissionName) + } + is SuperwallEvent.PermissionDenied -> { + Alerts.showPermissionDeclinedSheet(event.permissionName) + } + else -> Unit + } +} +``` + +You can also log the newer [`customerInfoDidChange`](/android/sdk-reference/SuperwallDelegate#customerinfodidchangefrom-customerinfo-to-customerinfo) callback if the permission subsequently unlocks new paywalls that grant entitlements. + +## Status values returned to the paywall + +The paywall receives a `permission_result` web event with: + +- `granted` – The system dialog reported success (or no dialog was needed). +- `denied` – The user denied the request or previously denied it. +- `unsupported` – The platform or manifest doesn't allow the requested permission. + +Use Liquid or custom Javascript inside the paywall to branch on these statuses—for example, replace a “Grant notification access” button with a checkmark when the result equals `granted`. + +## Troubleshooting + +- Seeing `unsupported`? Double-check the manifest entries above and confirm the permission exists on the device's API level (for example, notification permissions only apply on Android 13+). +- Nothing happens when you tap the button? Ensure the action is set to **Request permission** in the released paywall version. +- Want to provide next steps after a denial? Listen for `PermissionDenied` in your delegate to deep-link users into Settings or show educational copy. diff --git a/content/docs/android/guides/handling-deep-links.mdx b/content/docs/android/guides/handling-deep-links.mdx new file mode 100644 index 00000000..9ca38d22 --- /dev/null +++ b/content/docs/android/guides/handling-deep-links.mdx @@ -0,0 +1,6 @@ +--- +title: "Handling Deep Links" +description: "Use handleDeepLink and campaign rules to present paywalls from deep links without hardcoding logic in your app." +--- + +../../../shared/handling-deep-links.mdx diff --git a/content/docs/android/index.mdx b/content/docs/android/index.mdx index 32fee8f8..39c8338b 100644 --- a/content/docs/android/index.mdx +++ b/content/docs/android/index.mdx @@ -34,4 +34,4 @@ If you have feedback on any of our docs, please leave a rating and message at th If you have any issues with the SDK, please [open an issue on GitHub](https://github.com/superwall/superwall-android/issues). - \ No newline at end of file + \ No newline at end of file diff --git a/content/docs/android/meta.json b/content/docs/android/meta.json index e0887909..89b506bb 100644 --- a/content/docs/android/meta.json +++ b/content/docs/android/meta.json @@ -21,6 +21,7 @@ "guides/configuring", "guides/using-superwall-delegate", "guides/3rd-party-analytics", + "guides/handling-deep-links", "---SDK Reference---", "sdk-reference/index", diff --git a/content/docs/android/quickstart/install.mdx b/content/docs/android/quickstart/install.mdx index 5f18b605..a1d7ff23 100644 --- a/content/docs/android/quickstart/install.mdx +++ b/content/docs/android/quickstart/install.mdx @@ -20,16 +20,16 @@ can find the [latest release here](https://github.com/superwall/Superwall-Androi ```gradle build.gradle -implementation "com.superwall.sdk:superwall-android:2.6.5" +implementation "com.superwall.sdk:superwall-android:2.7.0" ``` ```kotlin build.gradle.kts -implementation("com.superwall.sdk:superwall-android:2.6.5") +implementation("com.superwall.sdk:superwall-android:2.7.0") ``` ```toml libs.version.toml [libraries] -superwall-android = { group = "com.superwall.sdk", name = "superwall-android", version = "2.6.5" } +superwall-android = { group = "com.superwall.sdk", name = "superwall-android", version = "2.7.0" } // And in your build.gradle.kts dependencies { diff --git a/content/docs/android/quickstart/tracking-subscription-state.mdx b/content/docs/android/quickstart/tracking-subscription-state.mdx index 14b733c6..6d7507f8 100644 --- a/content/docs/android/quickstart/tracking-subscription-state.mdx +++ b/content/docs/android/quickstart/tracking-subscription-state.mdx @@ -101,6 +101,46 @@ fun ContentScreen() { } ``` +## Reading detailed purchase history (2.6.6+) + +When you need more context than `SubscriptionStatus` provides (for example, to show the full transaction history or mix web redemptions with Google Play receipts), subscribe to `Superwall.instance.customerInfo`. The flow emits a `CustomerInfo` object that merges device, web, and external purchase controller data. + +```kotlin +class BillingDashboardFragment : Fragment() { + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + viewLifecycleOwner.lifecycleScope.launch { + Superwall.instance.customerInfo.collect { info -> + val subscriptions = info.subscriptions.map { it.productId to it.expiresDate } + val nonSubscriptions = info.nonSubscriptions.map { it.productId to it.purchaseDate } + val entitlementIds = info.entitlements.filter { it.isActive }.map { it.id } + + renderCustomerInfo( + activeProducts = info.activeSubscriptionProductIds, + entitlements = entitlementIds, + subscriptions = subscriptions, + oneTimePurchases = nonSubscriptions + ) + } + } + } +} +``` + +Need the latest value immediately (for example, during cold start)? Call `Superwall.instance.getCustomerInfo()` to synchronously read the most recent snapshot before collecting the flow: + +```kotlin +val cached = Superwall.instance.getCustomerInfo() +renderCustomerInfo( + activeProducts = cached.activeSubscriptionProductIds, + entitlements = cached.entitlements.filter { it.isActive }.map { it.id }, + subscriptions = cached.subscriptions.map { it.productId to it.purchaseDate }, + oneTimePurchases = cached.nonSubscriptions.map { it.productId to it.purchaseDate } +) +``` + +After you start collecting, you can also watch for [`SuperwallDelegate.customerInfoDidChange(from:to:)`](/android/sdk-reference/SuperwallDelegate#customerinfodidchangefrom-customerinfo-to-customerinfo) to run analytics or sync other systems whenever purchases change. + ## Checking for specific entitlements If your app has multiple subscription tiers (e.g., Bronze, Silver, Gold), you can check for specific entitlements: diff --git a/content/docs/android/sdk-reference/Superwall.mdx b/content/docs/android/sdk-reference/Superwall.mdx index d47cf0b8..72aaf636 100644 --- a/content/docs/android/sdk-reference/Superwall.mdx +++ b/content/docs/android/sdk-reference/Superwall.mdx @@ -132,6 +132,39 @@ Superwall.instance.setIntegrationAttributes( ) ``` +## Observe customer info (2.6.6+) + +Superwall now exposes purchase history and entitlement snapshots via a `StateFlow`. Each emission contains merged device, web, and external purchase controller data so you can react to subscription changes without wiring up your own polling layer. + +```kotlin +lifecycleScope.launch { + Superwall.instance.customerInfo.collect { info -> + val activeProductIds = info.activeSubscriptionProductIds + val activeEntitlementIds = info.entitlements + .filter { it.isActive } + .map { it.id } + + updateUi( + subscriptions = activeProductIds, + entitlements = activeEntitlementIds + ) + } +} +``` + +Need an immediate snapshot (for example during cold start)? Call `Superwall.instance.getCustomerInfo()` to synchronously read the latest cached value, or wire both together: + +```kotlin +val cachedInfo = Superwall.instance.getCustomerInfo() +render(cachedInfo) + +lifecycleScope.launch { + Superwall.instance.customerInfo.collect { render(it) } +} +``` + +Pair the flow with [`SuperwallDelegate.customerInfoDidChange(from:to:)`](/android/sdk-reference/SuperwallDelegate#customerinfodidchangefrom-customerinfo-to-customerinfo) when you need to mirror changes into analytics. + Java usage: ```java // Access the instance diff --git a/content/docs/android/sdk-reference/SuperwallDelegate.mdx b/content/docs/android/sdk-reference/SuperwallDelegate.mdx index 45cc0eec..8a92719a 100644 --- a/content/docs/android/sdk-reference/SuperwallDelegate.mdx +++ b/content/docs/android/sdk-reference/SuperwallDelegate.mdx @@ -21,6 +21,13 @@ interface SuperwallDelegate { from: SubscriptionStatus, to: SubscriptionStatus ) {} + + fun customerInfoDidChange( + from: CustomerInfo, + to: CustomerInfo + ) {} + + fun userAttributesDidChange(newAttributes: Map) {} fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) {} @@ -55,12 +62,37 @@ public interface SuperwallDelegateJava { SubscriptionStatus from, SubscriptionStatus to ) {} + + default void customerInfoDidChange( + CustomerInfo from, + CustomerInfo to + ) {} + + default void userAttributesDidChange(Map newAttributes) {} default void handleSuperwallEvent(SuperwallEventInfo eventInfo) {} default void handleCustomPaywallAction(String name) {} - // ... other methods + default void willDismissPaywall(PaywallInfo paywallInfo) {} + + default void willPresentPaywall(PaywallInfo paywallInfo) {} + + default void didDismissPaywall(PaywallInfo paywallInfo) {} + + default void didPresentPaywall(PaywallInfo paywallInfo) {} + + default void paywallWillOpenURL(String url) {} + + default void paywallWillOpenDeepLink(String url) {} + + default void handleLog( + LogLevel level, + LogScope scope, + String message, + Map info, + Throwable error + ) {} } ``` @@ -84,6 +116,16 @@ All methods are optional to implement. Key methods include: description: "Called when user taps elements with `data-pw-custom` tags.", required: true, }, + userAttributesDidChange: { + type: "newAttributes: Map", + description: "Called whenever paywall actions mutate user attributes (for example, forms or surveys).", + required: true, + }, + customerInfoDidChange: { + type: "from: CustomerInfo, to: CustomerInfo", + description: "Raised when Superwall merges device, web, and external purchase data into a new CustomerInfo snapshot.", + required: true, + }, willPresentPaywall: { type: "paywallInfo: PaywallInfo", description: "Called before paywall presentation.", @@ -135,6 +177,32 @@ override fun subscriptionStatusDidChange( } ``` +Mirror merged purchase history: +```kotlin +override fun customerInfoDidChange( + from: CustomerInfo, + to: CustomerInfo +) { + if (from.activeSubscriptionProductIds != to.activeSubscriptionProductIds) { + Analytics.track("customer_info_updated", mapOf( + "old_products" to from.activeSubscriptionProductIds.joinToString(), + "new_products" to to.activeSubscriptionProductIds.joinToString() + )) + } + + refreshEntitlementBadge(to.entitlements.filter { it.isActive }.map { it.id }) +} +``` + +Capture remote attribute changes: +```kotlin +override fun userAttributesDidChange(newAttributes: Map) { + // Paywall forms or surveys can set attributes directly. + // Forward them to your analytics platform or local cache. + analytics.identify(newAttributes) +} +``` + Forward analytics events: ```kotlin override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) { @@ -200,5 +268,19 @@ public class MainActivity extends AppCompatActivity implements SuperwallDelegate System.out.println("Subscription changed from " + from + " to " + to); updateUI(to); } + + @Override + public void customerInfoDidChange( + CustomerInfo from, + CustomerInfo to + ) { + Logger.i("Superwall", "Customer info updated: " + to.getActiveSubscriptionProductIds()); + syncUserPurchases(to); + } + + @Override + public void userAttributesDidChange(Map newAttributes) { + analytics.identify(newAttributes); + } } ``` diff --git a/content/docs/android/sdk-reference/SuperwallEvent.mdx b/content/docs/android/sdk-reference/SuperwallEvent.mdx index 39967296..6b1d6ca7 100644 --- a/content/docs/android/sdk-reference/SuperwallEvent.mdx +++ b/content/docs/android/sdk-reference/SuperwallEvent.mdx @@ -106,4 +106,45 @@ This is a sealed class that represents different event types. Events are receive ## Usage -These events are received via [`SuperwallDelegate.handleSuperwallEvent(eventInfo)`](/android/sdk-reference/SuperwallDelegate) for forwarding to your analytics platform. \ No newline at end of file +These events are received via [`SuperwallDelegate.handleSuperwallEvent(eventInfo)`](/android/sdk-reference/SuperwallDelegate) for forwarding to your analytics platform. + +## Deprecations in 2.7.0 + +- `PaywallWebviewLoadTimeout` is deprecated. This event was causing confusion due to its naming and has been removed from internal tracking. It will no longer fire. + +## New events in 2.6.6+ + +- `CustomerInfoDidChange` fires whenever the SDK merges device, web, and external purchase controller data into a new [`CustomerInfo`](/android/quickstart/tracking-subscription-state#reading-detailed-purchase-history-2-6-6) snapshot. The event includes the previous and next objects so you can diff entitlements or transactions. +- `PermissionRequested`, `PermissionGranted`, and `PermissionDenied` correspond to the new **Request permission** action in the paywall editor. Each event carries the `permissionName` and `paywallIdentifier`. +- `PaywallPreloadStart` and `PaywallPreloadComplete` track when preloading kicks off and how many paywalls finished warming the cache. + +Example handler: + +```kotlin +override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) { + when (val event = eventInfo.event) { + is SuperwallEvent.CustomerInfoDidChange -> { + analytics.track("customer_info_updated", mapOf( + "old_products" to event.from.activeSubscriptionProductIds.joinToString(), + "new_products" to event.to.activeSubscriptionProductIds.joinToString() + )) + } + is SuperwallEvent.PermissionRequested -> { + analytics.track("permission_requested", mapOf( + "permission" to event.permissionName, + "paywall_id" to event.paywallIdentifier + )) + } + is SuperwallEvent.PermissionGranted -> { + featureFlags.unlock(event.permissionName) + } + is SuperwallEvent.PermissionDenied -> { + showPermissionHelpSheet(event.permissionName) + } + is SuperwallEvent.PaywallPreloadComplete -> { + Logger.i("Superwall", "Preloaded ${event.paywallCount} paywalls") + } + else -> Unit + } +} +``` \ No newline at end of file diff --git a/content/docs/android/sdk-reference/index.mdx b/content/docs/android/sdk-reference/index.mdx index 20043953..d519108a 100644 --- a/content/docs/android/sdk-reference/index.mdx +++ b/content/docs/android/sdk-reference/index.mdx @@ -15,4 +15,4 @@ If you have feedback on any of our docs, please leave a rating and message at th If you have any issues with the SDK, please [open an issue on GitHub](https://github.com/superwall/superwall-android/issues). - \ No newline at end of file + \ No newline at end of file diff --git a/content/docs/changelog/index.mdx b/content/docs/changelog/index.mdx new file mode 100644 index 00000000..0d79c0c1 --- /dev/null +++ b/content/docs/changelog/index.mdx @@ -0,0 +1,7 @@ +--- +title: "Changelog" +description: "Track recent updates and additions to the Superwall documentation." +icon: "History" +--- + + diff --git a/content/docs/changelog/meta.json b/content/docs/changelog/meta.json new file mode 100644 index 00000000..597ded97 --- /dev/null +++ b/content/docs/changelog/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Changelog", + "icon": "History", + "root": true, + "pages": [ + "index" + ] +} diff --git a/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-layout.mdx b/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-layout.mdx index 4623cda2..aad4cadd 100644 --- a/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-layout.mdx +++ b/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-layout.mdx @@ -75,6 +75,8 @@ From left to right, here's what each icon does: - **Square on Square:** Copies the component. - **Plus sign:** Adds a new component. +You can also select a component directly in the **live preview canvas** and use `⌘+C` (Mac) or `Ctrl+C` (Windows) to copy it, then `⌘+V` or `Ctrl+V` to paste it. + To **rename** a component, **double click** on its current name to edit it. ### Context menu diff --git a/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-notifications.mdx b/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-notifications.mdx index 9f98a949..21d44782 100644 --- a/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-notifications.mdx +++ b/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-notifications.mdx @@ -2,11 +2,11 @@ title: "Notifications" --- -To configure a notification which displays before a free trials ends, click the **Notifications** button from the **sidebar** +To configure a notification which displays before a free trial ends, click the **Notifications** button from the **sidebar**: ![](/images/pe-editor-notification-sidebar.png) -You can add a local notification that fires after a number of days when a free trial has been purchased. After the user starts a free trial, it will ask them to enable notifications if they haven't already done so. +You can add a local notification that fires before a free trial ends. After the user starts a free trial, the app will ask them to enable notifications if they haven't already done so. In sandbox mode, the free trial reminder will fire after x minutes, instead of x days. @@ -19,10 +19,30 @@ To turn on a trial reminder notification, click **+ Add Notification**. From the 1. **Title**: Shows at the top of the notification. 2. **Subtitle**: Displays directly below the title in a smaller font. Not required. 3. **Body**: Shows in the primary body of the notification. -4. **Delay**: Any delay you'd like to apply after the free trial begins. +4. **Delay**: How many days before the trial ends the notification should fire. Here's where those values show up on a notification: ![](/images/pe-editor-notification-mapping.png) -Also, keep in mind that these will be scheduled as a local notification as soon as they are configured. +These are scheduled as local notifications as soon as they are configured. + +### Dynamic notification timing + +Requires iOS SDK v4.10.7+ or Android SDK v2.6.6+. + +The SDK automatically calculates the actual trial end date based on the product's introductory offer period from the app store. This means notifications are scheduled relative to when the trial **actually ends** — not when it starts. + +For example, if you set a delay of 3 days: + +| Product trial length | Notification fires on | +| -------------------- | --------------------- | +| 7-day trial | Day 4 (3 days before end) | +| 14-day trial | Day 11 (3 days before end) | +| 1-month trial | ~Day 27 (3 days before end) | + +This ensures users receive trial-ending reminders at the right time — when the reminder is actually relevant to their subscription decision — regardless of the product's trial length. + + +On older SDK versions (pre-4.10.7 on iOS), the notification fires X days **after** the trial starts rather than X days **before** it ends. Upgrade to the latest SDK for accurate timing. + diff --git a/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-products.mdx b/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-products.mdx index 7042fe3a..bd69ac6b 100644 --- a/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-products.mdx +++ b/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-products.mdx @@ -22,7 +22,7 @@ It's important to remember that _you_ retain full control over which of your pro ### Web Checkout Locations -When using web checkout products (Stripe or Paddle products), you can control how the checkout experience is presented to users. This setting determines where the web checkout interface appears: +When using web checkout products (Stripe products), you can control how the checkout experience is presented to users. This setting determines where the web checkout interface appears: ![](/images/web-checkout-location.png) diff --git a/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements.mdx b/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements.mdx index a6ea4535..9af733c0 100644 --- a/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements.mdx +++ b/content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements.mdx @@ -61,6 +61,7 @@ Available tap actions are: - **Purchase:** Begin a purchasing flow of the selected product. - **Set/Update Variable:** Sets or updates an existing variable's value. Options specific to the variable type will also be displayed. For example, a **Number** variable will have an option to choose an **Operation** such as Set, Increment or Decrement. Or, a **Boolean** variable's **Operation** will offer to either **Set** or **Toggle** the boolean value. +- **Set Attribute:** Sets a user attribute directly from the paywall. Enter a **Key** (e.g., `preferredPlan`, `onboardingComplete`) and a **Value** (e.g., `premium`, `true`). You can add multiple attributes per tap using **+ Add Attribute**. This behaves the same as calling `setUserAttributes()` in the SDK. Common use cases include capturing user preferences from paywall surveys, tracking paywall engagement, or segmenting users for A/B tests based on their choices. _Requires iOS SDK v4.10.7+._ - **Select Product:** Puts the chosen product in a selected state. Useful for scenarios such as focusing a product in a paywall drawer, for example, when something is tapped or clicked. - **Close:** Closes the paywall. - **Restore:** Begins a purchase restore operation. Useful for restoring purchases someone may have made prior. @@ -73,6 +74,8 @@ Available tap actions are: 2. **Written Review:** This option deeplinks to the repspective App Store so the user can write a written review. - **Custom Action:** Performs a custom action that you specify. Using custom actions, you can tie application logic to anything tapped or clicked on your paywall. Check out the docs [here](/custom-paywall-events). - **Custom Placement:** Registers the placement you specify in the **Name** field. One common use-case is presenting another paywall from a currently opened one. Check out the example [here](/presenting-paywalls-from-one-another). +- **Delay:** Pauses the action chain for a specified number of **Seconds** before continuing to the next action. This is useful when you have multiple actions on a single tap and want to space them out. For example, setting a variable and then closing the paywall after a short pause. The **Interruptible** option controls what happens if the user taps the same element again while a delay is pending: **Yes** cancels the current delay and restarts the action chain, while **No** ignores the tap until the delay completes. +- **Custom Callback:** Sends a named callback request to the SDK, allowing you to run custom app logic and respond with a result. Enter a **Name** for the callback and choose a **Behavior**: **Blocking** waits for the SDK to respond before continuing the action chain, while **Non-blocking** fires the request and continues immediately. You can pass paywall variables to the SDK using **+ Add Variable**. Use the **On Success** and **On Failure** sections to add follow-up actions that run depending on the SDK's response. For example, you could validate something in your app and then update a variable or close the paywall based on the result. _Requires iOS SDK v4.12.10+ or Android SDK v2.7.0+._ If a component has a tap action associated to it, you'll also see a **triangle** icon next to it within the sidebar: diff --git a/content/docs/dashboard/dashboard-demand-score/demand-score-experiments.mdx b/content/docs/dashboard/dashboard-demand-score/demand-score-experiments.mdx new file mode 100644 index 00000000..4defb79e --- /dev/null +++ b/content/docs/dashboard/dashboard-demand-score/demand-score-experiments.mdx @@ -0,0 +1,83 @@ +--- +title: "Using Demand Score in Campaigns" +description: "Learn how to create audiences based on demand score ranges to run targeted experiments and improve conversion." +--- + +Once you understand your demand score distribution, you can act on it by creating targeted audiences in your campaigns. Superwall provides a quick-start flow and manual options for building demand-score-based experiments. + + +Using Demand Score in campaign audience filters requires the **Scale** plan. Viewing Demand Score insights is available on all plans. + + +### Launching an experiment from Demand Score + +The fastest way to get started is the **Launch Experiment** button at the bottom of the Demand Score page: + +![](/images/demand-score-launch-experiment.jpg) + +When you click it, Superwall handles the setup automatically: + +1. **If you have no campaigns**, Superwall creates a new one called "Demand Score Campaign." +2. **If you have one campaign**, Superwall uses it directly. +3. **If you have multiple campaigns**, a dropdown appears so you can choose which campaign to use. + +Superwall then creates a new audience named **"Demand Score 80-100"** with the filter rule `demandScore >= 80 AND demandScore <= 100`. The audience starts disabled so you can configure your paywall and settings before going live. + +You'll be taken to the campaign page with the new audience ready for configuration. From there, you can [attach paywalls](/paywall-editor-overview), [adjust the score range](/campaigns-audience), and enable the audience when ready. + +### Creating a custom demand score audience + +You can also build demand score audiences manually in any campaign. This gives you full control over the score ranges and combinations: + +1. Navigate to your campaign and click to add a new **audience**. +2. In the audience filter settings, add a filter using the `demandScore` property. +3. Set the operator and value to define your target range. + +For example, to target mid-intent users: +- `demandScore` **is greater than or equal to** `40` +- **AND** `demandScore` **is less than or equal to** `79` + +You can combine demand score filters with any other audience filters (country, platform, app version, etc.) to create precise segments. + +For full details on audience configuration, see [Audiences](/campaigns-audience). + +### Choosing your score ranges + +Every app's demand score distribution is different. Rather than using fixed tiers, use the [Demand Score charts](/demand-score-insights) to find natural breakpoints in your own data. Look for where conversion rate jumps or where user volume is concentrated, then define ranges that match your audience. + +For example, if the Conversion Rate chart shows a clear uplift starting at score 65, you might define: +- **High intent:** 65–100 +- **Mid intent:** 30–64 +- **Low intent:** 1–29 + + +The right ranges depend on your app. Start with what the charts show you, run an experiment, and refine from there. + + +### Experiment strategies + +Here are a few approaches to get started with demand score experiments: + +**Target high-intent users with premium offers** + +Create an audience for your highest-scoring users and show them your strongest paywall with premium pricing, annual plans emphasized, and minimal distractions. These users are already likely to convert, so reduce friction and let your best offer do the work. + +**Use softer approaches for lower intent** + +For lower-scoring users, consider delaying the paywall, offering a free trial with a longer duration, or using introductory pricing. These users may need more time to see value before committing. + +**A/B test by score range** + +Run parallel experiments where different score ranges see different paywalls. For example: +- High-scoring users see a direct purchase paywall with annual pricing. +- Lower-scoring users see a trial-first paywall with monthly pricing and a "cancel anytime" message. + +Compare conversion rates across the ranges to learn what resonates with each segment. + +**Act on placement-specific insights** + +If the [Breakdown by Placement](/demand-score-insights#breakdown-by-placement) chart shows a placement with high demand but low conversion, that's a sign the paywall at that placement isn't matching user intent. Create a demand-score-filtered audience specifically for that placement and test a different offer. + + +Use the [AI Analysis](/demand-score-insights#ai-analysis) suggestions as a starting point. They're tailored to your actual data and often highlight the highest-leverage experiments to run first. + diff --git a/content/docs/dashboard/dashboard-demand-score/demand-score-insights.mdx b/content/docs/dashboard/dashboard-demand-score/demand-score-insights.mdx new file mode 100644 index 00000000..d3baf25a --- /dev/null +++ b/content/docs/dashboard/dashboard-demand-score/demand-score-insights.mdx @@ -0,0 +1,119 @@ +--- +title: "Understanding Demand Score Insights" +description: "Learn how to read and interpret the charts and breakdowns on the Demand Score page, including conversion rates, volume, trial outcomes, and per-placement and per-country analysis." +--- + +The Demand Score page provides several charts and breakdowns to help you understand how demand score correlates with conversion behavior in your app. Each section is collapsible and includes explanatory notes directly below the chart. + +### Conversion rate + +The **Conversion Rate** chart shows the observed conversion rate for each demand score bucket. Higher-demand buckets should generally have higher conversion rates. If they don't, it may point to a paywall or offer issue in that range. + +![](/images/demand-score-conversion-rate.jpg) + +You can click **Copy Data** in most Demand Score components to copy its data. + +Each bar is shaded by **confidence level** based on the sample size in that bucket: + +| Confidence | Meaning | +|------------|---------| +| **High** | Large sample size with a tight confidence interval. Reliable. | +| **Medium** | Moderate sample size. Directionally useful. | +| **Low** | Small sample or wide confidence interval. Interpret with caution. | + + +Look for variation points in the curve. Buckets where conversion drops unexpectedly may indicate that your paywall or pricing isn't resonating with that intent level. + + +### Total paywalled users by conversion + +This stacked bar chart shows the **absolute number of users** per demand score bucket, split into conversions and non-conversions: + +![](/images/demand-score-paywalled-users.jpg) + +Unlike the conversion rate chart (which normalizes by percentage), this view shows where your actual volume sits. A high-volume bucket with a low conversion rate represents more potential revenue impact than a low-volume bucket with the same rate. + +Use this chart to: +- **Identify where your users are concentrated.** If most volume sits in the 80–100 range, your user acquisition is bringing in high-intent users. +- **Prioritize experiments.** A high-volume, low-conversion bucket is the highest-leverage place to test a new offer. + +### Trial conversion and billing issues + +This stacked bar chart breaks down **uncancelled trial outcomes** by demand score bucket: + +![](/images/demand-score-trial-billing.jpg) + +Each bar shows three outcome types: + +| Outcome | Description | +|---------|-------------| +| **Trial conversion** | Users who completed their trial and converted to a paid subscription without billing issues. | +| **Billing issues (recovered)** | Users who hit a payment problem on conversion but later recovered and converted. | +| **Billing issues (unrecovered)** | Users who hit a payment problem and did not convert. | + +This chart helps you understand post-conversion behavior. If certain demand tiers show high billing issues or low trial conversion, consider adjusting trial length, payment timing, or trial-to-paid messaging for those segments. + +### Breakdown by placement + +The **Breakdown by Placement** table shows how demand score and conversion vary across each of your paywall placements: + +![](/images/demand-score-breakdown-placement.jpg) + +Each row displays: + +| Column | Description | +|--------|-------------| +| **Placement** | The placement name (e.g., `GetStarted`, `transaction_abandon`). | +| **Demand Score** | A range visualization showing the Q1 (lower quartile), median, Q3 (upper quartile), and average demand score for users at that placement. | +| **Conversion Rate** | The overall conversion rate at that placement. | +| **Paywalled Users** | Total number of unique users who saw a paywall at that placement. | + +Use the **Min. Paywalled Users** filter to hide low-volume placements and focus on statistically meaningful data. + +**How to read the demand score range:** A tight range (Q1 and Q3 close together) means you're addressing a specific demand tier at that placement. A wide spread suggests the placement sees a mix of intent levels, and you may benefit from sub-experiments targeting different tiers within that placement. + + +High demand score with low conversion at a placement may indicate a paywall or offer issue. Low demand score with solid conversion is a good sign that your offering resonates even with lower-intent users. + + +### Breakdown by country + +The **Breakdown by Country** table uses the same format as the placement breakdown, but groups data by the user's country: + +![](/images/demand-score-breakdown-country.png) + +Use this view to: +- **Compare intent vs. performance across markets.** If two countries have similar demand score ranges but different conversion rates, the gap is likely driven by localization, pricing, or product-market fit rather than user intent. +- **Simplify segmentation.** If countries with similar demand scores also show similar conversion rates, targeting by demand score alone may be more effective than targeting by geography. +- **Find underperforming markets.** Countries with reasonable demand ranges but low conversion are candidates for localized pricing or copy experiments. + +### AI Analysis + +The **AI Analysis** section generates an AI-powered summary of your demand score data for the selected date range. Click **Generate AI analysis** to create a report: + +![](/images/demand-score-ai-analysis.jpg) + +The report includes three sections: + +- **Insights:** Key patterns across your data, including what's working, what stands out, and where the opportunities are. +- **Demographics:** Observations about your user distribution and how volume concentration affects the analysis. +- **Experiments:** Two to three concrete next steps based on placement performance, country data, or demand tier opportunities. + +The analysis is cached locally. If you change the date range or the cached report is more than a day old, click **Regenerate** to get a fresh analysis. + + +The AI analysis is a great starting point for deciding what experiments to run. See [Using Demand Score in Campaigns](/demand-score-experiments) for how to act on these recommendations. + + +### Adjusting bucket size + +Each chart section includes a **Bucket size** slider that controls how demand scores are grouped. The available sizes are 1, 2, 4, 5, 10, 20, and 25: + +- **Smaller buckets** (e.g., 1 or 2) give more granular data but can be noisy with low sample sizes. +- **Larger buckets** (e.g., 10 or 25) smooth out noise and show clearer trends. + +Start with the default bucket size of 10 and adjust based on your user volume. + +### Exporting data + +Each chart section has a **Copy Data** button that copies the chart's data to your clipboard in CSV format. Use this to perform further analysis in a spreadsheet or share data with your team. diff --git a/content/docs/dashboard/dashboard-demand-score/demand-score.mdx b/content/docs/dashboard/dashboard-demand-score/demand-score.mdx new file mode 100644 index 00000000..06bdc92f --- /dev/null +++ b/content/docs/dashboard/dashboard-demand-score/demand-score.mdx @@ -0,0 +1,73 @@ +--- +title: "Using Demand Score" +description: "Demand Score is a 1-100 value assigned to each user by Superwall, indicating their likelihood to convert. Use it to understand your audience and target users with the right offers." +sidebarTitle: "Overview" +--- + +Demand Score helps you understand how likely each user is to convert, so you can target the right people with the right offers. To view it, click **Demand Score** in the **sidebar**: + +![](/images/ds_sidebar.jpeg) + + +Demand Score is currently in **beta**. Anyone can view Demand Score insights, but using it to target audiences in campaigns requires the **Scale** plan. + + +### What is Demand Score? + +Demand Score is a number from 1 to 100 assigned to each user by Superwall on every app open. A higher score means the user is more likely to convert. It's generated using several signals and data points, though no first-party user attributes are used. + +Some signals used include device model, OS version, device age, App Store country, connection type, number of app opens, and paywall views. The model is trained on hundreds of millions of real-world data points across the Superwall network. You can use demand score a few different ways: + +- **View interactive charts** that show how conversion rate, volume, and trial outcomes vary across demand score buckets. +- **Break down performance by placement and country** to see where your offerings resonate and where they don't. +- **Generate an AI-powered analysis** that highlights key patterns in your data and recommends experiments to run. +- **Filter campaign audiences** using `demandScore` to target users based on their likelihood to convert. +- **Launch experiments directly** from the Demand Score page to create high-intent audiences with one click. + + +Because Demand Score relies on device-level signals and not user attributes, it works out of the box. There's nothing to configure in the SDK. + + +### Data thresholds + +If your app is new, doesn't have enough paywall activity yet, or enough data processed, the Demand Score page will display empty results: + +![](/images/ds_layout_empty.jpeg) + +This happens when Superwall hasn't observed enough user sessions to generate reliable demand scores for your app. The model needs a baseline of app opens and paywall views across your user base before it can assign scores. + +No action is needed on your end. As users interact with your app and encounter paywalls, Superwall will automatically begin assigning demand scores and the charts will populate. + + +You can also try expanding the date range to **Last 90 days** or **Last 180 days** to capture a wider window of activity. + + +### Coverage + +At the top of the Demand Score page, the **Coverage** card shows what percentage of your recent paywall viewers have been assigned a demand score: + +![](/images/demand-score-coverage.jpg) + +Coverage is color-coded to help you quickly assess data reliability: + +| Coverage | Indicator | Meaning | +|----------|-----------|---------| +| Above 80% | Green | Great, you can confidently segment and run demand-score-based audiences. | +| 50–80% | Yellow | OK, results are usable but may have gaps. | +| Below 50% | Red | Low, try selecting a longer date range for more reliable results. | + +### Selecting a date range + +Use the date range selector in the top-right corner to adjust the time window for all charts and breakdowns on the page. Options include **Last 7 days**, **Last 30 days**, **Last 90 days**, and **Last 180 days**: + +![](/images/demand-score-overview.jpg) + +All sections on the page (coverage, charts, breakdowns, and AI analysis) update to reflect the selected range. + +### Using Demand Score for targeting + +The `demandScore` attribute (a number from 1 to 100) is available as an audience filter in [campaigns](/campaigns). Rather than using fixed tiers, use the charts on this page to understand where natural breakpoints exist in your own data, then create custom score ranges that match your app's audience. + +For example, if the [Conversion Rate](/demand-score-insights#conversion-rate) chart shows a clear jump at score 70, you might target 70–100 as your "high intent" range. Every app's distribution is different, so let your data guide the ranges you choose. + +For details on setting up demand score filters, see [Using Demand Score in Campaigns](/demand-score-experiments). diff --git a/content/docs/dashboard/dashboard-demand-score/meta.json b/content/docs/dashboard/dashboard-demand-score/meta.json new file mode 100644 index 00000000..88774467 --- /dev/null +++ b/content/docs/dashboard/dashboard-demand-score/meta.json @@ -0,0 +1,9 @@ +{ + "title": "Demand Score", + "pages": [ + "demand-score", + "demand-score-insights", + "demand-score-experiments", + "..." + ] +} diff --git a/content/docs/dashboard/dashboard-settings/overview-settings-team.mdx b/content/docs/dashboard/dashboard-settings/overview-settings-team.mdx index d93a375c..3561d6e3 100644 --- a/content/docs/dashboard/dashboard-settings/overview-settings-team.mdx +++ b/content/docs/dashboard/dashboard-settings/overview-settings-team.mdx @@ -4,20 +4,14 @@ title: "Team" In the **Team** section within **Settings**, you can view and edit your Superwall team: -![](/images/overview-settings-team.png) - Team members have access to all of your apps within Superwall, making collaboration seamless. ### Invite users To invite a user to collaborate on your apps, **click** on the **Invite Users** button at the top right: -![](/images/overview-settings-team-invite.png) - From there, fill out the details (name and email address) and **click** the **Invite** button: -![](/images/overview-settings-invite-modal.png) - Once the user accepts the invite, they'll show up in your Team section. You can add or remove team members at anytime. To remove a team member, **click** the **trashcan** icon under **Actions**. ### Team roles @@ -60,8 +54,16 @@ Only **Owners** or **Admins** can change team member roles. - Cannot create, update, or delete anything - Useful for stakeholders who need visibility but shouldn't make changes +#### User (Legacy) + + +The User role is a legacy role kept for backward compatibility. It has the same permissions as Admin. New team members should be assigned one of the roles listed above instead. + + +- Has the same permissions as Admin +- Exists only for backward compatibility with accounts created before the current role system +- If you see team members with the User role, consider reassigning them to the appropriate role (Owner, Admin, Editor, or Reader) + ### Renaming your team To rename your team, enter in a new value name under the **Team Name** section, and **click** the **Save** button: - -![](/images/overview-settings-team-rename.png) diff --git a/content/docs/dashboard/guides/superwall-mcp.mdx b/content/docs/dashboard/guides/superwall-mcp.mdx new file mode 100644 index 00000000..88311f52 --- /dev/null +++ b/content/docs/dashboard/guides/superwall-mcp.mdx @@ -0,0 +1,74 @@ +--- +title: "Superwall MCP" +description: "Manage Superwall projects, paywalls, campaigns, products, and more from AI tools like Claude Code and Cursor using the Superwall MCP." +--- + +The Superwall MCP lets AI agents manage your Superwall account through the [Model Context Protocol](https://modelcontextprotocol.io). Instead of clicking through the dashboard, you can create projects, paywalls, campaigns, products, and more directly from tools like Claude Code, Cursor, and Codex. + + +This is different from the [Superwall Docs MCP](/ios/guides/vibe-coding#superwall-docs-mcp), which gives AI tools access to Superwall documentation. The Superwall MCP gives AI tools access to your Superwall account to create and manage resources. + + +## Installation + +Add the Superwall MCP to your platform of choice using the URL `https://superwall-mcp.superwall.com/mcp`. You'll be prompted to log in to your Superwall account on first use. + +### Claude.ai + +Go to [Settings → Connectors](https://claude.ai/settings/connectors), click **Add Custom Connector**, and enter `https://superwall-mcp.superwall.com/mcp`: + +![Adding the Superwall MCP as a custom connector in Claude.ai](/images/sw_mcp_claude_connect.jpg) + +### Cursor + +Add the following to your `~/.cursor/mcp.json` file: + +```json +{ + "mcpServers": { + "superwall": { + "url": "https://superwall-mcp.superwall.com/mcp" + } + } +} +``` + +### Claude Code + +```bash +claude mcp add superwall --transport http https://superwall-mcp.superwall.com/mcp +``` + +### Codex + +```bash +codex mcp add superwall --url https://superwall-mcp.superwall.com/mcp +``` + +When you add the MCP via a CLI tool (Claude Code, Codex, or Cursor), a browser window will open to complete authentication. Log in to your Superwall account, and it will authenticate automatically. After that, ask the agent something like "check if you're connected to Superwall" — it will call the `whoami` tool and confirm the connection. + +![An example of using the Superwall MCP to manage your account from an AI agent](/images/sw_mcp_example.jpg) + +## What you can do + +The Superwall MCP can manage nearly everything you'd normally do in the dashboard: + +- **Organizations** — list your organizations or create new ones. +- **Projects** — create, update, archive, and unarchive projects. +- **Applications** — add iOS, Android, or web apps to a project. +- **Entitlements** — create, update, list, and delete entitlements that define what features users unlock. +- **Products** — create and manage products linked to App Store Connect or Google Play, with subscription details and pricing. +- **Templates** — browse available paywall templates to use as a starting point. +- **Paywalls** — create paywalls from templates or from scratch, attach products, set presentation style and feature gating, and archive or unarchive them. +- **Campaigns** — set up simple campaigns that show a paywall to 100% of users, or create advanced campaigns with A/B testing, holdout groups, and automatic optimization. +- **Webhooks** — create and manage webhook endpoints, inspect event deliveries, rotate secrets, and retry failed deliveries. + +## Quick setup + +You can use the Superwall MCP to go from zero to a fully working paywall setup without ever opening the dashboard. Just ask your AI agent to set up Superwall for your app. You can give it your app name, platform, bundle ID, and store product IDs, and it can create the project, application, products, paywall, and campaign for you. + +When the agent creates an application, it returns a `public_api_key`. That's what you pass to `Superwall.configure()` in your app. From there, fire a placement event with `register` and your paywall will show. + +## Related + +- [Superwall Docs MCP](/ios/guides/vibe-coding#superwall-docs-mcp) — give AI tools access to Superwall documentation for vibe coding assistance. diff --git a/content/docs/dashboard/meta.json b/content/docs/dashboard/meta.json index 1cec4400..0b0aba0e 100644 --- a/content/docs/dashboard/meta.json +++ b/content/docs/dashboard/meta.json @@ -11,6 +11,7 @@ "templates", "dashboard-creating-paywalls", "charts", + "dashboard-demand-score", "dashboard-campaigns", "products", "surveys", @@ -22,6 +23,7 @@ "manage-account", "---Guides---", + "guides/superwall-mcp", "guides/using-stripe-bottom-sheet-checkout-in-app", "guides/migrating-from-revenuecat-to-superwall", "guides/pre-launch-checklist", diff --git a/content/docs/dashboard/overview-metrics.mdx b/content/docs/dashboard/overview-metrics.mdx index 6db0b563..65783ea8 100644 --- a/content/docs/dashboard/overview-metrics.mdx +++ b/content/docs/dashboard/overview-metrics.mdx @@ -9,13 +9,17 @@ Once you've logged into Superwall, you'll be taken to the **Overview** page. Her You can toggle between your other apps to view with the Overview page too. Just use the toggle at the top left to choose another app. - - When you log in for the first time or add a new app, the Overview page will reflect a "Quickstart" - wizard to help you get up and running. Complete the interactive checklist to finish your Superwall - integration: -
- ![](/images/overview-new-app-ux.png) -
+### Quickstart + +When you log in for the first time or add a new app, the Overview page displays a Quickstart wizard to help you get up and running. Complete the interactive checklist to finish your Superwall integration: + +![](/images/overview-new-app-ux.png) + +You can also get started with AI using our MCP and prompts: + +![](/images/quickstart-ai-setup.jpeg) + +### Dashboard sections The overview dashboard is broken down between five main sections: diff --git a/content/docs/expo/changelog.mdx b/content/docs/expo/changelog.mdx index 6e6d1dd7..b23b01e0 100644 --- a/content/docs/expo/changelog.mdx +++ b/content/docs/expo/changelog.mdx @@ -3,6 +3,25 @@ title: "Changelog" description: "Release notes for the Superwall Expo SDK" --- +# Changelog + +## 1.0.2 + +### Patch Changes + +- Bump SuperwallKit iOS to 4.12.9 +- Bump Superwall Android SDK to 2.6.8 +- Add `introOfferToken` property to `StoreProduct`. +- Add missing SuperwallEvents: `paywallWebviewProcessTerminated`, `paywallProductsLoadMissingProducts`, `networkDecodingFail`, `customerInfoDidChange`, `integrationAttributes`, `reviewRequested`, `permissionRequested`, `permissionGranted`, `permissionDenied`, `paywallPreloadStart`, `paywallPreloadComplete`. + +## 1.0.1 + +### Patch Changes + +- a165d76: Bump superwall-android to 2.6.7 +- a165d76: Bump SuperwallKit iOS to 4.12.3 +- d96b449: Bridged android back button reroute handler + ## 1.0.0 ### Major Changes @@ -149,7 +168,7 @@ description: "Release notes for the Superwall Expo SDK" - 7920773: fix(android): handle nullable properties in RedemptionResult JSON serialization - Fixed a Kotlin compilation error where nullable properties (`variantId`, `experimentId`, `productIdentifier`) were being assigned directly to a `Map<String, Any>`. Now using the null-safe let operator to conditionally add these properties only when they have values. + Fixed a Kotlin compilation error where nullable properties (`variantId`, `experimentId`, `productIdentifier`) were being assigned directly to a Map<String, Any>. Now using the null-safe let operator to conditionally add these properties only when they have values. ## 0.6.0 diff --git a/content/docs/expo/guides/configuring.mdx b/content/docs/expo/guides/configuring.mdx index 585b3388..5ad45f83 100644 --- a/content/docs/expo/guides/configuring.mdx +++ b/content/docs/expo/guides/configuring.mdx @@ -2,4 +2,52 @@ title: Configuring --- -../../../shared/configuring/using-superwalloptions.mdx \ No newline at end of file +## Expo-specific options + +Expo apps inherit the native Superwall option surface. The following fields were added in release 1.0.0 and later and live directly on the `options` object that you pass to ``. + + + +```tsx +import { SuperwallProvider } from "expo-superwall"; + +export function App() { + return ( + + {/* your app */} + + ); +} +``` + +../../../shared/configuring/using-superwalloptions.mdx diff --git a/content/docs/expo/guides/handling-deep-links.mdx b/content/docs/expo/guides/handling-deep-links.mdx new file mode 100644 index 00000000..9ca38d22 --- /dev/null +++ b/content/docs/expo/guides/handling-deep-links.mdx @@ -0,0 +1,6 @@ +--- +title: "Handling Deep Links" +description: "Use handleDeepLink and campaign rules to present paywalls from deep links without hardcoding logic in your app." +--- + +../../../shared/handling-deep-links.mdx diff --git a/content/docs/expo/guides/web-checkout/post-checkout-redirecting.mdx b/content/docs/expo/guides/web-checkout/post-checkout-redirecting.mdx index 3092d4c1..8e1ad631 100644 --- a/content/docs/expo/guides/web-checkout/post-checkout-redirecting.mdx +++ b/content/docs/expo/guides/web-checkout/post-checkout-redirecting.mdx @@ -176,3 +176,50 @@ export class SWDelegate extends SuperwallDelegate { } } ``` + +### Access detailed product data + +Successful redeems now populate `result.redemptionInfo?.paywallInfo?.product` with the exact product the customer purchased. Use it to show localized pricing on your success screen or to pass metadata to your billing system. + + + +```ts +function trackProduct(result: RedemptionResult) { + if (result.type !== "success") return; + + const product = result.redemptionInfo?.paywallInfo?.product; + if (!product) return; + + analytics.track("web_checkout_completed", { + productId: product.identifier, + price: product.price, + period: product.periodly, + currency: product.currencyCode, + }); +} +``` + +`productIdentifier` remains for backwards compatibility but will be removed in a future Expo release, so migrate to the richer `product` object now. diff --git a/content/docs/expo/index.mdx b/content/docs/expo/index.mdx index 9ca42f6b..4ad70b61 100644 --- a/content/docs/expo/index.mdx +++ b/content/docs/expo/index.mdx @@ -39,4 +39,4 @@ If you have feedback on any of our docs, please leave a rating and message at th If you have any issues please [open an issue on GitHub](https://github.com/superwall/expo-superwall/issues). - \ No newline at end of file + diff --git a/content/docs/expo/meta.json b/content/docs/expo/meta.json index 604ea328..cdf4c6b9 100644 --- a/content/docs/expo/meta.json +++ b/content/docs/expo/meta.json @@ -19,6 +19,7 @@ "---Common Use Cases---", "guides/web-checkout", "guides/3rd-party-analytics", + "guides/handling-deep-links", "---SDK Reference---", "sdk-reference/index", diff --git a/content/docs/expo/sdk-reference/components/SuperwallProvider.mdx b/content/docs/expo/sdk-reference/components/SuperwallProvider.mdx index f814198a..e760c446 100644 --- a/content/docs/expo/sdk-reference/components/SuperwallProvider.mdx +++ b/content/docs/expo/sdk-reference/components/SuperwallProvider.mdx @@ -67,3 +67,27 @@ export default function App() { ); } +``` + +## Consume rerouted Android back buttons + +If the **Reroute back button** toggle is enabled on a paywall (/dashboard/dashboard-creating-paywalls/paywall-editor-settings#reroute-back-button), Superwall can hand control back to your app. Provide `options.paywalls.onBackPressed` to intercept the event and return `true` to consume it. + +```tsx + { + if (paywallInfo.identifier === "survey") { + showExitConfirmation(); + return true; // Prevent Superwall from dismissing automatically + } + return false; // Keep the default dismissal behavior + }, + }, + }} +/> +``` + +This callback only fires on Android and only when rerouting is enabled in the paywall editor. Use it to show confirmation modals, capture analytics, or resume gameplay before closing the paywall. diff --git a/content/docs/expo/sdk-reference/hooks/useSuperwallEvents.mdx b/content/docs/expo/sdk-reference/hooks/useSuperwallEvents.mdx index 598e8820..b91ecaf6 100644 --- a/content/docs/expo/sdk-reference/hooks/useSuperwallEvents.mdx +++ b/content/docs/expo/sdk-reference/hooks/useSuperwallEvents.mdx @@ -39,6 +39,10 @@ The `useSuperwallEvents` hook provides a low-level way to subscribe to *any* nat type: "(status: SubscriptionStatus) => void", description: "Called when the user's subscription status changes.", }, + onUserAttributesChange: { + type: "(newAttributes: Record) => void", + description: "Called when user attributes change outside your app (for example via the `Set Attribute` paywall action).", + }, onSuperwallEvent: { type: "(eventInfo: SuperwallEventInfo) => void", description: "Called for generic Superwall events with payload metadata.", @@ -91,6 +95,10 @@ The `useSuperwallEvents` hook provides a low-level way to subscribe to *any* nat type: "() => void", description: "Called when a purchase restore is initiated.", }, + onBackPressed: { + type: "(paywallInfo: PaywallInfo) => boolean", + description: "Android only. Triggered when a rerouted paywall back button is pressed. Return true to consume the event.", + }, handlerId: { type: "string?", description: "Optional scope for paywall events from a specific registerPlacement handler.", @@ -117,6 +125,10 @@ function EventLogger() { }, onPaywallPresent: (info) => { console.log('Paywall Presented (via useSuperwallEvents):', info.name); + }, + onUserAttributesChange: (newAttributes) => { + console.log('User Attributes Changed:', newAttributes); + // Sync with analytics or update in-memory state } }); diff --git a/content/docs/expo/sdk-reference/index.mdx b/content/docs/expo/sdk-reference/index.mdx index 8398fcc4..21c55bdc 100644 --- a/content/docs/expo/sdk-reference/index.mdx +++ b/content/docs/expo/sdk-reference/index.mdx @@ -15,4 +15,4 @@ If you have feedback on any of our docs, please leave a rating and message at th If you have any issues with the SDK, please [open an issue on GitHub](https://github.com/superwall/expo-superwall/issues). - \ No newline at end of file + diff --git a/content/docs/flutter/changelog.mdx b/content/docs/flutter/changelog.mdx index 2db3016d..5ec9d521 100644 --- a/content/docs/flutter/changelog.mdx +++ b/content/docs/flutter/changelog.mdx @@ -3,10 +3,28 @@ title: "Changelog" description: "Release notes for the Superwall Flutter SDK" --- +## 2.4.8 + +## Enhancements +- Adds `onCustomCallback` method to `PaywallPresentationHandler` that allows invoking custom callbacks and passing data from and to the paywall +- Updates iOS SDK to 4.12.10 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.12.10). +- Updates Android SDK to 2.7.0 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.7.0). + +### Fixes + +- Fixes an issue where `IdentityOptions` passed to `identify()` were not being sent to the native SDK due to a variable shadowing bug. + +## 2.4.7 + +### Enhancements + +- Updates iOS SDK to 4.12.7 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.12.7). +- Updates Android SDK to 2.6.8 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.6.8). ## 2.4.6 ## Enhancements + - Adds `isActive` convenience property on the `SubscriptionStatus` class - Adds support for `SuperwallOptions.paywalls.onBackPressed` callback to react to back button presses on Android @@ -17,7 +35,6 @@ description: "Release notes for the Superwall Flutter SDK" - Updates iOS SDK to 4.10.6 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.10.6). - Updates Android SDK to 2.6.5 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.6.5). - ## 2.4.4 ## Enhancements diff --git a/content/docs/flutter/guides/handling-deep-links.mdx b/content/docs/flutter/guides/handling-deep-links.mdx new file mode 100644 index 00000000..9ca38d22 --- /dev/null +++ b/content/docs/flutter/guides/handling-deep-links.mdx @@ -0,0 +1,6 @@ +--- +title: "Handling Deep Links" +description: "Use handleDeepLink and campaign rules to present paywalls from deep links without hardcoding logic in your app." +--- + +../../../shared/handling-deep-links.mdx diff --git a/content/docs/flutter/index.mdx b/content/docs/flutter/index.mdx index 89b528c3..81c1f69f 100644 --- a/content/docs/flutter/index.mdx +++ b/content/docs/flutter/index.mdx @@ -34,4 +34,4 @@ If you have feedback on any of our docs, please leave a rating and message at th If you have any issues with the SDK, please [open an issue on GitHub](https://github.com/superwall/superwall-flutter/issues). - \ No newline at end of file + \ No newline at end of file diff --git a/content/docs/flutter/meta.json b/content/docs/flutter/meta.json index 276c6a2a..722313c3 100644 --- a/content/docs/flutter/meta.json +++ b/content/docs/flutter/meta.json @@ -21,6 +21,7 @@ "guides/configuring", "guides/using-superwall-delegate", "guides/3rd-party-analytics", + "guides/handling-deep-links", "---SDK Reference---", "sdk-reference/index", @@ -45,6 +46,8 @@ "sdk-reference/PurchaseController", "sdk-reference/PresentationResult", "sdk-reference/CustomerInfo", + "sdk-reference/SubscriptionTransaction", + "sdk-reference/NonSubscriptionTransaction", "sdk-reference/Entitlements", "sdk-reference/IntegrationAttribute", "sdk-reference/handleDeepLink", diff --git a/content/docs/flutter/sdk-reference/NonSubscriptionTransaction.mdx b/content/docs/flutter/sdk-reference/NonSubscriptionTransaction.mdx new file mode 100644 index 00000000..3c8ba735 --- /dev/null +++ b/content/docs/flutter/sdk-reference/NonSubscriptionTransaction.mdx @@ -0,0 +1,70 @@ +--- +title: "NonSubscriptionTransaction" +description: "Represents a non-subscription transaction (consumables and non-consumables)." +--- + + +The `store` field was added in 2.4.7. + + +## Purpose +Provides details about one-time purchases in [`CustomerInfo`](/flutter/sdk-reference/CustomerInfo), including which store fulfilled the purchase. + +## Properties + + + +## Store values (2.4.7+) +`appStore`, `stripe`, `paddle`, `playStore`, `superwall`, `other`. + +## Usage + +Inspect non-subscription purchases: +```dart +final customerInfo = await Superwall.shared.getCustomerInfo(); + +for (final purchase in customerInfo.nonSubscriptions) { + print('Product: ${purchase.productId}'); + print('Store: ${purchase.store}'); + print('Consumable: ${purchase.isConsumable}'); + print('Revoked: ${purchase.isRevoked}'); +} +``` + +## Related + +- [`CustomerInfo`](/flutter/sdk-reference/CustomerInfo) - Source of transaction data +- [`SubscriptionTransaction`](/flutter/sdk-reference/SubscriptionTransaction) - Subscription transactions +- [`getCustomerInfo()`](/flutter/sdk-reference/getCustomerInfo) - Fetch customer info diff --git a/content/docs/flutter/sdk-reference/PaywallOptions.mdx b/content/docs/flutter/sdk-reference/PaywallOptions.mdx index ae3b4805..4f29c3c2 100644 --- a/content/docs/flutter/sdk-reference/PaywallOptions.mdx +++ b/content/docs/flutter/sdk-reference/PaywallOptions.mdx @@ -54,8 +54,8 @@ enum TransactionBackgroundView { spinner, none } }, "restoreFailed.message": { type: "String", - description: "Message for restore-failed alert. \"`", - default: "`\"We couldn't find an active subscription for your account", + description: "Message for restore-failed alert.", + default: "We couldn't find an active subscription for your account.", }, "restoreFailed.closeButtonTitle": { type: "String", diff --git a/content/docs/flutter/sdk-reference/PaywallPresentationHandler.mdx b/content/docs/flutter/sdk-reference/PaywallPresentationHandler.mdx index 3c433b40..8a2e057f 100644 --- a/content/docs/flutter/sdk-reference/PaywallPresentationHandler.mdx +++ b/content/docs/flutter/sdk-reference/PaywallPresentationHandler.mdx @@ -21,6 +21,9 @@ class PaywallPresentationHandler { void onDismiss(Function(PaywallInfo, PaywallResult) handler); void onSkip(Function(PaywallSkippedReason) handler); void onError(Function(String) handler); + void onCustomCallback( + Future Function(CustomCallback) handler, + ); } ``` @@ -47,28 +50,63 @@ class PaywallPresentationHandler { description: "Sets a handler called when an error occurs during presentation.", required: true, }, + onCustomCallback: { + type: "handler: (CustomCallback) -> Future", + description: "Sets an async handler called when the paywall requests a custom callback. Available in 2.4.8+.", + required: false, + }, }} /> - ## Returns / State Each method returns `void` and configures the handler for the specific paywall lifecycle event. +## CustomCallback (2.4.8+) +The `onCustomCallback` handler receives a `CustomCallback` object. + +?", + description: "Optional key-value pairs sent from the paywall.", + required: false, + }, + }} +/> + +## CustomCallbackResult (2.4.8+) +Return a `CustomCallbackResult` from your callback handler to control paywall flow: + +```dart +CustomCallbackResult.success([Map? data]) +CustomCallbackResult.failure([Map? data]) +``` + +Use `data` to send values back to the paywall, available as `callbacks..data.`. + +## PaywallInfo State (2.4.8+) +`PaywallInfo` now includes `state`, a `Map?` with current paywall state values. + ## Usage Basic handler setup: ```dart Future _registerFeatureWithHandler() async { final handler = PaywallPresentationHandler(); - + handler.onPresent((paywallInfo) { print('Paywall presented: ${paywallInfo.identifier}'); - // Pause background tasks, analytics, etc. + print('Paywall state: ${paywallInfo.state}'); }); - + handler.onDismiss((paywallInfo, result) { print('Paywall dismissed with result: $result'); - + switch (result) { case PaywallResult.purchased: _showSuccessMessage(); @@ -81,7 +119,7 @@ Future _registerFeatureWithHandler() async { break; } }); - + await Superwall.shared.registerPlacement( 'premium_feature', params: {'source': 'feature_screen'}, @@ -93,14 +131,14 @@ Future _registerFeatureWithHandler() async { } ``` -Handle skip and error cases: +Handle skip, error, and custom callbacks: ```dart Future _setupComprehensiveHandler() async { final handler = PaywallPresentationHandler(); - + handler.onSkip((reason) { print('Paywall skipped: $reason'); - + switch (reason) { case PaywallSkippedReason.userIsSubscribed: _proceedToFeature(); @@ -112,12 +150,25 @@ Future _setupComprehensiveHandler() async { break; } }); - + handler.onError((error) { print('Paywall error: $error'); _showErrorDialog(error); }); - + + handler.onCustomCallback((callback) async { + switch (callback.name) { + case 'validate_email': + final email = callback.variables?['email'] as String?; + if (email != null && email.contains('@')) { + return CustomCallbackResult.success({'validated': true}); + } + return CustomCallbackResult.failure({'error': 'Invalid email'}); + default: + return CustomCallbackResult.failure({'error': 'Unknown callback'}); + } + }); + await Superwall.shared.registerPlacement( 'remove_ads', handler: handler, @@ -127,41 +178,3 @@ Future _setupComprehensiveHandler() async { ); } ``` - -Reusable handler class: -```dart -class ReusablePaywallHandler { - static PaywallPresentationHandler create({ - VoidCallback? onSuccess, - VoidCallback? onCancel, - }) { - final handler = PaywallPresentationHandler(); - - handler.onPresent((_) { - // Analytics tracking - Analytics.track('paywall_presented'); - }); - - handler.onDismiss((_, result) { - switch (result) { - case PaywallResult.purchased: - onSuccess?.call(); - break; - case PaywallResult.cancelled: - onCancel?.call(); - break; - default: - break; - } - }); - - return handler; - } -} - -// Usage -final handler = ReusablePaywallHandler.create( - onSuccess: () => Navigator.pushNamed(context, '/premium'), - onCancel: () => _showRetentionOffer(), -); -``` \ No newline at end of file diff --git a/content/docs/flutter/sdk-reference/SubscriptionTransaction.mdx b/content/docs/flutter/sdk-reference/SubscriptionTransaction.mdx new file mode 100644 index 00000000..06caefd0 --- /dev/null +++ b/content/docs/flutter/sdk-reference/SubscriptionTransaction.mdx @@ -0,0 +1,104 @@ +--- +title: "SubscriptionTransaction" +description: "Represents a subscription transaction in the customer's purchase history." +--- + + +The `offerType`, `subscriptionGroupId`, and `store` fields were added in 2.4.7. + + +## Purpose +Provides details about a single subscription transaction returned from [`CustomerInfo`](/flutter/sdk-reference/CustomerInfo). Use this to understand renewal status, applied offers, and the store that fulfilled the purchase. + +## Properties + + + +## Offer types (2.4.7+) +- `trial` - introductory offer. +- `code` - offer redeemed with a promo code. +- `promotional` - promotional offer. +- `winback` - win-back offer (iOS 17.2+ only). + +## Store values (2.4.7+) +`appStore`, `stripe`, `paddle`, `playStore`, `superwall`, `other`. + +## Usage + +Inspect subscription transactions: +```dart +final customerInfo = await Superwall.shared.getCustomerInfo(); + +for (final subscription in customerInfo.subscriptions) { + print('Product: ${subscription.productId}'); + print('Active: ${subscription.isActive}'); + print('Store: ${subscription.store}'); + print('Offer: ${subscription.offerType}'); + print('Group: ${subscription.subscriptionGroupId ?? "unknown"}'); +} +``` + +## Related + +- [`CustomerInfo`](/flutter/sdk-reference/CustomerInfo) - Source of subscription data +- [`NonSubscriptionTransaction`](/flutter/sdk-reference/NonSubscriptionTransaction) - Non-subscription transactions +- [`getCustomerInfo()`](/flutter/sdk-reference/getCustomerInfo) - Fetch customer info diff --git a/content/docs/flutter/sdk-reference/SuperwallDelegate.mdx b/content/docs/flutter/sdk-reference/SuperwallDelegate.mdx index 9f1a0da8..7860244e 100644 --- a/content/docs/flutter/sdk-reference/SuperwallDelegate.mdx +++ b/content/docs/flutter/sdk-reference/SuperwallDelegate.mdx @@ -24,6 +24,7 @@ abstract class SuperwallDelegate { void paywallWillOpenDeepLink(Uri url); void handleSuperwallDeepLink(Uri fullURL, List pathComponents, Map queryParameters); void customerInfoDidChange(CustomerInfo from, CustomerInfo to); + void userAttributesDidChange(Map newAttributes); } ``` @@ -84,6 +85,12 @@ class MySuperwallDelegate extends SuperwallDelegate { print('Customer info changed'); // Sync with your backend, update UI, etc. } + + @override + void userAttributesDidChange(Map newAttributes) { + print('User attributes updated: $newAttributes'); + // Sync with analytics or update in-memory state. + } } ``` diff --git a/content/docs/flutter/sdk-reference/index.mdx b/content/docs/flutter/sdk-reference/index.mdx index 0239f4ed..8be83b3b 100644 --- a/content/docs/flutter/sdk-reference/index.mdx +++ b/content/docs/flutter/sdk-reference/index.mdx @@ -15,4 +15,4 @@ If you have feedback on any of our docs, please leave a rating and message at th If you have any issues with the SDK, please [open an issue on GitHub](https://github.com/superwall/superwall-flutter/issues). - \ No newline at end of file + \ No newline at end of file diff --git a/content/docs/flutter/sdk-reference/setUserAttributes.mdx b/content/docs/flutter/sdk-reference/setUserAttributes.mdx index c5e483e0..1a2256e2 100644 --- a/content/docs/flutter/sdk-reference/setUserAttributes.mdx +++ b/content/docs/flutter/sdk-reference/setUserAttributes.mdx @@ -30,6 +30,8 @@ Future setUserAttributes(Map userAttributes) ## Returns / State Returns a `Future` that completes when the user attributes are set. +If you have a [`SuperwallDelegate`](/flutter/sdk-reference/SuperwallDelegate) set, `userAttributesDidChange` is invoked after the SDK applies the updated attributes. + ## Usage Basic usage: diff --git a/content/docs/images/article-vid-example.gif b/content/docs/images/article-vid-example.gif new file mode 100644 index 00000000..e5165d41 Binary files /dev/null and b/content/docs/images/article-vid-example.gif differ diff --git a/content/docs/images/demand-score-ai-analysis.jpg b/content/docs/images/demand-score-ai-analysis.jpg new file mode 100644 index 00000000..9e4edfa7 Binary files /dev/null and b/content/docs/images/demand-score-ai-analysis.jpg differ diff --git a/content/docs/images/demand-score-breakdown-country.png b/content/docs/images/demand-score-breakdown-country.png new file mode 100644 index 00000000..61d17718 Binary files /dev/null and b/content/docs/images/demand-score-breakdown-country.png differ diff --git a/content/docs/images/demand-score-breakdown-placement.jpg b/content/docs/images/demand-score-breakdown-placement.jpg new file mode 100644 index 00000000..133da232 Binary files /dev/null and b/content/docs/images/demand-score-breakdown-placement.jpg differ diff --git a/content/docs/images/demand-score-conversion-rate.jpg b/content/docs/images/demand-score-conversion-rate.jpg new file mode 100644 index 00000000..8f27450d Binary files /dev/null and b/content/docs/images/demand-score-conversion-rate.jpg differ diff --git a/content/docs/images/demand-score-coverage.jpg b/content/docs/images/demand-score-coverage.jpg new file mode 100644 index 00000000..2dd28a2d Binary files /dev/null and b/content/docs/images/demand-score-coverage.jpg differ diff --git a/content/docs/images/demand-score-launch-experiment.jpg b/content/docs/images/demand-score-launch-experiment.jpg new file mode 100644 index 00000000..f108d50c Binary files /dev/null and b/content/docs/images/demand-score-launch-experiment.jpg differ diff --git a/content/docs/images/demand-score-overview.jpg b/content/docs/images/demand-score-overview.jpg new file mode 100644 index 00000000..71396e7e Binary files /dev/null and b/content/docs/images/demand-score-overview.jpg differ diff --git a/content/docs/images/demand-score-paywalled-users.jpg b/content/docs/images/demand-score-paywalled-users.jpg new file mode 100644 index 00000000..d9d35414 Binary files /dev/null and b/content/docs/images/demand-score-paywalled-users.jpg differ diff --git a/content/docs/images/demand-score-trial-billing.jpg b/content/docs/images/demand-score-trial-billing.jpg new file mode 100644 index 00000000..d483e335 Binary files /dev/null and b/content/docs/images/demand-score-trial-billing.jpg differ diff --git a/content/docs/images/ds_layout_empty.jpeg b/content/docs/images/ds_layout_empty.jpeg new file mode 100644 index 00000000..fc7b5bd8 Binary files /dev/null and b/content/docs/images/ds_layout_empty.jpeg differ diff --git a/content/docs/images/ds_sidebar.jpeg b/content/docs/images/ds_sidebar.jpeg new file mode 100644 index 00000000..607ebc25 Binary files /dev/null and b/content/docs/images/ds_sidebar.jpeg differ diff --git a/content/docs/images/integrations-customer-io.jpeg b/content/docs/images/integrations-customer-io.jpeg new file mode 100644 index 00000000..eda60084 Binary files /dev/null and b/content/docs/images/integrations-customer-io.jpeg differ diff --git a/content/docs/images/integrations-discord.jpeg b/content/docs/images/integrations-discord.jpeg new file mode 100644 index 00000000..0e1ca421 Binary files /dev/null and b/content/docs/images/integrations-discord.jpeg differ diff --git a/content/docs/images/integrations-facebook-pixel.jpeg b/content/docs/images/integrations-facebook-pixel.jpeg new file mode 100644 index 00000000..d89b61d3 Binary files /dev/null and b/content/docs/images/integrations-facebook-pixel.jpeg differ diff --git a/content/docs/images/quickstart-ai-setup.jpeg b/content/docs/images/quickstart-ai-setup.jpeg new file mode 100644 index 00000000..079ec1cd Binary files /dev/null and b/content/docs/images/quickstart-ai-setup.jpeg differ diff --git a/content/docs/images/showCustomAction.jpeg b/content/docs/images/showCustomAction.jpeg new file mode 100644 index 00000000..66e9ceea Binary files /dev/null and b/content/docs/images/showCustomAction.jpeg differ diff --git a/content/docs/images/stripe-test/step-1-install-page.png b/content/docs/images/stripe-test/step-1-install-page.png new file mode 100644 index 00000000..c79c393e Binary files /dev/null and b/content/docs/images/stripe-test/step-1-install-page.png differ diff --git a/content/docs/images/stripe-test/step-2-install.png b/content/docs/images/stripe-test/step-2-install.png new file mode 100644 index 00000000..84980509 Binary files /dev/null and b/content/docs/images/stripe-test/step-2-install.png differ diff --git a/content/docs/images/stripe-test/step-3-continue.png b/content/docs/images/stripe-test/step-3-continue.png new file mode 100644 index 00000000..b2137a51 Binary files /dev/null and b/content/docs/images/stripe-test/step-3-continue.png differ diff --git a/content/docs/images/stripe-test/step-4-reveal-api-key.png b/content/docs/images/stripe-test/step-4-reveal-api-key.png new file mode 100644 index 00000000..3ce6592f Binary files /dev/null and b/content/docs/images/stripe-test/step-4-reveal-api-key.png differ diff --git a/content/docs/images/stripe-test/step-5-copy-api-key.png b/content/docs/images/stripe-test/step-5-copy-api-key.png new file mode 100644 index 00000000..b86cc577 Binary files /dev/null and b/content/docs/images/stripe-test/step-5-copy-api-key.png differ diff --git a/content/docs/images/stripe/step-1-marketplace.png b/content/docs/images/stripe/step-1-marketplace.png new file mode 100644 index 00000000..b4c7407f Binary files /dev/null and b/content/docs/images/stripe/step-1-marketplace.png differ diff --git a/content/docs/images/stripe/step-2-install.png b/content/docs/images/stripe/step-2-install.png new file mode 100644 index 00000000..b7395488 Binary files /dev/null and b/content/docs/images/stripe/step-2-install.png differ diff --git a/content/docs/images/stripe/step-3-continue.png b/content/docs/images/stripe/step-3-continue.png new file mode 100644 index 00000000..8a3c0a0f Binary files /dev/null and b/content/docs/images/stripe/step-3-continue.png differ diff --git a/content/docs/images/stripe/step-4-generate-key.png b/content/docs/images/stripe/step-4-generate-key.png new file mode 100644 index 00000000..160e4602 Binary files /dev/null and b/content/docs/images/stripe/step-4-generate-key.png differ diff --git a/content/docs/images/stripe/step-5-generate-key-2.png b/content/docs/images/stripe/step-5-generate-key-2.png new file mode 100644 index 00000000..e7d99ba3 Binary files /dev/null and b/content/docs/images/stripe/step-5-generate-key-2.png differ diff --git a/content/docs/images/stripe/step-6-copy-keys.png b/content/docs/images/stripe/step-6-copy-keys.png new file mode 100644 index 00000000..ff91a12b Binary files /dev/null and b/content/docs/images/stripe/step-6-copy-keys.png differ diff --git a/content/docs/images/sw_mcp_claude_connect.jpg b/content/docs/images/sw_mcp_claude_connect.jpg new file mode 100644 index 00000000..33caaf5c Binary files /dev/null and b/content/docs/images/sw_mcp_claude_connect.jpg differ diff --git a/content/docs/images/sw_mcp_example.jpg b/content/docs/images/sw_mcp_example.jpg new file mode 100644 index 00000000..b362453f Binary files /dev/null and b/content/docs/images/sw_mcp_example.jpg differ diff --git a/content/docs/images/web_checkout_direct_to_stripe.jpg b/content/docs/images/web_checkout_direct_to_stripe.jpg index aa965a80..4f29944f 100644 Binary files a/content/docs/images/web_checkout_direct_to_stripe.jpg and b/content/docs/images/web_checkout_direct_to_stripe.jpg differ diff --git a/content/docs/integrations/amplitude.mdx b/content/docs/integrations/amplitude.mdx index f9dccaa0..609b3385 100644 --- a/content/docs/integrations/amplitude.mdx +++ b/content/docs/integrations/amplitude.mdx @@ -100,7 +100,7 @@ Every Amplitude event includes comprehensive properties: - `event_type`: Human-readable event name with `[Superwall]` prefix - `time`: Event timestamp (milliseconds) - `session_id`: Same as timestamp (groups related events) -- `platform`: Store name (APP_STORE, PLAY_STORE, STRIPE, PADDLE) +- `platform`: Store name (APP_STORE, PLAY_STORE, STRIPE) - `insert_id`: Unique event ID prefixed with `sw_` #### Revenue fields (when applicable) @@ -182,7 +182,6 @@ The `platform` field identifies the payment source: - `APP_STORE`: iOS App Store - `PLAY_STORE`: Google Play Store - `STRIPE`: Stripe web payments -- `PADDLE`: Paddle payments (coming soon) This helps analyze: - Revenue by platform diff --git a/content/docs/integrations/customer-io.mdx b/content/docs/integrations/customer-io.mdx new file mode 100644 index 00000000..4c190be9 --- /dev/null +++ b/content/docs/integrations/customer-io.mdx @@ -0,0 +1,405 @@ +--- +title: "Customer.io" +description: "The Customer.io integration sends subscription lifecycle events from Superwall to Customer.io's Data Pipelines API. This enables you to trigger targeted messaging campaigns, build user segments based on subscription behavior, and track the complete customer journey from trial to paid subscriber." +--- + +In the **Communication** section within **Integrations**, you can connect your Customer.io account to Superwall: + +![](../images/integrations-customer-io.jpeg) + +## Features + +- **Real-time Event Tracking**: Subscription events are sent immediately to Customer.io as they occur +- **Multi-Region Support**: Choose between US and EU data residency to comply with data regulations +- **Flexible Revenue Reporting**: Report either gross revenue or net proceeds after store fees +- **Sandbox Environment Support**: Separate API key for testing without polluting production data +- **Anonymous User Handling**: Configurable behavior for users without an identified app user ID +- **Custom Event Names**: Remap default event names to match your existing Customer.io conventions +- **Automatic User Identification**: Smart routing between `userId` and `anonymousId` based on user state + +## Configuration + +### Required Settings + +| Field | Description | Example | +|-------|-------------|---------| +| `integration_id` | Must be set to `"customerio"` | `"customerio"` | +| `region` | Data residency region for your Customer.io workspace | `"US"` or `"EU"` | +| `api_key` | Pipelines API key from your HTTP source | `"abc123def456..."` | +| `sales_reporting` | Whether to report gross Revenue or net Proceeds | `"Revenue"` or `"Proceeds"` | + +### Optional Settings + +| Field | Description | Default | +|-------|-------------|---------| +| `sandbox_api_key` | Separate Pipelines API key for sandbox/test events | None (sandbox events skipped) | +| `anonymous_user_behavior` | How to handle events from users without an app user ID | `"send"` | +| `eventNameMappings` | Custom mapping to rename default event names | None | + +### Example Configuration + +```json +{ + "integration_id": "customerio", + "region": "US", + "api_key": "your-pipelines-api-key", + "sales_reporting": "Revenue", + "sandbox_api_key": "your-sandbox-pipelines-api-key", + "anonymous_user_behavior": "send", + "eventNameMappings": { + "sw_trial_start": "trial_started", + "sw_subscription_start": "subscription_started", + "sw_renewal": "subscription_renewed" + } +} +``` + +## Getting Your API Key + +The Customer.io integration uses the **Pipelines API** (part of Customer.io Data Pipelines), not the Track API. To get your API key: + +1. Log in to your Customer.io account +2. Navigate to **Data Pipelines** in the left sidebar +3. Go to **Sources** +4. Click **Add Source** and select **HTTP** +5. Name your source (e.g., "Superwall Events") +6. Copy the **API Key** displayed after creation + +**Important**: The Pipelines API key is different from the Track API credentials (Site ID + API Key). Make sure you're using the correct key from Data Pipelines. + +## Event Mapping + +Superwall subscription events are transformed into Customer.io events based on the event type and subscription period. All events are prefixed with `sw_` by default. + +### Trial Events + +| Superwall Event | Condition | Customer.io Event | +|-----------------|-----------|-------------------| +| `INITIAL_PURCHASE` | `periodType = Trial` | `sw_trial_start` | +| `CANCELLATION` | `periodType = Trial` | `sw_trial_cancelled` | +| `UNCANCELLATION` | `periodType = Trial` | `sw_trial_uncancelled` | +| `EXPIRATION` | `periodType = Trial` | `sw_trial_expired` | +| `RENEWAL` | `periodType = Trial` | `sw_trial_converted` | + +### Intro Offer Events + +| Superwall Event | Condition | Customer.io Event | +|-----------------|-----------|-------------------| +| `INITIAL_PURCHASE` | `periodType = Intro` | `sw_intro_offer_start` | +| `CANCELLATION` | `periodType = Intro` | `sw_intro_offer_cancelled` | +| `UNCANCELLATION` | `periodType = Intro` | `sw_intro_offer_uncancelled` | +| `EXPIRATION` | `periodType = Intro` | `sw_intro_offer_expired` | +| `RENEWAL` | `periodType = Intro` | `sw_intro_offer_converted` | + +### Subscription Events + +| Superwall Event | Condition | Customer.io Event | +|-----------------|-----------|-------------------| +| `INITIAL_PURCHASE` | `periodType = Normal` | `sw_subscription_start` | +| `RENEWAL` | `periodType = Normal` | `sw_renewal` | +| `RENEWAL` | `isTrialConversion = true` | `sw_trial_converted` | +| `CANCELLATION` | `periodType = Normal` | `sw_subscription_cancelled` | +| `UNCANCELLATION` | `periodType = Normal` | `sw_subscription_uncancelled` | +| `EXPIRATION` | `periodType = Normal` | `sw_subscription_expired` | + +### Other Events + +| Superwall Event | Customer.io Event | +|-----------------|-------------------| +| `PRODUCT_CHANGE` | `sw_product_change` | +| `BILLING_ISSUE` | `sw_billing_issue` | +| `SUBSCRIPTION_PAUSED` | `sw_subscription_paused` | +| `NON_RENEWING_PURCHASE` | `sw_non_renewing_purchase` | +| Any event with `price < 0` | `sw_refund` | + +## Event Properties + +Each event sent to Customer.io includes comprehensive properties from the original Superwall event, plus additional formatted fields for revenue tracking. + +### Standard Properties + +All events include the complete Superwall event data: + +| Property | Description | Example | +|----------|-------------|---------| +| `id` | Unique event identifier | `"evt_abc123"` | +| `productId` | The subscription product ID | `"com.app.premium.monthly"` | +| `store` | App store (APP_STORE, PLAY_STORE) | `"APP_STORE"` | +| `environment` | Production or Sandbox | `"Production"` | +| `countryCode` | User's country code | `"US"` | +| `currencyCode` | Transaction currency | `"USD"` | +| `originalAppUserId` | Your app's user identifier | `"user_12345"` | +| `originalTransactionId` | Store's original transaction ID | `"1000000123456789"` | +| `transactionId` | Current transaction ID | `"1000000987654321"` | +| `purchasedAt` | Purchase timestamp (ms) | `1705312200000` | +| `expirationAt` | Subscription expiration (ms) | `1707990600000` | +| `periodType` | Trial, Intro, or Normal | `"Normal"` | +| `isTrialConversion` | Whether this converts a trial | `true` | +| `isFamilyShare` | Family sharing purchase | `false` | +| `bundleId` | App bundle identifier | `"com.example.app"` | + +### Revenue Properties + +When the event has a non-zero price, these additional properties are included: + +| Property | Description | Example | +|----------|-------------|---------| +| `price` | Amount based on `sales_reporting` setting | `9.99` | +| `currency` | Currency code | `"USD"` | +| `product_id` | Product identifier | `"com.app.premium.monthly"` | +| `subscription_id` | Original transaction ID | `"1000000123456789"` | +| `offer_code` | Promotional offer code (if present) | `"SUMMER2024"` | + +### Revenue vs Proceeds + +The `sales_reporting` setting controls which amount is sent: + +- **Revenue**: The full price charged to the customer (e.g., $9.99) +- **Proceeds**: The amount after store fees are deducted (e.g., $8.49 after Apple's 15-30% commission) + +## User Identification + +Customer.io uses either `userId` or `anonymousId` to identify users. The integration automatically selects the appropriate identifier based on user state. + +### Known Users + +For users with an `originalAppUserId` set in Superwall: + +```json +{ + "userId": "user_12345", + "event": "sw_subscription_start", + "properties": { ... }, + "timestamp": "2024-01-15T10:30:00.000Z" +} +``` + +### Anonymous Users + +For users without an `originalAppUserId`, the behavior depends on `anonymous_user_behavior`: + +**When set to `"send"` (default)**: +- Events are sent with an `anonymousId` constructed from the store and transaction ID +- Format: `$STORE_NAME:originalTransactionId` + +```json +{ + "anonymousId": "$APP_STORE:1000000123456789", + "event": "sw_subscription_start", + "properties": { ... }, + "timestamp": "2024-01-15T10:30:00.000Z" +} +``` + +**When set to `"dontSend"`**: +- Events from anonymous users are skipped entirely +- Useful if you only want to track identified users + +## Sandbox Handling + +The integration supports separate handling for sandbox (test) events: + +### With Sandbox API Key Configured + +When `sandbox_api_key` is provided: +- Production events use the main `api_key` +- Sandbox events use the `sandbox_api_key` +- Both are sent to Customer.io but can be routed to different destinations + +### Without Sandbox API Key + +When `sandbox_api_key` is not provided: +- Production events are sent normally +- Sandbox events are **skipped entirely** +- This prevents test data from polluting your production Customer.io workspace + +## Data Residency + +Customer.io offers data residency in two regions. The integration automatically routes to the correct endpoint: + +| Region | API Endpoint | +|--------|--------------| +| US | `https://cdp.customer.io/v1/track` | +| EU | `https://cdp-eu.customer.io/v1/track` | + +Choose the region that matches your Customer.io workspace configuration. Using the wrong region will result in authentication errors. + +## Custom Event Names + +Use `eventNameMappings` to rename default event names to match your existing Customer.io conventions: + +```json +{ + "eventNameMappings": { + "sw_trial_start": "Started Free Trial", + "sw_subscription_start": "Subscribed", + "sw_renewal": "Subscription Renewed", + "sw_subscription_cancelled": "Subscription Cancelled", + "sw_refund": "Refund Processed" + } +} +``` + +Only events you specify in the mapping are renamed. All other events keep their default `sw_` prefixed names. + +## Testing the Integration + +### 1. Validate Credentials + +The integration validates credentials by sending a test event to Customer.io. If the API key is invalid or the region is incorrect, you'll receive an authentication error. + +### 2. Verify in Customer.io + +After sending test events: +1. Go to **Data Pipelines** → **Sources** → your HTTP source +2. Click on **Events** to see incoming events +3. Verify event names and properties match expectations + +### 3. Test Scenarios + +Verify these scenarios work correctly: + +- [ ] Production event with known user (should use `userId`) +- [ ] Production event with anonymous user (should use `anonymousId` or skip) +- [ ] Sandbox event with sandbox API key (should send to Customer.io) +- [ ] Sandbox event without sandbox API key (should be skipped) +- [ ] Event with custom name mapping (should use remapped name) +- [ ] Revenue event (should include `price`, `currency`, `product_id`) +- [ ] Non-revenue event like cancellation (should not include revenue properties) + +## Best Practices + +1. **Use separate sandbox credentials**: Configure a `sandbox_api_key` to keep test data separate from production, or leave it blank to skip sandbox events entirely. + +2. **Choose the right sales reporting**: Use "Revenue" for customer-facing metrics and "Proceeds" for financial reporting that accounts for store fees. + +3. **Handle anonymous users thoughtfully**: If your app requires login, use `"dontSend"` to avoid cluttering Customer.io with unidentifiable users. + +4. **Keep event names consistent**: If you have existing events in Customer.io, use `eventNameMappings` to maintain naming consistency across your data. + +5. **Verify your region**: Ensure your `region` setting matches your Customer.io workspace location to avoid authentication failures. + +6. **Test with sandbox first**: Always test your integration configuration with sandbox events before going live with production data. + +## Common Use Cases + +### Win-Back Campaigns + +Trigger automated campaigns when users cancel: +1. Listen for `sw_subscription_cancelled` events +2. Create a segment of recently cancelled users +3. Send a series of win-back emails with special offers + +### Trial Conversion Optimization + +Improve trial-to-paid conversion: +1. Track `sw_trial_start` to begin a nurture sequence +2. Send educational content about premium features +3. Trigger a special offer before trial expiration +4. Track `sw_trial_converted` to measure success + +### Churn Prevention + +Identify and engage at-risk subscribers: +1. Monitor `sw_billing_issue` events +2. Send immediate notification to update payment method +3. Follow up with helpful support content +4. Track resolution with subsequent `sw_renewal` events + +### Revenue Analytics + +Build comprehensive revenue reporting: +1. Segment users by subscription status +2. Track lifetime value using revenue properties +3. Analyze conversion rates by cohort +4. Measure impact of promotional offers via `offer_code` + +## Troubleshooting + +### Events Not Appearing in Customer.io + +**Possible causes:** +- Incorrect API key (make sure you're using Pipelines API key, not Track API) +- Wrong region selected (US vs EU mismatch) +- Sandbox events without sandbox API key configured (events are skipped) +- Anonymous users with `dontSend` behavior (events are skipped) + +**Solution:** Verify your API key is from Data Pipelines → Sources → HTTP, and check that your region matches your workspace. + +### Authentication Errors + +**Possible causes:** +- Using Track API credentials instead of Pipelines API key +- Region mismatch between configuration and Customer.io workspace +- API key has been revoked or regenerated + +**Solution:** Generate a new HTTP source in Data Pipelines and use the fresh API key. + +### Missing Revenue Properties + +**Possible causes:** +- Event has zero price (cancellations, expirations) +- Refund events (price is negative, still included but as negative value) + +**Solution:** Revenue properties (`price`, `currency`, `product_id`, `subscription_id`) are only added when the price is non-zero. This is expected behavior. + +### Wrong Event Names + +**Possible causes:** +- Event name mappings not configured +- Typo in mapping configuration + +**Solution:** Check your `eventNameMappings` configuration. Keys should be the default event names (e.g., `sw_trial_start`), and values should be your desired custom names. + +## Rate Limits + +Customer.io's Pipelines API has generous rate limits suitable for high-volume event ingestion: + +- **Requests**: 500 requests per second per source +- **Payload size**: 32KB per request + +The integration sends one event per webhook, well within these limits. If you experience rate limiting, contact Customer.io support to increase your limits. + +## API Reference + +### Endpoint + +``` +POST https://cdp.customer.io/v1/track (US region) +POST https://cdp-eu.customer.io/v1/track (EU region) +``` + +### Authentication + +Basic Authentication with the Pipelines API key as username and empty password: + +``` +Authorization: Basic base64(api_key:) +``` + +### Request Format + +```json +{ + "userId": "user_12345", + "event": "sw_subscription_start", + "timestamp": "2024-01-15T10:30:00.000Z", + "properties": { + "productId": "com.app.premium.monthly", + "price": 9.99, + "currency": "USD", + "store": "APP_STORE", + "environment": "Production", + ... + } +} +``` + +### Response + +Success: `200 OK` with empty body or acknowledgment + +Errors: +- `401 Unauthorized`: Invalid API key or wrong region +- `400 Bad Request`: Malformed request body +- `429 Too Many Requests`: Rate limit exceeded diff --git a/content/docs/integrations/discord.mdx b/content/docs/integrations/discord.mdx new file mode 100644 index 00000000..b5e5c4c8 --- /dev/null +++ b/content/docs/integrations/discord.mdx @@ -0,0 +1,480 @@ +--- +title: "Discord" +description: "The Discord integration sends real-time subscription notifications to your Discord channels via webhooks. Get instant visibility into subscription activity with beautifully formatted, color-coded embed messages that make it easy to monitor revenue, track trials, and respond to billing issues as they happen." +--- + +In the **Communication** section within **Integrations**, you can connect your Discord account to Superwall: + +![](../images/integrations-discord.jpeg) + +## Features + +- **Rich Embed Messages**: Beautiful, color-coded notifications with emoji indicators for quick visual parsing +- **Event Type Filtering**: Choose between revenue-only events or all subscription lifecycle events +- **Real-Time Notifications**: Instant alerts when subscription events occur +- **Smart Formatting**: Currency formatting, country names, and human-readable descriptions +- **Flexible Revenue Reporting**: Report either gross revenue or net proceeds after store fees +- **Anonymous User Handling**: Configurable behavior for users without an identified app user ID +- **Custom Event Names**: Override default event titles to match your team's terminology +- **Sandbox Indicators**: Clear visual badge when events come from test environments + +## Configuration + +### Required Settings + +| Field | Description | Example | +|-------|-------------|---------| +| `integration_id` | Must be set to `"discord"` | `"discord"` | +| `webhook_url` | Discord webhook URL from your server | `"https://discord.com/api/webhooks/123/abc..."` | +| `sales_reporting` | Whether to report gross Revenue or net Proceeds | `"Revenue"` or `"Proceeds"` | + +### Optional Settings + +| Field | Description | Default | +|-------|-------------|---------| +| `event_type` | Filter which events to send | `"All Subscription Events"` | +| `anonymous_user_behavior` | How to handle events from users without an app user ID | `"send"` | +| `eventNameMappings` | Custom mapping to rename default event titles | None | + +### Example Configuration + +```json +{ + "integration_id": "discord", + "webhook_url": "https://discord.com/api/webhooks/1234567890/abcdefghijklmnop", + "sales_reporting": "Revenue", + "event_type": "All Subscription Events", + "anonymous_user_behavior": "send", + "eventNameMappings": { + "sw_subscription_start": "New Premium Member!", + "sw_trial_start": "New Trial Started" + } +} +``` + +## Creating a Discord Webhook + +1. Open your Discord server +2. Go to **Server Settings** (click the server name → Settings) +3. Navigate to **Integrations** → **Webhooks** +4. Click **New Webhook** +5. Configure the webhook: + - **Name**: Choose a name (e.g., "Superwall Events") + - **Channel**: Select the channel where notifications will appear + - **Avatar**: Optionally customize the webhook's avatar +6. Click **Copy Webhook URL** +7. Paste the URL into your integration configuration + +**Tip**: Create a dedicated channel like `#subscription-events` or `#revenue-alerts` to keep notifications organized. + +## Event Filtering + +The `event_type` setting controls which events are sent to Discord: + +### All Subscription Events (Default) + +Sends every subscription lifecycle event: +- Trial starts and conversions +- New subscriptions +- Renewals +- Cancellations and expirations +- Billing issues +- Product changes +- Refunds + +**Best for**: Teams that want complete visibility into all subscription activity. + +### Revenue Events Only + +Only sends events with non-zero revenue: +- New paid subscriptions +- Renewals +- Trial conversions +- One-time purchases +- Refunds (negative revenue) + +**Skips**: Trial starts, cancellations, expirations, billing issues (unless they have revenue attached). + +**Best for**: Teams focused on revenue notifications without the noise of non-revenue events. + +## Message Format + +Discord messages are sent as rich embeds with the following structure: + +### Embed Structure + +``` +┌─────────────────────────────────────────┐ +│ [Superwall Logo] Superwall │ ← Author +├─────────────────────────────────────────┤ +│ 💰 New Subscriber │ ← Title (with emoji) +│ │ +│ $9.99 subscription started from │ ← Description +│ United States │ +├─────────────────────────────────────────┤ +│ 👤 User 🎯 Product 📱 Store │ ← Fields (inline) +│ user_123 com.app.pro App Store • │ +│ United States │ +│ │ +│ 💰 Revenue │ ← Revenue field +│ $9.99 │ +├─────────────────────────────────────────┤ +│ Powered by Superwall Jan 15, 2024 │ ← Footer + Timestamp +└─────────────────────────────────────────┘ +``` + +### Embed Fields + +| Field | Description | When Shown | +|-------|-------------|------------| +| 👤 User | User ID or "Anonymous" | Always | +| 🎯 Product | Product identifier | Always | +| 📱 Store | Store name and country | Always | +| 💰 Revenue / 💵 Proceeds | Formatted amount | When price ≠ 0 | +| ⚙️ Sandbox | Test environment indicator | Sandbox events only | +| 🎁 Offer | Promotional offer code | When offer code present | +| 🔄 Product Change | Old → New product | Product change events | + +## Event Titles and Colors + +Each event type has a distinct emoji, title, and color for quick visual identification. + +### Color Coding + +| Color | Hex Code | Meaning | +|-------|----------|---------| +| Green | `#36A64F` | Revenue events (purchases, renewals, conversions) | +| Blue | `#3498DB` | Trial events (non-revenue) | +| Red | `#FA6A6A` | Negative events (cancellations, refunds, expirations) | +| Orange | `#FF9500` | Billing issues | +| Purple | `#9B59B6` | Product changes | +| Gray | `#666666` | Other events | + +### Event Title Reference + +#### Trial Events +| Event | Title | Color | +|-------|-------|-------| +| Trial Start | 🤩 Trial Start | Blue | +| Trial Conversion | 💰 Trial Conversion | Green | +| Trial Cancelled | 😞 Cancelled Trial | Red | +| Trial Refunded | 🤬 Refunded Trial | Red | +| Trial Expired | 😞 Expired Trial | Red | +| Trial Uncancelled | 🤩 Trial Uncancelled | Blue | + +#### Intro Offer Events +| Event | Title | Color | +|-------|-------|-------| +| Intro Start (free) | 🤩 Intro Offer Start | Blue | +| Intro Start (paid) | 💰 Intro Offer Start | Green | +| Intro Conversion | 💰 Intro Offer Conversion | Green | +| Intro Cancelled | 😞 Cancelled Intro Offer | Red | +| Intro Refunded | 🤬 Refunded Intro Offer | Red | + +#### Subscription Events +| Event | Title | Color | +|-------|-------|-------| +| New Subscription | 💰 New Subscriber | Green | +| Renewal | 💰 Renewal | Green | +| Cancellation | 😞 Cancelled Subscription | Red | +| Refund | 🤬 Refunded Subscription | Red | +| Expiration | 😞 Expired Subscription | Red | +| Uncancellation | 🤩 Subscription Uncancelled | Green | + +#### Other Events +| Event | Title | Color | +|-------|-------|-------| +| One-Time Purchase | 💰 One-Time Purchase | Green | +| Product Change | 😵‍💫 Product Change | Purple | +| Billing Issue | 🫠 Billing Issue | Orange | +| Subscription Paused | ⏸️ Subscription Paused | Gray | + +## Revenue Display + +### Revenue vs Proceeds + +The `sales_reporting` setting controls which amount is displayed: + +- **Revenue**: The full price charged to the customer (e.g., $9.99) +- **Proceeds**: The amount after store fees (e.g., $8.49 after Apple's 15-30% commission) + +The field label changes based on your setting: +- Revenue mode: "💰 Revenue" +- Proceeds mode: "💵 Proceeds" + +### Currency Formatting + +Amounts are automatically formatted with the correct currency symbol and locale: +- `$9.99` for USD +- `€9.99` for EUR +- `£9.99` for GBP +- `¥999` for JPY + +### Zero-Value Events + +Events without revenue (trial starts, cancellations, expirations) do not show a revenue field, keeping the message compact. + +### Refunds + +Refunds display negative amounts: +- "💰 Revenue: -$9.99" + +## Anonymous User Handling + +The `anonymous_user_behavior` setting controls how events from unidentified users are handled: + +### Send (Default) + +- Events from anonymous users are sent to Discord +- User field displays "Anonymous" +- Useful for complete visibility into all subscription activity + +### Don't Send + +- Events from anonymous users are skipped +- No notification is sent to Discord +- Useful if you only want to track identified users + +## Sandbox Events + +Events from sandbox/test environments are clearly marked: + +- A "⚙️ Sandbox" field is added with value "Test Environment" +- Helps distinguish test events from production activity +- Production events do not show any environment indicator + +## Custom Event Names + +Use `eventNameMappings` to customize event titles: + +```json +{ + "eventNameMappings": { + "sw_trial_start": "🎉 New Trial User!", + "sw_subscription_start": "💎 VIP Member Joined", + "sw_renewal": "🔄 Subscription Renewed", + "sw_subscription_cancelled": "👋 Member Churned" + } +} +``` + +### Available Event Keys + +| Key | Default Title | +|-----|---------------| +| `sw_trial_start` | 🤩 Trial Start | +| `sw_trial_converted` | 💰 Trial Conversion | +| `sw_trial_cancelled` | 😞 Cancelled Trial | +| `sw_subscription_start` | 💰 New Subscriber | +| `sw_renewal` | 💰 Renewal | +| `sw_subscription_cancelled` | 😞 Cancelled Subscription | +| `sw_subscription_expired` | 😞 Expired Subscription | +| `sw_refund` | 🤬 Refunded Subscription | +| `sw_billing_issue` | 🫠 Billing Issue | +| `sw_product_change` | 😵‍💫 Product Change | +| `sw_non_renewing_purchase` | 💰 One-Time Purchase | + +## Testing the Integration + +### 1. Validate Credentials + +The integration validates your webhook URL by sending a test event. If the URL is invalid or the webhook has been deleted, validation will fail. + +### 2. Send a Test Event + +Trigger a subscription event from your app (or use sandbox mode) to verify messages appear correctly. + +### 3. Verify in Discord + +Check your configured channel for the notification: +- Confirm the embed appears with correct formatting +- Verify colors match the event type +- Check that fields display correct information + +### 4. Test Scenarios + +- [ ] New subscription shows green with 💰 emoji +- [ ] Trial start shows blue with 🤩 emoji +- [ ] Cancellation shows red with 😞 emoji +- [ ] Revenue field shows correct amount +- [ ] Sandbox events show "⚙️ Sandbox" field +- [ ] Revenue-only filter skips zero-price events +- [ ] Anonymous users show "Anonymous" or are skipped per setting +- [ ] Custom event names appear in title + +## Best Practices + +1. **Create a dedicated channel**: Keep subscription notifications separate from general chat to avoid noise and make monitoring easier. + +2. **Use Revenue Events Only for busy apps**: If you have high volume, filtering to revenue-only events reduces noise while keeping you informed of important transactions. + +3. **Set up channel notifications**: Configure Discord channel notification settings (e.g., only notify for @mentions) to avoid constant pings. + +4. **Consider multiple webhooks**: Create separate webhooks for different event types (e.g., one for revenue in `#sales`, one for all events in `#subscription-logs`). + +5. **Monitor billing issues**: Pay special attention to orange "🫠 Billing Issue" notifications—these represent potential revenue at risk. + +6. **Use meaningful custom names**: If you customize event names, make them clear and actionable for your team. + +## Common Use Cases + +### Sales Celebration Channel + +Create a `#sales` channel with revenue-only events: +```json +{ + "event_type": "Revenue Events Only", + "sales_reporting": "Revenue" +} +``` +Celebrate new subscribers and renewals with your team! + +### Churn Monitoring + +Create a `#churn-alerts` channel and filter to cancellation events using a separate integration instance: +- Monitor cancellation patterns +- Quickly identify if something is causing unusual churn +- React to billing issues before they become cancellations + +### Customer Success Integration + +Use the user ID field to quickly look up users in your CRM or support system: +- Click the dashboard URL in the embed to view user details +- Reach out proactively to users who cancelled +- Thank high-value subscribers personally + +### Team Revenue Dashboard + +Display the Discord channel on a team dashboard or TV: +- Real-time visualization of subscription activity +- Color-coded events make it easy to gauge health at a glance +- Celebrate wins and identify issues quickly + +## Troubleshooting + +### Messages Not Appearing + +**Possible causes:** +- Invalid webhook URL +- Webhook was deleted in Discord +- Channel permissions prevent webhook posting +- Event filtered out by `event_type` setting + +**Solutions:** +1. Verify the webhook still exists in Server Settings → Integrations +2. Check that the webhook has permission to post in the target channel +3. Confirm `event_type` setting includes the event you're expecting +4. Re-create the webhook if it was deleted + +### Webhook Rate Limited + +**Possible causes:** +- Discord rate limits webhook requests (30 requests per minute per channel) +- High volume of subscription events + +**Solutions:** +1. Use "Revenue Events Only" to reduce volume +2. Consider using a less busy channel +3. Events will be queued and retried automatically + +### Wrong Event Names or Emojis + +**Possible causes:** +- Custom `eventNameMappings` overriding defaults +- Unexpected event type mapping + +**Solutions:** +1. Review your `eventNameMappings` configuration +2. Check the event title reference table above +3. Remove custom mappings to restore defaults + +### Missing Revenue Field + +**Possible causes:** +- Event has zero price (normal for trial starts, cancellations) +- This is expected behavior + +**Solutions:** +- Revenue field only appears when price ≠ 0 +- Trial starts, cancellations, and expirations typically have no revenue + +### Sandbox Badge Appearing + +**Possible causes:** +- Event came from sandbox/test environment +- This is expected behavior + +**Solutions:** +- The "⚙️ Sandbox" field only appears for sandbox events +- Verify you're testing in the correct environment + +## Rate Limits + +Discord enforces rate limits on webhooks: + +| Limit | Value | +|-------|-------| +| Requests per minute | 30 per channel | +| Embed limit | 10 embeds per message | +| Total character limit | 6,000 characters per message | + +The integration sends one embed per event, which is well within these limits. For extremely high-volume applications, consider using the "Revenue Events Only" filter. + +## API Reference + +### Endpoint + +Events are sent directly to your Discord webhook URL: + +``` +POST https://discord.com/api/webhooks/{webhook_id}/{webhook_token} +``` + +### Request Headers + +``` +Content-Type: application/json +``` + +### Request Body + +```json +{ + "embeds": [ + { + "author": { + "name": "Superwall", + "icon_url": "https://superwall.com/favicon.ico" + }, + "title": "💰 New Subscriber", + "description": "$9.99 subscription started from United States", + "url": "https://superwall.com/applications/{app_id}", + "color": 3582031, + "thumbnail": { + "url": "https://superwall.com/favicon.ico" + }, + "fields": [ + { "name": "👤 User", "value": "user_123", "inline": true }, + { "name": "🎯 Product", "value": "com.app.premium", "inline": true }, + { "name": "📱 Store", "value": "App Store • United States", "inline": true }, + { "name": "💰 Revenue", "value": "$9.99", "inline": true } + ], + "timestamp": "2024-01-15T10:30:00.000Z", + "footer": { + "text": "Powered by Superwall", + "icon_url": "https://superwall.com/favicon.ico" + } + } + ] +} +``` + +### Response + +**Success**: `204 No Content` (Discord returns no body on success) + +**Error**: +- `400 Bad Request`: Invalid embed structure +- `401 Unauthorized`: Invalid webhook token +- `404 Not Found`: Webhook was deleted +- `429 Too Many Requests`: Rate limited diff --git a/content/docs/integrations/facebook-pixel.mdx b/content/docs/integrations/facebook-pixel.mdx new file mode 100644 index 00000000..5ed37df0 --- /dev/null +++ b/content/docs/integrations/facebook-pixel.mdx @@ -0,0 +1,516 @@ +--- +title: "Facebook Pixel" +description: "The Meta Conversion API integration sends subscription lifecycle events from Superwall directly to Facebook's server-side Conversion API. This enables accurate attribution for Facebook and Instagram ad campaigns, optimizes ad delivery for subscription events, and provides reliable tracking that isn't affected by browser privacy restrictions or ad blockers." +--- + +In the **Marketing** section within **Integrations**, you can connect your Facebook Pixel account to Superwall: + +![](../images/integrations-facebook-pixel.jpeg) + +## Features + +- **Server-Side Event Tracking**: Events are sent directly to Meta's servers, bypassing browser limitations +- **Standard Event Mapping**: Automatically maps subscription events to Meta's standard events (Subscribe, Purchase, StartTrial) +- **Sandbox Environment Support**: Separate Pixel ID and access token for testing without affecting production data +- **Test Event Mode**: Use test event codes to validate integration in Meta Events Manager +- **Flexible Revenue Reporting**: Report either gross revenue or net proceeds after store fees +- **Anonymous User Handling**: Configurable behavior for users without an identified app user ID +- **Custom Event Names**: Override default event mappings to match your existing Meta Pixel conventions +- **Deduplication**: Event IDs prevent duplicate events from being counted multiple times + +## Configuration + +### Required Settings + +| Field | Description | Example | +|-------|-------------|---------| +| `integration_id` | Must be set to `"meta"` | `"meta"` | +| `access_token` | Meta access token with `ads_management` permission | `"EAAG..."` | +| `pixel_id` | Your Facebook Pixel ID | `"123456789012345"` | +| `sales_reporting` | Whether to report gross Revenue or net Proceeds | `"Revenue"` or `"Proceeds"` | + +### Optional Settings + +| Field | Description | Default | +|-------|-------------|---------| +| `sandbox_access_token` | Separate access token for sandbox/test events | None (sandbox events skipped) | +| `sandbox_pixel_id` | Separate Pixel ID for sandbox/test events | None (sandbox events skipped) | +| `test_event_code` | Test event code for validation in Events Manager | None | +| `anonymous_user_behavior` | How to handle events from users without an app user ID | `"send"` | +| `eventNameMappings` | Custom mapping to rename default event names | None | + +### Example Configuration + +```json +{ + "integration_id": "meta", + "access_token": "EAAG1234567890abcdef...", + "pixel_id": "123456789012345", + "sales_reporting": "Revenue", + "sandbox_access_token": "EAAG0987654321fedcba...", + "sandbox_pixel_id": "543210987654321", + "test_event_code": "TEST12345", + "anonymous_user_behavior": "send", + "eventNameMappings": { + "sw_subscription_cancelled": "CancelSubscription", + "sw_refund": "Refund" + } +} +``` + +## Getting Your Credentials + +### Access Token + +1. Go to [Meta Events Manager](https://business.facebook.com/events_manager) +2. Select your Pixel from **Data Sources** +3. Click **Settings** tab +4. Scroll to **Conversions API** section +5. Click **Generate access token** or use an existing System User token +6. Copy the access token + +**Note**: The access token requires `ads_management` permission. For production use, Meta recommends using a System User token rather than a personal access token. + +### Pixel ID + +1. Go to [Meta Events Manager](https://business.facebook.com/events_manager) +2. Select your Pixel from **Data Sources** +3. The Pixel ID is displayed at the top of the page (e.g., "Pixel ID: 123456789012345") + +### Test Event Code (Optional) + +1. In Events Manager, select your Pixel +2. Click the **Test Events** tab +3. Your test event code is displayed (e.g., "TEST12345") +4. Events sent with this code appear in the Test Events tab for validation + +## Event Mapping + +Superwall events are mapped to Meta's standard events when possible. Using standard events enables Meta's machine learning to optimize ad delivery for specific conversion goals. + +### Standard Event Mappings + +| Superwall Event | Meta Standard Event | Description | +|-----------------|---------------------|-------------| +| `sw_subscription_start` | `Subscribe` | New paid subscription | +| `sw_trial_start` | `StartTrial` | Free trial begins | +| `sw_renewal` | `Purchase` | Subscription renewal payment | +| `sw_trial_converted` | `Purchase` | Trial converts to paid | +| `sw_intro_offer_converted` | `Purchase` | Intro offer converts to paid | + +### Custom Event Mappings + +Events without a standard Meta equivalent are sent with their Superwall event names: + +| Superwall Event | Meta Event Name | +|-----------------|-----------------| +| `sw_subscription_cancelled` | `sw_subscription_cancelled` | +| `sw_trial_cancelled` | `sw_trial_cancelled` | +| `sw_subscription_expired` | `sw_subscription_expired` | +| `sw_billing_issue` | `sw_billing_issue` | +| `sw_refund` | `sw_refund` | +| `sw_product_change` | `sw_product_change` | + +### Complete Event Mapping Reference + +| Superwall Event | Condition | Meta Event | +|-----------------|-----------|------------| +| `INITIAL_PURCHASE` | `periodType = Trial` | `StartTrial` | +| `INITIAL_PURCHASE` | `periodType = Normal` | `Subscribe` | +| `INITIAL_PURCHASE` | `periodType = Intro` | `sw_intro_offer_start` | +| `RENEWAL` | `periodType = Trial` | `Purchase` | +| `RENEWAL` | `periodType = Normal` | `Purchase` | +| `RENEWAL` | `isTrialConversion = true` | `Purchase` | +| `CANCELLATION` | `periodType = Trial` | `sw_trial_cancelled` | +| `CANCELLATION` | `periodType = Normal` | `sw_subscription_cancelled` | +| `EXPIRATION` | Any | `sw_*_expired` | +| Any event | `price < 0` | `sw_refund` | + +## Event Format + +Events are sent to Meta's Conversion API in the following format: + +### API Endpoint + +``` +POST https://graph.facebook.com/v21.0/{pixel_id}/events?access_token={access_token} +``` + +### Request Payload + +```json +{ + "data": [ + { + "event_name": "Subscribe", + "event_time": 1705312200, + "event_id": "evt_abc123", + "action_source": "app", + "user_data": { + "external_id": ["user_12345"] + }, + "custom_data": { + "value": 9.99, + "currency": "USD", + "content_type": "product", + "content_name": "com.app.premium.monthly", + "content_ids": ["com.app.premium.monthly"] + } + } + ], + "test_event_code": "TEST12345" +} +``` + +### Event Fields + +| Field | Description | Example | +|-------|-------------|---------| +| `event_name` | Meta standard event or custom event name | `"Subscribe"` | +| `event_time` | Unix timestamp in seconds | `1705312200` | +| `event_id` | Unique event ID for deduplication | `"evt_abc123"` | +| `action_source` | Always set to `"app"` for mobile app events | `"app"` | +| `user_data` | User identification data | `{"external_id": ["user_12345"]}` | +| `custom_data` | Event-specific data including revenue | See below | + +### Custom Data Fields + +| Field | Description | Example | +|-------|-------------|---------| +| `value` | Revenue amount (based on `sales_reporting` setting) | `9.99` | +| `currency` | ISO 4217 currency code | `"USD"` | +| `content_type` | Always `"product"` for subscription events | `"product"` | +| `content_name` | Product identifier | `"com.app.premium.monthly"` | +| `content_ids` | Array containing the product ID | `["com.app.premium.monthly"]` | + +## User Identification + +Meta's Conversion API requires user identification for event matching. The integration uses `external_id` to identify users. + +### Known Users + +For users with an `originalAppUserId` set in Superwall: + +```json +{ + "user_data": { + "external_id": ["user_12345"] + } +} +``` + +### Anonymous Users + +For users without an `originalAppUserId`, the behavior depends on `anonymous_user_behavior`: + +**When set to `"send"` (default)**: +- Events are sent with a synthetic ID: `$STORE_NAME:originalTransactionId` + +```json +{ + "user_data": { + "external_id": ["$APP_STORE:1000000123456789"] + } +} +``` + +**When set to `"dontSend"`**: +- Events from anonymous users are skipped entirely + +## Revenue Tracking + +### Revenue vs Proceeds + +The `sales_reporting` setting controls which amount is sent in the `value` field: + +- **Revenue**: The full price charged to the customer (e.g., $9.99) +- **Proceeds**: The amount after store fees are deducted (e.g., $8.49 after Apple's 15-30% commission) + +### Zero-Value Events + +For events without revenue (cancellations, expirations), the `value` and `currency` fields are omitted: + +```json +{ + "custom_data": { + "content_type": "product", + "content_name": "com.app.premium.monthly", + "content_ids": ["com.app.premium.monthly"] + } +} +``` + +### Refund Events + +Refunds are sent with negative values: + +```json +{ + "event_name": "sw_refund", + "custom_data": { + "value": -9.99, + "currency": "USD", + "content_type": "product", + "content_name": "com.app.premium.monthly", + "content_ids": ["com.app.premium.monthly"] + } +} +``` + +## Sandbox Handling + +The integration supports separate handling for sandbox (test) events: + +### With Sandbox Credentials Configured + +When both `sandbox_pixel_id` and `sandbox_access_token` are provided: +- Production events use the main credentials +- Sandbox events use the sandbox credentials +- Events are tracked separately in Meta Events Manager + +### Without Sandbox Credentials + +When sandbox credentials are not provided: +- Production events are sent normally +- Sandbox events are **skipped entirely** +- This prevents test data from affecting your production Pixel + +## Test Event Mode + +Use the `test_event_code` setting to validate your integration without affecting production data: + +1. Get your test event code from Meta Events Manager → Test Events +2. Add `test_event_code` to your configuration +3. Send test events from your app +4. View events in the Test Events tab of Events Manager + +Events sent with a test event code: +- Appear in the Test Events tab +- Are **not** counted in your main event metrics +- Are **not** used for ad optimization +- Help validate your integration before going live + +**Important**: Remove the `test_event_code` before deploying to production. + +## Custom Event Names + +Use `eventNameMappings` to override default event names: + +```json +{ + "eventNameMappings": { + "sw_trial_start": "CustomTrialStart", + "sw_subscription_start": "CustomSubscribe", + "sw_renewal": "CustomRenewal" + } +} +``` + +**Note**: Mappings override both standard and custom event names. For example, mapping `sw_subscription_start` will send your custom name instead of the Meta standard `Subscribe` event. + +## Testing the Integration + +### 1. Configure Test Event Code + +Add your `test_event_code` from Meta Events Manager to validate events without affecting production metrics. + +### 2. Send Test Events + +Trigger subscription events from your app in sandbox mode. + +### 3. Verify in Meta Events Manager + +1. Go to Meta Events Manager → your Pixel +2. Click the **Test Events** tab +3. Look for events with your test event code +4. Verify event names, parameters, and user data are correct + +### 4. Check Event Quality + +1. Go to Meta Events Manager → your Pixel → **Overview** +2. Check the **Event Match Quality** score +3. Higher scores indicate better event matching + +### 5. Test Scenarios + +Verify these scenarios work correctly: + +- [ ] Production event sends to main Pixel +- [ ] Sandbox event sends to sandbox Pixel (if configured) +- [ ] Sandbox event is skipped when no sandbox credentials +- [ ] Trial start maps to `StartTrial` +- [ ] Subscription start maps to `Subscribe` +- [ ] Renewal maps to `Purchase` +- [ ] Cancellation sends as custom event +- [ ] Anonymous users handled per configuration +- [ ] Revenue is included for paid events +- [ ] Test event code appears in Test Events tab + +## Best Practices + +1. **Use System User tokens**: For production, create a System User in Meta Business Manager and use its access token instead of a personal token for better security and stability. + +2. **Configure sandbox credentials**: Use a separate test Pixel for development to keep your production data clean. + +3. **Remove test event code for production**: Test event codes prevent events from being used for optimization. + +4. **Match user IDs across platforms**: Use consistent `external_id` values between your Pixel browser events and server events for better cross-device attribution. + +5. **Monitor Event Match Quality**: Check your Event Match Quality score in Events Manager regularly. Scores below 6.0 indicate potential matching issues. + +6. **Use standard events when possible**: Standard events like `Subscribe`, `Purchase`, and `StartTrial` enable Meta's machine learning to optimize for those specific conversions. + +## Common Use Cases + +### Optimizing Campaigns for Subscriptions + +1. Send `Subscribe` events for new paid subscriptions +2. Create a Custom Conversion in Meta Ads Manager based on `Subscribe` +3. Optimize your campaigns for subscription conversions +4. Meta will show your ads to users most likely to subscribe + +### Measuring Trial-to-Paid Conversion + +1. Track `StartTrial` events for trial starts +2. Track `Purchase` events for trial conversions +3. Create a funnel in Meta Analytics +4. Analyze conversion rate and time-to-convert + +### Retargeting Churned Users + +1. Track `sw_subscription_cancelled` events +2. Create a Custom Audience of users who cancelled +3. Run re-engagement campaigns with special offers +4. Exclude recent subscribers to avoid wasted ad spend + +### Value-Based Optimization + +1. Include revenue in `custom_data.value` +2. Create Value-Based Custom Conversions +3. Optimize campaigns for highest value subscribers +4. Meta prioritizes showing ads to users likely to generate more revenue + +## Troubleshooting + +### Events Not Appearing in Events Manager + +**Possible causes:** +- Invalid access token (expired or insufficient permissions) +- Incorrect Pixel ID +- Sandbox events without sandbox credentials (events are skipped) +- Test event code routing events to Test Events tab only + +**Solutions:** +1. Verify your access token has `ads_management` permission +2. Confirm your Pixel ID matches Events Manager +3. Check for sandbox credentials if testing +4. Remove `test_event_code` to see events in main Overview + +### Authentication Errors (Error 190) + +**Possible causes:** +- Access token has expired +- Token doesn't have required permissions +- Token was revoked + +**Solutions:** +1. Generate a new access token in Events Manager +2. Ensure the token has `ads_management` permission +3. Consider using a System User token for stability + +### Low Event Match Quality + +**Possible causes:** +- Only `external_id` is being sent +- No additional user data available + +**Solutions:** +- Event Match Quality can be improved by including additional user data fields (email, phone, IP address) if available in your webhook data +- Ensure `external_id` values are consistent with other data sources + +### Events Show as "Duplicate" + +**Possible causes:** +- Same event being sent multiple times +- Event ID collision + +**Solutions:** +- The integration uses the Superwall event ID as `event_id` for deduplication +- Verify your webhook isn't triggering multiple times for the same event + +### Wrong Event Names + +**Possible causes:** +- Custom event name mappings overriding standard events +- Unexpected event type mapping + +**Solutions:** +- Review your `eventNameMappings` configuration +- Check the event mapping reference table above +- Test with `test_event_code` to verify event names + +## Rate Limits + +Meta's Conversion API has the following limits: + +| Limit | Value | +|-------|-------| +| Requests per hour | 10,000 per Pixel | +| Events per request | 1,000 maximum | +| Request body size | 1MB maximum | + +The integration sends one event per webhook, which is well within these limits. For high-volume applications, Meta automatically handles queuing. + +## API Reference + +### Endpoint + +``` +POST https://graph.facebook.com/v21.0/{pixel_id}/events +``` + +### Authentication + +Access token passed as URL parameter: + +``` +?access_token={access_token} +``` + +### Request Headers + +``` +Content-Type: application/json +Accept: */* +``` + +### Response + +**Success (200 OK)**: +```json +{ + "events_received": 1, + "messages": [], + "fbtrace_id": "ABC123..." +} +``` + +**Error (400/401/403)**: +```json +{ + "error": { + "message": "Invalid OAuth access token.", + "type": "OAuthException", + "code": 190, + "fbtrace_id": "ABC123..." + } +} +``` + +## Additional Resources + +- [Meta Conversion API Documentation](https://developers.facebook.com/docs/marketing-api/conversions-api) +- [Server Events Parameters Reference](https://developers.facebook.com/docs/marketing-api/conversions-api/parameters) +- [Event Quality Scoring Guide](https://www.facebook.com/business/help/765081237991954) +- [App Events Best Practices](https://developers.facebook.com/docs/app-events/best-practices) +- [Meta Events Manager](https://business.facebook.com/events_manager) diff --git a/content/docs/integrations/figma-plugin.mdx b/content/docs/integrations/figma-plugin.mdx index 3b4af614..3e9c1bde 100644 --- a/content/docs/integrations/figma-plugin.mdx +++ b/content/docs/integrations/figma-plugin.mdx @@ -3,7 +3,13 @@ title: "Figma Plugin" description: "The Superwall Figma Plugin allows designers to convert Figma designs into fully functional paywalls with one click." --- -The Superwall Figma Import plugin can automatically import Figma designs into the paywall editor. Each component is imported individually, preserving your design structure. To see it in action, check out the video demo: +The Superwall Figma Import plugin can automatically import Figma designs into the paywall editor. Each component is imported individually, preserving your design structure. + + +Auto Layout is required for the **entire frame** in your Figma files for the import to work. + + +To see it in action, check out the video demo: diff --git a/content/docs/integrations/index.mdx b/content/docs/integrations/index.mdx index 09f5f53b..daaff8b5 100644 --- a/content/docs/integrations/index.mdx +++ b/content/docs/integrations/index.mdx @@ -95,7 +95,7 @@ The `data` field contains detailed information about the subscription or payment | `transactionId` | string | Current transaction ID | | `originalTransactionId` | string | Original transaction ID (subscription ID) | | `originalAppUserId` | string or null | Original app user ID — requires SDK v4.5.2+ (see [details](#understanding-originalappuserid)) | -| `store` | string | Store: `APP_STORE`, `PLAY_STORE`, `STRIPE`, or `PADDLE` (see note below) | +| `store` | string | Store: `APP_STORE`, `PLAY_STORE`, or `STRIPE` (see note below) | | `purchasedAt` | number | Purchase timestamp (milliseconds) | | `currencyCode` | string | ISO currency code for priceInPurchasedCurrency | | `productId` | string | Product identifier | @@ -107,7 +107,7 @@ The `data` field contains detailed information about the subscription or payment | `expirationReason` | string (optional) | Reason for expiration (see [Cancel Reasons](#cancelexpiration-reasons)) | | `checkoutContext` | object (optional) | Stripe-specific checkout context | -**Note on Store field:** iOS and Android apps can receive events from any payment provider. For example, an iOS app can receive `STRIPE` or `PADDLE` events when users purchase through Superwall's App2Web features, which allow web-based checkout flows within mobile apps. The `store` field indicates where the payment was processed, not which platform the app runs on. +**Note on Store field:** iOS and Android apps can receive events from any payment provider. For example, an iOS app can receive `STRIPE` events when users purchase through Superwall's App2Web features, which allow web-based checkout flows within mobile apps. The `store` field indicates where the payment was processed, not which platform the app runs on. ## Event Names @@ -133,12 +133,11 @@ The `data` field contains detailed information about the subscription or payment ## Stores -| Store | Value | Description | -| ---------- | ------------ | ----------------------------- | -| App Store | `APP_STORE` | Apple App Store | -| Play Store | `PLAY_STORE` | Google Play Store | -| Stripe | `STRIPE` | Stripe payments | -| Paddle | `PADDLE` | Paddle payments (coming soon) | +| Store | Value | Description | +| ---------- | ------------ | ----------------- | +| App Store | `APP_STORE` | Apple App Store | +| Play Store | `PLAY_STORE` | Google Play Store | +| Stripe | `STRIPE` | Stripe payments | ## Environments @@ -482,7 +481,6 @@ Revenue events (initial_purchase, renewal, non_renewing_purchase) typically have | APP_STORE | ✅ Supported | Rarely used (1.3% of events), typically for win-back campaigns | | PLAY_STORE | ✅ Supported | Heavily used (72.1% of events), complex promotional system | | STRIPE | ❌ Not supported | Offer codes not available in webhook data | -| PADDLE | 🔜 Coming soon | Support planned | ### Environment Field @@ -499,19 +497,19 @@ Not all events are available for all stores. This table shows which events you c ### Event Support by Store -| Event Name | APP_STORE | PLAY_STORE | STRIPE | PADDLE | -| ----------------------- | --------- | ---------- | ------ | ------ | -| `billing_issue` | ✅ | ✅ | ✅ | 🔜 | -| `cancellation` | ✅ | ✅ | ✅ | 🔜 | -| `expiration` | ✅ | ✅ | ✅ | 🔜 | -| `initial_purchase` | ✅ | ✅ | ✅ | 🔜 | -| `non_renewing_purchase` | ✅ | ✅ | ❌ | 🔜 | -| `product_change` | ✅ | ✅ | ❌ | 🔜 | -| `renewal` | ✅ | ✅ | ✅ | 🔜 | -| `subscription_paused` | ❌ | ✅ | ❌ | 🔜 | -| `uncancellation` | ✅ | ✅ | ✅ | 🔜 | +| Event Name | APP_STORE | PLAY_STORE | STRIPE | +| ----------------------- | --------- | ---------- | ------ | +| `billing_issue` | ✅ | ✅ | ✅ | +| `cancellation` | ✅ | ✅ | ✅ | +| `expiration` | ✅ | ✅ | ✅ | +| `initial_purchase` | ✅ | ✅ | ✅ | +| `non_renewing_purchase` | ✅ | ✅ | ❌ | +| `product_change` | ✅ | ✅ | ❌ | +| `renewal` | ✅ | ✅ | ✅ | +| `subscription_paused` | ❌ | ✅ | ❌ | +| `uncancellation` | ✅ | ✅ | ✅ | -✅ = Supported | ❌ = Not supported | 🔜 = Coming soon +✅ = Supported | ❌ = Not supported ### Period Type Availability by Store @@ -536,9 +534,6 @@ Different stores support different period types for events: - `expiration` and `renewal` only occur with NORMAL period type - Does not support `non_renewing_purchase` or `product_change` events -#### PADDLE - -- Coming soon - full support planned! ### Store-Specific Considerations @@ -589,7 +584,7 @@ The `originalAppUserId` field represents the first app user ID associated with a ### Understanding originalTransactionId -The `originalTransactionId` is Apple's terminology that acts like a subscription ID. For simplicity and consistency with iOS and other revenue tracking platforms, we use this nomenclature and populate it accordingly for all platforms (Play Store, Stripe, Paddle, etc.). +The `originalTransactionId` is Apple's terminology that acts like a subscription ID. For simplicity and consistency with iOS and other revenue tracking platforms, we use this nomenclature and populate it accordingly for all platforms (Play Store, Stripe, etc.). - **One per subscription group**: Each user subscription gets one `originalTransactionId` - **Persists across renewals**: The same `originalTransactionId` is used for all renewals in that subscription diff --git a/content/docs/integrations/meta.json b/content/docs/integrations/meta.json index 2c69a544..9f205291 100644 --- a/content/docs/integrations/meta.json +++ b/content/docs/integrations/meta.json @@ -7,11 +7,16 @@ "---Integrations---", "webhooks", "apple-search-ads", + "facebook-pixel", "mixpanel", + "adjust", "amplitude", + "posthog", + "customer-io", "firebase", "statsig", "slack", + "discord", "figma-plugin" ] } \ No newline at end of file diff --git a/content/docs/integrations/webhooks/index.mdx b/content/docs/integrations/webhooks/index.mdx index 9dc59990..8740749a 100644 --- a/content/docs/integrations/webhooks/index.mdx +++ b/content/docs/integrations/webhooks/index.mdx @@ -95,7 +95,7 @@ The `data` field contains detailed information about the subscription or payment | `transactionId` | string | Current transaction ID | | `originalTransactionId` | string | Original transaction ID (subscription ID) | | `originalAppUserId` | string or null | Original app user ID — requires SDK v4.5.2+ (see [details](#understanding-originalappuserid)) | -| `store` | string | Store: `APP_STORE`, `PLAY_STORE`, `STRIPE`, or `PADDLE` (see note below) | +| `store` | string | Store: `APP_STORE`, `PLAY_STORE`, or `STRIPE` (see note below) | | `purchasedAt` | number | Purchase timestamp (milliseconds) | | `currencyCode` | string | ISO currency code for priceInPurchasedCurrency | | `productId` | string | Product identifier | @@ -108,7 +108,7 @@ The `data` field contains detailed information about the subscription or payment | `checkoutContext` | object (optional) | Stripe-specific checkout context | | `userAttributes` | object (optional) | Custom user attributes set via the SDK (see [User Attributes](#user-attributes)) | -**Note on Store field:** iOS and Android apps can receive events from any payment provider. For example, an iOS app can receive `STRIPE` or `PADDLE` events when users purchase through Superwall's App2Web features, which allow web-based checkout flows within mobile apps. The `store` field indicates where the payment was processed, not which platform the app runs on. +**Note on Store field:** iOS and Android apps can receive events from any payment provider. For example, an iOS app can receive `STRIPE` events when users purchase through Superwall's App2Web features, which allow web-based checkout flows within mobile apps. The `store` field indicates where the payment was processed, not which platform the app runs on. ## Event Names @@ -134,12 +134,11 @@ The `data` field contains detailed information about the subscription or payment ## Stores -| Store | Value | Description | -| ---------- | ------------ | ----------------------------- | -| App Store | `APP_STORE` | Apple App Store | -| Play Store | `PLAY_STORE` | Google Play Store | -| Stripe | `STRIPE` | Stripe payments | -| Paddle | `PADDLE` | Paddle payments (coming soon) | +| Store | Value | Description | +| ---------- | ------------ | ----------------- | +| App Store | `APP_STORE` | Apple App Store | +| Play Store | `PLAY_STORE` | Google Play Store | +| Stripe | `STRIPE` | Stripe payments | ## Environments @@ -489,7 +488,6 @@ Revenue events (initial_purchase, renewal, non_renewing_purchase) typically have | APP_STORE | ✅ Supported | Rarely used (1.3% of events), typically for win-back campaigns | | PLAY_STORE | ✅ Supported | Heavily used (72.1% of events), complex promotional system | | STRIPE | ❌ Not supported | Offer codes not available in webhook data | -| PADDLE | 🔜 Coming soon | Support planned | ### Environment Field @@ -506,19 +504,19 @@ Not all events are available for all stores. This table shows which events you c ### Event Support by Store -| Event Name | APP_STORE | PLAY_STORE | STRIPE | PADDLE | -| ----------------------- | --------- | ---------- | ------ | ------ | -| `billing_issue` | ✅ | ✅ | ✅ | 🔜 | -| `cancellation` | ✅ | ✅ | ✅ | 🔜 | -| `expiration` | ✅ | ✅ | ✅ | 🔜 | -| `initial_purchase` | ✅ | ✅ | ✅ | 🔜 | -| `non_renewing_purchase` | ✅ | ✅ | ❌ | 🔜 | -| `product_change` | ✅ | ✅ | ❌ | 🔜 | -| `renewal` | ✅ | ✅ | ✅ | 🔜 | -| `subscription_paused` | ❌ | ✅ | ❌ | 🔜 | -| `uncancellation` | ✅ | ✅ | ✅ | 🔜 | +| Event Name | APP_STORE | PLAY_STORE | STRIPE | +| ----------------------- | --------- | ---------- | ------ | +| `billing_issue` | ✅ | ✅ | ✅ | +| `cancellation` | ✅ | ✅ | ✅ | +| `expiration` | ✅ | ✅ | ✅ | +| `initial_purchase` | ✅ | ✅ | ✅ | +| `non_renewing_purchase` | ✅ | ✅ | ❌ | +| `product_change` | ✅ | ✅ | ❌ | +| `renewal` | ✅ | ✅ | ✅ | +| `subscription_paused` | ❌ | ✅ | ❌ | +| `uncancellation` | ✅ | ✅ | ✅ | -✅ = Supported | ❌ = Not supported | 🔜 = Coming soon +✅ = Supported | ❌ = Not supported ### Period Type Availability by Store @@ -543,9 +541,6 @@ Different stores support different period types for events: - `expiration` and `renewal` only occur with NORMAL period type - Does not support `non_renewing_purchase` or `product_change` events -#### PADDLE - -- Coming soon - full support planned! ### Store-Specific Considerations @@ -596,7 +591,7 @@ The `originalAppUserId` field represents the first app user ID associated with a ### Understanding originalTransactionId -The `originalTransactionId` is Apple's terminology that acts like a subscription ID. For simplicity and consistency with iOS and other revenue tracking platforms, we use this nomenclature and populate it accordingly for all platforms (Play Store, Stripe, Paddle, etc.). +The `originalTransactionId` is Apple's terminology that acts like a subscription ID. For simplicity and consistency with iOS and other revenue tracking platforms, we use this nomenclature and populate it accordingly for all platforms (Play Store, Stripe, etc.). - **One per subscription group**: Each user subscription gets one `originalTransactionId` - **Persists across renewals**: The same `originalTransactionId` is used for all renewals in that subscription diff --git a/content/docs/ios/changelog.mdx b/content/docs/ios/changelog.mdx index 0c320daf..6ef17b1b 100644 --- a/content/docs/ios/changelog.mdx +++ b/content/docs/ios/changelog.mdx @@ -3,1646 +3,203 @@ title: "Changelog" description: "Release notes for the Superwall iOS SDK" --- -## 4.12.0 - -### Enhancements - -- Adds `paywallPreload_start` and `paywallPreload_complete` events. -- Adds `request permission` action support allowing you to request notification, location, photos, contacts, and camera permissions from paywalls. -- Improves drawer presentation style corner rounding by applying the device radius on bottom corners. - -### Fixes - -- Updates Superscript version to 1.0.12. This fixes an issue with `appVersionPadded` comparison. View the original Rust release changelog [here](https://github.com/superwall/superscript/releases/tag/1.0.12). - -## 4.10.8 - -### Enhancements - -- Adds support for `Set user attributes` action. -- Adds new `SuperwallDelegate` method called `userAttributesDidChange` that notifies you when user attributes change from an external source. -- Adds `firebaseInstallationId` as an `IntegrationAttribute`. - -### Fixes - -- Fixes a crash caused by a race condition when accessing JSON dictionaries concurrently. -- Fixes issue returning the `PurchaseResult` from `Superwall.shared.purchase(_:)` when using StoreKit 1 inside a `PurchaseController`. -- Fixes `handleDeepLink` returning true for non-Superwall URLs when called before configuration completes. - -## 4.10.6 - -### Fixes - -- Fixes issue that prevented the SDK from being built on old Xcode versions. - -## 4.10.5 - -### Fixes - -- Updates `device.isApplePayAvailable` for more accurate filtering. Previously it returned true whenever the device supported Apple Pay, even if no card was added. It now returns true only when the device supports Apple Pay and the user has added a card. -- Fixes issue where `didRedeemLink` might not get called if there's no paywall available to present an alert from. - -## 4.10.4 - -### Fixes - -- Updates Superscript version to 1.0.10. This fixes an issue with namespacing in cocoapods. View the original Rust release changelog [here](https://github.com/superwall/superscript/releases/tag/1.0.10). -- Fixes some issues building for visionOS. - -## 4.10.3 - -### Fixes - -- Fixes issue where `Superwall.shared.confirmAllAssignments()` would be return an empty `Set` if config hadn't been retrieved. - -## 4.10.1 - -### Fixes - -- Fixes issue where `willRedeemLink` might get called twice during the web checkout payment sheet flow. -- Fixes issue where paywall might get dismissed prematurely during web checkout. -- Fixes issue where the spinner on the paywall wasn't showing for a few seconds after the system closed the web checkout payment sheet due to a successful purchase. - -## 4.10.0 - -### Enhancements - -- Adds `CustomerInfo`. This contains the latest information about all of the customer's purchase and subscription data. This can be accessed via the published property `Superwall.shared.customerInfo`, via `Superwall.shared.getCustomerInfo()`, via the `AsyncStream` `customerInfoStream`, or via the delegate method `customerInfoDidChange(from:to:)`. This updates the `Entitlement` object to have more properties such as `startsAt` and `expiredAt`. These can be used in audience filters. -- Adds `Superwall.shared.entitlements.byProductIds(_:)` to return a `Set` of `Entitlement` objects belonging to a given set of product identifiers. -- Changes the `PurchaseController` examples to account for `CustomerInfo` changes. -- Adds `transaction_abandon` capability to web checkout payment sheet. - -### Fixes - -- Fixes issue after purchasing web products where localized strings weren't correct in SDK wrappers like Expo. - -## 4.9.3 - -### Enhancements - -- Zero second delay when presenting paywalls after calling `Superwall.configure` if 1. the user is subscribed, and 2. there is a cached configuration. - -### Fixes - -- Allowed paywall webviews to refresh if they were terminated by the system. This typically happened in Expo apps if very large photos were used in the paywall. - -## 4.9.2 - -### Fixes - -- Fixes delay in paywall presentation if there's an issue retrieving web entitlements. - -## 4.9.1 - -### Fixes - -- Fixes a rare issue where calling reset while the SDK was still retrieving its configuration could occasionally prevent paywalls from appearing. -- Fixes positioning issue for surveys in iOS 26. - -## 4.9.0 - -### Enhancements - -- Adds ability to open the web checkout page in a payment sheet style web view. -- Updates Superscript version to 1.0.4. View the original Rust release changelog [here](https://github.com/superwall/superscript/releases/tag/1.0.4). -- Adds the `SuperwallOption` `shouldBypassAppTransactionCheck`, which allows you to opt out of `AppTransaction.shared` usage during SDK initialization. This is useful in testing environments to avoid triggering the Apple ID sign-in prompt. -- Adds `device.isApplePayAvailable` to the device attributes that can be used in audience filters. -- Adds `onWillDismiss` to the `PaywallPresentationHandler`, which is called when the paywall will dismiss. - -### Enhancements - -- Updates Superscript version to 1.0.4. View the original Rust release changelog [here](https://github.com/superwall/superscript/releases/tag/1.0.4). - -### Fixes - -- Changes "With/Without Free Trial" to "With/Without Intro Offer" in the debugger. -- Fixes rare crash caused by a concurrency issue. -- Fixes issue where no internet would cause a minute delay for paywall presentation. - -## 4.8.3 - -### Enhancements - -- Adds support for redeeming web entitlements with Paddle. - -## 4.8.2 - -### Enhancements - -- Adds `review_requested` event when a review is requested. - -## 4.8.1 - -### Enhancements - -- Adds ability to specify a custom height and corner radius for the drawer presentation style. -- Adds ability to grant an entitlement to anyone. -- Adds `Superwall.shared.setIntegrationAttributes(_:)` which allows you to set attributes for third-party integrations. -- Adds `Superwall.shared.setIntegrationAttribute(_:_:)` for setting individual integration attributes. -- Adds `Superwall.shared.integrationAttributes` to get the attributes you've set. -- Adds the ability to ask for an App Store review from a paywall tap action. -- Adds a popup presentation style. -- Adds product retrying if StoreKit 2 encounters an error while fetching products. -- Tracks a `paywallProductsLoad_missingProducts` event if the products were missing. - -### Fixes - -- Fixes issue with tracking `demandScore` and `demandTier` on paywall open. -- Fixes a rare crash due to memory allocation issues. -- Fixes a rare crash due to a race condition during data processing. -- Fixes issue where weekly StoreKit 2 products might have the wrong daily price. - -## 4.7.0 - -### Enhancements - -- Adds `placementsInX` which can be used in audience filters. This means you can make a filter that will only fire if a placement has been fired X times in the past hour/day/week/year/since install. -- Updates Superscript version to 1.0.2. View the original Rust release changelog [here](https://github.com/superwall/superscript/releases/tag/1.0.2). -- Adds `swiftVersion` and `compilerVersion` to the device attributes. - -### Fixes - -- Makes sure web entitlements are always redeemed the first time the app loads from a cold start. -- Fixes issue where the Superwall config wasn't timing out and falling back to the cached config after 1 second. - -## 4.6.0 - -### Enhancements - -- Adds the `PaywallOption` `overrideProductsByName`, which can be used to globally override products on any paywall that have a given name. This can also be set after configure has been called by setting `Superwall.shared.overrideProductsByName`. -- Adds the `PaywallOption` `shouldShowWebPurchaseConfirmationAlert`, which shows a localized alert confirming a successful purchase via web checkout. Defaults to `true`. - -### Fixes - -- Fixes issue where deep links passed to the SDK before configure completes aren’t handled after configure finishes. - -## 4.5.2 - -### Fixes - -- Replace `UIApplication.shared` with `sharedApplication` accessed via KVC so that SuperwallKit can be used in app extensions. -- Fixes issue where the paywall debugger would crash when viewing template variables if products weren't loaded. -- Fixes issue where an in-app web checkout wouldn't close Safari after purchase. - -## 4.5.1 - -### Fixes - -- Fixes issue where `webViewLoad_fail` events weren't being tracked. - -## 4.5.0 - -### Enhancements - -- Adds `handleSuperwallDeepLink(_:pathComponents:queryParameters:)` to the `SuperwallDelegate`. This is called when all deep links from the web checkout are handled. This link may arrive as either a universal link (`https://yoursubdomain.superwall.app/app-link/...`) or a custom URL scheme (`subdomain://yoursubdomain.superwall.app/app-link/...`). -- Adds `url`, `path`, `pathExtension`, `lastPathComponent`, `host`, `query`, and `fragment` to the `deepLink_open` event, which you can use in audience filters. - -### Fixes - -- Fixes a race condition when identifying and then immediately getting user attributes. -- Removes usage of private API `LSApplicationWorkspace`. - -## 4.4.2 - -### Enhancements - -- Updates Superscript to 0.2.8. - -### Fixes - -- Fix for old versions of Xcode not building due to not supporting `.winBack` transaction offer types. -- Fixes an issue where the paywall could be presented from the wrong scene in multi-window apps. - -## 4.4.1 - -### Fixes - -- Fixes race condition when initialising the SDK that could result in a crash. - -## 4.4.0 - -### Enhancements - -- Adds `Superwall.shared.getDeviceAttributes()`, which returns the device attributes that are used when evaluating audience filters. - -## 4.3.11 - -### Fixes - -- Fixes an issue when serialising some device attributes in the flutter SDK. - -## 4.3.10 - -### Enhancements - -- Adds `networkDecoding_fail` event to help with debugging if a decoding error happens. -- Adds `state` to the `PaywallInfo` object. This is set on dismiss of the paywall and can be used to access state variables set in the editor. - -### Fixes - -- Fixes issue where the configuration completion block could take a long time to complete if the user had a lot of transactions. -- Fixes issue where `didDismissPaywall(withInfo:)` and the `onDismiss` paywall presentation handler would be called before the presenting window was destroyed. - -## 4.3.9 - -### Fixes - -- Fixes issue with a paywall not closing if you had a survey attached to a paywall that fires after purchasing and a `survey_response` implicit trigger. - -## 4.3.8 - -### Fixes - -- Fixes build issue with VisionOS. - -## 4.3.7 - -### Enhancements - -- Adds `storeFrontCountryCode`, `storeFrontCurrency`, and `storeFrontId` to the device variables. These can be used to display web checkout paywalls exclusively to those in the USA. -- Adds support for redeeming a code via a universal link. -- Adds `code` and `type` to redemption events to improve debugging. - -### Fixes - -- Ensures that the UUID from the automatically assigned Superwall alias is passed as the `appAccountToken` for purchases when `identify(userId:)` has not been called. - -## 4.3.5 - -### Fixes - -- Fixes issue where `Superwall.shared.dismiss()` wouldn't work as expected if the in-app browser was open. - -## 4.3.4 - -### Enhancements - -- Sends the campaign details to the paywall. - -## 4.3.3 - -### Fixes - -- Prevents the blocking of paywall presentation if the enrichment request fails. -- Fixes an issue where an app extension accessing Superwall wouldn't work. -- Sometimes transactions weren't being retrieved by our SDK after a purchase so we've made this process more robust. -- Fixes issue with compiler failing to build for CocoaPods on older Xcode versions. - -## 4.3.0 - -### Enhancements - -- Adds a `SuperwallOption` named `enableExperimentalDeviceVariables`. When set to `true`, this enables additional device-level variables: `latestSubscriptionPeriodType`, `latestSubscriptionState`, and `latestSubscriptionWillAutoRenew`. These properties provide information about the most recent StoreKit 2 subscription on the device and can be used in audience filters. Note that due to their experimental nature, they are subject to change in future updates. - -## 4.2.2 - -### Fixes - -- Fixes an issue where computed properties used in audience filters (e.g. "minutes since") weren’t being properly encoded. This could lead to incorrect behavior when falling back to the cached config after a network issue. - -## 4.2.1 - -### Enhancements - -- Adds the `PaywallOption` `shouldShowWebRestorationAlert`, which can be set to `false` to suppress the alert prompting users to restore their purchase via the web. - -### Fixes - -- Fixes issue where the redeeming of web entitlements may have been incorrectly counted as a restoration. -- Fixes issue where the web checkout redemption endpoint was being called on identify even if you hadn't enabled web checkout. - -## 4.2.0 - -### Enhancements - -- Adds `demandScore` and `demandTier` to device attributes using an off-device advanced machine learning model. A user is assigned these based on a variety of factors to determine whether they're more or less likely to convert and can be used within audience filters. -- Adds the static method `Superwall.handleDeepLink(_:)` and deprecates the instance method `Superwall.shared.handleDeepLink(_:)`. Now if the deep link gets called before `configure` is called, we store the deep link and handle it after configuring. -- Adds support for web checkout. -- Updates Superscript to 0.2.4. - -### Fixes - -- Fixes a timeout issue for the loading of the Superwall config when the cached config feature flag is enabled. - -## 4.2.0-beta.1 - -### Enhancements - -- Adds `demandScore` and `demandTier` to device attributes using an off-device advanced machine learning model. A user is assigned these based on a variety of factors to determine whether they're more or less likely to convert and can be used within audience filters. -- Updates Superscript to 0.2.4. - -## 4.1.0-beta.6 - -### Breaking Changes - -- Renames `ExpiredInfo` to `ExpiredCodeInfo` to be more explicit. -- Renames `codeExpired` `RedemptionResult` case to `expiredCode`. - -### Enhancements - -- Adds Objective-C support for web checkout. - -## 4.1.0-beta.5 - -### Enhancements - -- Adds the static method `Superwall.handleDeepLink(_:)` and deprecates the instance method `Superwall.shared.handleDeepLink(_:)`. Now if the deep link gets called before `configure` is called, we store the deep link and handle it after configuring. - -## 4.1.0-beta.3 - -### Fixes - -- Updates the SuperwallDelegate `didRedeemCode(code:)` example code for RevenueCat web checkout. -- Fixes decoding error for an expired code. -- Prevents `willRedeem()` from being called on `identify`. -- Makes sure `subscriptionStatus` is set on the main thread when redeeming codes. - -## 4.1.0-beta.2 - -### Breaking Changes - -- Adds Stripe subscription IDs to the `stripe` `StoreIdentifier` case. -- Changes `codeExpired(code: String, expired: ExpiredInfo)` `RedemptionResult` case to `codeExpired(code: String, expiredInfo: ExpiredInfo)`. - -### Enhancements - -- Adds `willRedeemCode` to the `SuperwallDelegate` to indicate that a code redemption is about to happen. -- Adds `stripeSubscriptionIds` convenience variable to the `RedemptionResult` for quick access. - -### Fixes - -- Adjusts `RCPurchaseController` example and submits the stripe subscription ID from the `didRedeemCode(result:)` delegate method to the RevenueCat API. -- Makes sure to present alert on web entitlement restoration failure. - -## 4.1.0-beta.1 - -### Enhancements - -- Adds support for web checkout. - -## 4.0.6 - -### Fixes - -- Prevents all overloads of `Superwall.shared.purchase(_:)` from being called when the `shouldObservePurchases` `SuperwallOption` is set to `true`. - -## 4.0.5 - -### Fixes - -- Fixes a visionOS build-time issue. - -## 4.0.4 - -### Fixes - -- Adds in missing `appAccountToken` for StoreKit 2 purchases. - -## 4.0.3 - -### Enhancements - -- Adds `$storekitVersion`, `$maxConfigRetryCount`, and `$shouldObservePurchases` to the `config_attributes` event. -- Updates Superscript to 0.1.18. -- Confirms all paywall assignments locally to reduce the amount of preloading of paywalls on each cold app open. -- Migrates documents used for user and app data out of the documents folder and into the application support folder. -- If the SDK is using StoreKit 2 and not using a purchase controller, a refunded purchase is no longer considered active and therefore does not give the user an active entitlement. - -### Fixes - -- Deprecates the naming of `handleSuperwallPlacement(withInfo:)` back to `handleSuperwallEvent(withInfo:)`. -- Deprecates `SuperwallPlacement` back to `SuperwallEvent`. -- Deprecates `SuperwallPlacementInfo` back to `SuperwallEventInfo`. - -## 4.0.1 - -### Fixes - -- Fixes a bug where unsupported value types as user attributes or placement parameters would cause a `noAudienceMatch` - -## 4.0.0 - -### Breaking Changes - -- Removes `trigger_session_id` from `PaywallInfo` params. -- `ProductInfo` is renamed to `Product` and the old `Product` class no longer exists. -- Renames `subscriptionStatusDidChange(to:)` to `subscriptionStatusDidChange(to:from:)` in the `SuperwallDelegate`. -- Renames `productItems` to `products` in `PaywallInfo`. -- Renames `register(event:)` to `register(placement:)`. -- Renames `preloadPaywalls(forEvents:)` to `preloadPaywalls(forPlacements:)`. -- Renames `PaywallView(event:)` to `PaywallView(placement:)`. -- Renames `getPaywall(forEvent:)` to `getPaywall(forPlacement:)`. -- Renames `getPresentationResult(forEvent:)` to `getPresentationResult(forPlacement:)`. -- Renames the `TriggerResult`, `PresentationResult` and `PaywallSkippedReason` `eventNotFound` case to `placementNotFound` and `noEventMatch` to `noAudienceMatch`. -- Renames `handleSuperwallEvent(withInfo:)` to `handleSuperwallPlacement(withInfo:). -- Moves `ComputedPropertyRequestType` to be a top-level type. -- Renames `Store` to `ProductStore`. -- Removes `Superwall.shared.isConfigured` in favor of `Superwall.shared.configurationStatus`. -- Defaults to StoreKit 2 for product purchasing for apps running on iOS 15+. You can change this back to StoreKit 1 by setting the `SuperwallOption` `storeKitVersion` to `.storeKit1`. When using Objective-C and providing a PurchaseController or using observer mode, the SDK will default to `.storeKit1`. If you're using Objective-C and using `purchase(_:)`, you must use `.storeKit1`. -- Changes the `PurchaseController` purchase function to `func purchase(product: StoreProduct) async -> PurchaseResult`. There will be an StoreKit 2 product accessible via `product.sk2Product` by default. However, if you're using the StoreKit 1 `SuperwallOption` or your app is running on an iOS version lower than iOS 15, this will be `nil` and you can access the StoreKit 1 product via `product.sk1Product`. -- Consumables no longer count as lifetime subscriptions when using StoreKit 2. -- Renames the `PurchaseResult` case `purchased(productId: String)` to `purchased(Product)`. -- Changes the Swift `onDismiss` block of the `PaywallPresentationHandler` to accept both a `PaywallInfo` object and a `PaywallResult` object so you know which product was purchased after dismiss. -- Changes the `onRequestDismiss` block of the `PaywallView` to accept both a `PaywallInfo` object and a `PaywallResult` object. -- Changes the Objective-C `onDismiss` block of the `PaywallPresentationHandler` to accept both a `PaywallInfo` object, a `PaywallResult` object, and an optional `StoreProduct`, so you know which product was purchased after dismiss. -- Renames `LogScope` case `paywallTransactions` to `transactions`. -- Adds `type` to the `transactionComplete` placement. -- Removes the `restored` result from `PurchaseResult`. - -### Enhancements - -- Adds `purchase(_:)` support for both StoreKit 2 products and `StoreProduct`. -- Adds `Superwall.shared.subscriptionStatus.isActive` as a convenience variable. -- Adds entitlements as associated values to the `active` case of `Superwall.shared.subscriptionStatus`. If you're not using a `PurchaseController`, we will handle the entitlements for you depending on what products the user purchases. -- Adds `Superwall.shared.entitlements` which has the following properties: `all`, `active` and `inactive`. -- Adds `setUnknownSubscriptionStatus()`, `setInactiveSubscriptionStatus()`, and `setActiveSubscriptionStatus(with:)` for Objective-C users. -- Updates the example apps. We now have Basic and Advanced. Basic is a simple plug-and-play superwall setup that doesn't use entitlements. Advanced uses entitlements and has three possible ways of configuring Superwall: 1. Letting Superwall manage everything, 2. Using a purchase controller with StoreKit, 2. Using a purchase controller with RevenueCat. -- Uses `Superscript` for all audience filter evaluations. This is our in-house package that uses Google's Common Expression Language to evaluate audience filters. It allows for complex expressions within the audience filter builder. -- Adds StoreKit 2 observer mode. This can be enabled by setting the `SuperwallOptions` `shouldObservePurchases` to `true` and `storeKitVersion` to `.storeKit2` (which is the default value). Note that this is only available with apps running iOS 17.2+. -- Adds `products(for:)` which gets the ``StoreProduct`s for given product identifiers. - -Please see our [migration guide](https://superwall.com/docs/migrating-to-v4) and docs for a full breakdown of what's new. - -## 4.0.0-beta.7 - -### Fixes - -- Fixes an SK1 swift continuation leak when purchasing. - -## 4.0.0-beta.6 - -### Breaking Changes - -- Removes the `restored` result from `PurchaseResult`. - -## 4.0.0-beta.5 - -### Breaking Changes - -- Replaces `entitlements.status` with `subscriptionStatus`. -- `entitlementStatusDidChange` reverted to `subscriptionStatusDidChange`. -- `EntitlementStatus` removed in favor of `SubscriptionStatus`. - -### Enhancements - -- Adds `Superwall.shared.subscriptionStatus.isActive` as a convenience variable. - -## 4.0.0-beta.4 - -### Fixes - -- Fixes a crash that was caused by a concurrency issue. - -## 4.0.0-beta.3 - -### Breaking Changes - -- Renames `PaywallView(event:)` to `PaywallView(placement:)`. - -### Fixes - -- Adds extra check to get StoreKit 2 transaction data on `transaction_complete`. - -## 4.0.0-beta.2 - -### Fixes - -- Fixes an issue to do with audience filters. -- Re-adds unavailable functions from v3 to make the upgrade path smoother. - -## 4.0.0-beta.1 - -### Fixes - -- Removes date checking for transactions, which was resulting in some purchases being marked as restored. -- Checks for the `SKIncludeConsumableInAppPurchaseHistory` info.plist key. If set to `true`, defaults to using `.storeKit2` only if on iOS 18+. -- Fixes visionOS issues. - -## 4.0.0-alpha.4 - -### Fixes - -- Fixes bug for StoreKit version specified in a `transaction_complete` event. -- Reverts change of `presented_by_placement_name` to `presented_by_event_name` to fix campaign charts. - -## 4.0.0-alpha.3 - -### Enhancements - -- Adds the obsoleted attribute to more variants of `register(event:)` for a smoother upgrade path. - -### Fixes - -- Fixes issue with optional audience filter properties inside `PaywallInfo`. - -## 4.0.0-alpha.2 - -### Breaking Changes - -- Removes `trigger_session_id` from `PaywallInfo` params. -- `ProductInfo` is renamed to `Product` and the old `Product` class no longer exists. -- Removes `Superwall.shared.subscriptionStatus` in favor of entitlements. -- Removes `subscriptionStatus_didChange`. -- Removes `subscriptionStatusDidChange(to:)` from the `SuperwallDelegate`. -- Renames `productItems` to `products` in `PaywallInfo`. -- Renames `register(event:)` to `register(placement:)`. -- Renames `preloadPaywalls(forEvents:)` to `preloadPaywalls(forPlacements:)`. -- Renames `getPaywall(forEvent:)` to `getPaywall(forPlacement:)`. -- Renames `getPresentationResult(forEvent:)` to `getPresentationResult(forPlacement:)`. -- Renames the `TriggerResult`, `PresentationResult` and `PaywallSkippedReason` `eventNotFound` case to `placementNotFound` and `noEventMatch` to `noAudienceMatch`. -- Renames `handleSuperwallEvent(withInfo:)` to `handleSuperwallPlacement(withInfo:). -- Moves `ComputedPropertyRequestType` to be a top-level type. -- Renames `Store` to `ProductStore`. -- Removes `Superwall.shared.isConfigured` in favor of `Superwall.shared.configurationStatus`. -- Defaults to StoreKit 2 for product purchasing for apps running on iOS 15+. You can change this back to StoreKit 1 by setting the `SuperwallOption` `storeKitVersion` to `.storeKit1`. When using Objective-C and providing a PurchaseController or using observer mode, the SDK will default to `.storeKit1`. If you're using Objective-C and using `purchase(_:)`, you must use `.storeKit1`. -- Changes the `PurchaseController` purchase function to `func purchase(product: StoreProduct) async -> PurchaseResult`. There will be an StoreKit 2 product accessible via `product.sk2Product` by default. However, if you're using the StoreKit 1 `SuperwallOption` or your app is running on an iOS version lower than iOS 15, this will be `nil` and you can access the StoreKit 1 product via `product.sk1Product`. -- Consumables no longer count as lifetime subscriptions when using StoreKit 2. -- Renames the `PurchaseResult` case `purchased(productId: String)` to `purchased(Product)`. -- Changes the Swift `onDismiss` block of the `PaywallPresentationHandler` to accept both a `PaywallInfo` object and a `PaywallResult` object so you know which product was purchased after dismiss. -- Changes the `onRequestDismiss` block of the `PaywallView` to accept both a `PaywallInfo` object and a `PaywallResult` object. -- Changes the Objective-C `onDismiss` block of the `PaywallPresentationHandler` to accept both a `PaywallInfo` object, a `PaywallResult` object, and an optional `StoreProduct`, so you know which product was purchased after dismiss. -- Renames `LogScope` case `paywallTransactions` to `transactions`. -- Adds `type` to the `transactionComplete` placement. - -### Enhancements - -- Adds `purchase(_:)` support for both StoreKit 2 products and `StoreProduct`. -- Adds `Superwall.shared.entitlements`. This is a published property of type `EntitlementStatus`. If you're using Combine or SwiftUI, you can listen to this to receive updates whenever it changes. Otherwise, you can use the `SuperwallDelegate` method `entitlementStatusDidChange(from:to:)`. If you're not using a `PurchaseController`, we will handle the entitlements for you depending on what products the user purchases. However, if you're using a `PurchaseControler`, you can set the entitlement status, which can be `.unknown`, `.inactive`, or `.active(Set)`. You can also access `all`, `active` and `inactive` entitlements from the entitlements object. -- Adds `getStatus()`, `setUnknownStatus()`, `setInactiveStatus()`, and `setActiveStatus(with:)` on `Superwall.shared.entitlements` for Objective-C users. -- Tracks an `entitlementStatus_didChange` event whenever the entitlement status changes. -- Updates the example apps. We now have Basic and Advanced. Basic is a simple plug-and-play superwall setup that doesn't use entitlements. Advanced uses entitlements and has three possible ways of configuring Superwall: 1. Letting Superwall manage everything, 2. Using a purchase controller with StoreKit, 2. Using a purchase controller with RevenueCat. -- Uses `Superscript` for all audience filter evaluations. This is our in-house package that uses Google's Common Expression Language to evaluate audience filters. It allows for complex expressions within the audience filter builder. -- Adds the `LogScope` case `entitlements`. -- Adds StoreKit 2 observer mode. This can be enabled by setting the `SuperwallOptions` `shouldObservePurchases` to `true` and `storeKitVersion` to `.storeKit2` (which is the default value). Note that this is only available with apps running iOS 17.2+. -- Adds `products(for:)` which gets the ``StoreProduct`s for given product identifiers. - -## 3.12.5 - -### Fixes - -- Fixes a rare crash to do with a swift continuation leak when purchasing. - -## 3.12.4 - -### Fixes - -- Simplifies and corrects logic for choosing paywall variants. - -## 3.12.3 - -### Fixes - -- Fixes an issue where trying to purchase a product that was previously purchased may prevent the spinner from disappearing on the paywall. - -## 3.12.2 - -### Fixes - -- Fixes visionOS build issues. - -## 3.12.1 - -### Fixes - -- Fixes issue for flutter when enabling Apple Search Ads on the Dashboard. This is done by cleaning all user attributes such that only those that are JSON serializable are stored. -- Removes date checking for transactions, which was resulting in some purchases being marked as restored. - -## 3.12.0 - -### Enhancements - -- Adds the `SuperwallOption` `shouldObservePurchases`. Set this to `true` to allow us to observe StoreKit 1 transactions you make with your app outside of Superwall. When this is enabled Superwall will not finish your external transactions. StoreKit 2 will be widely supported in the next major version of our SDK. -- Adds Apple Search Ads attribution data to user attributes, which is visible on the user's page in Superwall. Attribution data will be collected if you have enabled Basic or Advanced Apple Search Ads in the Superwall dashboard settings. Advanced attribution data includes the keyword name, campaign name, bid amount, match type, and more. Otherwise, the basic attribution data will be collected, which is mostly IDs. This data will soon be added to Charts. -- Adds `isSubscribed` to product attributes so that you can use `products.primary.isSubscribed` as a dynamic value in the paywall editor. -- Adds `device.appVersionPadded` to the device properties that you can use in audience filters. -- Adds a `notificationPermissionsDenied` `PaywallOption`, which you can set to show an alert after a user denies notification permissions. - -### Fixes - -- Fixes issue where network requests that returned an error code weren't being retried. -- Fixes date formatting on a device property. - -## 3.11.3 - -### Enhancements - -- Updates Superscript to [0.1.16](https://github.com/superwall/Superscript-iOS/releases/tag/0.1.16). - -## 3.11.2 - -### Enhancements - -- Adds `shimmerView_start` and `shimmerView_complete` events. The `shimmerView_complete` event contains a `visible_duration` parameter which indicates how long the shimmer view was visible after paywall open, if at all. -- Adds `isScrollEnabled` to `PaywallInfo`, which indicates whether the webview should scroll or not. -- Updates Superscript to [v0.1.15](https://github.com/superwall/Superscript-iOS/releases/tag/0.1.15). -- Adds `$source`, `$store`, and `$storekit_version` to transaction events. - -### Fixes - -- Fixes issue where using a `PurchaseController` with `Superwall.shared.purchase(product)` was resulting in transaction events being tracked twice. -- Fixes build issues for visionOS, Mac Catalyst, and watchOS. - -## 3.11.1 - -### Fixes - -- Fixes an issue when getting the Superscript package. - -## 3.11.0 - -### Enhancements - -- Adds a `PaywallView` for SwiftUI users using iOS 14+. You can use this as a standalone paywall view that you can embed and present however you like instead of using `register`. This uses `getPaywall(forEvent:params:paywallOverrides:)` under the hood. Note that you're responsible for the deallocation of the view. If you have a `PaywallView` presented somewhere and you try to present the same `PaywallView` elsewhere, you will get a crash. -- Adds our `Superscript` package as a dependency. We are migrating towards using Google's Common Expression Language (CEL) in audience filters to allow for more complex expressions. The use of this is behind a feature flag. - -### Fixes - -- visionOS fixes. - -## 3.10.2 - -### Enhancements - -- Adds `maxConfigRetryCount` as a `SuperwallOption`. Use this to determine the number of times the SDK will attempt to get the Superwall configuration after a network failure before it times out. - -## 3.10.1 - -### Fixes - -- Tweaks logic for `purchase(_:)` and `restorePurchases()` so the SDK never finishes transactions made when there's a purchase controller present. -- Fixes internal caching issues of the Superwall config. - -## 3.10.0 - -### Enhancements - -- Adds `purchase(_:)` to initiate a purchase of an `SKProduct` via Superwall regardless of whether you are using paywalls or not. -- Adds `restorePurchases()` to restore purchases via Superwall. -- Adds an optional `paywall(_:loadingStateDidChange)` function to the `PaywallViewControllerDelegate`. This is called when the loading state of the presented `PaywallViewController` did change. -- Makes `loadingState` on the `PaywallViewController` a public published property. - -### Fixes - -- Tweaks AdServices token logic to prevent getting the token twice. - -## 3.9.1 - -### Fixes - -- Moves to collecting just the AdServices attribute token, which will be process by our backend. Adds `adServicesTokenRequest_start`, `adServicesTokenRequest_complete`, and `adServicesTokenRequest_fail`. - -## 3.9.0 - -### Enhancements - -- If a network issue occurs while retrieving the latest Superwall configuration, or it takes longer than 1s to retrieve, the SDK falls back to a cached version. Then it tries to refresh it in the background. This behavior is behind a feature flag. -- When the Superwall configuration is set or refreshed, a `config_refresh` event is tracked, which will give insight into whether a cached version of the Superwall configuration is being used or not. -- When the Superwall configuration fails to be retrieved, a `config_fail` event is tracked. -- Adds the `config_caching` capability. -- Adds the `SuperwallOption` `collectAdServicesAttribution`. When set to `true`, this will get the app-download campaign attributes associated with Apple Search Ads and attach them to the user attributes. This happens once per user per install. Calling `Superwall.shared.reset()` will fetch the attributes again and attach them to the new user. -- Adds`adServicesAttributionRequest_start`, `adServicesAttributionRequest_fail`, and `adServicesAttributionRequest_complete` events for the lifecycle of collecting AdServices attributes. - -### Fixes - -- Adds in missing `weak self` references inside task group closures. - -## 3.8.0 - -### Enhancements - -- Adds `Superwall.shared.confirmAllAssignments()`, which confirms assignments for all placements and returns an array of all confirmed experiment assignments. Note that the assignments may be different when a placement is registered due to changes in user, placement, or device parameters used in audience filters. -- Adds a published property `Superwall.shared.configurationStatus`, which replaces `isConfigured`. This is an enum which can either be `pending`, `configured`, or `failed`. - -### Fixes - -- Fixes `UIScreen unavailable in visionOS` error message in `PaywallViewController`. -- Fixes the error `Symbol not found: _$s10Foundation14NSDecimalRoundyySpySo0B0aG_SPyADGSiSo14NSRoundingModeVtF`, which is an Xcode 16 bug. - -## 3.7.4 - -### Fixes - -- Fixes rare crash caused by a Combine issue. -- Confirms the assigment to holdouts for implicit placements like `paywall_decline`. -- Tracks the `trigger_fire` event for implicit placements. - -## 3.7.3 - -### Fixes - -- Fixes issue with decoding custom placements from paywalls. - -## 3.7.2 - -### Fixes - -- Changes access level of a property used by our Flutter and React Native wrapper SDKs. - -## 3.7.1 - -### Enhancements - -- Adds a `custom_placement` event that you can attach to any element in the paywall with a dictionary of parameters. When the element is tapped, the event will be tracked. The name of the placement can be used to trigger a paywall and its params used in audience filters. -- Tracks a `config_attributes` event after calling `Superwall.configure`, which contains info about the configuration of the SDK. This gets tracked whenever you set the delegate. -- Adds in device attributes tracking after setting the interface style override. -- Adds `close_reason` to `PaywallInfo` properties. - -## 3.7.0 - -### Enhancements - -- Adds support for multiple paywall URLs, incase one CDN provider fails. -- Adds the ability for the SDK to refresh the Superwall configuration every session start, subject to a feature flag. This means the paywalls will be kept updated even if the app has been open for a long time in the background. -- Adds `build_id` and `cache_key` to `PaywallInfo`. -- Tracks a `config_refresh` Superwall event when the configuration is refreshed. -- Adds product retrying if we fail to fetch an `SKProduct`. This tracks a `paywallProductsLoad_retry` event whenever the product loading request gets retried. -- SW-2899: Adds `Superwall.shared.localeIdentifier` as a convenience variable that you can use to dynamically update the locale used for evaluating rules and getting localized paywalls. -- Adds feature flag to enable text interaction with a paywall. -- SW-2901: Adds `abandoned_product_id` to a `transaction_abandon` event to use in audience filters. You can use this to show a paywall if a user abandons the transaction for a specific product. -- Updates RevenueCat example app to use v5 of their SDK. - -### Fixes - -- Fixes error message `undefined is not an object` that sometimes appeared when opening a paywall. -- SW-2871: Makes sure to track device attributes after geo location data is found. -- Fixes issue where restored transactions were being finished even if a `PurchaseController` was supplied in configure. -- SW-2879: Adds `capabilities` to device attributes. This is a comma-separated list of capabilities the SDK has that you can target in audience filters. This release adds the `paywall_event_receiver` capability. This indicates that the paywall can receive transaction and restore events from the SDK. -- SW-2879: Adds `configCapabilties` which contains a `name` of the capability and any additional info. The `paywall_event_receiver` capability contains a list of eventNames specifying the exact events paywalljs can receive. -- Fixes a crash caused by an arithmetic overflow if there was an issue with audience filter limits. -- Fixes `UIScreen unavailable in visionOS` error message in `PaywallViewController`. - -## 3.6.6 - -### Enhancements - -- SW-2804: Exposes a `presentation` property on the `PaywallInfo` object. This contains information about the presentation of the paywall. -- Adds `restore_start`, `restore_complete`, and `restore_fail` events. -- SW-2850: Adds error message to `paywallWebviewLoad_fail`. -- SW-2851: Adds error message to `paywallProductsLoad_fail`. -- SW-2783: Logs error when trying to purchase a product that has failed to load. - -### Fixes - -- Makes sure the formatting of SK2 product variables use the same locale as the product. - -## 3.6.5 - -### Enhancements +# CHANGELOG -- Adds `enable_webview_process_pool`, `enable_suppresses_incremental_rendering`, `enable_throttle_scheduling_policy`, `enable_none_scheduling_policy` as feature flags for the webview configuration. +The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall/Superwall-iOS/releases) on GitHub. -## 3.6.4 +## 4.13.0 ### Enhancements -- Tweaks to webview configuration for performance improvements. +- Adds support for local images and videos in paywalls. +- Schedules trial notifications after purchasing Stripe products. ### Fixes -- Fixes bug where paywall background wasn't being set. +- Fixes race condition relating to the user ID when upgrading from v3 of the SDK to v4. +- Fixes issue where the Superscript version hadn't been upgraded to 1.0.13 if installed via CocoaPods. -## 3.6.3 +## 4.12.11 ### Enhancements -- SW-2828: Adds the Superwall `appUserId` as the `applicationUsername` for internal `SKPayments`. -- SW-2817: Adds support for dark mode paywall background color. -- SW-2815: Adds ability to target devices based on their IP address location. Use `device.ipRegion`, `device.ipRegionCode`, `device.ipCountry`, `device.ipCity`, `device.ipContinent`, or `device.ipTimezone`. -- Paywalls built with the new editor can be downloaded as webarchive files. This allows for shared resources and faster loading times for paywalls. +- Adds `appstackId` as an `IntegrationAttribute`. -### Fixes - -- Fixes issue where implicit triggers weren't sending a `paywallPresentationRequest` when they didn't result in a paywall. Now this applies only to implicit triggers that are derived from an action on the paywall, like `paywall_decline`. - -## 3.6.2 - -### Enhancements - -- Tracks an `identity_alias` event whenever identify is called to alias Superwall's anonymous ID with a developer provided id. -- Adds `setInterfaceStyle(to:)` which can be used to override the system interface style. -- Adds `device.interfaceStyleMode` to the device template, which can be `automatic` or `manual` if overriding the interface style. - -### Fixes - -- Changes the `$feature_gating` parameter in `PaywallInfo` from 0 and 1 to `GATED` and `NON_GATED` to prevent confusion. -- Fixes issue where feature gating wasn't working correctly when an implicit event triggered by `paywall_decline`, `transaction_fail`, `transaction_abandon`, or `survey_response` was resulting in a `skipped` `PaywallState`. -- Fixes issue where a `transaction_abandon` implicit event that resulted in a `skipped` `PaywallState` was accidentally closing a paywall when it shouldn't have. - -## 3.6.1 - -### Enhancements - -- Adds privacy manifest. - -## 3.6.0 - -### Enhancements - -- Adds support for unlimited products in a paywall. -- SW-2767: Adds `device.regionCode` and `device.preferredRegionCode`, which returns the `regionCode` of the locale. For example, if a locale is `en_GB`, the `regionCode` will be `GB`. You can use this in the filters of your campaign. -- Adds ability to specify custom API endpoints using `SuperwallOptions` to facilitate local testing more easily. - -### Fixes - -- Calls the completion block even if Superwall.configure is called more than once. -- `getPresentationResult` now confirms assignments for holdouts. - -## 3.5.0 - -### Enhancements - -- Adds visionOS support. - -### Fixes - -- Moves resources into their own resources bundle when installing via CocoaPods. - -## 3.5.0-rc.3 - -### Fixes - -- Moves resources into their own resources bundle when installing via CocoaPods. - -## 3.5.0-rc.1 - -This is our first visionOS pre-release, we'll test this on a few devices to -ensure everything works as expected! - -### Enhancements - -- Adds support for visionOS! - -## 3.4.8 - -### Enhancements - -- SW-2667: Adds `preferredLanguageCode` and `preferredLocale` to device attributes. If your app isn't already localized for a language you're trying to target, the `deviceLanguageCode` and `deviceLocale` may not be what you're expecting. Use these device attributes instead to access the first preferred locale the user has in their device settings. - -### Fixes - -- Fixes bug where a `transaction_abandon` or `transaction_fail` event would prevent the presented paywall from dismissing if `paywall_decline` was a trigger. -- SW-2678: Fixes issue where the `subscription_start` event was being fired even if a non-recurring product was purchased. -- SW-2659: Fixes issue on macOS where the window behind a paywall wasn't being removed when a paywall was dismissed, leading to the app appearing to be in a frozen state. - -## 3.4.6 - -### Enhancements - -- Adds internal code for SDK wrappers like Flutter. - -## 3.4.5 - -### Enhancements - -- Adds internal feature flag to disable verbose events like `paywallResponseLoad_start`. -- Tracks a Superwall Event `reset` whenever `Superwall.shared.reset()` is called. - -### Fixes - -- Fixes issue where holdouts were still matching even if the limit set for their corresponding rules were exceeded. -- Fixes potential crash if the free trial notification delay was set to zero seconds. - -## 3.4.4 - -### Enhancements - -- Tracks user attributes on session start. -- Exposes `triggerSessionId` on the `PaywallInfo` object. -- Makes `PaywallSkippedReason` conform to `CustomStringConvertible`. -- Adds the Superwall SDK version and your app's version/build number to the debugger menu. Press the hamburger icon on the top left in the debugger to access it. - -### Fixes - -- Changes the way paywall presentation serialization is performed to avoid mixing of concurrency paradigms. -- Prevents `preloadAllPaywalls()` from being called if the SDK is already preloading paywalls. -- Fixes issue where experiment and trigger session details were missing from transaction events if a paywall was closed before returning a `PurchaseResult` in the `PurchaseController`. -- Prevents multiple taps on a purchase button from firing the `PurchaseController` purchase function multiple times. -- Tracks `survey_response` when selected in debugger. - -## 3.4.3 - -### Enhancements - -- Exposes `isPaywallPresented` convenience variable. -- Adds `device_attributes` event, which tracks the device attributes every new session. -- Stops preloading paywalls that we know won't ever match. -- Adds a `.restored` case to `PurchaseResult` and `PurchaseResultObjc`. Return this from your `PurchaseController` when you detect a user has tried to purchase a product that they've already purchased. This happens when `transaction.transactionDate < purchaseDate`, where `purchaseDate` is the date that the purchase was initiated. Check out `RCPurchaseController.swift` in our Superwall-UIKit+RevenueCat example app for how to implement this. If you let Superwall handle purchasing, then we will automatically detect this. -- Adds `restore_via_purchase_attempt` to a `transaction_restore` event. This indicates whether the restoration happened due to the user purchasing or restoring. - -## 3.4.2 - -### Fixes - -- Fixes issue where multiple events registered in quick succession may not be performed in serial, resulting in unexpected paywalls. -- Fixes issue where transaction data wouldn't be available for those who are using a purchase controller. - -## 3.4.0 - -### Enhancements - -- Adds `sdkVersion`, `sdkVersionPadded`, `appBuildString`, and `appBuildStringNumber` to the device object for use in rules. `sdkVersion` is the version of the sdk, e.g. `3.4.0`. `sdkVersionPadded` is the sdk version padded with zeros for use with string comparison. For example `003.004.000`. `appBuildString` is the build of your app and `appBuildStringNumber` is the build of your app casted as an Int (if possible). -- When you experience `no_rule_match`, the `TriggerFire` event params will specify which part of the rules didn't match in the format `"unmatched_rule_": ""`. Where `outcome` will either be `OCCURRENCE`, referring to the limit applied to a rule, or `EXPRESSION`. The `id` is the experiment id. -- Adds a `touches_began` implicit trigger. By adding the `touches_began` event to a campaign, you can show a paywall the first time a user touches anywhere in your app. -- Adds the ability to include a close button on a survey. -- If running in sandbox, the duration of a free trial notification added to a paywall will be converted from days to minutes for testing purposes. -- Adds the ability to show a survey after purchasing a product. - -### Fixes - -- Fixes issue where a survey attached to a paywall wouldn't show if you were also using the `paywall_decline` trigger. -- Fixes issue where verification was happening after the finishing of transactions when not using a `PurchaseController`. -- Fixes issue where the retrieved `StoreTransaction` associated with the purchased product may be `nil`. -- Fixes issue where a `presentationRequest` wasn't being tracked for implicit triggers like `session_start` when there was no internet. - -## 3.3.2 - -### Fixes - -- Fixes issue where a rule added with `paywall_decline` would result in the feature block being called too early. -- Fixes issue where paywall assignments may not have been cleared when resetting. - -## 3.3.1 - -### Enhancements - -- Adds logic to enhance debugging by sending a stringified version of all the device/user/event parameters used to evaluate rules within the `paywallPresentationRequest` event. This is behind a feature flag. -- Adds logic to keep the user's generated `seed` value consistent when `Superwall.identify` is called. This is behind a feature flag. - -### Fixes - -- Fixes rare issue when using limits on a campaign rule. If a paywall encountered an error preventing it from being presented, it may still have been counted as having been presented. This would then have affected future paywall presentation requests underneath the same rule. -- Fixes issue where assets weren't being accessed correctly when installing the SDK via CocoaPods. -- Fixes crash if you tried to save an object that didn't conform to NSSecureCoding in user attributes. - -## 3.3.0 - -### Enhancements - -- Adds the ability to add a paywall exit survey. Surveys are configured via the dashboard and added to paywalls. When added to a paywall, it will attempt to display when the user taps the close button. If the paywall has the `modalPresentationStyle` of `pageSheet`, `formSheet`, or `popover`, the survey will also attempt to display when the user tries to drag to dismiss the paywall. The probability of the survey showing is determined by the survey's configuration in the dashboard. A user will only ever see the survey once unless you reset responses via the dashboard. The survey will always show on exit of the paywall in the debugger. -- Adds the ability to add `survey_response` as a trigger and use the selected option title in rules. -- Adds new `PaywallCloseReason` `.manualClose`. - -### Fixes - -- Fixes a recursive issue that was happening if you forgot to configure the Superwall instance. -- Fixes issue where a preloaded `Paywall` object wouldn't have had an experiment available on its `info` property. -- Fixes "error while deleting file" log on clean install of app. -- Exposes the `IdentityOptions` initializer. -- Fixes thread safety issues. - -## 3.2.2 - -### Fixes - -- If using a purchase controller, returning `.restored` from `restorePurchases()` would dismiss the paywall and assume an active subscription status. This was incorrect behavior. Now we specifically check both the subscription status and the restoration result to determine whether to dismiss the paywall, regardless of whether a purchase controller is being used. -- Added extra logging when a timeout occurs during paywall presentation. - -## 3.2.1 - -### Fixes - -- Fixes `user_attributes` being unnecessarily fired on every cold app launch. - -## 3.2.0 - -### Enhancements - -- Adds `user.seed` to user attributes for use in campaign rules. This assigns a user a random number from 0 to 99. This allows you to segment users into cohorts across campaigns. For example, in campaign A you may say `if user.seed < 50 { show variant A } else { show variant B }`, in campaign B you may say `if user.seed < 50 { show variant X } else { show variant Y }`. Therefore users who see variant A will then see variant X. -- Adds ability to use `device.interfaceType` in campaign rules to show different paywalls for different interface types. Use this instead of `device.deviceModel`, as that can lead to inaccurate results on some devices. `interfaceType` can be one of `ipad/iphone/mac/carplay/tv/unspecified`. Note that iPhone screen size emulated in iPad will be `iphone`. Built for iPad on Mac will be `ipad`. -- Adds `presentation_source_type` to `PaywallInfo`, which lets you know the source function that retrieved the paywall – register/getPaywall/implicit. -- Tracks whether a purchase controller is being used on the `AppInstall` event. - -### Fixes - -- Fixes issue where the transition from background to foreground may not have been detected on app launch, resulting in paywalls not showing. -- Fixes iOS 14 transaction validation issue that affects apps on v3.0.2+. -- Adds safeguard for developers returning an empty `NSError` on purchase failure which could cause a crash. - -## 3.1.1 - -### Enhancements - -- Adds `shouldShowPurchaseFailureAlert` as a `PaywallOption`. This defaults to `true`. If you're using a `PurchaseController`, set this to `false` to disable the alert that shows after the purchase fails. - -### Fixes - -- Fixes issue where a secondary paywall wouldn't present with the `transaction_fail` trigger. -- Fixes issue where the paywall preview wasn't obeying free trial/default paywall overrides. -- Fixes issue where preloaded paywalls may be associated with the incorrect experiment. - -## 3.1.0 - -### Enhancements - -- Adds support for paywalls that include a free trial notification. After starting a free trial, the app checks whether the paywall should notify the user when their trial is about to end. If so, the user will be asked to enable notifications (if they haven't already) before scheduling a local notification. You can add a free trial notification to your paywall from the paywall editor. -- Adds ability to use `device.minutesSince_X`, `device.hoursSince_X`, `device.daysSince_X`, `device.monthsSince_X`, and `device.yearsSince_X` in campaign rules and paywalls, where `X` is an event name. This can include Superwall events, such as `app_open`, or your own events. -- Prints out the Superwall SDK version when the `debug` logLevel is enabled. -- Adds `removeAllPendingSuperwallNotificationRequests()`, `removeAllPendingNonSuperwallNotificationRequests()`, `removeAllDeliveredSuperwallNotifications()`, and `removeAllDeliveredNonSuperwallNotifications()` to `UNUserNotificationCenter`. You can use these methods to remove your app's notifications without affecting Superwall's local notifications and vice-versa. -- Updates RevenueCat to the latest version in our RevenueCat example app. - -### Fixes - -- Fixes a Core Data multi-threading issue when performing a count. If you had enabled Core Data multi-threading assertions in Xcode, this will have caused a crash. -- Fixes very rare crash when purchasing without a `PurchaseController`. -- Reduces reliance on Combine when using register to fix memory management crashes. - -## 3.0.3 - -### Fixes - -- Fixes an issue where Superwall events `app_launch`, `app_install`, and `session_start` weren't working as paywall triggers from a cold start. - -## 3.0.2 - -### Fixes - -- Fixes issues with Xcode 15 and iOS 17. -- Moves the loading of localizations to only when the debugger is launched, therefore reducing setup time of Superwall. -- Removes reliance on force unwrapping/force casting as a safety precaution. -- Moves tracking of free trial start and transaction complete events to a higher priority Task. Before, this was of background priority and would take a while to track. -- Fix crash when trying to access `Superwall.shared.userId`. -- Prices in variables are now rounded down, e.g. 3.999 becomes 3.99, rather than 4.00. -- Fixes incorrect values for `trialPeriodPrice`, `trialPeriodDailyPrice`, `trialPeriodWeeklyPrice`, `trialPeriodMonthlyPrice`, `trialPeriodYearlyPrice` variables. - -## 3.0.1 - -### Fixes - -- Fixes bug that prevented Superwall from configuring when SwiftUI users in sandbox mode used the App file's `init()` to configure Superwall. - -## 3.0.0 - -Welcome to `SuperwallKit` v3.0, the framework formally known as `Paywall`! - -This update is a major release, containing lots of breaking changes, enhancements and bug fixes. We're excited for you to use it! - -We understand that transitions between major SDK releases can become frustrating, so we've made a [migration guide](https://docs.superwall.com/docs/migrating-to-v3) to make your life easier. We've also updated our [example apps](Examples) to v3, including RevenueCat+SuperwallKit and Objective-C apps. Finally, we recommend you check out our [updated docs](https://docs.superwall.com/docs). - -### Breaking Changes - -- Renames the package from `Paywall` to `SuperwallKit`. -- Renames the primary static class for integrating Superwall from `Paywall` to `Superwall`. -- Sets the minimum iOS version to iOS 13. -- Moves all functions and variables to the `shared` instance for consistency. -- Renames `preloadPaywalls(forTriggers:)` to `preloadPaywalls(forEvents:)` -- Renames `configure(apiKey:userId:delegate:options:)` to `configure(apiKey:purchaseController:options:completion:)`. You can use the completion block to know when Superwall has finished configuring. -- Removes delegate from `configure`. You now set the delegate via `Superwall.shared.delegate`. -- Changes `PaywallOptions` to `SuperwallOptions`. This now clearly defines which of the options are explicit to paywalls vs other configuration options within the SDK. -- Makes `Superwall.shared.options` internal so that options must be set in `configure`. -- Removes `Superwall.trigger(event:)` and replaces with register(event:params:handler:feature). This is Superwall's most powerful feature yet. Wrap your features with this method to conditionally show paywalls, lock features and more. -- Renames `Paywall.EventName` to `SuperwallEvent` and removes `.manualPresent` as a `SuperwallEvent`. -- Renames `PaywallDelegate` to `SuperwallDelegate`. -- Superwall now automatically handles all subscription-related logic. However, if you'd still like control (e.g. if you're using RevenueCat), you'll need to implement a `PurchaseController` and set `Superwall.shared.subscriptionStatus` yourself whenever the subscription status of the user changes. You pass your `PurchaseController` to `configure(apiKey:purchaseController:options:completion:)`. -- Removes `isUserSubscribed()` from the delegate and replaces this with a published instance variable `subscriptionStatus`. This is enum that defaults to `.unknown` on first install and the cached value on subsequent app opens. If you're using a `PurchaseController` to handle subscription-related logic, you must set `subscriptionStatus` every time the user's subscription status changes. If you're letting Superwall handle subscription-related logic, this value will be updated with the device receipt. -- For Objective-C users, this changes the `SWKPurchaseController` method `purchase(product:)` to `purchase(product:completion:)`. You call the completion block with the result of the user attempting to purchase a product, making sure you handle all cases of `SWKPurchaseResult`: `.purchased`, `.cancelled`, `.pending`, `failed`. When you have a purchasing error, you need to call the completion block with the `.failed` case along with the error. -- Changes `restorePurchases()` to an async function that returns a boolean instead of having a completion block. -- Removes `Paywall.load(identifier:)`. This was being used to preload a paywall by identifier. -- Removes `.triggerPaywall()` for SwiftUI apps. Instead, SwiftUI users should now use the UIKit function `Superwall.register()`. -- Changes the `period` and `periodly` attributes for 2, 3 and 6 month products. Previously, the `period` would be "month", and the `periodly` would be "monthly" for all three. Now the `period` returns "2 months", "quarter", "6 months" and the `periodly` returns "every 2 months", "quarterly", and "every 6 months". -- Removes `localizationOverride(localeIdentifier:)` and replaces it with the `SuperwallOption` `localeIdentifier`. You set this on configure. -- Removes ASN1Swift as a package dependency. -- Changes free trial logic. Previously we'd look at just the primary product. However, we determing free trial eligibility based on the first product in the paywall that has a free trial available. -- Changes Objective-C method `setUserAttributesDictionary(_:)` to `setUserAttributes(_:)`. -- Adds `PaywallInfo` to `SuperwallDelegate` methods `WillPresentPaywall(withInfo:)`, `didPresentPaywall(withInfo:)`, `willDismissPaywall(withInfo:)` and `didDismissPaywall(withInfo:)`. -- Renames `SuperwallDelegate` method `didTrackSuperwallEventInfo(_:SuperwallEventInfo)` to `handleSuperwallEvent(withInfo eventInfo: SuperwallEventInfo)` for clarity. -- Renames `SuperwallDelegate` methods `willOpenURL(url:)` and `willOpenDeepLink(url:)` to `paywallWillOpenURL(url:)` and `paywallWillOpenDeepLink(url:)` respectively. -- Changes the `logLevel` to be non-optional and introduces a `none` case to turn off logging. -- Removes all guides from the SDK documentation. From now on, our [online docs](https://docs.superwall.com/docs/) provide guides and the SDK documentation is only there as a technical reference. -- Changes the return type of `PurchaseController.restorePurchases()` from `Bool` to `RestorationResult`. -- Changes `DismissState` to `PaywallResult`. -- Renamed the `PaywallResult` case `closed` to `declined`. -- Removes .error(Error) from `PaywallSkippedReason` in favor of a new `PaywallState` case `.presentationError(Error)`. -- Exposes the `transactionBackgroundView` `PaywallOption` to Objective-C by making it non-optional and adding a `none` case in place of setting it to `nil`. - -### Enhancements - -- Adds `getPaywall(forEvent:params:paywallOverrides:delegate:)`! You can use this to request the `PaywallViewController` to present however you like. Please read our docs to learn more about how to use this. -- Adds paywall caching. This is enabled on all paywalls by default, however it can be turned off on a case by case basis via the dashboard. With this enhancement, your paywalls will load lightning fast and will reduce network load of your app. -- You can now pass an `IdentityOptions` object to `identify(userId:options)`. This should only be used in advanced use cases. By setting the `restorePaywallAssignments` property of `IdentityOptions` to `true`, it prevents paywalls from showing until after paywall assignments have been restored. If you expect users of your app to switch accounts or delete/reinstall a lot, you'd set this when identifying an existing account. -- Adds `Superwall.shared.isLoggedIn` to check whether the user is logged in to the SDK or not. This will be true if you've previously called `identify(userId:options:)`. This is added to user properties, which means you can create a rule based on whether the user is logged in vs. whether they're anonymous. -- Adds a new example app, UIKit+RevenueCat, which shows you how to use Superwall with RevenueCat. -- Adds a new Objective-C example app UIKit-Objc. -- Adds an Objective-C-only function `removeUserAttributes(_:)` to remove user attributes. In Swift, to remove attributes you can pass in `nil` for a specific attribute in `setUserAttributes(_:)`. -- Adds `getPresentationResult(forEvent:params:)`. This returns a `PresentationResult`, which preemptively gets the result of registering an event. This helps you determine whether a particular event will present a paywall in the future. -- Logs when products fail to load with a link to help diagnose the cause. -- Adds a published property `isConfigured`. This is a boolean which you can use to check whether Superwall is configured and ready to present paywalls. -- Adds `isFreeTrialAvailable` to `PaywallInfo`. -- Adds `subscriptionStatusDidChange(to:)` delegate function. If you're letting Superwall handle subscription logic you can use this to receive a callback whenever the user's internal subscription status changes. You can also listen to the published `subscriptionStatus` variable. -- Adds a completion handler to `Superwall.configure(...)` that lets you know when Superwall has finished configuring. You can also listen to the published `isConfigured` variable. -- If you let Superwall handle your subscription-related logic, we now assume that a non-consumable product on your paywall is a lifetime subscription. If not, you'll need to return a `SubscriptionController` from the delegate. -- `handleDeepLink(_:)` now returns a discardable `Bool` indicating whether the deep link was handled. If you're using `application(_:open:options:)` you can return its value there. -- Adds `togglePaywallSpinner(isHidden:)` to arbitrarily toggle the loading spinner on and off. This is particularly useful when you're doing async work when performing a custom action in `handleCustomPaywallAction(withName:)`. -- Adds a new event `SubscriptionStatusDidChange` which is logged on the dashboard whenever the user's subscription status changes. -- You can now target `device.isSandbox` in rules. -- Tweaks the loading indicator UI. -- Prevents the registering of events that have the same name as internally tracked `SuperwallEvents` like `paywall_open`. -- Adds a drawer display option which displays the paywall at 70% screen height on iOS 16 iPhones. -- Adds `$is_feature_gatable` standard property to register calls. -- Cleans up and reformats SDK logs. -- If you're using SwiftUI, you can now call `Superwall.configure` in the `init()` of your `App` file. This means you don't need to have a `UIApplicationDelegate`. -- You can access `device.subscriptionStatus` in a rule, which is a string that's either `ACTIVE`, `INACTIVE`, or `UNKNOWN`. -- You no longer need to have swiftlint installed to run our example apps. -- Adds static variable `Superwall.isInitialized` which is `true` when initialization is complete and `Superwall.shared` can be accessed. -- Adds `transaction_abandon`, `transaction_fail` and `paywall_decline` as potential triggers. This comes with a new `PaywallInfo` property called `closeReason`, which can either be `none`, `.systemLogic`, or `.forNextPaywall`. -- Changes default logging level to `INFO`. -- Adds new automatically tracked event `presentation_request` that gets sent with properties explaining why a paywall was or was not shown. -- Adds a `device.isFirstAppOpen` property that you can use in paywall rules. This is `true` for the very first time a user opens the app. When the user closes and reopens the app, this will be `false`. -- Adds `isInspectable` to the paywall web view if running on iOS 16.4+. -- Adds `rawTrialPeriodPrice`, `trialPeriodPrice`, `trialPeriodDailyPrice`, `trialPeriodWeeklyPrice`, `trialPeriodMonthlyPrice`, `trialPeriodYearlyPrice` to product variables. -- Fully handles what happens when there are network failures. - -### Fixes - -- Fixes race condition issue where the free trial paywall information would still be shown even if you had previously used a free trial on an expired product. -- Fixes a caching issue where the paywall was still showing in free trial mode when it shouldn't have. This was happening if you had purchased a free trial, let it expire, then reopened the paywall. Note that in Sandbox environments this issue may still occur due to introductory offers not being added to a receipt until after a purchase. -- The API uses background threads wherever possible, dispatching to the main thread only when necessary and when calling completion blocks. -- The API is now fully compatible with Objective-C. -- Setting the `PaywallOption` `automaticallyDismiss` to `false` now keeps the loading indicator visible after restoring and successfully purchasing until you manually dismiss the paywall. -- Improves the speed of requests by changing the cache policy of requests to our servers. -- Fixes `session_start`, `app_launch` and `first_seen` not being tracked if the SDK was initialised a few seconds after app launch. -- Stops the unnecessary retemplating of paywall variables when coming back to the paywall after visiting a link via the in-app browser. -- Removes the transaction timeout popup. This was causing a raft of issues so we now rely on overlayTimeout to cancel the transaction flow. -- Fixes bug in ` delete and reinstall the app -> open a paywall and purchase. -- Adds static variable `Superwall.isInitialized` which is `true` when initialization is complete and `Superwall.shared` can be accessed. -- Adds `transaction_abandon` and `transaction_fail` as potential triggers. This comes with a new `DismissState` case `closedForNextPaywall`, which is returned when dismissing one paywall for another. +## 4.12.7 ### Fixes -- Fixes issue where an invalid URL provided for an "Open URL" click behavior would result in a crash. -- Exposes `PaywallPresentationHandler` as `SWKPaywallPresentationHandler` for Objective-C. +- Fixes microphone permission request to prevent App Store Connect warnings. -## 3.0.0-rc.2 +## 4.12.6 ### Enhancements -- Simplifies Superwall-UIKit-Swift example project. +- Adds post purchase actions support. ### Fixes -- Fixes bug where calling Superwall.shared prior to Superwall.configure would result in a recursive loop. - -## 3.0.0-rc.1 +- Fixes a rare issue where TestFlight products could display in a different currency on the paywall than on Apple's payment sheet. -### Breaking Changes - -- Adds `PaywallInfo` to `SuperwallDelegate` methods `paywallWillPresent(withInfo:)`, `paywallDidPresent(withInfo:)`, `paywallWillDismiss(withInfo:)` and `paywallDidDismiss(withInfo:)`. -- Renames `SuperwallDelegate` method `didTrackSuperwallEventInfo(_:SuperwallEventInfo)` to `handleSuperwallEvent(withInfo eventInfo: SuperwallEventInfo)` for clarity -- Renames `SuperwallDelegate` methods `willOpenURL(url:)` and `willOpenDeepLink(url:)` to `paywallWillOpenURL(url:)` and `paywallWillOpenDeepLink(url:)` respectively -- Decouples associated value of `.dismissed` in `Superwall.shared.track()` closure to `PaywallInfo` and `DismissState`. -- Changes `subscription_status_did_change` to `subscriptionStatus_didChange`. -- Renames `TrackResult` to `PresentationResult` +## 4.12.5 ### Enhancements -- Introducing `Superwall.shared.register(event:params:handler:feature)`, Superwall's most powerful feature yet. Wrap your features with this method to conditionally show paywalls, lock features and more. -- Adds a drawer display option which displays the paywall at 70% screen height on iOS 16 iPhones. -- Adds warning if setting subscription status without passing through a PurchaseController during config. -- Adds `$is_feature_gatable` standard property to register and track calls -- Cleans up and reformats SDK logs +- Adds microphone permission request support. ### Fixes -- Fixes a long term bug where tracking an event to show a paywall and tracking an event that results in noRuleMatch would interfere with each other and cause the trigger session to be set to `nil`. This resulted in some paywall data being incorrect on the dashboard. +- Fixes issue where the notification permission prompt would not appear if provisional notification permission was already granted. -## 3.0.0-beta.8 +## 4.12.4 ### Enhancements -- Prevents the tracking of events that have the same name as internally tracked `SuperwallEvents` like `paywall_open`. - -### Fixes - -- Fixes an issue with reporting in the dashboard due to a mismatch of keys between client and server. - -## 3.0.0-beta.7 - -### Breaking Changes +- Adds back in contacts and location permission requests but this time will not get flagged in App Store review if they're not being used. +- Adds App Tracking Transparency permission request. -- Changes Objective-C method `getTrackInfo` to `getTrackResult` to be in line with the Swift API. -- Removes the error case from the `TrackResult` and adds in `userIsSubscribed` and `paywallNotAvailable` cases. -- Moves main actor conformance to functions of PurchaseController protocol rather than the whole protocol. -- Changes Objective-C method `setUserAttributesDictionary(_:)` to `setUserAttributes(_:)`. +## 4.12.3 ### Fixes -- Makes `NetworkEnvironment` Objective-C compatible. -- Fixes an issue where a manually dismissed modally presented paywall wouldn't properly dismiss. -- Fixes race condition when calling identify and tracking a paywall. - -## 3.0.0-beta.6 - -### Breaking Changes - -- `identify(userId:)` is not longer a throwing async function. Any error that occurs is logged. -- `reset` is no longer an async function. -- `presentedViewController` and `latestPaywallInfo` no longer restricted to the main actor. -- Removes `localizationOverride(localeIdentifier:)` and replaces it with the `SuperwallOption` `localeIdentifier`. You set this on configure. -- Removes delegate from `configure`. You now set the delegate via `Superwall.shared.delegate`. -- Removes `presenter` introduced in beta 5. -- Removes ASN1Swift as a package dependency. -- Changes free trial logic. Previously we'd look at just the primary product. However, we determing free trial eligibility based on the first product in the paywall that has a free trial available. - -### Enhancements +- Removes contacts and location permission APIs to prevent App Store warnings. -- You can now target `device.isSandbox` in rules. +## 4.12.2 ### Fixes -- Fixes bug where calling identify and immediately tracking a paywall would result in an error if it happened before configure returned. -- Fixes compiler bug when calling track. -- Tweaks the loading indicator. -- Fixes removing an attribute using Objective-C. -- Fixes issues where some functions tagged for the main actor weren't actually running on the main actor. -- Fixes issues with paywall product overrides. - -## 3.0.0-beta.5 +- Fixes issue building for Mac Catalyst. -### Breaking Changes - -- Changes `SubscriptionController` to `PurchaseController`. You now set this in `Superwall.shared.configure`, rather than via the delegate. -- Removes `isUserSubscribed()` from the `SuperwallDelegate` and replaces this with a published instance variable `subscriptionStatus`. This is enum that defaults to `.unknown` on first install and the cached value on subsequent app opens. If you're using a `SubscriptionController` to handle subscription-related logic, you must set `subscriptionStatus` every time the user's subscription status changes. If you're letting Superwall handle subscription-related logic, this value will be updated with the device receipt. -- `hasActiveSubscriptionDidChange(to:)` is replaced in favour of `subscriptionStatusDidChange(to:)`. -- Makes `Superwall.shared.options` internal so that options must be set in `configure`. +## 4.12.1 ### Enhancements -- Adds a new event `SubscriptionStatusDidChange` which is logged on the dashboard. -- Adds an optional `presenter` parameter to `track`. In v2 this was known as `on`. This takes a `UIViewController` which is used to present the paywall. - -## 3.0.0-beta.4 +- Adds `redemptionInfo.paywallInfo.product` which contains information about the product that was purchased. This deprecates `redemptionInfo.paywallInfo.productIdentifier` in favor of `redemptionInfo.paywallInfo.product.identifer`. -### Breaking Changes - -- Moves back to using `Superwall.shared.identify(userId: userId)` and `reset()` instead of logIn/createAccount/logout/reset. This is so that it's easier for integration. However, you can now pass an `IdentityOptions` object to `identify(userId:options)`. This should only be used in advanced use cases. By setting the `restorePaywallAssignments` property of `IdentityOptions` to `true`, it prevents paywalls from showing until after paywall assignments have been restored. If you expect users of your app to switch accounts or delete/reinstall a lot, you'd set this when identifying an existing account. +## 4.12.0 ### Enhancements -- Adds `hasActiveSubscriptionDidChange(to:)` delegate function. If you're letting Superwall handle subscription logic you can use this to receive a callback whenever the user's internal subscription status changes. You can also listen to the published `hasActiveSubscription` variable. -- Adds a completion handler to `Superwall.configure(...)` that lets you know when Superwall has finished configuring. You can also listen to the published `isConfigured` variable. -- If you let Superwall handle your subscription-related logic, we now assume that a non-consumable product on your paywall is a lifetime subscription. If not, you'll need to return a `SubscriptionController` from the delegate. -- `handleDeepLink(_:)` now returns a discardable `Bool` indicating whether the deep link was handled. If you're using `application(_:open:options:)` you can return its value there. -- Adds `togglePaywallSpinner(isHidden:)` to arbitrarily toggle the loading spinner on and off. This is particularly useful when you're doing async work when performing a custom action in `handleCustomPaywallAction(withName:)`. +- Adds `paywallPreload_start` and `paywallPreload_complete` events. +- Adds `request permission` action support allowing you to request notification, location, photos, contacts, and camera permissions from paywalls. +- Improves drawer presentation style corner rounding by applying the device radius on bottom corners. ### Fixes -- Fixes occasional thread safety related crash when loading products. -- Reverts a issue from the last beta where the paywall spinner would move up before the payment sheet appeared. +- Updates Superscript version to 1.0.12. This fixes an issue with `appVersionPadded` comparison. View the original Rust release changelog [here](https://github.com/superwall/superscript/releases/tag/1.0.12). -## 3.0.0-beta.3 +## 4.11.2 ### Fixes -- Fixes potential crash due to a using a lazy variable. - -## 3.0.0-beta.2 - -### Breaking Changes +- Deprecates `device.isApplePayAvailable` and defaults it to `true`. This also removes the PassKit import, which was getting flagged for some developers in review. -- Moves all functions and variables to the `shared` instance for consistency, e.g. it's now `Superwall.shared.track()` instead of `Superwall.track()`. - -### Enhancements - -- Readds `Superwall.shared.logLevel` as a top level static convenience variable so you can easily change the log level. -- Adds `isLoggedIn` to user properties, which means you can create a rule based on whether the user is logged in vs. whether they're anonymous. +## 4.11.1 ### Fixes -- Fixes bug in ``) for those Combine lovers. By subscribing to this publisher, you can receive state updates of your paywall. We've updated our sample apps to show you how to use that. -- Adds `Superwall.isLoggedIn` to check whether the user is logged in to the SDK or not. This will be true if you've previously called `logIn(userId:)` or `createAccount(userId:)`. -- Adds a new example app, UIKit+RevenueCat, which shows you how to use Superwall with RevenueCat. -- Adds a new Objective-C example app UIKit-Objc. -- Adds an Objective-C-only function `removeUserAttributes(_:)` to remove user attributes. In Swift, to remove attributes you can pass in `nil` for a specific attribute in `setUserAttributes(_:)`. -- Adds `getTrackResult(forEvent:params:)`. This returns a `TrackResult` which tells you the result of tracking an event, without actually tracking it. This is useful if you want to figure out whether a paywall will show in the future. -- Logs when products fail to load with a link to help diagnose the cause. -- Adds a published property `hasActiveSubscription`, which you can check to determine whether Superwall detects an active subscription. Its value is stored on disk and synced with the active purchases on device. If you're using Combine or SwiftUI, you can subscribe or bind to this to get notified whenever the user's subscription status changes. If you're implementing your own `SubscriptionController`, you should rely on your own logic to determine subscription status. -- Adds a published property `isConfigured`. This is a boolean which you can use to check whether Superwall is configured and ready to present paywalls. -- Adds `isFreeTrialAvailable` to `PaywallInfo`. -- Tracks whenever the paywall isn't presented for easier debugging. +- Adds the ability to override introductory offer eligibility via the paywall editor. +- Adds dynamic notification support and scheduling. +- Adds `refreshConfiguration()` to manually refresh the SDK configuration. This should only be used in wrapper SDKs in development for hot reloading. +- Adds `offerType`, `subscriptionGroupId` and `store` to `SubscriptionTransaction` and `NonSubscriptionTransaction`. ### Fixes -- Fixes a caching issue where the paywall was still showing in free trial mode when it shouldn't have. This was happening if you had purchased a free trial, let it expire, then reopened the paywall. Note that in Sandbox environments this issue may still occur due to introductory offers not being added to a receipt until after a purchase. -- The API uses background threads wherever possible, dispatching to the main thread only when necessary and when returning from completion blocks. -- The API is now fully compatible with Objective-C. -- Setting the `PaywallOption` `automaticallyDismiss` to `false` now keeps the loading indicator visible after restoring and successfully purchasing until you manually dismiss the paywall. -- Improves the speed of requests by changing the cache policy of requests to our servers. -- Fixes `session_start`, `app_launch` and `first_seen` not being tracked if the SDK was initialised a few seconds after app launch. -- Stops the unnecessary retemplating of paywall variables when coming back to the paywall after visiting a link via the in-app browser. -- Removes the transaction timeout popup. This was causing a raft of issues so we now rely on overlayTimeout to cancel the transaction flow. +- Fixes an issue where not all product IDs belonging to `Entitlement`s in `CustomerInfo` were being included. ---- - -## 2.5.8 +## 4.10.8 ### Enhancements -- Adds `isExternalDataCollectionEnabled` data privacy `PaywallOption`. When `false`, prevents non-Superwall events and properties from being sent back to the superwall servers. -- Adds an `X-Is-Sandbox` header to all requests such that sandbox data doesn't affect your production analytics on superwall.com. - -### Fixes - -- Fixes a bug that prevented the correct calculation of a new app session. -- Fixes missing loading times of the webview and products. - ---- - -## 2.5.6 - -### Fixes - -- Fixes a bug found in the previous version. Disabling the preloading of paywalls for specific triggers via remote config now works correctly. - ---- - -## 2.5.5 +- Adds support for `Set user attributes` action. +- Adds new `SuperwallDelegate` method called `userAttributesDidChange` that notifies you when user attributes change from an external source. +- Adds `firebaseInstallationId` as an `IntegrationAttribute`. ### Fixes -- Fixes a crash when all variants of a campaign rule are set to 0%. - -### Enhancements - -- Adds ablity to disable the preloading of paywalls from specific triggers via config. - ---- +- Fixes a crash caused by a race condition when accessing JSON dictionaries concurrently. +- Fixes issue returning the `PurchaseResult` from `Superwall.shared.purchase(_:)` when using StoreKit 1 inside a `PurchaseController`. +- Fixes `handleDeepLink` returning true for non-Superwall URLs when called before configuration completes. -## 2.5.4 +## 4.10.6 ### Fixes -- Fixes a crash issue where the completion blocks for triggering a paywall were being called on a background thread in a specific scenario. -- Fixes an issue where lazy properties were causing an occasional crash due to the use of multithreading. - ---- +- Fixes issue that prevented the SDK from being built on old Xcode versions. -## 2.5.3 +## 4.10.5 ### Fixes -- Fixes a bug where `Paywall.reset()` couldn't be called on a background thread. - ---- +- Updates `device.isApplePayAvailable` for more accurate filtering. Previously it returned true whenever the device supported Apple Pay, even if no card was added. It now returns true only when the device supports Apple Pay and the user has added a card. +- Fixes issue where `didRedeemLink` might not get called if there's no paywall available to present an alert from. -## 2.5.2 +## 4.10.4 ### Fixes -- Fixed memory and time issues associated with the shimmer view when loading a paywall. Special thanks to Martin from Planta for spotting that one. We've rebuilt the shimmer view and only add it when the paywall is visible and loading. This means it doesn't get added to paywalls preloading in the background. After loading, we remove the shimmer view from memory. -- Moves internal operations for templating paywall variables from the main thread to a background thread. This prevents hangs on the main thread. -- Stops UIAlertViewControllers being unnecessarily created when loading a paywall. -- Removes the dependency on `TPInAppReceipt` from our podspec and replaces it with a `ASN1Swift` dependency to keep it in line with our Swift Package file. - ---- - -## 2.5.0 - -### Enhancements +- Updates Superscript version to 1.0.10. This fixes an issue with namespacing in cocoapods. View the original Rust release changelog [here](https://github.com/superwall/superscript/releases/tag/1.0.10). +- Fixes some issues building for visionOS. -- Assigments of paywall variants are now performed on device, meaning reduced network calls and faster setup time for the SDK. -- Adds `Paywall.latestPaywallInfo`. You can read this to access the `PaywallInfo` object of the most recently presented view controller. -- Adds feature flags under the hood so new features can be turned on for specific organizations and apps. -- Adds the ability to specify `SKProducts` with triggers. These override products defined in the dashboard. You do this by creating a `PaywallProducts` object and calling `Paywall.trigger(event: "event", products: products)`. -- Updates sample projects to iOS 16. +## 4.10.3 ### Fixes -- Shimmer view is no longer visible beneath a paywall's `WKWebView` when there is no `body` or `html` background color set -- Previously calls to `Paywall.preloadPaywalls(forTriggers:)` before `Paywall.config()` finished were ignored. This has been fixed. -- If a user had already bought a product within a subscription group, they were still being offered a free trial on other products within that group. This is incorrect logic and this update fixes that. -- # Fixed a bug where `Paywall.reset()` couldn't be called on a background thread. -- Previously, calling `Paywall.preloadPaywalls(forTriggers:)` before `Paywall.config()` finished would not work. This has been fixed. -- Previously, if a user purchases a product within a subscription group, they would still be offered a free trial on other products within that group. This has been fixed. -- Fixes a bug where `Paywall.reset()` couldn't be called on a background thread. - ---- - -## 2.4.1 - -### Enhancements +- Fixes issue where `Superwall.shared.confirmAllAssignments()` would be return an empty `Set` if config hadn't been retrieved. -- Adds `Paywall.preloadAllPaywalls()` and `Paywall.preloadPaywalls(forTriggers:)`. Use this with `Superwall.options.shouldPreloadPaywall = false` to have more control over when/what paywalls are preloaded. +## 4.10.1 ### Fixes -- Paywall options specified prior to config are now respected, regardless of whether you pass an options object through to config or not. -- Ensures /config's request and response is always handled on the main thread - ---- - -## 2.4.0 - -### Enhancements - -- New _push_ presentation style. By selecting Push on the superwall dashboard, your paywall will push and pop in as if it's being pushed/popped from a navigation controller. If you are using UIKit, you can provide a view controller to `Paywall.trigger` like this: `Paywall.trigger(event: "MyEvent", on: self)`. This will make the push transition more realistic, by moving its view in the transition. Note: This is not backwards compatible with previous versions of the SDK. -- New _no animation_ presentation style. By selecting No Animation in the superwall dashboard, you can disable presentation/dismissal animation. This release deprecates `Paywall.shouldAnimatePaywallDismissal` and `Paywall.shouldAnimatePaywallPresentation`. -- A new `PaywallOptions` object that you configure and pass to `Paywall.configure(apiKey:userId:delegate:options) to override the default appearance and presentation of the paywall. This deprecates a lot of static variables for better organisation. -- New `shouldPreloadPaywalls` option. Set this to `false` to make paywalls load and cache in a just-in-time fashion. This replaces the old `Paywall.shouldPreloadTriggers` flag. -- New dedicated function for handling deeplinks: `Paywall.handleDeepLink(url)`. -- Deprecates old `track` functions. The only one you should use is `Superwall.track(_:_:)`, to which you pass an event name and a dictionary of parameters. Note: This is not backwards compatible with previous versions of the SDK. -- Adds a new way of internally tracking analytics associated with a paywall and the app session. This will greatly improve the Superwall dashboard analytics. -- Adds support for javascript expressions defined in rules on the Superwall dashboard. -- Updates the SDK documentation. -- Adds `trialPeriodEndDate` as a product variable. This means you can tell your users when their trial period will end, e.g. `Start your trial today — you won't be billed until {{primary.trialPeriodEndDate}}` will print out `Start your trial today — you won't be billed until June 21, 2023`. -- Adds support for having more than 3 products on your paywall. -- Exposes `Paywall.presentedViewController`. This gives you access to the `UIViewController` of the paywall incase you need to present a view controller on top of it. -- Adds `today`, `daysSinceInstall`, `minutesSinceInstall`, `daysSinceLastPaywallView`, `minutesSinceLastPaywallView` and `totalPaywallViews` as `device` parameters. These can be references in your rules and paywalls with `{{ device.paramName }}`. -- Paywalls can now be configured via the dashboard to always present, regardless of the subscription status of the user. -- Adds a `presentationStyleOverride` parameter to `Paywall.trigger()` and `Paywall.present()`. By setting this, you can override the configured presentation style on case by case basis. -- Rules can now be limited by occurrence and date. For example, you could set a rule to only match 10 times within the last 5 hours. -- Adds `Paywall.userId` to grab the id of the current user. -- Adds `$url`, `$path`, `$pathExtension`, `$lastPathComponent`, `$host`, `$query`, `$fragment` as standard parameters to the `deepLink_open` event trigger (automatically tracked). -- Parses URL parameters and adds them as trigger parameters to the `deepLink_open` event trigger (automatically tracked). -- Fixes window logic for opening the debugger and launching paywalls on `deepLink_open`. -- Launching a paywall using the `deepLink_open` Trigger now dismisses a currently presenting paywall before presenting the new one. - -### Fixes +- Fixes issue where `willRedeemLink` might get called twice during the web checkout payment sheet flow. +- Fixes issue where paywall might get dismissed prematurely during web checkout. +- Fixes issue where the spinner on the paywall wasn't showing for a few seconds after the system closed the web checkout payment sheet due to a successful purchase. -- Adds the missing Superwall events `app_install`, `paywallWebviewLoad_fail`, `paywallWebviewLoad_timeout` and `nonRecurringProduct_purchase`. -- Adds `trigger_name` to a `triggerFire` Superwall event, which can be accessed in the parameters sent back to the `trackAnalyticsEvent(name:params:)` delegate function. -- Product prices were being sent back to the dashboard with weird values like 89.999998. We fixed that. -- Modal presentation now uses `.pageSheet` instead of `.formSheet`. This results in a less compact paywall popover on iPad. Thanks to Daniel Yoo from the Daily Bible Inspirations app for spotting that! -- For SwiftUI users, we've fixed an issue where the explicitly triggered paywalls and presented paywalls would sometimes randomly dismiss. We found that state changes within the presenting view caused a rerendering of the view which temporarily reset the state of the binding that controlled the presentation of the paywall. This was causing the Paywall to dismiss. -- Fixes an issue where the wrong paywall was shown if a trigger was fired before the config was fetched from the server. Thanks to Zac from Blue Candy for help with finding that :) -- Future proofs enums internally to increase backwards compatibility. -- Fixes a bug where long term data was being stored in the cache directory. This update migrates that to the document directory. This means the data stays around until we tell it to delete, rather than the system deleting it at random. -- Prevents Paywall.configure from being called twice and logs a warning if this occurs. -- Prevents Paywall.configure from being called in the background. -- Fixes an issue where the keyboard couldn't be dismissed in the UIKit sample app. -- Mentions SwiftLint as a requirement to run the sample apps. -- Deprecates `Paywall.debugMode`. All logs are now controlled by setting the paywall option `.logLevel`. The default `logLevel` is now `.warn`. -- Fixes broken webview based deeplinks and closes the paywall view before calling the delegate handler. -- Deprecates `Paywall.present` for `Paywall.trigger`. -- Fixes issue where preloaded paywalls would be cleared upon calling `Paywall.identify()` if config was called without a `userId`. -- Fixes logic for grabbing the active view controller. - -## 2.3.0 +## 4.10.0 ### Enhancements -- New [UIKit Example App](Examples/SuperwallUIKitExample). -- Better [SDK documentation](https://sdk.superwall.me/documentation/paywall/). This is built from the ground up using DocC which means you view it directly in Xcode by selecting **Product ▸ Build Documentation**. -- New Pull Request and Bug Report templates for the repo. -- Added a setup file that installs GitHooks as well as SwiftLint if you don't already have it. This is located at `scripts/setup.sh` and can be run from anywhere. -- Added a [CONTIBUTING.md](CONTRIBUTING.md) file for detailed instructions on how to get set up and contribute to the codebase. -- Added a [Code of Conduct](CODE_OF_CONDUCT.md) file to the repo. -- Added a CHANGELOG.md file. -- Removed the `TPInnAppReceipt` dependency for the SDK. +- Adds `CustomerInfo`. This contains the latest information about all of the customer's purchase and subscription data. This can be accessed via the published property `Superwall.shared.customerInfo`, via `Superwall.shared.getCustomerInfo()`, via the `AsyncStream` `customerInfoStream`, or via the delegate method `customerInfoDidChange(from:to:)`. This updates the `Entitlement` object to have more properties such as `startsAt` and `expiredAt`. These can be used in audience filters. +- Adds `Superwall.shared.entitlements.byProductIds(_:)` to return a `Set` of `Entitlement` objects belonging to a given set of product identifiers. +- Changes the `PurchaseController` examples to account for `CustomerInfo` changes. +- Adds `transaction_abandon` capability to web checkout payment sheet. ### Fixes -- All readme links for the UIKit example app now work. -- Adds an `experiment` parameter to `PaywallInfo`. This will be useful in the next version of Triggers, where you can see details about the experiment that triggered the presentation of the paywall. -- When triggering or presenting a paywall, if the default value for `isPresented` was `true`, the paywall would not present/trigger. It now works as expected. +- Fixes issue after purchasing web products where localized strings weren't correct in SDK wrappers like Expo. diff --git a/content/docs/ios/guides/advanced/request-permissions-from-paywalls.mdx b/content/docs/ios/guides/advanced/request-permissions-from-paywalls.mdx index dfa26445..bbfffe95 100644 --- a/content/docs/ios/guides/advanced/request-permissions-from-paywalls.mdx +++ b/content/docs/ios/guides/advanced/request-permissions-from-paywalls.mdx @@ -4,9 +4,17 @@ description: "Trigger the iOS system permission dialog directly from a Superwall --- ## Overview + Use the **Request permission** action in the paywall editor when you want to gate features behind iOS permissions without sending users into your app settings flow. When the user taps the element, SuperwallKit presents the native prompt, reports the result back to the paywall so you can update the design, and emits analytics events you can forward through `SuperwallDelegate`. + + The **Request permission** action is rolling out to the paywall editor and is + not visible in the dashboard just yet. We're shipping it very soon, so keep an + eye on the changelog if you don't see it in your editor today. + + ## Add the action in the editor + 1. Open your paywall in the editor and select the button or element you want to wire up. 2. Set its action to **Request permission**. 3. Choose the permission to request. You can add multiple buttons if you need to prime more than one permission (for example, notification + camera). @@ -14,18 +22,23 @@ Use the **Request permission** action in the paywall editor when you want to gat ## Supported permissions and Info.plist keys -| Editor option | `permission_type` sent from the paywall | Required Info.plist keys | Notes | -|---------------|-----------------------------------------|--------------------------|-------| -| Notifications | `notification` | _None_ | Uses `UNUserNotificationCenter` with alert, badge, and sound options. | -| Location (When In Use) | `location` | `NSLocationWhenInUseUsageDescription` | Prompts for foreground access only. | -| Location (Always) | `background_location` | `NSLocationWhenInUseUsageDescription`, `NSLocationAlwaysAndWhenInUseUsageDescription` | The SDK first ensures When-In-Use is granted, then escalates to Always. | -| Photos | `read_images` | `NSPhotoLibraryUsageDescription` | Requests `.readWrite` access on iOS 14+. | -| Contacts | `contacts` | `NSContactsUsageDescription` | Uses `CNContactStore.requestAccess`. | -| Camera | `camera` | `NSCameraUsageDescription` | Uses `AVCaptureDevice.requestAccess`. | +| Editor option | `permission_type` sent from the paywall | Required Info.plist keys | Notes | +| ------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| Notifications | `notification` | _None_ | Uses `UNUserNotificationCenter` with alert, badge, and sound options. | +| Location (When In Use) | `location` | `NSLocationWhenInUseUsageDescription` | Prompts for foreground access only. | +| Location (Always) | `background_location` | `NSLocationWhenInUseUsageDescription`, `NSLocationAlwaysAndWhenInUseUsageDescription` | The SDK first ensures When-In-Use is granted, then escalates to Always. | +| Photos | `read_images` | `NSPhotoLibraryUsageDescription` | Requests `.readWrite` access on iOS 14+. | +| Contacts | `contacts` | `NSContactsUsageDescription` | Uses `CNContactStore.requestAccess`. | +| Camera | `camera` | `NSCameraUsageDescription` | Uses `AVCaptureDevice.requestAccess`. | +| Microphone | `microphone` | `NSMicrophoneUsageDescription` | Uses `AVAudioSession.requestRecordPermission()`. | +| App Tracking Transparency | `tracking` | `NSUserTrackingUsageDescription` | iOS 14+ only. Uses `ATTrackingManager.requestTrackingAuthorization()`. | If a required Info.plist key is missing—or the platform does not support the permission, such as background location on visionOS—the action finishes with an `unsupported` status, and the delegate receives a `permissionDenied` event so you can log the misconfiguration. +> **Note**: In iOS SDK 4.12.3, Contacts and Location permission requests were temporarily removed to prevent App Store warnings. If you need those, update to 4.12.4+. + ## What the SDK tracks + Each button tap generates three analytics events that flow through `handleSuperwallEvent(withInfo:)`: - `permission_requested` when the native dialog is about to appear. @@ -62,6 +75,7 @@ func handleSuperwallEvent(withInfo eventInfo: SuperwallEventInfo) { ``` ## Status values returned to the paywall + The paywall receives a `permission_result` event with one of the following statuses so you can branch in your paywall logic (for example, swapping a button for a checklist item): - `granted` – The system reported success. @@ -71,6 +85,7 @@ The paywall receives a `permission_result` event with one of the following statu Because the permissions are requested from real user interaction, you can safely stack actions—for example, ask for notifications first and, on success, show a camera prompt that immediately appears inside the same paywall session. ## Troubleshooting + - See `unsupported`? Double-check the Info.plist keys in the table above and confirm the permission exists on the target OS (background location is not available on visionOS). - Nothing happens when you tap the button? Make sure the action is published as **Request permission** and that the app has been updated with the new paywall revision. - Want to show fallback copy after a denial? Configure `PaywallOptions.notificationPermissionsDenied` or handle the `permissionDenied` event in your delegate to display a Settings deep link. diff --git a/content/docs/ios/guides/embedded-paywalls-in-scrollviews.mdx b/content/docs/ios/guides/embedded-paywalls-in-scrollviews.mdx new file mode 100644 index 00000000..5a06fa20 --- /dev/null +++ b/content/docs/ios/guides/embedded-paywalls-in-scrollviews.mdx @@ -0,0 +1,148 @@ +--- +title: "Article-Style Paywalls: Inline with Additional Plans" +description: "Embed an inline paywall in a scrollable article and optionally present a second full-screen paywall for additional plans." +--- + +Article-style paywalls let you keep readers in the flow of a long-form page while still prompting for upgrade options. You can place an inline paywall inside a scroll view, then present a second, full-screen paywall when users tap “see more plans.” + +This pattern is common in paid media and magazine apps: a portion of the article is readable, the rest is blurred or gated, and a footer paywall offers an inline purchase with a “see more plans” option that opens a full-screen paywall. Check out this working example: + +
+ ![](/images/article-vid-example.gif) +
+ +This guide will show you how to build this example by explaining the APIs involved, and then a full code sample. There’s also a live working example in [CaffeinePal](https://github.com/superwall/CaffeinePal/tree/using-superwall-sdk). Look at the `RecipesView` to see it in action. + +## Key APIs + +Use `getPaywall()` to fetch a paywall you can embed inline, and configure it with a placement so you can control which paywall variant shows from the dashboard. For more on presenting paywalls in custom presentations, check out our [blog post](https://superwall.com/blog/custom-paywall-presentation-in-ios-with-the-superwall-sdk/). + +For the second paywall, trigger a custom action from the inline paywall and call `getPaywall()` again to present the full-screen option. + + +You're responsible for removing embedded paywall views when users move on. Reusing the same `PaywallViewController` or `PaywallView` instance elsewhere can cause a crash. For UIKit, avoid mixing `register()` and `getPaywall()` when you embed paywalls. + + +## Presenting a second paywall + +To get the inline paywall to trigger a second, full-screen paywall, create a custom action in the paywall editor in the embedded paywall. In this example, a custom action called "showFromLine" is triggered from the "or, view all plans" button: + +![](/images/showCustomAction.jpeg) + +Then, respond to that action in your [`SuperwallDelegate`](/ios/sdk-reference/SuperwallDelegate) to retrieve the second paywall and present it. In the code below, our second paywall is normally triggered via the `showAllPlansPaywall` placement that was setup in the Superwall dashboard within a campaign: + +```swift +extension MyAppLogic: SuperwallDelegate, PaywallViewControllerDelegate { + // Custom action comes in + func handleCustomPaywallAction(withName name: String) { + if name == "showFromInline" { + Task { + await presentAllPlansPaywall() + } + } + } + + // MARK: PaywallViewControllerDelegate + + func paywall( + _ paywall: PaywallViewController, + didFinishWith result: PaywallResult, + shouldDismiss: Bool + ) { + if shouldDismiss { + paywall.dismiss(animated: true) + } + } + + func paywall( + _ paywall: PaywallViewController, + loadingStateDidChange loadingState: PaywallLoadingState + ) { + // Handle loading state changes if needed + } + + // MARK: Custom Paywall Presentation + + private func presentAllPlansPaywall() async { + do { + let paywallViewController = try await Superwall.shared.getPaywall( + forPlacement: "showAllPlansPaywall", + delegate: self + ) + + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let rootViewController = windowScene.windows.first?.rootViewController else { + return + } + + var topController = rootViewController + while let presented = topController.presentedViewController { + topController = presented + } + + topController.present(paywallViewController, animated: true) + } catch let reason as PaywallSkippedReason { + print("Paywall skipped: \(reason)") + } catch { + print("Error presenting paywall: \(error)") + } + } +} +``` + +This keeps the inline paywall embedded while you intentionally present the next paywall. The entire flow looks like this: + +**In your dashboard** +1. Have a paywall setup for your "footer" or bottom paywall. +2. Add a custom action to it to present a second paywall over it. +3. Make sure both paywalls are active in a campaign, and remember the placements used to trigger them + +**In your code** +1. Use `getPaywall` and `PaywallView` to embed the first paywall in your scrollview. +2. Users can purchase from there, or tap another button to present a second paywall. +3. Handle a custom action fired from a "View all plans" or similar button in a `SuperwallDelegate`. +4. Use `PaywallViewControllerDelegate` to manage presentation of the second one. + +Here's some code to model your approach, showing the first paywall as either an overlay at the bottom or inline with scrolled content: + +```swift +enum PaywallEmbedMode { + case overlay + case inline +} + +struct ArticlePaywallDemoView: View { + let mode: PaywallEmbedMode + let placement: String = "getPaywallTest" + + var body: some View { + ScrollView { + VStack(alignment: .leading) { + Text("How to embed a Superwall paywall alongside your own content") + .font(.title) + Text("By Superwall").font(.caption) + Text("...article content...") + .padding(.vertical, 16) + + if mode == .inline { + paywallContent + } + } + .padding() + .overlay(alignment: .bottom) { + if mode == .overlay { + paywallContent + } + } + } + } + + private var paywallContent: some View { + PaywallView(placement: placement) + .frame(maxWidth: .infinity) + .frame(height: 300) + } +} +``` + +If you need to remove the paywall, remove the `PaywallView` from the view hierarchy and recreate it when you need to show it again. diff --git a/content/docs/ios/guides/handling-deep-links.mdx b/content/docs/ios/guides/handling-deep-links.mdx new file mode 100644 index 00000000..9ca38d22 --- /dev/null +++ b/content/docs/ios/guides/handling-deep-links.mdx @@ -0,0 +1,6 @@ +--- +title: "Handling Deep Links" +description: "Use handleDeepLink and campaign rules to present paywalls from deep links without hardcoding logic in your app." +--- + +../../../shared/handling-deep-links.mdx diff --git a/content/docs/ios/guides/intro-offer-eligibility-override.mdx b/content/docs/ios/guides/intro-offer-eligibility-override.mdx new file mode 100644 index 00000000..762dfc09 --- /dev/null +++ b/content/docs/ios/guides/intro-offer-eligibility-override.mdx @@ -0,0 +1,70 @@ +--- +title: "Overriding Introductory Offer Eligibility" +description: "Control when users see free trials and intro offers on your paywalls by overriding the default eligibility logic." +--- + +## Overview + +Starting with iOS SDK 4.11.0, you can override the default introductory offer eligibility logic to control when users see free trials and intro offers on your paywalls. This allows you to show intro offers to returning users (as "promo offers") or to prevent them from appearing entirely. + +This feature is configured entirely through the Paywall Editor in the Superwall Dashboard. No code changes are required in your app. + +## Requirements + +- **iOS SDK:** Version 4.11.0 or later +- **Platform:** iOS 18.2+ only (App Store products) +- **Xcode Version:** 16.3+ +- You must have set up the [App Store Connect API](/overview-settings-revenue-tracking#app-store-connect-api) and the [In App Purchase Configuration](/overview-settings-revenue-tracking#in-app-purchase-configuration). + +## If you're using a `PurchaseController` + +`PurchaseController` support for intro offer eligibility override was added in SDK version 4.12.8. You'll need to add the JWS token that we generate for the product in a `PurchaseOption` that you pass to StoreKit when you purchase: + +```swift Swift +func purchase(product: StoreProduct) async -> PurchaseResult { + guard let sk2Product = product.sk2Product else { + return .cancelled + } + + var options: Set = [] + + // Grab the introOfferToken + if let introOfferToken = product.introOfferToken { + // Add it as a PurchaseOption + options.insert(.introductoryOfferEligibility(compactJWS: introOfferToken.token)) + } + + // Pass it in to the StoreKit purchase function + let result = try await sk2Product.purchase(options: options) + + // etc... +} +``` + +## How It Works + +By default, Superwall uses Apple's StoreKit to determine if a user is eligible for an introductory offer. Apple's rules state that users can only claim an introductory offer once per subscription group. + +With this feature, you can override this behavior to: + +- **Show intro offers to returning users** who have already used a trial (useful for win-back campaigns) +- **Hide intro offers entirely** even if users are eligible +- **Use the default behavior** (let StoreKit decide) + +## Configuration + +1. Open your paywall in the Paywall Editor +2. Go to the **Products** menu in the left sidebar +3. Select an option from the **"Introductory Offer Eligibility"** dropdown +4. Publish your paywall + +### Options + +**Automatic (Default)** +Uses Apple's default eligibility rules + +**Always Eligible** +Allows users to see and claim intro offers, even if they've used one before + +**Always Ineligible** +Prevents users from seeing intro offers diff --git a/content/docs/ios/index.mdx b/content/docs/ios/index.mdx index 3d3ec2d7..f4277ac5 100644 --- a/content/docs/ios/index.mdx +++ b/content/docs/ios/index.mdx @@ -9,7 +9,11 @@ description: "Welcome to the Superwall iOS SDK documentation" Get up and running with the Superwall iOS SDK - + Most common features and use cases @@ -18,13 +22,21 @@ description: "Welcome to the Superwall iOS SDK documentation" Guides for specific use cases - + Example apps for the Superwall iOS SDK Documentation for the Superwall Dashboard - + Guides for troubleshooting common issues @@ -37,4 +49,7 @@ If you have feedback on any of our docs, please leave a rating and message at th If you have any issues with the SDK, please [open an issue on GitHub](https://github.com/superwall/superwall-ios/issues). - \ No newline at end of file + diff --git a/content/docs/ios/meta.json b/content/docs/ios/meta.json index 3ad33ed0..681c72e1 100644 --- a/content/docs/ios/meta.json +++ b/content/docs/ios/meta.json @@ -21,6 +21,7 @@ "guides/configuring", "guides/using-superwall-delegate", "guides/3rd-party-analytics", + "guides/handling-deep-links", "---SDK Reference---", "sdk-reference/index", @@ -57,8 +58,10 @@ "guides/using-revenuecat", "guides/experimental-flags", "guides/testing-purchases", + "guides/embedded-paywalls-in-scrollviews", "guides/superwall-deep-links", "guides/app-privacy-nutrition-labels", + "guides/intro-offer-eligibility-override", "guides/advanced", "guides/migrations", "[Caffeine Pal Example App](https://github.com/superwall/CaffeinePal/tree/using-superwall-sdk)", diff --git a/content/docs/ios/sdk-reference/PaywallPresentationHandler.mdx b/content/docs/ios/sdk-reference/PaywallPresentationHandler.mdx index 7a669ea5..27ab78cc 100644 --- a/content/docs/ios/sdk-reference/PaywallPresentationHandler.mdx +++ b/content/docs/ios/sdk-reference/PaywallPresentationHandler.mdx @@ -23,6 +23,7 @@ public final class PaywallPresentationHandler: NSObject { public func onDismiss(_ handler: @escaping (PaywallInfo, PaywallResult) -> Void) public func onSkip(_ handler: @escaping (PaywallSkippedReason) -> Void) public func onError(_ handler: @escaping (Error) -> Void) + public func onCustomCallback(_ handler: @escaping (CustomCallback) async -> CustomCallbackResult) } ``` @@ -54,6 +55,11 @@ public final class PaywallPresentationHandler: NSObject { description: "Sets a handler called when an error occurs during presentation.", required: true, }, + onCustomCallback: { + type: "handler: (CustomCallback) async -> CustomCallbackResult", + description: "Sets an async handler called when the paywall requests a custom callback. The handler receives a CustomCallback with a name and optional variables, and returns a CustomCallbackResult indicating success or failure. Available in version 4.12.10+.", + required: false, + }, }} /> @@ -124,6 +130,30 @@ handler.onError { error in } ``` +Handle custom callbacks (4.12.10+): +```swift +let handler = PaywallPresentationHandler() + +handler.onCustomCallback { callback in + print("Custom callback: \(callback.name)") + + // Perform your async work based on the callback name + do { + let result = try await performAction(named: callback.name, with: callback.variables) + return .success(data: ["result": result]) + } catch { + return .failure(data: ["error": error.localizedDescription]) + } +} + +Superwall.shared.register( + placement: "premium_feature", + handler: handler +) { + unlockPremiumFeature() +} +``` + Method chaining style: ```swift func registerWithChaining() { diff --git a/content/docs/ios/sdk-reference/SuperwallEvent.mdx b/content/docs/ios/sdk-reference/SuperwallEvent.mdx index ffba1209..1ffff96f 100644 --- a/content/docs/ios/sdk-reference/SuperwallEvent.mdx +++ b/content/docs/ios/sdk-reference/SuperwallEvent.mdx @@ -81,6 +81,10 @@ This is an enum that represents different event types. Events are received via [ These events are received via [`SuperwallDelegate.handleSuperwallEvent(withInfo:)`](/ios/sdk-reference/SuperwallDelegate) for forwarding to your analytics platform. ## Permission events (4.12.0+) + +The **Request permission** action for the paywall editor is rolling out and isn't visible in the dashboard yet. Editor support is coming very soon, so you may not see the action in your workspace today. + + When you wire the **Request permission** action in the paywall editor, the SDK emits `permission_requested`, `permission_granted`, and `permission_denied` events. Use them to track opt-in funnels or adapt your UI: ```swift diff --git a/content/docs/ios/sdk-reference/index.mdx b/content/docs/ios/sdk-reference/index.mdx index 311ab80c..2bdbc48c 100644 --- a/content/docs/ios/sdk-reference/index.mdx +++ b/content/docs/ios/sdk-reference/index.mdx @@ -1,6 +1,6 @@ --- title: Overview -description: Reference documentation for the Superwall iOS SDK. +description: Reference documentation for the Superwall iOS SDK. --- ## Welcome to the Superwall iOS SDK Reference @@ -15,4 +15,7 @@ If you have feedback on any of our docs, please leave a rating and message at th If you have any issues with the SDK, please [open an issue on GitHub](https://github.com/superwall/superwall-ios/issues). - \ No newline at end of file + diff --git a/content/docs/ios/sdk-reference/setIntegrationAttributes.mdx b/content/docs/ios/sdk-reference/setIntegrationAttributes.mdx index 447d1b18..91e88405 100644 --- a/content/docs/ios/sdk-reference/setIntegrationAttributes.mdx +++ b/content/docs/ios/sdk-reference/setIntegrationAttributes.mdx @@ -62,6 +62,7 @@ Common integration attributes include: - `.amplitudeUserId` - Amplitude user ID - `.mixpanelDistinctId` - Mixpanel distinct ID - `.firebaseInstallationId` - Firebase installation ID (4.10.8+) +- `.appstackId` - Appstack identifier (4.12.11+) - `.custom(String)` - Custom attribute key ## Related diff --git a/content/docs/meta.json b/content/docs/meta.json index 39c531dc..0136e017 100644 --- a/content/docs/meta.json +++ b/content/docs/meta.json @@ -9,6 +9,7 @@ "web-checkout", "integrations", "support", + "changelog", "---SDKs---", "ios", diff --git a/content/docs/support/dashboard/how-to-reset-paywall-history-for-a-b-testing.mdx b/content/docs/support/dashboard/how-to-reset-paywall-history-for-a-b-testing.mdx new file mode 100644 index 00000000..db3333cb --- /dev/null +++ b/content/docs/support/dashboard/how-to-reset-paywall-history-for-a-b-testing.mdx @@ -0,0 +1,43 @@ +--- +title: "How to Start a Fresh A/B Test" +description: "Learn the different ways to start a new A/B test with clean metrics, whether by resetting assignments or creating a new campaign." +--- + +## Answer + +When you want to run a new A/B test with clean metrics, you have two main options: **reset assignments** on an existing experiment or **create a new campaign**. Which approach to use depends on what you are testing and whether you want to preserve historical data. + +### Option 1: Reset assignments + +Best when you want to reuse the same campaign and paywall setup but start measuring from scratch. + +1. Navigate to your campaign, select the audience, and click the **Paywalls** tab to view the experiment group. +2. When editing presentation percentages, look for the **refresh icon** (circular arrow) next to where it says "X assigned" below any paywall variant. +3. Choose to **reset a single variant** to clear history for just that paywall, or **reset all paywalls** to clear history for every variant in the experiment. + +Resetting creates a new variant ID behind the scenes. All analytics for that variant, including conversions, ARPU, users, and trial metrics, start fresh from that point forward. The paywall configuration itself stays the same. + + +Resetting assignments also resets the stats for the experiment. Only reset when you are ready to start measuring from scratch. + + +### Option 2: Create a new campaign + +Best when you want to keep your previous test results intact for reference, or when you are testing a fundamentally different audience or placement. + +Create a new campaign with the paywalls you want to compare. This gives you a completely separate experiment with its own analytics, while your old campaign's data remains untouched. + +### How assignments work + +When a user matches an audience and is assigned a paywall variant, that assignment is **sticky**. The user will continue to see the same variant regardless of percentage changes. Changing presentation percentages only affects new users. + +Resetting assignments clears these sticky assignments, so all users (new and returning) will be reassigned based on the current percentages. A new campaign has no prior assignments, so all users start fresh. + +### Which option to choose + +- **Reset assignments** when you want to keep your campaign structure and do not need to reference old test data. +- **Create a new campaign** when you want to preserve previous results or are changing the audience, placement, or overall test setup. + + +Avoid duplicating paywalls just to get clean metrics. Either reset assignments or create a new campaign instead. This keeps your dashboard organized. + diff --git a/content/docs/support/dashboard/what-is-the-legacy-user-role-in-team-settings.mdx b/content/docs/support/dashboard/what-is-the-legacy-user-role-in-team-settings.mdx new file mode 100644 index 00000000..7e285c73 --- /dev/null +++ b/content/docs/support/dashboard/what-is-the-legacy-user-role-in-team-settings.mdx @@ -0,0 +1,28 @@ +--- +title: "What is the legacy User role in team settings?" +description: "The User role is a legacy role with Admin-level permissions, kept for backward compatibility. New team members should use the current roles instead." +--- + +## Answer + +The **User** role is a legacy role that exists for backward compatibility with accounts created before Superwall introduced the current role system. It has the same permissions as the **Admin** role. + +Because the name "User" can be misleading, we recommend reassigning any team members with this role to one of the current roles: + +- **Owner** -- Full control over the team and organization +- **Admin** -- Full access with limited team management +- **Editor** -- Can create and modify paywalls, campaigns, and products +- **Reader** -- View-only access + + +The User role grants Admin-level permissions, not restricted permissions. If you have team members assigned the User role, review their access and reassign them to the appropriate role. + + +### How to change a team member's role + +1. Go to **Settings** in the Superwall dashboard +2. Select the **Team** section +3. Find the team member whose role you want to change +4. Update their role to Owner, Admin, Editor, or Reader + +Only **Owners** and **Admins** can change team member roles. diff --git a/content/docs/support/dashboard/why-does-the-sdk-releases-behind-warning-still-appear.mdx b/content/docs/support/dashboard/why-does-the-sdk-releases-behind-warning-still-appear.mdx new file mode 100644 index 00000000..3be3af23 --- /dev/null +++ b/content/docs/support/dashboard/why-does-the-sdk-releases-behind-warning-still-appear.mdx @@ -0,0 +1,29 @@ +--- +title: "Why does the \"SDK releases behind\" warning still appear after updating?" +--- + +The Superwall dashboard displays an alert when it detects that your app is running an outdated SDK version. This alert appears in the **SDK Alerts & News** section of the Overview page and typically reads something like "You're X SDK releases behind." + +## Why the warning persists after updating + +The SDK version warning is based on the SDK versions reported by **all active users** of your app, not just the version you are currently developing with. When you release a new app update with the latest Superwall SDK, users who have not yet updated their app will continue reporting the older SDK version to Superwall. + +Because the warning is calculated from this aggregate data, it will remain visible as long as a meaningful portion of your user base is still running an older version of your app. + +## When will it go away? + +The warning resolves on its own as your users update to the latest version of your app. Once a sufficient percentage of your active users are running the newer SDK version, the alert will disappear from your dashboard automatically. + +The speed at which this happens depends on your app's update adoption rate, which is influenced by factors such as: + +- Whether automatic app updates are enabled on users' devices +- How quickly your user base tends to adopt new versions +- The size of your active user base + +## What you should do + +If you have already updated to the latest Superwall SDK in your app and published the update to the relevant app store, no further action is required. The warning is informational and does not affect SDK functionality or paywall behavior for users on any version. + + +This behavior applies to all Superwall SDKs, including iOS, Android, Flutter, Expo, and React Native. The dashboard aggregates version data across all active users regardless of platform. + diff --git a/content/docs/support/faq/app-store-compliance-paywall-events.mdx b/content/docs/support/faq/app-store-compliance-paywall-events.mdx new file mode 100644 index 00000000..fbcf3b9c --- /dev/null +++ b/content/docs/support/faq/app-store-compliance-paywall-events.mdx @@ -0,0 +1,53 @@ +--- +title: "Are Paywall Events and Urgency Messaging App Store Compliant?" +description: "Learn about Apple App Store compliance for paywall events like Paywall Dismissed and Transaction Abandoned, as well as urgency-style messaging." +--- + +This guide covers common App Store compliance questions about paywall interaction tracking and urgency-based messaging. + +## Paywall Events Compliance + +**Paywall Dismissed** and **Transaction Abandoned** events are compliant with Apple App Store guidelines. Tracking paywall interactions and purchase flow analytics is standard practice across the industry. + +### Follow-up Flows + +Re-engagement flows triggered by these events are also compliant, such as: + +- Showing a different paywall with a discount after a transaction is abandoned +- Presenting alternative offers when a paywall is dismissed +- Push notifications to users who showed purchase intent + + +Push notification follow-ups require that users have opted into notifications. Always respect user notification preferences. + + +## Urgency Messaging Compliance + +Apple does not explicitly ban urgency-style messaging on paywalls. Phrases like: + +- "One-time offer" +- "You'll never see this again" +- "Limited time discount" + +These are permitted, but with one key rule: **the offer must be genuine**. + +### What to Avoid + +Apple prohibits deceptive practices under their bait-and-switch guidelines. If your "one-time offer" keeps reappearing to the same user, Apple may flag this as deceptive marketing. + + +If you advertise an offer as exclusive or time-limited, ensure it actually is. Showing the same "one-time offer" repeatedly to users who declined it previously could result in App Store rejection or removal. + + +### Best Practices + +1. **Be honest** - If an offer truly won't return, you can say so +2. **Use genuine scarcity** - Time-limited offers should have real deadlines +3. **Track offer history** - Use user attributes to avoid showing "one-time" offers repeatedly +4. **Document your logic** - Be prepared to explain your offer strategy if Apple requests clarification + +## Resources + +For the complete App Store Review Guidelines, visit: + +- [Apple App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/) diff --git a/content/docs/support/faq/how-to-extract-apple-search-ads-data-from-superwall.mdx b/content/docs/support/faq/how-to-extract-apple-search-ads-data-from-superwall.mdx new file mode 100644 index 00000000..8c27a6e2 --- /dev/null +++ b/content/docs/support/faq/how-to-extract-apple-search-ads-data-from-superwall.mdx @@ -0,0 +1,69 @@ +--- +title: "How do I extract Apple Search Ads attribution data from Superwall?" +description: "Learn how to access raw Apple Search Ads attribution data from the Superwall SDK on the client side, since ASA data is not included in webhooks or third-party integration payloads." +--- + +When the [Apple Search Ads integration](/integrations/apple-search-ads) is enabled, Superwall automatically fetches ASA attribution data and stores it as user attributes on the device. This data is available in the Superwall dashboard (on user profiles, in charts, and in campaign filters), but it is **not** included in webhook payloads or third-party integration events like Mixpanel or Amplitude. + +To forward raw ASA attribution data to your own backend or analytics service, you need to read it from the SDK on the client side. + +## How ASA data flows through the SDK + +When a user opens your app, the Superwall SDK: + +1. Fetches an AdServices attribution token from Apple's `AAAttribution` API. +2. Sends the token to Superwall's servers, which resolve it into attribution details (campaign name, keyword, ad group, etc.). +3. Stores the resolved attribution data as user attributes on the device. + +Because this happens automatically, you do not need to call any Apple APIs yourself. The attribution fields become available alongside your other user attributes once the process completes. + + +There is a delay between when a user downloads your app via a search ad and when the attribution data becomes available. The data may not be present immediately on the first app launch. Avoid reading ASA attributes during app startup without checking that they exist first. + + +## Reading ASA data from the SDK + +### Swift (iOS) + +Access the `userAttributes` property on the shared `Superwall` instance. ASA fields are stored with a `$` prefix (e.g., `$asa_campaign_name`, `$asa_keyword`). + +```swift +let attributes = Superwall.shared.userAttributes + +// Check if ASA data is available +if let campaignName = attributes["$asa_campaign_name"] as? String { + print("Campaign: \(campaignName)") + + // Forward to your backend or analytics + MyAnalytics.track("asa_attribution", properties: [ + "campaign_name": campaignName, + "keyword": attributes["$asa_keyword"] as? String ?? "", + "ad_group_name": attributes["$asa_ad_group_name"] as? String ?? "", + "campaign_id": attributes["$asa_campaign_id"] as? String ?? "" + ]) +} +``` + +You can also listen for attribute changes using the `SuperwallDelegate`: + +```swift +class MySuperwallDelegate: SuperwallDelegate { + func userAttributesDidChange(newAttributes: [String: Any]) { + // Called whenever user attributes update, including when ASA data arrives + if let campaignName = newAttributes["$asa_campaign_name"] as? String { + // ASA data is now available, forward it + } + } +} +``` + +## Where ASA data is and is not available + +| Channel | ASA data available? | +| --- | --- | +| SDK user attributes (client-side) | Yes | +| Superwall dashboard (user profiles) | Yes | +| Campaign audience filters | Yes | +| Dashboard charts (breakdowns) | Yes | +| Webhooks | No | +| Third-party integrations (Mixpanel, Amplitude, etc.) | No | \ No newline at end of file diff --git a/content/docs/support/paywall-editor/localizing-paywalls-with-dynamic-values.mdx b/content/docs/support/paywall-editor/localizing-paywalls-with-dynamic-values.mdx new file mode 100644 index 00000000..bc096e69 --- /dev/null +++ b/content/docs/support/paywall-editor/localizing-paywalls-with-dynamic-values.mdx @@ -0,0 +1,43 @@ +--- +title: "Localizing Paywalls with Dynamic Values" +description: "How to display localized paywall text using dynamic values and the device locale, without needing the built-in localization feature." +--- + +You can localize your paywall text for multiple languages using dynamic values in the paywall editor. This approach uses the device's locale to conditionally display different text for each language, and works on any Superwall plan. + +### How it works + +The paywall editor exposes `device.deviceLocale` (e.g. `en_US`, `fr_FR`) and `device.deviceLanguageCode` (e.g. `en`, `fr`) as built-in variables. By combining these with dynamic values, you can set up rules that show different text depending on the user's language. + +### Step-by-step setup + +1. Open your paywall in the editor and click on the text component you want to localize. +2. Click on the text property in the component editor, then choose **Dynamic** from the dropdown. This opens the dynamic values editor. +3. Click **Add Value**, then configure a rule: + - Set the **if** condition to `device.deviceLanguageCode` **equals** your target language code (e.g. `fr` for French). + - Set the **then** value to the translated string for that language. + - Repeat this for each language you want to support. Use the **otherwise** value for your default language (typically English). +4. Apply the same approach to every text component on your paywall that needs localization. +5. Click **Publish** to save your changes. The paywall will now display the correct text based on the user's device language. + +For example: + +| Condition | Then value | +|---|---| +| `device.deviceLanguageCode` equals `fr` | Commencez votre essai gratuit | +| `device.deviceLanguageCode` equals `es` | Comienza tu prueba gratuita | +| Otherwise | Start your free trial | + + +Use `device.deviceLanguageCode` (e.g. `en`, `fr`) when you want to match broadly by language. Use `device.deviceLocale` (e.g. `en_US`, `en_GB`, `fr_FR`) when you need region-specific variants. + + +### Testing localized text + +To test your localized paywall, change your device's language in the system settings and reopen the paywall. The dynamic values will evaluate based on the updated locale. + +You can also preview different language states directly in the editor by temporarily changing the value of `device.deviceLanguageCode` in the **Variables** sidebar. + + +This approach is separate from the built-in localization feature available on the Startup plan, which provides AI-powered translations and a dedicated localization management UI. If you need to localize many paywalls or manage translations at scale, consider using the built-in localization tools instead. + diff --git a/content/docs/support/troubleshooting/how-to-test-paywalls-with-local-currency-for-a-specific-country.mdx b/content/docs/support/troubleshooting/how-to-test-paywalls-with-local-currency-for-a-specific-country.mdx new file mode 100644 index 00000000..5598b01b --- /dev/null +++ b/content/docs/support/troubleshooting/how-to-test-paywalls-with-local-currency-for-a-specific-country.mdx @@ -0,0 +1,43 @@ +--- +title: "How to Test Paywalls with Local Currency for a Specific Country" +description: "Learn how to preview your paywall as a user in a specific country would see it, including local currency and audience matching." +--- + +When you create a campaign that targets users in a specific country, you may want to preview the paywall as those users would see it, with their local currency instead of your default. This requires two changes on your test device: matching the audience filter and changing the displayed currency. + +### Matching country-based audience filters + +If your campaign uses `device.storeFrontCountryCode` to filter by country, your test device must report that same country to match the audience. On iOS, change your App Store region: + +1. Open **Settings** on your device. +2. Tap your **Apple ID** at the top. +3. Go to **Media & Purchases** and tap **View Account**. +4. Tap **Country/Region** and select the country you want to test. + + +Changing your App Store region may require you to update your payment method to one valid in that country. You can switch back after testing. + + +If your campaign uses `device.deviceLocale` instead (e.g. `en_GB`, `fr_FR`), change the device locale in **Settings > General > Language & Region** to match the target locale. + +### Displaying local currency + +The currency shown on your paywall is determined by the App Store storefront country, not the device locale. To see prices in the currency a user in that country would see, change your App Store region using the steps above. + +The App Store returns product prices in the currency of the storefront country. For example, if your App Store region is set to France, prices will appear in EUR. If set to Japan, prices will appear in JPY. + + +The actual price amount is determined by what you have configured in App Store Connect for each territory. The storefront country controls which currency and price tier is used. + + +### Summary of required changes + +| What you want to test | What to change | Where to change it | +|---|---|---| +| Match a `storeFrontCountryCode` audience filter | App Store country/region | Settings > Apple ID > Media & Purchases > View Account > Country/Region | +| Match a `deviceLocale` audience filter | Device locale | Settings > General > Language & Region | +| See prices in local currency | App Store country/region | Settings > Apple ID > Media & Purchases > View Account > Country/Region | + + +If your campaign uses `storeFrontCountryCode`, changing the App Store region handles both matching the audience filter and displaying the correct local currency in one step. + diff --git a/content/docs/support/troubleshooting/troubleshooting-old-paywall-persists-after-removal.mdx b/content/docs/support/troubleshooting/troubleshooting-old-paywall-persists-after-removal.mdx new file mode 100644 index 00000000..d439b88a --- /dev/null +++ b/content/docs/support/troubleshooting/troubleshooting-old-paywall-persists-after-removal.mdx @@ -0,0 +1,56 @@ +--- +title: "Why Are Users Still Seeing a Removed Paywall?" +description: "Troubleshoot why some users continue to see a paywall that you have removed from all campaigns, and learn how to reset sticky assignments." +--- + +## Symptoms + +- A user purchases through or sees a paywall that no longer exists in any active campaign. +- You replaced an old paywall with a new one, but some users still see the old version. +- 100% of users should be seeing a new paywall, yet reports of the old paywall persist. + +## Cause + +Superwall uses **sticky assignments** to ensure consistent user experiences during experiments. When a user is first assigned to a paywall variant, that assignment is stored on the server and persists indefinitely. This means: + +- Removing a paywall from a campaign does not automatically clear existing assignments. +- Users who were previously assigned to the old paywall will continue to see it, even if it is no longer attached to any audience. +- Changing presentation percentages or swapping paywalls only affects **new users** who have not yet been assigned. + +This behavior is intentional. It preserves experiment integrity and allows you to keep existing users on old pricing while testing new pricing with new users. However, it can be surprising when you want all users to see a new paywall immediately. + + + Calling `Superwall.shared.reset()` clears on-device data but does **not** clear server-side assignments. The old assignment will be restored the next time the user triggers the placement. + + +## Solution + +To force all previously-assigned users onto your new paywall, you need to reset the assignments in the dashboard. + + + +### Open the campaign + +Go to **Campaigns** in the Superwall dashboard and select the campaign where the old paywall was previously configured. + +### Navigate to the audience's experiment + +Click the **Paywalls** tab for the relevant audience. You should see the old paywall variant listed, possibly with a count of assigned users still shown next to it. + +### Reset the assignments + +Click the **refresh icon** next to the assignment count for the old paywall variant. This clears all stored assignments for that variant, forcing those users to be reassigned based on your current campaign configuration the next time they trigger the placement. + + + + + Resetting assignments also resets the experiment stats for that audience. If you need to preserve metrics, consider setting the old paywall's presentation percentage to 0% instead of resetting, then allowing new assignments to naturally flow to the new paywall. + + +## Preventing This in the Future + +When replacing a paywall in a campaign, keep these points in mind: + +- **Swap, then reset.** After adding the new paywall and removing the old one, reset assignments so existing users are reassigned. +- **Use presentation percentages.** If you want a gradual rollout, set the old paywall to 0% and the new one to 100%. New users will see the new paywall, but existing users will keep their current assignment until you reset. +- **Remove vs. reset.** Removing a paywall from an audience does not reset its assignments. You must explicitly reset assignments to clear them. \ No newline at end of file diff --git a/content/docs/support/troubleshooting/troubleshooting-sandbox-entitlements-persist.mdx b/content/docs/support/troubleshooting/troubleshooting-sandbox-entitlements-persist.mdx new file mode 100644 index 00000000..4aa9d731 --- /dev/null +++ b/content/docs/support/troubleshooting/troubleshooting-sandbox-entitlements-persist.mdx @@ -0,0 +1,36 @@ +--- +title: "Sandbox Entitlements Persist After Reset" +description: "Why Apple sandbox entitlements remain active after clearing purchase history and reinstalling, and how to work around this limitation." +--- + +When testing subscriptions using Apple's Sandbox environment (via TestFlight or Xcode), you may find that entitlements persist even after taking all the typical reset steps: + +- Clearing purchase history from the Sandbox Apple ID +- Removing and re-adding the Sandbox account +- Deleting and reinstalling the app +- Calling `Superwall.shared.reset()` +- Restarting the device + +This is expected behavior caused by how Apple's sandbox infrastructure caches subscription state. It is not a Superwall issue. + +## Why this happens + +Apple's sandbox environment maintains entitlement state at the server level, not just on the device. Even when you clear local data and purchase history, the sandbox server may still report the subscription as active. This state can persist for an unpredictable amount of time. + +The `Superwall.shared.reset()` method clears Superwall-specific data (user ID, paywall assignments, and cached data), but it cannot clear Apple's StoreKit entitlement state because that state is managed entirely by Apple's systems. + +## The solution: Use fresh sandbox accounts + +The most reliable way to test a clean subscription flow is to use a new sandbox account for each test. Apple allows up to 10,000 sandbox test accounts per developer account, so you can create them freely. + +**Quick tip for creating multiple accounts:** + +Sandbox accounts do not require email verification. Use email aliases with the `+` syntax to create multiple accounts from a single email address: + +- `youremail+test1@gmail.com` +- `youremail+test2@gmail.com` +- `youremail+test3@gmail.com` + +Each alias creates a distinct sandbox account while all emails still arrive at your main inbox. + +When you need a guaranteed clean slate for testing subscriptions, always use a fresh sandbox account. diff --git a/content/docs/support/web-checkout/how-to-retrieve-stripe-customer-data-after-web-checkout.mdx b/content/docs/support/web-checkout/how-to-retrieve-stripe-customer-data-after-web-checkout.mdx new file mode 100644 index 00000000..0b8fb8fb --- /dev/null +++ b/content/docs/support/web-checkout/how-to-retrieve-stripe-customer-data-after-web-checkout.mdx @@ -0,0 +1,66 @@ +--- +title: "How do I retrieve Stripe customer data after web checkout?" +description: "Learn how to access the Stripe customer ID and email after a user completes a web checkout purchase." +--- + +## Context + +After a user completes a web checkout purchase via Stripe, you may need to access their Stripe customer ID and email for integration with your backend systems or third-party services. + +## Answer + +Use the `didRedeemLink(result:)` delegate method to access Stripe customer data when a redemption link is successfully processed. + +### Accessing Stripe customer ID and email + +When the redemption result is successful, the `RedemptionInfo` object contains a `PurchaserInfo` with the customer's email and store identifiers: + +```swift +func didRedeemLink(result: RedemptionResult) { + guard case let .success(_, redemptionInfo) = result else { return } + + // Get the customer's email + let email = redemptionInfo.purchaserInfo.email + + // Get the Stripe customer ID and subscription IDs + if case let .stripe(customerId, subscriptionIds) = redemptionInfo.purchaserInfo.storeIdentifiers { + print("Stripe Customer ID: \(customerId)") + print("Subscription IDs: \(subscriptionIds)") + + // Send to your backend or analytics + sendToBackend( + stripeCustomerId: customerId, + email: email, + subscriptionIds: subscriptionIds + ) + } +} +``` + +### What happens if the user kills the app during checkout? + +If a user completes the Stripe checkout but terminates the app before returning, the `didRedeemLink(result:)` callback will not fire. However, their purchase is not lost. + +**Recovery mechanisms:** + +1. **Redemption email**: After a successful Stripe checkout, Superwall automatically sends the customer an email with an activation link. When they tap it, your app opens and `didRedeemLink` fires with the Stripe customer ID and email. + +2. **Plan management page**: Users can visit `https://{yoursubdomain}.superwall.app/manage`, enter their email, and receive a new redemption link. + +3. **Automatic entitlement sync**: The SDK polls for web entitlements when the app enters foreground, so subscription status updates automatically. However, this sync only updates entitlements and does not trigger `didRedeemLink`, meaning you will not receive the Stripe customer ID or email through this path. + + +To programmatically receive the Stripe customer ID and email, the user must tap a redemption link (either from the automatic email or the manage page). + + +### Stripe metadata + +Superwall automatically includes your app user ID in the Stripe checkout session and subscription metadata. The key `_sw_app_user_id` contains the user ID you set via `Superwall.shared.identify(userId:)`. + +This allows you to correlate Stripe subscriptions with your users directly in Stripe or through webhooks. + +## Related + +- [Post-Checkout Redirecting](/sdk/guides/web-checkout/post-checkout-redirecting) +- [How do I disable the activation link email for web checkout?](/support/web-checkout/3969573187-how-do-i-disable-the-activation-link-email-for-web-checkout) +- [Web Checkout FAQ](/web-checkout/web-checkout-faq) diff --git a/content/docs/support/web-checkout/web-checkout-revenue-tracking-is-automatic.mdx b/content/docs/support/web-checkout/web-checkout-revenue-tracking-is-automatic.mdx new file mode 100644 index 00000000..0c70d42a --- /dev/null +++ b/content/docs/support/web-checkout/web-checkout-revenue-tracking-is-automatic.mdx @@ -0,0 +1,45 @@ +--- +title: "Why is there no revenue tracking setup for web checkout?" +description: "Web checkout revenue tracking is automatic when using Stripe, unlike iOS and Android which require manual configuration." +--- + +When using Superwall's web checkout with Stripe, revenue tracking is **automatic**. There is no separate "Revenue Tracking" setup required like there is for iOS (App Store Connect) or Android (Google Play). + +## Why the difference? + +For native mobile apps, Superwall needs you to configure server notifications from Apple or Google because those payment systems are external to Superwall. The revenue events must be forwarded to Superwall so they can be associated with paywalls and campaigns. + +With web checkout, payments flow directly through Superwall's integration with Stripe. Superwall processes these transactions and automatically captures all revenue data, including: + +- Subscription purchases +- One-time purchases +- Renewals +- Cancellations +- Refunds + +## What you need to do + +1. **Configure your Stripe keys** in your Superwall app's [Settings](/web-checkout-configuring-stripe-keys-and-settings) +2. **Create products** in Stripe and [add them to Superwall](/web-checkout-adding-a-stripe-product) +3. **Add products to your paywalls** and start showing them + +Once a purchase completes through your web checkout paywall, conversions and revenue will appear automatically in your Superwall dashboard. + + +It can take a few minutes for new transactions to appear in your dashboard metrics after a purchase completes. + + +## Troubleshooting missing conversions + +If you have completed purchases in Stripe but no conversions showing in Superwall: + +1. **Verify your Stripe keys are configured correctly** - Check that both your Publishable Key and Secret Key are entered in Settings and show "Configured" +2. **Check you're using the correct mode** - If testing, ensure you're using Sandbox keys with test purchases. Production keys are required for live transactions. +3. **Confirm the purchase was made through Superwall** - Only purchases initiated through Superwall paywalls will be tracked. Direct Stripe purchases outside of Superwall will not appear. +4. **Allow time for data to sync** - New transactions may take a few minutes to appear in your analytics + +## Related + +- [Stripe Setup](/web-checkout-configuring-stripe-keys-and-settings) +- [Revenue Tracking for iOS and Android](/dashboard/dashboard-settings/overview-settings-revenue-tracking) +- [Web Checkout Overview](/web-checkout-overview) diff --git a/content/docs/using-referral-or-promo-codes-with-superwall.mdx b/content/docs/using-referral-or-promo-codes-with-superwall.mdx index 4be78c0f..ab60477a 100644 --- a/content/docs/using-referral-or-promo-codes-with-superwall.mdx +++ b/content/docs/using-referral-or-promo-codes-with-superwall.mdx @@ -22,17 +22,18 @@ Given that information, here is how most referral flows can work: - ```mermaid -flowchart TD - A([Paywall is shown]) --> B[Referral Code claim
button is tapped] - B --> C[Custom Action is fired
from button tap] - C --> D[Responding in your Superwall Delegate,
you present your own referral UI
over the existing paywall] + + {`flowchart TD + A([Paywall is shown]) --> B[Referral Code claim\\nbutton is tapped] + B --> C[Custom Action is fired\\nfrom button tap] + C --> D[Responding in your Superwall Delegate,\\nyou present your own referral UI\\non top of the existing paywall] D --> E{Code is valid?} - E -- NO --> F[Dismiss referral UI,
existing paywall is still presented] - E -- YES --> G[Dismiss referral UI,
dismiss existing paywall.
Register placement for the referral code.] - G --> H[Present new paywall with
discounted product.
In Superwall, you can use campaign data to
attribute conversions, trials, etc.] -``` + E -- NO --> F[Dismiss referral UI,\\nexisting paywall is still presented] + E -- YES --> G[Dismiss referral UI,\\ndismiss existing paywall.\\nRegister placement for the referral code.] + G --> H[Present new paywall with discounted product.\\nIn Superwall, you can use campaign data\\nto attribute conversions, trials, etc.] + `} +
Now, let's go through each step in detail. We'll assume that you're triggering this flow from a paywall that's already presented, but if that's not the case — just skip to the step where you present your referral entry UI (step four): @@ -67,6 +68,11 @@ Now, let's go through each step in detail. We'll assume that you're triggering t ``` Using the [`SuperwallDelegate`](/using-superwall-delegate), the app responds to the custom action. It presents a customized referral redemption interface _on top_ of the existing paywall. This is optional, but presenting over the paywall means that the user will be taken back to it if the redemption code entry fails: + + + Presenting custom UI on top of an existing paywall is only supported in the native iOS and Android SDKs. + + ```swift @Observable class SWDelegate: SuperwallDelegate { @@ -160,4 +166,4 @@ Now, let's go through each step in detail. We'll assume that you're triggering t
-
\ No newline at end of file + diff --git a/content/docs/web-checkout/meta.json b/content/docs/web-checkout/meta.json index d3ed2af8..5e16c61a 100644 --- a/content/docs/web-checkout/meta.json +++ b/content/docs/web-checkout/meta.json @@ -8,11 +8,11 @@ "---Getting Started---", "web-checkout-creating-an-app", "web-checkout-configuring-stripe-keys-and-settings", - "web-checkout-paddle-setup", "web-checkout-adding-a-stripe-product", "web-checkout-creating-campaigns-to-show-paywalls", "---Implementation---", + "web-checkout-sdk-setup", "web-checkout-direct-stripe-checkout", "web-checkout-testing-purchases", "web-checkout-managing-memberships", diff --git a/content/docs/web-checkout/web-checkout-adding-a-stripe-product.mdx b/content/docs/web-checkout/web-checkout-adding-a-stripe-product.mdx index 98918e65..e4526e44 100644 --- a/content/docs/web-checkout/web-checkout-adding-a-stripe-product.mdx +++ b/content/docs/web-checkout/web-checkout-adding-a-stripe-product.mdx @@ -1,23 +1,16 @@ --- title: "Creating Products" -description: "Create products in Stripe or Paddle (private beta) to show on your web paywalls." +description: "Create products in Stripe to show on your web paywalls." --- - - Paddle support is currently in private beta. Contact your Superwall representative to enable it, then follow the [Paddle setup guide](/dashboard/web-checkout/web-checkout-paddle-setup). - - ### Adding products -Once your app is configured with Stripe or Paddle, you can create products in your provider of choice to show on your web paywalls. To get started, **click** on **Products** from the overview page: +Once your app is configured with Stripe, you can create products to show on your web paywalls. To get started, **click** on **Products** from the overview page: ![](/images/web2app_create_product.png) - Complete your provider setup first: - - - **Stripe:** Follow the steps in [Configuring Stripe Keys and Settings](/web-checkout/web-checkout-configuring-stripe-keys-and-settings). - - **Paddle (private beta):** Follow the steps in [Configuring Paddle Keys and Settings](/web-checkout/web-checkout-paddle-setup). + Complete your Stripe setup first by following the steps in [Configuring Stripe Keys and Settings](/web-checkout/web-checkout-configuring-stripe-keys-and-settings). Next, **click** on the **+ Import Products** button in the top right corner: @@ -72,15 +65,9 @@ Now, when you return to Superwall, select your product from the **Products** dro Be sure to associate the correct entitlement to the product as well. -### Using Paddle products (private beta) - -If you're part of the Paddle beta, the import flow mirrors Stripe. Choose **Paddle** as the provider when importing, then select the product to pull in its data. Paddle sandbox and live products are labeled so you can keep environments separate. For more specifics on Paddle credentials, see [Configuring Paddle Keys and Settings](/web-checkout/web-checkout-paddle-setup). - -You can also create new products directly from Paddle by visiting the Paddle dashboard. Define subscription plans and prices there, then return to Superwall and import them into your paywall. - ### Adding products to paywalls -Adding Stripe or Paddle products to web paywalls works the exact same way as it does for mobile paywalls. Check out the docs [here](/paywall-editor-products). For a quick overview: +Adding Stripe products to web paywalls works the exact same way as it does for mobile paywalls. Check out the docs [here](/paywall-editor-products). For a quick overview: 1. Open the paywall editor. 2. On the left sidebar click on **Products**. diff --git a/content/docs/web-checkout/web-checkout-configuring-stripe-keys-and-settings.mdx b/content/docs/web-checkout/web-checkout-configuring-stripe-keys-and-settings.mdx index c8217fe7..2564a386 100644 --- a/content/docs/web-checkout/web-checkout-configuring-stripe-keys-and-settings.mdx +++ b/content/docs/web-checkout/web-checkout-configuring-stripe-keys-and-settings.mdx @@ -1,9 +1,9 @@ --- title: "Stripe Setup" -description: "Create your Stripe keys to connect Superwall to Stripe. Fill out some settings to configure your app." +description: "Connect Superwall to Stripe using the official Stripe app and configure your settings." --- -Once you've created a [Stripe app](/web-checkout-creating-an-app), you'll need to configure it with your Stripe keys and fill in a few settings. This is a one-time setup that connects Superwall to Stripe. The easiest way to get started is to click on the link in your overview page, which will take you to your app's [Settings](/overview-settings) page: +Once you've created a [Stripe app](/web-checkout-creating-an-app), you'll need to connect it with Stripe and fill in a few settings. This is a one-time setup that connects Superwall to Stripe. The easiest way to get started is to click on the link in your overview page, which will take you to your app's [Settings](/overview-settings) page: ![](/images/web2app_prompt.jpeg) @@ -52,48 +52,86 @@ This is the domain your paywalls will be shown from. This was set when the Strip ![](/images/web-checkout-domain-section.png) ### Stripe Live Configuration -This section allows you to connect Stripe keys with Superwall. You will need: +To connect Stripe with Superwall, you'll use the official Superwall Stripe app: -1. **Publishable Key:** A Stripe publishable key. Stripe creates this key for you, you don't need to generate it yourself. -2. **Secret Key:** A Stripe secret key that you create. Once you've made one, paste it here. +#### Step 1: Visit the Stripe Marketplace +Visit the [Superwall app on Stripe Marketplace](https://marketplace.stripe.com/apps/superwall): -To access these, click on the **[API Keys](https://dashboard.stripe.com/apikeys)** link: +![](/images/stripe/step-1-marketplace.png) -![](/images/web2app_live_api_keys.png) +#### Step 2: Install the App +Click **Install app** to begin the authorization flow: -Under **Restricted Keys**, click on **+ Create restricted key**: +![](/images/stripe/step-2-install.png) -![](/images/web2app_rkey.png) +#### Step 3: Continue Installation +Click **Continue** to proceed with the installation: -Choose "Providing this key to another website" and click **Continue ->**: +![](/images/stripe/step-3-continue.png) -![](/images/web2app_rkey_make.png) +#### Step 4: Generate API Keys +Click **Generate keys** to create your API keys: -Use "Superwall" as the name and "superwall.com" as the URL, then click **Create restricted key**: +![](/images/stripe/step-4-generate-key.png) -![](/images/web2app_rkey_name.png) +#### Step 5: Confirm Key Generation +Click **Generate keys** again to confirm: -You'll get a modal of your restricted key, **copy this to your clipboard**, you won't be able to view it again: +![](/images/stripe/step-5-generate-key-2.png) -![](/images/web2app_key.png) +#### Step 6: Copy Your Keys +After installation, you'll receive a **Publishable Key** and a **Restricted Secret Key** with the proper permissions already configured. Copy both keys: -From there, copy your **Publishable Key** and **copied key** from the Stripe dashboard to Superwall: +![](/images/stripe/step-6-copy-keys.png) -![](/images/web2app_associatekeys.png) +#### Step 7: Configure Superwall +Paste the keys into the corresponding fields in Superwall: +- **Publishable Key**: Your Stripe publishable key from the app +- **Secret Key**: The restricted secret key provided by the app -Once you've provided those two keys, **click** on **Update Configuration** to save your changes. This section should say "Configured" at the top right if setup was successful: +Then **click** on **Update Configuration** to save your changes. + +This section should say "Configured" at the top right if setup was successful: ![](/images/web2app_live_config.png) ### Stripe Sandbox Configuration -For the sandbox configuration, you'll follow the same previous steps, **except you retrieve the keys from this link**: [Stripe Sandbox API Keys](https://dashboard.stripe.com/test/apikeys). + + The Superwall Stripe app provides both live and test mode keys. Make sure you're in the correct mode in your Stripe dashboard when copying keys. + + +For sandbox/test mode, switch to **Test mode** in your Stripe dashboard (toggle in the top-right corner), then follow these steps: + +#### Step 1: Access the Superwall App +Navigate to the Superwall app in your Stripe dashboard. If already installed, find it under **Apps**: + +![](/images/stripe-test/step-1-install-page.png) + +#### Step 2: Install in Test Mode +If you haven't installed the app in test mode yet, click **Install app**: + +![](/images/stripe-test/step-2-install.png) + +#### Step 3: Continue Installation +Click **Continue** to proceed: + +![](/images/stripe-test/step-3-continue.png) + +#### Step 4: Reveal Your API Keys +Click **Reveal test key** to view your test mode API keys: + +![](/images/stripe-test/step-4-reveal-api-key.png) + +#### Step 5: Copy Your Test Keys +Copy both the **Publishable Key** and **Secret Key**: -You should see something similar to this: +![](/images/stripe-test/step-5-copy-api-key.png) -![](/images/web2app_testKey.png) +#### Step 6: Configure Superwall +Paste both keys into the Sandbox Configuration fields in Superwall, then **click** on the **Update Configuration** button. -Paste the both the **Publishable Key** and **Secret Key** into Superwall for each respective field and **click** on the **Update Configuration** button. As before, this section should say "Configured" at the top right if setup was successful. +This section should say "Configured" at the top right if setup was successful. ### iOS configuration diff --git a/content/docs/web-checkout/web-checkout-creating-campaigns-to-show-paywalls.mdx b/content/docs/web-checkout/web-checkout-creating-campaigns-to-show-paywalls.mdx index ebbd832f..b11ebafa 100644 --- a/content/docs/web-checkout/web-checkout-creating-campaigns-to-show-paywalls.mdx +++ b/content/docs/web-checkout/web-checkout-creating-campaigns-to-show-paywalls.mdx @@ -3,7 +3,7 @@ title: "Web Checkout Links" description: "Learn how to use campaigns and placements to present web paywalls using Superwall's web checkout links." --- -Once you've [created a Stripe app](/web-checkout-creating-an-app), [configured Stripe with Superwall](web-checkout-configuring-stripe-keys-and-settings) and have [created Stripe products](web-checkout-adding-a-stripe-product) — you're ready to configure a campaign to show a web paywall. +Once you've [created a Stripe app](/web-checkout-creating-an-app), [configured Stripe with Superwall](web-checkout-configuring-stripe-keys-and-settings) via the [Superwall Stripe app](https://marketplace.stripe.com/apps/superwall) and have [created Stripe products](web-checkout-adding-a-stripe-product) — you're ready to configure a campaign to show a web paywall. Before you proceed, recall that web checkout has all of the advantages of the Superwall platform. If you are unfamiliar with how to create campaigns or what a placement is — we recommend you read through the [introduction](/home) documentation and [campaigns doc](/campaigns) first. diff --git a/content/docs/web-checkout/web-checkout-direct-stripe-checkout.mdx b/content/docs/web-checkout/web-checkout-direct-stripe-checkout.mdx index a1fdc543..9c461809 100644 --- a/content/docs/web-checkout/web-checkout-direct-stripe-checkout.mdx +++ b/content/docs/web-checkout/web-checkout-direct-stripe-checkout.mdx @@ -9,7 +9,7 @@ For customers in the United States, you can offer Stripe products directly from - First, follow the [web checkout setup guide](/web-checkout-overview#getting-setup) to create a Stripe app and configure your web checkout settings. Specifically, you'll need to complete the first three steps. This includes setting up your Stripe keys and configuring your app's settings. + First, follow the [web checkout setup guide](/web-checkout-overview#getting-setup) to create a Stripe app and configure your web checkout settings. Specifically, you'll need to complete the first three steps. This includes installing the [Superwall Stripe app](https://marketplace.stripe.com/apps/superwall) and setting up your app's settings. Select a paywall and add a Stripe product to it. This will allow users to purchase the product directly from the paywall. Stripe products are prepended with "stripe" in the product selector: diff --git a/content/docs/web-checkout/web-checkout-overview.mdx b/content/docs/web-checkout/web-checkout-overview.mdx index a9caa909..27c9320d 100644 --- a/content/docs/web-checkout/web-checkout-overview.mdx +++ b/content/docs/web-checkout/web-checkout-overview.mdx @@ -1,9 +1,9 @@ --- title: "Overview" -description: "Let customers purchase products online via Stripe or Paddle (private beta), then link them to your iOS app with one seamless flow. No authentication required." +description: "Let customers purchase products online via Stripe, then link them to your iOS app with one seamless flow. No authentication required." --- -Superwall's web checkout integration makes it easy to set up purchasing funnels for your app via the web. Web checkout is powered by Stripe or Paddle (private beta). Once an online purchase is complete, the customer will be redirected back to your app with a deep link that can be used to unlock content or features in your app via any associated [entitlement](/products#entitlements). +Superwall's web checkout integration makes it easy to set up purchasing funnels for your app via the web. Web checkout is powered by Stripe. Once an online purchase is complete, the customer will be redirected back to your app with a deep link that can be used to unlock content or features in your app via any associated [entitlement](/products#entitlements). Web checkout requires the Superwall iOS SDK 4.2.0 or later. @@ -39,13 +39,11 @@ Refer to the individual pages below to get started, but for a quick, high-level ## Getting setup -Before you start, you'll need to have a Superwall account and an account with your chosen provider. You can create a Stripe account [here](https://dashboard.stripe.com/register) or a Paddle account [here](https://vendors.paddle.com/register). +Before you start, you'll need to have a Superwall account and a Stripe account. You can create a Stripe account [here](https://dashboard.stripe.com/register). 1. **[Creating an app](/web-checkout-creating-an-app):** First, you'll add a Web Checkout app to an existing project within Superwall. -2. Payment provider setup: - - **[Stripe setup](/web-checkout-configuring-stripe-keys-and-settings):** - - **[Paddle setup (private beta)](/web-checkout-paddle-setup):** -4. **[Managing products](/web-checkout-adding-a-stripe-product):** Create or import products to add to your web paywalls. +2. **[Stripe setup](/web-checkout-configuring-stripe-keys-and-settings):** Install the [Superwall Stripe app](https://marketplace.stripe.com/apps/superwall) for automatic configuration. +3. **[Managing products](/web-checkout-adding-a-stripe-product):** Create or import products to add to your web paywalls. ### Creating paywalls and campaigns @@ -54,7 +52,7 @@ Before you start, you'll need to have a Superwall account and an account with yo ### Associating entitlements to your iOS apps 5. **[Linking purchases to your iOS app](/web-checkout-linking-membership-to-iOS-app):** Once a purchase occurs, the user will be prompted to download your app and click on a redemption link. -6. **[Managing memberships](/web-checkout-managing-memberships):** Users can cancel, update or manage their memberships via Stripe or Paddle. +6. **[Managing memberships](/web-checkout-managing-memberships):** Users can cancel, update or manage their memberships via Stripe. ### Testing purchases diff --git a/content/docs/web-checkout/web-checkout-paddle-setup.mdx b/content/docs/web-checkout/web-checkout-paddle-setup.mdx deleted file mode 100644 index 545614be..00000000 --- a/content/docs/web-checkout/web-checkout-paddle-setup.mdx +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: "Paddle Setup" -description: "Connect Paddle to Superwall web checkout." ---- - - - Paddle support is currently in private beta. - - -Once you've created a [Web Checkout app](/web-checkout/web-checkout-creating-an-app), you'll need to configure it with your Paddle keys and fill in a few settings. This is a one-time setup that connects Superwall to Paddle. The easiest way to get started is to click on the link in your overview page, which will take you to your app's [Settings](/overview-settings) page: - -## Application settings -Fill out the metadata about your iOS app just like you do for Stripe. These fields control the branding and support details that appear on the checkout and subscription management pages. - -1. **Icon:** Use the same marketing icon that appears in your Paddle checkout. Square PNG files work best. -2. **Application Name:** We recommend matching the App Store name so customers recognize it instantly. -3. **Support URL:** A URL where customers can self-serve billing questions. Paddle will link to this from receipts and the customer hub. -4. **Support Email:** The inbox your support team monitors for billing issues. -5. **Redeemable on Desktop:** Enable this if customers can redeem subscriptions on macOS via Catalyst or iPad on Mac. Otherwise leave it disabled so redemptions happen on iOS only. - -Once you've filled out this information, click **Update Application**. - -## Web Paywall Domain -The domain you set when creating the Paddle app is used for every Paddle checkout flow. It cannot be changed once the app is created. - -## Live configuration - -### Step 1: API keys -In your [Paddle dashboard](https://vendors.paddle.com/), go to **Developers → [API Keys](https://vendors.paddle.com/)**. Copy the values for: - -1. **Publishable Key** -2. **API Key** - -Paste both keys into Superwall and click **Update Configuration**. - -### Step 2: Redirect URL -Copy the **Paddle Redirect URL** shown in the Superwall dashboard and paste it into your Paddle webhook settings. This allows Superwall to receive post-checkout events. - -### Step 3: Hosted Checkout URL -Enter the **Hosted Checkout URL** from **Checkout → Hosted Checkouts** in Paddle. This URL is required for processing Paddle payments, so double-check that you're using the correct environment (live vs sandbox). - - - Hosted checkout URLs are environment-specific. Make sure you copy the live URL when configuring the live environment. - - -## Sandbox configuration -Repeat the same steps for the sandbox environment using the [sandbox Paddle dashboard](https://sandbox-vendors.paddle.com/). Use the sandbox API keys, redirect URL, and hosted checkout URL, then click **Save** to finish the sandbox setup. - -## iOS configuration -These settings control the deep link handoff back to your iOS app, and they are identical whether you use Stripe or Paddle. - -1. **Apple Custom URL Scheme:** Add the scheme you registered in Xcode. If you need help, review [Setting up deep links](/sdk/quickstart/in-app-paywall-previews). -2. **Apple App ID:** Find this in [App Store Connect](https://appstoreconnect.apple.com) under **General → App Information**. -3. **Bundle ID:** Located in Xcode under **Targets → General → Identity**. -4. **Team ID:** Available in the [Apple Developer account portal](https://developer.apple.com/account/#/membership/). - -After saving, all configuration sections should display **Configured**. You're now ready to [create products](/dashboard/web-checkout/web-checkout-adding-a-stripe-product) in Paddle and import them into Superwall paywalls. - -## Next steps - -- **Add Paddle products:** Follow the combined [product guide](/web-checkout/web-checkout-adding-a-stripe-product) for Stripe and Paddle. -- **Build paywalls and campaigns:** Continue with [Creating campaigns to show paywalls](/web-checkout/web-checkout-creating-campaigns-to-show-paywalls). -- **Test purchases:** Use the Paddle sandbox credentials with [Testing purchases](/web-checkout/web-checkout-testing-purchases). - diff --git a/content/docs/web-checkout/web-checkout-sdk-setup.mdx b/content/docs/web-checkout/web-checkout-sdk-setup.mdx new file mode 100644 index 00000000..bb19606e --- /dev/null +++ b/content/docs/web-checkout/web-checkout-sdk-setup.mdx @@ -0,0 +1,39 @@ +--- +title: "SDK Setup" +description: "Platform-specific setup guides for Web Checkout implementation" +--- + +Choose your platform below to get started with Web Checkout in your app: + + + + Setup Web Checkout for iOS apps with Swift + + + Setup Web Checkout for Android apps with Kotlin/Java + + + Setup Web Checkout for Flutter apps + + + Setup Web Checkout for Expo apps + + + +## What's Next? + +After setting up Web Checkout for your platform: + +1. **Configure Deep Links**: Each platform requires deep link setup to redirect users back to your app after purchase +2. **Handle Redemption**: Implement delegate methods to handle purchase redemption +3. **Test Your Integration**: Use test mode to verify your implementation before going live + +## Platform-Specific Features + +Each SDK provides platform-specific features for Web Checkout: + +- **Post-Checkout Redirecting**: Handle users returning to your app after purchase +- **Using RevenueCat**: Integrate Web Checkout with RevenueCat for subscription management +- **Linking Membership**: Connect web purchases to in-app subscriptions + +Visit your platform's guide above to learn more about these features and implementation details. diff --git a/content/docs/web-checkout/web-checkout-web-only.mdx b/content/docs/web-checkout/web-checkout-web-only.mdx index 30c43a4f..312b3ef2 100644 --- a/content/docs/web-checkout/web-checkout-web-only.mdx +++ b/content/docs/web-checkout/web-checkout-web-only.mdx @@ -32,11 +32,7 @@ Create a new app in Superwall for your web product in a new or existing project. ### 2. Configure Your Payment Provider -Set up your Stripe: - -- [Stripe Setup](/web-checkout-configuring-stripe-keys-and-settings) - -Paddle is not currently supported in this flow, but it does work in app to web scenarios. +Set up Stripe by following the [Stripe Setup](/web-checkout-configuring-stripe-keys-and-settings) guide. You can skip any iOS/Android related configuration sections since you won't be using a mobile app. diff --git a/content/shared/3rd-party-analytics/tracking-analytics.mdx b/content/shared/3rd-party-analytics/tracking-analytics.mdx index 72628785..f9ab302a 100644 --- a/content/shared/3rd-party-analytics/tracking-analytics.mdx +++ b/content/shared/3rd-party-analytics/tracking-analytics.mdx @@ -57,7 +57,6 @@ The full list of events is as follows: | `paywallWebviewLoad_fail` | When a paywall's webpage fails to load. | Same as `paywall_close` | | `paywallWebviewLoad_fallback` | When a paywall's webpage fails and loads a fallback version. | Same as `paywall_close` | | `paywallWebviewLoad_start` | When a paywall's webpage begins to load. | Same as `paywall_close` | -| `paywallWebviewLoad_timeout` | When the loading of a paywall's webpage times out. | Same as `paywall_close` | | `paywallWebviewLoad_processTerminated` | When the paywall's web view content process terminates. | Same as `paywall_close` | | `reset` | When `Superwall.reset()` is called. | None | | `restoreComplete` | When a restore completes successfully. | None | diff --git a/content/shared/handling-deep-links.mdx b/content/shared/handling-deep-links.mdx new file mode 100644 index 00000000..3e3599fb --- /dev/null +++ b/content/shared/handling-deep-links.mdx @@ -0,0 +1,205 @@ +--- +title: "Handling Deep Links" +description: "Use handleDeepLink and campaign rules to present paywalls from deep links without hardcoding logic in your app." +--- + +When your app receives a deep link, you might be tempted to write a switch statement that maps each URL to a specific placement and calls `register`. This works, but it means every time you add a new link or change which paywall shows, you have to ship an app update. + +A better approach is to pass the URL to `handleDeepLink` and let Superwall's [`deepLink_open`](/campaigns-standard-placements#deeplink_open) standard placement handle the rest. The SDK extracts the URL's path, query parameters, and other components, then fires `deepLink_open` as a placement. You write campaign rules on the dashboard to decide which paywall to show, which means there is no app update required. + +## The problem + +Here's a common pattern where deep link routing is hardcoded in the app: + +:::ios +```swift +func handleURL(_ url: URL) { + let placement: String? = switch url.path { + case "/promo": "promoPlacement" + case "/onboarding": "onboardingPlacement" + case "/upgrade": "upgradePlacement" + case "/special-offer": "specialOfferPlacement" + default: nil + } + + if let placement { + Superwall.shared.register(placement: placement) + } +} +``` +::: + +:::android +```kotlin +fun handleUrl(url: Uri) { + val placement = when (url.path) { + "/promo" -> "promoPlacement" + "/onboarding" -> "onboardingPlacement" + "/upgrade" -> "upgradePlacement" + "/special-offer" -> "specialOfferPlacement" + else -> null + } + + placement?.let { + Superwall.instance.register(placement = it) + } +} +``` +::: + +:::flutter +```dart +void handleUrl(Uri url) { + final placement = switch (url.path) { + '/promo' => 'promoPlacement', + '/onboarding' => 'onboardingPlacement', + '/upgrade' => 'upgradePlacement', + '/special-offer' => 'specialOfferPlacement', + _ => null, + }; + + if (placement != null) { + Superwall.shared.register(placement: placement); + } +} +``` +::: + +:::expo +```typescript +function handleUrl(url: string) { + const path = new URL(url).pathname; + let placement: string | undefined; + + switch (path) { + case "/promo": + placement = "promoPlacement"; + break; + case "/onboarding": + placement = "onboardingPlacement"; + break; + case "/upgrade": + placement = "upgradePlacement"; + break; + case "/special-offer": + placement = "specialOfferPlacement"; + break; + } + + if (placement) { + superwall.register(placement); + } +} +``` +::: + +Every new URL path means a code change, a build, and an app store review. If you want to change which paywall shows for `/promo`, that's another update too. + +## The solution: `handleDeepLink` + campaign rules + +Instead, pass the URL to `handleDeepLink`. The SDK fires the `deepLink_open` standard placement with all of the URL's components as parameters. Then, on the Superwall dashboard, you create campaign rules that match on those parameters to decide what to show. + +:::ios +```swift +func handleURL(_ url: URL) { + Superwall.handleDeepLink(url) +} +``` +::: + +:::android +```kotlin +fun handleUrl(url: Uri) { + Superwall.instance.handleDeepLink(url) +} +``` +::: + +:::flutter +```dart +Future handleUrl(Uri url) async { + await Superwall.shared.handleDeepLink(url); +} +``` +::: + +:::expo +```typescript +function handleUrl(url: string) { + SuperwallExpoModule.handleDeepLink(url); +} +``` +::: + +That's it on the app side. The routing logic lives on the dashboard. + +## Setting up campaign rules + +Once `handleDeepLink` is wired up, the `deepLink_open` placement fires every time a deep link arrives. The URL's path, host, query parameters, and other components are available as parameters you can match against in your campaign's audience filters. + + + + On the Superwall dashboard, create a new [campaign](/campaigns) — for example, "Deep Link Paywalls". + + + In your campaign, [add a placement](/campaigns-placements#adding-a-placement) and select `deepLink_open` from the standard placements list. + + + Edit the default audience and add filters that match the URL components you care about. For example, if your deep link is `myapp://promo?offer=summer`: + - Set `params.path` **is** `promo` to match the path. + - Set `params.offer` **is** `summer` to match the query parameter. + + See [`deepLink_open` parameters](/campaigns-standard-placements#deeplink_open) for the full list of available fields. + + + Click **Paywalls** at the top of the campaign and choose which paywall to present when the filters match. + + + +Now when a user opens `myapp://promo?offer=summer`, the SDK fires `deepLink_open`, the campaign rule matches, and the paywall shows. That's all without touching your app code. To add a new deep link path or change which paywall it shows, just update the campaign on the dashboard. + +## Multiple deep link routes + +You can handle several deep link patterns from a single campaign by adding multiple audiences, each with its own filters and paywalls. For example: + +| Deep link | Filter | Paywall | +|-----------|--------|---------| +| `myapp://promo?offer=summer` | `params.path` is `promo` AND `params.offer` is `summer` | Summer Sale | +| `myapp://promo?offer=newyear` | `params.path` is `promo` AND `params.offer` is `newyear` | New Year Offer | +| `myapp://upgrade` | `params.path` is `upgrade` | Upgrade Paywall | + +Each audience evaluates independently. When you need to add a new route, create a new audience on the dashboard — no app update needed. + +## Prerequisites + +To use `handleDeepLink`, your app needs deep link handling set up first. If you haven't done that yet, follow the setup guide: + +:::ios +- [Deep link setup](/ios/quickstart/in-app-paywall-previews) +::: +:::android +- [Deep link setup](/android/quickstart/in-app-paywall-previews) +::: +:::flutter +- [Deep link setup](/flutter/quickstart/in-app-paywall-previews) +::: +:::expo +- [Deep link setup](/expo/quickstart/in-app-paywall-previews) +::: + +## Related deep link guides + +:::ios +- [Deep Link Setup](/ios/quickstart/in-app-paywall-previews) — Configure URL schemes, universal links, and wire `handleDeepLink` into your app so Superwall can respond to incoming links. +- [Using Superwall Deep Links](/ios/guides/superwall-deep-links) — Trigger paywalls or custom in-app behavior using Superwall-hosted URLs at `*.superwall.app/app-link/...`. +::: +:::android +- [Deep Link Setup](/android/quickstart/in-app-paywall-previews) — Configure URL schemes and wire `handleDeepLink` into your app so Superwall can respond to incoming links. +::: +:::flutter +- [Deep Link Setup](/flutter/quickstart/in-app-paywall-previews) — Configure URL schemes, universal links, and wire `handleDeepLink` into your app so Superwall can respond to incoming links. +- [Using Superwall Deep Links](/flutter/guides/superwall-deep-links) — Trigger paywalls or custom in-app behavior using Superwall-hosted URLs at `*.superwall.app/app-link/...`. +::: +:::expo +- [Deep Link Setup](/expo/quickstart/in-app-paywall-previews) — Configure URL schemes, universal links, and wire `handleDeepLink` into your app so Superwall can respond to incoming links. +::: diff --git a/content/shared/in-app-paywall-previews.mdx b/content/shared/in-app-paywall-previews.mdx index f24f0fff..fa732503 100644 --- a/content/shared/in-app-paywall-previews.mdx +++ b/content/shared/in-app-paywall-previews.mdx @@ -603,3 +603,20 @@ On your device, scan this QR code. You can do this via Apple's Camera app. This ## Using Deep Links to Present Paywalls Deep links can also be used as a placement in a campaign to present paywalls. Simply add `deepLink_open` as an placement, and the URL parameters of the deep link can be used as parameters! You can also use custom placements for this purpose. [Read this doc](/presenting-paywalls-from-one-another) for examples of both. + +## Related deep link guides + +:::ios +- [Handling Deep Links](/ios/guides/handling-deep-links) — Use `handleDeepLink` with the `deepLink_open` standard placement and dashboard campaign rules to present paywalls from deep links, without hardcoding routing logic in your app. +- [Using Superwall Deep Links](/ios/guides/superwall-deep-links) — Trigger paywalls or custom in-app behavior using Superwall-hosted URLs at `*.superwall.app/app-link/...`. +::: +:::android +- [Handling Deep Links](/android/guides/handling-deep-links) — Use `handleDeepLink` with the `deepLink_open` standard placement and dashboard campaign rules to present paywalls from deep links, without hardcoding routing logic in your app. +::: +:::flutter +- [Handling Deep Links](/flutter/guides/handling-deep-links) — Use `handleDeepLink` with the `deepLink_open` standard placement and dashboard campaign rules to present paywalls from deep links, without hardcoding routing logic in your app. +- [Using Superwall Deep Links](/flutter/guides/superwall-deep-links) — Trigger paywalls or custom in-app behavior using Superwall-hosted URLs at `*.superwall.app/app-link/...`. +::: +:::expo +- [Handling Deep Links](/expo/guides/handling-deep-links) — Use `handleDeepLink` with the `deepLink_open` standard placement and dashboard campaign rules to present paywalls from deep links, without hardcoding routing logic in your app. +::: diff --git a/content/shared/showing-paywalls/handling-paywalls-during-poor-network-conditions.mdx b/content/shared/showing-paywalls/handling-paywalls-during-poor-network-conditions.mdx index 730f3229..ddf25e0b 100644 --- a/content/shared/showing-paywalls/handling-paywalls-during-poor-network-conditions.mdx +++ b/content/shared/showing-paywalls/handling-paywalls-during-poor-network-conditions.mdx @@ -6,7 +6,7 @@ sidebarTitle: "Handling Poor Network Conditions" Superwall's SDK handles network issues as gracefully as possible, but there are still some scenarios to consider. The behavior will be different based on if `subscriptionStatus` evaluates to `.active(_)` or not. -**If it is `.active(_)`** and Superwall has already fetched or cached its configuration, then paywall presentation proceeds as it normally would. If Superwall was unable to fetch its configuration, the SDK waits one second to give it a chance to be retrieved. After that time, if it's not available — then a timeout event is tracked and the `onError` handler will be invoked with the error code 105. The "feature" block or closure will not be invoked: +**If it is `.active(_)`** and Superwall has already fetched or cached its configuration, then paywall presentation proceeds as it normally would. If Superwall was unable to fetch its configuration, the SDK waits one second to give it a chance to be retrieved. After that time, if it's not available, the `onError` handler will be invoked with the error code 105. The "feature" block or closure will not be invoked: ```swift Superwall.shared.register(placement: "foo") { @@ -16,4 +16,4 @@ Superwall.shared.register(placement: "foo") { **If it's not `.active(_)`** then Superwall will retry network calls until we have retrieved the necessary data for up to one minute. If it's still unavailable, then the SDK fires the `onError` handler with the error code of `104`. -For more information, please visit this [blog post](https://superwall.com/blog/handling-connectivity-interruptions-with-superwall). \ No newline at end of file +For more information, please visit this [blog post](https://superwall.com/blog/handling-connectivity-interruptions-with-superwall). diff --git a/content/shared/showing-paywalls/using-the-presentation-handler.mdx b/content/shared/showing-paywalls/using-the-presentation-handler.mdx index 5b660780..27954b8f 100644 --- a/content/shared/showing-paywalls/using-the-presentation-handler.mdx +++ b/content/shared/showing-paywalls/using-the-presentation-handler.mdx @@ -10,6 +10,7 @@ You can provide a `PaywallPresentationHandler` to `register`, whose functions pr - `onPresent`: Called when the paywall did present. Accepts a `PaywallInfo` object containing info about the presented paywall. - `onError`: Called when an error occurred when trying to present a paywall. Accepts an `Error` indicating why the paywall could not present. - `onSkip`: Called when a paywall is skipped. Accepts a `PaywallSkippedReason` enum indicating why the paywall was skipped. +- `onCustomCallback` *(Android 2.7.0+)*: Called when the paywall requests a custom callback. Accepts a `CustomCallback` containing the callback name and optional variables, and returns a `CustomCallbackResult` indicating success or failure with optional data to pass back to the paywall. diff --git a/content/shared/superwall-deep-links.mdx b/content/shared/superwall-deep-links.mdx index 6b8e62b4..caaf6de4 100644 --- a/content/shared/superwall-deep-links.mdx +++ b/content/shared/superwall-deep-links.mdx @@ -113,4 +113,15 @@ class PaywallDelegate extends SuperwallDelegate { ``` :::: -Keep your own routing logic in place for non-Superwall URLs and for any additional behaviors you want to stack on top of Superwall’s default presentation flow. +Keep your own routing logic in place for non-Superwall URLs and for any additional behaviors you want to stack on top of Superwall's default presentation flow. + +## Related deep link guides + +:::ios +- [Deep Link Setup](/ios/quickstart/in-app-paywall-previews) — Configure URL schemes, universal links, and wire `handleDeepLink` into your app so Superwall can respond to incoming links. +- [Handling Deep Links](/ios/guides/handling-deep-links) — Use `handleDeepLink` with the `deepLink_open` standard placement and dashboard campaign rules to present paywalls from your own deep links, without hardcoding routing logic. +::: +:::flutter +- [Deep Link Setup](/flutter/quickstart/in-app-paywall-previews) — Configure URL schemes, universal links, and wire `handleDeepLink` into your app so Superwall can respond to incoming links. +- [Handling Deep Links](/flutter/guides/handling-deep-links) — Use `handleDeepLink` with the `deepLink_open` standard placement and dashboard campaign rules to present paywalls from your own deep links, without hardcoding routing logic. +::: diff --git a/content/shared/vibe-coding.mdx b/content/shared/vibe-coding.mdx index 329067e4..be7640ba 100644 --- a/content/shared/vibe-coding.mdx +++ b/content/shared/vibe-coding.mdx @@ -8,6 +8,7 @@ description: "How to Vibe Code using the knowledge of the Superwall Docs" We've built a few tools to help you Vibe Code using the knowledge of the Superwall Docs, right in your favorite AI tools: - [Superwall Docs MCP](#superwall-docs-mcp) in Claude Code, Cursor, etc. +- [Superwall MCP](#superwall-mcp) — manage your Superwall account (projects, paywalls, campaigns) from AI tools - [Superwall Docs GPT](#superwall-docs-gpt) in ChatGPT And right here in the Superwall Docs: @@ -45,6 +46,20 @@ You can install the MCP server in Claude Code by running the following command: claude mcp add --transport sse superwall-docs https://mcp.superwall.com/sse ``` +### Codex + +You can install the MCP server in Codex by running the following command: + +```bash +codex mcp add superwall --url https://mcp.superwall.com/mcp +``` + +## Superwall MCP + +The Superwall MCP is separate from the Docs MCP above — it connects AI tools to your **Superwall account**, letting agents create and manage projects, paywalls, campaigns, products, entitlements, and webhooks directly. Instead of switching to the dashboard, your AI assistant can set everything up for you. + +See the full [Superwall MCP guide](/dashboard/guides/superwall-mcp) for installation, a step-by-step quick setup, and the complete tool reference. + ## Superwall Docs GPT You can use the [Superwall Docs GPT](https://chatgpt.com/g/g-6888175f1684819180302d66f4e61971-superwall-docs-gpt) right in the ChatGPT app, and use it to ask any Superwall question. diff --git a/next-env.d.ts b/next-env.d.ts deleted file mode 100644 index c4b7818f..00000000 --- a/next-env.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/// -/// -import "./.next/dev/types/routes.d.ts"; - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/package.json b/package.json index fd78e06a..78db7cf3 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,10 @@ "generate:md": "bun scripts/generate-md-files.ts", "generate:llm": "bun scripts/generate-llm-files.ts", "generate:title-map": "bun scripts/generate-title-map.ts", - "build:prep": "bun run generate:title-map && bun run generate:llm && bun run generate:md && bun run copy:docs-images", + "generate:changelog": "bun scripts/generate-changelog.ts", + "build:prep": "bun run generate:changelog && bun run generate:title-map && bun run generate:llm && bun run generate:md && bun run copy:docs-images", "build": "bun run build:prep && fumadocs-mdx && bun run build:next", + "build:mixedbread": "bun run build && bun run sync:mixedbread", "build:next": "NEXT_PRIVATE_STANDALONE=true NEXT_PRIVATE_OUTPUT_TRACE_ROOT=$PWD next build --webpack", "build:cf": "bun run build && opennextjs-cloudflare build -- --skipNextBuild", "preview": "bun run build && opennextjs-cloudflare build -- --skipNextBuild && opennextjs-cloudflare preview", @@ -24,21 +26,25 @@ "start": "next start", "download:references": "bun scripts/download-references.ts", "generate:test-wrangler": "bun scripts/generate-test-wrangler.ts", - "sync:r2": "bun scripts/sync-r2.ts" + "sync:r2": "bun scripts/sync-r2.ts", + "sync:mixedbread": "bun scripts/sync-mixedbread.ts", + "create:mixedbread-store": "bun scripts/create-mixedbread-store.ts" }, "dependencies": { "@ai-sdk/react": "^2.0.109", "@aws-sdk/client-s3": "^3.948.0", + "@mixedbread/sdk": "^0.46.0", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.11", "ai": "^5.0.108", "class-variance-authority": "^0.7.1", "cli-progress": "^3.12.0", "clsx": "^2.1.0", - "fumadocs-core": "^16.0.5", - "fumadocs-mdx": "^13.0.2", - "fumadocs-ui": "^16.0.5", + "fumadocs-core": "16.0.5", + "fumadocs-mdx": "13.0.2", + "fumadocs-ui": "16.0.5", "lucide-react": "^0.503.0", + "mermaid": "^10.9.1", "nanoid": "^5.1.6", "next": "16.0.7", "react": "^19.1.0", diff --git a/scripts/create-mixedbread-store.ts b/scripts/create-mixedbread-store.ts new file mode 100644 index 00000000..6e7ba81d --- /dev/null +++ b/scripts/create-mixedbread-store.ts @@ -0,0 +1,37 @@ +import Mixedbread from "@mixedbread/sdk"; + +const { MIXEDBREAD_API_KEY } = process.env; + +if (!MIXEDBREAD_API_KEY) { + throw new Error("MIXEDBREAD_API_KEY is required"); +} + +const mxbai = new Mixedbread({ apiKey: MIXEDBREAD_API_KEY }); + +async function main() { + console.log("Creating new Mixedbread store with contextualization...\n"); + + const store = await mxbai.stores.create({ + name: "superwall-docs-v2", + description: "Superwall documentation with SDK and category metadata", + config: { + contextualization: { + with_metadata: ["title", "sdk", "category"], + }, + }, + }); + + console.log("✅ Store created successfully!\n"); + console.log("Store Details:"); + console.log(` ID: ${store.id}`); + console.log(` Name: ${store.name}`); + console.log(""); + console.log("Next steps:"); + console.log(` 1. Update your .env file with: MIXEDBREAD_STORE_ID=${store.name}`); + console.log(" 2. Run: bun run sync:mixedbread"); +} + +main().catch((err) => { + console.error("❌ Failed to create store:", err.message); + process.exit(1); +}); diff --git a/scripts/generate-changelog.ts b/scripts/generate-changelog.ts new file mode 100644 index 00000000..86b42df7 --- /dev/null +++ b/scripts/generate-changelog.ts @@ -0,0 +1,267 @@ +import fs from 'fs'; +import path from 'path'; +import matter from 'gray-matter'; +import { getChangedFilesSince, type GitFileChange } from './utils/git-history'; +import { + categorizeFile, + getCategoryOrder, +} from './utils/changelog-categorizer'; +import { + generateDescriptions, + hasApiKey, + type DescriptionRequest, +} from './utils/llm-descriptions'; + +// Configuration +const SINCE_DATE = new Date('2025-12-01'); +const OUTPUT_FILE = path.join(process.cwd(), 'src/lib/changelog-entries.json'); +const MONTHS_TO_KEEP = 3; + +interface ChangelogEntry { + /** Unique key: path:commitHash */ + key: string; + /** Relative path from content/docs */ + path: string; + /** Page title from frontmatter */ + title: string; + /** One-line description of the change */ + description: string; + /** Top-level category */ + category: string; + /** Subcategory (optional) */ + subcategory?: string; + /** Full docs URL */ + url: string; + /** ISO date string of the change */ + date: string; + /** Type of change */ + changeType: 'added' | 'modified'; +} + +interface ChangelogData { + /** When the changelog was last updated */ + lastUpdated: string; + /** All changelog entries (newest first) */ + entries: ChangelogEntry[]; +} + +/** + * Get the cutoff date for pruning old entries (first day of the month, N months ago). + */ +function getCutoffDate(monthsToKeep: number): Date { + const now = new Date(); + return new Date(now.getFullYear(), now.getMonth() - monthsToKeep + 1, 1); +} + +/** + * Filter entries to only keep those within the retention period. + */ +function pruneOldEntries(entries: ChangelogEntry[]): ChangelogEntry[] { + const cutoff = getCutoffDate(MONTHS_TO_KEEP); + return entries.filter((entry) => new Date(entry.date) >= cutoff); +} + +/** + * Load existing changelog data. + */ +function loadExistingChangelog(): ChangelogData { + try { + if (fs.existsSync(OUTPUT_FILE)) { + const content = fs.readFileSync(OUTPUT_FILE, 'utf-8'); + return JSON.parse(content); + } + } catch (error) { + console.warn('Warning: Failed to load existing changelog:', error); + } + return { lastUpdated: '', entries: [] }; +} + +/** + * Extract frontmatter from an MDX file. + */ +function extractFrontmatter( + filePath: string +): { title: string } | null { + try { + if (!fs.existsSync(filePath)) { + return null; + } + + const content = fs.readFileSync(filePath, 'utf-8'); + const { data } = matter(content); + + if (!data.title) { + return null; + } + + return { title: data.title }; + } catch (error) { + console.warn(`Warning: Failed to parse ${filePath}:`, error); + return null; + } +} + +/** + * Convert a file path to a docs URL. + */ +function filePathToUrl(relativePath: string): string { + const normalizedPath = relativePath.replace(/\\/g, '/'); + const cleanPath = normalizedPath + .replace(/^content\/docs\//, '') + .replace(/\.mdx?$/, '') + .replace(/\/index$/, ''); + return `/docs/${cleanPath}`; +} + +/** + * Main generator function. + */ +async function main() { + console.log('📝 Generating documentation changelog...'); + console.log(` Since: ${SINCE_DATE.toISOString().split('T')[0]}`); + + // Load existing changelog + const existing = loadExistingChangelog(); + const existingKeys = new Set(existing.entries.map((e) => e.key)); + console.log(` Existing entries: ${existingKeys.size}`); + + // Get changed files from git + const changes = getChangedFilesSince(SINCE_DATE); + console.log(` Found ${changes.length} changed files in git history`); + + // Find NEW changes (not already in changelog) + const newChanges: Array<{ + change: GitFileChange; + frontmatter: { title: string }; + categoryInfo: ReturnType; + key: string; + }> = []; + + for (const change of changes) { + const key = `${change.path}:${change.commitHash}`; + + // Skip if already in changelog + if (existingKeys.has(key)) { + continue; + } + + const absolutePath = path.join(process.cwd(), change.path); + const frontmatter = extractFrontmatter(absolutePath); + + if (!frontmatter) { + continue; + } + + const normalizedPath = change.path.replace(/\\/g, '/'); + const categoryInfo = categorizeFile(normalizedPath); + + newChanges.push({ + change, + frontmatter, + categoryInfo, + key, + }); + } + + console.log(` New entries to generate: ${newChanges.length}`); + + if (newChanges.length === 0) { + console.log('✅ Changelog is up to date, no new entries'); + return; + } + + // Check for API key - required for generating new entries + if (!hasApiKey()) { + console.log('\n⚠️ No ANTHROPIC_API_KEY found. Skipping changelog generation.'); + console.log(' To generate descriptions for new entries, add ANTHROPIC_API_KEY to .env.local'); + console.log(` ${newChanges.length} new entries were not added.\n`); + return; + } + + // Generate descriptions for new changes only + const descriptionRequests: DescriptionRequest[] = newChanges.map((item) => ({ + filePath: item.change.path, + commitHash: item.change.commitHash, + changeType: item.change.changeType, + title: item.frontmatter.title, + category: item.categoryInfo.category, + subcategory: item.categoryInfo.subcategory, + })); + + const descriptions = await generateDescriptions(descriptionRequests); + + // Build new entries + const newEntries: ChangelogEntry[] = newChanges.map((item) => { + const cacheKey = `${item.change.path}:${item.change.commitHash}`; + const descResult = descriptions.get(cacheKey); + const normalizedPath = item.change.path.replace(/\\/g, '/'); + const relativePath = normalizedPath.replace(/^content\/docs\//, ''); + + return { + key: item.key, + path: relativePath, + title: item.frontmatter.title, + description: descResult?.description || '', + category: item.categoryInfo.category, + subcategory: item.categoryInfo.subcategory, + url: filePathToUrl(normalizedPath), + date: item.change.date.toISOString(), + changeType: item.change.changeType, + }; + }); + + // Merge: new entries + existing entries + const mergedEntries = [...newEntries, ...existing.entries]; + + // Prune entries older than 3 months + const prunedEntries = pruneOldEntries(mergedEntries); + const prunedCount = mergedEntries.length - prunedEntries.length; + + // Sort by date (newest first), then by category order + prunedEntries.sort((a, b) => { + const dateCompare = new Date(b.date).getTime() - new Date(a.date).getTime(); + if (dateCompare !== 0) return dateCompare; + return getCategoryOrder(a.category) - getCategoryOrder(b.category); + }); + + // Build final changelog data + const changelogData: ChangelogData = { + lastUpdated: new Date().toISOString(), + entries: prunedEntries, + }; + + // Ensure output directory exists + const outputDir = path.dirname(OUTPUT_FILE); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + // Write output + fs.writeFileSync(OUTPUT_FILE, JSON.stringify(changelogData, null, 2), 'utf-8'); + + console.log(`✅ Changelog updated: ${path.relative(process.cwd(), OUTPUT_FILE)}`); + console.log(` Added ${newEntries.length} new entries`); + if (prunedCount > 0) { + console.log(` Pruned ${prunedCount} entries older than ${MONTHS_TO_KEEP} months`); + } + console.log(` Total entries: ${prunedEntries.length}`); + + // Show category breakdown for new entries + if (newEntries.length > 0) { + const categoryCount = new Map(); + for (const entry of newEntries) { + const count = categoryCount.get(entry.category) || 0; + categoryCount.set(entry.category, count + 1); + } + + console.log(' New entries by category:'); + for (const [category, count] of categoryCount.entries()) { + console.log(` - ${category}: ${count}`); + } + } +} + +main().catch((error) => { + console.error('❌ Changelog generation failed:', error); + process.exit(1); +}); diff --git a/scripts/generate-title-map.ts b/scripts/generate-title-map.ts index ad150ce5..db5cef8e 100644 --- a/scripts/generate-title-map.ts +++ b/scripts/generate-title-map.ts @@ -1,36 +1,12 @@ import fs from 'fs'; import path from 'path'; import { glob } from 'glob'; +import { extractTitle } from '../shared/utils/title'; interface TitleMap { [filepath: string]: string; } -/** - * Extract title from MDX content - * Priority: frontmatter title > first # heading > filename - */ -function extractTitle(content: string, filepath: string): string { - // Try frontmatter title first - const frontmatterMatch = content.match(/^---\s*\n.*?title:\s*["'](.+?)["']/ms); - if (frontmatterMatch) { - return frontmatterMatch[1]; - } - - // Try first markdown heading - const headingMatch = content.match(/^#\s+(.+)$/m); - if (headingMatch) { - return headingMatch[1].trim(); - } - - // Fallback to filename - const filename = path.basename(filepath, path.extname(filepath)); - return filename - .split(/[-_]/) - .map(word => word.charAt(0).toUpperCase() + word.slice(1)) - .join(' '); -} - /** * Generate a map of relative file paths to their titles */ diff --git a/scripts/sync-mixedbread.ts b/scripts/sync-mixedbread.ts new file mode 100644 index 00000000..f0d16f1c --- /dev/null +++ b/scripts/sync-mixedbread.ts @@ -0,0 +1,160 @@ +import Mixedbread from "@mixedbread/sdk"; +import { Glob } from "bun"; +import { SDK_PLATFORMS_SET, isSDKPlatform } from "../shared/constants/sdk"; +import { ChunkMetadata, createChunkMetadata } from "../shared/types/mixedbread"; +import { extractTitle } from "../shared/utils/title"; + +const STORE_ID = process.env.MIXEDBREAD_STORE_ID; +const API_KEY = process.env.MIXEDBREAD_API_KEY; + +if (!STORE_ID) { + throw new Error("MIXEDBREAD_STORE_ID is required"); +} +if (!API_KEY) { + throw new Error("MIXEDBREAD_API_KEY is required"); +} + +const mxbai = new Mixedbread({ apiKey: API_KEY }); + +// Concurrency limit for parallel uploads +const CONCURRENCY = 10; + +// Extract SDK from path (ios, android, flutter, expo, or null for non-SDK docs) +function extractSdk(relativePath: string): string | null { + const parts = relativePath.split("/"); + const firstPart = parts[0]; + return isSDKPlatform(firstPart) ? firstPart : null; +} + +// Extract category from path (quickstart, guides, sdk-reference, etc.) +function extractCategory(relativePath: string): string { + const parts = relativePath.split("/"); + // For SDK docs: ios/quickstart/... -> quickstart + // For non-SDK: dashboard/paywalls/... -> dashboard + if (SDK_PLATFORMS_SET.has(parts[0]) && parts.length > 1) { + return parts[1]; + } + return parts[0]; +} + +// Convert filepath to URL path +function filepathToUrlPath(relativePath: string): string { + let urlPath = relativePath.replace(/\.mdx?$/, ""); + + // Strip trailing /index for directory landing pages + if (urlPath.endsWith("/index")) { + urlPath = urlPath.slice(0, -6); + } else if (urlPath === "index") { + urlPath = ""; + } + + return urlPath; +} + +// Upload a single file +async function uploadFile(filepath: string): Promise<{ success: boolean; path: string; sdk: string | null; error?: string }> { + const relativePath = filepath.replace(/^content\/docs\//, ""); + + try { + const content = await Bun.file(filepath).text(); + + const title = extractTitle(content, filepath); + const sdk = extractSdk(relativePath); + const category = extractCategory(relativePath); + const urlPath = filepathToUrlPath(relativePath); + + const metadata: ChunkMetadata = createChunkMetadata(title, sdk, category, urlPath); + + const parts = filepath.split("/"); + const filename = parts[parts.length - 1]; + + const file = new File([content], filename, { + type: "text/markdown", + }); + + await mxbai.stores.files.uploadAndPoll(STORE_ID!, file, { + external_id: relativePath, + metadata, + }); + + return { success: true, path: relativePath, sdk }; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + return { success: false, path: relativePath, sdk: null, error: message }; + } +} + +// Process files with concurrency limit using a simple semaphore pattern +async function processWithConcurrency( + items: T[], + fn: (item: T) => Promise, + concurrency: number +): Promise { + const results: R[] = new Array(items.length); + let index = 0; + + async function worker() { + while (index < items.length) { + const currentIndex = index++; + const item = items[currentIndex]; + results[currentIndex] = await fn(item); + } + } + + // Start `concurrency` number of workers + const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker()); + await Promise.all(workers); + + return results; +} + +async function main() { + console.log("Syncing docs to Mixedbread store...\n"); + + // Find all MDX files using Bun's Glob, excluding legacy docs + const glob = new Glob("content/docs/**/*.mdx"); + const files: string[] = []; + for await (const file of glob.scan({ cwd: ".", onlyFiles: true })) { + // Skip node_modules and legacy docs + if (!file.includes("node_modules") && !file.includes("content/docs/legacy/")) { + files.push(file); + } + } + + console.log(`Found ${files.length} MDX files to sync (concurrency: ${CONCURRENCY})\n`); + + const startTime = Date.now(); + let completed = 0; + + const results = await processWithConcurrency( + files, + async (filepath) => { + const result = await uploadFile(filepath); + completed++; + const sdkLabel = result.sdk ? ` [${result.sdk}]` : ""; + if (result.success) { + console.log(`✓ [${completed}/${files.length}] ${result.path}${sdkLabel}`); + } else { + console.error(`✗ [${completed}/${files.length}] ${result.path}: ${result.error}`); + } + return result; + }, + CONCURRENCY + ); + + const successCount = results.filter((r) => r.success).length; + const errorCount = results.filter((r) => !r.success).length; + const duration = ((Date.now() - startTime) / 1000).toFixed(1); + + console.log(""); + console.log(`Sync complete in ${duration}s: ${successCount} succeeded, ${errorCount} failed`); + + if (errorCount > 0) { + process.exit(1); + } +} + +main().catch((err) => { + console.error("❌ Sync failed:", err); + process.exit(1); +}); diff --git a/scripts/utils/changelog-categorizer.ts b/scripts/utils/changelog-categorizer.ts new file mode 100644 index 00000000..d035e76f --- /dev/null +++ b/scripts/utils/changelog-categorizer.ts @@ -0,0 +1,102 @@ +export interface CategoryInfo { + /** Top-level category (e.g., "iOS SDK", "Dashboard") */ + category: string; + /** Subcategory within the main category (e.g., "Guides", "SDK Reference") */ + subcategory?: string; +} + +/** + * Map of top-level folder names to display categories. + */ +const CATEGORY_MAP: Record = { + ios: 'iOS SDK', + android: 'Android SDK', + flutter: 'Flutter SDK', + expo: 'Expo SDK', + 'react-native': 'React Native SDK', + dashboard: 'Dashboard', + integrations: 'Integrations', + 'web-checkout': 'Web Checkout', + support: 'Support', + community: 'Community', +}; + +/** + * Map of subfolder names to display subcategories. + */ +const SUBCATEGORY_MAP: Record = { + quickstart: 'Quickstart', + guides: 'Guides', + 'sdk-reference': 'SDK Reference', + 'dashboard-campaigns': 'Campaigns', + 'dashboard-creating-paywalls': 'Creating Paywalls', + 'dashboard-settings': 'Settings', + faq: 'FAQ', + troubleshooting: 'Troubleshooting', +}; + +/** + * Categorize a file based on its path. + * @param relativePath Path relative to content/docs (e.g., "ios/guides/advanced.mdx") + */ +export function categorizeFile(relativePath: string): CategoryInfo { + // Normalize path separators + const normalizedPath = relativePath.replace(/\\/g, '/'); + + // Remove content/docs prefix if present + const cleanPath = normalizedPath.replace(/^content\/docs\//, ''); + + // Split into segments + const segments = cleanPath.split('/').filter(Boolean); + + if (segments.length === 0) { + return { category: 'Documentation' }; + } + + // First segment is the top-level category + const topLevel = segments[0]; + const category = CATEGORY_MAP[topLevel] || 'Documentation'; + + // Second segment might be a subcategory (if it's a directory, not a file) + let subcategory: string | undefined; + if (segments.length > 2) { + const secondLevel = segments[1]; + subcategory = SUBCATEGORY_MAP[secondLevel]; + } + + return { category, subcategory }; +} + +/** + * Get display string for a category (combines category and subcategory). + */ +export function getCategoryDisplay(info: CategoryInfo): string { + if (info.subcategory) { + return `${info.category} → ${info.subcategory}`; + } + return info.category; +} + +/** + * Sort order for categories (lower = earlier). + */ +const CATEGORY_ORDER: Record = { + 'iOS SDK': 1, + 'Android SDK': 2, + 'Flutter SDK': 3, + 'Expo SDK': 4, + 'React Native SDK': 5, + Dashboard: 6, + Integrations: 7, + 'Web Checkout': 8, + Support: 9, + Community: 10, + Documentation: 99, +}; + +/** + * Get sort order for a category. + */ +export function getCategoryOrder(category: string): number { + return CATEGORY_ORDER[category] ?? 50; +} diff --git a/scripts/utils/git-history.ts b/scripts/utils/git-history.ts new file mode 100644 index 00000000..83c6f165 --- /dev/null +++ b/scripts/utils/git-history.ts @@ -0,0 +1,120 @@ +import { spawnSync } from 'child_process'; + +export interface GitFileChange { + /** Relative path from repo root */ + path: string; + /** Type of change */ + changeType: 'added' | 'modified'; + /** Date of the change */ + date: Date; + /** Commit hash */ + commitHash: string; +} + +/** + * Get all changed MDX files in content/docs since a specific date. + * Only includes additions and modifications (ignores deletions). + * Deduplicates by file path, keeping the most recent change. + */ +export function getChangedFilesSince( + sinceDate: Date, + repoRoot: string = process.cwd() +): GitFileChange[] { + const sinceDateStr = sinceDate.toISOString().split('T')[0]; + + // Use spawnSync to avoid shell interpretation issues with special characters + const result = spawnSync( + 'git', + [ + 'log', + `--since=${sinceDateStr}`, + '--diff-filter=AM', // Only Added or Modified + '--name-status', + '--pretty=format:%H|%aI', + '--', + 'content/docs', + ], + { + cwd: repoRoot, + encoding: 'utf-8', + } + ); + + if (result.error) { + console.warn('Warning: Git command failed:', result.error.message); + return []; + } + + if (result.status !== 0) { + // Git returns non-zero when no repo found or other errors + if (result.stderr?.includes('not a git repository')) { + console.warn('Warning: Not a git repository, generating empty changelog'); + return []; + } + // Some git errors are fine (like no commits matching) + if (result.stdout === '') { + return []; + } + console.warn('Warning: Git command returned non-zero:', result.stderr); + return []; + } + + const output = result.stdout || ''; + const changes = parseGitLog(output); + + // Filter to only .mdx files + const mdxChanges = changes.filter((c) => c.path.endsWith('.mdx')); + + return deduplicateByPath(mdxChanges); +} + +/** + * Parse git log output into structured changes. + */ +function parseGitLog(output: string): GitFileChange[] { + const changes: GitFileChange[] = []; + const lines = output.trim().split('\n').filter(Boolean); + + let currentCommit: { hash: string; date: Date } | null = null; + + for (const line of lines) { + // Commit line: hash|date (no tab character) + if (line.includes('|') && !line.includes('\t')) { + const [hash, dateStr] = line.split('|'); + currentCommit = { hash: hash.trim(), date: new Date(dateStr.trim()) }; + continue; + } + + // File change line: status\tpath + if (currentCommit && (line.startsWith('A\t') || line.startsWith('M\t'))) { + const [status, filePath] = line.split('\t'); + changes.push({ + path: filePath, + changeType: status === 'A' ? 'added' : 'modified', + date: currentCommit.date, + commitHash: currentCommit.hash, + }); + } + } + + return changes; +} + +/** + * Deduplicate changes by file path, keeping the most recent change for each file. + */ +function deduplicateByPath(changes: GitFileChange[]): GitFileChange[] { + const fileMap = new Map(); + + // Sort by date descending (newest first) + const sorted = [...changes].sort((a, b) => b.date.getTime() - a.date.getTime()); + + // Keep first occurrence (newest) for each file + for (const change of sorted) { + if (!fileMap.has(change.path)) { + fileMap.set(change.path, change); + } + } + + return Array.from(fileMap.values()); +} diff --git a/scripts/utils/llm-descriptions.ts b/scripts/utils/llm-descriptions.ts new file mode 100644 index 00000000..bd2a727f --- /dev/null +++ b/scripts/utils/llm-descriptions.ts @@ -0,0 +1,190 @@ +import { spawnSync } from 'child_process'; + +/** + * Get the git diff for a specific file at a specific commit. + */ +function getFileDiff(commitHash: string, filePath: string): string | null { + const result = spawnSync( + 'git', + ['show', '--no-color', '--format=', commitHash, '--', filePath], + { + cwd: process.cwd(), + encoding: 'utf-8', + maxBuffer: 1024 * 1024, // 1MB + } + ); + + if (result.error || result.status !== 0) { + return null; + } + + return result.stdout || null; +} + +/** + * Call Claude API to generate a description for a change. + */ +async function callClaudeAPI( + diff: string, + filePath: string, + changeType: 'added' | 'modified', + title: string +): Promise { + const apiKey = process.env.ANTHROPIC_API_KEY; + if (!apiKey) { + throw new Error('ANTHROPIC_API_KEY not set'); + } + + // Truncate diff if too long (keep first 4000 chars) + const truncatedDiff = diff.length > 4000 ? diff.slice(0, 4000) + '\n... (truncated)' : diff; + + const prompt = `You are writing a changelog entry for documentation changes. Given the following git diff for a documentation file, write a single concise sentence (max 15 words) describing what changed for end users. + +File: ${filePath} +Page title: ${title} +Change type: ${changeType === 'added' ? 'New page' : 'Updated page'} + +Git diff: +\`\`\` +${truncatedDiff} +\`\`\` + +Rules: +- Write from the perspective of what users/developers will learn or benefit from +- Be specific about the content, not generic like "Updated documentation" +- Use active voice and present tense +- Don't start with "This" or "The page" +- Don't mention "documentation" or "docs" +- Examples of good descriptions: + - "Adds step-by-step guide for configuring push notifications" + - "Explains how to handle subscription status changes" + - "Covers new paywall presentation options in v4.0" + - "Fixes incorrect code sample for purchase restoration" + +Write only the description, nothing else:`; + + const response = await fetch('https://api.anthropic.com/v1/messages', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': apiKey, + 'anthropic-version': '2023-06-01', + }, + body: JSON.stringify({ + model: 'claude-3-5-haiku-20241022', + max_tokens: 100, + messages: [ + { + role: 'user', + content: prompt, + }, + ], + }), + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error(`Claude API error: ${response.status} ${error}`); + } + + const data = await response.json(); + const content = data.content?.[0]?.text?.trim(); + + if (!content) { + throw new Error('Empty response from Claude API'); + } + + return content; +} + +/** + * Generate a fallback description when LLM call fails. + */ +function getFallbackDescription( + changeType: 'added' | 'modified', + category: string, + subcategory?: string +): string { + const action = changeType === 'added' ? 'New' : 'Updated'; + if (subcategory) { + return `${action} ${subcategory.toLowerCase()} for ${category}`; + } + return `${action} ${category} documentation`; +} + +export interface DescriptionRequest { + filePath: string; + commitHash: string; + changeType: 'added' | 'modified'; + title: string; + category: string; + subcategory?: string; +} + +export interface DescriptionResult { + description: string; + fromLLM: boolean; +} + +/** + * Check if API key is available. + */ +export function hasApiKey(): boolean { + return !!process.env.ANTHROPIC_API_KEY; +} + +/** + * Generate descriptions for multiple changes using LLM. + */ +export async function generateDescriptions( + requests: DescriptionRequest[] +): Promise> { + const results = new Map(); + + let llmGenerated = 0; + let fallbacks = 0; + + for (const request of requests) { + const cacheKey = `${request.filePath}:${request.commitHash}`; + + try { + const diff = getFileDiff(request.commitHash, request.filePath); + if (diff) { + const description = await callClaudeAPI( + diff, + request.filePath, + request.changeType, + request.title + ); + + results.set(cacheKey, { + description, + fromLLM: true, + }); + llmGenerated++; + continue; + } + } catch (error) { + console.warn(`Warning: Failed to generate description for ${request.filePath}:`, error); + } + + // Fallback description + const fallbackDesc = getFallbackDescription( + request.changeType, + request.category, + request.subcategory + ); + results.set(cacheKey, { + description: fallbackDesc, + fromLLM: false, + }); + fallbacks++; + } + + // Log summary + if (llmGenerated > 0 || fallbacks > 0) { + console.log(` Descriptions: ${llmGenerated} generated, ${fallbacks} fallback`); + } + + return results; +} diff --git a/shared/constants/sdk.ts b/shared/constants/sdk.ts new file mode 100644 index 00000000..49404424 --- /dev/null +++ b/shared/constants/sdk.ts @@ -0,0 +1,61 @@ +/** + * SDK platform definitions shared across sync scripts, API routes, and UI. + * + * IMPORTANT: This file is imported by both Next.js app code and standalone scripts. + * Keep it dependency-free (no React, no Next.js imports). + */ + +/** Canonical list of SDK platforms for documentation */ +export const SDK_PLATFORM_IDS = [ + "ios", + "android", + "flutter", + "expo", + "react-native", +] as const; + +export type SDKPlatformId = (typeof SDK_PLATFORM_IDS)[number]; + +/** Set for O(1) lookups (used in sync script) */ +export const SDK_PLATFORMS_SET = new Set(SDK_PLATFORM_IDS); + +/** UI display options with labels */ +export const SDK_OPTIONS = [ + { value: "", label: "None (dashboard)" }, + { value: "ios", label: "iOS" }, + { value: "android", label: "Android" }, + { value: "expo", label: "Expo" }, + { value: "flutter", label: "Flutter" }, + { value: "react-native", label: "React Native (Deprecated)" }, +] as const; + +export type SDKOption = (typeof SDK_OPTIONS)[number]; + +/** Human-readable names (for badges, display) */ +export const SDK_NAMES: Record = { + ios: "iOS", + android: "Android", + flutter: "Flutter", + expo: "Expo", + "react-native": "React Native (Deprecated)", +}; + +/** URL path roots (without /docs prefix) */ +export const SDK_URL_ROOTS: Record = { + ios: "/ios", + android: "/android", + flutter: "/flutter", + expo: "/expo", + "react-native": "/react-native", +}; + +/** Check if a string is a valid SDK platform */ +export function isSDKPlatform(value: string): value is SDKPlatformId { + return SDK_PLATFORMS_SET.has(value); +} + +/** Get label for SDK value (including empty string for "None") */ +export function getSDKLabel(value: string): string { + const option = SDK_OPTIONS.find((opt) => opt.value === value); + return option?.label || value; +} diff --git a/shared/types/mixedbread.ts b/shared/types/mixedbread.ts new file mode 100644 index 00000000..48583c97 --- /dev/null +++ b/shared/types/mixedbread.ts @@ -0,0 +1,56 @@ +/** + * Type definitions for Mixedbread chunk data. + * + * These types define the contract between: + * - sync-mixedbread.ts (producer: uploads docs with metadata) + * - search/route.ts (consumer: transforms search results) + */ + +/** + * Metadata attached to each document chunk in Mixedbread store. + * Fields are strings for Mixedbread compatibility. + */ +export interface ChunkMetadata { + /** Document title (from frontmatter or filename) */ + title: string; + /** SDK platform (ios, android, etc.) or "" for non-SDK docs */ + sdk: string; + /** Category/section (quickstart, guides, sdk-reference, etc.) */ + category: string; + /** URL path without /docs prefix (e.g., "ios/quickstart/install") */ + path: string; +} + +/** + * Heading extracted from markdown by Mixedbread's parser. + * Available in generated_metadata.chunk_headings. + */ +export interface ChunkHeading { + level: number; // 1-6 + text: string; +} + +/** + * Generated metadata that Mixedbread adds to markdown files. + */ +export interface GeneratedMeta { + chunk_headings?: ChunkHeading[]; +} + +/** + * Create metadata object for upload. + * Ensures consistent field names and types. + */ +export function createChunkMetadata( + title: string, + sdk: string | null, + category: string, + path: string +): ChunkMetadata { + return { + title, + sdk: sdk || "", // Normalize null to empty string + category, + path, + }; +} diff --git a/shared/utils/title.ts b/shared/utils/title.ts new file mode 100644 index 00000000..273205f4 --- /dev/null +++ b/shared/utils/title.ts @@ -0,0 +1,43 @@ +/** + * Title extraction utilities shared between sync and generation scripts. + * Extracts titles from MDX frontmatter with fallback to filename. + */ + +/** + * Extract title from MDX content. + * Priority: frontmatter title > filename fallback + * + * @param content - Raw MDX file content + * @param filepath - File path (used for fallback) + * @returns Extracted or generated title + */ +export function extractTitle(content: string, filepath: string): string { + // Try frontmatter title - handles both quoted and unquoted values + const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/); + if (frontmatterMatch) { + const titleMatch = frontmatterMatch[1].match( + /^title:\s*["']?(.+?)["']?\s*$/m + ); + if (titleMatch) { + return titleMatch[1]; + } + } + + // Fallback: convert filename to readable title + return filenameToTitle(filepath); +} + +/** + * Convert a filepath to a human-readable title. + * + * @param filepath - File path like "ios/quickstart/install.mdx" + * @returns Title like "Install" + */ +export function filenameToTitle(filepath: string): string { + const parts = filepath.split("/"); + const filename = parts[parts.length - 1].replace(/\.mdx?$/, ""); + return filename + .split(/[-_]/) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +} diff --git a/src/app/api/search/route.ts b/src/app/api/search/route.ts index 22d1205c..3be9ae6d 100644 --- a/src/app/api/search/route.ts +++ b/src/app/api/search/route.ts @@ -2,11 +2,15 @@ import { source } from "@/lib/source" import { createFromSource, type SortedResult } from "fumadocs-core/search/server" import { NextRequest, NextResponse } from "next/server"; import titleMap from '@/lib/title-map.json'; +import Mixedbread from '@mixedbread/sdk'; +import type { StoreSearchResponse } from '@mixedbread/sdk/resources'; +import type { ChunkMetadata, ChunkHeading, GeneratedMeta } from '@/lib/mixedbread'; +import { filenameToTitle } from '@/lib/mixedbread'; // Validate and default search mode const getSearchMode = () => { const mode = process.env.SEARCH_MODE; - if (mode === 'rag' || mode === 'fumadocs') { + if (mode === 'rag' || mode === 'fumadocs' || mode === 'mixedbread') { return mode; } return 'fumadocs'; // Default to fumadocs for any invalid/undefined value @@ -16,6 +20,16 @@ const SEARCH_MODE = getSearchMode(); const RAG_ENDPOINT = 'https://mcp.superwall.com/docs-search'; const IS_DEV = process.env.NODE_ENV === 'development' || process.env.NEXTJS_ENV === 'development'; +interface PageEntry { + page: SortedResult; + headings: SortedResult[]; +} + +// Mixedbread client (only initialized when needed) +const mixedbreadClient = SEARCH_MODE === 'mixedbread' && process.env.MIXEDBREAD_API_KEY + ? new Mixedbread({ apiKey: process.env.MIXEDBREAD_API_KEY }) + : null; + // Fumadocs search implementation const fumadocsSearch = createFromSource(source); @@ -38,12 +52,85 @@ function getTitle(filepath: string): string { } // Fallback: convert filename to readable title - const filename = filepath.split('/').pop() || filepath; - const withoutExt = filename.replace(/\.mdx?$/, ''); - const words = withoutExt.split(/[-_]/).map(word => - word.charAt(0).toUpperCase() + word.slice(1) - ); - return words.join(' '); + return filenameToTitle(filepath); +} + +// Convert heading text to URL anchor +function headingToAnchor(heading: string): string { + return heading + .toLowerCase() + .replace(/[^\w\s-]/g, '') + .replace(/\s+/g, '-'); +} + +// --- Mixedbread Result Helpers --- + +/** Checks if a heading is useful for navigation (h2-h3 only) */ +const isUsefulHeading = (heading: ChunkHeading): boolean => + heading.level >= 2 && heading.level <= 3; + +/** Creates a page result object */ +const createPageResult = (metadata: ChunkMetadata): SortedResult => ({ + id: `/docs/${metadata.path}`, + url: `/docs/${metadata.path}`, + type: 'page', + content: metadata.title, + ...(metadata.sdk && { tag: metadata.sdk }) +}); + +/** Creates a heading result object */ +const createHeadingResult = (baseUrl: string, heading: ChunkHeading): SortedResult => ({ + id: `${baseUrl}#${headingToAnchor(heading.text)}`, + url: `${baseUrl}#${headingToAnchor(heading.text)}`, + type: 'heading', + content: heading.text, +}); + +// Transform Mixedbread response to Fumadocs format with nested headings +function transformMixedbreadResults(response: StoreSearchResponse): SortedResult[] { + const seenHeadingUrls = new Set(); + const pageMap = new Map(); + + for (const chunk of response.data) { + const metadata = chunk.metadata as ChunkMetadata; + const generatedMeta = chunk.generated_metadata as GeneratedMeta | undefined; + const url = `/docs/${metadata.path}`; + + // Get or create page entry + if (!pageMap.has(url)) { + pageMap.set(url, { + page: createPageResult(metadata), + headings: [] + }); + } + + // Add unique h2/h3 headings + const headings = generatedMeta?.chunk_headings ?? []; + const pageEntry = pageMap.get(url)!; + + for (const heading of headings) { + if (!isUsefulHeading(heading)) continue; + + const headingUrl = `${url}#${headingToAnchor(heading.text)}`; + if (seenHeadingUrls.has(headingUrl)) continue; + + seenHeadingUrls.add(headingUrl); + pageEntry.headings.push(createHeadingResult(url, heading)); + } + } + + // Flatten: page followed by up to 2 headings + const results = [...pageMap.values()].flatMap(({ page, headings }) => [ + page, + ...headings.slice(0, 2) + ]); + + if (IS_DEV) { + console.log(`[Transform Mixedbread] Total results: ${results.length} (${pageMap.size} pages)`); + results.slice(0, 5).forEach(r => console.log(` - [${r.type}] ${r.url}`)); + } + + return results; } // Transform RAG response to Fumadocs format @@ -111,7 +198,56 @@ export async function GET(request: NextRequest) { const startTime = Date.now(); try { - if (SEARCH_MODE === 'rag') { + if (SEARCH_MODE === 'mixedbread' && mixedbreadClient) { + const storeId = process.env.MIXEDBREAD_STORE_ID; + if (!storeId) { + console.error('MIXEDBREAD_STORE_ID not configured'); + return NextResponse.json([]); + } + + if (IS_DEV) { + console.log(`search (mixedbread) query: "${query}"${sdk ? ` sdk: "${sdk}"` : ''} received...`); + } + + // Build filter based on SDK parameter: + // - sdk=none: filter for docs with sdk="" (non-SDK docs like dashboard, support) + // - sdk=ios/android/etc: filter for docs with that specific sdk + // - no sdk param: no filter (all docs) + type FilterCondition = { key: string; operator: "eq"; value: string }; + let filters: { all: FilterCondition[] } | undefined; + + if (sdk === 'none') { + // Filter for non-SDK docs (sdk field is empty string) + filters = { all: [{ key: "sdk", operator: "eq", value: "" }] }; + } else if (sdk) { + // Filter for specific SDK + filters = { all: [{ key: "sdk", operator: "eq", value: sdk }] }; + } + + const mbrerank = Number(process.env.MIXEDBREAD_RERANK); + const mbtopk = Number(process.env.MIXEDBREAD_TOPK); + // Use stores.search() with reranking and native SDK filtering + const searchResponse = await mixedbreadClient.stores.search({ + query, + store_identifiers: [storeId], + top_k: mbtopk || 8, + search_options: { + rerank: mbrerank ? {top_k: mbrerank} : false + }, + // Native SDK filtering (done by Mixedbread, not post-fetch) + ...(filters && { filters }), + }); + + const results = transformMixedbreadResults(searchResponse); + + const duration = ((Date.now() - startTime) / 1000).toFixed(1); + if (IS_DEV) { + const resultTitles = results.slice(0, 3).map(r => `"${String(r.content).slice(0, 30)}..."`).join(', '); + console.log(`search (mixedbread) query: "${query}" took ${duration}s, results: ${resultTitles}${results.length > 3 ? `, +${results.length - 3} more` : ''}`); + } + + return NextResponse.json(results); + } else if (SEARCH_MODE === 'rag') { if (IS_DEV) { console.log(`search (rag) query: "${query}"${sdk ? ` sdk: "${sdk}"` : ''} received...`); } diff --git a/src/app/global.css b/src/app/global.css index 8553193a..e6d87744 100644 --- a/src/app/global.css +++ b/src/app/global.css @@ -60,3 +60,21 @@ svg[data-icon="true"] { /* ← arrow in current Fumadocs build */ pointer-events: none; } + +/* Hide scrollbar while keeping scroll functionality */ +@utility scrollbar-hidden { + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE/Edge */ + &::-webkit-scrollbar { + display: none; /* Chrome, Safari, Opera */ + } +} + +.mermaid-diagram { + overflow-x: auto; +} + +.mermaid-diagram svg { + max-width: 100%; + height: auto; +} diff --git a/src/components/AskAI.tsx b/src/components/AskAI.tsx index 25ba28d9..4c2693e4 100644 --- a/src/components/AskAI.tsx +++ b/src/components/AskAI.tsx @@ -24,6 +24,7 @@ import { IS_LOCAL_DEV, } from './askai-dev'; import { useLocalStorage } from '@/hooks/useLocalStorage'; +import { SDK_OPTIONS } from '@/lib/mixedbread'; import { Conversation, ConversationContent, @@ -40,15 +41,6 @@ import { const API_URL = IS_LOCAL_DEV ? 'http://localhost:8787' : 'https://docs-ai-api.superwall.com'; -const SDK_OPTIONS = [ - { value: '', label: 'None' }, - { value: 'ios', label: 'iOS' }, - { value: 'android', label: 'Android' }, - { value: 'expo', label: 'Expo' }, - { value: 'flutter', label: 'Flutter' }, - { value: 'react-native', label: 'React Native (Deprecated)' }, -] as const; - type ChatMessage = { id: string; role: 'user' | 'assistant'; diff --git a/src/components/ChangelogEntry.tsx b/src/components/ChangelogEntry.tsx new file mode 100644 index 00000000..38440537 --- /dev/null +++ b/src/components/ChangelogEntry.tsx @@ -0,0 +1,93 @@ +import Link from 'next/link'; + +export interface ChangelogEntryProps { + title: string; + description: string; + url: string; + date: string; + changeType: 'added' | 'modified'; +} + +/** + * Format a date as relative time (e.g., "3 days ago", "2 weeks ago"). + */ +function formatRelativeDate(dateStr: string): string { + const date = new Date(dateStr); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) return 'Today'; + if (diffDays === 1) return 'Yesterday'; + if (diffDays < 7) return `${diffDays} days ago`; + if (diffDays < 14) return '1 week ago'; + if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`; + if (diffDays < 60) return '1 month ago'; + return `${Math.floor(diffDays / 30)} months ago`; +} + +/** + * Format a date for display in the title attribute. + */ +function formatFullDate(dateStr: string): string { + return new Date(dateStr).toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }); +} + +export function ChangelogEntry({ + title, + description, + url, + date, + changeType, +}: ChangelogEntryProps) { + return ( +
  • + {/* Title + Pill badge + Date */} +
    +

    + + {title} + +

    + + {/* Change type pill */} + + {changeType === 'added' ? 'New' : 'Update'} + + + {/* Date */} + +
    + + {/* Description */} + {description && ( + + {description} + + )} +
  • + ); +} diff --git a/src/components/ChangelogTimeline.tsx b/src/components/ChangelogTimeline.tsx new file mode 100644 index 00000000..3d3a6558 --- /dev/null +++ b/src/components/ChangelogTimeline.tsx @@ -0,0 +1,223 @@ +'use client'; + +import { ChangelogEntry } from './ChangelogEntry'; + +// Import the generated changelog data +// This file is incrementally updated by scripts/generate-changelog.ts +// and should be committed to the repo +import changelogData from '@/lib/changelog-entries.json'; + +interface ChangelogDataType { + lastUpdated: string; + entries: Array<{ + key: string; + path: string; + title: string; + description: string; + category: string; + subcategory?: string; + url: string; + date: string; + changeType: 'added' | 'modified'; + }>; +} + +interface TimePeriod { + label: string; + key: string; + entries: ChangelogDataType['entries']; +} + +interface CategoryGroup { + category: string; + entries: ChangelogDataType['entries']; +} + +/** + * Filter entries to only include the last N months. + */ +function filterToRecentMonths( + entries: ChangelogDataType['entries'], + months: number +): ChangelogDataType['entries'] { + const now = new Date(); + const cutoffDate = new Date(now.getFullYear(), now.getMonth() - months + 1, 1); + + return entries.filter((entry) => { + const entryDate = new Date(entry.date); + return entryDate >= cutoffDate; + }); +} + +/** + * Get the month key for a date (e.g., "2026-01"). + */ +function getMonthKey(date: Date): string { + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + return `${year}-${month}`; +} + +/** + * Get a human-readable label for a month key. + */ +function getMonthLabel(monthKey: string): string { + const [year, month] = monthKey.split('-'); + const date = new Date(parseInt(year, 10), parseInt(month, 10) - 1, 1); + return date.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }); +} + +/** + * Group entries by month. + */ +function groupByMonth(entries: ChangelogDataType['entries']): TimePeriod[] { + const monthMap = new Map(); + + for (const entry of entries) { + const date = new Date(entry.date); + const key = getMonthKey(date); + + if (!monthMap.has(key)) { + monthMap.set(key, []); + } + monthMap.get(key)!.push(entry); + } + + // Convert to array and sort by key (newest first) + const periods: TimePeriod[] = []; + for (const [key, periodEntries] of monthMap.entries()) { + periods.push({ + key, + label: getMonthLabel(key), + entries: periodEntries, + }); + } + + // Sort periods by key descending (newest first) + periods.sort((a, b) => b.key.localeCompare(a.key)); + + return periods; +} + +/** + * Group entries by category. + */ +function groupByCategory( + entries: ChangelogDataType['entries'] +): CategoryGroup[] { + const categoryMap = new Map(); + + for (const entry of entries) { + const key = entry.subcategory + ? `${entry.category} → ${entry.subcategory}` + : entry.category; + + if (!categoryMap.has(key)) { + categoryMap.set(key, []); + } + categoryMap.get(key)!.push(entry); + } + + // Convert to array + const groups: CategoryGroup[] = []; + for (const [category, categoryEntries] of categoryMap.entries()) { + groups.push({ category, entries: categoryEntries }); + } + + return groups; +} + +/** + * Empty state component when no entries exist. + */ +function EmptyState() { + return ( +
    +

    No documentation changes yet

    +

    + Changes to the documentation will appear here automatically. +

    +
    + ); +} + +/** + * ChangelogTimeline renders the auto-generated changelog grouped by month and category. + */ +const MONTHS_TO_SHOW = 3; + +export function ChangelogTimeline() { + const data = changelogData as ChangelogDataType; + + if (!data.entries || data.entries.length === 0) { + return ; + } + + // Only show entries from the last 3 months + const recentEntries = filterToRecentMonths(data.entries, MONTHS_TO_SHOW); + + if (recentEntries.length === 0) { + return ; + } + + const periods = groupByMonth(recentEntries); + + return ( +
    + {periods.map((period) => { + const categoryGroups = groupByCategory(period.entries); + + return ( +
    + {/* Month heading */} +

    + {period.label} +

    + + {/* Category groups within this month */} +
    + {categoryGroups.map((group) => ( +
    + {/* Category subheading */} +

    + {group.category} +

    + + {/* Entries in this category */} +
      + {group.entries.map((entry) => ( + + ))} +
    +
    + ))} +
    +
    + ); + })} + + {/* Footer with generation info */} +
    +

    + This changelog is automatically generated from git history. +
    + Last updated:{' '} + {new Date(data.lastUpdated).toLocaleDateString('en-US', { + month: 'long', + day: 'numeric', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + })} +

    +
    +
    + ); +} diff --git a/src/components/Mermaid.tsx b/src/components/Mermaid.tsx new file mode 100644 index 00000000..b21e2060 --- /dev/null +++ b/src/components/Mermaid.tsx @@ -0,0 +1,87 @@ +"use client" + +import mermaid from "mermaid" +import React from "react" + +let mermaidInitialized = false + +type MermaidProps = { + code?: string + children?: React.ReactNode +} + +const getDiagram = ({ code, children }: MermaidProps) => { + if (typeof code === "string") return code.trim() + + if (typeof children === "string") { + return children.trim() + } + + if (Array.isArray(children)) { + return children.join("").trim() + } + + return "" +} + +export function Mermaid(props: MermaidProps) { + const ref = React.useRef(null) + const id = React.useId().replace(/:/g, "") + const diagram = getDiagram(props) + + React.useEffect(() => { + if (!ref.current || !diagram) return + + if (!mermaidInitialized) { + const getCssVar = (name: string, fallback: string) => { + const value = getComputedStyle(document.documentElement) + .getPropertyValue(name) + .trim() + return value || fallback + } + + mermaid.initialize({ + startOnLoad: false, + theme: "base", + themeVariables: { + fontFamily: "Inter, ui-sans-serif, system-ui, sans-serif", + fontSize: "14px", + primaryColor: getCssVar("--color-fd-muted", "#1F2630"), + primaryTextColor: getCssVar("--color-fd-foreground", "#F6F8FA"), + primaryBorderColor: getCssVar("--color-fd-border", "#2A303C"), + lineColor: getCssVar("--color-fd-border", "#2A303C"), + secondaryColor: getCssVar("--color-fd-muted", "#1F2630"), + tertiaryColor: getCssVar("--color-fd-muted", "#1F2630"), + }, + flowchart: { + htmlLabels: false, + curve: "linear", + }, + }) + mermaidInitialized = true + } + + const container = ref.current + container.innerHTML = "" + let cancelled = false + + void mermaid + .render(`mermaid-${id}`, diagram) + .then(({ svg }) => { + if (!cancelled) { + container.innerHTML = svg + } + }) + .catch(() => { + if (!cancelled) { + container.textContent = diagram + } + }) + + return () => { + cancelled = true + } + }, [diagram, id]) + + return
    +} diff --git a/src/components/SearchDialog.tsx b/src/components/SearchDialog.tsx index ceb6a8dd..cdeb7691 100644 --- a/src/components/SearchDialog.tsx +++ b/src/components/SearchDialog.tsx @@ -10,21 +10,23 @@ import { Sparkles, CornerDownLeft, Text, - ChevronDown, + ArrowUp, + ArrowDown, } from 'lucide-react'; import { type ComponentProps, type ReactNode, useCallback, useEffect, - useMemo, useState, + useMemo, useRef, } from 'react'; import { useLocalStorage } from '@/hooks/useLocalStorage'; import { useI18n } from 'fumadocs-ui/contexts/i18n'; import { cn } from 'fumadocs-ui/utils/cn'; import { useSidebar } from 'fumadocs-ui/contexts/sidebar'; +import { SDK_OPTIONS } from '@/lib/mixedbread'; import { Dialog, @@ -48,6 +50,7 @@ type ReactSortedResult = Omit & { external?: boolean; content: ReactNode; tag?: string; + description?: string; }; export interface TagItem { @@ -76,27 +79,22 @@ interface SearchDialogProps extends SharedProps { footer?: ReactNode; } -const SDK_OPTIONS = [ - { value: '', label: 'None' }, - { value: 'ios', label: 'iOS' }, - { value: 'android', label: 'Android' }, - { value: 'expo', label: 'Expo' }, - { value: 'flutter', label: 'Flutter' }, - { value: 'react-native', label: 'React Native (Deprecated)' }, -] as const; - export function SearchDialogWrapper(props: SharedProps) { // Use same localStorage key as AskAI const [selectedSdk, setSelectedSdk] = useLocalStorage('superwall-ai-selected-sdk', ''); const [isDebouncing, setIsDebouncing] = useState(false); + // When no SDK is selected (empty string), pass 'none' to filter for non-SDK docs only + // When a specific SDK is selected, pass that SDK value + const sdkParam = selectedSdk || 'none'; + const { search, setSearch, query } = useDocsSearch({ type: 'fetch', - api: selectedSdk ? `/docs/api/search?sdk=${selectedSdk}` : '/docs/api/search', - delayMs: SEARCH_DEBOUNCE_MS + api: `/docs/api/search?sdk=${sdkParam}`, + delayMs: SEARCH_DEBOUNCE_MS, }); - // Track debouncing state in development + // Track debouncing state - show loading only after debounce, not during typing useEffect(() => { if (search.length > 0) { setIsDebouncing(true); @@ -116,13 +114,16 @@ export function SearchDialogWrapper(props: SharedProps) { } }, [query.data, search]); + // Only show loading when actually fetching (not during debounce) + const showLoading = query.isLoading && !isDebouncing; + return ( (null); + const [sdkFilter, setSdkFilter] = useState(''); + const searchInputRef = useRef(null); const router = useRouter(); + // Filter SDK options based on search + const filteredSdkOptions = useMemo(() => { + if (!sdkFilter.trim()) return SDK_OPTIONS; + const lower = sdkFilter.toLowerCase(); + return SDK_OPTIONS.filter(opt => + opt.label.toLowerCase().includes(lower) + ); + }, [sdkFilter]); + const selectSdk = (sdkValue: string) => { onSdkChange(sdkValue); - setShowSdkDropdown(false); + setShowSdkPanel(false); + setSdkFilter(''); + // Refocus search input after selection + setTimeout(() => { + searchInputRef.current?.focus(); + }, 0); }; - const getSelectedSdk = () => { + const getSelectedSdkLabel = () => { const found = SDK_OPTIONS.find(opt => opt.value === selectedSdk); - return found || SDK_OPTIONS[0]; // Default to "None" + return found?.label || 'None'; }; + // Reset highlight and filter when panel opens/closes + useEffect(() => { + if (showSdkPanel) { + setSdkFilter(''); + const idx = SDK_OPTIONS.findIndex(opt => opt.value === selectedSdk); + setSdkHighlight(idx >= 0 ? idx : 0); + } + }, [selectedSdk, showSdkPanel]); + + // Reset highlight when filter changes useEffect(() => { - const idx = SDK_OPTIONS.findIndex(opt => opt.value === selectedSdk); - setSdkHighlight(idx >= 0 ? idx : 0); - }, [selectedSdk, showSdkDropdown]); + setSdkHighlight(0); + }, [sdkFilter]); + // Keyboard shortcuts useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { const isMeta = e.metaKey || e.ctrlKey; + + // ⌘I - Go to AI page if (isMeta && !e.shiftKey && e.key.toLowerCase() === 'i') { e.preventDefault(); dialogProps.onOpenChange(false); @@ -172,98 +200,136 @@ function SearchDialogWrapperInner(props: SearchDialogWrapperInnerProps) { return; } - if (dialogProps.open && isMeta && e.shiftKey && e.key.toLowerCase() === 's') { + // ⌘K - Toggle SDK panel (when search is open) + if (dialogProps.open && isMeta && e.key.toLowerCase() === 'k') { e.preventDefault(); - setShowSdkDropdown(true); - const idx = SDK_OPTIONS.findIndex(opt => opt.value === selectedSdk); - setSdkHighlight(idx >= 0 ? idx : 0); + setShowSdkPanel(prev => !prev); return; } - if (showSdkDropdown) { + // SDK Panel keyboard navigation + if (showSdkPanel) { if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); setSdkHighlight(prev => { const delta = e.key === 'ArrowDown' ? 1 : -1; - return (prev + delta + SDK_OPTIONS.length) % SDK_OPTIONS.length; + const len = filteredSdkOptions.length; + if (len === 0) return 0; + return (prev + delta + len) % len; }); return; } if (e.key === 'Enter') { e.preventDefault(); - const opt = SDK_OPTIONS[sdkHighlight]; + const opt = filteredSdkOptions[sdkHighlight]; if (opt) selectSdk(opt.value); return; } - if (e.key === 'Escape') { - setShowSdkDropdown(false); - return; - } } }; window.addEventListener('keydown', onKeyDown); return () => window.removeEventListener('keydown', onKeyDown); - }, [dialogProps.open, selectedSdk, sdkHighlight, showSdkDropdown, router]); - - // Close dropdown when clicking outside - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { - setShowSdkDropdown(false); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); + }, [dialogProps.open, selectedSdk, sdkHighlight, showSdkPanel, router, dialogProps, filteredSdkOptions]); return ( - - - {showSdkDropdown && ( -
    - {SDK_OPTIONS.map((option) => ( + showSdkPanel={showSdkPanel} + sdkFilter={sdkFilter} + onClearSdkFilter={() => setSdkFilter('')} + onCloseSdkPanel={() => { + setShowSdkPanel(false); + setSdkFilter(''); + }} + searchInputRef={searchInputRef} + sdkPanel={ +
    + {/* Filter Input */} +
    + setSdkFilter(e.target.value)} + placeholder="Filter SDKs..." + className="w-full bg-transparent text-sm placeholder:text-fd-muted-foreground focus-visible:outline-none" + autoFocus + /> +
    + {/* SDK Options */} +
    + {filteredSdkOptions.length === 0 ? ( +
    + No matches +
    + ) : ( + filteredSdkOptions.map((option, index) => ( - ))} -
    - )} + )) + )} +
    } + sdkActionButton={ + + } /> ); } @@ -285,8 +351,22 @@ export function SearchDialog({ isLoading: propIsLoading, isDebouncing: propIsDebouncing, results: propResults, - sdkSelector, -}: SearchDialogProps & { sdkSelector?: ReactNode }) { + showSdkPanel, + sdkFilter, + onClearSdkFilter, + onCloseSdkPanel, + sdkPanel, + sdkActionButton, + searchInputRef, +}: SearchDialogProps & { + showSdkPanel?: boolean; + sdkFilter?: string; + onClearSdkFilter?: () => void; + onCloseSdkPanel?: () => void; + sdkPanel?: ReactNode; + sdkActionButton?: ReactNode; + searchInputRef?: React.RefObject; +}) { const { text } = useI18n(); const [active, setActive] = useState(); @@ -300,17 +380,6 @@ export function SearchDialog({ const isDebouncing = propIsDebouncing ?? false; const results = propResults ?? (query.data ?? 'empty'); - // Debounced search loading indicator - const [showSearchLoading, setShowSearchLoading] = useState(false); - - useEffect(() => { - if (isLoading) { - const t = setTimeout(() => setShowSearchLoading(true), 200); - return () => clearTimeout(t); - } - setShowSearchLoading(false); - }, [isLoading]); - // Default links if none provided const defaultLinks: SearchLink[] = [ ['Support', '/docs/support'], @@ -350,17 +419,46 @@ export function SearchDialog({ push(destination); }; + // Show empty state when no query entered OR while debouncing (user is still typing) + const showEmptyState = search.trim().length === 0 || isDebouncing; + return ( { + // Escape priority: + // 1. Clear SDK filter if it has text + // 2. Close SDK panel if filter is empty + // 3. Clear main search if it has text + // 4. Close dialog if search is empty + if (showSdkPanel) { + e.preventDefault(); + if (sdkFilter && sdkFilter.length > 0 && onClearSdkFilter) { + // Clear SDK filter input + onClearSdkFilter(); + } else if (onCloseSdkPanel) { + // Close SDK panel + onCloseSdkPanel(); + setTimeout(() => searchInputRef?.current?.focus(), 0); + } + } else if (search && search.trim().length > 0) { + e.preventDefault(); + // Clear main search input + onSearchChange(''); + } + // If none of the above, let the dialog close naturally + }} > {text.search} -
    - + + {/* Search Input */} +
    + { onSearchChange(e.target.value); @@ -374,32 +472,80 @@ export function SearchDialog({ } } }} - placeholder={text.search} - className="w-0 flex-1 bg-transparent py-3 text-base placeholder:text-fd-muted-foreground focus-visible:outline-none" + placeholder="Search the documentation..." + className="w-0 flex-1 bg-transparent py-4 text-base placeholder:text-fd-muted-foreground focus-visible:outline-none" /> - {sdkSelector} -
    - {allItems.length > 0 ? ( + {/* Content Area */} + {showEmptyState ? ( + /* Empty State - no query entered */ +
    + +

    + Enter a query to search the documentation +

    +
    + ) : isLoading ? ( + /* Loading State - centered spinner while searching */ +
    + +

    + Searching... +

    +
    + ) : ( + /* Search Results */ onOpenChange(false)} onAiSearch={handleAiSearch} + disableKeyboardNav={showSdkPanel} /> - ) : null} -
    - {footer} + )} + + {/* Footer with keyboard shortcuts and SDK action */} +
    + {/* Left side - Keyboard shortcuts */} +
    +
    + + + + + + + to navigate +
    +
    + + + + to select +
    +
    + + esc + + to close +
    +
    + + {/* Right side - SDK action button with popup */} +
    + {/* SDK Panel - Raycast style popup */} + {showSdkPanel && sdkPanel} + {sdkActionButton} +
    + + {footer && ( +
    + {footer} +
    + )}
    ); @@ -426,6 +572,7 @@ function SearchResults({ onActiveChange, onSelect, onAiSearch, + disableKeyboardNav, ...props }: ComponentProps<'div'> & { active?: string; @@ -433,6 +580,7 @@ function SearchResults({ items: (ReactSortedResult | AIPrompt)[]; onSelect?: (value: string) => void; onAiSearch: () => void; + disableKeyboardNav?: boolean; }) { const { text } = useI18n(); const router = useRouter(); @@ -446,6 +594,9 @@ function SearchResults({ }; const onKey = useEffectEvent((e: KeyboardEvent) => { + // Skip keyboard navigation when SDK panel is open + if (disableKeyboardNav) return; + if (e.key === 'ArrowDown' || e.key == 'ArrowUp') { const idx = items.findIndex((item) => item.id === active); if (idx === -1) { @@ -485,12 +636,15 @@ function SearchResults({
    {items.length === 0 ? ( -
    {text.searchNoResult}
    +
    + +

    {text.searchNoResult}

    +
    ) : null} {items.map((item) => { @@ -532,6 +686,11 @@ function SearchResults({ } }; + // Only show SDK badge for actual SDK folders + const sdkFolders = ['ios', 'android', 'flutter', 'expo', 'react-native']; + const showSdkBadge = sdkFolders.includes(rootFolder); + const description = (resultItem as any).description; + return ( ) : null} {getIcon(item.type, active === item.id)} - {/* Show tag badge for SDK results - moved to left */} - {item.type === 'page' && rootFolder && ( -
    - - {formatRootFolder(rootFolder)} - -
    - )}
    -

    {item.content}

    +

    {item.content}

    + {/* Show description for pages and headings */} + {description && ( +

    + {description} +

    + )}
    + {/* Show SDK badge only for SDK folders */} + {item.type === 'page' && showSdkBadge && ( + + {formatRootFolder(rootFolder)} + + )} {active === item.id && ( - + )}
    ); @@ -572,35 +735,6 @@ function SearchResults({ ); } -function LoadingIndicator({ isLoading, isDebouncing }: { isLoading: boolean; isDebouncing?: boolean }) { - const isDev = process.env.NODE_ENV === 'development'; - const showDebugState = isDev && (isLoading || isDebouncing); - - return ( -
    -
    - - -
    - {showDebugState && ( - - {isLoading ? 'Searching...' : isDebouncing ? 'Debouncing...' : ''} - - )} -
    - ); -} - function CommandItem({ active = false, ...props diff --git a/src/components/SmartRootToggle.tsx b/src/components/SmartRootToggle.tsx index 4fc619ee..c36d550f 100644 --- a/src/components/SmartRootToggle.tsx +++ b/src/components/SmartRootToggle.tsx @@ -23,7 +23,7 @@ interface SmartRootToggleProps { className?: string } -const GENERAL_ROOT_ORDER = ['dashboard', 'web-checkout', 'integrations', 'support'] as const +const GENERAL_ROOT_ORDER = ['dashboard', 'web-checkout', 'integrations', 'support', 'changelog'] as const const SDK_TARGETS = new Set(['ios', 'android', 'flutter', 'expo', 'react-native', 'dashboard']) const isSdkRoot = (key: string): key is SDKType => SDK_TARGETS.has(key as SDKType) diff --git a/src/lib/changelog-entries.json b/src/lib/changelog-entries.json new file mode 100644 index 00000000..347b38ef --- /dev/null +++ b/src/lib/changelog-entries.json @@ -0,0 +1,3223 @@ +{ + "lastUpdated": "2026-02-18T18:31:03.599Z", + "entries": [ + { + "key": "content/docs/ios/guides/handling-deep-links.mdx:05a92e49728094baaf0e49e70efc59fabf0403a7", + "path": "ios/guides/handling-deep-links.mdx", + "title": "Handling Deep Links", + "description": "Explains how to handle deep links for presenting paywalls without hardcoding app logic.", + "category": "iOS SDK", + "subcategory": "Guides", + "url": "/docs/ios/guides/handling-deep-links", + "date": "2026-02-18T18:22:08.000Z", + "changeType": "added" + }, + { + "key": "content/docs/android/guides/handling-deep-links.mdx:05a92e49728094baaf0e49e70efc59fabf0403a7", + "path": "android/guides/handling-deep-links.mdx", + "title": "Handling Deep Links", + "description": "Describes how to handle deep links and present paywalls using campaign rules in Android.", + "category": "Android SDK", + "subcategory": "Guides", + "url": "/docs/android/guides/handling-deep-links", + "date": "2026-02-18T18:22:08.000Z", + "changeType": "added" + }, + { + "key": "content/docs/flutter/guides/handling-deep-links.mdx:05a92e49728094baaf0e49e70efc59fabf0403a7", + "path": "flutter/guides/handling-deep-links.mdx", + "title": "Handling Deep Links", + "description": "Explains how to use deep links for presenting paywalls without hardcoding app logic.", + "category": "Flutter SDK", + "subcategory": "Guides", + "url": "/docs/flutter/guides/handling-deep-links", + "date": "2026-02-18T18:22:08.000Z", + "changeType": "added" + }, + { + "key": "content/docs/expo/guides/handling-deep-links.mdx:05a92e49728094baaf0e49e70efc59fabf0403a7", + "path": "expo/guides/handling-deep-links.mdx", + "title": "Handling Deep Links", + "description": "Added guide for handling deep links and presenting paywalls without hardcoding logic.", + "category": "Expo SDK", + "subcategory": "Guides", + "url": "/docs/expo/guides/handling-deep-links", + "date": "2026-02-18T18:22:08.000Z", + "changeType": "added" + }, + { + "key": "content/docs/dashboard/dashboard-settings/overview-settings-team.mdx:bec43e117c843351c6ee92dd6b2a452904fc2b5d", + "path": "dashboard/dashboard-settings/overview-settings-team.mdx", + "title": "Team", + "description": "Adds details about legacy User role and guidance for team role assignments.", + "category": "Dashboard", + "subcategory": "Settings", + "url": "/docs/dashboard/dashboard-settings/overview-settings-team", + "date": "2026-02-18T16:25:22.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/support/dashboard/how-to-reset-paywall-history-for-a-b-testing.mdx:bec43e117c843351c6ee92dd6b2a452904fc2b5d", + "path": "support/dashboard/how-to-reset-paywall-history-for-a-b-testing.mdx", + "title": "How to Start a Fresh A/B Test", + "description": "Learn how to reset A/B test assignments or create a new campaign for clean paywall metrics.", + "category": "Support", + "url": "/docs/support/dashboard/how-to-reset-paywall-history-for-a-b-testing", + "date": "2026-02-18T16:25:22.000Z", + "changeType": "added" + }, + { + "key": "content/docs/support/dashboard/what-is-the-legacy-user-role-in-team-settings.mdx:bec43e117c843351c6ee92dd6b2a452904fc2b5d", + "path": "support/dashboard/what-is-the-legacy-user-role-in-team-settings.mdx", + "title": "What is the legacy User role in team settings?", + "description": "Explains the legacy User role in team settings and recommends current role assignments.", + "category": "Support", + "url": "/docs/support/dashboard/what-is-the-legacy-user-role-in-team-settings", + "date": "2026-02-18T16:25:22.000Z", + "changeType": "added" + }, + { + "key": "content/docs/support/dashboard/why-does-the-sdk-releases-behind-warning-still-appear.mdx:bec43e117c843351c6ee92dd6b2a452904fc2b5d", + "path": "support/dashboard/why-does-the-sdk-releases-behind-warning-still-appear.mdx", + "title": "Why does the \"SDK releases behind\" warning still appear after updating?", + "description": "Explains why the \"SDK releases behind\" warning persists after updating the SDK version.", + "category": "Support", + "url": "/docs/support/dashboard/why-does-the-sdk-releases-behind-warning-still-appear", + "date": "2026-02-18T16:25:22.000Z", + "changeType": "added" + }, + { + "key": "content/docs/support/faq/how-to-extract-apple-search-ads-data-from-superwall.mdx:bec43e117c843351c6ee92dd6b2a452904fc2b5d", + "path": "support/faq/how-to-extract-apple-search-ads-data-from-superwall.mdx", + "title": "How do I extract Apple Search Ads attribution data from Superwall?", + "description": "Explains how to extract Apple Search Ads attribution data from the Superwall SDK on iOS.", + "category": "Support", + "subcategory": "FAQ", + "url": "/docs/support/faq/how-to-extract-apple-search-ads-data-from-superwall", + "date": "2026-02-18T16:25:22.000Z", + "changeType": "added" + }, + { + "key": "content/docs/support/paywall-editor/localizing-paywalls-with-dynamic-values.mdx:bec43e117c843351c6ee92dd6b2a452904fc2b5d", + "path": "support/paywall-editor/localizing-paywalls-with-dynamic-values.mdx", + "title": "Localizing Paywalls with Dynamic Values", + "description": "Learn how to localize paywall text using dynamic values based on device language.", + "category": "Support", + "url": "/docs/support/paywall-editor/localizing-paywalls-with-dynamic-values", + "date": "2026-02-18T16:25:22.000Z", + "changeType": "added" + }, + { + "key": "content/docs/support/troubleshooting/how-to-test-paywalls-with-local-currency-for-a-specific-country.mdx:bec43e117c843351c6ee92dd6b2a452904fc2b5d", + "path": "support/troubleshooting/how-to-test-paywalls-with-local-currency-for-a-specific-country.mdx", + "title": "How to Test Paywalls with Local Currency for a Specific Country", + "description": "Explains how to test paywalls with local currency and country-specific audience filters on iOS.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/how-to-test-paywalls-with-local-currency-for-a-specific-country", + "date": "2026-02-18T16:25:22.000Z", + "changeType": "added" + }, + { + "key": "content/docs/support/troubleshooting/troubleshooting-old-paywall-persists-after-removal.mdx:bec43e117c843351c6ee92dd6b2a452904fc2b5d", + "path": "support/troubleshooting/troubleshooting-old-paywall-persists-after-removal.mdx", + "title": "Why Are Users Still Seeing a Removed Paywall?", + "description": "Explains how to reset sticky paywall assignments and force users to see new variants.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/troubleshooting-old-paywall-persists-after-removal", + "date": "2026-02-18T16:25:22.000Z", + "changeType": "added" + }, + { + "key": "content/docs/ios/changelog.mdx:45ea10971484ea6fbf7e1b6b7b1f894f659145b3", + "path": "ios/changelog.mdx", + "title": "Changelog", + "description": "Adds new changelog entries for Superwall iOS SDK versions 4.12.0-4.12.11, including enhancements and fixes.", + "category": "iOS SDK", + "url": "/docs/ios/changelog", + "date": "2026-02-12T21:01:10.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/ios/guides/advanced/request-permissions-from-paywalls.mdx:45ea10971484ea6fbf7e1b6b7b1f894f659145b3", + "path": "ios/guides/advanced/request-permissions-from-paywalls.mdx", + "title": "Request permissions from paywalls", + "description": "Updates guidance on requesting iOS permissions directly from paywalls using new action.", + "category": "iOS SDK", + "subcategory": "Guides", + "url": "/docs/ios/guides/advanced/request-permissions-from-paywalls", + "date": "2026-02-12T21:01:10.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/ios/index.mdx:45ea10971484ea6fbf7e1b6b7b1f894f659145b3", + "path": "ios/index.mdx", + "title": "Welcome", + "description": "Updates SDK version to 4.12.11 and reformats card layout for improved readability.", + "category": "iOS SDK", + "url": "/docs/ios", + "date": "2026-02-12T21:01:10.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/ios/sdk-reference/PaywallPresentationHandler.mdx:45ea10971484ea6fbf7e1b6b7b1f894f659145b3", + "path": "ios/sdk-reference/PaywallPresentationHandler.mdx", + "title": "PaywallPresentationHandler", + "description": "Adds support for handling custom paywall callbacks with async handler method", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/PaywallPresentationHandler", + "date": "2026-02-12T21:01:10.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/ios/sdk-reference/index.mdx:45ea10971484ea6fbf7e1b6b7b1f894f659145b3", + "path": "ios/sdk-reference/index.mdx", + "title": "Overview", + "description": "Updates Superwall iOS SDK version reference to 4.12.11", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference", + "date": "2026-02-12T21:01:10.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/ios/sdk-reference/setIntegrationAttributes.mdx:45ea10971484ea6fbf7e1b6b7b1f894f659145b3", + "path": "ios/sdk-reference/setIntegrationAttributes.mdx", + "title": "setIntegrationAttributes", + "description": "Added `.appstackId` integration attribute for tracking Appstack identifier in iOS SDK.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/setIntegrationAttributes", + "date": "2026-02-12T21:01:10.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/dashboard/dashboard-demand-score/demand-score-experiments.mdx:784bc49b33f64cac5cdf8c7c23febeb2741b39f2", + "path": "dashboard/dashboard-demand-score/demand-score-experiments.mdx", + "title": "Using Demand Score in Campaigns", + "description": "Learn how to create and target campaigns using Demand Score audience ranges.", + "category": "Dashboard", + "url": "/docs/dashboard/dashboard-demand-score/demand-score-experiments", + "date": "2026-02-12T20:10:53.000Z", + "changeType": "added" + }, + { + "key": "content/docs/dashboard/dashboard-demand-score/demand-score-insights.mdx:784bc49b33f64cac5cdf8c7c23febeb2741b39f2", + "path": "dashboard/dashboard-demand-score/demand-score-insights.mdx", + "title": "Understanding Demand Score Insights", + "description": "Learn how demand score insights correlate with conversion rates, volume, and billing outcomes.", + "category": "Dashboard", + "url": "/docs/dashboard/dashboard-demand-score/demand-score-insights", + "date": "2026-02-12T20:10:53.000Z", + "changeType": "added" + }, + { + "key": "content/docs/dashboard/dashboard-demand-score/demand-score.mdx:784bc49b33f64cac5cdf8c7c23febeb2741b39f2", + "path": "dashboard/dashboard-demand-score/demand-score.mdx", + "title": "Using Demand Score", + "description": "Introduces Demand Score feature, explaining how to view and use conversion likelihood metrics.", + "category": "Dashboard", + "url": "/docs/dashboard/dashboard-demand-score/demand-score", + "date": "2026-02-12T20:10:53.000Z", + "changeType": "added" + }, + { + "key": "content/docs/android/changelog.mdx:99714fe5edf5260a20acbd5c8e59fe33fef41c66", + "path": "android/changelog.mdx", + "title": "Changelog", + "description": "Adds new paywall features including custom callbacks, purchase options, and Stripe checkout improvements.", + "category": "Android SDK", + "url": "/docs/android/changelog", + "date": "2026-02-12T01:08:01.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/android/guides/advanced/custom-callbacks.mdx:99714fe5edf5260a20acbd5c8e59fe33fef41c66", + "path": "android/guides/advanced/custom-callbacks.mdx", + "title": "Custom callbacks", + "description": "Introduces custom callbacks for running app-side logic and branching paywall actions in Android.", + "category": "Android SDK", + "subcategory": "Guides", + "url": "/docs/android/guides/advanced/custom-callbacks", + "date": "2026-02-12T01:08:01.000Z", + "changeType": "added" + }, + { + "key": "content/docs/android/guides/advanced/request-permissions-from-paywalls.mdx:99714fe5edf5260a20acbd5c8e59fe33fef41c66", + "path": "android/guides/advanced/request-permissions-from-paywalls.mdx", + "title": "Request permissions from paywalls", + "description": "Added guide for requesting Android runtime permissions directly from paywalls.", + "category": "Android SDK", + "subcategory": "Guides", + "url": "/docs/android/guides/advanced/request-permissions-from-paywalls", + "date": "2026-02-12T01:08:01.000Z", + "changeType": "added" + }, + { + "key": "content/docs/android/index.mdx:99714fe5edf5260a20acbd5c8e59fe33fef41c66", + "path": "android/index.mdx", + "title": "Welcome", + "description": "Updates Android SDK version reference to 2.7.0 in latest version component.", + "category": "Android SDK", + "url": "/docs/android", + "date": "2026-02-12T01:08:01.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/android/quickstart/install.mdx:99714fe5edf5260a20acbd5c8e59fe33fef41c66", + "path": "android/quickstart/install.mdx", + "title": "Install the SDK", + "description": "Updates Android SDK installation instructions to version 2.7.0.", + "category": "Android SDK", + "subcategory": "Quickstart", + "url": "/docs/android/quickstart/install", + "date": "2026-02-12T01:08:01.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/android/quickstart/tracking-subscription-state.mdx:99714fe5edf5260a20acbd5c8e59fe33fef41c66", + "path": "android/quickstart/tracking-subscription-state.mdx", + "title": "Tracking Subscription State", + "description": "Explains how to read detailed purchase history and access customer information in Android SDK.", + "category": "Android SDK", + "subcategory": "Quickstart", + "url": "/docs/android/quickstart/tracking-subscription-state", + "date": "2026-02-12T01:08:01.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/android/sdk-reference/PaywallOptions.mdx:99714fe5edf5260a20acbd5c8e59fe33fef41c66", + "path": "android/sdk-reference/PaywallOptions.mdx", + "title": "PaywallOptions", + "description": "Adds new `timeoutAfter` configuration to control paywall loading timeout duration.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/PaywallOptions", + "date": "2026-02-12T01:08:01.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/android/sdk-reference/Superwall.mdx:99714fe5edf5260a20acbd5c8e59fe33fef41c66", + "path": "android/sdk-reference/Superwall.mdx", + "title": "Superwall", + "description": "Adds reactive customer info observation using StateFlow with immediate snapshot support.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/Superwall", + "date": "2026-02-12T01:08:01.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/android/sdk-reference/SuperwallDelegate.mdx:99714fe5edf5260a20acbd5c8e59fe33fef41c66", + "path": "android/sdk-reference/SuperwallDelegate.mdx", + "title": "SuperwallDelegate", + "description": "Adds new delegate methods for tracking customer info and user attribute changes.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/SuperwallDelegate", + "date": "2026-02-12T01:08:01.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/android/sdk-reference/SuperwallEvent.mdx:99714fe5edf5260a20acbd5c8e59fe33fef41c66", + "path": "android/sdk-reference/SuperwallEvent.mdx", + "title": "SuperwallEvent", + "description": "Adds details about new Superwall events, deprecations, and example event handling in Android SDK.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/SuperwallEvent", + "date": "2026-02-12T01:08:01.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/android/sdk-reference/index.mdx:99714fe5edf5260a20acbd5c8e59fe33fef41c66", + "path": "android/sdk-reference/index.mdx", + "title": "Overview", + "description": "Updates Android SDK version reference to 2.7.0.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference", + "date": "2026-02-12T01:08:01.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/using-referral-or-promo-codes-with-superwall.mdx:8152d0f4dfa3f1228a269e686da73edc206b2139", + "path": "using-referral-or-promo-codes-with-superwall.mdx", + "title": "Using Referral or Promo Codes with Superwall", + "description": "Adds note about custom UI support for referral redemption in iOS and Android SDKs.", + "category": "Documentation", + "url": "/docs/using-referral-or-promo-codes-with-superwall", + "date": "2026-02-10T21:27:06.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/integrations/figma-plugin.mdx:f62d1f7a5268d8c607292869a14784a0d82a0b59", + "path": "integrations/figma-plugin.mdx", + "title": "Figma Plugin", + "description": "Clarifies Auto Layout requirement applies to entire Figma frame during import.", + "category": "Integrations", + "url": "/docs/integrations/figma-plugin", + "date": "2026-02-05T19:23:10.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-products.mdx:7d1678be2b6071c2859923c044a3d48b9aa8b38a", + "path": "dashboard/dashboard-creating-paywalls/paywall-editor-products.mdx", + "title": "Products", + "description": "Updated web checkout description to specify Stripe products instead of multiple providers.", + "category": "Dashboard", + "subcategory": "Creating Paywalls", + "url": "/docs/dashboard/dashboard-creating-paywalls/paywall-editor-products", + "date": "2026-02-05T05:06:12.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/integrations/amplitude.mdx:7d1678be2b6071c2859923c044a3d48b9aa8b38a", + "path": "integrations/amplitude.mdx", + "title": "Amplitude", + "description": "Removed Paddle payments from platform list in Amplitude event integration guide.", + "category": "Integrations", + "url": "/docs/integrations/amplitude", + "date": "2026-02-05T05:06:12.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/integrations/index.mdx:7d1678be2b6071c2859923c044a3d48b9aa8b38a", + "path": "integrations/index.mdx", + "title": "Integrations", + "description": "Removed references to Paddle and updated store descriptions for payment events.", + "category": "Integrations", + "url": "/docs/integrations", + "date": "2026-02-05T05:06:12.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/integrations/webhooks/index.mdx:7d1678be2b6071c2859923c044a3d48b9aa8b38a", + "path": "integrations/webhooks/index.mdx", + "title": "Webhooks", + "description": "Removes Paddle references and updates webhook store field descriptions for clarity.", + "category": "Integrations", + "url": "/docs/integrations/webhooks", + "date": "2026-02-05T05:06:12.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/web-checkout/web-checkout-adding-a-stripe-product.mdx:7d1678be2b6071c2859923c044a3d48b9aa8b38a", + "path": "web-checkout/web-checkout-adding-a-stripe-product.mdx", + "title": "Creating Products", + "description": "Removes Paddle beta references and focuses exclusively on Stripe product creation steps.", + "category": "Web Checkout", + "url": "/docs/web-checkout/web-checkout-adding-a-stripe-product", + "date": "2026-02-05T05:06:12.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/web-checkout/web-checkout-overview.mdx:7d1678be2b6071c2859923c044a3d48b9aa8b38a", + "path": "web-checkout/web-checkout-overview.mdx", + "title": "Overview", + "description": "Removes Paddle references and streamlines web checkout setup instructions for Stripe integration.", + "category": "Web Checkout", + "url": "/docs/web-checkout/web-checkout-overview", + "date": "2026-02-05T05:06:12.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/web-checkout/web-checkout-web-only.mdx:7d1678be2b6071c2859923c044a3d48b9aa8b38a", + "path": "web-checkout/web-checkout-web-only.mdx", + "title": "Web-Only Checkout", + "description": "Clarifies Stripe setup steps and removes Paddle unsupported note for web-only checkout.", + "category": "Web Checkout", + "url": "/docs/web-checkout/web-checkout-web-only", + "date": "2026-02-05T05:06:12.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/support/faq/app-store-compliance-paywall-events.mdx:c6cbd766b6198cf7e07e9fc3f645b22f707368a8", + "path": "support/faq/app-store-compliance-paywall-events.mdx", + "title": "Are Paywall Events and Urgency Messaging App Store Compliant?", + "description": "Clarifies App Store compliance for paywall events, urgency messaging, and re-engagement strategies.", + "category": "Support", + "subcategory": "FAQ", + "url": "/docs/support/faq/app-store-compliance-paywall-events", + "date": "2026-01-30T15:11:35.000Z", + "changeType": "added" + }, + { + "key": "content/docs/support/troubleshooting/troubleshooting-sandbox-entitlements-persist.mdx:c6cbd766b6198cf7e07e9fc3f645b22f707368a8", + "path": "support/troubleshooting/troubleshooting-sandbox-entitlements-persist.mdx", + "title": "Sandbox Entitlements Persist After Reset", + "description": "Explains why Apple sandbox entitlements persist and how to create fresh test accounts.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/troubleshooting-sandbox-entitlements-persist", + "date": "2026-01-30T15:11:35.000Z", + "changeType": "added" + }, + { + "key": "content/docs/support/web-checkout/how-to-retrieve-stripe-customer-data-after-web-checkout.mdx:c6cbd766b6198cf7e07e9fc3f645b22f707368a8", + "path": "support/web-checkout/how-to-retrieve-stripe-customer-data-after-web-checkout.mdx", + "title": "How do I retrieve Stripe customer data after web checkout?", + "description": "Learn how to retrieve Stripe customer ID and email after web checkout purchase.", + "category": "Support", + "url": "/docs/support/web-checkout/how-to-retrieve-stripe-customer-data-after-web-checkout", + "date": "2026-01-30T15:11:35.000Z", + "changeType": "added" + }, + { + "key": "content/docs/support/web-checkout/web-checkout-revenue-tracking-is-automatic.mdx:c6cbd766b6198cf7e07e9fc3f645b22f707368a8", + "path": "support/web-checkout/web-checkout-revenue-tracking-is-automatic.mdx", + "title": "Why is there no revenue tracking setup for web checkout?", + "description": "Explains how web checkout revenue tracking is automatic with Stripe compared to mobile platforms.", + "category": "Support", + "url": "/docs/support/web-checkout/web-checkout-revenue-tracking-is-automatic", + "date": "2026-01-30T15:11:35.000Z", + "changeType": "added" + }, + { + "key": "content/docs/android/sdk-reference/PaywallOptions.mdx:8bcb4e781628a112f65a14501b4b1fc6decca315", + "path": "android/sdk-reference/PaywallOptions.mdx", + "title": "PaywallOptions", + "description": "Removes `timeoutAfter` option from PaywallOptions SDK configuration for Android.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/PaywallOptions", + "date": "2026-01-29T19:25:18.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/ios/guides/intro-offer-eligibility-override.mdx:e0afc022a8d31aab921abfe1643ee331eeb175a1", + "path": "ios/guides/intro-offer-eligibility-override.mdx", + "title": "Overriding Introductory Offer Eligibility", + "description": "Updated guides for iOS SDK", + "category": "iOS SDK", + "subcategory": "Guides", + "url": "/docs/ios/guides/intro-offer-eligibility-override", + "date": "2026-01-23T11:36:25.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/dashboard/overview-metrics.mdx:4d42c4b03754f3ae641edfceeffdb38ef56a27d9", + "path": "dashboard/overview-metrics.mdx", + "title": "Overview", + "description": "Added Quickstart section with AI setup and updated overview page layout.", + "category": "Dashboard", + "url": "/docs/dashboard/overview-metrics", + "date": "2026-01-21T19:40:40.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/android/sdk-reference/SuperwallDelegate.mdx:356127e6b0af276b023a92a46478eaa5e431e7aa", + "path": "android/sdk-reference/SuperwallDelegate.mdx", + "title": "SuperwallDelegate", + "description": "Adds new `userAttributesDidChange` method to track changes in user attributes.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/SuperwallDelegate", + "date": "2026-01-21T13:58:08.000Z", + "changeType": "modified", + "commitHash": "356127e6b0af276b023a92a46478eaa5e431e7aa" + }, + { + "key": "content/docs/flutter/sdk-reference/PaywallOptions.mdx:356127e6b0af276b023a92a46478eaa5e431e7aa", + "path": "flutter/sdk-reference/PaywallOptions.mdx", + "title": "PaywallOptions", + "description": "Updates restore failure message description and default text for clarity.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/PaywallOptions", + "date": "2026-01-21T13:58:08.000Z", + "changeType": "modified", + "commitHash": "356127e6b0af276b023a92a46478eaa5e431e7aa" + }, + { + "key": "content/docs/expo/sdk-reference/hooks/useSuperwallEvents.mdx:356127e6b0af276b023a92a46478eaa5e431e7aa", + "path": "expo/sdk-reference/hooks/useSuperwallEvents.mdx", + "title": "useSuperwallEvents", + "description": "Adds new `onUserAttributesChange` event handler for tracking external user attribute updates.", + "category": "Expo SDK", + "subcategory": "SDK Reference", + "url": "/docs/expo/sdk-reference/hooks/useSuperwallEvents", + "date": "2026-01-21T13:58:08.000Z", + "changeType": "modified", + "commitHash": "356127e6b0af276b023a92a46478eaa5e431e7aa" + }, + { + "key": "content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-layout.mdx:356127e6b0af276b023a92a46478eaa5e431e7aa", + "path": "dashboard/dashboard-creating-paywalls/paywall-editor-layout.mdx", + "title": "Layout", + "description": "Learn keyboard shortcuts for copying and pasting components in the paywall editor.", + "category": "Dashboard", + "subcategory": "Creating Paywalls", + "url": "/docs/dashboard/dashboard-creating-paywalls/paywall-editor-layout", + "date": "2026-01-21T13:58:08.000Z", + "changeType": "modified", + "commitHash": "356127e6b0af276b023a92a46478eaa5e431e7aa" + }, + { + "key": "content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-notifications.mdx:356127e6b0af276b023a92a46478eaa5e431e7aa", + "path": "dashboard/dashboard-creating-paywalls/paywall-editor-notifications.mdx", + "title": "Notifications", + "description": "Clarifies how trial-end notifications work across different trial lengths and SDK versions.", + "category": "Dashboard", + "subcategory": "Creating Paywalls", + "url": "/docs/dashboard/dashboard-creating-paywalls/paywall-editor-notifications", + "date": "2026-01-21T13:58:08.000Z", + "changeType": "modified", + "commitHash": "356127e6b0af276b023a92a46478eaa5e431e7aa" + }, + { + "key": "content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements.mdx:356127e6b0af276b023a92a46478eaa5e431e7aa", + "path": "dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements.mdx", + "title": "Styling Elements", + "description": "Describes new `Set Attribute` tap action for capturing user preferences and tracking engagement.", + "category": "Dashboard", + "subcategory": "Creating Paywalls", + "url": "/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements", + "date": "2026-01-21T13:58:08.000Z", + "changeType": "modified", + "commitHash": "356127e6b0af276b023a92a46478eaa5e431e7aa" + }, + { + "key": "content/docs/integrations/figma-plugin.mdx:356127e6b0af276b023a92a46478eaa5e431e7aa", + "path": "integrations/figma-plugin.mdx", + "title": "Figma Plugin", + "description": "Highlights requirement of Auto Layout in Figma files for successful paywall design import.", + "category": "Integrations", + "url": "/docs/integrations/figma-plugin", + "date": "2026-01-21T13:58:08.000Z", + "changeType": "modified", + "commitHash": "356127e6b0af276b023a92a46478eaa5e431e7aa" + }, + { + "key": "content/docs/ios/guides/intro-offer-eligibility-override.mdx:0a080024cf2ff139d32d0c562960c8218c13003b", + "path": "ios/guides/intro-offer-eligibility-override.mdx", + "title": "Overriding Introductory Offer Eligibility", + "description": "Clarifies additional requirement for App Store Connect API setup for intro offer eligibility.", + "category": "iOS SDK", + "subcategory": "Guides", + "url": "/docs/ios/guides/intro-offer-eligibility-override", + "date": "2026-01-20T21:50:44.000Z", + "changeType": "modified", + "commitHash": "0a080024cf2ff139d32d0c562960c8218c13003b" + }, + { + "key": "content/docs/flutter/changelog.mdx:0650e8a5b97371e684944c86302b03e316eac961", + "path": "flutter/changelog.mdx", + "title": "Changelog", + "description": "Updates Flutter SDK changelog with version 2.4.7, highlighting iOS and Android SDK updates.", + "category": "Flutter SDK", + "url": "/docs/flutter/changelog", + "date": "2026-01-20T19:31:27.000Z", + "changeType": "modified", + "commitHash": "0650e8a5b97371e684944c86302b03e316eac961" + }, + { + "key": "content/docs/flutter/index.mdx:0650e8a5b97371e684944c86302b03e316eac961", + "path": "flutter/index.mdx", + "title": "Welcome", + "description": "Updates Superwall Flutter SDK version to 2.4.7 in version reference.", + "category": "Flutter SDK", + "url": "/docs/flutter", + "date": "2026-01-20T19:31:27.000Z", + "changeType": "modified", + "commitHash": "0650e8a5b97371e684944c86302b03e316eac961" + }, + { + "key": "content/docs/flutter/sdk-reference/NonSubscriptionTransaction.mdx:0650e8a5b97371e684944c86302b03e316eac961", + "path": "flutter/sdk-reference/NonSubscriptionTransaction.mdx", + "title": "NonSubscriptionTransaction", + "description": "Added reference for non-subscription transactions, including properties and usage examples.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/NonSubscriptionTransaction", + "date": "2026-01-20T19:31:27.000Z", + "changeType": "added", + "commitHash": "0650e8a5b97371e684944c86302b03e316eac961" + }, + { + "key": "content/docs/flutter/sdk-reference/SubscriptionTransaction.mdx:0650e8a5b97371e684944c86302b03e316eac961", + "path": "flutter/sdk-reference/SubscriptionTransaction.mdx", + "title": "SubscriptionTransaction", + "description": "Adds detailed reference for subscription transactions with new offer and store properties.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/SubscriptionTransaction", + "date": "2026-01-20T19:31:27.000Z", + "changeType": "added", + "commitHash": "0650e8a5b97371e684944c86302b03e316eac961" + }, + { + "key": "content/docs/flutter/sdk-reference/SuperwallDelegate.mdx:0650e8a5b97371e684944c86302b03e316eac961", + "path": "flutter/sdk-reference/SuperwallDelegate.mdx", + "title": "SuperwallDelegate", + "description": "Adds new `userAttributesDidChange` method to SuperwallDelegate for tracking user attribute updates", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/SuperwallDelegate", + "date": "2026-01-20T19:31:27.000Z", + "changeType": "modified", + "commitHash": "0650e8a5b97371e684944c86302b03e316eac961" + }, + { + "key": "content/docs/flutter/sdk-reference/index.mdx:0650e8a5b97371e684944c86302b03e316eac961", + "path": "flutter/sdk-reference/index.mdx", + "title": "Overview", + "description": "Updates Superwall Flutter SDK reference page to version 2.4.7.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference", + "date": "2026-01-20T19:31:27.000Z", + "changeType": "modified", + "commitHash": "0650e8a5b97371e684944c86302b03e316eac961" + }, + { + "key": "content/docs/flutter/sdk-reference/setUserAttributes.mdx:0650e8a5b97371e684944c86302b03e316eac961", + "path": "flutter/sdk-reference/setUserAttributes.mdx", + "title": "setUserAttributes()", + "description": "Clarifies `SuperwallDelegate` behavior when setting user attributes in Flutter SDK.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/setUserAttributes", + "date": "2026-01-20T19:31:27.000Z", + "changeType": "modified", + "commitHash": "0650e8a5b97371e684944c86302b03e316eac961" + }, + { + "key": "content/docs/expo/changelog.mdx:ef191593da584599bd65d16ce79c44e37cd7f726", + "path": "expo/changelog.mdx", + "title": "Changelog", + "description": "Updates Expo SDK changelog with version 1.0.1 release notes and Android back button handling.", + "category": "Expo SDK", + "url": "/docs/expo/changelog", + "date": "2026-01-17T00:50:39.000Z", + "changeType": "modified", + "commitHash": "ef191593da584599bd65d16ce79c44e37cd7f726" + }, + { + "key": "content/docs/expo/guides/configuring.mdx:ef191593da584599bd65d16ce79c44e37cd7f726", + "path": "expo/guides/configuring.mdx", + "title": "Configuring", + "description": "Adds detailed Expo-specific Superwall configuration options with example usage.", + "category": "Expo SDK", + "subcategory": "Guides", + "url": "/docs/expo/guides/configuring", + "date": "2026-01-17T00:50:39.000Z", + "changeType": "modified", + "commitHash": "ef191593da584599bd65d16ce79c44e37cd7f726" + }, + { + "key": "content/docs/expo/guides/web-checkout/post-checkout-redirecting.mdx:ef191593da584599bd65d16ce79c44e37cd7f726", + "path": "expo/guides/web-checkout/post-checkout-redirecting.mdx", + "title": "Post-Checkout Redirecting", + "description": "Adds details on accessing product data after successful checkout with new `product` object.", + "category": "Expo SDK", + "subcategory": "Guides", + "url": "/docs/expo/guides/web-checkout/post-checkout-redirecting", + "date": "2026-01-17T00:50:39.000Z", + "changeType": "modified", + "commitHash": "ef191593da584599bd65d16ce79c44e37cd7f726" + }, + { + "key": "content/docs/expo/index.mdx:ef191593da584599bd65d16ce79c44e37cd7f726", + "path": "expo/index.mdx", + "title": "Welcome", + "description": "Updates SDK version to v1.0.1 in Expo integration guide.", + "category": "Expo SDK", + "url": "/docs/expo", + "date": "2026-01-17T00:50:39.000Z", + "changeType": "modified", + "commitHash": "ef191593da584599bd65d16ce79c44e37cd7f726" + }, + { + "key": "content/docs/expo/sdk-reference/components/SuperwallProvider.mdx:ef191593da584599bd65d16ce79c44e37cd7f726", + "path": "expo/sdk-reference/components/SuperwallProvider.mdx", + "title": "SuperwallProvider", + "description": "Describes how to consume rerouted Android back buttons in SuperwallProvider with custom handling.", + "category": "Expo SDK", + "subcategory": "SDK Reference", + "url": "/docs/expo/sdk-reference/components/SuperwallProvider", + "date": "2026-01-17T00:50:39.000Z", + "changeType": "modified", + "commitHash": "ef191593da584599bd65d16ce79c44e37cd7f726" + }, + { + "key": "content/docs/expo/sdk-reference/index.mdx:ef191593da584599bd65d16ce79c44e37cd7f726", + "path": "expo/sdk-reference/index.mdx", + "title": "Overview", + "description": "Updates SDK reference to latest version v1.0.1.", + "category": "Expo SDK", + "subcategory": "SDK Reference", + "url": "/docs/expo/sdk-reference", + "date": "2026-01-17T00:50:39.000Z", + "changeType": "modified", + "commitHash": "ef191593da584599bd65d16ce79c44e37cd7f726" + }, + { + "key": "content/docs/ios/guides/advanced/request-permissions-from-paywalls.mdx:ff0cebd2c8c068dc4b6657f5235943be35b30b4c", + "path": "ios/guides/advanced/request-permissions-from-paywalls.mdx", + "title": "Request permissions from paywalls", + "description": "Adds note about upcoming **Request permission** action in paywall editor rollout.", + "category": "iOS SDK", + "subcategory": "Guides", + "url": "/docs/ios/guides/advanced/request-permissions-from-paywalls", + "date": "2026-01-16T07:30:31.000Z", + "changeType": "modified", + "commitHash": "ff0cebd2c8c068dc4b6657f5235943be35b30b4c" + }, + { + "key": "content/docs/ios/sdk-reference/SuperwallEvent.mdx:ff0cebd2c8c068dc4b6657f5235943be35b30b4c", + "path": "ios/sdk-reference/SuperwallEvent.mdx", + "title": "SuperwallEvent", + "description": "Added note about upcoming Request permission action in paywall editor for iOS SDK.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/SuperwallEvent", + "date": "2026-01-16T07:30:31.000Z", + "changeType": "modified", + "commitHash": "ff0cebd2c8c068dc4b6657f5235943be35b30b4c" + }, + { + "key": "content/docs/web-checkout/web-checkout-configuring-stripe-keys-and-settings.mdx:7eb91dc24bb1e2efb3656010e8b176b33a7d702a", + "path": "web-checkout/web-checkout-configuring-stripe-keys-and-settings.mdx", + "title": "Stripe Setup", + "description": "Provides detailed, illustrated steps for configuring Stripe keys in Superwall with live and test modes.", + "category": "Web Checkout", + "url": "/docs/web-checkout/web-checkout-configuring-stripe-keys-and-settings", + "date": "2026-01-16T01:56:14.000Z", + "changeType": "modified", + "commitHash": "7eb91dc24bb1e2efb3656010e8b176b33a7d702a" + }, + { + "key": "content/docs/ios/guides/embedded-paywalls-in-scrollviews.mdx:ec8cde22a92a78f65d4770b6a9813ca8bf89fbf8", + "path": "ios/guides/embedded-paywalls-in-scrollviews.mdx", + "title": "Article-Style Paywalls: Inline with Additional Plans", + "description": "Learn how to embed inline paywalls in scrollable articles with optional full-screen plans.", + "category": "iOS SDK", + "subcategory": "Guides", + "url": "/docs/ios/guides/embedded-paywalls-in-scrollviews", + "date": "2026-01-15T21:38:48.000Z", + "changeType": "added", + "commitHash": "ec8cde22a92a78f65d4770b6a9813ca8bf89fbf8" + }, + { + "key": "content/docs/integrations/customer-io.mdx:d4341530f075a041705df739ccbd958feb92f137", + "path": "integrations/customer-io.mdx", + "title": "Customer.io", + "description": "Introduces comprehensive guide for integrating Superwall events with Customer.io's Data Pipelines API.", + "category": "Integrations", + "url": "/docs/integrations/customer-io", + "date": "2026-01-14T17:05:48.000Z", + "changeType": "added", + "commitHash": "d4341530f075a041705df739ccbd958feb92f137" + }, + { + "key": "content/docs/integrations/discord.mdx:d4341530f075a041705df739ccbd958feb92f137", + "path": "integrations/discord.mdx", + "title": "Discord", + "description": "Adds comprehensive Discord integration guide with setup, configuration, and webhook creation steps.", + "category": "Integrations", + "url": "/docs/integrations/discord", + "date": "2026-01-14T17:05:48.000Z", + "changeType": "added", + "commitHash": "d4341530f075a041705df739ccbd958feb92f137" + }, + { + "key": "content/docs/integrations/facebook-pixel.mdx:d4341530f075a041705df739ccbd958feb92f137", + "path": "integrations/facebook-pixel.mdx", + "title": "Facebook Pixel", + "description": "Introduces Facebook Pixel integration guide with comprehensive configuration and tracking options.", + "category": "Integrations", + "url": "/docs/integrations/facebook-pixel", + "date": "2026-01-14T17:05:48.000Z", + "changeType": "added", + "commitHash": "d4341530f075a041705df739ccbd958feb92f137" + }, + { + "key": "content/docs/web-checkout/web-checkout-creating-campaigns-to-show-paywalls.mdx:788567fb5bd3dfd29ed6f7de98fa5a5600270061", + "path": "web-checkout/web-checkout-creating-campaigns-to-show-paywalls.mdx", + "title": "Web Checkout Links", + "description": "Added link to Superwall Stripe app in Stripe configuration instructions.", + "category": "Web Checkout", + "url": "/docs/web-checkout/web-checkout-creating-campaigns-to-show-paywalls", + "date": "2026-01-14T00:59:35.000Z", + "changeType": "modified", + "commitHash": "788567fb5bd3dfd29ed6f7de98fa5a5600270061" + }, + { + "key": "content/docs/web-checkout/web-checkout-direct-stripe-checkout.mdx:788567fb5bd3dfd29ed6f7de98fa5a5600270061", + "path": "web-checkout/web-checkout-direct-stripe-checkout.mdx", + "title": "App2Web", + "description": "Clarifies web checkout setup by specifying installation of Superwall Stripe app.", + "category": "Web Checkout", + "url": "/docs/web-checkout/web-checkout-direct-stripe-checkout", + "date": "2026-01-14T00:59:35.000Z", + "changeType": "modified", + "commitHash": "788567fb5bd3dfd29ed6f7de98fa5a5600270061" + }, + { + "key": "content/docs/web-checkout/web-checkout-overview.mdx:788567fb5bd3dfd29ed6f7de98fa5a5600270061", + "path": "web-checkout/web-checkout-overview.mdx", + "title": "Overview", + "description": "Added Stripe app installation guide and clarified Paddle setup steps for Web Checkout.", + "category": "Web Checkout", + "url": "/docs/web-checkout/web-checkout-overview", + "date": "2026-01-14T00:59:35.000Z", + "changeType": "modified", + "commitHash": "788567fb5bd3dfd29ed6f7de98fa5a5600270061" + }, + { + "key": "content/docs/ios/guides/testing-purchases.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/guides/testing-purchases.mdx", + "title": "Setting up StoreKit testing", + "description": "Explains how to set up local StoreKit testing environment for in-app purchases in Xcode.", + "category": "iOS SDK", + "subcategory": "Guides", + "url": "/docs/ios/guides/testing-purchases", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/NonSubscriptionTransaction.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/NonSubscriptionTransaction.mdx", + "title": "NonSubscriptionTransaction", + "description": "Replaces table with more detailed TypeTable component for NonSubscriptionTransaction properties.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/NonSubscriptionTransaction", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/PaywallOptions.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/PaywallOptions.mdx", + "title": "PaywallOptions", + "description": "Provides detailed configuration options for customizing paywall interactions and alerts in iOS SDK.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/PaywallOptions", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/PaywallPresentationHandler.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/PaywallPresentationHandler.mdx", + "title": "PaywallPresentationHandler", + "description": "Replaces table with TypeTable for PaywallPresentationHandler method descriptions and types.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/PaywallPresentationHandler", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/PurchaseController.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/PurchaseController.mdx", + "title": "PurchaseController", + "description": "Updated PurchaseController reference to use TypeTable for clearer parameter descriptions.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/PurchaseController", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/SubscriptionTransaction.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/SubscriptionTransaction.mdx", + "title": "SubscriptionTransaction", + "description": "Updated SubscriptionTransaction properties table with more detailed type and requirement information.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/SubscriptionTransaction", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/SuperwallDelegate.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/SuperwallDelegate.mdx", + "title": "SuperwallDelegate", + "description": "Updates SuperwallDelegate reference with more detailed method type and parameter information.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/SuperwallDelegate", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/SuperwallOptions.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/SuperwallOptions.mdx", + "title": "SuperwallOptions", + "description": "Updates SuperwallOptions reference with new TypeTable and clarified StoreKit, transaction check details.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/SuperwallOptions", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/advanced/getPaywall.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/advanced/getPaywall.mdx", + "title": "getPaywall()", + "description": "Improves getPaywall method reference with detailed parameter descriptions and TypeTable.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/advanced/getPaywall", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/configure.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/configure.mdx", + "title": "configure()", + "description": "Replaces markdown table with TypeTable component for clearer parameter descriptions and formatting.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/configure", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/customerInfo.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/customerInfo.mdx", + "title": "customerInfo", + "description": "Updated CustomerInfo reference with more detailed property descriptions and TypeTable.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/customerInfo", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/entitlements.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/entitlements.mdx", + "title": "entitlements", + "description": "Updates entitlements section with new TypeTable component and consistent property descriptions.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/entitlements", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/getPresentationResult.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/getPresentationResult.mdx", + "title": "getPresentationResult()", + "description": "Updated parameter table format for getPresentationResult() method using TypeTable component.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/getPresentationResult", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/handleDeepLink.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/handleDeepLink.mdx", + "title": "handleDeepLink()", + "description": "Updated handleDeepLink method documentation with improved parameter type definition and clarified return value.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/handleDeepLink", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/identify.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/identify.mdx", + "title": "identify()", + "description": "Updated parameter table format for identify() method with improved type description.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/identify", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/register.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/register.mdx", + "title": "register()", + "description": "Replaces parameter table with TypeTable for clearer, more structured register() method documentation", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/register", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/setIntegrationAttributes.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/setIntegrationAttributes.mdx", + "title": "setIntegrationAttributes", + "description": "Updates parameter table to use TypeTable component with clear attribute definition.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/setIntegrationAttributes", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/sdk-reference/setUserAttributes.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "ios/sdk-reference/setUserAttributes.mdx", + "title": "setUserAttributes()", + "description": "Updates parameter table format and clarifies setUserAttributes method details for iOS SDK.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/setUserAttributes", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/android/sdk-reference/PaywallOptions.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "android/sdk-reference/PaywallOptions.mdx", + "title": "PaywallOptions", + "description": "Updates PaywallOptions reference with new TypeTable format and enhanced parameter descriptions.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/PaywallOptions", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/android/sdk-reference/PurchaseController.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "android/sdk-reference/PurchaseController.mdx", + "title": "PurchaseController", + "description": "Updates PurchaseController reference with new TypeTable layout for clearer method parameter descriptions.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/PurchaseController", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/android/sdk-reference/SuperwallOptions.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "android/sdk-reference/SuperwallOptions.mdx", + "title": "SuperwallOptions", + "description": "Updates `SuperwallOptions` reference page with enhanced TypeTable for detailed configuration parameters.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/SuperwallOptions", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/android/sdk-reference/advanced/PaywallBuilder.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "android/sdk-reference/advanced/PaywallBuilder.mdx", + "title": "PaywallBuilder", + "description": "Replaces parameter table with TypeTable component, adding required field indicators.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/advanced/PaywallBuilder", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/android/sdk-reference/advanced/setSubscriptionStatus.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "android/sdk-reference/advanced/setSubscriptionStatus.mdx", + "title": "setSubscriptionStatus()", + "description": "Updates `setSubscriptionStatus()` parameter table to use TypeTable component for clarity.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/advanced/setSubscriptionStatus", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/android/sdk-reference/configure.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "android/sdk-reference/configure.mdx", + "title": "configure()", + "description": "Updated Android SDK configuration method with new ActivityProvider parameter and Result-based completion handler.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/configure", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/android/sdk-reference/getPresentationResult.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "android/sdk-reference/getPresentationResult.mdx", + "title": "getPresentationResult()", + "description": "Updates parameter table with new TypeTable component for getPresentationResult() method.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/getPresentationResult", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/android/sdk-reference/handleDeepLink.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "android/sdk-reference/handleDeepLink.mdx", + "title": "handleDeepLink()", + "description": "Updated parameter table format for `handleDeepLink()` method using TypeTable component.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/handleDeepLink", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/android/sdk-reference/identify.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "android/sdk-reference/identify.mdx", + "title": "identify()", + "description": "Replaces parameter table with TypeTable for improved clarity on `identify()` method parameters.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/identify", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/android/sdk-reference/register.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "android/sdk-reference/register.mdx", + "title": "register()", + "description": "Updates `register()` method reference with improved TypeTable for clearer parameter descriptions.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/register", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/android/sdk-reference/setUserAttributes.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "android/sdk-reference/setUserAttributes.mdx", + "title": "setUserAttributes()", + "description": "Updates user attributes parameter table with improved TypeTable component and description.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/setUserAttributes", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/guides/testing-purchases.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/guides/testing-purchases.mdx", + "title": "StoreKit testing (iOS only)", + "description": "Explains StoreKit testing setup in Xcode for local in-app purchase testing on iOS.", + "category": "Flutter SDK", + "subcategory": "Guides", + "url": "/docs/flutter/guides/testing-purchases", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/CustomerInfo.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/CustomerInfo.mdx", + "title": "CustomerInfo", + "description": "Improves CustomerInfo reference with enhanced table format and clarified property details.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/CustomerInfo", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/Entitlements.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/Entitlements.mdx", + "title": "Entitlements", + "description": "Describes Entitlements properties using a new TypeTable component for clearer presentation.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/Entitlements", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/IntegrationAttribute.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/IntegrationAttribute.mdx", + "title": "IntegrationAttribute", + "description": "Removes detailed table of integration attribute values from IntegrationAttribute reference page.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/IntegrationAttribute", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/PaywallPresentationHandler.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/PaywallPresentationHandler.mdx", + "title": "PaywallPresentationHandler", + "description": "Improves PaywallPresentationHandler reference with enhanced TypeTable and parameter details.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/PaywallPresentationHandler", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/PresentationResult.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/PresentationResult.mdx", + "title": "PresentationResult", + "description": "Removes detailed cases table for PresentationResult, replaced with placeholder TypeTable component.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/PresentationResult", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/PurchaseController.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/PurchaseController.mdx", + "title": "PurchaseController", + "description": "Updates PurchaseController parameter table to use TypeTable component with more structured information.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/PurchaseController", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/SuperwallOptions.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/SuperwallOptions.mdx", + "title": "SuperwallOptions", + "description": "Replaced table with detailed TypeTable for SuperwallOptions parameters with more context and defaults.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/SuperwallOptions", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/advanced/setSubscriptionStatus.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/advanced/setSubscriptionStatus.mdx", + "title": "setSubscriptionStatus()", + "description": "Updated parameter table format to improve readability and clarity for setSubscriptionStatus().", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/advanced/setSubscriptionStatus", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/configure.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/configure.mdx", + "title": "configure()", + "description": "Updates `configure()` parameter table with a new TypeTable component for improved readability.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/configure", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/consume.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/consume.mdx", + "title": "consume()", + "description": "Updates consume() parameter table to use TypeTable component for clearer presentation.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/consume", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/getPresentationResult.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/getPresentationResult.mdx", + "title": "getPresentationResult()", + "description": "Updates parameter table to use TypeTable component with clearer, more structured parameter descriptions.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/getPresentationResult", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/handleDeepLink.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/handleDeepLink.mdx", + "title": "handleDeepLink()", + "description": "Updates `handleDeepLink()` reference to use TypeTable for clearer parameter documentation.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/handleDeepLink", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/identify.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/identify.mdx", + "title": "identify()", + "description": "Updates identify() method reference with improved TypeTable component for parameter details.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/identify", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/overrideProductsByName.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/overrideProductsByName.mdx", + "title": "overrideProductsByName", + "description": "Updates reference page to use TypeTable component for parameter description.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/overrideProductsByName", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/register.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/register.mdx", + "title": "registerPlacement()", + "description": "Replaces parameter table with more detailed TypeTable for registerPlacement() function parameters.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/register", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/setIntegrationAttribute.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/setIntegrationAttribute.mdx", + "title": "setIntegrationAttribute()", + "description": "Updated parameter table format with improved type definition and clarity.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/setIntegrationAttribute", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/flutter/sdk-reference/setIntegrationAttributes.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "flutter/sdk-reference/setIntegrationAttributes.mdx", + "title": "setIntegrationAttributes()", + "description": "Updates parameter table to use TypeTable component for clearer attribute documentation.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/setIntegrationAttributes", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/expo/guides/testing-purchases.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "expo/guides/testing-purchases.mdx", + "title": "StoreKit testing (iOS only)", + "description": "Provides detailed steps for StoreKit testing in Expo with development builds on iOS.", + "category": "Expo SDK", + "subcategory": "Guides", + "url": "/docs/expo/guides/testing-purchases", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/expo/sdk-reference/components/CustomPurchaseControllerProvider.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "expo/sdk-reference/components/CustomPurchaseControllerProvider.mdx", + "title": "CustomPurchaseControllerProvider", + "description": "Clarifies how to handle purchase and restore outcomes in the CustomPurchaseControllerProvider.", + "category": "Expo SDK", + "subcategory": "SDK Reference", + "url": "/docs/expo/sdk-reference/components/CustomPurchaseControllerProvider", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/expo/sdk-reference/components/SuperwallError.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "expo/sdk-reference/components/SuperwallError.mdx", + "title": "SuperwallError", + "description": "Updates SuperwallError component props section with improved type and description clarity.", + "category": "Expo SDK", + "subcategory": "SDK Reference", + "url": "/docs/expo/sdk-reference/components/SuperwallError", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/expo/sdk-reference/components/SuperwallLoaded.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "expo/sdk-reference/components/SuperwallLoaded.mdx", + "title": "SuperwallLoaded", + "description": "Adds TypeTable to describe the `children` prop with type and requirement details.", + "category": "Expo SDK", + "subcategory": "SDK Reference", + "url": "/docs/expo/sdk-reference/components/SuperwallLoaded", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/expo/sdk-reference/components/SuperwallLoading.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "expo/sdk-reference/components/SuperwallLoading.mdx", + "title": "SuperwallLoading", + "description": "Adds prop details for SuperwallLoading component, explaining required children prop.", + "category": "Expo SDK", + "subcategory": "SDK Reference", + "url": "/docs/expo/sdk-reference/components/SuperwallLoading", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/expo/sdk-reference/getPresentationResult.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "expo/sdk-reference/getPresentationResult.mdx", + "title": "getPresentationResult()", + "description": "Updates `getPresentationResult()` reference with new `TypeTable` component and detailed result types.", + "category": "Expo SDK", + "subcategory": "SDK Reference", + "url": "/docs/expo/sdk-reference/getPresentationResult", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/expo/sdk-reference/hooks/usePlacement.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "expo/sdk-reference/hooks/usePlacement.mdx", + "title": "usePlacement", + "description": "Improves usePlacement hook documentation with new TypeTable component and detailed state descriptions", + "category": "Expo SDK", + "subcategory": "SDK Reference", + "url": "/docs/expo/sdk-reference/hooks/usePlacement", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/expo/sdk-reference/hooks/useSuperwall.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "expo/sdk-reference/hooks/useSuperwall.mdx", + "title": "useSuperwall", + "description": "Updated `useSuperwall` hook reference with improved type details and configuration options.", + "category": "Expo SDK", + "subcategory": "SDK Reference", + "url": "/docs/expo/sdk-reference/hooks/useSuperwall", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/expo/sdk-reference/hooks/useUser.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "expo/sdk-reference/hooks/useUser.mdx", + "title": "useUser", + "description": "Adds detailed type definitions and descriptions for all `useUser` hook returned values.", + "category": "Expo SDK", + "subcategory": "SDK Reference", + "url": "/docs/expo/sdk-reference/hooks/useUser", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/react-native/sdk-reference/PaywallOptions.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "react-native/sdk-reference/PaywallOptions.mdx", + "title": "PaywallOptions", + "description": "Updates PaywallOptions reference with more detailed type descriptions and new TypeTable component.", + "category": "React Native SDK", + "subcategory": "SDK Reference", + "url": "/docs/react-native/sdk-reference/PaywallOptions", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/react-native/sdk-reference/PaywallPresentationHandler.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "react-native/sdk-reference/PaywallPresentationHandler.mdx", + "title": "PaywallPresentationHandler", + "description": "Updates SDK reference table to TypeTable for PaywallPresentationHandler method details.", + "category": "React Native SDK", + "subcategory": "SDK Reference", + "url": "/docs/react-native/sdk-reference/PaywallPresentationHandler", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/react-native/sdk-reference/PurchaseController.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "react-native/sdk-reference/PurchaseController.mdx", + "title": "PurchaseController", + "description": "Updated PurchaseController reference with improved method type definitions and table layout.", + "category": "React Native SDK", + "subcategory": "SDK Reference", + "url": "/docs/react-native/sdk-reference/PurchaseController", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/react-native/sdk-reference/SuperwallDelegate.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "react-native/sdk-reference/SuperwallDelegate.mdx", + "title": "SuperwallDelegate", + "description": "Updated SuperwallDelegate reference with detailed TypeScript method signatures and descriptions.", + "category": "React Native SDK", + "subcategory": "SDK Reference", + "url": "/docs/react-native/sdk-reference/SuperwallDelegate", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/react-native/sdk-reference/SuperwallOptions.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "react-native/sdk-reference/SuperwallOptions.mdx", + "title": "SuperwallOptions", + "description": "Replaces parameter table with more detailed TypeTable for SuperwallOptions properties.", + "category": "React Native SDK", + "subcategory": "SDK Reference", + "url": "/docs/react-native/sdk-reference/SuperwallOptions", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/react-native/sdk-reference/configure.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "react-native/sdk-reference/configure.mdx", + "title": "configure()", + "description": "Updates configure() method reference with improved TypeTable component and parameter descriptions.", + "category": "React Native SDK", + "subcategory": "SDK Reference", + "url": "/docs/react-native/sdk-reference/configure", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/react-native/sdk-reference/handleDeepLink.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "react-native/sdk-reference/handleDeepLink.mdx", + "title": "handleDeepLink()", + "description": "Updates parameter table style and confirms `url` parameter is required for `handleDeepLink()`.", + "category": "React Native SDK", + "subcategory": "SDK Reference", + "url": "/docs/react-native/sdk-reference/handleDeepLink", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/react-native/sdk-reference/identify.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "react-native/sdk-reference/identify.mdx", + "title": "identify()", + "description": "Updates identify() method reference with improved TypeTable component for clearer parameter descriptions.", + "category": "React Native SDK", + "subcategory": "SDK Reference", + "url": "/docs/react-native/sdk-reference/identify", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/react-native/sdk-reference/register.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "react-native/sdk-reference/register.mdx", + "title": "register()", + "description": "Updates register() parameter reference to use TypeTable for clearer, more structured parameter descriptions.", + "category": "React Native SDK", + "subcategory": "SDK Reference", + "url": "/docs/react-native/sdk-reference/register", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/react-native/sdk-reference/setUserAttributes.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "react-native/sdk-reference/setUserAttributes.mdx", + "title": "setUserAttributes()", + "description": "Updates parameter table formatting for setUserAttributes() method reference.", + "category": "React Native SDK", + "subcategory": "SDK Reference", + "url": "/docs/react-native/sdk-reference/setUserAttributes", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/react-native/sdk-reference/subscriptionStatus.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "react-native/sdk-reference/subscriptionStatus.mdx", + "title": "Subscription Status", + "description": "Updates component reference for `setSubscriptionStatus` method using new TypeTable component.", + "category": "React Native SDK", + "subcategory": "SDK Reference", + "url": "/docs/react-native/sdk-reference/subscriptionStatus", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/dashboard/dashboard-campaigns/campaigns-standard-placements.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "dashboard/dashboard-campaigns/campaigns-standard-placements.mdx", + "title": "Standard Placements", + "description": "Expands Standard Placements documentation with detailed event descriptions, parameters, and usage examples.", + "category": "Dashboard", + "subcategory": "Campaigns", + "url": "/docs/dashboard/dashboard-campaigns/campaigns-standard-placements", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/support/troubleshooting/4780985851-troubleshooting-expo-sdk.mdx:43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1", + "path": "support/troubleshooting/4780985851-troubleshooting-expo-sdk.mdx", + "title": "Troubleshooting: Expo SDK", + "description": "Adds troubleshooting guide for Android paywall orientation with Expo SDK config plugin.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/4780985851-troubleshooting-expo-sdk", + "date": "2026-01-08T01:23:03.000Z", + "changeType": "modified", + "commitHash": "43e81a83ff2053e3aa39db3e2057c3ef0a2aa2c1" + }, + { + "key": "content/docs/ios/changelog.mdx:7884217842b337a88548b9aeba532fbdfc5976bb", + "path": "ios/changelog.mdx", + "title": "Changelog", + "description": "Adds preload events, permission request actions, and improves drawer presentation in iOS SDK.", + "category": "iOS SDK", + "url": "/docs/ios/changelog", + "date": "2026-01-07T20:49:41.000Z", + "changeType": "modified", + "commitHash": "7884217842b337a88548b9aeba532fbdfc5976bb" + }, + { + "key": "content/docs/ios/index.mdx:7884217842b337a88548b9aeba532fbdfc5976bb", + "path": "ios/index.mdx", + "title": "Welcome", + "description": "Updates SDK version reference to latest 4.12.0 release.", + "category": "iOS SDK", + "url": "/docs/ios", + "date": "2026-01-07T20:49:41.000Z", + "changeType": "modified", + "commitHash": "7884217842b337a88548b9aeba532fbdfc5976bb" + }, + { + "key": "content/docs/ios/sdk-reference/index.mdx:7884217842b337a88548b9aeba532fbdfc5976bb", + "path": "ios/sdk-reference/index.mdx", + "title": "Overview", + "description": "Updates SDK reference page with latest Superwall iOS SDK version 4.12.0.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference", + "date": "2026-01-07T20:49:41.000Z", + "changeType": "modified", + "commitHash": "7884217842b337a88548b9aeba532fbdfc5976bb" + }, + { + "key": "content/docs/community/capacitor.mdx:3e3e740d3a696d529bb2e14cc70bc86f36a31b5a", + "path": "community/capacitor.mdx", + "title": "Capacitor", + "description": "Adds Capacitor SDK integration guide with installation, configuration, and event handling steps", + "category": "Community", + "url": "/docs/community/capacitor", + "date": "2026-01-07T15:10:07.000Z", + "changeType": "added", + "commitHash": "3e3e740d3a696d529bb2e14cc70bc86f36a31b5a" + }, + { + "key": "content/docs/community/index.mdx:3e3e740d3a696d529bb2e14cc70bc86f36a31b5a", + "path": "community/index.mdx", + "title": "Community SDKs", + "description": "Introduces community-maintained Superwall SDKs, starting with Capacitor plugin support.", + "category": "Community", + "url": "/docs/community", + "date": "2026-01-07T15:10:07.000Z", + "changeType": "added", + "commitHash": "3e3e740d3a696d529bb2e14cc70bc86f36a31b5a" + }, + { + "key": "content/docs/ios/vibe-coding-guide.mdx:b93ae29fd12adc2c0d8cfcea97516bded272bc9f", + "path": "ios/vibe-coding-guide.mdx", + "title": "Superwall iOS Vibe Coding Guide", + "description": "Adds comprehensive iOS SDK integration guide covering installation, configuration, and features.", + "category": "iOS SDK", + "url": "/docs/ios/vibe-coding-guide", + "date": "2025-12-30T22:22:38.000Z", + "changeType": "added", + "commitHash": "b93ae29fd12adc2c0d8cfcea97516bded272bc9f" + }, + { + "key": "content/docs/android/vibe-coding-guide.mdx:b93ae29fd12adc2c0d8cfcea97516bded272bc9f", + "path": "android/vibe-coding-guide.mdx", + "title": "Superwall Android Vibe Coding Guide", + "description": "Added comprehensive Android SDK guide covering installation, configuration, and user management.", + "category": "Android SDK", + "url": "/docs/android/vibe-coding-guide", + "date": "2025-12-30T22:22:38.000Z", + "changeType": "added", + "commitHash": "b93ae29fd12adc2c0d8cfcea97516bded272bc9f" + }, + { + "key": "content/docs/flutter/vibe-coding-guide.mdx:b93ae29fd12adc2c0d8cfcea97516bded272bc9f", + "path": "flutter/vibe-coding-guide.mdx", + "title": "Superwall Flutter Vibe Coding Guide", + "description": "Adds comprehensive Flutter SDK guide covering installation, configuration, and best practices", + "category": "Flutter SDK", + "url": "/docs/flutter/vibe-coding-guide", + "date": "2025-12-30T22:22:38.000Z", + "changeType": "added", + "commitHash": "b93ae29fd12adc2c0d8cfcea97516bded272bc9f" + }, + { + "key": "content/docs/expo/vibe-coding-guide.mdx:b93ae29fd12adc2c0d8cfcea97516bded272bc9f", + "path": "expo/vibe-coding-guide.mdx", + "title": "Superwall Expo Vibe Coding Guide", + "description": "Adds comprehensive Expo Vibe SDK implementation guide with step-by-step instructions.", + "category": "Expo SDK", + "url": "/docs/expo/vibe-coding-guide", + "date": "2025-12-30T22:22:38.000Z", + "changeType": "added", + "commitHash": "b93ae29fd12adc2c0d8cfcea97516bded272bc9f" + }, + { + "key": "content/docs/react-native/vibe-coding-guide.mdx:b93ae29fd12adc2c0d8cfcea97516bded272bc9f", + "path": "react-native/vibe-coding-guide.mdx", + "title": "Superwall React Native Vibe Coding Guide", + "description": "Provides comprehensive guide for setting up Superwall SDK in React Native applications.", + "category": "React Native SDK", + "url": "/docs/react-native/vibe-coding-guide", + "date": "2025-12-30T22:22:38.000Z", + "changeType": "added", + "commitHash": "b93ae29fd12adc2c0d8cfcea97516bded272bc9f" + }, + { + "key": "content/docs/ios/guides/superwall-deep-links.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "ios/guides/superwall-deep-links.mdx", + "title": "Using Superwall Deep Links", + "description": "Added guide for using Superwall Deep Links to trigger paywalls and custom in-app behavior.", + "category": "iOS SDK", + "subcategory": "Guides", + "url": "/docs/ios/guides/superwall-deep-links", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "added", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/ios/sdk-reference/Superwall.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "ios/sdk-reference/Superwall.mdx", + "title": "Superwall", + "description": "Adds guidance on when and how to reset user state with `Superwall.shared.reset()`", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/Superwall", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "modified", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/android/changelog.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "android/changelog.mdx", + "title": "Changelog", + "description": "Adds detailed changelog for Superwall Android SDK, covering releases from 2.5.7 to 2.6.6.", + "category": "Android SDK", + "url": "/docs/android/changelog", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "added", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/android/sdk-reference/Superwall.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "android/sdk-reference/Superwall.mdx", + "title": "Superwall", + "description": "Adds guidance on resetting user state and when to use the reset method in the SDK.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference/Superwall", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "modified", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/flutter/guides/superwall-deep-links.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "flutter/guides/superwall-deep-links.mdx", + "title": "Using Superwall Deep Links", + "description": "Added guide for using Superwall Deep Links to trigger paywalls or custom behavior.", + "category": "Flutter SDK", + "subcategory": "Guides", + "url": "/docs/flutter/guides/superwall-deep-links", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "added", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/flutter/sdk-reference/Superwall.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "flutter/sdk-reference/Superwall.mdx", + "title": "Superwall.shared", + "description": "Added guidance on resetting user state and best practices for SDK user management.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/Superwall", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "modified", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/expo/guides/setting-locale.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "expo/guides/setting-locale.mdx", + "title": "Setting a Locale", + "description": "Explains how to override device locale for previewing localized paywalls and testing targeting rules.", + "category": "Expo SDK", + "subcategory": "Guides", + "url": "/docs/expo/guides/setting-locale", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "added", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/expo/quickstart/in-app-paywall-previews.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "expo/quickstart/in-app-paywall-previews.mdx", + "title": "Handling Deep Links", + "description": "Updates page title to clarify focus on handling deep links in Expo.", + "category": "Expo SDK", + "subcategory": "Quickstart", + "url": "/docs/expo/quickstart/in-app-paywall-previews", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "modified", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/react-native/changelog.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "react-native/changelog.mdx", + "title": "Changelog", + "description": "Introduces changelog tracking release notes for Superwall React Native SDK versions.", + "category": "React Native SDK", + "url": "/docs/react-native/changelog", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "added", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/react-native/sdk-reference/Superwall.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "react-native/sdk-reference/Superwall.mdx", + "title": "Superwall", + "description": "Added method to reset user state with guidance on when to use `Superwall.shared.reset()`.", + "category": "React Native SDK", + "subcategory": "SDK Reference", + "url": "/docs/react-native/sdk-reference/Superwall", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "modified", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/dashboard/dashboard-campaigns/campaigns-placements.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "dashboard/dashboard-campaigns/campaigns-placements.mdx", + "title": "Placements", + "description": "Added explanation of placement parameters and their uses in campaigns and paywalls.", + "category": "Dashboard", + "subcategory": "Campaigns", + "url": "/docs/dashboard/dashboard-campaigns/campaigns-placements", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "modified", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/dashboard/dashboard-settings/overview-settings-revenue-tracking-google-play.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "dashboard/dashboard-settings/overview-settings-revenue-tracking-google-play.mdx", + "title": "Google Play Revenue Tracking", + "description": "Added troubleshooting step for missing topic name field in Google Play Console setup.", + "category": "Dashboard", + "subcategory": "Settings", + "url": "/docs/dashboard/dashboard-settings/overview-settings-revenue-tracking-google-play", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "modified", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/dashboard/products.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "dashboard/products.mdx", + "title": "Adding Products", + "description": "Updates headings and clarifies Google Play offer selection and tagging process for developers.", + "category": "Dashboard", + "url": "/docs/dashboard/products", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "modified", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/web-checkout/web-checkout-faq.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "web-checkout/web-checkout-faq.mdx", + "title": "Web Checkout FAQ", + "description": "Explains Superwall's default email behavior after web checkout and how to disable it", + "category": "Web Checkout", + "url": "/docs/web-checkout/web-checkout-faq", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "modified", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/web-checkout/web-checkout-managing-memberships.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "web-checkout/web-checkout-managing-memberships.mdx", + "title": "Restoring & Managing Purchases", + "description": "Added details on default activation email process and how to disable Superwall emails.", + "category": "Web Checkout", + "url": "/docs/web-checkout/web-checkout-managing-memberships", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "modified", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/support/web-checkout/3969573187-how-do-i-disable-the-activation-link-email-for-web-checkout.mdx:4db2e26374a73d60e17cb3598f2bb13dd779a7f3", + "path": "support/web-checkout/3969573187-how-do-i-disable-the-activation-link-email-for-web-checkout.mdx", + "title": "How do I disable the activation link email for web checkout?", + "description": "Describes default activation email details for web checkout, including sender and subject.", + "category": "Support", + "url": "/docs/support/web-checkout/3969573187-how-do-i-disable-the-activation-link-email-for-web-checkout", + "date": "2025-12-23T00:12:28.000Z", + "changeType": "modified", + "commitHash": "4db2e26374a73d60e17cb3598f2bb13dd779a7f3" + }, + { + "key": "content/docs/ios/sdk-reference/getDeviceAttributes.mdx:37a09e99365e6020a181fc95fee8fd624c15e4c6", + "path": "ios/sdk-reference/getDeviceAttributes.mdx", + "title": "getDeviceAttributes", + "description": "Deprecates `isApplePayAvailable` attribute, which now always returns `true`", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/getDeviceAttributes", + "date": "2025-12-22T23:56:58.000Z", + "changeType": "modified", + "commitHash": "37a09e99365e6020a181fc95fee8fd624c15e4c6" + }, + { + "key": "content/docs/ios/sdk-reference/refreshConfiguration.mdx:37a09e99365e6020a181fc95fee8fd624c15e4c6", + "path": "ios/sdk-reference/refreshConfiguration.mdx", + "title": "refreshConfiguration", + "description": "Explains how to manually refresh Superwall configuration during development workflows.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/refreshConfiguration", + "date": "2025-12-22T23:56:58.000Z", + "changeType": "added", + "commitHash": "37a09e99365e6020a181fc95fee8fd624c15e4c6" + }, + { + "key": "content/docs/support/faq/can-i-pre-authenticate-users-on-subscription-management-page.mdx:8037ca30bd203ba62a8a4ff9f2db804585a36c9d", + "path": "support/faq/can-i-pre-authenticate-users-on-subscription-management-page.mdx", + "title": "Can I pre-authenticate users on the subscription management page?", + "description": "Added FAQ explaining how to pre-fill email for subscription management page.", + "category": "Support", + "subcategory": "FAQ", + "url": "/docs/support/faq/can-i-pre-authenticate-users-on-subscription-management-page", + "date": "2025-12-22T07:14:01.000Z", + "changeType": "added", + "commitHash": "8037ca30bd203ba62a8a4ff9f2db804585a36c9d" + }, + { + "key": "content/docs/support/faq/how-do-i-migrate-my-existing-purchases-to-revenuecat.mdx:8037ca30bd203ba62a8a4ff9f2db804585a36c9d", + "path": "support/faq/how-do-i-migrate-my-existing-purchases-to-revenuecat.mdx", + "title": "How do I migrate my existing purchases to RevenueCat?", + "description": "Added guide for migrating existing purchases to RevenueCat with Superwall support.", + "category": "Support", + "subcategory": "FAQ", + "url": "/docs/support/faq/how-do-i-migrate-my-existing-purchases-to-revenuecat", + "date": "2025-12-22T07:14:01.000Z", + "changeType": "added", + "commitHash": "8037ca30bd203ba62a8a4ff9f2db804585a36c9d" + }, + { + "key": "content/docs/support/faq/why-is-my-android-app-missing-historical-revenue-data.mdx:8037ca30bd203ba62a8a4ff9f2db804585a36c9d", + "path": "support/faq/why-is-my-android-app-missing-historical-revenue-data.mdx", + "title": "Why is my Android app missing historical revenue data after setting up the Google Play integration?", + "description": "Explains why Android apps lack historical revenue data when integrating Google Play", + "category": "Support", + "subcategory": "FAQ", + "url": "/docs/support/faq/why-is-my-android-app-missing-historical-revenue-data", + "date": "2025-12-22T07:14:01.000Z", + "changeType": "added", + "commitHash": "8037ca30bd203ba62a8a4ff9f2db804585a36c9d" + }, + { + "key": "content/docs/support/faq/why-is-my-transaction-failure-rate-high.mdx:8037ca30bd203ba62a8a4ff9f2db804585a36c9d", + "path": "support/faq/why-is-my-transaction-failure-rate-high.mdx", + "title": "Why is my transaction failure rate high?", + "description": "Explains common causes and strategies for reducing transaction failure rates in mobile apps.", + "category": "Support", + "subcategory": "FAQ", + "url": "/docs/support/faq/why-is-my-transaction-failure-rate-high", + "date": "2025-12-22T07:14:01.000Z", + "changeType": "added", + "commitHash": "8037ca30bd203ba62a8a4ff9f2db804585a36c9d" + }, + { + "key": "content/docs/support/troubleshooting/troubleshooting-pending-trials-not-converting.mdx:8037ca30bd203ba62a8a4ff9f2db804585a36c9d", + "path": "support/troubleshooting/troubleshooting-pending-trials-not-converting.mdx", + "title": "Why are my trials still showing as pending after they should have converted?", + "description": "Explains why trials can remain pending and how Apple's billing retry process works", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/troubleshooting-pending-trials-not-converting", + "date": "2025-12-22T07:14:01.000Z", + "changeType": "added", + "commitHash": "8037ca30bd203ba62a8a4ff9f2db804585a36c9d" + }, + { + "key": "content/docs/support/faq/how-to-migrate-from-another-provider-to-superwall.mdx:266dc32ca744ce6537c03063e9ee223bd8a0db90", + "path": "support/faq/how-to-migrate-from-another-provider-to-superwall.mdx", + "title": "How do I migrate to Superwall from another provider?", + "description": "Provides comprehensive guide for migrating subscriptions from other providers to Superwall", + "category": "Support", + "subcategory": "FAQ", + "url": "/docs/support/faq/how-to-migrate-from-another-provider-to-superwall", + "date": "2025-12-22T07:13:40.000Z", + "changeType": "added", + "commitHash": "266dc32ca744ce6537c03063e9ee223bd8a0db90" + }, + { + "key": "content/docs/support/troubleshooting/subscription-status-active-but-no-dashboard-data.mdx:266dc32ca744ce6537c03063e9ee223bd8a0db90", + "path": "support/troubleshooting/subscription-status-active-but-no-dashboard-data.mdx", + "title": "Why does my user show active subscription but no data in the dashboard?", + "description": "Explains why an active subscription might not show data in the Superwall dashboard.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/subscription-status-active-but-no-dashboard-data", + "date": "2025-12-22T07:13:40.000Z", + "changeType": "added", + "commitHash": "266dc32ca744ce6537c03063e9ee223bd8a0db90" + }, + { + "key": "content/docs/support/troubleshooting/why-is-my-paywall-not-updating-after-publishing.mdx:266dc32ca744ce6537c03063e9ee223bd8a0db90", + "path": "support/troubleshooting/why-is-my-paywall-not-updating-after-publishing.mdx", + "title": "Why is my paywall not updating after publishing?", + "description": "Added troubleshooting guide for resolving paywall update and visibility issues in Superwall.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/why-is-my-paywall-not-updating-after-publishing", + "date": "2025-12-22T07:13:40.000Z", + "changeType": "added", + "commitHash": "266dc32ca744ce6537c03063e9ee223bd8a0db90" + }, + { + "key": "content/docs/flutter/quickstart/tracking-subscription-state.mdx:169a9f4c93439c829321ab65c70dfeb9efe209be", + "path": "flutter/quickstart/tracking-subscription-state.mdx", + "title": "Tracking Subscription State", + "description": "Adds example of using `isActive` property to check subscription status more concisely.", + "category": "Flutter SDK", + "subcategory": "Quickstart", + "url": "/docs/flutter/quickstart/tracking-subscription-state", + "date": "2025-12-15T20:10:04.000Z", + "changeType": "modified", + "commitHash": "169a9f4c93439c829321ab65c70dfeb9efe209be" + }, + { + "key": "content/docs/flutter/sdk-reference/subscriptionStatus.mdx:169a9f4c93439c829321ab65c70dfeb9efe209be", + "path": "flutter/sdk-reference/subscriptionStatus.mdx", + "title": "subscriptionStatus", + "description": "Adds example for checking subscription status and using the `isActive` convenience property.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/subscriptionStatus", + "date": "2025-12-15T20:10:04.000Z", + "changeType": "modified", + "commitHash": "169a9f4c93439c829321ab65c70dfeb9efe209be" + }, + { + "key": "content/docs/integrations/webhooks/index.mdx:308978f6e42298a54910eea379f94aa9b6e813f2", + "path": "integrations/webhooks/index.mdx", + "title": "Webhooks", + "description": "Added details on including custom user attributes in webhook event payloads.", + "category": "Integrations", + "url": "/docs/integrations/webhooks", + "date": "2025-12-12T13:50:22.000Z", + "changeType": "modified", + "commitHash": "308978f6e42298a54910eea379f94aa9b6e813f2" + }, + { + "key": "content/docs/android/quickstart/install.mdx:6cfdb59fabd5aa8ab11fe4bfdd012f1f04fc77bb", + "path": "android/quickstart/install.mdx", + "title": "Install the SDK", + "description": "Updates Android SDK installation guide with correct Paywall Activity class name.", + "category": "Android SDK", + "subcategory": "Quickstart", + "url": "/docs/android/quickstart/install", + "date": "2025-12-11T19:11:52.000Z", + "changeType": "modified", + "commitHash": "6cfdb59fabd5aa8ab11fe4bfdd012f1f04fc77bb" + }, + { + "key": "content/docs/support/paywall-editor/6770999766-paywall-editor-menus-or-modals-closing.mdx:f1390dc4b90fa36947583769ecfa7ee1f112d62c", + "path": "support/paywall-editor/6770999766-paywall-editor-menus-or-modals-closing.mdx", + "title": "Paywall Editor: Menus or Modals Closing", + "description": "Troubleshooting guide for paywall editor menus or modals closing unexpectedly in web browsers.", + "category": "Support", + "url": "/docs/support/paywall-editor/6770999766-paywall-editor-menus-or-modals-closing", + "date": "2025-12-11T05:31:08.000Z", + "changeType": "added", + "commitHash": "f1390dc4b90fa36947583769ecfa7ee1f112d62c" + }, + { + "key": "content/docs/web-checkout/web-checkout-web-only.mdx:bfe2bf9bf4b39275f2e7d4c835731a4500f1ee61", + "path": "web-checkout/web-checkout-web-only.mdx", + "title": "Web-Only Checkout", + "description": "Clarifies Stripe integration steps and updates web checkout link generation details.", + "category": "Web Checkout", + "url": "/docs/web-checkout/web-checkout-web-only", + "date": "2025-12-10T14:20:49.000Z", + "changeType": "modified", + "commitHash": "bfe2bf9bf4b39275f2e7d4c835731a4500f1ee61" + }, + { + "key": "content/docs/support/dashboard/2125187334-how-do-i-change-my-login-email-address.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/2125187334-how-do-i-change-my-login-email-address.mdx", + "title": "How do I change my login email address?", + "description": "Added instructions for changing login email address by creating a new account", + "category": "Support", + "url": "/docs/support/dashboard/2125187334-how-do-i-change-my-login-email-address", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/2132346026-how-do-i-update-my-apple-small-business-program-status-in-superwall.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/2132346026-how-do-i-update-my-apple-small-business-program-status-in-superwall.mdx", + "title": "How do I update my Apple Small Business Program status in Superwall?", + "description": "Explains how to update Apple Small Business Program status in Superwall dashboard.", + "category": "Support", + "url": "/docs/support/dashboard/2132346026-how-do-i-update-my-apple-small-business-program-status-in-superwall", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/2172810792-why-don-t-trial-conversion-and-cancellation-rates-add-up-to-100.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/2172810792-why-don-t-trial-conversion-and-cancellation-rates-add-up-to-100.mdx", + "title": "Why don't trial conversion and cancellation rates add up to 100%?", + "description": "Explains why trial conversion and cancellation rates don't always sum to 100%.", + "category": "Support", + "url": "/docs/support/dashboard/2172810792-why-don-t-trial-conversion-and-cancellation-rates-add-up-to-100", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/2563776677-revenue-charts-tour.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/2563776677-revenue-charts-tour.mdx", + "title": "Video: Revenue Charts Tour", + "description": "Added video tutorial explaining how to use free revenue charts for tracking app proceeds.", + "category": "Support", + "url": "/docs/support/dashboard/2563776677-revenue-charts-tour", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/3633020467-breaking-down-revenue-charts-by-user-attributes.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/3633020467-breaking-down-revenue-charts-by-user-attributes.mdx", + "title": "Video: Breaking Down Revenue Charts by User Attributes", + "description": "Learn how to break down revenue data by user attributes in Superwall with this video tutorial.", + "category": "Support", + "url": "/docs/support/dashboard/3633020467-breaking-down-revenue-charts-by-user-attributes", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/4129730749-how-is-the-estimated-arpu-metric-calculated-in-superwall.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/4129730749-how-is-the-estimated-arpu-metric-calculated-in-superwall.mdx", + "title": "How is the Estimated ARPU metric calculated in Superwall?", + "description": "Explains how Superwall calculates Estimated ARPU metric using trial proceeds and user count.", + "category": "Support", + "url": "/docs/support/dashboard/4129730749-how-is-the-estimated-arpu-metric-calculated-in-superwall", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/5346543318-how-do-i-find-my-superwall-server-secret.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/5346543318-how-do-i-find-my-superwall-server-secret.mdx", + "title": "How do I find my Superwall Server Secret?", + "description": "Provides step-by-step instructions for finding Superwall Server Secret for RevenueCat integration.", + "category": "Support", + "url": "/docs/support/dashboard/5346543318-how-do-i-find-my-superwall-server-secret", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/5483562984-how-to-optimize-refund-protection-settings.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/5483562984-how-to-optimize-refund-protection-settings.mdx", + "title": "How to Optimize Refund Protection Settings", + "description": "Learn how to optimize refund protection settings to prevent unauthorized app refunds.", + "category": "Support", + "url": "/docs/support/dashboard/5483562984-how-to-optimize-refund-protection-settings", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/8137029255-configuring-products-to-use-in-superwall.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/8137029255-configuring-products-to-use-in-superwall.mdx", + "title": "Video: Configuring Products to use in Superwall", + "description": "Provides video walkthrough for configuring products in Superwall using App Store Connect or Revenue Cat.", + "category": "Support", + "url": "/docs/support/dashboard/8137029255-configuring-products-to-use-in-superwall", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/8360602963-how-to-show-different-paywalls-to-certain-users-with-audiences.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/8360602963-how-to-show-different-paywalls-to-certain-users-with-audiences.mdx", + "title": "Video: How to Show Different Paywalls to Certain Users with Audiences", + "description": "Learn how to show different paywalls to users based on audiences in Superwall.", + "category": "Support", + "url": "/docs/support/dashboard/8360602963-how-to-show-different-paywalls-to-certain-users-with-audiences", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/8851145816-get-started-with-apple-search-ads.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/8851145816-get-started-with-apple-search-ads.mdx", + "title": "Video: Get Started with Apple Search Ads", + "description": "Added video tutorial and guide for integrating Apple Search Ads with Superwall analytics.", + "category": "Support", + "url": "/docs/support/dashboard/8851145816-get-started-with-apple-search-ads", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/9023151337-how-do-i-add-my-vat-number-to-invoices.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/9023151337-how-do-i-add-my-vat-number-to-invoices.mdx", + "title": "How do I add my VAT number to invoices?", + "description": "Added instructions for adding VAT number to invoices in Billing Settings.", + "category": "Support", + "url": "/docs/support/dashboard/9023151337-how-do-i-add-my-vat-number-to-invoices", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/9149611399-can-i-recover-an-archived-product-in-superwall.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/9149611399-can-i-recover-an-archived-product-in-superwall.mdx", + "title": "How can I recover an archived product in Superwall?", + "description": "Learn how to recreate an archived product in Superwall when direct unarchiving is unavailable.", + "category": "Support", + "url": "/docs/support/dashboard/9149611399-can-i-recover-an-archived-product-in-superwall", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/9274386153-what-is-the-difference-between-trial-cancel-and-trial-expire-tags.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/9274386153-what-is-the-difference-between-trial-cancel-and-trial-expire-tags.mdx", + "title": "What is the difference between \"Trial Cancel\" and \"Trial Expire\" tags?", + "description": "Clarifies the difference between \"Trial Cancel\" and \"Trial Expire\" tags in user subscriptions.", + "category": "Support", + "url": "/docs/support/dashboard/9274386153-what-is-the-difference-between-trial-cancel-and-trial-expire-tags", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/9588935743-a-superwall-product-tour.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/9588935743-a-superwall-product-tour.mdx", + "title": "A Superwall product tour", + "description": "Introduces a video product tour covering Superwall features for new users and developers.", + "category": "Support", + "url": "/docs/support/dashboard/9588935743-a-superwall-product-tour", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/9621028514-why-is-initial-conversion-lower-than-new-trials-on-some-days.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/9621028514-why-is-initial-conversion-lower-than-new-trials-on-some-days.mdx", + "title": "Why is initial conversion lower than new trials on some days?", + "description": "Explains the difference between initial conversion and new trials metrics in Superwall analytics.", + "category": "Support", + "url": "/docs/support/dashboard/9621028514-why-is-initial-conversion-lower-than-new-trials-on-some-days", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/dashboard/index.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/dashboard/index.mdx", + "title": "Dashboard", + "description": "Provides overview of Superwall Dashboard for managing paywalls, campaigns, and users.", + "category": "Support", + "url": "/docs/support/dashboard", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/faq/1379595978-how-to-transfer-app-to-a-new-owner.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/faq/1379595978-how-to-transfer-app-to-a-new-owner.mdx", + "title": "How to Transfer Your App to a New Owner", + "description": "Added guide explaining how to transfer an app to a new owner through Superwall support.", + "category": "Support", + "subcategory": "FAQ", + "url": "/docs/support/faq/1379595978-how-to-transfer-app-to-a-new-owner", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/faq/2801653905-how-does-superwalls-pricing-work.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/faq/2801653905-how-does-superwalls-pricing-work.mdx", + "title": "How does Superwall's pricing work?", + "description": "Introduces Superwall's new pricing model based on Monthly Attributed Revenue (MAR)", + "category": "Support", + "subcategory": "FAQ", + "url": "/docs/support/faq/2801653905-how-does-superwalls-pricing-work", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/faq/index.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/faq/index.mdx", + "title": "FAQ", + "description": "Added FAQ section with common questions and answers about Superwall.", + "category": "Support", + "subcategory": "FAQ", + "url": "/docs/support/faq", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/index.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/index.mdx", + "title": "Support Center", + "description": "Introduces new Support Center with common issues, troubleshooting links, and help options", + "category": "Support", + "url": "/docs/support", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/1377495156-how-to-create-a-countdown-timer-on-a-paywall.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/1377495156-how-to-create-a-countdown-timer-on-a-paywall.mdx", + "title": "Video: How to create a countdown timer on a paywall", + "description": "Explains how to create countdown timers for paywalls using Superwall's editor through video tutorial.", + "category": "Support", + "url": "/docs/support/paywall-editor/1377495156-how-to-create-a-countdown-timer-on-a-paywall", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/1502195570-adding-products-to-paywalls.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/1502195570-adding-products-to-paywalls.mdx", + "title": "Video: Adding Products to Paywalls", + "description": "Provides video tutorial and guidance on adding products to paywalls with customization tips.", + "category": "Support", + "url": "/docs/support/paywall-editor/1502195570-adding-products-to-paywalls", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/1523245773-are-videos-automatically-compressed-when-uploaded-to-a-paywall-via-the-editor.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/1523245773-are-videos-automatically-compressed-when-uploaded-to-a-paywall-via-the-editor.mdx", + "title": "Are video and images automatically compressed when uploaded to a paywall via the editor?", + "description": "Added details about automatic video and image compression when uploading to a paywall.", + "category": "Support", + "url": "/docs/support/paywall-editor/1523245773-are-videos-automatically-compressed-when-uploaded-to-a-paywall-via-the-editor", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/1932646675-building-your-first-paywall.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/1932646675-building-your-first-paywall.mdx", + "title": "Video: Building your first paywall", + "description": "Adds video tutorial for building first paywall with step-by-step walkthrough from start to testing.", + "category": "Support", + "url": "/docs/support/paywall-editor/1932646675-building-your-first-paywall", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/2079174387-using-custom-actions-to-setup-referral-systems.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/2079174387-using-custom-actions-to-setup-referral-systems.mdx", + "title": "Video: Using Custom Actions to Setup Referral Systems", + "description": "Added video tutorial for setting up referral systems with custom actions in Superwall SDK.", + "category": "Support", + "url": "/docs/support/paywall-editor/2079174387-using-custom-actions-to-setup-referral-systems", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/2306253672-can-i-use-a-feature-shown-in-the-attached-screenshot.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/2306253672-can-i-use-a-feature-shown-in-the-attached-screenshot.mdx", + "title": "When adding a product, is it possible to fetch the price for non subscription products?", + "description": "Added guidance on pricing limitations for non-subscription products in paywall editor.", + "category": "Support", + "url": "/docs/support/paywall-editor/2306253672-can-i-use-a-feature-shown-in-the-attached-screenshot", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/2427577661-how-to-use-custom-actions-in-your-paywall.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/2427577661-how-to-use-custom-actions-in-your-paywall.mdx", + "title": "Video: How to use custom actions in your paywall", + "description": "Learn how to set up and use custom actions in your paywall with a helpful video tutorial.", + "category": "Support", + "url": "/docs/support/paywall-editor/2427577661-how-to-use-custom-actions-in-your-paywall", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/2455479259-showing-paywalls-with-a-discount-for-abandoned-transactions.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/2455479259-showing-paywalls-with-a-discount-for-abandoned-transactions.mdx", + "title": "Video: Showing paywalls with a discount for abandoned transactions", + "description": "Learn how to show discounted paywalls for abandoned transactions and boost revenue.", + "category": "Support", + "url": "/docs/support/paywall-editor/2455479259-showing-paywalls-with-a-discount-for-abandoned-transactions", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/3696237744-using-custom-placements-for-tracking-paywall-interactions.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/3696237744-using-custom-placements-for-tracking-paywall-interactions.mdx", + "title": "Video: Using Custom Placements for Tracking Paywall Interactions", + "description": "Learn how to track specific paywall interactions using custom placements with this video tutorial.", + "category": "Support", + "url": "/docs/support/paywall-editor/3696237744-using-custom-placements-for-tracking-paywall-interactions", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/4109039578-display-dynamic-images-in-your-paywalls.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/4109039578-display-dynamic-images-in-your-paywalls.mdx", + "title": "Display dynamic images in your paywalls", + "description": "Learn how to display dynamic images in paywalls using contextual variables.", + "category": "Support", + "url": "/docs/support/paywall-editor/4109039578-display-dynamic-images-in-your-paywalls", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/4876966592-feature-gating.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/4876966592-feature-gating.mdx", + "title": "Video: Feature Gating", + "description": "Explains feature gating in Superwall, demonstrating how to control paywall and pro content access.", + "category": "Support", + "url": "/docs/support/paywall-editor/4876966592-feature-gating", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/5453267914-how-to-make-paywalls-adapt-to-device-light-or-dark-mode-and-more.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/5453267914-how-to-make-paywalls-adapt-to-device-light-or-dark-mode-and-more.mdx", + "title": "Video: How to make paywalls adapt to device, light or dark mode, and more.", + "description": "Learn how to customize paywalls based on device, mode, and screen width in Superwall.", + "category": "Support", + "url": "/docs/support/paywall-editor/5453267914-how-to-make-paywalls-adapt-to-device-light-or-dark-mode-and-more", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/6032457649-building-a-multi-tier-paywall-using-the-superwall-editor.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/6032457649-building-a-multi-tier-paywall-using-the-superwall-editor.mdx", + "title": "Video: Building a Multi-Tier Paywall using the Superwall Editor", + "description": "Learn how to create multi-tier paywalls with different service levels using the Superwall Editor.", + "category": "Support", + "url": "/docs/support/paywall-editor/6032457649-building-a-multi-tier-paywall-using-the-superwall-editor", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/6426797732-how-do-i-animate-text-elements-in-the-superwall-editor.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/6426797732-how-do-i-animate-text-elements-in-the-superwall-editor.mdx", + "title": "How do I animate elements in the Superwall editor?", + "description": "Learn how to animate text and elements using Lottie and Effects in Superwall editor.", + "category": "Support", + "url": "/docs/support/paywall-editor/6426797732-how-do-i-animate-text-elements-in-the-superwall-editor", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/6485999553-how-to-use-dynamic-values.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/6485999553-how-to-use-dynamic-values.mdx", + "title": "Video: How to use Dynamic Values", + "description": "Learn how to use dynamic values to customize paywalls based on user conditions and device.", + "category": "Support", + "url": "/docs/support/paywall-editor/6485999553-how-to-use-dynamic-values", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/6666110181-tracking-user-behavior-on-paywalls.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/6666110181-tracking-user-behavior-on-paywalls.mdx", + "title": "Video: Tracking User Behavior on Paywalls", + "description": "Provides video tutorial on tracking user interactions and behavior on paywalls", + "category": "Support", + "url": "/docs/support/paywall-editor/6666110181-tracking-user-behavior-on-paywalls", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/7124788686-how-do-i-copy-a-paywall-from-one-account-to-another.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/7124788686-how-do-i-copy-a-paywall-from-one-account-to-another.mdx", + "title": "How do I copy a paywall from one account to another?", + "description": "Provides detailed steps for copying paywalls between Superwall accounts using sharing feature.", + "category": "Support", + "url": "/docs/support/paywall-editor/7124788686-how-do-i-copy-a-paywall-from-one-account-to-another", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/7448860745-can-i-delete-or-update-existing-snippets.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/7448860745-can-i-delete-or-update-existing-snippets.mdx", + "title": "Can I delete or update existing snippets?", + "description": "Added guide for updating existing snippets instead of creating new ones", + "category": "Support", + "url": "/docs/support/paywall-editor/7448860745-can-i-delete-or-update-existing-snippets", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/8950966564-ai-image-generation-in-paywalls.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/8950966564-ai-image-generation-in-paywalls.mdx", + "title": "Video: AI Image Generation in Paywalls", + "description": "Learn how to generate AI images for paywalls, banners, and icons with Superwall's new feature.", + "category": "Support", + "url": "/docs/support/paywall-editor/8950966564-ai-image-generation-in-paywalls", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/paywall-editor/index.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/paywall-editor/index.mdx", + "title": "Paywall Editor", + "description": "Learn how to use the Superwall Paywall Editor to create and manage paywalls.", + "category": "Support", + "url": "/docs/support/paywall-editor", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/sdk/3107042948-how-to-setup-sandbox-testing-without-affecting-production-metrics.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/sdk/3107042948-how-to-setup-sandbox-testing-without-affecting-production-metrics.mdx", + "title": "How to setup sandbox testing without affecting production metrics", + "description": "Explains how to set up sandbox testing without affecting production metrics in Superwall SDK.", + "category": "Support", + "url": "/docs/support/sdk/3107042948-how-to-setup-sandbox-testing-without-affecting-production-metrics", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/sdk/3165338058-how-to-present-a-paywall-from-the-first-touch-in-app.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/sdk/3165338058-how-to-present-a-paywall-from-the-first-touch-in-app.mdx", + "title": "Video: How to present a paywall from the first touch in app", + "description": "Demonstrates presenting a paywall on first user touch instead of app launch.", + "category": "Support", + "url": "/docs/support/sdk/3165338058-how-to-present-a-paywall-from-the-first-touch-in-app", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/sdk/3422417486-how-to-install-and-configure-the-superwall-sdk-in-flutter-in-under-2-minutes.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/sdk/3422417486-how-to-install-and-configure-the-superwall-sdk-in-flutter-in-under-2-minutes.mdx", + "title": "Video: How to Install and Configure the Superwall SDK in Flutter (In Under 2 Minutes!)", + "description": "Adds quick video tutorial for installing and configuring Superwall SDK in Flutter projects.", + "category": "Support", + "url": "/docs/support/sdk/3422417486-how-to-install-and-configure-the-superwall-sdk-in-flutter-in-under-2-minutes", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/sdk/3677919065-how-to-setup-web-restoration-alert-options.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/sdk/3677919065-how-to-setup-web-restoration-alert-options.mdx", + "title": "How to Setup Web Restoration Alert Options", + "description": "Explains how to control web restoration alert behavior in Superwall SDK configuration.", + "category": "Support", + "url": "/docs/support/sdk/3677919065-how-to-setup-web-restoration-alert-options", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/sdk/4341680566-presenting-a-paywall-in-an-ios-app-using-superwall.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/sdk/4341680566-presenting-a-paywall-in-an-ios-app-using-superwall.mdx", + "title": "Video: Presenting a Paywall in an iOS App Using Superwall", + "description": "Demonstrates how to present paywalls in iOS apps using Superwall's event-driven SDK approach.", + "category": "Support", + "url": "/docs/support/sdk/4341680566-presenting-a-paywall-in-an-ios-app-using-superwall", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/sdk/4506454639-using-xcode-s-transaction-manager.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/sdk/4506454639-using-xcode-s-transaction-manager.mdx", + "title": "Video: Using Xcode's Transaction Manager", + "description": "Added video tutorial on using Xcode's Transaction Manager for testing subscription states.", + "category": "Support", + "url": "/docs/support/sdk/4506454639-using-xcode-s-transaction-manager", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/sdk/5896158743-how-do-i-disable-streamlined-purchasing-for-ios-apps.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/sdk/5896158743-how-do-i-disable-streamlined-purchasing-for-ios-apps.mdx", + "title": "How do I disable Streamlined Purchasing for iOS apps?", + "description": "Explains how to disable Streamlined Purchasing feature for iOS apps using StoreKit APIs.", + "category": "Support", + "url": "/docs/support/sdk/5896158743-how-do-i-disable-streamlined-purchasing-for-ios-apps", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/sdk/6889849185-how-to-install-superwall-s-sdk-into-your-ios-app.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/sdk/6889849185-how-to-install-superwall-s-sdk-into-your-ios-app.mdx", + "title": "Video: How to Install Superwall's SDK into your iOS App", + "description": "Added video tutorial for installing Superwall's SDK into iOS apps using Swift Package Manager and Cocoapods.", + "category": "Support", + "url": "/docs/support/sdk/6889849185-how-to-install-superwall-s-sdk-into-your-ios-app", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/sdk/8208368408-making-a-purchase-with-superwall.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/sdk/8208368408-making-a-purchase-with-superwall.mdx", + "title": "Video: Making a Purchase with Superwall", + "description": "Added video tutorial demonstrating how to make first purchase using Superwall in iOS app.", + "category": "Support", + "url": "/docs/support/sdk/8208368408-making-a-purchase-with-superwall", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/sdk/index.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/sdk/index.mdx", + "title": "SDK", + "description": "Added SDK integration overview with comprehensive guide for developers.", + "category": "Support", + "url": "/docs/support/sdk", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/1205343891-why-isn-t-my-free-trial-showing-up-in-my-paywall-preview.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/1205343891-why-isn-t-my-free-trial-showing-up-in-my-paywall-preview.mdx", + "title": "Why isn't my free trial showing up in my paywall?", + "description": "Explains troubleshooting steps for free trial and introductory offer visibility in paywalls.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/1205343891-why-isn-t-my-free-trial-showing-up-in-my-paywall-preview", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/3578026824-troubleshooting-android-sdk.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/3578026824-troubleshooting-android-sdk.mdx", + "title": "Troubleshooting: Android SDK", + "description": "Added troubleshooting guide for common Android SDK integration and support issues.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/3578026824-troubleshooting-android-sdk", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/3920414585-why-aren-t-my-revenuecat-transactions-attributing-to-a-placement-paywall.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/3920414585-why-aren-t-my-revenuecat-transactions-attributing-to-a-placement-paywall.mdx", + "title": "Why aren't my transactions attributing to a placement/paywall?", + "description": "Provides troubleshooting steps for RevenueCat transaction attribution issues in Superwall paywalls", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/3920414585-why-aren-t-my-revenuecat-transactions-attributing-to-a-placement-paywall", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/4741512872-why-am-i-seeing-a-billing-issue-after-my-free-trial-ends.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/4741512872-why-am-i-seeing-a-billing-issue-after-my-free-trial-ends.mdx", + "title": "Why am I seeing a billing issue after my free trial ends?", + "description": "Explains how to handle billing issues when a free trial ends on App Store platforms.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/4741512872-why-am-i-seeing-a-billing-issue-after-my-free-trial-ends", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/5131096404-why-is-my-webhook-s-originalappuserid-different-from-the-user-id-i-set.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/5131096404-why-is-my-webhook-s-originalappuserid-different-from-the-user-id-i-set.mdx", + "title": "Why is my webhook's originalAppUserId different from the user ID I set?", + "description": "Explains how to correctly set user IDs in webhooks for accurate transaction tracking and attribution.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/5131096404-why-is-my-webhook-s-originalappuserid-different-from-the-user-id-i-set", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/5316844925-why-am-i-not-receiving-webhook-events-when-using-revenuecat.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/5316844925-why-am-i-not-receiving-webhook-events-when-using-revenuecat.mdx", + "title": "Why am I not receiving webhook events?", + "description": "Explains troubleshooting steps for missing webhook events when using RevenueCat with Superwall.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/5316844925-why-am-i-not-receiving-webhook-events-when-using-revenuecat", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/5990752866-why-are-my-webhook-events-not-triggering.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/5990752866-why-are-my-webhook-events-not-triggering.mdx", + "title": "Why are my webhook events not triggering?", + "description": "Troubleshoots webhook event configuration and testing for App Store integrations", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/5990752866-why-are-my-webhook-events-not-triggering", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/6022066375-troubleshooting.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/6022066375-troubleshooting.mdx", + "title": "Troubleshooting", + "description": "Provides troubleshooting guidance for paywall presentation, subscription status, and iOS testing.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/6022066375-troubleshooting", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/6204964383-why-am-i-seeing-sign-in-to-apple-account-when-testing-in-app-purchases.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/6204964383-why-am-i-seeing-sign-in-to-apple-account-when-testing-in-app-purchases.mdx", + "title": "Why am I seeing \"Sign in to Apple Account\" when testing in-app purchases?", + "description": "Explains how to resolve \"Sign in to Apple Account\" alerts when testing in-app purchases.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/6204964383-why-am-i-seeing-sign-in-to-apple-account-when-testing-in-app-purchases", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/6381986971-paywall-not-showing.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/6381986971-paywall-not-showing.mdx", + "title": "Paywall Not Showing", + "description": "Provides comprehensive troubleshooting guide for resolving paywall presentation and configuration issues.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/6381986971-paywall-not-showing", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/6999598520-troubleshooting-flutter-sdk.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/6999598520-troubleshooting-flutter-sdk.mdx", + "title": "Troubleshooting: Flutter SDK", + "description": "Added troubleshooting guide for common Flutter SDK integration issues and support resources.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/6999598520-troubleshooting-flutter-sdk", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/7581794451-why-am-i-seeing-520-errors-from-the-events-api-endpoint.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/7581794451-why-am-i-seeing-520-errors-from-the-events-api-endpoint.mdx", + "title": "Why am I seeing 520 errors from the events API endpoint?", + "description": "Explains 520 errors in Superwall's events API, why they occur, and how to handle them.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/7581794451-why-am-i-seeing-520-errors-from-the-events-api-endpoint", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/8550351416-why-am-i-getting-placementnotfound-errors-for-active-placements-in-expo.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/8550351416-why-am-i-getting-placementnotfound-errors-for-active-placements-in-expo.mdx", + "title": "Why am I getting PlacementNotFound errors for active placements in Expo?", + "description": "Explains how to resolve PlacementNotFound errors when using placements in Expo.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/8550351416-why-am-i-getting-placementnotfound-errors-for-active-placements-in-expo", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/8569105587-why-does-my-subscription-status-remain-inactive-after-successful-purchase.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/8569105587-why-does-my-subscription-status-remain-inactive-after-successful-purchase.mdx", + "title": "Why does my subscription status remain INACTIVE after successful purchase?", + "description": "Added troubleshooting guide for resolving inactive subscription status after purchase.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/8569105587-why-does-my-subscription-status-remain-inactive-after-successful-purchase", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/8881818609-troubleshooting-ios-sdk.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/8881818609-troubleshooting-ios-sdk.mdx", + "title": "Troubleshooting: iOS SDK", + "description": "Added troubleshooting guide for common iOS SDK integration and usage issues.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/8881818609-troubleshooting-ios-sdk", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/9207773532-why-does-my-entitlement-show-missing-app-id-after-connecting-app-store-connect-api.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/9207773532-why-does-my-entitlement-show-missing-app-id-after-connecting-app-store-connect-api.mdx", + "title": "Why does my entitlement show \"Missing App ID\" after connecting App Store Connect API?", + "description": "Explains how to resolve \"Missing App ID\" warning when integrating App Store Connect API with Superwall.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/9207773532-why-does-my-entitlement-show-missing-app-id-after-connecting-app-store-connect-api", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/9558513118-why-are-my-products-showing-unknown-status-in-superwall.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/9558513118-why-are-my-products-showing-unknown-status-in-superwall.mdx", + "title": "Why are my products showing \"Unknown\" status in Superwall?", + "description": "Explains how to resolve \"Unknown\" product status issues in Superwall dashboard.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/9558513118-why-are-my-products-showing-unknown-status-in-superwall", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/9619927130-why-is-my-paywall-not-loading-on-android-with-billing-service-unavailable-error.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/9619927130-why-is-my-paywall-not-loading-on-android-with-billing-service-unavailable-error.mdx", + "title": "Why is my paywall not loading on Android with Billing Service Unavailable?", + "description": "Added troubleshooting guide for Android paywall loading issues with billing service errors.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/9619927130-why-is-my-paywall-not-loading-on-android-with-billing-service-unavailable-error", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/index.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/index.mdx", + "title": "Troubleshooting", + "description": "Added troubleshooting support page with automatic listing of support resources.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/products-not-loading/1253018505-products-not-loading-ios.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/products-not-loading/1253018505-products-not-loading-ios.mdx", + "title": "Products Not Loading: iOS", + "description": "Provides comprehensive troubleshooting steps for resolving iOS product loading issues in-app.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/products-not-loading/1253018505-products-not-loading-ios", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/products-not-loading/3716347779-products-not-loading-android.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/products-not-loading/3716347779-products-not-loading-android.mdx", + "title": "Products Not Loading: Android", + "description": "Added troubleshooting steps for resolving product loading issues on Android.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/products-not-loading/3716347779-products-not-loading-android", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/products-not-loading/4739776203-products-not-loading-ios-simulator.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/products-not-loading/4739776203-products-not-loading-ios-simulator.mdx", + "title": "Products Not Loading: iOS Simulator", + "description": "Provides troubleshooting steps for loading products in iOS Simulator using StoreKit config files", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/products-not-loading/4739776203-products-not-loading-ios-simulator", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/products-not-loading/4845457144-products-not-loading-ios-device-with-storekit-config-file.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/products-not-loading/4845457144-products-not-loading-ios-device-with-storekit-config-file.mdx", + "title": "Products Not Loading: iOS Device with StoreKit config file", + "description": "Provides troubleshooting steps for loading products with StoreKit configuration files on iOS.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/products-not-loading/4845457144-products-not-loading-ios-device-with-storekit-config-file", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/products-not-loading/9984011656-products-not-loading-safari-preview.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/products-not-loading/9984011656-products-not-loading-safari-preview.mdx", + "title": "Products Not Loading: Browser Preview", + "description": "Added troubleshooting guidance for products not loading in browser previews.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/products-not-loading/9984011656-products-not-loading-safari-preview", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/troubleshooting/products-not-loading/index.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/troubleshooting/products-not-loading/index.mdx", + "title": "Products Not Loading: Troubleshooting Guide", + "description": "Added troubleshooting guide for resolving products not loading across platforms.", + "category": "Support", + "subcategory": "Troubleshooting", + "url": "/docs/support/troubleshooting/products-not-loading", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/web-checkout/1872595046-web-checkout-tour-on-ios.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/web-checkout/1872595046-web-checkout-tour-on-ios.mdx", + "title": "Video: Web Checkout Tour on iOS", + "description": "Introduces video tutorial demonstrating web checkout flow for iOS using Superwall and Stripe.", + "category": "Support", + "url": "/docs/support/web-checkout/1872595046-web-checkout-tour-on-ios", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/web-checkout/3771535881-why-am-i-seeing-an-error-occurred-while-loading-the-stripe-portal-in-the-manage-subscription-page.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/web-checkout/3771535881-why-am-i-seeing-an-error-occurred-while-loading-the-stripe-portal-in-the-manage-subscription-page.mdx", + "title": "Why am I seeing \"An error occurred while loading the Stripe Portal\" in the manage subscription page?", + "description": "Added troubleshooting guide for resolving Stripe Portal loading errors in web2app environment.", + "category": "Support", + "url": "/docs/support/web-checkout/3771535881-why-am-i-seeing-an-error-occurred-while-loading-the-stripe-portal-in-the-manage-subscription-page", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/support/web-checkout/index.mdx:0f1f809a11f5627eaf38b688be406426b6482722", + "path": "support/web-checkout/index.mdx", + "title": "Web Checkout", + "description": "Added guide for integrating Superwall Web Checkout into applications.", + "category": "Support", + "url": "/docs/support/web-checkout", + "date": "2025-12-10T04:07:13.000Z", + "changeType": "added", + "commitHash": "0f1f809a11f5627eaf38b688be406426b6482722" + }, + { + "key": "content/docs/integrations/firebase.mdx:a22566923c5984918e66c60120826cd16aaae4e8", + "path": "integrations/firebase.mdx", + "title": "Firebase", + "description": "Adds comprehensive guide for integrating Superwall events with Firebase Analytics across platforms.", + "category": "Integrations", + "url": "/docs/integrations/firebase", + "date": "2025-12-09T16:50:29.000Z", + "changeType": "added", + "commitHash": "a22566923c5984918e66c60120826cd16aaae4e8" + }, + { + "key": "content/docs/flutter/sdk-reference/getCustomerInfo.mdx:cdcef4df7d0435bb7a8d6d28841728b80b8e7223", + "path": "flutter/sdk-reference/getCustomerInfo.mdx", + "title": "getCustomerInfo()", + "description": "Adds reference for retrieving customer info, subscriptions, and entitlements in Flutter SDK.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/getCustomerInfo", + "date": "2025-12-09T01:39:26.000Z", + "changeType": "added", + "commitHash": "cdcef4df7d0435bb7a8d6d28841728b80b8e7223" + }, + { + "key": "content/docs/flutter/sdk-reference/getEntitlements.mdx:cdcef4df7d0435bb7a8d6d28841728b80b8e7223", + "path": "flutter/sdk-reference/getEntitlements.mdx", + "title": "getEntitlements()", + "description": "Introduces `getEntitlements()` method for retrieving and filtering user's subscription entitlements.", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/getEntitlements", + "date": "2025-12-09T01:39:26.000Z", + "changeType": "added", + "commitHash": "cdcef4df7d0435bb7a8d6d28841728b80b8e7223" + }, + { + "key": "content/docs/android/guides/migrations/migrating-to-v2.mdx:57a0dcba8837ea44c928ec81dd858d76b8bc7b93", + "path": "android/guides/migrations/migrating-to-v2.mdx", + "title": "Migrating from v1 to v2 - Android", + "description": "Updates Compose library artifact version to 2.6.5 in migration guide for Android SDK.", + "category": "Android SDK", + "subcategory": "Guides", + "url": "/docs/android/guides/migrations/migrating-to-v2", + "date": "2025-12-09T01:38:40.000Z", + "changeType": "modified", + "commitHash": "57a0dcba8837ea44c928ec81dd858d76b8bc7b93" + }, + { + "key": "content/docs/android/guides/web-checkout/post-checkout-redirecting.mdx:57a0dcba8837ea44c928ec81dd858d76b8bc7b93", + "path": "android/guides/web-checkout/post-checkout-redirecting.mdx", + "title": "Post-Checkout Redirecting", + "description": "Added code example for accessing product identifier after successful redemption.", + "category": "Android SDK", + "subcategory": "Guides", + "url": "/docs/android/guides/web-checkout/post-checkout-redirecting", + "date": "2025-12-09T01:38:40.000Z", + "changeType": "modified", + "commitHash": "57a0dcba8837ea44c928ec81dd858d76b8bc7b93" + }, + { + "key": "content/docs/android/index.mdx:57a0dcba8837ea44c928ec81dd858d76b8bc7b93", + "path": "android/index.mdx", + "title": "Welcome", + "description": "Updates Android SDK version reference to latest release 2.6.5.", + "category": "Android SDK", + "url": "/docs/android", + "date": "2025-12-09T01:38:40.000Z", + "changeType": "modified", + "commitHash": "57a0dcba8837ea44c928ec81dd858d76b8bc7b93" + }, + { + "key": "content/docs/android/sdk-reference/index.mdx:57a0dcba8837ea44c928ec81dd858d76b8bc7b93", + "path": "android/sdk-reference/index.mdx", + "title": "Overview", + "description": "Updated Android SDK version to 2.6.5 in latest version component.", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference", + "date": "2025-12-09T01:38:40.000Z", + "changeType": "modified", + "commitHash": "57a0dcba8837ea44c928ec81dd858d76b8bc7b93" + }, + { + "key": "content/docs/ios/sdk-reference/integrationAttributes.mdx:11c1752ed86f96dd9ad7765c7f4d9b1d131c79b2", + "path": "ios/sdk-reference/integrationAttributes.mdx", + "title": "integrationAttributes", + "description": "Demonstrates accessing Firebase installation ID from integration attributes.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/integrationAttributes", + "date": "2025-12-09T01:35:37.000Z", + "changeType": "modified", + "commitHash": "11c1752ed86f96dd9ad7765c7f4d9b1d131c79b2" + }, + { + "key": "content/docs/ios/sdk-reference/confirmAllAssignments.mdx:f96578543e2b33bd28912dc0adf05d662c2518aa", + "path": "ios/sdk-reference/confirmAllAssignments.mdx", + "title": "confirmAllAssignments", + "description": "Adds reference for confirming experiment assignments on iOS with code examples.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/confirmAllAssignments", + "date": "2025-12-02T18:40:52.000Z", + "changeType": "added", + "commitHash": "f96578543e2b33bd28912dc0adf05d662c2518aa" + }, + { + "key": "content/docs/ios/sdk-reference/getCustomerInfo.mdx:f96578543e2b33bd28912dc0adf05d662c2518aa", + "path": "ios/sdk-reference/getCustomerInfo.mdx", + "title": "getCustomerInfo", + "description": "Adds async method for retrieving customer information with examples in Swift.", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference/getCustomerInfo", + "date": "2025-12-02T18:40:52.000Z", + "changeType": "added", + "commitHash": "f96578543e2b33bd28912dc0adf05d662c2518aa" + }, + { + "key": "content/docs/dashboard/dashboard-campaigns/campaigns-audience.mdx:b74fc8774c1accd1247114be3e61c811f400dd46", + "path": "dashboard/dashboard-campaigns/campaigns-audience.mdx", + "title": "Audiences", + "description": "Expands entitlement matching options to include subscription status and auto-renew settings.", + "category": "Dashboard", + "subcategory": "Campaigns", + "url": "/docs/dashboard/dashboard-campaigns/campaigns-audience", + "date": "2025-12-02T18:36:07.000Z", + "changeType": "modified", + "commitHash": "b74fc8774c1accd1247114be3e61c811f400dd46" + }, + { + "key": "content/docs/dashboard/overview-users.mdx:299cacaeb337a379b97438aa69ff7ee436f5206d", + "path": "dashboard/overview-users.mdx", + "title": "Users", + "description": "Added guide for manually granting and revoking user entitlements in the dashboard.", + "category": "Dashboard", + "url": "/docs/dashboard/overview-users", + "date": "2025-12-02T17:32:15.000Z", + "changeType": "modified", + "commitHash": "299cacaeb337a379b97438aa69ff7ee436f5206d" + } + ] +} \ No newline at end of file diff --git a/src/lib/mixedbread.ts b/src/lib/mixedbread.ts new file mode 100644 index 00000000..a2189f87 --- /dev/null +++ b/src/lib/mixedbread.ts @@ -0,0 +1,33 @@ +/** + * Re-exports shared mixedbread types and utilities for app code. + * + * Why this file exists: + * - Provides @/ path imports for Next.js app code + * - Acts as a single import point for all mixedbread-related types + * - Allows future app-specific extensions without modifying shared/ + */ + +// Re-export all shared types +export type { + ChunkMetadata, + ChunkHeading, + GeneratedMeta, +} from "../../shared/types/mixedbread"; + +export { createChunkMetadata } from "../../shared/types/mixedbread"; + +// Re-export SDK constants +export { + SDK_PLATFORM_IDS, + SDK_PLATFORMS_SET, + SDK_OPTIONS, + SDK_NAMES, + SDK_URL_ROOTS, + isSDKPlatform, + getSDKLabel, +} from "../../shared/constants/sdk"; + +export type { SDKPlatformId, SDKOption } from "../../shared/constants/sdk"; + +// Re-export title utilities +export { extractTitle, filenameToTitle } from "../../shared/utils/title"; diff --git a/src/mdx-components.tsx b/src/mdx-components.tsx index 1bfdb357..38f73abb 100644 --- a/src/mdx-components.tsx +++ b/src/mdx-components.tsx @@ -13,6 +13,8 @@ import { SdkLatestVersion } from './components/SdkLatestVersion' import { GithubInfo as GithubInfoComponent } from 'fumadocs-ui/components/github-info'; import { WhenLoggedIn, WhenLoggedOut, LoginStatusProvider, BasedOnAuth, LoggedIn, LoggedOut } from './components/LoginStatusContext'; import { TypeTable } from './components/type-table'; +import { ChangelogTimeline } from './components/ChangelogTimeline'; +import { Mermaid } from "./components/Mermaid" // We'll add custom components here @@ -121,6 +123,48 @@ const Check = ({ children }: { children: React.ReactNode }) => { ) } +const ApplePlatformIcon = (props: React.SVGProps) => ( + + + + + + + + +) + +const AndroidPlatformIcon = (props: React.SVGProps) => ( + + + +) + +const FlutterPlatformIcon = (props: React.SVGProps) => ( + + + +) + +const ExpoPlatformIcon = (props: React.SVGProps) => ( + + + +) + +const PLATFORM_ICONS = { + apple: ApplePlatformIcon, + android: AndroidPlatformIcon, + flutter: FlutterPlatformIcon, + expo: ExpoPlatformIcon, +} as const + +const toPascalCase = (value: string) => + value + .split(/[-_\s]+/) + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join('') + const Card = ({ title, icon, @@ -139,18 +183,27 @@ const Card = ({ if (icon) { if (typeof icon === 'string') { - const LucideIcon = - (Lucide as Record)[ - icon.charAt(0).toUpperCase() + icon.slice(1) - ]; - if (LucideIcon) { - RenderedIcon = ( - - ); + const normalized = icon.trim().toLowerCase() + const PlatformIcon = PLATFORM_ICONS[normalized as keyof typeof PLATFORM_ICONS] + + if (PlatformIcon) { + RenderedIcon = + } else { + const lucideIcons = Lucide as Record + const LucideIcon = + lucideIcons[icon] ?? + lucideIcons[icon.charAt(0).toUpperCase() + icon.slice(1)] ?? + lucideIcons[toPascalCase(icon)] + + if (LucideIcon) { + RenderedIcon = ( + + ) + } } } else if (React.isValidElement(icon)) { RenderedIcon = React.cloneElement(icon as any, { @@ -278,11 +331,13 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents { SdkLatestVersion, GithubInfo, TypeTable, + Mermaid, WhenLoggedIn, WhenLoggedOut, LoginStatusProvider, BasedOnAuth, LoggedIn, LoggedOut, + ChangelogTimeline, } as MDXComponents } diff --git a/tsconfig.json b/tsconfig.json index 7b8b8a32..7032ee3f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,9 @@ ], "@/*": [ "./src/*" + ], + "@shared/*": [ + "./shared/*" ] }, "plugins": [