diff --git a/packages/docs/content/docs/api-reference/admin/api-clients.md b/packages/docs/content/docs/api-reference/admin/api-clients.md
index 6178c99..7dbbc74 100644
--- a/packages/docs/content/docs/api-reference/admin/api-clients.md
+++ b/packages/docs/content/docs/api-reference/admin/api-clients.md
@@ -8,9 +8,9 @@ A single API client represents your application, not individual users. Create on
Each client has an `hvc_`-prefixed client key and an `hvs_`-prefixed client secret. The secret is only returned at creation and is sha256-hashed in the database. Server-to-server callers pass the secret as `X-Client-Secret`. Browser callers use the `Origin` header, which is matched against the client's `client_uri`. Mismatches currently log warnings rather than rejecting the request, but rate limiting applies either way. See [Authentication — XRPC](../../getting-started/authentication.md#xrpc-api-client-identification) for the client-side view, and the [API Keys guide](../../guides/admin/api-keys.md) for how admin API keys differ from API clients.
-:::tip Third-Party API Clients
+
Third-party apps can also create, list, and delete their own API clients programmatically via the [XRPC API](../oauth/api-clients.md), without needing admin access.
-:::
+
```sh
# All examples assume $TOKEN is an API key (hv_...)
diff --git a/packages/docs/content/docs/api-reference/oauth/api-clients.md b/packages/docs/content/docs/api-reference/oauth/api-clients.md
index 72d257c..5d42f55 100644
--- a/packages/docs/content/docs/api-reference/oauth/api-clients.md
+++ b/packages/docs/content/docs/api-reference/oauth/api-clients.md
@@ -6,9 +6,9 @@ Third-party applications can manage their own API clients via the `dev.happyview
All endpoints use [DPoP authentication](../../getting-started/authentication.md#authenticating-users-for-procedures). See the [admin API client docs](../admin/api-clients.md) for managing clients through the admin API, and the [API Clients guide](../../guides/features/api-clients.md) for how API clients work.
-:::note
+
Only top-level API clients can call these endpoints. Third-party (child) clients receive `401 Unauthorized` or `403 Forbidden`.
-:::
+
## Authentication
diff --git a/packages/docs/content/docs/experimental/spaces/credentials.md b/packages/docs/content/docs/experimental/spaces/credentials.md
index ab17537..9049622 100644
--- a/packages/docs/content/docs/experimental/spaces/credentials.md
+++ b/packages/docs/content/docs/experimental/spaces/credentials.md
@@ -2,9 +2,9 @@
title: "Credentials"
---
-:::caution Experimental
+
This API is experimental and will change. See the [Permissioned Spaces overview](../spaces.md) for context.
-:::
+
Space credentials are short-lived JWTs for cross-service access to space data. A member proves their membership to get a grant, exchanges the grant for a credential JWT, then passes it to an external service that needs to read the space's records.
diff --git a/packages/docs/content/docs/experimental/spaces/index.md b/packages/docs/content/docs/experimental/spaces/index.md
index 37cda75..688af69 100644
--- a/packages/docs/content/docs/experimental/spaces/index.md
+++ b/packages/docs/content/docs/experimental/spaces/index.md
@@ -2,9 +2,9 @@
title: "Overview"
---
-:::caution Experimental
+
Permissioned Spaces are experimental and the API will change. This implementation follows Daniel Holmgren's [Permissioned Data Diaries](https://dholms.leaflet.pub/3meluqcwky22a) and aligns structurally with the `permissioned-data` branch on `bluesky-social/atproto`, but uses a `dev.happyview` namespace to allow iteration while the official spec stabilizes.
-:::
+
Spaces are containers for permissioned data in atproto. Unlike regular public records that live in a user's repo, space records are gated by membership — only members can read or write data within a space.
diff --git a/packages/docs/content/docs/experimental/spaces/invites.md b/packages/docs/content/docs/experimental/spaces/invites.md
index 169f512..a5ae5de 100644
--- a/packages/docs/content/docs/experimental/spaces/invites.md
+++ b/packages/docs/content/docs/experimental/spaces/invites.md
@@ -2,15 +2,15 @@
title: "Invites"
---
-:::caution Experimental
+
This API is experimental and will change. See the [Permissioned Spaces overview](../spaces.md) for context.
-:::
+
Invites let space owners distribute membership tokens without knowing recipients' DIDs in advance.
-:::info HappyView Extension
+
Invites are a HappyView-specific feature, not part of the AT Protocol spaces spec. They may be replaced by a different mechanism in the future.
-:::
+
## Creating an invite
@@ -51,9 +51,9 @@ curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.createInvit
}
```
-:::warning
+
The `token` is only returned once. It is stored as a SHA-256 hash — HappyView cannot recover the plaintext.
-:::
+
## Redeeming an invite
diff --git a/packages/docs/content/docs/experimental/spaces/managing-spaces.md b/packages/docs/content/docs/experimental/spaces/managing-spaces.md
index 59d2315..d588d7a 100644
--- a/packages/docs/content/docs/experimental/spaces/managing-spaces.md
+++ b/packages/docs/content/docs/experimental/spaces/managing-spaces.md
@@ -2,9 +2,9 @@
title: "Managing Spaces"
---
-:::caution Experimental
+
This API is experimental and will change. See the [Permissioned Spaces overview](../spaces.md) for context.
-:::
+
## Creating a space
@@ -132,6 +132,6 @@ curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.deleteSpace
-d '{"space": "ats://did:plc:abc123/com.example.forum/main"}'
```
-:::warning
+
Deleting a space does not currently cascade to records, members, or credentials. This behavior may change.
-:::
+
diff --git a/packages/docs/content/docs/experimental/spaces/members.md b/packages/docs/content/docs/experimental/spaces/members.md
index a290b1b..43d3c3d 100644
--- a/packages/docs/content/docs/experimental/spaces/members.md
+++ b/packages/docs/content/docs/experimental/spaces/members.md
@@ -2,9 +2,9 @@
title: "Members"
---
-:::caution Experimental
+
This API is experimental and will change. See the [Permissioned Spaces overview](../spaces.md) for context.
-:::
+
Membership determines who can read and write within a space. Members have either `read` or `write` access — write implies read.
diff --git a/packages/docs/content/docs/experimental/spaces/records.md b/packages/docs/content/docs/experimental/spaces/records.md
index bac88c3..4e2bb17 100644
--- a/packages/docs/content/docs/experimental/spaces/records.md
+++ b/packages/docs/content/docs/experimental/spaces/records.md
@@ -2,9 +2,9 @@
title: "Records"
---
-:::caution Experimental
+
This API is experimental and will change. See the [Permissioned Spaces overview](../spaces.md) for context.
-:::
+
Space records are stored separately from public AT Protocol records. They follow the same URI pattern but use the `ats://` scheme and include the space identity:
diff --git a/packages/docs/content/docs/getting-started/authentication.md b/packages/docs/content/docs/getting-started/authentication.md
index 16a443a..71a5844 100644
--- a/packages/docs/content/docs/getting-started/authentication.md
+++ b/packages/docs/content/docs/getting-started/authentication.md
@@ -170,9 +170,9 @@ sequenceDiagram
HV-->>Client: Response
```
-:::tip
+
The [JavaScript SDK](../sdk/overview.md) handles this entire flow for you. The raw HTTP flow below is useful for understanding the protocol or building a non-JavaScript client.
-:::
+
### API clients: confidential vs public
diff --git a/packages/docs/content/docs/getting-started/configuration.md b/packages/docs/content/docs/getting-started/configuration.md
index 421e65f..41f60cf 100644
--- a/packages/docs/content/docs/getting-started/configuration.md
+++ b/packages/docs/content/docs/getting-started/configuration.md
@@ -32,9 +32,9 @@ HappyView is configured via environment variables. A `.env` file in the project
| `TOS_URI` | no | --- | URL to terms of service. Overridden by database setting if set via admin API |
| `POLICY_URI` | no | --- | URL to privacy policy. Overridden by database setting if set via admin API |
-:::warning[Use 127.0.0.1, not localhost]
+
ATProto OAuth loopback clients are registered with `127.0.0.1`. If you set `PUBLIC_URL` to `http://localhost:3000`, OAuth sign-in will fail because the redirect URI won't match the loopback client ID. Always use `http://127.0.0.1:3000` for local development.
-:::
+
## Example `.env`
diff --git a/packages/docs/content/docs/getting-started/deployment/docker.md b/packages/docs/content/docs/getting-started/deployment/docker.md
index 2ca804a..0032897 100644
--- a/packages/docs/content/docs/getting-started/deployment/docker.md
+++ b/packages/docs/content/docs/getting-started/deployment/docker.md
@@ -18,9 +18,9 @@ cp .env.example .env
Edit `.env` and set at least `PUBLIC_URL` (e.g. `http://127.0.0.1:3000`) and `SESSION_SECRET` (at least 64 characters). The defaults work for everything else. See [Configuration](../configuration.md) for the full list of environment variables.
-:::warning[Use 127.0.0.1, not localhost]
+
ATProto OAuth loopback clients are registered with `127.0.0.1`. If you set `PUBLIC_URL` to `http://localhost:3000`, OAuth sign-in will fail because the redirect URI won't match the loopback client ID. Always use `http://127.0.0.1:3000` for local development.
-:::
+
## 2. Start the stack
@@ -39,9 +39,9 @@ HappyView runs migrations automatically on startup. The first build will take a
The `happyview` container serves its own bundled dashboard at `http://127.0.0.1:3000`, but that copy is baked in at container build time and only updates when you rebuild the image. For day-to-day development, use the dev dashboard at `http://127.0.0.1:3001` — it hot-reloads on changes to the `web/` source.
-:::tip
+
SQLite is the default and requires no extra services. To use Postgres instead, uncomment the `postgres` service in `docker-compose.yml` and update `DATABASE_URL` in `.env`. See the [database setup guide](../../guides/database/database-setup.md).
-:::
+
## Next steps
diff --git a/packages/docs/content/docs/getting-started/deployment/other.md b/packages/docs/content/docs/getting-started/deployment/other.md
index b048f28..69b12e3 100644
--- a/packages/docs/content/docs/getting-started/deployment/other.md
+++ b/packages/docs/content/docs/getting-started/deployment/other.md
@@ -26,9 +26,9 @@ PUBLIC_URL=http://127.0.0.1:3000
SESSION_SECRET=change-me-in-production
```
-:::warning[Use 127.0.0.1, not localhost]
+
ATProto OAuth loopback clients are registered with `127.0.0.1`. If you set `PUBLIC_URL` to `http://localhost:3000`, OAuth sign-in will fail because the redirect URI won't match the loopback client ID. Always use `http://127.0.0.1:3000` for local development.
-:::
+
Or if you prefer Postgres:
diff --git a/packages/docs/content/docs/guides/api-clients.md b/packages/docs/content/docs/guides/api-clients.md
index 14d7341..23a5a7f 100644
--- a/packages/docs/content/docs/guides/api-clients.md
+++ b/packages/docs/content/docs/guides/api-clients.md
@@ -29,9 +29,9 @@ Choose based on where your code runs:
| **Origin validation** | No | Yes — `Origin` must match `allowed_origins` |
| **PKCE required?** | No | Yes (S256) |
-:::tip
+
If your app has a backend that can securely store the client secret, use a confidential client even if the frontend is a browser app. The backend can proxy OAuth operations.
-:::
+
## Creating a client
diff --git a/packages/docs/content/docs/guides/database/sqlite-to-postgres-migration.md b/packages/docs/content/docs/guides/database/sqlite-to-postgres-migration.md
index edfead4..c9c6e46 100644
--- a/packages/docs/content/docs/guides/database/sqlite-to-postgres-migration.md
+++ b/packages/docs/content/docs/guides/database/sqlite-to-postgres-migration.md
@@ -59,9 +59,9 @@ Since HappyView indexes records from the atproto network, the simplest way to po
Backfill fetches all records fresh from the network, so no data transfer between databases is needed.
-:::tip
+
If you have many lexicons, you can export them from the old instance before switching. Use `GET /admin/lexicons` to list them and `POST /admin/lexicons` to re-upload after switching to Postgres.
-:::
+
## Step 6: Re-create admin settings
diff --git a/packages/docs/content/docs/guides/event-logs.md b/packages/docs/content/docs/guides/event-logs.md
index b1354d5..848d5cf 100644
--- a/packages/docs/content/docs/guides/event-logs.md
+++ b/packages/docs/content/docs/guides/event-logs.md
@@ -36,9 +36,9 @@ Logged when records are received from Jetstream and stored or removed from the l
Logged when Lua scripts run for XRPC query or procedure endpoints. Script errors capture the full context needed to reproduce and debug the issue: the error message, the complete Lua script source, the input that triggered it, and the caller's DID.
-:::note
+
For query scripts (unauthenticated), `caller_did` and `input` are omitted from the detail since queries don't have an authenticated user or request body.
-:::
+
### User events
diff --git a/packages/docs/content/docs/guides/lexicons.md b/packages/docs/content/docs/guides/lexicons.md
index 85c1354..9a34863 100644
--- a/packages/docs/content/docs/guides/lexicons.md
+++ b/packages/docs/content/docs/guides/lexicons.md
@@ -25,9 +25,9 @@ For example, a query lexicon `xyz.statusphere.listStatuses` would set `target_co
See the [admin API](../../reference/admin/lexicons.md#upload--upsert-a-lexicon) for how to set `target_collection` when uploading.
-:::note
+
The `target_collection` is available in Lua scripts as the `collection` global, but it is not required if your endpoint uses a Lua script.
-:::
+
## Backfill flag
@@ -53,9 +53,9 @@ Lexicons are stored as records themselves with the `com.atproto.lexicon.schema`
4. Parse the TXT record for a `did=` value.
5. Resolve the DID to a PDS endpoint via the PLC directory.
-:::note
+
The spec states that resolution must be **non-hierarchical**. Each authority requires its own explicit TXT record. If you have multiple levels of authority (e.g. `xyz.statusphere.status` and `xyz.statusphere.actor.profile`), each level must have an explicit TXT record.
-:::
+
### Fetching
diff --git a/packages/docs/content/docs/sdk/oauth-client-browser/overview.md b/packages/docs/content/docs/sdk/oauth-client-browser/overview.md
index ef521da..7df0a09 100644
--- a/packages/docs/content/docs/sdk/oauth-client-browser/overview.md
+++ b/packages/docs/content/docs/sdk/oauth-client-browser/overview.md
@@ -46,9 +46,9 @@ const client = new HappyViewBrowserClient({
});
```
-:::note
+
The API client must be registered as a **public** client (no secret) with your app's origin in `allowed_origins`. See [Authentication — API clients](../getting-started/authentication.md#api-clients-confidential-vs-public).
-:::
+
## Sign in
@@ -84,9 +84,9 @@ const { authorizationUrl, did, state } =
await client.prepareLogin("alice.bsky.social");
```
-:::note
+
`login()` still works as an alias for `signInRedirect()`.
-:::
+
### What happens during sign in
@@ -135,9 +135,9 @@ const session = await client.restore("did:plc:abc123");
Calling `restore()` with no arguments returns the last active session, or `null` if none is found.
-:::note
+
`callback()` still works as a standalone method that processes the OAuth callback and returns a session directly.
-:::
+
## Detecting callback params
@@ -191,9 +191,9 @@ Pass a relative path (prepends the HappyView instance URL) or a full URL (used a
await client.revoke(session.did);
```
-:::note
+
`logout()` still works as an alias for `revoke()`.
-:::
+
## Resolution utilities
@@ -286,9 +286,9 @@ Or from the session itself:
await session.signOut();
```
-:::note
+
`logout()` still works as an alias for `revoke()`.
-:::
+
## Identity resolution
diff --git a/packages/docs/content/docs/sdk/oauth-client/overview.md b/packages/docs/content/docs/sdk/oauth-client/overview.md
index 1b13b20..f793540 100644
--- a/packages/docs/content/docs/sdk/oauth-client/overview.md
+++ b/packages/docs/content/docs/sdk/oauth-client/overview.md
@@ -148,9 +148,9 @@ interface StorageAdapter {
If no `StorageAdapter` is provided, sessions are stored in memory and won't survive page reloads or process restarts.
-:::note
+
The built-in `MemoryStorage` is exported for testing. In production, always provide a persistent storage adapter.
-:::
+
## Error handling
diff --git a/packages/docs/content/docs/tutorials/statusphere.md b/packages/docs/content/docs/tutorials/statusphere.md
index 01c957d..26541ac 100644
--- a/packages/docs/content/docs/tutorials/statusphere.md
+++ b/packages/docs/content/docs/tutorials/statusphere.md
@@ -6,9 +6,9 @@ title: "Statusphere"
In this tutorial, you'll set up HappyView to act as the AppView for Statusphere. By the end, you'll have indexed records and working XRPC endpoints.
-:::tip
+
This tutorial assumes you have a running HappyView instance. If you don't, start with the [Quickstart](../getting-started/deployment/railway.md) or one of the local development guides ([Docker](../getting-started/deployment/docker.md), [from source](../getting-started/deployment/other.md)).
-:::
+
## The Statusphere lexicon
@@ -33,7 +33,7 @@ First, tell HappyView to start indexing Statusphere records. Since `xyz.statusph
HappyView now subscribes to `xyz.statusphere.status` via Jetstream and kicks off a backfill job to index historical records.
-:::tip
+
You can also add lexicons via the [admin API](../reference/admin/lexicons.md). This is useful for automation or CI/CD workflows:
```sh
@@ -63,7 +63,7 @@ curl -X POST http://127.0.0.1:3000/admin/lexicons \
}'
```
-:::
+
## Step 2: Verify records are being indexed