Paste your design tokens — Figma variables export, CSS custom properties, or a Tailwind config — get a live preview and exportable theme code (Tailwind, Chakra, shadcn/ui). Save a conversion and get a shareable link back to it.
Built by Rich Tillman — creator of ForgeKit.
This app is built on React Router in framework mode (loaders, actions, SSR) —
the current, Remix-team-recommended path for a new Remix-style app. Remix and React
Router merged; new projects start on React Router now rather than legacy Remix v2,
and it's not the unrelated non-React Remix v3 rewrite. The home route's loader +
action round-trip (see app/routes/home.tsx) is a genuine server-side data flow,
not a client-rendered stub — that round-trip is what the Save & Share feature later
builds on for real.
tokencast.vercel.app — paste tokens, get a live preview and export code, save a conversion and get back a real, server-rendered shareable link. Lighthouse: 100 accessibility, 99 performance.
- React Router (framework mode: SSR, loaders, actions, file-based routes)
- React 19, TypeScript (strict)
- Tailwind CSS 4
- Vitest (unit + live integration tests) + Testing Library + Playwright (e2e)
- ESLint + Prettier
- Supabase (Save & Share persistence)
- Deployed on Vercel
pnpm install
cp .env.example .env # public anon key + project URL, safe to copy as-is
pnpm dev # http://localhost:5173
pnpm build # production build
pnpm start # run the production server locally
pnpm typecheck # react-router typegen && tsc
pnpm lint # eslint .
pnpm test # vitest run — includes live Supabase integration tests
pnpm test:e2e # playwright testThe home route's action persists a parsed token model to Supabase and returns
a shareable slug; /t/:slug's loader server-loads it back for anyone visiting
the link cold — see app/routes/home.tsx and app/routes/share.tsx.
No direct table access, ever. Both token_shares and
tokencast_share_rate_limit have RLS enabled with zero policies — not
even a scoped one. The only way the app's anon key can touch either table is
through two SECURITY DEFINER RPCs (supabase/migrations/):
create_token_share(payload, client_ip)— validates payload size (also enforced by a DBCHECKconstraint as defense in depth beyond the app-layer check inapp/lib/tokens/validate-model.ts), rate-limits by IP (5 saves per 60s, tracked intokencast_share_rate_limit), inserts, returns a slug.get_token_share(slug)— returns exactly one row by exact slug match. There is no anonSELECTpolicy on the table itself, so the anon key cannot list or enumerate other users' saved shares — only fetch one by its exact slug.
Sanitization. Token names/values are stripped of <>"' (see
app/lib/tokens/sanitize.ts) before ever being persisted — defense in depth
on top of React's default JSX escaping, so a token named
<script>alert(1)</script> is inert in storage, in the live preview, and in
every generated export string.
Rate limiting is Supabase-backed (a table + a per-IP count check inside the RPC), not in-memory — in-memory counters don't survive across Vercel's serverless function instances, so they'd silently stop working in production.
Data retention. Saved shares and rate-limit rows aren't cleaned up
automatically yet. The policy (see the migration files): delete
token_shares older than 30 days, tokencast_share_rate_limit rows older
than 1 day. Enforcing it is a follow-up — wire the two delete statements in
supabase/migrations/20260708200000_create_tokencast_share_tables.sql into a
Supabase Cron job.
This project deploys to a Vercel-issued *.vercel.app URL automatically. To point
tokencast.richtillman.xyz at it:
- In the Vercel project's Settings → Domains, add
tokencast.richtillman.xyzand copy the CNAME target Vercel gives you. - In Cloudflare (where
richtillman.xyzis managed), add a CNAME record:- Name:
tokencast - Target: the value from step 1 (typically
cname.vercel-dns.com) - Proxy status: DNS only (grey cloud), so Vercel's own TLS/CDN handles it
- Name:
- Wait for DNS propagation, then verify the domain in Vercel's dashboard.
This step isn't automated — no Cloudflare access is wired into this build.
