Open comments for this post
monkeyspeak devlog more fixes (leaderboard + home + cors bug) - ship soon trust ..
tl;dr
- home page is a 3 column layout now — leaderboard on the left, hero in the middle, top score on the right
- global leaderboard via supabase — nickname + emoji after a run, no signup, same board for everyone on the site
- personal bests still local — top score card shows your best for the duration, not whoever is #1 globally
- supabase wired on vercel with service role key server side only (
/api/leaderboard get + post)
- cors scream from chrome fixed — browser no longer fetches render root directly for proxy health checks
what i changed
home page layout
the thing ig
what it does
hero leaderboard
duration tabs synced with config bar, crown svg for top spots, emoji avatars, your row pinned at the bottom even if you are not top 5
leaderboard save prompt
pops after a speed run — pick name + icon (defaulted to hehe 🐵), saves to supabase, remembers name locally for next time
top score card
personal best for current duration + prompt type only
visual cleanup
removed hero doodles, tightened title spacing, consolidated duplicate hero css
leaderboard rows used to live in zustand localStorage. ripped that out — supabase is source of truth now. name and emoji prefills still persist locally.
global leaderboard backend
- new table
leaderboard_entries in supabase (migration in supabase/migrations/001_leaderboard_entries.sql)
- rls enabled, no anon policies — all reads/writes through next.js with
SUPABASE_SERVICE_ROLE_KEY
- upsert rule matches old local behavior: same name + duration + prompt type (case insensitive) only updates if wpm goes up
- light rate limit on post (~30s per ip) — good enough for hobby scale, not fortress grade
browser → GET/POST /api/leaderboard (vercel)
↓ service role
supabase postgres
render backend unchanged — still deepgram only, no db env vars there.
cors fix (chrome (ofc brave as well) was mad again)
production deepgram mode probes whether the render proxy is alive before connecting. that probe used to be a cross origin fetch at https://monkeyspeak.onrender.com/ from the vercel app.
render cold starts and error pages often ship without cors headers even when express has origin: * — so chrome logged the whole blocked by cors policy thing and deepgram mode thought the proxy was dead.
fix: new same origin route GET /api/deepgram/proxy-health on vercel. server checks render, browser never touches render over http cors. also slapped explicit options handling on the render backend for anything that still hits it directly.
what’s next (maybe)
- signed run tokens so leaderboard posts are tied to an actual finished test
- shared rate limit (redis/kv) if spam shows up
- delete the testmonkey row sitting on prod from smoke testing
- render keep alive still on the list from v0.1
- preview env on vercel for prs
ok that was a lot of infra for a monkey with a crown svg but at least the board is real now. lmk in replies if you want the supabase dashboard walkthrough or the security fixes implemented properly.
Open comments for this post
monkeyspeak final devlog before ship ( hopefully )
tl;dr
- so brave and edge broke everything ( ye read what broke for more info )
- deepgram mode now transcribes live text again (words dissolve, wpm moves, momentum still reacts to your voice)
- brave and edge no longer hang for 25 seconds then fake a mic error
- production uses a render websocket proxy + vercel env, same path that worked locally
- github: nothariharan/monkeyspeak (commit
095b731 and earlier speech routing work on main)
- live: monkeyspeak-delta.vercel.app
what i changed
speech routing (back to something that made sense)
mode
behavior
browser
web speech api only. no deepgram hijack on brave/edge mount.
deepgram
try deepgram first (proxy → bridge fallback on chrome only). if that fails, fall back to web speech with a clear error.
the ui now shows errors for the provider that actually failed, not a generic “mic blocked” when stt died for other reasons.
deepgram client hardening
- prefer
var name ur wish websocket proxy over the vercel http bridge
- brave/edge without a reachable proxy: fail fast with a useful message (no 25s timeout)
-
utterance_end_ms locked to 1000 everywhere (deepgram rejects live ws with 400 below that)
- bridge watchdog clears on
BridgeReady, not on first random chunk
- server bridge waits for upstream deepgram before closing the socket
the transcript fix (the big one)
- client parses deepgram json whether it arrives as a string, blob, or arraybuffer
- render proxy forwards deepgram replies as utf-8 text frames instead of opaque binary
infra
-
backend/ express + ws proxy deployable on render (render.yaml included)
-
set up env with ur var name at vercel
-
redeployed frontend via vercel cli after env update
small polish
- clearer vad fallback logs (worker load fail vs no voice detected vs timeout)
- config bar hint on brave/edge when browser mode is selected
- momentum sprites + gsap monkey states (from earlier in the sprint)
what broke (and why it looked cursed)
-
brave and edge block or mishandle the vercel http audio bridge (duplex fetch upload), so deepgram connections hung ~25s and never returned transcripts.
-
those browsers also cannot open an authenticated websocket straight to api.deepgram.com, so they need the render ws proxy (wss://…/api/deepgram/proxy) instead of the chrome-friendly paths.
-
even after the proxy worked, deepgram sent json in binary ws frames and the client ignored anything that was not a string, so words never updated until we parsed blob/arraybuffer payloads.
what’s next (maybe)
- vendor ort wasm for vad so the worker stops complaining
- gsap scale split for clean console
- render keep-alive or paid instance if cold starts hurt demos
- preview env on vercel for pr deployments (production is wired today)
sorry if i yapped a lot and the fixes where actually slighly more techy stuff so i didnt wanna yap abt that as wel so yeah if u want to know lmk in replies :)
Open comments for this post
reworked the ui and got monkeyspeak ready for v0 ship launch :))
it’s basically monkeytype but for your voice — read a prompt out loud, get scored on speed + clarity.
features:
- speed mode: timed speaking tests (15s / 30s / 60s / 120s) with live wpm + word accuracy
- clarity mode: paste your transcript, get a word-by-word diff + letter grade
- click the monkey mascot to start (no separate mic button anymore lol)
- animated monkey companion that reacts to your speaking energy mid-test
- words dissolve on screen as you say them correctly
- personal bests saved locally
- browser speech api works out of the box, optional deepgram for better stt
- themes, accent colors, custom fonts
- keyboard shortcuts (enter to start, tab to reset, escape to stop)
built with next.js, typescript, gsap, zustand.
repo: github.com/nothariharan/monkeyspeak
Open comments for this post
made the ui more clean still settling the latency here and there
current new approach i am building around is using the deepgram api key for initial purpose later trying to replicate what they do
open to questions or recommendations for the whole stt etc.
Open comments for this post
cooking up 🚀🔥
soon making it public just lots of latency issue in speaking to text conversion who knew it would be a pain but yeah let me know if u guys think of an alternative for it or anything at all
thanks for reading :)
Open comments for this post
going with a much better ui soon launching :) 🚀
for yall to try it
Open comments for this post
built the first iteration of the website !
got lots of customizing options now i am working more towards reducing the latency between what u speak and what is being transcribed
give me your opinions and recommendations :)