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
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.focusablejoins navigation; arrow keys move focus, Enter activates.useDeviceOrientation·useDeviceMotion·useGeolocation·useNeuralBand: W3C sensor APIs wrapped as React hooks.useAi()+<AiResponse>: calls the Convexapi.ai.completeaction; 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/searchParamsin pages are async, soawaitthem.cookies(),headers(),draftMode()return Promises.fetchno 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:
| Component | What it is |
|---|---|
@convex-dev/stripe | Checkout, customer portal, subscription / payment sync, webhooks |
@convex-dev/resend | Transactional email: queued, batched, rate-limited, durable delivery |
@convex-dev/rate-limiter | Named rate limits (sign-up, checkout, AI prompts) |
App-level files in packages/backend/convex/:
| File | What it does |
|---|---|
schema.ts | Just users (identity, synced from Clerk). Each component owns its own tables. |
auth.config.ts | Clerk JWT as the Convex identity provider |
users.ts | current + upsertFromClerk |
stripe.ts | createCheckout, createPortalSession, cancelSubscription, reactivateSubscription, getMyAccount |
entitlements.ts | getMyPlan, derived live from the Stripe component |
email.ts | Resend sendWelcomeEmail (internal, scheduled by upsertFromClerk) |
ai.ts | complete, via Vercel AI SDK, Anthropic or OpenAI |
rateLimiter.ts | Named limits + a helper mutation for actions |
http.ts | Stripe + 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).