<?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>HTTP QUERY: Everything a Developer Needs to Know About HTTP's Newest Method</title>
      <dc:creator>Ali Alp</dc:creator>
      <pubDate>Sun, 16 Aug 2026 15:17:59 +0000</pubDate>
      <link>https://dev.to/alialp/http-query-everything-a-developer-needs-to-know-about-https-newest-method-1hkc</link>
      <guid>https://dev.to/alialp/http-query-everything-a-developer-needs-to-know-about-https-newest-method-1hkc</guid>
      <description>&lt;p&gt;For as long as most of us have been writing APIs, there has been one awkward question with no good answer: where do I put a complex search query?&lt;/p&gt;

&lt;p&gt;In June 2026 the IETF finally answered it. RFC 10008 defines a new HTTP method, &lt;code&gt;QUERY&lt;/code&gt;: safe and idempotent like &lt;code&gt;GET&lt;/code&gt;, cacheable like &lt;code&gt;GET&lt;/code&gt;, but with a request body like &lt;code&gt;POST&lt;/code&gt;. It's the first new general-purpose method HTTP has gained in about twenty years.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: GET vs POST was always a bad trade
&lt;/h2&gt;

&lt;p&gt;Say you're building a product search endpoint. You have two classic options, and both are compromises.&lt;/p&gt;

&lt;p&gt;The first is &lt;code&gt;GET&lt;/code&gt; with a query string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/products?category=books&amp;amp;maxPrice=30&amp;amp;sort=rating&amp;amp;fields=title,price&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get safety, idempotency, caching, bookmarks. You also get practical URL length limits; servers and proxies commonly cap URLs somewhere between 2 KB and 8 KB, which starts to hurt once your filter UI grows or you need to pass a long list of IDs. The whole query also lands in access logs and browser history, bad news if it contains personal data. And no, you can't just put a body on the GET instead: the spec gives a GET body no defined semantics, so servers and intermediaries do unpredictable things with it.&lt;/p&gt;

&lt;p&gt;The second option is &lt;code&gt;POST&lt;/code&gt; with a body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/products/search&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"books"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"maxPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rating"&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;Now there are no size limits and nothing sensitive in the URL. But POST is neither safe nor idempotent by definition. Caches have to assume it changes state, so responses can't be reused. A client that hits a network failure can't retry automatically, because it has no way to know whether that's safe. And the message itself no longer says "this is just a read".&lt;/p&gt;

&lt;p&gt;Every REST API that grew a &lt;code&gt;/search&lt;/code&gt; POST endpoint made this trade. QUERY exists so you don't have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet QUERY
&lt;/h2&gt;

&lt;p&gt;From RFC 10008, the QUERY method:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"requests that the request target process the enclosed content in a safe and idempotent manner and then respond with the result of that processing."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unpacked: &lt;em&gt;safe&lt;/em&gt; means the client neither requests nor expects any state change on the target resource. It's a read. &lt;em&gt;Idempotent&lt;/em&gt; means it can be repeated or retried, say after a connection failure, with no concern about partial state changes. &lt;em&gt;Cacheable&lt;/em&gt; means a cache MAY use the response to satisfy later equivalent QUERY requests. And unlike GET, the query itself travels in the request body, in whatever media type the resource supports.&lt;/p&gt;

&lt;p&gt;Here is a minimal exchange (adapted from the RFC's own example):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;QUERY&lt;/span&gt; &lt;span class="nn"&gt;/contacts&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example.org&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/x-www-form-urlencoded&lt;/span&gt;
&lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;

select=surname,givenname,email&amp;amp;limit=10&amp;amp;match="email=*@example.*"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&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;"surname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"givenname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"smith@example.org"&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;span class="nl"&gt;"surname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jones"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"givenname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sally"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sally.jones@example.com"&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;span class="nl"&gt;"surname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dubois"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"givenname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Camille"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"camille.dubois@example.net"&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;The body isn't limited to form encoding. The RFC's examples use &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;, JSONPath, SQL and even XSLT as query languages. The media type defines what the query &lt;em&gt;means&lt;/em&gt;; the resource defines what it's evaluated against.&lt;/p&gt;

&lt;h2&gt;
  
  
  GET vs QUERY vs POST at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;GET&lt;/th&gt;
&lt;th&gt;QUERY&lt;/th&gt;
&lt;th&gt;POST&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Safe&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;potentially no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idempotent&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;potentially no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request body&lt;/td&gt;
&lt;td&gt;no defined semantics&lt;/td&gt;
&lt;td&gt;expected&lt;/td&gt;
&lt;td&gt;expected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cacheable response&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;only for future GET/HEAD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic retry OK&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URI for the query itself&lt;/td&gt;
&lt;td&gt;yes, by definition&lt;/td&gt;
&lt;td&gt;optional (&lt;code&gt;Location&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URI for the result&lt;/td&gt;
&lt;td&gt;optional (&lt;code&gt;Content-Location&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;optional (&lt;code&gt;Content-Location&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;optional (&lt;code&gt;Content-Location&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The rules of the road
&lt;/h2&gt;

&lt;p&gt;A few things the RFC is strict about.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Content-Type&lt;/code&gt; is mandatory. Servers &lt;em&gt;must&lt;/em&gt; fail a QUERY request whose &lt;code&gt;Content-Type&lt;/code&gt; is missing or inconsistent with the actual content. No content sniffing.&lt;/p&gt;

&lt;p&gt;Error signaling is well defined:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Missing media type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;400 Bad Request&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Media type not supported for queries&lt;/td&gt;
&lt;td&gt;&lt;code&gt;415 Unsupported Media Type&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content doesn't match its declared type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;400 Bad Request&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query parses but is semantically invalid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;422 Unprocessable Content&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server can't produce a response type the client accepts&lt;/td&gt;
&lt;td&gt;&lt;code&gt;406 Not Acceptable&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server doesn't support QUERY at all&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;405 Method Not Allowed&lt;/code&gt; (with an &lt;code&gt;Allow&lt;/code&gt; header)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And the URI's query string still exists. &lt;code&gt;QUERY /contacts?active=true&lt;/code&gt; is legal; the query component still participates in identifying the resource, while the body carries the query &lt;em&gt;content&lt;/em&gt;. How the two combine is up to the resource.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discovery: &lt;code&gt;Allow&lt;/code&gt; and the new &lt;code&gt;Accept-Query&lt;/code&gt; header
&lt;/h2&gt;

&lt;p&gt;RFC 10008 also registers a new response header, &lt;code&gt;Accept-Query&lt;/code&gt;, which advertises the media types a resource accepts for QUERY bodies. It uses Structured Fields syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;HEAD&lt;/span&gt; &lt;span class="nn"&gt;/contacts&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example.org&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Accept-Query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/x-www-form-urlencoded, "application/sql"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two more discovery paths. Send &lt;code&gt;OPTIONS /contacts&lt;/code&gt; and look for QUERY in the &lt;code&gt;Allow&lt;/code&gt; response header. Or just try it: an unsupported method gets you a 405 with &lt;code&gt;Allow&lt;/code&gt;, and an unsupported query format gets you a 415, ideally with &lt;code&gt;Accept-Query&lt;/code&gt; telling you what would have worked.&lt;/p&gt;

&lt;p&gt;One subtlety: &lt;code&gt;Accept-Query&lt;/code&gt; applies to every URI on the server that shares the same &lt;em&gt;path&lt;/em&gt;. The URI's query component is ignored for its scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching: the killer feature
&lt;/h2&gt;

&lt;p&gt;This is the part that makes QUERY an actual improvement over tunneling reads through POST.&lt;/p&gt;

&lt;p&gt;Because QUERY is safe, its responses are cacheable. Unlike GET, though, the URL alone isn't enough to identify a response, so the RFC requires:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The cache key for a QUERY request &lt;strong&gt;must incorporate the request content&lt;/strong&gt; and related metadata (like &lt;code&gt;Content-Type&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words: same URL plus same query body (plus the relevant headers) equals a cache hit. A CDN or shared proxy can serve your repeated dashboard query without ever touching the origin, which was structurally impossible with POST-based search.&lt;/p&gt;

&lt;p&gt;Caches are also allowed to normalize the request content when building the key, for example by stripping content encodings or applying known conventions of the format (JSON's insignificant whitespace, say), so trivially different bodies can still hit the same cache entry. Normalization only ever affects the cache key; the request itself is left untouched. The RFC warns caches to be conservative here, because bad normalization means wrong responses served. Clients can send &lt;code&gt;Cache-Control: no-transform&lt;/code&gt; to discourage it, though the directive is advisory.&lt;/p&gt;

&lt;p&gt;The practical takeaway for API authors: emit the same caching headers on QUERY responses that you would on GET (&lt;code&gt;Cache-Control&lt;/code&gt;, &lt;code&gt;ETag&lt;/code&gt;, &lt;code&gt;Last-Modified&lt;/code&gt;, &lt;code&gt;Vary&lt;/code&gt;) and you get HTTP caching on complex searches for free as caches roll out support.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;Location&lt;/code&gt;, &lt;code&gt;Content-Location&lt;/code&gt;, and the "equivalent resource"
&lt;/h2&gt;

&lt;p&gt;The RFC introduces a concept called the &lt;strong&gt;equivalent resource&lt;/strong&gt;: the hypothetical resource that would answer a GET with the results of this exact query. Servers may give that resource a real URI, and two response headers let them tell you about it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Location&lt;/code&gt; on a 2xx response means: you can GET this URI later to re-run the query without resending the body. Think saved queries. &lt;code&gt;Content-Location&lt;/code&gt; means: you can GET this URI to retrieve this specific result snapshot, which may be temporary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/contacts/stored-queries/42          (re-runs the query)&lt;/span&gt;
&lt;span class="na"&gt;Content-Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/contacts/stored-results/17  (this specific result set)&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...results...&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;A server can even respond &lt;code&gt;303 See Other&lt;/code&gt; with a &lt;code&gt;Location&lt;/code&gt;, meaning the answer to your query is available as a plain GET over there. That's a useful pattern for expensive queries: QUERY once, then poll or share a cheap, cacheable GET URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conditional requests work too
&lt;/h2&gt;

&lt;p&gt;Because QUERY is a read, the whole conditional-request machinery from RFC 9110 applies. Send &lt;code&gt;If-None-Match&lt;/code&gt; or &lt;code&gt;If-Modified-Since&lt;/code&gt; with a repeated QUERY and the server can answer &lt;code&gt;304 Not Modified&lt;/code&gt;, skipping both the query execution and the response body. For heavy analytical queries over slowly changing data, that's a big deal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redirects
&lt;/h2&gt;

&lt;p&gt;Worth knowing because this differs from POST's legacy behavior. On 301, 308, 302 and 307, the client resends the QUERY, body included, to the new URI; the old browser habit of demoting redirected POSTs to GETs explicitly does not apply. On &lt;code&gt;303 See Other&lt;/code&gt;, the client performs a GET on the &lt;code&gt;Location&lt;/code&gt; URI instead.&lt;/p&gt;

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

&lt;p&gt;QUERY is just an HTTP method token, and most HTTP stacks can carry it already. What varies is framework sugar and intermediary awareness.&lt;/p&gt;

&lt;h3&gt;
  
  
  curl
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; QUERY https://api.example.org/products &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{ "category": "books", "maxPrice": 30 }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JavaScript (fetch)
&lt;/h3&gt;

&lt;p&gt;The Fetch Standard forbids only &lt;code&gt;CONNECT&lt;/code&gt;, &lt;code&gt;TRACE&lt;/code&gt;, and &lt;code&gt;TRACK&lt;/code&gt;, so QUERY is allowed:&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;QUERY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// uppercase: only the classic six methods are auto-normalized&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;books&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&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;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two caveats. Cross-origin QUERY always triggers a CORS preflight, since it's not a safelisted method. And browser and runtime support has been landing gradually through 2026, so verify in your targets before shipping.&lt;/p&gt;

&lt;h3&gt;
  
  
  .NET 10 / ASP.NET Core 10
&lt;/h3&gt;

&lt;p&gt;.NET 10 shipped support on both sides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// client&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpRequestMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"https://api.example.org/products"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ProductFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"books"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MaxPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// server (minimal APIs; use MapMethods, there's no MapQuery yet)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapMethods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;HttpMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ICatalog&lt;/span&gt; &lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProductFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SearchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filter&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;h3&gt;
  
  
  Node.js / Express
&lt;/h3&gt;

&lt;p&gt;Routing frameworks generally expose custom methods once the underlying HTTP parser accepts them. Check your Node version's method allowlist, then route with something like:&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/products&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;QUERY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// req body = the query&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Graceful fallback
&lt;/h3&gt;

&lt;p&gt;During the transition, attempt QUERY and fall back to your existing POST search endpoint when you get a &lt;code&gt;405 Method Not Allowed&lt;/code&gt; or a blocked request. Both can share the same handler server-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas and sharp edges
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;WAFs, load balancers and API gateways with method allowlists may reject or mangle QUERY. Audit your edge before rolling it out.&lt;/li&gt;
&lt;li&gt;The semantics allow shared caches and CDNs to cache QUERY, but the products need body-aware cache keys first. Treat CDN-level QUERY caching as a roadmap item for now.&lt;/li&gt;
&lt;li&gt;Every cross-origin browser use pays a CORS preflight. Cache it with &lt;code&gt;Access-Control-Max-Age&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;OpenAPI can't describe QUERY endpoints yet; ASP.NET Core 10, for instance, simply omits them from generated specs. Document them manually for now.&lt;/li&gt;
&lt;li&gt;Moving query parameters out of URLs is a privacy win, since less leaks into logs and history. But if your server mints &lt;code&gt;Location&lt;/code&gt; or &lt;code&gt;Content-Location&lt;/code&gt; URIs for queries, the RFC says those URIs SHOULD NOT embed sensitive parts of the query content. Don't undo the win.&lt;/li&gt;
&lt;li&gt;Idempotence is a promise you make. Marking an endpoint QUERY tells every client and intermediary "retry me freely, cache me". If the handler secretly writes state, you'll get all the classic broken-cache and double-execution bugs. Reads only.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to reach for QUERY
&lt;/h2&gt;

&lt;p&gt;Use it when a read doesn't fit comfortably in a URL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;complex filter and search endpoints (the &lt;code&gt;/search&lt;/code&gt; POST you already have)&lt;/li&gt;
&lt;li&gt;queries written in a real query language (JSONPath, SQL subsets, GraphQL documents)&lt;/li&gt;
&lt;li&gt;lookups with large ID lists ("give me these 5,000 SKUs")&lt;/li&gt;
&lt;li&gt;reads whose parameters are sensitive and shouldn't live in URLs and logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep plain &lt;code&gt;GET&lt;/code&gt; for anything that fits in a URL; bookmarks and universal caching are still unbeatable. Keep &lt;code&gt;POST&lt;/code&gt; for what it always meant: requests that change state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;QUERY closes a twenty-year-old gap in HTTP's vocabulary: a read with a body. The parts beyond that headline are what make it worth adopting, especially the body-aware caching and the stored-query pattern via &lt;code&gt;Location&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The ecosystem is still catching up. Middleboxes and CDN cache keys will take a while, and OpenAPI has no way to describe QUERY endpoints yet. But the standard is done and the first frameworks have shipped, and keeping a POST fallback around costs almost nothing. Next time you're about to add a &lt;code&gt;POST /search&lt;/code&gt; endpoint, try &lt;code&gt;QUERY&lt;/code&gt; first.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc10008.html" rel="noopener noreferrer"&gt;RFC 10008 — The HTTP QUERY Method&lt;/a&gt; (Proposed Standard, June 2026)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/" rel="noopener noreferrer"&gt;IETF datatracker history — draft-ietf-httpbis-safe-method-w-body&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html" rel="noopener noreferrer"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt; (method properties, conditional requests)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc9651.html" rel="noopener noreferrer"&gt;RFC 9651 — Structured Field Values&lt;/a&gt; (the &lt;code&gt;Accept-Query&lt;/code&gt; syntax)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>http</category>
      <category>api</category>
      <category>tutorial</category>
    </item>
    <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>
  </channel>
</rss>
