Leaderboard and TV fixes
July 24, 2026
- The leaderboard used to say 'No wins yet' while it was still loading, and gave up with a dead end if the connection hiccupped. It now tells you it's loading, and offers a Try again button when something goes wrong.
- The TV starts muted (browsers insist on it) — there's now a 🔇 Tap for sound button right on the screen instead of you having to hunt for the volume slider in the remote.
Search engines can finally find the whole site
July 24, 2026
- Our sitemap — the file that tells Google which pages exist — had been shipping empty. It now lists every public page plus the latest posts on The Plug, so new forum threads can actually turn up in search.
- The games test bench and the admin dashboards are marked 'don't index', so they stop showing up where they don't belong.
Cleaner front page
July 24, 2026
- The home page is faster off the line — it used to wait for your browser to build the whole thing before showing anything. Now it arrives ready.
- One footer instead of three, and it stays out of the way inside sessions and games.
- Your light/dark choice sticks between visits, and the 21+ check only asks once per visit instead of every time you tap a mode.
- The mode cards line up properly on every screen size, and there's a plain-English rundown of what Dope Vibes actually is down near the bottom — plus a straight link to the FAQ, which was weirdly hard to find before.
Weed Five Card is now High Card
July 19, 2026
- The five card draw table got a fresh name: High Card 🂠. Same game — ante up, swap your duds, best hand at showdown takes the grams. 2-5 players, rename only.
Weed Wars is now Plot the Pot
July 19, 2026
- Third name's the charm for the battleship board (Weed Battleship → Weed Wars → Plot the Pot 🔥). Hide your grow plots, firebomb your rivals', last farm standing wins — 2-4 player free-for-all, same leaderboard, your old wins still count. Rename only.
Weed Spades is now Buds & Bids
July 19, 2026
- The trick-taking table has a new sign over the door: Buds & Bids ♠️→🍁. Same cutthroat Spades — bid your tricks, follow the led suit, pot leaves beat everything, nil bids welcome. 2-4 players, rename only.
Weed Hold'em is now Texas Roll 'em
July 19, 2026
- The poker table got a proper name: Texas Roll 'em 🃏. Same Texas Hold'em rules, same grams on the line, same 2-6 player tables in chat and the Game Room — rename only.
Tic-Tac-Toe is now Tic-Tack-Toke
July 19, 2026
- Puff, puff, three in a row. Same 3x3 grid, same Xs and Os, same instant rematches — just finally wearing the name it always deserved. Rename only, nothing else changed.
Checkers is now Check Yo' Self
July 19, 2026
- Same board, same jumps, same 'king me' — new attitude. Checkers is now Check Yo' Self 🔴, because you really should before you wreck yo' self on a triple jump you didn't see coming. Rename only: your games, the rules, and the multi-jump chains all work exactly as before.
Per-Game Volume Control
July 19, 2026
- The 🔊 button on every game now has two sliders: one master volume for all games, and one just for the game you're playing — so you can keep Nug Warfare's theme song quiet while Weedopoly's dice stay loud. Each game remembers its own level, it stacks on the master knob, and it works everywhere games run. The Smuggler's Route also joined the games-with-sound club: every piece now lands with a satisfying thunk.
Chess is now The Smuggler's Route (The Retro High Seas)
July 19, 2026
- The chess board set sail for the 1970s coastline. Two smuggler fleets — the cream-teal-and-brass Sunset Fleet vs the rust-and-olive Midnight Fleet — face off across a sandy-and-seafoam grid, every piece redrawn as flat retro nautical art: Pawns are burlap-wrapped Bales drifting toward landfall (make the far shore and they're offloaded onto a fresh Mothership), Knights are Speedboats that cut hard corners and jump the sandbars, Bishops are Lighthouses sweeping long diagonal beams, Rooks are Hidden Coves anchoring the corners, the Queen is the Mothership dominating any lane, and the King is the Captain's Compass — lose it and your fleet is lost at sea.
- It's still real chess underneath — same moves, same checkmate, same 2-player duels in chat and the Game Room. Tap one of your vessels and the caption under the board tells you what it does. Check reads '🧭 Compass in danger!' now, which honestly hits harder.
Dots & Boxes is now Seeds and Stems
July 19, 2026
- The classic pencil-and-paper game got planted: the dots are now seeds 🌱, the lines you draw are green stems, and closing a plot grows a pot leaf where your box used to be. Same beloved steal-a-turn strategy, same 2-player duels in chat and the Game Room.
Shoot Me is now Joint Venture (The Jet)
July 19, 2026
- The tank arena traded its treads for wings. Joint Venture 🛩️ is fast, twitch-heavy, screen-wrapping dogfighting in the spirit of Atari Combat's jet battles: sleek minimalist paper planes and pre-roll rockets (with a lit cherry on the nose) that never stop flying. Carve turns with A/D, boost with W, brake with S — and every edge of the sky is seamless, so diving off the top brings you screaming out of the bottom.
- Your weapon is the Spark Trace: hold fire for a continuous stream of fast embers that burn out after traveling a third of the screen. No more long-range sniping — kills happen close and personal, leading your target through the wrap.
- One solid blocky obstacle sits dead center (a different silhouette every match). It absorbs shots — your only cover — and shreds any plane that flies into it.
- And the main event: Invisible Rockets mode. Host flips one toggle and every plane vanishes — you only flash into view for a split second when you fire or when you crash into the block. Winning means memorizing your own flight path as you wrap around the screen in total stealth. Up to 8 players, first to 10 kills, same leaderboard (your Shoot Me wins still count).
Ass is now Nug Warfare (The Strain Squads)
July 19, 2026
- Total glow-up: the cheeky Worms clone got reborn as Nug Warfare 🥦 — squads of 4 highly expressive little nugs in tiny hats. Pick your strain squad before the match: the Purple Urkle Outlaws (beanie + shades), the Sour D-moms (blue bucket hat), the Couch Locks (headphones, permanently sleepy), the Sativa Syndicate (camo cap, tactical vest, and dreads), or the White Widow Cartel (frosty). They blink, they grin, they panic when shells land close — take damage and bits of shake fly off; go down and your nug exhales one last puff of smoke and leaves a tiny pile of ash.
- New mobility: the Resin Rope (once per turn) flings a sticky strand along your aim line and zips your nug to the first ground it touches — great for escaping craters or storming a hill. And when the terrain gets blown out from under you, a Fan Leaf Parachute pops open and drifts you gently down.
- Three legendary specials, one each per game: the Holy Water Bong 🫧 lands, glows, sings a full psychedelic choir note (Aaaaaah…), then releases a massive map-clearing blast of white smoke. The Giant Grinder ⚙️ drops from the top of the screen and crushes straight down through the entire map, grinding any nug caught underneath into fine shake. And the Munchie Muncher 🍕 is a sentient pizza slice that waddles across the landscape until you hit MUNCH! — then detonates in a cloud of crumbs.
- The old standbys got rebranded too: Poop Catapult is now the Dab Glob, Manure Bomb is the Hash Bomb. Same four physics modes, same lockstep desync protection, same leaderboard (your old Ass wins still count). 2–5 players, in chat and the Game Room.
- And it's got a soundtrack: 'Tin Shell Tango' loops while the match runs — a proper artillery-duel groove that cuts out the moment a squad takes the turf.
New Game: Scorched Turf: Prohibition
July 18, 2026
- Game #18 hits the circuit: Scorched Turf: Prohibition 🥃 — artillery warfare over a blacked-out 1930s skyline. Armored bootlegger coupes duel across six procedurally built cityscapes (Skyline Row, the Warehouse District, the Alleys, the Viaduct, Dockside, Rooftop Shanties), all rendered in black-and-white noir with lit windows and your crew's colors as the only splashes of color.
- Three ways to make a delivery: the Midnight Special is your unlimited heavy shell — it blows clean chunks out of the brickwork. The Brick-and-Mortar (two per run) splits at the top of its arc into five crates that smash a whole block of rooftops. And the Moonshine Leak (two per run) doesn't explode at all — it spills a glowing trail that runs downhill, and the next blast that lands anywhere near it torches the entire line, burning the ground out from under anyone parked on it.
- Instead of wind, you're fighting the HEAT: a police patrol sweeps across the top of the screen and pushes your shots off course, shifting every turn. Up to 5 players, full leaderboard support, and it's in the Game Room lobby board too. It replaces Ka-Blam in the game picker — same artillery bones, whole new racket. The city itself is scenery: you fight through the streets below the skyline, and coupes can't drive over buildings.
- Aiming got hands-on: ↑/↓ arrow keys nudge your launcher angle a degree at a time (hold to sweep), ←/→ still drive. And everyone at the table now watches your barrel move live while you line up the shot — no more staring at a frozen gun wondering what your rival is planning.
Green Sweep: The Arsenal Is Complete
July 18, 2026
- The last two weapons are in. Slot 6 is the BHO Torch — a near-continuous jet of burning concentrate with the best sustained melt in the game. Get close, hold the trigger, and watch bosses cook. The catch: short reach, and the tank drains in about sixteen seconds of continuous burn, so feather it.
- Slot 7 is The Manifesto. One trigger pull fans eleven pellets of pure truth across nearly fifty degrees and flattens everything in front of you. You get eight pages per campaign run — spend them on the Loading Dock crowd or the Senate floor, not on a lone rookie. In sesh the pages are infinite but the cooldown makes you mean it.
- Both come with their own view models, muzzle art, and sounds — the torch roars, the Manifesto drops like a verdict. Keys 1-7 now cover the full seven-slot roster from the briefing, and the phone weapon button cycles through everything.
- Game Room hosts got a door to close: once your crew is in, the lock button (now labeled 'Close lobby') pulls your lobby off the public board and turns away stragglers — even ones holding the old room code. Reopen any time if you want more players.
- The radio learned geography: a new 🗺️ Places tab in the station picker lets you hop the globe — pick from 200+ countries (flags, station counts, quick search) and tune into the top stations anywhere from Kingston to Kathmandu, each labeled with its home city. Same favorites, same NEXT button, same visualizer.
- Phone game fixes: Shoot Me's touch d-pad no longer shrinks into un-tappable territory (its panel opens taller now, like Weed Wars), one game's panel height no longer carries into the next, and Weed Wars placement finally works properly on touch — tap once to preview where your plot lands, tap again to plant it. No more planting blind.
- Going live got more honest: if the stream relay starts dropping video (your upload outrunning the encoder), a '⚠ degraded' tag now appears next to the LIVE badge so you know viewers are seeing glitches — it clears itself once things catch up. Also fixed: changing your vibes mid-session now sticks in the URL, so a reload keeps your new tags instead of quietly reverting.
- The Game Room now hosts EVERY game — all 17. Solo score-runs like Brickweed and Stash Hunt make great competitive lobbies: up to 8 players grinding the same game on voice, trash-talking their way up the score. And the 2-player classics (Tic-Tac-Toe, Chess, Checkers, Dots & Boxes) are in too — their lobbies cap at 2, first joiner gets the seat.
- Weed Blocks is now Brickweed 🧱 — third name's the charm. Same stacker, same 20 levels, same saved best score, but it drops to a brand-new theme: 'Tumbling Loop', which still picks up speed with every level you climb.
- Green Sweep multiplayer (Game Sesh) got its first cross-PC playtest and three big upgrades. One: enemy bullets are now visible in sesh too — every ranged narc fires a real glowing bolt you can watch coming and strafe-dodge, instead of invisible instant damage. Two: your crew can pick you up — go down with a buddy alive and you get a 10-second window where a teammate standing over you revives you on the spot at half health; bleed out and you respawn as usual. Three: the mouse finally looks up and down — the view tilts with your aim in both campaign and sesh, so the raid feels like a proper FPS.
- Green Sweep campaign: dying no longer means starting the raid over. You now carry 5 lives per run — go down and you respawn at the top of the sector you died in, with the health, armor, ammo, and score you walked in with. The HUD counts your hearts (they go red on the last one), and only your final stand — a win or your fifth bust — locks in a score for the leaderboard. Sector entries also got fairer: at most two narcs jump you the moment you walk in; the rest of the welcome party trickles in after — though they won't just stand there if you stroll up to point-blank.
- Green Sweep campaign: you will never be hit by something you can't see again. Every enemy ranged attack is now a real projectile — a glowing bolt that flies through the level, splashes against walls, and only hurts on contact. Each narc has its own color (SWAT blue, wraith purple, aphid slime, the Czar's orange minigun stream), and you can strafe-dodge all of them. This also killed a nasty bug where wall-phasing wraiths could park inside the masonry and snipe you invisibly — now their phantasms are visible, physical, and stopped by the very walls they hide in.
New: The Game Room
July 17, 2026
- A fourth way to hang: the Game Room. It's voice-only — no cameras, just mics — built for one thing: getting a game going. Hit the new 🎮 card on the homepage to browse every lobby that's waiting for players, see the game and who's hosting, and jump in with one tap.
- Or start your own: pick any multiplayer game — Ka-Blam, Weed Wars, the poker tables, Weedopoly, Operation Green Sweep, all of them — and your lobby goes up on the board until people join. The moment your first player arrives, the game launches automatically. Talk trash, hear the room, play the game.
- Voice lobbies are their own thing: the matchmaker will never merge random video chatters into your game, and leaving a lobby doesn't dump you into the camera queue. Your name carries in from wherever you set it last.
Green Sweep: Episode 1 Complete — All 9 Sectors
July 17, 2026
- The campaign is finished. Five new sectors slot in between the Loading Dock and the Senate: raid The Dispensary's aisles through a Sprayer-and-drone swarm, take your own confiscated stash back from the Wraith-haunted Evidence Vault, fight through The Precinct's cell block past two Riot Captains and their packs, storm the Federal Courthouse colonnade, and survive the DEA Black Site — where a second Forfeiture Mech guards the yard. Then, and only then: the Senate Floor and The Czar.
- Your health, armor, and weapons carry across the whole nine-sector run, and the new maps are stocked accordingly — plan your pickups. The kill counter's 'TO WIN' now lands on sector nine, and the Game Rules story guide lists the full campaign.
- Multiplayer got a new home floor too: sesh rooms now play on The Trap House, a sesh-only remix of the Grow Op with double the pickups — a contested ounce in the center corridor, kevlar and CBD stashed deep — plus a settings menu (Esc or the pause button) with volume sliders that finally work in sesh and carry over from the campaign, and a tap-to-cycle weapon button for phones.
Green Sweep: The Big Sesh Update
July 17, 2026
- Multiplayer sesh just got the campaign's whole arsenal. All five weapons ride with you — Trimming Scissors, Pruner Pistol, Seed Slinger, Leaf Blower, and the CO2 Cannon — switch with keys 1-5. Each has its own feel, sound, and fire rate: shred swarms with the blower, delete a SWAT trooper with one CO2 slug, or go full lawnmower with the scissors. Infinite ammo; the cooldowns keep it honest, and the server referees every shot.
- Pickups are live in sesh too: nugs and eighths heal, the ounce overheals you past full, hoodies and kevlar soak 60% of incoming damage, and the CBD tincture drips health back for 15 seconds. Grabbed items respawn on their spot 20 seconds later — control the pickups, control the round. Full-health players walk over heals without wasting them, so leave the nug for your bleeding buddy.
- You now know where the shots are coming from: when you take a hit, the edge of the screen facing the attacker flares red — spin until the glow sits top-center and you're staring at the narc who did it. Works in both campaign and sesh.
- New sesh HUD readouts: an armor bar and CBD indicator under your health, plus your current weapon. And the whole game now runs to an original soundtrack — 'Pixel Slaughter' — with its own volume slider on the campaign pause screen.
- The enemies brought their tricks to sesh too: Riot Captains rally nearby narcs to charge 40% faster, Prohibition Wraiths drift straight through walls and cloud your screen with purple haze, Mutant Root Aphids slow you with mutagen slime, the Asset Forfeiture Mech confiscates any pickups you left lying around, and The Czar calls in a ring of reinforcements while he lives. Waves escalate to match — tougher types join from wave 2 on, a Mech mid-boss crashes every 3rd wave from 5, and from wave 10 The Czar himself shows up every 5th.
- One more thing: launching Green Sweep solo from the site now offers the full story campaign, not just the wave sesh — with your chat name carried straight in.
Weed Blocks + Green Sweep Un-Stuck
July 17, 2026
- Weed Tetris is now Weed Blocks — same falling pot-leaf stacker, same 20 levels, and your saved best score carries over. It also finally has a soundtrack: an original theme that loops while you stack and literally picks up the pace with every level — by level 20 it's playing at nearly double speed, and so are you. The volume knob is the same 🔊 game-volume button as everywhere else.
- Weed Blocks keys: WASD works now alongside the arrows — A/D to move, S to soft-drop, W to rotate, space to slam, P to pause. On-screen buttons unchanged.
- Operation Green Sweep: fixed the frozen-character bug. Respawning could drop you a hair's width inside a wall — and once embedded, no amount of key-mashing could free you (it usually struck right after your first death, so it felt like 'wave 2 breaks the game'). Spawn points now shove you clear of the masonry, verified across hundreds of test spawns.
- Also in Green Sweep: the floating suggestion box no longer parks itself on top of your health bar — it steps aside while the game is open and comes back when you're done.
Green Sweep Sesh: Smooth Moves
July 16, 2026
- If multiplayer Green Sweep felt sticky tonight — like the game kept yanking you back a step while you ran — that's fixed. This morning's anti-cheat speed cap was a little too eager: the connection the sesh rides on delivers your moves to the server in bursts, and after any little network hiccup the cap treated the catch-up burst like cheating and quietly threw part of it away. Every tossed move was ground you'd covered that the server took back — hence the rubber-band feel. The cap now knows the difference between a laggy burst and an actual speed-hacker: honest players keep every step (verified in simulation: zero lost moves, even on rough connections and 144Hz screens), while a flooding cheater is still pinned to the same speed limit as everyone else.
- Other players and enemies also move smoother now. Their on-screen motion was timed by when updates happened to arrive — which, in bursts, made everyone lurch and freeze. It's now timed by the server's own clock, so narcs advance on you in strides instead of stutter-steps.
Fair-Play Physics Pass for the Shooters
July 16, 2026
- Shoot Me is finally fair across monitors. The game used to run its physics once per screen refresh — so someone on a 144Hz gaming monitor drove, turned, and shot at more than twice the speed of someone on a normal 60Hz screen. The simulation now ticks at the same fixed rate for everyone, no matter the display; only the drawing runs faster on fancy screens.
- Ka-Blam and Ass got the same treatment (shots used to visibly fly faster on high-refresh screens), plus a real ballistics fix: a full-power shot could pass clean through a thin mountain peak between physics steps and sail out the other side. Shells now check the terrain along every couple of pixels of their path, so if it looks like your shot should've hit the ridge — it hits the ridge, right where it crossed.
- Same pass also covered Operation Green Sweep earlier today (enemy crowd physics, wall-proof sightlines, no more diagonal speed-running) and confirmed Stash Hunt was born clean.
- Capped the day with a full site health check — every page, the game servers, the leaderboard, and all the new Stash Hunt assets verified live and healthy, plus one last under-the-hood cleanup in Stash Hunt's intro.
New Game: Stash Hunt
July 16, 2026
- Duck Hunt, but the ducks are your stash. A gust took the whole harvest, the dog's bowl is empty, and he's made his terms very clear (there's a six-panel comic when you open the game — don't skip it, he worked hard on it). Grab the Canna-Zapper and shoot the harvest out of the sky before it floats away.
- How to play: aim with your mouse or finger and zap the flying targets — you get 3 zaps per volley. Bud nugs are 100 points, fan leaves 50, lighters and burning jays 150, terp droplets and rolled joints 200. Watch for the sparkling High-THC nug: zap it and everything scores double for the rest of the round.
- But don't get trigger-happy — seeds and stems get lobbed through the air as bait. Shoot one and it costs you 250 points and kills your 2× bonus. Just let them fly by.
- Let a good target escape and you lose a heart; lose all three and the hunt's over. Five rounds ramp up the chaos — Nug Hunt, Dank Hunt, Terp Target, Canna-Catch, and then The Great Stash Grab, where everything's in the air at once and it gets faster every round. Your best score saves on your device.
- Find it in the solo lounge, the in-chat game picker, and the group sesh row — full rules are always one tap away under the 📖 Game Rules button.
- And it opens with a show: a six-panel comic where the dog lays out his terms — fully voice-acted, with a ragtime chiptune backing track (The Entertainer, naturally). Tap through or sit back; skip it and he'll just tell you again next time.
Narc Nightmare Is Now Operation Green Sweep
July 16, 2026
- The raid FPS has taken its villains' codename for its own. Narc Nightmare is now Operation Green Sweep — the rogue DEA faction's secret plan you've been fighting since the intro is officially the name on the marquee. New title screens in the game, new name in the game picker and the Game Rules guide, and the leaderboard tab now reads Green Sweep. Everything else is exactly where you left it: same campaign, same sesh raids, and every score you've already posted still counts.
- The campaign HUD also got a clearer objective readout: instead of counting narcs left, it now tracks your kills — 'KILLS 7/12 · 5 TO NEXT SECTOR' — flipping to 'TO WIN' on the Senate floor and a green 'SECTOR CLEAR' when the coast is clear. When The Czar calls in reinforcements, the count grows live, so you always know exactly how many stand between you and the next sector. The multiplayer sesh HUD matches: '5 TO NEXT WAVE' under the wave number, and your personal kill count under your score.
- Under the hood, a full physics pass: enemies no longer pile into a single stacked sprite — K9 packs, Czar summon rings, and multiplayer waves now spread out and surround you properly. The Prohibition Wraith earned its ghost stripes and drifts straight through walls to find you (it still has to emerge to attack — or be hit). Shots and sightlines can no longer sneak through wall corners, scissors can't trim narcs through masonry, diagonal running no longer moves you faster than running straight, and the multiplayer server now caps how fast any player can move and fire — so nobody speed-hacks your sesh.
Narc Nightmare: The Senate Floor + Game Rules Button
July 16, 2026
- Narc Nightmare's Episode 1 is complete — and it ends on the Senate floor. The five newest enemies are now woven through the whole campaign: a Mutant Root Aphid infests the Grow Op, two Prohibition Wraiths haunt the Drying Loft, and the Loading Dock brings a Riot Captain rallying the swarm plus the Asset Forfeiture Mech mid-boss, a walking impound lot that confiscates your unclaimed pickups. Clear all that and you reach the brand-new Sector 4 finale: The Czar himself on his badge-plated hover turret, twin miniguns chattering, calling in reinforcements until you bring him down. Multiplayer sesh raids got the update too.
- One big 📖 Game Rules button now sits under the game picker — tap it for every game's how-to-play in one place, including Narc Nightmare's full story and level guide. The in-game '?' still works and jumps straight to the game you're playing.
- Solo Lounge: you can finally switch audio output (headphones, speakers, TV) while watching TV or listening to the radio — the 👂 picker now lives right inside those screens.
- Housekeeping from a full site audit: the coupons map is friendlier to keyboard and screen-reader folks, the Knowledge page loads over a megabyte lighter, and the room TV quietly refreshes its channel lineup every half hour so marathon seshes never sit on a stale list.
Weed Battleship Is Now Weed Wars
July 16, 2026
- Weed Battleship has a new name — Weed Wars — and the plots finally look like the garden they were always meant to be. The naval fleet is gone: your Greenhouse is now a glass grow-house with potted plants inside, the Grow Tent a dark fabric box glowing with grow-light purple, the Drying Shed a little wooden hut, the Compost Heap a slatted bin mounded with green scraps, and the Seedling Tray a row of fresh sprouts. Same cartoon art style, same hide-and-firebomb game — your scores and stats carry over untouched.
- There's also a fresh 'From Trichome to THC%' guide in place of the old knowledge section: a retro film-strip walkthrough, hosted by a friendly narrator, that takes you from the microscopic trichome where THC forms all the way up to the plant, then into the lab to show exactly how a gram of flower becomes the percentage on the package.
Coupons Map
July 15, 2026
- The coupons page got a glow-up: it now opens with a map of the United States, every state outlined, with the 24 adult-use states lit up green. Tap a state and the card flips over to reveal that state's promo codes, first-timer discounts, and weekly deals — then flip back and pick another. State buttons under the map work too if you're on a small screen, and the full A-to-Z list is still below for searching.
Cannabis Community Coupons
July 14, 2026
- New page: Cannabis Community Coupons. Every adult-use state — all 24 of them — with active dispensary promo codes, first-time shopper discounts, mix-and-match bundles, and weekly deal calendars, plus each state's purchase limits and ID rules. Search by state, dispensary, or code, or tap a state chip to jump straight there. Find it in the footer or the link cloud on the homepage. Deals move fast, so always double-check with the shop before you buy — and if you know a deal we're missing, drop it in the suggestion box or post it on The Plug.
Under-the-Hood Reliability Pass
July 7, 2026
- The room TV loads instantly again after quiet stretches. The shared channel lineup gets rebuilt every half hour behind the scenes, and the first person to open the TV after a rebuild was due used to eat the whole wait — up to ~25 seconds of blank screen. Now you always get the current lineup immediately and the refresh happens in the background.
- TV can't get stuck on 'no channels' anymore. If the lineup server is slow to answer rather than down, your TV used to wait on it forever. It now gives up after a few seconds and pulls the backup lineup instead, so there's always something to watch.
- Group video stays connected through relay-service hiccups. Some networks need a relay server to carry cam video, and if our relay provider had a blip, new connections on those networks silently failed. We now fall back to the last known-good relay credentials, which stay valid for hours — so a provider outage no longer breaks your sesh.
- General hardening on the pipes that feed the TV and the live-rooms list, so heavy traffic (or someone hammering them on purpose) can't slow the site down for everyone else.
Battleship Gets a Real Fleet
July 6, 2026
- Your Weed Battleship plots now look like an actual fleet. Instead of a row of identical pot leaves, each plot is drawn as its own cartoon vessel stretched across its squares — the Greenhouse is an aircraft carrier with a red flight deck and parked planes, the Grow Tent a gray battleship with twin orange turrets, the Drying Shed a green gunboat, the Compost Heap a navy-and-gold submarine, and the Seedling Tray a little red tugboat. The boards are proper water now, you get a ghost preview of each ship while placing it, and when a ship goes down it burns to a charred wreck — on your board and your rival's.
- Narc Nightmare's rogues gallery is growing. Five new enemies are in the engine and headed for the next episode sectors: the Riot Captain (a shielded commander whose megaphone rallies nearby narcs), the Prohibition Wraith (the literal ghost of the drug war — its touch clouds your vision in purple haze), the Mutant Root Aphid (spits mutagen that slows you down), the Asset Forfeiture Mech (a walking impound lot that confiscates health pickups mid-fight), and The Czar himself on a badge-plated hover turret, saving his twin miniguns for the Senate floor.
Big Tune-Up: Rooms, Games & TV
July 3, 2026
- Games keep your seat. In a group sesh, someone joining or leaving mid-game used to silently shuffle everyone's spot — suddenly your Battleship farm or poker hand was somebody else's. Now the seating locks in when the game starts and stays put until a new round begins.
- Every TV channel syncs now. Channels above CH 710 quietly never synced to the rest of the room — you'd be watching one thing, your sesh-mates another. The whole lineup is fair game now.
- The buddy Next → button actually works. Skipping to the next smoking buddy could leave you stuck on 'Finding buddy…' forever. It now drops you straight back into real matchmaking.
- No more waiting into the void. If you sat unmatched in a group sesh for 10 minutes, the server quietly gave up on you even though the screen said it was still looking. You now stay matchable as long as you're on the page.
- Fresh sesh, fresh game. Opening a game right after being matched with someone new could replay a leftover move from your previous partner's game. Games now start clean every time.
- Radio stays in sync. If a station change got lost in the shuffle, listeners could drift onto different stations — the room now self-corrects within seconds. And joining a room where music or TV is already going always asks first instead of blasting audio at you.
Room TV Finally Stays in Sync
June 19, 2026
- When you and your sesh-mates watch the TV together, you're now actually on the same channel. Before, the channel list was built fresh for each person at slightly different moments — so flipping to a channel like CH 420 (CLN) could land everyone on a different video even though the channel number matched, because that channel plays whatever's live on YouTube right then. Now everyone pulls one shared channel list from the same place, so a channel number means the exact same thing for the whole room. Change the channel and everyone follows along to the same thing, live streams included.
See When You're Waiting for a Sesh
June 19, 2026
- Jumping into a group or buddy sesh used to leave you staring at just your own camera with no sign anything was happening — easy to think it was broken when it was really just looking for someone to pair you with. Now there's a clear on-screen card while you wait: 'Looking for a sesh…', 'You're next in line…', or 'Finding your smoking buddy…', along with how many others are looking right now and how many people are online. The moment someone connects, it disappears and your sesh begins.
Flashlight for Dark Rooms
June 9, 2026
- Seshing in the dark? There's a new 🔦 flashlight button in the bottom control bar. On phones with a back-camera flash, it fires up the real LED torch so your shot is lit.
Smoother Multi-Cam
June 9, 2026
- Group video should freeze and pixelate a lot less now, especially in 3- and 4-cam rooms. Before, every cam tried to send the highest-quality video it could to everyone at once, which could overload your home internet's upload and make the picture stutter for everyone. Now each room automatically dials the upload to fit the number of people on cam — more cams, a touch softer picture, but motion stays smooth and connections hold steady. As people come and go, it adjusts on the fly.
New Home: dopevibes.now
May 23, 2026
- Dope Vibes now lives at dopevibes.now. Everything you know is the same — same community, same games, same Plug, same Solo lounge — just a shorter, easier URL to share. Old theweedvlog.com bookmarks keep working: every request gets 308-redirected to the same page on dopevibes.now, so links you've shared in the past won't break. Email addresses (moderation, alerts, suggestions) still use @theweedvlog.com while we finish DNS verification on the new domain.
Narc Nightmare Now Plays as a Game Sesh
May 22, 2026
- Narc Nightmare — the co-op raid FPS where you and your sesh-mates push through narcs together — is now in the in-chat game picker. When someone starts it, everyone else in the room gets a 'Raid in progress' prompt and can either jump in or sit it out on cam. If you sit out, voice chat keeps running and a floating 'Raid in progress — Join' button stays parked at the bottom of the screen so you can drop in any time without leaving the room. Each new raid re-asks every player, so you can sit one out and jump into the next.
How to Play, Right Inside the Game
May 22, 2026
- Every in-chat game picked up a '?' button up in the corner next to minimize and close. Tap it for a short how-to-play card — what the goal is, the basic rules, how a turn works — without having to leave the game or hunt around the site. Each game has its own card, from the classics like Chess and Checkers to the weed-themed games like Weedopoly, Weed Spades and Weed Battleship, all the way to Narc Nightmare. If a card reads wrong or feels off, the bottom of the card points you at the 💡 Suggestion Box so we can clean it up.
Solo Lounge Goes Camera-Free
May 22, 2026
- Solo Lounge no longer asks for your camera. It's a chill-alone space — TV, radio, and the in-chat games — so we cut the camera prompt out of the entry. If you decide to invite people in later, you can flip the cam on then. The Solo Lounge tile on the home page now reflects this: 'Chill solo with TV, radio & games — no camera, just vibes.'
Heads-Up on New Games
May 22, 2026
- The newer games in the picker now show a small 'work-in-progress' note when you open them, so first-time players know a game is still being polished. The note only appears while the game is open and disappears once you minimize it — and the games still play the same as before, this is just a little nudge that we're still iterating.
Group Sessions Get Game Night
May 21, 2026
- Weed Tetris, Weed Spades, Weed Dominos and Weed Battleship are now playable in group video sessions, not just 1-on-1 — they've joined the multi-cam game picker alongside Weedopoly, Ka-Blam and the rest.
- Weed Spades and Weed Dominos were rebuilt for 2 to 4 players. Dominos deals every seat a hand from the double-six set and the turn passes around the table; the board blocks when everyone passes in a row and the lightest hand wins. Spades deals thirteen cards per seat with the leftover deck as a shared draw stock — a four-player game uses the whole deck, so it skips the draw phase and goes straight to bidding — and every seat is scored on its own bid.
- Weed Battleship became a 2 to 4 player free-for-all. Everyone hides their own farm, and on your turn you pick which rival to firebomb before choosing a cell. A farm that's fully torched is knocked out and that player drops to spectating; the last farm standing wins. As always, no farm layout ever leaves your device — each side's own browser is the only thing that knows where its plots sit.
Weedopoly: The Free Sesh Kitty
May 21, 2026
- Free Sesh is no longer just a place to kick back. Every dollar paid to the bank now piles into a kitty — card fees and fines, jail bail, property purchases and building costs all feed it. Land on Free Sesh and you scoop the entire pot. The board square shows the running total so the whole table can watch it grow, and a fat kitty can swing a game in a single roll.
Games Fit Your Phone
May 21, 2026
- Opening a game on a phone no longer blanks out the video call. A game now docks as a panel along the bottom of the screen with the cameras still live above it — and you can drag the handle at the top of the panel to trade space between the game and the video.
- The game is scaled to fit that panel, so a big board like Weedopoly shows in full instead of running off the edge of the screen. Drag the panel taller for a bigger board.
- The “Pick a game” list is now collapsible and starts collapsed on phones, so a long list of games no longer pushes the camera view off-screen.
Now Available Worldwide
May 21, 2026
- Dope Vibes is no longer limited to the Americas — the regional restriction has been lifted, so the site is reachable from anywhere.
- Cleaned up the TV guide: 39 channels were taken down after testing. As with past channel removals, each is replaced with an Off Air placeholder so the surrounding channel numbers stay put, and the Channel Guide plus channel up/down and auto-scan skip straight past them.
Four New Games: Weed Dominos, Battleship, Spades & Tetris
May 21, 2026
- Weed Dominos — the classic double-six draw game for two. You're each dealt seven bones from a shuffled 28-tile set and the rest sit in the boneyard. Match a tile to an open end of the line; if you can't go, draw from the boneyard until you can, or pass when it's empty. First to empty their hand wins, and if the board locks up with nobody out, the lighter hand takes it. The pips are drawn as little pot leaves.
- Weed Battleship — hide your grow, firebomb theirs. Instead of ships you place five farm plots on a 10×10 grid — a five-cell Greenhouse down to a two-cell Seedling Tray — then trade turns lobbing molotov cocktails at the rival's farm. Hits set a plot ablaze, misses splash in the dirt, and the first farm fully torched loses. Drop your plots by hand or hit Random. Your layout is never sent to your opponent — each side's own device is the only thing that knows where its farm sits, so there's no peeking.
- Weed Spades — the trick-taking card classic, two-handed cutthroat style, with pot leaves as the trump suit in place of spades. After the deal there's a draw phase to build your hand: peek the top card of the stock and keep it (ditching one you don't want) or discard it. Then you bid how many tricks you'll take and play all thirteen out — follow the led suit if you can, leaves beat everything, and you score for hitting your bid or get set for missing it. Nil bids are in.
- Weed Tetris — a solo high-score run. The seven tetromino shapes fall as blocks of coloured pot leaves; clear lines to score and climb twenty levels as the drop speed creeps up. A ghost piece shows where the current shape will land, there's a next-piece preview, and your personal best is saved on your device. Play with the on-screen buttons, or on desktop the arrow keys — left/right to move, down to soft-drop, up to rotate, space to hard-drop.
- All four join the game picker in 2-person video sessions. You can also try them on the /games practice page, which runs each game in a two-seat layout so you can learn the rules without waiting for a match.
Weedopoly Audio Cleanup
May 20, 2026
- Weedopoly sound effects play at an even level now. Each hop/land/rent clip used to be cloned into a fresh audio node per play, so a long roll stacked overlapping hop sounds and the volume swelled square by square. It now reuses one element per sound and restarts it on each trigger, so every hop plays at the same fixed level no matter how far you roll.
- Landing on a property you owe rent on now plays only the rent sound. It used to play the land thud and then the rent sound a beat later; the token-hop animation now swaps the rent sound in for the land sound on the final square, so a rent landing is just the pay-rent audio — and it hits exactly when the token lands instead of 250ms after.
Checkers Multi-Jump
May 20, 2026
- Checkers now chains multi-jumps into a single turn. A capture used to end your turn even when the piece that just jumped could immediately jump again — so a double or triple jump was impossible. Now, after a capture, if the landing piece has another jump available it stays selected and the turn holds; you keep jumping until no capture remains. A man that reaches the back row mid-jump is kinged and stops there (standard rule); a king chains freely. The whole chain is sent to the other player as one move event carrying the start square plus the full path of hops, so the turn still maps to exactly one network message and every intermediate jump replays in order on the other side.
- Hid CH 1312 (TV Land Drama) — replaced with an Off Air placeholder so the surrounding channel numbers don't shift; the guide hides it and channel up/down/scan skip past it.
Two-Axis Tile Resize, Room-Synced TV, Weedopoly Buildings
May 20, 2026
- Multi-cam tiles can now be sized in both dimensions even in a 1-on-1. The bottom-edge row-resize handle only ever appeared when tiles wrapped onto two or more rows, so a 1-on-1 — two tiles side by side in a single row — had no boundary to drag vertically. Two additions close that. A stack-layout toggle (▤/▥ button at the top-center of the video grid) flips the room between side-by-side and stacked; stacked forces a single column, so every tile gets its own row and the existing ▲▼ row handle applies even with two people. Separately, every tile now carries a ◢ corner grip at its bottom-right — drag it to set that tile's own width and height anywhere from 30% to 100% of its grid cell, independent of the shared column/row lines, and double-click to reset. The vote-kick button moved up off the bottom-right corner to clear room for the grip.
- Room TV is now synced to the host. When the host has the TV up, anyone joining the room gets it too — popped up on the host's channel automatically. The TV tile used to be a purely client-side toggle: a guest who joined a room where the host was already watching never saw it, and a host showing or hiding the TV didn't propagate to anyone. The server now tracks TV visibility per session via a host-only tv:visible event (mirroring the existing channel-sync plumbing), pushes it to new joiners on every join path, and carries it on the 15-second session-sync heartbeat as a catch-all heal. A host showing or hiding the TV now syncs the whole room live. A guest can still toggle their own TV — a local override flag keeps the heartbeat from overriding their choice — and the host's next show/hide re-syncs everyone back onto the same vibe.
- Weedopoly gets a property development ladder. Once you own every property in a colour row (a monopoly), a 🏠 Build panel opens up: develop each property through six levels — one, two, three homegrows, then the three homegrows become a weed farm, a second weed farm, and finally a pot shop. Each addition costs half the property's price, and rent climbs with every level (property price × 0.30 at a bare monopoly, up to × 4.20 with a pot shop on it). Building happens pre-roll on your own turn, the same window as proposing a deal; the panel lists each monopolied property with its current tier and live rent, the board tiles show a 🌱/🏡/🏪 icon strip, and a player's buildings are released back to the bank if they go bankrupt.
- The /chat game picker now shows pot-leaf icons for Trap Star and Weedopoly instead of plain emoji, matching the look the standalone /games test harness already had — Trap Star a blue leaf, Weedopoly a green one — across all three picker render sites (the 2-cam grid, the multi-cam pill row, and the minimized-game pill).
- The About page picked up a short paragraph on the in-session games for SEO — naming Weedopoly, Trap Star, and the pot-leaf board-game classics — so the games surface in search alongside the video-chat and forum copy.
Matchmaking Strand Rescue + Solo-Room Monitoring
May 20, 2026
- The 1-on-1 queue got a periodic re-match rescue. findMatch2 — the 1-on-1 pairing pass — only ran on a join or skip event, so two people left waiting in the queue who were vibe-mismatched when they joined, or filtered apart by a skip-pair cooldown, were never re-evaluated and could sit stuck for the full 10-minute cooldown. New sweepQueue2 runs every 5 seconds, mirroring the multi-cam solo-room sweep: a normal re-match pass first (respecting cooldowns and vibe overlap once both have waited past a short window), then a cooldown-bypass pass after a 30-second grace period so a tiny pool can't strand itself. The queue snapshot is liveness-filtered so a dead socket can't be matched into a live one.
- Fixed a matchmaking aggravator in the queue:join handler — it was recording a 10-minute skip-pair cooldown against every current partner's IP on every re-queue. But queue:join isn't a skip: it fires on the initial connect, on a partner-left auto-rejoin, and on a manual re-search. The in-session 'next person' button is the separate skip event, which still records the cooldown correctly. The effect of the bug was that a multi-cam match which was signalled but never established at the WebRTC layer would, on re-queue, cooldown-ban the pair for 10 minutes — and in a small user pool that stranded both people on 'finding.' queue:join now passes through as a plain disconnect with no cooldown.
- The admin monitor counts solo rooms as their own metric now. A solo room — a multi-cam session with nobody else in it, someone parked alone waiting to merge — was being folded into the 'in session' and 'active rooms' counts, so the monitor would report something like '1 chatting, 1 room' when nobody was actually chatting. /admin/status now returns soloRooms separately, excluded from the in-session and active-room totals and subtracted from idle; the monitor page surfaces it in the hero line and the Connections check detail.
Vertical Tile Resize
May 15, 2026 — evening
- Multi-cam tiles can now be resized vertically too. Each tile already had a right-edge handle (◀ ▶ buttons + drag bar) for column width; mirrored it on the bottom edge with ▲ ▼ buttons + a drag bar that pushes the row boundary up or down. Drag-resize uses pointer Y delta + container height; tap-nudge bumps the boundary by 0.2fr at a time. Same min-fraction clamp (0.4fr nudge floor, 0.25fr drag floor) keeps the smaller side from collapsing to zero.
- Implementation collapsed the duplicated col-resize logic into a single beginResize(axis, idx, fractions) helper that closes over either colFractions or rowFractions; onResizeMove branches on the axis tag in resizeRef to read clientX/clientWidth or clientY/clientHeight. nudgeColumn / nudgeRow are thin shims over a shared nudgeBoundary. Net: one set of handlers serves both axes instead of two parallel implementations. rowCount = ceil(tileCount / colCount) drives a new gridTemplateRows alongside gridTemplateColumns (replaces the prior gridAutoRows: '1fr'). The bottom handle hides on the last row, on the row-spanning expanded tile, and on single-row layouts (2-cam in landscape, 3-cam in 3-col, etc.) so it never appears with nothing to resize against. Double-clicking either nudge button still resets both axes to 1fr.
Forum → 4chan Catalog Grid + Channel-Skip Fix
May 15, 2026
- Forum redesigned from a single-column Twitter-style feed into a 4chan-style catalog grid. Responsive 2/3/4/5 columns (mobile → desktop), dense ~180px-min cards showing avatar + handle, body snippet (line-clamp-6), one vibe chip with +N overflow, and a footer with like + reply counts. Container widened from max-w-xl to max-w-6xl. The whole card is the click target into /forum/[id]; nested Linkify <a> tags were dropped from the snippet so they can't steal the card click. Existing thread page and compose modal are unchanged.
- Threads now sort by last_reply_at (4chan-style bump) instead of created_at — active threads stay visible, dead ones sink. New schema column on forum_posts (backfilled from created_at, indexed desc nulls last) plus increment_reply_count() updated to set last_reply_at = now() on every reply. Realtime subscription was extended to forum_replies INSERTs so a new reply bumps the parent thread to the top of the grid live, without re-fetching. Migration must be run in the Supabase SQL editor before the deploy goes live or new replies will fail.
- Channel skip bug: working channels (the user hit it on CH 1235) were getting silently skipped on revisit — even after favoriting them. Two compounding bugs in app/components/RoomTV.tsx. First: a single fatal HLS error broadcast the channel as dead globally (POST /iptv/report-dead → flips iptv_health unhealthy in Supabase → io.emit('iptv:channel:dead') → every connected client's globalDeadUrls set + persisted across Railway restarts). One transient blip during ad transitions or buffer stalls killed the channel for everyone for up to 6 hours, until the next probe revived it. Second: favorites were cosmetic — the star toggle wrote to localStorage but isDeadIndex never consulted favorites, so a favorited dead channel was still skipped during chan-up/chan-down/scan.
- Fix: favorites now bypass isDeadIndex entirely (user explicitly opted in — never auto-marked dead, never skipped, never broadcast). Mid-watch errors no longer broadcast — added an everPlayedRef that flips true once 'playing' fires; if it's true when a fatal error hits, the failure is treated as transient and reported only locally to the current viewer's session, not pooled into the global dead map. Only failures that happen before the stream ever starts (manifest load failure, immediate fatal on first chunk) propagate to /iptv/report-dead. The ref resets on every channel change so the new channel starts fresh.
- Manual revival required for channels already poisoned in iptv_health from the old behavior — `update iptv_health set healthy = true where url = '...'` in the Supabase SQL editor, or wait up to 6h for the next probe cycle to re-verify them naturally.
One-Tap Audio Balance for Multi-Cam Rooms
May 14, 2026 — evening
- New 🎚️ button in the bottom control bar (right after the speaker picker) whenever you're in a multi-cam room with 2+ partners. Tap it and the room samples each cam's actual audio loudness over a 1.5-second window via Web Audio AnalyserNodes — the analyser is wire-tap only, never connected to your speakers, so the measurement doesn't double up the audio your video tiles already play. Then it attenuates the louder cams down toward the quietest audible cam so everyone sounds about equal. The room settles at the quiet cam's level rather than boosting the quiet one up — that's a hard cap of the HTML video volume property, which only goes 0–100%. The per-cam volume sliders still work the same as before; the button is for clearing wild loudness mismatches in one tap instead of opening every cam's slider in turn. Hidden in 1-on-1 mode (nothing to balance) and while you're alone in a multi-cam room. Pulses green and disables itself during the 1.5s sample so a double-tap can't kick off a second measurement.
- Plumbing note: partnerVolumes state was hoisted out of VideoGrid up into chat/page.tsx so the new ControlBar button and the existing per-cam sliders share one source of truth (VideoGrid now consumes partnerVolumes + setPartnerVolume as props). If we ever want true loudness equalization — boosting the quiet cam up instead of just turning the loud one down — the upgrade path is routing each remote stream through a per-stream GainNode and muting the underlying video element. Bigger lift; deferred until someone actually asks. Also bundled in: an uncommitted VideoGrid UI tweak that had been sitting in the tree (volume + expand buttons moved to bottom-left and made always-visible instead of hover-revealed) — same component, same commit.
3-Cam Connection Fix + Vote-to-Join Removed
May 14, 2026
- Fixed a 3-cam regression from the 5/13 onnegotiationneeded work. That handler fires whenever tracks are added to a peer connection — including during the initial startCall setup. The guard was 'signalingState !== stable', but a non-initiator's connection sits at 'stable' until the initiator's offer arrives, so it sailed past the guard and emitted an offer it was never supposed to send. With two people, perfect-negotiation rollback untangled the resulting glare; with three (six peer connections, six spurious offers racing three real ones) it overwhelmed the resolution and connections hung. The handler is now gated on the connection being established (connectionState 'connected', or iceConnectionState 'connected'/'completed'), so it only ever handles post-connection track changes — camera recovery, late joins — and never races the initial handshake.
- Vote-to-join removed — joining an active multi-cam room is now instant, no popup. Previously a 3rd/4th camera had to be voted in by the existing members; when they didn't vote (popup missed, AFK), the joiner got stuck in a vote-then-10-second-timeout loop and never connected. It was worst for two people on the same network, whom matchmaking routes toward the vote-gated room instead of toward each other. Since you can already vote-kick anyone and lock a room, the vote-in gate was redundant friction. tryJoinExistingRoom now adds the joiner directly; locked rooms are still excluded from the joinable set, so a locked room stays private. Removed end to end: the join-request/vote server logic, the JOIN_REQUEST/REQUESTING/VOTE/CANCELLED events, the vote modal and 'join failed' toast, and the join:requesting status — about 220 lines net. The joiner-initiates WebRTC wiring (JOIN_APPROVED) is unchanged, so connection reliability for late joiners is the same.
WebRTC Renegotiation + TURN Cred Refresh; Group-Mute Retired
May 13, 2026
- onnegotiationneeded is now wired on every peer connection — the gap the 5/9 troubleshooting note left explicitly open. Track changes on an already-established PC now renegotiate instead of silently going nowhere. The concrete win: when the camera recovers from audio-only fallback, the recovered video track is pushed straight into every live peer connection (replaceTrack if a video sender already exists, addTrack if the PC started audio-only) and the new handler sends the offer — partners see the recovered video immediately instead of waiting for the next match. Glare is handled with a per-partner makingOffer flag that onOffer checks alongside signalingState, so two simultaneous renegotiations resolve cleanly via perfect-negotiation rollback.
- TURN credentials now refresh when the cache ages out. They were fetched exactly once per page load and reused forever — so a long-lived tab that kept skipping eventually built new peer connections and ICE restarts with expired Metered creds, and anyone behind a strict NAT silently failed to connect. The module cache now carries a timestamp with a 5-minute TTL matching the server-side /turn-credentials cache, concurrent callers are deduped onto one in-flight fetch, and a new ensureIceServers() runs before every PC-creating path (startCall, addPartner, onOffer). On a refetch failure it prefers the stale creds over dropping to STUN-only.
- Added oniceconnectionstatechange as a backup failure trigger. Connection recovery hung entirely off connectionState 'failed'/'disconnected', but that event is unreliable on Firefox and older Safari — it can take 30s+ or never fire. iceConnectionState is the more portable signal; both now feed a shared handleDrop() that won't double-arm the disconnect watchdog when the two events report the same drop. The ICE-restart attempt budget already guards the overlap.
- Group-mute button removed. The host's 'mute everyone' control is redundant now that each cam tile has its own volume slider — anyone can turn down a loud partner without a host action, and per-partner is finer-grained than all-or-nothing. Removed the full path: the ControlBar button, the useChat muteRoom callback + roomMuted state + ROOM_MUTED listener, the useChatSession mute effect, the ROOM_MUTE/ROOM_MUTED socket events, and the server room:mute handler. No orphan events left behind.
Landing Page Redesign — 3-Card Layout
May 10, 2026
- Landing page restructured into the mockup's 3-column card layout. Left card 'Meet Our Community' merges the Day Ones strip with the embedded Leaderboard into one bordered panel. Right card 'Community Voices' carries the Dazed & Confused quote at the top with the 'Explore The Vibes' pill grid underneath. Center column is now logo → tagline → online count → big green Enter → outlined Become The Plug → a clean 4-up row of mode cards (Solo / Buddy / Group / Forum), with the Forum tile as a Link straight to /forum and the other three setting preferredMode before entering the age gate.
- Removed from the landing flow: the StockTicker strip, the inline 'How It Works' arrow-icon teaser, the social-proof quote tiles, and the dense mobile-stack section that duplicated the desktop sidebars (each column now stacks vertically on small screens via the same grid). StockTicker import is gone from HomePage; the component itself stays around since /updates and /chat still use it. Added a plain text-link footer (Home / How It Works / FAQ / For Creators / The Plug / Terms / Privacy / Guidelines) underneath the existing cloud-shaped button row, plus a copyright line.
- Subtle correctness fix in the age-gate handler. The previous code called setPreselectedVibe(null) before reading preselectedVibe in the if-branch and relied on the React closure still seeing the old value — worked by coincidence. New version captures the vibe into a local const v first, then clears state, then dispatches with v. Same observable behavior, no stale-closure dependency.
- Net diff: 477 lines touched in app/HomePage.tsx (−299 / +177). No other files affected — HomePage is only imported by app/page.tsx, and StockTicker's two remaining call sites were checked. All four entry paths (Enter, Become The Plug, mode card, vibe pill) traced end-to-end through agegate → vibes → mode/chat and produce the same final routes as before the redesign.
Global Dead-Channel Pool + Standalone weed-vlog-tv Repo
May 9, 2026 — evening
- IPTV dead-channel discovery is now pooled across all users. Previously each browser kept its own deadChannelsRef in RoomTV — so every viewer had to independently fail-walk through the same dead URLs, and the work never accumulated. New POST /iptv/report-dead endpoint flips the URL unhealthy in the same Supabase-backed health map the 6h server probe owns, then broadcasts iptv:channel:dead over Socket.io. Every connected client adds the URL to a shared globalDeadUrls set and skips it on chan-up / chan-down / scan / tune / guide without waiting on the 30-min /api/iptv cache TTL. RoomTV's three skip checks (no URL / locally observed dead / globally pooled dead) are now a single isDeadIndex helper. Pool persists across Railway redeploys via the existing iptv_health table; next probe re-verifies and self-heals if the failure was a transient client-side blip.
- Standalone weed-vlog-tv Next.js site spun up at github.com/Miggy420/weed-vlog-tv (private). Reuses RoomTV + /api/iptv verbatim and points NEXT_PUBLIC_SERVER_URL at the existing Railway backend, so the new site shares the same dead-channel pool and health probe with TouchGrass — failures discovered in either app skip the channel in both. No new backend service.
- Channel-sync GitHub Action (.github/workflows/sync-tv-channels.yml) mirrors app/api/iptv/channels.ts to weed-vlog-tv on every master push that touches the file. Uses a fine-grained PAT scoped to weed-vlog-tv only (Contents: write), stored as repo secret WEED_VLOG_TV_DEPLOY_TOKEN. TouchGrass remains the source of truth for the channel list; the standalone repo is a sync target.
- Standalone repo audit caught one CORS gap: server/modules/ip-utils.js ALLOWED_ORIGINS didn't include the new weed-vlog-tv Vercel subdomain, so once the standalone deployed, the browser→Railway calls (POST /iptv/report-dead and the Socket.io iptv:channel:dead handshake) would have been rejected and failures wouldn't have propagated between the two apps. Added https://weed-vlog-tv.vercel.app to the allowlist (default subdomain — update if Vercel assigns a different one on first deploy). The viewer baseline still worked without this since /api/iptv fetches /iptv/health server-to-server (no CORS), so dead URLs would have surfaced via the 6h probe + cache refresh; what was missing was the live cross-app broadcast loop.
- weed-vlog-tv shipped to Vercel at https://weed-vlog-tv.vercel.app — production domain matched the placeholder pre-added to ALLOWED_ORIGINS, no follow-up CORS edit needed. Standalone IPTV viewer is now live and sharing the dead-channel pool with TouchGrass: an HLS failure observed on either site flips the URL globally and broadcasts to the other within seconds. Standalone deploy reads NEXT_PUBLIC_SERVER_URL from Vercel env vars; first deploy from commit f92f84f.
Multi-Cam Disconnect Fix Pack + Audit Backlog Closeout + Next 16 Proxy Rename
May 9, 2026
- Multi-cam room split fix. When any member left a 3+ room via separation (Skip / room:join / group:leave / re-clicking a mode button), endSession recorded the 10-min skip-cooldown only with currently-remaining partners — so two sequential leavers never got a cooldown between each other. The solo-sweep then happily re-paired them, which is exactly what produced the "4-room → two 2-rooms" symptom users reported (originals stay together, leavers form their own room). Fix propagates each leaver's IP into remaining sessions' departedPartnerIps and records cooldowns with previously-departed peers on every subsequent separation. Bounded by SKIP_COOLDOWN_MS so a long-lived churning multi-cam room doesn't accumulate forever.
- Skip handler was stomping the local camera. handleSkip called webrtc.endCall() — full teardown that stops the local mic + camera tracks and nulls the stream — so on the next match addPartner had to re-acquire via getUserMedia, occasionally racing the device release and falling back to audio-only (visible as the blank You tile + "Audio only" badge users were reporting). resetConnections is the existing helper for exactly this case: closes peer connections but keeps the local stream alive across the re-match.
- Camera auto-retry while in audio-only fallback. Once startLocalStream fell back (camera busy or temporarily denied), nothing in the codebase ever re-attempted video — the user was stuck on a blank tile until they reloaded. Now polls navigator.mediaDevices.getUserMedia for video every 15s while cameraUnavailable is true and splices the recovered track into the existing localStream once the camera frees up. Capped at 5 attempts so a hard-denied permission doesn't poll forever. Local self-view recovers immediately on success; partners see the new video on the next match (peer-side restoration would need an onnegotiationneeded path the codebase doesn't have yet).
- Forming-room subsystem removed (~210 LoC across server + client). server/lib/group-rooms.js had joinOrCreateFormingRoom + tryStartFormingRoom + leaveFormingRoom + the formingRooms Map, and the client had GROUP_FORMING_JOINED / GROUP_MEMBER_JOINED / GROUP_SESSION_LIVE / GROUP_HOST_CHANGED / GROUP_FORMING_DISSOLVED listeners + formingRoom state + group_forming/group_live ChatStatus values — but no entry point ever called joinOrCreateFormingRoom, so socket.formingRoomId never got set and every downstream branch was unreachable. GROUP mode has been going straight through joinQueueMulti via the SESSION_MODE handler the whole time; the forming-room scaffold was vestigial.
- /live-rooms city names actually populate now. The QUEUE_GEO handler had been writing geo onto a queueMulti entry that hadn't been populated in ages — so the forum's live-rooms list never got the location subtitle it was designed for. Pointed the geo write at the session itself (session.geo) and updated /live-rooms to read from there. Forum cards show "city, region" again when geolocation is granted. Took the queueMulti array out while we were here: zero writes anywhere, every reader was a no-op (findMatchMulti always returned null, sweep loops ran on empty, admin stats reported 0). Follow-on fix: the QUEUE_GEO emit only fired once on mount (before any session existed), so session.geo silently stayed empty — now re-emits on status changes too.
- Locked rooms no longer silently unlock when the host disconnects. endSession unconditionally re-added the remaining-members key to upgradeableSessions, reversing the host's lock_room intent the moment they dropped. Now skips the re-add if any remaining session still has locked: true; the new host can re-open the room via /lock. Same fix applied to handlers/rooms.js room:join — joining a locked private room via code used to silently un-vote-lock the room.
- Disconnect cleanup reordered. The handler ran streamCleanup.cleanupStreams before leaveCurrentContext — so a 1:1 peer of someone Instagram-streaming would get STREAM_TAKEOVER ("you're now the feeder") a few milliseconds before PARTNER_LEFT (the session being torn down). Causal order now: leaveCurrentContext first, stream handoff after.
- approvedKicks leak closed. After a kick vote passed, the authorization entry sat in approvedKicks with a 60s TTL that was only checked lazily on the next access. If the target or initiator disconnected before kick_submit fired, and no further vote ever landed on that target, the entry stayed forever. Disconnect cleanup now drops them up front.
- room:join radio/TV sync now reads from the live promoted host instead of the (potentially stale) hostSession captured before the host promotion. Same path also dropped a redundant flagCounts.delete in the disconnect handler — modCleanup.cleanupFlags() already deleted it.
- middleware.ts → proxy.ts rename for Next 16 compatibility. Next 16 deprecated the middleware filename in favor of proxy.ts and the named export 'middleware' in favor of 'proxy'. Renamed file + function, updated doc references in CLAUDE.md and next.config.mjs. Behavior is unchanged: same geo-block, same CSP composition, same x-nonce header passed to the layout. Build confirms Next 16 picks it up as "ƒ Proxy (Middleware)" with no deprecation warning. Caveat: proxy runs on Node.js — the edge runtime is no longer supported in proxy. For our usage (header read + string composition) the runtime swap is invisible.
Reliability Pass: 3-Cam Joins, Graceful Restarts, IG Audio Mix, TURN Quota Alerts
April 28, 2026
- Instagram Live audio mix now reconciles against the live room. Previously the mixed track was a one-shot snapshot at stream start — viewers heard whoever was present then, but as partners cycled the mix decayed until only the streamer's mic was audible. The AudioContext + MediaStreamDestination are now persistent and a reconcile effect adds/removes MediaStreamAudioSourceNodes as remoteStreams changes. Cleanup disconnects every node before closing the context.
- 3-person multi-cam joins: vote threshold lowered from unanimous to majority, so a 2-person room only needs one approval to admit a 3rd cam (was 2 of 2 — one AFK member silently blocked all joins). 3+-person rooms still need majority. When a vote times out or rejects, the requester now sees a transient toast ("Members didn't respond — looking for another room" / "Members declined…") instead of dropping silently into a solo waiting room with no explanation. Server logs at timeout now include approval count for diagnosing the remaining failure modes.
- Graceful Railway restart: SIGTERM / SIGINT trap broadcasts a SERVER_RESTARTING event, kills any in-flight ffmpeg streams cleanly so RTMP endpoints get a proper EOS, then closes the HTTP and Socket.io servers in sequence within Railway's 30s grace window. The client renders a yellow "Server is updating — you'll auto-reconnect" banner in place of the generic reconnect spinner so users understand what's happening instead of assuming the call broke. Sessions still re-queue from scratch on reconnect; full state persistence would be a separate, much bigger lift.
- TURN bandwidth quota alerting: polls Metered.ca's /api/v1/turn/current_usage every 30 minutes, surfaces yellow at 70% / red at 90%-or-overage. Email alert via the existing health-monitor pipeline + a dedicated TURN Quota panel on /admin/monitor. Closes the silent-failure mode where multi-cam dies for users behind strict NAT once the monthly cap is hit. Requires METERED_SECRET_KEY env var on Railway (separate from METERED_API_KEY); skipped silently if not configured.
- Audit cleanup: late-banned users on QUEUE_JOIN and ROOM_JOIN were checked with banned() (silent boolean) instead of isBannedGuard() (which actually disconnects the socket), so a banned user could keep a socket open and silently re-fire those events forever (rate-limited but still alive). Now disconnected on the first attempt like everywhere else. Dropped the dead isBanned getter from makeBanGuard plus the threading through index.js.
Adaptive WebRTC, Pro Arc Physics, and Choose-Your-Lane Landing
April 26, 2026
- WebRTC media constraints now adapt to the device + network. pickVideoConstraints() reads navigator.connection.effectiveType / saveData / downlink and navigator.userAgent at module load: slow networks (2g/3g, saveData, <1.5 Mbps downlink) get 480p/15fps, mobile browsers get 720p/24fps, desktop gets 1080p/30fps. Same fallback semantics as before — getUserMedia ideal/max still gracefully degrades if the camera can't hit the target.
- DEFAULT_AUDIO_CONSTRAINTS made explicit (echoCancellation / noiseSuppression / autoGainControl) so Safari and Firefox match Chrome's defaults instead of dropping to raw mic. Applied to startLocalStream and switchMic so swapping mics keeps the same processing.
- applyVideoSenderPrefs() sets degradationPreference: 'maintain-framerate' on every video sender right after addTrack (in startCall, addPartner, and onOffer) — for video chat smooth motion reads as 'good quality' even when bandwidth drops. setParameters persists across replaceTrack so camera flips and screen-share toggles inherit it.
- Camera tracks now get contentHint='motion', audio gets contentHint='speech' — encoder optimization hint, costs nothing, helps quality on weak uplinks.
- Camera-recovery bug fix: if the audio-only fallback fired once (camera denied) and the user later granted camera permission on retry, cameraUnavailable used to stick at true. Now setCameraUnavailable(false) once video tracks are confirmed.
- cameraConstraints() helper consolidates 5 duplicated getUserMedia constraint sites — switchCamera strategies 1/2/last-resort and stopScreenShare camera reacquire all read from the same source now.
- Major Maggots + Ka-Blam (Scorched Earth) get a new 'Pro Arc' bullet physics mode: 2-substep integration to reduce tunneling on fast shots, light drag (0.998/frame ≈ 12% velocity loss per second at 60fps), and damped wall ricochet that only reduces vx (no fake vy energy loss). Default stays 'Wrap Around' so existing rooms keep current feel; Pro is opt-in via the host's physics picker.
- Game picker on /chat 2-cam mode redesigned as a 4-column card grid showing each game's blurb plus a 'chill' tag for non-shooters (TicTacToe, Chess, Checkers, Dots & Boxes, Trap Star). Multi-cam picker keeps the pill row but appends ' · chill' to non-shooters so the distinction is visible at a glance.
- Landing page gets a 'Choose your lane' 4-button grid below the Forum link: 🎧 Solo, 👥 Buddy, 👥👥 Group, 🗣 Forum. New SessionMode type + preferredMode state + startSession() helper let users skip the mode-selection modal entirely when they pick a lane upfront — age gate fast-paths straight into chat.
- middleware.ts matcher now exempts robots.txt, sitemap.xml, and manifest.json from the geofence so non-Americas SEO crawlers and PWA install flows can still reach those files. Comment added to the config block so the next person knows why those three are listed.
Audit-Driven Hardening Pass + Admin Room Occupancy View
April 24, 2026
- WebRTC data channel throttle: per-peer cap of ~1 inbound message/sec on dc.onmessage so a flooding peer can't lock the main thread by re-render-storming setPartnerGeo. Tracker map is cleared alongside dataChannelsRef on removePartner, startCall, reset, and unmount.
- getUserMedia timeout: wrapped both the camera+audio attempt and the audio-only fallback in a 15s Promise.race so a hung getUserMedia can't deadlock startStreamLock and freeze every subsequent startLocalStream call. Lock now clears in a finally block on every path.
- /api/geo crash guard: spoofed x-vercel-ip-city: %A used to throw URIError out of decodeURIComponent and 500 the route. Wrapped both header decodes in a safeDecode helper that falls back to the raw value on bad percent-encoding.
- /api/og DNS rebinding SSRF blocked: the hostname allowlist ran against the URL string, but fetch() then DNS-resolved it — an attacker pointing public-looking.example.com at 169.254.169.254 (AWS metadata) or any RFC1918 IP slipped through. Now resolves the hostname via dns.promises.lookup before fetch and re-checks every returned IP, with the same guard applied to redirect targets. Pinned the route to the Node.js runtime so dns/promises is available.
- Bounds-check inbound peer events in Chess, TicTacToe, and DotsAndBoxes. Out-of-range coordinates from a malicious peer used to crash the receiver's React tree on the next render (Uncaught TypeError on undefined[idx]). Validate index/coord ranges and role/dir enums before applying each move; silently drop bad payloads.
- Flag clip filenames now include crypto.randomUUID() between the timestamp and reason segments — same-millisecond bursts no longer race on the same Supabase storage key with upsert:false, and filenames are no longer guessable.
- urlBase64ToUint8Array no longer crashes the push subscription flow on malformed VAPID keys. atob() throws DOMException on bad input; the helper now wraps the decode in try/catch and returns null. Subscribe sites already wrap in try/catch so a null applicationServerKey is handled gracefully.
- Server: don't ban the literal string "unknown". When a flagged socket disconnects between flag and submit, the live-socket lookup returns undefined and used to fall through to banIP("unknown") — pollutes the bans table and isn't actionable. Now prefers sessions.get(id).ip (recorded at connect time) before falling back to the live socket, and skips banIP entirely when no real IP is available.
- Server: ban check moved to io.use() handshake middleware. Doing await isBanned(ip) inside io.on("connection") raced with client emits — socket.io drops events arriving before any listener is attached, so an immediate queue:join right after connect could silently disappear during the await. Middleware blocks the handshake itself; handlers register synchronously after it passes. The mid-session "banned" flag stays for the room:join late-ban guard.
- Server: /stream-proxy hard-capped at 2 GB. A compromised or buggy allowed host could otherwise stream forever and tie up the socket. Reader is cancelled and response ended when the cap trips.
- Diagnostic log on queue_join is now always-on (not gated behind DEBUG=1). Every join prints socket id, ip, raw client mode, normalized server mode, vibes, and queue/session counts so "two online but not connecting" can be diagnosed from Railway logs without flipping an env var.
- Admin monitor: new "Where is everyone" panel between the stoplight and system checks. Pulls /admin/debug/sockets in parallel with /admin/status and groups every connected socket into rooms (Sesh, Buddy, Solo waiting, Private /code, in-queue with wait time, Idle). Hosts marked with a star, vibes shown when not "whatever". Best-effort — if the debug endpoint isn't available the panel hides and the rest of the monitor still works.
Removed Sentry + Debug Overlay; Socket Events Centralized; Server Helpers Extracted
April 24, 2026
- Pulled Sentry out of the entire stack — @sentry/nextjs from the Next app, @sentry/node from the Railway server, plus the four config files (instrumentation.ts, instrumentation-client.ts, sentry.edge.config.ts, sentry.server.config.ts) and the withSentryConfig wrapper in next.config.mjs. Reasoning: it only emitted on uncaught crashes (rare on this codebase), and the cost/control trade-off favored relying on existing console.error logging. Every captureError call site already had an adjacent console.error, so error visibility on Railway logs is unchanged.
- Removed the ?debug=1 WebRTC connection diagnostic overlay. The diagnostic ref store, ensureDebug helper, parseCandidateType regex, turnStatusRef, getDebugSnapshot getter, and the ConnectionDebug.tsx component are all gone — useWebRTC.ts is back to its pre-debug shape and ~110 lines lighter. The overlay was added 4/23 to chase the mesh-bandwidth freeze; now superseded.
- All 77 socket event names centralized in app/lib/socketEvents.json — single source of truth shared by the Next.js client (TS literal types via resolveJsonModule) and the standalone Node server (require). 224 string-literal occurrences across 6 files (useChat.ts, useWebRTC.ts, useInstagramStream.ts, server/index.js, server/handlers/moderation.js, server/handlers/streaming.js) replaced with EVENTS.NAME references. Same bytes on the wire (zero runtime impact), but now a typo in any event name fails at compile time instead of becoming a silent ICE-restart-never-fires bug 30 seconds into a session. Renaming an event becomes a one-file change.
- server/index.js shrunk from 2,158 → 1,868 lines (−13%) by extracting eight cohesive helper modules: server/modules/{log,metrics,ip-utils,geo,tokens,db,email,web-push}.js. Each module is a singleton with a clear contract — metrics owns counters + tracking helpers, db owns the Supabase client + ban/flag/appeal queries, email owns Resend + esc + send helpers, tokens owns secret loading + win-token/room-code generation, geo owns the 5000-cap IP→country cache, web-push owns VAPID setup + queue notification factory. Pure refactor: zero behavior change, no events renamed, no DB queries touched. Verified by full module-load test plus boot test (server reaches its listen() call without error).
- server/index.js shrunk further to 1,783 lines (−85 from previous pass, −17% cumulative) by extracting shared mutable state into server/state.js (16 collections: sessions, queue2, queueMulti, buddyQueue, formingRooms, privateRooms, upgradeableSessions, pendingJoinRequests, pendingKickVotes, approvedKicks, lastSyncedPartners, soloSessionTimers, socketToStream, activeStreams, flagCounts, plus MAX_CAM/MAX_QUEUE_SIZE/MODE constants and the syncSnapshot/setSession helpers tightly bound to that state). Skip-pair cooldown logic moved to server/lib/skip-cooldown.js (self-contained — recentSkips Map + wasRecentlySkipped + recordSkip). Private-room expiry/warn timers moved to server/lib/room-timers.js as a factory closing over io. The state module is the foundation for the next pass — matchmaking and group-room logic now have a clean import surface instead of reaching into top-level consts.
- server/index.js shrunk further to 1,575 lines (−208 from previous pass, −27% cumulative) by extracting two more lib modules. server/lib/session-sync.js now owns the throttled online-count broadcast plus both background heartbeats (15s session-sync detecting partner/tv changes, 30s queue prune removing dead sockets) — all three were anonymous setIntervals scattered across the top of index.js. server/lib/group-rooms.js now owns the buddy-queue + forming-room lifecycle for Smoking Buddy and Virtual Sesh modes (joinBuddyQueue, joinOrCreateFormingRoom, tryStartFormingRoom, leaveFormingRoom, plus the generateFormingRoomId helper). leaveCurrentContext stays in index.js for now since it crosses into matchmaking territory (calls removeFromQueue + endSession) — that gets cleaned up when the matchmaking module lands next.
WebRTC Connection Debug Overlay + YouTube Live Verification
April 23, 2026
- New ?debug=1 URL param on /chat opens a corner diagnostic panel for troubleshooting "matched but no connection" reports — primarily a remote-support tool. Shows live per-peer signaling / ICE / connection state, ICE candidate types gathered (host / srflx / relay), selected candidate pair on connect (host→host means same LAN, relay→relay means TURN is carrying the media), TURN reachability, offer→answer→connected timings, and socket status. "copy" button serializes the whole snapshot to clipboard for pasting into a bug report.
- Implementation is observation-only: all diagnostic data lives in a ref so state updates don't trigger re-renders on the hot path. The overlay component is gated behind the URL param and lazy-loaded, so users without ?debug=1 pay zero cost (no polling, no DOM, no extra JS). Socket, signaling, and ICE handlers are augmented — never replaced — so there's no behavior change for the non-debug path.
- Attempted fix for 4+ person room freezing on Samsung/Android (capture resolution cap + adaptive sender bitrate per peer count) was shipped then reverted within 20 minutes after a "2 online but no connection" report came in. Mesh-bandwidth bug remains open; new debug overlay is the tool to diagnose it safely before the next attempt.
- CH 420 (CLN) and CH 710 (Ask Chad GL) were showing stale replays instead of real live streams. YouTube's /channel/ID/live URL returns 200 with the channel landing page when no broadcast is active, and the first videoId in that HTML is typically a featured or past-live clip — the scraper was pinning to it. Fix chases the candidate videoId with a /watch?v= verify and requires "isLiveNow":true before embedding; otherwise the slot falls back to Off Air until the next 30-min refresh picks up a fresh broadcast.
TV Favorites, Sync-Drift Fix, Perf + Audit Pass
April 22, 2026
- TV Favorites (per-device, localStorage): ☆/★ button on the TV face toggles the current channel; the Guide gets a ★ N filter toggle that shows favorites-only with a running count; each row in the Guide has its own star so you can add/remove without changing channels. Stored by channel name (not index) so curator reorders don't break your list. No login required — persists on this browser.
- Fixed TV channel sync drifting apart after ~a minute. Server rate-limits tv:channel at 30/10s and silently drops excess — which also skips the session.tvChannel write, so the 15s session:sync heartbeat compared equal snapshots and never healed partners. Now: host emit is trailing-debounced by 300ms (burst surfing coalesces to the final channel), and the host re-emits its current channel every 20s as a keep-alive so any dropped event gets healed on the next tick.
- Leaderboard wrapped in React.memo + useMemo on the filtered list — stops re-rendering 10 rows every time the landing page ticks.
- /api/news swapped from force-dynamic to revalidate=120 so the Cache-Control header actually works and the three RSS feeds aren't fetched on every page view.
- /api/push-notify now returns 410 on expired subscriptions and 502 on generic send failures instead of always 200 — clients get real feedback when a notification fails to deliver.
- admin/monitor: CheckRow and history timeline keyed by stable id (check.name / h.ts) instead of array index, so React reconciliation doesn't fight the live-updating list.
- .env.local.example documents ALLOWED_ORIGINS, LEADERBOARD_GAMES, STOCK_SYMBOLS; server/.env.example documents SESSION_SECRET, METERED_API_KEY, METERED_APP_NAME. Previously 7 env vars were read by the code but missing from the examples.
- Minor version bumps: next, @sentry/nextjs, @supabase/supabase-js, hls.js, resend, tailwindcss, @tailwindcss/postcss, typescript, eslint-config-next, postcss, @next/third-parties. npm prune removed a handful of extraneous WASM transitives.
- Verified: .env.local and the Google OAuth JSON are properly gitignored (audit false-positive flagged them as 'committed' — they never appear in git history).
Audit Sweep: IPTV Auto-Health, Skip Cooldown, Desync Guard, Tune Button, Event-Name Standardization
April 19, 2026
- IPTV channel health is now auto-probed by the backend every 6 hours (HTTP GET with HLS-magic-byte check). Dead URLs are marked Off Air without manual curation. Results persist to a new Supabase iptv_health table so Railway redeploys don't erase what we've learned.
- First /api/iptv request after a cold start now blocks (up to 15s) on the initial probe, so users see a filtered guide instead of the raw curated list during warm-up. Subsequent requests fast-path.
- New Tune button on the TV remote — random hop to a healthy channel in the current group (falls back to any group if the current slot is Off Air). Brief static flash so the dial-turn feels like an old TV.
- Channel Guide now fully hides no-signal channels instead of showing them greyed out. Dead-via-playback channels (client-discovered this session) get dropped from the list the same way backend-probed Off Air slots already were, so every entry you see is a channel you can actually watch.
- 10-minute skip-pair cooldown: when two people skip each other, the server won't re-match them for 10 minutes. Keyed by IP so the cooldown survives reconnects. Applied across 1-on-1 match, multi-cam room formation, and upgrade-a-solo-room paths.
- endSession(socketId, reason) centralises the cooldown recording — explicit skip / buddy skip / group leave / room join all record "separation"; plain disconnects don't (so a network blip doesn't burn a pairing).
- Pending-join cleanup now runs even when the disconnecting socket had no session yet — previously a requester dropping before the vote finished left partners stuck on the vote popup until the 10s timeout fired silently.
- Game desync guard hardened in Ass and Ka-Blam: only player 0 responds to request_sync (deterministic authority in 3+ player rooms instead of a snapshot race). Both the request and the response are rate-limited to one per 2 seconds.
- WebRTC ICE-restart backoff is now exponential (0s → 3s → 8s over 3 attempts in a 30s window) instead of the old flat 5s cooldown. Mobile Wi-Fi handoffs recover faster without burning the restart budget on flaky networks.
- Private rooms emit a room:expiring:soon event 5 minutes before the 30-min TTL and a room:expired event on kill. Orange banner on the chat page so hosts can share the link to keep the room alive.
- Socket reconnect banner: if the socket drops mid-session, a blue "Reconnecting…" banner now shows instead of the UI silently going stale. Only fires once you've had at least one partner — initial connection still shows the normal status pill.
- In-session Vibes editor: new 🎲 Vibes button in the chat header opens the tag picker pre-populated with your current vibes. update_vibes event updates your session + queue entry so the next match uses the new tags without re-entering from the landing page.
- Socket event names standardised to namespace:action with colons (~190 renames). partner_left → partner:left, create_room → room:create, kick_vote_update → kick:vote:update, tv_channel_changed → tv:channel:changed, webrtc_offer → webrtc:offer, etc. Single-word events (matched, skipped, waiting, typing, message, flagged, banned) left alone.
- Code cohesion: emitMatch2() helper dedupes the setSession + "matched" + "host_changed" pattern across 3 call sites. joinQueueMulti and joinQueueMultiSkip collapsed into one function with a { tryExistingFirst } flag. useAudioDevices, useTileReorder, and useGameState hooks extracted from chat/page.tsx (−130 lines).
- Win-token leaderboard: verified existing impl already has 60s TTL + consume-once replay protection. Audit finding was a false positive — no changes needed.
Ass (formerly Major Maggots), Arrow-Key Movement, 5 New Terrains, American Public Feeds, IPTV Off-Air Cleanup
April 18, 2026
- Major Maggots renamed to Ass (🍑) across the chat games menu, leaderboard tab, settings header, and how-it-works page. File and backend leaderboard key (major-maggots) kept stable to avoid breaking existing wins.
- Maggots replaced with procedural butt-cheek bodies: two tinted hemispheres with center crack, ground shadow, top highlights, idle wobble. Damage tints the flesh redder; death pose still uses the original sprite.
- Cheeks now flap while walking. Each unit tracks its own walk energy + phase as a visual-only ref — cheeks alternate up/down, body sways forward into motion, and each cheek squashes opposite the other so they look like they're taking steps. Energy decays back to the gentle wobble when stopped.
- Arrow keys (←/→) now move the active maggot in Ass and the active tank in Ka-Blam during your move phase. Window-level keydown listener that skips when typing in inputs.
- Five new terrain variants for both Ass and Ka-Blam: Mountain (single tall central peak), Canyon (deep central dip with high cliffs), Islands (three separated humps over a low floor), Flatlands (gentle plain), and Spikes (jagged saw-tooth, lighter smoothing). Type is picked deterministically from the seed (seed % 6) so both clients render the same map. Map name shown top-right of the canvas.
- Post-sesh "Good sesh?" modal disabled — it was firing 30% of the time when a partner left and was proving too intrusive. Component wiring left in place for easy re-enable.
- Dev-only CSP: 'unsafe-eval' added to script-src in NODE_ENV !== production so Next 16 Turbopack source maps and React dev mode stop throwing console errors. Production CSP unchanged.
- IPTV channel deletion model reworked: instead of removing entries (which renumbered every channel above the gap), deleted channels are now replaced with Off Air placeholders so channel numbers stay stable. Channel up/down/auto-scan skip past Off Air slots, and the Channel Guide hides them entirely — users get jumped straight to the next working feed.
- route.ts now matches the trailing OFF_AIR fill by reference when assigning live RTMP streams, so a live stream can't overwrite an intentional Off Air placeholder.
- 29 channels marked Off Air based on user testing for dead feeds (CH 9, 10, 17, 21, 24, 26, 30, 31, 33, 41, 47, 56, 57, 67, then 70, 73, 74, 91, 92, 98, 109, 110, 111, 115, 120, 121, 122, 125, 131).
- New "American Public Feeds" channel group (38 streams) curated from iptv-org's us_local registry: DC.gov channels, LA CityView, Long Beach, Houston (HTV 1/2), Galveston, Atlanta, New Orleans, Mobile County schools, Palm Beaches, Miami Beach, Jacksonville Freedom Fountain cam, Pasco TV, White Plains, Greensboro, Champaign, Colorado Front Range cities (Lakewood, Fort Collins, Thornton, Golden, Loveland, Brighton, Littleton), Yuma AZ, Glendale AZ, plus several California city/community channels.
Connection Audit Fixes, Tile Drag on Mobile, +421 IPTV Channels, Lint Cleanup
April 18, 2026
- Kick-submit now requires a successful vote. New approvedKicks map on the server populates only when kick_vote_cast crosses the threshold — the initiator gets a single 60s window to submit, and the entry is deleted on consume. Previously any client in a multi-cam session could emit kick_submit directly; 3 unique reporters on the same target still triggered the auto-ban path with zero voting ever happening.
- ICE-restart watchdog no longer burns through its 3-attempt budget on InvalidStateError. triggerIceRestart now bails early when pc.signalingState !== "stable" (both on entry and after the cooldown), so glare / mid-renegotiation doesn't silently consume real restart slots on flaky mobile connections.
- Vote-approved joiner no longer loses their PCs to a status flap. useChatSession now latches hasStartedCallRef inside the joinApproved async loop, so a matched→connecting→matched bounce (after joinApprovedPartnerIds clears) doesn't re-fire startCall and close the just-built peer connections.
- Video-tile reorder works again on phones. Press-and-hold the middle of any cam square for ~0.5s to pick it up, then drag onto another square to swap. Button targets (mute, expand, kick-vote, pass-host, name edit) are excluded from drag so taps on those keep doing their normal thing.
- Fixed the underlying reason drag was silently failing: tiles were relying on default touch-action which let mobile browsers cancel the long-press as a scroll gesture before it armed. Now set touch-action: none on tiles (grid already has overflow-hidden, no scroll was expected there) and setPointerCapture on pointerdown so all subsequent move/up events stay pinned to the picked-up tile.
- IPTV library grew by 421 channels — all HTTPS, CORS-verified against the site's origin, and deduplicated against the existing 2000 entries. Pulled from iptv-org's categorized playlists (Movies, Sports, News, Music, Comedy, and supporting categories); filtered out geo-blocked, non-24/7, and user-agent-required streams that don't play in-browser.
- React 19 lint surface cleaned: 28 warnings — 0 errors, 0 warnings. Unavoidable patterns (game-event processing from peer sockets, client-only feature detection, fetch-then-clear-loading) get targeted eslint-disable comments with reasons. Ref-during-render assignments in RoomTV and ScorchedEarth moved into useEffect. Missing-deps fixed in useWebRTC.addPartner and two ShootMe effects.
Sentry Error Tracking, Stream CPU Fix, Audit Cleanup
April 14, 2026
- Sentry error tracking wired up: production-only, gated on DSN env vars so local builds skip the SDK entirely. Session replay enabled on errors only (10% sample) to keep bundle impact low. CSP updated with worker-src for Replay.
- Instagram stream no longer hangs the in-app TV: bitrate reduced 4Mbps → 1.8Mbps, canvas capture dropped from 30fps (frame-skipped) to native 15fps, and HLS lowLatencyMode disabled while streaming. CPU headroom restored — TV playback stays smooth while broadcasting.
- RoomTV HLS retry timer leak fixed: _retryTimer was stashed on the old HLS instance that gets nulled/replaced on retry, so unmount cleanup never cleared it. Moved to a dedicated ref.
- Canvas reframing pipeline deduplicated: three identical 720×1280 letterbox loops (room mode, camera mode, stream takeover) collapsed into a single startReframe() helper — net −53 lines.
- Removed orphan server emit (room_has_stream) that had no client listener, plus archive-debris directories (files/, files_extracted/).
Shoot Me (new multiplayer game), Cam-to-Cam Fix, CSP Hardening
April 13, 2026
- New game — Shoot Me: 8-player real-time top-down tank combat inspired by Atari Combat. First to 10 kills wins. Seeded maze with mirrored wall layout for fairness. WASD/arrows + space to fire, plus on-screen touch controls for mobile.
- Shoot Me physics options (host-selectable in lobby): Bouncy (bullets ricochet off walls up to 3 times) or Absorb (walls kill bullets on contact).
- Shoot Me netcode: each client owns its own tank and claims its own deaths. 5Hz snapshots with velocity included so receivers extrapolate smoothly between updates — keeps bandwidth below the game_event rate limit while motion stays fluid.
- Shoot Me security hardening: all peer-supplied numeric payloads validated with Number.isFinite + range bounds (NaN/Infinity injection blocked). Hit events require the bullet to exist locally AND be owned by the claimed shooter — peers can't fabricate kills on bullets that were never fired. Live bullet count per peer capped at 4 to prevent spam.
- Cam-to-cam connections restored: WebRTC ICE uses stun: and turn: URI schemes which weren't covered by the CSP's https: allowlist. Newer Chrome/Safari builds enforce connect-src for ICE endpoints. Added stun:/turn:/turns: to connect-src.
- Forum login fonts self-hosted: Google Fonts @import was blocked after CSP tightening. Migrated DM Sans + Playfair Display to next/font/google in a login-scoped layout — fonts now bundled at build time, zero external fetches.
- Server game_event rate limit raised 30/5s → 60/5s to give Shoot Me headroom for snapshots + fire bursts + hit events. Turn-based games unaffected.
- Leaderboard API playerCount cap raised 5 → 8 so Shoot Me winners can actually save to the leaderboard (tokens are signed with playerCount; a 5-cap clamp was rejecting 8-player tokens as signature-mismatched).
- Server now distinguishes games by seed-event payload shape (physics field → shoot-me, bulletPhysics/playerCount → major-maggots) so the correct player-cap is used when minting win tokens.
- Supabase schema: game_wins CHECK constraint expanded to include 'shoot-me'.
- Leaderboard landing page now has a Shoot Me tab alongside Ka-Blam and Major Maggots.
Stream Quality Overhaul, Code Audits, Multi-Cam Fix, Security Hardening
April 11, 2026
- Instagram camera stream no longer blurry: canvas reframing (720×1280 portrait) now applied to camera mode, room mode, and stream takeover — all three paths produce consistent quality.
- Stream encoding pipeline overhauled: client bitrate raised to 4Mbps VP8, server uses CRF 25 with ultrafast preset and 4Mbps maxrate cap. Eliminated dial-up effect caused by FFmpeg backpressure on constrained containers.
- Canvas draw loop optimized: switched from 60fps requestAnimationFrame to frame-skipping RAF (~30fps). Matches captureStream(30) sample rate, halves CPU usage. Fixed flicker caused by setInterval desync.
- Stream chunk rate limit raised from 10/sec to 15/sec — 100ms MediaRecorder interval was hitting the limit with zero headroom on timing jitter.
- Backpressure logging added to FFmpeg stdin — Railway logs now show when chunks are silently dropped.
- Multi-cam 3rd person fix: when a 3rd person joins, a race between addPartner and onOffer could leave the peer connection without local tracks (camera/mic). Now ensures tracks are always attached regardless of which code path creates the PC first.
- TV and radio restored: CSP connect-src and media-src opened for IPTV streams (1087 unique CDN domains) and radio-browser.info API. Script-src, img-src, and frame-src remain locked down.
- Backend monitor fixed: /admin/status crashed with ReferenceError (rateLimits not defined) after rate-limiting module extraction. Added getSizes() export. Monitor now handles non-JSON responses gracefully.
- Vibes config consolidated: single source of truth in app/lib/vibes.ts, imported by VibesBar and forum page. Eliminated duplicate lists.
- Stock symbols, ALLOWED_ORIGINS, and leaderboard games now configurable via env vars (STOCK_SYMBOLS, ALLOWED_ORIGINS, LEADERBOARD_GAMES) with backwards-compatible defaults.
- Push notification URL validation now derives allowed hosts from ALLOWED_ORIGINS instead of maintaining a separate hardcoded list.
- urlBase64ToUint8Array utility deduplicated: extracted to app/lib/base64.ts from 3 copy-pasted instances.
- News API: skip RSS items with empty or non-HTTP links. LinkPreview: added AbortController + 8s timeout. StockTicker: useMemo for interleaved items so memo(TickerContent) actually works.
- WebRTC geo data: validate value types on peer-sent data, not just key names. parseInt radix fix in leaderboard token verification.
- Forming room host transfer: validate new host is still connected before assigning. Dissolve room if all members disconnected.
- approveJoin: delete original partners key from upgradeableSessions (not filtered livePartners key) to prevent orphaned entries.
- Typing indicator no longer leaks across sessions — timeout clears automatically when leaving matched state.
- Instagram stream rapid start/stop guard: cleanupStream called before starting new stream to prevent canvas corruption.
- Security: x-forwarded-for validated against IP format regex before trusting. Buddy queue deduplication prevents duplicate entries on rapid skip.
- Video element cleanup: hidden canvas video elements now tracked in ref, paused and nulled on stream stop.
Room Analytics, Game Minimize, Race Fix, UI Animations
April 8, 2026
- Room selection analytics: backend now tracks solo/buddy/group mode selections. New modes counter in /admin/status endpoint.
- Admin console: Room Selection card added to monitor dashboard — shows per-mode counts with percentage bars and totals.
- Health alert emails now include Room Selection breakdown table (buddy/group/solo counts).
- Game minimize: new ‘‒’ button on game overlay lets you minimize to a floating pill — access TV, radio, and chat mid-game. Game state preserved (component stays mounted via opacity toggle).
- Virtual Sesh race condition fixed: two users picking group mode simultaneously no longer create separate solo rooms. New becomeSoloRoom() function auto-merges if another solo room exists.
- becomeSoloRoom extracted as single source of truth — eliminates duplicated solo room creation logic in joinQueueMulti and joinQueueMultiSkip.
- Double-fire prevention: socket not added to upgradeableSessions until after merge check passes.
- Periodic cleanup expanded: buddyQueue and upgradeableSessions now pruned every 30s (removes entries from disconnected sockets).
- approveJoin hardened: filters to live partners (connected + valid session) instead of failing entirely if any partner dropped during vote window.
- Self-kick blocked: kick_vote_start now rejects targetId === socket.id.
- Self-flag blocked: flag handler rejects if resolved partnerId is the reporter.
- Video memory leak fixed: VideoBox now clears srcObject on unmount/stream change to release MediaStream references.
- UI fluidity overhaul: added fadeIn, scaleIn, slideUp, slideDown keyframes to Tailwind config. All modals and overlays now animate in smoothly.
- Animated components: age gate, vibes picker, mode selection, game overlay, ModalStack (vote/invite/error/stream key), kick vote toast, post-sesh prompt.
SEO Canonical Fix, Rate Limit Alert Overhaul
April 8, 2026
- Fixed sitemap/canonical mismatch: next-sitemap siteUrl changed from theweedvlog.com to www.theweedvlog.com to match metadataBase.
- Fixed root canonical tag applied to all pages: removed global alternates from layout.tsx. Homepage extracted to server component (app/page.tsx) with its own canonical. Client code moved to app/HomePage.tsx.
- Added page-specific canonical tags to 6 public pages missing them: community-guidelines, privacy, terms, updates, vibes/gamers, vibes/night-owls.
- Rate limit alert counter now time-windowed: resets every hour instead of accumulating since server start. Alert thresholds raised to 200 (yellow) / 1000 (red) to account for normal WebRTC ICE churn.
- Rate limit blocked events now tracked per event type (blockedByEvent) — /admin/status shows breakdown so webrtc_ice (normal) vs join_queue (suspicious) are distinguishable.
Security Hardening, Code Refactor, 2K Channels, Growth Features
April 6, 2026
- 6-pass security audit: hostname validation, HMAC full 256-bit, admin auth timing attack, CSP tightened, rate limit memory cleanup, email regex hardened, CVE patches (socket.io, path-to-regexp).
- Pen test (14/15 passed): CORS, CSP, auth bypass, SSRF, XSS, path traversal, rate limiting all verified. Load test: zero errors under sustained load.
- Code refactor: page.tsx 1,527 → 746 lines (-51%). Extracted VideoGrid, ControlBar, ChatPanel, ModalStack. New useChatSession orchestration hook. Instagram streaming extracted to useInstagramStream.
- Server disconnect handler split into 4 scoped cleanup callbacks (streams, flags, votes, rooms).
- Growth features: post-sesh modal (30% chance, links to forum + anonymous push opt-in), queue wait prompt (60s → 'ping me when someone joins' via web-push).
- 1,750 English IPTV channels added from iptv-org/iptv. TV Guide popup with search, grouped channels, live/dead indicators, and paginated loading.
- Instagram Live audio fixed: added audioBitsPerSecond to MediaRecorder. FFmpeg upgraded to stereo AAC at 128kbps.
- Instagram stream now survives mode switches (solo → group). Only full leave kills the stream.
- Per-partner mute button: hover any video tile to mute/unmute that person locally.
- Solo grid layout fixed: camera fills full width when alone. Grid uses total tile count (people + TV) for column decisions.
- iPhone TV volume fixed: AudioContext GainNode auto-created on first unmute (iOS ignores video.volume).
- 4th-person room crash fixed: race condition in addPartner where onOffer overwrites PC during iceReady await.
- Ghost socket fixed: pass_host now verifies target socket is still connected.
- Hydration error fixed: localStorage reads moved from useState initializers to useEffect.
- Audio output switching restored after component extraction. Per-element setSinkId on all video/audio tags.
- Dense IPTV packing: no more Off Air filler channels. CH 420 (CLN) and CH 710 (Ask Chad GL) reserved at exact slots.
Multi-Cam Fixes, Instagram 9:16, Security Audits & Solo Room Flow
April 5, 2026
- Group sesh: first person to join now becomes the room — next person connects directly with no queue wait or vote.
- Solo rooms auto-approve joins instantly (skip 10s vote for 1-person rooms).
- Fixed multi-cam join delay: eliminated SDP glare by having existing partners initiate toward joiner — no more double-offer rollback.
- Parallelized WebRTC offer creation in startCall with Promise.all for faster 3-way queue matches.
- Fixed floating connections: added removePartner() to close specific peer connections immediately when a partner leaves a group session.
- Fixed stuck 'Connecting...' status: set status to 'waiting' on socket connect before server responds.
- Socket transport order changed to polling-first — websocket-first was causing delays behind proxies/firewalls.
- Instagram Live stream reframed to 9:16 portrait via canvas — tab capture rendered onto 720×1280 canvas at 30fps with centered letterbox.
- FFmpeg video filter updated to scale+pad 720×1280 as server-side safety net for Instagram's vertical format.
- TV (RoomTV) HLS error recovery: fatal errors now get one retry (recoverMediaError or full reload) before showing 'No Signal'.
- TV static overlay now waits for actual 'playing' event instead of a blind timer — no more black screen gap between static and stream.
- CSP: removed fragile SCRIPT_SRC_PLACEHOLDER string matching — buildCSP(nonce) constructs directives directly.
- JSON-LD: escaped < as \u003c in dangerouslySetInnerHTML to prevent script breakout.
- Chat message sanitization: removed server-side HTML encoding (React JSX handles escaping).
- Security audit: fixed SSRF in /stream-proxy (port validation was inverted, allowing port 5432).
- Security audit: kick vote now verifies voter has active session with target.
- Security audit: auto-ban checks isBanned() before banIP() to prevent race with concurrent flags.
- Security audit: joinQueueMulti recursion capped at 10 excluded keys to prevent queue pollution.
- Security audit: game events reject objects nested >5 levels deep.
- Security audit: private room host transferred on disconnect instead of deleting room.
- Security audit: stream handoff re-checks s.ended after feeder selection to prevent race.
- Security audit: join_room snapshots connected partners atomically.
- Security audit: kick vote voters changed from Array to Set for O(1) lookups.
- Security audit: stream proxy rejects non-alphanumeric hostnames (blocks hex/octal IP bypass).
- Security audit: lift-ban IPv4 regex validates 0–255 octets.
- Security audit: OG image URLs reject path traversal (..) patterns.
- Leaderboard token cleanup: periodic 30s prune + hard cap at 2000 entries.
- FFmpeg: SIGKILL escalation after 5s if SIGTERM ignored — prevents zombie processes.
- FFmpeg: backpressure fix — cleanup always resumes socket (was stalling forever on FFmpeg crash).
- FFmpeg: stderr.resume() prevents backpressure buildup.
- ICE candidate buffer capped at 100 per partner to prevent memory leak.
- Data channel geo messages validated for expected keys only.
- Email validation upgraded from @ check to proper user@domain.tld regex.
- Metrics event type map capped at 100 entries.
- Service worker retries navigation once after 1.5s before showing offline page.
- Camera fallback: cameraUnavailable state exposed to UI with 'Audio only' badge.
- Stable refs for addPartner/removePartner in useEffects to prevent stale closures.
Code Audit: Security, Performance & Cleanup
April 4, 2026
- Removed 'unsafe-eval' from CSP script-src — tighter content security policy.
- Fixed session key signature truncation vulnerability — length check before timing-safe comparison.
- Added SSRF protection: radio and clip URLs now blocked from targeting private/internal IPs.
- Room codes now use crypto.randomBytes instead of Math.random for cryptographic safety.
- Geo lookup no longer falls back to untrusted client data when server lookup fails.
- Added error boundaries to chat, forum, forum post, and admin monitor routes.
- Added aria-labels to TV remote controls, theme toggle, and volume buttons for accessibility.
- Added Cache-Control headers to leaderboard API (s-maxage=30, stale-while-revalidate).
- Capped TV channel index validation from 5000 to 710 to match actual channel count.
- Extracted inline arrays (How It Works, Day Ones, Social Proof, Vibes, Footer) to module-scope constants — eliminates re-creation on every render.
- Memoized StockTicker TickerContent component with React.memo.
- Exposed connectionError state from useChat so UI can show socket failures.
- Improved GameEvent type: now requires a type field.
- Screen share 'ended' event listener uses { once: true } to prevent memory leaks.
- Stripped dead code: removed unused request_join_live socket handler, unused VIBES export, unused Tailwind marquee/ticker animations.
- Supabase env vars now fail hard on missing instead of silently using empty strings.
- Added AbortSignal.timeout to 7 unguarded fetch calls: TURN credentials, radio-browser, ip-api, metered, suggestion submit, leaderboard token and win submission.
Sprite Maggots, Security Audit #4 & Refactor
April 3, 2026
- Major Maggots: replaced canvas-drawn maggots with Metal Slug sprite sheet — crawl animation, sprite-based explosions, and death remnant sprites.
- Player color tinting via offscreen canvas compositing — each player's maggots have a distinct color overlay on the sprite.
- Maggot sprites auto-flip based on screen position (left half faces right, right half faces left).
- Damage shown as red tint overlay + blood droplets on sprites, scaling with HP loss.
- Fixed z-index hierarchy across all overlays: z-30 toasts, z-40 settings, z-50 game, z-60 critical modals — game overlay no longer hidden behind popups.
- Extracted shared Modal component for join-request, invite, and room-error overlays — removed triple-pasted wrapper divs.
- Major Maggots refactored: replaced 11 individual state-mirror refs with single game state ref (gs.current) as single source of truth for the animation loop.
- Synced setter pattern eliminates dual update paths — React state and game ref always agree.
- Removed callback ref pattern — game functions read gs.current directly, event handler lists them as proper dependencies.
- Fixed impact timer leak: cleanup on component unmount prevents orphaned setTimeout calls.
- Removed dead code: projectile state (never read), blinkTint (unused after sprite work).
- Extracted seededRandom utility, input validators (isFin, isValidWeapon, isValidPhysics), and drawing helpers (drawMaggot, drawProjectile, drawExplosion).
- Added bounds checking on angle (0–180) and power (10–100) in event handler to reject out-of-range values.
- Chat page fixes: fixed stale sendGeo closure via ref, merged duplicate game event watchers, removed manual DOM setSinkId (components handle it), extracted game list constants, consistent socket.id null guards.
- Geo fetch failures now logged instead of silently swallowed.
- Security audit #4: session keys now HMAC-SHA256 signed to prevent forged room joins.
- Push notification URL validated to http/https only — blocks javascript: and data: scheme injection.
- OG meta parser regex hardened against ReDoS with bounded quantifiers.
- Backend: pendingJoinRequests cleaned on disconnect, privateRooms capped at 1000, WebRTC payloads capped at 64KB.
- ipGeoCache eviction replaced O(n) sort with O(1) FIFO using Map insertion order.
- Startup warning if ADMIN_SECRET env var is not set.
- Fixed multiplayer desync: projectile going off-screen now sends end_turn event to all players instead of advancing locally only.
- Fixed stale sendGeo closure in commitName — was using captured function instead of ref.
- Game type runtime validation added before casting — prevents invalid game_start events from setting bad state.
- HMAC signature comparison hardened: padded buffer to fixed length before timingSafeEqual to prevent timing-based length leaks.
- Nonce-based CSP: every request generates a unique nonce via middleware — unsafe-inline fully removed from script-src.
- All scripts (Next.js chunks, hydration, JSON-LD, service worker, Google Analytics) receive per-request nonce automatically.
- CSP moved from static next.config.mjs headers to dynamic middleware.ts for per-request nonce generation.
- State serialization for Major Maggots and Ka-Blam: each end-of-turn sends a state hash, receiving client compares and requests full resync if mismatched.
- Desync recovery: request_sync triggers a full state snapshot (maggots/tanks, terrain, turn, wind, winner) from the authoritative client.
- Ka-Blam: fixed silent turn advance on projectile miss — now sends end_turn event to all players.
- Ka-Blam: impact-based turn advance now also sends state hash for desync detection.
- Leaderboard: game winners can enter their name (up to 20 chars) after winning Ka-Blam or Major Maggots.
- Landing page leaderboard section with tabbed view — shows top 10 winners per game with rank, name, player count, and date.
- New game_wins Supabase table with RLS — public read, server-only write via API route.
- Leaderboard API: GET /api/leaderboard (fetch top 20), POST /api/leaderboard (submit win, rate-limited to 5/min per IP).
- Leaderboard hardened: win submissions require HMAC-signed single-use token (60s expiry) — prevents casual API abuse and replay attacks.
- Server-side winner name validation: alphanumeric + safe chars only, varchar(20) DB constraint.
- Leaderboard token buffer truncation fix: exact 16-char signature length enforced before comparison.
- Error feedback: submit shows 'Failed to save' on error instead of false success. Leaderboard shows 'Couldn’t load' on fetch failure.
- Angle range extended to 0–180 degrees for both Ka-Blam and Major Maggots (was 5–85).
- Extracted shared hooks: useGameSounds (fire + impact audio) and useLeaderboard (token fetch, name submission, reset) — eliminates duplication between both games.
- Fixed 3-user WebRTC: server now designates which existing user creates the offer when a 3rd person joins — prevents dual-offer race condition that broke video connections.
- ICE candidate rate limit raised from 80 to 120 for 3+ user sessions to handle extra signaling volume.
- IPTV overhaul: replaced external GitHub playlist fetching with curated local channel list (498 verified working streams across 29 categories).
- Reserved channels: CH 420 = CLN (Cannabis Legalization News), CH 710 = Ask Chad GL.
- Fixed TV sync desync: removed client-side channel array padding that caused different channel indices between host and partner.
- CH 420 and CH 710 auto-update from YouTube RSS — latest video IDs fetched every 30 min, no manual updates needed.
- Fixed channel numbering: array padded to 710 entries at build time so channel numbers match what users see in the TV.
- Removed dead youtube-feed API route (replaced by IPTV route).
- Security: upgraded 39 HTTP stream URLs to HTTPS to prevent mixed content blocking.
- YouTube videoId validation: must be exactly 11 alphanumeric chars. Title sanitized and capped at 200 chars.
- Removed CDN cache on IPTV route — was serving stale channel list after deploys.
Game Settings, Tank Movement & Impact Sound
April 3, 2026
- Ka-Blam & Major Maggots: host now picks bullet physics before the game starts — Original (falls off screen), Wrap Around, or Bounce (bounces off walls).
- Ka-Blam: tanks can now move left and right with a 60-unit move budget per turn.
- Non-host players see a waiting screen until the host starts the game.
- Physics mode displayed in-game status and preserved across rematches.
- Explosion impact sound effect added to both Ka-Blam and Major Maggots — plays on every bullet landing.
- Security check: all game event fields validated as finite numbers before use, no raw user strings rendered as HTML, audio files loaded from same-origin /public only.
Security Audit, Live Streaming, Ka-Blam & Fixes
April 2, 2026
- Full security audit: timing-safe admin secret comparison using crypto.timingSafeEqual — prevents timing attacks on admin auth.
- Admin auth now header-only (x-admin-secret) — removed request body fallback that could leak secrets in logs.
- HSTS header added: enforces HTTPS with max-age=31536000.
- Permissions-Policy header added: restricts camera and microphone to same-origin, blocks geolocation abuse.
- CSP frame-ancestors 'none' added — replaces deprecated X-Frame-Options for clickjacking protection.
- IP validation on /admin/lift-ban: rejects malformed IP addresses before querying the database.
- HTML email escaping hardened: single-quote now escaped to prevent attribute-context injection.
- CSP connect-src scoped wss: to Railway server only — WebSocket connections restricted to the backend.
- IPTV channel filter loosened: accepts all valid HTTP/HTTPS stream URLs instead of only .m3u8 — picks up ~6% more channels.
- RoomTV native video fallback: non-HLS streams now fall back to native <video> playback instead of silently failing.
- Radio and TV playback restored: CSP media-src and connect-src corrected to allow external IPTV streams and radio-browser.info.
- Radio station names now visible: fixed text color inheritance in portaled overlay — names were black-on-dark due to rendering outside the app's color context.
- RoomTV channel switch cleanup: video now pauses, clears source, and removes event listeners on every channel change — prevents audio leaks from previous channels.
- Solo Go Live now works: added searching state with visual feedback — button hides and status switches to 'Finding match' after clicking.
- Solo Go Live pecking order: tries 1-on-1 match first, falls back to group queue if no partner available — new solo_live server mode.
- Group session connection fix: pruned stale (disconnected) socket IDs from room membership before capacity checks and session creation.
- Private room host failover: if the original room host disconnects, the next connected member is automatically promoted.
- startCall effect dependency fix: now watches partnerIds alongside status — prevents missed WebRTC connections when state updates arrive in separate render cycles.
- Vote-to-join failure handling: all partners now notified when approveJoin fails due to stale sessions, instead of silently re-queuing the requester.
- Session picker tagline: added 'It's 4:20 Somewhere' below the session type selection.
- Health alert false positives fixed: external service error rate alerts now require minimum 5 requests before triggering — no more red alerts from startup noise.
- ip-api geo lookups fixed: switched from HTTPS to HTTP — free tier returns 403 on HTTPS. This was causing 50% error rates on location labels.
- TURN credential race condition fixed: WebRTC peer connections now await TURN server fetch before creating — prevents cross-region calls from using stale fallback TURN servers.
- Ka-Blam (Scorched Earth tribute): rewritten for 2–5 players with unique colors, turn rotation, aim direction toggle, and last-tank-standing win condition. Now available in Virtual Sesh.
- WebRTC rate limits raised for multi-party mesh: ICE candidates 30→80/10s, offers/answers 20→40/10s — 3-person mesh was hitting old limits and dropping connections on phones.
- Rate limit alert thresholds tuned: red at 500 (was 50), yellow at 100 (was 10) — WebRTC setup legitimately generates high signaling volume.
- Security audit #2: clipUrl and radio URL now validated to HTTP(S) only, isSafeUrl uses URL parser instead of string checks, WebRTC data channel messages capped at 2KB, YouTube/TikTok embed IDs format-validated.
- Removed dead openrelay.metered.ca TURN fallbacks — reduces paid TURN usage by preventing unnecessary relay when direct connections would work.
- Ka-Blam barrel now follows angle slider in real-time. Projectiles wrap around the map horizontally instead of being a miss.
- Live streaming via Livepeer: 📡 button in room pushes host camera/mic to Livepeer via WHIP. Viewers watch at /live with HLS playback. Also supports OBS via RTMP stream key.
- /live page: browse active live streams, Go Live button generates stream key + RTMP URL for OBS setup, auto-refreshing stream grid.
- iOS tile drag fix: long press threshold raised to 600ms, touch-none only applied during active drag, finger movement cancels long press to allow scrolling.
- TV channel name overlay auto-hides after 3 seconds on mobile — no longer covers video content permanently.
- Geo label fix: city names from Vercel headers now URL-decoded (Las%20Cruces → Las Cruces).
- TV channel sync fix: server channel index cap raised from 999 to 5000 — padded channel list for CH 420/710 was exceeding the old limit, silently breaking group sync.
- Stop Camera button removed from control bar (redundant).
- Major Maggots: new Worms-style game — 2–5 players, 3 maggots each, walk around terrain, two weapons (Poop Catapult and Manure Bomb), 30-second turn timer, destructible terrain, wind.
- Major Maggots damage phases: healthy → minor cuts (green blink) → deep cuts with blood (yellow blink) → critical with blood loss (red blink) → death explosion with gore splatter and x_x skull remains.
- Ka-Blam renamed from Mortar Maggot. Custom tank fire sound effect added.
- Custom vote notification sound: plays your recording when someone requests to join the room.
- 📡 Stream key button in room: generates Livepeer RTMP URL + stream key for OBS broadcasting. Popup with copy buttons.
- Security audit #3: removed unprotected stream DELETE endpoint, flag handler now accepts targetId for multi-cam rooms, game event type validation prevents NaN/Infinity crashes.
- Code review cleanup: fixed user crash in stream API, added stream list caching, moved render-phase setState to useEffect, reuse ImageData in TV static, iOS drag uses stored coordinates.
- Game sync fix: game_start event type cast was missing Ka-Blam, Major Maggots, and Trap Star — partner's game wouldn't open.
Security Audit & Framework Upgrade
April 1, 2026
- Next.js upgraded from 14 to 15.5.14 — resolves 4 high-severity CVEs (image optimizer DoS, HTTP smuggling, RSC deserialization, disk cache growth).
- npm audit now reports zero vulnerabilities — patched brace-expansion, flatted, picomatch, socket.io-parser, glob, and eslint-config-next.
- Hardcoded Railway server URLs replaced with NEXT_PUBLIC_SERVER_URL environment variable across 5 files.
- Supabase getSupabase() no longer falls back to anon key — requires SUPABASE_SERVICE_ROLE_KEY, preventing privilege escalation.
- Server ban race condition fixed — isBanned() now awaited before registering socket event handlers.
- Vote kick no longer auto-bans on first report — requires 3 unique reporters, consistent with flag handler.
- All async socket handlers (join_queue, join_room, kick_submit, flag, appeal) wrapped in try-catch.
- banIP() and liftBan() now have internal error handling — Supabase failures no longer crash callers.
- Admin secret removed from query params — now header-only (x-admin-secret), no longer leaks in URLs/logs.
- Admin secret storage moved from localStorage to sessionStorage — clears when tab closes.
- Rate limiting added to push-notify (10/user/min) and suggestions (3/IP/hour) API routes.
- CSP hardened: removed 'unsafe-eval' from script-src.
- ReDoS fixes: non-greedy regex in OG meta tag parser and message sanitization.
- SSRF protection strengthened: blocks IPv6, 0.0.0.0/8, 240+/4 reserved ranges, validates IPv4 octets.
- Session keys in /live-rooms endpoint now base64-encoded — no longer exposes raw socket IDs.
- Lock check bypass fixed in request_join_live — uses sorted key to match upgradeableSessions.
- IP geolocation switched from HTTP to HTTPS, with response status checks on ip-api and TURN fetches.
- IP geo cache now has LRU eviction when exceeding 5,000 entries.
- useChat stale closure fixed — vibes/mode now use refs so socket handlers read latest values.
- RSS news feed now extracts article links (were always empty) with title length cap at 500 chars.
- Internal <a> tags replaced with Next.js <Link> components across all pages.
- Node engine requirement lowered from >=22 to >=20 to match current runtime.
Security, SEO & Code Quality Hardening
April 1, 2026
- Supabase RLS enabled on forum_posts and forum_replies tables with full CRUD policies — database-level access control now enforced.
- Google OAuth age gate bypass fixed — OAuth users without a DOB on file are now caught and routed through the age verification flow before accessing the forum.
- GA4 cleanup: removed 4 Vercel preview domains from cross-domain config, added vercel.app to unwanted referrals list.
- Sitemap fixed: corrected siteUrl from www to non-www, now generates 21 URLs. Submitted to Google Search Console.
- Google Search Console: removed duplicate www sitemap, requested indexing for 8 key routes.
- Trap Star UI reverted to modern dark Tailwind interface per user feedback — all extra scenarios and police sirens preserved.
- New shared API utilities (app/lib/api-utils.ts): extracted getSupabase() and authenticateRequest() used by 4 API routes — eliminated ~70 lines of duplicate code.
- New useSupabase() hook (app/hooks/useSupabase.ts): centralizes Supabase client + auth state with onAuthStateChange listener. Replaced scattered getSession() calls across forum pages.
- Deduplicated RadioStation interface — single source of truth in useChat.ts.
- Removed 9 debug console.log/warn statements from client-side hooks and components.
- Added ARIA labels to FlagButton, SuggestionBox close button, and VibesBar tag remove buttons.
- Added .gitattributes to enforce LF line endings — eliminates CRLF warnings on Windows.
- useClipUpload.ts simplified: removed redundant Supabase client creation, now uses shared instance.
Code Cleanup & Best Practices
April 1, 2026
- Google Analytics switched from raw script injection (dangerouslySetInnerHTML) to @next/third-parties GoogleAnalytics component — safer, faster, and auto-optimized by Next.js.
- Brand color palette centralized in tailwind.config.ts — bg-brand-dark, bg-brand-darker, bg-brand-forest, border-brand-gold, bg-brand-crimson replace 20+ hardcoded hex values across 19 files.
- FAQ brand consistency: "TouchGrass" references in two questions corrected to "Dope Vibes" to match site-wide branding.
- Reusable ContactLink component extracted in privacy, terms, and community guidelines — 11 duplicate "suggestion box" links now share one source of truth.
- Reusable FeatureItem component extracted on How It Works — 5 repeated icon-label-description blocks consolidated into one component.
- Landing page CTA updated: "Into The Weeds" renamed to "Become The Plug (click me)".
Trap Star: Retro UI (Reverted) & Major Feature Drop
March 31, 2026
- Trap Star was reskinned with a retro Windows 98 aesthetic inspired by Drug Lord 2 — reverted to original modern dark UI on April 1 based on user feedback. All gameplay features kept.
SEO Overhaul, Trap Star Multiplayer, 5 New Features & 10 Scenario Categories
March 31, 2026
- SEO: unique Open Graph and Twitter metadata per page — home, about, how it works, smoke sesh, creators, and forum each have keyword-targeted titles, descriptions, and canonical URLs.
- FAQ page at /faq with 10 questions targeting long-tail search terms. Includes FAQPage JSON-LD schema for Google rich results.
- OnlineCommunity structured data added to root layout JSON-LD — helps Google understand the site as a 21+ cannabis social network.
- Alt text audit: all images across the app now have descriptive alt text for accessibility and SEO.
- Shared site footer on every page with internal links for crawlability.
- Trap Star multiplayer: everyone in a Virtual Sesh room competes simultaneously. Ranked leaderboard at game over.
- Maui Wowie added as the 8th product — sits between Purple Haze and Edibles, priced $90–$700.
- 10 scenario categories with ~50 unique events: garden raids, police (bribe or flee), customer burns & snitches, bad storage, stoner uncle inheritance, rival dealers, weather, lucky breaks, street connections, and viral moments.
- Police encounters overhauled: bribe sweet spot is 20–80% of your cash. New flee option (40% escape / 60% arrest). Arrest = 2 days jail + lose half your cash. Police siren sound effect plays on encounter.
- Location-specific travel events: 5 unique events per location (The Block, Downtown, Campus, Strip, Suburbs, Plug House) that trigger based on where YOU travel — the key differentiator in multiplayer.
- Black Market Auction: ~10% daily chance of a rare bulk lot at a discount. Buy at asking price or pass.
- Snitch or Stay Quiet: witness a rival's deal and choose — snitch tanks your rep (sell prices -20% for 3 days) or stay quiet.
- Expand Territory: pay $2,000 to unlock The Dispensary — legal market with stable prices, no police, no random events.
- Hire a Runner: $500/day to auto-sell 1 unit of your cheapest product at market price. Toggle on/off in the bank.
- Insurance: $1,000 for a 5-day policy that absorbs the next loss event (raid, theft, bad storage).
- Chat button glows green with a pulse when unread messages arrive while the chat panel is closed.
- Event variety fix: replaced cascading probability thresholds with uniform pool selection — every event has equal chance, no more getting stuck on the same event.
- All market scenarios use seeded RNG — everyone in a group game sees the same price shifts for fairness.
Homepage Overhaul, Device Detection & Camera Fixes
March 30, 2026
- Homepage bounce reduction: new empty state CTA when no sessions are live — encourages visitors to be the first to start one.
- How It Works teaser: inline 3-step visual (Pick a Vibe → Get Matched → Go Live) so new visitors understand the product without navigating away.
- Social proof section: "What Happens Here" with session quotes to show the community is real.
- Vibe tags are now clickable entry points — pick a vibe and jump straight into the age gate → mode selection flow with that vibe pre-selected.
- Device detection: the app now identifies phones, tablets, and desktops using viewport size, touch capability, and userAgentData for smarter layout decisions going forward.
- Phone cam tiles no longer crop users — switched from object-cover to object-contain so your full frame is always visible.
- Samsung S24 camera switch fixed: stops the current camera track before acquiring the new one, preventing the black-screen freeze.
- Samsung camera fallback: if facingMode isn't supported, falls back to deviceId enumeration so the switch still works.
- Chat button (💬) added to the How It Works button guide.
- Contact info updated across all legal pages — reach us through the suggestion box on the home page.
Grid Layout, Chat Button, Cloud Footer, CH 710
March 29, 2026
- Video grid fix: tiles no longer get buried when a 3rd person joins or the TV is turned on. Grid properly constrains to viewport on both mobile and desktop.
- Responsive grid: mobile uses 2 columns, desktop scales up. 4 tiles (3 people + TV) now shows a clean 2x2 layout.
- Chat moved to control bar: chat is now a toggle button (💬) in the control bar alongside Radio and TV. Defaults to hidden — tap to open.
- Vote Kick moved out of Host Controls — it's available to all participants, not just the host.
- Safety card updated: step 4 now shows all 10 control bar buttons.
- Room Actions section added to How It Works for non-host features like Vote Kick.
- ASK CHAD GL YouTube feed added at TV channel 710.
- Footer buttons reshaped into puffy cloud buttons with SVG fractal noise displacement.
- Gamers page updated: notes that built-in games are only available in Smoking Buddy (1-on-1) sessions.
TV Panel, Tile Drag, Safety Card
March 29, 2026
- Room TV is now a toggleable tile: click the 📺 button to add TV to the cam grid, click again to remove it. TV only loads when visible.
- Long-press drag to reorder tiles: hold any tile for a moment, then slide it over another to swap positions. Works on desktop and mobile.
- Adaptive grid layout: columns adjust automatically based on how many tiles are in the room (cams + TV).
- TV audio leak fixed: AudioContext and HLS streams now properly clean up when TV unmounts.
- Tile swap crash fixed: missing state variable was causing a silent ReferenceError that broke the grid.
- Notification sound: replaced bong rip with a clean two-tone beep.
- How It Works page redesigned as an airplane safety card with pictogram panels for steps 1–4.
- Full button guide added to How It Works with every control bar button documented.
- Host Controls section expanded: explains who the host is, how host transfers, and all host-only features.
- Tiles & Layout section added: drag reorder, adaptive grid, and nametag documented.
Polish, Matching, and New Features
March 29, 2026
- Cam-off avatar: turn your cam off and set a custom image from your device as your avatar.
- Virtual Sesh matching fixed: async ban check was blocking event registration, causing users to sit in queue forever.
- Skip in Virtual Sesh now tries existing rooms first before creating a new one.
- Vote-to-join restored for all room sizes — rooms self-regulate who joins.
- TV volume on iPhone: uses Web Audio API GainNode for independent volume control, separate from phone system volume.
- Samsung camera switch fixed: uses facingMode instead of cycling through all camera devices.
- Solo Lounge Go Live button: opt into matchmaking from solo mode when you're ready.
- TV channel syncs to new joiners automatically.
- Solo cam layout: side-by-side cam and TV on mobile.
- Server fixes: broadcastOnlineCount loop, memory leaks, false memory alarms on startup.
- OAuth login no longer flashes the landing page.
- Screen share host-only toggle removed — screen share is open to all.
Room TV + Backend Monitor
March 28, 2026
- Room TV: live IPTV streaming right in your session. The TV sits in the center of the Brady Bunch cam grid and everyone watches together.
- TV Remote: tap the dial icon to open the remote overlay. Channel up/down, scan for live feeds, jump to a specific channel number, volume slider, and mute.
- Scan mode: automatically flips through channels and skips dead ones until it finds a live feed. Dead channels are remembered so they're never retried.
- TV audio routes to your selected speaker output, same as the radio.
- Stock ticker now scrolls smoothly on iPhone — no more sticking or jumping.
- Vote kick in group sessions now instantly bans the user. Flag button removed from Virtual Sesh since it's redundant.
- Backend health monitor at /admin/monitor with stoplight status and email alerts when systems leave the green zone.
- Connection stability: longer ping timeout and auto-reconnection so sessions don't drop.
- Solo Lounge mode: chill alone with the TV and radio without getting matched. Create a room and invite people when you're ready.
- Screen sharing open to all users, not just the host. Host can disable screen sharing for the room if someone abuses it — active shares get stopped automatically.
- Radio player overlay fixed — no longer clips behind the TV or gets trapped in the video panel.
- Landing page cleaned up: Enter button moved right below the logo, CLN and Grassy Logic added as Day Ones.
- Online count now visible during Connecting and Partner Left states.
Security Hardening
March 26, 2026
- Forum security: XSS protection, Content Security Policy headers, admin endpoint hardening.
- API route hardening: SSRF protection, input validation, cache headers across all endpoints.
Live Now + Room Lock
March 25, 2026
- Live Now section on the forum showing active chat sessions you can join.
- Room lock: host can prevent new people from joining the room.
- Nametags: set your display name and it shows on your cam tile.
- PWA install prompt for iOS and Android — install the app from your browser.
Multi-Cam + Games
March 22, 2026
- Virtual Sesh: multi-cam rooms with up to 8 people. New joiners are voted in by the room.
- Private Rooms: create a room with a shareable code and invite friends.
- Host controls: mute everyone, lock the room, pass host, screen share.
- Games: Tic-Tac-Toe, Chess, Checkers, and Dots & Boxes — play while you chat.
- Shared radio player: pick a station and everyone in the room hears the same thing.
- Audio device selector: switch mic and speaker output mid-session.
Launch
March 18, 2026
- 1-on-1 video chat with vibe-based matching.
- Text chat alongside live video.
- Geo-verified location labels on cam tiles.
- Report/flag system with clip capture.
- The Plug community forum with posts, likes, follows, and push notifications.
- Cannabis stock ticker and news feed.
Got ideas?
Drop a suggestion in the suggestion box on the home page or post in The Plug forum. We build fast.
Back to Sessions →