<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Saif Jlassi</title>
    <description>The latest articles on DEV Community by Saif Jlassi (@laakri).</description>
    <link>https://dev.to/laakri</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2553292%2Fa9613969-0e31-4cc3-8a4c-9c35d8f15e6e.png</url>
      <title>DEV Community: Saif Jlassi</title>
      <link>https://dev.to/laakri</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/laakri"/>
    <language>en</language>
    <item>
      <title>I'm building a scripting language for whiteboard animations</title>
      <dc:creator>Saif Jlassi</dc:creator>
      <pubDate>Sat, 19 Sep 2026 10:24:50 +0000</pubDate>
      <link>https://dev.to/laakri/im-building-a-scripting-language-for-whiteboard-animations-21gb</link>
      <guid>https://dev.to/laakri/im-building-a-scripting-language-for-whiteboard-animations-21gb</guid>
      <description>&lt;p&gt;I started &lt;strong&gt;Strokeline&lt;/strong&gt; with one dumb, simple idea:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if you could describe an animation the same way you describe a webpage — with text — and something else just... drew it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not AI-generated video.&lt;/p&gt;

&lt;p&gt;Not a drag-and-drop editor where you fight with alignment guides for twenty minutes.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;script&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CIRCLE browser
RECTANGLE server
ARROW browser -&amp;gt; server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hit run, and a hand-drawn whiteboard animation plays.&lt;/p&gt;

&lt;p&gt;The twist — and really the whole point — is that the scripting language is simple enough that you can ask &lt;strong&gt;ChatGPT, Claude, or another AI&lt;/strong&gt; to write it for you.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Explain how DNS works in 45 seconds.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;goes in.&lt;/p&gt;

&lt;p&gt;A script comes out.&lt;/p&gt;

&lt;p&gt;You paste it in.&lt;/p&gt;

&lt;p&gt;You hit run.&lt;/p&gt;

&lt;p&gt;And hopefully...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;an animation comes out.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Simple idea.&lt;/p&gt;

&lt;p&gt;Turns out &lt;strong&gt;"simple idea" and "simple to build" have almost nothing to do with each other.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The bug that ate 4GB of RAM
&lt;/h2&gt;

&lt;p&gt;Somewhere in week one, my test suite died.&lt;/p&gt;

&lt;p&gt;Not failed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Died.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Vitest ran out of memory and took the whole process down with it.&lt;/p&gt;

&lt;p&gt;It took a while to track down, but here's what was happening.&lt;/p&gt;

&lt;p&gt;My parser had a recovery mechanism.&lt;/p&gt;

&lt;p&gt;If it hit a broken line, it was supposed to skip ahead to the next recognizable keyword and keep going instead of crashing on the first typo.&lt;/p&gt;

&lt;p&gt;Good idea in theory.&lt;/p&gt;

&lt;p&gt;The bug was that one specific keyword — &lt;code&gt;TEXT&lt;/code&gt;, if you're curious — could be interpreted in two different ways.&lt;/p&gt;

&lt;p&gt;It could be a property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE server AS RECTANGLE
  TEXT "SERVER"
END
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it was also being treated as a statement boundary that the error-recovery logic could search for.&lt;/p&gt;

&lt;p&gt;Under the wrong conditions, the parser would "recover" to a position it had never actually left.&lt;/p&gt;

&lt;p&gt;So it did this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;parse
  ↓
error
  ↓
recover
  ↓
same position
  ↓
error
  ↓
recover
  ↓
same position
  ↓
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forever.&lt;/p&gt;

&lt;p&gt;Every iteration created another diagnostic object.&lt;/p&gt;

&lt;p&gt;Eventually:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4GB of RAM.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then the OS gave up.&lt;/p&gt;

&lt;p&gt;The actual fix was three lines.&lt;/p&gt;

&lt;p&gt;Finding those three lines was not.&lt;/p&gt;




&lt;h2&gt;
  
  
  The screen that just went black
&lt;/h2&gt;

&lt;p&gt;Once the parser was solid, I started building the actual editor.&lt;/p&gt;

&lt;p&gt;The basic idea is pretty simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────┬──────────────────────────┐
│                     │                          │
│    SCRIPT EDITOR    │      ANIMATION           │
│                     │                          │
│  CREATE browser...  │       ✏️ ○ ───────→ □   │
│                     │                          │
│                     │                          │
└─────────────────────┴──────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type on the left.&lt;/p&gt;

&lt;p&gt;Watch it animate on the right.&lt;/p&gt;

&lt;p&gt;I pasted in a two-scene test script to check everything worked end to end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Black screen.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Console full of red text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Maximum update depth exceeded.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great.&lt;/p&gt;

&lt;p&gt;This one wasn't the parser at all.&lt;/p&gt;

&lt;p&gt;It was React and my state manager, Zustand, getting into a fight neither of them could win.&lt;/p&gt;

&lt;p&gt;Somewhere in the code that renders the scene tabs, a piece of derived data — basically:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Give me the list of scenes."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;— was being recalculated on every render.&lt;/p&gt;

&lt;p&gt;That meant it was a new array every time, even when nothing had actually changed.&lt;/p&gt;

&lt;p&gt;React compares objects and arrays by reference.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new array
   ↓
"Something changed"
   ↓
re-render
   ↓
calculate scenes
   ↓
new array
   ↓
"Something changed"
   ↓
re-render
   ↓
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forever.&lt;/p&gt;

&lt;p&gt;It only showed up with &lt;strong&gt;two or more scenes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One scene?&lt;/p&gt;

&lt;p&gt;Totally fine.&lt;/p&gt;

&lt;p&gt;Two scenes?&lt;/p&gt;

&lt;p&gt;Welcome to hell.&lt;/p&gt;

&lt;p&gt;That's the kind of bug that makes you paranoid about every other component in your codebase doing the exact same thing quietly, waiting for the right input.&lt;/p&gt;




&lt;h2&gt;
  
  
  Text that vanishes the moment it finishes appearing
&lt;/h2&gt;

&lt;p&gt;This one's my favorite because it's so specific that it's almost funny.&lt;/p&gt;

&lt;p&gt;I finally got the "hand-drawn" reveal working.&lt;/p&gt;

&lt;p&gt;Text types itself onto the canvas character by character.&lt;/p&gt;

&lt;p&gt;Shapes trace their own outlines like a pen is drawing them.&lt;/p&gt;

&lt;p&gt;It was genuinely satisfying to watch.&lt;/p&gt;

&lt;p&gt;Except...&lt;/p&gt;

&lt;p&gt;A few seconds after any text finished typing, it would just...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;disappear.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not fade.&lt;/p&gt;

&lt;p&gt;Not glitch.&lt;/p&gt;

&lt;p&gt;Gone.&lt;/p&gt;

&lt;p&gt;Like it was never there.&lt;/p&gt;

&lt;p&gt;I had two separate rendering paths.&lt;/p&gt;

&lt;p&gt;One for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;still animating
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where the text was drawn live every frame.&lt;/p&gt;

&lt;p&gt;And another for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;finished
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where I could reuse a cached image instead of asking an expensive hand-drawn rendering library to redo the same work sixty times a second for something that wasn't moving.&lt;/p&gt;

&lt;p&gt;Smart idea.&lt;/p&gt;

&lt;p&gt;Better performance.&lt;/p&gt;

&lt;p&gt;Except the code calculating how large that cached image needed to be was using a rough guess:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text.length * fontSize * 0.55
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am not proud of that line.&lt;/p&gt;

&lt;p&gt;It also assumed the text started from a point instead of being centered on that point, which is how it actually gets drawn.&lt;/p&gt;

&lt;p&gt;So everything looked perfectly fine while the animation was running.&lt;/p&gt;

&lt;p&gt;Then the animation finished.&lt;/p&gt;

&lt;p&gt;The renderer switched to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"just show the cached version"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And suddenly the cached version had the wrong dimensions and the wrong position.&lt;/p&gt;

&lt;p&gt;Half the text disappeared silently.&lt;/p&gt;

&lt;p&gt;Every.&lt;/p&gt;

&lt;p&gt;Single.&lt;/p&gt;

&lt;p&gt;Time.&lt;/p&gt;

&lt;p&gt;The animation itself was working.&lt;/p&gt;

&lt;p&gt;The cache was working.&lt;/p&gt;

&lt;p&gt;The transition between the two was the problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  None of these bugs were exotic
&lt;/h2&gt;

&lt;p&gt;That's probably the part I find most interesting.&lt;/p&gt;

&lt;p&gt;There was no mysterious AI behavior.&lt;/p&gt;

&lt;p&gt;No obscure browser API.&lt;/p&gt;

&lt;p&gt;No impossible-to-reproduce hardware issue.&lt;/p&gt;

&lt;p&gt;Just normal software bugs.&lt;/p&gt;

&lt;p&gt;Different bugs.&lt;/p&gt;

&lt;p&gt;Different systems.&lt;/p&gt;

&lt;p&gt;Different symptoms.&lt;/p&gt;

&lt;p&gt;But underneath, they all had something in common:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the program believed something was true when it wasn't.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The parser thought it had recovered.&lt;/p&gt;

&lt;p&gt;It hadn't.&lt;/p&gt;

&lt;p&gt;React thought the data had changed.&lt;/p&gt;

&lt;p&gt;It hadn't.&lt;/p&gt;

&lt;p&gt;The renderer thought the cached text had the correct dimensions.&lt;/p&gt;

&lt;p&gt;It didn't.&lt;/p&gt;

&lt;p&gt;Three completely different corners of the codebase.&lt;/p&gt;

&lt;p&gt;Three completely different disguises.&lt;/p&gt;

&lt;p&gt;Same fundamental problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I'm telling you this instead of just shipping quietly
&lt;/h2&gt;

&lt;p&gt;Because I think there's a specific kind of satisfaction in watching something like this get built in public.&lt;/p&gt;

&lt;p&gt;Not just the polished announcement at the end.&lt;/p&gt;

&lt;p&gt;The bugs too.&lt;/p&gt;

&lt;p&gt;The broken builds.&lt;/p&gt;

&lt;p&gt;The weird rendering problems.&lt;/p&gt;

&lt;p&gt;The moments where you stare at a console error wondering how something this stupid could possibly be happening.&lt;/p&gt;

&lt;p&gt;The whole premise of Strokeline is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text
  ↓
program
  ↓
animation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And building the thing that makes that possible has turned out to be a pretty good example of exactly that process.&lt;/p&gt;

&lt;p&gt;You write something you believe is correct.&lt;/p&gt;

&lt;p&gt;Then reality — the browser, the state manager, the parser, the rendering pipeline — tells you:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"No."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And you investigate why.&lt;/p&gt;




&lt;h2&gt;
  
  
  The part I'm most excited about
&lt;/h2&gt;

&lt;p&gt;The interesting thing about a scripting language for animations isn't really the shapes.&lt;/p&gt;

&lt;p&gt;Drawing a circle isn't difficult.&lt;/p&gt;

&lt;p&gt;Drawing an arrow isn't difficult.&lt;/p&gt;

&lt;p&gt;Even animating them isn't particularly difficult.&lt;/p&gt;

&lt;p&gt;The interesting part is creating a language that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simple enough for humans to understand&lt;/li&gt;
&lt;li&gt;structured enough for a renderer to execute&lt;/li&gt;
&lt;li&gt;predictable enough for an AI to generate&lt;/li&gt;
&lt;li&gt;expressive enough to create real explanations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, I want something like this to be enough to explain a basic HTTP request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SCENE 1 "HTTP Request"

  CREATE browser AS CIRCLE
    POSITION 400 500
    RADIUS 100
    LABEL "Browser"
    DRAW 1s
  END

  CREATE server AS RECTANGLE
    POSITION 1400 500
    WIDTH 300
    HEIGHT 220
    TEXT "SERVER"
    DRAW 1s
  END

  ARROW browser -&amp;gt; server
    LABEL "HTTP Request"
    DRAW 1s
  END

END SCENE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the direction I'm aiming for.&lt;/p&gt;

&lt;p&gt;Not a giant animation framework that requires a 400-page manual.&lt;/p&gt;

&lt;p&gt;Something closer to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HTML, but for explaining things visually.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  And yes, AI is a big part of this
&lt;/h2&gt;

&lt;p&gt;This is where Strokeline gets particularly interesting to me.&lt;/p&gt;

&lt;p&gt;You don't necessarily need to learn the scripting language.&lt;/p&gt;

&lt;p&gt;You could simply ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Explain how a database index works in 60 seconds.

Use three scenes.

Start with a slow query.

Then show the index.

Finish by comparing indexed and non-indexed lookups.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI generates the script.&lt;/p&gt;

&lt;p&gt;Strokeline validates it.&lt;/p&gt;

&lt;p&gt;The renderer executes it.&lt;/p&gt;

&lt;p&gt;And you get an animation.&lt;/p&gt;

&lt;p&gt;That means the language becomes a kind of &lt;strong&gt;intermediate representation for visual explanations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The AI doesn't have to generate pixels.&lt;/p&gt;

&lt;p&gt;It generates instructions.&lt;/p&gt;

&lt;p&gt;The renderer handles the pixels.&lt;/p&gt;

&lt;p&gt;That's a much more interesting problem to me.&lt;/p&gt;




&lt;h2&gt;
  
  
  The project is still early
&lt;/h2&gt;

&lt;p&gt;The core loop works now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write script
    ↓
Parse script
    ↓
Build scene
    ↓
Render animation
    ↓
Play / scrub / inspect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've also been working on things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;camera movement&lt;/li&gt;
&lt;li&gt;scene management&lt;/li&gt;
&lt;li&gt;animation timing&lt;/li&gt;
&lt;li&gt;hand-drawn rendering&lt;/li&gt;
&lt;li&gt;text measurement&lt;/li&gt;
&lt;li&gt;caching&lt;/li&gt;
&lt;li&gt;error recovery&lt;/li&gt;
&lt;li&gt;script validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And yes...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;bugs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A lot of bugs.&lt;/p&gt;

&lt;p&gt;But that's part of building it.&lt;/p&gt;

&lt;p&gt;If you're the kind of person who reads:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"AI generates a whiteboard animation script and my platform just... plays it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and immediately thinks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Wait, how does the timeline handle scrubbing backward in the middle of an animation without breaking everything?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You're exactly who I'm building this for.&lt;/p&gt;

&lt;p&gt;And I'd genuinely love to hear what you'd worry about first.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs3r977157z0v9p8wdiil.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs3r977157z0v9p8wdiil.png" alt=" " width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;I'm still figuring out what Strokeline ultimately becomes.&lt;/p&gt;

&lt;p&gt;Right now, I'm focused on getting the fundamentals right:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;language → parser → timeline → renderer → animation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once that foundation is solid, there are a lot of directions this could go.&lt;/p&gt;

&lt;p&gt;More animation primitives.&lt;/p&gt;

&lt;p&gt;Better camera control.&lt;/p&gt;

&lt;p&gt;Reusable components.&lt;/p&gt;

&lt;p&gt;Timeline editing.&lt;/p&gt;

&lt;p&gt;AI-assisted script generation.&lt;/p&gt;

&lt;p&gt;Maybe eventually collaborative editing.&lt;/p&gt;

&lt;p&gt;But for now?&lt;/p&gt;

&lt;p&gt;I'm happy that I can type a few lines of text and watch the browser turn them into something that actually moves.&lt;/p&gt;

&lt;p&gt;Especially after spending several hours debugging why the text was disappearing.&lt;/p&gt;

&lt;p&gt;More soon.&lt;/p&gt;

&lt;p&gt;Probably with more bugs to tell you about.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Lakron : A Minimal, Encrypted, Realtime Life Scheduler with Supabase</title>
      <dc:creator>Saif Jlassi</dc:creator>
      <pubDate>Wed, 25 Jun 2025 12:59:16 +0000</pubDate>
      <link>https://dev.to/laakri/lakron-a-minimal-encrypted-realtime-life-scheduler-with-supabase-17bi</link>
      <guid>https://dev.to/laakri/lakron-a-minimal-encrypted-realtime-life-scheduler-with-supabase-17bi</guid>
      <description>&lt;p&gt;Lakron: A Minimal, Encrypted, Realtime Life Scheduler with Supabase&lt;/p&gt;

&lt;p&gt;Lakron is a minimal, smart life scheduler that keeps your data private and in sync. Every task is encrypted before it leaves your browser, and realtime sync means your schedule is always up to date everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Encryption Works
&lt;/h2&gt;

&lt;p&gt;Every task you create is encrypted in the browser using a key derived from your password and a salt. Only you can decrypt your data—nobody else, not even the database admin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TaskProvider.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;currentProfile&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;CryptoJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PBKDF2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentProfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ENCRYPTION_SALT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;keySize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentProfile&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When saving or loading a task, the title and description are encrypted/decrypted with this key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// supabase.ts&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;CryptoJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;CryptoJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CryptoJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Utf8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to Clone and Run Lakron
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repo:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   git clone https://github.com/laakri/lakron.git
   cd lakron
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Install dependencies:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;.env&lt;/code&gt; file in the root with your Supabase project keys.
&lt;strong&gt;Use your own values, never share your real keys publicly!&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   VITE_ENCRYPTION_KEY=your-random-salt
   VITE_SUPABASE_URL=https://your-project.supabase.co
   VITE_SUPABASE_KEY=your-service-role-key
   VITE_SUPABASE_ANON_KEY=your-anon-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Start the dev server:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enable Realtime on the Tasks Table
&lt;/h2&gt;

&lt;p&gt;Lakron uses Supabase Realtime to sync tasks instantly across devices.&lt;br&gt;&lt;br&gt;
To enable this, turn on Realtime for the &lt;code&gt;tasks&lt;/code&gt; table in your Supabase dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to your Supabase project&lt;/li&gt;
&lt;li&gt;Click on "Database" → "Replication"&lt;/li&gt;
&lt;li&gt;Find the &lt;code&gt;tasks&lt;/code&gt; table and enable Realtime&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  SQL to Create the Tables (All in One Block)
&lt;/h2&gt;

&lt;p&gt;If you need to create the &lt;code&gt;profiles&lt;/code&gt; and &lt;code&gt;tasks&lt;/code&gt; tables from scratch, here’s the full SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create profiles table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Create tasks table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;completed&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'task'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'event'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'task'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;profile_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;recurring&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"completedDates"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Add constraint to ensure recurrence_rule values are valid&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;
  &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;check_recurrence_rule&lt;/span&gt;
  &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'daily'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'weekly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'monthly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'yearly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'custom'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Optional: Standardize existing recurrence_rule values (safe to run even if table is empty)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'daily'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'daily'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'weekly'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'weekly'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'monthly'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'monthly'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'yearly'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'yearly'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'custom'&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'daily'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'weekly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'monthly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'yearly'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Realtime Sync
&lt;/h2&gt;

&lt;p&gt;Supabase’s realtime channels keep tasks in sync across tabs and devices. The provider subscribes to changes for the current profile, so if you add or complete a task on your phone, it updates instantly on your laptop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TaskProvider.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsubscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dbOperations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribeToTasks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentProfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// handle INSERT, UPDATE, DELETE&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;Deploying Lakron is as smooth as using it. The whole process is automated with GitHub Actions, so every push to the main branch triggers a build and deploy.&lt;/p&gt;

&lt;p&gt;The workflow does three things:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Installs dependencies and builds the frontend
&lt;/li&gt;
&lt;li&gt;Copies the built static files to the server using SCP over SSH
&lt;/li&gt;
&lt;li&gt;Reloads the Caddy server so your changes go live instantly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s the gist of the deployment workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/deploy.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies and build&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;npm ci&lt;/span&gt;
    &lt;span class="s"&gt;npm run build&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy dist to server&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appleboy/scp-action@v0.1.7&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_HOST }}&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_USER }}&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_SSH_KEY }}&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dist/*"&lt;/span&gt;
    &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/home/youruser/lakron/dist"&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Reload Caddy&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appleboy/ssh-action@v1.0.0&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_HOST }}&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_USER }}&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_SSH_KEY }}&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;sudo systemctl reload caddy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You just need to add your server’s SSH key and connection info as GitHub secrets.&lt;br&gt;&lt;br&gt;
Caddy serves the static files from the &lt;code&gt;dist&lt;/code&gt; directory, so as soon as the workflow finishes, your latest version is live.&lt;/p&gt;

&lt;p&gt;No manual uploads, no downtime, just push and your encrypted, realtime scheduler is up to date for everyone.&lt;/p&gt;




&lt;p&gt;Clone, set your env, run the SQL, enable realtime, and you’ve got a fully encrypted, realtime, minimal life scheduler.&lt;/p&gt;

</description>
      <category>react</category>
      <category>supabase</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a Secure Multi-Device Authentication System: The SyncBridge Story #1🔐</title>
      <dc:creator>Saif Jlassi</dc:creator>
      <pubDate>Mon, 23 Dec 2024 18:04:21 +0000</pubDate>
      <link>https://dev.to/laakri/building-a-secure-multi-device-authentication-system-the-syncbridge-story-1-1pgp</link>
      <guid>https://dev.to/laakri/building-a-secure-multi-device-authentication-system-the-syncbridge-story-1-1pgp</guid>
      <description>&lt;p&gt;Ever wondered how apps like WhatsApp Web handle secure device pairing? Or how Netflix manages to keep track of all your devices? Today, I'm diving deep into how I built SyncBridge's authentication system, handling everything from QR code login to token refresh mechanisms. Buckle up - it's gonna be a fun ride! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge 🎯
&lt;/h2&gt;

&lt;p&gt;Building SyncBridge's auth system wasn't just about letting users log in. I needed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Track multiple devices per user&lt;/li&gt;
&lt;li&gt;Handle secure device pairing&lt;/li&gt;
&lt;li&gt;Manage token refresh without compromising security&lt;/li&gt;
&lt;li&gt;Implement email verification&lt;/li&gt;
&lt;li&gt;Monitor suspicious activities&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Architecture 🏗️
&lt;/h2&gt;

&lt;p&gt;Here's what I came up with:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkypqu3rvd4oqmox9qrd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkypqu3rvd4oqmox9qrd.png" alt=" " width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Secret Sauce 🌟
&lt;/h3&gt;

&lt;p&gt;What makes this system special? Three key ingredients:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Device-Specific Tokens&lt;/strong&gt;: Each device gets its own unique identifier and refresh token. If one device is compromised, others stay safe.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rotating Refresh Tokens&lt;/strong&gt;: Every time a refresh token is used, it's invalidated and a new one is issued. This means if someone steals your refresh token, they have exactly one shot at using it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security Event Tracking&lt;/strong&gt;: Every login, token refresh, and device pairing is monitored. Suspicious patterns? We catch them before they become problems.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Let's Get Technical 🛠️
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Token Management: The Dance of Access and Refresh 💃
&lt;/h2&gt;

&lt;p&gt;Remember the last time you stayed logged into an app for weeks without entering your password? That's the magic of refresh tokens. But here's how we made it more secure in SyncBridge:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Two-Token Tango 🎭
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzta6tfm0v0o1tz1yjrpu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzta6tfm0v0o1tz1yjrpu.png" alt=" " width="800" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters 🤔
&lt;/h3&gt;

&lt;p&gt;Here's what makes our token system special:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Single-Use Refresh Tokens&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// When refreshing tokens
const deviceAuth = this.deviceAuthRepository.create({
device_id: device.device_id,
refresh_token: refreshToken,
expires_at: new Date(Date.now() + 7 24 60 60 1000), // 7 days
is_valid: true,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Device-Bound Tokens&lt;/strong&gt;&lt;br&gt;
Each refresh token is tied to a specific device. Try using it from another device? Not happening! 🚫&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Concurrent Request Handling&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (isRefreshing) {
try {
const token = await new Promise((resolve, reject) =&amp;gt; {
failedQueue.push({ resolve, reject });
});
// Handle concurrent requests during refresh
}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Security Checklist ✅
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Access tokens expire in 15 minutes&lt;/li&gt;
&lt;li&gt;✅ Refresh tokens are single-use&lt;/li&gt;
&lt;li&gt;✅ Device fingerprinting for extra security&lt;/li&gt;
&lt;li&gt;✅ Token rotation on every refresh&lt;/li&gt;
&lt;li&gt;✅ Concurrent request queue management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  QR Code Magic: Secure Device Pairing 📱
&lt;/h2&gt;

&lt;p&gt;One of the coolest features we built is the QR code login system. You know when you open WhatsApp Web and scan that QR code? We built something similar, but with our own security twist!&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works 🔍
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77n1bsuiuqqz4vvowfhn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77n1bsuiuqqz4vvowfhn.png" alt=" " width="799" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you click "Add Device", here's what happens behind the scenes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async generateLoginQR(userId: string): Promise&amp;lt;{ qrId: string; qrCode:string }&amp;gt; {
// Generate unique QR session ID
const qrId = uuidv4();
// Store QR session with Redis (5 minutes expiration)
await this.redis.set(
qr_login:${qrId},
JSON.stringify({
status: 'pending',
ownerId: userId,
createdAt: new Date().toISOString(),
}),
'EX',300
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Security First! 🛡️
&lt;/h3&gt;

&lt;p&gt;Here's how we keep the QR login secure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Time-Limited Sessions&lt;/strong&gt;: Each QR code expires in 5 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-Time Use&lt;/strong&gt;: Once scanned, the QR code becomes invalid&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Device Verification&lt;/strong&gt;: We check if the scanning device is authorized&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time Status Updates&lt;/strong&gt;: Using Redis for instant status changes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggi9e2jir1ekcbvpjoko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggi9e2jir1ekcbvpjoko.png" alt=" " width="800" height="607"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The User Experience 🎯
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User opens SyncBridge on their computer&lt;/li&gt;
&lt;li&gt;Clicks "Add Device" and gets a unique QR code&lt;/li&gt;
&lt;li&gt;Scans with their phone (already logged in)&lt;/li&gt;
&lt;li&gt;Both devices get notified of successful pairing&lt;/li&gt;
&lt;li&gt;New device receives its own unique tokens
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Real-time QR status monitoring
const checkQRStatus = async (qrId: string) =&amp;gt; {
const status = await redis.get(qr_login:${qrId});
if (status.authenticated) {
// Handle successful authentication
const { deviceId, tokens } = status;
await setupNewDevice(deviceId, tokens);
}
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Email Verification: Double-Check Everything ✉️
&lt;/h3&gt;

&lt;p&gt;Every new device pairing triggers an email notification:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3ontrehbrkgigi2lcg7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3ontrehbrkgigi2lcg7.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This gives users instant visibility into their account activity and a chance to revoke access if something looks suspicious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Monitoring: The Guardian Angel 👀
&lt;/h2&gt;

&lt;p&gt;What happens after users log in? We keep watching! Here's our security monitoring system in action:&lt;/p&gt;

&lt;h3&gt;
  
  
  Event Tracking System 📊
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async logSecurityEvent(params: {
user: User,
device: Device,
event_type: SecurityEventType,
severity: SecurityEventSeverity,
}) {
// Log and monitor security events
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What We Track 🔍
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🚪 Login attempts (successful and failed)&lt;/li&gt;
&lt;li&gt;🔄 Token refresh patterns&lt;/li&gt;
&lt;li&gt;📱 Device pairing activities&lt;/li&gt;
&lt;li&gt;🌍 Geographic anomalies&lt;/li&gt;
&lt;li&gt;⚠️ Suspicious IP changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting It All Together 🎯
&lt;/h2&gt;

&lt;p&gt;Here's what makes SyncBridge's authentication special:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Zero Trust Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every request is verified&lt;/li&gt;
&lt;li&gt;No device is inherently trusted&lt;/li&gt;
&lt;li&gt;Continuous validation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;User-First Security&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear email notifications&lt;/li&gt;
&lt;li&gt;Device management dashboard&lt;/li&gt;
&lt;li&gt;One-click device revocation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Future-Proof Design&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scalable token management&lt;/li&gt;
&lt;li&gt;Extensible security events&lt;/li&gt;
&lt;li&gt;Ready for new auth methods&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thoughts 💭
&lt;/h2&gt;

&lt;p&gt;Building a secure authentication system is like building a house - you need a solid foundation (token management), good locks (QR code pairing), and a reliable security system (event monitoring). &lt;/p&gt;

&lt;p&gt;The best part? This is just the beginning. Next up: implementing biometric authentication and hardware key support! 🚀&lt;/p&gt;




&lt;p&gt;Found this helpful? Follow me for more deep dives into web security and full-stack development! &lt;br&gt;
LINKED IN : &lt;a href="https://www.linkedin.com/in/seif-eddine-jlassi/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/seif-eddine-jlassi/&lt;/a&gt;&lt;br&gt;
GITHUB REPO :&lt;a href="https://github.com/laakri/SyncBridge" rel="noopener noreferrer"&gt;https://github.com/laakri/SyncBridge&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building SyncBridge: When "Copy Here, Paste There" Gets an Upgrade 🚀</title>
      <dc:creator>Saif Jlassi</dc:creator>
      <pubDate>Sat, 21 Dec 2024 19:23:54 +0000</pubDate>
      <link>https://dev.to/laakri/building-syncbridge-when-copy-here-paste-there-gets-an-upgrade-n4b</link>
      <guid>https://dev.to/laakri/building-syncbridge-when-copy-here-paste-there-gets-an-upgrade-n4b</guid>
      <description>&lt;p&gt;Ever had that moment when you're reading an article on your phone, but want to continue on your laptop? Or found yourself copying text between devices like a digital juggler? Yeah, me too. That's why I built SyncBridge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62u7jy5a2rej3c2gncaz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62u7jy5a2rej3c2gncaz.png" alt=" " width="660" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Aha!" Moment
&lt;/h2&gt;

&lt;p&gt;Picture this: I'm coding on my desktop, reading docs on my tablet, and messaging on my phone. Copy, switch device, paste. Switch again. Copy again. At that moment, I thought, "This is madness. Why isn't this seamless?"&lt;/p&gt;

&lt;h2&gt;
  
  
  What's SyncBridge, Anyway?
&lt;/h2&gt;

&lt;p&gt;Think of it as a digital bridge between all your devices. You're reading on your phone? Swipe up, continue on your laptop. Found a cool code snippet? It's already in your tablet's clipboard. Writing notes? They sync across devices before you even finish your thought.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssuma5h5p66vasy0o7s4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssuma5h5p66vasy0o7s4.png" alt=" " width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Secret Sauce 🌶️
&lt;/h2&gt;

&lt;p&gt;What makes SyncBridge different isn't just sync – it's the experience. No more "send to device" buttons or manual syncing. It just works. Like magic, but with better error handling. 😉&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cool Stuff Under the Hood:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Real-time sync that's faster than your coffee break&lt;/li&gt;
&lt;li&gt;Smart device pairing (scan &amp;amp; go)&lt;/li&gt;
&lt;li&gt;Automatic conflict resolution (no more "which version is newer?")&lt;/li&gt;
&lt;li&gt;Battery-friendly background sync&lt;/li&gt;
&lt;li&gt;End-to-end encryption (because your data is yours)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fudljoiahy4n14pzlqeu9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fudljoiahy4n14pzlqeu9.png" alt=" " width="662" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Status: The Foundation
&lt;/h2&gt;

&lt;p&gt;Right now, I've laid down the architecture (shown above) and mapped out the core features. Think of it as building the highway before the cars start rolling. The diagrams show the planned user flows and technical architecture – the blueprint of what's coming.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;This is just the beginning. I'm building SyncBridge in public, and I'll be sharing every major milestone, challenge, and "why didn't I think of this before?" moment. Next up: the real-time sync engine that makes everything feel instantaneous.&lt;/p&gt;

&lt;h2&gt;
  
  
  Join the Journey
&lt;/h2&gt;

&lt;p&gt;Want to see how a "wouldn't it be cool if..." moment turns into a full-fledged product? Follow along! I'll be posting regular updates as SyncBridge grows from these architecture diagrams into your next favorite tool.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>websocket</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building a Real-Time Auction Platform: Behind the Scenes</title>
      <dc:creator>Saif Jlassi</dc:creator>
      <pubDate>Tue, 10 Dec 2024 17:28:00 +0000</pubDate>
      <link>https://dev.to/laakri/building-a-real-time-auction-platform-behind-the-scenes-5hl6</link>
      <guid>https://dev.to/laakri/building-a-real-time-auction-platform-behind-the-scenes-5hl6</guid>
      <description>&lt;p&gt;As a developer, I recently built a sophisticated auction platform that combines the excitement of live bidding with modern social features. Today, I want to share the journey of creating the core auction experience, particularly focusing on real-time interactions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foywsdym36lrv9lkjv5sc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foywsdym36lrv9lkjv5sc.jpg" alt=" " width="799" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Auction Experience
&lt;/h2&gt;

&lt;p&gt;Our auction platform offers a dynamic, interactive environment where users can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Watch live auctions with real-time price updates&lt;/li&gt;
&lt;li&gt;Participate in auction-specific chat rooms&lt;/li&gt;
&lt;li&gt;See live viewer counts&lt;/li&gt;
&lt;li&gt;Place bids with instant feedback&lt;/li&gt;
&lt;li&gt;Track time remaining with precision countdowns&lt;/li&gt;
&lt;li&gt;Interact with other bidders in real-time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Live Bidding System
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Real-time price updates across all connected users&lt;/li&gt;
&lt;li&gt;Anti-sniping protection&lt;/li&gt;
&lt;li&gt;Minimum increment enforcement&lt;/li&gt;
&lt;li&gt;Instant bid validation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Social Integration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Live chat during auctions&lt;/li&gt;
&lt;li&gt;Real-time viewer count&lt;/li&gt;
&lt;li&gt;User presence indicators&lt;/li&gt;
&lt;li&gt;Follow/unfollow sellers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Owner Controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Real-time auction management&lt;/li&gt;
&lt;li&gt;Chat moderation capabilities&lt;/li&gt;
&lt;li&gt;Early ending option&lt;/li&gt;
&lt;li&gt;Private auction management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Real-Time Architecture
&lt;/h2&gt;

&lt;p&gt;The heart of our platform is the real-time communication system. Here's how we implemented it:&lt;/p&gt;

&lt;h3&gt;
  
  
  WebSocket Implementation
&lt;/h3&gt;

&lt;p&gt;We built a robust WebSocket architecture that handles:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Bidding Events
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Instant price updates&lt;/li&gt;
&lt;li&gt;Bid confirmations&lt;/li&gt;
&lt;li&gt;Outbid notifications&lt;/li&gt;
&lt;li&gt;Current price synchronization&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Room Management
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Auction room joining/leaving&lt;/li&gt;
&lt;li&gt;Viewer count tracking&lt;/li&gt;
&lt;li&gt;User presence management&lt;/li&gt;
&lt;li&gt;Chat room coordination&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. State Synchronization
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Auction status updates&lt;/li&gt;
&lt;li&gt;Timer synchronization&lt;/li&gt;
&lt;li&gt;Owner control changes&lt;/li&gt;
&lt;li&gt;Chat state management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges &amp;amp; Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Race Conditions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Implemented server-side bid validation&lt;/li&gt;
&lt;li&gt;Used atomic operations for price updates&lt;/li&gt;
&lt;li&gt;Maintained bid order consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. State Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Centralized state management for consistency&lt;/li&gt;
&lt;li&gt;Real-time data synchronization&lt;/li&gt;
&lt;li&gt;Optimistic UI updates with rollback&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Performance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Efficient WebSocket message handling&lt;/li&gt;
&lt;li&gt;Minimal payload sizes&lt;/li&gt;
&lt;li&gt;Smart reconnection handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  User Experience Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Responsive Updates
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Instant UI feedback for actions&lt;/li&gt;
&lt;li&gt;Smooth animations for price changes&lt;/li&gt;
&lt;li&gt;Real-time countdown synchronization&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Error Handling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Graceful connection loss handling&lt;/li&gt;
&lt;li&gt;Automatic reconnection&lt;/li&gt;
&lt;li&gt;State recovery after disconnection&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Security
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Bid verification&lt;/li&gt;
&lt;li&gt;User authentication&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Private auction access control&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Insights
&lt;/h2&gt;

&lt;p&gt;The most challenging aspect was maintaining data consistency across all connected clients while ensuring a smooth user experience. We achieved this through:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Event-Driven Architecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Centralized event handling&lt;/li&gt;
&lt;li&gt;Predictable state updates&lt;/li&gt;
&lt;li&gt;Clean separation of concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Real-Time Data Flow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Bidirectional communication&lt;/li&gt;
&lt;li&gt;Event queuing&lt;/li&gt;
&lt;li&gt;State reconciliation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Scalability Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Horizontal scaling capability&lt;/li&gt;
&lt;li&gt;Connection pooling&lt;/li&gt;
&lt;li&gt;Load balancing ready&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Always handle disconnections gracefully&lt;/li&gt;
&lt;li&gt;Implement proper error boundaries&lt;/li&gt;
&lt;li&gt;Use optimistic UI updates carefully&lt;/li&gt;
&lt;li&gt;Maintain a consistent state across all clients&lt;/li&gt;
&lt;li&gt;Consider latency in user experience design&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This project showcases how modern web technologies can create engaging, real-time experiences. The combination of WebSocket communication, state management, and user experience design creates a platform that's both powerful and user-friendly.&lt;/p&gt;




&lt;h1&gt;
  
  
  #webdev #javascript #typescript #websockets #react
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>typescript</category>
      <category>fastify</category>
    </item>
    <item>
      <title>Building Glide: A Modern E-commerce Platform with Angular 17 and .NET 8</title>
      <dc:creator>Saif Jlassi</dc:creator>
      <pubDate>Tue, 10 Dec 2024 16:40:29 +0000</pubDate>
      <link>https://dev.to/laakri/building-glide-a-modern-e-commerce-platform-with-angular-17-and-net-8-17im</link>
      <guid>https://dev.to/laakri/building-glide-a-modern-e-commerce-platform-with-angular-17-and-net-8-17im</guid>
      <description>&lt;p&gt;Hey fellow developers! Today I'm excited to share my journey building Glide, a modern e-commerce platform that combines cutting-edge technologies with practical solutions. Whether you're a student looking to learn or a developer seeking insights, this post will walk you through the key aspects of building a full-stack e-commerce application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyu7rdr1ytjrxl6vec4nw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyu7rdr1ytjrxl6vec4nw.jpg" alt=" " width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vision Behind Glide
&lt;/h2&gt;

&lt;p&gt;Every great project starts with a clear vision. For Glide, it was creating an e-commerce platform that not only processes transactions but delivers an exceptional user experience. The goal was to build something that both developers would appreciate for its clean architecture and users would love for its intuitive interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack: Why These Choices Matter
&lt;/h2&gt;

&lt;p&gt;The foundation of Glide rests on two powerful frameworks:&lt;/p&gt;

&lt;p&gt;🔹 &lt;strong&gt;Frontend: Angular 17&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standalone components for better modularity&lt;/li&gt;
&lt;li&gt;New control flow syntax for cleaner templates&lt;/li&gt;
&lt;li&gt;Built-in SSR capabilities for better SEO&lt;/li&gt;
&lt;li&gt;DaisyUI + Tailwind CSS for responsive design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔹 &lt;strong&gt;Backend: .NET 8&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Robust API architecture&lt;/li&gt;
&lt;li&gt;JWT + Google OAuth integration&lt;/li&gt;
&lt;li&gt;Entity Framework Core with MySQL&lt;/li&gt;
&lt;li&gt;Real-time capabilities with SignalR&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Features That Make Glide Stand Out
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Authentication &amp;amp; Security
&lt;/h3&gt;

&lt;p&gt;The authentication system combines traditional JWT tokens with Google OAuth, providing users with flexible login options while maintaining robust security. The implementation includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure token management&lt;/li&gt;
&lt;li&gt;Role-based access control&lt;/li&gt;
&lt;li&gt;Protected API endpoints&lt;/li&gt;
&lt;li&gt;CORS policy implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Real-Time Features
&lt;/h3&gt;

&lt;p&gt;Using SignalR, we've implemented:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live notifications&lt;/li&gt;
&lt;li&gt;Order status updates&lt;/li&gt;
&lt;li&gt;Real-time cart synchronization&lt;/li&gt;
&lt;li&gt;Instant messaging between users and support&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Admin Dashboard
&lt;/h3&gt;

&lt;p&gt;A comprehensive admin interface featuring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sales analytics with Chart.js&lt;/li&gt;
&lt;li&gt;Inventory management&lt;/li&gt;
&lt;li&gt;Order processing&lt;/li&gt;
&lt;li&gt;User management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Development Insights &amp;amp; Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Frontend Architecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Components are organized by feature&lt;/li&gt;
&lt;li&gt;Services handle state management&lt;/li&gt;
&lt;li&gt;Interceptors manage HTTP requests&lt;/li&gt;
&lt;li&gt;Guards protect restricted routes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Backend Structure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clean architecture principles&lt;/li&gt;
&lt;li&gt;Repository pattern for data access&lt;/li&gt;
&lt;li&gt;Middleware for request/response handling&lt;/li&gt;
&lt;li&gt;Structured error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges &amp;amp; Solutions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance Optimization&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implemented lazy loading for routes&lt;/li&gt;
&lt;li&gt;Used Angular's built-in optimization tools&lt;/li&gt;
&lt;li&gt;Configured proper caching strategies&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;State Management&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created service-based state management&lt;/li&gt;
&lt;li&gt;Implemented reactive patterns&lt;/li&gt;
&lt;li&gt;Used BehaviorSubjects for real-time updates&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;typescript&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; // Authentication configuration
&amp;gt; builder.Services.AddAuthentication(options =&amp;gt; {
&amp;gt;     options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
&amp;gt;     options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
&amp;gt; })
&amp;gt; .AddJwtBearer()
&amp;gt; .AddGoogle(options =&amp;gt; {
&amp;gt;     options.Scope.Add("email");
&amp;gt;     options.Scope.Add("profile");
&amp;gt; });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Learning Outcomes
&lt;/h2&gt;

&lt;p&gt;Building Glide taught valuable lessons about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modern web architecture&lt;/li&gt;
&lt;li&gt;Security best practices&lt;/li&gt;
&lt;li&gt;Performance optimization&lt;/li&gt;
&lt;li&gt;User experience design&lt;/li&gt;
&lt;li&gt;Real-time feature implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tips for Students
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Start Small&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Begin with core features&lt;/li&gt;
&lt;li&gt;Add complexity gradually&lt;/li&gt;
&lt;li&gt;Test thoroughly as you go&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Focus on Fundamentals&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Master TypeScript/C# basics&lt;/li&gt;
&lt;li&gt;Understand HTTP protocols&lt;/li&gt;
&lt;li&gt;Learn authentication concepts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Best Practices&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write clean, documented code&lt;/li&gt;
&lt;li&gt;Follow naming conventions&lt;/li&gt;
&lt;li&gt;Use version control effectively&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Future Roadmap
&lt;/h2&gt;

&lt;p&gt;Glide continues to evolve with planned features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI-powered product recommendations&lt;/li&gt;
&lt;li&gt;Enhanced analytics dashboard&lt;/li&gt;
&lt;li&gt;Mobile app development&lt;/li&gt;
&lt;li&gt;Performance optimizations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;The project is open source and welcomes contributions! Whether you're fixing bugs, adding features, or improving documentation, your input is valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources for Learning
&lt;/h2&gt;

&lt;p&gt;To build something similar, check out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Angular Documentation&lt;/li&gt;
&lt;li&gt;.NET 8 Documentation&lt;/li&gt;
&lt;li&gt;JWT Authentication guides&lt;/li&gt;
&lt;li&gt;SignalR tutorials&lt;/li&gt;
&lt;li&gt;DaisyUI components&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Connect &amp;amp; Contribute
&lt;/h2&gt;

&lt;p&gt;I believe in learning together and sharing knowledge. If you're interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contributing to the project&lt;/li&gt;
&lt;li&gt;Learning more about the implementation&lt;/li&gt;
&lt;li&gt;Discussing web development&lt;/li&gt;
&lt;li&gt;Sharing your own experiences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to reach out! You can find me on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: [Your GitHub]&lt;/li&gt;
&lt;li&gt;LinkedIn: [Your LinkedIn]&lt;/li&gt;
&lt;li&gt;Twitter: [Your Twitter]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember, every developer started somewhere. Keep learning, keep building, and most importantly, enjoy the process!&lt;/p&gt;

&lt;h1&gt;
  
  
  WebDevelopment #Angular #DotNet #Programming #OpenSource #TechEducation
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>angular</category>
      <category>dotnet</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
