Use Seige for this browser app's sign-in, data and files, and for AI only if a feature needs it. Docs: https://seige.ai/developers/, https://seige.ai/developers/sdk-reference.md, https://seige.ai/llms.txt. Setup: if there is no project yet, create one with npm create vite@latest my-app -- --template vanilla (or react), then cd my-app and npm install. Then run npm install @seige_sdk/sdk. Vite 5 to 8, Next.js 16 (Turbopack or webpack) and webpack 5 need no SDK configuration. In Next.js, use the SDK only in "use client" components and call it from event handlers or effects. Create one client: const seige = createSeige({ appName: "My app" }) from @seige_sdk/sdk. No client ID, secret, callback page or registration: Seige identifies the app by its website. createSeige({ issuer }) points at another Seige environment, such as a test one (default https://account.seige.ai/oauth). Do not build login screens, token storage, encryption, a backend or a database server. Results: every call returns { data, error } and never throws, except seige.sync.status() and seige.sync.resources() (plain values), seige.auth.onChange() (returns an unsubscribe function) and seige.close(). error.code is one of ACCESS_DENIED, SIGN_IN_CANCELLED, POPUP_BLOCKED, REAUTH_REQUIRED, PERMISSION_REVOKED, PERMISSION_DENIED, NETWORK_UNAVAILABLE, RATE_LIMITED, ALLOWANCE_EXCEEDED, CONFLICT, IN_USE, MIGRATION_FAILED, CONSENT_REQUIRED, UNSUPPORTED, OPERATION_FAILED, or a query's Postgres SQLSTATE (23505 unique, 23502 not null, 23503 foreign key, 42P01 no table, 42703 no column) or PGRST116 (single() did not match one row). error.message is the specific reason and error.hint tells the developer what to do. Log code, message and hint. Show users text chosen by code and name only buttons the app has, for example: - SIGN_IN_CANCELLED or ACCESS_DENIED: "Not connected to Seige. To try again, select ." ACCESS_DENIED with message "ai_access_denied" means only AI was declined; the app stays connected. - NETWORK_UNAVAILABLE: "Seige is unreachable. Changes you saved stay on this device and are sent when the connection returns." - RATE_LIMITED: "Too many requests right now. Try again in a few minutes." ALLOWANCE_EXCEEDED: "Your Seige account is out of storage or AI allowance." - IN_USE: "This app is saving in another browser or tab. Try again in a moment." - CONFLICT: "This changed on another device while you were offline. Your changes are kept on this device." Add how to review them only if the app has that screen. Full table with every code: https://seige.ai/developers/launch-checklist/#errors Capabilities: seige.capabilities({signal?}) checks service support without sign-in or loading keys. It returns {data:{familySharing,calendar},error}. SDK 0.7.0 supports familySharing:{version:1} through actual households and shared APIs; calendar remains null and there is no Calendar client. A capability is not user consent. Household management and per-installation workspace consent open first-party Seige windows from user actions. Keep personal workflows working and handle network errors separately from unsupported capabilities. Family workspaces: https://seige.ai/guides/share-a-family-workspace/. Use households.manage/list and shared.create/list/requestAccess/open. Membership alone grants no app access. Each person signs in separately and approves their own installation. Handle reads verify encrypted records and expose authoritative scoped record.memberId. Mutations need a stable mutationId and expectedRevision; inspect published/conflict/pending/reauthentication_required rather than claiming immediate publication. Subscribe returns a Result containing an AsyncIterable of opaque hints; reread verified data and catch iteration errors. Keep personal and shared storage separate. Removal freezes publication; owners rotate keys, then installations reapprove. Offer explicit review/export/discard for encrypted pending edits. Connecting: the first call that needs the user opens a Seige window where they sign in and approve; the call then continues. Browsers open windows only from clicks, so a call made without one (for example a read on page load) first shows a "Continue with Seige" card. On load, run const { data: user } = await seige.auth.user(); it never asks. data is null or { id } (id is specific to this website), plus email and emailVerified only after seige.auth.signIn({ email: true }) was approved; there is no username or display name. When it is null, show a "Sign in with Seige" button that calls seige.auth.signIn(). seige.auth.onChange(({ user }) => ...) reports later sign-in, sign-out, session end and other tabs, not the current state. seige.auth.signOut() ends this browser's session and keeps data; call seige.sync.flush() first. While the window is open the SDK shows a card with a connection badge (an emoji) that the Seige window repeats so the user can check they match; do not cover it. A sign-in started more than about three minutes after page load logs one failed 400 request (use_dpop_nonce) in the console: it is expected, and the SDK retries with a fresh nonce. Do not send Cross-Origin-Opener-Policy: same-origin (use same-origin-allow-popups or none). A Content-Security-Policy needs the Seige origin in connect-src, 'wasm-unsafe-eval' in script-src and worker-src 'self'. Data: seige.from(table) with select, insert, update, upsert, delete, filters (eq, neq, gt, gte, lt, lte, in, like, ilike, is, or) and order, limit, range, single, maybeSingle. Tables are created on first insert with an id and created_at; new keys become columns. Add SQL migration files (createSeige({ migrations })) only for unique or required constraints; a text primary key named id is filled with a UUID, reading an unknown table then returns 42P01, and an applied migration must never be edited (add a new one). A query reads one table: join in the app. update() and delete() need a filter. Write no access policies; each user's database is theirs. Files: seige.files.put(path, file), get(path) (data is a Blob), list(prefix) and delete(path) (permanent), with relative paths. AI: seige.ai.generate({ prompt, input, maxOutputTokens, signal }) returns { data: { text, usage } }; the first call asks the user for AI access. Send only the input the feature needs, offer cancel, show seige.ai.usage() limits and never retry automatically. Sync: writes are encrypted and saved in this browser first. A seige.from() write resolves after Seige has it when Seige is reachable; seige.files.put() resolves at once and uploads in the background. Offline, writes still resolve and are sent after reconnection. seige.sync.flush() resolves when everything reached Seige (NETWORK_UNAVAILABLE, CONFLICT, IN_USE or ALLOWANCE_EXCEEDED otherwise). seige.sync.status() returns "synced", "syncing", "offline", "needs_attention" or "reauthentication_required", reports "synced" before the first call and has no event: read it after writes and on an interval, and show it. A write made while another browser is writing waits up to about 45 seconds, then fails with IN_USE without saving: keep the user's input and offer to retry. If the page closes while the write waits, the write is kept in this browser and runs on the newest version the next time the app opens there; it can then surface through seige.sync as CONFLICT when this browser also had unpublished changes. For a conflict (resource reason "conflict"), const { data: conflict } = await seige.db.getConflict(); then conflict.resolve(async ({ remote, pending }) => { for (const row of await pending.all("SELECT * FROM ")) await remote.run("INSERT OR IGNORE INTO
() VALUES ()", ...values) }) keeps both versions; resolve("discard-local") drops this browser's changes and needs the user's choice. Never overwrite newer work. seige.close() stops the client (in tests or on teardown) without signing out. Ask for other folders with seige.request({ resource: "folder", id, access: ["read"] }) only when a feature needs them. Keep formats portable; disconnecting an app is separate from deleting data. Verify before calling the integration complete: - In a fresh browser profile, the first data call opens the Seige window (or the "Continue with Seige" card if it was not from a click). After approval, a row, and a file if the app stores files, are saved and read back. - Reload: the session and data return without asking again. - Cancel in the Seige window returns ACCESS_DENIED, closing it returns SIGN_IN_CANCELLED, and the app stays usable. - With the network blocked, a write still succeeds and is sent after reconnection. - If AI is used: the first AI call asks for access, and an answer, usage and cancel work. There is no Seige test mode or mock; automated tests drive the real Seige window (https://seige.ai/developers/testing/). Report the commands and results, and do not report mocked storage as saved data.