GlassKit

Stack overview

What's in the box across all three workspaces, why each piece is there, and how the pieces fit together.

GlassKit is a pnpm + Turborepo monorepo with three workspaces: the glasses app, the companion site, and the shared backend. The stack is opinionated; every choice trades flexibility for shipping speed. This page explains why each piece is there so you can decide what to keep, swap, or extend.

The big picture

Vite + React 19: the 600×600 Meta Web App
Next.js 16: the companion site
Convex: shared backend (both surfaces talk to it)

The split exists because Meta Web Apps are 600×600, D-pad-navigated, and can't run a checkout form, but you still need a real keyboard surface for sign-up and billing. The glasses app and the companion site share one Convex deployment, so a purchase on the companion unlocks entitlement on the glasses.

Toolchain

pnpm + Turborepo. pnpm-workspace.yaml declares the four workspaces; turbo.json defines the dev / build / typecheck / lint / test pipelines with ^build dependencies so workspace deps build first. Vercel auto-detects both files; remote caching is one click. See the Quickstart for the install path.

Frontends

app/: Vite + React 19 + TypeScript

The Meta Ray-Ban Display Web App. Vite (not Next.js) because the glasses surface is a single 600×600 screen with no routing, so a heavyweight framework is wrong; Vite gives you a fast static build that any HTTPS host can serve.

Primitives in app/src/lib/:

  • <GlassViewport>: the 600×600 surface (additive display: black is transparent).
  • useDpad(): the Neural Band / D-pad spatial focus system. Any element with .focusable joins navigation; arrow keys move focus, Enter activates.
  • useDeviceOrientation · useDeviceMotion · useGeolocation · useNeuralBand: W3C sensor APIs wrapped as React hooks.
  • useAi() + <AiResponse>: calls the Convex api.ai.complete action; provider key stays server-side.

Six fork-ready example apps in app/src/apps/ pair each sensor mix with an AI prompt: AI Wayfinder, Tour Guide, Running Coach, Sous-Chef, Presentation Coach, Trail Companion.

companion/: Next.js 16 + Tailwind v4

The keyboard-and-mouse half of the product: sign-up, checkout, account dashboard, marketing home.

Next.js 16 is not the Next.js most LLMs were trained on

APIs differ from Next.js 14. If you ask an AI assistant to write Next.js code here, point it at node_modules/next/dist/docs/ first.

Key changes:

  • params / searchParams in pages are async, so await them.
  • cookies(), headers(), draftMode() return Promises.
  • fetch no longer caches by default; opt in explicitly.

Styling is Tailwind v4 with @theme brand tokens in companion/app/globals.css: --color-bg, --color-surface*, --color-rule, --color-accent. Rebrand by editing those tokens; every utility class updates automatically. No tailwind.config.js.

Backend

packages/backend/: Convex with components

Convex is a TypeScript-native real-time backend: schema, queries, mutations, actions, HTTP routes, scheduled jobs, file storage, all deployed with convex deploy. Queries are reactive: any UI that reads a query auto-updates when the underlying data changes.

GlassKit leans on official Convex components so a fresh clone needs almost no hand-wiring. Registered in convex.config.ts:

ComponentWhat it is
@convex-dev/stripeCheckout, customer portal, subscription / payment sync, webhooks
@convex-dev/resendTransactional email: queued, batched, rate-limited, durable delivery
@convex-dev/rate-limiterNamed rate limits (sign-up, checkout, AI prompts)

App-level files in packages/backend/convex/:

FileWhat it does
schema.tsJust users (identity, synced from Clerk). Each component owns its own tables.
auth.config.tsClerk JWT as the Convex identity provider
users.tscurrent + upsertFromClerk
stripe.tscreateCheckout, createPortalSession, cancelSubscription, reactivateSubscription, getMyAccount
entitlements.tsgetMyPlan, derived live from the Stripe component
email.tsResend sendWelcomeEmail (internal, scheduled by upsertFromClerk)
ai.tscomplete, via Vercel AI SDK, Anthropic or OpenAI
rateLimiter.tsNamed limits + a helper mutation for actions
http.tsStripe + Resend webhook routes

First-run note

convex/_generated/ doesn't exist until you run pnpm exec convex dev once against a real deployment. Until then, any api import in the apps won't typecheck. See the Quickstart for the step.

Identity: Clerk

Used on both apps. The companion site is where users sign up (<SignIn /> / <SignUp /> Clerk components, since you can't type a password with a D-pad). The glasses app picks up the session via @clerk/clerk-react's useAuth.

Convex verifies the Clerk-issued JWT via ConvexProviderWithClerk, so every useQuery / useMutation / useAction is automatically authenticated. The Clerk JWT template must be named exactly convex.

Billing: Stripe (via the Convex component)

Not a hand-rolled lib/stripe.ts. The component handles checkout, the customer portal, cancel/reactivate, and webhook-driven sync of customers / subscriptions / payments / invoices into its own tables. The companion's <CheckoutButton> calls stripe.createCheckout (action), the dashboard reads stripe.getMyAccount (query).

Full setup: Payments add-on.

Email: Resend (via the Convex component)

Queued, batched, rate-limited, durable delivery. The boilerplate sends a welcome email when a new user is upserted; expand by calling resend.sendEmail(ctx, ...) from any Convex function.

AI: Vercel AI SDK in a Convex action

api.ai.complete is an action that calls generateText from the Vercel AI SDK. Provider toggles via AI_PROVIDER env (Anthropic default; openai switches). Rate-limited per user via the aiPrompt token bucket.

Full setup + how to extend: AI Showcase add-on.

Why this shape

  • One Convex deployment behind both apps means entitlements, user state, and AI responses sync automatically. There is no API layer between the apps and the data.
  • The companion is the only place users type. Sign-up, card details, password changes: everything that wants a keyboard lives there. The glasses app picks up the session.
  • Components > hand-rolled Stripe. A v0.1.x component takes a little trust, but the alternative is rewriting webhook signature verification + sync code for every project, forever.
  • No proprietary SDK on the glasses side. Meta Web Apps are pure web standards: keyboard events, W3C sensors, Geolocation, localStorage. The useDpad, useDeviceMotion, etc. hooks are thin React wrappers.

CI

GitHub Actions runs pnpm install --frozen-lockfile, then typecheck and build across every workspace, gated on _generated/ being committed (until that's done, CI stays green with a friendly notice rather than red errors).

On this page