934: We Built a Real-Time, Local-Data, Competitive Coding Game ![rw-book-cover](https://wsrv.nl/?url=https%3A%2F%2Fmegaphone.imgix.net%2Fpodcasts%2F5197fe5a-42f7-11f0-affd-87d9985a1760%2Fimage%2Fc86a54acd72683732c4773e25bf0ae14.png%3Fixlib%3Drails-4.3.1%26max-w%3D3000%26max-h%3D3000%26fit%3Dcrop%26auto%3Dformat%2Ccompress&w=512&h=512) ## Metadata - Author: [[Syntax - Tasty Web Development Treats]] - Full Title: 934: We Built a Real-Time, Local-Data, Competitive Coding Game - Category: #podcasts - URL: https://share.snipd.com/episode/48df9d22-3691-41eb-b3d3-7ec82b185336 ## Document Notes ## Highlights - **Local Editing With Real-Time Mirroring** - SynHax lets participants code locally in their own editor while the site mirrors updates in real time for viewers. - This design preserves developer comfort and enables live audience interaction and voting. Transcript: Scott Tolinski I'm talking today, folks. Synhacks. This is a competitive coding platform. It's built in SvelteKit. I'll talk maybe a little bit about the tech in a second. I just want to maybe talk about the platform first. It's a competitive coding platform where you can do a CSS battle. And it's a UI, HTML and CSS where you code. The idea here is that you code on your own local machine in your own text editor. And you can see those updates happen in real time on the site. Audience can watch as things are being built in real time. ([Time 0:02:30](https://share.snipd.com/snip/5f93958c-377e-43c4-8ee3-1959557b89c3)) - **Generate One Schema For Client And DB** - Use one source schema and generate client schemas rather than maintaining duplicates. - Scott used Drizzle plus a package to emit a ZeroSync schema from the Drizzle schema to avoid duplicated work. Transcript: Scott Tolinski What did I choose to build it in? The stack itself uses Postgres as a database, it uses drizzle as an ORM, it uses zero sync for the local data. And I've been able to connect those two, I can even like show some of that where like, typically what you're doing with like a schema is that you have to write a schema and you have that schema In drizzle, right? I was able to write the schema just in drizzle and then use a really wonderful package that connects zero to drizzle. So instead of having to write multiple schemas, I'm writing a schema once you run a, and that spits out the zero sync schema. Now, ignore this permissions right now that says, anyone can do anything, because I'm just sweeping that under the rug right now. Wes Bos Ah, you don't need permissions. Scott Tolinski We don't need permissions. ([Time 0:04:22](https://share.snipd.com/snip/d533a811-2803-4b4e-8a85-a5712c7204e8)) - **Design Logic Before Styling** - Plan app behavior and data before coding UI to speed iteration and reduce rework. - Scott wrote a manual README and built logic/HTML/JS before styling to validate flow first. Transcript: Scott Tolinski And we did a whole episode where I discovered planning, I wrote this readme by hand. This is, this is not done with AI. I wrote this. And this was just like, what do I want it to do? What is a general schema look like? What are the even some of the routing structures, and this might not even be up to date. But I wrote this whole thing by hand before I wrote any code. And that led me to be able to really get a good handle on things. And I was using this to then pass into AI to ask questions about stuff I'm forgetting, how should I modify this, etc. Another thing I did, which I don't typically do, is I built the entire functionality like the HTML and the JavaScript before ever writing a line of CSS, no CSS, no even thought to where Things are going to go in the UI. Just dump it on the page. The flow of the app should work. It should take you. It should do all the things you need to do. And then I can get into the UI. So very two different strategies for me. I planned and I did logic first. ([Time 0:06:41](https://share.snipd.com/snip/0143c7a7-41eb-44d1-913f-851303d00794)) - **Locking In Creates Local Project Files** - Locking into a battle triggers the app to request file system access and create a project folder locally. - Scott demonstrated that joining creates local HTML/CSS files and stores their strings in the database for live syncing. Transcript: Scott Tolinski Man, I got some CSS issues here. I should tune that up. Either way, if you are logged into this app, you're a battler, you can join Clicking join just creates another record, and that gets you in the lobby. So you'll be seen as the referee as a participant, but not locked in, so you don't show up here yet. So you can be potentially in the lobby, and then you could leave or join again, and then you could lock in, which allows you to essentially be in the battle. Now, the interesting technical stuff when you lock in is that you'll notice here there is code right here. It has an app already, which in the code itself, when you click lock in, what that's actually doing is it is accessing your file system and it is creating a folder. If you do not have permissions at that point, it will prompt you for permissions to say, what is your code here? ([Time 0:09:16](https://share.snipd.com/snip/af8db4bd-45c2-4aa1-8f91-24e3d7287bbd)) - **Local-First Saves With Reactive Sync** - The app mirrors local file saves into IndexedDB first, then pushes them to ZeroSync so viewers see updates quickly. - Scott treats ZeroSync as the reactive source of truth while local storage gives instant feedback. Transcript: Wes Bos This all works. So you have your file system access, which is the browser is monitoring your local files for changes. And then when those changes happen, that's getting thrown into, that's getting mirrored to zero sync, which will then as quickly as possible, will then sync it up to the cloud or to the Database. Yes. Scott Tolinski So let's say we want to start this battle and I can show you that in action. So we have Scott, we have the battle. I can click start and the battle started. You can see automatically I didn't have to do anything. It redirected me to the battle screen for the code. And this over here on the right is the referee view. So I can hide the referee view for now or at least move it over here and focus on the coding view. The coding view just shows you the code, the target, this is the target what we're trying to build. And this is the code that is active. So if this works right now, you can see when I click save, it immediately updated real time here. And the way this works is I'm simply pulling to check for file changes. So I'm saying, hey, did CSS change? If it changed, then update the database record, not update the UI, update the database record. That updates the database record, which then in turn triggers a zero sync. And it changes the UI. ([Time 0:14:40](https://share.snipd.com/snip/80398baa-ce18-4686-9023-1dde57ef9ce5)) - **ZeroSync Gives Real-Time Sync For Free** - ZeroSync provides real-time diffing, reconciliation, and multi-view synchronization out of the box. - That makes spectator, referee, and admin views update automatically without custom websockets. Transcript: CJ Reynolds And I guess to also take a step back, because you talked about the tech stack, but that's why you chose zero sync is you basically get real time for free. You get diffing for free, you get reconciliation for free. You're using their framework to automatically sync what the user is doing to the cloud, which means you could have a spectator view. You could have an admin view. Everyone else can see what everyone else is doing because of this zero sync architecture. Is that accurate to say? Scott Tolinski That's accurate to say. And like, this is what the audience is seeing. ([Time 0:16:42](https://share.snipd.com/snip/3c1fa36e-9920-41ef-9a33-877daaf26bad)) - **Poll Locally And Persist Before Cloud Sync** - Poll local files frequently and save to IndexedDB for instant local feedback before cloud sync. - Scott polls file changes and saves locally so viewers still see near-instant updates when battlers hit save. Transcript: Scott Tolinski Give me the related referee, all of the related participants, and then give me the related users of those participants and then give me the target itself and then it just spits us all Wes Bos Out into a big old beautiful object here that we can uh utilize so man i i love the simplicity of just using the browser to render html and css'm thinking like, oh, maybe you could hook up Like Vite virtual modules. But I don't think that you need that. No. Because like CSS support is so fantastic in all the browsers now. And of course we'll be using the latest one, right? You can use all of the features. I don't think you'd need like a bundler for any of this. Scott Tolinski You can use whatever CSS you want to that's being supported in the browser that you're working in. It just needs file system access is the only limit here is that you need that file system access. And this is being done with Highlight.js, by the way, they just just that, yeah. Wes Bos As we're developing this, right, like you want to have a pane open to watch it as your your thing is it is that fast enough to be to like see your your real-time changes or would it be better Scott Tolinski To like run a like a v server locally just see your changes as well you tell me um so here we have like the background i'm going to change this background to green i'm going to do command s One two, 2, 3, Command S. Oh, man. ([Time 0:22:40](https://share.snipd.com/snip/939068d0-c30f-4d5d-b57f-96da9d7d8152)) - **Disable SSR For ZeroSync Components** - Mark ZeroSync-driven components as client-only and disable SSR for them. - Scott sets SSR=false for components that query ZeroSync to avoid inconsistent server/state rendering. Transcript: Scott Tolinski And that, that yes, exactly. And so that, that, yeah, that's pretty much the main crux of the difficult things about this. Everything else is just treating that zero sync as a source of truth for the state for the entire app. You had mentioned server side. This app is like running server side code, but the only time it's really running server side code is in the better off stuff where I'm getting the current session and passing it into server Side stuff. And then I am using that in some routes to do some auth checks. Well, sorry, there's the server side is equal to false. Yeah, I am doing it for auth check server side. There's a whole admin section of this. CJ Reynolds So for that, it kind of brings up for me questions about how to use zero sync because I haven't used it before. So like that page that was rendering the hacks, the code to query zero sync is code that would only ever run on the client. Is that accurate? That's not server side code. Correct. Scott Tolinski Yes. Okay. Yeah. Yeah. Pretty much anytime you're doing data querying at all with zero, it is client side. Your SSR equals false for any component that's touching. ([Time 0:25:20](https://share.snipd.com/snip/5a79eb46-16fb-4633-b336-ded6611b0470)) - **Sanitize Rendered Code Before Public Use** - Sanitize user-supplied HTML/CSS before rendering to prevent XSS and script injection. - Scott flagged DOM purification as a blocker before making the app public. Transcript: Scott Tolinski A script tag in there, right? Let me tell you, there's a number of things. There's a number of reasons why this isn't public right now. The DOM purifying stuff is one of them that I haven't gotten done. Another one is like we had mentioned earlier, you go inside of permissions, permissions, anyone can do anything, which is a nice little helper that zero gives you permissions code In zero is super easy. I am passing the correct information into this. I just haven't written this code yet, which is checking server side auth roles and things like that. Just because I wanted to get the whole thing done and working. ([Time 0:27:10](https://share.snipd.com/snip/947e60cd-6bb3-4139-b0c7-d2b1b76260d9)) - **Live Recap With Animated Scores** - The recap view shows real user submissions, live code, and audience scoring that updates avatars and rankings instantly. - Scott demoed star ratings, animated score scaling, and avatar emotion sprites as part of the live recap. Transcript: Scott Tolinski So past battles, this one has all three of us in here. These are real users, so this wasn't like faked or something. These are real users. I just made you guys use your accounts that can't log in but update the picture. And there are two views for this. This is the recap view. Again, this one's going to be much better if you are watching this. But if you are somebody who's in the audience, you will see something that looks like this, where you could see their code. You could see the app that they built, you could try it out. And then we can come in here and say, all right, Scott's gets three stars, it is not feeling so good, and it's not accurate at all. And that does some fun things. It updates the score, which is just an average of all three of these. And it changes my profile photo to be angry. The profile photos are based on the place that you've landed. So first place is laughing. Second place is sad. And last place is angry. So there's also some neat stuff. You can see that the numbers get bigger. I thought that was like a fun little touch. Like if you're in first place, your score is the largest. So I was trying to go for like video game UI with a lot of this stuff right yeah and and there's a nice little animation um that happens of course and then in addition to that the uh battlers Can also rate the battle ([Time 0:28:33](https://share.snipd.com/snip/4313df4f-a6fb-434e-a155-c8f0dfde9726)) - **Simple Architecture For Real-Time Battles** - SynHax needs only a Node process for ZeroSync and can run the frontend on edge platforms like Cloudflare Workers. - The architecture keeps the stack simple: Postgres, ZeroSync, Drizzle, SvelteKit, and a small Node backend. Transcript: Scott Tolinski You just have a node process for the app, or you can throw it on Cloudflare. It would work fine on workers, and then you need a node process for XeroSync, and that's it. Wes Bos Man, and the database is Postgres. Yep. So what do you think you're going to do? I guess because use... Does 0sync has to be Postgres? Is that true? Scott Tolinski Yes. Yeah. Okay. Yeah, yeah. I just, yeah, just to have some basic drizzle commands, throw it up somewhere, change the URL, push those commands, and then that's good. ([Time 0:32:46](https://share.snipd.com/snip/365a8b8f-f7a9-4a6e-bcb7-f7977264ed45))