<?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: Ali Alp</title>
    <description>The latest articles on DEV Community by Ali Alp (@alialp).</description>
    <link>https://dev.to/alialp</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%2F75246%2F73159765-d92c-4500-bf44-2ccb5ddca661.jpg</url>
      <title>DEV Community: Ali Alp</title>
      <link>https://dev.to/alialp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alialp"/>
    <language>en</language>
    <item>
      <title>"Where.Was.I" — a task board built around the moment you switch</title>
      <dc:creator>Ali Alp</dc:creator>
      <pubDate>Mon, 20 Jul 2026 13:06:16 +0000</pubDate>
      <link>https://dev.to/alialp/wherewasi-a-task-board-built-around-the-moment-you-switch-25lj</link>
      <guid>https://dev.to/alialp/wherewasi-a-task-board-built-around-the-moment-you-switch-25lj</guid>
      <description>&lt;p&gt;&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://alicommit-malp.github.io/wherewasi/" rel="noopener noreferrer"&gt;https://alicommit-malp.github.io/wherewasi/&lt;/a&gt; · &lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/alicommit-malp/wherewasi" rel="noopener noreferrer"&gt;https://github.com/alicommit-malp/wherewasi&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most task apps are built around &lt;em&gt;what you have to do&lt;/em&gt;. wherewasi is built around a different moment: the one where you stop doing one thing and start doing another, and your entire mental context evaporates. It answers exactly one question, and it answers it fast — &lt;em&gt;where was I?&lt;/em&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%2F49y5yik59gns6rhc25mj.gif" 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%2F49y5yik59gns6rhc25mj.gif" alt="wherewasi in use" width="760" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://raw.githubusercontent.com/alicommit-malp/wherewasi/main/docs/demo.mp4" rel="noopener noreferrer"&gt;Watch the same clip as MP4&lt;/a&gt; — 40 seconds, no audio.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem it solves
&lt;/h2&gt;

&lt;p&gt;You have five things in flight. You're deep in one of them when something interrupts you. Two days later you come back and spend fifteen minutes reconstructing what you'd already figured out: what you'd finished, what you were blocked on, what the next move was going to be.&lt;/p&gt;

&lt;p&gt;That reconstruction is pure waste, and it's avoidable — the knowledge existed in your head at the moment you left. wherewasi is a place to dump it in five seconds, and to find it again instantly.&lt;/p&gt;

&lt;p&gt;Three rules define the whole app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Exactly one task is active at a time.&lt;/strong&gt; Not a philosophy about focus — just an honest record of where your attention actually is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every task carries a stack of short notes.&lt;/strong&gt; Newest first. "renewed the cert", "waiting on DNS", "next: draft the outline". Free text, no schema, no ceremony.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Switching is instant.&lt;/strong&gt; One click or one keystroke. No dialog, no prompt, no "are you sure" — because anything that adds friction to switching means you'll stop recording your switches, and then the app is worthless.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What it deliberately does &lt;em&gt;not&lt;/em&gt; do
&lt;/h2&gt;

&lt;p&gt;Design is mostly subtraction, and this app had a lot subtracted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No timers, no time tracking.&lt;/strong&gt; The first draft measured how long each task was active. It got cut, because the value was in restoring context, not producing timesheets — and a running clock creates a low-grade obligation to "start" and "stop" things correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No tags or categories.&lt;/strong&gt; An earlier version tagged each note as &lt;em&gt;did&lt;/em&gt; / &lt;em&gt;waiting&lt;/em&gt; / &lt;em&gt;next&lt;/em&gt;. In practice the tag was one more decision at the exact moment you want zero decisions. Plain text carries the same meaning: just type "waiting on DNS".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No forced note on switch.&lt;/strong&gt; The original design popped a modal on every switch asking what happened. It was correct in theory and unbearable in practice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No accounts, no server, no network.&lt;/strong&gt; Your data never leaves your browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those removals came from actually using the thing and noticing the friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The board
&lt;/h3&gt;

&lt;p&gt;One narrow column. Each row is a task: a dot, the title, and its three most recent notes underneath in muted text. The active task is highlighted and is the only row with a note input. That's the whole interface.&lt;/p&gt;

&lt;p&gt;Clicking a task title switches to it instantly and &lt;strong&gt;moves it to the top of the list&lt;/strong&gt;, so the board self-sorts by recency: what you're on is first, what you just left is right below it, and stale work sinks to the bottom on its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Naming the workspace
&lt;/h3&gt;

&lt;p&gt;Click the &lt;strong&gt;wherewasi&lt;/strong&gt; title in the header to give the board a name — &lt;em&gt;wherewasi — work&lt;/em&gt;, &lt;em&gt;wherewasi — side project&lt;/em&gt;, whatever. It also becomes the browser-tab title, so if you keep separate boards in separate browser profiles they stay easy to tell apart. Leave it blank and the header is just &lt;em&gt;wherewasi&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing notes
&lt;/h3&gt;

&lt;p&gt;The active task's input says &lt;em&gt;What happened?&lt;/em&gt;. Type, press Enter, done. Notes stack newest-first, each with a small timestamp so you can see whether a thought is from ten minutes ago or last week.&lt;/p&gt;

&lt;p&gt;Click any note to edit it inline (Enter saves, Escape cancels). If a task has more than three notes, a &lt;code&gt;+N more&lt;/code&gt; link expands the full history — and that expansion collapses automatically the moment you switch tasks, so the board never grows cluttered behind your back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Undo, because instant shouldn't mean irreversible
&lt;/h3&gt;

&lt;p&gt;Archiving a task or deleting a note happens with no confirmation dialog — but neither is a dead end. Both drop a five-second &lt;strong&gt;Undo&lt;/strong&gt; toast (click it, or press &lt;code&gt;u&lt;/code&gt;), and undo restores the item &lt;em&gt;exactly&lt;/em&gt; where it was: an archived task returns to its old board position rather than the bottom, and a deleted note slots back into its original place in the stack. It fits the whole design: act instantly, take it back if you didn't mean it. Let the five seconds lapse and it's committed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Staying in sync across tabs
&lt;/h3&gt;

&lt;p&gt;Open wherewasi in two tabs and they stay consistent — a change in one appears in the other within a moment, because every tab reads and writes the same local database and pings its siblings to refresh. No stale second tab quietly overwriting your work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keyboard
&lt;/h3&gt;

&lt;p&gt;The app is fully keyboard-driven; press &lt;code&gt;?&lt;/code&gt; at any time for this list.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;↓&lt;/code&gt;/&lt;code&gt;j&lt;/code&gt;, &lt;code&gt;↑&lt;/code&gt;/&lt;code&gt;k&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Move the selection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Enter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switch to selected — on the &lt;em&gt;active&lt;/em&gt; task, jump into the note input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write a note on the active task, from anywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;New task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;e&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rename selected task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Done — archive selected task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;u&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Undo the last archive or note deletion (while the prompt shows)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Space&lt;/code&gt; / &lt;code&gt;m&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Expand / collapse selected task's notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toggle the archive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Search (in the archive)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Esc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Leave input / close view / clear selection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The fastest possible loop is &lt;code&gt;j&lt;/code&gt; &lt;code&gt;j&lt;/code&gt; &lt;code&gt;Enter&lt;/code&gt; to land on a task, then &lt;code&gt;Enter&lt;/code&gt; again to start typing what happened. Single-letter shortcuts are suppressed while you're typing, so notes never trigger commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  On a phone
&lt;/h3&gt;

&lt;p&gt;It's desktop-first, but it works on touch: the per-row actions (archive, rename, delete) are always visible instead of hiding behind hover, and it installs as a real PWA — proper maskable and Apple-touch icons, so the home-screen tile looks right on both Android and iOS, and it runs fully offline once installed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finishing and finding things
&lt;/h3&gt;

&lt;p&gt;Pressing &lt;code&gt;d&lt;/code&gt; (or clicking the &lt;code&gt;✓&lt;/code&gt; that appears on the row) archives a task. It leaves the board but keeps its entire note stack, browsable under &lt;strong&gt;Archive&lt;/strong&gt; and reopenable at any time. The archive has full-text search across both task titles and note contents — so months later, searching "headcount" surfaces the task where you wrote "travel numbers in, headcount still missing", even if the title never mentioned it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it's built
&lt;/h2&gt;

&lt;p&gt;A deliberately small stack, chosen so the whole app stays readable in one sitting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vite 8 + Svelte 5&lt;/strong&gt; (runes) &lt;strong&gt;+ TypeScript&lt;/strong&gt; — small bundle, minimal boilerplate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dexie 4&lt;/strong&gt; over IndexedDB — typed local persistence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;vite-plugin-pwa&lt;/strong&gt; — generated service worker and manifest; installable and fully offline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions → GitHub Pages&lt;/strong&gt; — every push to &lt;code&gt;main&lt;/code&gt; builds and publishes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The data model
&lt;/h3&gt;

&lt;p&gt;Three tables, all with UUIDs and timestamps:&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="nx"&gt;Task&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;archived&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;archivedAt&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="nx"&gt;sortOrder&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;Note&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createdAt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;AppState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;id&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;activeTaskId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// exactly one row&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important detail is the smallest one: &lt;strong&gt;"one active task" is a single pointer in &lt;code&gt;AppState&lt;/code&gt;, not a boolean flag on each task.&lt;/strong&gt; Two tasks being active simultaneously isn't a bug that has to be prevented — it's unrepresentable. Whole classes of state-sync bugs simply don't exist.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sortOrder&lt;/code&gt; is what makes switching bubble a task to the top: activating a task rewrites its order to just below the current minimum, and that reordering is persisted like everything else. That same persisted order is why undo can drop an un-archived task back into its exact former place.&lt;/p&gt;

&lt;h3&gt;
  
  
  State flow
&lt;/h3&gt;

&lt;p&gt;A single store class (&lt;code&gt;src/lib/store.svelte.ts&lt;/code&gt;) holds &lt;code&gt;$state&lt;/code&gt; runes, hydrated from IndexedDB once at startup. Every mutation writes to Dexie &lt;strong&gt;first&lt;/strong&gt;, then updates in-memory state, then broadcasts a one-line "changed" ping to any other open tabs. There is no memory-only state anywhere, so a refresh, a crash, or a closed laptop lid loses nothing. Empty titles and empty notes throw rather than silently no-op.&lt;/p&gt;

&lt;p&gt;The UI is a handful of small components — the board, a task row, the note form, and a help overlay — none of which own any persistent state of their own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local-first, on purpose
&lt;/h3&gt;

&lt;p&gt;Everything lives in your browser's IndexedDB. No account, no sync server, no telemetry, no network calls at all. On load the app calls &lt;code&gt;navigator.storage.persist()&lt;/code&gt; to ask the browser not to evict the board under storage pressure. That local-first choice has real consequences worth being honest about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's single-device.&lt;/strong&gt; Tabs in the same browser stay in sync, but a different machine is a different board. The data model was built sync-ready (UUIDs and timestamps everywhere), so a sync layer can be added later without remodeling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser storage still isn't a vault.&lt;/strong&gt; Persistent storage makes eviction far less likely, but clearing site data wipes it. A JSON export is the obvious next hardening step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a tool whose whole job is holding your working context, that trade is deliberate: zero setup, zero latency, and nothing to trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it goes next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Already shipped since the first cut:&lt;/strong&gt; five-second undo for archiving and note deletion; multi-tab consistency via &lt;code&gt;BroadcastChannel&lt;/code&gt;; persistent storage via &lt;code&gt;navigator.storage.persist()&lt;/code&gt;; a nameable workspace; and clean PWA installation on iOS and Android with maskable and Apple-touch icons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Still ahead&lt;/strong&gt;, roughly in priority order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;JSON export / import&lt;/strong&gt; — the rest of durability: a backup you own and can move.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync&lt;/strong&gt; — a self-hosted backend (PocketBase or CouchDB/PouchDB) the static frontend talks to, giving multi-&lt;em&gt;device&lt;/em&gt; use without handing task contents to a third party.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests&lt;/strong&gt; — the store's invariants (one active task, archiving clears the pointer, switching reorders, undo restores in place) are exactly the logic worth pinning down with Vitest and &lt;code&gt;fake-indexeddb&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;&lt;a href="https://alicommit-malp.github.io/wherewasi/" rel="noopener noreferrer"&gt;https://alicommit-malp.github.io/wherewasi/&lt;/a&gt; — it installs as a PWA from the browser menu and works with the network off. Add a couple of the things you're juggling right now, switch between them, and leave yourself a note on the way out. The payoff arrives the next time you come back.&lt;/p&gt;

</description>
      <category>productivity</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Ali Alp</dc:creator>
      <pubDate>Mon, 06 Jul 2026 20:37:33 +0000</pubDate>
      <link>https://dev.to/alialp/-4iic</link>
      <guid>https://dev.to/alialp/-4iic</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/alialp/why-event-driven-architecture-isnt-about-speed-and-when-you-actually-need-it-2pol" class="crayons-story__hidden-navigation-link"&gt;Why Event-Driven Architecture Isn’t About Speed (and When You Actually Need It)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/alialp" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F75246%2F73159765-d92c-4500-bf44-2ccb5ddca661.jpg" alt="alialp profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/alialp" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ali Alp
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ali Alp
                
              
              &lt;div id="story-author-preview-content-4082657" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/alialp" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F75246%2F73159765-d92c-4500-bf44-2ccb5ddca661.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ali Alp&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/alialp/why-event-driven-architecture-isnt-about-speed-and-when-you-actually-need-it-2pol" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 6&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/alialp/why-event-driven-architecture-isnt-about-speed-and-when-you-actually-need-it-2pol" id="article-link-4082657"&gt;
          Why Event-Driven Architecture Isn’t About Speed (and When You Actually Need It)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/microservices"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;microservices&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/systemdesign"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;systemdesign&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/alialp/why-event-driven-architecture-isnt-about-speed-and-when-you-actually-need-it-2pol" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;6&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/alialp/why-event-driven-architecture-isnt-about-speed-and-when-you-actually-need-it-2pol#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              12&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Why Event-Driven Architecture Isn’t About Speed (and When You Actually Need It)</title>
      <dc:creator>Ali Alp</dc:creator>
      <pubDate>Mon, 06 Jul 2026 20:37:16 +0000</pubDate>
      <link>https://dev.to/alialp/why-event-driven-architecture-isnt-about-speed-and-when-you-actually-need-it-2pol</link>
      <guid>https://dev.to/alialp/why-event-driven-architecture-isnt-about-speed-and-when-you-actually-need-it-2pol</guid>
      <description>&lt;p&gt;We’ve all seen it happen. A engineering team wants to feel like they belong in the "cool kids" club, so they grab Apache Kafka or a massive event broker, throw it at a straightforward CRUD application, and suddenly a simple database write is wrapped in a complex web of event producers, consumers, and brokers. &lt;/p&gt;

&lt;p&gt;Before you introduce that level of massive operational overhead into your stack, we need to talk about what Event-Driven Architecture (EDA) actually delivers—and what it charges you in return. &lt;/p&gt;

&lt;p&gt;I recently sat down for a panel discussion on the reality of building real-time enterprises. If you want to skip the hype and hear the unvarnished truth about architectural trade-offs from practicing architects, you can &lt;a href="https://www.youtube.com/watch?v=rQYKLfI_bnw" rel="noopener noreferrer"&gt;watch the full panel discussion on YouTube here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Here is the breakdown of the core architectural realities we discussed.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. "Real-Time" is a Spectrum, Not Pure Speed
&lt;/h2&gt;

&lt;p&gt;When stakeholders or product managers say they want "real-time data," they usually just mean they want their system to feel fast. But real-time isn't a single setting; it's a massive spectrum. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hard Real-Time:&lt;/strong&gt; Think medical instrumentation, radar tracking, or high-frequency trading systems. Milliseconds or microseconds dictate success, and missing a deadline means total system failure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soft Real-Time:&lt;/strong&gt; A Grafana metrics dashboard or an activity feed. If an update takes an extra two seconds to show up, the business survives.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the hard truth that catches many off guard: &lt;strong&gt;EDA does not give you more speed.&lt;/strong&gt; Every broker, event log, network hop, and serialization boundary you insert adds infrastructure complexity and transport latency. You don't adopt EDA for raw single-stream performance; you adopt it for &lt;strong&gt;decoupling&lt;/strong&gt;. If your system cannot tolerate eventual consistency, forcing an EDA into your core transaction loop is the wrong choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Overkill Trap: When to Actually Deploy EDA
&lt;/h2&gt;

&lt;p&gt;Architectural patterns should evolve organically based on hard constraints, not because you want to copy LinkedIn’s or Netflix's infrastructure blogs. Attempting to build a brand-new service around a heavy event broker on day one is the definition of over-engineering. &lt;/p&gt;

&lt;p&gt;So, when is it actually time to shift to an event-driven model? &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Litmus Test:&lt;/strong&gt; You need EDA the moment your system emits a single &lt;strong&gt;fact&lt;/strong&gt; (e.g., &lt;code&gt;OrderPlaced&lt;/code&gt;) that multiple, completely independent business domains need to natively consume and react to at the same time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If an order placement requires immediate, distinct actions from Shipping, Billing, Inventory management, and Fraud Detection—all handled by different microservices or separate teams—EDA rescues you from tight coupling and nasty orchestration bottlenecks. If it’s just one service talking directly to another, stick to a simple, synchronous API call or a lightweight point-to-point queue until the system forces you to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Conway’s Law Wins Every Time
&lt;/h2&gt;

&lt;p&gt;Software architecture is socio-technical. You can design the most elegant, fully decoupled event-driven ecosystem on a whiteboard, but if your organization is managed by a rigid, top-down hierarchy where every cross-team decision requires centralized synchronization and global locks, your technical architecture will rot.&lt;/p&gt;

&lt;p&gt;Before rewriting your stack to use events, look at your org chart:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do your individual teams have the actual operational autonomy to own micro-domains? &lt;/li&gt;
&lt;li&gt;Can your engineers shift their mental model from immediate, synchronous database states to handling asynchronous flows, retries, and out-of-order events? &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the organizational maturity isn't there to support decentralized data ownership, forcing EDA into the codebase will only create technical friction and team frustration.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Why AI and EDA Are Perfect Partners
&lt;/h2&gt;

&lt;p&gt;While human developers often struggle to reason through sprawling, asynchronous event loops, artificial intelligence thrives in them. &lt;/p&gt;

&lt;p&gt;Parallelism is baked into the very DNA of modern AI orchestration. If you are building multi-agent AI frameworks, those autonomous agents spend a massive amount of time executing parallel background tasks, waiting on inputs from collaborator agents, and reacting to changing context states. Because an AI ecosystem is inherently asynchronous and distributed, an event-driven backbone is one of the most effective ways to cleanly coordinate intelligent systems without blocking execution threads.&lt;/p&gt;




&lt;h3&gt;
  
  
  Cut Through the Noise
&lt;/h3&gt;

&lt;p&gt;Architecture is always a game of trade-offs. EDA gives you incredible flexibility, system resilience, and structural decoupling, but it taxes you heavily in complexity and latency. Make sure your business domain actually requires what it's selling before you sign the check.&lt;/p&gt;

&lt;p&gt;For the deeper, unfiltered debate on these concepts—including how data governance and organizational maturity factor into the mix—check out the full session here:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/rQYKLfI_bnw"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>devops</category>
      <category>microservices</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>How I built one Manifest V3 extension that runs on Chrome, Edge, and Firefox</title>
      <dc:creator>Ali Alp</dc:creator>
      <pubDate>Thu, 14 May 2026 10:54:12 +0000</pubDate>
      <link>https://dev.to/alialp/how-i-built-one-manifest-v3-extension-that-runs-on-chrome-edge-and-firefox-262m</link>
      <guid>https://dev.to/alialp/how-i-built-one-manifest-v3-extension-that-runs-on-chrome-edge-and-firefox-262m</guid>
      <description>&lt;p&gt;I built a browser extension called &lt;strong&gt;Silenzio&lt;/strong&gt; that blurs or blacks out videos and images, mutes audio, and lets me set rules about where and when it applies. The motivation is a bit personal: scrolling feeds, search results, and "people you may know" lists kept stealing minutes I didn't actually want to spend. A blurred feed turns out to be a surprisingly effective speed bump.&lt;/p&gt;

&lt;p&gt;The technically interesting part is that the whole thing — Chrome, Edge, and Firefox — runs from a single source folder. No build step, no per-browser variants, no Webpack. Here's how that works, plus a handful of MV3-specific tricks I had to find along the way.&lt;/p&gt;

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

&lt;p&gt;Two independent toggles — videos and images — each with Off / Blur / Blackout. Audio follows the video toggle. Three more knobs on top: an allowlist or blocklist of sites, per-site or global pause timers, and an optional "working hours" schedule so the whole thing only runs during a daily window you pick.&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%2Fnc9srrauc2i3siucy8ae.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%2Fnc9srrauc2i3siucy8ae.png" alt="YouTube with video thumbnails blurred and the Silenzio popup open" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One manifest, three engines
&lt;/h2&gt;

&lt;p&gt;Manifest V3 is technically the same across Chrome, Edge, and Firefox 115+. In practice you can write a single &lt;code&gt;manifest.json&lt;/code&gt; that all three load, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chromium engines ignore unknown manifest fields.&lt;/li&gt;
&lt;li&gt;The MV3 fields Silenzio actually uses (&lt;code&gt;content_scripts&lt;/code&gt;, &lt;code&gt;host_permissions&lt;/code&gt;, &lt;code&gt;action&lt;/code&gt;, &lt;code&gt;options_page&lt;/code&gt;, &lt;code&gt;permissions&lt;/code&gt;) are part of the standard that Firefox now implements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Firefox-specific block goes at the bottom and Chromium ignores it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"browser_specific_settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gecko"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"silenzio@silenzio.local"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict_min_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"115.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The same files load in three browsers with zero conditional code.&lt;/p&gt;

&lt;p&gt;The one gotcha you can't solve in the manifest is that Firefox MV3 treats &lt;code&gt;&amp;lt;all_urls&amp;gt;&lt;/code&gt; host permissions as &lt;strong&gt;user-granted post-install&lt;/strong&gt;. After temporary-loading the add-on, you have to click the toolbar icon → puzzle-piece menu → Silenzio → "Always allow on all websites" once. Until then, the content script won't actually inject. Easy to miss the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catching short-form feeds
&lt;/h2&gt;

&lt;p&gt;The hard case for a media-filtering extension is short-form video — Shorts, Reels, the LinkedIn feed. Those surfaces aggressively mount and unmount &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; elements as you scroll. A naive &lt;code&gt;document.querySelectorAll("video")&lt;/code&gt; at load time finds nothing useful, because most of the videos haven't been mounted yet.&lt;/p&gt;

&lt;p&gt;The fix is a &lt;code&gt;MutationObserver&lt;/code&gt; rooted at &lt;code&gt;document.documentElement&lt;/code&gt;, started at &lt;code&gt;document_start&lt;/code&gt;:&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;observer&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;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;mutations&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;eff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;effectiveModes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for &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;m&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &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;node&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addedNodes&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodeType&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nx"&gt;SELECTOR&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nf"&gt;applyTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nx"&gt;SELECTOR&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;el&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;applyTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eff&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="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;childList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;subtree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the manifest sets &lt;code&gt;"run_at": "document_start"&lt;/code&gt;, the observer is in place before the body even exists. Newly-mounted videos and images get the filter class within a tick of being inserted.&lt;/p&gt;

&lt;p&gt;There's a small but important detail in the order: I start the observer &lt;strong&gt;before&lt;/strong&gt; loading the saved config from &lt;code&gt;chrome.storage.local&lt;/code&gt;. Storage is asynchronous, and the round trip easily takes long enough for an autoplay video to mount and start playing. So the observer runs with defaults immediately, then re-applies once storage resolves. The visible result is that pages never flash an unblurred frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  One CSS path for videos and images
&lt;/h2&gt;

&lt;p&gt;The CSS for the blur and blackout is dumb on purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.silenzio-blur&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;40px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.silenzio-blackout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;!important&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;&lt;code&gt;filter&lt;/code&gt; is an element-level property. It applies to whatever the box renders — video frames, image content, even async-loaded image bytes that arrive after the class is set. That means &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; share exactly one apply path. The element-type dispatch happens once, in &lt;code&gt;modeFor&lt;/code&gt;:&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;function&lt;/span&gt; &lt;span class="nf"&gt;modeFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eff&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;el&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HTMLVideoElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;eff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;video&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;el&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HTMLImageElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;eff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&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;el&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HTMLMediaElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;eff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;audio&amp;gt; follows video&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;off&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding new media types later is a two-line change in this function and one selector update. The CSS doesn't need to know anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Re-muting hostile sites
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;el.muted = true&lt;/code&gt; is fine 90% of the time. The 10% case is sites that programmatically unmute on a player event, or swap the &lt;code&gt;src&lt;/code&gt; on the same element when you scroll to the next short. So I attach two capture-phase listeners on &lt;code&gt;document&lt;/code&gt;:&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="nb"&gt;document&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="s2"&gt;volumechange&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;effectiveModes&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;off&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;target&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HTMLMediaElement&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;muted&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="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;muted&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;document&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="s2"&gt;play&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="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;target&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HTMLMediaElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;applyTo&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;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;effectiveModes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Capture phase matters here. By the time &lt;code&gt;volumechange&lt;/code&gt; bubbles to the document the site's own handler has already run; capture-phase intercepts before. This is enough to keep YouTube, LinkedIn, and Reels muted reliably.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision flow
&lt;/h2&gt;

&lt;p&gt;Every time the extension applies a mode it goes through &lt;code&gt;effectiveModes()&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scope&lt;/strong&gt; — if the current hostname doesn't match the user's allowlist or blocklist, both modes return &lt;code&gt;off&lt;/code&gt;. Match is exact-or-suffix, so adding &lt;code&gt;youtube.com&lt;/code&gt; covers &lt;code&gt;www.youtube.com&lt;/code&gt; and &lt;code&gt;m.youtube.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pause&lt;/strong&gt; — if there's a global or site-specific pause active right now, both modes return &lt;code&gt;off&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule&lt;/strong&gt; — if working hours are enabled and the current time is outside the window, both modes return &lt;code&gt;off&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Otherwise return the user's configured &lt;code&gt;{ video, image }&lt;/code&gt; modes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the only place precedence is decided, and the order matters: I want a one-click pause to override the user's other rules without needing to think about them.&lt;/p&gt;

&lt;p&gt;Time-based state is re-checked via a single &lt;code&gt;setTimeout&lt;/code&gt; per page that targets the next relevant boundary — the exact pause expiry, or the next minute boundary if a schedule is active. Doing exact schedule-edge math with days-of-week and midnight crossings is fussy; minute granularity is more than enough for a "working hours" feature and avoids a category of bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I didn't (yet) solve
&lt;/h2&gt;

&lt;p&gt;Three known gaps, all on the someday list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CSS &lt;code&gt;background-image&lt;/code&gt;&lt;/strong&gt; — many avatars and decorative banners are painted via &lt;code&gt;background-image&lt;/code&gt; rather than &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;. Filtering them requires walking computed styles and either injecting per-element CSS or patching stylesheets. I skipped it for v1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;&lt;/strong&gt; — mostly icons, rarely worth blurring. (&lt;code&gt;&amp;lt;img src="*.svg"&amp;gt;&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; filtered because the selector catches &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Audio API graphs&lt;/strong&gt; — &lt;code&gt;HTMLMediaElement.muted&lt;/code&gt; doesn't stop a Web Audio graph that pulls samples directly. Ordinary playback on YouTube, LinkedIn, etc. uses &lt;code&gt;HTMLMediaElement&lt;/code&gt; and is muted correctly; some games and audio editors aren't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each is solvable, just not at v1 scope.&lt;/p&gt;

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

&lt;p&gt;Silenzio is on the &lt;a href="https://chromewebstore.google.com/detail/pnlbpemienlnbiofmdjldbbnpfkfpndd" rel="noopener noreferrer"&gt;Chrome Web Store&lt;/a&gt; — works in Edge too. The source, including the Firefox load instructions, lives at &lt;a href="https://github.com/alicommit-malp/silenzio" rel="noopener noreferrer"&gt;github.com/alicommit-malp/silenzio&lt;/a&gt;. No build step — &lt;code&gt;Load unpacked&lt;/code&gt; and go.&lt;/p&gt;

&lt;p&gt;If you've built a cross-browser MV3 extension and have a cleaner answer for Firefox's host-permission UX, the Web Audio mute, or background-image filtering, I'd love to hear it.&lt;/p&gt;

</description>
      <category>webextensions</category>
      <category>javascript</category>
      <category>chrome</category>
      <category>firefox</category>
    </item>
    <item>
      <title>Did you ever wondered to see your whole life in one page calendar ?</title>
      <dc:creator>Ali Alp</dc:creator>
      <pubDate>Thu, 07 May 2026 20:36:38 +0000</pubDate>
      <link>https://dev.to/alialp/did-you-ever-wondered-to-see-your-whole-life-in-one-page-calendar--ecc</link>
      <guid>https://dev.to/alialp/did-you-ever-wondered-to-see-your-whole-life-in-one-page-calendar--ecc</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/alialp/i-built-a-life-in-weeks-poster-generator-in-one-html-file-40hp" class="crayons-story__hidden-navigation-link"&gt;I built a 'life in weeks' poster generator in one HTML file&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/alialp" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F75246%2F73159765-d92c-4500-bf44-2ccb5ddca661.jpg" alt="alialp profile" class="crayons-avatar__image" width="732" height="976"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/alialp" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ali Alp
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ali Alp
                
              
              &lt;div id="story-author-preview-content-3629078" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/alialp" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F75246%2F73159765-d92c-4500-bf44-2ccb5ddca661.jpg" class="crayons-avatar__image" alt="" width="732" height="976"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ali Alp&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/alialp/i-built-a-life-in-weeks-poster-generator-in-one-html-file-40hp" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 7&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/alialp/i-built-a-life-in-weeks-poster-generator-in-one-html-file-40hp" id="article-link-3629078"&gt;
          I built a 'life in weeks' poster generator in one HTML file
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/productivity"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;productivity&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/life"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;life&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/alialp/i-built-a-life-in-weeks-poster-generator-in-one-html-file-40hp" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/alialp/i-built-a-life-in-weeks-poster-generator-in-one-html-file-40hp#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>I built a 'life in weeks' poster generator in one HTML file</title>
      <dc:creator>Ali Alp</dc:creator>
      <pubDate>Thu, 07 May 2026 20:35:24 +0000</pubDate>
      <link>https://dev.to/alialp/i-built-a-life-in-weeks-poster-generator-in-one-html-file-40hp</link>
      <guid>https://dev.to/alialp/i-built-a-life-in-weeks-poster-generator-in-one-html-file-40hp</guid>
      <description>&lt;p&gt;I made a tool that draws every week of your life on a single sheet of paper.&lt;/p&gt;

&lt;p&gt;A long human life is roughly 5,200 weeks. That's a 100-row × 52-column grid — small enough to fit on one A4 page, big enough that you instinctively try to count them. The tool fills in the weeks you've already lived, leaves the rest empty, and exports a PDF you can print.&lt;/p&gt;

&lt;p&gt;Live: &lt;a href="https://alicommit-malp.github.io/life-in-weeks/" rel="noopener noreferrer"&gt;https://alicommit-malp.github.io/life-in-weeks/&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://github.com/alicommit-malp/life-in-weeks" rel="noopener noreferrer"&gt;https://github.com/alicommit-malp/life-in-weeks&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's also a quiet experiment in restraint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One file (&lt;code&gt;index.html&lt;/code&gt; — HTML, CSS, JS, all of it)&lt;/li&gt;
&lt;li&gt;No framework, no bundler, no &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Two CDN deps: jsPDF and Google Fonts&lt;/li&gt;
&lt;li&gt;No backend, no analytics, no &lt;code&gt;localStorage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;$0 hosting, $0 forever&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is about the design decisions. There's just enough code to show what's interesting; the rest is on GitHub.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why one file?
&lt;/h2&gt;

&lt;p&gt;Because the surface area is small. The whole product is "type your name and birthdate, click two buttons." A build step buys nothing — and "no build step" buys something real: anyone can clone the repo, double-click &lt;code&gt;index.html&lt;/code&gt;, and run it offline. No &lt;code&gt;npm install&lt;/code&gt;, no Node version mismatch, no broken Vite plugin two years from now.&lt;/p&gt;

&lt;p&gt;This wasn't dogma. I genuinely tried to imagine a v2 that needed React. I couldn't justify it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Drawing 5,200 circles fast
&lt;/h2&gt;

&lt;p&gt;The naive approach is &lt;code&gt;&amp;lt;circle&amp;gt;&lt;/code&gt; × 5,200. It works, but that's a lot of DOM nodes.&lt;/p&gt;

&lt;p&gt;The trick: &lt;strong&gt;two &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt; elements&lt;/strong&gt;, one for filled circles and one for empty ones. Build the path data as a single string of moveto + arc commands.&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;function&lt;/span&gt; &lt;span class="nf"&gt;circlePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`M&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; m&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,0 a&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 0 1,0 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,0 a&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 0 1,0 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,0 `&lt;/span&gt;&lt;span class="p"&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;filledD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emptyD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for &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;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;YEARS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="o"&gt;++&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;cy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gridTop&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ROW_H&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &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;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;WEEKS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="o"&gt;++&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;cx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;leftX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;CELL&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;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;WEEKS&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;w&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;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;totalWeeks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;filledD&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;circlePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CIRCLE_R&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;emptyD&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;circlePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CIRCLE_R&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;The &lt;code&gt;M cx,cy m -r,0 a r,r 0 1,0 2r,0 a r,r 0 1,0 -2r,0&lt;/code&gt; pattern draws a circle as two half-arcs from a moveto point. Subsequent &lt;code&gt;M&lt;/code&gt; commands inside the same &lt;code&gt;d&lt;/code&gt; attribute start a new disconnected subpath, so one &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt; element ends up containing 5,000+ circles.&lt;/p&gt;

&lt;p&gt;Two DOM nodes instead of 5,200. Render is instant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the PDF honest
&lt;/h2&gt;

&lt;p&gt;The on-screen preview is SVG. The download is a real PDF, generated by &lt;strong&gt;jsPDF&lt;/strong&gt; from &lt;code&gt;circle()&lt;/code&gt;, &lt;code&gt;line()&lt;/code&gt;, and &lt;code&gt;text()&lt;/code&gt; primitives. They have to look identical — if the preview lies, the user feels cheated the moment they hit "Download."&lt;/p&gt;

&lt;p&gt;The fix is shared coordinate math. jsPDF in &lt;code&gt;pt&lt;/code&gt; units uses A4 = 595.27 × 841.89, top-left origin, y growing down — exactly like SVG. So both renderers consume the same constants:&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;PAGE_W&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;595.27&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;PAGE_H&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;841.89&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;MM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.83465&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 mm in pt&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;computeLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;years&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;cellWMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PAGE_W&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;LEFT_M&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;RIGHT_M&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;52&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;cellHMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PAGE_H&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;TOP_M&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;BOTTOM_M&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;years&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;cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cellWMax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cellHMax&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ... gridW, gridH, leftX, gridTop, circleR, font sizes, tick step&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both &lt;code&gt;renderSVG()&lt;/code&gt; and &lt;code&gt;renderPDF()&lt;/code&gt; call &lt;code&gt;computeLayout(years)&lt;/code&gt; and then walk identical loops. Change a margin in one place, both renderers update. No divergence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The layout is height-constrained — and that's a feature
&lt;/h2&gt;

&lt;p&gt;100 rows × 52 columns of &lt;em&gt;square&lt;/em&gt; circles on a single A4 page means cell size is capped by &lt;strong&gt;page height&lt;/strong&gt;, not width. The grid ends up about 135 mm wide — there are ~24 mm of empty margin on the left and right. People keep wanting to "fix" this.&lt;/p&gt;

&lt;p&gt;Earlier iterations tried:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two-page A4&lt;/strong&gt; — wider circles, but you have to tape the pages together&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Landscape A4&lt;/strong&gt; — counterintuitively gives &lt;em&gt;smaller&lt;/em&gt; circles, because height becomes the new constraint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stretched ovals&lt;/strong&gt; — looks bad&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The empty side margins are correct. They're the price of the proportions. I kept them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding pets
&lt;/h2&gt;

&lt;p&gt;Recently I added a Human / Dog / Cat selector. Dogs get 20 rows × 52 columns; cats, 25. For shorter spans, the cell size becomes width-constrained instead, so circles get bigger, and the tick axis switches from a 10-year step to a 5-year step:&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;SPECIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;human&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;years&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;e.g. Ali&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;years&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;e.g. Spot&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;years&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;e.g. Mochi&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="c1"&gt;// inside computeLayout:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tickStep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;years&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because all the layout state went through one function, adding species was a small diff plus a CSS segmented control. I didn't have to touch the rendering loops at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going against the SaaS look
&lt;/h2&gt;

&lt;p&gt;Most "free tool" landing pages use the same recipe: Inter, gradient backgrounds, rounded cards with shadows, a Lucide icon next to every heading. Safe and forgettable.&lt;/p&gt;

&lt;p&gt;I went the other way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fraunces&lt;/strong&gt; for the display serif (italic accent on &lt;em&gt;in weeks&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JetBrains Mono&lt;/strong&gt; for everything else&lt;/li&gt;
&lt;li&gt;Warm off-white paper background with a faint radial-gradient grain&lt;/li&gt;
&lt;li&gt;One emphasis color — terracotta — used sparingly&lt;/li&gt;
&lt;li&gt;A magazine-style masthead bar at the top&lt;/li&gt;
&lt;li&gt;Inputs are typewriter-style: serif text, single underline, no boxed fields&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It looks more like a printed thing than a webapp. Which is the point — the output &lt;em&gt;is&lt;/em&gt; a printed thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy is the simple kind
&lt;/h2&gt;

&lt;p&gt;There's no backend, no &lt;code&gt;fetch&lt;/code&gt; to any server I control, no analytics, no cookies, no &lt;code&gt;localStorage&lt;/code&gt;. Your name and birthdate never leave the browser tab.&lt;/p&gt;

&lt;p&gt;The only network requests are Google Fonts and jsPDF from cdnjs. Neither receives the form data.&lt;/p&gt;

&lt;p&gt;I didn't need a privacy policy because there's nothing to disclose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;GitHub Pages serves the file. The fonts are free. jsPDF is MIT. Total monthly cost: &lt;strong&gt;$0&lt;/strong&gt;. I expect it to keep being.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Live: &lt;a href="https://alicommit-malp.github.io/life-in-weeks/" rel="noopener noreferrer"&gt;https://alicommit-malp.github.io/life-in-weeks/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/alicommit-malp/life-in-weeks" rel="noopener noreferrer"&gt;https://github.com/alicommit-malp/life-in-weeks&lt;/a&gt; (MIT)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fork it, print it, gift it.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>javascript</category>
      <category>life</category>
    </item>
    <item>
      <title>Introducing Tunnel Whisperer: Surgical Connectivity for Networks That Say "No"</title>
      <dc:creator>Ali Alp</dc:creator>
      <pubDate>Fri, 06 Mar 2026 15:54:27 +0000</pubDate>
      <link>https://dev.to/alialp/introducing-tunnel-whisperer-surgical-connectivity-for-networks-that-say-no-4hoe</link>
      <guid>https://dev.to/alialp/introducing-tunnel-whisperer-surgical-connectivity-for-networks-that-say-no-4hoe</guid>
      <description>&lt;p&gt;&lt;em&gt;Your MRI scanner needs to talk to a cloud AI platform. Your vendor needs to reach a single maintenance port on a factory-floor PLC. Your data scientist needs to query an on-premise database from a cloud notebook. The firewall says no to all of them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tunnel Whisperer says yes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Tunnel-Whisperer/Tunnel-Whisperer" rel="noopener noreferrer"&gt;Github page&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Enterprise networks are getting stricter. Firewalls block everything except port 443. Deep Packet Inspection kills anything that doesn't look like genuine HTTPS. Legacy devices — hospital scanners, industrial controllers, aging servers — can't run modern VPN clients. And even if you could install one, IT won't open a single inbound port.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;Connectivity Gap&lt;/strong&gt;: the space between what your applications need and what your network allows.&lt;/p&gt;

&lt;p&gt;Traditional solutions don't fit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VPNs&lt;/strong&gt; (WireGuard, Tailscale) require UDP ports or custom protocols — blocked by DPI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reverse proxies&lt;/strong&gt; (ngrok) expose services to the public internet — a non-starter for healthcare and manufacturing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSH tunnels&lt;/strong&gt; get flagged and dropped by next-gen firewalls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built Tunnel Whisperer to close this gap.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Tunnel Whisperer?
&lt;/h2&gt;

&lt;p&gt;Tunnel Whisperer is an open-source tool that creates &lt;strong&gt;resilient, port-to-port bridges&lt;/strong&gt; across separated private networks. It wraps your TCP traffic inside a genuine TLS-encrypted HTTPS stream using Xray's VLESS+XHTTP protocol. To the network, it looks exactly like someone browsing a website.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client Network             Public Cloud               Server Network
+--------------+        +------------------+        +--------------+
|  tw connect  |--443--&amp;gt;|     Relay VM     |&amp;lt;--443--|   tw serve   |
|              |  HTTPS |  Caddy + Xray    |  HTTPS |              |
| local ports  |        |  (reverse proxy) |        | SSH server   |
| :5432 :3389  |        |  Firewall: 80+443|        | port fwd     |
+--------------+        +------------------+        +--------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your PostgreSQL client connects to &lt;code&gt;localhost:5432&lt;/code&gt;. Your RDP client connects to &lt;code&gt;localhost:3389&lt;/code&gt;. The traffic flows through an HTTPS tunnel to the server network, where it reaches the actual services. The applications on both ends have no idea a tunnel exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why We Built It
&lt;/h2&gt;

&lt;p&gt;We kept running into the same scenario: a customer with a legitimate connectivity need, a network that blocks everything, and no tool that threads the needle between "too broad" (VPN) and "too public" (ngrok).&lt;/p&gt;

&lt;p&gt;Tunnel Whisperer is designed around three principles:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Surgical access, not broad connectivity.&lt;/strong&gt; Each user gets access to exactly the ports they need — nothing more. No host-to-host networking, no route tables, no exposure of adjacent services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Invisible to the network.&lt;/strong&gt; The traffic is indistinguishable from regular HTTPS. No special ports, no unusual protocols, no signatures for DPI to flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Zero trust at the relay.&lt;/strong&gt; The relay VM is a dumb pipe. It stores no SSH keys, no passwords, no application data. If it gets compromised, the attacker gets nothing useful — all plaintext remains encrypted by the SSH layer inside the tunnel.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works: Three Layers of Encryption
&lt;/h2&gt;

&lt;p&gt;Tunnel Whisperer stacks three encryption layers, each serving a different purpose:&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;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;TLS 1.3&lt;/strong&gt; (outer)&lt;/td&gt;
&lt;td&gt;Makes the traffic look like HTTPS. Caddy handles automatic Let's Encrypt certificates on the relay.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Xray VLESS + XHTTP&lt;/strong&gt; (middle)&lt;/td&gt;
&lt;td&gt;Routes users by UUID through the relay. XHTTP splits data into standard HTTP requests for resilience against connection timeouts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;SSH Ed25519&lt;/strong&gt; (inner)&lt;/td&gt;
&lt;td&gt;End-to-end encryption between client and server. Public-key only — no passwords, no brute-force surface. Per-user &lt;code&gt;permitopen&lt;/code&gt; restrictions lock each client to specific ports.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The relay terminates TLS but never sees the SSH-encrypted payload. Even if someone owns the relay, they can't read your data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Healthcare: DICOM Through a Firewall
&lt;/h3&gt;

&lt;p&gt;A hospital's MRI scanner needs to send images to a cloud AI platform for analysis. The scanner speaks DICOM on port 104 and can't install any software. Tunnel Whisperer runs on a gateway machine on the scanner's LAN, forwarding port 104 through the HTTPS tunnel to the cloud. The scanner sends to &lt;code&gt;localhost:104&lt;/code&gt; as if the AI platform were next door.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manufacturing: Vendor Access to a Single Port
&lt;/h3&gt;

&lt;p&gt;A machine vendor needs remote access to a PLC's maintenance port for diagnostics. Instead of a VPN that exposes the entire factory network, Tunnel Whisperer gives them access to exactly one port on one device. When the job is done, revoke the key — access gone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Science: Cloud Notebook to On-Premise Database
&lt;/h3&gt;

&lt;p&gt;A data scientist running Jupyter in the cloud needs to query a PostgreSQL database behind a corporate firewall. With Tunnel Whisperer, they connect to &lt;code&gt;localhost:5432&lt;/code&gt; and run queries as if the database were local. No VPN client, no firewall exceptions, no IT tickets.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started in 5 Minutes
&lt;/h2&gt;

&lt;p&gt;Tunnel Whisperer ships as a single binary for Linux, Windows, and macOS. Build from source with Go 1.25+:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make build          &lt;span class="c"&gt;# builds bin/tw&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Server Side
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tw dashboard                &lt;span class="c"&gt;# open the web UI&lt;/span&gt;
tw create relay-server      &lt;span class="c"&gt;# 8-step wizard: provision a relay VM&lt;/span&gt;
tw create user              &lt;span class="c"&gt;# 5-step wizard: create a client with port restrictions&lt;/span&gt;
tw serve                    &lt;span class="c"&gt;# start the server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relay provisioning wizard handles everything: spinning up a VM on Hetzner, DigitalOcean, or AWS, configuring Caddy for TLS, installing Xray, and locking down the firewall to ports 80 and 443 only.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client Side
&lt;/h3&gt;

&lt;p&gt;The server admin sends you a config bundle (a zip file with your config and SSH keys). Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tw dashboard                &lt;span class="c"&gt;# drag-and-drop the zip file&lt;/span&gt;
tw connect                  &lt;span class="c"&gt;# you're in&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Your configured ports are now available on localhost with automatic reconnection if the connection drops.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Web Dashboard
&lt;/h2&gt;

&lt;p&gt;Both server and client modes come with a browser-based dashboard for managing everything visually:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server dashboard&lt;/strong&gt; — create users, provision relays, monitor connected clients, stream logs in real-time, and open an interactive SSH terminal to the relay — all from the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client dashboard&lt;/strong&gt; — upload config bundles, connect/disconnect with one click, and see your active tunnels with bandwidth stats.&lt;/p&gt;

&lt;p&gt;No separate web app to deploy. The dashboard is embedded in the same &lt;code&gt;tw&lt;/code&gt; binary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Per-User Access Control
&lt;/h2&gt;

&lt;p&gt;Every user gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;unique Xray UUID&lt;/strong&gt; for relay routing&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;Ed25519 key pair&lt;/strong&gt; for SSH authentication&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;&lt;code&gt;permitopen&lt;/code&gt; directive&lt;/strong&gt; restricting them to specific ports
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# authorized_keys (auto-generated)
permitopen="127.0.0.1:5432",permitopen="127.0.0.1:3389" ssh-ed25519 AAAA... alice
permitopen="127.0.0.1:104" ssh-ed25519 AAAA... vendor-dicom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alice can reach PostgreSQL and RDP. The vendor can reach the DICOM port. Neither can reach anything else. Revoking access is instant — remove the key from &lt;code&gt;authorized_keys&lt;/code&gt; and the UUID from the relay config. No server restart needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Built for Resilience
&lt;/h2&gt;

&lt;p&gt;Connections drop. Networks hiccup. Tunnel Whisperer handles it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-reconnection&lt;/strong&gt; with exponential backoff (2s up to 30s max)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSH keepalive&lt;/strong&gt; every 15 seconds to detect dead connections early&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;XHTTP transport&lt;/strong&gt; splits data into HTTP requests, surviving connection resets that would kill WebSocket-based tunnels&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System service mode&lt;/strong&gt; (&lt;code&gt;tw service install&lt;/code&gt;) for auto-start on boot — Linux systemd, Windows SCM, or macOS launchd&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;We won't pretend there's zero overhead. Two relay hops and three encryption layers have a cost. Here are real numbers from our benchmarks (relay on Hetzner, ~30ms RTT):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Direct SSH&lt;/th&gt;
&lt;th&gt;Tunnel Whisperer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Throughput&lt;/strong&gt; (100 MB transfer)&lt;/td&gt;
&lt;td&gt;112 MB/s&lt;/td&gt;
&lt;td&gt;21 MB/s (~168 Mbps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Database TPS&lt;/strong&gt; (pgbench, 50 clients)&lt;/td&gt;
&lt;td&gt;~1,790&lt;/td&gt;
&lt;td&gt;~80-95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~28 ms&lt;/td&gt;
&lt;td&gt;~530-630 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tunnel Whisperer excels at &lt;strong&gt;bulk transfers, streaming, and interactive sessions&lt;/strong&gt; where human-speed interaction or continuous data flow hides the latency. For high-frequency RPC workloads with many small round-trips, you'll want to use connection pooling and place the relay geographically close to both endpoints.&lt;/p&gt;

&lt;p&gt;The tradeoff is clear: you lose raw speed, but you gain connectivity where no other tool can get through.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Compares
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Tunnel Whisperer&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;VPNs&lt;/strong&gt; (Tailscale/WireGuard)&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;Reverse Proxies&lt;/strong&gt; (ngrok)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Surgical (port-to-port)&lt;/td&gt;
&lt;td&gt;Broad (host-to-host)&lt;/td&gt;
&lt;td&gt;Public (port-to-web)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DPI Resistance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (genuine HTTPS)&lt;/td&gt;
&lt;td&gt;Low (custom protocols)&lt;/td&gt;
&lt;td&gt;Medium (standard HTTPS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gateway/sidecar&lt;/td&gt;
&lt;td&gt;Agent on every host&lt;/td&gt;
&lt;td&gt;Dev/test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Self-hosted&lt;/td&gt;
&lt;td&gt;SaaS/hybrid&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Relay Compromise&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No credential exposure&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Provider-dependent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;Tunnel Whisperer is currently in &lt;strong&gt;alpha&lt;/strong&gt;. The core tunnel, user management, dashboard, and multi-cloud relay provisioning are all working. Here's where we're heading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UDP support&lt;/strong&gt; for protocols that need it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-relay&lt;/strong&gt; topologies for geographic distribution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt; — OpenTelemetry-format logging is already in progress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-built binaries&lt;/strong&gt; and package manager distribution&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Tunnel Whisperer is MIT-licensed and open source.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Tunnel-Whisperer/Tunnel-Whisperer" rel="noopener noreferrer"&gt;github.com/Tunnel-Whisperer/Tunnel-Whisperer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation:&lt;/strong&gt; &lt;a href="https://tunnel-whisperer.github.io/Tunnel-Whisperer" rel="noopener noreferrer"&gt;tunnel-whisperer.github.io/Tunnel-Whisperer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video Tutorial:&lt;/strong&gt; &lt;a href="https://www.youtube.com/watch?v=cIe0-C1IMe4" rel="noopener noreferrer"&gt;Watch the walkthrough on YouTube&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've ever stared at a firewall rule that blocks everything and thought "there has to be a better way" — give Tunnel Whisperer a try. We'd love your feedback, bug reports, and contributions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tunnel Whisperer — because sometimes the only way through is to whisper.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>A practical, governance-first framework for turning your CI/CD pipeline into a secure, reproducible, and audit-defensible supply chain control point.</title>
      <dc:creator>Ali Alp</dc:creator>
      <pubDate>Fri, 13 Feb 2026 13:56:25 +0000</pubDate>
      <link>https://dev.to/alialp/a-practical-governance-first-framework-for-turning-your-cicd-pipeline-into-a-secure-4eaa</link>
      <guid>https://dev.to/alialp/a-practical-governance-first-framework-for-turning-your-cicd-pipeline-into-a-secure-4eaa</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/alialp" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F75246%2F73159765-d92c-4500-bf44-2ccb5ddca661.jpg" alt="alialp"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/alialp/scripted-ci-governing-your-build-pipeline-as-critical-infrastructure-5gaf" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;SCRIPTED CI: Governing Your Build Pipeline as Critical Infrastructure&lt;/h2&gt;
      &lt;h3&gt;Ali Alp ・ Feb 13&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#cicd&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#security&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#productivity&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>devops</category>
      <category>cicd</category>
      <category>security</category>
      <category>productivity</category>
    </item>
    <item>
      <title>SCRIPTED CI: Governing Your Build Pipeline as Critical Infrastructure</title>
      <dc:creator>Ali Alp</dc:creator>
      <pubDate>Fri, 13 Feb 2026 13:50:52 +0000</pubDate>
      <link>https://dev.to/alialp/scripted-ci-governing-your-build-pipeline-as-critical-infrastructure-5gaf</link>
      <guid>https://dev.to/alialp/scripted-ci-governing-your-build-pipeline-as-critical-infrastructure-5gaf</guid>
      <description>&lt;p&gt;CI/CD pipelines are amazing.&lt;/p&gt;

&lt;p&gt;They build, test, package, sign, and ship our software in minutes. They automate what used to take days. They make modern development possible.&lt;/p&gt;

&lt;p&gt;They also sit at one of the most dangerous control points in your entire system.&lt;/p&gt;

&lt;p&gt;If you build regulated, safety-critical, or security-sensitive software, your CI pipeline is not “just automation.” It executes code, holds secrets, produces artifacts, and pushes to production.&lt;/p&gt;

&lt;p&gt;That makes it part of your product’s trust boundary.&lt;/p&gt;

&lt;p&gt;So the real question isn’t:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is our application secure?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is our build system defensible?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s where &lt;strong&gt;SCRIPTED CI&lt;/strong&gt; comes in.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why CI Is a Supply Chain Control Point
&lt;/h2&gt;

&lt;p&gt;Most teams spend their security energy on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application security testing&lt;/li&gt;
&lt;li&gt;API authentication&lt;/li&gt;
&lt;li&gt;Infrastructure hardening&lt;/li&gt;
&lt;li&gt;Runtime monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All important.&lt;/p&gt;

&lt;p&gt;But CI pipelines quietly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execute third-party code (GitHub Actions, plugins, integrations)&lt;/li&gt;
&lt;li&gt;Access privileged credentials (cloud roles, signing keys, tokens)&lt;/li&gt;
&lt;li&gt;Produce signed release artifacts&lt;/li&gt;
&lt;li&gt;Modify repository state&lt;/li&gt;
&lt;li&gt;Pull dependencies dynamically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If an attacker compromises your CI, they don’t need to break your runtime.&lt;/p&gt;

&lt;p&gt;They can modify your build.&lt;/p&gt;

&lt;p&gt;And if your build is compromised, your product is compromised.&lt;/p&gt;

&lt;p&gt;That’s a supply chain problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The SCRIPTED Model
&lt;/h2&gt;

&lt;p&gt;SCRIPTED is a governance-first way to think about CI security.&lt;br&gt;
It turns abstract “best practices” into enforceable controls.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Letter&lt;/th&gt;
&lt;th&gt;Principle&lt;/th&gt;
&lt;th&gt;What It Means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Secure Secrets&lt;/td&gt;
&lt;td&gt;Minimize and scope credentials in CI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Control Execution&lt;/td&gt;
&lt;td&gt;Only run approved, immutable code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Repeatable Builds&lt;/td&gt;
&lt;td&gt;Make builds deterministic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;I&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Isolated Runtime&lt;/td&gt;
&lt;td&gt;Harden where builds execute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Policy Enforcement&lt;/td&gt;
&lt;td&gt;Automate guardrails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;T&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Traceability&lt;/td&gt;
&lt;td&gt;Make artifacts verifiable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Evidence&lt;/td&gt;
&lt;td&gt;Be able to prove your controls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;D&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Defensible Design&lt;/td&gt;
&lt;td&gt;Encode security in the system, not the process&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let’s walk through it.&lt;/p&gt;


&lt;h2&gt;
  
  
  S — Secure Secrets
&lt;/h2&gt;

&lt;p&gt;CI pipelines often accumulate powerful credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GITHUB_TOKEN&lt;/code&gt; with write access&lt;/li&gt;
&lt;li&gt;Cloud deployment roles&lt;/li&gt;
&lt;li&gt;Container registry credentials&lt;/li&gt;
&lt;li&gt;Code signing certificates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default mistake? Over-permissioned tokens.&lt;/p&gt;

&lt;p&gt;Instead of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write-all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other guardrails:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don’t expose secrets to fork-based PR workflows&lt;/li&gt;
&lt;li&gt;Use short-lived identities (OIDC) instead of static cloud keys&lt;/li&gt;
&lt;li&gt;Separate build credentials from deployment credentials&lt;/li&gt;
&lt;li&gt;Audit token usage regularly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a job doesn’t need a permission, it shouldn’t have it.&lt;/p&gt;

&lt;p&gt;Simple. Structural. Defensible.&lt;/p&gt;




&lt;h2&gt;
  
  
  C — Control Execution
&lt;/h2&gt;

&lt;p&gt;CI runs code. Sometimes that code is yours. Sometimes it’s not.&lt;/p&gt;

&lt;p&gt;Marketplace actions and plugins are convenient. They’re also external code entering your trust boundary.&lt;/p&gt;

&lt;p&gt;This is unsafe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? Because tags can move.&lt;/p&gt;

&lt;p&gt;This is safer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pin to a commit SHA.&lt;/p&gt;

&lt;p&gt;That makes the reference immutable.&lt;/p&gt;

&lt;p&gt;Stronger moves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mirror critical actions into your own org&lt;/li&gt;
&lt;li&gt;Vendor high-risk actions locally&lt;/li&gt;
&lt;li&gt;Maintain an allowlist of approved actions&lt;/li&gt;
&lt;li&gt;Automatically fail builds that reference unpinned actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Immutable references eliminate silent drift.&lt;/p&gt;




&lt;h2&gt;
  
  
  R — Repeatable Builds
&lt;/h2&gt;

&lt;p&gt;If you rebuild a release from two years ago, do you get the same binary?&lt;/p&gt;

&lt;p&gt;If not, you don’t have reproducibility.&lt;/p&gt;

&lt;p&gt;And without reproducibility, you don’t have defensibility.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node@sha256:...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enforce lockfiles (&lt;code&gt;go.sum&lt;/code&gt;, &lt;code&gt;package-lock.json&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;Pin toolchain versions&lt;/li&gt;
&lt;li&gt;Eliminate dynamic dependency resolution&lt;/li&gt;
&lt;li&gt;Avoid runtime internet downloads during build&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deterministic inputs must produce deterministic outputs.&lt;/p&gt;

&lt;p&gt;Anything else is guesswork.&lt;/p&gt;




&lt;h2&gt;
  
  
  I — Isolated Runtime
&lt;/h2&gt;

&lt;p&gt;Where your CI runs matters.&lt;/p&gt;

&lt;p&gt;Public runners are convenient, but they are shared infrastructure.&lt;/p&gt;

&lt;p&gt;For higher-assurance systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use self-hosted runners&lt;/li&gt;
&lt;li&gt;Make them ephemeral (destroy after each job)&lt;/li&gt;
&lt;li&gt;Restrict outbound network access&lt;/li&gt;
&lt;li&gt;Segregate runners by trust boundary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat CI runners as production infrastructure.&lt;/p&gt;

&lt;p&gt;Because they are.&lt;/p&gt;




&lt;h2&gt;
  
  
  P — Policy Enforcement
&lt;/h2&gt;

&lt;p&gt;Here’s a hard truth:&lt;/p&gt;

&lt;p&gt;Guidelines are not controls.&lt;/p&gt;

&lt;p&gt;A control that does not automatically fail is not a control.&lt;/p&gt;

&lt;p&gt;If you say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“All actions must be SHA pinned.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then enforce it.&lt;/p&gt;

&lt;p&gt;Add a validation step that fails when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An action is not pinned&lt;/li&gt;
&lt;li&gt;Permissions are too broad&lt;/li&gt;
&lt;li&gt;An unapproved marketplace action is referenced&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combine this with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Branch protection&lt;/li&gt;
&lt;li&gt;Required status checks&lt;/li&gt;
&lt;li&gt;Policy-as-Code (OPA, Conftest, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security must be encoded into the pipeline itself.&lt;/p&gt;

&lt;p&gt;Otherwise, it will erode.&lt;/p&gt;




&lt;h2&gt;
  
  
  T — Traceability
&lt;/h2&gt;

&lt;p&gt;For every artifact deployed to production, you should be able to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What source commit built this?&lt;/li&gt;
&lt;li&gt;What dependencies were included?&lt;/li&gt;
&lt;li&gt;What toolchain was used?&lt;/li&gt;
&lt;li&gt;Has it been modified since creation?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can’t answer those questions, incident response becomes speculation.&lt;/p&gt;

&lt;p&gt;To fix that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate an SBOM for every build&lt;/li&gt;
&lt;li&gt;Produce cryptographic provenance (SLSA-style attestations)&lt;/li&gt;
&lt;li&gt;Sign artifacts (Sigstore/Cosign)&lt;/li&gt;
&lt;li&gt;Keep immutable build logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns builds from opaque processes into verifiable records.&lt;/p&gt;




&lt;h2&gt;
  
  
  E — Evidence
&lt;/h2&gt;

&lt;p&gt;Security that cannot be demonstrated does not exist.&lt;/p&gt;

&lt;p&gt;In regulated environments, you will be asked to show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How you control third-party execution&lt;/li&gt;
&lt;li&gt;How you manage credentials&lt;/li&gt;
&lt;li&gt;How you ensure reproducibility&lt;/li&gt;
&lt;li&gt;How you maintain artifact lineage&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Architecture diagrams&lt;/li&gt;
&lt;li&gt;Policy definitions&lt;/li&gt;
&lt;li&gt;SBOM archives&lt;/li&gt;
&lt;li&gt;Signature records&lt;/li&gt;
&lt;li&gt;Audit logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Evidence is not paperwork.&lt;br&gt;
It’s proof of structural control.&lt;/p&gt;




&lt;h2&gt;
  
  
  D — Defensible Design
&lt;/h2&gt;

&lt;p&gt;This is the outcome.&lt;/p&gt;

&lt;p&gt;Not “we follow best practices.”&lt;/p&gt;

&lt;p&gt;Not “we trust our team.”&lt;/p&gt;

&lt;p&gt;Not “it should be fine.”&lt;/p&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Controls are enforced technically&lt;/li&gt;
&lt;li&gt;Third-party execution is bounded&lt;/li&gt;
&lt;li&gt;Privileges are minimized&lt;/li&gt;
&lt;li&gt;Builds are deterministic&lt;/li&gt;
&lt;li&gt;Artifacts are verifiable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You replace process-based assurances with structural guarantees.&lt;/p&gt;

&lt;p&gt;That’s defensibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phased Adoption (Don’t Boil the Ocean)
&lt;/h2&gt;

&lt;p&gt;You don’t need to do everything at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1 — Baseline Risk Reduction (Weeks 1–4)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pin all actions to SHAs&lt;/li&gt;
&lt;li&gt;Reduce token permissions&lt;/li&gt;
&lt;li&gt;Enable branch protection&lt;/li&gt;
&lt;li&gt;Add basic workflow linting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Low effort. Immediate risk reduction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2 — Structural Governance (Months 1–3)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Mirror critical actions internally&lt;/li&gt;
&lt;li&gt;Introduce “Golden Workflows”&lt;/li&gt;
&lt;li&gt;Enforce Policy-as-Code validation&lt;/li&gt;
&lt;li&gt;Deploy ephemeral runners&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Medium effort. Major risk reduction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 3 — Regulatory-Grade Assurance (Months 3–6)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Hermetic builds (no external network dependency)&lt;/li&gt;
&lt;li&gt;SBOM for every artifact&lt;/li&gt;
&lt;li&gt;Signed provenance attestations&lt;/li&gt;
&lt;li&gt;Hardware-backed signing for releases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;High effort. Audit-ready posture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;CI pipelines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execute external code&lt;/li&gt;
&lt;li&gt;Hold powerful secrets&lt;/li&gt;
&lt;li&gt;Produce signed artifacts&lt;/li&gt;
&lt;li&gt;Sit between source and production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are part of your threat model.&lt;/p&gt;

&lt;p&gt;In modern systems, the integrity of your product is inseparable from the integrity of its build process.&lt;/p&gt;

&lt;p&gt;SCRIPTED CI isn’t about paranoia.&lt;/p&gt;

&lt;p&gt;It’s about recognizing that the build is no longer a convenience layer.&lt;/p&gt;

&lt;p&gt;It’s infrastructure.&lt;/p&gt;

&lt;p&gt;And infrastructure must be governed.&lt;/p&gt;




&lt;p&gt;If you’re operating in regulated environments, or just want to level up your supply chain security, I’d be curious:&lt;/p&gt;

&lt;p&gt;Which part of SCRIPTED would be hardest to implement in your organization?&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>security</category>
      <category>productivity</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Ali Alp</dc:creator>
      <pubDate>Mon, 12 Jan 2026 17:39:36 +0000</pubDate>
      <link>https://dev.to/alialp/-14b5</link>
      <guid>https://dev.to/alialp/-14b5</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/alialp" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F75246%2F73159765-d92c-4500-bf44-2ccb5ddca661.jpg" alt="alialp"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/alialp/a-technical-documentation-to-ultimate-freedom-in-life-5cn7" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;A technical documentation to ultimate freedom in life&lt;/h2&gt;
      &lt;h3&gt;Ali Alp ・ Jan 12&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#freedom&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>freedom</category>
    </item>
    <item>
      <title>A technical documentation to ultimate freedom in life</title>
      <dc:creator>Ali Alp</dc:creator>
      <pubDate>Mon, 12 Jan 2026 17:38:05 +0000</pubDate>
      <link>https://dev.to/alialp/a-technical-documentation-to-ultimate-freedom-in-life-5cn7</link>
      <guid>https://dev.to/alialp/a-technical-documentation-to-ultimate-freedom-in-life-5cn7</guid>
      <description>&lt;h2&gt;
  
  
  This is a default human behavior in C
&lt;/h2&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/alicommit-malp" rel="noopener noreferrer"&gt;
        alicommit-malp
      &lt;/a&gt; / &lt;a href="https://github.com/alicommit-malp/freedom" rel="noopener noreferrer"&gt;
        freedom
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A technical documentation  to freedom in life for software developers :)
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;freedom&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A technical documentation  to freedom in life for software developers :)&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;This is a default human behavior in C&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-c notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;#include&lt;/span&gt; &lt;span class="pl-s"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;#include&lt;/span&gt; &lt;span class="pl-s"&gt;&amp;lt;stdbool.h&amp;gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;#include&lt;/span&gt; &lt;span class="pl-s"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;#include&lt;/span&gt; &lt;span class="pl-s"&gt;&amp;lt;time.h&amp;gt;&lt;/span&gt;

&lt;span class="pl-smi"&gt;int&lt;/span&gt; &lt;span class="pl-en"&gt;main&lt;/span&gt;(&lt;span class="pl-smi"&gt;void&lt;/span&gt;)
{
    &lt;span class="pl-smi"&gt;bool&lt;/span&gt; &lt;span class="pl-s1"&gt;anger_event&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; false;
    
    &lt;span class="pl-en"&gt;srand&lt;/span&gt;(&lt;span class="pl-en"&gt;time&lt;/span&gt;(&lt;span class="pl-c1"&gt;NULL&lt;/span&gt;));
    &lt;span class="pl-s1"&gt;anger_event&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;rand&lt;/span&gt;() % &lt;span class="pl-c1"&gt;2&lt;/span&gt; &lt;span class="pl-c1"&gt;==&lt;/span&gt; &lt;span class="pl-c1"&gt;0&lt;/span&gt;;

    &lt;span class="pl-k"&gt;if&lt;/span&gt; (&lt;span class="pl-s1"&gt;anger_event&lt;/span&gt;) {
        &lt;span class="pl-en"&gt;printf&lt;/span&gt;(&lt;span class="pl-s"&gt;"Show anger.\n"&lt;/span&gt;);
    } &lt;span class="pl-k"&gt;else&lt;/span&gt; {
        &lt;span class="pl-en"&gt;printf&lt;/span&gt;(&lt;span class="pl-s"&gt;"All is calm.\n"&lt;/span&gt;);
    }

    &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-c1"&gt;0&lt;/span&gt;;
}&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;This is the upgraded human behavior after doing meditation&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;While practicing meditation, let thoughts enter but decide if you agree to process them, this will create the gap (freedom) over time.&lt;/p&gt;
&lt;div class="highlight highlight-source-c notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;#include&lt;/span&gt; &lt;span class="pl-s"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;#include&lt;/span&gt; &lt;span class="pl-s"&gt;&amp;lt;stdbool.h&amp;gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;#include&lt;/span&gt; &lt;span class="pl-s"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;#include&lt;/span&gt; &lt;span class="pl-s"&gt;&amp;lt;time.h&amp;gt;&lt;/span&gt;
&lt;span class="pl-smi"&gt;int&lt;/span&gt; &lt;span class="pl-en"&gt;main&lt;/span&gt;(&lt;span class="pl-smi"&gt;void&lt;/span&gt;)
{
    &lt;span class="pl-smi"&gt;bool&lt;/span&gt; &lt;span class="pl-s1"&gt;anger_event&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; false;
    
    &lt;span class="pl-en"&gt;srand&lt;/span&gt;(&lt;span class="pl-en"&gt;time&lt;/span&gt;(&lt;span class="pl-c1"&gt;NULL&lt;/span&gt;));
    &lt;span class="pl-s1"&gt;anger_event&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;rand&lt;/span&gt;() % &lt;span class="pl-c1"&gt;2&lt;/span&gt; &lt;span class="pl-c1"&gt;==&lt;/span&gt; &lt;span class="pl-c1"&gt;0&lt;/span&gt;;

    &lt;span class="pl-smi"&gt;bool&lt;/span&gt; &lt;span class="pl-s1"&gt;conscious_choice&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; !&lt;span class="pl-s1"&gt;anger_event&lt;/span&gt;; &lt;span class="pl-c"&gt;// ← this&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/alicommit-malp/freedom" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;br&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdbool.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;time.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;anger_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;srand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;anger_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&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="n"&gt;anger_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Show anger.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"All is calm.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&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;return&lt;/span&gt; &lt;span class="mi"&gt;0&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;
  
  
  This is the upgraded human behavior after doing meditation
&lt;/h2&gt;

&lt;p&gt;While practicing meditation, let thoughts enter but decide if you agree to process them, this will create the gap (freedom) over time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdbool.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;time.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;anger_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;srand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;anger_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;conscious_choice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;anger_event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ← this is freedom&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anger_event&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;conscious_choice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Show anger.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"All is calm.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&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;return&lt;/span&gt; &lt;span class="mi"&gt;0&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;
  
  
  Diff
&lt;/h2&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%2Fy1jonaisovak0mfz2oa7.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%2Fy1jonaisovak0mfz2oa7.png" alt="Freedom added to default human behavior"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Worth sharing ?
&lt;/h2&gt;

</description>
      <category>freedom</category>
    </item>
    <item>
      <title>When Events Meet Clusters: Building Reactive Micro-services on Kubernetes</title>
      <dc:creator>Ali Alp</dc:creator>
      <pubDate>Wed, 10 Dec 2025 21:29:32 +0000</pubDate>
      <link>https://dev.to/alialp/when-events-meet-clusters-building-reactive-micro-services-on-kubernetes-433e</link>
      <guid>https://dev.to/alialp/when-events-meet-clusters-building-reactive-micro-services-on-kubernetes-433e</guid>
      <description>&lt;p&gt;Modern users expect digital systems to feel alive. We touch, we swipe, we click—and we expect something to happen instantly. Yet many micro-services architectures still behave like a slow bureaucracy: they wait, they poll, they block, and under pressure they break. This article explores a simple but powerful idea:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Events can transform ordinary micro-services into reactive, self-organizing systems that scale and recover like living organisms.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This idea matters because most failures in distributed systems arise not from bad code, but from bad &lt;em&gt;coordination&lt;/em&gt;. Services depend on each other too tightly. Scaling decisions arrive too late. Recovery mechanisms rely on brittle manual steps. And when demand surges suddenly, teams find themselves firefighting instead of innovating.&lt;/p&gt;

&lt;p&gt;To understand how events solve this, we start with a question.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Why do micro-services still wait?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Think of a food-delivery app during a sudden rainstorm. Orders jump tenfold in a minute. Kitchens fill. Drivers vanish. The back-end is overwhelmed. Requests start queuing. API timeouts cascade. Users refresh endlessly. Engineers scramble.&lt;/p&gt;

&lt;p&gt;But the underlying problem is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional micro-services react to stress &lt;em&gt;after&lt;/em&gt; it hurts.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We rely on metrics like CPU, retries, and liveness probes—signals that come &lt;em&gt;after&lt;/em&gt; something has already gone wrong. In biological terms, it’s like touching something hot and waiting for your brain to calculate the temperature before deciding to pull your hand away.&lt;/p&gt;

&lt;p&gt;Systems that wait get burned.&lt;/p&gt;

&lt;p&gt;So what would a system look like if it reacted instantly—before bottlenecks or failures reached the user?&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Reactive micro-services: systems that respond, not poll&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To explain reactive architecture, consider a train station:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Polling architecture:&lt;/strong&gt; You walk to the platform every 30 seconds and ask, &lt;em&gt;“Did the train arrive yet?”&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event-driven architecture:&lt;/strong&gt; A loudspeaker announces, &lt;em&gt;“Train arriving on platform 4.”&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reactive micro-services don’t keep checking. They listen. They respond. They scale when something actually happens. They recover by replaying what they missed.&lt;/p&gt;

&lt;p&gt;To build such behavior, we combine three technologies—each playing a different role in the metaphor of a living system.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Kafka: the nervous system’s signal carrier&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Kafka is the &lt;strong&gt;backbone of the event-driven organism&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It records every event in durable storage.&lt;/li&gt;
&lt;li&gt;It broadcasts signals to any service that needs them.&lt;/li&gt;
&lt;li&gt;It supports replay, allowing a service to rebuild state after failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Kubernetes is the &lt;em&gt;body&lt;/em&gt;, Kafka is the &lt;strong&gt;spinal cord&lt;/strong&gt;, reliably delivering every neural signal down the line.&lt;/p&gt;

&lt;p&gt;When a service dies, Kafka simply replays the events. When a new service appears, it can reconstruct exactly what happened before it joined. This behavior is essential for systems that heal themselves.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Knative: the reflex engine&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If Kafka is the nervous system, Knative provides the &lt;strong&gt;reflex arc&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Touch something hot → your hand pulls back before your brain consciously processes the danger.&lt;/p&gt;

&lt;p&gt;Knative Eventing works the same way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It watches Kafka topics.&lt;/li&gt;
&lt;li&gt;When an event arrives, Knative instantly activates the exact workload needed.&lt;/li&gt;
&lt;li&gt;It scales consumers up under load and down to zero when idle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This enables an infrastructure that responds &lt;em&gt;proportionally&lt;/em&gt; to real-world events.&lt;/p&gt;

&lt;p&gt;For example, a sudden spike in “OrderCreated” events results in instantaneous consumer scaling—not 60 seconds later, not after CPU hits 80%, but exactly when the load originates.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Kubernetes: the muscle and regeneration system&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Kubernetes is the body’s musculature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It runs containers reliably.&lt;/li&gt;
&lt;li&gt;It heals failed pods.&lt;/li&gt;
&lt;li&gt;It provides auto-scaling and stable infrastructure.&lt;/li&gt;
&lt;li&gt;It maintains the cluster’s general health.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kubernetes alone is not reactive—it lacks event understanding. But when paired with Kafka and Knative, it becomes the execution layer for a reactive organism.&lt;/p&gt;

&lt;p&gt;Together, they form this dynamic:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kafka senses.&lt;br&gt;
Knative reacts.&lt;br&gt;
Kubernetes adapts and stabilizes.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Patterns for building reactive micro-services&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Event Choreography&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Imagine a parcel moving through a logistics system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Order placed&lt;/li&gt;
&lt;li&gt;Payment confirmed&lt;/li&gt;
&lt;li&gt;Package packed&lt;/li&gt;
&lt;li&gt;Out for delivery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each step reacts to the previous event. No central controller. No chain of API calls. Just &lt;strong&gt;events that trigger reactions&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Event Sourcing&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Consider a bank account. Your balance is not stored; it is &lt;em&gt;computed&lt;/em&gt; by summing all transactions. Event sourcing uses Kafka to store every change.&lt;/p&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Perfect audit history&lt;/li&gt;
&lt;li&gt;Ability to rebuild state anytime&lt;/li&gt;
&lt;li&gt;Natural resilience to failure&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. CQRS with Kafka Streams&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Commands update state. Queries read from a fast, materialized view.&lt;/p&gt;

&lt;p&gt;This gives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;smooth scalability&lt;/li&gt;
&lt;li&gt;predictable performance&lt;/li&gt;
&lt;li&gt;clear separation of responsibilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kafka Streams keeps the views up to date in real time.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Why events reduce failures&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Most system failures originate from coupling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A slow service slows everything&lt;/li&gt;
&lt;li&gt;A failing service breaks everything&lt;/li&gt;
&lt;li&gt;A scaling service overloads everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Events cut these chains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failures become local instead of global.&lt;/strong&gt;&lt;br&gt;
A bad consumer does not impact producers. A slow processor does not block others. If a consumer crashes, Kafka simply replays events until it recovers.&lt;/p&gt;

&lt;p&gt;This is how living systems avoid dying from one malfunctioning cell.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Observability as the organism’s senses&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Observability in reactive architectures is not about dashboards—it’s about understanding motion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kafka lag = congestion on a highway&lt;/li&gt;
&lt;li&gt;Distributed tracing = route visualization&lt;/li&gt;
&lt;li&gt;Knative auto-scaling logs = heartbeat signals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to &lt;strong&gt;see the system as an organism&lt;/strong&gt;, reacting to stimuli and adapting continuously.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;What organizations gain&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Teams adopting this architecture see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;massive reductions in over-provisioning&lt;/li&gt;
&lt;li&gt;better stability under unpredictable workloads&lt;/li&gt;
&lt;li&gt;fewer cascading failures&lt;/li&gt;
&lt;li&gt;simpler understanding of system behavior&lt;/li&gt;
&lt;li&gt;improved developer autonomy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A reactive system frees engineers to build features rather than fight fires.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The idea worth sharing&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;At its core, this architecture re-frames how we think about distributed systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reactive micro-services aren’t faster machines—they are better listeners.&lt;/strong&gt;&lt;br&gt;
They don’t wait. They don’t poll. They don’t rely on rigid chains of synchronous calls.&lt;/p&gt;

&lt;p&gt;Instead, they respond to the world, recover from damage, scale when needed, and rest when idle.&lt;/p&gt;

&lt;p&gt;This is the shift:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From systems that must be controlled&lt;br&gt;
to systems that can self-organize.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And when events meet clusters, that shift becomes possible.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>microservices</category>
      <category>productivity</category>
      <category>kafka</category>
    </item>
  </channel>
</rss>
