Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 72 additions & 39 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,52 @@ Instructions for AI coding agents working in this repository (Superwall docs sit
For documentation-writing guidelines (tone, content conventions, etc.), see `content/README/AGENTS.md`.

## Project overview
- **Stack**: Next.js + Fumadocs (MDX) + TailwindCSS
- **Hosting**: Cloudflare (via `@opennextjs/cloudflare`)

- **Stack**: Vite + TanStack Start + Fumadocs (MDX) + TailwindCSS
- **Hosting**: Cloudflare Workers (via `@cloudflare/vite-plugin` + Wrangler)
- **Docs**: multi-platform SDK docs (iOS, Android, Flutter, Expo, React Native) + dashboard/integration guides
- **Search/AI**: built-in search + AI search mode; support center integration
- **Theming**: custom themes; dark/light mode is disabled by default

## Non-negotiable rules

- **Never deploy without explicit user approval.**
- **Content vs. engineering changes**:
- **Content changes**: update files in `content/docs/**` (and `content/shared/**` if used).
- **Engineering changes**: you may edit other parts of the repo as needed.
- **Never edit `public/**`** (it is generated and will be overwritten).
- **SDK references first**: if a task needs SDK APIs/changelogs, run `bun run download:references` before writing.
- **SDK reference tables**: use the Fumadocs-style `TypeTable` for parameters/types; do not use `<ParamTable>`.
- **Test**: run `bun run build:cf` and `bun test` after making changes to ensure you didn't break anything
- **Test**: run `bun run build` and `bun test` after making changes to ensure you didn't break anything

## Common commands (run from repo root)

```bash
# Dev (http://localhost:8293)
# Dev (http://localhost:3000/docs)
bun run dev

# Build (runs all generators, then MDX + Next build)
# Build (generates changelogs, copies images, then Vite build + static cache)
bun run build

# Type-check
bun run types:check

# Lint / format
bun run lint
bun run fmt

# Clear caches (when build/dev gets weird)
rm -rf .next
bun run clear:cache
rm -rf .source node_modules/.vite

# Content generation / assets
bun run generate:llm
bun run generate:md
bun run copy:docs-images
bun run watch:images
bun run generate:changelog
bun run scripts/copy-docs-images.cjs

# Sync docs to AI search
bun run sync:mixedbread

# Cloudflare builds / preview
bun run build:cf
bun run build:cf # same as bun run build
bun run preview

# Deploy (DO NOT RUN WITHOUT USER APPROVAL)
Expand All @@ -49,64 +59,82 @@ bun run deploy
```

## Content + navigation system

- **Navigation** is defined by `meta.json` files alongside content directories.
- If a `meta.json` does **not** include `"..."`, every page must be explicitly listed.
- Use **relative paths without extensions** (e.g. `"guides/my-guide"`, not `"guides/my-guide.mdx"`).
- You can nest objects to group related pages.
- **Adding a new page**:
- Create the `.mdx` file under the correct `content/docs/**` folder.
- Update the folders `meta.json` to include it in navigation.
- Update the folder's `meta.json` to include it in navigation.
- Run `bun run build` to verify generation + routing.
- **Images**: assets in docs content are copied into `public/` during build (use `bun run copy:docs-images`; in dev use `bun run watch:images`).
- **Images**: assets in docs content are copied into `public/` during build (runs automatically via `prebuild` script).

## Routing, redirects, and base path
- The site is served under **`/docs`** (`basePath` + `assetPrefix` in `next.config.ts`).

- The site is served under **`/docs`** (configured as `router.basepath` in `vite.config.ts`).
- `/docs/sdk/*` is a selector route that redirects to the chosen platform page.
- Redirects are generated from `redirects-map.ts` (`folderRedirectsMap`, `fileRedirectsMap`, `externalRedirectsMap`) inside `next.config.ts`.
**Changes require a rebuild** to take effect.
- Redirects are defined in `redirects-map.ts` (`folderRedirectsMap`, `fileRedirectsMap`) and processed at runtime via TanStack middleware in `src/start.ts`.

## MDX / remark pipeline (order matters)

Custom remark plugins are wired in `source.config.ts`. The current order is:

1. `remark-image-paths` (must run first)
2. `remark-follow-export`
3. Fumadocs “existing” plugins
4. `remark-include`
5. `remark-directive`
6. `remark-tabs-syntax`
7. `remark-code-language`
8. `remark-codegroup-to-tabs`
9. `remark-sdk-filter` (must run last; removes non-matching SDK blocks)
2. `remark-link-paths`
3. `remark-follow-export`
4. Fumadocs "existing" plugins
5. `remark-include`
6. `remark-directive`
7. `remark-tabs-syntax`
8. `remark-code-language`
9. `remark-codegroup-to-tabs`
10. `remark-sdk-filter` (must run last; removes non-matching SDK blocks)

## Key files (where to look first)
- **Config**: `source.config.ts`, `next.config.ts`, `open-next.config.ts`, `tsconfig.json`

- **Config**: `vite.config.ts`, `source.config.ts`, `wrangler.jsonc`, `tsconfig.json`
- **Redirects**: `redirects-map.ts`
- **Layout/routing**: `src/app/layout.config.tsx`, `src/app/(docs)/[[...slug]]/page.tsx`
- **Middleware**: `src/start.ts` (legacy redirects, LLM rewriting, static cache)
- **Router/routing**: `src/router.tsx`, `src/routes/__root.tsx`, `src/routes/$.tsx`
- **Source plumbing**: `src/lib/source.ts`, `src/mdx-components.tsx`
- **Plugins**: `plugins/*`
- **Scripts**: `scripts/*` (title map, generators, image copy/watch, cache clear, etc.)
- **New UI/components**: put React components in `src/components/` and wire site-level layout/nav in `src/app/layout.config.tsx` / `src/lib/source.ts`.
- **Scripts**: `scripts/*` (changelog generation, image copy, static cache, etc.)
- **New UI/components**: put React components in `src/components/`.

## What requires a rebuild (not just hot reload)

- `meta.json` navigation edits
- `redirects-map.ts` changes
- remark plugin changes (`plugins/*` or `source.config.ts`)
- new/updated images (make sure copy/watch runs)
- `wrangler.jsonc` changes
- new/updated images (ensure `copy-docs-images` runs)

## Cloudflare / deployment notes
- Cloudflare builds use `bun run build:cf` (OpenNext adapter).
- Cloudflare Workers disallow `eval()`: `next.config.ts` aliases the eval-based `fumadocs-ui` `hide-if-empty` component to `src/components/HideIfEmptyStub.tsx`.


- Cloudflare builds use `bun run build:cf` (Vite + `@cloudflare/vite-plugin`).
- Deployment config is in `wrangler.jsonc` (routes, compatibility flags, staging env).
- Deploy commands run `wrangler deploy` after build.

## Performance notes
- Builds use Turbo (`next dev --turbo`) and Cloudflare’s CDN for delivery.

- Shiki language filtering: custom Vite plugin in `vite.config.ts` reduces bundle from ~200 language chunks to ~20 actually used.
- Prerendering: enabled via TanStack Start with concurrency of 3.
- Static cache middleware in `src/start.ts` for server-side caching.
- Cloudflare smart placement enabled.

## SDK references (required for SDK-accuracy work)

Run:

```bash
bun run download:references
```

This clones/pulls SDK repos into `reference/` (gitignored). Use the source to confirm API signatures and behavior before updating docs.

## SDK changelogs (strict rules)

- **Docs location**: `content/docs/<sdk>/changelog.mdx` for `ios`, `android`, `flutter`, `expo`, `react-native`
- **Source of truth**: `reference/<sdk>/CHANGELOG.md`
- **Process**: run `bun run download:references`, then copy upstream changelog **verbatim**
Expand All @@ -115,20 +143,25 @@ This clones/pulls SDK repos into `reference/` (gitignored). Use the source to co
- **Navigation**: ensure each SDK `meta.json` lists `changelog` right after `index`.

## AI SDK / AI SDK Elements

- For any AI SDK or AI SDK Elements work, fetch the latest docs via Context7 MCP before making changes.

## Environment variables
- **`SEARCH_MODE`**: `'fumadocs'` (default) or `'rag'` (external AI search)
- **`NEXTJS_ENV`**: development/production

- **`CLOUDFLARE_ENV`**: `staging` or `production` (for Cloudflare builds)
- **`MIXEDBREAD_API_KEY`** / **`MIXEDBREAD_STORE_ID`**: for AI search sync (`bun run sync:mixedbread`)
- Optional integration keys may exist (Slack, Mesh, Unify, RB2B, Pylon). See project environment documentation if present.

## TypeScript path aliases

- `@/*` → `src/*`
- `@/.source` → `.source/index.ts` (generated by Fumadocs)
- `fumadocs-mdx:collections/*` → `.source/*` (generated by Fumadocs)

## Troubleshooting

- **Build failures**: check MDX syntax; validate `meta.json`; ensure listed pages exist; run `bun run build` for full errors.
- **Dev server issues**: `rm -rf .next`, `bun run clear:cache`, verify port 8293 is free, reinstall dependencies if needed.
- **Dev server issues**: `rm -rf .source node_modules/.vite`, verify port 3000 is free, reinstall dependencies if needed.

## Skill mirrors
- Keep `.agents/skills/` and `.claude/skills/` in sync for shared skills.

- Keep `.agents/skills/` and `.claude/skills/` in sync for shared skills.
4 changes: 3 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion content/docs/android/sdk-reference/SuperwallEvent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ These events are received via [`SuperwallDelegate.handleSuperwallEvent(eventInfo

## 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.
- `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-266) 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.

Expand Down
81 changes: 80 additions & 1 deletion content/docs/ios/guides/local-resources.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,83 @@ title: "Local Resources"
description: "Bundle images, videos, and other media in your app for use in paywalls, enabling faster load times and offline support."
---

<include>../../../shared/local-resources.mdx</include>
Local resources allow you to register media files bundled in your app (images, videos, audio) so that paywalls can reference them by ID instead of loading them from a remote URL. The SDK serves these files directly from disk, which means instant loading and no network dependency.

<Note>
Local resources require **iOS SDK v4.13.0+**. Make sure you're on a compatible version before
using this feature.
</Note>

## Registering local resources

Set the `localResources` property on `SuperwallOptions` **before** calling `configure()`. Each entry maps a **resource ID** (a string you choose) to a local file URL:

```swift Swift
let options = SuperwallOptions()
options.localResources = [
"hero-image": Bundle.main.url(forResource: "hero", withExtension: "png")!,
"onboarding-video": Bundle.main.url(forResource: "onboarding", withExtension: "mp4")!
]

Superwall.configure(apiKey: "pk_your_api_key", options: options)
```

The resource IDs you choose here are the same IDs you'll select in the [paywall editor](/dashboard/dashboard-creating-paywalls/paywall-editor-local-resources) when configuring an image or video component.

<Warning>
Local resources must be set **before** calling `configure()`. Resources added after configuration
will not be available for paywalls that have already loaded.
</Warning>

## Supported file types

The SDK supports a wide range of media formats:

| Category | Formats |
| -------- | ------------------------------------------------------ |
| Images | PNG, JPEG, GIF, WebP, SVG, HEIC, HEIF, AVIF, BMP, TIFF |
| Videos | MP4, MOV, WebM, AVI, HEVC/H.265 |

## Choosing resource IDs

Resource IDs are simple strings that act as the key between your app and the paywall editor. A few tips:

- **Use descriptive names** like `"hero-image"` or `"onboarding-video"` rather than `"img1"`.
- **Keep them stable.** If you change a resource ID, you'll need to update any paywalls that reference it in the editor.
- **They're case-sensitive.** `"Hero-Image"` and `"hero-image"` are different IDs.

## Fallback behavior

In the paywall editor, you can set both a local resource and a remote URL on the same image or video component. If the local file can't be loaded (for example, the resource ID isn't registered or the file is missing from the bundle), the paywall automatically falls back to the remote URL. This ensures paywalls still work on older SDK versions or if a resource is accidentally removed from the app bundle.

## Debugging

The SDK includes a built-in debug view for verifying your local resources are set up correctly. It shows each registered resource ID, its file path, and a preview of the content.

<Tip>
If a resource isn't showing up in the paywall editor dropdown, make sure your test device has
opened a paywall (or otherwise triggered a device attributes event) after configuring
`localResources`. The editor only shows resource IDs reported in the last 7 days.
</Tip>

## Example: Onboarding paywall with a bundled video

A common use case is bundling an onboarding video so it loads instantly the first time a user sees your paywall:

```swift Swift
// In your app's initialization
let options = SuperwallOptions()
options.localResources = [
"onboarding-video": Bundle.main.url(forResource: "welcome", withExtension: "mp4")!,
"app-logo": Bundle.main.url(forResource: "logo", withExtension: "png")!
]

Superwall.configure(apiKey: "pk_your_api_key", options: options)
```

Then in the paywall editor, select "onboarding-video" as the local resource for your video component and "app-logo" for the logo image. Set remote URLs as fallbacks for both.

## Related

- [`SuperwallOptions`](/ios/sdk-reference/SuperwallOptions): Full configuration reference.
- [Paywall Editor: Local Resources](/dashboard/dashboard-creating-paywalls/paywall-editor-local-resources): How to use local resources in the paywall editor.
Loading
Loading