···5353<script src="https://cdn.jsdelivr.net/gh/bigmoves/quickslice@v0.17.3/quickslice-client-js/dist/quickslice-client.min.js"></script>
5454```
55555656-### 3. OAuth flow
5656+### 3. the UI
57575858-quickslice handles the OAuth server side. the frontend just needs to:
5858+since quickslice serves its own admin UI at the root path, we host our frontend separately on cloudflare pages. the frontend is vanilla JS - no framework, just a single `app.js` file.
5959+6060+**OAuth with quickslice-client-js**
59616060-1. create a client with `QuicksliceClient.create()`
6161-2. call `client.signIn()` to start the flow
6262-3. handle the callback (quickslice redirects back with auth tokens)
6363-4. use `client.agent` for authenticated AT protocol operations
6262+the `quickslice-client-js` library handles the OAuth flow in the browser:
6363+6464+```javascript
6565+const client = await QuicksliceClient.create({
6666+ server: 'https://zzstoatzz-quickslice-status.fly.dev',
6767+ clientId: 'client_2mP9AwgVHkg1vaSpcWSsKw',
6868+ redirectUri: window.location.origin + '/',
6969+});
7070+7171+// start login
7272+await client.signIn(handle);
7373+7474+// after redirect, client.agent is authenticated
7575+const { data } = await client.agent.getProfile({ actor: client.agent.session.did });
7676+```
64776578the redirect URI is just the root of your site (e.g., `https://status.zzstoatzz.io/`).
66798080+**GraphQL queries**
8181+8282+quickslice auto-generates a GraphQL API from your lexicons. querying status records looks like:
8383+8484+```javascript
8585+const response = await fetch(`${CONFIG.server}/api/graphql`, {
8686+ method: 'POST',
8787+ headers: { 'Content-Type': 'application/json' },
8888+ body: JSON.stringify({
8989+ query: `
9090+ query GetStatuses($did: String!) {
9191+ ioZzstoatzzStatusRecords(
9292+ where: { did: { eq: $did } }
9393+ orderBy: { createdAt: DESC }
9494+ first: 50
9595+ ) {
9696+ nodes { uri did emoji text createdAt }
9797+ }
9898+ }
9999+ `,
100100+ variables: { did }
101101+ })
102102+});
103103+```
104104+105105+no need to write resolvers or schema - it's all generated from the lexicon definitions.
106106+67107## problems we hit
6810869109### the `sub` claim fix
···134174β AT Protocol β
135175β (bluesky PDS, jetstream firehose) β
136176βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
177177+```
178178+179179+## what quickslice eliminated
180180+181181+the rust backend was ~2000 lines of code handling:
182182+183183+- OAuth server implementation (PKCE + DPoP)
184184+- jetstream consumer for firehose ingestion
185185+- custom API endpoints for reading/writing statuses
186186+- session management
187187+- database queries
188188+189189+with quickslice, all of that is replaced by:
190190+191191+- a Dockerfile that builds quickslice from source
192192+- a fly.toml with env vars
193193+- two secrets
194194+195195+the frontend is still custom (~1200 lines), but the backend complexity is gone.
196196+197197+## deployment checklist
198198+199199+when deploying quickslice:
200200+201201+```bash
202202+# 1. set required secrets
203203+fly secrets set SECRET_KEY_BASE="$(openssl rand -base64 64 | tr -d '\n')"
204204+fly secrets set OAUTH_SIGNING_KEY="$(goat key generate -t p256 | tail -1)"
205205+206206+# 2. deploy (builds from source, takes ~3 min)
207207+fly deploy
208208+209209+# 3. in quickslice admin UI:
210210+# - set domain authority (e.g., io.zzstoatzz)
211211+# - add supported lexicons
212212+# - register OAuth client with redirect URI
137213```
138214139215## key takeaways