<?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: Mohamed Ismail</title>
    <description>The latest articles on DEV Community by Mohamed Ismail (@ihssmaheel).</description>
    <link>https://dev.to/ihssmaheel</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%2F1150571%2F61d4ce75-56f7-4bc3-ba52-7d3785b98a53.png</url>
      <title>DEV Community: Mohamed Ismail</title>
      <link>https://dev.to/ihssmaheel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ihssmaheel"/>
    <language>en</language>
    <item>
      <title>Why I Switched from ts-rest to oRPC</title>
      <dc:creator>Mohamed Ismail</dc:creator>
      <pubDate>Fri, 23 Oct 2026 07:30:32 +0000</pubDate>
      <link>https://dev.to/ihssmaheel/why-i-switched-from-ts-rest-to-orpc-3igf</link>
      <guid>https://dev.to/ihssmaheel/why-i-switched-from-ts-rest-to-orpc-3igf</guid>
      <description>&lt;p&gt;I've been using &lt;strong&gt;ts-rest&lt;/strong&gt; for a while, and I genuinely liked it.&lt;/p&gt;

&lt;p&gt;Contract-first APIs, end-to-end type safety, OpenAPI support, and a clean developer experience. It had pretty much everything I needed.&lt;/p&gt;

&lt;p&gt;But over time, I started having concerns about its maintenance. Development seemed to slow down, updates became less frequent, and I was looking for better Zod 4 support.&lt;/p&gt;

&lt;p&gt;It was still a good library, but I didn't want to start my next project with something I was already uncertain about.&lt;/p&gt;

&lt;p&gt;So I started looking for alternatives.&lt;/p&gt;

&lt;h2&gt;
  
  
  tRPC or oRPC?
&lt;/h2&gt;

&lt;p&gt;The two options that caught my attention were &lt;strong&gt;tRPC&lt;/strong&gt; and &lt;strong&gt;oRPC&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;tRPC is a solid choice. It's mature, has a great developer experience, and works really well with TypeScript and TanStack Query.&lt;/p&gt;

&lt;p&gt;But I wanted something more.&lt;/p&gt;

&lt;p&gt;I didn't want to give up OpenAPI. My backend might eventually serve a mobile app, a Python service, or external integrations.&lt;/p&gt;

&lt;p&gt;tRPC can support OpenAPI through additional tooling, but I wanted it to be a first-class feature.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;oRPC&lt;/strong&gt; stood out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I liked about oRPC
&lt;/h2&gt;

&lt;p&gt;After exploring the &lt;a href="https://orpc.dev/docs/comparison" rel="noopener noreferrer"&gt;official comparison&lt;/a&gt; and documentation, a few things caught my attention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. RPC + OpenAPI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Define procedures once and expose them through RPC or OpenAPI-compatible HTTP endpoints. No need to maintain two separate API implementations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Typed errors&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can define expected errors and get proper TypeScript support when handling them on the client. Really useful when dealing with complex business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Better type support&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Types like &lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;BigInt&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Set&lt;/code&gt;, and &lt;code&gt;URL&lt;/code&gt; work through its RPC serialization without requiring additional transformers. It also supports &lt;code&gt;File&lt;/code&gt; and &lt;code&gt;Blob&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Streaming&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Built-in support for typed Server-Sent Events and streaming responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Good integrations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Works with TanStack Query, NestJS, Next.js, OpenTelemetry, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Performance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The official benchmarks show some impressive results, including roughly 4x the throughput of tRPC in certain scenarios.&lt;/p&gt;

&lt;p&gt;Of course, benchmarks don't represent every real-world application, but they're still interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I made the switch
&lt;/h2&gt;

&lt;p&gt;My requirements were simple.&lt;/p&gt;

&lt;p&gt;I wanted the smooth developer experience of RPC, proper OpenAPI support, typed errors, and the flexibility to build APIs for different clients.&lt;/p&gt;

&lt;p&gt;I also liked that oRPC supports contract-first development, something I appreciated about ts-rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;oRPC gave me the combination I was looking for.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That doesn't mean it's better for everyone.&lt;/p&gt;

&lt;p&gt;tRPC is still excellent for TypeScript-only applications, and ts-rest remains a good option for REST-first, contract-driven APIs.&lt;/p&gt;

&lt;p&gt;I wouldn't recommend migrating a working application just because another library has more features.&lt;/p&gt;

&lt;p&gt;But for my new project, oRPC made more sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;I didn't switch because ts-rest was bad or because oRPC was newer.&lt;/p&gt;

&lt;p&gt;I switched because my requirements had changed, and oRPC felt like a better fit.&lt;/p&gt;

&lt;p&gt;I'm still exploring how it holds up as my projects grow, but so far, I like the direction.&lt;/p&gt;

&lt;p&gt;If you're evaluating TypeScript API libraries, I'd recommend checking out &lt;a href="https://orpc.dev/" rel="noopener noreferrer"&gt;oRPC&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you tried oRPC in production? I'd love to hear your experience, especially any limitations you've encountered.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Built a Receipt Printer for Your GitHub Year 🧾</title>
      <dc:creator>Mohamed Ismail</dc:creator>
      <pubDate>Fri, 09 Oct 2026 12:17:46 +0000</pubDate>
      <link>https://dev.to/ihssmaheel/i-built-a-receipt-printer-for-your-github-year-21jk</link>
      <guid>https://dev.to/ihssmaheel/i-built-a-receipt-printer-for-your-github-year-21jk</guid>
      <description>&lt;p&gt;Press a button. A tiny printer warms up. Paper slides out, and your entire GitHub year lands in your hands as a receipt.&lt;/p&gt;

&lt;p&gt;That's &lt;strong&gt;Git Receipts&lt;/strong&gt;, and you can print yours in about ten seconds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3u82czecmueuw890tp0w.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3u82czecmueuw890tp0w.gif" alt="Git Receipts printer feeding a receipt" width="479" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://git-receipts.vercel.app" rel="noopener noreferrer"&gt;Print your GitHub year →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No sign-in needed. Enter any public GitHub username: your own, a friend's, or a developer you admire.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is project #1 in &lt;strong&gt;Beyond the Green Squares&lt;/strong&gt;, a series where I turn GitHub activity into fun things you can try and keep.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Enter a GitHub username or profile link&lt;/li&gt;
&lt;li&gt;Pick a year&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;FEED&lt;/strong&gt; and watch it print&lt;/li&gt;
&lt;li&gt;Save it, share it, or print it on real paper&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwx9c0bwyya1x5y4xli9n.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%2Fwx9c0bwyya1x5y4xli9n.png" alt="The finished receipt" width="800" height="2120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a receipt?
&lt;/h2&gt;

&lt;p&gt;Everyone knows what a receipt looks like: a narrow strip of paper, neat rows, dashed lines, and a bold total at the bottom.&lt;/p&gt;

&lt;p&gt;It fits a year of GitHub activity surprisingly well. Your contributions become the total. Active days and streaks become small details you can scan at a glance.&lt;/p&gt;

&lt;p&gt;And there's something satisfying about it. You press a button, wait for the machine, and get a little object at the end. I wanted that moment to be part of the experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  A printer that feels like a printer
&lt;/h2&gt;

&lt;p&gt;I didn't want a box with rounded corners. The machine has an off-white body, a dark top deck, a paper slot, status lights, and a FEED button that clicks into place.&lt;/p&gt;

&lt;p&gt;The motion matters just as much. Paper has to emerge from the slot, feed through, and get cut at the end. If it floats, the magic is gone.&lt;/p&gt;

&lt;p&gt;If you prefer reduced motion, it skips the animation and shows your finished receipt. Sound is off by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers stay honest
&lt;/h2&gt;

&lt;p&gt;The design is playful. The data is not.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No guessing.&lt;/strong&gt; If GitHub doesn't provide a number, it's left off the receipt instead of being estimated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Samples are labeled.&lt;/strong&gt; The demo receipt on the landing page is clearly marked as a sample.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing borrowed.&lt;/strong&gt; If a lookup fails, you see an unavailable message, never someone else's activity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One more honest note: a contribution count can't capture everything. Reading, planning, learning, and time away from the keyboard all count as real work. The receipt is a keepsake of reported activity, not a measure of you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Take it with you
&lt;/h2&gt;

&lt;p&gt;When printing finishes, your receipt opens in a full-size viewer. Scroll through it, then keep it however you like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PNG&lt;/strong&gt; to share&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF&lt;/strong&gt; to print on paper&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SVG&lt;/strong&gt; and &lt;strong&gt;plain text&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;README embed&lt;/strong&gt; to show it on your GitHub profile&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa0zueoam3dltcwfcth5a.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%2Fa0zueoam3dltcwfcth5a.png" alt="Receipt with the achievement stamp" width="105" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit a milestone and your receipt gets an achievement stamp, pressed onto the paper like the real thing. Every receipt also carries a QR code that leads back to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put it on your GitHub profile
&lt;/h2&gt;

&lt;p&gt;Your receipt can live in your profile README, and it follows light and dark mode automatically. Click it and it opens your receipt on Git Receipts.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Copy the README snippet
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://git-receipts.vercel.app/?user=YOUR_USERNAME&amp;amp;amp;year=2026&amp;amp;amp;stamp=true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;media=&lt;/span&gt;&lt;span class="s"&gt;"(prefers-color-scheme: dark)"&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"https://git-receipts.vercel.app/api/receipt.svg?year=2026&amp;amp;amp;stamp=true&amp;amp;amp;user=YOUR_USERNAME&amp;amp;amp;theme=dark"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://git-receipts.vercel.app/api/receipt.svg?year=2026&amp;amp;amp;stamp=true&amp;amp;amp;user=YOUR_USERNAME&amp;amp;amp;theme=light"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"YOUR_USERNAME's 2026 GitHub contribution receipt"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Replace &lt;code&gt;YOUR_USERNAME&lt;/code&gt; in all three places with your GitHub username, and change the year if you want a different one.&lt;/p&gt;



&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Your turn 👀
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://git-receipts.vercel.app" rel="noopener noreferrer"&gt;Print your GitHub year →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then tell me what caught your eye: your total, your longest streak, or a month you'd completely forgotten about. Add it to your profile README, post your receipt, and tag me. I'd love to see it.&lt;/p&gt;

&lt;p&gt;And if you have a unique idea for turning GitHub activity into something worth keeping, drop it in the comments. The best ones might become the next post in &lt;strong&gt;Beyond the Green Squares&lt;/strong&gt;, and we'll build it together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should we build next?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>showdev</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Beyond the Green Squares: Turning GitHub Activity Into Things Worth Keeping</title>
      <dc:creator>Mohamed Ismail</dc:creator>
      <pubDate>Fri, 09 Oct 2026 12:15:44 +0000</pubDate>
      <link>https://dev.to/ihssmaheel/beyond-the-green-squares-turning-github-activity-into-things-worth-keeping-9en</link>
      <guid>https://dev.to/ihssmaheel/beyond-the-green-squares-turning-github-activity-into-things-worth-keeping-9en</guid>
      <description>&lt;p&gt;A series of small, playful projects built from your GitHub activity. Try them on your own profile, keep the results, and help decide what I build next.&lt;/p&gt;

&lt;p&gt;You push code for a whole year. Late nights, tiny fixes, big launches, reviews for other people's work.&lt;/p&gt;

&lt;p&gt;And GitHub shows all of that as a grid of green squares.&lt;/p&gt;

&lt;p&gt;Squares are fine. But I keep wondering what that year would look like if it became something you could hold, share, or stick on a wall.&lt;/p&gt;

&lt;p&gt;So I'm starting &lt;strong&gt;Beyond the Green Squares&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this series is
&lt;/h2&gt;

&lt;p&gt;Each post is one small project that turns GitHub activity into something unexpected. The rules are simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You can try it yourself.&lt;/strong&gt; Every project works with a public GitHub username, no sign-in needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can keep the result.&lt;/strong&gt; Download it, share it, or print it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The numbers stay honest.&lt;/strong&gt; If data is missing, I won't estimate it. Samples will always be labeled as samples.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I'll show how it's built.&lt;/strong&gt; What worked, what broke, and what I'd do differently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The design will be playful. The data will not be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project #1 is live 🧾
&lt;/h2&gt;

&lt;p&gt;The first build is &lt;strong&gt;Git Receipts&lt;/strong&gt;, a tiny thermal printer in your browser. Enter a GitHub username, press FEED, and your year prints out as a receipt, paper-feed animation and all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/ihssmaheel/i-built-a-receipt-printer-for-your-github-year-21jk"&gt;Read the full story: I Built a Receipt Printer for Your GitHub Year →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://git-receipts.vercel.app" rel="noopener noreferrer"&gt;Or skip the reading and print your year →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Now I need you 👀
&lt;/h2&gt;

&lt;p&gt;This series gets better with your ideas.&lt;/p&gt;

&lt;p&gt;If you have a unique idea for turning GitHub activity into something you'd actually want to share, drop it in the comments. Weird, beautiful, useless, brilliant, all welcome. The ones I can't stop thinking about might become the next post, with credit to you.&lt;/p&gt;

&lt;p&gt;So, what should we build next?&lt;/p&gt;

</description>
      <category>github</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>dep-blame: Understand How Your Dependencies Changed</title>
      <dc:creator>Mohamed Ismail</dc:creator>
      <pubDate>Thu, 08 Oct 2026 04:32:32 +0000</pubDate>
      <link>https://dev.to/ihssmaheel/dep-blame-understand-how-your-dependencies-changed-26j3</link>
      <guid>https://dev.to/ihssmaheel/dep-blame-understand-how-your-dependencies-changed-26j3</guid>
      <description>&lt;p&gt;Have you ever looked at a dependency and wondered when it was added, how its version changed, or which commit introduced an update?&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;dep-blame&lt;/strong&gt; to make those questions easier to answer.&lt;/p&gt;

&lt;p&gt;The inspiration came from &lt;a href="https://github.com/git-pkgs/git-pkgs" rel="noopener noreferrer"&gt;git-pkgs&lt;/a&gt;, a standalone Git tool for exploring dependency history. I wanted to bring that idea into a focused tool for JavaScript and TypeScript projects, with a CLI and a simple local dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does it do?
&lt;/h2&gt;

&lt;p&gt;dep-blame reads your repository’s Git history and shows dependency additions, updates, and removals.&lt;/p&gt;

&lt;p&gt;You can explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a dependency first appeared in the available history.&lt;/li&gt;
&lt;li&gt;How its versions changed over time.&lt;/li&gt;
&lt;li&gt;The commit author, date, and message behind each change.&lt;/li&gt;
&lt;li&gt;Dependency changes across workspace manifests.&lt;/li&gt;
&lt;li&gt;Declared requirements in &lt;code&gt;package.json&lt;/code&gt; and resolved versions in lockfiles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dashboard includes a searchable table, filters, a calendar, and a history view for individual dependencies.&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%2F57d9gkcuzfwag1qufdcv.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%2F57d9gkcuzfwag1qufdcv.png" alt="dep-blame ui loading" width="800" height="413"&gt;&lt;/a&gt;&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%2Fmqzpasix8li7378yc26o.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%2Fmqzpasix8li7378yc26o.png" alt="dep-blame ui" width="799" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The dep-blame dashboard makes dependency changes easier to browse and inspect.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why is it useful?
&lt;/h2&gt;

&lt;p&gt;Imagine your application starts behaving differently after a dependency upgrade. dep-blame helps you locate the version change and its commit, giving you a starting point for investigating the problem.&lt;/p&gt;

&lt;p&gt;It also helps when reviewing dependency updates, joining an unfamiliar project, or understanding how an older codebase evolved.&lt;/p&gt;

&lt;p&gt;The goal is simple: &lt;strong&gt;make dependency history easier to find, read, and understand.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;With Node.js 20+ and Git installed, run these commands inside your project’s Git repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# View dependency history in the terminal&lt;/span&gt;
npx dep-blame

&lt;span class="c"&gt;# Open the local dashboard&lt;/span&gt;
npx dep-blame ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you’ve ever searched through a long lockfile diff to understand one package update, dep-blame might be useful to you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explore the project:&lt;/strong&gt; &lt;a href="https://github.com/ihssmaheel-dev/dep-blame" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/dep-blame" rel="noopener noreferrer"&gt;npm&lt;/a&gt;&lt;/p&gt;

</description>
      <category>npm</category>
      <category>javascript</category>
      <category>dependencies</category>
      <category>git</category>
    </item>
    <item>
      <title>Looking for a opportunity</title>
      <dc:creator>Mohamed Ismail</dc:creator>
      <pubDate>Mon, 21 Sep 2026 13:03:46 +0000</pubDate>
      <link>https://dev.to/ihssmaheel/looking-for-a-opportunity-347i</link>
      <guid>https://dev.to/ihssmaheel/looking-for-a-opportunity-347i</guid>
      <description>&lt;p&gt;Looking for a freelance or job opportunities! I build modern, scalable, and maintainable full stack applications that are ready for production and built to grow. If you have an idea, a project, or need a developer to help bring your product to life, feel free to reach out — let’s build something great together!&lt;/p&gt;

&lt;p&gt;Email: &lt;a href="mailto:ihssmaheel@gmail.com"&gt;ihssmaheel@gmail.com&lt;/a&gt;&lt;br&gt;
Github: &lt;a href="https://github.com/ihssmaheel-dev" rel="noopener noreferrer"&gt;ihssmaheel-dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opportunity</category>
      <category>fullstack</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Wired the Boring 80% So You Can Build the Interesting 20%</title>
      <dc:creator>Mohamed Ismail</dc:creator>
      <pubDate>Wed, 09 Sep 2026 06:01:24 +0000</pubDate>
      <link>https://dev.to/ihssmaheel/the-modular-monolith-starter-build-fast-stay-clean-olm</link>
      <guid>https://dev.to/ihssmaheel/the-modular-monolith-starter-build-fast-stay-clean-olm</guid>
      <description>&lt;p&gt;&lt;em&gt;A ready-to-use fullstack boilerplate built for speed, clarity, and smooth AI-assisted coding.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/ihssmaheel-dev/modular-monolith-starter" rel="noopener noreferrer"&gt;github.com/ihssmaheel-dev/modular-monolith-starter&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Why I Built This
&lt;/h3&gt;

&lt;p&gt;We're in a strange, exciting era. Thanks to AI coding tools, a single developer can now attempt projects that used to take a whole team years to build. But there's a catch — most people don't spend weeks stuck on the hard part. They spend weeks stuck on the &lt;em&gt;boring&lt;/em&gt; part: wiring up auth, picking the right architecture, connecting the database, configuring queues, and just... getting to a point where they can start building the actual idea.&lt;/p&gt;

&lt;p&gt;I've felt that pain myself. So I built this starter to remove it.&lt;/p&gt;

&lt;p&gt;The goal is simple: give a solo developer (or a small team) a clean, production-ready foundation on day one — so the setup grind doesn't eat the weeks you should be spending on your actual product.&lt;/p&gt;




&lt;h3&gt;
  
  
  A Quick, Honest Note Before You Dive In
&lt;/h3&gt;

&lt;p&gt;No two developers build the same way, and that's completely fine. This starter isn't the "one true way" to structure a project — it's &lt;em&gt;my&lt;/em&gt; way, shaped by research into other boilerplates, a lot of blog posts, and more than a few conversations with AI tools along the way.&lt;/p&gt;

&lt;p&gt;If something here doesn't match how you like to work, change it. Rip out what you don't need, rename what bugs you, restructure what feels off — do it &lt;em&gt;before&lt;/em&gt; you start building on top of it, while it's still cheap to change. I'm not asking anyone to use this exactly as-is. I just believe it's a solid, well-thought-out starting point — and I'd rather you shape it to fit you than fight it the whole way through.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Idea: Clean Modules, One Codebase
&lt;/h3&gt;

&lt;p&gt;Instead of piling everything into one giant folder, this starter splits your app into separate modules — like &lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;notes&lt;/code&gt;, &lt;code&gt;tenancy&lt;/code&gt;, &lt;code&gt;files&lt;/code&gt;, &lt;code&gt;notifications&lt;/code&gt;, and &lt;code&gt;privacy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Each module follows the same four simple layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Presentation&lt;/strong&gt; – Thin controllers that just validate input and send responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application&lt;/strong&gt; – The actual logic, like &lt;code&gt;CreateNoteCommand&lt;/code&gt; or &lt;code&gt;GetUserQuery&lt;/code&gt;, each in its own small file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain&lt;/strong&gt; – Pure business rules. No frameworks, no database code, just logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure&lt;/strong&gt; – Database tables (Drizzle ORM) and repositories that talk to PostgreSQL.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  No More Surprise Crashes
&lt;/h4&gt;

&lt;p&gt;Most apps throw errors when something goes wrong — a bad password, a missing user — and one missed &lt;code&gt;try/catch&lt;/code&gt; can crash your server.&lt;/p&gt;

&lt;p&gt;This starter uses the &lt;strong&gt;Result pattern&lt;/strong&gt; (via &lt;code&gt;neverthrow&lt;/code&gt;) instead. Every function returns either &lt;code&gt;ok(data)&lt;/code&gt; or &lt;code&gt;err(error)&lt;/code&gt;. Errors become normal values you handle on purpose, not surprises that break things.&lt;/p&gt;




&lt;h3&gt;
  
  
  Built for AI Coding Tools
&lt;/h3&gt;

&lt;p&gt;If you use tools like Cursor, Claude Code, GitHub Copilot, or Antigravity, you already know they work best on clean code — and struggle once files get long or types get messy.&lt;/p&gt;

&lt;p&gt;This starter is designed with that in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small files.&lt;/strong&gt; Routes stay under 150 lines, and use cases stay under 400 — short enough for an AI tool to read in one go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One source of truth.&lt;/strong&gt; Every API request and response is defined with Zod schemas in &lt;code&gt;@repo/contracts&lt;/code&gt;, so nothing is left to guesswork.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in guardrails.&lt;/strong&gt; The build catches common mistakes automatically — like a UI component accidentally importing a database model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear instructions.&lt;/strong&gt; An &lt;code&gt;AGENTS.md&lt;/code&gt; file and an &lt;code&gt;ai_instructions/&lt;/code&gt; folder spell out the project's conventions for any AI assistant working in the codebase.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  What You Get Out of the Box
&lt;/h3&gt;

&lt;h4&gt;
  
  
  API, Two Ways
&lt;/h4&gt;

&lt;p&gt;Use &lt;strong&gt;oRPC&lt;/strong&gt; for fast, type-safe calls with auto-generated TanStack Query hooks, or plain &lt;strong&gt;REST&lt;/strong&gt; for webhooks and third-party integrations. Both share the same underlying logic, so you never write it twice.&lt;/p&gt;

&lt;h4&gt;
  
  
  Multi-Tenancy, Done Right
&lt;/h4&gt;

&lt;p&gt;Switch between single-tenant and multi-tenant modes with one environment variable. In multi-tenant mode, PostgreSQL's Row-Level Security enforces data isolation at the database level — so even a buggy query can't leak another organization's data.&lt;/p&gt;

&lt;h4&gt;
  
  
  Roles &amp;amp; Permissions
&lt;/h4&gt;

&lt;p&gt;A full authorization system with roles, ownership checks, and custom policies. Protecting a route is as simple as adding &lt;code&gt;@RequirePermission('notes:read')&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Solid Authentication
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;JWTs with automatic key rotation&lt;/li&gt;
&lt;li&gt;Single-use refresh tokens tracked in Redis to stop replay attacks&lt;/li&gt;
&lt;li&gt;"Log out everywhere" support&lt;/li&gt;
&lt;li&gt;CSRF protection and account lockout against brute-force attempts&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  No Double Charges
&lt;/h4&gt;

&lt;p&gt;Add &lt;code&gt;@Idempotent()&lt;/code&gt; and Redis will catch duplicate requests — so a double-clicked "Submit" button doesn't create two orders.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reliable Background Jobs
&lt;/h4&gt;

&lt;p&gt;Using the Transactional Outbox pattern, state changes and their events save in the same database transaction. A background worker then pushes them to BullMQ, so nothing gets lost even if the server restarts.&lt;/p&gt;

&lt;h4&gt;
  
  
  Direct-to-S3 Uploads
&lt;/h4&gt;

&lt;p&gt;Files go straight from the client to S3 or MinIO using a presigned URL — never through your API server. Unused files are cleaned up automatically after 7 days.&lt;/p&gt;

&lt;h4&gt;
  
  
  GDPR Ready
&lt;/h4&gt;

&lt;p&gt;Users can export all their data in one click, or request full account erasure — profiles are anonymized immediately and data is cleared after a 30-day grace period.&lt;/p&gt;

&lt;h4&gt;
  
  
  Realtime Updates
&lt;/h4&gt;

&lt;p&gt;WebSockets and Server-Sent Events, backed by Redis Streams, so realtime features scale across multiple servers without missing events.&lt;/p&gt;

&lt;h4&gt;
  
  
  Shared Design Tokens &amp;amp; i18n
&lt;/h4&gt;

&lt;p&gt;Edit colors, spacing, and fonts once in &lt;code&gt;active.json&lt;/code&gt;, and it updates your web CSS, mobile styles, and email templates together. Comes with English, Spanish, and French translations, plus checks that catch missing translation keys.&lt;/p&gt;

&lt;h4&gt;
  
  
  Built-In Observability
&lt;/h4&gt;

&lt;p&gt;Tracing, metrics, and structured logs are wired in from the start, with ready-made dashboards for viewing traces and logs locally — so you can actually see what your app is doing instead of guessing.&lt;/p&gt;




&lt;h3&gt;
  
  
  Add a New Feature in Seconds
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm generate:feature invoices invoice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one command scaffolds everything you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation schemas and API contracts&lt;/li&gt;
&lt;li&gt;Domain entities and error types&lt;/li&gt;
&lt;li&gt;Database tables and repositories&lt;/li&gt;
&lt;li&gt;CQRS commands and queries&lt;/li&gt;
&lt;li&gt;REST and oRPC controllers&lt;/li&gt;
&lt;li&gt;Frontend API client methods&lt;/li&gt;
&lt;li&gt;TanStack Query hooks and UI components&lt;/li&gt;
&lt;li&gt;Unit and integration tests&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  The Stack
&lt;/h3&gt;

&lt;p&gt;I picked every tool here deliberately — modern, open source, and battle-tested. This isn't the full list (that would be a very long table), just the pieces most worth knowing about:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monorepo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Turborepo + pnpm workspaces, TypeScript throughout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NestJS on Fastify&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Validation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zod (schemas) + neverthrow (&lt;code&gt;Result&lt;/code&gt; pattern for errors)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;oRPC (type-safe RPC) with REST as a compatibility layer, docs via Scalar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL with Row-Level Security + Drizzle ORM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache &amp;amp; Jobs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Redis + BullMQ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auth &amp;amp; Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Argon2 hashing, JWT with key rotation, CSRF protection, rate limiting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authorization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom hybrid engine (roles + ownership + policies)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;S3 / MinIO with direct-to-client presigned uploads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Email&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React Email, with Resend or plain SMTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Realtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;WebSockets + Server-Sent Events over Redis Streams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Web Frontend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TanStack Start (React 19, Router, Query, Zustand)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mobile&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Expo + Expo Router + NativeWind&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Base UI primitives + Tailwind CSS + shadcn-style theming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;i18n&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;react-i18next, with English, Spanish, and French out of the box&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vitest + Playwright, with architectural boundary checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Observability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OpenTelemetry, Prometheus, Pino, with Grafana/Jaeger/Loki for local dashboards&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All open source, with no vendor lock-in — and every piece here can be swapped out if it's not your taste.&lt;/p&gt;




&lt;h3&gt;
  
  
  Get Started in 3 Minutes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Clone the repository&lt;/span&gt;
git clone https://github.com/ihssmaheel-dev/modular-monolith-starter.git
&lt;span class="nb"&gt;cd &lt;/span&gt;modular-monolith-starter

&lt;span class="c"&gt;# 2. Install dependencies&lt;/span&gt;
pnpm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# 3. Start local services (Postgres, Redis, MinIO, Mailpit)&lt;/span&gt;
pnpm docker:up
pnpm docker:init

&lt;span class="c"&gt;# 4. Run database migrations&lt;/span&gt;
pnpm db:migrate

&lt;span class="c"&gt;# 5. Start development servers&lt;/span&gt;
pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your API runs at &lt;code&gt;http://localhost:3000&lt;/code&gt; (docs at &lt;code&gt;/api/docs&lt;/code&gt;), and the web app runs at &lt;code&gt;http://localhost:5173&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Star It, Try It, Help Shape It
&lt;/h3&gt;

&lt;p&gt;This project is still growing, and I'd love your feedback — bug reports, feature ideas, or just thoughts on what could be better. And remember: if a piece of this doesn't fit how you work, that's completely fine — unwire it, swap it, make it yours before you build on top.&lt;/p&gt;

&lt;p&gt;⭐ &lt;strong&gt;&lt;a href="https://github.com/ihssmaheel-dev/modular-monolith-starter" rel="noopener noreferrer"&gt;Star the repo on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
💬 &lt;strong&gt;&lt;a href="https://github.com/ihssmaheel-dev/modular-monolith-starter/issues" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt;&lt;/strong&gt; to share your thoughts.&lt;/p&gt;

&lt;p&gt;Let's build something great together.&lt;/p&gt;

</description>
      <category>modularmonolith</category>
      <category>ai</category>
      <category>webdev</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>I Wired the Boring 80% So You Can Build the Interesting 20%</title>
      <dc:creator>Mohamed Ismail</dc:creator>
      <pubDate>Tue, 08 Sep 2026 19:24:39 +0000</pubDate>
      <link>https://dev.to/ihssmaheel/i-wired-the-boring-80-so-you-can-build-the-interesting-20-3dfm</link>
      <guid>https://dev.to/ihssmaheel/i-wired-the-boring-80-so-you-can-build-the-interesting-20-3dfm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Built for the AI era: a production-ready TypeScript modular monolith&lt;br&gt;
with guardrails for robot coworkers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every new project starts the same way. You need auth, multi-tenancy,&lt;br&gt;
file uploads, notifications, emails, i18n, a web app, a mobile app,&lt;br&gt;
tests, observability… and three weeks later you're still wiring&lt;br&gt;
boilerplate instead of building the thing that actually matters.&lt;/p&gt;

&lt;p&gt;So I built the starter I always wished existed: a production-grade&lt;br&gt;
modular monolith in TypeScript — one repo, one deployable, zero&lt;br&gt;
premature microservices — with all the boring-but-critical stuff&lt;br&gt;
already done. It's open source and free: fork it, change it, make it&lt;br&gt;
yours → &lt;a href="https://github.com/ihssmaheel-dev/modular-monolith-starter" rel="noopener noreferrer"&gt;https://github.com/ihssmaheel-dev/modular-monolith-starter&lt;/a&gt;.&lt;br&gt;
Here's the tour. 🎢&lt;/p&gt;




&lt;h3&gt;
  
  
  Starting is easy. Surviving is hard.
&lt;/h3&gt;

&lt;p&gt;AI made it possible to build a working app in a weekend. That's amazing —&lt;br&gt;
but it's also where the trouble starts. Once real users, real data, and&lt;br&gt;
real money arrive, so do the questions nobody planned for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where does this business rule even live?&lt;/li&gt;
&lt;li&gt;Why does changing one screen break three others?&lt;/li&gt;
&lt;li&gt;Who can access whose data?&lt;/li&gt;
&lt;li&gt;How do we delete a user's data when the law says we must?&lt;/li&gt;
&lt;li&gt;How do we ship mobile without rewriting everything?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Speed without structure becomes pain. Every time.&lt;/p&gt;

&lt;p&gt;This starter exists so you can have both: move fast on day one, and&lt;br&gt;
still sleep well on day one thousand.&lt;/p&gt;




&lt;h3&gt;
  
  
  The 10-second pitch
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; NestJS 11 + Fastify + PostgreSQL 16 + Drizzle + Redis + BullMQ&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web:&lt;/strong&gt; TanStack Start (SSR) + TanStack Query + Zustand + Tailwind 4 + shadcn&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile:&lt;/strong&gt; Expo 57 + expo-router + NativeWind (mirrors the web features)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contracts:&lt;/strong&gt; Zod 4 schemas shared end-to-end — the compiler catches API drift&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Already wired:&lt;/strong&gt; fine-grained authorization (RBAC + ReBAC + ABAC),
multi-tenancy, GDPR tools, notifications, file uploads, one-file reskin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DX candy:&lt;/strong&gt; one-command bootstrap, a rebrand script, a full-stack
feature generator, interactive API docs, local observability stack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-native:&lt;/strong&gt; the repo ships machine-readable architecture laws,
so AI assistants write code that actually fits&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  The architecture: a modular monolith with lite DDD
&lt;/h3&gt;

&lt;p&gt;Big words, simple meaning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monolith&lt;/strong&gt; — one codebase, one deploy. No microservice maze when you
have three developers and a dream. You can always split later, when
(and if) you actually need to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modular&lt;/strong&gt; — inside that monolith, every feature (&lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;notes&lt;/code&gt;,
&lt;code&gt;files&lt;/code&gt;, &lt;code&gt;notifications&lt;/code&gt;…) is a strict module with walls around it.
Modules never touch each other's database tables. They talk through
clean commands, queries, and events — and CI blocks you if you cheat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lite DDD&lt;/strong&gt; — each module has small layers: presentation, application,
domain, infrastructure. Just enough structure to keep business logic
pure and testable, without the enterprise paperwork.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple to start, safe to scale — easy for a new developer (or an AI&lt;br&gt;
agent) to grasp in an afternoon, and solid when the team grows to twenty.&lt;/p&gt;




&lt;h3&gt;
  
  
  A real backend, not a todo-app demo
&lt;/h3&gt;

&lt;p&gt;Each domain module follows CQRS: thin controllers,&lt;br&gt;
single-responsibility commands and queries, pure domain entities, and&lt;br&gt;
Drizzle repositories. Application code never throws — it returns&lt;br&gt;
&lt;code&gt;neverthrow&lt;/code&gt; Results. Boring? Yes. The kind of boring that sleeps&lt;br&gt;
through the night? Also yes.&lt;/p&gt;

&lt;p&gt;Already wired behind those modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;oRPC + REST with parity&lt;/strong&gt; — type-safe RPC by default, REST fallback
during migrations, interactive Scalar API docs at &lt;code&gt;/api/docs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-grained authorization&lt;/strong&gt; — one vocabulary (&lt;code&gt;notes:create&lt;/code&gt;,
&lt;code&gt;team:invite&lt;/code&gt;…), ownership checks, tenant and department predicates,
enforced via decorators and services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-trust multi-tenancy&lt;/strong&gt; — single or multi-tenant via one flag,
automatic row-level isolation, Postgres RLS as defense in depth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transactional outbox&lt;/strong&gt; — domain events publish reliably, with
retries and a replayable dead-letter queue&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Realtime two ways&lt;/strong&gt; — WebSocket gateway plus user-scoped SSE, with
shared fan-out so web and mobile update live&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilience boring stuff&lt;/strong&gt; — rate limiting, WAF, circuit breakers,
bulkheads, idempotency keys, CSRF, scheduled jobs, Piscina worker
threads for CPU-heavy work, health probes via NestJS Terminus&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth done right&lt;/strong&gt; — Argon2 password hashing, short-lived Bearer
tokens held in memory, rotating refresh tokens and signing keys,
SecureStore on mobile, secure server sessions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File pipeline, not just uploads&lt;/strong&gt; — presigned S3 URLs, generic
upload-then-attach with slots for any parent record, and an orphan
janitor so abandoned bytes never become a storage bill&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request correlation everywhere&lt;/strong&gt; — &lt;code&gt;x-request-id&lt;/code&gt; flows through
logs, error envelopes, and traces, so a user report maps to the
exact request in seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transactional email&lt;/strong&gt; — React Email templates over Resend or SMTP
with circuit-breaker failover, previewed locally in Mailpit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GDPR built in&lt;/strong&gt; — data export, account erasure, organization
erasure, immutable audit trail with retention worker&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability from day one&lt;/strong&gt; — structured Pino logs, OpenTelemetry
traces, Prometheus metrics, and a provider-neutral error sink&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;i18n on both sides of the wire&lt;/strong&gt; — backend errors localized through
a shared dictionary service with &lt;code&gt;Accept-Language&lt;/code&gt; negotiation, the
same keys consumed by web and mobile, and CI parity checks so no
language ever falls behind&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Web + mobile, speaking the same language
&lt;/h3&gt;

&lt;p&gt;The web app (SSR on srvx, file-based routes, dark mode, EN/ES/FR out of&lt;br&gt;
the box) and the Expo mobile app (SecureStore auth, push notifications,&lt;br&gt;
deep links, offline-tolerant queries) consume the same Zod contracts as&lt;br&gt;
the API. Change a schema, and TypeScript yells at every layer that&lt;br&gt;
disagrees. API drift becomes a compile error instead of a 2 a.m. mystery.&lt;/p&gt;

&lt;p&gt;Features already living in both clients: auth flows, notes CRUD with&lt;br&gt;
attachments, user management, tenant invitations, notification center&lt;br&gt;
with preferences and live updates, GDPR export and erasure, avatar +&lt;br&gt;
file uploads (presigned S3 URLs via local MinIO), toasts, paginated&lt;br&gt;
data tables, and empty/loading/error states that don't lie to users.&lt;/p&gt;

&lt;p&gt;The notes feature doubles as the reference slice: a complete,&lt;br&gt;
production-shaped example — contracts, module, permissions,&lt;br&gt;
translations, tests, web and mobile UI — that new developers read&lt;br&gt;
first and copy when building their own features.&lt;/p&gt;

&lt;p&gt;And when it's time to make it yours: change one theme file, run one&lt;br&gt;
command, and the whole product — web, mobile, even emails — wears&lt;br&gt;
your brand.&lt;/p&gt;




&lt;h3&gt;
  
  
  The complete stack, and why each piece earned its place
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NestJS 11 + Fastify 5&lt;/td&gt;
&lt;td&gt;Modules and DI that mirror the monolith; Fastify for raw speed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL 16&lt;/td&gt;
&lt;td&gt;JSON, full-text search, RLS, rock-solid transactions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;SQL you can read, types you can trust, migrations you control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis 7 + BullMQ&lt;/td&gt;
&lt;td&gt;Queues, retries, digests, and scheduled work that survives restarts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zod 4 + oRPC&lt;/td&gt;
&lt;td&gt;One contract language, end to end — drift is a compile error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;neverthrow&lt;/td&gt;
&lt;td&gt;Errors as values, not hidden GOTO statements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Argon2&lt;/td&gt;
&lt;td&gt;Modern password hashing, not legacy bcrypt defaults&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pino + OpenTelemetry + prom-client&lt;/td&gt;
&lt;td&gt;Structured logs, traces, and metrics without extra wiring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nodemailer / Resend + React Email&lt;/td&gt;
&lt;td&gt;Transactional mail with failover and real templates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Piscina + NestJS Schedule&lt;/td&gt;
&lt;td&gt;CPU-heavy work off the event loop; cron without a sidecar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebSockets + SSE&lt;/td&gt;
&lt;td&gt;Live updates for browsers and phones alike&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TanStack Start + React 19&lt;/td&gt;
&lt;td&gt;File-based routes and SSR without framework magic taking over — explicit, portable, and easy for AI tools to reason about. No lock-in, no deploy surprises&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TanStack Query + Zustand 5&lt;/td&gt;
&lt;td&gt;Server state with caching; client state without boilerplate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tailwind 4 + shadcn + Base UI&lt;/td&gt;
&lt;td&gt;Utility styling plus accessible primitives, themed by tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-hook-form + zodResolver&lt;/td&gt;
&lt;td&gt;Forms validated by the same schemas as the API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-i18next + date-fns&lt;/td&gt;
&lt;td&gt;Every string and date localized, no hardcoding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vitest + Playwright&lt;/td&gt;
&lt;td&gt;Fast unit tests plus real browser journeys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;srvx + nginx&lt;/td&gt;
&lt;td&gt;Lean SSR server in front of a production-grade proxy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Mobile&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Expo 57 + expo-router&lt;/td&gt;
&lt;td&gt;Real native apps, file-based routes like the web&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NativeWind + Tailwind 3&lt;/td&gt;
&lt;td&gt;Same design tokens, native rendering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SecureStore + expo-notifications&lt;/td&gt;
&lt;td&gt;Hardware-backed secrets; push that actually arrives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same api-client + contracts&lt;/td&gt;
&lt;td&gt;One API language across web, mobile, and server&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Platform&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Turborepo + pnpm 10 + TypeScript 6&lt;/td&gt;
&lt;td&gt;Fast monorepo builds, one lockfile, strict types everywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker + MinIO + Mailpit + pgAdmin&lt;/td&gt;
&lt;td&gt;Prod-like local infra: S3, inbox, and DB GUI included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prometheus + Loki + Jaeger + Grafana&lt;/td&gt;
&lt;td&gt;Metrics, logs, and traces locally before you need them in prod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Husky + commitlint + Changesets&lt;/td&gt;
&lt;td&gt;Clean commits and versioned releases by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Actions CI/CD&lt;/td&gt;
&lt;td&gt;Typecheck, lint, tests, architecture rules — enforced, not suggested&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nothing trendy-for-trendy's-sake. Everything chosen to make the next&lt;br&gt;
five years easier, not just the next five minutes.&lt;/p&gt;




&lt;h3&gt;
  
  
  The developer experience is the feature
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pnpm bootstrap&lt;/code&gt; → deps, env, Docker services, migrations, build.
Then &lt;code&gt;pnpm dev&lt;/code&gt;. That's it. (An optional idempotent seed creates
your first admin.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pnpm project:init&lt;/code&gt; rebrands the whole starter — name, slug, app
titles, bundle IDs — so your fork stops looking like a template in
minutes, with a dry-run plan before it touches anything.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pnpm generate:feature &amp;lt;module&amp;gt; &amp;lt;feature&amp;gt;&lt;/code&gt; scaffolds a full vertical
slice: contracts, backend module, API client, web + mobile UI. A new
feature stops being a two-day wiring exercise.&lt;/li&gt;
&lt;li&gt;Local infra with one command each: Postgres, Redis, MinIO, Mailpit
(a fake inbox!), pgAdmin — plus Grafana, Prometheus, Loki, and Jaeger
when you want to feel like a platform team.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pnpm rules:check&lt;/code&gt; enforces architecture as code: file placement, no
stray &lt;code&gt;fetch&lt;/code&gt;, locale parity across languages, co-located tests for
every data module. The rules bite — I know, they've bitten me.&lt;/li&gt;
&lt;li&gt;Production is planned, not improvised: nginx proxy, workers, and
migration runners in compose, pg_dump backups with verified restores,
secrets via &lt;code&gt;_FILE&lt;/code&gt; mounts, traces and logs wired from the start.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Testing without the lecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API:&lt;/strong&gt; 119 Vitest files (unit + real-Postgres integration + e2e)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web:&lt;/strong&gt; 87 unit and component tests + Playwright journeys&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile:&lt;/strong&gt; 91 logic tests with native modules mocked at one seam&lt;/li&gt;
&lt;li&gt;Coverage gates start low and ratchet up. No ceremony, no 3-hour suites.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Built for the AI-assisted era 🤖
&lt;/h3&gt;

&lt;p&gt;Here's the part I'm most excited about. The repo has an&lt;br&gt;
&lt;code&gt;ai_instructions/&lt;/code&gt; folder — mandatory, machine-readable architecture&lt;br&gt;
laws: locked stack, where every file belongs, how errors, i18n, and&lt;br&gt;
tests work. Combined with small single-responsibility files and&lt;br&gt;
end-to-end types, AI assistants stop hallucinating random patterns and&lt;br&gt;
start writing code that fits the codebase on the first try. It turned my&lt;br&gt;
own workflow from "review every line suspiciously" into "review, nod,&lt;br&gt;
merge." The feature generator plus strict contracts plus enforced rules&lt;br&gt;
basically function as guardrails for robot coworkers.&lt;/p&gt;




&lt;h3&gt;
  
  
  How it makes life simpler, concretely
&lt;/h3&gt;

&lt;p&gt;Last week I needed a full feature with API, web UI, mobile UI,&lt;br&gt;
permissions, translations in three languages, and tests. Old me: two&lt;br&gt;
days. With this starter: scaffold the slice, fill in the business logic,&lt;br&gt;
run the gate (&lt;code&gt;typecheck&lt;/code&gt;, &lt;code&gt;lint&lt;/code&gt;, &lt;code&gt;test:unit&lt;/code&gt;, &lt;code&gt;rules:check&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;format:check&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;), commit. The checklist does the worrying.&lt;/p&gt;




&lt;h3&gt;
  
  
  It's still cooking — come help 👨‍🍳
&lt;/h3&gt;

&lt;p&gt;An honest note: this is an active, early-stage project. Mobile component&lt;br&gt;
tests are now in place, coverage keeps ratcheting, docs keep growing —&lt;br&gt;
and next up is a Python-based intelligence layer, so AI features live in&lt;br&gt;
a dedicated service beside the NestJS API. There will still be rough&lt;br&gt;
edges and bugs we haven't met. But everything you need to start&lt;br&gt;
a serious project today is already here and wired.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get the code in 2 minutes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ihssmaheel-dev/modular-monolith-starter.git
&lt;span class="nb"&gt;cd &lt;/span&gt;modular-monolith-starter
pnpm bootstrap
pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole install. If it helps you, a star on &lt;a href="https://github.com/ihssmaheel-dev/modular-monolith-starter" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; is the cheapest way to say thanks — and it helps other developers find it too. ⭐&lt;/p&gt;

&lt;p&gt;If you try it and something breaks, confuses you, or just smells wrong —&lt;br&gt;
please &lt;a href="https://github.com/ihssmaheel-dev/modular-monolith-starter/issues" rel="noopener noreferrer"&gt;open an issue on GitHub&lt;/a&gt;&lt;br&gt;
or drop it in the comments. Feature requests doubly welcome. The fastest&lt;br&gt;
way to make a starter great is people actually using it and complaining&lt;br&gt;
loudly. 🙂&lt;/p&gt;

&lt;p&gt;Happy building — and may your monolith stay modular.&lt;/p&gt;

</description>
      <category>biolerplate</category>
      <category>starter</category>
      <category>nestjs</category>
      <category>react</category>
    </item>
    <item>
      <title>I Built a Library to Sync Browser Tabs</title>
      <dc:creator>Mohamed Ismail</dc:creator>
      <pubDate>Mon, 15 Jun 2026 19:58:20 +0000</pubDate>
      <link>https://dev.to/ihssmaheel/i-built-a-library-to-sync-browser-tabs-3g5d</link>
      <guid>https://dev.to/ihssmaheel/i-built-a-library-to-sync-browser-tabs-3g5d</guid>
      <description>&lt;h2&gt;
  
  
  Here's Why and How to Use It
&lt;/h2&gt;

&lt;p&gt;We've all been there. You're building a web app, everything works fine in one tab — but the moment a user opens a second tab, things start breaking. Cart items disappear. Login state gets out of sync. Background tasks run twice.&lt;/p&gt;

&lt;p&gt;I ran into this problem one too many times, so I built &lt;strong&gt;tabcoord&lt;/strong&gt; to solve it properly.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is tabcoord?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;tabcoord&lt;/strong&gt; is a small library (under 5KB, zero dependencies) that keeps your browser tabs in sync. It gives you shared state, leader election, locks, and an event bus — all working across tabs automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;tabcoord tabcoord-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;tabcoord-react&lt;/code&gt; uses &lt;code&gt;tabcoord&lt;/code&gt; as a peer dependency, so you need both installed.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Built on Top of the Native BroadcastChannel API
&lt;/h2&gt;

&lt;p&gt;tabcoord uses the browser's built-in &lt;code&gt;BroadcastChannel&lt;/code&gt; API under the hood — so it's not replacing it, it's building on top of it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BroadcastChannel&lt;/code&gt; is great at one thing: sending raw messages between tabs. But that's where it stops. Everything on top of that — syncing state, persisting it, bootstrapping a new tab that opens late, handling older browsers, working safely in SSR — you'd have to wire up yourself.&lt;/p&gt;

&lt;p&gt;tabcoord takes care of all of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State management&lt;/strong&gt; — not just messages, but a real shared store with React bindings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New tab bootstrap&lt;/strong&gt; — when a tab opens late, it automatically gets the current state from existing tabs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence&lt;/strong&gt; — state survives page refreshes via localStorage, without you touching it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leader election and locks&lt;/strong&gt; — coordination primitives built on the same channel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;localStorage fallback&lt;/strong&gt; — for older browsers or Safari private mode where BroadcastChannel isn't available&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSR safety&lt;/strong&gt; — no crashes in Next.js or Remix because the server never touches BroadcastChannel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if you already know BroadcastChannel, tabcoord will feel familiar — it's just everything you'd have had to build on top of it, already done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Shared State Across Tabs
&lt;/h2&gt;

&lt;p&gt;The core feature is &lt;code&gt;createSharedStore&lt;/code&gt;. You define a store once, and every tab that uses it stays in sync automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// store.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createSharedStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabcoord&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSharedStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use it in your React components with &lt;code&gt;useSharedStore&lt;/code&gt; from &lt;code&gt;tabcoord-react&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSharedStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabcoord-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./store&lt;/span&gt;&lt;span class="dl"&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;CartButton&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSharedStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-item&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}))}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nc"&gt;Cart &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&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;p&gt;Open your app in two tabs. Add something to the cart in one tab. The other tab updates immediately — no refresh, no extra setup.&lt;/p&gt;

&lt;p&gt;State is also &lt;strong&gt;persisted to localStorage&lt;/strong&gt; automatically, so it survives page refreshes too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Only One Tab Does the Heavy Work
&lt;/h2&gt;

&lt;p&gt;If you have background polling or periodic API calls, you don't want every tab running them at the same time. That's wasteful and can cause real problems.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;leaderElection&lt;/code&gt; picks one tab to be in charge. If that tab closes, another one automatically takes over.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;leaderElection&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabcoord&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;election&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;leaderElection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background-sync&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;election&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onElected&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/updates&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;election&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDemoted&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&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;p&gt;One tab polls. The rest stay quiet. When the leader closes, a new one steps up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Preventing Race Conditions with Locks
&lt;/h2&gt;

&lt;p&gt;Sometimes two tabs might try to do the same thing at the same time — like submitting a form or running an import. &lt;code&gt;lockManager&lt;/code&gt; makes them take turns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;lockManager&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabcoord&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lockManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-import&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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;await&lt;/span&gt; &lt;span class="nf"&gt;runImport&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Only one tab runs this at a time&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first tab to get the lock runs. Every other tab waits. When it's done, the next one goes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sending Events Across Tabs
&lt;/h2&gt;

&lt;p&gt;Not everything needs shared state. Sometimes you just want to tell other tabs that something happened — like a logout or a notification.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;eventBus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabcoord&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;eventBus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// In one tab, listen:&lt;/span&gt;
&lt;span class="nx"&gt;bus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user:logout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;redirectToLogin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// In another tab, fire:&lt;/span&gt;
&lt;span class="nx"&gt;bus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user:logout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session-expired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The event goes to every tab instantly. It supports wildcards (&lt;code&gt;user:*&lt;/code&gt;) and a replay buffer so new tabs can catch up on recent events they missed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Works with Next.js and Remix
&lt;/h2&gt;

&lt;p&gt;Server-side rendering is where most tab-sync libraries break. They crash because browser APIs like &lt;code&gt;BroadcastChannel&lt;/code&gt; don't exist on the server.&lt;/p&gt;

&lt;p&gt;tabcoord handles this cleanly. On the server, stores return your initial values and behave like regular local state. When the page loads in the browser, they connect to the real cross-tab sync automatically. No crashes, no special wrappers needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Does It Compare?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  tabcoord vs Native BroadcastChannel
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;tabcoord&lt;/th&gt;
&lt;th&gt;Native BroadcastChannel&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;State sync&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;td&gt;❌ Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;td&gt;✅ Automatic&lt;/td&gt;
&lt;td&gt;❌ Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leader election&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;td&gt;❌ Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock manager&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;td&gt;❌ Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser fallback&lt;/td&gt;
&lt;td&gt;✅ localStorage&lt;/td&gt;
&lt;td&gt;❌ None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSR support&lt;/td&gt;
&lt;td&gt;✅ Works&lt;/td&gt;
&lt;td&gt;❌ Crashes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bundle size&lt;/td&gt;
&lt;td&gt;4.78 KB&lt;/td&gt;
&lt;td&gt;0 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Use tabcoord&lt;/strong&gt; when you need state management, persistence, leader election, or locks.&lt;br&gt;
&lt;strong&gt;Use native BroadcastChannel&lt;/strong&gt; when you just need simple one-off message passing.&lt;/p&gt;




&lt;h3&gt;
  
  
  tabcoord vs &lt;code&gt;broadcast-channel&lt;/code&gt; (2k stars, 3M+ weekly downloads)
&lt;/h3&gt;

&lt;p&gt;A popular BroadcastChannel polyfill with basic leader election.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;tabcoord&lt;/th&gt;
&lt;th&gt;broadcast-channel&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;State sync&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;td&gt;❌ Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;td&gt;✅ Automatic&lt;/td&gt;
&lt;td&gt;❌ Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock manager&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;td&gt;❌ Not available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event bus&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;td&gt;❌ Not available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSR support&lt;/td&gt;
&lt;td&gt;✅ Works&lt;/td&gt;
&lt;td&gt;❌ Browser only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;❌ SSR only&lt;/td&gt;
&lt;td&gt;✅ Full support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bundle size&lt;/td&gt;
&lt;td&gt;4.78 KB&lt;/td&gt;
&lt;td&gt;8.2 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Use tabcoord&lt;/strong&gt; when you need the full coordination layer.&lt;br&gt;
&lt;strong&gt;Use broadcast-channel&lt;/strong&gt; when you need a polyfill for old browsers or Node.js IPC.&lt;/p&gt;




&lt;h3&gt;
  
  
  tabcoord vs WebSocket
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;tabcoord&lt;/th&gt;
&lt;th&gt;WebSocket&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Setup&lt;/td&gt;
&lt;td&gt;Zero config&lt;/td&gt;
&lt;td&gt;Server required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;&amp;lt; 5ms&lt;/td&gt;
&lt;td&gt;50–200ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works offline&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ Needs server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-user&lt;/td&gt;
&lt;td&gt;❌ One user's tabs&lt;/td&gt;
&lt;td&gt;✅ Many users&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Use tabcoord&lt;/strong&gt; when it's one person's tabs on one machine.&lt;br&gt;
&lt;strong&gt;Use WebSocket&lt;/strong&gt; when you need real-time multi-user collaboration.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Small Is It?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Gzipped&lt;/th&gt;
&lt;th&gt;Dependencies&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tabcoord&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4.78 KB&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tabcoord-react&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.9 KB&lt;/td&gt;
&lt;td&gt;0 (peer dep: &lt;code&gt;tabcoord&lt;/code&gt;, &lt;code&gt;react&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Under 6 KB total. Nothing else gets pulled in.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ Shared state across tabs, synced in real time&lt;/li&gt;
&lt;li&gt;✅ Persists across page refreshes automatically&lt;/li&gt;
&lt;li&gt;✅ Leader election — one tab handles background work&lt;/li&gt;
&lt;li&gt;✅ Lock manager — no duplicate actions across tabs&lt;/li&gt;
&lt;li&gt;✅ Event bus with wildcard support and replay&lt;/li&gt;
&lt;li&gt;✅ SSR safe — works with Next.js and Remix&lt;/li&gt;
&lt;li&gt;✅ Falls back gracefully on older browsers&lt;/li&gt;
&lt;li&gt;✅ TypeScript first, zero dependencies&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you've ever hacked together &lt;code&gt;localStorage&lt;/code&gt; event listeners to sync tabs and thought &lt;em&gt;there has to be a better way&lt;/em&gt; — this is what I built for exactly that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/tabcoord" rel="noopener noreferrer"&gt;tabcoord on npm&lt;/a&gt;&lt;/strong&gt; · &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/tabcoord-react" rel="noopener noreferrer"&gt;tabcoord-react on npm&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javasc</category>
      <category>react</category>
      <category>npm</category>
    </item>
    <item>
      <title>I Built a Tool That Saves Me 5 Minutes Every Morning</title>
      <dc:creator>Mohamed Ismail</dc:creator>
      <pubDate>Fri, 12 Jun 2026 11:00:31 +0000</pubDate>
      <link>https://dev.to/ihssmaheel/i-built-a-tool-that-saves-me-5-minutes-every-morning-20hb</link>
      <guid>https://dev.to/ihssmaheel/i-built-a-tool-that-saves-me-5-minutes-every-morning-20hb</guid>
      <description>&lt;p&gt;Every morning I open my laptop, start the backend, start the frontend, open a db tab, open a git tab. Five minutes gone before I've written a single line of code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I made this
&lt;/h2&gt;

&lt;p&gt;I kept writing the same batch script over and over for different projects. Every time I started something new, I'd copy an old one and mess up the paths. Half the time it wouldn't run because I forgot to escape a backslash somewhere.&lt;/p&gt;

&lt;p&gt;So I built something to fix that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Meet WinTerm Pilot
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://winterm-pilot.netlify.app" rel="noopener noreferrer"&gt;WinTerm Pilot&lt;/a&gt;&lt;/strong&gt; is a free web tool that builds Windows Terminal launch scripts for you.&lt;/p&gt;

&lt;p&gt;You tell it what your dev setup looks like — which folders, which commands, which tabs you need open — and it generates a &lt;code&gt;.bat&lt;/code&gt; script you can double-click every morning. One click, everything opens exactly how you like it.&lt;/p&gt;

&lt;p&gt;No installation. No account. Just open the site, fill in your setup, download the script, done.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://winterm-pilot.netlify.app" rel="noopener noreferrer"&gt;winterm-pilot.netlify.app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You'll see a simple builder. Add your tabs — give each one a name, a folder path, and the command to run (like &lt;code&gt;npm run dev&lt;/code&gt; or &lt;code&gt;dotnet run&lt;/code&gt;). You can set which tab opens first, choose split panes if you want, and pick your shell (PowerShell, CMD, or WSL).&lt;/p&gt;

&lt;p&gt;Hit generate. Copy or download the script. Run it.&lt;/p&gt;

&lt;p&gt;That's it. Your whole dev environment launches in one go.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who is this actually for?
&lt;/h2&gt;

&lt;p&gt;If you work on Windows and you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have more than two things running when you code&lt;/li&gt;
&lt;li&gt;Keep forgetting which folder your backend lives in&lt;/li&gt;
&lt;li&gt;Wish your terminal just opened the way you want it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...then this is for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://winterm-pilot.netlify.app" rel="noopener noreferrer"&gt;winterm-pilot.netlify.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's free, it runs in your browser, and your data never leaves your machine. If it saves you even two minutes a day, that's over ten hours a year back in your pocket.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by a developer who was tired of clicking the same things every morning.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>winterm</category>
      <category>productivity</category>
      <category>developertools</category>
      <category>script</category>
    </item>
    <item>
      <title>The BroadcastChannel API — The Browser Feature You've Been Ignoring</title>
      <dc:creator>Mohamed Ismail</dc:creator>
      <pubDate>Tue, 09 Jun 2026 07:14:52 +0000</pubDate>
      <link>https://dev.to/ihssmaheel/the-broadcastchannel-api-the-browser-feature-youve-been-ignoring-5alg</link>
      <guid>https://dev.to/ihssmaheel/the-broadcastchannel-api-the-browser-feature-youve-been-ignoring-5alg</guid>
      <description>&lt;p&gt;Most developers find this API by accident — usually while chasing a bug where the user logged out in one tab and the other three still show the dashboard. Embarrassing in dev, security issue in prod.&lt;/p&gt;

&lt;p&gt;The fix is built into every modern browser. It's called &lt;strong&gt;BroadcastChannel&lt;/strong&gt;. Most people skip it because they didn't know it was there.&lt;/p&gt;




&lt;h2&gt;
  
  
  What even is it?
&lt;/h2&gt;

&lt;p&gt;You know how your browser can have the same website open in multiple tabs? Those tabs are completely isolated from each other — they can't talk. Open five tabs of your app and each one lives in its own little bubble.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;BroadcastChannel API&lt;/strong&gt; fixes that. It lets any tab, window, or iframe on the &lt;strong&gt;same origin&lt;/strong&gt; send a message to all the others. One line to open a channel, one line to send, one line to listen. That's it.&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;// Open a channel&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Send a message&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Listen in another tab&lt;/span&gt;
&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// { type: 'hello', payload: 'world' }&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No libraries. No server. No WebSockets. Just the browser doing the work for free.&lt;/p&gt;




&lt;h2&gt;
  
  
  01 — Sync logout across tabs
&lt;/h2&gt;

&lt;p&gt;The obvious one, but worth stating. User logs out in tab A — tabs B, C, D don't know. They sit there showing an authenticated UI to nobody.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth&lt;/span&gt;&lt;span class="dl"&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;logout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;clearSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;authChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LOGOUT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&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;authChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LOGOUT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Works both ways — broadcast login too. An idle tab can automatically refresh without the user lifting a finger.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  02 — Keep UI state in sync without a round-trip
&lt;/h2&gt;

&lt;p&gt;Power users open your app in two windows side by side. They update something in the left window. The right window is stale. BroadcastChannel pushes that state update to every open tab the moment it changes — no refetch, no polling.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uiChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ui-state&lt;/span&gt;&lt;span class="dl"&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;updateTodos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTodos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setTodos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTodos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;uiChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TODOS_UPDATED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newTodos&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;uiChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TODOS_UPDATED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setTodos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// works with React, Zustand, anything&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;
  
  
  03 — Single-tab leader election
&lt;/h2&gt;

&lt;p&gt;A polling loop hitting your API every 5 seconds doesn't need to run in all 8 tabs the user has open. That's 8× the server load for zero benefit. Elect one tab as the leader, let it do the work, broadcast results to the rest.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;leader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isLeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;contested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CLAIM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CLAIM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isLeader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TAKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// we're already here&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TAKEN&lt;/span&gt;&lt;span class="dl"&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;contested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// back off&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;setTimeout&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;contested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;isLeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;startPolling&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// only this tab polls&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;When the leader tab closes, another tab claims it after the timeout. Users never notice anything happened.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  04 — Deduplicate push notifications
&lt;/h2&gt;

&lt;p&gt;Push notification fires from a service worker. You have 3 tabs open. Without coordination, all 3 show the same toast. The user sees the same notification three times. Not great.&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;// In the service worker — broadcast the push&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;notifications&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;push&lt;/span&gt;&lt;span class="dl"&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;e&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="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PUSH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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="c1"&gt;// In the app — first tab to handle it wins&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;handled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PUSH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;handled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;handled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&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="nx"&gt;handled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  05 — Theme and settings sync
&lt;/h2&gt;

&lt;p&gt;User flips to dark mode in one tab. Every other tab should switch too — right now. Same for language, font size, accessibility preferences. Anything that should feel global.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;settingsChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-settings&lt;/span&gt;&lt;span class="dl"&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;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;applyTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;settingsChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;THEME&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;settingsChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;THEME&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;applyTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&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;
  
  
  06 — Share expensive computation results
&lt;/h2&gt;

&lt;p&gt;User uploads a 50MB CSV in Tab A. Your app parses and aggregates it — takes 2 seconds. Tab B is open and needs the same data. Without coordination it parses the whole thing again from scratch. Instead, let Tab A do the work and broadcast the result.&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;// Tab A — does the heavy lifting&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processAndShare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;parseHugeCSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;processed-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CSV_READY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Tab B — gets it for free&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;processed-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CSV_READY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;renderTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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;
  
  
  07 — Multi-tab form draft recovery
&lt;/h2&gt;

&lt;p&gt;User starts filling a long form in Tab A, then opens the same route in Tab B by accident. Tab B should detect this and warn: &lt;em&gt;"you have an unsaved draft open in another tab."&lt;/em&gt; Saves real data loss, especially in CRMs and ERPs.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form-lock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// On mount — check if another tab already has this form open&lt;/span&gt;
&lt;span class="nx"&gt;formChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CHECK_LOCK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;formId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invoice-42&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;formChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CHECK_LOCK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invoice-42&lt;/span&gt;&lt;span class="dl"&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;formChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LOCKED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;formId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invoice-42&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LOCKED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;showWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This form is already open in another tab&lt;/span&gt;&lt;span class="dl"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  08 — Cross-tab undo history
&lt;/h2&gt;

&lt;p&gt;User makes a change in Tab A. Tab B reflects it. Then they hit undo in Tab B. Without coordination — chaos. With BroadcastChannel you can keep a shared undo stack that works across tabs, not just within one.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;historyChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undo-stack&lt;/span&gt;&lt;span class="dl"&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;pushAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;undoStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;historyChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PUSH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&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;undo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;undoStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;historyChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UNDO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;rerender&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;historyChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PUSH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;undoStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UNDO&lt;/span&gt;&lt;span class="dl"&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;undoStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nf"&gt;rerender&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  09 — Iframe-to-host communication
&lt;/h2&gt;

&lt;p&gt;You're building an embedded widget or microfrontend inside an iframe. &lt;code&gt;window.postMessage&lt;/code&gt; works but gets messy fast — origin checks, event filtering, nested frame edge cases. If the host and embed share the same origin, BroadcastChannel is cleaner. Both sides open the same channel and it just works.&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;// Inside the iframe&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bridge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;widget-bridge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;bridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ITEM_SELECTED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;bridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RESET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;resetWidget&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;
  
  
  Three things to know before you ship
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Same origin only.&lt;/strong&gt; &lt;code&gt;https://yourapp.com&lt;/code&gt; and &lt;code&gt;https://app.yourapp.com&lt;/code&gt; are different origins. They cannot talk to each other through this API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sender doesn't receive its own message.&lt;/strong&gt; The tab that calls &lt;code&gt;postMessage&lt;/code&gt; won't trigger its own &lt;code&gt;onmessage&lt;/code&gt;. If the sender also needs to react, call your update function directly alongside the broadcast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clean up in React.&lt;/strong&gt; Close the channel on unmount — &lt;code&gt;return () =&amp;gt; channel.close()&lt;/code&gt; inside &lt;code&gt;useEffect&lt;/code&gt;. Otherwise you'll leak listeners across re-mounts.&lt;/p&gt;




&lt;p&gt;Browser support is all modern browsers — Chrome, Firefox, Safari, Edge, all fine. The whole API is four things: &lt;code&gt;postMessage&lt;/code&gt;, &lt;code&gt;onmessage&lt;/code&gt;, &lt;code&gt;addEventListener&lt;/code&gt;, and &lt;code&gt;close&lt;/code&gt;. Once you know it exists, you start seeing the problem it solves in almost every multi-tab web app.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>frontend</category>
      <category>browser</category>
      <category>api</category>
    </item>
    <item>
      <title>How I Stop Myself From Building Bad Ideas</title>
      <dc:creator>Mohamed Ismail</dc:creator>
      <pubDate>Fri, 29 May 2026 13:54:49 +0000</pubDate>
      <link>https://dev.to/ihssmaheel/how-i-stop-myself-from-building-bad-ideas-42km</link>
      <guid>https://dev.to/ihssmaheel/how-i-stop-myself-from-building-bad-ideas-42km</guid>
      <description>&lt;h2&gt;
  
  
  How I Stop Myself From Building Bad Ideas
&lt;/h2&gt;

&lt;p&gt;I have a problem. I get excited about product ideas way too fast.&lt;/p&gt;

&lt;p&gt;I'll be showering, and suddenly — bam — a billion-dollar idea. I write a PRD, open a code editor, and start building. Two weeks later, I realize nobody actually needs it.&lt;/p&gt;

&lt;p&gt;I needed a way to kill bad ideas before I invested time in them. So I built a filter.&lt;/p&gt;

&lt;h3&gt;
  
  
  What It Does
&lt;/h3&gt;

&lt;p&gt;It's a simple workflow. You write your PRD like you normally would, then run it through a set of prompts with any AI assistant. The AI analyzes your idea across 25 different angles — stuff like market demand, competition, pricing, risks, technical complexity, and even things like compliance and accessibility.&lt;/p&gt;

&lt;p&gt;At the end, you get an HTML dashboard. It looks like a professional investment memo, but you get it in about 5 minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You'll See in the Dashboard
&lt;/h3&gt;

&lt;p&gt;The dashboard has everything on one page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overall score&lt;/strong&gt; — a number from 0 to 100 with a verdict label&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Radar chart&lt;/strong&gt; — see all your scores at a glance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SWOT grid&lt;/strong&gt; — strengths, weaknesses, opportunities, threats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk register&lt;/strong&gt; — what could kill your idea, with severity levels&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market sizing&lt;/strong&gt; — TAM, SAM, SOM breakdown&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitors&lt;/strong&gt; — how you stack up against existing solutions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature fit&lt;/strong&gt; — which features actually matter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing suggestions&lt;/strong&gt; — what people might pay&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;And a lot more&lt;/strong&gt; — 25 analysis modules total&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is in one HTML file. Open it, scroll around.&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%2Ft9w31sm68acqkfgyr2dd.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%2Ft9w31sm68acqkfgyr2dd.png" alt=" " width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't think this is it. This is just for the reference and it does much more analysis. clone it and surf yourself. &lt;/p&gt;

&lt;h3&gt;
  
  
  Why the Scores Feel Harsh
&lt;/h3&gt;

&lt;p&gt;The AI is told to be honest. Brutally honest. Most ideas score below 70. That's not a bug — it's the point.&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%2Fd6xhlodtim9yv7q5mrkf.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%2Fd6xhlodtim9yv7q5mrkf.png" alt=" " width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I wanted a tool that tells me the truth, not what I want to hear. If my idea scores 30, I want to know why so I can either fix it or move on.&lt;/p&gt;

&lt;p&gt;The prompts specifically tell the AI to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never sugarcoat&lt;/li&gt;
&lt;li&gt;Never invent traction or users&lt;/li&gt;
&lt;li&gt;Be direct about flaws&lt;/li&gt;
&lt;li&gt;Use real market assumptions, not wishful thinking&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How I Use It
&lt;/h3&gt;

&lt;p&gt;I write a PRD for any idea that excites me. Takes about an hour. Then I run the analysis. If the score is low and the feedback reveals problems I can't solve, I drop the idea and move to the next one.&lt;/p&gt;

&lt;p&gt;It saves me weeks of building things nobody wants.&lt;/p&gt;

&lt;p&gt;If the score is decent and the feedback points to fixable problems, I know exactly what to work on first.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Project Is Open Source
&lt;/h3&gt;

&lt;p&gt;Everything is on GitHub — the prompts, the HTML template, the workflow instructions. You can use it with any AI chatbot you like. No installation needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/ihssmaheel-dev/prd-intelligence-skill" rel="noopener noreferrer"&gt;github.com/ihssmaheel-dev/prd-intelligence-skill&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I still get excited about ideas. I just don't build all of them anymore.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productdevelopment</category>
      <category>indiehacking</category>
      <category>startup</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>PRD Intelligence: Turn Your Product Ideas into Strategic Reports</title>
      <dc:creator>Mohamed Ismail</dc:creator>
      <pubDate>Sun, 10 May 2026 19:10:44 +0000</pubDate>
      <link>https://dev.to/ihssmaheel/prd-intelligence-turn-your-product-ideas-into-strategic-reports-3l1</link>
      <guid>https://dev.to/ihssmaheel/prd-intelligence-turn-your-product-ideas-into-strategic-reports-3l1</guid>
      <description>&lt;h3&gt;
  
  
  What is PRD Intelligence?
&lt;/h3&gt;

&lt;p&gt;PRD Intelligence is a tool that helps product teams analyze their ideas quickly. Just paste your Product Requirements Document (PRD) and get a complete analysis in minutes.&lt;/p&gt;

&lt;h4&gt;
  
  
  How it Works
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;You write down your product idea&lt;/li&gt;
&lt;li&gt;The tool analyzes it using AI&lt;/li&gt;
&lt;li&gt;You get a full report with 20 business areas covered&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Key Features
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;20 analysis areas&lt;/strong&gt;: Market size, competition, risks, marketing, and more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy first&lt;/strong&gt;: Your data stays on your computer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works with any AI provider&lt;/strong&gt;: OpenAI, Claude, and others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No signup needed&lt;/strong&gt;: Everything works in your browser&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Why It's Useful
&lt;/h4&gt;

&lt;p&gt;Product planning usually takes weeks. This tool does it in minutes. Just write your idea, and get instant business analysis.&lt;/p&gt;

&lt;h4&gt;
  
  
  Try It Yourself
&lt;/h4&gt;

&lt;p&gt;Visit &lt;a href="https://prdis.netlify.app/" rel="noopener noreferrer"&gt;PRD Intelligence&lt;/a&gt; to try it out.&lt;/p&gt;




&lt;p&gt;If you have any suggestions, please comment down below!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have a great idea? Just paste it and see what insights you get!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>product</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
