Package the Frame Control web UI as an installable Electron Mac app and bring in the tooling built alongside it. - app/: Electron wrapper that starts ui/server.py on a free loopback port, hardened window (sandbox, no navigation, runAsNode fuse off), login-shell PATH so Homebrew tools work from Finder, first-run offer to run connect.sh, ad-hoc signed DMG/zip via electron-builder. - ui/: headset view (OpenVR screenshots), device status, library, Steam "Get games" (owned games, install, store search), Android apps as persistent Lepton instances with a rated F-Droid catalogue and a private compatibility database, Android display controls over ADB, file and clipboard transfer, Flatpaks, remote and power actions. - apk-catalog/, compat-db/, frame/: catalogue build pipeline, Lakebed capsule for compatibility reports, Frame-side launchers. - tests/ and CI: server guard and validation tests plus Steam helper tests, run on Python 3.9 with script and app syntax checks. - Docs: README leads with the Mac app; new Android, panels, Steam games and field-notes docs; security notes on LAN-exposed ADB ports. Screenshot values for the headset's IP and Wi-Fi name are placeholders. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
7.8 KiB
Lakebed app instructions
Treat this capsule directory as the whole app. Use Lakebed's built-in APIs and CLI.
Limits to check first
- The public alpha is not production-ready.
- App code cannot use arbitrary npm packages or Node built-ins. Do not install app dependencies.
- Database fields support
string(),boolean(),number(),id(...), anduserId(). Chain.optional()or.default(value)on any field. - Local database data and uploaded files reset when the dev server restarts.
- Hosted server secrets and outbound server-side
fetchrequire a claimed deploy. - Unclaimed deploys expire. Use the expiry printed by the CLI. Claimed deploys do not expire.
App structure and APIs
server/index.tsexports the defaultcapsule()definition. Put server code inserver/. Import fromlakebed/serveror relative server and shared files.client/index.tsxexportsApp. Put client code inclient/. Import fromlakebed/client,preact,preact/hooks,preact/jsx-runtime,preact/jsx-dev-runtime, or relative client and shared files.- Keep
shared/pure TypeScript. Do not import DOM APIs, Node built-ins, env values, or Lakebed runtimes there. - In client code, use
import type app from "../server/index"andcreateClient<typeof app>()for typed queries, mutations, and actions. Query hooks returnundefineduntil the first result arrives. - Database calls are async. Await or return every database operation. Declare indexes with
.index(name, fields)and query withwithIndex. Useby_creationfor unfiltered creation-order queries. Do not use legacywhere,orderBy,limit, orall. - Queries and actions cannot write to the database. Use mutations or endpoints for writes. Filter user-owned data by the caller's
userIdand check ownership again before updates or deletes. - Guests get protected browser sessions without setup. Use
ctx.authon the server anduseAuth()on the client. A user ID is not a credential. Do not invent guest IDs or check their prefixes. - Use
ctx.auth.requireIdentity()for data that belongs to a guest or signed-in user. Usectx.auth.requireSignedIn()for account-only operations.isGuestandisSignedInare separate checks. Neither is true without a session. - Set
auth: { requireSignIn: true }incapsule()to block all app data operations until sign-in. Client UI checks alone do not protect data. On the client, gate data components oncanAccessApp()fromlakebed/client. - If
auth.errorblocks access, showretryAuth()and Google sign-in. Retry cannot renew an expired or revoked token for a pending guest upgrade. Keep data components unmounted until auth recovers. - Declare Lakebed user fields with
userId()fromlakebed/server, neverstring(). When a guest signs in, declareduserId()fields follow them to their account.userId()does not grant access. Keep owner filters and ownership checks. Make shared data intentional with a shared query, not a fake global user. - Use
auth.onGuestUpgradeonly for app-specific merge rules. It runs before automatic reference transfer in the same transaction. Plain strings, profile text, and external data do not transfer automatically. - Add Google sign-in with
SignInWithGoogleorsignInWithGoogle()fromlakebed/client. For custom endpoints, send the identity token fromgetIdentity().tokenin theX-Lakebed-Tokenheader.Authorizationbelongs to the app. Same-origin guest cookies work without that header. - Read server secrets through
ctx.env, with values in.env.lakebed.server. They are not available at build time. Never put secrets in client or shared code. Deploy sync replaces hosted env with the file contents after the deploy is claimed. - Use complete Tailwind class names in JSX. Lakebed compiles CSS automatically from client files and their imports. Use inline styles for values loaded at runtime. Do not add CSS files, CSS modules, PostCSS, or a separate Tailwind build step.
- Use the router from
lakebed/clientfor pages. There is no file-based routing. Useendpoint({ method, path }, handler)fromlakebed/serverfor webhooks and external HTTP clients. Request helpers includeheaders.get(name),query,json(),text(), andbytes(). - Static capsule assets are limited to the favicon. Use
favicon.svg,favicon.ico, or thefaviconoption incapsule(). Useclient.storagefor user uploads.
External data and dashboards
Use global fetch(url, options) inside a handler, not ctx.fetch. Queries, mutations, actions, and endpoints can fetch locally and on claimed deploys. A mutation or writable endpoint can fetch external data and write rows in the same call. Data does not need to pass through the browser. Fetch shares the handler time budget and can hold up other writes, so ingest one small batch per call.
Lakebed has no built-in scheduler or durable continuation queue yet. For periodic ingest, use an external scheduler to call a protected POST endpoint. Return a cursor for the caller to advance across separate requests. Keep auth.requireSignIn off for public reads and check an app secret in the ingest endpoint. CLI deploy tokens do not authenticate app endpoint callers.
Database read budgets apply to the whole handler. A loop over paginate() does not bypass them. For totals larger than one handler can read, maintain summary rows during ingest. Store timestamps with number() as epoch milliseconds. See the handler capability table, dashboard ingest example, and resource limits before planning a backfill.
Run and verify
Run commands from this capsule directory with npx lakebed.
Start dev in a terminal session that can stay open:
npx lakebed dev
Keep that process running. Edit the starter to build the requested app, then test its behavior at the URL printed by dev. Use another terminal to inspect logs and data:
npx lakebed logs --port 3000
npx lakebed db dump --port 3000
Use the dev server's port if it differs from 3000. Fix compile errors and runtime errors before deploying. Check user-owned data with separate browser profiles or the ?lakebed_guest=<name> local test override when the app stores private data. Named overrides are local test identities and cannot upgrade to an account.
Deploy and verify
After local checks pass, deploy from another terminal:
npx lakebed deploy
If the CLI requires a claim for server secrets or outbound fetch, follow its claim instructions and deploy again. A claim-required preview is not a working app.
Open the returned URL and test the requested behavior. Inspect the deployed app from this capsule directory, using its returned ID or URL:
npx lakebed inspect <deploy-id-or-url>
npx lakebed logs <deploy-id-or-url>
Hosted inspection is private by default. The CLI uses saved credentials. Report the working URL, the checks you ran, and the expiry if the deploy is unclaimed. Default app URLs use lakebed.app subdomains.
Read when needed
- For server and client API details, read the capsule API.
- For indexes and queries, read the database guide.
- For Google sign-in and identity, read the auth guide.
- For user uploads, read the storage guide.
- For claiming, domains, and other CLI commands, read the reference.
- For an older capsule using synchronous database calls, read the migration guide.
- For anything else, read the docs index. It lists every page and section so you can fetch only the one you need.